Merge "Publish non-openstack namespace tempest plugins"
diff --git a/.zuul.yaml b/.zuul.yaml
index 8ffe90d..5bec9f9 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -615,8 +615,6 @@
             irrelevant-files: *tempest-irrelevant-files
         - neutron-tempest-dvr-ha-multinode-full:
             irrelevant-files: *tempest-irrelevant-files
-        - nova-cells-v1:
-            irrelevant-files: *tempest-irrelevant-files
         - nova-tempest-v2-api:
             irrelevant-files: *tempest-irrelevant-files
         - legacy-tempest-dsvm-lvm-multibackend:
diff --git a/README.rst b/README.rst
index e8206ee..841fae6 100644
--- a/README.rst
+++ b/README.rst
@@ -119,6 +119,17 @@
    will run the same set of tests as the default gate jobs. Or you can
    use `unittest`_ compatible test runners such as `testr`_, `pytest`_ etc.
 
+   Tox also contains several existing job configurations. For example::
+
+    $ tox -e full
+
+   which will run the same set of tests as the OpenStack gate. (it's exactly how
+   the gate invokes Tempest) Or::
+
+    $ tox -e smoke
+
+   to run the tests tagged as smoke.
+
 .. _unittest: https://docs.python.org/3/library/unittest.html
 .. _testr: https://testrepository.readthedocs.org/en/latest/MANUAL.html
 .. _stestr: https://stestr.readthedocs.org/en/latest/MANUAL.html
@@ -270,14 +281,3 @@
 To run one single test serially ::
 
     $ testr run tempest.api.compute.servers.test_servers_negative.ServersNegativeTestJSON.test_reboot_non_existent_server
-
-Tox also contains several existing job configurations. For example::
-
-    $ tox -e full
-
-which will run the same set of tests as the OpenStack gate. (it's exactly how
-the gate invokes Tempest) Or::
-
-    $ tox -e smoke
-
-to run the tests tagged as smoke.
diff --git a/releasenotes/notes/QoS-client-for-placement-based-minimum-bw-allocation-8e5854d5754cec68.yaml b/releasenotes/notes/QoS-client-for-placement-based-minimum-bw-allocation-8e5854d5754cec68.yaml
new file mode 100644
index 0000000..b66ea3a
--- /dev/null
+++ b/releasenotes/notes/QoS-client-for-placement-based-minimum-bw-allocation-8e5854d5754cec68.yaml
@@ -0,0 +1,25 @@
+---
+features:
+  - |
+    Add ``qos-policies`` and ``qos-minimum-bandwidth-rule`` clients
+    to Tempest to make possible the testing of the placement based
+    bandwidth allocation feature.
+    The following API calls are available for tempest from now:
+
+    ``QoS policies`` client:
+
+    * GET /qos/policies
+    * POST /qos/policies
+    * GET /qos/policies/{policy_id}
+    * PUT /qos/policies/{policy_id}
+    * DELETE /qos/policies/{policy_id}
+
+
+    ``QoS minimum bandwidth rules`` client:
+
+    * GET qos/policies/{policy_id}/minimum_bandwidth_rules
+    * POST /qos/policies/{policy_id}/minimum_bandwidth_rules
+    * GET qos/policies/{policy_id}/minimum_bandwidth_rules/{rule_id}
+    * PUT qos/policies/{policy_id}/minimum_bandwidth_rules/{rule_id}
+    * DELETE /qos/policies/{policy_id}/minimum_bandwidth_rules/{rule_id}
+
diff --git a/releasenotes/notes/remove-some-deprecated-auth-and-identity-options-xa1xd9b8fb948g4f.yaml b/releasenotes/notes/remove-some-deprecated-auth-and-identity-options-xa1xd9b8fb948g4f.yaml
new file mode 100644
index 0000000..fa21afd
--- /dev/null
+++ b/releasenotes/notes/remove-some-deprecated-auth-and-identity-options-xa1xd9b8fb948g4f.yaml
@@ -0,0 +1,8 @@
+upgrade:
+  - |
+    Remove deprecated config option ``endpoint_type`` from
+    ``identity`` group. Use ``v2_public_endpoint_type`` from
+    ``identity`` group instead.
+    Remove deprecated config option ``tenant_isolation_domain_name``
+    from ``auth`` group. Use ``default_credentials_domain_name`` from
+    ``auth`` group instead.
diff --git a/releasenotes/notes/remove-some-deprecated-identity-options-0ffxd1b8db928e43.yaml b/releasenotes/notes/remove-some-deprecated-identity-options-0ffxd1b8db928e43.yaml
new file mode 100644
index 0000000..e9e9444
--- /dev/null
+++ b/releasenotes/notes/remove-some-deprecated-identity-options-0ffxd1b8db928e43.yaml
@@ -0,0 +1,11 @@
+upgrade:
+  - |
+    Remove deprecated config option ``admin_username`` from
+    ``identity`` groups. Use ``admin_username`` from ``auth`` instead.
+    Remove deprecated config option ``admin_tenant_name`` from
+    ``auth`` and ``identity`` groups. Use ``admin_project_name`` from
+    ``auth`` instead.
+    Remove deprecated config option ``admin_password`` from
+    ``identity`` groups. Use ``admin_password`` from ``auth`` instead.
+    Remove deprecated config option ``admin_domain_name`` from
+    ``identity`` groups. Use ``admin_domain_name`` from ``auth`` instead.
\ No newline at end of file
diff --git a/releasenotes/notes/support-microversion-in-scenario-test-b4fbfdd3a977fc58.yaml b/releasenotes/notes/support-microversion-in-scenario-test-b4fbfdd3a977fc58.yaml
new file mode 100644
index 0000000..4d0a3dd
--- /dev/null
+++ b/releasenotes/notes/support-microversion-in-scenario-test-b4fbfdd3a977fc58.yaml
@@ -0,0 +1,14 @@
+---
+features:
+  - |
+    Add microversion support for scenario tests. Scenario test calls
+    multiple service API within same test and many services like compute,
+    volume and placement etc support API microversion. With microversion
+    support in scenario test, we can call different service API with
+    different microvesion. Which means we can implement scenario tests
+    for microversion also.
+    Currently Scenario manager support below services microversion:
+
+    * Compute
+    * Volume
+    * Placement
diff --git a/tempest/api/compute/servers/test_novnc.py b/tempest/api/compute/servers/test_novnc.py
index daf6a06..50ffb21 100644
--- a/tempest/api/compute/servers/test_novnc.py
+++ b/tempest/api/compute/servers/test_novnc.py
@@ -16,6 +16,7 @@
 import struct
 
 import six
