Merge "Bug#898257 abstract out disk image access methods"
This commit is contained in:
1
Authors
1
Authors
@@ -113,6 +113,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))
|
||||
|
21
nova/virt/disk/__init__.py
Normal file
21
nova/virt/disk/__init__.py
Normal file
@@ -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
|
41
nova/virt/disk/loop.py
Normal file
41
nova/virt/disk/loop.py
Normal file
@@ -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
|
142
nova/virt/disk/mount.py
Normal file
142
nova/virt/disk/mount.py
Normal file
@@ -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()
|
95
nova/virt/disk/nbd.py
Normal file
95
nova/virt/disk/nbd.py
Normal file
@@ -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
|
||||
|
||||
|
Reference in New Issue
Block a user