Merge "Move test_extension and use base for non-admin"
diff --git a/HACKING.rst b/HACKING.rst
index a209b3f..910a977 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -313,7 +313,7 @@
 qualified test name and track test functionality through refactoring. The
 format of the metadata looks like::
 
-    @test.idempotent_id('585e934c-448e-43c4-acbf-d06a9b899997')
+    @decorators.idempotent_id('585e934c-448e-43c4-acbf-d06a9b899997')
     def test_list_servers_with_detail(self):
         # The created server should be in the detailed list of all servers
         ...
diff --git a/doc/source/test-removal.rst b/doc/source/test-removal.rst
index 4757dc4..d06e4ba 100644
--- a/doc/source/test-removal.rst
+++ b/doc/source/test-removal.rst
@@ -64,7 +64,7 @@
 
 SELECT * from tests where test_id like "%test_id%";
 (where $test_id is the full test_id, but truncated to the class because of
-setupClass or tearDownClass failures)
+setUpClass or tearDownClass failures)
 
 You can access the infra mysql subunit2sql db w/ read-only permissions with:
 
@@ -82,7 +82,7 @@
  #. run the query: MySQL [subunit2sql]> select * from tests where test_id like
     "tempest.api.compute.admin.test_flavors_negative.FlavorsAdminNegativeTestJSON%";
     which will return a table of all the tests in the class (but it will also
-    catch failures in setupClass and tearDownClass)
+    catch failures in setUpClass and tearDownClass)
  #. paste the output table with numbers and the mysql command you ran to
     generate it into the etherpad.
 
@@ -185,4 +185,4 @@
 If a test exists in Tempest that would meet this criteria as consequence of a
 change, the test must be removed according to the procedure discussed into
 this document. The API change should not be merged until all conditions
-required for test removal can be met.
\ No newline at end of file
+required for test removal can be met.
diff --git a/releasenotes/notes/add-tempest-run-combine-option-e94c1049ba8985d5.yaml b/releasenotes/notes/add-tempest-run-combine-option-e94c1049ba8985d5.yaml
new file mode 100644
index 0000000..73900ca
--- /dev/null
+++ b/releasenotes/notes/add-tempest-run-combine-option-e94c1049ba8985d5.yaml
@@ -0,0 +1,6 @@
+---
+features:
+  - |
+    Adds a new cli option to tempest run, --combine, which is used to indicate
+    you want the subunit stream output combined with the previous run's in
+    the testr repository
diff --git a/releasenotes/notes/deprecate-resources-prefix-option-ad490c0a30a0266b.yaml b/releasenotes/notes/deprecate-resources-prefix-option-ad490c0a30a0266b.yaml
new file mode 100644
index 0000000..f679208
--- /dev/null
+++ b/releasenotes/notes/deprecate-resources-prefix-option-ad490c0a30a0266b.yaml
@@ -0,0 +1,10 @@
+---
+upgrade:
+  - The default value of rand_name()'s prefix argument is changed
+    to 'tempest' from None to identify resources are created by
+    Tempest.
+deprecations:
+  - The resources_prefix is marked as deprecated because it is
+    enough to set 'tempest' as the prefix on rand_name() to
+    ideintify resources which are created by Tempest and no
+    projects set this option on OpenStack dev community.
diff --git a/releasenotes/notes/deprecate-skip_unless_attr-decorator-450a1ed727494724.yaml b/releasenotes/notes/deprecate-skip_unless_attr-decorator-450a1ed727494724.yaml
new file mode 100644
index 0000000..4d8b941
--- /dev/null
+++ b/releasenotes/notes/deprecate-skip_unless_attr-decorator-450a1ed727494724.yaml
@@ -0,0 +1,5 @@
+---
+deprecations:
+  - The ``skip_unless_attr`` decorator in lib/decorators.py has been deprecated,
+    please use the standard ``testtools.skipUnless`` and ``testtools.skipIf``
+    decorators.
diff --git a/releasenotes/notes/use-keystone-v3-api-935860d30ddbb8e9.yaml b/releasenotes/notes/use-keystone-v3-api-935860d30ddbb8e9.yaml
new file mode 100644
index 0000000..dd6e924
--- /dev/null
+++ b/releasenotes/notes/use-keystone-v3-api-935860d30ddbb8e9.yaml
@@ -0,0 +1,5 @@
+---
+upgrade:
+  - Tempest now defaults to using Keystone v3 API for the
+    authentication, because Keystone v3 API is CURRENT and
+    the v2 API is deprecated.
diff --git a/tempest/api/compute/servers/test_create_server.py b/tempest/api/compute/servers/test_create_server.py
index fd5e50e..38dbb50 100644
--- a/tempest/api/compute/servers/test_create_server.py
+++ b/tempest/api/compute/servers/test_create_server.py
@@ -122,7 +122,8 @@
             self.validation_resources['keypair']['private_key'],
             server=self.server,
             servers_client=self.client)
-        self.assertEqual(flavor['vcpus'], linux_client.get_number_of_vcpus())
+        output = linux_client.exec_command('grep -c ^processor /proc/cpuinfo')
+        self.assertEqual(flavor['vcpus'], int(output))
 
     @decorators.idempotent_id('ac1ad47f-984b-4441-9274-c9079b7a0666')
     @testtools.skipUnless(CONF.validation.run_validation,
@@ -136,7 +137,7 @@
             self.validation_resources['keypair']['private_key'],
             server=self.server,
             servers_client=self.client)
-        hostname = linux_client.get_hostname()
+        hostname = linux_client.exec_command("hostname").rstrip()
         msg = ('Failed while verifying servername equals hostname. Expected '
                'hostname "%s" but got "%s".' % (self.name, hostname))
         self.assertEqual(self.name.lower(), hostname, msg)
diff --git a/tempest/api/compute/servers/test_list_server_filters.py b/tempest/api/compute/servers/test_list_server_filters.py
index c0a8eae..7b782de 100644
--- a/tempest/api/compute/servers/test_list_server_filters.py
+++ b/tempest/api/compute/servers/test_list_server_filters.py
@@ -12,13 +12,17 @@
 #    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 testtools
 
 from tempest.api.compute import base
 from tempest.common import fixed_network
 from tempest.common.utils import data_utils
 from tempest.common import waiters
+from tempest import config
 from tempest.lib import decorators
-from tempest.lib import exceptions as lib_exc
+
+
+CONF = config.CONF
 
 
 class ListServerFiltersTestJSON(base.BaseV2ComputeTest):
