diff --git a/nova/test.py b/nova/test.py index b483ef469513..8359aafa8bd3 100644 --- a/nova/test.py +++ b/nova/test.py @@ -422,6 +422,12 @@ class TestCase(base.BaseTestCase): for k, v in kw.items(): CONF.set_override(k, v, group) + def reset_flags(self, *k, **kw): + """Reset flag variables for a test.""" + group = kw.pop('group') + for flag in k: + CONF.clear_override(flag, group) + def enforce_fk_constraints(self, engine=None): if engine is None: engine = db_api.get_engine() diff --git a/nova/tests/fixtures/libvirt.py b/nova/tests/fixtures/libvirt.py index 76840ef52c58..5b2689d0105b 100644 --- a/nova/tests/fixtures/libvirt.py +++ b/nova/tests/fixtures/libvirt.py @@ -905,6 +905,13 @@ def _parse_disk_info(element): return disk_info +def _parse_vcpu_info(element): + vcpu_info = {} + vcpu_info['number'] = int(element.text) + vcpu_info['cpuset'] = element.get('cpuset') + return vcpu_info + + def _parse_nic_info(element): nic_info = {} nic_info['type'] = element.get('type', 'bridge') @@ -1097,7 +1104,7 @@ class Domain(object): vcpu = tree.find('./vcpu') if vcpu is not None: - definition['vcpu'] = int(vcpu.text) + definition['vcpu'] = _parse_vcpu_info(vcpu) memory = tree.find('./memory') if memory is not None: @@ -1543,12 +1550,16 @@ class Domain(object): """ + vcpuset = '' + if self._def['vcpu'].get('cpuset'): + vcpuset = ' cpuset="' + self._def['vcpu']['cpuset'] + '"' + return ''' %(name)s %(uuid)s %(memory)s %(memory)s - %(vcpu)s + %(vcpu)s hvm @@ -1596,7 +1607,8 @@ class Domain(object): ''' % {'name': self._def['name'], 'uuid': self._def['uuid'], 'memory': self._def['memory'], - 'vcpu': self._def['vcpu'], + 'vcpuset': vcpuset, + 'vcpu': self._def['vcpu']['number'], 'arch': self._def['os']['arch'], 'disks': disks, 'nics': nics, @@ -1628,7 +1640,7 @@ class Domain(object): def vcpus(self): vcpus = ([], []) - for i in range(0, self._def['vcpu']): + for i in range(0, self._def['vcpu']['number']): vcpus[0].append((i, 1, 120405, i)) vcpus[1].append((True, True, True, True)) return vcpus diff --git a/nova/tests/functional/libvirt/test_live_migration.py b/nova/tests/functional/libvirt/test_live_migration.py index 31ff9dfca05b..c339a79196f7 100644 --- a/nova/tests/functional/libvirt/test_live_migration.py +++ b/nova/tests/functional/libvirt/test_live_migration.py @@ -210,3 +210,124 @@ class LiveMigrationQueuedAbortTestLeftoversRemoved(LiveMigrationWithLockBase): ) self.assertEqual(1, len(port_binding_server_b)) self.assertNotIn('dest', port_binding_server_b) + + +class LiveMigrationWithCpuSharedSet( + libvirt_base.LibvirtMigrationMixin, + libvirt_base.ServersTestBase, + integrated_helpers.InstanceHelperMixin +): + + api_major_version = 'v2.1' + # Microversion 2.74 is required to boot a server on a specific host, + # which is used in the below tests. + microversion = '2.74' + ADMIN_API = True + + def setUp(self): + super().setUp() + + self.src_hostname = self.start_compute(hostname='src') + self.dest_hostname = self.start_compute(hostname='dest') + + self.src = self.computes[self.src_hostname] + self.dest = self.computes[self.dest_hostname] + + def get_host(self, server_id): + server = self.api.get_server(server_id) + return server['OS-EXT-SRV-ATTR:host'] + + def test_live_migration_to_different_cpu_shared_set(self): + """Reproducer for bug 1869804 #1. + An instance live migrated from a host with a cpu_shared_set to a + destination host with a different cpu_shared_set should be updated + to use the destination cpu_shared_set. + """ + self.flags(cpu_shared_set='0,1', group='compute') + self.restart_compute_service('src') + self.restart_compute_service('dest') + self.server = self._create_server(host='src', networks='none') + + conn = self.src.driver._host.get_connection() + dom = conn.lookupByUUIDString(self.server['id']) + xml = dom.XMLDesc(0) + self.assertIn('1', xml) + + self.flags(cpu_shared_set='3,4', group='compute') + self.restart_compute_service('dest') + self._live_migrate(self.server, 'completed') + self.assertEqual('dest', self.get_host(self.server['id'])) + + conn = self.dest.driver._host.get_connection() + dom = conn.lookupByUUIDString(self.server['id']) + xml = dom.XMLDesc(0) + # The destination should be updated to "3-4" but it is not the case. + self.assertIn('1', xml) + self.assertNotIn('1', xml) + + def test_live_migration_to_no_cpu_shared_set(self): + """Reproducer for bug 1869804 #2. + An instance live migrated from a host with a cpu_shared_set to a + destination host without cpu_shared_set should not keep cpuset + settings. + """ + self.flags(cpu_shared_set='0,1', group='compute') + self.restart_compute_service('src') + self.restart_compute_service('dest') + self.server = self._create_server(host='src', networks='none') + + self.reset_flags('cpu_shared_set', group='compute') + self.restart_compute_service('src') + self.restart_compute_service('dest') + + # Here we just create a server2 to ensure cpu_shared_set is not + # configured on destination host. + self.server2 = self._create_server(host='dest', networks='none') + + conn = self.src.driver._host.get_connection() + dom = conn.lookupByUUIDString(self.server['id']) + xml = dom.XMLDesc(0) + self.assertIn('1', xml) + + conn = self.dest.driver._host.get_connection() + dom = conn.lookupByUUIDString(self.server2['id']) + xml = dom.XMLDesc(0) + # This prove that cpu_shared_set is not configured on destination host + self.assertIn('1', xml) + + self._live_migrate(self.server, 'completed') + self.assertEqual('dest', self.get_host(self.server['id'])) + + conn = self.dest.driver._host.get_connection() + dom = conn.lookupByUUIDString(self.server['id']) + xml = dom.XMLDesc(0) + # The destination cpuset should be removed because the + # host has no cpu_shared_set configured. Which is not the case due to + # the bug. + self.assertIn('1', xml) + self.assertNotIn('1', xml) + + def test_live_migration_from_no_cpu_shared_set_to_cpu_shared_set(self): + """Reproducer for bug 1869804 #3. + An instance live migrated from a host without a cpu_shared_set to a + destination host with cpu_shared_set should be updated to use + the destination cpu_shared_set. + """ + self.server = self._create_server(host='src', networks='none') + + conn = self.src.driver._host.get_connection() + dom = conn.lookupByUUIDString(self.server['id']) + xml = dom.XMLDesc(0) + self.assertIn('1', xml) + + self.flags(cpu_shared_set='0,1', group='compute') + self.restart_compute_service('dest') + self._live_migrate(self.server, 'completed') + self.assertEqual('dest', self.get_host(self.server['id'])) + + conn = self.dest.driver._host.get_connection() + dom = conn.lookupByUUIDString(self.server['id']) + xml = dom.XMLDesc(0) + # The destination should be updated to "0-1". + self.assertIn('1', xml) + self.assertNotIn('1', xml) diff --git a/nova/tests/unit/fixtures/test_libvirt.py b/nova/tests/unit/fixtures/test_libvirt.py index d5be81af43cd..905a12fda83a 100644 --- a/nova/tests/unit/fixtures/test_libvirt.py +++ b/nova/tests/unit/fixtures/test_libvirt.py @@ -172,7 +172,7 @@ class FakeLibvirtTests(test.NoDBTestCase): self.assertEqual(info[0], libvirt.VIR_DOMAIN_RUNNING) self.assertEqual(info[1], 128000) self.assertLessEqual(info[2], 128000) - self.assertEqual(info[3], 1) + self.assertEqual(info[3]['number'], 1) self.assertIs(type(info[4]), int) def test_createXML_runs_domain(self):