Merge "trivial: Fix typos in the test_removal documentation page"
diff --git a/.zuul.yaml b/.zuul.yaml
index 8ab3028..fd3aa2a 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -144,8 +144,11 @@
 
 # TODO(gmann): needs to migrate this to zuulv3
 - job:
-    name: tempest-scenario-multinode-lvm-multibackend
+    name: tempest-scenario-all
     parent: legacy-dsvm-base-multinode
+    description: |
+      This job will run all scenario tests including slow tests
+      with lvm multibackend setup. This job will not run any API tests.
     run: playbooks/tempest-scenario-multinode-lvm-multibackend/run.yaml
     post-run: playbooks/tempest-scenario-multinode-lvm-multibackend/post.yaml
     timeout: 10800
@@ -336,8 +339,7 @@
               - ^tempest/hacking/.*$
               - ^tempest/tests/.*$
         - tempest-tox-plugin-sanity-check
-        - tempest-scenario-multinode-lvm-multibackend:
-            voting: false
+        - tempest-scenario-all:
             irrelevant-files:
               - ^(test-|)requirements.txt$
               - ^.*\.rst$
@@ -347,6 +349,7 @@
               - ^setup.cfg$
               - ^tempest/hacking/.*$
               - ^tempest/tests/.*$
+              - ^tempest/api/.*$
         - nova-cells-v1:
             irrelevant-files:
               - ^(test-|)requirements.txt$
@@ -357,12 +360,21 @@
               - ^setup.cfg$
               - ^tempest/hacking/.*$
               - ^tempest/tests/.*$
+        - nova-live-migration:
+            voting: false
+            irrelevant-files:
+              - ^(test-|)requirements.txt$
+              - ^.*\.rst$
+              - ^doc/.*$
+              - ^etc/.*$
+              - ^releasenotes/.*$
+              - ^setup.cfg$
+              - ^tempest/hacking/.*$
+              - ^tempest/tests/.*$
     gate:
       jobs:
         - nova-multiattach
-    experimental:
-      jobs:
-        - nova-live-migration:
+        - tempest-scenario-all:
             irrelevant-files:
               - ^(test-|)requirements.txt$
               - ^.*\.rst$
@@ -372,6 +384,9 @@
               - ^setup.cfg$
               - ^tempest/hacking/.*$
               - ^tempest/tests/.*$
+              - ^tempest/api/.*$
+    experimental:
+      jobs:
         - tempest-cinder-v2-api:
             irrelevant-files:
               - ^(test-|)requirements.txt$
diff --git a/doc/source/microversion_testing.rst b/doc/source/microversion_testing.rst
index ea868ae..6dd00d3 100644
--- a/doc/source/microversion_testing.rst
+++ b/doc/source/microversion_testing.rst
@@ -358,22 +358,30 @@
 
   .. _2.49: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id44
 
+  * `2.53`_
+
+  .. _2.53: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#maximum-in-pike
+
   * `2.54`_
 
-  .. _2.54: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id4
+  .. _2.54: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id49
 
   * `2.55`_
 
-  .. _2.55: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id49
+  .. _2.55: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id50
 
   * `2.57`_
 
-  .. _2.57: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id51
+  .. _2.57: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id52
 
   * `2.60`_
 
   .. _2.60: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#maximum-in-queens
 
+  * `2.61`_
+
+  .. _2.61: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id55
+
   * `2.63`_
 
   .. _2.63: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id57
diff --git a/playbooks/tempest-scenario-multinode-lvm-multibackend/run.yaml b/playbooks/tempest-scenario-multinode-lvm-multibackend/run.yaml
index 03f64f9..57b4074 100644
--- a/playbooks/tempest-scenario-multinode-lvm-multibackend/run.yaml
+++ b/playbooks/tempest-scenario-multinode-lvm-multibackend/run.yaml
@@ -47,8 +47,8 @@
           set -x
           export PYTHONUNBUFFERED=true
           export DEVSTACK_GATE_TEMPEST=1
-          # Run scenario and nova migration tests with concurrency 2
-          export DEVSTACK_GATE_TEMPEST_REGEX='(^tempest\.(scenario|api\.compute\.admin\.test_(live_|)migration))'
+          # Run all scenario tests including slow tests with concurrency 2
+          export DEVSTACK_GATE_TEMPEST_REGEX='(^tempest\.(scenario))'
           export TEMPEST_CONCURRENCY=2
           export DEVSTACK_GATE_NEUTRON=1
           export DEVSTACK_GATE_TLSPROXY=1
diff --git a/releasenotes/notes/tempest-lib-compute-update-service-6019d2dcfe4a1c5d.yaml b/releasenotes/notes/tempest-lib-compute-update-service-6019d2dcfe4a1c5d.yaml
new file mode 100644
index 0000000..d67cdb8
--- /dev/null
+++ b/releasenotes/notes/tempest-lib-compute-update-service-6019d2dcfe4a1c5d.yaml
@@ -0,0 +1,11 @@
+---
+features:
+  - |
+    The ``update_service`` API is added to the ``services_client`` compute
+    library. This API is introduced in microversion 2.53 and supersedes
+    the following APIs:
+
+    * ``PUT /os-services/disable`` (``disable_service``)
+    * ``PUT /os-services/disable-log-reason`` (``disable_log_reason``)
+    * ``PUT /os-services/enable`` (``enable_service``)
+    * ``PUT /os-services/force-down`` (``update_forced_down``)
diff --git a/tempest/api/compute/admin/test_flavors_microversions.py b/tempest/api/compute/admin/test_flavors_microversions.py
index 027af25..9f014e6 100644
--- a/tempest/api/compute/admin/test_flavors_microversions.py
+++ b/tempest/api/compute/admin/test_flavors_microversions.py
@@ -41,3 +41,11 @@
         self.flavors_client.list_flavors(detail=True)['flavors']
         # Checking list API response schema
         self.flavors_client.list_flavors()['flavors']
