From bd7533cda067d2b369d0cc8e5d3fd83439e88d35 Mon Sep 17 00:00:00 2001 From: Federico Ressi Date: Wed, 14 Apr 2021 11:52:25 +0200 Subject: [PATCH] Use Ironic undercloud APIs to power on/off Overcloud nodes Fixes topology node discovery to avoid repeating listing undercloud servers and to ensuring overcloud nodes are power on when they are being discovered. Change-Id: Ia834aa4282b604fca502948710e6b10676764c81 --- .../tests/functional/tripleo/test_topology.py | 2 +- tobiko/tripleo/_overcloud.py | 62 +++++++++++++++---- tobiko/tripleo/_topology.py | 46 ++++++++++++-- 3 files changed, 93 insertions(+), 17 deletions(-) diff --git a/tobiko/tests/functional/tripleo/test_topology.py b/tobiko/tests/functional/tripleo/test_topology.py index adda99e37..5345b69c3 100644 --- a/tobiko/tests/functional/tripleo/test_topology.py +++ b/tobiko/tests/functional/tripleo/test_topology.py @@ -21,11 +21,11 @@ from tobiko import tripleo from tobiko.tests.functional.openstack import test_topology +@tripleo.skip_if_missing_undercloud class TripleoTopologyTest(test_topology.OpenStackTopologyTest): topology = tobiko.required_setup_fixture(tripleo.TripleoTopology) - @tripleo.skip_if_missing_undercloud def test_undercloud_group(self): ssh_client = tripleo.undercloud_ssh_client() name = sh.get_hostname(ssh_client=ssh_client).split('.')[0] diff --git a/tobiko/tripleo/_overcloud.py b/tobiko/tripleo/_overcloud.py index f4f1b4367..dca9b852e 100644 --- a/tobiko/tripleo/_overcloud.py +++ b/tobiko/tripleo/_overcloud.py @@ -15,6 +15,7 @@ from __future__ import absolute_import import io import os +import typing from oslo_log import log import six @@ -22,6 +23,7 @@ import six import tobiko from tobiko import config from tobiko.openstack import keystone +from tobiko.openstack import ironic from tobiko.openstack import nova from tobiko.shell import sh from tobiko.shell import ssh @@ -67,17 +69,47 @@ def find_overcloud_node(**params): return nova.find_server(client=client, **params) -def overcloud_ssh_client(hostname, ip_version=None, network_name=None): - host_config = overcloud_host_config(hostname=hostname, - ip_version=ip_version, - network_name=network_name) +def power_on_overcloud_node(server: typing.Union[nova.ServerType]): + session = _undercloud.undercloud_keystone_session() + node_id = getattr(server, 'OS-EXT-SRV-ATTR:hypervisor_hostname', + None) + if node_id is None: + client = nova.get_nova_client(session=session) + nova.activate_server(client=client, server=server) + else: + client = ironic.get_ironic_client(session=session) + ironic.power_on_node(client=client, node=node_id) + + +def power_off_overcloud_node(server: typing.Union[nova.ServerType]) \ + -> nova.NovaServer: + session = _undercloud.undercloud_keystone_session() + node_id = getattr(server, 'OS-EXT-SRV-ATTR:hypervisor_hostname', + None) + if node_id is None: + client = nova.get_nova_client(session=session) + nova.shutoff_server(client=client, server=server) + else: + client = ironic.get_ironic_client(session=session) + ironic.power_off_node(client=client, node=node_id) + + +def overcloud_ssh_client(hostname=None, ip_version=None, network_name=None, + server=None, host_config=None): + if host_config is None: + host_config = overcloud_host_config(hostname=hostname, + ip_version=ip_version, + network_name=network_name, + server=server) return ssh.ssh_client(host=hostname, host_config=host_config) -def overcloud_host_config(hostname, ip_version=None, network_name=None): +def overcloud_host_config(hostname=None, ip_version=None, network_name=None, + server=None): host_config = OvercloudHostConfig(host=hostname, ip_version=ip_version, - network_name=network_name) + network_name=network_name, + server=server) return tobiko.setup_fixture(host_config) @@ -130,6 +162,7 @@ def _get_undercloud_file(ssh_client, source, destination, mode): class OvercloudHostConfig(tobiko.SharedFixture): + host = None hostname = None port = None username = None @@ -137,22 +170,29 @@ class OvercloudHostConfig(tobiko.SharedFixture): ip_version = None network_name = None key_filename = None + server = None - def __init__(self, host, ip_version=None, network_name=None, - **kwargs): + def __init__(self, host=None, ip_version=None, network_name=None, + server=None, **kwargs): super(OvercloudHostConfig, self).__init__() - tobiko.check_valid_type(host, six.string_types) - self.host = host + if host: + self.host = host if ip_version: self.ip_version = ip_version if network_name: self.network_name = network_name + if server: + self.server = server + if self.host is None: + self.host = server.name + tobiko.check_valid_type(self.host, six.string_types) self._connect_parameters = ssh.gather_ssh_connect_parameters(**kwargs) def setup_fixture(self): self.hostname = str(overcloud_node_ip_address( name=self.host, ip_version=self.ip_version, - network_name=self.network_name)) + network_name=self.network_name, + server=self.server)) self.port = self.port or CONF.tobiko.tripleo.overcloud_ssh_port self.username = (self.username or CONF.tobiko.tripleo.overcloud_ssh_username) diff --git a/tobiko/tripleo/_topology.py b/tobiko/tripleo/_topology.py index 418778889..e65624400 100644 --- a/tobiko/tripleo/_topology.py +++ b/tobiko/tripleo/_topology.py @@ -19,8 +19,10 @@ import typing # noqa from oslo_log import log from tobiko.openstack import neutron +from tobiko.openstack import nova from tobiko.openstack import topology from tobiko.shell import files +from tobiko.shell import sh from tobiko.tripleo import _overcloud from tobiko.tripleo import _undercloud @@ -66,6 +68,12 @@ class TripleoTopology(topology.OpenStackTopology): neutron.SERVER: '/var/log/containers/neutron/server.log*', } + def create_node(self, name, ssh_client, **kwargs): + return TripleoTopologyNode(topology=self, + name=name, + ssh_client=ssh_client, + **kwargs) + def discover_nodes(self): self.discover_ssh_proxy_jump_node() self.discover_undercloud_nodes() @@ -82,15 +90,18 @@ class TripleoTopology(topology.OpenStackTopology): def discover_overcloud_nodes(self): if _overcloud.has_overcloud(): for server in _overcloud.list_overcloud_nodes(): - config = _overcloud.overcloud_host_config(server.name) - ssh_client = _overcloud.overcloud_ssh_client(server.name) - node = self.add_node(address=config.hostname, + _overcloud.power_on_overcloud_node(server) + host_config = _overcloud.overcloud_host_config(server=server) + ssh_client = _overcloud.overcloud_ssh_client( + hostname=server.name, + host_config=host_config) + node = self.add_node(address=host_config.hostname, hostname=server.name, group='overcloud', ssh_client=ssh_client) + assert isinstance(node, TripleoTopologyNode) + node.overcloud_server = server self.discover_overcloud_node_subgroups(node) - else: - super(TripleoTopology, self).discover_nodes() def discover_overcloud_node_subgroups(self, node): # set of subgroups extracted from node name @@ -120,6 +131,31 @@ class TripleoTopology(topology.OpenStackTopology): return subgroups +class TripleoTopologyNode(topology.OpenStackTopologyNode): + + overcloud_server: typing.Optional[nova.NovaServer] = None + + def power_on_overcloud_node(self): + server = self.overcloud_server + if server is None: + raise TypeError(f"Node {self.name} is not and Overcloud server") + self.ssh_client.close() + LOG.debug(f"Ensuring overcloud node {self.name} power is on...") + _overcloud.power_on_overcloud_node(server) + hostname = sh.get_hostname(ssh_client=self.ssh_client) + LOG.debug(f"Overcloud node {self.name} power is on (" + f"hostname={hostname})") + + def power_off_overcloud_node(self): + server = self.overcloud_server + if server is None: + raise TypeError(f"Node {self.name} is not and Overcloud server") + self.ssh_client.close() + LOG.debug(f"Ensuring overcloud node {self.name} power is off...") + _overcloud.power_off_overcloud_node(server) + LOG.debug(f"Overcloud server node {self.name} power is off.") + + def is_valid_overcloud_group_name(group_name: str, node_name: str = None): if not group_name: return False