[openstack-nova] Update libguestfs support

Pádraig Brady pbrady at fedoraproject.org
Wed Dec 14 13:19:44 UTC 2011


commit 8220d6e51adcfd136645ba98fbb090ec22774204
Author: Pádraig Brady <P at draigBrady.com>
Date:   Wed Dec 14 13:16:03 2011 +0000

    Update libguestfs support

 ...57-abstract-out-disk-image-access-methods.patch | 1221 ++++++++++++++------
 ...7-support-handling-images-with-libguestfs.patch |  138 ++-
 openstack-nova.spec                                |    1 +
 3 files changed, 979 insertions(+), 381 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
index 1665aed..a61ccf9 100644
--- a/0001-Bug-898257-abstract-out-disk-image-access-methods.patch
+++ b/0001-Bug-898257-abstract-out-disk-image-access-methods.patch
@@ -1,6 +1,6 @@
-From ca9b37578ada6e5b5c51abe8227d885dd79fa159 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
+From e63b54e824e5cf3585f9d9c87272941a75b6cea9 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?P=C3=A1draig=20Brady?= <pbrady at redhat.com>
+Date: Wed, 30 Nov 2011 17:00:17 +0000
 Subject: [PATCH 1/2] Bug#898257 abstract out disk image access methods
 
 Rather than providing two mutually exlusive image
@@ -8,334 +8,217 @@ 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
+* nova/virt/mount.py: A new Mount class to 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.
+* nova/virt/disk/loop.py: A specialization of the base Mount class
+to provide loop back mounting support.
+* nova/virt/disk/nbd.py: A specialization of the base Mount class
+to provide qemu-nbd mounting support.
+* nova/virt/disk/base.py(img_handlers): A new list of access methods
+to try, with the order being honored.
+(_DiskImage): An internal helper class that uses the plugin classes
+above, 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.
+Instead, on failure to mount an image, errors are collated and raised.
+Interveining errors are logged in debug mode for successful mounts.
 * 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.
+* nova/utils.py (trycmd): A helper function to both deal with,
+commands that issue ignorable warnings to stderr,
+and commands that EXIT_SUCCESS while issuing errors to stderr.
 
 Change-Id: If3a4b1c8f4e2f2e7300a21071340dcc839cb36d7
 ---
- nova/virt/disk.py               |  405 ++++++++++++++++++++++++++++-----------
+ nova/utils.py                   |   30 ++++
+ nova/virt/disk.py               |  303 ---------------------------------------
+ nova/virt/disk/__init__.py      |   23 +++
+ nova/virt/disk/base.py          |  286 ++++++++++++++++++++++++++++++++++++
+ nova/virt/disk/loop.py          |   41 ++++++
+ nova/virt/disk/mount.py         |  142 ++++++++++++++++++
+ nova/virt/disk/nbd.py           |   95 ++++++++++++
  nova/virt/libvirt/connection.py |   15 +-
- 2 files changed, 300 insertions(+), 120 deletions(-)
+ 8 files changed, 625 insertions(+), 310 deletions(-)
+ delete mode 100644 nova/virt/disk.py
+ create mode 100644 nova/virt/disk/__init__.py
+ create mode 100644 nova/virt/disk/base.py
+ create mode 100644 nova/virt/disk/loop.py
+ create mode 100644 nova/virt/disk/mount.py
+ create mode 100644 nova/virt/disk/nbd.py
 