+
+
+class FlavorsV261TestJSON(FlavorsV255TestJSON):
+    min_microversion = '2.61'
+    max_microversion = 'latest'
+
+    # NOTE(gmann): This class tests the flavors APIs
+    # response schema for the 2.61 microversion.
diff --git a/tempest/api/compute/admin/test_services_negative.py b/tempest/api/compute/admin/test_services_negative.py
index 201670a..993c8ec 100644
--- a/tempest/api/compute/admin/test_services_negative.py
+++ b/tempest/api/compute/admin/test_services_negative.py
@@ -13,12 +13,14 @@
 #    under the License.
 
 from tempest.api.compute import base
+from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
 from tempest.lib import exceptions as lib_exc
 
 
 class ServicesAdminNegativeTestJSON(base.BaseV2ComputeAdminTest):
     """Tests Services API. List and Enable/Disable require admin privileges."""
+    max_microversion = '2.52'
 
     @classmethod
     def setup_clients(cls):
@@ -35,7 +37,8 @@
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('d0884a69-f693-4e79-a9af-232d15643bf7')
     def test_get_service_by_invalid_params(self):
-        # return all services if send the request with invalid parameter
+        # Expect all services to be returned when the request contains invalid
+        # parameters.
         services = self.client.list_services()['services']
         services_xxx = (self.client.list_services(xxx='nova-compute')
                         ['services'])
@@ -58,3 +61,45 @@
         services = self.client.list_services(host='xxx',
                                              binary=binary_name)['services']
         self.assertEmpty(services)
+
+
+class ServicesAdminNegativeV253TestJSON(ServicesAdminNegativeTestJSON):
+    min_microversion = '2.53'
+    max_microversion = 'latest'
+
+    # NOTE(felipemonteiro): This class tests the services APIs response schema
+    # for the 2.53 microversion. Schema testing is done for `list_services`
+    # tests.
+
+    @classmethod
+    def resource_setup(cls):
+        super(ServicesAdminNegativeV253TestJSON, cls).resource_setup()
+        # Nova returns 400 if `binary` is not nova-compute.
+        cls.binary = 'nova-compute'
+        cls.fake_service_id = data_utils.rand_uuid()
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('508671aa-c929-4479-bd10-8680d40dd0a6')
+    def test_enable_service_with_invalid_service_id(self):
+        self.assertRaises(lib_exc.NotFound,
+                          self.client.update_service,
+                          service_id=self.fake_service_id,
+                          status='enabled')
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('a9eeeade-42b3-419f-87aa-c9342aa068cf')
+    def test_disable_service_with_invalid_service_id(self):
+        self.assertRaises(lib_exc.NotFound,
+                          self.client.update_service,
+                          service_id=self.fake_service_id,
+                          status='disabled')
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('f46a9d91-1e85-4b96-8e7a-db7706fa2e9a')
+    def test_disable_log_reason_with_invalid_service_id(self):
+        # disabled_reason requires that status='disabled' be provided.
+        self.assertRaises(lib_exc.NotFound,
+                          self.client.update_service,
+                          service_id=self.fake_service_id,
+                          status='disabled',
+                          disabled_reason='maintenance')
diff --git a/tempest/api/compute/keypairs/test_keypairs_negative.py b/tempest/api/compute/keypairs/test_keypairs_negative.py
index f9050a8..81635ca 100644
--- a/tempest/api/compute/keypairs/test_keypairs_negative.py
+++ b/tempest/api/compute/keypairs/test_keypairs_negative.py
@@ -84,6 +84,6 @@
     @decorators.idempotent_id('45fbe5e0-acb5-49aa-837a-ff8d0719db91')
     def test_create_keypair_invalid_name(self):
         # Keypairs with name being an invalid name should not be created
-        k_name = 'key_/.\@:'
+        k_name = r'key_/.\@:'
         self.assertRaises(lib_exc.BadRequest, self.create_keypair,
                           k_name)
diff --git a/tempest/api/compute/servers/test_list_server_filters.py b/tempest/api/compute/servers/test_list_server_filters.py
index 14aecfd..3dffd01 100644
--- a/tempest/api/compute/servers/test_list_server_filters.py
+++ b/tempest/api/compute/servers/test_list_server_filters.py
@@ -227,7 +227,7 @@
     @decorators.idempotent_id('24a89b0c-0d55-4a28-847f-45075f19b27b')
     def test_list_servers_filtered_by_name_regex(self):
         # list of regex that should match s1, s2 and s3
-        regexes = ['^.*\-instance\-[0-9]+$', '^.*\-instance\-.*$']
+        regexes = [r'^.*\-instance\-[0-9]+$', r'^.*\-instance\-.*$']
         for regex in regexes:
             params = {'name': regex}
             body = self.client.list_servers(**params)
diff --git a/tempest/api/identity/admin/v3/test_tokens.py b/tempest/api/identity/admin/v3/test_tokens.py
index 532f0d7..8ae43d6 100644
--- a/tempest/api/identity/admin/v3/test_tokens.py
+++ b/tempest/api/identity/admin/v3/test_tokens.py
@@ -19,7 +19,6 @@
 from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
-from tempest.lib import exceptions as lib_exc
 
 CONF = config.CONF
 
@@ -28,30 +27,6 @@
 
     credentials = ['primary', 'admin', 'alt']
 
