From 21655a3741f80afad43c138910e1ff3d5979535c Mon Sep 17 00:00:00 2001 From: Alex Kavanagh Date: Sat, 16 May 2020 18:52:35 +0100 Subject: [PATCH 01/59] Fix broken dictionary assignment for domain --- zaza/openstack/utilities/openstack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zaza/openstack/utilities/openstack.py b/zaza/openstack/utilities/openstack.py index 47cabf3..3148940 100644 --- a/zaza/openstack/utilities/openstack.py +++ b/zaza/openstack/utilities/openstack.py @@ -1600,7 +1600,7 @@ def get_undercloud_auth(): 'API_VERSION': 3, } if domain: - auth_settings['OS_DOMAIN_NAME': 'admin_domain'] = domain + auth_settings['OS_DOMAIN_NAME'] = domain else: auth_settings['OS_USER_DOMAIN_NAME'] = ( os.environ.get('OS_USER_DOMAIN_NAME')) From 2e6a2ef534d61e7dc11526eb8408aaf41dd07263 Mon Sep 17 00:00:00 2001 From: Frode Nordahl Date: Sat, 6 Jun 2020 14:38:38 +0200 Subject: [PATCH 02/59] Add helper to format IP addresses appropriately The ``format_addr`` helper will allow you to write IP version agnostic code by encapsulating IPv6 addresses in brackets ('[]'). --- unit_tests/charm_tests/test_utils.py | 12 ++++++++++++ zaza/openstack/charm_tests/test_utils.py | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/unit_tests/charm_tests/test_utils.py b/unit_tests/charm_tests/test_utils.py index 280e2e2..a415360 100644 --- a/unit_tests/charm_tests/test_utils.py +++ b/unit_tests/charm_tests/test_utils.py @@ -30,3 +30,15 @@ class TestOpenStackBaseTest(unittest.TestCase): MyTestClass.setUpClass('foo', 'bar') _setUpClass.assert_called_with('foo', 'bar') + + +class TestUtils(unittest.TestCase): + + def test_format_addr(self): + self.assertEquals('1.2.3.4', test_utils.format_addr('1.2.3.4')) + self.assertEquals( + '[2001:db8::42]', test_utils.format_addr('2001:db8::42')) + with self.assertRaises(ValueError): + test_utils.format_addr('999.999.999.999') + with self.assertRaises(ValueError): + test_utils.format_addr('2001:db8::g') diff --git a/zaza/openstack/charm_tests/test_utils.py b/zaza/openstack/charm_tests/test_utils.py index bcb5aff..47c12d7 100644 --- a/zaza/openstack/charm_tests/test_utils.py +++ b/zaza/openstack/charm_tests/test_utils.py @@ -14,6 +14,7 @@ """Module containing base class for implementing charm tests.""" import contextlib import logging +import ipaddress import subprocess import unittest @@ -431,3 +432,20 @@ class OpenStackBaseTest(BaseCharmTest): cls.keystone_session = openstack_utils.get_overcloud_keystone_session( model_name=cls.model_name) cls.cacert = openstack_utils.get_cacert() + + +def format_addr(addr): + """Validate and format IP address. + + :param addr: IPv6 or IPv4 address + :type addr: str + :returns: Address string, optionally encapsulated in brackets([]) + :rtype: str + :raises: ValueError + """ + ipaddr = ipaddress.ip_address(addr) + if isinstance(ipaddr, ipaddress.IPv6Address): + fmt = '[{}]' + else: + fmt = '{}' + return fmt.format(ipaddr) From 37311df9c2592e9e0fa20a717aca17a4baece643 Mon Sep 17 00:00:00 2001 From: Frode Nordahl Date: Sat, 6 Jun 2020 14:40:06 +0200 Subject: [PATCH 03/59] vault: fix formatting of IP addresses to support IPv6 At present an invalid URL to Vault will be produced in the event of the IP address used being an IPv6 address. --- zaza/openstack/charm_tests/vault/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zaza/openstack/charm_tests/vault/utils.py b/zaza/openstack/charm_tests/vault/utils.py index c05fcf6..a708d78 100644 --- a/zaza/openstack/charm_tests/vault/utils.py +++ b/zaza/openstack/charm_tests/vault/utils.py @@ -27,6 +27,7 @@ import yaml import collections import zaza.model +import zaza.openstack.charm_tests.test_utils as test_utils AUTH_FILE = "vault_tests.yaml" CharmVaultClient = collections.namedtuple( @@ -101,7 +102,7 @@ def get_unit_api_url(ip): transport = 'http' if vault_config['ssl-cert']['value']: transport = 'https' - return '{}://{}:8200'.format(transport, ip) + return '{}://{}:8200'.format(transport, test_utils.format_addr(ip)) def get_hvac_client(vault_url, cacert=None): From effbf131908f67c7eda55eeb56f1a621292bfef2 Mon Sep 17 00:00:00 2001 From: Frode Nordahl Date: Wed, 10 Jun 2020 08:29:50 +0200 Subject: [PATCH 04/59] Add CephFS pause/resume test --- zaza/openstack/charm_tests/ceph/fs/tests.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/zaza/openstack/charm_tests/ceph/fs/tests.py b/zaza/openstack/charm_tests/ceph/fs/tests.py index 63cc492..4b0842b 100644 --- a/zaza/openstack/charm_tests/ceph/fs/tests.py +++ b/zaza/openstack/charm_tests/ceph/fs/tests.py @@ -14,6 +14,7 @@ """Encapsulate CephFS testing.""" +import logging from tenacity import Retrying, stop_after_attempt, wait_exponential import zaza.model as model @@ -124,3 +125,18 @@ write_files: def _indent(text, amount, ch=' '): padding = amount * ch return ''.join(padding+line for line in text.splitlines(True)) + + +class CharmOperationTest(test_utils.BaseCharmTest): + """CephFS Charm operation tests.""" + + def test_pause_resume(self): + """Run pause and resume tests. + + Pause service and check services are stopped, then resume and check + they are started. + """ + services = ['ceph-mds'] + with self.pause_resume(services): + logging.info('Testing pause resume (services="{}")' + .format(services)) From 931fcb4aa7b72bcc04010e7375af59514a76aabc Mon Sep 17 00:00:00 2001 From: James Page Date: Fri, 12 Jun 2020 10:40:11 +0100 Subject: [PATCH 05/59] Add setup for glance-simplestreams-sync Use action to complete initial image sync for the gss charm. This avoids races where the images end up in the wrong locations and allows the tests to actually know when images should be discoverable. Update tests to wait for at least four images (20.04 is synced by default). --- .../glance_simplestreams_sync/setup.py | 40 +++++++++++++++++++ .../glance_simplestreams_sync/tests.py | 5 ++- 2 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 zaza/openstack/charm_tests/glance_simplestreams_sync/setup.py diff --git a/zaza/openstack/charm_tests/glance_simplestreams_sync/setup.py b/zaza/openstack/charm_tests/glance_simplestreams_sync/setup.py new file mode 100644 index 0000000..06e172d --- /dev/null +++ b/zaza/openstack/charm_tests/glance_simplestreams_sync/setup.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 + +# Copyright 2019 Canonical Ltd. +# +# 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. + +"""Code for configuring glance-simplestreams-sync.""" + +import logging + +import zaza.model as zaza_model +import zaza.openstack.utilities.generic as generic_utils + + +def sync_images(): + """Run image sync using an action. + + Execute an initial image sync using an action to ensure that the + cloud is populated with images at the right point in time during + deployment. + """ + logging.info("Synchronising images using glance-simplestreams-sync") + generic_utils.assertActionRanOK( + zaza_model.run_action_on_leader( + "glance-simplestreams-sync", + "sync-images", + raise_on_failure=True, + action_params={}, + ) + ) diff --git a/zaza/openstack/charm_tests/glance_simplestreams_sync/tests.py b/zaza/openstack/charm_tests/glance_simplestreams_sync/tests.py index 648172a..adbc87b 100644 --- a/zaza/openstack/charm_tests/glance_simplestreams_sync/tests.py +++ b/zaza/openstack/charm_tests/glance_simplestreams_sync/tests.py @@ -24,7 +24,7 @@ import zaza.openstack.utilities.openstack as openstack_utils @tenacity.retry( - retry=tenacity.retry_if_result(lambda images: len(images) < 3), + retry=tenacity.retry_if_result(lambda images: len(images) < 4), wait=tenacity.wait_fixed(6), # interval between retries stop=tenacity.stop_after_attempt(100)) # retry times def retry_image_sync(glance_client): @@ -61,7 +61,7 @@ class GlanceSimpleStreamsSyncTest(test_utils.OpenStackBaseTest): cls.keystone_session) def test_010_wait_for_image_sync(self): - """Wait for images to be synced. Expect at least three.""" + """Wait for images to be synced. Expect at least four.""" self.assertTrue(retry_image_sync(self.glance_client)) def test_050_gss_permissions_regression_check_lp1611987(self): @@ -94,6 +94,7 @@ class GlanceSimpleStreamsSyncTest(test_utils.OpenStackBaseTest): 'com.ubuntu.cloud:server:14.04:amd64', 'com.ubuntu.cloud:server:16.04:amd64', 'com.ubuntu.cloud:server:18.04:amd64', + 'com.ubuntu.cloud:server:20.04:amd64', ] uri = "streams/v1/auto.sync.json" key = "url" From 1f2b4890ef0a44b089b71b96dd8c582e774ba77c Mon Sep 17 00:00:00 2001 From: Drew Freiberger Date: Thu, 18 Jun 2020 08:36:41 -0500 Subject: [PATCH 06/59] Fix racy swift global replication testing During some runs of functional testing, swift global replication would only have 2 copies rather than 3 of the object that was just written. This patch attempts to resolve the race condition and also adds ordering to more recently added tests. Closes-Bug: #1882247 --- zaza/openstack/charm_tests/swift/tests.py | 41 +++++++++++++++++++---- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/zaza/openstack/charm_tests/swift/tests.py b/zaza/openstack/charm_tests/swift/tests.py index 32441df..75f82b7 100644 --- a/zaza/openstack/charm_tests/swift/tests.py +++ b/zaza/openstack/charm_tests/swift/tests.py @@ -178,15 +178,28 @@ class SwiftGlobalReplicationTests(test_utils.OpenStackBaseTest): logging.info('Deleting container {}'.format(container['name'])) cls.swift_region1.delete_container(container['name']) - def test_two_regions_any_zones_two_replicas(self): - """Create an object with two replicas across two regions.""" + def test_901_two_regions_any_zones_two_replicas(self): + """Create an object with two replicas across two regions. + + We set write affinity to write the first copy in the local + region of the proxy used to perform the write, the other + replica will land in the remote region. + """ swift_utils.apply_proxy_config( self.region1_proxy_app, { - 'write-affinity': 'r1, r2', + 'write-affinity': 'r1', 'write-affinity-node-count': '1', 'replicas': '2'}, self.region1_model_name) + swift_utils.apply_proxy_config( + self.region2_proxy_app, + { + 'write-affinity': 'r2', + 'write-affinity-node-count': '1', + 'replicas': '2'}, + self.region2_model_name) + logging.info('Proxy configs updated in both regions') container_name, obj_name, obj_replicas = swift_utils.create_object( self.swift_region1, self.region1_proxy_app, @@ -204,15 +217,29 @@ class SwiftGlobalReplicationTests(test_utils.OpenStackBaseTest): len(obj_replicas.all_zones), 2) - def test_two_regions_any_zones_three_replicas(self): - """Create an object with three replicas across two regions.""" + def test_902_two_regions_any_zones_three_replicas(self): + """Create an object with three replicas across two regions. + + We set write affinity to write the first copy in the local + region of the proxy used to perform the write, at least one + of the other two replicas will end up in the opposite region + based on primary partitions in the ring. + """ swift_utils.apply_proxy_config( self.region1_proxy_app, { - 'write-affinity': 'r1, r2', + 'write-affinity': 'r1', 'write-affinity-node-count': '1', 'replicas': '3'}, self.region1_model_name) + swift_utils.apply_proxy_config( + self.region2_proxy_app, + { + 'write-affinity': 'r2', + 'write-affinity-node-count': '1', + 'replicas': '3'}, + self.region2_model_name) + logging.info('Proxy configs updated in both regions') container_name, obj_name, obj_replicas = swift_utils.create_object( self.swift_region1, self.region1_proxy_app, @@ -258,7 +285,7 @@ class S3APITest(test_utils.OpenStackBaseTest): # Create AWS compatible application credentials in Keystone cls.ec2_creds = ks_client.ec2.create(user_id, project_id) - def test_s3_list_buckets(self): + def test_901_s3_list_buckets(self): """Use S3 API to list buckets.""" # We use a mix of the high- and low-level API with common arguments kwargs = { From 95eb158e7e00f3958f6f3c698fa2900ec5648abd Mon Sep 17 00:00:00 2001 From: Frode Nordahl Date: Fri, 19 Jun 2020 08:28:18 +0200 Subject: [PATCH 07/59] Support undercloud running TLS (#330) Consume the `OS_CACERT` environment variable when setting up undercloud auth. Fixes #329 --- zaza/openstack/utilities/openstack.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/zaza/openstack/utilities/openstack.py b/zaza/openstack/utilities/openstack.py index 4963247..780d2df 100644 --- a/zaza/openstack/utilities/openstack.py +++ b/zaza/openstack/utilities/openstack.py @@ -1621,6 +1621,10 @@ def get_undercloud_auth(): if os_project_id is not None: auth_settings['OS_PROJECT_ID'] = os_project_id + _os_cacert = os.environ.get('OS_CACERT') + if _os_cacert: + auth_settings.update({'OS_CACERT': _os_cacert}) + # Validate settings for key, settings in list(auth_settings.items()): if settings is None: From 537473ad3addeaa3d1e4e2d0fd556aeaa4018eb2 Mon Sep 17 00:00:00 2001 From: Frode Nordahl Date: Fri, 19 Jun 2020 09:50:28 +0200 Subject: [PATCH 08/59] Store local overcloud CACERT copy in relative path At present the overcloud CACERT is copied to /tmp and as such it is not possbile to run multiple tests at once without them stepping on each other. Store the copy in a path relative to where the test is executed, in line with how the SSH keys are stored etc. Fixes #331 --- zaza/openstack/utilities/openstack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zaza/openstack/utilities/openstack.py b/zaza/openstack/utilities/openstack.py index 780d2df..64d42a8 100644 --- a/zaza/openstack/utilities/openstack.py +++ b/zaza/openstack/utilities/openstack.py @@ -163,7 +163,7 @@ WORKLOAD_STATUS_EXCEPTIONS = { KEYSTONE_CACERT = "keystone_juju_ca_cert.crt" KEYSTONE_REMOTE_CACERT = ( "/usr/local/share/ca-certificates/{}".format(KEYSTONE_CACERT)) -KEYSTONE_LOCAL_CACERT = ("/tmp/{}".format(KEYSTONE_CACERT)) +KEYSTONE_LOCAL_CACERT = ("tests/{}".format(KEYSTONE_CACERT)) def get_cacert(): From f0ceb33f3a31fc5a3c8ed18971cc19009a10259f Mon Sep 17 00:00:00 2001 From: Aurelien Lourot Date: Thu, 18 Jun 2020 15:03:17 +0200 Subject: [PATCH 09/59] Add functional tests for neutron-arista --- zaza/openstack/charm_tests/neutron/tests.py | 21 +++++- .../charm_tests/neutron_arista/__init__.py | 15 +++++ .../charm_tests/neutron_arista/setup.py | 39 +++++++++++ .../charm_tests/neutron_arista/tests.py | 53 +++++++++++++++ .../charm_tests/neutron_arista/utils.py | 67 +++++++++++++++++++ 5 files changed, 194 insertions(+), 1 deletion(-) create mode 100644 zaza/openstack/charm_tests/neutron_arista/__init__.py create mode 100644 zaza/openstack/charm_tests/neutron_arista/setup.py create mode 100644 zaza/openstack/charm_tests/neutron_arista/tests.py create mode 100644 zaza/openstack/charm_tests/neutron_arista/utils.py diff --git a/zaza/openstack/charm_tests/neutron/tests.py b/zaza/openstack/charm_tests/neutron/tests.py index 7e28112..ba014e2 100644 --- a/zaza/openstack/charm_tests/neutron/tests.py +++ b/zaza/openstack/charm_tests/neutron/tests.py @@ -293,8 +293,23 @@ class NeutronCreateNetworkTest(test_utils.OpenStackBaseTest): cls.neutron_client = ( openstack_utils.get_neutron_session_client(cls.keystone_session)) + def _test_400_additional_validation(self, expected_network_names): + """Additional assertions for test_400_create_network. + + Can be overridden in derived classes. + + :type expected_network_names: List[str] + """ + pass + def test_400_create_network(self): - """Create a network, verify that it exists, and then delete it.""" + """Create a network, verify that it exists, and then delete it. + + Additional verifications on the created network can be performed by + deriving this class and overriding _test_400_additional_validation(). + """ + self._test_400_additional_validation([]) + logging.debug('Creating neutron network...') self.neutron_client.format = 'json' net_name = 'test_net' @@ -319,10 +334,14 @@ class NeutronCreateNetworkTest(test_utils.OpenStackBaseTest): network = networks['networks'][0] assert network['name'] == net_name, "network ext_net not found" + self._test_400_additional_validation([net_name]) + # Cleanup logging.debug('Deleting neutron network...') self.neutron_client.delete_network(network['id']) + self._test_400_additional_validation([]) + class NeutronApiTest(NeutronCreateNetworkTest): """Test basic Neutron API Charm functionality.""" diff --git a/zaza/openstack/charm_tests/neutron_arista/__init__.py b/zaza/openstack/charm_tests/neutron_arista/__init__.py new file mode 100644 index 0000000..8d3f5c9 --- /dev/null +++ b/zaza/openstack/charm_tests/neutron_arista/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2020 Canonical Ltd. +# +# 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. + +"""Collection of code for setting up and testing neutron-arista.""" diff --git a/zaza/openstack/charm_tests/neutron_arista/setup.py b/zaza/openstack/charm_tests/neutron_arista/setup.py new file mode 100644 index 0000000..50878e6 --- /dev/null +++ b/zaza/openstack/charm_tests/neutron_arista/setup.py @@ -0,0 +1,39 @@ +# Copyright 2020 Canonical Ltd. +# +# 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. + +"""Code for setting up neutron-arista.""" + +import logging +import tenacity +import zaza +import zaza.openstack.charm_tests.neutron_arista.utils as arista_utils + + +def test_fixture(): + """Pass arista-virt-test-fixture's IP address to neutron-arista.""" + fixture_ip_addr = arista_utils.fixture_ip_addr() + logging.info( + "{}'s IP address is '{}'. Passing it to neutron-arista...".format( + arista_utils.FIXTURE_APP_NAME, fixture_ip_addr)) + zaza.model.set_application_config('neutron-arista', + {'eapi-host': fixture_ip_addr}) + + logging.info('Waiting for {} to become ready...'.format( + arista_utils.FIXTURE_APP_NAME)) + for attempt in tenacity.Retrying( + wait=tenacity.wait_fixed(10), # seconds + stop=tenacity.stop_after_attempt(30), + reraise=True): + with attempt: + arista_utils.query_fixture_networks(fixture_ip_addr) diff --git a/zaza/openstack/charm_tests/neutron_arista/tests.py b/zaza/openstack/charm_tests/neutron_arista/tests.py new file mode 100644 index 0000000..c47c9c8 --- /dev/null +++ b/zaza/openstack/charm_tests/neutron_arista/tests.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 + +# Copyright 2020 Canonical Ltd. +# +# 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. + +"""Encapsulating `neutron-arista` testing.""" + +import logging +import tenacity +import zaza.openstack.charm_tests.neutron.tests as neutron_tests +import zaza.openstack.charm_tests.neutron_arista.utils as arista_utils + + +class NeutronCreateAristaNetworkTest(neutron_tests.NeutronCreateNetworkTest): + """Test creating an Arista Neutron network through the API.""" + + @classmethod + def setUpClass(cls): + """Run class setup for running Neutron Arista tests.""" + super(NeutronCreateAristaNetworkTest, cls).setUpClass() + + logging.info('Waiting for Neutron to become ready...') + for attempt in tenacity.Retrying( + wait=tenacity.wait_fixed(5), # seconds + stop=tenacity.stop_after_attempt(12), + reraise=True): + with attempt: + cls.neutron_client.list_networks() + + def _test_400_additional_validation(self, expected_network_names): + """Arista-specific assertions for test_400_create_network. + + :type expected_network_names: List[str] + """ + logging.info("Querying Arista CVX's networks...") + actual_network_names = arista_utils.query_fixture_networks( + arista_utils.fixture_ip_addr()) + + # NOTE(lourot): the assertion name is misleading as it's not only + # checking the item count but also that all items are present in + # both lists, without checking the order. + self.assertCountEqual(actual_network_names, expected_network_names) diff --git a/zaza/openstack/charm_tests/neutron_arista/utils.py b/zaza/openstack/charm_tests/neutron_arista/utils.py new file mode 100644 index 0000000..3c68f33 --- /dev/null +++ b/zaza/openstack/charm_tests/neutron_arista/utils.py @@ -0,0 +1,67 @@ +# Copyright 2020 Canonical Ltd. +# +# 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. + +"""Common Arista-related utils.""" + +import json +import requests +import urllib3 +import zaza + +FIXTURE_APP_NAME = 'arista-virt-test-fixture' + + +def fixture_ip_addr(): + """Return the public IP address of the Arista test fixture.""" + return zaza.model.get_units(FIXTURE_APP_NAME)[0].public_address + + +_FIXTURE_LOGIN = 'admin' +_FIXTURE_PASSWORD = 'password123' + + +def query_fixture_networks(ip_addr): + """Query the Arista test fixture's list of networks.""" + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + + session = requests.Session() + session.headers['Content-Type'] = 'application/json' + session.headers['Accept'] = 'application/json' + session.verify = False + session.auth = (_FIXTURE_LOGIN, _FIXTURE_PASSWORD) + + data = { + 'id': 'Zaza neutron-arista tests', + 'method': 'runCmds', + 'jsonrpc': '2.0', + 'params': { + 'timestamps': False, + 'format': 'json', + 'version': 1, + 'cmds': ['show openstack networks'] + } + } + + response = session.post( + 'https://{}/command-api/'.format(ip_addr), + data=json.dumps(data), + timeout=10 # seconds + ) + + result = [] + for _, region in response.json()['result'][0]['regions'].items(): + for _, tenant in region['tenants'].items(): + for _, network in tenant['tenantNetworks'].items(): + result.append(network['networkName']) + return result From 13b590ea12376ad5d2fc3568db2ada0d490beb3c Mon Sep 17 00:00:00 2001 From: Aurelien Lourot Date: Tue, 23 Jun 2020 13:03:04 +0200 Subject: [PATCH 10/59] Refactor overriding mechanism --- zaza/openstack/charm_tests/neutron/tests.py | 61 ++++++++----------- .../charm_tests/neutron_arista/tests.py | 20 +++--- 2 files changed, 35 insertions(+), 46 deletions(-) diff --git a/zaza/openstack/charm_tests/neutron/tests.py b/zaza/openstack/charm_tests/neutron/tests.py index ba014e2..f270604 100644 --- a/zaza/openstack/charm_tests/neutron/tests.py +++ b/zaza/openstack/charm_tests/neutron/tests.py @@ -292,55 +292,44 @@ class NeutronCreateNetworkTest(test_utils.OpenStackBaseTest): # set up clients cls.neutron_client = ( openstack_utils.get_neutron_session_client(cls.keystone_session)) + cls.neutron_client.format = 'json' - def _test_400_additional_validation(self, expected_network_names): - """Additional assertions for test_400_create_network. - - Can be overridden in derived classes. - - :type expected_network_names: List[str] - """ - pass + _TEST_NET_NAME = 'test_net' def test_400_create_network(self): - """Create a network, verify that it exists, and then delete it. - - Additional verifications on the created network can be performed by - deriving this class and overriding _test_400_additional_validation(). - """ - self._test_400_additional_validation([]) + """Create a network, verify that it exists, and then delete it.""" + self._assert_test_network_doesnt_exist() + self._create_test_network() + net_id = self._assert_test_network_exists_and_return_id() + self._delete_test_network(net_id) + self._assert_test_network_doesnt_exist() + def _create_test_network(self): logging.debug('Creating neutron network...') - self.neutron_client.format = 'json' - net_name = 'test_net' - - # Verify that the network doesn't exist - networks = self.neutron_client.list_networks(name=net_name) - net_count = len(networks['networks']) - assert net_count == 0, ( - "Expected zero networks, found {}".format(net_count)) - - # Create a network and verify that it exists - network = {'name': net_name} + network = {'name': self._TEST_NET_NAME} self.neutron_client.create_network({'network': network}) - networks = self.neutron_client.list_networks(name=net_name) + def _delete_test_network(self, net_id): + logging.debug('Deleting neutron network...') + self.neutron_client.delete_network(net_id) + + def _assert_test_network_exists_and_return_id(self): + logging.debug('Confirming new neutron network...') + networks = self.neutron_client.list_networks(name=self._TEST_NET_NAME) logging.debug('Networks: {}'.format(networks)) net_len = len(networks['networks']) assert net_len == 1, ( "Expected 1 network, found {}".format(net_len)) - - logging.debug('Confirming new neutron network...') network = networks['networks'][0] - assert network['name'] == net_name, "network ext_net not found" + assert network['name'] == self._TEST_NET_NAME, \ + "network {} not found".format(self._TEST_NET_NAME) + return network['id'] - self._test_400_additional_validation([net_name]) - - # Cleanup - logging.debug('Deleting neutron network...') - self.neutron_client.delete_network(network['id']) - - self._test_400_additional_validation([]) + def _assert_test_network_doesnt_exist(self): + networks = self.neutron_client.list_networks(name=self._TEST_NET_NAME) + net_count = len(networks['networks']) + assert net_count == 0, ( + "Expected zero networks, found {}".format(net_count)) class NeutronApiTest(NeutronCreateNetworkTest): diff --git a/zaza/openstack/charm_tests/neutron_arista/tests.py b/zaza/openstack/charm_tests/neutron_arista/tests.py index c47c9c8..4f6b1de 100644 --- a/zaza/openstack/charm_tests/neutron_arista/tests.py +++ b/zaza/openstack/charm_tests/neutron_arista/tests.py @@ -38,16 +38,16 @@ class NeutronCreateAristaNetworkTest(neutron_tests.NeutronCreateNetworkTest): with attempt: cls.neutron_client.list_networks() - def _test_400_additional_validation(self, expected_network_names): - """Arista-specific assertions for test_400_create_network. - - :type expected_network_names: List[str] - """ - logging.info("Querying Arista CVX's networks...") + def _assert_test_network_exists_and_return_id(self): actual_network_names = arista_utils.query_fixture_networks( arista_utils.fixture_ip_addr()) + self.assertEqual(actual_network_names, [self._TEST_NET_NAME]) + return super(NeutronCreateAristaNetworkTest, + self)._assert_test_network_exists_and_return_id() - # NOTE(lourot): the assertion name is misleading as it's not only - # checking the item count but also that all items are present in - # both lists, without checking the order. - self.assertCountEqual(actual_network_names, expected_network_names) + def _assert_test_network_doesnt_exist(self): + actual_network_names = arista_utils.query_fixture_networks( + arista_utils.fixture_ip_addr()) + self.assertEqual(actual_network_names, []) + super(NeutronCreateAristaNetworkTest, + self)._assert_test_network_doesnt_exist() From a04cc1f077abe1a96859744ca6039960e0356c9a Mon Sep 17 00:00:00 2001 From: Aurelien Lourot Date: Tue, 23 Jun 2020 13:11:50 +0200 Subject: [PATCH 11/59] Cosmetic improvements --- zaza/openstack/charm_tests/neutron_arista/setup.py | 4 ++-- zaza/openstack/charm_tests/neutron_arista/utils.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/zaza/openstack/charm_tests/neutron_arista/setup.py b/zaza/openstack/charm_tests/neutron_arista/setup.py index 50878e6..b990751 100644 --- a/zaza/openstack/charm_tests/neutron_arista/setup.py +++ b/zaza/openstack/charm_tests/neutron_arista/setup.py @@ -24,8 +24,8 @@ def test_fixture(): """Pass arista-virt-test-fixture's IP address to neutron-arista.""" fixture_ip_addr = arista_utils.fixture_ip_addr() logging.info( - "{}'s IP address is '{}'. Passing it to neutron-arista...".format( - arista_utils.FIXTURE_APP_NAME, fixture_ip_addr)) + "{}'s IP address is '{}'. Passing it to neutron-arista..." + .format(arista_utils.FIXTURE_APP_NAME, fixture_ip_addr)) zaza.model.set_application_config('neutron-arista', {'eapi-host': fixture_ip_addr}) diff --git a/zaza/openstack/charm_tests/neutron_arista/utils.py b/zaza/openstack/charm_tests/neutron_arista/utils.py index 3c68f33..78732d6 100644 --- a/zaza/openstack/charm_tests/neutron_arista/utils.py +++ b/zaza/openstack/charm_tests/neutron_arista/utils.py @@ -60,8 +60,8 @@ def query_fixture_networks(ip_addr): ) result = [] - for _, region in response.json()['result'][0]['regions'].items(): - for _, tenant in region['tenants'].items(): - for _, network in tenant['tenantNetworks'].items(): + for region in response.json()['result'][0]['regions'].values(): + for tenant in region['tenants'].values(): + for network in tenant['tenantNetworks'].values(): result.append(network['networkName']) return result From b1ccc4bd24522c11b138c3a713b8ab09d11417bc Mon Sep 17 00:00:00 2001 From: Aurelien Lourot Date: Tue, 23 Jun 2020 15:50:34 +0200 Subject: [PATCH 12/59] Rename neutron arista charm --- zaza/openstack/charm_tests/neutron_arista/__init__.py | 2 +- zaza/openstack/charm_tests/neutron_arista/setup.py | 8 ++++---- zaza/openstack/charm_tests/neutron_arista/tests.py | 2 +- zaza/openstack/charm_tests/neutron_arista/utils.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/zaza/openstack/charm_tests/neutron_arista/__init__.py b/zaza/openstack/charm_tests/neutron_arista/__init__.py index 8d3f5c9..c0eae4e 100644 --- a/zaza/openstack/charm_tests/neutron_arista/__init__.py +++ b/zaza/openstack/charm_tests/neutron_arista/__init__.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Collection of code for setting up and testing neutron-arista.""" +"""Collection of code for setting up and testing neutron-api-plugin-arista.""" diff --git a/zaza/openstack/charm_tests/neutron_arista/setup.py b/zaza/openstack/charm_tests/neutron_arista/setup.py index b990751..2b15d5a 100644 --- a/zaza/openstack/charm_tests/neutron_arista/setup.py +++ b/zaza/openstack/charm_tests/neutron_arista/setup.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Code for setting up neutron-arista.""" +"""Code for setting up neutron-api-plugin-arista.""" import logging import tenacity @@ -21,12 +21,12 @@ import zaza.openstack.charm_tests.neutron_arista.utils as arista_utils def test_fixture(): - """Pass arista-virt-test-fixture's IP address to neutron-arista.""" + """Pass arista-virt-test-fixture's IP address to Neutron.""" fixture_ip_addr = arista_utils.fixture_ip_addr() logging.info( - "{}'s IP address is '{}'. Passing it to neutron-arista..." + "{}'s IP address is '{}'. Passing it to neutron-api-plugin-arista..." .format(arista_utils.FIXTURE_APP_NAME, fixture_ip_addr)) - zaza.model.set_application_config('neutron-arista', + zaza.model.set_application_config('neutron-api-plugin-arista', {'eapi-host': fixture_ip_addr}) logging.info('Waiting for {} to become ready...'.format( diff --git a/zaza/openstack/charm_tests/neutron_arista/tests.py b/zaza/openstack/charm_tests/neutron_arista/tests.py index 4f6b1de..3f78bf7 100644 --- a/zaza/openstack/charm_tests/neutron_arista/tests.py +++ b/zaza/openstack/charm_tests/neutron_arista/tests.py @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Encapsulating `neutron-arista` testing.""" +"""Encapsulating `neutron-api-plugin-arista` testing.""" import logging import tenacity diff --git a/zaza/openstack/charm_tests/neutron_arista/utils.py b/zaza/openstack/charm_tests/neutron_arista/utils.py index 78732d6..bc86e4b 100644 --- a/zaza/openstack/charm_tests/neutron_arista/utils.py +++ b/zaza/openstack/charm_tests/neutron_arista/utils.py @@ -42,7 +42,7 @@ def query_fixture_networks(ip_addr): session.auth = (_FIXTURE_LOGIN, _FIXTURE_PASSWORD) data = { - 'id': 'Zaza neutron-arista tests', + 'id': 'Zaza neutron-api-plugin-arista tests', 'method': 'runCmds', 'jsonrpc': '2.0', 'params': { From 87d5667990c2eab293bb6e97716dcfb0b27ec4a4 Mon Sep 17 00:00:00 2001 From: Aurelien Lourot Date: Wed, 24 Jun 2020 10:38:02 +0200 Subject: [PATCH 13/59] Download Arista image --- .../charm_tests/neutron_arista/setup.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/zaza/openstack/charm_tests/neutron_arista/setup.py b/zaza/openstack/charm_tests/neutron_arista/setup.py index 2b15d5a..a1ade9e 100644 --- a/zaza/openstack/charm_tests/neutron_arista/setup.py +++ b/zaza/openstack/charm_tests/neutron_arista/setup.py @@ -15,9 +15,41 @@ """Code for setting up neutron-api-plugin-arista.""" import logging +import os import tenacity import zaza import zaza.openstack.charm_tests.neutron_arista.utils as arista_utils +import zaza.openstack.utilities.openstack as openstack_utils + + +def download_arista_image(): + """Download arista-cvx-virt-test.qcow2 from a web server. + + If TEST_ARISTA_IMAGE_LOCAL isn't set, set it to + `/tmp/arista-cvx-virt-test.qcow2`. If TEST_ARISTA_IMAGE_REMOTE is set (e.g. + to `http://example.com/swift/v1/images/arista-cvx-virt-test.qcow2`), + download it to TEST_ARISTA_IMAGE_LOCAL. + """ + try: + os.environ['TEST_ARISTA_IMAGE_LOCAL'] + except KeyError: + os.environ['TEST_ARISTA_IMAGE_LOCAL'] = '' + if not os.environ['TEST_ARISTA_IMAGE_LOCAL']: + os.environ['TEST_ARISTA_IMAGE_LOCAL'] \ + = '/tmp/arista-cvx-virt-test.qcow2' + + try: + if os.environ['TEST_ARISTA_IMAGE_REMOTE']: + logging.info('Downloading Arista image from {}' + .format(os.environ['TEST_ARISTA_IMAGE_REMOTE'])) + openstack_utils.download_image( + os.environ['TEST_ARISTA_IMAGE_REMOTE'], + os.environ['TEST_ARISTA_IMAGE_LOCAL']) + except KeyError: + pass + + logging.info('Arista image can be found at {}' + .format(os.environ['TEST_ARISTA_IMAGE_LOCAL'])) def test_fixture(): From 68af0d2079089fbbc4f3d1c6cc4bb1fd73ca5ce9 Mon Sep 17 00:00:00 2001 From: Aurelien Lourot Date: Wed, 24 Jun 2020 16:27:35 +0200 Subject: [PATCH 14/59] Clarification --- zaza/openstack/charm_tests/neutron_arista/setup.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/zaza/openstack/charm_tests/neutron_arista/setup.py b/zaza/openstack/charm_tests/neutron_arista/setup.py index a1ade9e..4b8dfe7 100644 --- a/zaza/openstack/charm_tests/neutron_arista/setup.py +++ b/zaza/openstack/charm_tests/neutron_arista/setup.py @@ -25,10 +25,13 @@ import zaza.openstack.utilities.openstack as openstack_utils def download_arista_image(): """Download arista-cvx-virt-test.qcow2 from a web server. - If TEST_ARISTA_IMAGE_LOCAL isn't set, set it to - `/tmp/arista-cvx-virt-test.qcow2`. If TEST_ARISTA_IMAGE_REMOTE is set (e.g. - to `http://example.com/swift/v1/images/arista-cvx-virt-test.qcow2`), - download it to TEST_ARISTA_IMAGE_LOCAL. + The download will happen only if the env var TEST_ARISTA_IMAGE_REMOTE has + been set, so you don't have to set it if you already have the image + locally. + + If the env var TEST_ARISTA_IMAGE_LOCAL isn't set, it will be set to + `/tmp/arista-cvx-virt-test.qcow2`. This is where the image will be + downloaded to if TEST_ARISTA_IMAGE_REMOTE has been set. """ try: os.environ['TEST_ARISTA_IMAGE_LOCAL'] @@ -46,6 +49,8 @@ def download_arista_image(): os.environ['TEST_ARISTA_IMAGE_REMOTE'], os.environ['TEST_ARISTA_IMAGE_LOCAL']) except KeyError: + # TEST_ARISTA_IMAGE_REMOTE isn't set, which means the image is already + # available at TEST_ARISTA_IMAGE_LOCAL pass logging.info('Arista image can be found at {}' From b69455d3e83e4c10755d7fc2a17a9ad7f8d286f0 Mon Sep 17 00:00:00 2001 From: Alvaro Uria Date: Tue, 30 Jun 2020 12:10:26 +0200 Subject: [PATCH 15/59] CinderTests: Support enabled-services option charm-cinder supports units with a limited number of services running (api/scheduler on a unit, volume service on another one). Functional tests wait for all services to be restarted when a subset of them should be observed. Partial-Bug: #1779310 --- zaza/openstack/charm_tests/cinder/tests.py | 28 ++++++++++++---------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/zaza/openstack/charm_tests/cinder/tests.py b/zaza/openstack/charm_tests/cinder/tests.py index 4cbf7c0..552d8a2 100644 --- a/zaza/openstack/charm_tests/cinder/tests.py +++ b/zaza/openstack/charm_tests/cinder/tests.py @@ -217,12 +217,22 @@ class CinderTests(test_utils.OpenStackBaseTest): @property def services(self): """Return a list services for the selected OpenStack release.""" - services = ['cinder-scheduler', 'cinder-volume'] - if (openstack_utils.get_os_release() >= - openstack_utils.get_os_release('xenial_ocata')): - services.append('apache2') + current_value = zaza.model.get_application_config( + self.application_name)['enabled-services']['value'] + + if current_value == "all": + services = ['cinder-scheduler', 'cinder-volume', 'cinder-api'] else: - services.append('cinder-api') + services = ['cinder-{}'.format(svc) + for svc in ('api', 'scheduler', 'volume') + if svc in current_value] + + if ('cinder-api' in services and + (openstack_utils.get_os_release() >= + openstack_utils.get_os_release('xenial_ocata'))): + services.remove('cinder-api') + services.append('apache2') + return services def test_900_restart_on_config_change(self): @@ -246,13 +256,7 @@ class CinderTests(test_utils.OpenStackBaseTest): Pause service and check services are stopped then resume and check they are started """ - services = ['cinder-scheduler', 'cinder-volume'] - if (openstack_utils.get_os_release() >= - openstack_utils.get_os_release('xenial_ocata')): - services.append('apache2') - else: - services.append('cinder-api') - with self.pause_resume(services): + with self.pause_resume(self.services): logging.info("Testing pause resume") From e184ad74985b0b543b42f2626ef5d370137df428 Mon Sep 17 00:00:00 2001 From: Aurelien Lourot Date: Tue, 30 Jun 2020 16:28:15 +0200 Subject: [PATCH 16/59] Wait for neutron to be connected to Arista before querying it --- zaza/openstack/charm_tests/neutron_arista/setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zaza/openstack/charm_tests/neutron_arista/setup.py b/zaza/openstack/charm_tests/neutron_arista/setup.py index 4b8dfe7..286a23e 100644 --- a/zaza/openstack/charm_tests/neutron_arista/setup.py +++ b/zaza/openstack/charm_tests/neutron_arista/setup.py @@ -68,6 +68,8 @@ def test_fixture(): logging.info('Waiting for {} to become ready...'.format( arista_utils.FIXTURE_APP_NAME)) + zaza.model.wait_for_agent_status() + zaza.model.wait_for_application_states() for attempt in tenacity.Retrying( wait=tenacity.wait_fixed(10), # seconds stop=tenacity.stop_after_attempt(30), From e75902ac219c012284125e26ea809569bd1cc2ae Mon Sep 17 00:00:00 2001 From: Liam Young Date: Tue, 30 Jun 2020 14:42:25 +0000 Subject: [PATCH 17/59] Set application_name for masakari tests Set application_name for masakari tests so that the tests can be run without relying on getting the application name from the tests.yaml. --- zaza/openstack/charm_tests/masakari/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zaza/openstack/charm_tests/masakari/tests.py b/zaza/openstack/charm_tests/masakari/tests.py index c2a3d85..d9490d2 100644 --- a/zaza/openstack/charm_tests/masakari/tests.py +++ b/zaza/openstack/charm_tests/masakari/tests.py @@ -38,7 +38,7 @@ class MasakariTest(test_utils.OpenStackBaseTest): @classmethod def setUpClass(cls): """Run class setup for running tests.""" - super(MasakariTest, cls).setUpClass() + super(MasakariTest, cls).setUpClass(application_name="masakari") cls.current_release = openstack_utils.get_os_release() cls.keystone_session = openstack_utils.get_overcloud_keystone_session() cls.model_name = zaza.model.get_juju_model() From 2331907134fefc9f2922df1ae86ce0f69c08f201 Mon Sep 17 00:00:00 2001 From: Camille Rodriguez <42066843+camille-rodriguez@users.noreply.github.com> Date: Tue, 30 Jun 2020 10:30:39 -0500 Subject: [PATCH 18/59] Add Gnocchi S3 storage backend functional test (#334) Add tests for Gnocchi S3 storage backend support. --- zaza/openstack/charm_tests/gnocchi/setup.py | 69 +++++++++++++++++++++ zaza/openstack/charm_tests/gnocchi/tests.py | 54 +++++++++++++++- 2 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 zaza/openstack/charm_tests/gnocchi/setup.py diff --git a/zaza/openstack/charm_tests/gnocchi/setup.py b/zaza/openstack/charm_tests/gnocchi/setup.py new file mode 100644 index 0000000..4995f22 --- /dev/null +++ b/zaza/openstack/charm_tests/gnocchi/setup.py @@ -0,0 +1,69 @@ +# Copyright 2020 Canonical Ltd. +# +# 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. + +"""Setup for Gnocchi tests.""" + +import logging + +import zaza.model as model +import zaza.openstack.utilities.openstack as openstack_utils + + +def configure_s3_backend(): + """Inject S3 parameters from Swift for Gnocchi config.""" + session = openstack_utils.get_overcloud_keystone_session() + ks_client = openstack_utils.get_keystone_session_client(session) + + logging.info('Retrieving S3 connection data from Swift') + token_data = ks_client.tokens.get_token_data(session.get_token()) + project_id = token_data['token']['project']['id'] + user_id = token_data['token']['user']['id'] + + # Store URL to service providing S3 compatible API + for entry in token_data['token']['catalog']: + if entry['type'] == 's3': + for endpoint in entry['endpoints']: + if endpoint['interface'] == 'public': + s3_region = endpoint['region'] + s3_endpoint = endpoint['url'] + + # Create AWS compatible application credentials in Keystone + ec2_creds = ks_client.ec2.create(user_id, project_id) + + logging.info('Changing Gnocchi charm config to connect to S3') + model.set_application_config( + 'gnocchi', + {'s3-endpoint-url': s3_endpoint, + 's3-region-name': s3_region, + 's3-access-key-id': ec2_creds.access, + 's3-secret-access-key': ec2_creds.secret} + ) + logging.info('Waiting for units to execute config-changed hook') + model.wait_for_agent_status() + logging.info('Waiting for units to reach target states') + model.wait_for_application_states( + states={ + 'gnocchi': { + 'workload-status-': 'active', + 'workload-status-message': 'Unit is ready' + }, + 'ceilometer': { + 'workload-status': 'blocked', + 'workload-status-message': 'Run the ' + + 'ceilometer-upgrade action on the leader ' + + 'to initialize ceilometer and gnocchi' + } + } + ) + model.block_until_all_units_idle() diff --git a/zaza/openstack/charm_tests/gnocchi/tests.py b/zaza/openstack/charm_tests/gnocchi/tests.py index f789cff..680bf3c 100644 --- a/zaza/openstack/charm_tests/gnocchi/tests.py +++ b/zaza/openstack/charm_tests/gnocchi/tests.py @@ -16,9 +16,11 @@ """Encapsulate Gnocchi testing.""" +import boto3 import logging - +import pprint from gnocchiclient.v1 import client as gnocchi_client + import zaza.openstack.charm_tests.test_utils as test_utils import zaza.openstack.utilities.openstack as openstack_utils @@ -58,3 +60,53 @@ class GnocchiTest(test_utils.OpenStackBaseTest): """ with self.pause_resume(self.services): logging.info("Testing pause and resume") + + +class GnocchiS3Test(test_utils.OpenStackBaseTest): + """Test Gnocchi for S3 storage backend.""" + + @classmethod + def setUpClass(cls): + """Run class setup for running tests.""" + super(GnocchiS3Test, cls).setUpClass() + + session = openstack_utils.get_overcloud_keystone_session() + ks_client = openstack_utils.get_keystone_session_client(session) + + # Get token data so we can glean our user_id and project_id + token_data = ks_client.tokens.get_token_data(session.get_token()) + project_id = token_data['token']['project']['id'] + user_id = token_data['token']['user']['id'] + + # Store URL to service providing S3 compatible API + for entry in token_data['token']['catalog']: + if entry['type'] == 's3': + for endpoint in entry['endpoints']: + if endpoint['interface'] == 'public': + cls.s3_region = endpoint['region'] + cls.s3_endpoint = endpoint['url'] + + # Create AWS compatible application credentials in Keystone + cls.ec2_creds = ks_client.ec2.create(user_id, project_id) + + def test_s3_list_gnocchi_buckets(self): + """Verify that the gnocchi buckets were created in the S3 backend.""" + kwargs = { + 'region_name': self.s3_region, + 'aws_access_key_id': self.ec2_creds.access, + 'aws_secret_access_key': self.ec2_creds.secret, + 'endpoint_url': self.s3_endpoint, + 'verify': self.cacert, + } + s3_client = boto3.client('s3', **kwargs) + + bucket_names = ['gnocchi-measure', 'gnocchi-aggregates'] + # Validate their presence + bucket_list = s3_client.list_buckets() + logging.info(pprint.pformat(bucket_list)) + for bkt in bucket_list['Buckets']: + for gnocchi_bkt in bucket_names: + if bkt['Name'] == gnocchi_bkt: + break + else: + AssertionError('Bucket "{}" not found'.format(gnocchi_bkt)) From 1e682e94801927ed05f3a8074a33b2c8a8c5cc1b Mon Sep 17 00:00:00 2001 From: Aurelien Lourot Date: Wed, 1 Jul 2020 11:07:49 +0200 Subject: [PATCH 19/59] Fix application name in trace --- zaza/openstack/charm_tests/neutron_arista/setup.py | 9 +++++---- zaza/openstack/charm_tests/neutron_arista/utils.py | 3 ++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/zaza/openstack/charm_tests/neutron_arista/setup.py b/zaza/openstack/charm_tests/neutron_arista/setup.py index 286a23e..4e35d33 100644 --- a/zaza/openstack/charm_tests/neutron_arista/setup.py +++ b/zaza/openstack/charm_tests/neutron_arista/setup.py @@ -61,13 +61,14 @@ def test_fixture(): """Pass arista-virt-test-fixture's IP address to Neutron.""" fixture_ip_addr = arista_utils.fixture_ip_addr() logging.info( - "{}'s IP address is '{}'. Passing it to neutron-api-plugin-arista..." - .format(arista_utils.FIXTURE_APP_NAME, fixture_ip_addr)) - zaza.model.set_application_config('neutron-api-plugin-arista', + "{}'s IP address is '{}'. Passing it to {}..." + .format(arista_utils.FIXTURE_APP_NAME, fixture_ip_addr, + arista_utils.PLUGIN_APP_NAME)) + zaza.model.set_application_config(arista_utils.PLUGIN_APP_NAME, {'eapi-host': fixture_ip_addr}) logging.info('Waiting for {} to become ready...'.format( - arista_utils.FIXTURE_APP_NAME)) + arista_utils.PLUGIN_APP_NAME)) zaza.model.wait_for_agent_status() zaza.model.wait_for_application_states() for attempt in tenacity.Retrying( diff --git a/zaza/openstack/charm_tests/neutron_arista/utils.py b/zaza/openstack/charm_tests/neutron_arista/utils.py index bc86e4b..6fb77b3 100644 --- a/zaza/openstack/charm_tests/neutron_arista/utils.py +++ b/zaza/openstack/charm_tests/neutron_arista/utils.py @@ -20,6 +20,7 @@ import urllib3 import zaza FIXTURE_APP_NAME = 'arista-virt-test-fixture' +PLUGIN_APP_NAME = 'neutron-api-plugin-arista' def fixture_ip_addr(): @@ -42,7 +43,7 @@ def query_fixture_networks(ip_addr): session.auth = (_FIXTURE_LOGIN, _FIXTURE_PASSWORD) data = { - 'id': 'Zaza neutron-api-plugin-arista tests', + 'id': 'Zaza {} tests'.format(PLUGIN_APP_NAME), 'method': 'runCmds', 'jsonrpc': '2.0', 'params': { From 58182b86b893ed09fe7b397ebd11aeab4c1673da Mon Sep 17 00:00:00 2001 From: Liam Young Date: Wed, 1 Jul 2020 13:38:19 +0100 Subject: [PATCH 20/59] Deprecate zaza.openstack.utilities.juju (#340) This is the 3rd change in the effort to deprecate zaza.openstack.utilities.juju in favour of zaza.utilities.juju. All functions now just wrap their equivalents in zaza.utilities.juju and a decorator has been added which logs a warning if the function is used. --- .../utilities/test_zaza_utilities_juju.py | 312 ------------------ zaza/openstack/utilities/juju.py | 237 +++++-------- 2 files changed, 82 insertions(+), 467 deletions(-) delete mode 100644 unit_tests/utilities/test_zaza_utilities_juju.py diff --git a/unit_tests/utilities/test_zaza_utilities_juju.py b/unit_tests/utilities/test_zaza_utilities_juju.py deleted file mode 100644 index 4255f9e..0000000 --- a/unit_tests/utilities/test_zaza_utilities_juju.py +++ /dev/null @@ -1,312 +0,0 @@ -# Copyright 2018 Canonical Ltd. -# -# 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. - -import mock -import unit_tests.utils as ut_utils -from zaza.openstack.utilities import juju as juju_utils - - -class TestJujuUtils(ut_utils.BaseTestCase): - - def setUp(self): - super(TestJujuUtils, self).setUp() - - # Juju Status Object and data - self.key = "instance-id" - self.key_data = "machine-uuid" - self.machine = "1" - self.machine_data = {self.key: self.key_data} - self.unit = "app/1" - self.unit_data = {"machine": self.machine} - self.application = "app" - self.application_data = {"units": {self.unit: self.unit_data}} - self.subordinate_application = "subordinate_application" - self.subordinate_application_data = { - "subordinate-to": [self.application]} - self.juju_status = mock.MagicMock() - self.juju_status.name = "juju_status_object" - self.juju_status.applications.get.return_value = self.application_data - self.juju_status.machines.get.return_value = self.machine_data - - # Model - self.patch_object(juju_utils, "model") - self.model_name = "model-name" - self.model.get_juju_model.return_value = self.model_name - self.model.get_status.return_value = self.juju_status - self.run_output = {"Code": "0", "Stderr": "", "Stdout": "RESULT"} - self.error_run_output = {"Code": "1", "Stderr": "ERROR", "Stdout": ""} - self.model.run_on_unit.return_value = self.run_output - - # Clouds - self.cloud_name = "FakeCloudName" - self.cloud_type = "FakeCloudType" - self.clouds = { - "clouds": - {self.cloud_name: - {"type": self.cloud_type}}} - - # Controller - self.patch_object(juju_utils, "controller") - self.controller.get_cloud.return_value = self.cloud_name - - def test_get_application_status(self): - self.patch_object(juju_utils, "get_full_juju_status") - self.get_full_juju_status.return_value = self.juju_status - - # Full status juju object return - self.assertEqual( - juju_utils.get_application_status(), self.juju_status) - self.get_full_juju_status.assert_called_once() - - # Application only dictionary return - self.assertEqual( - juju_utils.get_application_status(application=self.application), - self.application_data) - - # Unit no application dictionary return - self.assertEqual( - juju_utils.get_application_status(unit=self.unit), - self.unit_data) - - def test_get_cloud_configs(self): - self.patch_object(juju_utils.Path, "home") - self.patch_object(juju_utils.generic_utils, "get_yaml_config") - self.get_yaml_config.return_value = self.clouds - - # All the cloud configs - self.assertEqual(juju_utils.get_cloud_configs(), self.clouds) - - # With cloud specified - self.assertEqual(juju_utils.get_cloud_configs(self.cloud_name), - self.clouds["clouds"][self.cloud_name]) - - def test_get_full_juju_status(self): - self.assertEqual(juju_utils.get_full_juju_status(), self.juju_status) - self.model.get_status.assert_called_once_with(model_name=None) - - def test_get_machines_for_application(self): - self.patch_object(juju_utils, "get_application_status") - self.get_application_status.return_value = self.application_data - - # Machine data - self.assertEqual( - next(juju_utils.get_machines_for_application(self.application)), - self.machine) - self.get_application_status.assert_called_once() - - # Subordinate application has no units - def _get_application_status(application, model_name=None): - _apps = { - self.application: self.application_data, - self.subordinate_application: - self.subordinate_application_data} - return _apps[application] - self.get_application_status.side_effect = _get_application_status - - self.assertEqual( - next(juju_utils.get_machines_for_application( - self.subordinate_application)), - self.machine) - - def test_get_unit_name_from_host_name(self): - unit_mock1 = mock.MagicMock() - unit_mock1.data = {'machine-id': 12} - unit_mock1.entity_id = 'myapp/2' - unit_mock2 = mock.MagicMock() - unit_mock2.data = {'machine-id': 15} - unit_mock2.entity_id = 'myapp/5' - self.model.get_units.return_value = [unit_mock1, unit_mock2] - self.assertEqual( - juju_utils.get_unit_name_from_host_name('juju-model-12', 'myapp'), - 'myapp/2') - - def test_get_machine_status(self): - self.patch_object(juju_utils, "get_full_juju_status") - self.get_full_juju_status.return_value = self.juju_status - - # All machine data - self.assertEqual( - juju_utils.get_machine_status(self.machine), - self.machine_data) - self.get_full_juju_status.assert_called_once() - - # Request a specific key - self.assertEqual( - juju_utils.get_machine_status(self.machine, self.key), - self.key_data) - - def test_get_machine_uuids_for_application(self): - self.patch_object(juju_utils, "get_machines_for_application") - self.get_machines_for_application.return_value = [self.machine] - - self.assertEqual( - next(juju_utils.get_machine_uuids_for_application( - self.application)), - self.machine_data.get("instance-id")) - self.get_machines_for_application.assert_called_once_with( - self.application, model_name=None) - - def test_get_provider_type(self): - self.patch_object(juju_utils, "get_cloud_configs") - self.get_cloud_configs.return_value = {"type": self.cloud_type} - self.assertEqual(juju_utils.get_provider_type(), - self.cloud_type) - self.get_cloud_configs.assert_called_once_with(self.cloud_name) - - def test_remote_run(self): - _cmd = "do the thing" - - # Success - self.assertEqual(juju_utils.remote_run(self.unit, _cmd), - self.run_output["Stdout"]) - self.model.run_on_unit.assert_called_once_with( - self.unit, _cmd, timeout=None, model_name=None) - - # Non-fatal failure - self.model.run_on_unit.return_value = self.error_run_output - self.assertEqual( - juju_utils.remote_run( - self.unit, - _cmd, - fatal=False, - model_name=None), - self.error_run_output["Stderr"]) - - # Fatal failure - with self.assertRaises(Exception): - juju_utils.remote_run(self.unit, _cmd, fatal=True) - - def test_get_unit_names(self): - self.patch('zaza.model.get_first_unit_name', new_callable=mock.Mock(), - name='_get_first_unit_name') - juju_utils._get_unit_names(['aunit/0', 'otherunit/0']) - self.assertFalse(self._get_first_unit_name.called) - - def test_get_unit_names_called_with_application_name(self): - self.patch_object(juju_utils, 'model') - juju_utils._get_unit_names(['aunit', 'otherunit/0']) - self.model.get_first_unit_name.assert_called() - - def test_get_relation_from_unit(self): - self.patch_object(juju_utils, '_get_unit_names') - self.patch_object(juju_utils, 'yaml') - self.patch_object(juju_utils, 'model') - self._get_unit_names.return_value = ['aunit/0', 'otherunit/0'] - data = {'foo': 'bar'} - self.model.get_relation_id.return_value = 42 - self.model.run_on_unit.return_value = {'Code': 0, 'Stdout': str(data)} - juju_utils.get_relation_from_unit('aunit/0', 'otherunit/0', - 'arelation') - self.model.run_on_unit.assert_called_with( - 'aunit/0', - 'relation-get --format=yaml -r "42" - "otherunit/0"', - model_name=None) - self.yaml.safe_load.assert_called_with(str(data)) - - def test_get_relation_from_unit_fails(self): - self.patch_object(juju_utils, '_get_unit_names') - self.patch_object(juju_utils, 'yaml') - self.patch_object(juju_utils, 'model') - self._get_unit_names.return_value = ['aunit/0', 'otherunit/0'] - self.model.get_relation_id.return_value = 42 - self.model.run_on_unit.return_value = {'Code': 1, 'Stderr': 'ERROR'} - with self.assertRaises(Exception): - juju_utils.get_relation_from_unit('aunit/0', 'otherunit/0', - 'arelation') - self.model.run_on_unit.assert_called_with( - 'aunit/0', - 'relation-get --format=yaml -r "42" - "otherunit/0"', - model_name=None) - self.assertFalse(self.yaml.safe_load.called) - - def test_leader_get(self): - self.patch_object(juju_utils, 'yaml') - self.patch_object(juju_utils, 'model') - data = {'foo': 'bar'} - self.model.run_on_leader.return_value = { - 'Code': 0, 'Stdout': str(data)} - juju_utils.leader_get('application') - self.model.run_on_leader.assert_called_with( - 'application', 'leader-get --format=yaml ', model_name=None) - self.yaml.safe_load.assert_called_with(str(data)) - - def test_leader_get_key(self): - self.patch_object(juju_utils, 'yaml') - self.patch_object(juju_utils, 'model') - data = {'foo': 'bar'} - self.model.run_on_leader.return_value = { - 'Code': 0, 'Stdout': data['foo']} - juju_utils.leader_get('application', 'foo') - self.model.run_on_leader.assert_called_with( - 'application', 'leader-get --format=yaml foo', model_name=None) - self.yaml.safe_load.assert_called_with(data['foo']) - - def test_leader_get_fails(self): - self.patch_object(juju_utils, 'yaml') - self.patch_object(juju_utils, 'model') - self.model.run_on_leader.return_value = { - 'Code': 1, 'Stderr': 'ERROR'} - with self.assertRaises(Exception): - juju_utils.leader_get('application') - self.model.run_on_leader.assert_called_with( - 'application', 'leader-get --format=yaml ', - model_name=None) - self.assertFalse(self.yaml.safe_load.called) - - def test_get_machine_series(self): - self.patch( - 'zaza.openstack.utilities.juju.get_machine_status', - new_callable=mock.MagicMock(), - name='_get_machine_status' - ) - self._get_machine_status.return_value = 'xenial' - expected = 'xenial' - actual = juju_utils.get_machine_series('6') - self._get_machine_status.assert_called_with( - machine='6', - key='series', - model_name=None - ) - self.assertEqual(expected, actual) - - def test_get_subordinate_units(self): - juju_status = mock.MagicMock() - juju_status.applications = { - 'nova-compute': { - 'units': { - 'nova-compute/0': { - 'subordinates': { - 'neutron-openvswitch/2': { - 'charm': 'cs:neutron-openvswitch-22'}}}}}, - 'cinder': { - 'units': { - 'cinder/1': { - 'subordinates': { - 'cinder-hacluster/0': { - 'charm': 'cs:hacluster-42'}, - 'cinder-ceph/3': { - 'charm': 'cs:cinder-ceph-2'}}}}}, - } - self.assertEqual( - sorted(juju_utils.get_subordinate_units( - ['nova-compute/0', 'cinder/1'], - status=juju_status)), - sorted(['neutron-openvswitch/2', 'cinder-hacluster/0', - 'cinder-ceph/3'])) - self.assertEqual( - juju_utils.get_subordinate_units( - ['nova-compute/0', 'cinder/1'], - charm_name='ceph', - status=juju_status), - ['cinder-ceph/3']) diff --git a/zaza/openstack/utilities/juju.py b/zaza/openstack/utilities/juju.py index 3bdf4b8..073a26f 100644 --- a/zaza/openstack/utilities/juju.py +++ b/zaza/openstack/utilities/juju.py @@ -13,18 +13,31 @@ # 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. -"""Module for interacting with juju.""" -import os -from pathlib import Path -import yaml +"""Deprecated, please use zaza.utilities.juju.""" -from zaza import ( - model, - controller, -) -from zaza.openstack.utilities import generic as generic_utils +import logging +import functools + +import zaza.utilities.juju +def deprecate(): + """Add a deprecation warning to wrapped function.""" + def wrap(f): + + @functools.wraps(f) + def wrapped_f(*args, **kwargs): + msg = ( + "{} from zaza.openstack.utilities.juju is deprecated. " + "Please use the equivalent from zaza.utilities.juju".format( + f.__name__)) + logging.warning(msg) + return f(*args, **kwargs) + return wrapped_f + return wrap + + +@deprecate() def get_application_status(application=None, unit=None, model_name=None): """Return the juju status for an application. @@ -37,39 +50,29 @@ def get_application_status(application=None, unit=None, model_name=None): :returns: Juju status output for an application :rtype: dict """ - status = get_full_juju_status() - - if unit and not application: - application = unit.split("/")[0] - - if application: - status = status.applications.get(application) - if unit: - status = status.get("units").get(unit) - return status + return zaza.utilities.juju.get_application_status( + application=application, + unit=unit, + model_name=model_name) -def get_application_ip(application): +@deprecate() +def get_application_ip(application, model_name=None): """Get the application's IP address. :param application: Application name :type application: str + :param model_name: Name of model to query. + :type model_name: str :returns: Application's IP address :rtype: str """ - try: - app_config = model.get_application_config(application) - except KeyError: - return '' - vip = app_config.get("vip").get("value") - if vip: - ip = vip - else: - unit = model.get_units(application)[0] - ip = unit.public_address - return ip + return zaza.utilities.juju.get_application_ip( + application, + model_name=model_name) +@deprecate() def get_cloud_configs(cloud=None): """Get cloud configuration from local clouds.yaml. @@ -81,14 +84,11 @@ def get_cloud_configs(cloud=None): :returns: Dictionary of cloud configuration :rtype: dict """ - home = str(Path.home()) - cloud_config = os.path.join(home, ".local", "share", "juju", "clouds.yaml") - if cloud: - return generic_utils.get_yaml_config(cloud_config)["clouds"].get(cloud) - else: - return generic_utils.get_yaml_config(cloud_config) + return zaza.utilities.juju.get_cloud_configs( + cloud=cloud) +@deprecate() def get_full_juju_status(model_name=None): """Return the full juju status output. @@ -97,10 +97,11 @@ def get_full_juju_status(model_name=None): :returns: Full juju status output :rtype: dict """ - status = model.get_status(model_name=model_name) - return status + return zaza.utilities.juju.get_full_juju_status( + model_name=model_name) +@deprecate() def get_machines_for_application(application, model_name=None): """Return machines for a given application. @@ -111,20 +112,12 @@ def get_machines_for_application(application, model_name=None): :returns: machines for an application :rtype: Iterator[str] """ - status = get_application_status(application, model_name=model_name) - if not status: - return - - # libjuju juju status no longer has units for subordinate charms - # Use the application it is subordinate-to to find machines - if not status.get("units") and status.get("subordinate-to"): - status = get_application_status(status.get("subordinate-to")[0], - model_name=model_name) - - for unit in status.get("units").keys(): - yield status.get("units").get(unit).get("machine") + return zaza.utilities.juju.get_machines_for_application( + application, + model_name=model_name) +@deprecate() def get_unit_name_from_host_name(host_name, application, model_name=None): """Return the juju unit name corresponding to a hostname. @@ -135,17 +128,13 @@ def get_unit_name_from_host_name(host_name, application, model_name=None): :param model_name: Name of model to query. :type model_name: str """ - # Assume that a juju managed hostname always ends in the machine number and - # remove the domain name if it present. - machine_number = host_name.split('-')[-1].split('.')[0] - unit_names = [ - u.entity_id - for u in model.get_units(application_name=application, - model_name=model_name) - if int(u.data['machine-id']) == int(machine_number)] - return unit_names[0] + return zaza.utilities.juju.get_unit_name_from_host_name( + host_name, + application, + model_name=model_name) +@deprecate() def get_machine_status(machine, key=None, model_name=None): """Return the juju status for a machine. @@ -158,17 +147,13 @@ def get_machine_status(machine, key=None, model_name=None): :returns: Juju status output for a machine :rtype: dict """ - status = get_full_juju_status(model_name=model_name) - if "lxd" in machine: - host = machine.split('/')[0] - status = status.machines.get(host)['containers'][machine] - else: - status = status.machines.get(machine) - if key: - status = status.get(key) - return status + return zaza.utilities.juju.get_machine_status( + machine, + key=key, + model_name=model_name) +@deprecate() def get_machine_series(machine, model_name=None): """Return the juju series for a machine. @@ -179,13 +164,12 @@ def get_machine_series(machine, model_name=None): :returns: Juju series :rtype: string """ - return get_machine_status( - machine=machine, - key='series', - model_name=model_name - ) + return zaza.utilities.juju.get_machine_series( + machine, + model_name=model_name) +@deprecate() def get_machine_uuids_for_application(application, model_name=None): """Return machine uuids for a given application. @@ -196,30 +180,22 @@ def get_machine_uuids_for_application(application, model_name=None): :returns: machine uuuids for an application :rtype: Iterator[str] """ - for machine in get_machines_for_application(application, - model_name=model_name): - yield get_machine_status(machine, key="instance-id", - model_name=model_name) + return zaza.utilities.juju.get_machine_uuids_for_application( + application, + model_name=model_name) +@deprecate() def get_provider_type(): """Get the type of the undercloud. :returns: Name of the undercloud type :rtype: string """ - cloud = controller.get_cloud() - if cloud: - # If the controller was deployed from this system with - # the cloud configured in ~/.local/share/juju/clouds.yaml - # Determine the cloud type directly - return get_cloud_configs(cloud)["type"] - else: - # If the controller was deployed elsewhere - # For now assume openstack - return "openstack" + return zaza.utilities.juju.get_provider_type() +@deprecate() def remote_run(unit, remote_cmd, timeout=None, fatal=None, model_name=None): """Run command on unit and return the output. @@ -238,46 +214,15 @@ def remote_run(unit, remote_cmd, timeout=None, fatal=None, model_name=None): :rtype: string :raises: model.CommandRunFailed """ - if fatal is None: - fatal = True - result = model.run_on_unit( + return zaza.utilities.juju.remote_run( unit, remote_cmd, timeout=timeout, + fatal=fatal, model_name=model_name) - if result: - if int(result.get("Code")) == 0: - return result.get("Stdout") - else: - if fatal: - raise model.CommandRunFailed(remote_cmd, result) - return result.get("Stderr") - - -def _get_unit_names(names, model_name=None): - """Resolve given application names to first unit name of said application. - - Helper function that resolves application names to first unit name of - said application. Any already resolved unit names are returned as-is. - - :param names: List of units/applications to translate - :type names: list(str) - :param model_name: Name of model to query. - :type model_name: str - :returns: List of units - :rtype: list(str) - """ - result = [] - for name in names: - if '/' in name: - result.append(name) - else: - result.append(model.get_first_unit_name( - name, - model_name=model_name)) - return result +@deprecate() def get_relation_from_unit(entity, remote_entity, remote_interface_name, model_name=None): """Get relation data passed between two units. @@ -302,22 +247,14 @@ def get_relation_from_unit(entity, remote_entity, remote_interface_name, :rtype: dict :raises: model.CommandRunFailed """ - application = entity.split('/')[0] - remote_application = remote_entity.split('/')[0] - rid = model.get_relation_id(application, remote_application, - remote_interface_name=remote_interface_name, - model_name=model_name) - (unit, remote_unit) = _get_unit_names( - [entity, remote_entity], + return zaza.utilities.juju.get_relation_from_unit( + entity, + remote_entity, + remote_interface_name, model_name=model_name) - cmd = 'relation-get --format=yaml -r "{}" - "{}"' .format(rid, remote_unit) - result = model.run_on_unit(unit, cmd, model_name=model_name) - if result and int(result.get('Code')) == 0: - return yaml.safe_load(result.get('Stdout')) - else: - raise model.CommandRunFailed(cmd, result) +@deprecate() def leader_get(application, key='', model_name=None): """Get leader settings from leader unit of named application. @@ -331,14 +268,13 @@ def leader_get(application, key='', model_name=None): :rtype: dict :raises: model.CommandRunFailed """ - cmd = 'leader-get --format=yaml {}'.format(key) - result = model.run_on_leader(application, cmd, model_name=model_name) - if result and int(result.get('Code')) == 0: - return yaml.safe_load(result.get('Stdout')) - else: - raise model.CommandRunFailed(cmd, result) + return zaza.utilities.juju.leader_get( + application, + key=key, + model_name=model_name) +@deprecate() def get_subordinate_units(unit_list, charm_name=None, status=None, model_name=None): """Get a list of all subordinate units associated with units in unit_list. @@ -369,17 +305,8 @@ def get_subordinate_units(unit_list, charm_name=None, status=None, :returns: List of matching unit names. :rtype: [] """ - if not status: - status = model.get_status(model_name=model_name) - sub_units = [] - for unit_name in unit_list: - app_name = unit_name.split('/')[0] - subs = status.applications[app_name]['units'][unit_name].get( - 'subordinates') or {} - if charm_name: - for unit_name, unit_data in subs.items(): - if charm_name in unit_data['charm']: - sub_units.append(unit_name) - else: - sub_units.extend([n for n in subs.keys()]) - return sub_units + return zaza.utilities.juju.get_subordinate_units( + unit_list, + charm_name=charm_name, + status=status, + model_name=model_name) From fe54ebeb985d94882c788656ba4398e9d2e79224 Mon Sep 17 00:00:00 2001 From: Aurelien Lourot Date: Wed, 1 Jul 2020 16:20:29 +0200 Subject: [PATCH 21/59] Minor improvements to keystone tests --- zaza/openstack/charm_tests/keystone/tests.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/zaza/openstack/charm_tests/keystone/tests.py b/zaza/openstack/charm_tests/keystone/tests.py index 9f1d31d..ed5f42a 100644 --- a/zaza/openstack/charm_tests/keystone/tests.py +++ b/zaza/openstack/charm_tests/keystone/tests.py @@ -21,7 +21,7 @@ import keystoneauth1 import zaza.model import zaza.openstack.utilities.exceptions as zaza_exceptions -import zaza.openstack.utilities.juju as juju_utils +import zaza.utilities.juju as juju_utils import zaza.openstack.utilities.openstack as openstack_utils import zaza.charm_lifecycle.utils as lifecycle_utils import zaza.openstack.charm_tests.test_utils as test_utils @@ -262,6 +262,7 @@ class AuthenticationAuthorizationTest(BaseKeystoneTest): openrc['OS_CACERT'] = openstack_utils.KEYSTONE_LOCAL_CACERT openrc['OS_AUTH_URL'] = ( openrc['OS_AUTH_URL'].replace('http', 'https')) + logging.info('keystone IP {}'.format(ip)) keystone_session = openstack_utils.get_keystone_session( openrc) keystone_client = openstack_utils.get_keystone_session_client( @@ -319,10 +320,7 @@ class AuthenticationAuthorizationTest(BaseKeystoneTest): 'OS_PROJECT_DOMAIN_NAME': DEMO_DOMAIN, 'OS_PROJECT_NAME': DEMO_PROJECT, } - with self.config_change( - {'preferred-api-version': self.default_api_version}, - {'preferred-api-version': self.api_v3}, - application_name="keystone"): + with self.v3_keystone_preferred(): for ip in self.keystone_ips: openrc.update( {'OS_AUTH_URL': 'http://{}:5000/v3'.format(ip)}) From 670292c6830d765b779d98ac4111b0a94446fb17 Mon Sep 17 00:00:00 2001 From: Aurelien Lourot Date: Wed, 1 Jul 2020 18:05:06 +0200 Subject: [PATCH 22/59] Remove more deprecation warnings --- unit_tests/utilities/test_zaza_utilities_openstack.py | 4 ++-- zaza/openstack/utilities/generic.py | 2 +- zaza/openstack/utilities/openstack.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/unit_tests/utilities/test_zaza_utilities_openstack.py b/unit_tests/utilities/test_zaza_utilities_openstack.py index a6aa190..7271708 100644 --- a/unit_tests/utilities/test_zaza_utilities_openstack.py +++ b/unit_tests/utilities/test_zaza_utilities_openstack.py @@ -809,12 +809,12 @@ class TestOpenStackUtils(ut_utils.BaseTestCase): name='_get_os_version' ) self.patch( - 'zaza.openstack.utilities.juju.get_machines_for_application', + 'zaza.utilities.juju.get_machines_for_application', new_callable=mock.MagicMock(), name='_get_machines' ) self.patch( - 'zaza.openstack.utilities.juju.get_machine_series', + 'zaza.utilities.juju.get_machine_series', new_callable=mock.MagicMock(), name='_get_machine_series' ) diff --git a/zaza/openstack/utilities/generic.py b/zaza/openstack/utilities/generic.py index 0874cc6..dd8b1e0 100644 --- a/zaza/openstack/utilities/generic.py +++ b/zaza/openstack/utilities/generic.py @@ -23,9 +23,9 @@ import telnetlib import yaml from zaza import model -from zaza.openstack.utilities import juju as juju_utils from zaza.openstack.utilities import exceptions as zaza_exceptions from zaza.openstack.utilities.os_versions import UBUNTU_OPENSTACK_RELEASE +from zaza.utilities import juju as juju_utils def assertActionRanOK(action): diff --git a/zaza/openstack/utilities/openstack.py b/zaza/openstack/utilities/openstack.py index bb834f0..4c79a7d 100644 --- a/zaza/openstack/utilities/openstack.py +++ b/zaza/openstack/utilities/openstack.py @@ -40,6 +40,7 @@ from keystoneauth1.identity import ( ) import zaza.openstack.utilities.cert as cert import zaza.utilities.deployment_env as deployment_env +import zaza.utilities.juju as juju_utils from novaclient import client as novaclient_client from neutronclient.v2_0 import client as neutronclient from neutronclient.common import exceptions as neutronexceptions @@ -66,7 +67,6 @@ from zaza import model from zaza.openstack.utilities import ( exceptions, generic as generic_utils, - juju as juju_utils, ) CIRROS_RELEASE_URL = 'http://download.cirros-cloud.net/version/released' From 520830905bb4ed45705c5f5818fdb9e18474161d Mon Sep 17 00:00:00 2001 From: Frode Nordahl Date: Thu, 2 Jul 2020 13:03:42 +0200 Subject: [PATCH 23/59] Increase wait time for cloud_init_complete We often see test runs being killed prematurely due to slow to complete cloud-init step on a loaded CI cloud. Related issue #311 --- zaza/openstack/utilities/openstack.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zaza/openstack/utilities/openstack.py b/zaza/openstack/utilities/openstack.py index bb834f0..28ba29a 100644 --- a/zaza/openstack/utilities/openstack.py +++ b/zaza/openstack/utilities/openstack.py @@ -2283,7 +2283,7 @@ def get_ports_from_device_id(neutron_client, device_id): @tenacity.retry(wait=tenacity.wait_exponential(multiplier=1, max=120), - reraise=True, stop=tenacity.stop_after_attempt(12)) + reraise=True, stop=tenacity.stop_after_delay(1800)) def cloud_init_complete(nova_client, vm_id, bootstring): """Wait for cloud init to complete on the given vm. From 0f3d9bf7c481486b4ac6ab4e8919b3d7a3faeb6c Mon Sep 17 00:00:00 2001 From: Frode Nordahl Date: Thu, 2 Jul 2020 13:33:03 +0200 Subject: [PATCH 24/59] Move useful helpers from Neutron tests to OpenStackBaseTest The dual-instance launch and retrieve is a common pattern in tests. Let's share them. --- zaza/openstack/charm_tests/neutron/tests.py | 54 ++---------- zaza/openstack/charm_tests/test_utils.py | 94 +++++++++++++++++++++ 2 files changed, 99 insertions(+), 49 deletions(-) diff --git a/zaza/openstack/charm_tests/neutron/tests.py b/zaza/openstack/charm_tests/neutron/tests.py index f270604..2ddfda4 100644 --- a/zaza/openstack/charm_tests/neutron/tests.py +++ b/zaza/openstack/charm_tests/neutron/tests.py @@ -25,10 +25,7 @@ import logging import tenacity import unittest -import novaclient - import zaza -import zaza.openstack.charm_tests.glance.setup as glance_setup import zaza.openstack.charm_tests.nova.utils as nova_utils import zaza.openstack.charm_tests.test_utils as test_utils import zaza.openstack.configure.guest as guest @@ -608,7 +605,7 @@ class NeutronOpenvSwitchTest(NeutronPluginApiSharedTests): logging.info('Testing pause resume') -class NeutronNetworkingBase(unittest.TestCase): +class NeutronNetworkingBase(test_utils.OpenStackBaseTest): """Base for checking openstack instances have valid networking.""" RESOURCE_PREFIX = 'zaza-neutrontests' @@ -616,10 +613,7 @@ class NeutronNetworkingBase(unittest.TestCase): @classmethod def setUpClass(cls): """Run class setup for running Neutron API Networking tests.""" - cls.keystone_session = ( - openstack_utils.get_overcloud_keystone_session()) - cls.nova_client = ( - openstack_utils.get_nova_session_client(cls.keystone_session)) + super(NeutronNetworkingBase, cls).setUpClass() cls.neutron_client = ( openstack_utils.get_neutron_session_client(cls.keystone_session)) # NOTE(fnordahl): in the event of a test failure we do not want to run @@ -737,44 +731,6 @@ class NeutronNetworkingBase(unittest.TestCase): assert agent['admin_state_up'] assert agent['alive'] - def launch_guests(self): - """Launch two guests to use in tests.""" - guest.launch_instance( - glance_setup.LTS_IMAGE_NAME, - vm_name='{}-ins-1'.format(self.RESOURCE_PREFIX)) - guest.launch_instance( - glance_setup.LTS_IMAGE_NAME, - vm_name='{}-ins-2'.format(self.RESOURCE_PREFIX)) - - def retrieve_guest(self, nova_client, guest_name): - """Return guest matching name. - - :param nova_client: Nova client to use when checking status - :type nova_client: Nova client - :returns: the matching guest - :rtype: Union[novaclient.Server, None] - """ - try: - return nova_client.servers.find(name=guest_name) - except novaclient.exceptions.NotFound: - return None - - def retrieve_guests(self, nova_client): - """Return test guests. - - :param nova_client: Nova client to use when checking status - :type nova_client: Nova client - :returns: the matching guest - :rtype: Union[novaclient.Server, None] - """ - instance_1 = self.retrieve_guest( - nova_client, - '{}-ins-1'.format(self.RESOURCE_PREFIX)) - instance_2 = self.retrieve_guest( - nova_client, - '{}-ins-1'.format(self.RESOURCE_PREFIX)) - return instance_1, instance_2 - def check_connectivity(self, instance_1, instance_2): """Run North/South and East/West connectivity tests.""" def verify(stdin, stdout, stderr): @@ -845,7 +801,7 @@ class NeutronNetworkingTest(NeutronNetworkingBase): def test_instances_have_networking(self): """Validate North/South and East/West networking.""" self.launch_guests() - instance_1, instance_2 = self.retrieve_guests(self.nova_client) + instance_1, instance_2 = self.retrieve_guests() self.check_connectivity(instance_1, instance_2) self.run_tearDown = True @@ -855,10 +811,10 @@ class NeutronNetworkingVRRPTests(NeutronNetworkingBase): def test_gateway_failure(self): """Validate networking in the case of a gateway failure.""" - instance_1, instance_2 = self.retrieve_guests(self.nova_client) + instance_1, instance_2 = self.retrieve_guests() if not all([instance_1, instance_2]): self.launch_guests() - instance_1, instance_2 = self.retrieve_guests(self.nova_client) + instance_1, instance_2 = self.retrieve_guests() self.check_connectivity(instance_1, instance_2) routers = self.neutron_client.list_routers( diff --git a/zaza/openstack/charm_tests/test_utils.py b/zaza/openstack/charm_tests/test_utils.py index 47c12d7..4b4e1d0 100644 --- a/zaza/openstack/charm_tests/test_utils.py +++ b/zaza/openstack/charm_tests/test_utils.py @@ -16,12 +16,17 @@ import contextlib import logging import ipaddress import subprocess +import tenacity import unittest +import novaclient + import zaza.model as model import zaza.charm_lifecycle.utils as lifecycle_utils +import zaza.openstack.configure.guest as configure_guest import zaza.openstack.utilities.openstack as openstack_utils import zaza.openstack.utilities.generic as generic_utils +import zaza.openstack.charm_tests.glance.setup as glance_setup def skipIfNotHA(service_name): @@ -432,6 +437,95 @@ class OpenStackBaseTest(BaseCharmTest): cls.keystone_session = openstack_utils.get_overcloud_keystone_session( model_name=cls.model_name) cls.cacert = openstack_utils.get_cacert() + cls.nova_client = ( + openstack_utils.get_nova_session_client(cls.keystone_session)) + + def launch_guest(self, guest_name, userdata=None): + """Launch two guests to use in tests. + + Note that it is up to the caller to have set the RESOURCE_PREFIX class + variable prior to calling this method. + + Also note that this method will remove any already existing instance + with same name as what is requested. + + :param guest_name: Name of instance + :type guest_name: str + :param userdata: Userdata to attach to instance + :type userdata: Optional[str] + :returns: Nova instance objects + :rtype: Server + """ + instance_name = '{}-{}'.format(self.RESOURCE_PREFIX, guest_name) + + instance = self.retrieve_guest(instance_name) + if instance: + logging.info('Removing already existing instance ({}) with ' + 'requested name ({})' + .format(instance.id, instance_name)) + openstack_utils.delete_resource( + self.nova_client.servers, + instance.id, + msg="server") + + return configure_guest.launch_instance( + glance_setup.LTS_IMAGE_NAME, + vm_name=instance_name, + userdata=userdata) + + def launch_guests(self, userdata=None): + """Launch two guests to use in tests. + + Note that it is up to the caller to have set the RESOURCE_PREFIX class + variable prior to calling this method. + + :param userdata: Userdata to attach to instance + :type userdata: Optional[str] + :returns: List of launched Nova instance objects + :rtype: List[Server] + """ + launched_instances = [] + for guest_number in range(1, 2+1): + for attempt in tenacity.Retrying( + stop=tenacity.stop_after_attempt(3), + wait=tenacity.wait_exponential( + multiplier=1, min=2, max=10)): + with attempt: + launched_instances.append( + self.launch_guest( + guest_name='ins-{}'.format(guest_number), + userdata=userdata)) + return launched_instances + + def retrieve_guest(self, guest_name): + """Return guest matching name. + + :param nova_client: Nova client to use when checking status + :type nova_client: Nova client + :returns: the matching guest + :rtype: Union[novaclient.Server, None] + """ + try: + return self.nova_client.servers.find(name=guest_name) + except novaclient.exceptions.NotFound: + return None + + def retrieve_guests(self): + """Return test guests. + + Note that it is up to the caller to have set the RESOURCE_PREFIX class + variable prior to calling this method. + + :param nova_client: Nova client to use when checking status + :type nova_client: Nova client + :returns: the matching guest + :rtype: Union[novaclient.Server, None] + """ + instance_1 = self.retrieve_guest( + '{}-ins-1'.format(self.RESOURCE_PREFIX)) + instance_2 = self.retrieve_guest( + '{}-ins-1'.format(self.RESOURCE_PREFIX)) + return instance_1, instance_2 def format_addr(addr): From 2472a7b5052156e03f3005e97e94f7a20a5a59a6 Mon Sep 17 00:00:00 2001 From: Frode Nordahl Date: Thu, 2 Jul 2020 13:47:55 +0200 Subject: [PATCH 25/59] ceph/fs: Use common helpers for launching test instances Fixes #311 --- zaza/openstack/charm_tests/ceph/fs/tests.py | 26 ++++----------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/zaza/openstack/charm_tests/ceph/fs/tests.py b/zaza/openstack/charm_tests/ceph/fs/tests.py index 4b0842b..28ac259 100644 --- a/zaza/openstack/charm_tests/ceph/fs/tests.py +++ b/zaza/openstack/charm_tests/ceph/fs/tests.py @@ -18,7 +18,6 @@ import logging from tenacity import Retrying, stop_after_attempt, wait_exponential import zaza.model as model -import zaza.openstack.charm_tests.glance.setup as glance_setup import zaza.openstack.charm_tests.neutron.tests as neutron_tests import zaza.openstack.charm_tests.nova.utils as nova_utils import zaza.openstack.charm_tests.test_utils as test_utils @@ -64,27 +63,10 @@ write_files: conf = model.run_on_leader( 'ceph-mon', 'cat /etc/ceph/ceph.conf')['Stdout'] # Spawn Servers - for attempt in Retrying( - stop=stop_after_attempt(3), - wait=wait_exponential(multiplier=1, min=2, max=10)): - with attempt: - instance_1 = guest.launch_instance( - glance_setup.LTS_IMAGE_NAME, - vm_name='{}-ins-1'.format(self.RESOURCE_PREFIX), - userdata=self.INSTANCE_USERDATA.format( - _indent(conf, 8), - _indent(keyring, 8))) - - for attempt in Retrying( - stop=stop_after_attempt(3), - wait=wait_exponential(multiplier=1, min=2, max=10)): - with attempt: - instance_2 = guest.launch_instance( - glance_setup.LTS_IMAGE_NAME, - vm_name='{}-ins-2'.format(self.RESOURCE_PREFIX), - userdata=self.INSTANCE_USERDATA.format( - _indent(conf, 8), - _indent(keyring, 8))) + instance_1, instance_2 = self.launch_guests( + userdata=self.INSTANCE_USERDATA.format( + _indent(conf, 8), + _indent(keyring, 8))) # Write a file on instance_1 def verify_setup(stdin, stdout, stderr): From 39b6065879710772ae94e00f8c18036e9d22009b Mon Sep 17 00:00:00 2001 From: David Ames Date: Thu, 2 Jul 2020 16:10:39 -0700 Subject: [PATCH 26/59] Do not run API check on Ocata Since commit c98aa001f997d2cc149e0c64e81f0339a83adc50, there are some missing changes. This stops the API check on Ocata when gnocchi is in play. Gnocchi must be in play for Ocata but heretofore has not been. --- zaza/openstack/charm_tests/ceilometer/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zaza/openstack/charm_tests/ceilometer/tests.py b/zaza/openstack/charm_tests/ceilometer/tests.py index eb50cb0..c460cdb 100644 --- a/zaza/openstack/charm_tests/ceilometer/tests.py +++ b/zaza/openstack/charm_tests/ceilometer/tests.py @@ -101,7 +101,7 @@ class CeilometerTest(test_utils.OpenStackBaseTest): def test_400_api_connection(self): """Simple api calls to check service is up and responding.""" - if self.current_release >= CeilometerTest.XENIAL_PIKE: + if self.current_release >= CeilometerTest.XENIAL_OCATA: logging.info('Skipping API checks as ceilometer api has been ' 'removed') return From 0df72bf47c88a69a20010255e60bd18a43e4d23c Mon Sep 17 00:00:00 2001 From: Frode Nordahl Date: Fri, 3 Jul 2020 08:04:09 +0200 Subject: [PATCH 27/59] Make shared resource cleanup method class instance method At present the shared resource cleanup method is a class method. This will prohibit descendents of the class to influence whether cleanup should be run. Remove the @classmethod decorator so that it will make its descisions based on class instance variables set by the descendent. --- zaza/openstack/charm_tests/test_utils.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/zaza/openstack/charm_tests/test_utils.py b/zaza/openstack/charm_tests/test_utils.py index 4b4e1d0..6abb381 100644 --- a/zaza/openstack/charm_tests/test_utils.py +++ b/zaza/openstack/charm_tests/test_utils.py @@ -100,8 +100,7 @@ class BaseCharmTest(unittest.TestCase): run_resource_cleanup = False - @classmethod - def resource_cleanup(cls): + def resource_cleanup(self): """Cleanup any resources created during the test run. Override this method with a method which removes any resources @@ -111,12 +110,13 @@ class BaseCharmTest(unittest.TestCase): """ pass - @classmethod - def tearDown(cls): + # this must be a class instance method otherwise descentents will not be + # able to influence if cleanup should be run. + def tearDown(self): """Run teardown for test class.""" - if cls.run_resource_cleanup: + if self.run_resource_cleanup: logging.info('Running resource cleanup') - cls.resource_cleanup() + self.resource_cleanup() @classmethod def setUpClass(cls, application_name=None, model_alias=None): @@ -440,6 +440,7 @@ class OpenStackBaseTest(BaseCharmTest): cls.nova_client = ( openstack_utils.get_nova_session_client(cls.keystone_session)) + def launch_guest(self, guest_name, userdata=None): """Launch two guests to use in tests. From 29356f6419ef76dd182c63322bdba5edcfcefac9 Mon Sep 17 00:00:00 2001 From: Frode Nordahl Date: Fri, 3 Jul 2020 08:14:50 +0200 Subject: [PATCH 28/59] Move cleanup of launched instances to common class Use the nomenclature for resource cleanup as defined in the `BaseCharmTest`. --- zaza/openstack/charm_tests/neutron/tests.py | 20 +------------------- zaza/openstack/charm_tests/test_utils.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/zaza/openstack/charm_tests/neutron/tests.py b/zaza/openstack/charm_tests/neutron/tests.py index 2ddfda4..620767e 100644 --- a/zaza/openstack/charm_tests/neutron/tests.py +++ b/zaza/openstack/charm_tests/neutron/tests.py @@ -616,24 +616,6 @@ class NeutronNetworkingBase(test_utils.OpenStackBaseTest): super(NeutronNetworkingBase, cls).setUpClass() cls.neutron_client = ( openstack_utils.get_neutron_session_client(cls.keystone_session)) - # NOTE(fnordahl): in the event of a test failure we do not want to run - # tear down code as it will make debugging a problem virtually - # impossible. To alleviate each test method will set the - # `run_tearDown` instance variable at the end which will let us run - # tear down only when there were no failure. - cls.run_tearDown = False - - @classmethod - def tearDown(cls): - """Remove test resources.""" - if cls.run_tearDown: - logging.info('Running teardown') - for server in cls.nova_client.servers.list(): - if server.name.startswith(cls.RESOURCE_PREFIX): - openstack_utils.delete_resource( - cls.nova_client.servers, - server.id, - msg="server") @tenacity.retry(wait=tenacity.wait_exponential(multiplier=1, max=60), reraise=True, stop=tenacity.stop_after_attempt(8)) @@ -803,7 +785,7 @@ class NeutronNetworkingTest(NeutronNetworkingBase): self.launch_guests() instance_1, instance_2 = self.retrieve_guests() self.check_connectivity(instance_1, instance_2) - self.run_tearDown = True + self.run_resource_cleanup = True class NeutronNetworkingVRRPTests(NeutronNetworkingBase): diff --git a/zaza/openstack/charm_tests/test_utils.py b/zaza/openstack/charm_tests/test_utils.py index 6abb381..83595b0 100644 --- a/zaza/openstack/charm_tests/test_utils.py +++ b/zaza/openstack/charm_tests/test_utils.py @@ -440,6 +440,20 @@ class OpenStackBaseTest(BaseCharmTest): cls.nova_client = ( openstack_utils.get_nova_session_client(cls.keystone_session)) + def resource_cleanup(self): + """Remove test resources.""" + try: + logging.info('Removing instances launched by test ({}*)' + .format(self.RESOURCE_PREFIX)) + for server in self.nova_client.servers.list(): + if server.name.startswith(self.RESOURCE_PREFIX): + openstack_utils.delete_resource( + self.nova_client.servers, + server.id, + msg="server") + except AttributeError: + # Test did not define self.RESOURCE_PREFIX, ignore. + pass def launch_guest(self, guest_name, userdata=None): """Launch two guests to use in tests. From b4f155d5e32ff45c50500bbedd073ea0485db223 Mon Sep 17 00:00:00 2001 From: Frode Nordahl Date: Fri, 3 Jul 2020 08:28:38 +0200 Subject: [PATCH 29/59] octavia: Consume common helpers for test instance lifecycle --- zaza/openstack/charm_tests/octavia/setup.py | 19 ------- zaza/openstack/charm_tests/octavia/tests.py | 56 +++++++++++++-------- 2 files changed, 34 insertions(+), 41 deletions(-) diff --git a/zaza/openstack/charm_tests/octavia/setup.py b/zaza/openstack/charm_tests/octavia/setup.py index 55f8903..729fb16 100644 --- a/zaza/openstack/charm_tests/octavia/setup.py +++ b/zaza/openstack/charm_tests/octavia/setup.py @@ -98,25 +98,6 @@ def configure_octavia(): pass -def prepare_payload_instance(): - """Prepare a instance we can use as payload test.""" - session = openstack.get_overcloud_keystone_session() - keystone = openstack.get_keystone_session_client(session) - neutron = openstack.get_neutron_session_client(session) - project_id = openstack.get_project_id( - keystone, 'admin', domain_name='admin_domain') - openstack.add_neutron_secgroup_rules( - neutron, - project_id, - [{'protocol': 'tcp', - 'port_range_min': '80', - 'port_range_max': '80', - 'direction': 'ingress'}]) - zaza.openstack.configure.guest.launch_instance( - glance_setup.LTS_IMAGE_NAME, - userdata='#cloud-config\npackages:\n - apache2\n') - - def centralized_fip_network(): """Create network with centralized router for connecting lb and fips. diff --git a/zaza/openstack/charm_tests/octavia/tests.py b/zaza/openstack/charm_tests/octavia/tests.py index b189484..7049942 100644 --- a/zaza/openstack/charm_tests/octavia/tests.py +++ b/zaza/openstack/charm_tests/octavia/tests.py @@ -48,12 +48,13 @@ class LBAASv2Test(test_utils.OpenStackBaseTest): def setUpClass(cls): """Run class setup for running LBaaSv2 service tests.""" super(LBAASv2Test, cls).setUpClass() - - cls.keystone_session = openstack_utils.get_overcloud_keystone_session() + cls.keystone_client = openstack_utils.get_keystone_session_client( + cls.keystone_session) cls.neutron_client = openstack_utils.get_neutron_session_client( cls.keystone_session) cls.octavia_client = openstack_utils.get_octavia_session_client( cls.keystone_session) + cls.RESOURCE_PREFIX = 'zaza-octavia' # NOTE(fnordahl): in the event of a test failure we do not want to run # tear down code as it will make debugging a problem virtually @@ -63,28 +64,24 @@ class LBAASv2Test(test_utils.OpenStackBaseTest): cls.run_tearDown = False # List of load balancers created by this test cls.loadbalancers = [] - # LIst of floating IPs created by this test + # List of floating IPs created by this test cls.fips = [] - @classmethod - def tearDown(cls): - """Remove resources created during test execution. - - Note that resources created in the configure step prior to executing - the test should not be touched here. - """ - if not cls.run_tearDown: - return - for lb in cls.loadbalancers: - cls.octavia_client.load_balancer_delete(lb['id'], cascade=True) + def resource_cleanup(self): + """Remove resources created during test execution.""" + for lb in self.loadbalancers: + self.octavia_client.load_balancer_delete(lb['id'], cascade=True) try: - cls.wait_for_lb_resource( - cls.octavia_client.load_balancer_show, lb['id'], + self.wait_for_lb_resource( + self.octavia_client.load_balancer_show, lb['id'], provisioning_status='DELETED') except osc_lib.exceptions.NotFound: pass - for fip in cls.fips: - cls.neutron_client.delete_floatingip(fip) + for fip in self.fips: + self.neutron_client.delete_floatingip(fip) + # we run the parent resource_cleanup last as it will remove instances + # referenced as members in the above cleaned up load balancers + super(LBAASv2Test, self).resource_cleanup() @staticmethod @tenacity.retry(retry=tenacity.retry_if_exception_type(AssertionError), @@ -238,12 +235,27 @@ class LBAASv2Test(test_utils.OpenStackBaseTest): def test_create_loadbalancer(self): """Create load balancer.""" - nova_client = openstack_utils.get_nova_session_client( - self.keystone_session) + # Prepare payload instances + # First we allow communication to port 80 by adding a security group + # rule + project_id = openstack_utils.get_project_id( + self.keystone_client, 'admin', domain_name='admin_domain') + openstack_utils.add_neutron_secgroup_rules( + self.neutron_client, + project_id, + [{'protocol': 'tcp', + 'port_range_min': '80', + 'port_range_max': '80', + 'direction': 'ingress'}]) + + # Then we request two Ubuntu instances with the Apache web server + # installed + instance_1, instance_2 = self.launch_guests( + userdata='#cloud-config\npackages:\n - apache2\n') # Get IP of the prepared payload instances payload_ips = [] - for server in nova_client.servers.list(): + for server in (instance_1, instance_2): payload_ips.append(server.networks['private'][0]) self.assertTrue(len(payload_ips) > 0) @@ -274,4 +286,4 @@ class LBAASv2Test(test_utils.OpenStackBaseTest): lb_fp['floating_ip_address'])) # If we get here, it means the tests passed - self.run_tearDown = True + self.run_resource_cleanup = True From 29849225c2a8b3b71fd456f4860ade23266d2c28 Mon Sep 17 00:00:00 2001 From: James Page Date: Mon, 22 Jun 2020 13:02:15 +0100 Subject: [PATCH 30/59] Pass deployment CA cert to requests Ensure that the CA cert for the deployment is passed to requests when retrieving stream data from swift. Refresh the product catalog entry on each attempt to retrieve stream data as the URL will update once data was been written to the swift endpoint in the deployment. Retry on KeyError and drop number of retry attempts Add a log message to detail URL being used for product-stream data Log return data from streams endpoint Fixup endpoint resolution --- .../glance_simplestreams_sync/tests.py | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/zaza/openstack/charm_tests/glance_simplestreams_sync/tests.py b/zaza/openstack/charm_tests/glance_simplestreams_sync/tests.py index adbc87b..b5947a3 100644 --- a/zaza/openstack/charm_tests/glance_simplestreams_sync/tests.py +++ b/zaza/openstack/charm_tests/glance_simplestreams_sync/tests.py @@ -42,7 +42,7 @@ def get_product_streams(url): # There is a race between the images being available in glance and any # metadata being written. Use tenacity to avoid this race. client = requests.session() - json_data = client.get(url).text + json_data = client.get(url, verify=openstack_utils.get_cacert()).text return json.loads(json_data) @@ -102,24 +102,31 @@ class GlanceSimpleStreamsSyncTest(test_utils.OpenStackBaseTest): if openstack_utils.get_os_release() <= xenial_pike: key = "publicURL" - catalog = self.keystone_client.service_catalog.get_endpoints() - ps_interface = catalog["product-streams"][0][key] - url = "{}/{}".format(ps_interface, uri) - # There is a race between the images being available in glance and the # metadata being written for each image. Use tenacity to avoid this # race and make the test idempotent. @tenacity.retry( - retry=tenacity.retry_if_exception_type(AssertionError), + retry=tenacity.retry_if_exception_type( + (AssertionError, KeyError) + ), wait=tenacity.wait_fixed(10), reraise=True, - stop=tenacity.stop_after_attempt(10)) - def _check_local_product_streams(url, expected_images): + stop=tenacity.stop_after_attempt(25)) + def _check_local_product_streams(expected_images): + # Refresh from catalog as URL may change if swift in use. + catalog = self.keystone_client.service_catalog.get_endpoints() + ps_interface = self.keystone_client.service_catalog.url_for( + service_type='product-streams', interface='publicURL' + ) + url = "{}/{}".format(ps_interface, uri) + logging.info('Retrieving product stream information' + ' from {}'.format(url)) product_streams = get_product_streams(url) + logging.debug(product_streams) images = product_streams["products"] for image in expected_images: self.assertIn(image, images) - _check_local_product_streams(url, expected_images) + _check_local_product_streams(expected_images) logging.debug("Local product stream successful") From ecb47bfa4fa223b811511be94e748a5728a19cd9 Mon Sep 17 00:00:00 2001 From: James Page Date: Fri, 3 Jul 2020 10:59:39 +0100 Subject: [PATCH 31/59] Fix lint --- .../openstack/charm_tests/glance_simplestreams_sync/tests.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/zaza/openstack/charm_tests/glance_simplestreams_sync/tests.py b/zaza/openstack/charm_tests/glance_simplestreams_sync/tests.py index b5947a3..0c0d472 100644 --- a/zaza/openstack/charm_tests/glance_simplestreams_sync/tests.py +++ b/zaza/openstack/charm_tests/glance_simplestreams_sync/tests.py @@ -97,10 +97,6 @@ class GlanceSimpleStreamsSyncTest(test_utils.OpenStackBaseTest): 'com.ubuntu.cloud:server:20.04:amd64', ] uri = "streams/v1/auto.sync.json" - key = "url" - xenial_pike = openstack_utils.get_os_release('xenial_pike') - if openstack_utils.get_os_release() <= xenial_pike: - key = "publicURL" # There is a race between the images being available in glance and the # metadata being written for each image. Use tenacity to avoid this @@ -113,7 +109,6 @@ class GlanceSimpleStreamsSyncTest(test_utils.OpenStackBaseTest): stop=tenacity.stop_after_attempt(25)) def _check_local_product_streams(expected_images): # Refresh from catalog as URL may change if swift in use. - catalog = self.keystone_client.service_catalog.get_endpoints() ps_interface = self.keystone_client.service_catalog.url_for( service_type='product-streams', interface='publicURL' ) From 0fe56cbc33e5232a0e9b76c8c8089f51fcc9380b Mon Sep 17 00:00:00 2001 From: Liam Young Date: Fri, 3 Jul 2020 10:48:12 +0000 Subject: [PATCH 32/59] Switch pre_deploy_certs from OS_ to TEST_ vars --- zaza/openstack/configure/pre_deploy_certs.py | 34 ++++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/zaza/openstack/configure/pre_deploy_certs.py b/zaza/openstack/configure/pre_deploy_certs.py index 11ec709..34af066 100644 --- a/zaza/openstack/configure/pre_deploy_certs.py +++ b/zaza/openstack/configure/pre_deploy_certs.py @@ -14,19 +14,19 @@ def set_cidr_certs(): """Create certs and keys for deploy using IP SANS from CIDR. Create a certificate authority certificate and key. The CA cert and key - are then base 64 encoded and assigned to the OS_TEST_CAKEY and - OS_TEST_CACERT environment variables. + are then base 64 encoded and assigned to the TEST_CAKEY and + TEST_CACERT environment variables. Using the CA key a second certificate and key are generated. The new certificate has a SAN entry for the first 2^11 IPs in the CIDR. - The cert and key are then base 64 encoded and assigned to the OS_TEST_KEY - and OS_TEST_CERT environment variables. + The cert and key are then base 64 encoded and assigned to the TEST_KEY + and TEST_CERT environment variables. """ (cakey, cacert) = zaza.openstack.utilities.cert.generate_cert( ISSUER_NAME, generate_ca=True) - os.environ['OS_TEST_CAKEY'] = base64.b64encode(cakey).decode() - os.environ['OS_TEST_CACERT'] = base64.b64encode(cacert).decode() + os.environ['TEST_CAKEY'] = base64.b64encode(cakey).decode() + os.environ['TEST_CACERT'] = base64.b64encode(cacert).decode() # We need to restrain the number of SubjectAlternativeNames we attempt to # put # in the certificate. There is a hard limit for what length the sum # of all extensions in the certificate can have. @@ -34,37 +34,37 @@ def set_cidr_certs(): # - 2^11 ought to be enough for anybody alt_names = [] for addr in itertools.islice( - ipaddress.IPv4Network(os.environ.get('OS_CIDR_EXT')), 2**11): + ipaddress.IPv4Network(os.environ.get('TEST_CIDR_EXT')), 2**11): alt_names.append(str(addr)) (key, cert) = zaza.openstack.utilities.cert.generate_cert( '*.serverstack', alternative_names=alt_names, issuer_name=ISSUER_NAME, signing_key=cakey) - os.environ['OS_TEST_KEY'] = base64.b64encode(key).decode() - os.environ['OS_TEST_CERT'] = base64.b64encode(cert).decode() + os.environ['TEST_KEY'] = base64.b64encode(key).decode() + os.environ['TEST_CERT'] = base64.b64encode(cert).decode() def set_certs_per_vips(): """Create certs and keys for deploy using VIPS. Create a certificate authority certificate and key. The CA cert and key - are then base 64 encoded and assigned to the OS_TEST_CAKEY and - OS_TEST_CACERT environment variables. + are then base 64 encoded and assigned to the TEST_CAKEY and + TEST_CACERT environment variables. Using the CA key a certificate and key is generated for each VIP specified - via environment variables. eg if OS_VIP06=172.20.0.107 is set in the + via environment variables. eg if TEST_VIP06=172.20.0.107 is set in the environment then a cert with a SAN entry for 172.20.0.107 is generated. - The cert and key are then base 64 encoded and assigned to the OS_VIP06_KEY - and OS_VIP06_CERT environment variables. + The cert and key are then base 64 encoded and assigned to the + TEST_VIP06_KEY and TEST_VIP06_CERT environment variables. """ (cakey, cacert) = zaza.openstack.utilities.cert.generate_cert( ISSUER_NAME, generate_ca=True) - os.environ['OS_TEST_CAKEY'] = base64.b64encode(cakey).decode() - os.environ['OS_TEST_CACERT'] = base64.b64encode(cacert).decode() + os.environ['TEST_CAKEY'] = base64.b64encode(cakey).decode() + os.environ['TEST_CACERT'] = base64.b64encode(cacert).decode() for vip_name, vip_ip in os.environ.items(): - if vip_name.startswith('OS_VIP'): + if vip_name.startswith('TEST_VIP'): (key, cert) = zaza.openstack.utilities.cert.generate_cert( '*.serverstack', alternative_names=[vip_ip], From 37dfa53bafec129d167331dc839e083a0fdd8d66 Mon Sep 17 00:00:00 2001 From: Chris MacNaughton Date: Fri, 3 Jul 2020 14:44:08 +0200 Subject: [PATCH 33/59] ensure that we add the bare cinder endpoint When there is a cinderv2 or cinderv3 endpoint, we should enable the bare cinder bits in the tempest config. --- zaza/openstack/charm_tests/tempest/setup.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py index 1cdaf1e..af1ee4c 100644 --- a/zaza/openstack/charm_tests/tempest/setup.py +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -235,6 +235,9 @@ def get_tempest_context(): 'cinder': add_cinder_config, 'keystone': add_keystone_config} ctxt['enabled_services'] = get_service_list(keystone_session) + if set(['cinderv2', 'cinderv3']) \ + .intersection(set(ctxt['enabled_services'])): + ctxt['enabled_services'].append('cinder') ctxt['disabled_services'] = list( set(TEMPEST_SVC_LIST) - set(ctxt['enabled_services'])) add_application_ips(ctxt) From 849e08528b4e57a23f4c342c501f6927a5b98ebe Mon Sep 17 00:00:00 2001 From: Frode Nordahl Date: Fri, 3 Jul 2020 10:27:24 +0200 Subject: [PATCH 34/59] octavia: fix pause/resume test and temporarily disable it Re-enable when LP: #1886202 is fixed. --- zaza/openstack/charm_tests/octavia/tests.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/zaza/openstack/charm_tests/octavia/tests.py b/zaza/openstack/charm_tests/octavia/tests.py index 7049942..e84ac3e 100644 --- a/zaza/openstack/charm_tests/octavia/tests.py +++ b/zaza/openstack/charm_tests/octavia/tests.py @@ -38,7 +38,20 @@ class CharmOperationTest(test_utils.OpenStackBaseTest): Pause service and check services are stopped, then resume and check they are started. """ - self.pause_resume(['apache2']) + services = [ + 'apache2', + 'octavia-health-manager', + 'octavia-housekeeping', + 'octavia-worker', + ] + if openstack_utils.ovn_present(): + services.append('octavia-driver-agent') + logging.info('Skipping pause resume test LP: #1886202...') + return + logging.info('Testing pause resume (services="{}")' + .format(services)) + with self.pause_resume(services, pgrep_full=True): + pass class LBAASv2Test(test_utils.OpenStackBaseTest): From d0981d64af57b062376de6676bc4f846ba74f5d8 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Tue, 7 Jul 2020 08:57:40 +0000 Subject: [PATCH 35/59] Add auto_initialize_no_validation_no_wait When vault is in its own model with no clients then vault needs to be initialised without waiting for clients to start executing and without validating a client has recieved the cert. To achieve this, this PR adds auto_initialize_no_validation_no_wait. --- zaza/openstack/charm_tests/vault/setup.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/zaza/openstack/charm_tests/vault/setup.py b/zaza/openstack/charm_tests/vault/setup.py index 0709162..4db90b5 100644 --- a/zaza/openstack/charm_tests/vault/setup.py +++ b/zaza/openstack/charm_tests/vault/setup.py @@ -16,6 +16,7 @@ import base64 import functools +import logging import requests import tempfile @@ -99,7 +100,7 @@ async def async_mojo_unseal_by_unit(): unit_name, './hooks/update-status') -def auto_initialize(cacert=None, validation_application='keystone'): +def auto_initialize(cacert=None, validation_application='keystone', wait=True): """Auto initialize vault for testing. Generate a csr and uploading a signed certificate. @@ -114,6 +115,7 @@ def auto_initialize(cacert=None, validation_application='keystone'): :returns: None :rtype: None """ + logging.info('Running auto_initialize') basic_setup(cacert=cacert, unseal_and_authorize=True) action = vault_utils.run_get_csr() @@ -131,10 +133,11 @@ def auto_initialize(cacert=None, validation_application='keystone'): root_ca=cacertificate, allowed_domains='openstack.local') - zaza.model.wait_for_agent_status() - test_config = lifecycle_utils.get_charm_config(fatal=False) - zaza.model.wait_for_application_states( - states=test_config.get('target_deploy_status', {})) + if wait: + zaza.model.wait_for_agent_status() + test_config = lifecycle_utils.get_charm_config(fatal=False) + zaza.model.wait_for_application_states( + states=test_config.get('target_deploy_status', {})) if validation_application: validate_ca(cacertificate, application=validation_application) @@ -163,6 +166,12 @@ auto_initialize_no_validation = functools.partial( validation_application=None) +auto_initialize_no_validation_no_wait = functools.partial( + auto_initialize, + validation_application=None, + wait=False) + + def validate_ca(cacertificate, application="keystone", port=5000): """Validate Certificate Authority against application. From 8f44ab681ae6ed44939038726c914d77e0d4d4f4 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Tue, 7 Jul 2020 09:03:36 +0000 Subject: [PATCH 36/59] Add wait_for_cacert wait_for_cacert will wait for keystone to recieve and install a cacert. This is particularly useful when the certificate issuer is in a different model. --- zaza/openstack/charm_tests/keystone/setup.py | 23 ++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/zaza/openstack/charm_tests/keystone/setup.py b/zaza/openstack/charm_tests/keystone/setup.py index 6dbb7c1..748439f 100644 --- a/zaza/openstack/charm_tests/keystone/setup.py +++ b/zaza/openstack/charm_tests/keystone/setup.py @@ -14,8 +14,12 @@ """Code for setting up keystone.""" +import logging + import keystoneauth1 +import zaza.charm_lifecycle.utils as lifecycle_utils +import zaza.model import zaza.openstack.utilities.openstack as openstack_utils from zaza.openstack.charm_tests.keystone import ( BaseKeystoneTest, @@ -30,6 +34,25 @@ from zaza.openstack.charm_tests.keystone import ( ) +def wait_for_cacert(model_name=None): + """Wait for keystone to install a cacert. + + :param model_name: Name of model to query. + :type model_name: str + """ + logging.info("Waiting for cacert") + zaza.model.block_until_file_has_contents( + 'keystone', + openstack_utils.KEYSTONE_REMOTE_CACERT, + 'CERTIFICATE', + model_name=model_name) + zaza.model.block_until_all_units_idle(model_name=model_name) + test_config = lifecycle_utils.get_charm_config(fatal=False) + zaza.model.wait_for_application_states( + states=test_config.get('target_deploy_status', {}), + model_name=model_name) + + def add_demo_user(): """Add a demo user to the current deployment.""" def _v2(): From 9c52b3390d4c6d391b002fba15ff3ce5c3324d83 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Tue, 7 Jul 2020 09:06:35 +0000 Subject: [PATCH 37/59] Add LTSGuestCreateVolumeBackedTest Add a test class which launches a volume backed instance. --- zaza/openstack/charm_tests/nova/tests.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/zaza/openstack/charm_tests/nova/tests.py b/zaza/openstack/charm_tests/nova/tests.py index 1b12240..c4dd5b4 100644 --- a/zaza/openstack/charm_tests/nova/tests.py +++ b/zaza/openstack/charm_tests/nova/tests.py @@ -56,6 +56,16 @@ class LTSGuestCreateTest(BaseGuestCreateTest): glance_setup.LTS_IMAGE_NAME) +class LTSGuestCreateVolumeBackedTest(BaseGuestCreateTest): + """Tests to launch a LTS image.""" + + def test_launch_small_instance(self): + """Launch a Bionic instance and test connectivity.""" + zaza.openstack.configure.guest.launch_instance( + glance_setup.LTS_IMAGE_NAME, + use_boot_volume=True) + + class NovaCompute(test_utils.OpenStackBaseTest): """Run nova-compute specific tests.""" From a829b372e0d6c5bb3e759a40b84e83ac56e409ee Mon Sep 17 00:00:00 2001 From: Liam Young Date: Tue, 7 Jul 2020 09:56:25 +0000 Subject: [PATCH 38/59] Use model tmpdor for key storage Use the model specific tmp dir to store the test ssh private key. This avoids the key being overwritten in CMR tests. Depends-On: https://github.com/openstack-charmers/zaza/pull/371 --- zaza/openstack/utilities/openstack.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zaza/openstack/utilities/openstack.py b/zaza/openstack/utilities/openstack.py index 28ba29a..034cae3 100644 --- a/zaza/openstack/utilities/openstack.py +++ b/zaza/openstack/utilities/openstack.py @@ -2202,7 +2202,8 @@ def get_private_key_file(keypair_name): :returns: Path to file containing key :rtype: str """ - return 'tests/id_rsa_{}'.format(keypair_name) + tmp_dir = deployment_env.get_tmpdir() + return '{}/id_rsa_{}'.format(tmp_dir, keypair_name) def write_private_key(keypair_name, key): From 76fdb61e0f1b364713a4ecbc474fd3f8b900deb9 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Tue, 7 Jul 2020 12:18:01 +0000 Subject: [PATCH 39/59] Fix up unit tests --- .../.test_zaza_utilities_openstack.py.swp | Bin 0 -> 73728 bytes .../utilities/test_zaza_utilities_openstack.py | 10 ++++++++-- zaza/openstack/utilities/.openstack.py.swp | Bin 0 -> 16384 bytes 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 unit_tests/utilities/.test_zaza_utilities_openstack.py.swp create mode 100644 zaza/openstack/utilities/.openstack.py.swp diff --git a/unit_tests/utilities/.test_zaza_utilities_openstack.py.swp b/unit_tests/utilities/.test_zaza_utilities_openstack.py.swp new file mode 100644 index 0000000000000000000000000000000000000000..2ef70f74efe45a7a7db302b8b0e7266b3d9ec4d8 GIT binary patch literal 73728 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%0AjUXHRl4eUJnLYj8_^v1 zW5>pZM@nYe5Nb@F8jZw~s+2HKE=5ehW4~LsTg*Z6tygauneg^HC}W$Ym?0^cj`fy>;%Hz1Vv{ zC$!C;>RWqM=-^9i#Lb9W6du=!z4(3c(v4GuBE4re+zi_@#N1yTm{mk}EPm(m#797? zjrfsb#h=HmL$OD@{woG}%%ZmwmnyUHF}~WH+(ihuFNI~w+ofGbqrLDeUUaL-J|hy! z9Po%&f^x$$dTQM}=r${bHeLbI`qF}?f*pysz_VM=ZmFFqteo^)L{*y6?@QL*=xy8& zl;TWPW6DuiWQTWEN1FA>{yv~O?Clhjjh9!TOmv_+@`eqgvbjY=POk{Y#A>(~d)-Qj z<%&+^#ODfADwO{(B-TIEqSIt%IL#KO(7lWH=eEu~=fYXU*RO&zz>|TT zHSjR-ee`=-?=N%yF9f^6CLn7AZbHYegNKqF8|ly9pFkJy1ks` zC+h%S4K4#u0Z#qmj`_xZx(*yYQBu|p+iqKKK#+lR*< z9(m8COA7@+-@^7(Xtw11rR>y>xt$kJ>y~8%YAwrYBC`KvmLnmy4ULQsjcl149pAob zbo=J5mnFqX>P6#(cM2t;4{-)nx$sJ-P8$E!p3XI@=Bd&Awj-S zY`QB>+qRqK0Fa@5oHJHmaak;2XsJPw+u8HQfyv*fHWh@GPrHHJw1p+o4gfv0YBD>g zg}?u=4Q66WOXGcak7om`|Xi!)5BcFK_0=D3zz{J0;a6m%i1p4_k+@wQa;Y zt4?zCV3oGKG`4(^eWezYd$`WMTT;)aS)oU#htQ*KNbi)xjyU~_ld-4->A}6-vO_l< zIsSvRGc3DO&st=ZM_b`lQjkfK1HN*9o!642pHsw6dA zw#8Bsq}kizk${$5q-<6%IPX2w8m;#`$8PeI0oAVFN8Nkcsmt<0z+?htydP@c`b;X| zksm_JFJYF}yc;?|NVx1_-c)nchA{{uy&O{JMbcVy7|DAOtx0+RdFzpkgu)o>5+bou zMC)DGO7wo08Vs`6BH&3x$S0+H$6UsQi$PRN{Ozjs3Db4!-^k_NYPkmQ4+h6Y21W3M z)jP*ExaXFx3`z3ub*ld$nyy~|j;`Mzh$kcdFH1;*B6iA3F-Y1)V5>D}1Z|aNEG&e! zX|&zpUUnWxOx2i+Q`RnD3Z+uQF~8MP?Y=8ktx0l{R3e$<9M>Wimfo;}be?LB7}&z- zp>3hpIZkg9EH=eyO6od`B>F$#3AbP*}`Tsc(-G3O|iJmWe z`+XF=3Y5S$uoWB!?m*Z71b72j2AjYm!M~vEe*(M^Q zvfwU?=|C-#3Xayvt}Mx`!(7Oq3SxFIDM?+f!x@sVLyNnwO$L!yMAz$b+#c1$C9%E29_n_})-(r+jORopra0aCoRZAt(s37D z+%wiBwonk(KuQIRgw(cIjyP5){1}$hp!glT89&fiuLN(3XE2uQmEbYM(Bf66;WrVK zf26Z?jzBF`tY3}rM zx1YXi`{}cnWeq>jNKni2KB6q|THJ%M!MWeL@C)vxc=Ihe+Eg5bO|sp{GhOUx`gWtX zjR0|rc`C?i9oFd?UNePy^8v9<2R7;4gMG>)Ru$6i9s1Nq&!%aq@57L4Z$2q1b&_N# z%;A^5RBN-e(b2^%m3@&G4&-AEA`t2PlH)WB{`2-ze7*J5_WWp4FCB_-_$67ElX~Z* zp7}6(Pz}59s47d`(Q;Qc_({gb)> zM+4FQuLH6N;5?A||D(Yd(ewWlTn=V|?By@}{=FZ(9;^Y;`%eQWgRi6OzX!-3fU`i> z^?x5d|3lyA52I04*^o_`nkFmS;DxC7n(ec+{FKbQs^z%S78WqrS_?YH;-6BbJ^ z7+h-i2WI3M_Ls1`%JOBc7vKNSg|WnHp_=_#z^1!b!&4zi3UJ3KySrz~~^ zv2!%zgO1DqjF^i>FrAjcEjyS)hwr9x*>gb#&ps%D0CkYIIV?O09*n0c>55X&@)s zpyvqWFHeXUu@|F($B;1Y%2;A&2$`}#D{3}U1!dm_c2&G^X3u%kJLarPmH^qQ3wP!( zo}QWAxo0<3G=63LHIE}e-cKNZ)}CEcJ9jG+l2oX#Fy=igZc_45vy&00(I#||lUSE} z2rh=GL!#nkw;lzOm|- z@wG@v4VjlzMPOBvp#E;Kem670Kn<(f@Cx>Ulo`!xJ4uZruzGltJjP)Hk%ZJ~McyP_ zT-G>J7q4Zvxi;jBg++ySvW`-hRiz!G&pKe9$JVKDXlF_diM3z9Nkxt}Ew;`Sru$?> z%^tBv66PaZ?$AEGGkz0WhZ!r7brsy^8daVoUSbo!B%oI-Hm6xRvZqec3pPn#M;&? zF14%6Z0vqzZQ54eR+`vaTG;Ar-85b<7q*U;Mz)P?+cL7H=!{Jij3mP56%IPo?iu|t znXF?`GkVsGLl|aH>m*H!I=Ev!eeGp^v^?=A@bbf0`(#Twg=qe>b=q z$Qb~e!Li`$==pN)Ulp7KMEAcNJ^!!4yFmf`Hh2X1F}l92`M&{({=XmOfEXNZMc=;` z90Zqwjo^47Hh`aizXN{=E&(Tl&!h8S1 zK-U-je*x?UvabKL@KX5sGU4fZE*WmfrR1%%l9{coR8iLP(u(Yq)=SLNN}WS1y$LF- zPwA2;r(SXfhO8an+#)s}tg_A=%K|g(`+1KVe)1de@Jz2d>BXA%;oP7+JJs%P=>jgG zl}dVNb}5a`gShyC;`-UF;kwOK9e!Tv1#uItmPjQ_NJ24TTG#soY)GHI^Xa>$w|fzN zx6y)HJuYWVrRAmLCC0)50y*h&tBTt{E~k(t8I*kAx^T+J+bf|}ck+uAakU_aG~@Wk zkSV4)D>i$oMDi}WM(vn8fv9+*Q5cRx)^_Rh=R0d^kM_ZI@J&yd8u=#ffqF!;75(gf z$x!sLdZ51L7JG+ag%RMPx!zLc7!> zmmZ0=-8>aJx~)Wsm0wCiJ2JqQv`+PJng&ynd?@MBlG$}FK|@)WVc-fD3pR$g^OjUP z5*yT3ds|PuCX$AowUtIt3COfmru8D~PKi|&Dv^{FH2J!JDJ1|rEW{E~N$rc_pms<``BD7*7>(QJR1P}Zz#a6T9 z{Bs%?FODr63VF~JB;|G3i4AYh4#uZ|hW0kN@d@9mJeISqP1?k!WL27b;NKbO%H8`K zp`dS9mdTef%M97OB-w`9X_vi-4%GH7?<@H2G&Tfv`!MX(wC5MBRYz+ZtEgQtND!4@F< z{@)0iU>wNVfWHYoM0sDdY06Yxbg1-Mn@Ii0@hC z_+K){s9wnO0~zNF_!0fJOIWEjxqceh@$5mTPql5e&_0xYR4KkeOK!QgJRobbVtGhF z@<}|UK$S?xkYWVIYhX4tw`-3V&)f6eCnvW5jec)#NS{@6yw$R6PBX3SDwh28QgMmP z^ByF1wjn)Lk-b;fywW$U;>(}{J}O1Sp5hB+Pd!XY4aueM_R#8rRPCnLPRb-*+xFo5 z`s5qZB71Fhkw}No_9F59h7`FspNafDgw~jps+#xL4I+|7`^Fjxrdw*{w%1-Gw=#PZ zT5Tjm`?ebirU@3=h%%dpE-j=*WIm>l^bK{e2w`-d({;E*&hZsIiexdYvcV z<}>o!Oi8ETzO@Ho^6Z_Rk_IQ2#ib~v3C%o#d14662Z_7O@iu6b-fq>0`i|Av1vS-M zlhpB!OeM+u#eG{r|5F=az)6uN5eoC|n;;~93lq5wYAn{aR5S^p-wW!NIkuI@e$~m> zcuNR{Ix;phqNrD8@S77|{5GcZlyuv-B6(^xACr)EG#QDO&{))MYY=;=%|y})*xg$L zkr)AU`1(Ne_kXJN3!7qMABvuN&{<0K|3P%pH=&=({C}YT-++$4A6x>C2cJa0m%aa1 zfb98yH25I;{Q)or{tbQpR`5!&0~`%Li!T3iPy`F$N#JMb^s>L-)nEba1mfp^Joqbg z`fI@=I14-pJQn;K_zAlGmw~MJe+QTYli*?C)9ClIuYVn!4ITsJ9KRocZvdJ5uYson z(fz-Jo_`|{{eLg`9U%MteGa?=RKPRAG`JW2{^Q`y;7yOl$!QaEzHv{4C8->T~xunYEQvHlN+TGhbIpSMB&}dZh;vb|Ah4wlMYhK87 zMb8JpV*=7Smz6Qu;=?<5I*kKK=y=~_wq}%kf-`#O+|AgBkl~@1wL3r8U6!s%#;EnA z!1(X2)YdPB*o(-ve~2y|HJP%dNynyBTD?HnB?NQHo!z^%V`zhpaRFg=rGg$}~5(@{Ezwtk`x zV}>WJRLkHV5Yx>tFX5~!hj$ETgby_9g=)ilyZZ0grtIOV+*U#>f|+2%wCyzqBS(FN+u%qt!NsaOp_pv4 zEMrIox1CDES*IW#`MJ3xQFA>`s|4jXJ|CQw?Ct!|UPnerIB`i=vdk)m59xZmH&nE) zL%9~QQ0v*k7?P~WI#0wMKD-|n7zw#Id@jF&OL;J*f?wvRIvaM!%-mU1b5jY;HI#kB z5iIJNH0cIM-Wxf2{clnt;I+~N|bnceY1a< zrdJQ5qqNs)u-Obo29sV~l>b26QI5BRx_PNOHH|T0U*YenFLE8SQBAwZwtg9Zd})YH z*rTj_U=n+0ZQuZ|U~)HL%%KwMjWv%5o%*aa(f^-0x9)My4 z_zm!7bpGE5Vgr~5r-M7u{r??^EkNe{)8JGf^Zx$_l)-u6H^9l@8`uC|3gn!>5|DHL zzJ%^C_JCdBap0@y`(g*U9880wfcW`;9=sh~0!{>*A z-vu{;KLB-bK9K$WQb6YT-vH*oHgGEV8an-F!DqnRKpl*MAEV2E0K5^%-hL;8Uk5*e zZ=&N1|I``^pMQt;5r~L<+TvSw*}1;8(5g0Dd3TA^st2+QxU;(~A53Qpg?e${#HQ|B zw}qC0cyc0_rkzxX2-XRgYO7TyD@wVdTd&s`I>^#?nY?G@KkVNd9UJn=7-D~>{W*8Z zToTZ>NM7uqNIe%s(reW#NV3`hknw_YNfxdRY~+0ft=4OQAv-#z2RUIQk0fHpi9A#} zq$FdxR02Kp|B)n&)*HF6l4c~T%3vNsWOZne&!3ch2FA`P>s0JCj7mJt(rIK8%%3r z$MmQ^l2*Hlkg^)FbN)uVw8Kh8OO_F$w}eJ@LKHnExyYr{X}hUeC>aTLYKw-F*9&E- z{JE^FsW|n(^d6h(IOu6g_>}s)1T-$kb$=~;rDbqhXt|Y=-}>4nX6C3QL?@CYn2}at zuZ=L%vRkV8EZR_GPde3#$9nU>DNB>Aif9hZP0!Bd&z+mQP;66AzjV@KQ=1*%yfoFg zmvvN?+M1lyCZn+?({#purd|7$oDm&Yer+}D9@FD4tiyuRprL2XDm410Lr6*Z;S1K` zC1h5YpHf83BN8I})f`xh8B7)Z21RY9QwyVtVzQ%c93$Ps6=)+VTb_+aQE|2(9BFA5 zWw$TFmSA*x&*x&TYF#3nEWA-=rOQ6h>)isH15|@4A*~R3dC=vig5FOx2_eJ?FU!+y zQBB@WIdk^Z`P0f$tW5-6`xRejwlZ8#hvKJTmn*R+>A(h|)E*26eCh2oacN1&Qk%ZE zy^UYDN?)DyyajcSpY#UR(>!(_iFK=kcdyz6>cx98CJ&0(P>ejH>83(eTR)OK<;h{l zO0)onB`Q*w!%DzwLrJoxsf^W%EozF(Z7n0dB}MFohA~8bdRwMu$_s#{8@3 z|ETe)D}4Pw*#Ga7==-k#F9**CXMlSM_z_S8PXymU&wn+@gCQ^o{u>?ttKb^&6fg;n z1OJGgFZ=z8&M*7@KN19M{Xd4z{~92^0FMFppznVV{2lmP@M<9E{yhQw2)$qY{a*(T zf;@O87z01V2Jn8c4{QW-=HJh-1Nh3wS4Z2RH!sfQNuvu?5K4f3go?89Wi(1y8RB!dtbzB4QK>dJ(U> zS;=2()zQJ~&Jwj&YTF-#ZO3l=b_xS5iS*J`OPfbVOXZ2p1;bd^CS!BFOVf-Prd5(V5X%op=e`>{2`xoIa)2+0)BQhH^!c^v*T1 zxG`@j=x%A!$XKhBk(Anv(88t~E1cTQG{%=B!jlMPJ(qEzuyt%?)53_k^L!+w^_MYL zJgEV-1+~l865gJUON9e@+3R|8Y(x*p!f?zhGgf8eIJTi!;s9`$)5V=-ZINI}-sX+}hP!NFT+zmxo)kTSMobUP+T=+X>cKXW4q$>G~Q zGAltaB1s)KuiDFG!pK2bC|868%@%pIe%9Pya@VTfhmu!Dremz^nHN!h8ckkzypbU5 z5TN_6%(3E1^#?{@Z#rhUsHVFZhI$*wjasu`s<*~YB<~>CS#i3L6jThiN?*bmJHp=% z2K%T`a%{TF{qZpmW|8ZNGQRwxk*71a*ys* zQZq=6oHuElMj-B9bK=ufD0=NU)z~6xD!5hHq!2z%xoD6QIkZ%Rg1&iPu za5nf~;8yGe*MWmT_6x`WIXB>w*a@x&{~Hv*6nGN&AvS_9f@{H5;2Gc)a1{6vHiB=1 zH-Q5{>;_}t5#ZtAHf#kq0NF!eJNPMfg1-R22lj%qz-iz_a0hiF_3~b+8~>78mP^TO zREm1+Q`AcF*vcN|O_Ned`~R|P9yWR_v9pK-@#j&MP98q6`yhY0l@d=&iJ%>ej#hH( zb_33=x(f@HwRISibCOcotzan2=yZ*S+e^c5F;!P6dA()6S<8C`TxTNDids)(y!>)> zO&su;@hZ-UZ1B2khhwrl&ra6Z7ZsNi-)$lR>~Aoc-6?bGINcVSE%tLPxaK}x5=bu3 zyPGW`!(lJmEUqN(Y`?8EIy$zwP)=}&HOk`8;ufOs$u}&bOM_{z z#GYeeN}|i92I)$nQzHVM-%`-VXxUYuC`s)Lm4=O7}fL{RF)Cj*%V1_K+scJtM&3GTEZsW^RP8B$jGpkS0&D zGE`loZ7HnO=zCakF?`nZOhXt$H4Bf}w#?TC22E<4dI-|(#gwl|?=nUY(nCVj9Eu@# z;Ug(RyV?we-Kws{cchq=CmV%=M?m%KtJSK01GrYusyT~)Rj zu}-oVL|ev&!6n*y1wx0nvJG2Pz_i}-Pco|O`W6b?Tk z2@Nt`7o}7Ot-+~V3=P`Ik)%&JRURnR7e@!v{2x>Q$92ZiDFoyUuqGiEa=Ya))1{M& fNt*pKuqCyJ>5CwWVP}?N>8@Cdl)4{=A58s!W(kc5 literal 0 HcmV?d00001 diff --git a/unit_tests/utilities/test_zaza_utilities_openstack.py b/unit_tests/utilities/test_zaza_utilities_openstack.py index a6aa190..b0d76b0 100644 --- a/unit_tests/utilities/test_zaza_utilities_openstack.py +++ b/unit_tests/utilities/test_zaza_utilities_openstack.py @@ -581,17 +581,23 @@ class TestOpenStackUtils(ut_utils.BaseTestCase): nova_mock.keypairs.create.assert_called_once_with(name='mykeys') def test_get_private_key_file(self): + self.patch_object(openstack_utils.deployment_env, 'get_tmpdir', + return_value=False) + self.get_tmpdir.return_value = '/tmp/zaza-model1' self.assertEqual( openstack_utils.get_private_key_file('mykeys'), - 'tests/id_rsa_mykeys') + '/tmp/zaza-model1/id_rsa_mykeys') def test_write_private_key(self): + self.patch_object(openstack_utils.deployment_env, 'get_tmpdir', + return_value=False) + self.get_tmpdir.return_value = '/tmp/zaza-model1' m = mock.mock_open() with mock.patch( 'zaza.openstack.utilities.openstack.open', m, create=False ): openstack_utils.write_private_key('mykeys', 'keycontents') - m.assert_called_once_with('tests/id_rsa_mykeys', 'w') + m.assert_called_once_with('/tmp/zaza-model1/id_rsa_mykeys', 'w') handle = m() handle.write.assert_called_once_with('keycontents') diff --git a/zaza/openstack/utilities/.openstack.py.swp b/zaza/openstack/utilities/.openstack.py.swp new file mode 100644 index 0000000000000000000000000000000000000000..8928f8e097d100e8cc7cc6552fcb436d622ccf75 GIT binary patch literal 16384 zcmeHOZHy#E8E*OT1ov%

9!J($7W~T3Y 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 Date: Tue, 7 Jul 2020 12:24:32 +0000 Subject: [PATCH 40/59] Remove swap files --- .../.test_zaza_utilities_openstack.py.swp | Bin 73728 -> 0 bytes zaza/openstack/utilities/.openstack.py.swp | Bin 16384 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 unit_tests/utilities/.test_zaza_utilities_openstack.py.swp delete mode 100644 zaza/openstack/utilities/.openstack.py.swp diff --git a/unit_tests/utilities/.test_zaza_utilities_openstack.py.swp b/unit_tests/utilities/.test_zaza_utilities_openstack.py.swp deleted file mode 100644 index 2ef70f74efe45a7a7db302b8b0e7266b3d9ec4d8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 73728 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%0AjUXHRl4eUJnLYj8_^v1 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 diff --git a/zaza/openstack/utilities/.openstack.py.swp b/zaza/openstack/utilities/.openstack.py.swp deleted file mode 100644 index 8928f8e097d100e8cc7cc6552fcb436d622ccf75..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16384 zcmeHOZHy#E8E*OT1ov%

9!J($7W~T3Y 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 Date: Tue, 7 Jul 2020 12:32:54 +0000 Subject: [PATCH 41/59] Fix another unit test --- unit_tests/utilities/test_zaza_utilities_openstack.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/unit_tests/utilities/test_zaza_utilities_openstack.py b/unit_tests/utilities/test_zaza_utilities_openstack.py index b0d76b0..0b504b1 100644 --- a/unit_tests/utilities/test_zaza_utilities_openstack.py +++ b/unit_tests/utilities/test_zaza_utilities_openstack.py @@ -582,16 +582,14 @@ class TestOpenStackUtils(ut_utils.BaseTestCase): def test_get_private_key_file(self): self.patch_object(openstack_utils.deployment_env, 'get_tmpdir', - return_value=False) - self.get_tmpdir.return_value = '/tmp/zaza-model1' + return_value='/tmp/zaza-model1') self.assertEqual( openstack_utils.get_private_key_file('mykeys'), '/tmp/zaza-model1/id_rsa_mykeys') def test_write_private_key(self): self.patch_object(openstack_utils.deployment_env, 'get_tmpdir', - return_value=False) - self.get_tmpdir.return_value = '/tmp/zaza-model1' + return_value='/tmp/zaza-model1') m = mock.mock_open() with mock.patch( 'zaza.openstack.utilities.openstack.open', m, create=False @@ -602,6 +600,8 @@ class TestOpenStackUtils(ut_utils.BaseTestCase): handle.write.assert_called_once_with('keycontents') def test_get_private_key(self): + self.patch_object(openstack_utils.deployment_env, 'get_tmpdir', + return_value='/tmp/zaza-model1') self.patch_object(openstack_utils.os.path, "isfile", return_value=True) m = mock.mock_open(read_data='myprivkey') From 7f122b8611198fc8b36f9a8ce6942e7224b1197c Mon Sep 17 00:00:00 2001 From: Liam Young Date: Tue, 7 Jul 2020 12:38:18 +0000 Subject: [PATCH 42/59] Last unit test fix (passing locally, was failing in travis) --- unit_tests/utilities/test_zaza_utilities_openstack.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/unit_tests/utilities/test_zaza_utilities_openstack.py b/unit_tests/utilities/test_zaza_utilities_openstack.py index 0b504b1..549ed71 100644 --- a/unit_tests/utilities/test_zaza_utilities_openstack.py +++ b/unit_tests/utilities/test_zaza_utilities_openstack.py @@ -613,6 +613,8 @@ class TestOpenStackUtils(ut_utils.BaseTestCase): 'myprivkey') def test_get_private_key_file_missing(self): + self.patch_object(openstack_utils.deployment_env, 'get_tmpdir', + return_value='/tmp/zaza-model1') self.patch_object(openstack_utils.os.path, "isfile", return_value=False) self.assertIsNone(openstack_utils.get_private_key('mykeys')) From 7265b79769c05abc28373da0e5ecd60df8c87b17 Mon Sep 17 00:00:00 2001 From: David Ames Date: Wed, 8 Jul 2020 10:27:01 -0700 Subject: [PATCH 43/59] Fixes for MySQL testing * Better handle workload state checking during cold start * Switch from debug to info to see log messages --- zaza/openstack/charm_tests/mysql/tests.py | 53 +++++++++++------------ 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/zaza/openstack/charm_tests/mysql/tests.py b/zaza/openstack/charm_tests/mysql/tests.py index 7bf0979..bf61932 100644 --- a/zaza/openstack/charm_tests/mysql/tests.py +++ b/zaza/openstack/charm_tests/mysql/tests.py @@ -149,7 +149,7 @@ class MySQLCommonTests(MySQLBaseTest): set_alternate = {"max-connections": "1000"} # Make config change, check for service restarts - logging.debug("Setting max connections ...") + logging.info("Setting max connections ...") self.restart_on_changed( self.conf_file, set_default, @@ -198,7 +198,7 @@ class PerconaClusterBaseTest(MySQLBaseTest): output = zaza.model.run_on_leader( self.application, cmd)["Stdout"].strip() value = re.search(r"^.+?\s+(.+)", output).group(1) - logging.debug("%s = %s" % (attr, value)) + logging.info("%s = %s" % (attr, value)) return value def is_pxc_bootstrapped(self): @@ -236,7 +236,7 @@ class PerconaClusterBaseTest(MySQLBaseTest): cmd = "ip -br addr" result = zaza.model.run_on_unit(unit.entity_id, cmd) output = result.get("Stdout").strip() - logging.debug(output) + logging.info(output) if self.vip in output: logging.info("vip ({}) running in {}".format( self.vip, @@ -333,12 +333,12 @@ class PerconaClusterColdStartTest(PerconaClusterBaseTest): juju_utils.get_machine_uuids_for_application(self.application)) # Stop Nodes # Avoid hitting an update-status hook - logging.debug("Wait till model is idle ...") + logging.info("Wait till model is idle ...") zaza.model.block_until_all_units_idle() logging.info("Stopping instances: {}".format(_machines)) for uuid in _machines: self.nova_client.servers.stop(uuid) - logging.debug("Wait till all machines are shutoff ...") + logging.info("Wait till all machines are shutoff ...") for uuid in _machines: openstack_utils.resource_reaches_status(self.nova_client.servers, uuid, @@ -357,7 +357,7 @@ class PerconaClusterColdStartTest(PerconaClusterBaseTest): 'unknown', negate_match=True) - logging.debug("Wait till model is idle ...") + logging.info("Wait till model is idle ...") # XXX If a hook was executing on a unit when it was powered off # it comes back in an error state. try: @@ -366,7 +366,7 @@ class PerconaClusterColdStartTest(PerconaClusterBaseTest): self.resolve_update_status_errors() zaza.model.block_until_all_units_idle() - logging.debug("Wait for application states ...") + logging.info("Wait for application states ...") for unit in zaza.model.get_units(self.application): try: zaza.model.run_on_unit(unit.entity_id, "hooks/update-status") @@ -389,7 +389,7 @@ class PerconaClusterColdStartTest(PerconaClusterBaseTest): _non_leaders[0], "bootstrap-pxc", action_params={}) - logging.debug("Wait for application states ...") + logging.info("Wait for application states ...") for unit in zaza.model.get_units(self.application): zaza.model.run_on_unit(unit.entity_id, "hooks/update-status") states = {"percona-cluster": { @@ -403,7 +403,7 @@ class PerconaClusterColdStartTest(PerconaClusterBaseTest): self.application, "notify-bootstrapped", action_params={}) - logging.debug("Wait for application states ...") + logging.info("Wait for application states ...") for unit in zaza.model.get_units(self.application): zaza.model.run_on_unit(unit.entity_id, "hooks/update-status") test_config = lifecycle_utils.get_charm_config(fatal=False) @@ -532,12 +532,12 @@ class MySQLInnoDBClusterColdStartTest(MySQLBaseTest): juju_utils.get_machine_uuids_for_application(self.application)) # Stop Nodes # Avoid hitting an update-status hook - logging.debug("Wait till model is idle ...") + logging.info("Wait till model is idle ...") zaza.model.block_until_all_units_idle() logging.info("Stopping instances: {}".format(_machines)) for uuid in _machines: self.nova_client.servers.stop(uuid) - logging.debug("Wait till all machines are shutoff ...") + logging.info("Wait till all machines are shutoff ...") for uuid in _machines: openstack_utils.resource_reaches_status(self.nova_client.servers, uuid, @@ -550,38 +550,37 @@ class MySQLInnoDBClusterColdStartTest(MySQLBaseTest): for uuid in _machines: self.nova_client.servers.start(uuid) + logging.info( + "Wait till all {} units are in state 'unkown' ..." + .format(self.application)) for unit in zaza.model.get_units(self.application): zaza.model.block_until_unit_wl_status( unit.entity_id, 'unknown', negate_match=True) - logging.debug("Wait till model is idle ...") + logging.info("Wait till model is idle ...") try: zaza.model.block_until_all_units_idle() except zaza.model.UnitError: self.resolve_update_status_errors() zaza.model.block_until_all_units_idle() - logging.debug("Clear error hooks after reboot ...") + logging.info("Clear error hooks after reboot ...") for unit in zaza.model.get_units(self.application): try: zaza.model.run_on_unit(unit.entity_id, "hooks/update-status") except zaza.model.UnitError: self.resolve_update_status_errors() zaza.model.run_on_unit(unit.entity_id, "hooks/update-status") - logging.debug("Wait for application states blocked ...") - states = { - self.application: { - "workload-status": "blocked", - "workload-status-message": - "MySQL InnoDB Cluster not healthy: None"}, - "mysql-router": { - "workload-status": "blocked", - "workload-status-message": - "Failed to connect to MySQL"}} - zaza.model.wait_for_application_states(states=states) + logging.info( + "Wait till all {} units are in state 'blocked' ..." + .format(self.application)) + for unit in zaza.model.get_units(self.application): + zaza.model.block_until_unit_wl_status( + unit.entity_id, + 'blocked') logging.info("Execute reboot-cluster-from-complete-outage " "action after cold boot ...") @@ -592,15 +591,15 @@ class MySQLInnoDBClusterColdStartTest(MySQLBaseTest): unit.entity_id, "reboot-cluster-from-complete-outage", action_params={}) - if "Success" in action.data["results"].get("outcome"): + if "Success" in action.data.get("results", {}).get("outcome", ""): break else: - logging.info(action.data["results"].get("output")) + logging.info(action.data.get("results", {}).get("output", "")) assert "Success" in action.data["results"]["outcome"], ( "Reboot cluster from complete outage action failed: {}" .format(action.data)) - logging.debug("Wait for application states ...") + logging.info("Wait for application states ...") for unit in zaza.model.get_units(self.application): zaza.model.run_on_unit(unit.entity_id, "hooks/update-status") test_config = lifecycle_utils.get_charm_config(fatal=False) From 701e751398e4fe9077b5cc20f9f5d59875ad5e00 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Thu, 9 Jul 2020 10:15:56 +0000 Subject: [PATCH 44/59] Add check that guest is up On a recent mosci run the guest used for a restart test had a pending power task which stopped the restart test working properly. This PR adds an assertion that the guest is ready. --- zaza/openstack/charm_tests/masakari/tests.py | 22 ++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/zaza/openstack/charm_tests/masakari/tests.py b/zaza/openstack/charm_tests/masakari/tests.py index d9490d2..6f27d17 100644 --- a/zaza/openstack/charm_tests/masakari/tests.py +++ b/zaza/openstack/charm_tests/masakari/tests.py @@ -134,6 +134,26 @@ class MasakariTest(test_utils.OpenStackBaseTest): vm_uuid, model_name=self.model_name) + @tenacity.retry(wait=tenacity.wait_exponential(multiplier=2, max=60), + reraise=True, stop=tenacity.stop_after_attempt(5), + retry=tenacity.retry_if_exception_type(AssertionError)) + def wait_for_guest_ready(self, vm_name): + """Wait for the guest to be ready. + + :param vm_name: Name of guest to check. + :type vm_name: str + """ + guest_ready_attr_checks = [ + ('OS-EXT-STS:task_state', None), + ('status', 'ACTIVE'), + ('OS-EXT-STS:power_state', 1), + ('OS-EXT-STS:vm_state', 'active')] + guest = self.nova_client.servers.find(name=vm_name) + logging.info('Checking guest {} attributes'.format(vm_name)) + for (attr, required_state) in guest_ready_attr_checks: + logging.info('Checking {} is {}'.format(attr, required_state)) + assert getattr(guest, attr) == required_state + def test_instance_failover(self): """Test masakari managed guest migration.""" # Workaround for Bug #1874719 @@ -168,6 +188,7 @@ class MasakariTest(test_utils.OpenStackBaseTest): model_name=self.model_name) openstack_utils.enable_all_nova_services(self.nova_client) zaza.openstack.configure.masakari.enable_hosts() + self.wait_for_guest_ready(vm_name) def test_instance_restart_on_fail(self): """Test single guest crash and recovery.""" @@ -178,6 +199,7 @@ class MasakariTest(test_utils.OpenStackBaseTest): self.current_release)) vm_name = 'zaza-test-instance-failover' vm = self.ensure_guest(vm_name) + self.wait_for_guest_ready(vm_name) _, unit_name = self.get_guests_compute_info(vm_name) logging.info('{} is running on {}'.format(vm_name, unit_name)) guest_pid = self.get_guest_qemu_pid( From 0287664d9243efbc26994739f095f910bf12a047 Mon Sep 17 00:00:00 2001 From: Aurelien Lourot Date: Thu, 9 Jul 2020 16:35:00 +0200 Subject: [PATCH 45/59] Fix paramiko.ssh_exception.BadAuthenticationType (#355) Fix paramiko.ssh_exception.BadAuthenticationType when SSHing to a new Ubuntu instance. Note that paramiko still has a few issues around authentication: https://github.com/paramiko/paramiko/pull/1106/files This paramiko PR also shows that password='' isn't the same as password=None --- unit_tests/utilities/test_zaza_utilities_openstack.py | 2 +- zaza/openstack/utilities/openstack.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/unit_tests/utilities/test_zaza_utilities_openstack.py b/unit_tests/utilities/test_zaza_utilities_openstack.py index 549ed71..a3ad423 100644 --- a/unit_tests/utilities/test_zaza_utilities_openstack.py +++ b/unit_tests/utilities/test_zaza_utilities_openstack.py @@ -773,7 +773,7 @@ class TestOpenStackUtils(ut_utils.BaseTestCase): privkey='myprivkey') paramiko_mock.connect.assert_called_once_with( '10.0.0.10', - password='', + password=None, pkey='akey', username='bob') diff --git a/zaza/openstack/utilities/openstack.py b/zaza/openstack/utilities/openstack.py index 034cae3..bf7a041 100644 --- a/zaza/openstack/utilities/openstack.py +++ b/zaza/openstack/utilities/openstack.py @@ -2401,7 +2401,7 @@ def ssh_command(username, ssh.connect(ip, username=username, password=password) else: key = paramiko.RSAKey.from_private_key(io.StringIO(privkey)) - ssh.connect(ip, username=username, password='', pkey=key) + ssh.connect(ip, username=username, password=None, pkey=key) logging.info("Running {} on {}".format(command, vm_name)) stdin, stdout, stderr = ssh.exec_command(command) if verify and callable(verify): From 4546e821c1bf1bf868c2a6c852a97df937b7b660 Mon Sep 17 00:00:00 2001 From: David Ames Date: Thu, 9 Jul 2020 09:27:25 -0700 Subject: [PATCH 46/59] Wait for resolving update-status hook errors The cold boot restart takes too long on update-status. Make the timeout longer. --- zaza/openstack/charm_tests/mysql/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zaza/openstack/charm_tests/mysql/tests.py b/zaza/openstack/charm_tests/mysql/tests.py index bf61932..c9a767d 100644 --- a/zaza/openstack/charm_tests/mysql/tests.py +++ b/zaza/openstack/charm_tests/mysql/tests.py @@ -521,7 +521,7 @@ class MySQLInnoDBClusterColdStartTest(MySQLBaseTest): zaza.model.resolve_units( application_name=self.application, erred_hook='update-status', - wait=True) + wait=True, timeout=180) def test_100_reboot_cluster_from_complete_outage(self): """Reboot cluster from complete outage. From f6e2a40166caefcdd9b8ce0bc9e2389791551a11 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Mon, 13 Jul 2020 14:33:35 +0000 Subject: [PATCH 47/59] Set application_name for ceph-radosgw tests Set application_name for ceph-radosgw tests so that the tests can be run without relying on getting the application name from the tests.yaml. --- zaza/openstack/charm_tests/ceph/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zaza/openstack/charm_tests/ceph/tests.py b/zaza/openstack/charm_tests/ceph/tests.py index ff79820..c25df2a 100644 --- a/zaza/openstack/charm_tests/ceph/tests.py +++ b/zaza/openstack/charm_tests/ceph/tests.py @@ -544,7 +544,7 @@ class CephRGWTest(test_utils.OpenStackBaseTest): @classmethod def setUpClass(cls): """Run class setup for running ceph low level tests.""" - super(CephRGWTest, cls).setUpClass() + super(CephRGWTest, cls).setUpClass(application_name='ceph-radosgw') @property def expected_apps(self): From 65cc6aa604912291dc9772db1b59473414f83d49 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Mon, 13 Jul 2020 14:36:59 +0000 Subject: [PATCH 48/59] Stop assuming RegionOne in ceph-radosgw tests Stop assuming RegionOne in ceph-radosgw tests and get the region from the app config instead. --- zaza/openstack/charm_tests/ceph/tests.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/zaza/openstack/charm_tests/ceph/tests.py b/zaza/openstack/charm_tests/ceph/tests.py index ff79820..b4ae613 100644 --- a/zaza/openstack/charm_tests/ceph/tests.py +++ b/zaza/openstack/charm_tests/ceph/tests.py @@ -622,7 +622,9 @@ class CephRGWTest(test_utils.OpenStackBaseTest): 'multisite configuration') logging.info('Checking Swift REST API') keystone_session = zaza_openstack.get_overcloud_keystone_session() - region_name = 'RegionOne' + region_name = zaza_model.get_application_config( + self.application_name, + model_name=self.model_name)['region']['value'] swift_client = zaza_openstack.get_swift_session_client( keystone_session, region_name, From 9e395f69b76e6a70f053c2a35cf4741a7dfca7f0 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Mon, 13 Jul 2020 14:42:43 +0000 Subject: [PATCH 49/59] Add ceph_ready config step Add a ceph_ready config step which just checks that the ceph units are in a 'active' 'Unit is Ready.*' state. This is useful when encryption at rest is being used and the tests.yaml assumes that the units are going to come up blocked. Once vault is ready the tests then need to wait for the ceph units to become unblocked. --- zaza/openstack/charm_tests/ceph/setup.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/zaza/openstack/charm_tests/ceph/setup.py b/zaza/openstack/charm_tests/ceph/setup.py index c53ff3c..c93fec8 100644 --- a/zaza/openstack/charm_tests/ceph/setup.py +++ b/zaza/openstack/charm_tests/ceph/setup.py @@ -14,7 +14,23 @@ """Setup for ceph-osd deployments.""" +import logging +import zaza.model + def basic_setup(): """Run basic setup for ceph-osd.""" pass + + +def ceph_ready(): + """Wait for ceph to be ready. + + Wait for ceph to be ready. This is useful if the target_deploy_status in + the tests.yaml is expecting ceph to be in a blocked state. After ceph + has been unblocked the deploy may need to wait to be ceph to be ready. + """ + logging.info("Waiting for ceph units to settle") + zaza.model.wait_for_application_states() + zaza.model.block_until_all_units_idle() + logging.info("Ceph units settled") From c4176881d00b1191b290f882e245f9ed1b4f4e31 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Mon, 13 Jul 2020 14:56:04 +0000 Subject: [PATCH 50/59] Fix spelling typo --- zaza/openstack/charm_tests/ceph/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zaza/openstack/charm_tests/ceph/setup.py b/zaza/openstack/charm_tests/ceph/setup.py index c93fec8..87f213d 100644 --- a/zaza/openstack/charm_tests/ceph/setup.py +++ b/zaza/openstack/charm_tests/ceph/setup.py @@ -28,7 +28,7 @@ def ceph_ready(): Wait for ceph to be ready. This is useful if the target_deploy_status in the tests.yaml is expecting ceph to be in a blocked state. After ceph - has been unblocked the deploy may need to wait to be ceph to be ready. + has been unblocked the deploy may need to wait for ceph to be ready. """ logging.info("Waiting for ceph units to settle") zaza.model.wait_for_application_states() From e409b68639f75bc5b333b6d4074ae92602bc4216 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Tue, 14 Jul 2020 12:39:58 +0000 Subject: [PATCH 51/59] Add Groovy Victoria support --- zaza/openstack/utilities/os_versions.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/zaza/openstack/utilities/os_versions.py b/zaza/openstack/utilities/os_versions.py index b2e430e..648a73b 100644 --- a/zaza/openstack/utilities/os_versions.py +++ b/zaza/openstack/utilities/os_versions.py @@ -35,6 +35,7 @@ UBUNTU_OPENSTACK_RELEASE = OrderedDict([ ('disco', 'stein'), ('eoan', 'train'), ('focal', 'ussuri'), + ('groovy', 'victoria'), ]) @@ -57,6 +58,7 @@ OPENSTACK_CODENAMES = OrderedDict([ ('2019.1', 'stein'), ('2019.2', 'train'), ('2020.1', 'ussuri'), + ('2020.2', 'victoria'), ]) OPENSTACK_RELEASES_PAIRS = [ @@ -66,7 +68,8 @@ OPENSTACK_RELEASES_PAIRS = [ 'xenial_pike', 'artful_pike', 'xenial_queens', 'bionic_queens', 'bionic_rocky', 'cosmic_rocky', 'bionic_stein', 'disco_stein', 'bionic_train', - 'eoan_train', 'bionic_ussuri', 'focal_ussuri'] + 'eoan_train', 'bionic_ussuri', 'focal_ussuri', + 'focal-victoria', 'groovy-victoria'] # The ugly duckling - must list releases oldest to newest SWIFT_CODENAMES = OrderedDict([ @@ -106,6 +109,8 @@ SWIFT_CODENAMES = OrderedDict([ ['2.22.0']), ('ussuri', ['2.24.0']), + ('victoria', + ['2.25.0']), ]) # >= Liberty version->codename mapping @@ -121,6 +126,7 @@ PACKAGE_CODENAMES = { ('19', 'stein'), ('20', 'train'), ('21', 'ussuri'), + ('22', 'victoria'), ]), 'neutron-common': OrderedDict([ ('7', 'liberty'), @@ -133,6 +139,7 @@ PACKAGE_CODENAMES = { ('14', 'stein'), ('15', 'train'), ('16', 'ussuri'), + ('17', 'victoria'), ]), 'cinder-common': OrderedDict([ ('7', 'liberty'), @@ -145,6 +152,7 @@ PACKAGE_CODENAMES = { ('14', 'stein'), ('15', 'train'), ('16', 'ussuri'), + ('17', 'victoria'), ]), 'keystone': OrderedDict([ ('8', 'liberty'), @@ -157,6 +165,7 @@ PACKAGE_CODENAMES = { ('15', 'stein'), ('16', 'train'), ('17', 'ussuri'), + ('18', 'victoria'), ]), 'horizon-common': OrderedDict([ ('8', 'liberty'), @@ -169,6 +178,7 @@ PACKAGE_CODENAMES = { ('15', 'stein'), ('16', 'train'), ('17', 'ussuri'), + ('19', 'victoria'), ]), 'ceilometer-common': OrderedDict([ ('5', 'liberty'), @@ -181,6 +191,7 @@ PACKAGE_CODENAMES = { ('12', 'stein'), ('13', 'train'), ('14', 'ussuri'), + ('15', 'victoria'), ]), 'heat-common': OrderedDict([ ('5', 'liberty'), @@ -193,6 +204,7 @@ PACKAGE_CODENAMES = { ('12', 'stein'), ('13', 'train'), ('14', 'ussuri'), + ('15', 'victoria'), ]), 'glance-common': OrderedDict([ ('11', 'liberty'), @@ -205,6 +217,7 @@ PACKAGE_CODENAMES = { ('18', 'stein'), ('19', 'train'), ('20', 'ussuri'), + ('21', 'victoria'), ]), 'openstack-dashboard': OrderedDict([ ('8', 'liberty'), @@ -216,7 +229,8 @@ PACKAGE_CODENAMES = { ('14', 'rocky'), ('15', 'stein'), ('16', 'train'), - ('17', 'ussuri'), + ('18', 'ussuri'), + ('19', 'victoria'), ]), 'designate-common': OrderedDict([ ('1', 'liberty'), @@ -229,6 +243,7 @@ PACKAGE_CODENAMES = { ('8', 'stein'), ('9', 'train'), ('10', 'ussuri'), + ('11', 'victoria'), ]), 'ovn-common': OrderedDict([ ('2', 'train'), From 2d3eaa6e8478679c7bc62c8eef6b18ab96d7ed30 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Tue, 14 Jul 2020 12:42:29 +0000 Subject: [PATCH 52/59] Fix typo --- zaza/openstack/utilities/os_versions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zaza/openstack/utilities/os_versions.py b/zaza/openstack/utilities/os_versions.py index 648a73b..fcf15df 100644 --- a/zaza/openstack/utilities/os_versions.py +++ b/zaza/openstack/utilities/os_versions.py @@ -69,7 +69,7 @@ OPENSTACK_RELEASES_PAIRS = [ 'bionic_queens', 'bionic_rocky', 'cosmic_rocky', 'bionic_stein', 'disco_stein', 'bionic_train', 'eoan_train', 'bionic_ussuri', 'focal_ussuri', - 'focal-victoria', 'groovy-victoria'] + 'focal_victoria', 'groovy_victoria'] # The ugly duckling - must list releases oldest to newest SWIFT_CODENAMES = OrderedDict([ From e729c64956ce6103fd6dca790139474e39278723 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Tue, 14 Jul 2020 12:53:07 +0000 Subject: [PATCH 53/59] Fixed existing codename errors spotted by coreycb --- zaza/openstack/utilities/os_versions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zaza/openstack/utilities/os_versions.py b/zaza/openstack/utilities/os_versions.py index fcf15df..b46fc21 100644 --- a/zaza/openstack/utilities/os_versions.py +++ b/zaza/openstack/utilities/os_versions.py @@ -108,7 +108,7 @@ SWIFT_CODENAMES = OrderedDict([ ('train', ['2.22.0']), ('ussuri', - ['2.24.0']), + ['2.24.0', '2.25.0']), ('victoria', ['2.25.0']), ]) @@ -177,7 +177,7 @@ PACKAGE_CODENAMES = { ('14', 'rocky'), ('15', 'stein'), ('16', 'train'), - ('17', 'ussuri'), + ('18', 'ussuri'), ('19', 'victoria'), ]), 'ceilometer-common': OrderedDict([ From 9896c8d9c54be397deef20ea104043a99d6e2ca2 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Tue, 14 Jul 2020 14:07:59 +0000 Subject: [PATCH 54/59] Skip test_003_test_overide_is_observed Skip test_003_test_overide_is_observed until Bug #1880959 is fix released. --- zaza/openstack/charm_tests/policyd/tests.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/zaza/openstack/charm_tests/policyd/tests.py b/zaza/openstack/charm_tests/policyd/tests.py index 88d6699..67b9909 100644 --- a/zaza/openstack/charm_tests/policyd/tests.py +++ b/zaza/openstack/charm_tests/policyd/tests.py @@ -401,6 +401,10 @@ class BasePolicydSpecialization(PolicydTest, def test_003_test_overide_is_observed(self): """Test that the override is observed by the underlying service.""" + if (openstack_utils.get_os_release() < + openstack_utils.get_os_release('groovy_victoria')): + raise unittest.SkipTest( + "Test skipped until Bug #1880959 is fix released") if self._test_name is None: logging.info("Doing policyd override for {}" .format(self._service_name)) From a670e274fafe032525bb8fd342262b87a47ae797 Mon Sep 17 00:00:00 2001 From: Andrew McLeod Date: Wed, 15 Jul 2020 16:52:27 +0200 Subject: [PATCH 55/59] add image arch and properties specification --- zaza/openstack/charm_tests/glance/setup.py | 26 +++++++++++++++++----- zaza/openstack/utilities/openstack.py | 10 ++++++++- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/zaza/openstack/charm_tests/glance/setup.py b/zaza/openstack/charm_tests/glance/setup.py index 8c7bd3e..ab0a3b3 100644 --- a/zaza/openstack/charm_tests/glance/setup.py +++ b/zaza/openstack/charm_tests/glance/setup.py @@ -16,6 +16,7 @@ import logging import zaza.openstack.utilities.openstack as openstack_utils +import zaza.utilities.deployment_env as deployment_env CIRROS_IMAGE_NAME = "cirros" CIRROS_ALT_IMAGE_NAME = "cirros_alt" @@ -31,7 +32,8 @@ def basic_setup(): """ -def add_image(image_url, glance_client=None, image_name=None, tags=[]): +def add_image(image_url, glance_client=None, image_name=None, tags=[], + properties=None): """Retrieve image from ``image_url`` and add it to glance. :param image_url: Retrievable URL with image data @@ -42,6 +44,8 @@ def add_image(image_url, glance_client=None, image_name=None, tags=[]): :type image_name: str :param tags: List of tags to add to image :type tags: list of str + :param properties: Properties to add to image + :type properties: dict """ if not glance_client: keystone_session = openstack_utils.get_overcloud_keystone_session() @@ -60,7 +64,8 @@ def add_image(image_url, glance_client=None, image_name=None, tags=[]): glance_client, image_url, image_name, - tags=tags) + tags=tags, + properties=properties) def add_cirros_image(glance_client=None, image_name=None): @@ -90,7 +95,8 @@ def add_cirros_alt_image(glance_client=None, image_name=None): add_cirros_image(glance_client, image_name) -def add_lts_image(glance_client=None, image_name=None, release=None): +def add_lts_image(glance_client=None, image_name=None, release=None, + properties=None): """Add an Ubuntu LTS image to the current deployment. :param glance: Authenticated glanceclient @@ -99,12 +105,22 @@ def add_lts_image(glance_client=None, image_name=None, release=None): :type image_name: str :param release: Name of ubuntu release. :type release: str + :param properties: Custom image properties + :type properties: dict """ + deploy_ctxt = deployment_env.get_deployment_context() + image_arch = deploy_ctxt.get('TEST_IMAGE_ARCH', 'amd64') + arch_image_properties = { + 'arm64': {'hw_firmware_type': 'uefi'}, + 'ppc64el': {'architecture': 'ppc64'}} + properties = properties or arch_image_properties.get(image_arch) + logging.info("Image architecture set to {}".format(image_arch)) image_name = image_name or LTS_IMAGE_NAME release = release or LTS_RELEASE image_url = openstack_utils.find_ubuntu_image( release=release, - arch='amd64') + arch=image_arch) add_image(image_url, glance_client=glance_client, - image_name=image_name) + image_name=image_name, + properties=properties) diff --git a/zaza/openstack/utilities/openstack.py b/zaza/openstack/utilities/openstack.py index bf7a041..76c80d2 100644 --- a/zaza/openstack/utilities/openstack.py +++ b/zaza/openstack/utilities/openstack.py @@ -2036,7 +2036,8 @@ def upload_image_to_glance(glance, local_path, image_name, disk_format='qcow2', return image -def create_image(glance, image_url, image_name, image_cache_dir=None, tags=[]): +def create_image(glance, image_url, image_name, image_cache_dir=None, tags=[], + properties=None): """Download the image and upload it to glance. Download an image from image_url and upload it to glance labelling @@ -2053,6 +2054,8 @@ def create_image(glance, image_url, image_name, image_cache_dir=None, tags=[]): :type image_cache_dir: Option[str, None] :param tags: Tags to add to image :type tags: list of str + :param properties: Properties and values to add to image + :type properties: dict :returns: glance image pointer :rtype: glanceclient.common.utils.RequestIdProxy """ @@ -2074,6 +2077,11 @@ def create_image(glance, image_url, image_name, image_cache_dir=None, tags=[]): logging.debug( 'applying tag to image: glance.image_tags.update({}, {}) = {}' .format(image.id, tags, result)) + + logging.info("Setting image properties: {}".format(properties)) + if properties: + result = glance.images.update(image.id, **properties) + return image From ce6cfe68b31ed3266964b8f118fb95d48e5ac12d Mon Sep 17 00:00:00 2001 From: Liam Young Date: Wed, 15 Jul 2020 20:07:17 +0000 Subject: [PATCH 56/59] Add defaut app name for vrrp tests Set a default application_name for vrrp tests so they can be called by charms other than the neutron-api gate tests. --- zaza/openstack/charm_tests/neutron/tests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zaza/openstack/charm_tests/neutron/tests.py b/zaza/openstack/charm_tests/neutron/tests.py index 620767e..ec6998d 100644 --- a/zaza/openstack/charm_tests/neutron/tests.py +++ b/zaza/openstack/charm_tests/neutron/tests.py @@ -613,7 +613,8 @@ class NeutronNetworkingBase(test_utils.OpenStackBaseTest): @classmethod def setUpClass(cls): """Run class setup for running Neutron API Networking tests.""" - super(NeutronNetworkingBase, cls).setUpClass() + super(NeutronNetworkingBase, cls).setUpClass( + application_name='neutron-api') cls.neutron_client = ( openstack_utils.get_neutron_session_client(cls.keystone_session)) From 158e8ce37f9d9240a396136e91c0387d840a2891 Mon Sep 17 00:00:00 2001 From: Frode Nordahl Date: Wed, 22 Jul 2020 10:40:29 +0200 Subject: [PATCH 57/59] octavia/policyd: Change target for policyd test List available provider drivers as policyd test, this should be more light weight than creating and retrieving a load-balancer. Fixes #349 --- zaza/openstack/charm_tests/policyd/tests.py | 87 +-------------------- 1 file changed, 3 insertions(+), 84 deletions(-) diff --git a/zaza/openstack/charm_tests/policyd/tests.py b/zaza/openstack/charm_tests/policyd/tests.py index 67b9909..e69aaff 100644 --- a/zaza/openstack/charm_tests/policyd/tests.py +++ b/zaza/openstack/charm_tests/policyd/tests.py @@ -659,7 +659,7 @@ class HeatTests(BasePolicydSpecialization): class OctaviaTests(BasePolicydSpecialization): """Test the policyd override using the octavia client.""" - _rule = {'rule.yaml': "{'os_load-balancer_api:loadbalancer:get_one': '!'}"} + _rule = {'rule.yaml': "{'os_load-balancer_api:provider:get_all': '!'}"} @classmethod def setUpClass(cls, application_name=None): @@ -667,89 +667,8 @@ class OctaviaTests(BasePolicydSpecialization): super(OctaviaTests, cls).setUpClass(application_name="octavia") cls.application_name = "octavia" - def setup_for_attempt_operation(self, ip): - """Create a loadbalancer. - - This is necessary so that the attempt is to show the load-balancer and - this is an operator that the policy can stop. Unfortunately, octavia, - whilst it has a policy for just listing load-balancers, unfortunately, - it doesn't work; whereas showing the load-balancer can be stopped. - - NB this only works if the setup phase of the octavia tests have been - completed. - - :param ip: the ip of for keystone. - :type ip: str - """ - logging.info("Setting up loadbalancer.") - auth = openstack_utils.get_overcloud_auth(address=ip) - sess = openstack_utils.get_keystone_session(auth) - - octavia_client = openstack_utils.get_octavia_session_client(sess) - neutron_client = openstack_utils.get_neutron_session_client(sess) - - if openstack_utils.dvr_enabled(): - network_name = 'private_lb_fip_network' - else: - network_name = 'private' - resp = neutron_client.list_networks(name=network_name) - - vip_subnet_id = resp['networks'][0]['subnets'][0] - - res = octavia_client.load_balancer_create( - json={ - 'loadbalancer': { - 'description': 'Created by Zaza', - 'admin_state_up': True, - 'vip_subnet_id': vip_subnet_id, - 'name': 'zaza-lb-0', - }}) - self.lb_id = res['loadbalancer']['id'] - # now wait for it to get to the active state - - @tenacity.retry(wait=tenacity.wait_fixed(1), - reraise=True, stop=tenacity.stop_after_delay(900)) - def wait_for_lb_resource(client, resource_id): - resp = client.load_balancer_show(resource_id) - logging.info(resp['provisioning_status']) - assert resp['provisioning_status'] == 'ACTIVE', ( - 'load balancer resource has not reached ' - 'expected provisioning status: {}' - .format(resp)) - return resp - - logging.info('Awaiting loadbalancer to reach provisioning_status ' - '"ACTIVE"') - resp = wait_for_lb_resource(octavia_client, self.lb_id) - logging.info(resp) - logging.info("Setup loadbalancer complete.") - - def cleanup_for_attempt_operation(self, ip): - """Remove the loadbalancer. - - :param ip: the ip of for keystone. - :type ip: str - """ - logging.info("Deleting loadbalancer {}.".format(self.lb_id)) - auth = openstack_utils.get_overcloud_auth(address=ip) - sess = openstack_utils.get_keystone_session(auth) - - octavia_client = openstack_utils.get_octavia_session_client(sess) - octavia_client.load_balancer_delete(self.lb_id) - logging.info("Deleting loadbalancer in progress ...") - - @tenacity.retry(wait=tenacity.wait_fixed(1), - reraise=True, stop=tenacity.stop_after_delay(900)) - def wait_til_deleted(client, lb_id): - lb_list = client.load_balancer_list() - ids = [lb['id'] for lb in lb_list['loadbalancers']] - assert lb_id not in ids, 'load balancer still deleting' - - wait_til_deleted(octavia_client, self.lb_id) - logging.info("Deleted loadbalancer.") - def get_client_and_attempt_operation(self, ip): - """Attempt to show the loadbalancer as a policyd override. + """Attempt to list available provider drivers. This operation should pass normally, and fail when the rule has been overriden (see the `rule` class variable. @@ -761,6 +680,6 @@ class OctaviaTests(BasePolicydSpecialization): octavia_client = openstack_utils.get_octavia_session_client( self.get_keystone_session_admin_user(ip)) try: - octavia_client.load_balancer_show(self.lb_id) + octavia_client.provider_list() except octaviaclient.OctaviaClientException: raise PolicydOperationFailedException() From a1eb37c36562766ca69ba6d320e563bc09cac35b Mon Sep 17 00:00:00 2001 From: Aurelien Lourot Date: Thu, 23 Jul 2020 09:49:48 +0200 Subject: [PATCH 58/59] Make neutron/arista tests more robust When creating an Arista-backed Neutron network, the API call from Neutron to Arista sometimes spuriously fails. This is not fatal and Neutron simply retries a few seconds later. The tests now account for that. --- .../charm_tests/neutron_arista/tests.py | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/zaza/openstack/charm_tests/neutron_arista/tests.py b/zaza/openstack/charm_tests/neutron_arista/tests.py index 3f78bf7..ba29bd6 100644 --- a/zaza/openstack/charm_tests/neutron_arista/tests.py +++ b/zaza/openstack/charm_tests/neutron_arista/tests.py @@ -39,15 +39,36 @@ class NeutronCreateAristaNetworkTest(neutron_tests.NeutronCreateNetworkTest): cls.neutron_client.list_networks() def _assert_test_network_exists_and_return_id(self): - actual_network_names = arista_utils.query_fixture_networks( - arista_utils.fixture_ip_addr()) - self.assertEqual(actual_network_names, [self._TEST_NET_NAME]) + logging.info('Checking that the test network exists on the Arista ' + 'test fixture...') + + # Sometimes the API call from Neutron to Arista fails and Neutron + # retries a couple of seconds later, which is why the newly created + # test network may not be immediately visible on Arista's API. + for attempt in tenacity.Retrying( + wait=tenacity.wait_fixed(10), # seconds + stop=tenacity.stop_after_attempt(3), + reraise=True): + with attempt: + actual_network_names = arista_utils.query_fixture_networks( + arista_utils.fixture_ip_addr()) + self.assertEqual(actual_network_names, [self._TEST_NET_NAME]) + return super(NeutronCreateAristaNetworkTest, self)._assert_test_network_exists_and_return_id() def _assert_test_network_doesnt_exist(self): - actual_network_names = arista_utils.query_fixture_networks( - arista_utils.fixture_ip_addr()) - self.assertEqual(actual_network_names, []) + logging.info("Checking that the test network doesn't exist on the " + "Arista test fixture...") + + for attempt in tenacity.Retrying( + wait=tenacity.wait_fixed(10), # seconds + stop=tenacity.stop_after_attempt(3), + reraise=True): + with attempt: + actual_network_names = arista_utils.query_fixture_networks( + arista_utils.fixture_ip_addr()) + self.assertEqual(actual_network_names, []) + super(NeutronCreateAristaNetworkTest, self)._assert_test_network_doesnt_exist() From 59d68e49a5830741a602aee0a6e7fa73d8eda776 Mon Sep 17 00:00:00 2001 From: Alex Kavanagh <567675+ajkavanagh@users.noreply.github.com> Date: Thu, 23 Jul 2020 14:07:21 +0100 Subject: [PATCH 59/59] Ensure that the tests directory exists for the local cacert (#373) A recent change to allow multiple zazas to run at the same time fixed the cacert file into a local 'tests/' directory. Unfortunately, that doesn't exist for every environment where zaza runs (e.g. mojo) and so this patch ensures that the path exists prior to trying to download into it. --- zaza/openstack/utilities/openstack.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/zaza/openstack/utilities/openstack.py b/zaza/openstack/utilities/openstack.py index 4424cea..14c708c 100644 --- a/zaza/openstack/utilities/openstack.py +++ b/zaza/openstack/utilities/openstack.py @@ -1737,6 +1737,15 @@ def get_overcloud_auth(address=None, model_name=None): } if tls_rid: unit = model.get_first_unit_name('keystone', model_name=model_name) + + # ensure that the path to put the local cacert in actually exists. The + # assumption that 'tests/' exists for, say, mojo is false. + # Needed due to: + # commit: 537473ad3addeaa3d1e4e2d0fd556aeaa4018eb2 + _dir = os.path.dirname(KEYSTONE_LOCAL_CACERT) + if not os.path.exists(_dir): + os.makedirs(_dir) + model.scp_from_unit( unit, KEYSTONE_REMOTE_CACERT,