Merge "Remove nova Direct API"
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,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 = {}
|
|
||||||
Reference in New Issue
Block a user