From d4ea94298fad209087f3556f4aa2319d7e683c3c Mon Sep 17 00:00:00 2001 From: Jose Delarosa Date: Fri, 25 Oct 2019 15:54:03 -0500 Subject: [PATCH 1/6] Add heat functional tests --- setup.py | 1 + zaza/openstack/charm_tests/heat/__init__.py | 17 + zaza/openstack/charm_tests/heat/tests.py | 363 ++++++++++++++++++ .../tests/files/icehouse/hot_hello_world.yaml | 66 ++++ .../tests/files/queens/hot_hello_world.yaml | 69 ++++ zaza/openstack/utilities/openstack.py | 13 + 6 files changed, 529 insertions(+) create mode 100644 zaza/openstack/charm_tests/heat/__init__.py create mode 100644 zaza/openstack/charm_tests/heat/tests.py create mode 100644 zaza/openstack/charm_tests/heat/tests/files/icehouse/hot_hello_world.yaml create mode 100644 zaza/openstack/charm_tests/heat/tests/files/queens/hot_hello_world.yaml diff --git a/setup.py b/setup.py index 7fe8b81..71c965b 100644 --- a/setup.py +++ b/setup.py @@ -36,6 +36,7 @@ install_require = [ 'tenacity', 'oslo.config', 'aodhclient', + 'python-heatclient', 'python-glanceclient', 'python-keystoneclient', 'python-novaclient', diff --git a/zaza/openstack/charm_tests/heat/__init__.py b/zaza/openstack/charm_tests/heat/__init__.py new file mode 100644 index 0000000..276455e --- /dev/null +++ b/zaza/openstack/charm_tests/heat/__init__.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 + +# Copyright 2019 Canonical Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Collection of code for setting up and testing heat.""" diff --git a/zaza/openstack/charm_tests/heat/tests.py b/zaza/openstack/charm_tests/heat/tests.py new file mode 100644 index 0000000..f627da0 --- /dev/null +++ b/zaza/openstack/charm_tests/heat/tests.py @@ -0,0 +1,363 @@ +#!/usr/bin/env python3 +# +# Copyright 2019 Canonical Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Encapsulate heat testing.""" +import logging +import json +import os +import subprocess +from urllib import parse as urlparse +from heatclient.common import template_utils +from novaclient import exceptions + +import zaza.model +import zaza.openstack.charm_tests.test_utils as test_utils +import zaza.openstack.utilities.openstack as openstack_utils + +# Resource and name constants +IMAGE_NAME = 'cirros-image-1' +KEYPAIR_NAME = 'testkey' +STACK_NAME = 'hello_world' +RESOURCE_TYPE = 'server' +TEMPLATES_PATH = 'tests/files' +FLAVOR_NAME = 'm1.tiny' + + +class HeatBasicDeployment(test_utils.OpenStackBaseTest): + """Encapsulate Heat tests.""" + + @classmethod + def setUpClass(cls): + """Run class setup for running Heat tests.""" + super(HeatBasicDeployment, cls).setUpClass() + cls.application = 'heat' + cls.keystone_session = openstack_utils.get_overcloud_keystone_session() + cls.heat_client = openstack_utils.get_heat_session_client( + cls.keystone_session) + cls.glance_client = openstack_utils.get_glance_session_client( + cls.keystone_session) + cls.nova_client = openstack_utils.get_nova_session_client( + cls.keystone_session) + + @property + def services(self): + """Return a list services for OpenStack release.""" + services = ['heat-api', 'heat-api-cfn', 'heat-engine'] + return services + + def _image_create(self): + """Create an image to be used by the heat template, verify it exists""" + logging.info('Creating glance image ({})...'.format(IMAGE_NAME)) + + # Create a new image + image_url = openstack_utils.find_cirros_image(arch='x86_64') + image_new = openstack_utils.create_image( + self.glance_client, + image_url, + IMAGE_NAME) + + # Confirm image is created and has status of 'active' + if not image_new: + message = 'glance image create failed' + logging.error(message) + + # Verify new image name + images_list = list(self.glance_client.images.list()) + if images_list[0].name != IMAGE_NAME: + message = ('glance image create failed or unexpected ' + 'image name {}'.format(images_list[0].name)) + logging.error(message) + + def _keypair_create(self): + """Create a keypair to be used by the heat template, + or get a keypair if it exists.""" + + logging.info('Creating keypair {} if none exists'.format(KEYPAIR_NAME)) + if not openstack_utils.valid_key_exists(self.nova_client, + KEYPAIR_NAME): + key = openstack_utils.create_ssh_key( + self.nova_client, + KEYPAIR_NAME, + replace=True) + openstack_utils.write_private_key( + KEYPAIR_NAME, + key.private_key) + logging.info('Keypair created') + else: + logging.info('Keypair not created') + + def _stack_create(self): + """Create a heat stack from a basic heat template, verify its status""" + logging.info('Creating heat stack...') + + t_name = 'hot_hello_world.yaml' + if (openstack_utils.get_os_release() < + openstack_utils.get_os_release('xenial_queens')): + os_release = 'icehouse' + else: + os_release = 'queens' + + file_rel_path = os.path.join(TEMPLATES_PATH, os_release, t_name) + file_abs_path = os.path.abspath(file_rel_path) + t_url = urlparse.urlparse(file_abs_path, scheme='file').geturl() + logging.info('template url: {}'.format(t_url)) + + # Create flavor + try: + self.nova_client.flavors.find(name=FLAVOR_NAME) + except (exceptions.NotFound, exceptions.NoUniqueMatch): + logging.info('Creating flavor ({})'.format(FLAVOR_NAME)) + self.nova_client.flavors.create(FLAVOR_NAME, ram=512, vcpus=1, + disk=1, flavorid=1) + + r_req = self.heat_client.http_client + + t_files, template = template_utils.get_template_contents(t_url, r_req) + env_files, env = template_utils.process_environment_and_files( + env_path=None) + + fields = { + 'stack_name': STACK_NAME, + 'timeout_mins': '15', + 'disable_rollback': False, + 'parameters': { + 'admin_pass': 'Ubuntu', + 'key_name': KEYPAIR_NAME, + 'image': IMAGE_NAME + }, + 'template': template, + 'files': dict(list(t_files.items()) + list(env_files.items())), + 'environment': env + } + + # Create the stack. + try: + _stack = self.heat_client.stacks.create(**fields) + logging.info('Stack data: {}'.format(_stack)) + _stack_id = _stack['stack']['id'] + logging.info('Creating new stack, ID: {}'.format(_stack_id)) + except Exception as e: + # Generally, an api or cloud config error if this is hit. + msg = 'Failed to create heat stack: {}'.format(e) + logging.error(msg) + raise + + # Confirm stack reaches COMPLETE status. + # /!\ Heat stacks reach a COMPLETE status even when nova cannot + # find resources (a valid hypervisor) to fit the instance, in + # which case the heat stack self-deletes! Confirm anyway... + openstack_utils.resource_reaches_status(self.heat_client.stacks, + _stack_id, + expected_status="COMPLETE", + msg="Stack status wait") + _stacks = list(self.heat_client.stacks.list()) + logging.info('All stacks: {}'.format(_stacks)) + + # Confirm stack still exists. + try: + _stack = self.heat_client.stacks.get(STACK_NAME) + except Exception as e: + # Generally, a resource availability issue if this is hit. + msg = 'Failed to get heat stack: {}'.format(e) + logging.error(msg) + + # Confirm stack name. + logging.info('Expected, actual stack name: {}, ' + '{}'.format(STACK_NAME, _stack.stack_name)) + if STACK_NAME != _stack.stack_name: + msg = 'Stack name mismatch, {} != {}'.format(STACK_NAME, + _stack.stack_name) + logging.error(msg) + + def _stack_resource_compute(self): + """Confirm that the stack has created a subsequent nova + compute resource, and confirm its status.""" + logging.info('Confirming heat stack resource status...') + + # Confirm existence of a heat-generated nova compute resource. + _resource = self.heat_client.resources.get(STACK_NAME, RESOURCE_TYPE) + _server_id = _resource.physical_resource_id + if _server_id: + logging.debug('Heat template spawned nova instance, ' + 'ID: {}'.format(_server_id)) + else: + msg = 'Stack failed to spawn a nova compute resource (instance).' + logging.error(msg) + + # Confirm nova instance reaches ACTIVE status. + openstack_utils.resource_reaches_status(self.nova_client.servers, + _server_id, + expected_status="ACTIVE", + msg="nova instance") + logging.info('Nova instance reached ACTIVE status') + + def _stack_delete(self): + """Delete a heat stack, verify.""" + logging.info('Deleting heat stack...') + openstack_utils.delete_resource(self.heat_client.stacks, + STACK_NAME, msg="heat stack") + + def _image_delete(self): + """Delete that image.""" + logging.info('Deleting glance image...') + image = self.nova_client.glance.find_image(IMAGE_NAME) + openstack_utils.delete_resource(self.glance_client.images, + image.id, msg="glance image") + + def _keypair_delete(self): + """Delete that keypair.""" + logging.info('Deleting keypair...') + openstack_utils.delete_resource(self.nova_client.keypairs, + KEYPAIR_NAME, msg="nova keypair") + + def test_100_domain_setup(self): + # Action is REQUIRED to run for a functioning heat deployment + logging.info('Running domain-setup action on heat unit...') + unit = zaza.model.get_units(self.application_name)[0] + assert unit.workload_status == "active" + zaza.model.run_action(unit.entity_id, "domain-setup") + zaza.model.block_until_unit_wl_status(unit.entity_id, "active") + unit = zaza.model.get_unit_from_name(unit.entity_id) + assert unit.workload_status == "active" + + def test_400_heat_resource_types_list(self): + """Check default heat resource list behavior, also confirm + heat functionality.""" + logging.info('Checking default heat resource list...') + try: + types = list(self.heat_client.resource_types.list()) + if type(types) is list: + logging.info('Resource type list check is ok.') + else: + msg = 'Resource type list is not a list!' + logging.error('{}'.format(msg)) + raise + if len(types) > 0: + logging.info('Resource type list is populated ' + '({}, ok).'.format(len(types))) + else: + msg = 'Resource type list length is zero!' + logging.error(msg) + raise + except Exception as e: + msg = 'Resource type list failed: {}'.format(e) + logging.error(msg) + raise + + def test_402_heat_stack_list(self): + """Check default heat stack list behavior, also confirm + heat functionality.""" + logging.info('Checking default heat stack list...') + try: + stacks = list(self.heat_client.stacks.list()) + if type(stacks) is list: + logging.info("Stack list check is ok.") + else: + msg = 'Stack list returned something other than a list.' + logging.error(msg) + raise + except Exception as e: + msg = 'Heat stack list failed: {}'.format(e) + logging.error(msg) + raise + + def test_410_heat_stack_create_delete(self): + """Create a heat stack from template, confirm that a corresponding + nova compute resource is spawned, delete stack.""" + logging.info('Creating, deleting heat stack (compute)...') + self._image_create() + self._keypair_create() + self._stack_create() + self._stack_resource_compute() + self._stack_delete() + self._image_delete() + self._keypair_delete() + + def test_500_auth_encryption_key_same_on_units(self): + """Test that the auth_encryption_key in heat.conf is the same on all of + the units. + """ + logging.info("Checking the 'auth_encryption_key' is the same on " + "all units.") + output, ret = self._run_arbitrary( + "--application heat " + "--format json " + "grep auth_encryption_key /etc/heat/heat.conf") + if ret: + msg = "juju run returned error: ({}) -> {}".format(ret, output) + logging.error("Error: {}".format(msg)) + output = json.loads(output) + keys = {} + for r in output: + k = r['Stdout'].split('=')[1].strip() + keys[r['UnitId']] = k + # see if keys are different. + ks = list(keys.values()) + if any(((k != ks[0]) for k in ks[1:])): + msg = ("'auth_encryption_key' is not identical on every unit: {}" + .format("{}={}".format(k, v) for k, v in keys.items())) + logging.error("Error: {}".format(msg)) + + @staticmethod + def _run_arbitrary(command, timeout=300): + """Run an arbitrary command (as root), but not necessarily on a unit. + + (Otherwise the self.run(...) command could have been used for the unit + + :param str command: The command to run. + :param int timeout: Seconds to wait before timing out. + :return: A 2-tuple containing the output of the command and the exit + code of the command. + """ + cmd = ['juju', 'run', '--timeout', "{}s".format(timeout), + ] + command.split() + p = subprocess.Popen( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = p.communicate() + output = stdout if p.returncode == 0 else stderr + return output.decode('utf8').strip(), p.returncode + + def test_900_heat_restart_on_config_change(self): + """Verify that the specified services are restarted when the config + is changed.""" + logging.info('Testing restart on configuration change') + + # Expected default and alternate values + set_default = {'use-syslog': 'False'} + set_alternate = {'use-syslog': 'True'} + + # Config file affected by juju set config change + conf_file = '/etc/heat/heat.conf' + + # Make config change, check for service restarts + # In Amulet we waited 30 seconds...do we still need to? + logging.info('Making configuration change') + self.restart_on_changed( + conf_file, + set_default, + set_alternate, + None, + None, + self.services) + + def test_910_pause_and_resume(self): + """Run services pause and resume tests.""" + logging.info('Checking pause and resume actions...') + + with self.pause_resume(self.services): + logging.info("Testing pause resume") diff --git a/zaza/openstack/charm_tests/heat/tests/files/icehouse/hot_hello_world.yaml b/zaza/openstack/charm_tests/heat/tests/files/icehouse/hot_hello_world.yaml new file mode 100644 index 0000000..f799a2a --- /dev/null +++ b/zaza/openstack/charm_tests/heat/tests/files/icehouse/hot_hello_world.yaml @@ -0,0 +1,66 @@ +# +# This is a hello world HOT template just defining a single compute +# server. +# +heat_template_version: 2013-05-23 + +description: > + Hello world HOT template that just defines a single server. + Contains just base features to verify base HOT support. + +parameters: + key_name: + type: string + description: Name of an existing key pair to use for the server + constraints: + - custom_constraint: nova.keypair + flavor: + type: string + description: Flavor for the server to be created + default: m1.tiny + constraints: + - custom_constraint: nova.flavor + image: + type: string + description: Image ID or image name to use for the server + constraints: + - custom_constraint: glance.image + admin_pass: + type: string + description: Admin password + hidden: true + constraints: + - length: { min: 6, max: 8 } + description: Password length must be between 6 and 8 characters + - allowed_pattern: "[a-zA-Z0-9]+" + description: Password must consist of characters and numbers only + - allowed_pattern: "[A-Z]+[a-zA-Z0-9]*" + description: Password must start with an uppercase character + db_port: + type: number + description: Database port number + default: 50000 + constraints: + - range: { min: 40000, max: 60000 } + description: Port number must be between 40000 and 60000 + +resources: + server: + type: OS::Nova::Server + properties: + key_name: { get_param: key_name } + image: { get_param: image } + flavor: { get_param: flavor } + admin_pass: { get_param: admin_pass } + user_data: + str_replace: + template: | + #!/bin/bash + echo db_port + params: + db_port: { get_param: db_port } + +outputs: + server_networks: + description: The networks of the deployed server + value: { get_attr: [server, networks] } diff --git a/zaza/openstack/charm_tests/heat/tests/files/queens/hot_hello_world.yaml b/zaza/openstack/charm_tests/heat/tests/files/queens/hot_hello_world.yaml new file mode 100644 index 0000000..53302c1 --- /dev/null +++ b/zaza/openstack/charm_tests/heat/tests/files/queens/hot_hello_world.yaml @@ -0,0 +1,69 @@ +# +# This is a hello world HOT template just defining a single compute +# server. +# +heat_template_version: 2013-05-23 + +description: > + Hello world HOT template that just defines a single server. + Contains just base features to verify base HOT support. + +parameters: + key_name: + type: string + description: Name of an existing key pair to use for the server + constraints: + - custom_constraint: nova.keypair + flavor: + type: string + description: Flavor for the server to be created + default: m1.tiny + constraints: + - custom_constraint: nova.flavor + image: + type: string + description: Image ID or image name to use for the server + constraints: + - custom_constraint: glance.image + admin_pass: + type: string + description: Admin password + hidden: true + constraints: + - length: { min: 6, max: 8 } + description: Password length must be between 6 and 8 characters + - allowed_pattern: "[a-zA-Z0-9]+" + description: Password must consist of characters and numbers only + - allowed_pattern: "[A-Z]+[a-zA-Z0-9]*" + description: Password must start with an uppercase character + db_port: + type: number + description: Database port number + default: 50000 + constraints: + - range: { min: 40000, max: 60000 } + description: Port number must be between 40000 and 60000 + +resources: + server: + type: OS::Nova::Server + properties: + key_name: { get_param: key_name } + image: { get_param: image } + flavor: { get_param: flavor } + admin_pass: { get_param: admin_pass } + # See https://docs.openstack.org/heat/queens/template_guide/contrib.html#OS::Nova::Server-prop-networks + networks: + - allocate_network: none + user_data: + str_replace: + template: | + #!/bin/bash + echo db_port + params: + db_port: { get_param: db_port } + +outputs: + server_networks: + description: The networks of the deployed server + value: { get_attr: [server, networks] } diff --git a/zaza/openstack/utilities/openstack.py b/zaza/openstack/utilities/openstack.py index f8ccd1b..4cc764c 100644 --- a/zaza/openstack/utilities/openstack.py +++ b/zaza/openstack/utilities/openstack.py @@ -27,6 +27,7 @@ from openstack import connection from aodhclient.v2 import client as aodh_client from cinderclient import client as cinderclient +from heatclient import client as heatclient from glanceclient import Client as GlanceClient from keystoneclient.v2_0 import client as keystoneclient_v2 @@ -258,6 +259,18 @@ def get_octavia_session_client(session, service_type='load-balancer', endpoint=endpoint.url) +def get_heat_session_client(session, version=1): + """Return heatclient authenticated by keystone session. + + :param session: Keystone session object + :type session: keystoneauth1.session.Session object + :param version: Heat API version + :type version: int + :returns: Authenticated cinderclient + :rtype: heatclient.Client object + """ + return heatclient.Client(session=session, version=version) + def get_cinder_session_client(session, version=2): """Return cinderclient authenticated by keystone session. From 08b5b7085408e41f96e146798e9e5c2cfc5113d3 Mon Sep 17 00:00:00 2001 From: Jose Delarosa Date: Fri, 25 Oct 2019 16:28:27 -0500 Subject: [PATCH 2/6] Fix pep8 failures --- zaza/openstack/charm_tests/heat/tests.py | 28 +++++++++--------------- zaza/openstack/utilities/openstack.py | 1 + 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/zaza/openstack/charm_tests/heat/tests.py b/zaza/openstack/charm_tests/heat/tests.py index f627da0..8ea741b 100644 --- a/zaza/openstack/charm_tests/heat/tests.py +++ b/zaza/openstack/charm_tests/heat/tests.py @@ -59,7 +59,7 @@ class HeatBasicDeployment(test_utils.OpenStackBaseTest): return services def _image_create(self): - """Create an image to be used by the heat template, verify it exists""" + """Create an image for use by the heat template, verify it exists.""" logging.info('Creating glance image ({})...'.format(IMAGE_NAME)) # Create a new image @@ -82,9 +82,7 @@ class HeatBasicDeployment(test_utils.OpenStackBaseTest): logging.error(message) def _keypair_create(self): - """Create a keypair to be used by the heat template, - or get a keypair if it exists.""" - + """Create a keypair or get a keypair if it exists.""" logging.info('Creating keypair {} if none exists'.format(KEYPAIR_NAME)) if not openstack_utils.valid_key_exists(self.nova_client, KEYPAIR_NAME): @@ -100,7 +98,7 @@ class HeatBasicDeployment(test_utils.OpenStackBaseTest): logging.info('Keypair not created') def _stack_create(self): - """Create a heat stack from a basic heat template, verify its status""" + """Create a heat stack from a heat template, verify its status.""" logging.info('Creating heat stack...') t_name = 'hot_hello_world.yaml' @@ -183,8 +181,7 @@ class HeatBasicDeployment(test_utils.OpenStackBaseTest): logging.error(msg) def _stack_resource_compute(self): - """Confirm that the stack has created a subsequent nova - compute resource, and confirm its status.""" + """Confirm the stack has created a nova resource and check status.""" logging.info('Confirming heat stack resource status...') # Confirm existence of a heat-generated nova compute resource. @@ -224,6 +221,7 @@ class HeatBasicDeployment(test_utils.OpenStackBaseTest): KEYPAIR_NAME, msg="nova keypair") def test_100_domain_setup(self): + """Run required action for a working Heat unit.""" # Action is REQUIRED to run for a functioning heat deployment logging.info('Running domain-setup action on heat unit...') unit = zaza.model.get_units(self.application_name)[0] @@ -234,8 +232,7 @@ class HeatBasicDeployment(test_utils.OpenStackBaseTest): assert unit.workload_status == "active" def test_400_heat_resource_types_list(self): - """Check default heat resource list behavior, also confirm - heat functionality.""" + """Check default resource list behavior and confirm functionality.""" logging.info('Checking default heat resource list...') try: types = list(self.heat_client.resource_types.list()) @@ -258,8 +255,7 @@ class HeatBasicDeployment(test_utils.OpenStackBaseTest): raise def test_402_heat_stack_list(self): - """Check default heat stack list behavior, also confirm - heat functionality.""" + """Check default heat stack list behavior, confirm functionality.""" logging.info('Checking default heat stack list...') try: stacks = list(self.heat_client.stacks.list()) @@ -275,8 +271,7 @@ class HeatBasicDeployment(test_utils.OpenStackBaseTest): raise def test_410_heat_stack_create_delete(self): - """Create a heat stack from template, confirm that a corresponding - nova compute resource is spawned, delete stack.""" + """Create stack, confirm nova compute resource, delete stack.""" logging.info('Creating, deleting heat stack (compute)...') self._image_create() self._keypair_create() @@ -287,9 +282,7 @@ class HeatBasicDeployment(test_utils.OpenStackBaseTest): self._keypair_delete() def test_500_auth_encryption_key_same_on_units(self): - """Test that the auth_encryption_key in heat.conf is the same on all of - the units. - """ + """Test the auth_encryption_key in heat.conf is same on all units.""" logging.info("Checking the 'auth_encryption_key' is the same on " "all units.") output, ret = self._run_arbitrary( @@ -333,8 +326,7 @@ class HeatBasicDeployment(test_utils.OpenStackBaseTest): return output.decode('utf8').strip(), p.returncode def test_900_heat_restart_on_config_change(self): - """Verify that the specified services are restarted when the config - is changed.""" + """Verify the specified services are restarted when config changes.""" logging.info('Testing restart on configuration change') # Expected default and alternate values diff --git a/zaza/openstack/utilities/openstack.py b/zaza/openstack/utilities/openstack.py index 4cc764c..442c878 100644 --- a/zaza/openstack/utilities/openstack.py +++ b/zaza/openstack/utilities/openstack.py @@ -271,6 +271,7 @@ def get_heat_session_client(session, version=1): """ return heatclient.Client(session=session, version=version) + def get_cinder_session_client(session, version=2): """Return cinderclient authenticated by keystone session. From 80d1f1086d0bd9ff7aad7a41fa133805c40ec244 Mon Sep 17 00:00:00 2001 From: Jose Delarosa Date: Mon, 28 Oct 2019 23:30:51 -0500 Subject: [PATCH 3/6] Code improvements and fixes * Removed heat templates * Improved Docstring content * Added code to assert errors when failing * Removed private variable names * Removed test_402_heat_stack_list function --- zaza/openstack/charm_tests/heat/tests.py | 107 ++++++++---------- .../tests/files/icehouse/hot_hello_world.yaml | 66 ----------- .../tests/files/queens/hot_hello_world.yaml | 69 ----------- 3 files changed, 50 insertions(+), 192 deletions(-) delete mode 100644 zaza/openstack/charm_tests/heat/tests/files/icehouse/hot_hello_world.yaml delete mode 100644 zaza/openstack/charm_tests/heat/tests/files/queens/hot_hello_world.yaml diff --git a/zaza/openstack/charm_tests/heat/tests.py b/zaza/openstack/charm_tests/heat/tests.py index 8ea741b..878b108 100644 --- a/zaza/openstack/charm_tests/heat/tests.py +++ b/zaza/openstack/charm_tests/heat/tests.py @@ -26,13 +26,14 @@ from novaclient import exceptions import zaza.model import zaza.openstack.charm_tests.test_utils as test_utils import zaza.openstack.utilities.openstack as openstack_utils +import zaza.charm_lifecycle.utils as charm_lifecycle_utils # Resource and name constants IMAGE_NAME = 'cirros-image-1' KEYPAIR_NAME = 'testkey' STACK_NAME = 'hello_world' RESOURCE_TYPE = 'server' -TEMPLATES_PATH = 'tests/files' +TEMPLATES_PATH = 'files' FLAVOR_NAME = 'm1.tiny' @@ -54,7 +55,11 @@ class HeatBasicDeployment(test_utils.OpenStackBaseTest): @property def services(self): - """Return a list services for OpenStack release.""" + """Return a list services for OpenStack release. + + :returns: List of services + :rtype: [str] + """ services = ['heat-api', 'heat-api-cfn', 'heat-engine'] return services @@ -71,15 +76,14 @@ class HeatBasicDeployment(test_utils.OpenStackBaseTest): # Confirm image is created and has status of 'active' if not image_new: - message = 'glance image create failed' - logging.error(message) + assert False, 'glance image create failed' # Verify new image name images_list = list(self.glance_client.images.list()) if images_list[0].name != IMAGE_NAME: message = ('glance image create failed or unexpected ' 'image name {}'.format(images_list[0].name)) - logging.error(message) + assert False, message def _keypair_create(self): """Create a keypair or get a keypair if it exists.""" @@ -95,7 +99,7 @@ class HeatBasicDeployment(test_utils.OpenStackBaseTest): key.private_key) logging.info('Keypair created') else: - logging.info('Keypair not created') + assert False, 'Keypair not created' def _stack_create(self): """Create a heat stack from a heat template, verify its status.""" @@ -108,7 +112,13 @@ class HeatBasicDeployment(test_utils.OpenStackBaseTest): else: os_release = 'queens' - file_rel_path = os.path.join(TEMPLATES_PATH, os_release, t_name) + # Get location of template files in charm-heat + bundle_path = charm_lifecycle_utils.BUNDLE_DIR + if bundle_path[-1:] == "/": + bundle_path = bundle_path[0:-1] + + file_rel_path = os.path.join(os.path.dirname(bundle_path), + TEMPLATES_PATH, os_release, t_name) file_abs_path = os.path.abspath(file_rel_path) t_url = urlparse.urlparse(file_abs_path, scheme='file').geturl() logging.info('template url: {}'.format(t_url)) @@ -143,10 +153,10 @@ class HeatBasicDeployment(test_utils.OpenStackBaseTest): # Create the stack. try: - _stack = self.heat_client.stacks.create(**fields) - logging.info('Stack data: {}'.format(_stack)) - _stack_id = _stack['stack']['id'] - logging.info('Creating new stack, ID: {}'.format(_stack_id)) + stack = self.heat_client.stacks.create(**fields) + logging.info('Stack data: {}'.format(stack)) + stack_id = stack['stack']['id'] + logging.info('Creating new stack, ID: {}'.format(stack_id)) except Exception as e: # Generally, an api or cloud config error if this is hit. msg = 'Failed to create heat stack: {}'.format(e) @@ -158,45 +168,46 @@ class HeatBasicDeployment(test_utils.OpenStackBaseTest): # find resources (a valid hypervisor) to fit the instance, in # which case the heat stack self-deletes! Confirm anyway... openstack_utils.resource_reaches_status(self.heat_client.stacks, - _stack_id, + stack_id, expected_status="COMPLETE", msg="Stack status wait") - _stacks = list(self.heat_client.stacks.list()) - logging.info('All stacks: {}'.format(_stacks)) + # List stack + stacks = list(self.heat_client.stacks.list()) + logging.info('All stacks: {}'.format(stacks)) - # Confirm stack still exists. + # Get stack information try: - _stack = self.heat_client.stacks.get(STACK_NAME) + stack = self.heat_client.stacks.get(STACK_NAME) except Exception as e: # Generally, a resource availability issue if this is hit. msg = 'Failed to get heat stack: {}'.format(e) - logging.error(msg) + assert False, msg # Confirm stack name. logging.info('Expected, actual stack name: {}, ' - '{}'.format(STACK_NAME, _stack.stack_name)) - if STACK_NAME != _stack.stack_name: + '{}'.format(STACK_NAME, stack.stack_name)) + if STACK_NAME != stack.stack_name: msg = 'Stack name mismatch, {} != {}'.format(STACK_NAME, - _stack.stack_name) - logging.error(msg) + stack.stack_name) + assert False, msg def _stack_resource_compute(self): """Confirm the stack has created a nova resource and check status.""" logging.info('Confirming heat stack resource status...') # Confirm existence of a heat-generated nova compute resource. - _resource = self.heat_client.resources.get(STACK_NAME, RESOURCE_TYPE) - _server_id = _resource.physical_resource_id - if _server_id: + resource = self.heat_client.resources.get(STACK_NAME, RESOURCE_TYPE) + server_id = resource.physical_resource_id + if server_id: logging.debug('Heat template spawned nova instance, ' - 'ID: {}'.format(_server_id)) + 'ID: {}'.format(server_id)) else: msg = 'Stack failed to spawn a nova compute resource (instance).' logging.error(msg) # Confirm nova instance reaches ACTIVE status. openstack_utils.resource_reaches_status(self.nova_client.servers, - _server_id, + server_id, expected_status="ACTIVE", msg="nova instance") logging.info('Nova instance reached ACTIVE status') @@ -228,47 +239,26 @@ class HeatBasicDeployment(test_utils.OpenStackBaseTest): assert unit.workload_status == "active" zaza.model.run_action(unit.entity_id, "domain-setup") zaza.model.block_until_unit_wl_status(unit.entity_id, "active") - unit = zaza.model.get_unit_from_name(unit.entity_id) - assert unit.workload_status == "active" def test_400_heat_resource_types_list(self): """Check default resource list behavior and confirm functionality.""" logging.info('Checking default heat resource list...') try: - types = list(self.heat_client.resource_types.list()) + types = self.heat_client.resource_types.list() if type(types) is list: logging.info('Resource type list check is ok.') else: msg = 'Resource type list is not a list!' - logging.error('{}'.format(msg)) - raise + assert False, msg if len(types) > 0: logging.info('Resource type list is populated ' '({}, ok).'.format(len(types))) else: msg = 'Resource type list length is zero!' - logging.error(msg) - raise + assert False, msg except Exception as e: msg = 'Resource type list failed: {}'.format(e) - logging.error(msg) - raise - - def test_402_heat_stack_list(self): - """Check default heat stack list behavior, confirm functionality.""" - logging.info('Checking default heat stack list...') - try: - stacks = list(self.heat_client.stacks.list()) - if type(stacks) is list: - logging.info("Stack list check is ok.") - else: - msg = 'Stack list returned something other than a list.' - logging.error(msg) - raise - except Exception as e: - msg = 'Heat stack list failed: {}'.format(e) - logging.error(msg) - raise + assert False, msg def test_410_heat_stack_create_delete(self): """Create stack, confirm nova compute resource, delete stack.""" @@ -290,8 +280,8 @@ class HeatBasicDeployment(test_utils.OpenStackBaseTest): "--format json " "grep auth_encryption_key /etc/heat/heat.conf") if ret: - msg = "juju run returned error: ({}) -> {}".format(ret, output) - logging.error("Error: {}".format(msg)) + msg = "juju run error: ret: {}, output: {}".format(ret, output) + self.assertEqual(ret, 0, msg) output = json.loads(output) keys = {} for r in output: @@ -310,10 +300,13 @@ class HeatBasicDeployment(test_utils.OpenStackBaseTest): (Otherwise the self.run(...) command could have been used for the unit - :param str command: The command to run. - :param int timeout: Seconds to wait before timing out. - :return: A 2-tuple containing the output of the command and the exit - code of the command. + :param command: The command to run. + :type command: str + :param timeout: Seconds to wait before timing out. + :type timeout: int + :raises: subprocess.CalledProcessError. + :returns: A pair containing the output of the command and exit value + :rtype: (str, int) """ cmd = ['juju', 'run', '--timeout', "{}s".format(timeout), ] + command.split() diff --git a/zaza/openstack/charm_tests/heat/tests/files/icehouse/hot_hello_world.yaml b/zaza/openstack/charm_tests/heat/tests/files/icehouse/hot_hello_world.yaml deleted file mode 100644 index f799a2a..0000000 --- a/zaza/openstack/charm_tests/heat/tests/files/icehouse/hot_hello_world.yaml +++ /dev/null @@ -1,66 +0,0 @@ -# -# This is a hello world HOT template just defining a single compute -# server. -# -heat_template_version: 2013-05-23 - -description: > - Hello world HOT template that just defines a single server. - Contains just base features to verify base HOT support. - -parameters: - key_name: - type: string - description: Name of an existing key pair to use for the server - constraints: - - custom_constraint: nova.keypair - flavor: - type: string - description: Flavor for the server to be created - default: m1.tiny - constraints: - - custom_constraint: nova.flavor - image: - type: string - description: Image ID or image name to use for the server - constraints: - - custom_constraint: glance.image - admin_pass: - type: string - description: Admin password - hidden: true - constraints: - - length: { min: 6, max: 8 } - description: Password length must be between 6 and 8 characters - - allowed_pattern: "[a-zA-Z0-9]+" - description: Password must consist of characters and numbers only - - allowed_pattern: "[A-Z]+[a-zA-Z0-9]*" - description: Password must start with an uppercase character - db_port: - type: number - description: Database port number - default: 50000 - constraints: - - range: { min: 40000, max: 60000 } - description: Port number must be between 40000 and 60000 - -resources: - server: - type: OS::Nova::Server - properties: - key_name: { get_param: key_name } - image: { get_param: image } - flavor: { get_param: flavor } - admin_pass: { get_param: admin_pass } - user_data: - str_replace: - template: | - #!/bin/bash - echo db_port - params: - db_port: { get_param: db_port } - -outputs: - server_networks: - description: The networks of the deployed server - value: { get_attr: [server, networks] } diff --git a/zaza/openstack/charm_tests/heat/tests/files/queens/hot_hello_world.yaml b/zaza/openstack/charm_tests/heat/tests/files/queens/hot_hello_world.yaml deleted file mode 100644 index 53302c1..0000000 --- a/zaza/openstack/charm_tests/heat/tests/files/queens/hot_hello_world.yaml +++ /dev/null @@ -1,69 +0,0 @@ -# -# This is a hello world HOT template just defining a single compute -# server. -# -heat_template_version: 2013-05-23 - -description: > - Hello world HOT template that just defines a single server. - Contains just base features to verify base HOT support. - -parameters: - key_name: - type: string - description: Name of an existing key pair to use for the server - constraints: - - custom_constraint: nova.keypair - flavor: - type: string - description: Flavor for the server to be created - default: m1.tiny - constraints: - - custom_constraint: nova.flavor - image: - type: string - description: Image ID or image name to use for the server - constraints: - - custom_constraint: glance.image - admin_pass: - type: string - description: Admin password - hidden: true - constraints: - - length: { min: 6, max: 8 } - description: Password length must be between 6 and 8 characters - - allowed_pattern: "[a-zA-Z0-9]+" - description: Password must consist of characters and numbers only - - allowed_pattern: "[A-Z]+[a-zA-Z0-9]*" - description: Password must start with an uppercase character - db_port: - type: number - description: Database port number - default: 50000 - constraints: - - range: { min: 40000, max: 60000 } - description: Port number must be between 40000 and 60000 - -resources: - server: - type: OS::Nova::Server - properties: - key_name: { get_param: key_name } - image: { get_param: image } - flavor: { get_param: flavor } - admin_pass: { get_param: admin_pass } - # See https://docs.openstack.org/heat/queens/template_guide/contrib.html#OS::Nova::Server-prop-networks - networks: - - allocate_network: none - user_data: - str_replace: - template: | - #!/bin/bash - echo db_port - params: - db_port: { get_param: db_port } - -outputs: - server_networks: - description: The networks of the deployed server - value: { get_attr: [server, networks] } From b2141a1c6264e23b22f01cfc7eb7f90e6867d376 Mon Sep 17 00:00:00 2001 From: Jose Delarosa Date: Tue, 29 Oct 2019 23:28:07 -0500 Subject: [PATCH 4/6] heat: more enhancements * Move code into test_410_heat_stack...() instead of calling functions * Use glance and nova helper functions as available * Simplify method for detecting duplicate encryption keys --- zaza/openstack/charm_tests/heat/tests.py | 146 +++++++++-------------- 1 file changed, 55 insertions(+), 91 deletions(-) diff --git a/zaza/openstack/charm_tests/heat/tests.py b/zaza/openstack/charm_tests/heat/tests.py index 878b108..ebb491b 100644 --- a/zaza/openstack/charm_tests/heat/tests.py +++ b/zaza/openstack/charm_tests/heat/tests.py @@ -24,13 +24,15 @@ from heatclient.common import template_utils from novaclient import exceptions import zaza.model +import zaza.openstack.charm_tests.glance.setup as glance_setup +import zaza.openstack.charm_tests.nova.setup as nova_setup +import zaza.openstack.charm_tests.nova.utils as nova_utils import zaza.openstack.charm_tests.test_utils as test_utils import zaza.openstack.utilities.openstack as openstack_utils import zaza.charm_lifecycle.utils as charm_lifecycle_utils # Resource and name constants -IMAGE_NAME = 'cirros-image-1' -KEYPAIR_NAME = 'testkey' +IMAGE_NAME = 'cirros' STACK_NAME = 'hello_world' RESOURCE_TYPE = 'server' TEMPLATES_PATH = 'files' @@ -63,20 +65,41 @@ class HeatBasicDeployment(test_utils.OpenStackBaseTest): services = ['heat-api', 'heat-api-cfn', 'heat-engine'] return services - def _image_create(self): - """Create an image for use by the heat template, verify it exists.""" + def test_100_domain_setup(self): + """Run required action for a working Heat unit.""" + # Action is REQUIRED to run for a functioning heat deployment + logging.info('Running domain-setup action on heat unit...') + unit = zaza.model.get_units(self.application_name)[0] + zaza.model.block_until_unit_wl_status(unit.entity_id, "active") + zaza.model.run_action(unit.entity_id, "domain-setup") + zaza.model.block_until_unit_wl_status(unit.entity_id, "active") + + def test_400_heat_resource_types_list(self): + """Check default resource list behavior and confirm functionality.""" + logging.info('Checking default heat resource list...') + try: + types = self.heat_client.resource_types.list() + if type(types) is list: + logging.info('Resource type list check is ok.') + else: + msg = 'Resource type list is not a list!' + assert False, msg + if len(types) > 0: + logging.info('Resource type list is populated ' + '({}, ok).'.format(len(types))) + else: + msg = 'Resource type list length is zero!' + assert False, msg + except Exception as e: + msg = 'Resource type list failed: {}'.format(e) + assert False, msg + + def test_410_heat_stack_create_delete(self): + """Create stack, confirm nova compute resource, delete stack.""" + + # Create an image for use by the heat template logging.info('Creating glance image ({})...'.format(IMAGE_NAME)) - - # Create a new image - image_url = openstack_utils.find_cirros_image(arch='x86_64') - image_new = openstack_utils.create_image( - self.glance_client, - image_url, - IMAGE_NAME) - - # Confirm image is created and has status of 'active' - if not image_new: - assert False, 'glance image create failed' + glance_setup.add_cirros_image(image_name=IMAGE_NAME) # Verify new image name images_list = list(self.glance_client.images.list()) @@ -85,24 +108,11 @@ class HeatBasicDeployment(test_utils.OpenStackBaseTest): 'image name {}'.format(images_list[0].name)) assert False, message - def _keypair_create(self): - """Create a keypair or get a keypair if it exists.""" - logging.info('Creating keypair {} if none exists'.format(KEYPAIR_NAME)) - if not openstack_utils.valid_key_exists(self.nova_client, - KEYPAIR_NAME): - key = openstack_utils.create_ssh_key( - self.nova_client, - KEYPAIR_NAME, - replace=True) - openstack_utils.write_private_key( - KEYPAIR_NAME, - key.private_key) - logging.info('Keypair created') - else: - assert False, 'Keypair not created' + # Create a keypair + logging.info('Creating keypair {}'.format(nova_utils.KEYPAIR_NAME)) + nova_setup.manage_ssh_key(self.nova_client) - def _stack_create(self): - """Create a heat stack from a heat template, verify its status.""" + # Create a heat stack from a heat template, verify its status logging.info('Creating heat stack...') t_name = 'hot_hello_world.yaml' @@ -132,7 +142,6 @@ class HeatBasicDeployment(test_utils.OpenStackBaseTest): disk=1, flavorid=1) r_req = self.heat_client.http_client - t_files, template = template_utils.get_template_contents(t_url, r_req) env_files, env = template_utils.process_environment_and_files( env_path=None) @@ -143,7 +152,7 @@ class HeatBasicDeployment(test_utils.OpenStackBaseTest): 'disable_rollback': False, 'parameters': { 'admin_pass': 'Ubuntu', - 'key_name': KEYPAIR_NAME, + 'key_name': nova_utils.KEYPAIR_NAME, 'image': IMAGE_NAME }, 'template': template, @@ -151,7 +160,7 @@ class HeatBasicDeployment(test_utils.OpenStackBaseTest): 'environment': env } - # Create the stack. + # Create the stack try: stack = self.heat_client.stacks.create(**fields) logging.info('Stack data: {}'.format(stack)) @@ -191,11 +200,8 @@ class HeatBasicDeployment(test_utils.OpenStackBaseTest): stack.stack_name) assert False, msg - def _stack_resource_compute(self): - """Confirm the stack has created a nova resource and check status.""" + # Confirm existence of a heat-generated nova compute resource logging.info('Confirming heat stack resource status...') - - # Confirm existence of a heat-generated nova compute resource. resource = self.heat_client.resources.get(STACK_NAME, RESOURCE_TYPE) server_id = resource.physical_resource_id if server_id: @@ -205,71 +211,29 @@ class HeatBasicDeployment(test_utils.OpenStackBaseTest): msg = 'Stack failed to spawn a nova compute resource (instance).' logging.error(msg) - # Confirm nova instance reaches ACTIVE status. + # Confirm nova instance reaches ACTIVE status openstack_utils.resource_reaches_status(self.nova_client.servers, server_id, expected_status="ACTIVE", msg="nova instance") logging.info('Nova instance reached ACTIVE status') - def _stack_delete(self): - """Delete a heat stack, verify.""" + # Delete stack logging.info('Deleting heat stack...') openstack_utils.delete_resource(self.heat_client.stacks, STACK_NAME, msg="heat stack") - def _image_delete(self): - """Delete that image.""" + # Delete image logging.info('Deleting glance image...') image = self.nova_client.glance.find_image(IMAGE_NAME) openstack_utils.delete_resource(self.glance_client.images, image.id, msg="glance image") - def _keypair_delete(self): - """Delete that keypair.""" + # Delete keypair logging.info('Deleting keypair...') openstack_utils.delete_resource(self.nova_client.keypairs, - KEYPAIR_NAME, msg="nova keypair") - - def test_100_domain_setup(self): - """Run required action for a working Heat unit.""" - # Action is REQUIRED to run for a functioning heat deployment - logging.info('Running domain-setup action on heat unit...') - unit = zaza.model.get_units(self.application_name)[0] - assert unit.workload_status == "active" - zaza.model.run_action(unit.entity_id, "domain-setup") - zaza.model.block_until_unit_wl_status(unit.entity_id, "active") - - def test_400_heat_resource_types_list(self): - """Check default resource list behavior and confirm functionality.""" - logging.info('Checking default heat resource list...') - try: - types = self.heat_client.resource_types.list() - if type(types) is list: - logging.info('Resource type list check is ok.') - else: - msg = 'Resource type list is not a list!' - assert False, msg - if len(types) > 0: - logging.info('Resource type list is populated ' - '({}, ok).'.format(len(types))) - else: - msg = 'Resource type list length is zero!' - assert False, msg - except Exception as e: - msg = 'Resource type list failed: {}'.format(e) - assert False, msg - - def test_410_heat_stack_create_delete(self): - """Create stack, confirm nova compute resource, delete stack.""" - logging.info('Creating, deleting heat stack (compute)...') - self._image_create() - self._keypair_create() - self._stack_create() - self._stack_resource_compute() - self._stack_delete() - self._image_delete() - self._keypair_delete() + nova_utils.KEYPAIR_NAME, + msg="nova keypair") def test_500_auth_encryption_key_same_on_units(self): """Test the auth_encryption_key in heat.conf is same on all units.""" @@ -287,12 +251,12 @@ class HeatBasicDeployment(test_utils.OpenStackBaseTest): for r in output: k = r['Stdout'].split('=')[1].strip() keys[r['UnitId']] = k - # see if keys are different. - ks = list(keys.values()) - if any(((k != ks[0]) for k in ks[1:])): + # see if keys are different + ks = set(keys.values()) + if len(ks) != 1: msg = ("'auth_encryption_key' is not identical on every unit: {}" .format("{}={}".format(k, v) for k, v in keys.items())) - logging.error("Error: {}".format(msg)) + assert False, msg @staticmethod def _run_arbitrary(command, timeout=300): From 806e6afd8704ce6b9afa4d9d39ba648917573f27 Mon Sep 17 00:00:00 2001 From: Jose Delarosa Date: Tue, 29 Oct 2019 23:46:52 -0500 Subject: [PATCH 5/6] Removed blank line after docstring for pep8 compliance --- zaza/openstack/charm_tests/heat/tests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/zaza/openstack/charm_tests/heat/tests.py b/zaza/openstack/charm_tests/heat/tests.py index ebb491b..fba3577 100644 --- a/zaza/openstack/charm_tests/heat/tests.py +++ b/zaza/openstack/charm_tests/heat/tests.py @@ -96,7 +96,6 @@ class HeatBasicDeployment(test_utils.OpenStackBaseTest): def test_410_heat_stack_create_delete(self): """Create stack, confirm nova compute resource, delete stack.""" - # Create an image for use by the heat template logging.info('Creating glance image ({})...'.format(IMAGE_NAME)) glance_setup.add_cirros_image(image_name=IMAGE_NAME) From 337c83b68dbd86eb7466977f2843b9513f045970 Mon Sep 17 00:00:00 2001 From: Jose Delarosa Date: Wed, 30 Oct 2019 11:32:54 -0500 Subject: [PATCH 6/6] heat: Reduce number of asserts and use helpers when appropriate --- zaza/openstack/charm_tests/heat/tests.py | 79 +++++------------------- 1 file changed, 14 insertions(+), 65 deletions(-) diff --git a/zaza/openstack/charm_tests/heat/tests.py b/zaza/openstack/charm_tests/heat/tests.py index fba3577..040aa02 100644 --- a/zaza/openstack/charm_tests/heat/tests.py +++ b/zaza/openstack/charm_tests/heat/tests.py @@ -21,11 +21,8 @@ import os import subprocess from urllib import parse as urlparse from heatclient.common import template_utils -from novaclient import exceptions import zaza.model -import zaza.openstack.charm_tests.glance.setup as glance_setup -import zaza.openstack.charm_tests.nova.setup as nova_setup import zaza.openstack.charm_tests.nova.utils as nova_utils import zaza.openstack.charm_tests.test_utils as test_utils import zaza.openstack.utilities.openstack as openstack_utils @@ -79,41 +76,21 @@ class HeatBasicDeployment(test_utils.OpenStackBaseTest): logging.info('Checking default heat resource list...') try: types = self.heat_client.resource_types.list() - if type(types) is list: - logging.info('Resource type list check is ok.') - else: - msg = 'Resource type list is not a list!' - assert False, msg - if len(types) > 0: - logging.info('Resource type list is populated ' - '({}, ok).'.format(len(types))) - else: - msg = 'Resource type list length is zero!' - assert False, msg + self.assertIsInstance(types, list, "Resource type is not a list!") + self.assertGreater(len(types), 0, "Resource type list len is zero") except Exception as e: msg = 'Resource type list failed: {}'.format(e) - assert False, msg + self.fail(msg) def test_410_heat_stack_create_delete(self): """Create stack, confirm nova compute resource, delete stack.""" - # Create an image for use by the heat template - logging.info('Creating glance image ({})...'.format(IMAGE_NAME)) - glance_setup.add_cirros_image(image_name=IMAGE_NAME) - # Verify new image name images_list = list(self.glance_client.images.list()) - if images_list[0].name != IMAGE_NAME: - message = ('glance image create failed or unexpected ' - 'image name {}'.format(images_list[0].name)) - assert False, message - - # Create a keypair - logging.info('Creating keypair {}'.format(nova_utils.KEYPAIR_NAME)) - nova_setup.manage_ssh_key(self.nova_client) + self.assertEqual(images_list[0].name, IMAGE_NAME, + "glance image create failed or unexpected") # Create a heat stack from a heat template, verify its status logging.info('Creating heat stack...') - t_name = 'hot_hello_world.yaml' if (openstack_utils.get_os_release() < openstack_utils.get_os_release('xenial_queens')): @@ -132,14 +109,6 @@ class HeatBasicDeployment(test_utils.OpenStackBaseTest): t_url = urlparse.urlparse(file_abs_path, scheme='file').geturl() logging.info('template url: {}'.format(t_url)) - # Create flavor - try: - self.nova_client.flavors.find(name=FLAVOR_NAME) - except (exceptions.NotFound, exceptions.NoUniqueMatch): - logging.info('Creating flavor ({})'.format(FLAVOR_NAME)) - self.nova_client.flavors.create(FLAVOR_NAME, ram=512, vcpus=1, - disk=1, flavorid=1) - r_req = self.heat_client.http_client t_files, template = template_utils.get_template_contents(t_url, r_req) env_files, env = template_utils.process_environment_and_files( @@ -168,8 +137,7 @@ class HeatBasicDeployment(test_utils.OpenStackBaseTest): except Exception as e: # Generally, an api or cloud config error if this is hit. msg = 'Failed to create heat stack: {}'.format(e) - logging.error(msg) - raise + self.fail(msg) # Confirm stack reaches COMPLETE status. # /!\ Heat stacks reach a COMPLETE status even when nova cannot @@ -189,26 +157,20 @@ class HeatBasicDeployment(test_utils.OpenStackBaseTest): except Exception as e: # Generally, a resource availability issue if this is hit. msg = 'Failed to get heat stack: {}'.format(e) - assert False, msg + self.fail(msg) # Confirm stack name. logging.info('Expected, actual stack name: {}, ' '{}'.format(STACK_NAME, stack.stack_name)) - if STACK_NAME != stack.stack_name: - msg = 'Stack name mismatch, {} != {}'.format(STACK_NAME, - stack.stack_name) - assert False, msg + self.assertEqual(stack.stack_name, STACK_NAME, + 'Stack name mismatch, ' + '{} != {}'.format(STACK_NAME, stack.stack_name)) # Confirm existence of a heat-generated nova compute resource logging.info('Confirming heat stack resource status...') resource = self.heat_client.resources.get(STACK_NAME, RESOURCE_TYPE) server_id = resource.physical_resource_id - if server_id: - logging.debug('Heat template spawned nova instance, ' - 'ID: {}'.format(server_id)) - else: - msg = 'Stack failed to spawn a nova compute resource (instance).' - logging.error(msg) + self.assertTrue(server_id, "Stack failed to spawn a compute resource.") # Confirm nova instance reaches ACTIVE status openstack_utils.resource_reaches_status(self.nova_client.servers, @@ -222,18 +184,6 @@ class HeatBasicDeployment(test_utils.OpenStackBaseTest): openstack_utils.delete_resource(self.heat_client.stacks, STACK_NAME, msg="heat stack") - # Delete image - logging.info('Deleting glance image...') - image = self.nova_client.glance.find_image(IMAGE_NAME) - openstack_utils.delete_resource(self.glance_client.images, - image.id, msg="glance image") - - # Delete keypair - logging.info('Deleting keypair...') - openstack_utils.delete_resource(self.nova_client.keypairs, - nova_utils.KEYPAIR_NAME, - msg="nova keypair") - def test_500_auth_encryption_key_same_on_units(self): """Test the auth_encryption_key in heat.conf is same on all units.""" logging.info("Checking the 'auth_encryption_key' is the same on " @@ -252,10 +202,9 @@ class HeatBasicDeployment(test_utils.OpenStackBaseTest): keys[r['UnitId']] = k # see if keys are different ks = set(keys.values()) - if len(ks) != 1: - msg = ("'auth_encryption_key' is not identical on every unit: {}" - .format("{}={}".format(k, v) for k, v in keys.items())) - assert False, msg + self.assertEqual(len(ks), 1, "'auth_encryption_key' is not identical " + "on every unit: {}".format("{}={}".format(k, v) + for k, v in keys.items())) @staticmethod def _run_arbitrary(command, timeout=300):