From 65b9dfbea28f1607ef471e78b73ba77183d943f6 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 24 Feb 2011 01:53:01 -0800 Subject: capability aggregation working --- nova/api/openstack/zones.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py index 989b3a235..c6c27dd4b 100644 --- a/nova/api/openstack/zones.py +++ b/nova/api/openstack/zones.py @@ -15,9 +15,10 @@ import common +from nova import db from nova import flags +from nova import log as logging from nova import wsgi -from nova import db from nova.scheduler import api @@ -67,8 +68,16 @@ class Controller(wsgi.Controller): def info(self, req): """Return name and capabilities for this zone.""" - return dict(zone=dict(name=FLAGS.zone_name, - capabilities=FLAGS.zone_capabilities)) + items = api.API().get_zone_capabilities(req.environ['nova.context']) + + zone = dict(name=FLAGS.zone_name) + caps = FLAGS.zone_capabilities.split(';') + for cap in caps: + key_values = cap.split(':') + zone[key_values[0]] = key_values[1] + for item, (min_value, max_value) in items.iteritems(): + zone[item] = "%s,%s" % (min_value, max_value) + return dict(zone=zone) def show(self, req, id): """Return data about the given zone id""" -- cgit From 47bbfaab52642f3ff79bcdefb8d705fb02b549f9 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 24 Feb 2011 15:23:15 -0800 Subject: new tests --- nova/api/openstack/zones.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py index c6c27dd4b..fecbd6fa3 100644 --- a/nova/api/openstack/zones.py +++ b/nova/api/openstack/zones.py @@ -53,7 +53,7 @@ class Controller(wsgi.Controller): """Return all zones in brief""" # Ask the ZoneManager in the Scheduler for most recent data, # or fall-back to the database ... - items = api.API().get_zone_list(req.environ['nova.context']) + items = api.API.get_zone_list(req.environ['nova.context']) if not items: items = db.zone_get_all(req.environ['nova.context']) @@ -68,7 +68,7 @@ class Controller(wsgi.Controller): def info(self, req): """Return name and capabilities for this zone.""" - items = api.API().get_zone_capabilities(req.environ['nova.context']) + items = api.API.get_zone_capabilities(req.environ['nova.context']) zone = dict(name=FLAGS.zone_name) caps = FLAGS.zone_capabilities.split(';') -- cgit From 7afebad78de462918b89d61f5d8e0cee8bc11068 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Fri, 4 Mar 2011 13:45:43 -0500 Subject: Fix api logging to show proper path and controller:action. --- nova/api/ec2/__init__.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 5adc2c075..2493adc95 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -61,10 +61,13 @@ class RequestLogging(wsgi.Middleware): return rv def log_request_completion(self, response, request, start): - controller = request.environ.get('ec2.controller', None) - if controller: - controller = controller.__class__.__name__ - action = request.environ.get('ec2.action', None) + apireq = request.environ.get('ec2.request', None) + if apirequest: + controller = apireq.controller + action = apireq.action + else: + controller = None + action = None ctxt = request.environ.get('ec2.context', None) delta = utils.utcnow() - start seconds = delta.seconds @@ -75,7 +78,7 @@ class RequestLogging(wsgi.Middleware): microseconds, request.remote_addr, request.method, - request.path_info, + "%s%s" % (request.script_name, request.path_info), controller, action, response.status_int, -- cgit From ceccffaab6fb5fce3b0951b5a8eea65f523e8563 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Fri, 4 Mar 2011 19:13:27 -0500 Subject: apirequest -> apireq. --- nova/api/ec2/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index 2493adc95..b06289e67 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -62,7 +62,7 @@ class RequestLogging(wsgi.Middleware): def log_request_completion(self, response, request, start): apireq = request.environ.get('ec2.request', None) - if apirequest: + if apireq: controller = apireq.controller action = apireq.action else: -- cgit From 6b95c5133452ae26da2cb7f08267aa4cb056e7af Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 10 Mar 2011 15:05:04 -0500 Subject: Initial support fo extension resources. Tests. --- nova/api/openstack/__init__.py | 8 +++++++- nova/api/openstack/extensions.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 nova/api/openstack/extensions.py (limited to 'nova/api') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index ab9dbb780..28e2a1691 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -30,6 +30,7 @@ from nova import wsgi from nova.api.openstack import faults from nova.api.openstack import backup_schedules from nova.api.openstack import consoles +from nova.api.openstack import extensions from nova.api.openstack import flavors from nova.api.openstack import images from nova.api.openstack import servers @@ -68,7 +69,7 @@ class APIRouter(wsgi.Router): """Simple paste factory, :class:`nova.wsgi.Router` doesn't have one""" return cls() - def __init__(self): + def __init__(self, ext_manager=None): mapper = routes.Mapper() server_members = {'action': 'POST'} @@ -111,6 +112,11 @@ class APIRouter(wsgi.Router): collection={'detail': 'GET'}, controller=shared_ip_groups.Controller()) + if ext_manager is None: + ext_manager = extensions.ExtensionManager() + for resource in ext_manager.get_resources(): + resource.add_routes(mapper) + super(APIRouter, self).__init__(mapper) diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py new file mode 100644 index 000000000..1c539c500 --- /dev/null +++ b/nova/api/openstack/extensions.py @@ -0,0 +1,29 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# 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. + +class ExtensionManager(object): + + def get_resources(self): + """ + returns a list of ExtensionResource objects + """ + return [] + +class ExtensionResource(object): + + def add_routes(self, mapper): + pass -- cgit From 7d4eae131f2f844f368aa5ff79c68191756775b6 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Fri, 11 Mar 2011 14:33:12 -0500 Subject: Add config for osapi_extensions_path. Update the ExtensionManager so that it loads extensions in the osapi_extensions_path. --- nova/api/openstack/__init__.py | 9 +++++---- nova/api/openstack/extensions.py | 32 +++++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 5 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 28e2a1691..8f6076511 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -69,7 +69,7 @@ class APIRouter(wsgi.Router): """Simple paste factory, :class:`nova.wsgi.Router` doesn't have one""" return cls() - def __init__(self, ext_manager=None): + def __init__(self, ext_mgr=None): mapper = routes.Mapper() server_members = {'action': 'POST'} @@ -112,9 +112,10 @@ class APIRouter(wsgi.Router): collection={'detail': 'GET'}, controller=shared_ip_groups.Controller()) - if ext_manager is None: - ext_manager = extensions.ExtensionManager() - for resource in ext_manager.get_resources(): + if ext_mgr is None: + ext_mgr = extensions.ExtensionManager(FLAGS.osapi_extensions_path) + for resource in ext_mgr.get_resources(): + print resource resource.add_routes(mapper) super(APIRouter, self).__init__(mapper) diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 1c539c500..24846d9cd 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -15,15 +15,45 @@ # License for the specific language governing permissions and limitations # under the License. +import imp +import os +import sys + + class ExtensionManager(object): + def __init__(self, path): + + self.path = path + self.extensions = [] + self._load_extensions() + def get_resources(self): """ returns a list of ExtensionResource objects """ - return [] + resources = [] + for ext in self.extensions: + resources.append(ext.get_resources()) + return resources + + def _load_extensions(self): + if not os.path.exists(self.path): + return + + for f in os.listdir(self.path): + mod_name, file_ext = os.path.splitext(os.path.split(f)[-1]) + ext_path = os.path.join(self.path, f) + if file_ext.lower() == '.py': + mod = imp.load_source(mod_name, ext_path) + self.extensions.append(getattr(mod, 'get_extension')()) + class ExtensionResource(object): + """ + Example ExtensionResource object. All ExtensionResource objects should + adhere to this interface. + """ def add_routes(self, mapper): pass -- cgit From 2bfa7b29c7882da559041cea771b9243555828fa Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Sun, 13 Mar 2011 13:51:42 -0400 Subject: The extension name is constructed from the camel cased module_name + 'Extension'. --- nova/api/openstack/extensions.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 24846d9cd..13789863b 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -38,6 +38,12 @@ class ExtensionManager(object): return resources def _load_extensions(self): + """ + Load extensions from the configured path. The extension name is + constructed from the camel cased module_name + 'Extension'. If your + extension module was named widgets.py the extension class within that + module should be 'WidgetsExtension'. + """ if not os.path.exists(self.path): return @@ -46,7 +52,8 @@ class ExtensionManager(object): ext_path = os.path.join(self.path, f) if file_ext.lower() == '.py': mod = imp.load_source(mod_name, ext_path) - self.extensions.append(getattr(mod, 'get_extension')()) + ext_name = mod_name[0].upper() + mod_name[1:] + 'Extension' + self.extensions.append(getattr(mod, ext_name)()) class ExtensionResource(object): -- cgit From 229c5bc3324d5df39ca959d71a540a806bc5ad3e Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 14 Mar 2011 16:58:03 -0400 Subject: Implement action extensions. --- nova/api/openstack/__init__.py | 1 - nova/api/openstack/extensions.py | 106 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 9b7b76a91..8a458eea1 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -124,7 +124,6 @@ class APIRouter(wsgi.Router): if ext_mgr is None: ext_mgr = extensions.ExtensionManager(FLAGS.osapi_extensions_path) for resource in ext_mgr.get_resources(): - print resource resource.add_routes(mapper) super(APIRouter, self).__init__(mapper) diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 13789863b..e41de3120 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -18,11 +18,101 @@ import imp import os import sys +import routes +import webob.dec +import webob.exc + +from nova import flags +from nova import log as logging +from nova import wsgi + + +LOG = logging.getLogger('extensions') + + +FLAGS = flags.FLAGS + + +class ExtensionActionController(wsgi.Controller): + + def __init__(self, application, action_name, handler): + + self.application = application + self.action_name = action_name + self.handler = handler + + def action(self, req, id): + + input_dict = self._deserialize(req.body, req.get_content_type()) + if self.action_name in input_dict: + return self.handler(input_dict, req, id) + # no action handler found (bump to downstream application) + res = self.application + return res + + +class ExtensionMiddleware(wsgi.Middleware): + """ + Extensions middleware that intercepts configured routes for extensions. + """ + @classmethod + def factory(cls, global_config, **local_config): + """ paste factory """ + def _factory(app): + return cls(app, **local_config) + return _factory + + def __init__(self, application, ext_mgr=None): + mapper = routes.Mapper() + + if ext_mgr is None: + ext_mgr = ExtensionManager(FLAGS.osapi_extensions_path) + + # create custom mapper connections for extended actions + for action in ext_mgr.get_actions(): + controller = ExtensionActionController(application, action.name, + action.handler) + mapper.connect("/%s/{id}/action.:(format)" % action.collection, + action='action', + controller=controller, + conditions=dict(method=['POST'])) + mapper.connect("/%s/{id}/action" % action.collection, + action='action', + controller=controller, + conditions=dict(method=['POST'])) + + self._router = routes.middleware.RoutesMiddleware(self._dispatch, + mapper) + + super(ExtensionMiddleware, self).__init__(application) + + @webob.dec.wsgify(RequestClass=wsgi.Request) + def __call__(self, req): + """ + Route the incoming request with router. + """ + req.environ['extended.app'] = self.application + return self._router + + @staticmethod + @webob.dec.wsgify(RequestClass=wsgi.Request) + def _dispatch(req): + """ + Called by self._router after matching the incoming request to a route + and putting the information into req.environ. Either returns the + routed WSGI app's response or defers to the extended application. + """ + match = req.environ['wsgiorg.routing_args'][1] + if not match: + return req.environ['extended.app'] + app = match['controller'] + return app class ExtensionManager(object): def __init__(self, path): + LOG.audit(_('Initializing extension manager.')) self.path = path self.extensions = [] @@ -37,6 +127,12 @@ class ExtensionManager(object): resources.append(ext.get_resources()) return resources + def get_actions(self): + actions = [] + for ext in self.extensions: + actions.extend(ext.get_actions()) + return actions + def _load_extensions(self): """ Load extensions from the configured path. The extension name is @@ -48,6 +144,7 @@ class ExtensionManager(object): return for f in os.listdir(self.path): + LOG.audit(_('Loading extension file: %s'), f) mod_name, file_ext = os.path.splitext(os.path.split(f)[-1]) ext_path = os.path.join(self.path, f) if file_ext.lower() == '.py': @@ -56,6 +153,15 @@ class ExtensionManager(object): self.extensions.append(getattr(mod, ext_name)()) +class ExtensionAction(object): + + def __init__(self, member, collection, name, handler): + self.member = member + self.collection = collection + self.name = name + self.handler = handler + + class ExtensionResource(object): """ Example ExtensionResource object. All ExtensionResource objects should -- cgit From a56a973e9d839df5bcd956126300afd7df4c2fe9 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Tue, 15 Mar 2011 00:37:13 +0000 Subject: Fixing API per spec, to get unit-tests to pass --- nova/api/openstack/images.py | 118 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 108 insertions(+), 10 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 98f0dd96b..7b3800429 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -15,10 +15,13 @@ # License for the specific language governing permissions and limitations # under the License. +import datetime + from webob import exc from nova import compute from nova import flags +from nova import log from nova import utils from nova import wsgi import nova.api.openstack @@ -27,6 +30,8 @@ from nova.api.openstack import faults import nova.image.service +LOG = log.getLogger('nova.api.openstack.images') + FLAGS = flags.FLAGS @@ -84,8 +89,6 @@ def _translate_status(item): # S3ImageService pass - return item - def _filter_keys(item, keys): """ @@ -104,6 +107,89 @@ def _convert_image_id_to_hash(image): image['id'] = image_id +def _translate_s3_like_images(image_metadata): + """Work-around for leaky S3ImageService abstraction""" + api_metadata = image_metadata.copy() + _convert_image_id_to_hash(api_metadata) + api_metadata = _translate_keys(api_metadata) + _translate_status(api_metadata) + return api_metadata + + +def _translate_metadata_for_api_detail(image_metadata): + """Translate from ImageService to OpenStack API style attribute names + + This involves 3 steps: + + 1. Translating required keys + + 2. Translating optional keys (ex. progress, serverId) + + 3. Formatting values according to API spec (for example dates must + look like "2010-08-10T12:00:00Z") + """ + service_metadata = image_metadata.copy() + api_metadata = {} + + # 1. Translate required keys + required_image_service2api = { + 'id': 'id', + 'name': 'name', + 'updated_at': 'updated', + 'created_at': 'created', + 'status': 'status'} + for service_attr, api_attr in required_image_service2api.items(): + api_metadata[api_attr] = service_metadata[service_attr] + + # 2. Translate optional keys + optional_image_service2api = {'instance_id': 'serverId'} + for service_attr, api_attr in optional_image_service2api.items(): + if service_attr in service_metadata: + api_metadata[api_attr] = service_metadata[service_attr] + + # 2a. Progress special case + # TODO(sirp): ImageService doesn't have a notion of progress yet, so for + # now just fake it + if service_metadata['status'] == 'saving': + api_metadata['progress'] = 0 + + # 3. Format values + + # 3a. Format Image Status (API requires uppercase) + status_service2api = {'queued': 'QUEUED', + 'preparing': 'PREPARING', + 'saving': 'SAVING', + 'active': 'ACTIVE', + 'killed': 'FAILED'} + api_metadata['status'] = status_service2api[api_metadata['status']] + + # 3b. Format timestamps + def _format_timestamp(dt_str): + """Return a timestamp formatted for OpenStack API + + NOTE(sirp): + + ImageService (specifically GlanceImageService) is currently + returning timestamps as strings. This should probably be datetime + objects. In the mean time, we work around this by using strptime() to + create datetime objects. + """ + if dt_str is None: + return None + + service_timestamp_fmt = "%Y-%m-%dT%H:%M:%S" + api_timestamp_fmt = "%Y-%m-%dT%H:%M:%SZ" + dt = datetime.datetime.strptime(dt_str, service_timestamp_fmt) + return dt.strftime(api_timestamp_fmt) + + for ts_attr in ('created', 'updated'): + if ts_attr in api_metadata: + formatted_timestamp = _format_timestamp(api_metadata[ts_attr]) + api_metadata[ts_attr] = formatted_timestamp + + return api_metadata + + class Controller(wsgi.Controller): _serialization_metadata = { @@ -125,16 +211,28 @@ class Controller(wsgi.Controller): def detail(self, req): """Return all public images in detail""" try: - items = self._service.detail(req.environ['nova.context']) + service_image_metas = self._service.detail( + req.environ['nova.context']) except NotImplementedError: - items = self._service.index(req.environ['nova.context']) - for image in items: - _convert_image_id_to_hash(image) + service_image_metas = 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] - return dict(images=items) + service_image_metas = common.limited(service_image_metas, req) + + # FIXME(sirp): The S3ImageService appears to be leaking implementation + # details, including its internal attribute names, and internal + # `status` values. Working around it for now. + s3_like_image = (service_image_metas and + ('imageId' in service_image_metas[0])) + if s3_like_image: + translate = _translate_s3_like_images + else: + translate = _translate_metadata_for_api_detail + + api_image_metas = [translate(service_image_meta) + for service_image_meta in service_image_metas] + + return dict(images=api_image_metas) def show(self, req, id): """Return data about the given image id""" -- cgit From f0141b1616e1b1fc9e52e33b37cc3a1091c57587 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 14 Mar 2011 22:24:34 -0400 Subject: Moved extended resource code into the extensions.py module. --- nova/api/openstack/__init__.py | 6 ------ nova/api/openstack/extensions.py | 29 +++++++++++++++++++++-------- 2 files changed, 21 insertions(+), 14 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 8a458eea1..ff91c77cf 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -31,7 +31,6 @@ from nova.api.openstack import accounts from nova.api.openstack import faults from nova.api.openstack import backup_schedules from nova.api.openstack import consoles -from nova.api.openstack import extensions from nova.api.openstack import flavors from nova.api.openstack import images from nova.api.openstack import servers @@ -121,11 +120,6 @@ class APIRouter(wsgi.Router): collection={'detail': 'GET'}, controller=shared_ip_groups.Controller()) - if ext_mgr is None: - ext_mgr = extensions.ExtensionManager(FLAGS.osapi_extensions_path) - for resource in ext_mgr.get_resources(): - resource.add_routes(mapper) - super(APIRouter, self).__init__(mapper) diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index e41de3120..f32471051 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -68,7 +68,15 @@ class ExtensionMiddleware(wsgi.Middleware): if ext_mgr is None: ext_mgr = ExtensionManager(FLAGS.osapi_extensions_path) - # create custom mapper connections for extended actions + # extended resources + for resource in ext_mgr.get_resources(): + mapper.resource(resource.member, resource.collection, + controller=resource.controller, + collection=resource.collection_actions, + member=resource.member_actions, + parent_resource=resource.parent) + + # extended actions for action in ext_mgr.get_actions(): controller = ExtensionActionController(application, action.name, action.handler) @@ -124,10 +132,13 @@ class ExtensionManager(object): """ resources = [] for ext in self.extensions: - resources.append(ext.get_resources()) + resources.extend(ext.get_resources()) return resources def get_actions(self): + """ + returns a list of ExtensionAction objects + """ actions = [] for ext in self.extensions: actions.extend(ext.get_actions()) @@ -163,10 +174,12 @@ class ExtensionAction(object): class ExtensionResource(object): - """ - Example ExtensionResource object. All ExtensionResource objects should - adhere to this interface. - """ - def add_routes(self, mapper): - pass + def __init__(self, member, collection, controller, + parent=None, collection_actions={}, member_actions={}): + self.member = member + self.collection = collection + self.controller = controller + self.parent = parent + self.collection_actions = collection_actions + self.member_actions = member_actions -- cgit From ad6f82909060cd4d1d99a1b1a9f33aa2788d8c94 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Tue, 15 Mar 2011 05:37:08 +0000 Subject: serverId returned as int per spec --- nova/api/openstack/images.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 7b3800429..0c56b5f0d 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -116,7 +116,7 @@ def _translate_s3_like_images(image_metadata): return api_metadata -def _translate_metadata_for_api_detail(image_metadata): +def _translate_from_image_service_to_api(image_metadata): """Translate from ImageService to OpenStack API style attribute names This involves 3 steps: @@ -227,7 +227,7 @@ class Controller(wsgi.Controller): if s3_like_image: translate = _translate_s3_like_images else: - translate = _translate_metadata_for_api_detail + translate = _translate_from_image_service_to_api api_image_metas = [translate(service_image_meta) for service_image_meta in service_image_metas] -- cgit From f91d7925761f8204fdd46435ff57d74ae17483cf Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Tue, 15 Mar 2011 18:29:26 -0700 Subject: first pass openstack redirect working --- nova/api/openstack/servers.py | 1 + 1 file changed, 1 insertion(+) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 85999764f..ffcbe628c 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -343,6 +343,7 @@ class Controller(wsgi.Controller): """ Permit Admins to Pause the server. """ ctxt = req.environ['nova.context'] try: + LOG.debug(_("*** Compute.api::pause %s"), id) self.compute_api.pause(ctxt, id) except: readable = traceback.format_exc() -- cgit From 0053b776276e9cac617c812931c248be7e49fea2 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Tue, 15 Mar 2011 23:00:09 -0400 Subject: Add ResponseExtensions. --- nova/api/openstack/extensions.py | 144 ++++++++++++++++++++++++++++++++------- 1 file changed, 118 insertions(+), 26 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index f32471051..8b8806c8a 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -33,24 +33,43 @@ LOG = logging.getLogger('extensions') FLAGS = flags.FLAGS -class ExtensionActionController(wsgi.Controller): +class ActionExtensionController(wsgi.Controller): - def __init__(self, application, action_name, handler): + def __init__(self, application): self.application = application - self.action_name = action_name - self.handler = handler + self.action_handlers = {} + + def add_action(self, action_name, handler): + self.action_handlers[action_name] = handler def action(self, req, id): input_dict = self._deserialize(req.body, req.get_content_type()) - if self.action_name in input_dict: - return self.handler(input_dict, req, id) + for action_name, handler in self.action_handlers.iteritems(): + if action_name in input_dict: + return handler(input_dict, req, id) # no action handler found (bump to downstream application) res = self.application return res +class ResponseExtensionController(wsgi.Controller): + + def __init__(self, application): + self.application = application + self.handlers = [] + + def add_handler(self, handler): + self.handlers.append(handler) + + def process(self, req, *args, **kwargs): + res = req.get_response(self.application) + # currently response handlers are un-ordered + for handler in self.handlers: + return handler(res) + + class ExtensionMiddleware(wsgi.Middleware): """ Extensions middleware that intercepts configured routes for extensions. @@ -62,33 +81,80 @@ class ExtensionMiddleware(wsgi.Middleware): return cls(app, **local_config) return _factory + def _actions_by_collection(self, application, ext_mgr): + """ + Return a dict of ActionExtensionController objects by collection + """ + action_controllers = {} + for action in ext_mgr.get_actions(): + if not action.collection in action_controllers.keys(): + controller = ActionExtensionController(application) + action_controllers[action.collection] = controller + return action_controllers + + def _responses_by_collection(self, application, ext_mgr): + """ + Return a dict of ResponseExtensionController objects by collection + """ + response_ext_controllers = {} + for resp_ext in ext_mgr.get_response_extensions(): + if not resp_ext.url_route in response_ext_controllers.keys(): + controller = ResponseExtensionController(application) + response_ext_controllers[resp_ext.url_route] = controller + return response_ext_controllers + def __init__(self, application, ext_mgr=None): - mapper = routes.Mapper() if ext_mgr is None: ext_mgr = ExtensionManager(FLAGS.osapi_extensions_path) + self.ext_mgr = ext_mgr + + mapper = routes.Mapper() # extended resources for resource in ext_mgr.get_resources(): - mapper.resource(resource.member, resource.collection, + LOG.debug(_('Extended resource: %s'), + resource.collection) + mapper.resource(resource.collection, resource.collection, controller=resource.controller, collection=resource.collection_actions, member=resource.member_actions, parent_resource=resource.parent) # extended actions + action_controllers = self._actions_by_collection(application, ext_mgr) for action in ext_mgr.get_actions(): - controller = ExtensionActionController(application, action.name, - action.handler) - mapper.connect("/%s/{id}/action.:(format)" % action.collection, + LOG.debug(_('Extended collection/action: %s/%s'), + action.collection, + action.action_name) + controller = action_controllers[action.collection] + controller.add_action(action.action_name, action.handler) + + mapper.connect("/%s/:(id)/action.:(format)" % action.collection, action='action', controller=controller, conditions=dict(method=['POST'])) - mapper.connect("/%s/{id}/action" % action.collection, + mapper.connect("/%s/:(id)/action" % action.collection, action='action', controller=controller, conditions=dict(method=['POST'])) + # extended responses + resp_controllers = self._responses_by_collection(application, ext_mgr) + for response_ext in ext_mgr.get_response_extensions(): + LOG.debug(_('Extended response: %s'), response_ext.url_route) + controller = resp_controllers[response_ext.url_route] + controller.add_handler(response_ext.handler) + mapper.connect(response_ext.url_route + '.:(format)', + action='process', + controller=controller, + conditions=response_ext.conditions) + + mapper.connect(response_ext.url_route, + action='process', + controller=controller, + conditions=response_ext.conditions) + self._router = routes.middleware.RoutesMiddleware(self._dispatch, mapper) @@ -106,9 +172,8 @@ class ExtensionMiddleware(wsgi.Middleware): @webob.dec.wsgify(RequestClass=wsgi.Request) def _dispatch(req): """ - Called by self._router after matching the incoming request to a route - and putting the information into req.environ. Either returns the - routed WSGI app's response or defers to the extended application. + Returns the routed WSGI app's response or defers to the extended + application. """ match = req.environ['wsgiorg.routing_args'][1] if not match: @@ -128,7 +193,7 @@ class ExtensionManager(object): def get_resources(self): """ - returns a list of ExtensionResource objects + returns a list of ResourceExtension objects """ resources = [] for ext in self.extensions: @@ -137,13 +202,22 @@ class ExtensionManager(object): def get_actions(self): """ - returns a list of ExtensionAction objects + returns a list of ActionExtension objects """ actions = [] for ext in self.extensions: actions.extend(ext.get_actions()) return actions + def get_response_extensions(self): + """ + returns a list of ResponseExtension objects + """ + response_exts = [] + for ext in self.extensions: + response_exts.extend(ext.get_response_extensions()) + return response_exts + def _load_extensions(self): """ Load extensions from the configured path. The extension name is @@ -160,24 +234,42 @@ class ExtensionManager(object): ext_path = os.path.join(self.path, f) if file_ext.lower() == '.py': mod = imp.load_source(mod_name, ext_path) - ext_name = mod_name[0].upper() + mod_name[1:] + 'Extension' + ext_name = mod_name[0].upper() + mod_name[1:] self.extensions.append(getattr(mod, ext_name)()) -class ExtensionAction(object): +class ResponseExtension(object): + """ + ResponseExtension objects can be used to add data to responses from + core nova OpenStack API controllers. + """ - def __init__(self, member, collection, name, handler): - self.member = member + def __init__(self, url_route, method, handler): + self.url_route = url_route + self.conditions = dict(method=[method]) + self.handler = handler + + +class ActionExtension(object): + """ + ActionExtension objects can be used to add custom actions to core nova + nova OpenStack API controllers. + """ + + def __init__(self, collection, action_name, handler): self.collection = collection - self.name = name + self.action_name = action_name self.handler = handler -class ExtensionResource(object): +class ResourceExtension(object): + """ + ResourceExtension objects can be used to add add top level resources + to the OpenStack API in nova. + """ - def __init__(self, member, collection, controller, - parent=None, collection_actions={}, member_actions={}): - self.member = member + def __init__(self, collection, controller, parent=None, + collection_actions={}, member_actions={}): self.collection = collection self.controller = controller self.parent = parent -- cgit From 7a61965908ebfc076ad3b1d9cdc5773ade50bf75 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Tue, 15 Mar 2011 20:30:27 -0700 Subject: response working --- nova/api/zone_redirect.py | 100 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 nova/api/zone_redirect.py (limited to 'nova/api') diff --git a/nova/api/zone_redirect.py b/nova/api/zone_redirect.py new file mode 100644 index 000000000..5e40a82dd --- /dev/null +++ b/nova/api/zone_redirect.py @@ -0,0 +1,100 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 OpenStack LLC. +# 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.import datetime + +"""Reroutes calls to child zones on ZoneRouteException's.""" + +import httplib +import re +import webob +import webob.dec +import webob.exc +import urlparse +import urllib + +from nova import exception +from nova import log as logging +from nova import wsgi + +import novaclient.client as client + +try: + import json +except ImportError: + import simplejson as json + + +LOG = logging.getLogger('server') + + +class ZoneRedirectMiddleware(wsgi.Middleware): + """Catches Zone Routing exceptions and delegates the call + to child zones.""" + + @webob.dec.wsgify + def __call__(self, req): + try: + return req.get_response(self.application) + except exception.ZoneRouteException as e: + if len(e.zones) == 0: + exc = webob.exc.HTTPInternalServerError(explanation= + _("No zones to reroute to.")) + return faults.Fault(exc) + + zone = e.zones[0] + # Todo(sandy): This only works for OpenStack API currently. + # Needs to be broken out into a driver. + url = zone.api_url + LOG.info(_("Zone redirect to:[url:%(api_url)s, username:%(username)s]" + % dict(api_url=zone.api_url, username=zone.username))) + + LOG.info(_("Zone Initial Req: %s"), req) + nova = client.OpenStackClient(zone.username, zone.password, + zone.api_url) + nova.authenticate() + new_req = req.copy() + #m = re.search('(https?://.+)/(v\d+\.\d+)/', url) + + scheme, netloc, path, query, frag = urlparse.urlsplit(new_req.path_qs) + query = urlparse.parse_qsl(query) + LOG.debug("**** QUERY=%s^%s^%s", path, query, frag) + query = [(key, value) for key, value in query if key != 'fresh'] + query = urllib.urlencode(query) + url = urlparse.urlunsplit((scheme, netloc, path, query, frag)) + + m = re.search('/(v\d+\.\d+)/(.+)', url) + version = m.group(1) + resource = m.group(2) + + LOG.info(_("New Request Data: %s"), new_req.body) + #LOG.info(_("New Request Headers: %s"), new_req.headers) + LOG.info(_("New Request Path: %s"), resource) + if req.method == 'GET': + response, body = nova.get(resource, body=new_req.body) + elif req.method == 'POST': + response, body = nova.post(resource, body=new_req.body) + elif req.method == 'PUT': + response, body = nova.put(resource, body=new_req.body) + elif req.method == 'DELETE': + response, body = nova.delete(resource, body=new_req.body) + #response, body = nova.request(req.path_qs, headers=new_req.headers, body=new_req.body) + LOG.info(_("Zone Response: %s / %s"), response, body) + res = webob.Response() + res.status = response['status'] + res.content_type = response['content-type'] + res.body = json.dumps(body) + LOG.info(_("Zone WebOb Response: %s"), res) + return res -- cgit From ddeede35a5036aa3c80742fde69468aedbc74892 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 16 Mar 2011 06:09:00 -0700 Subject: Error codes handled properly now --- nova/api/zone_redirect.py | 94 ++++++++++++++++++++++++++--------------------- 1 file changed, 53 insertions(+), 41 deletions(-) (limited to 'nova/api') diff --git a/nova/api/zone_redirect.py b/nova/api/zone_redirect.py index 5e40a82dd..ad47a6216 100644 --- a/nova/api/zone_redirect.py +++ b/nova/api/zone_redirect.py @@ -30,6 +30,7 @@ from nova import log as logging from nova import wsgi import novaclient.client as client +import novaclient.exceptions as osexceptions try: import json @@ -49,52 +50,63 @@ class ZoneRedirectMiddleware(wsgi.Middleware): try: return req.get_response(self.application) except exception.ZoneRouteException as e: - if len(e.zones) == 0: + if not e.zones: exc = webob.exc.HTTPInternalServerError(explanation= _("No zones to reroute to.")) return faults.Fault(exc) - zone = e.zones[0] # Todo(sandy): This only works for OpenStack API currently. # Needs to be broken out into a driver. - url = zone.api_url - LOG.info(_("Zone redirect to:[url:%(api_url)s, username:%(username)s]" - % dict(api_url=zone.api_url, username=zone.username))) - - LOG.info(_("Zone Initial Req: %s"), req) - nova = client.OpenStackClient(zone.username, zone.password, - zone.api_url) - nova.authenticate() - new_req = req.copy() - #m = re.search('(https?://.+)/(v\d+\.\d+)/', url) - - scheme, netloc, path, query, frag = urlparse.urlsplit(new_req.path_qs) - query = urlparse.parse_qsl(query) - LOG.debug("**** QUERY=%s^%s^%s", path, query, frag) - query = [(key, value) for key, value in query if key != 'fresh'] - query = urllib.urlencode(query) - url = urlparse.urlunsplit((scheme, netloc, path, query, frag)) - - m = re.search('/(v\d+\.\d+)/(.+)', url) - version = m.group(1) - resource = m.group(2) - - LOG.info(_("New Request Data: %s"), new_req.body) - #LOG.info(_("New Request Headers: %s"), new_req.headers) - LOG.info(_("New Request Path: %s"), resource) - if req.method == 'GET': - response, body = nova.get(resource, body=new_req.body) - elif req.method == 'POST': - response, body = nova.post(resource, body=new_req.body) - elif req.method == 'PUT': - response, body = nova.put(resource, body=new_req.body) - elif req.method == 'DELETE': - response, body = nova.delete(resource, body=new_req.body) - #response, body = nova.request(req.path_qs, headers=new_req.headers, body=new_req.body) - LOG.info(_("Zone Response: %s / %s"), response, body) + for zone in e.zones: + url = zone.api_url + LOG.info(_("Zone redirect to:[url:%(api_url)s, " + "username:%(username)s]" + % dict(api_url=zone.api_url, + username=zone.username))) + + nova = client.OpenStackClient(zone.username, zone.password, + zone.api_url) + nova.authenticate() + new_req = req.copy() + + scheme, netloc, path, query, frag = \ + urlparse.urlsplit(new_req.path_qs) + query = urlparse.parse_qsl(query) + query = [(key, value) for key, value in query if key != 'fresh'] + query = urllib.urlencode(query) + url = urlparse.urlunsplit((scheme, netloc, path, query, frag)) + + m = re.search('/(v\d+\.\d+)/(.+)', url) + version = m.group(1) + resource = m.group(2) + + #LOG.info(_("New Request Data: %s"), new_req.body) + #LOG.info(_("New Request Path: %s"), resource) + try: + if req.method == 'GET': + response, body = nova.get(resource, body=new_req.body) + elif req.method == 'POST': + response, body = nova.post(resource, body=new_req.body) + elif req.method == 'PUT': + response, body = nova.put(resource, body=new_req.body) + elif req.method == 'DELETE': + response, body = nova.delete(resource, + body=new_req.body) + except osexceptions.OpenStackException, e: + LOG.info(_("Zone returned error: %s ('%s', '%s')"), + e.code, e.message, e.details) + continue + + LOG.info(_("Zone Response: %s [%s]/ %s"), response, + response.status, body) + if response.status == 200: + res = webob.Response() + res.status = response['status'] + res.content_type = response['content-type'] + res.body = json.dumps(body) + return res + + LOG.info(_("Returning 404 ...")) res = webob.Response() - res.status = response['status'] - res.content_type = response['content-type'] - res.body = json.dumps(body) - LOG.info(_("Zone WebOb Response: %s"), res) + res.status = "404" return res -- cgit From 8dffae687e78a1fa2a8cf0d321d64ee95a35cc1f Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 16 Mar 2011 06:47:27 -0700 Subject: Checks locally before routing --- nova/api/zone_redirect.py | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) (limited to 'nova/api') diff --git a/nova/api/zone_redirect.py b/nova/api/zone_redirect.py index ad47a6216..fec1b1af3 100644 --- a/nova/api/zone_redirect.py +++ b/nova/api/zone_redirect.py @@ -57,9 +57,20 @@ class ZoneRedirectMiddleware(wsgi.Middleware): # Todo(sandy): This only works for OpenStack API currently. # Needs to be broken out into a driver. + new_req = req.copy() + + scheme, netloc, path, query, frag = \ + urlparse.urlsplit(new_req.path_qs) + query = urlparse.parse_qsl(query) + query = [(key, value) for key, value in query if key != 'fresh'] + query = urllib.urlencode(query) + url = urlparse.urlunsplit((scheme, netloc, path, query, frag)) + + m = re.search('/v\d+\.\d+/(.+)', url) + resource = m.group(1) + for zone in e.zones: - url = zone.api_url - LOG.info(_("Zone redirect to:[url:%(api_url)s, " + LOG.debug(_("Zone redirect to:[url:%(api_url)s, " "username:%(username)s]" % dict(api_url=zone.api_url, username=zone.username))) @@ -67,21 +78,6 @@ class ZoneRedirectMiddleware(wsgi.Middleware): nova = client.OpenStackClient(zone.username, zone.password, zone.api_url) nova.authenticate() - new_req = req.copy() - - scheme, netloc, path, query, frag = \ - urlparse.urlsplit(new_req.path_qs) - query = urlparse.parse_qsl(query) - query = [(key, value) for key, value in query if key != 'fresh'] - query = urllib.urlencode(query) - url = urlparse.urlunsplit((scheme, netloc, path, query, frag)) - - m = re.search('/(v\d+\.\d+)/(.+)', url) - version = m.group(1) - resource = m.group(2) - - #LOG.info(_("New Request Data: %s"), new_req.body) - #LOG.info(_("New Request Path: %s"), resource) try: if req.method == 'GET': response, body = nova.get(resource, body=new_req.body) @@ -97,7 +93,7 @@ class ZoneRedirectMiddleware(wsgi.Middleware): e.code, e.message, e.details) continue - LOG.info(_("Zone Response: %s [%s]/ %s"), response, + LOG.debug(_("Zone Response: %s [%s]/ %s"), response, response.status, body) if response.status == 200: res = webob.Response() @@ -106,7 +102,7 @@ class ZoneRedirectMiddleware(wsgi.Middleware): res.body = json.dumps(body) return res - LOG.info(_("Returning 404 ...")) + LOG.debug(_("Zone Redirect Middleware returning 404 ...")) res = webob.Response() res.status = "404" return res -- cgit From d714df5ed4a6a1d4f1c0f7680c2fbb6a6abb81a5 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Wed, 16 Mar 2011 11:02:22 -0400 Subject: Implement top level extensions. --- nova/api/openstack/extensions.py | 94 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 86 insertions(+), 8 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 8b8806c8a..66ddd8078 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -25,6 +25,7 @@ import webob.exc from nova import flags from nova import log as logging from nova import wsgi +from nova.api.openstack import faults LOG = logging.getLogger('extensions') @@ -70,6 +71,42 @@ class ResponseExtensionController(wsgi.Controller): return handler(res) +class ExtensionController(wsgi.Controller): + + def __init__(self, extension_manager): + self.extension_manager = extension_manager + + def _translate(self, ext): + ext_data = {} + ext_data['name'] = ext.get_name() + ext_data['alias'] = ext.get_alias() + ext_data['description'] = ext.get_description() + ext_data['namespace'] = ext.get_namespace() + ext_data['updated'] = ext.get_updated() + ext_data['links'] = [] # TODO: implement extension links + return ext_data + + def index(self, req): + extensions = [] + for alias, ext in self.extension_manager.extensions.iteritems(): + extensions.append(self._translate(ext)) + return dict(extensions=extensions) + + def show(self, req, id): + # NOTE: the extensions alias is used as the 'id' for show + ext = self.extension_manager.extensions[id] + return self._translate(ext) + + def delete(self, req, id): + raise faults.Fault(exc.HTTPNotFound()) + + def create(self, req): + raise faults.Fault(exc.HTTPNotFound()) + + def delete(self, req, id): + raise faults.Fault(exc.HTTPNotFound()) + + class ExtensionMiddleware(wsgi.Middleware): """ Extensions middleware that intercepts configured routes for extensions. @@ -183,12 +220,17 @@ class ExtensionMiddleware(wsgi.Middleware): class ExtensionManager(object): + """ + Load extensions from the configured extension path. + See nova/tests/api/openstack/extensions/foxinsocks.py for an example + extension implementation. + """ def __init__(self, path): LOG.audit(_('Initializing extension manager.')) self.path = path - self.extensions = [] + self.extensions = {} self._load_extensions() def get_resources(self): @@ -196,8 +238,14 @@ class ExtensionManager(object): returns a list of ResourceExtension objects """ resources = [] - for ext in self.extensions: - resources.extend(ext.get_resources()) + resources.append(ResourceExtension('extensions', + ExtensionController(self))) + for alias, ext in self.extensions.iteritems(): + try: + resources.extend(ext.get_resources()) + except AttributeError: + # NOTE: Extension aren't required to have resource extensions + pass return resources def get_actions(self): @@ -205,8 +253,12 @@ class ExtensionManager(object): returns a list of ActionExtension objects """ actions = [] - for ext in self.extensions: - actions.extend(ext.get_actions()) + for alias, ext in self.extensions.iteritems(): + try: + actions.extend(ext.get_actions()) + except AttributeError: + # NOTE: Extension aren't required to have action extensions + pass return actions def get_response_extensions(self): @@ -214,16 +266,36 @@ class ExtensionManager(object): returns a list of ResponseExtension objects """ response_exts = [] - for ext in self.extensions: - response_exts.extend(ext.get_response_extensions()) + for alias, ext in self.extensions.iteritems(): + try: + response_exts.extend(ext.get_response_extensions()) + except AttributeError: + # NOTE: Extension aren't required to have response extensions + pass return response_exts + def _check_extension(self, extension): + """ + Checks for required methods in extension objects. + """ + try: + LOG.debug(_('Ext name: %s'), extension.get_name()) + LOG.debug(_('Ext alias: %s'), extension.get_alias()) + LOG.debug(_('Ext description: %s'), extension.get_description()) + LOG.debug(_('Ext namespace: %s'), extension.get_namespace()) + LOG.debug(_('Ext updated: %s'), extension.get_updated()) + except AttributeError as ex: + LOG.exception(_("Exception loading extension: %s"), unicode(ex)) + def _load_extensions(self): """ Load extensions from the configured path. The extension name is constructed from the camel cased module_name + 'Extension'. If your extension module was named widgets.py the extension class within that module should be 'WidgetsExtension'. + + See nova/tests/api/openstack/extensions/foxinsocks.py for an example + extension implementation. """ if not os.path.exists(self.path): return @@ -235,7 +307,13 @@ class ExtensionManager(object): if file_ext.lower() == '.py': mod = imp.load_source(mod_name, ext_path) ext_name = mod_name[0].upper() + mod_name[1:] - self.extensions.append(getattr(mod, ext_name)()) + try: + new_ext = getattr(mod, ext_name)() + self._check_extension(new_ext) + self.extensions[new_ext.get_alias()] = new_ext + except AttributeError as ex: + LOG.exception(_("Exception loading extension: %s"), + unicode(ex)) class ResponseExtension(object): -- cgit From b2456e983178b97ad94f48c77ef210000d6d6ca4 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Wed, 16 Mar 2011 14:03:38 -0400 Subject: Move mapper code into the _action_ext_controllers and _response_ext_controllers methods. --- nova/api/openstack/extensions.py | 65 +++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 31 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 66ddd8078..4adfcfc5b 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -118,7 +118,7 @@ class ExtensionMiddleware(wsgi.Middleware): return cls(app, **local_config) return _factory - def _actions_by_collection(self, application, ext_mgr): + def _action_ext_controllers(self, application, ext_mgr, mapper): """ Return a dict of ActionExtensionController objects by collection """ @@ -126,18 +126,38 @@ class ExtensionMiddleware(wsgi.Middleware): for action in ext_mgr.get_actions(): if not action.collection in action_controllers.keys(): controller = ActionExtensionController(application) + mapper.connect("/%s/:(id)/action.:(format)" % + action.collection, + action='action', + controller=controller, + conditions=dict(method=['POST'])) + mapper.connect("/%s/:(id)/action" % action.collection, + action='action', + controller=controller, + conditions=dict(method=['POST'])) action_controllers[action.collection] = controller + return action_controllers - def _responses_by_collection(self, application, ext_mgr): + def _response_ext_controllers(self, application, ext_mgr, mapper): """ Return a dict of ResponseExtensionController objects by collection """ response_ext_controllers = {} for resp_ext in ext_mgr.get_response_extensions(): - if not resp_ext.url_route in response_ext_controllers.keys(): + if not resp_ext.key in response_ext_controllers.keys(): controller = ResponseExtensionController(application) - response_ext_controllers[resp_ext.url_route] = controller + mapper.connect(resp_ext.url_route + '.:(format)', + action='process', + controller=controller, + conditions=resp_ext.conditions) + + mapper.connect(resp_ext.url_route, + action='process', + controller=controller, + conditions=resp_ext.conditions) + response_ext_controllers[resp_ext.key] = controller + return response_ext_controllers def __init__(self, application, ext_mgr=None): @@ -159,38 +179,20 @@ class ExtensionMiddleware(wsgi.Middleware): parent_resource=resource.parent) # extended actions - action_controllers = self._actions_by_collection(application, ext_mgr) + action_controllers = self._action_ext_controllers(application, ext_mgr, + mapper) for action in ext_mgr.get_actions(): - LOG.debug(_('Extended collection/action: %s/%s'), - action.collection, - action.action_name) + LOG.debug(_('Extended action: %s'), action.action_name) controller = action_controllers[action.collection] controller.add_action(action.action_name, action.handler) - mapper.connect("/%s/:(id)/action.:(format)" % action.collection, - action='action', - controller=controller, - conditions=dict(method=['POST'])) - mapper.connect("/%s/:(id)/action" % action.collection, - action='action', - controller=controller, - conditions=dict(method=['POST'])) - # extended responses - resp_controllers = self._responses_by_collection(application, ext_mgr) + resp_controllers = self._response_ext_controllers(application, ext_mgr, + mapper) for response_ext in ext_mgr.get_response_extensions(): - LOG.debug(_('Extended response: %s'), response_ext.url_route) - controller = resp_controllers[response_ext.url_route] + LOG.debug(_('Extended response: %s'), response_ext.key) + controller = resp_controllers[response_ext.key] controller.add_handler(response_ext.handler) - mapper.connect(response_ext.url_route + '.:(format)', - action='process', - controller=controller, - conditions=response_ext.conditions) - - mapper.connect(response_ext.url_route, - action='process', - controller=controller, - conditions=response_ext.conditions) self._router = routes.middleware.RoutesMiddleware(self._dispatch, mapper) @@ -322,10 +324,11 @@ class ResponseExtension(object): core nova OpenStack API controllers. """ - def __init__(self, url_route, method, handler): + def __init__(self, method, url_route, handler): self.url_route = url_route - self.conditions = dict(method=[method]) self.handler = handler + self.conditions = dict(method=[method]) + self.key = "%s-%s" % (method, url_route) class ActionExtension(object): -- cgit From f91cd67c403d7de54600eea0d91c223af0493788 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Wed, 16 Mar 2011 14:09:29 -0400 Subject: Comment update. --- nova/api/openstack/extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 4adfcfc5b..557b12fd9 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -345,7 +345,7 @@ class ActionExtension(object): class ResourceExtension(object): """ - ResourceExtension objects can be used to add add top level resources + ResourceExtension objects can be used to add top level resources to the OpenStack API in nova. """ -- cgit From 4d057c9c2df77816ead6f30fa2795148aa8148d3 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 16 Mar 2011 11:44:40 -0700 Subject: Refactored ZoneRedirect into ZoneChildHelper so ZoneManager can use this too. --- nova/api/zone_redirect.py | 79 ++++++++++++++++++++++++++--------------------- 1 file changed, 44 insertions(+), 35 deletions(-) (limited to 'nova/api') diff --git a/nova/api/zone_redirect.py b/nova/api/zone_redirect.py index fec1b1af3..0adf94046 100644 --- a/nova/api/zone_redirect.py +++ b/nova/api/zone_redirect.py @@ -28,6 +28,7 @@ import urllib from nova import exception from nova import log as logging from nova import wsgi +from nova.scheduler import api import novaclient.client as client import novaclient.exceptions as osexceptions @@ -41,6 +42,43 @@ except ImportError: LOG = logging.getLogger('server') +class RequestForwarder(api.ChildZoneHelper): + + def __init__(self, resource, method, body): + self.resource = resource + self.method = method + self.body = body + + def process(self, client, zone): + api_url = zone.api_url + LOG.debug(_("Zone redirect to: %(api_url)s, " % locals())) + try: + if self.method == 'GET': + response, body = client.get(self.resource, body=self.body) + elif self.method == 'POST': + response, body = client.post(self.resource, body=self.body) + elif self.method == 'PUT': + response, body = client.put(self.resource, body=self.body) + elif self.method == 'DELETE': + response, body = client.delete(self.resource, body=self.body) + except osexceptions.OpenStackException, e: + LOG.info(_("Zone returned error: %s ('%s', '%s')"), + e.code, e.message, e.details) + res = webob.Response() + res.status = "404" + return res + + status = response.status + LOG.debug(_("Zone %(api_url)s response: " + "%(response)s [%(status)s]/ %(body)s") % + locals()) + res = webob.Response() + res.status = response['status'] + res.content_type = response['content-type'] + res.body = json.dumps(body) + return res + + class ZoneRedirectMiddleware(wsgi.Middleware): """Catches Zone Routing exceptions and delegates the call to child zones.""" @@ -57,10 +95,8 @@ class ZoneRedirectMiddleware(wsgi.Middleware): # Todo(sandy): This only works for OpenStack API currently. # Needs to be broken out into a driver. - new_req = req.copy() - scheme, netloc, path, query, frag = \ - urlparse.urlsplit(new_req.path_qs) + urlparse.urlsplit(req.path_qs) query = urlparse.parse_qsl(query) query = [(key, value) for key, value in query if key != 'fresh'] query = urllib.urlencode(query) @@ -69,38 +105,11 @@ class ZoneRedirectMiddleware(wsgi.Middleware): m = re.search('/v\d+\.\d+/(.+)', url) resource = m.group(1) - for zone in e.zones: - LOG.debug(_("Zone redirect to:[url:%(api_url)s, " - "username:%(username)s]" - % dict(api_url=zone.api_url, - username=zone.username))) - - nova = client.OpenStackClient(zone.username, zone.password, - zone.api_url) - nova.authenticate() - try: - if req.method == 'GET': - response, body = nova.get(resource, body=new_req.body) - elif req.method == 'POST': - response, body = nova.post(resource, body=new_req.body) - elif req.method == 'PUT': - response, body = nova.put(resource, body=new_req.body) - elif req.method == 'DELETE': - response, body = nova.delete(resource, - body=new_req.body) - except osexceptions.OpenStackException, e: - LOG.info(_("Zone returned error: %s ('%s', '%s')"), - e.code, e.message, e.details) - continue - - LOG.debug(_("Zone Response: %s [%s]/ %s"), response, - response.status, body) - if response.status == 200: - res = webob.Response() - res.status = response['status'] - res.content_type = response['content-type'] - res.body = json.dumps(body) - return res + forwarder = RequestForwarder(resource, req.method, req.body) + for result in forwarder.start(e.zones): + # Todo(sandy): We need to aggregate multiple successes. + if result.status_int == 200: + return result LOG.debug(_("Zone Redirect Middleware returning 404 ...")) res = webob.Response() -- cgit From 703e680aa6d0da1953ec6f8ae3a6aa66dc9fad7e Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Wed, 16 Mar 2011 16:13:24 -0700 Subject: Fix the errors that pylint was reporting on this file This was meant more as a test of whether pylint was now returning false-positives. It looks like the bugs it's reporting are at least partially real. --- nova/api/openstack/servers.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 3ecd4fb01..dfaf35128 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -25,8 +25,9 @@ from nova import compute from nova import exception from nova import flags from nova import log as logging -from nova import wsgi +from nova import quota from nova import utils +from nova import wsgi from nova.api.openstack import common from nova.api.openstack import faults from nova.auth import manager as auth_manager @@ -188,7 +189,7 @@ class Controller(wsgi.Controller): key_data=key_data, metadata=metadata, injected_files=injected_files) - except QuotaError as error: + except quota.QuotaError as error: self._handle_quota_error(error) server = _translate_keys(instances[0]) @@ -238,7 +239,7 @@ class Controller(wsgi.Controller): injected_files.append((path, contents)) return injected_files - def _handle_quota_errors(self, error): + def _handle_quota_error(self, error): """ Reraise quota errors as api-specific http exceptions """ -- cgit From a1e2959312b51757653447de3e8c9e92029da6fd Mon Sep 17 00:00:00 2001 From: Justin Santa Barbara Date: Wed, 16 Mar 2011 16:23:31 -0700 Subject: Fix a few of the more obvious non-errors while we're in here --- nova/api/openstack/servers.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index dfaf35128..42cf693de 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -15,11 +15,10 @@ import base64 import hashlib -import json import traceback -from xml.dom import minidom from webob import exc +from xml.dom import minidom from nova import compute from nova import exception @@ -33,7 +32,6 @@ from nova.api.openstack import faults from nova.auth import manager as auth_manager from nova.compute import instance_types from nova.compute import power_state -import nova.api.openstack LOG = logging.getLogger('server') @@ -270,7 +268,7 @@ class Controller(wsgi.Controller): update_dict['admin_pass'] = inst_dict['server']['adminPass'] try: self.compute_api.set_admin_password(ctxt, id) - except exception.TimeoutException, e: + except exception.TimeoutException: return exc.HTTPRequestTimeout() if 'name' in inst_dict['server']: update_dict['display_name'] = inst_dict['server']['name'] -- cgit From c9158dfcf4efd2cf22df9aed7b1bb01e037e8eb2 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 16 Mar 2011 19:04:27 -0700 Subject: moved scheduler API check into db.api decorator --- nova/api/zone_redirect.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/zone_redirect.py b/nova/api/zone_redirect.py index 0adf94046..4fe255c99 100644 --- a/nova/api/zone_redirect.py +++ b/nova/api/zone_redirect.py @@ -43,7 +43,7 @@ LOG = logging.getLogger('server') class RequestForwarder(api.ChildZoneHelper): - + """Worker for sending an OpenStack Request to each child zone.""" def __init__(self, resource, method, body): self.resource = resource self.method = method @@ -98,10 +98,13 @@ class ZoneRedirectMiddleware(wsgi.Middleware): scheme, netloc, path, query, frag = \ urlparse.urlsplit(req.path_qs) query = urlparse.parse_qsl(query) + # Remove any cache busters from old novaclient calls ... query = [(key, value) for key, value in query if key != 'fresh'] query = urllib.urlencode(query) url = urlparse.urlunsplit((scheme, netloc, path, query, frag)) + # Strip off the API version, since this is given when the + # child zone was added. m = re.search('/v\d+\.\d+/(.+)', url) resource = m.group(1) -- cgit From cfe77c1236b68aa96dd85503582e08a07a23f77f Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 16 Mar 2011 19:21:32 -0700 Subject: cleanup --- nova/api/openstack/servers.py | 1 - 1 file changed, 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index ffcbe628c..85999764f 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -343,7 +343,6 @@ class Controller(wsgi.Controller): """ Permit Admins to Pause the server. """ ctxt = req.environ['nova.context'] try: - LOG.debug(_("*** Compute.api::pause %s"), id) self.compute_api.pause(ctxt, id) except: readable = traceback.format_exc() -- cgit From 609a912fa8a816c1f47140489dcc1131356cd67c Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 16 Mar 2011 19:26:54 -0700 Subject: pep8 --- nova/api/zone_redirect.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'nova/api') diff --git a/nova/api/zone_redirect.py b/nova/api/zone_redirect.py index 4fe255c99..7ebae1401 100644 --- a/nova/api/zone_redirect.py +++ b/nova/api/zone_redirect.py @@ -48,7 +48,7 @@ class RequestForwarder(api.ChildZoneHelper): self.resource = resource self.method = method self.body = body - + def process(self, client, zone): api_url = zone.api_url LOG.debug(_("Zone redirect to: %(api_url)s, " % locals())) @@ -89,12 +89,12 @@ class ZoneRedirectMiddleware(wsgi.Middleware): return req.get_response(self.application) except exception.ZoneRouteException as e: if not e.zones: - exc = webob.exc.HTTPInternalServerError(explanation= - _("No zones to reroute to.")) + exc = webob.exc.HTTPInternalServerError(explanation=_( + "No zones to reroute to.")) return faults.Fault(exc) # Todo(sandy): This only works for OpenStack API currently. - # Needs to be broken out into a driver. + # Needs to be broken out into a driver. scheme, netloc, path, query, frag = \ urlparse.urlsplit(req.path_qs) query = urlparse.parse_qsl(query) -- cgit From abc6c82449dfc46a33dcd8190840e51f44b5b930 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Thu, 17 Mar 2011 07:30:22 -0700 Subject: Replaced capability flags with List --- nova/api/openstack/zones.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py index 547920901..ebfc7743c 100644 --- a/nova/api/openstack/zones.py +++ b/nova/api/openstack/zones.py @@ -71,9 +71,9 @@ class Controller(wsgi.Controller): items = api.API.get_zone_capabilities(req.environ['nova.context']) zone = dict(name=FLAGS.zone_name) - caps = FLAGS.zone_capabilities.split(';') + caps = FLAGS.zone_capabilities for cap in caps: - key_values = cap.split(':') + key_values = cap.split('=') zone[key_values[0]] = key_values[1] for item, (min_value, max_value) in items.iteritems(): zone[item] = "%s,%s" % (min_value, max_value) -- cgit From 41c097000c1eeb4f1532b22f136c383b8174e6cc Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 17 Mar 2011 11:35:32 -0400 Subject: Add tests and code to handle multiple ResponseExtension objects. --- nova/api/openstack/extensions.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 557b12fd9..23181d2a6 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -66,9 +66,20 @@ class ResponseExtensionController(wsgi.Controller): def process(self, req, *args, **kwargs): res = req.get_response(self.application) + content_type = req.best_match_content_type() # currently response handlers are un-ordered for handler in self.handlers: - return handler(res) + res=handler(res) + try: + body = res.body + headers = res.headers + except AttributeError: + body = self._serialize(res, content_type) + headers={"Content-Type": content_type} + res = webob.Response() + res.body = body + res.headers = headers + return res class ExtensionController(wsgi.Controller): -- cgit From acb7a7355055e04b9bb05fbba5f6590e57d681fa Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Fri, 18 Mar 2011 00:18:55 -0400 Subject: Support for markers for pagination as defined in the 1.1 spec. --- nova/api/openstack/common.py | 28 ++++++++++++++++++++++++++++ nova/api/openstack/servers.py | 8 +++++++- 2 files changed, 35 insertions(+), 1 deletion(-) (limited to 'nova/api') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index b224cbfb4..8106f841b 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -55,6 +55,34 @@ def limited(items, request, max_limit=1000): return items[offset:range_end] +def limited_by_marker(items, request, max_limit=1000): + ''' Return a slice of items according to requested marker and limit. ''' + + marker = request.GET.get('marker') + + try: + limit = int(request.GET.get('limit', max_limit)) + except ValueError: + raise webob.exc.HTTPBadRequest(_('limit param must be an integer')) + + if limit < 0: + raise webob.exc.HTTPBadRequest(_('limit param must be positive')) + + limit = min(max_limit, limit or max_limit) + start_index = 0 + if marker != None: + found_it = False + for i, item in enumerate(items): + if str(item['id']) == marker: + start_index = i + found_it = True + break + if not found_it: + raise webob.exc.HTTPBadRequest(_('marker not found')) + range_end = start_index + limit + return items[start_index:range_end] + + def get_image_id_from_image_hash(image_service, context, image_hash): """Given an Image ID Hash, return an objectstore Image ID. diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index e3141934b..461bf5989 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -81,7 +81,7 @@ class Controller(wsgi.Controller): builder - the response model builder """ instance_list = self.compute_api.get_all(req.environ['nova.context']) - limited_list = common.limited(instance_list, req) + limited_list = self._limit_items(instance_list, req) builder = self._get_view_builder(req) servers = [builder.build(inst, is_detail)['server'] for inst in limited_list] @@ -528,6 +528,9 @@ class ControllerV10(Controller): def _get_addresses_view_builder(self, req): return nova.api.openstack.views.addresses.ViewBuilderV10(req) + def _limit_items(self, items, req): + return common.limited(items, req) + class ControllerV11(Controller): def _image_id_from_req_data(self, data): @@ -551,6 +554,9 @@ class ControllerV11(Controller): def _get_addresses_view_builder(self, req): return nova.api.openstack.views.addresses.ViewBuilderV11(req) + def _limit_items(self, items, req): + return common.limited_by_marker(items, req) + class ServerCreateRequestXMLDeserializer(object): """ -- cgit From 70e8b431334989ad067f0a5543aea408b7186c5c Mon Sep 17 00:00:00 2001 From: Brian Lamar Date: Fri, 18 Mar 2011 10:34:08 -0400 Subject: Fixed 'Undefined variable' errors generated by pylint (E0602). --- nova/api/openstack/accounts.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/accounts.py b/nova/api/openstack/accounts.py index 2510ffb61..86066fa20 100644 --- a/nova/api/openstack/accounts.py +++ b/nova/api/openstack/accounts.py @@ -14,6 +14,7 @@ # under the License. import common +import webob.exc from nova import exception from nova import flags @@ -51,10 +52,10 @@ class Controller(wsgi.Controller): raise exception.NotAuthorized(_("Not admin user.")) def index(self, req): - raise faults.Fault(exc.HTTPNotImplemented()) + raise faults.Fault(webob.exc.HTTPNotImplemented()) def detail(self, req): - raise faults.Fault(exc.HTTPNotImplemented()) + raise faults.Fault(webob.exc.HTTPNotImplemented()) def show(self, req, id): """Return data about the given account id""" @@ -69,7 +70,7 @@ class Controller(wsgi.Controller): def create(self, req): """We use update with create-or-update semantics because the id comes from an external source""" - raise faults.Fault(exc.HTTPNotImplemented()) + raise faults.Fault(webob.exc.HTTPNotImplemented()) def update(self, req, id): """This is really create or update.""" -- cgit From ef33d6bde27276fb4c93ed6bbcb972977f03a370 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Fri, 18 Mar 2011 09:21:08 -0700 Subject: results --- 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 9f14a6b82..49f714d47 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -130,7 +130,7 @@ class Controller(wsgi.Controller): try: LOG.debug(_("***SHOW")) instance = self.compute_api.routing_get(req.environ['nova.context'], id) - LOG.debug(_("***SHOW")) + LOG.debug(_("***SHOW OUT %s" % instance)) return _translate_detail_keys(instance) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) -- cgit From 705020cc4acded862633aa5e02d5bb46c88dbc51 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Fri, 18 Mar 2011 11:46:27 -0700 Subject: api decorator --- nova/api/openstack/servers.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 49f714d47..17d620562 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -33,6 +33,7 @@ from nova.auth import manager as auth_manager from nova.compute import instance_types from nova.compute import power_state import nova.api.openstack +from nova.scheduler import api as scheduler_api LOG = logging.getLogger('server') @@ -125,6 +126,7 @@ class Controller(wsgi.Controller): res = [entity_maker(inst)['server'] for inst in limited_list] return dict(servers=res) + @scheduler_api.redirect_handler def show(self, req, id): """ Returns server details by server id """ try: -- cgit From feb5c82e29303285d3f914c37116a59538fec28f Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Fri, 18 Mar 2011 12:23:57 -0700 Subject: fix ups --- nova/api/openstack/servers.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 17d620562..86414fab2 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -137,6 +137,7 @@ class Controller(wsgi.Controller): except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) + @scheduler_api.redirect_handler def delete(self, req, id): """ Destroys a server """ try: @@ -258,6 +259,7 @@ class Controller(wsgi.Controller): # if the original error is okay, just reraise it raise error + @scheduler_api.redirect_handler def update(self, req, id): """ Updates the server name or password """ if len(req.body) == 0: @@ -283,6 +285,7 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPNotFound()) return exc.HTTPNoContent() + @scheduler_api.redirect_handler def action(self, req, id): """Multi-purpose method used to reboot, rebuild, or resize a server""" @@ -348,6 +351,7 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() + @scheduler_api.redirect_handler def lock(self, req, id): """ lock the instance with id @@ -363,6 +367,7 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() + @scheduler_api.redirect_handler def unlock(self, req, id): """ unlock the instance with id @@ -378,6 +383,7 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() + @scheduler_api.redirect_handler def get_lock(self, req, id): """ return the boolean state of (instance with id)'s lock @@ -392,6 +398,7 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() + @scheduler_api.redirect_handler def reset_network(self, req, id): """ Reset networking on an instance (admin only). @@ -406,6 +413,7 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() + @scheduler_api.redirect_handler def inject_network_info(self, req, id): """ Inject network info for an instance (admin only). @@ -420,6 +428,7 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() + @scheduler_api.redirect_handler def pause(self, req, id): """ Permit Admins to Pause the server. """ ctxt = req.environ['nova.context'] @@ -431,6 +440,7 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() + @scheduler_api.redirect_handler def unpause(self, req, id): """ Permit Admins to Unpause the server. """ ctxt = req.environ['nova.context'] @@ -442,6 +452,7 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() + @scheduler_api.redirect_handler def suspend(self, req, id): """permit admins to suspend the server""" context = req.environ['nova.context'] @@ -453,6 +464,7 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() + @scheduler_api.redirect_handler def resume(self, req, id): """permit admins to resume the server from suspend""" context = req.environ['nova.context'] @@ -464,6 +476,7 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() + @scheduler_api.redirect_handler def rescue(self, req, id): """Permit users to rescue the server.""" context = req.environ["nova.context"] @@ -475,6 +488,7 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() + @scheduler_api.redirect_handler def unrescue(self, req, id): """Permit users to unrescue the server.""" context = req.environ["nova.context"] @@ -486,6 +500,7 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() + @scheduler_api.redirect_handler def get_ajax_console(self, req, id): """ Returns a url to an instance's ajaxterm console. """ try: @@ -495,6 +510,7 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPNotFound()) return exc.HTTPAccepted() + @scheduler_api.redirect_handler def diagnostics(self, req, id): """Permit Admins to retrieve server diagnostics.""" ctxt = req.environ["nova.context"] -- cgit From 8f0b60f598c28b2f558f3ecdaa2f9604926393e6 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Mon, 21 Mar 2011 07:49:58 -0700 Subject: remove scheduler.api.API. naming changes. --- nova/api/openstack/zones.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py index ebfc7743c..d129cf34f 100644 --- a/nova/api/openstack/zones.py +++ b/nova/api/openstack/zones.py @@ -53,7 +53,7 @@ class Controller(wsgi.Controller): """Return all zones in brief""" # Ask the ZoneManager in the Scheduler for most recent data, # or fall-back to the database ... - items = api.API.get_zone_list(req.environ['nova.context']) + items = api.get_zone_list(req.environ['nova.context']) if not items: items = db.zone_get_all(req.environ['nova.context']) @@ -68,7 +68,7 @@ class Controller(wsgi.Controller): def info(self, req): """Return name and capabilities for this zone.""" - items = api.API.get_zone_capabilities(req.environ['nova.context']) + items = api.get_zone_capabilities(req.environ['nova.context']) zone = dict(name=FLAGS.zone_name) caps = FLAGS.zone_capabilities -- cgit From f988df6c6f29d6c885d44c6768297aaf489faf34 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 21 Mar 2011 13:58:39 -0400 Subject: Fix pep8 issues in nova/api/openstack/extensions.py. --- nova/api/openstack/extensions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 23181d2a6..f881dbde7 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -69,13 +69,13 @@ class ResponseExtensionController(wsgi.Controller): content_type = req.best_match_content_type() # currently response handlers are un-ordered for handler in self.handlers: - res=handler(res) + res = handler(res) try: body = res.body headers = res.headers except AttributeError: body = self._serialize(res, content_type) - headers={"Content-Type": content_type} + headers = {"Content-Type": content_type} res = webob.Response() res.body = body res.headers = headers -- cgit From ff2d6dc656c03b8aeab5e50c5d39ca9dcde9b9b1 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 21 Mar 2011 14:00:39 -0400 Subject: Updated comment per the extension naming convention we actually use. --- nova/api/openstack/extensions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index f881dbde7..9d98d849a 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -303,9 +303,9 @@ class ExtensionManager(object): def _load_extensions(self): """ Load extensions from the configured path. The extension name is - constructed from the camel cased module_name + 'Extension'. If your - extension module was named widgets.py the extension class within that - module should be 'WidgetsExtension'. + constructed from the module_name. If your extension module was named + widgets.py the extension class within that module should be + 'Widgets'. See nova/tests/api/openstack/extensions/foxinsocks.py for an example extension implementation. -- cgit From 97e8f300af824145c8b92949ccbdfe81c0d7ca95 Mon Sep 17 00:00:00 2001 From: Josh Kleinpeter Date: Tue, 22 Mar 2011 12:33:34 -0500 Subject: Changed default for disabled on service_get_all to None. Changed calls to service_get_all so that the results should still be as they previously were. --- nova/api/ec2/admin.py | 2 +- nova/api/ec2/cloud.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/admin.py b/nova/api/ec2/admin.py index d9a4ef999..3ae29d8ce 100644 --- a/nova/api/ec2/admin.py +++ b/nova/api/ec2/admin.py @@ -299,7 +299,7 @@ class AdminController(object): * Volume (up, down, None) * Volume Count """ - services = db.service_get_all(context) + services = db.service_get_all(context, False) now = datetime.datetime.utcnow() hosts = [] rv = [] diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index e257e44e7..2afcea77c 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -196,7 +196,7 @@ class CloudController(object): def _describe_availability_zones(self, context, **kwargs): ctxt = context.elevated() - enabled_services = db.service_get_all(ctxt) + enabled_services = db.service_get_all(ctxt, False) disabled_services = db.service_get_all(ctxt, True) available_zones = [] for zone in [service.availability_zone for service @@ -221,7 +221,7 @@ class CloudController(object): rv = {'availabilityZoneInfo': [{'zoneName': 'nova', 'zoneState': 'available'}]} - services = db.service_get_all(context) + services = db.service_get_all(context, False) now = datetime.datetime.utcnow() hosts = [] for host in [service['host'] for service in services]: -- cgit From 2a38aa7583be37ece6c42ba9307c2db0232dbed3 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Tue, 22 Mar 2011 10:37:56 -0700 Subject: Whoops --- nova/api/openstack/servers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index be423c572..199d89c6d 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -92,9 +92,10 @@ class Controller(wsgi.Controller): """ Returns server details by server id """ try: LOG.debug(_("***SHOW")) - instance = self.compute_api.get(req.environ['nova.context'], id) - builder = servers_views.get_view_builder(req) + instance = self.compute_api.routing_get( + req.environ['nova.context'], id) LOG.debug(_("***SHOW OUT %s" % instance)) + builder = servers_views.get_view_builder(req) return builder.build(instance, is_detail=True) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) -- cgit From 789fcb46915dce5fa533357ac462040ec6aa8968 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Tue, 22 Mar 2011 20:26:45 +0000 Subject: Adding BASE_IMAGE_ATTRS to ImageService --- nova/api/openstack/images.py | 57 +++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 27 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 0c56b5f0d..97e62c22d 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -119,43 +119,46 @@ def _translate_s3_like_images(image_metadata): def _translate_from_image_service_to_api(image_metadata): """Translate from ImageService to OpenStack API style attribute names - This involves 3 steps: + This involves 4 steps: - 1. Translating required keys + 1. Filter out attributes that the OpenStack API doesn't need - 2. Translating optional keys (ex. progress, serverId) + 2. Translate from base image attributes from names used by + BaseImageService to names used by OpenStack API - 3. Formatting values according to API spec (for example dates must + 3. Add in any image properties + + 4. Format values according to API spec (for example dates must look like "2010-08-10T12:00:00Z") """ service_metadata = image_metadata.copy() - api_metadata = {} - - # 1. Translate required keys - required_image_service2api = { - 'id': 'id', - 'name': 'name', - 'updated_at': 'updated', - 'created_at': 'created', - 'status': 'status'} - for service_attr, api_attr in required_image_service2api.items(): - api_metadata[api_attr] = service_metadata[service_attr] - - # 2. Translate optional keys - optional_image_service2api = {'instance_id': 'serverId'} - for service_attr, api_attr in optional_image_service2api.items(): - if service_attr in service_metadata: - api_metadata[api_attr] = service_metadata[service_attr] - - # 2a. Progress special case + properties = service_metadata.pop('properties', {}) + + # 1. Filter out unecessary attributes + api_keys = ['id', 'name', 'updated_at', 'created_at', 'status'] + api_metadata = utils.partition_dict(service_metadata, api_keys)[0] + + # 2. Translate base image attributes + api_map = {'updated_at': 'updated', 'created_at': 'created'} + api_metadata = utils.map_dict_keys(api_metadata, api_map) + + # 3. Add in any image properties + # 3a. serverId is used for backups and snapshots + try: + api_metadata['serverId'] = int(properties['instance_id']) + except KeyError: + pass # skip if it's not present + except ValueError: + pass # skip if it's not an integer + + # 3b. Progress special case # TODO(sirp): ImageService doesn't have a notion of progress yet, so for # now just fake it if service_metadata['status'] == 'saving': api_metadata['progress'] = 0 - # 3. Format values - - # 3a. Format Image Status (API requires uppercase) + # 4. Format values + # 4a. Format Image Status (API requires uppercase) status_service2api = {'queued': 'QUEUED', 'preparing': 'PREPARING', 'saving': 'SAVING', @@ -163,7 +166,7 @@ def _translate_from_image_service_to_api(image_metadata): 'killed': 'FAILED'} api_metadata['status'] = status_service2api[api_metadata['status']] - # 3b. Format timestamps + # 4b. Format timestamps def _format_timestamp(dt_str): """Return a timestamp formatted for OpenStack API -- cgit From 7d97f700811468303c21159454db64e84f71a2a0 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Tue, 22 Mar 2011 23:49:24 +0000 Subject: Refactored out _safe_translate code --- nova/api/openstack/images.py | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 97e62c22d..2d2f67fe1 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -193,6 +193,21 @@ def _translate_from_image_service_to_api(image_metadata): return api_metadata +def _safe_translate(image_metadata): + """Translate attributes for OpenStack API, temporary workaround for + S3ImageService attribute leakage. + """ + # FIXME(sirp): The S3ImageService appears to be leaking implementation + # details, including its internal attribute names, and internal + # `status` values. Working around it for now. + s3_like_image = ('imageId' in image_metadata) + if s3_like_image: + translate = _translate_s3_like_images + else: + translate = _translate_from_image_service_to_api + return translate(image_metadata) + + class Controller(wsgi.Controller): _serialization_metadata = { @@ -221,30 +236,18 @@ class Controller(wsgi.Controller): req.environ['nova.context']) service_image_metas = common.limited(service_image_metas, req) - - # FIXME(sirp): The S3ImageService appears to be leaking implementation - # details, including its internal attribute names, and internal - # `status` values. Working around it for now. - s3_like_image = (service_image_metas and - ('imageId' in service_image_metas[0])) - if s3_like_image: - translate = _translate_s3_like_images - else: - translate = _translate_from_image_service_to_api - - api_image_metas = [translate(service_image_meta) + api_image_metas = [_safe_translate(service_image_meta) for service_image_meta in service_image_metas] - return dict(images=api_image_metas) def show(self, req, id): """Return data about the given image id""" image_id = common.get_image_id_from_image_hash(self._service, req.environ['nova.context'], id) - - image = self._service.show(req.environ['nova.context'], image_id) - _convert_image_id_to_hash(image) - return dict(image=image) + service_image_meta = self._service.show( + req.environ['nova.context'], image_id) + api_image_meta = _safe_translate(service_image_meta) + return dict(image=api_image_meta) def delete(self, req, id): # Only public images are supported for now. @@ -255,11 +258,10 @@ class Controller(wsgi.Controller): env = self._deserialize(req.body, req.get_content_type()) instance_id = env["image"]["serverId"] name = env["image"]["name"] - - image_meta = compute.API().snapshot( + service_image_meta = compute.API().snapshot( context, instance_id, name) - - return dict(image=image_meta) + api_image_meta = _safe_translate(service_image_meta) + return dict(image=api_image_meta) def update(self, req, id): # Users may not modify public images, and that's all that -- cgit From 209da18033a49062bbcfaf7739db5959be87b142 Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Tue, 22 Mar 2011 20:36:49 -0700 Subject: pep8 and fixed up zone-list --- nova/api/openstack/servers.py | 2 -- nova/api/openstack/zones.py | 18 +++++++----------- 2 files changed, 7 insertions(+), 13 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 199d89c6d..db6a1de97 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -91,10 +91,8 @@ class Controller(wsgi.Controller): def show(self, req, id): """ Returns server details by server id """ try: - LOG.debug(_("***SHOW")) instance = self.compute_api.routing_get( req.environ['nova.context'], id) - LOG.debug(_("***SHOW OUT %s" % instance)) builder = servers_views.get_view_builder(req) return builder.build(instance, is_detail=True) except exception.NotFound: diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py index d129cf34f..6ce27e9a9 100644 --- a/nova/api/openstack/zones.py +++ b/nova/api/openstack/zones.py @@ -15,7 +15,6 @@ import common -from nova import db from nova import flags from nova import log as logging from nova import wsgi @@ -39,7 +38,8 @@ def _exclude_keys(item, keys): def _scrub_zone(zone): - return _filter_keys(zone, ('id', 'api_url')) + return _exclude_keys(zone, ('username', 'password', 'created_at', + 'deleted', 'deleted_at', 'updated_at')) class Controller(wsgi.Controller): @@ -54,12 +54,8 @@ class Controller(wsgi.Controller): # Ask the ZoneManager in the Scheduler for most recent data, # or fall-back to the database ... items = api.get_zone_list(req.environ['nova.context']) - if not items: - items = db.zone_get_all(req.environ['nova.context']) - items = common.limited(items, req) - items = [_exclude_keys(item, ['username', 'password']) - for item in items] + items = [_scrub_zone(item) for item in items] return dict(zones=items) def detail(self, req): @@ -82,23 +78,23 @@ class Controller(wsgi.Controller): def show(self, req, id): """Return data about the given zone id""" zone_id = int(id) - zone = db.zone_get(req.environ['nova.context'], zone_id) + zone = api.zone_get(req.environ['nova.context'], zone_id) return dict(zone=_scrub_zone(zone)) def delete(self, req, id): zone_id = int(id) - db.zone_delete(req.environ['nova.context'], zone_id) + api.zone_delete(req.environ['nova.context'], zone_id) return {} def create(self, req): context = req.environ['nova.context'] env = self._deserialize(req.body, req.get_content_type()) - zone = db.zone_create(context, env["zone"]) + zone = api.zone_create(context, env["zone"]) return dict(zone=_scrub_zone(zone)) def update(self, req, id): context = req.environ['nova.context'] env = self._deserialize(req.body, req.get_content_type()) zone_id = int(id) - zone = db.zone_update(context, zone_id, env["zone"]) + zone = api.zone_update(context, zone_id, env["zone"]) return dict(zone=_scrub_zone(zone)) -- cgit From ac475d05e6807804a74bca665563c7260523a733 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Wed, 23 Mar 2011 06:00:04 +0000 Subject: Small cleanup of openstack/images.py --- nova/api/openstack/images.py | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 2d2f67fe1..d914f5196 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -221,32 +221,28 @@ class Controller(wsgi.Controller): def index(self, req): """Return all public images in brief""" - items = self._service.index(req.environ['nova.context']) - items = common.limited(items, req) - items = [_filter_keys(item, ('id', 'name')) for item in items] - return dict(images=items) + context = req.environ['nova.context'] + image_metas = self._service.index(context) + image_metas = common.limited(image_metas, req) + return dict(images=image_metas) def detail(self, req): """Return all public images in detail""" - try: - service_image_metas = self._service.detail( - req.environ['nova.context']) - except NotImplementedError: - service_image_metas = self._service.index( - req.environ['nova.context']) - - service_image_metas = common.limited(service_image_metas, req) - api_image_metas = [_safe_translate(service_image_meta) - for service_image_meta in service_image_metas] + context = req.environ['nova.context'] + image_metas = self._service.detail(context) + image_metas = common.limited(image_metas, req) + api_image_metas = [_safe_translate(image_meta) + for image_meta in image_metas] return dict(images=api_image_metas) def show(self, req, id): """Return data about the given image id""" - image_id = common.get_image_id_from_image_hash(self._service, - req.environ['nova.context'], id) - service_image_meta = self._service.show( - req.environ['nova.context'], image_id) - api_image_meta = _safe_translate(service_image_meta) + context = req.environ['nova.context'] + image_id = common.get_image_id_from_image_hash( + self._service, req.environ['nova.context'], id) + + image_meta = self._service.show(context, image_id) + api_image_meta = _safe_translate(image_meta) return dict(image=api_image_meta) def delete(self, req, id): @@ -258,9 +254,9 @@ class Controller(wsgi.Controller): env = self._deserialize(req.body, req.get_content_type()) instance_id = env["image"]["serverId"] name = env["image"]["name"] - service_image_meta = compute.API().snapshot( + image_meta = compute.API().snapshot( context, instance_id, name) - api_image_meta = _safe_translate(service_image_meta) + api_image_meta = _safe_translate(image_meta) return dict(image=api_image_meta) def update(self, req, id): -- cgit From 32e1c38ef9539be6f914adc69f30e409b159a9e6 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Wed, 23 Mar 2011 06:55:28 +0000 Subject: Adding tests for owned and non-existent images --- nova/api/openstack/images.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index d914f5196..ab286bb45 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -20,6 +20,7 @@ import datetime from webob import exc from nova import compute +from nova import exception from nova import flags from nova import log from nova import utils @@ -238,8 +239,11 @@ class Controller(wsgi.Controller): def show(self, req, id): """Return data about the given image id""" context = req.environ['nova.context'] - image_id = common.get_image_id_from_image_hash( - self._service, req.environ['nova.context'], id) + try: + image_id = common.get_image_id_from_image_hash( + self._service, context, id) + except exception.NotFound: + raise faults.Fault(exc.HTTPNotFound()) image_meta = self._service.show(context, image_id) api_image_meta = _safe_translate(image_meta) -- cgit From 07af0c9653863575600986158b89ff6afa48996e Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Wed, 23 Mar 2011 07:30:01 +0000 Subject: Use subset_dict --- 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 ab286bb45..19edcf194 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -137,7 +137,7 @@ def _translate_from_image_service_to_api(image_metadata): # 1. Filter out unecessary attributes api_keys = ['id', 'name', 'updated_at', 'created_at', 'status'] - api_metadata = utils.partition_dict(service_metadata, api_keys)[0] + api_metadata = utils.subset_dict(service_metadata, api_keys) # 2. Translate base image attributes api_map = {'updated_at': 'updated', 'created_at': 'created'} -- cgit From 05e6f82aa971606f7d33fb1de8f2c1c170d030de Mon Sep 17 00:00:00 2001 From: Sandy Walsh Date: Wed, 23 Mar 2011 12:31:15 -0700 Subject: indenting cleanup --- nova/api/openstack/zones.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/zones.py b/nova/api/openstack/zones.py index d129cf34f..d4a59993b 100644 --- a/nova/api/openstack/zones.py +++ b/nova/api/openstack/zones.py @@ -17,7 +17,6 @@ import common from nova import db from nova import flags -from nova import log as logging from nova import wsgi from nova.scheduler import api @@ -73,8 +72,8 @@ class Controller(wsgi.Controller): zone = dict(name=FLAGS.zone_name) caps = FLAGS.zone_capabilities for cap in caps: - key_values = cap.split('=') - zone[key_values[0]] = key_values[1] + key, value = cap.split('=') + zone[key] = value for item, (min_value, max_value) in items.iteritems(): zone[item] = "%s,%s" % (min_value, max_value) return dict(zone=zone) -- cgit From 1ad0faf980ac89e904a246f1dfeddf51a21fd740 Mon Sep 17 00:00:00 2001 From: Naveed Massjouni Date: Thu, 24 Mar 2011 13:48:04 -0400 Subject: Paginated results should not include the item starting at marker. Improved implementation of common.limited_by_marker as suggested by Matt Dietz. Added flag osapi_max_limit. --- nova/api/openstack/common.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) (limited to 'nova/api') diff --git a/nova/api/openstack/common.py b/nova/api/openstack/common.py index f85d1df9d..f598ac824 100644 --- a/nova/api/openstack/common.py +++ b/nova/api/openstack/common.py @@ -20,9 +20,11 @@ from urlparse import urlparse import webob from nova import exception +from nova import flags +FLAGS = flags.FLAGS -def limited(items, request, max_limit=1000): +def limited(items, request, max_limit=FLAGS.osapi_max_limit): """ Return a slice of items according to requested offset and limit. @@ -56,10 +58,13 @@ def limited(items, request, max_limit=1000): return items[offset:range_end] -def limited_by_marker(items, request, max_limit=1000): - ''' Return a slice of items according to requested marker and limit. ''' +def limited_by_marker(items, request, max_limit=FLAGS.osapi_max_limit): + """Return a slice of items according to the requested marker and limit.""" - marker = request.GET.get('marker') + try: + marker = int(request.GET.get('marker', 0)) + except ValueError: + raise webob.exc.HTTPBadRequest(_('marker param must be an integer')) try: limit = int(request.GET.get('limit', max_limit)) @@ -69,17 +74,16 @@ def limited_by_marker(items, request, max_limit=1000): if limit < 0: raise webob.exc.HTTPBadRequest(_('limit param must be positive')) - limit = min(max_limit, limit or max_limit) + limit = min(max_limit, limit) start_index = 0 - if marker != None: - found_it = False + if marker: + start_index = -1 for i, item in enumerate(items): - if str(item['id']) == marker: - start_index = i - found_it = True + if item['id'] == marker: + start_index = i + 1 break - if not found_it: - raise webob.exc.HTTPBadRequest(_('marker not found')) + if start_index < 0: + raise webob.exc.HTTPBadRequest(_('marker [%s] not found' % marker)) range_end = start_index + limit return items[start_index:range_end] -- cgit From c7ccbd7a16a546cbd0717427772691ce7d8b4da6 Mon Sep 17 00:00:00 2001 From: termie Date: Thu, 24 Mar 2011 12:42:46 -0700 Subject: support volume and network in the direct api --- nova/api/ec2/cloud.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 2afcea77c..5d31d71d3 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -541,7 +541,7 @@ class CloudController(object): volumes = [] for ec2_id in volume_id: internal_id = ec2utils.ec2_id_to_id(ec2_id) - volume = self.volume_api.get(context, internal_id) + volume = self.volume_api.get(context, volume_id=internal_id) volumes.append(volume) else: volumes = self.volume_api.get_all(context) @@ -585,9 +585,11 @@ class CloudController(object): def create_volume(self, context, size, **kwargs): LOG.audit(_("Create volume of %s GB"), size, context=context) - volume = self.volume_api.create(context, size, - kwargs.get('display_name'), - kwargs.get('display_description')) + volume = self.volume_api.create( + context, + size=size, + name=kwargs.get('display_name'), + description=kwargs.get('display_description')) # TODO(vish): Instance should be None at db layer instead of # trying to lazy load, but for now we turn it into # a dict to avoid an error. @@ -606,7 +608,7 @@ class CloudController(object): if field in kwargs: changes[field] = kwargs[field] if changes: - self.volume_api.update(context, volume_id, kwargs) + self.volume_api.update(context, volume_id=volume_id, fields=changes) return True def attach_volume(self, context, volume_id, instance_id, device, **kwargs): @@ -619,7 +621,7 @@ class CloudController(object): instance_id=instance_id, volume_id=volume_id, device=device) - volume = self.volume_api.get(context, volume_id) + volume = self.volume_api.get(context, volume_id=volume_id) return {'attachTime': volume['attach_time'], 'device': volume['mountpoint'], 'instanceId': ec2utils.id_to_ec2_id(instance_id), @@ -630,7 +632,7 @@ class CloudController(object): def detach_volume(self, context, volume_id, **kwargs): volume_id = ec2utils.ec2_id_to_id(volume_id) LOG.audit(_("Detach volume %s"), volume_id, context=context) - volume = self.volume_api.get(context, volume_id) + volume = self.volume_api.get(context, volume_id=volume_id) instance = self.compute_api.detach_volume(context, volume_id=volume_id) return {'attachTime': volume['attach_time'], 'device': volume['mountpoint'], @@ -768,7 +770,7 @@ class CloudController(object): def release_address(self, context, public_ip, **kwargs): LOG.audit(_("Release address %s"), public_ip, context=context) - self.network_api.release_floating_ip(context, public_ip) + self.network_api.release_floating_ip(context, address=public_ip) return {'releaseResponse': ["Address released."]} def associate_address(self, context, instance_id, public_ip, **kwargs): @@ -782,7 +784,7 @@ class CloudController(object): def disassociate_address(self, context, public_ip, **kwargs): LOG.audit(_("Disassociate address %s"), public_ip, context=context) - self.network_api.disassociate_floating_ip(context, public_ip) + self.network_api.disassociate_floating_ip(context, address=public_ip) return {'disassociateResponse': ["Address disassociated."]} def run_instances(self, context, **kwargs): -- cgit From ef5c9e11595a00de468783adbb60cfbc2cbbf13d Mon Sep 17 00:00:00 2001 From: termie Date: Thu, 24 Mar 2011 12:42:46 -0700 Subject: add Limited, an API limiting/versioning wrapper --- nova/api/direct.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) (limited to 'nova/api') diff --git a/nova/api/direct.py b/nova/api/direct.py index dfca250e0..1011091a6 100644 --- a/nova/api/direct.py +++ b/nova/api/direct.py @@ -211,6 +211,42 @@ class ServiceWrapper(wsgi.Controller): return result +class Limited(object): + __notdoc = """Limit the available methods on a given object. + + (Not a docstring so that the docstring can be conditionally overriden.) + + Useful when defining a public API that only exposes a subset of an + internal API. + + Expected usage of this class is to define a subclass that lists the allowed + methods in the 'allowed' variable. + + Additionally where appropriate methods can be added or overwritten, for + example to provide backwards compatibility. + + """ + + _allowed = None + + def __init__(self, proxy): + self._proxy = proxy + if not self.__doc__: + self.__doc__ = proxy.__doc__ + if not self._allowed: + self._allowed = [] + + def __getattr__(self, key): + """Only return methods that are named in self._allowed.""" + if key not in self._allowed: + raise AttributeError() + return getattr(self._proxy, key) + + def __dir__(self): + """Only return methods that are named in self._allowed.""" + return [x for x in dir(self._proxy) if x in self._allowed] + + class Proxy(object): """Pretend a Direct API endpoint is an object.""" def __init__(self, app, prefix=None): -- cgit From 5c03ade2ee82350d845c8306d5aab9eda3073137 Mon Sep 17 00:00:00 2001 From: termie Date: Thu, 24 Mar 2011 12:42:47 -0700 Subject: add some more docs to direct.py --- nova/api/direct.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'nova/api') diff --git a/nova/api/direct.py b/nova/api/direct.py index 1011091a6..2e158e89e 100644 --- a/nova/api/direct.py +++ b/nova/api/direct.py @@ -225,6 +225,10 @@ class Limited(object): Additionally where appropriate methods can be added or overwritten, for example to provide backwards compatibility. + The wrapping approach has been chosen so that the wrapped API can maintain + its own internal consistency, for example if it calls "self.create" it + should get its own create method rather than anything we do here. + """ _allowed = None -- cgit From 4a6db815b01c71076bae96c155396e5adbe8af90 Mon Sep 17 00:00:00 2001 From: termie Date: Thu, 24 Mar 2011 12:42:47 -0700 Subject: better error handling and serialization --- nova/api/direct.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'nova/api') diff --git a/nova/api/direct.py b/nova/api/direct.py index 2e158e89e..bb2ace1c9 100644 --- a/nova/api/direct.py +++ b/nova/api/direct.py @@ -38,6 +38,7 @@ import routes import webob from nova import context +from nova import exception from nova import flags from nova import utils from nova import wsgi @@ -205,10 +206,12 @@ class ServiceWrapper(wsgi.Controller): # NOTE(vish): make sure we have no unicode keys for py2.6. params = dict([(str(k), v) for (k, v) in params.iteritems()]) result = method(context, **params) - if type(result) is dict or type(result) is list: - return self._serialize(result, req.best_match_content_type()) - else: + if result is None or type(result) is str or type(result) is unicode: return result + try: + return self._serialize(result, req.best_match_content_type()) + except: + raise exception.Error("returned non-serializable type: %s" % result) class Limited(object): -- cgit From c5cbec20d2785d3060d57b55a264fbf936709500 Mon Sep 17 00:00:00 2001 From: termie Date: Thu, 24 Mar 2011 13:20:15 -0700 Subject: pep8 cleanups --- nova/api/direct.py | 3 ++- nova/api/ec2/cloud.py | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/direct.py b/nova/api/direct.py index bb2ace1c9..e5f33cee4 100644 --- a/nova/api/direct.py +++ b/nova/api/direct.py @@ -211,7 +211,8 @@ class ServiceWrapper(wsgi.Controller): try: return self._serialize(result, req.best_match_content_type()) except: - raise exception.Error("returned non-serializable type: %s" % result) + raise exception.Error("returned non-serializable type: %s" + % result) class Limited(object): diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 5d31d71d3..0da642318 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -608,7 +608,9 @@ class CloudController(object): if field in kwargs: changes[field] = kwargs[field] if changes: - self.volume_api.update(context, volume_id=volume_id, fields=changes) + self.volume_api.update(context, + volume_id=volume_id, + fields=changes) return True def attach_volume(self, context, volume_id, instance_id, device, **kwargs): -- cgit