Merge "Add a test for deleting a suspended server"
diff --git a/HACKING.rst b/HACKING.rst
index 025bf74..29d5bf4 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -227,3 +227,48 @@
 
 2. The unit tests cannot use setUpClass, instead fixtures and testresources
    should be used for shared state between tests.
+
+
+.. _TestDocumentation:
+
+Test Documentation
+------------------
+For tests being added we need to require inline documentation in the form of
+docstings to explain what is being tested. In API tests for a new API a class
+level docstring should be added to an API reference doc. If one doesn't exist
+a TODO comment should be put indicating that the reference needs to be added.
+For individual API test cases a method level docstring should be used to
+explain the functionality being tested if the test name isn't descriptive
+enough. For example::
+
+    def test_get_role_by_id(self):
+        """Get a role by its id."""
+
+the docstring there is superfluous and shouldn't be added. but for a method
+like::
+
+    def test_volume_backup_create_get_detailed_list_restore_delete(self):
+        pass
+
+a docstring would be useful because while the test title is fairly descriptive
+the operations being performed are complex enough that a bit more explanation
+will help people figure out the intent of the test.
+
+For scenario tests a class level docstring describing the steps in the scenario
+is required. If there is more than one test case in the class individual
+docstrings for the workflow in each test methods can be used instead. A good
+example of this would be::
+
+    class TestVolumeBootPattern(manager.OfficialClientTest):
+    """
+    This test case attempts to reproduce the following steps:
+
+     * Create in Cinder some bootable volume importing a Glance image
+     * Boot an instance from the bootable volume
+     * Write content to the volume
+     * Delete an instance and Boot a new instance from the volume
+     * Check written content in the instance
+     * Create a volume snapshot while the instance is running
+     * Boot an additional instance from the new snapshot based volume
+     * Check written content in the instance booted from snapshot
+    """
diff --git a/REVIEWING.rst b/REVIEWING.rst
index d6dc83e..74bd2ad 100644
--- a/REVIEWING.rst
+++ b/REVIEWING.rst
@@ -51,6 +51,15 @@
 whether to skip or not.
 
 
+Test Documentation
+------------------
+When a new test is being added refer to the :ref:`TestDocumentation` section in
+hacking to see if the requirements are being met. With the exception of a class
+level docstring linking to the API ref doc in the API tests and a docstring for
+scenario tests this is up to the reviewers discretion whether a docstring is
+required or not.
+
+
 When to approve
 ---------------
  * Every patch needs two +2s before being approved.
diff --git a/requirements.txt b/requirements.txt
index 9a3b74d..708ede3 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,25 +1,28 @@
+# The order of packages is significant, because pip processes them in the order
+# of appearance. Changing the order has an impact on the overall integration
+# process, which may cause wedges in the gate later.
 pbr>=0.6,!=0.7,<1.0
 anyjson>=0.3.3
 httplib2>=0.7.5
 jsonschema>=2.0.0,<3.0.0
 testtools>=0.9.34
 lxml>=2.3
-boto>=2.12.0,!=2.13.0
+boto>=2.32.1
 paramiko>=1.13.0
-netaddr>=0.7.6
+netaddr>=0.7.12
 python-ceilometerclient>=1.0.6
-python-glanceclient>=0.13.1
-python-keystoneclient>=0.9.0
-python-novaclient>=2.17.0
-python-neutronclient>=2.3.5,<3
-python-cinderclient>=1.0.7
+python-glanceclient>=0.14.0
+python-keystoneclient>=0.10.0
+python-novaclient>=2.18.0
+python-neutronclient>=2.3.6,<3
+python-cinderclient>=1.1.0
 python-heatclient>=0.2.9
-python-ironicclient
-python-saharaclient>=0.6.0
-python-swiftclient>=2.0.2
+python-ironicclient>=0.2.1
+python-saharaclient>=0.7.3
+python-swiftclient>=2.2.0
 testresources>=0.2.4
 testrepository>=0.0.18
-oslo.config>=1.2.1
+oslo.config>=1.4.0  # Apache-2.0
 six>=1.7.0
 iso8601>=0.1.9
 fixtures>=0.3.14
diff --git a/tempest/api/compute/servers/test_server_rescue.py b/tempest/api/compute/servers/test_server_rescue.py
index 25f24b9..a984ade 100644
--- a/tempest/api/compute/servers/test_server_rescue.py
+++ b/tempest/api/compute/servers/test_server_rescue.py
@@ -64,7 +64,8 @@
     def resource_cleanup(cls):
         # Deleting the floating IP which is created in this method
         cls.floating_ips_client.delete_floating_ip(cls.floating_ip_id)