-    @decorators.idempotent_id('0f9f5a5f-d5cd-4a86-8a5b-c5ded151f212')
-    def test_tokens(self):
-        # Valid user's token is authenticated
-        # Create a User
-        u_name = data_utils.rand_name('user')
-        u_desc = '%s-description' % u_name
-        u_password = data_utils.rand_password()
-        user = self.create_test_user(
-            name=u_name, description=u_desc, password=u_password)
-        # Perform Authentication
-        resp = self.token.auth(user_id=user['id'],
-                               password=u_password).response
-        subject_token = resp['x-subject-token']
-        self.client.check_token_existence(subject_token)
-        # Perform GET Token
-        token_details = self.client.show_token(subject_token)['token']
-        self.assertEqual(resp['x-subject-token'], subject_token)
-        self.assertEqual(token_details['user']['id'], user['id'])
-        self.assertEqual(token_details['user']['name'], u_name)
-        # Perform Delete Token
-        self.client.delete_token(subject_token)
-        self.assertRaises(lib_exc.NotFound, self.client.check_token_existence,
-                          subject_token)
-
     @decorators.idempotent_id('565fa210-1da1-4563-999b-f7b5b67cf112')
     def test_rescope_token(self):
         """Rescope a token.
diff --git a/tempest/api/identity/v3/test_tokens.py b/tempest/api/identity/v3/test_tokens.py
index 4c72d82..f13aa10 100644
--- a/tempest/api/identity/v3/test_tokens.py
+++ b/tempest/api/identity/v3/test_tokens.py
@@ -91,3 +91,28 @@
             self.assertIsNotNone(subject_name, 'Expected user name in token.')
 
         self.assertEqual(resp['methods'][0], 'password')
+
+    @decorators.idempotent_id('0f9f5a5f-d5cd-4a86-8a5b-c5ded151f212')
+    def test_token_auth_creation_existence_deletion(self):
+        # Tests basic token auth functionality in a way that is compatible with
+        # pre-provisioned credentials. The default user is used for token
+        # authentication.
+
+        # Valid user's token is authenticated
+        user = self.os_primary.credentials
+        # Perform Authentication
+        resp = self.non_admin_token.auth(
+            user_id=user.user_id, password=user.password).response
+        subject_token = resp['x-subject-token']
+        self.non_admin_client.check_token_existence(subject_token)
+        # Perform GET Token
+        token_details = self.non_admin_client.show_token(
+            subject_token)['token']
+        self.assertEqual(resp['x-subject-token'], subject_token)
+        self.assertEqual(token_details['user']['id'], user.user_id)
+        self.assertEqual(token_details['user']['name'], user.username)
+        # Perform Delete Token
+        self.non_admin_client.delete_token(subject_token)
+        self.assertRaises(lib_exc.NotFound,
+                          self.non_admin_client.check_token_existence,
+                          subject_token)
diff --git a/tempest/api/network/admin/test_negative_quotas.py b/tempest/api/network/admin/test_negative_quotas.py
index 6849653..e79f8c3 100644
--- a/tempest/api/network/admin/test_negative_quotas.py
+++ b/tempest/api/network/admin/test_negative_quotas.py
@@ -59,7 +59,7 @@
         # Try to create a third network while the quota is two
         with self.assertRaisesRegex(
                 lib_exc.Conflict,
-                "Quota exceeded for resources: \['network'\].*"):
+                r"Quota exceeded for resources: \['network'\].*"):
             n3 = self.networks_client.create_network()
             self.addCleanup(self.networks_client.delete_network,
                             n3['network']['id'])
diff --git a/tempest/api/volume/admin/test_user_messages.py b/tempest/api/volume/admin/test_user_messages.py
index 20c3538..9907497 100644
--- a/tempest/api/volume/admin/test_user_messages.py
+++ b/tempest/api/volume/admin/test_user_messages.py
@@ -62,8 +62,16 @@
         return message_id
 
     @decorators.idempotent_id('50f29e6e-f363-42e1-8ad1-f67ae7fd4d5a')
-    def test_list_messages(self):
-        self._create_user_message()
+    def test_list_show_messages(self):
+        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)
+
+        # list messages
         messages = self.messages_client.list_messages()['messages']
         self.assertIsInstance(messages, list)
         for message in messages:
@@ -71,16 +79,6 @@
                 self.assertIn(key, message.keys(),
                               'Missing expected key %s' % key)
 
-    @decorators.idempotent_id('55a4a61e-c7b2-4ba0-a05d-b914bdef3070')
-    def test_show_message(self):
-        message_id = self._create_user_message()
-        self.addCleanup(self.messages_client.delete_message, message_id)
-
-        message = self.messages_client.show_message(message_id)['message']
-
-        for key in MESSAGE_KEYS:
-            self.assertIn(key, message.keys(), 'Missing expected key %s' % key)
-
     @decorators.idempotent_id('c6eb6901-cdcc-490f-b735-4fe251842aed')
     def test_delete_message(self):
         message_id = self._create_user_message()
diff --git a/tempest/cmd/account_generator.py b/tempest/cmd/account_generator.py
index 1c671ec..9be8ee2 100755
--- a/tempest/cmd/account_generator.py
+++ b/tempest/cmd/account_generator.py
@@ -311,5 +311,6 @@
         resources.extend(generate_resources(cred_provider, opts.admin))
     dump_accounts(resources, opts.identity_version, opts.accounts)
 
+
 if __name__ == "__main__":
     main()
diff --git a/tempest/cmd/init.py b/tempest/cmd/init.py
index 84c8631..d84f3a3 100644
--- a/tempest/cmd/init.py
+++ b/tempest/cmd/init.py
@@ -26,7 +26,7 @@
 
 LOG = logging.getLogger(__name__)
 
-STESTR_CONF = """[DEFAULT]
+STESTR_CONF = r"""[DEFAULT]
 test_path=%s
 top_dir=%s
 group_regex=([^\.]*\.)*
diff --git a/tempest/cmd/subunit_describe_calls.py b/tempest/cmd/subunit_describe_calls.py
index a4402fe..8dcf575 100644
--- a/tempest/cmd/subunit_describe_calls.py
+++ b/tempest/cmd/subunit_describe_calls.py
@@ -95,7 +95,7 @@
     ip_re = re.compile(r'(^|[^0-9])[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]'
                        '{1,3}([^0-9]|$)')
     url_re = re.compile(r'.*INFO.*Request \((?P<name>.*)\): (?P<code>[\d]{3}) '
-                        '(?P<verb>\w*) (?P<url>.*) .*')
+                        r'(?P<verb>\w*) (?P<url>.*) .*')
     port_re = re.compile(r'.*:(?P<port>\d+).*')
     path_re = re.compile(r'http[s]?://[^/]*/(?P<path>.*)')
     request_re = re.compile(r'.* Request - Headers: (?P<headers>.*)')
diff --git a/tempest/cmd/verify_tempest_config.py b/tempest/cmd/verify_tempest_config.py
index 15af271..aa333b3 100644
--- a/tempest/cmd/verify_tempest_config.py
+++ b/tempest/cmd/verify_tempest_config.py
@@ -488,5 +488,6 @@
             traceback.print_exc()
             raise
 
+
 if __name__ == "__main__":
     main()
diff --git a/tempest/common/credentials_factory.py b/tempest/common/credentials_factory.py
index 75db155..c6e5dcb 100644
--- a/tempest/common/credentials_factory.py
+++ b/tempest/common/credentials_factory.py
@@ -210,6 +210,7 @@
     except exceptions.InvalidConfiguration:
         return False
 
+
 # === Credentials
 
 # Type of credentials available from configuration
diff --git a/tempest/common/custom_matchers.py b/tempest/common/custom_matchers.py
index ed11b21..c702d88 100644
--- a/tempest/common/custom_matchers.py
+++ b/tempest/common/custom_matchers.py
@@ -225,9 +225,9 @@
             elif key in ('content-type', 'date', 'last-modified',
                          'x-copied-from-last-modified') and not value:
                 return InvalidFormat(key, value)
-            elif key == 'x-timestamp' and not re.match("^\d+\.?\d*\Z", value):
+            elif key == 'x-timestamp' and not re.match(r"^\d+\.?\d*\Z", value):
                 return InvalidFormat(key, value)
-            elif key == 'x-copied-from' and not re.match("\S+/\S+", value):
+            elif key == 'x-copied-from' and not re.match(r"\S+/\S+", value):
                 return InvalidFormat(key, value)
             elif key == 'x-trans-id' and \
                 not re.match("^tx[0-9a-f]{21}-[0-9a-f]{10}.*", value):
diff --git a/tempest/common/utils/__init__.py b/tempest/common/utils/__init__.py
index 225a713..167bf5b 100644
--- a/tempest/common/utils/__init__.py
+++ b/tempest/common/utils/__init__.py
@@ -40,6 +40,7 @@
         self.__dict__[attr] = attr_obj
         return attr_obj
 
+
 data_utils = DataUtils()
 
 
diff --git a/tempest/hacking/checks.py b/tempest/hacking/checks.py
index b6e7f8c..a57a360 100644
--- a/tempest/hacking/checks.py
+++ b/tempest/hacking/checks.py
@@ -287,10 +287,10 @@
     if pep8.noqa(physical_line):
         return
 
-    if not re.match('class .*Test.*\(.*Admin.*\):', logical_line):
+    if not re.match(r'class .*Test.*\(.*Admin.*\):', logical_line):
         return
 
-    if not re.match('.\/tempest\/api\/.*\/admin\/.*', filename):
+    if not re.match(r'.\/tempest\/api\/.*\/admin\/.*', filename):
         msg = 'T115: All admin tests should exist under admin path.'
         yield(0, msg)
 
diff --git a/tempest/lib/api_schema/response/compute/v2_1/flavors.py b/tempest/lib/api_schema/response/compute/v2_1/flavors.py
index af5e67f..43e80cc 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/flavors.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/flavors.py
@@ -82,10 +82,6 @@
     }
 }
 
-unset_flavor_extra_specs = {
-    'status_code': [200]
-}
-
 create_update_get_flavor_details = {
     'status_code': [200],
     'response_body': {
diff --git a/tempest/lib/api_schema/response/compute/v2_1/flavors_extra_specs.py b/tempest/lib/api_schema/response/compute/v2_1/flavors_extra_specs.py
index a438d48..3aa1eda 100644
--- a/tempest/lib/api_schema/response/compute/v2_1/flavors_extra_specs.py
+++ b/tempest/lib/api_schema/response/compute/v2_1/flavors_extra_specs.py
@@ -20,7 +20,7 @@
             'extra_specs': {
                 'type': 'object',
                 'patternProperties': {
-                    '^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'}
+                    r'^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'}
                 }
             }
         },
@@ -29,12 +29,16 @@
     }
 }
 
+unset_flavor_extra_specs = {
+    'status_code': [200]
+}
+
 set_get_flavor_extra_specs_key = {
     'status_code': [200],
     'response_body': {
         'type': 'object',
         'patternProperties': {
-            '^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'}
+            r'^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'}
         }
     }
 }
diff --git a/tempest/lib/api_schema/response/compute/v2_11/services.py b/tempest/lib/api_schema/response/compute/v2_11/services.py
index 18b833b..9ece1f9 100644
--- a/tempest/lib/api_schema/response/compute/v2_11/services.py
+++ b/tempest/lib/api_schema/response/compute/v2_11/services.py
@@ -44,3 +44,10 @@
         'required': ['service']
     }
 }
+
+# **** Schemas unchanged in microversion 2.11 since microversion 2.1 ****
+# Note(felipemonteiro): Below are the unchanged schema in this microversion. We
+# need to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+enable_disable_service = copy.deepcopy(services.enable_disable_service)
+disable_log_reason = copy.deepcopy(services.disable_log_reason)
diff --git a/tempest/lib/api_schema/response/compute/v2_47/servers.py b/tempest/lib/api_schema/response/compute/v2_47/servers.py
index 935be70..5d6d4c3 100644
--- a/tempest/lib/api_schema/response/compute/v2_47/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_47/servers.py
@@ -26,7 +26,7 @@
         'extra_specs': {
             'type': 'object',
             'patternProperties': {
-                '^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'}
+                r'^[a-zA-Z0-9_\-\. :]+$': {'type': 'string'}
             }
         }
     },
diff --git a/tempest/lib/api_schema/response/compute/v2_53/__init__.py b/tempest/lib/api_schema/response/compute/v2_53/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_53/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_53/services.py b/tempest/lib/api_schema/response/compute/v2_53/services.py
new file mode 100644
index 0000000..aa132a9
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_53/services.py
@@ -0,0 +1,70 @@
+# Copyright 2018 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 tempest.lib.api_schema.response.compute.v2_1 import parameter_types
+from tempest.lib.api_schema.response.compute.v2_11 import services \
+    as servicesv211
+
+# ***************** Schemas changed in microversion 2.53 *****************
+
+# NOTE(felipemonteiro): This is schema for microversion 2.53 which includes:
+#
+# * changing the service 'id' to 'string' type only
+# * adding update_service which supersedes enable_service, disable_service,
+#   disable_log_reason, update_forced_down.
+
+list_services = copy.deepcopy(servicesv211.list_services)
+# The ID of the service is a uuid, so v2.1 pattern does not apply.
+list_services['response_body']['properties']['services']['items'][
+    'properties']['id'] = {'type': 'string', 'format': 'uuid'}
+
+update_service = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'service': {
+                'type': 'object',
+                'properties': {
+                    'id': {'type': 'string', 'format': 'uuid'},
+                    'binary': {'type': 'string'},
+                    'disabled_reason': {'type': 'string'},
+                    'host': {'type': 'string'},
+                    'state': {'type': 'string'},
+                    'status': {'type': 'string'},
+                    'updated_at': parameter_types.date_time,
+                    'zone': {'type': 'string'},
+                    'forced_down': {'type': 'boolean'}
+                },
+                'additionalProperties': False,
+                'required': ['id', 'binary', 'disabled_reason', 'host',
+                             'state', 'status', 'updated_at', 'zone',
+                             'forced_down']
+            }
+        },
+        'additionalProperties': False,
+        'required': ['service']
+    }
+}
+
+# **** Schemas unchanged in microversion 2.53 since microversion 2.11 ****
+# Note(felipemonteiro): Below are the unchanged schema in this microversion. We
+# need to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+enable_disable_service = copy.deepcopy(servicesv211.enable_disable_service)
+update_forced_down = copy.deepcopy(servicesv211.update_forced_down)
+disable_log_reason = copy.deepcopy(servicesv211.disable_log_reason)
diff --git a/tempest/lib/api_schema/response/compute/v2_61/__init__.py b/tempest/lib/api_schema/response/compute/v2_61/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_61/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_61/flavors.py b/tempest/lib/api_schema/response/compute/v2_61/flavors.py
new file mode 100644
index 0000000..381fb64
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_61/flavors.py
@@ -0,0 +1,102 @@
+# Copyright 2018 NEC 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
+from tempest.lib.api_schema.response.compute.v2_55 import flavors \
+    as flavorsv255
+
+# ****** Schemas changed in microversion 2.61 *****************
+
+# Note(gmann): This is schema for microversion 2.61 which includes the
+# Flavor extra_specs in the Response body of the following APIs:
+#    - ``PUT /flavors/{flavor_id}``
+#    - ``GET /flavors/detail``
+#    - ``GET /flavors/{flavor_id}``
+#    - ``POST /flavors``
+
+flavor_description = {
+    'type': ['string', 'null'],
+    'minLength': 0, 'maxLength': 65535
+}
+
+flavor_extra_specs = {
+    'type': 'object',
+    'patternProperties': {
+        '^[a-zA-Z0-9-_:. ]{1,255}$': {'type': 'string'}
+    }
+}
+
+common_flavor_info = {
+    'type': 'object',
+    'properties': {
+        'name': {'type': 'string'},
+        'links': parameter_types.links,
+        'ram': {'type': 'integer'},
+        'vcpus': {'type': 'integer'},
+        # 'swap' attributes comes as integer value but if it is empty
+        # it comes as "". So defining type of as string and integer.
+        'swap': {'type': ['integer', 'string']},
+        'disk': {'type': 'integer'},
+        'id': {'type': 'string'},
+        'OS-FLV-DISABLED:disabled': {'type': 'boolean'},
+        'os-flavor-access:is_public': {'type': 'boolean'},
+        'rxtx_factor': {'type': 'number'},
+        'OS-FLV-EXT-DATA:ephemeral': {'type': 'integer'},
+        'description': flavor_description,
+        'extra_specs': flavor_extra_specs
+    },
+    'additionalProperties': False,
+    # 'OS-FLV-DISABLED', 'os-flavor-access', 'rxtx_factor' and
+    # 'OS-FLV-EXT-DATA' are API extensions. so they are not 'required'.
+    'required': ['name', 'links', 'ram', 'vcpus', 'swap', 'disk', 'id',
+                 'description']
+}
+
+list_flavors_details = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'flavors': {
+                'type': 'array',
+                'items': common_flavor_info
+            },
+            # NOTE(gmann): flavors_links attribute is not necessary
+            # to be present always so it is not 'required'.
+            'flavors_links': parameter_types.links
+        },
+        'additionalProperties': False,
+        'required': ['flavors']
+    }
+}
+
+create_update_get_flavor_details = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'flavor': common_flavor_info
+        },
+        'additionalProperties': False,
+        'required': ['flavor']
+    }
+}
+
+# ****** Schemas unchanged in microversion 2.61 since microversion 2.55 ***
+# Note(gmann): Below are the unchanged schema in this microversion. We need
+# to keep this schema in this file to have the generic way to select the
+# right schema based on self.schema_versions_info mapping in service client.
+list_flavors = copy.deepcopy(flavorsv255.list_flavors)
diff --git a/tempest/lib/cli/output_parser.py b/tempest/lib/cli/output_parser.py
index a7d5e49..45d41c7 100644
--- a/tempest/lib/cli/output_parser.py
+++ b/tempest/lib/cli/output_parser.py
@@ -25,7 +25,7 @@
 LOG = logging.getLogger(__name__)
 
 
-delimiter_line = re.compile('^\+\-[\+\-]+\-\+$')
+delimiter_line = re.compile(r'^\+\-[\+\-]+\-\+$')
 
 
 def details_multiple(output_lines, with_label=False):
diff --git a/tempest/lib/cmd/check_uuid.py b/tempest/lib/cmd/check_uuid.py
index d1f0888..82fcd0b 100755
--- a/tempest/lib/cmd/check_uuid.py
+++ b/tempest/lib/cmd/check_uuid.py
@@ -358,5 +358,6 @@
                  "Run 'tox -v -e uuidgen' to automatically fix tests with\n"
                  "missing @decorators.idempotent_id decorators.")
 
+
 if __name__ == '__main__':
     run()
diff --git a/tempest/lib/services/compute/flavors_client.py b/tempest/lib/services/compute/flavors_client.py
index 4923d7e..2fad0a4 100644
--- a/tempest/lib/services/compute/flavors_client.py
+++ b/tempest/lib/services/compute/flavors_client.py
@@ -23,6 +23,8 @@
     as schema_extra_specs
 from tempest.lib.api_schema.response.compute.v2_55 import flavors \
     as schemav255
+from tempest.lib.api_schema.response.compute.v2_61 import flavors \
+    as schemav261
 from tempest.lib.common import rest_client
 from tempest.lib.services.compute import base_compute_client
 
@@ -31,7 +33,8 @@
 
     schema_versions_info = [
         {'min': None, 'max': '2.54', 'schema': schema},
-        {'min': '2.55', 'max': None, 'schema': schemav255}]
+        {'min': '2.55', 'max': '2.60', 'schema': schemav255},
+        {'min': '2.61', 'max': None, 'schema': schemav261}]
 
     def list_flavors(self, detail=False, **params):
         """Lists flavors.
