Sophie

Sophie

distrib > Fedora > 16 > i386 > by-pkgid > 5c655bb31b7eacedb96e8b5da992c6ce > files > 17

openstack-nova-2011.3.1-11.fc16.src.rpm

From dfc0e2be1ceac19ce92cff8fc3ea4e1c79c8ad56 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?P=C3=A1draig=20Brady?= <pbrady@redhat.com>
Date: Wed, 30 Nov 2011 17:00:17 +0000
Subject: [PATCH] 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/mount.py: A new Mount class to abstract the
devce allocation, partition mapping and file sys mounting,
for each access type.
* 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, 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/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 +-
 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/utils.py b/nova/utils.py
index 696e060..2062293 100644
--- a/nova/utils.py
+++ b/nova/utils.py
@@ -203,6 +203,36 @@ def execute(*cmd, **kwargs):
             greenthread.sleep(0)
 
 
+def trycmd(*args, **kwargs):
+    """
+    A wrapper around execute() to more easily handle warnings and errors.
+
+    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
+
+    """
+    discard_warnings = kwargs.pop('discard_warnings', False)
+
+    try:
+        out, err = execute(*args, **kwargs)
+        failed = False
+    except exception.ProcessExecutionError, exn:
+        out, err = '', str(exn)
+        LOG.debug(err)
+        failed = True
+
+    if not failed and discard_warnings and err:
+        # Handle commands that output to stderr but otherwise succeed
+        LOG.debug(err)
+        err = ''
+
+    return out, err
+
+
 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):
-    """Injects a ssh key and optionally net data into a disk image.
-
-    it will mount the image as a fully partitioned disk and attempt to inject
-    into the specified partition number.
-
-    If partition is not specified it mounts the image as a single partition.
-
-    """
-    device = _link_device(image, nbd)
-    try:
-        if not partition is None:
-            # create partition
-            out, err = utils.execute('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)
-        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)
-    finally:
-        _unlink_device(device, nbd)
-
-
-def setup_container(image, container_dir=None, nbd=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:
-        device = _link_device(image, nbd)
-        utils.execute('mount', device, container_dir, run_as_root=True)
-    except Exception, exn:
-        LOG.exception(_('Failed to mount filesystem: %s'), exn)
-        _unlink_device(device, nbd)
-
-
-def destroy_container(target, instance, nbd=False):
-    """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.
-
-    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)
-    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
-    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 17667ea..58c63d2 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)
@@ -348,7 +349,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)
 
@@ -1004,11 +1005,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]:
@@ -1018,8 +1019,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
@@ -1027,9 +1028,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)