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