Category Archives: Keystone

Creating an OpenStack Keystone ‘HelloWorld’ Extension

The OpenStack ‘cloud operating system’ provides a model, framework and  a core set of platform services managing core virtualized datacenter resources.  As of the Folsom release, the following services are packaged as part of the solution:

  • Keystone – Identity, Authentication and Authorization
  • Glance – Image Management
  • Nova – Compute
  • Quantum – Network
  • Swift – Object Storage
  • Cinder – Block Storage
  • Horizon – Web App Dashboard

Development of OpenStack is a collaborative venture between a global community of individuals and organizations.  Given the loosely coupled, standards based approach to development, a systems architecture composed of loosely coupled services adhering to a standardized API specification was needed to permit the project to move forward rapidly with a minimum of centralized coordination.

One of the core elements of the OpenStack architecture is support for extensibility.  The evolution of OpenStack will in part be governed by the development of experimental extensions to the base platform which may be promoted to first-class members of the platform should the extension prove generally valuable.  Beyond that, extensibility is needed to permit ‘customization’ of a base OpenStack package for specific deployment configurations or service requirements.

Though the Extension API has some sparse documentation on the OpenStack.org site, there don’t yet appear to be any simple ‘Hello World’ type examples for adding an extension to the Keystone service.  The process is not difficult but it took me some poking around in the code to figure out how to add one of my own.

Development Environment :

I use the DevStack distribution installed on an Ubuntu 12.04 Server OS for development.  In the absence of intrusive network proxies or firewalls, the DevStack distribution ‘just works’.  That said, DevStack is neither intended nor suitable for production deployments.  Be sure to read the DevStack caveats before you start using it so that you minimize potentially unpleasant surprises

Creating an Extension :

Step 1: Create subdirectory under ‘contrib’

For a vanilla DevStack installation, the solution root directory is ‘/opt/stack’.  Keystone extensions are typically placed in individual subdirectories of ‘/opt/stack/keystone/keystone/contrib’.  For this example create a directory named ‘hello_world’.

Step 2: Create a core.py file for the extension mapper and controller

The OpenStack extension architecture relies on the wsgi python framework.  There are some OpenStack wrapper classes that simplify creating an extension.  The code should be placed in the ‘contrib’ directory.  The python code for the hello_world extension appears below.

# vim: tabstop=4 shiftwidth=4 softtabstop=4

# 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.

from keystone.common import wsgi

from keystone import identity
from keystone import token

class HelloWorldExtension(wsgi.ExtensionRouter):

    def add_routes(self, mapper):
        controller = HelloWorldController()

        mapper.connect( '/example/hello_world',
                        controller=controller,
                        action='get_hello_world',
                        conditions=dict(method=['GET']))

        mapper.connect( '/example/hello_world/{identifier}',
                        controller=controller,
                        action='get_hello_world_with_id',
                        conditions=dict(method=['GET']))

class HelloWorldController(wsgi.Application):

    def __init__(self):
        self.token_api = token.Manager()
        super(HelloWorldController, self).__init__()

    def get_hello_world(self, context):
#       self.assert_admin(context)
        return {
            'SEF-EXAMPLE:hello_world': [
                {
                    'hello': 'world',
                    'description': 'Simple Hello World Keystone Extension',
                },
            ]
        }

    def get_hello_world_with_id(self, context, identifier):
#       self.assert_admin(context)
        return {
            'SEF-EXAMPLE:hello_world_id': [
                {
                    'hello': 'world',
                    'description': 'Simple Hello World Keystone Extension with Identifier',
                    'identifier': identifier,
                },
            ]
        }

The code is fairly straightforward.  The HelloWorldExtension class creates a controller an in the add_routes() method it associates URLs with code handlers.  The code handlers are defined in the HelloWorldController class.

