Merge "Add assert statements for Volume Pagination test to get passed"
diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst
index 2e5f706..36828e0 100644
--- a/doc/source/configuration.rst
+++ b/doc/source/configuration.rst
@@ -108,12 +108,12 @@
 insufficient for certain use cases because of the admin credentials requirement
 to create the credential sets on demand. To get around that the accounts.yaml
 file was introduced and with that a new internal credential provider to enable
-using the list of credentials instead of creating them on demand. With locking
-test accounts each test class will reserve a set of credentials from the
-accounts.yaml before executing any of its tests so that each class is isolated
-like with dynamic credentials.
+using the list of credentials instead of creating them on demand. With
+pre-provisioned credentials (also known as locking test accounts) each test
+class will reserve a set of credentials from the accounts.yaml before executing
+any of its tests so that each class is isolated like with dynamic credentials.
 
-To enable and use locking test accounts you need do a few things:
+To enable and use pre-provisioned credentials you need do a few things:
 
 #. Create an accounts.yaml file which contains the set of pre-existing
    credentials to use for testing. To make sure you don't have a credentials
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/compute/servers/test_servers.py b/tempest/api/compute/servers/test_servers.py
index 76d65dd..1e3e966 100644
--- a/tempest/api/compute/servers/test_servers.py
+++ b/tempest/api/compute/servers/test_servers.py
@@ -209,7 +209,8 @@
                                        server['id'], 'ACTIVE')
 
         # Check rebuild API response schema
-        self.servers_client.rebuild_server(server['id'], self.image_ref_alt)
+        self.servers_client.rebuild_server(
+            server['id'], CONF.compute.certified_image_ref)
         waiters.wait_for_server_status(self.servers_client,
                                        server['id'], 'ACTIVE')
 
diff --git a/tempest/api/identity/admin/v3/test_list_projects.py b/tempest/api/identity/admin/v3/test_list_projects.py
index 299a618..9022b2d 100644
--- a/tempest/api/identity/admin/v3/test_list_projects.py
+++ b/tempest/api/identity/admin/v3/test_list_projects.py
@@ -94,40 +94,43 @@
     @classmethod
     def resource_setup(cls):
         super(ListProjectsStaticTestJSON, cls).resource_setup()
-        cls.domain_id = CONF.identity.default_domain_id
-        cls.project_ids = list()
-        cls.p1_name = cls.os_primary.credentials.project_name
+        # Fetch an existing project from os_primary
         cls.p1 = cls.projects_client.show_project(
             cls.os_primary.credentials.project_id)['project']
-        cls.project_ids.append(cls.p1['id'])
+        # Create a test project
         p2_name = data_utils.rand_name('project')
+        p2_domain_id = CONF.identity.default_domain_id
         cls.p2 = cls.projects_client.create_project(
-            p2_name, domain_id=cls.domain_id)['project']
+            p2_name, domain_id=p2_domain_id)['project']
         cls.addClassResourceCleanup(cls.projects_client.delete_project,
                                     cls.p2['id'])
-        cls.project_ids.append(cls.p2['id'])
 
     @decorators.idempotent_id('1d830662-22ad-427c-8c3e-4ec854b0af44')
     def test_list_projects(self):
         # List projects
         list_projects = self.projects_client.list_projects()['projects']
 
-        for p in self.project_ids:
-            show_project = self.projects_client.show_project(p)['project']
+        for p in [self.p1, self.p2]:
+            show_project = self.projects_client.show_project(p['id'])[
+                'project']
             self.assertIn(show_project, list_projects)
 
     @decorators.idempotent_id('fa178524-4e6d-4925-907c-7ab9f42c7e26')
     def test_list_projects_with_name(self):
         # List projects with name
         self._list_projects_with_params(
-            [self.p1], [self.p2], {'name': self.p1_name}, 'name')
+            [self.p1], [self.p2], {'name': self.p1['name']}, 'name')
 
     @decorators.idempotent_id('fab13f3c-f6a6-4b9f-829b-d32fd44fdf10')
     def test_list_projects_with_domains(self):
-        # List projects with domain
+        # Verify project list filtered by domain
         key = 'domain_id'
