diff options
Diffstat (limited to 'nova/api')
| -rw-r--r-- | nova/api/openstack/extensions.py | 5 | ||||
| -rw-r--r-- | nova/api/openstack/volume/__init__.py | 10 | ||||
| -rw-r--r-- | nova/api/openstack/volume/contrib/admin_actions.py | 129 | ||||
| -rw-r--r-- | nova/api/openstack/volume/snapshots.py | 7 | ||||
| -rw-r--r-- | nova/api/openstack/volume/volumes.py | 7 | ||||
| -rw-r--r-- | nova/api/openstack/wsgi.py | 3 |
6 files changed, 149 insertions, 12 deletions
diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index e7334123a..f36586443 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -223,11 +223,12 @@ class ExtensionManager(object): controller_exts = [] for ext in self.sorted_extensions(): try: - controller_exts.extend(ext.get_controller_extensions()) + get_ext_method = ext.get_controller_extensions except AttributeError: # NOTE(Vek): Extensions aren't required to have # controller extensions - pass + continue + controller_exts.extend(get_ext_method()) return controller_exts def _check_extension(self, extension): diff --git a/nova/api/openstack/volume/__init__.py b/nova/api/openstack/volume/__init__.py index 092ce6c5d..cc161416c 100644 --- a/nova/api/openstack/volume/__init__.py +++ b/nova/api/openstack/volume/__init__.py @@ -47,16 +47,18 @@ class APIRouter(nova.api.openstack.APIRouter): mapper.redirect("", "/") - self.resources['volumes'] = volumes.create_resource() + self.resources['volumes'] = volumes.create_resource(ext_mgr) mapper.resource("volume", "volumes", controller=self.resources['volumes'], - collection={'detail': 'GET'}) + collection={'detail': 'GET'}, + member={'action': 'POST'}) self.resources['types'] = types.create_resource() mapper.resource("type", "types", controller=self.resources['types']) - self.resources['snapshots'] = snapshots.create_resource() + self.resources['snapshots'] = snapshots.create_resource(ext_mgr) mapper.resource("snapshot", "snapshots", controller=self.resources['snapshots'], - collection={'detail': 'GET'}) + collection={'detail': 'GET'}, + member={'action': 'POST'}) diff --git a/nova/api/openstack/volume/contrib/admin_actions.py b/nova/api/openstack/volume/contrib/admin_actions.py new file mode 100644 index 000000000..7e93283f7 --- /dev/null +++ b/nova/api/openstack/volume/contrib/admin_actions.py @@ -0,0 +1,129 @@ +# Copyright 2012 OpenStack, LLC. +# +# 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 +from webob import exc + +from nova.api.openstack import extensions +from nova.api.openstack import wsgi +from nova import db +from nova import exception +from nova.openstack.common import log as logging +from nova import volume + + +LOG = logging.getLogger(__name__) + + +class AdminController(wsgi.Controller): + """Abstract base class for AdminControllers.""" + + collection = None # api collection to extend + + # FIXME(clayg): this will be hard to keep up-to-date + # Concrete classes can expand or over-ride + valid_status = set([ + 'creating', + 'available', + 'deleting', + 'error', + 'error_deleting', + ]) + + def __init__(self, *args, **kwargs): + super(AdminController, self).__init__(*args, **kwargs) + # singular name of the resource + self.resource_name = self.collection.rstrip('s') + self.volume_api = volume.API() + + def _update(self, *args, **kwargs): + raise NotImplementedError() + + def _validate_status(self, status): + if status not in self.valid_status: + raise exc.HTTPBadRequest("Must specify a valid status") + + def authorize(self, context, action_name): + # e.g. "snapshot_admin_actions:reset_status" + action = '%s_admin_actions:%s' % (self.resource_name, action_name) + extensions.extension_authorizer('volume', action)(context) + + @wsgi.action('os-reset_status') + def _reset_status(self, req, id, body): + """Reset status on the resource.""" + context = req.environ['nova.context'] + self.authorize(context, 'reset_status') + try: + new_status = body['os-reset_status']['status'] + except (TypeError, KeyError): + raise exc.HTTPBadRequest("Must specify 'status'") + self._validate_status(new_status) + msg = _("Updating status of %(resource)s '%(id)s' to '%(status)s'") + LOG.debug(msg, {'resource': self.resource_name, 'id': id, + 'status': new_status}) + try: + self._update(context, id, {'status': new_status}) + except exception.NotFound, e: + raise exc.HTTPNotFound(e) + return webob.Response(status_int=202) + + +class VolumeAdminController(AdminController): + """AdminController for Volumes.""" + + collection = 'volumes' + valid_status = AdminController.valid_status.union( + set(['attaching', 'in-use', 'detaching'])) + + def _update(self, *args, **kwargs): + db.volume_update(*args, **kwargs) + + @wsgi.action('os-force_delete') + def _force_delete(self, req, id, body): + """Delete a resource, bypassing the check that it must be available.""" + context = req.environ['nova.context'] + self.authorize(context, 'force_delete') + try: + volume = self.volume_api.get(context, id) + except exception.NotFound: + raise exc.HTTPNotFound() + self.volume_api.delete(context, volume, force=True) + return webob.Response(status_int=202) + + +class SnapshotAdminController(AdminController): + """AdminController for Snapshots.""" + + collection = 'snapshots' + + def _update(self, *args, **kwargs): + db.snapshot_update(*args, **kwargs) + + +class Admin_actions(extensions.ExtensionDescriptor): + """Enable admin actions.""" + + name = "AdminActions" + alias = "os-admin-actions" + namespace = "http://docs.openstack.org/volume/ext/admin-actions/api/v1.1" + updated = "2012-08-25T00:00:00+00:00" + + def get_controller_extensions(self): + exts = [] + for class_ in (VolumeAdminController, SnapshotAdminController): + controller = class_() + extension = extensions.ControllerExtension( + self, class_.collection, controller) + exts.append(extension) + return exts diff --git a/nova/api/openstack/volume/snapshots.py b/nova/api/openstack/volume/snapshots.py index 755398369..14c14cf86 100644 --- a/nova/api/openstack/volume/snapshots.py +++ b/nova/api/openstack/volume/snapshots.py @@ -88,8 +88,9 @@ class SnapshotsTemplate(xmlutil.TemplateBuilder): class SnapshotsController(object): """The Volumes API controller for the OpenStack API.""" - def __init__(self): + def __init__(self, ext_mgr=None): self.volume_api = volume.API() + self.ext_mgr = ext_mgr super(SnapshotsController, self).__init__() @wsgi.serializers(xml=SnapshotTemplate) @@ -175,5 +176,5 @@ class SnapshotsController(object): return {'snapshot': retval} -def create_resource(): - return wsgi.Resource(SnapshotsController()) +def create_resource(ext_mgr): + return wsgi.Resource(SnapshotsController(ext_mgr)) diff --git a/nova/api/openstack/volume/volumes.py b/nova/api/openstack/volume/volumes.py index e83030945..be5b5bd83 100644 --- a/nova/api/openstack/volume/volumes.py +++ b/nova/api/openstack/volume/volumes.py @@ -195,8 +195,9 @@ class CreateDeserializer(CommonDeserializer): class VolumeController(object): """The Volumes API controller for the OpenStack API.""" - def __init__(self): + def __init__(self, ext_mgr=None): self.volume_api = volume.API() + self.ext_mgr = ext_mgr super(VolumeController, self).__init__() @wsgi.serializers(xml=VolumeTemplate) @@ -309,8 +310,8 @@ class VolumeController(object): return ('name', 'status') -def create_resource(): - return wsgi.Resource(VolumeController()) +def create_resource(ext_mgr): + return wsgi.Resource(VolumeController(ext_mgr)) def remove_invalid_options(context, search_options, allowed_search_options): diff --git a/nova/api/openstack/wsgi.py b/nova/api/openstack/wsgi.py index 658b59645..571f91e5b 100644 --- a/nova/api/openstack/wsgi.py +++ b/nova/api/openstack/wsgi.py @@ -1070,6 +1070,9 @@ class ControllerMetaclass(type): # Find all actions actions = {} extensions = [] + # start with wsgi actions from base classes + for base in bases: + actions.update(getattr(base, 'wsgi_actions', {})) for key, value in cls_dict.items(): if not callable(value): continue |
