Merge "Add topology which represents Podified RHOSP deployment"
This commit is contained in:
commit
d6327733d8
@ -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
|
||||
|
@ -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']
|
||||
|
||||
|
||||
|
24
tobiko/podified/__init__.py
Normal file
24
tobiko/podified/__init__.py
Normal 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
126
tobiko/podified/_edpm.py
Normal 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
|
82
tobiko/podified/_openshift.py
Normal file
82
tobiko/podified/_openshift.py
Normal 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
|
95
tobiko/podified/_topology.py
Normal file
95
tobiko/podified/_topology.py
Normal 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
23
tobiko/podified/config.py
Normal 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
23
tobiko/rhosp/__init__.py
Normal 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
117
tobiko/rhosp/_topology.py
Normal 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
0
tobiko/rhosp/_utils.py
Normal file
110
tobiko/rhosp/config.py
Normal file
110
tobiko/rhosp/config.py
Normal 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))]
|
@ -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}")
|
||||
|
@ -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')
|
||||
|
||||
|
0
tobiko/tests/functional/podified/__init__.py
Normal file
0
tobiko/tests/functional/podified/__init__.py
Normal file
32
tobiko/tests/functional/podified/test_topology.py
Normal file
32
tobiko/tests/functional/podified/test_topology.py
Normal 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.")
|
@ -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 = \
|
||||
|
@ -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:
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user