Skip to content

Commit 22d06e3

Browse files
authored
Merge pull request #56 from stackhpc/bugfix/yoga-live-migrate-rocky-9
libvirt: remove default cputune shares value
2 parents e40b516 + fa3c95b commit 22d06e3

File tree

6 files changed

+83
-38
lines changed

6 files changed

+83
-38
lines changed

doc/source/admin/resource-limits.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ CPU limits
3838
Libvirt enforces CPU limits in terms of *shares* and *quotas*, configured
3939
via :nova:extra-spec:`quota:cpu_shares` and :nova:extra-spec:`quota:cpu_period`
4040
/ :nova:extra-spec:`quota:cpu_quota`, respectively. Both are implemented using
41-
the `cgroups v1 cpu controller`__.
41+
the `cgroups cpu controller`__. Note that allowed values for *shares* are
42+
platform dependant.
4243

4344
CPU shares are a proportional weighted share of total CPU resources relative to
4445
other instances. It does not limit CPU usage if CPUs are not busy. There is no

nova/tests/unit/virt/libvirt/test_driver.py

Lines changed: 12 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2989,7 +2989,7 @@ def test_get_guest_config_numa_host_instance_fits(self):
29892989
cfg = drvr._get_guest_config(instance_ref, [],
29902990
image_meta, disk_info)
29912991
self.assertIsNone(cfg.cpuset)
2992-
self.assertEqual(0, len(cfg.cputune.vcpupin))
2992+
self.assertIsNone(cfg.cputune)
29932993
self.assertIsNone(cfg.cpu.numa)
29942994

29952995
@mock.patch('nova.privsep.utils.supports_direct_io',
@@ -3024,7 +3024,7 @@ def test_get_guest_config_numa_host_instance_no_fit(self):
30243024
image_meta, disk_info)
30253025
self.assertFalse(choice_mock.called)
30263026
self.assertIsNone(cfg.cpuset)
3027-
self.assertEqual(0, len(cfg.cputune.vcpupin))
3027+
self.assertIsNone(cfg.cputune)
30283028
self.assertIsNone(cfg.cpu.numa)
30293029

30303030
def _test_get_guest_memory_backing_config(
@@ -3429,7 +3429,7 @@ def test_get_guest_config_numa_host_instance_pci_no_numa_info(self):
34293429
cfg = conn._get_guest_config(instance_ref, [],
34303430
image_meta, disk_info)
34313431
self.assertEqual(set([3]), cfg.cpuset)
3432-
self.assertEqual(0, len(cfg.cputune.vcpupin))
3432+
self.assertIsNone(cfg.cputune)
34333433
self.assertIsNone(cfg.cpu.numa)
34343434

34353435
@mock.patch('nova.privsep.utils.supports_direct_io',
@@ -3481,7 +3481,7 @@ def test_get_guest_config_numa_host_instance_2pci_no_fit(self):
34813481
image_meta, disk_info)
34823482
self.assertFalse(choice_mock.called)
34833483
self.assertEqual(set([3]), cfg.cpuset)
3484-
self.assertEqual(0, len(cfg.cputune.vcpupin))
3484+
self.assertIsNone(cfg.cputune)
34853485
self.assertIsNone(cfg.cpu.numa)
34863486

34873487
@mock.patch.object(fakelibvirt.Connection, 'getType')
@@ -3577,7 +3577,7 @@ def test_get_guest_config_numa_host_instance_fit_w_cpu_pinset(self):
35773577
# NOTE(ndipanov): we make sure that pin_set was taken into account
35783578
# when choosing viable cells
35793579
self.assertEqual(set([2, 3]), cfg.cpuset)
3580-
self.assertEqual(0, len(cfg.cputune.vcpupin))
3580+
self.assertIsNone(cfg.cputune)
35813581
self.assertIsNone(cfg.cpu.numa)
35823582

35833583
def test_get_guest_config_non_numa_host_instance_topo(self):
@@ -3617,7 +3617,7 @@ def test_get_guest_config_non_numa_host_instance_topo(self):
36173617
cfg = drvr._get_guest_config(instance_ref, [],
36183618
image_meta, disk_info)
36193619
self.assertIsNone(cfg.cpuset)
3620-
self.assertEqual(0, len(cfg.cputune.vcpupin))
3620+
self.assertIsNone(cfg.cputune)
36213621
self.assertIsNone(cfg.numatune)
36223622
self.assertIsNotNone(cfg.cpu.numa)
36233623
for instance_cell, numa_cfg_cell in zip(
@@ -7020,25 +7020,9 @@ def test_get_guest_config_with_rng_dev_not_present(self, mock_path):
70207020
[],
70217021
image_meta, disk_info)
70227022

7023-
def test_guest_cpu_shares_with_multi_vcpu(self):
7024-
self.flags(virt_type='kvm', group='libvirt')
7025-
7026-
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
7027-
7028-
instance_ref = objects.Instance(**self.test_instance)
7029-
instance_ref.flavor.vcpus = 4
7030-
image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
7031-
7032-
disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,
7033-
instance_ref,
7034-
image_meta)
7035-
7036-
cfg = drvr._get_guest_config(instance_ref, [],
7037-
image_meta, disk_info)
7038-
7039-
self.assertEqual(4096, cfg.cputune.shares)
7040-
7041-
def test_get_guest_config_with_cpu_quota(self):
7023+
@mock.patch.object(
7024+
host.Host, "is_cpu_control_policy_capable", return_value=True)
7025+
def test_get_guest_config_with_cpu_quota(self, is_able):
70427026
self.flags(virt_type='kvm', group='libvirt')
70437027

