virt: allow for direct mounting of LocalBlockImages

Block backed images, are already presented as block devices, so
LocalBlockImages can be mounted directly instead of being linked
with loopback or nbd to another block device.

Change-Id: I5aae1e293832f7cff7bc00d793bcc86ededf5eec
Closes-bug: #1287754
This commit is contained in:
Andrew Melton 2015-08-20 12:50:13 -07:00 committed by Thomas Maddox
parent 240df42859
commit 0e16afa588
6 changed files with 189 additions and 9 deletions

View File

@ -16,6 +16,10 @@ import mock
from nova import test
from nova.virt.disk.mount import api
from nova.virt.disk.mount import block
from nova.virt.disk.mount import loop
from nova.virt.disk.mount import nbd
from nova.virt.image import model as imgmodel
PARTITION = 77
@ -114,3 +118,81 @@ class MountTestCase(test.NoDBTestCase):
self.assertEqual(ORIG_DEVICE, mount.mapped_device)
self.assertFalse(mount.automapped)
self.assertTrue(mount.mapped)
def test_instance_for_format_raw(self):
image = imgmodel.LocalFileImage("/some/file.raw",
imgmodel.FORMAT_RAW)
mount_dir = '/mount/dir'
partition = -1
inst = api.Mount.instance_for_format(image, mount_dir, partition)
self.assertIsInstance(inst, loop.LoopMount)
def test_instance_for_format_qcow2(self):
image = imgmodel.LocalFileImage("/some/file.qcows",
imgmodel.FORMAT_QCOW2)
mount_dir = '/mount/dir'
partition = -1
inst = api.Mount.instance_for_format(image, mount_dir, partition)
self.assertIsInstance(inst, nbd.NbdMount)
def test_instance_for_format_block(self):
image = imgmodel.LocalBlockImage(
"/dev/mapper/instances--instance-0000001_disk",)
mount_dir = '/mount/dir'
partition = -1
inst = api.Mount.instance_for_format(image, mount_dir, partition)
self.assertIsInstance(inst, block.BlockMount)
def test_instance_for_device_loop(self):
image = mock.MagicMock()
mount_dir = '/mount/dir'
partition = -1
device = '/dev/loop0'
inst = api.Mount.instance_for_device(image, mount_dir, partition,
device)
self.assertIsInstance(inst, loop.LoopMount)
def test_instance_for_device_loop_partition(self):
image = mock.MagicMock()
mount_dir = '/mount/dir'
partition = 1
device = '/dev/mapper/loop0p1'
inst = api.Mount.instance_for_device(image, mount_dir, partition,
device)
self.assertIsInstance(inst, loop.LoopMount)
def test_instance_for_device_nbd(self):
image = mock.MagicMock()
mount_dir = '/mount/dir'
partition = -1
device = '/dev/nbd0'
inst = api.Mount.instance_for_device(image, mount_dir, partition,
device)
self.assertIsInstance(inst, nbd.NbdMount)
def test_instance_for_device_nbd_partition(self):
image = mock.MagicMock()
mount_dir = '/mount/dir'
partition = 1
device = '/dev/mapper/nbd0p1'
inst = api.Mount.instance_for_device(image, mount_dir, partition,
device)
self.assertIsInstance(inst, nbd.NbdMount)
def test_instance_for_device_block(self):
image = mock.MagicMock()
mount_dir = '/mount/dir'
partition = -1
device = '/dev/mapper/instances--instance-0000001_disk'
inst = api.Mount.instance_for_device(image, mount_dir, partition,
device)
self.assertIsInstance(inst, block.BlockMount)
def test_instance_for_device_block_partiton(self,):
image = mock.MagicMock()
mount_dir = '/mount/dir'
partition = 1
device = '/dev/mapper/instances--instance-0000001_diskp1'
inst = api.Mount.instance_for_device(image, mount_dir, partition,
device)
self.assertIsInstance(inst, block.BlockMount)

View File

