summaryrefslogtreecommitdiff
path: root/nova/tests/functional/libvirt/test_power_manage.py
blob: fb1ac7d0cde1e79a094f4c65f1845283b809efa2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

from unittest import mock

import fixtures

from nova import context as nova_context
from nova import exception
from nova import objects
from nova.tests import fixtures as nova_fixtures
from nova.tests.fixtures import libvirt as fakelibvirt
from nova.tests.functional.libvirt import base
from nova.virt import hardware
from nova.virt.libvirt import cpu


class PowerManagementTestsBase(base.ServersTestBase):

    ADDITIONAL_FILTERS = ['NUMATopologyFilter']

    ADMIN_API = True

    def setUp(self):
        super(PowerManagementTestsBase, self).setUp()

        self.ctxt = nova_context.get_admin_context()

        # Mock the 'NUMATopologyFilter' filter, as most tests need to inspect
        # this
        host_manager = self.scheduler.manager.host_manager
        numa_filter_class = host_manager.filter_cls_map['NUMATopologyFilter']
        host_pass_mock = mock.Mock(wraps=numa_filter_class().host_passes)
        _p = mock.patch('nova.scheduler.filters'
                        '.numa_topology_filter.NUMATopologyFilter.host_passes',
                        side_effect=host_pass_mock)
        self.mock_filter = _p.start()
        self.addCleanup(_p.stop)

        # for the sake of resizing, we need to patch the two methods below
        self.useFixture(fixtures.MockPatch(
            'nova.virt.libvirt.LibvirtDriver._get_instance_disk_info',
             return_value=[]))
        self.useFixture(fixtures.MockPatch('os.rename'))

        self.useFixture(nova_fixtures.PrivsepFixture())

        # Defining the main flavor for 4 vCPUs all pinned
        self.extra_spec = {
            'hw:cpu_policy': 'dedicated',
            'hw:cpu_thread_policy': 'prefer',
        }
        self.pcpu_flavor_id = self._create_flavor(
            vcpu=4, extra_spec=self.extra_spec)

    def _assert_server_cpus_state(self, server, expected='online'):
        inst = objects.Instance.get_by_uuid(self.ctxt, server['id'])
        if not inst.numa_topology:
            self.fail('Instance should have a NUMA topology in order to know '
                      'its physical CPUs')
        instance_pcpus = inst.numa_topology.cpu_pinning
        self._assert_cpu_set_state(instance_pcpus, expected=expected)
        return instance_pcpus

    def _assert_cpu_set_state(self, cpu_set, expected='online'):
        for i in cpu_set:
            core = cpu.Core(i)
            if expected == 'online':
                self.assertTrue(core.online, f'{i} is not online')
            elif expected == 'offline':
                self.assertFalse(core.online, f'{i} is online')
            elif expected == 'powersave':
                self.assertEqual('powersave', core.governor)
            elif expected == 'performance':
                self.assertEqual('performance', core.governor)


class PowerManagementTests(PowerManagementTestsBase):
    """Test suite for a single host with 9 dedicated cores and 1 used for OS"""

    def setUp(self):
        super(PowerManagementTests, self).setUp()

        self.useFixture(nova_fixtures.SysFileSystemFixture())

        # Definining the CPUs to be pinned.
        self.flags(cpu_dedicated_set='1-9', cpu_shared_set=None,
                   group='compute')
        self.flags(vcpu_pin_set=None)
        self.flags(cpu_power_management=True, group='libvirt')

        self.flags(allow_resize_to_same_host=True)
        self.host_info = fakelibvirt.HostInfo(cpu_nodes=1, cpu_sockets=1,
                                         cpu_cores=5, cpu_threads=2)
        self.compute1 = self.start_compute(host_info=self.host_info,
                                           hostname='compute1')

        # All cores are shutdown at startup, let's check.
        cpu_dedicated_set = hardware.get_cpu_dedicated_set()
        self._assert_cpu_set_state(cpu_dedicated_set, expected='offline')

    def test_hardstop_compute_service_if_wrong_opt(self):
        self.flags(cpu_dedicated_set=None, cpu_shared_set=None,
                   group='compute')
        self.flags(vcpu_pin_set=None)
        self.flags(cpu_power_management=True, group='libvirt')
        self.assertRaises(exception.InvalidConfiguration,
                          self.start_compute, host_info=self.host_info,
                                              hostname='compute2')

    def test_create_server(self):
        server = self._create_server(
            flavor_id=self.pcpu_flavor_id,
            expected_state='ACTIVE')
        # Let's verify that the pinned CPUs are now online
        self._assert_server_cpus_state(server, expected='online')

        # Verify that the unused CPUs are still offline
        inst = objects.Instance.get_by_uuid(self.ctxt, server['id'])
        instance_pcpus = inst.numa_topology.cpu_pinning
        cpu_dedicated_set = hardware.get_cpu_dedicated_set()
        unused_cpus = cpu_dedicated_set - instance_pcpus
        self._assert_cpu_set_state(unused_cpus, expected='offline')

    def test_stop_start_server(self):
        server = self._create_server(
            flavor_id=self.pcpu_flavor_id,
            expected_state='ACTIVE')

        server = self._stop_server(server)
        # Let's verify that the pinned CPUs are now stopped...
        self._assert_server_cpus_state(server, expected='offline')

        server = self._start_server(server)
        # ...and now, they should be back.
        self._assert_server_cpus_state(server, expected='online')

    def test_resize(self):
        server = self._create_server(
            flavor_id=self.pcpu_flavor_id,
            expected_state='ACTIVE')
        server_pcpus = self._assert_server_cpus_state(server,
                                                      expected='online')

        new_flavor_id = self._create_flavor(
            vcpu=5, extra_spec=self.extra_spec)
        self._resize_server(server, new_flavor_id)
        server2_pcpus = self._assert_server_cpus_state(server,
                                                       expected='online')
        # Even if the resize is not confirmed yet, the original guest is now
        # destroyed so the cores are now offline.
        self._assert_cpu_set_state(server_pcpus, expected='offline')

        # let's revert the resize
        self._revert_resize(server)
        # So now the original CPUs will be online again, while the previous
        # cores should be back offline.
        self._assert_cpu_set_state(server_pcpus, expected='online')
        self._assert_cpu_set_state(server2_pcpus, expected='offline')

    def test_changing_strategy_fails(self):
        # As a reminder, all cores have been shutdown before.
        # Now we want to change the strategy and then we restart the service
        self.flags(cpu_power_management_strategy='governor', group='libvirt')
        # See, this is not possible as we would have offline CPUs.
        self.assertRaises(exception.InvalidConfiguration,
                          self.restart_compute_service, hostname='compute1')