@@ -202,7 +205,8 @@
         """
         resp, body = self.delete('flavors/%s/os-extra_specs/%s' %
                                  (flavor_id, key))
-        self.validate_response(schema.unset_flavor_extra_specs, resp, body)
+        self.validate_response(schema_extra_specs.unset_flavor_extra_specs,
+                               resp, body)
         return rest_client.ResponseBody(resp, body)
 
     def list_flavor_access(self, flavor_id):
diff --git a/tempest/lib/services/compute/services_client.py b/tempest/lib/services/compute/services_client.py
index b046c35..d52de3a 100644
--- a/tempest/lib/services/compute/services_client.py
+++ b/tempest/lib/services/compute/services_client.py
@@ -20,6 +20,8 @@
 from tempest.lib.api_schema.response.compute.v2_1 import services as schema
 from tempest.lib.api_schema.response.compute.v2_11 import services \
     as schemav211
+from tempest.lib.api_schema.response.compute.v2_53 import services \
+    as schemav253
 from tempest.lib.common import rest_client
 from tempest.lib.services.compute import base_compute_client
 
@@ -28,7 +30,8 @@
 
     schema_versions_info = [
         {'min': None, 'max': '2.10', 'schema': schema},
-        {'min': '2.11', 'max': None, 'schema': schemav211}]
+        {'min': '2.11', 'max': '2.52', 'schema': schemav211},
+        {'min': '2.53', 'max': None, 'schema': schemav253}]
 
     def list_services(self, **params):
         """Lists all running Compute services for a tenant.
