From 0a8889796605d33315e7fed93743c21eba1d2c6b Mon Sep 17 00:00:00 2001 From: Seyeong Kim Date: Tue, 9 Apr 2024 15:34:05 +0900 Subject: [PATCH 01/13] Adding fixing broken configuration test for mysql-router --- zaza/openstack/charm_tests/mysql/tests.py | 63 +++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/zaza/openstack/charm_tests/mysql/tests.py b/zaza/openstack/charm_tests/mysql/tests.py index 710195541..7c574a919 100644 --- a/zaza/openstack/charm_tests/mysql/tests.py +++ b/zaza/openstack/charm_tests/mysql/tests.py @@ -1145,3 +1145,66 @@ def test_910_restart_on_config_change(self): {}, {}, self.services) logging.info("Passed restart on changed test.") + + def test_920_mysqlrouter_conf_broken(self): + """Checking conf broken case. + + Run the bootstrap when conf is broken + """ + # application_name on test is keystone-mysql-router + # using self.conf_file introduces error. + # instead of changing current self.conf_file, + # define one (it could introduce another issue) + config_file = ( + "/var/lib/mysql/{}/mysqlrouter.conf" + .format(self.application_name)) + + logging.info("Starting broken conf test") + + # put empty string to conf_file and make it wrong + logging.info("Breaking configuration file") + zaza.model.run_on_leader(self.application, + "echo '[DEFAULT]\n \ + [metadata_cache:[\\w$]+$] \ + ' > {}".format( + config_file)) + + logging.info("Getting configuration file") + recovered = zaza.model.run_on_leader(self.application, + "cat {}".format( + config_file))['Stdout'] + + # Checking conf file length, + # if file is broken it is around 250 + assert len(recovered) < 1000, ( + "Breaking mysqlrouter conf failed.") + + # verify it is in error state + for attempt in tenacity.Retrying( + reraise=True, + wait=tenacity.wait_fixed(10), + stop=tenacity.stop_after_attempt(30), + ): + with attempt: + # update status to make the status error + logging.info("Run update-status") + self.run_update_status_hooks(['keystone-mysql-router/0']) + + # get current status + unit_status = (zaza.model.get_status() + .applications + ['keystone-mysql-router']['status']) + logging.info("Status:{}".format(unit_status['status'])) + self.assertEqual(unit_status['status'], "active") + + logging.info("Getting configuration file") + recovered = zaza.model.run_on_leader(self.application, + "cat {}".format( + config_file))['Stdout'] + + # Checking conf file length, + # if file is broken it is around 250 + assert len(recovered) > 1000, ( + "Fixing mysqlrouter conf failed.") + + logging.info("Passed broken conf test.") From c0f04747620bfd2d62dd50959c49a96919b90b84 Mon Sep 17 00:00:00 2001 From: Ionut Balutoiu Date: Tue, 19 Mar 2024 00:44:16 +0200 Subject: [PATCH 02/13] CephRGWTest: Reorder test cases * Rename `test_004_migration_and_multisite_failover` to `test_100_migration_and_multisite_failover` * This will allow us to insert more multi-site tests after `test_003`, before scale-down scenario is run in `test_100_migration_and_multisite_failover`. * Rename `test_005_virtual_hosted_bucket` to `test_101_virtual_hosted_bucket`. * This was previously run after `test_004_migration_and_multisite_failover`. So, we rename the test case to `test_101` to keep the same order. Signed-off-by: Ionut Balutoiu --- zaza/openstack/charm_tests/ceph/tests.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zaza/openstack/charm_tests/ceph/tests.py b/zaza/openstack/charm_tests/ceph/tests.py index 85de53dcf..5c047c7cb 100644 --- a/zaza/openstack/charm_tests/ceph/tests.py +++ b/zaza/openstack/charm_tests/ceph/tests.py @@ -648,7 +648,7 @@ class CephRGWTest(test_utils.BaseCharmTest): This Testset is not idempotent, because we don't support scale down from multisite to singlesite (yet). Tests can be performed independently. - However, If test_004 has completed migration, retriggering the test-set + However, If test_100 has completed migration, retriggering the test-set would cause a time-out in test_003. """ @@ -1066,7 +1066,7 @@ def test_003_object_storage_and_secondary_block(self): zaza_model.block_until_unit_wl_status(self.secondary_rgw_unit, 'active') - def test_004_migration_and_multisite_failover(self): + def test_100_migration_and_multisite_failover(self): """Perform multisite migration and verify failover.""" container_name = 'zaza-container' obj_data = 'Test data from Zaza' @@ -1224,7 +1224,7 @@ def test_004_migration_and_multisite_failover(self): self.purge_bucket(self.secondary_rgw_app, 'zaza-container') self.purge_bucket(self.secondary_rgw_app, 'failover-container') - def test_005_virtual_hosted_bucket(self): + def test_101_virtual_hosted_bucket(self): """Test virtual hosted bucket.""" primary_rgw_unit = zaza_model.get_unit_from_name(self.primary_rgw_unit) if primary_rgw_unit.workload_status != "active": From e68c902fc868f6631b2c447c2a493b98a1e13887 Mon Sep 17 00:00:00 2001 From: Ionut Balutoiu Date: Wed, 24 Apr 2024 19:20:33 +0300 Subject: [PATCH 03/13] CephRGWTest: Wait for sites to be syncronised before recovery scenario If `self.promote_rgw_to_primary(self.primary_rgw_app)` is executed before sites are syncronised, the sites will not be syncronised after. Signed-off-by: Ionut Balutoiu --- zaza/openstack/charm_tests/ceph/tests.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/zaza/openstack/charm_tests/ceph/tests.py b/zaza/openstack/charm_tests/ceph/tests.py index 5c047c7cb..546aa569b 100644 --- a/zaza/openstack/charm_tests/ceph/tests.py +++ b/zaza/openstack/charm_tests/ceph/tests.py @@ -1167,6 +1167,10 @@ def test_100_migration_and_multisite_failover(self): 'Body' ].read().decode('UTF-8') + # Wait for Sites to be syncronised. + self.wait_for_status(self.primary_rgw_app, is_primary=False) + self.wait_for_status(self.secondary_rgw_app, is_primary=True) + # Recovery scenario, reset ceph-rgw as primary. self.promote_rgw_to_primary(self.primary_rgw_app) self.wait_for_status(self.primary_rgw_app, is_primary=True) From 5054a7992cd3c48eedba99dc13197931347f0f93 Mon Sep 17 00:00:00 2001 From: Ionut Balutoiu Date: Tue, 23 Apr 2024 23:04:30 +0300 Subject: [PATCH 04/13] CephRGWTest: Add integration test for multisite sync policies feature Signed-off-by: Ionut Balutoiu --- zaza/openstack/charm_tests/ceph/tests.py | 224 +++++++++++++++++++++++ 1 file changed, 224 insertions(+) diff --git a/zaza/openstack/charm_tests/ceph/tests.py b/zaza/openstack/charm_tests/ceph/tests.py index 546aa569b..d905b9a6a 100644 --- a/zaza/openstack/charm_tests/ceph/tests.py +++ b/zaza/openstack/charm_tests/ceph/tests.py @@ -1066,6 +1066,230 @@ def test_003_object_storage_and_secondary_block(self): zaza_model.block_until_unit_wl_status(self.secondary_rgw_unit, 'active') + def test_004_multisite_directional_sync_policy(self): + """Verify Multisite Directional Sync Policy.""" + # Skip multisite tests if not compatible with bundle. + if not self.multisite: + logging.info('Skipping multisite sync policy verification') + return + + container_name = 'zaza-container' + primary_obj_name = 'primary-testfile' + primary_obj_data = 'Primary test data' + secondary_directional_obj_name = 'secondary-directional-testfile' + secondary_directional_obj_data = 'Secondary directional test data' + secondary_symmetrical_obj_name = 'secondary-symmetrical-testfile' + secondary_symmetrical_obj_data = 'Secondary symmetrical test data' + + logging.info('Verifying multisite directional sync policy') + + # Set default sync policy to "allowed", which allows buckets to sync, + # but the sync is disabled by default in the zone group. Also, set the + # secondary zone sync policy flow type policy to "directional". + zaza_model.set_application_config( + self.primary_rgw_app, + { + "sync-policy-state": "allowed", + } + ) + zaza_model.set_application_config( + self.secondary_rgw_app, + { + "sync-policy-flow-type": "directional", + } + ) + zaza_model.wait_for_unit_idle(self.secondary_rgw_unit) + zaza_model.wait_for_unit_idle(self.primary_rgw_unit) + + # Setup multisite relation. + multisite_relation = zaza_model.get_relation_id( + self.primary_rgw_app, self.secondary_rgw_app, + remote_interface_name='secondary' + ) + if multisite_relation is None: + logging.info('Configuring Multisite') + self.configure_rgw_apps_for_multisite() + zaza_model.add_relation( + self.primary_rgw_app, + self.primary_rgw_app + ":primary", + self.secondary_rgw_app + ":secondary" + ) + zaza_model.block_until_unit_wl_status( + self.secondary_rgw_unit, "waiting" + ) + zaza_model.block_until_unit_wl_status( + self.primary_rgw_unit, "active" + ) + zaza_model.block_until_unit_wl_status( + self.secondary_rgw_unit, "active" + ) + zaza_model.wait_for_unit_idle(self.secondary_rgw_unit) + zaza_model.wait_for_unit_idle(self.primary_rgw_unit) + + logging.info('Waiting for Data and Metadata to Synchronize') + # NOTE: We only check the secondary zone, because the sync policy flow + # type is set to "directional" between the primary and secondary zones. + self.wait_for_status(self.secondary_rgw_app, is_primary=False) + + # Create bucket on primary RGW zone. + logging.info('Creating bucket on primary zone') + primary_endpoint = self.get_rgw_endpoint(self.primary_rgw_unit) + self.assertNotEqual(primary_endpoint, None) + + access_key, secret_key = self.get_client_keys() + primary_client = boto3.resource("s3", + verify=False, + endpoint_url=primary_endpoint, + aws_access_key_id=access_key, + aws_secret_access_key=secret_key) + primary_client.Bucket(container_name).create() + + # Enable sync on the bucket. + logging.info('Enabling sync on the bucket from the primary zone') + zaza_model.run_action_on_leader( + self.primary_rgw_app, + 'enable-buckets-sync', + action_params={ + 'buckets': container_name, + }, + raise_on_failure=True, + ) + + # Check that sync cannot be enabled using secondary Juju RGW app. + with self.assertRaises(zaza_model.ActionFailed): + zaza_model.run_action_on_leader( + self.secondary_rgw_app, + 'enable-buckets-sync', + action_params={ + 'buckets': container_name, + }, + raise_on_failure=True, + ) + + logging.info('Waiting for Data and Metadata to Synchronize') + self.wait_for_status(self.secondary_rgw_app, is_primary=False) + + # Perform IO on primary zone bucket. + logging.info('Performing IO on primary zone bucket') + primary_object = primary_client.Object( + container_name, + primary_obj_name + ) + primary_object.put(Body=primary_obj_data) + + # Verify that the object is replicated to the secondary zone. + logging.info('Verifying that the object is replicated to the ' + 'secondary zone') + secondary_endpoint = self.get_rgw_endpoint(self.secondary_rgw_unit) + self.assertNotEqual(secondary_endpoint, None) + + secondary_client = boto3.resource("s3", + verify=False, + endpoint_url=secondary_endpoint, + aws_access_key_id=access_key, + aws_secret_access_key=secret_key) + secondary_data = self.fetch_rgw_object( + secondary_client, + container_name, + primary_obj_name + ) + self.assertEqual(secondary_data, primary_obj_data) + + # Write object to the secondary zone bucket, when the sync policy + # flow type is set to "directional" between the zones. + logging.info('Writing object to the secondary zone bucket, which ' + 'should not be replicated to the primary zone') + secondary_object = secondary_client.Object( + container_name, + secondary_directional_obj_name + ) + secondary_object.put(Body=secondary_directional_obj_data) + + # Verify that the object is not replicated to the primary zone. + logging.info('Verifying that the object is not replicated to the ' + 'primary zone') + with self.assertRaises(botocore.exceptions.ClientError): + self.fetch_rgw_object( + primary_client, + container_name, + secondary_directional_obj_name + ) + + logging.info('Setting sync policy flow to "symmetrical" on the ' + 'secondary RGW zone') + zaza_model.set_application_config( + self.secondary_rgw_app, + { + "sync-policy-flow-type": "symmetrical", + } + ) + zaza_model.wait_for_unit_idle(self.secondary_rgw_unit) + zaza_model.wait_for_unit_idle(self.primary_rgw_unit) + + # Write another object to the secondary zone bucket. + logging.info('Writing another object to the secondary zone bucket.') + secondary_object = secondary_client.Object( + container_name, + secondary_symmetrical_obj_name + ) + secondary_object.put(Body=secondary_symmetrical_obj_data) + + logging.info('Waiting for Data and Metadata to Synchronize') + # NOTE: This time, we check both the primary and secondary zones, + # because the sync policy flow type is set to "symmetrical" between + # the zones. + self.wait_for_status(self.secondary_rgw_app, is_primary=False) + self.wait_for_status(self.primary_rgw_app, is_primary=True) + + # Verify that all objects are replicated to the primary zone. + logging.info('Verifying that all objects are replicated to the ' + 'primary zone (including older objects).') + test_cases = [ + { + 'obj_name': primary_obj_name, + 'obj_data': primary_obj_data, + }, + { + 'obj_name': secondary_directional_obj_name, + 'obj_data': secondary_directional_obj_data, + }, + { + 'obj_name': secondary_symmetrical_obj_name, + 'obj_data': secondary_symmetrical_obj_data, + }, + ] + for tc in test_cases: + logging.info('Verifying that object "{}" is replicated'.format( + tc['obj_name'])) + primary_data = self.fetch_rgw_object( + primary_client, + container_name, + tc['obj_name'] + ) + self.assertEqual(primary_data, tc['obj_data']) + + # Cleanup. + logging.info('Cleaning up buckets after test case') + self.purge_bucket(self.primary_rgw_app, container_name) + self.purge_bucket(self.secondary_rgw_app, container_name) + + logging.info('Waiting for Data and Metadata to Synchronize') + self.wait_for_status(self.secondary_rgw_app, is_primary=False) + self.wait_for_status(self.primary_rgw_app, is_primary=True) + + # Set multisite sync policy state to "enabled" on the primary RGW app. + # Paired with "symmetrical" sync policy flow on the secondary RGW app, + # this enables bidirectional sync between the zones (which is the + # default behaviour without multisite sync policies configured). + logging.info('Setting sync policy state to "enabled".') + zaza_model.set_application_config( + self.primary_rgw_app, + { + "sync-policy-state": "enabled", + } + ) + zaza_model.wait_for_unit_idle(self.primary_rgw_unit) + def test_100_migration_and_multisite_failover(self): """Perform multisite migration and verify failover.""" container_name = 'zaza-container' From d2b38e6279aaba4d22300ac3e0e06c19b3efebfb Mon Sep 17 00:00:00 2001 From: Ionut Balutoiu Date: Thu, 25 Apr 2024 10:41:43 +0300 Subject: [PATCH 05/13] CephRGWTest: Deduplicate code to create multi-site relation Signed-off-by: Ionut Balutoiu --- zaza/openstack/charm_tests/ceph/tests.py | 69 ++++++++++-------------- 1 file changed, 29 insertions(+), 40 deletions(-) diff --git a/zaza/openstack/charm_tests/ceph/tests.py b/zaza/openstack/charm_tests/ceph/tests.py index d905b9a6a..3fe2762a2 100644 --- a/zaza/openstack/charm_tests/ceph/tests.py +++ b/zaza/openstack/charm_tests/ceph/tests.py @@ -878,6 +878,33 @@ def configure_rgw_apps_for_multisite(self): } ) + def configure_rgw_multisite_relation(self): + """Configure multi-site relation between primary and secondary apps.""" + multisite_relation = zaza_model.get_relation_id( + self.primary_rgw_app, self.secondary_rgw_app, + remote_interface_name='secondary' + ) + if multisite_relation is None: + logging.info('Configuring Multisite') + self.configure_rgw_apps_for_multisite() + zaza_model.add_relation( + self.primary_rgw_app, + self.primary_rgw_app + ":primary", + self.secondary_rgw_app + ":secondary" + ) + zaza_model.block_until_unit_wl_status( + self.secondary_rgw_unit, "waiting" + ) + + zaza_model.block_until_unit_wl_status( + self.secondary_rgw_unit, "active" + ) + zaza_model.block_until_unit_wl_status( + self.primary_rgw_unit, "active" + ) + zaza_model.wait_for_unit_idle(self.secondary_rgw_unit) + zaza_model.wait_for_unit_idle(self.primary_rgw_unit) + def clean_rgw_multisite_config(self, app_name): """Clear Multisite Juju config values to default. @@ -1102,29 +1129,7 @@ def test_004_multisite_directional_sync_policy(self): zaza_model.wait_for_unit_idle(self.primary_rgw_unit) # Setup multisite relation. - multisite_relation = zaza_model.get_relation_id( - self.primary_rgw_app, self.secondary_rgw_app, - remote_interface_name='secondary' - ) - if multisite_relation is None: - logging.info('Configuring Multisite') - self.configure_rgw_apps_for_multisite() - zaza_model.add_relation( - self.primary_rgw_app, - self.primary_rgw_app + ":primary", - self.secondary_rgw_app + ":secondary" - ) - zaza_model.block_until_unit_wl_status( - self.secondary_rgw_unit, "waiting" - ) - zaza_model.block_until_unit_wl_status( - self.primary_rgw_unit, "active" - ) - zaza_model.block_until_unit_wl_status( - self.secondary_rgw_unit, "active" - ) - zaza_model.wait_for_unit_idle(self.secondary_rgw_unit) - zaza_model.wait_for_unit_idle(self.primary_rgw_unit) + self.configure_rgw_multisite_relation() logging.info('Waiting for Data and Metadata to Synchronize') # NOTE: We only check the secondary zone, because the sync policy flow @@ -1317,24 +1322,8 @@ def test_100_migration_and_multisite_failover(self): ).put(Body=obj_data) # If Primary/Secondary relation does not exist, add it. - if zaza_model.get_relation_id( - self.primary_rgw_app, self.secondary_rgw_app, - remote_interface_name='secondary' - ) is None: - logging.info('Configuring Multisite') - self.configure_rgw_apps_for_multisite() - zaza_model.add_relation( - self.primary_rgw_app, - self.primary_rgw_app + ":primary", - self.secondary_rgw_app + ":secondary" - ) - zaza_model.block_until_unit_wl_status( - self.secondary_rgw_unit, "waiting" - ) + self.configure_rgw_multisite_relation() - zaza_model.block_until_unit_wl_status( - self.secondary_rgw_unit, "active" - ) logging.info('Waiting for Data and Metadata to Synchronize') self.wait_for_status(self.secondary_rgw_app, is_primary=False) self.wait_for_status(self.primary_rgw_app, is_primary=True) From 2a7c49b4985c8a17482935fd488f1199a7467ab9 Mon Sep 17 00:00:00 2001 From: Myles Penner Date: Fri, 24 May 2024 15:27:39 -0700 Subject: [PATCH 06/13] Add class for keystone audit middleware testing Added general class for testing keystone audit middleware functionality in charms. Tests correct rendering of api-paste.ini file. Example usage with charm Heat: tests/tests.yaml tests: - zaza.openstack.charm_tests.audit.tests.KeystoneAuditMiddlewareTest tests_options: audit-middleware: service: heat --- zaza/openstack/charm_tests/audit/__init__.py | 20 +++++ zaza/openstack/charm_tests/audit/tests.py | 90 ++++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 zaza/openstack/charm_tests/audit/__init__.py create mode 100644 zaza/openstack/charm_tests/audit/tests.py diff --git a/zaza/openstack/charm_tests/audit/__init__.py b/zaza/openstack/charm_tests/audit/__init__.py new file mode 100644 index 000000000..0c3a4c526 --- /dev/null +++ b/zaza/openstack/charm_tests/audit/__init__.py @@ -0,0 +1,20 @@ +# Copyright 2024 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. + +""" +Keystone audit middleware. + +Collection of code for setting up and testing Keystone audit middleware +functionality. +""" diff --git a/zaza/openstack/charm_tests/audit/tests.py b/zaza/openstack/charm_tests/audit/tests.py new file mode 100644 index 000000000..146c1d45e --- /dev/null +++ b/zaza/openstack/charm_tests/audit/tests.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +# +# Copyright 2024 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. + +""" +Keystone audit middleware API logging testing. + +These methods test the +rendering of the charm's api-paste.ini file to ensure the appropriate sections +are rendered or not rendered depending on the state of the audit-middleware +configuration option. +""" + +import logging +import zaza.model +import zaza.openstack.charm_tests.test_utils as test_utils + + +class KeystoneAuditMiddlewareBaseTest(test_utils.OpenStackBaseTest): + """Base class for Keystone audit middleware tests.""" + + @classmethod + def setUpClass(cls): + """Run class setup for Keystone audit middleware tests.""" + super(KeystoneAuditMiddlewareBaseTest, cls).setUpClass() + test_config = cls.test_config['tests_options']['audit-middleware'] + cls.service_name = test_config['service'] + cls.lead_unit = zaza.model.get_lead_unit_name( + cls.service_name, + model_name=cls.model_name + ) + logging.info('Leader unit is %s', cls.lead_unit) + logging.info('Service name is %s', cls.service_name) + + def fetch_api_paste_content(self): + """Fetch content of api-paste.ini file.""" + api_paste_ini_path = f"/etc/{self.service_name}/api-paste.ini" + try: + return zaza.model.file_contents( + self.lead_unit, + api_paste_ini_path, + ) + except zaza.model.CommandRunFailed as e: + self.fail("Error fetching api-paste.ini: {}".format(str(e))) + + +class KeystoneAuditMiddlewareTest(KeystoneAuditMiddlewareBaseTest): + """Keystone audit middleware API logging feature tests.""" + + def test_101_apipaste_includes_audit_section(self): + """Test api-paste.ini renders audit section when enabled.""" + expected_content = [ + "[filter:audit]", + "paste.filter_factory = keystonemiddleware.audit:filter_factory", + f"audit_map_file = /etc/{self.service_name}/api_audit_map.conf", + f"service_name = {self.service_name}" + ] + + set_default = {'audit-middleware': False} + set_alternate = {'audit-middleware': True} + + with self.config_change(default_config=set_default, + alternate_config=set_alternate, + application_name=self.service_name): + api_paste_content = self.fetch_api_paste_content() + for line in expected_content: + self.assertIn(line, api_paste_content) + + def test_102_apipaste_excludes_audit_section(self): + """Test api_paste.ini does not render audit section when disabled.""" + section_heading = '[filter:audit]' + + if not self.config_current(self.service_name)['audit-middleware']: + api_paste_content = self.fetch_api_paste_content() + self.assertNotIn(section_heading, api_paste_content) + else: + self.fail("Failed due to audit-middleware being incorrectly set to\ + True") From df3858d0d198b9665520d2e549ae128f32e40284 Mon Sep 17 00:00:00 2001 From: Myles Penner Date: Mon, 27 May 2024 10:11:13 -0700 Subject: [PATCH 07/13] Add fix for when service and app name differ --- zaza/openstack/charm_tests/audit/tests.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/zaza/openstack/charm_tests/audit/tests.py b/zaza/openstack/charm_tests/audit/tests.py index 146c1d45e..02b05b12e 100644 --- a/zaza/openstack/charm_tests/audit/tests.py +++ b/zaza/openstack/charm_tests/audit/tests.py @@ -37,10 +37,19 @@ def setUpClass(cls): super(KeystoneAuditMiddlewareBaseTest, cls).setUpClass() test_config = cls.test_config['tests_options']['audit-middleware'] cls.service_name = test_config['service'] + + # For cases when service name and application name differ + if not test_config["application"]: + cls.application_name = cls.service_name + else: + cls.application_name = test_config['application'] + print(test_config) + print(cls.model_name) cls.lead_unit = zaza.model.get_lead_unit_name( - cls.service_name, + cls.application_name, model_name=cls.model_name ) + print(cls.lead_unit) logging.info('Leader unit is %s', cls.lead_unit) logging.info('Service name is %s', cls.service_name) @@ -73,7 +82,7 @@ def test_101_apipaste_includes_audit_section(self): with self.config_change(default_config=set_default, alternate_config=set_alternate, - application_name=self.service_name): + application_name=self.application_name): api_paste_content = self.fetch_api_paste_content() for line in expected_content: self.assertIn(line, api_paste_content) @@ -82,9 +91,8 @@ def test_102_apipaste_excludes_audit_section(self): """Test api_paste.ini does not render audit section when disabled.""" section_heading = '[filter:audit]' - if not self.config_current(self.service_name)['audit-middleware']: + if not self.config_current(self.application_name)['audit-middleware']: api_paste_content = self.fetch_api_paste_content() self.assertNotIn(section_heading, api_paste_content) else: - self.fail("Failed due to audit-middleware being incorrectly set to\ - True") + self.fail("Config option audit-middleware incorrectly set to true") From 38a7c9b0c2f9ce0ed7878e1d4dcf5d91712d7ce1 Mon Sep 17 00:00:00 2001 From: Myles Penner Date: Tue, 28 May 2024 10:57:47 -0700 Subject: [PATCH 08/13] Make corrections as per review comments --- zaza/openstack/charm_tests/audit/tests.py | 81 +++++++++++++---------- 1 file changed, 47 insertions(+), 34 deletions(-) diff --git a/zaza/openstack/charm_tests/audit/tests.py b/zaza/openstack/charm_tests/audit/tests.py index 02b05b12e..3872777fa 100644 --- a/zaza/openstack/charm_tests/audit/tests.py +++ b/zaza/openstack/charm_tests/audit/tests.py @@ -17,65 +17,77 @@ """ Keystone audit middleware API logging testing. -These methods test the -rendering of the charm's api-paste.ini file to ensure the appropriate sections -are rendered or not rendered depending on the state of the audit-middleware -configuration option. +These methods test the rendering of the charm api-paste.ini file to +ensure the appropriate sections are rendered or not rendered depending +on the state of the audit-middleware configuration option. """ +import textwrap import logging import zaza.model import zaza.openstack.charm_tests.test_utils as test_utils -class KeystoneAuditMiddlewareBaseTest(test_utils.OpenStackBaseTest): - """Base class for Keystone audit middleware tests.""" +class KeystoneAuditMiddlewareTest(test_utils.OpenStackBaseTest): + """Keystone audit middleware test class.""" @classmethod def setUpClass(cls): """Run class setup for Keystone audit middleware tests.""" - super(KeystoneAuditMiddlewareBaseTest, cls).setUpClass() + super(KeystoneAuditMiddlewareTest, cls).setUpClass() test_config = cls.test_config['tests_options']['audit-middleware'] cls.service_name = test_config['service'] - # For cases when service name and application name differ - if not test_config["application"]: - cls.application_name = cls.service_name - else: + if test_config['application']: cls.application_name = test_config['application'] - print(test_config) - print(cls.model_name) - cls.lead_unit = zaza.model.get_lead_unit_name( + else: + cls.application_name = cls.service_name + logging.info('Application name not set, using service name:\ + %s', cls.service_name) + + cls.initial_audit_middleware = zaza.model.get_application_config( + cls.application_name)['audit-middleware']['value'] + + @classmethod + def tearDownClass(cls): + """Restore the audit-middleware configuration to its original state.""" + super(KeystoneAuditMiddlewareTest, cls).tearDownClass() + logging.info("Running teardown on %s" % cls.application_name) + zaza.model.set_application_config( cls.application_name, + {'audit-middleware': str(cls.initial_audit_middleware)}, + model_name=cls.model_name + ) + zaza.model.wait_for_application_states( + states={cls.application_name: { + 'workload-status': 'active', + 'workload-status-message': 'Unit is ready'}}, model_name=cls.model_name ) - print(cls.lead_unit) - logging.info('Leader unit is %s', cls.lead_unit) - logging.info('Service name is %s', cls.service_name) def fetch_api_paste_content(self): """Fetch content of api-paste.ini file.""" api_paste_ini_path = f"/etc/{self.service_name}/api-paste.ini" + lead_unit = zaza.model.get_lead_unit_name( + self.application_name, + model_name=self.model_name + ) try: return zaza.model.file_contents( - self.lead_unit, + lead_unit, api_paste_ini_path, ) except zaza.model.CommandRunFailed as e: - self.fail("Error fetching api-paste.ini: {}".format(str(e))) - - -class KeystoneAuditMiddlewareTest(KeystoneAuditMiddlewareBaseTest): - """Keystone audit middleware API logging feature tests.""" + self.fail("Error fetching api-paste.ini: %s" % e) def test_101_apipaste_includes_audit_section(self): """Test api-paste.ini renders audit section when enabled.""" - expected_content = [ - "[filter:audit]", - "paste.filter_factory = keystonemiddleware.audit:filter_factory", - f"audit_map_file = /etc/{self.service_name}/api_audit_map.conf", - f"service_name = {self.service_name}" - ] + expected_content = textwrap.dedent(f"""\ + [filter:audit] + paste.filter_factory = keystonemiddleware.audit:filter_factory + audit_map_file = /etc/{self.service_name}/api_audit_map.conf + service_name = {self.service_name} + """) set_default = {'audit-middleware': False} set_alternate = {'audit-middleware': True} @@ -84,15 +96,16 @@ def test_101_apipaste_includes_audit_section(self): alternate_config=set_alternate, application_name=self.application_name): api_paste_content = self.fetch_api_paste_content() - for line in expected_content: - self.assertIn(line, api_paste_content) + self.assertIn(expected_content, api_paste_content) def test_102_apipaste_excludes_audit_section(self): """Test api_paste.ini does not render audit section when disabled.""" section_heading = '[filter:audit]' + set_default = {'audit-middleware': True} + set_alternate = {'audit-middleware': False} - if not self.config_current(self.application_name)['audit-middleware']: + with self.config_change(default_config=set_default, + alternate_config=set_alternate, + application_name=self.application_name): api_paste_content = self.fetch_api_paste_content() self.assertNotIn(section_heading, api_paste_content) - else: - self.fail("Config option audit-middleware incorrectly set to true") From 3af8d41067facd258426f0625c7ca7eee26b4d49 Mon Sep 17 00:00:00 2001 From: Myles Penner Date: Wed, 29 May 2024 08:38:49 -0700 Subject: [PATCH 09/13] Fix dictionary lookup method --- zaza/openstack/charm_tests/audit/tests.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/zaza/openstack/charm_tests/audit/tests.py b/zaza/openstack/charm_tests/audit/tests.py index 3872777fa..8b8f5f62e 100644 --- a/zaza/openstack/charm_tests/audit/tests.py +++ b/zaza/openstack/charm_tests/audit/tests.py @@ -38,12 +38,8 @@ def setUpClass(cls): test_config = cls.test_config['tests_options']['audit-middleware'] cls.service_name = test_config['service'] - if test_config['application']: - cls.application_name = test_config['application'] - else: - cls.application_name = cls.service_name - logging.info('Application name not set, using service name:\ - %s', cls.service_name) + cls.application_name = test_config.get('application', cls.service_name) + logging.info('Using application name: %s', cls.application_name) cls.initial_audit_middleware = zaza.model.get_application_config( cls.application_name)['audit-middleware']['value'] From 2ddde4307e6a14f310e3835a410dedf94838d481 Mon Sep 17 00:00:00 2001 From: Myles Penner Date: Wed, 29 May 2024 15:11:20 -0700 Subject: [PATCH 10/13] Remove redundant cinder tests --- zaza/openstack/charm_tests/cinder/tests.py | 40 ---------------------- 1 file changed, 40 deletions(-) diff --git a/zaza/openstack/charm_tests/cinder/tests.py b/zaza/openstack/charm_tests/cinder/tests.py index 7668127dc..421d30631 100644 --- a/zaza/openstack/charm_tests/cinder/tests.py +++ b/zaza/openstack/charm_tests/cinder/tests.py @@ -305,46 +305,6 @@ def verify(stdin, stdout, stderr): 'lsblk -sn -o SIZE /dev/vdb', privkey=privkey, verify=verify) - def test_300_apipaste_includes_audit_section(self): - """Test api-paste.ini renders audit section when enabled.""" - service_name = 'cinder' - api_paste_ini_path = f"/etc/{service_name}/api-paste.ini" - expected_content = [ - "[filter:audit]", - "paste.filter_factory = keystonemiddleware.audit:filter_factory", - f"audit_map_file = /etc/{service_name}/api_audit_map.conf", - f"service_name = {service_name}" - ] - - set_default = {'audit-middleware': False} - set_alternate = {'audit-middleware': True} - - with self.config_change(set_default, set_alternate): - try: - api_paste_content = zaza.model.file_contents( - self.lead_unit, - api_paste_ini_path, - ) - except Exception as e: - self.fail("Error fetching api-paste.ini: {}".format(str(e))) - for line in expected_content: - self.assertIn(line, api_paste_content) - - def test_301_apipaste_excludes_audit_section(self): - """Test api_paste.ini does not render audit section when disabled.""" - service_name = 'cinder' - section_heading = '[filter:audit]' - api_paste_ini_path = f"/etc/{service_name}/api-paste.ini" - - try: - api_paste_content = zaza.model.file_contents( - self.lead_unit, - api_paste_ini_path - ) - except Exception as e: - self.fail("Error fetching api-paste.ini: {}".format(str(e))) - self.assertNotIn(section_heading, api_paste_content) - @property def services(self): """Return a list services for the selected OpenStack release.""" From d83e01f86270dd7005c0fab26a4e387977726701 Mon Sep 17 00:00:00 2001 From: Alex Kavanagh Date: Thu, 30 May 2024 10:56:37 +0100 Subject: [PATCH 11/13] Remove designate-agent for caracal+ testing The designate-agent service was removed at caracal. This patch removes the designate-agent from the list of services that designate supports so that the pause/resume testing works appropriately. --- zaza/openstack/charm_tests/designate/tests.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/zaza/openstack/charm_tests/designate/tests.py b/zaza/openstack/charm_tests/designate/tests.py index ed68a8c93..2203c3a4d 100644 --- a/zaza/openstack/charm_tests/designate/tests.py +++ b/zaza/openstack/charm_tests/designate/tests.py @@ -44,8 +44,15 @@ def setUpClass(cls, application_name=None, model_alias=None): model_alias = model_alias or "" super(BaseDesignateTest, cls).setUpClass(application_name, model_alias) os_release = openstack_utils.get_os_release + current_release = os_release() - if os_release() >= os_release('bionic_rocky'): + if current_release >= os_release('jammy_caracal'): + cls.designate_svcs = [ + 'designate-api', 'designate-central', + 'designate-mdns', 'designate-worker', 'designate-sink', + 'designate-producer', + ] + elif current_release >= os_release('bionic_rocky'): cls.designate_svcs = [ 'designate-agent', 'designate-api', 'designate-central', 'designate-mdns', 'designate-worker', 'designate-sink', From 21b51a467fd504ad8427a3e423d654399ed4e5b9 Mon Sep 17 00:00:00 2001 From: Utkarsh Bhatt Date: Wed, 12 Jun 2024 18:35:00 +0530 Subject: [PATCH 12/13] Fixes endpoint discovery The zaza test used the default endpoint as exposed by the unit to perform object IO, however, SSL is configured for PUBLIC,INTERNAL, and ADMIN bindings. Signed-off-by: Utkarsh Bhatt --- zaza/openstack/charm_tests/ceph/tests.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/zaza/openstack/charm_tests/ceph/tests.py b/zaza/openstack/charm_tests/ceph/tests.py index 9e6f327b1..a45b16e09 100644 --- a/zaza/openstack/charm_tests/ceph/tests.py +++ b/zaza/openstack/charm_tests/ceph/tests.py @@ -840,13 +840,12 @@ def get_rgw_endpoint(self, unit_name: str): :param unit_name: Unit name for which RGW endpoint is required. :type unit_name: str """ - unit = zaza_model.get_unit_from_name(unit_name) - unit_address = zaza_model.get_unit_public_address( - unit, - self.model_name - ) + # Get address "public" network binding. + unit_address = zaza_model.run_on_unit( + unit_name, "network-get public --bind-address" + ).get('Stdout', '').strip() - logging.debug("Unit: {}, Endpoint: {}".format(unit_name, unit_address)) + logging.info("Unit: {}, Endpoint: {}".format(unit_name, unit_address)) if unit_address is None: return None # Evaluate port From 61ac4bc8ded11b4a4cf2c233f170780238b97450 Mon Sep 17 00:00:00 2001 From: Alex Kavanagh <567675+ajkavanagh@users.noreply.github.com> Date: Tue, 18 Jun 2024 18:40:00 +0100 Subject: [PATCH 13/13] Revert "Adding fixing broken configuration test for mysql-router" --- zaza/openstack/charm_tests/mysql/tests.py | 63 ----------------------- 1 file changed, 63 deletions(-) diff --git a/zaza/openstack/charm_tests/mysql/tests.py b/zaza/openstack/charm_tests/mysql/tests.py index 7c574a919..710195541 100644 --- a/zaza/openstack/charm_tests/mysql/tests.py +++ b/zaza/openstack/charm_tests/mysql/tests.py @@ -1145,66 +1145,3 @@ def test_910_restart_on_config_change(self): {}, {}, self.services) logging.info("Passed restart on changed test.") - - def test_920_mysqlrouter_conf_broken(self): - """Checking conf broken case. - - Run the bootstrap when conf is broken - """ - # application_name on test is keystone-mysql-router - # using self.conf_file introduces error. - # instead of changing current self.conf_file, - # define one (it could introduce another issue) - config_file = ( - "/var/lib/mysql/{}/mysqlrouter.conf" - .format(self.application_name)) - - logging.info("Starting broken conf test") - - # put empty string to conf_file and make it wrong - logging.info("Breaking configuration file") - zaza.model.run_on_leader(self.application, - "echo '[DEFAULT]\n \ - [metadata_cache:[\\w$]+$] \ - ' > {}".format( - config_file)) - - logging.info("Getting configuration file") - recovered = zaza.model.run_on_leader(self.application, - "cat {}".format( - config_file))['Stdout'] - - # Checking conf file length, - # if file is broken it is around 250 - assert len(recovered) < 1000, ( - "Breaking mysqlrouter conf failed.") - - # verify it is in error state - for attempt in tenacity.Retrying( - reraise=True, - wait=tenacity.wait_fixed(10), - stop=tenacity.stop_after_attempt(30), - ): - with attempt: - # update status to make the status error - logging.info("Run update-status") - self.run_update_status_hooks(['keystone-mysql-router/0']) - - # get current status - unit_status = (zaza.model.get_status() - .applications - ['keystone-mysql-router']['status']) - logging.info("Status:{}".format(unit_status['status'])) - self.assertEqual(unit_status['status'], "active") - - logging.info("Getting configuration file") - recovered = zaza.model.run_on_leader(self.application, - "cat {}".format( - config_file))['Stdout'] - - # Checking conf file length, - # if file is broken it is around 250 - assert len(recovered) > 1000, ( - "Fixing mysqlrouter conf failed.") - - logging.info("Passed broken conf test.")