class PowerManagementTestsGovernor(PowerManagementTestsBase):
    """Test suite for speific governor usage (same 10-core host)"""

    def setUp(self):
        super(PowerManagementTestsGovernor, self).setUp()

        self.useFixture(nova_fixtures.SysFileSystemFixture())

        # Definining the CPUs to be pinned.
        self.flags(cpu_dedicated_set='1-9', cpu_shared_set=None,
                   group='compute')
        self.flags(vcpu_pin_set=None)
        self.flags(cpu_power_management=True, group='libvirt')
        self.flags(cpu_power_management_strategy='governor', group='libvirt')

        self.flags(allow_resize_to_same_host=True)
        self.host_info = fakelibvirt.HostInfo(cpu_nodes=1, cpu_sockets=1,
                                         cpu_cores=5, cpu_threads=2)
        self.compute1 = self.start_compute(host_info=self.host_info,
                                           hostname='compute1')

    def test_create(self):
        cpu_dedicated_set = hardware.get_cpu_dedicated_set()
        # With the governor strategy, cores are still online but run with a
        # powersave governor.
        self._assert_cpu_set_state(cpu_dedicated_set, expected='powersave')

        # Now, start an instance
        server = self._create_server(
            flavor_id=self.pcpu_flavor_id,
            expected_state='ACTIVE')
        # When pinned cores are run, the governor state is now performance
        self._assert_server_cpus_state(server, expected='performance')

    def test_changing_strategy_fails(self):
        # Arbitratly set a core governor strategy to be performance
        cpu.Core(1).set_high_governor()
        # and then forget about it while changing the strategy.
        self.flags(cpu_power_management_strategy='cpu_state', group='libvirt')
        # This time, this wouldn't be acceptable as some core would have a
        # difference performance while Nova would only online/offline it.
        self.assertRaises(exception.InvalidConfiguration,
                          self.restart_compute_service, hostname='compute1')


class PowerManagementMixedInstances(PowerManagementTestsBase):
    """Test suite for a single host with 6 dedicated cores, 3 shared and one
    OS-restricted.
    """

    def setUp(self):
        super(PowerManagementMixedInstances, self).setUp()

        self.useFixture(nova_fixtures.SysFileSystemFixture())

        # Definining 6 CPUs to be dedicated, not all of them in a series.
        self.flags(cpu_dedicated_set='1-3,5-7', cpu_shared_set='4,8-9',
                   group='compute')
        self.flags(vcpu_pin_set=None)
        self.flags(cpu_power_management=True, group='libvirt')

        self.host_info = fakelibvirt.HostInfo(cpu_nodes=1, cpu_sockets=1,
                                         cpu_cores=5, cpu_threads=2)
        self.compute1 = self.start_compute(host_info=self.host_info,
                                           hostname='compute1')

        # Make sure only 6 are offline now
        cpu_dedicated_set = hardware.get_cpu_dedicated_set()
        self._assert_cpu_set_state(cpu_dedicated_set, expected='offline')

        # cores 4 and 8-9 should be online
        self._assert_cpu_set_state({4, 8, 9}, expected='online')

    def test_standard_server_works_and_passes(self):

        std_flavor_id = self._create_flavor(vcpu=2)
        self._create_server(flavor_id=std_flavor_id, expected_state='ACTIVE')

        # Since this is an instance with floating vCPUs on the shared set, we
        # can only lookup the host CPUs and see they haven't changed state.
        cpu_dedicated_set = hardware.get_cpu_dedicated_set()
        self._assert_cpu_set_state(cpu_dedicated_set, expected='offline')
        self._assert_cpu_set_state({4, 8, 9}, expected='online')

        # We can now try to boot an instance with pinned CPUs to test the mix
        pinned_server = self._create_server(
            flavor_id=self.pcpu_flavor_id,
            expected_state='ACTIVE')
        # We'll see that its CPUs are now online
        self._assert_server_cpus_state(pinned_server, expected='online')
        # but it doesn't change the shared set
        self._assert_cpu_set_state({4, 8, 9}, expected='online')