summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/source/support-matrix.ini2
-rw-r--r--nova/api/openstack/compute/contrib/virtual_interfaces.py10
-rw-r--r--nova/api/openstack/compute/plugins/v3/virtual_interfaces.py12
-rw-r--r--nova/cells/messaging.py26
-rw-r--r--nova/compute/api.py12
-rw-r--r--nova/tests/unit/api/openstack/compute/contrib/test_virtual_interfaces.py12
-rw-r--r--nova/tests/unit/cells/test_cells_messaging.py132
-rw-r--r--nova/tests/unit/compute/test_compute_api.py20
-rw-r--r--nova/tests/unit/virt/libvirt/test_driver.py82
-rw-r--r--nova/tests/unit/virt/libvirt/test_host.py21
-rw-r--r--nova/tests/unit/virt/test_block_device.py103
-rw-r--r--nova/tests/unit/virt/test_virt_drivers.py1
-rw-r--r--nova/virt/block_device.py38
-rw-r--r--nova/virt/libvirt/driver.py41
-rw-r--r--nova/virt/libvirt/host.py5
15 files changed, 376 insertions, 141 deletions
diff --git a/doc/source/support-matrix.ini b/doc/source/support-matrix.ini
index be6d6cdfb6..00ab29c42f 100644
--- a/doc/source/support-matrix.ini
+++ b/doc/source/support-matrix.ini
@@ -807,7 +807,7 @@ driver-impl-libvirt-kvm-s390x=complete
driver-impl-libvirt-qemu-x86=complete
driver-impl-libvirt-lxc=complete
driver-impl-libvirt-xen=complete
-driver-impl-vmware=complete
+driver-impl-vmware=missing
driver-impl-hyperv=complete
driver-impl-ironic=missing
driver-impl-libvirt-parallels-vm=complete
diff --git a/nova/api/openstack/compute/contrib/virtual_interfaces.py b/nova/api/openstack/compute/contrib/virtual_interfaces.py
index db75afc533..1e319efa4e 100644
--- a/nova/api/openstack/compute/contrib/virtual_interfaces.py
+++ b/nova/api/openstack/compute/contrib/virtual_interfaces.py
@@ -15,9 +15,12 @@
"""The virtual interfaces extension."""
+import webob
+
from nova.api.openstack import common
from nova.api.openstack import extensions
from nova import compute
+from nova.i18n import _
from nova import network
@@ -46,7 +49,12 @@ class ServerVirtualInterfaceController(object):
context = req.environ['nova.context']
instance = common.get_instance(self.compute_api, context, server_id)
- vifs = self.network_api.get_vifs_by_instance(context, instance)
+ try:
+ vifs = self.network_api.get_vifs_by_instance(context, instance)
+ except NotImplementedError:
+ msg = _('Listing virtual interfaces is not supported by this '
+ 'cloud.')
+ raise webob.exc.HTTPBadRequest(explanation=msg)
limited_list = common.limited(vifs, req)
res = [entity_maker(context, vif) for vif in limited_list]
return {'virtual_interfaces': res}
diff --git a/nova/api/openstack/compute/plugins/v3/virtual_interfaces.py b/nova/api/openstack/compute/plugins/v3/virtual_interfaces.py
index 50c86a754b..df35624c78 100644
--- a/nova/api/openstack/compute/plugins/v3/virtual_interfaces.py
+++ b/nova/api/openstack/compute/plugins/v3/virtual_interfaces.py
@@ -15,10 +15,13 @@
"""The virtual interfaces extension."""
+import webob
+
from nova.api.openstack import common
from nova.api.openstack import extensions
from nova.api.openstack import wsgi
from nova import compute
+from nova.i18n import _
from nova import network
@@ -49,12 +52,17 @@ class ServerVirtualInterfaceController(wsgi.Controller):
authorize(context)
instance = common.get_instance(self.compute_api, context, server_id)
- vifs = self.network_api.get_vifs_by_instance(context, instance)
+ try:
+ vifs = self.network_api.get_vifs_by_instance(context, instance)
+ except NotImplementedError:
+ msg = _('Listing virtual interfaces is not supported by this '
+ 'cloud.')
+ raise webob.exc.HTTPBadRequest(explanation=msg)
limited_list = common.limited(vifs, req)
res = [entity_maker(context, vif) for vif in limited_list]
return {'virtual_interfaces': res}
- @extensions.expected_errors((404))
+ @extensions.expected_errors((400, 404))
def index(self, req, server_id):
"""Returns the list of VIFs for a given instance."""
return self._items(req, server_id,
diff --git a/nova/cells/messaging.py b/nova/cells/messaging.py
index a4ac21d481..baa6eef412 100644
--- a/nova/cells/messaging.py
+++ b/nova/cells/messaging.py
@@ -668,9 +668,15 @@ class _TargetedMessageMethods(_BaseMessageMethods):
# 1st arg is instance_uuid that we need to turn into the
# instance object.
instance_uuid = args[0]
+ # NOTE: compute/api.py loads these when retrieving an instance for an
+ # API request, so there's a good chance that this is what was loaded.
+ expected_attrs = ['metadata', 'system_metadata', 'security_groups',
+ 'info_cache']
+
try:
- instance = self.db.instance_get_by_uuid(message.ctxt,
- instance_uuid)
+ instance = objects.Instance.get_by_uuid(message.ctxt,
+ instance_uuid, expected_attrs=expected_attrs)
+ args[0] = instance
except exception.InstanceNotFound:
with excutils.save_and_reraise_exception():
# Must be a race condition. Let's try to resolve it by
@@ -679,22 +685,6 @@ class _TargetedMessageMethods(_BaseMessageMethods):
instance = {'uuid': instance_uuid}
self.msg_runner.instance_destroy_at_top(message.ctxt,
instance)
- # FIXME(comstud): This is temporary/transitional until I can
- # work out a better way to pass full objects down.
- EXPECTS_OBJECTS = ['start', 'stop', 'delete_instance_metadata',
- 'update_instance_metadata', 'shelve', 'unshelve']
- if method in EXPECTS_OBJECTS:
- inst_obj = objects.Instance()
- expected_attrs = None
- # shelve and unshelve requires 'info_cache' and 'metadata',
- # because of this fetching same from database.
- if method in ['shelve', 'unshelve']:
- expected_attrs = ['metadata', 'info_cache']
-
- inst_obj._from_db_object(message.ctxt, inst_obj, instance,
- expected_attrs=expected_attrs)
- instance = inst_obj
- args[0] = instance
return fn(message.ctxt, *args, **method_info['method_kwargs'])
def update_capabilities(self, message, cell_name, capabilities):
diff --git a/nova/compute/api.py b/nova/compute/api.py
index 433e855164..0fb8e6a23c 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -2490,7 +2490,7 @@ class API(base.Base):
elevated, instance.uuid, 'finished')
# reverse quota reservation for increased resource usage
- deltas = self._reverse_upsize_quota_delta(context, migration)
+ deltas = self._reverse_upsize_quota_delta(context, instance)
quotas = self._reserve_quota_delta(context, deltas, instance)
instance.task_state = task_states.RESIZE_REVERTING
@@ -2580,16 +2580,12 @@ class API(base.Base):
return API._resize_quota_delta(context, new_flavor, old_flavor, 1, 1)
@staticmethod
- def _reverse_upsize_quota_delta(context, migration_ref):
+ def _reverse_upsize_quota_delta(context, instance):
"""Calculate deltas required to reverse a prior upsizing
quota adjustment.
"""
- old_flavor = objects.Flavor.get_by_id(
- context, migration_ref['old_instance_type_id'])
- new_flavor = objects.Flavor.get_by_id(
- context, migration_ref['new_instance_type_id'])
-
- return API._resize_quota_delta(context, new_flavor, old_flavor, -1, -1)
+ return API._resize_quota_delta(context, instance.new_flavor,
+ instance.old_flavor, -1, -1)
@staticmethod
def _downsize_quota_delta(context, instance):
diff --git a/nova/tests/unit/api/openstack/compute/contrib/test_virtual_interfaces.py b/nova/tests/unit/api/openstack/compute/contrib/test_virtual_interfaces.py
index 5a595ec232..87d71ff1ff 100644
--- a/nova/tests/unit/api/openstack/compute/contrib/test_virtual_interfaces.py
+++ b/nova/tests/unit/api/openstack/compute/contrib/test_virtual_interfaces.py
@@ -85,6 +85,18 @@ class ServerVirtualInterfaceTestV21(test.NoDBTestCase):
self.controller.index,
fake_req, 'fake_uuid')
+ def test_list_vifs_neutron_notimplemented(self):
+ """Tests that a 400 is returned when using neutron as the backend"""
+ # unset the get_vifs_by_instance stub from setUp
+ self.mox.UnsetStubs()
+ self.flags(network_api_class='nova.network.neutronv2.api.API')
+ # reset the controller to use the neutron network API
+ self._set_controller()
+ self.stubs.Set(compute.api.API, "get", compute_api_get)
+ req = fakes.HTTPRequest.blank('')
+ self.assertRaises(webob.exc.HTTPBadRequest,
+ self.controller.index, req, FAKE_UUID)
+
class ServerVirtualInterfaceTestV20(ServerVirtualInterfaceTestV21):
diff --git a/nova/tests/unit/cells/test_cells_messaging.py b/nova/tests/unit/cells/test_cells_messaging.py
index 3d1d1ace06..a1824ab814 100644
--- a/nova/tests/unit/cells/test_cells_messaging.py
+++ b/nova/tests/unit/cells/test_cells_messaging.py
@@ -683,120 +683,62 @@ class CellsTargetedMethodsTestCase(test.TestCase):
self.src_msg_runner.build_instances(self.ctxt, self.tgt_cell_name,
build_inst_kwargs)
- def test_run_compute_api_method(self):
-
- instance_uuid = 'fake_instance_uuid'
- method_info = {'method': 'backup',
- 'method_args': (instance_uuid, 2, 3),
- 'method_kwargs': {'arg1': 'val1', 'arg2': 'val2'}}
- self.mox.StubOutWithMock(self.tgt_compute_api, 'backup')
- self.mox.StubOutWithMock(self.tgt_db_inst, 'instance_get_by_uuid')
-
- self.tgt_db_inst.instance_get_by_uuid(self.ctxt,
- instance_uuid).AndReturn('fake_instance')
- self.tgt_compute_api.backup(self.ctxt, 'fake_instance', 2, 3,
- arg1='val1', arg2='val2').AndReturn('fake_result')
- self.mox.ReplayAll()
-
- response = self.src_msg_runner.run_compute_api_method(
- self.ctxt,
- self.tgt_cell_name,
- method_info,
- True)
- result = response.value_or_raise()
- self.assertEqual('fake_result', result)
-
- def _run_compute_api_method_expects_object(self, tgt_compute_api_function,
- method_name,
- expected_attrs=None):
- # runs compute api methods which expects instance to be an object
- instance_uuid = 'fake_instance_uuid'
+ def _run_compute_api_method(self, method_name):
+ instance = objects.Instance(self.ctxt, uuid=uuidutils.generate_uuid())
method_info = {'method': method_name,
- 'method_args': (instance_uuid, 2, 3),
+ 'method_args': (instance.uuid, 2, 3),
'method_kwargs': {'arg1': 'val1', 'arg2': 'val2'}}
- self.mox.StubOutWithMock(self.tgt_db_inst, 'instance_get_by_uuid')
-
- self.tgt_db_inst.instance_get_by_uuid(self.ctxt,
- instance_uuid).AndReturn('fake_instance')
-
- def get_instance_mock():
- # NOTE(comstud): This block of code simulates the following
- # mox code:
- #
- # self.mox.StubOutWithMock(objects, 'Instance',
- # use_mock_anything=True)
- # self.mox.StubOutWithMock(objects.Instance,
- # '_from_db_object')
- # instance_mock = self.mox.CreateMock(objects.Instance)
- # objects.Instance().AndReturn(instance_mock)
- #
- # Unfortunately, the above code fails on py27 do to some
- # issue with the Mock object do to similar issue as this:
- # https://code.google.com/p/pymox/issues/detail?id=35
- #
- class FakeInstance(object):
- @classmethod
- def _from_db_object(cls, ctxt, obj, db_obj, **kwargs):
- pass
-
- instance_mock = FakeInstance()
-
- def fake_instance():
- return instance_mock
-
- self.stubs.Set(objects, 'Instance', fake_instance)
- self.mox.StubOutWithMock(instance_mock, '_from_db_object')
- return instance_mock
-
- instance = get_instance_mock()
- instance._from_db_object(self.ctxt,
- instance,
- 'fake_instance',
- expected_attrs=expected_attrs
- ).AndReturn(instance)
- tgt_compute_api_function(self.ctxt, instance, 2, 3,
- arg1='val1', arg2='val2').AndReturn('fake_result')
- self.mox.ReplayAll()
-
- response = self.src_msg_runner.run_compute_api_method(
- self.ctxt,
- self.tgt_cell_name,
- method_info,
- True)
- result = response.value_or_raise()
- self.assertEqual('fake_result', result)
+ expected_attrs = ['metadata', 'system_metadata', 'security_groups',
+ 'info_cache']
+
+ @mock.patch.object(self.tgt_compute_api, method_name,
+ return_value='fake-result')
+ @mock.patch.object(objects.Instance, 'get_by_uuid',
+ return_value=instance)
+ def run_method(mock_get_by_uuid, mock_method):
+ response = self.src_msg_runner.run_compute_api_method(
+ self.ctxt,
+ self.tgt_cell_name,
+ method_info,
+ True)
+ result = response.value_or_raise()
+ self.assertEqual('fake-result', result)
+
+ mock_get_by_uuid.assert_called_once_with(self.ctxt, instance.uuid,
+ expected_attrs=expected_attrs)
+ mock_method.assert_called_once_with(self.ctxt, instance, 2, 3,
+ arg1='val1', arg2='val2')
+
+ run_method()
def test_run_compute_api_method_expects_obj(self):
# Run compute_api start method
- self.mox.StubOutWithMock(self.tgt_compute_api, 'start')
- self._run_compute_api_method_expects_object(self.tgt_compute_api.start,
- 'start')
+ self._run_compute_api_method('start')
- def test_run_compute_api_method_expects_obj_with_info_cache(self):
+ def test_run_compute_api_method_shelve_with_info_cache(self):
# Run compute_api shelve method as it requires info_cache and
# metadata to be present in instance object
- self.mox.StubOutWithMock(self.tgt_compute_api, 'shelve')
- self._run_compute_api_method_expects_object(
- self.tgt_compute_api.shelve, 'shelve',
- expected_attrs=['metadata', 'info_cache'])
+ self._run_compute_api_method('shelve')
def test_run_compute_api_method_unknown_instance(self):
# Unknown instance should send a broadcast up that instance
# is gone.
- instance_uuid = 'fake_instance_uuid'
- instance = {'uuid': instance_uuid}
+ instance = objects.Instance(self.ctxt, uuid=uuidutils.generate_uuid())
+ instance_uuid = instance.uuid
method_info = {'method': 'reboot',
'method_args': (instance_uuid, 2, 3),
'method_kwargs': {'arg1': 'val1', 'arg2': 'val2'}}
- self.mox.StubOutWithMock(self.tgt_db_inst, 'instance_get_by_uuid')
+ self.mox.StubOutWithMock(objects.Instance, 'get_by_uuid')
self.mox.StubOutWithMock(self.tgt_msg_runner,
'instance_destroy_at_top')
- self.tgt_db_inst.instance_get_by_uuid(self.ctxt,
- 'fake_instance_uuid').AndRaise(
- exception.InstanceNotFound(instance_id=instance_uuid))
- self.tgt_msg_runner.instance_destroy_at_top(self.ctxt, instance)
+ objects.Instance.get_by_uuid(self.ctxt, instance.uuid,
+ expected_attrs=['metadata', 'system_metadata',
+ 'security_groups', 'info_cache']).AndRaise(
+ exception.InstanceNotFound(instance_id=instance_uuid))
+ self.tgt_msg_runner.instance_destroy_at_top(self.ctxt,
+ {'uuid': instance.uuid})
self.mox.ReplayAll()
diff --git a/nova/tests/unit/compute/test_compute_api.py b/nova/tests/unit/compute/test_compute_api.py
index fc613e9cff..1f3916c43b 100644
--- a/nova/tests/unit/compute/test_compute_api.py
+++ b/nova/tests/unit/compute/test_compute_api.py
@@ -1168,7 +1168,7 @@ class _ComputeAPIUnitTestMixIn(object):
self.context, fake_inst['uuid'], 'finished').AndReturn(
fake_mig)
self.compute_api._reverse_upsize_quota_delta(
- self.context, fake_mig).AndReturn('deltas')
+ self.context, fake_inst).AndReturn('deltas')
resvs = ['resvs']
fake_quotas = objects.Quotas.from_reservations(self.context, resvs)
@@ -1226,7 +1226,7 @@ class _ComputeAPIUnitTestMixIn(object):
delta = ['delta']
self.compute_api._reverse_upsize_quota_delta(
- self.context, fake_mig).AndReturn(delta)
+ self.context, fake_inst).AndReturn(delta)
resvs = ['resvs']
fake_quotas = objects.Quotas.from_reservations(self.context, resvs)
self.compute_api._reserve_quota_delta(
@@ -1244,6 +1244,22 @@ class _ComputeAPIUnitTestMixIn(object):
self.context,
fake_inst)
+ def test_reverse_quota_delta(self):
+ inst = self._create_instance_obj(params=None)
+ inst.old_flavor = self._create_flavor(vcpus=1, memory_mb=512)
+ inst.new_flavor = self._create_flavor(vcpus=2, memory_mb=4096)
+
+ expected_deltas = {
+ 'cores': -1 * (inst.new_flavor['vcpus'] -
+ inst.old_flavor['vcpus']),
+ 'ram': -1 * (inst.new_flavor['memory_mb'] -
+ inst.old_flavor['memory_mb'])
+ }
+
+ deltas = self.compute_api._reverse_upsize_quota_delta(
+ self.context, inst)
+ self.assertEqual(expected_deltas, deltas)
+
def _test_resize(self, flavor_id_passed=True,
same_host=False, allow_same_host=False,
allow_mig_same_host=False,
diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py
index 8930d9dea7..6ef3997588 100644
--- a/nova/tests/unit/virt/libvirt/test_driver.py
+++ b/nova/tests/unit/virt/libvirt/test_driver.py
@@ -6608,6 +6608,43 @@ class LibvirtConnTestCase(test.NoDBTestCase):
self.context, instance, block_device_info=None,
network_info=[], disk_info={}, migrate_data={})
+ def test_pre_live_migration_recreate_disk_info(self):
+
+ migrate_data = {'is_shared_block_storage': False,
+ 'is_shared_instance_path': False,
+ 'block_migration': True,
+ 'instance_relative_path': '/some/path/'}
+ disk_info = [{'disk_size': 5368709120, 'type': 'raw',
+ 'virt_disk_size': 5368709120,
+ 'path': '/some/path/disk',
+ 'backing_file': '', 'over_committed_disk_size': 0},
+ {'disk_size': 1073741824, 'type': 'raw',
+ 'virt_disk_size': 1073741824,
+ 'path': '/some/path/disk.eph0',
+ 'backing_file': '', 'over_committed_disk_size': 0}]
+ image_disk_info = {'/some/path/disk': 'raw',
+ '/some/path/disk.eph0': 'raw'}
+
+ drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False)
+ instance = objects.Instance(**self.test_instance)
+ instance_path = os.path.dirname(disk_info[0]['path'])
+ disk_info_path = os.path.join(instance_path, 'disk.info')
+
+ with contextlib.nested(
+ mock.patch.object(os, 'mkdir'),
+ mock.patch.object(fake_libvirt_utils, 'write_to_file'),
+ mock.patch.object(drvr, '_create_images_and_backing')
+ ) as (
+ mkdir, write_to_file, create_images_and_backing
+ ):
+ drvr.pre_live_migration(self.context, instance,
+ block_device_info=None,
+ network_info=[],
+ disk_info=jsonutils.dumps(disk_info),
+ migrate_data=migrate_data)
+ write_to_file.assert_called_with(disk_info_path,
+ jsonutils.dumps(image_disk_info))
+
def test_get_instance_disk_info_works_correctly(self):
# Test data
instance = objects.Instance(**self.test_instance)
@@ -11607,6 +11644,51 @@ class LibvirtDriverTestCase(test.NoDBTestCase):
flavor_obj = objects.Flavor(**flavor)
self._test_migrate_disk_and_power_off(flavor_obj)
+ @mock.patch('nova.utils.execute')
+ @mock.patch('nova.virt.libvirt.utils.copy_image')
+ @mock.patch('nova.virt.libvirt.driver.LibvirtDriver._destroy')
+ @mock.patch('nova.virt.libvirt.utils.get_instance_path')
+ @mock.patch('nova.virt.libvirt.driver.LibvirtDriver'
+ '._is_storage_shared_with')
+ @mock.patch('nova.virt.libvirt.driver.LibvirtDriver'
+ '.get_instance_disk_info')
+ def test_migrate_disk_and_power_off_resize_copy_disk_info(self,
+ mock_disk_info,
+ mock_shared,
+ mock_path,
+ mock_destroy,
+ mock_copy,
+ mock_execuate):
+
+ instance = self._create_instance()
+ disk_info = self._disk_info()
+ disk_info_text = jsonutils.loads(disk_info)
+ instance_base = os.path.dirname(disk_info_text[0]['path'])
+ flavor = {'root_gb': 10, 'ephemeral_gb': 25}
+ flavor_obj = objects.Flavor(**flavor)
+
+ mock_disk_info.return_value = disk_info
+ mock_path.return_value = instance_base
+ mock_shared.return_value = False
+
+ src_disk_info_path = os.path.join(instance_base + '_resize',
+ 'disk.info')
+
+ with mock.patch.object(os.path, 'exists', autospec=True) \
+ as mock_exists:
+ # disk.info exists on the source
+ mock_exists.side_effect = \
+ lambda path: path == src_disk_info_path
+ self.drvr.migrate_disk_and_power_off(context.get_admin_context(),
+ instance, mock.sentinel,
+ flavor_obj, None)
+ self.assertTrue(mock_exists.called)
+
+ dst_disk_info_path = os.path.join(instance_base, 'disk.info')
+ mock_copy.assert_any_call(src_disk_info_path, dst_disk_info_path,
+ host=mock.sentinel, on_execute=mock.ANY,
+ on_completion=mock.ANY)
+
def test_wait_for_running(self):
def fake_get_info(instance):
if instance['name'] == "not_found":
diff --git a/nova/tests/unit/virt/libvirt/test_host.py b/nova/tests/unit/virt/libvirt/test_host.py
index 1f7f68ca6d..e7df6b40eb 100644
--- a/nova/tests/unit/virt/libvirt/test_host.py
+++ b/nova/tests/unit/virt/libvirt/test_host.py
@@ -660,6 +660,27 @@ class HostTestCase(test.NoDBTestCase):
self.assertEqual(vconfig.LibvirtConfigCaps, type(caps))
self.assertNotIn('aes', [x.name for x in caps.host.cpu.features])
+ def test_get_capabilities_no_host_cpu_model(self):
+ """Tests that cpu features are not retrieved when the host cpu model
+ is not in the capabilities.
+ """
+ fake_caps_xml = '''
+<capabilities>
+ <host>
+ <uuid>cef19ce0-0ca2-11df-855d-b19fbce37686</uuid>
+ <cpu>
+ <arch>x86_64</arch>
+ <vendor>Intel</vendor>
+ </cpu>
+ </host>
+</capabilities>'''
+ with mock.patch.object(fakelibvirt.virConnect, 'getCapabilities',
+ return_value=fake_caps_xml):
+ caps = self.host.get_capabilities()
+ self.assertEqual(vconfig.LibvirtConfigCaps, type(caps))
+ self.assertIsNone(caps.host.cpu.model)
+ self.assertEqual(0, len(caps.host.cpu.features))
+
@mock.patch.object(fakelibvirt.virConnect, "getHostname")
def test_get_hostname_caching(self, mock_hostname):
mock_hostname.return_value = "foo"
diff --git a/nova/tests/unit/virt/test_block_device.py b/nova/tests/unit/virt/test_block_device.py
index 15e1a3c4e0..937d242da2 100644
--- a/nova/tests/unit/virt/test_block_device.py
+++ b/nova/tests/unit/virt/test_block_device.py
@@ -363,13 +363,15 @@ class TestDriverBlockDevice(test.NoDBTestCase):
fake_volume, check_attach=True,
fail_check_attach=False, driver_attach=False,
fail_driver_attach=False, volume_attach=True,
- fail_volume_attach=False, access_mode='rw'):
+ fail_volume_attach=False, access_mode='rw',
+ availability_zone=None):
elevated_context = self.context.elevated()
self.stubs.Set(self.context, 'elevated',
lambda: elevated_context)
self.mox.StubOutWithMock(driver_bdm._bdm_obj, 'save')
self.mox.StubOutWithMock(encryptors, 'get_encryption_metadata')
- instance_detail = {'id': '123', 'uuid': 'fake_uuid'}
+ instance_detail = {'id': '123', 'uuid': 'fake_uuid',
+ 'availability_zone': availability_zone}
instance = fake_instance.fake_instance_obj(self.context,
**instance_detail)
connector = {'ip': 'fake_ip', 'host': 'fake_host'}
@@ -591,6 +593,35 @@ class TestDriverBlockDevice(test.NoDBTestCase):
self.virt_driver, wait_func)
self.assertEqual(test_bdm.volume_id, 'fake-volume-id-2')
+ def test_snapshot_attach_no_volume_cinder_cross_az_attach_false(self):
+ # Tests that the volume created from the snapshot has the same AZ as
+ # the instance.
+ self.flags(cross_az_attach=False, group='cinder')
+ no_volume_snapshot = self.snapshot_bdm.copy()
+ no_volume_snapshot['volume_id'] = None
+ test_bdm = self.driver_classes['snapshot'](no_volume_snapshot)
+
+ snapshot = {'id': 'fake-volume-id-1',
+ 'attach_status': 'detached'}
+ volume = {'id': 'fake-volume-id-2',
+ 'attach_status': 'detached'}
+
+ wait_func = self.mox.CreateMockAnything()
+
+ self.volume_api.get_snapshot(self.context,
+ 'fake-snapshot-id-1').AndReturn(snapshot)
+ self.volume_api.create(self.context, 3, '', '', snapshot,
+ availability_zone='test-az').AndReturn(volume)
+ wait_func(self.context, 'fake-volume-id-2').AndReturn(None)
+ instance, expected_conn_info = self._test_volume_attach(
+ test_bdm, no_volume_snapshot, volume,
+ availability_zone='test-az')
+ self.mox.ReplayAll()
+
+ test_bdm.attach(self.context, instance, self.volume_api,
+ self.virt_driver, wait_func)
+ self.assertEqual('fake-volume-id-2', test_bdm.volume_id)
+
def test_snapshot_attach_fail_volume(self):
fail_volume_snapshot = self.snapshot_bdm.copy()
fail_volume_snapshot['volume_id'] = None
@@ -672,6 +703,32 @@ class TestDriverBlockDevice(test.NoDBTestCase):
self.virt_driver, wait_func)
self.assertEqual(test_bdm.volume_id, 'fake-volume-id-2')
+ def test_image_attach_no_volume_cinder_cross_az_attach_false(self):
+ # Tests that the volume created from the image has the same AZ as the
+ # instance.
+ self.flags(cross_az_attach=False, group='cinder')
+ no_volume_image = self.image_bdm.copy()
+ no_volume_image['volume_id'] = None
+ test_bdm = self.driver_classes['image'](no_volume_image)
+
+ image = {'id': 'fake-image-id-1'}
+ volume = {'id': 'fake-volume-id-2',
+ 'attach_status': 'detached'}
+
+ wait_func = self.mox.CreateMockAnything()
+
+ self.volume_api.create(self.context, 1, '', '', image_id=image['id'],
+ availability_zone='test-az').AndReturn(volume)
+ wait_func(self.context, 'fake-volume-id-2').AndReturn(None)
+ instance, expected_conn_info = self._test_volume_attach(
+ test_bdm, no_volume_image, volume,
+ availability_zone='test-az')
+ self.mox.ReplayAll()
+
+ test_bdm.attach(self.context, instance, self.volume_api,
+ self.virt_driver, wait_func)
+ self.assertEqual('fake-volume-id-2', test_bdm.volume_id)
+
def test_image_attach_fail_volume(self):
fail_volume_image = self.image_bdm.copy()
fail_volume_image['volume_id'] = None
@@ -755,7 +812,7 @@ class TestDriverBlockDevice(test.NoDBTestCase):
vol_create.assert_called_once_with(
self.context, test_bdm.volume_size, 'fake-uuid-blank-vol',
- '', availability_zone=instance.availability_zone)
+ '', availability_zone=None)
vol_delete.assert_called_once_with(
self.context, volume['id'])
@@ -778,13 +835,42 @@ class TestDriverBlockDevice(test.NoDBTestCase):
vol_create.assert_called_once_with(
self.context, test_bdm.volume_size, 'fake-uuid-blank-vol',
- '', availability_zone=instance.availability_zone)
+ '', availability_zone=None)
vol_attach.assert_called_once_with(self.context, instance,
self.volume_api,
self.virt_driver,
do_check_attach=True)
self.assertEqual('fake-volume-id-2', test_bdm.volume_id)
+ def test_blank_attach_volume_cinder_cross_az_attach_false(self):
+ # Tests that the blank volume created is in the same availability zone
+ # as the instance.
+ self.flags(cross_az_attach=False, group='cinder')
+ no_blank_volume = self.blank_bdm.copy()
+ no_blank_volume['volume_id'] = None
+ test_bdm = self.driver_classes['blank'](no_blank_volume)
+ updates = {'uuid': 'fake-uuid', 'availability_zone': 'test-az'}
+ instance = fake_instance.fake_instance_obj(mock.sentinel.ctx,
+ **updates)
+ volume_class = self.driver_classes['volume']
+ volume = {'id': 'fake-volume-id-2',
+ 'display_name': 'fake-uuid-blank-vol'}
+
+ with mock.patch.object(self.volume_api, 'create',
+ return_value=volume) as vol_create:
+ with mock.patch.object(volume_class, 'attach') as vol_attach:
+ test_bdm.attach(self.context, instance, self.volume_api,
+ self.virt_driver)
+
+ vol_create.assert_called_once_with(
+ self.context, test_bdm.volume_size, 'fake-uuid-blank-vol',
+ '', availability_zone='test-az')
+ vol_attach.assert_called_once_with(self.context, instance,
+ self.volume_api,
+ self.virt_driver,
+ do_check_attach=True)
+ self.assertEqual('fake-volume-id-2', test_bdm.volume_id)
+
def test_convert_block_devices(self):
converted = driver_block_device._convert_block_devices(
self.driver_classes['volume'],
@@ -864,3 +950,12 @@ class TestDriverBlockDevice(test.NoDBTestCase):
for bdm in (test_swap, test_ephemeral):
self.assertFalse(driver_block_device.is_block_device_mapping(
bdm._bdm_obj))
+
+ def test_get_volume_create_az_cinder_cross_az_attach_true(self):
+ # Tests that we get None back if cinder.cross_az_attach=True even if
+ # the instance has an AZ assigned. Note that since cross_az_attach
+ # defaults to True we don't need to set a flag explicitly for the test.
+ updates = {'availability_zone': 'test-az'}
+ instance = fake_instance.fake_instance_obj(self.context, **updates)
+ self.assertIsNone(
+ driver_block_device._get_volume_create_az_value(instance))
diff --git a/nova/tests/unit/virt/test_virt_drivers.py b/nova/tests/unit/virt/test_virt_drivers.py
index 07fc30a8c2..250907e552 100644
--- a/nova/tests/unit/virt/test_virt_drivers.py
+++ b/nova/tests/unit/virt/test_virt_drivers.py
@@ -114,6 +114,7 @@ class _FakeDriverBackendTestCase(object):
rescue_kernel_id="3",
rescue_ramdisk_id=None,
snapshots_directory='./',
+ sysinfo_serial='none',
group='libvirt')
def fake_extend(image, size):
diff --git a/nova/virt/block_device.py b/nova/virt/block_device.py
index 8499522d35..14202ba08d 100644
--- a/nova/virt/block_device.py
+++ b/nova/virt/block_device.py
@@ -16,6 +16,7 @@ import functools
import itertools
import operator
+from oslo_config import cfg
from oslo_log import log as logging
from oslo_serialization import jsonutils
from oslo_utils import excutils
@@ -29,6 +30,9 @@ from nova import objects
from nova.objects import base as obj_base
from nova.volume import encryptors
+CONF = cfg.CONF
+CONF.import_opt('cross_az_attach', 'nova.volume.cinder', group='cinder')
+
LOG = logging.getLogger(__name__)
@@ -55,6 +59,34 @@ def update_db(method):
return wrapped
+def _get_volume_create_az_value(instance):
+ """Determine az to use when creating a volume
+
+ Uses the cinder.cross_az_attach config option to determine the availability
+ zone value to use when creating a volume.
+
+ :param nova.objects.Instance instance: The instance for which the volume
+ will be created and attached.
+ :returns: The availability_zone value to pass to volume_api.create
+ """
+ # If we're allowed to attach a volume in any AZ to an instance in any AZ,
+ # then we don't care what AZ the volume is in so don't specify anything.
+ if CONF.cinder.cross_az_attach:
+ return None
+ # Else the volume has to be in the same AZ as the instance otherwise we
+ # fail. If the AZ is not in Cinder the volume create will fail. But on the
+ # other hand if the volume AZ and instance AZ don't match and
+ # cross_az_attach is False, then volume_api.check_attach will fail too, so
+ # we can't really win. :)
+ # TODO(mriedem): It would be better from a UX perspective if we could do
+ # some validation in the API layer such that if we know we're going to
+ # specify the AZ when creating the volume and that AZ is not in Cinder, we
+ # could fail the boot from volume request early with a 400 rather than
+ # fail to build the instance on the compute node which results in a
+ # NoValidHost error.
+ return instance.availability_zone
+
+
class DriverBlockDevice(dict):
"""A dict subclass that represents block devices used by the virt layer.
@@ -327,7 +359,7 @@ class DriverSnapshotBlockDevice(DriverVolumeBlockDevice):
virt_driver, wait_func=None, do_check_attach=True):
if not self.volume_id:
- av_zone = instance.availability_zone
+ av_zone = _get_volume_create_az_value(instance)
snapshot = volume_api.get_snapshot(context,
self.snapshot_id)
vol = volume_api.create(context, self.volume_size, '', '',
@@ -351,7 +383,7 @@ class DriverImageBlockDevice(DriverVolumeBlockDevice):
def attach(self, context, instance, volume_api,
virt_driver, wait_func=None, do_check_attach=True):
if not self.volume_id:
- av_zone = instance.availability_zone
+ av_zone = _get_volume_create_az_value(instance)
vol = volume_api.create(context, self.volume_size,
'', '', image_id=self.image_id,
availability_zone=av_zone)
@@ -374,7 +406,7 @@ class DriverBlankBlockDevice(DriverVolumeBlockDevice):
virt_driver, wait_func=None, do_check_attach=True):
if not self.volume_id:
vol_name = instance.uuid + '-blank-vol'
- av_zone = instance.availability_zone
+ av_zone = _get_volume_create_az_value(instance)
vol = volume_api.create(context, self.volume_size, vol_name, '',
availability_zone=av_zone)
if wait_func:
diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py
index 1b5c4b9a99..37c06ed50b 100644
--- a/nova/virt/libvirt/driver.py
+++ b/nova/virt/libvirt/driver.py
@@ -5206,7 +5206,8 @@ class LibvirtDriver(driver.ComputeDriver):
else:
cpu = self._vcpu_model_to_cpu_config(guest_cpu)
- u = "http://libvirt.org/html/libvirt-libvirt.html#virCPUCompareResult"
+ u = ("http://libvirt.org/html/libvirt-libvirt-host.html#"
+ "virCPUCompareResult")
m = _("CPU doesn't have compatibility.\n\n%(ret)s\n\nRefer to %(u)s")
# unknown character exists in xml, then libvirt complains
try:
@@ -5825,6 +5826,24 @@ class LibvirtDriver(driver.ComputeDriver):
raise exception.DestinationDiskExists(path=instance_dir)
os.mkdir(instance_dir)
+ # Recreate the disk.info file and in doing so stop the
+ # imagebackend from recreating it incorrectly by inspecting the
+ # contents of each file when using the Raw backend.
+ if disk_info:
+ image_disk_info = {}
+ for info in jsonutils.loads(disk_info):
+ image_file = os.path.basename(info['path'])
+ image_path = os.path.join(instance_dir, image_file)
+ image_disk_info[image_path] = info['type']
+
+ LOG.debug('Creating disk.info with the contents: %s',
+ image_disk_info, instance=instance)
+
+ image_disk_info_path = os.path.join(instance_dir,
+ 'disk.info')
+ libvirt_utils.write_to_file(image_disk_info_path,
+ jsonutils.dumps(image_disk_info))
+
if not is_shared_block_storage:
# Ensure images and backing files are present.
self._create_images_and_backing(
@@ -6320,6 +6339,11 @@ class LibvirtDriver(driver.ComputeDriver):
dest = None
utils.execute('mkdir', '-p', inst_base)
+ on_execute = lambda process: \
+ self.job_tracker.add_job(instance, process.pid)
+ on_completion = lambda process: \
+ self.job_tracker.remove_job(instance, process.pid)
+
active_flavor = instance.get_flavor()
for info in disk_info:
# assume inst_base == dirname(info['path'])
@@ -6339,11 +6363,6 @@ class LibvirtDriver(driver.ComputeDriver):
# finish_migration/_create_image to re-create it for us.
continue
- on_execute = lambda process: self.job_tracker.add_job(
- instance, process.pid)
- on_completion = lambda process: self.job_tracker.remove_job(
- instance, process.pid)
-
if info['type'] == 'qcow2' and info['backing_file']:
tmp_path = from_path + "_rbase"
# merge backing file
@@ -6362,6 +6381,16 @@ class LibvirtDriver(driver.ComputeDriver):
libvirt_utils.copy_image(from_path, img_path, host=dest,
on_execute=on_execute,
on_completion=on_completion)
+
+ # Ensure disk.info is written to the new path to avoid disks being
+ # reinspected and potentially changing format.
+ src_disk_info_path = os.path.join(inst_base_resize, 'disk.info')
+ if os.path.exists(src_disk_info_path):
+ dst_disk_info_path = os.path.join(inst_base, 'disk.info')
+ libvirt_utils.copy_image(src_disk_info_path,
+ dst_disk_info_path,
+ host=dest, on_execute=on_execute,
+ on_completion=on_completion)
except Exception:
with excutils.save_and_reraise_exception():
self._cleanup_remote_migration(dest, inst_base,
diff --git a/nova/virt/libvirt/host.py b/nova/virt/libvirt/host.py
index 36a9942673..ea35b856e3 100644
--- a/nova/virt/libvirt/host.py
+++ b/nova/virt/libvirt/host.py
@@ -743,7 +743,10 @@ class Host(object):
LOG.info(_LI("Libvirt host capabilities %s"), xmlstr)
self._caps = vconfig.LibvirtConfigCaps()
self._caps.parse_str(xmlstr)
- if hasattr(libvirt, 'VIR_CONNECT_BASELINE_CPU_EXPAND_FEATURES'):
+ # NOTE(mriedem): Don't attempt to get baseline CPU features
+ # if libvirt can't determine the host cpu model.
+ if (hasattr(libvirt, 'VIR_CONNECT_BASELINE_CPU_EXPAND_FEATURES')
+ and self._caps.host.cpu.model is not None):
try:
features = self.get_connection().baselineCPU(
[self._caps.host.cpu.to_xml()],