Allow selecting nodes by group name regular expression

Change-Id: Icf1dd91a5c1d3052a258b42fa4460533a251d448
This commit is contained in:
Federico Ressi 2021-09-09 15:08:57 +02:00
parent 81618412de
commit 6258ec1e7c
2 changed files with 94 additions and 14 deletions

View File

@ -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

View File

@ -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]