-diff --git a/nova/virt/disk.py b/nova/virt/disk.py
-index 9fe164c..0d6e65f 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,278 @@ def extend(image, size):
-     utils.execute('resize2fs', image, check_exit_code=False)
+diff --git a/nova/utils.py b/nova/utils.py
+index 4648f55..672d2ca 100644
+--- a/nova/utils.py
++++ b/nova/utils.py
+@@ -202,6 +202,36 @@ def execute(*cmd, **kwargs):
+             greenthread.sleep(0)
  
  
-+# 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 = self.attempt('kpartx', '-a', self.device, 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 = self.attempt('tune2fs', '-c', 0, '-i', 0,
-+                                    self.mapped_device, 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 = self.attempt('mount', self.mapped_device, self.mount_dir,
-+                                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
-+
-+    @staticmethod
-+    def attempt(*args, **kwargs):
-+        """A wrapper around utils.execute() to convert error exit exceptions
-+           to an error message and return code. If the 'discard_warnings'
-+           parameter is set to True, then any output to stderr is ignored."""
-+        discard_warnings = kwargs.pop('discard_warnings', False)
-+
-+        try:
-+            out, err = utils.execute(*args, **kwargs)
-+            failed = False
-+        except exception.ProcessExecutionError, exn:
-+            out, err = None, str(exn)
-+            LOG.debug(err)
-+            failed = True
-+
-+        if not failed and discard_warnings:
-+            # Handle commands that output to stderr but otherwise succeed
-+            err = None
-+
-+        return out, err
-+
-+    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 = self.attempt('losetup', '--find', '--show', self.image,
-+                                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 = self.attempt('qemu-nbd', '-c', device, self.image,
-+                                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 trycmd(*args, **kwargs):
++    """
++    A wrapper around execute() to more easily handle warnings and errors.
 +
-+    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
++    Returns an (out, err) tuple of strings containing the output of
++    the command's stdout and stderr.  If 'err' is not empty then the
++    command can be considered to have failed.
 +
++    :discard_warnings   True | False. Defaults to False. If set to True,
++                        then for succeeding commands, stderr is cleared
 +
-+class _DiskImage(_loopMnt, _nbdMnt):
-+    """Provide operations on a disk image file."""
++    """
++    discard_warnings = kwargs.pop('discard_warnings', False)
 +
-+    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
++    try:
++        out, err = execute(*args, **kwargs)
++        failed = False
++    except exception.ProcessExecutionError, exn:
++        out, err = '', str(exn)
++        LOG.debug(err)
++        failed = True
 +
-+        self.mode = None
-+        self.linked = self.mapped = self.mounted = self.mkdir = False
-+        self.device = self.mapped_device = None
-+        self.error = ""
++    if not failed and discard_warnings and err:
++        # Handle commands that output to stderr but otherwise succeed
++        LOG.debug(err)
++        err = ''
 +
-+        # 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')
++    return out, err
 +
-+    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,
+ def ssh_execute(ssh, cmd, process_input=None,
+                 addl_env=None, check_exit_code=True):
+     LOG.debug(_('Running cmd (SSH): %s'), ' '.join(cmd))
+diff --git a/nova/virt/disk.py b/nova/virt/disk.py
+deleted file mode 100644
+index 9fe164c..0000000
+--- a/nova/virt/disk.py
++++ /dev/null
+@@ -1,303 +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.
+-#
+-# Copyright 2011, Piston Cloud Computing, Inc.
+-#
+-# 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 json
+-import os
+-import tempfile
+-import time
+-
+-from nova import context
+-from nova import db
+-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')
+-flags.DEFINE_string('injected_network_template',
+-                    utils.abspath('virt/interfaces.template'),
+-                    'Template file for injected network')
+-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')
+-
+-# NOTE(yamahata): DEFINE_list() doesn't work because the command may
+-#                 include ','. For example,
+-#                 mkfs.ext3 -O dir_index,extent -E stride=8,stripe-width=16
+-#                 --label %(fs_label)s %(target)s
+-#
+-#                 DEFINE_list() parses its argument by
+-#                 [s.strip() for s in argument.split(self._token)]
+-#                 where self._token = ','
+-#                 No escape nor exceptional handling for ','.
+-#                 DEFINE_list() doesn't give us what we need.
+-flags.DEFINE_multistring('virt_mkfs',
+-                         ['windows=mkfs.ntfs --fast --label %(fs_label)s '
+-                          '%(target)s',
+-                          # NOTE(yamahata): vfat case
+-                          #'windows=mkfs.vfat -n %(fs_label)s %(target)s',
+-                          'linux=mkfs.ext3 -L %(fs_label)s -F %(target)s',
+-                          'default=mkfs.ext3 -L %(fs_label)s -F %(target)s'],
+-                         'mkfs commands for ephemeral device. The format is'
+-                         '<os_type>=<mkfs command>')
+-
+-
+-_MKFS_COMMAND = {}
+-_DEFAULT_MKFS_COMMAND = None
+-
+-
+-for s in FLAGS.virt_mkfs:
+-    # NOTE(yamahata): mkfs command may includes '=' for its options.
+-    #                 So item.partition('=') doesn't work here
+-    os_type, mkfs_command = s.split('=', 1)
+-    if os_type:
+-        _MKFS_COMMAND[os_type] = mkfs_command
+-    if os_type == 'default':
+-        _DEFAULT_MKFS_COMMAND = mkfs_command
+-
+-
+-def mkfs(os_type, fs_label, target):
+-    mkfs_command = (_MKFS_COMMAND.get(os_type, _DEFAULT_MKFS_COMMAND) or
+-                    '') % locals()
+-    if mkfs_command:
+-        utils.execute(*mkfs_command.split())
+-
+-
+-def extend(image, size):
+-    """Increase image to size"""
+-    file_size = os.path.getsize(image)
+-    if file_size >= size:
+-        return
+-    utils.execute('qemu-img', 'resize', image, size)
+-    # NOTE(vish): attempts to resize filesystem
+-    utils.execute('e2fsck', '-fp', image, check_exit_code=False)
+-    utils.execute('resize2fs', image, check_exit_code=False)
+-
+-
+-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 +388,18 @@ def inject_data(image, key=None, net=None, metadata=None,
-     If partition is not specified it mounts the image as a single partition.
- 
-     """
+-    """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)
-+    img = _DiskImage(image=image, partition=partition, use_cow=use_cow,
-+                     disable_auto_fsck=disable_auto_fsck)
-     try:
+-    try:
 -        if not partition is None:
 -            # create partition
 -            out, err = utils.execute('kpartx', '-a', device, run_as_root=True)
@@ -343,9 +226,7 @@ index 9fe164c..0d6e65f 100644
 -                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:
+-        else:
 -            mapped_device = device
 -
 -        try:
@@ -382,60 +263,47 @@ index 9fe164c..0d6e65f 100644
 -            if not partition is None:
 -                # remove partitions
 -                utils.execute('kpartx', '-d', device, run_as_root=True)
-+            raise exception.Error(img.error)
-     finally:
+-    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 +408,31 @@ def setup_container(image, container_dir=None, nbd=False):
-     LXC does not support qcow2 images yet.
-     """
-     try:
+-    """Setup the LXC container.
+-
+-    It will mount the loopback image to the container directory in order
+-    to create the root filesystem for the container.
+-
+-    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)
+-    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.
- 
+-    """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.
-     """
+-
+-    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:
+-    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)
- 
- 
+-    except Exception, exn:
+-        LOG.exception(_('Failed to remove container: %s'), exn)
+-
+-
 -def _link_device(image, nbd):
 -    """Link image to device using loopback or nbd"""
 -
@@ -488,9 +356,670 @@ index 9fe164c..0d6e65f 100644
 -    _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
+-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
+-    in a different way to inject_data
+-    """
+-    if key:
+-        _inject_key_into_fs(key, fs, execute=execute)
+-    if net:
+-        _inject_net_into_fs(net, fs, execute=execute)
+-    if metadata:
+-        _inject_metadata_into_fs(metadata, fs, execute=execute)
+-
+-
+-def _inject_metadata_into_fs(metadata, fs, execute=None):
+-    metadata_path = os.path.join(fs, "meta.js")
+-    metadata = dict([(m.key, m.value) for m in metadata])
+-
+-    utils.execute('tee', metadata_path,
+-                  process_input=json.dumps(metadata), run_as_root=True)
+-
+-
+-def _inject_key_into_fs(key, fs, execute=None):
+-    """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('mkdir', '-p', sshdir, run_as_root=True)
+-    utils.execute('chown', 'root', sshdir, run_as_root=True)
+-    utils.execute('chmod', '700', sshdir, run_as_root=True)
+-    keyfile = os.path.join(sshdir, 'authorized_keys')
+-    utils.execute('tee', '-a', keyfile,
+-                  process_input='\n' + key.strip() + '\n', run_as_root=True)
+-
+-
+-def _inject_net_into_fs(net, fs, execute=None):
+-    """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('mkdir', '-p', netdir, run_as_root=True)
+-    utils.execute('chown', 'root:root', netdir, run_as_root=True)
+-    utils.execute('chmod', 755, netdir, run_as_root=True)
+-    netfile = os.path.join(netdir, 'interfaces')
+-    utils.execute('tee', netfile, process_input=net, run_as_root=True)
+diff --git a/nova/virt/disk/__init__.py b/nova/virt/disk/__init__.py
+new file mode 100644
+index 0000000..fa89ca0
+--- /dev/null
++++ b/nova/virt/disk/__init__.py
+@@ -0,0 +1,23 @@
++# vim: tabstop=4 shiftwidth=4 softtabstop=4
++
++# Copyright 2011 Red Hat, Inc.
++#
++# 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.
++"""
++Operations on disk images including:
++
++resize, file system creation, data injection.
++
++"""
++
++from base import *
+diff --git a/nova/virt/disk/base.py b/nova/virt/disk/base.py
+new file mode 100644
+index 0000000..8b5acc4
+--- /dev/null
++++ b/nova/virt/disk/base.py
+@@ -0,0 +1,286 @@
++# vim: tabstop=4 shiftwidth=4 softtabstop=4
++
++# Copyright 2010 United States Government as represented by the
++# Administrator of the National Aeronautics and Space Administration.
++#
++# Copyright 2011, Piston Cloud Computing, Inc.
++#
++# 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 json
++import os
++import tempfile
++
++from nova import exception
++from nova import flags
++from nova import log as logging
++from nova import utils
++from nova.virt.disk import loop, nbd
++
++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')
++flags.DEFINE_string('injected_network_template',
++                    utils.abspath('virt/interfaces.template'),
++                    'Template file for injected network')
++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,
++#                 mkfs.ext3 -O dir_index,extent -E stride=8,stripe-width=16
++#                 --label %(fs_label)s %(target)s
++#
++#                 DEFINE_list() parses its argument by
++#                 [s.strip() for s in argument.split(self._token)]
++#                 where self._token = ','
++#                 No escape nor exceptional handling for ','.
++#                 DEFINE_list() doesn't give us what we need.
++flags.DEFINE_multistring('virt_mkfs',
++                         ['windows=mkfs.ntfs --fast --label %(fs_label)s '
++                          '%(target)s',
++                          # NOTE(yamahata): vfat case
++                          #'windows=mkfs.vfat -n %(fs_label)s %(target)s',
++                          'linux=mkfs.ext3 -L %(fs_label)s -F %(target)s',
++                          'default=mkfs.ext3 -L %(fs_label)s -F %(target)s'],
++                         'mkfs commands for ephemeral device. The format is'
++                         '<os_type>=<mkfs command>')
++
++
++_MKFS_COMMAND = {}
++_DEFAULT_MKFS_COMMAND = None
++
++
++for s in FLAGS.virt_mkfs:
++    # NOTE(yamahata): mkfs command may includes '=' for its options.
++    #                 So item.partition('=') doesn't work here
++    os_type, mkfs_command = s.split('=', 1)
++    if os_type:
++        _MKFS_COMMAND[os_type] = mkfs_command
++    if os_type == 'default':
++        _DEFAULT_MKFS_COMMAND = mkfs_command
++
++
++def mkfs(os_type, fs_label, target):
++    mkfs_command = (_MKFS_COMMAND.get(os_type, _DEFAULT_MKFS_COMMAND) or
++                    '') % locals()
++    if mkfs_command:
++        utils.execute(*mkfs_command.split())
++
++
++def extend(image, size):
++    """Increase image to size"""
++    file_size = os.path.getsize(image)
++    if file_size >= size:
++        return
++    utils.execute('qemu-img', 'resize', image, size)
++    # NOTE(vish): attempts to resize filesystem
++    utils.execute('e2fsck', '-fp', image, check_exit_code=False)
++    utils.execute('resize2fs', image, check_exit_code=False)
++
++
++class _DiskImage(object):
++    """Provide operations on a disk image file."""
++
++    def __init__(self, image, partition=None, use_cow=False,
++                 disable_auto_fsck=False, mount_dir=None):
++        # These passed to each mounter
++        self.image = image
++        self.partition = partition
++        self.disable_auto_fsck = disable_auto_fsck
++        self.mount_dir = mount_dir
++
++        # Internal
++        self._mkdir = False
++        self._mounter = None
++        self._errors = []
++
++        # As a performance tweak, don't bother trying to
++        # directly loopback mount a cow image.
++        self.handlers = FLAGS.img_handlers[:]
++        if use_cow:
++            self.handlers.remove('loop')
++
++    @property
++    def errors(self):
++        """Return the collated errors from all operations."""
++        return '\n--\n'.join([''] + self._errors)
++
++    @staticmethod
++    def _handler_class(mode):
++        """Look up the appropriate class to use based on MODE."""
++        for cls in (loop.Mount, nbd.Mount):
++            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.
++
++        The first supported means provided by the mount classes is used.
++
++        True, or False is returned and the 'errors' attribute
++        contains any diagnostics.
++        """
++        if self._mounter:
++            raise exception.Error(_('image already mounted'))
++
++        if not self.mount_dir:
++            self.mount_dir = tempfile.mkdtemp()
++            self._mkdir = True
++
++        try:
++            for h in self.handlers:
++                mounter_cls = self._handler_class(h)
++                mounter = mounter_cls(image=self.image,
++                                      partition=self.partition,
++                                      disable_auto_fsck=self.disable_auto_fsck,
++                                      mount_dir=self.mount_dir)
++                if mounter.do_mount():
++                    self._mounter = mounter
++                    break
++                else:
++                    LOG.debug(mounter.error)
++                    self._errors.append(mounter.error)
++        finally:
++            if not self._mounter:
++                self.umount()  # rmdir
++
++        return bool(self._mounter)
++
++    def umount(self):
++        """Unmount a disk image from the file system."""
++        try:
++            if self._mounter:
++                self._mounter.do_umount()
++        finally:
++            if self._mkdir:
++                os.rmdir(self.mount_dir)
++
++
++# Public module functions
++
++def inject_data(image, key=None, net=None, metadata=None,
++                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
++    into the specified partition number.
++
++    If partition is not specified it mounts the image as a single partition.
++
++    """
++    img = _DiskImage(image=image, partition=partition, use_cow=use_cow,
++                     disable_auto_fsck=disable_auto_fsck)
++    if img.mount():
++        try:
++            inject_data_into_fs(img.mount_dir, key, net, metadata,
++                                utils.execute)
++        finally:
++            img.umount()
++    else:
++        raise exception.Error(img.errors)
++
++
++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
++    to create the root filesystem for the container.
++
++    LXC does not support qcow2 images yet.
++    """
++    try:
++        img = _DiskImage(image=image, use_cow=use_cow, mount_dir=container_dir)
++        if img.mount():
++            return img
++        else:
++            raise exception.Error(img.errors)
++    except Exception, exn:
++        LOG.exception(_('Failed to mount filesystem: %s'), exn)
++
++
++def destroy_container(img):
++    """Destroy the container once it terminates.
++
++    It will umount the container that is mounted,
++    and delete any  linked devices.
++
++    LXC does not support qcow2 images yet.
++    """
++    try:
++        if img:
++            img.umount()
++    except Exception, exn:
++        LOG.exception(_('Failed to remove container: %s'), exn)
++
++
++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
++    in a different way to inject_data
++    """
++    if key:
++        _inject_key_into_fs(key, fs, execute=execute)
++    if net:
++        _inject_net_into_fs(net, fs, execute=execute)
++    if metadata:
++        _inject_metadata_into_fs(metadata, fs, execute=execute)
++
++
++def _inject_metadata_into_fs(metadata, fs, execute=None):
++    metadata_path = os.path.join(fs, "meta.js")
++    metadata = dict([(m.key, m.value) for m in metadata])
++
++    utils.execute('tee', metadata_path,
++                  process_input=json.dumps(metadata), run_as_root=True)
++
++
++def _inject_key_into_fs(key, fs, execute=None):
++    """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('mkdir', '-p', sshdir, run_as_root=True)
++    utils.execute('chown', 'root', sshdir, run_as_root=True)
++    utils.execute('chmod', '700', sshdir, run_as_root=True)
++    keyfile = os.path.join(sshdir, 'authorized_keys')
++    utils.execute('tee', '-a', keyfile,
++                  process_input='\n' + key.strip() + '\n', run_as_root=True)
++
++
++def _inject_net_into_fs(net, fs, execute=None):
++    """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('mkdir', '-p', netdir, run_as_root=True)
++    utils.execute('chown', 'root:root', netdir, run_as_root=True)
++    utils.execute('chmod', 755, netdir, run_as_root=True)
++    netfile = os.path.join(netdir, 'interfaces')
++    utils.execute('tee', netfile, process_input=net, run_as_root=True)
+diff --git a/nova/virt/disk/loop.py b/nova/virt/disk/loop.py
+new file mode 100644
+index 0000000..cbfd6f6
+--- /dev/null
++++ b/nova/virt/disk/loop.py
+@@ -0,0 +1,41 @@
++# vim: tabstop=4 shiftwidth=4 softtabstop=4
++
++# Copyright 2011 Red Hat, Inc.
++#
++# 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.
++"""Support for mounting images with the loop device"""
++
++from nova import utils
++from nova.virt.disk import mount
++
++
++class Mount(mount.Mount):
++    """loop back support for raw images."""
++    mode = 'loop'
++
++    def get_dev(self):
++        out, err = utils.trycmd('losetup', '--find', '--show', self.image,
++                                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
+diff --git a/nova/virt/disk/mount.py b/nova/virt/disk/mount.py
+new file mode 100644
+index 0000000..c9afb9a
+--- /dev/null
++++ b/nova/virt/disk/mount.py
+@@ -0,0 +1,142 @@
++# vim: tabstop=4 shiftwidth=4 softtabstop=4
++
++# Copyright 2011 Red Hat, Inc.
++#
++# 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.
++"""Support for mounting virtual image files"""
++
++import os
++
++from nova import log as logging
++from nova import utils
++
++LOG = logging.getLogger('nova.compute.disk')
++
++
++class Mount(object):
++    """Standard mounting operations, that can be overridden by subclasses.
++
++    The basic device operations provided are get, map and mount,
++    to be called in that order.
++    """
++
++    def __init__(self, image, mount_dir, partition=None,
++                 disable_auto_fsck=False):
++
++        # Input
++        self.image = image
++        self.partition = partition
++        self.disable_auto_fsck = disable_auto_fsck
++        self.mount_dir = mount_dir
++
++        # Output
++        self.error = ""
++
++        # Internal
++        self.linked = self.mapped = self.mounted = False
++        self.device = self.mapped_device = None
++
++    def get_dev(self):
++        """Make the image available as a block device in the file system."""
++        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):
++        """Map partitions of the device to the file system namespace."""
++        assert(os.path.exists(self.device))
++
++        if self.partition:
++            map_path = '/dev/mapper/%sp%s' % (self.device.split('/')[-1],
++                                              self.partition)
++            assert(not os.path.exists(map_path))
++
++            # Note kpartx can output warnings to stderr and succeed
++            # Also it can output failures to stderr and "succeed"
++            # So we just go on the existence of the mapped device
++            _out, err = utils.trycmd('kpartx', '-a', self.device,
++                                     run_as_root=True, discard_warnings=True)
++
++            # Note kpartx does nothing when presented with a raw image,
++            # so given we only use it when we expect a partitioned image, fail
++            if not os.path.exists(map_path):
++                if not err:
++                    err = _('no partitions found')
++                self.error = _('Failed to map partitions: %s') % err
++            else:
++                self.mapped_device = map_path
++                self.mapped = True
++        else:
++            self.mapped_device = self.device
++            self.mapped = True
++
++        # This is an orthogonal operation
++        # which only needs to be done once
++        if self.disable_auto_fsck and self.mapped:
++            self.disable_auto_fsck = False
++            # attempt to set ext[234] so that it doesn't auto-fsck
++            _out, err = utils.trycmd('tune2fs', '-c', 0, '-i', 0,
++                                     self.mapped_device, run_as_root=True)
++            if err:
++                LOG.info(_('Failed to disable fs check: %s') % err)
++
++        return self.mapped
++
++    def unmap_dev(self):
++        """Remove 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.trycmd('mount', self.mapped_device, self.mount_dir,
++                                 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):
++        """Call the get, map and mnt operations."""
++        status = False
++        try:
++            status = self.get_dev() and self.map_dev() and self.mnt_dev()
++        finally:
++            if not status:
++                self.do_umount()
++        return status
++
++    def do_umount(self):
++        """Call the unmnt, unmap and unget operations."""
++        if self.mounted:
++            self.unmnt_dev()
++        if self.mapped:
++            self.unmap_dev()
++        if self.linked:
++            self.unget_dev()
+diff --git a/nova/virt/disk/nbd.py b/nova/virt/disk/nbd.py
+new file mode 100644
+index 0000000..55b287e
+--- /dev/null
++++ b/nova/virt/disk/nbd.py
+@@ -0,0 +1,95 @@
++# vim: tabstop=4 shiftwidth=4 softtabstop=4
++
++# Copyright 2011 Red Hat, Inc.
++#
++# 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.
++"""Support for mounting images with qemu-nbd"""
++
++import os
++import time
++
++from nova import flags
++from nova import utils
++from nova.virt.disk import mount
++
++FLAGS = flags.FLAGS
++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')
++
++
++class Mount(mount.Mount):
++    """qemu-nbd support disk 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.trycmd('qemu-nbd', '-c', device, self.image,
++                                 run_as_root=True)
++        if err:
++            self.error = _('qemu-nbd error: %s') % err
++            self._free_nbd(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(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
 diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py
 index ba1dc86..bba2e0b 100644
 --- a/nova/virt/libvirt/connection.py
diff --git a/0002-Bug-898257-support-handling-images-with-libguestfs.patch b/0002-Bug-898257-support-handling-images-with-libguestfs.patch
index 601f29c..1a1b012 100644
--- a/0002-Bug-898257-support-handling-images-with-libguestfs.patch
+++ b/0002-Bug-898257-support-handling-images-with-libguestfs.patch
@@ -1,5 +1,5 @@
-From 82436c3605de262de095790b2e7670e07f2253a3 Mon Sep 17 00:00:00 2001
-From: =?UTF-8?q?P=C3=A1draig=20Brady?= <P at draigBrady.com>
+From 89e0437aff4bf039568e04f05f95b5142e392c04 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?P=C3=A1draig=20Brady?= <pbrady at redhat.com>
 Date: Wed, 30 Nov 2011 17:00:17 +0000
 Subject: [PATCH 2/2] Bug#898257 support handling images with libguestfs
 
@@ -11,9 +11,10 @@ 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.
+* nova/virt/disk/base.py (img_handlers): Add guestfs to the default
+list of access methods to try, to act as a fallback.
+* nova/virt/disk/guestfs.py: A new plugin class to provide support
+for libguestfs mounting.
 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.
@@ -22,28 +23,72 @@ greater granularity of control over the image.
 
 Change-Id: I2e22c9d149fff7a73cd8cebaa280d68d3fb9096c
 ---
- nova/virt/disk.py |   42 ++++++++++++++++++++++++++++++++++++++++--
- 1 files changed, 40 insertions(+), 2 deletions(-)
+ nova/virt/disk/base.py    |    6 ++--
+ nova/virt/disk/guestfs.py |   88 +++++++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 91 insertions(+), 3 deletions(-)
+ create mode 100644 nova/virt/disk/guestfs.py
 
-diff --git a/nova/virt/disk.py b/nova/virt/disk.py
-index 0d6e65f..7ea2125 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')
+diff --git a/nova/virt/disk/base.py b/nova/virt/disk/base.py
+index 8b5acc4..9ae0bd1 100644
+--- a/nova/virt/disk/base.py
++++ b/nova/virt/disk/base.py
+@@ -33,7 +33,7 @@ from nova import exception
+ from nova import flags
+ from nova import log as logging
+ from nova import utils
+-from nova.virt.disk import loop, nbd
++from nova.virt.disk import loop, nbd, guestfs
+ 
+ LOG = logging.getLogger('nova.compute.disk')
+ FLAGS = flags.FLAGS
+@@ -44,7 +44,7 @@ flags.DEFINE_integer('block_size', 1024 * 1024 * 256,
+ flags.DEFINE_string('injected_network_template',
+                     utils.abspath('virt/interfaces.template'),
+                     'Template file for injected network')
 -flags.DEFINE_list('img_handlers', ['loop', 'nbd'],
 +flags.DEFINE_list('img_handlers', ['loop', 'nbd', 'guestfs'],
                      'Order of methods used to mount disk images')
  
  
-@@ -323,7 +323,45 @@ class _nbdMnt(_baseMnt):
-         self.device = None
- 
- 
--class _DiskImage(_loopMnt, _nbdMnt):
-+class _guestfsMnt(_baseMnt):
+@@ -131,7 +131,7 @@ class _DiskImage(object):
+     @staticmethod
+     def _handler_class(mode):
+         """Look up the appropriate class to use based on MODE."""
+-        for cls in (loop.Mount, nbd.Mount):
++        for cls in (loop.Mount, nbd.Mount, guestfs.Mount):
+             if cls.mode == mode:
+                 return cls
+         raise exception.Error(_("unknown disk image handler: %s" % mode))
+diff --git a/nova/virt/disk/guestfs.py b/nova/virt/disk/guestfs.py
+new file mode 100644
+index 0000000..6323dc8
+--- /dev/null
++++ b/nova/virt/disk/guestfs.py
+@@ -0,0 +1,88 @@
++# vim: tabstop=4 shiftwidth=4 softtabstop=4
++
++# Copyright 2011 Red Hat, Inc.
++#
++# 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.
++"""Support for mounting images with libguestfs"""
++
++import os
++
++from nova import utils
++from nova.virt.disk import mount
++
++
++class Mount(mount.Mount):
 +    """libguestfs support for arbitrary images."""
 +    mode = 'guestfs'
 +
@@ -56,35 +101,58 @@ index 0d6e65f..7ea2125 100644
 +
 +    def mnt_dev(self):
 +        args = ('guestmount', '--rw', '-a', self.image)
-+        if self.partition:
++        if self.partition == -1:
++            args += ('-i',)  # find the OS partition
++        elif self.partition:
 +            args += ('-m', '/dev/sda%d' % self.partition)
 +        else:
-+            args += ('-i',) # find the OS partition
++            # We don't resort to -i for this case yet,
++            # as some older versions of libguestfs
++            # have problems identifying ttylinux images for example
++            args += ('-m', '/dev/sda')
 +        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 = self.attempt(*args, discard_warnings=True)
++        # root access should not required for guestfs (if the user
++        # has permissions to fusermount (by being part of the fuse
++        # group for example)).  Also note the image and mount_dir
++        # have appropriate creditials at this point for read/write
++        # mounting by the nova user.  However currently there are
++        # subsequent access issues by both the nova and root users
++        # if the nova user mounts the image, as detailed here:
++        # https://bugzilla.redhat.com/show_bug.cgi?id=765814
++        _out, err = utils.trycmd(*args, discard_warnings=True,
++                                 run_as_root=True)
 +        if err:
 +            self.error = _('Failed to mount filesystem: %s') % err
++            # Be defensive and ensure this is unmounted,
++            # as I'm not sure guestmount will never have
++            # mounted when it returns EXIT_FAILURE.
++            # This is required if discard_warnings=False above
++            utils.trycmd('fusermount', '-u', self.mount_dir, run_as_root=True)
 +            return False
 +
++        # More defensiveness as there are edge cases where
++        # guestmount can return success while not mounting
++        try:
++            if not os.listdir(self.mount_dir):
++                # Assume we've just got the original empty temp dir
++                err = _('unknown guestmount error')
++                self.error = _('Failed to mount filesystem: %s') % err
++                return False
++        except OSError:
++            # This is the usual path and means root has
++            # probably mounted fine
++            pass
++
 +        self.mounted = True
 +        return True
 +
 +    def unmnt_dev(self):
 +        if not self.mounted:
 +            return
-+        # root users don't need a specific unmnt_dev
++        # root users don't need a specific unmnt_dev()
 +        # but ordinary users do
-+        utils.execute('fusermount', '-u', self.mount_dir)
++        utils.execute('fusermount', '-u', self.mount_dir, run_as_root=True)
 +        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 cf5c888..17fad7d 100644
--- a/openstack-nova.spec
+++ b/openstack-nova.spec
@@ -448,6 +448,7 @@ fi
 %changelog
 * Tue Dec 14 2011 Pádraig Brady <P at draigBrady.com> - 2011.3-12
 - Sanitize EC2 manifests and image tarballs (#767236, CVE 2011-4596)
+- update libguestfs support
 
 * Tue Dec 06 2011 Russell Bryant <rbryant at redhat.com> - 2011.3-11
 - Add --yes, --rootpw, and --novapw options to openstack-nova-db-setup.


More information about the scm-commits mailing list