virt: convert disk mount API to use nova.virt.image.model

Convert the nova.virt.disk.mount classes to use the
nova.virt.image.model classes instead of manually
passing a filename and image format as parameters.

Related-Bug: #1257674
Change-Id: Ibabcf48c95185ba443ef7575590eae6a63e0f8e5
This commit is contained in:
Daniel P. Berrange 2014-10-29 18:10:37 +00:00
parent 79af02046d
commit 0ea510f556
11 changed files with 150 additions and 67 deletions

View File

@ -1892,3 +1892,7 @@ class EnumFieldUnset(Invalid):
class InvalidImageFormat(Invalid):
msg_fmt = _("Invalid image format '%(format)s'")
class UnsupportedImageModel(Invalid):
msg_fmt = _("Image model '%(image)s' is not supported")

View File

@ -18,6 +18,7 @@ import fixtures
from nova import test
from nova.virt.disk.mount import loop
from nova.virt.image import model as imgmodel
def _fake_noop(*args, **kwargs):
@ -33,9 +34,15 @@ def _fake_trycmd_losetup_fails(*args, **kwards):
class LoopTestCase(test.NoDBTestCase):
def setUp(self):
super(LoopTestCase, self).setUp()
self.file = imgmodel.LocalFileImage("/some/file.qcow2",
imgmodel.FORMAT_QCOW2)
def test_get_dev(self):
tempdir = self.useFixture(fixtures.TempDir()).path
l = loop.LoopMount(None, tempdir)
l = loop.LoopMount(self.file, tempdir)
self.useFixture(fixtures.MonkeyPatch('nova.utils.trycmd',
_fake_trycmd_losetup_works))
self.useFixture(fixtures.MonkeyPatch('nova.utils.execute',
@ -55,7 +62,7 @@ class LoopTestCase(test.NoDBTestCase):
def test_inner_get_dev_fails(self):
tempdir = self.useFixture(fixtures.TempDir()).path
l = loop.LoopMount(None, tempdir)
l = loop.LoopMount(self.file, tempdir)
self.useFixture(fixtures.MonkeyPatch('nova.utils.trycmd',
_fake_trycmd_losetup_fails))
@ -72,7 +79,7 @@ class LoopTestCase(test.NoDBTestCase):
def test_get_dev_timeout(self):
tempdir = self.useFixture(fixtures.TempDir()).path
l = loop.LoopMount(None, tempdir)
l = loop.LoopMount(self.file, tempdir)
self.useFixture(fixtures.MonkeyPatch('time.sleep', _fake_noop))
self.useFixture(fixtures.MonkeyPatch('nova.utils.trycmd',
_fake_trycmd_losetup_fails))
@ -89,7 +96,7 @@ class LoopTestCase(test.NoDBTestCase):
def test_unget_dev(self):
tempdir = self.useFixture(fixtures.TempDir()).path
l = loop.LoopMount(None, tempdir)
l = loop.LoopMount(self.file, tempdir)
self.useFixture(fixtures.MonkeyPatch('nova.utils.execute',
_fake_noop))

View File

@ -23,6 +23,7 @@ import fixtures
from nova import test
from nova.virt.disk.mount import nbd
from nova.virt.image import model as imgmodel
ORIG_EXISTS = os.path.exists
ORIG_LISTDIR = os.listdir
@ -67,24 +68,26 @@ class NbdTestCase(test.NoDBTestCase):
_fake_detect_nbd_devices)
self.useFixture(fixtures.MonkeyPatch('os.listdir',
_fake_listdir_nbd_devices))
self.file = imgmodel.LocalFileImage("/some/file.qcow2",
imgmodel.FORMAT_QCOW2)
def test_nbd_no_devices(self):
tempdir = self.useFixture(fixtures.TempDir()).path
self.stubs.Set(nbd.NbdMount, '_detect_nbd_devices',
_fake_detect_nbd_devices_none)
n = nbd.NbdMount(None, tempdir)
n = nbd.NbdMount(self.file, tempdir)
self.assertIsNone(n._allocate_nbd())
def test_nbd_no_free_devices(self):
tempdir = self.useFixture(fixtures.TempDir()).path
n = nbd.NbdMount(None, tempdir)
n = nbd.NbdMount(self.file, tempdir)
self.useFixture(fixtures.MonkeyPatch('os.path.exists',
_fake_exists_all_used))
self.assertIsNone(n._allocate_nbd())
def test_nbd_not_loaded(self):
tempdir = self.useFixture(fixtures.TempDir()).path
n = nbd.NbdMount(None, tempdir)
n = nbd.NbdMount(self.file, tempdir)
# Fake out os.path.exists
def fake_exists(path):
@ -101,7 +104,7 @@ class NbdTestCase(test.NoDBTestCase):
def test_nbd_allocation(self):
tempdir = self.useFixture(fixtures.TempDir()).path
n = nbd.NbdMount(None, tempdir)
n = nbd.NbdMount(self.file, tempdir)
self.useFixture(fixtures.MonkeyPatch('os.path.exists',
_fake_exists_no_users))
self.useFixture(fixtures.MonkeyPatch('random.shuffle', _fake_noop))
@ -111,7 +114,7 @@ class NbdTestCase(test.NoDBTestCase):
def test_nbd_allocation_one_in_use(self):
tempdir = self.useFixture(fixtures.TempDir()).path
n = nbd.NbdMount(None, tempdir)
n = nbd.NbdMount(self.file, tempdir)
self.useFixture(fixtures.MonkeyPatch('random.shuffle', _fake_noop))
# Fake out os.path.exists
@ -135,12 +138,12 @@ class NbdTestCase(test.NoDBTestCase):
tempdir = self.useFixture(fixtures.TempDir()).path
self.stubs.Set(nbd.NbdMount, '_detect_nbd_devices',
_fake_detect_nbd_devices_none)
n = nbd.NbdMount(None, tempdir)
n = nbd.NbdMount(self.file, tempdir)
self.assertFalse(n._inner_get_dev())
def test_inner_get_dev_qemu_fails(self):
tempdir = self.useFixture(fixtures.TempDir()).path
n = nbd.NbdMount(None, tempdir)
n = nbd.NbdMount(self.file, tempdir)
self.useFixture(fixtures.MonkeyPatch('os.path.exists',
_fake_exists_no_users))
@ -155,7 +158,7 @@ class NbdTestCase(test.NoDBTestCase):
def test_inner_get_dev_qemu_timeout(self):
tempdir = self.useFixture(fixtures.TempDir()).path
n = nbd.NbdMount(None, tempdir)
n = nbd.NbdMount(self.file, tempdir)
self.useFixture(fixtures.MonkeyPatch('os.path.exists',
_fake_exists_no_users))
@ -195,7 +198,7 @@ class NbdTestCase(test.NoDBTestCase):
def test_inner_get_dev_works(self):
tempdir = self.useFixture(fixtures.TempDir()).path
n = nbd.NbdMount(None, tempdir)
n = nbd.NbdMount(self.file, tempdir)
self.useFixture(fixtures.MonkeyPatch('random.shuffle', _fake_noop))
self.useFixture(fixtures.MonkeyPatch('os.path.exists',
self.fake_exists_one))
@ -219,13 +222,13 @@ class NbdTestCase(test.NoDBTestCase):
# This test is just checking we don't get an exception when we unget
# something we don't have
tempdir = self.useFixture(fixtures.TempDir()).path
n = nbd.NbdMount(None, tempdir)
n = nbd.NbdMount(self.file, tempdir)
self.useFixture(fixtures.MonkeyPatch('nova.utils.execute', _fake_noop))
n.unget_dev()
def test_get_dev(self):
tempdir = self.useFixture(fixtures.TempDir()).path
n = nbd.NbdMount(None, tempdir)
n = nbd.NbdMount(self.file, tempdir)
self.useFixture(fixtures.MonkeyPatch('random.shuffle', _fake_noop))
self.useFixture(fixtures.MonkeyPatch('nova.utils.execute', _fake_noop))
self.useFixture(fixtures.MonkeyPatch('os.path.exists',
@ -252,7 +255,7 @@ class NbdTestCase(test.NoDBTestCase):
self.stubs.Set(nbd.NbdMount, '_inner_get_dev', fake_get_dev_fails)
tempdir = self.useFixture(fixtures.TempDir()).path
n = nbd.NbdMount(None, tempdir)
n = nbd.NbdMount(self.file, tempdir)
self.useFixture(fixtures.MonkeyPatch('random.shuffle', _fake_noop))
self.useFixture(fixtures.MonkeyPatch('time.sleep', _fake_noop))
self.useFixture(fixtures.MonkeyPatch('nova.utils.execute', _fake_noop))
@ -318,7 +321,7 @@ class NbdTestCase(test.NoDBTestCase):
pid_exists))
def get_a_device():
n = nbd.NbdMount(None, tempdir)
n = nbd.NbdMount(self.file, tempdir)
n.get_dev()
chosen_devices.append(n.device)

View File

@ -23,13 +23,14 @@ from nova import test
from nova import utils
from nova.virt.disk import api
from nova.virt.disk.mount import api as mount
from nova.virt.image import model as imgmodel
class FakeMount(object):
device = None
@staticmethod
def instance_for_format(imgfile, mountdir, partition, imgfmt):
def instance_for_format(image, mountdir, partition):
return FakeMount()
def get_dev(self):
@ -115,10 +116,11 @@ class APITestCase(test.NoDBTestCase):
imgsize = 10
device = "/dev/sdh"
use_cow = True
image = imgmodel.LocalFileImage(imgfile, imgmodel.FORMAT_QCOW2)
self.flags(resize_fs_using_block_device=True)
mounter = FakeMount.instance_for_format(
imgfile, None, None, 'qcow2')
image, None, None)
mounter.device = device
self.mox.StubOutWithMock(api, 'can_resize_image')
@ -133,8 +135,7 @@ class APITestCase(test.NoDBTestCase):
api.can_resize_image(imgfile, imgsize).AndReturn(True)
utils.execute('qemu-img', 'resize', imgfile, imgsize)
api.is_image_extendable(imgfile, use_cow).AndReturn(True)
mount.Mount.instance_for_format(
imgfile, None, None, 'qcow2').AndReturn(mounter)
mount.Mount.instance_for_format(image, None, None).AndReturn(mounter)
mounter.get_dev().AndReturn(True)
api.resize2fs(mounter.device, run_as_root=True, check_exit_code=[0])
mounter.unget_dev()

