Remove nova Direct API
blueprint remove-nova-direct-api Change-Id: I3229f8d7f37d66fcd6b978966f3a428a69e08bb1
This commit is contained in:
		@@ -1,110 +0,0 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# pylint: disable=C0103
 | 
			
		||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
 | 
			
		||||
 | 
			
		||||
# Copyright 2010 United States Government as represented by the
 | 
			
		||||
# Administrator of the National Aeronautics and Space Administration.
 | 
			
		||||
# All Rights Reserved.
 | 
			
		||||
#
 | 
			
		||||
#    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.
 | 
			
		||||
 | 
			
		||||
"""Starter script for Nova Direct API."""
 | 
			
		||||
 | 
			
		||||
import eventlet
 | 
			
		||||
eventlet.monkey_patch()
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
# If ../nova/__init__.py exists, add ../ to Python search path, so that
 | 
			
		||||
# it will override what happens to be installed in /usr/(local/)lib/python...
 | 
			
		||||
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
 | 
			
		||||
                                   os.pardir,
 | 
			
		||||
                                   os.pardir))
 | 
			
		||||
if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
 | 
			
		||||
    sys.path.insert(0, possible_topdir)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from nova import compute
 | 
			
		||||
from nova import flags
 | 
			
		||||
from nova import log as logging
 | 
			
		||||
from nova import network
 | 
			
		||||
from nova.openstack.common import cfg
 | 
			
		||||
from nova import service
 | 
			
		||||
from nova import utils
 | 
			
		||||
from nova import volume
 | 
			
		||||
from nova import wsgi
 | 
			
		||||
from nova.api import direct
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
direct_api_opts = [
 | 
			
		||||
    cfg.IntOpt('direct_port',
 | 
			
		||||
               default=8001,
 | 
			
		||||
               help='Direct API port'),
 | 
			
		||||
    cfg.StrOpt('direct_host',
 | 
			
		||||
               default='0.0.0.0',
 | 
			
		||||
               help='Direct API host'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
FLAGS = flags.FLAGS
 | 
			
		||||
FLAGS.register_cli_opts(direct_api_opts)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# An example of an API that only exposes read-only methods.
 | 
			
		||||
# In this case we're just limiting which methods are exposed.
 | 
			
		||||
class ReadOnlyCompute(direct.Limited):
 | 
			
		||||
    """Read-only Compute API."""
 | 
			
		||||
 | 
			
		||||
    _allowed = ['get', 'get_all', 'get_console_output']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# An example of an API that provides a backwards compatibility layer.
 | 
			
		||||
# In this case we're overwriting the implementation to ensure
 | 
			
		||||
# compatibility with an older version. In reality we would want the
 | 
			
		||||
# "description=None" to be part of the actual API so that code
 | 
			
		||||
# like this isn't even necessary, but this example shows what one can
 | 
			
		||||
# do if that isn't the situation.
 | 
			
		||||
class VolumeVersionOne(direct.Limited):
 | 
			
		||||
    _allowed = ['create', 'delete', 'update', 'get']
 | 
			
		||||
 | 
			
		||||
    def create(self, context, size, name):
 | 
			
		||||
        self.proxy.create(context, size, name, description=None)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    utils.default_flagfile()
 | 
			
		||||
    FLAGS(sys.argv)
 | 
			
		||||
    logging.setup()
 | 
			
		||||
 | 
			
		||||
    direct.register_service('compute', compute.API())
 | 
			
		||||
    direct.register_service('volume', volume.API())
 | 
			
		||||
    direct.register_service('network', network.API())
 | 
			
		||||
    direct.register_service('reflect', direct.Reflection())
 | 
			
		||||
 | 
			
		||||
    # Here is how we could expose the code in the examples above.
 | 
			
		||||
    #direct.register_service('compute-readonly',
 | 
			
		||||
    #                        ReadOnlyCompute(compute.API()))
 | 
			
		||||
    #direct.register_service('volume-v1', VolumeVersionOne(volume.API()))
 | 
			
		||||
 | 
			
		||||
    router = direct.Router()
 | 
			
		||||
    with_json = direct.JsonParamsMiddleware(router)
 | 
			
		||||
    with_req = direct.PostParamsMiddleware(with_json)
 | 
			
		||||
    with_auth = direct.DelegatedAuthMiddleware(with_req)
 | 
			
		||||
 | 
			
		||||
    server = wsgi.Server("Direct API",
 | 
			
		||||
                         with_auth,
 | 
			
		||||
                         host=FLAGS.direct_host,
 | 
			
		||||
                         port=FLAGS.direct_port)
 | 
			
		||||
 | 
			
		||||
    service.serve(server)
 | 
			
		||||
    service.wait()
 | 
			
		||||
							
								
								
									
										162
									
								
								bin/stack
									
									
									
									
									
								
							
							
						
						
									
										162
									
								
								bin/stack
									
									
									
									
									
								
							@@ -1,162 +0,0 @@
 | 
			
		||||
#!/usr/bin/env python
 | 
			
		||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
 | 
			
		||||
 | 
			
		||||
# Copyright 2010 United States Government as represented by the
 | 
			
		||||
# Administrator of the National Aeronautics and Space Administration.
 | 
			
		||||
# All Rights Reserved.
 | 
			
		||||
#
 | 
			
		||||
#    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.
 | 
			
		||||
 | 
			
		||||
"""CLI for the Direct API."""
 | 
			
		||||
 | 
			
		||||
import eventlet
 | 
			
		||||
eventlet.monkey_patch()
 | 
			
		||||
 | 
			
		||||
import json
 | 
			
		||||
import os
 | 
			
		||||
import pprint
 | 
			
		||||
import sys
 | 
			
		||||
import textwrap
 | 
			
		||||
import urllib
 | 
			
		||||
import urllib2
 | 
			
		||||
 | 
			
		||||
# If ../nova/__init__.py exists, add ../ to Python search path, so that
 | 
			
		||||
# it will override what happens to be installed in /usr/(local/)lib/python...
 | 
			
		||||
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
 | 
			
		||||
                                   os.pardir,
 | 
			
		||||
                                   os.pardir))
 | 
			
		||||
