Skip to content

Commit 874acc1

Browse files
committed
pwr mgmt: make API into a per-driver object
We want to test power management in our functional tests in multinode scenarios (ex: live migration). This was previously impossible because all the methods in nova.virt.libvirt.cpu.api and were at the module level, meaning both source and destination libvirt drivers would call the same method to online and offline cores. This made it impossible to maintain distinct core power state between source and destination. This patch inserts a nova.virt.libvirt.cpu.api.API class, and gives the libvirt driver a cpu_api attribute with an instance of that class. Along with the tiny API.core() helper, this allows new functional tests in the subsequent patches to stub out the core "model" code with distinct objects on the source and destination libvirt drivers, and enables a whole bunch of testing (and fixes!) around live migration. Related-bug: 2056613 Change-Id: I052535249b9a3e144bb68b8c588b5995eb345b97 (cherry picked from commit 29dc044) (cherry picked from commit 2a0e638)
1 parent 0d0dff8 commit 874acc1

File tree

3 files changed

+126
-102
lines changed

3 files changed

+126
-102
lines changed

nova/tests/unit/virt/libvirt/cpu/test_api.py

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class TestAPI(test.NoDBTestCase):
2424
def setUp(self):
2525
super(TestAPI, self).setUp()
2626
self.core_1 = api.Core(1)
27+
self.api = api.API()
2728

2829
# Create a fake instance with two pinned CPUs but only one is on the
2930
# dedicated set
@@ -82,7 +83,7 @@ def test_power_up_online(self, mock_online):
8283
self.flags(cpu_power_management=True, group='libvirt')
8384
self.flags(cpu_dedicated_set='1-2', group='compute')
8485

85-
api.power_up(self.fake_inst)
86+
self.api.power_up(self.fake_inst)
8687
# only core #2 can be set as core #0 is not on the dedicated set
8788
# As a reminder, core(i).online calls set_online(i)
8889
mock_online.assert_called_once_with(2)
@@ -93,29 +94,29 @@ def test_power_up_governor(self, mock_set_governor):
9394
self.flags(cpu_power_management_strategy='governor', group='libvirt')
9495
self.flags(cpu_dedicated_set='1-2', group='compute')
9596

96-
api.power_up(self.fake_inst)
97+
self.api.power_up(self.fake_inst)
9798
# only core #2 can be set as core #1 is not on the dedicated set
9899
# As a reminder, core(i).set_high_governor calls set_governor(i)
99100
mock_set_governor.assert_called_once_with(2, 'performance')
100101

101102
@mock.patch.object(core, 'set_online')
102103
def test_power_up_skipped(self, mock_online):
103104
self.flags(cpu_power_management=False, group='libvirt')
104-
api.power_up(self.fake_inst)
105+
self.api.power_up(self.fake_inst)
105106
mock_online.assert_not_called()
106107

107108
@mock.patch.object(core, 'set_online')
108109
def test_power_up_skipped_if_standard_instance(self, mock_online):
109110
self.flags(cpu_power_management=True, group='libvirt')
110-
api.power_up(objects.Instance(numa_topology=None))
111+
self.api.power_up(objects.Instance(numa_topology=None))
111112
mock_online.assert_not_called()
112113

113114
@mock.patch.object(core, 'set_offline')
114115
def test_power_down_offline(self, mock_offline):
115116
self.flags(cpu_power_management=True, group='libvirt')
116117
self.flags(cpu_dedicated_set='1-2', group='compute')
117118

118-
api.power_down(self.fake_inst)
119+
self.api.power_down(self.fake_inst)
119120
# only core #2 can be set as core #1 is not on the dedicated set
120121
# As a reminder, core(i).online calls set_online(i)
121122
mock_offline.assert_called_once_with(2)
@@ -126,7 +127,7 @@ def test_power_down_governor_cpu0_ignored(self, mock_set_governor):
126127
self.flags(cpu_power_management_strategy='governor', group='libvirt')
127128
self.flags(cpu_dedicated_set='0-1', group='compute')
128129

129-
api.power_down(self.fake_inst)
130+
self.api.power_down(self.fake_inst)
130131

