Merge "Add network tags client"
diff --git a/releasenotes/notes/add-manage-snapshot-ref-config-option-67efd04897335b67.yaml b/releasenotes/notes/add-manage-snapshot-ref-config-option-67efd04897335b67.yaml
new file mode 100644
index 0000000..bc7bcc8
--- /dev/null
+++ b/releasenotes/notes/add-manage-snapshot-ref-config-option-67efd04897335b67.yaml
@@ -0,0 +1,7 @@
+---
+features:
+  - |
+    A new config option 'manage_snapshot_ref' is added in the volume section,
+    to specify snapshot ref parameter for different storage backend drivers
+    when managing an existing snapshot. By default it is set to fit the LVM
+    driver.
diff --git a/releasenotes/notes/prevent-error-in-parse-resp-when-nullable-list-9898cd0f22180986.yaml b/releasenotes/notes/prevent-error-in-parse-resp-when-nullable-list-9898cd0f22180986.yaml
new file mode 100644
index 0000000..afb6006
--- /dev/null
+++ b/releasenotes/notes/prevent-error-in-parse-resp-when-nullable-list-9898cd0f22180986.yaml
@@ -0,0 +1,6 @@
+---
+fixes:
+  - |
+    When receiving nullable list as a response body, tempest.lib
+    rest_client module raised an exception without valid json
+    deserialization. A new release fixes this bug.
diff --git a/tempest/api/compute/admin/test_hosts.py b/tempest/api/compute/admin/test_hosts.py
index 5a523b4..0e1e7ed 100644
--- a/tempest/api/compute/admin/test_hosts.py
+++ b/tempest/api/compute/admin/test_hosts.py
@@ -58,7 +58,7 @@
         hosts = self.client.list_hosts()['hosts']
 
         hosts = [host for host in hosts if host['service'] == 'compute']
-        self.assertGreaterEqual(len(hosts), 1)
+        self.assertNotEmpty(hosts)
 
         for host in hosts:
             hostname = host['host_name']
diff --git a/tempest/api/compute/servers/test_server_addresses.py b/tempest/api/compute/servers/test_server_addresses.py
index e5e381a..022ceba 100644
--- a/tempest/api/compute/servers/test_server_addresses.py
+++ b/tempest/api/compute/servers/test_server_addresses.py
@@ -48,7 +48,7 @@
 
         # We do not know the exact network configuration, but an instance
         # should at least have a single public or private address
-        self.assertGreaterEqual(len(addresses), 1)
+        self.assertNotEmpty(addresses)
         for network_addresses in addresses.values():
             self.assertNotEmpty(network_addresses)
             for address in network_addresses:
diff --git a/tempest/api/identity/admin/v2/test_roles_negative.py b/tempest/api/identity/admin/v2/test_roles_negative.py
index 86d06e2..f3b7494 100644
--- a/tempest/api/identity/admin/v2/test_roles_negative.py
+++ b/tempest/api/identity/admin/v2/test_roles_negative.py
@@ -143,7 +143,7 @@
     @decorators.idempotent_id('99b297f6-2b5d-47c7-97a9-8b6bb4f91042')
     def test_assign_user_role_for_non_existent_role(self):
         # Attempt to assign a non existent role to user should fail
