From a5aa1c9167958ee806ac43d1b0be39ea1ce96917 Mon Sep 17 00:00:00 2001 From: Eric Harney Date: Fri, 19 Jul 2013 10:02:51 -0400 Subject: [PATCH] New update_snapshot_status API Adds new snapshot_actions module Update_snapshot_status: Allows updating of 'state' and 'progress' fields of a snapshot. This is used by Nova to inform Cinder of its outcome when performing snapshot operations for attached volumes. Updates are restricted to a subset of possible start and finish states. Implements blueprint qemu-assisted-snapshots Change-Id: I54772f794b97e1cc6b24b121b757219248e37109 --- cinder/api/contrib/snapshot_actions.py | 107 ++++++++++++++++++ .../api/contrib/test_snapshot_actions.py | 75 ++++++++++++ cinder/tests/policy.json | 2 + etc/cinder/policy.json | 4 +- 4 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 cinder/api/contrib/snapshot_actions.py create mode 100644 cinder/tests/api/contrib/test_snapshot_actions.py diff --git a/cinder/api/contrib/snapshot_actions.py b/cinder/api/contrib/snapshot_actions.py new file mode 100644 index 000000000..781f7a863 --- /dev/null +++ b/cinder/api/contrib/snapshot_actions.py @@ -0,0 +1,107 @@ +# Copyright 2013, Red Hat, Inc. +# +# 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. + +from oslo.config import cfg +import webob + +from cinder.api import extensions +from cinder.api.openstack import wsgi +from cinder import db +from cinder.openstack.common import log as logging + +LOG = logging.getLogger(__name__) + + +def authorize(context, action_name): + action = 'snapshot_actions:%s' % action_name + extensions.extension_authorizer('snapshot', action)(context) + + +class SnapshotActionsController(wsgi.Controller): + def __init__(self, *args, **kwargs): + super(SnapshotActionsController, self).__init__(*args, **kwargs) + LOG.debug("SnapshotActionsController initialized") + + @wsgi.action('os-update_snapshot_status') + def _update_snapshot_status(self, req, id, body): + """Update database fields related to status of a snapshot. + + Intended for creation of snapshots, so snapshot state + must start as 'creating' and be changed to 'available', + 'creating', or 'error'. + """ + + context = req.environ['cinder.context'] + authorize(context, 'update_snapshot_status') + + LOG.debug("body: %s" % body) + status = body['os-update_snapshot_status']['status'] + + # Allowed state transitions + status_map = {'creating': ['creating', 'available', 'error'], + 'deleting': ['deleting', 'error_deleting']} + + current_snapshot = db.snapshot_get(context, id) + + if current_snapshot['status'] not in status_map: + msg = _("Snapshot status %(cur)s not allowed for " + "update_snapshot_status") % { + 'cur': current_snapshot['status']} + raise webob.exc.HTTPBadRequest(explanation=msg) + + if status not in status_map[current_snapshot['status']]: + msg = _("Provided snapshot status %(provided)s not allowed for " + "snapshot with status %(current)s.") % \ + {'provided': status, + 'current': current_snapshot['status']} + raise webob.exc.HTTPBadRequest(explanation=msg) + + update_dict = {'id': id, + 'status': status} + + progress = body['os-update_snapshot_status'].get('progress', None) + if progress: + # This is expected to be a string like '73%' + msg = _('progress must be an integer percentage') + try: + integer = int(progress[:-1]) + except ValueError: + raise webob.exc.HTTPBadRequest(explanation=msg) + if integer < 0 or integer > 100 or progress[-1] != '%': + raise webob.exc.HTTPBadRequest(explanation=msg) + + update_dict.update({'progress': progress}) + + LOG.info("Updating snapshot %(id)s with info %(dict)s" % + {'id': id, 'dict': update_dict}) + + db.snapshot_update(context, id, update_dict) + return webob.Response(status_int=202) + + +class Snapshot_actions(extensions.ExtensionDescriptor): + """Enable snapshot manager actions.""" + + name = "SnapshotActions" + alias = "os-snapshot-actions" + namespace = \ + "http://docs.openstack.org/volume/ext/snapshot-actions/api/v1.1" + updated = "2013-07-16T00:00:00+00:00" + + def get_controller_extensions(self): + controller = SnapshotActionsController() + extension = extensions.ControllerExtension(self, + 'snapshots', + controller) + return [extension] diff --git a/cinder/tests/api/contrib/test_snapshot_actions.py b/cinder/tests/api/contrib/test_snapshot_actions.py new file mode 100644 index 000000000..5ec2fed1d --- /dev/null +++ b/cinder/tests/api/contrib/test_snapshot_actions.py @@ -0,0 +1,75 @@ +# Copyright 2013, Red Hat, Inc. +# +# 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 +import uuid +import webob + +from cinder import db +from cinder import exception +from cinder.openstack.common import jsonutils +from cinder.openstack.common.rpc import common as rpc_common +from cinder import test +from cinder.tests.api import fakes +from cinder.tests.api.v2 import stubs +from cinder import volume +from cinder.volume import api as volume_api + + +class SnapshotActionsTest(test.TestCase): + + def setUp(self): + super(SnapshotActionsTest, self).setUp() + + def test_update_snapshot_status(self): + self.stubs.Set(db, 'snapshot_get', stub_snapshot_get) + self.stubs.Set(db, 'snapshot_update', stub_snapshot_update) + + body = {'os-update_snapshot_status': {'status': 'available'}} + req = webob.Request.blank('/v2/fake/snapshots/1/action') + req.method = "POST" + req.body = jsonutils.dumps(body) + req.headers["content-type"] = "application/json" + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 202) + + def test_update_snapshot_status_invalid_status(self): + self.stubs.Set(db, 'snapshot_get', stub_snapshot_get) + body = {'os-update_snapshot_status': {'status': 'in-use'}} + req = webob.Request.blank('/v2/fake/snapshots/1/action') + req.method = "POST" + req.body = jsonutils.dumps(body) + req.headers["content-type"] = "application/json" + + res = req.get_response(fakes.wsgi_app()) + self.assertEqual(res.status_int, 400) + + +def stub_snapshot_get(context, snapshot_id): + snapshot = stubs.stub_snapshot(snapshot_id) + if snapshot_id == 3: + snapshot['status'] = 'error' + elif snapshot_id == 1: + snapshot['status'] = 'creating' + elif snapshot_id == 7: + snapshot['status'] = 'available' + else: + snapshot['status'] = 'creating' + + return snapshot + + +def stub_snapshot_update(self, context, id, **kwargs): + pass diff --git a/cinder/tests/policy.json b/cinder/tests/policy.json index 669f416dd..c8a8a9347 100644 --- a/cinder/tests/policy.json +++ b/cinder/tests/policy.json @@ -56,6 +56,8 @@ "volume_extension:quotas:show": [], "volume_extension:quotas:update": [], + "snapshot_extension:snapshot_actions:update_snapshot_status": [], + "volume:create_transfer": [], "volume:accept_transfer": [], "volume:delete_transfer": [], diff --git a/etc/cinder/policy.json b/etc/cinder/policy.json index 7b3065c0a..a7fdab412 100644 --- a/etc/cinder/policy.json +++ b/etc/cinder/policy.json @@ -50,5 +50,7 @@ "backup:delete": [], "backup:get": [], "backup:get_all": [], - "backup:restore": [] + "backup:restore": [], + + "snapshot_extension:snapshot_actions:update_snapshot_status": [] } -- 2.45.2