From 1538db58ffe89c0a022c6b6c44e6f98005452fac Mon Sep 17 00:00:00 2001 From: Chris MacNaughton Date: Tue, 14 Apr 2020 12:45:37 +0200 Subject: [PATCH 001/140] Ensure that upgrading non-leader first can handle pause arguments --- zaza/openstack/utilities/series_upgrade.py | 80 +++++++++++++++++++--- 1 file changed, 69 insertions(+), 11 deletions(-) diff --git a/zaza/openstack/utilities/series_upgrade.py b/zaza/openstack/utilities/series_upgrade.py index 7f3efa3..605aca9 100644 --- a/zaza/openstack/utilities/series_upgrade.py +++ b/zaza/openstack/utilities/series_upgrade.py @@ -107,11 +107,15 @@ def run_post_upgrade_functions(post_upgrade_functions): cl_utils.get_class(func)() -def series_upgrade_non_leaders_first(application, from_series="trusty", - to_series="xenial", - origin='openstack-origin', - completed_machines=[], - post_upgrade_functions=None): +def series_upgrade_non_leaders_first( + application, from_series="trusty", + to_series="xenial", + origin='openstack-origin', + completed_machines=[], + post_upgrade_functions=None, + pause_non_leader_primary=False, + pause_non_leader_subordinate=False +): """Series upgrade non leaders first. Wrap all the functionality to handle series upgrade for charms @@ -129,6 +133,14 @@ def series_upgrade_non_leaders_first(application, from_series="trusty", :param completed_machines: List of completed machines which do no longer require series upgrade. :type completed_machines: list + :param pause_non_leader_primary: Whether the non-leader applications should + be paused + :type pause_non_leader_primary: bool + :param pause_non_leader_subordinate: Whether the non-leader subordinate + hacluster applications should be + paused + :type pause_non_leader_subordinate: bool + :param from_series: The series from which to upgrade :returns: None :rtype: None """ @@ -141,6 +153,23 @@ def series_upgrade_non_leaders_first(application, from_series="trusty", else: non_leaders.append(unit) + # Pause the non-leaders + for unit in non_leaders: + if pause_non_leader_subordinate: + if status["units"][unit].get("subordinates"): + for subordinate in status["units"][unit]["subordinates"]: + _app = subordinate.split('/')[0] + if _app in SUBORDINATE_PAUSE_RESUME_BLACKLIST: + logging.info("Skipping pausing {} - blacklisted" + .format(subordinate)) + else: + logging.info("Pausing {}".format(subordinate)) + model.run_action( + subordinate, "pause", action_params={}) + if pause_non_leader_primary: + logging.info("Pausing {}".format(unit)) + model.run_action(unit, "pause", action_params={}) + # Series upgrade the non-leaders first for unit in non_leaders: machine = status["units"][unit]["machine"] @@ -173,12 +202,16 @@ def series_upgrade_non_leaders_first(application, from_series="trusty", model.block_until_all_units_idle() -async def async_series_upgrade_non_leaders_first(application, - from_series="trusty", - to_series="xenial", - origin='openstack-origin', - completed_machines=[], - post_upgrade_functions=None): +async def async_series_upgrade_non_leaders_first( + application, + from_series="trusty", + to_series="xenial", + origin='openstack-origin', + completed_machines=[], + post_upgrade_functions=None, + pause_non_leader_primary=False, + pause_non_leader_subordinate=False +): """Series upgrade non leaders first. Wrap all the functionality to handle series upgrade for charms @@ -196,6 +229,14 @@ async def async_series_upgrade_non_leaders_first(application, :param completed_machines: List of completed machines which do no longer require series upgrade. :type completed_machines: list + :param pause_non_leader_primary: Whether the non-leader applications should + be paused + :type pause_non_leader_primary: bool + :param pause_non_leader_subordinate: Whether the non-leader subordinate + hacluster applications should be + paused + :type pause_non_leader_subordinate: bool + :param from_series: The series from which to upgrade :returns: None :rtype: None """ @@ -208,6 +249,23 @@ async def async_series_upgrade_non_leaders_first(application, else: non_leaders.append(unit) + # Pause the non-leaders + for unit in non_leaders: + if pause_non_leader_subordinate: + if status["units"][unit].get("subordinates"): + for subordinate in status["units"][unit]["subordinates"]: + _app = subordinate.split('/')[0] + if _app in SUBORDINATE_PAUSE_RESUME_BLACKLIST: + logging.info("Skipping pausing {} - blacklisted" + .format(subordinate)) + else: + logging.info("Pausing {}".format(subordinate)) + await model.async_run_action( + subordinate, "pause", action_params={}) + if pause_non_leader_primary: + logging.info("Pausing {}".format(unit)) + await model.async_run_action(unit, "pause", action_params={}) + # Series upgrade the non-leaders first for unit in non_leaders: machine = status["units"][unit]["machine"] From eb0cba9efc0ba5c665d034986423ddee13503aec Mon Sep 17 00:00:00 2001 From: James Page Date: Mon, 20 Apr 2020 18:10:03 +0100 Subject: [PATCH 002/140] Add zaza tests for TrilioVault Add setup and tests for trilio-{data-mover,dm-api,wlm} charms. Add attach_volume utility to attach cinder volumes to nova servers. --- zaza/openstack/charm_tests/trilio/__init__.py | 15 + zaza/openstack/charm_tests/trilio/setup.py | 83 ++++ zaza/openstack/charm_tests/trilio/tests.py | 379 ++++++++++++++++++ zaza/openstack/utilities/openstack.py | 40 ++ 4 files changed, 517 insertions(+) create mode 100644 zaza/openstack/charm_tests/trilio/__init__.py create mode 100644 zaza/openstack/charm_tests/trilio/setup.py create mode 100644 zaza/openstack/charm_tests/trilio/tests.py diff --git a/zaza/openstack/charm_tests/trilio/__init__.py b/zaza/openstack/charm_tests/trilio/__init__.py new file mode 100644 index 0000000..d22e570 --- /dev/null +++ b/zaza/openstack/charm_tests/trilio/__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 TrilioVault.""" diff --git a/zaza/openstack/charm_tests/trilio/setup.py b/zaza/openstack/charm_tests/trilio/setup.py new file mode 100644 index 0000000..a7ba7b3 --- /dev/null +++ b/zaza/openstack/charm_tests/trilio/setup.py @@ -0,0 +1,83 @@ +#!/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 Trilio.""" + +import logging +import os + +import zaza.model as zaza_model +import zaza.openstack.utilities.juju as juju_utils +import zaza.openstack.utilities.generic as generic_utils + + +def basic_setup(): + """Run setup for testing Trilio. + + Setup for testing Trilio is currently part of functional + tests. + """ + logging.info("Configuring NFS Server") + nfs_server_ip = zaza_model.get_app_ips("nfs-server-test-fixture")[0] + trilio_wlm_unit = zaza_model.get_first_unit_name("trilio-wlm") + + nfs_shares_conf = {"nfs-shares": "{}:/srv/testing".format(nfs_server_ip)} + _trilio_services = ["trilio-wlm", "trilio-data-mover"] + + conf_changed = False + for juju_service in _trilio_services: + app_config = zaza_model.get_application_config(juju_service) + if app_config["nfs-shares"] != nfs_shares_conf["nfs-shares"]: + zaza_model.set_application_config(juju_service, nfs_shares_conf) + conf_changed = True + + if conf_changed: + zaza_model.wait_for_agent_status() + # NOTE(jamespage): wlm-api service must be running in order + # to execute the setup actions + zaza_model.block_until_service_status( + unit_name=trilio_wlm_unit, + services=["wlm-api"], + target_status="active", + ) + + logging.info("Executing create-cloud-admin-trust") + password = juju_utils.leader_get("keystone", "admin_passwd") + + generic_utils.assertActionRanOK( + zaza_model.run_action_on_leader( + "trilio-wlm", + "create-cloud-admin-trust", + raise_on_failure=True, + action_params={"password": password}, + ) + ) + + logging.info("Executing create-license") + test_license = os.environ.get("TEST_TRILIO_LICENSE") + if test_license and os.path.exists(test_license): + zaza_model.attach_resource("trilio-wlm", + resource_name='license', + resource_path=test_license) + generic_utils.assertActionRanOK( + zaza_model.run_action_on_leader( + "trilio-wlm", "create-license", + raise_on_failure=True + ) + ) + + else: + logging.error("Unable to find Trilio License file") diff --git a/zaza/openstack/charm_tests/trilio/tests.py b/zaza/openstack/charm_tests/trilio/tests.py new file mode 100644 index 0000000..e7595a5 --- /dev/null +++ b/zaza/openstack/charm_tests/trilio/tests.py @@ -0,0 +1,379 @@ +#!/usr/bin/env python3 + +# 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. + +"""Collection of tests for vault.""" + +import logging +import tenacity + +import zaza.model as zaza_model + +import zaza.openstack.charm_tests.test_utils as test_utils +import zaza.openstack.utilities.juju as juju_utils +import zaza.openstack.utilities.openstack as openstack_utils +import zaza.openstack.charm_tests.glance.setup as glance_setup +import zaza.openstack.configure.guest as guest_utils + + +def _resource_reaches_status( + unit, auth_args, command, resource_id, target_status +): + """Wait for a workload resource to reach a status. + + :param unit: unit to run cli commands on + :type unit: zaza_model.Unit + :param auth_args: authentication arguments for command + :type auth_args: str + :param command: command to execute + :type command: str + :param resource_id: resource ID to monitor + :type resource_id: str + :param target_status: status to monitor for + :type target_status: str + """ + resource_status = ( + juju_utils.remote_run( + unit, + remote_cmd=command.format( + auth_args=auth_args, resource_id=resource_id + ), + timeout=180, + fatal=True, + ) + .strip() + .split("\n")[-1] + ) + logging.info( + "Checking resource ({}) status: {}".format( + resource_id, resource_status + ) + ) + if resource_status == target_status: + return + raise Exception("Resource not ready: {}".format(resource_status)) + + +class WorkloadmgrCLIHelper(object): + """Helper for working with workloadmgrcli.""" + + WORKLOAD_CREATE_CMD = ( + "openstack {auth_args} workload create " + "--instance instance-id={instance_id} " + "-f value -c ID" + ) + + WORKLOAD_STATUS_CMD = ( + "openstack {auth_args} workload show " + "-f value -c status " + " {resource_id} " + ) + + SNAPSHOT_CMD = ( + "openstack {auth_args} workload snapshot --full {workload_id}" + ) + + SNAPSHOT_ID_CMD = ( + "openstack {auth_args} workload snapshot list " + "--workload_id {workload_id} " + "-f value -c ID" + ) + + SNAPSHOT_STATUS_CMD = ( + "openstack {auth_args} workload snapshot show " + "-f value -c status " + "{resource_id} " + ) + + ONECLICK_RESTORE_CMD = ( + "openstack {auth_args} workload snapshot oneclick-restore " + "{snapshot_id} " + ) + + def __init__(self, keystone_client): + """Initialise helper. + + :param keystone_client: keystone client + :type keystone_client: keystoneclient.v3 + """ + self.trilio_wlm_unit = zaza_model.get_first_unit_name("trilio-wlm") + self.auth_args = self._auth_arguments(keystone_client) + + @classmethod + def _auth_arguments(cls, keystone_client): + """Generate workloadmgrcli arguments for cloud authentication. + + :returns: string of required cli arguments for authentication + :rtype: str + """ + overcloud_auth = openstack_utils.get_overcloud_auth() + overcloud_auth.update( + { + "OS_DOMAIN_ID": openstack_utils.get_domain_id( + keystone_client, domain_name="admin_domain" + ), + "OS_TENANT_ID": openstack_utils.get_project_id( + keystone_client, + project_name="admin", + domain_name="admin_domain", + ), + "OS_TENANT_NAME": "admin", + } + ) + + _required_keys = [ + "OS_AUTH_URL", + "OS_USERNAME", + "OS_PASSWORD", + "OS_REGION_NAME", + "OS_DOMAIN_ID", + "OS_TENANT_ID", + "OS_TENANT_NAME", + ] + + params = [] + for os_key in _required_keys: + params.append( + "--{}={}".format( + os_key.lower().replace("_", "-"), overcloud_auth[os_key] + ) + ) + return " ".join(params) + + def create_workload(self, instance_id): + """Create a new workload. + + :param instance_id: instance ID to create workload from + :type instance_id: str + :returns: workload ID + :rtype: str + """ + workload_id = juju_utils.remote_run( + self.trilio_wlm_unit, + remote_cmd=self.WORKLOAD_CREATE_CMD.format( + auth_args=self.auth_args, instance_id=instance_id + ), + timeout=180, + fatal=True, + ).strip() + + retryer = tenacity.Retrying( + wait=tenacity.wait_exponential(multiplier=1, max=60), + stop=tenacity.stop_after_delay(180), + reraise=True, + ) + retryer( + _resource_reaches_status, + self.trilio_wlm_unit, + self.auth_args, + self.WORKLOAD_STATUS_CMD, + workload_id, + "available", + ) + + return workload_id + + def create_snapshot(self, workload_id): + """Create a new snapshot. + + :param workload_id: workload ID to create snapshot from + :type workload_id: str + :returns: snapshot ID + :rtype: str + """ + juju_utils.remote_run( + self.trilio_wlm_unit, + remote_cmd=self.SNAPSHOT_CMD.format( + auth_args=self.auth_args, workload_id=workload_id + ), + timeout=180, + fatal=True, + ) + snapshot_id = juju_utils.remote_run( + self.trilio_wlm_unit, + remote_cmd=self.SNAPSHOT_ID_CMD.format( + auth_args=self.auth_args, workload_id=workload_id + ), + timeout=180, + fatal=True, + ).strip() + + retryer = tenacity.Retrying( + wait=tenacity.wait_exponential(multiplier=1, max=60), + stop=tenacity.stop_after_delay(720), + reraise=True, + ) + + retryer( + _resource_reaches_status, + self.trilio_wlm_unit, + self.auth_args, + self.SNAPSHOT_STATUS_CMD, + snapshot_id, + "available", + ) + + return snapshot_id + + def oneclick_restore(self, snapshot_id): + """Restore a workload from a snapshot. + + :param snapshot_id: snapshot ID to restore + :type snapshot_id: str + """ + juju_utils.remote_run( + self.trilio_wlm_unit, + remote_cmd=self.ONECLICK_RESTORE_CMD.format( + auth_args=self.auth_args, snapshot_id=snapshot_id + ), + timeout=180, + fatal=True, + ) + + # TODO validate restore but currently failing with 4.0 + # pre-release + + +class TrilioBaseTest(test_utils.OpenStackBaseTest): + """Base test class for charms.""" + + RESOURCE_PREFIX = "zaza-triliovault-tests" + conf_file = None + + @classmethod + def setUpClass(cls): + """Run class setup for running tests.""" + super().setUpClass() + cls.cinder_client = openstack_utils.get_cinder_session_client( + cls.keystone_session + ) + cls.nova_client = openstack_utils.get_nova_session_client( + cls.keystone_session + ) + cls.keystone_client = openstack_utils.get_keystone_session_client( + cls.keystone_session + ) + + def test_restart_on_config_change(self): + """Check restart happens on config change. + + Change debug mode and assert that change propagates to the correct + file and that services are restarted as a result + """ + # Expected default and alternate values + set_default = {"debug": False} + set_alternate = {"debug": True} + + # Make config change, check for service restarts + self.restart_on_changed( + self.conf_file, + set_default, + set_alternate, + {"DEFAULT": {"debug": ["False"]}}, + {"DEFAULT": {"debug": ["True"]}}, + self.services, + ) + + def test_pause_resume(self): + """Run pause and resume tests. + + Pause service and check services are stopped then resume and check + they are started + """ + with self.pause_resume(self.services, pgrep_full=False): + logging.info("Testing pause resume") + + def test_snapshot_workload(self): + """Ensure that a workload can be created and snapshot'ed.""" + # Setup volume and instance and attach one to the other + volume = openstack_utils.create_volume( + self.cinder_client, + size="1", + name="{}-100-vol".format(self.RESOURCE_PREFIX), + ) + + instance = guest_utils.launch_instance( + glance_setup.CIRROS_IMAGE_NAME, + vm_name="{}-server".format(self.RESOURCE_PREFIX), + ) + + # Trilio need direct access to ceph - OMG + openstack_utils.attach_volume(self.nova_client, volume.id, instance.id) + + workloadmgrcli = WorkloadmgrCLIHelper(self.keystone_client) + + # Create workload using instance + logging.info("Creating workload configuration") + workload_id = workloadmgrcli.create_workload(instance.id) + logging.info("Created workload: {}".format(workload_id)) + + logging.info("Initiating snapshot") + snapshot_id = workloadmgrcli.create_snapshot(workload_id) + logging.info( + "Snapshot of workload {} created: {}".format( + workload_id, snapshot_id + ) + ) + + logging.info("Deleting server and volume ready for restore") + openstack_utils.delete_resource( + self.nova_client.servers, instance.id, "deleting instance" + ) + # NOTE: Trilio leaves a snapshot in place - + # drop before volume deletion. + for volume_snapshot in self.cinder_client.volume_snapshots.list(): + openstack_utils.delete_resource( + self.cinder_client.volume_snapshots, + volume_snapshot.id, + "deleting snapshot", + ) + openstack_utils.delete_resource( + self.cinder_client.volumes, volume.id, "deleting volume" + ) + + logging.info("Initiating restore") + workloadmgrcli.oneclick_restore(snapshot_id) + + +class TrilioWLMTest(TrilioBaseTest): + """Tests for Trilio Workload Manager charm.""" + + conf_file = "/etc/workloadmgr/workloadmgr.conf" + application_name = "trilio-wlm" + + services = [ + "workloadmgr-api", + "workloadmgr-scheduler", + "workloadmgr-workloads", + "workloadmgr-cron", + ] + + +class TrilioDMAPITest(TrilioBaseTest): + """Tests for Trilio Data Mover API charm.""" + + conf_file = "/etc/dmapi/dmapi.conf" + application_name = "trilio-dm-api" + + services = ["dmapi-api"] + + +class TrilioDataMoverTest(TrilioBaseTest): + """Tests for Trilio Data Mover charm.""" + + conf_file = "/etc/tvault-contego/tvault-contego.conf" + application_name = "trilio-data-mover" + + services = ["tvault-contego"] diff --git a/zaza/openstack/utilities/openstack.py b/zaza/openstack/utilities/openstack.py index 875071b..3e72faf 100644 --- a/zaza/openstack/utilities/openstack.py +++ b/zaza/openstack/utilities/openstack.py @@ -487,6 +487,24 @@ def get_project_id(ks_client, project_name, api_version=2, domain_name=None): return None +def get_domain_id(ks_client, domain_name, api_version=2): + """Return domain ID. + + :param ks_client: Authenticated keystoneclient + :type ks_client: keystoneclient.v3.Client object + :param domain_name: Name of the domain + :type domain_name: string + :param api_version: API version number + :type api_version: int + :returns: Domain ID + :rtype: string or None + """ + all_domains = ks_client.domains.list(name=domain_name) + if all_domains: + return all_domains[0].id + return None + + # Neutron Helpers def get_gateway_uuids(): """Return machine uuids for neutron-gateway(s). @@ -2069,6 +2087,28 @@ def create_volume(cinder, size, name=None, image=None): return volume +def attach_volume(nova, volume_id, instance_id): + """Attach a cinder volume to a nova instance. + + :param nova: Authenticated nova client + :type nova: novaclient.v2.client.Client + :param volume_id: the id of the volume to attach + :type volume_id: str + :param instance_id: the id of the instance to attach the volume to + :type instance_id: str + :returns: nova volume pointer + :rtype: novaclient.v2.volumes.Volume + """ + logging.info( + 'Attaching volume {} to instance {}'.format( + volume_id, instance_id + ) + ) + return nova.volumes.create_server_volume(server_id=instance_id, + volume_id=volume_id, + device='/dev/vdx') + + def create_volume_backup(cinder, volume_id, name=None): """Create cinder volume backup. From 740bc2060f4e1d267aaa5c265b68b3e46221ba38 Mon Sep 17 00:00:00 2001 From: Dmitrii Shcherbakov Date: Mon, 4 May 2020 20:45:55 +0300 Subject: [PATCH 003/140] Pass arguments BaseCharmTest.setUpClass Not doing so triggers an incorrect behavior leading to functional test failures as the application name is not set correctly. https://github.com/openstack-charmers/zaza-openstack-tests/issues/256 https://review.opendev.org/#/c/712980/1 --- unit_tests/charm_tests/test_utils.py | 32 ++++++++++++++++++++++++ zaza/openstack/charm_tests/test_utils.py | 2 +- 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 unit_tests/charm_tests/test_utils.py diff --git a/unit_tests/charm_tests/test_utils.py b/unit_tests/charm_tests/test_utils.py new file mode 100644 index 0000000..280e2e2 --- /dev/null +++ b/unit_tests/charm_tests/test_utils.py @@ -0,0 +1,32 @@ +# 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. + +import unittest +import zaza.openstack.charm_tests.test_utils as test_utils + +from unittest.mock import patch + + +class TestOpenStackBaseTest(unittest.TestCase): + + @patch.object(test_utils.openstack_utils, 'get_cacert') + @patch.object(test_utils.openstack_utils, 'get_overcloud_keystone_session') + @patch.object(test_utils.BaseCharmTest, 'setUpClass') + def test_setUpClass(self, _setUpClass, _get_ovcks, _get_cacert): + + class MyTestClass(test_utils.OpenStackBaseTest): + model_name = 'deadbeef' + + MyTestClass.setUpClass('foo', 'bar') + _setUpClass.assert_called_with('foo', 'bar') diff --git a/zaza/openstack/charm_tests/test_utils.py b/zaza/openstack/charm_tests/test_utils.py index ae431c1..bcb5aff 100644 --- a/zaza/openstack/charm_tests/test_utils.py +++ b/zaza/openstack/charm_tests/test_utils.py @@ -427,7 +427,7 @@ class OpenStackBaseTest(BaseCharmTest): @classmethod def setUpClass(cls, application_name=None, model_alias=None): """Run setup for test class to create common resources.""" - super(OpenStackBaseTest, cls).setUpClass() + super(OpenStackBaseTest, cls).setUpClass(application_name, model_alias) cls.keystone_session = openstack_utils.get_overcloud_keystone_session( model_name=cls.model_name) cls.cacert = openstack_utils.get_cacert() From db0fff648040f4f08ba6a76f039f968c29302e73 Mon Sep 17 00:00:00 2001 From: James Page Date: Tue, 5 May 2020 12:28:09 +0100 Subject: [PATCH 004/140] triliovault: Check restore completes Ensure that the oneclick restore process completes as part of the snapshot test case. Drop use of api_version in get_domain_id utility. --- zaza/openstack/charm_tests/trilio/tests.py | 56 ++++++++++++++++++---- zaza/openstack/utilities/openstack.py | 4 +- 2 files changed, 49 insertions(+), 11 deletions(-) diff --git a/zaza/openstack/charm_tests/trilio/tests.py b/zaza/openstack/charm_tests/trilio/tests.py index e7595a5..99ef3e0 100644 --- a/zaza/openstack/charm_tests/trilio/tests.py +++ b/zaza/openstack/charm_tests/trilio/tests.py @@ -102,13 +102,26 @@ class WorkloadmgrCLIHelper(object): "{snapshot_id} " ) + RESTORE_LIST_CMD = ( + "openstack {auth_args} workloadmgr restore list " + "--snapshot_id {snapshot_id} " + "-f value -c ID" + ) + + RESTORE_STATUS_CMD = ( + "openstack {auth_args} workloadmgr restore show " + "-f value -c status {resource_id}" + ) + def __init__(self, keystone_client): """Initialise helper. :param keystone_client: keystone client :type keystone_client: keystoneclient.v3 """ - self.trilio_wlm_unit = zaza_model.get_first_unit_name("trilio-wlm") + self.trilio_wlm_unit = zaza_model.get_first_unit_name( + "trilio-wlm" + ) self.auth_args = self._auth_arguments(keystone_client) @classmethod @@ -147,7 +160,8 @@ class WorkloadmgrCLIHelper(object): for os_key in _required_keys: params.append( "--{}={}".format( - os_key.lower().replace("_", "-"), overcloud_auth[os_key] + os_key.lower().replace("_", "-"), + overcloud_auth[os_key], ) ) return " ".join(params) @@ -170,7 +184,7 @@ class WorkloadmgrCLIHelper(object): ).strip() retryer = tenacity.Retrying( - wait=tenacity.wait_exponential(multiplier=1, max=60), + wait=tenacity.wait_exponential(multiplier=1, max=30), stop=tenacity.stop_after_delay(180), reraise=True, ) @@ -211,7 +225,7 @@ class WorkloadmgrCLIHelper(object): ).strip() retryer = tenacity.Retrying( - wait=tenacity.wait_exponential(multiplier=1, max=60), + wait=tenacity.wait_exponential(multiplier=1, max=30), stop=tenacity.stop_after_delay(720), reraise=True, ) @@ -241,9 +255,31 @@ class WorkloadmgrCLIHelper(object): timeout=180, fatal=True, ) + restore_id = juju_utils.remote_run( + self.trilio_wlm_unit, + remote_cmd=self.RESTORE_LIST_CMD.format( + auth_args=self.auth_args, snapshot_id=snapshot_id + ), + timeout=180, + fatal=True, + ).strip() - # TODO validate restore but currently failing with 4.0 - # pre-release + retryer = tenacity.Retrying( + wait=tenacity.wait_exponential(multiplier=1, max=30), + stop=tenacity.stop_after_delay(720), + reraise=True, + ) + + retryer( + _resource_reaches_status, + self.trilio_wlm_unit, + self.auth_args, + self.RESTORE_STATUS_CMD, + restore_id, + "available", + ) + + return restore_id class TrilioBaseTest(test_utils.OpenStackBaseTest): @@ -310,7 +346,9 @@ class TrilioBaseTest(test_utils.OpenStackBaseTest): ) # Trilio need direct access to ceph - OMG - openstack_utils.attach_volume(self.nova_client, volume.id, instance.id) + openstack_utils.attach_volume( + self.nova_client, volume.id, instance.id + ) workloadmgrcli = WorkloadmgrCLIHelper(self.keystone_client) @@ -333,7 +371,9 @@ class TrilioBaseTest(test_utils.OpenStackBaseTest): ) # NOTE: Trilio leaves a snapshot in place - # drop before volume deletion. - for volume_snapshot in self.cinder_client.volume_snapshots.list(): + for ( + volume_snapshot + ) in self.cinder_client.volume_snapshots.list(): openstack_utils.delete_resource( self.cinder_client.volume_snapshots, volume_snapshot.id, diff --git a/zaza/openstack/utilities/openstack.py b/zaza/openstack/utilities/openstack.py index 3e72faf..55f8d1b 100644 --- a/zaza/openstack/utilities/openstack.py +++ b/zaza/openstack/utilities/openstack.py @@ -487,15 +487,13 @@ def get_project_id(ks_client, project_name, api_version=2, domain_name=None): return None -def get_domain_id(ks_client, domain_name, api_version=2): +def get_domain_id(ks_client, domain_name): """Return domain ID. :param ks_client: Authenticated keystoneclient :type ks_client: keystoneclient.v3.Client object :param domain_name: Name of the domain :type domain_name: string - :param api_version: API version number - :type api_version: int :returns: Domain ID :rtype: string or None """ From c35b4e0fcf185339b17c103031eb54d660e31a64 Mon Sep 17 00:00:00 2001 From: Aurelien Lourot Date: Tue, 5 May 2020 16:22:52 +0200 Subject: [PATCH 005/140] Disable test_101_neutron_sriov_config on Trusty https://bugs.launchpad.net/charm-neutron-openvswitch/+bug/1876888 --- zaza/openstack/charm_tests/neutron/tests.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zaza/openstack/charm_tests/neutron/tests.py b/zaza/openstack/charm_tests/neutron/tests.py index d94b47f..e40831c 100644 --- a/zaza/openstack/charm_tests/neutron/tests.py +++ b/zaza/openstack/charm_tests/neutron/tests.py @@ -438,10 +438,10 @@ class NeutronOpenvSwitchTest(NeutronPluginApiSharedTests): def test_101_neutron_sriov_config(self): """Verify data in the sriov agent config file.""" - trusty_kilo = openstack_utils.get_os_release('trusty_kilo') - if self.current_os_release < trusty_kilo: + xenial_mitaka = openstack_utils.get_os_release('xenial_mitaka') + if self.current_os_release < xenial_mitaka: logging.debug('Skipping test, sriov agent not supported on < ' - 'trusty/kilo') + 'xenial/mitaka') return zaza.model.set_application_config( From c04e632ae092dd91dcbb2f316b84c3c95ad75df9 Mon Sep 17 00:00:00 2001 From: Alex Kavanagh Date: Wed, 6 May 2020 14:59:32 +0100 Subject: [PATCH 006/140] The octavia tests require multiple LTS images This patch ensures that bionic and focal images are available for the LTS octavia tests. --- zaza/openstack/charm_tests/octavia/setup.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/zaza/openstack/charm_tests/octavia/setup.py b/zaza/openstack/charm_tests/octavia/setup.py index 4f93097..55f8903 100644 --- a/zaza/openstack/charm_tests/octavia/setup.py +++ b/zaza/openstack/charm_tests/octavia/setup.py @@ -26,6 +26,12 @@ import zaza.openstack.utilities.openstack as openstack import zaza.openstack.configure.guest +def ensure_lts_images(): + """Ensure that bionic and focal images are available for the tests.""" + glance_setup.add_lts_image(image_name='bionic', release='bionic') + glance_setup.add_lts_image(image_name='focal', release='focal') + + def add_amphora_image(image_url=None): """Add Octavia ``amphora`` test image to glance. From 59ec82f542f68d4ffc4b02742dc865a4fcf99cf1 Mon Sep 17 00:00:00 2001 From: Aurelien Lourot Date: Thu, 7 May 2020 12:53:19 +0200 Subject: [PATCH 007/140] Make ssh_test() more robust --- .../test_zaza_utilities_openstack.py | 3 ++- zaza/openstack/charm_tests/barbican/tests.py | 2 +- zaza/openstack/charm_tests/neutron/tests.py | 1 - zaza/openstack/utilities/openstack.py | 19 ++++++++++++++++--- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/unit_tests/utilities/test_zaza_utilities_openstack.py b/unit_tests/utilities/test_zaza_utilities_openstack.py index 46efb0d..5c344bf 100644 --- a/unit_tests/utilities/test_zaza_utilities_openstack.py +++ b/unit_tests/utilities/test_zaza_utilities_openstack.py @@ -738,7 +738,8 @@ class TestOpenStackUtils(ut_utils.BaseTestCase): 'bob', '10.0.0.10', 'myvm', - password='reallyhardpassord') + password='reallyhardpassord', + retry=False) paramiko_mock.connect.assert_called_once_with( '10.0.0.10', password='reallyhardpassord', diff --git a/zaza/openstack/charm_tests/barbican/tests.py b/zaza/openstack/charm_tests/barbican/tests.py index 3d54917..a11ff1f 100644 --- a/zaza/openstack/charm_tests/barbican/tests.py +++ b/zaza/openstack/charm_tests/barbican/tests.py @@ -22,7 +22,7 @@ import zaza.openstack.utilities.openstack as openstack_utils class BarbicanTest(test_utils.OpenStackBaseTest): - """Run nova-compute specific tests.""" + """Run barbican specific tests.""" _SERVICES = ['apache2', 'barbican-worker'] diff --git a/zaza/openstack/charm_tests/neutron/tests.py b/zaza/openstack/charm_tests/neutron/tests.py index e40831c..1f07ca9 100644 --- a/zaza/openstack/charm_tests/neutron/tests.py +++ b/zaza/openstack/charm_tests/neutron/tests.py @@ -712,7 +712,6 @@ class NeutronNetworkingTest(unittest.TestCase): openstack_utils.ssh_command( username, address, 'instance', 'ping -c 1 192.168.0.1', password=password, privkey=privkey, verify=verify) - pass def floating_ips_from_instance(instance): diff --git a/zaza/openstack/utilities/openstack.py b/zaza/openstack/utilities/openstack.py index 55f8d1b..98bb838 100644 --- a/zaza/openstack/utilities/openstack.py +++ b/zaza/openstack/utilities/openstack.py @@ -2300,7 +2300,7 @@ def ping_response(ip): check=True) -def ssh_test(username, ip, vm_name, password=None, privkey=None): +def ssh_test(username, ip, vm_name, password=None, privkey=None, retry=True): """SSH to given ip using supplied credentials. :param username: Username to connect with @@ -2315,6 +2315,9 @@ def ssh_test(username, ip, vm_name, password=None, privkey=None): :param privkey: Private key to authenticate with. If a password is supplied it is used rather than the private key. :type privkey: str + :param retry: If True, retry a few times if an exception is raised in the + process, e.g. on connection failure. + :type retry: boolean :raises: exceptions.SSHFailed """ def verify(stdin, stdout, stderr): @@ -2328,8 +2331,18 @@ def ssh_test(username, ip, vm_name, password=None, privkey=None): vm_name)) raise exceptions.SSHFailed() - ssh_command(username, ip, vm_name, 'uname -n', - password=password, privkey=privkey, verify=verify) + # NOTE(lourot): paramiko.SSHClient().connect() calls read_all() which can + # raise an EOFError, see + # * https://docs.paramiko.org/en/stable/api/packet.html + # * https://github.com/paramiko/paramiko/issues/925 + # So retrying a few times makes sense. + for attempt in tenacity.Retrying( + stop=tenacity.stop_after_attempt(3 if retry else 1), + wait=tenacity.wait_exponential(multiplier=1, min=2, max=10), + reraise=True): + with attempt: + ssh_command(username, ip, vm_name, 'uname -n', + password=password, privkey=privkey, verify=verify) def ssh_command(username, From c8ea324ccb3ff57e84ada7c07d054aaa4df8918b Mon Sep 17 00:00:00 2001 From: Alex Kavanagh Date: Thu, 7 May 2020 20:09:02 +0100 Subject: [PATCH 008/140] Add focal template to image list --- zaza/openstack/utilities/openstack.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/zaza/openstack/utilities/openstack.py b/zaza/openstack/utilities/openstack.py index 875071b..dc50a68 100644 --- a/zaza/openstack/utilities/openstack.py +++ b/zaza/openstack/utilities/openstack.py @@ -72,7 +72,9 @@ CIRROS_RELEASE_URL = 'http://download.cirros-cloud.net/version/released' CIRROS_IMAGE_URL = 'http://download.cirros-cloud.net' UBUNTU_IMAGE_URLS = { 'bionic': ('http://cloud-images.ubuntu.com/{release}/current/' - '{release}-server-cloudimg-{arch}.img') + '{release}-server-cloudimg-{arch}.img'), + 'focal': ('http://cloud-images.ubuntu.com/{release}/current/' + '{release}-server-cloudimg-{arch}.img'), } CHARM_TYPES = { From 7783e38f668758101374d3151a762cfcbd8e3cb8 Mon Sep 17 00:00:00 2001 From: David Ames Date: Thu, 7 May 2020 22:17:31 +0000 Subject: [PATCH 009/140] Handle race in policyd tests --- zaza/openstack/charm_tests/policyd/tests.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zaza/openstack/charm_tests/policyd/tests.py b/zaza/openstack/charm_tests/policyd/tests.py index 2845781..88d6699 100644 --- a/zaza/openstack/charm_tests/policyd/tests.py +++ b/zaza/openstack/charm_tests/policyd/tests.py @@ -430,6 +430,8 @@ class BasePolicydSpecialization(PolicydTest, # now do the policyd override. logging.info("Doing policyd override with: {}".format(self._rule)) self._set_policy_with(self._rule) + zaza_model.block_until_wl_status_info_starts_with( + self.application_name, "PO:") zaza_model.block_until_all_units_idle() # now make sure the operation fails From 8edff98f94ea39b901caf02982a705f32cc7ab36 Mon Sep 17 00:00:00 2001 From: Frode Nordahl Date: Fri, 8 May 2020 08:28:24 +0200 Subject: [PATCH 010/140] Add OVN charm tests --- zaza/openstack/charm_tests/ovn/__init__.py | 15 +++++ zaza/openstack/charm_tests/ovn/tests.py | 73 ++++++++++++++++++++++ zaza/openstack/utilities/openstack.py | 5 ++ zaza/openstack/utilities/os_versions.py | 4 ++ 4 files changed, 97 insertions(+) create mode 100644 zaza/openstack/charm_tests/ovn/__init__.py create mode 100644 zaza/openstack/charm_tests/ovn/tests.py diff --git a/zaza/openstack/charm_tests/ovn/__init__.py b/zaza/openstack/charm_tests/ovn/__init__.py new file mode 100644 index 0000000..bd5900c --- /dev/null +++ b/zaza/openstack/charm_tests/ovn/__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 OVN.""" diff --git a/zaza/openstack/charm_tests/ovn/tests.py b/zaza/openstack/charm_tests/ovn/tests.py new file mode 100644 index 0000000..0708c67 --- /dev/null +++ b/zaza/openstack/charm_tests/ovn/tests.py @@ -0,0 +1,73 @@ +# 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. + +"""Encapsulate OVN testing.""" + +import logging + +import zaza.openstack.charm_tests.test_utils as test_utils +import zaza.openstack.utilities.openstack as openstack_utils + + +class BaseCharmOperationTest(test_utils.BaseCharmTest): + """Base OVN Charm operation tests.""" + + # override if not possible to determine release pair from charm under test + release_application = None + + @classmethod + def setUpClass(cls): + """Run class setup for OVN charm operation tests.""" + super(BaseCharmOperationTest, cls).setUpClass() + cls.services = ['NotImplemented'] # This must be overridden + cls.current_release = openstack_utils.get_os_release( + openstack_utils.get_current_os_release_pair( + cls.release_application or cls.application_name)) + + def test_pause_resume(self): + """Run pause and resume tests. + + Pause service and check services are stopped, then resume and check + they are started. + """ + with self.pause_resume(self.services): + logging.info('Testing pause resume (services="{}")' + .format(self.services)) + + +class CentralCharmOperationTest(BaseCharmOperationTest): + """OVN Central Charm operation tests.""" + + @classmethod + def setUpClass(cls): + """Run class setup for OVN Central charm operation tests.""" + super(CentralCharmOperationTest, cls).setUpClass() + cls.services = [ + 'ovn-northd', + 'ovsdb-server', + ] + + +class ChassisCharmOperationTest(BaseCharmOperationTest): + """OVN Chassis Charm operation tests.""" + + release_application = 'ovn-central' + + @classmethod + def setUpClass(cls): + """Run class setup for OVN Chassis charm operation tests.""" + super(ChassisCharmOperationTest, cls).setUpClass() + cls.services = [ + 'ovn-controller', + ] diff --git a/zaza/openstack/utilities/openstack.py b/zaza/openstack/utilities/openstack.py index 98bb838..5c7d46a 100644 --- a/zaza/openstack/utilities/openstack.py +++ b/zaza/openstack/utilities/openstack.py @@ -108,6 +108,10 @@ CHARM_TYPES = { 'pkg': 'designate-common', 'origin_setting': 'openstack-origin' }, + 'ovn-central': { + 'pkg': 'ovn-common', + 'origin_setting': 'source' + }, } # Older tests use the order the services appear in the list to imply @@ -126,6 +130,7 @@ UPGRADE_SERVICES = [ {'name': 'nova-compute', 'type': CHARM_TYPES['nova']}, {'name': 'openstack-dashboard', 'type': CHARM_TYPES['openstack-dashboard']}, + {'name': 'ovn-central', 'type': CHARM_TYPES['ovn-central']}, ] diff --git a/zaza/openstack/utilities/os_versions.py b/zaza/openstack/utilities/os_versions.py index 4ffd445..b2e430e 100644 --- a/zaza/openstack/utilities/os_versions.py +++ b/zaza/openstack/utilities/os_versions.py @@ -230,4 +230,8 @@ PACKAGE_CODENAMES = { ('9', 'train'), ('10', 'ussuri'), ]), + 'ovn-common': OrderedDict([ + ('2', 'train'), + ('20', 'ussuri'), + ]), } From 37297aff8f2eda9f496eb687f28ce20f7fd8bc76 Mon Sep 17 00:00:00 2001 From: David Ames Date: Fri, 8 May 2020 21:53:01 +0000 Subject: [PATCH 011/140] Reduce the number of permutations of msg checks Reduce the number of checks by 30%. Remove duplicate test 410. Use logging.info to get output. --- .../charm_tests/rabbitmq_server/tests.py | 94 ++++++++----------- 1 file changed, 37 insertions(+), 57 deletions(-) diff --git a/zaza/openstack/charm_tests/rabbitmq_server/tests.py b/zaza/openstack/charm_tests/rabbitmq_server/tests.py index b114314..43e9bc8 100644 --- a/zaza/openstack/charm_tests/rabbitmq_server/tests.py +++ b/zaza/openstack/charm_tests/rabbitmq_server/tests.py @@ -87,6 +87,9 @@ class RmqTests(test_utils.OpenStackBaseTest): for check_unit in units: check_unit_name = check_unit.entity_id + if dest_unit_name == check_unit_name: + logging.info("Skipping check for this unit to itself.") + continue check_unit_host = check_unit.public_address check_unit_host_name = host_names[check_unit_name] @@ -95,20 +98,20 @@ class RmqTests(test_utils.OpenStackBaseTest): dest_unit_host, amqp_msg_stamp)).upper() # Publish amqp message - logging.debug('Publish message to: {} ' - '({} {})'.format(dest_unit_host, - dest_unit_name, - dest_unit_host_name)) + logging.info('Publish message to: {} ' + '({} {})'.format(dest_unit_host, + dest_unit_name, + dest_unit_host_name)) rmq_utils.publish_amqp_message_by_unit(dest_unit, amqp_msg, ssl=ssl, port=port) # Get amqp message - logging.debug('Get message from: {} ' - '({} {})'.format(check_unit_host, - check_unit_name, - check_unit_host_name)) + logging.info('Get message from: {} ' + '({} {})'.format(check_unit_host, + check_unit_name, + check_unit_host_name)) amqp_msg_rcvd = self._retry_get_amqp_message(check_unit, ssl=ssl, @@ -116,8 +119,8 @@ class RmqTests(test_utils.OpenStackBaseTest): # Validate amqp message content if amqp_msg == amqp_msg_rcvd: - logging.debug('Message {} received ' - 'OK.'.format(amqp_msg_counter)) + logging.info('Message {} received ' + 'OK.'.format(amqp_msg_counter)) else: logging.error('Expected: {}'.format(amqp_msg)) logging.error('Actual: {}'.format(amqp_msg_rcvd)) @@ -131,8 +134,8 @@ class RmqTests(test_utils.OpenStackBaseTest): def test_400_rmq_cluster_running_nodes(self): """Verify cluster status shows every cluster node as running member.""" - logging.debug('Checking that all units are in cluster_status ' - 'running nodes...') + logging.info('Checking that all units are in cluster_status ' + 'running nodes...') units = zaza.model.get_units(self.application_name) @@ -148,8 +151,8 @@ class RmqTests(test_utils.OpenStackBaseTest): unit for messages. Uses Standard amqp tcp port, no ssl. """ - logging.debug('Checking amqp message publish/get on all units ' - '(ssl off)...') + logging.info('Checking amqp message publish/get on all units ' + '(ssl off)...') units = zaza.model.get_units(self.application_name) self._test_rmq_amqp_messages_all_units(units, ssl=False) @@ -170,36 +173,13 @@ class RmqTests(test_utils.OpenStackBaseTest): logging.info('Skipping SSL tests due to client' ' compatibility issues') return - logging.debug('Checking amqp message publish/get on all units ' - '(ssl on)...') + logging.info('Checking amqp message publish/get on all units ' + '(ssl on)...') self._test_rmq_amqp_messages_all_units(units, ssl=True, port=5671) logging.info('OK') - def test_410_rmq_amqp_messages_all_units_ssl_alt_port(self): - """Send (and check) amqp messages to every rmq unit (alt ssl port). - - Send amqp messages with ssl on, to every rmq unit and check - every rmq unit for messages. Custom ssl tcp port. - - """ - units = zaza.model.get_units(self.application_name) - - # http://pad.lv/1625044 - if CompareHostReleases(get_series(units[0])) <= 'trusty': - logging.info('SKIP') - logging.info('Skipping SSL tests due to client' - ' compatibility issues') - return - logging.debug('Checking amqp message publish/get on all units ' - '(ssl on)...') - - units = zaza.model.get_units(self.application_name) - self._test_rmq_amqp_messages_all_units(units, - ssl=True, port=5999) - logging.info('OK') - @tenacity.retry( retry=tenacity.retry_if_result(lambda ret: ret is not None), wait=tenacity.wait_fixed(30), @@ -211,14 +191,14 @@ class RmqTests(test_utils.OpenStackBaseTest): def test_412_rmq_management_plugin(self): """Enable and check management plugin.""" - logging.debug('Checking tcp socket connect to management plugin ' - 'port on all rmq units...') + logging.info('Checking tcp socket connect to management plugin ' + 'port on all rmq units...') units = zaza.model.get_units(self.application_name) mgmt_port = 15672 # Enable management plugin - logging.debug('Enabling management_plugin charm config option...') + logging.info('Enabling management_plugin charm config option...') config = {'management_plugin': 'True'} zaza.model.set_application_config('rabbitmq-server', config) rmq_utils.wait_for_cluster() @@ -227,10 +207,10 @@ class RmqTests(test_utils.OpenStackBaseTest): ret = self._retry_port_knock_units(units, mgmt_port) self.assertIsNone(ret, msg=ret) - logging.debug('Connect to all units (OK)') + logging.info('Connect to all units (OK)') # Disable management plugin - logging.debug('Disabling management_plugin charm config option...') + logging.info('Disabling management_plugin charm config option...') config = {'management_plugin': 'False'} zaza.model.set_application_config('rabbitmq-server', config) rmq_utils.wait_for_cluster() @@ -259,21 +239,21 @@ class RmqTests(test_utils.OpenStackBaseTest): host_names = generic_utils.get_unit_hostnames(units) # check_rabbitmq monitor - logging.debug('Checking nrpe check_rabbitmq on units...') + logging.info('Checking nrpe check_rabbitmq on units...') cmds = ['egrep -oh /usr/local.* /etc/nagios/nrpe.d/' 'check_rabbitmq.cfg'] ret = self._retry_check_commands_on_units(cmds, units) self.assertIsNone(ret, msg=ret) # check_rabbitmq_queue monitor - logging.debug('Checking nrpe check_rabbitmq_queue on units...') + logging.info('Checking nrpe check_rabbitmq_queue on units...') cmds = ['egrep -oh /usr/local.* /etc/nagios/nrpe.d/' 'check_rabbitmq_queue.cfg'] ret = self._retry_check_commands_on_units(cmds, units) self.assertIsNone(ret, msg=ret) # check dat file existence - logging.debug('Checking nrpe dat file existence on units...') + logging.info('Checking nrpe dat file existence on units...') for u in units: unit_host_name = host_names[u.entity_id] @@ -291,7 +271,7 @@ class RmqTests(test_utils.OpenStackBaseTest): def test_910_pause_and_resume(self): """The services can be paused and resumed.""" - logging.debug('Checking pause and resume actions...') + logging.info('Checking pause and resume actions...') unit = zaza.model.get_units(self.application_name)[0] assert unit.workload_status == "active" @@ -307,21 +287,21 @@ class RmqTests(test_utils.OpenStackBaseTest): assert unit.workload_status == "active" rmq_utils.wait_for_cluster() - logging.debug('OK') + logging.info('OK') def test_911_cluster_status(self): """Test rabbitmqctl cluster_status action can be returned.""" - logging.debug('Checking cluster status action...') + logging.info('Checking cluster status action...') unit = zaza.model.get_units(self.application_name)[0] action = zaza.model.run_action(unit.entity_id, "cluster-status") self.assertIsInstance(action, juju.action.Action) - logging.debug('OK') + logging.info('OK') def test_912_check_queues(self): """Test rabbitmqctl check_queues action can be returned.""" - logging.debug('Checking cluster status action...') + logging.info('Checking cluster status action...') unit = zaza.model.get_units(self.application_name)[0] action = zaza.model.run_action(unit.entity_id, "check-queues") @@ -329,7 +309,7 @@ class RmqTests(test_utils.OpenStackBaseTest): def test_913_list_unconsumed_queues(self): """Test rabbitmqctl list-unconsumed-queues action can be returned.""" - logging.debug('Checking list-unconsumed-queues action...') + logging.info('Checking list-unconsumed-queues action...') unit = zaza.model.get_units(self.application_name)[0] self._test_rmq_amqp_messages_all_units([unit]) @@ -354,7 +334,7 @@ class RmqTests(test_utils.OpenStackBaseTest): # should have already been consumed. assert queue_data['messages'] == 0, 'Found unexpected message count.' - logging.debug('OK') + logging.info('OK') @tenacity.retry( retry=tenacity.retry_if_result(lambda errors: bool(errors)), @@ -370,8 +350,8 @@ class RmqTests(test_utils.OpenStackBaseTest): RabbitMQ cluster on removal """ - logging.debug('Checking that units correctly clean up after ' - 'themselves on unit removal...') + logging.info('Checking that units correctly clean up after ' + 'themselves on unit removal...') config = {'min-cluster-size': '2'} zaza.model.set_application_config('rabbitmq-server', config) rmq_utils.wait_for_cluster() @@ -397,4 +377,4 @@ class RmqTests(test_utils.OpenStackBaseTest): errors.append(e) self.assertFalse(errors, msg=errors) - logging.debug('OK') + logging.info('OK') From a8a25b213476b89df9449aca7ba4fcbe4f85b876 Mon Sep 17 00:00:00 2001 From: Chris MacNaughton Date: Mon, 11 May 2020 10:02:42 +0200 Subject: [PATCH 012/140] Handle leader prepare step before non-leaders --- zaza/openstack/utilities/parallel_series_upgrade.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zaza/openstack/utilities/parallel_series_upgrade.py b/zaza/openstack/utilities/parallel_series_upgrade.py index ba8b205..aa6ab0e 100755 --- a/zaza/openstack/utilities/parallel_series_upgrade.py +++ b/zaza/openstack/utilities/parallel_series_upgrade.py @@ -212,11 +212,11 @@ async def parallel_series_upgrade( for unit in status["units"] ] await asyncio.gather(*app_idle) + await prepare_series_upgrade(leader_machine, to_series=to_series) prepare_group = [ prepare_series_upgrade(machine, to_series=to_series) for machine in machines] await asyncio.gather(*prepare_group) - await prepare_series_upgrade(leader_machine, to_series=to_series) if leader_machine not in completed_machines: machines.append(leader_machine) upgrade_group = [ From 9ae0d464b3f6e6d73e8214efc171912e41e57b5e Mon Sep 17 00:00:00 2001 From: Aurelien Lourot Date: Mon, 11 May 2020 14:54:27 +0200 Subject: [PATCH 013/140] Fix "'NeutronGatewayTest' object has no attribute 'neutron_client'" in test_401_enable_qos --- zaza/openstack/charm_tests/neutron/tests.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/zaza/openstack/charm_tests/neutron/tests.py b/zaza/openstack/charm_tests/neutron/tests.py index 1f07ca9..6e2759b 100644 --- a/zaza/openstack/charm_tests/neutron/tests.py +++ b/zaza/openstack/charm_tests/neutron/tests.py @@ -113,6 +113,10 @@ class NeutronGatewayTest(NeutronPluginApiSharedTests): super(NeutronGatewayTest, cls).setUpClass(cls) cls.services = cls._get_services() + # set up clients + cls.neutron_client = ( + openstack_utils.get_neutron_session_client(cls.keystone_session)) + _APP_NAME = 'neutron-gateway' def test_401_enable_qos(self): @@ -128,7 +132,8 @@ class NeutronGatewayTest(NeutronPluginApiSharedTests): self._validate_openvswitch_agent_qos() - @tenacity.retry(wait=tenacity.wait_exponential(min=5, max=60)) + @tenacity.retry(wait=tenacity.wait_exponential(min=5, max=60), + reraise=True, stop=tenacity.stop_after_attempt(8)) def _validate_openvswitch_agent_qos(self): """Validate that the qos extension is enabled in the ovs agent.""" # obtain the dhcp agent to identify the neutron-gateway host From 695fbd85dacdf1bcb2fcb7d58dedb7295ddab521 Mon Sep 17 00:00:00 2001 From: Chris MacNaughton Date: Tue, 12 May 2020 14:12:35 +0200 Subject: [PATCH 014/140] Ensure the secondary-first function takes the same arguments --- zaza/openstack/utilities/series_upgrade.py | 24 ++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/zaza/openstack/utilities/series_upgrade.py b/zaza/openstack/utilities/series_upgrade.py index 98626b7..bff8afc 100644 --- a/zaza/openstack/utilities/series_upgrade.py +++ b/zaza/openstack/utilities/series_upgrade.py @@ -112,9 +112,11 @@ def series_upgrade_non_leaders_first( to_series="xenial", origin='openstack-origin', completed_machines=[], - post_upgrade_functions=None, pause_non_leader_primary=False, - pause_non_leader_subordinate=False + pause_non_leader_subordinate=False, + files=None, + workaround_script=None, + post_upgrade_functions=None ): """Series upgrade non leaders first. @@ -141,6 +143,10 @@ def series_upgrade_non_leaders_first( paused :type pause_non_leader_subordinate: bool :param from_series: The series from which to upgrade + :param files: Workaround files to scp to unit under upgrade + :type files: list + :param workaround_script: Workaround script to run during series upgrade + :type workaround_script: str :returns: None :rtype: None """ @@ -194,6 +200,8 @@ def series_upgrade_non_leaders_first( series_upgrade(leader, machine, from_series=from_series, to_series=to_series, origin=origin, + workaround_script=workaround_script, + files=files, post_upgrade_functions=post_upgrade_functions) completed_machines.append(machine) else: @@ -208,9 +216,11 @@ async def async_series_upgrade_non_leaders_first( to_series="xenial", origin='openstack-origin', completed_machines=[], - post_upgrade_functions=None, pause_non_leader_primary=False, - pause_non_leader_subordinate=False + pause_non_leader_subordinate=False, + files=None, + workaround_script=None, + post_upgrade_functions=None ): """Series upgrade non leaders first. @@ -237,6 +247,10 @@ async def async_series_upgrade_non_leaders_first( paused :type pause_non_leader_subordinate: bool :param from_series: The series from which to upgrade + :param files: Workaround files to scp to unit under upgrade + :type files: list + :param workaround_script: Workaround script to run during series upgrade + :type workaround_script: str :returns: None :rtype: None """ @@ -292,6 +306,8 @@ async def async_series_upgrade_non_leaders_first( leader, machine, from_series=from_series, to_series=to_series, origin=origin, + workaround_script=workaround_script, + files=files, post_upgrade_functions=post_upgrade_functions) completed_machines.append(machine) else: From 19fd66a17829f679fb95e85ad02c62e606d77fb0 Mon Sep 17 00:00:00 2001 From: Chris MacNaughton Date: Tue, 12 May 2020 14:22:16 +0200 Subject: [PATCH 015/140] Fix new lint errors --- zaza/openstack/charm_tests/manila_ganesha/tests.py | 3 +-- zaza/openstack/utilities/series_upgrade.py | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/zaza/openstack/charm_tests/manila_ganesha/tests.py b/zaza/openstack/charm_tests/manila_ganesha/tests.py index 44ed2cd..e24a9f3 100644 --- a/zaza/openstack/charm_tests/manila_ganesha/tests.py +++ b/zaza/openstack/charm_tests/manila_ganesha/tests.py @@ -125,6 +125,5 @@ packages: openstack_utils.ssh_command( username, fip_2, 'instance-2', - 'sudo cat /mnt/ceph/test'.format( - mount_path), + 'sudo cat /mnt/ceph/test', password=password, privkey=privkey, verify=verify) diff --git a/zaza/openstack/utilities/series_upgrade.py b/zaza/openstack/utilities/series_upgrade.py index bff8afc..f12f77b 100644 --- a/zaza/openstack/utilities/series_upgrade.py +++ b/zaza/openstack/utilities/series_upgrade.py @@ -190,7 +190,7 @@ def series_upgrade_non_leaders_first( completed_machines.append(machine) else: logging.info("Skipping unit: {}. Machine: {} already upgraded. " - .format(unit, machine, application)) + .format(unit, machine)) model.block_until_all_units_idle() # Series upgrade the leader @@ -206,7 +206,7 @@ def series_upgrade_non_leaders_first( completed_machines.append(machine) else: logging.info("Skipping unit: {}. Machine: {} already upgraded." - .format(unit, machine, application)) + .format(unit, machine)) model.block_until_all_units_idle() @@ -295,7 +295,7 @@ async def async_series_upgrade_non_leaders_first( completed_machines.append(machine) else: logging.info("Skipping unit: {}. Machine: {} already upgraded. " - .format(unit, machine, application)) + .format(unit, machine)) await model.async_block_until_all_units_idle() # Series upgrade the leader @@ -312,7 +312,7 @@ async def async_series_upgrade_non_leaders_first( completed_machines.append(machine) else: logging.info("Skipping unit: {}. Machine: {} already upgraded." - .format(unit, machine, application)) + .format(unit, machine)) await model.async_block_until_all_units_idle() From 593254f7905f6329bde1bcf41003043b1617167f Mon Sep 17 00:00:00 2001 From: Alex Kavanagh Date: Wed, 13 May 2020 11:52:48 +0100 Subject: [PATCH 016/140] libjuju-2.8 breaks with juju 2.7.6 and 2.8.0 - pinning This is a temporary pin until a fix is provided. --- requirements.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index fa0e460..1f685cf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ aiounittest async_generator boto3 -juju +juju<2.8.0 juju_wait PyYAML<=4.2,>=3.0 flake8>=2.2.4 diff --git a/setup.py b/setup.py index 6d08832..3e781da 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ install_require = [ 'cryptography', 'hvac<0.7.0', 'jinja2', - 'juju', + 'juju<2.8.0', 'juju-wait', 'lxml', 'PyYAML', From fd3f0bb09355d73574cb83700a7d8381e981260f Mon Sep 17 00:00:00 2001 From: Alex Kavanagh Date: Tue, 21 Apr 2020 15:39:48 +0100 Subject: [PATCH 017/140] Migrate 499 cinder-ceph test to zaza This takes the 499 test from basic_deployment.py (Amulet) test and ports it over to zaza. --- .../charm_tests/ceph/mon/__init__.py | 15 ++++ zaza/openstack/charm_tests/ceph/mon/tests.py | 69 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 zaza/openstack/charm_tests/ceph/mon/__init__.py create mode 100644 zaza/openstack/charm_tests/ceph/mon/tests.py diff --git a/zaza/openstack/charm_tests/ceph/mon/__init__.py b/zaza/openstack/charm_tests/ceph/mon/__init__.py new file mode 100644 index 0000000..867c3af --- /dev/null +++ b/zaza/openstack/charm_tests/ceph/mon/__init__.py @@ -0,0 +1,15 @@ +# 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. + +"""Collection of code for setting up and testing ceph-mon for cinder-ceph.""" diff --git a/zaza/openstack/charm_tests/ceph/mon/tests.py b/zaza/openstack/charm_tests/ceph/mon/tests.py new file mode 100644 index 0000000..50063c7 --- /dev/null +++ b/zaza/openstack/charm_tests/ceph/mon/tests.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. + +"""Ceph-mon Testing for cinder-ceph.""" + +import logging +import unittest + +import zaza.model + +from zaza.openstack.utilities import ( + generic as generic_utils, + openstack as openstack_utils, +) + + +class CinderCephMonTest(unittest.TestCase): + """Verify that the ceph mon units are healthy.""" + + @classmethod + def setUpClass(cls): + """Run class setup for running ceph security tests.""" + super().setUpClass() + + # ported from the cinder-ceph Amulet test + def test_499_ceph_cmds_exit_zero(self): + """Verify expected state with security-checklist.""" + logging.info("Checking exit values are 0 on ceph commands.") + + units = zaza.model.get_units("ceph-mon", model_name=self.model_name) + current_release = openstack_utils.get_os_release() + bionic_train = openstack_utils.get_os_release('bionic_train') + if current_release < bionic_train: + units.extend(zaza.model.get_utils("cinder-ceph", + model_name=self.model_name)) + + commands = [ + 'sudo ceph health', + 'sudo ceph mds stat', + 'sudo ceph pg stat', + 'sudo ceph osd stat', + 'sudo ceph mon stat', + ] + + for unit in units: + run_commands(unit.name, commands) + + +def run_commands(self, unit_name, commands): + """Run commands on unit. + + Apply context to commands until all variables have been replaced, then + run the command on the given unit. + """ + for cmd in commands: + generic_utils.assertRemoteRunOK(zaza.model.run_on_unit( + unit_name, + cmd)) From c6f047268139b418c2e6f24b054daab57b9e0f18 Mon Sep 17 00:00:00 2001 From: Alex Kavanagh Date: Mon, 4 May 2020 18:23:07 +0100 Subject: [PATCH 018/140] Fix the broken bits in the tests copied from amulet --- zaza/openstack/charm_tests/ceph/mon/tests.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/zaza/openstack/charm_tests/ceph/mon/tests.py b/zaza/openstack/charm_tests/ceph/mon/tests.py index 50063c7..276ed9f 100644 --- a/zaza/openstack/charm_tests/ceph/mon/tests.py +++ b/zaza/openstack/charm_tests/ceph/mon/tests.py @@ -23,14 +23,15 @@ from zaza.openstack.utilities import ( generic as generic_utils, openstack as openstack_utils, ) +import zaza.openstack.charm_tests.test_utils as test_utils -class CinderCephMonTest(unittest.TestCase): +class CinderCephMonTest(test_utils.OpenStackBaseTest): """Verify that the ceph mon units are healthy.""" @classmethod def setUpClass(cls): - """Run class setup for running ceph security tests.""" + """Run class setup for running ceph mon tests with cinder.""" super().setUpClass() # ported from the cinder-ceph Amulet test @@ -57,7 +58,7 @@ class CinderCephMonTest(unittest.TestCase): run_commands(unit.name, commands) -def run_commands(self, unit_name, commands): +def run_commands(unit_name, commands): """Run commands on unit. Apply context to commands until all variables have been replaced, then From 294fd83e498cfaeefe8c92913626271ed6fcb162 Mon Sep 17 00:00:00 2001 From: Alex Kavanagh Date: Mon, 4 May 2020 18:23:34 +0100 Subject: [PATCH 019/140] Fix up cinder tests to work when cinder isn't the application under test The cinder tests were written with the assumption that cinder was the charm that was under test. This modifies the test so that the cinder tests work in a model with cinder where cinder isn't the application that is being explicitly tested. --- zaza/openstack/charm_tests/cinder/tests.py | 68 ++++++++++++++++++---- 1 file changed, 57 insertions(+), 11 deletions(-) diff --git a/zaza/openstack/charm_tests/cinder/tests.py b/zaza/openstack/charm_tests/cinder/tests.py index d645eed..460ff00 100644 --- a/zaza/openstack/charm_tests/cinder/tests.py +++ b/zaza/openstack/charm_tests/cinder/tests.py @@ -32,7 +32,10 @@ class CinderTests(test_utils.OpenStackBaseTest): @classmethod def setUpClass(cls): """Run class setup for running tests.""" - super(CinderTests, cls).setUpClass() + super(CinderTests, cls).setUpClass(application_name='cinder') + cls.application_name = 'cinder' + cls.lead_unit = zaza.model.get_lead_unit_name( + "cinder", model_name=cls.model_name) cls.cinder_client = openstack_utils.get_cinder_session_client( cls.keystone_session) cls.nova_client = openstack_utils.get_nova_session_client( @@ -42,18 +45,61 @@ class CinderTests(test_utils.OpenStackBaseTest): def tearDown(cls): """Remove test resources.""" logging.info('Running teardown') - for snapshot in cls.cinder_client.volume_snapshots.list(): + volumes = list(cls.cinder_client.volumes.list()) + snapped_volumes = [v for v in volumes if v.name.endswith("-from-snap")] + if snapped_volumes: + logging.info("Removing volumes from snapshot") + cls._remove_volumes(snapped_volumes) + volumes = list(cls.cinder_client.volumes.list()) + + snapshots = list(cls.cinder_client.volume_snapshots.list()) + if snapshots: + logging.info("tearDown - snapshots: {}".format( + ", ".join(s.name for s in snapshots))) + cls._remove_snapshots(snapshots) + + if volumes: + logging.info("tearDown - volumes: {}".format( + ", ".join(v.name for v in volumes))) + cls._remove_volumes(volumes) + + @classmethod + def _remove_snapshots(cls, snapshots): + """Remove snapshots passed as param. + + :param volumes: the snapshots to delete + :type volumes: List[snapshot objects] + """ + for snapshot in snapshots: if snapshot.name.startswith(cls.RESOURCE_PREFIX): - openstack_utils.delete_resource( - cls.cinder_client.volume_snapshots, - snapshot.id, - msg="snapshot") - for volume in cls.cinder_client.volumes.list(): + logging.info("removing snapshot: {}".format(snapshot.name)) + try: + openstack_utils.delete_resource( + cls.cinder_client.volume_snapshots, + snapshot.id, + msg="snapshot") + except Exception as e: + logging.error("error removing snapshot: {}".format(str(e))) + raise + + @classmethod + def _remove_volumes(cls, volumes): + """Remove volumes passed as param. + + :param volumes: the volumes to delete + :type volumes: List[volume objects] + """ + for volume in volumes: if volume.name.startswith(cls.RESOURCE_PREFIX): - openstack_utils.delete_resource( - cls.cinder_client.volumes, - volume.id, - msg="volume") + logging.info("removing volume: {}".format(volume.name)) + try: + openstack_utils.delete_resource( + cls.cinder_client.volumes, + volume.id, + msg="volume") + except Exception as e: + logging.error("error removing volume: {}".format(str(e))) + raise def test_100_volume_create_extend_delete(self): """Test creating, extending a volume.""" From 3de62513161efbb0b1ea831628bff15764086a59 Mon Sep 17 00:00:00 2001 From: Alex Kavanagh Date: Tue, 5 May 2020 15:07:44 +0100 Subject: [PATCH 020/140] Fix broken code and collect errors on ceph commands --- zaza/openstack/charm_tests/ceph/mon/tests.py | 16 ++++++++++++---- zaza/openstack/utilities/exceptions.py | 6 ++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/zaza/openstack/charm_tests/ceph/mon/tests.py b/zaza/openstack/charm_tests/ceph/mon/tests.py index 276ed9f..0fea084 100644 --- a/zaza/openstack/charm_tests/ceph/mon/tests.py +++ b/zaza/openstack/charm_tests/ceph/mon/tests.py @@ -22,6 +22,7 @@ import zaza.model from zaza.openstack.utilities import ( generic as generic_utils, openstack as openstack_utils, + exceptions as zaza_exceptions ) import zaza.openstack.charm_tests.test_utils as test_utils @@ -43,7 +44,7 @@ class CinderCephMonTest(test_utils.OpenStackBaseTest): current_release = openstack_utils.get_os_release() bionic_train = openstack_utils.get_os_release('bionic_train') if current_release < bionic_train: - units.extend(zaza.model.get_utils("cinder-ceph", + units.extend(zaza.model.get_units("cinder-ceph", model_name=self.model_name)) commands = [ @@ -64,7 +65,14 @@ def run_commands(unit_name, commands): Apply context to commands until all variables have been replaced, then run the command on the given unit. """ + errors = [] for cmd in commands: - generic_utils.assertRemoteRunOK(zaza.model.run_on_unit( - unit_name, - cmd)) + try: + generic_utils.assertRemoteRunOK(zaza.model.run_on_unit( + unit_name, + cmd)) + except Exception as e: + errors.append("unit: {}, command: {}, error: {}" + .format(unit_name, cmd, str(e))) + if errors: + raise zaza_exceptions.CephGenericError("\n".join(errors)) diff --git a/zaza/openstack/utilities/exceptions.py b/zaza/openstack/utilities/exceptions.py index e9e5d23..f8673a1 100644 --- a/zaza/openstack/utilities/exceptions.py +++ b/zaza/openstack/utilities/exceptions.py @@ -168,6 +168,12 @@ class CephPoolNotConfigured(Exception): pass +class CephGenericError(Exception): + """A generic/other Ceph error occurred.""" + + pass + + class NovaGuestMigrationFailed(Exception): """Nova guest migration failed.""" From 935af95dd23c38a4d8d570482498c6f72f649be6 Mon Sep 17 00:00:00 2001 From: Alex Kavanagh Date: Wed, 6 May 2020 12:09:22 +0100 Subject: [PATCH 021/140] Add in missing 500 test from amulet test This is the missing 500 test from the previous amulet tests that were missed on the first pass. --- zaza/openstack/charm_tests/ceph/mon/tests.py | 66 +++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/zaza/openstack/charm_tests/ceph/mon/tests.py b/zaza/openstack/charm_tests/ceph/mon/tests.py index 0fea084..39cf4a2 100644 --- a/zaza/openstack/charm_tests/ceph/mon/tests.py +++ b/zaza/openstack/charm_tests/ceph/mon/tests.py @@ -15,7 +15,6 @@ """Ceph-mon Testing for cinder-ceph.""" import logging -import unittest import zaza.model @@ -58,6 +57,57 @@ class CinderCephMonTest(test_utils.OpenStackBaseTest): for unit in units: run_commands(unit.name, commands) + # ported from the cinder-ceph Amulet test + def test_500_ceph_alternatives_cleanup(self): + """Check ceph alternatives removed when ceph-mon relation is broken.""" + # Skip this test if release is less than xenial_ocata as in that case + # cinder HAS a relation with ceph directly and this test would fail + current_release = openstack_utils.get_os_release() + xenial_ocata = openstack_utils.get_os_release('xenial_ocata') + if current_release < xenial_ocata: + logging.info("Skipping test as release < xenial-ocata") + return + + units = zaza.model.get_units("cinder-ceph", + model_name=self.model_name) + + # check each unit prior to breaking relation + for unit in units: + dir_list = directory_listing(unit.name, "/etc/ceph") + if 'ceph.conf' in dir_list: + logging.debug( + "/etc/ceph/ceph.conf exists BEFORE relation-broken") + else: + raise zaza_exceptions.CephGenericError( + "unit: {} - /etc/ceph/ceph.conf does not exist " + "BEFORE relation-broken".format(unit.name)) + + # remove the relation so that /etc/ceph/ceph.conf is removed + logging.info("Removing ceph-mon:client <-> cinder-ceph:ceph relation") + zaza.model.remove_relation( + "ceph-mon", "ceph-mon:client" "cinder-ceph:ceph") + logging.info("Wait till model is idle ...") + zaza.model.block_until_all_units_idle() + + # check each unit after to breaking relation + for unit in units: + dir_list = directory_listing(unit.name, "/etc/ceph") + if 'ceph.conf' not in dir_list: + logging.debug( + "/etc/ceph/ceph.conf removed AFTER relation-broken") + else: + raise zaza_exceptions.CephGenericError( + "unit: {} - /etc/ceph/ceph.conf still exists " + "AFTER relation-broken".format(unit.name)) + + # Restore cinder-ceph and ceph-mon relation to keep tests idempotent + logging.info("Restoring ceph-mon:client <-> cinder-ceph:ceph relation") + zaza.model.add_relation( + "ceph-mon", "ceph-mon:client" "cinder-ceph:ceph") + logging.info("Wait till model is idle ...") + zaza.model.block_until_all_units_idle() + logging.info("... Done.") + def run_commands(unit_name, commands): """Run commands on unit. @@ -76,3 +126,17 @@ def run_commands(unit_name, commands): .format(unit_name, cmd, str(e))) if errors: raise zaza_exceptions.CephGenericError("\n".join(errors)) + + +def directory_listing(unit_name, directory): + """Return a list of files/directories from a directory on a unit. + + :param unit_name: the unit to fetch the directory listing from + :type unit_name: str + :param directory: the directory to fetch the listing from + :type directory: str + :returns: A listing using "ls -1" on the unit + :rtype: List[str] + """ + result = zaza.model.run_on_unit(unit_name, "ls -1 {}".format(directory)) + return result['Stdout'].splitlines() From a7df335cc84cc5fb175cb4c8df00faf7c38d838c Mon Sep 17 00:00:00 2001 From: Alex Kavanagh Date: Wed, 6 May 2020 15:31:53 +0100 Subject: [PATCH 022/140] Fix missing comma --- zaza/openstack/charm_tests/ceph/mon/tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zaza/openstack/charm_tests/ceph/mon/tests.py b/zaza/openstack/charm_tests/ceph/mon/tests.py index 39cf4a2..dd6d7aa 100644 --- a/zaza/openstack/charm_tests/ceph/mon/tests.py +++ b/zaza/openstack/charm_tests/ceph/mon/tests.py @@ -85,7 +85,7 @@ class CinderCephMonTest(test_utils.OpenStackBaseTest): # remove the relation so that /etc/ceph/ceph.conf is removed logging.info("Removing ceph-mon:client <-> cinder-ceph:ceph relation") zaza.model.remove_relation( - "ceph-mon", "ceph-mon:client" "cinder-ceph:ceph") + "ceph-mon", "ceph-mon:client", "cinder-ceph:ceph") logging.info("Wait till model is idle ...") zaza.model.block_until_all_units_idle() @@ -103,7 +103,7 @@ class CinderCephMonTest(test_utils.OpenStackBaseTest): # Restore cinder-ceph and ceph-mon relation to keep tests idempotent logging.info("Restoring ceph-mon:client <-> cinder-ceph:ceph relation") zaza.model.add_relation( - "ceph-mon", "ceph-mon:client" "cinder-ceph:ceph") + "ceph-mon", "ceph-mon:client", "cinder-ceph:ceph") logging.info("Wait till model is idle ...") zaza.model.block_until_all_units_idle() logging.info("... Done.") From 8e826f6f8bc277b75b962b2516238ae1a80a3a83 Mon Sep 17 00:00:00 2001 From: Alex Kavanagh Date: Wed, 13 May 2020 07:57:24 +0100 Subject: [PATCH 023/140] Add debug to 105 test --- zaza/openstack/charm_tests/cinder/tests.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/zaza/openstack/charm_tests/cinder/tests.py b/zaza/openstack/charm_tests/cinder/tests.py index 460ff00..ec6caf0 100644 --- a/zaza/openstack/charm_tests/cinder/tests.py +++ b/zaza/openstack/charm_tests/cinder/tests.py @@ -126,12 +126,18 @@ class CinderTests(test_utils.OpenStackBaseTest): def test_105_volume_create_from_img(self): """Test creating a volume from an image.""" + logging.debug("finding image {} ..." + .format(glance_setup.LTS_IMAGE_NAME) image = self.nova_client.glance.find_image( glance_setup.LTS_IMAGE_NAME) + logging.debug("using cinder_client to create volume from image {}" + .format(image.id)) vol_img = self.cinder_client.volumes.create( name='{}-105-vol-from-img'.format(self.RESOURCE_PREFIX), size=3, imageRef=image.id) + logging.debug("now waiting for volume {} to reach available" + .format(vol_img.id)) openstack_utils.resource_reaches_status( self.cinder_client.volumes, vol_img.id, From 4c2f723332e2bfa5e67e80741c606e845050895c Mon Sep 17 00:00:00 2001 From: Alex Kavanagh Date: Wed, 13 May 2020 14:34:13 +0100 Subject: [PATCH 024/140] Fix missing brace --- zaza/openstack/charm_tests/cinder/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zaza/openstack/charm_tests/cinder/tests.py b/zaza/openstack/charm_tests/cinder/tests.py index ec6caf0..4a851ce 100644 --- a/zaza/openstack/charm_tests/cinder/tests.py +++ b/zaza/openstack/charm_tests/cinder/tests.py @@ -127,7 +127,7 @@ class CinderTests(test_utils.OpenStackBaseTest): def test_105_volume_create_from_img(self): """Test creating a volume from an image.""" logging.debug("finding image {} ..." - .format(glance_setup.LTS_IMAGE_NAME) + .format(glance_setup.LTS_IMAGE_NAME)) image = self.nova_client.glance.find_image( glance_setup.LTS_IMAGE_NAME) logging.debug("using cinder_client to create volume from image {}" From a56fa1fd74668f3ed3f5dcba3bdd3f43c59e7695 Mon Sep 17 00:00:00 2001 From: Marco Silva Date: Thu, 14 May 2020 09:34:44 +0100 Subject: [PATCH 025/140] Add mysqldump test to MySqlCommonTests Copy function from MySQLInnoDBClusterTests to test mysqldump as standard on mysql applications. DocImpact Closes-Bug: #0000000 Implements: MySQL dump test --- zaza/openstack/charm_tests/mysql/tests.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/zaza/openstack/charm_tests/mysql/tests.py b/zaza/openstack/charm_tests/mysql/tests.py index 77bc966..fb1ac61 100644 --- a/zaza/openstack/charm_tests/mysql/tests.py +++ b/zaza/openstack/charm_tests/mysql/tests.py @@ -75,6 +75,23 @@ class MySQLBaseTest(test_utils.OpenStackBaseTest): class MySQLCommonTests(MySQLBaseTest): """Common mysql charm tests.""" + def test_110_mysqldump(self): + """Backup mysql. + + Run the mysqldump action. + """ + _db = "keystone" + _file_key = "mysqldump-file" + logging.info("Execute mysqldump action") + action = zaza.model.run_action_on_leader( + self.application, + "mysqldump", + action_params={"databases": _db}) + _results = action.data["results"] + assert _db in _results[_file_key], ( + "Mysqldump action failed: {}".format(action.data)) + logging.info("Passed mysqldump action test.") + def test_910_restart_on_config_change(self): """Checking restart happens on config change. From 5d05eb70ccc3d71613b5326a6a28407175498d8d Mon Sep 17 00:00:00 2001 From: Alex Kavanagh Date: Thu, 14 May 2020 19:37:45 +0100 Subject: [PATCH 026/140] Ensure model starts executing before the wait There was a race before a block_until_all_units_idle() where it could blast through the check before it started removing the relation. This ensures that it waits until something happens and then waits for it to finish. --- zaza/openstack/charm_tests/ceph/mon/tests.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zaza/openstack/charm_tests/ceph/mon/tests.py b/zaza/openstack/charm_tests/ceph/mon/tests.py index dd6d7aa..63c4033 100644 --- a/zaza/openstack/charm_tests/ceph/mon/tests.py +++ b/zaza/openstack/charm_tests/ceph/mon/tests.py @@ -86,6 +86,7 @@ class CinderCephMonTest(test_utils.OpenStackBaseTest): logging.info("Removing ceph-mon:client <-> cinder-ceph:ceph relation") zaza.model.remove_relation( "ceph-mon", "ceph-mon:client", "cinder-ceph:ceph") + zaza.model.wait_for_agent_status() logging.info("Wait till model is idle ...") zaza.model.block_until_all_units_idle() @@ -104,6 +105,7 @@ class CinderCephMonTest(test_utils.OpenStackBaseTest): logging.info("Restoring ceph-mon:client <-> cinder-ceph:ceph relation") zaza.model.add_relation( "ceph-mon", "ceph-mon:client", "cinder-ceph:ceph") + zaza.model.wait_for_agent_status() logging.info("Wait till model is idle ...") zaza.model.block_until_all_units_idle() logging.info("... Done.") From 52ee23927c27bbf02bf0a47fa5090e78d01a094a Mon Sep 17 00:00:00 2001 From: Liam Young Date: Fri, 15 May 2020 13:54:01 +0000 Subject: [PATCH 027/140] Make masakari wait longer for hosts to online A recent functional test of masakari monitors failed due to a tenacity retry missing the host coming online by 10s, so wait longer. FWIW that this affects the post-test cleanup rather than the test itself. *1 https://openstack-ci-reports.ubuntu.com/artifacts/test_charm_pipeline_func_full/openstack/charm-masakari-monitors/726808/1/5655/consoleText.test_charm_func_full_8812.txt ` --- zaza/openstack/configure/masakari.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zaza/openstack/configure/masakari.py b/zaza/openstack/configure/masakari.py index a529bbe..707416f 100644 --- a/zaza/openstack/configure/masakari.py +++ b/zaza/openstack/configure/masakari.py @@ -85,7 +85,7 @@ def create_segments(segment_number=1, host_assignment_method=None): @tenacity.retry( wait=tenacity.wait_exponential(multiplier=2, max=60), - reraise=True, stop=tenacity.stop_after_attempt(5), + reraise=True, stop=tenacity.stop_after_attempt(10), retry=tenacity.retry_if_exception_type(ostack_except.ConflictException)) def enable_host(masakari_client, host, segment): """Enable hypervisor within masakari. From 1a0210b0a53871b1c59b8f5110d56a5cfedf3c5b Mon Sep 17 00:00:00 2001 From: Liam Young Date: Sat, 16 May 2020 08:29:12 +0000 Subject: [PATCH 028/140] Remove node1 Due to LP #1874719 delete node1 as its not used. --- zaza/openstack/charm_tests/pacemaker_remote/tests.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/zaza/openstack/charm_tests/pacemaker_remote/tests.py b/zaza/openstack/charm_tests/pacemaker_remote/tests.py index f0d0b4d..328d5cf 100644 --- a/zaza/openstack/charm_tests/pacemaker_remote/tests.py +++ b/zaza/openstack/charm_tests/pacemaker_remote/tests.py @@ -26,5 +26,8 @@ class PacemakerRemoteTest(unittest.TestCase): def test_check_nodes_online(self): """Test that all nodes are online.""" + zaza.openstack.configure.hacluster.remove_node( + 'api', + 'node1') self.assertTrue( zaza.openstack.configure.hacluster.check_all_nodes_online('api')) From 21655a3741f80afad43c138910e1ff3d5979535c Mon Sep 17 00:00:00 2001 From: Alex Kavanagh Date: Sat, 16 May 2020 18:52:35 +0100 Subject: [PATCH 029/140] 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 215e5accef726ce74b4f92f78b65635a993b1a75 Mon Sep 17 00:00:00 2001 From: Alex Kavanagh Date: Sun, 17 May 2020 21:19:14 +0100 Subject: [PATCH 030/140] Add retries to instance pinging It's a bit too optimistic to expect an instance to respond to the first ping. This patch gives the instance up to 8 retries with increasingly lengthened waits to respond to a ping. This should help with Juju storage backed nova instances. Fixes: #265 --- zaza/openstack/configure/guest.py | 28 ++++++++++++++++++++------ zaza/openstack/utilities/exceptions.py | 6 ++++++ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/zaza/openstack/configure/guest.py b/zaza/openstack/configure/guest.py index bd70bca..c2e20de 100644 --- a/zaza/openstack/configure/guest.py +++ b/zaza/openstack/configure/guest.py @@ -22,6 +22,14 @@ import time import zaza.openstack.utilities.openstack as openstack_utils import zaza.openstack.charm_tests.nova.utils as nova_utils +import zaza.openstack.utilities.exceptions as openstack_exceptions + +from tenacity import ( + RetryError, + Retrying, + stop_after_attempt, + wait_exponential, +) boot_tests = { 'cirros': { @@ -134,12 +142,20 @@ def launch_instance(instance_key, use_boot_volume=False, vm_name=None, port=port)['floating_ip_address'] logging.info('Assigned floating IP {} to {}'.format(ip, vm_name)) try: - openstack_utils.ping_response(ip) - except subprocess.CalledProcessError as e: - logging.error('Pinging {} failed with {}'.format(ip, e.returncode)) - logging.error('stdout: {}'.format(e.stdout)) - logging.error('stderr: {}'.format(e.stderr)) - raise + for attempt in Retrying( + stop=stop_after_attempt(8), + wait=wait_exponential(multiplier=1, min=2, max=60)): + with attempt: + try: + openstack_utils.ping_response(ip) + except subprocess.CalledProcessError as e: + logging.error('Pinging {} failed with {}' + .format(ip, e.returncode)) + logging.error('stdout: {}'.format(e.stdout)) + logging.error('stderr: {}'.format(e.stderr)) + raise + except RetryError: + raise openstack_exceptions.NovaGuestNoPingResponse() # Check ssh'ing to instance. logging.info('Testing ssh access.') diff --git a/zaza/openstack/utilities/exceptions.py b/zaza/openstack/utilities/exceptions.py index e9e5d23..ab4e258 100644 --- a/zaza/openstack/utilities/exceptions.py +++ b/zaza/openstack/utilities/exceptions.py @@ -180,6 +180,12 @@ class NovaGuestRestartFailed(Exception): pass +class NovaGuestNoPingResponse(Exception): + """Nova guest failed to respond to pings.""" + + pass + + class PolicydError(Exception): """Policyd override failed.""" From 5e5c8e488bda1ae2d50b1c9f01ed3fe42f6d02cd Mon Sep 17 00:00:00 2001 From: Alex Kavanagh Date: Mon, 18 May 2020 12:52:19 +0100 Subject: [PATCH 031/140] Add retries to teardown as it seems to be sensitive to network glitches. --- zaza/openstack/charm_tests/cinder/tests.py | 41 ++++++++++++++-------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/zaza/openstack/charm_tests/cinder/tests.py b/zaza/openstack/charm_tests/cinder/tests.py index 4a851ce..2648538 100644 --- a/zaza/openstack/charm_tests/cinder/tests.py +++ b/zaza/openstack/charm_tests/cinder/tests.py @@ -23,6 +23,13 @@ import zaza.openstack.charm_tests.test_utils as test_utils import zaza.openstack.utilities.openstack as openstack_utils import zaza.openstack.charm_tests.glance.setup as glance_setup +from tenacity import ( + RetryError, + Retrying, + stop_after_attempt, + wait_exponential, +) + class CinderTests(test_utils.OpenStackBaseTest): """Encapsulate Cinder tests.""" @@ -45,23 +52,27 @@ class CinderTests(test_utils.OpenStackBaseTest): def tearDown(cls): """Remove test resources.""" logging.info('Running teardown') - volumes = list(cls.cinder_client.volumes.list()) - snapped_volumes = [v for v in volumes if v.name.endswith("-from-snap")] - if snapped_volumes: - logging.info("Removing volumes from snapshot") - cls._remove_volumes(snapped_volumes) - volumes = list(cls.cinder_client.volumes.list()) + for attempt in Retrying( + stop=stop_after_attempt(8), + wait=wait_exponential(multiplier=1, min=2, max=60)): + with attempt: + volumes = list(cls.cinder_client.volumes.list()) + snapped_volumes = [v for v in volumes if v.name.endswith("-from-snap")] + if snapped_volumes: + logging.info("Removing volumes from snapshot") + cls._remove_volumes(snapped_volumes) + volumes = list(cls.cinder_client.volumes.list()) - snapshots = list(cls.cinder_client.volume_snapshots.list()) - if snapshots: - logging.info("tearDown - snapshots: {}".format( - ", ".join(s.name for s in snapshots))) - cls._remove_snapshots(snapshots) + snapshots = list(cls.cinder_client.volume_snapshots.list()) + if snapshots: + logging.info("tearDown - snapshots: {}".format( + ", ".join(s.name for s in snapshots))) + cls._remove_snapshots(snapshots) - if volumes: - logging.info("tearDown - volumes: {}".format( - ", ".join(v.name for v in volumes))) - cls._remove_volumes(volumes) + if volumes: + logging.info("tearDown - volumes: {}".format( + ", ".join(v.name for v in volumes))) + cls._remove_volumes(volumes) @classmethod def _remove_snapshots(cls, snapshots): From 7834eda00ea5c303ad499b51913142fd0eb460c2 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Mon, 18 May 2020 12:48:05 +0000 Subject: [PATCH 032/140] Switch to cirros image for ceph mirror tests The image used for the tests is just treated as a blob, no guests are booted using it so to speed up the tests when using slow storage by switching to using cirros image. The cirros image is ~20 times smaller than the bionic one. --- zaza/openstack/charm_tests/ceph/rbd_mirror/tests.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/zaza/openstack/charm_tests/ceph/rbd_mirror/tests.py b/zaza/openstack/charm_tests/ceph/rbd_mirror/tests.py index 16ebfc0..2e238ae 100644 --- a/zaza/openstack/charm_tests/ceph/rbd_mirror/tests.py +++ b/zaza/openstack/charm_tests/ceph/rbd_mirror/tests.py @@ -23,7 +23,9 @@ import zaza.model import zaza.openstack.utilities.ceph import zaza.openstack.utilities.openstack as openstack -from zaza.openstack.charm_tests.glance.setup import LTS_IMAGE_NAME +from zaza.openstack.charm_tests.glance.setup import ( + LTS_IMAGE_NAME, + CIRROS_IMAGE_NAME) class CephRBDMirrorBase(test_utils.OpenStackBaseTest): @@ -197,7 +199,12 @@ class CephRBDMirrorTest(CephRBDMirrorBase): glance = openstack.get_glance_session_client(session) cinder = openstack.get_cinder_session_client(session) - image = next(glance.images.list(name=LTS_IMAGE_NAME)) + image = openstack.get_images_by_name(glance, CIRROS_IMAGE_NAME) + if not image: + logging.info("Failed to find {} image, falling back to {}".format( + CIRROS_IMAGE_NAME, + LTS_IMAGE_NAME)) + image = openstack.get_images_by_name(glance, LTS_IMAGE_NAME) # NOTE(fnordahl): for some reason create volume from image often fails # when run just after deployment is finished. We should figure out From 0ec2e43041ea52d26f0ba5f4e49e29997af22251 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Mon, 18 May 2020 18:25:09 +0000 Subject: [PATCH 033/140] Fix ceph rbd mirror test I recently broke the ceph rbd mirror test due to get_images_by_name returning a list of images not a single image. --- zaza/openstack/charm_tests/ceph/rbd_mirror/tests.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/zaza/openstack/charm_tests/ceph/rbd_mirror/tests.py b/zaza/openstack/charm_tests/ceph/rbd_mirror/tests.py index 2e238ae..6c2fa1b 100644 --- a/zaza/openstack/charm_tests/ceph/rbd_mirror/tests.py +++ b/zaza/openstack/charm_tests/ceph/rbd_mirror/tests.py @@ -199,12 +199,14 @@ class CephRBDMirrorTest(CephRBDMirrorBase): glance = openstack.get_glance_session_client(session) cinder = openstack.get_cinder_session_client(session) - image = openstack.get_images_by_name(glance, CIRROS_IMAGE_NAME) - if not image: + images = openstack.get_images_by_name(glance, CIRROS_IMAGE_NAME) + if images: + image = images[0] + else: logging.info("Failed to find {} image, falling back to {}".format( CIRROS_IMAGE_NAME, LTS_IMAGE_NAME)) - image = openstack.get_images_by_name(glance, LTS_IMAGE_NAME) + image = openstack.get_images_by_name(glance, LTS_IMAGE_NAME)[0] # NOTE(fnordahl): for some reason create volume from image often fails # when run just after deployment is finished. We should figure out From dfc62353620d0229fec14b8f4a05fcfd6332c34c Mon Sep 17 00:00:00 2001 From: Dmitrii Shcherbakov Date: Mon, 18 May 2020 19:58:16 +0300 Subject: [PATCH 034/140] Add cleanup to the unit removal case For some reason test cases are sometimes executed out of order and so test_921_remove_unit is sometimes run before the pause_and_resume test case which results in an error. While the root cause for it must be found it would also be good to avoid side-effects in individual test cases and return the environment back to its original state. There is no 'start' hook implementation for charm-rabbitmq-server, however, changes that close to the 20.05 release are discouraged so this change uses an upgrade-charm event simulation to re-trigger the addition of a unit (which was previously removed) to the cluster. NOTE: after an execution of hooks/upgrade-charm finishes, the charm will stay in the waiting state with the following status until the next update-status event: 'Unit has peers, but RabbitMQ not clustered' Related-Bug: #1730709 --- .../charm_tests/rabbitmq_server/tests.py | 58 ++++++++++++++----- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/zaza/openstack/charm_tests/rabbitmq_server/tests.py b/zaza/openstack/charm_tests/rabbitmq_server/tests.py index 43e9bc8..5220b3e 100644 --- a/zaza/openstack/charm_tests/rabbitmq_server/tests.py +++ b/zaza/openstack/charm_tests/rabbitmq_server/tests.py @@ -343,11 +343,14 @@ class RmqTests(test_utils.OpenStackBaseTest): def _retry_check_unit_cluster_nodes(self, u, unit_node_names): return rmq_utils.check_unit_cluster_nodes(u, unit_node_names) - def test_921_remove_unit(self): + def test_921_remove_and_add_unit(self): """Test if unit cleans up when removed from Rmq cluster. Test if a unit correctly cleans up by removing itself from the - RabbitMQ cluster on removal + RabbitMQ cluster on removal. + + Add the unit back to the cluster at the end of the test case to + avoid side-effects. """ logging.info('Checking that units correctly clean up after ' @@ -356,25 +359,48 @@ class RmqTests(test_utils.OpenStackBaseTest): zaza.model.set_application_config('rabbitmq-server', config) rmq_utils.wait_for_cluster() - units = zaza.model.get_units(self.application_name) - removed_unit = units[-1] - left_units = units[:-1] + all_units = zaza.model.get_units(self.application_name) + removed_unit = all_units[-1] + left_units = all_units[:-1] + logging.info('Simulating unit {} removal'.format(removed_unit)) zaza.model.run_on_unit(removed_unit.entity_id, 'hooks/stop') + logging.info('Waiting until unit {} reaches "waiting" state' + ''.format(removed_unit)) zaza.model.block_until_unit_wl_status(removed_unit.entity_id, "waiting") - unit_host_names = generic_utils.get_unit_hostnames(left_units) - unit_node_names = [] - for unit in unit_host_names: - unit_node_names.append('rabbit@{}'.format(unit_host_names[unit])) - errors = [] + def check_units(units): + unit_host_names = generic_utils.get_unit_hostnames(units) + unit_node_names = [] + for unit in unit_host_names: + unit_node_names.append('rabbit@{}'.format( + unit_host_names[unit])) + errors = [] - for u in left_units: - e = self._retry_check_unit_cluster_nodes(u, - unit_node_names) - if e: - errors.append(e) + for u in units: + e = self._retry_check_unit_cluster_nodes(u, + unit_node_names) + if e: + errors.append(e) + + self.assertFalse(errors, msg=errors) + + logging.info('Checking that all units except for {} are present' + 'in the cluster'.format(removed_unit)) + check_units(left_units) + + logging.info('Re-adding the removed unit {} back to the cluster' + 'by simulating the upgrade-charm event' + ''.format(removed_unit)) + # TODO(dmitriis): Fix the rabbitmq charm to add a proper way to add a + # unit back to the cluster and replace this. + zaza.model.run_on_unit(removed_unit.entity_id, 'hooks/upgrade-charm') + logging.info('Waiting until unit {} reaches "active" state' + ''.format(removed_unit)) + zaza.model.block_until_unit_wl_status(removed_unit.entity_id, + "active") + logging.info('Checking that all units are present in the cluster') + check_units(all_units) - self.assertFalse(errors, msg=errors) logging.info('OK') From 0dad4aca1480fdb85992a82def909cfe739b1a11 Mon Sep 17 00:00:00 2001 From: Aurelien Lourot Date: Fri, 15 May 2020 15:29:51 +0200 Subject: [PATCH 035/140] Minor improvements to RmqTests.test_913_list_unconsumed_queues --- .../charm_tests/rabbitmq_server/tests.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/zaza/openstack/charm_tests/rabbitmq_server/tests.py b/zaza/openstack/charm_tests/rabbitmq_server/tests.py index 5220b3e..d0d2306 100644 --- a/zaza/openstack/charm_tests/rabbitmq_server/tests.py +++ b/zaza/openstack/charm_tests/rabbitmq_server/tests.py @@ -76,7 +76,9 @@ class RmqTests(test_utils.OpenStackBaseTest): rmq_utils.configure_ssl_off(units) # Publish and get amqp messages in all possible unit combinations. - # Qty of checks == (qty of units) ^ 2 + # Qty of checks == qty_of_units * (qty_of_units - 1) + assert len(units) >= 2, 'Test is useful only with 2 units or more.' + amqp_msg_counter = 1 host_names = generic_utils.get_unit_hostnames(units) @@ -311,8 +313,9 @@ class RmqTests(test_utils.OpenStackBaseTest): """Test rabbitmqctl list-unconsumed-queues action can be returned.""" logging.info('Checking list-unconsumed-queues action...') - unit = zaza.model.get_units(self.application_name)[0] - self._test_rmq_amqp_messages_all_units([unit]) + units = zaza.model.get_units(self.application_name) + self._test_rmq_amqp_messages_all_units(units) + unit = units[0] action = zaza.model.run_action(unit.entity_id, 'list-unconsumed-queues') self.assertIsInstance(action, juju.action.Action) @@ -332,7 +335,15 @@ class RmqTests(test_utils.OpenStackBaseTest): # Since we just reused _test_rmq_amqp_messages_all_units, we should # have created the queue if it didn't already exist, but all messages # should have already been consumed. - assert queue_data['messages'] == 0, 'Found unexpected message count.' + if queue_data['messages'] != 0: + logging.error( + '{} has {} remaining messages in {} instead of 0.'.format( + unit.entity_id, queue_data['messages'], + queue_data['name'])) + if queue_data['messages'] >= 1: + logging.error('One message is: {}'.format( + self._retry_get_amqp_message(unit))) + assert False, 'Found unexpected message count.' logging.info('OK') From c06cb8d62513bb5962a878e3d304066143c3b959 Mon Sep 17 00:00:00 2001 From: David Ames Date: Tue, 19 May 2020 07:47:55 -0700 Subject: [PATCH 036/140] MySQL 8 Scale-in and Scale-out tests (#275) * MySQL 8 Scale-in and Scale-out tests Depends-On: https://github.com/openstack-charmers/zaza/pull/353 * Review requests * Typo --- zaza/openstack/charm_tests/mysql/tests.py | 166 ++++++++++++++++++++-- 1 file changed, 155 insertions(+), 11 deletions(-) diff --git a/zaza/openstack/charm_tests/mysql/tests.py b/zaza/openstack/charm_tests/mysql/tests.py index 77bc966..5088010 100644 --- a/zaza/openstack/charm_tests/mysql/tests.py +++ b/zaza/openstack/charm_tests/mysql/tests.py @@ -71,6 +71,45 @@ class MySQLBaseTest(test_utils.OpenStackBaseTest): self.non_leaders.append(unit) return self.leader, self.non_leaders + def get_cluster_status(self): + """Get cluster status. + + Return cluster status dict from the cluster-status action or raise + assertion error. + + :returns: Dictionary of cluster status + :rtype: dict + """ + logging.info("Running cluster-status action") + action = zaza.model.run_action_on_leader( + self.application, + "cluster-status", + action_params={}) + assert action.data.get("results") is not None, ( + "Cluster status action failed: No results: {}" + .format(action.data)) + assert action.data["results"].get("cluster-status") is not None, ( + "Cluster status action failed: No cluster-status: {}" + .format(action.data)) + return json.loads(action.data["results"]["cluster-status"]) + + def get_rw_primary_node(self): + """Get RW primary node. + + Return RW primary node unit. + + :returns: Unit object of primary node + :rtype: Union[Unit, None] + """ + _status = self.get_cluster_status() + _primary_ip = _status['groupInformationSourceMember'] + if ":" in _primary_ip: + _primary_ip = _primary_ip.split(':')[0] + units = zaza.model.get_units(self.application_name) + for unit in units: + if _primary_ip in unit.public_address: + return unit + class MySQLCommonTests(MySQLBaseTest): """Common mysql charm tests.""" @@ -412,14 +451,10 @@ class MySQLInnoDBClusterTests(MySQLCommonTests): Run the cluster-status action. """ logging.info("Execute cluster-status action") - action = zaza.model.run_action_on_leader( - self.application, - "cluster-status", - action_params={}) - cluster_status = json.loads(action.data["results"]["cluster-status"]) + cluster_status = self.get_cluster_status() assert "OK" in cluster_status["defaultReplicaSet"]["status"], ( - "Cluster status action failed: {}" - .format(action.data)) + "Cluster status is not OK: {}" + .format(cluster_status)) logging.info("Passed cluster-status action test.") def test_110_mysqldump(self): @@ -538,10 +573,15 @@ class MySQLInnoDBClusterColdStartTest(MySQLBaseTest): logging.info("Execute reboot-cluster-from-complete-outage " "action after cold boot ...") - action = zaza.model.run_action_on_leader( - self.application, - "reboot-cluster-from-complete-outage", - action_params={}) + # We do not know which unit has the most up to date data + # run reboot-cluster-from-complete-outage until we get a success. + for unit in zaza.model.get_units(self.application): + action = zaza.model.run_action( + unit.entity_id, + "reboot-cluster-from-complete-outage", + action_params={}) + if action.data["results"].get("outcome"): + break assert "Success" in action.data["results"]["outcome"], ( "Reboot cluster from complete outage action failed: {}" .format(action.data)) @@ -670,3 +710,107 @@ class MySQL8MigrationTests(MySQLBaseTest): test_config = lifecycle_utils.get_charm_config(fatal=False) zaza.model.wait_for_application_states( states=test_config.get("target_deploy_status", {})) + + +class MySQLInnoDBClusterScaleTest(MySQLBaseTest): + """Percona Cluster cold start tests.""" + + @classmethod + def setUpClass(cls): + """Run class setup for running mysql-innodb-cluster scale tests.""" + super().setUpClass() + cls.application = "mysql-innodb-cluster" + cls.test_config = lifecycle_utils.get_charm_config(fatal=False) + cls.states = cls.test_config.get("target_deploy_status", {}) + + def test_800_remove_leader(self): + """Remove leader node. + + We start with a three node cluster, remove one, down to two. + The cluster will be in waiting state. + """ + logging.info("Scale in test: remove leader") + leader, nons = self.get_leaders_and_non_leaders() + leader_unit = zaza.model.get_unit_from_name(leader) + zaza.model.destroy_unit(self.application_name, leader) + + logging.info("Wait units are waiting ...") + zaza.model.block_until_unit_wl_status(nons[0], "waiting") + + logging.info("Wait till model is idle ...") + zaza.model.block_until_all_units_idle() + + logging.info( + "Removing old unit from cluster: {} " + .format(leader_unit.public_address)) + action = zaza.model.run_action( + nons[0], + "remove-instance", + action_params={ + "address": leader_unit.public_address, + "force": True}) + assert action.data.get("results") is not None, ( + "Remove instance action failed: No results: {}" + .format(action.data)) + + def test_801_add_unit(self): + """Add mysql-innodb-cluster node. + + We start with two node cluster in waiting, add one, back to a full + cluster of three. + """ + logging.info("Wait till model is idle ...") + zaza.model.block_until_all_units_idle() + + logging.info("Adding unit after removed unit ...") + zaza.model.add_unit(self.application_name) + + logging.info("Wait for application states ...") + zaza.model.wait_for_application_states(states=self.states) + + def test_802_add_unit(self): + """Add another mysql-innodb-cluster node. + + We start with a three node full cluster, add another, up to a four node + cluster. + """ + logging.info("Wait till model is idle ...") + zaza.model.block_until_all_units_idle() + + logging.info("Adding unit after full cluster ...") + zaza.model.add_unit(self.application_name) + + logging.info("Wait for application states ...") + zaza.model.wait_for_application_states(states=self.states) + + def test_803_remove_fourth(self): + """Remove mysql-innodb-cluster node. + + We start with a four node full cluster, remove one, down to a three + node full cluster. + """ + logging.info("Wait till model is idle ...") + zaza.model.block_until_all_units_idle() + + leader, nons = self.get_leaders_and_non_leaders() + non_leader_unit = zaza.model.get_unit_from_name(nons[0]) + zaza.model.destroy_unit(self.application_name, nons[0]) + + logging.info("Wait till model is idle ...") + zaza.model.block_until_all_units_idle() + + logging.info("Scale in test: back down to three") + zaza.model.wait_for_application_states(states=self.states) + + logging.info( + "Removing old unit from cluster: {} " + .format(non_leader_unit.public_address)) + action = zaza.model.run_action( + leader, + "remove-instance", + action_params={ + "address": non_leader_unit.public_address, + "force": True}) + assert action.data.get("results") is not None, ( + "Remove instance action failed: No results: {}" + .format(action.data)) From ec40306f7b4c94409d52ce9c6d8104ede0ee7d23 Mon Sep 17 00:00:00 2001 From: Alex Kavanagh Date: Tue, 19 May 2020 20:08:33 +0100 Subject: [PATCH 037/140] Improve detection of removing and adding relation --- zaza/openstack/charm_tests/ceph/mon/tests.py | 66 ++++++++++++++++++-- zaza/openstack/charm_tests/cinder/tests.py | 4 +- 2 files changed, 63 insertions(+), 7 deletions(-) diff --git a/zaza/openstack/charm_tests/ceph/mon/tests.py b/zaza/openstack/charm_tests/ceph/mon/tests.py index 63c4033..4258a36 100644 --- a/zaza/openstack/charm_tests/ceph/mon/tests.py +++ b/zaza/openstack/charm_tests/ceph/mon/tests.py @@ -86,11 +86,19 @@ class CinderCephMonTest(test_utils.OpenStackBaseTest): logging.info("Removing ceph-mon:client <-> cinder-ceph:ceph relation") zaza.model.remove_relation( "ceph-mon", "ceph-mon:client", "cinder-ceph:ceph") - zaza.model.wait_for_agent_status() - logging.info("Wait till model is idle ...") - zaza.model.block_until_all_units_idle() + # zaza.model.wait_for_agent_status() + logging.info("Wait till relation is removed...") + ceph_mon_units = zaza.model.get_units("ceph-mon", + model_name=self.model_name) + conditions = [ + invert_condition( + does_relation_exist( + u.name, "ceph-mon", "cinder-ceph", "ceph", + self.model_name)) + for u in ceph_mon_units] + zaza.model.block_until(*conditions) - # check each unit after to breaking relation + logging.info("Checking each unit after breaking relation...") for unit in units: dir_list = directory_listing(unit.name, "/etc/ceph") if 'ceph.conf' not in dir_list: @@ -105,12 +113,60 @@ class CinderCephMonTest(test_utils.OpenStackBaseTest): logging.info("Restoring ceph-mon:client <-> cinder-ceph:ceph relation") zaza.model.add_relation( "ceph-mon", "ceph-mon:client", "cinder-ceph:ceph") - zaza.model.wait_for_agent_status() + conditions = [ + does_relation_exist( + u.name, "ceph-mon", "cinder-ceph", "ceph", self.model_name) + for u in ceph_mon_units] logging.info("Wait till model is idle ...") + zaza.model.block_until(*conditions) zaza.model.block_until_all_units_idle() logging.info("... Done.") +def does_relation_exist(unit_name, + application_name, + remote_application_name, + remote_interface_name, + model_name): + """For use in async blocking function, return True if it exists. + + :param unit_name: the unit (by name) that to check on. + :type unit_name: str + :param application_name: Name of application on this side of relation + :type application_name: str + :param remote_application_name: the relation name at that unit to check for + :type relation_application_name: str + :param remote_interface_name: the interface name at that unit to check for + :type relation_interface_name: str + :param model_name: the model to check on + :type model_name: str + :returns: Corouting that returns True if the relation was found + :rtype: Coroutine[[], boolean] + """ + async def _async_does_relation_exist_closure(): + async with zaza.model.run_in_model(model_name) as model: + spec = "{}:{}".format( + remote_application_name, remote_interface_name) + for rel in model.applications[application_name].relations: + if rel.matches(spec): + return True + return False + return _async_does_relation_exist_closure + + +def invert_condition(async_condition): + """Invert the condition provided so it can be provided to the blocking fn. + + :param async_condition: the async callable that is the test + :type async_condition: Callable[] + :returns: Corouting that returns not of the result of a the callable + :rtype: Coroutine[[], bool] + """ + async def _async_invert_condition_closure(): + return not(await async_condition()) + return _async_invert_condition_closure + + def run_commands(unit_name, commands): """Run commands on unit. diff --git a/zaza/openstack/charm_tests/cinder/tests.py b/zaza/openstack/charm_tests/cinder/tests.py index 2648538..4cbf7c0 100644 --- a/zaza/openstack/charm_tests/cinder/tests.py +++ b/zaza/openstack/charm_tests/cinder/tests.py @@ -24,7 +24,6 @@ import zaza.openstack.utilities.openstack as openstack_utils import zaza.openstack.charm_tests.glance.setup as glance_setup from tenacity import ( - RetryError, Retrying, stop_after_attempt, wait_exponential, @@ -57,7 +56,8 @@ class CinderTests(test_utils.OpenStackBaseTest): wait=wait_exponential(multiplier=1, min=2, max=60)): with attempt: volumes = list(cls.cinder_client.volumes.list()) - snapped_volumes = [v for v in volumes if v.name.endswith("-from-snap")] + snapped_volumes = [v for v in volumes + if v.name.endswith("-from-snap")] if snapped_volumes: logging.info("Removing volumes from snapshot") cls._remove_volumes(snapped_volumes) From 51bce56f9edcfe62671e6ba723624ad12496063b Mon Sep 17 00:00:00 2001 From: Dmitrii Shcherbakov Date: Wed, 20 May 2020 00:35:30 +0300 Subject: [PATCH 038/140] Skip test_921_remove_and_add_unit Due to test stability issues we need to skip it and work on it after release. --- zaza/openstack/charm_tests/rabbitmq_server/tests.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/zaza/openstack/charm_tests/rabbitmq_server/tests.py b/zaza/openstack/charm_tests/rabbitmq_server/tests.py index d0d2306..bbb6c3b 100644 --- a/zaza/openstack/charm_tests/rabbitmq_server/tests.py +++ b/zaza/openstack/charm_tests/rabbitmq_server/tests.py @@ -18,6 +18,7 @@ import json import logging import time import uuid +import unittest import juju import tenacity @@ -354,6 +355,10 @@ class RmqTests(test_utils.OpenStackBaseTest): def _retry_check_unit_cluster_nodes(self, u, unit_node_names): return rmq_utils.check_unit_cluster_nodes(u, unit_node_names) + @unittest.skip( + "Skipping as a significant rework is required, see" + "https://github.com/openstack-charmers/zaza-openstack-tests/issues/290" + ) def test_921_remove_and_add_unit(self): """Test if unit cleans up when removed from Rmq cluster. From 28924d29090c0ac2247fea6fb1a79a73227d8145 Mon Sep 17 00:00:00 2001 From: Marco Silva Date: Wed, 20 May 2020 13:05:43 +0100 Subject: [PATCH 039/140] Move mysqldump from MySQLInnoDBClusterTests to MySQLCommonTests Moved mysqldump function. Added check for percona-cluster application on the function, to avoid breaking checks on non-percona applications. --- zaza/openstack/charm_tests/mysql/tests.py | 30 +++++++---------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/zaza/openstack/charm_tests/mysql/tests.py b/zaza/openstack/charm_tests/mysql/tests.py index fb1ac61..61c37e0 100644 --- a/zaza/openstack/charm_tests/mysql/tests.py +++ b/zaza/openstack/charm_tests/mysql/tests.py @@ -83,6 +83,13 @@ class MySQLCommonTests(MySQLBaseTest): _db = "keystone" _file_key = "mysqldump-file" logging.info("Execute mysqldump action") + # Need to change strict mode to be able to dump database + if self.application_name == "percona-cluster": + action = zaza.model.run_action_on_leader( + self.application_name, + "set-pxc-strict-mode", + action_params={"mode": "MASTER"}) + action = zaza.model.run_action_on_leader( self.application, "mysqldump", @@ -283,10 +290,9 @@ class PerconaClusterColdStartTest(PerconaClusterBaseTest): After bootstrapping a non-leader node, notify bootstrapped on the leader node. """ - _machines = list( + _machines = sorted( juju_utils.get_machine_uuids_for_application(self.application)) # Stop Nodes - _machines.sort() # Avoid hitting an update-status hook logging.debug("Wait till model is idle ...") zaza.model.block_until_all_units_idle() @@ -439,23 +445,6 @@ class MySQLInnoDBClusterTests(MySQLCommonTests): .format(action.data)) logging.info("Passed cluster-status action test.") - def test_110_mysqldump(self): - """Backup mysql. - - Run the mysqldump action. - """ - _db = "keystone" - _file_key = "mysqldump-file" - logging.info("Execute mysqldump action") - action = zaza.model.run_action_on_leader( - self.application, - "mysqldump", - action_params={"databases": _db}) - _results = action.data["results"] - assert _db in _results[_file_key], ( - "Mysqldump action failed: {}".format(action.data)) - logging.info("Passed mysqldump action test.") - def test_120_set_cluster_option(self): """Set cluster option. @@ -504,10 +493,9 @@ class MySQLInnoDBClusterColdStartTest(MySQLBaseTest): After a cold start, reboot cluster from complete outage. """ - _machines = list( + _machines = sorted( juju_utils.get_machine_uuids_for_application(self.application)) # Stop Nodes - _machines.sort() # Avoid hitting an update-status hook logging.debug("Wait till model is idle ...") zaza.model.block_until_all_units_idle() From e347f0cb7a7e88eb959a90c21ddf9f891185126c Mon Sep 17 00:00:00 2001 From: David Ames Date: Tue, 19 May 2020 09:19:44 -0700 Subject: [PATCH 040/140] Handle reboot_from_complete_outage for mysql Test updates to re-enable reboot_from_complete_outage testing on mysql-innodb-cluster. --- zaza/openstack/charm_tests/mysql/tests.py | 24 ++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/zaza/openstack/charm_tests/mysql/tests.py b/zaza/openstack/charm_tests/mysql/tests.py index 5088010..4ed3cd2 100644 --- a/zaza/openstack/charm_tests/mysql/tests.py +++ b/zaza/openstack/charm_tests/mysql/tests.py @@ -558,17 +558,24 @@ class MySQLInnoDBClusterColdStartTest(MySQLBaseTest): self.resolve_update_status_errors() zaza.model.block_until_all_units_idle() - logging.debug("Wait for application states ...") + logging.debug("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") - states = {self.application: { - "workload-status": "blocked", - "workload-status-message": - "MySQL InnoDB Cluster not healthy: None"}} + 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("Execute reboot-cluster-from-complete-outage " @@ -580,8 +587,11 @@ class MySQLInnoDBClusterColdStartTest(MySQLBaseTest): unit.entity_id, "reboot-cluster-from-complete-outage", action_params={}) - if action.data["results"].get("outcome"): + if "Success" in action.data["results"].get("outcome"): break + else: + loggging.info(action.data["results"].get("output")) + assert "Success" in action.data["results"]["outcome"], ( "Reboot cluster from complete outage action failed: {}" .format(action.data)) @@ -734,7 +744,7 @@ class MySQLInnoDBClusterScaleTest(MySQLBaseTest): leader_unit = zaza.model.get_unit_from_name(leader) zaza.model.destroy_unit(self.application_name, leader) - logging.info("Wait units are waiting ...") + logging.info("Wait until unit is in waiting state ...") zaza.model.block_until_unit_wl_status(nons[0], "waiting") logging.info("Wait till model is idle ...") From 4802e632bdf5c8ca7562bddc045e1c199a5b1e2d Mon Sep 17 00:00:00 2001 From: David Ames Date: Wed, 20 May 2020 15:05:37 -0700 Subject: [PATCH 041/140] lint fix --- 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 4ed3cd2..44fc98f 100644 --- a/zaza/openstack/charm_tests/mysql/tests.py +++ b/zaza/openstack/charm_tests/mysql/tests.py @@ -590,7 +590,7 @@ class MySQLInnoDBClusterColdStartTest(MySQLBaseTest): if "Success" in action.data["results"].get("outcome"): break else: - loggging.info(action.data["results"].get("output")) + logging.info(action.data["results"].get("output")) assert "Success" in action.data["results"]["outcome"], ( "Reboot cluster from complete outage action failed: {}" From c34f2b2a545ab79cb61ed5e9b43ff02e47f76163 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Tue, 26 May 2020 07:45:54 +0000 Subject: [PATCH 042/140] Enable guest restart test on ussuri As documented in the 20.05 release note the guest restart function of masakari is currently supported on ussuri+ so enable the test for bionic-ussuri+ --- zaza/openstack/charm_tests/masakari/tests.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/zaza/openstack/charm_tests/masakari/tests.py b/zaza/openstack/charm_tests/masakari/tests.py index 80390f5..c2a3d85 100644 --- a/zaza/openstack/charm_tests/masakari/tests.py +++ b/zaza/openstack/charm_tests/masakari/tests.py @@ -39,6 +39,7 @@ class MasakariTest(test_utils.OpenStackBaseTest): def setUpClass(cls): """Run class setup for running tests.""" super(MasakariTest, cls).setUpClass() + 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() cls.nova_client = openstack_utils.get_nova_session_client( @@ -169,8 +170,12 @@ class MasakariTest(test_utils.OpenStackBaseTest): zaza.openstack.configure.masakari.enable_hosts() def test_instance_restart_on_fail(self): - """Test singlee guest crash and recovery.""" - raise unittest.SkipTest("Bug #1866638") + """Test single guest crash and recovery.""" + if self.current_release < openstack_utils.get_os_release( + 'bionic_ussuri'): + raise unittest.SkipTest( + "Not supported on {}. Bug #1866638".format( + self.current_release)) vm_name = 'zaza-test-instance-failover' vm = self.ensure_guest(vm_name) _, unit_name = self.get_guests_compute_info(vm_name) @@ -198,6 +203,6 @@ class MasakariTest(test_utils.OpenStackBaseTest): unit_name, vm.id, model_name=self.model_name) - logging.info('{} pid is now {}'.format(vm_name, guest_pid)) + logging.info('{} pid is now {}'.format(vm_name, new_guest_pid)) assert new_guest_pid and new_guest_pid != guest_pid, ( "Restart failed or never happened") From 924b95b47b219bfb4ab8d640a371b79e17287d8d Mon Sep 17 00:00:00 2001 From: Alex Kavanagh Date: Tue, 26 May 2020 14:23:16 +0100 Subject: [PATCH 043/140] Unpin juju (libjuju) from < 2.8 python-libjuju was broken at 2.8 and thus zaza and zaza-openstack-tests needed to be pinned to < 2.8. This patch releases that so that the latest versions of libjuju are used in testing. This may get pinned again before the next release window. --- requirements.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 1f685cf..fa0e460 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ aiounittest async_generator boto3 -juju<2.8.0 +juju juju_wait PyYAML<=4.2,>=3.0 flake8>=2.2.4 diff --git a/setup.py b/setup.py index 3e781da..6d08832 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ install_require = [ 'cryptography', 'hvac<0.7.0', 'jinja2', - 'juju<2.8.0', + 'juju', 'juju-wait', 'lxml', 'PyYAML', From 8434827f5c3849a5740bf1d90e6176d40e9c3318 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Wed, 27 May 2020 10:25:49 +0000 Subject: [PATCH 044/140] Add methods for creating pre-deploy certs --- zaza/openstack/configure/pre_deploy_certs.py | 76 ++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 zaza/openstack/configure/pre_deploy_certs.py diff --git a/zaza/openstack/configure/pre_deploy_certs.py b/zaza/openstack/configure/pre_deploy_certs.py new file mode 100644 index 0000000..11ec709 --- /dev/null +++ b/zaza/openstack/configure/pre_deploy_certs.py @@ -0,0 +1,76 @@ +"""Module to setup pre-deploy TLS certs.""" + +import ipaddress +import itertools +import base64 +import os + +import zaza.openstack.utilities.cert + +ISSUER_NAME = 'OSCI' + + +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. + + 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. + """ + (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() + # 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. + # + # - 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): + 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() + + +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. + + 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 + 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. + """ + (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() + for vip_name, vip_ip in os.environ.items(): + if vip_name.startswith('OS_VIP'): + (key, cert) = zaza.openstack.utilities.cert.generate_cert( + '*.serverstack', + alternative_names=[vip_ip], + issuer_name=ISSUER_NAME, + signing_key=cakey) + os.environ[ + '{}_KEY'.format(vip_name)] = base64.b64encode(key).decode() + os.environ[ + '{}_CERT'.format(vip_name)] = base64.b64encode(cert).decode() From 362be92006259f6dfaeb1c29b57c015772935446 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Thu, 28 May 2020 09:19:12 +0000 Subject: [PATCH 045/140] Retrieve ssl-ca from vault when using vault api If the ssl-{key,chain,ca} charm config option have been set than retrieve the ssl-ca from the vault charm and use it when making called to the vault api. --- zaza/openstack/charm_tests/vault/setup.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/zaza/openstack/charm_tests/vault/setup.py b/zaza/openstack/charm_tests/vault/setup.py index 21c3793..18f617d 100644 --- a/zaza/openstack/charm_tests/vault/setup.py +++ b/zaza/openstack/charm_tests/vault/setup.py @@ -14,6 +14,7 @@ """Run configuration phase.""" +import base64 import functools import requests import tempfile @@ -27,6 +28,22 @@ import zaza.openstack.utilities.generic import zaza.utilities.juju as juju_utils +def get_cacert_file(): + """Retrieve CA cert used for vault EP and write to file. + + :returns: Path to file with CA cert used for Vault EPs + :rtype: str + """ + cacert_file = None + vault_config = zaza.model.get_application_config('vault') + cacert_b64 = vault_config['ssl-ca']['value'] + if cacert_b64: + with tempfile.NamedTemporaryFile(mode='wb', delete=False) as fp: + fp.write(base64.b64decode(cacert_b64)) + cacert_file = fp.name + return cacert_file + + def basic_setup(cacert=None, unseal_and_authorize=False): """Run basic setup for vault tests. @@ -35,6 +52,7 @@ def basic_setup(cacert=None, unseal_and_authorize=False): :param unseal_and_authorize: Whether to unseal and authorize vault. :type unseal_and_authorize: bool """ + cacert = cacert or get_cacert_file() vault_svc = vault_utils.VaultFacade(cacert=cacert) if unseal_and_authorize: vault_svc.unseal() @@ -47,6 +65,7 @@ def basic_setup_and_unseal(cacert=None): :param cacert: Path to CA cert used for vaults api cert. :type cacert: str """ + cacert = cacert or get_cacert_file() vault_svc = vault_utils.VaultFacade(cacert=cacert) vault_svc.unseal() for unit in zaza.model.get_units('vault'): From 2f6d6be3ef2b360c1d0fd1f5d5bc9d2a6a12f89e Mon Sep 17 00:00:00 2001 From: Liam Young Date: Thu, 28 May 2020 09:35:25 +0000 Subject: [PATCH 046/140] Update doc strings --- zaza/openstack/charm_tests/vault/setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zaza/openstack/charm_tests/vault/setup.py b/zaza/openstack/charm_tests/vault/setup.py index 18f617d..0709162 100644 --- a/zaza/openstack/charm_tests/vault/setup.py +++ b/zaza/openstack/charm_tests/vault/setup.py @@ -29,9 +29,9 @@ import zaza.utilities.juju as juju_utils def get_cacert_file(): - """Retrieve CA cert used for vault EP and write to file. + """Retrieve CA cert used for vault endpoints and write to file. - :returns: Path to file with CA cert used for Vault EPs + :returns: Path to file with CA cert. :rtype: str """ cacert_file = None From 24306b8880a0009b5f9bc6ac89584e6973fb3aea Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Thu, 28 May 2020 10:32:07 +0000 Subject: [PATCH 047/140] Fix get_machines_for_applications for subordinates In the case where we have a subordinate charm, libjuju juju status on the unit returns an empty dictionary, therefore the existing None check would fail and the subordinate status would never get set. The result being subordinates not included in the returned Iterator. --- zaza/openstack/utilities/juju.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zaza/openstack/utilities/juju.py b/zaza/openstack/utilities/juju.py index 80dd69b..b3e54bc 100644 --- a/zaza/openstack/utilities/juju.py +++ b/zaza/openstack/utilities/juju.py @@ -96,7 +96,7 @@ def get_machines_for_application(application, model_name=None): # libjuju juju status no longer has units for subordinate charms # Use the application it is subordinate-to to find machines - if status.get("units") is None and status.get("subordinate-to"): + if not status.get("units") and status.get("subordinate-to"): status = get_application_status(status.get("subordinate-to")[0], model_name=model_name) From 7b9b81696720497284ec800296717f359a2fe85a Mon Sep 17 00:00:00 2001 From: Liam Young Date: Sat, 22 Feb 2020 19:16:03 +0000 Subject: [PATCH 048/140] Redumentary tempest support --- zaza/openstack/charm_tests/glance/setup.py | 16 +++ zaza/openstack/charm_tests/tempest/setup.py | 134 ++++++++++++++++++ .../tempest/templates/tempest_v3.py | 95 +++++++++++++ 3 files changed, 245 insertions(+) create mode 100644 zaza/openstack/charm_tests/tempest/setup.py create mode 100644 zaza/openstack/charm_tests/tempest/templates/tempest_v3.py diff --git a/zaza/openstack/charm_tests/glance/setup.py b/zaza/openstack/charm_tests/glance/setup.py index c992917..0a920c2 100644 --- a/zaza/openstack/charm_tests/glance/setup.py +++ b/zaza/openstack/charm_tests/glance/setup.py @@ -18,6 +18,7 @@ import logging import zaza.openstack.utilities.openstack as openstack_utils CIRROS_IMAGE_NAME = "cirros" +CIRROS_ALT_IMAGE_NAME = "cirros_alt" LTS_RELEASE = "bionic" LTS_IMAGE_NAME = "bionic" @@ -77,6 +78,21 @@ def add_cirros_image(glance_client=None, image_name=None): image_name=image_name) +def add_cirros_alt_image(glance_client=None, image_name=None): + """Add a cirros image to the current deployment. + + :param glance: Authenticated glanceclient + :type glance: glanceclient.Client + :param image_name: Label for the image in glance + :type image_name: str + """ + image_name = image_name or CIRROS_ALT_IMAGE_NAME + image_url = openstack_utils.find_cirros_image(arch='x86_64') + add_image(image_url, + glance_client=glance_client, + image_name=image_name) + + def add_lts_image(glance_client=None, image_name=None, release=None): """Add an Ubuntu LTS image to the current deployment. diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py new file mode 100644 index 0000000..c09e61f --- /dev/null +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -0,0 +1,134 @@ +# 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 configuring tempest.""" +import urllib.parse +import os + +import zaza.model +import zaza.openstack.utilities.openstack as openstack_utils +import zaza.openstack.charm_tests.glance.setup as glance_setup +import zaza.openstack.charm_tests.tempest.templates.tempest_v3 as tempest_v3 + +SETUP_ENV_VARS = [ + 'OS_TEST_GATEWAY', + 'OS_TEST_CIDR_EXT', + 'OS_TEST_FIP_RANGE', + 'OS_TEST_NAMESERVER', + 'OS_TEST_CIDR_PRIV', + 'OS_TEST_SWIFT_IP', +] + + +def get_app_access_ip(application_name): + try: + app_config = zaza.model.get_application_config(application_name) + except KeyError: + return '' + vip = app_config.get("vip").get("value") + if vip: + ip = vip + else: + unit = zaza.model.get_units(application_name)[0] + ip = unit.public_address + return ip + + +def add_application_ips(ctxt): + ctxt['keystone'] = get_app_access_ip('keystone') + ctxt['dashboard'] = get_app_access_ip('openstack-dashboard') + ctxt['ncc'] = get_app_access_ip('nova-cloud-controller') + + +def add_neutron_config(ctxt, keystone_session): + neutron_client = openstack_utils.get_neutron_session_client( + keystone_session) + for net in neutron_client.list_networks()['networks']: + if net['name'] == 'ext_net': + ctxt['ext_net'] = net['id'] + break + for router in neutron_client.list_routers()['routers']: + if router['name'] == 'provider-router': + ctxt['provider_router_id'] = router['id'] + break + + +def add_glance_config(ctxt, keystone_session): + glance_client = openstack_utils.get_glance_session_client( + keystone_session) + image = openstack_utils.get_images_by_name( + glance_client, glance_setup.CIRROS_IMAGE_NAME) + image_alt = openstack_utils.get_images_by_name( + glance_client, glance_setup.CIRROS_ALT_IMAGE_NAME) + if image: + ctxt['image_id'] = image[0].id + if image_alt: + ctxt['image_alt_id'] = image_alt[0].id + + +def add_keystone_config(ctxt, keystone_session): + keystone_client = openstack_utils.get_keystone_session_client( + keystone_session) + for domain in keystone_client.domains.list(): + if domain.name == 'admin_domain': + ctxt['default_domain_id'] = domain.id + break + + +def add_environment_var_config(ctxt): + for var in SETUP_ENV_VARS: + value = os.environ.get(var) + if value: + ctxt[var.lower()] = value + else: + raise ValueError( + ("Environment variables {} must all be set to run this" + " test").format(', '.join(SETUP_ENV_VARS))) + + +def add_access_protocol(ctxt): + overcloud_auth = openstack_utils.get_overcloud_auth() + ctxt['proto'] = urllib.parse.urlparse(overcloud_auth['OS_AUTH_URL']).scheme + + +def get_tempest_context(): + keystone_session = openstack_utils.get_overcloud_keystone_session() + ctxt = {} + add_application_ips(ctxt) + add_neutron_config(ctxt, keystone_session) + add_glance_config(ctxt, keystone_session) + add_keystone_config(ctxt, keystone_session) + add_environment_var_config(ctxt) + add_access_protocol(ctxt) + return ctxt + + +def render_tempest_config(target_file, ctxt, tempest_template): + with open(target_file, 'w') as f: + f.write(tempest_template.file_contents.format(**ctxt)) + + +def setup_tempest(tempest_template): + try: + os.mkdir('tempest') + except FileExistsError: + pass + render_tempest_config( + 'tempest/tempest.conf', + get_tempest_context(), + tempest_template) + + +def tempest_keystone_v3(): + setup_tempest(tempest_v3) diff --git a/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py b/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py new file mode 100644 index 0000000..9e4727a --- /dev/null +++ b/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py @@ -0,0 +1,95 @@ +file_contents = """ +[DEFAULT] +debug = true +use_stderr = false +log_file = tempest.log + +[auth] +test_accounts_file = accounts.yaml +default_credentials_domain_name = admin_domain +admin_username = admin +admin_project_name = admin +admin_password = openstack +admin_domain_name = admin_domain + +[compute] +image_ref = {image_id} +image_ref_alt = {image_alt_id} +flavor_ref = 7 +flavor_ref_alt = 8 +min_compute_nodes = 3 + +# TODO: review this as its release specific +# min_microversion = 2.2 +# max_microversion = latest + +[compute-feature-enabled] +console_output = true +resize = true +live_migration = true +block_migration_for_live_migration = true +attach_encrypted_volume = false + +[identity] +uri = {proto}://{keystone}:5000/v2.0 +uri_v3 = {proto}://{keystone}:5000/v3 +auth_version = v3 +admin_role = Admin +region = RegionOne +default_domain_id = {default_domain_id} +admin_domain_scope = true + +[identity-feature-enabled] +api_v2 = false +api_v3 = true + +[image] +http_image = http://{os_test_swift_ip}:80/swift/v1/images/cirros-0.3.4-x86_64-uec.tar.gz + +[network] +project_network_cidr = {os_test_cidr_priv} +public_network_id = {ext_net} +dns_servers = {os_test_nameserver} +project_networks_reachable = false + +[network-feature-enabled] +ipv6 = false + +[orchestration] +stack_owner_role = Admin +instance_type = m1.small +keypair_name = testkey + +[oslo_concurrency] +lock_path = /tmp + +[scenario] +img_dir = /home/ubuntu/images +img_file = cirros-0.3.4-x86_64-disk.img +img_container_format = bare +img_disk_format = qcow2 + +[validation] +run_validation = true +image_ssh_user = cirros + +[service_available] +ceilometer = true +cinder = true +glance = true +heat = true +horizon = true +ironic = false +neutron = true +nova = true +sahara = false +swift = true +trove = false +zaqar = false + +[volume] +backend_names = cinder-ceph +storage_protocol = ceph + +[volume-feature-enabled] +backup = false""" From 1724e482a1b67a4bbf8b032d3a54782d6959b658 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Sat, 22 Feb 2020 19:49:45 +0000 Subject: [PATCH 049/140] Tempest setup --- zaza/openstack/charm_tests/tempest/setup.py | 24 +++++++++++++++---- .../charm_tests/tempest/templates/accounts.py | 8 +++++++ 2 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 zaza/openstack/charm_tests/tempest/templates/accounts.py diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py index c09e61f..4bd8c4f 100644 --- a/zaza/openstack/charm_tests/tempest/setup.py +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -15,11 +15,13 @@ """Code for configuring tempest.""" import urllib.parse import os +import subprocess import zaza.model import zaza.openstack.utilities.openstack as openstack_utils import zaza.openstack.charm_tests.glance.setup as glance_setup import zaza.openstack.charm_tests.tempest.templates.tempest_v3 as tempest_v3 +import zaza.openstack.charm_tests.tempest.templates.accounts as accounts SETUP_ENV_VARS = [ 'OS_TEST_GATEWAY', @@ -119,16 +121,30 @@ def render_tempest_config(target_file, ctxt, tempest_template): f.write(tempest_template.file_contents.format(**ctxt)) -def setup_tempest(tempest_template): +def setup_tempest(tempest_template, accounts_template): try: - os.mkdir('tempest') + os.makedirs('tempest/etc/') except FileExistsError: pass render_tempest_config( - 'tempest/tempest.conf', + 'tempest/etc/tempest.conf', get_tempest_context(), tempest_template) + render_tempest_config( + 'tempest/etc/accounts.yaml', + get_tempest_context(), + accounts_template) def tempest_keystone_v3(): - setup_tempest(tempest_v3) + setup_tempest(tempest_v3, accounts) + + +def clone_tempest(): + if not os.path.isdir('tempest'): + subprocess.check_call( + [ + 'git', + 'clone', + 'https://opendev.org/openstack/tempest', + 'tempest']) diff --git a/zaza/openstack/charm_tests/tempest/templates/accounts.py b/zaza/openstack/charm_tests/tempest/templates/accounts.py new file mode 100644 index 0000000..3e5329d --- /dev/null +++ b/zaza/openstack/charm_tests/tempest/templates/accounts.py @@ -0,0 +1,8 @@ +file_contents = """ +- username: 'demo' + tenant_name: 'demo' + password: 'pass' +- username: 'alt_demo' + tenant_name: 'alt_demo' + password: 'secret' +""" From 6347bb707f17fa7ec542f53dc39213b300e05951 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Mon, 24 Feb 2020 10:34:48 +0000 Subject: [PATCH 050/140] More updates --- zaza/openstack/charm_tests/glance/setup.py | 16 ---- .../openstack/charm_tests/tempest/__init__.py | 15 ++++ zaza/openstack/charm_tests/tempest/setup.py | 81 ++++++++++++++++++- .../charm_tests/tempest/templates/__init__.py | 15 ++++ .../tempest/templates/tempest_v3.py | 14 ++-- zaza/openstack/charm_tests/tempest/tests.py | 14 ++++ 6 files changed, 128 insertions(+), 27 deletions(-) create mode 100644 zaza/openstack/charm_tests/tempest/__init__.py create mode 100644 zaza/openstack/charm_tests/tempest/templates/__init__.py create mode 100644 zaza/openstack/charm_tests/tempest/tests.py diff --git a/zaza/openstack/charm_tests/glance/setup.py b/zaza/openstack/charm_tests/glance/setup.py index 0a920c2..c992917 100644 --- a/zaza/openstack/charm_tests/glance/setup.py +++ b/zaza/openstack/charm_tests/glance/setup.py @@ -18,7 +18,6 @@ import logging import zaza.openstack.utilities.openstack as openstack_utils CIRROS_IMAGE_NAME = "cirros" -CIRROS_ALT_IMAGE_NAME = "cirros_alt" LTS_RELEASE = "bionic" LTS_IMAGE_NAME = "bionic" @@ -78,21 +77,6 @@ def add_cirros_image(glance_client=None, image_name=None): image_name=image_name) -def add_cirros_alt_image(glance_client=None, image_name=None): - """Add a cirros image to the current deployment. - - :param glance: Authenticated glanceclient - :type glance: glanceclient.Client - :param image_name: Label for the image in glance - :type image_name: str - """ - image_name = image_name or CIRROS_ALT_IMAGE_NAME - image_url = openstack_utils.find_cirros_image(arch='x86_64') - add_image(image_url, - glance_client=glance_client, - image_name=image_name) - - def add_lts_image(glance_client=None, image_name=None, release=None): """Add an Ubuntu LTS image to the current deployment. diff --git a/zaza/openstack/charm_tests/tempest/__init__.py b/zaza/openstack/charm_tests/tempest/__init__.py new file mode 100644 index 0000000..ed3be2f --- /dev/null +++ b/zaza/openstack/charm_tests/tempest/__init__.py @@ -0,0 +1,15 @@ +# 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. + +"""Collection of code for setting up and using tempest.""" diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py index 4bd8c4f..3f0a066 100644 --- a/zaza/openstack/charm_tests/tempest/setup.py +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -18,11 +18,15 @@ import os import subprocess import zaza.model +import zaza.utilities.deployment_env as deployment_env import zaza.openstack.utilities.openstack as openstack_utils import zaza.openstack.charm_tests.glance.setup as glance_setup import zaza.openstack.charm_tests.tempest.templates.tempest_v3 as tempest_v3 import zaza.openstack.charm_tests.tempest.templates.accounts as accounts +import keystoneauth1 +import novaclient + SETUP_ENV_VARS = [ 'OS_TEST_GATEWAY', 'OS_TEST_CIDR_EXT', @@ -31,6 +35,9 @@ SETUP_ENV_VARS = [ 'OS_TEST_CIDR_PRIV', 'OS_TEST_SWIFT_IP', ] +TEMPEST_FLAVOR_NAME = 'm1.tempest' +TEMPEST_ALT_FLAVOR_NAME = 'm2.tempest' +TEMPEST_CIRROS_ALT_IMAGE_NAME = 'cirros_alt' def get_app_access_ip(application_name): @@ -53,6 +60,16 @@ def add_application_ips(ctxt): ctxt['ncc'] = get_app_access_ip('nova-cloud-controller') +def add_nova_config(ctxt, keystone_session): + nova_client = openstack_utils.get_nova_session_client( + keystone_session) + for flavor in nova_client.flavors.list(): + if flavor.name == TEMPEST_FLAVOR_NAME: + ctxt['flavor_ref'] = flavor.id + if flavor.name == TEMPEST_ALT_FLAVOR_NAME: + ctxt['flavor_ref_alt'] = flavor.id + + def add_neutron_config(ctxt, keystone_session): neutron_client = openstack_utils.get_neutron_session_client( keystone_session) @@ -72,7 +89,7 @@ def add_glance_config(ctxt, keystone_session): image = openstack_utils.get_images_by_name( glance_client, glance_setup.CIRROS_IMAGE_NAME) image_alt = openstack_utils.get_images_by_name( - glance_client, glance_setup.CIRROS_ALT_IMAGE_NAME) + glance_client, TEMPEST_CIRROS_ALT_IMAGE_NAME) if image: ctxt['image_id'] = image[0].id if image_alt: @@ -89,8 +106,9 @@ def add_keystone_config(ctxt, keystone_session): def add_environment_var_config(ctxt): + deploy_env = deployment_env.get_deployment_context() for var in SETUP_ENV_VARS: - value = os.environ.get(var) + value = deploy_env.get(var) if value: ctxt[var.lower()] = value else: @@ -102,12 +120,19 @@ def add_environment_var_config(ctxt): def add_access_protocol(ctxt): overcloud_auth = openstack_utils.get_overcloud_auth() ctxt['proto'] = urllib.parse.urlparse(overcloud_auth['OS_AUTH_URL']).scheme + ctxt['admin_username'] = overcloud_auth['OS_USERNAME'] + ctxt['admin_password'] = overcloud_auth['OS_PASSWORD'] + ctxt['admin_project_name'] = overcloud_auth['OS_PROJECT_NAME'] + ctxt['admin_domain_name'] = overcloud_auth['OS_DOMAIN_NAME'] + ctxt['default_credentials_domain_name'] = overcloud_auth[ + 'OS_PROJECT_DOMAIN_NAME'] def get_tempest_context(): keystone_session = openstack_utils.get_overcloud_keystone_session() ctxt = {} add_application_ips(ctxt) + add_nova_config(ctxt, keystone_session) add_neutron_config(ctxt, keystone_session) add_glance_config(ctxt, keystone_session) add_keystone_config(ctxt, keystone_session) @@ -136,7 +161,7 @@ def setup_tempest(tempest_template, accounts_template): accounts_template) -def tempest_keystone_v3(): +def render_tempest_config_keystone_v3(): setup_tempest(tempest_v3, accounts) @@ -145,6 +170,54 @@ def clone_tempest(): subprocess.check_call( [ 'git', - 'clone', + 'clone', 'https://opendev.org/openstack/tempest', 'tempest']) + + +def add_cirros_alt_image(): + """Add a cirros image to the current deployment. + + :param glance: Authenticated glanceclient + :type glance: glanceclient.Client + :param image_name: Label for the image in glance + :type image_name: str + """ + image_url = openstack_utils.find_cirros_image(arch='x86_64') + glance_setup.add_image( + image_url, + glance_client=None, + image_name=TEMPEST_CIRROS_ALT_IMAGE_NAME) + + +def add_tempest_flavors(): + keystone_session = openstack_utils.get_overcloud_keystone_session() + nova_client = openstack_utils.get_nova_session_client( + keystone_session) + try: + nova_client.flavors.create( + name=TEMPEST_FLAVOR_NAME, + ram=256, + vcpus=1, + disk=1) + except novaclient.exceptions.Conflict: + pass + try: + nova_client.flavors.create( + name=TEMPEST_ALT_FLAVOR_NAME, + ram=512, + vcpus=1, + disk=1) + except novaclient.exceptions.Conflict: + pass + + +def add_tempest_roles(): + keystone_session = openstack_utils.get_overcloud_keystone_session() + keystone_client = openstack_utils.get_keystone_session_client( + keystone_session) + for role_name in ['Member', 'ResellerAdmin']: + try: + keystone_client.roles.create('Member') + except keystoneauth1.exceptions.http.Conflict: + pass diff --git a/zaza/openstack/charm_tests/tempest/templates/__init__.py b/zaza/openstack/charm_tests/tempest/templates/__init__.py new file mode 100644 index 0000000..1400887 --- /dev/null +++ b/zaza/openstack/charm_tests/tempest/templates/__init__.py @@ -0,0 +1,15 @@ +# 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. + +"""Collection of templates for tempest.""" diff --git a/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py b/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py index 9e4727a..ea2d88d 100644 --- a/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py +++ b/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py @@ -6,17 +6,17 @@ log_file = tempest.log [auth] test_accounts_file = accounts.yaml -default_credentials_domain_name = admin_domain -admin_username = admin -admin_project_name = admin -admin_password = openstack -admin_domain_name = admin_domain +default_credentials_domain_name = {default_credentials_domain_name} +admin_username = {admin_username} +admin_project_name = {admin_project_name} +admin_password = {admin_password} +admin_domain_name = {admin_domain_name} [compute] image_ref = {image_id} image_ref_alt = {image_alt_id} -flavor_ref = 7 -flavor_ref_alt = 8 +flavor_ref = {flavor_ref} +flavor_ref_alt = {flavor_ref_alt} min_compute_nodes = 3 # TODO: review this as its release specific diff --git a/zaza/openstack/charm_tests/tempest/tests.py b/zaza/openstack/charm_tests/tempest/tests.py new file mode 100644 index 0000000..86b4f5b --- /dev/null +++ b/zaza/openstack/charm_tests/tempest/tests.py @@ -0,0 +1,14 @@ +import zaza.charm_lifecycle.test +import tempest.cmd.main + + +class TempestTest(): + + test_runner = zaza.charm_lifecycle.test.DIRECT + + def run(self): + the_app = tempest.cmd.main.Main() + return the_app.run([ + 'run', + '--smoke', + '--config', 'tempest/etc/tempest.conf']) From e8624d3297e8f03b682dcb3403454dda5c298132 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Mon, 24 Feb 2020 15:23:16 +0000 Subject: [PATCH 051/140] Fix missing ResellerAdmin --- zaza/openstack/charm_tests/tempest/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py index 3f0a066..f1c773e 100644 --- a/zaza/openstack/charm_tests/tempest/setup.py +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -218,6 +218,6 @@ def add_tempest_roles(): keystone_session) for role_name in ['Member', 'ResellerAdmin']: try: - keystone_client.roles.create('Member') + keystone_client.roles.create(role_name) except keystoneauth1.exceptions.http.Conflict: pass From be3659c4a2ca13d3c0135cef954510285cf69964 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Tue, 3 Mar 2020 12:49:41 +0000 Subject: [PATCH 052/140] Add support for blacklist/whitelist etc --- zaza/openstack/charm_tests/tempest/tests.py | 41 +++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/zaza/openstack/charm_tests/tempest/tests.py b/zaza/openstack/charm_tests/tempest/tests.py index 86b4f5b..8880756 100644 --- a/zaza/openstack/charm_tests/tempest/tests.py +++ b/zaza/openstack/charm_tests/tempest/tests.py @@ -1,11 +1,52 @@ +import os + +import zaza +import zaza.charm_lifecycle.utils import zaza.charm_lifecycle.test import tempest.cmd.main +import tempfile class TempestTest(): test_runner = zaza.charm_lifecycle.test.DIRECT + def run(self): + charm_config = zaza.charm_lifecycle.utils.get_charm_config() + tempest_options = ['run', '--config', 'tempest/etc/tempest.conf'] + for model_alias in zaza.model.get_juju_model_aliases().keys(): + tempest_test_key = model_alias + if model_alias == zaza.charm_lifecycle.utils.DEFAULT_MODEL_ALIAS: + tempest_test_key = 'default' + config = charm_config['tests_options']['tempest'][tempest_test_key] + if config.get('regex'): + tempest_options.extend(['--regex', config.get('regex')]) + if config.get('black-regex'): + tempest_options.extend(['--black-regex', config.get('black-regex')]) + with tempfile.TemporaryDirectory() as tmpdirname: + if config.get('whitelist'): + white_file = os.path.join(tmpdirname, 'white.cfg') + with open(white_file, 'w') as f: + f.write('\n'.join(config.get('whitelist'))) + f.write('\n') + tempest_options.extend(['--whitelist-file', white_file]) + if config.get('blacklist'): + black_file = os.path.join(tmpdirname, 'black.cfg') + with open(black_file, 'w') as f: + f.write('\n'.join(config.get('blacklist'))) + f.write('\n') + tempest_options.extend(['--blacklist-file', black_file]) + print(tempest_options) + the_app = tempest.cmd.main.Main() + _exec_tempest = the_app.run(tempest_options) + if not _exec_tempest: + return False + return True + +class TempestSmokeTest(): + + test_runner = zaza.charm_lifecycle.test.DIRECT + def run(self): the_app = tempest.cmd.main.Main() return the_app.run([ From 182cda90138b8f46396055285462fa6ce9e8d3bd Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Wed, 4 Mar 2020 02:08:01 +0000 Subject: [PATCH 053/140] Add smoke to TempestTest as blacklist/whitelist/black-regex will be useful with it. --- zaza/openstack/charm_tests/tempest/tests.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/zaza/openstack/charm_tests/tempest/tests.py b/zaza/openstack/charm_tests/tempest/tests.py index 8880756..67ba09b 100644 --- a/zaza/openstack/charm_tests/tempest/tests.py +++ b/zaza/openstack/charm_tests/tempest/tests.py @@ -19,6 +19,8 @@ class TempestTest(): if model_alias == zaza.charm_lifecycle.utils.DEFAULT_MODEL_ALIAS: tempest_test_key = 'default' config = charm_config['tests_options']['tempest'][tempest_test_key] + if config.get('smoke'): + tempest_options.extend(['--smoke']) if config.get('regex'): tempest_options.extend(['--regex', config.get('regex')]) if config.get('black-regex'): @@ -42,14 +44,3 @@ class TempestTest(): if not _exec_tempest: return False return True - -class TempestSmokeTest(): - - test_runner = zaza.charm_lifecycle.test.DIRECT - - def run(self): - the_app = tempest.cmd.main.Main() - return the_app.run([ - 'run', - '--smoke', - '--config', 'tempest/etc/tempest.conf']) From 456b08c032fddea41ab618292542d663fb2dacbf Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Wed, 4 Mar 2020 15:16:18 +0000 Subject: [PATCH 054/140] minor cleanup --- zaza/openstack/charm_tests/tempest/setup.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py index f1c773e..d1d2ebe 100644 --- a/zaza/openstack/charm_tests/tempest/setup.py +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -168,11 +168,10 @@ def render_tempest_config_keystone_v3(): def clone_tempest(): if not os.path.isdir('tempest'): subprocess.check_call( - [ - 'git', - 'clone', - 'https://opendev.org/openstack/tempest', - 'tempest']) + ['git', + 'clone', + 'https://opendev.org/openstack/tempest', + 'tempest']) def add_cirros_alt_image(): From 4d8cd803ffb1663325e96294fc9c450e60dc25ef Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Wed, 4 Mar 2020 18:52:34 +0000 Subject: [PATCH 055/140] Convert list of (black-)regex's to a space-separated string --- zaza/openstack/charm_tests/tempest/tests.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/zaza/openstack/charm_tests/tempest/tests.py b/zaza/openstack/charm_tests/tempest/tests.py index 67ba09b..f621a8a 100644 --- a/zaza/openstack/charm_tests/tempest/tests.py +++ b/zaza/openstack/charm_tests/tempest/tests.py @@ -22,9 +22,13 @@ class TempestTest(): if config.get('smoke'): tempest_options.extend(['--smoke']) if config.get('regex'): - tempest_options.extend(['--regex', config.get('regex')]) + tempest_options.extend( + ['--regex', + ' '.join([reg for reg in config.get('regex')])]) if config.get('black-regex'): - tempest_options.extend(['--black-regex', config.get('black-regex')]) + tempest_options.extend( + ['--black-regex', + ' '.join([reg for reg in config.get('black-regex')])]) with tempfile.TemporaryDirectory() as tmpdirname: if config.get('whitelist'): white_file = os.path.join(tmpdirname, 'white.cfg') From 2eaa55bd51c078f82773d531d6be9431512c9439 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Wed, 4 Mar 2020 18:54:51 +0000 Subject: [PATCH 056/140] Drop git clone of tempest --- zaza/openstack/charm_tests/tempest/setup.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py index d1d2ebe..09d0be9 100644 --- a/zaza/openstack/charm_tests/tempest/setup.py +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -165,15 +165,6 @@ def render_tempest_config_keystone_v3(): setup_tempest(tempest_v3, accounts) -def clone_tempest(): - if not os.path.isdir('tempest'): - subprocess.check_call( - ['git', - 'clone', - 'https://opendev.org/openstack/tempest', - 'tempest']) - - def add_cirros_alt_image(): """Add a cirros image to the current deployment. From a8a6e72ef30e4786246c4c2ae9ee786cafd98057 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Thu, 5 Mar 2020 14:24:55 +0000 Subject: [PATCH 057/140] Use tempest workspace to avoid conflicts with any charm unit test .stestr config --- zaza/openstack/charm_tests/tempest/setup.py | 6 +++--- zaza/openstack/charm_tests/tempest/tests.py | 10 +++++++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py index 09d0be9..cc344ba 100644 --- a/zaza/openstack/charm_tests/tempest/setup.py +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -148,15 +148,15 @@ def render_tempest_config(target_file, ctxt, tempest_template): def setup_tempest(tempest_template, accounts_template): try: - os.makedirs('tempest/etc/') + os.makedirs('tempest_workspace/etc/') except FileExistsError: pass render_tempest_config( - 'tempest/etc/tempest.conf', + 'tempest_workspace/etc/tempest.conf', get_tempest_context(), tempest_template) render_tempest_config( - 'tempest/etc/accounts.yaml', + 'tempest_workspace/etc/accounts.yaml', get_tempest_context(), accounts_template) diff --git a/zaza/openstack/charm_tests/tempest/tests.py b/zaza/openstack/charm_tests/tempest/tests.py index f621a8a..58a31eb 100644 --- a/zaza/openstack/charm_tests/tempest/tests.py +++ b/zaza/openstack/charm_tests/tempest/tests.py @@ -12,8 +12,16 @@ class TempestTest(): test_runner = zaza.charm_lifecycle.test.DIRECT def run(self): + tempest_options = ['init', 'tempest_workspace'] + the_app = tempest.cmd.main.Main() + print(tempest_options) + _exec_tempest = the_app.run(tempest_options) + #if not _exec_tempest: + # return False + charm_config = zaza.charm_lifecycle.utils.get_charm_config() - tempest_options = ['run', '--config', 'tempest/etc/tempest.conf'] + tempest_options = ['run', '--config', 'tempest_workspace/etc/tempest.conf', + '--workspace', 'tempest_workspace'] for model_alias in zaza.model.get_juju_model_aliases().keys(): tempest_test_key = model_alias if model_alias == zaza.charm_lifecycle.utils.DEFAULT_MODEL_ALIAS: From 57594a466bfc19f42bfd23e34390639c26940363 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Thu, 5 Mar 2020 20:21:39 +0000 Subject: [PATCH 058/140] Fix the_app.run ret code checks and don't recreate workspace if already exists --- zaza/openstack/charm_tests/tempest/tests.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/zaza/openstack/charm_tests/tempest/tests.py b/zaza/openstack/charm_tests/tempest/tests.py index 58a31eb..10854e4 100644 --- a/zaza/openstack/charm_tests/tempest/tests.py +++ b/zaza/openstack/charm_tests/tempest/tests.py @@ -7,20 +7,24 @@ import tempest.cmd.main import tempfile + class TempestTest(): test_runner = zaza.charm_lifecycle.test.DIRECT def run(self): - tempest_options = ['init', 'tempest_workspace'] - the_app = tempest.cmd.main.Main() - print(tempest_options) - _exec_tempest = the_app.run(tempest_options) - #if not _exec_tempest: - # return False + tempest_workspace = 'tempst_workspace' + tempest_options = ['init', tempest_workspace] + if not os.path.isdir(tempest_workspace): + the_app = tempest.cmd.main.Main() + print(tempest_options) + _exec_tempest = the_app.run(tempest_options) + if _exec_tempest != 0: + return False charm_config = zaza.charm_lifecycle.utils.get_charm_config() - tempest_options = ['run', '--config', 'tempest_workspace/etc/tempest.conf', + tempest_options = ['run', '--config', + os.path.join(tempest_workspace, 'etc/tempest.conf'), '--workspace', 'tempest_workspace'] for model_alias in zaza.model.get_juju_model_aliases().keys(): tempest_test_key = model_alias @@ -53,6 +57,6 @@ class TempestTest(): print(tempest_options) the_app = tempest.cmd.main.Main() _exec_tempest = the_app.run(tempest_options) - if not _exec_tempest: + if _exec_tempest != 0: return False return True From eff595c6ee7d50adb8702908ec979889d33f6d82 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Mon, 9 Mar 2020 13:54:04 +0000 Subject: [PATCH 059/140] Add render_tempest_config_keystone_v2 for older deployments --- zaza/openstack/charm_tests/tempest/setup.py | 5 + .../tempest/templates/tempest_v2.py | 94 +++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 zaza/openstack/charm_tests/tempest/templates/tempest_v2.py diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py index cc344ba..2fd95f4 100644 --- a/zaza/openstack/charm_tests/tempest/setup.py +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -21,6 +21,7 @@ import zaza.model import zaza.utilities.deployment_env as deployment_env import zaza.openstack.utilities.openstack as openstack_utils import zaza.openstack.charm_tests.glance.setup as glance_setup +import zaza.openstack.charm_tests.tempest.templates.tempest_v2 as tempest_v2 import zaza.openstack.charm_tests.tempest.templates.tempest_v3 as tempest_v3 import zaza.openstack.charm_tests.tempest.templates.accounts as accounts @@ -161,6 +162,10 @@ def setup_tempest(tempest_template, accounts_template): accounts_template) +def render_tempest_config_keystone_v2(): + setup_tempest(tempest_v2, accounts) + + def render_tempest_config_keystone_v3(): setup_tempest(tempest_v3, accounts) diff --git a/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py b/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py new file mode 100644 index 0000000..3421910 --- /dev/null +++ b/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py @@ -0,0 +1,94 @@ +file_contents = """ +[DEFAULT] +debug = true +use_stderr = false +log_file = tempest.log + +[auth] +test_accounts_file = accounts.yaml +default_credentials_domain_name = {default_credentials_domain_name} +admin_username = {admin_username} +admin_project_name = {admin_project_name} +admin_password = {admin_password} +admin_domain_name = {admin_domain_name} + +[compute] +image_ref = {image_id} +image_ref_alt = {image_alt_id} +flavor_ref = {flavor_ref} +flavor_ref_alt = {flavor_ref_alt} +min_compute_nodes = 3 + +# TODO: review this as its release specific +# min_microversion = 2.2 +# max_microversion = latest + +[compute-feature-enabled] +console_output = true +resize = true +live_migration = true +block_migration_for_live_migration = true +attach_encrypted_volume = false + +[identity] +uri = {proto}://{keystone}:5000/v2.0 +auth_version = v2 +admin_role = Admin +region = RegionOne +default_domain_id = {default_domain_id} +admin_domain_scope = true + +[identity-feature-enabled] +api_v2 = true +api_v3 = false + +[image] +http_image = http://{os_test_swift_ip}:80/swift/v1/images/cirros-0.3.4-x86_64-uec.tar.gz + +[network] +project_network_cidr = {os_test_cidr_priv} +public_network_id = {ext_net} +dns_servers = {os_test_nameserver} +project_networks_reachable = false + +[network-feature-enabled] +ipv6 = false + +[orchestration] +stack_owner_role = Admin +instance_type = m1.small +keypair_name = testkey + +[oslo_concurrency] +lock_path = /tmp + +[scenario] +img_dir = /home/ubuntu/images +img_file = cirros-0.3.4-x86_64-disk.img +img_container_format = bare +img_disk_format = qcow2 + +[validation] +run_validation = true +image_ssh_user = cirros + +[service_available] +ceilometer = true +cinder = true +glance = true +heat = true +horizon = true +ironic = false +neutron = true +nova = true +sahara = false +swift = true +trove = false +zaqar = false + +[volume] +backend_names = cinder-ceph +storage_protocol = ceph + +[volume-feature-enabled] +backup = false""" From f017934c9b890a7e604107b17f63701e7a0753c0 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Mon, 9 Mar 2020 15:57:03 +0000 Subject: [PATCH 060/140] Fix typo in tempest workspace name --- zaza/openstack/charm_tests/tempest/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zaza/openstack/charm_tests/tempest/tests.py b/zaza/openstack/charm_tests/tempest/tests.py index 10854e4..1626dc5 100644 --- a/zaza/openstack/charm_tests/tempest/tests.py +++ b/zaza/openstack/charm_tests/tempest/tests.py @@ -13,7 +13,7 @@ class TempestTest(): test_runner = zaza.charm_lifecycle.test.DIRECT def run(self): - tempest_workspace = 'tempst_workspace' + tempest_workspace = 'tempest_workspace' tempest_options = ['init', tempest_workspace] if not os.path.isdir(tempest_workspace): the_app = tempest.cmd.main.Main() From 67de027d986df6c20fe6eeac7da653ebb49b8576 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Tue, 10 Mar 2020 20:53:40 +0000 Subject: [PATCH 061/140] Move tempest init to setup.py and use a local workspace path --- zaza/openstack/charm_tests/tempest/setup.py | 17 +++++++++++------ zaza/openstack/charm_tests/tempest/tests.py | 11 ++--------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py index 2fd95f4..9ffe663 100644 --- a/zaza/openstack/charm_tests/tempest/setup.py +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -15,6 +15,7 @@ """Code for configuring tempest.""" import urllib.parse import os +import shutil import subprocess import zaza.model @@ -24,6 +25,7 @@ import zaza.openstack.charm_tests.glance.setup as glance_setup import zaza.openstack.charm_tests.tempest.templates.tempest_v2 as tempest_v2 import zaza.openstack.charm_tests.tempest.templates.tempest_v3 as tempest_v3 import zaza.openstack.charm_tests.tempest.templates.accounts as accounts +import tempest.cmd.main import keystoneauth1 import novaclient @@ -148,16 +150,18 @@ def render_tempest_config(target_file, ctxt, tempest_template): def setup_tempest(tempest_template, accounts_template): - try: - os.makedirs('tempest_workspace/etc/') - except FileExistsError: - pass + tempest_workspace = 'tempest_workspace' + the_app = tempest.cmd.main.Main() + tempest_options = ['init', '--workspace-path', './.tempest/workspace.yaml', + tempest_workspace] + print(tempest_options) + _exec_tempest = the_app.run(tempest_options) render_tempest_config( - 'tempest_workspace/etc/tempest.conf', + os.path.join(tempest_workspace, 'etc/tempest.conf'), get_tempest_context(), tempest_template) render_tempest_config( - 'tempest_workspace/etc/accounts.yaml', + os.path.join(tempest_workspace, 'etc/accounts.yaml'), get_tempest_context(), accounts_template) @@ -216,3 +220,4 @@ def add_tempest_roles(): keystone_client.roles.create(role_name) except keystoneauth1.exceptions.http.Conflict: pass + diff --git a/zaza/openstack/charm_tests/tempest/tests.py b/zaza/openstack/charm_tests/tempest/tests.py index 1626dc5..a4f97e4 100644 --- a/zaza/openstack/charm_tests/tempest/tests.py +++ b/zaza/openstack/charm_tests/tempest/tests.py @@ -13,18 +13,11 @@ class TempestTest(): test_runner = zaza.charm_lifecycle.test.DIRECT def run(self): - tempest_workspace = 'tempest_workspace' - tempest_options = ['init', tempest_workspace] - if not os.path.isdir(tempest_workspace): - the_app = tempest.cmd.main.Main() - print(tempest_options) - _exec_tempest = the_app.run(tempest_options) - if _exec_tempest != 0: - return False - charm_config = zaza.charm_lifecycle.utils.get_charm_config() + tempest_workspace = 'tempest_workspace' tempest_options = ['run', '--config', os.path.join(tempest_workspace, 'etc/tempest.conf'), + '--workspace-path', './.tempest/workspace.yaml', '--workspace', 'tempest_workspace'] for model_alias in zaza.model.get_juju_model_aliases().keys(): tempest_test_key = model_alias From a1f34e2c0148fafd3686752db37d088ca737dca2 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Wed, 11 Mar 2020 14:22:27 +0000 Subject: [PATCH 062/140] Handle tempest changing to workspace directory --- zaza/openstack/charm_tests/tempest/tests.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zaza/openstack/charm_tests/tempest/tests.py b/zaza/openstack/charm_tests/tempest/tests.py index a4f97e4..0641520 100644 --- a/zaza/openstack/charm_tests/tempest/tests.py +++ b/zaza/openstack/charm_tests/tempest/tests.py @@ -49,7 +49,9 @@ class TempestTest(): tempest_options.extend(['--blacklist-file', black_file]) print(tempest_options) the_app = tempest.cmd.main.Main() + project_root = os.getcwd() _exec_tempest = the_app.run(tempest_options) + os.chdir(project_root) if _exec_tempest != 0: return False return True From 4a745e3ab0848927e2f87bafad53289faf2b535a Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Thu, 12 Mar 2020 12:58:21 +0000 Subject: [PATCH 063/140] Align tempest_v2.py with o-c-t v2 tempest.conf.template --- .../charm_tests/tempest/templates/tempest_v2.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py b/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py index 3421910..4f40d90 100644 --- a/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py +++ b/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py @@ -6,17 +6,18 @@ log_file = tempest.log [auth] test_accounts_file = accounts.yaml -default_credentials_domain_name = {default_credentials_domain_name} +default_credentials_domain_name = Default admin_username = {admin_username} -admin_project_name = {admin_project_name} +admin_project_name = admin admin_password = {admin_password} -admin_domain_name = {admin_domain_name} +admin_domain_name = Default [compute] image_ref = {image_id} image_ref_alt = {image_alt_id} flavor_ref = {flavor_ref} flavor_ref_alt = {flavor_ref_alt} +region = RegionOne min_compute_nodes = 3 # TODO: review this as its release specific @@ -35,8 +36,6 @@ uri = {proto}://{keystone}:5000/v2.0 auth_version = v2 admin_role = Admin region = RegionOne -default_domain_id = {default_domain_id} -admin_domain_scope = true [identity-feature-enabled] api_v2 = true @@ -89,6 +88,10 @@ zaqar = false [volume] backend_names = cinder-ceph storage_protocol = ceph +# NOTE(coreycb): Need to enalbe catalog_type, determined by: +# volume_version=$(openstack endpoint list -c 'Service Type' -f value | grep -m 1 volumev3 || +# openstack endpoint list -c 'Service Type' -f value | grep -m 1 volumev2) +# catalog_type = __VOLUME_VERSION__ [volume-feature-enabled] backup = false""" From aa33797879fc20143dc471f11ef3609b38d18470 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Thu, 12 Mar 2020 13:32:21 +0000 Subject: [PATCH 064/140] Updates to add_access_protocol for keystone v2 support --- zaza/openstack/charm_tests/tempest/setup.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py index 9ffe663..6d7c26e 100644 --- a/zaza/openstack/charm_tests/tempest/setup.py +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -125,10 +125,14 @@ def add_access_protocol(ctxt): ctxt['proto'] = urllib.parse.urlparse(overcloud_auth['OS_AUTH_URL']).scheme ctxt['admin_username'] = overcloud_auth['OS_USERNAME'] ctxt['admin_password'] = overcloud_auth['OS_PASSWORD'] - ctxt['admin_project_name'] = overcloud_auth['OS_PROJECT_NAME'] - ctxt['admin_domain_name'] = overcloud_auth['OS_DOMAIN_NAME'] - ctxt['default_credentials_domain_name'] = overcloud_auth[ - 'OS_PROJECT_DOMAIN_NAME'] + if overcloud_auth['API_VERSION'] == 3: + ctxt['admin_project_name'] = overcloud_auth['OS_PROJECT_NAME'] + ctxt['admin_domain_name'] = overcloud_auth['OS_DOMAIN_NAME'] + ctxt['default_credentials_domain_name'] = overcloud_auth[ + 'OS_PROJECT_DOMAIN_NAME'] + elif overcloud_auth['API_VERSION'] == 2: + #ctxt['admin_tenant_name'] = overcloud_auth['OS_TENANT_NAME'] + pass def get_tempest_context(): From af2cff3c6ccce4b6dfe605813300fdda3d36e5c8 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Fri, 13 Mar 2020 14:20:23 +0000 Subject: [PATCH 065/140] Set tempest debug to false --- zaza/openstack/charm_tests/tempest/templates/tempest_v2.py | 2 +- zaza/openstack/charm_tests/tempest/templates/tempest_v3.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py b/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py index 4f40d90..ec95577 100644 --- a/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py +++ b/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py @@ -1,6 +1,6 @@ file_contents = """ [DEFAULT] -debug = true +debug = false use_stderr = false log_file = tempest.log diff --git a/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py b/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py index ea2d88d..887b0b5 100644 --- a/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py +++ b/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py @@ -1,6 +1,6 @@ file_contents = """ [DEFAULT] -debug = true +debug = false use_stderr = false log_file = tempest.log From 82a1f1b75de8456d13fa32d405550cd98ccfd96d Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Fri, 13 Mar 2020 17:35:26 +0000 Subject: [PATCH 066/140] Fix tempest run config-file flag --- zaza/openstack/charm_tests/tempest/tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zaza/openstack/charm_tests/tempest/tests.py b/zaza/openstack/charm_tests/tempest/tests.py index 0641520..27e12ed 100644 --- a/zaza/openstack/charm_tests/tempest/tests.py +++ b/zaza/openstack/charm_tests/tempest/tests.py @@ -15,10 +15,10 @@ class TempestTest(): def run(self): charm_config = zaza.charm_lifecycle.utils.get_charm_config() tempest_workspace = 'tempest_workspace' - tempest_options = ['run', '--config', + tempest_options = ['run', '--config-file', os.path.join(tempest_workspace, 'etc/tempest.conf'), '--workspace-path', './.tempest/workspace.yaml', - '--workspace', 'tempest_workspace'] + '--workspace', tempest_workspace] for model_alias in zaza.model.get_juju_model_aliases().keys(): tempest_test_key = model_alias if model_alias == zaza.charm_lifecycle.utils.DEFAULT_MODEL_ALIAS: From 2b9de3a2b7dab0d53b5e91a11daa986f63f45f0e Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Fri, 13 Mar 2020 20:47:54 +0000 Subject: [PATCH 067/140] Updates to tempest config rendering and init --- zaza/openstack/charm_tests/tempest/setup.py | 28 +++++++++++++++++---- zaza/openstack/charm_tests/tempest/tests.py | 13 +++++++--- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py index 6d7c26e..04d4bf9 100644 --- a/zaza/openstack/charm_tests/tempest/setup.py +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -154,18 +154,36 @@ def render_tempest_config(target_file, ctxt, tempest_template): def setup_tempest(tempest_template, accounts_template): - tempest_workspace = 'tempest_workspace' + config_dir = '.tempest' + config_etc_dir = os.path.join(config_dir, 'etc') + config_etc_tempest = os.path.join(config_etc_dir, 'tempest.conf') + config_workspace_yaml = os.path.join(config_dir, 'workspace.yaml') + workspace_name = 'workspace' + workspace_dir = os.path.join(config_dir, workspace_name) + workspace_etc_dir = os.path.join(workspace_dir, 'etc') + workspace_etc_accounts = os.path.join(workspace_etc_dir, 'accounts.yaml') + workspace_etc_tempest = os.path.join(workspace_etc_dir, 'tempest.conf') the_app = tempest.cmd.main.Main() - tempest_options = ['init', '--workspace-path', './.tempest/workspace.yaml', - tempest_workspace] + + # note sure this is needed or not + if not os.path.isdir(config_dir): + os.mkdir(config_dir) + os.mkdir(config_etc_dir) + render_tempest_config( + config_etc_tempest, + get_tempest_context(), + tempest_template) + tempest_options = ['init', '--workspace-path', config_workspace_yaml, + '--name', workspace_name, workspace_dir] print(tempest_options) _exec_tempest = the_app.run(tempest_options) + # This was mising /etc/tempest/ and just going to /etc/ render_tempest_config( - os.path.join(tempest_workspace, 'etc/tempest.conf'), + workspace_etc_tempest, get_tempest_context(), tempest_template) render_tempest_config( - os.path.join(tempest_workspace, 'etc/accounts.yaml'), + workspace_etc_accounts, get_tempest_context(), accounts_template) diff --git a/zaza/openstack/charm_tests/tempest/tests.py b/zaza/openstack/charm_tests/tempest/tests.py index 27e12ed..9bc6221 100644 --- a/zaza/openstack/charm_tests/tempest/tests.py +++ b/zaza/openstack/charm_tests/tempest/tests.py @@ -14,11 +14,16 @@ class TempestTest(): def run(self): charm_config = zaza.charm_lifecycle.utils.get_charm_config() - tempest_workspace = 'tempest_workspace' + config_dir = '.tempest' + config_workspace_yaml = os.path.join(config_dir, 'workspace.yaml') + workspace_name = 'workspace' + workspace_dir = os.path.join(config_dir, workspace_name) + workspace_etc_dir = os.path.join(workspace_dir, 'etc') + workspace_etc_tempest = os.path.join(workspace_etc_dir, 'tempest.conf') tempest_options = ['run', '--config-file', - os.path.join(tempest_workspace, 'etc/tempest.conf'), - '--workspace-path', './.tempest/workspace.yaml', - '--workspace', tempest_workspace] + workspace_etc_tempest, + '--workspace-path', config_workspace_yaml, + '--workspace', workspace_name] for model_alias in zaza.model.get_juju_model_aliases().keys(): tempest_test_key = model_alias if model_alias == zaza.charm_lifecycle.utils.DEFAULT_MODEL_ALIAS: From eaca793e522c4d121b074409e5ae61d9a7428721 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Mon, 16 Mar 2020 13:56:29 +0000 Subject: [PATCH 068/140] tmp debug testing --- zaza/openstack/charm_tests/tempest/setup.py | 14 +-- .../tempest/templates/tempest_v2.py | 96 +------------------ .../tempest/templates/tempest_v3.py | 94 +----------------- 3 files changed, 9 insertions(+), 195 deletions(-) diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py index 04d4bf9..7336299 100644 --- a/zaza/openstack/charm_tests/tempest/setup.py +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -138,13 +138,13 @@ def add_access_protocol(ctxt): def get_tempest_context(): keystone_session = openstack_utils.get_overcloud_keystone_session() ctxt = {} - add_application_ips(ctxt) - add_nova_config(ctxt, keystone_session) - add_neutron_config(ctxt, keystone_session) - add_glance_config(ctxt, keystone_session) - add_keystone_config(ctxt, keystone_session) - add_environment_var_config(ctxt) - add_access_protocol(ctxt) + #add_application_ips(ctxt) + #add_nova_config(ctxt, keystone_session) + #add_neutron_config(ctxt, keystone_session) + #add_glance_config(ctxt, keystone_session) + #add_keystone_config(ctxt, keystone_session) + #add_environment_var_config(ctxt) + #add_access_protocol(ctxt) return ctxt diff --git a/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py b/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py index ec95577..aa6b555 100644 --- a/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py +++ b/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py @@ -1,97 +1,3 @@ file_contents = """ [DEFAULT] -debug = false -use_stderr = false -log_file = tempest.log - -[auth] -test_accounts_file = accounts.yaml -default_credentials_domain_name = Default -admin_username = {admin_username} -admin_project_name = admin -admin_password = {admin_password} -admin_domain_name = Default - -[compute] -image_ref = {image_id} -image_ref_alt = {image_alt_id} -flavor_ref = {flavor_ref} -flavor_ref_alt = {flavor_ref_alt} -region = RegionOne -min_compute_nodes = 3 - -# TODO: review this as its release specific -# min_microversion = 2.2 -# max_microversion = latest - -[compute-feature-enabled] -console_output = true -resize = true -live_migration = true -block_migration_for_live_migration = true -attach_encrypted_volume = false - -[identity] -uri = {proto}://{keystone}:5000/v2.0 -auth_version = v2 -admin_role = Admin -region = RegionOne - -[identity-feature-enabled] -api_v2 = true -api_v3 = false - -[image] -http_image = http://{os_test_swift_ip}:80/swift/v1/images/cirros-0.3.4-x86_64-uec.tar.gz - -[network] -project_network_cidr = {os_test_cidr_priv} -public_network_id = {ext_net} -dns_servers = {os_test_nameserver} -project_networks_reachable = false - -[network-feature-enabled] -ipv6 = false - -[orchestration] -stack_owner_role = Admin -instance_type = m1.small -keypair_name = testkey - -[oslo_concurrency] -lock_path = /tmp - -[scenario] -img_dir = /home/ubuntu/images -img_file = cirros-0.3.4-x86_64-disk.img -img_container_format = bare -img_disk_format = qcow2 - -[validation] -run_validation = true -image_ssh_user = cirros - -[service_available] -ceilometer = true -cinder = true -glance = true -heat = true -horizon = true -ironic = false -neutron = true -nova = true -sahara = false -swift = true -trove = false -zaqar = false - -[volume] -backend_names = cinder-ceph -storage_protocol = ceph -# NOTE(coreycb): Need to enalbe catalog_type, determined by: -# volume_version=$(openstack endpoint list -c 'Service Type' -f value | grep -m 1 volumev3 || -# openstack endpoint list -c 'Service Type' -f value | grep -m 1 volumev2) -# catalog_type = __VOLUME_VERSION__ - -[volume-feature-enabled] -backup = false""" +debug = false""" diff --git a/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py b/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py index 887b0b5..aa6b555 100644 --- a/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py +++ b/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py @@ -1,95 +1,3 @@ file_contents = """ [DEFAULT] -debug = false -use_stderr = false -log_file = tempest.log - -[auth] -test_accounts_file = accounts.yaml -default_credentials_domain_name = {default_credentials_domain_name} -admin_username = {admin_username} -admin_project_name = {admin_project_name} -admin_password = {admin_password} -admin_domain_name = {admin_domain_name} - -[compute] -image_ref = {image_id} -image_ref_alt = {image_alt_id} -flavor_ref = {flavor_ref} -flavor_ref_alt = {flavor_ref_alt} -min_compute_nodes = 3 - -# TODO: review this as its release specific -# min_microversion = 2.2 -# max_microversion = latest - -[compute-feature-enabled] -console_output = true -resize = true -live_migration = true -block_migration_for_live_migration = true -attach_encrypted_volume = false - -[identity] -uri = {proto}://{keystone}:5000/v2.0 -uri_v3 = {proto}://{keystone}:5000/v3 -auth_version = v3 -admin_role = Admin -region = RegionOne -default_domain_id = {default_domain_id} -admin_domain_scope = true - -[identity-feature-enabled] -api_v2 = false -api_v3 = true - -[image] -http_image = http://{os_test_swift_ip}:80/swift/v1/images/cirros-0.3.4-x86_64-uec.tar.gz - -[network] -project_network_cidr = {os_test_cidr_priv} -public_network_id = {ext_net} -dns_servers = {os_test_nameserver} -project_networks_reachable = false - -[network-feature-enabled] -ipv6 = false - -[orchestration] -stack_owner_role = Admin -instance_type = m1.small -keypair_name = testkey - -[oslo_concurrency] -lock_path = /tmp - -[scenario] -img_dir = /home/ubuntu/images -img_file = cirros-0.3.4-x86_64-disk.img -img_container_format = bare -img_disk_format = qcow2 - -[validation] -run_validation = true -image_ssh_user = cirros - -[service_available] -ceilometer = true -cinder = true -glance = true -heat = true -horizon = true -ironic = false -neutron = true -nova = true -sahara = false -swift = true -trove = false -zaqar = false - -[volume] -backend_names = cinder-ceph -storage_protocol = ceph - -[volume-feature-enabled] -backup = false""" +debug = false""" From 816c6462ca19c15dd4aa9be8a6ad6b53fa87bdd0 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Wed, 22 Apr 2020 17:42:54 +0000 Subject: [PATCH 069/140] Revert to remember --- zaza/openstack/charm_tests/tempest/setup.py | 56 ++++++++++++--------- zaza/openstack/charm_tests/tempest/tests.py | 21 ++++---- 2 files changed, 42 insertions(+), 35 deletions(-) diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py index 7336299..214b816 100644 --- a/zaza/openstack/charm_tests/tempest/setup.py +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -15,7 +15,7 @@ """Code for configuring tempest.""" import urllib.parse import os -import shutil +#import shutil import subprocess import zaza.model @@ -25,7 +25,7 @@ import zaza.openstack.charm_tests.glance.setup as glance_setup import zaza.openstack.charm_tests.tempest.templates.tempest_v2 as tempest_v2 import zaza.openstack.charm_tests.tempest.templates.tempest_v3 as tempest_v3 import zaza.openstack.charm_tests.tempest.templates.accounts as accounts -import tempest.cmd.main +#import tempest.cmd.main import keystoneauth1 import novaclient @@ -154,36 +154,42 @@ def render_tempest_config(target_file, ctxt, tempest_template): def setup_tempest(tempest_template, accounts_template): - config_dir = '.tempest' - config_etc_dir = os.path.join(config_dir, 'etc') - config_etc_tempest = os.path.join(config_etc_dir, 'tempest.conf') - config_workspace_yaml = os.path.join(config_dir, 'workspace.yaml') - workspace_name = 'workspace' - workspace_dir = os.path.join(config_dir, workspace_name) - workspace_etc_dir = os.path.join(workspace_dir, 'etc') - workspace_etc_accounts = os.path.join(workspace_etc_dir, 'accounts.yaml') - workspace_etc_tempest = os.path.join(workspace_etc_dir, 'tempest.conf') - the_app = tempest.cmd.main.Main() + try: + os.makedirs('tempest/etc/') + except FileExistsError: + pass + #config_dir = '.tempest' + #config_etc_dir = os.path.join(config_dir, 'etc') + #config_etc_tempest = os.path.join(config_etc_dir, 'tempest.conf') + #config_workspace_yaml = os.path.join(config_dir, 'workspace.yaml') + #workspace_name = 'workspace' + #workspace_dir = os.path.join(config_dir, workspace_name) + #workspace_etc_dir = os.path.join(workspace_dir, 'etc') + #workspace_etc_accounts = os.path.join(workspace_etc_dir, 'accounts.yaml') + #workspace_etc_tempest = os.path.join(workspace_etc_dir, 'tempest.conf') + #the_app = tempest.cmd.main.Main() # note sure this is needed or not - if not os.path.isdir(config_dir): - os.mkdir(config_dir) - os.mkdir(config_etc_dir) - render_tempest_config( - config_etc_tempest, - get_tempest_context(), - tempest_template) - tempest_options = ['init', '--workspace-path', config_workspace_yaml, - '--name', workspace_name, workspace_dir] - print(tempest_options) - _exec_tempest = the_app.run(tempest_options) + #if not os.path.isdir(config_dir): + # os.mkdir(config_dir) + # os.mkdir(config_etc_dir) + #render_tempest_config( + # config_etc_tempest, + # get_tempest_context(), + # tempest_template) + #tempest_options = ['init', '--workspace-path', config_workspace_yaml, + # '--name', workspace_name, workspace_dir] + #print(tempest_options) + #_exec_tempest = the_app.run(tempest_options) # This was mising /etc/tempest/ and just going to /etc/ render_tempest_config( - workspace_etc_tempest, + 'tempest/etc/tempest.conf', + #workspace_etc_tempest, get_tempest_context(), tempest_template) render_tempest_config( - workspace_etc_accounts, + 'tempest/etc/accounts.yaml', + #workspace_etc_accounts, get_tempest_context(), accounts_template) diff --git a/zaza/openstack/charm_tests/tempest/tests.py b/zaza/openstack/charm_tests/tempest/tests.py index 9bc6221..5e949aa 100644 --- a/zaza/openstack/charm_tests/tempest/tests.py +++ b/zaza/openstack/charm_tests/tempest/tests.py @@ -14,16 +14,17 @@ class TempestTest(): def run(self): charm_config = zaza.charm_lifecycle.utils.get_charm_config() - config_dir = '.tempest' - config_workspace_yaml = os.path.join(config_dir, 'workspace.yaml') - workspace_name = 'workspace' - workspace_dir = os.path.join(config_dir, workspace_name) - workspace_etc_dir = os.path.join(workspace_dir, 'etc') - workspace_etc_tempest = os.path.join(workspace_etc_dir, 'tempest.conf') - tempest_options = ['run', '--config-file', - workspace_etc_tempest, - '--workspace-path', config_workspace_yaml, - '--workspace', workspace_name] + tempest_options = ['run', '--config', 'tempest/etc/tempest.conf'] + #config_dir = '.tempest' + #config_workspace_yaml = os.path.join(config_dir, 'workspace.yaml') + #workspace_name = 'workspace' + #workspace_dir = os.path.join(config_dir, workspace_name) + #workspace_etc_dir = os.path.join(workspace_dir, 'etc') + #workspace_etc_tempest = os.path.join(workspace_etc_dir, 'tempest.conf') + #tempest_options = ['run', '--config-file', + # workspace_etc_tempest, + # '--workspace-path', config_workspace_yaml, + # '--workspace', workspace_name] for model_alias in zaza.model.get_juju_model_aliases().keys(): tempest_test_key = model_alias if model_alias == zaza.charm_lifecycle.utils.DEFAULT_MODEL_ALIAS: From 27cc4ebdcf21e5efe966de6e8d3d7d4198155f91 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Wed, 22 Apr 2020 18:53:15 +0000 Subject: [PATCH 070/140] Switch to running tempest with subprocess and use workspace --- zaza/openstack/charm_tests/tempest/setup.py | 21 ++++++++++++++------- zaza/openstack/charm_tests/tempest/tests.py | 16 ++++++++++------ 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py index 214b816..9789d1e 100644 --- a/zaza/openstack/charm_tests/tempest/setup.py +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -15,7 +15,7 @@ """Code for configuring tempest.""" import urllib.parse import os -#import shutil +import shutil import subprocess import zaza.model @@ -154,10 +154,17 @@ def render_tempest_config(target_file, ctxt, tempest_template): def setup_tempest(tempest_template, accounts_template): - try: - os.makedirs('tempest/etc/') - except FileExistsError: - pass + #try: + # os.makedirs('tempest/etc/') + #except FileExistsError: + # pass + if os.path.isdir('tempest-workspace'): + try: + subprocess.check_call(['tempest', 'workspace', 'remove', '--rmdir', '--name', 'tempest-workspace']) + except subprocess.CalledProcessError: + pass + #shutil.rmtree('tempest-workspace') + subprocess.check_call(['tempest', 'init', 'tempest-workspace']) #config_dir = '.tempest' #config_etc_dir = os.path.join(config_dir, 'etc') #config_etc_tempest = os.path.join(config_etc_dir, 'tempest.conf') @@ -183,12 +190,12 @@ def setup_tempest(tempest_template, accounts_template): #_exec_tempest = the_app.run(tempest_options) # This was mising /etc/tempest/ and just going to /etc/ render_tempest_config( - 'tempest/etc/tempest.conf', + 'tempest-workspace/etc/tempest.conf', #workspace_etc_tempest, get_tempest_context(), tempest_template) render_tempest_config( - 'tempest/etc/accounts.yaml', + 'tempest-workspace/etc/accounts.yaml', #workspace_etc_accounts, get_tempest_context(), accounts_template) diff --git a/zaza/openstack/charm_tests/tempest/tests.py b/zaza/openstack/charm_tests/tempest/tests.py index 5e949aa..a4360a9 100644 --- a/zaza/openstack/charm_tests/tempest/tests.py +++ b/zaza/openstack/charm_tests/tempest/tests.py @@ -1,4 +1,5 @@ import os +import subprocess import zaza import zaza.charm_lifecycle.utils @@ -14,7 +15,8 @@ class TempestTest(): def run(self): charm_config = zaza.charm_lifecycle.utils.get_charm_config() - tempest_options = ['run', '--config', 'tempest/etc/tempest.conf'] + tempest_options = ['run', '--workspace', 'tempest-workspace', + '--config', 'tempest-workspace/etc/tempest.conf'] #config_dir = '.tempest' #config_workspace_yaml = os.path.join(config_dir, 'workspace.yaml') #workspace_name = 'workspace' @@ -54,10 +56,12 @@ class TempestTest(): f.write('\n') tempest_options.extend(['--blacklist-file', black_file]) print(tempest_options) - the_app = tempest.cmd.main.Main() - project_root = os.getcwd() - _exec_tempest = the_app.run(tempest_options) - os.chdir(project_root) - if _exec_tempest != 0: + #the_app = tempest.cmd.main.Main() + #project_root = os.getcwd() + #_exec_tempest = the_app.run(tempest_options) + #os.chdir(project_root) + try: + subprocess.check_call(tempest_options) + except subprocess.CalledProcessError: return False return True From 411951d1a1f99308a3fd98940a3d6cb596ce3575 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Wed, 22 Apr 2020 20:35:18 +0000 Subject: [PATCH 071/140] uncomment get_tempest_context --- zaza/openstack/charm_tests/tempest/setup.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py index 9789d1e..6bb900b 100644 --- a/zaza/openstack/charm_tests/tempest/setup.py +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -138,13 +138,13 @@ def add_access_protocol(ctxt): def get_tempest_context(): keystone_session = openstack_utils.get_overcloud_keystone_session() ctxt = {} - #add_application_ips(ctxt) - #add_nova_config(ctxt, keystone_session) - #add_neutron_config(ctxt, keystone_session) - #add_glance_config(ctxt, keystone_session) - #add_keystone_config(ctxt, keystone_session) - #add_environment_var_config(ctxt) - #add_access_protocol(ctxt) + add_application_ips(ctxt) + add_nova_config(ctxt, keystone_session) + add_neutron_config(ctxt, keystone_session) + add_glance_config(ctxt, keystone_session) + add_keystone_config(ctxt, keystone_session) + add_environment_var_config(ctxt) + add_access_protocol(ctxt) return ctxt From 2df890cabf8a0204d2ea74bdfe8b068c19ddb853 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Thu, 23 Apr 2020 13:25:19 +0000 Subject: [PATCH 072/140] s/run/tempest run/ --- zaza/openstack/charm_tests/tempest/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zaza/openstack/charm_tests/tempest/tests.py b/zaza/openstack/charm_tests/tempest/tests.py index a4360a9..10a2298 100644 --- a/zaza/openstack/charm_tests/tempest/tests.py +++ b/zaza/openstack/charm_tests/tempest/tests.py @@ -15,7 +15,7 @@ class TempestTest(): def run(self): charm_config = zaza.charm_lifecycle.utils.get_charm_config() - tempest_options = ['run', '--workspace', 'tempest-workspace', + tempest_options = ['tempest', 'run', '--workspace', 'tempest-workspace', '--config', 'tempest-workspace/etc/tempest.conf'] #config_dir = '.tempest' #config_workspace_yaml = os.path.join(config_dir, 'workspace.yaml') From f4c150f3e70e71dfba4d039b90655bd3e93c37f2 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Thu, 23 Apr 2020 15:23:49 +0000 Subject: [PATCH 073/140] restore templates --- .../tempest/templates/tempest_v2.py | 96 ++++++++++++++++++- .../tempest/templates/tempest_v3.py | 94 +++++++++++++++++- 2 files changed, 188 insertions(+), 2 deletions(-) diff --git a/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py b/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py index aa6b555..ec95577 100644 --- a/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py +++ b/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py @@ -1,3 +1,97 @@ file_contents = """ [DEFAULT] -debug = false""" +debug = false +use_stderr = false +log_file = tempest.log + +[auth] +test_accounts_file = accounts.yaml +default_credentials_domain_name = Default +admin_username = {admin_username} +admin_project_name = admin +admin_password = {admin_password} +admin_domain_name = Default + +[compute] +image_ref = {image_id} +image_ref_alt = {image_alt_id} +flavor_ref = {flavor_ref} +flavor_ref_alt = {flavor_ref_alt} +region = RegionOne +min_compute_nodes = 3 + +# TODO: review this as its release specific +# min_microversion = 2.2 +# max_microversion = latest + +[compute-feature-enabled] +console_output = true +resize = true +live_migration = true +block_migration_for_live_migration = true +attach_encrypted_volume = false + +[identity] +uri = {proto}://{keystone}:5000/v2.0 +auth_version = v2 +admin_role = Admin +region = RegionOne + +[identity-feature-enabled] +api_v2 = true +api_v3 = false + +[image] +http_image = http://{os_test_swift_ip}:80/swift/v1/images/cirros-0.3.4-x86_64-uec.tar.gz + +[network] +project_network_cidr = {os_test_cidr_priv} +public_network_id = {ext_net} +dns_servers = {os_test_nameserver} +project_networks_reachable = false + +[network-feature-enabled] +ipv6 = false + +[orchestration] +stack_owner_role = Admin +instance_type = m1.small +keypair_name = testkey + +[oslo_concurrency] +lock_path = /tmp + +[scenario] +img_dir = /home/ubuntu/images +img_file = cirros-0.3.4-x86_64-disk.img +img_container_format = bare +img_disk_format = qcow2 + +[validation] +run_validation = true +image_ssh_user = cirros + +[service_available] +ceilometer = true +cinder = true +glance = true +heat = true +horizon = true +ironic = false +neutron = true +nova = true +sahara = false +swift = true +trove = false +zaqar = false + +[volume] +backend_names = cinder-ceph +storage_protocol = ceph +# NOTE(coreycb): Need to enalbe catalog_type, determined by: +# volume_version=$(openstack endpoint list -c 'Service Type' -f value | grep -m 1 volumev3 || +# openstack endpoint list -c 'Service Type' -f value | grep -m 1 volumev2) +# catalog_type = __VOLUME_VERSION__ + +[volume-feature-enabled] +backup = false""" diff --git a/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py b/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py index aa6b555..887b0b5 100644 --- a/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py +++ b/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py @@ -1,3 +1,95 @@ file_contents = """ [DEFAULT] -debug = false""" +debug = false +use_stderr = false +log_file = tempest.log + +[auth] +test_accounts_file = accounts.yaml +default_credentials_domain_name = {default_credentials_domain_name} +admin_username = {admin_username} +admin_project_name = {admin_project_name} +admin_password = {admin_password} +admin_domain_name = {admin_domain_name} + +[compute] +image_ref = {image_id} +image_ref_alt = {image_alt_id} +flavor_ref = {flavor_ref} +flavor_ref_alt = {flavor_ref_alt} +min_compute_nodes = 3 + +# TODO: review this as its release specific +# min_microversion = 2.2 +# max_microversion = latest + +[compute-feature-enabled] +console_output = true +resize = true +live_migration = true +block_migration_for_live_migration = true +attach_encrypted_volume = false + +[identity] +uri = {proto}://{keystone}:5000/v2.0 +uri_v3 = {proto}://{keystone}:5000/v3 +auth_version = v3 +admin_role = Admin +region = RegionOne +default_domain_id = {default_domain_id} +admin_domain_scope = true + +[identity-feature-enabled] +api_v2 = false +api_v3 = true + +[image] +http_image = http://{os_test_swift_ip}:80/swift/v1/images/cirros-0.3.4-x86_64-uec.tar.gz + +[network] +project_network_cidr = {os_test_cidr_priv} +public_network_id = {ext_net} +dns_servers = {os_test_nameserver} +project_networks_reachable = false + +[network-feature-enabled] +ipv6 = false + +[orchestration] +stack_owner_role = Admin +instance_type = m1.small +keypair_name = testkey + +[oslo_concurrency] +lock_path = /tmp + +[scenario] +img_dir = /home/ubuntu/images +img_file = cirros-0.3.4-x86_64-disk.img +img_container_format = bare +img_disk_format = qcow2 + +[validation] +run_validation = true +image_ssh_user = cirros + +[service_available] +ceilometer = true +cinder = true +glance = true +heat = true +horizon = true +ironic = false +neutron = true +nova = true +sahara = false +swift = true +trove = false +zaqar = false + +[volume] +backend_names = cinder-ceph +storage_protocol = ceph + +[volume-feature-enabled] +backup = false""" From 635866afe4a5d8c506d9b3c92142aba7ee9b4660 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Thu, 23 Apr 2020 18:51:43 +0000 Subject: [PATCH 074/140] cleanup --- zaza/openstack/charm_tests/tempest/setup.py | 37 +++------------------ zaza/openstack/charm_tests/tempest/tests.py | 14 -------- 2 files changed, 4 insertions(+), 47 deletions(-) diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py index 6bb900b..cae3ab5 100644 --- a/zaza/openstack/charm_tests/tempest/setup.py +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -25,7 +25,6 @@ import zaza.openstack.charm_tests.glance.setup as glance_setup import zaza.openstack.charm_tests.tempest.templates.tempest_v2 as tempest_v2 import zaza.openstack.charm_tests.tempest.templates.tempest_v3 as tempest_v3 import zaza.openstack.charm_tests.tempest.templates.accounts as accounts -#import tempest.cmd.main import keystoneauth1 import novaclient @@ -154,49 +153,21 @@ def render_tempest_config(target_file, ctxt, tempest_template): def setup_tempest(tempest_template, accounts_template): - #try: - # os.makedirs('tempest/etc/') - #except FileExistsError: - # pass if os.path.isdir('tempest-workspace'): try: subprocess.check_call(['tempest', 'workspace', 'remove', '--rmdir', '--name', 'tempest-workspace']) except subprocess.CalledProcessError: pass - #shutil.rmtree('tempest-workspace') - subprocess.check_call(['tempest', 'init', 'tempest-workspace']) - #config_dir = '.tempest' - #config_etc_dir = os.path.join(config_dir, 'etc') - #config_etc_tempest = os.path.join(config_etc_dir, 'tempest.conf') - #config_workspace_yaml = os.path.join(config_dir, 'workspace.yaml') - #workspace_name = 'workspace' - #workspace_dir = os.path.join(config_dir, workspace_name) - #workspace_etc_dir = os.path.join(workspace_dir, 'etc') - #workspace_etc_accounts = os.path.join(workspace_etc_dir, 'accounts.yaml') - #workspace_etc_tempest = os.path.join(workspace_etc_dir, 'tempest.conf') - #the_app = tempest.cmd.main.Main() - - # note sure this is needed or not - #if not os.path.isdir(config_dir): - # os.mkdir(config_dir) - # os.mkdir(config_etc_dir) - #render_tempest_config( - # config_etc_tempest, - # get_tempest_context(), - # tempest_template) - #tempest_options = ['init', '--workspace-path', config_workspace_yaml, - # '--name', workspace_name, workspace_dir] - #print(tempest_options) - #_exec_tempest = the_app.run(tempest_options) - # This was mising /etc/tempest/ and just going to /etc/ + try: + subprocess.check_call(['tempest', 'init', 'tempest-workspace']) + except subprocess.CalledProcessError: + pass render_tempest_config( 'tempest-workspace/etc/tempest.conf', - #workspace_etc_tempest, get_tempest_context(), tempest_template) render_tempest_config( 'tempest-workspace/etc/accounts.yaml', - #workspace_etc_accounts, get_tempest_context(), accounts_template) diff --git a/zaza/openstack/charm_tests/tempest/tests.py b/zaza/openstack/charm_tests/tempest/tests.py index 10a2298..dfdc247 100644 --- a/zaza/openstack/charm_tests/tempest/tests.py +++ b/zaza/openstack/charm_tests/tempest/tests.py @@ -17,16 +17,6 @@ class TempestTest(): charm_config = zaza.charm_lifecycle.utils.get_charm_config() tempest_options = ['tempest', 'run', '--workspace', 'tempest-workspace', '--config', 'tempest-workspace/etc/tempest.conf'] - #config_dir = '.tempest' - #config_workspace_yaml = os.path.join(config_dir, 'workspace.yaml') - #workspace_name = 'workspace' - #workspace_dir = os.path.join(config_dir, workspace_name) - #workspace_etc_dir = os.path.join(workspace_dir, 'etc') - #workspace_etc_tempest = os.path.join(workspace_etc_dir, 'tempest.conf') - #tempest_options = ['run', '--config-file', - # workspace_etc_tempest, - # '--workspace-path', config_workspace_yaml, - # '--workspace', workspace_name] for model_alias in zaza.model.get_juju_model_aliases().keys(): tempest_test_key = model_alias if model_alias == zaza.charm_lifecycle.utils.DEFAULT_MODEL_ALIAS: @@ -56,10 +46,6 @@ class TempestTest(): f.write('\n') tempest_options.extend(['--blacklist-file', black_file]) print(tempest_options) - #the_app = tempest.cmd.main.Main() - #project_root = os.getcwd() - #_exec_tempest = the_app.run(tempest_options) - #os.chdir(project_root) try: subprocess.check_call(tempest_options) except subprocess.CalledProcessError: From 9e3b2f74165d038cf61ed3db01569fea0017ac45 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Mon, 27 Apr 2020 16:57:24 +0000 Subject: [PATCH 075/140] Add catalog_type tempest config support --- zaza/openstack/charm_tests/tempest/setup.py | 12 ++++++++++++ .../charm_tests/tempest/templates/tempest_v2.py | 5 +---- .../charm_tests/tempest/templates/tempest_v3.py | 1 + 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py index cae3ab5..73480df 100644 --- a/zaza/openstack/charm_tests/tempest/setup.py +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -98,6 +98,17 @@ def add_glance_config(ctxt, keystone_session): ctxt['image_alt_id'] = image_alt[0].id +def add_cinder_config(ctxt, keystone_session): + volume_types = ['volumev2', 'volumev3'] + keystone_client = openstack_utils.get_keystone_session_client( + keystone_session) + for volume_type in volume_types: + service = keystone_client.services.list(type=volume_type) + if service: + ctxt['catalog_type'] = volume_type + break + + def add_keystone_config(ctxt, keystone_session): keystone_client = openstack_utils.get_keystone_session_client( keystone_session) @@ -141,6 +152,7 @@ def get_tempest_context(): add_nova_config(ctxt, keystone_session) add_neutron_config(ctxt, keystone_session) add_glance_config(ctxt, keystone_session) + add_cinder_config(ctxt, keystone_session) add_keystone_config(ctxt, keystone_session) add_environment_var_config(ctxt) add_access_protocol(ctxt) diff --git a/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py b/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py index ec95577..e4b94ba 100644 --- a/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py +++ b/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py @@ -88,10 +88,7 @@ zaqar = false [volume] backend_names = cinder-ceph storage_protocol = ceph -# NOTE(coreycb): Need to enalbe catalog_type, determined by: -# volume_version=$(openstack endpoint list -c 'Service Type' -f value | grep -m 1 volumev3 || -# openstack endpoint list -c 'Service Type' -f value | grep -m 1 volumev2) -# catalog_type = __VOLUME_VERSION__ +catalog_type = {catalog_type} [volume-feature-enabled] backup = false""" diff --git a/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py b/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py index 887b0b5..6f71765 100644 --- a/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py +++ b/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py @@ -90,6 +90,7 @@ zaqar = false [volume] backend_names = cinder-ceph storage_protocol = ceph +catalog_type = {catalog_type} [volume-feature-enabled] backup = false""" From 8a39e07cbf5919da9128759bbd8d15ec784352e6 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Thu, 30 Apr 2020 14:54:20 +0000 Subject: [PATCH 076/140] Set disable_ssl_certificate_validation to true to deal with self-signed certs --- zaza/openstack/charm_tests/tempest/templates/tempest_v2.py | 1 + zaza/openstack/charm_tests/tempest/templates/tempest_v3.py | 1 + 2 files changed, 2 insertions(+) diff --git a/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py b/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py index e4b94ba..ecca5ce 100644 --- a/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py +++ b/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py @@ -36,6 +36,7 @@ uri = {proto}://{keystone}:5000/v2.0 auth_version = v2 admin_role = Admin region = RegionOne +disable_ssl_certificate_validation = true [identity-feature-enabled] api_v2 = true diff --git a/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py b/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py index 6f71765..f189ad5 100644 --- a/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py +++ b/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py @@ -38,6 +38,7 @@ admin_role = Admin region = RegionOne default_domain_id = {default_domain_id} admin_domain_scope = true +disable_ssl_certificate_validation = true [identity-feature-enabled] api_v2 = false From 19be6f7d48e9af8b49630dffc92e8f93149418b6 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Mon, 18 May 2020 18:04:04 +0000 Subject: [PATCH 077/140] Fix copyright --- zaza/openstack/charm_tests/tempest/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zaza/openstack/charm_tests/tempest/__init__.py b/zaza/openstack/charm_tests/tempest/__init__.py index ed3be2f..9703646 100644 --- a/zaza/openstack/charm_tests/tempest/__init__.py +++ b/zaza/openstack/charm_tests/tempest/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2018 Canonical Ltd. +# 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. From a21ddd33e6dcf1976d615561627f89a744a0c4e8 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Mon, 18 May 2020 20:04:47 +0000 Subject: [PATCH 078/140] Code cleanup --- zaza/openstack/charm_tests/tempest/setup.py | 138 ++++++++++++++++-- .../charm_tests/tempest/templates/__init__.py | 2 +- zaza/openstack/charm_tests/tempest/tests.py | 30 +++- 3 files changed, 153 insertions(+), 17 deletions(-) diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py index 73480df..59b3b8b 100644 --- a/zaza/openstack/charm_tests/tempest/setup.py +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -13,6 +13,7 @@ # limitations under the License. """Code for configuring tempest.""" + import urllib.parse import os import shutil @@ -43,6 +44,13 @@ TEMPEST_CIRROS_ALT_IMAGE_NAME = 'cirros_alt' def get_app_access_ip(application_name): + """Get the application's access IP + + :param application_name: Name of application + :type application_name: str + :returns: Application's access IP + :rtype: str + """ try: app_config = zaza.model.get_application_config(application_name) except KeyError: @@ -57,12 +65,28 @@ def get_app_access_ip(application_name): def add_application_ips(ctxt): + """Add application access IPs to context + + :param ctxt: Context dictionary + :type ctxt: dict + :returns: None + :rtype: None + """ ctxt['keystone'] = get_app_access_ip('keystone') ctxt['dashboard'] = get_app_access_ip('openstack-dashboard') ctxt['ncc'] = get_app_access_ip('nova-cloud-controller') def add_nova_config(ctxt, keystone_session): + """Add nova config to context + + :param ctxt: Context dictionary + :type ctxt: dict + :returns keystone_session: keystoneauth1.session.Session object + :type: keystoneauth1.session.Session + :returns: None + :rtype: None + """ nova_client = openstack_utils.get_nova_session_client( keystone_session) for flavor in nova_client.flavors.list(): @@ -73,6 +97,15 @@ def add_nova_config(ctxt, keystone_session): def add_neutron_config(ctxt, keystone_session): + """Add neutron config to context + + :param ctxt: Context dictionary + :type ctxt: dict + :returns keystone_session: keystoneauth1.session.Session object + :type: keystoneauth1.session.Session + :returns: None + :rtype: None + """ neutron_client = openstack_utils.get_neutron_session_client( keystone_session) for net in neutron_client.list_networks()['networks']: @@ -86,6 +119,15 @@ def add_neutron_config(ctxt, keystone_session): def add_glance_config(ctxt, keystone_session): + """Add glance config to context + + :param ctxt: Context dictionary + :type ctxt: dict + :returns keystone_session: keystoneauth1.session.Session object + :type: keystoneauth1.session.Session + :returns: None + :rtype: None + """ glance_client = openstack_utils.get_glance_session_client( keystone_session) image = openstack_utils.get_images_by_name( @@ -99,6 +141,15 @@ def add_glance_config(ctxt, keystone_session): def add_cinder_config(ctxt, keystone_session): + """Add cinder config to context + + :param ctxt: Context dictionary + :type ctxt: dict + :returns keystone_session: keystoneauth1.session.Session object + :type: keystoneauth1.session.Session + :returns: None + :rtype: None + """ volume_types = ['volumev2', 'volumev3'] keystone_client = openstack_utils.get_keystone_session_client( keystone_session) @@ -110,6 +161,15 @@ def add_cinder_config(ctxt, keystone_session): def add_keystone_config(ctxt, keystone_session): + """Add keystone config to context + + :param ctxt: Context dictionary + :type ctxt: dict + :returns keystone_session: keystoneauth1.session.Session object + :type: keystoneauth1.session.Session + :returns: None + :rtype: None + """ keystone_client = openstack_utils.get_keystone_session_client( keystone_session) for domain in keystone_client.domains.list(): @@ -119,6 +179,13 @@ def add_keystone_config(ctxt, keystone_session): def add_environment_var_config(ctxt): + """Add environment variable config to context + + :param ctxt: Context dictionary + :type ctxt: dict + :returns: None + :rtype: None + """ deploy_env = deployment_env.get_deployment_context() for var in SETUP_ENV_VARS: value = deploy_env.get(var) @@ -130,7 +197,14 @@ def add_environment_var_config(ctxt): " test").format(', '.join(SETUP_ENV_VARS))) -def add_access_protocol(ctxt): +def add_auth_config(ctxt): + """Add authorization config to context + + :param ctxt: Context dictionary + :type ctxt: dict + :returns: None + :rtype: None + """ overcloud_auth = openstack_utils.get_overcloud_auth() ctxt['proto'] = urllib.parse.urlparse(overcloud_auth['OS_AUTH_URL']).scheme ctxt['admin_username'] = overcloud_auth['OS_USERNAME'] @@ -140,12 +214,14 @@ def add_access_protocol(ctxt): ctxt['admin_domain_name'] = overcloud_auth['OS_DOMAIN_NAME'] ctxt['default_credentials_domain_name'] = overcloud_auth[ 'OS_PROJECT_DOMAIN_NAME'] - elif overcloud_auth['API_VERSION'] == 2: - #ctxt['admin_tenant_name'] = overcloud_auth['OS_TENANT_NAME'] - pass def get_tempest_context(): + """Generate the tempest config context + + :returns: Context dictionary + :rtype: dict + """ keystone_session = openstack_utils.get_overcloud_keystone_session() ctxt = {} add_application_ips(ctxt) @@ -155,16 +231,37 @@ def get_tempest_context(): add_cinder_config(ctxt, keystone_session) add_keystone_config(ctxt, keystone_session) add_environment_var_config(ctxt) - add_access_protocol(ctxt) + add_auth_config(ctxt) return ctxt -def render_tempest_config(target_file, ctxt, tempest_template): +def render_tempest_config(target_file, ctxt, template): + """Render tempest config for specified config file and template + + :param target_file: Name of file to render config to + :type target_file: str + :param ctxt: Context dictionary + :type ctxt: dict + :param template: Template module + :type template: module + :returns: None + :rtype: None + """ + # TODO: switch to jinja2 and generate config based on available services with open(target_file, 'w') as f: f.write(tempest_template.file_contents.format(**ctxt)) def setup_tempest(tempest_template, accounts_template): + """Initialize tempest and render tempest config + + :param tempest_template: tempest.conf template + :type tempest_template: module + :param accounts_template: accounts.yaml template + :type accounts_template: module + :returns: None + :rtype: None + """ if os.path.isdir('tempest-workspace'): try: subprocess.check_call(['tempest', 'workspace', 'remove', '--rmdir', '--name', 'tempest-workspace']) @@ -185,20 +282,28 @@ def setup_tempest(tempest_template, accounts_template): def render_tempest_config_keystone_v2(): + """Render tempest config for Keystone V2 API + + :returns: None + :rtype: None + """ setup_tempest(tempest_v2, accounts) def render_tempest_config_keystone_v3(): + """Render tempest config for Keystone V3 API + + :returns: None + :rtype: None + """ setup_tempest(tempest_v3, accounts) def add_cirros_alt_image(): - """Add a cirros image to the current deployment. + """Add cirros alternate image to overcloud - :param glance: Authenticated glanceclient - :type glance: glanceclient.Client - :param image_name: Label for the image in glance - :type image_name: str + :returns: None + :rtype: None """ image_url = openstack_utils.find_cirros_image(arch='x86_64') glance_setup.add_image( @@ -208,6 +313,11 @@ def add_cirros_alt_image(): def add_tempest_flavors(): + """Add tempest flavors to overcloud + + :returns: None + :rtype: None + """ keystone_session = openstack_utils.get_overcloud_keystone_session() nova_client = openstack_utils.get_nova_session_client( keystone_session) @@ -230,6 +340,11 @@ def add_tempest_flavors(): def add_tempest_roles(): + """Add tempest roles overcloud + + :returns: None + :rtype: None + """ keystone_session = openstack_utils.get_overcloud_keystone_session() keystone_client = openstack_utils.get_keystone_session_client( keystone_session) @@ -238,4 +353,3 @@ def add_tempest_roles(): keystone_client.roles.create(role_name) except keystoneauth1.exceptions.http.Conflict: pass - diff --git a/zaza/openstack/charm_tests/tempest/templates/__init__.py b/zaza/openstack/charm_tests/tempest/templates/__init__.py index 1400887..9269e37 100644 --- a/zaza/openstack/charm_tests/tempest/templates/__init__.py +++ b/zaza/openstack/charm_tests/tempest/templates/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2018 Canonical Ltd. +# 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. diff --git a/zaza/openstack/charm_tests/tempest/tests.py b/zaza/openstack/charm_tests/tempest/tests.py index dfdc247..6fc58d7 100644 --- a/zaza/openstack/charm_tests/tempest/tests.py +++ b/zaza/openstack/charm_tests/tempest/tests.py @@ -1,3 +1,19 @@ +# 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 running tempest tests.""" + import os import subprocess @@ -7,13 +23,19 @@ import zaza.charm_lifecycle.test import tempest.cmd.main import tempfile - - class TempestTest(): - - test_runner = zaza.charm_lifecycle.test.DIRECT + """Tempest test class.""" def run(self): + """Run tempest tests as specified in tests/tests.yaml + + Test keys are parsed from ['tests_options']['tempest']['model'], where + valid test keys are: smoke (bool), whitelist (list of tests), blacklist + (list of tests), and regex (list of regex's). + + :returns: Status of tempest run + :rtype: bool + """ charm_config = zaza.charm_lifecycle.utils.get_charm_config() tempest_options = ['tempest', 'run', '--workspace', 'tempest-workspace', '--config', 'tempest-workspace/etc/tempest.conf'] From 9336e3efa74b65ffe3cbfd8b96b0b9984d8f0fb4 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Mon, 18 May 2020 20:07:31 +0000 Subject: [PATCH 079/140] Minor update --- zaza/openstack/charm_tests/tempest/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py index 59b3b8b..e36b026 100644 --- a/zaza/openstack/charm_tests/tempest/setup.py +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Code for configuring tempest.""" +"""Code for configuring and initializing tempest.""" import urllib.parse import os From f668784f410f0d606cfa24e04057460c67f03862 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Mon, 18 May 2020 20:09:37 +0000 Subject: [PATCH 080/140] Add the missing test_runner --- zaza/openstack/charm_tests/tempest/tests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/zaza/openstack/charm_tests/tempest/tests.py b/zaza/openstack/charm_tests/tempest/tests.py index 6fc58d7..bbc9d9e 100644 --- a/zaza/openstack/charm_tests/tempest/tests.py +++ b/zaza/openstack/charm_tests/tempest/tests.py @@ -25,6 +25,7 @@ import tempfile class TempestTest(): """Tempest test class.""" + test_runner = zaza.charm_lifecycle.test.DIRECT def run(self): """Run tempest tests as specified in tests/tests.yaml From db47aa1fb9fa627481bcd14e8cc947a6eadfe267 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Mon, 18 May 2020 20:10:52 +0000 Subject: [PATCH 081/140] Minor update --- zaza/openstack/charm_tests/tempest/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zaza/openstack/charm_tests/tempest/__init__.py b/zaza/openstack/charm_tests/tempest/__init__.py index 9703646..cf4c994 100644 --- a/zaza/openstack/charm_tests/tempest/__init__.py +++ b/zaza/openstack/charm_tests/tempest/__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 using tempest.""" +"""Collection of code for setting up and running tempest.""" From 5075e97470677c450c2e044493596c03bb55ac94 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Wed, 20 May 2020 18:13:48 +0000 Subject: [PATCH 082/140] Switch Member -> member --- zaza/openstack/charm_tests/tempest/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py index e36b026..4f25686 100644 --- a/zaza/openstack/charm_tests/tempest/setup.py +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -348,7 +348,7 @@ def add_tempest_roles(): keystone_session = openstack_utils.get_overcloud_keystone_session() keystone_client = openstack_utils.get_keystone_session_client( keystone_session) - for role_name in ['Member', 'ResellerAdmin']: + for role_name in ['member', 'ResellerAdmin']: try: keystone_client.roles.create(role_name) except keystoneauth1.exceptions.http.Conflict: From 19725c73b54f86440d8bbf8a5b8ea329ad7540fd Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Wed, 20 May 2020 19:48:04 +0000 Subject: [PATCH 083/140] Fix template variable in render_tempest_config --- zaza/openstack/charm_tests/tempest/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py index 4f25686..5dd833e 100644 --- a/zaza/openstack/charm_tests/tempest/setup.py +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -249,7 +249,7 @@ def render_tempest_config(target_file, ctxt, template): """ # TODO: switch to jinja2 and generate config based on available services with open(target_file, 'w') as f: - f.write(tempest_template.file_contents.format(**ctxt)) + f.write(template.file_contents.format(**ctxt)) def setup_tempest(tempest_template, accounts_template): From c98aa001f997d2cc149e0c64e81f0339a83adc50 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Fri, 22 May 2020 19:43:10 +0000 Subject: [PATCH 084/140] Adjust basic_setup to run upgrade for < ocata --- zaza/openstack/charm_tests/ceilometer/setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zaza/openstack/charm_tests/ceilometer/setup.py b/zaza/openstack/charm_tests/ceilometer/setup.py index c966077..0ff1fb5 100644 --- a/zaza/openstack/charm_tests/ceilometer/setup.py +++ b/zaza/openstack/charm_tests/ceilometer/setup.py @@ -28,11 +28,11 @@ def basic_setup(): tests. """ current_release = openstack_utils.get_os_release() - xenial_pike = openstack_utils.get_os_release('xenial_pike') + xenial_ocata = openstack_utils.get_os_release('xenial_ocata') - if current_release < xenial_pike: + if current_release < xenial_ocata: logging.info( - 'Skipping ceilometer-upgrade as it is not supported before Pike') + 'Skipping ceilometer-upgrade as it is not supported before ocata') return logging.debug('Checking ceilometer-upgrade') From 30b7b91904f3195d10ae138a1072b8eb376fd21e Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Wed, 27 May 2020 13:50:43 +0000 Subject: [PATCH 085/140] Set api_extensions and floating_network_name api_extensions and floating_network_name are set in tempest.conf to fix tempest failures for: * test_list_show_extensions (missing 'l3_agent_scheduler' extension) * test_server_basic_ops (404 'Floating IP pool not found') --- zaza/openstack/charm_tests/tempest/setup.py | 23 +++++++++++++++++++ .../tempest/templates/tempest_v3.py | 2 ++ 2 files changed, 25 insertions(+) diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py index 5dd833e..7f97014 100644 --- a/zaza/openstack/charm_tests/tempest/setup.py +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -106,6 +106,8 @@ def add_neutron_config(ctxt, keystone_session): :returns: None :rtype: None """ + current_release = openstack_utils.get_os_release() + focal_ussuri = openstack_utils.get_os_release('focal_ussuri') neutron_client = openstack_utils.get_neutron_session_client( keystone_session) for net in neutron_client.list_networks()['networks']: @@ -116,6 +118,27 @@ def add_neutron_config(ctxt, keystone_session): if router['name'] == 'provider-router': ctxt['provider_router_id'] = router['id'] break + # For focal+ with OVN, we use the same settings as upstream gate. + # This is because the l3_agent_scheduler extension is only + # applicable for OVN when conventional layer-3 agent enabled: + # https://docs.openstack.org/networking-ovn/2.0.1/features.html + # This enables test_list_show_extensions to run successfully. + if current_release >= focal_ussuri: + extensions = ('address-scope,agent,allowed-address-pairs,' + 'auto-allocated-topology,availability_zone,' + 'binding,default-subnetpools,external-net,' + 'extra_dhcp_opt,multi-provider,net-mtu,' + 'network_availability_zone,network-ip-availability,' + 'port-security,provider,quotas,rbac-address-scope,' + 'rbac-policies,standard-attr-revisions,security-group,' + 'standard-attr-description,subnet_allocation,' + 'standard-attr-tag,standard-attr-timestamp,trunk,' + 'quota_details,router,extraroute,ext-gw-mode,' + 'fip-port-details,pagination,sorting,project-id,' + 'dns-integration,qos') + ctxt['neutron_api_extensions'] = extensions + else: + ctxt['neutron_api_extensions'] = 'all' def add_glance_config(ctxt, keystone_session): diff --git a/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py b/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py index f189ad5..7c95e3e 100644 --- a/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py +++ b/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py @@ -52,9 +52,11 @@ project_network_cidr = {os_test_cidr_priv} public_network_id = {ext_net} dns_servers = {os_test_nameserver} project_networks_reachable = false +floating_network_name = {ext_net} [network-feature-enabled] ipv6 = false +api_extensions = {neutron_api_extensions} [orchestration] stack_owner_role = Admin From e7da28abc93803c00f1954ee45758887114b29e4 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Wed, 27 May 2020 21:19:35 +0000 Subject: [PATCH 086/140] Revert "Set api_extensions and floating_network_name" This reverts commit 1cc280292963ee70aeff9f9dbb96f457f398999f. --- zaza/openstack/charm_tests/tempest/setup.py | 23 ------------------- .../tempest/templates/tempest_v3.py | 2 -- 2 files changed, 25 deletions(-) diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py index 7f97014..5dd833e 100644 --- a/zaza/openstack/charm_tests/tempest/setup.py +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -106,8 +106,6 @@ def add_neutron_config(ctxt, keystone_session): :returns: None :rtype: None """ - current_release = openstack_utils.get_os_release() - focal_ussuri = openstack_utils.get_os_release('focal_ussuri') neutron_client = openstack_utils.get_neutron_session_client( keystone_session) for net in neutron_client.list_networks()['networks']: @@ -118,27 +116,6 @@ def add_neutron_config(ctxt, keystone_session): if router['name'] == 'provider-router': ctxt['provider_router_id'] = router['id'] break - # For focal+ with OVN, we use the same settings as upstream gate. - # This is because the l3_agent_scheduler extension is only - # applicable for OVN when conventional layer-3 agent enabled: - # https://docs.openstack.org/networking-ovn/2.0.1/features.html - # This enables test_list_show_extensions to run successfully. - if current_release >= focal_ussuri: - extensions = ('address-scope,agent,allowed-address-pairs,' - 'auto-allocated-topology,availability_zone,' - 'binding,default-subnetpools,external-net,' - 'extra_dhcp_opt,multi-provider,net-mtu,' - 'network_availability_zone,network-ip-availability,' - 'port-security,provider,quotas,rbac-address-scope,' - 'rbac-policies,standard-attr-revisions,security-group,' - 'standard-attr-description,subnet_allocation,' - 'standard-attr-tag,standard-attr-timestamp,trunk,' - 'quota_details,router,extraroute,ext-gw-mode,' - 'fip-port-details,pagination,sorting,project-id,' - 'dns-integration,qos') - ctxt['neutron_api_extensions'] = extensions - else: - ctxt['neutron_api_extensions'] = 'all' def add_glance_config(ctxt, keystone_session): diff --git a/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py b/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py index 7c95e3e..f189ad5 100644 --- a/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py +++ b/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py @@ -52,11 +52,9 @@ project_network_cidr = {os_test_cidr_priv} public_network_id = {ext_net} dns_servers = {os_test_nameserver} project_networks_reachable = false -floating_network_name = {ext_net} [network-feature-enabled] ipv6 = false -api_extensions = {neutron_api_extensions} [orchestration] stack_owner_role = Admin From 924da664cff843e159dcd4091986d00f6ad2ae90 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Thu, 28 May 2020 01:29:14 +0000 Subject: [PATCH 087/140] Revert "Revert "Set api_extensions and floating_network_name"" This reverts commit 21ee604329552faacfe7984180eb34e003d0cc1b. --- zaza/openstack/charm_tests/tempest/setup.py | 23 +++++++++++++++++++ .../tempest/templates/tempest_v3.py | 2 ++ 2 files changed, 25 insertions(+) diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py index 5dd833e..7f97014 100644 --- a/zaza/openstack/charm_tests/tempest/setup.py +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -106,6 +106,8 @@ def add_neutron_config(ctxt, keystone_session): :returns: None :rtype: None """ + current_release = openstack_utils.get_os_release() + focal_ussuri = openstack_utils.get_os_release('focal_ussuri') neutron_client = openstack_utils.get_neutron_session_client( keystone_session) for net in neutron_client.list_networks()['networks']: @@ -116,6 +118,27 @@ def add_neutron_config(ctxt, keystone_session): if router['name'] == 'provider-router': ctxt['provider_router_id'] = router['id'] break + # For focal+ with OVN, we use the same settings as upstream gate. + # This is because the l3_agent_scheduler extension is only + # applicable for OVN when conventional layer-3 agent enabled: + # https://docs.openstack.org/networking-ovn/2.0.1/features.html + # This enables test_list_show_extensions to run successfully. + if current_release >= focal_ussuri: + extensions = ('address-scope,agent,allowed-address-pairs,' + 'auto-allocated-topology,availability_zone,' + 'binding,default-subnetpools,external-net,' + 'extra_dhcp_opt,multi-provider,net-mtu,' + 'network_availability_zone,network-ip-availability,' + 'port-security,provider,quotas,rbac-address-scope,' + 'rbac-policies,standard-attr-revisions,security-group,' + 'standard-attr-description,subnet_allocation,' + 'standard-attr-tag,standard-attr-timestamp,trunk,' + 'quota_details,router,extraroute,ext-gw-mode,' + 'fip-port-details,pagination,sorting,project-id,' + 'dns-integration,qos') + ctxt['neutron_api_extensions'] = extensions + else: + ctxt['neutron_api_extensions'] = 'all' def add_glance_config(ctxt, keystone_session): diff --git a/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py b/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py index f189ad5..7c95e3e 100644 --- a/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py +++ b/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py @@ -52,9 +52,11 @@ project_network_cidr = {os_test_cidr_priv} public_network_id = {ext_net} dns_servers = {os_test_nameserver} project_networks_reachable = false +floating_network_name = {ext_net} [network-feature-enabled] ipv6 = false +api_extensions = {neutron_api_extensions} [orchestration] stack_owner_role = Admin From 9eefa08ce4d20a9e5afb850744831642a2a428cb Mon Sep 17 00:00:00 2001 From: Liam Young Date: Thu, 28 May 2020 10:57:29 +0000 Subject: [PATCH 088/140] Refactor Designate tests so API tests can be called Refactor Designate tests so that the api tests can be called without running pause/resume tests. This is useful in the mojo replacement functional tests. --- zaza/openstack/charm_tests/designate/tests.py | 68 +++++++++++-------- 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/zaza/openstack/charm_tests/designate/tests.py b/zaza/openstack/charm_tests/designate/tests.py index f8a32db..5583f0b 100644 --- a/zaza/openstack/charm_tests/designate/tests.py +++ b/zaza/openstack/charm_tests/designate/tests.py @@ -83,8 +83,8 @@ class BaseDesignateTest(test_utils.OpenStackBaseTest): cls.server_delete = cls.designate.servers.delete -class DesignateTests(BaseDesignateTest): - """Designate charm restart and pause tests.""" +class DesignateAPITests(BaseDesignateTest): + """Tests interact with designate api.""" TEST_DOMAIN = 'amuletexample.com.' TEST_NS1_RECORD = 'ns1.{}'.format(TEST_DOMAIN) @@ -92,33 +92,6 @@ class DesignateTests(BaseDesignateTest): TEST_WWW_RECORD = "www.{}".format(TEST_DOMAIN) TEST_RECORD = {TEST_WWW_RECORD: '10.0.0.23'} - def test_900_restart_on_config_change(self): - """Checking restart happens on config change. - - Change debug mode and assert that change propagates to the correct - file and that services are restarted as a result - """ - # Services which are expected to restart upon config change, - # and corresponding config files affected by the change - conf_file = '/etc/designate/designate.conf' - - # Make config change, check for service restarts - self.restart_on_changed_debug_oslo_config_file( - conf_file, - self.designate_svcs, - ) - - def test_910_pause_and_resume(self): - """Run pause and resume tests. - - Pause service and check services are stopped then resume and check - they are started - """ - with self.pause_resume( - self.designate_svcs, - pgrep_full=False): - logging.info("Testing pause resume") - def _get_server_id(self, server_name=None, server_id=None): for srv in self.server_list(): if isinstance(srv, dict): @@ -245,3 +218,40 @@ class DesignateTests(BaseDesignateTest): logging.debug('Tidy up delete test record') self._wait_on_domain_gone(domain_id) logging.debug('OK') + + +class DesignateCharmTests(BaseDesignateTest): + """Designate charm restart and pause tests.""" + + def test_900_restart_on_config_change(self): + """Checking restart happens on config change. + + Change debug mode and assert that change propagates to the correct + file and that services are restarted as a result + """ + # Services which are expected to restart upon config change, + # and corresponding config files affected by the change + conf_file = '/etc/designate/designate.conf' + + # Make config change, check for service restarts + self.restart_on_changed_debug_oslo_config_file( + conf_file, + self.designate_svcs, + ) + + def test_910_pause_and_resume(self): + """Run pause and resume tests. + + Pause service and check services are stopped then resume and check + they are started + """ + with self.pause_resume( + self.designate_svcs, + pgrep_full=False): + logging.info("Testing pause resume") + + +class DesignateTests(DesignateAPITests, DesignateCharmTests): + """Collection of all Designate test classes.""" + + pass From 8c747390d3505c0f9699a0e4c51a7df2398212ba Mon Sep 17 00:00:00 2001 From: Frode Nordahl Date: Thu, 28 May 2020 13:23:11 +0200 Subject: [PATCH 089/140] Fail early when no units found for external port creation Fixes #298 --- .../test_zaza_utilities_openstack.py | 30 +++++++++++++++++++ zaza/openstack/utilities/openstack.py | 8 +++++ 2 files changed, 38 insertions(+) diff --git a/unit_tests/utilities/test_zaza_utilities_openstack.py b/unit_tests/utilities/test_zaza_utilities_openstack.py index 5c344bf..3083204 100644 --- a/unit_tests/utilities/test_zaza_utilities_openstack.py +++ b/unit_tests/utilities/test_zaza_utilities_openstack.py @@ -1216,3 +1216,33 @@ class TestOpenStackUtils(ut_utils.BaseTestCase): self.assertTrue(openstack_utils.ovn_present()) self.get_application.side_effect = [KeyError, KeyError] self.assertFalse(openstack_utils.ovn_present()) + + def test_configure_gateway_ext_port(self): + # FIXME: this is not a complete unit test for the function as one did + # not exist at all I'm adding this to test one bit and we'll add more + # as we go. + self.patch_object(openstack_utils, 'deprecated_external_networking') + self.patch_object(openstack_utils, 'dvr_enabled') + self.patch_object(openstack_utils, 'ovn_present') + self.patch_object(openstack_utils, 'get_gateway_uuids') + self.patch_object(openstack_utils, 'get_admin_net') + self.dvr_enabled = False + self.ovn_present = False + self.get_admin_net.return_value = {'id': 'fakeid'} + + novaclient = mock.MagicMock() + neutronclient = mock.MagicMock() + + def _fake_empty_generator(empty=True): + if empty: + return + yield + + self.get_gateway_uuids.side_effect = _fake_empty_generator + with self.assertRaises(RuntimeError): + openstack_utils.configure_gateway_ext_port( + novaclient, neutronclient) + # provide a uuid and check that we don't raise RuntimeError + self.get_gateway_uuids.side_effect = ['fake-uuid'] + openstack_utils.configure_gateway_ext_port( + novaclient, neutronclient) diff --git a/zaza/openstack/utilities/openstack.py b/zaza/openstack/utilities/openstack.py index 47cabf3..ea2a6ba 100644 --- a/zaza/openstack/utilities/openstack.py +++ b/zaza/openstack/utilities/openstack.py @@ -732,6 +732,7 @@ def configure_gateway_ext_port(novaclient, neutronclient, net_id=None, if not net_id: net_id = get_admin_net(neutronclient)['id'] + ports_created = 0 for uuid in uuids: server = novaclient.servers.get(uuid) ext_port_name = "{}_ext-port".format(server.name) @@ -752,12 +753,19 @@ def configure_gateway_ext_port(novaclient, neutronclient, net_id=None, } } port = neutronclient.create_port(body=body_value) + ports_created += 1 server.interface_attach(port_id=port['port']['id'], net_id=None, fixed_ip=None) if add_dataport_to_netplan: mac_address = get_mac_from_port(port, neutronclient) add_interface_to_netplan(server.name, mac_address=mac_address) + if not ports_created: + # NOTE: uuids is an iterator so testing it for contents or length prior + # to iterating over it is futile. + raise RuntimeError('Unable to determine UUIDs for machines to attach ' + 'external networking to.') + ext_br_macs = [] for port in neutronclient.list_ports(network_id=net_id)['ports']: if 'ext-port' in port['name']: From 35a9e55ea543bda3b26ac5193e168fe18d74dc58 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Thu, 28 May 2020 14:19:04 +0000 Subject: [PATCH 090/140] Fix pep8 issues --- zaza/openstack/charm_tests/tempest/setup.py | 42 +++++++++---------- .../charm_tests/tempest/templates/accounts.py | 1 + .../tempest/templates/tempest_v2.py | 1 + .../tempest/templates/tempest_v3.py | 1 + zaza/openstack/charm_tests/tempest/tests.py | 10 +++-- 5 files changed, 30 insertions(+), 25 deletions(-) diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py index 7f97014..9f442bc 100644 --- a/zaza/openstack/charm_tests/tempest/setup.py +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -16,7 +16,6 @@ import urllib.parse import os -import shutil import subprocess import zaza.model @@ -44,7 +43,7 @@ TEMPEST_CIRROS_ALT_IMAGE_NAME = 'cirros_alt' def get_app_access_ip(application_name): - """Get the application's access IP + """Get the application's access IP. :param application_name: Name of application :type application_name: str @@ -65,7 +64,7 @@ def get_app_access_ip(application_name): def add_application_ips(ctxt): - """Add application access IPs to context + """Add application access IPs to context. :param ctxt: Context dictionary :type ctxt: dict @@ -78,7 +77,7 @@ def add_application_ips(ctxt): def add_nova_config(ctxt, keystone_session): - """Add nova config to context + """Add nova config to context. :param ctxt: Context dictionary :type ctxt: dict @@ -97,7 +96,7 @@ def add_nova_config(ctxt, keystone_session): def add_neutron_config(ctxt, keystone_session): - """Add neutron config to context + """Add neutron config to context. :param ctxt: Context dictionary :type ctxt: dict @@ -142,7 +141,7 @@ def add_neutron_config(ctxt, keystone_session): def add_glance_config(ctxt, keystone_session): - """Add glance config to context + """Add glance config to context. :param ctxt: Context dictionary :type ctxt: dict @@ -164,7 +163,7 @@ def add_glance_config(ctxt, keystone_session): def add_cinder_config(ctxt, keystone_session): - """Add cinder config to context + """Add cinder config to context. :param ctxt: Context dictionary :type ctxt: dict @@ -184,7 +183,7 @@ def add_cinder_config(ctxt, keystone_session): def add_keystone_config(ctxt, keystone_session): - """Add keystone config to context + """Add keystone config to context. :param ctxt: Context dictionary :type ctxt: dict @@ -202,7 +201,7 @@ def add_keystone_config(ctxt, keystone_session): def add_environment_var_config(ctxt): - """Add environment variable config to context + """Add environment variable config to context. :param ctxt: Context dictionary :type ctxt: dict @@ -221,7 +220,7 @@ def add_environment_var_config(ctxt): def add_auth_config(ctxt): - """Add authorization config to context + """Add authorization config to context. :param ctxt: Context dictionary :type ctxt: dict @@ -235,12 +234,12 @@ def add_auth_config(ctxt): if overcloud_auth['API_VERSION'] == 3: ctxt['admin_project_name'] = overcloud_auth['OS_PROJECT_NAME'] ctxt['admin_domain_name'] = overcloud_auth['OS_DOMAIN_NAME'] - ctxt['default_credentials_domain_name'] = overcloud_auth[ - 'OS_PROJECT_DOMAIN_NAME'] + ctxt['default_credentials_domain_name'] = ( + overcloud_auth['OS_PROJECT_DOMAIN_NAME']) def get_tempest_context(): - """Generate the tempest config context + """Generate the tempest config context. :returns: Context dictionary :rtype: dict @@ -259,7 +258,7 @@ def get_tempest_context(): def render_tempest_config(target_file, ctxt, template): - """Render tempest config for specified config file and template + """Render tempest config for specified config file and template. :param target_file: Name of file to render config to :type target_file: str @@ -276,7 +275,7 @@ def render_tempest_config(target_file, ctxt, template): def setup_tempest(tempest_template, accounts_template): - """Initialize tempest and render tempest config + """Initialize tempest and render tempest config. :param tempest_template: tempest.conf template :type tempest_template: module @@ -287,7 +286,8 @@ def setup_tempest(tempest_template, accounts_template): """ if os.path.isdir('tempest-workspace'): try: - subprocess.check_call(['tempest', 'workspace', 'remove', '--rmdir', '--name', 'tempest-workspace']) + subprocess.check_call(['tempest', 'workspace', 'remove', '--rmdir', + '--name', 'tempest-workspace']) except subprocess.CalledProcessError: pass try: @@ -305,7 +305,7 @@ def setup_tempest(tempest_template, accounts_template): def render_tempest_config_keystone_v2(): - """Render tempest config for Keystone V2 API + """Render tempest config for Keystone V2 API. :returns: None :rtype: None @@ -314,7 +314,7 @@ def render_tempest_config_keystone_v2(): def render_tempest_config_keystone_v3(): - """Render tempest config for Keystone V3 API + """Render tempest config for Keystone V3 API. :returns: None :rtype: None @@ -323,7 +323,7 @@ def render_tempest_config_keystone_v3(): def add_cirros_alt_image(): - """Add cirros alternate image to overcloud + """Add cirros alternate image to overcloud. :returns: None :rtype: None @@ -336,7 +336,7 @@ def add_cirros_alt_image(): def add_tempest_flavors(): - """Add tempest flavors to overcloud + """Add tempest flavors to overcloud. :returns: None :rtype: None @@ -363,7 +363,7 @@ def add_tempest_flavors(): def add_tempest_roles(): - """Add tempest roles overcloud + """Add tempest roles overcloud. :returns: None :rtype: None diff --git a/zaza/openstack/charm_tests/tempest/templates/accounts.py b/zaza/openstack/charm_tests/tempest/templates/accounts.py index 3e5329d..0c5cf5a 100644 --- a/zaza/openstack/charm_tests/tempest/templates/accounts.py +++ b/zaza/openstack/charm_tests/tempest/templates/accounts.py @@ -1,3 +1,4 @@ +# flake8: noqa file_contents = """ - username: 'demo' tenant_name: 'demo' diff --git a/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py b/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py index ecca5ce..c5649d0 100644 --- a/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py +++ b/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py @@ -1,3 +1,4 @@ +# flake8: noqa file_contents = """ [DEFAULT] debug = false diff --git a/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py b/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py index 7c95e3e..4f183dd 100644 --- a/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py +++ b/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py @@ -1,3 +1,4 @@ +# flake8: noqa file_contents = """ [DEFAULT] debug = false diff --git a/zaza/openstack/charm_tests/tempest/tests.py b/zaza/openstack/charm_tests/tempest/tests.py index bbc9d9e..d726930 100644 --- a/zaza/openstack/charm_tests/tempest/tests.py +++ b/zaza/openstack/charm_tests/tempest/tests.py @@ -20,15 +20,16 @@ import subprocess import zaza import zaza.charm_lifecycle.utils import zaza.charm_lifecycle.test -import tempest.cmd.main import tempfile + class TempestTest(): """Tempest test class.""" + test_runner = zaza.charm_lifecycle.test.DIRECT def run(self): - """Run tempest tests as specified in tests/tests.yaml + """Run tempest tests as specified in tests/tests.yaml. Test keys are parsed from ['tests_options']['tempest']['model'], where valid test keys are: smoke (bool), whitelist (list of tests), blacklist @@ -38,8 +39,9 @@ class TempestTest(): :rtype: bool """ charm_config = zaza.charm_lifecycle.utils.get_charm_config() - tempest_options = ['tempest', 'run', '--workspace', 'tempest-workspace', - '--config', 'tempest-workspace/etc/tempest.conf'] + tempest_options = ['tempest', 'run', '--workspace', + 'tempest-workspace', '--config', + 'tempest-workspace/etc/tempest.conf'] for model_alias in zaza.model.get_juju_model_aliases().keys(): tempest_test_key = model_alias if model_alias == zaza.charm_lifecycle.utils.DEFAULT_MODEL_ALIAS: From 820667011db87f4b0282dd55bf25095036484e9e Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Thu, 28 May 2020 15:23:54 +0000 Subject: [PATCH 091/140] Add initial tempest branches to requirements.txt --- requirements.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/requirements.txt b/requirements.txt index e43c4e5..1446e9d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -40,3 +40,7 @@ paramiko sphinx sphinxcontrib-asyncio git+https://github.com/openstack-charmers/zaza#egg=zaza + +# Tempest tests +git+https://opendev.org/openstack/tempest.git#egg=tempest +git+https://opendev.org/openstack/cinder-tempest-plugin.git#egg=cinder-tempest-plugin From d84622d088bf3f17302a3f1c292fc3eb27a3efc4 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Fri, 29 May 2020 16:52:16 +0000 Subject: [PATCH 092/140] Drop tempest from requirements.txt as it doesn't support py35 --- requirements.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index 1446e9d..e43c4e5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -40,7 +40,3 @@ paramiko sphinx sphinxcontrib-asyncio git+https://github.com/openstack-charmers/zaza#egg=zaza - -# Tempest tests -git+https://opendev.org/openstack/tempest.git#egg=tempest -git+https://opendev.org/openstack/cinder-tempest-plugin.git#egg=cinder-tempest-plugin From 1beef2ddb2426b70160055432ff59f7369d7630b Mon Sep 17 00:00:00 2001 From: Liam Young Date: Fri, 29 May 2020 13:49:03 +0000 Subject: [PATCH 093/140] Port designate-bind expand and shrink test Port designate-bind expand and shrink test (and helpers) from mojo to zaza. The mojo designate-bind test is here *1 *1 https://github.com/openstack-charmers/openstack-mojo-specs/blob/master/helper/tests/expand_and_shrink_bind.py --- zaza/openstack/charm_tests/designate/tests.py | 62 ++++++ zaza/openstack/charm_tests/designate/utils.py | 205 ++++++++++++++++++ 2 files changed, 267 insertions(+) create mode 100644 zaza/openstack/charm_tests/designate/utils.py diff --git a/zaza/openstack/charm_tests/designate/tests.py b/zaza/openstack/charm_tests/designate/tests.py index 5583f0b..94c5f31 100644 --- a/zaza/openstack/charm_tests/designate/tests.py +++ b/zaza/openstack/charm_tests/designate/tests.py @@ -14,14 +14,19 @@ """Encapsulate designate testing.""" import logging +import unittest import tenacity import subprocess + import designateclient.v1.domains as domains import designateclient.v1.records as records import designateclient.v1.servers as servers + +import zaza.model import zaza.openstack.utilities.juju as zaza_juju import zaza.openstack.charm_tests.test_utils as test_utils import zaza.openstack.utilities.openstack as openstack_utils +import zaza.openstack.charm_tests.designate.utils as designate_utils class BaseDesignateTest(test_utils.OpenStackBaseTest): @@ -255,3 +260,60 @@ class DesignateTests(DesignateAPITests, DesignateCharmTests): """Collection of all Designate test classes.""" pass + + +class DesignateBindExpand(BaseDesignateTest): + """Test expanding and shrinking bind.""" + + TEST_DOMAIN = 'zazabindtesting.com.' + TEST_NS1_RECORD = 'ns1.{}'.format(TEST_DOMAIN) + TEST_NS2_RECORD = 'ns2.{}'.format(TEST_DOMAIN) + TEST_WWW_RECORD = "www.{}".format(TEST_DOMAIN) + TEST_RECORD = {TEST_WWW_RECORD: '10.0.0.24'} + + def test_expand_and_contract(self): + """Test expanding and shrinking bind.""" + if not self.post_xenial_queens: + raise unittest.SkipTest("Test not supported before Queens") + + domain = designate_utils.create_or_return_zone( + self.designate, + name=self.TEST_DOMAIN, + email="test@zaza.com") + + designate_utils.create_or_return_recordset( + self.designate, + domain['id'], + 'www', + 'A', + [self.TEST_RECORD[self.TEST_WWW_RECORD]]) + + # Test record is in bind and designate + designate_utils.check_dns_entry( + self.designate, + self.TEST_RECORD[self.TEST_WWW_RECORD], + self.TEST_DOMAIN, + record_name=self.TEST_WWW_RECORD) + + logging.debug('Adding a designate-bind unit') + zaza.model.add_unit('designate-bind') + zaza.model.block_until_all_units_idle() + + logging.debug('Performing DNS lookup on all units') + designate_utils.check_dns_entry( + self.designate, + self.TEST_RECORD[self.TEST_WWW_RECORD], + self.TEST_DOMAIN, + record_name=self.TEST_WWW_RECORD) + + units = zaza.model.get_status().applications['designate-bind']['units'] + doomed_unit = sorted(units.keys())[0] + logging.debug('Removing {}'.format(doomed_unit)) + zaza.model.destroy_unit('designate-bind', doomed_unit) + + logging.debug('Performing DNS lookup on all units') + designate_utils.check_dns_entry( + self.designate, + self.TEST_RECORD[self.TEST_WWW_RECORD], + self.TEST_DOMAIN, + record_name=self.TEST_WWW_RECORD) diff --git a/zaza/openstack/charm_tests/designate/utils.py b/zaza/openstack/charm_tests/designate/utils.py new file mode 100644 index 0000000..bde69ff --- /dev/null +++ b/zaza/openstack/charm_tests/designate/utils.py @@ -0,0 +1,205 @@ +"""Utilities for interacting with designate.""" + +import dns.resolver +import logging +import tenacity + +import designateclient.exceptions + +import zaza.model + + +def create_or_return_zone(client, name, email): + """Create zone or return matching existing zone. + + :param designate_client: Client to query designate + :type designate_client: designateclient.v2.Client + :param name: Name of zone + :type name: str + :param email: Email address to associate with zone. + :type email: str + :returns: Zone + :rtype: designateclient.v2.zones.Zone + """ + try: + zone = client.zones.create( + name=name, + email=email) + except designateclient.exceptions.Conflict: + logging.info('{} zone already exists.'.format(name)) + zones = [z for z in client.zones.list() if z['name'] == name] + assert len(zones) == 1, "Wrong number of zones found {}".format(zones) + zone = zones[0] + return zone + + +def create_or_return_recordset(client, zone_id, sub_domain, record_type, data): + """Create recordset or return matching existing recordset. + + :param designate_client: Client to query designate + :type designate_client: designateclient.v2.Client + :param zone_id: uuid of zone + :type zone_id: str + :param sub_domain: Subdomain to associate records with + :type sub_domain: str + :param data: Dictionary of entries eg {'www.test.com': '10.0.0.24'} + :type data: dict + :returns: RecordSet + :rtype: designateclient.v2.recordsets.RecordSet + """ + try: + rs = client.recordsets.create( + zone_id, + sub_domain, + record_type, + data) + except designateclient.exceptions.Conflict: + logging.info('{} record already exists.'.format(data)) + for r in client.recordsets.list(zone_id): + if r['name'].split('.')[0] == sub_domain: + rs = r + return rs + + +def get_designate_zone_objects(designate_client, domain_name=None, + domain_id=None): + """Get all domains matching a given domain_name or domain_id. + + :param designate_client: Client to query designate + :type designate_client: designateclient.v2.Client + :param domain_name: Name of domain to lookup + :type domain_name: str + :param domain_id: UUID of domain to lookup + :type domain_id: str + :returns: List of Domain objects matching domain_name or domain_id + :rtype: [designateclient.v2.domains.Domain,] + """ + all_zones = designate_client.zones.list() + a = [z for z in all_zones + if z['name'] == domain_name or z['id'] == domain_id] + return a + + +def get_designate_domain_object(designate_client, domain_name): + """Get the one and only domain matching the given domain_name. + + :param designate_client: Client to query designate + :type designate_client: designateclient.v2.Client + :param domain_name: Name of domain to lookup + :type domain_name:str + :returns: Domain with name domain_name + :rtype: designateclient.v2.domains.Domain + :raises: AssertionError + """ + dns_zone_id = get_designate_zone_objects(designate_client, + domain_name=domain_name) + msg = "Found {} domains for {}".format( + len(dns_zone_id), + domain_name) + assert len(dns_zone_id) == 1, msg + return dns_zone_id[0] + + +def get_designate_dns_records(designate_client, domain_name, ip): + """Look for records in designate that match the given ip. + + :param designate_client: Client to query designate + :type designate_client: designateclient.v2.Client + :param domain_name: Name of domain to lookup + :type domain_name:str + :returns: List of Record objects matching matching IP address + :rtype: [designateclient.v2.records.Record,] + """ + dns_zone = get_designate_domain_object(designate_client, domain_name) + return [r for r in designate_client.recordsets.list(dns_zone['id']) + if r['records'] == ip] + + +def check_dns_record_exists(dns_server_ip, query_name, expected_ip, + retry_count=3): + """Lookup a DNS record against the given dns server address. + + :param dns_server_ip: IP address to run query against + :type dns_server_ip: str + :param query_name: Record to lookup + :type query_name: str + :param expected_ip: IP address expected to be associated with record. + :type expected_ip: str + :param retry_count: Number of times to retry query. Useful if waiting + for record to propagate. + :type retry_count: int + :raises: AssertionError + """ + my_resolver = dns.resolver.Resolver() + my_resolver.nameservers = [dns_server_ip] + for attempt in tenacity.Retrying( + stop=tenacity.stop_after_attempt(retry_count), + wait=tenacity.wait_exponential(multiplier=1, min=2, max=10), + reraise=True): + with attempt: + logging.info("Checking record {} against {}".format( + query_name, + dns_server_ip)) + answers = my_resolver.query(query_name) + for rdata in answers: + logging.info("Checking address returned by {} is correct".format( + dns_server_ip)) + assert str(rdata) == expected_ip + + +def check_dns_entry(des_client, ip, domain, record_name): + """Check that record for ip is in designate and in bind. + + :param ip: IP address to lookup + :type ip: str + :param domain_name: Domain to look for record in + :type domain_name:str + :param record_name: record name + :type record_name: str + """ + check_dns_entry_in_designate(des_client, [ip], domain, + record_name=record_name) + check_dns_entry_in_bind(ip, record_name) + + +def check_dns_entry_in_designate(des_client, ip, domain, record_name=None): + """Look for records in designate that match the given ip domain. + + :param designate_client: Client to query designate + :type designate_client: designateclient.v2.Client + :param ip: IP address to lookup in designate + :type ip: str + :param domain_name: Name of domain to lookup + :type domain_name:str + :param record_name: Retrieved record should have this name + :type record_name: str + :raises: AssertionError + """ + records = get_designate_dns_records(des_client, domain, ip) + assert records, "Record not found for {} in designate".format(ip) + logging.info('Found record in {} for {} in designate'.format(domain, ip)) + + if record_name: + recs = [r for r in records if r['name'] == record_name] + assert recs, "No DNS entry name matches expected name {}".format( + record_name) + logging.info('Found record in {} for {} in designate'.format( + domain, + record_name)) + + +def check_dns_entry_in_bind(ip, record_name, model_name=None): + """Check that record for ip address is in bind. + + :param ip: IP address to lookup + :type ip: str + :param record_name: record name + :type record_name: str + """ + for addr in zaza.model.get_app_ips('designate-bind', + model_name=model_name): + logging.info("Checking {} is {} against ({})".format( + record_name, + ip, + addr)) + check_dns_record_exists(addr, record_name, ip, retry_count=6) From 3a6002b61f3bfc86829f16569f7a624d0454bb0c Mon Sep 17 00:00:00 2001 From: Liam Young Date: Sat, 30 May 2020 16:56:25 +0000 Subject: [PATCH 094/140] Port aodh server alarm test from mojo Port over the aodh alarm test from mojo. Mojo source is here: *1 *1: https://github.com/openstack-charmers/openstack-mojo-specs/blob/master/helper/tests/validate_aodh.py --- zaza/openstack/charm_tests/aodh/tests.py | 79 +++++++++++++++++++++++- zaza/openstack/configure/telemetry.py | 24 +++++++ 2 files changed, 102 insertions(+), 1 deletion(-) diff --git a/zaza/openstack/charm_tests/aodh/tests.py b/zaza/openstack/charm_tests/aodh/tests.py index c11e054..e0178df 100644 --- a/zaza/openstack/charm_tests/aodh/tests.py +++ b/zaza/openstack/charm_tests/aodh/tests.py @@ -19,7 +19,11 @@ import logging import tenacity +import novaclient.exceptions + import zaza.model +import zaza.openstack.configure.guest +import zaza.openstack.charm_tests.glance.setup as glance_setup import zaza.openstack.charm_tests.test_utils as test_utils import zaza.openstack.utilities.openstack as openstack_utils import zaza.openstack.configure.telemetry as telemetry_utils @@ -33,7 +37,7 @@ class AodhTest(test_utils.OpenStackBaseTest): @classmethod def setUpClass(cls): """Run class setup for running tests.""" - super(AodhTest, cls).setUpClass() + super(AodhTest, cls).setUpClass(application_name='aodh') cls.xenial_ocata = openstack_utils.get_os_release('xenial_ocata') cls.xenial_newton = openstack_utils.get_os_release('xenial_newton') cls.bionic_stein = openstack_utils.get_os_release('bionic_stein') @@ -134,3 +138,76 @@ class AodhTest(test_utils.OpenStackBaseTest): pgrep_full=False): logging.info("Testing pause resume") self.query_aodh_api() + + +class AodhServerAlarmTest(test_utils.OpenStackBaseTest): + """Test server events trigger Aodh alarms.""" + + RESOURCE_PREFIX = 'zaza-aodhtests' + + @classmethod + def setUpClass(cls): + """Run class setup for running tests.""" + super(AodhServerAlarmTest, cls).setUpClass(application_name='aodh') + cls.aodh_client = openstack_utils.get_aodh_session_client( + cls.keystone_session) + cls.nova_client = openstack_utils.get_nova_session_client( + cls.keystone_session) + cls.run_resource_cleanup = True + + @classmethod + def resource_cleanup(cls): + """Remove test resources.""" + logging.info('Running teardown') + for alarm in cls.aodh_client.alarm.list(): + if alarm['name'].startswith(cls.RESOURCE_PREFIX): + logging.info('Removing Alarm {}'.format(alarm['name'])) + telemetry_utils.delete_alarm( + cls.aodh_client, + alarm['name'], + cache_wait=False) + for server in cls.nova_client.servers.list(): + if server.name.startswith(cls.RESOURCE_PREFIX): + logging.info('Removing server {}'.format(server.name)) + openstack_utils.delete_resource( + cls.nova_client.servers, + server.id, + msg="server") + + def test_alarm_on_power_off(self): + """Test server alarm is triggered when server is powered off.""" + server_name = '{}-server'.format(self.RESOURCE_PREFIX) + alarm_name = '{}_instance_off'.format(self.RESOURCE_PREFIX) + try: + server = self.nova_client.servers.find(name=server_name) + logging.info("Found existing server {}".format(server_name)) + except novaclient.exceptions.NotFound: + logging.info("Launching new server {}".format(server_name)) + server = zaza.openstack.configure.guest.launch_instance( + glance_setup.LTS_IMAGE_NAME, + vm_name=server_name) + assert server.status == 'ACTIVE', "Server {} not active".format( + server.name) + + logging.info('Deleting alarm {} if it exists'.format(alarm_name)) + telemetry_utils.delete_alarm( + self.aodh_client, + alarm_name, + cache_wait=True) + logging.info('Creating alarm {}'.format(alarm_name)) + alarm_info = telemetry_utils.create_server_power_off_alarm( + self.aodh_client, + alarm_name, + server.id) + alarm_state = telemetry_utils.get_alarm_state( + self.aodh_client, + alarm_info['alarm_id']) + logging.info('Alarm in state {}'.format(alarm_state)) + # Until data is collected alarm come up in an 'insufficient data' + # state. + self.assertEqual(alarm_state, 'insufficient data') + logging.info('Stopping server {}'.format(server.name)) + server.stop() + telemetry_utils.block_until_alarm_state( + self.aodh_client, + alarm_info['alarm_id']) diff --git a/zaza/openstack/configure/telemetry.py b/zaza/openstack/configure/telemetry.py index 86102ac..8bc01b8 100644 --- a/zaza/openstack/configure/telemetry.py +++ b/zaza/openstack/configure/telemetry.py @@ -18,6 +18,8 @@ Functions for managing masakari resources and simulating compute node loss and recovery. """ +import logging +import tenacity import time import zaza.model @@ -119,3 +121,25 @@ def create_server_power_off_alarm(aodh_client, alarm_name, server_uuid): 'type': 'string', 'value': server_uuid}]}} return aodh_client.alarm.create(alarm_def) + + +def block_until_alarm_state(aodh_client, alarm_id, target_state='alarm'): + """Block until alarm has reached target state. + + :param aodh_client: Authenticated aodh v2 client + :type aodh_client: aodhclient.v2.client.Client + :param alarm_id: ID of provided alarm + :type alarm_id: str + :param target_state: uuid of alarm to check + :stype target_state: str + """ + for attempt in tenacity.Retrying( + stop=tenacity.stop_after_attempt(3), + wait=tenacity.wait_exponential(multiplier=1, min=2, max=10)): + with attempt: + alarm_state = get_alarm_state( + aodh_client, + alarm_id) + + logging.info('Alarm in state {}'.format(alarm_state)) + assert alarm_state == target_state From 944335a39ff4f7cd3e74a5c61eaa86fe7ddf5b8f Mon Sep 17 00:00:00 2001 From: Liam Young Date: Wed, 3 Jun 2020 07:19:11 +0000 Subject: [PATCH 095/140] Add dnspython dep and wait for add/remove units --- setup.py | 1 + zaza/openstack/charm_tests/designate/tests.py | 21 +++++++++++++------ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index 6d08832..a77d425 100644 --- a/setup.py +++ b/setup.py @@ -29,6 +29,7 @@ install_require = [ 'async_generator', 'boto3', 'cryptography', + 'dnspython', 'hvac<0.7.0', 'jinja2', 'juju', diff --git a/zaza/openstack/charm_tests/designate/tests.py b/zaza/openstack/charm_tests/designate/tests.py index 94c5f31..3c56ee1 100644 --- a/zaza/openstack/charm_tests/designate/tests.py +++ b/zaza/openstack/charm_tests/designate/tests.py @@ -27,6 +27,7 @@ import zaza.openstack.utilities.juju as zaza_juju import zaza.openstack.charm_tests.test_utils as test_utils import zaza.openstack.utilities.openstack as openstack_utils import zaza.openstack.charm_tests.designate.utils as designate_utils +import zaza.charm_lifecycle.utils as lifecycle_utils class BaseDesignateTest(test_utils.OpenStackBaseTest): @@ -273,6 +274,8 @@ class DesignateBindExpand(BaseDesignateTest): def test_expand_and_contract(self): """Test expanding and shrinking bind.""" + test_config = lifecycle_utils.get_charm_config(fatal=False) + states = test_config.get("target_deploy_status", {}) if not self.post_xenial_queens: raise unittest.SkipTest("Test not supported before Queens") @@ -295,11 +298,12 @@ class DesignateBindExpand(BaseDesignateTest): self.TEST_DOMAIN, record_name=self.TEST_WWW_RECORD) - logging.debug('Adding a designate-bind unit') - zaza.model.add_unit('designate-bind') + logging.info('Adding a designate-bind unit') + zaza.model.add_unit('designate-bind', wait_appear=True) zaza.model.block_until_all_units_idle() + zaza.model.wait_for_application_states(states=states) - logging.debug('Performing DNS lookup on all units') + logging.info('Performing DNS lookup on all units') designate_utils.check_dns_entry( self.designate, self.TEST_RECORD[self.TEST_WWW_RECORD], @@ -308,10 +312,15 @@ class DesignateBindExpand(BaseDesignateTest): units = zaza.model.get_status().applications['designate-bind']['units'] doomed_unit = sorted(units.keys())[0] - logging.debug('Removing {}'.format(doomed_unit)) - zaza.model.destroy_unit('designate-bind', doomed_unit) + logging.info('Removing {}'.format(doomed_unit)) + zaza.model.destroy_unit( + 'designate-bind', + doomed_unit, + wait_disappear=True) + zaza.model.block_until_all_units_idle() + zaza.model.wait_for_application_states(states=states) - logging.debug('Performing DNS lookup on all units') + logging.info('Performing DNS lookup on all units') designate_utils.check_dns_entry( self.designate, self.TEST_RECORD[self.TEST_WWW_RECORD], From a6b2a9799de9219c23cc06238fc151f9066c3184 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Thu, 4 Jun 2020 12:32:54 +0000 Subject: [PATCH 096/140] Fix hacluster tests Tidy up hacluster tests. In the process this fixes the error: ``` UnboundLocalError: local variable 'primary_status' referenced before assignment ``` This was caused by libjuju now returning an empty dict rather than None when listing a subordinates units. --- zaza/openstack/charm_tests/hacluster/tests.py | 76 ++----------------- 1 file changed, 6 insertions(+), 70 deletions(-) diff --git a/zaza/openstack/charm_tests/hacluster/tests.py b/zaza/openstack/charm_tests/hacluster/tests.py index ebffb84..199d915 100644 --- a/zaza/openstack/charm_tests/hacluster/tests.py +++ b/zaza/openstack/charm_tests/hacluster/tests.py @@ -20,7 +20,6 @@ import logging import os import zaza.openstack.charm_tests.test_utils as test_utils -import zaza.openstack.utilities.juju as juju_utils import zaza.openstack.configure.hacluster @@ -35,78 +34,15 @@ class HaclusterTest(test_utils.OpenStackBaseTest): def test_900_action_cleanup(self): """The services can be cleaned up.""" - status = zaza.model.get_status().applications[self.application_name] - - # libjuju juju status no longer has units for subordinate charms - # Use the application it is subordinate-to to check workload status - if status.get("units") is None and status.get("subordinate-to"): - primary_status = juju_utils.get_application_status( - status.get("subordinate-to")[0]) - leader = None - for unit in primary_status["units"]: - if primary_status["units"][unit].get('leader'): - leader = unit - - if primary_status["units"][leader].get("subordinates"): - for subordinate in primary_status["units"][leader]["subordinates"]: - # mysql-router is a subordinate from focal onwards - _app = subordinate.split('/')[0] - if _app != 'hacluster': - continue - logging.info("Cleaning {}".format(subordinate)) - _action = "cleanup" - action_id = zaza.model.run_action(subordinate, "cleanup") - assert "success" in action_id.data["results"]["result"], ( - "Set hacluster action {} failed: {}" - .format(_action, action_id.data)) - - logging.info("Cleaning action w/resource {}" - .format(subordinate)) - params = {'resource': 'res_ks_haproxy'} - _action = "cleanup res_ks_haproxy" - zaza.model.run_action(subordinate, "cleanup", - action_params=params) - assert "success" in action_id.data["results"]["result"], ( - "Set hacluster action {} failed: {}" - .format(_action, action_id.data)) + zaza.model.run_action_on_leader( + self.application_name, + 'cleanup', + raise_on_failure=True) def test_910_pause_and_resume(self): """The services can be paused and resumed.""" - logging.debug('Checking pause and resume actions...') - - status = zaza.model.get_status().applications[self.application_name] - - # libjuju juju status no longer has units for subordinate charms - # Use the application it is subordinate-to to check workload status - if status.get("units") is None and status.get("subordinate-to"): - primary_status = juju_utils.get_application_status( - status.get("subordinate-to")[0]) - leader = None - for unit in primary_status["units"]: - if primary_status["units"][unit].get('leader'): - leader = unit - - if primary_status["units"][leader].get("subordinates"): - for subordinate in primary_status["units"][leader]["subordinates"]: - # mysql-router is a subordinate from focal onwards - _app = subordinate.split('/')[0] - if _app != 'hacluster': - continue - logging.info("Pausing {}".format(subordinate)) - zaza.model.run_action(subordinate, "pause") - zaza.model.block_until_unit_wl_status( - subordinate, - "maintenance") - - logging.info("Resuming {}".format(subordinate)) - zaza.model.run_action(subordinate, "resume") - zaza.model.block_until_unit_wl_status(subordinate, "active") - - _states = {"hacluster": { - "workload-status": "active", - "workload-status-message": "Unit is ready and clustered"}} - zaza.model.wait_for_application_states(states=_states) - logging.debug('OK') + with self.pause_resume([]): + logging.info("Testing pause resume") def _toggle_maintenance_and_wait(self, expected): """Configure cluster maintenance-mode. From 46d9887b45a63468f1aaf7e752d1c25ef5ee3dcb Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Fri, 5 Jun 2020 14:35:29 +0000 Subject: [PATCH 097/140] Move get_application_ip to zaza/openstack/utilities/juju.py --- zaza/openstack/charm_tests/tempest/setup.py | 28 +++------------------ zaza/openstack/utilities/juju.py | 21 ++++++++++++++++ 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py index 9f442bc..f8772ea 100644 --- a/zaza/openstack/charm_tests/tempest/setup.py +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -20,6 +20,7 @@ import subprocess import zaza.model import zaza.utilities.deployment_env as deployment_env +import zaza.openstack.utilities.juju as juju_utils import zaza.openstack.utilities.openstack as openstack_utils import zaza.openstack.charm_tests.glance.setup as glance_setup import zaza.openstack.charm_tests.tempest.templates.tempest_v2 as tempest_v2 @@ -42,27 +43,6 @@ TEMPEST_ALT_FLAVOR_NAME = 'm2.tempest' TEMPEST_CIRROS_ALT_IMAGE_NAME = 'cirros_alt' -def get_app_access_ip(application_name): - """Get the application's access IP. - - :param application_name: Name of application - :type application_name: str - :returns: Application's access IP - :rtype: str - """ - try: - app_config = zaza.model.get_application_config(application_name) - except KeyError: - return '' - vip = app_config.get("vip").get("value") - if vip: - ip = vip - else: - unit = zaza.model.get_units(application_name)[0] - ip = unit.public_address - return ip - - def add_application_ips(ctxt): """Add application access IPs to context. @@ -71,9 +51,9 @@ def add_application_ips(ctxt): :returns: None :rtype: None """ - ctxt['keystone'] = get_app_access_ip('keystone') - ctxt['dashboard'] = get_app_access_ip('openstack-dashboard') - ctxt['ncc'] = get_app_access_ip('nova-cloud-controller') + ctxt['keystone'] = juju_utils.get_application_ip('keystone') + ctxt['dashboard'] = juju_utils.get_application_ip('openstack-dashboard') + ctxt['ncc'] = juju_utils.get_application_ip('nova-cloud-controller') def add_nova_config(ctxt, keystone_session): diff --git a/zaza/openstack/utilities/juju.py b/zaza/openstack/utilities/juju.py index b3e54bc..191bc99 100644 --- a/zaza/openstack/utilities/juju.py +++ b/zaza/openstack/utilities/juju.py @@ -49,6 +49,27 @@ def get_application_status(application=None, unit=None, model_name=None): return status +def get_application_ip(application): + """Get the application's IP address. + + :param application: Application name + :type application: 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 = zaza.model.get_units(application_name)[0] + ip = unit.public_address + return ip + + def get_cloud_configs(cloud=None): """Get cloud configuration from local clouds.yaml. From 616f04f0bc8f67b1a744e3f8881a1fc3c88a9e97 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Fri, 5 Jun 2020 14:54:26 +0000 Subject: [PATCH 098/140] Move add_cirros_alt_image to glance setup.py and fix up loops --- zaza/openstack/charm_tests/glance/setup.py | 13 ++++++++ zaza/openstack/charm_tests/tempest/setup.py | 34 +++++---------------- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/zaza/openstack/charm_tests/glance/setup.py b/zaza/openstack/charm_tests/glance/setup.py index c992917..8c7bd3e 100644 --- a/zaza/openstack/charm_tests/glance/setup.py +++ b/zaza/openstack/charm_tests/glance/setup.py @@ -18,6 +18,7 @@ import logging import zaza.openstack.utilities.openstack as openstack_utils CIRROS_IMAGE_NAME = "cirros" +CIRROS_ALT_IMAGE_NAME = "cirros_alt" LTS_RELEASE = "bionic" LTS_IMAGE_NAME = "bionic" @@ -77,6 +78,18 @@ def add_cirros_image(glance_client=None, image_name=None): image_name=image_name) +def add_cirros_alt_image(glance_client=None, image_name=None): + """Add alt cirros image to the current deployment. + + :param glance: Authenticated glanceclient + :type glance: glanceclient.Client + :param image_name: Label for the image in glance + :type image_name: str + """ + image_name = image_name or CIRROS_ALT_IMAGE_NAME + add_cirros_image(glance_client, image_name) + + def add_lts_image(glance_client=None, image_name=None, release=None): """Add an Ubuntu LTS image to the current deployment. diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py index f8772ea..0e90597 100644 --- a/zaza/openstack/charm_tests/tempest/setup.py +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -40,7 +40,6 @@ SETUP_ENV_VARS = [ ] TEMPEST_FLAVOR_NAME = 'm1.tempest' TEMPEST_ALT_FLAVOR_NAME = 'm2.tempest' -TEMPEST_CIRROS_ALT_IMAGE_NAME = 'cirros_alt' def add_application_ips(ctxt): @@ -89,14 +88,10 @@ def add_neutron_config(ctxt, keystone_session): focal_ussuri = openstack_utils.get_os_release('focal_ussuri') neutron_client = openstack_utils.get_neutron_session_client( keystone_session) - for net in neutron_client.list_networks()['networks']: - if net['name'] == 'ext_net': - ctxt['ext_net'] = net['id'] - break - for router in neutron_client.list_routers()['routers']: - if router['name'] == 'provider-router': - ctxt['provider_router_id'] = router['id'] - break + net = neutron_client.find_resource("network", "ext_net") + ctxt['ext_net'] = net['id'] + router = neutron_client.find_resource("router", "provider-router") + ctxt['provider_router_id'] = router['id'] # For focal+ with OVN, we use the same settings as upstream gate. # This is because the l3_agent_scheduler extension is only # applicable for OVN when conventional layer-3 agent enabled: @@ -135,7 +130,7 @@ def add_glance_config(ctxt, keystone_session): image = openstack_utils.get_images_by_name( glance_client, glance_setup.CIRROS_IMAGE_NAME) image_alt = openstack_utils.get_images_by_name( - glance_client, TEMPEST_CIRROS_ALT_IMAGE_NAME) + glance_client, glance_setup.CIRROS_ALT_IMAGE_NAME) if image: ctxt['image_id'] = image[0].id if image_alt: @@ -174,10 +169,8 @@ def add_keystone_config(ctxt, keystone_session): """ keystone_client = openstack_utils.get_keystone_session_client( keystone_session) - for domain in keystone_client.domains.list(): - if domain.name == 'admin_domain': - ctxt['default_domain_id'] = domain.id - break + domain = keystone_client.domains.find(name="admin_domain") + ctxt['default_domain_id'] = domain.id def add_environment_var_config(ctxt): @@ -302,19 +295,6 @@ def render_tempest_config_keystone_v3(): setup_tempest(tempest_v3, accounts) -def add_cirros_alt_image(): - """Add cirros alternate image to overcloud. - - :returns: None - :rtype: None - """ - image_url = openstack_utils.find_cirros_image(arch='x86_64') - glance_setup.add_image( - image_url, - glance_client=None, - image_name=TEMPEST_CIRROS_ALT_IMAGE_NAME) - - def add_tempest_flavors(): """Add tempest flavors to overcloud. From 3837b1ac85f4852c5314ab23e0180725a3e22c96 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Fri, 5 Jun 2020 15:54:33 +0000 Subject: [PATCH 099/140] Drop add_tempest_flavors in favor of using nova.setup.create_flavors --- zaza/openstack/charm_tests/nova/utils.py | 10 ++++++++ zaza/openstack/charm_tests/tempest/setup.py | 27 --------------------- 2 files changed, 10 insertions(+), 27 deletions(-) diff --git a/zaza/openstack/charm_tests/nova/utils.py b/zaza/openstack/charm_tests/nova/utils.py index 974edd7..f2fcefc 100644 --- a/zaza/openstack/charm_tests/nova/utils.py +++ b/zaza/openstack/charm_tests/nova/utils.py @@ -35,5 +35,15 @@ FLAVORS = { 'ram': 8192, 'disk': 40, 'vcpus': 4}, + 'm1.tempest': { + 'flavorid': 5, + 'ram': 256, + 'disk': 1, + 'vcpus': 1}, + 'm2.tempest': { + 'flavorid': 6, + 'ram': 512, + 'disk': 1, + 'vcpus': 1}, } KEYPAIR_NAME = 'zaza' diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py index 0e90597..f93b3d3 100644 --- a/zaza/openstack/charm_tests/tempest/setup.py +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -295,33 +295,6 @@ def render_tempest_config_keystone_v3(): setup_tempest(tempest_v3, accounts) -def add_tempest_flavors(): - """Add tempest flavors to overcloud. - - :returns: None - :rtype: None - """ - keystone_session = openstack_utils.get_overcloud_keystone_session() - nova_client = openstack_utils.get_nova_session_client( - keystone_session) - try: - nova_client.flavors.create( - name=TEMPEST_FLAVOR_NAME, - ram=256, - vcpus=1, - disk=1) - except novaclient.exceptions.Conflict: - pass - try: - nova_client.flavors.create( - name=TEMPEST_ALT_FLAVOR_NAME, - ram=512, - vcpus=1, - disk=1) - except novaclient.exceptions.Conflict: - pass - - def add_tempest_roles(): """Add tempest roles overcloud. From 4e993e425167078a7fea63368b8a487440316c12 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Fri, 5 Jun 2020 17:56:10 +0000 Subject: [PATCH 100/140] Move add_tempest_roles to keystone setup.py --- .../charm_tests/keystone/__init__.py | 2 ++ zaza/openstack/charm_tests/keystone/setup.py | 30 +++++++++++++++++++ zaza/openstack/charm_tests/tempest/setup.py | 29 ++++-------------- 3 files changed, 37 insertions(+), 24 deletions(-) diff --git a/zaza/openstack/charm_tests/keystone/__init__.py b/zaza/openstack/charm_tests/keystone/__init__.py index 6c4cca6..4d2e829 100644 --- a/zaza/openstack/charm_tests/keystone/__init__.py +++ b/zaza/openstack/charm_tests/keystone/__init__.py @@ -26,6 +26,8 @@ DEMO_ADMIN_USER_PASSWORD = 'password' DEMO_USER = 'demo' DEMO_PASSWORD = 'password' +TEMPEST_ROLES = ['member', 'ResellerAdmin'] + class BaseKeystoneTest(test_utils.OpenStackBaseTest): """Base for Keystone charm tests.""" diff --git a/zaza/openstack/charm_tests/keystone/setup.py b/zaza/openstack/charm_tests/keystone/setup.py index 688e3cd..6dbb7c1 100644 --- a/zaza/openstack/charm_tests/keystone/setup.py +++ b/zaza/openstack/charm_tests/keystone/setup.py @@ -14,6 +14,8 @@ """Code for setting up keystone.""" +import keystoneauth1 + import zaza.openstack.utilities.openstack as openstack_utils from zaza.openstack.charm_tests.keystone import ( BaseKeystoneTest, @@ -24,6 +26,7 @@ from zaza.openstack.charm_tests.keystone import ( DEMO_ADMIN_USER_PASSWORD, DEMO_USER, DEMO_PASSWORD, + TEMPEST_ROLES, ) @@ -115,3 +118,30 @@ def add_demo_user(): else: # create only V3 user _v3() + + +def _add_additional_roles(roles): + """Add additional roles to this deployment. + + :param ctxt: roles + :type ctxt: list + :returns: None + :rtype: None + """ + keystone_session = openstack_utils.get_overcloud_keystone_session() + keystone_client = openstack_utils.get_keystone_session_client( + keystone_session) + for role_name in roles: + try: + keystone_client.roles.create(role_name) + except keystoneauth1.exceptions.http.Conflict: + pass + + +def add_tempest_roles(): + """Add tempest roles to this deployment. + + :returns: None + :rtype: None + """ + _add_additional_roles(TEMPEST_ROLES) diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py index f93b3d3..1b5b4de 100644 --- a/zaza/openstack/charm_tests/tempest/setup.py +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -27,9 +27,6 @@ import zaza.openstack.charm_tests.tempest.templates.tempest_v2 as tempest_v2 import zaza.openstack.charm_tests.tempest.templates.tempest_v3 as tempest_v3 import zaza.openstack.charm_tests.tempest.templates.accounts as accounts -import keystoneauth1 -import novaclient - SETUP_ENV_VARS = [ 'OS_TEST_GATEWAY', 'OS_TEST_CIDR_EXT', @@ -60,7 +57,7 @@ def add_nova_config(ctxt, keystone_session): :param ctxt: Context dictionary :type ctxt: dict - :returns keystone_session: keystoneauth1.session.Session object + :param keystone_session: keystoneauth1.session.Session object :type: keystoneauth1.session.Session :returns: None :rtype: None @@ -79,7 +76,7 @@ def add_neutron_config(ctxt, keystone_session): :param ctxt: Context dictionary :type ctxt: dict - :returns keystone_session: keystoneauth1.session.Session object + :param keystone_session: keystoneauth1.session.Session object :type: keystoneauth1.session.Session :returns: None :rtype: None @@ -120,7 +117,7 @@ def add_glance_config(ctxt, keystone_session): :param ctxt: Context dictionary :type ctxt: dict - :returns keystone_session: keystoneauth1.session.Session object + :param keystone_session: keystoneauth1.session.Session object :type: keystoneauth1.session.Session :returns: None :rtype: None @@ -142,7 +139,7 @@ def add_cinder_config(ctxt, keystone_session): :param ctxt: Context dictionary :type ctxt: dict - :returns keystone_session: keystoneauth1.session.Session object + :param keystone_session: keystoneauth1.session.Session object :type: keystoneauth1.session.Session :returns: None :rtype: None @@ -162,7 +159,7 @@ def add_keystone_config(ctxt, keystone_session): :param ctxt: Context dictionary :type ctxt: dict - :returns keystone_session: keystoneauth1.session.Session object + :param keystone_session: keystoneauth1.session.Session object :type: keystoneauth1.session.Session :returns: None :rtype: None @@ -293,19 +290,3 @@ def render_tempest_config_keystone_v3(): :rtype: None """ setup_tempest(tempest_v3, accounts) - - -def add_tempest_roles(): - """Add tempest roles overcloud. - - :returns: None - :rtype: None - """ - keystone_session = openstack_utils.get_overcloud_keystone_session() - keystone_client = openstack_utils.get_keystone_session_client( - keystone_session) - for role_name in ['member', 'ResellerAdmin']: - try: - keystone_client.roles.create(role_name) - except keystoneauth1.exceptions.http.Conflict: - pass From 7d0dfadc80016f31ac156c459e1bd85577c2bc31 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Fri, 5 Jun 2020 20:02:45 +0000 Subject: [PATCH 101/140] Fix incorrect reference to model --- zaza/openstack/utilities/juju.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zaza/openstack/utilities/juju.py b/zaza/openstack/utilities/juju.py index 191bc99..069801d 100644 --- a/zaza/openstack/utilities/juju.py +++ b/zaza/openstack/utilities/juju.py @@ -65,7 +65,7 @@ def get_application_ip(application): if vip: ip = vip else: - unit = zaza.model.get_units(application_name)[0] + unit = model.get_units(application_name)[0] ip = unit.public_address return ip From 5401be3a9700782d84bef0f78cdd3ccc06d6378a Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Fri, 5 Jun 2020 20:04:51 +0000 Subject: [PATCH 102/140] Fix variable name --- zaza/openstack/utilities/juju.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zaza/openstack/utilities/juju.py b/zaza/openstack/utilities/juju.py index 069801d..3bdf4b8 100644 --- a/zaza/openstack/utilities/juju.py +++ b/zaza/openstack/utilities/juju.py @@ -65,7 +65,7 @@ def get_application_ip(application): if vip: ip = vip else: - unit = model.get_units(application_name)[0] + unit = model.get_units(application)[0] ip = unit.public_address return ip From 5d59fa43a5907eb5d939f05f43d0196b75905778 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Fri, 5 Jun 2020 22:55:24 +0000 Subject: [PATCH 103/140] Update flavor ids --- zaza/openstack/charm_tests/nova/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zaza/openstack/charm_tests/nova/utils.py b/zaza/openstack/charm_tests/nova/utils.py index f2fcefc..b16eef4 100644 --- a/zaza/openstack/charm_tests/nova/utils.py +++ b/zaza/openstack/charm_tests/nova/utils.py @@ -36,12 +36,12 @@ FLAVORS = { 'disk': 40, 'vcpus': 4}, 'm1.tempest': { - 'flavorid': 5, + 'flavorid': 6, 'ram': 256, 'disk': 1, 'vcpus': 1}, 'm2.tempest': { - 'flavorid': 6, + 'flavorid': 7, 'ram': 512, 'disk': 1, 'vcpus': 1}, From 2e6a2ef534d61e7dc11526eb8408aaf41dd07263 Mon Sep 17 00:00:00 2001 From: Frode Nordahl Date: Sat, 6 Jun 2020 14:38:38 +0200 Subject: [PATCH 104/140] 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 105/140] 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 7f6ed4665d83bd7ff243856e999c8ba718898a29 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Tue, 9 Jun 2020 14:01:27 +0000 Subject: [PATCH 106/140] Fix pep8 error --- zaza/openstack/charm_tests/tempest/setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py index 1b5b4de..e6d7295 100644 --- a/zaza/openstack/charm_tests/tempest/setup.py +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -18,7 +18,6 @@ import urllib.parse import os import subprocess -import zaza.model import zaza.utilities.deployment_env as deployment_env import zaza.openstack.utilities.juju as juju_utils import zaza.openstack.utilities.openstack as openstack_utils From bdc2e96e93339a2fa3ec5a292ce90288de9118b6 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Tue, 9 Jun 2020 14:22:26 +0000 Subject: [PATCH 107/140] Drop OS_ prefix from SETUP_ENV_VARS --- zaza/openstack/charm_tests/tempest/setup.py | 12 ++++++------ .../charm_tests/tempest/templates/tempest_v2.py | 6 +++--- .../charm_tests/tempest/templates/tempest_v3.py | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py index e6d7295..4266ddc 100644 --- a/zaza/openstack/charm_tests/tempest/setup.py +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -27,12 +27,12 @@ import zaza.openstack.charm_tests.tempest.templates.tempest_v3 as tempest_v3 import zaza.openstack.charm_tests.tempest.templates.accounts as accounts SETUP_ENV_VARS = [ - 'OS_TEST_GATEWAY', - 'OS_TEST_CIDR_EXT', - 'OS_TEST_FIP_RANGE', - 'OS_TEST_NAMESERVER', - 'OS_TEST_CIDR_PRIV', - 'OS_TEST_SWIFT_IP', + 'TEST_GATEWAY', + 'TEST_CIDR_EXT', + 'TEST_FIP_RANGE', + 'TEST_NAMESERVER', + 'TEST_CIDR_PRIV', + 'TEST_SWIFT_IP', ] TEMPEST_FLAVOR_NAME = 'm1.tempest' TEMPEST_ALT_FLAVOR_NAME = 'm2.tempest' diff --git a/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py b/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py index c5649d0..8b0f939 100644 --- a/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py +++ b/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py @@ -44,12 +44,12 @@ api_v2 = true api_v3 = false [image] -http_image = http://{os_test_swift_ip}:80/swift/v1/images/cirros-0.3.4-x86_64-uec.tar.gz +http_image = http://{test_swift_ip}:80/swift/v1/images/cirros-0.3.4-x86_64-uec.tar.gz [network] -project_network_cidr = {os_test_cidr_priv} +project_network_cidr = {test_cidr_priv} public_network_id = {ext_net} -dns_servers = {os_test_nameserver} +dns_servers = {test_nameserver} project_networks_reachable = false [network-feature-enabled] diff --git a/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py b/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py index 4f183dd..5140deb 100644 --- a/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py +++ b/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py @@ -46,12 +46,12 @@ api_v2 = false api_v3 = true [image] -http_image = http://{os_test_swift_ip}:80/swift/v1/images/cirros-0.3.4-x86_64-uec.tar.gz +http_image = http://{test_swift_ip}:80/swift/v1/images/cirros-0.3.4-x86_64-uec.tar.gz [network] -project_network_cidr = {os_test_cidr_priv} +project_network_cidr = {test_cidr_priv} public_network_id = {ext_net} -dns_servers = {os_test_nameserver} +dns_servers = {test_nameserver} project_networks_reachable = false floating_network_name = {ext_net} From 9d9b47a27587df9db340a2c7eb87761f74c646ce Mon Sep 17 00:00:00 2001 From: Liam Young Date: Tue, 9 Jun 2020 15:06:01 +0000 Subject: [PATCH 108/140] Add test for networking when gateways are stopped Add test for networking when gateways are stopped. This includes refactoring the existing network test so I can reuse some of the code. --- zaza/openstack/charm_tests/neutron/tests.py | 191 ++++++++++++++++---- 1 file changed, 157 insertions(+), 34 deletions(-) diff --git a/zaza/openstack/charm_tests/neutron/tests.py b/zaza/openstack/charm_tests/neutron/tests.py index 6e2759b..7e28112 100644 --- a/zaza/openstack/charm_tests/neutron/tests.py +++ b/zaza/openstack/charm_tests/neutron/tests.py @@ -25,6 +25,8 @@ 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 @@ -598,8 +600,8 @@ class NeutronOpenvSwitchTest(NeutronPluginApiSharedTests): logging.info('Testing pause resume') -class NeutronNetworkingTest(unittest.TestCase): - """Ensure that openstack instances have valid networking.""" +class NeutronNetworkingBase(unittest.TestCase): + """Base for checking openstack instances have valid networking.""" RESOURCE_PREFIX = 'zaza-neutrontests' @@ -610,6 +612,8 @@ class NeutronNetworkingTest(unittest.TestCase): openstack_utils.get_overcloud_keystone_session()) cls.nova_client = ( openstack_utils.get_nova_session_client(cls.keystone_session)) + 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 @@ -629,38 +633,6 @@ class NeutronNetworkingTest(unittest.TestCase): server.id, msg="server") - def test_instances_have_networking(self): - """Validate North/South and East/West networking.""" - 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)) - - instance_1 = self.nova_client.servers.find( - name='{}-ins-1'.format(self.RESOURCE_PREFIX)) - - instance_2 = self.nova_client.servers.find( - name='{}-ins-2'.format(self.RESOURCE_PREFIX)) - - def verify(stdin, stdout, stderr): - """Validate that the SSH command exited 0.""" - self.assertEqual(stdout.channel.recv_exit_status(), 0) - - # Verify network from 1 to 2 - self.validate_instance_can_reach_other(instance_1, instance_2, verify) - - # Verify network from 2 to 1 - self.validate_instance_can_reach_other(instance_2, instance_1, verify) - - # Validate tenant to external network routing - self.validate_instance_can_reach_router(instance_1, verify) - self.validate_instance_can_reach_router(instance_2, verify) - - # If we get here, it means the tests passed - self.run_tearDown = True - @tenacity.retry(wait=tenacity.wait_exponential(multiplier=1, max=60), reraise=True, stop=tenacity.stop_after_attempt(8)) def validate_instance_can_reach_other(self, @@ -718,6 +690,99 @@ class NeutronNetworkingTest(unittest.TestCase): username, address, 'instance', 'ping -c 1 192.168.0.1', password=password, privkey=privkey, verify=verify) + @tenacity.retry(wait=tenacity.wait_exponential(min=5, max=60), + reraise=True, stop=tenacity.stop_after_attempt(8), + retry=tenacity.retry_if_exception_type(AssertionError)) + def check_server_state(self, nova_client, state, server_id=None, + server_name=None): + """Wait for server to reach desired state. + + :param nova_client: Nova client to use when checking status + :type nova_client: nova client + :param state: Target state for server + :type state: str + :param server_id: UUID of server to check + :type server_id: str + :param server_name: Name of server to check + :type server_name: str + :raises: AssertionError + """ + if server_name: + server_id = nova_client.servers.find(name=server_name).id + server = nova_client.servers.find(id=server_id) + assert server.status == state + + @tenacity.retry(wait=tenacity.wait_exponential(min=5, max=60), + reraise=True, stop=tenacity.stop_after_attempt(8), + retry=tenacity.retry_if_exception_type(AssertionError)) + def check_neutron_agent_up(self, neutron_client, host_name): + """Wait for agents to come up. + + :param neutron_client: Neutron client to use when checking status + :type neutron_client: neutron client + :param host_name: The name of the host whose agents need checking + :type host_name: str + :raises: AssertionError + """ + for agent in neutron_client.list_agents()['agents']: + if agent['host'] == host_name: + 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): + """Validate that the SSH command exited 0.""" + self.assertEqual(stdout.channel.recv_exit_status(), 0) + + # Verify network from 1 to 2 + self.validate_instance_can_reach_other(instance_1, instance_2, verify) + + # Verify network from 2 to 1 + self.validate_instance_can_reach_other(instance_2, instance_1, verify) + + # Validate tenant to external network routing + self.validate_instance_can_reach_router(instance_1, verify) + self.validate_instance_can_reach_router(instance_2, verify) + def floating_ips_from_instance(instance): """ @@ -764,3 +829,61 @@ def ips_from_instance(instance, ip_type): return list([ ip['addr'] for ip in instance.addresses['private'] if ip['OS-EXT-IPS:type'] == ip_type]) + + +class NeutronNetworkingTest(NeutronNetworkingBase): + """Ensure that openstack instances have valid networking.""" + + 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) + self.check_connectivity(instance_1, instance_2) + self.run_tearDown = True + + +class NeutronNetworkingVRRPTests(NeutronNetworkingBase): + """Check networking when gateways are restarted.""" + + def test_gateway_failure(self): + """Validate networking in the case of a gateway failure.""" + instance_1, instance_2 = self.retrieve_guests(self.nova_client) + if not all([instance_1, instance_2]): + self.launch_guests() + instance_1, instance_2 = self.retrieve_guests(self.nova_client) + self.check_connectivity(instance_1, instance_2) + + routers = self.neutron_client.list_routers( + name='provider-router')['routers'] + assert len(routers) == 1, "Unexpected router count {}".format( + len(routers)) + provider_router = routers[0] + l3_agents = self.neutron_client.list_l3_agent_hosting_routers( + router=provider_router['id'])['agents'] + logging.info( + 'Checking there are multiple L3 agents running tenant router') + assert len(l3_agents) == 2, "Unexpected l3 agent count {}".format( + len(l3_agents)) + uc_ks_session = openstack_utils.get_undercloud_keystone_session() + uc_nova_client = openstack_utils.get_nova_session_client(uc_ks_session) + uc_neutron_client = openstack_utils.get_neutron_session_client( + uc_ks_session) + for agent in l3_agents: + gateway_hostname = agent['host'] + gateway_server = uc_nova_client.servers.find(name=gateway_hostname) + logging.info("Shutting down {}".format(gateway_hostname)) + gateway_server.stop() + self.check_server_state( + uc_nova_client, + 'SHUTOFF', + server_name=gateway_hostname) + self.check_connectivity(instance_1, instance_2) + gateway_server.start() + self.check_server_state( + uc_nova_client, + 'ACTIVE', + server_name=gateway_hostname) + self.check_neutron_agent_up( + uc_neutron_client, + gateway_hostname) + self.check_connectivity(instance_1, instance_2) From 9844701d1984f181f6215202a36ac5e754bac714 Mon Sep 17 00:00:00 2001 From: Dmitrii Shcherbakov Date: Wed, 10 Jun 2020 14:18:29 +0300 Subject: [PATCH 109/140] Wait until a share status reaches 'available' (#310) * Wait until a share status reaches 'available' It appears to be that the test_manila_share test case does not wait until a share becomes available which results in failures like this: manilaclient.common.apiclient.exceptions.BadRequest: New access rules cannot be applied while the share or any of its replicas or migration copies lacks a valid host or is in an invalid state. (HTTP 400) (Request-ID: req-8d609e13-9a80-428b-953b-17ab8d0e0cae) Fixes: #309 --- zaza/openstack/charm_tests/manila_ganesha/tests.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/zaza/openstack/charm_tests/manila_ganesha/tests.py b/zaza/openstack/charm_tests/manila_ganesha/tests.py index e24a9f3..2f9f8be 100644 --- a/zaza/openstack/charm_tests/manila_ganesha/tests.py +++ b/zaza/openstack/charm_tests/manila_ganesha/tests.py @@ -74,6 +74,15 @@ packages: fip_1 = neutron_tests.floating_ips_from_instance(instance_1)[0] fip_2 = neutron_tests.floating_ips_from_instance(instance_2)[0] + # Wait for the created share to become available before it gets used. + openstack_utils.resource_reaches_status( + self.manila_client.shares, + share.id, + wait_iteration_max_time=120, + stop_after_attempt=2, + expected_status="available", + msg="Waiting for a share to become available") + share.allow(access_type='ip', access=fip_1, access_level='rw') share.allow(access_type='ip', access=fip_2, access_level='rw') From 8336b75c4029717b0aad09259169265fe39df2cf Mon Sep 17 00:00:00 2001 From: Liam Young Date: Thu, 11 Jun 2020 10:01:00 +0000 Subject: [PATCH 110/140] Add test to perform charm upgrade. Add test to perform charm upgrade and a few small tweaks to the upgrade utils. --- .../charm_tests/charm_upgrade/__init__.py | 15 ++++ .../charm_tests/charm_upgrade/tests.py | 78 +++++++++++++++++++ zaza/openstack/utilities/upgrade_utils.py | 57 +++++++++----- 3 files changed, 130 insertions(+), 20 deletions(-) create mode 100644 zaza/openstack/charm_tests/charm_upgrade/__init__.py create mode 100644 zaza/openstack/charm_tests/charm_upgrade/tests.py diff --git a/zaza/openstack/charm_tests/charm_upgrade/__init__.py b/zaza/openstack/charm_tests/charm_upgrade/__init__.py new file mode 100644 index 0000000..fc5baa5 --- /dev/null +++ b/zaza/openstack/charm_tests/charm_upgrade/__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. + +"""Test charm upgrade.""" diff --git a/zaza/openstack/charm_tests/charm_upgrade/tests.py b/zaza/openstack/charm_tests/charm_upgrade/tests.py new file mode 100644 index 0000000..0ffd30b --- /dev/null +++ b/zaza/openstack/charm_tests/charm_upgrade/tests.py @@ -0,0 +1,78 @@ +#!/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. + +"""Define class for Charm Upgrade.""" + +import logging +import unittest + +import zaza.model +from zaza.openstack.utilities import ( + cli as cli_utils, + upgrade_utils as upgrade_utils, +) +from zaza.openstack.charm_tests.nova.tests import LTSGuestCreateTest + + +class FullCloudCharmUpgradeTest(unittest.TestCase): + """Class to encapsulate Charm Upgrade Tests.""" + + @classmethod + def setUpClass(cls): + """Run setup for Charm Upgrades.""" + cli_utils.setup_logging() + cls.lts = LTSGuestCreateTest() + cls.target_charm_namespace = '~openstack-charmers-next' + + def get_upgrade_url(self, charm_url): + """Return the charm_url to upgrade to. + + :param charm_url: Current charm url. + :type charm_url: str + """ + charm_name = upgrade_utils.extract_charm_name_from_url( + charm_url) + next_charm_url = zaza.model.get_latest_charm_url( + "cs:{}/{}".format(self.target_charm_namespace, charm_name)) + return next_charm_url + + def test_200_run_charm_upgrade(self): + """Run charm upgrade.""" + self.lts.test_launch_small_instance() + applications = zaza.model.get_status().applications + groups = upgrade_utils.get_charm_upgrade_groups() + for group_name, group in groups.items(): + logging.info("About to upgrade {} ({})".format(group_name, group)) + for application, app_details in applications.items(): + if application not in group: + continue + target_url = self.get_upgrade_url(app_details['charm']) + if target_url == app_details['charm']: + logging.warn( + "Skipping upgrade of {}, already using {}".format( + application, + target_url)) + else: + logging.info("Upgrading {} to {}".format( + application, + target_url)) + zaza.model.upgrade_charm( + application, + switch=target_url) + logging.info("Waiting for charm url to update") + zaza.model.block_until_charm_url(application, target_url) + zaza.model.block_until_all_units_idle() + self.lts.test_launch_small_instance() diff --git a/zaza/openstack/utilities/upgrade_utils.py b/zaza/openstack/utilities/upgrade_utils.py index 4066d19..b134a69 100644 --- a/zaza/openstack/utilities/upgrade_utils.py +++ b/zaza/openstack/utilities/upgrade_utils.py @@ -20,7 +20,8 @@ import zaza.model SERVICE_GROUPS = collections.OrderedDict([ - ('Stateful Services', ['percona-cluster', 'rabbitmq-server', 'ceph-mon']), + ('Stateful Services', ['percona-cluster', 'rabbitmq-server', 'ceph-mon', + 'mysql-innodb-cluster']), ('Core Identity', ['keystone']), ('Control Plane', [ 'aodh', 'barbican', 'ceilometer', 'ceph-fs', @@ -92,6 +93,19 @@ def _filter_non_openstack_services(app, app_config, model_name=None): return False +def _apply_extra_filters(filters, extra_filters): + if extra_filters: + if isinstance(extra_filters, list): + filters.extend(extra_filters) + elif callable(extra_filters): + filters.append(extra_filters) + else: + raise RuntimeError( + "extra_filters should be a list of " + "callables") + return filters + + def get_upgrade_groups(model_name=None, extra_filters=None): """Place apps in the model into their upgrade groups. @@ -108,18 +122,10 @@ def get_upgrade_groups(model_name=None, extra_filters=None): _filter_openstack_upgrade_list, _filter_non_openstack_services, ] - if extra_filters: - if isinstance(extra_filters, list): - filters.extend(extra_filters) - elif callable(extra_filters): - filters.append(extra_filters) - else: - raise RuntimeError( - "extra_filters should be a list of " - "callables") + filters = _apply_extra_filters(filters, extra_filters) apps_in_model = get_upgrade_candidates( model_name=model_name, - filters=filters,) + filters=filters) return _build_service_groups(apps_in_model) @@ -136,15 +142,26 @@ def get_series_upgrade_groups(model_name=None, extra_filters=None): :rtype: collections.OrderedDict """ filters = [_filter_subordinates] - if extra_filters: - if isinstance(extra_filters, list): - filters.extend(extra_filters) - elif callable(extra_filters): - filters.append(extra_filters) - else: - raise RuntimeError( - "extra_filters should be a list of " - "callables") + filters = _apply_extra_filters(filters, extra_filters) + apps_in_model = get_upgrade_candidates( + model_name=model_name, + filters=filters) + + return _build_service_groups(apps_in_model) + + +def get_charm_upgrade_groups(model_name=None, extra_filters=None): + """Place apps in the model into their upgrade groups for a charm upgrade. + + Place apps in the model into their upgrade groups. If an app is deployed + but is not in SERVICE_GROUPS then it is placed in a sweep_up group. + + :param model_name: Name of model to query. + :type model_name: str + :returns: Dict of group lists keyed on group name. + :rtype: collections.OrderedDict + """ + filters = _apply_extra_filters([], extra_filters) apps_in_model = get_upgrade_candidates( model_name=model_name, filters=filters) From d837c0ed97b9bc9d4401c56f040c63371acbca89 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Thu, 11 Jun 2020 14:12:16 +0000 Subject: [PATCH 111/140] Automatically enable/disable config for tempest. Automatically enable/disable config for tempest based on the contents of the keystone service catalogue. --- zaza/openstack/charm_tests/tempest/setup.py | 90 ++++++++++------ .../templates/{accounts.py => accounts.j2} | 3 - .../{tempest_v2.py => tempest_v2.j2} | 56 +++++----- .../tempest/templates/tempest_v3.j2 | 101 ++++++++++++++++++ .../tempest/templates/tempest_v3.py | 100 ----------------- 5 files changed, 187 insertions(+), 163 deletions(-) rename zaza/openstack/charm_tests/tempest/templates/{accounts.py => accounts.j2} (76%) rename zaza/openstack/charm_tests/tempest/templates/{tempest_v2.py => tempest_v2.j2} (57%) create mode 100644 zaza/openstack/charm_tests/tempest/templates/tempest_v3.j2 delete mode 100644 zaza/openstack/charm_tests/tempest/templates/tempest_v3.py diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py index 4266ddc..8e338f6 100644 --- a/zaza/openstack/charm_tests/tempest/setup.py +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -14,6 +14,7 @@ """Code for configuring and initializing tempest.""" +import jinja2 import urllib.parse import os import subprocess @@ -22,20 +23,18 @@ import zaza.utilities.deployment_env as deployment_env import zaza.openstack.utilities.juju as juju_utils import zaza.openstack.utilities.openstack as openstack_utils import zaza.openstack.charm_tests.glance.setup as glance_setup -import zaza.openstack.charm_tests.tempest.templates.tempest_v2 as tempest_v2 -import zaza.openstack.charm_tests.tempest.templates.tempest_v3 as tempest_v3 -import zaza.openstack.charm_tests.tempest.templates.accounts as accounts -SETUP_ENV_VARS = [ - 'TEST_GATEWAY', - 'TEST_CIDR_EXT', - 'TEST_FIP_RANGE', - 'TEST_NAMESERVER', - 'TEST_CIDR_PRIV', - 'TEST_SWIFT_IP', -] +SETUP_ENV_VARS = { + 'neutron': ['TEST_GATEWAY', 'TEST_CIDR_EXT', 'TEST_FIP_RANGE', + 'TEST_NAMESERVER', 'TEST_CIDR_PRIV'], + 'swift': ['TEST_SWIFT_IP'], +} + TEMPEST_FLAVOR_NAME = 'm1.tempest' TEMPEST_ALT_FLAVOR_NAME = 'm2.tempest' +TEMPEST_SVC_LIST = ['ceilometer', 'cinder', 'glance', 'heat', 'horizon', + 'ironic', 'neutron', 'nova', 'sahara', 'swift', 'trove', + 'zaqar'] def add_application_ips(ctxt): @@ -169,7 +168,20 @@ def add_keystone_config(ctxt, keystone_session): ctxt['default_domain_id'] = domain.id -def add_environment_var_config(ctxt): +def get_service_list(keystone_session): + """Add keystone config to context. + + :param keystone_session: keystoneauth1.session.Session object + :type: keystoneauth1.session.Session + :returns: None + :rtype: None + """ + keystone_client = openstack_utils.get_keystone_session_client( + keystone_session) + return [s.name for s in keystone_client.services.list() if s.enabled] + + +def add_environment_var_config(ctxt, services): """Add environment variable config to context. :param ctxt: Context dictionary @@ -178,14 +190,16 @@ def add_environment_var_config(ctxt): :rtype: None """ deploy_env = deployment_env.get_deployment_context() - for var in SETUP_ENV_VARS: - value = deploy_env.get(var) - if value: - ctxt[var.lower()] = value - else: - raise ValueError( - ("Environment variables {} must all be set to run this" - " test").format(', '.join(SETUP_ENV_VARS))) + for svc, env_vars in SETUP_ENV_VARS.items(): + if svc in services: + for var in env_vars: + value = deploy_env.get(var) + if value: + ctxt[var.lower()] = value + else: + raise ValueError( + ("Environment variables {} must all be set to run this" + " test").format(', '.join(env_vars))) def add_auth_config(ctxt): @@ -215,32 +229,42 @@ def get_tempest_context(): """ keystone_session = openstack_utils.get_overcloud_keystone_session() ctxt = {} + ctxt_funcs = { + 'nova': add_nova_config, + 'neutron': add_nova_config, + 'glance': add_keystone_config, + 'cinder': add_cinder_config, + 'keystone': add_keystone_config} + ctxt['enabled_services'] = get_service_list(keystone_session) + ctxt['disabled_services'] = list( + set(TEMPEST_SVC_LIST) - set(ctxt['enabled_services'])) add_application_ips(ctxt) - add_nova_config(ctxt, keystone_session) - add_neutron_config(ctxt, keystone_session) - add_glance_config(ctxt, keystone_session) - add_cinder_config(ctxt, keystone_session) - add_keystone_config(ctxt, keystone_session) - add_environment_var_config(ctxt) + for svc_name, ctxt_func in ctxt_funcs.items(): + if svc_name in ctxt['enabled_services']: + ctxt_func(ctxt, keystone_session) + add_environment_var_config(ctxt, ctxt['enabled_services']) add_auth_config(ctxt) return ctxt -def render_tempest_config(target_file, ctxt, template): +def render_tempest_config(target_file, ctxt, template_name): """Render tempest config for specified config file and template. :param target_file: Name of file to render config to :type target_file: str :param ctxt: Context dictionary :type ctxt: dict - :param template: Template module - :type template: module + :param template_name: Name of template file + :type template_name: str :returns: None :rtype: None """ - # TODO: switch to jinja2 and generate config based on available services + jenv = jinja2.Environment(loader=jinja2.PackageLoader( + 'zaza.openstack', + 'charm_tests/tempest/templates')) + template = jenv.get_template(template_name) with open(target_file, 'w') as f: - f.write(template.file_contents.format(**ctxt)) + f.write(template.render(ctxt)) def setup_tempest(tempest_template, accounts_template): @@ -279,7 +303,7 @@ def render_tempest_config_keystone_v2(): :returns: None :rtype: None """ - setup_tempest(tempest_v2, accounts) + setup_tempest('tempest_v2.j2', 'accounts.j2') def render_tempest_config_keystone_v3(): @@ -288,4 +312,4 @@ def render_tempest_config_keystone_v3(): :returns: None :rtype: None """ - setup_tempest(tempest_v3, accounts) + setup_tempest('tempest_v3.j2', 'accounts.j2') diff --git a/zaza/openstack/charm_tests/tempest/templates/accounts.py b/zaza/openstack/charm_tests/tempest/templates/accounts.j2 similarity index 76% rename from zaza/openstack/charm_tests/tempest/templates/accounts.py rename to zaza/openstack/charm_tests/tempest/templates/accounts.j2 index 0c5cf5a..c4dd21a 100644 --- a/zaza/openstack/charm_tests/tempest/templates/accounts.py +++ b/zaza/openstack/charm_tests/tempest/templates/accounts.j2 @@ -1,9 +1,6 @@ -# flake8: noqa -file_contents = """ - username: 'demo' tenant_name: 'demo' password: 'pass' - username: 'alt_demo' tenant_name: 'alt_demo' password: 'secret' -""" diff --git a/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py b/zaza/openstack/charm_tests/tempest/templates/tempest_v2.j2 similarity index 57% rename from zaza/openstack/charm_tests/tempest/templates/tempest_v2.py rename to zaza/openstack/charm_tests/tempest/templates/tempest_v2.j2 index 8b0f939..b4f2dfc 100644 --- a/zaza/openstack/charm_tests/tempest/templates/tempest_v2.py +++ b/zaza/openstack/charm_tests/tempest/templates/tempest_v2.j2 @@ -1,5 +1,3 @@ -# flake8: noqa -file_contents = """ [DEFAULT] debug = false use_stderr = false @@ -8,16 +6,17 @@ log_file = tempest.log [auth] test_accounts_file = accounts.yaml default_credentials_domain_name = Default -admin_username = {admin_username} +admin_username = {{ admin_username }} admin_project_name = admin -admin_password = {admin_password} +admin_password = {{ admin_password }} admin_domain_name = Default +{% if 'nova' in enabled_services %} [compute] -image_ref = {image_id} -image_ref_alt = {image_alt_id} -flavor_ref = {flavor_ref} -flavor_ref_alt = {flavor_ref_alt} +image_ref = {{ image_id }} +image_ref_alt = {{ image_alt_id }} +flavor_ref = {{ flavor_ref }} +flavor_ref_alt = {{ flavor_ref_alt }} region = RegionOne min_compute_nodes = 3 @@ -31,9 +30,11 @@ resize = true live_migration = true block_migration_for_live_migration = true attach_encrypted_volume = false +{% endif %} +{% if 'keystone' in enabled_services %} [identity] -uri = {proto}://{keystone}:5000/v2.0 +uri = {proto}://{{ keystone }}:5000/v2.0 auth_version = v2 admin_role = Admin region = RegionOne @@ -42,23 +43,28 @@ disable_ssl_certificate_validation = true [identity-feature-enabled] api_v2 = true api_v3 = false +{% endif %} [image] -http_image = http://{test_swift_ip}:80/swift/v1/images/cirros-0.3.4-x86_64-uec.tar.gz +http_image = http://{{ test_swift_ip }}:80/swift/v1/images/cirros-0.3.4-x86_64-uec.tar.gz +{% if 'neutron' in enabled_services %} [network] -project_network_cidr = {test_cidr_priv} -public_network_id = {ext_net} -dns_servers = {test_nameserver} +project_network_cidr = {{ test_cidr_priv }} +public_network_id = {{ ext_net }} +dns_servers = {{ test_nameserver }} project_networks_reachable = false [network-feature-enabled] ipv6 = false +{% endif %} +{% if 'heat' in enabled_services %} [orchestration] stack_owner_role = Admin instance_type = m1.small keypair_name = testkey +{% endif %} [oslo_concurrency] lock_path = /tmp @@ -74,23 +80,19 @@ run_validation = true image_ssh_user = cirros [service_available] -ceilometer = true -cinder = true -glance = true -heat = true -horizon = true -ironic = false -neutron = true -nova = true -sahara = false -swift = true -trove = false -zaqar = false +{% for svc in enabled_services -%} +{{ svc }} = true +{% endfor -%} +{% for svc in disabled_services -%} +{{ svc }} = false +{% endfor %} +{% if 'cinder' in enabled_services %} [volume] backend_names = cinder-ceph storage_protocol = ceph -catalog_type = {catalog_type} +catalog_type = {{ catalog_type }} [volume-feature-enabled] -backup = false""" +backup = false +{% endif %} diff --git a/zaza/openstack/charm_tests/tempest/templates/tempest_v3.j2 b/zaza/openstack/charm_tests/tempest/templates/tempest_v3.j2 new file mode 100644 index 0000000..dc7d8db --- /dev/null +++ b/zaza/openstack/charm_tests/tempest/templates/tempest_v3.j2 @@ -0,0 +1,101 @@ +[DEFAULT] +debug = false +use_stderr = false +log_file = tempest.log + +[auth] +test_accounts_file = accounts.yaml +default_credentials_domain_name = {{ default_credentials_domain_name }} +admin_username = {{ admin_username }} +admin_project_name = {{ admin_project_name }} +admin_password = {{ admin_password }} +admin_domain_name = {{ admin_domain_name }} + +{% if 'nova' in enabled_services %} +[compute] +image_ref = {{ image_id }} +image_ref_alt = {{ image_alt_id }} +flavor_ref = {{ flavor_ref }} +flavor_ref_alt = {{ flavor_ref_alt }} +min_compute_nodes = 3 + +# TODO: review this as its release specific +# min_microversion = 2.2 +# max_microversion = latest + +[compute-feature-enabled] +console_output = true +resize = true +live_migration = true +block_migration_for_live_migration = true +attach_encrypted_volume = false +{% endif %} + +{% if 'keystone' in enabled_services %} +[identity] +uri = {{ proto }}://{{ keystone }}:5000/v2.0 +uri_v3 = {{ proto }}://{{ keystone }}:5000/v3 +auth_version = v3 +admin_role = Admin +region = RegionOne +default_domain_id = {{ default_domain_id }} +admin_domain_scope = true +disable_ssl_certificate_validation = true + +[identity-feature-enabled] +api_v2 = false +api_v3 = true +{% endif %} + +[image] +http_image = http://{{ test_swift_ip }}:80/swift/v1/images/cirros-0.3.4-x86_64-uec.tar.gz + +{% if 'neutron' in enabled_services %} +[network] +project_network_cidr = {{ test_cidr_priv }} +public_network_id = {{ ext_net }} +dns_servers = {{ test_nameserver }} +project_networks_reachable = false +floating_network_name = {{ ext_net }} + +[network-feature-enabled] +ipv6 = false +api_extensions = {{ neutron_api_extensions }} +{% endif %} + +{% if 'heat' in enabled_services %} +[orchestration] +stack_owner_role = Admin +instance_type = m1.small +keypair_name = testkey +{% endif %} + +[oslo_concurrency] +lock_path = /tmp + +[scenario] +img_dir = /home/ubuntu/images +img_file = cirros-0.3.4-x86_64-disk.img +img_container_format = bare +img_disk_format = qcow2 + +[validation] +run_validation = true +image_ssh_user = cirros + +[service_available] +{% for svc in enabled_services -%} +{{ svc }} = true +{% endfor -%} +{% for svc in disabled_services -%} +{{ svc }} = false +{% endfor %} + +{% if 'cinder' in enabled_services %} +[volume] +backend_names = cinder-ceph +storage_protocol = ceph +catalog_type = {{ catalog_type }} + +[volume-feature-enabled] +{% endif %} diff --git a/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py b/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py deleted file mode 100644 index 5140deb..0000000 --- a/zaza/openstack/charm_tests/tempest/templates/tempest_v3.py +++ /dev/null @@ -1,100 +0,0 @@ -# flake8: noqa -file_contents = """ -[DEFAULT] -debug = false -use_stderr = false -log_file = tempest.log - -[auth] -test_accounts_file = accounts.yaml -default_credentials_domain_name = {default_credentials_domain_name} -admin_username = {admin_username} -admin_project_name = {admin_project_name} -admin_password = {admin_password} -admin_domain_name = {admin_domain_name} - -[compute] -image_ref = {image_id} -image_ref_alt = {image_alt_id} -flavor_ref = {flavor_ref} -flavor_ref_alt = {flavor_ref_alt} -min_compute_nodes = 3 - -# TODO: review this as its release specific -# min_microversion = 2.2 -# max_microversion = latest - -[compute-feature-enabled] -console_output = true -resize = true -live_migration = true -block_migration_for_live_migration = true -attach_encrypted_volume = false - -[identity] -uri = {proto}://{keystone}:5000/v2.0 -uri_v3 = {proto}://{keystone}:5000/v3 -auth_version = v3 -admin_role = Admin -region = RegionOne -default_domain_id = {default_domain_id} -admin_domain_scope = true -disable_ssl_certificate_validation = true - -[identity-feature-enabled] -api_v2 = false -api_v3 = true - -[image] -http_image = http://{test_swift_ip}:80/swift/v1/images/cirros-0.3.4-x86_64-uec.tar.gz - -[network] -project_network_cidr = {test_cidr_priv} -public_network_id = {ext_net} -dns_servers = {test_nameserver} -project_networks_reachable = false -floating_network_name = {ext_net} - -[network-feature-enabled] -ipv6 = false -api_extensions = {neutron_api_extensions} - -[orchestration] -stack_owner_role = Admin -instance_type = m1.small -keypair_name = testkey - -[oslo_concurrency] -lock_path = /tmp - -[scenario] -img_dir = /home/ubuntu/images -img_file = cirros-0.3.4-x86_64-disk.img -img_container_format = bare -img_disk_format = qcow2 - -[validation] -run_validation = true -image_ssh_user = cirros - -[service_available] -ceilometer = true -cinder = true -glance = true -heat = true -horizon = true -ironic = false -neutron = true -nova = true -sahara = false -swift = true -trove = false -zaqar = false - -[volume] -backend_names = cinder-ceph -storage_protocol = ceph -catalog_type = {catalog_type} - -[volume-feature-enabled] -backup = false""" From effbf131908f67c7eda55eeb56f1a621292bfef2 Mon Sep 17 00:00:00 2001 From: Frode Nordahl Date: Wed, 10 Jun 2020 08:29:50 +0200 Subject: [PATCH 112/140] 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 b95c79c9e3f4d0b373b604eb2e165942b5a12c05 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Thu, 11 Jun 2020 15:53:51 +0000 Subject: [PATCH 113/140] Include templates --- MANIFEST.in | 1 + setup.py | 1 + .../charm_tests/tempest/templates/__init__.py | 15 --------------- 3 files changed, 2 insertions(+), 15 deletions(-) create mode 100644 MANIFEST.in delete mode 100644 zaza/openstack/charm_tests/tempest/templates/__init__.py diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..96aeb92 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +recursive-include zaza/openstack *.j2 diff --git a/setup.py b/setup.py index a77d425..f4e31f8 100644 --- a/setup.py +++ b/setup.py @@ -108,6 +108,7 @@ setup( license='Apache-2.0: http://www.apache.org/licenses/LICENSE-2.0', packages=find_packages(exclude=["unit_tests"]), zip_safe=False, + include_package_data=True, cmdclass={'test': Tox}, install_requires=install_require, extras_require={ diff --git a/zaza/openstack/charm_tests/tempest/templates/__init__.py b/zaza/openstack/charm_tests/tempest/templates/__init__.py deleted file mode 100644 index 9269e37..0000000 --- a/zaza/openstack/charm_tests/tempest/templates/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# 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 templates for tempest.""" From e8898979b5cd860edb85f3b10f88fbf647bcdb9c Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Fri, 12 Jun 2020 01:56:17 +0000 Subject: [PATCH 114/140] Drop check for tempest workspace directory This check is not necessary. The workspace is recreated on each run, so just attempt to remove the workspace each time. --- zaza/openstack/charm_tests/tempest/setup.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py index 4266ddc..8f37f02 100644 --- a/zaza/openstack/charm_tests/tempest/setup.py +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -15,7 +15,6 @@ """Code for configuring and initializing tempest.""" import urllib.parse -import os import subprocess import zaza.utilities.deployment_env as deployment_env @@ -253,12 +252,11 @@ def setup_tempest(tempest_template, accounts_template): :returns: None :rtype: None """ - if os.path.isdir('tempest-workspace'): - try: - subprocess.check_call(['tempest', 'workspace', 'remove', '--rmdir', - '--name', 'tempest-workspace']) - except subprocess.CalledProcessError: - pass + try: + subprocess.check_call(['tempest', 'workspace', 'remove', '--rmdir', + '--name', 'tempest-workspace']) + except subprocess.CalledProcessError: + pass try: subprocess.check_call(['tempest', 'init', 'tempest-workspace']) except subprocess.CalledProcessError: From de893b66b838f369eb7638e51fab2d10a66eec3c Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Fri, 12 Jun 2020 01:58:10 +0000 Subject: [PATCH 115/140] Drop OS_ prefix from OS_TEST_HTTP_PROXY As part of this change also switch to using deployment_env. --- unit_tests/utilities/test_zaza_utilities_openstack.py | 10 ++++++---- zaza/openstack/utilities/openstack.py | 8 +++++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/unit_tests/utilities/test_zaza_utilities_openstack.py b/unit_tests/utilities/test_zaza_utilities_openstack.py index 3083204..a6aa190 100644 --- a/unit_tests/utilities/test_zaza_utilities_openstack.py +++ b/unit_tests/utilities/test_zaza_utilities_openstack.py @@ -293,8 +293,9 @@ class TestOpenStackUtils(ut_utils.BaseTestCase): self.patch_object(openstack_utils.urllib.request, "ProxyHandler") self.patch_object(openstack_utils.urllib.request, "HTTPHandler") self.patch_object(openstack_utils.urllib.request, "build_opener") - self.patch_object(openstack_utils.os, "getenv") - self.getenv.return_value = None + self.patch_object(openstack_utils.deployment_env, + "get_deployment_context", + return_value=dict(TEST_HTTP_PROXY=None)) HTTPHandler_mock = mock.MagicMock() self.HTTPHandler.return_value = HTTPHandler_mock openstack_utils.get_urllib_opener() @@ -305,8 +306,9 @@ class TestOpenStackUtils(ut_utils.BaseTestCase): self.patch_object(openstack_utils.urllib.request, "ProxyHandler") self.patch_object(openstack_utils.urllib.request, "HTTPHandler") self.patch_object(openstack_utils.urllib.request, "build_opener") - self.patch_object(openstack_utils.os, "getenv") - self.getenv.return_value = 'http://squidy' + self.patch_object(openstack_utils.deployment_env, + "get_deployment_context", + return_value=dict(TEST_HTTP_PROXY='http://squidy')) ProxyHandler_mock = mock.MagicMock() self.ProxyHandler.return_value = ProxyHandler_mock openstack_utils.get_urllib_opener() diff --git a/zaza/openstack/utilities/openstack.py b/zaza/openstack/utilities/openstack.py index ea2a6ba..4963247 100644 --- a/zaza/openstack/utilities/openstack.py +++ b/zaza/openstack/utilities/openstack.py @@ -39,6 +39,7 @@ from keystoneauth1.identity import ( v2, ) import zaza.openstack.utilities.cert as cert +import zaza.utilities.deployment_env as deployment_env from novaclient import client as novaclient_client from neutronclient.v2_0 import client as neutronclient from neutronclient.common import exceptions as neutronexceptions @@ -1749,14 +1750,15 @@ def get_urllib_opener(): Using urllib.request.urlopen will automatically handle proxies so none of this function is needed except we are currently specifying proxies - via OS_TEST_HTTP_PROXY rather than http_proxy so a ProxyHandler is needed + via TEST_HTTP_PROXY rather than http_proxy so a ProxyHandler is needed explicitly stating the proxies. :returns: An opener which opens URLs via BaseHandlers chained together :rtype: urllib.request.OpenerDirector """ - http_proxy = os.getenv('OS_TEST_HTTP_PROXY') - logging.debug('OS_TEST_HTTP_PROXY: {}'.format(http_proxy)) + deploy_env = deployment_env.get_deployment_context() + http_proxy = deploy_env.get('TEST_HTTP_PROXY') + logging.debug('TEST_HTTP_PROXY: {}'.format(http_proxy)) if http_proxy: handler = urllib.request.ProxyHandler({'http': http_proxy}) From 931fcb4aa7b72bcc04010e7375af59514a76aabc Mon Sep 17 00:00:00 2001 From: James Page Date: Fri, 12 Jun 2020 10:40:11 +0100 Subject: [PATCH 116/140] 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 12cf1725cb56d9ba3a1739c7095be795bc9bfa46 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Fri, 12 Jun 2020 12:54:57 +0000 Subject: [PATCH 117/140] Fix service -> setup f map --- zaza/openstack/charm_tests/tempest/setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py index 8e338f6..eaa855a 100644 --- a/zaza/openstack/charm_tests/tempest/setup.py +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -231,8 +231,8 @@ def get_tempest_context(): ctxt = {} ctxt_funcs = { 'nova': add_nova_config, - 'neutron': add_nova_config, - 'glance': add_keystone_config, + 'neutron': add_neutron_config, + 'glance': add_glance_config, 'cinder': add_cinder_config, 'keystone': add_keystone_config} ctxt['enabled_services'] = get_service_list(keystone_session) From 68648aede5121dee0f7ed8d2726392976aaeea5e Mon Sep 17 00:00:00 2001 From: Liam Young Date: Fri, 12 Jun 2020 13:14:28 +0000 Subject: [PATCH 118/140] Tidyup following review feedback --- zaza/openstack/charm_tests/tempest/setup.py | 2 +- zaza/openstack/charm_tests/tempest/templates/tempest_v2.j2 | 2 ++ zaza/openstack/charm_tests/tempest/templates/tempest_v3.j2 | 3 +++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py index eaa855a..23308af 100644 --- a/zaza/openstack/charm_tests/tempest/setup.py +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -169,7 +169,7 @@ def add_keystone_config(ctxt, keystone_session): def get_service_list(keystone_session): - """Add keystone config to context. + """Retrieve list of services from keystone. :param keystone_session: keystoneauth1.session.Session object :type: keystoneauth1.session.Session diff --git a/zaza/openstack/charm_tests/tempest/templates/tempest_v2.j2 b/zaza/openstack/charm_tests/tempest/templates/tempest_v2.j2 index b4f2dfc..f5505ad 100644 --- a/zaza/openstack/charm_tests/tempest/templates/tempest_v2.j2 +++ b/zaza/openstack/charm_tests/tempest/templates/tempest_v2.j2 @@ -45,8 +45,10 @@ api_v2 = true api_v3 = false {% endif %} +{% if 'glance' in enabled_services %} [image] http_image = http://{{ test_swift_ip }}:80/swift/v1/images/cirros-0.3.4-x86_64-uec.tar.gz +{% endif %} {% if 'neutron' in enabled_services %} [network] diff --git a/zaza/openstack/charm_tests/tempest/templates/tempest_v3.j2 b/zaza/openstack/charm_tests/tempest/templates/tempest_v3.j2 index dc7d8db..0ed401a 100644 --- a/zaza/openstack/charm_tests/tempest/templates/tempest_v3.j2 +++ b/zaza/openstack/charm_tests/tempest/templates/tempest_v3.j2 @@ -47,8 +47,10 @@ api_v2 = false api_v3 = true {% endif %} +{% if 'glance' in enabled_services %} [image] http_image = http://{{ test_swift_ip }}:80/swift/v1/images/cirros-0.3.4-x86_64-uec.tar.gz +{% endif %} {% if 'neutron' in enabled_services %} [network] @@ -98,4 +100,5 @@ storage_protocol = ceph catalog_type = {{ catalog_type }} [volume-feature-enabled] +backup = false {% endif %} From 1f2b4890ef0a44b089b71b96dd8c582e774ba77c Mon Sep 17 00:00:00 2001 From: Drew Freiberger Date: Thu, 18 Jun 2020 08:36:41 -0500 Subject: [PATCH 119/140] 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 120/140] 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 121/140] 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 122/140] 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 123/140] 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 124/140] 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 125/140] 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 126/140] 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 127/140] 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 128/140] 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 129/140] 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 130/140] 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 131/140] 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 132/140] 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 133/140] 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 520830905bb4ed45705c5f5818fdb9e18474161d Mon Sep 17 00:00:00 2001 From: Frode Nordahl Date: Thu, 2 Jul 2020 13:03:42 +0200 Subject: [PATCH 134/140] 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 135/140] 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 136/140] 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 137/140] 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 29849225c2a8b3b71fd456f4860ade23266d2c28 Mon Sep 17 00:00:00 2001 From: James Page Date: Mon, 22 Jun 2020 13:02:15 +0100 Subject: [PATCH 138/140] 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 139/140] 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 140/140] 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],