Skip to content

Commit 6aa1931

Browse files
committed
libvirt: apply mixed instance CPU policy
For an instance with a 'mixed' CPU allocation policy, the dedicated CPUs and the shared CPUs are mixed in one instance. From the host CPU using perspective, dedicated CPU exclusively occupies one host CPU but may offer some CPU time slots to instance emulator if the emulator thread policy is not 'ISOLATE'. While the shared CPU is sharing host CPU with other instance CPUs, the shared CPU floats over a list of host CPUs of a NUMA cell if the instance is NUMA-aware. The emulator thread is running on the reserved host CPU if the emulator thread policy is 'ISOLATE', if the policy is not 'ISOLATE', it will float on host CPUs listing in '[compute] cpu_shared_set' if this configuration is provided and the emulator thread policy is explicitly set as 'SHARE'. otherwise, the emulator thread is floating on all non-realtime CPUs that the instance is used. Later, we'll introduce the 'mixed' instance mixing with realtime CPUs and shared CPUs. Change-Id: I0ea3a0a1342ef53ed891c145561eb7402f553a8b Signed-off-by: Wang Huaqiang <[email protected]>
1 parent c4e9e2e commit 6aa1931

File tree

2 files changed

+339
-5
lines changed

2 files changed

+339
-5
lines changed

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

Lines changed: 329 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3816,6 +3816,335 @@ def test_get_guest_config_numa_host_instance_topo_cpu_pinning(self):
38163816
self.assertEqual([instance_cell.id], memnode.nodeset)
38173817
self.assertEqual("strict", memnode.mode)
38183818

