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/api.py: A new file containing the nova.virt.disk module interface. (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. nova/virt/xenapi/vm_utils.py: Adjust for the moved virt.disk package Change-Id: If3a4b1c8f4e2f2e7300a21071340dcc839cb36d7
This commit is contained in:
parent
b2f36879b0
commit
5335b4ab0e
1
Authors
1
Authors
|
@ -112,6 +112,7 @@ Naveed Massjouni <naveedm9@gmail.com>
|
|||
Nikolay Sokolov <nsokolov@griddynamics.com>
|
||||
Nirmal Ranganathan <nirmal.ranganathan@rackspace.com>
|
||||
Ollie Leahy <oliver.leahy@hp.com>
|
||||
Pádraig Brady <pbrady@redhat.com>
|
||||
Paul Voccio <paul@openstack.org>
|
||||
Renuka Apte <renuka.apte@citrix.com>
|
||||
Ricardo Carrillo Cruz <emaildericky@gmail.com>
|
||||
|
|
|
@ -19,53 +19,50 @@
|
|||
from nova.rootwrap.filters import CommandFilter, DnsmasqFilter
|
||||
|
||||
filters = [
|
||||
# nova/virt/disk.py: 'kpartx', '-a', device
|
||||
# nova/virt/disk.py: 'kpartx', '-d', device
|
||||
# nova/virt/disk/mount.py: 'kpartx', '-a', device
|
||||
# nova/virt/disk/mount.py: 'kpartx', '-d', device
|
||||
CommandFilter("/sbin/kpartx", "root"),
|
||||
|
||||
# nova/virt/disk.py: 'tune2fs', '-c', 0, '-i', 0, mapped_device
|
||||
# nova/virt/disk/mount.py: 'tune2fs', '-c', 0, '-i', 0, mapped_device
|
||||
# nova/virt/xenapi/vm_utils.py: "tune2fs", "-O ^has_journal", part_path
|
||||
# nova/virt/xenapi/vm_utils.py: "tune2fs", "-j", partition_path
|
||||
CommandFilter("/sbin/tune2fs", "root"),
|
||||
|
||||
# nova/virt/disk.py: 'mount', mapped_device, tmpdir
|
||||
# nova/virt/disk.py: 'mount', device, container_dir
|
||||
# nova/virt/disk.py: 'mount'
|
||||
# nova/virt/disk/mount.py: 'mount', mapped_device, mount_dir
|
||||
# nova/virt/xenapi/vm_utils.py: 'mount', '-t', 'ext2,ext3,ext4,reiserfs'..
|
||||
CommandFilter("/bin/mount", "root"),
|
||||
|
||||
# nova/virt/disk.py: 'umount', mapped_device
|
||||
# nova/virt/disk.py: 'umount', container_dir
|
||||
# nova/virt/disk/mount.py: 'umount', mapped_device
|
||||
# nova/virt/xenapi/vm_utils.py: 'umount', dev_path
|
||||
CommandFilter("/bin/umount", "root"),
|
||||
|
||||
# nova/virt/disk.py: 'qemu-nbd', '-c', device, image
|
||||
# nova/virt/disk.py: 'qemu-nbd', '-d', device
|
||||
# nova/virt/disk/nbd.py: 'qemu-nbd', '-c', device, image
|
||||
# nova/virt/disk/nbd.py: 'qemu-nbd', '-d', device
|
||||
CommandFilter("/usr/bin/qemu-nbd", "root"),
|
||||
|
||||
# nova/virt/disk.py: 'losetup', '--find', '--show', image
|
||||
# nova/virt/disk.py: 'losetup', '--detach', device
|
||||
# nova/virt/disk/loop.py: 'losetup', '--find', '--show', image
|
||||
# nova/virt/disk/loop.py: 'losetup', '--detach', device
|
||||
CommandFilter("/sbin/losetup", "root"),
|
||||
|
||||
# nova/virt/disk.py: 'tee', metadata_path
|
||||
# nova/virt/disk.py: 'tee', '-a', keyfile
|
||||
# nova/virt/disk.py: 'tee', netfile
|
||||
# nova/virt/disk/api.py: 'tee', metadata_path
|
||||
# nova/virt/disk/api.py: 'tee', '-a', keyfile
|
||||
# nova/virt/disk/api.py: 'tee', netfile
|
||||
CommandFilter("/usr/bin/tee", "root"),
|
||||
|
||||
# nova/virt/disk.py: 'mkdir', '-p', sshdir
|
||||
# nova/virt/disk.py: 'mkdir', '-p', netdir
|
||||
# nova/virt/disk/api.py: 'mkdir', '-p', sshdir
|
||||
# nova/virt/disk/api.py: 'mkdir', '-p', netdir
|
||||
CommandFilter("/bin/mkdir", "root"),
|
||||
|
||||
# nova/virt/disk.py: 'chown', 'root', sshdir
|
||||
# nova/virt/disk.py: 'chown', 'root:root', netdir
|
||||
# nova/virt/disk/api.py: 'chown', 'root', sshdir
|
||||
# nova/virt/disk/api.py: 'chown', 'root:root', netdir
|
||||
# nova/virt/libvirt/connection.py: 'chown', os.getuid(), console_log
|
||||
# nova/virt/libvirt/connection.py: 'chown', os.getuid(), console_log
|
||||
# nova/virt/libvirt/connection.py: 'chown', 'root', basepath('disk')
|
||||
# nova/virt/xenapi/vm_utils.py: 'chown', os.getuid(), dev_path
|
||||
CommandFilter("/bin/chown", "root"),
|
||||
|
||||
# nova/virt/disk.py: 'chmod', '700', sshdir
|
||||
# nova/virt/disk.py: 'chmod', 755, netdir
|
||||
# nova/virt/disk/api.py: 'chmod', '700', sshdir
|
||||
# nova/virt/disk/api.py: 'chmod', 755, netdir
|
||||
CommandFilter("/bin/chmod", "root"),
|
||||
|
||||
# nova/virt/libvirt/vif.py: 'ip', 'tuntap', 'add', dev, 'mode', 'tap'
|
||||
|
|
|
@ -36,7 +36,7 @@ from nova import utils
|
|||
from nova.api.ec2 import cloud
|
||||
from nova.compute import power_state
|
||||
from nova.compute import vm_states
|
||||
from nova.virt import disk
|
||||
from nova.virt.disk import api as disk
|
||||
from nova.virt import images
|
||||
from nova.virt import driver
|
||||
from nova.virt.libvirt import connection
|
||||
|
|
|
@ -238,6 +238,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))
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
# 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.
|
||||
|
||||
"""
|
|
@ -28,13 +28,13 @@ Includes injection of SSH PGP keys into authorized_keys file.
|
|||
import json
|
||||
import os
|
||||
import tempfile
|
||||
import time
|
||||
|
||||
from nova import exception
|
||||
from nova import flags
|
||||
from nova import log as logging
|
||||
from nova import utils
|
||||
|
||||
from nova.virt.disk import loop
|
||||
from nova.virt.disk import nbd
|
||||
|
||||
LOG = logging.getLogger('nova.compute.disk')
|
||||
FLAGS = flags.FLAGS
|
||||
|
@ -43,10 +43,9 @@ flags.DEFINE_integer('minimum_root_size', 1024 * 1024 * 1024 * 10,
|
|||
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')
|
||||
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,
|
||||
|
@ -101,8 +100,89 @@ def extend(image, size):
|
|||
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, 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
|
||||
|
@ -111,57 +191,19 @@ def inject_data(image, key=None, net=None, metadata=None,
|
|||
If partition is not specified it mounts the image as a single partition.
|
||||
|
||||
"""
|
||||
device = _link_device(image, nbd)
|
||||
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
|
||||
|
||||
img = _DiskImage(image=image, partition=partition, use_cow=use_cow,
|
||||
disable_auto_fsck=disable_auto_fsck)
|
||||
if img.mount():
|
||||
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)
|
||||
inject_data_into_fs(img.mount_dir, key, net, metadata,
|
||||
utils.execute)
|
||||
finally:
|
||||
if not partition is None:
|
||||
# remove partitions
|
||||
utils.execute('kpartx', '-d', device, run_as_root=True)
|
||||
finally:
|
||||
_unlink_device(device, nbd)
|
||||
img.umount()
|
||||
else:
|
||||
raise exception.Error(img.errors)
|
||||
|
||||
|
||||
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
|
||||
|
@ -170,86 +212,30 @@ def setup_container(image, container_dir=None, nbd=False):
|
|||
LXC does not support qcow2 images yet.
|
||||
"""
|
||||
try:
|
||||
device = _link_device(image, nbd)
|
||||
utils.execute('mount', device, container_dir, run_as_root=True)
|
||||
img = _DiskImage(image=image, use_cow=use_cow, mount_dir=container_dir)
|
||||
if img.mount():
|
||||
return img
|
||||
else:
|
||||
raise exception.Error(img.errors)
|
||||
except Exception, exn:
|
||||
LOG.exception(_('Failed to mount filesystem: %s'), exn)
|
||||
_unlink_device(device, nbd)
|
||||
|
||||
|
||||
def destroy_container(target, instance, nbd=False):
|
||||
def destroy_container(img):
|
||||
"""Destroy the container once it terminates.
|
||||
|
||||
It will umount the container that is mounted, try to find the loopback
|
||||
device associated with the container and delete it.
|
||||
It will umount the container that is mounted,
|
||||
and delete any linked devices.
|
||||
|
||||
LXC does not support qcow2 images yet.
|
||||
"""
|
||||
out, err = utils.execute('mount', run_as_root=True)
|
||||
for loop in out.splitlines():
|
||||
if instance['name'] in loop:
|
||||
device = loop.split()[0]
|
||||
|
||||
try:
|
||||
container_dir = '%s/rootfs' % target
|
||||
utils.execute('umount', container_dir, run_as_root=True)
|
||||
_unlink_device(device, nbd)
|
||||
if img:
|
||||
img.umount()
|
||||
except Exception, exn:
|
||||
LOG.exception(_('Failed to remove container: %s'), exn)
|
||||
|
||||
|
||||
def _link_device(image, nbd):
|
||||
"""Link image to device using loopback or nbd"""
|
||||
|
||||
if nbd:
|
||||
device = _allocate_device()
|
||||
utils.execute('qemu-nbd', '-c', device, image, run_as_root=True)
|
||||
# NOTE(vish): this forks into another process, so give it a chance
|
||||
# to set up before continuuing
|
||||
for i in xrange(FLAGS.timeout_nbd):
|
||||
if os.path.exists("/sys/block/%s/pid" % os.path.basename(device)):
|
||||
return device
|
||||
time.sleep(1)
|
||||
raise exception.Error(_('nbd device %s did not show up') % device)
|
||||
else:
|
||||
out, err = utils.execute('losetup', '--find', '--show', image,
|
||||
run_as_root=True)
|
||||
if err:
|
||||
raise exception.Error(_('Could not attach image to loopback: %s')
|
||||
% err)
|
||||
return out.strip()
|
||||
|
||||
|
||||
def _unlink_device(device, nbd):
|
||||
"""Unlink image from device using loopback or nbd"""
|
||||
if nbd:
|
||||
utils.execute('qemu-nbd', '-d', device, run_as_root=True)
|
||||
_free_device(device)
|
||||
else:
|
||||
utils.execute('losetup', '--detach', device, run_as_root=True)
|
||||
|
||||
|
||||
_DEVICES = ['/dev/nbd%s' % i for i in xrange(FLAGS.max_nbd_devices)]
|
||||
|
||||
|
||||
def _allocate_device():
|
||||
# NOTE(vish): This assumes no other processes are allocating nbd devices.
|
||||
# It may race cause a race condition if multiple
|
||||
# workers are running on a given machine.
|
||||
|
||||
while True:
|
||||
if not _DEVICES:
|
||||
raise exception.Error(_('No free nbd devices'))
|
||||
device = _DEVICES.pop()
|
||||
if not os.path.exists("/sys/block/%s/pid" % os.path.basename(device)):
|
||||
break
|
||||
return device
|
||||
|
||||
|
||||
def _free_device(device):
|
||||
_DEVICES.append(device)
|
||||
|
||||
|
||||
def inject_data_into_fs(fs, key, net, metadata, execute):
|
||||
"""Injects data into a filesystem already mounted by the caller.
|
||||
Virt connections can call this directly if they mount their fs
|
|
@ -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
|
|
@ -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()
|
|
@ -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
|
|
@ -63,7 +63,7 @@ from nova import flags
|
|||
import nova.image
|
||||
from nova import log as logging
|
||||
from nova import utils
|
||||
from nova.virt import disk
|
||||
from nova.virt.disk import api as disk
|
||||
from nova.virt import driver
|
||||
from nova.virt import images
|
||||
from nova.virt.libvirt import utils as libvirt_utils
|
||||
|
@ -181,6 +181,7 @@ class LibvirtConnection(driver.ComputeDriver):
|
|||
|
||||
self._host_state = None
|
||||
self._wrapped_conn = None
|
||||
self.container = None
|
||||
self.read_only = read_only
|
||||
|
||||
fw_class = utils.import_class(FLAGS.firewall_driver)
|
||||
|
@ -404,7 +405,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)
|
||||
|
||||
|
@ -1037,11 +1038,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]:
|
||||
|
@ -1051,8 +1052,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
|
||||
|
@ -1060,9 +1061,9 @@ class LibvirtConnection(driver.ComputeDriver):
|
|||
' data into image %(img_id)s (%(e)s)') % locals())
|
||||
|
||||
if FLAGS.libvirt_type == 'lxc':
|
||||
disk.setup_container(basepath('disk'),
|
||||
container_dir=container_dir,
|
||||
nbd=FLAGS.use_cow_images)
|
||||
self.container = disk.setup_container(basepath('disk'),
|
||||
container_dir=container_dir,
|
||||
use_cow=FLAGS.use_cow_images)
|
||||
|
||||
if FLAGS.libvirt_type == 'uml':
|
||||
libvirt_utils.chown(basepath('disk'), 'root')
|
||||
|
|
|
@ -26,7 +26,7 @@ import shutil
|
|||
from nova import exception
|
||||
from nova import flags
|
||||
from nova import utils
|
||||
from nova.virt import disk
|
||||
from nova.virt.disk import api as disk
|
||||
from nova.virt import images
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
|
|
|
@ -40,7 +40,7 @@ from nova import log as logging
|
|||
from nova import utils
|
||||
from nova.compute import instance_types
|
||||
from nova.compute import power_state
|
||||
from nova.virt import disk
|
||||
from nova.virt.disk import api as disk
|
||||
from nova.virt.xenapi import HelperBase
|
||||
from nova.virt.xenapi import volume_utils
|
||||
|
||||
|
|
Loading…
Reference in New Issue