Merge "Negative test added for rescuing a paused vm"
diff --git a/openstack-common.conf b/openstack-common.conf
index 8568f22..ff84404 100644
--- a/openstack-common.conf
+++ b/openstack-common.conf
@@ -4,6 +4,7 @@
 module=install_venv_common
 module=lockutils
 module=log
+module=importlib
 
 # The base module to hold the copy of openstack.common
 base=tempest
diff --git a/requirements.txt b/requirements.txt
index 06aa9f3..cc61b01 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -19,3 +19,4 @@
 oslo.config>=1.1.0
 # Needed for whitebox testing
 sqlalchemy
+eventlet>=0.12.0
diff --git a/tempest/api/compute/admin/test_services.py b/tempest/api/compute/admin/test_services.py
index ce16353..dc960db 100644
--- a/tempest/api/compute/admin/test_services.py
+++ b/tempest/api/compute/admin/test_services.py
@@ -69,7 +69,10 @@
         # lookups, so only compare binary lists.
         s1 = map(lambda x: x['binary'], services)
         s2 = map(lambda x: x['binary'], services_on_host)
-        self.assertEqual(s1, s2)
+
+        #sort the lists before comparing, to take out dependency
+        #on order.
+        self.assertEqual(sorted(s1), sorted(s2))
 
     @attr(type=['negative', 'gate'])
     def test_get_service_by_invalid_params(self):
diff --git a/tempest/api/compute/servers/test_servers_negative.py b/tempest/api/compute/servers/test_servers_negative.py
index af58b5f..703f143 100644
--- a/tempest/api/compute/servers/test_servers_negative.py
+++ b/tempest/api/compute/servers/test_servers_negative.py
@@ -221,13 +221,10 @@
     @attr(type=['negative', 'gate'])
     def test_delete_a_server_of_another_tenant(self):
         # Delete a server that belongs to another tenant
-        try:
-            resp, server = self.create_server(wait_until='ACTIVE')
-            self.assertRaises(exceptions.NotFound,
-                              self.alt_client.delete_server,
-                              server['id'])
-        finally:
-            self.client.delete_server(server['id'])
+        resp, server = self.create_server(wait_until='ACTIVE')
+        self.assertRaises(exceptions.NotFound,
+                          self.alt_client.delete_server,
+                          server['id'])
 
     @attr(type=['negative', 'gate'])
     def test_delete_server_pass_negative_id(self):
diff --git a/tempest/stress/actions/volume_attach_delete.py b/tempest/stress/actions/volume_attach_delete.py
new file mode 100644
index 0000000..a7b872f
--- /dev/null
+++ b/tempest/stress/actions/volume_attach_delete.py
@@ -0,0 +1,70 @@
+# (c) 2013 Deutsche Telekom AG
+#    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.
+
+from tempest.common.utils.data_utils import rand_name
+import tempest.stress.stressaction as stressaction
+
+
+class VolumeAttachDeleteTest(stressaction.StressAction):
+
+    def setUp(self, **kwargs):
+        self.image = self.manager.config.compute.image_ref
+        self.flavor = self.manager.config.compute.flavor_ref
+
+    def run(self):
+        # Step 1: create volume
+        name = rand_name("volume")
+        self.logger.info("creating volume: %s" % name)
+        resp, volume = self.manager.volumes_client.create_volume(size=1,
+                                                                 display_name=
+                                                                 name)
+        assert(resp.status == 200)
+        self.manager.volumes_client.wait_for_volume_status(volume['id'],
+                                                           'available')
+        self.logger.info("created volume: %s" % volume['id'])
+
+        # Step 2: create vm instance
+        vm_name = rand_name("instance")
+        self.logger.info("creating vm: %s" % vm_name)
+        resp, server = self.manager.servers_client.create_server(
+            vm_name, self.image, self.flavor)
+        server_id = server['id']
+        assert(resp.status == 202)
+        self.manager.servers_client.wait_for_server_status(server_id, 'ACTIVE')
+        self.logger.info("created vm %s" % server_id)
+
+        # Step 3: attach volume to vm
+        self.logger.info("attach volume (%s) to vm %s" %
+                        (volume['id'], server_id))
+        resp, body = self.manager.servers_client.attach_volume(server_id,
+                                                               volume['id'],
+                                                               '/dev/vdc')
+        assert(resp.status == 200)
+        self.manager.volumes_client.wait_for_volume_status(volume['id'],
+                                                           'in-use')
+        self.logger.info("volume (%s) attached to vm %s" %
+                         (volume['id'], server_id))
+
+        # Step 4: delete vm
+        self.logger.info("deleting vm: %s" % vm_name)
+        resp, _ = self.manager.servers_client.delete_server(server_id)
+        assert(resp.status == 204)
+        self.manager.servers_client.wait_for_server_termination(server_id)
+        self.logger.info("deleted vm: %s" % server_id)
+
+        # Step 5: delete volume
+        self.logger.info("deleting volume: %s" % volume['id'])
+        resp, _ = self.manager.volumes_client.delete_volume(volume['id'])
+        assert(resp.status == 202)
+        self.manager.volumes_client.wait_for_resource_deletion(volume['id'])
+        self.logger.info("deleted volume: %s" % volume['id'])
diff --git a/tempest/stress/driver.py b/tempest/stress/driver.py
index d170eb8..c4c2041 100644
--- a/tempest/stress/driver.py
+++ b/tempest/stress/driver.py
@@ -12,7 +12,6 @@
 #    See the License for the specific language governing permissions and
 #    limitations under the License.
 
