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
This commit is contained in:
Slawek Kaplonski 2023-11-17 15:52:36 +01:00
parent 683a4299f7
commit e1105c97b7
20 changed files with 647 additions and 135 deletions

View File

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

View File

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

View File

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

126
tobiko/podified/_edpm.py Normal file
View File

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

View File

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

View File

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

23
tobiko/podified/config.py Normal file
View File

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

23
tobiko/rhosp/__init__.py Normal file
View File

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

117
tobiko/rhosp/_topology.py Normal file
View File

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

0
tobiko/rhosp/_utils.py Normal file
View File

110
tobiko/rhosp/config.py Normal file
View File

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

View File

@ -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}")

View File

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

View File

@ -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.")

View File

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

View File

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

View File

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

View File

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