summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortsv <tsv@hp.com>2014-03-12 14:37:02 -0700
committertsv <tsv@hp.com>2014-06-09 13:09:47 -0600
commit01d23227b0fa2b17038dfb3710e700b553778e15 (patch)
tree78c6f47e59873976ff8453147b6ec993cc0f947a
parentf392e55383c001617995713a344c364d16a66e29 (diff)
downloadpython-barbicanclient-01d23227b0fa2b17038dfb3710e700b553778e15.tar.gz
Add Keystone V3 compliant session/auth plugin support
Barbican client uses Keystone V2 client via a Barbican auth plugin. It also uses a regular requests.Session(). This commit adds support for keystone session and replaces the heavy keystone client with a lighter V2/V3 Password auth plugin. This change is backwards compatible; it supports existing callers and keystone session/plugin support. On the testing front, this commit also introduces httpretty along with a keystoneclient fixture. The keystoneclient fixture could eventually be called directly from keystoneclient module instead of copying it over here. Testing the keystone session/plugin support is done mainly using httpretty Patches: 7: Refactored Client.__init__ for better readability 8: Fixing pep8/py33 errors w.r.t new tox.ini changes DocImpact bp/barbican-client-has-to-be-keystone-v3.0-complaint Change-Id: I8ef178b0338fe430a64c30bfe193406aabf3caf1
-rw-r--r--barbicanclient/barbican.py62
-rw-r--r--barbicanclient/client.py131
-rw-r--r--barbicanclient/common/auth.py96
-rw-r--r--barbicanclient/openstack/common/timeutils.py2
-rw-r--r--barbicanclient/test/common/test_auth.py3
-rw-r--r--barbicanclient/test/keystone_client_fixtures.py189
-rw-r--r--barbicanclient/test/test_barbican.py170
-rw-r--r--barbicanclient/test/test_client.py293
-rw-r--r--requirements.txt3
-rw-r--r--test-requirements.txt1
10 files changed, 869 insertions, 81 deletions
diff --git a/barbicanclient/barbican.py b/barbicanclient/barbican.py
index ea2297f..8561d2a 100644
--- a/barbicanclient/barbican.py
+++ b/barbicanclient/barbican.py
@@ -25,6 +25,7 @@ from barbicanclient import client
class Barbican:
+
def __init__(self):
self.parser = self._get_main_parser()
self.subparsers = self.parser.add_subparsers(
@@ -59,10 +60,22 @@ class Barbican:
metavar='<auth-user-name>',
default=client.env('OS_USERNAME'),
help='Defaults to env[OS_USERNAME].')
+ parser.add_argument('--os-user-id',
+ metavar='<auth-user-id>',
+ default=client.env('OS_USER_ID'),
+ help='Defaults to env[OS_USER_ID].')
parser.add_argument('--os-password', '-P',
metavar='<auth-password>',
default=client.env('OS_PASSWORD'),
help='Defaults to env[OS_PASSWORD].')
+ parser.add_argument('--os-user-domain-id',
+ metavar='<auth-user-domain-id>',
+ default=client.env('OS_USER_DOMAIN_ID'),
+ help='Defaults to env[OS_USER_DOMAIN_ID].')
+ parser.add_argument('--os-user-domain-name',
+ metavar='<auth-user-domain-name>',
+ default=client.env('OS_USER_DOMAIN_NAME'),
+ help='Defaults to env[OS_USER_DOMAIN_NAME].')
parser.add_argument('--os-tenant-name', '-T',
metavar='<auth-tenant-name>',
default=client.env('OS_TENANT_NAME'),
@@ -71,6 +84,28 @@ class Barbican:
metavar='<tenant-id>',
default=client.env('OS_TENANT_ID'),
help='Defaults to env[OS_TENANT_ID].')
+ parser.add_argument('--os-project-id',
+ metavar='<auth-project-id>',
+ default=client.env('OS_PROJECT__ID'),
+ help='Another way to specify tenant ID. '
+ 'This option is mutually exclusive with '
+ ' --os-tenant-id. '
+ 'Defaults to env[OS_PROJECT_ID].')
+ parser.add_argument('--os-project-name',
+ metavar='<auth-project-name>',
+ default=client.env('OS_PROJECT_NAME'),
+ help='Another way to specify tenant name. '
+ 'This option is mutually exclusive with '
+ ' --os-tenant-name. '
+ 'Defaults to env[OS_PROJECT_NAME].')
+ parser.add_argument('--os-project-domain-id',
+ metavar='<auth-project-domain-id>',
+ default=client.env('OS_PROJECT_DOMAIN_ID'),
+ help='Defaults to env[OS_PROJECT_DOMAIN_ID].')
+ parser.add_argument('--os-project-domain-name',
+ metavar='<auth-project-domain-name>',
+ default=client.env('OS_PROJECT_DOMAIN_NAME'),
+ help='Defaults to env[OS_PROJECT_DOMAIN_NAME].')
parser.add_argument('--endpoint', '-E',
metavar='<barbican-url>',
default=client.env('BARBICAN_ENDPOINT'),
@@ -315,21 +350,24 @@ class Barbican:
def execute(self, **kwargs):
args = self.parser.parse_args(kwargs.get('argv'))
if args.no_auth:
+ if not all([args.endpoint, args.os_tenant_id or
+ args.os_project_id]):
+ self.parser.exit(
+ status=1,
+ message='ERROR: please specify --endpoint and '
+ '--os-project-id(or --os-tenant-id)\n')
self.client = client.Client(endpoint=args.endpoint,
- tenant_id=args.os_tenant_id,
+ tenant_id=args.os_tenant_id or
+ args.os_project_id,
insecure=args.insecure)
- elif all([args.os_auth_url, args.os_username, args.os_password,
- args.os_tenant_name]):
- self._keystone = auth.KeystoneAuthV2(
- auth_url=args.os_auth_url,
- username=args.os_username,
- password=args.os_password,
- tenant_name=args.os_tenant_name,
- insecure=args.insecure
- )
- self.client = client.Client(auth_plugin=self._keystone,
+ elif all([args.os_auth_url, args.os_user_id or args.os_username,
+ args.os_password, args.os_tenant_name or args.os_tenant_id or
+ args.os_project_name or args.os_project_id]):
+ ks_session = auth.create_keystone_auth_session(args)
+ self.client = client.Client(session=ks_session,
endpoint=args.endpoint,
- tenant_id=args.os_tenant_id,
+ tenant_id=args.os_tenant_id or
+ args.os_project_id,
insecure=args.insecure)
else:
self.parser.exit(
diff --git a/barbicanclient/client.py b/barbicanclient/client.py
index 2d7bc52..1115ed4 100644
--- a/barbicanclient/client.py
+++ b/barbicanclient/client.py
@@ -16,8 +16,11 @@ import json
import logging
import os
-import requests
+from keystoneclient.auth.base import BaseAuthPlugin
+from keystoneclient import exceptions
+from keystoneclient import session as ks_session
+from barbicanclient.common.auth import KeystoneAuthPluginWrapper
from barbicanclient.openstack.common.gettextutils import _
from barbicanclient import orders
from barbicanclient import secrets
@@ -28,22 +31,27 @@ LOG = logging.getLogger(__name__)
class HTTPError(Exception):
+
"""Base exception for HTTP errors."""
+
def __init__(self, message):
super(HTTPError, self).__init__(message)
class HTTPServerError(HTTPError):
+
"""Raised for 5xx responses from the server."""
pass
class HTTPClientError(HTTPError):
+
"""Raised for 4xx responses from the server."""
pass
class HTTPAuthError(HTTPError):
+
"""Raised for 401 Unauthorized responses from the server."""
pass
@@ -51,49 +59,43 @@ class HTTPAuthError(HTTPError):
class Client(object):
def __init__(self, session=None, auth_plugin=None, endpoint=None,
- tenant_id=None, insecure=False):
+ tenant_id=None, insecure=False, service_type='keystore',
+ interface='public'):
"""
Barbican client object used to interact with barbican service.
+ :param session: This can be either requests.Session or
+ keystoneclient.session.Session
:param auth_plugin: Authentication backend plugin
- defaults to None
+ defaults to None. This can also be a keystoneclient authentication
+ plugin.
:param endpoint: Barbican endpoint url. Required when not using
an auth_plugin. When not provided, the client will try to
fetch this from the auth service catalog
:param tenant_id: The tenant ID used for context in barbican.
Required when not using auth_plugin. When not provided,
the client will try to get this from the auth_plugin.
+ :param insecure: Explicitly allow barbicanclient to perform
+ "insecure" TLS (https) requests. The server's certificate
+ will not be verified against any certificate authorities.
+ This option should be used with caution.
+ :param service_type: Used as an endpoint filter when using a
+ keystone auth plugin. Defaults to 'keystore'
+ :param interface: Another endpoint filter. Defaults to 'public'
"""
- LOG.debug("Creating Client object")
-
- self._session = session or requests.Session()
- self.verify = not insecure
- self.auth_plugin = auth_plugin
-
- if self.auth_plugin is not None:
- try:
- self._barbican_url = self.auth_plugin.barbican_url
- except:
- if endpoint:
- self._barbican_url = endpoint
- else:
- raise
-
- self._tenant_id = self.auth_plugin.tenant_id
- self._session.headers.update(
- {'X-Auth-Token': self.auth_plugin.auth_token}
- )
+ LOG.debug(_("Creating Client object"))
+ self._wrap_session_with_keystone_if_required(session, insecure)
+ auth_plugin = self._update_session_auth_plugin(auth_plugin)
+
+ if auth_plugin:
+ self._barbican_url = self._session.get_endpoint(
+ service_type=service_type, interface=interface)
+ self._tenant_id = self._get_tenant_id(self._session, auth_plugin)
else:
- if endpoint is None:
- raise ValueError('Barbican endpoint url must be provided, or '
- 'must be available from auth_plugin')
- if tenant_id is None:
- raise ValueError('Tenant ID must be provided, or must be'
- ' available from the auth_plugin')
- if endpoint.endswith('/'):
- self._barbican_url = endpoint[:-1]
- else:
- self._barbican_url = endpoint
+ # neither auth_plugin is provided nor it is available from session
+ # fallback to passed in parameters
+ self._validate_endpoint_and_tenant_id(endpoint, tenant_id)
+ self._barbican_url = self._get_normalized_endpoint(endpoint)
self._tenant_id = tenant_id
self.base_url = '{0}/{1}'.format(self._barbican_url, self._tenant_id)
@@ -101,27 +103,80 @@ class Client(object):
self.orders = orders.OrderManager(self)
self.verifications = verifications.VerificationManager(self)
+ def _wrap_session_with_keystone_if_required(self, session, insecure):
+ # if session is not a keystone session, wrap it
+ if not isinstance(session, ks_session.Session):
+ self._session = ks_session.Session(
+ session=session, verify=not insecure)
+ else:
+ self._session = session
+
+ def _update_session_auth_plugin(self, auth_plugin):
+ # if auth_plugin is not provided and the session
+ # has one, use it
+ using_auth_from_session = False
+ if auth_plugin is None and self._session.auth is not None:
+ auth_plugin = self._session.auth
+ using_auth_from_session = True
+
+ ks_auth_plugin = auth_plugin
+ # if auth_plugin is not a keystone plugin, wrap it
+ if auth_plugin and not isinstance(auth_plugin, BaseAuthPlugin):
+ ks_auth_plugin = KeystoneAuthPluginWrapper(auth_plugin)
+
+ # if auth_plugin is provided, override the session's auth with it
+ if not using_auth_from_session:
+ self._session.auth = ks_auth_plugin
+
+ return auth_plugin
+
+ def _validate_endpoint_and_tenant_id(self, endpoint, tenant_id):
+ if endpoint is None:
+ raise ValueError('Barbican endpoint url must be provided, or '
+ 'must be available from auth_plugin or '
+ 'keystone_client')
+ if tenant_id is None:
+ raise ValueError('Tenant ID must be provided, or must be '
+ 'available from the auth_plugin or '
+ 'keystone-client')
+
+ def _get_normalized_endpoint(self, endpoint):
+ if endpoint.endswith('/'):
+ endpoint = endpoint[:-1]
+ return endpoint
+
+ def _get_tenant_id(self, session, auth_plugin):
+ if isinstance(auth_plugin, BaseAuthPlugin):
+ # this is a keystoneclient auth plugin
+ if hasattr(auth_plugin, 'get_access'):
+ return auth_plugin.get_access(session).project_id
+ else:
+ # not an identity auth plugin and we don't know how to lookup
+ # the tenant_id
+ raise ValueError('Unable to obtain tenant_id from auth plugin')
+ else:
+ # this is a Barbican auth plugin
+ return auth_plugin.tenant_id
+
def get(self, href, params=None):
headers = {'Accept': 'application/json'}
- resp = self._session.get(href, params=params, headers=headers,
- verify=self.verify)
+ resp = self._session.get(href, params=params, headers=headers)
self._check_status_code(resp)
return resp.json()
def get_raw(self, href, headers):
- resp = self._session.get(href, headers=headers, verify=self.verify)
+ resp = self._session.get(href, headers=headers)
self._check_status_code(resp)
return resp.content
def delete(self, href):
- resp = self._session.delete(href, verify=self.verify)
+ resp = self._session.delete(href)
self._check_status_code(resp)
def post(self, path, data):
url = '{0}/{1}/'.format(self.base_url, path)
headers = {'content-type': 'application/json'}
- resp = self._session.post(url, data=json.dumps(data), headers=headers,
- verify=self.verify)
+ resp = self._session.post(url, data=json.dumps(data), headers=headers)
self._check_status_code(resp)
return resp.json()
diff --git a/barbicanclient/common/auth.py b/barbicanclient/common/auth.py
index d858101..2944f28 100644
--- a/barbicanclient/common/auth.py
+++ b/barbicanclient/common/auth.py
@@ -16,22 +16,112 @@ import abc
import json
import logging
+from keystoneclient.auth.base import BaseAuthPlugin
from keystoneclient.v2_0 import client as ksclient
from keystoneclient import exceptions
+from keystoneclient import session as ks_session
+from keystoneclient import discover
import requests
import six
LOG = logging.getLogger(__name__)
+"""
+This class is for backward compatibility only and is an
+adapter for using barbican style auth_plugin in place of
+the recommended keystone auth_plugin.
+"""
+
+
+class KeystoneAuthPluginWrapper(BaseAuthPlugin):
+
+ def __init__(self, barbican_auth_plugin):
+ self.barbican_auth_plugin = barbican_auth_plugin
+
+ def get_token(self, session, **kwargs):
+ return self.barbican_auth_plugin.auth_token
+
+ def get_endpoint(self, session, **kwargs):
+ # NOTE(gyee): this is really a hack as Barbican auth plugin only
+ # cares about Barbican endpoint.
+ return self.barbican_auth_plugin.barbican_url
+
+
+def _discover_keystone_info(auth_url):
+ # From the auth_url, figure the keystone client version to use
+ try:
+ disco = discover.Discover(auth_url=auth_url)
+ versions = disco.available_versions()
+ except:
+ error_msg = 'Error: failed to discover keystone version '\
+ 'using auth_url: %s' % auth_url
+ raise ValueError(error_msg)
+ else:
+ # use the first one in the list
+ if len(versions) > 0:
+ version = versions[0]['id']
+ else:
+ error_msg = 'Error: Unable to discover a keystone plugin '\
+ 'for the specified --os-auth-url.\n'\
+ 'Please provide a valid auth url'
+ raise ValueError(error_msg)
+ try:
+ # the input auth_url may not have the version info in the
+ # url. get the correct auth_url from the versions
+ auth_url = versions[0]['links'][0]['href']
+ except:
+ raise ValueError('Error: Unable to discover the correct auth url')
+ return version, auth_url
+
+
+def create_keystone_auth_session(args):
+ """
+ Creates an authenticated keystone session using
+ the supplied arguments.
+ """
+ version, auth_url = _discover_keystone_info(args.os_auth_url)
+ project_name = args.os_project_name or args.os_tenant_name
+ project_id = args.os_project_id or args.os_tenant_id
+
+ # FIXME(tsv): we are depending on the keystone version interface here.
+ # If keystone changes it, this code will need to be changed accordingly
+ if version == 'v2.0':
+ # create a V2 Password plugin
+ from keystoneclient.auth.identity import v2
+ auth_plugin = v2.Password(auth_url=auth_url,
+ username=args.os_username,
+ password=args.os_password,
+ tenant_name=project_name,
+ tenant_id=project_id)
+ elif version == 'v3.0':
+ # create a V3 Password plugin
+ from keystoneclient.auth.identity import v3
+ auth_plugin = v3.Password(auth_url=auth_url,
+ username=args.os_username,
+ user_id=args.os_user_id,
+ user_domain_name=args.os_user_domain_name,
+ user_domain_id=args.os_user_domain_id,
+ password=args.os_password,
+ project_id=project_id,
+ project_name=project_name,
+ project_domain_id=args.os_project_domain_id,
+ project_domain_name=args.
+ os_project_domain_name)
+ else:
+ raise ValueError('Error: unsupported keystone version!')
+ return ks_session.Session(auth=auth_plugin, verify=not args.insecure)
+
class AuthException(Exception):
+
"""Raised when authorization fails."""
pass
@six.add_metaclass(abc.ABCMeta)
class AuthPluginBase(object):
+
"""Base class for Auth plugins."""
@abc.abstractproperty
@@ -49,6 +139,7 @@ class AuthPluginBase(object):
class KeystoneAuthV2(AuthPluginBase):
+
def __init__(self, auth_url='', username='', password='',
tenant_name='', tenant_id='', insecure=False, keystone=None):
if not keystone:
@@ -58,7 +149,7 @@ class KeystoneAuthV2(AuthPluginBase):
' and tenant_id or tenant_name.')
self._barbican_url = None
- #TODO(dmend): make these configurable
+ # TODO(dmend): make these configurable
self._service_type = 'keystore'
self._endpoint_type = 'publicURL'
@@ -95,6 +186,7 @@ class KeystoneAuthV2(AuthPluginBase):
class RackspaceAuthV2(AuthPluginBase):
+
def __init__(self, auth_url='', username='', api_key='', password=''):
if not all([auth_url, username, api_key or password]):
raise ValueError('Please provide auth_url, username, api_key or '
@@ -141,7 +233,7 @@ class RackspaceAuthV2(AuthPluginBase):
LOG.error(msg)
raise AuthException(msg)
else:
- #TODO(dmend): get barbican_url from catalog
+ # TODO(dmend): get barbican_url from catalog
self._auth_token = data['access']['token']['id']
self.tenant_id = data['access']['token']['tenant']['id']
diff --git a/barbicanclient/openstack/common/timeutils.py b/barbicanclient/openstack/common/timeutils.py
index 52688a0..e4b8f1a 100644
--- a/barbicanclient/openstack/common/timeutils.py
+++ b/barbicanclient/openstack/common/timeutils.py
@@ -134,7 +134,7 @@ def set_time_override(override_time=None):
def advance_time_delta(timedelta):
"""Advance overridden time using a datetime.timedelta."""
- assert(not utcnow.override_time is None)
+ assert(utcnow.override_time is not None)
try:
for dt in utcnow.override_time:
dt += timedelta
diff --git a/barbicanclient/test/common/test_auth.py b/barbicanclient/test/common/test_auth.py
index d728aef..0a8890b 100644
--- a/barbicanclient/test/common/test_auth.py
+++ b/barbicanclient/test/common/test_auth.py
@@ -38,8 +38,7 @@ class WhenTestingKeystoneAuthentication(testtools.TestCase):
password=self.password,
tenant_name=self.tenant_name,
tenant_id=self.tenant_id,
- keystone=
- self.keystone_client)
+ keystone=self.keystone_client)
def test_endpoint_username_password_tenant_are_required(self):
with testtools.ExpectedException(ValueError):
diff --git a/barbicanclient/test/keystone_client_fixtures.py b/barbicanclient/test/keystone_client_fixtures.py
new file mode 100644
index 0000000..328ca69
--- /dev/null
+++ b/barbicanclient/test/keystone_client_fixtures.py
@@ -0,0 +1,189 @@
+# 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 copy
+import uuid
+
+from barbicanclient.openstack.common import jsonutils
+
+
+# these are copied from python-keystoneclient tests
+BASE_HOST = 'http://keystone.example.com'
+BASE_URL = "%s:5000/" % BASE_HOST
+UPDATED = '2013-03-06T00:00:00Z'
+
+V2_URL = "%sv2.0" % BASE_URL
+V2_DESCRIBED_BY_HTML = {'href': 'http://docs.openstack.org/api/'
+ 'openstack-identity-service/2.0/content/',
+ 'rel': 'describedby',
+ 'type': 'text/html'}
+V2_DESCRIBED_BY_PDF = {'href': 'http://docs.openstack.org/api/openstack-ident'
+ 'ity-service/2.0/identity-dev-guide-2.0.pdf',
+ 'rel': 'describedby',
+ 'type': 'application/pdf'}
+
+V2_VERSION = {'id': 'v2.0',
+ 'links': [{'href': V2_URL, 'rel': 'self'},
+ V2_DESCRIBED_BY_HTML, V2_DESCRIBED_BY_PDF],
+ 'status': 'stable',
+ 'updated': UPDATED}
+
+V3_URL = "%sv3" % BASE_URL
+V3_MEDIA_TYPES = [{'base': 'application/json',
+ 'type': 'application/vnd.openstack.identity-v3+json'},
+ {'base': 'application/xml',
+ 'type': 'application/vnd.openstack.identity-v3+xml'}]
+
+V3_VERSION = {'id': 'v3.0',
+ 'links': [{'href': V3_URL, 'rel': 'self'}],
+ 'media-types': V3_MEDIA_TYPES,
+ 'status': 'stable',
+ 'updated': UPDATED}
+
+
+def _create_version_list(versions):
+ return jsonutils.dumps({'versions': {'values': versions}})
+
+
+def _create_single_version(version):
+ return jsonutils.dumps({'version': version})
+
+
+V3_VERSION_LIST = _create_version_list([V3_VERSION, V2_VERSION])
+V2_VERSION_LIST = _create_version_list([V2_VERSION])
+
+V3_VERSION_ENTRY = _create_single_version(V3_VERSION)
+V2_VERSION_ENTRY = _create_single_version(V2_VERSION)
+
+BARBICAN_ENDPOINT = 'http://www.barbican.com/v1'
+
+
+def _get_normalized_token_data(**kwargs):
+ ref = copy.deepcopy(kwargs)
+ # normalized token data
+ ref['user_id'] = ref.get('user_id', uuid.uuid4().hex)
+ ref['username'] = ref.get('username', uuid.uuid4().hex)
+ ref['project_id'] = ref.get('project_id',
+ ref.get('tenant_id', uuid.uuid4().hex))
+ ref['project_name'] = ref.get('tenant_name',
+ ref.get('tenant_name', uuid.uuid4().hex))
+ ref['user_domain_id'] = ref.get('user_domain_id', uuid.uuid4().hex)
+ ref['user_domain_name'] = ref.get('user_domain_name', uuid.uuid4().hex)
+ ref['project_domain_id'] = ref.get('project_domain_id', uuid.uuid4().hex)
+ ref['project_domain_name'] = ref.get('project_domain_name',
+ uuid.uuid4().hex)
+ ref['roles'] = ref.get('roles', [{'name': uuid.uuid4().hex,
+ 'id': uuid.uuid4().hex}])
+ ref['roles_link'] = ref.get('roles_link', [])
+ ref['barbican_url'] = ref.get('barbican_url', BARBICAN_ENDPOINT)
+
+ return ref
+
+
+def generate_v2_project_scoped_token(**kwargs):
+ """Generate a Keystone V2 token based on auth request."""
+ ref = _get_normalized_token_data(**kwargs)
+
+ o = {'access': {'token': {'id': uuid.uuid4().hex,
+ 'expires': '2099-05-22T00:02:43.941430Z',
+ 'issued_at': '2013-05-21T00:02:43.941473Z',
+ 'tenant': {'enabled': True,
+ 'id': ref.get('project_id'),
+ 'name': ref.get('project_id')
+ }
+ },
+ 'user': {'id': ref.get('user_id'),
+ 'name': uuid.uuid4().hex,
+ 'username': ref.get('username'),
+ 'roles': ref.get('roles'),
+ 'roles_links': ref.get('roles_links')
+ }
+ }}
+
+ # we only care about Barbican and Keystone endpoints
+ o['access']['serviceCatalog'] = [
+ {'endpoints': [
+ {'publicURL': ref.get('barbican_url'),
+ 'id': uuid.uuid4().hex,
+ 'region': 'RegionOne'
+ }],
+ 'endpoints_links': [],
+ 'name': 'Barbican',
+ 'type': 'keystore'},
+ {'endpoints': [
+ {'publicURL': ref.get('auth_url'),
+ 'adminURL': ref.get('auth_url'),
+ 'id': uuid.uuid4().hex,
+ 'region': 'RegionOne'
+ }],
+ 'endpoint_links': [],
+ 'name': 'keystone',
+ 'type': 'identity'}]
+
+ return o
+
+
+def generate_v3_project_scoped_token(**kwargs):
+ """Generate a Keystone V3 token based on auth request."""
+ ref = _get_normalized_token_data(**kwargs)
+
+ o = {'token': {'expires_at': '2099-05-22T00:02:43.941430Z',
+ 'issued_at': '2013-05-21T00:02:43.941473Z',
+ 'methods': ['password'],
+ 'project': {'id': ref.get('project_id'),
+ 'name': ref.get('project_name'),
+ 'domain': {'id': ref.get('project_domain_id'),
+ 'name': ref.get(
+ 'project_domain_name')
+ }
+ },
+ 'user': {'id': ref.get('user_id'),
+ 'name': ref.get('username'),
+ 'domain': {'id': ref.get('user_domain_id'),
+ 'name': ref.get('user_domain_name')
+ }
+ },
+ 'roles': ref.get('roles')
+ }}
+
+ # we only care about Barbican and Keystone endpoints
+ o['token']['catalog'] = [
+ {'endpoints': [
+ {
+ 'id': uuid.uuid4().hex,
+ 'interface': 'public',
+ 'region': 'RegionTwo',
+ 'url': ref.get('barbican_url')
+ }],
+ 'id': uuid.uuid4().hex,
+ 'type': 'keystore'},
+ {'endpoints': [
+ {
+ 'id': uuid.uuid4().hex,
+ 'interface': 'public',
+ 'region': 'RegionTwo',
+ 'url': ref.get('auth_url')
+ },
+ {
+ 'id': uuid.uuid4().hex,
+ 'interface': 'admin',
+ 'region': 'RegionTwo',
+ 'url': ref.get('auth_url')
+ }],
+ 'id': uuid.uuid4().hex,
+ 'type': 'identity'}]
+
+ # token ID is conveyed via the X-Subject-Token header so we are generating
+ # one to stash there
+ token_id = uuid.uuid4().hex
+
+ return token_id, o
diff --git a/barbicanclient/test/test_barbican.py b/barbicanclient/test/test_barbican.py
index cdf2bc8..995ce1a 100644
--- a/barbicanclient/test/test_barbican.py
+++ b/barbicanclient/test/test_barbican.py
@@ -18,30 +18,188 @@ import sys
import six
import testtools
+import httpretty
+import uuid
+import json
+from barbicanclient.test import keystone_client_fixtures
+from barbicanclient.test import test_client
import barbicanclient.barbican
-class TestBarbican(testtools.TestCase):
+class WhenTestingBarbicanCLI(test_client.BaseEntityResource):
+
+ def setUp(self):
+ self._setUp('barbican')
+
def barbican(self, argstr):
"""Source: Keystone client's shell method in test_shell.py"""
orig = sys.stdout
+ orig_err = sys.stderr
clean_env = {}
_old_env, os.environ = os.environ, clean_env.copy()
+ exit_code = 0
try:
sys.stdout = six.StringIO()
+ sys.stderr = sys.stdout
_barbican = barbicanclient.barbican.Barbican()
_barbican.execute(argv=argstr.split())
except SystemExit:
exc_type, exc_value, exc_traceback = sys.exc_info()
- self.assertEqual(exc_value.code, 0)
+ exit_code = exc_value.code
finally:
- out = sys.stdout.getvalue()
+ if exit_code == 0:
+ out = sys.stdout.getvalue()
+ else:
+ out = sys.stderr.getvalue()
sys.stdout.close()
sys.stdout = orig
+ sys.stderr = orig_err
os.environ = _old_env
- return out
+ return exit_code, out
+
+ def test_should_show_usage_error_with_no_args(self):
+ args = ""
+ exit_code, out = self.barbican(args)
+ self.assertEqual(2, exit_code)
+ self.assertIn('error: the following', out)
- def test_help(self):
+ def test_should_show_usage_with_help_flag(self):
args = "-h"
- self.assertIn('usage: ', self.barbican(args))
+ exit_code, out = self.barbican(args)
+ self.assertEqual(0, exit_code)
+ self.assertIn('usage: ', out)
+
+ def test_should_error_if_noauth_and_authurl_both_specified(self):
+ args = "--no-auth --os-auth-url http://localhost:5000/v3"
+ exit_code, out = self.barbican(args)
+ self.assertEqual(2, exit_code)
+ self.assertIn(
+ 'error: argument --os-auth-url/-A: not allowed with '
+ 'argument --no-auth/-N', out)
+
+ def _expect_error_with_invalid_noauth_args(self, args):
+ exit_code, out = self.barbican(args)
+ self.assertEqual(1, exit_code)
+ expected_err_msg = 'ERROR: please specify --endpoint '\
+ 'and --os-project-id(or --os-tenant-id)\n'
+ self.assertIn(expected_err_msg, out)
+
+ def test_should_error_if_noauth_and_missing_endpoint_tenantid_args(self):
+ self._expect_error_with_invalid_noauth_args("--no-auth secret list")
+ self._expect_error_with_invalid_noauth_args(
+ "--no-auth --endpoint http://xyz secret list")
+ self._expect_error_with_invalid_noauth_args(
+ "--no-auth --os-tenant-id 123 secret list")
+ self._expect_error_with_invalid_noauth_args(
+ "--no-auth --os-project-id 123 secret list")
+
+ def _expect_success_code(self, args):
+ exit_code, out = self.barbican(args)
+ self.assertEqual(0, exit_code)
+
+ def _expect_failure_code(self, args, code=1):
+ exit_code, out = self.barbican(args)
+ self.assertEqual(code, exit_code)
+
+ def _assert_status_code_and_msg(self, args, expected_msg, code=1):
+ exit_code, out = self.barbican(args)
+ self.assertEqual(code, exit_code)
+ self.assertIn(expected_msg, out)
+
+ @httpretty.activate
+ def test_should_succeed_if_noauth_with_valid_args_specified(self):
+ list_secrets_content = '{"secrets": [], "total": 0}'
+ list_secrets_url = '%s%s/secrets' % (
+ self.endpoint, self.tenant_id)
+ httpretty.register_uri(
+ httpretty.GET, list_secrets_url,
+ body=list_secrets_content)
+ self._expect_success_code(
+ "--no-auth --endpoint %s --os-tenant-id %s secret list"
+ % (self.endpoint, self.tenant_id))
+
+ def test_should_error_if_required_keystone_auth_arguments_are_missing(
+ self):
+ expected_error_msg = 'ERROR: please specify authentication credentials'
+ self._assert_status_code_and_msg(
+ '--os-auth-url http://localhost:35357/v2.0 secret list',
+ expected_error_msg)
+ self._assert_status_code_and_msg('--os-auth-url '
+ 'http://localhost:35357/v2.0 '
+ '--os-username barbican '
+ '--os-password barbican '
+ 'secret list', expected_error_msg)
+
+
+class TestBarbicanWithKeystoneClient(testtools.TestCase):
+
+ def setUp(self):
+ super(TestBarbicanWithKeystoneClient, self).setUp()
+ self.kwargs = {'auth_url': keystone_client_fixtures.V3_URL}
+ for arg in ['username', 'password', 'project_name',
+ 'user_domain_name', 'project_domain_name']:
+ self.kwargs[arg] = uuid.uuid4().hex
+ self.barbican = barbicanclient.barbican.Barbican()
+
+ def _to_argv(self, **kwargs):
+ """Format Keystone client arguments into command line argv."""
+ argv = []
+ for k, v in six.iteritems(kwargs):
+ argv.append('--os-' + k.replace('_', '-'))
+ argv.append(v)
+ return argv
+
+ @httpretty.activate
+ def test_v2_auth(self):
+ self.kwargs['auth_url'] = keystone_client_fixtures.V2_URL
+ argv = self._to_argv(**self.kwargs)
+ argv.append('secret')
+ argv.append('list')
+ argv.append('-h')
+ argv.append('mysecretid')
+ # emulate Keystone version discovery
+ httpretty.register_uri(httpretty.GET,
+ self.kwargs['auth_url'],
+ body=keystone_client_fixtures.V2_VERSION_ENTRY)
+ # emulate Keystone v2 token request
+ v2_token = keystone_client_fixtures.generate_v2_project_scoped_token()
+ httpretty.register_uri(httpretty.POST,
+ '%s/tokens' % (self.kwargs['auth_url']),
+ body=json.dumps(v2_token))
+ # emulate get secrets
+ barbican_url = keystone_client_fixtures.BARBICAN_ENDPOINT
+ httpretty.register_uri(
+ httpretty.DELETE,
+ '%s/%s/secrets/mysecretid' % (
+ barbican_url,
+ v2_token['access']['token']['tenant']['id']),
+ status=200)
+ self.barbican.execute(argv=argv)
+
+ @httpretty.activate
+ def test_v3_auth(self):
+ argv = self._to_argv(**self.kwargs)
+ argv.append('secret')
+ argv.append('list')
+ argv.append('-h')
+ argv.append('mysecretid')
+ # emulate Keystone version discovery
+ httpretty.register_uri(httpretty.GET,
+ self.kwargs['auth_url'],
+ body=keystone_client_fixtures.V3_VERSION_ENTRY)
+ # emulate Keystone v3 token request
+ id, v3_token = \
+ keystone_client_fixtures.generate_v3_project_scoped_token()
+ httpretty.register_uri(httpretty.POST,
+ '%s/auth/tokens' % (self.kwargs['auth_url']),
+ body=json.dumps(v3_token))
+ # emulate delete secret
+ barbican_url = keystone_client_fixtures.BARBICAN_ENDPOINT
+ httpretty.register_uri(
+ httpretty.DELETE,
+ '%s/%s/secrets/mysecretid' % (
+ barbican_url,
+ v3_token['token']['project']['id']),
+ status=200)
+ self.barbican.execute(argv=argv)
diff --git a/barbicanclient/test/test_client.py b/barbicanclient/test/test_client.py
index 5e33633..74fc150 100644
--- a/barbicanclient/test/test_client.py
+++ b/barbicanclient/test/test_client.py
@@ -14,15 +14,24 @@
# limitations under the License.
import mock
+import httpretty
import requests
import testtools
+import json
+import uuid
from barbicanclient import client
+from barbicanclient.test import keystone_client_fixtures
from barbicanclient.openstack.common import timeutils
from barbicanclient.openstack.common import jsonutils
+from keystoneclient import session as ks_session
+from keystoneclient.auth.identity import v2
+from keystoneclient.auth.identity import v3
+
class FakeAuth(object):
+
def __init__(self, auth_token, barbican_url, tenant_name, tenant_id):
self.auth_token = auth_token
self.barbican_url = barbican_url
@@ -31,6 +40,7 @@ class FakeAuth(object):
class FakeResp(object):
+
def __init__(self, status_code, response_dict=None, content=None):
self.status_code = status_code
self.response_dict = response_dict
@@ -47,7 +57,34 @@ class FakeResp(object):
return self.content
+class KeystonePasswordPlugins(object):
+ v2_auth_url = keystone_client_fixtures.V2_URL
+ v3_auth_url = keystone_client_fixtures.V3_URL
+ username = 'username'
+ password = 'password'
+ project_name = tenant_name = 'tenantname'
+ tenant_id = project_id = 'tenantid'
+ user_domain_name = 'udomain_name'
+ user_domain_id = 'udomain_id'
+ project_domain_name = 'pdomain_name'
+ project_domain_id = 'pdomain_id'
+
+ @classmethod
+ def get_v2_plugin(cls):
+ return v2.Password(auth_url=cls.v2_auth_url, username=cls.username,
+ password=cls.password, tenant_name=cls.tenant_name)
+
+ @classmethod
+ def get_v3_plugin(cls):
+ return v3.Password(auth_url=cls.v3_auth_url, username=cls.username,
+ password=cls.password,
+ project_name=cls.project_name,
+ user_domain_name=cls.user_domain_name,
+ project_domain_name=cls.project_domain_name)
+
+
class WhenTestingClientInit(testtools.TestCase):
+
def setUp(self):
super(WhenTestingClientInit, self).setUp()
self.auth_endpoint = 'https://localhost:5000/v2.0/'
@@ -71,12 +108,12 @@ class WhenTestingClientInit(testtools.TestCase):
def test_can_be_used_without_auth_plugin(self):
c = client.Client(auth_plugin=None, endpoint=self.endpoint,
tenant_id=self.tenant_id)
- self.assertNotIn('X-Auth-Token', c._session.headers)
+ expected = '%s%s' % (self.endpoint, self.tenant_id)
+ self.assertEqual(expected, c.base_url)
def test_auth_token_header_is_set_when_using_auth_plugin(self):
c = client.Client(auth_plugin=self.fake_auth)
- self.assertIn('X-Auth-Token', c._session.headers)
- self.assertEqual(c._session.headers.get('X-Auth-Token'),
+ self.assertEqual(c._session.get_token(),
self.auth_token)
def test_error_thrown_when_no_auth_and_no_endpoint(self):
@@ -91,6 +128,10 @@ class WhenTestingClientInit(testtools.TestCase):
c = client.Client(endpoint=self.endpoint, tenant_id=self.tenant_id)
self.assertEqual(c._barbican_url, self.endpoint.strip('/'))
+ def test_base_url_starts_with_endpoint_url(self):
+ c = client.Client(auth_plugin=self.fake_auth)
+ self.assertTrue(c.base_url.startswith(self.endpoint))
+
def test_base_url_ends_with_tenant_id(self):
c = client.Client(auth_plugin=self.fake_auth)
self.assertTrue(c.base_url.endswith(self.tenant_id))
@@ -112,6 +153,7 @@ class WhenTestingClientInit(testtools.TestCase):
class WhenTestingClientWithSession(testtools.TestCase):
+
def setUp(self):
super(WhenTestingClientWithSession, self).setUp()
self.endpoint = 'https://localhost:9311/v1/'
@@ -133,16 +175,17 @@ class WhenTestingClientWithSession(testtools.TestCase):
tenant_id=self.tenant_id)
def test_should_post(self):
- self.session.post.return_value = FakeResp(200, {'entity_ref':
- self.entity_href})
+ self.session.request.return_value = mock.MagicMock(status_code=200)
+ self.session.request.return_value.json.return_value = {
+ 'entity_ref': self.entity_href}
resp_dict = self.client.post(self.entity, self.entity_dict)
self.assertEqual(self.entity_href, resp_dict['entity_ref'])
# Verify the correct URL was used to make the call.
- args, kwargs = self.session.post.call_args
- url = args[0]
+ args, kwargs = self.session.request.call_args
+ url = args[1]
self.assertEqual(self.entity_base, url)
# Verify that correct information was sent in the call.
@@ -150,16 +193,16 @@ class WhenTestingClientWithSession(testtools.TestCase):
self.assertEqual(self.entity_name, data['name'])
def test_should_get(self):
- self.session.get.return_value = FakeResp(200, {'name':
- self.entity_name})
-
+ self.session.request.return_value = mock.MagicMock(status_code=200)
+ self.session.request.return_value.json.return_value = {
+ 'name': self.entity_name}
resp_dict = self.client.get(self.entity_href)
self.assertEqual(self.entity_name, resp_dict['name'])
# Verify the correct URL was used to make the call.
- args, kwargs = self.session.get.call_args
- url = args[0]
+ args, kwargs = self.session.request.call_args
+ url = args[1]
self.assertEqual(self.entity_href, url)
# Verify that correct information was sent in the call.
@@ -167,7 +210,8 @@ class WhenTestingClientWithSession(testtools.TestCase):
self.assertEqual('application/json', headers['Accept'])
def test_should_get_raw(self):
- self.session.get.return_value = FakeResp(200, content='content')
+ self.session.request.return_value = mock.MagicMock(status_code=200,
+ content='content')
headers = {'Accept': 'application/octet-stream'}
content = self.client.get_raw(self.entity_href, headers)
@@ -175,8 +219,8 @@ class WhenTestingClientWithSession(testtools.TestCase):
self.assertEqual('content', content)
# Verify the correct URL was used to make the call.
- args, kwargs = self.session.get.call_args
- url = args[0]
+ args, kwargs = self.session.request.call_args
+ url = args[1]
self.assertEqual(self.entity_href, url)
# Verify that correct information was sent in the call.
@@ -184,17 +228,230 @@ class WhenTestingClientWithSession(testtools.TestCase):
self.assertEqual('application/octet-stream', headers['Accept'])
def test_should_delete(self):
- self.session.delete.return_value = FakeResp(200)
+ self.session.request.return_value = mock.MagicMock(status_code=200)
self.client.delete(self.entity_href)
# Verify the correct URL was used to make the call.
- args, kwargs = self.session.delete.call_args
- url = args[0]
+ args, kwargs = self.session.request.call_args
+ url = args[1]
self.assertEqual(self.entity_href, url)
+class WhenTestingClientWithKeystoneV2(WhenTestingClientWithSession):
+
+ def setUp(self):
+ super(WhenTestingClientWithKeystoneV2, self).setUp()
+
+ @httpretty.activate
+ def test_should_get(self):
+ # emulate Keystone version discovery
+ httpretty.register_uri(httpretty.GET,
+ keystone_client_fixtures.V2_URL,
+ body=keystone_client_fixtures.V2_VERSION_ENTRY)
+ # emulate Keystone v2 token request
+ v2_token = keystone_client_fixtures.generate_v2_project_scoped_token()
+ httpretty.register_uri(httpretty.POST,
+ '%s/tokens' % (keystone_client_fixtures.V2_URL),
+ body=json.dumps(v2_token))
+ auth_plugin = KeystonePasswordPlugins.get_v2_plugin()
+ c = client.Client(auth_plugin=auth_plugin)
+ # emulate list secrets
+ list_secrets_url = '%s/secrets' % (c.base_url)
+ httpretty.register_uri(
+ httpretty.GET,
+ list_secrets_url,
+ status=200,
+ body='{"name": "%s", "secret_ref": "%s"}' %
+ (self.entity_name, self.entity_href))
+ resp = c.get(list_secrets_url)
+ self.assertEqual(self.entity_name, resp['name'])
+ self.assertEqual(self.entity_href, resp['secret_ref'])
+
+ @httpretty.activate
+ def test_should_post(self):
+ # emulate Keystone version discovery
+ httpretty.register_uri(httpretty.GET,
+ keystone_client_fixtures.V2_URL,
+ body=keystone_client_fixtures.V2_VERSION_ENTRY)
+ # emulate Keystone v2 token request
+ v2_token = keystone_client_fixtures.generate_v2_project_scoped_token()
+ httpretty.register_uri(httpretty.POST,
+ '%s/tokens' % (keystone_client_fixtures.V2_URL),
+ body=json.dumps(v2_token))
+ auth_plugin = KeystonePasswordPlugins.get_v2_plugin()
+ c = client.Client(auth_plugin=auth_plugin)
+ # emulate list secrets
+ post_secret_url = '%s/secrets/' % (c.base_url)
+ httpretty.register_uri(
+ httpretty.POST,
+ post_secret_url,
+ status=200,
+ body='{"name": "%s", "secret_ref": "%s"}'
+ % (self.entity_name, self.entity_href))
+ resp = c.post('secrets', '{"name":"test"}')
+ self.assertEqual(self.entity_name, resp['name'])
+ self.assertEqual(self.entity_href, resp['secret_ref'])
+
+ @httpretty.activate
+ def test_should_get_raw(self):
+ # emulate Keystone version discovery
+ httpretty.register_uri(httpretty.GET,
+ keystone_client_fixtures.V2_URL,
+ body=keystone_client_fixtures.V2_VERSION_ENTRY)
+ # emulate Keystone v2 token request
+ v2_token = keystone_client_fixtures.generate_v2_project_scoped_token()
+ httpretty.register_uri(httpretty.POST,
+ '%s/tokens' % (keystone_client_fixtures.V2_URL),
+ body=json.dumps(v2_token))
+ auth_plugin = KeystonePasswordPlugins.get_v2_plugin()
+ c = client.Client(auth_plugin=auth_plugin)
+ # emulate list secrets
+ get_secret_url = '%s/secrets/s1' % (c.base_url)
+ httpretty.register_uri(
+ httpretty.GET,
+ get_secret_url,
+ status=200, body='content')
+ headers = {"Content-Type": "application/json"}
+ resp = c.get_raw(get_secret_url, headers)
+ self.assertEqual(b'content', resp)
+
+ @httpretty.activate
+ def test_should_delete(self):
+ # emulate Keystone version discovery
+ httpretty.register_uri(httpretty.GET,
+ keystone_client_fixtures.V2_URL,
+ body=keystone_client_fixtures.V2_VERSION_ENTRY)
+ # emulate Keystone v2 token request
+ v2_token = keystone_client_fixtures.generate_v2_project_scoped_token()
+ httpretty.register_uri(httpretty.POST,
+ '%s/tokens' % (keystone_client_fixtures.V2_URL),
+ body=json.dumps(v2_token))
+ auth_plugin = KeystonePasswordPlugins.get_v2_plugin()
+ c = client.Client(auth_plugin=auth_plugin)
+ # emulate list secrets
+ delete_secret_url = '%s/secrets/s1' % (c.base_url)
+ httpretty.register_uri(
+ httpretty.DELETE,
+ delete_secret_url,
+ status=201)
+ c.delete(delete_secret_url)
+
+
+class WhenTestingClientWithKeystoneV3(WhenTestingClientWithSession):
+
+ def setUp(self):
+ super(WhenTestingClientWithKeystoneV3, self).setUp()
+
+ @httpretty.activate
+ def test_should_get(self):
+ # emulate Keystone version discovery
+ httpretty.register_uri(httpretty.GET,
+ keystone_client_fixtures.V3_URL,
+ body=keystone_client_fixtures.V3_VERSION_ENTRY)
+ # emulate Keystone v3 token request
+ id, v3_token = keystone_client_fixtures.\
+ generate_v3_project_scoped_token()
+ httpretty.register_uri(httpretty.POST,
+ '%s/auth/tokens' % (
+ keystone_client_fixtures.V3_URL),
+ body=json.dumps(v3_token), x_subject_token=id)
+ auth_plugin = KeystonePasswordPlugins.get_v3_plugin()
+ c = client.Client(auth_plugin=auth_plugin)
+ # emulate list secrets
+ list_secrets_url = '%s/secrets' % (c.base_url)
+ httpretty.register_uri(
+ httpretty.GET,
+ list_secrets_url,
+ status=200,
+ body='{"name": "%s", "secret_ref": "%s"}'
+ % (self.entity_name, self.entity_href))
+ resp = c.get(list_secrets_url)
+ self.assertEqual(self.entity_name, resp['name'])
+ self.assertEqual(self.entity_href, resp['secret_ref'])
+
+ @httpretty.activate
+ def test_should_post(self):
+ # emulate Keystone version discovery
+ httpretty.register_uri(httpretty.GET,
+ keystone_client_fixtures.V3_URL,
+ body=keystone_client_fixtures.V3_VERSION_ENTRY)
+ # emulate Keystone v3 token request
+ id, v3_token = keystone_client_fixtures.\
+ generate_v3_project_scoped_token()
+ httpretty.register_uri(httpretty.POST,
+ '%s/auth/tokens' % (
+ keystone_client_fixtures.V3_URL),
+ body=json.dumps(v3_token),
+ x_subject_token=id)
+ auth_plugin = KeystonePasswordPlugins.get_v3_plugin()
+ c = client.Client(auth_plugin=auth_plugin)
+ # emulate list secrets
+ post_secret_url = '%s/secrets/' % (c.base_url)
+ httpretty.register_uri(
+ httpretty.POST,
+ post_secret_url,
+ status=200,
+ x_subject_token=id,
+ body='{"name": "%s", "secret_ref": "%s"}'
+ % (self.entity_name, self.entity_href))
+ resp = c.post('secrets', '{"name":"test"}')
+ self.assertEqual(self.entity_name, resp['name'])
+ self.assertEqual(self.entity_href, resp['secret_ref'])
+
+ @httpretty.activate
+ def test_should_get_raw(self):
+ # emulate Keystone version discovery
+ httpretty.register_uri(httpretty.GET,
+ keystone_client_fixtures.V3_URL,
+ body=keystone_client_fixtures.V3_VERSION_ENTRY)
+ # emulate Keystone v3 token request
+ id, v3_token = keystone_client_fixtures.\
+ generate_v3_project_scoped_token()
+ httpretty.register_uri(httpretty.POST,
+ '%s/auth/tokens' % (
+ keystone_client_fixtures.V3_URL),
+ body=json.dumps(v3_token),
+ x_subject_token=id)
+ auth_plugin = KeystonePasswordPlugins.get_v3_plugin()
+ c = client.Client(auth_plugin=auth_plugin)
+ # emulate list secrets
+ get_secret_url = '%s/secrets/s1' % (c.base_url)
+ httpretty.register_uri(
+ httpretty.GET,
+ get_secret_url,
+ status=200, body='content')
+ headers = {"Content-Type": "application/json"}
+ resp = c.get_raw(get_secret_url, headers)
+ self.assertEqual(b'content', resp)
+
+ @httpretty.activate
+ def test_should_delete(self):
+ # emulate Keystone version discovery
+ httpretty.register_uri(httpretty.GET,
+ keystone_client_fixtures.V3_URL,
+ body=keystone_client_fixtures.V3_VERSION_ENTRY)
+ # emulate Keystone v3 token request
+ id, v3_token = keystone_client_fixtures.\
+ generate_v3_project_scoped_token()
+ httpretty.register_uri(httpretty.POST,
+ '%s/auth/tokens' % (
+ keystone_client_fixtures.V3_URL),
+ body=json.dumps(v3_token),
+ x_subject_token=id)
+ auth_plugin = KeystonePasswordPlugins.get_v3_plugin()
+ c = client.Client(auth_plugin=auth_plugin)
+ # emulate list secrets
+ delete_secret_url = '%s/secrets/s1' % (c.base_url)
+ httpretty.register_uri(
+ httpretty.DELETE,
+ delete_secret_url,
+ status=201)
+ c.delete(delete_secret_url)
+
+
class BaseEntityResource(testtools.TestCase):
+
def _setUp(self, entity):
super(BaseEntityResource, self).setUp()
self.endpoint = 'https://localhost:9311/v1/'
diff --git a/requirements.txt b/requirements.txt
index 86ce461..927084d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,5 @@
pbr>=0.5.21,<1.0
-
argparse
requests>=1.2.3
six>=1.5.2
-python-keystoneclient>=0.3.2
+python-keystoneclient>=0.9.0
diff --git a/test-requirements.txt b/test-requirements.txt
index 0d6ec30..170f7e0 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -1,6 +1,7 @@
coverage>=3.6
discover
hacking>=0.7.0
+httpretty>=0.8.0
mock>=1.0.1
testrepository>=0.0.17
testtools>=0.9.32,<0.9.35