@@ -37,31 +41,6 @@
     def resource_setup(cls):
         super(ListServerFiltersTestJSON, cls).resource_setup()
 
-        # Check to see if the alternate image ref actually exists...
-        images_client = cls.compute_images_client
-        images = images_client.list_images()['images']
-
-        if cls.image_ref != cls.image_ref_alt and \
-            any([image for image in images
-                 if image['id'] == cls.image_ref_alt]):
-            cls.multiple_images = True
-        else:
-            cls.image_ref_alt = cls.image_ref
-
-        # Do some sanity checks here. If one of the images does
-        # not exist, fail early since the tests won't work...
-        try:
-            cls.compute_images_client.show_image(cls.image_ref)
-        except lib_exc.NotFound:
-            raise RuntimeError("Image %s (image_ref) was not found!" %
-                               cls.image_ref)
-
-        try:
-            cls.compute_images_client.show_image(cls.image_ref_alt)
-        except lib_exc.NotFound:
-            raise RuntimeError("Image %s (image_ref_alt) was not found!" %
-                               cls.image_ref_alt)
-
         network = cls.get_tenant_network()
         if network:
             cls.fixed_network_name = network.get('name')
@@ -74,9 +53,12 @@
                                         **network_kwargs)
 
         cls.s2_name = data_utils.rand_name(cls.__name__ + '-instance')
-        cls.s2 = cls.create_test_server(name=cls.s2_name,
-                                        image_id=cls.image_ref_alt,
-                                        wait_until='ACTIVE')
+        # If image_ref_alt is "" or None then we still want to boot a server
+        # but we rely on `testtools.skipUnless` decorator to actually skip
+        # the irrelevant tests.
+        cls.s2 = cls.create_test_server(
+            name=cls.s2_name, image_id=cls.image_ref_alt or cls.image_ref,
+            wait_until='ACTIVE')
 
         cls.s3_name = data_utils.rand_name(cls.__name__ + '-instance')
         cls.s3 = cls.create_test_server(name=cls.s3_name,
@@ -84,7 +66,8 @@
                                         wait_until='ACTIVE')
 
     @decorators.idempotent_id('05e8a8e7-9659-459a-989d-92c2f501f4ba')
-    @decorators.skip_unless_attr('multiple_images', 'Only one image found')
+    @testtools.skipUnless(CONF.compute.image_ref != CONF.compute.image_ref_alt,
+                          "Need distinct images to run this test")
     def test_list_servers_filter_by_image(self):
         # Filter the list of servers by image
         params = {'image': self.image_ref}
@@ -169,7 +152,8 @@
                          len([x for x in servers['servers'] if 'id' in x]))
 
     @decorators.idempotent_id('b3304c3b-97df-46d2-8cd3-e2b6659724e7')
-    @decorators.skip_unless_attr('multiple_images', 'Only one image found')
+    @testtools.skipUnless(CONF.compute.image_ref != CONF.compute.image_ref_alt,
+                          "Need distinct images to run this test")
     def test_list_servers_detailed_filter_by_image(self):
         # Filter the detailed list of servers by image
         params = {'image': self.image_ref}
@@ -269,16 +253,34 @@
         if not self.fixed_network_name:
             msg = 'fixed_network_name needs to be configured to run this test'
             raise self.skipException(msg)
+
+        # list servers filter by ip is something "regexp match", i.e,
+        # filter by "10.1.1.1" will return both "10.1.1.1" and "10.1.1.10".
+        # so here look for the longest server ip, and filter by that ip,
+        # so as to ensure only one server is returned.
+        ip_list = {}
         self.s1 = self.client.show_server(self.s1['id'])['server']
         # Get first ip address inspite of v4 or v6
-        addr_spec = self.s1['addresses'][self.fixed_network_name][0]
-        params = {'ip': addr_spec['addr']}
+        ip_addr = self.s1['addresses'][self.fixed_network_name][0]['addr']
+        ip_list[ip_addr] = self.s1['id']
+
+        self.s2 = self.client.show_server(self.s2['id'])['server']
+        ip_addr = self.s2['addresses'][self.fixed_network_name][0]['addr']
+        ip_list[ip_addr] = self.s2['id']
+
+        self.s3 = self.client.show_server(self.s3['id'])['server']
+        ip_addr = self.s3['addresses'][self.fixed_network_name][0]['addr']
+        ip_list[ip_addr] = self.s3['id']
+
+        longest_ip = max([[len(ip), ip] for ip in ip_list])[1]
+        params = {'ip': longest_ip}
         body = self.client.list_servers(**params)
         servers = body['servers']
 
-        self.assertIn(self.s1_name, map(lambda x: x['name'], servers))
-        self.assertNotIn(self.s2_name, map(lambda x: x['name'], servers))
-        self.assertNotIn(self.s3_name, map(lambda x: x['name'], servers))
+        self.assertIn(ip_list[longest_ip], map(lambda x: x['id'], servers))
+        del ip_list[longest_ip]
+        for ip in ip_list:
+            self.assertNotIn(ip_list[ip], map(lambda x: x['id'], servers))
 
     @decorators.skip_because(bug="1540645")
     @decorators.idempotent_id('a905e287-c35e-42f2-b132-d02b09f3654a')
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 6160024..b915739 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -471,7 +471,7 @@
 
         # NOTE: SHUTOFF is irregular status. To avoid test instability,
         #       one server is created only for this test without using
-        #       the server that was created in setupClass.
+        #       the server that was created in setUpClass.
         server = self.create_test_server(wait_until='ACTIVE')
         temp_server_id = server['id']
 
diff --git a/tempest/api/compute/test_versions.py b/tempest/api/compute/test_versions.py
index c9f0724..dcab067 100644
--- a/tempest/api/compute/test_versions.py
+++ b/tempest/api/compute/test_versions.py
@@ -14,11 +14,13 @@
 
 from tempest.api.compute import base
 from tempest.lib import decorators
+from tempest import test
 
 
 class TestVersions(base.BaseV2ComputeTest):
 
     @decorators.idempotent_id('6c0a0990-43b6-4529-9b61-5fd8daf7c55c')
+    @test.attr(type='smoke')
     def test_list_api_versions(self):
         """Test that a get of the unversioned url returns the choices doc.
 
@@ -37,6 +39,7 @@
                          "The first listed version should be v2.0")
 
     @decorators.idempotent_id('b953a29e-929c-4a8e-81be-ec3a7e03cb76')
+    @test.attr(type='smoke')
     def test_get_version_details(self):
         """Test individual version endpoints info works.
 
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index 80e7936..344779c 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -15,6 +15,7 @@
 
 from tempest.common.utils import data_utils
 from tempest import config