131132
# Make sure that core #0 is ignored, since it is special and cannot
132133
# be powered down.
@@ -138,7 +139,7 @@ def test_power_down_governor(self, mock_set_governor):
138139
self.flags(cpu_power_management_strategy='governor', group='libvirt')
139140
self.flags(cpu_dedicated_set='1-2', group='compute')
140141

141-
api.power_down(self.fake_inst)
142+
self.api.power_down(self.fake_inst)
142143

143144
# only core #2 can be set as core #0 is not on the dedicated set
144145
# As a reminder, core(i).set_high_governor calls set_governor(i)
@@ -147,21 +148,21 @@ def test_power_down_governor(self, mock_set_governor):
147148
@mock.patch.object(core, 'set_offline')
148149
def test_power_down_skipped(self, mock_offline):
149150
self.flags(cpu_power_management=False, group='libvirt')
150-
api.power_down(self.fake_inst)
151+
self.api.power_down(self.fake_inst)
151152
mock_offline.assert_not_called()
152153

153154
@mock.patch.object(core, 'set_offline')
154155
def test_power_down_skipped_if_standard_instance(self, mock_offline):
155156
self.flags(cpu_power_management=True, group='libvirt')
156-
api.power_down(objects.Instance(numa_topology=None))
157+
self.api.power_down(objects.Instance(numa_topology=None))
157158
mock_offline.assert_not_called()
158159

159160
@mock.patch.object(core, 'set_offline')
160161
def test_power_down_all_dedicated_cpus_offline(self, mock_offline):
161162
self.flags(cpu_power_management=True, group='libvirt')
162163
self.flags(cpu_dedicated_set='0-2', group='compute')
163164

164-
api.power_down_all_dedicated_cpus()
165+
self.api.power_down_all_dedicated_cpus()
165166
# All dedicated CPUs are turned offline, except CPU0
166167
mock_offline.assert_has_calls([mock.call(1), mock.call(2)])
167168

@@ -171,15 +172,15 @@ def test_power_down_all_dedicated_cpus_governor(self, mock_set_governor):
171172
self.flags(cpu_power_management_strategy='governor', group='libvirt')
172173
self.flags(cpu_dedicated_set='0-2', group='compute')
173174

174-
api.power_down_all_dedicated_cpus()
175+
self.api.power_down_all_dedicated_cpus()
175176
# All dedicated CPUs are turned offline, except CPU0
176177
mock_set_governor.assert_has_calls([mock.call(1, 'powersave'),
177178
mock.call(2, 'powersave')])
178179

179180
@mock.patch.object(core, 'set_offline')
180181
def test_power_down_all_dedicated_cpus_skipped(self, mock_offline):
181182
self.flags(cpu_power_management=False, group='libvirt')
182-
api.power_down_all_dedicated_cpus()
183+
self.api.power_down_all_dedicated_cpus()
183184
mock_offline.assert_not_called()
184185

185186
@mock.patch.object(core, 'set_offline')
@@ -188,7 +189,7 @@ def test_power_down_all_dedicated_cpus_no_dedicated_cpus_configured(
188189
):
189190
self.flags(cpu_power_management=True, group='libvirt')
190191
self.flags(cpu_dedicated_set=None, group='compute')
191-
api.power_down_all_dedicated_cpus()
192+
self.api.power_down_all_dedicated_cpus()
192193
mock_offline.assert_not_called()
193194

194195
@mock.patch.object(core, 'get_governor')
@@ -201,7 +202,7 @@ def test_validate_all_dedicated_cpus_for_governor(self, mock_get_online,
201202
mock_get_governor.return_value = 'performance'
202203
mock_get_online.side_effect = (True, False)
203204
self.assertRaises(exception.InvalidConfiguration,
204-
api.validate_all_dedicated_cpus)
205+
self.api.validate_all_dedicated_cpus)
205206

206207
@mock.patch.object(core, 'get_governor')
207208
@mock.patch.object(core, 'get_online')
@@ -213,7 +214,7 @@ def test_validate_all_dedicated_cpus_for_cpu_state(self, mock_get_online,
213214
mock_get_online.return_value = True
214215
mock_get_governor.side_effect = ('powersave', 'performance')
215216
self.assertRaises(exception.InvalidConfiguration,
216-
api.validate_all_dedicated_cpus)
217+
self.api.validate_all_dedicated_cpus)
217218