@@ -47,9 +50,30 @@
         self.validate_response(_schema.list_services, resp, body)
         return rest_client.ResponseBody(resp, body)
 
+    def update_service(self, service_id, **kwargs):
+        """Update a compute service.
+
+        Update a compute service to enable or disable scheduling, including
+        recording a reason why a compute service was disabled from scheduling.
+
+        This API is available starting with microversion 2.53.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/compute/#update-compute-service
+        """
+        put_body = json.dumps(kwargs)
+        resp, body = self.put('os-services/%s' % service_id, put_body)
+        body = json.loads(body)
+        _schema = self.get_schema(self.schema_versions_info)
+        self.validate_response(_schema.update_service, resp, body)
+        return rest_client.ResponseBody(resp, body)
+
     def enable_service(self, **kwargs):
         """Enable service on a host.
 
+        ``update_service`` supersedes this API starting with microversion 2.53.
+
         For a full list of available parameters, please refer to the official
         API reference:
         https://developer.openstack.org/api-ref/compute/#enable-scheduling-for-a-compute-service
@@ -63,6 +87,8 @@
     def disable_service(self, **kwargs):
         """Disable service on a host.
 
+        ``update_service`` supersedes this API starting with microversion 2.53.
+
         For a full list of available parameters, please refer to the official
         API reference:
         https://developer.openstack.org/api-ref/compute/#disable-scheduling-for-a-compute-service
