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:
@@ -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
|
||||
|
@@ -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)
|
||||
|
@@ -9,6 +9,10 @@ class CommandError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ValidationError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class AuthorizationFailure(Exception):
|
||||
pass
|
||||
|
||||
|
1
keystoneclient/v3/__init__.py
Normal file
1
keystoneclient/v3/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from keystoneclient.v3.client import Client
|
85
keystoneclient/v3/client.py
Normal file
85
keystoneclient/v3/client.py
Normal 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)
|
57
keystoneclient/v3/credentials.py
Normal file
57
keystoneclient/v3/credentials.py
Normal 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))
|
55
keystoneclient/v3/domains.py
Normal file
55
keystoneclient/v3/domains.py
Normal 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))
|
86
keystoneclient/v3/endpoints.py
Normal file
86
keystoneclient/v3/endpoints.py
Normal 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))
|
77
keystoneclient/v3/policies.py
Normal file
77
keystoneclient/v3/policies.py
Normal 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))
|
82
keystoneclient/v3/projects.py
Normal file
82
keystoneclient/v3/projects.py
Normal 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
110
keystoneclient/v3/roles.py
Normal 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))
|
60
keystoneclient/v3/services.py
Normal file
60
keystoneclient/v3/services.py
Normal 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))
|
70
keystoneclient/v3/users.py
Normal file
70
keystoneclient/v3/users.py
Normal 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
0
tests/v3/__init__.py
Normal file
22
tests/v3/test_credentials.py
Normal file
22
tests/v3/test_credentials.py
Normal 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
20
tests/v3/test_domains.py
Normal 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
|
78
tests/v3/test_endpoints.py
Normal file
78
tests/v3/test_endpoints.py
Normal 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
21
tests/v3/test_policies.py
Normal 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
69
tests/v3/test_projects.py
Normal 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
252
tests/v3/test_roles.py
Normal 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
21
tests/v3/test_services.py
Normal 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
23
tests/v3/test_users.py
Normal 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
227
tests/v3/utils.py
Normal 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'])
|
Reference in New Issue
Block a user