diff --git a/nova/compute/api.py b/nova/compute/api.py
index 800fe5143a3..aa13e966ab0 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -46,6 +46,7 @@
from nova.i18n import _
from nova.i18n import _LE
from nova import image
+from nova import keymgr
from nova import network
from nova.network import model as network_model
from nova.network.security_group import openstack_driver
@@ -111,9 +112,32 @@
'boot from volume. A negative number means unlimited.'),
]
+ephemeral_storage_encryption_group = cfg.OptGroup(
+ name='ephemeral_storage_encryption',
+ title='Ephemeral storage encryption options')
+
+ephemeral_storage_encryption_opts = [
+ cfg.BoolOpt('enabled',
+ default=False,
+ help='Whether to encrypt ephemeral storage'),
+ cfg.StrOpt('cipher',
+ default='aes-xts-plain64',
+ help='The cipher and mode to be used to encrypt ephemeral '
+ 'storage. Which ciphers are available ciphers depends '
+ 'on kernel support. See /proc/crypto for the list of '
+ 'available options.'),
+ cfg.IntOpt('key_size',
+ default=512,
+ help='The bit length of the encryption key to be used to '
+ 'encrypt ephemeral storage (in XTS mode only half of '
+ 'the bits are used for encryption key)')
+]
CONF = cfg.CONF
CONF.register_opts(compute_opts)
+CONF.register_group(ephemeral_storage_encryption_group)
+CONF.register_opts(ephemeral_storage_encryption_opts,
+ group='ephemeral_storage_encryption')
CONF.import_opt('compute_topic', 'nova.compute.rpcapi')
CONF.import_opt('enable', 'nova.cells.opts', group='cells')
CONF.import_opt('default_ephemeral_format', 'nova.virt.driver')
@@ -244,6 +268,8 @@ def __init__(self, image_api=None, network_api=None, volume_api=None,
self._compute_task_api = None
self.servicegroup_api = servicegroup.API()
self.notifier = rpc.get_notifier('compute', CONF.host)
+ if CONF.ephemeral_storage_encryption.enabled:
+ self.key_manager = keymgr.API()
super(API, self).__init__(**kwargs)
@@ -1185,7 +1211,7 @@ def _populate_instance_names(self, instance, num_instances):
def _default_display_name(self, instance_uuid):
return "Server %s" % instance_uuid
- def _populate_instance_for_create(self, instance, image,
+ def _populate_instance_for_create(self, context, instance, image,
index, security_groups, instance_type):
"""Build the beginning of a new instance."""
@@ -1201,6 +1227,12 @@ def _populate_instance_for_create(self, instance, image,
info_cache.instance_uuid = instance.uuid
info_cache.network_info = network_model.NetworkInfo()
instance.info_cache = info_cache
+ if CONF.ephemeral_storage_encryption.enabled:
+ instance.ephemeral_key_uuid = self.key_manager.create_key(
+ context,
+ length=CONF.ephemeral_storage_encryption.key_size)
+ else:
+ instance.ephemeral_key_uuid = None
# Store image properties so we can use them later
# (for notifications, etc). Only store what we can.
@@ -1233,7 +1265,7 @@ def create_db_entry_for_new_instance(self, context, instance_type, image,
This is called by the scheduler after a location for the
instance has been determined.
"""
- self._populate_instance_for_create(instance, image, index,
+ self._populate_instance_for_create(context, instance, image, index,
security_group, instance_type)
self._populate_instance_names(instance, num_instances)
diff --git a/nova/tests/compute/test_compute.py b/nova/tests/compute/test_compute.py
index 3ec0d8f1071..8a01b09636b 100644
--- a/nova/tests/compute/test_compute.py
+++ b/nova/tests/compute/test_compute.py
@@ -7313,6 +7313,7 @@ def test_populate_instance_for_create(self):
instance.update(base_options)
inst_type = flavors.get_flavor_by_name("m1.tiny")
instance = self.compute_api._populate_instance_for_create(
+ self.context,
instance,
self.fake_image,
1,
@@ -7331,9 +7332,9 @@ def test_default_hostname_generator(self):
orig_populate = self.compute_api._populate_instance_for_create
- def _fake_populate(base_options, *args, **kwargs):
+ def _fake_populate(context, base_options, *args, **kwargs):
base_options['uuid'] = fake_uuids.pop(0)
- return orig_populate(base_options, *args, **kwargs)
+ return orig_populate(context, base_options, *args, **kwargs)
self.stubs.Set(self.compute_api,
'_populate_instance_for_create',
diff --git a/nova/tests/virt/libvirt/fake_imagebackend.py b/nova/tests/virt/libvirt/fake_imagebackend.py
index 66822c18ed4..297fc6071e0 100644
--- a/nova/tests/virt/libvirt/fake_imagebackend.py
+++ b/nova/tests/virt/libvirt/fake_imagebackend.py
@@ -52,10 +52,12 @@ def libvirt_info(self, disk_bus, disk_dev, device_type,
return FakeImage(instance, name)
- def snapshot(self, path, image_type=''):
+ def snapshot(self, instance, disk_path, image_type=''):
# NOTE(bfilippov): this is done in favor for
# snapshot tests in test_libvirt.LibvirtConnTestCase
- return imagebackend.Backend(True).snapshot(path, image_type)
+ return imagebackend.Backend(True).snapshot(instance,
+ disk_path,
+ image_type=image_type)
class Raw(imagebackend.Image):
diff --git a/nova/tests/virt/libvirt/fake_libvirt_utils.py b/nova/tests/virt/libvirt/fake_libvirt_utils.py
index f2f0781eb00..e25487e78ff 100644
--- a/nova/tests/virt/libvirt/fake_libvirt_utils.py
+++ b/nova/tests/virt/libvirt/fake_libvirt_utils.py
@@ -156,7 +156,12 @@ def file_open(path, mode=None):
def find_disk(virt_dom):
- return "filename"
+ if disk_type == 'lvm':
+ return "/dev/nova-vg/lv"
+ elif disk_type in ['raw', 'qcow2']:
+ return "filename"
+ else:
+ return "unknown_type_disk"
def load_file(path):
diff --git a/nova/tests/virt/libvirt/test_driver.py b/nova/tests/virt/libvirt/test_driver.py
index 78c6ebae1c1..1af7358db2a 100644
--- a/nova/tests/virt/libvirt/test_driver.py
+++ b/nova/tests/virt/libvirt/test_driver.py
@@ -3634,6 +3634,83 @@ def convert_image(source, dest, out_format):
self.assertEqual(snapshot['disk_format'], 'raw')
self.assertEqual(snapshot['name'], snapshot_name)
+ def test_lvm_snapshot_in_raw_format(self):
+ # Tests Lvm backend snapshot functionality with raw format
+ # snapshots.
+ xml = """
+
+
+
+
+
+
+
+ """
+ update_task_state_calls = [
+ mock.call(task_state=task_states.IMAGE_PENDING_UPLOAD),
+ mock.call(task_state=task_states.IMAGE_UPLOADING,
+ expected_state=task_states.IMAGE_PENDING_UPLOAD)]
+ mock_update_task_state = mock.Mock()
+ mock_lookupByName = mock.Mock(return_value=FakeVirtDomain(xml),
+ autospec=True)
+ volume_info = {'VG': 'nova-vg', 'LV': 'disk'}
+ mock_volume_info = mock.Mock(return_value=volume_info,
+ autospec=True)
+ mock_volume_info_calls = [mock.call('/dev/nova-vg/lv')]
+ mock_convert_image = mock.Mock()
+
+ def convert_image_side_effect(source, dest, out_format,
+ run_as_root=True):
+ libvirt_driver.libvirt_utils.files[dest] = ''
+ mock_convert_image.side_effect = convert_image_side_effect
+
+ self.flags(snapshots_directory='./',
+ snapshot_image_format='raw',
+ images_type='lvm',
+ images_volume_group='nova-vg', group='libvirt')
+ libvirt_driver.libvirt_utils.disk_type = "lvm"
+
+ # Start test
+ image_service = nova.tests.image.fake.FakeImageService()
+ instance_ref = db.instance_create(self.context, self.test_instance)
+ properties = {'instance_id': instance_ref['id'],
+ 'user_id': str(self.context.user_id)}
+ snapshot_name = 'test-snap'
+ sent_meta = {'name': snapshot_name, 'is_public': False,
+ 'status': 'creating', 'properties': properties}
+ recv_meta = image_service.create(context, sent_meta)
+
+ conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
+ with contextlib.nested(
+ mock.patch.object(libvirt_driver.LibvirtDriver,
+ '_conn',
+ autospec=True),
+ mock.patch.object(libvirt_driver.imagebackend.lvm,
+ 'volume_info',
+ mock_volume_info),
+ mock.patch.object(libvirt_driver.imagebackend.images,
+ 'convert_image',
+ mock_convert_image),
+ mock.patch.object(libvirt_driver.LibvirtDriver,
+ '_lookup_by_name',
+ mock_lookupByName)):
+ conn.snapshot(self.context, instance_ref, recv_meta['id'],
+ mock_update_task_state)
+
+ mock_lookupByName.assert_called_once_with("instance-00000001")
+ mock_volume_info.assert_has_calls(mock_volume_info_calls)
+ mock_convert_image.assert_called_once()
+ snapshot = image_service.show(context, recv_meta['id'])
+ mock_update_task_state.assert_has_calls(update_task_state_calls)
+ self.assertEqual('available', snapshot['properties']['image_state'])
+ self.assertEqual('active', snapshot['status'])
+ self.assertEqual('raw', snapshot['disk_format'])
+ self.assertEqual(snapshot_name, snapshot['name'])
+ # This is for all the subsequent tests that do not set the value of
+ # images type
+ self.flags(images_type='default', group='libvirt')
+ libvirt_driver.libvirt_utils.disk_type = "qcow2"
+
def test_lxc_snapshot_in_raw_format(self):
expected_calls = [
{'args': (),
@@ -3665,6 +3742,7 @@ def test_lxc_snapshot_in_raw_format(self):
self.mox.StubOutWithMock(libvirt_driver.utils, 'execute')
libvirt_driver.utils.execute = self.fake_execute
self.stubs.Set(libvirt_driver.libvirt_utils, 'disk_type', 'raw')
+ libvirt_driver.libvirt_utils.disk_type = "raw"
def convert_image(source, dest, out_format):
libvirt_driver.libvirt_utils.files[dest] = ''
@@ -3775,6 +3853,80 @@ def test_lxc_snapshot_in_qcow2_format(self):
self.assertEqual(snapshot['disk_format'], 'qcow2')
self.assertEqual(snapshot['name'], snapshot_name)
+ def test_lvm_snapshot_in_qcow2_format(self):
+ # Tests Lvm backend snapshot functionality with raw format
+ # snapshots.
+ xml = """
+
+
+
+
+
+
+
+ """
+ update_task_state_calls = [
+ mock.call(task_state=task_states.IMAGE_PENDING_UPLOAD),
+ mock.call(task_state=task_states.IMAGE_UPLOADING,
+ expected_state=task_states.IMAGE_PENDING_UPLOAD)]
+ mock_update_task_state = mock.Mock()
+ mock_lookupByName = mock.Mock(return_value=FakeVirtDomain(xml),
+ autospec=True)
+ volume_info = {'VG': 'nova-vg', 'LV': 'disk'}
+ mock_volume_info = mock.Mock(return_value=volume_info, autospec=True)
+ mock_volume_info_calls = [mock.call('/dev/nova-vg/lv')]
+ mock_convert_image = mock.Mock()
+
+ def convert_image_side_effect(source, dest, out_format,
+ run_as_root=True):
+ libvirt_driver.libvirt_utils.files[dest] = ''
+ mock_convert_image.side_effect = convert_image_side_effect
+
+ self.flags(snapshots_directory='./',
+ snapshot_image_format='qcow2',
+ images_type='lvm',
+ images_volume_group='nova-vg', group='libvirt')
+ libvirt_driver.libvirt_utils.disk_type = "lvm"
+
+ # Start test
+ image_service = nova.tests.image.fake.FakeImageService()
+ instance_ref = db.instance_create(self.context, self.test_instance)
+ properties = {'instance_id': instance_ref['id'],
+ 'user_id': str(self.context.user_id)}
+ snapshot_name = 'test-snap'
+ sent_meta = {'name': snapshot_name, 'is_public': False,
+ 'status': 'creating', 'properties': properties}
+ recv_meta = image_service.create(context, sent_meta)
+
+ conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
+ with contextlib.nested(
+ mock.patch.object(libvirt_driver.LibvirtDriver,
+ '_conn',
+ autospec=True),
+ mock.patch.object(libvirt_driver.imagebackend.lvm,
+ 'volume_info',
+ mock_volume_info),
+ mock.patch.object(libvirt_driver.imagebackend.images,
+ 'convert_image',
+ mock_convert_image),
+ mock.patch.object(libvirt_driver.LibvirtDriver,
+ '_lookup_by_name',
+ mock_lookupByName)):
+ conn.snapshot(self.context, instance_ref, recv_meta['id'],
+ mock_update_task_state)
+
+ mock_lookupByName.assert_called_once_with("instance-00000001")
+ mock_volume_info.assert_has_calls(mock_volume_info_calls)
+ mock_convert_image.assert_called_once()
+ snapshot = image_service.show(context, recv_meta['id'])
+ mock_update_task_state.assert_has_calls(update_task_state_calls)
+ self.assertEqual('available', snapshot['properties']['image_state'])
+ self.assertEqual('active', snapshot['status'])
+ self.assertEqual('qcow2', snapshot['disk_format'])
+ self.assertEqual(snapshot_name, snapshot['name'])
+ self.flags(images_type='default', group='libvirt')
+ libvirt_driver.libvirt_utils.disk_type = "qcow2"
+
def test_snapshot_no_image_architecture(self):
expected_calls = [
{'args': (),
@@ -3857,6 +4009,7 @@ def test_lxc_snapshot_no_image_architecture(self):
libvirt_driver.LibvirtDriver._conn.lookupByName = self.fake_lookup
self.mox.StubOutWithMock(libvirt_driver.utils, 'execute')
libvirt_driver.utils.execute = self.fake_execute
+ libvirt_driver.libvirt_utils.disk_type = "qcow2"
self.mox.ReplayAll()
@@ -3927,6 +4080,7 @@ def test_lxc_snapshot_no_original_image(self):
self.flags(snapshots_directory='./',
virt_type='lxc',
group='libvirt')
+ libvirt_driver.libvirt_utils.disk_type = "qcow2"
# Assign a non-existent image
test_instance = copy.deepcopy(self.test_instance)
@@ -6281,7 +6435,6 @@ def _test_destroy_removes_disk(self, volume_fail=False):
self.mox.StubOutWithMock(libvirt_driver.LibvirtDriver,
'_undefine_domain')
- libvirt_driver.LibvirtDriver._undefine_domain(instance)
self.mox.StubOutWithMock(db, 'instance_get_by_uuid')
db.instance_get_by_uuid(mox.IgnoreArg(), mox.IgnoreArg(),
columns_to_join=['info_cache',
@@ -6304,8 +6457,7 @@ def _test_destroy_removes_disk(self, volume_fail=False):
'delete_instance_files')
(libvirt_driver.LibvirtDriver.delete_instance_files(mox.IgnoreArg()).
AndReturn(True))
- self.mox.StubOutWithMock(libvirt_driver.LibvirtDriver, '_cleanup_lvm')
- libvirt_driver.LibvirtDriver._cleanup_lvm(instance)
+ libvirt_driver.LibvirtDriver._undefine_domain(instance)
# Start test
self.mox.ReplayAll()
diff --git a/nova/tests/virt/libvirt/test_imagebackend.py b/nova/tests/virt/libvirt/test_imagebackend.py
index 3a87c2e3979..2b4b9dcf2b0 100644
--- a/nova/tests/virt/libvirt/test_imagebackend.py
+++ b/nova/tests/virt/libvirt/test_imagebackend.py
@@ -13,16 +13,19 @@
# License for the specific language governing permissions and limitations
# under the License.
+import contextlib
+import inspect
import os
import shutil
import tempfile
import fixtures
+import mock
from oslo.config import cfg
-import inspect
-
+from nova import context
from nova import exception
+from nova import keymgr
from nova.openstack.common import units
from nova.openstack.common import uuidutils
from nova import test
@@ -52,6 +55,7 @@ def setUp(self):
self.INSTANCE['uuid'], 'disk.info')
self.NAME = 'fake.vm'
self.TEMPLATE = 'template'
+ self.CONTEXT = context.get_admin_context()
self.OLD_STYLE_INSTANCE_PATH = \
fake_libvirt_utils.get_instance_path(self.INSTANCE, forceold=True)
@@ -462,10 +466,11 @@ def setUp(self):
self.image_class = imagebackend.Lvm
super(LvmTestCase, self).setUp()
self.flags(images_volume_group=self.VG, group='libvirt')
+ self.flags(enabled=False, group='ephemeral_storage_encryption')
+ self.INSTANCE['ephemeral_key_uuid'] = None
self.LV = '%s_%s' % (self.INSTANCE['uuid'], self.NAME)
self.OLD_STYLE_INSTANCE_PATH = None
self.PATH = os.path.join('/dev', self.VG, self.LV)
-
self.disk = imagebackend.disk
self.utils = imagebackend.utils
self.lvm = imagebackend.lvm
@@ -656,6 +661,378 @@ def fake_fetch(target, *args, **kwargs):
self.assertEqual(fake_processutils.fake_execute_get_log(), [])
+class EncryptedLvmTestCase(_ImageTestCase, test.TestCase):
+ VG = 'FakeVG'
+ TEMPLATE_SIZE = 512
+ SIZE = 1024
+
+ def setUp(self):
+ super(EncryptedLvmTestCase, self).setUp()
+ self.image_class = imagebackend.Lvm
+ self.flags(enabled=True, group='ephemeral_storage_encryption')
+ self.flags(cipher='aes-xts-plain64',
+ group='ephemeral_storage_encryption')
+ self.flags(key_size=512, group='ephemeral_storage_encryption')
+ self.flags(fixed_key='00000000000000000000000000000000'
+ '00000000000000000000000000000000',
+ group='keymgr')
+ self.flags(images_volume_group=self.VG, group='libvirt')
+ self.LV = '%s_%s' % (self.INSTANCE['uuid'], self.NAME)
+ self.OLD_STYLE_INSTANCE_PATH = None
+ self.LV_PATH = os.path.join('/dev', self.VG, self.LV)
+ self.PATH = os.path.join('/dev/mapper',
+ imagebackend.dmcrypt.volume_name(self.LV))
+ self.key_manager = keymgr.API()
+ self.INSTANCE['ephemeral_key_uuid'] =\
+ self.key_manager.create_key(self.CONTEXT)
+ self.KEY = self.key_manager.get_key(self.CONTEXT,
+ self.INSTANCE['ephemeral_key_uuid']).get_encoded()
+
+ self.lvm = imagebackend.lvm
+ self.disk = imagebackend.disk
+ self.utils = imagebackend.utils
+ self.libvirt_utils = imagebackend.libvirt_utils
+ self.dmcrypt = imagebackend.dmcrypt
+
+ def _create_image(self, sparse):
+ with contextlib.nested(
+ mock.patch.object(self.lvm, 'create_volume', mock.Mock()),
+ mock.patch.object(self.lvm, 'remove_volumes', mock.Mock()),
+ mock.patch.object(self.disk, 'resize2fs', mock.Mock()),
+ mock.patch.object(self.disk, 'get_disk_size',
+ mock.Mock(return_value=self.TEMPLATE_SIZE)),
+ mock.patch.object(self.dmcrypt, 'create_volume', mock.Mock()),
+ mock.patch.object(self.dmcrypt, 'delete_volume', mock.Mock()),
+ mock.patch.object(self.dmcrypt, 'list_volumes', mock.Mock()),
+ mock.patch.object(self.libvirt_utils, 'create_lvm_image',
+ mock.Mock()),
+ mock.patch.object(self.libvirt_utils, 'remove_logical_volumes',
+ mock.Mock()),
+ mock.patch.object(self.utils, 'execute', mock.Mock())):
+ fn = mock.Mock()
+
+ image = self.image_class(self.INSTANCE, self.NAME)
+ image.create_image(fn, self.TEMPLATE_PATH, self.TEMPLATE_SIZE,
+ context=self.CONTEXT)
+
+ fn.assert_called_with(context=self.CONTEXT,
+ max_size=self.TEMPLATE_SIZE,
+ target=self.TEMPLATE_PATH)
+ self.lvm.create_volume.assert_called_with(self.VG,
+ self.LV,
+ self.TEMPLATE_SIZE,
+ sparse=sparse)
+ self.dmcrypt.create_volume.assert_called_with(
+ self.PATH.rpartition('/')[2],
+ self.LV_PATH,
+ CONF.ephemeral_storage_encryption.cipher,
+ CONF.ephemeral_storage_encryption.key_size,
+ self.KEY)
+ cmd = ('qemu-img',
+ 'convert',
+ '-O',
+ 'raw',
+ self.TEMPLATE_PATH,
+ self.PATH)
+ self.utils.execute.assert_called_with(*cmd, run_as_root=True)
+
+ def _create_image_generated(self, sparse):
+ with contextlib.nested(
+ mock.patch.object(self.lvm, 'create_volume', mock.Mock()),
+ mock.patch.object(self.lvm, 'remove_volumes', mock.Mock()),
+ mock.patch.object(self.disk, 'resize2fs', mock.Mock()),
+ mock.patch.object(self.disk, 'get_disk_size',
+ mock.Mock(return_value=self.TEMPLATE_SIZE)),
+ mock.patch.object(self.dmcrypt, 'create_volume', mock.Mock()),
+ mock.patch.object(self.dmcrypt, 'delete_volume', mock.Mock()),
+ mock.patch.object(self.dmcrypt, 'list_volumes', mock.Mock()),
+ mock.patch.object(self.libvirt_utils, 'create_lvm_image',
+ mock.Mock()),
+ mock.patch.object(self.libvirt_utils, 'remove_logical_volumes',
+ mock.Mock()),
+ mock.patch.object(self.utils, 'execute', mock.Mock())):
+ fn = mock.Mock()
+
+ image = self.image_class(self.INSTANCE, self.NAME)
+ image.create_image(fn, self.TEMPLATE_PATH,
+ self.SIZE,
+ ephemeral_size=None,
+ context=self.CONTEXT)
+
+ self.lvm.create_volume.assert_called_with(
+ self.VG,
+ self.LV,
+ self.SIZE,
+ sparse=sparse)
+ self.dmcrypt.create_volume.assert_called_with(
+ self.PATH.rpartition('/')[2],
+ self.LV_PATH,
+ CONF.ephemeral_storage_encryption.cipher,
+ CONF.ephemeral_storage_encryption.key_size,
+ self.KEY)
+ fn.assert_called_with(target=self.PATH,
+ ephemeral_size=None, context=self.CONTEXT)
+
+ def _create_image_resize(self, sparse):
+ with contextlib.nested(
+ mock.patch.object(self.lvm, 'create_volume', mock.Mock()),
+ mock.patch.object(self.lvm, 'remove_volumes', mock.Mock()),
+ mock.patch.object(self.disk, 'resize2fs', mock.Mock()),
+ mock.patch.object(self.disk, 'get_disk_size',
+ mock.Mock(return_value=self.TEMPLATE_SIZE)),
+ mock.patch.object(self.dmcrypt, 'create_volume', mock.Mock()),
+ mock.patch.object(self.dmcrypt, 'delete_volume', mock.Mock()),
+ mock.patch.object(self.dmcrypt, 'list_volumes', mock.Mock()),
+ mock.patch.object(self.libvirt_utils, 'create_lvm_image',
+ mock.Mock()),
+ mock.patch.object(self.libvirt_utils, 'remove_logical_volumes',
+ mock.Mock()),
+ mock.patch.object(self.utils, 'execute', mock.Mock())):
+ fn = mock.Mock()
+
+ image = self.image_class(self.INSTANCE, self.NAME)
+ image.create_image(fn, self.TEMPLATE_PATH, self.SIZE,
+ context=self.CONTEXT)
+
+ fn.assert_called_with(context=self.CONTEXT, max_size=self.SIZE,
+ target=self.TEMPLATE_PATH)
+ self.disk.get_disk_size.assert_called_with(self.TEMPLATE_PATH)
+ self.lvm.create_volume.assert_called_with(
+ self.VG,
+ self.LV,
+ self.SIZE,
+ sparse=sparse)
+ self.dmcrypt.create_volume.assert_called_with(
+ self.PATH.rpartition('/')[2],
+ self.LV_PATH,
+ CONF.ephemeral_storage_encryption.cipher,
+ CONF.ephemeral_storage_encryption.key_size,
+ self.KEY)
+ cmd = ('qemu-img',
+ 'convert',
+ '-O',
+ 'raw',
+ self.TEMPLATE_PATH,
+ self.PATH)
+ self.utils.execute.assert_called_with(*cmd, run_as_root=True)
+ self.disk.resize2fs.assert_called_with(self.PATH, run_as_root=True)
+
+ def test_create_image(self):
+ self._create_image(False)
+
+ def test_create_image_sparsed(self):
+ self.flags(sparse_logical_volumes=True, group='libvirt')
+ self._create_image(True)
+
+ def test_create_image_generated(self):
+ self._create_image_generated(False)
+
+ def test_create_image_generated_sparsed(self):
+ self.flags(sparse_logical_volumes=True, group='libvirt')
+ self._create_image_generated(True)
+
+ def test_create_image_resize(self):
+ self._create_image_resize(False)
+
+ def test_create_image_resize_sparsed(self):
+ self.flags(sparse_logical_volumes=True, group='libvirt')
+ self._create_image_resize(True)
+
+ def test_create_image_negative(self):
+ with contextlib.nested(
+ mock.patch.object(self.lvm, 'create_volume', mock.Mock()),
+ mock.patch.object(self.lvm, 'remove_volumes', mock.Mock()),
+ mock.patch.object(self.disk, 'resize2fs', mock.Mock()),
+ mock.patch.object(self.disk, 'get_disk_size',
+ mock.Mock(return_value=self.TEMPLATE_SIZE)),
+ mock.patch.object(self.dmcrypt, 'create_volume', mock.Mock()),
+ mock.patch.object(self.dmcrypt, 'delete_volume', mock.Mock()),
+ mock.patch.object(self.dmcrypt, 'list_volumes', mock.Mock()),
+ mock.patch.object(self.libvirt_utils, 'create_lvm_image',
+ mock.Mock()),
+ mock.patch.object(self.libvirt_utils, 'remove_logical_volumes',
+ mock.Mock()),
+ mock.patch.object(self.utils, 'execute', mock.Mock())):
+ fn = mock.Mock()
+ self.lvm.create_volume.side_effect = RuntimeError()
+
+ image = self.image_class(self.INSTANCE, self.NAME)
+ self.assertRaises(
+ RuntimeError,
+ image.create_image,
+ fn,
+ self.TEMPLATE_PATH,
+ self.SIZE,
+ context=self.CONTEXT)
+
+ fn.assert_called_with(
+ context=self.CONTEXT,
+ max_size=self.SIZE,
+ target=self.TEMPLATE_PATH)
+ self.disk.get_disk_size.assert_called_with(
+ self.TEMPLATE_PATH)
+ self.lvm.create_volume.assert_called_with(
+ self.VG,
+ self.LV,
+ self.SIZE,
+ sparse=False)
+ self.dmcrypt.delete_volume.assert_called_with(
+ self.PATH.rpartition('/')[2])
+ self.lvm.remove_volumes.assert_called_with(self.LV_PATH)
+
+ def test_create_image_encrypt_negative(self):
+ with contextlib.nested(
+ mock.patch.object(self.lvm, 'create_volume', mock.Mock()),
+ mock.patch.object(self.lvm, 'remove_volumes', mock.Mock()),
+ mock.patch.object(self.disk, 'resize2fs', mock.Mock()),
+ mock.patch.object(self.disk, 'get_disk_size',
+ mock.Mock(return_value=self.TEMPLATE_SIZE)),
+ mock.patch.object(self.dmcrypt, 'create_volume', mock.Mock()),
+ mock.patch.object(self.dmcrypt, 'delete_volume', mock.Mock()),
+ mock.patch.object(self.dmcrypt, 'list_volumes', mock.Mock()),
+ mock.patch.object(self.libvirt_utils, 'create_lvm_image',
+ mock.Mock()),
+ mock.patch.object(self.libvirt_utils, 'remove_logical_volumes',
+ mock.Mock()),
+ mock.patch.object(self.utils, 'execute', mock.Mock())):
+ fn = mock.Mock()
+ self.dmcrypt.create_volume.side_effect = RuntimeError()
+
+ image = self.image_class(self.INSTANCE, self.NAME)
+ self.assertRaises(
+ RuntimeError,
+ image.create_image,
+ fn,
+ self.TEMPLATE_PATH,
+ self.SIZE,
+ context=self.CONTEXT)
+
+ fn.assert_called_with(
+ context=self.CONTEXT,
+ max_size=self.SIZE,
+ target=self.TEMPLATE_PATH)
+ self.disk.get_disk_size.assert_called_with(self.TEMPLATE_PATH)
+ self.lvm.create_volume.assert_called_with(
+ self.VG,
+ self.LV,
+ self.SIZE,
+ sparse=False)
+ self.dmcrypt.create_volume.assert_called_with(
+ self.dmcrypt.volume_name(self.LV),
+ self.LV_PATH,
+ CONF.ephemeral_storage_encryption.cipher,
+ CONF.ephemeral_storage_encryption.key_size,
+ self.KEY)
+ self.dmcrypt.delete_volume.assert_called_with(
+ self.PATH.rpartition('/')[2])
+ self.lvm.remove_volumes.assert_called_with(self.LV_PATH)
+
+ def test_create_image_generated_negative(self):
+ with contextlib.nested(
+ mock.patch.object(self.lvm, 'create_volume', mock.Mock()),
+ mock.patch.object(self.lvm, 'remove_volumes', mock.Mock()),
+ mock.patch.object(self.disk, 'resize2fs', mock.Mock()),
+ mock.patch.object(self.disk, 'get_disk_size',
+ mock.Mock(return_value=self.TEMPLATE_SIZE)),
+ mock.patch.object(self.dmcrypt, 'create_volume', mock.Mock()),
+ mock.patch.object(self.dmcrypt, 'delete_volume', mock.Mock()),
+ mock.patch.object(self.dmcrypt, 'list_volumes', mock.Mock()),
+ mock.patch.object(self.libvirt_utils, 'create_lvm_image',
+ mock.Mock()),
+ mock.patch.object(self.libvirt_utils, 'remove_logical_volumes',
+ mock.Mock()),
+ mock.patch.object(self.utils, 'execute', mock.Mock())):
+ fn = mock.Mock()
+ fn.side_effect = RuntimeError()
+
+ image = self.image_class(self.INSTANCE, self.NAME)
+ self.assertRaises(RuntimeError,
+ image.create_image,
+ fn,
+ self.TEMPLATE_PATH,
+ self.SIZE,
+ ephemeral_size=None,
+ context=self.CONTEXT)
+
+ self.lvm.create_volume.assert_called_with(
+ self.VG,
+ self.LV,
+ self.SIZE,
+ sparse=False)
+ self.dmcrypt.create_volume.assert_called_with(
+ self.PATH.rpartition('/')[2],
+ self.LV_PATH,
+ CONF.ephemeral_storage_encryption.cipher,
+ CONF.ephemeral_storage_encryption.key_size,
+ self.KEY)
+ fn.assert_called_with(
+ target=self.PATH,
+ ephemeral_size=None,
+ context=self.CONTEXT)
+ self.dmcrypt.delete_volume.assert_called_with(
+ self.PATH.rpartition('/')[2])
+ self.lvm.remove_volumes.assert_called_with(self.LV_PATH)
+
+ def test_create_image_generated_encrypt_negative(self):
+ with contextlib.nested(
+ mock.patch.object(self.lvm, 'create_volume', mock.Mock()),
+ mock.patch.object(self.lvm, 'remove_volumes', mock.Mock()),
+ mock.patch.object(self.disk, 'resize2fs', mock.Mock()),
+ mock.patch.object(self.disk, 'get_disk_size',
+ mock.Mock(return_value=self.TEMPLATE_SIZE)),
+ mock.patch.object(self.dmcrypt, 'create_volume', mock.Mock()),
+ mock.patch.object(self.dmcrypt, 'delete_volume', mock.Mock()),
+ mock.patch.object(self.dmcrypt, 'list_volumes', mock.Mock()),
+ mock.patch.object(self.libvirt_utils, 'create_lvm_image',
+ mock.Mock()),
+ mock.patch.object(self.libvirt_utils, 'remove_logical_volumes',
+ mock.Mock()),
+ mock.patch.object(self.utils, 'execute', mock.Mock())):
+ fn = mock.Mock()
+ fn.side_effect = RuntimeError()
+
+ image = self.image_class(self.INSTANCE, self.NAME)
+ self.assertRaises(
+ RuntimeError,
+ image.create_image,
+ fn,
+ self.TEMPLATE_PATH,
+ self.SIZE,
+ ephemeral_size=None,
+ context=self.CONTEXT)
+
+ self.lvm.create_volume.assert_called_with(
+ self.VG,
+ self.LV,
+ self.SIZE,
+ sparse=False)
+ self.dmcrypt.create_volume.assert_called_with(
+ self.PATH.rpartition('/')[2],
+ self.LV_PATH,
+ CONF.ephemeral_storage_encryption.cipher,
+ CONF.ephemeral_storage_encryption.key_size,
+ self.KEY)
+ self.dmcrypt.delete_volume.assert_called_with(
+ self.PATH.rpartition('/')[2])
+ self.lvm.remove_volumes.assert_called_with(self.LV_PATH)
+
+ def test_prealloc_image(self):
+ self.flags(preallocate_images='space')
+ fake_processutils.fake_execute_clear_log()
+ fake_processutils.stub_out_processutils_execute(self.stubs)
+ image = self.image_class(self.INSTANCE, self.NAME)
+
+ def fake_fetch(target, *args, **kwargs):
+ return
+
+ self.stubs.Set(os.path, 'exists', lambda _: True)
+ self.stubs.Set(image, 'check_image_exists', lambda: True)
+
+ image.cache(fake_fetch, self.TEMPLATE_PATH, self.SIZE)
+
+ self.assertEqual(fake_processutils.fake_execute_get_log(), [])
+
+
class RbdTestCase(_ImageTestCase, test.NoDBTestCase):
POOL = "FakePool"
USER = "FakeUser"
@@ -862,6 +1239,8 @@ class BackendTestCase(test.NoDBTestCase):
def setUp(self):
super(BackendTestCase, self).setUp()
+ self.flags(enabled=False, group='ephemeral_storage_encryption')
+ self.INSTANCE['ephemeral_key_uuid'] = None
def get_image(self, use_cow, image_type):
return imagebackend.Backend(use_cow).image(self.INSTANCE,
diff --git a/nova/virt/libvirt/dmcrypt.py b/nova/virt/libvirt/dmcrypt.py
index fb973725220..0864e7286a4 100644
--- a/nova/virt/libvirt/dmcrypt.py
+++ b/nova/virt/libvirt/dmcrypt.py
@@ -29,6 +29,14 @@ def volume_name(base):
return base + _dmcrypt_suffix
+def is_encrypted(path):
+ """Returns true if the path corresponds to an encrypted disk."""
+ if path.startswith('/dev/mapper'):
+ return path.rpartition('/')[2].endswith(_dmcrypt_suffix)
+ else:
+ return False
+
+
def create_volume(target, device, cipher, key_size, key):
"""Sets up a dmcrypt mapping
diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py
index 425d68e1d2e..1bd5c076cba 100644
--- a/nova/virt/libvirt/driver.py
+++ b/nova/virt/libvirt/driver.py
@@ -92,6 +92,7 @@
from nova.virt import hardware
from nova.virt.libvirt import blockinfo
from nova.virt.libvirt import config as vconfig
+from nova.virt.libvirt import dmcrypt
from nova.virt.libvirt import firewall as libvirt_firewall
from nova.virt.libvirt import imagebackend
from nova.virt.libvirt import imagecache
@@ -243,6 +244,12 @@
CONF.import_opt('my_ip', 'nova.netconf')
CONF.import_opt('default_ephemeral_format', 'nova.virt.driver')
CONF.import_opt('use_cow_images', 'nova.virt.driver')
+CONF.import_opt('enabled', 'nova.compute.api',
+ group='ephemeral_storage_encryption')
+CONF.import_opt('cipher', 'nova.compute.api',
+ group='ephemeral_storage_encryption')
+CONF.import_opt('key_size', 'nova.compute.api',
+ group='ephemeral_storage_encryption')
CONF.import_opt('live_migration_retry_count', 'nova.compute.manager')
CONF.import_opt('vncserver_proxyclient_address', 'nova.vnc')
CONF.import_opt('server_proxyclient_address', 'nova.spice', group='spice')
@@ -1045,7 +1052,6 @@ def _undefine_domain(self, instance):
def cleanup(self, context, instance, network_info, block_device_info=None,
destroy_disks=True, migrate_data=None, destroy_vifs=True):
- self._undefine_domain(instance)
if destroy_vifs:
self._unplug_vifs(instance, network_info, True)
@@ -1119,16 +1125,27 @@ def cleanup(self, context, instance, network_info, block_device_info=None,
{'vol_id': vol.get('volume_id'), 'exc': exc},
instance=instance)
+ if destroy_disks:
+ # NOTE(haomai): destroy volumes if needed
+ if CONF.libvirt.images_type == 'lvm':
+ self._cleanup_lvm(instance)
+ if CONF.libvirt.images_type == 'rbd':
+ self._cleanup_rbd(instance)
+
if destroy_disks or (
migrate_data and migrate_data.get('is_shared_block_storage',
False)):
self._delete_instance_files(instance)
- if destroy_disks:
- self._cleanup_lvm(instance)
- # NOTE(haomai): destroy volumes if needed
- if CONF.libvirt.images_type == 'rbd':
- self._cleanup_rbd(instance)
+ self._undefine_domain(instance)
+
+ def _detach_encrypted_volumes(self, instance):
+ """Detaches encrypted volumes attached to instance."""
+ disks = jsonutils.loads(self.get_instance_disk_info(instance['name']))
+ encrypted_volumes = filter(dmcrypt.is_encrypted,
+ [disk['path'] for disk in disks])
+ for path in encrypted_volumes:
+ dmcrypt.delete_volume(path)
if CONF.serial_console.enabled:
for host, port in self._get_serial_ports_from_instance(instance):
@@ -1162,6 +1179,9 @@ def _cleanup_rbd(self, instance):
def _cleanup_lvm(self, instance):
"""Delete all LVM disks for given instance object."""
+ if instance.get('ephemeral_key_uuid') is not None:
+ self._detach_encrypted_volumes(instance)
+
disks = self._lvm_disks(instance)
if disks:
lvm.remove_volumes(disks)
@@ -1593,10 +1613,16 @@ def snapshot(self, context, instance, image_id, update_task_state):
# NOTE(rmk): Live snapshots require QEMU 1.3 and Libvirt 1.0.0.
# These restrictions can be relaxed as other configurations
# can be validated.
- if self._has_min_version(MIN_LIBVIRT_LIVESNAPSHOT_VERSION,
- MIN_QEMU_LIVESNAPSHOT_VERSION,
- REQ_HYPERVISOR_LIVESNAPSHOT) \
- and not source_format == "lvm" and not source_format == 'rbd':
+ # NOTE(dgenin): Instances with LVM encrypted ephemeral storage require
+ # cold snapshots. Currently, checking for encryption is
+ # redundant because LVM supports only cold snapshots.
+ # It is necessary in case this situation changes in the
+ # future.
+ if (self._has_min_version(MIN_LIBVIRT_LIVESNAPSHOT_VERSION,
+ MIN_QEMU_LIVESNAPSHOT_VERSION,
+ REQ_HYPERVISOR_LIVESNAPSHOT)
+ and source_format not in ('lvm', 'rbd')
+ and not CONF.ephemeral_storage_encryption.enabled):
live_snapshot = True
# Abort is an idempotent operation, so make sure any block
# jobs which may have failed are ended. This operation also
@@ -1626,7 +1652,8 @@ def snapshot(self, context, instance, image_id, update_task_state):
pci_manager.get_instance_pci_devs(instance))
virt_dom.managedSave(0)
- snapshot_backend = self.image_backend.snapshot(disk_path,
+ snapshot_backend = self.image_backend.snapshot(instance,
+ disk_path,
image_type=source_format)
if live_snapshot:
@@ -2732,7 +2759,7 @@ def _create_local(target, local_size, unit='G',
def _create_ephemeral(self, target, ephemeral_size,
fs_label, os_type, is_block_dev=False,
- max_size=None, specified_fs=None):
+ max_size=None, context=None, specified_fs=None):
if not is_block_dev:
self._create_local(target, ephemeral_size)
@@ -2741,7 +2768,7 @@ def _create_ephemeral(self, target, ephemeral_size,
specified_fs=specified_fs)
@staticmethod
- def _create_swap(target, swap_mb, max_size=None):
+ def _create_swap(target, swap_mb, max_size=None, context=None):
"""Create a swap file of specified size."""
libvirt_utils.create_image('raw', target, '%dM' % swap_mb)
utils.mkfs('swap', target)
@@ -2961,6 +2988,7 @@ def clone_fallback_to_fetch(*args, **kwargs):
fname = "ephemeral_%s_%s" % (ephemeral_gb, os_type_with_default)
size = ephemeral_gb * units.Gi
disk_image.cache(fetch_func=fn,
+ context=context,
filename=fname,
size=size,
ephemeral_size=ephemeral_gb)
@@ -2980,8 +3008,8 @@ def clone_fallback_to_fetch(*args, **kwargs):
is_block_dev=disk_image.is_block_dev)
size = eph['size'] * units.Gi
fname = "ephemeral_%s_%s" % (eph['size'], os_type_with_default)
- disk_image.cache(
- fetch_func=fn,
+ disk_image.cache(fetch_func=fn,
+ context=context,
filename=fname,
size=size,
ephemeral_size=eph['size'],
@@ -3002,6 +3030,7 @@ def clone_fallback_to_fetch(*args, **kwargs):
if swap_mb > 0:
size = swap_mb * units.Mi
image('disk.swap').cache(fetch_func=self._create_swap,
+ context=context,
filename="swap_%s" % swap_mb,
size=size,
swap_mb=swap_mb)
@@ -5281,7 +5310,7 @@ def _get_instance_disk_info(self, instance_name, xml,
for cnt, path_node in enumerate(path_nodes):
disk_type = disk_nodes[cnt].get('type')
- path = path_node.get('file')
+ path = path_node.get('file') or path_node.get('dev')
target = target_nodes[cnt].attrib['dev']
if not path:
@@ -5289,8 +5318,8 @@ def _get_instance_disk_info(self, instance_name, xml,
instance_name)
continue
- if disk_type != 'file':
- LOG.debug('skipping %s since it looks like volume', path)
+ if disk_type not in ['file', 'block']:
+ LOG.debug('skipping disk because it looks like a volume', path)
continue
if target in volume_devices:
@@ -5300,7 +5329,10 @@ def _get_instance_disk_info(self, instance_name, xml,
# get the real disk size or
# raise a localized error if image is unavailable
- dk_size = int(os.path.getsize(path))
+ if disk_type == 'file':
+ dk_size = int(os.path.getsize(path))
+ elif disk_type == 'block':
+ dk_size = lvm.get_volume_size(path)
disk_type = driver_nodes[cnt].get('type')
if disk_type == "qcow2":
diff --git a/nova/virt/libvirt/imagebackend.py b/nova/virt/libvirt/imagebackend.py
index 54c586bc3b3..f5d699506cd 100644
--- a/nova/virt/libvirt/imagebackend.py
+++ b/nova/virt/libvirt/imagebackend.py
@@ -24,6 +24,7 @@
from nova.i18n import _
from nova.i18n import _LE
from nova import image
+from nova import keymgr
from nova.openstack.common import excutils
from nova.openstack.common import fileutils
from nova.openstack.common import jsonutils
@@ -33,6 +34,7 @@
from nova.virt.disk import api as disk
from nova.virt import images
from nova.virt.libvirt import config as vconfig
+from nova.virt.libvirt import dmcrypt
from nova.virt.libvirt import lvm
from nova.virt.libvirt import rbd_utils
from nova.virt.libvirt import utils as libvirt_utils
@@ -69,6 +71,12 @@
CONF.register_opts(__imagebackend_opts, 'libvirt')
CONF.import_opt('image_cache_subdirectory_name', 'nova.virt.imagecache')
CONF.import_opt('preallocate_images', 'nova.virt.driver')
+CONF.import_opt('enabled', 'nova.compute.api',
+ group='ephemeral_storage_encryption')
+CONF.import_opt('cipher', 'nova.compute.api',
+ group='ephemeral_storage_encryption')
+CONF.import_opt('key_size', 'nova.compute.api',
+ group='ephemeral_storage_encryption')
CONF.import_opt('rbd_user', 'nova.virt.libvirt.volume', group='libvirt')
CONF.import_opt('rbd_secret_uuid', 'nova.virt.libvirt.volume', group='libvirt')
@@ -88,6 +96,12 @@ def __init__(self, source_type, driver_format, is_block_dev=False):
:driver_format: raw or qcow2
:is_block_dev:
"""
+ if (CONF.ephemeral_storage_encryption.enabled and
+ not self._supports_encryption()):
+ raise exception.NovaException(_('Incompatible settings: '
+ 'ephemeral storage encryption is supported '
+ 'only for LVM images.'))
+
self.source_type = source_type
self.driver_format = driver_format
self.is_block_dev = is_block_dev
@@ -103,6 +117,12 @@ def __init__(self, source_type, driver_format, is_block_dev=False):
# are trying to create a base file at the same time
self.lock_path = os.path.join(CONF.instances_path, 'locks')
+ def _supports_encryption(self):
+ """Used to test that the backend supports encryption.
+ Override in the subclass if backend supports encryption.
+ """
+ return False
+
@abc.abstractmethod
def create_image(self, prepare_template, base, size, *args, **kwargs):
"""Create image from template.
@@ -317,6 +337,7 @@ def clone(self, context, image_id_or_uri):
class Raw(Image):
def __init__(self, instance=None, disk_name=None, path=None):
+ self.disk_name = disk_name
super(Raw, self).__init__("file", "raw", is_block_dev=False)
self.path = (path or
@@ -331,6 +352,18 @@ def _get_driver_format(self):
data = images.qemu_img_info(self.path)
return data.file_format or 'raw'
+ def _supports_encryption(self):
+ # NOTE(dgenin): Kernel, ramdisk and disk.config are fetched using
+ # the Raw backend regardless of which backend is configured for
+ # ephemeral storage. Encryption for the Raw backend is not yet
+ # implemented so this loophole is necessary to allow other
+ # backends already supporting encryption to function. This can
+ # be removed once encryption for Raw is implemented.
+ if self.disk_name not in ['kernel', 'ramdisk', 'disk.config']:
+ return False
+ else:
+ return True
+
def correct_format(self):
if os.path.exists(self.path):
self.driver_format = self.resolve_driver_format()
@@ -436,11 +469,21 @@ def escape(filename):
def __init__(self, instance=None, disk_name=None, path=None):
super(Lvm, self).__init__("block", "raw", is_block_dev=True)
+ self.ephemeral_key_uuid = instance.get('ephemeral_key_uuid')
+
+ if self.ephemeral_key_uuid is not None:
+ self.key_manager = keymgr.API()
+ else:
+ self.key_manager = None
+
if path:
- info = lvm.volume_info(path)
- self.vg = info['VG']
- self.lv = info['LV']
self.path = path
+ if self.ephemeral_key_uuid is None:
+ info = lvm.volume_info(path)
+ self.vg = info['VG']
+ self.lv = info['LV']
+ else:
+ self.vg = CONF.libvirt.images_volume_group
else:
if not CONF.libvirt.images_volume_group:
raise RuntimeError(_('You should specify'
@@ -449,20 +492,32 @@ def __init__(self, instance=None, disk_name=None, path=None):
self.vg = CONF.libvirt.images_volume_group
self.lv = '%s_%s' % (instance['uuid'],
self.escape(disk_name))
- self.path = os.path.join('/dev', self.vg, self.lv)
+ if self.ephemeral_key_uuid is None:
+ self.path = os.path.join('/dev', self.vg, self.lv)
+ else:
+ self.lv_path = os.path.join('/dev', self.vg, self.lv)
+ self.path = '/dev/mapper/' + dmcrypt.volume_name(self.lv)
# TODO(pbrady): possibly deprecate libvirt.sparse_logical_volumes
# for the more general preallocate_images
self.sparse = CONF.libvirt.sparse_logical_volumes
self.preallocate = not self.sparse
+ def _supports_encryption(self):
+ return True
+
def _can_fallocate(self):
return False
def create_image(self, prepare_template, base, size, *args, **kwargs):
- filename = os.path.split(base)[-1]
-
- @utils.synchronized(filename, external=True, lock_path=self.lock_path)
+ def encrypt_lvm_image():
+ dmcrypt.create_volume(self.path.rpartition('/')[2],
+ self.lv_path,
+ CONF.ephemeral_storage_encryption.cipher,
+ CONF.ephemeral_storage_encryption.key_size,
+ key)
+
+ @utils.synchronized(base, external=True, lock_path=self.lock_path)
def create_lvm_image(base, size):
base_size = disk.get_disk_size(base)
self.verify_base_size(base, size, base_size=base_size)
@@ -470,17 +525,35 @@ def create_lvm_image(base, size):
size = size if resize else base_size
lvm.create_volume(self.vg, self.lv,
size, sparse=self.sparse)
+ if self.ephemeral_key_uuid is not None:
+ encrypt_lvm_image()
images.convert_image(base, self.path, 'raw', run_as_root=True)
if resize:
disk.resize2fs(self.path, run_as_root=True)
generated = 'ephemeral_size' in kwargs
-
+ if self.ephemeral_key_uuid is not None:
+ if 'context' in kwargs:
+ try:
+ # NOTE(dgenin): Key manager corresponding to the
+ # specific backend catches and reraises an
+ # an exception if key retrieval fails.
+ key = self.key_manager.get_key(kwargs['context'],
+ self.ephemeral_key_uuid).get_encoded()
+ except Exception:
+ with excutils.save_and_reraise_exception():
+ LOG.error(_LE("Failed to retrieve ephemeral encryption"
+ " key"))
+ else:
+ raise exception.NovaException(
+ _("Instance disk to be encrypted but no context provided"))
# Generate images with specified size right on volume
if generated and size:
lvm.create_volume(self.vg, self.lv,
size, sparse=self.sparse)
with self.remove_volume_on_error(self.path):
+ if self.ephemeral_key_uuid is not None:
+ encrypt_lvm_image()
prepare_template(target=self.path, *args, **kwargs)
else:
if not os.path.exists(base):
@@ -494,7 +567,11 @@ def remove_volume_on_error(self, path):
yield
except Exception:
with excutils.save_and_reraise_exception():
- lvm.remove_volumes(path)
+ if self.ephemeral_key_uuid is None:
+ lvm.remove_volumes(path)
+ else:
+ dmcrypt.delete_volume(path.rpartition('/')[2])
+ lvm.remove_volumes(self.lv_path)
def snapshot_extract(self, target, out_format):
images.convert_image(self.path, target, out_format,
@@ -655,16 +732,15 @@ def image(self, instance, disk_name, image_type=None):
:name: Image name.
:image_type: Image type.
Optional, is CONF.libvirt.images_type by default.
-
"""
backend = self.backend(image_type)
return backend(instance=instance, disk_name=disk_name)
- def snapshot(self, disk_path, image_type=None):
+ def snapshot(self, instance, disk_path, image_type=None):
"""Returns snapshot for given image
:path: path to image
:image_type: type of image
"""
backend = self.backend(image_type)
- return backend(path=disk_path)
+ return backend(instance=instance, path=disk_path)