+import six.moves.urllib.parse as urlparse
 import urllib3
 
 from tempest.api.compute import base
@@ -73,8 +74,9 @@
                          'initial call: ' + six.text_type(resp.status))
         # Do some basic validation to make sure it is an expected HTML document
         resp_data = resp.data.decode()
-        self.assertIn('<html>', resp_data,
-                      'Not a valid html document in the response.')
+        # This is needed in the case of example: <html lang="en">
+        self.assertRegex(resp_data, '<html.*>',
+                         'Not a valid html document in the response.')
         self.assertIn('</html>', resp_data,
                       'Not a valid html document in the response.')
         # Just try to make sure we got JavaScript back for noVNC, since we
@@ -204,7 +206,18 @@
                                                type='novnc')['console']
         self.assertEqual('novnc', body['type'])
         # Do the WebSockify HTTP Request to novncproxy with a bad token
-        url = body['url'].replace('token=', 'token=bad')
+        parts = urlparse.urlparse(body['url'])
+        qparams = urlparse.parse_qs(parts.query)
+        if 'path' in qparams:
+            qparams['path'] = urlparse.unquote(qparams['path'][0]).replace(
+                'token=', 'token=bad')
+        elif 'token' in qparams:
+            qparams['token'] = 'bad' + qparams['token'][0]
+        new_query = urlparse.urlencode(qparams)
+        new_parts = urlparse.ParseResult(parts.scheme, parts.netloc,
+                                         parts.path, parts.params, new_query,
+                                         parts.fragment)
+        url = urlparse.urlunparse(new_parts)
         self._websocket = compute.create_websocket(url)
         # Make sure the novncproxy rejected the connection and closed it
         data = self._websocket.receive_frame()
diff --git a/tempest/api/identity/v2/test_users.py b/tempest/api/identity/v2/test_users.py
index 158dfb3..2eea860 100644
--- a/tempest/api/identity/v2/test_users.py
+++ b/tempest/api/identity/v2/test_users.py
@@ -15,6 +15,8 @@
 
 import time
 
+import testtools
+
 from tempest.api.identity import base
 from tempest import config
 from tempest.lib.common.utils import data_utils
@@ -78,6 +80,10 @@
         self.non_admin_users_client.auth_provider.set_auth()
 
     @decorators.idempotent_id('165859c9-277f-4124-9479-a7d1627b0ca7')
+    @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source,
+                      'Skipped because environment has an '
+                      'immutable user source and solely '
+                      'provides read-only access to users.')
     def test_user_update_own_password(self):
         old_pass = self.creds.password
         old_token = self.non_admin_users_client.token
diff --git a/tempest/api/identity/v3/test_users.py b/tempest/api/identity/v3/test_users.py
index 13b5161..d4e7612 100644
--- a/tempest/api/identity/v3/test_users.py
+++ b/tempest/api/identity/v3/test_users.py
@@ -77,6 +77,10 @@
         self.non_admin_users_client.auth_provider.set_auth()
 
     @decorators.idempotent_id('ad71bd23-12ad-426b-bb8b-195d2b635f27')
+    @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source,
+                      'Skipped because environment has an '
+                      'immutable user source and solely '
+                      'provides read-only access to users.')
     def test_user_update_own_password(self):
         old_pass = self.creds.password
         old_token = self.non_admin_client.token
@@ -102,6 +106,10 @@
     @testtools.skipUnless(CONF.identity_feature_enabled.security_compliance,
                           'Security compliance not available.')
     @decorators.idempotent_id('941784ee-5342-4571-959b-b80dd2cea516')