3819+
def test_get_guest_config_numa_host_instance_cpu_mixed(self):
3820+
"""Test to create mixed instance libvirt configuration which has a
3821+
default emulator thread policy and verify the NUMA topology related
3822+
settings.
3823+
"""
3824+
self.flags(cpu_shared_set='2-5,8-29',
3825+
cpu_dedicated_set='6,7,30,31',
3826+
group='compute')
3827+
3828+
instance_topology = objects.InstanceNUMATopology(cells=[
3829+
objects.InstanceNUMACell(
3830+
id=3, cpuset=set([0, 1]), pcpuset=set([2, 3]), memory=1024,
3831+
cpu_pinning={2: 30, 3: 31}
3832+
),
3833+
objects.InstanceNUMACell(
3834+
id=0, cpuset=set([4, 5, 6]), pcpuset=set([7]), memory=1024,
3835+
cpu_pinning={7: 6}
3836+
),
3837+
])
3838+
instance_ref = objects.Instance(**self.test_instance)
3839+
instance_ref.numa_topology = instance_topology
3840+
image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
3841+
flavor = objects.Flavor(memory_mb=2048, vcpus=8, root_gb=496,
3842+
ephemeral_gb=8128, swap=33550336, name='fake',
3843+
extra_specs={})
3844+
instance_ref.flavor = flavor
3845+
3846+
caps = vconfig.LibvirtConfigCaps()
3847+
caps.host = vconfig.LibvirtConfigCapsHost()
3848+
caps.host.cpu = vconfig.LibvirtConfigCPU()
3849+
caps.host.cpu.arch = fields.Architecture.X86_64
3850+
caps.host.topology = fakelibvirt.NUMATopology(
3851+
cpu_nodes=4, cpu_sockets=1, cpu_cores=4, cpu_threads=2)
3852+
conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
3853+
disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,
3854+
instance_ref,
3855+
image_meta)
3856+
3857+
with test.nested(
3858+
mock.patch.object(
3859+
objects.InstanceNUMATopology, "get_by_instance_uuid",
3860+
return_value=instance_topology),
3861+
mock.patch.object(host.Host, 'has_min_version', return_value=True),
3862+
mock.patch.object(host.Host, "get_capabilities",
3863+
return_value=caps),
3864+
mock.patch.object(host.Host, 'get_online_cpus',
3865+
return_value=set(range(32)))
3866+
):
3867+
cfg = conn._get_guest_config(instance_ref, [],
3868+
image_meta, disk_info)
3869+
self.assertIsNone(cfg.cpuset)
3870+
3871+
# NOTE(huaqiang): Within a mixed instance, it is expected that the
3872+
# pinned and unpinned CPUs, which belong to the same instance NUMA
3873+
# cell, are scheduled on a same host NUMA cell, the instance pinned
3874+
# CPU is 1:1 scheduled to a dedicated host CPU, each unpinned CPU
3875+
# floats over the shared CPU list of the same host NUMA cell.
3876+
# The host NUMA cell's dedicated CPU list and shared CPU list are
3877+
# calculated from a combination of '[compute]cpu_dedicated_set',
3878+
# '[compute]cpu_shared_set', the host NUMA topology and the online
3879+
# CPUs.
3880+
#
3881+
# The first instance NUMA cell is fit into the fourth host NUMA
3882+
# cell due to having the same 'id' 3. Instance CPU 0 and 1 are
3883+
# unpinned CPUs, check each of them floats on the host NUMA cell's
3884+
# sharing CPUs, which are CPU 24-29.
3885+
self.assertEqual(0, cfg.cputune.vcpupin[0].id)
3886+
self.assertEqual(set(range(24, 30)), cfg.cputune.vcpupin[0].cpuset)
3887+
self.assertEqual(1, cfg.cputune.vcpupin[1].id)
3888+
self.assertEqual(set(range(24, 30)), cfg.cputune.vcpupin[1].cpuset)
3889+
# Check each of the instance NUMA cell's pinned CPUs is pinned to a
3890+
# dedicated CPU from the fourth host NUMA cell.
3891+
self.assertEqual(2, cfg.cputune.vcpupin[2].id)
3892+
self.assertEqual(set([30]), cfg.cputune.vcpupin[2].cpuset)
3893+
self.assertEqual(3, cfg.cputune.vcpupin[3].id)
3894+
self.assertEqual(set([31]), cfg.cputune.vcpupin[3].cpuset)
3895+
3896+
# Instance CPU 4-7 belong to the second instance NUMA cell, which
3897+
# is fit into host NUMA cell 0. CPU 4-6 are unpinned CPUs, each of
3898+
# them floats on the host NUMA cell's sharing CPU set, CPU 2-5.
3899+
self.assertEqual(4, cfg.cputune.vcpupin[4].id)
3900+
self.assertEqual(set(range(2, 6)), cfg.cputune.vcpupin[4].cpuset)
3901+
self.assertEqual(5, cfg.cputune.vcpupin[5].id)
3902+
self.assertEqual(set(range(2, 6)), cfg.cputune.vcpupin[5].cpuset)
3903+
self.assertEqual(6, cfg.cputune.vcpupin[6].id)
3904+
self.assertEqual(set(range(2, 6)), cfg.cputune.vcpupin[6].cpuset)
3905+
# Instance CPU 7 is pinned to the host NUMA cell's dedicated CPU 6.
3906+
self.assertEqual(set([6]), cfg.cputune.vcpupin[7].cpuset)
3907+
self.assertIsNotNone(cfg.cpu.numa)
3908+
3909+
# Check emulator thread is pinned to union of
3910+
# cfg.cputune.vcpupin[*].cpuset
3911+
self.assertIsInstance(cfg.cputune.emulatorpin,
3912+
vconfig.LibvirtConfigGuestCPUTuneEmulatorPin)
3913+
self.assertEqual(
3914+
set([2, 3, 4, 5, 6, 24, 25, 26, 27, 28, 29, 30, 31]),
3915+
cfg.cputune.emulatorpin.cpuset)
3916+
3917+
for i, (instance_cell, numa_cfg_cell) in enumerate(
3918+
zip(instance_topology.cells, cfg.cpu.numa.cells)
3919+
):
3920+
self.assertEqual(i, numa_cfg_cell.id)
3921+
self.assertEqual(instance_cell.total_cpus, numa_cfg_cell.cpus)
3922+
self.assertEqual(instance_cell.memory * units.Ki,
3923+
numa_cfg_cell.memory)
3924+
self.assertIsNone(numa_cfg_cell.memAccess)
3925+
3926+
allnodes = set([cell.id for cell in instance_topology.cells])
3927+
self.assertEqual(allnodes, set(cfg.numatune.memory.nodeset))
3928+
self.assertEqual("strict", cfg.numatune.memory.mode)
3929+
3930+
for i, (instance_cell, memnode) in enumerate(
3931+
zip(instance_topology.cells, cfg.numatune.memnodes)
3932+
):
3933+
self.assertEqual(i, memnode.cellid)
3934+
self.assertEqual([instance_cell.id], memnode.nodeset)
3935+
3936+
def test_get_guest_config_numa_host_instance_cpu_mixed_isolated_emu(self):
3937+
"""Test to create mixed instance libvirt configuration which has an
3938+
ISOLATED emulator thread policy and verify the NUMA topology related
3939+
settings.
3940+
"""
3941+
self.flags(cpu_shared_set='2-5,8-29',
3942+
cpu_dedicated_set='6,7,30,31',
3943+
group='compute')
3944+
instance_topology = objects.InstanceNUMATopology(
3945+
emulator_threads_policy=fields.CPUEmulatorThreadsPolicy.ISOLATE,
3946+
cells=[objects.InstanceNUMACell(
3947+
id=0, cpuset=set([0, 1]), pcpuset=set([2]), memory=1024,
3948+
cpu_pinning={2: 6},
3949+
cpuset_reserved=set([7]))])
3950+
instance_ref = objects.Instance(**self.test_instance)
3951+
instance_ref.numa_topology = instance_topology
3952+
image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
3953+
flavor = objects.Flavor(memory_mb=2048, vcpus=8, root_gb=496,
3954+
ephemeral_gb=8128, swap=33550336, name='fake',
3955+
extra_specs={})
3956+
instance_ref.flavor = flavor
3957+
3958+
caps = vconfig.LibvirtConfigCaps()
3959+
caps.host = vconfig.LibvirtConfigCapsHost()
3960+
caps.host.cpu = vconfig.LibvirtConfigCPU()
3961+
caps.host.cpu.arch = fields.Architecture.X86_64
3962+
caps.host.topology = fakelibvirt.NUMATopology(
3963+
cpu_nodes=4, cpu_sockets=1, cpu_cores=4, cpu_threads=2)
3964+
3965+
conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
3966+
disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,
3967+
instance_ref,
3968+
image_meta)
3969+
3970+
with test.nested(
3971+
mock.patch.object(
3972+
objects.InstanceNUMATopology, "get_by_instance_uuid",
3973+
return_value=instance_topology),
3974+
mock.patch.object(host.Host, 'has_min_version',
3975+
return_value=True),
3976+
mock.patch.object(host.Host, "get_capabilities",
3977+
return_value=caps),
3978+
mock.patch.object(host.Host, 'get_online_cpus',
3979+
return_value=set(range(32))),
3980+
):
3981+
cfg = conn._get_guest_config(instance_ref, [],
3982+
image_meta, disk_info)
3983+
self.assertIsNone(cfg.cpuset)
3984+
# NOTE(huaqiang): The instance NUMA cell is fit into the first host
3985+
# NUMA cell, which is matched by the 'id' fields of two objects.
3986+
# CPU 2-5 are the first host NUMA cell's floating CPU set.
3987+
# Check any instance unpinned CPU is floating on this CPU set.
3988+
self.assertEqual(0, cfg.cputune.vcpupin[0].id)
3989+
self.assertEqual(set([2, 3, 4, 5]), cfg.cputune.vcpupin[0].cpuset)
3990+
self.assertEqual(1, cfg.cputune.vcpupin[1].id)
3991+
self.assertEqual(set([2, 3, 4, 5]), cfg.cputune.vcpupin[1].cpuset)
3992+
# Check instance CPU 2, a pinned CPU, is pinned to a dedicated CPU
3993+
# of host's first NUMA cell.
3994+
self.assertEqual(2, cfg.cputune.vcpupin[2].id)
3995+
self.assertEqual(set([6]), cfg.cputune.vcpupin[2].cpuset)
3996+
self.assertIsNotNone(cfg.cpu.numa)
3997+
3998+
# With an ISOLATE policy, emulator thread will be pinned to the
3999+
# reserved host CPU.
4000+
self.assertIsInstance(cfg.cputune.emulatorpin,
4001+
vconfig.LibvirtConfigGuestCPUTuneEmulatorPin)
4002+
self.assertEqual(set([7]), cfg.cputune.emulatorpin.cpuset)
4003+
4004+
for i, (instance_cell, numa_cfg_cell) in enumerate(
4005+
zip(instance_topology.cells, cfg.cpu.numa.cells)
4006+
):
4007+
self.assertEqual(i, numa_cfg_cell.id)
4008+
self.assertEqual(instance_cell.total_cpus, numa_cfg_cell.cpus)
4009+
self.assertEqual(instance_cell.memory * units.Ki,
4010+
numa_cfg_cell.memory)
4011+
self.assertIsNone(numa_cfg_cell.memAccess)
4012+
4013+
allnodes = set([cell.id for cell in instance_topology.cells])
4014+
self.assertEqual(allnodes, set(cfg.numatune.memory.nodeset))
4015+
self.assertEqual("strict", cfg.numatune.memory.mode)
4016+
4017+
for i, (instance_cell, memnode) in enumerate(
4018+
zip(instance_topology.cells, cfg.numatune.memnodes)
4019+
):
4020+
self.assertEqual(i, memnode.cellid)
4021+
self.assertEqual([instance_cell.id], memnode.nodeset)
4022+
4023+
def test_get_guest_config_numa_host_instance_cpu_mixed_realtime(self):
4024+
"""Test of creating mixed instance libvirt configuration. which is
4025+
created through 'hw:cpu_realtime_mask' and 'hw:cpu_realtime' extra
4026+
specs, verifying the NUMA topology and real-time related settings.
4027+
"""
4028+
self.flags(cpu_shared_set='2-5,8-29',
4029+
cpu_dedicated_set='6,7,30,31',
4030+
group='compute')
4031+
4032+
instance_topology = objects.InstanceNUMATopology(
4033+
cells=[
4034+
objects.InstanceNUMACell(
4035+
id=0, cpuset=set([2]), pcpuset=set([0, 1]),
4036+
cpu_pinning={0: 6, 1: 7},
4037+
memory=1024, pagesize=2048),
4038+
objects.InstanceNUMACell(
4039+
id=3, cpuset=set([3]), pcpuset=set([4, 5]),
4040+
cpu_pinning={4: 30, 5: 31},
4041+
memory=1024, pagesize=2048)])
4042+
instance_ref = objects.Instance(**self.test_instance)
4043+
instance_ref.numa_topology = instance_topology
4044+
image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
4045+
# NOTE(huaqiang): libvirt driver takes the real-time CPU list from the
4046+
# flavor extra spec 'hw:cpu_realtime_mask'. For a mixed instance with
4047+
# real-time CPUs, the dedicated CPU and the real-time CPU are the
4048+
# same CPU set, this is checked in API layer.
4049+
flavor = objects.Flavor(
4050+
vcpus=6, memory_mb=2048, root_gb=496,
4051+
ephemeral_gb=8128, swap=33550336, name='fake',
4052+
extra_specs={
4053+
"hw:numa_nodes": "2",
4054+
"hw:cpu_realtime": "yes",
4055+
"hw:cpu_policy": "mixed",
4056+
"hw:cpu_realtime_mask": "^2-3"
4057+
})
4058+
instance_ref.flavor = flavor
4059+
4060+
caps = vconfig.LibvirtConfigCaps()
4061+
caps.host = vconfig.LibvirtConfigCapsHost()
4062+
caps.host.cpu = vconfig.LibvirtConfigCPU()
4063+
caps.host.cpu.arch = fields.Architecture.X86_64
4064+
caps.host.topology = fakelibvirt.NUMATopology(
4065+
cpu_nodes=4, cpu_sockets=1, cpu_cores=4, cpu_threads=2)
4066+
for i, cell in enumerate(caps.host.topology.cells):
4067+
cell.mempages = fakelibvirt.create_mempages(
4068+
[(4, 1024 * i), (2048, i)])
4069+
4070+
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
4071+
disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,
4072+
instance_ref,
4073+
image_meta)
4074+
4075+
with test.nested(
4076+
mock.patch.object(
4077+
objects.InstanceNUMATopology, "get_by_instance_uuid",
4078+
return_value=instance_topology),
4079+
mock.patch.object(host.Host, 'has_min_version', return_value=True),
4080+
mock.patch.object(host.Host, "get_capabilities",
4081+
return_value=caps),
4082+
mock.patch.object(host.Host, 'get_online_cpus',
4083+
return_value=set(range(32))),
4084+
):
4085+
cfg = drvr._get_guest_config(instance_ref, [],
4086+
image_meta, disk_info)
4087+
4088+
for instance_cell, numa_cfg_cell, index in zip(
4089+
instance_topology.cells,
4090+
cfg.cpu.numa.cells,
4091+
range(len(instance_topology.cells))
4092+
):
4093+
self.assertEqual(index, numa_cfg_cell.id)
4094+
self.assertEqual(instance_cell.total_cpus, numa_cfg_cell.cpus)
4095+
self.assertEqual(instance_cell.memory * units.Ki,
4096+
numa_cfg_cell.memory)
4097+
self.assertEqual("shared", numa_cfg_cell.memAccess)
4098+
4099+
allnodes = [cell.id for cell in instance_topology.cells]
4100+
self.assertEqual(allnodes, cfg.numatune.memory.nodeset)
4101+
self.assertEqual("strict", cfg.numatune.memory.mode)
4102+
4103+
for instance_cell, memnode, index in zip(
4104+
instance_topology.cells,
4105+
cfg.numatune.memnodes,
4106+
range(len(instance_topology.cells))
4107+
):
4108+
self.assertEqual(index, memnode.cellid)
4109+
self.assertEqual([instance_cell.id], memnode.nodeset)
4110+
self.assertEqual("strict", memnode.mode)
4111+
4112+
# NOTE(huaqiang): Instance first NUMA cell is fit to the first host
4113+
# NUMA cell. In this host NUMA cell, CPU 2-5 are the sharing CPU
4114+
# set, CPU 6, 7 are dedicated CPUs.
4115+
#
4116+
# Check instance CPU 0, 1 are 1:1 pinned on host NUMA cell's
4117+
# dedicated CPUs.
4118+
self.assertEqual(set([6]), cfg.cputune.vcpupin[0].cpuset)
4119+
self.assertEqual(set([7]), cfg.cputune.vcpupin[1].cpuset)
4120+
# Check CPU 2, an unpinned CPU, is floating on this host NUMA
4121+
# cell's sharing CPU set.
4122+
self.assertEqual(set([2, 3, 4, 5]), cfg.cputune.vcpupin[2].cpuset)
4123+
4124+
# The second instance NUMA cell is fit to the fourth host NUMA
4125+
# cell due to a same 'id'. Host CPU 24-29 are sharing CPU set, host
4126+
# CPU 30, 31 are dedicated CPU to be pinning.
4127+
#
4128+
# Check CPU 3 is floating on the sharing CPU set.
4129+
self.assertEqual(set([24, 25, 26, 27, 28, 29]),
4130+
cfg.cputune.vcpupin[3].cpuset)
4131+
# Check CPU 4, 5 are pinned on host dedicated CPUs.
4132+
self.assertEqual(set([30]), cfg.cputune.vcpupin[4].cpuset)
4133+
self.assertEqual(set([31]), cfg.cputune.vcpupin[5].cpuset)
4134+
4135+
# Check the real-time host CPUs are excluded from the host CPU
4136+
# list the emulator is floating on.
4137+
self.assertEqual(set([2, 3, 4, 5, 24, 25, 26, 27, 28, 29]),
4138+
cfg.cputune.emulatorpin.cpuset)
4139+
4140+
# Check the real-time scheduler is set, and all real-time CPUs are
4141+
# in the vcpusched[0].vcpus list. In nova, the real-time scheduler
4142+
# is always set to 'fifo', and there is always only one element in
4143+
# cfg.cputune.vcpusched.
4144+
self.assertEqual(1, len(cfg.cputune.vcpusched))
4145+
self.assertEqual("fifo", cfg.cputune.vcpusched[0].scheduler)
4146+
self.assertEqual(set([0, 1, 4, 5]), cfg.cputune.vcpusched[0].vcpus)
4147+
38194148
def test_get_guest_config_numa_host_mempages_shared(self):
38204149
self.flags(cpu_shared_set='2-5', cpu_dedicated_set=None,
38214150
group='compute')

