From 4ff2da231d485598232d9aacc41538950005ac34 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Wed, 22 Dec 2010 16:43:47 -0800 Subject: Basic Easy API functionality --- nova/api/easy.py | 163 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 nova/api/easy.py (limited to 'nova/api') diff --git a/nova/api/easy.py b/nova/api/easy.py new file mode 100644 index 000000000..a284e9685 --- /dev/null +++ b/nova/api/easy.py @@ -0,0 +1,163 @@ +# 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_body 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 json +import urllib + +import routes +import webob + +from nova import context +from nova import flags +from nova import wsgi + +# prxy compute_api in amazon tests + + +EASY_ROUTES = {} + + +def register_service(path, handle): + EASY_ROUTES[path] = handle + + +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 = json.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 ReqParamsMiddleware(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 SundayMorning(wsgi.Router): + def __init__(self, mapper=None): + if mapper is None: + mapper = routes.Mapper() + + self._load_registered_routes(mapper) + super(SundayMorning, self).__init__(mapper=mapper) + + def _load_registered_routes(self, mapper): + for route in EASY_ROUTES: + mapper.connect('/%s/{action}' % route, + controller=ServiceWrapper(EASY_ROUTES[route])) + + +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: + return self._serialize(result, req) + else: + return result + + +class Proxy(object): + """Pretend an Easy 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': json.dumps(kwargs)}) + req.environ['openstack.context'] = context + resp = req.get_response(self.app) + try: + return json.loads(resp.body) + except Exception: + return resp.body + + def __getattr__(self, key): + if self.prefix is None: + return self.__class__(self.app, key) + + def _wrapper(context, **kwargs): + return self.__do_request('/%s/%s' % (self.prefix, key), + context, + **kwargs) + _wrapper.func_name = key + return _wrapper + + + -- cgit From 70d254c626e925f6de8408f0ca70f3de28a7307a Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Wed, 22 Dec 2010 17:53:42 -0800 Subject: added tests to ensure the easy api works as a backend for Compute API --- nova/api/easy.py | 10 +++++----- nova/api/ec2/cloud.py | 34 ++++++++++++++++++++-------------- 2 files changed, 25 insertions(+), 19 deletions(-) (limited to 'nova/api') diff --git a/nova/api/easy.py b/nova/api/easy.py index a284e9685..9ef487739 100644 --- a/nova/api/easy.py +++ b/nova/api/easy.py @@ -31,7 +31,6 @@ The general flow of a request is: """ -import json import urllib import routes @@ -39,6 +38,7 @@ import webob from nova import context from nova import flags +from nova import utils from nova import wsgi # prxy compute_api in amazon tests @@ -65,7 +65,7 @@ class JsonParamsMiddleware(wsgi.Middleware): return params_json = request.params['json'] - params_parsed = json.loads(params_json) + params_parsed = utils.loads(params_json) params = {} for k, v in params_parsed.iteritems(): if k in ('self', 'context'): @@ -125,7 +125,7 @@ class ServiceWrapper(wsgi.Controller): method = getattr(self.service_handle, action) result = method(context, **params) - if type(result) is dict: + if type(result) is dict or type(result) is list: return self._serialize(result, req) else: return result @@ -140,11 +140,11 @@ class Proxy(object): def __do_request(self, path, context, **kwargs): req = webob.Request.blank(path) req.method = 'POST' - req.body = urllib.urlencode({'json': json.dumps(kwargs)}) + req.body = urllib.urlencode({'json': utils.dumps(kwargs)}) req.environ['openstack.context'] = context resp = req.get_response(self.app) try: - return json.loads(resp.body) + return utils.loads(resp.body) except Exception: return resp.body diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index e09261f00..61c6c0da1 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -118,7 +118,8 @@ class CloudController(object): def _get_mpi_data(self, context, project_id): result = {} - for instance in self.compute_api.get_instances(context, project_id): + for instance in self.compute_api.get_instances(context, + project_id=project_id): if instance['fixed_ip']: line = '%s slots=%d' % (instance['fixed_ip']['address'], instance['vcpus']) @@ -442,7 +443,8 @@ class CloudController(object): # instance_id is passed in as a list of instances ec2_id = instance_id[0] internal_id = ec2_id_to_internal_id(ec2_id) - instance_ref = self.compute_api.get_instance(context, internal_id) + instance_ref = self.compute_api.get_instance(context, + instance_id=internal_id) output = rpc.call(context, '%s.%s' % (FLAGS.compute_topic, instance_ref['host']), @@ -541,7 +543,8 @@ class CloudController(object): if volume_ref['attach_status'] == "attached": raise exception.ApiError(_("Volume is already attached")) internal_id = ec2_id_to_internal_id(instance_id) - instance_ref = self.compute_api.get_instance(context, internal_id) + instance_ref = self.compute_api.get_instance(context, + instance_id=internal_id) host = instance_ref['host'] rpc.cast(context, db.queue_get_for(context, FLAGS.compute_topic, host), @@ -722,14 +725,15 @@ class CloudController(object): def associate_address(self, context, instance_id, public_ip, **kwargs): internal_id = ec2_id_to_internal_id(instance_id) - instance_ref = self.compute_api.get_instance(context, internal_id) + instance_ref = self.compute_api.get_instance(context, + instance_id=internal_id) fixed_address = db.instance_get_fixed_address(context, instance_ref['id']) floating_ip_ref = db.floating_ip_get_by_address(context, public_ip) # NOTE(vish): Perhaps we should just pass this on to compute and # let compute communicate with network. - network_topic = self.compute_api.get_network_topic(context, - internal_id) + network_topic = self.compute_api.get_network_topic( + context, instance_id=internal_id) rpc.cast(context, network_topic, {"method": "associate_floating_ip", @@ -754,8 +758,9 @@ class CloudController(object): def run_instances(self, context, **kwargs): max_count = int(kwargs.get('max_count', 1)) instances = self.compute_api.create_instances(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), @@ -765,7 +770,7 @@ class CloudController(object): key_name=kwargs.get('key_name'), user_data=kwargs.get('user_data'), security_group=kwargs.get('security_group'), - generate_hostname=internal_id_to_ec2_id) + hostname_format='ec2') return self._format_run_instances(context, instances[0]['reservation_id']) @@ -775,26 +780,26 @@ class CloudController(object): logging.debug("Going to start terminating instances") for ec2_id in instance_id: internal_id = ec2_id_to_internal_id(ec2_id) - self.compute_api.delete_instance(context, internal_id) + self.compute_api.delete_instance(context, instance_id=internal_id) return True def reboot_instances(self, context, instance_id, **kwargs): """instance_id is a list of instance ids""" for ec2_id in instance_id: internal_id = ec2_id_to_internal_id(ec2_id) - self.compute_api.reboot(context, internal_id) + self.compute_api.reboot(context, instance_id=internal_id) return True def rescue_instance(self, context, instance_id, **kwargs): """This is an extension to the normal ec2_api""" internal_id = ec2_id_to_internal_id(instance_id) - self.compute_api.rescue(context, internal_id) + self.compute_api.rescue(context, instance_id=internal_id) return True def unrescue_instance(self, context, instance_id, **kwargs): """This is an extension to the normal ec2_api""" internal_id = ec2_id_to_internal_id(instance_id) - self.compute_api.unrescue(context, internal_id) + self.compute_api.unrescue(context, instance_id=internal_id) return True def update_instance(self, context, ec2_id, **kwargs): @@ -805,7 +810,8 @@ class CloudController(object): changes[field] = kwargs[field] if changes: internal_id = ec2_id_to_internal_id(ec2_id) - inst = self.compute_api.get_instance(context, internal_id) + inst = self.compute_api.get_instance(context, + instance_id=internal_id) db.instance_update(context, inst['id'], kwargs) return True -- cgit From 7c1b3ef521c652ce375390a1ecb04a60d1f100f0 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Wed, 22 Dec 2010 18:04:35 -0800 Subject: remove some notes --- nova/api/easy.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/easy.py b/nova/api/easy.py index 9ef487739..1be52069f 100644 --- a/nova/api/easy.py +++ b/nova/api/easy.py @@ -41,8 +41,6 @@ from nova import flags from nova import utils from nova import wsgi -# prxy compute_api in amazon tests - EASY_ROUTES = {} -- cgit From a1b5220879632d093f450413f96668a8f77c0613 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Thu, 23 Dec 2010 12:04:19 -0800 Subject: adds a reflection api --- nova/api/easy.py | 52 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 4 deletions(-) (limited to 'nova/api') diff --git a/nova/api/easy.py b/nova/api/easy.py index 1be52069f..0e4f8a892 100644 --- a/nova/api/easy.py +++ b/nova/api/easy.py @@ -31,6 +31,7 @@ The general flow of a request is: """ +import inspect import urllib import routes @@ -102,6 +103,52 @@ class SundayMorning(wsgi.Router): mapper.connect('/%s/{action}' % route, controller=ServiceWrapper(EASY_ROUTES[route])) + +class Reflection(object): + def __init__(self): + self._methods = {} + + def _gather_methods(self): + methods = {} + for route, handler in EASY_ROUTES.iteritems(): + 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), defaults_r.pop(0))) + else: + args_out.append(str(args_r.pop(0))) + + methods['/%s/%s' % (route, k)] = { + 'name': k, + 'args': list(reversed(args_out))} + return methods + + def get_methods(self, context): + if not self._methods: + self._methods = self._gather_methods() + + method_list = self._methods.keys() + method_list.sort() + return {'methods': method_list} + + def get_method_info(self, context, method): + if not self._methods: + self._methods = self._gather_methods() + return self._methods[method] + class ServiceWrapper(wsgi.Controller): def __init__(self, service_handle): @@ -148,7 +195,7 @@ class Proxy(object): def __getattr__(self, key): if self.prefix is None: - return self.__class__(self.app, key) + return self.__class__(self.app, prefix=key) def _wrapper(context, **kwargs): return self.__do_request('/%s/%s' % (self.prefix, key), @@ -156,6 +203,3 @@ class Proxy(object): **kwargs) _wrapper.func_name = key return _wrapper - - - -- cgit From 8e1b74aa1c5a2f9113473eedc8e35b38b41445ea Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Mon, 27 Dec 2010 15:15:24 -0800 Subject: Added stack command-line tool --- nova/api/easy.py | 57 +++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 15 deletions(-) (limited to 'nova/api') diff --git a/nova/api/easy.py b/nova/api/easy.py index 0e4f8a892..7468e3115 100644 --- a/nova/api/easy.py +++ b/nova/api/easy.py @@ -25,7 +25,7 @@ The general flow of a request is: (/controller/method) - Parameters are parsed from the request and passed to a method on the controller as keyword arguments. - - Optionally json_body is decoded to provide all the parameters. + - 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. @@ -94,7 +94,7 @@ class SundayMorning(wsgi.Router): def __init__(self, mapper=None): if mapper is None: mapper = routes.Mapper() - + self._load_registered_routes(mapper) super(SundayMorning, self).__init__(mapper=mapper) @@ -103,14 +103,18 @@ class SundayMorning(wsgi.Router): mapper.connect('/%s/{action}' % route, controller=ServiceWrapper(EASY_ROUTES[route])) - + 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 EASY_ROUTES.iteritems(): + controllers[route] = handler.__doc__.split('\n')[0] for k in dir(handler): if k.startswith('_'): continue @@ -120,40 +124,63 @@ class Reflection(object): # bunch of ugly formatting stuff argspec = inspect.getargspec(f) - args = [x for x in argspec[0] if x != 'self' and x != 'context'] + 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), defaults_r.pop(0))) + args_out.append((args_r.pop(0), + repr(defaults_r.pop(0)))) else: - args_out.append(str(args_r.pop(0))) + 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))} - return methods + + 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._methods = self._gather_methods() + self._gather_methods() method_list = self._methods.keys() method_list.sort() - return {'methods': method_list} + 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._methods = self._gather_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] @@ -165,10 +192,10 @@ class ServiceWrapper(wsgi.Controller): 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) @@ -181,7 +208,7 @@ class Proxy(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' @@ -196,7 +223,7 @@ class Proxy(object): 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, -- cgit From 35d3050511ef513ff440fbd9f8b44695ea8be797 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Tue, 4 Jan 2011 14:07:46 -0800 Subject: rename Easy API to Direct API --- nova/api/direct.py | 232 +++++++++++++++++++++++++++++++++++++++++++++++++++++ nova/api/easy.py | 232 ----------------------------------------------------- 2 files changed, 232 insertions(+), 232 deletions(-) create mode 100644 nova/api/direct.py delete mode 100644 nova/api/easy.py (limited to 'nova/api') 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/easy.py b/nova/api/easy.py deleted file mode 100644 index 7468e3115..000000000 --- a/nova/api/easy.py +++ /dev/null @@ -1,232 +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 - -from nova import context -from nova import flags -from nova import utils -from nova import wsgi - - -EASY_ROUTES = {} - - -def register_service(path, handle): - EASY_ROUTES[path] = handle - - -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 ReqParamsMiddleware(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 SundayMorning(wsgi.Router): - def __init__(self, mapper=None): - if mapper is None: - mapper = routes.Mapper() - - self._load_registered_routes(mapper) - super(SundayMorning, self).__init__(mapper=mapper) - - def _load_registered_routes(self, mapper): - for route in EASY_ROUTES: - mapper.connect('/%s/{action}' % route, - controller=ServiceWrapper(EASY_ROUTES[route])) - - -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 EASY_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 an Easy 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 -- cgit From f55dbc2f599ed56fb59c7f7a94cd81d3fd82c8dd Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 4 Jan 2011 18:01:29 -0500 Subject: Rework how routing is done in ec2 endpoint. --- nova/api/ec2/__init__.py | 43 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 8 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index aa3bfaeb4..a5810479e 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -29,6 +29,7 @@ import webob.exc from nova import context from nova import exception from nova import flags +from nova import utils from nova import wsgi from nova.api.ec2 import apirequest from nova.api.ec2 import admin @@ -157,6 +158,31 @@ class Authenticate(wsgi.Middleware): return self.application +class Requestify(wsgi.Middleware): + + def __init__(self, app, controller_name): + super(Requestify, self).__init__(app) + self.controller = utils.import_class(controller_name)() + + @webob.dec.wsgify + def __call__(self, req): + non_args = ['Action', 'Signature', 'AWSAccessKeyId', 'SignatureMethod', + 'SignatureVersion', 'Version', 'Timestamp'] + args = dict(req.params) + try: + # Raise KeyError if omitted + action = req.params['Action'] + for non_arg in non_args: + # Remove, but raise KeyError if omitted + args.pop(non_arg) + except: + raise webob.exc.HTTPBadRequest() + api_request = apirequest.APIRequest(self.controller, action) + req.environ['ec2.request'] = api_request + req.environ['ec2.action_args'] = args + return self.application + + class Router(wsgi.Middleware): """Add ec2.'controller', .'action', and .'action_args' to WSGI environ.""" @@ -256,10 +282,9 @@ class Authorizer(wsgi.Middleware): @webob.dec.wsgify def __call__(self, req): context = req.environ['ec2.context'] - controller_name = req.environ['ec2.controller'].__class__.__name__ - action = req.environ['ec2.action'] - allowed_roles = self.action_roles[controller_name].get(action, - ['none']) + controller = req.environ['ec2.request'].controller.__class__.__name__ + action = req.environ['ec2.request'].action + allowed_roles = self.action_roles[controller].get(action, ['none']) if self._matches_any_role(context, allowed_roles): return self.application else: @@ -289,11 +314,8 @@ class Executor(wsgi.Application): @webob.dec.wsgify def __call__(self, req): context = req.environ['ec2.context'] - controller = req.environ['ec2.controller'] - action = req.environ['ec2.action'] args = req.environ['ec2.action_args'] - - api_request = apirequest.APIRequest(controller, action) + api_request = req.environ['ec2.request'] result = None try: result = api_request.send(context, **args) @@ -369,3 +391,8 @@ def executor_factory(global_args, **local_args): def versions_factory(global_args, **local_args): return Versions() + +def requestify_factory(global_args, **local_args): + def requestifier(app): + return Requestify(app, local_args['controller']) + return requestifier -- cgit From 2491c2484f025cb3f061fcc6a5c6915006feb47b Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 4 Jan 2011 18:16:16 -0500 Subject: Make paste the default api pattern. * get rid of the --use_lockout flag since it will be specified in paste config (Example line is commented out in etc/nova-api.conf, factory is in place) * remove old nova-api binary and promote nova-api-paste * change how we store ec2 parameters to bin the the ApiRequest * get rid of Router, since paste.urlmap is equally effective (Requestify now gets passed the name of the controller requests are to.) --- nova/api/ec2/__init__.py | 74 +++++----------------------------------------- nova/api/ec2/apirequest.py | 7 +++-- 2 files changed, 11 insertions(+), 70 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index a5810479e..2f8f4f272 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -21,7 +21,6 @@ Starting point for routing EC2 requests. """ import logging -import routes import webob import webob.dec import webob.exc @@ -32,8 +31,6 @@ from nova import flags from nova import utils from nova import wsgi from nova.api.ec2 import apirequest -from nova.api.ec2 import admin -from nova.api.ec2 import cloud from nova.auth import manager @@ -41,8 +38,6 @@ FLAGS = flags.FLAGS flags.DEFINE_boolean('use_forwarded_for', False, 'Treat X-Forwarded-For as the canonical remote address. ' 'Only enable this if you have a sanitizing proxy.') -flags.DEFINE_boolean('use_lockout', False, - 'Whether or not to use lockout middleware.') flags.DEFINE_integer('lockout_attempts', 5, 'Number of failed auths before lockout.') flags.DEFINE_integer('lockout_minutes', 15, @@ -57,15 +52,6 @@ _log = logging.getLogger("api") _log.setLevel(logging.DEBUG) -class API(wsgi.Middleware): - """Routing for all EC2 API requests.""" - - def __init__(self): - self.application = Authenticate(Router(Authorizer(Executor()))) - if FLAGS.use_lockout: - self.application = Lockout(self.application) - - class Lockout(wsgi.Middleware): """Lockout for x minutes on y failed auths in a z minute period. @@ -177,55 +163,12 @@ class Requestify(wsgi.Middleware): args.pop(non_arg) except: raise webob.exc.HTTPBadRequest() - api_request = apirequest.APIRequest(self.controller, action) + api_request = apirequest.APIRequest(self.controller, action, args) req.environ['ec2.request'] = api_request req.environ['ec2.action_args'] = args return self.application -class Router(wsgi.Middleware): - - """Add ec2.'controller', .'action', and .'action_args' to WSGI environ.""" - - def __init__(self, application): - super(Router, self).__init__(application) - self.map = routes.Mapper() - self.map.connect("/{controller_name}/") - self.controllers = dict(Cloud=cloud.CloudController(), - Admin=admin.AdminController()) - - @webob.dec.wsgify - def __call__(self, req): - # Obtain the appropriate controller and action for this request. - try: - match = self.map.match(req.path_info) - controller_name = match['controller_name'] - controller = self.controllers[controller_name] - except: - raise webob.exc.HTTPNotFound() - non_args = ['Action', 'Signature', 'AWSAccessKeyId', 'SignatureMethod', - 'SignatureVersion', 'Version', 'Timestamp'] - args = dict(req.params) - try: - # Raise KeyError if omitted - action = req.params['Action'] - for non_arg in non_args: - # Remove, but raise KeyError if omitted - args.pop(non_arg) - except: - raise webob.exc.HTTPBadRequest() - - _log.debug(_('action: %s') % action) - for key, value in args.items(): - _log.debug(_('arg: %s\t\tval: %s') % (key, value)) - - # Success! - req.environ['ec2.controller'] = controller - req.environ['ec2.action'] = action - req.environ['ec2.action_args'] = args - return self.application - - class Authorizer(wsgi.Middleware): """Authorize an EC2 API request. @@ -314,13 +257,11 @@ class Executor(wsgi.Application): @webob.dec.wsgify def __call__(self, req): context = req.environ['ec2.context'] - args = req.environ['ec2.action_args'] api_request = req.environ['ec2.request'] result = None try: - result = api_request.send(context, **args) + result = api_request.invoke(context) except exception.ApiError as ex: - if ex.code: return self._error(req, ex.code, ex.message) else: @@ -373,12 +314,6 @@ def authenticate_factory(global_args, **local_args): return authenticator -def router_factory(global_args, **local_args): - def router(app): - return Router(app) - return router - - def authorizer_factory(global_args, **local_args): def authorizer(app): return Authorizer(app) @@ -396,3 +331,8 @@ def requestify_factory(global_args, **local_args): def requestifier(app): return Requestify(app, local_args['controller']) return requestifier + +def lockout_factory(global_args, **local_args): + def locksmith(app): + return Lockout(app) + return locksmith diff --git a/nova/api/ec2/apirequest.py b/nova/api/ec2/apirequest.py index a90fbeb0c..8a1dd3978 100644 --- a/nova/api/ec2/apirequest.py +++ b/nova/api/ec2/apirequest.py @@ -83,11 +83,12 @@ def _try_convert(value): class APIRequest(object): - def __init__(self, controller, action): + def __init__(self, controller, action, args): self.controller = controller self.action = action + self.args = args - def send(self, context, **kwargs): + def invoke(self, context): try: method = getattr(self.controller, _camelcase_to_underscore(self.action)) @@ -100,7 +101,7 @@ class APIRequest(object): raise Exception(_error) args = {} - for key, value in kwargs.items(): + for key, value in self.args.items(): parts = key.split(".") key = _camelcase_to_underscore(parts[0]) if isinstance(value, str) or isinstance(value, unicode): -- cgit From 6dc2e665b5b6f690882e6029984a11dc7063b437 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 4 Jan 2011 18:33:17 -0500 Subject: remove unused nova/api/__init__.py --- nova/api/__init__.py | 111 --------------------------------------------------- 1 file changed, 111 deletions(-) delete mode 100644 nova/api/__init__.py (limited to 'nova/api') diff --git a/nova/api/__init__.py b/nova/api/__init__.py deleted file mode 100644 index 26fed847b..000000000 --- a/nova/api/__init__.py +++ /dev/null @@ -1,111 +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. -""" -Root WSGI middleware for all API controllers. - -**Related Flags** - -:osapi_subdomain: subdomain running the OpenStack API (default: api) -:ec2api_subdomain: subdomain running the EC2 API (default: ec2) - -""" -import logging - -import routes -import webob.dec - -from nova import flags -from nova import wsgi -from nova.api import ec2 -from nova.api import openstack -from nova.api.ec2 import metadatarequesthandler - - -flags.DEFINE_string('osapi_subdomain', 'api', - 'subdomain running the OpenStack API') -flags.DEFINE_string('ec2api_subdomain', 'ec2', - 'subdomain running the EC2 API') - -FLAGS = flags.FLAGS - - -class API(wsgi.Router): - """Routes top-level requests to the appropriate controller.""" - - def __init__(self, default_api): - osapi_subdomain = {'sub_domain': [FLAGS.osapi_subdomain]} - ec2api_subdomain = {'sub_domain': [FLAGS.ec2api_subdomain]} - if default_api == 'os': - osapi_subdomain = {} - elif default_api == 'ec2': - ec2api_subdomain = {} - mapper = routes.Mapper() - mapper.sub_domains = True - - mapper.connect("/", controller=self.osapi_versions, - conditions=osapi_subdomain) - mapper.connect("/v1.0/{path_info:.*}", controller=openstack.API(), - conditions=osapi_subdomain) - - mapper.connect("/", controller=self.ec2api_versions, - conditions=ec2api_subdomain) - mapper.connect("/services/{path_info:.*}", controller=ec2.API(), - conditions=ec2api_subdomain) - mrh = metadatarequesthandler.MetadataRequestHandler() - for s in ['/latest', - '/2009-04-04', - '/2008-09-01', - '/2008-02-01', - '/2007-12-15', - '/2007-10-10', - '/2007-08-29', - '/2007-03-01', - '/2007-01-19', - '/1.0']: - mapper.connect('%s/{path_info:.*}' % s, controller=mrh, - conditions=ec2api_subdomain) - - super(API, self).__init__(mapper) - - @webob.dec.wsgify - def osapi_versions(self, req): - """Respond to a request for all OpenStack API versions.""" - response = { - "versions": [ - dict(status="CURRENT", id="v1.0")]} - metadata = { - "application/xml": { - "attributes": dict(version=["status", "id"])}} - return wsgi.Serializer(req.environ, metadata).to_content_type(response) - - @webob.dec.wsgify - def ec2api_versions(self, req): - """Respond to a request for all EC2 versions.""" - # available api versions - versions = [ - '1.0', - '2007-01-19', - '2007-03-01', - '2007-08-29', - '2007-10-10', - '2007-12-15', - '2008-02-01', - '2008-09-01', - '2009-04-04', - ] - return ''.join('%s\n' % v for v in versions) -- cgit From 406c8cdf027b13636ab3c8fa609aabe929057d6f Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 4 Jan 2011 18:46:12 -0500 Subject: Remove flags and unused API class from openstack api, since such things are specified in paste config now. --- nova/api/openstack/__init__.py | 29 ----------------------------- 1 file changed, 29 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index a1430caed..c5de03a09 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -43,40 +43,11 @@ from nova.api.openstack import sharedipgroups FLAGS = flags.FLAGS -flags.DEFINE_string('os_api_auth', - 'nova.api.openstack.auth.AuthMiddleware', - 'The auth mechanism to use for the OpenStack API implemenation') - -flags.DEFINE_string('os_api_ratelimiting', - 'nova.api.openstack.ratelimiting.RateLimitingMiddleware', - 'Default ratelimiting implementation for the Openstack API') - flags.DEFINE_bool('allow_admin_api', False, 'When True, this API service will accept admin operations.') -class API(wsgi.Middleware): - """WSGI entry point for all OpenStack API requests.""" - - def __init__(self): - auth_middleware = utils.import_class(FLAGS.os_api_auth) - ratelimiting_middleware = \ - utils.import_class(FLAGS.os_api_ratelimiting) - app = auth_middleware(ratelimiting_middleware(APIRouter())) - super(API, self).__init__(app) - - @webob.dec.wsgify - def __call__(self, req): - try: - return req.get_response(self.application) - except Exception as ex: - logging.warn(_("Caught error: %s") % str(ex)) - logging.error(traceback.format_exc()) - exc = webob.exc.HTTPInternalServerError(explanation=str(ex)) - return faults.Fault(exc) - - class APIRouter(wsgi.Router): """ Routes requests on the OpenStack API to the appropriate controller -- cgit From 8926f33d4da9def15dde68a5a15fd9477aee6452 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 4 Jan 2011 19:02:56 -0500 Subject: pep8 fixes. --- nova/api/ec2/__init__.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'nova/api') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 2f8f4f272..dfa919e07 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -327,11 +327,13 @@ def executor_factory(global_args, **local_args): def versions_factory(global_args, **local_args): return Versions() + def requestify_factory(global_args, **local_args): def requestifier(app): return Requestify(app, local_args['controller']) return requestifier + def lockout_factory(global_args, **local_args): def locksmith(app): return Lockout(app) -- cgit From bccec6c8bac90517a972a5eb8bb91a82b3a13065 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 6 Jan 2011 02:08:01 -0500 Subject: Fix openstack api tests and add a FaultWrapper to turn exceptions to faults. --- nova/api/openstack/__init__.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index c5de03a09..21b9b1d7d 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -20,8 +20,6 @@ WSGI middleware for OpenStack API controllers. """ -import time - import logging import routes import traceback @@ -29,15 +27,12 @@ import webob.dec import webob.exc import webob -from nova import context from nova import flags -from nova import utils from nova import wsgi from nova.api.openstack import faults from nova.api.openstack import backup_schedules from nova.api.openstack import flavors from nova.api.openstack import images -from nova.api.openstack import ratelimiting from nova.api.openstack import servers from nova.api.openstack import sharedipgroups @@ -48,6 +43,19 @@ flags.DEFINE_bool('allow_admin_api', 'When True, this API service will accept admin operations.') +class FaultWrapper(wsgi.Middleware): + """Calls down the middleware stack, making exceptions into faults.""" + + @webob.dec.wsgify + def __call__(self, req): + try: + return req.get_response(self.application) + except Exception as ex: + logging.warn(_("Caught error: %s") % str(ex)) + logging.error(traceback.format_exc()) + exc = webob.exc.HTTPInternalServerError(explanation=str(ex)) + return faults.Fault(exc) + class APIRouter(wsgi.Router): """ Routes requests on the OpenStack API to the appropriate controller @@ -105,3 +113,9 @@ def router_factory(global_cof, **local_conf): def versions_factory(global_conf, **local_conf): return Versions() + + +def fault_wrapper_factory(global_conf, **local_conf): + def fwrap(app): + return FaultWrapper(app) + return fwrap -- cgit From 787631f7b3d882b6743ed52dc948301fdbca471e Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 6 Jan 2011 13:17:28 -0500 Subject: Add blank __init__ file for fixing importability. The stale .pyc masked this error locally. --- nova/api/__init__.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 nova/api/__init__.py (limited to 'nova/api') diff --git a/nova/api/__init__.py b/nova/api/__init__.py new file mode 100644 index 000000000..0fedbbfad --- /dev/null +++ b/nova/api/__init__.py @@ -0,0 +1,19 @@ +# 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. + +"""No-op __init__ for directory full of api goodies.""" -- cgit From 4e034f3d69c6aba6920dd7dd38e07aeb495b45db Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 6 Jan 2011 13:57:48 -0500 Subject: Remove module-level factory methods in favor of having a factory class-method on wsgi components themselves. Local options from config are passed to the __init__ method of the component as kwargs. --- nova/api/ec2/__init__.py | 36 ++--------------------------- nova/api/ec2/metadatarequesthandler.py | 7 ++---- nova/api/openstack/__init__.py | 19 ++++----------- nova/api/openstack/auth.py | 6 ----- nova/api/openstack/ratelimiting/__init__.py | 6 ----- 5 files changed, 9 insertions(+), 65 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index dfa919e07..0836c3411 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -146,9 +146,9 @@ class Authenticate(wsgi.Middleware): class Requestify(wsgi.Middleware): - def __init__(self, app, controller_name): + def __init__(self, app, controller): super(Requestify, self).__init__(app) - self.controller = utils.import_class(controller_name)() + self.controller = utils.import_class(controller)() @webob.dec.wsgify def __call__(self, req): @@ -306,35 +306,3 @@ class Versions(wsgi.Application): '2009-04-04', ] return ''.join('%s\n' % v for v in versions) - - -def authenticate_factory(global_args, **local_args): - def authenticator(app): - return Authenticate(app) - return authenticator - - -def authorizer_factory(global_args, **local_args): - def authorizer(app): - return Authorizer(app) - return authorizer - - -def executor_factory(global_args, **local_args): - return Executor() - - -def versions_factory(global_args, **local_args): - return Versions() - - -def requestify_factory(global_args, **local_args): - def requestifier(app): - return Requestify(app, local_args['controller']) - return requestifier - - -def lockout_factory(global_args, **local_args): - def locksmith(app): - return Lockout(app) - return locksmith diff --git a/nova/api/ec2/metadatarequesthandler.py b/nova/api/ec2/metadatarequesthandler.py index a57a6698a..9067568a4 100644 --- a/nova/api/ec2/metadatarequesthandler.py +++ b/nova/api/ec2/metadatarequesthandler.py @@ -24,13 +24,14 @@ import webob.dec import webob.exc from nova import flags +from nova import wsgi from nova.api.ec2 import cloud FLAGS = flags.FLAGS -class MetadataRequestHandler(object): +class MetadataRequestHandler(wsgi.Application): """Serve metadata from the EC2 API.""" def print_data(self, data): @@ -79,7 +80,3 @@ class MetadataRequestHandler(object): if data is None: raise webob.exc.HTTPNotFound() return self.print_data(data) - - -def metadata_factory(global_args, **local_args): - return MetadataRequestHandler() diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 21b9b1d7d..76e3aeed9 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -62,6 +62,11 @@ class APIRouter(wsgi.Router): and method. """ + @classmethod + def factory(cls, global_config, **local_config): + """Simple paste factory, :class:`nova.wsgi.Router` doesn't have one""" + return cls() + def __init__(self): mapper = routes.Mapper() @@ -105,17 +110,3 @@ class Versions(wsgi.Application): "application/xml": { "attributes": dict(version=["status", "id"])}} return wsgi.Serializer(req.environ, metadata).to_content_type(response) - - -def router_factory(global_cof, **local_conf): - return APIRouter() - - -def versions_factory(global_conf, **local_conf): - return Versions() - - -def fault_wrapper_factory(global_conf, **local_conf): - def fwrap(app): - return FaultWrapper(app) - return fwrap diff --git a/nova/api/openstack/auth.py b/nova/api/openstack/auth.py index 00e817c8d..1dfdd5318 100644 --- a/nova/api/openstack/auth.py +++ b/nova/api/openstack/auth.py @@ -134,9 +134,3 @@ class AuthMiddleware(wsgi.Middleware): token = self.db.auth_create_token(ctxt, token_dict) return token, user return None, None - - -def auth_factory(global_conf, **local_conf): - def auth(app): - return AuthMiddleware(app) - return auth diff --git a/nova/api/openstack/ratelimiting/__init__.py b/nova/api/openstack/ratelimiting/__init__.py index 81b83142f..cbb4b897e 100644 --- a/nova/api/openstack/ratelimiting/__init__.py +++ b/nova/api/openstack/ratelimiting/__init__.py @@ -219,9 +219,3 @@ class WSGIAppProxy(object): # No delay return None return float(resp.getheader('X-Wait-Seconds')) - - -def ratelimit_factory(global_conf, **local_conf): - def rl(app): - return RateLimitingMiddleware(app) - return rl -- cgit From 8003dd2f5b027491f4e171f92ccd2a1cf2946315 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Thu, 6 Jan 2011 14:22:11 -0500 Subject: Pep8 --- nova/api/openstack/__init__.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova/api') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 21b9b1d7d..452290505 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -56,6 +56,7 @@ class FaultWrapper(wsgi.Middleware): exc = webob.exc.HTTPInternalServerError(explanation=str(ex)) return faults.Fault(exc) + class APIRouter(wsgi.Router): """ Routes requests on the OpenStack API to the appropriate controller -- cgit From 3bf9bc6f6c0fbf90e3f4eab68a9bd99d85fcc422 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Thu, 6 Jan 2011 21:37:33 -0600 Subject: Reserving image before uploading --- nova/api/openstack/images.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 867ee5a7e..4d1af77d9 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -115,7 +115,8 @@ class Controller(wsgi.Controller): items = self._service.index(req.environ['nova.context']) items = common.limited(items, req) items = [_translate_keys(item) for item in items] - items = [_translate_status(item) for item in items] + #TODO(sirp): removing for glance + #items = [_translate_status(item) for item in items] return dict(images=items) def show(self, req, id): @@ -131,7 +132,12 @@ class Controller(wsgi.Controller): env = self._deserialize(req.body, req) instance_id = env["image"]["serverId"] name = env["image"]["name"] - return compute_api.ComputeAPI().snapshot(context, instance_id, name) + + image_meta = compute_api.ComputeAPI().snapshot( + context, instance_id, name) + + #TODO(sirp): need to map Glance attrs to OpenStackAPI attrs + return dict(image=image_meta) def update(self, req, id): # Users may not modify public images, and that's all that -- cgit From 8de96296dfb22d0e6c491fcaf072210dfbaa67e8 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Thu, 6 Jan 2011 23:38:01 -0600 Subject: Removing some FIXMEs --- nova/api/openstack/images.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 4d1af77d9..a826b8435 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -76,7 +76,14 @@ def _translate_status(item): 'decrypting': 'preparing', 'untarring': 'saving', 'available': 'active'} - item['status'] = status_mapping[item['status']] + try: + item['status'] = status_mapping[item['status']] + except KeyError: + # TODO(sirp): Performing translation of status (if necessary) here for + # now. Perhaps this should really be done in EC2 API and + # S3ImageService + pass + return item @@ -115,8 +122,7 @@ class Controller(wsgi.Controller): items = self._service.index(req.environ['nova.context']) items = common.limited(items, req) items = [_translate_keys(item) for item in items] - #TODO(sirp): removing for glance - #items = [_translate_status(item) for item in items] + items = [_translate_status(item) for item in items] return dict(images=items) def show(self, req, id): @@ -132,11 +138,10 @@ class Controller(wsgi.Controller): env = self._deserialize(req.body, req) instance_id = env["image"]["serverId"] name = env["image"]["name"] - + image_meta = compute_api.ComputeAPI().snapshot( context, instance_id, name) - #TODO(sirp): need to map Glance attrs to OpenStackAPI attrs return dict(image=image_meta) def update(self, req, id): -- cgit From b8fc639af336630c56ce3807639a5e26c0d07982 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Fri, 7 Jan 2011 13:02:55 -0800 Subject: set the hostname factory in the service init --- nova/api/ec2/cloud.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 8e3091a68..b16ab42bd 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -93,8 +93,10 @@ class CloudController(object): def __init__(self): self.network_manager = utils.import_object(FLAGS.network_manager) self.image_service = utils.import_object(FLAGS.image_service) - self.compute_api = compute_api.ComputeAPI(self.network_manager, - self.image_service) + self.compute_api = compute_api.ComputeAPI( + network_manager=self.network_manager, + image_service=self.image_service, + hostname_factory=internal_id_to_ec2_id) self.setup() def __str__(self): @@ -807,7 +809,6 @@ class CloudController(object): key_name=kwargs.get('key_name'), user_data=kwargs.get('user_data'), security_group=kwargs.get('security_group'), - hostname_format='ec2', availability_zone=kwargs.get('placement', {}).get( 'AvailabilityZone')) return self._format_run_instances(context, -- cgit From d79600c1029ab91de8a81809df9efddc762351c0 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Fri, 7 Jan 2011 16:42:38 -0800 Subject: small cleanups --- nova/api/openstack/servers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 0953b01ed..3f0fdc575 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -49,7 +49,7 @@ def _translate_detail_keys(inst): power_state.SHUTOFF: 'active', power_state.CRASHED: 'error'} inst_dict = {} - print inst + mapped_keys = dict(status='state', imageId='image_id', flavorId='instance_type', name='display_name', id='id') -- cgit From 69a2612be4e865063fa5982462673f1843e8befc Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Tue, 11 Jan 2011 13:54:23 -0600 Subject: Changes per Edays comments --- nova/api/openstack/images.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 1102098b8..8f3f94ea1 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -138,7 +138,7 @@ class Controller(wsgi.Controller): instance_id = env["image"]["serverId"] name = env["image"]["name"] - image_meta = compute_api.ComputeAPI().snapshot( + image_meta = compute.ComputeAPI().snapshot( context, instance_id, name) return dict(image=image_meta) -- cgit From 2c7ffd2dab260c2bfc308ccd0c8d52e57a015413 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Thu, 13 Jan 2011 13:48:01 -0600 Subject: Fixing Image ID workaround and typo --- nova/api/openstack/images.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 4415db75f..9d56bc508 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -99,9 +99,11 @@ def _filter_keys(item, keys): def _convert_image_id_to_hash(image): - image_id = abs(hash(image['imageId'])) - image['imageId'] = image_id - image['id'] = image_id + if 'imageId' in image: + # Convert EC2-style ID (i-blah) to Rackspace-style (int) + image_id = abs(hash(image['imageId'])) + image['imageId'] = image_id + image['id'] = image_id class Controller(wsgi.Controller): @@ -155,7 +157,7 @@ class Controller(wsgi.Controller): instance_id = env["image"]["serverId"] name = env["image"]["name"] - image_meta = compute.ComputeAPI().snapshot( + image_meta = compute.API().snapshot( context, instance_id, name) return dict(image=image_meta) -- cgit From 69c11c27c20c74aced491ecfe78a80872ad6232a Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Fri, 14 Jan 2011 17:54:36 -0800 Subject: pep8 fixes... largely to things from trunk? --- nova/api/ec2/cloud.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index d1aec23be..a1ae70fb0 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -515,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=instance_id) + output = self.compute_api.get_console_output( + context, instance_id=instance_id) now = datetime.datetime.utcnow() return {"InstanceId": ec2_id, "Timestamp": now, -- cgit From a0779f5df2829f91bdc944e7275f44bd831643cc Mon Sep 17 00:00:00 2001 From: Kei Masumoto Date: Wed, 19 Jan 2011 08:49:17 +0900 Subject: fixed based on reviewer's comment --- nova/api/ec2/cloud.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 061f3f70f..c807bc13c 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -729,8 +729,6 @@ class CloudController(object): ec2_id = None if (floating_ip_ref['fixed_ip'] and floating_ip_ref['fixed_ip']['instance']): - # modified by masumotok - #instance_id = floating_ip_ref['fixed_ip']['instance']['ec2_id'] instance_id = floating_ip_ref['fixed_ip']['instance']['id'] ec2_id = id_to_ec2_id(instance_id) address_rv = {'public_ip': address, -- cgit From d91229f7a3b60095677e1bb76a548668c59ee9e2 Mon Sep 17 00:00:00 2001 From: Andy Smith Date: Tue, 18 Jan 2011 11:01:16 -0800 Subject: revert live_migration branch --- nova/api/ec2/cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index c94540793..57d41ed67 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -729,7 +729,7 @@ class CloudController(object): ec2_id = None if (floating_ip_ref['fixed_ip'] and floating_ip_ref['fixed_ip']['instance']): - instance_id = floating_ip_ref['fixed_ip']['instance']['id'] + instance_id = floating_ip_ref['fixed_ip']['instance']['ec2_id'] ec2_id = id_to_ec2_id(instance_id) address_rv = {'public_ip': address, 'instance_id': ec2_id} -- cgit