Merge "Add NetworkSubnetPools to tempest cleanup"
diff --git a/.zuul.yaml b/.zuul.yaml
index 8ff151f..a6a4b3e 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -478,6 +478,7 @@
       - openstack-python-jobs
       - openstack-python35-jobs
       - openstack-python36-jobs
+      - openstack-python37-jobs
       - publish-openstack-docs-pti
       - release-notes-jobs-python3
     check:
diff --git a/releasenotes/notes/conditional-attr-a8564ec5a70ec840.yaml b/releasenotes/notes/conditional-attr-a8564ec5a70ec840.yaml
new file mode 100644
index 0000000..c707f14
--- /dev/null
+++ b/releasenotes/notes/conditional-attr-a8564ec5a70ec840.yaml
@@ -0,0 +1,6 @@
+---
+features:
+  - |
+    The ``tempest.lib.decorators.attr`` decorator now supports a ``condition``
+    kwarg which can be used to conditionally apply the attr to the test
+    function if the condition evaluates to True.
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index f7b5b4b..8bb4eaa 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -65,6 +65,8 @@
 class AttachVolumeTestJSON(BaseAttachVolumeTest):
 
     @decorators.idempotent_id('52e9045a-e90d-4c0d-9087-79d657faffff')
+    # This test is conditionally marked slow if SSH validation is enabled.
+    @decorators.attr(type='slow', condition=CONF.validation.run_validation)
     def test_attach_detach_volume(self):
         # Stop and Start a server with an attached volume, ensuring that
         # the volume remains attached.
diff --git a/tempest/api/identity/admin/v3/test_groups.py b/tempest/api/identity/admin/v3/test_groups.py
index 80d7469..df0d79d 100644
--- a/tempest/api/identity/admin/v3/test_groups.py
+++ b/tempest/api/identity/admin/v3/test_groups.py
@@ -23,6 +23,10 @@
 
 
 class GroupsV3TestJSON(base.BaseIdentityV3AdminTest):
+    # NOTE: force_tenant_isolation is true in the base class by default but
+    # overridden to false here to allow test execution for clouds using the
+    # pre-provisioned credentials provider.
+    force_tenant_isolation = False
 
     @classmethod
     def resource_setup(cls):
diff --git a/tempest/lib/common/rest_client.py b/tempest/lib/common/rest_client.py
index e612bd1..3be441e 100644
--- a/tempest/lib/common/rest_client.py
+++ b/tempest/lib/common/rest_client.py
@@ -282,7 +282,7 @@
     def get(self, url, headers=None, extra_headers=False):
         """Send a HTTP GET request using keystone service catalog and auth
 
-        :param str url: the relative url to send the post request to
+        :param str url: the relative url to send the get request to
         :param dict headers: The headers to use for the request
         :param bool extra_headers: Boolean value than indicates if the headers
                                    returned by the get_headers() method are to
@@ -297,7 +297,7 @@
     def delete(self, url, headers=None, body=None, extra_headers=False):
         """Send a HTTP DELETE request using keystone service catalog and auth
 
-        :param str url: the relative url to send the post request to
+        :param str url: the relative url to send the delete request to
         :param dict headers: The headers to use for the request
         :param dict body: the request body
         :param bool extra_headers: Boolean value than indicates if the headers
@@ -313,7 +313,7 @@
     def patch(self, url, body, headers=None, extra_headers=False):
         """Send a HTTP PATCH request using keystone service catalog and auth
 
-        :param str url: the relative url to send the post request to
+        :param str url: the relative url to send the patch request to
         :param dict body: the request body
         :param dict headers: The headers to use for the request
         :param bool extra_headers: Boolean value than indicates if the headers
@@ -329,7 +329,7 @@
     def put(self, url, body, headers=None, extra_headers=False, chunked=False):
         """Send a HTTP PUT request using keystone service catalog and auth
 
-        :param str url: the relative url to send the post request to
+        :param str url: the relative url to send the put request to
         :param dict body: the request body
         :param dict headers: The headers to use for the request
         :param bool extra_headers: Boolean value than indicates if the headers
@@ -346,7 +346,7 @@
     def head(self, url, headers=None, extra_headers=False):
         """Send a HTTP HEAD request using keystone service catalog and auth
 
-        :param str url: the relative url to send the post request to
+        :param str url: the relative url to send the head request to
         :param dict headers: The headers to use for the request
         :param bool extra_headers: Boolean value than indicates if the headers
                                    returned by the get_headers() method are to
@@ -361,7 +361,7 @@
     def copy(self, url, headers=None, extra_headers=False):
         """Send a HTTP COPY request using keystone service catalog and auth
 
-        :param str url: the relative url to send the post request to
+        :param str url: the relative url to send the copy request to
         :param dict headers: The headers to use for the request
         :param bool extra_headers: Boolean value than indicates if the headers
                                    returned by the get_headers() method are to
