Merge "Retry http requests to generate plugins list"
diff --git a/releasenotes/notes/add-show-api-v3-details-api-to-v3-versions-client-4b408427379cabfe.yaml b/releasenotes/notes/add-show-api-v3-details-api-to-v3-versions-client-4b408427379cabfe.yaml
new file mode 100644
index 0000000..50f10fa
--- /dev/null
+++ b/releasenotes/notes/add-show-api-v3-details-api-to-v3-versions-client-4b408427379cabfe.yaml
@@ -0,0 +1,7 @@
+---
+features:
+  - |
+    Add show api version details function to v3
+    versions_client library for cinder.
+
+    * show_version
\ No newline at end of file
diff --git a/tempest/api/compute/images/test_images.py b/tempest/api/compute/images/test_images.py
index c8221c2..7cf26fb 100644
--- a/tempest/api/compute/images/test_images.py
+++ b/tempest/api/compute/images/test_images.py
@@ -47,6 +47,10 @@
     def test_delete_saving_image(self):
         server = self.create_test_server(wait_until='ACTIVE')
         self.addCleanup(self.servers_client.delete_server, server['id'])
+        # wait for server active to avoid conflict when deleting server
+        # in task_state image_snapshot
+        self.addCleanup(waiters.wait_for_server_status, self.servers_client,
+                        server['id'], 'ACTIVE')
         image = self.create_image_from_server(server['id'],
                                               wait_until='SAVING')
         self.client.delete_image(image['id'])
diff --git a/tempest/api/identity/v3/test_tokens.py b/tempest/api/identity/v3/test_tokens.py
index f13aa10..fa1c47f 100644
--- a/tempest/api/identity/v3/test_tokens.py
+++ b/tempest/api/identity/v3/test_tokens.py
@@ -43,7 +43,15 @@
         self.assertEqual(authenticated_token, token_body)
         # test to see if token has been properly authenticated
         self.assertEqual(authenticated_token['user']['id'], user_id)
-        self.assertEqual(authenticated_token['user']['name'], username)
+        # NOTE: resource name that are case-sensitive in keystone
+        # depends on backends such as MySQL or LDAP which are
+        # case-insensitive, case-preserving. Resource name is
+        # returned as it is stored in the backend, not as it is
+        # requested. Verifying the username with both lower-case to
+        # avoid failure on different backends
+        self.assertEqual(
+            authenticated_token['user']['name'].lower(), username.lower())
+
         self.non_admin_client.delete_token(subject_token)
         self.assertRaises(
             lib_exc.NotFound, self.non_admin_client.show_token, subject_token)
@@ -84,10 +92,17 @@
             self.assertIsNotNone(subject_id, 'Expected user ID in token.')
 
         subject_name = resp['user']['name']
+
         if username:
-            self.assertEqual(subject_name, username)
+            # NOTE: resource name that are case-sensitive in keystone
+            # depends on backends such as MySQL or LDAP which are
+            # case-insensitive, case-preserving. Resource name is
+            # returned as it is stored in the backend, not as it is
+            # requested. Verifying the username with both lower-case to
+            # avoid failure on different backends
+            self.assertEqual(subject_name.lower(), username.lower())
         else:
-            # Expect a user name, but don't know what it will be.
+            # Expect a user name, but don't know what it will be
             self.assertIsNotNone(subject_name, 'Expected user name in token.')
 
         self.assertEqual(resp['methods'][0], 'password')
@@ -110,7 +125,15 @@
             subject_token)['token']
         self.assertEqual(resp['x-subject-token'], subject_token)
         self.assertEqual(token_details['user']['id'], user.user_id)