70447028
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
@@ -11608,7 +11592,7 @@ def test_live_migration_update_graphics_xml(self, mock_xml,
1160811592
mock_migrateToURI3,
1160911593
mock_min_version):
1161011594
self.compute = manager.ComputeManager()
11611-
instance_ref = self.test_instance
11595+
instance_ref = objects.Instance(**self.test_instance)
1161211596
target_connection = '127.0.0.2'
1161311597

1161411598
xml_tmpl = ("<domain type='kvm'>"
@@ -12288,7 +12272,7 @@ def test_live_migration_update_serial_console_xml(self, mock_xml,
1228812272
mock_get,
1228912273
mock_min_version):
1229012274
self.compute = manager.ComputeManager()
12291-
instance_ref = self.test_instance
12275+
instance_ref = objects.Instance(**self.test_instance)
1229212276
target_connection = '127.0.0.2'
1229312277

1229412278
xml_tmpl = ("<domain type='kvm'>"
@@ -12578,7 +12562,7 @@ def test_live_migration_raises_exception(self, mock_xml,
1257812562
mock_min_version):
1257912563
# Prepare data
1258012564
self.compute = manager.ComputeManager()
12581-
instance_ref = self.test_instance
12565+
instance_ref = objects.Instance(**self.test_instance)
1258212566
target_connection = '127.0.0.2'
1258312567

1258412568
disk_paths = ['vda', 'vdb']

nova/tests/unit/virt/libvirt/test_migration.py

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from nova import test
2929
from nova.tests import fixtures as nova_fixtures
3030
from nova.tests.fixtures import libvirt as fakelibvirt
31+
from nova.tests.unit.virt.libvirt import test_driver
3132
from nova.virt.libvirt import config as vconfig
3233
from nova.virt.libvirt import guest as libvirt_guest
3334
from nova.virt.libvirt import host
@@ -80,16 +81,51 @@ def test_get_updated_guest_xml(
8081
get_volume_config = mock.MagicMock()
8182
mock_guest.get_xml_desc.return_value = '<domain></domain>'
8283

83-
migration.get_updated_guest_xml(
84-
mock.sentinel.instance, mock_guest, data, get_volume_config)
84+
instance = objects.Instance(**test_driver._create_test_instance())
85+
migration.get_updated_guest_xml(instance, mock_guest, data,
86+
get_volume_config)
8587
mock_graphics.assert_called_once_with(mock.ANY, data)
8688
mock_serial.assert_called_once_with(mock.ANY, data)
8789
mock_volume.assert_called_once_with(
88-
mock.ANY, data, mock.sentinel.instance, get_volume_config)
90+
mock.ANY, data, instance, get_volume_config)
8991
mock_perf_events_xml.assert_called_once_with(mock.ANY, data)
9092
mock_memory_backing.assert_called_once_with(mock.ANY, data)
9193
self.assertEqual(1, mock_tostring.called)
9294

95+
def test_update_quota_xml(self):
96+
old_xml = """<domain>
97+
<name>fake-instance</name>
98+
<cputune>
99+
<shares>42</shares>
100+
<period>1337</period>
101+
</cputune>
102+
</domain>"""
103+
instance = objects.Instance(**test_driver._create_test_instance())
104+
new_xml = migration._update_quota_xml(instance,
105+
etree.fromstring(old_xml))
106+
new_xml = etree.tostring(new_xml, encoding='unicode')
107+
self.assertXmlEqual(
108+
"""<domain>
109+
<name>fake-instance</name>
110+
<cputune>
111+
<period>1337</period>
112+
</cputune>
113+
</domain>""", new_xml)
114+
115+
def test_update_quota_xml_empty_cputune(self):
116+
old_xml = """<domain>
117+
<name>fake-instance</name>
118+
<cputune>
119+
<shares>42</shares>
120+
</cputune>
121+
</domain>"""
122+
instance = objects.Instance(**test_driver._create_test_instance())
123+
new_xml = migration._update_quota_xml(instance,
124+
etree.fromstring(old_xml))
125+
new_xml = etree.tostring(new_xml, encoding='unicode')
126+
self.assertXmlEqual('<domain><name>fake-instance</name></domain>',
127+
new_xml)
128+
93129
def test_update_device_resources_xml_vpmem(self):
94130
# original xml for vpmems, /dev/dax0.1 and /dev/dax0.2 here
95131
# are vpmem device path on source host

nova/virt/libvirt/driver.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5682,15 +5682,11 @@ def _update_guest_cputune(self, guest, flavor):
56825682
if not is_able or CONF.libvirt.virt_type not in ('lxc', 'kvm', 'qemu'):
56835683
return
56845684

5685-
if guest.cputune is None:
5686-
guest.cputune = vconfig.LibvirtConfigGuestCPUTune()
5687-
# Setting the default cpu.shares value to be a value
5688-
# dependent on the number of vcpus
5689-
guest.cputune.shares = 1024 * guest.vcpus
5690-
56915685
for name in cputuning:
56925686
key = "quota:cpu_" + name
56935687
if key in flavor.extra_specs:
5688+
if guest.cputune is None:
5689+
guest.cputune = vconfig.LibvirtConfigGuestCPUTune()
56945690
setattr(guest.cputune, name,
56955691
int(flavor.extra_specs[key]))
56965692

nova/virt/libvirt/migration.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ def get_updated_guest_xml(instance, guest, migrate_data, get_volume_config,
6262
xml_doc, migrate_data, instance, get_volume_config)
6363
xml_doc = _update_perf_events_xml(xml_doc, migrate_data)
6464
xml_doc = _update_memory_backing_xml(xml_doc, migrate_data)
65+
xml_doc = _update_quota_xml(instance, xml_doc)
6566
if get_vif_config is not None:
6667
xml_doc = _update_vif_xml(xml_doc, migrate_data, get_vif_config)
6768
if 'dst_numa_info' in migrate_data:
@@ -71,6 +72,18 @@ def get_updated_guest_xml(instance, guest, migrate_data, get_volume_config,
7172
return etree.tostring(xml_doc, encoding='unicode')
7273

7374

75+
def _update_quota_xml(instance, xml_doc):
76+
flavor_shares = instance.flavor.extra_specs.get('quota:cpu_shares')
77+
cputune = xml_doc.find('./cputune')
78+
shares = xml_doc.find('./cputune/shares')
79+
if shares is not None and not flavor_shares:
80+
cputune.remove(shares)
81+
# Remove the cputune element entirely if it has no children left.
82+
if cputune is not None and not list(cputune):
83+
xml_doc.remove(cputune)
84+
return xml_doc
85+
86+
7487
def _update_device_resources_xml(xml_doc, new_resources):
7588
vpmems = []
7689
for resource in new_resources:
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
upgrade:
2+
- |
3+
In the libvirt driver, the default value of the ``<cputune><shares>``
4+
element has been removed, and is now left to libvirt to decide. This is
5+
because allowed values are platform dependant, and the previous code was
6+
not guaranteed to be supported on all platforms. If any of your flavors are
7+
using the quota:cpu_shares extra spec, you may need to resize to a
8+
supported value before upgrading.
9+
10+
To facilitate the transition to no Nova default for ``<cputune><shares>``,
11+
its value will be removed during live migration unless a value is set in
12+
the ``quota:cpu_shares`` extra spec. This can cause temporary CPU
13+
starvation for the live migrated instance if other instances on the
14+
destination host still have the old default ``<cputune><shares>`` value. To
15+
fix this, hard reboot, cold migrate, or live migrate the other instances.

0 commit comments

Comments
 (0)