-        params = {key: self.domain_id}
-        # Verify both projects are in the self.domain_id which is the default
-        # domain
-        self._list_projects_with_params(
-            [self.p1, self.p2], [], params, key)
+        for p in [self.p1, self.p2]:
+            params = {key: p[key]}
+            # Verify filter shows both projects in their respective domains
+            self._list_projects_with_params([p], [], params, key)
+        # Verify filter excludes projects that are filtered out
+        if self.p1[key] != self.p2[key]:
+            exclude = [self.p2]
+            params = {key: self.p1[key]}
+            self._list_projects_with_params([self.p1], exclude, params, key)
diff --git a/tempest/api/volume/admin/test_user_messages.py b/tempest/api/volume/admin/test_user_messages.py
index 9907497..8048017 100644
--- a/tempest/api/volume/admin/test_user_messages.py
+++ b/tempest/api/volume/admin/test_user_messages.py
@@ -20,18 +20,6 @@
 
 CONF = config.CONF
 
-MESSAGE_KEYS = [
-    'created_at',
-    'event_id',
-    'guaranteed_until',
-    'id',
-    'message_level',
-    'request_id',
-    'resource_type',
-    'resource_uuid',
-    'user_message',
-    'links']
-
 
 class UserMessagesTest(base.BaseVolumeAdminTest):
     _api_version = 3
@@ -66,18 +54,11 @@
         message_id = self._create_user_message()
         self.addCleanup(self.messages_client.delete_message, message_id)
 
-        # show message
-        message = self.messages_client.show_message(message_id)['message']
-        for key in MESSAGE_KEYS:
-            self.assertIn(key, message.keys(), 'Missing expected key %s' % key)
+        # show message, check response schema
+        self.messages_client.show_message(message_id)
 
-        # list messages
-        messages = self.messages_client.list_messages()['messages']
-        self.assertIsInstance(messages, list)
-        for message in messages:
-            for key in MESSAGE_KEYS:
-                self.assertIn(key, message.keys(),
-                              'Missing expected key %s' % key)
+        # list messages, check response schema
+        self.messages_client.list_messages()
 
     @decorators.idempotent_id('c6eb6901-cdcc-490f-b735-4fe251842aed')
     def test_delete_message(self):
diff --git a/tempest/api/volume/admin/test_volume_retype.py b/tempest/api/volume/admin/test_volume_retype.py
index 9136139..18e0b9b 100644
--- a/tempest/api/volume/admin/test_volume_retype.py
+++ b/tempest/api/volume/admin/test_volume_retype.py
@@ -94,7 +94,7 @@
         super(VolumeRetypeTest, cls).skip_checks()
 
         if not CONF.volume_feature_enabled.multi_backend:
-            raise cls.skipException("Cinder multi-backend feature disabled.")
+            raise cls.skipException("Cinder multi-backend feature disabled")
 
         if len(set(CONF.volume.backend_names)) < 2:
             raise cls.skipException("Requires at least two different "
diff --git a/tempest/lib/api_schema/response/volume/extensions.py b/tempest/lib/api_schema/response/volume/extensions.py
new file mode 100644
index 0000000..8dcb07d
--- /dev/null
+++ b/tempest/lib/api_schema/response/volume/extensions.py
@@ -0,0 +1,43 @@
+# Copyright 2018 ZTE Corporation.  All rights reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+
+list_extensions = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'extensions': {
+                'type': 'array',
+                'items': {
+                    'type': 'object',
+                    'properties': {
+                        'updated': parameter_types.date_time,
+                        'description': {'type': 'string'},
+                        'links': {'type': 'array'},
+                        'namespace': {'type': 'string'},
+                        'alias': {'type': 'string'},
+                        'name': {'type': 'string'}
+                    },
+                    'additionalProperties': False,
+                    'required': ['updated', 'links', 'alias', 'name',
+                                 'description']
+                }
+            }
+        },
+        'additionalProperties': False,
+        'required': ['extensions'],
+    }
+}
diff --git a/tempest/lib/api_schema/response/volume/messages.py b/tempest/lib/api_schema/response/volume/messages.py
new file mode 100644
index 0000000..381f542
--- /dev/null
+++ b/tempest/lib/api_schema/response/volume/messages.py
@@ -0,0 +1,64 @@
+# Copyright 2018 ZTE Corporation.  All rights reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+
+delete_message = {
+    'status_code': [204],
+}
+
+common_show_message = {
+    'type': 'object',
+    'properties': {
+        'request_id': {'type': 'string'},
+        'message_level': {'type': 'string'},
+        'links': parameter_types.links,
+        'event_id': {'type': 'string'},
+        'created_at': parameter_types.date_time,
+        'guaranteed_until': parameter_types.date_time,
+        'resource_uuid': {'type': 'string', 'format': 'uuid'},
+        'id': {'type': 'string', 'format': 'uuid'},
+        'resource_type': {'type': 'string'},
+        'user_message': {'type': 'string'}},
+    'additionalProperties': False,
+    'required': ['request_id', 'message_level', 'event_id', 'created_at',
+                 'id', 'user_message'],
+}
+
+show_message = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'message': common_show_message
+        },
+        'additionalProperties': False,
+        'required': ['message']
+    }
+}
+
+list_messages = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'messages': {
+                'type': 'array',
+                'items': common_show_message
+            },
+        },
+        'additionalProperties': False,
+        'required': ['messages']
+    }
+}
diff --git a/tempest/lib/api_schema/response/volume/services.py b/tempest/lib/api_schema/response/volume/services.py
new file mode 100644
index 0000000..70de878
--- /dev/null
+++ b/tempest/lib/api_schema/response/volume/services.py
@@ -0,0 +1,92 @@
+# Copyright 2018 ZTE Corporation.  All rights reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+
+list_services = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'services': {
+                'type': 'array',
+                'items': {
+                    'type': 'object',
+                    'properties': {
+                        'binary': {'type': 'string'},
+                        'disabled_reason': {'type': ['string', 'null']},
+                        'host': {'type': 'string'},
+                        'state': {'enum': ['up', 'down']},
+                        'status': {'enum': ['enabled', 'disabled']},
+                        'frozen': {'type': 'boolean'},
+                        'updated_at': parameter_types.date_time,
+                        'zone': {'type': 'string'},
+                        # TODO(zhufl): cluster is added in 3.7, we should move
+                        # it to the 3.7 schema file when microversion is
+                        # supported in volume interfaces
+                        'cluster': {'type': 'string'},
+                        'replication_status': {'type': 'string'},
+                        'active_backend_id': {'type': ['string', 'null']},
+                        'backend_state': {'type': 'string'},
+                    },
+                    'additionalProperties': False,
+                    'required': ['binary', 'disabled_reason', 'host', 'state',
+                                 'status', 'updated_at', 'zone']
+                }
+            }
+        },
+        'additionalProperties': False,
+        'required': ['services']
+    }
+}
+
+enable_service = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'disabled': {'type': 'boolean'},
+            'status': {'enum': ['enabled', 'disabled']},
+            'host': {'type': 'string'},
+            'service': {'type': 'string'},
+            'binary': {'type': 'string'},
+            'disabled_reason': {'type': ['string', 'null']}
+        },
+        'additionalProperties': False,
+        'required': ['disabled', 'status', 'host', 'service',
+                     'binary', 'disabled_reason']
+    }
+}
+
+disable_service = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'disabled': {'type': 'boolean'},
+            'status': {'enum': ['enabled', 'disabled']},
+            'host': {'type': 'string'},
+            'service': {'type': 'string'},
+            'binary': {'type': 'string'},
+        },
+        'additionalProperties': False,
+        'required': ['disabled', 'status', 'host', 'service', 'binary']
+    }
+}
+
+disable_log_reason = copy.deepcopy(enable_service)
+
+freeze_host = {'status_code': [200]}
+thaw_host = {'status_code': [200]}
diff --git a/tempest/lib/services/network/metering_label_rules_client.py b/tempest/lib/services/network/metering_label_rules_client.py
index 8ccabee..dd5fd52 100644
--- a/tempest/lib/services/network/metering_label_rules_client.py
+++ b/tempest/lib/services/network/metering_label_rules_client.py
@@ -27,10 +27,17 @@
         return self.create_resource(uri, post_data)
 
     def show_metering_label_rule(self, metering_label_rule_id, **fields):
+        """Show metering label rule.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://docs.openstack.org/api-ref/network/v2/index.html#show-metering-label-rule-details
+        """
         uri = '/metering/metering-label-rules/%s' % metering_label_rule_id
         return self.show_resource(uri, **fields)
 
     def delete_metering_label_rule(self, metering_label_rule_id):
