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