Merge "Add a test for creation of volumes' snapshots."
diff --git a/README.rst b/README.rst
index 04a4b3e..1f44832 100644
--- a/README.rst
+++ b/README.rst
@@ -33,11 +33,6 @@
     devstack uploaded and set the image_ref value in the [environment]
     section in the tempest.conf to that image UUID.
 
-    In addition, the ``<devstack-repo>/tools/configure_tempest.sh`` script can
-    also be used to generate a tempest.conf based on your devstack's rc files.
-    TEMPEST_DIR variable points to location /opt/stack/temptest. Update this
-    variable if the location is different..
-
 Tempest is not tied to any single test runner, but Nose been the most commonly
 used tool. After setting up your configuration file, you can execute
 the set of Tempest tests by using ``nosetests`` ::
diff --git a/openstack-common.conf b/openstack-common.conf
index 501328c..24af119 100644
--- a/openstack-common.conf
+++ b/openstack-common.conf
@@ -1,7 +1,7 @@
 [DEFAULT]
 
 # The list of modules to copy from openstack-common
-modules=setup,install_venv_common
+modules=install_venv_common
 
 # The base module to hold the copy of openstack.common
 base=tempest
diff --git a/setup.cfg b/setup.cfg
index b522f3a..59050cd 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,3 +1,30 @@
+[metadata]
+name = tempest
+version = 2013.2
+summary = OpenStack Integration Testing
+description-file =
+    README.rst
+author = OpenStack QA
+author-email = openstack-qa@lists.openstack.org
+home-page = http://www.openstack.org/
+classifier =
+    Intended Audience :: Information Technology
+    Intended Audience :: System Administrators
+    Intended Audience :: Developers
+    License :: OSI Approved :: Apache Software License
+    Operating System :: POSIX :: Linux
+    Programming Language :: Python
+    Programming Language :: Python :: 2
+    Programming Language :: Python :: 2.7
+
+[global]
+setup-hooks =
+    pbr.hooks.setup_hook
+
+[files]
+scripts =
+    bin/tempest
+
 [nosetests]
 # NOTE(jkoelker) To run the test suite under nose install the following
 #                coverage http://pypi.python.org/pypi/coverage
diff --git a/setup.py b/setup.py
index 1507797..59a0090 100755
--- a/setup.py
+++ b/setup.py
@@ -1,51 +1,21 @@
 #!/usr/bin/env python
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2010 United States Government as represented by the
-# Administrator of the National Aeronautics and Space Administration.
-# All Rights Reserved.
+# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
 #
-#    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
+# 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
+#    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.
+# 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 setuptools
 
-from tempest.openstack.common import setup as common_setup
-
-requires = common_setup.parse_requirements()
-depend_links = common_setup.parse_dependency_links()
-
-setuptools.setup(name='tempest',
-                 version=common_setup.get_version('tempest', "2013.2"),
-                 description='Integration test tools',
-                 author='OpenStack',
-                 author_email='openstack-qa@lists.launchpad.net',
-                 url='http://www.openstack.org/',
-                 classifiers=['Environment :: OpenStack',
-                              'Intended Audience :: Information Technology',
-                              'Intended Audience :: System Administrators',
-                              'Intended Audience :: Developers',
-                              'License :: OSI Approved :'
-                              ': Apache Software License',
-                              'Operating System :: POSIX :: Linux',
-                              'Programming Language :: Python',
-                              'Programming Language :: Python :: 2',
-                              'Programming Language :: Python :: 2.7', ],
-                 cmdclass=common_setup.get_cmdclass(),
-                 packages=setuptools.find_packages(exclude=['bin']),
-                 install_requires=requires,
-                 dependency_links=depend_links,
-                 include_package_data=True,
-                 test_suite='nose.collector',
-                 setup_requires=['setuptools_git>=0.4'],
-                 scripts=['bin/tempest'],
-                 py_modules=[])
+setuptools.setup(
+    setup_requires=['d2to1', 'pbr'],
+    d2to1=True)
diff --git a/tempest/clients.py b/tempest/clients.py
index 732a982..2934f81 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -64,7 +64,11 @@
 from tempest.services.identity.json.identity_client import TokenClientJSON
 from tempest.services.identity.v3.json.endpoints_client import \
     EndPointClientJSON
+from tempest.services.identity.v3.json.identity_client import \
+    IdentityV3ClientJSON
 from tempest.services.identity.v3.xml.endpoints_client import EndPointClientXML
+from tempest.services.identity.v3.xml.identity_client import \
+    IdentityV3ClientXML
 from tempest.services.identity.xml.identity_client import IdentityClientXML
 from tempest.services.identity.xml.identity_client import TokenClientXML
 from tempest.services.image.v1.json.image_client import ImageClientJSON
@@ -153,6 +157,11 @@
     "xml": IdentityClientXML,
 }
 
