Merge "Remove unused variable"
diff --git a/releasenotes/notes/create-server-tags-client-8c0042a77e859af6.yaml b/releasenotes/notes/create-server-tags-client-8c0042a77e859af6.yaml
new file mode 100644
index 0000000..9927971
--- /dev/null
+++ b/releasenotes/notes/create-server-tags-client-8c0042a77e859af6.yaml
@@ -0,0 +1,8 @@
+---
+features:
+  - |
+    Add server tags APIs to the servers_client library.
+    This feature enables the possibility of upating, deleting
+    and checking existence of a tag on a server, as well
+    as updating and deleting all tags on a server.
+
diff --git a/requirements.txt b/requirements.txt
index d9a9ebb..124da7a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -12,7 +12,7 @@
 oslo.config!=3.18.0,>=3.14.0 # Apache-2.0
 oslo.log>=3.11.0 # Apache-2.0
 oslo.serialization>=1.10.0 # Apache-2.0
-oslo.utils>=3.18.0 # Apache-2.0
+oslo.utils>=3.20.0 # Apache-2.0
 six>=1.9.0 # MIT
 fixtures>=3.0.0 # Apache-2.0/BSD
 PyYAML>=3.10.0 # MIT
diff --git a/tempest/api/compute/admin/test_flavors_access.py b/tempest/api/compute/admin/test_flavors_access.py
index a9daba8..5a38acc 100644
--- a/tempest/api/compute/admin/test_flavors_access.py
+++ b/tempest/api/compute/admin/test_flavors_access.py
@@ -37,7 +37,6 @@
 
         # Non admin tenant ID
         cls.tenant_id = cls.flavors_client.tenant_id
-        cls.flavor_name_prefix = 'test_flavor_access_'
         cls.ram = 512
         cls.vcpus = 1
         cls.disk = 10
diff --git a/tempest/api/compute/admin/test_flavors_access_negative.py b/tempest/api/compute/admin/test_flavors_access_negative.py
index 33d5d73..12e4587 100644
--- a/tempest/api/compute/admin/test_flavors_access_negative.py
+++ b/tempest/api/compute/admin/test_flavors_access_negative.py
@@ -39,7 +39,6 @@
         super(FlavorsAccessNegativeTestJSON, cls).resource_setup()
 
         cls.tenant_id = cls.flavors_client.tenant_id
-        cls.flavor_name_prefix = 'test_flavor_access_'
         cls.ram = 512
         cls.vcpus = 1
         cls.disk = 10
diff --git a/tempest/api/compute/admin/test_migrations.py b/tempest/api/compute/admin/test_migrations.py
index aa75348..18655cb 100644
--- a/tempest/api/compute/admin/test_migrations.py
+++ b/tempest/api/compute/admin/test_migrations.py
@@ -30,7 +30,6 @@
     def setup_clients(cls):
         super(MigrationsAdminTest, cls).setup_clients()
         cls.client = cls.os_adm.migrations_client
-        cls.flavors_admin_client = cls.os_adm.flavors_client
 
     @decorators.idempotent_id('75c0b83d-72a0-4cf8-a153-631e83e7d53f')
     def test_list_migrations(self):
@@ -54,8 +53,8 @@
 
     def _flavor_clean_up(self, flavor_id):
         try:
-            self.flavors_admin_client.delete_flavor(flavor_id)
-            self.flavors_admin_client.wait_for_resource_deletion(flavor_id)
+            self.admin_flavors_client.delete_flavor(flavor_id)
+            self.admin_flavors_client.wait_for_resource_deletion(flavor_id)
         except exceptions.NotFound:
             pass
 
@@ -68,9 +67,9 @@
 
         # First we have to create a flavor that we can delete so make a copy
         # of the normal flavor from which we'd create a server.