+    @testtools.skipIf(CONF.identity_feature_enabled.immutable_user_source,
+                      'Skipped because environment has an '
+                      'immutable user source and solely '
+                      'provides read-only access to users.')
     def test_password_history_check_self_service_api(self):
         old_pass = self.creds.password
         new_pass1 = data_utils.rand_password()
diff --git a/tempest/api/network/test_security_groups.py b/tempest/api/network/test_security_groups.py
index ea68005..ef19122 100644
--- a/tempest/api/network/test_security_groups.py
+++ b/tempest/api/network/test_security_groups.py
@@ -175,7 +175,14 @@
 
         sg_id = group_create_body['security_group']['id']
         direction = 'ingress'
-        protocol = 'icmp'
+        # The Neutron API accepts 'icmp', 'icmpv6' and 'ipv6-icmp' for
+        # IPv6 ICMP protocol names, but the latter is preferred and the
+        # others considered "legacy".  Use 'ipv6-icmp' as the API could
+        # change to return only that value, see
+        # https://review.opendev.org/#/c/453346/
+        # The neutron-tempest-plugin API tests pass all three and verify
+        # the output, so there is no need to duplicate that here.
+        protocol = 'ipv6-icmp' if self._ip_version == 6 else 'icmp'
         icmp_type_codes = [(3, 2), (3, 0), (8, 0), (0, 0), (11, None)]
         for icmp_type, icmp_code in icmp_type_codes:
             self._create_verify_security_group_rule(sg_id, direction,
diff --git a/tempest/api/volume/test_volumes_extend.py b/tempest/api/volume/test_volumes_extend.py
index ac9a9c7..c3f44e2 100644
--- a/tempest/api/volume/test_volumes_extend.py
+++ b/tempest/api/volume/test_volumes_extend.py
@@ -32,7 +32,7 @@
     @decorators.idempotent_id('9a36df71-a257-43a5-9555-dc7c88e66e0e')
     def test_volume_extend(self):
         # Extend Volume Test.
-        volume = self.create_volume(image_ref=self.image_ref)
+        volume = self.create_volume(imageRef=self.image_ref)
         extend_size = volume['size'] * 2
         self.volumes_client.extend_volume(volume['id'],
                                           new_size=extend_size)
diff --git a/tempest/api/volume/test_volumes_snapshots.py b/tempest/api/volume/test_volumes_snapshots.py
index 1855386..72e7290 100644
--- a/tempest/api/volume/test_volumes_snapshots.py
+++ b/tempest/api/volume/test_volumes_snapshots.py
@@ -44,7 +44,7 @@
         server = self.create_server()
         # NOTE(zhufl) Here we create volume from self.image_ref for adding
         # coverage for "creating snapshot from non-blank volume".
-        volume = self.create_volume(image_ref=self.image_ref)
+        volume = self.create_volume(imageRef=self.image_ref)
         self.attach_volume(server['id'], volume['id'])
 
         # Snapshot a volume which attached to an instance with force=False
diff --git a/tempest/clients.py b/tempest/clients.py
index 0506646..f7a83be 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -69,6 +69,8 @@
         self.network_versions_client = self.network.NetworkVersionsClient()
         self.service_providers_client = self.network.ServiceProvidersClient()
         self.tags_client = self.network.TagsClient()
+        self.qos_client = self.network.QosClient()
+        self.qos_min_bw_client = self.network.QosMinimumBandwidthRulesClient()
 
     def _set_image_clients(self):
         if CONF.service_available.glance:
diff --git a/tempest/common/compute.py b/tempest/common/compute.py
index 1489e60..4ac92d9 100644
--- a/tempest/common/compute.py
+++ b/tempest/common/compute.py
@@ -392,8 +392,11 @@
 
     def _upgrade(self, url):
         """Upgrade the HTTP connection to a WebSocket and verify."""
-        # The real request goes to the /websockify URI always
-        reqdata = 'GET /websockify HTTP/1.1\r\n'
+        # It is possible to pass the path as a query parameter in the request,
+        # so use it if present
+        qparams = urlparse.parse_qs(url.query)
+        path = qparams['path'][0] if 'path' in qparams else '/websockify'
+        reqdata = 'GET %s HTTP/1.1\r\n' % path
         reqdata += 'Host: %s' % url.hostname
         # Add port only if we have one specified
         if url.port:
@@ -402,7 +405,7 @@
         reqdata += '\r\n'
         # Tell the HTTP Server to Upgrade the connection to a WebSocket
         reqdata += 'Upgrade: websocket\r\nConnection: Upgrade\r\n'
-        # The token=xxx is sent as a Cookie not in the URI
+        # The token=xxx is sent as a Cookie not in the URI for noVNC < v1.1.0
         reqdata += 'Cookie: %s\r\n' % url.query
         # Use a hard-coded WebSocket key since a test program
         reqdata += 'Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n'
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
old mode 100644
new mode 100755
index 77ec0f8..e1b8cf5
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -202,6 +202,8 @@
                 resource_name=resource_name, resource_id=resource_id)
         if resource_name == 'volume' and resource_status == 'error_restoring':
             raise exceptions.VolumeRestoreErrorException(volume_id=resource_id)