+from tempest.lib.common.utils import test_utils
 import tempest.test
 
 CONF = config.CONF
@@ -72,7 +73,9 @@
             kwargs['password'] = user_password
         user = self.users_client.create_user(**kwargs)['user']
         # Delete the user at the end of the test
-        self.addCleanup(self.users_client.delete_user, user['id'])
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.users_client.delete_user, user['id'])
         return user
 
     def setup_test_role(self, domain_id=None):
@@ -83,7 +86,9 @@
 
         role = self.roles_client.create_role(**params)['role']
         # Delete the role at the end of the test
-        self.addCleanup(self.roles_client.delete_role, role['id'])
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.roles_client.delete_role, role['id'])
         return role
 
 
@@ -152,7 +157,9 @@
             name=data_utils.rand_name('test_tenant'),
             description=data_utils.rand_name('desc'))['tenant']
         # Delete the tenant at the end of the test
-        self.addCleanup(self.tenants_client.delete_tenant, tenant['id'])
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.tenants_client.delete_tenant, tenant['id'])
         return tenant
 
 
@@ -246,12 +253,16 @@
             name=data_utils.rand_name('test_project'),
             description=data_utils.rand_name('desc'))['project']
         # Delete the project at the end of the test
-        self.addCleanup(self.projects_client.delete_project, project['id'])
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.projects_client.delete_project, project['id'])
         return project
 
     def setup_test_domain(self):
         """Set up a test domain."""
         domain = self.create_domain()
         # Delete the domain at the end of the test
-        self.addCleanup(self.delete_domain, domain['id'])
+        self.addCleanup(
+            test_utils.call_and_ignore_notfound_exc,
+            self.delete_domain, domain['id'])
         return domain
diff --git a/tempest/api/orchestration/stacks/test_neutron_resources.py b/tempest/api/orchestration/stacks/test_neutron_resources.py
index 3a52108..0154658 100644
--- a/tempest/api/orchestration/stacks/test_neutron_resources.py
+++ b/tempest/api/orchestration/stacks/test_neutron_resources.py
@@ -84,12 +84,15 @@
                 # to heat.
                 body = cls.client.show_resource(cls.stack_identifier,
                                                 'Server')
-                server_id = body['physical_resource_id']
-                LOG.debug('Console output for %s', server_id)
-                output = cls.servers_client.get_console_output(
-                    server_id)['output']
-                LOG.debug(output)
-            raise
+                server_id = body.get('physical_resource_id')
+                if server_id:
+                    LOG.debug('Console output for %s', server_id)
+                    output = cls.servers_client.get_console_output(
+                        server_id)['output']
+                    LOG.debug(output)
+                else:
+                    LOG.debug('Server resource is %s', body)
+            raise  # original exception
 
         cls.test_resources = {}
         for resource in resources:
diff --git a/tempest/cmd/run.py b/tempest/cmd/run.py
index 54b844a..b36bf5c 100644
--- a/tempest/cmd/run.py
+++ b/tempest/cmd/run.py
@@ -78,11 +78,20 @@
 subunit-trace output filter. But, if you would prefer a subunit v2 stream be
 output to STDOUT use the **--subunit** flag
 
+Combining Runs
+==============
+
+There are certain situations in which you want to split a single run of tempest
+across 2 executions of tempest run. (for example to run part of the tests
+serially and others in parallel) To accomplish this but still treat the results
+as a single run you can leverage the **--combine** option which will append
+the current run's results with the previous runs.
 """
 
 import io
 import os
 import sys
+import tempfile
 import threading
 
 from cliff import command
@@ -165,6 +174,12 @@
         else:
             print("No .testr.conf file was found for local execution")
             sys.exit(2)
+        if parsed_args.combine:
+            temp_stream = tempfile.NamedTemporaryFile()
+            return_code = run_argv(['tempest', 'last', '--subunit'], sys.stdin,
+                                   temp_stream, sys.stderr)
+            if return_code > 0:
+                sys.exit(return_code)
 
         regex = self._build_regex(parsed_args)
         if parsed_args.list_tests:
@@ -173,6 +188,16 @@
         else:
             options = self._build_options(parsed_args)
             returncode = self._run(regex, options)
+            if returncode > 0:
+                sys.exit(returncode)
+
+        if parsed_args.combine:
+            return_code = run_argv(['tempest', 'last', '--subunit'], sys.stdin,
+                                   temp_stream, sys.stderr)
+            if return_code > 0:
+                sys.exit(return_code)
+            returncode = run_argv(['tempest', 'load', temp_stream.name],
+                                  sys.stdin, sys.stdout, sys.stderr)
         sys.exit(returncode)
 
     def get_description(self):
@@ -231,6 +256,10 @@
         # output args
         parser.add_argument("--subunit", action='store_true',
                             help='Enable subunit v2 output')
+        parser.add_argument("--combine", action='store_true',
+                            help='Combine the output of this run with the '
+                                 "previous run's as a combined stream in the "
+                                 "testr repository after it finish")
 
         parser.set_defaults(parallel=True)
         return parser
diff --git a/tempest/common/utils/__init__.py b/tempest/common/utils/__init__.py
index b6565d1..84e31d0 100644
--- a/tempest/common/utils/__init__.py
+++ b/tempest/common/utils/__init__.py
@@ -19,10 +19,6 @@
 
 CONF = config.CONF
 
-PING_IPV4_COMMAND = 'ping -c 3 '
-PING_IPV6_COMMAND = 'ping6 -c 3 '
-PING_PACKET_LOSS_REGEX = '(\d{1,3})\.?\d*\% packet loss'
-
 
 class DataUtils(object):
     def __getattr__(self, attr):
diff --git a/tempest/common/utils/linux/remote_client.py b/tempest/common/utils/linux/remote_client.py
index 1487c1d..1aa09e6 100644
--- a/tempest/common/utils/linux/remote_client.py
+++ b/tempest/common/utils/linux/remote_client.py
@@ -75,9 +75,13 @@
         """
         self.server = server
         self.servers_client = servers_client
+
         ssh_timeout = CONF.validation.ssh_timeout
         connect_timeout = CONF.validation.connect_timeout
         self.log_console = CONF.compute_feature_enabled.console_output