+        """Delete metering label rule."""
         uri = '/metering/metering-label-rules/%s' % metering_label_rule_id
         return self.delete_resource(uri)
 
diff --git a/tempest/lib/services/volume/v3/extensions_client.py b/tempest/lib/services/volume/v3/extensions_client.py
index 45b7a56..f1fe5c9 100644
--- a/tempest/lib/services/volume/v3/extensions_client.py
+++ b/tempest/lib/services/volume/v3/extensions_client.py
@@ -15,6 +15,7 @@
 
 from oslo_serialization import jsonutils as json
 
+from tempest.lib.api_schema.response.volume import extensions as schema
 from tempest.lib.common import rest_client
 
 
@@ -25,5 +26,5 @@
         url = 'extensions'
         resp, body = self.get(url)
         body = json.loads(body)
-        self.expected_success(200, resp.status)
+        self.validate_response(schema.list_extensions, resp, body)
         return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v3/messages_client.py b/tempest/lib/services/volume/v3/messages_client.py
index 47538cd..b770fac 100644
--- a/tempest/lib/services/volume/v3/messages_client.py
+++ b/tempest/lib/services/volume/v3/messages_client.py
@@ -15,6 +15,7 @@
 
 from oslo_serialization import jsonutils as json
 
+from tempest.lib.api_schema.response.volume import messages as schema
 from tempest.lib.common import rest_client
 from tempest.lib import exceptions as lib_exc
 from tempest.lib.services.volume import base_client
@@ -28,7 +29,7 @@
         url = 'messages/%s' % str(message_id)
         resp, body = self.get(url)
         body = json.loads(body)
