Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/feature/keystone-v3' into HEAD
Browse files Browse the repository at this point in the history
Conflicts:
	tests/v2_0/test_tenants.py

Change-Id: I37037e60210edd574da86b1dc07aa73e6761e338
  • Loading branch information
dolph committed Oct 30, 2012
2 parents 026dc8e + 62c55bc commit 1b7eca8
Show file tree
Hide file tree
Showing 23 changed files with 1,554 additions and 6 deletions.
129 changes: 124 additions & 5 deletions keystoneclient/base.py
Expand Up @@ -18,6 +18,8 @@
Base utilities to build API operation managers and objects on top of.
"""

import urllib

from keystoneclient import exceptions


Expand Down Expand Up @@ -76,20 +78,25 @@ def _list(self, url, response_key, obj_class=None, body=None):

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)
Expand All @@ -100,7 +107,7 @@ def _update(self, url, body, response_key=None, method="PUT"):
% 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):
Expand Down Expand Up @@ -142,6 +149,115 @@ def findall(self, **kwargs):
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,
Expand Down Expand Up @@ -188,6 +304,9 @@ def get(self):
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
Expand Down
11 changes: 10 additions & 1 deletion keystoneclient/client.py
Expand Up @@ -108,6 +108,9 @@ def http_log_resp(self, resp, body):
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.
Expand All @@ -123,7 +126,7 @@ def request(self, url, method, **kwargs):
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,
Expand Down Expand Up @@ -180,11 +183,17 @@ def _cs_request(self, url, method, **kwargs):
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)
4 changes: 4 additions & 0 deletions keystoneclient/exceptions.py
Expand Up @@ -9,6 +9,10 @@ class CommandError(Exception):
pass


class ValidationError(Exception):
pass


class AuthorizationFailure(Exception):
pass

Expand Down
1 change: 1 addition & 0 deletions keystoneclient/v3/__init__.py
@@ -0,0 +1 @@
from keystoneclient.v3.client import Client
85 changes: 85 additions & 0 deletions keystoneclient/v3/client.py
@@ -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 changes: 57 additions & 0 deletions keystoneclient/v3/credentials.py
@@ -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))

0 comments on commit 1b7eca8

Please sign in to comment.