Merge "Fix subunit describe calls utility document warnings"
diff --git a/.gitignore b/.gitignore
index 5b87cec..e96deb1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,6 +23,7 @@
 !.coveragerc
 cover/
 doc/source/_static/tempest.conf.sample
+doc/source/plugin-registry.rst
 
 # Files created by releasenotes build
 releasenotes/build
diff --git a/doc/source/conf.py b/doc/source/conf.py
index eef3620..127613d 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -15,6 +15,17 @@
 import os
 import subprocess
 
+# Build the plugin registry
+def build_plugin_registry(app):
+    root_dir = os.path.dirname(
+        os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+    subprocess.call(['tools/generate-tempest-plugins-list.sh'], cwd=root_dir)
+
+def setup(app):
+    app.connect('builder-inited', build_plugin_registry)
+
+
+
 # If extensions (or modules to document with autodoc) are in another directory,
 # add these directories to sys.path here. If the directory is relative to the
 # documentation root, use os.path.abspath to make it absolute, like shown here.
diff --git a/doc/source/plugin-registry.rst b/doc/source/plugin-registry.rst
deleted file mode 100644
index 517e5b8..0000000
--- a/doc/source/plugin-registry.rst
+++ /dev/null
@@ -1,23 +0,0 @@
-..
-  Note to patch submitters: this file is covered by a periodic proposal
-  job.  You should edit the files data/tempest-plugins-registry.footer
-  data/tempest-plugins-registry.header instead of this one.
-
-==========================
- Tempest Plugin Registry
-==========================
-
-Since we've created the external plugin mechanism, it's gotten used by
-a lot of projects. The following is a list of plugins that currently
-exist.
-
-Detected Plugins
-================
-
-The following will list plugins that a script has found in the openstack/
-namespace, which includes but is not limited to official OpenStack
-projects.
-
-+----------------------------+-------------------------------------------------------------------------+
-|Plugin Name                 |URL                                                                      |
-+----------------------------+-------------------------------------------------------------------------+
diff --git a/releasenotes/notes/identity-clients-as-library-e663c6132fcac6c2.yaml b/releasenotes/notes/identity-clients-as-library-e663c6132fcac6c2.yaml
index b7850d0..f9173a0 100644
--- a/releasenotes/notes/identity-clients-as-library-e663c6132fcac6c2.yaml
+++ b/releasenotes/notes/identity-clients-as-library-e663c6132fcac6c2.yaml
@@ -7,3 +7,7 @@
     any maintenance changes.
 
       * endpoints_client(v2)
+      * roles_client(v2)
+      * services_client(v2)
+      * tenants_client(v2)
+      * users_client(v2)
diff --git a/requirements.txt b/requirements.txt
index 45fe345..7d01f69 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,7 +5,7 @@
 cliff!=1.16.0,!=1.17.0,>=1.15.0 # Apache-2.0
 jsonschema!=2.5.0,<3.0.0,>=2.0.0 # MIT
 testtools>=1.4.0 # MIT
-paramiko>=2.0 # LGPL
+paramiko>=2.0 # LGPLv2.1+
 netaddr!=0.7.16,>=0.7.12 # BSD
 testrepository>=0.0.18 # Apache-2.0/BSD
 pyOpenSSL>=0.14 # Apache-2.0
diff --git a/tempest/api/compute/servers/test_disk_config.py b/tempest/api/compute/servers/test_disk_config.py
index 617cdd5..aba0240 100644
--- a/tempest/api/compute/servers/test_disk_config.py
+++ b/tempest/api/compute/servers/test_disk_config.py
@@ -37,17 +37,11 @@
         super(ServerDiskConfigTestJSON, cls).setup_clients()
         cls.client = cls.os.servers_client
 
-    @classmethod
-    def resource_setup(cls):
-        super(ServerDiskConfigTestJSON, cls).resource_setup()
-        server = cls.create_test_server(wait_until='ACTIVE')
-        cls.server_id = server['id']
-
-    def _update_server_with_disk_config(self, disk_config):
-        server = self.client.show_server(self.server_id)['server']
+    def _update_server_with_disk_config(self, server_id, disk_config):
+        server = self.client.show_server(server_id)['server']
         if disk_config != server['OS-DCF:diskConfig']:
             server = self.client.update_server(
-                self.server_id, disk_config=disk_config)['server']
+                server_id, disk_config=disk_config)['server']
             waiters.wait_for_server_status(self.client, server['id'], 'ACTIVE')
             server = self.client.show_server(server['id'])['server']
             self.assertEqual(disk_config, server['OS-DCF:diskConfig'])
@@ -55,9 +49,12 @@
     @test.idempotent_id('bef56b09-2e8c-4883-a370-4950812f430e')
     def test_rebuild_server_with_manual_disk_config(self):
         # A server should be rebuilt using the manual disk config option
-        self._update_server_with_disk_config(disk_config='AUTO')
+        server = self.create_test_server(wait_until='ACTIVE')
+        self.addCleanup(self.client.delete_server, server['id'])
+        self._update_server_with_disk_config(server['id'],
+                                             disk_config='AUTO')
 
