diff --git a/doc/source/contributor/testing.rst b/doc/source/contributor/testing.rst index c3261df16..275a418b2 100644 --- a/doc/source/contributor/testing.rst +++ b/doc/source/contributor/testing.rst @@ -68,8 +68,9 @@ configured for the one cloud. These accounts are: configurable via the ``OPENSTACKSDK_DEMO_CLOUD_ALT`` environment variable In addition, you must indicate the names of the flavor and image that should be -used for tests. These can be configured via ``functional.flavor_name`` and -``functional.image_name`` settings in the ``clouds.yaml`` file. +used for tests. These can be configured via ``OPENSTACKSDK_FLAVOR`` and +``OPENSTACKSDK_IMAGE`` environment variables or ``functional.flavor_name`` and +``functional.image_name`` settings in the ``clouds.yaml`` file, respectively. Finally, you can configure the timeout for tests using the ``OPENSTACKSDK_FUNC_TEST_TIMEOUT`` environment variable (defaults to 300 diff --git a/openstack/tests/functional/base.py b/openstack/tests/functional/base.py index 19dc66d80..1e20f3ee4 100644 --- a/openstack/tests/functional/base.py +++ b/openstack/tests/functional/base.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +import operator import os from keystoneauth1 import discover @@ -28,9 +29,8 @@ TEST_CLOUD_NAME = os.getenv('OS_CLOUD', 'devstack-admin') TEST_CLOUD_REGION = openstack.config.get_cloud_region(cloud=TEST_CLOUD_NAME) -def _get_resource_value(resource_key, default): - return TEST_CONFIG.get_extra_config( - 'functional').get(resource_key, default) +def _get_resource_value(resource_key): + return TEST_CONFIG.get_extra_config('functional').get(resource_key) def _disable_keep_alive(conn): @@ -38,10 +38,6 @@ def _disable_keep_alive(conn): sess.keep_alive = False -IMAGE_NAME = _get_resource_value('image_name', 'cirros-0.4.0-x86_64-disk') -FLAVOR_NAME = _get_resource_value('flavor_name', 'm1.small') - - class BaseFunctionalTest(base.TestCase): _wait_for_timeout_key = '' @@ -52,10 +48,12 @@ class BaseFunctionalTest(base.TestCase): _disable_keep_alive(self.conn) self._demo_name = os.environ.get('OPENSTACKSDK_DEMO_CLOUD', 'devstack') - self._demo_name_alt = os.environ.get('OPENSTACKSDK_DEMO_CLOUD_ALT', - 'devstack-alt') + self._demo_name_alt = os.environ.get( + 'OPENSTACKSDK_DEMO_CLOUD_ALT', 'devstack-alt', + ) self._op_name = os.environ.get( - 'OPENSTACKSDK_OPERATOR_CLOUD', 'devstack-admin') + 'OPENSTACKSDK_OPERATOR_CLOUD', 'devstack-admin', + ) self.config = openstack.config.OpenStackConfig() self._set_user_cloud() @@ -64,6 +62,9 @@ class BaseFunctionalTest(base.TestCase): self.identity_version = \ self.operator_cloud.config.get_api_version('identity') + self.flavor = self._pick_flavor() + self.image = self._pick_image() + # Defines default timeout for wait_for methods used # in the functional tests self._wait_for_timeout = int( @@ -71,8 +72,7 @@ class BaseFunctionalTest(base.TestCase): 'OPENSTACKSDK_FUNC_TEST_TIMEOUT', 300))) def _set_user_cloud(self, **kwargs): - user_config = self.config.get_one( - cloud=self._demo_name, **kwargs) + user_config = self.config.get_one(cloud=self._demo_name, **kwargs) self.user_cloud = connection.Connection(config=user_config) _disable_keep_alive(self.user_cloud) @@ -84,37 +84,95 @@ class BaseFunctionalTest(base.TestCase): _disable_keep_alive(self.user_cloud_alt) def _set_operator_cloud(self, **kwargs): - operator_config = self.config.get_one( - cloud=self._op_name, **kwargs) + operator_config = self.config.get_one(cloud=self._op_name, **kwargs) self.operator_cloud = connection.Connection(config=operator_config) _disable_keep_alive(self.operator_cloud) - def pick_image(self): + def _pick_flavor(self): + """Pick a sensible flavor to run tests with. + + This returns None if the compute service is not present (e.g. + ironic-only deployments). + """ + if not self.user_cloud.has_service('compute'): + return None + + flavors = self.user_cloud.list_flavors(get_extra=False) + self.add_info_on_exception('flavors', flavors) + + flavor_name = os.environ.get('OPENSTACKSDK_FLAVOR') + + if not flavor_name: + flavor_name = _get_resource_value('flavor_name') + + if flavor_name: + for flavor in flavors: + if flavor.name == flavor_name: + return flavor + + raise self.failureException( + "Cloud does not have flavor '%s'", flavor_name, + ) + + # Enable running functional tests against RAX, which requires + # performance flavors be used for boot from volume + + for flavor in sorted(flavors, key=operator.attrgetter('ram')): + if 'performance' in flavor.name: + return flavor + + # Otherwise, pick the smallest flavor with a ephemeral disk configured + + for flavor in sorted(flavors, key=operator.attrgetter('ram')): + if flavor.disk: + return flavor + + raise self.failureException('No sensible flavor found') + + def _pick_image(self): + """Pick a sensible image to run tests with. + + This returns None if the image service is not present. + """ + if not self.user_cloud.has_service('image'): + return None + images = self.user_cloud.list_images() self.add_info_on_exception('images', images) image_name = os.environ.get('OPENSTACKSDK_IMAGE') + + if not image_name: + image_name = _get_resource_value('image_name') + if image_name: for image in images: if image.name == image_name: return image - self.assertFalse( - "Cloud does not have {image}".format(image=image_name)) + + raise self.failureException( + "Cloud does not have image '%s'", image_name, + ) for image in images: if image.name.startswith('cirros') and image.name.endswith('-uec'): return image + for image in images: - if (image.name.startswith('cirros') - and image.disk_format == 'qcow2'): + if ( + image.name.startswith('cirros') + and image.disk_format == 'qcow2' + ): return image + for image in images: if image.name.lower().startswith('ubuntu'): return image for image in images: if image.name.lower().startswith('centos'): return image - self.assertFalse('no sensible image available') + + raise self.failureException('No sensible image found') def addEmptyCleanup(self, func, *args, **kwargs): def cleanup(): @@ -125,12 +183,12 @@ class BaseFunctionalTest(base.TestCase): def require_service(self, service_type, min_microversion=None, **kwargs): """Method to check whether a service exists - Usage: - class TestMeter(base.BaseFunctionalTest): - ... - def setUp(self): - super(TestMeter, self).setUp() - self.require_service('metering') + Usage:: + + class TestMeter(base.BaseFunctionalTest): + def setUp(self): + super(TestMeter, self).setUp() + self.require_service('metering') :returns: True if the service exists, otherwise False. """ @@ -144,16 +202,19 @@ class BaseFunctionalTest(base.TestCase): data = self.conn.session.get_endpoint_data( service_type=service_type, **kwargs) - if not (data.min_microversion - and data.max_microversion - and discover.version_between( - data.min_microversion, - data.max_microversion, - min_microversion)): - self.skipTest('Service {service_type} does not provide ' - 'microversion {ver}'.format( - service_type=service_type, - ver=min_microversion)) + if not ( + data.min_microversion + and data.max_microversion + and discover.version_between( + data.min_microversion, + data.max_microversion, + min_microversion, + ) + ): + self.skipTest( + f'Service {service_type} does not provide microversion ' + f'{min_microversion}' + ) class KeystoneBaseFunctionalTest(BaseFunctionalTest): diff --git a/openstack/tests/functional/cloud/test_clustering.py b/openstack/tests/functional/cloud/test_clustering.py index 354f2d965..80fd8a7a7 100644 --- a/openstack/tests/functional/cloud/test_clustering.py +++ b/openstack/tests/functional/cloud/test_clustering.py @@ -115,8 +115,8 @@ class TestClustering(base.BaseFunctionalTest): profile_name = "test_profile" spec = { "properties": { - "flavor": "m1.tiny", - "image": base.IMAGE_NAME, + "flavor": self.flavor.name, + "image": self.image.name, "networks": [ { "network": "private" @@ -145,8 +145,8 @@ class TestClustering(base.BaseFunctionalTest): profile_name = "test_profile" spec = { "properties": { - "flavor": "m1.tiny", - "image": base.IMAGE_NAME, + "flavor": self.flavor.name, + "image": self.image.name, "networks": [ { "network": "private" @@ -189,8 +189,8 @@ class TestClustering(base.BaseFunctionalTest): profile_name = "test_profile" spec = { "properties": { - "flavor": "m1.tiny", - "image": base.IMAGE_NAME, + "flavor": self.flavor.name, + "image": self.image.name, "networks": [ { "network": "private" @@ -232,8 +232,8 @@ class TestClustering(base.BaseFunctionalTest): profile_name = "test_profile" spec = { "properties": { - "flavor": "m1.tiny", - "image": base.IMAGE_NAME, + "flavor": self.flavor.name, + "image": self.image.name, "networks": [ { "network": "private" @@ -318,8 +318,8 @@ class TestClustering(base.BaseFunctionalTest): profile_name = "test_profile" spec = { "properties": { - "flavor": "m1.tiny", - "image": base.IMAGE_NAME, + "flavor": self.flavor.name, + "image": self.image.name, "networks": [ { "network": "private" @@ -392,8 +392,8 @@ class TestClustering(base.BaseFunctionalTest): profile_name = "test_profile" spec = { "properties": { - "flavor": "m1.tiny", - "image": base.IMAGE_NAME, + "flavor": self.flavor.name, + "image": self.image.name, "networks": [ { "network": "private" @@ -474,8 +474,8 @@ class TestClustering(base.BaseFunctionalTest): profile_name = "test_profile" spec = { "properties": { - "flavor": "m1.tiny", - "image": base.IMAGE_NAME, + "flavor": self.flavor.name, + "image": self.image.name, "networks": [ { "network": "private" @@ -567,8 +567,8 @@ class TestClustering(base.BaseFunctionalTest): profile_name = "test_profile" spec = { "properties": { - "flavor": "m1.tiny", - "image": base.IMAGE_NAME, + "flavor": self.flavor.name, + "image": self.image.name, "networks": [ { "network": "private" @@ -656,8 +656,8 @@ class TestClustering(base.BaseFunctionalTest): profile_name = "test_profile" spec = { "properties": { - "flavor": "m1.tiny", - "image": base.IMAGE_NAME, + "flavor": self.flavor.name, + "image": self.image.name, "networks": [ { "network": "private" @@ -713,8 +713,8 @@ class TestClustering(base.BaseFunctionalTest): profile_name = "test_profile" spec = { "properties": { - "flavor": "m1.tiny", - "image": base.IMAGE_NAME, + "flavor": self.flavor.name, + "image": self.image.name, "networks": [ { "network": "private" @@ -775,8 +775,8 @@ class TestClustering(base.BaseFunctionalTest): profile_name = "test_profile" spec = { "properties": { - "flavor": "m1.tiny", - "image": base.IMAGE_NAME, + "flavor": self.flavor.name, + "image": self.image.name, "networks": [ { "network": "private" @@ -863,8 +863,8 @@ class TestClustering(base.BaseFunctionalTest): profile_name = "test_profile" spec = { "properties": { - "flavor": "m1.tiny", - "image": base.IMAGE_NAME, + "flavor": self.flavor.name, + "image": self.image.name, "networks": [ { "network": "private" @@ -914,8 +914,8 @@ class TestClustering(base.BaseFunctionalTest): profile_name = "test_profile" spec = { "properties": { - "flavor": "m1.tiny", - "image": base.IMAGE_NAME, + "flavor": self.flavor.name, + "image": self.image.name, "networks": [ { "network": "private" @@ -1018,8 +1018,8 @@ class TestClustering(base.BaseFunctionalTest): profile_name = "test_profile" spec = { "properties": { - "flavor": "m1.tiny", - "image": base.IMAGE_NAME, + "flavor": self.flavor.name, + "image": self.image.name, "networks": [ { "network": "private" @@ -1056,8 +1056,8 @@ class TestClustering(base.BaseFunctionalTest): profile_name = "test_profile" spec = { "properties": { - "flavor": "m1.tiny", - "image": base.IMAGE_NAME, + "flavor": self.flavor.name, + "image": self.image.name, "networks": [ { "network": "private" @@ -1094,8 +1094,8 @@ class TestClustering(base.BaseFunctionalTest): profile_name = "test_profile" spec = { "properties": { - "flavor": "m1.tiny", - "image": base.IMAGE_NAME, + "flavor": self.flavor.name, + "image": self.image.name, "networks": [ { "network": "private" @@ -1130,8 +1130,8 @@ class TestClustering(base.BaseFunctionalTest): profile_name = "test_profile" spec = { "properties": { - "flavor": "m1.tiny", - "image": base.IMAGE_NAME, + "flavor": self.flavor.name, + "image": self.image.name, "networks": [ { "network": "private" @@ -1297,8 +1297,8 @@ class TestClustering(base.BaseFunctionalTest): profile_name = "test_profile" spec = { "properties": { - "flavor": "m1.tiny", - "image": base.IMAGE_NAME, + "flavor": self.flavor.name, + "image": self.image.name, "networks": [ { "network": "private" @@ -1356,8 +1356,8 @@ class TestClustering(base.BaseFunctionalTest): profile_name = "test_profile" spec = { "properties": { - "flavor": "m1.tiny", - "image": base.IMAGE_NAME, + "flavor": self.flavor.name, + "image": self.image.name, "networks": [ { "network": "private" diff --git a/openstack/tests/functional/cloud/test_compute.py b/openstack/tests/functional/cloud/test_compute.py index 787f356da..c91fe7e08 100644 --- a/openstack/tests/functional/cloud/test_compute.py +++ b/openstack/tests/functional/cloud/test_compute.py @@ -23,7 +23,6 @@ from fixtures import TimeoutException from openstack.cloud import exc from openstack.tests.functional import base -from openstack.tests.functional.cloud.util import pick_flavor from openstack import utils @@ -34,11 +33,6 @@ class TestCompute(base.BaseFunctionalTest): self.TIMEOUT_SCALING_FACTOR = 1.5 super(TestCompute, self).setUp() - self.flavor = pick_flavor( - self.user_cloud.list_flavors(get_extra=False)) - if self.flavor is None: - self.assertFalse('no sensible flavor available') - self.image = self.pick_image() self.server_name = self.getUniqueString() def _cleanup_servers_and_volumes(self, server_name): diff --git a/openstack/tests/functional/cloud/test_floating_ip.py b/openstack/tests/functional/cloud/test_floating_ip.py index 3014977bd..24d72ed4b 100644 --- a/openstack/tests/functional/cloud/test_floating_ip.py +++ b/openstack/tests/functional/cloud/test_floating_ip.py @@ -28,7 +28,6 @@ from openstack.cloud.exc import OpenStackCloudException from openstack.cloud import meta from openstack import proxy from openstack.tests.functional import base -from openstack.tests.functional.cloud.util import pick_flavor from openstack import utils @@ -36,12 +35,7 @@ class TestFloatingIP(base.BaseFunctionalTest): timeout = 60 def setUp(self): - super(TestFloatingIP, self).setUp() - self.flavor = pick_flavor( - self.user_cloud.list_flavors(get_extra=False)) - if self.flavor is None: - self.assertFalse('no sensible flavor available') - self.image = self.pick_image() + super().setUp() # Generate a random name for these tests self.new_item_name = self.getUniqueString() diff --git a/openstack/tests/functional/cloud/test_image.py b/openstack/tests/functional/cloud/test_image.py index 46692866d..b6e43fbb0 100644 --- a/openstack/tests/functional/cloud/test_image.py +++ b/openstack/tests/functional/cloud/test_image.py @@ -25,9 +25,6 @@ from openstack.tests.functional import base class TestImage(base.BaseFunctionalTest): - def setUp(self): - super(TestImage, self).setUp() - self.image = self.pick_image() def test_create_image(self): test_image = tempfile.NamedTemporaryFile(delete=False) diff --git a/openstack/tests/functional/cloud/test_inventory.py b/openstack/tests/functional/cloud/test_inventory.py index 9b9d52702..92f0a3e84 100644 --- a/openstack/tests/functional/cloud/test_inventory.py +++ b/openstack/tests/functional/cloud/test_inventory.py @@ -21,21 +21,15 @@ Functional tests for `shade` inventory methods. from openstack.cloud import inventory from openstack.tests.functional import base -from openstack.tests.functional.cloud.util import pick_flavor class TestInventory(base.BaseFunctionalTest): def setUp(self): - super(TestInventory, self).setUp() + super().setUp() # This needs to use an admin account, otherwise a public IP # is not allocated from devstack. self.inventory = inventory.OpenStackInventory(cloud='devstack-admin') self.server_name = self.getUniqueString('inventory') - self.flavor = pick_flavor( - self.user_cloud.list_flavors(get_extra=False)) - if self.flavor is None: - self.assertTrue(False, 'no sensible flavor available') - self.image = self.pick_image() self.addCleanup(self._cleanup_server) server = self.operator_cloud.create_server( name=self.server_name, image=self.image, flavor=self.flavor, diff --git a/openstack/tests/functional/cloud/util.py b/openstack/tests/functional/cloud/util.py deleted file mode 100644 index d16bfd8c1..000000000 --- a/openstack/tests/functional/cloud/util.py +++ /dev/null @@ -1,43 +0,0 @@ -# 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. - -""" -util --------------------------------- - -Util methods for functional tests -""" -import operator -import os - - -def pick_flavor(flavors): - """Given a flavor list pick the smallest one.""" - # Enable running functional tests against rax - which requires - # performance flavors be used for boot from volume - flavor_name = os.environ.get('OPENSTACKSDK_FLAVOR') - if flavor_name: - for flavor in flavors: - if flavor.name == flavor_name: - return flavor - return None - - for flavor in sorted( - flavors, - key=operator.attrgetter('ram')): - if 'performance' in flavor.name: - return flavor - for flavor in sorted( - flavors, - key=operator.attrgetter('ram')): - if flavor.disk: - return flavor diff --git a/openstack/tests/functional/clustering/test_cluster.py b/openstack/tests/functional/clustering/test_cluster.py index 4f03e9f21..9db6e18fa 100644 --- a/openstack/tests/functional/clustering/test_cluster.py +++ b/openstack/tests/functional/clustering/test_cluster.py @@ -40,8 +40,8 @@ class TestCluster(base.BaseFunctionalTest): 'version': 1.0, 'properties': { 'name': self.getUniqueString(), - 'flavor': base.FLAVOR_NAME, - 'image': base.IMAGE_NAME, + 'flavor': self.flavor.name, + 'image': self.image.name, 'networks': [{'network': self.network.id}] }}} diff --git a/openstack/tests/functional/compute/v2/test_server.py b/openstack/tests/functional/compute/v2/test_server.py index d19b7605b..8803607a4 100644 --- a/openstack/tests/functional/compute/v2/test_server.py +++ b/openstack/tests/functional/compute/v2/test_server.py @@ -11,7 +11,6 @@ # under the License. from openstack.compute.v2 import server -from openstack.tests.functional import base from openstack.tests.functional.compute import base as ft_base from openstack.tests.functional.network.v2 import test_network @@ -23,21 +22,24 @@ class TestServerAdmin(ft_base.BaseComputeTest): self._set_operator_cloud(interface='admin') self.NAME = 'needstobeshortandlowercase' self.USERDATA = 'SSdtIGFjdHVhbGx5IGEgZ29hdC4=' - flavor = self.conn.compute.find_flavor(base.FLAVOR_NAME, - ignore_missing=False) - image = self.conn.compute.find_image(base.IMAGE_NAME, - ignore_missing=False) volume = self.conn.create_volume(1) sot = self.conn.compute.create_server( - name=self.NAME, flavor_id=flavor.id, image_id=image.id, - networks='none', user_data=self.USERDATA, - block_device_mapping=[{ - 'uuid': volume.id, - 'source_type': 'volume', - 'boot_index': 0, - 'destination_type': 'volume', - 'delete_on_termination': True, - 'volume_size': 1}]) + name=self.NAME, + flavor_id=self.flavor.id, + image_id=self.image.id, + networks='none', + user_data=self.USERDATA, + block_device_mapping=[ + { + 'uuid': volume.id, + 'source_type': 'volume', + 'boot_index': 0, + 'destination_type': 'volume', + 'delete_on_termination': True, + 'volume_size': 1, + }, + ], + ) self.conn.compute.wait_for_server(sot, wait=self._wait_for_timeout) assert isinstance(sot, server.Server) self.assertEqual(self.NAME, sot.name) @@ -72,10 +74,6 @@ class TestServer(ft_base.BaseComputeTest): self.subnet = None self.cidr = '10.99.99.0/16' - flavor = self.conn.compute.find_flavor(base.FLAVOR_NAME, - ignore_missing=False) - image = self.conn.compute.find_image(base.IMAGE_NAME, - ignore_missing=False) self.network, self.subnet = test_network.create_network( self.conn, self.NAME, @@ -83,8 +81,11 @@ class TestServer(ft_base.BaseComputeTest): self.assertIsNotNone(self.network) sot = self.conn.compute.create_server( - name=self.NAME, flavor_id=flavor.id, image_id=image.id, - networks=[{"uuid": self.network.id}]) + name=self.NAME, + flavor_id=self.flavor.id, + image_id=self.image.id, + networks=[{"uuid": self.network.id}], + ) self.conn.compute.wait_for_server(sot, wait=self._wait_for_timeout) assert isinstance(sot, server.Server) self.assertEqual(self.NAME, sot.name)