From 914b0554a092d2b38f292942dc4d7ddea5d99b9a Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 13 Jan 2011 15:13:31 -0800 Subject: Modified per sorens review. Moved disk.py Removed disk.partition Changed docstrings Use pid to find nbd devices --- nova/compute/disk.py | 247 ---------------------------------------------- nova/virt/disk.py | 186 ++++++++++++++++++++++++++++++++++ nova/virt/libvirt_conn.py | 10 +- 3 files changed, 190 insertions(+), 253 deletions(-) delete mode 100644 nova/compute/disk.py create mode 100644 nova/virt/disk.py diff --git a/nova/compute/disk.py b/nova/compute/disk.py deleted file mode 100644 index b9c34750d..000000000 --- a/nova/compute/disk.py +++ /dev/null @@ -1,247 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -""" -Utility methods to resize, repartition, and modify disk images. - -Includes injection of SSH PGP keys into authorized_keys file. - -""" - -import os -import tempfile -import time - -from nova import exception -from nova import flags -from nova import log as logging -from nova import utils - - -LOG = logging.getLogger('nova.compute.disk') -FLAGS = flags.FLAGS -flags.DEFINE_integer('minimum_root_size', 1024 * 1024 * 1024 * 10, - 'minimum size in bytes of root partition') -flags.DEFINE_integer('block_size', 1024 * 1024 * 256, - 'block_size to use for dd') - - -def partition(infile, outfile, local_bytes=0, resize=True, local_type='ext2'): - """ - Turns a partition (infile) into a bootable drive image (outfile). - - The first 63 sectors (0-62) of the resulting image is a master boot record. - Infile becomes the first primary partition. - If local bytes is specified, a second primary partition is created and - formatted as ext2. - - :: - - In the diagram below, dashes represent drive sectors. - +-----+------. . .-------+------. . .------+ - | 0 a| b c|d e| - +-----+------. . .-------+------. . .------+ - | mbr | primary partiton | local partition | - +-----+------. . .-------+------. . .------+ - - """ - sector_size = 512 - file_size = os.path.getsize(infile) - if resize and file_size < FLAGS.minimum_root_size: - last_sector = FLAGS.minimum_root_size / sector_size - 1 - utils.execute('dd if=/dev/zero of=%s count=1 seek=%d bs=%d' - % (infile, last_sector, sector_size)) - utils.execute('e2fsck -fp %s' % infile, check_exit_code=False) - utils.execute('resize2fs %s' % infile) - file_size = FLAGS.minimum_root_size - elif file_size % sector_size != 0: - LOG.warn(_("Input partition size not evenly divisible by" - " sector size: %d / %d"), file_size, sector_size) - primary_sectors = file_size / sector_size - if local_bytes % sector_size != 0: - LOG.warn(_("Bytes for local storage not evenly divisible" - " by sector size: %d / %d"), local_bytes, sector_size) - local_sectors = local_bytes / sector_size - - mbr_last = 62 # a - primary_first = mbr_last + 1 # b - primary_last = primary_first + primary_sectors - 1 # c - local_first = primary_last + 1 # d - local_last = local_first + local_sectors - 1 # e - last_sector = local_last # e - - # create an empty file - utils.execute('dd if=/dev/zero of=%s count=1 seek=%d bs=%d' - % (outfile, mbr_last, sector_size)) - - # make mbr partition - utils.execute('parted --script %s mklabel msdos' % outfile) - - # append primary file - utils.execute('dd if=%s of=%s bs=%s conv=notrunc,fsync oflag=append' - % (infile, outfile, FLAGS.block_size)) - - # make primary partition - utils.execute('parted --script %s mkpart primary %ds %ds' - % (outfile, primary_first, primary_last)) - - if local_bytes > 0: - # make the file bigger - utils.execute('dd if=/dev/zero of=%s count=1 seek=%d bs=%d' - % (outfile, last_sector, sector_size)) - # make and format local partition - utils.execute('parted --script %s mkpartfs primary %s %ds %ds' - % (outfile, local_type, local_first, local_last)) - - -def extend(image, size): - """Increase image to size""" - file_size = os.path.getsize(image) - if file_size >= size: - return - utils.execute('truncate -s %s %s' % (size, image)) - # NOTE(vish): attempts to resize filesystem - utils.execute('e2fsck -fp %s' % image, check_exit_code=False) - utils.execute('resize2fs %s' % image, check_exit_code=False) - - -def inject_data(image, key=None, net=None, partition=None, nbd=False): - """Injects a ssh key and optionally net data into a disk image. - - it will mount the image as a fully partitioned disk and attempt to inject - into the specified partition number. - - If partition is not specified it mounts the image as a single partition. - - """ - device = _link_device(image, nbd) - try: - if not partition is None: - # create partition - out, err = utils.execute('sudo kpartx -a %s' % device) - if err: - raise exception.Error(_('Failed to load partition: %s') % err) - mapped_device = '/dev/mapper/%sp%s' % (device.split('/')[-1], - partition) - else: - mapped_device = device - - # We can only loopback mount raw images. If the device isn't there, - # it's normally because it's a .vmdk or a .vdi etc - if not os.path.exists(mapped_device): - raise exception.Error('Mapped device was not found (we can' - ' only inject raw disk images): %s' % - mapped_device) - - # Configure ext2fs so that it doesn't auto-check every N boots - out, err = utils.execute('sudo tune2fs -c 0 -i 0 %s' % mapped_device) - - tmpdir = tempfile.mkdtemp() - try: - # mount loopback to dir - out, err = utils.execute( - 'sudo mount %s %s' % (mapped_device, tmpdir)) - if err: - raise exception.Error(_('Failed to mount filesystem: %s') - % err) - - try: - if key: - # inject key file - _inject_key_into_fs(key, tmpdir) - if net: - _inject_net_into_fs(net, tmpdir) - finally: - # unmount device - utils.execute('sudo umount %s' % mapped_device) - finally: - # remove temporary directory - utils.execute('rmdir %s' % tmpdir) - if not partition is None: - # remove partitions - utils.execute('sudo kpartx -d %s' % device) - finally: - _unlink_device(device, nbd) - - -def _link_device(image, nbd): - """Link image to device using loopback or nbd""" - if nbd: - device = _allocate_device() - utils.execute('sudo qemu-nbd -c %s %s' % (device, image)) - # NOTE(vish): this forks into another process, so give it a chance - # to set up before continuuing - time.sleep(1) - return device - else: - out, err = utils.execute('sudo losetup --find --show %s' % image) - if err: - raise exception.Error(_('Could not attach image to loopback: %s') - % err) - return out.strip() - - -def _unlink_device(device, nbd): - """Unlink image from device using loopback or nbd""" - if nbd: - utils.execute('sudo qemu-nbd -d %s' % device) - _free_device(device) - else: - utils.execute('sudo losetup --detach %s' % device) - - -_DEVICES = ['/dev/nbd%s' % i for i in xrange(16)] - - -def _allocate_device(): - # NOTE(vish): This assumes no other processes are using nbd devices. - # It will race cause a race condition if multiple - # workers are running on a given machine. - if not _DEVICES: - raise exception.Error(_('No free nbd devices')) - return _DEVICES.pop() - - -def _free_device(device): - _DEVICES.append(device) - - -def _inject_key_into_fs(key, fs): - """Add the given public ssh key to root's authorized_keys. - - key is an ssh key string. - fs is the path to the base of the filesystem into which to inject the key. - """ - sshdir = os.path.join(fs, 'root', '.ssh') - utils.execute('sudo mkdir -p %s' % sshdir) # existing dir doesn't matter - utils.execute('sudo chown root %s' % sshdir) - utils.execute('sudo chmod 700 %s' % sshdir) - keyfile = os.path.join(sshdir, 'authorized_keys') - utils.execute('sudo tee -a %s' % keyfile, '\n' + key.strip() + '\n') - - -def _inject_net_into_fs(net, fs): - """Inject /etc/network/interfaces into the filesystem rooted at fs. - - net is the contents of /etc/network/interfaces. - """ - netdir = os.path.join(os.path.join(fs, 'etc'), 'network') - utils.execute('sudo mkdir -p %s' % netdir) # existing dir doesn't matter - utils.execute('sudo chown root:root %s' % netdir) - utils.execute('sudo chmod 755 %s' % netdir) - netfile = os.path.join(netdir, 'interfaces') - utils.execute('sudo tee %s' % netfile, net) diff --git a/nova/virt/disk.py b/nova/virt/disk.py new file mode 100644 index 000000000..c5565abfa --- /dev/null +++ b/nova/virt/disk.py @@ -0,0 +1,186 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" +Utility methods to resize, repartition, and modify disk images. + +Includes injection of SSH PGP keys into authorized_keys file. + +""" + +import os +import tempfile +import time + +from nova import exception +from nova import flags +from nova import log as logging +from nova import utils + + +LOG = logging.getLogger('nova.compute.disk') +FLAGS = flags.FLAGS +flags.DEFINE_integer('minimum_root_size', 1024 * 1024 * 1024 * 10, + 'minimum size in bytes of root partition') +flags.DEFINE_integer('block_size', 1024 * 1024 * 256, + 'block_size to use for dd') + + +def extend(image, size): + """Increase image to size""" + file_size = os.path.getsize(image) + if file_size >= size: + return + utils.execute('truncate -s %s %s' % (size, image)) + # NOTE(vish): attempts to resize filesystem + utils.execute('e2fsck -fp %s' % image, check_exit_code=False) + utils.execute('resize2fs %s' % image, check_exit_code=False) + + +def inject_data(image, key=None, net=None, partition=None, nbd=False): + """Injects a ssh key and optionally net data into a disk image. + + it will mount the image as a fully partitioned disk and attempt to inject + into the specified partition number. + + If partition is not specified it mounts the image as a single partition. + + """ + device = _link_device(image, nbd) + try: + if not partition is None: + # create partition + out, err = utils.execute('sudo kpartx -a %s' % device) + if err: + raise exception.Error(_('Failed to load partition: %s') % err) + mapped_device = '/dev/mapper/%sp%s' % (device.split('/')[-1], + partition) + else: + mapped_device = device + + # We can only loopback mount raw images. If the device isn't there, + # it's normally because it's a .vmdk or a .vdi etc + if not os.path.exists(mapped_device): + raise exception.Error('Mapped device was not found (we can' + ' only inject raw disk images): %s' % + mapped_device) + + # Configure ext2fs so that it doesn't auto-check every N boots + out, err = utils.execute('sudo tune2fs -c 0 -i 0 %s' % mapped_device) + + tmpdir = tempfile.mkdtemp() + try: + # mount loopback to dir + out, err = utils.execute( + 'sudo mount %s %s' % (mapped_device, tmpdir)) + if err: + raise exception.Error(_('Failed to mount filesystem: %s') + % err) + + try: + if key: + # inject key file + _inject_key_into_fs(key, tmpdir) + if net: + _inject_net_into_fs(net, tmpdir) + finally: + # unmount device + utils.execute('sudo umount %s' % mapped_device) + finally: + # remove temporary directory + utils.execute('rmdir %s' % tmpdir) + if not partition is None: + # remove partitions + utils.execute('sudo kpartx -d %s' % device) + finally: + _unlink_device(device, nbd) + + +def _link_device(image, nbd): + """Link image to device using loopback or nbd""" + if nbd: + device = _allocate_device() + utils.execute('sudo qemu-nbd -c %s %s' % (device, image)) + # NOTE(vish): this forks into another process, so give it a chance + # to set up before continuuing + for i in xrange(10): + if os.path.exists("/sys/block/%s/pid" % os.path.basename(device)): + return device + time.sleep(1) + raise exception.Error(_('nbd device %s did not show up') % device) + else: + out, err = utils.execute('sudo losetup --find --show %s' % image) + if err: + raise exception.Error(_('Could not attach image to loopback: %s') + % err) + return out.strip() + + +def _unlink_device(device, nbd): + """Unlink image from device using loopback or nbd""" + if nbd: + utils.execute('sudo qemu-nbd -d %s' % device) + _free_device(device) + else: + utils.execute('sudo losetup --detach %s' % device) + + +_DEVICES = ['/dev/nbd%s' % i for i in xrange(16)] + + +def _allocate_device(): + # NOTE(vish): This assumes no other processes are allocating nbd devices. + # It may race cause a race condition if multiple + # workers are running on a given machine. + while True: + if not _DEVICES: + raise exception.Error(_('No free nbd devices')) + device = _DEVICES.pop() + if not os.path.exists("/sys/block/%s/pid" % os.path.basename(device)): + break + return device + + +def _free_device(device): + _DEVICES.append(device) + + +def _inject_key_into_fs(key, fs): + """Add the given public ssh key to root's authorized_keys. + + key is an ssh key string. + fs is the path to the base of the filesystem into which to inject the key. + """ + sshdir = os.path.join(fs, 'root', '.ssh') + utils.execute('sudo mkdir -p %s' % sshdir) # existing dir doesn't matter + utils.execute('sudo chown root %s' % sshdir) + utils.execute('sudo chmod 700 %s' % sshdir) + keyfile = os.path.join(sshdir, 'authorized_keys') + utils.execute('sudo tee -a %s' % keyfile, '\n' + key.strip() + '\n') + + +def _inject_net_into_fs(net, fs): + """Inject /etc/network/interfaces into the filesystem rooted at fs. + + net is the contents of /etc/network/interfaces. + """ + netdir = os.path.join(os.path.join(fs, 'etc'), 'network') + utils.execute('sudo mkdir -p %s' % netdir) # existing dir doesn't matter + utils.execute('sudo chown root:root %s' % netdir) + utils.execute('sudo chmod 755 %s' % netdir) + netfile = os.path.join(netdir, 'interfaces') + utils.execute('sudo tee %s' % netfile, net) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 760bfef78..c77d25bfd 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -58,9 +58,9 @@ from nova import log as logging from nova import utils #from nova.api import context from nova.auth import manager -from nova.compute import disk from nova.compute import instance_types from nova.compute import power_state +from nova.virt import disk from nova.virt import images libvirt = None @@ -495,7 +495,7 @@ class LibvirtConnection(object): return {'token': token, 'host': host, 'port': port} def _cache_image(self, fn, target, fname, cow=False, *args, **kwargs): - """Wrapper to cache a method that creates an image. + """Wrapper for a method that creates an image that caches the image. This wrapper will save the image into a common store and create a copy for use by the hypervisor. @@ -504,7 +504,7 @@ class LibvirtConnection(object): where the image will be saved. fname is used as the filename of the base image. The filename needs - to be fname to a given image. + to be unique to a given image. If cow is True, it will make a CoW image instead of a copy. """ @@ -531,9 +531,7 @@ class LibvirtConnection(object): def _create_local(self, target, local_gb): """Create a blank image of specified size""" - last_mb = local_gb * 1024 - 1 - utils.execute('dd if=/dev/zero of=%s bs=1M count=1 ' - 'seek=%s' % (target, last_mb)) + utils.execute('truncate %s -s %dG' % (target, local_gb)) # TODO(vish): should we format disk by default? def _create_image(self, inst, libvirt_xml, prefix='', disk_images=None): -- cgit