From 6258ec1e7c26d7f0de352f3d5681023c29f885d8 Mon Sep 17 00:00:00 2001 From: Federico Ressi Date: Thu, 9 Sep 2021 15:08:57 +0200 Subject: [PATCH] Allow selecting nodes by group name regular expression Change-Id: Icf1dd91a5c1d3052a258b42fa4460533a251d448 --- tobiko/openstack/topology/_topology.py | 65 +++++++++++++++---- .../functional/openstack/test_topology.py | 43 +++++++++++- 2 files changed, 94 insertions(+), 14 deletions(-) diff --git a/tobiko/openstack/topology/_topology.py b/tobiko/openstack/topology/_topology.py index c740acee6..5584fda71 100644 --- a/tobiko/openstack/topology/_topology.py +++ b/tobiko/openstack/topology/_topology.py @@ -15,7 +15,7 @@ from __future__ import absolute_import import collections import re -import typing # noqa +import typing import weakref import netaddr @@ -40,21 +40,31 @@ from tobiko.openstack.topology import _exception LOG = log.getLogger(__name__) -def list_openstack_nodes(topology=None, group=None, hostnames=None, **kwargs): +PatternType = type(re.compile(r'.*')) +OpenstackGroupNameType = typing.Union[str, typing.Pattern] +OpenstackGroupNamesType = typing.Union[OpenstackGroupNameType, typing.Iterable[ + OpenstackGroupNameType]] + + +def list_openstack_nodes(topology: 'OpenStackTopology' = None, + group: OpenstackGroupNamesType = None, + hostnames=None, **kwargs): topology = topology or get_openstack_topology() if group is None: nodes = topology.nodes elif isinstance(group, str): nodes = topology.get_group(group=group) + elif isinstance(group, PatternType): + nodes = topology.get_groups(groups=[group]) else: + assert isinstance(group, collections.Iterable) nodes = topology.get_groups(groups=group) if hostnames: names = {node_name_from_hostname(hostname) for hostname in hostnames} - nodes = [node - for node in nodes - if node.name in names] + nodes = nodes.select(lambda node: node.name in names) + if kwargs: nodes = nodes.with_attributes(**kwargs) return nodes @@ -412,18 +422,29 @@ class OpenStackTopology(tobiko.SharedFixture): def create_group() -> tobiko.Selection[OpenStackTopologyNode]: return tobiko.Selection() - def get_group(self, group) -> tobiko.Selection[OpenStackTopologyNode]: + def get_group(self, group: str) \ + -> tobiko.Selection[OpenStackTopologyNode]: + tobiko.check_valid_type(group, str) try: - return self._groups[group] + return tobiko.Selection(self._groups[group]) except KeyError as ex: raise _exception.NoSuchOpenStackTopologyNodeGroup( group=group) from ex - def get_groups(self, groups) -> tobiko.Selection[OpenStackTopologyNode]: - nodes: tobiko.Selection[OpenStackTopologyNode] = tobiko.Selection() - for group in groups: - nodes.extend(self.get_group(group)) - return nodes + def list_group_names(self, + *matchers: 'MatchStringType') \ + -> typing.List[str]: + group_names: typing.List[str] = list(self._groups.keys()) + if matchers and group_names: + group_names = match_strings(group_names, *matchers) + return group_names + + def get_groups(self, groups: typing.Iterable['MatchStringType']) -> \ + tobiko.Selection[OpenStackTopologyNode]: + node_names: typing.Set[str] = set() + for group in self.list_group_names(*groups): + node_names.update(node.name for node in self.get_group(group)) + return self.nodes.select(lambda node: node.name in node_names) @property def groups(self) -> typing.List[str]: @@ -600,3 +621,23 @@ def skip_unless_osp_version(version, higher=False, lower=False): skip_msg = "OSP version doesn't match the requirement" return tobiko.skip_unless(skip_msg, verify_osp_version, version, higher, lower) + + +MatchStringType = typing.Union[str, typing.Pattern] + + +def match_strings(strings: typing.Iterable[str], + *matchers: MatchStringType) -> \ + typing.List[str]: + matching: typing.List[str] = [] + for matcher in matchers: + tobiko.check_valid_type(matcher, str, PatternType) + if isinstance(matcher, str): + if matcher in strings: + matching.append(matcher) + else: + assert isinstance(matcher, PatternType) + for string in strings: + if matcher.match(string): + matching.append(string) + return matching diff --git a/tobiko/tests/functional/openstack/test_topology.py b/tobiko/tests/functional/openstack/test_topology.py index 562edd805..e7ec27df8 100644 --- a/tobiko/tests/functional/openstack/test_topology.py +++ b/tobiko/tests/functional/openstack/test_topology.py @@ -15,7 +15,9 @@ # under the License. from __future__ import absolute_import +import collections import random +import re import testtools @@ -27,6 +29,9 @@ from tobiko.shell import ping from tobiko.shell import sh +PatternType = type(re.compile(r'')) + + @keystone.skip_unless_has_keystone_credentials() class OpenStackTopologyTest(testtools.TestCase): @@ -74,9 +79,23 @@ class OpenStackTopologyTest(testtools.TestCase): nodes = topology.list_openstack_nodes( topology=self.topology, group=group, hostnames=hostnames) self.assertTrue(set(nodes).issubset(set(self.topology.nodes))) + self.assertEqual(len(set(nodes)), len(nodes), + f"Repeated node found: {nodes}") for node in nodes: - if group: + if isinstance(group, str): self.assertIn(group, node.groups) + elif isinstance(group, PatternType): + for actual_group in node.groups: + if group.match(actual_group): + break + else: + self.fail(f"Any node {node.name} group matches " + f"'{group}': {node.groups}") + elif isinstance(group, collections.Iterable): + matching_groups = set(group) & set(node.groups) + self.assertNotEqual(set(), matching_groups, + f"Any group of node {node.name} " + f"matches '{group}': {node.groups}") if hostnames: hostnames = [node_name_from_hostname(h) for h in hostnames] @@ -84,7 +103,27 @@ class OpenStackTopologyTest(testtools.TestCase): return nodes def test_list_openstack_topology_with_group(self): - self.test_list_openstack_topology(group='compute') + group = self.topology.groups[0] + expected_nodes = set(self.topology.get_group(group)) + actual_nodes = set(self.test_list_openstack_topology(group=group)) + self.assertEqual(expected_nodes, actual_nodes) + + def test_list_openstack_topology_with_group_pattern(self): + groups = list(self.topology.groups)[:2] + pattern = re.compile('|'.join(groups)) + expected_nodes = set() + for group in groups: + expected_nodes.update(self.topology.get_group(group)) + actual_nodes = set(self.test_list_openstack_topology(group=pattern)) + self.assertEqual(expected_nodes, actual_nodes) + + def test_list_openstack_topology_with_groups(self): + groups = list(self.topology.groups)[:2] + expected_nodes = set() + for group in groups: + expected_nodes.update(self.topology.get_group(group)) + actual_nodes = set(self.test_list_openstack_topology(group=groups)) + self.assertEqual(expected_nodes, actual_nodes) def test_list_openstack_topology_with_hostnames(self): expected_nodes = self.topology.nodes[0::2]