From d2525a9c5ebf194a77b16070c697b6309282d148 Mon Sep 17 00:00:00 2001 From: Steve Baker Date: Mon, 6 May 2013 15:29:03 +1200 Subject: [PATCH] Initial heat orchestration tests. This does a creat/list/show/delete of a stack with no resources. It should run fast and is suitable for gating, since it doesn't create any nova instances. I'd consider it to be a smoke test. It confirms that heat-api can communicate with heat-engine, and that heat-engine has a properly configured database. Blueprint: add-basic-heat-tests Change-Id: Id249a34c3a28e1e69d9eb000c6c5c2e82d619178 --- tempest/api/orchestration/__init__.py | 0 tempest/api/orchestration/base.py | 110 ++++++++++++++++++ tempest/api/orchestration/stacks/__init__.py | 0 .../api/orchestration/stacks/test_stacks.py | 77 ++++++++++++ 4 files changed, 187 insertions(+) create mode 100644 tempest/api/orchestration/__init__.py create mode 100644 tempest/api/orchestration/base.py create mode 100644 tempest/api/orchestration/stacks/__init__.py create mode 100644 tempest/api/orchestration/stacks/test_stacks.py diff --git a/tempest/api/orchestration/__init__.py b/tempest/api/orchestration/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tempest/api/orchestration/base.py b/tempest/api/orchestration/base.py new file mode 100644 index 0000000000..544558ef56 --- /dev/null +++ b/tempest/api/orchestration/base.py @@ -0,0 +1,110 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# 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 logging +import time + +from tempest import clients +from tempest.common.utils.data_utils import rand_name +import tempest.test + + +LOG = logging.getLogger(__name__) + + +class BaseOrchestrationTest(tempest.test.BaseTestCase): + """Base test case class for all Orchestration API tests.""" + + @classmethod + def setUpClass(cls): + + os = clients.OrchestrationManager() + cls.orchestration_cfg = os.config.orchestration + if not cls.orchestration_cfg.heat_available: + raise cls.skipException("Heat support is required") + + cls.os = os + cls.orchestration_client = os.orchestration_client + cls.keypairs_client = os.keypairs_client + cls.stacks = [] + + @classmethod + def _get_identity_admin_client(cls): + """ + Returns an instance of the Identity Admin API client + """ + os = clients.AdminManager(interface=cls._interface) + admin_client = os.identity_client + return admin_client + + @classmethod + def _get_client_args(cls): + + return ( + cls.config, + cls.config.identity.admin_username, + cls.config.identity.admin_password, + cls.config.identity.uri + ) + + def create_stack(self, stack_name, template_data, parameters={}): + resp, body = self.client.create_stack( + stack_name, + template=template_data, + parameters=parameters) + self.assertEqual('201', resp['status']) + stack_id = resp['location'].split('/')[-1] + stack_identifier = '%s/%s' % (stack_name, stack_id) + self.stacks.append(stack_identifier) + return stack_identifier + + @classmethod + def clear_stacks(cls): + for stack_identifier in cls.stacks: + try: + cls.orchestration_client.delete_stack(stack_identifier) + except Exception: + pass + + for stack_identifier in cls.stacks: + try: + cls.orchestration_client.wait_for_stack_status( + stack_identifier, 'DELETE_COMPLETE') + except Exception: + pass + + def _create_keypair(self, namestart='keypair-heat-'): + kp_name = rand_name(namestart) + resp, body = self.keypairs_client.create_keypair(kp_name) + self.assertEqual(body['name'], kp_name) + return body + + @classmethod + def tearDownClass(cls): + cls.clear_stacks() + + def wait_for(self, condition): + """Repeatedly calls condition() until a timeout.""" + start_time = int(time.time()) + while True: + try: + condition() + except Exception: + pass + else: + return + if int(time.time()) - start_time >= self.build_timeout: + condition() + return + time.sleep(self.build_interval) diff --git a/tempest/api/orchestration/stacks/__init__.py b/tempest/api/orchestration/stacks/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tempest/api/orchestration/stacks/test_stacks.py b/tempest/api/orchestration/stacks/test_stacks.py new file mode 100644 index 0000000000..8847c08d2c --- /dev/null +++ b/tempest/api/orchestration/stacks/test_stacks.py @@ -0,0 +1,77 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# 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 logging + +from tempest.api.orchestration import base +from tempest.common.utils.data_utils import rand_name +from tempest.test import attr + + +LOG = logging.getLogger(__name__) + + +class StacksTestJSON(base.BaseOrchestrationTest): + _interface = 'json' + + empty_template = "HeatTemplateFormatVersion: '2012-12-12'\n" + + @classmethod + def setUpClass(cls): + super(StacksTestJSON, cls).setUpClass() + cls.client = cls.orchestration_client + + @attr(type='smoke') + def test_stack_list_responds(self): + resp, body = self.client.list_stacks() + stacks = body['stacks'] + self.assertEqual('200', resp['status']) + self.assertIsInstance(stacks, list) + + @attr(type='smoke') + def test_stack_crud_no_resources(self): + stack_name = rand_name('heat') + + # count how many stacks to start with + resp, body = self.client.list_stacks() + stack_count = len(body['stacks']) + + # create the stack + stack_identifier = self.create_stack( + stack_name, self.empty_template) + + # wait for create complete (with no resources it should be instant) + self.client.wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE') + + # stack count will increment by 1 + resp, body = self.client.list_stacks() + self.assertEqual(stack_count + 1, len(body['stacks']), + 'Expected stack count to increment by 1') + + # fetch the stack + resp, body = self.client.get_stack(stack_identifier) + self.assertEqual('CREATE_COMPLETE', body['stack_status']) + + # fetch the stack by name + resp, body = self.client.get_stack(stack_name) + self.assertEqual('CREATE_COMPLETE', body['stack_status']) + + # fetch the stack by id + stack_id = stack_identifier.split('/')[1] + resp, body = self.client.get_stack(stack_id) + self.assertEqual('CREATE_COMPLETE', body['stack_status']) + + # delete the stack + resp = self.client.delete_stack(stack_identifier) + self.assertEqual('204', resp[0]['status'])