From 2a7c49b4985c8a17482935fd488f1199a7467ab9 Mon Sep 17 00:00:00 2001 From: Myles Penner Date: Fri, 24 May 2024 15:27:39 -0700 Subject: [PATCH 1/5] 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 2/5] 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 3/5] 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 4/5] 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 5/5] 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."""