nova/virt/libvirt/driver.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4971,19 +4971,24 @@ def _get_cell_pairs(self, guest_cpu_numa_config, host_topology):
49714971
cell_pairs.append((guest_config_cell, host_cell))
49724972
return cell_pairs
49734973

4974-
def _get_pin_cpuset(self, vcpu, object_numa_cell, host_cell):
4974+
def _get_pin_cpuset(self, vcpu, inst_cell, host_cell):
49754975
"""Returns the config object of LibvirtConfigGuestCPUTuneVCPUPin.
4976+
49764977
Prepares vcpupin config for the guest with the following caveats:
49774978
4978-
a) If there is pinning information in the cell, we pin vcpus to
4979-
individual CPUs
4979+
a) If the specified instance vCPU is intended to be pinned, we pin
4980+
it to the previously selected host CPU.
49804981
b) Otherwise we float over the whole host NUMA node
49814982
"""
49824983
pin_cpuset = vconfig.LibvirtConfigGuestCPUTuneVCPUPin()
49834984
pin_cpuset.id = vcpu
49844985

4985-
if object_numa_cell.cpu_pinning:
4986-
pin_cpuset.cpuset = set([object_numa_cell.cpu_pinning[vcpu]])
4986+
# 'InstanceNUMACell.cpu_pinning' tracks the CPU pinning pair for guest
4987+
# CPU and host CPU. If the guest CPU is in the keys of 'cpu_pinning',
4988+
# fetch the host CPU from it and pin on it, otherwise, let the guest
4989+
# CPU be floating on the sharing CPU set belonging to this NUMA cell.
4990+
if inst_cell.cpu_pinning and vcpu in inst_cell.cpu_pinning:
4991+
pin_cpuset.cpuset = set([inst_cell.cpu_pinning[vcpu]])
49874992
else:
49884993
pin_cpuset.cpuset = host_cell.cpuset
49894994

0 commit comments

Comments
 (0)