+        self.ssh_shell_prologue = CONF.validation.ssh_shell_prologue
+        self.ping_count = CONF.validation.ping_count
+        self.ping_size = CONF.validation.ping_size
 
         self.ssh_client = ssh.Client(ip_address, username, password,
                                      ssh_timeout, pkey=pkey,
@@ -87,7 +91,7 @@
     def exec_command(self, cmd):
         # Shell options below add more clearness on failures,
         # path is extended for some non-cirros guest oses (centos7)
-        cmd = CONF.validation.ssh_shell_prologue + " " + cmd
+        cmd = self.ssh_shell_prologue + " " + cmd
         LOG.debug("Remote command: %s", cmd)
         return self.ssh_client.exec_command(cmd)
 
@@ -99,20 +103,6 @@
         """
         self.ssh_client.test_connection_auth()
 
-    def get_hostname(self):
-        # Get host name using command "hostname"
-        actual_hostname = self.exec_command("hostname").rstrip()
-        return actual_hostname
-
-    def get_ram_size_in_mb(self):
-        output = self.exec_command('free -m | grep Mem')
-        if output:
-            return output.split()[1]
-
-    def get_number_of_vcpus(self):
-        output = self.exec_command('grep -c ^processor /proc/cpuinfo')
-        return int(output)
-
     def get_disks(self):
         # Select root disk devices as shown by lsblk
         command = 'lsblk -lb --nodeps'
@@ -142,8 +132,12 @@
         cmd = 'sudo sh -c "echo \\"%s\\" >/dev/console"' % message
         return self.exec_command(cmd)
 
-    def ping_host(self, host, count=CONF.validation.ping_count,
-                  size=CONF.validation.ping_size, nic=None):
+    def ping_host(self, host, count=None, size=None, nic=None):
+        if count is None:
+            count = self.ping_count
+        if size is None:
+            size = self.ping_size
+
         addr = netaddr.IPAddress(host)
         cmd = 'ping6' if addr.version == 6 else 'ping'
         if nic:
@@ -176,11 +170,9 @@
         cmd = "ip address"
         return self.exec_command(cmd)
 
-    def assign_static_ip(self, nic, addr):
+    def assign_static_ip(self, nic, addr, network_mask_bits=28):
         cmd = "sudo ip addr add {ip}/{mask} dev {nic}".format(
-            ip=addr, mask=CONF.network.project_network_mask_bits,
-            nic=nic
-        )
+            ip=addr, mask=network_mask_bits, nic=nic)
         return self.exec_command(cmd)
 
     def set_nic_state(self, nic, state="up"):
@@ -218,7 +210,7 @@
         cmd = "sudo /sbin/dhclient -r && sudo /sbin/dhclient"
         self.exec_command(cmd)
 
-    def renew_lease(self, fixed_ip=None):
+    def renew_lease(self, fixed_ip=None, dhcp_client='udhcpc'):
         """Wrapper method for renewing DHCP lease via given client
 
         Supporting:
@@ -227,7 +219,6 @@
         """
         # TODO(yfried): add support for dhcpcd
         supported_clients = ['udhcpc', 'dhclient']
-        dhcp_client = CONF.scenario.dhcp_client
         if dhcp_client not in supported_clients:
             raise tempest.lib.exceptions.InvalidConfiguration(
                 '%s DHCP client unsupported' % dhcp_client)
diff --git a/tempest/config.py b/tempest/config.py
index 651c32e..274cd21 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -133,7 +133,7 @@
     cfg.StrOpt('uri_v3',
                help='Full URI of the OpenStack Identity API (Keystone), v3'),
     cfg.StrOpt('auth_version',
-               default='v2',
+               default='v3',
                help="Identity API version to be used for authentication "
                     "for API tests."),
     cfg.StrOpt('region',
@@ -1028,7 +1028,12 @@
                help="Prefix to be added when generating the name for "
                     "test resources. It can be used to discover all "
                     "resources associated with a specific test run when "
-                    "running tempest on a real-life cloud"),
+                    "running tempest on a real-life cloud",
+               deprecated_for_removal=True,
+               deprecated_reason="It is enough to add 'tempest' as this "
+                                 "prefix to ideintify resources which are "
+                                 "created by Tempest and no projects set "
+                                 "this option on OpenStack dev community."),
 ]
 
 _opts = [
diff --git a/tempest/lib/api_schema/response/compute/v2_1/keypairs.py b/tempest/lib/api_schema/response/compute/v2_1/keypairs.py
index 2828097..e7dcf79 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/keypairs.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/keypairs.py
@@ -34,12 +34,9 @@
 
                 },
                 'additionalProperties': False,
-                # When we run the get keypair API, response body includes
-                # all the above mentioned attributes.
-                # But in Nova API sample file, response body includes only
-                # 'public_key', 'name' & 'fingerprint'. So only 'public_key',
-                # 'name' & 'fingerprint' are defined as 'required'.
-                'required': ['public_key', 'name', 'fingerprint']
+                'required': ['public_key', 'name', 'fingerprint', 'user_id',
+                             'deleted', 'created_at', 'updated_at',
+                             'deleted_at', 'id']
             }
         },
         'additionalProperties': False,
diff --git a/tempest/lib/cmd/check_uuid.py b/tempest/lib/cmd/check_uuid.py
index 283b10f..eafde44 100755
--- a/tempest/lib/cmd/check_uuid.py
+++ b/tempest/lib/cmd/check_uuid.py
@@ -26,10 +26,6 @@
 from oslo_utils import uuidutils
 import six.moves.urllib.parse as urlparse
 
-# TODO(oomichi): Need to remove this after switching all modules to decorators
-# on all OpenStack projects because they runs check-uuid on their own gates.
-OLD_DECORATOR_MODULE = 'test'
-
 DECORATOR_MODULE = 'decorators'
 DECORATOR_NAME = 'idempotent_id'
 DECORATOR_IMPORT = 'tempest.%s' % DECORATOR_MODULE
@@ -128,8 +124,7 @@
                 hasattr(decorator.func, 'attr') and
                 decorator.func.attr == DECORATOR_NAME and
                 hasattr(decorator.func, 'value') and
-                (decorator.func.value.id == DECORATOR_MODULE or
-                 decorator.func.value.id == OLD_DECORATOR_MODULE)):
+                decorator.func.value.id == DECORATOR_MODULE):
                 for arg in decorator.args:
                     idempotent_id = ast.literal_eval(arg)
         return idempotent_id
@@ -361,7 +356,7 @@
         sys.exit("@decorators.idempotent_id existence and uniqueness checks "
                  "failed\n"
                  "Run 'tox -v -euuidgen' to automatically fix tests with\n"
-                 "missing @test.idempotent_id decorators.")
+                 "missing @decorators.idempotent_id decorators.")
 
 if __name__ == '__main__':
     run()
diff --git a/tempest/lib/common/utils/data_utils.py b/tempest/lib/common/utils/data_utils.py
index 642514b..a0941ef 100644
--- a/tempest/lib/common/utils/data_utils.py
+++ b/tempest/lib/common/utils/data_utils.py
@@ -43,7 +43,7 @@
     return uuid.uuid4().hex
 
 
-def rand_name(name='', prefix=None):
+def rand_name(name='', prefix='tempest'):
     """Generate a random name that includes a random number
 
     :param str name: The name that you want to include
diff --git a/tempest/lib/decorators.py b/tempest/lib/decorators.py
index 6ed99b4..92f9698 100644
--- a/tempest/lib/decorators.py
+++ b/tempest/lib/decorators.py
@@ -15,6 +15,7 @@
 import functools
 import uuid
 
+import debtcollector.removals
 import six
 import testtools
 
@@ -61,6 +62,7 @@
     return decorator
 
 
+@debtcollector.removals.remove(removal_version='Queen')
 class skip_unless_attr(object):
     """Decorator to skip tests if a specified attr does not exists or False"""
     def __init__(self, attr, msg=None):
diff --git a/tempest/scenario/test_aggregates_basic_ops.py b/tempest/scenario/test_aggregates_basic_ops.py
index 5152472..b0b516a 100644
--- a/tempest/scenario/test_aggregates_basic_ops.py
+++ b/tempest/scenario/test_aggregates_basic_ops.py
@@ -96,6 +96,7 @@
         return aggregate
 
     @decorators.idempotent_id('cb2b4c4f-0c7c-4164-bdde-6285b302a081')
+    @test.attr(type='slow')
     @test.services('compute')
     def test_aggregate_basic_ops(self):
         self.useFixture(fixtures.LockFixture('availability_zone'))
diff --git a/tempest/scenario/test_encrypted_cinder_volumes.py b/tempest/scenario/test_encrypted_cinder_volumes.py
index da29485..a05b1b1 100644
--- a/tempest/scenario/test_encrypted_cinder_volumes.py
+++ b/tempest/scenario/test_encrypted_cinder_volumes.py
@@ -62,6 +62,7 @@
         self.nova_volume_detach(server, attached_volume)
 
     @decorators.idempotent_id('79165fb4-5534-4b9d-8429-97ccffb8f86e')
+    @test.attr(type='slow')
     @test.services('compute', 'volume', 'image')
     def test_encrypted_cinder_volumes_luks(self):
         server = self.launch_instance()
@@ -71,6 +72,7 @@
         self.attach_detach_volume(server, volume)
 
     @decorators.idempotent_id('cbc752ed-b716-4717-910f-956cce965722')
+    @test.attr(type='slow')
     @test.services('compute', 'volume', 'image')
     def test_encrypted_cinder_volumes_cryptsetup(self):
         server = self.launch_instance()
diff --git a/tempest/scenario/test_network_advanced_server_ops.py b/tempest/scenario/test_network_advanced_server_ops.py
index 1196659..6665fa7 100644
--- a/tempest/scenario/test_network_advanced_server_ops.py
+++ b/tempest/scenario/test_network_advanced_server_ops.py
@@ -104,6 +104,7 @@
         return body['OS-EXT-SRV-ATTR:host']
 
     @decorators.idempotent_id('61f1aa9a-1573-410e-9054-afa557cab021')
+    @test.attr(type='slow')
     @test.services('compute', 'network')
     def test_server_connectivity_stop_start(self):
         keypair = self.create_keypair()
@@ -129,6 +130,7 @@
             server, keypair, floating_ip)
 
     @decorators.idempotent_id('88a529c2-1daa-4c85-9aec-d541ba3eb699')
