Skip to content

Commit

Permalink
Register Extensions
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Adam Young committed Jul 12, 2013
1 parent 9a5b0c3 commit c5900d0
Show file tree
Hide file tree
Showing 8 changed files with 182 additions and 50 deletions.
47 changes: 47 additions & 0 deletions keystone/common/extension.py
@@ -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
21 changes: 21 additions & 0 deletions keystone/contrib/admin_crud/core.py
Expand Up @@ -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.
Expand Down
20 changes: 20 additions & 0 deletions keystone/contrib/ec2/core.py
Expand Up @@ -40,6 +40,7 @@

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
Expand All @@ -51,6 +52,25 @@
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.
Expand Down
18 changes: 18 additions & 0 deletions keystone/contrib/s3/core.py
Expand Up @@ -27,6 +27,7 @@
import hashlib
import hmac

from keystone.common import extension
from keystone.common import utils
from keystone.common import wsgi
from keystone import config
Expand All @@ -35,6 +36,23 @@

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):
Expand Down
18 changes: 18 additions & 0 deletions keystone/contrib/stats/core.py
Expand Up @@ -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
Expand All @@ -27,6 +28,23 @@
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.
Expand Down
20 changes: 20 additions & 0 deletions keystone/contrib/user_crud/core.py
Expand Up @@ -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
Expand All @@ -26,6 +27,25 @@
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')
Expand Down
40 changes: 11 additions & 29 deletions keystone/controllers.py
Expand Up @@ -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
Expand All @@ -32,10 +33,10 @@
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()}}
Expand All @@ -48,34 +49,15 @@ def get_extension_info(self, context, extension_alias):


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):
Expand Down
48 changes: 27 additions & 21 deletions tests/test_content_types.py
Expand Up @@ -23,6 +23,7 @@

from keystone import test

from keystone.common import extension
from keystone.common import serializer
from keystone.openstack.common import jsonutils

Expand Down Expand Up @@ -334,14 +335,14 @@ def test_admin_version(self):
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',
Expand All @@ -353,7 +354,8 @@ def test_public_osksadm_extension_404(self):

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(
Expand Down Expand Up @@ -611,24 +613,26 @@ def assertValidErrorResponse(self, r):
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):
Expand Down Expand Up @@ -850,29 +854,31 @@ def assertValidErrorResponse(self, r):
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)
Expand Down

0 comments on commit c5900d0

Please sign in to comment.