From 682011c1f63df8c6b5179f1c14c7b336c29a7801 Mon Sep 17 00:00:00 2001 From: Federico Ressi Date: Tue, 5 Jul 2022 12:51:01 +0200 Subject: [PATCH] Add Metalsmith client In OSP-17 and newer, there is no Nova running as undercloud service and instead of Nova client, Metalsmith client has to be used to e.g. get overcloud nodes and its IP addresses. This adds Metalsmith client with Metalsmith client support Change-Id: Iaeef3e87a29513e97f1702085928aa05e1aa82ad --- lower-constraints.txt | 1 + requirements.txt | 1 + tobiko/openstack/metalsmith/__init__.py | 31 +++++++ tobiko/openstack/metalsmith/_client.py | 88 +++++++++++++++++++ tobiko/openstack/metalsmith/_instance.py | 81 +++++++++++++++++ .../unit/openstack/metalsmith/__init__.py | 0 .../unit/openstack/metalsmith/test_client.py | 47 ++++++++++ upper-constraints.txt | 2 +- 8 files changed, 250 insertions(+), 1 deletion(-) create mode 100644 tobiko/openstack/metalsmith/__init__.py create mode 100644 tobiko/openstack/metalsmith/_client.py create mode 100644 tobiko/openstack/metalsmith/_instance.py create mode 100644 tobiko/tests/unit/openstack/metalsmith/__init__.py create mode 100644 tobiko/tests/unit/openstack/metalsmith/test_client.py diff --git a/lower-constraints.txt b/lower-constraints.txt index 2e5ad9af3..c17a12f8d 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -5,6 +5,7 @@ docker==4.4.1 fixtures==3.0.0 Jinja2==2.11.2 keystoneauth1==4.3.0 +metalsmith==1.6.2 mock==3.0.5 netaddr==0.8.0 neutron-lib==2.7.0 diff --git a/requirements.txt b/requirements.txt index d3bcc4146..3954379d3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,6 +6,7 @@ docker>=4.4.1 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD Jinja2>=2.11.2 # BSD keystoneauth1>=4.3.0 # Apache-2.0 +metalsmith>=1.6.2 # Apache-2.0 netaddr>=0.8.0 # BSD neutron-lib>=2.7.0 # Apache-2.0 oslo.config>=8.4.0 # Apache-2.0 diff --git a/tobiko/openstack/metalsmith/__init__.py b/tobiko/openstack/metalsmith/__init__.py new file mode 100644 index 000000000..8de46b059 --- /dev/null +++ b/tobiko/openstack/metalsmith/__init__.py @@ -0,0 +1,31 @@ +# Copyright 2022 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.openstack.metalsmith import _client +from tobiko.openstack.metalsmith import _instance + + +CLIENT_CLASSES = _client.CLIENT_CLASSES +MetalsmithClient = _client.MetalsmithClient +MetalsmithClientFixture = _client.MetalsmithClientFixture +MetalsmithClientType = _client.MetalsmithClientType +metalsmith_client = _client.metalsmith_client +get_metalsmith_client = _client.get_metalsmith_client + +MetalsmithInstance = _instance.MetalsmithInstance +list_instances = _instance.list_instances +find_instance = _instance.find_instance +list_instance_ip_addresses = _instance.list_instance_ip_addresses +find_instance_ip_address = _instance.find_instance_ip_address diff --git a/tobiko/openstack/metalsmith/_client.py b/tobiko/openstack/metalsmith/_client.py new file mode 100644 index 000000000..7fe3c034e --- /dev/null +++ b/tobiko/openstack/metalsmith/_client.py @@ -0,0 +1,88 @@ +# Copyright 2022 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 metalsmith +from oslo_log import log + +import tobiko +from tobiko.openstack import keystone +from tobiko.openstack import _client + + +LOG = log.getLogger(__name__) + + +CLIENT_CLASSES = (metalsmith.Provisioner,) +MetalsmithClient = typing.Union[metalsmith.Provisioner] + + +class MetalsmithClientFixture(_client.OpenstackClientFixture): + + def init_client(self, session) -> MetalsmithClient: + return metalsmith.Provisioner(session=session) + + +class MetalsmithClientManager(_client.OpenstackClientManager): + + def create_client(self, session) -> MetalsmithClientFixture: + return MetalsmithClientFixture(session=session) + + +CLIENTS = MetalsmithClientManager() + + +def metalsmith_client_manager(manager: MetalsmithClientManager = None) \ + -> MetalsmithClientManager: + if manager is None: + manager = CLIENTS + return manager + + +MetalsmithClientType = typing.Union[ + MetalsmithClient, + MetalsmithClientFixture, + typing.Type[MetalsmithClientFixture]] + + +def metalsmith_client(obj: MetalsmithClientType = None) \ + -> MetalsmithClient: + if obj is None: + return get_metalsmith_client() + + if isinstance(obj, CLIENT_CLASSES): + return obj + + fixture = tobiko.setup_fixture(obj) + if isinstance(fixture, MetalsmithClientFixture): + assert fixture.client is not None + return fixture.client + + message = f"Object '{obj}' is not a MetalsmithProvisionerFixture" + raise TypeError(message) + + +def get_metalsmith_client(session: keystone.KeystoneSessionType = None, + shared=True, + init_client=None, + manager: MetalsmithClientManager = None) \ + -> MetalsmithClient: + manager = metalsmith_client_manager(manager) + client = manager.get_client(session=session, + shared=shared, + init_client=init_client) + tobiko.setup_fixture(client) + return client.client diff --git a/tobiko/openstack/metalsmith/_instance.py b/tobiko/openstack/metalsmith/_instance.py new file mode 100644 index 000000000..7b051890a --- /dev/null +++ b/tobiko/openstack/metalsmith/_instance.py @@ -0,0 +1,81 @@ +# Copyright 2022 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 metalsmith +import netaddr + +import tobiko +from tobiko.shell import ping +from tobiko.shell import ssh +from tobiko.openstack.metalsmith import _client + + +MetalsmithInstance = typing.Union[metalsmith.Instance] + + +def list_instances(client: _client.MetalsmithClientType = None, + **params) \ + -> tobiko.Selection[MetalsmithInstance]: + instances = tobiko.select( + _client.metalsmith_client(client).list_instances()) + if params: + instances = instances.with_attributes(**params) + return instances + + +def find_instance(client: _client.MetalsmithClientType = None, + unique=False, **params) -> MetalsmithInstance: + servers = list_instances(client=client, **params) + if unique: + return servers.unique + else: + return servers.first + + +def list_instance_ip_addresses(instance: MetalsmithInstance, + ip_version: int = None, + network_name: str = None, + check_connectivity=False, + ssh_client: ssh.SSHClientType = None)\ + -> tobiko.Selection[netaddr.IPAddress]: + ip_addresses = tobiko.Selection[netaddr.IPAddress]() + for _network, addresses in instance.ip_addresses().items(): + if network_name not in [None, _network]: + continue + + for address in addresses: + ip_address = netaddr.IPAddress(address) + if ip_version not in [None, ip_address.version]: + continue + ip_addresses.append(ip_address) + + # check ICMP connectivity + if check_connectivity: + ip_addresses = ping.list_reachable_hosts( + ip_addresses, ssh_client=ssh_client) + + return ip_addresses + + +def find_instance_ip_address(instance: MetalsmithInstance, + unique=False, + **params) -> netaddr.IPAddress: + addresses = list_instance_ip_addresses(instance=instance, **params) + if unique: + return addresses.unique + else: + return addresses.first diff --git a/tobiko/tests/unit/openstack/metalsmith/__init__.py b/tobiko/tests/unit/openstack/metalsmith/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tobiko/tests/unit/openstack/metalsmith/test_client.py b/tobiko/tests/unit/openstack/metalsmith/test_client.py new file mode 100644 index 000000000..05ee59c04 --- /dev/null +++ b/tobiko/tests/unit/openstack/metalsmith/test_client.py @@ -0,0 +1,47 @@ +# Copyright 2022 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.openstack import keystone +from tobiko.openstack import metalsmith +from tobiko.tests.unit import openstack +from tobiko.tests.unit.openstack import test_client + + +class MetalsmithClientFixtureTest(test_client.OpenstackClientFixtureTest): + + def create_client(self, session=None): + return metalsmith.MetalsmithClientFixture(session=session) + + +class GetMetalsmithClientTest(openstack.OpenstackTest): + + def test_get_metalsmith_client(self, session=None, shared=True): + client1 = metalsmith.get_metalsmith_client( + session=session, shared=shared) + client2 = metalsmith.get_metalsmith_client( + session=session, shared=shared) + if shared: + self.assertIs(client1, client2) + else: + self.assertIsNot(client1, client2) + self.assertIsInstance(client1, metalsmith.CLIENT_CLASSES) + self.assertIsInstance(client2, metalsmith.CLIENT_CLASSES) + + def test_get_metalsmith_client_with_not_shared(self): + self.test_get_metalsmith_client(shared=False) + + def test_get_metalsmith_client_with_session(self): + session = keystone.get_keystone_session() + self.test_get_metalsmith_client(session=session) diff --git a/upper-constraints.txt b/upper-constraints.txt index 83caa4236..abc6f8b24 100644 --- a/upper-constraints.txt +++ b/upper-constraints.txt @@ -192,7 +192,7 @@ Mako===1.1.6 marathon===0.13.0 MarkupSafe===2.0.1 mbstrdecoder===1.1.0 -metalsmith===1.6.1 +metalsmith===1.6.2 microversion-parse===1.0.1 mistral-lib===2.5.0 mitba===1.1.1