-        server = self.client.rebuild_server(self.server_id,
+        server = self.client.rebuild_server(server['id'],
                                             self.image_ref_alt,
                                             disk_config='MANUAL')['server']
 
@@ -71,9 +68,12 @@
     @test.idempotent_id('9c9fae77-4feb-402f-8450-bf1c8b609713')
     def test_rebuild_server_with_auto_disk_config(self):
         # A server should be rebuilt using the auto disk config option
-        self._update_server_with_disk_config(disk_config='MANUAL')
+        server = self.create_test_server(wait_until='ACTIVE')
+        self.addCleanup(self.client.delete_server, server['id'])
+        self._update_server_with_disk_config(server['id'],
+                                             disk_config='MANUAL')
 
-        server = self.client.rebuild_server(self.server_id,
+        server = self.client.rebuild_server(server['id'],
                                             self.image_ref_alt,
                                             disk_config='AUTO')['server']
 
@@ -84,31 +84,24 @@
         server = self.client.show_server(server['id'])['server']
         self.assertEqual('AUTO', server['OS-DCF:diskConfig'])
 
-    def _get_alternative_flavor(self):
-        server = self.client.show_server(self.server_id)['server']
-
-        if server['flavor']['id'] == self.flavor_ref:
-            return self.flavor_ref_alt
-        else:
-            return self.flavor_ref
-
     @test.idempotent_id('414e7e93-45b5-44bc-8e03-55159c6bfc97')
     @testtools.skipUnless(CONF.compute_feature_enabled.resize,
                           'Resize not available.')
     def test_resize_server_from_manual_to_auto(self):
         # A server should be resized from manual to auto disk config
-        self._update_server_with_disk_config(disk_config='MANUAL')
-
+        server = self.create_test_server(wait_until='ACTIVE')
+        self.addCleanup(self.client.delete_server, server['id'])
+        self._update_server_with_disk_config(server['id'],
+                                             disk_config='MANUAL')
         # Resize with auto option
-        flavor_id = self._get_alternative_flavor()
-        self.client.resize_server(self.server_id, flavor_id,
+        self.client.resize_server(server['id'], self.flavor_ref_alt,
                                   disk_config='AUTO')
-        waiters.wait_for_server_status(self.client, self.server_id,
+        waiters.wait_for_server_status(self.client, server['id'],
                                        'VERIFY_RESIZE')
-        self.client.confirm_resize_server(self.server_id)
-        waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
+        self.client.confirm_resize_server(server['id'])
+        waiters.wait_for_server_status(self.client, server['id'], 'ACTIVE')
 
-        server = self.client.show_server(self.server_id)['server']
+        server = self.client.show_server(server['id'])['server']
         self.assertEqual('AUTO', server['OS-DCF:diskConfig'])
 
     @test.idempotent_id('693d16f3-556c-489a-8bac-3d0ca2490bad')
@@ -116,27 +109,31 @@
                           'Resize not available.')
     def test_resize_server_from_auto_to_manual(self):
         # A server should be resized from auto to manual disk config
-        self._update_server_with_disk_config(disk_config='AUTO')
-
+        server = self.create_test_server(wait_until='ACTIVE')
+        self.addCleanup(self.client.delete_server, server['id'])
+        self._update_server_with_disk_config(server['id'],
+                                             disk_config='AUTO')
         # Resize with manual option
-        flavor_id = self._get_alternative_flavor()
-        self.client.resize_server(self.server_id, flavor_id,
+        self.client.resize_server(server['id'], self.flavor_ref_alt,
                                   disk_config='MANUAL')
-        waiters.wait_for_server_status(self.client, self.server_id,
+        waiters.wait_for_server_status(self.client, server['id'],
                                        'VERIFY_RESIZE')
-        self.client.confirm_resize_server(self.server_id)
-        waiters.wait_for_server_status(self.client, self.server_id, 'ACTIVE')
+        self.client.confirm_resize_server(server['id'])
+        waiters.wait_for_server_status(self.client, server['id'], 'ACTIVE')
 
-        server = self.client.show_server(self.server_id)['server']
+        server = self.client.show_server(server['id'])['server']
         self.assertEqual('MANUAL', server['OS-DCF:diskConfig'])
 
     @test.idempotent_id('5ef18867-358d-4de9-b3c9-94d4ba35742f')
     def test_update_server_from_auto_to_manual(self):
         # A server should be updated from auto to manual disk config
-        self._update_server_with_disk_config(disk_config='AUTO')
+        server = self.create_test_server(wait_until='ACTIVE')
+        self.addCleanup(self.client.delete_server, server['id'])
+        self._update_server_with_disk_config(server['id'],
+                                             disk_config='AUTO')
 
         # Update the disk_config attribute to manual
-        server = self.client.update_server(self.server_id,
+        server = self.client.update_server(server['id'],
                                            disk_config='MANUAL')['server']
         waiters.wait_for_server_status(self.client, server['id'], 'ACTIVE')
 
diff --git a/tempest/cmd/javelin.py b/tempest/cmd/javelin.py
index 08ad94f..a9e5167 100755
--- a/tempest/cmd/javelin.py
+++ b/tempest/cmd/javelin.py
@@ -126,15 +126,15 @@
 from tempest.lib.services.compute import security_group_rules_client
 from tempest.lib.services.compute import security_groups_client
 from tempest.lib.services.compute import servers_client
+from tempest.lib.services.identity.v2 import roles_client
+from tempest.lib.services.identity.v2 import tenants_client
+from tempest.lib.services.identity.v2 import users_client
 from tempest.lib.services.image.v2 import images_client
 from tempest.lib.services.network import networks_client
 from tempest.lib.services.network import ports_client
 from tempest.lib.services.network import routers_client
 from tempest.lib.services.network import subnets_client
 from tempest.services.identity.v2.json import identity_client
-from tempest.services.identity.v2.json import roles_client
-from tempest.services.identity.v2.json import tenants_client
-from tempest.services.identity.v2.json import users_client
 from tempest.services.object_storage import container_client
 from tempest.services.object_storage import object_client
 from tempest.services.volume.v1.json import volumes_client
diff --git a/tempest/cmd/run.py b/tempest/cmd/run.py
index 26bd418..8777c0e 100644
--- a/tempest/cmd/run.py
+++ b/tempest/cmd/run.py
@@ -23,6 +23,27 @@
                    any tests that match on re.match() with the regex
  * **--smoke**: Run all the tests tagged as smoke
 
+There are also the **--blacklist_file** and **--whitelist_file** options that
+let you pass a filepath to tempest run with the file format being a line
+seperated regex, with '#' used to signify the start of a comment on a line.
+For example::
+
+    # Regex file
+    ^regex1 # Match these tests
+    .*regex2 # Match those tests
+
+The blacklist file will be used to construct a negative lookahead regex and
+the whitelist file will simply OR all the regexes in the file. The whitelist
+and blacklist file options are mutually exclusive so you can't use them
+together. However, you can combine either with a normal regex or the *--smoke*
+flag. When used with a blacklist file the generated regex will be combined to
+something like::
+
+    ^((?!black_regex1|black_regex2).)*$cli_regex1
+
+When combined with a whitelist file all the regexes from the file and the CLI
+regexes will be ORed.
+
 You can also use the **--list-tests** option in conjunction with selection
 arguments to list which tests will be run.
 
@@ -33,6 +54,16 @@
 If you want to adjust the number of workers use the **--concurrency** option
 and if you want to run tests serially use **--serial**
 
+Running with Workspaces
+-----------------------
+Tempest run enables you to run your tempest tests from any setup tempest
+workspace it relies on you having setup a tempest workspace with either the
+``tempest init`` or ``tempest workspace`` commands. Then using the
+``--workspace`` CLI option you can specify which one of your workspaces you
+want to run tempest from. Using this option you don't have to run Tempest
+directly with you current working directory being the workspace, Tempest will
+take care of managing everything to be executed from there.
+
 Test Output
 ===========
 By default tempest run's output to STDOUT will be generated using the
@@ -47,10 +78,12 @@
 import threading
 
 from cliff import command
+from os_testr import regex_builder
 from os_testr import subunit_trace
 from oslo_log import log as logging
 from testrepository.commands import run_argv
 
+from tempest.cmd import workspace
 from tempest import config
 
 
@@ -68,18 +101,31 @@
         else:
             os.environ["TESTR_PDB"] = ""
 
+    def _create_testrepository(self):
+        if not os.path.isdir('.testrepository'):
+            returncode = run_argv(['testr', 'init'], sys.stdin, sys.stdout,
+                                  sys.stderr)
+            if returncode:
+                sys.exit(returncode)
+
     def take_action(self, parsed_args):
         self._set_env()
         returncode = 0
+        # Workspace execution mode
+        if parsed_args.workspace:
+            workspace_mgr = workspace.WorkspaceManager(
+                parsed_args.workspace_path)
+            path = workspace_mgr.get_workspace(parsed_args.workspace)
+            os.chdir(path)
+            # NOTE(mtreinish): tempest init should create a .testrepository dir
+            # but since workspaces can be imported let's sanity check and
+            # ensure that one is created
+            self._create_testrepository()
         # Local execution mode
-        if os.path.isfile('.testr.conf'):
+        elif os.path.isfile('.testr.conf'):
             # If you're running in local execution mode and there is not a
             # testrepository dir create one
-            if not os.path.isdir('.testrepository'):
-                returncode = run_argv(['testr', 'init'], sys.stdin, sys.stdout,
-                                      sys.stderr)
-                if returncode:
-                    sys.exit(returncode)
+            self._create_testrepository()
         else:
             print("No .testr.conf file was found for local execution")
             sys.exit(2)
@@ -102,6 +148,15 @@
         return parser
 
     def _add_args(self, parser):
+        # workspace args
+        parser.add_argument('--workspace', default=None,
+                            help='Name of tempest workspace to use for running'
+                                 ' tests. You can see a list of workspaces '
+                                 'with tempest workspace list')
+        parser.add_argument('--workspace-path', default=None,
+                            dest='workspace_path',
+                            help="The path to the workspace file, the default "
+                                 "is ~/.tempest/workspace.yaml")
         # test selection args
         regex = parser.add_mutually_exclusive_group()
         regex.add_argument('--smoke', action='store_true',
@@ -109,6 +164,15 @@
         regex.add_argument('--regex', '-r', default='',
                            help='A normal testr selection regex used to '
                                 'specify a subset of tests to run')
+        list_selector = parser.add_mutually_exclusive_group()
+        list_selector.add_argument('--whitelist_file',
+                                   help="Path to a whitelist file, this file "
+                                        "contains a seperate regex on each "
+                                        "newline.")
+        list_selector.add_argument('--blacklist_file',
+                                   help='Path to a blacklist file, this file '
+                                        'contains a separate regex exclude on '
+                                        'each newline')
         # list only args
         parser.add_argument('--list-tests', '-l', action='store_true',
                             help='List tests',
@@ -138,6 +202,10 @@
             regex = 'smoke'
         elif parsed_args.regex:
             regex = parsed_args.regex
+        if parsed_args.whitelist_file or parsed_args.blacklist_file:
+            regex = regex_builder.construct_regex(parsed_args.blacklist_file,
+                                                  parsed_args.whitelist_file,
+                                                  regex, False)
         return regex
 
     def _build_options(self, parsed_args):
diff --git a/tempest/lib/services/compute/aggregates_client.py b/tempest/lib/services/compute/aggregates_client.py
index ae747d8..7ad14bc 100644
--- a/tempest/lib/services/compute/aggregates_client.py
+++ b/tempest/lib/services/compute/aggregates_client.py
@@ -41,7 +41,7 @@
         """Create a new aggregate.
 
         Available params: see http://developer.openstack.org/
-                              api-ref-compute-v2.1.html#createaggregate
+                              api-ref-compute-v2.1.html#createAggregate
         """
         post_body = json.dumps({'aggregate': kwargs})
         resp, body = self.post('os-aggregates', post_body)
@@ -54,7 +54,7 @@
         """Update an aggregate.
 
         Available params: see http://developer.openstack.org/
-                              api-ref-compute-v2.1.html#updateaggregate
+                              api-ref-compute-v2.1.html#updateAggregate
         """
         put_body = json.dumps({'aggregate': kwargs})
         resp, body = self.put('os-aggregates/%s' % aggregate_id, put_body)
@@ -85,7 +85,7 @@
         """Add a host to the given aggregate.
 
         Available params: see http://developer.openstack.org/
-                              api-ref-compute-v2.1.html#addhost
+                              api-ref-compute-v2.1.html#addHost
         """
         post_body = json.dumps({'add_host': kwargs})
         resp, body = self.post('os-aggregates/%s/action' % aggregate_id,
@@ -98,7 +98,7 @@
         """Remove a host from the given aggregate.
 
         Available params: see http://developer.openstack.org/
-                              api-ref-compute-v2.1.html#removehost
+                              api-ref-compute-v2.1.html#removeAggregateHost
         """
         post_body = json.dumps({'remove_host': kwargs})
         resp, body = self.post('os-aggregates/%s/action' % aggregate_id,
diff --git a/tempest/lib/services/compute/flavors_client.py b/tempest/lib/services/compute/flavors_client.py
index 0d80a82..5be8272 100644
--- a/tempest/lib/services/compute/flavors_client.py
+++ b/tempest/lib/services/compute/flavors_client.py
@@ -91,7 +91,7 @@
         """Set extra Specs to the mentioned flavor.
 
         Available params: see http://developer.openstack.org/
-                              api-ref-compute-v2.1.html#updateFlavorExtraSpec
+                              api-ref-compute-v2.1.html#createFlavorExtraSpec
         """
         post_body = json.dumps({'extra_specs': kwargs})
         resp, body = self.post('flavors/%s/os-extra_specs' % flavor_id,
@@ -123,7 +123,7 @@
         """Update specified extra Specs of the mentioned flavor and key.
 
         Available params: see http://developer.openstack.org/
-                              api-ref-compute-v2.1.html#updateflavorspec
+                              api-ref-compute-v2.1.html#updateFlavorExtraSpec
         """
         resp, body = self.put('flavors/%s/os-extra_specs/%s' %
                               (flavor_id, key), json.dumps(kwargs))
diff --git a/tempest/lib/services/compute/migrations_client.py b/tempest/lib/services/compute/migrations_client.py
index 5eae8aa..62246d3 100644
--- a/tempest/lib/services/compute/migrations_client.py
+++ b/tempest/lib/services/compute/migrations_client.py
@@ -26,7 +26,7 @@
         """List all migrations.
 
         Available params: see http://developer.openstack.org/
-                              api-ref-compute-v2.1.html#returnmigrations
+                              api-ref-compute-v2.1.html#listMigrations
         """
 
         url = 'os-migrations'
diff --git a/tempest/lib/services/compute/quotas_client.py b/tempest/lib/services/compute/quotas_client.py
index 184a3d7..6d41f4b 100644
--- a/tempest/lib/services/compute/quotas_client.py
+++ b/tempest/lib/services/compute/quotas_client.py
@@ -46,7 +46,7 @@
         """Updates the tenant's quota limits for one or more resources.
 
         Available params: see http://developer.openstack.org/
-                              api-ref-compute-v2.1.html#updatesquotatenant
+                              api-ref-compute-v2.1.html#updateQuota
         """
 
         post_body = json.dumps({'quota_set': kwargs})
diff --git a/tempest/lib/services/identity/v2/roles_client.py b/tempest/lib/services/identity/v2/roles_client.py
new file mode 100644
index 0000000..15c8834
--- /dev/null
+++ b/tempest/lib/services/identity/v2/roles_client.py
@@ -0,0 +1,107 @@
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+
+
+class RolesClient(rest_client.RestClient):
+    api_version = "v2.0"
+
+    def create_role(self, **kwargs):
+        """Create a role.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-identity-v2-ext.html#createRole
+        """
+        post_body = json.dumps({'role': kwargs})
+        resp, body = self.post('OS-KSADM/roles', post_body)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_role(self, role_id_or_name):
+        """Get a role by its id or name.
+
+        Available params: see
+            http://developer.openstack.org/
+            api-ref-identity-v2-ext.html#showRoleByID
+            OR
+            http://developer.openstack.org/
+            api-ref-identity-v2-ext.html#showRoleByName
+        """
+        resp, body = self.get('OS-KSADM/roles/%s' % role_id_or_name)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_roles(self, **params):
+        """Returns roles.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-identity-v2-ext.html#listRoles
+        """
+        url = 'OS-KSADM/roles'
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_role(self, role_id):
+        """Delete a role.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-identity-v2-ext.html#deleteRole
+        """
+        resp, body = self.delete('OS-KSADM/roles/%s' % str(role_id))
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def create_user_role_on_project(self, tenant_id, user_id, role_id):
+        """Add roles to a user on a tenant.
+
+        Available params: see
+            http://developer.openstack.org/
+            api-ref-identity-v2-ext.html#grantRoleToUserOnTenant
+        """
+        resp, body = self.put('/tenants/%s/users/%s/roles/OS-KSADM/%s' %
+                              (tenant_id, user_id, role_id), "")
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_user_roles_on_project(self, tenant_id, user_id, **params):
+        """Returns a list of roles assigned to a user for a tenant."""
+        # TODO(gmann): Need to write API-ref link, Bug# 1592711
+        url = '/tenants/%s/users/%s/roles' % (tenant_id, user_id)
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_role_from_user_on_project(self, tenant_id, user_id, role_id):
+        """Removes a role assignment for a user on a tenant.
+
+        Available params: see
+            http://developer.openstack.org/
+            api-ref-identity-v2-ext.html#revokeRoleFromUserOnTenant
+        """
+        resp, body = self.delete('/tenants/%s/users/%s/roles/OS-KSADM/%s' %
+                                 (tenant_id, user_id, role_id))
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/services/identity/v2/json/services_client.py b/tempest/lib/services/identity/v2/services_client.py
similarity index 100%
rename from tempest/services/identity/v2/json/services_client.py
rename to tempest/lib/services/identity/v2/services_client.py
diff --git a/tempest/lib/services/identity/v2/tenants_client.py b/tempest/lib/services/identity/v2/tenants_client.py
new file mode 100644
index 0000000..77ddaa5
--- /dev/null
+++ b/tempest/lib/services/identity/v2/tenants_client.py
@@ -0,0 +1,98 @@
+# Copyright 2015 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+
+
+class TenantsClient(rest_client.RestClient):
+    api_version = "v2.0"
+
+    def create_tenant(self, **kwargs):
+        """Create a tenant
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-identity-v2-ext.html#createTenant
+        """
+        post_body = json.dumps({'tenant': kwargs})
+        resp, body = self.post('tenants', post_body)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_tenant(self, tenant_id):
+        """Delete a tenant.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-identity-v2-ext.html#deleteTenant
+        """
+        resp, body = self.delete('tenants/%s' % str(tenant_id))
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_tenant(self, tenant_id):
+        """Get tenant details.
+
+        Available params: see
+            http://developer.openstack.org/
+            api-ref-identity-v2-ext.html#admin-showTenantById
+        """
+        resp, body = self.get('tenants/%s' % str(tenant_id))
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_tenants(self, **params):
+        """Returns tenants.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-identity-v2-ext.html#admin-listTenants
+        """
+        url = 'tenants'
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_tenant(self, tenant_id, **kwargs):
+        """Updates a tenant.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-identity-v2-ext.html#updateTenant
+        """
+        if 'id' not in kwargs:
+            kwargs['id'] = tenant_id
+        post_body = json.dumps({'tenant': kwargs})
+        resp, body = self.post('tenants/%s' % tenant_id, post_body)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_tenant_users(self, tenant_id, **params):
+        """List users for a Tenant.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-identity-v2-ext.html#listUsersForTenant
+        """
+        url = '/tenants/%s/users' % tenant_id
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/identity/v2/users_client.py b/tempest/lib/services/identity/v2/users_client.py
new file mode 100644
index 0000000..4ea17f9
--- /dev/null
+++ b/tempest/lib/services/identity/v2/users_client.py
@@ -0,0 +1,152 @@
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest.lib.common import rest_client
+
+
+class UsersClient(rest_client.RestClient):
+    api_version = "v2.0"
+
+    def create_user(self, **kwargs):
+        """Create a user.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-identity-admin-v2.html#admin-createUser
+        """
+        post_body = json.dumps({'user': kwargs})
+        resp, body = self.post('users', post_body)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_user(self, user_id, **kwargs):
+        """Updates a user.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-identity-admin-v2.html#admin-updateUser
+        """
+        put_body = json.dumps({'user': kwargs})
+        resp, body = self.put('users/%s' % user_id, put_body)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_user(self, user_id):
+        """GET a user.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-identity-admin-v2.html#admin-showUser
+        """
+        resp, body = self.get("users/%s" % user_id)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_user(self, user_id):
+        """Delete a user.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-identity-admin-v2.html#admin-deleteUser
+        """
+        resp, body = self.delete("users/%s" % user_id)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_users(self, **params):
+        """Get the list of users.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-identity-admin-v2.html#admin-listUsers
+        """
+        url = "users"
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_user_enabled(self, user_id, **kwargs):
+        """Enables or disables a user.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-identity-v2-ext.html#enableUser
+        """
+        # NOTE: The URL (users/<id>/enabled) is different from the api-site
+        # one (users/<id>/OS-KSADM/enabled) , but they are the same API
+        # because of the fact that in keystone/contrib/admin_crud/core.py
+        # both api use same action='set_user_enabled'
+        put_body = json.dumps({'user': kwargs})
+        resp, body = self.put('users/%s/enabled' % user_id, put_body)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_user_password(self, user_id, **kwargs):
+        """Update User Password."""
+        # 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/1524147
+        put_body = json.dumps({'user': kwargs})
+        resp, body = self.put('users/%s/OS-KSADM/password' % user_id, put_body)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_user_own_password(self, user_id, **kwargs):
+        """User updates own password"""
+        # 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/1524153
+        # NOTE: This API is used for updating user password by itself.
+        # Ref: http://lists.openstack.org/pipermail/openstack-dev/2015-December
+        #      /081803.html
+        patch_body = json.dumps({'user': kwargs})
+        resp, body = self.patch('OS-KSCRUD/users/%s' % user_id, patch_body)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def create_user_ec2_credential(self, user_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.
+        post_body = json.dumps(kwargs)
+        resp, body = self.post('/users/%s/credentials/OS-EC2' % user_id,
+                               post_body)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_user_ec2_credential(self, user_id, access):
+        resp, body = self.delete('/users/%s/credentials/OS-EC2/%s' %
+                                 (user_id, access))
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_user_ec2_credentials(self, user_id):
+        resp, body = self.get('/users/%s/credentials/OS-EC2' % user_id)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_user_ec2_credential(self, user_id, access):
+        resp, body = self.get('/users/%s/credentials/OS-EC2/%s' %
+                              (user_id, access))
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/network/routers_client.py b/tempest/lib/services/network/routers_client.py
index 78ffa77..2ba1938 100644
--- a/tempest/lib/services/network/routers_client.py
+++ b/tempest/lib/services/network/routers_client.py
@@ -55,7 +55,7 @@
         """Remove router interface.
 
         Available params: see http://developer.openstack.org/
-                              api-ref-networking-v2-ext.html#removeRouterInterface
+                              api-ref-networking-v2-ext.html#deleteRouterInterface
         """
         uri = '/routers/%s/remove_router_interface' % router_id
         return self.update_resource(uri, kwargs)
diff --git a/tempest/scenario/test_server_basic_ops.py b/tempest/scenario/test_server_basic_ops.py
index 80728dc..446c87a 100644
--- a/tempest/scenario/test_server_basic_ops.py
+++ b/tempest/scenario/test_server_basic_ops.py
@@ -14,6 +14,7 @@
 #    under the License.
 
 import json
+import re
 
 from oslo_log import log as logging
 
@@ -83,9 +84,9 @@
     def verify_metadata_on_config_drive(self):
         if self.run_ssh and CONF.compute_feature_enabled.config_drive:
             # Verify metadata on config_drive
-            cmd_blkid = 'blkid -t LABEL=config-2 -o device'
-            dev_name = self.ssh_client.exec_command(cmd_blkid)
-            dev_name = dev_name.rstrip()
+            cmd_blkid = 'blkid | grep -i config-2'
+            result = self.ssh_client.exec_command(cmd_blkid)
+            dev_name = re.match('([^:]+)', result).group()
             self.ssh_client.exec_command('sudo mount %s /mnt' % dev_name)
             cmd_md = 'sudo cat /mnt/openstack/latest/meta_data.json'
             result = self.ssh_client.exec_command(cmd_md)
diff --git a/tempest/services/identity/v2/__init__.py b/tempest/services/identity/v2/__init__.py
index 6f4ebcf..ac2a874 100644
--- a/tempest/services/identity/v2/__init__.py
+++ b/tempest/services/identity/v2/__init__.py
@@ -13,12 +13,12 @@
 # the License.
 
 from tempest.lib.services.identity.v2.endpoints_client import EndpointsClient
+from tempest.lib.services.identity.v2.roles_client import RolesClient
+from tempest.lib.services.identity.v2.services_client import ServicesClient
+from tempest.lib.services.identity.v2.tenants_client import TenantsClient
 from tempest.lib.services.identity.v2.token_client import TokenClient
+from tempest.lib.services.identity.v2.users_client import UsersClient
 from tempest.services.identity.v2.json.identity_client import IdentityClient
-from tempest.services.identity.v2.json.roles_client import RolesClient
-from tempest.services.identity.v2.json.services_client import ServicesClient
-from tempest.services.identity.v2.json.tenants_client import TenantsClient
-from tempest.services.identity.v2.json.users_client import UsersClient
 
 __all__ = ['EndpointsClient', 'TokenClient', 'IdentityClient', 'RolesClient',
            'ServicesClient', 'TenantsClient', 'UsersClient']
diff --git a/tempest/tests/cmd/test_run.py b/tempest/tests/cmd/test_run.py
index dcffd21..772391f 100644
--- a/tempest/tests/cmd/test_run.py
+++ b/tempest/tests/cmd/test_run.py
@@ -46,18 +46,24 @@
         args = mock.Mock(spec=argparse.Namespace)
         setattr(args, 'smoke', False)
         setattr(args, 'regex', '')
+        setattr(args, 'whitelist_file', None)
+        setattr(args, 'blacklist_file', None)
         self.assertEqual('', self.run_cmd._build_regex(args))
 
     def test__build_regex_smoke(self):
         args = mock.Mock(spec=argparse.Namespace)
         setattr(args, "smoke", True)
         setattr(args, 'regex', '')
+        setattr(args, 'whitelist_file', None)
+        setattr(args, 'blacklist_file', None)
         self.assertEqual('smoke', self.run_cmd._build_regex(args))
 
     def test__build_regex_regex(self):
         args = mock.Mock(spec=argparse.Namespace)
         setattr(args, 'smoke', False)
         setattr(args, "regex", 'i_am_a_fun_little_regex')
+        setattr(args, 'whitelist_file', None)
+        setattr(args, 'blacklist_file', None)
         self.assertEqual('i_am_a_fun_little_regex',
                          self.run_cmd._build_regex(args))
 
diff --git a/tempest/tests/common/test_dynamic_creds.py b/tempest/tests/common/test_dynamic_creds.py
index 7a8637f..b7cc05d 100644
--- a/tempest/tests/common/test_dynamic_creds.py
+++ b/tempest/tests/common/test_dynamic_creds.py
@@ -22,14 +22,14 @@
 from tempest import exceptions
 from tempest.lib.common import rest_client
 from tempest.lib import exceptions as lib_exc
+from tempest.lib.services.identity.v2 import roles_client as v2_roles_client
+from tempest.lib.services.identity.v2 import tenants_client as \
+    v2_tenants_client
 from tempest.lib.services.identity.v2 import token_client as v2_token_client
+from tempest.lib.services.identity.v2 import users_client as v2_users_client
 from tempest.lib.services.identity.v3 import token_client as v3_token_client
 from tempest.lib.services.network import routers_client
 from tempest.services.identity.v2.json import identity_client as v2_iden_client
-from tempest.services.identity.v2.json import roles_client as v2_roles_client
-from tempest.services.identity.v2.json import tenants_client as \
-    v2_tenants_client
-from tempest.services.identity.v2.json import users_client as v2_users_client
 from tempest.services.identity.v3.json import domains_client
 from tempest.services.identity.v3.json import identity_client as v3_iden_client
 from tempest.services.identity.v3.json import projects_client as \
diff --git a/tempest/tests/services/identity/v2/test_roles_client.py b/tempest/tests/lib/services/identity/v2/test_roles_client.py
similarity index 98%
rename from tempest/tests/services/identity/v2/test_roles_client.py
rename to tempest/tests/lib/services/identity/v2/test_roles_client.py
index e36ec18..464a715 100644
--- a/tempest/tests/services/identity/v2/test_roles_client.py
+++ b/tempest/tests/lib/services/identity/v2/test_roles_client.py
@@ -12,7 +12,7 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-from tempest.services.identity.v2.json import roles_client
+from tempest.lib.services.identity.v2 import roles_client
 from tempest.tests.lib import fake_auth_provider
 from tempest.tests.lib.services import base
 
diff --git a/tempest/tests/lib/services/identity/v2/test_services_client.py b/tempest/tests/lib/services/identity/v2/test_services_client.py
new file mode 100644
index 0000000..bafb6b1
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v2/test_services_client.py
@@ -0,0 +1,97 @@
+# Copyright 2016 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.
+
+from tempest.lib.services.identity.v2 import services_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestServicesClient(base.BaseServiceTest):
+    FAKE_SERVICE_INFO = {
+        "OS-KSADM:service": {
+            "id": "1",
+            "name": "test",
+            "type": "compute",
+            "description": "test_description"
+        }
+    }
+
+    FAKE_LIST_SERVICES = {
+        "OS-KSADM:services": [
+            {
+                "id": "1",
+                "name": "test",
+                "type": "compute",
+                "description": "test_description"
+            },
+            {
+                "id": "2",
+                "name": "test2",
+                "type": "compute",
+                "description": "test2_description"
+            }
+        ]
+    }
+
+    def setUp(self):
+        super(TestServicesClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = services_client.ServicesClient(fake_auth,
+                                                     'identity', 'regionOne')
+
+    def _test_create_service(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.create_service,
+            'tempest.lib.common.rest_client.RestClient.post',
+            self.FAKE_SERVICE_INFO,
+            bytes_body,
+            id="1",
+            name="test",
+            type="compute",
+            description="test_description")
+
+    def _test_show_service(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_service,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_SERVICE_INFO,
+            bytes_body,
+            service_id="1")
+
+    def _test_list_services(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_services,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_LIST_SERVICES,
+            bytes_body)
+
+    def test_create_service_with_str_body(self):
+        self._test_create_service()
+
+    def test_create_service_with_bytes_body(self):
+        self._test_create_service(bytes_body=True)
+
+    def test_show_service_with_str_body(self):
+        self._test_show_service()
+
+    def test_show_service_with_bytes_body(self):
+        self._test_show_service(bytes_body=True)
+
+    def test_delete_service(self):
+        self.check_service_client_function(
+            self.client.delete_service,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            service_id="1",
+            status=204)
diff --git a/tempest/tests/lib/services/identity/v2/test_tenants_client.py b/tempest/tests/lib/services/identity/v2/test_tenants_client.py
new file mode 100644
index 0000000..ae3d13a
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v2/test_tenants_client.py
@@ -0,0 +1,131 @@
+# Copyright 2016 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.
+
+from tempest.lib.services.identity.v2 import tenants_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestTenantsClient(base.BaseServiceTest):
+    FAKE_TENANT_INFO = {
+        "tenant": {
+            "id": "1",
+            "name": "test",
+            "description": "test_description",
+            "enabled": True
+        }
+    }
+
+    FAKE_LIST_TENANTS = {
+        "tenants": [
+            {
+                "id": "1",
+                "name": "test",
+                "description": "test_description",
+                "enabled": True
+            },
+            {
+                "id": "2",
+                "name": "test2",
+                "description": "test2_description",
+                "enabled": True
+            }
+        ]
+    }
+
+    def setUp(self):
+        super(TestTenantsClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = tenants_client.TenantsClient(fake_auth,
+                                                   'identity', 'regionOne')
+
+    def _test_create_tenant(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.create_tenant,
+            'tempest.lib.common.rest_client.RestClient.post',
+            self.FAKE_TENANT_INFO,
+            bytes_body,
+            name="test",
+            description="test_description")
+
+    def _test_show_tenant(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_tenant,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_TENANT_INFO,
+            bytes_body,
+            tenant_id="1")
+
+    def _test_update_tenant(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.update_tenant,
+            'tempest.lib.common.rest_client.RestClient.post',
+            self.FAKE_TENANT_INFO,
+            bytes_body,
+            tenant_id="1",
+            name="test",
+            description="test_description")
+
+    def _test_list_tenants(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_tenants,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_LIST_TENANTS,
+            bytes_body)
+
+    def _test_list_tenant_users(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_tenant_users,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_LIST_TENANTS,
+            bytes_body,
+            tenant_id="1")
+
+    def test_create_tenant_with_str_body(self):
+        self._test_create_tenant()
+
+    def test_create_tenant_with_bytes_body(self):
+        self._test_create_tenant(bytes_body=True)
+
+    def test_show_tenant_with_str_body(self):
+        self._test_show_tenant()
+
+    def test_show_tenant_with_bytes_body(self):
+        self._test_show_tenant(bytes_body=True)
+
+    def test_update_tenant_with_str_body(self):
+        self._test_update_tenant()
+
+    def test_update_tenant_with_bytes_body(self):
+        self._test_update_tenant(bytes_body=True)
+
+    def test_list_tenants_with_str_body(self):
+        self._test_list_tenants()
+
+    def test_list_tenants_with_bytes_body(self):
+        self._test_list_tenants(bytes_body=True)
+
+    def test_delete_tenant(self):
+        self.check_service_client_function(
+            self.client.delete_tenant,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            tenant_id="1",
+            status=204)
+
+    def test_list_tenant_users_with_str_body(self):
+        self._test_list_tenant_users()
+
+    def test_list_tenant_users_with_bytes_body(self):
+        self._test_list_tenant_users(bytes_body=True)
diff --git a/tempest/tests/lib/services/identity/v2/test_users_client.py b/tempest/tests/lib/services/identity/v2/test_users_client.py
new file mode 100644
index 0000000..9534e44
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v2/test_users_client.py
@@ -0,0 +1,243 @@
+# Copyright 2016 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.
+
+from tempest.lib.services.identity.v2 import users_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestUsersClient(base.BaseServiceTest):
+    FAKE_USER_INFO = {
+        "user": {
+            "id": "1",
+            "name": "test",
+            "email": "john.smith@example.org",
+            "enabled": True
+        }
+    }
+
+    FAKE_LIST_USERS = {
+        "users": [
+            {
+                "id": "1",
+                "name": "test",
+                "email": "john.smith@example.org",
+                "enabled": True
+            },
+            {
+                "id": "2",
+                "name": "test2",
+                "email": "john.smith@example.org",
+                "enabled": True
+            }
+        ]
+    }
+
+    FAKE_USER_EC2_CREDENTIAL_INFO = {
+        "credential": {
+            'user_id': '9beb0e12f3e5416db8d7cccfc785db3b',
+            'access': '79abf59acc77492a86170cbe2f1feafa',
+            'secret': 'c4e7d3a691fd4563873d381a40320f46',
+            'trust_id': None,
+            'tenant_id': '596557269d7b4dd78631a602eb9f151d'
+        }
+    }
+
+    FAKE_LIST_USER_EC2_CREDENTIALS = {
+        "credentials": [
+            {
+                'user_id': '9beb0e12f3e5416db8d7cccfc785db3b',
+                'access': '79abf59acc77492a86170cbe2f1feafa',
+                'secret': 'c4e7d3a691fd4563873d381a40320f46',
+                'trust_id': None,
+                'tenant_id': '596557269d7b4dd78631a602eb9f151d'
+            },
+            {
+                'user_id': '3beb0e12f3e5416db8d7cccfc785de4r',
+                'access': '45abf59acc77492a86170cbe2f1fesde',
+                'secret': 'g4e7d3a691fd4563873d381a40320e45',
+                'trust_id': None,
+                'tenant_id': '123557269d7b4dd78631a602eb9f112f'
+            }
+        ]
+    }
+
+    def setUp(self):
+        super(TestUsersClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.client = users_client.UsersClient(fake_auth,
+                                               'identity', 'regionOne')
+
+    def _test_create_user(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.create_user,
+            'tempest.lib.common.rest_client.RestClient.post',
+            self.FAKE_USER_INFO,
+            bytes_body,
+            name="test",
+            email="john.smith@example.org")
+
+    def _test_update_user(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.update_user,
+            'tempest.lib.common.rest_client.RestClient.put',
+            self.FAKE_USER_INFO,
+            bytes_body,
+            user_id="1",
+            name="test")
+
+    def _test_show_user(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_user,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_USER_INFO,
+            bytes_body,
+            user_id="1")
+
+    def _test_list_users(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_users,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_LIST_USERS,
+            bytes_body)
+
+    def _test_update_user_enabled(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.update_user_enabled,
+            'tempest.lib.common.rest_client.RestClient.put',
+            self.FAKE_USER_INFO,
+            bytes_body,
+            user_id="1",
+            enabled=True)
+
+    def _test_update_user_password(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.update_user_password,
+            'tempest.lib.common.rest_client.RestClient.put',
+            self.FAKE_USER_INFO,
+            bytes_body,
+            user_id="1",
+            password="pass")
+
+    def _test_update_user_own_password(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.update_user_own_password,
+            'tempest.lib.common.rest_client.RestClient.patch',
+            self.FAKE_USER_INFO,
+            bytes_body,
+            user_id="1",
+            password="pass")
+
+    def _test_create_user_ec2_credential(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.create_user_ec2_credential,
+            'tempest.lib.common.rest_client.RestClient.post',
+            self.FAKE_USER_EC2_CREDENTIAL_INFO,
+            bytes_body,
+            user_id="1",
+            tenant_id="123")
+
+    def _test_show_user_ec2_credential(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.show_user_ec2_credential,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_USER_EC2_CREDENTIAL_INFO,
+            bytes_body,
+            user_id="1",
+            access="123")
+
+    def _test_list_user_ec2_credentials(self, bytes_body=False):
+        self.check_service_client_function(
+            self.client.list_user_ec2_credentials,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_LIST_USER_EC2_CREDENTIALS,
+            bytes_body,
+            user_id="1")
+
+    def test_create_user_with_str_body(self):
+        self._test_create_user()
+
+    def test_create_user_with_bytes_body(self):
+        self._test_create_user(bytes_body=True)
+
+    def test_update_user_with_str_body(self):
+        self._test_update_user()
+
+    def test_update_user_with_bytes_body(self):
+        self._test_update_user(bytes_body=True)
+
+    def test_show_user_with_str_body(self):
+        self._test_show_user()
+
+    def test_show_user_with_bytes_body(self):
+        self._test_show_user(bytes_body=True)
+
+    def test_list_users_with_str_body(self):
+        self._test_list_users()
+
+    def test_list_users_with_bytes_body(self):
+        self._test_list_users(bytes_body=True)
+
+    def test_delete_user(self):
+        self.check_service_client_function(
+            self.client.delete_user,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            user_id="1",
+            status=204)
+
+    def test_update_user_enabled_with_str_body(self):
+        self._test_update_user_enabled()
+
+    def test_update_user_enabled_with_bytes_body(self):
+        self._test_update_user_enabled(bytes_body=True)
+
+    def test_update_user_password_with_str_body(self):
+        self._test_update_user_password()
+
+    def test_update_user_password_with_bytes_body(self):
+        self._test_update_user_password(bytes_body=True)
+
+    def test_update_user_own_password_with_str_body(self):
+        self._test_update_user_own_password()
+
+    def test_update_user_own_password_with_bytes_body(self):
+        self._test_update_user_own_password(bytes_body=True)
+
+    def test_create_user_ec2_credential_with_str_body(self):
+        self._test_create_user_ec2_credential()
+
+    def test_create_user_ec2_credential_with_bytes_body(self):
+        self._test_create_user_ec2_credential(bytes_body=True)
+
+    def test_show_user_ec2_credential_with_str_body(self):
+        self._test_show_user_ec2_credential()
+
+    def test_show_user_ec2_credential_with_bytes_body(self):
+        self._test_show_user_ec2_credential(bytes_body=True)
+
+    def test_list_user_ec2_credentials_with_str_body(self):
+        self._test_list_user_ec2_credentials()
+
+    def test_list_user_ec2_credentials_with_bytes_body(self):
+        self._test_list_user_ec2_credentials(bytes_body=True)
+
+    def test_delete_user_ec2_credential(self):
+        self.check_service_client_function(
+            self.client.delete_user_ec2_credential,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            user_id="123",
+            access="1234",
+            status=204)
diff --git a/tempest/tests/services/identity/__init__.py b/tempest/tests/services/identity/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/tests/services/identity/__init__.py
+++ /dev/null
diff --git a/tempest/tests/services/identity/v2/__init__.py b/tempest/tests/services/identity/v2/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/tests/services/identity/v2/__init__.py
+++ /dev/null
diff --git a/test-requirements.txt b/test-requirements.txt
index a5b0d29..04c3d6d 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -3,10 +3,10 @@
 # process, which may cause wedges in the gate later.
 hacking<0.12,>=0.11.0 # Apache-2.0
 # needed for doc build
-sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 # BSD
+sphinx!=1.3b1,<1.3,>=1.2.1 # BSD
 python-subunit>=0.0.18 # Apache-2.0/BSD
 oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
-reno>=1.6.2 # Apache2
+reno>=1.8.0 # Apache2
 mock>=2.0 # BSD
 coverage>=3.6 # Apache-2.0
 oslotest>=1.10.0 # Apache-2.0