Merge "Move test_volume_manage to unversioned path"
diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst
index 18269bf..2314222 100644
--- a/doc/source/configuration.rst
+++ b/doc/source/configuration.rst
@@ -9,6 +9,8 @@
 config file which explains the purpose of each individual option. You can see
 the sample config file here: :ref:`tempest-sampleconf`
 
+.. _tempest_cred_provider_conf:
+
 Test Credentials
 ----------------
 
@@ -232,6 +234,9 @@
 
 Enabling Remote Access to Created Servers
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. _tempest_conf_network_allocation:
+
 Network Creation/Usage for Servers
 """"""""""""""""""""""""""""""""""
 When Tempest creates servers for testing, some tests require being able to
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 896cd98..1264ecc 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -67,7 +67,8 @@
    HACKING
    REVIEWING
    microversion_testing
-   test-removal
+   test_removal
+   write_tests
 
 -------
 Plugins
diff --git a/doc/source/plugin.rst b/doc/source/plugin.rst
index 6b30825..b3af92f 100644
--- a/doc/source/plugin.rst
+++ b/doc/source/plugin.rst
@@ -1,3 +1,5 @@
+.. _tempest_plugin:
+
 =============================
 Tempest Test Plugin Interface
 =============================
diff --git a/doc/source/test-removal.rst b/doc/source/test_removal.rst
similarity index 100%
rename from doc/source/test-removal.rst
rename to doc/source/test_removal.rst
diff --git a/doc/source/write_tests.rst b/doc/source/write_tests.rst
new file mode 100644
index 0000000..768bf0f
--- /dev/null
+++ b/doc/source/write_tests.rst
@@ -0,0 +1,243 @@
+.. _tempest_test_writing:
+
+Tempest Test Writing Guide
+==========================
+
+This guide serves as a starting point for developers working on writing new
+Tempest tests. At a high level tests in Tempest are just tests that conform to
+the standard python `unit test`_ framework. But there are several aspects of
+that are unique to tempest and it's role as an integration test suite running
+against a real cloud.
+
+.. _unit test: https://docs.python.org/3.6/library/unittest.html
+
+.. note:: This guide is for writing tests in the tempest repository. While many
+          parts of this guide are also applicable to tempest plugins, not all
+          the APIs mentioned are considered stable or recommended for use in
+          plugins. Please refer to :ref:`tempest_plugin` for details about
+          writing plugins
+
+
+Adding a New TestCase
+=====================
+
+The base unit of testing in Tempest is the `TestCase`_ (also called the test
+class). Each TestCase contains test methods which are the individual tests that
+will be executed by the test runner. But, the TestCase is the smallest self
+contained unit for tests from the tempest perspective. It's also the level at
+which tempest is parallel safe. In other words, multiple TestCases can be
+executed in parallel, but individual test methods in the same TestCase can not.
+Also, all test methods within a TestCase are assumed to be executed serially. As
+such you can use the test case to store variables that are shared between
+methods.
+
+.. _TestCase: https://docs.python.org/3.6/library/unittest.html#unittest.TestCase
+
+In standard unittest the lifecycle of a TestCase can be described in the
+following phases:
+
+ #. setUpClass
+ #. setUp
+ #. Test Execution
+ #. tearDown
+ #. doCleanups
+ #. tearDownClass
+
+setUpClass
+----------
+
+The setUpClass phase is the first phase executed by the test runner and is used
+to perform any setup required for all the test methods to be executed. In
+Tempest this is a very important step and will automatically do the necessary
+setup for interacting with the configured cloud.
+
+To accomplish this you do **not** define a setUpClass function, instead there
+are a number of predefined phases to setUpClass that are used. The phases are:
+
+ * skip_checks
+ * setup_credentials
+ * setup_clients
+ * resource_setup
+
+which is executed in that order. An example of a TestCase which defines all
+of these would be::
+
+  from tempest import config
+  from tempest import test
+
+  CONF = config.CONF
+
+
+  class TestExampleCase(test.BaseTestCase):
+
+    @classmethod
+    def skip_checks(cls):
+        """This section is used to evaluate config early and skip all test
+           methods based on these checks
+        """
+        super(TestExampleCase, cls).skip_checks()
+        if not CONF.section.foo
+            cls.skip('A helpful message')
+
+    @classmethod
+    def setup_credentials(cls):
+        """This section is used to do any manual credential allocation and also
+           in the case of dynamic credentials to override the default network
+           resource creation/auto allocation
+        """
+        # This call is used to tell the credential allocator to not create any
+        # network resources for this test case. It also enables selective
+        # creation of other neutron resources. NOTE: it must go before the
+        # super call
+        cls.set_network_resources()
+        super(TestExampleCase, cls).setup_credentials()
+
+    @classmethod
+    def setup_clients(cls):
+        """This section is used to setup client aliases from the manager object
+           or to initialize any additional clients. Except in a few very
+           specific situations you should not need to use this.
+        """
+        super(TestExampleCase, cls).setup_clients()
+        cls.servers_client = cls.os.servers_client
+
+    @classmethod
+    def resource_setup(cls):
+        """This section is used to create any resources or objects which are
+           going to be used and shared by **all** test methods in the
+           TestCase. Note then anything created in this section must also be
+           destroyed in the corresponding resource_cleanup() method (which will
+           be run during tearDownClass())
+        """
+        super(TestExampleCase, cls).resource_setup()
+        cls.shared_server = cls.servers_client.create_server(...)
+
+
+Allocating Credentials
+''''''''''''''''''''''
+
+Since Tempest tests are all about testing a running cloud, every test will need
+credentials to be able to make API requests against the cloud. Since this is
+critical to operation and, when running in parallel, easy to make a mistake,
+the base TestCase class will automatically allocate a regular user for each
+TestCase during the setup_credentials() phase. During this process it will also
+initialize a client manager object using those credentials, which will be your
+entry point into interacting with the cloud. For more details on how credentials
+are allocated the :ref:`tempest_cred_provider_conf` section of the Tempest
+Configuration Guide provides more details on the operation of this.
+
+There are some cases when you need more than a single set of credentials, or
+credentials with a more specialized set of roles. To accomplish this you have
+to set a class variable ``credentials`` on the TestCase directly. For example::
+
+    from tempest import test
+
+    class TestExampleAdmin(test.BaseTestCase):
+
+        credentials = ['primary', 'admin']
+
+    @classmethod
+    def skip_checks(cls):
+    ...
+
+In this example the ``TestExampleAdmin`` TestCase will allocate 2 sets of
+credentials, one regular user and one admin user. The corresponding manager
+objects will be set as class variables cls.os and cls.os_adm respectively. You
+can also allocate a second user by putting **'alt'** in the list too. A set of
+alt credentials are the same as primary but can be used for tests cases that
+need a second user/project.
+
+You can also specify credentials with specific roles assigned. This is useful
+for cases where there are specific RBAC requirements hard coded into an API.
+The canonical example of this are swift tests which often want to test swift's
+concepts of operator and reseller_admin. An actual example from tempest on how
+to do this is::
+
+    class PublicObjectTest(base.BaseObjectTest):
+
+        credentials = [['operator', CONF.object_storage.operator_role],
+                       ['operator_alt', CONF.object_storage.operator_role]]
+
+        @classmethod
+        def setup_credentials(cls):
+            super(PublicObjectTest, cls).setup_credentials()
+            ...
+
+In this case the manager objects will be set to ``cls.os_roles_operator`` and
+``cls.os_roles_operator_alt`` respectively.
+
+
+There is no limit to how many credentials you can allocate in this manner,
+however in almost every case you should **not** need more than 3 sets of
+credentials per test case.
+
+To figure out the mapping of manager objects set on the TestCase and the
+requested credentials you can reference:
+
++-------------------+---------------------+
+| Credentials Entry | Manager Variable    |
++===================+=====================+
+| primary           | cls.os              |
++-------------------+---------------------+
+| admin             | cls.os_adm          |
++-------------------+---------------------+
+| alt               | cls.os_alt          |
++-------------------+---------------------+
+| [$label, $role]   | cls.os_roles_$label |
++-------------------+---------------------+
+
+By default cls.os is available since it is allocated in the base tempest test
+class. (located in tempest/test.py) If your TestCase inherits from a different
+direct parent class (it'll still inherit from the BaseTestCase, just not
+directly) be sure to check if that class overrides allocated credentials.
+
+Dealing with Network Allocation
+'''''''''''''''''''''''''''''''
+
+When neutron is enabled and a testing requires networking this isn't normally
+automatically setup when a tenant is created. Since tempest needs isolated
+tenants to function properly it also needs to handle network allocation. By
+default the base test class will allocate a network, subnet, and router
+automatically. (this depends on the configured credential provider, for more
+details see: :ref:`tempest_conf_network_allocation`) However, there are
+situations where you do no need all of these resources allocated. (or your
+TestCase inherits from a class that overrides the default in tempest/test.py)
+There is a class level mechanism to override this allocation and specify which
+resources you need. To do this you need to call `cls.set_network_resources()`
+in the `setup_credentials()` method before the `super()`. For example::
+
+  from tempest import test
+
+
+  class TestExampleCase(test.BaseTestCase):
+
+  @classmethod
+  def setup_credentials(cls):
+      cls.set_network_resources(network=True, subnet=True, router=False)
+      super(TestExampleCase, cls).setup_credentials()
+
+There are 2 quirks with the usage here. First for the set_network_resources
+function to work properly it **must be called before super()**. This is so
+that children classes' settings are always used instead of a parent classes'.
+The other quirk here is that if you do not want to allocate any network
+resources for your test class simply call `set_network_resources()` without
+any arguments. For example::
+
+  from tempest import test
+
+
+  class TestExampleCase(test.BaseTestCase):
+
+  @classmethod
+  def setup_credentials(cls):
+      cls.set_network_resources()
+      super(TestExampleCase, cls).setup_credentials()
+
+This will not allocate any networking resources. This is because by default all
+the arguments default to False.
+
+It's also worth pointing out that it is common for base test classes for
+different services (and scenario tests) to override this setting. When
+inheriting from classes other than the base TestCase in tempest/test.py it is
+worth checking the immediate parent for what is set to determine if your
+class needs to override that setting.
diff --git a/tempest/api/volume/admin/test_user_messages.py b/tempest/api/volume/admin/test_user_messages.py
index 991397a..20c3538 100755
--- a/tempest/api/volume/admin/test_user_messages.py
+++ b/tempest/api/volume/admin/test_user_messages.py
@@ -13,7 +13,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest.api.volume.v3 import base
+from tempest.api.volume import base
 from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
@@ -33,7 +33,8 @@
     'links']
 
 
-class UserMessagesTest(base.VolumesV3AdminTest):
+class UserMessagesTest(base.BaseVolumeAdminTest):
+    _api_version = 3
     min_microversion = '3.3'
     max_microversion = 'latest'
 
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index 2f719c8..0cd5ec7 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -13,9 +13,11 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from tempest.api.volume import api_microversion_fixture
 from tempest.common import compute
 from tempest.common import waiters
 from tempest import config
+from tempest.lib.common import api_version_utils
 from tempest.lib.common.utils import data_utils
 from tempest.lib.common.utils import test_utils
 from tempest.lib import exceptions
@@ -24,7 +26,8 @@
 CONF = config.CONF
 
 
-class BaseVolumeTest(tempest.test.BaseTestCase):
+class BaseVolumeTest(api_version_utils.BaseMicroversionTest,
+                     tempest.test.BaseTestCase):
     """Base test case class for all Cinder API tests."""
 
     _api_version = 2
@@ -49,6 +52,10 @@
             msg = ("Invalid Cinder API version (%s)" % cls._api_version)
             raise exceptions.InvalidConfiguration(msg)
 
+        api_version_utils.check_skip_with_microversion(
+            cls.min_microversion, cls.max_microversion,
+            CONF.volume.min_microversion, CONF.volume.max_microversion)
+
     @classmethod
     def setup_credentials(cls):
         cls.set_network_resources()
@@ -68,10 +75,21 @@
         cls.availability_zone_client = (
             cls.os.volume_v2_availability_zone_client)
         cls.volume_limits_client = cls.os.volume_v2_limits_client
+        cls.messages_client = cls.os.volume_v3_messages_client
+        cls.versions_client = cls.os.volume_v3_versions_client
+
+    def setUp(self):
+        super(BaseVolumeTest, self).setUp()
+        self.useFixture(api_microversion_fixture.APIMicroversionFixture(
+            self.request_microversion))
 
     @classmethod
     def resource_setup(cls):
         super(BaseVolumeTest, cls).resource_setup()
+        cls.request_microversion = (
+            api_version_utils.select_request_microversion(
+                cls.min_microversion,
+                CONF.volume.min_microversion))
 
         cls.snapshots = []
         cls.volumes = []
@@ -240,6 +258,8 @@
             cls.os_adm.volume_capabilities_v2_client
         cls.admin_scheduler_stats_client = \
             cls.os_adm.volume_scheduler_stats_v2_client
+        cls.admin_messages_client = cls.os_adm.volume_v3_messages_client
+        cls.admin_volume_types_client = cls.os_adm.volume_types_v2_client
 
     @classmethod
     def resource_setup(cls):
diff --git a/tempest/api/volume/v2/test_image_metadata.py b/tempest/api/volume/test_image_metadata.py
similarity index 100%
rename from tempest/api/volume/v2/test_image_metadata.py
rename to tempest/api/volume/test_image_metadata.py
diff --git a/tempest/api/volume/v3/test_versions.py b/tempest/api/volume/test_versions.py
similarity index 90%
rename from tempest/api/volume/v3/test_versions.py
rename to tempest/api/volume/test_versions.py
index 20f1657..76f2a99 100644
--- a/tempest/api/volume/v3/test_versions.py
+++ b/tempest/api/volume/test_versions.py
@@ -12,12 +12,14 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-from tempest.api.volume.v3 import base
+from tempest.api.volume import base
 from tempest.lib import decorators
 from tempest import test
 
 
-class VersionsTest(base.VolumesV3Test):
+class VersionsTest(base.BaseVolumeTest):
+
+    _api_version = 3
 
     @decorators.idempotent_id('77838fc4-b49b-4c64-9533-166762517369')
     @test.attr(type='smoke')
diff --git a/tempest/api/volume/test_volumes_snapshots_negative.py b/tempest/api/volume/test_volumes_snapshots_negative.py
index 6e6cd35..2e30d04 100644
--- a/tempest/api/volume/test_volumes_snapshots_negative.py
+++ b/tempest/api/volume/test_volumes_snapshots_negative.py
@@ -68,3 +68,17 @@
         self.assertRaises(lib_exc.BadRequest,
                           self.snapshots_client.list_snapshots,
                           limit='invalid')
+
+    @test.attr(type=['negative'])
+    @decorators.idempotent_id('27b5f37f-bf69-4e8c-986e-c44f3d6819b8')
+    def test_list_snapshots_invalid_param_sort(self):
+        self.assertRaises(lib_exc.BadRequest,
+                          self.snapshots_client.list_snapshots,
+                          sort_key='invalid')
+
+    @test.attr(type=['negative'])
+    @decorators.idempotent_id('b68deeda-ca79-4a32-81af-5c51179e553a')
+    def test_list_snapshots_invalid_param_marker(self):
+        self.assertRaises(lib_exc.NotFound,
+                          self.snapshots_client.list_snapshots,
+                          marker=data_utils.rand_uuid())
diff --git a/tempest/api/volume/v2/test_volumes_snapshots_negative.py b/tempest/api/volume/v2/test_volumes_snapshots_negative.py
deleted file mode 100644
index 42ae859..0000000
--- a/tempest/api/volume/v2/test_volumes_snapshots_negative.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# Copyright 2017 Red Hat, Inc.
-# All Rights Reserved.
-#
-#    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.api.volume import base
-from tempest import config
-from tempest.lib.common.utils import data_utils
-from tempest.lib import decorators
-from tempest.lib import exceptions as lib_exc
-from tempest import test
-
-CONF = config.CONF
-
-
-class VolumesSnapshotNegativeTest(base.BaseVolumeTest):
-
-    @classmethod
-    def skip_checks(cls):
-        super(VolumesSnapshotNegativeTest, cls).skip_checks()
-        if not CONF.volume_feature_enabled.snapshot:
-            raise cls.skipException("Cinder volume snapshots are disabled")
-
-    @test.attr(type=['negative'])
-    @decorators.idempotent_id('27b5f37f-bf69-4e8c-986e-c44f3d6819b8')
-    def test_list_snapshots_invalid_param_sort(self):
-        self.assertRaises(lib_exc.BadRequest,
-                          self.snapshots_client.list_snapshots,
-                          sort_key='invalid')
-
-    @test.attr(type=['negative'])
-    @decorators.idempotent_id('b68deeda-ca79-4a32-81af-5c51179e553a')
-    def test_list_snapshots_invalid_param_marker(self):
-        self.assertRaises(lib_exc.NotFound,
-                          self.snapshots_client.list_snapshots,
-                          marker=data_utils.rand_uuid())
diff --git a/tempest/api/volume/v3/__init__.py b/tempest/api/volume/v3/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/api/volume/v3/__init__.py
+++ /dev/null
diff --git a/tempest/api/volume/v3/base.py b/tempest/api/volume/v3/base.py
deleted file mode 100644
index 7f76e6f..0000000
--- a/tempest/api/volume/v3/base.py
+++ /dev/null
@@ -1,66 +0,0 @@
-# Copyright 2016 Andrew Kerr
-# All Rights Reserved.
-#
-#    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.api.volume import api_microversion_fixture
-from tempest.api.volume import base
-from tempest import config
-from tempest.lib.common import api_version_utils
-
-CONF = config.CONF
-
-
-class VolumesV3Test(api_version_utils.BaseMicroversionTest,
-                    base.BaseVolumeTest):
-    """Base test case class for all v3 Cinder API tests."""
-
-    _api_version = 3
-
-    @classmethod
-    def skip_checks(cls):
-        super(VolumesV3Test, cls).skip_checks()
-        api_version_utils.check_skip_with_microversion(
-            cls.min_microversion, cls.max_microversion,
-            CONF.volume.min_microversion, CONF.volume.max_microversion)
-
-    @classmethod
-    def resource_setup(cls):
-        super(VolumesV3Test, cls).resource_setup()
-        cls.request_microversion = (
-            api_version_utils.select_request_microversion(
-                cls.min_microversion,
-                CONF.volume.min_microversion))
-
-    @classmethod
-    def setup_clients(cls):
-        super(VolumesV3Test, cls).setup_clients()
-        cls.messages_client = cls.os.volume_v3_messages_client
-        cls.versions_client = cls.os.volume_v3_versions_client
-
-    def setUp(self):
-        super(VolumesV3Test, self).setUp()
-        self.useFixture(api_microversion_fixture.APIMicroversionFixture(
-            self.request_microversion))
-
-
-class VolumesV3AdminTest(VolumesV3Test,
-                         base.BaseVolumeAdminTest):
-    """Base test case class for all v3 Volume Admin API tests."""
-
-    credentials = ['primary', 'admin']
-
-    @classmethod
-    def setup_clients(cls):
-        super(VolumesV3AdminTest, cls).setup_clients()
-        cls.admin_messages_client = cls.os_adm.volume_v3_messages_client
-        cls.admin_volume_types_client = cls.os_adm.volume_types_v2_client
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index 3e5600c..9c83c99 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -261,16 +261,16 @@
         time.sleep(client.build_interval)
 
 
-def wait_for_interface_status(client, server, port_id, status):
+def wait_for_interface_status(client, server_id, port_id, status):
     """Waits for an interface to reach a given status."""
-    body = (client.show_interface(server, port_id)
+    body = (client.show_interface(server_id, port_id)
             ['interfaceAttachment'])
     interface_status = body['port_state']
     start = int(time.time())
 
     while(interface_status != status):
         time.sleep(client.build_interval)
-        body = (client.show_interface(server, port_id)
+        body = (client.show_interface(server_id, port_id)
                 ['interfaceAttachment'])
         interface_status = body['port_state']