-        cls.delete_volume(cls.volume['id'])
+        if getattr(cls, 'volume', None):
+            cls.delete_volume(cls.volume['id'])
         resp, cls.sg = cls.security_groups_client.delete_security_group(
             cls.sg_id)
         super(ServerRescueTestJSON, cls).resource_cleanup()
diff --git a/tempest/api/compute/servers/test_server_rescue_negative.py b/tempest/api/compute/servers/test_server_rescue_negative.py
index aa406f7..0d29968 100644
--- a/tempest/api/compute/servers/test_server_rescue_negative.py
+++ b/tempest/api/compute/servers/test_server_rescue_negative.py
@@ -56,7 +56,8 @@
 
     @classmethod
     def resource_cleanup(cls):
-        cls.delete_volume(cls.volume['id'])
+        if getattr(cls, 'volume', None):
+            cls.delete_volume(cls.volume['id'])
         super(ServerRescueNegativeTestJSON, cls).resource_cleanup()
 
     def _detach(self, server_id, volume_id):
diff --git a/tempest/api/network/test_fwaas_extensions.py b/tempest/api/network/test_fwaas_extensions.py
index 193bf76..11588d6 100644
--- a/tempest/api/network/test_fwaas_extensions.py
+++ b/tempest/api/network/test_fwaas_extensions.py
@@ -86,7 +86,6 @@
                  (fw_id, target_states))
             raise exceptions.TimeoutException(m)
 
-    @test.attr(type='smoke')
     def test_list_firewall_rules(self):
         # List firewall rules
         _, fw_rules = self.client.list_firewall_rules()
@@ -104,7 +103,6 @@
                         m['ip_version'],
                         m['enabled']) for m in fw_rules])
 
-    @test.attr(type='smoke')
     def test_create_update_delete_firewall_rule(self):
         # Create firewall rule
         _, body = self.client.create_firewall_rule(
@@ -125,14 +123,12 @@
         self.assertNotIn(fw_rule_id,
                          [m['id'] for m in fw_rules['firewall_rules']])
 
-    @test.attr(type='smoke')
     def test_show_firewall_rule(self):
         # show a created firewall rule
         _, fw_rule = self.client.show_firewall_rule(self.fw_rule['id'])
         for key, value in fw_rule['firewall_rule'].iteritems():
             self.assertEqual(self.fw_rule[key], value)
 
-    @test.attr(type='smoke')
     def test_list_firewall_policies(self):
         _, fw_policies = self.client.list_firewall_policies()
         fw_policies = fw_policies['firewall_policies']
@@ -143,7 +139,6 @@
                         m['name'],
                         m['firewall_rules']) for m in fw_policies])
 
-    @test.attr(type='smoke')
     def test_create_update_delete_firewall_policy(self):
         # Create firewall policy
         _, body = self.client.create_firewall_policy(
@@ -166,7 +161,6 @@
         fw_policies = fw_policies['firewall_policies']
         self.assertNotIn(fw_policy_id, [m['id'] for m in fw_policies])
 
-    @test.attr(type='smoke')
     def test_show_firewall_policy(self):
         # show a created firewall policy
         _, fw_policy = self.client.show_firewall_policy(self.fw_policy['id'])
@@ -174,7 +168,6 @@
         for key, value in fw_policy.iteritems():
             self.assertEqual(self.fw_policy[key], value)
 
-    @test.attr(type='smoke')
     def test_create_show_delete_firewall(self):
         # Create tenant network resources required for an ACTIVE firewall
         network = self.create_network()
diff --git a/tempest/cmd/javelin.py b/tempest/cmd/javelin.py
index bf0cc70..27e388d 100755
--- a/tempest/cmd/javelin.py
+++ b/tempest/cmd/javelin.py
@@ -525,7 +525,8 @@
     for volume in volumes:
         client = client_for_user(volume['owner'])
         volume_id = _get_volume_by_name(client, volume['name'])['id']
-        r, body = client.volumes.delete_volume(volume_id)
+        client.volumes.detach_volume(volume_id)
+        client.volumes.delete_volume(volume_id)
 
 
 def attach_volumes(volumes):
diff --git a/tempest/scenario/utils.py b/tempest/scenario/utils.py
index e2adb34..c20f20c 100644
--- a/tempest/scenario/utils.py
+++ b/tempest/scenario/utils.py
@@ -40,33 +40,33 @@
         self.non_ssh_image_pattern = \
             CONF.input_scenario.non_ssh_image_regex
         # Setup clients
-        ocm = clients.OfficialClientManager(
-            auth.get_default_credentials('user'))
-        self.client = ocm.compute_client
+        os = clients.Manager()
+        self.images_client = os.images_client
+        self.flavors_client = os.flavors_client
 
     def ssh_user(self, image_id):
-        _image = self.client.images.get(image_id)
+        _, _image = self.images_client.get_image(image_id)
         for regex, user in self.ssh_users:
             # First match wins
-            if re.match(regex, _image.name) is not None:
+            if re.match(regex, _image['name']) is not None:
                 return user
         else:
             return self.default_ssh_user
 
     def _is_sshable_image(self, image):
         return not re.search(pattern=self.non_ssh_image_pattern,
-                             string=str(image.name))
+                             string=str(image['name']))
 
     def is_sshable_image(self, image_id):
