Register Extensions

Extensions register themselves with keystone/common/extension.py
as either public, admin, or both, and they show up in the extensions
collection on http://<hostname>:<port>/v2.0/extensions/

Bug 1177531

Change-Id: Ic0b5c84e28342e96c3197c1b46f8b1656e2d7050
This commit is contained in:
Adam Young 2013-07-09 19:09:50 -04:00
parent 9a5b0c3a95
commit c5900d0f43
8 changed files with 182 additions and 50 deletions

View File

@ -0,0 +1,47 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 OpenStack LLC
#
# 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.
ADMIN_EXTENSIONS = {}
PUBLIC_EXTENSIONS = {}
def register_admin_extension(url_prefix, extension_data):
"""Register extension with collection of admin extensions.
Extensions register the information here that will show
up in the /extensions page as a way to indicate that the extension is
active.
url_prefix: unique key for the extension that will appear in the
urls generated by the extension.
extension_data is a dictionary. The expected fields are:
'name': short, human readable name of the extnsion
'namespace': xml namespace
'alias': identifier for the extension
'updated': date the extension was last updated
'description': text description of the extension
'links': hyperlinks to documents describing the extension
"""
ADMIN_EXTENSIONS[url_prefix] = extension_data
def register_public_extension(url_prefix, extension_data):
"""Same as register_admin_extension but for public extensions."""
PUBLIC_EXTENSIONS[url_prefix] = extension_data

View File

@ -14,10 +14,31 @@
# License for the specific language governing permissions and limitations
# under the License.
from keystone import catalog
from keystone.common import extension
from keystone.common import wsgi
from keystone import identity
extension.register_admin_extension(
'OS-KSADM', {
'name': 'OpenStack Keystone Admin',
'namespace': 'http://docs.openstack.org/identity/api/ext/'
'OS-KSADM/v1.0',
'alias': 'OS-KSADM',
'updated': '2013-07-11T17:14:00-00:00',
'description': 'OpenStack extensions to Keystone v2.0 API '
'enabling Administrative Operations.',
'links': [
{
'rel': 'describedby',
# TODO(dolph): link needs to be revised after
# bug 928059 merges
'type': 'text/html',
'href': 'https://github.com/openstack/identity-api',
}
]})
class CrudExtension(wsgi.ExtensionRouter):
"""Previously known as the OS-KSADM extension.

View File

@ -40,6 +40,7 @@ from keystoneclient.contrib.ec2 import utils as ec2_utils
from keystone.common import controller
from keystone.common import dependency
from keystone.common import extension
from keystone.common import manager
from keystone.common import utils
from keystone.common import wsgi
@ -51,6 +52,25 @@ from keystone import token
CONF = config.CONF
EXTENSION_DATA = {
'name': 'OpenStack EC2 API',
'namespace': 'http://docs.openstack.org/identity/api/ext/'
'OS-EC2/v1.0',
'alias': 'OS-EC2',
'updated': '2013-07-07T12:00:0-00:00',
'description': 'OpenStack EC2 Credentials backend.',
'links': [
{
'rel': 'describedby',
# TODO(ayoung): needs a description
'type': 'text/html',
'href': 'https://github.com/openstack/identity-api',
}
]}
extension.register_admin_extension(EXTENSION_DATA['alias'], EXTENSION_DATA)
extension.register_public_extension(EXTENSION_DATA['alias'], EXTENSION_DATA)
@dependency.provider('ec2_api')
class Manager(manager.Manager):
"""Default pivot point for the EC2 Credentials backend.

View File

@ -27,6 +27,7 @@ import base64
import hashlib
import hmac
from keystone.common import extension
from keystone.common import utils
from keystone.common import wsgi
from keystone import config
@ -35,6 +36,23 @@ from keystone import exception
CONF = config.CONF
EXTENSION_DATA = {
'name': 'OpenStack S3 API',
'namespace': 'http://docs.openstack.org/identity/api/ext/'
's3tokens/v1.0',
'alias': 's3tokens',
'updated': '2013-07-07T12:00:0-00:00',
'description': 'OpenStack S3 API.',
'links': [
{
'rel': 'describedby',
# TODO(ayoung): needs a description
'type': 'text/html',
'href': 'https://github.com/openstack/identity-api',
}
]}
extension.register_admin_extension(EXTENSION_DATA['alias'], EXTENSION_DATA)
class S3Extension(wsgi.ExtensionRouter):
def add_routes(self, mapper):

