Remove NSM from emul thread tests

A continuation of [1] in which we are trying to reduce the dependency on
NovaServiceManager when interacting with cpu_dedicated_set and
cpu_shared_set.

This commit introduces two new configuration options:
 * whitebox_hardware.dedicated_cpus_per_numa
 * whitebox_hardware.shared_cpus_per_numa

These options describe the number of pCPUs alloacted to either shared or
dedicated sets for a given compute host. We are making the assumption
that although the pCPUs allocated to their respective sets may be
different on each compute host, the number of pCPUs allocated are the
same.

Commit also removes NSM from three of the four base emulator thread
testcases and reduces some of the complexity originally present in the
tests. Since vcpu_pin_set and cpu_dedicated_set are no longer
directly modified in the test, their respective child tests have been
removed leaving just the parent to covering scenarios based on the
deployment.

[1] cca33388c2

Change-Id: I57243d49de0d1715fecaa6496a92fc2c576c448a
This commit is contained in:
James Parker 2021-10-26 10:16:29 -04:00
parent 0276f52886
commit 7385e1bc55
6 changed files with 130 additions and 301 deletions

View File

@ -86,7 +86,7 @@
vars:
# NOTE(artom) We can't have this on the parent job, otherwise the two
# -cpupinnig jobs will inherit it as well.
tempest_exclude_regex: test_live_migrate_and_reboot|test_shared_pinned_and_unpinned_guest
tempest_exclude_regex: 'test_live_migrate_and_reboot|test_shared_pinned_and_unpinned_guest|^whitebox_tempest_plugin.api.compute.test_cpu_pinning.EmulatorThreadTest'
- job:
name: whitebox-devstack-multinode-cpupinning
@ -95,7 +95,7 @@
Runs the CPU pinning tests on single-NUMA, non-SMT, nested virt VMs. Uses
[compute]cpu_dedicated_set to configure host CPUs for pinning.
vars:
tempest_test_regex: test_live_migrate_and_reboot|test_shared_pinned_and_unpinned_guest
tempest_test_regex: 'test_live_migrate_and_reboot|test_shared_pinned_and_unpinned_guest|^whitebox_tempest_plugin.api.compute.test_cpu_pinning.EmulatorThreadTest'
devstack_local_conf:
post-config:
$NOVA_CONF:
@ -118,7 +118,7 @@
Runs the CPU pinning tests on single-NUMA, non-SMT, nested virt VMs. Uses
[DEFAULT]vcpu_pin_set to configure host CPUs for pinning.
vars:
tempest_test_regex: 'test_live_migrate_and_reboot'
tempest_test_regex: 'test_live_migrate_and_reboot|^whitebox_tempest_plugin.api.compute.test_cpu_pinning.EmulatorThreadTest'
devstack_local_conf:
post-config:
$NOVA_CONF:

View File

