[openstack-nova] add support for mounting disk images with libguestfs

Pádraig Brady pbrady at fedoraproject.org
Thu Dec 1 17:47:26 UTC 2011


commit 66f8f58e083a2a71d88f5b7126724778b03fa74c
Author: Pádraig Brady <P at draigBrady.com>
Date:   Thu Dec 1 17:22:15 2011 +0000

    add support for mounting disk images with libguestfs

 ...57-abstract-out-disk-image-access-methods.patch |  535 ++++++++++++++++++++
 ...7-support-handling-images-with-libguestfs.patch |   90 ++++
 openstack-nova.spec                                |   24 +-
 3 files changed, 644 insertions(+), 5 deletions(-)
---
diff --git a/0001-Bug-898257-abstract-out-disk-image-access-methods.patch b/0001-Bug-898257-abstract-out-disk-image-access-methods.patch
new file mode 100644
index 0000000..2fcb03f
--- /dev/null
+++ b/0001-Bug-898257-abstract-out-disk-image-access-methods.patch
@@ -0,0 +1,535 @@
+From 105e3d6f72411d77b84abe23810e7a5b882bf340 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?P=C3=A1draig=20Brady?= <P at draigBrady.com>
+Date: Mon, 28 Nov 2011 14:31:58 +0000
+Subject: [PATCH 1/2] Bug#898257 abstract out disk image access methods
+
+Rather than providing two mutually exlusive image
+access methods (loop and qemu-nbd), try each in turn.
+This is to prepare for a follow up patch which will
+add libguestfs as a method to try.
+
+* nova/virt/disk.py(img_handlers): A new list of access methods to try,
+with the order being honored.
+(_baseMnt, _loopMnt, _nbdMnt): New mixin classes that abstract the
+devce allocation, partition mapping and file sys mounting,
+for each access type.
+(_DiskImage): An internal helper class that uses the mixin classes
+to provide the operations available on a disk image file.
+When mounting, iterate over each access method until one succeeds.
+If a hint is provided about a CoW format image, the list of
+methods to try will be reduced accordingly.
+Note expected errors are no longer raised as exceptions during mounting.
+Instead, on failure to mount an image, the last error is raised,
+while interveining errors are logged.
+* nova/virt/libvirt/connection.py: Adjust the function parameter
+names to be more general, rather than referencing specific
+implementations like 'nbd' and 'tune2fs'.
+Simplify the destroy_container() by storing and passing
+back a reference to the _DiskImage object, which has the
+necessary state to unmount.
+
+Change-Id: If3a4b1c8f4e2f2e7300a21071340dcc839cb36d7
+---
+ nova/virt/disk.py               |  386 +++++++++++++++++++++++++++------------
+ nova/virt/libvirt/connection.py |   15 +-
+ 2 files changed, 281 insertions(+), 120 deletions(-)
+
+diff --git a/nova/virt/disk.py b/nova/virt/disk.py
+index 9fe164c..71531e8 100644
+--- a/nova/virt/disk.py
++++ b/nova/virt/disk.py
+@@ -51,6 +51,9 @@ flags.DEFINE_integer('timeout_nbd', 10,
+                      'time to wait for a NBD device coming up')
+ flags.DEFINE_integer('max_nbd_devices', 16,
+                      'maximum number of possible nbd devices')
++flags.DEFINE_list('img_handlers', ['loop', 'nbd'],
++                    'Order of methods used to mount disk images')
++
+ 
+ # NOTE(yamahata): DEFINE_list() doesn't work because the command may
+ #                 include ','. For example,
+@@ -105,8 +108,259 @@ def extend(image, size):
+     utils.execute('resize2fs', image, check_exit_code=False)
+ 
+ 
++# Mixin classes for each image access method
++
++class _baseMnt(object):
++    """Standard operations, that can be overridden
++    by specialised classes below.  The basic operations
++    provided are get, map and mount."""
++
++    def get_dev(self):
++        """Make the image available as a block device
++        in the file system namespace."""
++        self.device = None
++        self.linked = True
++        return True
++
++    def unget_dev(self):
++        """Release the block device
++        from the file system namespace."""
++        self.linked = False
++
++    def map_dev(self):
++        """Make any partitions of the device
++        available in the file system namespace."""
++        if self.partition:
++            out, err = utils.execute('kpartx', '-a', self.device,
++                                     check_exit_code=False, run_as_root=True)
++            if err:
++                self.error = _('Failed to load partition: %s') % err
++                return False
++            self.mapped_device = '/dev/mapper/%sp%s' % (self.device.split('/')[-1],
++                                                        self.partition)
++        else:
++            self.mapped_device = self.device
++
++        # Note kpartx does nothing when presented with a raw image,
++        # so given we only use it when we expect a partitioned image, fail here
++        if not os.path.exists(self.mapped_device):
++            self.error = _('Failed to load partition: %s') % _('no partitions found')
++            return False
++
++        # This is an orthogonal operation
++        # which only needs to be done once
++        if self.disable_auto_fsck:
++            self.disable_auto_fsck = False
++            # Attempt to set ext[234] so that it doesn't auto-fsck
++            out, err = utils.execute('tune2fs', '-c', 0, '-i', 0,
++                                     self.mapped_device, check_exit_code=False,
++                                     run_as_root=True)
++            if err:
++                LOG.info(_('Failed to disable fs check: %s') % err)
++
++        self.mapped = True
++        return True
++
++    def unmap_dev(self):
++        """Release any partitions of the device
++        from the file system namespace."""
++        if not self.mapped:
++            return
++        if self.partition:
++            utils.execute('kpartx', '-d', self.device, run_as_root=True)
++        self.mapped = False
++
++    def mnt_dev(self):
++        """Mount the device into the file system."""
++        out, err = utils.execute('mount', self.mapped_device, self.mount_dir,
++                                 check_exit_code=False, run_as_root=True)
++        if err:
++            self.error = _('Failed to mount filesystem: %s') % err
++            return False
++
++        self.mounted = True
++        return True
++
++    def unmnt_dev(self):
++        """Unmount the device from the file system."""
++        if not self.mounted:
++            return
++        utils.execute('umount', self.mapped_device, run_as_root=True)
++        self.mounted = False
++
++    def do_mount(self,cls):
++        """Call the get, map and mnt operations above,
++        which may be specialised by mixin classes below."""
++        try:
++            if cls.get_dev(self) and cls.map_dev(self) and cls.mnt_dev(self):
++                self.mode = cls.mode
++                return True
++            else:
++                LOG.info(self.error)
++                self.do_umount(cls)
++        except:
++                self.do_umount(cls)
++                # XXX: Sometimes exception is cleared
++                # so we can fail to reraise here?
++                # That's because of greenthread call in utils.execute() ?
++                raise
++        return False
++
++    def do_umount(self,cls):
++        """Call the unmnt, unmap and unget operations,
++        which may be specialised by mixin classes below."""
++        self.mode = None
++        if self.mounted:
++            cls.unmnt_dev(self)
++        if self.mapped:
++            cls.unmap_dev(self)
++        if self.linked:
++            cls.unget_dev(self)
++
++
++class _loopMnt(_baseMnt):
++    """loop back support for raw images."""
++    mode = 'loop'
++
++    def get_dev(self):
++        out, err = utils.execute('losetup', '--find', '--show', self.image,
++                                 check_exit_code=False, run_as_root=True)
++        if err:
++            self.error = _('Could not attach image to loopback: %s') % err
++            return False
++
++        self.device = out.strip()
++        self.linked = True
++        return True
++
++    def unget_dev(self):
++        if not self.linked:
++            return
++        utils.execute('losetup', '--detach', self.device, run_as_root=True)
++        self.linked = False
++
++
++class _nbdMnt(_baseMnt):
++    """qemu-nbd support for CoW images."""
++    mode = 'nbd'
++
++    # NOTE(padraig): There are three issues with this nbd device handling
++    #  1. max_nbd_devices should be inferred (#861504)
++    #  2. We assume nothing else on the system uses nbd devices
++    #  3. Multiple workers on a system can race against each other
++    # A patch has been proposed in Nov 2011, to add add a -f option to
++    # qemu-nbd, akin to losetup -f. One could test for this by running qemu-nbd
++    # with just the -f option, where it will fail if not supported, or if there
++    # are no free devices. Note that patch currently hardcodes 16 devices.
++    # We might be able to alleviate problem 2. by scanning /proc/partitions
++    # like the aformentioned patch does.
++    _DEVICES = ['/dev/nbd%s' % i for i in range(FLAGS.max_nbd_devices)]
++    def _allocate_nbd(self):
++        while True:
++            if not self._DEVICES:
++                # really want to log this info, not raise
++                self.error = _('No free nbd devices')
++                return None
++            device = self._DEVICES.pop()
++            if not os.path.exists("/sys/block/%s/pid" % os.path.basename(device)):
++                break
++        return device
++
++    def _free_nbd(self, device):
++        self._DEVICES.append(device)
++
++    def get_dev(self):
++        device = self._allocate_nbd()
++        if not device:
++            return False
++        out, err = utils.execute('qemu-nbd', '-c', device, self.image,
++                                 check_exit_code=False, run_as_root=True)
++        if err:
++            self.error = _('qemu-nbd error: %s') % err
++            self._free_nbd(self.device)
++            return False
++
++        # NOTE(vish): this forks into another process, so give it a chance
++        #             to set up before continuing
++        for i in range(FLAGS.timeout_nbd):
++            if os.path.exists("/sys/block/%s/pid" % os.path.basename(device)):
++                self.device = device
++                break
++            time.sleep(1)
++        else:
++            self.error = _('nbd device %s did not show up') % device
++            self._free_nbd(self.device)
++            return False
++
++        self.linked = True
++        return True
++
++    def unget_dev(self):
++        if not self.linked:
++            return
++        utils.execute('qemu-nbd', '-d', self.device, run_as_root=True)
++        self._free_nbd(self.device)
++        self.linked = False
++        self.device = None
++
++
++class _DiskImage(_loopMnt, _nbdMnt):
++    """Provide operations on a disk image file."""
++
++    def __init__(self, image, partition=None, use_cow=False,
++                 disable_auto_fsck=False, mount_dir=None):
++        self.image = image
++        self.partition = partition
++        self.use_cow = use_cow
++        self.disable_auto_fsck = disable_auto_fsck
++        self.mount_dir = mount_dir
++
++        self.mode = None
++        self.linked = self.mapped = self.mounted = self.mkdir = False
++        self.device = self.mapped_device = None
++        self.error = ""
++
++        # As a performance tweak, don't bother trying to
++        # directly loopback mount a cow image.
++        self.handlers = FLAGS.img_handlers[:]
++        if self.use_cow:
++            self.handlers.remove('loop')
++
++    def _handler_class(self, mode):
++        """Look up the appropriate class to use based on MODE."""
++        for cls in self.__class__.__bases__:
++            if cls.mode == mode:
++                return cls
++        raise exception.Error(_("unknown disk image handler: %s" % mode))
++
++    def mount(self):
++        """Mount a disk image using the object attributes,
++        and first supported means provided by the mixin clases."""
++        if self.mode:
++            raise exception.Error(_('image already mounted'))
++
++        if not self.mount_dir:
++            self.mount_dir = tempfile.mkdtemp()
++            self.mkdir = True
++
++        # This explicit class lookup might be avoided with a multimethod decorator,
++        # but this is simple enough not to warrant this abstraction.
++        return any(self.do_mount(self._handler_class(h)) for h in self.handlers)
++
++    def umount(self):
++        """Unmount a disk image from the file system."""
++        try:
++            if self.mode:
++                self.do_umount(self._handler_class(self.mode))
++        finally:
++            if self.mkdir:
++                os.rmdir(self.mount_dir)
++
++
++# Public module functions
++
+ def inject_data(image, key=None, net=None, metadata=None,
+-                partition=None, nbd=False, tune2fs=True):
++                partition=None, use_cow=False, disable_auto_fsck=True):
+     """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
+@@ -115,57 +369,18 @@ def inject_data(image, key=None, net=None, metadata=None,
+     If partition is not specified it mounts the image as a single partition.
+ 
+     """
+-    device = _link_device(image, nbd)
++    img = _DiskImage(image=image, partition=partition, use_cow=use_cow,
++                     disable_auto_fsck=disable_auto_fsck)
+     try:
+-        if not partition is None:
+-            # create partition
+-            out, err = utils.execute('kpartx', '-a', device, run_as_root=True)
+-            if err:
+-                raise exception.Error(_('Failed to load partition: %s') % err)
+-            mapped_device = '/dev/mapper/%sp%s' % (device.split('/')[-1],
+-                                                   partition)
++        if img.mount():
++            inject_data_into_fs(img.mount_dir, key, net, metadata, utils.execute)
+         else:
+-            mapped_device = device
+-
+-        try:
+-            # 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)
+-
+-            if tune2fs:
+-                # Configure ext2fs so that it doesn't auto-check every N boots
+-                out, err = utils.execute('tune2fs', '-c', 0, '-i', 0,
+-                                         mapped_device, run_as_root=True)
+-            tmpdir = tempfile.mkdtemp()
+-            try:
+-                # mount loopback to dir
+-                out, err = utils.execute('mount', mapped_device, tmpdir,
+-                                         run_as_root=True)
+-                if err:
+-                    raise exception.Error(_('Failed to mount filesystem: %s')
+-                                          % err)
+-
+-                try:
+-                    inject_data_into_fs(tmpdir, key, net, metadata,
+-                                        utils.execute)
+-                finally:
+-                    # unmount device
+-                    utils.execute('umount', mapped_device, run_as_root=True)
+-            finally:
+-                # remove temporary directory
+-                utils.execute('rmdir', tmpdir)
+-        finally:
+-            if not partition is None:
+-                # remove partitions
+-                utils.execute('kpartx', '-d', device, run_as_root=True)
++            raise exception.Error(img.error)
+     finally:
+-        _unlink_device(device, nbd)
++        img.umount()
+ 
+ 
+-def setup_container(image, container_dir=None, nbd=False):
++def setup_container(image, container_dir=None, use_cow=False):
+     """Setup the LXC container.
+ 
+     It will mount the loopback image to the container directory in order
+@@ -174,86 +389,31 @@ def setup_container(image, container_dir=None, nbd=False):
+     LXC does not support qcow2 images yet.
+     """
+     try:
+-        device = _link_device(image, nbd)
+-        utils.execute('mount', device, container_dir, run_as_root=True)
++        img = _DiskImage(image=image, use_cow=use_cow, mount_dir=container_dir)
++        if img.mount():
++            return img
++        else:
++            raise exception.Error(img.error)
+     except Exception, exn:
+         LOG.exception(_('Failed to mount filesystem: %s'), exn)
+-        _unlink_device(device, nbd)
++        img.umount()
+ 
+ 
+-def destroy_container(target, instance, nbd=False):
++def destroy_container(img):
+     """Destroy the container once it terminates.
+ 
+-    It will umount the container that is mounted, try to find the loopback
+-    device associated with the container and delete it.
++    It will umount the container that is mounted,
++    and delete any  linked devices.
+ 
+     LXC does not support qcow2 images yet.
+     """
+-    out, err = utils.execute('mount', run_as_root=True)
+-    for loop in out.splitlines():
+-        if instance['name'] in loop:
+-            device = loop.split()[0]
+-
+     try:
+-        container_dir = '%s/rootfs' % target
+-        utils.execute('umount', container_dir, run_as_root=True)
+-        _unlink_device(device, nbd)
++        if img:
++            img.umount()
+     except Exception, exn:
+         LOG.exception(_('Failed to remove container: %s'), exn)
+ 
+ 
+-def _link_device(image, nbd):
+-    """Link image to device using loopback or nbd"""
+-
+-    if nbd:
+-        device = _allocate_device()
+-        utils.execute('qemu-nbd', '-c', device, image, run_as_root=True)
+-        # NOTE(vish): this forks into another process, so give it a chance
+-        #             to set up before continuuing
+-        for i in xrange(FLAGS.timeout_nbd):
+-            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('losetup', '--find', '--show', image,
+-                                 run_as_root=True)
+-        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('qemu-nbd', '-d', device, run_as_root=True)
+-        _free_device(device)
+-    else:
+-        utils.execute('losetup', '--detach', device, run_as_root=True)
+-
+-
+-_DEVICES = ['/dev/nbd%s' % i for i in xrange(FLAGS.max_nbd_devices)]
+-
+-
+-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_data_into_fs(fs, key, net, metadata, execute):
+     """Injects data into a filesystem already mounted by the caller.
+     Virt connections can call this directly if they mount their fs
+diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py
+index ba1dc86..bba2e0b 100644
+--- a/nova/virt/libvirt/connection.py
++++ b/nova/virt/libvirt/connection.py
+@@ -179,6 +179,7 @@ class LibvirtConnection(driver.ComputeDriver):
+         self.libvirt_xml = open(FLAGS.libvirt_xml_template).read()
+         self.cpuinfo_xml = open(FLAGS.cpuinfo_xml_template).read()
+         self._wrapped_conn = None
++        self.container = None
+         self.read_only = read_only
+ 
+         fw_class = utils.import_class(FLAGS.firewall_driver)
+@@ -347,7 +348,7 @@ class LibvirtConnection(driver.ComputeDriver):
+         LOG.info(_('instance %(instance_name)s: deleting instance files'
+                 ' %(target)s') % locals())
+         if FLAGS.libvirt_type == 'lxc':
+-            disk.destroy_container(target, instance, nbd=FLAGS.use_cow_images)
++            disk.destroy_container(self.container)
+         if os.path.exists(target):
+             shutil.rmtree(target)
+ 
+@@ -1003,11 +1004,11 @@ class LibvirtConnection(driver.ComputeDriver):
+             if config_drive:  # Should be True or None by now.
+                 injection_path = basepath('disk.config')
+                 img_id = 'config-drive'
+-                tune2fs = False
++                disable_auto_fsck = False
+             else:
+                 injection_path = basepath('disk')
+                 img_id = inst.image_ref
+-                tune2fs = True
++                disable_auto_fsck = True
+ 
+             for injection in ('metadata', 'key', 'net'):
+                 if locals()[injection]:
+@@ -1017,8 +1018,8 @@ class LibvirtConnection(driver.ComputeDriver):
+             try:
+                 disk.inject_data(injection_path, key, net, metadata,
+                                  partition=target_partition,
+-                                 nbd=FLAGS.use_cow_images,
+-                                 tune2fs=tune2fs)
++                                 use_cow=FLAGS.use_cow_images,
++                                 disable_auto_fsck=disable_auto_fsck)
+ 
+             except Exception as e:
+                 # This could be a windows image, or a vmdk format disk
+@@ -1026,9 +1027,9 @@ class LibvirtConnection(driver.ComputeDriver):
+                         ' data into image %(img_id)s (%(e)s)') % locals())
+ 
+         if FLAGS.libvirt_type == 'lxc':
+-            disk.setup_container(basepath('disk'),
++            self.container=disk.setup_container(basepath('disk'),
+                                 container_dir=container_dir,
+-                                nbd=FLAGS.use_cow_images)
++                                use_cow=FLAGS.use_cow_images)
+ 
+         if FLAGS.libvirt_type == 'uml':
+             utils.execute('chown', 'root', basepath('disk'), run_as_root=True)
+-- 
+1.7.6.4
+
diff --git a/0002-Bug-898257-support-handling-images-with-libguestfs.patch b/0002-Bug-898257-support-handling-images-with-libguestfs.patch
new file mode 100644
index 0000000..18343a9
--- /dev/null
+++ b/0002-Bug-898257-support-handling-images-with-libguestfs.patch
@@ -0,0 +1,90 @@
+From d2bf5c7d318fdcf557aa641aadb5efe9f54d052b Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?P=C3=A1draig=20Brady?= <P at draigBrady.com>
+Date: Wed, 30 Nov 2011 17:00:17 +0000
+Subject: [PATCH 2/2] Bug#898257 support handling images with libguestfs
+
+http://libguestfs.org/ provides both utilities and libraries
+to manipulate image files containing various operating systems.
+It supports various image file formats and so will expand
+the formats and guest types supported by openstack.
+It does have extra overhead in that it starts a VM to
+access the image. This has both advantages and disadvantages.
+Also qemu-nbd is not supported on some systems like RHEL 6.
+
+* nova/virt/disk.py (img_handlers): Add guestfs to the default list
+of access methods to try, to act as a fallback.
+(_guestfsMnt): A new mixin class to provide the access method.
+Note we use the guestmount utility, as a non root user,
+so the user will need the ability to use fusermount, which
+is often provided by being a member of the 'fuser' group.
+In future we might use the guestfs python module to give
+greater granularity of control over the image.
+
+Change-Id: I2e22c9d149fff7a73cd8cebaa280d68d3fb9096c
+---
+ nova/virt/disk.py |   42 ++++++++++++++++++++++++++++++++++++++++--
+ 1 files changed, 40 insertions(+), 2 deletions(-)
+
+diff --git a/nova/virt/disk.py b/nova/virt/disk.py
+index 71531e8..87b06ff 100644
+--- a/nova/virt/disk.py
++++ b/nova/virt/disk.py
+@@ -51,7 +51,7 @@ flags.DEFINE_integer('timeout_nbd', 10,
+                      'time to wait for a NBD device coming up')
+ flags.DEFINE_integer('max_nbd_devices', 16,
+                      'maximum number of possible nbd devices')
+-flags.DEFINE_list('img_handlers', ['loop', 'nbd'],
++flags.DEFINE_list('img_handlers', ['loop', 'nbd', 'guestfs'],
+                     'Order of methods used to mount disk images')
+ 
+ 
+@@ -304,7 +304,45 @@ class _nbdMnt(_baseMnt):
+         self.device = None
+ 
+ 
+-class _DiskImage(_loopMnt, _nbdMnt):
++class _guestfsMnt(_baseMnt):
++    """libguestfs support for arbitrary images."""
++    mode = 'guestfs'
++
++    def map_dev(self):
++        self.mapped = True
++        return True
++
++    def unmap_dev(self):
++        self.mapped = False
++
++    def mnt_dev(self):
++        args = ('guestmount', '--rw', '-a', self.image)
++        if self.partition:
++            args += ('-m', '/dev/sda%d' % self.partition)
++        else:
++            args += ('-i',) # find the OS partition
++        args += (self.mount_dir,)
++        # root access is not required for guestfs, but the current
++        # user must be able to fusermount (by being part of the
++        # fuser group for example).
++        out, err = utils.execute(*args, check_exit_code=False)
++        if err:
++            self.error = _('Failed to mount filesystem: %s') % err
++            return False
++
++        self.mounted = True
++        return True
++
++    def unmnt_dev(self):
++        if not self.mounted:
++            return
++        # root users don't need a specific unmnt_dev
++        # but ordinary users do
++        utils.execute('fusermount', '-u', self.mount_dir)
++        self.mounted = False
++
++
++class _DiskImage(_loopMnt, _nbdMnt, _guestfsMnt):
+     """Provide operations on a disk image file."""
+ 
+     def __init__(self, image, partition=None, use_cow=False,
+-- 
+1.7.6.4
+
diff --git a/openstack-nova.spec b/openstack-nova.spec
index 72bcd08..cb19199 100644
--- a/openstack-nova.spec
+++ b/openstack-nova.spec
@@ -2,7 +2,7 @@
 
 Name:             openstack-nova
 Version:          2011.3
-Release:          9%{?dist}
+Release:          10%{?dist}
 Summary:          OpenStack Compute (nova)
 
 Group:            Applications/System
@@ -78,6 +78,10 @@ Patch40:          0040-Refactor-ietadm-tgtadm-calls-out-into-helper-classes.patc
 # These are fedora specific
 Patch100:         openstack-nova-nonet.patch
 
+# These are additional patches for upstream but not maintained at the above repo
+Patch200:         0001-Bug-898257-abstract-out-disk-image-access-methods.patch
+Patch201:         0002-Bug-898257-support-handling-images-with-libguestfs.patch
+
 BuildArch:        noarch
 BuildRequires:    intltool
 BuildRequires:    python-setuptools
@@ -91,6 +95,7 @@ Requires:         openstack-glance
 Requires:         python-paste
 Requires:         python-paste-deploy
 
+Requires:         libguestfs-mount >= 1.7.17
 Requires:         libvirt-python
 Requires:         libvirt >= 0.8.7
 Requires:         libxml2-python
@@ -252,6 +257,10 @@ This package contains documentation files for nova.
 # apply local patches
 %patch100 -p1
 
+# apply misc patches
+%patch200 -p1
+%patch201 -p1
+
 find . \( -name .gitignore -o -name .placeholder \) -delete
 
 find nova -name \*.py -exec sed -i '/\/usr\/bin\/env python/d' {} \;
@@ -349,9 +358,11 @@ rm -f %{buildroot}/usr/share/doc/nova/README*
 
 %pre
 getent group nova >/dev/null || groupadd -r nova --gid 162
-getent passwd nova >/dev/null || \
-useradd --uid 162 -r -g nova -G nova,nobody,qemu -d %{_sharedstatedir}/nova -s /sbin/nologin \
--c "OpenStack Nova Daemons" nova
+if ! getent passwd nova >/dev/null; then
+  useradd -u 162 -r -g nova -G nova,nobody,qemu,fuse -d %{_sharedstatedir}/nova -s /sbin/nologin -c "OpenStack Nova Daemons" nova
+else
+  usermod -a -G fuse nova
+fi
 exit 0
 
 %post
@@ -433,7 +444,10 @@ fi
 %endif
 
 %changelog
-* Tue Nov 22 2011 Pádraig Brady <P at draigBrady.com> - 2011.3-9
+* Wed Nov 30 2011 Pádraig Brady <P at draigBrady.com> - 2011.3-10
+- Add libguestfs support
+
+* Tue Nov 29 2011 Pádraig Brady <P at draigBrady.com> - 2011.3-9
 - Update the libvirt dependency from 0.8.2 to 0.8.7
 - Ensure we don't access the net when building docs
 


More information about the scm-commits mailing list