View File

@ -14,6 +14,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from keystone.common import extension
from keystone.common import logging
from keystone.common import manager
from keystone.common import wsgi
@ -27,6 +28,23 @@ from keystone import token
CONF = config.CONF
LOG = logging.getLogger(__name__)
extension_data = {
'name': 'Openstack Keystone Stats API',
'namespace': 'http://docs.openstack.org/identity/api/ext/'
'OS-STATS/v1.0',
'alias': 'OS-STATS',
'updated': '2013-07-07T12:00:0-00:00',
'description': 'Openstack Keystone Stats API.',
'links': [
{
'rel': 'describedby',
# TODO(ayoung): needs a description
'type': 'text/html',
'href': 'https://github.com/openstack/identity-api',
}
]}
extension.register_admin_extension(extension_data['alias'], extension_data)
class Manager(manager.Manager):
"""Default pivot point for the Stats backend.

View File

@ -17,6 +17,7 @@
import copy
import uuid
from keystone.common import extension
from keystone.common import logging
from keystone.common import wsgi
from keystone import exception
@ -26,6 +27,25 @@ from keystone import identity
LOG = logging.getLogger(__name__)
extension.register_public_extension(
'OS-KSCRUD', {
'name': 'OpenStack Keystone User CRUD',
'namespace': 'http://docs.openstack.org/identity/api/ext/'
'OS-KSCRUD/v1.0',
'alias': 'OS-KSCRUD',
'updated': '2013-07-07T12:00:0-00:00',
'description': 'OpenStack extensions to Keystone v2.0 API '
'enabling User Operations.',
'links': [
{
'rel': 'describedby',
# TODO(ayoung): needs a description
'type': 'text/html',
'href': 'https://github.com/openstack/identity-api',
}
]})
class UserController(identity.controllers.User):
def set_user_password(self, context, user_id, user):
token_id = context.get('token_id')

View File

@ -14,6 +14,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from keystone.common import extension
from keystone.common import logging
from keystone.common import wsgi
from keystone import config
@ -32,10 +33,10 @@ _VERSIONS = []
class Extensions(wsgi.Application):
"""Base extensions controller to be extended by public and admin API's."""
def __init__(self, extensions=None):
super(Extensions, self).__init__()
self.extensions = extensions or {}
#extend in subclass to specify the set of extensions
@property
def extensions(self):
return None
def get_extensions_info(self, context):
return {'extensions': {'values': self.extensions.values()}}
@ -48,34 +49,15 @@ class Extensions(wsgi.Application):
class AdminExtensions(Extensions):
def __init__(self, *args, **kwargs):
super(AdminExtensions, self).__init__(*args, **kwargs)
# TODO(dolph): Extensions should obviously provide this information
# themselves, but hardcoding it here allows us to match
# the API spec in the short term with minimal complexity.
self.extensions['OS-KSADM'] = {
'name': 'Openstack Keystone Admin',
'namespace': 'http://docs.openstack.org/identity/api/ext/'
'OS-KSADM/v1.0',
'alias': 'OS-KSADM',
'updated': '2011-08-19T13:25:27-06:00',
'description': 'Openstack extensions to Keystone v2.0 API '
'enabling Admin Operations.',
'links': [
{
'rel': 'describedby',
# TODO(dolph): link needs to be revised after
# bug 928059 merges
'type': 'text/html',
'href': 'https://github.com/openstack/identity-api',
}
]
}
@property
def extensions(self):
return extension.ADMIN_EXTENSIONS
class PublicExtensions(Extensions):
pass
@property
def extensions(self):
return extension.PUBLIC_EXTENSIONS
def register_version(version):

View File