218219
@mock.patch.object(core, 'get_governor')
219220
@mock.patch.object(core, 'get_online')
@@ -227,7 +228,7 @@ def test_validate_all_dedicated_cpus_for_cpu_state_warning(
227228
mock_get_online.return_value = True
228229
mock_get_governor.return_value = 'performance'
229230

230-
api.validate_all_dedicated_cpus()
231+
self.api.validate_all_dedicated_cpus()
231232

232233
# Make sure we skipped CPU0
233234
mock_get_online.assert_has_calls([mock.call(1), mock.call(2)])
@@ -240,6 +241,6 @@ def test_validate_all_dedicated_cpus_for_cpu_state_warning(
240241
def test_validate_all_dedicated_cpus_no_cpu(self):
241242
self.flags(cpu_power_management=True, group='libvirt')
242243
self.flags(cpu_dedicated_set=None, group='compute')
243-
api.validate_all_dedicated_cpus()
244+
self.api.validate_all_dedicated_cpus()
244245
# no assert we want to make sure the validation won't raise if
245246
# no dedicated cpus are configured

nova/virt/libvirt/cpu/api.py

Lines changed: 94 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -75,88 +75,102 @@ def set_low_governor(self) -> None:
7575
core.set_governor(self.ident, CONF.libvirt.cpu_power_governor_low)
7676

7777

78-
def power_up(instance: objects.Instance) -> None:
79-
if not CONF.libvirt.cpu_power_management:
80-
return
81-
if instance.numa_topology is None:
82-
return
83-
84-
cpu_dedicated_set = hardware.get_cpu_dedicated_set_nozero() or set()
85-
pcpus = instance.numa_topology.cpu_pinning.union(
86-
instance.numa_topology.cpuset_reserved)
87-
powered_up = set()
88-
for pcpu in pcpus:
89-
if pcpu in cpu_dedicated_set:
90-
pcpu = Core(pcpu)
91-
if CONF.libvirt.cpu_power_management_strategy == 'cpu_state':
92-
pcpu.online = True
93-
else:
94-
pcpu.set_high_governor()
95-
powered_up.add(str(pcpu))
96-
LOG.debug("Cores powered up : %s", powered_up)
97-
98-
99-
def power_down(instance: objects.Instance) -> None:
100-
if not CONF.libvirt.cpu_power_management:
101-
return
102-
if instance.numa_topology is None:
103-
return
104-
105-
cpu_dedicated_set = hardware.get_cpu_dedicated_set_nozero() or set()
106-
pcpus = instance.numa_topology.cpu_pinning.union(
107-
instance.numa_topology.cpuset_reserved)
108-
powered_down = set()
109-
for pcpu in pcpus:
110-
if pcpu in cpu_dedicated_set:
111-
pcpu = Core(pcpu)
78+
class API(object):
79+
80+
def core(self, i):
81+
"""From a purely functional point of view, there is no need for this
82+
method. However, we want to test power management in multinode
83+
scenarios (ex: live migration) in our functional tests. If we
84+
instantiated the Core class directly in the methods below, the
85+
functional tests would not be able to distinguish between cores on the
86+
source and destination hosts. In functional tests we can replace this
87+
helper method by a stub that returns a fixture, allowing us to maintain
88+
distinct core power state for each host.
89+
90+
See also nova.virt.libvirt.driver.LibvirtDriver.cpu_api.
91+
"""
92+
return Core(i)
93+
94+
def power_up(self, instance: objects.Instance) -> None:
95+
if not CONF.libvirt.cpu_power_management:
96+
return
97+
if instance.numa_topology is None:
98+
return
99+
100+
cpu_dedicated_set = hardware.get_cpu_dedicated_set_nozero() or set()
101+
pcpus = instance.numa_topology.cpu_pinning.union(
102+
instance.numa_topology.cpuset_reserved)
103+
powered_up = set()
104+
for pcpu in pcpus:
105+
if pcpu in cpu_dedicated_set:
106+
pcpu = self.core(pcpu)
107+
if CONF.libvirt.cpu_power_management_strategy == 'cpu_state':
108+
pcpu.online = True
109+
else:
110+
pcpu.set_high_governor()
111+
powered_up.add(str(pcpu))
112+
LOG.debug("Cores powered up : %s", powered_up)
113+
114+
def power_down(self, instance: objects.Instance) -> None:
115+
if not CONF.libvirt.cpu_power_management:
116+
return
117+
if instance.numa_topology is None:
118+
return
119+
120+
cpu_dedicated_set = hardware.get_cpu_dedicated_set_nozero() or set()
121+
pcpus = instance.numa_topology.cpu_pinning.union(
122+
instance.numa_topology.cpuset_reserved)
123+
powered_down = set()
124+
for pcpu in pcpus:
125+
if pcpu in cpu_dedicated_set:
126+
pcpu = self.core(pcpu)
127+
if CONF.libvirt.cpu_power_management_strategy == 'cpu_state':
128+
pcpu.online = False
129+
else:
130+
pcpu.set_low_governor()
131+
powered_down.add(str(pcpu))
132+
LOG.debug("Cores powered down : %s", powered_down)
133+
134+
def power_down_all_dedicated_cpus(self) -> None:
135+
if not CONF.libvirt.cpu_power_management:
136+
return
137+
138+
cpu_dedicated_set = hardware.get_cpu_dedicated_set_nozero() or set()
139+
for pcpu in cpu_dedicated_set:
140+
pcpu = self.core(pcpu)
112141
if CONF.libvirt.cpu_power_management_strategy == 'cpu_state':
113142
pcpu.online = False
114143
else:
115144
pcpu.set_low_governor()
116-
powered_down.add(str(pcpu))
117-
LOG.debug("Cores powered down : %s", powered_down)
118-
119-
120-
def power_down_all_dedicated_cpus() -> None:
121-
if not CONF.libvirt.cpu_power_management:
122-
return
123-
124-
cpu_dedicated_set = hardware.get_cpu_dedicated_set_nozero() or set()
125-
for pcpu in cpu_dedicated_set:
126-
pcpu = Core(pcpu)
145+
LOG.debug("Cores powered down : %s", cpu_dedicated_set)
146+
147+
def validate_all_dedicated_cpus(self) -> None:
148+
if not CONF.libvirt.cpu_power_management:
149+
return
150+
cpu_dedicated_set = hardware.get_cpu_dedicated_set() or set()
151+
governors = set()
152+
cpu_states = set()
153+
for pcpu in cpu_dedicated_set:
154+
if (pcpu == 0 and
155+
CONF.libvirt.cpu_power_management_strategy == 'cpu_state'):
156+
LOG.warning('CPU0 is in cpu_dedicated_set, '
157+
'but it is not eligible for state management '
158+
'and will be ignored')
159+
continue
160+
pcpu = self.core(pcpu)
161+
# we need to collect the governors strategy and the CPU states
162+
governors.add(pcpu.governor)
163+
cpu_states.add(pcpu.online)
127164
if CONF.libvirt.cpu_power_management_strategy == 'cpu_state':
128-
pcpu.online = False
129-
else:
130-
pcpu.set_low_governor()
131-
LOG.debug("Cores powered down : %s", cpu_dedicated_set)
132-
133-
134-
def validate_all_dedicated_cpus() -> None:
135-
if not CONF.libvirt.cpu_power_management:
136-
return
137-
cpu_dedicated_set = hardware.get_cpu_dedicated_set() or set()
138-
governors = set()
139-
cpu_states = set()
140-
for pcpu in cpu_dedicated_set:
141-
if (pcpu == 0 and
142-
CONF.libvirt.cpu_power_management_strategy == 'cpu_state'):
143-
LOG.warning('CPU0 is in cpu_dedicated_set, but it is not eligible '
144-
'for state management and will be ignored')
145-
continue
146-
pcpu = Core(pcpu)
147-
# we need to collect the governors strategy and the CPU states
148-
governors.add(pcpu.governor)
149-
cpu_states.add(pcpu.online)
150-
if CONF.libvirt.cpu_power_management_strategy == 'cpu_state':
151-
# all the cores need to have the same governor strategy
152-
if len(governors) > 1:
153-
msg = _("All the cores need to have the same governor strategy"
154-
"before modifying the CPU states. You can reboot the "
155-
"compute node if you prefer.")
156-
raise exception.InvalidConfiguration(msg)
157-
elif CONF.libvirt.cpu_power_management_strategy == 'governor':
158-
# all the cores need to be online
159-
if False in cpu_states:
160-
msg = _("All the cores need to be online before modifying the "
161-
"governor strategy.")
162-
raise exception.InvalidConfiguration(msg)
165+
# all the cores need to have the same governor strategy
166+
if len(governors) > 1:
167+
msg = _("All the cores need to have the same governor strategy"
168+
"before modifying the CPU states. You can reboot the "
169+
"compute node if you prefer.")
170+
raise exception.InvalidConfiguration(msg)
171+
elif CONF.libvirt.cpu_power_management_strategy == 'governor':
172+
# all the cores need to be online
173+
if False in cpu_states:
174+
msg = _("All the cores need to be online before modifying the "
175+
"governor strategy.")
176+
raise exception.InvalidConfiguration(msg)

nova/virt/libvirt/driver.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,15 @@ def __init__(self, virtapi, read_only=False):
545545
# events about success or failure.
546546
self._device_event_handler = AsyncDeviceEventsHandler()
547547

548+
# NOTE(artom) From a pure functionality point of view, there's no need
549+
# for this to be an attribute of self. However, we want to test power
550+
# management in multinode scenarios (ex: live migration) in our
551+
# functional tests. If the power management code was just a bunch of
552+
# module level functions, the functional tests would not be able to
553+
# distinguish between cores on the source and destination hosts.
554+
# See also nova.virt.libvirt.cpu.api.API.core().
555+
self.cpu_api = libvirt_cpu.API()
556+
548557
def _discover_vpmems(self, vpmem_conf=None):
549558
"""Discover vpmems on host and configuration.
550559

@@ -822,13 +831,13 @@ def init_host(self, host):
822831
# modified by Nova before. Note that it can provide an exception if
823832
# either the governor strategies are different between the cores or if
824833
# the cores are offline.
825-
libvirt_cpu.validate_all_dedicated_cpus()
834+
self.cpu_api.validate_all_dedicated_cpus()
826835
# NOTE(sbauza): We powerdown all dedicated CPUs but if some instances
827836
# exist that are pinned for some CPUs, then we'll later powerup those
828837
# CPUs when rebooting the instance in _init_instance()
829838
# Note that it can provide an exception if the config options are
830839
# wrongly modified.
831-
libvirt_cpu.power_down_all_dedicated_cpus()
840+
self.cpu_api.power_down_all_dedicated_cpus()
832841

833842
# TODO(sbauza): Remove this code once mediated devices are persisted
834843
# across reboots.
@@ -1526,7 +1535,7 @@ def _wait_for_destroy(expected_domid):
15261535
if CONF.libvirt.virt_type == 'lxc':
15271536
self._teardown_container(instance)
15281537
# We're sure the instance is gone, we can shutdown the core if so
1529-
libvirt_cpu.power_down(instance)
1538+
self.cpu_api.power_down(instance)
15301539

15311540
def destroy(self, context, instance, network_info, block_device_info=None,
15321541
destroy_disks=True, destroy_secrets=True):
@@ -3180,7 +3189,7 @@ def _resume_guest_after_snapshot(
31803189

31813190
current_power_state = guest.get_power_state(self._host)
31823191

3183-
libvirt_cpu.power_up(instance)
3192+
self.cpu_api.power_up(instance)
31843193
# TODO(stephenfin): Any reason we couldn't use 'self.resume' here?
31853194
guest.launch(pause=current_power_state == power_state.PAUSED)
31863195

@@ -7661,7 +7670,7 @@ def _create_guest(
76617670
post_xml_callback()
76627671

76637672
if power_on or pause:
7664-
libvirt_cpu.power_up(instance)
7673+
self.cpu_api.power_up(instance)
76657674
guest.launch(pause=pause)
76667675

76677676
return guest

0 commit comments

Comments
 (0)