Merge "Display all versions info in versions controller"

This commit is contained in:
Jenkins 2015-06-24 19:07:32 +00:00 committed by Gerrit Code Review
commit 094530c2e6
9 changed files with 233 additions and 80 deletions
barbican
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')