+    @test.attr(type='slow')
     @test.services('compute', 'network')
     def test_server_connectivity_rebuild(self):
         keypair = self.create_keypair()
@@ -143,6 +145,7 @@
     @decorators.idempotent_id('2b2642db-6568-4b35-b812-eceed3fa20ce')
     @testtools.skipUnless(CONF.compute_feature_enabled.pause,
                           'Pause is not available.')
+    @test.attr(type='slow')
     @test.services('compute', 'network')
     def test_server_connectivity_pause_unpause(self):
         keypair = self.create_keypair()
@@ -160,6 +163,7 @@
     @decorators.idempotent_id('5cdf9499-541d-4923-804e-b9a60620a7f0')
     @testtools.skipUnless(CONF.compute_feature_enabled.suspend,
                           'Suspend is not available.')
+    @test.attr(type='slow')
     @test.services('compute', 'network')
     def test_server_connectivity_suspend_resume(self):
         keypair = self.create_keypair()
@@ -177,6 +181,7 @@
     @decorators.idempotent_id('719eb59d-2f42-4b66-b8b1-bb1254473967')
     @testtools.skipUnless(CONF.compute_feature_enabled.resize,
                           'Resize is not available.')
+    @test.attr(type='slow')
     @test.services('compute', 'network')
     def test_server_connectivity_resize(self):
         resize_flavor = CONF.compute.flavor_ref_alt
@@ -200,6 +205,7 @@
     @testtools.skipUnless(CONF.compute.min_compute_nodes > 1,
                           'Less than 2 compute nodes, skipping multinode '
                           'tests.')
+    @test.attr(type='slow')
     @test.services('compute', 'network')
     def test_server_connectivity_cold_migration(self):
         keypair = self.create_keypair()
@@ -225,6 +231,7 @@
     @testtools.skipUnless(CONF.compute.min_compute_nodes > 1,
                           'Less than 2 compute nodes, skipping multinode '
                           'tests.')
+    @test.attr(type='slow')
     @test.services('compute', 'network')
     def test_server_connectivity_cold_migration_revert(self):
         keypair = self.create_keypair()
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index 51b59c9..85d7e37 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -285,9 +285,9 @@
                                               % CONF.network.build_timeout)
 
         num, new_nic = self.diff_list[0]
-        ssh_client.assign_static_ip(nic=new_nic,
-                                    addr=new_port['fixed_ips'][0][
-                                        'ip_address'])
+        ssh_client.assign_static_ip(
+            nic=new_nic, addr=new_port['fixed_ips'][0]['ip_address'],
+            network_mask_bits=CONF.network.project_network_mask_bits)
         ssh_client.set_nic_state(nic=new_nic)
 
     def _get_server_nics(self, ssh_client):
