From c1f7914f9d8c4f7687c67de37c5eda5a95245a0d Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 28 Sep 2010 16:41:39 -0400 Subject: Remove TODO, since apparently newer boto doesn't die on extra fields. --- nova/api/ec2/cloud.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'nova/api') diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 4defef87e..528380f0f 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -286,8 +286,6 @@ class CloudController(object): else: v['attachmentSet'] = [{}] - # TODO(todd): check api version and only pass back to nova-aware - # clients v['display_name'] = volume['display_name'] v['display_description'] = volume['display_description'] return v -- cgit From c80c0786baadf521c86ceff21288e3760aaea5bd Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Tue, 28 Sep 2010 16:47:29 -0400 Subject: Add authorization info for cloud endpoints. --- nova/api/ec2/__init__.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'nova/api') diff --git a/nova/api/ec2/__init__.py b/nova/api/ec2/__init__.py index f0aa57ee4..7a958f841 100644 --- a/nova/api/ec2/__init__.py +++ b/nova/api/ec2/__init__.py @@ -158,12 +158,14 @@ class Authorizer(wsgi.Middleware): 'RunInstances': ['projectmanager', 'sysadmin'], 'TerminateInstances': ['projectmanager', 'sysadmin'], 'RebootInstances': ['projectmanager', 'sysadmin'], + 'UpdateInstance': ['projectmanager', 'sysadmin'], 'DeleteVolume': ['projectmanager', 'sysadmin'], 'DescribeImages': ['all'], 'DeregisterImage': ['projectmanager', 'sysadmin'], 'RegisterImage': ['projectmanager', 'sysadmin'], 'DescribeImageAttribute': ['all'], 'ModifyImageAttribute': ['projectmanager', 'sysadmin'], + 'UpdateImage': ['projectmanager', 'sysadmin'], }, 'AdminController': { # All actions have the same permission: ['none'] (the default) -- cgit From 05e3e188e03624884ed019fe9cd8f216c9262f98 Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Tue, 28 Sep 2010 20:36:50 -0400 Subject: Fault support --- nova/api/rackspace/__init__.py | 9 ++++--- nova/api/rackspace/auth.py | 7 ++--- nova/api/rackspace/faults.py | 61 ++++++++++++++++++++++++++++++++++++++++++ nova/api/rackspace/flavors.py | 3 ++- nova/api/rackspace/images.py | 7 ++--- 5 files changed, 77 insertions(+), 10 deletions(-) create mode 100644 nova/api/rackspace/faults.py (limited to 'nova/api') diff --git a/nova/api/rackspace/__init__.py b/nova/api/rackspace/__init__.py index c24d08585..447037020 100644 --- a/nova/api/rackspace/__init__.py +++ b/nova/api/rackspace/__init__.py @@ -31,6 +31,7 @@ import webob from nova import flags from nova import utils from nova import wsgi +from nova.api.rackspace import faults from nova.api.rackspace import flavors from nova.api.rackspace import images from nova.api.rackspace import ratelimiting @@ -66,7 +67,7 @@ class AuthMiddleware(wsgi.Middleware): user = self.auth_driver.authorize_token(req.headers["X-Auth-Token"]) if not user: - return webob.exc.HTTPUnauthorized() + return faults.Fault(webob.exc.HTTPUnauthorized()) context = {'user': user} req.environ['nova.context'] = context return self.application @@ -109,8 +110,10 @@ class RateLimitingMiddleware(wsgi.Middleware): delay = self.get_delay(action_name, username) if delay: # TODO(gundlach): Get the retry-after format correct. - raise webob.exc.HTTPRequestEntityTooLarge(headers={ - 'Retry-After': time.time() + delay}) + exc = webob.exc.HTTPRequestEntityTooLarge( + explanation='Too many requests.', + headers={'Retry-After': time.time() + delay}) + raise faults.Fault(exc) return self.application def get_delay(self, action_name, username): diff --git a/nova/api/rackspace/auth.py b/nova/api/rackspace/auth.py index ce5a967eb..519263367 100644 --- a/nova/api/rackspace/auth.py +++ b/nova/api/rackspace/auth.py @@ -9,6 +9,7 @@ from nova import auth from nova import manager from nova import db from nova import utils +from nova.api.rackspace import faults FLAGS = flags.FLAGS @@ -34,13 +35,13 @@ class BasicApiAuthManager(object): # honor it path_info = req.path_info if len(path_info) > 1: - return webob.exc.HTTPUnauthorized() + return faults.Fault(webob.exc.HTTPUnauthorized()) try: username, key = req.headers['X-Auth-User'], \ req.headers['X-Auth-Key'] except KeyError: - return webob.exc.HTTPUnauthorized() + return faults.Fault(webob.exc.HTTPUnauthorized()) username, key = req.headers['X-Auth-User'], req.headers['X-Auth-Key'] token, user = self._authorize_user(username, key) @@ -55,7 +56,7 @@ class BasicApiAuthManager(object): res.status = '204' return res else: - return webob.exc.HTTPUnauthorized() + return faults.Fault(webob.exc.HTTPUnauthorized()) def authorize_token(self, token_hash): """ retrieves user information from the datastore given a token diff --git a/nova/api/rackspace/faults.py b/nova/api/rackspace/faults.py new file mode 100644 index 000000000..fd6bc3623 --- /dev/null +++ b/nova/api/rackspace/faults.py @@ -0,0 +1,61 @@ +# 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 webob.dec + +from nova import wsgi + + +class Fault(wsgi.Application): + + """An RS API fault response.""" + + _fault_names = { + 400: "badRequest", + 401: "unauthorized", + 403: "resizeNotAllowed", + 404: "itemNotFound", + 405: "badMethod", + 409: "inProgress", + 413: "overLimit", + 415: "badMediaType", + 501: "notImplemented", + 503: "serviceUnavailable"} + + def __init__(self, exception): + """Create a Fault for the given webob.exc.exception.""" + self.exception = exception + + @webob.dec.wsgify + def __call__(self, req): + """Generate a WSGI response based on self.exception.""" + # Replace the body with fault details. + code = self.exception.status_int + fault_name = self._fault_names.get(code, "cloudServersFault") + fault_data = { + fault_name: { + 'code': code, + 'message': self.exception.explanation}} + if code == 413: + retry = self.exception.headers['Retry-After'] + fault_data[fault_name]['retryAfter'] = retry + # 'code' is an attribute on the fault tag itself + metadata = {'application/xml': {'attributes': {fault_name: 'code'}}} + serializer = wsgi.Serializer(req.environ, metadata) + self.exception.body = serializer.to_content_type(fault_data) + return self.exception diff --git a/nova/api/rackspace/flavors.py b/nova/api/rackspace/flavors.py index 60b35c939..6cc57be33 100644 --- a/nova/api/rackspace/flavors.py +++ b/nova/api/rackspace/flavors.py @@ -16,6 +16,7 @@ # under the License. from nova.api.rackspace import base +from nova.api.rackspace import faults from nova.compute import instance_types from webob import exc @@ -47,7 +48,7 @@ class Controller(base.Controller): item = dict(ram=val['memory_mb'], disk=val['local_gb'], id=val['flavorid'], name=name) return dict(flavor=item) - raise exc.HTTPNotFound() + raise faults.Fault(exc.HTTPNotFound()) def _all_ids(self): """Return the list of all flavorids.""" diff --git a/nova/api/rackspace/images.py b/nova/api/rackspace/images.py index 2f3e928b9..1c50d0bec 100644 --- a/nova/api/rackspace/images.py +++ b/nova/api/rackspace/images.py @@ -18,6 +18,7 @@ import nova.image.service from nova.api.rackspace import base from nova.api.rackspace import _id_translator +from nova.api.rackspace import faults from webob import exc class Controller(base.Controller): @@ -57,14 +58,14 @@ class Controller(base.Controller): def delete(self, req, id): # Only public images are supported for now. - raise exc.HTTPNotFound() + raise faults.Fault(exc.HTTPNotFound()) def create(self, req): # Only public images are supported for now, so a request to # make a backup of a server cannot be supproted. - raise exc.HTTPNotFound() + raise faults.Fault(exc.HTTPNotFound()) def update(self, req, id): # Users may not modify public images, and that's all that # we support for now. - raise exc.HTTPNotFound() + raise faults.Fault(exc.HTTPNotFound()) -- cgit From 4c1aa3d96f0c44d3e01864ca3128e9b052d1d7fd Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Wed, 29 Sep 2010 10:17:10 -0400 Subject: After update from trunk, a few more exceptions that need to be converted to Faults --- nova/api/rackspace/backup_schedules.py | 7 ++++--- nova/api/rackspace/servers.py | 17 +++++++++-------- 2 files changed, 13 insertions(+), 11 deletions(-) (limited to 'nova/api') diff --git a/nova/api/rackspace/backup_schedules.py b/nova/api/rackspace/backup_schedules.py index 46da778ee..cb83023bc 100644 --- a/nova/api/rackspace/backup_schedules.py +++ b/nova/api/rackspace/backup_schedules.py @@ -20,6 +20,7 @@ from webob import exc from nova import wsgi from nova.api.rackspace import _id_translator +from nova.api.rackspace import faults import nova.image.service class Controller(wsgi.Controller): @@ -27,12 +28,12 @@ class Controller(wsgi.Controller): pass def index(self, req, server_id): - return exc.HTTPNotFound() + return faults.Fault(exc.HTTPNotFound()) def create(self, req, server_id): """ No actual update method required, since the existing API allows both create and update through a POST """ - return exc.HTTPNotFound() + return faults.Fault(exc.HTTPNotFound()) def delete(self, req, server_id): - return exc.HTTPNotFound() + return faults.Fault(exc.HTTPNotFound()) diff --git a/nova/api/rackspace/servers.py b/nova/api/rackspace/servers.py index 4ab04bde7..888d67542 100644 --- a/nova/api/rackspace/servers.py +++ b/nova/api/rackspace/servers.py @@ -24,6 +24,7 @@ from nova import rpc from nova import utils from nova import wsgi from nova.api.rackspace import _id_translator +from nova.api.rackspace import faults from nova.compute import power_state import nova.image.service @@ -120,7 +121,7 @@ class Controller(wsgi.Controller): if inst: if inst.user_id == user_id: return _entity_detail(inst) - raise exc.HTTPNotFound() + raise faults.Fault(exc.HTTPNotFound()) def delete(self, req, id): """ Destroys a server """ @@ -128,13 +129,13 @@ class Controller(wsgi.Controller): instance = self.db_driver.instance_get(None, id) if instance and instance['user_id'] == user_id: self.db_driver.instance_destroy(None, id) - return exc.HTTPAccepted() - return exc.HTTPNotFound() + return faults.Fault(exc.HTTPAccepted()) + return faults.Fault(exc.HTTPNotFound()) def create(self, req): """ Creates a new server for a given user """ if not req.environ.has_key('inst_dict'): - return exc.HTTPUnprocessableEntity() + return faults.Fault(exc.HTTPUnprocessableEntity()) inst = self._build_server_instance(req) @@ -147,22 +148,22 @@ class Controller(wsgi.Controller): def update(self, req, id): """ Updates the server name or password """ if not req.environ.has_key('inst_dict'): - return exc.HTTPUnprocessableEntity() + return faults.Fault(exc.HTTPUnprocessableEntity()) instance = self.db_driver.instance_get(None, id) if not instance: - return exc.HTTPNotFound() + return faults.Fault(exc.HTTPNotFound()) attrs = req.environ['nova.context'].get('model_attributes', None) if attrs: self.db_driver.instance_update(None, id, _filter_params(attrs)) - return exc.HTTPNoContent() + return faults.Fault(exc.HTTPNoContent()) def action(self, req, id): """ multi-purpose method used to reboot, rebuild, and resize a server """ if not req.environ.has_key('inst_dict'): - return exc.HTTPUnprocessableEntity() + return faults.Fault(exc.HTTPUnprocessableEntity()) def _build_server_instance(self, req): """Build instance data structure and save it to the data store.""" -- cgit From 29eca7e7992fc5c073d70f7c8ca5e5bc03f62af7 Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Wed, 29 Sep 2010 11:37:26 -0400 Subject: Limit entity lists by &offset and &limit --- nova/api/rackspace/__init__.py | 20 ++++++++++++++++++++ nova/api/rackspace/flavors.py | 5 ++++- nova/api/rackspace/images.py | 2 ++ nova/api/rackspace/servers.py | 18 ++++++++++++------ 4 files changed, 38 insertions(+), 7 deletions(-) (limited to 'nova/api') diff --git a/nova/api/rackspace/__init__.py b/nova/api/rackspace/__init__.py index 98802663f..48104f6df 100644 --- a/nova/api/rackspace/__init__.py +++ b/nova/api/rackspace/__init__.py @@ -165,3 +165,23 @@ class APIRouter(wsgi.Router): controller=sharedipgroups.Controller()) super(APIRouter, self).__init__(mapper) + + +def limited(items, req): + """Return a slice of items according to requested offset and limit. + + items - a sliceable + req - wobob.Request possibly containing offset and limit GET variables. + offset is where to start in the list, and limit is the maximum number + of items to return. + + If limit is not specified, 0, or > 1000, defaults to 1000. + """ + offset = int(req.GET.get('offset', 0)) + limit = int(req.GET.get('limit', 0)) + if not limit: + limit = 1000 + limit = min(1000, limit) + range_end = offset + limit + return items[offset:range_end] + diff --git a/nova/api/rackspace/flavors.py b/nova/api/rackspace/flavors.py index 3bcf170e5..ba7aa937c 100644 --- a/nova/api/rackspace/flavors.py +++ b/nova/api/rackspace/flavors.py @@ -15,9 +15,11 @@ # License for the specific language governing permissions and limitations # under the License. +from webob import exc + from nova.compute import instance_types from nova import wsgi -from webob import exc +import nova.api.rackspace class Controller(wsgi.Controller): """Flavor controller for the Rackspace API.""" @@ -38,6 +40,7 @@ class Controller(wsgi.Controller): def detail(self, req): """Return all flavors in detail.""" items = [self.show(req, id)['flavor'] for id in self._all_ids()] + items = nova.api.rackspace.limited(items, req) return dict(flavors=items) def show(self, req, id): diff --git a/nova/api/rackspace/images.py b/nova/api/rackspace/images.py index 11b058dec..7da17e6a7 100644 --- a/nova/api/rackspace/images.py +++ b/nova/api/rackspace/images.py @@ -19,6 +19,7 @@ from webob import exc from nova import wsgi from nova.api.rackspace import _id_translator +import nova.api.rackspace import nova.image.service class Controller(wsgi.Controller): @@ -45,6 +46,7 @@ class Controller(wsgi.Controller): def detail(self, req): """Return all public images in detail.""" data = self._service.index() + data = nova.api.rackspace.limited(data, req) for img in data: img['id'] = self._id_translator.to_rs_id(img['id']) return dict(images=data) diff --git a/nova/api/rackspace/servers.py b/nova/api/rackspace/servers.py index 4ab04bde7..958fc86a3 100644 --- a/nova/api/rackspace/servers.py +++ b/nova/api/rackspace/servers.py @@ -25,6 +25,7 @@ from nova import utils from nova import wsgi from nova.api.rackspace import _id_translator from nova.compute import power_state +import nova.api.rackspace import nova.image.service FLAGS = flags.FLAGS @@ -101,16 +102,21 @@ class Controller(wsgi.Controller): def index(self, req): """ Returns a list of server names and ids for a given user """ - user_id = req.environ['nova.context']['user']['id'] - instance_list = self.db_driver.instance_get_all_by_user(None, user_id) - res = [_entity_inst(inst)['server'] for inst in instance_list] - return _entity_list(res) + return self._items(req, entity_maker=_entity_inst) def detail(self, req): """ Returns a list of server details for a given user """ + return self._items(req, entity_maker=_entity_detail) + + def _items(self, req, entity_maker): + """Returns a list of servers for a given user. + + entity_maker - either _entity_detail or _entity_inst + """ user_id = req.environ['nova.context']['user']['id'] - res = [_entity_detail(inst)['server'] for inst in - self.db_driver.instance_get_all_by_user(None, user_id)] + instance_list = self.db_driver.instance_get_all_by_user(None, user_id) + limited_list = nova.api.rackspace.limited(instance_list, req) + res = [entity_maker(inst)['server'] for inst in limited_list] return _entity_list(res) def show(self, req, id): -- cgit From 2136f12d29cef9acc7dc6ee0a5901fa3878160f8 Mon Sep 17 00:00:00 2001 From: Michael Gundlach Date: Wed, 29 Sep 2010 15:09:39 -0400 Subject: Make Fault raiseable by inheriting from webob.exc.HTTPException. Change from using self.exception which is reserved by HTTPException to self.wrapped_exc. --- nova/api/rackspace/faults.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) (limited to 'nova/api') diff --git a/nova/api/rackspace/faults.py b/nova/api/rackspace/faults.py index fd6bc3623..32e5c866f 100644 --- a/nova/api/rackspace/faults.py +++ b/nova/api/rackspace/faults.py @@ -17,11 +17,12 @@ import webob.dec +import webob.exc from nova import wsgi -class Fault(wsgi.Application): +class Fault(webob.exc.HTTPException): """An RS API fault response.""" @@ -39,23 +40,23 @@ class Fault(wsgi.Application): def __init__(self, exception): """Create a Fault for the given webob.exc.exception.""" - self.exception = exception + self.wrapped_exc = exception @webob.dec.wsgify def __call__(self, req): - """Generate a WSGI response based on self.exception.""" + """Generate a WSGI response based on the exception passed to ctor.""" # Replace the body with fault details. - code = self.exception.status_int + code = self.wrapped_exc.status_int fault_name = self._fault_names.get(code, "cloudServersFault") fault_data = { fault_name: { 'code': code, - 'message': self.exception.explanation}} + 'message': self.wrapped_exc.explanation}} if code == 413: - retry = self.exception.headers['Retry-After'] + retry = self.wrapped_exc.headers['Retry-After'] fault_data[fault_name]['retryAfter'] = retry # 'code' is an attribute on the fault tag itself metadata = {'application/xml': {'attributes': {fault_name: 'code'}}} serializer = wsgi.Serializer(req.environ, metadata) - self.exception.body = serializer.to_content_type(fault_data) - return self.exception + self.wrapped_exc.body = serializer.to_content_type(fault_data) + return self.wrapped_exc -- cgit