From 2cab47dec252cf7e030d4a553afa7bf510621b44 Mon Sep 17 00:00:00 2001 From: Xav Paice Date: Fri, 2 Oct 2020 18:06:21 +1300 Subject: [PATCH 01/62] Add NRPE check tests for Designate Adds a simple test for the Designate application to ensure that the NRPE checks for services have been created. Related-Bug: #1897809 --- zaza/openstack/charm_tests/designate/tests.py | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/zaza/openstack/charm_tests/designate/tests.py b/zaza/openstack/charm_tests/designate/tests.py index 3c56ee1..c01014e 100644 --- a/zaza/openstack/charm_tests/designate/tests.py +++ b/zaza/openstack/charm_tests/designate/tests.py @@ -24,6 +24,7 @@ import designateclient.v1.servers as servers import zaza.model import zaza.openstack.utilities.juju as zaza_juju +import zaza.openstack.utilities.generic as generic_utils 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 @@ -88,6 +89,14 @@ class BaseDesignateTest(test_utils.OpenStackBaseTest): cls.server_create = cls.designate.servers.create cls.server_delete = cls.designate.servers.delete + @tenacity.retry( + retry=tenacity.retry_if_result(lambda ret: ret is not None), + # sleep for 2mins to allow 1min cron job to run... + wait=tenacity.wait_fixed(120), + stop=tenacity.stop_after_attempt(2)) + def _retry_check_commands_on_units(self, cmds, units): + return generic_utils.check_commands_on_units(cmds, units) + class DesignateAPITests(BaseDesignateTest): """Tests interact with designate api.""" @@ -257,7 +266,26 @@ class DesignateCharmTests(BaseDesignateTest): logging.info("Testing pause resume") -class DesignateTests(DesignateAPITests, DesignateCharmTests): +class DesignateMonitoringTests(BaseDesignateTest): + """Designate charm monitoring tests.""" + + def test_nrpe_configured(self): + """Confirm that the NRPE service check files are created.""" + units = zaza.model.get_units(self.application_name) + cmds = [] + for check_name in self.designate_svcs: + cmds.append( + 'egrep -oh /usr/local.* /etc/nagios/nrpe.d/' + 'check_{}.cfg'.format(check_name) + ) + ret = self._retry_check_commands_on_units(cmds, units) + if ret: + logging.info(ret) + self.assertIsNone(ret, msg=ret) + + +class DesignateTests(DesignateAPITests, DesignateCharmTests, + DesignateMonitoringTests): """Collection of all Designate test classes.""" pass From 21ff72926b7633e298341bfa514b03e163d4b82f Mon Sep 17 00:00:00 2001 From: Alex Kavanagh Date: Thu, 29 Apr 2021 19:25:34 +0100 Subject: [PATCH 02/62] Additions for the Octavia Upgrade tests These are additions to make the Octavia Upgrade tests in the charmed-openstack-tests repository a little easier to specify. Also depends on the zaza change [1]. [1] https://github.com/openstack-charmers/zaza/pull/442 --- .../charm_tests/openstack_upgrade/tests.py | 54 +++++++++++++------ zaza/openstack/utilities/exceptions.py | 6 +++ 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/zaza/openstack/charm_tests/openstack_upgrade/tests.py b/zaza/openstack/charm_tests/openstack_upgrade/tests.py index 82c7fbb..c34acae 100644 --- a/zaza/openstack/charm_tests/openstack_upgrade/tests.py +++ b/zaza/openstack/charm_tests/openstack_upgrade/tests.py @@ -19,16 +19,21 @@ import logging import unittest +import zaza.model +import zaza.global_options + from zaza.openstack.utilities import ( cli as cli_utils, upgrade_utils as upgrade_utils, openstack as openstack_utils, openstack_upgrade as openstack_upgrade, + exceptions, + generic, ) from zaza.openstack.charm_tests.nova.tests import LTSGuestCreateTest -class OpenStackUpgradeVMLaunchBase(object): +class OpenStackUpgradeVMLaunchBase(unittest.TestCase): """A base class to peform a simple validation on the cloud. This wraps an OpenStack upgrade with a VM launch before and after the @@ -97,15 +102,19 @@ class OpenStackUpgradeTestsFocalUssuri(OpenStackUpgradeVMLaunchBase): openstack_upgrade.run_upgrade_tests("cloud:focal-victoria") -class OpenStackUpgradeTests(OpenStackUpgradeVMLaunchBase): +class OpenStackUpgradeTestsByOption(OpenStackUpgradeVMLaunchBase): """A Principal Class to encapsulate OpenStack Upgrade Tests. - A generic Test class that can discover which Ubuntu version and OpenStack - version to upgrade from. + A generic Test class that uses the options in the tests.yaml to use a charm + to detect the Ubuntu and OpenStack versions and then workout what to + upgrade to. - TODO: Not used at present. Use the declarative tests directly that choose - the version to upgrade to. The functions that this class depends on need a - bit more work regarding how the determine which version to go to. + tests_options: + openstack-upgrade: + detect-charm: keystone + + This will use the octavia application, detect the ubuntu version and then + read the config to discover the current OpenStack version. """ @classmethod @@ -122,16 +131,30 @@ class OpenStackUpgradeTests(OpenStackUpgradeVMLaunchBase): determine which ubuntu version to work from. Don't use until we can make it better. """ - # TODO: work out the most recent Ubuntu version; we assume this is the - # version that OpenStack is running on. - ubuntu_version = "focal" - logging.info("Getting all principle applications ...") - principle_services = upgrade_utils.get_all_principal_applications() + # get the tests_options / openstack-upgrade.detect-using-charm so that + # the ubuntu version and OpenStack version can be detected. + try: + detect_charm = ( + zaza.global_options.get_options() + .openstack_upgrade.detect_using_charm) + except KeyError: + raise exceptions.InvalidTestConfig( + "Missing tests_options.openstack-upgrade.detect-charm config.") + + unit = zaza.model.get_lead_unit(detect_charm) + ubuntu_version = generic.get_series(unit) + logging.info("Current version detected from {} is {}" + .format(detect_charm, ubuntu_version)) + logging.info( - "Getting OpenStack vesions from principal applications ...") + "Getting OpenStack version from %s ..." % detect_charm) current_versions = openstack_utils.get_current_os_versions( - principle_services) - logging.info("current versions: %s" % current_versions) + [detect_charm]) + if not current_versions: + raise exceptions.ApplicationNotFound( + "No version found for {}?".format(detect_charm)) + + logging.info("current version: %s" % current_versions[detect_charm]) # Find the lowest value openstack release across all services and make # sure all servcies are upgraded to one release higher than the lowest from_version = upgrade_utils.get_lowest_openstack_version( @@ -140,7 +163,6 @@ class OpenStackUpgradeTests(OpenStackUpgradeVMLaunchBase): to_version = upgrade_utils.determine_next_openstack_release( from_version)[1] logging.info("to version: %s" % to_version) - # TODO: need to determine the ubuntu base verion that is being upgraded target_source = upgrade_utils.determine_new_source( ubuntu_version, from_version, to_version, single_increment=True) logging.info("target source: %s" % target_source) diff --git a/zaza/openstack/utilities/exceptions.py b/zaza/openstack/utilities/exceptions.py index 364ab49..7f73be4 100644 --- a/zaza/openstack/utilities/exceptions.py +++ b/zaza/openstack/utilities/exceptions.py @@ -15,6 +15,12 @@ """Module of exceptions that zaza may raise.""" +class InvalidTestConfig(Exception): + """Exception when the test configuration is invalid.""" + + pass + + class MissingOSAthenticationException(Exception): """Exception when some data needed to authenticate is missing.""" From bcff352f9c03aff776e847c4dc6111ee1e3734c6 Mon Sep 17 00:00:00 2001 From: Felipe Reyes Date: Tue, 13 Jul 2021 17:11:22 -0400 Subject: [PATCH 03/62] designate: test new configuration options New configuration options: - default-ttl - default-soa-minimum - default-soa-refresh-min - default-soa-refresh-max - default-soa-retry Related-Bug: #1902922 --- zaza/openstack/charm_tests/designate/tests.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/zaza/openstack/charm_tests/designate/tests.py b/zaza/openstack/charm_tests/designate/tests.py index 78a5f31..4fffede 100644 --- a/zaza/openstack/charm_tests/designate/tests.py +++ b/zaza/openstack/charm_tests/designate/tests.py @@ -33,6 +33,8 @@ import zaza.charm_lifecycle.utils as lifecycle_utils class BaseDesignateTest(test_utils.OpenStackBaseTest): """Base for Designate charm tests.""" + DESIGNATE_CONF = '/etc/designate/designate.conf' + @classmethod def setUpClass(cls, application_name=None, model_alias=None): """Run class setup for running Designate charm operation tests.""" @@ -119,6 +121,41 @@ class DesignateAPITests(BaseDesignateTest): self.server_delete(server_id) return wait() + def test_300_default_soa_config_options(self): + """Configure default SOA options.""" + test_domain = "test_300_example.com." + DEFAULT_TTL = 60 + alternate_config = {'default-soa-minimum': 600, + 'default-ttl': DEFAULT_TTL, + 'default-soa-refresh-min': 300, + 'default-soa-refresh-max': 400, + 'default-soa-retry': 30} + with self.config_change({}, alternate_config, "designate", + reset_to_charm_default=True): + for key, value in alternate_config.items(): + expected = "\n%s = %s\n" % (key.replace('-', '_'), value) + zaza.model.block_until_file_has_contents(self.application_name, + self.DESIGNATE_CONF, + expected) + logging.debug('Creating domain %s' % test_domain) + domain = domains.Domain(name=test_domain, + email="fred@amuletexample.com") + + if self.post_xenial_queens: + new_domain = self.domain_create( + name=domain.name, email=domain.email) + domain_id = new_domain['id'] + else: + new_domain = self.domain_create(domain) + domain_id = new_domain.id + + self.assertIsNotNone(new_domain) + self.assertEqual(new_domain['ttl'], DEFAULT_TTL) + + logging.debug('Tidy up delete test record %s' % domain_id) + self._wait_on_domain_gone(domain_id) + logging.debug('Done with deletion of domain %s' % domain_id) + def test_400_server_creation(self): """Simple api calls to create a server.""" # Designate does not allow the last server to be deleted so ensure From 797e505f8401ba78313a77aea7fd4b0b0584463c Mon Sep 17 00:00:00 2001 From: Aurelien Lourot Date: Tue, 20 Jul 2021 10:37:51 +0200 Subject: [PATCH 04/62] Add connection timeout to CirrOS image download (#604) Without this timeout it has been observed that Zaza may hang forever on open(). It is better to fail faster and save time and resources. --- zaza/openstack/utilities/openstack.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zaza/openstack/utilities/openstack.py b/zaza/openstack/utilities/openstack.py index 3082068..a5ba6f6 100644 --- a/zaza/openstack/utilities/openstack.py +++ b/zaza/openstack/utilities/openstack.py @@ -2194,8 +2194,9 @@ def find_cirros_image(arch): :returns: URL for latest cirros image :rtype: str """ + http_connection_timeout = 10 # seconds opener = get_urllib_opener() - f = opener.open(CIRROS_RELEASE_URL) + f = opener.open(CIRROS_RELEASE_URL, timeout=http_connection_timeout) version = f.read().strip().decode() cirros_img = 'cirros-{}-{}-disk.img'.format(version, arch) return '{}/{}/{}'.format(CIRROS_IMAGE_URL, version, cirros_img) From 40e15732774ea08ba2ec181137e691eef0494418 Mon Sep 17 00:00:00 2001 From: Aurelien Lourot Date: Mon, 12 Jul 2021 15:45:55 +0200 Subject: [PATCH 05/62] Add Impish/Xena/Yoga bits Change-Id: I61ef8120fb487dc4a1a12a51d14c75825099bcbb --- zaza/openstack/utilities/os_versions.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/zaza/openstack/utilities/os_versions.py b/zaza/openstack/utilities/os_versions.py index beaedec..2ea089e 100644 --- a/zaza/openstack/utilities/os_versions.py +++ b/zaza/openstack/utilities/os_versions.py @@ -37,6 +37,7 @@ UBUNTU_OPENSTACK_RELEASE = OrderedDict([ ('focal', 'ussuri'), ('groovy', 'victoria'), ('hirsute', 'wallaby'), + ('impish', 'xena'), ]) @@ -71,7 +72,10 @@ OPENSTACK_RELEASES_PAIRS = [ 'bionic_stein', 'disco_stein', 'bionic_train', 'eoan_train', 'bionic_ussuri', 'focal_ussuri', 'focal_victoria', 'groovy_victoria', - 'focal_wallaby', 'hirsute_wallaby'] + 'focal_wallaby', 'hirsute_wallaby', + 'focal_xena', 'impish_xena', + 'focal_yoga' +] SWIFT_CODENAMES = OrderedDict([ ('diablo', From ee647a7552647767815ea2f7faebab6aca0501a3 Mon Sep 17 00:00:00 2001 From: Aurelien Lourot Date: Wed, 21 Jul 2021 11:14:00 +0200 Subject: [PATCH 06/62] Removed unneeded dependency to distro-info distro-info hasn't been maintained for 8 years and pip is now failing to fetch it on Python 3.8. This dependency seems to have been introduced by accident in 22e7ffc. --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index adec5bc..1621491 100644 --- a/requirements.txt +++ b/requirements.txt @@ -41,7 +41,6 @@ python-novaclient python-octaviaclient python-swiftclient tenacity -distro-info paramiko # Documentation requirements From 988efd6b25528d13b17c80f4959dca33e4aba7ad Mon Sep 17 00:00:00 2001 From: Martin Kalcok Date: Wed, 28 Jul 2021 14:52:04 +0200 Subject: [PATCH 07/62] Tests for new "status" actions in neutron-gateway --- zaza/openstack/charm_tests/neutron/tests.py | 112 ++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/zaza/openstack/charm_tests/neutron/tests.py b/zaza/openstack/charm_tests/neutron/tests.py index 54420e8..b3daafe 100644 --- a/zaza/openstack/charm_tests/neutron/tests.py +++ b/zaza/openstack/charm_tests/neutron/tests.py @@ -21,6 +21,7 @@ import copy +import json import logging import tenacity @@ -248,6 +249,117 @@ class NeutronGatewayTest(NeutronPluginApiSharedTests): return services +class NeutronGatewayStatusActionsTest(test_utils.OpenStackBaseTest): + """Test status actions of Neutron Gateway Charm. + + actions: + * get-status-routers + * get-status-dhcp + * get-status-lb + """ + + @classmethod + def setUpClass(cls, application_name='neutron-gateway', model_alias=None): + """Run class setup for running Neutron Gateway tests.""" + super(NeutronGatewayStatusActionsTest, cls).setUpClass(application_name, + model_alias) + # set up clients + cls.neutron_client = ( + openstack_utils.get_neutron_session_client(cls.keystone_session)) + + def tearDown(self): + """Cleanup loadbalancers if there are any left over.""" + super(NeutronGatewayStatusActionsTest, self).tearDown() + load_balancers = self.neutron_client.list_loadbalancers().get('loadbalancers', + []) + for lbaas in load_balancers: + self.neutron_client.delete_loadbalancer(lbaas['id']) + + def test_get_status_routers(self): + """Test that routers reported by neutron client match those from action.""" + ngw_unit = zaza.model.get_units(self.application_name, + model_name=self.model_name)[0] + routers_from_client = self.neutron_client.list_routers().get('routers', []) + + if not routers_from_client: + self.fail('At least one router must be configured for this test to pass.') + + result = zaza.model.run_action(ngw_unit.entity_id, + 'get-status-routers', + model_name=self.model_name, + action_params={"format": "json"}) + self.assertEqual(result.status, 'completed') + action_data = result.data.get('results', {}).get('router-list') + routers_from_action = json.loads(action_data) + + ids_from_client = {router['id'] for router in routers_from_client} + ids_from_action = {router['id'] for router in routers_from_action} + + self.assertEqual(ids_from_action, ids_from_client) + + def test_get_status_dhcp(self): + """Test that DHCP networks reported by neutron client match those from action.""" + ngw_unit = zaza.model.get_units(self.application_name, + model_name=self.model_name)[0] + networks_from_client = self.neutron_client.list_networks().get('networks', []) + + if not networks_from_client: + self.fail('At least one network must be configured for this test to pass.') + + result = zaza.model.run_action(ngw_unit.entity_id, + 'get-status-dhcp', + model_name=self.model_name, + action_params={"format": "json"}) + self.assertEqual(result.status, 'completed') + action_data = result.data.get('results', {}).get('dhcp-networks') + networks_from_action = json.loads(action_data) + + ids_from_client = {net['id'] for net in networks_from_client} + ids_from_action = {net['id'] for net in networks_from_action} + + self.assertEqual(ids_from_action, ids_from_client) + + def test_get_status_load_balancers(self): + """Test that loadbalancers reported by neutron client match those from action.""" + current_release = openstack_utils.get_os_release() + bionic_train = openstack_utils.get_os_release('bionic_train') + if current_release >= bionic_train: + self.skipTest('LBaasV2 is not supported in this version.') + + # create LBaasV2 for the purpose of this test + lbaas_name = 'test_lbaas' + + subnet_list = self.neutron_client.list_subnets(name='private_subnet').\ + get('subnets', []) + + if not subnet_list: + raise RuntimeError('Expected subnet "private_subnet" is not configured.') + + subnet = subnet_list[0] + loadbalancer_data = {'loadbalancer': {'name': lbaas_name, + 'vip_subnet_id': subnet['id']}} + self.neutron_client.create_loadbalancer(body=loadbalancer_data) + + # test that client and action report same data + ngw_unit = zaza.model.get_units(self.application_name, + model_name=self.model_name)[0] + lbaas_from_client = self.neutron_client.list_loadbalancers()\ + .get('loadbalancers', []) + + result = zaza.model.run_action(ngw_unit.entity_id, + 'get-status-lb', + model_name=self.model_name, + action_params={"format": "json"}) + self.assertEqual(result.status, 'completed') + action_data = result.data.get('results', {}).get('load-balancers') + lbaas_from_action = json.loads(action_data) + + ids_from_client = {lbaas['id'] for lbaas in lbaas_from_client} + ids_from_action = {lbaas['id'] for lbaas in lbaas_from_action} + + self.assertEqual(ids_from_action, ids_from_client) + + class NeutronCreateNetworkTest(test_utils.OpenStackBaseTest): """Test creating a Neutron network through the API. From 5f0c43da2ea06f7e80f1d0fdb6ba3fad7fcc82e9 Mon Sep 17 00:00:00 2001 From: Martin Kalcok Date: Wed, 28 Jul 2021 15:59:12 +0200 Subject: [PATCH 08/62] Skip loadbalancer cleanup on releases that don't support them --- zaza/openstack/charm_tests/neutron/tests.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/zaza/openstack/charm_tests/neutron/tests.py b/zaza/openstack/charm_tests/neutron/tests.py index b3daafe..328f771 100644 --- a/zaza/openstack/charm_tests/neutron/tests.py +++ b/zaza/openstack/charm_tests/neutron/tests.py @@ -258,6 +258,8 @@ class NeutronGatewayStatusActionsTest(test_utils.OpenStackBaseTest): * get-status-lb """ + SKIP_LBAAS_TESTS = True + @classmethod def setUpClass(cls, application_name='neutron-gateway', model_alias=None): """Run class setup for running Neutron Gateway tests.""" @@ -267,13 +269,19 @@ class NeutronGatewayStatusActionsTest(test_utils.OpenStackBaseTest): cls.neutron_client = ( openstack_utils.get_neutron_session_client(cls.keystone_session)) + # Loadbalancer tests not supported on Train and above + current_release = openstack_utils.get_os_release() + bionic_train = openstack_utils.get_os_release('bionic_train') + cls.SKIP_LBAAS_TESTS = current_release >= bionic_train + def tearDown(self): """Cleanup loadbalancers if there are any left over.""" super(NeutronGatewayStatusActionsTest, self).tearDown() - load_balancers = self.neutron_client.list_loadbalancers().get('loadbalancers', - []) - for lbaas in load_balancers: - self.neutron_client.delete_loadbalancer(lbaas['id']) + if not self.SKIP_LBAAS_TESTS: + load_balancers = self.neutron_client.list_loadbalancers().get('loadbalancers', + []) + for lbaas in load_balancers: + self.neutron_client.delete_loadbalancer(lbaas['id']) def test_get_status_routers(self): """Test that routers reported by neutron client match those from action.""" @@ -321,9 +329,7 @@ class NeutronGatewayStatusActionsTest(test_utils.OpenStackBaseTest): def test_get_status_load_balancers(self): """Test that loadbalancers reported by neutron client match those from action.""" - current_release = openstack_utils.get_os_release() - bionic_train = openstack_utils.get_os_release('bionic_train') - if current_release >= bionic_train: + if self.SKIP_LBAAS_TESTS: self.skipTest('LBaasV2 is not supported in this version.') # create LBaasV2 for the purpose of this test From e693fc1840471bb7e39f473d839be0941358101e Mon Sep 17 00:00:00 2001 From: Aurelien Lourot Date: Wed, 28 Jul 2021 16:52:33 +0200 Subject: [PATCH 09/62] Nail pip 20.2.3 Co-authored-by: Alex Kavanagh --- pip.sh | 4 ++++ tox.ini | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100755 pip.sh diff --git a/pip.sh b/pip.sh new file mode 100755 index 0000000..cdd3588 --- /dev/null +++ b/pip.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +pip install pip==20.2.3 +pip install "$@" diff --git a/tox.ini b/tox.ini index aea173f..890b193 100644 --- a/tox.ini +++ b/tox.ini @@ -22,7 +22,7 @@ minversion = 3.2.0 setenv = VIRTUAL_ENV={envdir} PYTHONHASHSEED=0 install_command = - pip install {opts} {packages} + {toxinidir}/pip.sh install {opts} {packages} commands = nosetests --with-coverage --cover-package=zaza.openstack {posargs} {toxinidir}/unit_tests From 74959dee3aa360bff5e13c9cdebe45050f2e9f91 Mon Sep 17 00:00:00 2001 From: Martin Kalcok Date: Thu, 29 Jul 2021 09:07:39 +0200 Subject: [PATCH 10/62] fix linting errors --- zaza/openstack/charm_tests/neutron/tests.py | 29 ++++++++++++--------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/zaza/openstack/charm_tests/neutron/tests.py b/zaza/openstack/charm_tests/neutron/tests.py index 328f771..dd54757 100644 --- a/zaza/openstack/charm_tests/neutron/tests.py +++ b/zaza/openstack/charm_tests/neutron/tests.py @@ -263,8 +263,8 @@ class NeutronGatewayStatusActionsTest(test_utils.OpenStackBaseTest): @classmethod def setUpClass(cls, application_name='neutron-gateway', model_alias=None): """Run class setup for running Neutron Gateway tests.""" - super(NeutronGatewayStatusActionsTest, cls).setUpClass(application_name, - model_alias) + super(NeutronGatewayStatusActionsTest, cls).setUpClass( + application_name, model_alias) # set up clients cls.neutron_client = ( openstack_utils.get_neutron_session_client(cls.keystone_session)) @@ -278,19 +278,21 @@ class NeutronGatewayStatusActionsTest(test_utils.OpenStackBaseTest): """Cleanup loadbalancers if there are any left over.""" super(NeutronGatewayStatusActionsTest, self).tearDown() if not self.SKIP_LBAAS_TESTS: - load_balancers = self.neutron_client.list_loadbalancers().get('loadbalancers', - []) + load_balancers = self.neutron_client.list_loadbalancers().get( + 'loadbalancers', []) for lbaas in load_balancers: self.neutron_client.delete_loadbalancer(lbaas['id']) def test_get_status_routers(self): - """Test that routers reported by neutron client match those from action.""" + """Test that get-status-routers reports correct neutron routers.""" ngw_unit = zaza.model.get_units(self.application_name, model_name=self.model_name)[0] - routers_from_client = self.neutron_client.list_routers().get('routers', []) + routers_from_client = self.neutron_client.list_routers().get( + 'routers', []) if not routers_from_client: - self.fail('At least one router must be configured for this test to pass.') + self.fail('At least one router must be configured for this test ' + 'to pass.') result = zaza.model.run_action(ngw_unit.entity_id, 'get-status-routers', @@ -306,13 +308,15 @@ class NeutronGatewayStatusActionsTest(test_utils.OpenStackBaseTest): self.assertEqual(ids_from_action, ids_from_client) def test_get_status_dhcp(self): - """Test that DHCP networks reported by neutron client match those from action.""" + """Test that get-status-dhcp reports correct DHCP networks.""" ngw_unit = zaza.model.get_units(self.application_name, model_name=self.model_name)[0] - networks_from_client = self.neutron_client.list_networks().get('networks', []) + networks_from_client = self.neutron_client.list_networks().get( + 'networks', []) if not networks_from_client: - self.fail('At least one network must be configured for this test to pass.') + self.fail('At least one network must be configured for this test ' + 'to pass.') result = zaza.model.run_action(ngw_unit.entity_id, 'get-status-dhcp', @@ -328,7 +332,7 @@ class NeutronGatewayStatusActionsTest(test_utils.OpenStackBaseTest): self.assertEqual(ids_from_action, ids_from_client) def test_get_status_load_balancers(self): - """Test that loadbalancers reported by neutron client match those from action.""" + """Test that get-status-lb reports correct loadbalancers.""" if self.SKIP_LBAAS_TESTS: self.skipTest('LBaasV2 is not supported in this version.') @@ -339,7 +343,8 @@ class NeutronGatewayStatusActionsTest(test_utils.OpenStackBaseTest): get('subnets', []) if not subnet_list: - raise RuntimeError('Expected subnet "private_subnet" is not configured.') + raise RuntimeError('Expected subnet "private_subnet" is not ' + 'configured.') subnet = subnet_list[0] loadbalancer_data = {'loadbalancer': {'name': lbaas_name, From 4d2761e84dacaec8691b107d5e04373176c17cf2 Mon Sep 17 00:00:00 2001 From: Aurelien Lourot Date: Thu, 29 Jul 2021 10:56:23 +0200 Subject: [PATCH 11/62] Don't 'pip install install' (#612) Change-Id: I916858a3e3af9366ab8e8b68e875ab670848d571 --- pip.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pip.sh b/pip.sh index cdd3588..8a71ce4 100755 --- a/pip.sh +++ b/pip.sh @@ -1,4 +1,4 @@ #!/usr/bin/env bash pip install pip==20.2.3 -pip install "$@" +pip "$@" From ea341f1eb4a1cc4d7447ceb16a34352fd90af362 Mon Sep 17 00:00:00 2001 From: Gabriel Angelo Sgarbi Cocenza Date: Thu, 29 Jul 2021 06:09:33 -0300 Subject: [PATCH 12/62] Check blocked ovn-chassis units with bad config (#605) * Check blocked ovn-chassis units with bad config LP bug#1919481 [0] needs to have a func-test to check if units can handle bad bridge-interface-mappings configuration and set units workload to block state and return to active state with good config. [0] https://bugs.launchpad.net/charm-ovn-chassis/+bug/1919481 * Changing test_wrong_bridge_config to use config_change Instead of using block_until_unit_wl_status, change the test_config to be able to check blocked state for ovn-chassis units Co-authored-by: Aurelien Lourot --- zaza/openstack/charm_tests/ovn/tests.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/zaza/openstack/charm_tests/ovn/tests.py b/zaza/openstack/charm_tests/ovn/tests.py index 2478d66..43409ae 100644 --- a/zaza/openstack/charm_tests/ovn/tests.py +++ b/zaza/openstack/charm_tests/ovn/tests.py @@ -168,6 +168,28 @@ class ChassisCharmOperationTest(BaseCharmOperationTest): '{}: "{}" no longer present' .format(unit.entity_id, expected_key)) + def test_wrong_bridge_config(self): + """Confirm that ovn-chassis units block with wrong bridge config.""" + stored_target_deploy_status = self.test_config.get( + 'target_deploy_status', {}) + new_target_deploy_status = stored_target_deploy_status.copy() + new_target_deploy_status[self.application_name] = { + 'ovn-chassis': 'blocked', + } + if 'target_deploy_status' in self.test_config: + self.test_config['target_deploy_status'].update( + new_target_deploy_status) + else: + self.test_config['target_deploy_status'] = new_target_deploy_status + + with self.config_change( + {'bridge-interface-mappings': ''}, + {'bridge-interface-mappings': 'incorrect'}): + logging.info('Charm went into blocked state as expected, restore ' + 'configuration') + self.test_config[ + 'target_deploy_status'] = stored_target_deploy_status + class OVSOVNMigrationTest(test_utils.BaseCharmTest): """OVS to OVN migration tests.""" From cbc9c8419460889228e5ec068b8d47e8ccb16f63 Mon Sep 17 00:00:00 2001 From: Martin Kalcok Date: Thu, 29 Jul 2021 12:53:45 +0200 Subject: [PATCH 13/62] Move common functionality into helper method --- zaza/openstack/charm_tests/neutron/tests.py | 57 ++++++++++++--------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/zaza/openstack/charm_tests/neutron/tests.py b/zaza/openstack/charm_tests/neutron/tests.py index dd54757..8d34a88 100644 --- a/zaza/openstack/charm_tests/neutron/tests.py +++ b/zaza/openstack/charm_tests/neutron/tests.py @@ -283,8 +283,27 @@ class NeutronGatewayStatusActionsTest(test_utils.OpenStackBaseTest): for lbaas in load_balancers: self.neutron_client.delete_loadbalancer(lbaas['id']) + def _assert_result_match(self, action_result, resource_list, + resource_name): + """Assert that action_result contains same data as resource_list.""" + # make sure that action completed successfully + self.assertEqual(action_result.status, 'completed') + + # extract data from juju action + action_data = action_result.data.get('results', {}).get(resource_name) + resources_from_action = json.loads(action_data) + + # pull resource IDs from expected resource list and juju action data + expected_resource_ids = {resource['id'] for resource in resource_list} + result_resource_ids = {resource['id'] for resource in + resources_from_action} + + # assert that juju action returned expected resources + self.assertEqual(result_resource_ids, expected_resource_ids) + def test_get_status_routers(self): """Test that get-status-routers reports correct neutron routers.""" + # fetch neutron routers using neutron client ngw_unit = zaza.model.get_units(self.application_name, model_name=self.model_name)[0] routers_from_client = self.neutron_client.list_routers().get( @@ -294,21 +313,18 @@ class NeutronGatewayStatusActionsTest(test_utils.OpenStackBaseTest): self.fail('At least one router must be configured for this test ' 'to pass.') + # fetch neutron routers using juju-action result = zaza.model.run_action(ngw_unit.entity_id, 'get-status-routers', model_name=self.model_name, action_params={"format": "json"}) - self.assertEqual(result.status, 'completed') - action_data = result.data.get('results', {}).get('router-list') - routers_from_action = json.loads(action_data) - ids_from_client = {router['id'] for router in routers_from_client} - ids_from_action = {router['id'] for router in routers_from_action} - - self.assertEqual(ids_from_action, ids_from_client) + # assert that data from neutron client match data from juju action + self._assert_result_match(result, routers_from_client, 'router-list') def test_get_status_dhcp(self): """Test that get-status-dhcp reports correct DHCP networks.""" + # fetch DHCP networks using neutron client ngw_unit = zaza.model.get_units(self.application_name, model_name=self.model_name)[0] networks_from_client = self.neutron_client.list_networks().get( @@ -318,18 +334,15 @@ class NeutronGatewayStatusActionsTest(test_utils.OpenStackBaseTest): self.fail('At least one network must be configured for this test ' 'to pass.') + # fetch DHCP networks using juju-action result = zaza.model.run_action(ngw_unit.entity_id, 'get-status-dhcp', model_name=self.model_name, action_params={"format": "json"}) - self.assertEqual(result.status, 'completed') - action_data = result.data.get('results', {}).get('dhcp-networks') - networks_from_action = json.loads(action_data) - ids_from_client = {net['id'] for net in networks_from_client} - ids_from_action = {net['id'] for net in networks_from_action} - - self.assertEqual(ids_from_action, ids_from_client) + # assert that data from neutron client match data from juju action + self._assert_result_match(result, networks_from_client, + 'dhcp-networks') def test_get_status_load_balancers(self): """Test that get-status-lb reports correct loadbalancers.""" @@ -339,8 +352,8 @@ class NeutronGatewayStatusActionsTest(test_utils.OpenStackBaseTest): # create LBaasV2 for the purpose of this test lbaas_name = 'test_lbaas' - subnet_list = self.neutron_client.list_subnets(name='private_subnet').\ - get('subnets', []) + subnet_list = self.neutron_client.list_subnets( + name='private_subnet').get('subnets', []) if not subnet_list: raise RuntimeError('Expected subnet "private_subnet" is not ' @@ -354,21 +367,15 @@ class NeutronGatewayStatusActionsTest(test_utils.OpenStackBaseTest): # test that client and action report same data ngw_unit = zaza.model.get_units(self.application_name, model_name=self.model_name)[0] - lbaas_from_client = self.neutron_client.list_loadbalancers()\ - .get('loadbalancers', []) + lbaas_from_client = self.neutron_client.list_loadbalancers().get( + 'loadbalancers', []) result = zaza.model.run_action(ngw_unit.entity_id, 'get-status-lb', model_name=self.model_name, action_params={"format": "json"}) - self.assertEqual(result.status, 'completed') - action_data = result.data.get('results', {}).get('load-balancers') - lbaas_from_action = json.loads(action_data) - ids_from_client = {lbaas['id'] for lbaas in lbaas_from_client} - ids_from_action = {lbaas['id'] for lbaas in lbaas_from_action} - - self.assertEqual(ids_from_action, ids_from_client) + self._assert_result_match(result, lbaas_from_client, 'load-balancers') class NeutronCreateNetworkTest(test_utils.OpenStackBaseTest): From 3371fe9c2a015105ed1c6a125a90e7ed86499637 Mon Sep 17 00:00:00 2001 From: Aurelien Lourot Date: Thu, 29 Jul 2021 13:15:53 +0200 Subject: [PATCH 14/62] Wait between actions on OpenStack upgrade (#607) We're more likely to time out while waiting for the action's result if we launch an action while the model is still executing. Thus it's safer to wait for the model to settle between actions. --- zaza/openstack/utilities/openstack_upgrade.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/zaza/openstack/utilities/openstack_upgrade.py b/zaza/openstack/utilities/openstack_upgrade.py index c279e58..f75a05c 100755 --- a/zaza/openstack/utilities/openstack_upgrade.py +++ b/zaza/openstack/utilities/openstack_upgrade.py @@ -132,12 +132,23 @@ def action_upgrade_apps(applications, model_name=None): status=status, model_name=model_name) + # NOTE(lourot): we're more likely to time out while waiting for the + # action's result if we launch an action while the model is still + # executing. Thus it's safer to wait for the model to settle between + # actions. + zaza.model.block_until_all_units_idle(model_name) pause_units(hacluster_units, model_name=model_name) + + zaza.model.block_until_all_units_idle(model_name) pause_units(target, model_name=model_name) + zaza.model.block_until_all_units_idle(model_name) action_unit_upgrade(target, model_name=model_name) + zaza.model.block_until_all_units_idle(model_name) resume_units(target, model_name=model_name) + + zaza.model.block_until_all_units_idle(model_name) resume_units(hacluster_units, model_name=model_name) done.extend(target) From 837bfad3956c48de33cd642e16f8462479147c5c Mon Sep 17 00:00:00 2001 From: Martin Kalcok Date: Thu, 29 Jul 2021 17:54:29 +0200 Subject: [PATCH 15/62] Skip LBaas tests on releases below mitaka. The last mitaka test bundle (xenial-mitaka) does not have loadbalancer services enabled. --- zaza/openstack/charm_tests/neutron/tests.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/zaza/openstack/charm_tests/neutron/tests.py b/zaza/openstack/charm_tests/neutron/tests.py index 8d34a88..889a46d 100644 --- a/zaza/openstack/charm_tests/neutron/tests.py +++ b/zaza/openstack/charm_tests/neutron/tests.py @@ -269,10 +269,13 @@ class NeutronGatewayStatusActionsTest(test_utils.OpenStackBaseTest): cls.neutron_client = ( openstack_utils.get_neutron_session_client(cls.keystone_session)) - # Loadbalancer tests not supported on Train and above + # Loadbalancer tests not supported on Train and above and on + # releases Mitaka and below current_release = openstack_utils.get_os_release() bionic_train = openstack_utils.get_os_release('bionic_train') - cls.SKIP_LBAAS_TESTS = current_release >= bionic_train + xenial_mitaka = openstack_utils.get_os_release('xenial_mitaka') + cls.SKIP_LBAAS_TESTS = (xenial_mitaka <= current_release or + current_release >= bionic_train) def tearDown(self): """Cleanup loadbalancers if there are any left over.""" From 12afecced1860a7a831c92693a36db1be71b274c Mon Sep 17 00:00:00 2001 From: Martin Kalcok Date: Fri, 30 Jul 2021 14:43:55 +0200 Subject: [PATCH 16/62] move loadbalancer cleanup logic to specific test. --- zaza/openstack/charm_tests/neutron/tests.py | 65 +++++++++++---------- 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/zaza/openstack/charm_tests/neutron/tests.py b/zaza/openstack/charm_tests/neutron/tests.py index 889a46d..bf57d6d 100644 --- a/zaza/openstack/charm_tests/neutron/tests.py +++ b/zaza/openstack/charm_tests/neutron/tests.py @@ -274,18 +274,9 @@ class NeutronGatewayStatusActionsTest(test_utils.OpenStackBaseTest): current_release = openstack_utils.get_os_release() bionic_train = openstack_utils.get_os_release('bionic_train') xenial_mitaka = openstack_utils.get_os_release('xenial_mitaka') - cls.SKIP_LBAAS_TESTS = (xenial_mitaka <= current_release or + cls.SKIP_LBAAS_TESTS = (current_release <= xenial_mitaka or current_release >= bionic_train) - def tearDown(self): - """Cleanup loadbalancers if there are any left over.""" - super(NeutronGatewayStatusActionsTest, self).tearDown() - if not self.SKIP_LBAAS_TESTS: - load_balancers = self.neutron_client.list_loadbalancers().get( - 'loadbalancers', []) - for lbaas in load_balancers: - self.neutron_client.delete_loadbalancer(lbaas['id']) - def _assert_result_match(self, action_result, resource_list, resource_name): """Assert that action_result contains same data as resource_list.""" @@ -352,33 +343,45 @@ class NeutronGatewayStatusActionsTest(test_utils.OpenStackBaseTest): if self.SKIP_LBAAS_TESTS: self.skipTest('LBaasV2 is not supported in this version.') - # create LBaasV2 for the purpose of this test - lbaas_name = 'test_lbaas' + loadbalancer_id = None - subnet_list = self.neutron_client.list_subnets( - name='private_subnet').get('subnets', []) + try: + # create LBaasV2 for the purpose of this test + lbaas_name = 'test_lbaas' + subnet_list = self.neutron_client.list_subnets( + name='private_subnet').get('subnets', []) - if not subnet_list: - raise RuntimeError('Expected subnet "private_subnet" is not ' - 'configured.') + if not subnet_list: + raise RuntimeError('Expected subnet "private_subnet" is not ' + 'configured.') - subnet = subnet_list[0] - loadbalancer_data = {'loadbalancer': {'name': lbaas_name, - 'vip_subnet_id': subnet['id']}} - self.neutron_client.create_loadbalancer(body=loadbalancer_data) + subnet = subnet_list[0] + loadbalancer_data = {'loadbalancer': {'name': lbaas_name, + 'vip_subnet_id': subnet['id'] + } + } + loadbalancer = self.neutron_client.create_loadbalancer( + body=loadbalancer_data) + loadbalancer_id = loadbalancer['loadbalancer']['id'] - # test that client and action report same data - ngw_unit = zaza.model.get_units(self.application_name, - model_name=self.model_name)[0] - lbaas_from_client = self.neutron_client.list_loadbalancers().get( - 'loadbalancers', []) + # test that client and action report same data + ngw_unit = zaza.model.get_units(self.application_name, + model_name=self.model_name)[0] + lbaas_from_client = self.neutron_client.list_loadbalancers().get( + 'loadbalancers', []) - result = zaza.model.run_action(ngw_unit.entity_id, - 'get-status-lb', - model_name=self.model_name, - action_params={"format": "json"}) + result = zaza.model.run_action(ngw_unit.entity_id, + 'get-status-lb', + model_name=self.model_name, + action_params={"format": "json"}) - self._assert_result_match(result, lbaas_from_client, 'load-balancers') + self._assert_result_match(result, lbaas_from_client, + 'load-balancers') + except Exception as exc: + raise exc + finally: + if loadbalancer_id: + self.neutron_client.delete_loadbalancer(loadbalancer_id) class NeutronCreateNetworkTest(test_utils.OpenStackBaseTest): From a5e5d9ec0fe2666d804fc3656b7b09d0958144d5 Mon Sep 17 00:00:00 2001 From: Martin Kalcok Date: Mon, 2 Aug 2021 09:27:54 +0200 Subject: [PATCH 17/62] add more info to output of juju action fails --- zaza/openstack/charm_tests/neutron/tests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zaza/openstack/charm_tests/neutron/tests.py b/zaza/openstack/charm_tests/neutron/tests.py index bf57d6d..0d1738f 100644 --- a/zaza/openstack/charm_tests/neutron/tests.py +++ b/zaza/openstack/charm_tests/neutron/tests.py @@ -281,7 +281,8 @@ class NeutronGatewayStatusActionsTest(test_utils.OpenStackBaseTest): resource_name): """Assert that action_result contains same data as resource_list.""" # make sure that action completed successfully - self.assertEqual(action_result.status, 'completed') + if action_result.status != 'completed': + self.fail('Juju Action failed: {}'.format(action_result.message)) # extract data from juju action action_data = action_result.data.get('results', {}).get(resource_name) From 15ed2ab6dbbc2689f601083d9bf761befdb93f91 Mon Sep 17 00:00:00 2001 From: Martin Kalcok Date: Mon, 2 Aug 2021 17:50:38 +0200 Subject: [PATCH 18/62] Flip test logic for better readability & remove unnecessary `except` block --- zaza/openstack/charm_tests/neutron/tests.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/zaza/openstack/charm_tests/neutron/tests.py b/zaza/openstack/charm_tests/neutron/tests.py index 0d1738f..3ed8239 100644 --- a/zaza/openstack/charm_tests/neutron/tests.py +++ b/zaza/openstack/charm_tests/neutron/tests.py @@ -271,11 +271,10 @@ class NeutronGatewayStatusActionsTest(test_utils.OpenStackBaseTest): # Loadbalancer tests not supported on Train and above and on # releases Mitaka and below - current_release = openstack_utils.get_os_release() + current = openstack_utils.get_os_release() bionic_train = openstack_utils.get_os_release('bionic_train') xenial_mitaka = openstack_utils.get_os_release('xenial_mitaka') - cls.SKIP_LBAAS_TESTS = (current_release <= xenial_mitaka or - current_release >= bionic_train) + cls.SKIP_LBAAS_TESTS = not (xenial_mitaka > current < bionic_train) def _assert_result_match(self, action_result, resource_list, resource_name): @@ -378,8 +377,6 @@ class NeutronGatewayStatusActionsTest(test_utils.OpenStackBaseTest): self._assert_result_match(result, lbaas_from_client, 'load-balancers') - except Exception as exc: - raise exc finally: if loadbalancer_id: self.neutron_client.delete_loadbalancer(loadbalancer_id) From caa8303cc776bbc6faf09a14470a6e53915d0506 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Tue, 3 Aug 2021 12:24:49 +0000 Subject: [PATCH 19/62] Bump trilio snapshot create timeout A recent testrun failed when using an S3 backend because the snapshot creation timed out. The time out is set to 900s but it took 959s. --- zaza/openstack/charm_tests/trilio/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zaza/openstack/charm_tests/trilio/tests.py b/zaza/openstack/charm_tests/trilio/tests.py index 6fb7692..156f1e2 100644 --- a/zaza/openstack/charm_tests/trilio/tests.py +++ b/zaza/openstack/charm_tests/trilio/tests.py @@ -262,7 +262,7 @@ class WorkloadmgrCLIHelper(object): retryer = tenacity.Retrying( wait=tenacity.wait_exponential(multiplier=1, max=30), - stop=tenacity.stop_after_delay(900), + stop=tenacity.stop_after_delay(1200), reraise=True, ) From 12596b4a7a2d3988f9ddd039aa7ee8cc0940b2bf Mon Sep 17 00:00:00 2001 From: Liam Young Date: Tue, 10 Aug 2021 11:25:50 +0100 Subject: [PATCH 20/62] Very basic trilio update test (#616) Simple test that the action to update trilio completes. --- zaza/openstack/charm_tests/trilio/tests.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/zaza/openstack/charm_tests/trilio/tests.py b/zaza/openstack/charm_tests/trilio/tests.py index 156f1e2..8e81ae7 100644 --- a/zaza/openstack/charm_tests/trilio/tests.py +++ b/zaza/openstack/charm_tests/trilio/tests.py @@ -18,6 +18,7 @@ import logging import tenacity +import unittest import zaza.model as zaza_model @@ -424,6 +425,22 @@ class TrilioBaseTest(test_utils.OpenStackBaseTest): logging.info("Initiating restore") workloadmgrcli.oneclick_restore(snapshot_id) + def test_update_trilio_action(self): + """Test that the action runs succesfully.""" + action_name = 'update-trilio' + actions = zaza_model.get_actions( + self.application_name) + if action_name not in actions: + raise unittest.SkipTest( + 'Action {} not defined'.format(action_name)) + + generic_utils.assertActionRanOK(zaza_model.run_action( + self.lead_unit, + action_name, + action_params={}, + model_name=self.model_name) + ) + class TrilioGhostNFSShareTest(TrilioBaseTest): """Tests for Trilio charms providing the ghost-share action.""" From 25ec6f84db14bfdd214ff4a8e124f5510874ac49 Mon Sep 17 00:00:00 2001 From: Aurelien Lourot Date: Tue, 10 Aug 2021 12:29:19 +0200 Subject: [PATCH 21/62] Add ceilometer-agent tests (#615) https://launchpad.net/bugs/1927277 --- .../charm_tests/ceilometer_agent/__init__.py | 17 ++ .../charm_tests/ceilometer_agent/tests.py | 225 ++++++++++++++++++ zaza/openstack/charm_tests/neutron/setup.py | 2 +- zaza/openstack/charm_tests/test_utils.py | 2 +- 4 files changed, 244 insertions(+), 2 deletions(-) create mode 100644 zaza/openstack/charm_tests/ceilometer_agent/__init__.py create mode 100644 zaza/openstack/charm_tests/ceilometer_agent/tests.py diff --git a/zaza/openstack/charm_tests/ceilometer_agent/__init__.py b/zaza/openstack/charm_tests/ceilometer_agent/__init__.py new file mode 100644 index 0000000..6272e26 --- /dev/null +++ b/zaza/openstack/charm_tests/ceilometer_agent/__init__.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 + +# Copyright 2021 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 ceilometer-agent.""" diff --git a/zaza/openstack/charm_tests/ceilometer_agent/tests.py b/zaza/openstack/charm_tests/ceilometer_agent/tests.py new file mode 100644 index 0000000..655b1c7 --- /dev/null +++ b/zaza/openstack/charm_tests/ceilometer_agent/tests.py @@ -0,0 +1,225 @@ +#!/usr/bin/env python3 + +# Copyright 2021 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 ceilometer-agent testing.""" + +import logging +import time +from gnocchiclient.v1 import client as gnocchi_client + +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 + + +class CeilometerAgentTest(test_utils.OpenStackBaseTest): + """Encapsulate ceilometer-agent tests.""" + + def tearDown(self): + """Cleanup of VM guests.""" + self.resource_cleanup() + + def test_400_gnocchi_metrics(self): + """Verify that ceilometer-agent publishes metrics to gnocchi.""" + current_os_release = openstack_utils.get_os_release() + openstack_pike_or_older = ( + current_os_release <= + openstack_utils.get_os_release('xenial_pike')) + if openstack_pike_or_older: + # Both the charm and Ceilometer itself had different behaviors in + # terms of which metrics were published and how fast, which would + # lead to a combinatorial explosion if we had to maintain test + # expectations for these old releases. + logging.info( + 'OpenStack Pike or older, skipping') + return + + # ceilometer-agent-compute reports metrics for each existing VM, so at + # least one VM is needed: + self.RESOURCE_PREFIX = 'zaza-ceilometer-agent' + self.launch_guest( + 'ubuntu', instance_key=glance_setup.LTS_IMAGE_NAME) + + logging.info('Instantiating gnocchi client...') + overcloud_auth = openstack_utils.get_overcloud_auth() + keystone = openstack_utils.get_keystone_client(overcloud_auth) + gnocchi_ep = keystone.service_catalog.url_for( + service_type='metric', + interface='publicURL' + ) + gnocchi = gnocchi_client.Client( + session=openstack_utils.get_overcloud_keystone_session(), + adapter_options={ + 'endpoint_override': gnocchi_ep, + } + ) + + expected_metric_names = self.__get_expected_metric_names( + current_os_release) + + min_timeout_seconds = 500 + polling_interval_seconds = ( + openstack_utils.get_application_config_option( + self.application_name, 'polling-interval')) + timeout_seconds = max(10 * polling_interval_seconds, + min_timeout_seconds) + logging.info('Giving ceilometer-agent {}s to publish all metrics to ' + 'gnocchi...'.format(timeout_seconds)) + + max_time = time.time() + timeout_seconds + while time.time() < max_time: + found_metric_names = {metric['name'] + for metric in gnocchi.metric.list()} + missing_metric_names = expected_metric_names - found_metric_names + if len(missing_metric_names) == 0: + logging.info('All expected metrics found.') + break + time.sleep(polling_interval_seconds) + + unexpected_found_metric_names = ( + found_metric_names - expected_metric_names) + if len(unexpected_found_metric_names) > 0: + self.fail( + 'Unexpected metrics ' + 'published: ' + ', '.join(unexpected_found_metric_names)) + + if len(missing_metric_names) > 0: + self.fail('These metrics should have been published but ' + "weren't: " + ', '.join(missing_metric_names)) + + def __get_expected_metric_names(self, current_os_release): + expected_metric_names = { + 'compute.instance.booting.time', + 'disk.ephemeral.size', + 'disk.root.size', + 'image.download', + 'image.serve', + 'image.size', + 'memory', + 'vcpus', + } + + all_polsters_are_enabled = ( + openstack_utils.get_application_config_option( + self.application_name, 'enable-all-pollsters')) + + if all_polsters_are_enabled: + expected_metric_names |= { + 'disk.device.allocation', + 'disk.device.capacity', + 'disk.device.read.latency', + 'disk.device.usage', + 'disk.device.write.latency', + 'memory.resident', + 'memory.swap.in', + 'memory.swap.out', + 'network.incoming.packets.drop', + 'network.incoming.packets.error', + 'network.outgoing.packets.drop', + 'network.outgoing.packets.error', + } + + openstack_queens_or_older = ( + current_os_release <= + openstack_utils.get_os_release('bionic_queens')) + openstack_rocky_or_older = ( + current_os_release <= + openstack_utils.get_os_release('bionic_rocky')) + openstack_victoria_or_older = ( + current_os_release <= + openstack_utils.get_os_release('groovy_victoria')) + + if openstack_victoria_or_older: + expected_metric_names |= { + 'cpu', + 'disk.device.read.bytes', + 'disk.device.read.requests', + 'disk.device.write.bytes', + 'disk.device.write.requests', + 'memory.usage', + 'network.incoming.bytes', + 'network.incoming.packets', + 'network.outgoing.bytes', + 'network.outgoing.packets', + } + + if openstack_rocky_or_older: + expected_metric_names |= { + 'cpu.delta', + 'cpu_util', + 'disk.device.read.bytes.rate', + 'disk.device.read.requests.rate', + 'disk.device.write.bytes.rate', + 'disk.device.write.requests.rate', + 'network.incoming.bytes.rate', + 'network.incoming.packets.rate', + 'network.outgoing.bytes.rate', + 'network.outgoing.packets.rate', + } + if all_polsters_are_enabled: + expected_metric_names |= { + 'disk.allocation', + 'disk.capacity', + 'disk.read.bytes', + 'disk.read.bytes.rate', + 'disk.read.requests', + 'disk.read.requests.rate', + 'disk.usage', + 'disk.write.bytes', + 'disk.write.bytes.rate', + 'disk.write.requests', + 'disk.write.requests.rate', + } + + if openstack_queens_or_older: + expected_metric_names |= { + 'cpu_l3_cache', + 'disk.allocation', + 'disk.capacity', + 'disk.device.allocation', + 'disk.device.capacity', + 'disk.device.iops', + 'disk.device.latency', + 'disk.device.read.latency', + 'disk.device.usage', + 'disk.device.write.latency', + 'disk.iops', + 'disk.latency', + 'disk.read.bytes', + 'disk.read.bytes.rate', + 'disk.read.requests', + 'disk.read.requests.rate', + 'disk.usage', + 'disk.write.bytes', + 'disk.write.bytes.rate', + 'disk.write.requests', + 'disk.write.requests.rate', + 'memory.bandwidth.local', + 'memory.bandwidth.total', + 'memory.resident', + 'memory.swap.in', + 'memory.swap.out', + 'network.incoming.packets.drop', + 'network.incoming.packets.error', + 'network.outgoing.packets.drop', + 'network.outgoing.packets.error', + 'perf.cache.misses', + 'perf.cache.references', + 'perf.cpu.cycles', + 'perf.instructions', + } + + return expected_metric_names diff --git a/zaza/openstack/charm_tests/neutron/setup.py b/zaza/openstack/charm_tests/neutron/setup.py index a1d1dd4..11a01bc 100644 --- a/zaza/openstack/charm_tests/neutron/setup.py +++ b/zaza/openstack/charm_tests/neutron/setup.py @@ -82,7 +82,7 @@ def basic_overcloud_network(limit_gws=None): # Get keystone session keystone_session = openstack_utils.get_overcloud_keystone_session() - # Get optional use_juju_wait for netw ork option + # Get optional use_juju_wait for network option options = (lifecycle_utils .get_charm_config(fatal=False) .get('configure_options', {})) diff --git a/zaza/openstack/charm_tests/test_utils.py b/zaza/openstack/charm_tests/test_utils.py index bc67933..be47dd9 100644 --- a/zaza/openstack/charm_tests/test_utils.py +++ b/zaza/openstack/charm_tests/test_utils.py @@ -635,7 +635,7 @@ class OpenStackBaseTest(BaseCharmTest): def launch_guest(self, guest_name, userdata=None, use_boot_volume=False, instance_key=None): - """Launch two guests to use in tests. + """Launch one guest 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. From 3229ce2d426523594ad7184d6223d14010ef9d34 Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Wed, 11 Aug 2021 13:53:45 +0000 Subject: [PATCH 22/62] Switch tempest config to use include and exclude flags --- zaza/openstack/charm_tests/tempest/tests.py | 35 ++++++++++++--------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/zaza/openstack/charm_tests/tempest/tests.py b/zaza/openstack/charm_tests/tempest/tests.py index 979caff..5c83ec0 100644 --- a/zaza/openstack/charm_tests/tempest/tests.py +++ b/zaza/openstack/charm_tests/tempest/tests.py @@ -33,8 +33,13 @@ class TempestTest(): """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), regex (list of regex's), and keep-workspace (bool). + valid test keys are: + - smoke (bool) + - include-list (list of tests) + - exclude-list (list of tests) + - regex (list of regex's) + - exclude-regex (list of regex's) + - keep-workspace (bool) :returns: Status of tempest run :rtype: bool @@ -57,23 +62,23 @@ class TempestTest(): tempest_options.extend( ['--regex', ' '.join([reg for reg in config.get('regex')])]) - if config.get('black-regex'): + if config.get('exclude-regex'): tempest_options.extend( - ['--black-regex', - ' '.join([reg for reg in config.get('black-regex')])]) + ['--exclude-regex', + ' '.join([reg for reg in config.get('exclude-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'))) + if config.get('include-list'): + include_file = os.path.join(tmpdirname, 'include.cfg') + with open(include_file, 'w') as f: + f.write('\n'.join(config.get('include-list'))) 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'))) + tempest_options.extend(['--include-list', include_file]) + if config.get('exclude-list'): + exclude_file = os.path.join(tmpdirname, 'exclude.cfg') + with open(exclude_file, 'w') as f: + f.write('\n'.join(config.get('exclude-list'))) f.write('\n') - tempest_options.extend(['--blacklist-file', black_file]) + tempest_options.extend(['--exclude-list', exclude_file]) print(tempest_options) try: subprocess.check_call(tempest_options) From 6e8201f696aa0c5d4495cb981dfb28bef44a5912 Mon Sep 17 00:00:00 2001 From: Frode Nordahl Date: Fri, 13 Aug 2021 08:26:18 +0200 Subject: [PATCH 23/62] Only configure one interface on MAAS provider The Zaza MAAS code selects interfaces that are attached to the provided CIDR, set up as unconfigured and has link. In the event a machine has multiple unconfigured interfaces attached to the same physical network, adding them all to the configuration might lead to undesired side effects such as network loops. --- unit_tests/__init__.py | 5 ---- .../test_zaza_utilities_openstack.py | 29 +++++++++++++++++++ zaza/openstack/utilities/openstack.py | 19 ++++++++---- 3 files changed, 43 insertions(+), 10 deletions(-) diff --git a/unit_tests/__init__.py b/unit_tests/__init__.py index 03c4879..8203d13 100644 --- a/unit_tests/__init__.py +++ b/unit_tests/__init__.py @@ -11,8 +11,3 @@ # 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 sys -import unittest.mock as mock - -sys.modules['zaza.utilities.maas'] = mock.MagicMock() diff --git a/unit_tests/utilities/test_zaza_utilities_openstack.py b/unit_tests/utilities/test_zaza_utilities_openstack.py index 0434a2b..2dd3fcd 100644 --- a/unit_tests/utilities/test_zaza_utilities_openstack.py +++ b/unit_tests/utilities/test_zaza_utilities_openstack.py @@ -24,6 +24,7 @@ import tenacity import unit_tests.utils as ut_utils from zaza.openstack.utilities import openstack as openstack_utils from zaza.openstack.utilities import exceptions +from zaza.utilities.maas import LinkMode, MachineInterfaceMac class TestOpenStackUtils(ut_utils.BaseTestCase): @@ -1428,6 +1429,34 @@ class TestOpenStackUtils(ut_utils.BaseTestCase): self.move.assert_called_once_with( 'tempfilename', '/tmp/default/ca1.cert') + def test_configure_charmed_openstack_on_maas(self): + self.patch_object(openstack_utils, 'get_charm_networking_data') + self.patch_object(openstack_utils.zaza.utilities.maas, + 'get_macs_from_cidr') + self.patch_object(openstack_utils.zaza.utilities.maas, + 'get_maas_client_from_juju_cloud_data') + self.patch_object(openstack_utils.zaza.model, 'get_cloud_data') + self.patch_object(openstack_utils, 'configure_networking_charms') + self.get_charm_networking_data.return_value = 'fakenetworkingdata' + self.get_macs_from_cidr.return_value = [ + MachineInterfaceMac('id_a', 'ens6', '00:53:00:00:00:01', + '192.0.2.0/24', LinkMode.LINK_UP), + MachineInterfaceMac('id_a', 'ens7', '00:53:00:00:00:02', + '192.0.2.0/24', LinkMode.LINK_UP), + MachineInterfaceMac('id_b', 'ens6', '00:53:00:00:01:01', + '192.0.2.0/24', LinkMode.LINK_UP), + + ] + network_config = {'external_net_cidr': '192.0.2.0/24'} + expect = [ + '00:53:00:00:00:01', + '00:53:00:00:01:01', + ] + openstack_utils.configure_charmed_openstack_on_maas( + network_config) + self.configure_networking_charms.assert_called_once_with( + 'fakenetworkingdata', expect, use_juju_wait=False) + class TestAsyncOpenstackUtils(ut_utils.AioTestCase): diff --git a/zaza/openstack/utilities/openstack.py b/zaza/openstack/utilities/openstack.py index a5ba6f6..fc0d914 100644 --- a/zaza/openstack/utilities/openstack.py +++ b/zaza/openstack/utilities/openstack.py @@ -1056,14 +1056,23 @@ def configure_charmed_openstack_on_maas(network_config, limit_gws=None): :type limit_gws: Optional[int] """ networking_data = get_charm_networking_data(limit_gws=limit_gws) - macs = [ - mim.mac - for mim in zaza.utilities.maas.get_macs_from_cidr( + macs = [] + machines = set() + for mim in zaza.utilities.maas.get_macs_from_cidr( zaza.utilities.maas.get_maas_client_from_juju_cloud_data( zaza.model.get_cloud_data()), network_config['external_net_cidr'], - link_mode=zaza.utilities.maas.LinkMode.LINK_UP) - ] + link_mode=zaza.utilities.maas.LinkMode.LINK_UP): + if mim.machine_id in machines: + logging.warning("Machine {} has multiple unconfigured interfaces, " + "ignoring interface {} ({})." + .format(mim.machine_id, mim.ifname, mim.mac)) + continue + logging.info("Machine {} selected {} ({}) for external networking." + .format(mim.machine_id, mim.ifname, mim.mac)) + machines.add(mim.machine_id) + macs.append(mim.mac) + if macs: configure_networking_charms( networking_data, macs, use_juju_wait=False) From 66d08c0866cdf6929d79cfba2ee79d197f4bfe30 Mon Sep 17 00:00:00 2001 From: Aurelien Lourot Date: Fri, 13 Aug 2021 10:44:32 +0200 Subject: [PATCH 24/62] Make vault/setup/validate_ca() more robust --- zaza/openstack/charm_tests/vault/setup.py | 20 ++-------- zaza/openstack/charm_tests/vault/tests.py | 22 +---------- zaza/openstack/charm_tests/vault/utils.py | 48 ++++++++++++++++++++--- 3 files changed, 47 insertions(+), 43 deletions(-) diff --git a/zaza/openstack/charm_tests/vault/setup.py b/zaza/openstack/charm_tests/vault/setup.py index c792508..87917bc 100644 --- a/zaza/openstack/charm_tests/vault/setup.py +++ b/zaza/openstack/charm_tests/vault/setup.py @@ -17,14 +17,12 @@ import base64 import functools import logging -import requests import tempfile import zaza.charm_lifecycle.utils as lifecycle_utils import zaza.openstack.charm_tests.vault.utils as vault_utils import zaza.model import zaza.openstack.utilities.cert -import zaza.openstack.utilities.openstack import zaza.openstack.utilities.generic import zaza.openstack.utilities.exceptions as zaza_exceptions import zaza.utilities.juju as juju_utils @@ -95,7 +93,7 @@ def mojo_unseal_by_unit(): def unseal_by_unit(cacert=None): """Unseal any units reported as sealed using mojo cacert.""" cacert = cacert or get_cacert_file() - vault_creds = vault_utils.get_credentails() + vault_creds = vault_utils.get_credentials() for client in vault_utils.get_clients(cacert=cacert): if client.hvac_client.is_sealed(): client.hvac_client.unseal(vault_creds['keys'][0]) @@ -126,7 +124,7 @@ async def async_mojo_unseal_by_unit(): async def async_unseal_by_unit(cacert=None): """Unseal any units reported as sealed using vault cacert.""" cacert = cacert or get_cacert_file() - vault_creds = vault_utils.get_credentails() + vault_creds = vault_utils.get_credentials() for client in vault_utils.get_clients(cacert=cacert): if client.hvac_client.is_sealed(): client.hvac_client.unseal(vault_creds['keys'][0]) @@ -222,16 +220,4 @@ def validate_ca(cacertificate, application="keystone", port=5000): :returns: None :rtype: None """ - zaza.openstack.utilities.openstack.block_until_ca_exists( - application, - cacertificate.decode().strip()) - vip = (zaza.model.get_application_config(application) - .get("vip").get("value")) - if vip: - ip = vip - else: - ip = zaza.model.get_app_ips(application)[0] - with tempfile.NamedTemporaryFile(mode='w') as fp: - fp.write(cacertificate.decode()) - fp.flush() - requests.get('https://{}:{}'.format(ip, str(port)), verify=fp.name) + vault_utils.validate_ca(cacertificate, application, port) diff --git a/zaza/openstack/charm_tests/vault/tests.py b/zaza/openstack/charm_tests/vault/tests.py index a83440f..4ba2e58 100644 --- a/zaza/openstack/charm_tests/vault/tests.py +++ b/zaza/openstack/charm_tests/vault/tests.py @@ -21,9 +21,7 @@ import json import logging import unittest import uuid -import tempfile -import requests import tenacity from hvac.exceptions import InternalServerError @@ -64,7 +62,7 @@ class BaseVaultTest(test_utils.OpenStackBaseTest): cls.vip_client = vault_utils.get_vip_client() if cls.vip_client: cls.clients.append(cls.vip_client) - cls.vault_creds = vault_utils.get_credentails() + cls.vault_creds = vault_utils.get_credentials() vault_utils.unseal_all(cls.clients, cls.vault_creds['keys'][0]) vault_utils.auth_all(cls.clients, cls.vault_creds['root_token']) vault_utils.ensure_secret_backend(cls.clients[0]) @@ -180,26 +178,10 @@ class VaultTest(BaseVaultTest): except KeyError: # Already removed pass - zaza.openstack.utilities.openstack.block_until_ca_exists( - 'keystone', - cacert.decode().strip()) zaza.model.wait_for_application_states( states=test_config.get('target_deploy_status', {})) - ip = zaza.model.get_app_ips( - 'keystone')[0] - with tempfile.NamedTemporaryFile(mode='w') as fp: - fp.write(cacert.decode()) - fp.flush() - # Avoid race condition and retry - for attempt in tenacity.Retrying( - stop=tenacity.stop_after_attempt(3), - wait=tenacity.wait_exponential( - multiplier=2, min=2, max=10)): - with attempt: - logging.info( - "Attempting to connect to https://{}:5000".format(ip)) - requests.get('https://{}:5000'.format(ip), verify=fp.name) + vault_utils.validate_ca(cacert) def test_all_clients_authenticated(self): """Check all vault clients are authenticated.""" diff --git a/zaza/openstack/charm_tests/vault/utils.py b/zaza/openstack/charm_tests/vault/utils.py index b4b5579..c29c745 100644 --- a/zaza/openstack/charm_tests/vault/utils.py +++ b/zaza/openstack/charm_tests/vault/utils.py @@ -18,6 +18,7 @@ import base64 import hvac +import logging import requests import tempfile import urllib3 @@ -27,6 +28,7 @@ import tenacity import collections import zaza.model +import zaza.openstack.utilities.openstack import zaza.utilities.networking as network_utils AUTH_FILE = "vault_tests.yaml" @@ -70,10 +72,10 @@ class VaultFacade: def initialize(self): """Initialise vault and store resulting credentials.""" if self.is_initialized: - self.vault_creds = get_credentails() + self.vault_creds = get_credentials() else: self.vault_creds = init_vault(self.unseal_client) - store_credentails(self.vault_creds) + store_credentials(self.vault_creds) self.initialized = is_initialized(self.unseal_client) def unseal(self): @@ -294,7 +296,7 @@ def find_unit_with_creds(): return unit -def get_credentails(): +def get_credentials(): """Retrieve vault token and keys from unit. Retrieve vault token and keys from unit. These are stored on a unit @@ -315,7 +317,7 @@ def get_credentails(): return creds -def store_credentails(creds): +def store_credentials(creds): """Store the supplied credentials. Store the supplied credentials on a vault unit. ONLY USE FOR FUNCTIONAL @@ -334,7 +336,7 @@ def store_credentails(creds): '~/{}'.format(AUTH_FILE)) -def get_credentails_from_file(auth_file): +def get_credentials_from_file(auth_file): """Read the vault credentials from the auth_file. :param auth_file: Path to file with credentials @@ -347,7 +349,7 @@ def get_credentails_from_file(auth_file): return vault_creds -def write_credentails(auth_file, vault_creds): +def write_credentials(auth_file, vault_creds): """Write the vault credentials to the auth_file. :param auth_file: Path to file to write credentials @@ -434,3 +436,37 @@ def run_upload_signed_csr(pem, root_ca, allowed_domains): 'root-ca': base64.b64encode(root_ca).decode(), 'allowed-domains=': allowed_domains, 'ttl': '24h'}) + + +@tenacity.retry( + reraise=True, + wait=tenacity.wait_exponential(multiplier=2, min=2, max=10), + stop=tenacity.stop_after_attempt(3)) +def validate_ca(cacertificate, application="keystone", port=5000): + """Validate Certificate Authority against application. + + :param cacertificate: PEM formatted CA certificate + :type cacertificate: str + :param application: Which application to validate against. + :type application: str + :param port: Port to validate against. + :type port: int + :returns: None + :rtype: None + """ + zaza.openstack.utilities.openstack.block_until_ca_exists( + application, + cacertificate.decode().strip()) + vip = (zaza.model.get_application_config(application) + .get("vip").get("value")) + if vip: + ip = vip + else: + ip = zaza.model.get_app_ips(application)[0] + with tempfile.NamedTemporaryFile(mode='w') as fp: + fp.write(cacertificate.decode()) + fp.flush() + keystone_url = 'https://{}:{}'.format(ip, str(port)) + logging.info( + 'Attempting to connect to {}'.format(keystone_url)) + requests.get(keystone_url, verify=fp.name) From 8e537dc7338bb992c0de38bc99c9b92a64e75df3 Mon Sep 17 00:00:00 2001 From: Gustavo Sanchez Date: Tue, 17 Aug 2021 20:17:54 -0400 Subject: [PATCH 25/62] Adds tests for NRPE checks in Manila Adds a test to ensure that Manila units related to NRPE get service check files created. Closes-Bug: #1925977 --- zaza/openstack/charm_tests/manila/tests.py | 26 ++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/zaza/openstack/charm_tests/manila/tests.py b/zaza/openstack/charm_tests/manila/tests.py index 8381f2f..6cfa80f 100644 --- a/zaza/openstack/charm_tests/manila/tests.py +++ b/zaza/openstack/charm_tests/manila/tests.py @@ -23,6 +23,7 @@ from manilaclient import client as manilaclient import zaza.model import zaza.openstack.configure.guest as guest +import zaza.openstack.utilities.generic as generic_utils import zaza.openstack.utilities.openstack as openstack_utils import zaza.openstack.charm_tests.test_utils as test_utils import zaza.openstack.charm_tests.nova.utils as nova_utils @@ -85,6 +86,31 @@ class ManilaTests(test_utils.OpenStackBaseTest): def _list_shares(self): return self.manila_client.shares.list() + @tenacity.retry( + retry=tenacity.retry_if_result(lambda ret: ret is not None), + wait=tenacity.wait_fixed(120), + stop=tenacity.stop_after_attempt(2)) + def _retry_check_commands_on_units(self, cmds, units): + return generic_utils.check_commands_on_units(cmds, units) + + def test_902_nrpe_service_checks(self): + """Confirm that the NRPE service check files are created.""" + units = zaza.model.get_units('manila') + services = ['apache2', 'haproxy', 'manila-scheduler', 'manila-data'] + + cmds = [] + for check_name in services: + cmds.append( + 'egrep -oh /usr/local.* /etc/nagios/nrpe.d/' + 'check_{}.cfg'.format(check_name) + ) + + ret = self._retry_check_commands_on_units(cmds, units) + if ret: + logging.info(ret) + + self.assertIsNone(ret, msg=ret) + class ManilaBaseTest(test_utils.OpenStackBaseTest): """Encapsulate a Manila basic functionality test.""" From b6f6db365b84af5240ab8622c2009cc40e841136 Mon Sep 17 00:00:00 2001 From: Martin Kalcok Date: Fri, 20 Aug 2021 14:23:37 +0200 Subject: [PATCH 26/62] Fix after change of get-status-* actions output --- zaza/openstack/charm_tests/neutron/tests.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/zaza/openstack/charm_tests/neutron/tests.py b/zaza/openstack/charm_tests/neutron/tests.py index 3ed8239..1ab88c6 100644 --- a/zaza/openstack/charm_tests/neutron/tests.py +++ b/zaza/openstack/charm_tests/neutron/tests.py @@ -27,6 +27,7 @@ import tenacity from neutronclient.common import exceptions as neutronexceptions +import yaml import zaza import zaza.openstack.charm_tests.nova.utils as nova_utils import zaza.openstack.charm_tests.test_utils as test_utils @@ -285,12 +286,11 @@ class NeutronGatewayStatusActionsTest(test_utils.OpenStackBaseTest): # extract data from juju action action_data = action_result.data.get('results', {}).get(resource_name) - resources_from_action = json.loads(action_data) + resources_from_action = yaml.load(action_data) # pull resource IDs from expected resource list and juju action data expected_resource_ids = {resource['id'] for resource in resource_list} - result_resource_ids = {resource['id'] for resource in - resources_from_action} + result_resource_ids = resources_from_action.keys() # assert that juju action returned expected resources self.assertEqual(result_resource_ids, expected_resource_ids) @@ -310,8 +310,7 @@ class NeutronGatewayStatusActionsTest(test_utils.OpenStackBaseTest): # fetch neutron routers using juju-action result = zaza.model.run_action(ngw_unit.entity_id, 'get-status-routers', - model_name=self.model_name, - action_params={"format": "json"}) + model_name=self.model_name) # assert that data from neutron client match data from juju action self._assert_result_match(result, routers_from_client, 'router-list') @@ -331,8 +330,7 @@ class NeutronGatewayStatusActionsTest(test_utils.OpenStackBaseTest): # fetch DHCP networks using juju-action result = zaza.model.run_action(ngw_unit.entity_id, 'get-status-dhcp', - model_name=self.model_name, - action_params={"format": "json"}) + model_name=self.model_name) # assert that data from neutron client match data from juju action self._assert_result_match(result, networks_from_client, @@ -372,8 +370,7 @@ class NeutronGatewayStatusActionsTest(test_utils.OpenStackBaseTest): result = zaza.model.run_action(ngw_unit.entity_id, 'get-status-lb', - model_name=self.model_name, - action_params={"format": "json"}) + model_name=self.model_name) self._assert_result_match(result, lbaas_from_client, 'load-balancers') From 00742d547f24627c267bcbbd757f38699e22f097 Mon Sep 17 00:00:00 2001 From: Martin Kalcok Date: Fri, 20 Aug 2021 14:28:24 +0200 Subject: [PATCH 27/62] remove unused import --- zaza/openstack/charm_tests/neutron/tests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/zaza/openstack/charm_tests/neutron/tests.py b/zaza/openstack/charm_tests/neutron/tests.py index 1ab88c6..99088a4 100644 --- a/zaza/openstack/charm_tests/neutron/tests.py +++ b/zaza/openstack/charm_tests/neutron/tests.py @@ -21,7 +21,6 @@ import copy -import json import logging import tenacity From c759a23962d2de3c7c67d45841114ceb1ae1cf7e Mon Sep 17 00:00:00 2001 From: coreycb Date: Tue, 24 Aug 2021 09:48:55 -0400 Subject: [PATCH 28/62] Do not update external network data port if already set (#625) --- zaza/openstack/utilities/openstack.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/zaza/openstack/utilities/openstack.py b/zaza/openstack/utilities/openstack.py index fc0d914..a4fcbc3 100644 --- a/zaza/openstack/utilities/openstack.py +++ b/zaza/openstack/utilities/openstack.py @@ -986,8 +986,11 @@ def configure_networking_charms(networking_data, macs, use_juju_wait=True): current_data_port = get_application_config_option( application_name, networking_data.port_config_key) - if current_data_port == config[networking_data.port_config_key]: - logging.info('Config already set to value') + if current_data_port: + logging.info("Skip update of external network data port config." + "Config '{}' already set to value: {}".format( + networking_data.port_config_key, + current_data_port)) return model.set_application_config( From f3a38c0c905b9c587c4143dc52300763ac732458 Mon Sep 17 00:00:00 2001 From: Luciano Lo Giudice Date: Wed, 25 Aug 2021 20:11:10 -0300 Subject: [PATCH 29/62] Implement the tests for the cinder-lvm charm The cinder-lvm charm is a subordinate of the cinder charm. The spec can be found here: https://github.com/openstack/charm-specs/blob/master/specs/xena/backlog/cinder-lvm.rst --- .../charm_tests/cinder_lvm/__init__.py | 15 +++ .../openstack/charm_tests/cinder_lvm/tests.py | 124 ++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 zaza/openstack/charm_tests/cinder_lvm/__init__.py create mode 100644 zaza/openstack/charm_tests/cinder_lvm/tests.py diff --git a/zaza/openstack/charm_tests/cinder_lvm/__init__.py b/zaza/openstack/charm_tests/cinder_lvm/__init__.py new file mode 100644 index 0000000..740be90 --- /dev/null +++ b/zaza/openstack/charm_tests/cinder_lvm/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2021 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 cinder-lvm.""" diff --git a/zaza/openstack/charm_tests/cinder_lvm/tests.py b/zaza/openstack/charm_tests/cinder_lvm/tests.py new file mode 100644 index 0000000..6363bb1 --- /dev/null +++ b/zaza/openstack/charm_tests/cinder_lvm/tests.py @@ -0,0 +1,124 @@ +#!/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. + +"""Encapsulate cinder-lvm testing.""" + +import logging +import uuid + +import zaza.model +import zaza.openstack.charm_tests.test_utils as test_utils +import zaza.openstack.utilities.openstack as openstack_utils + + +def with_conf(key, value): + def patched(f): + def inner(*args, **kwargs): + prev = openstack_utils.get_application_config_option( + 'cinder-lvm', key) + try: + zaza.model.set_application_config('cinder-lvm', {key: value}) + zaza.model.wait_for_agent_status(model_name=None) + return f(*args, **kwargs) + finally: + zaza.model.set_application_config( + 'cinder-lvm', {key: str(prev)}) + zaza.model.wait_for_agent_status(model_name=None) + return inner + return patched + + +class CinderLVMTest(test_utils.OpenStackBaseTest): + """Encapsulate cinder-lvm tests.""" + + @classmethod + def setUpClass(cls): + """Run class setup for running tests.""" + super(CinderLVMTest, cls).setUpClass(application_name='cinder-lvm') + cls.model_name = zaza.model.get_juju_model() + cls.cinder_client = openstack_utils.get_cinder_session_client( + cls.keystone_session) + + @classmethod + def tearDown(cls): + volumes = cls.cinder_client.volumes + for volume in volumes.list(): + if volume.name.startswith('zaza'): + try: + volume.detach() + volumes.delete(volume) + except Exception: + pass + + def test_cinder_config(self): + logging.info('cinder-lvm') + expected_contents = { + 'LVM-zaza-lvm': { + 'volume_clear': ['zero'], + 'volumes_dir': ['/var/lib/cinder/volumes'], + 'volume_name_template': ['volume-%s'], + 'volume_clear_size': ['0'], + 'volume_driver': ['cinder.volume.drivers.lvm.LVMVolumeDriver'], + }} + + zaza.model.run_on_leader( + 'cinder', + 'sudo cp /etc/cinder/cinder.conf /tmp/', + model_name=self.model_name) + zaza.model.block_until_oslo_config_entries_match( + 'cinder', + '/tmp/cinder.conf', + expected_contents, + model_name=self.model_name, + timeout=10) + + def _tst_create_volume(self): + test_vol_name = "zaza{}".format(uuid.uuid1().fields[0]) + vol_new = self.cinder_client.volumes.create( + name=test_vol_name, + size='1') + openstack_utils.resource_reaches_status( + self.cinder_client.volumes, + vol_new.id, + wait_iteration_max_time=12000, + stop_after_attempt=5, + expected_status='available', + msg='Volume status wait') + return self.cinder_client.volumes.find(name=test_vol_name) + + def test_create_volume(self): + test_vol = self._tst_create_volume() + self.assertTrue(test_vol) + + host = getattr(test_vol, 'os-vol-host-attr:host').split('#')[0] + self.assertTrue(host.startswith('cinder@LVM')) + + @with_conf('overwrite', 'true') + @with_conf('block-device', '/dev/vdc') + def test_volume_overwrite(self): + self._tst_create_volume() + + @with_conf('block-device', 'none') + def test_device_none(self): + self._tst_create_volume() + + @with_conf('remove-missing', 'true') + def test_remove_missing_volume(self): + self._tst_create_volume() + + @with_conf('remove-missing-force', 'true') + def test_remove_missing_force(self): + self._tst_create_volume() From fef55ec31862caff5ed3f7cd7a4daa7bbfc57f93 Mon Sep 17 00:00:00 2001 From: Luciano Lo Giudice Date: Wed, 25 Aug 2021 20:27:57 -0300 Subject: [PATCH 30/62] PEP8 style fixes --- zaza/openstack/charm_tests/cinder_lvm/tests.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/zaza/openstack/charm_tests/cinder_lvm/tests.py b/zaza/openstack/charm_tests/cinder_lvm/tests.py index 6363bb1..43b4faa 100644 --- a/zaza/openstack/charm_tests/cinder_lvm/tests.py +++ b/zaza/openstack/charm_tests/cinder_lvm/tests.py @@ -25,6 +25,7 @@ import zaza.openstack.utilities.openstack as openstack_utils def with_conf(key, value): + """Temporarily set a configuration option for the cinder-lvm unit.""" def patched(f): def inner(*args, **kwargs): prev = openstack_utils.get_application_config_option( @@ -54,6 +55,7 @@ class CinderLVMTest(test_utils.OpenStackBaseTest): @classmethod def tearDown(cls): + """Remove tests resources.""" volumes = cls.cinder_client.volumes for volume in volumes.list(): if volume.name.startswith('zaza'): @@ -64,6 +66,7 @@ class CinderLVMTest(test_utils.OpenStackBaseTest): pass def test_cinder_config(self): + """Test that configuration options match our expectations.""" logging.info('cinder-lvm') expected_contents = { 'LVM-zaza-lvm': { @@ -86,6 +89,7 @@ class CinderLVMTest(test_utils.OpenStackBaseTest): timeout=10) def _tst_create_volume(self): + """Create a volume via the LVM backend.""" test_vol_name = "zaza{}".format(uuid.uuid1().fields[0]) vol_new = self.cinder_client.volumes.create( name=test_vol_name, @@ -100,6 +104,7 @@ class CinderLVMTest(test_utils.OpenStackBaseTest): return self.cinder_client.volumes.find(name=test_vol_name) def test_create_volume(self): + """Test creating a volume with basic configuration.""" test_vol = self._tst_create_volume() self.assertTrue(test_vol) @@ -109,16 +114,20 @@ class CinderLVMTest(test_utils.OpenStackBaseTest): @with_conf('overwrite', 'true') @with_conf('block-device', '/dev/vdc') def test_volume_overwrite(self): + """Test creating a volume by overwriting one on the /dev/vdc device.""" self._tst_create_volume() @with_conf('block-device', 'none') def test_device_none(self): + """Test creating a volume in a dummy device (set as 'none').""" self._tst_create_volume() @with_conf('remove-missing', 'true') def test_remove_missing_volume(self): + """Test creating a volume after removing missing ones in a group.""" self._tst_create_volume() @with_conf('remove-missing-force', 'true') def test_remove_missing_force(self): + """Test volume creation by forcefully removing missing ones.""" self._tst_create_volume() From 5a5974233339d2392c5a70914f2f66280276e6dd Mon Sep 17 00:00:00 2001 From: Luciano Lo Giudice Date: Tue, 31 Aug 2021 12:24:03 -0300 Subject: [PATCH 31/62] Update the functional tests for cinder-lvm. This brings the changes from the 'cinder-lvm' repo itself, from where this class will be removed. --- .../openstack/charm_tests/cinder_lvm/tests.py | 47 ++++++++++--------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/zaza/openstack/charm_tests/cinder_lvm/tests.py b/zaza/openstack/charm_tests/cinder_lvm/tests.py index 43b4faa..6ed1d60 100644 --- a/zaza/openstack/charm_tests/cinder_lvm/tests.py +++ b/zaza/openstack/charm_tests/cinder_lvm/tests.py @@ -24,20 +24,24 @@ import zaza.openstack.charm_tests.test_utils as test_utils import zaza.openstack.utilities.openstack as openstack_utils -def with_conf(key, value): - """Temporarily set a configuration option for the cinder-lvm unit.""" +def with_conf(application, config, model_name=None): + """Temporarily change the config options for an application in a model.""" + prev = {} + for key in config.keys(): + prev[key] = str(openstack_utils.get_application_config_option( + application, key, model_name=model_name)) + def patched(f): def inner(*args, **kwargs): - prev = openstack_utils.get_application_config_option( - 'cinder-lvm', key) try: - zaza.model.set_application_config('cinder-lvm', {key: value}) - zaza.model.wait_for_agent_status(model_name=None) + zaza.model.set_application_config( + application, config, model_name=model_name) + zaza.model.wait_for_agent_status(model_name=model_name) return f(*args, **kwargs) finally: zaza.model.set_application_config( - 'cinder-lvm', {key: str(prev)}) - zaza.model.wait_for_agent_status(model_name=None) + application, prev, model_name=model_name) + zaza.model.wait_for_agent_status(model_name=model_name) return inner return patched @@ -55,7 +59,7 @@ class CinderLVMTest(test_utils.OpenStackBaseTest): @classmethod def tearDown(cls): - """Remove tests resources.""" + """Remove test resources.""" volumes = cls.cinder_client.volumes for volume in volumes.list(): if volume.name.startswith('zaza'): @@ -88,7 +92,7 @@ class CinderLVMTest(test_utils.OpenStackBaseTest): model_name=self.model_name, timeout=10) - def _tst_create_volume(self): + def _create_volume(self): """Create a volume via the LVM backend.""" test_vol_name = "zaza{}".format(uuid.uuid1().fields[0]) vol_new = self.cinder_client.volumes.create( @@ -105,29 +109,28 @@ class CinderLVMTest(test_utils.OpenStackBaseTest): def test_create_volume(self): """Test creating a volume with basic configuration.""" - test_vol = self._tst_create_volume() + test_vol = self._create_volume() self.assertTrue(test_vol) host = getattr(test_vol, 'os-vol-host-attr:host').split('#')[0] self.assertTrue(host.startswith('cinder@LVM')) - @with_conf('overwrite', 'true') - @with_conf('block-device', '/dev/vdc') + @with_conf('cinder-lvm', {'overwrite': 'true', 'block-device': '/dev/vdc'}) def test_volume_overwrite(self): """Test creating a volume by overwriting one on the /dev/vdc device.""" - self._tst_create_volume() + self._create_volume() - @with_conf('block-device', 'none') + @with_conf('cinder-lvm', {'block-device': 'none'}) def test_device_none(self): """Test creating a volume in a dummy device (set as 'none').""" - self._tst_create_volume() + self._create_volume() - @with_conf('remove-missing', 'true') + @with_conf('cinder-lvm', {'remove-missing': 'true'}) def test_remove_missing_volume(self): - """Test creating a volume after removing missing ones in a group.""" - self._tst_create_volume() + """Test creating a volume after remove missing ones in a group.""" + self._create_volume() - @with_conf('remove-missing-force', 'true') + @with_conf('cinder-lvm', {'remove-missing-force': 'true'}) def test_remove_missing_force(self): - """Test volume creation by forcefully removing missing ones.""" - self._tst_create_volume() + """Test creating a volume by forcefully removing missing ones.""" + self._create_volume() From 5baf16237f1a6fc6805324237f59c002ebc03e19 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Tue, 31 Aug 2021 13:04:25 +0000 Subject: [PATCH 32/62] Add certificate check for Ceph dashboard Add setup step for the dashboard which will block until the certificates are present and the model is idle. This is to prevent the tests from continuing when the certificates are not ready. Also up CephDashboardTest to use standard tools for collecting ca cert. --- .../charm_tests/ceph/dashboard/setup.py | 34 +++++++++++++++++++ .../charm_tests/ceph/dashboard/tests.py | 20 ++--------- 2 files changed, 37 insertions(+), 17 deletions(-) create mode 100644 zaza/openstack/charm_tests/ceph/dashboard/setup.py diff --git a/zaza/openstack/charm_tests/ceph/dashboard/setup.py b/zaza/openstack/charm_tests/ceph/dashboard/setup.py new file mode 100644 index 0000000..7be7e7d --- /dev/null +++ b/zaza/openstack/charm_tests/ceph/dashboard/setup.py @@ -0,0 +1,34 @@ +# Copyright 2021 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 Ceph Dashboard.""" + +import logging + +import zaza.model +import zaza.openstack.utilities.openstack + + +def check_dashboard_cert(model_name=None): + """Wait for Dashboard to be ready. + + :param model_name: Name of model to query. + :type model_name: str + """ + logging.info("Check dashbaord Waiting for cacert") + zaza.openstack.utilities.openstack.block_until_ca_exists( + 'ceph-dashboard', + 'CERTIFICATE', + model_name=model_name) + zaza.model.block_until_all_units_idle(model_name=model_name) diff --git a/zaza/openstack/charm_tests/ceph/dashboard/tests.py b/zaza/openstack/charm_tests/ceph/dashboard/tests.py index e7c8863..1727699 100644 --- a/zaza/openstack/charm_tests/ceph/dashboard/tests.py +++ b/zaza/openstack/charm_tests/ceph/dashboard/tests.py @@ -15,12 +15,11 @@ """Encapsulating `ceph-dashboard` testing.""" import collections -import os import requests import zaza import zaza.openstack.charm_tests.test_utils as test_utils -import zaza.utilities.deployment_env as deployment_env +import zaza.openstack.utilities.openstack as openstack_utils class CephDashboardTest(test_utils.BaseCharmTest): @@ -34,21 +33,8 @@ class CephDashboardTest(test_utils.BaseCharmTest): """Run class setup for running ceph dashboard tests.""" super().setUpClass() cls.application_name = 'ceph-dashboard' - cls.local_ca_cert = cls.collect_ca() - - @classmethod - def collect_ca(cls): - """Collect CA from ceph-dashboard unit.""" - local_ca_cert = os.path.join( - deployment_env.get_tmpdir(), - os.path.basename(cls.REMOTE_CERT_FILE)) - if not os.path.isfile(local_ca_cert): - units = zaza.model.get_units(cls.application_name) - zaza.model.scp_from_unit( - units[0].entity_id, - cls.REMOTE_CERT_FILE, - local_ca_cert) - return local_ca_cert + cls.local_ca_cert = openstack_utils.get_remote_ca_cert_file( + cls.application_name) def test_dashboard_units(self): """Check dashboard units are configured correctly.""" From e6f9a3fce0009a0bfd4e719280499f2977273126 Mon Sep 17 00:00:00 2001 From: Gustavo Sanchez Date: Fri, 3 Sep 2021 12:31:11 -0400 Subject: [PATCH 33/62] Removes method invoked only once --- zaza/openstack/charm_tests/manila/tests.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/zaza/openstack/charm_tests/manila/tests.py b/zaza/openstack/charm_tests/manila/tests.py index 6cfa80f..786efa9 100644 --- a/zaza/openstack/charm_tests/manila/tests.py +++ b/zaza/openstack/charm_tests/manila/tests.py @@ -86,13 +86,6 @@ class ManilaTests(test_utils.OpenStackBaseTest): def _list_shares(self): return self.manila_client.shares.list() - @tenacity.retry( - retry=tenacity.retry_if_result(lambda ret: ret is not None), - wait=tenacity.wait_fixed(120), - stop=tenacity.stop_after_attempt(2)) - def _retry_check_commands_on_units(self, cmds, units): - return generic_utils.check_commands_on_units(cmds, units) - def test_902_nrpe_service_checks(self): """Confirm that the NRPE service check files are created.""" units = zaza.model.get_units('manila') @@ -105,11 +98,16 @@ class ManilaTests(test_utils.OpenStackBaseTest): 'check_{}.cfg'.format(check_name) ) - ret = self._retry_check_commands_on_units(cmds, units) - if ret: - logging.info(ret) - - self.assertIsNone(ret, msg=ret) + for attempt in tenacity.Retrying( + retry=tenacity.retry_if_result(lambda ret: ret is not None), + wait=tenacity.wait_fixed(120), + stop=tenacity.stop_after_attempt(2) + ): + with attempt: + ret = generic_utils.check_commands_on_units(cmds, units) + if ret: + logging.info(ret) + self.assertIsNone(ret, msg=ret) class ManilaBaseTest(test_utils.OpenStackBaseTest): From a62260f9bddbfc14c3098ec875b6ef8f2fa6ce58 Mon Sep 17 00:00:00 2001 From: Chris MacNaughton Date: Mon, 6 Sep 2021 13:21:19 +0200 Subject: [PATCH 34/62] Attempt to restart any ganesha application shares. Hard-coding the manila-ganesha application names makes these tests fail in bundles that are using the manila- ganesha application with it's default name. This change updates the tests to try to restart the desired services on any applications named *ganesha*. --- .../charm_tests/manila_ganesha/tests.py | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/zaza/openstack/charm_tests/manila_ganesha/tests.py b/zaza/openstack/charm_tests/manila_ganesha/tests.py index 2e9a186..0e99871 100644 --- a/zaza/openstack/charm_tests/manila_ganesha/tests.py +++ b/zaza/openstack/charm_tests/manila_ganesha/tests.py @@ -40,15 +40,19 @@ class ManilaGaneshaTests(manila_tests.ManilaBaseTest): def _restart_share_instance(self): logging.info('Restarting manila-share and nfs-ganesha') # It would be better for thie to derive the application name, - # manila-ganesha-az1, from deployed instances fo the manila-ganesha + # manila-ganesha-az1, from deployed instances of the manila-ganesha # charm; however, that functionality isn't present yet in zaza, so - # this is hard coded to the application name used in that charm's - # test bundles. - for unit in zaza.model.get_units('manila-ganesha-az1'): - # While we really only need to run this on the machine hosting - # nfs-ganesha and manila-share, running it everywhere isn't - # harmful. Pacemaker handles restarting the services - zaza.model.run_on_unit( - unit.entity_id, - "systemctl stop manila-share nfs-ganesha") + # this is a best-guestimate arrived at by looking for applications + # with the word 'ganesha' in their names. + ganeshas = [ + app for app in zaza.model.sync_deployed() + if 'ganesha' in app and 'mysql' not in app] + for ganesha in ganeshas: + for unit in zaza.model.get_units(ganesha): + # While we really only need to run this on the machine hosting + # nfs-ganesha and manila-share, running it everywhere isn't + # harmful. Pacemaker handles restarting the services + zaza.model.run_on_unit( + unit.entity_id, + "systemctl stop manila-share nfs-ganesha") return True From 5993f3f2c586c67a261c510324254dcc5879b8c4 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Wed, 11 Aug 2021 13:00:56 +0000 Subject: [PATCH 35/62] Dashboard test updates Verify CA now that bug is fix released *1. Check logging in to the dashbaord works Use a random username for tests so a test reruns work. *1 https://bugs.launchpad.net/cloud-archive/+bug/1933410 --- .../charm_tests/ceph/dashboard/tests.py | 61 +++++++++++++++++-- 1 file changed, 55 insertions(+), 6 deletions(-) diff --git a/zaza/openstack/charm_tests/ceph/dashboard/tests.py b/zaza/openstack/charm_tests/ceph/dashboard/tests.py index 1727699..1b5e46d 100644 --- a/zaza/openstack/charm_tests/ceph/dashboard/tests.py +++ b/zaza/openstack/charm_tests/ceph/dashboard/tests.py @@ -15,7 +15,9 @@ """Encapsulating `ceph-dashboard` testing.""" import collections +import json import requests +import uuid import zaza import zaza.openstack.charm_tests.test_utils as test_utils @@ -36,13 +38,27 @@ class CephDashboardTest(test_utils.BaseCharmTest): cls.local_ca_cert = openstack_utils.get_remote_ca_cert_file( cls.application_name) + def get_master_dashboard_url(self): + """Get the url of the dashboard servicing requests. + + Only one unit serves requests at any one time, the other units + redirect to that unit. + + :returns: URL of dashboard on unit + :rtype: Union[str, None] + """ + units = zaza.model.get_units(self.application_name) + for unit in units: + r = requests.get( + 'https://{}:8443'.format(unit.public_address), + verify=self.local_ca_cert, + allow_redirects=False) + if r.status_code == requests.codes.ok: + return 'https://{}:8443'.format(unit.public_address) + def test_dashboard_units(self): """Check dashboard units are configured correctly.""" - # XXX: Switch to using CA for verification when - # https://bugs.launchpad.net/cloud-archive/+bug/1933410 - # is fix released. - # verify = self.local_ca_cert - verify = False + verify = self.local_ca_cert units = zaza.model.get_units(self.application_name) rcs = collections.defaultdict(list) for unit in units: @@ -72,12 +88,45 @@ class CephDashboardTest(test_utils.BaseCharmTest): 'role': role}) return action + def get_random_username(self): + """Generate a username to use in tests. + + :returns: Username + :rtype: str + """ + return "zazauser-{}".format(uuid.uuid1()) + def test_create_user(self): """Test create user action.""" - test_user = 'marvin' + test_user = self.get_random_username() action = self.create_user(test_user) self.assertEqual(action.status, "completed") self.assertTrue(action.data['results']['password']) action = self.create_user(test_user) # Action should fail as the user already exists self.assertEqual(action.status, "failed") + + def access_dashboard(self, dashboard_url): + """Test logging via a dashboard url. + + :param dashboard_url: Base url to use to login to + :type dashboard_url: str + """ + user = self.get_random_username() + action = self.create_user(username=user) + self.assertEqual(action.status, "completed") + password = action.data['results']['password'] + path = "api/auth" + headers = {'Content-type': 'application/json'} + payload = {"username": user, "password": password} + verify = self.local_ca_cert + r = requests.post( + "{}/{}".format(dashboard_url, path), + data=json.dumps(payload), + headers=headers, + verify=verify) + self.assertEqual(r.status_code, requests.codes.created) + + def test_access_dashboard(self): + """Test logging in to the dashboard.""" + self.access_dashboard(self.get_master_dashboard_url()) From bee69edb7687595ccc396f5643fecb4389735c59 Mon Sep 17 00:00:00 2001 From: Gustavo Sanchez Date: Mon, 6 Sep 2021 09:14:37 -0400 Subject: [PATCH 36/62] Reduce wait time and corrections --- zaza/openstack/charm_tests/manila/tests.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/zaza/openstack/charm_tests/manila/tests.py b/zaza/openstack/charm_tests/manila/tests.py index 786efa9..455feef 100644 --- a/zaza/openstack/charm_tests/manila/tests.py +++ b/zaza/openstack/charm_tests/manila/tests.py @@ -99,14 +99,12 @@ class ManilaTests(test_utils.OpenStackBaseTest): ) for attempt in tenacity.Retrying( - retry=tenacity.retry_if_result(lambda ret: ret is not None), - wait=tenacity.wait_fixed(120), - stop=tenacity.stop_after_attempt(2) + wait=tenacity.wait_fixed(20), + stop=tenacity.stop_after_attempt(2), + reraise=True ): with attempt: ret = generic_utils.check_commands_on_units(cmds, units) - if ret: - logging.info(ret) self.assertIsNone(ret, msg=ret) From 879d35d6fb01576f966bb030ba300149a41f3d22 Mon Sep 17 00:00:00 2001 From: Luciano Lo Giudice Date: Mon, 6 Sep 2021 19:16:17 -0300 Subject: [PATCH 37/62] Cleanup the test file Instead of introducing a new decorator to temporarily change the configuration, use the existing infrastructure. Furthermore, avoid passing the model name in some calls since it's implied already. --- .../openstack/charm_tests/cinder_lvm/tests.py | 50 +++++++------------ 1 file changed, 17 insertions(+), 33 deletions(-) diff --git a/zaza/openstack/charm_tests/cinder_lvm/tests.py b/zaza/openstack/charm_tests/cinder_lvm/tests.py index 6ed1d60..45fd126 100644 --- a/zaza/openstack/charm_tests/cinder_lvm/tests.py +++ b/zaza/openstack/charm_tests/cinder_lvm/tests.py @@ -24,28 +24,6 @@ import zaza.openstack.charm_tests.test_utils as test_utils import zaza.openstack.utilities.openstack as openstack_utils -def with_conf(application, config, model_name=None): - """Temporarily change the config options for an application in a model.""" - prev = {} - for key in config.keys(): - prev[key] = str(openstack_utils.get_application_config_option( - application, key, model_name=model_name)) - - def patched(f): - def inner(*args, **kwargs): - try: - zaza.model.set_application_config( - application, config, model_name=model_name) - zaza.model.wait_for_agent_status(model_name=model_name) - return f(*args, **kwargs) - finally: - zaza.model.set_application_config( - application, prev, model_name=model_name) - zaza.model.wait_for_agent_status(model_name=model_name) - return inner - return patched - - class CinderLVMTest(test_utils.OpenStackBaseTest): """Encapsulate cinder-lvm tests.""" @@ -56,6 +34,8 @@ class CinderLVMTest(test_utils.OpenStackBaseTest): cls.model_name = zaza.model.get_juju_model() cls.cinder_client = openstack_utils.get_cinder_session_client( cls.keystone_session) + cls.block_device = openstack_utils.get_application_config_option( + 'cinder-lvm', 'block-device', model_name=cls.model_name) @classmethod def tearDown(cls): @@ -83,13 +63,11 @@ class CinderLVMTest(test_utils.OpenStackBaseTest): zaza.model.run_on_leader( 'cinder', - 'sudo cp /etc/cinder/cinder.conf /tmp/', - model_name=self.model_name) + 'sudo cp /etc/cinder/cinder.conf /tmp/') zaza.model.block_until_oslo_config_entries_match( 'cinder', '/tmp/cinder.conf', expected_contents, - model_name=self.model_name, timeout=10) def _create_volume(self): @@ -115,22 +93,28 @@ class CinderLVMTest(test_utils.OpenStackBaseTest): host = getattr(test_vol, 'os-vol-host-attr:host').split('#')[0] self.assertTrue(host.startswith('cinder@LVM')) - @with_conf('cinder-lvm', {'overwrite': 'true', 'block-device': '/dev/vdc'}) def test_volume_overwrite(self): """Test creating a volume by overwriting one on the /dev/vdc device.""" - self._create_volume() + with self.config_change({'overwrite': 'false', + 'block-device': self.block_device}, + {'overwrite': 'true', + 'block-device': '/dev/vdc'}): + self._create_volume() - @with_conf('cinder-lvm', {'block-device': 'none'}) def test_device_none(self): """Test creating a volume in a dummy device (set as 'none').""" - self._create_volume() + with self.config_change({'block-device': self.block_device}, + {'block-device': 'none'}): + self._create_volume() - @with_conf('cinder-lvm', {'remove-missing': 'true'}) def test_remove_missing_volume(self): """Test creating a volume after remove missing ones in a group.""" - self._create_volume() + with self.config_change({'remove-missing': 'false'}, + {'remove-missing': 'true'}): + self._create_volume() - @with_conf('cinder-lvm', {'remove-missing-force': 'true'}) def test_remove_missing_force(self): """Test creating a volume by forcefully removing missing ones.""" - self._create_volume() + with self.config_change({'remove-missing-force': 'false'}, + {'remove-missing-force': 'true'}): + self._create_volume() From 36d85aece547e037bdd4eca36af7519187fc6406 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Wed, 8 Sep 2021 13:34:30 +0000 Subject: [PATCH 38/62] Pacific requires client to set accepted api version --- zaza/openstack/charm_tests/ceph/dashboard/tests.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/zaza/openstack/charm_tests/ceph/dashboard/tests.py b/zaza/openstack/charm_tests/ceph/dashboard/tests.py index 1b5e46d..9c6eae7 100644 --- a/zaza/openstack/charm_tests/ceph/dashboard/tests.py +++ b/zaza/openstack/charm_tests/ceph/dashboard/tests.py @@ -117,7 +117,9 @@ class CephDashboardTest(test_utils.BaseCharmTest): self.assertEqual(action.status, "completed") password = action.data['results']['password'] path = "api/auth" - headers = {'Content-type': 'application/json'} + headers = { + 'Content-type': 'application/json', + 'Accept': 'application/vnd.ceph.api.v1.0'} payload = {"username": user, "password": password} verify = self.local_ca_cert r = requests.post( From 23f95655b932713bfcfb74a3d9e1b852cfaf23b4 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Wed, 8 Sep 2021 17:44:11 +0000 Subject: [PATCH 39/62] Add setup method for setting grafana api for ceph dashboard --- .../charm_tests/ceph/dashboard/setup.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/zaza/openstack/charm_tests/ceph/dashboard/setup.py b/zaza/openstack/charm_tests/ceph/dashboard/setup.py index 7be7e7d..7a2488f 100644 --- a/zaza/openstack/charm_tests/ceph/dashboard/setup.py +++ b/zaza/openstack/charm_tests/ceph/dashboard/setup.py @@ -32,3 +32,20 @@ def check_dashboard_cert(model_name=None): 'CERTIFICATE', model_name=model_name) zaza.model.block_until_all_units_idle(model_name=model_name) + + +def set_grafana_url(model_name=None): + """Set the url for the grafana api. + + :param model_name: Name of model to query. + :type model_name: str + """ + try: + unit = zaza.model.get_units('grafana')[0] + except KeyError: + return + zaza.model.set_application_config( + 'ceph-dashboard', + { + 'grafana-api-url': "https://{}:3000".format( + unit.public_address)}) From f8a88a98a6845f43367c22fd2e01b8dfe0922bf0 Mon Sep 17 00:00:00 2001 From: Aurelien Lourot Date: Thu, 9 Sep 2021 10:27:20 +0200 Subject: [PATCH 40/62] Set timeout on juju_wait() (#630) * Set timeout on juju_wait() * Remove leftover traces Traces should be made with logging.debug(). These traces are probably leftovers from the development phase. --- zaza/openstack/charm_tests/openstack_upgrade/tests.py | 4 ---- zaza/openstack/utilities/openstack.py | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/zaza/openstack/charm_tests/openstack_upgrade/tests.py b/zaza/openstack/charm_tests/openstack_upgrade/tests.py index c34acae..620bda0 100644 --- a/zaza/openstack/charm_tests/openstack_upgrade/tests.py +++ b/zaza/openstack/charm_tests/openstack_upgrade/tests.py @@ -49,7 +49,6 @@ class OpenStackUpgradeVMLaunchBase(unittest.TestCase): @classmethod def setUpClass(cls): """Run setup for OpenStack Upgrades.""" - print("Running OpenStackUpgradeMixin setUpClass") super().setUpClass() cls.lts = LTSGuestCreateTest() cls.lts.setUpClass() @@ -76,7 +75,6 @@ class WaitForMySQL(unittest.TestCase): @classmethod def setUpClass(cls): """Set up class.""" - print("Running OpenstackUpgradeTests setUpClass") super().setUpClass() cli_utils.setup_logging() @@ -93,7 +91,6 @@ class OpenStackUpgradeTestsFocalUssuri(OpenStackUpgradeVMLaunchBase): @classmethod def setUpClass(cls): """Run setup for OpenStack Upgrades.""" - print("Running OpenstackUpgradeTests setUpClass") super().setUpClass() cli_utils.setup_logging() @@ -120,7 +117,6 @@ class OpenStackUpgradeTestsByOption(OpenStackUpgradeVMLaunchBase): @classmethod def setUpClass(cls): """Run setup for OpenStack Upgrades.""" - print("Running OpenstackUpgradeTests setUpClass") super().setUpClass() cli_utils.setup_logging() diff --git a/zaza/openstack/utilities/openstack.py b/zaza/openstack/utilities/openstack.py index a4fcbc3..36ff964 100644 --- a/zaza/openstack/utilities/openstack.py +++ b/zaza/openstack/utilities/openstack.py @@ -1000,7 +1000,7 @@ def configure_networking_charms(networking_data, macs, use_juju_wait=True): # to deal with all the non ['active', 'idle', 'Unit is ready.'] # workload/agent states and msgs that our mojo specs are exposed to. if use_juju_wait: - juju_wait.wait(wait_for_workload=True) + juju_wait.wait(wait_for_workload=True, max_wait=2700) else: zaza.model.wait_for_agent_status() # TODO: shouldn't access get_charm_config() here as it relies on From 728c9fc294693e532423a8fdb41ec6d0d989e561 Mon Sep 17 00:00:00 2001 From: Martin Kalcok Date: Fri, 10 Sep 2021 16:30:24 +0200 Subject: [PATCH 41/62] Rename actions from "get-status-*" to "show-*" --- zaza/openstack/charm_tests/neutron/tests.py | 30 ++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/zaza/openstack/charm_tests/neutron/tests.py b/zaza/openstack/charm_tests/neutron/tests.py index 99088a4..96b45de 100644 --- a/zaza/openstack/charm_tests/neutron/tests.py +++ b/zaza/openstack/charm_tests/neutron/tests.py @@ -249,13 +249,13 @@ class NeutronGatewayTest(NeutronPluginApiSharedTests): return services -class NeutronGatewayStatusActionsTest(test_utils.OpenStackBaseTest): - """Test status actions of Neutron Gateway Charm. +class NeutronGatewayShowActionsTest(test_utils.OpenStackBaseTest): + """Test "show" actions of Neutron Gateway Charm. actions: - * get-status-routers - * get-status-dhcp - * get-status-lb + * show-routers + * show-dhcp-networks + * show-loadbalancers """ SKIP_LBAAS_TESTS = True @@ -263,7 +263,7 @@ class NeutronGatewayStatusActionsTest(test_utils.OpenStackBaseTest): @classmethod def setUpClass(cls, application_name='neutron-gateway', model_alias=None): """Run class setup for running Neutron Gateway tests.""" - super(NeutronGatewayStatusActionsTest, cls).setUpClass( + super(NeutronGatewayShowActionsTest, cls).setUpClass( application_name, model_alias) # set up clients cls.neutron_client = ( @@ -294,8 +294,8 @@ class NeutronGatewayStatusActionsTest(test_utils.OpenStackBaseTest): # assert that juju action returned expected resources self.assertEqual(result_resource_ids, expected_resource_ids) - def test_get_status_routers(self): - """Test that get-status-routers reports correct neutron routers.""" + def test_show_routers(self): + """Test that show-routers action reports correct neutron routers.""" # fetch neutron routers using neutron client ngw_unit = zaza.model.get_units(self.application_name, model_name=self.model_name)[0] @@ -308,14 +308,14 @@ class NeutronGatewayStatusActionsTest(test_utils.OpenStackBaseTest): # fetch neutron routers using juju-action result = zaza.model.run_action(ngw_unit.entity_id, - 'get-status-routers', + 'show-routers', model_name=self.model_name) # assert that data from neutron client match data from juju action self._assert_result_match(result, routers_from_client, 'router-list') - def test_get_status_dhcp(self): - """Test that get-status-dhcp reports correct DHCP networks.""" + def test_show_dhcp_networks(self): + """Test that show-dhcp-networks reports correct DHCP networks.""" # fetch DHCP networks using neutron client ngw_unit = zaza.model.get_units(self.application_name, model_name=self.model_name)[0] @@ -328,15 +328,15 @@ class NeutronGatewayStatusActionsTest(test_utils.OpenStackBaseTest): # fetch DHCP networks using juju-action result = zaza.model.run_action(ngw_unit.entity_id, - 'get-status-dhcp', + 'show-dhcp-networks', model_name=self.model_name) # assert that data from neutron client match data from juju action self._assert_result_match(result, networks_from_client, 'dhcp-networks') - def test_get_status_load_balancers(self): - """Test that get-status-lb reports correct loadbalancers.""" + def test_show_load_balancers(self): + """Test that show-loadbalancers reports correct loadbalancers.""" if self.SKIP_LBAAS_TESTS: self.skipTest('LBaasV2 is not supported in this version.') @@ -368,7 +368,7 @@ class NeutronGatewayStatusActionsTest(test_utils.OpenStackBaseTest): 'loadbalancers', []) result = zaza.model.run_action(ngw_unit.entity_id, - 'get-status-lb', + 'show-load-balancers', model_name=self.model_name) self._assert_result_match(result, lbaas_from_client, From 86b440a46ba253019e84b1499545bc86a1c8eb9a Mon Sep 17 00:00:00 2001 From: Luciano Lo Giudice Date: Mon, 13 Sep 2021 13:31:36 -0300 Subject: [PATCH 42/62] Add the cinder-netapp tests --- .../charm_tests/cinder_netapp/__init__.py | 15 ++++ .../charm_tests/cinder_netapp/tests.py | 77 +++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 zaza/openstack/charm_tests/cinder_netapp/__init__.py create mode 100644 zaza/openstack/charm_tests/cinder_netapp/tests.py diff --git a/zaza/openstack/charm_tests/cinder_netapp/__init__.py b/zaza/openstack/charm_tests/cinder_netapp/__init__.py new file mode 100644 index 0000000..31c5165 --- /dev/null +++ b/zaza/openstack/charm_tests/cinder_netapp/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2021 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 cinder-netapp.""" diff --git a/zaza/openstack/charm_tests/cinder_netapp/tests.py b/zaza/openstack/charm_tests/cinder_netapp/tests.py new file mode 100644 index 0000000..ef6cff9 --- /dev/null +++ b/zaza/openstack/charm_tests/cinder_netapp/tests.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 + +# Copyright 2021 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 cinder-netapp testing.""" + +import logging +import uuid + +import zaza.model +import zaza.openstack.charm_tests.test_utils as test_utils +import zaza.openstack.utilities.openstack as openstack_utils + + +class CindernetappTest(test_utils.OpenStackBaseTest): + """Encapsulate netapp tests.""" + + @classmethod + def setUpClass(cls): + """Run class setup for running tests.""" + super(CindernetappTest, cls).setUpClass() + cls.keystone_session = openstack_utils.get_overcloud_keystone_session() + cls.model_name = zaza.model.get_juju_model() + cls.cinder_client = openstack_utils.get_cinder_session_client( + cls.keystone_session) + + def test_cinder_config(self): + """Test that configuration options match our expectations.""" + logging.info('cinder-netapp') + expected_contents = { + 'cinder-netapp': { + 'netapp_storage_family': ['ontap_cluster'], + 'netapp_storage_protocol': ['iscsi'], + 'volume_backend_name': ['cinder_netapp'], + 'volume_driver': + ['cinder.volume.drivers.netapp.common.NetAppDriver'], + }} + + zaza.model.run_on_leader( + 'cinder', + 'sudo cp /etc/cinder/cinder.conf /tmp/') + zaza.model.block_until_oslo_config_entries_match( + 'cinder', + '/tmp/cinder.conf', + expected_contents, + timeout=2) + + def test_create_volume(self): + """Test creating a volume with basic configuration.""" + test_vol_name = "zaza{}".format(uuid.uuid1().fields[0]) + vol_new = self.cinder_client.volumes.create( + name=test_vol_name, + size='1') + openstack_utils.resource_reaches_status( + self.cinder_client.volumes, + vol_new.id, + wait_iteration_max_time=12000, + stop_after_attempt=5, + expected_status='available', + msg='Volume status wait') + test_vol = self.cinder_client.volumes.find(name=test_vol_name) + self.assertEqual( + getattr(test_vol, 'os-vol-host-attr:host').split('#')[0], + 'cinder@cinder-netapp') + self.cinder_client.volumes.delete(vol_new) From a94cbb1d3a13949c1dedaf5e75823fc0151e99f8 Mon Sep 17 00:00:00 2001 From: Luciano Lo Giudice Date: Mon, 13 Sep 2021 14:00:36 -0300 Subject: [PATCH 43/62] Rename the test class to be more uniform --- zaza/openstack/charm_tests/cinder_netapp/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zaza/openstack/charm_tests/cinder_netapp/tests.py b/zaza/openstack/charm_tests/cinder_netapp/tests.py index ef6cff9..5ef9ea3 100644 --- a/zaza/openstack/charm_tests/cinder_netapp/tests.py +++ b/zaza/openstack/charm_tests/cinder_netapp/tests.py @@ -24,7 +24,7 @@ import zaza.openstack.charm_tests.test_utils as test_utils import zaza.openstack.utilities.openstack as openstack_utils -class CindernetappTest(test_utils.OpenStackBaseTest): +class CinderNetAppTest(test_utils.OpenStackBaseTest): """Encapsulate netapp tests.""" @classmethod From 89d47dc016bdb8134fceabf2d3ab7f277eae35df Mon Sep 17 00:00:00 2001 From: Luciano Lo Giudice Date: Mon, 13 Sep 2021 14:07:15 -0300 Subject: [PATCH 44/62] Fix name in call to super --- zaza/openstack/charm_tests/cinder_netapp/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zaza/openstack/charm_tests/cinder_netapp/tests.py b/zaza/openstack/charm_tests/cinder_netapp/tests.py index 5ef9ea3..b5ce8fb 100644 --- a/zaza/openstack/charm_tests/cinder_netapp/tests.py +++ b/zaza/openstack/charm_tests/cinder_netapp/tests.py @@ -30,7 +30,7 @@ class CinderNetAppTest(test_utils.OpenStackBaseTest): @classmethod def setUpClass(cls): """Run class setup for running tests.""" - super(CindernetappTest, cls).setUpClass() + super(CinderNetAppTest, cls).setUpClass() cls.keystone_session = openstack_utils.get_overcloud_keystone_session() cls.model_name = zaza.model.get_juju_model() cls.cinder_client = openstack_utils.get_cinder_session_client( From b7e4bbf0fce951365e1211b1f95062b8157b4ed6 Mon Sep 17 00:00:00 2001 From: Aurelien Lourot Date: Wed, 15 Sep 2021 14:29:17 +0200 Subject: [PATCH 45/62] Allow several tempest runs with different config This deprecates: configure: - zaza.openstack.charm_tests.tempest.setup.render_tempest_config_keystone_v3 tests: - zaza.openstack.charm_tests.tempest.tests.TempestTest by allowing multiple runs with different config: tests: - zaza.openstack.charm_tests.tempest.tests.TempestTestWithKeystoneV2 - zaza.openstack.charm_tests.openstack_upgrade.tests.OpenStackUpgradeTestsByOption - zaza.openstack.charm_tests.tempest.tests.TempestTestWithKeystoneV3 --- unit_tests/charm_tests/test_tempest.py | 19 +- zaza/openstack/charm_tests/tempest/setup.py | 320 +------------------ zaza/openstack/charm_tests/tempest/tests.py | 56 +++- zaza/openstack/charm_tests/tempest/utils.py | 325 +++++++++++++++++++- 4 files changed, 397 insertions(+), 323 deletions(-) diff --git a/unit_tests/charm_tests/test_tempest.py b/unit_tests/charm_tests/test_tempest.py index 3c4c161..ac6962e 100644 --- a/unit_tests/charm_tests/test_tempest.py +++ b/unit_tests/charm_tests/test_tempest.py @@ -15,25 +15,22 @@ import mock import unittest -import zaza.openstack.charm_tests.tempest.setup as tempest_setup +import zaza.openstack.charm_tests.tempest.utils as tempest_utils -class TestTempestSetup(unittest.TestCase): - """Test class to encapsulate testing Mysql test utils.""" - - def setUp(self): - super(TestTempestSetup, self).setUp() +class TestTempestUtils(unittest.TestCase): + """Test class to encapsulate testing Tempest test utils.""" def test_add_environment_var_config_with_missing_variable(self): ctxt = {} with self.assertRaises(Exception) as context: - tempest_setup.add_environment_var_config(ctxt, ['swift']) + tempest_utils._add_environment_var_config(ctxt, ['swift']) self.assertEqual( ('Environment variables [TEST_SWIFT_IP] must all be ' 'set to run this test'), str(context.exception)) - @mock.patch.object(tempest_setup.deployment_env, 'get_deployment_context') + @mock.patch.object(tempest_utils.deployment_env, 'get_deployment_context') def test_add_environment_var_config_with_all_variables( self, get_deployment_context): @@ -45,10 +42,10 @@ class TestTempestSetup(unittest.TestCase): 'TEST_NAME_SERVER': 'test', 'TEST_CIDR_PRIV': 'test', } - tempest_setup.add_environment_var_config(ctxt, ['neutron']) + tempest_utils._add_environment_var_config(ctxt, ['neutron']) self.assertEqual(ctxt['test_gateway'], 'test') - @mock.patch.object(tempest_setup.deployment_env, 'get_deployment_context') + @mock.patch.object(tempest_utils.deployment_env, 'get_deployment_context') def test_add_environment_var_config_with_some_variables( self, get_deployment_context): @@ -59,7 +56,7 @@ class TestTempestSetup(unittest.TestCase): 'TEST_CIDR_PRIV': 'test', } with self.assertRaises(Exception) as context: - tempest_setup.add_environment_var_config(ctxt, ['neutron']) + tempest_utils._add_environment_var_config(ctxt, ['neutron']) self.assertEqual( ('Environment variables [TEST_CIDR_EXT, TEST_FIP_RANGE] must ' 'all be set to run this test'), diff --git a/zaza/openstack/charm_tests/tempest/setup.py b/zaza/openstack/charm_tests/tempest/setup.py index 7ae0ba3..627290c 100644 --- a/zaza/openstack/charm_tests/tempest/setup.py +++ b/zaza/openstack/charm_tests/tempest/setup.py @@ -14,315 +14,9 @@ """Code for configuring and initializing tempest.""" -import jinja2 -import urllib.parse -import os -import subprocess +import logging -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.tempest.utils as tempest_utils -import zaza.openstack.charm_tests.glance.setup as glance_setup - -SETUP_ENV_VARS = { - 'neutron': ['TEST_GATEWAY', 'TEST_CIDR_EXT', 'TEST_FIP_RANGE', - 'TEST_NAME_SERVER', 'TEST_CIDR_PRIV'], - 'swift': ['TEST_SWIFT_IP'], -} - -IGNORABLE_VARS = ['TEST_CIDR_PRIV'] - -TEMPEST_FLAVOR_NAME = 'm1.tempest' -TEMPEST_ALT_FLAVOR_NAME = 'm2.tempest' -TEMPEST_SVC_LIST = ['ceilometer', 'cinder', 'glance', 'heat', 'horizon', - 'ironic', 'neutron', 'nova', 'octavia', 'sahara', 'swift', - 'trove', 'zaqar'] - - -def add_application_ips(ctxt): - """Add application access IPs to context. - - :param ctxt: Context dictionary - :type ctxt: dict - :returns: None - :rtype: None - """ - 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): - """Add nova config to context. - - :param ctxt: Context dictionary - :type ctxt: dict - :param 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(): - 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): - """Add neutron config to context. - - :param ctxt: Context dictionary - :type ctxt: dict - :param keystone_session: keystoneauth1.session.Session object - :type: keystoneauth1.session.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) - 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: - # 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): - """Add glance config to context. - - :param ctxt: Context dictionary - :type ctxt: dict - :param 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( - 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_cinder_config(ctxt, keystone_session): - """Add cinder config to context. - - :param ctxt: Context dictionary - :type ctxt: dict - :param 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) - 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): - """Add keystone config to context. - - :param ctxt: Context dictionary - :type ctxt: dict - :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) - domain = keystone_client.domains.find(name="admin_domain") - ctxt['default_domain_id'] = domain.id - - -def add_octavia_config(ctxt): - """Add octavia config to context. - - :param ctxt: Context dictionary - :type ctxt: dict - :returns: None - :rtype: None - :raises: subprocess.CalledProcessError - """ - subprocess.check_call([ - 'curl', - "{}:80/swift/v1/fixtures/test_server.bin".format( - ctxt['test_swift_ip']), - '-o', "{}/test_server.bin".format(ctxt['workspace_path']) - ]) - subprocess.check_call([ - 'chmod', '+x', - "{}/test_server.bin".format(ctxt['workspace_path']) - ]) - - -def get_service_list(keystone_session): - """Retrieve list of services from keystone. - - :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 - :type ctxt: dict - :returns: None - :rtype: None - """ - deploy_env = deployment_env.get_deployment_context() - missing_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: - if var not in IGNORABLE_VARS: - missing_vars.append(var) - if missing_vars: - raise ValueError( - ("Environment variables [{}] must all be set to run this" - " test").format(', '.join(missing_vars))) - - -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'] - ctxt['admin_password'] = overcloud_auth['OS_PASSWORD'] - 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']) - - -def get_tempest_context(workspace_path): - """Generate the tempest config context. - - :returns: Context dictionary - :rtype: dict - """ - keystone_session = openstack_utils.get_overcloud_keystone_session() - ctxt = {} - ctxt['workspace_path'] = workspace_path - ctxt_funcs = { - 'nova': add_nova_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) - if set(['cinderv2', 'cinderv3']) \ - .intersection(set(ctxt['enabled_services'])): - ctxt['enabled_services'].append('cinder') - ctxt['disabled_services'] = list( - set(TEMPEST_SVC_LIST) - set(ctxt['enabled_services'])) - add_application_ips(ctxt) - 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) - if 'octavia' in ctxt['enabled_services']: - add_octavia_config(ctxt) - return ctxt - - -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_name: Name of template file - :type template_name: str - :returns: None - :rtype: None - """ - 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.render(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 - """ - workspace_name, workspace_path = tempest_utils.get_workspace() - tempest_utils.destroy_workspace(workspace_name, workspace_path) - tempest_utils.init_workspace(workspace_path) - context = get_tempest_context(workspace_path) - render_tempest_config( - os.path.join(workspace_path, 'etc/tempest.conf'), - context, - tempest_template) - render_tempest_config( - os.path.join(workspace_path, 'etc/accounts.yaml'), - context, - accounts_template) def render_tempest_config_keystone_v2(): @@ -331,7 +25,11 @@ def render_tempest_config_keystone_v2(): :returns: None :rtype: None """ - setup_tempest('tempest_v2.j2', 'accounts.j2') + logging.warning( + 'The render_tempest_config_keystone_v2 config step is deprecated. ' + 'This is now directly done by the TempestTestWithKeystoneV2 test ' + 'class.') + tempest_utils.render_tempest_config_keystone_v2() def render_tempest_config_keystone_v3(): @@ -340,4 +38,8 @@ def render_tempest_config_keystone_v3(): :returns: None :rtype: None """ - setup_tempest('tempest_v3.j2', 'accounts.j2') + logging.warning( + 'The render_tempest_config_keystone_v3 config step is deprecated. ' + 'This is now directly done by the TempestTestWithKeystoneV3 test ' + 'class.') + tempest_utils.render_tempest_config_keystone_v3() diff --git a/zaza/openstack/charm_tests/tempest/tests.py b/zaza/openstack/charm_tests/tempest/tests.py index 5c83ec0..b7fc859 100644 --- a/zaza/openstack/charm_tests/tempest/tests.py +++ b/zaza/openstack/charm_tests/tempest/tests.py @@ -14,6 +14,7 @@ """Code for running tempest tests.""" +import logging import os import subprocess @@ -24,8 +25,8 @@ import zaza.openstack.charm_tests.tempest.utils as tempest_utils import tempfile -class TempestTest(): - """Tempest test class.""" +class TempestTestBase(): + """Tempest test base class.""" test_runner = zaza.charm_lifecycle.test.DIRECT @@ -89,3 +90,54 @@ class TempestTest(): if not keep_workspace or keep_workspace is not True: tempest_utils.destroy_workspace(workspace_name, workspace_path) return result + + +class TempestTestWithKeystoneV2(TempestTestBase): + """Tempest test class to validate an OpenStack setup with Keystone V2.""" + + def run(self): + """Run tempest tests as specified in tests/tests.yaml. + + See TempestTestBase.run() for the available test options. + + :returns: Status of tempest run + :rtype: bool + """ + tempest_utils.render_tempest_config_keystone_v2() + return super().run() + + +class TempestTestWithKeystoneV3(TempestTestBase): + """Tempest test class to validate an OpenStack setup with Keystone V2.""" + + def run(self): + """Run tempest tests as specified in tests/tests.yaml. + + See TempestTestBase.run() for the available test options. + + :returns: Status of tempest run + :rtype: bool + """ + tempest_utils.render_tempest_config_keystone_v3() + return super().run() + + +class TempestTest(TempestTestBase): + """Tempest test class. + + Requires running one of the render_tempest_config_keystone_v? Zaza + configuration steps before. + """ + + def run(self): + """Run tempest tests as specified in tests/tests.yaml. + + See TempestTestBase.run() for the available test options. + + :returns: Status of tempest run + :rtype: bool + """ + logging.warning( + 'The TempestTest test class is deprecated. Please use one of the ' + 'TempestTestWithKeystoneV? test classes instead.') + return super().run() diff --git a/zaza/openstack/charm_tests/tempest/utils.py b/zaza/openstack/charm_tests/tempest/utils.py index 52ae126..28ad65e 100644 --- a/zaza/openstack/charm_tests/tempest/utils.py +++ b/zaza/openstack/charm_tests/tempest/utils.py @@ -14,12 +14,50 @@ """Utility code for working with tempest workspaces.""" +import jinja2 import os from pathlib import Path import shutil import subprocess +import urllib.parse import zaza.model as 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 + +SETUP_ENV_VARS = { + 'neutron': ['TEST_GATEWAY', 'TEST_CIDR_EXT', 'TEST_FIP_RANGE', + 'TEST_NAME_SERVER', 'TEST_CIDR_PRIV'], + 'swift': ['TEST_SWIFT_IP'], +} + +IGNORABLE_VARS = ['TEST_CIDR_PRIV'] + +TEMPEST_FLAVOR_NAME = 'm1.tempest' +TEMPEST_ALT_FLAVOR_NAME = 'm2.tempest' +TEMPEST_SVC_LIST = ['ceilometer', 'cinder', 'glance', 'heat', 'horizon', + 'ironic', 'neutron', 'nova', 'octavia', 'sahara', 'swift', + 'trove', 'zaqar'] + + +def render_tempest_config_keystone_v2(): + """Render tempest config for Keystone V2 API. + + :returns: None + :rtype: None + """ + _setup_tempest('tempest_v2.j2', 'accounts.j2') + + +def render_tempest_config_keystone_v3(): + """Render tempest config for Keystone V3 API. + + :returns: None + :rtype: None + """ + _setup_tempest('tempest_v3.j2', 'accounts.j2') def get_workspace(): @@ -53,7 +91,7 @@ def destroy_workspace(workspace_name, workspace_path): shutil.rmtree(workspace_path) -def init_workspace(workspace_path): +def _init_workspace(workspace_path): """Initialize tempest workspace. :param workspace_path: directory path where workspace is stored @@ -65,3 +103,288 @@ def init_workspace(workspace_path): subprocess.check_call(['tempest', 'init', workspace_path]) except subprocess.CalledProcessError: pass + + +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 + """ + workspace_name, workspace_path = get_workspace() + destroy_workspace(workspace_name, workspace_path) + _init_workspace(workspace_path) + context = _get_tempest_context(workspace_path) + _render_tempest_config( + os.path.join(workspace_path, 'etc/tempest.conf'), + context, + tempest_template) + _render_tempest_config( + os.path.join(workspace_path, 'etc/accounts.yaml'), + context, + accounts_template) + + +def _get_tempest_context(workspace_path): + """Generate the tempest config context. + + :returns: Context dictionary + :rtype: dict + """ + keystone_session = openstack_utils.get_overcloud_keystone_session() + ctxt = {} + ctxt['workspace_path'] = workspace_path + ctxt_funcs = { + 'nova': _add_nova_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) + if set(['cinderv2', 'cinderv3']) \ + .intersection(set(ctxt['enabled_services'])): + ctxt['enabled_services'].append('cinder') + ctxt['disabled_services'] = list( + set(TEMPEST_SVC_LIST) - set(ctxt['enabled_services'])) + _add_application_ips(ctxt) + 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) + if 'octavia' in ctxt['enabled_services']: + _add_octavia_config(ctxt) + return ctxt + + +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_name: Name of template file + :type template_name: str + :returns: None + :rtype: None + """ + 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.render(ctxt)) + + +def _add_application_ips(ctxt): + """Add application access IPs to context. + + :param ctxt: Context dictionary + :type ctxt: dict + :returns: None + :rtype: None + """ + 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): + """Add nova config to context. + + :param ctxt: Context dictionary + :type ctxt: dict + :param 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(): + 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): + """Add neutron config to context. + + :param ctxt: Context dictionary + :type ctxt: dict + :param keystone_session: keystoneauth1.session.Session object + :type: keystoneauth1.session.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) + 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: + # 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): + """Add glance config to context. + + :param ctxt: Context dictionary + :type ctxt: dict + :param 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( + 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_cinder_config(ctxt, keystone_session): + """Add cinder config to context. + + :param ctxt: Context dictionary + :type ctxt: dict + :param 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) + 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): + """Add keystone config to context. + + :param ctxt: Context dictionary + :type ctxt: dict + :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) + domain = keystone_client.domains.find(name="admin_domain") + ctxt['default_domain_id'] = domain.id + + +def _add_octavia_config(ctxt): + """Add octavia config to context. + + :param ctxt: Context dictionary + :type ctxt: dict + :returns: None + :rtype: None + :raises: subprocess.CalledProcessError + """ + subprocess.check_call([ + 'curl', + "{}:80/swift/v1/fixtures/test_server.bin".format( + ctxt['test_swift_ip']), + '-o', "{}/test_server.bin".format(ctxt['workspace_path']) + ]) + subprocess.check_call([ + 'chmod', '+x', + "{}/test_server.bin".format(ctxt['workspace_path']) + ]) + + +def _add_environment_var_config(ctxt, services): + """Add environment variable config to context. + + :param ctxt: Context dictionary + :type ctxt: dict + :returns: None + :rtype: None + """ + deploy_env = deployment_env.get_deployment_context() + missing_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: + if var not in IGNORABLE_VARS: + missing_vars.append(var) + if missing_vars: + raise ValueError( + ("Environment variables [{}] must all be set to run this" + " test").format(', '.join(missing_vars))) + + +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'] + ctxt['admin_password'] = overcloud_auth['OS_PASSWORD'] + 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']) + + +def _get_service_list(keystone_session): + """Retrieve list of services from keystone. + + :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] From 40a9a5bfb3a786e8c2911786a740d4671bf5dab6 Mon Sep 17 00:00:00 2001 From: Luciano Lo Giudice Date: Mon, 20 Sep 2021 11:55:52 -0300 Subject: [PATCH 46/62] Safely remove volume Move the checks inside a 'try' block so that the volume can be safely remove as a 'finally' statement. --- .../charm_tests/cinder_netapp/tests.py | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/zaza/openstack/charm_tests/cinder_netapp/tests.py b/zaza/openstack/charm_tests/cinder_netapp/tests.py index b5ce8fb..11bfe01 100644 --- a/zaza/openstack/charm_tests/cinder_netapp/tests.py +++ b/zaza/openstack/charm_tests/cinder_netapp/tests.py @@ -63,15 +63,17 @@ class CinderNetAppTest(test_utils.OpenStackBaseTest): vol_new = self.cinder_client.volumes.create( name=test_vol_name, size='1') - openstack_utils.resource_reaches_status( - self.cinder_client.volumes, - vol_new.id, - wait_iteration_max_time=12000, - stop_after_attempt=5, - expected_status='available', - msg='Volume status wait') - test_vol = self.cinder_client.volumes.find(name=test_vol_name) - self.assertEqual( - getattr(test_vol, 'os-vol-host-attr:host').split('#')[0], - 'cinder@cinder-netapp') - self.cinder_client.volumes.delete(vol_new) + try: + openstack_utils.resource_reaches_status( + self.cinder_client.volumes, + vol_new.id, + wait_iteration_max_time=12000, + stop_after_attempt=5, + expected_status='available', + msg='Volume status wait') + test_vol = self.cinder_client.volumes.find(name=test_vol_name) + self.assertEqual( + getattr(test_vol, 'os-vol-host-attr:host').split('#')[0], + 'cinder@cinder-netapp') + finally: + self.cinder_client.volumes.delete(vol_new) From a5831c43df11d772e4da50c08d1efb1539488362 Mon Sep 17 00:00:00 2001 From: Luciano Lo Giudice Date: Mon, 20 Sep 2021 11:57:06 -0300 Subject: [PATCH 47/62] Remove pointless tracing. --- zaza/openstack/charm_tests/cinder_netapp/tests.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/zaza/openstack/charm_tests/cinder_netapp/tests.py b/zaza/openstack/charm_tests/cinder_netapp/tests.py index 11bfe01..41456ba 100644 --- a/zaza/openstack/charm_tests/cinder_netapp/tests.py +++ b/zaza/openstack/charm_tests/cinder_netapp/tests.py @@ -16,7 +16,6 @@ """Encapsulate cinder-netapp testing.""" -import logging import uuid import zaza.model @@ -38,7 +37,6 @@ class CinderNetAppTest(test_utils.OpenStackBaseTest): def test_cinder_config(self): """Test that configuration options match our expectations.""" - logging.info('cinder-netapp') expected_contents = { 'cinder-netapp': { 'netapp_storage_family': ['ontap_cluster'], From 839e1a0868d13f39f28ace5604510efbcc7a0c6f Mon Sep 17 00:00:00 2001 From: Luciano Lo Giudice Date: Mon, 20 Sep 2021 20:06:37 -0300 Subject: [PATCH 48/62] Don't make assumptions about present devices Instead of using '/dev/vdc' for a test, use a loop device, since it should always be available and it allows us to set a specific size for it. --- zaza/openstack/charm_tests/cinder_lvm/tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/zaza/openstack/charm_tests/cinder_lvm/tests.py b/zaza/openstack/charm_tests/cinder_lvm/tests.py index 45fd126..a90c0bf 100644 --- a/zaza/openstack/charm_tests/cinder_lvm/tests.py +++ b/zaza/openstack/charm_tests/cinder_lvm/tests.py @@ -94,11 +94,11 @@ class CinderLVMTest(test_utils.OpenStackBaseTest): self.assertTrue(host.startswith('cinder@LVM')) def test_volume_overwrite(self): - """Test creating a volume by overwriting one on the /dev/vdc device.""" + """Test creating a volume by overwriting one on a loop device.""" with self.config_change({'overwrite': 'false', 'block-device': self.block_device}, {'overwrite': 'true', - 'block-device': '/dev/vdc'}): + 'block-device': '/tmp/vol|2G'}): self._create_volume() def test_device_none(self): From c32a6d420b7c085fe93ebd9bdee560616dbdac86 Mon Sep 17 00:00:00 2001 From: Aurelien Lourot Date: Fri, 24 Sep 2021 14:20:01 +0200 Subject: [PATCH 49/62] Fix various typos --- zaza/openstack/charm_tests/mysql/tests.py | 2 +- zaza/openstack/charm_tests/openstack_upgrade/tests.py | 5 +++-- zaza/openstack/charm_tests/trilio/tests.py | 2 +- zaza/openstack/utilities/openstack.py | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/zaza/openstack/charm_tests/mysql/tests.py b/zaza/openstack/charm_tests/mysql/tests.py index 5fbfb7e..165e66f 100644 --- a/zaza/openstack/charm_tests/mysql/tests.py +++ b/zaza/openstack/charm_tests/mysql/tests.py @@ -223,7 +223,7 @@ class MySQLCommonTests(MySQLBaseTest): logging.info("Wait till model is idle ...") zaza.model.block_until_all_units_idle() - # If there are any blocekd mysql routers restart them. + # If there are any blocked mysql routers restart them. self.restart_blocked_mysql_routers() assert not self.get_blocked_mysql_routers(), ( "Should no longer be blocked mysql-router units") diff --git a/zaza/openstack/charm_tests/openstack_upgrade/tests.py b/zaza/openstack/charm_tests/openstack_upgrade/tests.py index 620bda0..661a076 100644 --- a/zaza/openstack/charm_tests/openstack_upgrade/tests.py +++ b/zaza/openstack/charm_tests/openstack_upgrade/tests.py @@ -108,7 +108,7 @@ class OpenStackUpgradeTestsByOption(OpenStackUpgradeVMLaunchBase): tests_options: openstack-upgrade: - detect-charm: keystone + detect-using-charm: keystone This will use the octavia application, detect the ubuntu version and then read the config to discover the current OpenStack version. @@ -135,7 +135,8 @@ class OpenStackUpgradeTestsByOption(OpenStackUpgradeVMLaunchBase): .openstack_upgrade.detect_using_charm) except KeyError: raise exceptions.InvalidTestConfig( - "Missing tests_options.openstack-upgrade.detect-charm config.") + "Missing tests_options.openstack-upgrade.detect-using-charm " + "config.") unit = zaza.model.get_lead_unit(detect_charm) ubuntu_version = generic.get_series(unit) diff --git a/zaza/openstack/charm_tests/trilio/tests.py b/zaza/openstack/charm_tests/trilio/tests.py index 8e81ae7..c5902c2 100644 --- a/zaza/openstack/charm_tests/trilio/tests.py +++ b/zaza/openstack/charm_tests/trilio/tests.py @@ -426,7 +426,7 @@ class TrilioBaseTest(test_utils.OpenStackBaseTest): workloadmgrcli.oneclick_restore(snapshot_id) def test_update_trilio_action(self): - """Test that the action runs succesfully.""" + """Test that the action runs successfully.""" action_name = 'update-trilio' actions = zaza_model.get_actions( self.application_name) diff --git a/zaza/openstack/utilities/openstack.py b/zaza/openstack/utilities/openstack.py index 36ff964..ce57157 100644 --- a/zaza/openstack/utilities/openstack.py +++ b/zaza/openstack/utilities/openstack.py @@ -2832,7 +2832,7 @@ def ssh_test(username, ip, vm_name, password=None, privkey=None, retry=True): return_string = stdout.readlines()[0].strip() if return_string == vm_name: - logging.info('SSH to %s(%s) succesfull' % (vm_name, ip)) + logging.info('SSH to %s(%s) successful' % (vm_name, ip)) else: logging.info('SSH to %s(%s) failed (%s != %s)' % (vm_name, ip, return_string, From d6dcf4fbd445787cb91c3a4f9cc8ba06099c179e Mon Sep 17 00:00:00 2001 From: Chris MacNaughton Date: Sun, 3 Oct 2021 16:54:05 +0200 Subject: [PATCH 50/62] Remove duplicated code in Dashboard setup (#644) --- zaza/openstack/charm_tests/openstack_dashboard/tests.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/zaza/openstack/charm_tests/openstack_dashboard/tests.py b/zaza/openstack/charm_tests/openstack_dashboard/tests.py index 5cd341c..4746b9f 100644 --- a/zaza/openstack/charm_tests/openstack_dashboard/tests.py +++ b/zaza/openstack/charm_tests/openstack_dashboard/tests.py @@ -495,17 +495,16 @@ class OpenStackDashboardPolicydTests(policyd.BasePolicydSpecialization, zaza_model.get_lead_unit_name(self.application_name)) logging.info("Dashboard is at {}".format(unit.public_address)) overcloud_auth = openstack_utils.get_overcloud_auth() - password = overcloud_auth['OS_PASSWORD'], + password = overcloud_auth['OS_PASSWORD'] logging.info("admin password is {}".format(password)) # try to get the url which will either pass or fail with a 403 - overcloud_auth = openstack_utils.get_overcloud_auth() - domain = 'admin_domain', - username = 'admin', - password = overcloud_auth['OS_PASSWORD'], + domain = 'admin_domain' + username = 'admin' client, response = _login( self.get_horizon_url(), domain, username, password) # now attempt to get the domains page _url = self.url.format(unit.public_address) + logging.info("URL is {}".format(_url)) result = client.get(_url) if result.status_code == 403: raise policyd.PolicydOperationFailedException("Not authenticated") From 6918f9ebb32d7b68f225a104e5a9e55d0a385d8f Mon Sep 17 00:00:00 2001 From: Liam Young Date: Sun, 3 Oct 2021 16:13:50 +0100 Subject: [PATCH 51/62] Extend Ceph Dashboards tests to check ceph services are registered (#643) --- .../charm_tests/ceph/dashboard/tests.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/zaza/openstack/charm_tests/ceph/dashboard/tests.py b/zaza/openstack/charm_tests/ceph/dashboard/tests.py index 9c6eae7..53119dd 100644 --- a/zaza/openstack/charm_tests/ceph/dashboard/tests.py +++ b/zaza/openstack/charm_tests/ceph/dashboard/tests.py @@ -16,6 +16,7 @@ import collections import json +import logging import requests import uuid @@ -132,3 +133,26 @@ class CephDashboardTest(test_utils.BaseCharmTest): def test_access_dashboard(self): """Test logging in to the dashboard.""" self.access_dashboard(self.get_master_dashboard_url()) + + def test_ceph_keys(self): + """Check that ceph services are properly registered.""" + status = zaza.model.get_status() + applications = status.applications.keys() + dashboard_keys = [] + ceph_keys = [] + if 'ceph-radosgw' in applications: + dashboard_keys.extend(['RGW_API_ACCESS_KEY', 'RGW_API_SECRET_KEY']) + if 'grafana' in applications: + dashboard_keys.append('GRAFANA_API_URL') + if 'prometheus' in applications: + dashboard_keys.append('PROMETHEUS_API_HOST') + ceph_keys.extend( + ['config/mgr/mgr/dashboard/{}'.format(k) for k in dashboard_keys]) + if 'ceph-iscsi' in applications: + ceph_keys.append('mgr/dashboard/_iscsi_config') + for key in ceph_keys: + logging.info("Checking key {} exists".format(key)) + check_out = zaza.model.run_on_leader( + 'ceph-dashboard', + 'ceph config-key exists {}'.format(key)) + self.assertEqual(check_out['Code'], '0') From 8e94f4bcd43f2de10ebbaa1c66f07dff5f0a9823 Mon Sep 17 00:00:00 2001 From: Alex Kavanagh Date: Sun, 3 Oct 2021 17:27:18 +0100 Subject: [PATCH 52/62] Pin coverage < 6.0.0 for Py3.5 support Coverage dropped py3.5 suppot at 6.0.0 and zaza.openstack.tests currently support Xenial. --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 1621491..964fc52 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ flake8>=2.2.4 flake8-docstrings flake8-per-file-ignores pydocstyle<4.0.0 -coverage +coverage<6.0.0 # coverage 6.0+ drops support for py3.5/py2.7 mock>=1.2 nose>=1.3.7 pbr>=1.8.0,<1.9.0 From d689fb91fc284d79bdde7eceee23e8c9a2b66d55 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Mon, 4 Oct 2021 12:16:43 +0100 Subject: [PATCH 53/62] Add retries when accessing Ceph dashboard (#646) --- .../charm_tests/ceph/dashboard/tests.py | 62 +++++++++++++++++-- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/zaza/openstack/charm_tests/ceph/dashboard/tests.py b/zaza/openstack/charm_tests/ceph/dashboard/tests.py index 53119dd..50cdbaf 100644 --- a/zaza/openstack/charm_tests/ceph/dashboard/tests.py +++ b/zaza/openstack/charm_tests/ceph/dashboard/tests.py @@ -18,6 +18,7 @@ import collections import json import logging import requests +import tenacity import uuid import zaza @@ -39,6 +40,57 @@ class CephDashboardTest(test_utils.BaseCharmTest): cls.local_ca_cert = openstack_utils.get_remote_ca_cert_file( cls.application_name) + @tenacity.retry(wait=tenacity.wait_exponential(multiplier=1, + min=5, max=10), + retry=tenacity.retry_if_exception_type( + requests.exceptions.ConnectionError), + reraise=True) + def _run_request_get(self, url, verify, allow_redirects): + """Run a GET request against `url` with tenacity retries. + + :param url: url to access + :type url: str + :param verify: Path to a CA_BUNDLE file or directory with certificates + of trusted CAs or False to ignore verifying the SSL + certificate. + :type verify: Union[str, bool] + :param allow_redirects: Set to True if redirect following is allowed. + :type allow_redirects: bool + :returns: Request response + :rtype: requests.models.Response + """ + return requests.get( + url, + verify=verify, + allow_redirects=allow_redirects) + + @tenacity.retry(wait=tenacity.wait_exponential(multiplier=1, + min=5, max=10), + retry=tenacity.retry_if_exception_type( + requests.exceptions.ConnectionError), + reraise=True) + def _run_request_post(self, url, verify, data, headers): + """Run a POST request against `url` with tenacity retries. + + :param url: url to access + :type url: str + :param verify: Path to a CA_BUNDLE file or directory with certificates + of trusted CAs or False to ignore verifying the SSL + certificate. + :type verify: Union[str, bool] + :param data: Data to post to url + :type data: str + :param headers: Headers to set when posting + :type headers: dict + :returns: Request response + :rtype: requests.models.Response + """ + return requests.post( + url, + data=data, + headers=headers, + verify=verify) + def get_master_dashboard_url(self): """Get the url of the dashboard servicing requests. @@ -50,7 +102,7 @@ class CephDashboardTest(test_utils.BaseCharmTest): """ units = zaza.model.get_units(self.application_name) for unit in units: - r = requests.get( + r = self._run_request_get( 'https://{}:8443'.format(unit.public_address), verify=self.local_ca_cert, allow_redirects=False) @@ -63,7 +115,7 @@ class CephDashboardTest(test_utils.BaseCharmTest): units = zaza.model.get_units(self.application_name) rcs = collections.defaultdict(list) for unit in units: - r = requests.get( + r = self._run_request_get( 'https://{}:8443'.format(unit.public_address), verify=verify, allow_redirects=False) @@ -123,11 +175,11 @@ class CephDashboardTest(test_utils.BaseCharmTest): 'Accept': 'application/vnd.ceph.api.v1.0'} payload = {"username": user, "password": password} verify = self.local_ca_cert - r = requests.post( + r = self._run_request_post( "{}/{}".format(dashboard_url, path), + verify=verify, data=json.dumps(payload), - headers=headers, - verify=verify) + headers=headers) self.assertEqual(r.status_code, requests.codes.created) def test_access_dashboard(self): From eeebe68117db46f38170ff4c016517901bc4cd2f Mon Sep 17 00:00:00 2001 From: Aurelien Lourot Date: Mon, 4 Oct 2021 15:14:51 +0200 Subject: [PATCH 54/62] Increase timeout on instance creation See https://bugs.launchpad.net/charm-nova-compute/+bug/1945991 Change-Id: I2e66e22258fce4b0fcd5443b104fad6a5a720b0b --- zaza/openstack/configure/guest.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/zaza/openstack/configure/guest.py b/zaza/openstack/configure/guest.py index c2e20de..d63938d 100644 --- a/zaza/openstack/configure/guest.py +++ b/zaza/openstack/configure/guest.py @@ -125,6 +125,9 @@ def launch_instance(instance_key, use_boot_volume=False, vm_name=None, nova_client.servers, instance.id, expected_status='ACTIVE', + # NOTE(lourot): in some models this may sometimes take more than 15 + # minutes. See lp:1945991 + wait_iteration_max_time=120, stop_after_attempt=16) logging.info('Checking cloud init is complete') From aa3807dfcc9637e10c66701ebdfab03a6d41045f Mon Sep 17 00:00:00 2001 From: Chris MacNaughton Date: Tue, 5 Oct 2021 17:10:46 +0200 Subject: [PATCH 55/62] Add a request timeout, and pass in cacert when talking to horizon (#649) --- zaza/openstack/charm_tests/openstack_dashboard/tests.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/zaza/openstack/charm_tests/openstack_dashboard/tests.py b/zaza/openstack/charm_tests/openstack_dashboard/tests.py index 4746b9f..60ac7eb 100644 --- a/zaza/openstack/charm_tests/openstack_dashboard/tests.py +++ b/zaza/openstack/charm_tests/openstack_dashboard/tests.py @@ -66,7 +66,7 @@ def _login(dashboard_url, domain, username, password, cafile=None): # start session, get csrftoken client = requests.session() - client.get(auth_url, verify=cafile) + client.get(auth_url, verify=cafile, timeout=30) if 'csrftoken' in client.cookies: csrftoken = client.cookies['csrftoken'] else: @@ -501,7 +501,8 @@ class OpenStackDashboardPolicydTests(policyd.BasePolicydSpecialization, domain = 'admin_domain' username = 'admin' client, response = _login( - self.get_horizon_url(), domain, username, password) + self.get_horizon_url(), domain, username, password, + cafile=self.cacert) # now attempt to get the domains page _url = self.url.format(unit.public_address) logging.info("URL is {}".format(_url)) From 2a4ad7f74e332d03e8bee57e454f47b0a88a488e Mon Sep 17 00:00:00 2001 From: Luciano Lo Giudice Date: Thu, 7 Oct 2021 22:07:00 -0300 Subject: [PATCH 56/62] Fix test for cinder-lvm Since the charm is now marked stateful, the 'host' attribute of a volume is mangled differently. This change makes the test more robust against future changes as well. --- zaza/openstack/charm_tests/cinder_lvm/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zaza/openstack/charm_tests/cinder_lvm/tests.py b/zaza/openstack/charm_tests/cinder_lvm/tests.py index a90c0bf..ae9bc92 100644 --- a/zaza/openstack/charm_tests/cinder_lvm/tests.py +++ b/zaza/openstack/charm_tests/cinder_lvm/tests.py @@ -91,7 +91,7 @@ class CinderLVMTest(test_utils.OpenStackBaseTest): self.assertTrue(test_vol) host = getattr(test_vol, 'os-vol-host-attr:host').split('#')[0] - self.assertTrue(host.startswith('cinder@LVM')) + self.assertIn('@LVM', host) def test_volume_overwrite(self): """Test creating a volume by overwriting one on a loop device.""" From 6c834770edaa19a2754c79dcee5652fca1b9534e Mon Sep 17 00:00:00 2001 From: coreycb Date: Fri, 8 Oct 2021 10:05:36 -0400 Subject: [PATCH 57/62] Add neutron setup for VLAN provider network (#639) This adds a new setup function that will setup a VLAN provider network. It can be called by tests.yaml after basic_overcloud_network: - zaza.openstack.charm_tests.neutron.setup.basic_overcloud_network - zaza.openstack.charm_tests.neutron.setup.vlan_provider_overcloud_network --- .../test_zaza_utilities_openstack.py | 17 ++--- zaza/openstack/charm_tests/neutron/setup.py | 30 ++++++++- zaza/openstack/configure/network.py | 63 +++++++++++++++++-- zaza/openstack/utilities/openstack.py | 43 ++++++++----- 4 files changed, 126 insertions(+), 27 deletions(-) diff --git a/unit_tests/utilities/test_zaza_utilities_openstack.py b/unit_tests/utilities/test_zaza_utilities_openstack.py index 2dd3fcd..472b35b 100644 --- a/unit_tests/utilities/test_zaza_utilities_openstack.py +++ b/unit_tests/utilities/test_zaza_utilities_openstack.py @@ -58,11 +58,12 @@ class TestOpenStackUtils(ut_utils.BaseTestCase): self.network = { "network": {"id": "network_id", - "name": self.ext_net, - "tenant_id": self.project_id, - "router:external": True, - "provider:physical_network": "physnet1", - "provider:network_type": "flat"}} + "name": self.ext_net, + "router:external": True, + "shared": False, + "tenant_id": self.project_id, + "provider:physical_network": "physnet1", + "provider:network_type": "flat"}} self.networks = { "networks": [self.network["network"]]} @@ -157,12 +158,12 @@ class TestOpenStackUtils(ut_utils.BaseTestCase): self.neutronclient.create_address_scope.assert_called_once_with( address_scope_msg) - def test_create_external_network(self): + def test_create_provider_network(self): self.patch_object(openstack_utils, "get_net_uuid") self.get_net_uuid.return_value = self.net_uuid # Already exists - network = openstack_utils.create_external_network( + network = openstack_utils.create_provider_network( self.neutronclient, self.project_id) self.assertEqual(network, self.network["network"]) self.neutronclient.create_network.assert_not_called() @@ -172,7 +173,7 @@ class TestOpenStackUtils(ut_utils.BaseTestCase): "networks": []} network_msg = copy.deepcopy(self.network) network_msg["network"].pop("id") - network = openstack_utils.create_external_network( + network = openstack_utils.create_provider_network( self.neutronclient, self.project_id) self.assertEqual(network, self.network["network"]) self.neutronclient.create_network.assert_called_once_with( diff --git a/zaza/openstack/charm_tests/neutron/setup.py b/zaza/openstack/charm_tests/neutron/setup.py index 11a01bc..174c51d 100644 --- a/zaza/openstack/charm_tests/neutron/setup.py +++ b/zaza/openstack/charm_tests/neutron/setup.py @@ -44,6 +44,13 @@ OVERCLOUD_NETWORK_CONFIG = { "subnetpool_prefix": "192.168.0.0/16", } +OVERCLOUD_PROVIDER_VLAN_NETWORK_CONFIG = { + "provider_vlan_net_name": "provider_vlan", + "provider_vlan_subnet_name": "provider_vlan_subnet", + "provider_vlan_cidr": "10.42.33.0/24", + "provider_vlan_id": "2933", +} + # The undercloud network configuration settings are substrate specific to # the environment where the tests are being executed. These settings may be # overridden by environment variables. See the doc string documentation for @@ -110,10 +117,31 @@ def basic_overcloud_network(limit_gws=None): ' charm network configuration.' .format(provider_type)) - # Confugre the overcloud network + # Configure the overcloud network network.setup_sdn(network_config, keystone_session=keystone_session) +def vlan_provider_overcloud_network(): + """Run setup to create a VLAN provider network.""" + cli_utils.setup_logging() + + # Get network configuration settings + network_config = {} + # Declared overcloud settings + network_config.update(OVERCLOUD_NETWORK_CONFIG) + # Declared provider vlan overcloud settings + network_config.update(OVERCLOUD_PROVIDER_VLAN_NETWORK_CONFIG) + # Environment specific settings + network_config.update(generic_utils.get_undercloud_env_vars()) + + # Get keystone session + keystone_session = openstack_utils.get_overcloud_keystone_session() + + # Configure the overcloud network + network.setup_sdn_provider_vlan(network_config, + keystone_session=keystone_session) + + # Configure function to get one gateway with external network overcloud_network_one_gw = functools.partial( basic_overcloud_network, diff --git a/zaza/openstack/configure/network.py b/zaza/openstack/configure/network.py index ad1a07b..5d3a2f8 100755 --- a/zaza/openstack/configure/network.py +++ b/zaza/openstack/configure/network.py @@ -126,19 +126,19 @@ def setup_sdn(network_config, keystone_session=None): logging.info("Configuring overcloud network") # Create the external network - ext_network = openstack_utils.create_external_network( + ext_network = openstack_utils.create_provider_network( neutron_client, project_id, network_config["external_net_name"]) - openstack_utils.create_external_subnet( + openstack_utils.create_provider_subnet( neutron_client, project_id, ext_network, + network_config["external_subnet_name"], network_config["default_gateway"], network_config["external_net_cidr"], network_config["start_floating_ip"], - network_config["end_floating_ip"], - network_config["external_subnet_name"]) + network_config["end_floating_ip"]) provider_router = ( openstack_utils.create_provider_router(neutron_client, project_id)) openstack_utils.plug_extnet_into_router( @@ -183,6 +183,61 @@ def setup_sdn(network_config, keystone_session=None): openstack_utils.add_neutron_secgroup_rules(neutron_client, project_id) +def setup_sdn_provider_vlan(network_config, keystone_session=None): + """Perform setup for Software Defined Network, specifically a provider VLAN. + + :param network_config: Network configuration settings dictionary + :type network_config: dict + :param keystone_session: Keystone session object for overcloud + :type keystone_session: keystoneauth1.session.Session object + :returns: None + :rtype: None + """ + # If a session has not been provided, acquire one + if not keystone_session: + keystone_session = openstack_utils.get_overcloud_keystone_session() + + # Get authenticated clients + keystone_client = openstack_utils.get_keystone_session_client( + keystone_session) + neutron_client = openstack_utils.get_neutron_session_client( + keystone_session) + + admin_domain = None + if openstack_utils.get_keystone_api_version() > 2: + admin_domain = "admin_domain" + # Resolve the project name from the overcloud openrc into a project id + project_id = openstack_utils.get_project_id( + keystone_client, + "admin", + domain_name=admin_domain, + ) + + logging.info("Configuring VLAN provider network") + # Create the external network + provider_vlan_network = openstack_utils.create_provider_network( + neutron_client, + project_id, + net_name=network_config["provider_vlan_net_name"], + external=False, + shared=True, + network_type='vlan', + vlan_id=network_config["provider_vlan_id"]) + provider_vlan_subnet = openstack_utils.create_provider_subnet( + neutron_client, + project_id, + provider_vlan_network, + network_config["provider_vlan_subnet_name"], + cidr=network_config["provider_vlan_cidr"], + dhcp=True) + openstack_utils.plug_subnet_into_router( + neutron_client, + network_config["router_name"], + provider_vlan_network, + provider_vlan_subnet) + openstack_utils.add_neutron_secgroup_rules(neutron_client, project_id) + + def setup_gateway_ext_port(network_config, keystone_session=None, limit_gws=None, use_juju_wait=True): diff --git a/zaza/openstack/utilities/openstack.py b/zaza/openstack/utilities/openstack.py index ce57157..2f19905 100644 --- a/zaza/openstack/utilities/openstack.py +++ b/zaza/openstack/utilities/openstack.py @@ -1139,8 +1139,10 @@ def create_project_network(neutron_client, project_id, net_name='private', return network -def create_external_network(neutron_client, project_id, net_name='ext_net'): - """Create the external network. +def create_provider_network(neutron_client, project_id, net_name='ext_net', + external=True, shared=False, network_type='flat', + vlan_id=None): + """Create a provider network. :param neutron_client: Authenticated neutronclient :type neutron_client: neutronclient.Client object @@ -1148,25 +1150,35 @@ def create_external_network(neutron_client, project_id, net_name='ext_net'): :type project_id: string :param net_name: Network name :type net_name: string + :param shared: The network should be external + :type shared: boolean + :param shared: The network should be shared between projects + :type shared: boolean + :param net_type: Network type: GRE, VXLAN, local, VLAN + :type net_type: string + :param net_name: VLAN ID + :type net_name: string :returns: Network object :rtype: dict """ networks = neutron_client.list_networks(name=net_name) if len(networks['networks']) == 0: - logging.info('Configuring external network') + logging.info('Creating %s %s network: %s', network_type, + 'external' if external else 'provider', net_name) network_msg = { 'name': net_name, - 'router:external': True, + 'router:external': external, + 'shared': shared, 'tenant_id': project_id, 'provider:physical_network': 'physnet1', - 'provider:network_type': 'flat', + 'provider:network_type': network_type, } - logging.info('Creating new external network definition: %s', - net_name) + if network_type == 'vlan': + network_msg['provider:segmentation_id'] = int(vlan_id) network = neutron_client.create_network( {'network': network_msg})['network'] - logging.info('New external network created: %s', network['id']) + logging.info('Network %s created: %s', net_name, network['id']) else: logging.warning('Network %s already exists.', net_name) network = networks['networks'][0] @@ -1226,11 +1238,12 @@ def create_project_subnet(neutron_client, project_id, network, cidr, dhcp=True, return subnet -def create_external_subnet(neutron_client, project_id, network, +def create_provider_subnet(neutron_client, project_id, network, + subnet_name='ext_net_subnet', default_gateway=None, cidr=None, start_floating_ip=None, end_floating_ip=None, - subnet_name='ext_net_subnet'): - """Create the external subnet. + dhcp=False): + """Create the provider subnet. :param neutron_client: Authenticated neutronclient :type neutron_client: neutronclient.Client object @@ -1240,14 +1253,16 @@ def create_external_subnet(neutron_client, project_id, network, :type network: dict :param default_gateway: Deafault gateway IP address :type default_gateway: string + :param subnet_name: Subnet name + :type subnet_name: string :param cidr: Network CIDR :type cidr: string :param start_floating_ip: Start of floating IP range: IP address :type start_floating_ip: string or None :param end_floating_ip: End of floating IP range: IP address :type end_floating_ip: string or None - :param subnet_name: Subnet name - :type subnet_name: string + :param dhcp: Run DHCP on this subnet + :type dhcp: boolean :returns: Subnet object :rtype: dict """ @@ -1256,7 +1271,7 @@ def create_external_subnet(neutron_client, project_id, network, subnet_msg = { 'name': subnet_name, 'network_id': network['id'], - 'enable_dhcp': False, + 'enable_dhcp': dhcp, 'ip_version': 4, 'tenant_id': project_id } From 860a9fd76ceb79c1704d1b6d8dd38c50061a1fec Mon Sep 17 00:00:00 2001 From: Corey Bryant Date: Thu, 14 Oct 2021 14:40:23 +0000 Subject: [PATCH 58/62] Favor the V3 volume endpoint for tempest config If the V3 volume endpoint is available, use it for tempest testing. The V2 volume endpoint is removed in OpenStack Xena, so this allows us to use the latest available endpoint. --- zaza/openstack/charm_tests/tempest/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zaza/openstack/charm_tests/tempest/utils.py b/zaza/openstack/charm_tests/tempest/utils.py index 28ad65e..830600e 100644 --- a/zaza/openstack/charm_tests/tempest/utils.py +++ b/zaza/openstack/charm_tests/tempest/utils.py @@ -286,7 +286,8 @@ def _add_cinder_config(ctxt, keystone_session): :returns: None :rtype: None """ - volume_types = ['volumev2', 'volumev3'] + # The most most recent API version must be listed first. + volume_types = ['volumev3', 'volumev2'] keystone_client = openstack_utils.get_keystone_session_client( keystone_session) for volume_type in volume_types: From cc291b8400bae240d72a0cd2218be11b88dc1f41 Mon Sep 17 00:00:00 2001 From: Aurelien Lourot Date: Mon, 18 Oct 2021 10:18:39 +0200 Subject: [PATCH 59/62] Extend OPENSTACK_CODENAMES up to Yoga (#654) This dict is used in the OpenStack upgrade tests in order to automatically determine the next release and set the openstack-origin accordingly. Also taking the opportunity to extend neighboring lists. --- zaza/openstack/utilities/os_versions.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/zaza/openstack/utilities/os_versions.py b/zaza/openstack/utilities/os_versions.py index 2ea089e..e67a97f 100644 --- a/zaza/openstack/utilities/os_versions.py +++ b/zaza/openstack/utilities/os_versions.py @@ -38,6 +38,7 @@ UBUNTU_OPENSTACK_RELEASE = OrderedDict([ ('groovy', 'victoria'), ('hirsute', 'wallaby'), ('impish', 'xena'), + ('jammy', 'yoga'), ]) @@ -61,6 +62,9 @@ OPENSTACK_CODENAMES = OrderedDict([ ('2019.2', 'train'), ('2020.1', 'ussuri'), ('2020.2', 'victoria'), + ('2021.1', 'wallaby'), + ('2021.2', 'xena'), + ('2022.1', 'yoga'), ]) OPENSTACK_RELEASES_PAIRS = [ @@ -74,7 +78,7 @@ OPENSTACK_RELEASES_PAIRS = [ 'focal_victoria', 'groovy_victoria', 'focal_wallaby', 'hirsute_wallaby', 'focal_xena', 'impish_xena', - 'focal_yoga' + 'focal_yoga', 'jammy_yoga', ] SWIFT_CODENAMES = OrderedDict([ From ae50ed3199dac6f27d5d3d17c30cb00805cf0b27 Mon Sep 17 00:00:00 2001 From: Alex Kavanagh Date: Mon, 25 Oct 2021 15:01:25 +0100 Subject: [PATCH 60/62] Pin pyparsing to work with pinned version of aodhclient aodhclient is pinned at 1.4.0 and pyparsing needs to be pinned at < 3.0.0 to work with. Note zaza also pins pyparsing as many, many modules use pyparsing and if zaza gets installed first ti might break pip resolution. --- requirements.txt | 1 + setup.py | 1 + 2 files changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index 964fc52..9a069f9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,7 @@ # This is necessary for Xenial builders # BUG: https://github.com/openstack-charmers/zaza-openstack-tests/issues/530 lxml<4.6.3 +pyparsing<3.0.0 # pin for aodhclient which is held for py35 aiounittest async_generator boto3 diff --git a/setup.py b/setup.py index fb4377d..d90e716 100644 --- a/setup.py +++ b/setup.py @@ -43,6 +43,7 @@ install_require = [ 'PyYAML', 'tenacity', 'oslo.config<6.12.0', + 'pyparsing<3.0.0', # pin for aodhclient which is held for py35 'aodhclient<1.4.0', 'gnocchiclient>=7.0.5,<8.0.0', 'pika>=1.1.0,<2.0.0', From 5b2ef5df9bca41d227bcd1a408c8a64a5f3ddb43 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Thu, 28 Oct 2021 08:08:19 +0000 Subject: [PATCH 61/62] Add auto_initialize_opportunistic Add a method which will init vault if its present and skip if it is not. This allows much simpler tests.yaml if all test and config steps are the same for TLS and non-TLS test cases apart from initialising vault. --- zaza/openstack/charm_tests/vault/setup.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/zaza/openstack/charm_tests/vault/setup.py b/zaza/openstack/charm_tests/vault/setup.py index 87917bc..88e3d3e 100644 --- a/zaza/openstack/charm_tests/vault/setup.py +++ b/zaza/openstack/charm_tests/vault/setup.py @@ -135,7 +135,8 @@ async def async_unseal_by_unit(cacert=None): unit_name, './hooks/update-status') -def auto_initialize(cacert=None, validation_application='keystone', wait=True): +def auto_initialize(cacert=None, validation_application='keystone', wait=True, + skip_on_absent=False): """Auto initialize vault for testing. Generate a csr and uploading a signed certificate. @@ -147,9 +148,16 @@ def auto_initialize(cacert=None, validation_application='keystone', wait=True): :param validation_application: Name of application to be used as a client for validation. :type validation_application: str + :param skip_on_absent: Non-fatal skip initialise if vault absent. + :type validation_application: bool :returns: None :rtype: None """ + if skip_on_absent: + status = zaza.model.get_status() + if 'vault' not in status.applications.keys(): + logging.info('Skipping auto_initialize, vault not in model') + return logging.info('Running auto_initialize') basic_setup(cacert=cacert, unseal_and_authorize=True) @@ -197,6 +205,11 @@ def auto_initialize(cacert=None, validation_application='keystone', wait=True): pass +auto_initialize_opportunistic = functools.partial( + auto_initialize, + skip_on_absent=True) + + auto_initialize_no_validation = functools.partial( auto_initialize, validation_application=None) From 14999ca1a199e4b9ec81292103f5d29ab012a7bf Mon Sep 17 00:00:00 2001 From: Liam Young Date: Thu, 28 Oct 2021 11:16:58 +0100 Subject: [PATCH 62/62] Add two more auto initialise options (#664) --- zaza/openstack/charm_tests/vault/setup.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/zaza/openstack/charm_tests/vault/setup.py b/zaza/openstack/charm_tests/vault/setup.py index 88e3d3e..05e8cc5 100644 --- a/zaza/openstack/charm_tests/vault/setup.py +++ b/zaza/openstack/charm_tests/vault/setup.py @@ -210,6 +210,19 @@ auto_initialize_opportunistic = functools.partial( skip_on_absent=True) +auto_initialize_opportunistic_no_validation = functools.partial( + auto_initialize, + validation_application=None, + skip_on_absent=True) + + +auto_initialize_opportunistic_no_validation_no_wait = functools.partial( + auto_initialize, + validation_application=None, + wait=False, + skip_on_absent=True) + + auto_initialize_no_validation = functools.partial( auto_initialize, validation_application=None)