@@ -406,6 +406,7 @@
     @decorators.idempotent_id('b158ea55-472e-4086-8fa9-c64ac0c6c1d0')
     @testtools.skipUnless(test.is_extension_enabled('net-mtu', 'network'),
                           'No way to calculate MTU for networks')
+    @test.attr(type='slow')
     @test.services('compute', 'network')
     def test_mtu_sized_frames(self):
         """Validate that network MTU sized frames fit through."""
@@ -418,6 +419,7 @@
                       'Connectivity can only be tested when in a '
                       'multitenant network environment')
     @decorators.skip_because(bug="1610994")
+    @test.attr(type='slow')
     @test.services('compute', 'network')
     def test_connectivity_between_vms_on_different_networks(self):
         """Test connectivity between VMs on different networks
@@ -493,6 +495,7 @@
     @testtools.skipIf(CONF.network.shared_physical_network,
                       'Router state can be altered only with multitenant '
                       'networks capabilities')
+    @test.attr(type='slow')
     @test.services('compute', 'network')
     def test_update_router_admin_state(self):
         """Test to update admin state up of router
@@ -526,6 +529,7 @@
                       'network isolation not available')
     @testtools.skipUnless(CONF.scenario.dhcp_client,
                           "DHCP client is not available.")
+    @test.attr(type='slow')
     @test.services('compute', 'network')
     def test_subnet_details(self):
         """Tests that subnet's extra configuration details are affecting VMs.
@@ -592,7 +596,8 @@
             # NOTE(amuller): we are renewing the lease as part of the retry
             # because Neutron updates dnsmasq asynchronously after the
             # subnet-update API call returns.
-            ssh_client.renew_lease(fixed_ip=floating_ip['fixed_ip_address'])
+            ssh_client.renew_lease(fixed_ip=floating_ip['fixed_ip_address'],
+                                   dhcp_client=CONF.scenario.dhcp_client)
             if ssh_client.get_dns_servers() != [alt_dns_server]:
                 LOG.debug("Failed to update DNS nameservers")
                 return False
@@ -608,6 +613,7 @@
     @testtools.skipUnless(CONF.network_feature_enabled.port_admin_state_change,
                           "Changing a port's admin state is not supported "
                           "by the test environment")
+    @test.attr(type='slow')
     @test.services('compute', 'network')
     def test_update_instance_port_admin_state(self):
         """Test to update admin_state_up attribute of instance port
@@ -653,6 +659,7 @@
                                        should_succeed=True)
 
     @decorators.idempotent_id('759462e1-8535-46b0-ab3a-33aa45c55aaa')
+    @test.attr(type='slow')
     @test.services('compute', 'network')
     def test_preserve_preexisting_port(self):
         """Test preserve pre-existing port
@@ -704,6 +711,7 @@
 
     @test.requires_ext(service='network', extension='l3_agent_scheduler')
     @decorators.idempotent_id('2e788c46-fb3f-4ac9-8f82-0561555bea73')
+    @test.attr(type='slow')
     @test.services('compute', 'network')
     def test_router_rescheduling(self):
         """Tests that router can be removed from agent and add to a new agent.
@@ -783,6 +791,7 @@
     @testtools.skipUnless(CONF.compute_feature_enabled.interface_attach,
                           'NIC hotplug not available')
     @decorators.idempotent_id('7c0bb1a2-d053-49a4-98f9-ca1a1d849f63')
+    @test.attr(type='slow')
     @test.services('compute', 'network')
     def test_port_security_macspoofing_port(self):
         """Tests port_security extension enforces mac spoofing
diff --git a/tempest/scenario/test_network_v6.py b/tempest/scenario/test_network_v6.py
index fcf395d..d8a1363 100644
--- a/tempest/scenario/test_network_v6.py
+++ b/tempest/scenario/test_network_v6.py
@@ -240,6 +240,7 @@
     def test_dualnet_dhcp6_stateless_from_os(self):
         self._prepare_and_test(address6_mode='dhcpv6-stateless', dualnet=True)
 
+    @test.attr(type='slow')
     @decorators.idempotent_id('cf1c4425-766b-45b8-be35-e2959728eb00')
     @test.services('compute', 'network')
     def test_dualnet_multi_prefix_dhcpv6_stateless(self):
diff --git a/tempest/scenario/test_object_storage_basic_ops.py b/tempest/scenario/test_object_storage_basic_ops.py
index c989e01..7fd8c91 100644
--- a/tempest/scenario/test_object_storage_basic_ops.py
+++ b/tempest/scenario/test_object_storage_basic_ops.py
@@ -46,6 +46,7 @@
         self.delete_container(container_name)
 
     @decorators.idempotent_id('916c7111-cb1f-44b2-816d-8f760e4ea910')
+    @test.attr(type='slow')
     @test.services('object_storage')
     def test_swift_acl_anonymous_download(self):
         """This test will cover below steps:
diff --git a/tempest/scenario/test_security_groups_basic_ops.py b/tempest/scenario/test_security_groups_basic_ops.py
index 3d383f7..fa12f33 100644
--- a/tempest/scenario/test_security_groups_basic_ops.py
+++ b/tempest/scenario/test_security_groups_basic_ops.py
@@ -494,6 +494,7 @@
             raise
 
     @decorators.idempotent_id('f4d556d7-1526-42ad-bafb-6bebf48568f6')
+    @test.attr(type='slow')
     @test.services('compute', 'network')
     def test_port_update_new_security_group(self):
         """Verifies the traffic after updating the vm port
@@ -548,6 +549,7 @@
             raise
 
     @decorators.idempotent_id('d2f77418-fcc4-439d-b935-72eca704e293')
+    @test.attr(type='slow')
     @test.services('compute', 'network')
     def test_multiple_security_groups(self):
         """Verify multiple security groups and checks that rules
@@ -579,6 +581,7 @@
                                    private_key=private_key,
                                    should_connect=True)
 
+    @test.attr(type='slow')
     @test.requires_ext(service='network', extension='port-security')
     @decorators.idempotent_id('7c811dcc-263b-49a3-92d2-1b4d8405f50c')
     @test.services('compute', 'network')
@@ -619,6 +622,7 @@
                 self._log_console_output(servers=tenant.servers)
             raise
 
