Merge "Multi-server handling in base.py"
diff --git a/tempest/api/compute/images/test_images_oneserver.py b/tempest/api/compute/images/test_images_oneserver.py
index c7f0b23..4163245 100644
--- a/tempest/api/compute/images/test_images_oneserver.py
+++ b/tempest/api/compute/images/test_images_oneserver.py
@@ -131,7 +131,7 @@
         # Verify the image was deleted correctly
         resp, body = self.client.delete_image(image_id)
         self.assertEqual('204', resp['status'])
-        self.assertRaises(exceptions.NotFound, self.client.get_image, image_id)
+        self.client.wait_for_resource_deletion(image_id)
 
     @testtools.skipUnless(compute.MULTI_USER,
                           'Need multiple users for this test.')
diff --git a/tempest/api/compute/servers/test_list_servers_negative.py b/tempest/api/compute/servers/test_list_servers_negative.py
index 0f35ee5..db9bdc1 100644
--- a/tempest/api/compute/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/servers/test_list_servers_negative.py
@@ -15,6 +15,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import datetime
 
 from tempest.api import compute
 from tempest.api.compute import base
@@ -170,7 +171,8 @@
     @attr(type='gate')
     def test_list_servers_by_changes_since(self):
         # Servers are listed by specifying changes-since date
-        changes_since = {'changes-since': '2011-01-01T12:34:00Z'}
+        since = datetime.datetime.utcnow() - datetime.timedelta(minutes=2)
+        changes_since = {'changes-since': since.isoformat()}
         resp, body = self.client.list_servers(changes_since)
         self.assertEqual('200', resp['status'])
         # changes-since returns all instances, including deleted.
diff --git a/tempest/api/image/v1/test_images.py b/tempest/api/image/v1/test_images.py
index c5d3f93..640daa5 100644
--- a/tempest/api/image/v1/test_images.py
+++ b/tempest/api/image/v1/test_images.py
@@ -47,7 +47,6 @@
                                        properties=properties)
         self.assertTrue('id' in body)
         image_id = body.get('id')
-        self.created_images.append(image_id)
         self.assertEqual('New Name', body.get('name'))
         self.assertTrue(body.get('is_public'))
         self.assertEqual('queued', body.get('status'))
@@ -71,8 +70,6 @@
                                        properties={'key1': 'value1',
                                                    'key2': 'value2'})
         self.assertTrue('id' in body)
-        image_id = body.get('id')
-        self.created_images.append(image_id)
         self.assertEqual('New Remote Image', body.get('name'))
         self.assertTrue(body.get('is_public'))
         self.assertEqual('active', body.get('status'))
@@ -88,7 +85,6 @@
                                        copy_from=self.config.images.http_image)
         self.assertTrue('id' in body)
         image_id = body.get('id')
-        self.created_images.append(image_id)
         self.assertEqual('New Http Image', body.get('name'))
         self.assertTrue(body.get('is_public'))
         self.client.wait_for_image_status(image_id, 'active')
@@ -106,8 +102,6 @@
                                        min_ram=40,
                                        properties=properties)
         self.assertTrue('id' in body)
-        image_id = body.get('id')
-        self.created_images.append(image_id)
         self.assertEqual('New_image_with_min_ram', body.get('name'))
         self.assertTrue(body.get('is_public'))
         self.assertEqual('queued', body.get('status'))
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index 966adc3..34db6e3 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -50,7 +50,6 @@
                                        visibility='public')
         self.assertTrue('id' in body)
         image_id = body.get('id')
-        self.created_images.append(image_id)
         self.assertTrue('name' in body)
         self.assertEqual('New Name', body.get('name'))
         self.assertTrue('visibility' in body)
@@ -79,7 +78,7 @@
         # We add a few images here to test the listing functionality of
         # the images API
         for x in xrange(0, 10):
-            cls.created_images.append(cls._create_standard_image(x))
+            cls._create_standard_image(x)
 
     @classmethod
     def _create_standard_image(cls, number):
diff --git a/tempest/api/object_storage/test_object_services.py b/tempest/api/object_storage/test_object_services.py
index 2b8feef..31e7eb9 100644
--- a/tempest/api/object_storage/test_object_services.py
+++ b/tempest/api/object_storage/test_object_services.py
@@ -17,8 +17,6 @@
 
 import time
 
