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
This commit is contained in:
Federico Ressi 2021-04-14 11:52:25 +02:00
parent 9e63f9d1c7
commit bd7533cda0
3 changed files with 93 additions and 17 deletions

View File

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

View File

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

View File

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