+IDENTITY_V3_CLIENT = {
+    "json": IdentityV3ClientJSON,
+    "xml": IdentityV3ClientXML,
+}
+
 TOKEN_CLIENT = {
     "json": TokenClientJSON,
     "xml": TokenClientXML,
@@ -241,6 +250,8 @@
             self.volume_types_client = \
                 VOLUME_TYPES_CLIENTS[interface](*client_args)
             self.identity_client = IDENTITY_CLIENT[interface](*client_args)
+            self.identity_v3_client = \
+                IDENTITY_V3_CLIENT[interface](*client_args)
             self.token_client = TOKEN_CLIENT[interface](self.config)
             self.security_groups_client = \
                 SECURITY_GROUPS_CLIENT[interface](*client_args)
diff --git a/tempest/openstack/common/__init__.py b/tempest/openstack/common/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/openstack/common/__init__.py
+++ /dev/null
diff --git a/tempest/openstack/common/setup.py b/tempest/openstack/common/setup.py
deleted file mode 100644
index 80a0ece..0000000
--- a/tempest/openstack/common/setup.py
+++ /dev/null
@@ -1,359 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2011 OpenStack LLC.
-# Copyright 2012-2013 Hewlett-Packard Development Company, L.P.
-# 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.
-
-"""
-Utilities with minimum-depends for use in setup.py
-"""
-
-import email
-import os
-import re
-import subprocess
-import sys
-
-from setuptools.command import sdist
-
-
-def parse_mailmap(mailmap='.mailmap'):
-    mapping = {}
-    if os.path.exists(mailmap):
-        with open(mailmap, 'r') as fp:
-            for l in fp:
-                try:
-                    canonical_email, alias = re.match(
-                        r'[^#]*?(<.+>).*(<.+>).*', l).groups()
-                except AttributeError:
-                    continue
-                mapping[alias] = canonical_email
-    return mapping
-
-
-def _parse_git_mailmap(git_dir, mailmap='.mailmap'):
-    mailmap = os.path.join(os.path.dirname(git_dir), mailmap)
-    return parse_mailmap(mailmap)
-
-
-def canonicalize_emails(changelog, mapping):
-    """Takes in a string and an email alias mapping and replaces all
-       instances of the aliases in the string with their real email.
-    """
-    for alias, email_address in mapping.iteritems():
-        changelog = changelog.replace(alias, email_address)
-    return changelog
-
-
-# Get requirements from the first file that exists
-def get_reqs_from_files(requirements_files):
-    for requirements_file in requirements_files:
-        if os.path.exists(requirements_file):
-            with open(requirements_file, 'r') as fil:
-                return fil.read().split('\n')
-    return []
-
-
-def parse_requirements(requirements_files=['requirements.txt',
-                                           'tools/pip-requires']):
-    requirements = []
-    for line in get_reqs_from_files(requirements_files):
-        # For the requirements list, we need to inject only the portion
-        # after egg= so that distutils knows the package it's looking for
-        # such as:
-        # -e git://github.com/openstack/nova/master#egg=nova
-        if re.match(r'\s*-e\s+', line):
-            requirements.append(re.sub(r'\s*-e\s+.*#egg=(.*)$', r'\1',
-                                line))
-        # such as:
-        # http://github.com/openstack/nova/zipball/master#egg=nova
-        elif re.match(r'\s*https?:', line):
-            requirements.append(re.sub(r'\s*https?:.*#egg=(.*)$', r'\1',
-                                line))
-        # -f lines are for index locations, and don't get used here
-        elif re.match(r'\s*-f\s+', line):
-            pass
-        # argparse is part of the standard library starting with 2.7
-        # adding it to the requirements list screws distro installs
-        elif line == 'argparse' and sys.version_info >= (2, 7):
-            pass
-        else:
-            requirements.append(line)
-
-    return requirements
-
-
-def parse_dependency_links(requirements_files=['requirements.txt',
-                                               'tools/pip-requires']):
-    dependency_links = []
-    # dependency_links inject alternate locations to find packages listed
-    # in requirements
-    for line in get_reqs_from_files(requirements_files):
-        # skip comments and blank lines
-        if re.match(r'(\s*#)|(\s*$)', line):
-            continue
-        # lines with -e or -f need the whole line, minus the flag
-        if re.match(r'\s*-[ef]\s+', line):
-            dependency_links.append(re.sub(r'\s*-[ef]\s+', '', line))
-        # lines that are only urls can go in unmolested
-        elif re.match(r'\s*https?:', line):
-            dependency_links.append(line)
-    return dependency_links
-
-
-def _run_shell_command(cmd, throw_on_error=False):
-    if os.name == 'nt':
-        output = subprocess.Popen(["cmd.exe", "/C", cmd],
-                                  stdout=subprocess.PIPE,
-                                  stderr=subprocess.PIPE)
-    else:
-        output = subprocess.Popen(["/bin/sh", "-c", cmd],
-                                  stdout=subprocess.PIPE,
-                                  stderr=subprocess.PIPE)
-    out = output.communicate()
-    if output.returncode and throw_on_error:
-        raise Exception("%s returned %d" % cmd, output.returncode)
-    if len(out) == 0:
-        return None
-    if len(out[0].strip()) == 0:
-        return None
-    return out[0].strip()
-
-
-def _get_git_directory():
-    parent_dir = os.path.dirname(__file__)
-    while True:
-        git_dir = os.path.join(parent_dir, '.git')
-        if os.path.exists(git_dir):
-            return git_dir
-        parent_dir, child = os.path.split(parent_dir)
-        if not child:   # reached to root dir
-            return None
-
-
-def write_git_changelog():
-    """Write a changelog based on the git changelog."""
-    new_changelog = 'ChangeLog'
-    git_dir = _get_git_directory()
-    if not os.getenv('SKIP_WRITE_GIT_CHANGELOG'):
-        if git_dir:
-            git_log_cmd = 'git --git-dir=%s log --stat' % git_dir
-            changelog = _run_shell_command(git_log_cmd)
-            mailmap = _parse_git_mailmap(git_dir)
-            with open(new_changelog, "w") as changelog_file:
-                changelog_file.write(canonicalize_emails(changelog, mailmap))
-    else:
-        open(new_changelog, 'w').close()
-
-
-def generate_authors():
-    """Create AUTHORS file using git commits."""
-    jenkins_email = 'jenkins@review.(openstack|stackforge).org'
-    old_authors = 'AUTHORS.in'
-    new_authors = 'AUTHORS'
-    git_dir = _get_git_directory()
-    if not os.getenv('SKIP_GENERATE_AUTHORS'):
-        if git_dir:
-            # don't include jenkins email address in AUTHORS file
-            git_log_cmd = ("git --git-dir=" + git_dir +
-                           " log --format='%aN <%aE>' | sort -u | "
-                           "egrep -v '" + jenkins_email + "'")
-            changelog = _run_shell_command(git_log_cmd)
-            mailmap = _parse_git_mailmap(git_dir)
-            with open(new_authors, 'w') as new_authors_fh:
-                new_authors_fh.write(canonicalize_emails(changelog, mailmap))
-                if os.path.exists(old_authors):
-                    with open(old_authors, "r") as old_authors_fh:
-                        new_authors_fh.write('\n' + old_authors_fh.read())
-    else:
-        open(new_authors, 'w').close()
-
-
-_rst_template = """%(heading)s
-%(underline)s
-
-.. automodule:: %(module)s
-  :members:
-  :undoc-members:
-  :show-inheritance:
-"""
-
-
-def get_cmdclass():
-    """Return dict of commands to run from setup.py."""
-
-    cmdclass = dict()
-
-    def _find_modules(arg, dirname, files):
-        for filename in files:
-            if filename.endswith('.py') and filename != '__init__.py':
-                arg["%s.%s" % (dirname.replace('/', '.'),
-                               filename[:-3])] = True
-
-    class LocalSDist(sdist.sdist):
-        """Builds the ChangeLog and Authors files from VC first."""
-
-        def run(self):
-            write_git_changelog()
-            generate_authors()
-            # sdist.sdist is an old style class, can't use super()
-            sdist.sdist.run(self)
-
-    cmdclass['sdist'] = LocalSDist
-
-    # If Sphinx is installed on the box running setup.py,
-    # enable setup.py to build the documentation, otherwise,
-    # just ignore it
-    try:
-        from sphinx.setup_command import BuildDoc
-
-        class LocalBuildDoc(BuildDoc):
-
-            builders = ['html', 'man']
-
-            def generate_autoindex(self):
-                print "**Autodocumenting from %s" % os.path.abspath(os.curdir)
-                modules = {}
-                option_dict = self.distribution.get_option_dict('build_sphinx')
-                source_dir = os.path.join(option_dict['source_dir'][1], 'api')
-                if not os.path.exists(source_dir):
-                    os.makedirs(source_dir)
-                for pkg in self.distribution.packages:
-                    if '.' not in pkg:
-                        os.path.walk(pkg, _find_modules, modules)
-                module_list = modules.keys()
-                module_list.sort()
-                autoindex_filename = os.path.join(source_dir, 'autoindex.rst')
-                with open(autoindex_filename, 'w') as autoindex:
-                    autoindex.write(""".. toctree::
-   :maxdepth: 1
-
-""")
-                    for module in module_list:
-                        output_filename = os.path.join(source_dir,
-                                                       "%s.rst" % module)
-                        heading = "The :mod:`%s` Module" % module
-                        underline = "=" * len(heading)
-                        values = dict(module=module, heading=heading,
-                                      underline=underline)
-
-                        print "Generating %s" % output_filename
-                        with open(output_filename, 'w') as output_file:
-                            output_file.write(_rst_template % values)
-                        autoindex.write("   %s.rst\n" % module)
-
-            def run(self):
-                if not os.getenv('SPHINX_DEBUG'):
-                    self.generate_autoindex()
-
-                for builder in self.builders:
-                    self.builder = builder
-                    self.finalize_options()
-                    self.project = self.distribution.get_name()
-                    self.version = self.distribution.get_version()
-                    self.release = self.distribution.get_version()
-                    BuildDoc.run(self)
-
-        class LocalBuildLatex(LocalBuildDoc):
-            builders = ['latex']
-
-        cmdclass['build_sphinx'] = LocalBuildDoc
-        cmdclass['build_sphinx_latex'] = LocalBuildLatex
-    except ImportError:
-        pass
-
-    return cmdclass
-
-
-def _get_revno(git_dir):
-    """Return the number of commits since the most recent tag.
-
-    We use git-describe to find this out, but if there are no
-    tags then we fall back to counting commits since the beginning
-    of time.
-    """
-    describe = _run_shell_command(
-        "git --git-dir=%s describe --always" % git_dir)
-    if "-" in describe:
-        return describe.rsplit("-", 2)[-2]
-
-    # no tags found
-    revlist = _run_shell_command(
-        "git --git-dir=%s rev-list --abbrev-commit HEAD" % git_dir)
-    return len(revlist.splitlines())
-
-
-def _get_version_from_git(pre_version):
-    """Return a version which is equal to the tag that's on the current
-    revision if there is one, or tag plus number of additional revisions
-    if the current revision has no tag."""
-
-    git_dir = _get_git_directory()
-    if git_dir:
-        if pre_version:
-            try:
-                return _run_shell_command(
-                    "git --git-dir=" + git_dir + " describe --exact-match",
-                    throw_on_error=True).replace('-', '.')
-            except Exception:
-                sha = _run_shell_command(
-                    "git --git-dir=" + git_dir + " log -n1 --pretty=format:%h")
-                return "%s.a%s.g%s" % (pre_version, _get_revno(git_dir), sha)
-        else:
-            return _run_shell_command(
-                "git --git-dir=" + git_dir + " describe --always").replace(
-                    '-', '.')
-    return None
-
-
-def _get_version_from_pkg_info(package_name):
-    """Get the version from PKG-INFO file if we can."""
-    try:
-        pkg_info_file = open('PKG-INFO', 'r')
-    except (IOError, OSError):
-        return None
-    try:
-        pkg_info = email.message_from_file(pkg_info_file)
-    except email.MessageError:
-        return None
-    # Check to make sure we're in our own dir
-    if pkg_info.get('Name', None) != package_name:
-        return None
-    return pkg_info.get('Version', None)
-
-
-def get_version(package_name, pre_version=None):
-    """Get the version of the project. First, try getting it from PKG-INFO, if
-    it exists. If it does, that means we're in a distribution tarball or that
-    install has happened. Otherwise, if there is no PKG-INFO file, pull the
-    version from git.
-
-    We do not support setup.py version sanity in git archive tarballs, nor do
-    we support packagers directly sucking our git repo into theirs. We expect
-    that a source tarball be made from our git repo - or that if someone wants
-    to make a source tarball from a fork of our repo with additional tags in it
-    that they understand and desire the results of doing that.
-    """
-    version = os.environ.get("OSLO_PACKAGE_VERSION", None)
-    if version:
-        return version
-    version = _get_version_from_pkg_info(package_name)
-    if version:
-        return version
-    version = _get_version_from_git(pre_version)
-    if version:
-        return version
-    raise Exception("Versioning for this project requires either an sdist"
-                    " tarball, or access to an upstream git repository.")
diff --git a/tempest/services/identity/json/identity_client.py b/tempest/services/identity/json/identity_client.py
index c806949..a216b55 100644
--- a/tempest/services/identity/json/identity_client.py
+++ b/tempest/services/identity/json/identity_client.py
@@ -138,6 +138,12 @@
         body = json.loads(body)
         return resp, body['user']
 
+    def get_user(self, user_id):
+        """GET a user."""
+        resp, body = self.get("users/%s" % user_id)
+        body = json.loads(body)
+        return resp, body['user']
+
     def delete_user(self, user_id):
         """Delete a user."""
         resp, body = self.delete("users/%s" % user_id)
diff --git a/tempest/services/identity/v3/json/identity_client.py b/tempest/services/identity/v3/json/identity_client.py
new file mode 100644
index 0000000..014df1e
--- /dev/null
+++ b/tempest/services/identity/v3/json/identity_client.py
@@ -0,0 +1,162 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+# 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 json
+from urlparse import urlparse
+
+from tempest.common.rest_client import RestClient
+
+
+class IdentityV3ClientJSON(RestClient):
+
+    def __init__(self, config, username, password, auth_url, tenant_name=None):
+        super(IdentityV3ClientJSON, self).__init__(config, username, password,
+                                                   auth_url, tenant_name)
+        self.service = self.config.identity.catalog_type
+        self.endpoint_url = 'adminURL'
+
+    def request(self, method, url, headers=None, body=None, wait=None):
+        """Overriding the existing HTTP request in super class rest_client."""
+        self._set_auth()
+        self.base_url = self.base_url.replace(urlparse(self.base_url).path,
+                                              "/v3")
+        return super(IdentityV3ClientJSON, self).request(method, url,
+                                                         headers=headers,
+                                                         body=body)
+
+    def create_user(self, user_name, **kwargs):
+        """Creates a user."""
+        password = kwargs.get('password', None)
+        email = kwargs.get('email', None)
+        en = kwargs.get('enabled', True)
+        project_id = kwargs.get('project_id', None)
+        description = kwargs.get('description', None)
+        domain_id = kwargs.get('domain_id', 'default')
+        post_body = {
+            'project_id': project_id,
+            'description': description,
+            'domain_id': domain_id,
+            'email': email,
+            'enabled': en,
+            'name': user_name,
+            'password': password
+        }
+        post_body = json.dumps({'user': post_body})
+        resp, body = self.post('users', post_body,
+                               self.headers)
+        body = json.loads(body)
+        return resp, body['user']
+
+    def update_user(self, user_id, name, **kwargs):
+        """Updates a user."""
+        email = kwargs.get('email', None)
+        en = kwargs.get('enabled', True)
+        project_id = kwargs.get('project_id', None)
+        description = kwargs.get('description', None)
+        domain_id = kwargs.get('domain_id', 'default')
+        post_body = {
+            'name': name,
+            'email': email,
+            'enabled': en,
+            'project_id': project_id,
+            'id': user_id,
+            'domain_id': domain_id,
+            'description': description
+        }
+        post_body = json.dumps({'user': post_body})
+        resp, body = self.patch('users/%s' % user_id, post_body,
+                                self.headers)
+        body = json.loads(body)
+        return resp, body['user']
+
+    def list_user_projects(self, user_id):
+        """Lists the projects on which a user has roles assigned."""
+        resp, body = self.get('users/%s/projects' % user_id, self.headers)
+        body = json.loads(body)
+        return resp, body['projects']
+
+    def get_users(self):
+        """Get the list of users."""
+        resp, body = self.get("users")
+        body = json.loads(body)
+        return resp, body['users']
+
+    def get_user(self, user_id):
+        """GET a user."""
+        resp, body = self.get("users/%s" % user_id)
+        body = json.loads(body)
+        return resp, body['user']
+
+    def delete_user(self, user_id):
+        """Deletes a User."""
+        resp, body = self.delete("users/%s" % user_id)
+        return resp, body
+
+    def create_project(self, name, **kwargs):
+        """Creates a project."""
+        description = kwargs.get('description', None)
+        en = kwargs.get('enabled', True)
+        domain_id = kwargs.get('domain_id', 'default')
+        post_body = {
+            'description': description,
+            'domain_id': domain_id,
+            'enabled': en,
+            'name': name
+        }
+        post_body = json.dumps({'project': post_body})
+        resp, body = self.post('projects', post_body, self.headers)
+        body = json.loads(body)
+        return resp, body['project']
+
+    def get_project(self, project_id):
+        """GET a Project."""
+        resp, body = self.get("projects/%s" % project_id)
+        body = json.loads(body)
+        return resp, body['project']
+
+    def delete_project(self, project_id):
+        """Delete a project."""
+        resp, body = self.delete('projects/%s' % str(project_id))
+        return resp, body
+
+    def create_role(self, name):
+        """Create a Role."""
+        post_body = {
+            'name': name
+        }
+        post_body = json.dumps({'role': post_body})
+        resp, body = self.post('roles', post_body, self.headers)
+        body = json.loads(body)
+        return resp, body['role']
+
+    def get_role(self, role_id):
+        """GET a Role."""
+        resp, body = self.get('roles/%s' % str(role_id))
+        body = json.loads(body)
+        return resp, body['role']
+
+    def delete_role(self, role_id):
+        """Delete a role."""
+        resp, body = self.delete('roles/%s' % str(role_id))
+        return resp, body
+
+    def assign_user_role(self, project_id, user_id, role_id):
+        """Add roles to a user on a project."""
+        resp, body = self.put('projects/%s/users/%s/roles/%s' %
+                              (project_id, user_id, role_id), None,
+                              self.headers)
+        return resp, body
diff --git a/tempest/services/identity/v3/xml/identity_client.py b/tempest/services/identity/v3/xml/identity_client.py
new file mode 100644
index 0000000..92151dd
--- /dev/null
+++ b/tempest/services/identity/v3/xml/identity_client.py
@@ -0,0 +1,187 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 OpenStack Foundation
+# 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 urlparse import urlparse
+
+from lxml import etree
+
+from tempest.common.rest_client import RestClientXML
+from tempest.services.compute.xml.common import Document
+from tempest.services.compute.xml.common import Element
+from tempest.services.compute.xml.common import xml_to_json
+
+
+XMLNS = "http://docs.openstack.org/identity/api/v3"
+
+
+class IdentityV3ClientXML(RestClientXML):
+
+    def __init__(self, config, username, password, auth_url, tenant_name=None):
+        super(IdentityV3ClientXML, self).__init__(config, username, password,
+                                                  auth_url, tenant_name)
+        self.service = self.config.identity.catalog_type
+        self.endpoint_url = 'adminURL'
+
+    def _parse_projects(self, node):
+        array = []
+        for child in node.getchildren():
+            tag_list = child.tag.split('}', 1)
+            if tag_list[1] == "project":
+                array.append(xml_to_json(child))
+        return array
+
+    def _parse_array(self, node):
+        array = []
+        for child in node.getchildren():
+            array.append(xml_to_json(child))
+        return array
+
+    def _parse_body(self, body):
+        json = xml_to_json(body)
+        return json
+
+    def request(self, method, url, headers=None, body=None, wait=None):
+        """Overriding the existing HTTP request in super class RestClient."""
+        self._set_auth()
+        self.base_url = self.base_url.replace(urlparse(self.base_url).path,
+                                              "/v3")
+        return super(IdentityV3ClientXML, self).request(method, url,
+                                                        headers=headers,
+                                                        body=body)
+
+    def create_user(self, user_name, **kwargs):
+        """Creates a user."""
+        password = kwargs.get('password', None)
+        email = kwargs.get('email', None)
+        en = kwargs.get('enabled', 'true')
+        project_id = kwargs.get('project_id', None)
+        description = kwargs.get('description', None)
+        domain_id = kwargs.get('domain_id', 'default')
+        post_body = Element("user",
+                            xmlns=XMLNS,
+                            name=user_name,
+                            password=password,
+                            description=description,
+                            email=email,
+                            enabled=str(en).lower(),
+                            project_id=project_id,
+                            domain_id=domain_id)
+        resp, body = self.post('users', str(Document(post_body)),
+                               self.headers)
+        body = self._parse_body(etree.fromstring(body))
+        return resp, body
+
+    def update_user(self, user_id, name, **kwargs):
+        """Updates a user."""
+        email = kwargs.get('email', None)
+        en = kwargs.get('enabled', True)
+        project_id = kwargs.get('project_id', None)
+        domain_id = kwargs.get('domain_id', 'default')
+        description = kwargs.get('description', None)
+        update_user = Element("user",
+                              xmlns=XMLNS,
+                              name=name,
+                              email=email,
+                              project_id=project_id,
+                              domain_id=domain_id,
+                              description=description,
+                              enabled=str(en).lower())
+        resp, body = self.patch('users/%s' % user_id,
+                                str(Document(update_user)),
+                                self.headers)
+        body = self._parse_body(etree.fromstring(body))
+        return resp, body
+
+    def list_user_projects(self, user_id):
+        """Lists the projects on which a user has roles assigned."""
+        resp, body = self.get('users/%s/projects' % user_id, self.headers)
+        body = self._parse_projects(etree.fromstring(body))
+        return resp, body
+
+    def get_users(self):
+        """Get the list of users."""
+        resp, body = self.get("users", self.headers)
+        body = self._parse_array(etree.fromstring(body))
+        return resp, body
+
+    def get_user(self, user_id):
+        """GET a user."""
+        resp, body = self.get("users/%s" % user_id, self.headers)
+        body = self._parse_body(etree.fromstring(body))
+        return resp, body
+
+    def delete_user(self, user_id):
+        """Deletes a User."""
+        resp, body = self.delete("users/%s" % user_id, self.headers)
+        return resp, body
+
+    def create_project(self, name, **kwargs):
+        """Creates a project."""
+        description = kwargs.get('description', None)
+        en = kwargs.get('enabled', 'true')
+        domain_id = kwargs.get('domain_id', 'default')
+        post_body = Element("project",
+                            xmlns=XMLNS,
+                            description=description,
+                            domain_id=domain_id,
+                            enabled=str(en).lower(),
+                            name=name)
+        resp, body = self.post('projects',
+                               str(Document(post_body)),
+                               self.headers)
+        body = self._parse_body(etree.fromstring(body))
+        return resp, body
+
+    def get_project(self, project_id):
+        """GET a Project."""
+        resp, body = self.get("projects/%s" % project_id, self.headers)
+        body = self._parse_body(etree.fromstring(body))
+        return resp, body
+
+    def delete_project(self, project_id):
+        """Delete a project."""
+        resp, body = self.delete('projects/%s' % str(project_id))
+        return resp, body
+
+    def create_role(self, name):
+        """Create a Role."""
+        post_body = Element("role",
+                            xmlns=XMLNS,
+                            name=name)
+        resp, body = self.post('roles',
+                               str(Document(post_body)),
+                               self.headers)
+        body = self._parse_body(etree.fromstring(body))
+        return resp, body
+
+    def get_role(self, role_id):
+        """GET a Role."""
+        resp, body = self.get('roles/%s' % str(role_id), self.headers)
+        body = self._parse_body(etree.fromstring(body))
+        return resp, body
+
+    def delete_role(self, role_id):
+        """Delete a role."""
+        resp, body = self.delete('roles/%s' % str(role_id),
+                                 self.headers)
+        return resp, body
+
+    def assign_user_role(self, project_id, user_id, role_id):
+        """Add roles to a user on a tenant."""
+        resp, body = self.put('projects/%s/users/%s/roles/%s' %
+                              (project_id, user_id, role_id), '', self.headers)
+        return resp, body
diff --git a/tempest/services/identity/xml/identity_client.py b/tempest/services/identity/xml/identity_client.py
index 6f1b1b3..99a155a 100644
--- a/tempest/services/identity/xml/identity_client.py
+++ b/tempest/services/identity/xml/identity_client.py
@@ -172,6 +172,12 @@
         body = self._parse_body(etree.fromstring(body))
         return resp, body
 
+    def get_user(self, user_id):
+        """GET a user."""
+        resp, body = self.get("users/%s" % user_id, self.headers)
+        body = self._parse_body(etree.fromstring(body))
+        return resp, body
+
     def delete_user(self, user_id):
         """Delete a user."""
         resp, body = self.delete("users/%s" % user_id, self.headers)
diff --git a/tempest/tests/compute/test_live_block_migration.py b/tempest/tests/compute/test_live_block_migration.py
index e438098..30ff882 100644
--- a/tempest/tests/compute/test_live_block_migration.py
+++ b/tempest/tests/compute/test_live_block_migration.py
@@ -31,11 +31,7 @@
     _host_key = 'OS-EXT-SRV-ATTR:host'
     _interface = 'json'
 
-    live_migration_available = (
-        config.TempestConfig().compute.live_migration_available)
-    use_block_migration_for_live_migration = (
-        config.TempestConfig().compute.use_block_migration_for_live_migration)
-    run_ssh = config.TempestConfig().compute.run_ssh
+    CONF = config.TempestConfig()
 
     @classmethod
     def setUpClass(cls):
@@ -63,7 +59,8 @@
 
     def _migrate_server_to(self, server_id, dest_host):
         _resp, body = self.admin_servers_client.live_migrate_server(
-            server_id, dest_host, self.use_block_migration_for_live_migration)
+            server_id, dest_host,
+            self.config.compute.use_block_migration_for_live_migration)
         return body
 
     def _get_host_other_than(self, host):