-import testtools
-
 from tempest.api.object_storage import base
 from tempest.common.utils.data_utils import arbitrary_string
 from tempest.common.utils.data_utils import rand_name
@@ -443,71 +441,3 @@
         except Exception as e:
             self.fail("Failed to get public readable object with another"
                       " user creds raised exception is %s" % e)
-
-    @testtools.skip('Until Bug #1020722 is resolved.')
-    @attr(type='smoke')
-    def test_write_public_object_without_using_creds(self):
-        # make container public-writable, and create object anonymously, e.g.
-        # without using credentials
-        try:
-            # update container metadata to make publicly writable
-            cont_headers = {'X-Container-Write': '-*'}
-            resp_meta, body = self.container_client.update_container_metadata(
-                self.container_name, metadata=cont_headers, metadata_prefix='')
-            self.assertEqual(resp_meta['status'], '204')
-            # list container metadata
-            resp, _ = self.container_client.list_container_metadata(
-                self.container_name)
-            self.assertEqual(resp['status'], '204')
-            self.assertIn('x-container-write', resp)
-            self.assertEqual(resp['x-container-write'], '-*')
-
-            object_name = rand_name(name='Object')
-            data = arbitrary_string(size=len(object_name),
-                                    base_text=object_name)
-            headers = {'Content-Type': 'application/json',
-                       'Accept': 'application/json'}
-            # create object as anonymous user
-            resp, body = self.custom_object_client.create_object(
-                self.container_name, object_name, data, metadata=headers)
-            self.assertEqual(resp['status'], '201')
-
-        except Exception as e:
-            self.fail("Failed to create public writable object without using"
-                      " creds raised exception is %s" % e)
-
-    @testtools.skip('Until Bug #1020722 is resolved.')
-    @attr(type='smoke')
-    def test_write_public_with_another_user_creds(self):
-        # make container public-writable, and create object with another user's
-        # credentials
-        try:
-            # update container metadata to make it publicly writable
-            cont_headers = {'X-Container-Write': '-*'}
-            resp_meta, body = self.container_client.update_container_metadata(
-                self.container_name, metadata=cont_headers,
-                metadata_prefix='')
-            self.assertEqual(resp_meta['status'], '204')
-            # list container metadata
-            resp, _ = self.container_client.list_container_metadata(
-                self.container_name)
-            self.assertEqual(resp['status'], '204')
-            self.assertIn('x-container-write', resp)
-            self.assertEqual(resp['x-container-write'], '-*')
-
-            # trying to get auth token of alternative user
-            token = self.identity_client_alt.get_auth()
-            headers = {'Content-Type': 'application/json',
-                       'Accept': 'application/json',
-                       'X-Auth-Token': token}
-
-            # trying to create an object with another user's creds
-            object_name = rand_name(name='Object')
-            data = arbitrary_string(size=len(object_name),
-                                    base_text=object_name)
-            resp, body = self.custom_object_client.create_object(
-                self.container_name, object_name, data, metadata=headers)
-            self.assertEqual(resp['status'], '201')
-        except Exception as e:
-            self.fail("Failed to create public writable object with another"
-                      " user creds raised exception is %s" % e)
diff --git a/tempest/api/orchestration/stacks/test_instance_cfn_init.py b/tempest/api/orchestration/stacks/test_instance_cfn_init.py
new file mode 100644
index 0000000..2349830
--- /dev/null
+++ b/tempest/api/orchestration/stacks/test_instance_cfn_init.py
@@ -0,0 +1,152 @@
+# 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 json
+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 InstanceCfnInitTestJSON(base.BaseOrchestrationTest):
+    _interface = 'json'
+
+    template = """
+HeatTemplateFormatVersion: '2012-12-12'
+Description: |
+  Template which uses a wait condition to confirm that a minimal
+  cfn-init and cfn-signal has worked
+Parameters:
+  KeyName:
+    Type: String
+  InstanceType:
+    Type: String
+  ImageId:
+    Type: String
+Resources:
+  CfnUser:
+    Type: AWS::IAM::User
+  SmokeKeys:
+    Type: AWS::IAM::AccessKey
+    Properties:
+      UserName: {Ref: CfnUser}
+  SmokeServer:
+    Type: AWS::EC2::Instance
+    Metadata:
+      AWS::CloudFormation::Init:
+        config:
+          files:
+            /tmp/smoke-status:
+              content: smoke test complete
+            /etc/cfn/cfn-credentials:
+              content:
+                Fn::Join:
+                - ''
+                - - AWSAccessKeyId=
+                  - {Ref: SmokeKeys}
+                  - '
+
+                    '
+                  - AWSSecretKey=
+                  - Fn::GetAtt: [SmokeKeys, SecretAccessKey]
+                  - '
+
+                    '
+              mode: '000400'
+              owner: root
+              group: root
+    Properties:
+      ImageId: {Ref: ImageId}
+      InstanceType: {Ref: InstanceType}
+      KeyName: {Ref: KeyName}
+      UserData:
+        Fn::Base64:
+          Fn::Join:
+          - ''
+          - - |-
+                #!/bin/bash -v
+                /opt/aws/bin/cfn-init
+            - |-
+                || error_exit ''Failed to run cfn-init''
+                /opt/aws/bin/cfn-signal -e 0 --data "`cat /tmp/smoke-status`" '
+            - {Ref: WaitHandle}
+            - '''
+
+              '
+  WaitHandle:
+    Type: AWS::CloudFormation::WaitConditionHandle
+  WaitCondition:
+    Type: AWS::CloudFormation::WaitCondition
+    DependsOn: SmokeServer
+    Properties:
+      Handle: {Ref: WaitHandle}
+      Timeout: '600'
+Outputs:
+  WaitConditionStatus:
+    Description: Contents of /tmp/smoke-status on SmokeServer
+    Value:
+      Fn::GetAtt: [WaitCondition, Data]
+"""
+
+    @classmethod
+    def setUpClass(cls):
+        super(InstanceCfnInitTestJSON, cls).setUpClass()
+        if not cls.orchestration_cfg.image_ref:
+            raise cls.skipException("No image available to test")
+        cls.client = cls.orchestration_client
+
+    def setUp(self):
+        super(InstanceCfnInitTestJSON, self).setUp()
+        stack_name = rand_name('heat')
+        keypair_name = (self.orchestration_cfg.keypair_name or
+                        self._create_keypair()['name'])
+
+        # create the stack
+        self.stack_identifier = self.create_stack(
+            stack_name,
+            self.template,
+            parameters={
+                'KeyName': keypair_name,
+                'InstanceType': self.orchestration_cfg.instance_type,
+                'ImageId': self.orchestration_cfg.image_ref
+            })
+
+    @attr(type='gate')
+    def test_stack_wait_condition_data(self):
+
+        sid = self.stack_identifier
+
+        # wait for create to complete.
+        self.client.wait_for_stack_status(sid, 'CREATE_COMPLETE')
+
+        # fetch the stack
+        resp, body = self.client.get_stack(sid)
+        self.assertEqual('CREATE_COMPLETE', body['stack_status'])
+
+        # fetch the stack
+        resp, body = self.client.get_stack(sid)
+        self.assertEqual('CREATE_COMPLETE', body['stack_status'])
+
+        # This is an assert of great significance, as it means the following
+        # has happened:
+        # - cfn-init read the provided metadata and wrote out a file
+        # - a user was created and credentials written to the instance
+        # - a cfn-signal was built which was signed with provided credentials
+        # - the wait condition was fulfilled and the stack has changed state
+        wait_status = json.loads(body['outputs'][0]['output_value'])
+        self.assertEqual('smoke test complete', wait_status['00000'])
diff --git a/tempest/cli/simple_read_only/test_keystone.py b/tempest/cli/simple_read_only/test_keystone.py
index 067f58c..45d519b 100644
--- a/tempest/cli/simple_read_only/test_keystone.py
+++ b/tempest/cli/simple_read_only/test_keystone.py
@@ -107,3 +107,14 @@
 
     def test_admin_bashcompletion(self):
         self.keystone('bash-completion')