+    @test.attr(type='slow')
     @test.requires_ext(service='network', extension='port-security')
     @decorators.idempotent_id('13ccf253-e5ad-424b-9c4a-97b88a026699')
     @testtools.skipUnless(
diff --git a/tempest/scenario/test_server_advanced_ops.py b/tempest/scenario/test_server_advanced_ops.py
index ec839cd..1960e9a 100644
--- a/tempest/scenario/test_server_advanced_ops.py
+++ b/tempest/scenario/test_server_advanced_ops.py
@@ -48,6 +48,7 @@
         cls.set_network_resources()
         super(TestServerAdvancedOps, cls).setup_credentials()
 
+    @test.attr(type='slow')
     @decorators.idempotent_id('e6c28180-7454-4b59-b188-0257af08a63b')
     @testtools.skipUnless(CONF.compute_feature_enabled.resize,
                           'Resize is not available.')
@@ -69,6 +70,7 @@
         waiters.wait_for_server_status(self.servers_client, instance_id,
                                        'ACTIVE')
 
+    @test.attr(type='slow')
     @decorators.idempotent_id('949da7d5-72c8-4808-8802-e3d70df98e2c')
     @testtools.skipUnless(CONF.compute_feature_enabled.suspend,
                           'Suspend is not available.')
diff --git a/tempest/scenario/test_shelve_instance.py b/tempest/scenario/test_shelve_instance.py
index 75cef88..9e763f8 100644
--- a/tempest/scenario/test_shelve_instance.py
+++ b/tempest/scenario/test_shelve_instance.py
@@ -74,6 +74,7 @@
                                         private_key=keypair['private_key'])
         self.assertEqual(timestamp, timestamp2)
 
+    @test.attr(type='slow')
     @decorators.idempotent_id('1164e700-0af0-4a4c-8792-35909a88743c')
     @testtools.skipUnless(CONF.network.public_network_id,
                           'The public_network_id option must be specified.')
@@ -81,6 +82,7 @@
     def test_shelve_instance(self):
         self._create_server_then_shelve_and_unshelve()
 
+    @test.attr(type='slow')
     @decorators.idempotent_id('c1b6318c-b9da-490b-9c67-9339b627271f')
     @testtools.skipUnless(CONF.network.public_network_id,
                           'The public_network_id option must be specified.')
diff --git a/tempest/scenario/test_snapshot_pattern.py b/tempest/scenario/test_snapshot_pattern.py
index 6dedd1d..a699de2 100644
--- a/tempest/scenario/test_snapshot_pattern.py
+++ b/tempest/scenario/test_snapshot_pattern.py
@@ -41,6 +41,7 @@
             raise cls.skipException("Snapshotting is not available.")
 
     @decorators.idempotent_id('608e604b-1d63-4a82-8e3e-91bc665c90b4')
+    @test.attr(type='slow')
     @testtools.skipUnless(CONF.network.public_network_id,
                           'The public_network_id option must be specified.')
     @test.services('compute', 'network', 'image')
diff --git a/tempest/scenario/test_stamp_pattern.py b/tempest/scenario/test_stamp_pattern.py
index 716c0bf..aabb767 100644
--- a/tempest/scenario/test_stamp_pattern.py
+++ b/tempest/scenario/test_stamp_pattern.py
@@ -88,6 +88,7 @@
                                           CONF.compute.build_interval):
             raise lib_exc.TimeoutException
 
+    @test.attr(type='slow')
     @decorators.skip_because(bug="1664793")
     @decorators.idempotent_id('10fd234a-515c-41e5-b092-8323060598c5')
     @testtools.skipUnless(CONF.compute_feature_enabled.snapshot,
diff --git a/tempest/scenario/test_volume_boot_pattern.py b/tempest/scenario/test_volume_boot_pattern.py
index b72dae9..8cab19c 100644
--- a/tempest/scenario/test_volume_boot_pattern.py
+++ b/tempest/scenario/test_volume_boot_pattern.py
@@ -95,7 +95,6 @@
         waiters.wait_for_server_termination(self.servers_client, server['id'])
 
     @decorators.idempotent_id('557cd2c2-4eb8-4dce-98be-f86765ff311b')
-    @test.attr(type='smoke')
     @testtools.skipUnless(CONF.network.public_network_id,
                           'The public_network_id option must be specified.')
     @test.services('compute', 'volume', 'image')
@@ -177,6 +176,7 @@
         self.assertEqual(timestamp, timestamp3)
 
     @decorators.idempotent_id('05795fb2-b2a7-4c9f-8fac-ff25aedb1489')
+    @test.attr(type='slow')
     @test.services('compute', 'image', 'volume')
     def test_create_server_from_volume_snapshot(self):
         # Create a volume from an image
diff --git a/tempest/scenario/test_volume_migrate_attached.py b/tempest/scenario/test_volume_migrate_attached.py
index 891e22d..f580ea6 100644
--- a/tempest/scenario/test_volume_migrate_attached.py
+++ b/tempest/scenario/test_volume_migrate_attached.py
@@ -91,6 +91,7 @@
         waiters.wait_for_volume_retype(self.volumes_client,
                                        volume_id, new_volume_type)
 
+    @test.attr(type='slow')
     @decorators.idempotent_id('deadd2c2-beef-4dce-98be-f86765ff311b')
     @test.services('compute', 'volume')
     def test_volume_migrate_attached(self):
diff --git a/tempest/test.py b/tempest/test.py
index 970e97c..52994ac 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -643,7 +643,11 @@
             cred_provider, networks_client, CONF.compute.fixed_network_name)
 
     def assertEmpty(self, list, msg=None):
+        if msg is None:
+            msg = "list is not empty: %s" % list
         self.assertEqual(0, len(list), msg)
 
     def assertNotEmpty(self, list, msg=None):
+        if msg is None:
+            msg = "list is empty."
         self.assertGreater(len(list), 0, msg)
diff --git a/tempest/tests/common/utils/linux/test_remote_client.py b/tempest/tests/common/utils/linux/test_remote_client.py
index 5be6229..1c5e89f 100644
--- a/tempest/tests/common/utils/linux/test_remote_client.py
+++ b/tempest/tests/common/utils/linux/test_remote_client.py
@@ -67,16 +67,6 @@
         self.ssh_mock = self.useFixture(mockpatch.PatchObject(self.conn,
                                                               'ssh_client'))
 