@@ -76,6 +102,8 @@
     def disable_log_reason(self, **kwargs):
         """Disables scheduling for a Compute service and logs reason.
 
+        ``update_service`` supersedes this API starting with microversion 2.53.
+
         For a full list of available parameters, please refer to the official
         API reference:
         https://developer.openstack.org/api-ref/compute/#disable-scheduling-for-a-compute-service-and-log-disabled-reason
@@ -89,6 +117,8 @@
     def update_forced_down(self, **kwargs):
         """Set or unset ``forced_down`` flag for the service.
 
+        ``update_service`` supersedes this API starting with microversion 2.53.
+
         For a full list of available parameters, please refer to the official
         API reference:
         https://developer.openstack.org/api-ref/compute/#update-forced-down
diff --git a/tempest/lib/services/network/agents_client.py b/tempest/lib/services/network/agents_client.py
index a0f832e..5068121 100644
--- a/tempest/lib/services/network/agents_client.py
+++ b/tempest/lib/services/network/agents_client.py
@@ -87,9 +87,11 @@
         return self.delete_resource(uri)
 
     def add_dhcp_agent_to_network(self, agent_id, **kwargs):
-        # TODO(piyush): Current api-site doesn't contain this API description.
-        # After fixing the api-site, we need to fix here also for putting the
-        # link to api-site.
-        # LP: https://bugs.launchpad.net/openstack-api-site/+bug/1526212
+        """Schedule a network to a DHCP agent.
+
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/network/v2/#schedule-a-network-to-a-dhcp-agent
+        """
         uri = '/agents/%s/dhcp-networks' % agent_id
         return self.create_resource(uri, kwargs, expect_empty_body=True)
