summaryrefslogtreecommitdiffstats
path: root/nova/api
diff options
context:
space:
mode:
authorAndy Smith <code@term.ie>2011-01-15 02:25:00 +0000
committerTarmac <>2011-01-15 02:25:00 +0000
commit825652456ac826a2108956ba8a9cbdc8221520dc (patch)
tree9b47def598777854627be4ffa3124049b46ca08f /nova/api
parent34ceed1ce114ab01eca06eced00a204ae71dc3db (diff)
parent69c11c27c20c74aced491ecfe78a80872ad6232a (diff)
Adds a developer interface with direct access to the internal inter-service APIs and a command-line tool based on reflection to interact with them.
Example output from command-line tool: (.nova-venv)termie@preciousroy:p/nova/easy_api % ./bin/stack 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 ./bin/stack:   -?,--[no]help: show this help   --[no]helpshort: show usage only for this module   --[no]helpxml: like --help, but generates XML output   --host: Direct API host     (default: '127.0.0.1')   --port: Direct API host     (default: '8001')     (an integer)   --project: Direct API project     (default: 'proj1')   --user: Direct API username     (default: 'user1') Available controllers:    reflect Reflection methods to list available methods.    compute API for interacting with the compute manager. (.nova-venv)termie@preciousroy:p/nova/easy_api % ./bin/stack help reflect Available methods for reflect:    get_controllers List available controllers.    get_methods List available methods.    get_method_info Get detailed information about a method. (.nova-venv)termie@preciousroy:p/nova/easy_api % ./bin/stack help reflect get_method_info get_method_info(method): Get detailed information about a method. (.nova-venv)termie@preciousroy:p/nova/easy_api % ./bin/stack reflect get_method_info method=/reflect/get_method_info {u'args': [[u'method']],  u'doc': u'Get detailed information about a method.',  u'name': u'get_method_info',  u'short_doc': u'Get detailed information about a method.'}
Diffstat (limited to 'nova/api')
-rw-r--r--nova/api/direct.py232
-rw-r--r--nova/api/ec2/cloud.py47
2 files changed, 262 insertions, 17 deletions
diff --git a/nova/api/direct.py b/nova/api/direct.py
new file mode 100644
index 000000000..81b3ae202
--- /dev/null
+++ b/nova/api/direct.py
@@ -0,0 +1,232 @@
+# 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
+
+from nova import context
+from nova import flags
+from nova import utils
+from nova import wsgi
+
+
+ROUTES = {}
+
+
+def register_service(path, handle):
+ ROUTES[path] = handle
+
+
+class Router(wsgi.Router):
+ 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):
+ def process_request(self, request):
+ os_user = request.headers['X-OpenStack-User']
+ os_project = request.headers['X-OpenStack-Project']
+ context_ref = context.RequestContext(user=os_user, project=os_project)
+ request.environ['openstack.context'] = context_ref
+
+
+class JsonParamsMiddleware(wsgi.Middleware):
+ 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):
+ 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."""
+ def __init__(self):
+ self._methods = {}
+ self._controllers = {}
+
+ def _gather_methods(self):
+ 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],))
+
+ methods['/%s/%s' % (route, k)] = {
+ 'short_doc': f.__doc__.split('\n')[0],
+ 'doc': f.__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(wsgi.Controller):
+ def __init__(self, service_handle):
+ self.service_handle = service_handle
+
+ @webob.dec.wsgify
+ 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)
+
+ result = method(context, **params)
+ if type(result) is dict or type(result) is list:
+ return self._serialize(result, req)
+ else:
+ return result
+
+
+class Proxy(object):
+ """Pretend a Direct API endpoint is an object."""
+ def __init__(self, app, prefix=None):
+ self.app = app
+ self.prefix = prefix
+
+ def __do_request(self, path, context, **kwargs):
+ req = webob.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
diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py
index fb7e6a59f..a1ae70fb0 100644
--- a/nova/api/ec2/cloud.py
+++ b/nova/api/ec2/cloud.py
@@ -92,8 +92,11 @@ class CloudController(object):
self.image_service = utils.import_object(FLAGS.image_service)
self.network_api = network.API()
self.volume_api = volume.API()
- self.compute_api = compute.API(self.image_service, self.network_api,
- self.volume_api)
+ self.compute_api = compute.API(
+ network_api=self.network_api,
+ image_service=self.image_service,
+ volume_api=self.volume_api,
+ hostname_factory=id_to_ec2_id)
self.setup()
def __str__(self):
@@ -512,7 +515,8 @@ class CloudController(object):
# instance_id is passed in as a list of instances
ec2_id = instance_id[0]
instance_id = ec2_id_to_id(ec2_id)
- output = self.compute_api.get_console_output(context, instance_id)
+ output = self.compute_api.get_console_output(
+ context, instance_id=instance_id)
now = datetime.datetime.utcnow()
return {"InstanceId": ec2_id,
"Timestamp": now,
@@ -580,7 +584,7 @@ class CloudController(object):
def delete_volume(self, context, volume_id, **kwargs):
volume_id = ec2_id_to_id(volume_id)
- self.volume_api.delete(context, volume_id)
+ self.volume_api.delete(context, volume_id=volume_id)
return True
def update_volume(self, context, volume_id, **kwargs):
@@ -597,9 +601,12 @@ class CloudController(object):
def attach_volume(self, context, volume_id, instance_id, device, **kwargs):
volume_id = ec2_id_to_id(volume_id)
instance_id = ec2_id_to_id(instance_id)
- LOG.audit(_("Attach volume %s to instacne %s at %s"), volume_id,
+ LOG.audit(_("Attach volume %s to instance %s at %s"), volume_id,
instance_id, device, context=context)
- self.compute_api.attach_volume(context, instance_id, volume_id, device)
+ self.compute_api.attach_volume(context,
+ instance_id=instance_id,
+ volume_id=volume_id,
+ device=device)
volume = self.volume_api.get(context, volume_id)
return {'attachTime': volume['attach_time'],
'device': volume['mountpoint'],
@@ -612,7 +619,7 @@ class CloudController(object):
volume_id = ec2_id_to_id(volume_id)
LOG.audit(_("Detach volume %s"), volume_id, context=context)
volume = self.volume_api.get(context, volume_id)
- instance = self.compute_api.detach_volume(context, volume_id)
+ instance = self.compute_api.detach_volume(context, volume_id=volume_id)
return {'attachTime': volume['attach_time'],
'device': volume['mountpoint'],
'instanceId': id_to_ec2_id(instance['id']),
@@ -643,6 +650,10 @@ class CloudController(object):
return i[0]
def _format_instances(self, context, instance_id=None, **kwargs):
+ # TODO(termie): this method is poorly named as its name does not imply
+ # that it will be making a variety of database calls
+ # rather than simply formatting a bunch of instances that
+ # were handed to it
reservations = {}
# NOTE(vish): instance_id is an optional list of ids to filter by
if instance_id:
@@ -743,7 +754,9 @@ class CloudController(object):
LOG.audit(_("Associate address %s to instance %s"), public_ip,
instance_id, context=context)
instance_id = ec2_id_to_id(instance_id)
- self.compute_api.associate_floating_ip(context, instance_id, public_ip)
+ self.compute_api.associate_floating_ip(context,
+ instance_id=instance_id,
+ address=public_ip)
return {'associateResponse': ["Address associated."]}
def disassociate_address(self, context, public_ip, **kwargs):
@@ -754,8 +767,9 @@ class CloudController(object):
def run_instances(self, context, **kwargs):
max_count = int(kwargs.get('max_count', 1))
instances = self.compute_api.create(context,
- instance_types.get_by_type(kwargs.get('instance_type', None)),
- kwargs['image_id'],
+ instance_type=instance_types.get_by_type(
+ kwargs.get('instance_type', None)),
+ image_id=kwargs['image_id'],
min_count=int(kwargs.get('min_count', max_count)),
max_count=max_count,
kernel_id=kwargs.get('kernel_id', None),
@@ -766,8 +780,7 @@ class CloudController(object):
user_data=kwargs.get('user_data'),
security_group=kwargs.get('security_group'),
availability_zone=kwargs.get('placement', {}).get(
- 'AvailabilityZone'),
- generate_hostname=id_to_ec2_id)
+ 'AvailabilityZone'))
return self._format_run_instances(context,
instances[0]['reservation_id'])
@@ -777,7 +790,7 @@ class CloudController(object):
LOG.debug(_("Going to start terminating instances"))
for ec2_id in instance_id:
instance_id = ec2_id_to_id(ec2_id)
- self.compute_api.delete(context, instance_id)
+ self.compute_api.delete(context, instance_id=instance_id)
return True
def reboot_instances(self, context, instance_id, **kwargs):
@@ -785,19 +798,19 @@ class CloudController(object):
LOG.audit(_("Reboot instance %r"), instance_id, context=context)
for ec2_id in instance_id:
instance_id = ec2_id_to_id(ec2_id)
- self.compute_api.reboot(context, instance_id)
+ self.compute_api.reboot(context, instance_id=instance_id)
return True
def rescue_instance(self, context, instance_id, **kwargs):
"""This is an extension to the normal ec2_api"""
instance_id = ec2_id_to_id(instance_id)
- self.compute_api.rescue(context, instance_id)
+ self.compute_api.rescue(context, instance_id=instance_id)
return True
def unrescue_instance(self, context, instance_id, **kwargs):
"""This is an extension to the normal ec2_api"""
instance_id = ec2_id_to_id(instance_id)
- self.compute_api.unrescue(context, instance_id)
+ self.compute_api.unrescue(context, instance_id=instance_id)
return True
def update_instance(self, context, ec2_id, **kwargs):
@@ -808,7 +821,7 @@ class CloudController(object):
changes[field] = kwargs[field]
if changes:
instance_id = ec2_id_to_id(ec2_id)
- self.compute_api.update(context, instance_id, **kwargs)
+ self.compute_api.update(context, instance_id=instance_id, **kwargs)
return True
def describe_images(self, context, image_id=None, **kwargs):