-        flavor = self.flavors_admin_client.show_flavor(
+        flavor = self.admin_flavors_client.show_flavor(
             self.flavor_ref)['flavor']
-        flavor = self.flavors_admin_client.create_flavor(
+        flavor = self.admin_flavors_client.create_flavor(
             name=data_utils.rand_name('test_resize_flavor_'),
             ram=flavor['ram'],
             disk=flavor['disk'],
diff --git a/tempest/api/compute/admin/test_servers_negative.py b/tempest/api/compute/admin/test_servers_negative.py
index 13a411b..adb49a5 100644
--- a/tempest/api/compute/admin/test_servers_negative.py
+++ b/tempest/api/compute/admin/test_servers_negative.py
@@ -34,7 +34,6 @@
         super(ServersAdminNegativeTestJSON, cls).setup_clients()
         cls.client = cls.os_adm.servers_client
         cls.non_adm_client = cls.servers_client
-        cls.flavors_client = cls.os_adm.flavors_client
         cls.quotas_client = cls.os_adm.quotas_client
 
     @classmethod
diff --git a/tempest/api/compute/servers/test_create_server.py b/tempest/api/compute/servers/test_create_server.py
index 20646d1..fd5e50e 100644
--- a/tempest/api/compute/servers/test_create_server.py
+++ b/tempest/api/compute/servers/test_create_server.py
@@ -236,7 +236,6 @@
     @classmethod
     def setup_clients(cls):
         super(ServersWithSpecificFlavorTestJSON, cls).setup_clients()
-        cls.flavor_client = cls.os_adm.flavors_client
         cls.client = cls.servers_client
 
     @classmethod
diff --git a/tempest/api/compute/servers/test_server_tags.py b/tempest/api/compute/servers/test_server_tags.py
new file mode 100644
index 0000000..20e2cee
--- /dev/null
+++ b/tempest/api/compute/servers/test_server_tags.py
@@ -0,0 +1,108 @@
+# Copyright 2017 AT&T Corp.
+# 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 six
+
+from tempest.api.compute import base
+from tempest.common.utils import data_utils
+from tempest.lib import decorators
+from tempest import test
+
+
+class ServerTagsTestJSON(base.BaseV2ComputeTest):
+
+    min_microversion = '2.26'
+    max_microversion = 'latest'
+
+    @classmethod
+    def skip_checks(cls):
+        super(ServerTagsTestJSON, cls).skip_checks()
+        if not test.is_extension_enabled('os-server-tags', 'compute'):
+            msg = "os-server-tags extension is not enabled."
+            raise cls.skipException(msg)
+
+    @classmethod
+    def setup_clients(cls):
+        super(ServerTagsTestJSON, cls).setup_clients()
+        cls.client = cls.servers_client
+
+    @classmethod
+    def resource_setup(cls):
+        super(ServerTagsTestJSON, cls).resource_setup()
+        cls.server = cls.create_test_server(wait_until='ACTIVE')
+
+    def _update_server_tags(self, server_id, tags):
+        if not isinstance(tags, (list, tuple)):
+            tags = [tags]
+        for tag in tags:
+            self.client.update_tag(server_id, tag)
+        self.addCleanup(self.client.delete_all_tags, server_id)
+
+    @decorators.idempotent_id('8d95abe2-c658-4c42-9a44-c0258500306b')
+    def test_create_delete_tag(self):
+        # Check that no tags exist.
+        fetched_tags = self.client.list_tags(self.server['id'])['tags']
+        self.assertEmpty(fetched_tags)
+
+        # Add server tag to the server.
+        assigned_tag = data_utils.rand_name('tag')
+        self._update_server_tags(self.server['id'], assigned_tag)
+
+        # Check that added tag exists.
+        fetched_tags = self.client.list_tags(self.server['id'])['tags']
+        self.assertEqual([assigned_tag], fetched_tags)
+
+        # Remove assigned tag from server and check that it was removed.
+        self.client.delete_tag(self.server['id'], assigned_tag)
+        fetched_tags = self.client.list_tags(self.server['id'])['tags']
+        self.assertEmpty(fetched_tags)
+
+    @decorators.idempotent_id('a2c1af8c-127d-417d-974b-8115f7e3d831')
+    def test_update_all_tags(self):
+        # Add server tags to the server.
+        tags = [data_utils.rand_name('tag'), data_utils.rand_name('tag')]
+        self._update_server_tags(self.server['id'], tags)
+
+        # Replace tags with new tags and check that they are present.
+        new_tags = [data_utils.rand_name('tag'), data_utils.rand_name('tag')]
+        replaced_tags = self.client.update_all_tags(
+            self.server['id'], new_tags)['tags']
+        six.assertCountEqual(self, new_tags, replaced_tags)
+
+        # List the tags and check that the tags were replaced.
+        fetched_tags = self.client.list_tags(self.server['id'])['tags']
+        six.assertCountEqual(self, new_tags, fetched_tags)
+
+    @decorators.idempotent_id('a63b2a74-e918-4b7c-bcab-10c855f3a57e')
+    def test_delete_all_tags(self):
+        # Add server tags to the server.
+        assigned_tags = [data_utils.rand_name('tag'),
+                         data_utils.rand_name('tag')]
+        self._update_server_tags(self.server['id'], assigned_tags)
+
+        # Delete tags from the server and check that they were deleted.
+        self.client.delete_all_tags(self.server['id'])
+        fetched_tags = self.client.list_tags(self.server['id'])['tags']
+        self.assertEmpty(fetched_tags)
+
+    @decorators.idempotent_id('81279a66-61c3-4759-b830-a2dbe64cbe08')
+    def test_check_tag_existence(self):
+        # Add server tag to the server.
+        assigned_tag = data_utils.rand_name('tag')
+        self._update_server_tags(self.server['id'], assigned_tag)
+
+        # Check that added tag exists. Throws a 404 if not found, else a 204,
+        # which was already checked by the schema validation.
+        self.client.check_tag_existence(self.server['id'], assigned_tag)
diff --git a/tempest/api/identity/admin/v2/test_endpoints.py b/tempest/api/identity/admin/v2/test_endpoints.py
index df55d2f..0ea2eb3 100644
--- a/tempest/api/identity/admin/v2/test_endpoints.py
+++ b/tempest/api/identity/admin/v2/test_endpoints.py
@@ -27,10 +27,10 @@
         s_name = data_utils.rand_name('service')
         s_type = data_utils.rand_name('type')
         s_description = data_utils.rand_name('description')
-        cls.service_data = cls.services_client.create_service(
+        service_data = cls.services_client.create_service(
             name=s_name, type=s_type,
             description=s_description)['OS-KSADM:service']
-        cls.service_id = cls.service_data['id']
+        cls.service_id = service_data['id']
         cls.service_ids.append(cls.service_id)
         # Create endpoints so as to use for LIST and GET test cases
         cls.setup_endpoints = list()
diff --git a/tempest/api/identity/admin/v3/test_endpoints.py b/tempest/api/identity/admin/v3/test_endpoints.py
index 686743b..9a0b3e4 100644
--- a/tempest/api/identity/admin/v3/test_endpoints.py
+++ b/tempest/api/identity/admin/v3/test_endpoints.py
@@ -33,11 +33,10 @@
         s_name = data_utils.rand_name('service')
         s_type = data_utils.rand_name('type')
         s_description = data_utils.rand_name('description')
-        cls.service_data = (
+        service_data = (
             cls.services_client.create_service(name=s_name, type=s_type,
                                                description=s_description))
-        cls.service_data = cls.service_data['service']
-        cls.service_id = cls.service_data['id']
+        cls.service_id = service_data['service']['id']
         cls.service_ids.append(cls.service_id)
         # Create endpoints so as to use for LIST and GET test cases
         cls.setup_endpoints = list()
diff --git a/tempest/api/identity/admin/v3/test_endpoints_negative.py b/tempest/api/identity/admin/v3/test_endpoints_negative.py
index 53c2b1f..8e00193 100644
--- a/tempest/api/identity/admin/v3/test_endpoints_negative.py
+++ b/tempest/api/identity/admin/v3/test_endpoints_negative.py
@@ -35,11 +35,11 @@
         s_name = data_utils.rand_name('service')
         s_type = data_utils.rand_name('type')
         s_description = data_utils.rand_name('description')
-        cls.service_data = (
+        service_data = (
             cls.services_client.create_service(name=s_name, type=s_type,
                                                description=s_description)
             ['service'])
-        cls.service_id = cls.service_data['id']
+        cls.service_id = service_data['id']
         cls.service_ids.append(cls.service_id)
 
     @classmethod
diff --git a/tempest/lib/api_schema/response/compute/v2_26/servers.py b/tempest/lib/api_schema/response/compute/v2_26/servers.py
index bc5d18e..d873402 100644
--- a/tempest/lib/api_schema/response/compute/v2_26/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_26/servers.py
@@ -1,4 +1,5 @@
 # Copyright 2016 IBM Corp.
+# Copyright 2017 AT&T Corp.
 #
 #    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
@@ -45,3 +46,41 @@
 # list response schema wasn't changed for v2.26 so use v2.1
 
 list_servers = copy.deepcopy(servers21.list_servers)
+
+list_tags = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'tags': {
+                'type': 'array',
+                'items': {
+                    'type': 'string'
+                }
+            }
+        },
+        'additionalProperties': False,
+        'required': ['tags']
+    }
+}
+
+update_all_tags = copy.deepcopy(list_tags)
+
+delete_all_tags = {'status_code': [204]}
+
+check_tag_existence = {'status_code': [204]}
+
+update_tag = {
+    'status_code': [201, 204],
+    'response_header': {
+        'type': 'object',
+        'properties': {
+            'location': {
+                'type': 'string'
+            }
+        },
+        'required': ['location']
+    }
+}
+
+delete_tag = {'status_code': [204]}
diff --git a/tempest/lib/common/ssh.py b/tempest/lib/common/ssh.py
index 5e65bee..657c0c1 100644
--- a/tempest/lib/common/ssh.py
+++ b/tempest/lib/common/ssh.py
@@ -111,6 +111,7 @@
             except (EOFError,
                     socket.error, socket.timeout,
                     paramiko.SSHException) as e:
+                ssh.close()
                 if self._is_timed_out(_start_time):
                     LOG.exception("Failed to establish authenticated ssh"
                                   " connection to %s@%s after %d attempts",
diff --git a/tempest/lib/services/compute/servers_client.py b/tempest/lib/services/compute/servers_client.py
index adff244..c167d81 100644
--- a/tempest/lib/services/compute/servers_client.py
+++ b/tempest/lib/services/compute/servers_client.py
@@ -1,5 +1,6 @@
 # Copyright 2012 OpenStack Foundation
 # Copyright 2013 Hewlett-Packard Development Company, L.P.
+# Copyright 2017 AT&T Corp.
 # All Rights Reserved.
 #
 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -732,3 +733,92 @@
         self.validate_response(security_groups_schema.list_security_groups,
                                resp, body)
         return rest_client.ResponseBody(resp, body)
+
+    def list_tags(self, server_id):
+        """Lists all tags for a server.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/compute/#list-tags
+        """
+        url = 'servers/%s/tags' % server_id
+        resp, body = self.get(url)
+        body = json.loads(body)
+        schema = self.get_schema(self.schema_versions_info)
+        self.validate_response(schema.list_tags, resp, body)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_all_tags(self, server_id, tags):
+        """Replaces all tags on specified server with the new set of tags.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/compute/#replace-tags
+
+        :param tags: List of tags to replace current server tags with.
+        """
+        url = 'servers/%s/tags' % server_id
+        put_body = {'tags': tags}
+        resp, body = self.put(url, json.dumps(put_body))
+        body = json.loads(body)
+        schema = self.get_schema(self.schema_versions_info)
+        self.validate_response(schema.update_all_tags, resp, body)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_all_tags(self, server_id):
+        """Deletes all tags from the specified server.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/compute/#delete-all-tags
+        """
+        url = 'servers/%s/tags' % server_id
+        resp, body = self.delete(url)
+        schema = self.get_schema(self.schema_versions_info)
+        self.validate_response(schema.delete_all_tags, resp, body)
+        return rest_client.ResponseBody(resp, body)
+
+    def check_tag_existence(self, server_id, tag):
+        """Checks tag existence on the server.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/compute/#check-tag-existence
+
+        :param tag: Check for existence of tag on specified server.
+        """
+        url = 'servers/%s/tags/%s' % (server_id, tag)
+        resp, body = self.get(url)
+        schema = self.get_schema(self.schema_versions_info)
+        self.validate_response(schema.check_tag_existence, resp, body)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_tag(self, server_id, tag):
+        """Adds a single tag to the server if server has no specified tag.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/compute/#add-a-single-tag
+
+        :param tag: Tag to be added to the specified server.
+        """
+        url = 'servers/%s/tags/%s' % (server_id, tag)
+        resp, body = self.put(url, None)
+        schema = self.get_schema(self.schema_versions_info)
+        self.validate_response(schema.update_tag, resp, body)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_tag(self, server_id, tag):
+        """Deletes a single tag from the specified server.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/compute/#delete-a-single-tag
+
+        :param tag: Tag to be removed from the specified server.
+        """
+        url = 'servers/%s/tags/%s' % (server_id, tag)
+        resp, body = self.delete(url)
+        schema = self.get_schema(self.schema_versions_info)
+        self.validate_response(schema.delete_tag, resp, body)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 1642f16..e5f5f68 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -926,14 +926,15 @@
 
     def _check_remote_connectivity(self, source, dest, should_succeed=True,
                                    nic=None):
-        """check ping server via source ssh connection
+        """assert ping server via source ssh connection
+
+        Note: This is an internal method.  Use check_remote_connectivity
+        instead.
 
         :param source: RemoteClient: an ssh connection from which to ping
         :param dest: and IP to ping against
         :param should_succeed: boolean should ping succeed or not
         :param nic: specific network interface to ping from
-        :returns: boolean -- should_succeed == ping
-        :returns: ping is false if ping failed
         """
         def ping_remote():
             try:
@@ -948,6 +949,25 @@
                                           CONF.validation.ping_timeout,
                                           1)
 
+    def check_remote_connectivity(self, source, dest, should_succeed=True,
+                                  nic=None):
+        """assert ping server via source ssh connection
+
+        :param source: RemoteClient: an ssh connection from which to ping
+        :param dest: and IP to ping against
+        :param should_succeed: boolean should ping succeed or not
+        :param nic: specific network interface to ping from
+        """
+        result = self._check_remote_connectivity(source, dest, should_succeed,
+                                                 nic)
+        source_host = source.ssh_client.host
+        if should_succeed:
+            msg = "Timed out waiting for %s to become reachable from %s" \
+                % (dest, source_host)
+        else:
+            msg = "%s is reachable from %s" % (dest, source_host)
+        self.assertTrue(result, msg)
+
     def _create_security_group(self, security_group_rules_client=None,
                                tenant_id=None,
                                namestart='secgroup-smoke',
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index d8c8d4a..51b59c9 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -347,20 +347,8 @@
             ip_address, private_key=private_key)
 
         for remote_ip in address_list:
-            if should_connect:
-                msg = ("Timed out waiting for %s to become "
-                       "reachable") % remote_ip
-            else:
-                msg = "ip address %s is reachable" % remote_ip
-            try:
-                self.assertTrue(self._check_remote_connectivity
-                                (ssh_source, remote_ip, should_connect),
-                                msg)
-            except Exception:
-                LOG.exception("Unable to access {dest} via ssh to "
-                              "floating-ip {src}".format(dest=remote_ip,
-                                                         src=floating_ip))
-                raise
+            self.check_remote_connectivity(ssh_source, remote_ip,
+                                           should_connect)
 
     @test.attr(type='smoke')
     @decorators.idempotent_id('f323b3ba-82f8-4db7-8ea6-6a895869ec49')
@@ -648,21 +636,21 @@
         self.check_public_network_connectivity(
             should_connect=True, msg="before updating "
             "admin_state_up of instance port to False")
-        self._check_remote_connectivity(ssh_client, dest=server_pip,
-                                        should_succeed=True)
+        self.check_remote_connectivity(ssh_client, dest=server_pip,
+                                       should_succeed=True)
         self.ports_client.update_port(port_id, admin_state_up=False)
         self.check_public_network_connectivity(
             should_connect=False, msg="after updating "
             "admin_state_up of instance port to False",
             should_check_floating_ip_status=False)
-        self._check_remote_connectivity(ssh_client, dest=server_pip,
-                                        should_succeed=False)
+        self.check_remote_connectivity(ssh_client, dest=server_pip,
+                                       should_succeed=False)
         self.ports_client.update_port(port_id, admin_state_up=True)
         self.check_public_network_connectivity(
             should_connect=True, msg="after updating "
             "admin_state_up of instance port to True")
-        self._check_remote_connectivity(ssh_client, dest=server_pip,
-                                        should_succeed=True)
+        self.check_remote_connectivity(ssh_client, dest=server_pip,
+                                       should_succeed=True)
 
     @decorators.idempotent_id('759462e1-8535-46b0-ab3a-33aa45c55aaa')
     @test.services('compute', 'network')
@@ -833,15 +821,15 @@
         spoof_nic = ssh_client.get_nic_name_by_mac(spoof_port["mac_address"])
         peer = self._create_server(self.new_net)
         peer_address = peer['addresses'][self.new_net['name']][0]['addr']
-        self._check_remote_connectivity(ssh_client, dest=peer_address,
-                                        nic=spoof_nic, should_succeed=True)
+        self.check_remote_connectivity(ssh_client, dest=peer_address,
+                                       nic=spoof_nic, should_succeed=True)
         ssh_client.set_mac_address(spoof_nic, spoof_mac)
         new_mac = ssh_client.get_mac_address(nic=spoof_nic)
         self.assertEqual(spoof_mac, new_mac)
-        self._check_remote_connectivity(ssh_client, dest=peer_address,
-                                        nic=spoof_nic, should_succeed=False)
+        self.check_remote_connectivity(ssh_client, dest=peer_address,
+                                       nic=spoof_nic, should_succeed=False)
         self.ports_client.update_port(spoof_port["id"],
                                       port_security_enabled=False,
                                       security_groups=[])
-        self._check_remote_connectivity(ssh_client, dest=peer_address,
-                                        nic=spoof_nic, should_succeed=True)
+        self.check_remote_connectivity(ssh_client, dest=peer_address,
+                                       nic=spoof_nic, should_succeed=True)
diff --git a/tempest/scenario/test_network_v6.py b/tempest/scenario/test_network_v6.py
index 3eae522..fcf395d 100644
--- a/tempest/scenario/test_network_v6.py
+++ b/tempest/scenario/test_network_v6.py
@@ -191,25 +191,18 @@
             self.assertTrue(test_utils.call_until_true(srv2_v6_addr_assigned,
                             CONF.validation.ping_timeout, 1))
 
-        self._check_connectivity(sshv4_1, ips_from_api_2['4'])
-        self._check_connectivity(sshv4_2, ips_from_api_1['4'])
+        self.check_remote_connectivity(sshv4_1, ips_from_api_2['4'])
+        self.check_remote_connectivity(sshv4_2, ips_from_api_1['4'])
 
         for i in range(n_subnets6):
-            self._check_connectivity(sshv4_1,
-                                     ips_from_api_2['6'][i])
-            self._check_connectivity(sshv4_1,
-                                     self.subnets_v6[i]['gateway_ip'])
-            self._check_connectivity(sshv4_2,
-                                     ips_from_api_1['6'][i])
-            self._check_connectivity(sshv4_2,
-                                     self.subnets_v6[i]['gateway_ip'])
-
-    def _check_connectivity(self, source, dest):
-        self.assertTrue(
-            self._check_remote_connectivity(source, dest),
-            "Timed out waiting for %s to become reachable from %s" %
-            (dest, source.ssh_client.host)
-        )
+            self.check_remote_connectivity(sshv4_1,
+                                           ips_from_api_2['6'][i])
+            self.check_remote_connectivity(sshv4_1,
+                                           self.subnets_v6[i]['gateway_ip'])
+            self.check_remote_connectivity(sshv4_2,
+                                           ips_from_api_1['6'][i])
+            self.check_remote_connectivity(sshv4_2,
+                                           self.subnets_v6[i]['gateway_ip'])
 
     @test.attr(type='slow')
     @decorators.idempotent_id('2c92df61-29f0-4eaa-bee3-7c65bef62a43')
diff --git a/tempest/scenario/test_security_groups_basic_ops.py b/tempest/scenario/test_security_groups_basic_ops.py
index a01124d..3d383f7 100644
--- a/tempest/scenario/test_security_groups_basic_ops.py
+++ b/tempest/scenario/test_security_groups_basic_ops.py
@@ -368,20 +368,12 @@
             access_point_ssh, private_key=private_key)
         return access_point_ssh
 
-    def _check_connectivity(self, access_point, ip, should_succeed=True):
-        if should_succeed:
-            msg = "Timed out waiting for %s to become reachable" % ip
-        else:
-            msg = "%s is reachable" % ip
-        self.assertTrue(self._check_remote_connectivity(access_point, ip,
-                                                        should_succeed), msg)
-
     def _test_in_tenant_block(self, tenant):
         access_point_ssh = self._connect_to_access_point(tenant)
         for server in tenant.servers:
-            self._check_connectivity(access_point=access_point_ssh,
-                                     ip=self._get_server_ip(server),
-                                     should_succeed=False)
+            self.check_remote_connectivity(source=access_point_ssh,
+                                           dest=self._get_server_ip(server),
+                                           should_succeed=False)
 
     def _test_in_tenant_allow(self, tenant):
         ruleset = dict(
@@ -396,8 +388,8 @@
         )
         access_point_ssh = self._connect_to_access_point(tenant)
         for server in tenant.servers:
-            self._check_connectivity(access_point=access_point_ssh,
-                                     ip=self._get_server_ip(server))
+            self.check_remote_connectivity(source=access_point_ssh,
+                                           dest=self._get_server_ip(server))
 
     def _test_cross_tenant_block(self, source_tenant, dest_tenant):
         # if public router isn't defined, then dest_tenant access is via
@@ -405,8 +397,8 @@
         access_point_ssh = self._connect_to_access_point(source_tenant)
         ip = self._get_server_ip(dest_tenant.access_point,
                                  floating=self.floating_ip_access)
-        self._check_connectivity(access_point=access_point_ssh, ip=ip,
-                                 should_succeed=False)
+        self.check_remote_connectivity(source=access_point_ssh, dest=ip,
+                                       should_succeed=False)
 
     def _test_cross_tenant_allow(self, source_tenant, dest_tenant):
         """check for each direction:
@@ -427,7 +419,7 @@
         access_point_ssh = self._connect_to_access_point(source_tenant)
         ip = self._get_server_ip(dest_tenant.access_point,
                                  floating=self.floating_ip_access)
-        self._check_connectivity(access_point_ssh, ip)
+        self.check_remote_connectivity(access_point_ssh, ip)
 
         # test that reverse traffic is still blocked
         self._test_cross_tenant_block(dest_tenant, source_tenant)
@@ -444,7 +436,7 @@
         access_point_ssh_2 = self._connect_to_access_point(dest_tenant)
         ip = self._get_server_ip(source_tenant.access_point,
                                  floating=self.floating_ip_access)
-        self._check_connectivity(access_point_ssh_2, ip)
+        self.check_remote_connectivity(access_point_ssh_2, ip)
 
     def _verify_mac_addr(self, tenant):
         """Verify that VM has the same ip, mac as listed in port"""
@@ -537,9 +529,9 @@
         # Check connectivity failure with default security group
         try:
             access_point_ssh = self._connect_to_access_point(new_tenant)
-            self._check_connectivity(access_point=access_point_ssh,
-                                     ip=self._get_server_ip(server),
-                                     should_succeed=False)
+            self.check_remote_connectivity(source=access_point_ssh,
+                                           dest=self._get_server_ip(server),
+                                           should_succeed=False)
             server_id = server['id']
             port_id = self.admin_manager.ports_client.list_ports(
                 device_id=server_id)['ports'][0]['id']
@@ -547,9 +539,9 @@
             # update port with new security group and check connectivity
             self.ports_client.update_port(port_id, security_groups=[
                 new_tenant.security_groups['new_sg']['id']])
-            self._check_connectivity(
-                access_point=access_point_ssh,
-                ip=self._get_server_ip(server))
+            self.check_remote_connectivity(
+                source=access_point_ssh,
+                dest=self._get_server_ip(server))
         except Exception:
             for tenant in self.tenants.values():
                 self._log_console_output(servers=tenant.servers)
@@ -612,16 +604,16 @@
             self.ports_client.update_port(port_id,
                                           port_security_enabled=True,
                                           security_groups=[])
-            self._check_connectivity(access_point=access_point_ssh,
-                                     ip=self._get_server_ip(server),
-                                     should_succeed=False)
+            self.check_remote_connectivity(source=access_point_ssh,
+                                           dest=self._get_server_ip(server),
+                                           should_succeed=False)
 
             self.ports_client.update_port(port_id,
                                           port_security_enabled=False,
                                           security_groups=[])
-            self._check_connectivity(
-                access_point=access_point_ssh,
-                ip=self._get_server_ip(server))
+            self.check_remote_connectivity(
+                source=access_point_ssh,
+                dest=self._get_server_ip(server))
         except Exception:
             for tenant in self.tenants.values():
                 self._log_console_output(servers=tenant.servers)
diff --git a/tempest/tests/lib/services/compute/test_servers_client.py b/tempest/tests/lib/services/compute/test_servers_client.py
index b563ab2..8d391c1 100644
--- a/tempest/tests/lib/services/compute/test_servers_client.py
+++ b/tempest/tests/lib/services/compute/test_servers_client.py
@@ -1,4 +1,5 @@
 # Copyright 2015 IBM Corp.
+# Copyright 2017 AT&T Corp.
 #
 #    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
@@ -14,6 +15,9 @@
 
 import copy
 
+import mock
+
+from tempest.lib.services.compute import base_compute_client
 from tempest.lib.services.compute import servers_client
 from tempest.tests.lib import fake_auth_provider
 from tempest.tests.lib.services import base
@@ -186,6 +190,9 @@
     FAKE_REBUILD_SERVER = copy.deepcopy(FAKE_SERVER_GET)
     FAKE_REBUILD_SERVER['server']['adminPass'] = 'fake-admin-pass'
 
+    FAKE_TAGS = ["foo", "bar"]
+    REPLACE_FAKE_TAGS = ["baz", "qux"]
+
     server_id = FAKE_SERVER_GET['server']['id']
     network_id = 'a6b0875b-6b5d-4a5a-81eb-0c3aa62e5fdb'
 
@@ -194,6 +201,7 @@
         fake_auth = fake_auth_provider.FakeAuthProvider()
         self.client = servers_client.ServersClient(
             fake_auth, 'compute', 'regionOne')
+        self.addCleanup(mock.patch.stopall)
 
     def test_list_servers_with_str_body(self):
         self._test_list_servers()
@@ -1031,3 +1039,113 @@
             {'security_groups': self.FAKE_SECURITY_GROUPS},
             server_id=self.server_id,
             )
+
+    @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+                       new_callable=mock.PropertyMock(return_value='2.26'))
+    def test_list_tags_str_body(self, _):
+        self._test_list_tags()
+
+    @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+                       new_callable=mock.PropertyMock(return_value='2.26'))
+    def test_list_tags_byte_body(self, _):
+        self._test_list_tags(bytes_body=True)
+
+    def _test_list_tags(self, bytes_body=False):
+        expected = {"tags": self.FAKE_TAGS}
+        self.check_service_client_function(
+            self.client.list_tags,
+            'tempest.lib.common.rest_client.RestClient.get',
+            expected,
+            server_id=self.server_id,
+            to_utf=bytes_body)
+
+    @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+                       new_callable=mock.PropertyMock(return_value='2.26'))
+    def test_update_all_tags_str_body(self, _):
+        self._test_update_all_tags()
+
+    @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+                       new_callable=mock.PropertyMock(return_value='2.26'))
+    def test_update_all_tags_byte_body(self, _):
+        self._test_update_all_tags(bytes_body=True)
+
+    def _test_update_all_tags(self, bytes_body=False):
+        expected = {"tags": self.REPLACE_FAKE_TAGS}
+        self.check_service_client_function(
+            self.client.update_all_tags,
+            'tempest.lib.common.rest_client.RestClient.put',
+            expected,
+            server_id=self.server_id,
+            tags=self.REPLACE_FAKE_TAGS,
+            to_utf=bytes_body)
+
+    @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+                       new_callable=mock.PropertyMock(return_value='2.26'))
+    def test_delete_all_tags(self, _):
+        self.check_service_client_function(
+            self.client.delete_all_tags,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            server_id=self.server_id,
+            status=204)
+
+    @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+                       new_callable=mock.PropertyMock(return_value='2.26'))
+    def test_check_tag_existence_str_body(self, _):
+        self._test_check_tag_existence()
+
+    @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+                       new_callable=mock.PropertyMock(return_value='2.26'))
+    def test_check_tag_existence_byte_body(self, _):
+        self._test_check_tag_existence(bytes_body=True)
+
+    def _test_check_tag_existence(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.check_tag_existence,
+            'tempest.lib.common.rest_client.RestClient.get',
+            {},
+            server_id=self.server_id,
+            tag=self.FAKE_TAGS[0],
+            status=204,
+            to_utf=bytes_body)
+
+    @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+                       new_callable=mock.PropertyMock(return_value='2.26'))
+    def test_update_tag_str_body(self, _):
+        self._test_update_tag()
+
+    @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+                       new_callable=mock.PropertyMock(return_value='2.26'))
+    def test_update_tag_byte_body(self, _):
+        self._test_update_tag(bytes_body=True)
+
+    def _test_update_tag(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.update_tag,
+            'tempest.lib.common.rest_client.RestClient.put',
+            {},
+            server_id=self.server_id,
+            tag=self.FAKE_TAGS[0],
+            status=201,
+            headers={'location': 'fake_location'},
+            to_utf=bytes_body)
+
+    @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+                       new_callable=mock.PropertyMock(return_value='2.26'))
+    def test_delete_tag_str_body(self, _):
+        self._test_delete_tag()
+
+    @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+                       new_callable=mock.PropertyMock(return_value='2.26'))
+    def test_delete_tag_byte_body(self, _):
+        self._test_delete_tag(bytes_body=True)
+
+    def _test_delete_tag(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.delete_tag,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            server_id=self.server_id,
+            tag=self.FAKE_TAGS[0],
+            status=204,
+            to_utf=bytes_body)
diff --git a/test-requirements.txt b/test-requirements.txt
index f7d63a8..844d32c 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -3,7 +3,7 @@
 # process, which may cause wedges in the gate later.
 hacking<0.13,>=0.12.0 # Apache-2.0
 # needed for doc build
-sphinx!=1.3b1,<1.4,>=1.2.1 # BSD
+sphinx>=1.5.1 # BSD
 oslosphinx>=4.7.0 # Apache-2.0
 reno>=1.8.0 # Apache-2.0
 mock>=2.0 # BSD