View File

@ -23,6 +23,7 @@ from nova import test
from nova.tests.unit import utils as tests_utils
import nova.utils
from nova.virt.disk.vfs import localfs as vfsimpl
from nova.virt.image import model as imgmodel
CONF = cfg.CONF
@ -432,7 +433,9 @@ class VirtDiskVFSLocalFSTest(test.NoDBTestCase):
self.assertTrue(mkdtemp.called)
NbdMount.assert_called_once_with(
'img.qcow2', 'tmp/', None)
imgmodel.LocalFileImage('img.qcow2',
imgmodel.FORMAT_QCOW2),
'tmp/', None)
mounter.do_mount.assert_called_once_with()
@mock.patch.object(tempfile, 'mkdtemp')
@ -448,5 +451,7 @@ class VirtDiskVFSLocalFSTest(test.NoDBTestCase):
self.assertTrue(mkdtemp.called)
NbdMount.assert_called_once_with(
'img.qcow2', 'tmp/', None)
imgmodel.LocalFileImage('img.qcow2',
imgmodel.FORMAT_QCOW2),
'tmp/', None)
self.assertFalse(mounter.do_mount.called)

View File

@ -131,7 +131,7 @@ class TestDiskImage(test.NoDBTestCase):
mountdir = '/mnt/fake_rootfs'
fakemount = FakeMount(image, mountdir, None)
def fake_instance_for_format(imgfile, mountdir, partition, imgfmt):
def fake_instance_for_format(image, mountdir, partition):
return fakemount
self.stubs.Set(mount.Mount, 'instance_for_format',
@ -149,7 +149,7 @@ class TestDiskImage(test.NoDBTestCase):
mountdir = '/mnt/fake_rootfs'
fakemount = FakeMount(image, mountdir, None)
def fake_instance_for_format(imgfile, mountdir, partition, imgfmt):
def fake_instance_for_format(image, mountdir, partition):
return fakemount
self.stubs.Set(mount.Mount, 'instance_for_format',
@ -169,7 +169,7 @@ class TestDiskImage(test.NoDBTestCase):
mountdir = '/mnt/fake_rootfs'
fakemount = FakeMount(image, mountdir, None)
def fake_instance_for_format(imgfile, mountdir, partition, imgfmt):
def fake_instance_for_format(image, mountdir, partition):
return fakemount
self.stubs.Set(mount.Mount, 'instance_for_format',
@ -200,8 +200,8 @@ class TestVirtDisk(test.NoDBTestCase):
def proc_mounts(self, mount_point):
return None
def fake_instance_for_format(imgfile, mountdir, partition, imgfmt):
return FakeMount(imgfile, mountdir, partition)
def fake_instance_for_format(image, mountdir, partition):
return FakeMount(image, mountdir, partition)
self.stubs.Set(os.path, 'exists', lambda _: True)
self.stubs.Set(disk_api._DiskImage, '_device_for_path', proc_mounts)

View File

@ -42,6 +42,7 @@ from nova.i18n import _LW
from nova import utils
from nova.virt.disk.mount import api as mount
from nova.virt.disk.vfs import api as vfs
from nova.virt.image import model as imgmodel
from nova.virt import images
@ -173,7 +174,12 @@ def get_disk_size(path):
def extend(image, size, use_cow=False):
"""Increase image to size."""
"""Increase image to size.
:param image: path to disk image file
:param size: image size in bytes
:param use_cow: whether the disk is a qcow2 file
"""
if not can_resize_image(image, size):
return
@ -200,7 +206,9 @@ def extend(image, size, use_cow=False):
# in case of non-raw disks we can't just resize the image, but
# rather the mounted device instead
mounter = mount.Mount.instance_for_format(
image, None, None, 'qcow2')
imgmodel.LocalFileImage(image,
imgmodel.FORMAT_QCOW2),
None, None)
if mounter.get_dev():
safe_resize2fs(mounter.device,
run_as_root=True,
@ -270,11 +278,24 @@ class _DiskImage(object):
tmp_prefix = 'openstack-disk-mount-tmp'
def __init__(self, image, partition=None, use_cow=False, mount_dir=None):
"""Create a new _DiskImage object instance
:param image: the path to the disk image file
:param partition: the partition number within the image
:param use_cow: whether the disk is in qcow2 format
:param mount_dir: the directory to mount the image on
"""
# These passed to each mounter
self.image = image
self.partition = partition
self.mount_dir = mount_dir
self.use_cow = use_cow
if use_cow:
self.image = imgmodel.LocalFileImage(image,
imgmodel.FORMAT_QCOW2)
else:
self.image = imgmodel.LocalFileImage(image,
imgmodel.FORMAT_RAW)
# Internal
self._mkdir = False
@ -328,14 +349,10 @@ class _DiskImage(object):
self.mount_dir = tempfile.mkdtemp(prefix=self.tmp_prefix)
self._mkdir = True
imgfmt = "raw"
if self.use_cow:
imgfmt = "qcow2"
mounter = mount.Mount.instance_for_format(self.image,
self.mount_dir,
self.partition,
imgfmt)
self.partition)
if mounter.do_mount():
self._mounter = mounter
return self._mounter.device

View File

@ -19,8 +19,10 @@ import time
from oslo_log import log as logging
from oslo_utils import importutils
from nova import exception
from nova.i18n import _, _LI, _LW
from nova import utils
from nova.virt.image import model as imgmodel
LOG = logging.getLogger(__name__)
@ -37,42 +39,79 @@ class Mount(object):
mode = None # to be overridden in subclasses
@staticmethod
def instance_for_format(imgfile, mountdir, partition, imgfmt):
LOG.debug("Instance for format imgfile=%(imgfile)s "
"mountdir=%(mountdir)s partition=%(partition)s "
"imgfmt=%(imgfmt)s",
{'imgfile': imgfile, 'mountdir': mountdir,
'partition': partition, 'imgfmt': imgfmt})
if imgfmt == "raw":
LOG.debug("Using LoopMount")
return importutils.import_object(
"nova.virt.disk.mount.loop.LoopMount",
imgfile, mountdir, partition)
def instance_for_format(image, mountdir, partition):
"""Get a Mount instance for the image type
:param image: instance of nova.virt.image.model.Image
:param mountdir: path to mount the image at
:param partition: partition number to mount
"""
LOG.debug("Instance for format image=%(image)s "
"mountdir=%(mountdir)s partition=%(partition)s",
{'image': image, 'mountdir': mountdir,
'partition': partition})
if isinstance(image, imgmodel.LocalFileImage):
if image.format == imgmodel.FORMAT_RAW:
LOG.debug("Using LoopMount")
return importutils.import_object(
"nova.virt.disk.mount.loop.LoopMount",
image, mountdir, partition)
else:
LOG.debug("Using NbdMount")
return importutils.import_object(
"nova.virt.disk.mount.nbd.NbdMount",
image, mountdir, partition)
else:
LOG.debug("Using NbdMount")
return importutils.import_object(
"nova.virt.disk.mount.nbd.NbdMount",
imgfile, mountdir, partition)
# TODO(berrange) we could mount images of
# type LocalBlockImage directly without
# involving loop or nbd devices
#
# We could also mount RBDImage directly
# using kernel RBD block dev support.
#
# This is left as an enhancement for future
# motivated developers todo, since raising
# an exception is on par with what this
# code did historically
raise exception.UnsupportedImageModel(
image.__class__.__name__)
@staticmethod
def instance_for_device(imgfile, mountdir, partition, device):
LOG.debug("Instance for device imgfile=%(imgfile)s "
def instance_for_device(image, mountdir, partition, device):
"""Get a Mount instance for the device type
:param image: instance of nova.virt.image.model.Image
:param mountdir: path to mount the image at
:param partition: partition number to mount
:param device: mounted device path
"""
LOG.debug("Instance for device image=%(image)s "
"mountdir=%(mountdir)s partition=%(partition)s "
"device=%(device)s",
{'imgfile': imgfile, 'mountdir': mountdir,
{'image': image, 'mountdir': mountdir,
'partition': partition, 'device': device})
if "loop" in device:
LOG.debug("Using LoopMount")
return importutils.import_object(
"nova.virt.disk.mount.loop.LoopMount",
imgfile, mountdir, partition, device)
image, mountdir, partition, device)
else:
LOG.debug("Using NbdMount")
return importutils.import_object(
"nova.virt.disk.mount.nbd.NbdMount",
imgfile, mountdir, partition, device)
image, mountdir, partition, device)
def __init__(self, image, mount_dir, partition=None, device=None):
"""Create a new Mount instance
:param image: instance of nova.virt.image.model.Image
:param mount_dir: path to mount the image at
:param partition: partition number to mount
:param device: mounted device path
"""
# Input
self.image = image

View File

@ -27,7 +27,8 @@ class LoopMount(api.Mount):
mode = 'loop'
def _inner_get_dev(self):
out, err = utils.trycmd('losetup', '--find', '--show', self.image,
out, err = utils.trycmd('losetup', '--find', '--show',
self.image.path,
run_as_root=True)
if err:
self.error = _('Could not attach image to loopback: %s') % err

View File

@ -83,8 +83,9 @@ class NbdMount(api.Mount):
# NOTE(mikal): qemu-nbd will return an error if the device file is
# already in use.
LOG.debug('Get nbd device %(dev)s for %(imgfile)s',
{'dev': device, 'imgfile': self.image})
_out, err = utils.trycmd('qemu-nbd', '-c', device, self.image,
{'dev': device, 'imgfile': self.image.path})
_out, err = utils.trycmd('qemu-nbd', '-c', device,
self.image.path,
run_as_root=True)
if err:
self.error = _('qemu-nbd error: %s') % err

View File

@ -24,6 +24,7 @@ from nova import utils
from nova.virt.disk.mount import loop
from nova.virt.disk.mount import nbd
from nova.virt.disk.vfs import api as vfs
from nova.virt.image import model as imgmodel
LOG = logging.getLogger(__name__)
@ -65,14 +66,18 @@ class VFSLocalFS(vfs.VFS):
try:
if self.imgfmt == "raw":
LOG.debug("Using LoopMount")
mnt = loop.LoopMount(self.imgfile,
self.imgdir,
self.partition)
mnt = loop.LoopMount(
imgmodel.LocalFileImage(self.imgfile,
imgmodel.FORMAT_RAW),
self.imgdir,
self.partition)
else:
LOG.debug("Using NbdMount")
mnt = nbd.NbdMount(self.imgfile,
self.imgdir,
self.partition)
mnt = nbd.NbdMount(
imgmodel.LocalFileImage(self.imgfile,
imgmodel.FORMAT_QCOW2),
self.imgdir,
self.partition)
if mount:
if not mnt.do_mount():
raise exception.NovaException(mnt.error)