WIP: Architecture

Using the basis architecture from the python-marconiclient package. This
code doesn't really work yet, but I wanted to get an initial version up
as there was some interest.
This commit is contained in:
Jarret Raim
2013-05-16 08:07:59 -05:00
parent 96871cac79
commit 23aed669b6
11 changed files with 347 additions and 1042 deletions

218
barbicanclient/client.py Normal file
View File

@@ -0,0 +1,218 @@
from eventlet.green.urllib import quote
import eventlet
eventlet.monkey_patch(socket=True, select=True)
import json
import requests
from barbicanclient.common.auth import authenticate
from barbicanclient.common.utils import proc_template
from barbicanclient.common.exceptions import ClientException
from urlparse import urljoin
class Connection(object):
def __init__(self, auth_endpoint, user, key, **kwargs):
"""
:param auth_endpoint: The auth URL to authenticate against
:param user: The user to authenticate as
:param key: The API key or passowrd to auth with
"""
self._auth_endpoint = auth_endpoint
self._user = user
self._key = key
self._endpoint = kwargs.get('endpoint') or 'https://barbican.api.rackspacecloud.com/v1/'
self._cacert = kwargs.get('cacert')
# Hardcoded uri's right now
self.secrets_href = 'secrets/'
@property
def _conn(self):
"""
Property to enable decorators to work
properly
"""
return self
@property
def auth_endpoint(self):
"""The fully-qualified URI of the auth endpoint"""
return self._auth_endpoint
@property
def endpoint(self):
"""The fully-qualified URI of the endpoint"""
return self._endpoint
def connect(self, token=None):
"""
Establishes a connection. If token is not None the
token will be used for this connection and auth will
not happen.
"""
self._session = requests.Session()
#headers = {"Client-Id": self._client_id}
#self._session.headers.update(headers)
self._session.verify = True
if token:
self.auth_token = token
else:
(self._endpoint,
self.auth_token) = authenticate(self._auth_endpoint,
self._user, self._key,
endpoint=self._endpoint,
cacert=self._cacert)
#self._load_homedoc_hrefs()
@property
def auth_token(self):
try:
return self._session.headers['X-Auth-Token']
except KeyError:
return None
@auth_token.setter
def auth_token(self, value):
self._token = value
self._session.headers['X-Auth-Token'] = value
def list_secrets(self):
"""
Returns the list of secrets for the auth'd tenant
"""
href = proc_template(self.secrets_href)
hdrs, body = self._perform_http(href=href, method='GET')
#return Queue(self, href=href, name=queue_name, metadata=body)
#
# def _load_homedoc_hrefs(self):
# """
# Loads the home document hrefs for each endpoint
# Note: at the present time homedocs have not been
# implemented so these hrefs are simply hard-coded. When
# they are implemented we should update this function to
# actually parse the home document.
# """
#
# # Queues endpoint{" + name + "}", quote(str(value)))
# self.queues_href = self._endpoint + "/queues"
#
# # Specific queue endpoint
# self.queue_href = self.queues_href + "/{queue_name}"
#
# # Messages endpoint
# self.messages_href = self.queue_href + "/messages"
#
# # Specific message endpoint
# self.message_href = self.messages_href + "/{message_id}"
#
# # Claims endpoint
# self._claims_href = self.queues_href + "/claims"
#
# # Specific claim endpoint
# self._claim_href = self.queues_href + "/claims/{claim_id}"
#
# # Actions endpoint
# self.actions_href = self._endpoint + "/actions"
#
# # Specific action endpoint
# self.action_href = self.actions_href + "/{action_id}"
#
# # Statistics endpoint
# self.stats_href = self.queue_href + "/stats"
#
# def create_queue(self, queue_name):
# """
# Creates a queue with the specified name
#
# :param queue_name: The name of the queue
# :param ttl: The default time-to-live for messages in this queue
# """
# href = proc_template(self.queue_href, queue_name=queue_name)
# body = {}
#
# self._perform_http(href=href, method='PUT', request_body=body)
#
# return Queue(self, href=href, name=queue_name, metadata=body)
#
# def get_queue(self, queue_name):
# """
# Gets a queue by name
#
# :param queue_name: The name of the queue
# """
# href = proc_template(self.queue_href, queue_name=queue_name)
#
# try:
# hdrs, body = self._perform_http(href=href, method='GET')
# except ClientException as ex:
# raise NoSuchQueueError(queue_name) if ex.http_status == 404 else ex
#
# return Queue(self, href=href, name=queue_name, metadata=body)
#
# def get_queues(self):
# href = self.queues_href
#
# hdrs, res = self._perform_http(href=href, method='GET')
# queues = res["queues"]
#
# for queue in queues:
# yield Queue(conn=self._conn, name=queue['name'],
# href=queue['href'], metadata=queue['metadata'])
#
# def delete_queue(self, queue_name):
# """
# Deletes a queue
#
# :param queue_name: The name of the queue
# """
# href = proc_template(self.queue_href, queue_name=queue_name)
# self._perform_http(href=href, method='DELETE')
#
# def get_queue_metadata(self, queue_name):
# href = proc_template(self._queue_href, queue_name=queue_name)
#
# try:
# return self._perform_http(conn, href, 'GET')
# except ClientException as ex:
# raise NoSuchQueueError(queue_name) if ex.http_status == 404 else ex
def _perform_http(self, method, href, request_body='', headers={}):
"""
Perform an HTTP operation, checking for appropriate
errors, etc. and returns the response
:param conn: The HTTPConnection or HTTPSConnection to use
:param method: The http method to use (GET, PUT, etc)
:param body: The optional body to submit
:param headers: Any additional headers to submit
:return: (headers, body)
"""
if not isinstance(request_body, str):
request_body = json.dumps(request_body)
url = urljoin(self._endpoint, href)
response = requests.request(method=method, url=url, data=request_body)
#response = self._session.request(method=method, url=url,
# data=request_body, headers=headers)
# Check if the status code is 2xx class
if not response.ok:
raise ClientException(href=href, method=method, http_status=response.status_code,
http_response_content=response.content)
resp_body = json.loads(response.content) if response.content else ''
return response.headers, resp_body