There are two elements of the controller that merit a bit of explanation.  First, the example contains the commented out command: ‘self.assert_admin(context)’ in both code handlers. This command enforces authentication for the extension. It is commented out in the example to make the example easier to invoke with curl. Second, in the mapper the connection: “mapper.connect( ‘/example/hello_world/{identifier}'” specifies ‘{identifier}’ as an argument to the handler. The handler signature: ‘def get_hello_world_with_id(self, context, identifier)’ includes ‘identifier’ as the parameter parsed from the URL.

There are formalized naming conventions for extensions and their namespaces described in the OpenStack documentation.  For anything more than a HelloWorld example, these conventions should be followed.

Step 3: Create __init__.py to load the extension

The ‘__init__.py’ file below should be placed in the  ‘contrib’ subdirectory with the extension code. The file is very straightforward, it just loads the extension.


# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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.

from keystone.contrib.hello_world.core import *

Step 4: Add an entry for the extension filter in keystone.conf

With the extension created and the ‘__init__.py’ file to load it, the next step is to modify the keystone configuration file to add a filter entry for the extension and then add the filter to the admin API pipeline.  For a standard OpenStack install, this would be the ‘/etc/keystone/keystone.conf’ file.  For DevStack, modifications should be made to the ‘/opt/stack/keystone/etc/keystone.conf.sample’ file which serves as a template for the ‘keystone.conf’ file generated during DevStack start-up.  The content to be added to the configuration file appears below:


[filter:hello_world_extension]
paste.filter_factory = keystone.contrib.hello_world:HelloWorldExtension.factory

[pipeline:admin_api]
pipeline = access_log sizelimit stats_monitoring url_normalize token_auth admin_token_auth xml_body json_body debug stats_reporting ec2_extension s3_extension crud_extension hello_world_extension admin_service

The ‘[filter:hello_world_extension]’ should be added to the end of the list of filters in the configuration file.  The ‘[pipeline:admin_api]’ should already exist in the file, so all that is necessary for that line should be to add the name of the filter to the pipeline.

Step 5: Add self.extensions entry to controllers.py

Extensions are not self-describing so when querying an OpenStack Keystone instance for the extensions it has loaded, it is necessary to add the descriptive metadata to the ‘controllers.py’ class.  For DevStack, this file can be found at ‘/opt/stack/keystone/keystone/controllers.py’.  The code fragment below should be inserted in to the ‘__init__()’ method.


self.extensions['SEF-HELLO-WORLD'] = {
 'name': 'Hello World Example Extension',
 'namespace': 'http://docs.openstack.org/identity/api/ext/'
     'SEF-HELLO-WORLD/v1.0',
 'alias': 'SEF-HELLO-WORLD',
 'updated': '2013-03-18T13:25:27-06:00',
 'description': 'Openstack extensions to Keystone v2.0 API '
 'enabling Admin Operations.',
 'links': [
         {
             'rel': 'describedby',
             'type': 'text/html',
             'href': 'https://github.com/openstack/identity-api',
         }
     ]
 }

Step 6: Check Extension Functionality

If you are using DevStack, the easiest thing to do is to restart it and it will compile and load the new extension. After it has started, there should be two new files: ‘core.pyc’ and ‘__init__.pyc’ in the ‘contrib/hello_world/’ subdirectory. Files with ‘.pyc’ extensions are ‘compiled python’ files which contains python byte code. To check the new extension description, use the following ‘curl’ command and you should see the description in the response:


$ curl http://<em>openstack_ip_addr</em>:35357/v2.0/extensions

{"extensions": {"values": [{"updated": "2013-03-18T13:25:27-06:00", "name": "Hello World Example Extension", "links": [{"href": "https://github.com/openstack/identity-api", "type": "text/html", "rel": "describedby"}], "namespace": "http://docs.openstack.org/identity/api/ext/SEF-HELLO-WORLD/v1.0", "alias": "SEF-HELLO-WORLD", "description": "Openstack extensions to Keystone v2.0 API enabling Admin Operations."}}

To actually invoke the extension, use the following for the two non-authenticated operations:

$ curl http://<em>openstack_ip_addr</em>:35357/v2.0/example/hello_world

{"SEF-EXAMPLE:hello_world": [{"hello": "world", "description": "Simple Hello World Keystone Extension"}]}

$ curl http://<em>openstack_ip_addr</em>:35357/v2.0/example/hello_world/token

{"SEF-EXAMPLE:hello_world_id": [{"identifier": "token", "hello": "world", "description": "Simple Hello World Keystone Extension with Identifier"}]}

Note in the second example that the URL parameter ‘token’ has been passed to the extension handler as the ‘{identifier}’.

Conclusion:

The above gets you going with a Keystone extension, at least for Folsom.  Given the rate at which OpenStack is evolving, it is quite possible that the extension framework may well change in an upcoming release.  One nice enhancement would be to make extensions self-describing which would eliminate the need to add that descriptive meta-data to the ‘controllers.py’ file.