diff --git a/tempest/lib/services/volume/v3/snapshots_client.py b/tempest/lib/services/volume/v3/snapshots_client.py
index f79bcd8..08e6c94 100644
--- a/tempest/lib/services/volume/v3/snapshots_client.py
+++ b/tempest/lib/services/volume/v3/snapshots_client.py
@@ -114,12 +114,12 @@
         return rest_client.ResponseBody(resp, body)
 
     def update_snapshot_status(self, snapshot_id, **kwargs):
-        """Update the specified snapshot's status."""
-        # TODO(gmann): api-site doesn't contain doc ref
-        # for this API. After fixing the api-site, we need to
-        # add the link here.
-        # Bug https://bugs.launchpad.net/openstack-api-site/+bug/1532645
+        """Update status of a snapshot.
 
+        For a full list of available parameters, please refer to the official
+        API reference:
+        https://developer.openstack.org/api-ref/block-storage/v3/#update-status-of-a-snapshot
+        """
         post_body = json.dumps({'os-update_snapshot_status': kwargs})
         url = 'snapshots/%s/action' % snapshot_id
         resp, body = self.post(url, post_body)
diff --git a/tempest/tests/common/utils/linux/test_remote_client.py b/tempest/tests/common/utils/linux/test_remote_client.py
index 739357b..1f0080f 100644
--- a/tempest/tests/common/utils/linux/test_remote_client.py
+++ b/tempest/tests/common/utils/linux/test_remote_client.py
@@ -77,7 +77,7 @@
 
     def test_write_to_console_special_chars(self):
         self._test_write_to_console_helper(
-            '\`',
+            r'\`',
             'sudo sh -c "echo \\"\\\\\\`\\" >/dev/console"')
         self.conn.write_to_console('$')
         self._assert_exec_called_with(
diff --git a/tempest/tests/fake_config.py b/tempest/tests/fake_config.py
index 4a2fff4..be54130 100644
--- a/tempest/tests/fake_config.py
+++ b/tempest/tests/fake_config.py
@@ -59,6 +59,7 @@
         self._set_attrs()
         self.lock_path = cfg.CONF.oslo_concurrency.lock_path
 
+
 fake_service1_group = cfg.OptGroup(name='fake-service1', title='Fake service1')
 
 FakeService1Group = [
diff --git a/tempest/tests/lib/common/utils/test_data_utils.py b/tempest/tests/lib/common/utils/test_data_utils.py
index b8385b2..a0267d0 100644
--- a/tempest/tests/lib/common/utils/test_data_utils.py
+++ b/tempest/tests/lib/common/utils/test_data_utils.py
@@ -88,7 +88,7 @@
     def test_rand_url(self):
         actual = data_utils.rand_url()
         self.assertIsInstance(actual, str)
-        self.assertRegex(actual, "^https://url-[0-9]*\.com$")
+        self.assertRegex(actual, r"^https://url-[0-9]*\.com$")
         actual2 = data_utils.rand_url()
         self.assertNotEqual(actual, actual2)
 
diff --git a/tempest/tests/lib/services/compute/test_services_client.py b/tempest/tests/lib/services/compute/test_services_client.py
index 2dd981c..ba432e3 100644
--- a/tempest/tests/lib/services/compute/test_services_client.py
+++ b/tempest/tests/lib/services/compute/test_services_client.py
@@ -56,6 +56,20 @@
         }
     }
 
+    FAKE_UPDATE_SERVICE = {
+        "service": {
+            "id": "e81d66a4-ddd3-4aba-8a84-171d1cb4d339",
+            "binary": "nova-compute",
+            "disabled_reason": "test2",
+            "host": "host1",
+            "state": "down",
+            "status": "disabled",
+            "updated_at": "2012-10-29T13:42:05.000000",
+            "forced_down": False,
+            "zone": "nova"
+        }
+    }
+
     def setUp(self):
         super(TestServicesClient, self).setUp()
         fake_auth = fake_auth_provider.FakeAuthProvider()
