[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