diff --git a/unit_tests/charm_tests/test_magnum.py b/unit_tests/charm_tests/test_magnum.py new file mode 100644 index 0000000..77c8c5e --- /dev/null +++ b/unit_tests/charm_tests/test_magnum.py @@ -0,0 +1,72 @@ +# Copyright 2023 Canonical Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import unittest + +from unittest import mock + +import zaza.openstack.charm_tests.magnum.setup as magnum_setup + + +class TestMagnumSetup(unittest.TestCase): + + @mock.patch.object(magnum_setup.openstack_utils, + 'get_current_os_release_pair') + def test_get_fedora_coreos_image_url(self, get_current_os_release_pair): + get_current_os_release_pair.return_value = 'focal_ussuri' + self.assertEqual(magnum_setup.FEDORA_COREOS_IMAGE['ussuri'], + magnum_setup.get_fedora_coreos_image_url()) + + self.assertEqual(magnum_setup.FEDORA_COREOS_IMAGE['xena'], + magnum_setup.get_fedora_coreos_image_url('xena')) + self.assertEqual(magnum_setup.DEFAULT_FEDORA_COREOS_IMAGE_URL, + magnum_setup.get_fedora_coreos_image_url('foobar')) + + @mock.patch.object(magnum_setup, 'get_fedora_coreos_image_url') + @mock.patch.object(magnum_setup.openstack_utils, + 'get_overcloud_keystone_session') + @mock.patch.object(magnum_setup.openstack_utils, + 'get_glance_session_client') + @mock.patch.object(magnum_setup.openstack_utils, + 'create_image') + def test_add_image(self, create_image, get_glance_session_client, + get_overcloud_keystone_session, + get_fedora_coreos_image_url): + image_url = 'http://example.com/image.qcow2' + with mock.patch.dict(os.environ, + {'TEST_MAGNUM_QCOW2_IMAGE_URL': + image_url}, + clear=True) as environ: # noqa:F841 + magnum_setup.add_image() + create_image.assert_called_with( + get_glance_session_client(), + image_url, + magnum_setup.IMAGE_NAME, + properties={'os_distro': magnum_setup.IMAGE_NAME} + ) + get_fedora_coreos_image_url.assert_not_called() + + image_url = 'http://example.com/fedora-coreos.qcow2' + get_fedora_coreos_image_url.return_value = image_url + with mock.patch.dict(os.environ, {}, + clear=True) as environ: # noqa:F841 + magnum_setup.add_image() + create_image.assert_called_with( + get_glance_session_client(), + image_url, + magnum_setup.IMAGE_NAME, + properties={'os_distro': magnum_setup.IMAGE_NAME} + ) + get_fedora_coreos_image_url.assert_called() diff --git a/unit_tests/charm_tests/test_tempest.py b/unit_tests/charm_tests/test_tempest.py index ac6962e..ddbcdeb 100644 --- a/unit_tests/charm_tests/test_tempest.py +++ b/unit_tests/charm_tests/test_tempest.py @@ -12,9 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -import mock +import os import unittest +from unittest import mock + import zaza.openstack.charm_tests.tempest.utils as tempest_utils @@ -61,3 +63,21 @@ class TestTempestUtils(unittest.TestCase): ('Environment variables [TEST_CIDR_EXT, TEST_FIP_RANGE] must ' 'all be set to run this test'), str(context.exception)) + + @mock.patch.object(tempest_utils, '_add_image_id') + def test_add_magnum_config(self, _add_image_id): + ctxt = {} + keystone_session = mock.MagicMock() + with mock.patch.dict(os.environ, + {'TEST_REGISTRY_PREFIX': '1.2.3.4:5000'}, + clear=True) as environ: # noqa:F841 + tempest_utils._add_magnum_config(ctxt, keystone_session) + self.assertIn('test_registry_prefix', ctxt) + self.assertEqual(ctxt['test_registry_prefix'], '1.2.3.4:5000') + + _add_image_id.assert_called() + ctxt = {} + with mock.patch.dict(os.environ, {}, + clear=True) as environ: # noqa:F841 + tempest_utils._add_magnum_config(ctxt, keystone_session) + self.assertNotIn('test_registry_prefix', ctxt) diff --git a/unit_tests/charm_tests/test_utils.py b/unit_tests/charm_tests/test_utils.py index c1e8166..f810a17 100644 --- a/unit_tests/charm_tests/test_utils.py +++ b/unit_tests/charm_tests/test_utils.py @@ -42,8 +42,8 @@ class TestBaseCharmTest(ut_utils.BaseTestCase): return self.get_my_tests_options('aKey', 'aDefault') f = FakeTest() - self.assertEquals(f.method({}), 'aDefault') - self.assertEquals(f.method({ + self.assertEqual(f.method({}), 'aDefault') + self.assertEqual(f.method({ 'tests_options': { 'unit_tests.charm_tests.test_utils.' 'FakeTest.method.aKey': 'aValue', diff --git a/unit_tests/utilities/test_zaza_utilities_ceph.py b/unit_tests/utilities/test_zaza_utilities_ceph.py index e6ac337..ff2fe9b 100644 --- a/unit_tests/utilities/test_zaza_utilities_ceph.py +++ b/unit_tests/utilities/test_zaza_utilities_ceph.py @@ -127,7 +127,7 @@ class TestCephUtils(ut_utils.BaseTestCase): '{"op": "create-pool", "name": "cinder-ceph", ' '"compression-mode": "aggressive"}]}'), } - self.assertEquals( + self.assertEqual( ceph_utils.get_pools_from_broker_req( 'anApplication', 'aModelName'), ['cinder-ceph']) diff --git a/unit_tests/utilities/test_zaza_utilities_openstack.py b/unit_tests/utilities/test_zaza_utilities_openstack.py index 476152f..469d997 100644 --- a/unit_tests/utilities/test_zaza_utilities_openstack.py +++ b/unit_tests/utilities/test_zaza_utilities_openstack.py @@ -1322,7 +1322,7 @@ class TestOpenStackUtils(ut_utils.BaseTestCase): self.patch_object(openstack_utils.juju_utils, 'get_machine_uuids_for_application') self.get_machine_uuids_for_application.return_value = 'ret' - self.assertEquals(openstack_utils.get_gateway_uuids(), 'ret') + self.assertEqual(openstack_utils.get_gateway_uuids(), 'ret') self.get_machine_uuids_for_application.assert_called_once_with( 'neutron-gateway') @@ -1330,7 +1330,7 @@ class TestOpenStackUtils(ut_utils.BaseTestCase): self.patch_object(openstack_utils.juju_utils, 'get_machine_uuids_for_application') self.get_machine_uuids_for_application.return_value = 'ret' - self.assertEquals(openstack_utils.get_ovs_uuids(), 'ret') + self.assertEqual(openstack_utils.get_ovs_uuids(), 'ret') self.get_machine_uuids_for_application.assert_called_once_with( 'neutron-openvswitch') @@ -1338,8 +1338,8 @@ class TestOpenStackUtils(ut_utils.BaseTestCase): self.patch_object(openstack_utils.juju_utils, 'get_machine_uuids_for_application') self.get_machine_uuids_for_application.return_value = ['ret'] - self.assertEquals(list(openstack_utils.get_ovn_uuids()), - ['ret', 'ret']) + self.assertEqual(list(openstack_utils.get_ovn_uuids()), + ['ret', 'ret']) self.get_machine_uuids_for_application.assert_has_calls([ mock.call('ovn-chassis'), mock.call('ovn-dedicated-chassis'), @@ -1387,7 +1387,7 @@ class TestOpenStackUtils(ut_utils.BaseTestCase): with self.assertRaises(RuntimeError): openstack_utils.get_charm_networking_data() self.ngw_present.return_value = True - self.assertEquals( + self.assertEqual( openstack_utils.get_charm_networking_data(), openstack_utils.CharmedOpenStackNetworkingData( openstack_utils.OpenStackNetworkingTopology.ML2_OVS, @@ -1396,7 +1396,7 @@ class TestOpenStackUtils(ut_utils.BaseTestCase): 'data-port', {})) self.dvr_enabled.return_value = True - self.assertEquals( + self.assertEqual( openstack_utils.get_charm_networking_data(), openstack_utils.CharmedOpenStackNetworkingData( openstack_utils.OpenStackNetworkingTopology.ML2_OVS_DVR, @@ -1405,7 +1405,7 @@ class TestOpenStackUtils(ut_utils.BaseTestCase): 'data-port', {})) self.ngw_present.return_value = False - self.assertEquals( + self.assertEqual( openstack_utils.get_charm_networking_data(), openstack_utils.CharmedOpenStackNetworkingData( openstack_utils.OpenStackNetworkingTopology.ML2_OVS_DVR_SNAT, @@ -1415,7 +1415,7 @@ class TestOpenStackUtils(ut_utils.BaseTestCase): {})) self.dvr_enabled.return_value = False self.ovn_present.return_value = True - self.assertEquals( + self.assertEqual( openstack_utils.get_charm_networking_data(), openstack_utils.CharmedOpenStackNetworkingData( openstack_utils.OpenStackNetworkingTopology.ML2_OVN, @@ -1424,7 +1424,7 @@ class TestOpenStackUtils(ut_utils.BaseTestCase): 'bridge-interface-mappings', {'ovn-bridge-mappings': 'physnet1:br-ex'})) self.get_application.side_effect = None - self.assertEquals( + self.assertEqual( openstack_utils.get_charm_networking_data(), openstack_utils.CharmedOpenStackNetworkingData( openstack_utils.OpenStackNetworkingTopology.ML2_OVN, diff --git a/zaza/openstack/charm_tests/ceph/fs/tests.py b/zaza/openstack/charm_tests/ceph/fs/tests.py index fb3c061..ef01cbb 100644 --- a/zaza/openstack/charm_tests/ceph/fs/tests.py +++ b/zaza/openstack/charm_tests/ceph/fs/tests.py @@ -147,7 +147,7 @@ class CephFSTests(unittest.TestCase): """ model.set_application_config('ceph-fs', mds_config) results = _get_conf() - self.assertEquals( + self.assertEqual( results['mds_cache_memory_limit'], mds_config['mds-cache-memory-limit']) self.assertAlmostEqual( diff --git a/zaza/openstack/charm_tests/ceph/tests.py b/zaza/openstack/charm_tests/ceph/tests.py index 16136f6..0d480c2 100644 --- a/zaza/openstack/charm_tests/ceph/tests.py +++ b/zaza/openstack/charm_tests/ceph/tests.py @@ -1323,12 +1323,12 @@ class BlueStoreCompressionCharmOperation(test_utils.BaseCharmTest): if pd['pool_name'] == pool: if 'options' in expected_properties: for k, v in expected_properties['options'].items(): - self.assertEquals(pd['options'][k], v) + self.assertEqual(pd['options'][k], v) log_func("['options']['{}'] == {}".format(k, v)) for k, v in expected_properties.items(): if k == 'options': continue - self.assertEquals(pd[k], v) + self.assertEqual(pd[k], v) log_func("{} == {}".format(k, v)) def test_configure_compression(self): diff --git a/zaza/openstack/charm_tests/magnum/setup.py b/zaza/openstack/charm_tests/magnum/setup.py index 4255f8c..3baa72a 100644 --- a/zaza/openstack/charm_tests/magnum/setup.py +++ b/zaza/openstack/charm_tests/magnum/setup.py @@ -23,8 +23,30 @@ import tenacity import zaza.model import zaza.openstack.utilities.openstack as openstack_utils +TEST_SWIFT_IP = os.environ.get('TEST_SWIFT_IP') IMAGE_NAME = 'fedora-coreos' +# https://docs.openstack.org/magnum/latest/user/index.html#supported-versions +# List of published image available at: +# https://builds.coreos.fedoraproject.org/browser?stream=stable&arch=x86_64 +# +# Source images: +# https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/35.20220424.3.0/x86_64/fedora-coreos-35.20220424.3.0-openstack.x86_64.qcow2.xz # noqa +# https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/31.20200517.3.0/x86_64/fedora-coreos-31.20200517.3.0-openstack.x86_64.qcow2.xz # noqa +# https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/32.20201104.3.0/x86_64/fedora-coreos-32.20201104.3.0-openstack.x86_64.qcow2.xz # noqa +FEDORA_COREOS_31 = 'http://%s/magnum/images/fedora-coreos-31.qcow2' % TEST_SWIFT_IP # noqa +FEDORA_COREOS_32 = 'http://%s/magnum/images/fedora-coreos-32.qcow2' % TEST_SWIFT_IP # noqa +FEDORA_COREOS_35 = 'http://%s/magnum/images/fedora-coreos-35.qcow2' % TEST_SWIFT_IP # noqa +DEFAULT_FEDORA_COREOS_IMAGE_URL = FEDORA_COREOS_35 +FEDORA_COREOS_IMAGE = { + 'ussuri': FEDORA_COREOS_32, + 'victoria': FEDORA_COREOS_31, + 'wallaby': FEDORA_COREOS_31, + 'xena': FEDORA_COREOS_31, + 'yoga': FEDORA_COREOS_35, + 'zed': FEDORA_COREOS_35, +} + def domain_setup(application_name='magnum'): """Run required action for a working Magnum application.""" @@ -37,6 +59,25 @@ def domain_setup(application_name='magnum'): "Unit is ready") +def get_fedora_coreos_image_url(os_release: str = None) -> str: + """Get Fedora CoreOS image url compatible with the Magnum release deployed. + + :param os_release: OpenStack release codename (e.g. ussuri). + :returns: url where the image can be GET. + """ + if not os_release: + pair = openstack_utils.get_current_os_release_pair('keystone') + os_release = pair.split('_', 1)[1] + if os_release in FEDORA_COREOS_IMAGE: + return FEDORA_COREOS_IMAGE[os_release] + else: + logging.warning( + 'No image found for OpenStack %s, using default image %s', + os_release, DEFAULT_FEDORA_COREOS_IMAGE_URL + ) + return DEFAULT_FEDORA_COREOS_IMAGE_URL + + def add_image(image_url=None): """Upload Magnum image. @@ -47,9 +88,8 @@ def add_image(image_url=None): :type image_url: str """ image_url = image_url or os.environ.get( - 'TEST_MAGNUM_QCOW2_IMAGE_URL', None) - if image_url is None: - raise ValueError("Missing image_url") + 'TEST_MAGNUM_QCOW2_IMAGE_URL', None) or get_fedora_coreos_image_url() + for attempt in tenacity.Retrying( stop=tenacity.stop_after_attempt(3), reraise=True): diff --git a/zaza/openstack/charm_tests/magnum/upload_fedora_coreos_images.sh b/zaza/openstack/charm_tests/magnum/upload_fedora_coreos_images.sh new file mode 100755 index 0000000..604d419 --- /dev/null +++ b/zaza/openstack/charm_tests/magnum/upload_fedora_coreos_images.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +wget -O - https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/31.20200517.3.0/x86_64/fedora-coreos-31.20200517.3.0-openstack.x86_64.qcow2.xz \ + | xzcat > ./fedora-coreos-31.qcow2 + +openstack object create --name images/fedora-coreos-31.qcow2 magnum ./fedora-coreos-31.qcow2 + +wget -O - https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/32.20201104.3.0/x86_64/fedora-coreos-32.20201104.3.0-openstack.x86_64.qcow2.xz \ + | xzcat > ./fedora-coreos-32.qcow2 + +openstack object create --name images/fedora-coreos-32.qcow2 magnum ./fedora-coreos-32.qcow2 + +wget -O - https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/35.20220424.3.0/x86_64/fedora-coreos-35.20220424.3.0-openstack.x86_64.qcow2.xz \ + | xzcat > ./fedora-coreos-35.qcow2 + +openstack object create --name images/fedora-coreos-35.qcow2 magnum ./fedora-coreos-35.qcow2 diff --git a/zaza/openstack/charm_tests/tempest/templates/tempest_v3.j2 b/zaza/openstack/charm_tests/tempest/templates/tempest_v3.j2 index c7ba8a5..06932d9 100644 --- a/zaza/openstack/charm_tests/tempest/templates/tempest_v3.j2 +++ b/zaza/openstack/charm_tests/tempest/templates/tempest_v3.j2 @@ -122,5 +122,19 @@ test_server_path = {{ workspace_path }}/test_server.bin provider = amphora {% endif %} +{%- if 'magnum' in enabled_services %} +[magnum] +nic_id = {{ ext_net }} +image_id = {{ fedora_coreos_id }} +master_flavor_id = {{ flavor_ref }} +flavor_id = {{ flavor_ref }} +dns_nameserver = {{ test_name_server }} +network_driver = flannel +{%- if test_registry_prefix %} +labels = container_infra_prefix:{{ test_registry_prefix }} +insecure_registry = {{ test_registry_prefix }} +{%- endif %} +{%- endif %} + [dns] nameservers = {{ test_name_server }} diff --git a/zaza/openstack/charm_tests/tempest/utils.py b/zaza/openstack/charm_tests/tempest/utils.py index d1ef96c..a811bdb 100644 --- a/zaza/openstack/charm_tests/tempest/utils.py +++ b/zaza/openstack/charm_tests/tempest/utils.py @@ -15,6 +15,7 @@ """Utility code for working with tempest workspaces.""" import jinja2 +import logging import os from pathlib import Path import shutil @@ -28,6 +29,7 @@ import zaza.utilities.deployment_env as deployment_env import zaza.openstack.utilities.juju as juju_utils import zaza.openstack.utilities.openstack as openstack_utils import zaza.openstack.charm_tests.glance.setup as glance_setup +import zaza.openstack.charm_tests.magnum.setup as magnum_setup SETUP_ENV_VARS = { 'neutron': ['TEST_GATEWAY', 'TEST_CIDR_EXT', 'TEST_FIP_RANGE', @@ -156,7 +158,9 @@ def _get_tempest_context(workspace_path, missing_fatal=True): 'neutron': _add_neutron_config, 'glance': _add_glance_config, 'cinder': _add_cinder_config, - 'keystone': _add_keystone_config} + 'keystone': _add_keystone_config, + 'magnum': _add_magnum_config, + } ctxt['enabled_services'] = _get_service_list(keystone_session) if set(['cinderv2', 'cinderv3']) \ .intersection(set(ctxt['enabled_services'])): @@ -284,16 +288,39 @@ def _add_glance_config(ctxt, keystone_session, missing_fatal=True): :returns: None :rtype: None """ + _add_image_id(ctxt, keystone_session, + glance_setup.CIRROS_IMAGE_NAME, 'image_id', + missing_fatal) + _add_image_id(ctxt, keystone_session, + glance_setup.CIRROS_ALT_IMAGE_NAME, 'image_alt_id', + missing_fatal) + + +def _add_image_id(ctxt, keystone_session, image_name, ctxt_key, + missing_fatal=True): + """Add an image id to the context. + + :param ctxt: Context dictionary + :type ctxt: dict + :param keystone_session: keystoneauth1.session.Session object + :type: keystoneauth1.session.Session + :param image_name: Image's name to search in glance. + :type image_name: str + :param ctxt_key: key to use when adding the image id to the context. + :type ctxt_key: str + :param missing_fatal: Raise an exception if a resource is missing + :type missing_fatal: bool + """ 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) + image = openstack_utils.get_images_by_name(glance_client, image_name) if image: - ctxt['image_id'] = image[0].id - if image_alt: - ctxt['image_alt_id'] = image_alt[0].id + ctxt[ctxt_key] = image[0].id + else: + msg = 'Image %s not found' % image_name + logging.warning(msg) + if missing_fatal: + raise Exception(msg) def _add_cinder_config(ctxt, keystone_session, missing_fatal=True): @@ -360,6 +387,27 @@ def _add_octavia_config(ctxt, missing_fatal=True): ]) +def _add_magnum_config(ctxt, keystone_session, missing_fatal=True): + """Add magnum config to context. + + :param ctxt: Context dictionary + :type ctxt: dict + :param missing_fatal: Raise an exception if a resource is missing + :type missing_fatal: bool + :returns: None + :rtype: None + :raises: subprocess.CalledProcessError + """ + _add_image_id(ctxt, keystone_session, + magnum_setup.IMAGE_NAME, 'fedora_coreos_id', + missing_fatal) + test_registry_prefix = os.environ.get('TEST_REGISTRY_PREFIX') + if test_registry_prefix: + ctxt['test_registry_prefix'] = test_registry_prefix + else: + logging.warning('Environment variable TEST_REGISTRY_PREFIX not found') + + def _add_environment_var_config(ctxt, services, missing_fatal=True): """Add environment variable config to context.