diff --git a/nova/tests/unit/virt/xenapi/test_vmops.py b/nova/tests/unit/virt/xenapi/test_vmops.py index 624a6fcf9cec..fe4a187fbb0e 100644 --- a/nova/tests/unit/virt/xenapi/test_vmops.py +++ b/nova/tests/unit/virt/xenapi/test_vmops.py @@ -1794,6 +1794,78 @@ class LiveMigrateHelperTestCase(VMOpsTestBase): self._call_live_migrate_command_with_migrate_send_data, migrate_data) + @mock.patch.object(vmops.VMOps, '_call_live_migrate_command') + def test_check_can_live_migrate_source_with_xcp2(self, mock_call_migrate): + ctxt = 'ctxt' + fake_instance = {"name": "fake_instance"} + fake_dest_check_data = objects.XenapiLiveMigrateData() + fake_dest_check_data.block_migration = True + mock_call_migrate.side_effect = \ + xenapi_fake.xenapi_session.XenAPI.Failure(['VDI_NOT_IN_MAP']) + + with mock.patch.object(self.vmops, + '_get_iscsi_srs') as mock_iscsi_srs, \ + mock.patch.object(self.vmops, + '_get_vm_opaque_ref') as mock_vm, \ + mock.patch.object(self.vmops, + '_get_host_software_versions') as mock_host_sw: + mock_iscsi_srs.return_value = [] + mock_vm.return_value = 'vm_ref' + mock_host_sw.return_value = {'platform_name': 'XCP', + 'platform_version': '2.1.0'} + fake_returned_data = self.vmops.check_can_live_migrate_source( + ctxt, fake_instance, fake_dest_check_data) + + self.assertEqual(fake_returned_data, fake_dest_check_data) + + @mock.patch.object(vmops.VMOps, '_call_live_migrate_command') + def test_check_can_live_migrate_source_with_xcp2_vif_raise(self, + mock_call_migrate): + ctxt = 'ctxt' + fake_instance = {"name": "fake_instance"} + fake_dest_check_data = objects.XenapiLiveMigrateData() + fake_dest_check_data.block_migration = True + mock_call_migrate.side_effect = \ + xenapi_fake.xenapi_session.XenAPI.Failure(['VIF_NOT_IN_MAP']) + + with mock.patch.object(self.vmops, + '_get_iscsi_srs') as mock_iscsi_srs, \ + mock.patch.object(self.vmops, + '_get_vm_opaque_ref') as mock_vm, \ + mock.patch.object(self.vmops, + '_get_host_software_versions') as mock_host_sw: + mock_iscsi_srs.return_value = [] + mock_vm.return_value = 'vm_ref' + mock_host_sw.return_value = {'platform_name': 'XCP', + 'platform_version': '2.1.0'} + self.assertRaises(exception.MigrationPreCheckError, + self.vmops.check_can_live_migrate_source, ctxt, + fake_instance, fake_dest_check_data) + + @mock.patch.object(vmops.VMOps, '_call_live_migrate_command') + def test_check_can_live_migrate_source_with_xcp2_sw_raise(self, + mock_call_migrate): + ctxt = 'ctxt' + fake_instance = {"name": "fake_instance"} + fake_dest_check_data = objects.XenapiLiveMigrateData() + fake_dest_check_data.block_migration = True + mock_call_migrate.side_effect = \ + xenapi_fake.xenapi_session.XenAPI.Failure(['VDI_NOT_IN_MAP']) + + with mock.patch.object(self.vmops, + '_get_iscsi_srs') as mock_iscsi_srs, \ + mock.patch.object(self.vmops, + '_get_vm_opaque_ref') as mock_vm, \ + mock.patch.object(self.vmops, + '_get_host_software_versions') as mock_host_sw: + mock_iscsi_srs.return_value = [] + mock_vm.return_value = 'vm_ref' + mock_host_sw.return_value = {'platform_name': 'XCP', + 'platform_version': '1.1.0'} + self.assertRaises(exception.MigrationPreCheckError, + self.vmops.check_can_live_migrate_source, ctxt, + fake_instance, fake_dest_check_data) + def test_generate_vif_network_map(self): with mock.patch.object(self._session.VIF, 'get_other_config') as mock_other_config, \ diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py index fd1b7e40a958..a0f9784ba489 100644 --- a/nova/virt/xenapi/fake.py +++ b/nova/virt/xenapi/fake.py @@ -109,11 +109,14 @@ def _create_pool(name_label): {'name_label': name_label}) -def create_host(name_label, hostname='fake_name', address='fake_addr'): +def create_host(name_label, hostname='fake_name', address='fake_addr', + software_version={'platform_name': 'fake_platform', + 'platform_version': '1.0.0'}): host_ref = _create_object('host', - {'name_label': name_label, - 'hostname': hostname, - 'address': address}) + {'name_label': name_label, + 'hostname': hostname, + 'address': address, + 'software_version': software_version}) host_default_sr_ref = _create_local_srs(host_ref) _create_local_pif(host_ref) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 3d0a4e9af6bd..27fe23abc7fb 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -36,6 +36,7 @@ from oslo_utils import netutils from oslo_utils import strutils from oslo_utils import timeutils from oslo_utils import units +from oslo_utils import versionutils import six from nova import block_device @@ -2227,6 +2228,22 @@ class VMOps(object): host_uuid = self._get_host_uuid_from_aggregate(context, hostname) return self._session.call_xenapi("host.get_by_uuid", host_uuid) + def _get_host_ref_no_aggr(self): + # Pull the current host ref from Dom0's resident_on field. This + # allows us a simple way to pull the accurate host without aggregates + dom0_rec = self._session.call_xenapi("VM.get_all_records_where", + 'field "domid"="0"') + dom0_ref = list(dom0_rec.keys())[0] + + return dom0_rec[dom0_ref]['resident_on'] + + def _get_host_software_versions(self): + # Get software versions from host.get_record. + # Works around aggregate checking as not all places use aggregates. + host_ref = self._get_host_ref_no_aggr() + host_rec = self._session.call_xenapi("host.get_record", host_ref) + return host_rec['software_version'] + def _get_network_ref(self): # Get the network to for migrate. # This is the one associated with the pif marked management. From cli: @@ -2345,14 +2362,27 @@ class VMOps(object): raise exception.MigrationError(reason=_('XAPI supporting ' 'relax-xsm-sr-check=true required')) + # TODO(bkaminski): This entire block needs to be removed from this + # if statement. Live Migration should assert_can_migrate either way. if ('block_migration' in dest_check_data and dest_check_data.block_migration): vm_ref = self._get_vm_opaque_ref(instance_ref) + host_sw = self._get_host_software_versions() + host_pfv = host_sw['platform_version'] try: self._call_live_migrate_command( "VM.assert_can_migrate", vm_ref, dest_check_data) except self._session.XenAPI.Failure as exc: reason = exc.details[0] + # XCP>=2.1 Will error on this assert call if iSCSI are attached + # as the SR has not been configured on the hypervisor at this + # point in the migration. We swallow this exception until a + # more intensive refactor can be done to correct this. + if ("VDI_NOT_IN_MAP" in reason and + host_sw['platform_name'] == "XCP" and + versionutils.is_compatible("2.1.0", host_pfv)): + LOG.debug("Skipping exception for XCP>=2.1.0, %s", reason) + return dest_check_data msg = _('assert_can_migrate failed because: %s') % reason LOG.debug(msg, exc_info=True) raise exception.MigrationPreCheckError(reason=msg)