+        if resource_status == 'error_extending' and resource_status != status:
+            raise exceptions.VolumeExtendErrorException(volume_id=resource_id)
 
         if int(time.time()) - start >= client.build_timeout:
             message = ('%s %s failed to reach %s status (current %s) '
diff --git a/tempest/config.py b/tempest/config.py
index e3ac47c..9e4718b 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -69,10 +69,7 @@
     cfg.StrOpt('default_credentials_domain_name',
                default='Default',
                help="Default domain used when getting v3 credentials. "
-                    "This is the name keystone uses for v2 compatibility.",
-               deprecated_opts=[cfg.DeprecatedOpt(
-                                'tenant_isolation_domain_name',
-                                group='auth')]),
+                    "This is the name keystone uses for v2 compatibility."),
     cfg.BoolOpt('create_isolated_networks',
                 default=True,
                 help="If use_dynamic_credentials is set to True and Neutron "
@@ -84,27 +81,20 @@
     cfg.StrOpt('admin_username',
                help="Username for an administrative user. This is needed for "
                     "authenticating requests made by project isolation to "
-                    "create users and projects",
-               deprecated_group='identity'),
+                    "create users and projects"),
     cfg.StrOpt('admin_project_name',
                help="Project name to use for an administrative user. This is "
                     "needed for authenticating requests made by project "
-                    "isolation to create users and projects",
-               deprecated_opts=[cfg.DeprecatedOpt('admin_tenant_name',
-                                                  group='auth'),
-                                cfg.DeprecatedOpt('admin_tenant_name',
-                                                  group='identity')]),
+                    "isolation to create users and projects"),
     cfg.StrOpt('admin_password',
                help="Password to use for an administrative user. This is "
                     "needed for authenticating requests made by project "
                     "isolation to create users and projects",
-               secret=True,
-               deprecated_group='identity'),
+               secret=True),
     cfg.StrOpt('admin_domain_name',
                default='Default',
                help="Admin domain name for authentication (Keystone V3). "
-                    "The same domain applies to user and project",
-               deprecated_group='identity'),
+                    "The same domain applies to user and project"),
 ]
 
 identity_group = cfg.OptGroup(name='identity',
@@ -146,9 +136,7 @@
                choices=['public', 'admin', 'internal',
                         'publicURL', 'adminURL', 'internalURL'],
                help="The public endpoint type to use for OpenStack Identity "
-                    "(Keystone) API v2",
-               deprecated_opts=[cfg.DeprecatedOpt('endpoint_type',
-                                                  group='identity')]),
+                    "(Keystone) API v2"),
     cfg.StrOpt('v3_endpoint_type',
                default='adminURL',
                choices=['public', 'admin', 'internal',
diff --git a/tempest/exceptions.py b/tempest/exceptions.py
old mode 100644
new mode 100755
index a430d5d..c05e7a6
--- a/tempest/exceptions.py
+++ b/tempest/exceptions.py
@@ -42,6 +42,11 @@
     message = "Volume %(volume_id)s failed to restore and is in ERROR status"
 
 
+class VolumeExtendErrorException(exceptions.TempestException):
+    message = ("Volume %(volume_id)s failed to extend and "
+               "is in error_extending status")
+
+
 class StackBuildErrorException(exceptions.TempestException):
     message = ("Stack %(stack_identifier)s is in %(stack_status)s status "
                "due to '%(stack_status_reason)s'")
diff --git a/tempest/lib/services/network/__init__.py b/tempest/lib/services/network/__init__.py
index 419e593..69f178e 100644
--- a/tempest/lib/services/network/__init__.py
+++ b/tempest/lib/services/network/__init__.py
@@ -21,6 +21,9 @@
     MeteringLabelsClient
 from tempest.lib.services.network.networks_client import NetworksClient
 from tempest.lib.services.network.ports_client import PortsClient
+from tempest.lib.services.network.qos_client import QosClient
+from tempest.lib.services.network.qos_minimum_bandwidth_rules_client import \
+    QosMinimumBandwidthRulesClient
 from tempest.lib.services.network.quotas_client import QuotasClient
 from tempest.lib.services.network.routers_client import RoutersClient
 from tempest.lib.services.network.security_group_rules_client import \
@@ -37,6 +40,7 @@
 __all__ = ['AgentsClient', 'ExtensionsClient', 'FloatingIPsClient',
            'MeteringLabelRulesClient', 'MeteringLabelsClient',
            'NetworksClient', 'NetworkVersionsClient', 'PortsClient',
-           'QuotasClient', 'RoutersClient', 'SecurityGroupRulesClient',
-           'SecurityGroupsClient', 'ServiceProvidersClient',
-           'SubnetpoolsClient', 'SubnetsClient', 'TagsClient']
+           'QosClient', 'QosMinimumBandwidthRulesClient', 'QuotasClient',
+           'RoutersClient', 'SecurityGroupRulesClient', 'SecurityGroupsClient',
+           'ServiceProvidersClient', 'SubnetpoolsClient', 'SubnetsClient',
+           'TagsClient']
diff --git a/tempest/lib/services/network/qos_client.py b/tempest/lib/services/network/qos_client.py
new file mode 100644
index 0000000..bcd1066
--- /dev/null
+++ b/tempest/lib/services/network/qos_client.py
@@ -0,0 +1,70 @@
+# Copyright (c) 2019 Ericsson
+#
+#    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.lib.services.network import base
+
+
+class QosClient(base.BaseNetworkClient):
+
+    def create_qos_policy(self, **kwargs):
+        """Creates a QoS policy.
+
+        For full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/network/v2/index.html#create-qos-policy
+        """
+        uri = '/qos/policies'
+        post_data = {'policy': kwargs}
+        return self.create_resource(uri, post_data)
+
+    def update_qos_policy(self, qos_policy_id, **kwargs):
+        """Updates a QoS policy.
+
+        For full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/network/v2/index.html#update-qos-policy
+        """
+        uri = '/qos/policies/%s' % qos_policy_id
+        post_data = {'policy': kwargs}
+        return self.update_resource(uri, post_data)
+
+    def show_qos_policy(self, qos_policy_id, **fields):
+        """Show details of a QoS policy.
+
+        For full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/network/v2/index.html#show-qos-policy-details
+        """
+        uri = '/qos/policies/%s' % qos_policy_id
+        return self.show_resource(uri, **fields)
+
+    def delete_qos_policy(self, qos_policy_id):
+        """Deletes a QoS policy.
+
+        For full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/network/v2/index.html#delete-qos-policy
+        """
+        uri = '/qos/policies/%s' % qos_policy_id
+        return self.delete_resource(uri)
+
+    def list_qos_policies(self, **filters):
+        """Lists QoS policies.
+
+        For full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/network/v2/index.html#list-qos-policies
+        """
+        uri = '/qos/policies'
+        return self.list_resources(uri, **filters)
diff --git a/tempest/lib/services/network/qos_minimum_bandwidth_rules_client.py b/tempest/lib/services/network/qos_minimum_bandwidth_rules_client.py
new file mode 100644
index 0000000..4f4ee3f
--- /dev/null
+++ b/tempest/lib/services/network/qos_minimum_bandwidth_rules_client.py
@@ -0,0 +1,73 @@
+# Copyright (c) 2019 Ericsson
+#
+#    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.lib.services.network import base
+
+
+class QosMinimumBandwidthRulesClient(base.BaseNetworkClient):
+
+    def create_minimum_bandwidth_rule(self, qos_policy_id, **kwargs):
+        """Creates a minimum bandwidth rule for a QoS policy.
+
+        For full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/network/v2/index.html#create-minimum-bandwidth-rule
+        """
+        uri = '/qos/policies/%s/minimum_bandwidth_rules' % qos_policy_id
+        post_data = {'minimum_bandwidth_rule': kwargs}
+        return self.create_resource(uri, post_data)
+
+    def update_minimum_bandwidth_rule(self, qos_policy_id, rule_id, **kwargs):
+        """Updates a minimum bandwidth rule.
+
+        For full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/network/v2/index.html#update-minimum-bandwidth-rule
+        """
+        uri = '/qos/policies/%s/minimum_bandwidth_rules/%s' % (
+            qos_policy_id, rule_id)
+        post_data = {'minimum_bandwidth_rule': kwargs}
+        return self.update_resource(uri, post_data)
+
+    def show_minimum_bandwidth_rule(self, qos_policy_id, rule_id, **fields):
+        """Show details of a minimum bandwidth rule.
+
+        For full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/network/v2/index.html#show-minimum-bandwidth-rule-details
+        """
+        uri = '/qos/policies/%s/minimum_bandwidth_rules/%s' % (
+            qos_policy_id, rule_id)
+        return self.show_resource(uri, **fields)
+
+    def delete_minimum_bandwidth_rule(self, qos_policy_id, rule_id):
+        """Deletes a minimum bandwidth rule for a QoS policy.
+
+        For full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/network/v2/index.html#delete-minimum-bandwidth-rule
+        """
+        uri = '/qos/policies/%s/minimum_bandwidth_rules/%s' % (
+            qos_policy_id, rule_id)
+        return self.delete_resource(uri)
+
+    def list_minimum_bandwidth_rules(self, qos_policy_id, **filters):
+        """Lists all minimum bandwidth rules for a QoS policy.
+
+        For full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/network/v2/index.html#list-minimum-bandwidth-rules-for-qos-policy
+        """
+        uri = '/qos/policies/%s/minimum_bandwidth_rules' % qos_policy_id
+        return self.list_resources(uri, **filters)
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index aa06fd0..87d7e76 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -28,6 +28,8 @@
 from tempest.common import waiters
 from tempest import config
 from tempest import exceptions
+from tempest.lib.common import api_microversion_fixture
+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 as lib_exc
@@ -37,12 +39,57 @@
 
 LOG = log.getLogger(__name__)
 
+LATEST_MICROVERSION = 'latest'
+
 
 class ScenarioTest(tempest.test.BaseTestCase):
     """Base class for scenario tests. Uses tempest own clients. """
 
     credentials = ['primary']
 
+    compute_min_microversion = None
+    compute_max_microversion = LATEST_MICROVERSION
+    volume_min_microversion = None
+    volume_max_microversion = LATEST_MICROVERSION
+    placement_min_microversion = None
+    placement_max_microversion = LATEST_MICROVERSION
+
+    @classmethod
+    def skip_checks(cls):
+        super(ScenarioTest, cls).skip_checks()
+        api_version_utils.check_skip_with_microversion(
+            cls.compute_min_microversion, cls.compute_max_microversion,
+            CONF.compute.min_microversion, CONF.compute.max_microversion)
+        api_version_utils.check_skip_with_microversion(
+            cls.volume_min_microversion, cls.volume_max_microversion,
+            CONF.volume.min_microversion, CONF.volume.max_microversion)
+        api_version_utils.check_skip_with_microversion(
+            cls.placement_min_microversion, cls.placement_max_microversion,
+            CONF.placement.min_microversion, CONF.placement.max_microversion)
+
+    @classmethod
+    def resource_setup(cls):
+        super(ScenarioTest, cls).resource_setup()
+        cls.compute_request_microversion = (
+            api_version_utils.select_request_microversion(
+                cls.compute_min_microversion,
+                CONF.compute.min_microversion))
+        cls.volume_request_microversion = (
+            api_version_utils.select_request_microversion(
+                cls.volume_min_microversion,
+                CONF.volume.min_microversion))
+        cls.placement_request_microversion = (
+            api_version_utils.select_request_microversion(
+                cls.placement_min_microversion,
+                CONF.placement.min_microversion))
+
+    def setUp(self):
+        super(ScenarioTest, self).setUp()
+        self.useFixture(api_microversion_fixture.APIMicroversionFixture(
+            compute_microversion=self.compute_request_microversion,
+            volume_microversion=self.volume_request_microversion,
+            placement_microversion=self.placement_request_microversion))
+
     @classmethod
     def setup_clients(cls):
         super(ScenarioTest, cls).setup_clients()
diff --git a/tempest/tests/common/test_waiters.py b/tempest/tests/common/test_waiters.py
old mode 100644
new mode 100755
index d56e8a4..02e1c99
--- a/tempest/tests/common/test_waiters.py
+++ b/tempest/tests/common/test_waiters.py
@@ -73,6 +73,25 @@
                                     mock.call(volume_id)])
         mock_sleep.assert_called_once_with(1)
 
