summaryrefslogtreecommitdiffstats
path: root/nova/api
diff options
context:
space:
mode:
Diffstat (limited to 'nova/api')
-rw-r--r--nova/api/openstack/extensions.py5
-rw-r--r--nova/api/openstack/volume/__init__.py10
-rw-r--r--nova/api/openstack/volume/contrib/admin_actions.py129
-rw-r--r--nova/api/openstack/volume/snapshots.py7
-rw-r--r--nova/api/openstack/volume/volumes.py7
-rw-r--r--nova/api/openstack/wsgi.py3
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