diff --git a/tempest/lib/decorators.py b/tempest/lib/decorators.py
index b399aa0..62a5d67 100644
--- a/tempest/lib/decorators.py
+++ b/tempest/lib/decorators.py
@@ -136,9 +136,16 @@
 
     This decorator applies the testtools.testcase.attr if it is in the list of
     attributes to testtools we want to apply.
+
+    :param condition: Optional condition which if true will apply the attr. If
+        a condition is specified which is false the attr will not be applied to
+        the test function. If not specified, the attr is always applied.
     """
 
     def decorator(f):
+        # Check to see if the attr should be conditional applied.
+        if 'condition' in kwargs and not kwargs.get('condition'):
+            return f
         if 'type' in kwargs and isinstance(kwargs['type'], str):
             f = testtools.testcase.attr(kwargs['type'])(f)
         elif 'type' in kwargs and isinstance(kwargs['type'], list):
diff --git a/tempest/lib/services/identity/v3/oauth_token_client.py b/tempest/lib/services/identity/v3/oauth_token_client.py
index b1d298b..94da043 100644
--- a/tempest/lib/services/identity/v3/oauth_token_client.py
+++ b/tempest/lib/services/identity/v3/oauth_token_client.py
@@ -74,6 +74,7 @@
         scheme, netloc, path, params, query, fragment = urlparse.urlparse(uri)
         scheme = scheme.lower()
         netloc = netloc.lower()
+        path = path.replace('//', '/')
         normalized_uri = urlparse.urlunparse((scheme, netloc, path,
                                               params, '', ''))
 
diff --git a/tempest/scenario/test_network_advanced_server_ops.py b/tempest/scenario/test_network_advanced_server_ops.py
index 4be2b29..37bcd04 100644
--- a/tempest/scenario/test_network_advanced_server_ops.py
+++ b/tempest/scenario/test_network_advanced_server_ops.py
@@ -119,6 +119,7 @@
             server, keypair, floating_ip)
 
     @decorators.idempotent_id('7b6860c2-afa3-4846-9522-adeb38dfbe08')
+    @decorators.attr(type='slow')
     @utils.services('compute', 'network')
     def test_server_connectivity_reboot(self):
         keypair = self.create_keypair()
diff --git a/tempest/scenario/test_volume_boot_pattern.py b/tempest/scenario/test_volume_boot_pattern.py
index 810480b..3bc5b0b 100644
--- a/tempest/scenario/test_volume_boot_pattern.py
+++ b/tempest/scenario/test_volume_boot_pattern.py
@@ -76,6 +76,7 @@
         self.snapshots_client.wait_for_resource_deletion(snapshot_id)
 
     @decorators.idempotent_id('557cd2c2-4eb8-4dce-98be-f86765ff311b')
+    @decorators.attr(type='slow')
     # Note: This test is being skipped based on 'public_network_id'.
     # It is being used in create_floating_ip() method which gets called
     # from get_server_ip() method
diff --git a/tempest/tests/lib/test_decorators.py b/tempest/tests/lib/test_decorators.py
index 0b1a599..3e6160e 100644
--- a/tempest/tests/lib/test_decorators.py
+++ b/tempest/tests/lib/test_decorators.py
@@ -32,9 +32,17 @@
         # By our decorators.attr decorator the attribute __testtools_attrs
         # will be set only for 'type' argument, so we test it first.
         if 'type' in decorator_args:
-            # this is what testtools sets
-            self.assertEqual(getattr(foo, '__testtools_attrs'),
-                             set(expected_attrs))
+            if 'condition' in decorator_args:
+                if decorator_args['condition']:
+                    # The expected attrs should be in the function.
+                    self.assertEqual(set(expected_attrs),
+                                     getattr(foo, '__testtools_attrs'))
+                else:
+                    # The expected attrs should not be in the function.
+                    self.assertNotIn('__testtools_attrs', foo)
+            else:
+                self.assertEqual(set(expected_attrs),
+                                 getattr(foo, '__testtools_attrs'))
 
     def test_attr_without_type(self):
         self._test_attr_helper(expected_attrs='baz', bar='baz')
@@ -50,6 +58,13 @@
     def test_attr_decorator_with_duplicated_type(self):
         self._test_attr_helper(expected_attrs=['foo'], type=['foo', 'foo'])
 
+    def test_attr_decorator_condition_false(self):
+        self._test_attr_helper(None, type='slow', condition=False)
+
+    def test_attr_decorator_condition_true(self):
+        self._test_attr_helper(expected_attrs=['slow'], type='slow',
+                               condition=True)
+
 
 class TestSkipBecauseDecorator(base.TestCase):
     def _test_skip_because_helper(self, expected_to_skip=True,