View File

@@ -1,255 +1,73 @@
from barbicanclient import exceptions
from exceptions import ClientException
from keystoneclient.v2_0 import client as ksclient
from keystoneclient import exceptions
def get_authenticator_cls(cls_or_name):
"""Factory method to retrieve Authenticator class."""
if isinstance(cls_or_name, type):
return cls_or_name
elif isinstance(cls_or_name, basestring):
if cls_or_name == "keystone":
return KeyStoneV2Authenticator
elif cls_or_name == "rax":
return RaxAuthenticator
elif cls_or_name == "auth1.1":
return Auth1_1
elif cls_or_name == "fake":
return FakeAuth
def authenticate(auth_url, user, key, **kwargs):
"""Authenticates against the endpoint to use. The correct
endpoint to use is looked up in the service catalog. The
caller can override this lookup by passing the endpoint
as a parameter.
raise ValueError("Could not determine authenticator class from the given "
"value %r." % cls_or_name)
:param auth_url: The keystone auth endpoint to use
:param user: The username to use for auth
:param key: The apikey to use for authentiation
:param endpoint: The Marconi endpoint to use. IOW, don't
look up an endpoint in the service catalog, just use
this one instead.
:param tenant_name: The optional tenant-name to use
:param tenant_id: The optional tenant ID toi use
:param cacert: The cacert PEM file to use
:param service_type: The service type to look for in
the service catalog
:param endpoint_type The endpoint type to reference in
the service catalog
:param region_name The region to pass for authentication
:returns: Tuple containing Marconi endpoint and token
class Authenticator(object):
:raises: ClientException
"""
Helper class to perform Keystone or other miscellaneous authentication.
insecure = kwargs.get('insecure', False)
endpoint = kwargs.get('endpoint')
tenant_name = kwargs.get('tenant_name')
tenant_id = kwargs.get('tenant_id')
cacert = kwargs.get('cacert')
The "authenticate" method returns a ServiceCatalog, which can be used
to obtain a token.
try:
_ksclient = ksclient.Client(username=user,
password=key,
tenant_name=tenant_name,
tenant_id=tenant_id,
cacert=cacert,
auth_url=auth_url,
insecure=insecure)
"""
except exceptions.Unauthorized as ex:
raise ClientException('Unauthorized. Check username, password'
' and tenant name/id')
URL_REQUIRED = True
except exceptions.AuthorizationFailure as err:
raise ClientException('Authorization Failure. %s' % err)
def __init__(self, client, type, url, username, password, tenant,
region=None, service_type=None, service_name=None,
service_url=None):
self.client = client
self.type = type
self.url = url
self.username = username
self.password = password
self.tenant = tenant
self.region = region
self.service_type = service_type
self.service_name = service_name
self.service_url = service_url
if not endpoint:
# The user did not pass in an endpoint, so we need to
# look one up on their behalf in the service catalog
def _authenticate(self, url, body, root_key='access'):
"""Authenticate and extract the service catalog."""
# Make sure we follow redirects when trying to reach Keystone
tmp_follow_all_redirects = self.client.follow_all_redirects
self.client.follow_all_redirects = True
# TODO(jdp): Ensure that this is the correct service_type field
service_type = kwargs.get('service_type', 'queueing')
endpoint_type = kwargs.get('endpoint_type', 'publicURL')
region = kwargs.get('region_name')
try:
resp, body = self.client._time_request(url, "POST", body=body)
finally:
self.client.follow_all_redirects = tmp_follow_all_redirects
endpoint = _ksclient.service_catalog.url_for(
attr='region',
filter_value=region,
service_type=service_type,
endpoint_type=endpoint_type)
except exceptions.EndpointNotFound as ex:
raise ClientException('Endpoint not found in service catalog')
if resp.status == 200: # content must always present
try:
return ServiceCatalog(body, region=self.region,
service_type=self.service_type,
service_name=self.service_name,
service_url=self.service_url,
root_key=root_key)
except exceptions.AmbiguousEndpoints:
print "Found more than one valid endpoint. Use a more "\
"restrictive filter"
raise
except KeyError:
raise exceptions.AuthorizationFailure()
except exceptions.EndpointNotFound:
print "Could not find any suitable endpoint. Correct region?"
raise
elif resp.status == 305:
return resp['location']
else:
raise exceptions.from_response(resp, body)
def authenticate(self):
raise NotImplementedError("Missing authenticate method.")
class KeyStoneV2Authenticator(Authenticator):
def authenticate(self):
if self.url is None:
raise exceptions.AuthUrlNotGiven()
return self._v2_auth(self.url)
def _v2_auth(self, url):
"""Authenticate against a v2.0 auth service."""
body = {"auth": {
"passwordCredentials": {
"username": self.username,
"password": self.password}
}
}
if self.tenant:
body['auth']['tenantName'] = self.tenant
return self._authenticate(url, body)
class Auth1_1(Authenticator):
def authenticate(self):
"""Authenticate against a v2.0 auth service."""
if self.url is None:
raise exceptions.AuthUrlNotGiven()
auth_url = self.url
body = {"credentials": {"username": self.username,
"key": self.password}}
return self._authenticate(auth_url, body, root_key='auth')
try:
print(resp_body)
self.auth_token = resp_body['auth']['token']['id']
except KeyError:
raise nova_exceptions.AuthorizationFailure()
catalog = resp_body['auth']['serviceCatalog']
if 'cloudDatabases' not in catalog:
raise nova_exceptions.EndpointNotFound()
endpoints = catalog['cloudDatabases']
for endpoint in endpoints:
if self.region_name is None or \
endpoint['region'] == self.region_name:
self.management_url = endpoint['publicURL']
return
raise nova_exceptions.EndpointNotFound()
class RaxAuthenticator(Authenticator):
def authenticate(self):
if self.url is None:
raise exceptions.AuthUrlNotGiven()
return self._rax_auth(self.url)
def _rax_auth(self, url):
"""Authenticate against the Rackspace auth service."""
body = {'auth': {
'RAX-KSKEY:apiKeyCredentials': {
'username': self.username,
'apiKey': self.password,
'tenantName': self.tenant}
}
}
return self._authenticate(self.url, body)
class FakeAuth(Authenticator):
"""Useful for faking auth."""
def authenticate(self):
class FakeCatalog(object):
def __init__(self, auth):
self.auth = auth
def get_public_url(self):
return "%s/%s" % ('http://localhost:8779/v1.0',
self.auth.tenant)
def get_token(self):
return self.auth.tenant
return FakeCatalog(self)
class ServiceCatalog(object):
"""Represents a Keystone Service Catalog which describes a service.
This class has methods to obtain a valid token as well as a public service
url and a management url.
"""
def __init__(self, resource_dict, region=None, service_type=None,
service_name=None, service_url=None, root_key='access'):
self.catalog = resource_dict
self.region = region
self.service_type = service_type
self.service_name = service_name
self.service_url = service_url
self.management_url = None
self.public_url = None
self.root_key = root_key
self._load()
def _load(self):
if not self.service_url:
self.public_url = self._url_for(attr='region',
filter_value=self.region,
endpoint_type="publicURL")
self.management_url = self._url_for(attr='region',
filter_value=self.region,
endpoint_type="adminURL")
else:
self.public_url = self.service_url
self.management_url = self.service_url
def get_token(self):
return self.catalog[self.root_key]['token']['id']
def get_management_url(self):
return self.management_url
def get_public_url(self):
return self.public_url
def _url_for(self, attr=None, filter_value=None,
endpoint_type='publicURL'):
"""
Fetch the public URL from the Reddwarf service for a particular
endpoint attribute. If none given, return the first.
"""
matching_endpoints = []
if 'endpoints' in self.catalog:
# We have a bastardized service catalog. Treat it special. :/
for endpoint in self.catalog['endpoints']:
if not filter_value or endpoint[attr] == filter_value:
matching_endpoints.append(endpoint)
if not matching_endpoints:
raise exceptions.EndpointNotFound()
# We don't always get a service catalog back ...
if not 'serviceCatalog' in self.catalog[self.root_key]:
raise exceptions.EndpointNotFound()
# Full catalog ...
catalog = self.catalog[self.root_key]['serviceCatalog']
for service in catalog:
if service.get("type") != self.service_type:
continue
if (self.service_name and self.service_type == 'database' and
service.get('name') != self.service_name):
continue
endpoints = service['endpoints']
for endpoint in endpoints:
if not filter_value or endpoint.get(attr) == filter_value:
endpoint["serviceName"] = service.get("name")
matching_endpoints.append(endpoint)
if not matching_endpoints:
raise exceptions.EndpointNotFound()
elif len(matching_endpoints) > 1:
raise exceptions.AmbiguousEndpoints(endpoints=matching_endpoints)
else:
return matching_endpoints[0].get(endpoint_type, None)
return (endpoint, _ksclient.auth_token)

View File

@@ -0,0 +1,12 @@
class ClientException(Exception):
"""Exception for wrapping up Marconi client errors"""
def __init__(self, href='', http_status=0,
method='', http_response_content=''):
self.method = method
self.href = href
self.http_status = http_status
self.http_response_content = http_response_content
msg = "%s %s returned %d" % (self.method, self.href, self.http_status)
Exception.__init__(self, msg)

View File

@@ -1,229 +0,0 @@
import httplib2
import logging
from barbicanclient.common import auth
class BarbicanHTTPClient(httplib2.Http):
USER_AGENT = 'python-barbicanclient'
def __init__(self, user, password, tenant, auth_url, service_name,
service_url=None,
auth_strategy=None, insecure=False,
timeout=None, proxy_tenant_id=None,
proxy_token=None, region_name=None,
endpoint_type='publicURL', service_type=None,
timings=False):
super(BarbicanHTTPClient, self).__init__(timeout=timeout)
self.username = user
self.password = password
self.tenant = tenant
if auth_url:
self.auth_url = auth_url.rstrip('/')
else:
self.auth_url = None
self.region_name = region_name
self.endpoint_type = endpoint_type
self.service_url = service_url
self.service_type = service_type
self.service_name = service_name
self.timings = timings
self.times = [] # [("item", starttime, endtime), ...]
self.auth_token = None
self.proxy_token = proxy_token
self.proxy_tenant_id = proxy_tenant_id
# httplib2 overrides
self.force_exception_to_status_code = True
self.disable_ssl_certificate_validation = insecure
auth_cls = auth.get_authenticator_cls(auth_strategy)
self.authenticator = auth_cls(self, auth_strategy,
self.auth_url, self.username,
self.password, self.tenant,
region=region_name,
service_type=service_type,
service_name=service_name,
service_url=service_url)
def get_timings(self):
return self.times
def http_log(self, args, kwargs, resp, body):
if not RDC_PP:
self.simple_log(args, kwargs, resp, body)
else:
self.pretty_log(args, kwargs, resp, body)
def simple_log(self, args, kwargs, resp, body):
if not _logger.isEnabledFor(logging.DEBUG):
return
string_parts = ['curl -i']
for element in args:
if element in ('GET', 'POST'):
string_parts.append(' -X %s' % element)
else:
string_parts.append(' %s' % element)
for element in kwargs['headers']:
header = ' -H "%s: %s"' % (element, kwargs['headers'][element])
string_parts.append(header)
_logger.debug("REQ: %s\n" % "".join(string_parts))
if 'body' in kwargs:
_logger.debug("REQ BODY: %s\n" % (kwargs['body']))
_logger.debug("RESP:%s %s\n", resp, body)
def pretty_log(self, args, kwargs, resp, body):
from reddwarfclient import common
if not _logger.isEnabledFor(logging.DEBUG):
return
string_parts = ['curl -i']
for element in args:
if element in ('GET', 'POST'):
string_parts.append(' -X %s' % element)
else:
string_parts.append(' %s' % element)
for element in kwargs['headers']:
header = ' -H "%s: %s"' % (element, kwargs['headers'][element])
string_parts.append(header)
curl_cmd = "".join(string_parts)
_logger.debug("REQUEST:")
if 'body' in kwargs:
_logger.debug("%s -d '%s'" % (curl_cmd, kwargs['body']))
try:
req_body = json.dumps(json.loads(kwargs['body']),
sort_keys=True, indent=4)
except:
req_body = kwargs['body']
_logger.debug("BODY: %s\n" % (req_body))
else:
_logger.debug(curl_cmd)
try:
resp_body = json.dumps(json.loads(body), sort_keys=True, indent=4)
except:
resp_body = body
_logger.debug("RESPONSE HEADERS: %s" % resp)
_logger.debug("RESPONSE BODY : %s" % resp_body)
def request(self, *args, **kwargs):
kwargs.setdefault('headers', kwargs.get('headers', {}))
kwargs['headers']['User-Agent'] = self.USER_AGENT
self.morph_request(kwargs)
resp, body = super(ReddwarfHTTPClient, self).request(*args, **kwargs)
# Save this in case anyone wants it.
self.last_response = (resp, body)
self.http_log(args, kwargs, resp, body)
if body:
try:
body = self.morph_response_body(body)
except exceptions.ResponseFormatError:
# Acceptable only if the response status is an error code.
# Otherwise its the API or client misbehaving.
self.raise_error_from_status(resp, None)
raise # Not accepted!
else:
body = None
if resp.status in expected_errors:
raise exceptions.from_response(resp, body)
return resp, body
def raise_error_from_status(self, resp, body):
if resp.status in expected_errors:
raise exceptions.from_response(resp, body)
def morph_request(self, kwargs):
kwargs['headers']['Accept'] = 'application/json'
kwargs['headers']['Content-Type'] = 'application/json'
if 'body' in kwargs:
kwargs['body'] = json.dumps(kwargs['body'])
def morph_response_body(self, body_string):
try:
return json.loads(body_string)
except ValueError:
raise exceptions.ResponseFormatError()
def _time_request(self, url, method, **kwargs):
start_time = time.time()
resp, body = self.request(url, method, **kwargs)
self.times.append(("%s %s" % (method, url),
start_time, time.time()))
return resp, body
def _cs_request(self, url, method, **kwargs):
def request():
kwargs.setdefault('headers', {})['X-Auth-Token'] = self.auth_token
if self.tenant:
kwargs['headers']['X-Auth-Project-Id'] = self.tenant
resp, body = self._time_request(self.service_url + url, method,
**kwargs)
return resp, body
if not self.auth_token or not self.service_url:
self.authenticate()
# Perform the request once. If we get a 401 back then it
# might be because the auth token expired, so try to
# re-authenticate and try again. If it still fails, bail.
try:
return request()
except exceptions.Unauthorized, ex:
self.authenticate()
return request()
def get(self, url, **kwargs):
return self._cs_request(url, 'GET', **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 delete(self, url, **kwargs):
return self._cs_request(url, 'DELETE', **kwargs)
def authenticate(self):
"""Auths the client and gets a token. May optionally set a service url.
The client will get auth errors until the authentication step
occurs. Additionally, if a service_url was not explicitly given in
the clients __init__ method, one will be obtained from the auth
service.
"""
catalog = self.authenticator.authenticate()
if self.service_url:
possible_service_url = None
else:
if self.endpoint_type == "publicURL":
possible_service_url = catalog.get_public_url()
elif self.endpoint_type == "adminURL":
possible_service_url = catalog.get_management_url()
self.authenticate_with_token(catalog.get_token(), possible_service_url)
def authenticate_with_token(self, token, service_url=None):
self.auth_token = token
if not self.service_url:
if not service_url:
raise exceptions.ServiceUrlNotGiven()
else:
self.service_url = service_url

View File

@@ -0,0 +1,10 @@
import urllib
def proc_template(template, **kwargs):
"""
Processes a templated URL by substituting the
dictionary args and returning the strings.
"""
return template.format(**dict([(k, urllib.quote(v))
for k, v in kwargs.items()]))

View File

@@ -1,164 +0,0 @@
class UnsupportedVersion(Exception):
"""Indicates that the user is trying to use an unsupported
version of the API"""
pass
class CommandError(Exception):
pass
class AuthorizationFailure(Exception):
pass
class NoUniqueMatch(Exception):
pass
class NoTokenLookupException(Exception):
"""This form of authentication does not support looking up
endpoints from an existing token."""
pass
class EndpointNotFound(Exception):
"""Could not find Service or Region in Service Catalog."""
pass
class AuthUrlNotGiven(EndpointNotFound):
"""The auth url was not given."""
pass
class ServiceUrlNotGiven(EndpointNotFound):
"""The service url was not given."""
pass
class ResponseFormatError(Exception):
"""Could not parse the response format."""
pass
class AmbiguousEndpoints(Exception):
"""Found more than one matching endpoint in Service Catalog."""
def __init__(self, endpoints=None):
self.endpoints = endpoints
def __str__(self):
return "AmbiguousEndpoints: %s" % repr(self.endpoints)
class ClientException(Exception):
"""
The base exception class for all exceptions this library raises.
"""
def __init__(self, code, message=None, details=None, request_id=None):
self.code = code
self.message = message or self.__class__.message
self.details = details
self.request_id = request_id
def __str__(self):
formatted_string = "%s (HTTP %s)" % (self.message, self.code)
if self.request_id:
formatted_string += " (Request-ID: %s)" % self.request_id
return formatted_string
class BadRequest(ClientException):
"""
HTTP 400 - Bad request: you sent some malformed data.
"""
http_status = 400
message = "Bad request"
class Unauthorized(ClientException):
"""
HTTP 401 - Unauthorized: bad credentials.
"""
http_status = 401
message = "Unauthorized"
class Forbidden(ClientException):
"""
HTTP 403 - Forbidden: your credentials don't give you access to this
resource.
"""
http_status = 403
message = "Forbidden"
class NotFound(ClientException):
"""
HTTP 404 - Not found
"""
http_status = 404
message = "Not found"
class OverLimit(ClientException):
"""
HTTP 413 - Over limit: you're over the API limits for this time period.
"""
http_status = 413
message = "Over limit"
# NotImplemented is a python keyword.
class HTTPNotImplemented(ClientException):
"""
HTTP 501 - Not Implemented: the server does not support this operation.
"""
http_status = 501
message = "Not Implemented"
class UnprocessableEntity(ClientException):
"""
HTTP 422 - Unprocessable Entity: The request cannot be processed.
"""
http_status = 422
message = "Unprocessable Entity"
# In Python 2.4 Exception is old-style and thus doesn't have a __subclasses__()
# so we can do this:
# _code_map = dict((c.http_status, c)
# for c in ClientException.__subclasses__())
#
# Instead, we have to hardcode it:
_code_map = dict((c.http_status, c) for c in [BadRequest, Unauthorized,
Forbidden, NotFound, OverLimit,
HTTPNotImplemented,
UnprocessableEntity])
def from_response(response, body):
"""
Return an instance of an ClientException or subclass
based on an httplib2 response.
Usage::
resp, body = http.request(...)
if resp.status != 200:
raise exception_from_response(resp, body)
"""
cls = _code_map.get(response.status, ClientException)
if body:
message = "n/a"
details = "n/a"
if hasattr(body, 'keys'):
error = body[body.keys()[0]]
message = error.get('message', None)
details = error.get('details', None)
return cls(code=response.status, message=message, details=details)
else:
request_id = response.get('x-compute-request-id')
return cls(code=response.status, request_id=request_id)

14
barbicanclient/secrets.py Normal file
View File

@@ -0,0 +1,14 @@
class Secret(object):
"""
A secret is any data the user has stored in the key management system.
"""
def __init__(self, connection, json):
"""
Builds a secret object from a json representation. Includes the connection object for subtasks.
"""
def __repr__(self):
return "<Secret %s>" % self.name

31
examples/secrets.py Normal file
View File

@@ -0,0 +1,31 @@
import argparse
from barbicanclient import client
IDENTITY = 'https://identity.api.rackspacecloud.com/v2.0'
ENDPOINT = 'https://barbican.api.rackspacecloud.com/v1/'
def list_secrets(username, password):
connection = client.Connection(IDENTITY, username, password)
secrets = connection.list_secrets()
print secrets.list()
def parse_args():
parser = argparse.ArgumentParser(description='Testing code for barbican secrets api resource.')
parser.add_argument('--username', help='The keystone username used for for authentication')
parser.add_argument('--password', help='The keystone password used for for authentication')
parser.add_argument('--keystone', default=IDENTITY,
help='The keystone endpoint used for for authentication')
parser.add_argument('--endpoint', default=ENDPOINT,
help='The barbican endpoint to test against')
args = parser.parse_args()
return args
if __name__ == '__main__':
args = parse_args()
list_secrets(args.username, args.password)

View File

@@ -1,407 +0,0 @@
import contextlib
from testtools import TestCase
from barbicanclient.common import auth
from mock import Mock
from barbicanclient import exceptions
#Unit tests for the classes and functions in auth.py.
def check_url_none(test_case, auth_class):
# url is None, it must throw exception
authObj = auth_class(url=None, type=auth_class, client=None,
username=None, password=None, tenant=None)
try:
authObj.authenticate()
test_case.fail("AuthUrlNotGiven exception expected")
except exceptions.AuthUrlNotGiven:
pass
class AuthenticatorTest(TestCase):
def setUp(self):
super(AuthenticatorTest, self).setUp()
self.orig_load = auth.ServiceCatalog._load
self.orig__init = auth.ServiceCatalog.__init__
def tearDown(self):
super(AuthenticatorTest, self).tearDown()
auth.ServiceCatalog._load = self.orig_load
auth.ServiceCatalog.__init__ = self.orig__init
def test_get_authenticator_cls(self):
class_list = (auth.KeyStoneV2Authenticator,
auth.RaxAuthenticator,
auth.Auth1_1,
auth.FakeAuth)
for c in class_list:
self.assertEqual(c, auth.get_authenticator_cls(c))
class_names = {"keystone": auth.KeyStoneV2Authenticator,
"rax": auth.RaxAuthenticator,
"auth1.1": auth.Auth1_1,
"fake": auth.FakeAuth}
for cn in class_names.keys():
self.assertEqual(class_names[cn], auth.get_authenticator_cls(cn))
cls_or_name = "_unknown_"
self.assertRaises(ValueError, auth.get_authenticator_cls, cls_or_name)
def test__authenticate(self):
authObj = auth.Authenticator(Mock(), auth.KeyStoneV2Authenticator,
Mock(), Mock(), Mock(), Mock())
# test response code 200
resp = Mock()
resp.status = 200
body = "test_body"
auth.ServiceCatalog._load = Mock(return_value=1)
authObj.client._time_request = Mock(return_value=(resp, body))
sc = authObj._authenticate(Mock(), Mock())
self.assertEqual(body, sc.catalog)
# test AmbiguousEndpoints exception
auth.ServiceCatalog.__init__ = \
Mock(side_effect=exceptions.AmbiguousEndpoints)
self.assertRaises(exceptions.AmbiguousEndpoints,
authObj._authenticate, Mock(), Mock())
# test handling KeyError and raising AuthorizationFailure exception
auth.ServiceCatalog.__init__ = Mock(side_effect=KeyError)
self.assertRaises(exceptions.AuthorizationFailure,
authObj._authenticate, Mock(), Mock())
# test EndpointNotFound exception
mock = Mock(side_effect=exceptions.EndpointNotFound)
auth.ServiceCatalog.__init__ = mock
self.assertRaises(exceptions.EndpointNotFound,
authObj._authenticate, Mock(), Mock())
mock.side_effect = None
# test response code 305
resp.__getitem__ = Mock(return_value='loc')
resp.status = 305
body = "test_body"
authObj.client._time_request = Mock(return_value=(resp, body))
l = authObj._authenticate(Mock(), Mock())
self.assertEqual('loc', l)
# test any response code other than 200 and 305
resp.status = 404
exceptions.from_response = Mock(side_effect=ValueError)
self.assertRaises(ValueError, authObj._authenticate, Mock(), Mock())
def test_authenticate(self):
authObj = auth.Authenticator(Mock(), auth.KeyStoneV2Authenticator,
Mock(), Mock(), Mock(), Mock())
self.assertRaises(NotImplementedError, authObj.authenticate)
class KeyStoneV2AuthenticatorTest(TestCase):
def test_authenticate(self):
# url is None
check_url_none(self, auth.KeyStoneV2Authenticator)
# url is not None, so it must not throw exception
url = "test_url"
cls_type = auth.KeyStoneV2Authenticator
authObj = auth.KeyStoneV2Authenticator(url=url, type=cls_type,
client=None, username=None,
password=None, tenant=None)
def side_effect_func(url):
return url
mock = Mock()
mock.side_effect = side_effect_func
authObj._v2_auth = mock
r = authObj.authenticate()
self.assertEqual(url, r)
def test__v2_auth(self):
username = "reddwarf_user"
password = "reddwarf_password"
tenant = "tenant"
cls_type = auth.KeyStoneV2Authenticator
authObj = auth.KeyStoneV2Authenticator(url=None, type=cls_type,
client=None,
username=username,
password=password,
tenant=tenant)
def side_effect_func(url, body):
return body
mock = Mock()
mock.side_effect = side_effect_func
authObj._authenticate = mock
body = authObj._v2_auth(Mock())
self.assertEqual(username,
body['auth']['passwordCredentials']['username'])
self.assertEqual(password,
body['auth']['passwordCredentials']['password'])
self.assertEqual(tenant, body['auth']['tenantName'])
class Auth1_1Test(TestCase):
def test_authenticate(self):
# handle when url is None
check_url_none(self, auth.Auth1_1)
# url is not none
username = "reddwarf_user"
password = "reddwarf_password"
url = "test_url"
authObj = auth.Auth1_1(url=url,
type=auth.Auth1_1,
client=None, username=username,
password=password, tenant=None)
def side_effect_func(auth_url, body, root_key):
return auth_url, body, root_key
mock = Mock()
mock.side_effect = side_effect_func
authObj._authenticate = mock
auth_url, body, root_key = authObj.authenticate()
self.assertEqual(username, body['credentials']['username'])
self.assertEqual(password, body['credentials']['key'])
self.assertEqual(auth_url, url)
self.assertEqual('auth', root_key)
class RaxAuthenticatorTest(TestCase):
def test_authenticate(self):
# url is None
check_url_none(self, auth.RaxAuthenticator)
# url is not None, so it must not throw exception
url = "test_url"
authObj = auth.RaxAuthenticator(url=url,
type=auth.RaxAuthenticator,
client=None, username=None,
password=None, tenant=None)
def side_effect_func(url):
return url
mock = Mock()
mock.side_effect = side_effect_func
authObj._rax_auth = mock
r = authObj.authenticate()
self.assertEqual(url, r)
def test__rax_auth(self):
username = "reddwarf_user"
password = "reddwarf_password"
tenant = "tenant"
authObj = auth.RaxAuthenticator(url=None,
type=auth.RaxAuthenticator,
client=None, username=username,
password=password, tenant=tenant)
def side_effect_func(url, body):
return body
mock = Mock()
mock.side_effect = side_effect_func
authObj._authenticate = mock
body = authObj._rax_auth(Mock())
v = body['auth']['RAX-KSKEY:apiKeyCredentials']['username']
self.assertEqual(username, v)
v = body['auth']['RAX-KSKEY:apiKeyCredentials']['apiKey']
self.assertEqual(password, v)
v = body['auth']['RAX-KSKEY:apiKeyCredentials']['tenantName']
self.assertEqual(tenant, v)
class FakeAuthTest(TestCase):
def test_authenticate(self):
tenant = "tenant"
authObj = auth.FakeAuth(url=None,
type=auth.FakeAuth,
client=None, username=None,
password=None, tenant=tenant)
fc = authObj.authenticate()
public_url = "%s/%s" % ('http://localhost:8779/v1.0', tenant)
self.assertEqual(public_url, fc.get_public_url())
self.assertEqual(tenant, fc.get_token())
class ServiceCatalogTest(TestCase):
def setUp(self):
super(ServiceCatalogTest, self).setUp()
self.orig_url_for = auth.ServiceCatalog._url_for
self.orig__init__ = auth.ServiceCatalog.__init__
auth.ServiceCatalog.__init__ = Mock(return_value=None)
self.test_url = "http://localhost:1234/test"
def tearDown(self):
super(ServiceCatalogTest, self).tearDown()
auth.ServiceCatalog._url_for = self.orig_url_for
auth.ServiceCatalog.__init__ = self.orig__init__
def test__load(self):
url = "random_url"
auth.ServiceCatalog._url_for = Mock(return_value=url)
# when service_url is None
scObj = auth.ServiceCatalog()
scObj.region = None
scObj.service_url = None
scObj._load()
self.assertEqual(url, scObj.public_url)
self.assertEqual(url, scObj.management_url)
# service url is not None
service_url = "service_url"
scObj = auth.ServiceCatalog()
scObj.region = None
scObj.service_url = service_url
scObj._load()
self.assertEqual(service_url, scObj.public_url)
self.assertEqual(service_url, scObj.management_url)
def test_get_token(self):
test_id = "test_id"
scObj = auth.ServiceCatalog()
scObj.root_key = "root_key"
scObj.catalog = dict()
scObj.catalog[scObj.root_key] = dict()
scObj.catalog[scObj.root_key]['token'] = dict()
scObj.catalog[scObj.root_key]['token']['id'] = test_id
self.assertEqual(test_id, scObj.get_token())
def test_get_management_url(self):
test_mng_url = "test_management_url"
scObj = auth.ServiceCatalog()
scObj.management_url = test_mng_url
self.assertEqual(test_mng_url, scObj.get_management_url())
def test_get_public_url(self):
test_public_url = "test_public_url"
scObj = auth.ServiceCatalog()
scObj.public_url = test_public_url
self.assertEqual(test_public_url, scObj.get_public_url())
def test__url_for(self):
scObj = auth.ServiceCatalog()
# case for no endpoint found
self.case_no_endpoint_match(scObj)
# case for empty service catalog
self.case_endpoing_with_empty_catalog(scObj)
# more than one matching endpoints
self.case_ambiguous_endpoint(scObj)
# happy case
self.case_unique_endpoint(scObj)
# testing if-statements in for-loop to iterate services in catalog
self.case_iterating_services_in_catalog(scObj)
def case_no_endpoint_match(self, scObj):
# empty endpoint list
scObj.catalog = dict()
scObj.catalog['endpoints'] = list()
self.assertRaises(exceptions.EndpointNotFound, scObj._url_for)
def side_effect_func_ep(attr):
return "test_attr_value"
# simulating dict
endpoint = Mock()
mock = Mock()
mock.side_effect = side_effect_func_ep
endpoint.__getitem__ = mock
scObj.catalog['endpoints'].append(endpoint)
# not-empty list but not matching endpoint
filter_value = "not_matching_value"
self.assertRaises(exceptions.EndpointNotFound, scObj._url_for,
attr="test_attr", filter_value=filter_value)
filter_value = "test_attr_value" # so that we have an endpoint match
scObj.root_key = "access"
scObj.catalog[scObj.root_key] = dict()
self.assertRaises(exceptions.EndpointNotFound, scObj._url_for,
attr="test_attr", filter_value=filter_value)
def case_endpoing_with_empty_catalog(self, scObj):
# first, test with empty catalog, this should pass since
# there is already enpoint added
scObj.catalog[scObj.root_key]['serviceCatalog'] = list()
endpoint = scObj.catalog['endpoints'][0]
endpoint.get = Mock(return_value=self.test_url)
r_url = scObj._url_for(attr="test_attr",
filter_value="test_attr_value")
self.assertEqual(self.test_url, r_url)
def case_ambiguous_endpoint(self, scObj):
scObj.service_type = "reddwarf"
scObj.service_name = "test_service_name"
def side_effect_func_service(key):
if key == "type":
return "reddwarf"
elif key == "name":
return "test_service_name"
return None
mock1 = Mock()
mock1.side_effect = side_effect_func_service
service1 = Mock()
service1.get = mock1
endpoint2 = {"test_attr": "test_attr_value"}
service1.__getitem__ = Mock(return_value=[endpoint2])
scObj.catalog[scObj.root_key]['serviceCatalog'] = [service1]
self.assertRaises(exceptions.AmbiguousEndpoints, scObj._url_for,
attr="test_attr", filter_value="test_attr_value")
def case_unique_endpoint(self, scObj):
# changing the endpoint2 attribute to pass the filter
service1 = scObj.catalog[scObj.root_key]['serviceCatalog'][0]
endpoint2 = service1[0][0]
endpoint2["test_attr"] = "new value not matching filter"
r_url = scObj._url_for(attr="test_attr",
filter_value="test_attr_value")
self.assertEqual(self.test_url, r_url)
def case_iterating_services_in_catalog(self, scObj):
service1 = scObj.catalog[scObj.root_key]['serviceCatalog'][0]
scObj.catalog = dict()
scObj.root_key = "access"
scObj.catalog[scObj.root_key] = dict()
scObj.service_type = "no_match"
scObj.catalog[scObj.root_key]['serviceCatalog'] = [service1]
self.assertRaises(exceptions.EndpointNotFound, scObj._url_for)
scObj.service_type = "database"
scObj.service_name = "no_match"
self.assertRaises(exceptions.EndpointNotFound, scObj._url_for)
# no endpoints and no 'serviceCatalog' in catalog => raise exception
scObj = auth.ServiceCatalog()
scObj.catalog = dict()
scObj.root_key = "access"
scObj.catalog[scObj.root_key] = dict()
scObj.catalog[scObj.root_key]['serviceCatalog'] = []
self.assertRaises(exceptions.EndpointNotFound, scObj._url_for,
attr="test_attr", filter_value="test_attr_value")

View File

@@ -1,2 +1,4 @@
httplib2>=0.7.7
argparse>=1.2.1
argparse>=1.2.1
python-keystoneclient>=0.2.3
eventlet>=0.12.1