-import importlib
 import logging
 import multiprocessing
 import time
@@ -21,6 +20,7 @@
 from tempest.common import ssh
 from tempest.common.utils.data_utils import rand_name
 from tempest import exceptions
+from tempest.openstack.common import importutils
 from tempest.stress import cleanup
 
 admin_manager = clients.AdminManager()
@@ -93,11 +93,6 @@
     return None
 
 
-def get_action_object(path):
-    (module_part, _, obj_name) = path.rpartition('.')
-    return getattr(importlib.import_module(module_part), obj_name)
-
-
 def stress_openstack(tests, duration, max_runs=None):
     """
     Workload driver. Executes an action function against a nova-cluster.
@@ -131,7 +126,7 @@
                                           password="pass",
                                           tenant_name=tenant_name)
 
-            test_obj = get_action_object(test['action'])
+            test_obj = importutils.import_class(test['action'])
             test_run = test_obj(manager, logger, max_runs)
 
             kwargs = test.get('kwargs', {})
diff --git a/tempest/stress/etc/stress-tox-job.json b/tempest/stress/etc/stress-tox-job.json
index 159794b..3534c26 100644
--- a/tempest/stress/etc/stress-tox-job.json
+++ b/tempest/stress/etc/stress-tox-job.json
@@ -9,5 +9,11 @@
   "use_admin": false,
   "use_isolated_tenants": false,
   "kwargs": {}
+  },
+  {"action": "tempest.stress.actions.volume_attach_delete.VolumeAttachDeleteTest",
+  "threads": 2,
+  "use_admin": false,
+  "use_isolated_tenants": false,
+  "kwargs": {}
   }
 ]
diff --git a/tempest/stress/etc/volume-assign-delete-test.json b/tempest/stress/etc/volume-assign-delete-test.json
new file mode 100644
index 0000000..4553ff8
--- /dev/null
+++ b/tempest/stress/etc/volume-assign-delete-test.json
@@ -0,0 +1,7 @@
+[{"action": "tempest.stress.actions.volume_attach_delete.VolumeAttachDeleteTest",
+  "threads": 4,
+  "use_admin": false,
+  "use_isolated_tenants": false,
+  "kwargs": {}
+  }
+]
diff --git a/tempest/thirdparty/boto/test_s3_ec2_images.py b/tempest/thirdparty/boto/test_s3_ec2_images.py
index 5e1e2cb..e2ca15f 100644
--- a/tempest/thirdparty/boto/test_s3_ec2_images.py
+++ b/tempest/thirdparty/boto/test_s3_ec2_images.py
@@ -22,7 +22,6 @@
 from tempest.test import attr
 from tempest.thirdparty.boto.test import BotoTestCase
 from tempest.thirdparty.boto.utils.s3 import s3_upload_dir
-from tempest.thirdparty.boto.utils.wait import state_wait
 
 
 class S3ImagesTest(BotoTestCase):
@@ -51,8 +50,6 @@
                                cls.bucket_name)
         s3_upload_dir(bucket, cls.materials_path)
 
-    #Note(afazekas): Without the normal status change test!
-    # otherwise I would skip it too
     @attr(type='smoke')
     def test_register_get_deregister_ami_image(self):
         # Register and deregister ami image
@@ -70,13 +67,8 @@
         retrieved_image = self.images_client.get_image(image["image_id"])
         self.assertTrue(retrieved_image.name == image["name"])
         self.assertTrue(retrieved_image.id == image["image_id"])
-        state = retrieved_image.state
-        if state != "available":
-            def _state():
-                retr = self.images_client.get_image(image["image_id"])
-                return retr.state
-            state = state_wait(_state, "available")
-        self.assertEqual("available", state)
+        if retrieved_image.state != "available":
+            self.assertImageStateWait(retrieved_image, "available")
         self.images_client.deregister_image(image["image_id"])
         self.assertNotIn(image["image_id"], str(
             self.images_client.get_all_images()))
diff --git a/tools/pretty_tox_serial.sh b/tools/pretty_tox_serial.sh
new file mode 100755
index 0000000..490d263
--- /dev/null
+++ b/tools/pretty_tox_serial.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+TESTRARGS=$@
+
+if [ ! -d .testrepository ]; then
+    testr init
+fi
+testr run --subunit $TESTRARGS | subunit-2to1 | tools/colorizer.py
+testr slowest
diff --git a/tox.ini b/tox.ini
index 93a53ac..7eae948 100644
--- a/tox.ini
+++ b/tox.ini
@@ -26,6 +26,12 @@
 commands =
   nosetests --logging-format '%(asctime)-15s %(message)s' --with-xunit --xunit-file=nosetests-full.xml -sv tempest/api tempest/scenario tempest/thirdparty tempest/cli
 
+[testenv:testr-serial]
+sitepackages = True
+setenv = VIRTUAL_ENV={envdir}
+commands =
+  sh tools/pretty_tox_serial.sh '{posargs} tempest.api tempest.scenario tempest.thirdparty tempest.cli'
+
 [testenv:testr-full]
 sitepackages = True
 setenv = VIRTUAL_ENV={envdir}