|
| 1 | +# Licensed under the Apache License, Version 2.0 (the "License"); you may |
| 2 | +# not use this file except in compliance with the License. You may obtain |
| 3 | +# a copy of the License at |
| 4 | +# |
| 5 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 6 | +# |
| 7 | +# Unless required by applicable law or agreed to in writing, software |
| 8 | +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| 9 | +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| 10 | +# License for the specific language governing permissions and limitations |
| 11 | +# under the License. |
| 12 | + |
| 13 | +from unittest import mock |
| 14 | + |
| 15 | +import fixtures |
| 16 | + |
| 17 | +from nova import context as nova_context |
| 18 | +from nova import exception |
| 19 | +from nova import objects |
| 20 | +from nova.tests import fixtures as nova_fixtures |
| 21 | +from nova.tests.fixtures import libvirt as fakelibvirt |
| 22 | +from nova.tests.functional.libvirt import base |
| 23 | +from nova.virt import hardware |
| 24 | +from nova.virt.libvirt import cpu |
| 25 | + |
| 26 | + |
| 27 | +class PowerManagementTestsBase(base.ServersTestBase): |
| 28 | + |
| 29 | + ADDITIONAL_FILTERS = ['NUMATopologyFilter'] |
| 30 | + |
| 31 | + ADMIN_API = True |
| 32 | + |
| 33 | + def setUp(self): |
| 34 | + super(PowerManagementTestsBase, self).setUp() |
| 35 | + |
| 36 | + self.ctxt = nova_context.get_admin_context() |
| 37 | + |
| 38 | + # Mock the 'NUMATopologyFilter' filter, as most tests need to inspect |
| 39 | + # this |
| 40 | + host_manager = self.scheduler.manager.host_manager |
| 41 | + numa_filter_class = host_manager.filter_cls_map['NUMATopologyFilter'] |
| 42 | + host_pass_mock = mock.Mock(wraps=numa_filter_class().host_passes) |
| 43 | + _p = mock.patch('nova.scheduler.filters' |
| 44 | + '.numa_topology_filter.NUMATopologyFilter.host_passes', |
| 45 | + side_effect=host_pass_mock) |
| 46 | + self.mock_filter = _p.start() |
| 47 | + self.addCleanup(_p.stop) |
| 48 | + |
| 49 | + # for the sake of resizing, we need to patch the two methods below |
| 50 | + self.useFixture(fixtures.MockPatch( |
| 51 | + 'nova.virt.libvirt.LibvirtDriver._get_instance_disk_info', |
| 52 | + return_value=[])) |
| 53 | + self.useFixture(fixtures.MockPatch('os.rename')) |
| 54 | + |
| 55 | + self.useFixture(nova_fixtures.PrivsepFixture()) |
| 56 | + |
| 57 | + # Defining the main flavor for 4 vCPUs all pinned |
| 58 | + self.extra_spec = { |
| 59 | + 'hw:cpu_policy': 'dedicated', |
| 60 | + 'hw:cpu_thread_policy': 'prefer', |
| 61 | + } |
| 62 | + self.pcpu_flavor_id = self._create_flavor( |
| 63 | + vcpu=4, extra_spec=self.extra_spec) |
| 64 | + |
| 65 | + def _assert_server_cpus_state(self, server, expected='online'): |
| 66 | + inst = objects.Instance.get_by_uuid(self.ctxt, server['id']) |
| 67 | + if not inst.numa_topology: |
| 68 | + self.fail('Instance should have a NUMA topology in order to know ' |
| 69 | + 'its physical CPUs') |
| 70 | + instance_pcpus = inst.numa_topology.cpu_pinning |
| 71 | + self._assert_cpu_set_state(instance_pcpus, expected=expected) |
| 72 | + return instance_pcpus |
| 73 | + |
| 74 | + def _assert_cpu_set_state(self, cpu_set, expected='online'): |
| 75 | + for i in cpu_set: |
| 76 | + core = cpu.Core(i) |
| 77 | + if expected == 'online': |
| 78 | + self.assertTrue(core.online, f'{i} is not online') |
| 79 | + elif expected == 'offline': |
| 80 | + self.assertFalse(core.online, f'{i} is online') |
| 81 | + elif expected == 'powersave': |
| 82 | + self.assertEqual('powersave', core.governor) |
| 83 | + elif expected == 'performance': |
| 84 | + self.assertEqual('performance', core.governor) |
| 85 | + |
| 86 | + |
| 87 | +class PowerManagementTests(PowerManagementTestsBase): |
| 88 | + """Test suite for a single host with 9 dedicated cores and 1 used for OS""" |
| 89 | + |
| 90 | + def setUp(self): |
| 91 | + super(PowerManagementTests, self).setUp() |
| 92 | + |
| 93 | + self.useFixture(nova_fixtures.SysFileSystemFixture()) |
| 94 | + |
| 95 | + # Definining the CPUs to be pinned. |
| 96 | + self.flags(cpu_dedicated_set='1-9', cpu_shared_set=None, |
| 97 | + group='compute') |
| 98 | + self.flags(vcpu_pin_set=None) |
| 99 | + self.flags(cpu_power_management=True, group='libvirt') |
| 100 | + |
| 101 | + self.flags(allow_resize_to_same_host=True) |
| 102 | + self.host_info = fakelibvirt.HostInfo(cpu_nodes=1, cpu_sockets=1, |
| 103 | + cpu_cores=5, cpu_threads=2) |
| 104 | + self.compute1 = self.start_compute(host_info=self.host_info, |
| 105 | + hostname='compute1') |
| 106 | + |
| 107 | + # All cores are shutdown at startup, let's check. |
| 108 | + cpu_dedicated_set = hardware.get_cpu_dedicated_set() |
| 109 | + self._assert_cpu_set_state(cpu_dedicated_set, expected='offline') |
| 110 | + |
| 111 | + def test_hardstop_compute_service_if_wrong_opt(self): |
| 112 | + self.flags(cpu_dedicated_set=None, cpu_shared_set=None, |
| 113 | + group='compute') |
| 114 | + self.flags(vcpu_pin_set=None) |
| 115 | + self.flags(cpu_power_management=True, group='libvirt') |
| 116 | + self.assertRaises(exception.InvalidConfiguration, |
| 117 | + self.start_compute, host_info=self.host_info, |
| 118 | + hostname='compute2') |
| 119 | + |
| 120 | + def test_create_server(self): |
| 121 | + server = self._create_server( |
| 122 | + flavor_id=self.pcpu_flavor_id, |
| 123 | + expected_state='ACTIVE') |
| 124 | + # Let's verify that the pinned CPUs are now online |
| 125 | + self._assert_server_cpus_state(server, expected='online') |
| 126 | + |
| 127 | + # Verify that the unused CPUs are still offline |
| 128 | + inst = objects.Instance.get_by_uuid(self.ctxt, server['id']) |
| 129 | + instance_pcpus = inst.numa_topology.cpu_pinning |
| 130 | + cpu_dedicated_set = hardware.get_cpu_dedicated_set() |
| 131 | + unused_cpus = cpu_dedicated_set - instance_pcpus |
| 132 | + self._assert_cpu_set_state(unused_cpus, expected='offline') |
| 133 | + |
| 134 | + def test_stop_start_server(self): |
| 135 | + server = self._create_server( |
| 136 | + flavor_id=self.pcpu_flavor_id, |
| 137 | + expected_state='ACTIVE') |
| 138 | + |
| 139 | + server = self._stop_server(server) |
| 140 | + # Let's verify that the pinned CPUs are now stopped... |
| 141 | + self._assert_server_cpus_state(server, expected='offline') |
| 142 | + |
| 143 | + server = self._start_server(server) |
| 144 | + # ...and now, they should be back. |
| 145 | + self._assert_server_cpus_state(server, expected='online') |
| 146 | + |
| 147 | + def test_resize(self): |
| 148 | + server = self._create_server( |
| 149 | + flavor_id=self.pcpu_flavor_id, |
| 150 | + expected_state='ACTIVE') |
| 151 | + server_pcpus = self._assert_server_cpus_state(server, |
| 152 | + expected='online') |
| 153 | + |
| 154 | + new_flavor_id = self._create_flavor( |
| 155 | + vcpu=5, extra_spec=self.extra_spec) |
| 156 | + self._resize_server(server, new_flavor_id) |
| 157 | + server2_pcpus = self._assert_server_cpus_state(server, |
| 158 | + expected='online') |
| 159 | + # Even if the resize is not confirmed yet, the original guest is now |
| 160 | + # destroyed so the cores are now offline. |
| 161 | + self._assert_cpu_set_state(server_pcpus, expected='offline') |
| 162 | + |
| 163 | + # let's revert the resize |
| 164 | + self._revert_resize(server) |
| 165 | + # So now the original CPUs will be online again, while the previous |
| 166 | + # cores should be back offline. |
| 167 | + self._assert_cpu_set_state(server_pcpus, expected='online') |
| 168 | + self._assert_cpu_set_state(server2_pcpus, expected='offline') |
| 169 | + |
| 170 | + def test_changing_strategy_fails(self): |
| 171 | + # As a reminder, all cores have been shutdown before. |
| 172 | + # Now we want to change the strategy and then we restart the service |
| 173 | + self.flags(cpu_power_management_strategy='governor', group='libvirt') |
| 174 | + # See, this is not possible as we would have offline CPUs. |
| 175 | + self.assertRaises(exception.InvalidConfiguration, |
| 176 | + self.restart_compute_service, hostname='compute1') |
| 177 | + |
| 178 | + |
| 179 | +class PowerManagementTestsGovernor(PowerManagementTestsBase): |
| 180 | + """Test suite for speific governor usage (same 10-core host)""" |
| 181 | + |
| 182 | + def setUp(self): |
| 183 | + super(PowerManagementTestsGovernor, self).setUp() |
| 184 | + |
| 185 | + self.useFixture(nova_fixtures.SysFileSystemFixture()) |
| 186 | + |
| 187 | + # Definining the CPUs to be pinned. |
| 188 | + self.flags(cpu_dedicated_set='1-9', cpu_shared_set=None, |
| 189 | + group='compute') |
| 190 | + self.flags(vcpu_pin_set=None) |
| 191 | + self.flags(cpu_power_management=True, group='libvirt') |
| 192 | + self.flags(cpu_power_management_strategy='governor', group='libvirt') |
| 193 | + |
| 194 | + self.flags(allow_resize_to_same_host=True) |
| 195 | + self.host_info = fakelibvirt.HostInfo(cpu_nodes=1, cpu_sockets=1, |
| 196 | + cpu_cores=5, cpu_threads=2) |
| 197 | + self.compute1 = self.start_compute(host_info=self.host_info, |
| 198 | + hostname='compute1') |
| 199 | + |
| 200 | + def test_create(self): |
| 201 | + cpu_dedicated_set = hardware.get_cpu_dedicated_set() |
| 202 | + # With the governor strategy, cores are still online but run with a |
| 203 | + # powersave governor. |
| 204 | + self._assert_cpu_set_state(cpu_dedicated_set, expected='powersave') |
| 205 | + |
| 206 | + # Now, start an instance |
| 207 | + server = self._create_server( |
| 208 | + flavor_id=self.pcpu_flavor_id, |
| 209 | + expected_state='ACTIVE') |
| 210 | + # When pinned cores are run, the governor state is now performance |
| 211 | + self._assert_server_cpus_state(server, expected='performance') |
| 212 | + |
| 213 | + def test_changing_strategy_fails(self): |
| 214 | + # Arbitratly set a core governor strategy to be performance |
| 215 | + cpu.Core(1).set_high_governor() |
| 216 | + # and then forget about it while changing the strategy. |
| 217 | + self.flags(cpu_power_management_strategy='cpu_state', group='libvirt') |
| 218 | + # This time, this wouldn't be acceptable as some core would have a |
| 219 | + # difference performance while Nova would only online/offline it. |
| 220 | + self.assertRaises(exception.InvalidConfiguration, |
| 221 | + self.restart_compute_service, hostname='compute1') |
| 222 | + |
| 223 | + |
| 224 | +class PowerManagementMixedInstances(PowerManagementTestsBase): |
| 225 | + """Test suite for a single host with 6 dedicated cores, 3 shared and one |
| 226 | + OS-restricted. |
| 227 | + """ |
| 228 | + |
| 229 | + def setUp(self): |
| 230 | + super(PowerManagementMixedInstances, self).setUp() |
| 231 | + |
| 232 | + self.useFixture(nova_fixtures.SysFileSystemFixture()) |
| 233 | + |
| 234 | + # Definining 6 CPUs to be dedicated, not all of them in a series. |
| 235 | + self.flags(cpu_dedicated_set='1-3,5-7', cpu_shared_set='4,8-9', |
| 236 | + group='compute') |
| 237 | + self.flags(vcpu_pin_set=None) |
| 238 | + self.flags(cpu_power_management=True, group='libvirt') |
| 239 | + |
| 240 | + self.host_info = fakelibvirt.HostInfo(cpu_nodes=1, cpu_sockets=1, |
| 241 | + cpu_cores=5, cpu_threads=2) |
| 242 | + self.compute1 = self.start_compute(host_info=self.host_info, |
| 243 | + hostname='compute1') |
| 244 | + |
| 245 | + # Make sure only 6 are offline now |
| 246 | + cpu_dedicated_set = hardware.get_cpu_dedicated_set() |
| 247 | + self._assert_cpu_set_state(cpu_dedicated_set, expected='offline') |
| 248 | + |
| 249 | + # cores 4 and 8-9 should be online |
| 250 | + self._assert_cpu_set_state({4, 8, 9}, expected='online') |
| 251 | + |
| 252 | + def test_standard_server_works_and_passes(self): |
| 253 | + |
| 254 | + std_flavor_id = self._create_flavor(vcpu=2) |
| 255 | + self._create_server(flavor_id=std_flavor_id, expected_state='ACTIVE') |
| 256 | + |
| 257 | + # Since this is an instance with floating vCPUs on the shared set, we |
| 258 | + # can only lookup the host CPUs and see they haven't changed state. |
| 259 | + cpu_dedicated_set = hardware.get_cpu_dedicated_set() |
| 260 | + self._assert_cpu_set_state(cpu_dedicated_set, expected='offline') |
| 261 | + self._assert_cpu_set_state({4, 8, 9}, expected='online') |
| 262 | + |
| 263 | + # We can now try to boot an instance with pinned CPUs to test the mix |
| 264 | + pinned_server = self._create_server( |
| 265 | + flavor_id=self.pcpu_flavor_id, |
| 266 | + expected_state='ACTIVE') |
| 267 | + # We'll see that its CPUs are now online |
| 268 | + self._assert_server_cpus_state(pinned_server, expected='online') |
| 269 | + # but it doesn't change the shared set |
| 270 | + self._assert_cpu_set_state({4, 8, 9}, expected='online') |
0 commit comments