-        self.expected_success(200, resp.status)
+        self.validate_response(schema.show_message, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def list_messages(self):
@@ -36,14 +37,14 @@
         url = 'messages'
         resp, body = self.get(url)
         body = json.loads(body)
-        self.expected_success(200, resp.status)
+        self.validate_response(schema.list_messages, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def delete_message(self, message_id):
         """Delete a single message."""
         url = 'messages/%s' % str(message_id)
         resp, body = self.delete(url)
-        self.expected_success(204, resp.status)
+        self.validate_response(schema.delete_message, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def is_resource_deleted(self, id):
diff --git a/tempest/lib/services/volume/v3/services_client.py b/tempest/lib/services/volume/v3/services_client.py
index c4a511c..8bc82c9 100644
--- a/tempest/lib/services/volume/v3/services_client.py
+++ b/tempest/lib/services/volume/v3/services_client.py
@@ -16,6 +16,7 @@
 from oslo_serialization import jsonutils as json
 from six.moves.urllib import parse as urllib
 
+from tempest.lib.api_schema.response.volume import services as schema
 from tempest.lib.common import rest_client
 
 
@@ -35,7 +36,7 @@
 
         resp, body = self.get(url)
         body = json.loads(body)
-        self.expected_success(200, resp.status)
+        self.validate_response(schema.list_services, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def enable_service(self, **kwargs):
@@ -48,7 +49,7 @@
         put_body = json.dumps(kwargs)
         resp, body = self.put('os-services/enable', put_body)
         body = json.loads(body)
-        self.expected_success(200, resp.status)
+        self.validate_response(schema.enable_service, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def disable_service(self, **kwargs):
@@ -61,7 +62,7 @@
         put_body = json.dumps(kwargs)
         resp, body = self.put('os-services/disable', put_body)
         body = json.loads(body)
-        self.expected_success(200, resp.status)
+        self.validate_response(schema.disable_service, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def disable_log_reason(self, **kwargs):
@@ -74,7 +75,7 @@
         put_body = json.dumps(kwargs)
         resp, body = self.put('os-services/disable-log-reason', put_body)
         body = json.loads(body)
-        self.expected_success(200, resp.status)
+        self.validate_response(schema.disable_log_reason, resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def freeze_host(self, **kwargs):
@@ -85,8 +86,8 @@
         https://docs.openstack.org/api-ref/block-storage/v3/#freeze-a-cinder-backend-host
         """
         put_body = json.dumps(kwargs)
-        resp, _ = self.put('os-services/freeze', put_body)
-        self.expected_success(200, resp.status)
+        resp, body = self.put('os-services/freeze', put_body)
+        self.validate_response(schema.freeze_host, resp, body)
         return rest_client.ResponseBody(resp)
 
     def thaw_host(self, **kwargs):
@@ -97,6 +98,6 @@
         https://docs.openstack.org/api-ref/block-storage/v3/#thaw-a-cinder-backend-host
         """
         put_body = json.dumps(kwargs)
-        resp, _ = self.put('os-services/thaw', put_body)
-        self.expected_success(200, resp.status)
+        resp, body = self.put('os-services/thaw', put_body)
+        self.validate_response(schema.thaw_host, resp, body)
         return rest_client.ResponseBody(resp)
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/tools/generate-tempest-plugins-list.py b/tools/generate-tempest-plugins-list.py
index f1003a0..d4ffd01 100644
--- a/tools/generate-tempest-plugins-list.py
+++ b/tools/generate-tempest-plugins-list.py
@@ -27,14 +27,8 @@
 import re
 import sys
 
-try:
-    # For Python 3.0 and later
-    from urllib.error import HTTPError
-    import urllib.request as urllib
-except ImportError:
-    # Fall back to Python 2's urllib2
-    import urllib2 as urllib
-    from urllib2 import HTTPError
+import urllib3
+from urllib3.util import retry
 
 # List of projects having tempest plugin stale or unmaintained for a long time
 # (6 months or more)
@@ -56,7 +50,6 @@
     # https://review.opendev.org/#/c/637718/
     'openstack/neutron-vpnaas',  # https://review.opendev.org/#/c/637719/
     'x/valet',  # https://review.opendev.org/#/c/638339/
-    'x/ranger-tempest-plugin',  # It looks this doesn't work correctly yet.
 ]
 
 url = 'https://review.opendev.org/projects/'
@@ -69,23 +62,21 @@
   },
 '''
 
+http = urllib3.PoolManager(cert_reqs='CERT_REQUIRED')
+retries = retry.Retry(status_forcelist=[500], backoff_factor=1.0)
 
-# Rather than returning a 404 for a nonexistent file, cgit delivers a
-# 0-byte response to a GET request.  It also does not provide a
-# Content-Length in a HEAD response, so the way we tell if a file exists
-# is to check the length of the entire GET response body.
+
 def has_tempest_plugin(proj):
     try:
-        r = urllib.urlopen(
-            "https://opendev.org/%s/raw/branch/"
-            "master/setup.cfg" % proj)
-    except HTTPError as err:
-        if err.code == 404:
+        r = http.request('GET', "https://opendev.org/%s/raw/branch/"
+                         "master/setup.cfg" % proj, retries=retries)
+        if r.status == 404:
             return False
+    except urllib3.exceptions.MaxRetryError as err:
         # We should not ignore non 404 errors.
         raise err
     p = re.compile(r'^tempest\.test_plugins', re.M)
-    if p.findall(r.read().decode('utf-8')):
+    if p.findall(r.data.decode('utf-8')):
         return True
     else:
         False
@@ -98,11 +89,11 @@
     # So, this exits here.
     sys.exit()
 
-r = urllib.urlopen(url)
+r = http.request('GET', url, retries=retries)
 # Gerrit prepends 4 garbage octets to the JSON, in order to counter
 # cross-site scripting attacks.  Therefore we must discard it so the
 # json library won't choke.
-content = r.read().decode('utf-8')[4:]
+content = r.data.decode('utf-8')[4:]
 projects = sorted(json.loads(content))
 
 # Retrieve projects having no deployment tool repo (such as deb,
diff --git a/tools/generate-tempest-plugins-list.sh b/tools/generate-tempest-plugins-list.sh
index b6a7c77..961cd09 100755
--- a/tools/generate-tempest-plugins-list.sh
+++ b/tools/generate-tempest-plugins-list.sh
@@ -69,7 +69,7 @@
     i=0
     for plugin in $1; do
         i=$((i+1))
-        giturl="https://opendev.org/openstack/${plugin}"
+        giturl="https://opendev.org/${plugin}"
         printf "%-3s %-${name_col_len}s %s\n" "$i" "${plugin}" "${giturl}"
     done
 
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