-    def test_get_hostname(self):
-        self.ssh_mock.mock.exec_command.return_value = 'fake_hostname'
-        self.assertEqual(self.conn.get_hostname(), 'fake_hostname')
-
-    def test_get_ram_size(self):
-        free_output = "Mem:         48294      45738       2555          0" \
-                      "402      40346"
-        self.ssh_mock.mock.exec_command.return_value = free_output
-        self.assertEqual(self.conn.get_ram_size_in_mb(), '48294')
-
     def test_write_to_console_regular_str(self):
         self.conn.write_to_console('test')
         self._assert_exec_called_with(
@@ -102,11 +92,6 @@
         cmd = "set -eu -o pipefail; PATH=$PATH:/sbin; " + cmd
         self.ssh_mock.mock.exec_command.assert_called_with(cmd)
 
-    def test_get_number_of_vcpus(self):
-        self.ssh_mock.mock.exec_command.return_value = '16'
-        self.assertEqual(self.conn.get_number_of_vcpus(), 16)
-        self._assert_exec_called_with('grep -c ^processor /proc/cpuinfo')
-
     def test_get_disks(self):
         output_lsblk = """\
 NAME       MAJ:MIN    RM          SIZE RO TYPE MOUNTPOINT
diff --git a/tempest/tests/lib/common/utils/test_data_utils.py b/tempest/tests/lib/common/utils/test_data_utils.py
index 4446e5c..8bdf70e 100644
--- a/tempest/tests/lib/common/utils/test_data_utils.py
+++ b/tempest/tests/lib/common/utils/test_data_utils.py
@@ -37,16 +37,20 @@
         actual2 = data_utils.rand_uuid_hex()
         self.assertNotEqual(actual, actual2)
 
-    def test_rand_name(self):
-        actual = data_utils.rand_name()
+    def test_rand_name_with_default_prefix(self):
+        actual = data_utils.rand_name('foo')
         self.assertIsInstance(actual, str)
-        actual2 = data_utils.rand_name()
+        self.assertTrue(actual.startswith('tempest-foo'))
+        actual2 = data_utils.rand_name('foo')
+        self.assertTrue(actual2.startswith('tempest-foo'))
         self.assertNotEqual(actual, actual2)
 
-        actual = data_utils.rand_name('foo')
+    def test_rand_name_with_none_prefix(self):
+        actual = data_utils.rand_name('foo', prefix=None)
+        self.assertIsInstance(actual, str)
         self.assertTrue(actual.startswith('foo'))
-        actual2 = data_utils.rand_name('foo')
-        self.assertTrue(actual.startswith('foo'))
+        actual2 = data_utils.rand_name('foo', prefix=None)
+        self.assertTrue(actual2.startswith('foo'))
         self.assertNotEqual(actual, actual2)
 
     def test_rand_name_with_prefix(self):
diff --git a/tools/tox_install.sh b/tools/tox_install.sh
new file mode 100755
index 0000000..43468e4
--- /dev/null
+++ b/tools/tox_install.sh
@@ -0,0 +1,30 @@
+#!/usr/bin/env bash
+
+# Client constraint file contains this client version pin that is in conflict
+# with installing the client from source. We should remove the version pin in
+# the constraints file before applying it for from-source installation.
+
+CONSTRAINTS_FILE=$1
+shift 1
+
+set -e
+
+# NOTE(tonyb): Place this in the tox enviroment's log dir so it will get
+# published to logs.openstack.org for easy debugging.
+localfile="$VIRTUAL_ENV/log/upper-constraints.txt"
+
+if [[ $CONSTRAINTS_FILE != http* ]]; then
+    CONSTRAINTS_FILE=file://$CONSTRAINTS_FILE
+fi
+# NOTE(tonyb): need to add curl to bindep.txt if the project supports bindep
+curl $CONSTRAINTS_FILE --insecure --progress-bar --output $localfile
+
+pip install -c$localfile openstack-requirements
+
+# This is the main purpose of the script: Allow local installation of
+# the current repo. It is listed in constraints file and thus any
+# install will be constrained and we need to unconstrain it.
+edit-constraints $localfile -- $CLIENT_NAME
+
+pip install -c$localfile -U $*
+exit $?
diff --git a/tox.ini b/tox.ini
index d8d390e..dfa8332 100644
--- a/tox.ini
+++ b/tox.ini
@@ -8,6 +8,8 @@
 setenv =
     VIRTUAL_ENV={envdir}
     OS_TEST_PATH=./tempest/test_discover
+    BRANCH_NAME=master
+    CLIENT_NAME=tempest
 deps =
     setuptools
     -r{toxinidir}/requirements.txt
@@ -17,9 +19,12 @@
     VIRTUAL_ENV={envdir}
     OS_TEST_PATH=./tempest/tests
     PYTHONWARNINGS=default::DeprecationWarning
-passenv = OS_STDOUT_CAPTURE OS_STDERR_CAPTURE OS_TEST_TIMEOUT OS_TEST_LOCK_PATH OS_TEST_PATH TEMPEST_CONFIG TEMPEST_CONFIG_DIR http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY
+    BRANCH_NAME=master
+    CLIENT_NAME=tempest
+passenv = OS_STDOUT_CAPTURE OS_STDERR_CAPTURE OS_TEST_TIMEOUT OS_TEST_LOCK_PATH OS_TEST_PATH TEMPEST_CONFIG TEMPEST_CONFIG_DIR http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY ZUUL_CACHE_DIR REQUIREMENTS_PIP_LOCATION
 usedevelop = True
-install_command = pip install -U {opts} {packages}
+install_command =
+    {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
 whitelist_externals = *
 deps =
     -r{toxinidir}/requirements.txt
@@ -78,7 +83,8 @@
 # See the testrepository bug: https://bugs.launchpad.net/testrepository/+bug/1208610
 commands =
     find . -type f -name "*.pyc" -delete
-    tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario))' {posargs}
+    tempest run --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.api)' {posargs}
+    tempest run --combine --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.scenario)' {posargs}
 
 [testenv:full-serial]
 envdir = .tox/tempest
@@ -91,6 +97,16 @@
     find . -type f -name "*.pyc" -delete
     tempest run --serial --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario))' {posargs}
 
+[testenv:scenario]
+envdir = .tox/tempest
+sitepackages = {[tempestenv]sitepackages}
+setenv = {[tempestenv]setenv}
+deps = {[tempestenv]deps}
+# The regex below is used to select all scenario tests
+commands =
+    find . -type f -name "*.pyc" -delete
+    tempest run --serial --regex '(^tempest\.scenario)' {posargs}
+
 [testenv:smoke]
 envdir = .tox/tempest
 sitepackages = {[tempestenv]sitepackages}