Merge "Remove nova Direct API"
This commit is contained in:
commit
013cf05a13
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user