Merge "Initial heat orchestration tests."
diff --git a/tempest/api/orchestration/__init__.py b/tempest/api/orchestration/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/api/orchestration/__init__.py
diff --git a/tempest/api/orchestration/base.py b/tempest/api/orchestration/base.py
new file mode 100644
index 0000000..544558e
--- /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 0000000..e69de29
--- /dev/null
+++ b/tempest/api/orchestration/stacks/__init__.py
diff --git a/tempest/api/orchestration/stacks/test_stacks.py b/tempest/api/orchestration/stacks/test_stacks.py
new file mode 100644
index 0000000..8847c08
--- /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'])