Merge "Added 2 user related testcases for Keystone V3API"
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/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/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")