Added 2 user related testcases for Keystone V3API
This submission adds two test cases namely 'test_user_update' and
'test_list_user_projects' in script test_users.py. The suporting
methods are added to identity_client.py for JSON and XML calls.
Change-Id: I0a66b0c40bc275f4c0da3f57ea9c9d33cdeea455
Implements: blueprint keystone-v3-users-api-test
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")