@@ -119,6 +133,28 @@
             binary="controller",
             disabled_reason='test reason')
 
+    def _test_update_service(self, bytes_body=False, status=None,
+                             disabled_reason=None, forced_down=None):
+        resp_body = copy.deepcopy(self.FAKE_UPDATE_SERVICE)
+        kwargs = {}
+
+        if status is not None:
+            kwargs['status'] = status
+        if disabled_reason is not None:
+            kwargs['disabled_reason'] = disabled_reason
+        if forced_down is not None:
+            kwargs['forced_down'] = forced_down
+
+        resp_body['service'].update(kwargs)
+
+        self.check_service_client_function(
+            self.client.update_service,
+            'tempest.lib.common.rest_client.RestClient.put',
+            resp_body,
+            bytes_body,
+            service_id=resp_body['service']['id'],
+            **kwargs)
+
     def test_log_reason_disabled_service_with_str_body(self):
         self._test_log_reason_disabled_service()
 
@@ -144,3 +180,36 @@
                        new_callable=mock.PropertyMock(return_value='2.11'))
     def test_update_forced_down_with_bytes_body(self, _):
         self._test_update_forced_down(bytes_body=True)
+
+    @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+                       new_callable=mock.PropertyMock(return_value='2.53'))
+    def test_update_service_disable_scheduling_with_str_body(self, _):
+        self._test_update_service(status='disabled',
+                                  disabled_reason='maintenance')
+
+    @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+                       new_callable=mock.PropertyMock(return_value='2.53'))
+    def test_update_service_disable_scheduling_with_bytes_body(self, _):
+        self._test_update_service(status='disabled',
+                                  disabled_reason='maintenance',
+                                  bytes_body=True)
+
+    @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+                       new_callable=mock.PropertyMock(return_value='2.53'))
+    def test_update_service_enable_scheduling_with_str_body(self, _):
+        self._test_update_service(status='enabled')
+
+    @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+                       new_callable=mock.PropertyMock(return_value='2.53'))
+    def test_update_service_enable_scheduling_with_bytes_body(self, _):
+        self._test_update_service(status='enabled', bytes_body=True)
+
+    @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+                       new_callable=mock.PropertyMock(return_value='2.53'))
+    def test_update_service_forced_down_with_str_body(self, _):
+        self._test_update_service(forced_down=True)
+
+    @mock.patch.object(base_compute_client, 'COMPUTE_MICROVERSION',
+                       new_callable=mock.PropertyMock(return_value='2.53'))
+    def test_update_service_forced_down_with_bytes_body(self, _):
+        self._test_update_service(forced_down=True, bytes_body=True)
diff --git a/tempest/tests/test_list_tests.py b/tempest/tests/test_list_tests.py
index 4af7463..1cc9c9a 100644
--- a/tempest/tests/test_list_tests.py
+++ b/tempest/tests/test_list_tests.py
@@ -34,7 +34,7 @@
                          "error on import %s" % ids)
         ids = six.text_type(ids).split('\n')
         for test_id in ids:
-            if re.match('(\w+\.){3}\w+', test_id):
+            if re.match(r'(\w+\.){3}\w+', test_id):
                 if not test_id.startswith('tempest.'):
                     parts = test_id.partition('tempest')
                     fail_id = parts[1] + parts[2]
diff --git a/tools/check_logs.py b/tools/check_logs.py
index b80ccc0..de7e41d 100755
--- a/tools/check_logs.py
+++ b/tools/check_logs.py
@@ -96,7 +96,7 @@
 def collect_url_logs(url):
     page = urlreq.urlopen(url)
     content = page.read()
-    logs = re.findall('(screen-[\w-]+\.txt\.gz)</a>', content)
+    logs = re.findall(r'(screen-[\w-]+\.txt\.gz)</a>', content)
     return logs
 
 
@@ -162,6 +162,7 @@
     print("ok")
     return 0
 
+
 usage = """
 Find non-white-listed log errors in log files from a devstack-gate run.
 Log files will be searched for ERROR or CRITICAL messages. If any
diff --git a/tools/generate-tempest-plugins-list.py b/tools/generate-tempest-plugins-list.py
index bbb9019..4eb78fb 100644
--- a/tools/generate-tempest-plugins-list.py
+++ b/tools/generate-tempest-plugins-list.py
@@ -63,12 +63,13 @@
     except HTTPError as err:
         if err.code == 404:
             return False
-    p = re.compile('^tempest\.test_plugins', re.M)
+    p = re.compile(r'^tempest\.test_plugins', re.M)
     if p.findall(r.read().decode('utf-8')):
         return True
     else:
         False
 
+
 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
diff --git a/tox.ini b/tox.ini
index da0233a..de4f1b7 100644
--- a/tox.ini
+++ b/tox.ini
@@ -19,7 +19,7 @@
     OS_STDOUT_CAPTURE=1
     OS_STDERR_CAPTURE=1
     OS_TEST_TIMEOUT=160
-    PYTHONWARNINGS=default::DeprecationWarning
+    PYTHONWARNINGS=default::DeprecationWarning,ignore::DeprecationWarning:distutils,ignore::DeprecationWarning:site
 passenv = OS_STDOUT_CAPTURE OS_STDERR_CAPTURE OS_TEST_TIMEOUT OS_TEST_LOCK_PATH TEMPEST_CONFIG TEMPEST_CONFIG_DIR http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY ZUUL_CACHE_DIR REQUIREMENTS_PIP_LOCATION GENERATE_TEMPEST_PLUGIN_LIST
 usedevelop = True
 install_command = pip install {opts} {packages}