@ -31,6 +31,8 @@ function configure {
iniset $TEMPEST_CONFIG whitebox-database host $DATABASE_HOST
iniset $TEMPEST_CONFIG whitebox-hardware cpu_topology "$WHITEBOX_CPU_TOPOLOGY"
iniset $TEMPEST_CONFIG whitebox-hardware dedicated_cpus_per_numa "$WHITEBOX_DEDICATED_CPUS_PER_NUMA"
iniset $TEMPEST_CONFIG whitebox-hardware shared_cpus_per_numa "$WHITEBOX_SHARED_CPUS_PER_NUMA"
iniset $TEMPEST_CONFIG compute-feature-enabled virtio_rng "$COMPUTE_FEATURE_VIRTIO_RNG"
iniset $TEMPEST_CONFIG compute-feature-enabled rbd_download "$COMPUTE_FEATURE_RBD_DOWNLOAD"

View File

@ -15,6 +15,8 @@ WHITEBOX_LIBVIRT_MASK_COMMAND=${WHITEBOX_LIBVIRT_MASK_COMMAND:-'systemctl mask l
WHITEBOX_LIBVIRT_UNMASK_COMMAND=${WHITEBOX_LIBVIRT_UNMASK_COMMAND:-'systemctl unmask libvirtd'}
WHITEBOX_CPU_TOPOLOGY=${WHITEBOX_CPU_TOPOLOGY:-''}
WHITEBOX_DEDICATED_CPUS_PER_NUMA=${WHITEBOX_DEDICATED_CPUS_PER_NUMA:-4}
WHITEBOX_SHARED_CPUS_PER_NUMA=${WHITEBOX_SHARED_CPUS_PER_NUMA:-2}
COMPUTE_FEATURE_VIRTIO_RNG=${COMPUTE_FEATURE_VIRTIO_RNG:-'True'}
COMPUTE_FEATURE_RBD_DOWNLOAD=${COMPUTE_FEATURE_RBD_DOWNLOAD:-'False'}

View File

@ -29,6 +29,23 @@ class NUMAHelperMixin(object):
pinset |= hardware.parse_cpu_spec(pin.get('cpuset'))
return pinset
def get_server_emulator_threads(self, server_id):
"""Get the host CPU numbers to which the server's emulator threads are
pinned.
:param server_id: The instance UUID to look up.
:return emulator_threads: A set of host CPU numbers.
"""
root = self.get_server_xml(server_id)
emulatorpins = root.findall('./cputune/emulatorpin')
emulator_threads = set()
for pin in emulatorpins:
emulator_threads |= hardware.parse_cpu_spec(
pin.get('cpuset'))
return emulator_threads
def get_host_pcpus_for_guest_vcpu(self, server_id, instance_cpu_id):
"""Search the xml vcpu element of the provided instance for its cpuset.
Convert cpuset found into a set of integers.

View File

@ -370,22 +370,20 @@ class CPUThreadPolicyTest(BasePinningTest):
"host have HyperThreading enabled?")
class EmulatorExtraCPUTest(BasePinningTest):
class EmulatorThreadTest(BasePinningTest, numa_helper.NUMAHelperMixin):
vcpus = 1
min_microversion = '2.74'
def setUp(self):
super(EmulatorExtraCPUTest, self).setUp()
# Iterate over cpu_topology and find the NUMA Node with the most
# pCPUs and set that as the NUMA Node to use throughout the test
largest_numa_node = max(CONF.whitebox_hardware.cpu_topology.items(),
key=lambda k: len(k[1]))
self.numa_to_use = largest_numa_node[0]
super(EmulatorThreadTest, self).setUp()
self.dedicated_cpus_per_numa = \
CONF.whitebox_hardware.dedicated_cpus_per_numa
self.shared_cpus_per_numa = \
CONF.whitebox_hardware.shared_cpus_per_numa
@classmethod
def skip_checks(cls):
super(EmulatorExtraCPUTest, cls).skip_checks()
super(EmulatorThreadTest, cls).skip_checks()
if not utils.is_extension_enabled('OS-FLV-EXT-DATA', 'compute'):
msg = "OS-FLV-EXT-DATA extension not enabled."
raise cls.skipException(msg)
@ -394,7 +392,7 @@ class EmulatorExtraCPUTest(BasePinningTest):
raise cls.skipException(msg)
def create_flavor(self, threads_policy, vcpus):
flavor = super(EmulatorExtraCPUTest,
flavor = super(EmulatorThreadTest,
self).create_flavor(vcpus=vcpus, disk=1)
specs = {
@ -404,133 +402,63 @@ class EmulatorExtraCPUTest(BasePinningTest):
self.flavors_client.set_flavor_extra_spec(flavor['id'], **specs)
return flavor
def get_server_emulator_threads(self, server_id):
"""Get the host CPU numbers to which the server's emulator threads are
pinned.
:param server_id: The instance UUID to look up.
:return emulator_threads: A set of host CPU numbers.
"""
root = self.get_server_xml(server_id)
emulatorpins = root.findall('./cputune/emulatorpin')
emulator_threads = set()
for pin in emulatorpins:
emulator_threads |= hardware.parse_cpu_spec(
pin.get('cpuset'))
return emulator_threads
def policy_share_cpu_shared_set(self, section, pin_set_mode):
def test_policy_share_cpu_shared_set(self):
"""With policy set to share and cpu_share_set set, emulator threads
should be pinned to cpu_share_set.
:param section: The nova.conf section to apply the pin_set
configuration
:param pin_set_mode: Which pCPU list mode to use when exposing
dedicated pCPU's to the guest, either vcpu_pin_set or cpu_dedicated_set
:param single_shared_cpu bool, wheter to use a single pCPU for the
shared set or a range of pCPUs for the shared_set range.
"""
# NOTE: this scenario is based around upstream BP:
# https://blueprints.launchpad.net/nova/+spec/overhead-pin-set and
# downstream BZ https://bugzilla.redhat.com/show_bug.cgi?id=1468004#c0
# which allows for multiple guest's emulator threads to share the same
# cpu_shared_set range
if len(CONF.whitebox_hardware.cpu_topology[self.numa_to_use]) < 3:
raise self.skipException('Test requires NUMA Node with 3 or more '
'CPUs to run')
dedicated_set = hardware.format_cpu_spec(
CONF.whitebox_hardware.cpu_topology[self.numa_to_use][:2])
if self.shared_cpus_per_numa == 0:
raise self.skipException('Test requires cpu_shared_set to be '
'configured on the compute hosts')
cpu_shared_set_str = hardware.format_cpu_spec(
CONF.whitebox_hardware.cpu_topology[self.numa_to_use][2:])
hostname = self.list_compute_hosts()[0]
host_sm = clients.NovaServiceManager(
hostname, 'nova-compute', self.os_admin.services_client)
# Update the compute node's cpu_shared_set to cpu_shared_set_str and
# dedicated CPUs to the range found for dedicated_set
with host_sm.config_options((section, pin_set_mode, dedicated_set),
('compute', 'cpu_shared_set',
cpu_shared_set_str)):
# Create a flavor using the shared threads_policy and two instances
# on the same host
# Create a flavor using the shared threads_policy and create an
# instance
flavor = self.create_flavor(threads_policy='share',
vcpus=self.vcpus)
server_a = self.create_test_server(
clients=self.os_admin,
flavor=flavor['id'],
host=hostname
)
server_b = self.create_test_server(
clients=self.os_admin,
flavor=flavor['id'],
host=hostname
)
vcpus=self.shared_cpus_per_numa)
# Gather the emulator threads from both servers
emulator_threads_a = \
self.get_server_emulator_threads(server_a['id'])
emulator_threads_b = \
self.get_server_emulator_threads(server_b['id'])
server = self.create_test_server(flavor=flavor['id'])
# Confirm the emulator threads from server's A and B are both equal
# to cpu_shared_set
cpu_shared_set = hardware.parse_cpu_spec(cpu_shared_set_str)
# Determine the compute host the guest was scheduled to and gather
# the cpu shared set from the host
host = self.get_host_for_server(server['id'])
host_sm = clients.NovaServiceManager(host, 'nova-compute',
self.os_admin.services_client)
cpu_shared_set = host_sm.get_cpu_shared_set()
# Gather the emulator threads from the server
emulator_threads = \
self.get_server_emulator_threads(server['id'])
# Confirm the emulator threads from the server is equal to the host's
# cpu_shared_set
self.assertEqual(
emulator_threads_a, cpu_shared_set,
'Emulator threads for server A %s are not the same as CPU set '
'%s' % (emulator_threads_a, cpu_shared_set))
cpu_shared_set, emulator_threads,
'Emulator threads for server %s is not the same as CPU set '
'%s' % (emulator_threads, cpu_shared_set))
self.assertEqual(
emulator_threads_b, cpu_shared_set,
'Emulator threads for server B %s are not the same as CPU set '
'%s' % (emulator_threads_b, cpu_shared_set))
self.delete_server(server['id'])
self.delete_server(server_a['id'])
self.delete_server(server_b['id'])
def policy_share_cpu_shared_unset(self, section, pin_set_mode):
def test_policy_share_cpu_shared_unset(self):
"""With policy set to share and cpu_share_set unset, emulator threads
should float over the instance's pCPUs.
:param section: The nova.conf section to apply the pin_set
configuration
:param pin_set_mode: Which pCPU list mode to use when exposing
dedicated pCPU's to the guest, either vcpu_pin_set or cpu_dedicated_set
"""
if len(CONF.whitebox_hardware.cpu_topology[self.numa_to_use]) < 2:
raise self.skipException('Test requires NUMA Node with 2 or more '
'CPUs to run')
if self.dedicated_cpus_per_numa < 2:
raise self.skipException(
'Need at least 2 or more pCPUs per NUMA allocated to the '
'cpu_dedicated_set of the compute host')
dedicated_set = hardware.format_cpu_spec(
CONF.whitebox_hardware.cpu_topology[self.numa_to_use][:2])
hostname = self.list_compute_hosts()[0]
host_sm = clients.NovaServiceManager(
hostname, 'nova-compute', self.os_admin.services_client)
# Update the compute node's cpu_shared_set to None and dedicated CPUs
# to the range found for dedicated_set
with host_sm.config_options((section, pin_set_mode, dedicated_set),
('compute', 'cpu_shared_set', None)):
with self.config_all_computes(('compute', 'cpu_shared_set', None)):
# Create a flavor using the shared threads_policy and two instances
# on the same host
flavor = self.create_flavor(threads_policy='share',
vcpus=self.vcpus)
server_a = self.create_test_server(
clients=self.os_admin,
flavor=flavor['id'],
host=hostname
)
flavor = self.create_flavor(
threads_policy='share',
vcpus=int(self.dedicated_cpus_per_numa / 2))
server_a = self.create_test_server(flavor=flavor['id'])
server_b = self.create_test_server(
clients=self.os_admin,
flavor=flavor['id'],
host=hostname
scheduler_hints={'same_host': server_a['id']}
)
# Gather the emulator threads from server A and B. Then gather the
@ -555,212 +483,84 @@ class EmulatorExtraCPUTest(BasePinningTest):
'Threads %s not the same as CPU pins %s' % (emulator_threads_b,
cpu_pins_b))
# Confirm the pinned cpus from server a to do no intersect with
# server b
# Confirm the pinned pCPUs from server A do not intersect with
# the pinned pCPUs of server B
self.assertTrue(
cpu_pins_a.isdisjoint(cpu_pins_b),
'Different server pins overlap: %s and %s' % (cpu_pins_a,
cpu_pins_b))
self.delete_server(server_a['id'])
self.delete_server(server_b['id'])
def policy_isolate(self, section, pin_set_mode):
def test_policy_isolate(self):
"""With policy isolate, cpu_shared_set is ignored, and emulator threads
should be pinned to a pCPU distinct from the instance's pCPUs.
:param section: The nova.conf section to apply the pin_set
configuration
:param pin_set_mode: Which pCPU list mode to use when exposing
dedicated pCPU's to the guest, either vcpu_pin_set or cpu_dedicated_set
"""
if len(CONF.whitebox_hardware.cpu_topology[self.numa_to_use]) < 4:
raise self.skipException('Test requires NUMA Node with 4 or more '
'CPUs to run')
dedicated_set = \
set(CONF.whitebox_hardware.cpu_topology[self.numa_to_use])
dedicated_set_str = hardware.format_cpu_spec(dedicated_set)
if self.dedicated_cpus_per_numa < 2:
raise self.skipException(
'Need at least 2 or more pCPUs per NUMA allocated to the '
'cpu_dedicated_set of the compute host')
hostname = self.list_compute_hosts()[0]
host_sm = clients.NovaServiceManager(
hostname, 'nova-compute', self.os_admin.services_client)
# Update the compute node's cpu_shared_set to None and dedicated CPUs
# to the range found for dedicated_set_str
with host_sm.config_options((section, pin_set_mode, dedicated_set_str),
('compute', 'cpu_shared_set', None)):
# Create a flavor using the isolate threads_policy and two
# instances on the same host
# Create a flavor using the isolate threads_policy and then launch
# an instance with the flavor
flavor = self.create_flavor(threads_policy='isolate',
vcpus=self.vcpus)
server_a = self.create_test_server(
clients=self.os_admin,
flavor=flavor['id'],
host=hostname
)
server_b = self.create_test_server(
clients=self.os_admin,
flavor=flavor['id'],
host=hostname
)
vcpus=(self.dedicated_cpus_per_numa - 1))
# Gather the emulator threads from server A and B. Then gather the
# pinned PCPUs from server A and B.
emulator_threads_a = \
self.get_server_emulator_threads(server_a['id'])
emulator_threads_b = \
self.get_server_emulator_threads(server_b['id'])
server = self.create_test_server(flavor=flavor['id'])
cpu_pins_a = self.get_pinning_as_set(server_a['id'])
cpu_pins_b = self.get_pinning_as_set(server_b['id'])
# Gather the emulator threads and the pinned PCPUs from the guest
emulator_threads = \
self.get_server_emulator_threads(server['id'])
cpu_pins = self.get_pinning_as_set(server['id'])
# Check that every value gathered is a subset of the
# cpu_dedicate_set configured for the host
for pin_set in [emulator_threads_a, emulator_threads_b, cpu_pins_a,
cpu_pins_b]:
self.assertTrue(
pin_set.issubset(dedicated_set), 'Pin set value %s is not '
'a subset of %s' % (pin_set, dedicated_set))
# Determine the compute host the guest was scheduled to and gather
# the cpu dedicated set from the host
host = self.get_host_for_server(server['id'])
host_sm = clients.NovaServiceManager(host, 'nova-compute',
self.os_admin.services_client)
cpu_dedicated_set = host_sm.get_cpu_dedicated_set()
# Validate that all emulator thread and guest pinned CPUs are all
# disjointed from each other
# Confirm the pinned cpus from the guest are part of the dedicated
# range of the compute host it is scheduled to
self.assertTrue(
cpu_pins_a.isdisjoint(emulator_threads_a),
'Threads %s overlap with CPUs %s' % (emulator_threads_a,
cpu_pins_a))
self.assertTrue(
cpu_pins_b.isdisjoint(emulator_threads_b),
'Threads %s overlap with CPUs %s' % (emulator_threads_b,
cpu_pins_b))
self.assertTrue(
cpu_pins_a.isdisjoint(cpu_pins_b),
'Different server pins overlap: %s and %s' % (cpu_pins_a,
cpu_pins_b))
self.assertTrue(
emulator_threads_a.isdisjoint(emulator_threads_b),
'Different threads overlap: %s and %s' % (emulator_threads_a,
emulator_threads_b))
self.delete_server(server_a['id'])
self.delete_server(server_b['id'])
cpu_pins.issubset(cpu_dedicated_set), 'Pin set value %s is not '
'a subset of %s' % (cpu_pins, cpu_dedicated_set))
def emulator_no_extra_cpu(self, section, pin_set_mode):
# Validate the emulator thread is disjoined from the pinned CPUs of the
# guest.
self.assertTrue(
cpu_pins.isdisjoint(emulator_threads),
'Threads %s overlap with CPUs %s' % (emulator_threads,
cpu_pins))
# Confirm the emulator thread is a subset of the compute host's cpu
# dedicated set
self.assertTrue(
emulator_threads.issubset(cpu_dedicated_set), 'Emulator thread '
'value %s is not a subset of cpu dedicated set %s' %
(emulator_threads, cpu_dedicated_set))
self.delete_server(server['id'])
def test_emulator_no_extra_cpu(self):
"""Create a flavor that consumes all available pCPUs for the guest.
The flavor should also be set to isolate emulator pinning. Instance
should fail to build, since there are no distinct pCPUs available for
the emulator thread.
:param pin_set_mode: Which pCPU list mode to use when exposing
dedicated pCPU's to the guest, either vcpu_pin_set or cpu_dedicated_set
"""
if len(CONF.whitebox_hardware.cpu_topology[self.numa_to_use]) < 2:
raise self.skipException('Test requires NUMA Node with 2 or more '
'CPUs to run')
dedicated_set = hardware.format_cpu_spec(
CONF.whitebox_hardware.cpu_topology[self.numa_to_use][:2])
hostname = self.list_compute_hosts()[0]
host_sm = clients.NovaServiceManager(
hostname, 'nova-compute', self.os_admin.services_client)
with host_sm.config_options((section, pin_set_mode, dedicated_set),
('compute', 'cpu_shared_set', None)):
# Create a dedicated flavor with a vcpu size equal to the number
# of available pCPUs in the dedicated set. With threads_policy
# being set to isolate, the build should fail since no more
# pCPUs will be available.
flavor = self.create_flavor(threads_policy='isolate',
vcpus=len(dedicated_set))
vcpus=self.dedicated_cpus_per_numa)
# Confirm the instance cannot be built
self.assertRaises(BuildErrorException,
self.create_test_server,
clients=self.os_admin,
flavor=flavor['id'],
host=hostname)
class VCPUPinSetEmulatorThreads(EmulatorExtraCPUTest):
max_microversion = '2.79'
pin_set_mode = 'vcpu_pin_set'
pin_section = 'DEFAULT'
def test_policy_share_cpu_shared_set(self):
"""With policy set to share and cpu_share_set set, emulator threads
should be pinned to cpu_share_set.
"""
super(VCPUPinSetEmulatorThreads,
self).policy_share_cpu_shared_set(
section=self.pin_section, pin_set_mode=self.pin_set_mode)
def test_policy_share_cpu_shared_unset(self):
"""With policy set to share and cpu_share_set unset, emulator threads
should float over the instance's pCPUs.
"""
super(VCPUPinSetEmulatorThreads,
self).policy_share_cpu_shared_unset(
section=self.pin_section, pin_set_mode=self.pin_set_mode)
def test_policy_isolate(self):
"""With policy isolate, cpu_shared_set is ignored, and emulator threads
sould be pinned to a pCPU distinct from the instance's pCPUs. pCPU's
are exposed to the guest via vcpu_pin_set.
"""
super(VCPUPinSetEmulatorThreads,
self).policy_isolate(
section=self.pin_section, pin_set_mode=self.pin_set_mode)
def test_emulator_no_extra_cpu(self):
"""With policy isolate and an instance's vCPU's consuming all available
pCPU's from the vcpu_pin_set, build should fail since there are no
pCPU's available for the emulator thread
"""
super(VCPUPinSetEmulatorThreads,
self).emulator_no_extra_cpu(
section=self.pin_section, pin_set_mode=self.pin_set_mode)
class CPUDedicatedEmulatorThreads(EmulatorExtraCPUTest):
compute_min_microversion = '2.79'
compute_max_microversion = 'latest'
pin_set_mode = 'cpu_dedicated_set'
pin_section = 'compute'
def test_policy_share_cpu_shared_set(self):
"""With policy set to share and cpu_share_set set, emulator threads
should be pinned to cpu_share_set.
"""
super(CPUDedicatedEmulatorThreads,
self).policy_share_cpu_shared_set(
section=self.pin_section, pin_set_mode=self.pin_set_mode)
def test_policy_share_cpu_shared_unset(self):
"""With policy set to share and cpu_share_set unset, emulator threads
should float over the instance's pCPUs.
"""
super(CPUDedicatedEmulatorThreads,
self).policy_share_cpu_shared_unset(
section=self.pin_section, pin_set_mode=self.pin_set_mode)
def test_policy_isolate(self):
"""With policy isolate, cpu_shared_set is ignored, and emulator threads
sould be pinned to a pCPU distinct from the instance's pCPUs. pCPU's
are exposed to the guest via cpu_dedicated_set.
"""
super(CPUDedicatedEmulatorThreads,
self).policy_isolate(
section=self.pin_section, pin_set_mode=self.pin_set_mode)
def test_emulator_no_extra_cpu(self):
"""With policy isolate and an instance's vCPU's consuming all available
pCPU's from the cpu_dedicated_set, build should fail since there are no
pCPU's available for the emulator thread
"""
super(CPUDedicatedEmulatorThreads,
self).emulator_no_extra_cpu(
section=self.pin_section, pin_set_mode=self.pin_set_mode)
flavor=flavor['id'])
class NUMALiveMigrationBase(BasePinningTest):

View File

@ -225,6 +225,14 @@ hardware_opts = [
'<List of CPUs in that node>. For example, if NUMA node 0 has '
'CPUs 0 and 1, and NUMA node 1 has CPUs 2 and 3, the value to '
'set would be `0: [0,1], 1: [2, 3]`.'),
cfg.IntOpt(
'dedicated_cpus_per_numa',
default=0,
help='Number of pCPUs allocated for cpu_dedicated_set per NUMA'),
cfg.IntOpt(
'shared_cpus_per_numa',
default=0,
help='Number of pCPUs allocated for cpu_shared_set per NUMA'),
cfg.StrOpt(
'sriov_physnet',
default=None,