if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
 | 
			
		||||
    sys.path.insert(0, possible_topdir)
 | 
			
		||||
 | 
			
		||||
import gflags
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
FLAGS = gflags.FLAGS
 | 
			
		||||
gflags.DEFINE_string('host', '127.0.0.1', 'Direct API host')
 | 
			
		||||
gflags.DEFINE_integer('port', 8001, 'Direct API host')
 | 
			
		||||
gflags.DEFINE_string('user', 'user1', 'Direct API username')
 | 
			
		||||
gflags.DEFINE_string('project', 'proj1', 'Direct API project')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
USAGE = """usage: stack [options] <controller> <method> [arg1=value arg2=value]
 | 
			
		||||
 | 
			
		||||
  `stack help` should output the list of available controllers
 | 
			
		||||
  `stack <controller>` should output the available methods for that controller
 | 
			
		||||
  `stack help <controller>` should do the same
 | 
			
		||||
  `stack help <controller> <method>` should output info for a method
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def format_help(d):
 | 
			
		||||
    """Format help text, keys are labels and values are descriptions."""
 | 
			
		||||
    MAX_INDENT = 30
 | 
			
		||||
    indent = max([len(k) for k in d])
 | 
			
		||||
    if indent > MAX_INDENT:
 | 
			
		||||
        indent = MAX_INDENT - 6
 | 
			
		||||
 | 
			
		||||
    out = []
 | 
			
		||||
    for k, v in sorted(d.iteritems()):
 | 
			
		||||
        if (len(k) + 6) > MAX_INDENT:
 | 
			
		||||
            out.extend(['   %s' % k])
 | 
			
		||||
            initial_indent = ' ' * (indent + 6)
 | 
			
		||||
        else:
 | 
			
		||||
            initial_indent = '   %s   ' % k.ljust(indent)
 | 
			
		||||
        subsequent_indent = ' ' * (indent + 6)
 | 
			
		||||
        t = textwrap.TextWrapper(initial_indent=initial_indent,
 | 
			
		||||
                                 subsequent_indent=subsequent_indent)
 | 
			
		||||
        out.extend(t.wrap(v))
 | 
			
		||||
    return out
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def help_all():
 | 
			
		||||
    rv = do_request('reflect', 'get_controllers')
 | 
			
		||||
    out = format_help(rv)
 | 
			
		||||
    return (USAGE + str(FLAGS.MainModuleHelp()) +
 | 
			
		||||
            '\n\nAvailable controllers:\n' +
 | 
			
		||||
            '\n'.join(out) + '\n')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def help_controller(controller):
 | 
			
		||||
    rv = do_request('reflect', 'get_methods')
 | 
			
		||||
    methods = dict([(k.split('/')[2], v) for k, v in rv.iteritems()
 | 
			
		||||
               if k.startswith('/%s' % controller)])
 | 
			
		||||
    return ('Available methods for %s:\n' % controller +
 | 
			
		||||
            '\n'.join(format_help(methods)))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def help_method(controller, method):
 | 
			
		||||
    rv = do_request('reflect',
 | 
			
		||||
                    'get_method_info',
 | 
			
		||||
                    {'method': '/%s/%s' % (controller, method)})
 | 
			
		||||
 | 
			
		||||
    sig = '%s(%s):' % (method, ', '.join(['='.join(x) for x in rv['args']]))
 | 
			
		||||
    out = textwrap.wrap(sig, subsequent_indent=' ' * len('%s(' % method))
 | 
			
		||||
    out.append('\n' + rv['doc'])
 | 
			
		||||
    return '\n'.join(out)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def do_request(controller, method, params=None):
 | 
			
		||||
    if params:
 | 
			
		||||
        data = urllib.urlencode(params)
 | 
			
		||||
    else:
 | 
			
		||||
        data = None
 | 
			
		||||
 | 
			
		||||
    url = 'http://%s:%s/%s/%s' % (FLAGS.host, FLAGS.port, controller, method)
 | 
			
		||||
    headers = {'X-OpenStack-User': FLAGS.user,
 | 
			
		||||
               'X-OpenStack-Project': FLAGS.project}
 | 
			
		||||
 | 
			
		||||
    req = urllib2.Request(url, data, headers)
 | 
			
		||||
    try:
 | 
			
		||||
        resp = urllib2.urlopen(req)
 | 
			
		||||
    except urllib2.HTTPError, e:
 | 
			
		||||
        print e.read()
 | 
			
		||||
        sys.exit(1)
 | 
			
		||||
    except urllib2.URLError, e:
 | 
			
		||||
        print 'Failed to connect to %s: %s' % (url, e.reason)
 | 
			
		||||
        sys.exit(1)
 | 
			
		||||
    return json.loads(resp.read())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    args = FLAGS(sys.argv)
 | 
			
		||||
 | 
			
		||||
    cmd = args.pop(0)
 | 
			
		||||
    if not args:
 | 
			
		||||
        print help_all()
 | 
			
		||||
        sys.exit()
 | 
			
		||||
 | 
			
		||||
    first = args.pop(0)
 | 
			
		||||
    if first == 'help':
 | 
			
		||||
        action = help_all
 | 
			
		||||
        params = []
 | 
			
		||||
        if args:
 | 
			
		||||
            params.append(args.pop(0))
 | 
			
		||||
            action = help_controller
 | 
			
		||||
        if args:
 | 
			
		||||
            params.append(args.pop(0))
 | 
			
		||||
            action = help_method
 | 
			
		||||
        print action(*params)
 | 
			
		||||
        sys.exit(0)
 | 
			
		||||
 | 
			
		||||
    controller = first
 | 
			
		||||
    if not args:
 | 
			
		||||
        print help_controller(controller)
 | 
			
		||||
        sys.exit()
 | 
			
		||||
 | 
			
		||||
    method = args.pop(0)
 | 
			
		||||
    params = {}
 | 
			
		||||
    for x in args:
 | 
			
		||||
        key, value = x.split('=', 1)
 | 
			
		||||
        params[key] = value
 | 
			
		||||
 | 
			
		||||
    pprint.pprint(do_request(controller, method, params))
 | 
			
		||||
@@ -1,378 +0,0 @@
 | 
			
		||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
 | 
			
		||||
 | 
			
		||||
# Copyright 2010 United States Government as represented by the
 | 
			
		||||
# Administrator of the National Aeronautics and Space Administration.
 | 
			
		||||
# All Rights Reserved.
 | 
			
		||||
#
 | 
			
		||||
#    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.
 | 
			
		||||
 | 
			
		||||
"""Public HTTP interface that allows services to self-register.
 | 
			
		||||
 | 
			
		||||