@ -23,6 +23,7 @@ import webtest
from keystone import test
from keystone.common import extension
from keystone.common import serializer
from keystone.openstack.common import jsonutils
@ -334,14 +335,14 @@ class CoreApiTests(object):
self.assertValidVersionResponse(r)
def test_public_extensions(self):
self.public_request(path='/v2.0/extensions',)
# TODO(dolph): can't test this without any public extensions defined
# self.assertValidExtensionListResponse(r)
r = self.public_request(path='/v2.0/extensions')
self.assertValidExtensionListResponse(r,
extension.PUBLIC_EXTENSIONS)
def test_admin_extensions(self):
r = self.admin_request(path='/v2.0/extensions',)
self.assertValidExtensionListResponse(r)
r = self.admin_request(path='/v2.0/extensions')
self.assertValidExtensionListResponse(r,
extension.ADMIN_EXTENSIONS)
def test_admin_extensions_404(self):
self.admin_request(path='/v2.0/extensions/invalid-extension',
@ -353,7 +354,8 @@ class CoreApiTests(object):
def test_admin_osksadm_extension(self):
r = self.admin_request(path='/v2.0/extensions/OS-KSADM')
self.assertValidExtensionResponse(r)
self.assertValidExtensionResponse(r,
extension.ADMIN_EXTENSIONS)
def test_authenticate(self):
r = self.public_request(
@ -611,24 +613,26 @@ class JsonTestCase(RestfulTestCase, CoreApiTests):
self.assertValidError(r.result['error'])
self.assertEqual(r.result['error']['code'], r.status_code)
def assertValidExtension(self, extension):
def assertValidExtension(self, extension, expected):
super(JsonTestCase, self).assertValidExtension(extension)
self.assertIsNotNone(extension.get('description'))
descriptions = [ext['description'] for ext in expected.itervalues()]
description = extension.get('description')
self.assertIsNotNone(description)
self.assertIn(description, descriptions)
self.assertIsNotNone(extension.get('links'))
self.assertNotEmpty(extension.get('links'))
for link in extension.get('links'):
self.assertValidExtensionLink(link)
def assertValidExtensionListResponse(self, r):
def assertValidExtensionListResponse(self, r, expected):
self.assertIsNotNone(r.result.get('extensions'))
self.assertIsNotNone(r.result['extensions'].get('values'))
self.assertNotEmpty(r.result['extensions'].get('values'))
for extension in r.result['extensions']['values']:
self.assertValidExtension(extension)
self.assertValidExtension(extension, expected)
def assertValidExtensionResponse(self, r):
self.assertValidExtension(r.result.get('extension'))
def assertValidExtensionResponse(self, r, expected):
self.assertValidExtension(r.result.get('extension'), expected)
def assertValidAuthenticationResponse(self, r,
require_service_catalog=False):
@ -850,29 +854,31 @@ class XmlTestCase(RestfulTestCase, CoreApiTests):
self.assertValidError(xml)
self.assertEqual(xml.get('code'), str(r.status_code))
def assertValidExtension(self, extension):
def assertValidExtension(self, extension, expected):
super(XmlTestCase, self).assertValidExtension(extension)
self.assertIsNotNone(extension.find(self._tag('description')))
self.assertTrue(extension.find(self._tag('description')).text)
links = extension.find(self._tag('links'))
self.assertNotEmpty(links.findall(self._tag('link')))
descriptions = [ext['description'] for ext in expected.itervalues()]
description = extension.find(self._tag('description')).text
self.assertIn(description, descriptions)
for link in links.findall(self._tag('link')):
self.assertValidExtensionLink(link)
def assertValidExtensionListResponse(self, r):
def assertValidExtensionListResponse(self, r, expected):
xml = r.result
self.assertEqual(xml.tag, self._tag('extensions'))
self.assertNotEmpty(xml.findall(self._tag('extension')))
for extension in xml.findall(self._tag('extension')):
self.assertValidExtension(extension)
for ext in xml.findall(self._tag('extension')):
self.assertValidExtension(ext, expected)
def assertValidExtensionResponse(self, r):
def assertValidExtensionResponse(self, r, expected):
xml = r.result
self.assertEqual(xml.tag, self._tag('extension'))
self.assertValidExtension(xml)
self.assertValidExtension(xml, expected)
def assertValidVersion(self, version):
super(XmlTestCase, self).assertValidVersion(version)