Merge "Display all versions info in versions controller"
This commit is contained in:
commit
094530c2e6
barbican
api
common
tests/api
contrib/devstack/lib
doc/source/setup
etc/barbican
functionaltests/api/v1/smoke
@ -26,11 +26,6 @@ except ImportError:
|
||||
|
||||
from oslo_log import log
|
||||
|
||||
from barbican.api.controllers import cas
|
||||
from barbican.api.controllers import containers
|
||||
from barbican.api.controllers import orders
|
||||
from barbican.api.controllers import secrets
|
||||
from barbican.api.controllers import transportkeys
|
||||
from barbican.api.controllers import versions
|
||||
from barbican.api import hooks
|
||||
from barbican.common import config
|
||||
@ -44,16 +39,6 @@ if newrelic_loaded:
|
||||
newrelic.agent.initialize('/etc/newrelic/newrelic.ini')
|
||||
|
||||
|
||||
class RootController(object):
|
||||
def __init__(self):
|
||||
# Adding the controllers at runtime to due config loading issues
|
||||
self.secrets = secrets.SecretsController()
|
||||
self.orders = orders.OrdersController()
|
||||
self.containers = containers.ContainersController()
|
||||
self.transport_keys = transportkeys.TransportKeysController()
|
||||
self.cas = cas.CertificateAuthoritiesController()
|
||||
|
||||
|
||||
def build_wsgi_app(controller=None, transactional=False):
|
||||
"""WSGI application creation helper
|
||||
|
||||
@ -68,42 +53,47 @@ def build_wsgi_app(controller=None, transactional=False):
|
||||
|
||||
# Create WSGI app
|
||||
wsgi_app = pecan.Pecan(
|
||||
controller or RootController(),
|
||||
controller or versions.AVAILABLE_VERSIONS[versions.DEFAULT_VERSION](),
|
||||
hooks=request_hooks,
|
||||
force_canonical=False
|
||||
)
|
||||
return wsgi_app
|
||||
|
||||
|
||||
def create_main_app(global_config, **local_conf):
|
||||
def main_app(func):
|
||||
def _wrapper(global_config, **local_conf):
|
||||
# Queuing initialization
|
||||
queue.init(CONF, is_server_side=False)
|
||||
|
||||
# Configure oslo logging and configuration services.
|
||||
log.setup(CONF, 'barbican')
|
||||
|
||||
config.setup_remote_pydev_debug()
|
||||
|
||||
# Initializing the database engine and session factory before the app
|
||||
# starts ensures we don't lose requests due to lazy initialiation of db
|
||||
# connections.
|
||||
repositories.setup_database_engine_and_factory()
|
||||
|
||||
wsgi_app = func(global_config, **local_conf)
|
||||
|
||||
if newrelic_loaded:
|
||||
wsgi_app = newrelic.agent.WSGIApplicationWrapper(wsgi_app)
|
||||
LOG = log.getLogger(__name__)
|
||||
LOG.info(u._LI('Barbican app created and initialized'))
|
||||
return wsgi_app
|
||||
return _wrapper
|
||||
|
||||
|
||||
@main_app
|
||||
def create_main_app_v1(global_config, **local_conf):
|
||||
"""uWSGI factory method for the Barbican-API application."""
|
||||
|
||||
# Queuing initialization
|
||||
queue.init(CONF, is_server_side=False)
|
||||
|
||||
# Configure oslo logging and configuration services.
|
||||
log.setup(CONF, 'barbican')
|
||||
config.setup_remote_pydev_debug()
|
||||
|
||||
# Initializing the database engine and session factory before the app
|
||||
# starts ensures we don't lose requests due to lazy initialiation of db
|
||||
# connections.
|
||||
repositories.setup_database_engine_and_factory()
|
||||
|
||||
# Setup app with transactional hook enabled
|
||||
wsgi_app = build_wsgi_app(transactional=True)
|
||||
|
||||
if newrelic_loaded:
|
||||
wsgi_app = newrelic.agent.WSGIApplicationWrapper(wsgi_app)
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
LOG.info(u._LI('Barbican app created and initialized'))
|
||||
|
||||
return wsgi_app
|
||||
return build_wsgi_app(versions.V1Controller(), transactional=True)
|
||||
|
||||
|
||||
def create_admin_app(global_config, **local_conf):
|
||||
wsgi_app = pecan.make_app(versions.VersionController())
|
||||
wsgi_app = pecan.make_app(versions.VersionsController())
|
||||
return wsgi_app
|
||||
|
||||
|
||||
|
@ -11,8 +11,14 @@
|
||||
# under the License.
|
||||
|
||||
import pecan
|
||||
from six.moves.urllib import parse
|
||||
|
||||
from barbican.api import controllers
|
||||
from barbican.api.controllers import cas
|
||||
from barbican.api.controllers import containers
|
||||
from barbican.api.controllers import orders
|
||||
from barbican.api.controllers import secrets
|
||||
from barbican.api.controllers import transportkeys
|
||||
from barbican.common import utils
|
||||
from barbican import i18n as u
|
||||
from barbican import version
|
||||
@ -20,21 +26,145 @@ from barbican import version
|
||||
LOG = utils.getLogger(__name__)
|
||||
|
||||
|
||||
class VersionController(object):
|
||||
MIME_TYPE_JSON = 'application/json'
|
||||
MIME_TYPE_JSON_HOME = 'application/json-home'
|
||||
MEDIA_TYPE_JSON = 'application/vnd.openstack.keymanagement-%s+json'
|
||||
|
||||
|
||||
def _version_not_found():
|
||||
"""Throw exception indicating version not found."""
|
||||
pecan.abort(404, u._("The version you requested wasn't found"))
|
||||
|
||||
|
||||
def _get_versioned_url(full_url, version):
|
||||
parsed_url, _ = parse.urldefrag(full_url)
|
||||
|
||||
if version[-1] != '/':
|
||||
version += '/'
|
||||
return parse.urljoin(parsed_url, version)
|
||||
|
||||
|
||||
class BaseVersionController(object):
|
||||
"""Base class for the version-specific controllers"""
|
||||
|
||||
@classmethod
|
||||
def get_version_info(cls, request):
|
||||
return {
|
||||
'id': cls.version_id,
|
||||
'status': 'stable',
|
||||
'updated': cls.last_updated,
|
||||
'links': [
|
||||
{
|
||||
'rel': 'self',
|
||||
'href': _get_versioned_url(request.url,
|
||||
cls.version_string),
|
||||
}, {
|
||||
'rel': 'describedby',
|
||||
'type': 'text/html',
|
||||
'href': 'http://docs.openstack.org/'
|
||||
}
|
||||
],
|
||||
'media-types': [
|
||||
{
|
||||
'base': MIME_TYPE_JSON,
|
||||
'type': MEDIA_TYPE_JSON % cls.version_string
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
class V1Controller(BaseVersionController):
|
||||
"""Root controller for the v1 API"""
|
||||
|
||||
version_string = 'v1'
|
||||
|
||||
# NOTE(jaosorior): We might start using decimals in the future, meanwhile
|
||||
# this is the same as the version string.
|
||||
version_id = 'v1'
|
||||
|
||||
last_updated = '2015-04-28T00:00:00Z'
|
||||
|
||||
def __init__(self):
|
||||
LOG.debug('=== Creating VersionController ===')
|
||||
LOG.debug('=== Creating V1Controller ===')
|
||||
self.secrets = secrets.SecretsController()
|
||||
self.orders = orders.OrdersController()
|
||||
self.containers = containers.ContainersController()
|
||||
self.transport_keys = transportkeys.TransportKeysController()
|
||||
self.cas = cas.CertificateAuthoritiesController()
|
||||
|
||||
self.__controllers = [
|
||||
self.secrets,
|
||||
self.orders,
|
||||
self.containers,
|
||||
self.transport_keys,
|
||||
self.cas,
|
||||
]
|
||||
|
||||
@pecan.expose(generic=True)
|
||||
def index(self):
|
||||
pecan.abort(405) # HTTP 405 Method Not Allowed as default
|
||||
|
||||
@index.when(method='GET', template='json')
|
||||
@utils.allow_certain_content_types(MIME_TYPE_JSON, MIME_TYPE_JSON_HOME)
|
||||
@controllers.handle_exceptions(u._('Version retrieval'))
|
||||
def on_get(self):
|
||||
body = {
|
||||
'v1': 'current',
|
||||
'build': version.__version__
|
||||
return {'version': self.get_version_info(pecan.request)}
|
||||
|
||||
|
||||
AVAILABLE_VERSIONS = {
|
||||
V1Controller.version_string: V1Controller,
|
||||
}
|
||||
|
||||
DEFAULT_VERSION = V1Controller.version_string
|
||||
|
||||
|
||||
class VersionsController(object):
|
||||
|
||||
def __init__(self):
|
||||
LOG.debug('=== Creating VersionsController ===')
|
||||
|
||||
@pecan.expose(generic=True)
|
||||
def index(self, **kwargs):
|
||||
pecan.abort(405) # HTTP 405 Method Not Allowed as default
|
||||
|
||||
@index.when(method='GET', template='json')
|
||||
@utils.allow_certain_content_types(MIME_TYPE_JSON, MIME_TYPE_JSON_HOME)
|
||||
def on_get(self, **kwargs):
|
||||
"""The list of versions is dependent on the context."""
|
||||
self._redirect_to_default_json_home_if_needed(pecan.request)
|
||||
|
||||
if 'build' in kwargs:
|
||||
return {'build': version.__version__}
|
||||
|
||||
versions_info = [version_class.get_version_info(pecan.request)
|
||||
for version_class in AVAILABLE_VERSIONS.itervalues()]
|
||||
|
||||
version_output = {
|
||||
'versions': {
|
||||
'values': versions_info
|
||||
}
|
||||
}
|
||||
LOG.info(u._LI('Retrieved version'))
|
||||
return body
|
||||
|
||||
# Since we are returning all the versions available, the proper status
|
||||
# code is Multiple Choices (300)
|
||||
pecan.response.status = 300
|
||||
return version_output
|
||||
|
||||
def _redirect_to_default_json_home_if_needed(self, request):
|
||||
if self._mime_best_match(request.accept) == MIME_TYPE_JSON_HOME:
|
||||
url = _get_versioned_url(request.url, DEFAULT_VERSION)
|
||||
LOG.debug("Redirecting Request to " + url)
|
||||
# NOTE(jaosorior): This issues an "external" redirect because of
|
||||
# two reasons:
|
||||
# * This module doesn't require authorization, and accessing
|
||||
# specific version info needs that.
|
||||
# * The resource is a separate app_factory and won't be found
|
||||
# internally
|
||||
pecan.redirect(url, request=request)
|
||||
|
||||
def _mime_best_match(self, accept):
|
||||
if not accept:
|
||||
return MIME_TYPE_JSON
|
||||
|
||||
SUPPORTED_TYPES = [MIME_TYPE_JSON, MIME_TYPE_JSON_HOME]
|
||||
return accept.best_match(SUPPORTED_TYPES)
|
||||
|
@ -35,13 +35,23 @@ CONF = config.CONF
|
||||
API_VERSION = 'v1'
|
||||
|
||||
|
||||
def allow_all_content_types(f):
|
||||
# Pecan decorator to not limit content types for controller routes
|
||||
cfg = pecan.util._cfg(f)
|
||||
def _do_allow_certain_content_types(func, content_types_list=[]):
|
||||
# Allows you to bypass pecan's content-type restrictions
|
||||
cfg = pecan.util._cfg(func)
|
||||
cfg.setdefault('content_types', {})
|
||||
cfg['content_types'].update((value, '')
|
||||
for value in mimetypes.types_map.values())
|
||||
return f
|
||||
for value in content_types_list)
|
||||
return func
|
||||
|
||||
|
||||
def allow_certain_content_types(*content_types_list):
|
||||
def _wrapper(func):
|
||||
return _do_allow_certain_content_types(func, content_types_list)
|
||||
return _wrapper
|
||||
|
||||
|
||||
def allow_all_content_types(f):
|
||||
return _do_allow_certain_content_types(f, mimetypes.types_map.values())
|
||||
|
||||
|
||||
def hostname_for_refs(resource=None):
|
||||
|
@ -16,15 +16,32 @@ from barbican.api import controllers
|
||||
from barbican.tests import utils
|
||||
|
||||
|
||||
class WhenTestingVersionResource(utils.BarbicanAPIBaseTestCase):
|
||||
root_controller = controllers.versions.VersionController()
|
||||
class WhenTestingVersionsResource(utils.BarbicanAPIBaseTestCase):
|
||||
root_controller = controllers.versions.VersionsController()
|
||||
|
||||
def test_should_return_200_on_get(self):
|
||||
def test_should_return_multiple_choices_on_get(self):
|
||||
resp = self.app.get('/')
|
||||
self.assertEqual(200, resp.status_int)
|
||||
self.assertEqual(300, resp.status_int)
|
||||
|
||||
def test_should_return_multiple_choices_on_get_if_json_accept_header(self):
|
||||
headers = {'Accept': 'application/json'}
|
||||
resp = self.app.get('/', headers=headers)
|
||||
self.assertEqual(300, resp.status_int)
|
||||
|
||||
def test_should_redirect_if_json_home_accept_header_present(self):
|
||||
headers = {'Accept': 'application/json-home'}
|
||||
resp = self.app.get('/', headers=headers)
|
||||
self.assertEqual(302, resp.status_int)
|
||||
|
||||
def test_should_return_version_json(self):
|
||||
resp = self.app.get('/')
|
||||
|
||||
self.assertTrue('v1' in resp.json)
|
||||
self.assertEqual(resp.json.get('v1'), 'current')
|
||||
versions_response = resp.json['versions']['values']
|
||||
v1_info = versions_response[0]
|
||||
|
||||
# NOTE(jaosorior): I used assertIn instead of assertEqual because we
|
||||
# might start using decimal numbers in the future. So when that happens
|
||||
# this test will still be valid.
|
||||
self.assertIn('v1', v1_info['id'])
|
||||
self.assertEqual(len(v1_info['media-types']), 1)
|
||||
self.assertEqual(v1_info['media-types'][0]['base'], 'application/json')
|
||||
|
@ -69,8 +69,8 @@ class TestableResource(object):
|
||||
return self.controller.on_delete(*args, **kwargs)
|
||||
|
||||
|
||||
class VersionResource(TestableResource):
|
||||
controller_cls = versions.VersionController
|
||||
class VersionsResource(TestableResource):
|
||||
controller_cls = versions.VersionsController
|
||||
|
||||
|
||||
class SecretsResource(TestableResource):
|
||||
@ -203,32 +203,32 @@ class BaseTestCase(utils.BaseTestCase, utils.MockModelRepositoryMixin):
|
||||
self.assertEqual(403, exception.status_int)
|
||||
|
||||
|
||||
class WhenTestingVersionResource(BaseTestCase):
|
||||
"""RBAC tests for the barbican.api.resources.VersionResource class."""
|
||||
class WhenTestingVersionsResource(BaseTestCase):
|
||||
"""RBAC tests for the barbican.api.resources.VersionsResource class."""
|
||||
def setUp(self):
|
||||
super(WhenTestingVersionResource, self).setUp()
|
||||
super(WhenTestingVersionsResource, self).setUp()
|
||||
|
||||
self.resource = VersionResource()
|
||||
self.resource = VersionsResource()
|
||||
|
||||
def test_rules_should_be_loaded(self):
|
||||
self.assertIsNotNone(self.policy_enforcer.rules)
|
||||
|
||||
def test_should_pass_get_version(self):
|
||||
def test_should_pass_get_versions(self):
|
||||
# Can't use base method that short circuits post-RBAC processing here,
|
||||
# as version GET is trivial
|
||||
for role in ['admin', 'observer', 'creator', 'audit']:
|
||||
self.req = self._generate_req(roles=[role] if role else [])
|
||||
self._invoke_on_get()
|
||||
|
||||
def test_should_pass_get_version_with_bad_roles(self):
|
||||
def test_should_pass_get_versions_with_bad_roles(self):
|
||||
self.req = self._generate_req(roles=[None, 'bunkrolehere'])
|
||||
self._invoke_on_get()
|
||||
|
||||
def test_should_pass_get_version_with_no_roles(self):
|
||||
def test_should_pass_get_versions_with_no_roles(self):
|
||||
self.req = self._generate_req()
|
||||
self._invoke_on_get()
|
||||
|
||||
def test_should_pass_get_version_multiple_roles(self):
|
||||
def test_should_pass_get_versions_multiple_roles(self):
|
||||
self.req = self._generate_req(roles=['admin', 'observer', 'creator',
|
||||
'audit'])
|
||||
self._invoke_on_get()
|
||||
|
@ -130,7 +130,7 @@ function configure_barbican {
|
||||
## Set up keystone
|
||||
|
||||
# Turn on the middleware
|
||||
iniset $BARBICAN_PASTE_CONF 'pipeline:barbican_api' pipeline 'keystone_authtoken context apiapp'
|
||||
iniset $BARBICAN_PASTE_CONF 'pipeline:barbican_api' pipeline 'keystone_authtoken context apiapp_v1'
|
||||
|
||||
# Set the keystone parameters
|
||||
iniset $BARBICAN_PASTE_CONF 'filter:keystone_authtoken' auth_protocol $KEYSTONE_AUTH_PROTOCOL
|
||||
|
@ -32,7 +32,7 @@ the get version call.
|
||||
.. code-block:: ini
|
||||
|
||||
[pipeline:barbican_api]
|
||||
pipeline = keystone_authtoken context apiapp
|
||||
pipeline = keystone_authtoken context apiapp_v1
|
||||
|
||||
2. Replace ``keystone_authtoken`` filter values to match your Keystone
|
||||
setup
|
||||
|
@ -9,21 +9,20 @@ pipeline = versionapp
|
||||
|
||||
# Use this pipeline for Barbican API - DEFAULT no authentication
|
||||
[pipeline:barbican_api]
|
||||
pipeline = unauthenticated-context apiapp
|
||||
####pipeline = simple apiapp
|
||||
#pipeline = keystone_authtoken context apiapp
|
||||
pipeline = unauthenticated-context apiapp_v1
|
||||
#pipeline = keystone_authtoken context apiapp_v1
|
||||
|
||||
#Use this pipeline to activate a repoze.profile middleware and HTTP port,
|
||||
# to provide profiling information for the REST API processing.
|
||||
[pipeline:barbican-profile]
|
||||
pipeline = unauthenticated-context egg:Paste#cgitb egg:Paste#httpexceptions profile apiapp
|
||||
pipeline = unauthenticated-context egg:Paste#cgitb egg:Paste#httpexceptions profile apiapp_v1
|
||||
|
||||
#Use this pipeline for keystone auth
|
||||
[pipeline:barbican-api-keystone]
|
||||
pipeline = keystone_authtoken context apiapp
|
||||
pipeline = keystone_authtoken context apiapp_v1
|
||||
|
||||
[app:apiapp]
|
||||
paste.app_factory = barbican.api.app:create_main_app
|
||||
[app:apiapp_v1]
|
||||
paste.app_factory = barbican.api.app:create_main_app_v1
|
||||
|
||||
[app:versionapp]
|
||||
paste.app_factory = barbican.api.app:create_version_app
|
||||
|
@ -35,6 +35,13 @@ class VersionDiscoveryTestCase(base.TestCase):
|
||||
resp = self.client.get(url_without_version, use_auth=use_auth)
|
||||
body = resp.json()
|
||||
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertEqual(body.get('v1'), 'current')
|
||||
self.assertGreater(len(body.get('build')), 1)
|
||||
self.assertEqual(resp.status_code, 300)
|
||||
versions_response = body['versions']['values']
|
||||
v1_info = versions_response[0]
|
||||
|
||||
# NOTE(jaosorior): I used assertIn instead of assertEqual because we
|
||||
# might start using decimal numbers in the future. So when that happens
|
||||
# this test will still be valid.
|
||||
self.assertIn('v1', v1_info['id'])
|
||||
self.assertEqual(len(v1_info['media-types']), 1)
|
||||
self.assertEqual(v1_info['media-types'][0]['base'], 'application/json')
|
||||
|
Loading…
x
Reference in New Issue
Block a user