The general flow of a request is:
 | 
			
		||||
    - Request is parsed into WSGI bits.
 | 
			
		||||
    - Some middleware checks authentication.
 | 
			
		||||
    - Routing takes place based on the URL to find a controller.
 | 
			
		||||
      (/controller/method)
 | 
			
		||||
    - Parameters are parsed from the request and passed to a method on the
 | 
			
		||||
      controller as keyword arguments.
 | 
			
		||||
      - Optionally 'json' is decoded to provide all the parameters.
 | 
			
		||||
    - Actual work is done and a result is returned.
 | 
			
		||||
    - That result is turned into json and returned.
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import inspect
 | 
			
		||||
import urllib
 | 
			
		||||
 | 
			
		||||
import routes
 | 
			
		||||
import webob
 | 
			
		||||
 | 
			
		||||
import nova.api.openstack.wsgi
 | 
			
		||||
from nova import context
 | 
			
		||||
from nova import exception
 | 
			
		||||
from nova import utils
 | 
			
		||||
from nova import wsgi
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Global storage for registering modules.
 | 
			
		||||
ROUTES = {}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def register_service(path, handle):
 | 
			
		||||
    """Register a service handle at a given path.
 | 
			
		||||
 | 
			
		||||
    Services registered in this way will be made available to any instances of
 | 
			
		||||
    nova.api.direct.Router.
 | 
			
		||||
 | 
			
		||||
    :param path: `routes` path, can be a basic string like "/path"
 | 
			
		||||
    :param handle: an object whose methods will be made available via the api
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    ROUTES[path] = handle
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Router(wsgi.Router):
 | 
			
		||||
    """A simple WSGI router configured via `register_service`.
 | 
			
		||||
 | 
			
		||||
    This is a quick way to attach multiple services to a given endpoint.
 | 
			
		||||
    It will automatically load the routes registered in the `ROUTES` global.
 | 
			
		||||
 | 
			
		||||
    TODO(termie): provide a paste-deploy version of this.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, mapper=None):
 | 
			
		||||
        if mapper is None:
 | 
			
		||||
            mapper = routes.Mapper()
 | 
			
		||||
 | 
			
		||||
        self._load_registered_routes(mapper)
 | 
			
		||||
        super(Router, self).__init__(mapper=mapper)
 | 
			
		||||
 | 
			
		||||
    def _load_registered_routes(self, mapper):
 | 
			
		||||
        for route in ROUTES:
 | 
			
		||||
            mapper.connect('/%s/{action}' % route,
 | 
			
		||||
                           controller=ServiceWrapper(ROUTES[route]))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DelegatedAuthMiddleware(wsgi.Middleware):
 | 
			
		||||
    """A simple and naive authentication middleware.
 | 
			
		||||
 | 
			
		||||
    Designed mostly to provide basic support for alternative authentication
 | 
			
		||||
    schemes, this middleware only desires the identity of the user and will
 | 
			
		||||
    generate the appropriate nova.context.RequestContext for the rest of the
 | 
			
		||||
    application. This allows any middleware above it in the stack to
 | 
			
		||||
    authenticate however it would like while only needing to conform to a
 | 
			
		||||
    minimal interface.
 | 
			
		||||
 | 
			
		||||
    Expects two headers to determine identity:
 | 
			
		||||
     - X-OpenStack-User
 | 
			
		||||
     - X-OpenStack-Project
 | 
			
		||||
 | 
			
		||||
    This middleware is tied to identity management and will need to be kept
 | 
			
		||||
    in sync with any changes to the way identity is dealt with internally.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def process_request(self, request):
 | 
			
		||||
        os_user = request.headers['X-OpenStack-User']
 | 
			
		||||
        os_project = request.headers['X-OpenStack-Project']
 | 
			
		||||
        context_ref = context.RequestContext(user_id=os_user,
 | 
			
		||||
                                             project_id=os_project)
 | 
			
		||||
        request.environ['openstack.context'] = context_ref
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class JsonParamsMiddleware(wsgi.Middleware):
 | 
			
		||||
    """Middleware to allow method arguments to be passed as serialized JSON.
 | 
			
		||||
 | 
			
		||||
    Accepting arguments as JSON is useful for accepting data that may be more
 | 
			
		||||
    complex than simple primitives.
 | 
			
		||||
 | 
			
		||||
    In this case we accept it as urlencoded data under the key 'json' as in
 | 
			
		||||
    json=<urlencoded_json> but this could be extended to accept raw JSON
 | 
			
		||||
    in the POST body.
 | 
			
		||||
 | 
			
		||||
    Filters out the parameters `self`, `context` and anything beginning with
 | 
			
		||||
    an underscore.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def process_request(self, request):
 | 
			
		||||
        if 'json' not in request.params:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        params_json = request.params['json']
 | 
			
		||||
        params_parsed = utils.loads(params_json)
 | 
			
		||||
        params = {}
 | 
			
		||||
        for k, v in params_parsed.iteritems():
 | 
			
		||||
            if k in ('self', 'context'):
 | 
			
		||||
                continue
 | 
			
		||||
            if k.startswith('_'):
 | 
			
		||||
                continue
 | 
			
		||||
            params[k] = v
 | 
			
		||||
 | 
			
		||||
        request.environ['openstack.params'] = params
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PostParamsMiddleware(wsgi.Middleware):
 | 
			
		||||
    """Middleware to allow method arguments to be passed as POST parameters.
 | 
			
		||||
 | 
			
		||||
    Filters out the parameters `self`, `context` and anything beginning with
 | 
			
		||||
    an underscore.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def process_request(self, request):
 | 
			
		||||
        params_parsed = request.params
 | 
			
		||||
        params = {}
 | 
			
		||||
        for k, v in params_parsed.iteritems():
 | 
			
		||||
            if k in ('self', 'context'):
 | 
			
		||||
                continue
 | 
			
		||||
            if k.startswith('_'):
 | 
			
		||||
                continue
 | 
			
		||||
            params[k] = v
 | 
			
		||||
 | 
			
		||||
        request.environ['openstack.params'] = params
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Reflection(object):
 | 
			
		||||
    """Reflection methods to list available methods.
 | 
			
		||||
 | 
			
		||||
    This is an object that expects to be registered via register_service.
 | 
			
		||||
    These methods allow the endpoint to be self-describing. They introspect
 | 
			
		||||
    the exposed methods and provide call signatures and documentation for
 | 
			
		||||
    them allowing quick experimentation.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        self._methods = {}
 | 
			
		||||
        self._controllers = {}
 | 
			
		||||
 | 
			
		||||
    def _gather_methods(self):
 | 
			
		||||
        """Introspect available methods and generate documentation for them."""
 | 
			
		||||
        methods = {}
 | 
			
		||||
        controllers = {}
 | 
			
		||||
        for route, handler in ROUTES.iteritems():
 | 
			
		||||
            controllers[route] = handler.__doc__.split('\n')[0]
 | 
			
		||||
            for k in dir(handler):
 | 
			
		||||
                if k.startswith('_'):
 | 
			
		||||
                    continue
 | 
			
		||||
                f = getattr(handler, k)
 | 
			
		||||
                if not callable(f):
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                # bunch of ugly formatting stuff
 | 
			
		||||
                argspec = inspect.getargspec(f)
 | 
			
		||||
                args = [x for x in argspec[0]
 | 
			
		||||
                        if x != 'self' and x != 'context']
 | 
			
		||||
                defaults = argspec[3] and argspec[3] or []
 | 
			
		||||
                args_r = list(reversed(args))
 | 
			
		||||
                defaults_r = list(reversed(defaults))
 | 
			
		||||
 | 
			
		||||
                args_out = []
 | 
			
		||||
                while args_r:
 | 
			
		||||
                    if defaults_r:
 | 
			
		||||
                        args_out.append((args_r.pop(0),
 | 
			
		||||
                                         repr(defaults_r.pop(0))))
 | 
			
		||||
                    else:
 | 
			
		||||
                        args_out.append((str(args_r.pop(0)),))
 | 
			
		||||
 | 
			
		||||
                # if the method accepts keywords
 | 
			
		||||
                if argspec[2]:
 | 
			
		||||
                    args_out.insert(0, ('**%s' % argspec[2],))
 | 
			
		||||
 | 
			
		||||
                if f.__doc__:
 | 
			
		||||
                    short_doc = f.__doc__.split('\n')[0]
 | 
			
		||||
                    doc = f.__doc__
 | 
			
		||||
                else:
 | 
			
		||||
                    short_doc = doc = _('not available')
 | 
			
		||||
 | 
			
		||||
                methods['/%s/%s' % (route, k)] = {
 | 
			
		||||
                        'short_doc': short_doc,
 | 
			
		||||
                        'doc': doc,
 | 
			
		||||
                        'name': k,
 | 
			
		||||
                        'args': list(reversed(args_out))}
 | 
			
		||||
 | 
			
		||||
        self._methods = methods
 | 
			
		||||
        self._controllers = controllers
 | 
			
		||||
 | 
			
		||||
    def get_controllers(self, context):
 | 
			
		||||
        """List available controllers."""
 | 
			
		||||
        if not self._controllers:
 | 
			
		||||
            self._gather_methods()
 | 
			
		||||
 | 
			
		||||
        return self._controllers
 | 
			
		||||
 | 
			
		||||
    def get_methods(self, context):
 | 
			
		||||
        """List available methods."""
 | 
			
		||||
        if not self._methods:
 | 
			
		||||
            self._gather_methods()
 | 
			
		||||
 | 
			
		||||
        method_list = self._methods.keys()
 | 
			
		||||
        method_list.sort()
 | 
			
		||||
        methods = {}
 | 
			
		||||
        for k in method_list:
 | 
			
		||||
            methods[k] = self._methods[k]['short_doc']
 | 
			
		||||
        return methods
 | 
			
		||||
 | 
			
		||||
    def get_method_info(self, context, method):
 | 
			
		||||
        """Get detailed information about a method."""
 | 
			
		||||
        if not self._methods:
 | 
			
		||||
            self._gather_methods()
 | 
			
		||||
        return self._methods[method]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ServiceWrapper(object):
 | 
			
		||||
    """Wrapper to dynamically provide a WSGI controller for arbitrary objects.
 | 
			
		||||
 | 
			
		||||
    With lightweight introspection allows public methods on the object to
 | 
			
		||||
    be accessed via simple WSGI routing and parameters and serializes the
 | 
			
		||||
    return values.
 | 
			
		||||
 | 
			
		||||
    Automatically used be nova.api.direct.Router to wrap registered instances.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, service_handle):
 | 
			
		||||
        self.service_handle = service_handle
 | 
			
		||||
 | 
			
		||||
    @webob.dec.wsgify(RequestClass=nova.api.openstack.wsgi.Request)
 | 
			
		||||
    def __call__(self, req):
 | 
			
		||||
        arg_dict = req.environ['wsgiorg.routing_args'][1]
 | 
			
		||||
        action = arg_dict['action']
 | 
			
		||||
        del arg_dict['action']
 | 
			
		||||
 | 
			
		||||
        context = req.environ['openstack.context']
 | 
			
		||||
        # allow middleware up the stack to override the params
 | 
			
		||||
        params = {}
 | 
			
		||||
        if 'openstack.params' in req.environ:
 | 
			
		||||
            params = req.environ['openstack.params']
 | 
			
		||||
 | 
			
		||||
        # TODO(termie): do some basic normalization on methods
 | 
			
		||||
        method = getattr(self.service_handle, action)
 | 
			
		||||
 | 
			
		||||
        # NOTE(vish): make sure we have no unicode keys for py2.6.
 | 
			
		||||
        params = dict([(str(k), v) for (k, v) in params.iteritems()])
 | 
			
		||||
        result = method(context, **params)
 | 
			
		||||
 | 
			
		||||
        if result is None or isinstance(result, basestring):
 | 
			
		||||
            return result
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            content_type = req.best_match_content_type()
 | 
			
		||||
            serializer = {
 | 
			
		||||
              'application/xml': nova.api.openstack.wsgi.XMLDictSerializer(),
 | 
			
		||||
              'application/json': nova.api.openstack.wsgi.JSONDictSerializer(),
 | 
			
		||||
            }[content_type]
 | 
			
		||||
            return serializer.serialize(result)
 | 
			
		||||
        except Exception, e:
 | 
			
		||||
            raise exception.Error(_("Returned non-serializeable type: %s")
 | 
			
		||||
                                  % result)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Limited(object):
 | 
			
		||||
    __notdoc = """Limit the available methods on a given object.
 | 
			
		||||
 | 
			
		||||
    (Not a docstring so that the docstring can be conditionally overridden.)
 | 
			
		||||
 | 
			
		||||
    Useful when defining a public API that only exposes a subset of an
 | 
			
		||||
    internal API.
 | 
			
		||||
 | 
			
		||||
    Expected usage of this class is to define a subclass that lists the allowed
 | 
			
		||||
    methods in the 'allowed' variable.
 | 
			
		||||
 | 
			
		||||
    Additionally where appropriate methods can be added or overwritten, for
 | 
			
		||||
    example to provide backwards compatibility.
 | 
			
		||||
 | 
			
		||||
    The wrapping approach has been chosen so that the wrapped API can maintain
 | 
			
		||||
    its own internal consistency, for example if it calls "self.create" it
 | 
			
		||||
    should get its own create method rather than anything we do here.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    _allowed = None
 | 
			
		||||
 | 
			
		||||
    def __init__(self, proxy):
 | 
			
		||||
        self._proxy = proxy
 | 
			
		||||
        if not self.__doc__:  # pylint: disable=E0203
 | 
			
		||||
            self.__doc__ = proxy.__doc__
 | 
			
		||||
        if not self._allowed:
 | 
			
		||||
            self._allowed = []
 | 
			
		||||
 | 
			
		||||
    def __getattr__(self, key):
 | 
			
		||||
        """Only return methods that are named in self._allowed."""
 | 
			
		||||
        if key not in self._allowed:
 | 
			
		||||
            raise AttributeError()
 | 
			
		||||
        return getattr(self._proxy, key)
 | 
			
		||||
 | 
			
		||||
    def __dir__(self):
 | 
			
		||||
        """Only return methods that are named in self._allowed."""
 | 
			
		||||
        return [x for x in dir(self._proxy) if x in self._allowed]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Proxy(object):
 | 
			
		||||
    """Pretend a Direct API endpoint is an object.
 | 
			
		||||
 | 
			
		||||
    This is mostly useful in testing at the moment though it should be easily
 | 
			
		||||
    extendable to provide a basic API library functionality.
 | 
			
		||||
 | 
			
		||||
    In testing we use this to stub out internal objects to verify that results
 | 
			
		||||
    from the API are serializable.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, app, prefix=None):
 | 
			
		||||
        self.app = app
 | 
			
		||||
        self.prefix = prefix
 | 
			
		||||
 | 
			
		||||
    def __do_request(self, path, context, **kwargs):
 | 
			
		||||
        req = wsgi.Request.blank(path)
 | 
			
		||||
        req.method = 'POST'
 | 
			
		||||
        req.body = urllib.urlencode({'json': utils.dumps(kwargs)})
 | 
			
		||||
        req.environ['openstack.context'] = context
 | 
			
		||||
        resp = req.get_response(self.app)
 | 
			
		||||
        try:
 | 
			
		||||
            return utils.loads(resp.body)
 | 
			
		||||
        except Exception:
 | 
			
		||||
            return resp.body
 | 
			
		||||
 | 
			
		||||
    def __getattr__(self, key):
 | 
			
		||||
        if self.prefix is None:
 | 
			
		||||
            return self.__class__(self.app, prefix=key)
 | 
			
		||||
 | 
			
		||||
        def _wrapper(context, **kwargs):
 | 
			
		||||
            return self.__do_request('/%s/%s' % (self.prefix, key),
 | 
			
		||||
                                     context,
 | 
			
		||||
                                     **kwargs)
 | 
			
		||||
        _wrapper.func_name = key
 | 
			
		||||
        return _wrapper
 | 
			
		||||
@@ -1,216 +0,0 @@
 | 
			
		||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
 | 
			
		||||
 | 
			
		||||
# Copyright 2010 United States Government as represented by the
 | 
			
		||||
# Administrator of the National Aeronautics and Space Administration.
 | 
			
		||||
# All Rights Reserved.
 | 
			
		||||
#
 | 
			
		||||
#    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.
 | 
			
		||||
 | 
			
		||||
"""Tests for Direct API."""
 | 
			
		||||
 | 
			
		||||
import json
 | 
			
		||||
 | 
			
		||||
import webob
 | 
			
		||||
 | 
			
		||||
from nova import context
 | 
			
		||||
from nova import exception
 | 
			
		||||
from nova import test
 | 
			
		||||
from nova.api import direct
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ArbitraryObject(object):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FakeService(object):
 | 
			
		||||
    def echo(self, context, data):
 | 
			
		||||
        return {'data': data}
 | 
			
		||||
 | 
			
		||||
    def context(self, context):
 | 
			
		||||
        return {'user': context.user_id,
 | 
			
		||||
                'project': context.project_id}
 | 
			
		||||
 | 
			
		||||
    def echo_data_directly(self, context, data):
 | 
			
		||||
        return data
 | 
			
		||||
 | 
			
		||||
    def invalid_return(self, context):
 | 
			
		||||
        return ArbitraryObject()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MyLimited(direct.Limited):
 | 
			
		||||
    _allowed = ['var1', 'func1']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MyProxy(object):
 | 
			
		||||
    var1 = var2 = True
 | 
			
		||||
 | 
			
		||||
    def func1(self):
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def func2(self):
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DirectTestCase(test.TestCase):
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super(DirectTestCase, self).setUp()
 | 
			
		||||
        direct.register_service('fake', FakeService())
 | 
			
		||||
        self.router = direct.PostParamsMiddleware(
 | 
			
		||||
                direct.JsonParamsMiddleware(
 | 
			
		||||
                        direct.Router()))
 | 
			
		||||
        self.auth_router = direct.DelegatedAuthMiddleware(self.router)
 | 
			
		||||
        self.context = context.RequestContext('user1', 'proj1')
 | 
			
		||||
 | 
			
		||||
    def tearDown(self):
 | 
			
		||||
        direct.ROUTES = {}
 | 
			
		||||
        super(DirectTestCase, self).tearDown()
 | 
			
		||||
 | 
			
		||||
    def test_delegated_auth(self):
 | 
			
		||||
        req = webob.Request.blank('/fake/context')
 | 
			
		||||
        req.headers['X-OpenStack-User'] = 'user1'
 | 
			
		||||
        req.headers['X-OpenStack-Project'] = 'proj1'
 | 
			
		||||
        resp = req.get_response(self.auth_router)
 | 
			
		||||
        self.assertEqual(resp.status_int, 200)
 | 
			
		||||
        data = json.loads(resp.body)
 | 
			
		||||
        self.assertEqual(data['user'], 'user1')
 | 
			
		||||
        self.assertEqual(data['project'], 'proj1')
 | 
			
		||||
 | 
			
		||||
    def test_json_params(self):
 | 
			
		||||
        req = webob.Request.blank('/fake/echo')
 | 
			
		||||
        req.environ['openstack.context'] = self.context
 | 
			
		||||
        req.method = 'POST'
 | 
			
		||||
        req.body = 'json=%s' % json.dumps({'data': 'foo'})
 | 
			
		||||
        resp = req.get_response(self.router)
 | 
			
		||||
        self.assertEqual(resp.status_int, 200)
 | 
			
		||||
        resp_parsed = json.loads(resp.body)
 | 
			
		||||
        self.assertEqual(resp_parsed['data'], 'foo')
 | 
			
		||||
 | 
			
		||||
    def test_filter_json_params(self):
 | 
			
		||||
        req = webob.Request.blank('/fake/echo')
 | 
			
		||||
        req.environ['openstack.context'] = self.context
 | 
			
		||||
        req.method = 'POST'
 | 
			
		||||
        req.body = 'json=%s' % json.dumps({'data': 'foo',
 | 
			
		||||
                                           '_underscored': 'ignoreMe',
 | 
			
		||||
                                           'self': 'ignoreMe',
 | 
			
		||||
                                           'context': 'ignoreMe'})
 | 
			
		||||
        resp = req.get_response(self.router)
 | 
			
		||||
        self.assertEqual(resp.status_int, 200)
 | 
			
		||||
        resp_parsed = json.loads(resp.body)
 | 
			
		||||
        self.assertEqual(resp_parsed['data'], 'foo')
 | 
			
		||||
        self.assertNotIn('_underscored', resp_parsed)
 | 
			
		||||
        self.assertNotIn('self', resp_parsed)
 | 
			
		||||
        self.assertNotIn('context', resp_parsed)
 | 
			
		||||
 | 
			
		||||
    def test_post_params(self):
 | 
			
		||||
        req = webob.Request.blank('/fake/echo')
 | 
			
		||||
        req.environ['openstack.context'] = self.context
 | 
			
		||||
        req.method = 'POST'
 | 
			
		||||
        req.body = 'data=foo'
 | 
			
		||||
        resp = req.get_response(self.router)
 | 
			
		||||
        self.assertEqual(resp.status_int, 200)
 | 
			
		||||
        resp_parsed = json.loads(resp.body)
 | 
			
		||||
        self.assertEqual(resp_parsed['data'], 'foo')
 | 
			
		||||
 | 
			
		||||
    def test_filter_post_params(self):
 | 
			
		||||
        req = webob.Request.blank('/fake/echo')
 | 
			
		||||
        req.environ['openstack.context'] = self.context
 | 
			
		||||
        req.method = 'POST'
 | 
			
		||||
        req.body = ('data=foo&_underscored=ignoreMe&self=ignoreMe&context='
 | 
			
		||||
                    'ignoreMe')
 | 
			
		||||
        resp = req.get_response(self.router)
 | 
			
		||||
        self.assertEqual(resp.status_int, 200)
 | 
			
		||||
        resp_parsed = json.loads(resp.body)
 | 
			
		||||
        self.assertEqual(resp_parsed['data'], 'foo')
 | 
			
		||||
        self.assertNotIn('_underscored', resp_parsed)
 | 
			
		||||
        self.assertNotIn('self', resp_parsed)
 | 
			
		||||
        self.assertNotIn('context', resp_parsed)
 | 
			
		||||
 | 
			
		||||
    def test_string_resp(self):
 | 
			
		||||
        req = webob.Request.blank('/fake/echo_data_directly')
 | 
			
		||||
        req.environ['openstack.context'] = self.context
 | 
			
		||||
        req.method = 'POST'
 | 
			
		||||
        req.body = 'data=foo'
 | 
			
		||||
        resp = req.get_response(self.router)
 | 
			
		||||
        self.assertEqual(resp.status_int, 200)
 | 
			
		||||
        self.assertEqual(resp.body, 'foo')
 | 
			
		||||
 | 
			
		||||
    def test_invalid(self):
 | 
			
		||||
        req = webob.Request.blank('/fake/invalid_return')
 | 
			
		||||
        req.environ['openstack.context'] = self.context
 | 
			
		||||
        req.method = 'POST'
 | 
			
		||||
        self.assertRaises(exception.Error, req.get_response, self.router)
 | 
			
		||||
 | 
			
		||||
    def test_proxy(self):
 | 
			
		||||
        proxy = direct.Proxy(self.router)
 | 
			
		||||
        rv = proxy.fake.echo(self.context, data='baz')
 | 
			
		||||
        self.assertEqual(rv['data'], 'baz')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LimitedTestCase(test.TestCase):
 | 
			
		||||
    def test_limited_class_getattr(self):
 | 
			
		||||
        limited = MyLimited(MyProxy())
 | 
			
		||||
 | 
			
		||||
        # Allowed are still visible
 | 
			
		||||
        self.assertTrue(limited.func1())
 | 
			
		||||
        self.assertTrue(limited.var1)
 | 
			
		||||
 | 
			
		||||
        # Non-allowed are no longer visible
 | 
			
		||||
        self.assertRaises(AttributeError, getattr, limited, 'func2')
 | 
			
		||||
        self.assertRaises(AttributeError, getattr, limited, 'var2')
 | 
			
		||||
 | 
			
		||||
    def test_limited_class_dir(self):
 | 
			
		||||
        limited = MyLimited(MyProxy())
 | 
			
		||||
 | 
			
		||||
        # Allowed are still visible
 | 
			
		||||
        self.assertIn('func1', dir(limited))
 | 
			
		||||
        self.assertIn('var1', dir(limited))
 | 
			
		||||
 | 
			
		||||
        # Non-allowed are no longer visible
 | 
			
		||||
        self.assertNotIn('func2', dir(limited))
 | 
			
		||||
        self.assertNotIn('var2', dir(limited))
 | 
			
		||||
 | 
			
		||||
    def test_limited_class_no_allowed(self):
 | 
			
		||||
 | 
			
		||||
        # New MyLimited class with no _allowed variable
 | 
			
		||||
        class MyLimited(direct.Limited):
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        limited = MyLimited(MyProxy())
 | 
			
		||||
 | 
			
		||||
        # Nothing in MyProxy object visible now
 | 
			
		||||
        self.assertNotIn('func1', dir(limited))
 | 
			
		||||
        self.assertNotIn('var1', dir(limited))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# NOTE(jkoelker): This fails using the EC2 api
 | 
			
		||||
#class DirectCloudTestCase(test_cloud.CloudTestCase):
 | 
			
		||||
#    def setUp(self):
 | 
			
		||||
#        super(DirectCloudTestCase, self).setUp()
 | 
			
		||||
#        compute_handle = compute.API(image_service=self.cloud.image_service)
 | 
			
		||||
#        volume_handle = volume.API()
 | 
			
		||||
#        network_handle = network.API()
 | 
			
		||||
#        direct.register_service('compute', compute_handle)
 | 
			
		||||
#        direct.register_service('volume', volume_handle)
 | 
			
		||||
#        direct.register_service('network', network_handle)
 | 
			
		||||
#
 | 
			
		||||
#        self.router = direct.JsonParamsMiddleware(direct.Router())
 | 
			
		||||
#        proxy = direct.Proxy(self.router)
 | 
			
		||||
#        self.cloud.compute_api = proxy.compute
 | 
			
		||||
#        self.cloud.volume_api = proxy.volume
 | 
			
		||||
#        self.cloud.network_api = proxy.network
 | 
			
		||||
#        compute_handle.volume_api = proxy.volume
 | 
			
		||||
#        compute_handle.network_api = proxy.network
 | 
			
		||||
#
 | 
			
		||||
#    def tearDown(self):
 | 
			
		||||
#        super(DirectCloudTestCase, self).tearDown()
 | 
			
		||||
#        direct.ROUTES = {}
 | 
			
		||||
							
								
								
									
										4
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								setup.py
									
									
									
									
									
								
							@@ -76,13 +76,11 @@ setuptools.setup(name='nova',
 | 
			
		||||
               'bin/nova-console',
 | 
			
		||||
               'bin/nova-consoleauth',
 | 
			
		||||
               'bin/nova-dhcpbridge',
 | 
			
		||||
               'bin/nova-direct-api',
 | 
			
		||||
               'bin/nova-manage',
 | 
			
		||||
               'bin/nova-network',
 | 
			
		||||
               'bin/nova-objectstore',
 | 
			
		||||
               'bin/nova-rootwrap',
 | 
			
		||||
               'bin/nova-scheduler',
 | 
			
		||||
               'bin/nova-volume',
 | 
			
		||||
               'bin/nova-xvpvncproxy',
 | 
			
		||||
               'bin/stack'],
 | 
			
		||||
               'bin/nova-xvpvncproxy'],
 | 
			
		||||
        py_modules=[])
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,6 @@ kombu==1.0.4
 | 
			
		||||
lockfile==0.8
 | 
			
		||||
lxml==2.3
 | 
			
		||||
python-daemon==1.5.5
 | 
			
		||||
python-gflags==1.3
 | 
			
		||||
python-novaclient
 | 
			
		||||
routes==1.12.3
 | 
			
		||||
WebOb==1.0.8
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user