Merge remote-tracking branch 'origin/feature/keystone-v3' into HEAD

Conflicts:
	tests/v2_0/test_tenants.py

Change-Id: I37037e60210edd574da86b1dc07aa73e6761e338
This commit is contained in:
Dolph Mathews
2012-10-30 22:22:04 +00:00
23 changed files with 1554 additions and 6 deletions

View File

@@ -18,6 +18,8 @@
Base utilities to build API operation managers and objects on top of.
"""
import urllib
from keystoneclient import exceptions
@@ -76,20 +78,25 @@ class Manager(object):
def _get(self, url, response_key):
resp, body = self.api.get(url)
return self.resource_class(self, body[response_key])
return self.resource_class(self, body[response_key], loaded=True)
def _head(self, url):
resp, body = self.api.head(url)
return resp.status == 204
def _create(self, url, body, response_key, return_raw=False):
resp, body = self.api.post(url, body=body)
if return_raw:
return body[response_key]
return self.resource_class(self, body[response_key])
return self.resource_class(self, body[response_key], loaded=True)
def _delete(self, url):
resp, body = self.api.delete(url)
def _update(self, url, body, response_key=None, method="PUT"):
def _update(self, url, body=None, response_key=None, method="PUT"):
methods = {"PUT": self.api.put,
"POST": self.api.post}
"POST": self.api.post,
"PATCH": self.api.patch}
try:
if body is not None:
resp, body = methods[method](url, body=body)
@@ -100,7 +107,7 @@ class Manager(object):
% method)
# PUT requests may not return a body
if body:
return self.resource_class(self, body[response_key])
return self.resource_class(self, body[response_key], loaded=True)
class ManagerWithFind(Manager):
@@ -142,6 +149,115 @@ class ManagerWithFind(Manager):
return found
class CrudManager(Manager):
"""Base manager class for manipulating Keystone entities.
Children of this class are expected to define a `collection_key` and `key`.
- `collection_key`: Usually a plural noun by convention (e.g. `entities`);
used to refer collections in both URL's (e.g. `/v3/entities`) and JSON
objects containing a list of member resources (e.g. `{'entities': [{},
{}, {}]}`).
- `key`: Usually a singular noun by convention (e.g. `entity`); used to
refer to an individual member of the collection.
"""
collection_key = None
key = None
def build_url(self, base_url=None, **kwargs):
"""Builds a resource URL for the given kwargs.
Given an example collection where `collection_key = 'entities'` and
`key = 'entity'`, the following URL's could be generated.
By default, the URL will represent a collection of entities, e.g.::
/entities
If kwargs contains an `entity_id`, then the URL will represent a
specific member, e.g.::
/entities/{entity_id}
If a `base_url` is provided, the generated URL will be appended to it.
"""
url = base_url if base_url is not None else ''
url += '/%s' % self.collection_key
# do we have a specific entity?
entity_id = kwargs.get('%s_id' % self.key)
if entity_id is not None:
url += '/%s' % entity_id
return url
def _filter_kwargs(self, kwargs):
# drop null values
for key, ref in kwargs.copy().iteritems():
if ref is None:
kwargs.pop(key)
else:
id_value = getid(ref)
if id_value != ref:
kwargs.pop(key)
kwargs['%s_id' % key] = id_value
return kwargs
def create(self, **kwargs):
kwargs = self._filter_kwargs(kwargs)
return self._create(
self.build_url(**kwargs),
{self.key: kwargs},
self.key)
def get(self, **kwargs):
kwargs = self._filter_kwargs(kwargs)
return self._get(
self.build_url(**kwargs),
self.key)
def head(self, **kwargs):
kwargs = self._filter_kwargs(kwargs)
return self._head(self.build_url(**kwargs))
def list(self, base_url=None, **kwargs):
kwargs = self._filter_kwargs(kwargs)
return self._list(
'%(base_url)s%(query)s' % {
'base_url': self.build_url(base_url=base_url, **kwargs),
'query': '?%s' % urllib.urlencode(kwargs) if kwargs else '',
},
self.collection_key)
def put(self, base_url=None, **kwargs):
kwargs = self._filter_kwargs(kwargs)
return self._update(
self.build_url(base_url=base_url, **kwargs),
method='PUT')
def update(self, **kwargs):
kwargs = self._filter_kwargs(kwargs)
params = kwargs.copy()
params.pop('%s_id' % self.key)
return self._update(
self.build_url(**kwargs),
{self.key: params},
self.key,
method='PATCH')
def delete(self, **kwargs):
kwargs = self._filter_kwargs(kwargs)
return self._delete(
self.build_url(**kwargs))
class Resource(object):
"""
A resource represents a particular instance of an object (tenant, user,
@@ -188,6 +304,9 @@ class Resource(object):
if new:
self._add_details(new._info)
def delete(self):
return self.manager.delete(self)
def __eq__(self, other):
if not isinstance(other, self.__class__):
return False

View File

@@ -108,6 +108,9 @@ class HTTPClient(httplib2.Http):
if self.debug_log:
_logger.debug("RESP: %s\nRESP BODY: %s\n", resp, body)
def serialize(self, entity):
return json.dumps(entity)
def request(self, url, method, **kwargs):
""" Send an http request with the specified characteristics.
@@ -123,7 +126,7 @@ class HTTPClient(httplib2.Http):
self.original_ip, self.USER_AGENT)
if 'body' in kwargs:
request_kwargs['headers']['Content-Type'] = 'application/json'
request_kwargs['body'] = json.dumps(kwargs['body'])
request_kwargs['body'] = self.serialize(kwargs['body'])
self.http_log_req((url, method,), request_kwargs)
resp, body = super(HTTPClient, self).request(url,
@@ -180,11 +183,17 @@ class HTTPClient(httplib2.Http):
def get(self, url, **kwargs):
return self._cs_request(url, 'GET', **kwargs)
def head(self, url, **kwargs):
return self._cs_request(url, 'HEAD', **kwargs)
def post(self, url, **kwargs):
return self._cs_request(url, 'POST', **kwargs)
def put(self, url, **kwargs):
return self._cs_request(url, 'PUT', **kwargs)
def patch(self, url, **kwargs):
return self._cs_request(url, 'PATCH', **kwargs)
def delete(self, url, **kwargs):
return self._cs_request(url, 'DELETE', **kwargs)

View File

@@ -9,6 +9,10 @@ class CommandError(Exception):
pass
class ValidationError(Exception):
pass
class AuthorizationFailure(Exception):
pass

View File

@@ -0,0 +1 @@
from keystoneclient.v3.client import Client

View File

@@ -0,0 +1,85 @@
# Copyright 2011 Nebula, Inc.
# 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
import logging
from keystoneclient.v2_0 import client
from keystoneclient.v3 import credentials
from keystoneclient.v3 import endpoints
from keystoneclient.v3 import domains
from keystoneclient.v3 import policies
from keystoneclient.v3 import projects
from keystoneclient.v3 import roles
from keystoneclient.v3 import services
from keystoneclient.v3 import users
_logger = logging.getLogger(__name__)
class Client(client.Client):
"""Client for the OpenStack Identity API v3.
:param string username: Username for authentication. (optional)
:param string password: Password for authentication. (optional)
:param string token: Token for authentication. (optional)
:param string tenant_name: Tenant id. (optional)
:param string tenant_id: Tenant name. (optional)
:param string auth_url: Keystone service endpoint for authorization.
:param string region_name: Name of a region to select when choosing an
endpoint from the service catalog.
:param string endpoint: A user-supplied endpoint URL for the keystone
service. Lazy-authentication is possible for API
service calls if endpoint is set at
instantiation.(optional)
:param integer timeout: Allows customization of the timeout for client
http requests. (optional)
Example::
>>> from keystoneclient.v3 import client
>>> keystone = client.Client(username=USER,
password=PASS,
tenant_name=TENANT_NAME,
auth_url=KEYSTONE_URL)
>>> keystone.tenants.list()
...
>>> user = keystone.users.get(USER_ID)
>>> user.delete()
"""
def __init__(self, endpoint=None, **kwargs):
""" Initialize a new client for the Keystone v2.0 API. """
super(Client, self).__init__(endpoint=endpoint, **kwargs)
self.credentials = credentials.CredentialManager(self)
self.endpoints = endpoints.EndpointManager(self)
self.domains = domains.DomainManager(self)
self.policies = policies.PolicyManager(self)
self.projects = projects.ProjectManager(self)
self.roles = roles.RoleManager(self)
self.services = services.ServiceManager(self)
self.users = users.UserManager(self)
# NOTE(gabriel): If we have a pre-defined endpoint then we can
# get away with lazy auth. Otherwise auth immediately.
if endpoint:
self.management_url = endpoint
else:
self.authenticate()
def serialize(self, entity):
return json.dumps(entity, sort_keys=True)

View File

@@ -0,0 +1,57 @@
# Copyright 2011 OpenStack LLC.
# Copyright 2011 Nebula, Inc.
# 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 keystoneclient import base
class Credential(base.Resource):
"""Represents an Identity credential.
Attributes:
* id: a uuid that identifies the credential
"""
pass
class CredentialManager(base.CrudManager):
"""Manager class for manipulating Identity credentials."""
resource_class = Credential
collection_key = 'credentials'
key = 'credential'
def create(self, user, type, data, project=None):
return super(CredentialManager, self).create(
user_id=base.getid(user),
type=type,
data=data,
project_id=base.getid(project))
def get(self, credential):
return super(CredentialManager, self).get(
credential_id=base.getid(credential))
def update(self, credential, user, type=None, data=None, project=None):
return super(CredentialManager, self).update(
credential_id=base.getid(credential),
user_id=base.getid(user),
type=type,
data=data,
project_id=base.getid(project))
def delete(self, credential):
return super(CredentialManager, self).delete(
credential_id=base.getid(credential))

View File

@@ -0,0 +1,55 @@
# Copyright 2011 OpenStack LLC.
# Copyright 2011 Nebula, Inc.
# 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 keystoneclient import base
class Domain(base.Resource):
"""Represents an Identity domain.
Attributes:
* id: a uuid that identifies the domain
"""
pass
class DomainManager(base.CrudManager):
"""Manager class for manipulating Identity domains."""
resource_class = Domain
collection_key = 'domains'
key = 'domain'
def create(self, name, description=None, enabled=True):
return super(DomainManager, self).create(
name=name,
description=description,
enabled=enabled)
def get(self, domain):
return super(DomainManager, self).get(
domain_id=base.getid(domain))
def update(self, domain, name=None, description=None, enabled=None):
return super(DomainManager, self).update(
domain_id=base.getid(domain),
name=name,
description=description,
enabled=enabled)
def delete(self, domain):
return super(DomainManager, self).delete(
domain_id=base.getid(domain))

View File

@@ -0,0 +1,86 @@
# Copyright 2011 OpenStack LLC.
# Copyright 2011 Nebula, Inc.
# 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 keystoneclient import base
VALID_INTERFACES = ['public', 'admin', 'internal']
class Endpoint(base.Resource):
"""Represents an Identity endpoint.
Attributes:
* id: a uuid that identifies the endpoint
* interface: 'public', 'admin' or 'internal' network interface
* region: geographic location of the endpoint
* service_id: service to which the endpoint belongs
* url: fully qualified service endpoint
* enabled: determines whether the endpoint appears in the catalog
"""
pass
class EndpointManager(base.CrudManager):
"""Manager class for manipulating Identity endpoints."""
resource_class = Endpoint
collection_key = 'endpoints'
key = 'endpoint'
def _validate_interface(self, interface):
if interface is not None and interface not in VALID_INTERFACES:
msg = '"interface" must be one of: %s'
msg = msg % ', '.join(VALID_INTERFACES)
raise Exception(msg)
def create(self, service, url, name=None, interface=None, region=None,
enabled=True):
self._validate_interface(interface)
return super(EndpointManager, self).create(
service_id=base.getid(service),
interface=interface,
url=url,
region=region,
enabled=enabled)
def get(self, endpoint):
return super(EndpointManager, self).get(
endpoint_id=base.getid(endpoint))
def list(self, service=None, name=None, interface=None, region=None,
enabled=None):
self._validate_interface(interface)
return super(EndpointManager, self).list(
service_id=base.getid(service),
interface=interface,
region=region,
enabled=enabled)
def update(self, endpoint, service=None, url=None, name=None,
interface=None, region=None, enabled=None):
self._validate_interface(interface)
return super(EndpointManager, self).update(
endpoint_id=base.getid(endpoint),
service_id=base.getid(service),
interface=interface,
url=url,
region=region,
enabled=enabled)
def delete(self, endpoint):
return super(EndpointManager, self).delete(
endpoint_id=base.getid(endpoint))

View File

@@ -0,0 +1,77 @@
# Copyright 2011 OpenStack LLC.
# Copyright 2011 Nebula, Inc.
# 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 keystoneclient import base
class Policy(base.Resource):
"""Represents an Identity policy.
Attributes:
* id: a uuid that identifies the policy
* endpoint_id: references the endpoint the policy applies to
* blob: a policy document (blob)
* type: the mime type of the policy blob
"""
def update(self, endpoint=None, blob=None, type=None):
kwargs = {
'endpoint_id': (base.getid(endpoint)
if endpoint is not None
else self.endpoint_id),
'blob': blob if blob is not None else self.blob,
'type': type if type is not None else self.type,
}
try:
retval = self.manager.update(self.id, **kwargs)
self = retval
except Exception:
retval = None
return retval
class PolicyManager(base.CrudManager):
"""Manager class for manipulating Identity policies."""
resource_class = Policy
collection_key = 'policies'
key = 'policy'
def create(self, endpoint, blob, type='application/json'):
return super(PolicyManager, self).create(
endpoint_id=base.getid(endpoint),
blob=blob,
type=type)
def get(self, policy):
return super(PolicyManager, self).get(
policy_id=base.getid(policy))
def list(self, endpoint=None):
return super(PolicyManager, self).list(
endpoint_id=base.getid(endpoint))
def update(self, entity, endpoint=None, blob=None, type=None):
return super(PolicyManager, self).update(
policy_id=base.getid(entity),
endpoint_id=base.getid(endpoint),
blob=blob,
type=type)
def delete(self, policy):
return super(PolicyManager, self).delete(
policy_id=base.getid(policy))

View File

@@ -0,0 +1,82 @@
# Copyright 2011 OpenStack LLC.
# Copyright 2011 Nebula, Inc.
# 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 keystoneclient import base
class Project(base.Resource):
"""Represents an Identity project.
Attributes:
* id: a uuid that identifies the project
* name: project name
* description: project description
* enabled: boolean to indicate if project is enabled
"""
def update(self, name=None, description=None, enabled=None):
kwargs = {
'name': name if name is not None else self.name,
'description': (description
if description is not None
else self.description),
'enabled': enabled if enabled is not None else self.enabled,
}
try:
retval = self.manager.update(self.id, **kwargs)
self = retval
except Exception:
retval = None
return retval
class ProjectManager(base.CrudManager):
"""Manager class for manipulating Identity projects."""
resource_class = Project
collection_key = 'projects'
key = 'project'
def create(self, name, domain, description=None, enabled=True):
return super(ProjectManager, self).create(
domain_id=base.getid(domain),
name=name,
description=description,
enabled=enabled)
def list(self, domain=None, user=None):
base_url = '/users/%s' % base.getid(user) if user else None
return super(ProjectManager, self).list(
base_url=base_url,
domain_id=base.getid(domain))
def get(self, project):
return super(ProjectManager, self).get(
project_id=base.getid(project))
def update(self, project, name=None, domain=None, description=None,
enabled=None):
return super(ProjectManager, self).update(
project_id=base.getid(project),
domain_id=base.getid(domain),
name=name,
description=description,
enabled=enabled)
def delete(self, project):
return super(ProjectManager, self).delete(
project_id=base.getid(project))

110
keystoneclient/v3/roles.py Normal file
View File

@@ -0,0 +1,110 @@
# Copyright 2011 OpenStack LLC.
# Copyright 2011 Nebula, Inc.
# 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 keystoneclient import base
from keystoneclient import exceptions
class Role(base.Resource):
"""Represents an Identity role.
Attributes:
* id: a uuid that identifies the role
* name: user-facing identifier
"""
pass
class RoleManager(base.CrudManager):
"""Manager class for manipulating Identity roles."""
resource_class = Role
collection_key = 'roles'
key = 'role'
def _role_grants_base_url(self, user, domain, project):
params = {'user_id': base.getid(user)}
if domain:
params['domain_id'] = base.getid(domain)
base_url = '/domains/%(domain_id)s/users/%(user_id)s'
elif project:
params['project_id'] = base.getid(project)
base_url = '/projects/%(project_id)s/users/%(user_id)s'
return base_url % params
def _require_domain_or_project(self, domain, project):
if (domain and project) or (not domain and not project):
msg = 'Specify either a domain or project, not both'
raise exceptions.ValidationError(msg)
def create(self, name):
return super(RoleManager, self).create(
name=name)
def get(self, role):
return super(RoleManager, self).get(
role_id=base.getid(role))
def list(self, user=None, domain=None, project=None):
"""Lists roles and role grants.
If no arguments are provided, all roles in the system will be listed.
If a user is specified, you must also specify either a domain or
project to list role grants on that pair.
"""
if user:
self._require_domain_or_project(domain, project)
return super(RoleManager, self).list(
base_url=self._role_grants_base_url(user, domain, project))
return super(RoleManager, self).list()
def update(self, role, name=None):
return super(RoleManager, self).update(
role_id=base.getid(role),
name=name)
def delete(self, role):
return super(RoleManager, self).delete(
role_id=base.getid(role))
def grant(self, role, user, domain=None, project=None):
"""Grants a role to a user on either a domain or project."""
self._require_domain_or_project(domain, project)
return super(RoleManager, self).put(
base_url=self._role_grants_base_url(user, domain, project),
role_id=base.getid(role))
def check(self, role, user, domain=None, project=None):
"""Grants a role to a user on either a domain or project."""
self._require_domain_or_project(domain, project)
return super(RoleManager, self).head(
base_url=self._role_grants_base_url(user, domain, project),
role_id=base.getid(role))
def revoke(self, role, user, domain=None, project=None):
"""Revokes a role from a user on either a domain or project."""
self._require_domain_or_project(domain, project)
return super(RoleManager, self).delete(
base_url=self._role_grants_base_url(user, domain, project),
role_id=base.getid(role))

View File

@@ -0,0 +1,60 @@
# Copyright 2011 OpenStack LLC.
# Copyright 2011 Nebula, Inc.
# 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 keystoneclient import base
class Service(base.Resource):
"""Represents an Identity service.
Attributes:
* id: a uuid that identifies the service
* name: user-facing name of the service (e.g. Keystone)
* type: 'compute', 'identity', etc
* enabled: determines whether the service appears in the catalog
"""
pass
class ServiceManager(base.CrudManager):
"""Manager class for manipulating Identity services."""
resource_class = Service
collection_key = 'services'
key = 'service'
def create(self, name, type, enabled=True, **kwargs):
return super(ServiceManager, self).create(
name=name,
type=type,
enabled=enabled,
**kwargs)
def get(self, service):
return super(ServiceManager, self).get(
service_id=base.getid(service))
def update(self, service, name=None, type=None, enabled=None, **kwargs):
return super(ServiceManager, self).update(
service_id=base.getid(service),
name=name,
type=type,
enabled=enabled,
**kwargs)
def delete(self, service):
return super(ServiceManager, self).delete(
service_id=base.getid(service))

View File

@@ -0,0 +1,70 @@
# Copyright 2011 OpenStack LLC.
# Copyright 2011 Nebula, Inc.
# 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 keystoneclient import base
class User(base.Resource):
"""Represents an Identity user.
Attributes:
* id: a uuid that identifies the user
"""
pass
class UserManager(base.CrudManager):
"""Manager class for manipulating Identity users."""
resource_class = User
collection_key = 'users'
key = 'user'
def create(self, name, domain=None, project=None, password=None,
email=None, description=None, enabled=True):
return super(UserManager, self).create(
name=name,
domain_id=base.getid(domain),
project_id=base.getid(project),
password=password,
email=email,
description=description,
enabled=enabled)
def list(self, project=None, domain=None):
return super(UserManager, self).list(
domain_id=base.getid(domain),
project_id=base.getid(project))
def get(self, user):
return super(UserManager, self).get(
user_id=base.getid(user))
def update(self, user, name=None, domain=None, project=None, password=None,
email=None, description=None, enabled=None):
return super(UserManager, self).update(
user_id=base.getid(user),
name=name,
domain_id=base.getid(domain),
project_id=base.getid(project),
password=password,
email=email,
description=description,
enabled=enabled)
def delete(self, user):
return super(UserManager, self).delete(
user_id=base.getid(user))

0
tests/v3/__init__.py Normal file
View File

View File

@@ -0,0 +1,22 @@
import uuid
from keystoneclient.v3 import credentials
from tests.v3 import utils
class CredentialTests(utils.TestCase, utils.CrudTests):
def setUp(self):
super(CredentialTests, self).setUp()
self.additionalSetUp()
self.key = 'credential'
self.collection_key = 'credentials'
self.model = credentials.Credential
self.manager = self.client.credentials
def new_ref(self, **kwargs):
kwargs = super(CredentialTests, self).new_ref(**kwargs)
kwargs.setdefault('data', uuid.uuid4().hex)
kwargs.setdefault('project_id', uuid.uuid4().hex)
kwargs.setdefault('type', uuid.uuid4().hex)
kwargs.setdefault('user_id', uuid.uuid4().hex)
return kwargs

20
tests/v3/test_domains.py Normal file
View File

@@ -0,0 +1,20 @@
import uuid
from keystoneclient.v3 import domains
from tests.v3 import utils
class DomainTests(utils.TestCase, utils.CrudTests):
def setUp(self):
super(DomainTests, self).setUp()
self.additionalSetUp()
self.key = 'domain'
self.collection_key = 'domains'
self.model = domains.Domain
self.manager = self.client.domains
def new_ref(self, **kwargs):
kwargs = super(DomainTests, self).new_ref(**kwargs)
kwargs.setdefault('enabled', True)
kwargs.setdefault('name', uuid.uuid4().hex)
return kwargs

View File

@@ -0,0 +1,78 @@
import uuid
from keystoneclient.v3 import endpoints
from tests.v3 import utils
class EndpointTests(utils.TestCase, utils.CrudTests):
def setUp(self):
super(EndpointTests, self).setUp()
self.additionalSetUp()
self.key = 'endpoint'
self.collection_key = 'endpoints'
self.model = endpoints.Endpoint
self.manager = self.client.endpoints
def new_ref(self, **kwargs):
kwargs = super(EndpointTests, self).new_ref(**kwargs)
kwargs.setdefault('interface', 'public')
kwargs.setdefault('region', uuid.uuid4().hex)
kwargs.setdefault('service_id', uuid.uuid4().hex)
kwargs.setdefault('url', uuid.uuid4().hex)
kwargs.setdefault('enabled', True)
return kwargs
def test_create_public_interface(self):
ref = self.new_ref(interface='public')
self.test_create(ref)
def test_create_admin_interface(self):
ref = self.new_ref(interface='admin')
self.test_create(ref)
def test_create_internal_interface(self):
ref = self.new_ref(interface='internal')
self.test_create(ref)
def test_create_invalid_interface(self):
ref = self.new_ref(interface=uuid.uuid4().hex)
with self.assertRaises(Exception):
self.manager.create(**utils.parameterize(ref))
def test_update_public_interface(self):
ref = self.new_ref(interface='public')
self.test_update(ref)
def test_update_admin_interface(self):
ref = self.new_ref(interface='admin')
self.test_update(ref)
def test_update_internal_interface(self):
ref = self.new_ref(interface='internal')
self.test_update(ref)
def test_update_invalid_interface(self):
ref = self.new_ref(interface=uuid.uuid4().hex)
with self.assertRaises(Exception):
self.manager.update(**utils.parameterize(ref))
def test_list_public_interface(self):
interface = 'public'
expected_path = 'v3/%s?interface=%s' % (self.collection_key, interface)
self.test_list(expected_path=expected_path, interface=interface)
def test_list_admin_interface(self):
interface = 'admin'
expected_path = 'v3/%s?interface=%s' % (self.collection_key, interface)
self.test_list(expected_path=expected_path, interface=interface)
def test_list_internal_interface(self):
interface = 'admin'
expected_path = 'v3/%s?interface=%s' % (self.collection_key, interface)
self.test_list(expected_path=expected_path, interface=interface)
def test_list_invalid_interface(self):
interface = uuid.uuid4().hex
expected_path = 'v3/%s?interface=%s' % (self.collection_key, interface)
with self.assertRaises(Exception):
self.manager.list(expected_path=expected_path, interface=interface)

21
tests/v3/test_policies.py Normal file
View File

@@ -0,0 +1,21 @@
import uuid
from keystoneclient.v3 import policies
from tests.v3 import utils
class PolicyTests(utils.TestCase, utils.CrudTests):
def setUp(self):
super(PolicyTests, self).setUp()
self.additionalSetUp()
self.key = 'policy'
self.collection_key = 'policies'
self.model = policies.Policy
self.manager = self.client.policies
def new_ref(self, **kwargs):
kwargs = super(PolicyTests, self).new_ref(**kwargs)
kwargs.setdefault('endpoint_id', uuid.uuid4().hex)
kwargs.setdefault('type', uuid.uuid4().hex)
kwargs.setdefault('blob', uuid.uuid4().hex)
return kwargs

69
tests/v3/test_projects.py Normal file
View File

@@ -0,0 +1,69 @@
import httplib2
import urlparse
import uuid
from keystoneclient.v3 import projects
from tests.v3 import utils
class ProjectTests(utils.TestCase, utils.CrudTests):
def setUp(self):
super(ProjectTests, self).setUp()
self.additionalSetUp()
self.key = 'project'
self.collection_key = 'projects'
self.model = projects.Project
self.manager = self.client.projects
def new_ref(self, **kwargs):
kwargs = super(ProjectTests, self).new_ref(**kwargs)
kwargs.setdefault('domain_id', uuid.uuid4().hex)
kwargs.setdefault('enabled', True)
kwargs.setdefault('name', uuid.uuid4().hex)
return kwargs
def test_list_projects_for_user(self):
ref_list = [self.new_ref(), self.new_ref()]
user_id = uuid.uuid4().hex
resp = httplib2.Response({
'status': 200,
'body': self.serialize(ref_list),
})
method = 'GET'
httplib2.Http.request(
urlparse.urljoin(
self.TEST_URL,
'v3/users/%s/%s' % (user_id, self.collection_key)),
method,
headers=self.headers[method]) \
.AndReturn((resp, resp['body']))
self.mox.ReplayAll()
returned_list = self.manager.list(user=user_id)
self.assertTrue(len(returned_list))
[self.assertTrue(isinstance(r, self.model)) for r in returned_list]
def test_list_projects_for_domain(self):
ref_list = [self.new_ref(), self.new_ref()]
domain_id = uuid.uuid4().hex
resp = httplib2.Response({
'status': 200,
'body': self.serialize(ref_list),
})
method = 'GET'
httplib2.Http.request(
urlparse.urljoin(
self.TEST_URL,
'v3/%s?domain_id=%s' % (self.collection_key, domain_id)),
method,
headers=self.headers[method]) \
.AndReturn((resp, resp['body']))
self.mox.ReplayAll()
returned_list = self.manager.list(domain=domain_id)
self.assertTrue(len(returned_list))
[self.assertTrue(isinstance(r, self.model)) for r in returned_list]

252
tests/v3/test_roles.py Normal file
View File

@@ -0,0 +1,252 @@
import httplib2
import urlparse
import uuid
from keystoneclient import exceptions
from keystoneclient.v3 import roles
from tests.v3 import utils
class RoleTests(utils.TestCase, utils.CrudTests):
def setUp(self):
super(RoleTests, self).setUp()
self.additionalSetUp()
self.key = 'role'
self.collection_key = 'roles'
self.model = roles.Role
self.manager = self.client.roles
def new_ref(self, **kwargs):
kwargs = super(RoleTests, self).new_ref(**kwargs)
kwargs.setdefault('name', uuid.uuid4().hex)
return kwargs
def test_domain_role_grant(self):
user_id = uuid.uuid4().hex
domain_id = uuid.uuid4().hex
ref = self.new_ref()
resp = httplib2.Response({
'status': 201,
'body': '',
})
method = 'PUT'
httplib2.Http.request(
urlparse.urljoin(
self.TEST_URL,
'v3/domains/%s/users/%s/%s/%s' % (
domain_id, user_id, self.collection_key, ref['id'])),
method,
headers=self.headers[method]) \
.AndReturn((resp, resp['body']))
self.mox.ReplayAll()
self.manager.grant(role=ref['id'], domain=domain_id, user=user_id)
def test_domain_role_list(self):
user_id = uuid.uuid4().hex
domain_id = uuid.uuid4().hex
ref_list = [self.new_ref(), self.new_ref()]
resp = httplib2.Response({
'status': 200,
'body': self.serialize(ref_list),
})
method = 'GET'
httplib2.Http.request(
urlparse.urljoin(
self.TEST_URL,
'v3/domains/%s/users/%s/%s' % (
domain_id, user_id, self.collection_key)),
method,
headers=self.headers[method]) \
.AndReturn((resp, resp['body']))
self.mox.ReplayAll()
self.manager.list(domain=domain_id, user=user_id)
def test_domain_role_check(self):
user_id = uuid.uuid4().hex
domain_id = uuid.uuid4().hex
ref = self.new_ref()
resp = httplib2.Response({
'status': 200,
'body': '',
})
method = 'HEAD'
httplib2.Http.request(
urlparse.urljoin(
self.TEST_URL,
'v3/domains/%s/users/%s/%s/%s' % (
domain_id, user_id, self.collection_key, ref['id'])),
method,
headers=self.headers[method]) \
.AndReturn((resp, resp['body']))
self.mox.ReplayAll()
self.manager.check(role=ref['id'], domain=domain_id, user=user_id)
def test_domain_role_revoke(self):
user_id = uuid.uuid4().hex
domain_id = uuid.uuid4().hex
ref = self.new_ref()
resp = httplib2.Response({
'status': 204,
'body': '',
})
method = 'DELETE'
httplib2.Http.request(
urlparse.urljoin(
self.TEST_URL,
'v3/domains/%s/users/%s/%s/%s' % (
domain_id, user_id, self.collection_key, ref['id'])),
method,
headers=self.headers[method]) \
.AndReturn((resp, resp['body']))
self.mox.ReplayAll()
self.manager.revoke(role=ref['id'], domain=domain_id, user=user_id)
def test_project_role_grant(self):
user_id = uuid.uuid4().hex
project_id = uuid.uuid4().hex
ref = self.new_ref()
resp = httplib2.Response({
'status': 201,
'body': '',
})
method = 'PUT'
httplib2.Http.request(
urlparse.urljoin(
self.TEST_URL,
'v3/projects/%s/users/%s/%s/%s' % (
project_id, user_id, self.collection_key, ref['id'])),
method,
headers=self.headers[method]) \
.AndReturn((resp, resp['body']))
self.mox.ReplayAll()
self.manager.grant(role=ref['id'], project=project_id, user=user_id)
def test_project_role_list(self):
user_id = uuid.uuid4().hex
project_id = uuid.uuid4().hex
ref_list = [self.new_ref(), self.new_ref()]
resp = httplib2.Response({
'status': 200,
'body': self.serialize(ref_list),
})
method = 'GET'
httplib2.Http.request(
urlparse.urljoin(
self.TEST_URL,
'v3/projects/%s/users/%s/%s' % (
project_id, user_id, self.collection_key)),
method,
headers=self.headers[method]) \
.AndReturn((resp, resp['body']))
self.mox.ReplayAll()
self.manager.list(project=project_id, user=user_id)
def test_project_role_check(self):
user_id = uuid.uuid4().hex
project_id = uuid.uuid4().hex
ref = self.new_ref()
resp = httplib2.Response({
'status': 200,
'body': '',
})
method = 'HEAD'
httplib2.Http.request(
urlparse.urljoin(
self.TEST_URL,
'v3/projects/%s/users/%s/%s/%s' % (
project_id, user_id, self.collection_key, ref['id'])),
method,
headers=self.headers[method]) \
.AndReturn((resp, resp['body']))
self.mox.ReplayAll()
self.manager.check(role=ref['id'], project=project_id, user=user_id)
def test_project_role_revoke(self):
user_id = uuid.uuid4().hex
project_id = uuid.uuid4().hex
ref = self.new_ref()
resp = httplib2.Response({
'status': 204,
'body': '',
})
method = 'DELETE'
httplib2.Http.request(
urlparse.urljoin(
self.TEST_URL,
'v3/projects/%s/users/%s/%s/%s' % (
project_id, user_id, self.collection_key, ref['id'])),
method,
headers=self.headers[method]) \
.AndReturn((resp, resp['body']))
self.mox.ReplayAll()
self.manager.revoke(role=ref['id'], project=project_id, user=user_id)
def test_domain_project_role_grant_fails(self):
user_id = uuid.uuid4().hex
project_id = uuid.uuid4().hex
domain_id = uuid.uuid4().hex
ref = self.new_ref()
self.assertRaises(
exceptions.ValidationError,
self.manager.grant,
role=ref['id'],
domain=domain_id,
project=project_id,
user=user_id)
def test_domain_project_role_list_fails(self):
user_id = uuid.uuid4().hex
project_id = uuid.uuid4().hex
domain_id = uuid.uuid4().hex
self.assertRaises(
exceptions.ValidationError,
self.manager.list,
domain=domain_id,
project=project_id,
user=user_id)
def test_domain_project_role_check_fails(self):
user_id = uuid.uuid4().hex
project_id = uuid.uuid4().hex
domain_id = uuid.uuid4().hex
ref = self.new_ref()
self.assertRaises(
exceptions.ValidationError,
self.manager.check,
role=ref['id'],
domain=domain_id,
project=project_id,
user=user_id)
def test_domain_project_role_revoke_fails(self):
user_id = uuid.uuid4().hex
project_id = uuid.uuid4().hex
domain_id = uuid.uuid4().hex
ref = self.new_ref()
self.assertRaises(
exceptions.ValidationError,
self.manager.revoke,
role=ref['id'],
domain=domain_id,
project=project_id,
user=user_id)

21
tests/v3/test_services.py Normal file
View File

@@ -0,0 +1,21 @@
import uuid
from keystoneclient.v3 import services
from tests.v3 import utils
class ServiceTests(utils.TestCase, utils.CrudTests):
def setUp(self):
super(ServiceTests, self).setUp()
self.additionalSetUp()
self.key = 'service'
self.collection_key = 'services'
self.model = services.Service
self.manager = self.client.services
def new_ref(self, **kwargs):
kwargs = super(ServiceTests, self).new_ref(**kwargs)
kwargs.setdefault('name', uuid.uuid4().hex)
kwargs.setdefault('type', uuid.uuid4().hex)
kwargs.setdefault('enabled', True)
return kwargs

23
tests/v3/test_users.py Normal file
View File

@@ -0,0 +1,23 @@
import uuid
from keystoneclient.v3 import users
from tests.v3 import utils
class UserTests(utils.TestCase, utils.CrudTests):
def setUp(self):
super(UserTests, self).setUp()
self.additionalSetUp()
self.key = 'user'
self.collection_key = 'users'
self.model = users.User
self.manager = self.client.users
def new_ref(self, **kwargs):
kwargs = super(UserTests, self).new_ref(**kwargs)
kwargs.setdefault('description', uuid.uuid4().hex)
kwargs.setdefault('domain_id', uuid.uuid4().hex)
kwargs.setdefault('enabled', True)
kwargs.setdefault('name', uuid.uuid4().hex)
kwargs.setdefault('project_id', uuid.uuid4().hex)
return kwargs

227
tests/v3/utils.py Normal file
View File

@@ -0,0 +1,227 @@
import json
import uuid
import time
import urlparse
import httplib2
import mox
import unittest2 as unittest
from keystoneclient.v3 import client
def parameterize(ref):
"""Rewrites attributes to match the kwarg naming convention in client.
>>> paramterize({'project_id': 0})
{'project': 0}
"""
params = ref.copy()
for key in ref:
if key[-3:] == '_id':
params.setdefault(key[:-3], params.pop(key))
return params
class TestCase(unittest.TestCase):
TEST_TENANT_NAME = 'aTenant'
TEST_TOKEN = 'aToken'
TEST_USER = 'test'
TEST_ROOT_URL = 'http://127.0.0.1:5000/'
TEST_URL = '%s%s' % (TEST_ROOT_URL, 'v3')
TEST_ROOT_ADMIN_URL = 'http://127.0.0.1:35357/'
TEST_ADMIN_URL = '%s%s' % (TEST_ROOT_ADMIN_URL, 'v3')
def setUp(self):
super(TestCase, self).setUp()
self.mox = mox.Mox()
self._original_time = time.time
time.time = lambda: 1234
httplib2.Http.request = self.mox.CreateMockAnything()
self.client = client.Client(username=self.TEST_USER,
token=self.TEST_TOKEN,
tenant_name=self.TEST_TENANT_NAME,
auth_url=self.TEST_URL,
endpoint=self.TEST_URL)
def tearDown(self):
time.time = self._original_time
super(TestCase, self).tearDown()
self.mox.UnsetStubs()
self.mox.VerifyAll()
class UnauthenticatedTestCase(unittest.TestCase):
""" Class used as base for unauthenticated calls """
TEST_ROOT_URL = 'http://127.0.0.1:5000/'
TEST_URL = '%s%s' % (TEST_ROOT_URL, 'v3')
TEST_ROOT_ADMIN_URL = 'http://127.0.0.1:35357/'
TEST_ADMIN_URL = '%s%s' % (TEST_ROOT_ADMIN_URL, 'v3')
def setUp(self):
super(UnauthenticatedTestCase, self).setUp()
self.mox = mox.Mox()
self._original_time = time.time
time.time = lambda: 1234
httplib2.Http.request = self.mox.CreateMockAnything()
def tearDown(self):
time.time = self._original_time
super(UnauthenticatedTestCase, self).tearDown()
self.mox.UnsetStubs()
self.mox.VerifyAll()
class CrudTests(object):
key = None
collection_key = None
model = None
manager = None
def new_ref(self, **kwargs):
kwargs.setdefault('id', uuid.uuid4().hex)
return kwargs
def additionalSetUp(self):
self.headers = {
'GET': {
'X-Auth-Token': 'aToken',
'User-Agent': 'python-keystoneclient',
}
}
self.headers['HEAD'] = self.headers['GET'].copy()
self.headers['DELETE'] = self.headers['GET'].copy()
self.headers['PUT'] = self.headers['GET'].copy()
self.headers['POST'] = self.headers['GET'].copy()
self.headers['POST']['Content-Type'] = 'application/json'
self.headers['PATCH'] = self.headers['POST'].copy()
def serialize(self, entity):
if isinstance(entity, dict):
return json.dumps({self.key: entity}, sort_keys=True)
if isinstance(entity, list):
return json.dumps({self.collection_key: entity}, sort_keys=True)
raise NotImplementedError('Are you sure you want to serialize that?')
def test_create(self, ref=None):
ref = ref or self.new_ref()
resp = httplib2.Response({
'status': 201,
'body': self.serialize(ref),
})
method = 'POST'
req_ref = ref.copy()
req_ref.pop('id')
httplib2.Http.request(
urlparse.urljoin(
self.TEST_URL,
'v3/%s' % self.collection_key),
method,
body=self.serialize(req_ref),
headers=self.headers[method]) \
.AndReturn((resp, resp['body']))
self.mox.ReplayAll()
returned = self.manager.create(**parameterize(req_ref))
self.assertTrue(isinstance(returned, self.model))
for attr in ref:
self.assertEqual(
getattr(returned, attr),
ref[attr],
'Expected different %s' % attr)
def test_get(self, ref=None):
ref = ref or self.new_ref()
resp = httplib2.Response({
'status': 200,
'body': self.serialize(ref),
})
method = 'GET'
httplib2.Http.request(
urlparse.urljoin(
self.TEST_URL,
'v3/%s/%s' % (self.collection_key, ref['id'])),
method,
headers=self.headers[method]) \
.AndReturn((resp, resp['body']))
self.mox.ReplayAll()
returned = self.manager.get(ref['id'])
self.assertTrue(isinstance(returned, self.model))
for attr in ref:
self.assertEqual(
getattr(returned, attr),
ref[attr],
'Expected different %s' % attr)
def test_list(self, ref_list=None, expected_path=None, **filter_kwargs):
ref_list = ref_list or [self.new_ref(), self.new_ref()]
resp = httplib2.Response({
'status': 200,
'body': self.serialize(ref_list),
})
method = 'GET'
httplib2.Http.request(
urlparse.urljoin(
self.TEST_URL,
expected_path or 'v3/%s' % self.collection_key),
method,
headers=self.headers[method]) \
.AndReturn((resp, resp['body']))
self.mox.ReplayAll()
returned_list = self.manager.list(**filter_kwargs)
self.assertTrue(len(returned_list))
[self.assertTrue(isinstance(r, self.model)) for r in returned_list]
def test_update(self, ref=None):
ref = ref or self.new_ref()
req_ref = ref.copy()
del req_ref['id']
resp = httplib2.Response({
'status': 200,
'body': self.serialize(ref),
})
method = 'PATCH'
httplib2.Http.request(
urlparse.urljoin(
self.TEST_URL,
'v3/%s/%s' % (self.collection_key, ref['id'])),
method,
body=self.serialize(req_ref),
headers=self.headers[method]) \
.AndReturn((resp, resp['body']))
self.mox.ReplayAll()
returned = self.manager.update(ref['id'], **parameterize(req_ref))
self.assertTrue(isinstance(returned, self.model))
for attr in ref:
self.assertEqual(
getattr(returned, attr),
ref[attr],
'Expected different %s' % attr)
def test_delete(self, ref=None):
ref = ref or self.new_ref()
method = 'DELETE'
resp = httplib2.Response({
'status': 204,
'body': '',
})
httplib2.Http.request(
urlparse.urljoin(
self.TEST_URL,
'v3/%s/%s' % (self.collection_key, ref['id'])),
method,
headers=self.headers[method]) \
.AndReturn((resp, resp['body']))
self.mox.ReplayAll()
self.manager.delete(ref['id'])