@@ -95,8 +92,8 @@
             return server_id
 
     @attr(type='positive')
-    @testtools.skipIf(not live_migration_available,
-                      'Block Live migration not available')
+    @testtools.skipIf(not CONF.compute.live_migration_available,
+                      'Live migration not available')
     def test_live_block_migration(self):
         # Live block migrate an instance to another host
         if len(self._get_compute_hostnames()) < 2:
@@ -109,11 +106,10 @@
         self.servers_client.wait_for_server_status(server_id, 'ACTIVE')
         self.assertEquals(target_host, self._get_host_for_server(server_id))
 
-    @testtools.skipIf(not live_migration_available,
-                      'Block Live migration not available')
+    @testtools.skipIf(not CONF.compute.live_migration_available,
+                      'Live migration not available')
     def test_invalid_host_for_migration(self):
         # Migrating to an invalid host should not change the status
-
         server_id = self._get_an_active_server()
         target_host = self._get_non_existing_host_name()
 
diff --git a/tempest/tests/identity/admin/v3/test_users.py b/tempest/tests/identity/admin/v3/test_users.py
new file mode 100644
index 0000000..7118241
--- /dev/null
+++ b/tempest/tests/identity/admin/v3/test_users.py
@@ -0,0 +1,121 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+# 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.common.utils.data_utils import rand_name
+from tempest.test import attr
+from tempest.tests.identity import base
+
+
+class UsersV3TestJSON(base.BaseIdentityAdminTest):
+    _interface = 'json'
+
+    @attr('smoke')
+    def test_user_update(self):
+        # Test case to check if updating of user attributes is successful.
+        #Creating first user
+        u_name = rand_name('user-')
+        u_desc = u_name + 'description'
+        u_email = u_name + '@testmail.tm'
+        u_password = rand_name('pass-')
+        resp, user = self.v3_client.create_user(
+            u_name, description=u_desc, password=u_password,
+            email=u_email, enabled=False)
+        # Delete the User at the end of this method
+        self.addCleanup(self.v3_client.delete_user, user['id'])
+        #Creating second project for updation
+        resp, project = self.v3_client.create_project(
+            rand_name('project-'), description=rand_name('project-desc-'))
+        # Delete the Project at the end of this method
+        self.addCleanup(self.v3_client.delete_project, project['id'])
+        #Updating user details with new values
+        u_name2 = rand_name('user2-')
+        u_email2 = u_name2 + '@testmail.tm'
+        u_description2 = u_name2 + ' description'
+        resp, update_user = self.v3_client.update_user(
+            user['id'], name=u_name2, description=u_description2,
+            project_id=project['id'],
+            email=u_email2, enabled=False)
+        #Assert response body of update user.
+        self.assertEqual(200, resp.status)
+        self.assertEqual(u_name2, update_user['name'])
+        self.assertEqual(u_description2, update_user['description'])
+        self.assertEqual(project['id'],
+                         update_user['project_id'])
+        self.assertEqual(u_email2, update_user['email'])
+        self.assertEqual('false', str(update_user['enabled']).lower())
+        #GET by id after updation
+        resp, new_user_get = self.v3_client.get_user(user['id'])
+        #Assert response body of GET after updation
+        self.assertEqual(u_name2, new_user_get['name'])
+        self.assertEqual(u_description2, new_user_get['description'])
+        self.assertEqual(project['id'],
+                         new_user_get['project_id'])
+        self.assertEqual(u_email2, new_user_get['email'])
+        self.assertEqual('false', str(new_user_get['enabled']).lower())
+
+    @attr('smoke')
+    def test_list_user_projects(self):
+        #List the projects that a user has access upon
+        assigned_project_ids = list()
+        fetched_project_ids = list()
+        _, u_project = self.v3_client.create_project(
+            rand_name('project-'), description=rand_name('project-desc-'))
+        #Create a user.
+        u_name = rand_name('user-')
+        u_desc = u_name + 'description'
+        u_email = u_name + '@testmail.tm'
+        u_password = rand_name('pass-')
+        _, user_body = self.v3_client.create_user(
+            u_name, description=u_desc, password=u_password,
+            email=u_email, enabled=False, project_id=u_project['id'])
+        # Delete the User at the end of this method
+        self.addCleanup(self.v3_client.delete_user, user_body['id'])
+        # Creating Role
+        _, role_body = self.v3_client.create_role(rand_name('role-'))
+        # Delete the Role at the end of this method
+        self.addCleanup(self.v3_client.delete_role, role_body['id'])
+
+        _, user = self.v3_client.get_user(user_body['id'])
+        _, role = self.v3_client.get_role(role_body['id'])
+        for i in range(2):
+            # Creating project so as to assign role
+            _, project_body = self.v3_client.create_project(
+                rand_name('project-'), description=rand_name('project-desc-'))
+            _, project = self.v3_client.get_project(project_body['id'])
+            # Delete the Project at the end of this method
+            self.addCleanup(self.v3_client.delete_project, project_body['id'])
+            #Assigning roles to user on project
+            self.v3_client.assign_user_role(project['id'],
+                                            user['id'],
+                                            role['id'])
+            assigned_project_ids.append(project['id'])
+        resp, body = self.v3_client.list_user_projects(user['id'])
+        self.assertEqual(200, resp.status)
+        for i in body:
+            fetched_project_ids.append(i['id'])
+        #verifying the project ids in list
+        missing_projects =\
+            [p for p in assigned_project_ids
+             if p not in fetched_project_ids]
+        self.assertEqual(0, len(missing_projects),
+                         "Failed to find project %s in fetched list" %
+                         ', '.join(m_project for m_project
+                         in missing_projects))
+
+
+class UsersV3TestXML(UsersV3TestJSON):
+    _interface = 'xml'
diff --git a/tempest/tests/identity/base.py b/tempest/tests/identity/base.py
index 64b8993..718b74f 100644
--- a/tempest/tests/identity/base.py
+++ b/tempest/tests/identity/base.py
@@ -29,6 +29,7 @@
         cls.client = os.identity_client
         cls.token_client = os.token_client
         cls.endpoints_client = os.endpoints_client
+        cls.v3_client = os.identity_v3_client
 
         if not cls.client.has_admin_extensions():
             raise cls.skipException("Admin extensions disabled")
diff --git a/tools/pip-requires b/tools/pip-requires
index 758442c..4873d75 100644
--- a/tools/pip-requires
+++ b/tools/pip-requires
@@ -1,3 +1,5 @@
+d2to1>=0.2.10,<0.3
+pbr>=0.5,<0.6
 anyjson
 nose
 httplib2>=0.7.0