James Parker 30c8018731 Test soft reboot after live-migration
Commit looks to add regression coverage for [1]. It introduces two tests
that follow the same procedure with the only differing feature being the
parameter utilized to define the available pinned CPUs on the compute
host. The test updates two compute hosts to use different cpu ranges for
cpu_dedicated_set, e.g. host0 uses [0-1] and host1 [2-3]. An instance is
created and its associated pinned CPUs are recorded. It is then migrated
to the other host and the updated pinned CPUs are compared against the
CPUs prior to migration, asserting they are no longer the same. Finally
the test soft reboots the instance and asserts that its pinned CPUs
remain the same.

Test introduces four classes to test_live_migration.py. First is
LiveMigrationAndReboot which handles all of the test logic. It's two
children, VCPUPinSetMigrateAndReboot and CPUDedicatedMigrateAndReboot,
provide test parameters necessary to execute the test logic with either
vcpu_pin_set or cpu_dedicated_set. Lastly it creates a new base class
for all tests, LiveMigrationBase, that allows for LiveMigrationTest and
LiveMigrationAndReboot to both inherit from.

The tests needs to leverage a lot of the helper functions found in
test_cpu_pinning.NUMALiveMigrationBase. To prevent duplication and since
these tests does not work with anything NUMA specific, the necessary
helper functions were moved to base.BaseWhiteboxComputeTest. This
includes get_all_cpus, get_pinning_as_set, and _get_cpu_set.

Lastly it moves parse_cpu_spec from compute.test_cpu_pinning to the
utils module.

[1] https://bugs.launchpad.net/nova/+bug/1890501

Change-Id: I0271894acd0689b947974c86910b3d8c41aa9d72
2020-10-05 14:43:26 -04:00

134 lines
4.5 KiB
Python

# Copyright 2020 Red Hat
#
# 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.
import six
from oslo_serialization import jsonutils
from tempest import config
from whitebox_tempest_plugin import exceptions
if six.PY2:
import contextlib2 as contextlib
else:
import contextlib
CONF = config.CONF
def normalize_json(json):
"""Normalizes a JSON dict for consistent equality tests. Sorts the keys,
and sorts any values that are lists.
"""
def sort_list_values(json):
for k, v in json.items():
if isinstance(v, list):
v.sort()
[sort_list_values(x) for x in v if isinstance(x, dict)]
elif isinstance(v, dict):
sort_list_values(v)
json = jsonutils.loads(jsonutils.dumps(json, sort_keys=True))
sort_list_values(json)
return json
@contextlib.contextmanager
def multicontext(*context_managers):
with contextlib.ExitStack() as stack:
yield [stack.enter_context(mgr) for mgr in context_managers]
def get_ctlplane_address(compute_hostname):
"""Return the appropriate host address depending on a deployment.
In TripleO deployments the Undercloud does not have DNS entries for
the compute hosts. This method checks if there are 'DNS' mappings of
the provided hostname to its control plane IP address and returns it.
For Devstack deployments, no such parameters will exist and the method
will just return compute_hostname
:param compute_hostname: str the compute hostname
:return: The address to be used to access the compute host. For
devstack deployments, this is compute_host itself. For TripleO, it needs
to be looked up in the configuration.
"""
if not CONF.whitebox.ctlplane_addresses:
return compute_hostname
if compute_hostname in CONF.whitebox.ctlplane_addresses:
return CONF.whitebox.ctlplane_addresses[compute_hostname]
raise exceptions.CtrlplaneAddressResolutionError(host=compute_hostname)
def parse_cpu_spec(spec):
"""Parse a CPU set specification.
NOTE(artom): This has been lifted from Nova with minor
exceptions-related adjustments.
Each element in the list is either a single CPU number, a range of
CPU numbers, or a caret followed by a CPU number to be excluded
from a previous range.
:param spec: cpu set string eg "1-4,^3,6"
:returns: a set of CPU indexes
"""
cpuset_ids = set()
cpuset_reject_ids = set()
for rule in spec.split(','):
rule = rule.strip()
# Handle multi ','
if len(rule) < 1:
continue
# Note the count limit in the .split() call
range_parts = rule.split('-', 1)
if len(range_parts) > 1:
reject = False
if range_parts[0] and range_parts[0][0] == '^':
reject = True
range_parts[0] = str(range_parts[0][1:])
# So, this was a range; start by converting the parts to ints
try:
start, end = [int(p.strip()) for p in range_parts]
except ValueError:
raise exceptions.InvalidCPUSpec(spec=spec)
# Make sure it's a valid range
if start > end:
raise exceptions.InvalidCPUSpec(spec=spec)
# Add available CPU ids to set
if not reject:
cpuset_ids |= set(range(start, end + 1))
else:
cpuset_reject_ids |= set(range(start, end + 1))
elif rule[0] == '^':
# Not a range, the rule is an exclusion rule; convert to int
try:
cpuset_reject_ids.add(int(rule[1:].strip()))
except ValueError:
raise exceptions.InvalidCPUSpec(spec=spec)
else:
# OK, a single CPU to include; convert to int
try:
cpuset_ids.add(int(rule))
except ValueError:
raise exceptions.InvalidCPUSpec(spec=spec)
# Use sets to handle the exclusion rules for us
cpuset_ids -= cpuset_reject_ids
return cpuset_ids