@ -0,0 +1,46 @@
# Copyright 2015 Rackspace Hosting, 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.
import fixtures
from nova import test
from nova.virt.disk.mount import block
from nova.virt.image import model as imgmodel
class LoopTestCase(test.NoDBTestCase):
def setUp(self):
super(LoopTestCase, self).setUp()
device_path = '/dev/mapper/instances--instance-0000001_disk'
self.image = imgmodel.LocalBlockImage(device_path)
def test_get_dev(self):
tempdir = self.useFixture(fixtures.TempDir()).path
b = block.BlockMount(self.image, tempdir)
self.assertTrue(b.get_dev())
self.assertTrue(b.linked)
self.assertEqual(self.image.path, b.device)
def test_unget_dev(self):
tempdir = self.useFixture(fixtures.TempDir()).path
b = block.BlockMount(self.image, tempdir)
b.unget_dev()
self.assertIsNone(b.device)
self.assertFalse(b.linked)

View File

@ -254,6 +254,11 @@ class TestVirtDisk(test.NoDBTestCase):
('qemu-nbd', '-d', '/dev/nbd15'),
]
# NOTE(thomasem): Not adding any commands in this case, because we're
# not expecting an additional umount for LocalBlockImages. This is to
# assert that no additional commands are run in this case.
disk_api.teardown_container('/dev/volume-group/uuid_disk')
self.assertEqual(self.executes, expected_commands)
def test_lxc_teardown_container_with_namespace_cleaned(self):

View File

@ -472,10 +472,13 @@ def teardown_container(container_dir, container_root_device=None):
LOG.debug("Release loop device %s", container_root_device)
utils.execute('losetup', '--detach', container_root_device,
run_as_root=True, attempts=3)
else:
elif 'nbd' in container_root_device:
LOG.debug('Release nbd device %s', container_root_device)
utils.execute('qemu-nbd', '-d', container_root_device,
run_as_root=True)
else:
LOG.debug('No release necessary for block device %s' %
container_root_device)
except Exception:
LOG.exception(_LE('Failed to teardown container filesystem'))

View File

@ -62,12 +62,13 @@ class Mount(object):
return importutils.import_object(
"nova.virt.disk.mount.nbd.NbdMount",
image, mountdir, partition)
elif isinstance(image, imgmodel.LocalBlockImage):
LOG.debug("Using BlockMount")
return importutils.import_object(
"nova.virt.disk.mount.block.BlockMount",
image, mountdir, partition)
else:
# TODO(berrange) we could mount images of
# type LocalBlockImage directly without
# involving loop or nbd devices
#
# We could also mount RBDImage directly
# TODO(berrange) We could mount RBDImage directly
# using kernel RBD block dev support.
#
# This is left as an enhancement for future
@ -98,11 +99,16 @@ class Mount(object):
return importutils.import_object(
"nova.virt.disk.mount.loop.LoopMount",
image, mountdir, partition, device)
else:
elif "nbd" in device:
LOG.debug("Using NbdMount")
return importutils.import_object(
"nova.virt.disk.mount.nbd.NbdMount",
image, mountdir, partition, device)
else:
LOG.debug("Using BlockMount")
return importutils.import_object(
"nova.virt.disk.mount.block.BlockMount",
image, mountdir, partition, device)
def __init__(self, image, mount_dir, partition=None, device=None):
"""Create a new Mount instance
@ -139,8 +145,9 @@ class Mount(object):
if os.path.isabs(device) and os.path.exists(device):
if device.startswith('/dev/mapper/'):
device = os.path.basename(device)
device, self.partition = device.rsplit('p', 1)
self.device = os.path.join('/dev', device)
if 'p' in device:
device, self.partition = device.rsplit('p', 1)
self.device = os.path.join('/dev', device)
def get_dev(self):
"""Make the image available as a block device in the file system."""

View File

@ -0,0 +1,37 @@
# Copyright 2015 Rackspace Hosting, 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 block device based images directly."""
from oslo_log import log as logging
from nova.virt.disk.mount import api
LOG = logging.getLogger(__name__)
class BlockMount(api.Mount):
"""Block device backed images do not need to be linked because
they are already exposed as block devices and can be mounted
directly.
"""
mode = 'block'
def get_dev(self):
self.device = self.image.path
self.linked = True
return True
def unget_dev(self):
self.linked = False
self.device = None