From e1105c97b789ad6ed63c93b1c08da68fbc2f7b57 Mon Sep 17 00:00:00 2001 From: Slawek Kaplonski Date: Fri, 17 Nov 2023 15:52:36 +0100 Subject: [PATCH] Add topology which represents Podified RHOSP deployment Topology class can now discover EDPM nodes based on the data from the OCP cluster. There are also basic functional tests for new Topology class implemented. Unfortunately those tests can't be run in the upstream CI as it required OCP (CRC at least) and OSP deployment on top of that OCP cluster. And this requires too much resources for the upstream CI infra. Related-Jira: #OSP-22166 Change-Id: I60385f52f01ca0dc4742ed105b14338c1317075e --- extra-requirements.txt | 1 + tobiko/config.py | 2 + tobiko/podified/__init__.py | 24 ++++ tobiko/podified/_edpm.py | 126 ++++++++++++++++++ tobiko/podified/_openshift.py | 82 ++++++++++++ tobiko/podified/_topology.py | 95 +++++++++++++ tobiko/podified/config.py | 23 ++++ tobiko/rhosp/__init__.py | 23 ++++ tobiko/rhosp/_topology.py | 117 ++++++++++++++++ tobiko/rhosp/_utils.py | 0 .../_rhosp.py => rhosp/_version_utils.py} | 0 tobiko/rhosp/config.py | 110 +++++++++++++++ tobiko/tests/faults/ha/cloud_disruptions.py | 8 +- tobiko/tests/faults/octavia/test_faults.py | 2 +- tobiko/tests/functional/podified/__init__.py | 0 .../functional/podified/test_topology.py | 32 +++++ tobiko/tripleo/__init__.py | 4 - tobiko/tripleo/_topology.py | 46 +------ tobiko/tripleo/_undercloud.py | 4 +- tobiko/tripleo/config.py | 83 ------------ 20 files changed, 647 insertions(+), 135 deletions(-) create mode 100644 tobiko/podified/__init__.py create mode 100644 tobiko/podified/_edpm.py create mode 100644 tobiko/podified/_openshift.py create mode 100644 tobiko/podified/_topology.py create mode 100644 tobiko/podified/config.py create mode 100644 tobiko/rhosp/__init__.py create mode 100644 tobiko/rhosp/_topology.py create mode 100644 tobiko/rhosp/_utils.py rename tobiko/{tripleo/_rhosp.py => rhosp/_version_utils.py} (100%) create mode 100644 tobiko/rhosp/config.py create mode 100644 tobiko/tests/functional/podified/__init__.py create mode 100644 tobiko/tests/functional/podified/test_topology.py diff --git a/extra-requirements.txt b/extra-requirements.txt index beeb4366c..002adde45 100644 --- a/extra-requirements.txt +++ b/extra-requirements.txt @@ -2,6 +2,7 @@ ansi2html # LGPLv3+ dpkt # BSD +openshift-client # Apache-2.0 pandas # BSD podman==4.7.0 # Apache-2.0 pytest-cov # MIT diff --git a/tobiko/config.py b/tobiko/config.py index 54aa86d66..9bb1ae0c0 100644 --- a/tobiko/config.py +++ b/tobiko/config.py @@ -42,6 +42,8 @@ CONFIG_MODULES = ['tobiko.common._case', 'tobiko.shell.iperf3.config', 'tobiko.shell.sh.config', 'tobiko.shiftstack.config', + 'tobiko.rhosp.config', + 'tobiko.podified.config', 'tobiko.tripleo.config'] diff --git a/tobiko/podified/__init__.py b/tobiko/podified/__init__.py new file mode 100644 index 000000000..dcf6656c5 --- /dev/null +++ b/tobiko/podified/__init__.py @@ -0,0 +1,24 @@ +# Copyright 2023 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. +from __future__ import absolute_import + +from tobiko.podified import _topology +from tobiko.podified import _openshift + + +PodifiedTopology = _topology.PodifiedTopology + +skip_if_not_podified = _topology.skip_if_not_podified + +get_dataplane_ssh_keypair = _openshift.get_dataplane_ssh_keypair diff --git a/tobiko/podified/_edpm.py b/tobiko/podified/_edpm.py new file mode 100644 index 000000000..ce282d0fe --- /dev/null +++ b/tobiko/podified/_edpm.py @@ -0,0 +1,126 @@ + +# Copyright 2023 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. +from __future__ import absolute_import + +import base64 +import io +import os +import socket + +import tobiko +from tobiko import config +from tobiko.podified import _openshift +from tobiko.shell import ssh + +CONF = config.CONF + + +def edpm_host_config(node=None, + ip_version: int = None, + key_filename: str = None): + node = node or {} + host_config = EdpmHostConfig( + host=node.get('host'), + hostname=node.get('hostname'), + ip_version=ip_version, + key_filename=key_filename, + port=node.get('port'), + username=node.get('username')) + return tobiko.setup_fixture(host_config) + + +def edpm_ssh_client(ip_version: int = None, + host_config=None, + node=None): + if host_config is None: + host_config = edpm_host_config(node=node, + ip_version=ip_version) + tobiko.check_valid_type(host_config.host, str) + return ssh.ssh_client(host=host_config.host, + **host_config.connect_parameters) + + +class EdpmSshKeyFileFixture(tobiko.SharedFixture): + + @property + def key_filename(self): + return tobiko.tobiko_config_path( + CONF.tobiko.podified.edpm_ssh_key_filename) + + def setup_fixture(self): + self.setup_key_file() + + def setup_key_file(self): + priv_key_filename = self.key_filename + pub_key_filename = priv_key_filename + ".pub" + key_dirname = os.path.dirname(priv_key_filename) + tobiko.makedirs(key_dirname, mode=0o700) + + private_key, public_key = _openshift.get_dataplane_ssh_keypair() + if private_key: + with io.open(priv_key_filename, 'wb') as fd: + fd.write(base64.b64decode(private_key)) + os.chmod(priv_key_filename, 0o600) + if public_key: + with io.open(pub_key_filename, 'wb') as fd: + fd.write(base64.b64decode(public_key)) + os.chmod(pub_key_filename, 0o600) + + +class EdpmHostConfig(tobiko.SharedFixture): + + key_file = tobiko.required_fixture(EdpmSshKeyFileFixture) + + def __init__(self, + host: str, + hostname: str = None, + ip_version: int = None, + key_filename: str = None, + port: int = None, + username: str = None, + **kwargs): + super(EdpmHostConfig, self).__init__() + self.host = host + self.hostname = self._get_hostname(hostname) + self.ip_version = ip_version + self.key_filename = key_filename + self.port = port + self.username = username + self._connect_parameters = ssh.gather_ssh_connect_parameters(**kwargs) + + def setup_fixture(self): + if self.port is None: + # TODO(slaweq): add config option + self.port = 22 + if self.username is None: + # TODO(slaweq): add config option + self.username = 'cloud-admin' + if self.key_filename is None: + self.key_filename = self.key_file.key_filename + + def _get_hostname(self, hostname): + try: + socket.gethostbyname(hostname) + return hostname + except socket.gaierror: + return self.host + + @property + def connect_parameters(self): + parameters = ssh.ssh_host_config( + host=str(self.hostname)).connect_parameters + parameters.update(ssh.gather_ssh_connect_parameters(self)) + parameters.update(self._connect_parameters) + return parameters diff --git a/tobiko/podified/_openshift.py b/tobiko/podified/_openshift.py new file mode 100644 index 000000000..8ba73be88 --- /dev/null +++ b/tobiko/podified/_openshift.py @@ -0,0 +1,82 @@ +# Copyright 2023 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. +from __future__ import absolute_import + +import openshift as oc +from oslo_log import log + +from tobiko.shell import sh + +LOG = log.getLogger(__name__) + +OSP_CONTROLPLANE = 'openstackcontrolplane' +OSP_DP_NODESET = 'openstackdataplanenodeset' +DP_SSH_SECRET_NAME = 'secret/dataplane-ansible-ssh-private-key-secret' + + +def _is_oc_client_available() -> bool: + try: + if sh.execute('which oc').exit_status == 0: + return True + except sh.ShellCommandFailed: + pass + return False + + +def has_podified_cp() -> bool: + if not _is_oc_client_available(): + LOG.debug("Openshift CLI client isn't installed.") + return False + try: + return bool(oc.selector(OSP_CONTROLPLANE).objects()) + except oc.OpenShiftPythonException: + return False + + +def get_dataplane_ssh_keypair(): + private_key = "" + public_key = "" + try: + secret_object = oc.selector(DP_SSH_SECRET_NAME).object() + private_key = secret_object.as_dict()['data']['ssh-privatekey'] + public_key = secret_object.as_dict()['data']['ssh-publickey'] + except oc.OpenShiftPythonException as err: + LOG.error("Error while trying to get Dataplane secret SSH Key: %s", + err) + return private_key, public_key + + +def list_edpm_nodes(): + nodes = [] + nodeset_sel = oc.selector(OSP_DP_NODESET) + for nodeset in nodeset_sel.objects(): + node_template = nodeset.as_dict()['spec']['nodeTemplate'] + nodeset_nodes = nodeset.as_dict()['spec']['nodes'] + for node in nodeset_nodes.values(): + node_dict = { + 'hostname': node.get('hostName'), + 'host': node['ansible']['ansibleHost'], + 'port': ( + node.get('ansible', {}).get('ansiblePort') or + node_template.get('ansible', {}).get('ansiblePort')), + 'username': ( + node.get('ansible', {}).get('ansibleUser') or + node_template.get('ansible', {}).get('ansibleUser')), + } + nodes.append(node_dict) + return nodes + + +def list_ocp_workers(): + pass diff --git a/tobiko/podified/_topology.py b/tobiko/podified/_topology.py new file mode 100644 index 000000000..6680f6c3f --- /dev/null +++ b/tobiko/podified/_topology.py @@ -0,0 +1,95 @@ +# Copyright 2023 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. +from __future__ import absolute_import + +from oslo_log import log + +import tobiko +from tobiko.openstack import neutron +from tobiko.openstack import topology +from tobiko import rhosp +from tobiko.podified import _edpm +from tobiko.podified import _openshift + +LOG = log.getLogger(__name__) + + +skip_if_not_podified = tobiko.skip_unless( + "Podified deployment not configured", _openshift.has_podified_cp +) + + +class PodifiedTopology(rhosp.RhospTopology): + + # NOTE(slaweq): those service names are only valid for the EDPM nodes + agent_to_service_name_mappings = { + neutron.DHCP_AGENT: 'tripleo_neutron_dhcp', + neutron.OVN_METADATA_AGENT: 'tripleo_ovn_metadata_agent', + neutron.NEUTRON_OVN_METADATA_AGENT: 'tripleo_ovn_metadata_agent', + neutron.OVN_CONTROLLER: 'tripleo_ovn_controller', + neutron.OVN_BGP_AGENT: 'tripleo_ovn_bgp_agent', + neutron.FRR: 'tripleo_frr' + } + + # NOTE(slaweq): those container names are only valid for the EDPM nodes + agent_to_container_name_mappings = { + neutron.DHCP_AGENT: 'neutron_dhcp', + neutron.OVN_METADATA_AGENT: 'ovn_metadata_agent', + neutron.NEUTRON_OVN_METADATA_AGENT: 'ovn_metadata_agent', + neutron.OVN_CONTROLLER: 'ovn_controller', + neutron.OVN_BGP_AGENT: 'ovn_bgp_agent', + neutron.FRR: 'frr' + } + + def create_node(self, name, ssh_client, **kwargs): + return EdpmNode(topology=self, + name=name, + ssh_client=ssh_client, + **kwargs) + + def discover_nodes(self): + self.discover_ssh_proxy_jump_node() + self.discover_ocp_worker_nodes() + self.discover_edpm_nodes() + + def discover_ssh_proxy_jump_node(self): + pass + + def discover_ocp_worker_nodes(self): + # TODO(slaweq): discover OCP nodes where OpenStack CP is running + pass + + def discover_edpm_nodes(self): + for node in _openshift.list_edpm_nodes(): + LOG.debug(f"Found EDPM node {node['hostname']} " + f"(IP: {node['host']})") + host_config = _edpm.edpm_host_config(node) + ssh_client = _edpm.edpm_ssh_client(host_config=host_config) + node = self.add_node(address=host_config.host, + group='compute', + ssh_client=ssh_client) + assert isinstance(node, EdpmNode) + + +class EdpmNode(rhosp.RhospNode): + + def power_on_node(self): + LOG.debug(f"Ensuring EDPM node {self.name} power is on...") + + def power_off_node(self): + LOG.debug(f"Ensuring EDPM node {self.name} power is off...") + + +def setup_podified_topology(): + topology.set_default_openstack_topology_class(PodifiedTopology) diff --git a/tobiko/podified/config.py b/tobiko/podified/config.py new file mode 100644 index 000000000..052eafe09 --- /dev/null +++ b/tobiko/podified/config.py @@ -0,0 +1,23 @@ +# Copyright 2023 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. +from __future__ import absolute_import + + +def setup_tobiko_config(conf): + # pylint: disable=unused-argument + from tobiko.podified import _openshift + from tobiko.podified import _topology + + if _openshift.has_podified_cp(): + _topology.setup_podified_topology() diff --git a/tobiko/rhosp/__init__.py b/tobiko/rhosp/__init__.py new file mode 100644 index 000000000..2e05fb878 --- /dev/null +++ b/tobiko/rhosp/__init__.py @@ -0,0 +1,23 @@ +# Copyright 2023 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. +from __future__ import absolute_import + +from tobiko.rhosp import _version_utils +from tobiko.rhosp import _topology + +RhospTopology = _topology.RhospTopology +RhospNode = _topology.RhospNode + +get_rhosp_release = _version_utils.get_rhosp_release +get_rhosp_version = _version_utils.get_rhosp_version diff --git a/tobiko/rhosp/_topology.py b/tobiko/rhosp/_topology.py new file mode 100644 index 000000000..93b314271 --- /dev/null +++ b/tobiko/rhosp/_topology.py @@ -0,0 +1,117 @@ +# Copyright 2023 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. +from __future__ import absolute_import + +import typing + +import netaddr +from oslo_log import log + +import tobiko +from tobiko.openstack import nova +from tobiko.openstack import topology +from tobiko.rhosp import _version_utils +from tobiko.shell import ssh + +LOG = log.getLogger(__name__) + + +class RhospTopology(topology.OpenStackTopology): + + """Base topology for Red Hat OpenStack deployments. + + This is base topology which represents common parts between Tripleo and + Podified deployments. + """ + + has_containers = True + + +class RhospNode(topology.OpenStackTopologyNode): + + """Base RHOSP Node + + This class represents common parts between Overcloud nodes and EDPM nodes + in Red Hat OpenStack deployments. + """ + + def __init__(self, + topology: topology.OpenStackTopology, + name: str, + ssh_client: ssh.SSHClientFixture, + addresses: typing.Iterable[netaddr.IPAddress], + hostname: str, + rhosp_version: tobiko.Version = None): + # pylint: disable=redefined-outer-name + super().__init__(topology=topology, + name=name, + ssh_client=ssh_client, + addresses=addresses, + hostname=hostname) + self._rhosp_version = rhosp_version + + @property + def rhosp_version(self) -> tobiko.Version: + if self._rhosp_version is None: + self._rhosp_version = self._get_rhosp_version() + return self._rhosp_version + + def _get_rhosp_version(self) -> tobiko.Version: + return _version_utils.get_rhosp_version(connection=self.connection) + + def power_on_node(self): + pass + + def power_off_node(self): + pass + + def reboot_node(self, reactivate_servers=True): + """Reboot node + + This method reboots a node and may start every Nova + server which is not in SHUTOFF status before restarting. + + :param reactivate_servers: whether or not to re-start the servers which + are hosted on the compute node after the reboot + """ + + running_servers: typing.List[nova.NovaServer] = [] + if reactivate_servers: + running_servers = self.list_running_servers() + LOG.debug(f'Servers to restart after reboot: {running_servers}') + + self.power_off_node() + self.power_on_node() + + if running_servers: + LOG.info(f'Restart servers after rebooting compute node ' + f'{self.name}...') + for server in running_servers: + nova.wait_for_server_status(server=server.id, + status='SHUTOFF') + LOG.debug(f'Re-activate server {server.name} with ID ' + f'{server.id}') + nova.activate_server(server=server) + LOG.debug(f'Server {server.name} with ID {server.id} has ' + f'been reactivated') + + def list_running_servers(self) -> typing.List[nova.NovaServer]: + running_servers = list() + for server in nova.list_servers(): + if server.status != 'SHUTOFF': + hypervisor_name = nova.get_server_hypervisor(server, + short=True) + if self.name == hypervisor_name: + running_servers.append(server) + return running_servers diff --git a/tobiko/rhosp/_utils.py b/tobiko/rhosp/_utils.py new file mode 100644 index 000000000..e69de29bb diff --git a/tobiko/tripleo/_rhosp.py b/tobiko/rhosp/_version_utils.py similarity index 100% rename from tobiko/tripleo/_rhosp.py rename to tobiko/rhosp/_version_utils.py diff --git a/tobiko/rhosp/config.py b/tobiko/rhosp/config.py new file mode 100644 index 000000000..db765019d --- /dev/null +++ b/tobiko/rhosp/config.py @@ -0,0 +1,110 @@ +# Copyright 2019 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. +from __future__ import absolute_import + +import itertools + +from oslo_config import cfg + + +TRIPLEO_GROUP_NAME = 'tripleo' +TRIPLEO_OPTIONS = [ + # Undercloud options + cfg.StrOpt('undercloud_ssh_hostname', + default='undercloud-0', + help="hostname or IP address to be used to connect to " + "undercloud host"), + cfg.IntOpt('undercloud_ssh_port', + default=None, + help="TCP port of SSH server on undercloud host"), + cfg.StrOpt('undercloud_ssh_username', + default='stack', + help="Username with access to stackrc and overcloudrc files"), + cfg.StrOpt('undercloud_ssh_key_filename', + default=None, + help="SSH key filename used to login to Undercloud node"), + cfg.ListOpt('undercloud_rcfile', + default=['~/stackrc'], + help="Undercloud RC filename"), + cfg.StrOpt('undercloud_cloud_name', + default='undercloud', + help='undercloud cloud name to be used for loading credentials ' + 'from the undercloud clouds files'), + cfg.StrOpt('undercloud_cacert_file', + default='/etc/pki/tls/certs/ca-bundle.trust.crt', + help='Path to cacert file that can be used to send https ' + 'request from the undercloud'), + + # TODO(slaweq): those options may be also applicable for edpm nodes so + # maybe we will need to rename them to use in both topologies + # Overcloud options + cfg.IntOpt('overcloud_ssh_port', + default=None, + help="TCP port of SSH server on overcloud hosts"), + cfg.StrOpt('overcloud_ssh_username', + default=None, + help="Default username used to connect to overcloud nodes"), + cfg.StrOpt('overcloud_ssh_key_filename', + default='~/.ssh/id_overcloud', + help="SSH key filename used to login to Overcloud nodes"), + cfg.ListOpt('overcloud_rcfile', + default=['~/overcloudrc', '~/qe-Cloud-0rc'], + help="Overcloud RC filenames"), + cfg.StrOpt('overcloud_cloud_name', + default='overcloud', + help='overcloud cloud name to be used for loading credentials ' + 'from the overcloud clouds files'), + cfg.IntOpt('overcloud_ip_version', + help=("Default IP address version to be used to connect to " + "overcloud nodes ")), + cfg.StrOpt('overcloud_network_name', + help="Name of network used to connect to overcloud nodes"), + cfg.DictOpt('overcloud_groups_dict', + help='Dictionary with the node groups corresponding to ' + 'different hostname prefixes', + default={'ctrl': 'controller', 'cmp': 'compute'}), + + # NOTE(slaweq): same here + # Other options + cfg.StrOpt('inventory_file', + default='.ansible/inventory/tripleo.yaml', + help="path to where to export tripleo inventory file"), + + cfg.BoolOpt('has_external_load_balancer', + default=False, + help="OSP env was done with an external load balancer"), + + cfg.BoolOpt('ceph_rgw', + default=False, + help="whether Ceph RGW is deployed"), +] + +PODIFIED_GROUP_NAME = "podified" +PODIFIED_OPTIONS = [ + cfg.StrOpt('edpm_ssh_key_filename', + default='~/.ssh/id_podified_edpm', + help="SSH key filename used to login to EDPM nodes"), +] + + +def register_tobiko_options(conf): + conf.register_opts(group=cfg.OptGroup(TRIPLEO_GROUP_NAME), + opts=TRIPLEO_OPTIONS) + conf.register_opts(group=cfg.OptGroup(PODIFIED_GROUP_NAME), + opts=PODIFIED_OPTIONS) + + +def list_options(): + return [(TRIPLEO_GROUP_NAME, itertools.chain(TRIPLEO_OPTIONS)), + (PODIFIED_GROUP_NAME, itertools.chain(PODIFIED_OPTIONS))] diff --git a/tobiko/tests/faults/ha/cloud_disruptions.py b/tobiko/tests/faults/ha/cloud_disruptions.py index 1f6025893..7bc529485 100644 --- a/tobiko/tests/faults/ha/cloud_disruptions.py +++ b/tobiko/tests/faults/ha/cloud_disruptions.py @@ -825,7 +825,7 @@ def test_controllers_shutdown(): LOG.info("Ensure all controller nodes are running: " f"{all_node_names}") for node in all_nodes: - node.power_on_overcloud_node() + node.power_on_node() topology.assert_reachable_nodes(all_nodes) LOG.debug('Check VM is running while all controllers nodes are on') @@ -840,8 +840,8 @@ def test_controllers_shutdown(): LOG.info(f"Power off {quorum_level} random controller nodes: " f"{node_names}") for node in nodes: - node.power_off_overcloud_node() - test_case.addCleanup(node.power_on_overcloud_node) + node.power_off_node() + test_case.addCleanup(node.power_on_node) topology.assert_unreachable_nodes(nodes, retry_count=1) topology.assert_reachable_nodes(node for node in all_nodes @@ -860,7 +860,7 @@ def test_controllers_shutdown(): random.shuffle(nodes) LOG.info(f"Power on controller nodes: {node_names}") for node in nodes: - node.power_on_overcloud_node() + node.power_on_node() LOG.debug("Check all controller nodes are running again: " f"{all_node_names}") diff --git a/tobiko/tests/faults/octavia/test_faults.py b/tobiko/tests/faults/octavia/test_faults.py index ed445f797..1caefdc31 100644 --- a/tobiko/tests/faults/octavia/test_faults.py +++ b/tobiko/tests/faults/octavia/test_faults.py @@ -90,7 +90,7 @@ class OctaviaBasicFaultTest(testtools.TestCase): LOG.debug('Rebooting compute node...') # Reboot Amphora's compute node will initiate a failover - amphora_compute_host.reboot_overcloud_node() + amphora_compute_host.reboot_node() LOG.debug('Compute node has been rebooted') diff --git a/tobiko/tests/functional/podified/__init__.py b/tobiko/tests/functional/podified/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tobiko/tests/functional/podified/test_topology.py b/tobiko/tests/functional/podified/test_topology.py new file mode 100644 index 000000000..ac4ab5c7a --- /dev/null +++ b/tobiko/tests/functional/podified/test_topology.py @@ -0,0 +1,32 @@ +# Copyright (c) 2023 Red Hat, Inc. +# +# All Rights Reserved. +# +# 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. +from __future__ import absolute_import + +import tobiko +from tobiko.tests.functional.openstack import test_topology +from tobiko import podified + + +@podified.skip_if_not_podified +class PodifiedTopologyTest(test_topology.OpenStackTopologyTest): + + @property + 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.") diff --git a/tobiko/tripleo/__init__.py b/tobiko/tripleo/__init__.py index 76e65f628..57b618db5 100644 --- a/tobiko/tripleo/__init__.py +++ b/tobiko/tripleo/__init__.py @@ -15,7 +15,6 @@ from __future__ import absolute_import from tobiko.tripleo import _ansible from tobiko.tripleo import _overcloud as overcloud -from tobiko.tripleo import _rhosp from tobiko.tripleo import _topology as topology from tobiko.tripleo import _undercloud as undercloud from tobiko.tripleo import containers @@ -56,9 +55,6 @@ get_overcloud_ssh_username = overcloud.get_overcloud_ssh_username skip_if_ceph_rgw = containers.skip_if_ceph_rgw get_container_runtime_name = containers.get_container_runtime_name -get_rhosp_release = _rhosp.get_rhosp_release -get_rhosp_version = _rhosp.get_rhosp_version - TripleoTopology = topology.TripleoTopology UndercloudKeystoneCredentialsFixture = \ diff --git a/tobiko/tripleo/_topology.py b/tobiko/tripleo/_topology.py index e1938b77a..97d1a04d3 100644 --- a/tobiko/tripleo/_topology.py +++ b/tobiko/tripleo/_topology.py @@ -25,18 +25,18 @@ from tobiko import config from tobiko.openstack import neutron from tobiko.openstack import nova from tobiko.openstack import topology +from tobiko import rhosp from tobiko.shell import files from tobiko.shell import sh from tobiko.shell import ssh from tobiko.tripleo import _overcloud -from tobiko.tripleo import _rhosp from tobiko.tripleo import _undercloud CONF = config.CONF LOG = log.getLogger(__name__) -class TripleoTopology(topology.OpenStackTopology): +class TripleoTopology(rhosp.RhospTopology): agent_to_service_name_mappings = { neutron.DHCP_AGENT: 'tripleo_neutron_dhcp', @@ -63,8 +63,6 @@ class TripleoTopology(topology.OpenStackTopology): neutron.FRR: 'frr' } - has_containers = True - config_file_mappings = { 'ml2_conf.ini': '/var/lib/config-data/puppet-generated/neutron' '/etc/neutron/plugins/ml2/ml2_conf.ini', @@ -154,7 +152,7 @@ class TripleoTopology(topology.OpenStackTopology): return subgroups -class TripleoTopologyNode(topology.OpenStackTopologyNode): +class TripleoTopologyNode(rhosp.RhospNode): def __init__(self, topology: topology.OpenStackTopology, @@ -186,37 +184,6 @@ class TripleoTopologyNode(topology.OpenStackTopologyNode): l3_agent_conf_path = ( '/var/lib/config-data/neutron/etc/neutron/l3_agent.ini') - def reboot_overcloud_node(self, - reactivate_servers=True): - """Reboot overcloud node - - This method reboots an overcloud node and may start every Nova - server which is not in SHUTOFF status before restarting. - - :param reactivate_servers: whether or not to re-start the servers which - are hosted on the compute node after the reboot - """ - - running_servers: typing.List[nova.NovaServer] = [] - if reactivate_servers: - running_servers = self.list_running_servers() - LOG.debug(f'Servers to restart after reboot: {running_servers}') - - self.power_off_overcloud_node() - self.power_on_overcloud_node() - - if running_servers: - LOG.info(f'Restart servers after rebooting overcloud compute node ' - f'{self.name}...') - for server in running_servers: - nova.wait_for_server_status(server=server.id, - status='SHUTOFF') - LOG.debug(f'Re-activate server {server.name} with ID ' - f'{server.id}') - nova.activate_server(server=server) - LOG.debug(f'Server {server.name} with ID {server.id} has ' - f'been reactivated') - def list_running_servers(self) -> typing.List[nova.NovaServer]: running_servers = list() for server in nova.list_servers(): @@ -227,7 +194,7 @@ class TripleoTopologyNode(topology.OpenStackTopologyNode): running_servers.append(server) return running_servers - def power_on_overcloud_node(self): + def power_on_node(self): if self.overcloud_instance is None: raise TypeError(f"Node {self.name} is not and Overcloud server") self.ssh_client.close() @@ -237,7 +204,7 @@ class TripleoTopologyNode(topology.OpenStackTopologyNode): LOG.debug(f"Overcloud node {self.name} power is on (" f"hostname={hostname})") - def power_off_overcloud_node(self): + def power_off_node(self): if self.overcloud_instance is None: raise TypeError(f"Node {self.name} is not and Overcloud server") self.ssh_client.close() @@ -245,9 +212,6 @@ class TripleoTopologyNode(topology.OpenStackTopologyNode): _overcloud.power_off_overcloud_node(instance=self.overcloud_instance) LOG.debug(f"Overcloud server node {self.name} power is off.") - def _get_rhosp_version(self) -> tobiko.Version: - return _rhosp.get_rhosp_version(connection=self.connection) - def is_valid_overcloud_group_name(group_name: str, node_name: str = None): if not group_name: diff --git a/tobiko/tripleo/_undercloud.py b/tobiko/tripleo/_undercloud.py index ab901479e..6cb0135ef 100644 --- a/tobiko/tripleo/_undercloud.py +++ b/tobiko/tripleo/_undercloud.py @@ -23,9 +23,9 @@ from oslo_log import log import tobiko from tobiko import config from tobiko.openstack import keystone +from tobiko import rhosp from tobiko.shell import ssh from tobiko.shell import sh -from tobiko.tripleo import _rhosp CONF = config.CONF @@ -218,7 +218,7 @@ def undercloud_keystone_credentials() -> keystone.KeystoneCredentialsFixture: @functools.lru_cache() def undercloud_version() -> tobiko.Version: ssh_client = undercloud_ssh_client() - return _rhosp.get_rhosp_version(connection=ssh_client) + return rhosp.get_rhosp_version(connection=ssh_client) def check_undercloud(min_version: tobiko.Version = None, diff --git a/tobiko/tripleo/config.py b/tobiko/tripleo/config.py index e641f531b..a34a4e1e2 100644 --- a/tobiko/tripleo/config.py +++ b/tobiko/tripleo/config.py @@ -13,89 +13,6 @@ # under the License. from __future__ import absolute_import -import itertools - -from oslo_config import cfg - - -GROUP_NAME = 'tripleo' -OPTIONS = [ - # Undercloud options - cfg.StrOpt('undercloud_ssh_hostname', - default='undercloud-0', - help="hostname or IP address to be used to connect to " - "undercloud host"), - cfg.IntOpt('undercloud_ssh_port', - default=None, - help="TCP port of SSH server on undercloud host"), - cfg.StrOpt('undercloud_ssh_username', - default='stack', - help="Username with access to stackrc and overcloudrc files"), - cfg.StrOpt('undercloud_ssh_key_filename', - default=None, - help="SSH key filename used to login to Undercloud node"), - cfg.ListOpt('undercloud_rcfile', - default=['~/stackrc'], - help="Undercloud RC filename"), - cfg.StrOpt('undercloud_cloud_name', - default='undercloud', - help='undercloud cloud name to be used for loading credentials ' - 'from the undercloud clouds files'), - cfg.StrOpt('undercloud_cacert_file', - default='/etc/pki/tls/certs/ca-bundle.trust.crt', - help='Path to cacert file that can be used to send https ' - 'request from the undercloud'), - - - # Overcloud options - cfg.IntOpt('overcloud_ssh_port', - default=None, - help="TCP port of SSH server on overcloud hosts"), - cfg.StrOpt('overcloud_ssh_username', - default=None, - help="Default username used to connect to overcloud nodes"), - cfg.StrOpt('overcloud_ssh_key_filename', - default='~/.ssh/id_overcloud', - help="SSH key filename used to login to Overcloud nodes"), - cfg.ListOpt('overcloud_rcfile', - default=['~/overcloudrc', '~/qe-Cloud-0rc'], - help="Overcloud RC filenames"), - cfg.StrOpt('overcloud_cloud_name', - default='overcloud', - help='overcloud cloud name to be used for loading credentials ' - 'from the overcloud clouds files'), - cfg.IntOpt('overcloud_ip_version', - help=("Default IP address version to be used to connect to " - "overcloud nodes ")), - cfg.StrOpt('overcloud_network_name', - help="Name of network used to connect to overcloud nodes"), - cfg.DictOpt('overcloud_groups_dict', - help='Dictionary with the node groups corresponding to ' - 'different hostname prefixes', - default={'ctrl': 'controller', 'cmp': 'compute'}), - - # Other options - cfg.StrOpt('inventory_file', - default='.ansible/inventory/tripleo.yaml', - help="path to where to export tripleo inventory file"), - - cfg.BoolOpt('has_external_load_balancer', - default=False, - help="OSP env was done with an external load balancer"), - - cfg.BoolOpt('ceph_rgw', - default=False, - help="whether Ceph RGW is deployed"), -] - - -def register_tobiko_options(conf): - conf.register_opts(group=cfg.OptGroup(GROUP_NAME), opts=OPTIONS) - - -def list_options(): - return [(GROUP_NAME, itertools.chain(OPTIONS))] - def setup_tobiko_config(conf): # pylint: disable=unused-argument