-        (user, tenant, role) = self._get_role_params()
+        (user, tenant, _) = self._get_role_params()
         non_existent_role = data_utils.rand_uuid_hex()
         self.assertRaises(lib_exc.NotFound,
                           self.roles_client.create_user_role_on_project,
@@ -153,7 +153,7 @@
     @decorators.idempotent_id('b2285aaa-9e76-4704-93a9-7a8acd0a6c8f')
     def test_assign_user_role_for_non_existent_tenant(self):
         # Attempt to assign a role on a non existent tenant should fail
-        (user, tenant, role) = self._get_role_params()
+        (user, _, role) = self._get_role_params()
         non_existent_tenant = data_utils.rand_uuid_hex()
         self.assertRaises(lib_exc.NotFound,
                           self.roles_client.create_user_role_on_project,
@@ -244,7 +244,7 @@
     @decorators.idempotent_id('682adfb2-fd5f-4b0a-a9ca-322e9bebb907')
     def test_list_user_roles_request_without_token(self):
         # Request to list user's roles without a valid token should fail
-        (user, tenant, role) = self._get_role_params()
+        (user, tenant, _) = self._get_role_params()
         token = self.client.auth_provider.get_token()
         self.client.delete_token(token)
         try:
diff --git a/tempest/api/identity/admin/v3/test_default_project_id.py b/tempest/api/identity/admin/v3/test_default_project_id.py
index ac2faa9..302a0e5 100644
--- a/tempest/api/identity/admin/v3/test_default_project_id.py
+++ b/tempest/api/identity/admin/v3/test_default_project_id.py
@@ -79,7 +79,7 @@
         admin_client = clients.Manager(credentials=creds)
 
         # verify the user's token and see that it is scoped to the project
-        token, auth_data = admin_client.auth_provider.get_auth()
+        token, _ = admin_client.auth_provider.get_auth()
         result = admin_client.identity_v3_client.show_token(token)['token']
         self.assertEqual(result['project']['domain']['id'], dom_id)
         self.assertEqual(result['project']['id'], proj_id)
diff --git a/tempest/api/network/test_security_groups.py b/tempest/api/network/test_security_groups.py
index 0d5e230..a121864 100644
--- a/tempest/api/network/test_security_groups.py
+++ b/tempest/api/network/test_security_groups.py
@@ -83,7 +83,7 @@
     @decorators.attr(type='smoke')
     @decorators.idempotent_id('bfd128e5-3c92-44b6-9d66-7fe29d22c802')
     def test_create_list_update_show_delete_security_group(self):
-        group_create_body, name = self._create_security_group()
+        group_create_body, _ = self._create_security_group()
 
         # List security groups and verify if created group is there in response
         list_body = self.security_groups_client.list_security_groups()
diff --git a/tempest/api/object_storage/base.py b/tempest/api/object_storage/base.py
index 2bac8d3..13614cb 100644
--- a/tempest/api/object_storage/base.py
+++ b/tempest/api/object_storage/base.py
@@ -43,8 +43,7 @@
     for cont in containers:
         try:
             params = {'limit': 9999, 'format': 'json'}
-            resp, objlist = container_client.list_container_contents(
-                cont, params)
+            _, objlist = container_client.list_container_contents(cont, params)
             # delete every object in the container
             for obj in objlist:
                 test_utils.call_and_ignore_notfound_exc(
diff --git a/tempest/api/object_storage/test_account_bulk.py b/tempest/api/object_storage/test_account_bulk.py
index 0a72d75..e765414 100644
--- a/tempest/api/object_storage/test_account_bulk.py
+++ b/tempest/api/object_storage/test_account_bulk.py
@@ -112,7 +112,7 @@
         self._upload_archive(filepath)
 
         data = '%s/%s\n%s' % (container_name, object_name, container_name)
-        resp, body = self.bulk_client.delete_bulk_data(data=data)
+        resp, _ = self.bulk_client.delete_bulk_data(data=data)
 
         # When deleting multiple files using the bulk operation, the response
         # does not contain 'content-length' header. This is the special case,
@@ -138,7 +138,7 @@
 
         data = '%s/%s\n%s' % (container_name, object_name, container_name)
 
-        resp, body = self.bulk_client.delete_bulk_data_with_post(data=data)
+        resp, _ = self.bulk_client.delete_bulk_data_with_post(data=data)
 
         # When deleting multiple files using the bulk operation, the response
         # does not contain 'content-length' header. This is the special case,
diff --git a/tempest/api/object_storage/test_account_services.py b/tempest/api/object_storage/test_account_services.py
index 811e530..9e62046 100644
--- a/tempest/api/object_storage/test_account_services.py
+++ b/tempest/api/object_storage/test_account_services.py
@@ -135,7 +135,7 @@
         not CONF.object_storage_feature_enabled.discoverability,
         'Discoverability function is disabled')
     def test_list_extensions(self):
-        resp, extensions = self.capabilities_client.list_capabilities()
+        resp, _ = self.capabilities_client.list_capabilities()
 
         self.assertThat(resp, custom_matchers.AreAllWellFormatted())
 
@@ -297,7 +297,7 @@
             create_update_metadata=metadata)
         self.assertHeaders(resp, 'Account', 'POST')
 
-        resp, body = self.account_client.list_account_metadata()
+        resp, _ = self.account_client.list_account_metadata()
         self.assertIn('x-account-meta-test-account-meta1', resp)
         self.assertEqual(resp['x-account-meta-test-account-meta1'],
                          metadata['test-account-meta1'])
@@ -352,7 +352,7 @@
         self.account_client.create_update_or_delete_account_metadata(
             create_update_metadata=metadata_1)
         metadata_2 = {'test-account-meta2': 'Meta2'}
-        resp, body = (
+        resp, _ = (
             self.account_client.create_update_or_delete_account_metadata(
                 create_update_metadata=metadata_2,
                 delete_metadata=metadata_1))
diff --git a/tempest/api/object_storage/test_container_acl.py b/tempest/api/object_storage/test_container_acl.py
index d4e5ec5..4b66ebf 100644
--- a/tempest/api/object_storage/test_container_acl.py
+++ b/tempest/api/object_storage/test_container_acl.py
@@ -41,10 +41,10 @@
         tenant_name = self.os_roles_operator_alt.credentials.tenant_name
         username = self.os_roles_operator_alt.credentials.username
         cont_headers = {'X-Container-Read': tenant_name + ':' + username}
-        resp_meta, body = self.os_roles_operator.container_client.\
-            update_container_metadata(
+        resp_meta, _ = (
+            self.os_roles_operator.container_client.update_container_metadata(
                 self.container_name, metadata=cont_headers,
-                metadata_prefix='')
+                metadata_prefix=''))
         self.assertHeaders(resp_meta, 'Container', 'POST')
         # create object
         object_name = data_utils.rand_name(name='Object')
@@ -68,10 +68,10 @@
         tenant_name = self.os_roles_operator_alt.credentials.tenant_name
         username = self.os_roles_operator_alt.credentials.username
         cont_headers = {'X-Container-Write': tenant_name + ':' + username}
-        resp_meta, body = self.os_roles_operator.container_client.\
-            update_container_metadata(self.container_name,
-                                      metadata=cont_headers,
-                                      metadata_prefix='')
+        resp_meta, _ = (
+            self.os_roles_operator.container_client.update_container_metadata(
+                self.container_name, metadata=cont_headers,
+                metadata_prefix=''))
         self.assertHeaders(resp_meta, 'Container', 'POST')
         # set alternative authentication data; cannot simply use the
         # other object client.
diff --git a/tempest/api/object_storage/test_container_acl_negative.py b/tempest/api/object_storage/test_container_acl_negative.py
index 9c9d821..655626c 100644
--- a/tempest/api/object_storage/test_container_acl_negative.py
+++ b/tempest/api/object_storage/test_container_acl_negative.py
@@ -65,8 +65,8 @@
     def test_delete_object_without_using_creds(self):
         # create object
         object_name = data_utils.rand_name(name='Object')
-        resp, _ = self.object_client.create_object(self.container_name,
-                                                   object_name, 'data')
+        self.object_client.create_object(self.container_name, object_name,
+                                         'data')
         # trying to delete object with empty headers
         # X-Auth-Token is not provided
         self.object_client.auth_provider.set_alt_auth_data(
@@ -134,7 +134,7 @@
         # attempt to read object using non-authorized user
         # update X-Container-Read metadata ACL
         cont_headers = {'X-Container-Read': 'badtenant:baduser'}
-        resp_meta, body = self.container_client.update_container_metadata(
+        resp_meta, _ = self.container_client.update_container_metadata(
             self.container_name, metadata=cont_headers,
             metadata_prefix='')
         self.assertHeaders(resp_meta, 'Container', 'POST')
@@ -158,7 +158,7 @@
         # attempt to write object using non-authorized user
         # update X-Container-Write metadata ACL
         cont_headers = {'X-Container-Write': 'badtenant:baduser'}
-        resp_meta, body = self.container_client.update_container_metadata(
+        resp_meta, _ = self.container_client.update_container_metadata(
             self.container_name, metadata=cont_headers,
             metadata_prefix='')
         self.assertHeaders(resp_meta, 'Container', 'POST')
@@ -183,7 +183,7 @@
         cont_headers = {'X-Container-Read':
                         tenant_name + ':' + username,
                         'X-Container-Write': ''}
-        resp_meta, body = self.container_client.update_container_metadata(
+        resp_meta, _ = self.container_client.update_container_metadata(
             self.container_name, metadata=cont_headers,
             metadata_prefix='')
         self.assertHeaders(resp_meta, 'Container', 'POST')
@@ -208,7 +208,7 @@
         cont_headers = {'X-Container-Read':
                         tenant_name + ':' + username,
                         'X-Container-Write': ''}
-        resp_meta, body = self.container_client.update_container_metadata(
+        resp_meta, _ = self.container_client.update_container_metadata(
             self.container_name, metadata=cont_headers,
             metadata_prefix='')
         self.assertHeaders(resp_meta, 'Container', 'POST')
diff --git a/tempest/api/object_storage/test_container_services.py b/tempest/api/object_storage/test_container_services.py
index 2b5692d..76fe8d4 100644
--- a/tempest/api/object_storage/test_container_services.py
+++ b/tempest/api/object_storage/test_container_services.py
@@ -27,7 +27,7 @@
     @decorators.idempotent_id('92139d73-7819-4db1-85f8-3f2f22a8d91f')
     def test_create_container(self):
         container_name = data_utils.rand_name(name='TestContainer')
-        resp, body = self.container_client.create_container(container_name)
+        resp, _ = self.container_client.create_container(container_name)
         self.containers.append(container_name)
         self.assertHeaders(resp, 'Container', 'PUT')
 
diff --git a/tempest/api/object_storage/test_container_staticweb.py b/tempest/api/object_storage/test_container_staticweb.py
index 9e01c26..378061a 100644
--- a/tempest/api/object_storage/test_container_staticweb.py
+++ b/tempest/api/object_storage/test_container_staticweb.py
@@ -124,9 +124,8 @@
 
         # test GET on http://account_url/container_name
         # we should retrieve a listing of objects
-        resp, body = self.account_client.request("GET",
-                                                 self.container_name,
-                                                 headers={})
+        _, body = self.account_client.request("GET", self.container_name,
+                                              headers={})
         self.assertIn(self.object_name, body.decode())
         css = '<link rel="stylesheet" type="text/css" href="listings.css" />'
         self.assertIn(css, body.decode())
diff --git a/tempest/api/object_storage/test_container_sync.py b/tempest/api/object_storage/test_container_sync.py
index 3c11a51..4cb1914 100644
--- a/tempest/api/object_storage/test_container_sync.py
+++ b/tempest/api/object_storage/test_container_sync.py
@@ -90,8 +90,7 @@
             cont_client = [self.clients[c][0] for c in cont]
             obj_client = [self.clients[c][1] for c in cont]
             headers = make_headers(cont[1], cont_client[1])
-            resp, body = \
-                cont_client[0].put(str(cont[0]), body=None, headers=headers)
+            cont_client[0].put(str(cont[0]), body=None, headers=headers)
             # create object in container
             object_name = data_utils.rand_name(name='TestSyncObject')
             data = object_name[::-1].encode()  # Raw data, we need bytes
diff --git a/tempest/api/object_storage/test_object_expiry.py b/tempest/api/object_storage/test_object_expiry.py
index 7768d23..ed1be90 100644
--- a/tempest/api/object_storage/test_object_expiry.py
+++ b/tempest/api/object_storage/test_object_expiry.py
@@ -54,8 +54,8 @@
         # actually expire, so figure out how many secs in the future that is.
         sleepy_time = int(resp['x-delete-at']) - int(time.time())
         sleepy_time = sleepy_time if sleepy_time > 0 else 0
-        resp, body = self.object_client.get_object(self.container_name,
-                                                   self.object_name)
+        resp, _ = self.object_client.get_object(self.container_name,
+                                                self.object_name)
         self.assertHeaders(resp, 'Object', 'GET')
         self.assertIn('x-delete-at', resp)
 
diff --git a/tempest/api/object_storage/test_object_services.py b/tempest/api/object_storage/test_object_services.py
index e9d2de0..b29a77f 100644
--- a/tempest/api/object_storage/test_object_services.py
+++ b/tempest/api/object_storage/test_object_services.py
@@ -48,7 +48,7 @@
         data_segments = [data + str(i) for i in range(segments)]
         # uploading segments
         for i in range(segments):
-            resp, _ = self.object_client.create_object_segments(
+            self.object_client.create_object_segments(
                 self.container_name, object_name, i, data_segments[i])
 
         return object_name, data_segments
@@ -184,7 +184,7 @@
         # create object with transfer_encoding
         object_name = data_utils.rand_name(name='TestObject')
         data = data_utils.random_bytes(1024)
-        status, _, resp_headers = self.object_client.put_object_with_chunk(
+        _, _, resp_headers = self.object_client.put_object_with_chunk(
             container=self.container_name,
             name=object_name,
             contents=data_utils.chunkify(data, 512)
@@ -394,7 +394,7 @@
         # update object metadata with x_object_manifest
 
         # uploading segments
-        object_name, data_segments = self._upload_segments()
+        object_name, _ = self._upload_segments()
         # creating a manifest file
         data_empty = ''
         self.object_client.create_object(self.container_name,
@@ -494,7 +494,7 @@
         # get object metadata with x_object_manifest
 
         # uploading segments
-        object_name, data_segments = self._upload_segments()
+        object_name, _ = self._upload_segments()
         # creating a manifest file
         object_prefix = '%s/%s' % (self.container_name, object_name)
         metadata = {'X-Object-Manifest': object_prefix}
@@ -520,7 +520,7 @@
         self.assertTrue(resp['etag'].startswith('\"'))
         self.assertTrue(resp['etag'].endswith('\"'))
         self.assertTrue(resp['etag'].strip('\"').isalnum())
-        self.assertTrue(re.match("^\d+\.?\d*\Z", resp['x-timestamp']))
+        self.assertTrue(re.match(r"^\d+\.?\d*\Z", resp['x-timestamp']))
         self.assertNotEmpty(resp['content-type'])
         self.assertTrue(re.match("^tx[0-9a-f]{21}-[0-9a-f]{10}.*",
                                  resp['x-trans-id']))
@@ -612,7 +612,7 @@
         self.assertTrue(resp['etag'].startswith('\"'))
         self.assertTrue(resp['etag'].endswith('\"'))
         self.assertTrue(resp['etag'].strip('\"').isalnum())
-        self.assertTrue(re.match("^\d+\.?\d*\Z", resp['x-timestamp']))
+        self.assertTrue(re.match(r"^\d+\.?\d*\Z", resp['x-timestamp']))
         self.assertNotEmpty(resp['content-type'])
         self.assertTrue(re.match("^tx[0-9a-f]{21}-[0-9a-f]{10}.*",
                                  resp['x-trans-id']))
@@ -955,7 +955,7 @@
         local_data = "something different"
         md5 = hashlib.md5(local_data.encode()).hexdigest()
         headers = {'If-None-Match': md5}
-        resp, body = self.object_client.get(url, headers=headers)
+        resp, _ = self.object_client.get(url, headers=headers)
         self.assertHeaders(resp, 'Object', 'GET')
 
 
diff --git a/tempest/api/object_storage/test_object_slo.py b/tempest/api/object_storage/test_object_slo.py
index 085ad13..894e42d 100644
--- a/tempest/api/object_storage/test_object_slo.py
+++ b/tempest/api/object_storage/test_object_slo.py
@@ -127,7 +127,7 @@
         # list static large object metadata using multipart manifest
         object_name = self._create_large_object()
 
-        resp, body = self.object_client.list_object_metadata(
+        resp, _ = self.object_client.list_object_metadata(
             self.container_name,
             object_name)
 
@@ -155,7 +155,7 @@
         object_name = self._create_large_object()
 
         params_del = {'multipart-manifest': 'delete'}
-        resp, body = self.object_client.delete_object(
+        resp, _ = self.object_client.delete_object(
             self.container_name,
             object_name,
             params=params_del)
diff --git a/tempest/api/object_storage/test_object_temp_url.py b/tempest/api/object_storage/test_object_temp_url.py
index 4b506f8..91bc677 100644
--- a/tempest/api/object_storage/test_object_temp_url.py
+++ b/tempest/api/object_storage/test_object_temp_url.py
@@ -128,7 +128,7 @@
         url = self._get_temp_url(self.container_name,
                                  self.object_name, "GET",
                                  expires, key2)
-        resp, body = self.object_client.get(url)
+        _, body = self.object_client.get(url)
         self.assertEqual(body, self.content)
 
     @decorators.idempotent_id('9b08dade-3571-4152-8a4f-a4f2a873a735')
@@ -168,7 +168,7 @@
                                  expires, self.key)
 
         # Testing a HEAD on this Temp URL
-        resp, body = self.object_client.head(url)
+        resp, _ = self.object_client.head(url)
         self.assertHeaders(resp, 'Object', 'HEAD')
 
     @decorators.idempotent_id('9d9cfd90-708b-465d-802c-e4a8090b823d')
diff --git a/tempest/api/object_storage/test_object_temp_url_negative.py b/tempest/api/object_storage/test_object_temp_url_negative.py
index f4d63fd..3edaa86 100644
--- a/tempest/api/object_storage/test_object_temp_url_negative.py
+++ b/tempest/api/object_storage/test_object_temp_url_negative.py
@@ -46,7 +46,7 @@
 
     @classmethod
     def resource_cleanup(cls):
-        resp, _ = cls.account_client.create_update_or_delete_account_metadata(
+        cls.account_client.create_update_or_delete_account_metadata(
             delete_metadata=cls.metadata)
 
         cls.delete_containers()
diff --git a/tempest/api/volume/admin/test_snapshot_manage.py b/tempest/api/volume/admin/test_snapshot_manage.py
index a2d5fb1..6c09042 100644
--- a/tempest/api/volume/admin/test_snapshot_manage.py
+++ b/tempest/api/volume/admin/test_snapshot_manage.py
@@ -13,11 +13,10 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import testtools
-
 from tempest.api.volume import base
 from tempest.common import waiters
 from tempest import config
+from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
 
 CONF = config.CONF
@@ -31,9 +30,18 @@
      managed by Cinder from a storage back end to Cinder
     """
 
+    @classmethod
+    def skip_checks(cls):
+        super(SnapshotManageAdminTest, cls).skip_checks()
+
+        if not CONF.volume_feature_enabled.manage_snapshot:
+            raise cls.skipException("Manage snapshot tests are disabled")
+
+        if len(CONF.volume.manage_snapshot_ref) != 2:
+            raise cls.skipException("Manage snapshot ref is not correctly "
+                                    "configured")
+
     @decorators.idempotent_id('0132f42d-0147-4b45-8501-cc504bbf7810')
-    @testtools.skipUnless(CONF.volume_feature_enabled.manage_snapshot,
-                          "Manage snapshot tests are disabled")
     def test_unmanage_manage_snapshot(self):
         # Create a volume
         volume = self.create_volume()
@@ -47,20 +55,30 @@
         self.admin_snapshots_client.unmanage_snapshot(snapshot['id'])
         self.admin_snapshots_client.wait_for_resource_deletion(snapshot['id'])
 
-        # Fetch snapshot ids
-        snapshot_list = [
-            snap['id'] for snap in
-            self.snapshots_client.list_snapshots()['snapshots']
-        ]
-
-        # Verify snapshot does not exist in snapshot list
-        self.assertNotIn(snapshot['id'], snapshot_list)
+        # Verify the original snapshot does not exist in snapshot list
+        params = {'all_tenants': 1}
+        all_snapshots = self.admin_snapshots_client.list_snapshots(
+            detail=True, params=params)['snapshots']
+        self.assertNotIn(snapshot['id'], [v['id'] for v in all_snapshots])
 
         # Manage the snapshot
-        snapshot_ref = '_snapshot-%s' % snapshot['id']
+        name = data_utils.rand_name(self.__class__.__name__ +
+                                    '-Managed-Snapshot')
+        description = data_utils.rand_name(self.__class__.__name__ +
+                                           '-Managed-Snapshot-Description')
+        metadata = {"manage-snap-meta1": "value1",
+                    "manage-snap-meta2": "value2",
+                    "manage-snap-meta3": "value3"}
+        snapshot_ref = {
+            'volume_id': volume['id'],
+            'ref': {CONF.volume.manage_snapshot_ref[0]:
+                    CONF.volume.manage_snapshot_ref[1] % snapshot['id']},
+            'name': name,
+            'description': description,
+            'metadata': metadata
+        }
         new_snapshot = self.admin_snapshot_manage_client.manage_snapshot(
-            volume_id=volume['id'],
-            ref={'source-name': snapshot_ref})['snapshot']
+            **snapshot_ref)['snapshot']
         self.addCleanup(self.delete_snapshot, new_snapshot['id'],
                         self.admin_snapshots_client)
 
@@ -70,4 +88,9 @@
                                                 'available')
 
         # Verify the managed snapshot has the expected parent volume
-        self.assertEqual(new_snapshot['volume_id'], volume['id'])
+        # and the expected field values.
+        new_snapshot_info = self.admin_snapshots_client.show_snapshot(
+            new_snapshot['id'])['snapshot']
+        self.assertEqual(snapshot['size'], new_snapshot_info['size'])
+        for key in ['volume_id', 'name', 'description', 'metadata']:
+            self.assertEqual(snapshot_ref[key], new_snapshot_info[key])
diff --git a/tempest/api/volume/test_volumes_backup.py b/tempest/api/volume/test_volumes_backup.py
index da4324a..4b4aeec 100644
--- a/tempest/api/volume/test_volumes_backup.py
+++ b/tempest/api/volume/test_volumes_backup.py
@@ -118,9 +118,8 @@
                                     name=backup_name, force=True)
         self.assertEqual(backup_name, backup['name'])
 
-    @testtools.skipUnless(CONF.service_available.glance,
-                          "Glance is not available")
     @decorators.idempotent_id('2a8ba340-dff2-4511-9db7-646f07156b15')
+    @test.services('image')
     def test_bootable_volume_backup_and_restore(self):
         # Create volume from image
         img_uuid = CONF.compute.image_ref
diff --git a/tempest/api/volume/test_volumes_clone.py b/tempest/api/volume/test_volumes_clone.py
index a6bbb0a..4c13375 100644
--- a/tempest/api/volume/test_volumes_clone.py
+++ b/tempest/api/volume/test_volumes_clone.py
@@ -13,11 +13,10 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import testtools
-
 from tempest.api.volume import base
 from tempest import config
 from tempest.lib import decorators
+from tempest import test
 
 
 CONF = config.CONF
@@ -47,9 +46,8 @@
         self.assertEqual(volume['source_volid'], src_vol['id'])
         self.assertEqual(volume['size'], src_size + 1)
 
-    @testtools.skipUnless(CONF.service_available.glance,
-                          "Glance is not available")
     @decorators.idempotent_id('cbbcd7c6-5a6c-481a-97ac-ca55ab715d16')
+    @test.services('image')
     def test_create_from_bootable_volume(self):
         # Create volume from image
         img_uuid = CONF.compute.image_ref
diff --git a/tempest/config.py b/tempest/config.py
index 989d53a..fbe0a1b 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -768,6 +768,12 @@
                      "It contains two elements, the first is ref type "
                      "(like 'source-name', 'source-id', etc), the second is "
                      "volume name template used in storage backend"),
+    cfg.ListOpt('manage_snapshot_ref',
+                default=['source-name', '_snapshot-%s'],
+                help="A reference to existing snapshot for snapshot manage. "
+                     "It contains two elements, the first is ref type "
+                     "(like 'source-name', 'source-id', etc), the second is "
+                     "snapshot name template used in storage backend"),
     cfg.StrOpt('min_microversion',
                default=None,
                help="Lower version of the test target microversion range. "
diff --git a/tempest/lib/common/rest_client.py b/tempest/lib/common/rest_client.py
index d72b4dd..63cf07f 100644
--- a/tempest/lib/common/rest_client.py
+++ b/tempest/lib/common/rest_client.py
@@ -474,7 +474,7 @@
             # Ensure there are not more than one top-level keys
             # NOTE(freerunner): Ensure, that JSON is not nullable to
             # to prevent StopIteration Exception
-            if len(body.keys()) != 1:
+            if not hasattr(body, "keys") or len(body.keys()) != 1:
                 return body
             # Just return the "wrapped" element
             first_key, first_item = six.next(six.iteritems(body))
diff --git a/tempest/tests/lib/test_rest_client.py b/tempest/tests/lib/common/test_rest_client.py
similarity index 99%
rename from tempest/tests/lib/test_rest_client.py
rename to tempest/tests/lib/common/test_rest_client.py
index ace2b80..4c0bb57 100644
--- a/tempest/tests/lib/test_rest_client.py
+++ b/tempest/tests/lib/common/test_rest_client.py
@@ -276,6 +276,11 @@
         body = self.rest_client._parse_resp(json.dumps(self.null_dict))
         self.assertEqual(self.null_dict, body)
 
+    def test_parse_empty_list(self):
+        empty_list = []
+        body = self.rest_client._parse_resp(json.dumps(empty_list))
+        self.assertEqual(empty_list, body)
+
 
 class TestRestClientErrorCheckerJSON(base.TestCase):
     c_type = "application/json"
diff --git a/tempest/tests/lib/services/network/test_security_groups_client.py b/tempest/tests/lib/services/network/test_security_groups_client.py
new file mode 100644
index 0000000..d066378
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_security_groups_client.py
@@ -0,0 +1,157 @@
+# Copyright 2017 AT&T 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 oslo_serialization import jsonutils as json
+
+from tempest.lib.services.network import security_groups_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestSecurityGroupsClient(base.BaseServiceTest):
+
+    FAKE_SEC_GROUP_ID = "85cc3048-abc3-43cc-89b3-377341426ac5"
+
+    FAKE_SECURITY_GROUPS = {
+        "security_groups": [
+            {
+                "description": "default",
+                "id": FAKE_SEC_GROUP_ID,
+                "name": "fake-security-group-name",
+                "security_group_rules": [
+                    {
+                        "direction": "egress",
+                        "ethertype": "IPv4",
+                        "id": "38ce2d8e-e8f1-48bd-83c2-d33cb9f50c3d",
+                        "port_range_max": None,
+                        "port_range_min": None,
+                        "protocol": None,
+                        "remote_group_id": None,
+                        "remote_ip_prefix": None,
+                        "security_group_id": FAKE_SEC_GROUP_ID,
+                        "project_id": "e4f50856753b4dc6afee5fa6b9b6c550",
+                        "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550",
+                        "description": ""
+                    },
+                    {
+                        "direction": "egress",
+                        "ethertype": "IPv6",
+                        "id": "565b9502-12de-4ffd-91e9-68885cff6ae1",
+                        "port_range_max": None,
+                        "port_range_min": None,
+                        "protocol": None,
+                        "remote_group_id": None,
+                        "remote_ip_prefix": None,
+                        "security_group_id": FAKE_SEC_GROUP_ID,
+                        "project_id": "e4f50856753b4dc6afee5fa6b9b6c550",
+                        "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550",
+                        "description": ""
+                    }
+                ],
+                "project_id": "e4f50856753b4dc6afee5fa6b9b6c550",
+                "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550"
+            }
+        ]
+    }
+
+    FAKE_SECURITY_GROUP = {
+        "security_group": copy.deepcopy(
+            FAKE_SECURITY_GROUPS["security_groups"][0])
+    }
+
+    def setUp(self):
+        super(TestSecurityGroupsClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = security_groups_client.SecurityGroupsClient(
+            fake_auth, 'network', 'regionOne')
+
+    def _test_list_security_groups(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_security_groups,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_SECURITY_GROUPS,
+            bytes_body,
+            mock_args='v2.0/security-groups')
+
+    def _test_create_security_group(self, bytes_body=False):
+        kwargs = {'name': 'fake-security-group-name'}
+        self.check_service_client_function(
+            self.client.create_security_group,
+            'tempest.lib.common.rest_client.RestClient.post',
+            self.FAKE_SECURITY_GROUP,
+            bytes_body,
+            status=201,
+            mock_args=['v2.0/security-groups',
+                       json.dumps({"security_group": kwargs})],
+            **kwargs)
+
+    def _test_show_security_group(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_security_group,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_SECURITY_GROUP,
+            bytes_body,
+            security_group_id=self.FAKE_SEC_GROUP_ID,
+            mock_args='v2.0/security-groups/%s' % self.FAKE_SEC_GROUP_ID)
+
+    def _test_update_security_group(self, bytes_body=False):
+        kwargs = {'name': 'updated-security-group-name'}
+        resp_body = copy.deepcopy(self.FAKE_SECURITY_GROUP)
+        resp_body["security_group"]["name"] = 'updated-security-group-name'
+
+        self.check_service_client_function(
+            self.client.update_security_group,
+            'tempest.lib.common.rest_client.RestClient.put',
+            resp_body,
+            bytes_body,
+            security_group_id=self.FAKE_SEC_GROUP_ID,
+            mock_args=['v2.0/security-groups/%s' % self.FAKE_SEC_GROUP_ID,
+                       json.dumps({'security_group': kwargs})],
+            **kwargs)
+
+    def test_list_security_groups_with_str_body(self):
+        self._test_list_security_groups()
+
+    def test_list_security_groups_with_bytes_body(self):
+        self._test_list_security_groups(bytes_body=True)
+
+    def test_create_security_group_with_str_body(self):
+        self._test_create_security_group()
+
+    def test_create_security_group_with_bytes_body(self):
+        self._test_create_security_group(bytes_body=True)
+
+    def test_show_security_group_with_str_body(self):
+        self._test_show_security_group()
+
+    def test_show_security_group_with_bytes_body(self):
+        self._test_show_security_group(bytes_body=True)
+
+    def test_update_security_group_with_str_body(self):
+        self._test_update_security_group()
+
+    def test_update_security_group_with_bytes_body(self):
+        self._test_update_security_group(bytes_body=True)
+
+    def test_delete_security_group(self):
+        self.check_service_client_function(
+            self.client.delete_security_group,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            status=204,
+            security_group_id=self.FAKE_SEC_GROUP_ID,
+            mock_args='v2.0/security-groups/%s' % self.FAKE_SEC_GROUP_ID)
diff --git a/tools/generate-tempest-plugins-list.py b/tools/generate-tempest-plugins-list.py
index 8d26c25..238a976 100644
--- a/tools/generate-tempest-plugins-list.py
+++ b/tools/generate-tempest-plugins-list.py
@@ -26,7 +26,12 @@
 import json
 import re
 
-import requests
+try:
+    # For Python 3.0 and later
+    import urllib
+except ImportError:
+    # Fall back to Python 2's urllib2
+    import urllib2 as urllib
 
 url = 'https://review.openstack.org/projects/'
 
@@ -49,21 +54,23 @@
 
 
 def has_tempest_plugin(proj):
-    if proj.startswith('openstack/deb-'):
-        return False
-    r = requests.get(
-        "https://git.openstack.org/cgit/%s/plain/setup.cfg" % proj)
+    try:
+        r = urllib.urlopen("https://git.openstack.org/cgit/%s/plain/setup.cfg"
+                           % proj)
+    except urllib.HTTPError as err:
+        if err.code == 404:
+            return False
     p = re.compile('^tempest\.test_plugins', re.M)
-    if p.findall(r.text):
+    if p.findall(r.read()):
         return True
     else:
         False
 
-r = requests.get(url)
+r = urllib.urlopen(url)
 # 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.
-projects = sorted(filter(is_in_openstack_namespace, json.loads(r.text[4:])))
+projects = sorted(filter(is_in_openstack_namespace, json.loads(r.read()[4:])))
 
 found_plugins = list(filter(has_tempest_plugin, projects))