diff --git a/tobiko/openstack/topology/_topology.py b/tobiko/openstack/topology/_topology.py index 5b39dc1bc..17d550155 100644 --- a/tobiko/openstack/topology/_topology.py +++ b/tobiko/openstack/topology/_topology.py @@ -217,7 +217,7 @@ class OpenStackTopologyNode: def __init__(self, topology: 'OpenStackTopology', name: str, - ssh_client: ssh.SSHClientFixture, + ssh_client: typing.Optional[ssh.SSHClientFixture], addresses: typing.Iterable[netaddr.IPAddress], hostname: str): self._topology = weakref.ref(topology) @@ -474,8 +474,9 @@ class OpenStackTopology(tobiko.SharedFixture): addresses: typing.List[netaddr.IPAddress], hostname: str = None, ssh_client: ssh.SSHClientFixture = None, + create_ssh_client: bool = True, **create_params): - if ssh_client is None: + if ssh_client is None and create_ssh_client: ssh_client = self._ssh_connect(hostname=hostname, addresses=addresses) addresses.extend(self._list_addresses_from_host(ssh_client=ssh_client)) @@ -485,10 +486,12 @@ class OpenStackTopology(tobiko.SharedFixture): try: node = self._names[name] except KeyError: + ssh_login = ( + ssh_client.login if ssh_client else "No SSH Client configured") LOG.debug("Add topology node:\n" f" - name: {name}\n" f" - hostname: {hostname}\n" - f" - login: {ssh_client.login}\n" + f" - login: {ssh_login}\n" f" - addresses: {addresses}\n") self._names[name] = node = self.create_node(name=name, hostname=hostname, @@ -622,7 +625,11 @@ class OpenStackTopology(tobiko.SharedFixture): ip_version = self.config.conf.ip_version return ip_version and int(ip_version) or None - def _list_addresses_from_host(self, ssh_client: ssh.SSHClientFixture): + def _list_addresses_from_host( + self, + ssh_client: typing.Optional[ssh.SSHClientFixture]): + if not ssh_client: + return tobiko.Selection() return ip.list_ip_addresses(ssh_client=ssh_client, ip_version=self.ip_version, scope='global') diff --git a/tobiko/podified/_openshift.py b/tobiko/podified/_openshift.py index aea779e22..6bcfb9335 100644 --- a/tobiko/podified/_openshift.py +++ b/tobiko/podified/_openshift.py @@ -13,6 +13,7 @@ # under the License. from __future__ import absolute_import +import netaddr import openshift as oc from oslo_log import log @@ -23,6 +24,7 @@ LOG = log.getLogger(__name__) OSP_CONTROLPLANE = 'openstackcontrolplane' OSP_DP_NODESET = 'openstackdataplanenodeset' DP_SSH_SECRET_NAME = 'secret/dataplane-ansible-ssh-private-key-secret' +OCP_WORKERS = 'nodes' OVN_DP_SERVICE_NAME = 'ovn' COMPUTE_DP_SERVICE_NAMES = ['nova', 'nova-custom'] @@ -50,6 +52,19 @@ def _get_group(services): return EDPM_OTHER_GROUP +def _get_ocp_worker_hostname(worker): + for address in worker.get('status', {}).get('addresses', []): + if address.get('type') == 'Hostname': + return address['address'] + + +def _get_ocp_worker_addresses(worker): + return [ + netaddr.IPAddress(address['address']) for + address in worker.get('status', {}).get('addresses', []) + if address.get('type') != 'Hostname'] + + def has_podified_cp() -> bool: if not _is_oc_client_available(): LOG.debug("Openshift CLI client isn't installed.") @@ -98,4 +113,12 @@ def list_edpm_nodes(): def list_ocp_workers(): - pass + nodes_sel = oc.selector(OCP_WORKERS) + ocp_workers = [] + for node in nodes_sel.objects(): + node_dict = node.as_dict() + ocp_workers.append({ + 'hostname': _get_ocp_worker_hostname(node_dict), + 'addresses': _get_ocp_worker_addresses(node_dict) + }) + return ocp_workers diff --git a/tobiko/podified/_topology.py b/tobiko/podified/_topology.py index 63f7f5110..20fe4ac23 100644 --- a/tobiko/podified/_topology.py +++ b/tobiko/podified/_topology.py @@ -41,6 +41,8 @@ COMPUTE_GROUPS = [ _openshift.EDPM_OTHER_GROUP ] ALL_COMPUTES_GROUP_NAME = 'compute' +OCP_WORKER = 'ocp_worker' +EDPM_NODE = 'edpm_node' class PodifiedTopology(rhosp.RhospTopology): @@ -65,6 +67,10 @@ class PodifiedTopology(rhosp.RhospTopology): neutron.FRR: 'frr' } + def __init__(self): + super(PodifiedTopology, self).__init__() + self.ocp_workers = {} + def add_node(self, hostname: typing.Optional[str] = None, address: typing.Optional[str] = None, @@ -89,10 +95,13 @@ class PodifiedTopology(rhosp.RhospTopology): return node def create_node(self, name, ssh_client, **kwargs): - return EdpmNode(topology=self, - name=name, - ssh_client=ssh_client, - **kwargs) + node_type = kwargs.pop('node_type') + if node_type == OCP_WORKER: + return OcpWorkerNode(topology=self, name=name, ssh_client=None, + **kwargs) + else: + return EdpmNode(topology=self, name=name, ssh_client=ssh_client, + **kwargs) def discover_nodes(self): self.discover_ssh_proxy_jump_node() @@ -103,8 +112,23 @@ class PodifiedTopology(rhosp.RhospTopology): pass def discover_ocp_worker_nodes(self): - # TODO(slaweq): discover OCP nodes where OpenStack CP is running - pass + # NOTE(slaweq): For now this will only discover nodes but there will be + # no ssh_client created to ssh to those nodes. Getting + # ssh_client to those nodes may be implemented in the future if that + # will be needed, but this may be hard e.g. for the CRC environments as + # in that case internal OCP worker's IP address is not accessible from + # outside at all + for worker_data in _openshift.list_ocp_workers(): + node = self._add_node( + addresses=worker_data['addresses'], + hostname=worker_data['hostname'], + ssh_client=None, + create_ssh_client=False, + node_type=OCP_WORKER) + group_nodes = self.add_group(group='controller') + if node not in group_nodes: + group_nodes.append(node) + node.add_group(group='controller') def discover_edpm_nodes(self): for node in _openshift.list_edpm_nodes(): @@ -115,7 +139,8 @@ class PodifiedTopology(rhosp.RhospTopology): ssh_client = _edpm.edpm_ssh_client(host_config=host_config) node = self.add_node(address=host_config.host, group=group, - ssh_client=ssh_client) + ssh_client=ssh_client, + node_type=EDPM_NODE) assert isinstance(node, EdpmNode) @@ -128,5 +153,9 @@ class EdpmNode(rhosp.RhospNode): LOG.debug(f"Ensuring EDPM node {self.name} power is off...") +class OcpWorkerNode(rhosp.RhospNode): + pass + + def setup_podified_topology(): topology.set_default_openstack_topology_class(PodifiedTopology) diff --git a/tobiko/tests/faults/neutron/test_agents.py b/tobiko/tests/faults/neutron/test_agents.py index 261bd2114..9cf70c2af 100644 --- a/tobiko/tests/faults/neutron/test_agents.py +++ b/tobiko/tests/faults/neutron/test_agents.py @@ -656,7 +656,14 @@ class OvnControllerTest(BaseAgentTest): self.skipTest(f"Missing container(s): '{self.container_name}'") for host in hosts: - ssh_client = topology.get_openstack_node(hostname=host).ssh_client + node = topology.get_openstack_node(hostname=host) + if not node.ssh_client: + LOG.debug(f'Host {host} is probably an OCP worker in ' + f'the Podified environment. SSH to that kind of ' + f'nodes is currently not supported.') + continue + + ssh_client = node.ssh_client pid = self._get_ovn_controller_pid(ssh_client) self.assertIsNotNone(pid) LOG.debug(f'Killing process {pid} from container ' diff --git a/tobiko/tests/functional/podified/test_topology.py b/tobiko/tests/functional/podified/test_topology.py index ac4ab5c7a..52575d6eb 100644 --- a/tobiko/tests/functional/podified/test_topology.py +++ b/tobiko/tests/functional/podified/test_topology.py @@ -16,6 +16,8 @@ from __future__ import absolute_import import tobiko +from tobiko.shell import ping +from tobiko.shell import sh from tobiko.tests.functional.openstack import test_topology from tobiko import podified @@ -27,6 +29,17 @@ class PodifiedTopologyTest(test_topology.OpenStackTopologyTest): def topology(self) -> podified.PodifiedTopology: return tobiko.setup_fixture(podified.PodifiedTopology) - def test_controller_group(self): - self.skipTest("Discovery of the OCP workers is " - "not implemented yet.") + def test_ping_node(self): + # NOTE(slaweq): in podified topology we expect connectivity only to the + # edpm nodes, not to the OCP workers + for node in self.topology.get_group("compute"): + ping.ping(node.public_ip, count=1, timeout=5.).assert_replied() + + def test_ssh_client(self): + # NOTE(slaweq): in podified topology we expect connectivity only to the + # edpm nodes, not to the OCP workers + for node in self.topology.get_group("compute"): + self.assertIsNotNone(node.ssh_client) + hostname = sh.ssh_hostname( + ssh_client=node.ssh_client).split('.')[0] + self.assertEqual(node.name, hostname)