From 1cc5e933ccc29a88d09d2050e5224ee27eda767c Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 5 Jan 2011 08:05:11 +0000 Subject: [PATCH 1/6] stop using partitions and first pass at cow images --- nova/compute/disk.py | 108 +++++++++++++++++++++------------ nova/virt/libvirt.xml.template | 6 ++ nova/virt/libvirt_conn.py | 106 ++++++++++++++++++-------------- 3 files changed, 137 insertions(+), 83 deletions(-) diff --git a/nova/compute/disk.py b/nova/compute/disk.py index 814a258cd61e..766f27d3523c 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 3fb2243dacbd..d6d9d3de62dd 100644 --- a/nova/virt/libvirt.xml.template +++ b/nova/virt/libvirt.xml.template @@ -58,6 +58,12 @@ + #if $getVar('local', False) + + + + + #end if #end if diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 00edfbdc8ddd..883913926ae1 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) + 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) - 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) + + 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" From b5f8ab0e913c121a80ff0efe358960099e7c87f8 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 5 Jan 2011 19:16:17 +0000 Subject: [PATCH 2/6] fix injection and xml --- contrib/nova.sh | 5 +- nova/compute/disk.py | 9 +-- nova/virt/libvirt.xml.template | 10 ++- nova/virt/libvirt_conn.py | 107 ++++++++++++++++++++++----------- 4 files changed, 88 insertions(+), 43 deletions(-) diff --git a/contrib/nova.sh b/contrib/nova.sh index da1ba030c3f1..80bf6789b98b 100755 --- a/contrib/nova.sh +++ b/contrib/nova.sh @@ -82,9 +82,10 @@ if [ "$CMD" == "install" ]; then sudo /etc/init.d/iscsitarget restart sudo modprobe kvm sudo /etc/init.d/libvirt-bin restart + sudo modprobe nbd sudo apt-get install -y python-twisted python-sqlalchemy python-mox python-greenlet python-carrot - sudo apt-get install -y python-daemon python-eventlet python-gflags python-tornado python-ipy - sudo apt-get install -y python-libvirt python-libxml2 python-routes + sudo apt-get install -y python-daemon python-eventlet python-gflags python-ipy + sudo apt-get install -y python-libvirt python-libxml2 python-routes python-cheetah if [ "$USE_MYSQL" == 1 ]; then cat <= size: return - return utils.execute('truncate -s size %s' % (image,)) + return utils.execute('truncate -s %s %s' % (size, image)) def inject_data(image, key=None, net=None, partition=None): @@ -175,7 +175,8 @@ def inject_data(image, key=None, net=None, partition=None): def _link_device(image): if FLAGS.use_cow_images: device = _allocate_device() - utils.execute('sudo qemu-nbd --connect=%s %s' % (device, image)) + utils.execute('sudo qemu-nbd -c %s %s' % (device, image)) + return device else: out, err = utils.execute('sudo losetup --find --show %s' % image) if err: @@ -184,9 +185,9 @@ def _link_device(image): return out.strip() -def _unlink_device(image, device): +def _unlink_device(device): if FLAGS.use_cow_images: - utils.execute('sudo qemu-nbd --disconnect %s' % image) + utils.execute('sudo qemu-nbd -d %s' % device) _free_device(device) else: utils.execute('sudo losetup --detach %s' % device) diff --git a/nova/virt/libvirt.xml.template b/nova/virt/libvirt.xml.template index d6d9d3de62dd..995d8f4698f2 100644 --- a/nova/virt/libvirt.xml.template +++ b/nova/virt/libvirt.xml.template @@ -7,13 +7,13 @@ #set $disk_bus = 'uml' uml /usr/bin/linux - /dev/ubda1 + /dev/ubda #else #if $type == 'xen' #set $disk_prefix = 'sd' #set $disk_bus = 'scsi' linux - /dev/xvda1 + /dev/xvda #else #set $disk_prefix = 'vd' #set $disk_bus = 'virtio' @@ -28,7 +28,7 @@ #if $type == 'xen' ro #else - root=/dev/vda1 console=ttyS0 + root=/dev/vda console=ttyS0 #end if #if $getVar('ramdisk', None) ${ramdisk} @@ -46,20 +46,24 @@ #if $getVar('rescue', False) + + #else + #if $getVar('local', False) + diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 883913926ae1..ae725b766a74 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -118,6 +118,32 @@ def _get_net_and_mask(cidr): return str(net.net()), str(net.netmask()) +def wrap_cow_image(key): + def _decorator(retrieve_fn): + def _wrap(*args, **kwargs): + target = kwargs['target'] + if not os.path.exists(target): + if FLAGS.use_cow_images: + base_dir = os.path.join(FLAGS.instances_path, '_base') + if not os.path.exists(base_dir): + os.mkdir(base_dir) + os.chmod(base_dir, 0777) + base = os.path.join(base_dir, str(kwargs[key])) + if not os.path.exists(base): + kwargs['target'] = base + retrieve_fn(*args, **kwargs) + if kwargs.get('cow'): + utils.execute('qemu-img create -f qcow2 -o ' + 'cluster_size=2M,backing_file=%s %s' + % (base, target)) + else: + utils.execute('cp %s %s' % (base, target)) + else: + retrieve_fn(*args, **kwargs) + _wrap.func_name = retrieve_fn.func_name + return _wrap + return _decorator + class LibvirtConnection(object): def __init__(self, read_only): @@ -421,38 +447,36 @@ class LibvirtConnection(object): return self._dump_file(fpath) - def _get_image(self, image_id, target, user, project, size=None): + def _get_image(self, retrieve_fn, key, target, *args, **kwargs): if not os.path.exists(target): if FLAGS.use_cow_images: - base = os.path.join(FLAGS.instances_path, '_base') + base_dir = os.path.join(FLAGS.instances_path, '_base') + if not os.path.exists(base_dir): + os.mkdir(base_dir) + os.chmod(base_dir, 0777) + base = os.path.join(base_dir, kwargs[key]) 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) + retrieve_fn(target=base, *args, **kwargs) 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) + retrieve_fn(target=base, *args, **kwargs) - 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)) + @wrap_cow_image('image_id') + def _fetch_image(self, target, image_id, user, project, + size=None, **kwargs): + images.fetch(image_id, target, user, project) + # TODO(vish): resize filesystem + if size: + disk.extend(target, size) + + @wrap_cow_image('local_gb') + def _create_local(self, target, local_gb): + last_mb = local_gb * 1024 - 1 + utils.execute('dd if=/dev/zero of=%s bs=1M count=1' + 'seek=%s' % (target, last_mb)) + # TODO(vish): format disk def _create_image(self, inst, libvirt_xml, prefix='', disk_images=None): # syntactic nicety @@ -483,11 +507,15 @@ class LibvirtConnection(object): 'ramdisk_id': inst['ramdisk_id']} if disk_images['kernel_id']: - self._get_image(disk_images['kernel_id'], basepath('kernel'), - user, project) + self._fetch_image(target=basepath('kernel'), + image_id=disk_images['kernel_id'], + user=user, + project=project) if disk_images['ramdisk_id']: - self._get_image(disk_images['ramdisk_id'], basepath('ramdisk'), - user, project) + self._fetch_image(target=basepath('ramdisk'), + image_id=disk_images['ramdisk_id'], + user=user, + project=project) size = FLAGS.minimum_root_size @@ -495,12 +523,17 @@ class LibvirtConnection(object): if inst['instance_type'] == 'm1.tiny' or prefix == 'rescue-': size = None - self._get_image(disk_images['image_id'], basepath('disk'), - user, project, size) - + self._fetch_image(target=basepath('disk'), + image_id=disk_images['image_id'], + user=user, + project=project, + size=size, + cow=True) type_data = instance_types.INSTANCE_TYPES[inst['instance_type']] - self._get_local(type_data['local_gb'], basepath('local'), - user, project, size) + + if type_data['local_gb']: + self._create_local(target=basepath('local'), + local_gb=type_data['local_gb']) # 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 @@ -534,6 +567,7 @@ class LibvirtConnection(object): partition=target_partition) except Exception as e: # This could be a windows image, or a vmdk format disk + logging.exception('test') logging.warn(_('instance %s: ignoring error injecting data' ' into image %s (%s)'), inst['name'], inst.image_id, e) @@ -563,6 +597,10 @@ class LibvirtConnection(object): "value=\"%s\" />\n") % (net, mask) else: extra_params = "\n" + if FLAGS.use_cow_images: + driver_type = 'qcow2' + else: + driver_type = 'raw' xml_info = {'type': FLAGS.libvirt_type, 'name': instance['name'], @@ -576,7 +614,8 @@ class LibvirtConnection(object): 'dhcp_server': dhcp_server, 'extra_params': extra_params, 'rescue': rescue, - 'local': instance_type['local_gb']} + 'local': instance_type['local_gb'], + 'driver_type': driver_type} if not rescue: if instance['kernel_id']: xml_info['kernel'] = xml_info['basepath'] + "/kernel" From f1f292a787ba20134c007da087bd9585d1875e86 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 5 Jan 2011 19:50:39 +0000 Subject: [PATCH 3/6] more fixes, docstrings --- nova/compute/disk.py | 18 +++++---- nova/virt/libvirt_conn.py | 83 +++++++++++++++++++-------------------- 2 files changed, 51 insertions(+), 50 deletions(-) diff --git a/nova/compute/disk.py b/nova/compute/disk.py index f640bdbbb141..ac0d689d597b 100644 --- a/nova/compute/disk.py +++ b/nova/compute/disk.py @@ -107,13 +107,15 @@ def partition(infile, outfile, local_bytes=0, resize=True, local_type='ext2'): def extend(image, size): + """Increase image to size""" file_size = os.path.getsize(image) if file_size >= size: return + # TODO(vish): attempt to resize filesystem return utils.execute('truncate -s %s %s' % (size, image)) -def inject_data(image, key=None, net=None, partition=None): +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 @@ -122,7 +124,7 @@ def inject_data(image, key=None, net=None, partition=None): If partition is not specified it mounts the image as a single partition. """ - device = _link_device(image) + device = _link_device(image, nbd) try: if not partition is None: # create partition @@ -169,11 +171,12 @@ def inject_data(image, key=None, net=None, partition=None): # remove partitions utils.execute('sudo kpartx -d %s' % device) finally: - _unlink_device(image, device) + _unlink_device(device, nbd) -def _link_device(image): - if FLAGS.use_cow_images: +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)) return device @@ -185,8 +188,9 @@ def _link_device(image): return out.strip() -def _unlink_device(device): - if FLAGS.use_cow_images: +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: diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index ae725b766a74..95a374603ed0 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -118,28 +118,38 @@ def _get_net_and_mask(cidr): return str(net.net()), str(net.netmask()) -def wrap_cow_image(key): +def wrap_image_cache(key): + """Decorator for a method that creates an image to store it in cache. + + This wrapper will save the image into a common store and create a + copy for use by the hypervisor. + + The underlying method should be called with kwargs and specify + a kwarg of target representing where the image will be saved. The + key argument to the wrapper defines which kwarg of the underlying + method to use as the filename of the base image. The filename needs + to be unique to a given image. + + If kwargs['cow'] is True, it will make a CoW image instead of a copy. + """ def _decorator(retrieve_fn): def _wrap(*args, **kwargs): target = kwargs['target'] if not os.path.exists(target): - if FLAGS.use_cow_images: - base_dir = os.path.join(FLAGS.instances_path, '_base') - if not os.path.exists(base_dir): - os.mkdir(base_dir) - os.chmod(base_dir, 0777) - base = os.path.join(base_dir, str(kwargs[key])) - if not os.path.exists(base): - kwargs['target'] = base - retrieve_fn(*args, **kwargs) - if kwargs.get('cow'): - utils.execute('qemu-img create -f qcow2 -o ' - 'cluster_size=2M,backing_file=%s %s' - % (base, target)) - else: - utils.execute('cp %s %s' % (base, target)) - else: + base_dir = os.path.join(FLAGS.instances_path, '_base') + if not os.path.exists(base_dir): + os.mkdir(base_dir) + os.chmod(base_dir, 0777) + base = os.path.join(base_dir, str(kwargs[key])) + if not os.path.exists(base): + kwargs['target'] = base retrieve_fn(*args, **kwargs) + if kwargs.get('cow'): + utils.execute('qemu-img create -f qcow2 -o ' + 'cluster_size=2M,backing_file=%s %s' + % (base, target)) + else: + utils.execute('cp %s %s' % (base, target)) _wrap.func_name = retrieve_fn.func_name return _wrap return _decorator @@ -447,36 +457,21 @@ class LibvirtConnection(object): return self._dump_file(fpath) - def _get_image(self, retrieve_fn, key, target, *args, **kwargs): - if not os.path.exists(target): - if FLAGS.use_cow_images: - base_dir = os.path.join(FLAGS.instances_path, '_base') - if not os.path.exists(base_dir): - os.mkdir(base_dir) - os.chmod(base_dir, 0777) - base = os.path.join(base_dir, kwargs[key]) - if not os.path.exists(base): - retrieve_fn(target=base, *args, **kwargs) - utils.execute('qemu-img create -f qcow2 -o ' - 'cluster_size=2M,backing_file=%s %s' - % (base, target)) - else: - retrieve_fn(target=base, *args, **kwargs) - - @wrap_cow_image('image_id') + @wrap_image_cache('image_id') def _fetch_image(self, target, image_id, user, project, - size=None, **kwargs): + size=None, cow=False): + """Grab image and optionally attempt to resize it""" images.fetch(image_id, target, user, project) - # TODO(vish): resize filesystem if size: disk.extend(target, size) - @wrap_cow_image('local_gb') - def _create_local(self, target, local_gb): + @wrap_image_cache('local_gb') + def _create_local(self, target, local_gb, cow=False): + """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' + utils.execute('dd if=/dev/zero of=%s bs=1M count=1 ' 'seek=%s' % (target, last_mb)) - # TODO(vish): format disk + # TODO(vish): should we format disk by default? def _create_image(self, inst, libvirt_xml, prefix='', disk_images=None): # syntactic nicety @@ -528,12 +523,13 @@ class LibvirtConnection(object): user=user, project=project, size=size, - cow=True) + cow=FLAGS.use_cow_images) type_data = instance_types.INSTANCE_TYPES[inst['instance_type']] if type_data['local_gb']: self._create_local(target=basepath('local'), - local_gb=type_data['local_gb']) + local_gb=type_data['local_gb'], + cow=FLAGS.use_cow_images) # 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 @@ -564,7 +560,8 @@ class LibvirtConnection(object): inst['name'], inst.image_id) try: disk.inject_data(basepath('disk'), key, net, - partition=target_partition) + partition=target_partition, + nbd=FLAGS.use_cow_images) except Exception as e: # This could be a windows image, or a vmdk format disk logging.exception('test') From 3d30bb1706812c4e6f9c1e01b373bb076a9f7ee3 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 5 Jan 2011 19:52:55 +0000 Subject: [PATCH 4/6] pep8 cleanup --- nova/compute/disk.py | 1 + nova/virt/libvirt_conn.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/nova/compute/disk.py b/nova/compute/disk.py index ac0d689d597b..bbcd55678436 100644 --- a/nova/compute/disk.py +++ b/nova/compute/disk.py @@ -199,6 +199,7 @@ def _unlink_device(device, nbd): _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 diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 95a374603ed0..77ff281d56b9 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -154,6 +154,7 @@ def wrap_image_cache(key): return _wrap return _decorator + class LibvirtConnection(object): def __init__(self, read_only): @@ -512,7 +513,6 @@ class LibvirtConnection(object): user=user, project=project) - size = FLAGS.minimum_root_size if not FLAGS.use_cow_images: if inst['instance_type'] == 'm1.tiny' or prefix == 'rescue-': From a6b82b3015a64922a0733bd0dd5463b1a49ca080 Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Wed, 5 Jan 2011 20:46:18 +0000 Subject: [PATCH 5/6] simplify decorator into a wrapper fn --- nova/compute/disk.py | 10 +++- nova/virt/libvirt_conn.py | 104 +++++++++++++++++++------------------- 2 files changed, 59 insertions(+), 55 deletions(-) diff --git a/nova/compute/disk.py b/nova/compute/disk.py index bbcd55678436..8b7453f322fb 100644 --- a/nova/compute/disk.py +++ b/nova/compute/disk.py @@ -25,6 +25,7 @@ Includes injection of SSH PGP keys into authorized_keys file. import logging import os import tempfile +import time from nova import exception from nova import flags @@ -111,8 +112,10 @@ def extend(image, size): file_size = os.path.getsize(image) if file_size >= size: return - # TODO(vish): attempt to resize filesystem - return utils.execute('truncate -s %s %s' % (size, image)) + 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): @@ -179,6 +182,9 @@ def _link_device(image, 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) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 77ff281d56b9..f2803ab5504c 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -118,43 +118,6 @@ def _get_net_and_mask(cidr): return str(net.net()), str(net.netmask()) -def wrap_image_cache(key): - """Decorator for a method that creates an image to store it in cache. - - This wrapper will save the image into a common store and create a - copy for use by the hypervisor. - - The underlying method should be called with kwargs and specify - a kwarg of target representing where the image will be saved. The - key argument to the wrapper defines which kwarg of the underlying - method to use as the filename of the base image. The filename needs - to be unique to a given image. - - If kwargs['cow'] is True, it will make a CoW image instead of a copy. - """ - def _decorator(retrieve_fn): - def _wrap(*args, **kwargs): - target = kwargs['target'] - if not os.path.exists(target): - base_dir = os.path.join(FLAGS.instances_path, '_base') - if not os.path.exists(base_dir): - os.mkdir(base_dir) - os.chmod(base_dir, 0777) - base = os.path.join(base_dir, str(kwargs[key])) - if not os.path.exists(base): - kwargs['target'] = base - retrieve_fn(*args, **kwargs) - if kwargs.get('cow'): - utils.execute('qemu-img create -f qcow2 -o ' - 'cluster_size=2M,backing_file=%s %s' - % (base, target)) - else: - utils.execute('cp %s %s' % (base, target)) - _wrap.func_name = retrieve_fn.func_name - return _wrap - return _decorator - - class LibvirtConnection(object): def __init__(self, read_only): @@ -458,16 +421,42 @@ class LibvirtConnection(object): return self._dump_file(fpath) - @wrap_image_cache('image_id') - def _fetch_image(self, target, image_id, user, project, - size=None, cow=False): + def _cache_image(self, fn, target, fname, cow=False, *args, **kwargs): + """Wrapper to cache a method that creates an image. + + This wrapper will save the image into a common store and create a + copy for use by the hypervisor. + + The underlying method should specify a kwarg of target representing + 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. + + If cow is True, it will make a CoW image instead of a copy. + """ + if not os.path.exists(target): + base_dir = os.path.join(FLAGS.instances_path, '_base') + if not os.path.exists(base_dir): + os.mkdir(base_dir) + os.chmod(base_dir, 0777) + base = os.path.join(base_dir, fname) + if not os.path.exists(base): + fn(target=base, *args, **kwargs) + if cow: + utils.execute('qemu-img create -f qcow2 -o ' + 'cluster_size=2M,backing_file=%s %s' + % (base, target)) + else: + utils.execute('cp %s %s' % (base, target)) + + def _fetch_image(self, target, image_id, user, project, size=None): """Grab image and optionally attempt to resize it""" images.fetch(image_id, target, user, project) if size: disk.extend(target, size) - @wrap_image_cache('local_gb') - def _create_local(self, target, local_gb, cow=False): + 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 ' @@ -503,33 +492,42 @@ class LibvirtConnection(object): 'ramdisk_id': inst['ramdisk_id']} if disk_images['kernel_id']: - self._fetch_image(target=basepath('kernel'), + self._cache_image(fn=self._fetch_image, + target=basepath('kernel'), + fname=disk_images['kernel_id'], image_id=disk_images['kernel_id'], user=user, project=project) if disk_images['ramdisk_id']: - self._fetch_image(target=basepath('ramdisk'), + self._cache_image(fn=self._fetch_image, + target=basepath('ramdisk'), + fname=disk_images['ramdisk_id'], image_id=disk_images['ramdisk_id'], user=user, project=project) + root_fname = disk_images['image_id'] size = FLAGS.minimum_root_size - if not FLAGS.use_cow_images: - if inst['instance_type'] == 'm1.tiny' or prefix == 'rescue-': - size = None + if inst['instance_type'] == 'm1.tiny' or prefix == 'rescue-': + size = None + root_fname += "_sm" - self._fetch_image(target=basepath('disk'), + self._cache_image(fn=self._fetch_image, + target=basepath('disk'), + fname=root_fname, + cow=FLAGS.use_cow_images, image_id=disk_images['image_id'], user=user, project=project, - size=size, - cow=FLAGS.use_cow_images) + size=size) type_data = instance_types.INSTANCE_TYPES[inst['instance_type']] if type_data['local_gb']: - self._create_local(target=basepath('local'), - local_gb=type_data['local_gb'], - cow=FLAGS.use_cow_images) + self._cache_image(fn=self._create_local, + target=basepath('local'), + fname="local_%s" % type_data['local_gb'], + cow=FLAGS.use_cow_images, + local_gb=type_data['local_gb']) # 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 From 914b0554a092d2b38f292942dc4d7ddea5d99b9a Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 13 Jan 2011 15:13:31 -0800 Subject: [PATCH 6/6] Modified per sorens review. Moved disk.py Removed disk.partition Changed docstrings Use pid to find nbd devices --- nova/{compute => virt}/disk.py | 89 ++++++---------------------------- nova/virt/libvirt_conn.py | 10 ++-- 2 files changed, 18 insertions(+), 81 deletions(-) rename nova/{compute => virt}/disk.py (66%) diff --git a/nova/compute/disk.py b/nova/virt/disk.py similarity index 66% rename from nova/compute/disk.py rename to nova/virt/disk.py index b9c34750d442..c5565abfaf8c 100644 --- a/nova/compute/disk.py +++ b/nova/virt/disk.py @@ -40,74 +40,6 @@ 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) @@ -185,8 +117,11 @@ def _link_device(image, nbd): 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 + 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: @@ -208,12 +143,16 @@ _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 + # 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. - if not _DEVICES: - raise exception.Error(_('No free nbd devices')) - return _DEVICES.pop() + 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): diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 760bfef78a80..c77d25bfdae0 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):