+    @mock.patch.object(time, 'sleep')
+    def test_wait_for_volume_status_error_extending(self, mock_sleep):
+        # Tests that the wait method raises VolumeExtendErrorException if
+        # the volume status is 'error_extending'.
+        client = mock.Mock(spec=volumes_client.VolumesClient,
+                           resource_type="volume",
+                           build_interval=1)
+        volume1 = {'volume': {'status': 'extending'}}
+        volume2 = {'volume': {'status': 'error_extending'}}
+        mock_show = mock.Mock(side_effect=(volume1, volume2))
+        client.show_volume = mock_show
+        volume_id = '7532b91e-aa0a-4e06-b3e5-20c0c5ee1caa'
+        self.assertRaises(exceptions.VolumeExtendErrorException,
+                          waiters.wait_for_volume_resource_status,
+                          client, volume_id, 'available')
+        mock_show.assert_has_calls([mock.call(volume_id),
+                                    mock.call(volume_id)])
+        mock_sleep.assert_called_once_with(1)
+
 
 class TestInterfaceWaiters(base.TestCase):
 
diff --git a/tempest/tests/lib/services/network/test_qos_client.py b/tempest/tests/lib/services/network/test_qos_client.py
new file mode 100644
index 0000000..b04b847
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_qos_client.py
@@ -0,0 +1,139 @@
+# Copyright (c) 2019 Ericsson
+#
+#    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.
+
+import copy
+
+from tempest.lib.services.network import qos_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestQosClient(base.BaseServiceTest):
+
+    FAKE_QOS_POLICY_ID = "f1011b08-1297-11e9-a1e7-c7e6825a2616"
+
+    FAKE_QOS_POLICY_REQUEST = {
+        'name': 'foo',
+        'shared': True
+    }
+
+    FAKE_QOS_POLICY_RESPONSE = {
+        'policy': {
+            "name": "10Mbit",
+            "description": "This policy limits the ports to 10Mbit max.",
+            "rules": [],
+            "id": FAKE_QOS_POLICY_ID,
+            "is_default": False,
+            "project_id": "8d4c70a21fed4aeba121a1a429ba0d04",
+            "revision_number": 1,
+            "tenant_id": "8d4c70a21fed4aeba121a1a429ba0d04",
+            "created_at": "2018-04-03T21:26:39Z",
+            "updated_at": "2018-04-03T21:26:39Z",
+            "shared": False,
+            "tags": ["tag1,tag2"]
+        }
+    }
+
+    FAKE_QOS_POLICIES = {
+        'policies': [
+            FAKE_QOS_POLICY_RESPONSE['policy']
+        ]
+    }
+
+    def setUp(self):
+        super(TestQosClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.qos_client = qos_client.QosClient(
+            fake_auth, "network", "regionOne")
+
+    def _test_create_qos_policy(self, bytes_body=False):
+        self.check_service_client_function(
+            self.qos_client.create_qos_policy,
+            "tempest.lib.common.rest_client.RestClient.post",
+            self.FAKE_QOS_POLICY_RESPONSE,
+            bytes_body,
+            201,
+            **self.FAKE_QOS_POLICY_REQUEST)
+
+    def _test_list_qos_policies(self, bytes_body=False):
+        self.check_service_client_function(
+            self.qos_client.list_qos_policies,
+            "tempest.lib.common.rest_client.RestClient.get",
+            self.FAKE_QOS_POLICIES,
+            bytes_body,
+            200)
+
+    def _test_show_qos_policy(self, bytes_body=False):
+        self.check_service_client_function(
+            self.qos_client.show_qos_policy,
+            "tempest.lib.common.rest_client.RestClient.get",
+            self.FAKE_QOS_POLICY_RESPONSE,
+            bytes_body,
+            200,
+            qos_policy_id=self.FAKE_QOS_POLICY_ID)
+
+    def _test_update_qos_polcy(self, bytes_body=False):
+        update_kwargs = {
+            "name": "100Mbit",
+            "description": "This policy limits the ports to 100Mbit max.",
+            "shared": True
+        }
+
+        resp_body = {
+            "policy": copy.deepcopy(
+                self.FAKE_QOS_POLICY_RESPONSE['policy']
+            )
+        }
+        resp_body["policy"].update(update_kwargs)
+
+        self.check_service_client_function(
+            self.qos_client.update_qos_policy,
+            "tempest.lib.common.rest_client.RestClient.put",
+            resp_body,
+            bytes_body,
+            200,
+            qos_policy_id=self.FAKE_QOS_POLICY_ID,
+            **update_kwargs)
+
+    def test_create_qos_policy_with_str_body(self):
+        self._test_create_qos_policy()
+
+    def test_create_qos_policy_with_bytes_body(self):
+        self._test_create_qos_policy(bytes_body=True)
+
+    def test_update_qos_policy_with_str_body(self):
+        self._test_update_qos_polcy()
+
+    def test_update_qos_policy_with_bytes_body(self):
+        self._test_update_qos_polcy(bytes_body=True)
+
+    def test_show_qos_policy_with_str_body(self):
+        self._test_show_qos_policy()
+
+    def test_show_qos_policy_with_bytes_body(self):
+        self._test_show_qos_policy(bytes_body=True)
+
+    def test_delete_qos_policy(self):
+        self.check_service_client_function(
+            self.qos_client.delete_qos_policy,
+            "tempest.lib.common.rest_client.RestClient.delete",
+            {},
+            status=204,
+            qos_policy_id=self.FAKE_QOS_POLICY_ID)
+
+    def test_list_qos_policies_with_str_body(self):
+        self._test_list_qos_policies()
+
+    def test_list_qos_policies_with_bytes_body(self):
+        self._test_list_qos_policies(bytes_body=True)
diff --git a/tempest/tests/lib/services/network/test_qos_minimum_bandwidth_rules_client.py b/tempest/tests/lib/services/network/test_qos_minimum_bandwidth_rules_client.py
new file mode 100644
index 0000000..8234dda
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_qos_minimum_bandwidth_rules_client.py
@@ -0,0 +1,137 @@
+# Copyright (c) 2019 Ericsson
+#
+#    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.
+
+import copy
+
+from tempest.lib.services.network import qos_minimum_bandwidth_rules_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestQosMinimumBandwidthRulesClient(base.BaseServiceTest):
+
+    FAKE_QOS_POLICY_ID = "f1011b08-1297-11e9-a1e7-c7e6825a2616"
+    FAKE_MIN_BW_RULE_ID = "e758c89e-1297-11e9-a6cf-cf46a71e6699"
+
+    FAKE_MIN_BW_RULE_REQUEST = {
+        'qos_policy_id': FAKE_QOS_POLICY_ID,
+        'min_kbps': 1000,
+        'direction': 'ingress'
+    }
+
+    FAKE_MIN_BW_RULE_RESPONSE = {
+        'minimum_bandwidth_rule': {
+            'id': FAKE_MIN_BW_RULE_ID,
+            'min_kbps': 10000,
+            'direction': 'egress'
+        }
+    }
+
+    FAKE_MIN_BW_RULES = {
+        'bandwidth_limit_rules': [
+            FAKE_MIN_BW_RULE_RESPONSE['minimum_bandwidth_rule']
+        ]
+    }
+
+    def setUp(self):
+        super(TestQosMinimumBandwidthRulesClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.qos_min_bw_client = qos_minimum_bandwidth_rules_client.\
+            QosMinimumBandwidthRulesClient(fake_auth, "network", "regionOne")
+
+    def _test_create_minimum_bandwidth_rule(self, bytes_body=False):
+        self.check_service_client_function(
+            self.qos_min_bw_client.create_minimum_bandwidth_rule,
+            "tempest.lib.common.rest_client.RestClient.post",
+            self.FAKE_MIN_BW_RULE_RESPONSE,
+            bytes_body,
+            201,
+            **self.FAKE_MIN_BW_RULE_REQUEST
+        )
+
+    def _test_list_minimum_bandwidth_rules(self, bytes_body=False):
+        self.check_service_client_function(
+            self.qos_min_bw_client.list_minimum_bandwidth_rules,
+            "tempest.lib.common.rest_client.RestClient.get",
+            self.FAKE_MIN_BW_RULES,
+            bytes_body,
+            200,
+            qos_policy_id=self.FAKE_QOS_POLICY_ID
+        )
+
+    def _test_show_minimum_bandwidth_rule(self, bytes_body=False):
+        self.check_service_client_function(
+            self.qos_min_bw_client.show_minimum_bandwidth_rule,
+            "tempest.lib.common.rest_client.RestClient.get",
+            self.FAKE_MIN_BW_RULE_RESPONSE,
+            bytes_body,
+            200,
+            qos_policy_id=self.FAKE_QOS_POLICY_ID,
+            rule_id=self.FAKE_MIN_BW_RULE_ID
+        )
+
+    def _test_update_qos_polcy(self, bytes_body=False):
+        update_kwargs = {
+            "min_kbps": "20000"
+        }
+
+        resp_body = {
+            "minimum_bandwidth_rule": copy.deepcopy(
+                self.FAKE_MIN_BW_RULE_RESPONSE['minimum_bandwidth_rule']
+            )
+        }
+        resp_body["minimum_bandwidth_rule"].update(update_kwargs)
+
+        self.check_service_client_function(
+            self.qos_min_bw_client.update_minimum_bandwidth_rule,
+            "tempest.lib.common.rest_client.RestClient.put",
+            resp_body,
+            bytes_body,
+            200,
+            qos_policy_id=self.FAKE_QOS_POLICY_ID,
+            rule_id=self.FAKE_MIN_BW_RULE_ID,
+            **update_kwargs)
+
+    def test_create_minimum_bandwidth_rule_with_str_body(self):
+        self._test_create_minimum_bandwidth_rule()
+
+    def test_create_minimum_bandwidth_rule_with_bytes_body(self):
+        self._test_create_minimum_bandwidth_rule(bytes_body=True)
+
+    def test_update_minimum_bandwidth_rule_with_str_body(self):
+        self._test_update_qos_polcy()
+
+    def test_update_minimum_bandwidth_rule_with_bytes_body(self):
+        self._test_update_qos_polcy(bytes_body=True)
+
+    def test_show_minimum_bandwidth_rule_with_str_body(self):
+        self._test_show_minimum_bandwidth_rule()
+
+    def test_show_minimum_bandwidth_rule_with_bytes_body(self):
+        self._test_show_minimum_bandwidth_rule(bytes_body=True)
+
+    def test_delete_minimum_bandwidth_rule(self):
+        self.check_service_client_function(
+            self.qos_min_bw_client.delete_minimum_bandwidth_rule,
+            "tempest.lib.common.rest_client.RestClient.delete",
+            {},
+            status=204,
+            qos_policy_id=self.FAKE_QOS_POLICY_ID,
+            rule_id=self.FAKE_MIN_BW_RULE_ID)
+
+    def test_list_minimum_bandwidth_rule_with_str_body(self):
+        self._test_list_minimum_bandwidth_rules()
+
+    def test_list_minimum_bandwidth_rule_with_bytes_body(self):
+        self._test_list_minimum_bandwidth_rules(bytes_body=True)
diff --git a/tools/tempest-plugin-sanity.sh b/tools/tempest-plugin-sanity.sh
index 56ce521..d38687e 100644
--- a/tools/tempest-plugin-sanity.sh
+++ b/tools/tempest-plugin-sanity.sh
@@ -155,8 +155,10 @@
     fi
 done
 
+echo "Passed Plugins: $passed_plugin"
+echo "Failed Plugins: $failed_plugin"
+
 # Check for failed status
 if [[ -n $failed_plugin ]]; then
-    echo "Failed Plugins: $failed_plugin"
     exit 1
 fi
diff --git a/tox.ini b/tox.ini
index 48a2baa..291d899 100644
--- a/tox.ini
+++ b/tox.ini
@@ -62,7 +62,7 @@
 deps = {[tempestenv]deps}
 commands =
     find . -type f -name "*.pyc" -delete
-    tempest run --regex {posargs}
+    tempest run --regex {posargs:''}
 
 [testenv:all-plugin]
 # DEPRECATED
@@ -82,7 +82,7 @@
     echo "WARNING: The all-plugin env is deprecated and will be removed"
     echo "WARNING  Please use the 'all' environment for Tempest plugins."
     find . -type f -name "*.pyc" -delete
-    tempest run --regex {posargs}
+    tempest run --regex {posargs:''}
 
 [testenv:all-site-packages]
 sitepackages = True
@@ -93,7 +93,7 @@
 deps = {[tempestenv]deps}
 commands =
     find . -type f -name "*.pyc" -delete
-    tempest run --regex {posargs}
+    tempest run --regex {posargs:''}
 
 [testenv:full]
 envdir = .tox/tempest