-        _image = self.client.images.get(image_id)
+        _, _image = self.images_client.get_image(image_id)
         return self._is_sshable_image(_image)
 
     def _is_flavor_enough(self, flavor, image):
-        return image.minDisk <= flavor.disk
+        return image['minDisk'] <= flavor['disk']
 
     def is_flavor_enough(self, flavor_id, image_id):
-        _image = self.client.images.get(image_id)
-        _flavor = self.client.flavors.get(flavor_id)
+        _, _image = self.images_client.get_image(image_id)
+        _, _flavor = self.flavors_client.get_flavor_details(flavor_id)
         return self._is_flavor_enough(_flavor, _image)
 
 
@@ -81,7 +81,7 @@
     load_tests = testscenarios.load_tests_apply_scenarios
 
 
-    class TestInputScenario(manager.OfficialClientTest):
+    class TestInputScenario(manager.ScenarioTest):
 
         scenario_utils = utils.InputScenarioUtils()
         scenario_flavor = scenario_utils.scenario_flavors
@@ -91,17 +91,18 @@
 
         def test_create_server_metadata(self):
             name = rand_name('instance')
-            _ = self.compute_client.servers.create(name=name,
-                                                   flavor=self.flavor_ref,
-                                                   image=self.image_ref)
+            self.servers_client.create_server(name=name,
+                                              flavor_ref=self.flavor_ref,
+                                              image_ref=self.image_ref)
     """
     validchars = "-_.{ascii}{digit}".format(ascii=string.ascii_letters,
                                             digit=string.digits)
 
     def __init__(self):
-        ocm = clients.OfficialClientManager(
+        os = clients.Manager(
             auth.get_default_credentials('user', fill_in=False))
-        self.client = ocm.compute_client
+        self.images_client = os.images_client
+        self.flavors_client = os.flavors_client
         self.image_pattern = CONF.input_scenario.image_regex
         self.flavor_pattern = CONF.input_scenario.flavor_regex
 
@@ -118,10 +119,11 @@
         if not CONF.service_available.glance:
             return []
         if not hasattr(self, '_scenario_images'):
-            images = self.client.images.list(detailed=False)
+            _, images = self.images_client.list_images()
             self._scenario_images = [
-                (self._normalize_name(i.name), dict(image_ref=i.id))
-                for i in images if re.search(self.image_pattern, str(i.name))
+                (self._normalize_name(i['name']), dict(image_ref=i['id']))
+                for i in images if re.search(self.image_pattern,
+                                             str(i['name']))
             ]
         return self._scenario_images
 
@@ -131,10 +133,11 @@
         :return: a scenario with name and uuid of flavors
         """
         if not hasattr(self, '_scenario_flavors'):
-            flavors = self.client.flavors.list(detailed=False)
+            _, flavors = self.flavors_client.list_flavors()
             self._scenario_flavors = [
-                (self._normalize_name(f.name), dict(flavor_ref=f.id))
-                for f in flavors if re.search(self.flavor_pattern, str(f.name))
+                (self._normalize_name(f['name']), dict(flavor_ref=f['id']))
+                for f in flavors if re.search(self.flavor_pattern,
+                                              str(f['name']))
             ]
         return self._scenario_flavors
 
diff --git a/test-requirements.txt b/test-requirements.txt
index cd8154b..ba70259 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -1,10 +1,13 @@
+# The order of packages is significant, because pip processes them in the order
+# of appearance. Changing the order has an impact on the overall integration
+# process, which may cause wedges in the gate later.
 hacking>=0.9.2,<0.10
 # needed for doc build
 sphinx>=1.1.2,!=1.2.0,<1.3
 python-subunit>=0.0.18
-oslosphinx
+oslosphinx>=2.2.0  # Apache-2.0
 mox>=0.5.3
 mock>=1.0
 coverage>=3.6
-oslotest
-stevedore>=0.14
+oslotest>=1.1.0  # Apache-2.0
+stevedore>=1.0.0  # Apache-2.0