-        self.assertEqual(token_details['user']['name'], user.username)
+        # NOTE: resource name that are case-sensitive in keystone
+        # depends on backends such as MySQL or LDAP which are
+        # case-insensitive, case-preserving. Resource name is
+        # returned as it is stored in the backend, not as it is
+        # requested. Verifying the username with both lower-case to
+        # avoid failure on different backends
+        self.assertEqual(
+            token_details['user']['name'].lower(),
+            user.username.lower())
         # Perform Delete Token
         self.non_admin_client.delete_token(subject_token)
         self.assertRaises(lib_exc.NotFound,
diff --git a/tempest/cmd/cleanup.py b/tempest/cmd/cleanup.py
index f0d7264..645a952 100644
--- a/tempest/cmd/cleanup.py
+++ b/tempest/cmd/cleanup.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-#
 # Copyright 2014 Dell Inc.
 # 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
@@ -72,6 +70,15 @@
     deleted unless the ``--delete-tempest-conf-objects`` flag is used to
     force their deletion.
 
+.. note::
+
+    If during execution of ``tempest cleanup`` NotImplemented exception
+    occurres, ``tempest cleanup`` won't fail on that, it will be logged only.
+    NotImplemented errors are ignored because they are an outcome of some
+    extensions being disabled and ``tempest cleanup`` is not checking their
+    availability as it tries to clean up as much as possible without any
+    complicated logic.
+
 """
 import sys
 import traceback
@@ -85,6 +92,7 @@
 from tempest.common import credentials_factory as credentials
 from tempest.common import identity
 from tempest import config
+from tempest.lib import exceptions
 
 SAVED_STATE_JSON = "saved_state.json"
 DRY_RUN_JSON = "dry_run.json"
@@ -105,7 +113,13 @@
             LOG.exception("Failure during cleanup")
             traceback.print_exc()
             raise
-        if self.GOT_EXCEPTIONS:
+        # ignore NotImplemented errors as those are an outcome of some
+        # extensions being disabled and cleanup is not checking their
+        # availability as it tries to clean up as much as possible without
+        # any complicated logic
+        critical_exceptions = [ex for ex in self.GOT_EXCEPTIONS if
+                               not isinstance(ex, exceptions.NotImplemented)]
+        if critical_exceptions:
             raise Exception(self.GOT_EXCEPTIONS)
 
     def init(self, parsed_args):
diff --git a/tempest/cmd/cleanup_service.py b/tempest/cmd/cleanup_service.py
index e2e1bfb..8b625d0 100644
--- a/tempest/cmd/cleanup_service.py
+++ b/tempest/cmd/cleanup_service.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-
 # Copyright 2015 Dell Inc.
 #
 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -144,7 +142,7 @@
             msg = ("Got NotImplemented error in %s, full exception: %s" %
                    (str(self.__class__), str(exc)))
             LOG.exception(msg)
-            self.got_exceptions.append(msg)
+            self.got_exceptions.append(exc)
 
 
 class SnapshotService(BaseService):
diff --git a/tempest/cmd/list_plugins.py b/tempest/cmd/list_plugins.py
index 86732da..51decc7 100644
--- a/tempest/cmd/list_plugins.py
+++ b/tempest/cmd/list_plugins.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-
 # 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
diff --git a/tempest/lib/api_schema/response/volume/versions.py b/tempest/lib/api_schema/response/volume/versions.py
index 2391a8c..c845f7f 100644
--- a/tempest/lib/api_schema/response/volume/versions.py
+++ b/tempest/lib/api_schema/response/volume/versions.py
@@ -58,3 +58,49 @@
         'required': ['versions'],
     }
 }
+
+volume_api_version_details = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'versions': {
+                'type': 'array',
+                'items': {
+                    'type': 'object',
+                    'properties': {
+                        'status': {'type': 'string'},
+                        'updated': {'type': 'string'},
+                        'id': {'type': 'string'},
+                        'links': {
+                            'type': 'array',
+                            'items': {
+                                'type': 'object',
+                                'properties': {
+                                    'href': {'type': 'string',
+                                             'format': 'uri'},
+                                    'rel': {'type': 'string'},
+                                    'type': {'type': 'string'},
+                                },
+                                'required': ['href', 'rel']
+                            }
+                        },
+                        'min_version': {'type': 'string'},
+                        'version': {'type': 'string'},
+                        'media-types': {
+                            'type': 'array',
+                            'properties': {
+                                'base': {'type': 'string'},
+                                'type': {'type': 'string'}
+                            },
+                            'required': ['base', 'type']
+                        }
+                    },
+                    'required': ['status', 'updated', 'id', 'links',
+                                 'min_version', 'version', 'media-types']
+                }
+            }
+        },
+        'required': ['versions'],
+    }
+}
diff --git a/tempest/lib/services/network/quotas_client.py b/tempest/lib/services/network/quotas_client.py
index c479799..997d201 100644
--- a/tempest/lib/services/network/quotas_client.py
+++ b/tempest/lib/services/network/quotas_client.py
@@ -35,10 +35,22 @@
         return self.delete_resource(uri)
 
     def show_quotas(self, tenant_id, **fields):
+        """Show quota for a project.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/network/v2/index.html#list-quotas-for-a-project
+        """
         uri = '/quotas/%s' % tenant_id
         return self.show_resource(uri, **fields)
 
     def list_quotas(self, **filters):
+        """List quotas for projects with non default quota values.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/network/v2/index.html#list-quotas-for-projects-with-non-default-quota-values
+        """
         uri = '/quotas'
         return self.list_resources(uri, **filters)
 
diff --git a/tempest/lib/services/volume/v3/versions_client.py b/tempest/lib/services/volume/v3/versions_client.py
index 29c3fb0..fc8e92f 100644
--- a/tempest/lib/services/volume/v3/versions_client.py
+++ b/tempest/lib/services/volume/v3/versions_client.py
@@ -12,6 +12,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import os
 import time
 
 from oslo_serialization import jsonutils as json
@@ -45,3 +46,17 @@
         body = json.loads(body)
         self.validate_response(schema.list_versions, resp, body)
         return rest_client.ResponseBody(resp, body)
+
+    def show_version(self, version):
+        """Show API version details
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/block-storage/v3/#show-api-v3-details
+        """
+
+        version_url = os.path.join(self._get_base_version_url(), version)
+        resp, body = self.get(version_url)
+        body = json.loads(body)
+        self.validate_response(schema.volume_api_version_details, resp, body)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/scenario/test_network_advanced_server_ops.py b/tempest/scenario/test_network_advanced_server_ops.py
index 5f6dae8..f03e9de 100644
--- a/tempest/scenario/test_network_advanced_server_ops.py
+++ b/tempest/scenario/test_network_advanced_server_ops.py
@@ -200,7 +200,8 @@
             self.assertEqual(resize_flavor, server['flavor']['id'])
         else:
             flavor = self.flavors_client.show_flavor(resize_flavor)['flavor']
-            for key in ['original_name', 'ram', 'vcpus', 'disk']:
+            self.assertEqual(flavor['name'], server['original_name'])
+            for key in ['ram', 'vcpus', 'disk']:
                 self.assertEqual(flavor[key], server['flavor'][key])
         self._wait_server_status_and_check_network_connectivity(
             server, keypair, floating_ip)
diff --git a/tempest/tests/lib/services/volume/v3/test_group_types_client.py b/tempest/tests/lib/services/volume/v3/test_group_types_client.py
index a333b81..8b853d7 100644
--- a/tempest/tests/lib/services/volume/v3/test_group_types_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_group_types_client.py
@@ -22,10 +22,13 @@
 class TestGroupTypesClient(base.BaseServiceTest):
     FAKE_CREATE_GROUP_TYPE = {
         "group_type": {
-            "name": "group-type-001",
-            "description": "Test group type 1",
-            "group_specs": {},
+            "id": "6685584b-1eac-4da6-b5c3-555430cf68ff",
+            "name": "grp-type-001",
+            "description": "group type 001",
             "is_public": True,
+            "group_specs": {
+                "consistent_group_snapshot_enabled": "<is> False"
+            }
         }
     }
 
@@ -35,7 +38,6 @@
             "name": "group-type-001",
             "description": "Test group type 1",
             "is_public": True,
-            "created_at": "20127-06-20T03:50:07Z",
             "group_specs": {},
         }
     }
@@ -57,24 +59,27 @@
                 "name": "group-type-001",
                 "description": "Test group type 1",
                 "is_public": True,
-                "created_at": "2017-06-20T03:50:07Z",
-                "group_specs": {},
+                "group_specs": {
+                    "consistent_group_snapshot_enabled": "<is> False"
+                }
             },
             {
                 "id": "e479997c-650b-40a4-9dfe-77655818b0d2",
                 "name": "group-type-002",
                 "description": "Test group type 2",
                 "is_public": True,
-                "created_at": "2017-06-19T01:52:47Z",
-                "group_specs": {},
+                "group_specs": {
+                    "consistent_group_snapshot_enabled": "<is> False"
+                }
             },
             {
                 "id": "c5c4769e-213c-40a6-a568-8e797bb691d4",
                 "name": "group-type-003",
                 "description": "Test group type 3",
                 "is_public": True,
-                "created_at": "2017-06-18T06:34:32Z",
-                "group_specs": {},
+                "group_specs": {
+                    "consistent_group_snapshot_enabled": "<is> False"
+                }
             }
         ]
     }
@@ -140,15 +145,12 @@
 
     def _test_update_group_types(self, bytes_body=False):
         resp_body = copy.deepcopy(self.FAKE_INFO_GROUP_TYPE)
-        resp_body['group_type'].pop('created_at')
-
         self.check_service_client_function(
             self.client.update_group_type,
             'tempest.lib.common.rest_client.RestClient.put',
             resp_body,
             bytes_body,
-            group_type_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5",
-            name='updated-group-type-name')
+            group_type_id="3fbbcccf-d058-4502-8844-6feeffdf4cb5")
 
     def _test_create_or_update_group_type_specs(self, bytes_body=False):
         group_specs = self.FAKE_CREATE_GROUP_TYPE_SPECS['group_specs']
diff --git a/tempest/tests/lib/services/volume/v3/test_groups_client.py b/tempest/tests/lib/services/volume/v3/test_groups_client.py
index 918e958..5a5ae88 100644
--- a/tempest/tests/lib/services/volume/v3/test_groups_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_groups_client.py
@@ -20,27 +20,22 @@
 class TestGroupsClient(base.BaseServiceTest):
     FAKE_CREATE_GROUP = {
         "group": {
-            "name": "group-001",
-            "description": "Test group 1",
-            "group_type": "0e58433f-d108-4bf3-a22c-34e6b71ef86b",
-            "volume_types": ["2103099d-7cc3-4e52-a2f1-23a5284416f3"],
-            "availability_zone": "az1",
+            "id": "6f519a48-3183-46cf-a32f-41815f816666",
+            "name": "first_group"
         }
     }
 
     FAKE_CREATE_GROUP_FROM_GROUP_SNAPSHOT = {
-        "create-from-src": {
-            "name": "group-002",
-            "description": "Test group 2",
-            "group_snapshot_id": "79c9afdb-7e46-4d71-9249-1f022886963c",
+        "group": {
+            "id": "6f519a48-3183-46cf-a32f-41815f816668",
+            "name": "first_group"
         }
     }
 
     FAKE_CREATE_GROUP_FROM_GROUP = {
-        "create-from-src": {
-            "name": "group-003",
-            "description": "Test group 3",
-            "source_group_id": "e92f9dc7-0b20-492d-8ab2-3ad8fdac270e",
+        "group": {
+            "id": "6f519a48-3183-46cf-a32f-41815f816667",
+            "name": "other_group"
         }
     }
 
diff --git a/tempest/tests/lib/services/volume/v3/test_versions_client.py b/tempest/tests/lib/services/volume/v3/test_versions_client.py
index 9627b9a..b9abd45 100644
--- a/tempest/tests/lib/services/volume/v3/test_versions_client.py
+++ b/tempest/tests/lib/services/volume/v3/test_versions_client.py
@@ -69,6 +69,27 @@
         ]
     }
 
+    FAKE_VERSION_DETAILS = {
+        "versions": [
+            {
+                "id": "v3.0",
+                "links": [
+                    {"href": "https://docs.openstack.org/",
+                     "type": "text/html", "rel": "describedby"},
+                    {"href": "http://127.0.0.1:44895/v3/", "rel": "self"}
+                ],
+                "media-types": [
+                    {"base": "application/json",
+                     "type": "application/vnd.openstack.volume+json;version=3"}
+                ],
+                "min_version": "3.0",
+                "status": "CURRENT",
+                "updated": "2018-07-17T00:00:00Z",
+                "version": "3.59"
+            }
+        ]
+    }
+
     def setUp(self):
         super(TestVersionsClient, self).setUp()
         fake_auth = fake_auth_provider.FakeAuthProvider()
@@ -89,3 +110,17 @@
 
     def test_list_versions_with_bytes_body(self):
         self._test_list_versions(bytes_body=True)
+
+    def _test_show_version(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_version,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_VERSION_DETAILS,
+            bytes_body,
+            200, version='v3')
+
+    def test_show_version_details_with_str_body(self):
+        self._test_show_version()
+
+    def test_show_version_details_with_bytes_body(self):
+        self._test_show_version(bytes_body=True)
diff --git a/tools/verify-ipv6-only-deployments.sh b/tools/verify-ipv6-only-deployments.sh
index 90807a3..2596395 100755
--- a/tools/verify-ipv6-only-deployments.sh
+++ b/tools/verify-ipv6-only-deployments.sh
@@ -15,10 +15,14 @@
 source $TOP_DIR/openrc admin admin
 
 function verify_devstack_ipv6_setting {
-    local _service_host=$(echo $SERVICE_HOST | tr -d [])
-    local _host_ipv6=$(echo $HOST_IPV6 | tr -d [])
-    local _service_listen_address=$(echo $SERVICE_LISTEN_ADDRESS | tr -d [])
-    local _service_local_host=$(echo $SERVICE_LOCAL_HOST | tr -d [])
+    local _service_host=''
+    _service_host=$(echo $SERVICE_HOST | tr -d [])
+    local _host_ipv6=''
+    _host_ipv6=$(echo $HOST_IPV6 | tr -d [])
+    local _service_listen_address=''
+    _service_listen_address=$(echo $SERVICE_LISTEN_ADDRESS | tr -d [])
+    local _service_local_host=''
+    _service_local_host=$(echo $SERVICE_LOCAL_HOST | tr -d [])
     if [[ "$SERVICE_IP_VERSION" != 6 ]]; then
         echo $SERVICE_IP_VERSION "SERVICE_IP_VERSION is not set to 6 which is must for devstack to deploy services with IPv6 address."
         exit 1
@@ -61,9 +65,11 @@
     local all_ipv6=True
     endpoints=$(openstack endpoint list -f value -c URL)
     for endpoint in ${endpoints}; do
-        local endpoint_address=$(echo "$endpoint" | awk -F/ '{print $3}' | awk -F] '{print $1}')
+        local endpoint_address=''
+        endpoint_address=$(echo "$endpoint" | awk -F/ '{print $3}' | awk -F] '{print $1}')
         endpoint_address=$(echo $endpoint_address | tr -d [])
-        local is_endpoint_ipv6=$(python3 -c 'import oslo_utils.netutils as nutils; print(nutils.is_valid_ipv6("'$endpoint_address'"))')
+        local is_endpoint_ipv6=''
+        is_endpoint_ipv6=$(python3 -c 'import oslo_utils.netutils as nutils; print(nutils.is_valid_ipv6("'$endpoint_address'"))')
         if [[ "$is_endpoint_ipv6" != "True" ]]; then
             all_ipv6=False
             echo $endpoint ": This is not ipv6 endpoint which means corresponding service is not listening on IPv6 address."
diff --git a/tox.ini b/tox.ini
index b6ea143..ca4bb3f 100644
--- a/tox.ini
+++ b/tox.ini
@@ -327,7 +327,7 @@
          -not \( -type d -name .?\* -prune \) \
          -type f                              \
          -name \*.sh                          \
-         -print0 | xargs -0 bashate -v"
+         -print0 | xargs -0 bashate -v -eE005,E042 -i E006"
 
 [testenv:pip-check-reqs]
 # Do not install test-requirements as that will pollute the virtualenv for