diff options
author | Vishvananda Ishaya <vishvananda@gmail.com> | 2011-01-05 08:05:11 +0000 |
---|---|---|
committer | Vishvananda Ishaya <vishvananda@gmail.com> | 2011-01-05 08:05:11 +0000 |
commit | 1cc5e933ccc29a88d09d2050e5224ee27eda767c (patch) | |
tree | 3139253271784ac1eaaf429d9097081ac5838186 | |
parent | dd1e36b9690a2c2de18c565c496b25295a13d0aa (diff) | |
download | nova-1cc5e933ccc29a88d09d2050e5224ee27eda767c.tar.gz nova-1cc5e933ccc29a88d09d2050e5224ee27eda767c.tar.xz nova-1cc5e933ccc29a88d09d2050e5224ee27eda767c.zip |
stop using partitions and first pass at cow images
-rw-r--r-- | nova/compute/disk.py | 108 | ||||
-rw-r--r-- | nova/virt/libvirt.xml.template | 6 | ||||
-rw-r--r-- | nova/virt/libvirt_conn.py | 110 |
3 files changed, 139 insertions, 85 deletions
diff --git a/nova/compute/disk.py b/nova/compute/disk.py index 814a258cd..766f27d35 100644 --- a/nova/compute/disk.py +++ b/nova/compute/disk.py @@ -28,6 +28,7 @@ import tempfile from nova import exception from nova import flags +from nova import utils FLAGS = flags.FLAGS @@ -37,8 +38,7 @@ 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', execute=None): +def partition(infile, outfile, local_bytes=0, resize=True, local_type='ext2'): """ Turns a partition (infile) into a bootable drive image (outfile). @@ -61,10 +61,10 @@ def partition(infile, outfile, local_bytes=0, resize=True, file_size = os.path.getsize(infile) if resize and file_size < FLAGS.minimum_root_size: last_sector = FLAGS.minimum_root_size / sector_size - 1 - execute('dd if=/dev/zero of=%s count=1 seek=%d bs=%d' + utils.execute('dd if=/dev/zero of=%s count=1 seek=%d bs=%d' % (infile, last_sector, sector_size)) - execute('e2fsck -fp %s' % infile, check_exit_code=False) - execute('resize2fs %s' % infile) + 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: logging.warn(_("Input partition size not evenly divisible by" @@ -83,37 +83,37 @@ def partition(infile, outfile, local_bytes=0, resize=True, last_sector = local_last # e # create an empty file - execute('dd if=/dev/zero of=%s count=1 seek=%d bs=%d' + utils.execute('dd if=/dev/zero of=%s count=1 seek=%d bs=%d' % (outfile, mbr_last, sector_size)) # make mbr partition - execute('parted --script %s mklabel msdos' % outfile) + utils.execute('parted --script %s mklabel msdos' % outfile) # append primary file - execute('dd if=%s of=%s bs=%s conv=notrunc,fsync oflag=append' + utils.execute('dd if=%s of=%s bs=%s conv=notrunc,fsync oflag=append' % (infile, outfile, FLAGS.block_size)) # make primary partition - execute('parted --script %s mkpart primary %ds %ds' + utils.execute('parted --script %s mkpart primary %ds %ds' % (outfile, primary_first, primary_last)) if local_bytes > 0: # make the file bigger - execute('dd if=/dev/zero of=%s count=1 seek=%d bs=%d' + utils.execute('dd if=/dev/zero of=%s count=1 seek=%d bs=%d' % (outfile, last_sector, sector_size)) # make and format local partition - execute('parted --script %s mkpartfs primary %s %ds %ds' + utils.execute('parted --script %s mkpartfs primary %s %ds %ds' % (outfile, local_type, local_first, local_last)) -def extend(image, size, execute): +def extend(image, size): file_size = os.path.getsize(image) if file_size >= size: return - return execute('truncate -s size %s' % (image,)) + return utils.execute('truncate -s size %s' % (image,)) -def inject_data(image, key=None, net=None, partition=None, execute=None): +def inject_data(image, key=None, net=None, partition=None): """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 @@ -122,15 +122,11 @@ def inject_data(image, key=None, net=None, partition=None, execute=None): If partition is not specified it mounts the image as a single partition. """ - out, err = execute('sudo losetup --find --show %s' % image) - if err: - raise exception.Error(_('Could not attach image to loopback: %s') - % err) - device = out.strip() + device = _link_device(image) try: if not partition is None: # create partition - out, err = execute('sudo kpartx -a %s' % device) + 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], @@ -146,12 +142,12 @@ def inject_data(image, key=None, net=None, partition=None, execute=None): mapped_device) # Configure ext2fs so that it doesn't auto-check every N boots - out, err = execute('sudo tune2fs -c 0 -i 0 %s' % mapped_device) + out, err = utils.execute('sudo tune2fs -c 0 -i 0 %s' % mapped_device) tmpdir = tempfile.mkdtemp() try: # mount loopback to dir - out, err = execute( + out, err = utils.execute( 'sudo mount %s %s' % (mapped_device, tmpdir)) if err: raise exception.Error(_('Failed to mount filesystem: %s') @@ -160,45 +156,79 @@ def inject_data(image, key=None, net=None, partition=None, execute=None): try: if key: # inject key file - _inject_key_into_fs(key, tmpdir, execute=execute) + _inject_key_into_fs(key, tmpdir) if net: - _inject_net_into_fs(net, tmpdir, execute=execute) + _inject_net_into_fs(net, tmpdir) finally: # unmount device - execute('sudo umount %s' % mapped_device) + utils.execute('sudo umount %s' % mapped_device) finally: # remove temporary directory - execute('rmdir %s' % tmpdir) + utils.execute('rmdir %s' % tmpdir) if not partition is None: # remove partitions - execute('sudo kpartx -d %s' % device) + utils.execute('sudo kpartx -d %s' % device) finally: - # remove loopback - execute('sudo losetup --detach %s' % device) + _unlink_device(image, device) -def _inject_key_into_fs(key, fs, execute=None): +def _link_device(image): + if FLAGS.use_cow_images: + device = _allocate_device() + utils.execute('sudo qemu-nbd --connect=%s %s' % (device, image)) + 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(image, device): + if FLAGS.use_cow_images: + utils.execute('sudo qemu-nbd --disconnect %s' % image) + _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') - execute('sudo mkdir -p %s' % sshdir) # existing dir doesn't matter - execute('sudo chown root %s' % sshdir) - execute('sudo chmod 700 %s' % sshdir) + 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') - execute('sudo tee -a %s' % keyfile, '\n' + key.strip() + '\n') + utils.execute('sudo tee -a %s' % keyfile, '\n' + key.strip() + '\n') -def _inject_net_into_fs(net, fs, execute=None): +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') - execute('sudo mkdir -p %s' % netdir) # existing dir doesn't matter - execute('sudo chown root:root %s' % netdir) - execute('sudo chmod 755 %s' % netdir) + 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') - execute('sudo tee %s' % netfile, net) + utils.execute('sudo tee %s' % netfile, net) diff --git a/nova/virt/libvirt.xml.template b/nova/virt/libvirt.xml.template index 3fb2243da..d6d9d3de6 100644 --- a/nova/virt/libvirt.xml.template +++ b/nova/virt/libvirt.xml.template @@ -58,6 +58,12 @@ <source file='${basepath}/disk'/> <target dev='${disk_prefix}a' bus='${disk_bus}'/> </disk> + #if $getVar('local', False) + <disk type='file'> + <source file='${basepath}/local'/> + <target dev='${disk_prefix}b' bus='${disk_bus}'/> + </disk> + #end if #end if <interface type='bridge'> <source bridge='${bridge_name}'/> diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 00edfbdc8..883913926 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -85,6 +85,9 @@ flags.DEFINE_string('libvirt_uri', flags.DEFINE_bool('allow_project_net_traffic', True, 'Whether to allow in project network traffic') +flags.DEFINE_bool('use_cow_images', + True, + 'Whether to use cow images') def get_connection(read_only): @@ -418,19 +421,50 @@ class LibvirtConnection(object): return self._dump_file(fpath) + def _get_image(self, image_id, target, user, project, size=None): + if not os.path.exists(target): + if FLAGS.use_cow_images: + base = os.path.join(FLAGS.instances_path, '_base') + if not os.path.exists(base): + images.fetch(image_id, base, user, project) + if size: + # TODO(vish): Attempt to resize the filesystem + disk.extend(base, size) + utils.execute('qemu-img create -f qcow2 -o ' + 'cluster_size=2M,backing_file=%s %s' + % (base, target)) + else: + images.fetch(image_id, target, user, project) + if size: + # TODO(vish): Attempt to resize the filesystem + disk.extend(target, size) + + def _get_local(self, local_gb, target): + if not os.path.exists(target): + last_mb = local_gb * 1024 - 1 + if FLAGS.use_cow_images: + base = os.path.join(FLAGS.instances_path, '_base') + if not os.path.exists(base): + utils.execute('dd if=/dev/zero of=%s bs=1M count=1' + 'seek=%s' % (base, last_mb)) + utils.execute('qemu-img create -f qcow2 -o ' + 'cluster_size=2M,backing_file=%s %s' + % (base, target)) + else: + utils.execute('dd if=/dev/zero of=%s bs=1M count=1' + 'seek=%s' % (base, last_mb)) + def _create_image(self, inst, libvirt_xml, prefix='', disk_images=None): # syntactic nicety - basepath = lambda fname = '', prefix = prefix: os.path.join( - FLAGS.instances_path, - inst['name'], - prefix + fname) + def basepath(fname='', prefix=prefix): + return os.path.join(FLAGS.instances_path, + inst['name'], + prefix + fname) # ensure directories exist and are writable utils.execute('mkdir -p %s' % basepath(prefix='')) utils.execute('chmod 0777 %s' % basepath(prefix='')) - # TODO(termie): these are blocking calls, it would be great - # if they weren't. logging.info(_('instance %s: Creating image'), inst['name']) f = open(basepath('libvirt.xml'), 'w') f.write(libvirt_xml) @@ -447,23 +481,26 @@ class LibvirtConnection(object): disk_images = {'image_id': inst['image_id'], 'kernel_id': inst['kernel_id'], 'ramdisk_id': inst['ramdisk_id']} - if not os.path.exists(basepath('disk')): - images.fetch(inst.image_id, basepath('disk-raw'), user, - project) - - if inst['kernel_id']: - if not os.path.exists(basepath('kernel')): - images.fetch(inst['kernel_id'], basepath('kernel'), - user, project) - if inst['ramdisk_id']: - if not os.path.exists(basepath('ramdisk')): - images.fetch(inst['ramdisk_id'], basepath('ramdisk'), - user, project) - - def execute(cmd, process_input=None, check_exit_code=True): - return utils.execute(cmd=cmd, - process_input=process_input, - check_exit_code=check_exit_code) + + if disk_images['kernel_id']: + self._get_image(disk_images['kernel_id'], basepath('kernel'), + user, project) + if disk_images['ramdisk_id']: + self._get_image(disk_images['ramdisk_id'], basepath('ramdisk'), + user, project) + + + size = FLAGS.minimum_root_size + if not FLAGS.use_cow_images: + if inst['instance_type'] == 'm1.tiny' or prefix == 'rescue-': + size = None + + self._get_image(disk_images['image_id'], basepath('disk'), + user, project, size) + + type_data = instance_types.INSTANCE_TYPES[inst['instance_type']] + self._get_local(type_data['local_gb'], basepath('local'), + user, project, size) # For now, we assume that if we're not using a kernel, we're using a # partitioned disk image where the target partition is the first @@ -493,34 +530,14 @@ class LibvirtConnection(object): logging.info(_('instance %s: injecting net into image %s'), inst['name'], inst.image_id) try: - disk.inject_data(basepath('disk-raw'), key, net, - partition=target_partition, - execute=execute) + disk.inject_data(basepath('disk'), key, net, + partition=target_partition) except Exception as e: # This could be a windows image, or a vmdk format disk logging.warn(_('instance %s: ignoring error injecting data' ' into image %s (%s)'), inst['name'], inst.image_id, e) - if inst['kernel_id']: - if os.path.exists(basepath('disk')): - utils.execute('rm -f %s' % basepath('disk')) - - local_bytes = (instance_types.INSTANCE_TYPES[inst.instance_type] - ['local_gb'] - * 1024 * 1024 * 1024) - - resize = True - if inst['instance_type'] == 'm1.tiny' or prefix == 'rescue-': - resize = False - - if inst['kernel_id']: - disk.partition(basepath('disk-raw'), basepath('disk'), - local_bytes, resize, execute=execute) - else: - os.rename(basepath('disk-raw'), basepath('disk')) - disk.extend(basepath('disk'), local_bytes, execute=execute) - if FLAGS.libvirt_type == 'uml': utils.execute('sudo chown root %s' % basepath('disk')) @@ -558,7 +575,8 @@ class LibvirtConnection(object): 'ip_address': ip_address, 'dhcp_server': dhcp_server, 'extra_params': extra_params, - 'rescue': rescue} + 'rescue': rescue, + 'local': instance_type['local_gb']} if not rescue: if instance['kernel_id']: xml_info['kernel'] = xml_info['basepath'] + "/kernel" |