+
+    # Optional arguments:
+
+    def test_admin_version(self):
+        self.keystone('', flags='--version')
+
+    def test_admin_debug_list(self):
+        self.keystone('catalog', flags='--debug')
+
+    def test_admin_timeout(self):
+        self.keystone('catalog', flags='--timeout 15')
diff --git a/tempest/scenario/test_server_advanced_ops.py b/tempest/scenario/test_server_advanced_ops.py
index 9ac0cc0..6202e91 100644
--- a/tempest/scenario/test_server_advanced_ops.py
+++ b/tempest/scenario/test_server_advanced_ops.py
@@ -28,6 +28,7 @@
     This test case stresses some advanced server instance operations:
 
      * Resizing an instance
+     * Sequence suspend resume
     """
 
     @classmethod
@@ -44,11 +45,6 @@
             msg = "Skipping test - flavor_ref and flavor_ref_alt are identical"
             raise cls.skipException(msg)
 
-    @classmethod
-    def tearDownClass(cls):
-        for thing in cls.resources:
-            thing.delete()
-
     def test_resize_server_confirm(self):
         # We create an instance for use in this test
         i_name = rand_name('instance')
@@ -56,12 +52,8 @@
         base_image_id = self.config.compute.image_ref
         self.instance = self.compute_client.servers.create(
             i_name, base_image_id, flavor_id)
-        try:
-            self.assertEqual(self.instance.name, i_name)
-            self.set_resource('instance', self.instance)
-        except AttributeError:
-            self.fail("Instance not successfully created.")
-
+        self.assertEqual(self.instance.name, i_name)
+        self.set_resource('instance', self.instance)
         self.assertEqual(self.instance.status, 'BUILD')
         instance_id = self.get_resource('instance').id
         self.status_timeout(
@@ -77,5 +69,42 @@
 
         LOG.debug("Confirming resize of instance %s", instance_id)
         instance.confirm_resize()
+
         self.status_timeout(
             self.compute_client.servers, instance_id, 'ACTIVE')
+
+    def test_server_sequence_suspend_resume(self):
+        # We create an instance for use in this test
+        i_name = rand_name('instance')
+        flavor_id = self.config.compute.flavor_ref
+        base_image_id = self.config.compute.image_ref
+        self.instance = self.compute_client.servers.create(
+            i_name, base_image_id, flavor_id)
+        self.assertEqual(self.instance.name, i_name)
+        self.set_resource('instance', self.instance)
+        self.assertEqual(self.instance.status, 'BUILD')
+        instance_id = self.get_resource('instance').id
+        self.status_timeout(
+            self.compute_client.servers, instance_id, 'ACTIVE')
+        instance = self.get_resource('instance')
+        instance_id = instance.id
+        LOG.debug("Suspending instance %s. Current status: %s",
+                  instance_id, instance.status)
+        instance.suspend()
+        self.status_timeout(self.compute_client.servers, instance_id,
+                            'SUSPENDED')
+        LOG.debug("Resuming instance %s. Current status: %s",
+                  instance_id, instance.status)
+        instance.resume()
+        self.status_timeout(self.compute_client.servers, instance_id,
+                            'ACTIVE')
+        LOG.debug("Suspending instance %s. Current status: %s",
+                  instance_id, instance.status)
+        instance.suspend()
+        self.status_timeout(self.compute_client.servers, instance_id,
+                            'SUSPENDED')
+        LOG.debug("Resuming instance %s. Current status: %s",
+                  instance_id, instance.status)
+        instance.resume()
+        self.status_timeout(self.compute_client.servers, instance_id,
+                            'ACTIVE')
diff --git a/tempest/services/compute/json/images_client.py b/tempest/services/compute/json/images_client.py
index 376dafc..b13d0f1 100644
--- a/tempest/services/compute/json/images_client.py
+++ b/tempest/services/compute/json/images_client.py
@@ -150,3 +150,10 @@
         resp, body = self.delete("images/%s/metadata/%s" %
                                  (str(image_id), key))
         return resp, body
+
+    def is_resource_deleted(self, id):
+        try:
+            self.get_image(id)
+        except exceptions.NotFound:
+            return True
+        return False
diff --git a/tempest/services/compute/xml/images_client.py b/tempest/services/compute/xml/images_client.py
index c7e337b..cc13aa1 100644
--- a/tempest/services/compute/xml/images_client.py
+++ b/tempest/services/compute/xml/images_client.py
@@ -226,3 +226,10 @@
         """Deletes a single image metadata key/value pair."""
         return self.delete("images/%s/metadata/%s" % (str(image_id), key),
                            self.headers)
+
+    def is_resource_deleted(self, id):
+        try:
+            self.get_image(id)
+        except exceptions.NotFound:
+            return True
+        return False
diff --git a/tempest/services/identity/v3/json/endpoints_client.py b/tempest/services/identity/v3/json/endpoints_client.py
old mode 100755
new mode 100644
diff --git a/tempest/services/identity/v3/xml/endpoints_client.py b/tempest/services/identity/v3/xml/endpoints_client.py
old mode 100755
new mode 100644