diff --git a/doc/api_samples/all_extensions/extensions-get-resp.json b/doc/api_samples/all_extensions/extensions-get-resp.json index e340dda72669..4db707bc1d86 100644 --- a/doc/api_samples/all_extensions/extensions-get-resp.json +++ b/doc/api_samples/all_extensions/extensions-get-resp.json @@ -128,6 +128,14 @@ "namespace": "http://docs.openstack.org/compute/ext/aggregates/api/v1.1", "updated": "2012-01-12T00:00:00+00:00" }, + { + "alias": "os-assisted-volume-snapshots", + "description": "Assisted volume snapshots.", + "links": [], + "name": "AssistedVolumeSnapshots", + "namespace": "http://docs.openstack.org/compute/ext/assisted-volume-snapshots/api/v2", + "updated": "2013-08-15T00:00:00-00:00" + }, { "alias": "os-attach-interfaces", "description": "Attach interface support.", @@ -472,14 +480,6 @@ "namespace": "http://docs.openstack.org/compute/ext/quotas-sets/api/v1.1", "updated": "2011-08-08T00:00:00+00:00" }, - { - "alias": "os-user-quotas", - "description": "Project user quota support.", - "links": [], - "name": "UserQuotas", - "namespace": "http://docs.openstack.org/compute/ext/user_quotas/api/v1.1", - "updated": "2013-07-18T00:00:00+00:00" - }, { "alias": "os-rescue", "description": "Instance rescue mode.", @@ -584,6 +584,14 @@ "namespace": "http://docs.openstack.org/compute/ext/userdata/api/v1.1", "updated": "2012-08-07T00:00:00+00:00" }, + { + "alias": "os-user-quotas", + "description": "Project user quota support.", + "links": [], + "name": "UserQuotas", + "namespace": "http://docs.openstack.org/compute/ext/user_quotas/api/v1.1", + "updated": "2013-07-18T00:00:00+00:00" + }, { "alias": "os-virtual-interfaces", "description": "Virtual interface support.", @@ -609,4 +617,4 @@ "updated": "2011-03-25T00:00:00+00:00" } ] -} +} \ No newline at end of file diff --git a/doc/api_samples/all_extensions/extensions-get-resp.xml b/doc/api_samples/all_extensions/extensions-get-resp.xml index 73ea795a7247..61b43a0fda44 100644 --- a/doc/api_samples/all_extensions/extensions-get-resp.xml +++ b/doc/api_samples/all_extensions/extensions-get-resp.xml @@ -52,6 +52,9 @@ Admin-only aggregate administration. + + Assisted volume snapshots. + Attach interface support. @@ -197,9 +200,6 @@ Quotas management support. - - Project user quota support. - Instance rescue mode. @@ -239,6 +239,9 @@ Add user_data to the Create Server v1.1 API. + + Project user quota support. + Virtual interface support. @@ -248,4 +251,4 @@ Volumes support. - + \ No newline at end of file diff --git a/doc/api_samples/os-assisted-volume-snapshots/snapshot-create-assisted-req.json b/doc/api_samples/os-assisted-volume-snapshots/snapshot-create-assisted-req.json new file mode 100644 index 000000000000..4425b9dc7ee4 --- /dev/null +++ b/doc/api_samples/os-assisted-volume-snapshots/snapshot-create-assisted-req.json @@ -0,0 +1,10 @@ +{ + "snapshot": { + "display_name": "snap-001", + "display_description": "Daily backup", + "volume_id": "521752a6-acf6-4b2d-bc7a-119f9148cd8c", + "force": false, + "assisted": true, + "create_info": {} + } +} diff --git a/doc/api_samples/os-assisted-volume-snapshots/snapshot-create-assisted-req.xml b/doc/api_samples/os-assisted-volume-snapshots/snapshot-create-assisted-req.xml new file mode 100644 index 000000000000..24100bcc670a --- /dev/null +++ b/doc/api_samples/os-assisted-volume-snapshots/snapshot-create-assisted-req.xml @@ -0,0 +1,9 @@ + + + snap-001 + Daily backup + 521752a6-acf6-4b2d-bc7a-119f9148cd8c + false + true + + \ No newline at end of file diff --git a/doc/api_samples/os-assisted-volume-snapshots/snapshot-create-assisted-resp.json b/doc/api_samples/os-assisted-volume-snapshots/snapshot-create-assisted-resp.json new file mode 100644 index 000000000000..acfc149658ce --- /dev/null +++ b/doc/api_samples/os-assisted-volume-snapshots/snapshot-create-assisted-resp.json @@ -0,0 +1,6 @@ +{ + "snapshot": { + "id": 100, + "volumeId": "521752a6-acf6-4b2d-bc7a-119f9148cd8c" + } +} \ No newline at end of file diff --git a/doc/api_samples/os-assisted-volume-snapshots/snapshot-create-assisted-resp.xml b/doc/api_samples/os-assisted-volume-snapshots/snapshot-create-assisted-resp.xml new file mode 100644 index 000000000000..419d6d40669c --- /dev/null +++ b/doc/api_samples/os-assisted-volume-snapshots/snapshot-create-assisted-resp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/etc/nova/policy.json b/etc/nova/policy.json index 55bf5b2fc40b..8bbd2545ac5a 100644 --- a/etc/nova/policy.json +++ b/etc/nova/policy.json @@ -243,6 +243,8 @@ "compute_extension:migrations:index": "rule:admin_api", "compute_extension:v3:os-migrations:index": "rule:admin_api", "compute_extension:v3:os-migrations:discoverable": "", + "compute_extension:os-assisted-volume-snapshots:create": "rule:admin_api", + "compute_extension:os-assisted-volume-snapshots:delete": "rule:admin_api", "volume:create": "", diff --git a/nova/api/openstack/compute/contrib/assisted_volume_snapshots.py b/nova/api/openstack/compute/contrib/assisted_volume_snapshots.py new file mode 100644 index 000000000000..94dd50b60533 --- /dev/null +++ b/nova/api/openstack/compute/contrib/assisted_volume_snapshots.py @@ -0,0 +1,110 @@ +# 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 webob + +from nova.api.openstack import extensions +from nova.api.openstack import wsgi +from nova.api.openstack import xmlutil +from nova import compute +from nova import exception +from nova.openstack.common.gettextutils import _ +from nova.openstack.common import jsonutils +from nova.openstack.common import log as logging + + +LOG = logging.getLogger(__name__) +authorize = extensions.extension_authorizer('compute', + 'os-assisted-volume-snapshots') + + +def make_snapshot(elem): + elem.set('id') + elem.set('volumeId') + + +class SnapshotTemplate(xmlutil.TemplateBuilder): + def construct(self): + root = xmlutil.TemplateElement('snapshot', selector='snapshot') + make_snapshot(root) + return xmlutil.MasterTemplate(root, 1) + + +class AssistedVolumeSnapshotsController(wsgi.Controller): + + def __init__(self): + self.compute_api = compute.API() + super(AssistedVolumeSnapshotsController, self).__init__() + + @wsgi.serializers(xml=SnapshotTemplate) + def create(self, req, body): + """Creates a new snapshot.""" + context = req.environ['nova.context'] + authorize(context, action='create') + + if not self.is_valid_body(body, 'snapshot'): + raise webob.exc.HTTPBadRequest() + + try: + snapshot = body['snapshot'] + create_info = snapshot['create_info'] + volume_id = snapshot['volume_id'] + except KeyError: + raise webob.exc.HTTPBadRequest() + + LOG.audit(_("Create assisted snapshot from volume %s"), volume_id, + context=context) + + return self.compute_api.volume_snapshot_create(context, volume_id, + create_info) + + def delete(self, req, id): + """Delete a snapshot.""" + context = req.environ['nova.context'] + authorize(context, action='delete') + + LOG.audit(_("Delete snapshot with id: %s"), id, context=context) + + delete_metadata = {} + delete_metadata.update(req.GET) + + try: + delete_info = jsonutils.loads(delete_metadata['delete_info']) + volume_id = delete_info['volume_id'] + except (KeyError, ValueError) as e: + raise webob.exc.HTTPBadRequest(explanation=str(e)) + + try: + self.compute_api.volume_snapshot_delete(context, volume_id, + id, delete_info) + except exception.NotFound: + return webob.exc.HTTPNotFound() + + return webob.Response(status_int=204) + + +class Assisted_volume_snapshots(extensions.ExtensionDescriptor): + """Assisted volume snapshots.""" + + name = "AssistedVolumeSnapshots" + alias = "os-assisted-volume-snapshots" + namespace = ("http://docs.openstack.org/compute/ext/" + "assisted-volume-snapshots/api/v2") + updated = "2013-08-29T00:00:00-00:00" + + def get_resources(self): + resource = extensions.ResourceExtension('os-assisted-volume-snapshots', + AssistedVolumeSnapshotsController()) + + return [resource] diff --git a/nova/tests/api/openstack/compute/contrib/test_volumes.py b/nova/tests/api/openstack/compute/contrib/test_volumes.py index 3e11367481e8..a5da6e0db5a2 100644 --- a/nova/tests/api/openstack/compute/contrib/test_volumes.py +++ b/nova/tests/api/openstack/compute/contrib/test_volumes.py @@ -1,4 +1,5 @@ # Copyright 2013 Josh Durgin +# Copyright 2013 Red Hat, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -20,6 +21,8 @@ from oslo.config import cfg import webob from webob import exc +from nova.api.openstack.compute.contrib import assisted_volume_snapshots as \ + assisted_snaps from nova.api.openstack.compute.contrib import volumes from nova.api.openstack import extensions from nova.compute import api as compute_api @@ -79,6 +82,16 @@ def fake_delete_snapshot(self, context, snapshot_id): pass +def fake_compute_volume_snapshot_delete(self, context, volume_id, snapshot_id, + delete_info): + pass + + +def fake_compute_volume_snapshot_create(self, context, volume_id, + create_info): + pass + + def fake_get_instance_bdms(self, context, instance): return [{'id': 1, 'instance_uuid': instance['uuid'], @@ -793,3 +806,51 @@ class DeleteSnapshotTestCase(test.TestCase): self.req.method = 'DELETE' result = self.controller.delete(self.req, result['snapshot']['id']) self.assertEqual(result.status_int, 202) + + +class AssistedSnapshotCreateTestCase(test.TestCase): + def setUp(self): + super(AssistedSnapshotCreateTestCase, self).setUp() + + self.controller = assisted_snaps.AssistedVolumeSnapshotsController() + self.stubs.Set(compute_api.API, 'volume_snapshot_create', + fake_compute_volume_snapshot_create) + + def test_assisted_create(self): + req = fakes.HTTPRequest.blank('/v2/fake/os-assisted-volume-snapshots') + body = {'snapshot': {'volume_id': 1, 'create_info': {}}} + req.method = 'POST' + self.controller.create(req, body=body) + + def test_assisted_create_missing_create_info(self): + req = fakes.HTTPRequest.blank('/v2/fake/os-assisted-volume-snapshots') + body = {'snapshot': {'volume_id': 1}} + req.method = 'POST' + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create, + req, body=body) + + +class AssistedSnapshotDeleteTestCase(test.TestCase): + def setUp(self): + super(AssistedSnapshotDeleteTestCase, self).setUp() + + self.controller = assisted_snaps.AssistedVolumeSnapshotsController() + self.stubs.Set(compute_api.API, 'volume_snapshot_delete', + fake_compute_volume_snapshot_delete) + + def test_assisted_delete(self): + params = { + 'delete_info': jsonutils.dumps({'volume_id': 1}), + } + req = fakes.HTTPRequest.blank( + '/v2/fake/os-assisted-volume-snapshots?%s' % + '&'.join(['%s=%s' % (k, v) for k, v in params.iteritems()])) + req.method = 'DELETE' + result = self.controller.delete(req, '5') + self.assertEqual(result.status_int, 204) + + def test_assisted_delete_missing_delete_info(self): + req = fakes.HTTPRequest.blank('/v2/fake/os-assisted-volume-snapshots') + req.method = 'DELETE' + self.assertRaises(webob.exc.HTTPBadRequest, self.controller.delete, + req, '5') diff --git a/nova/tests/api/openstack/compute/test_extensions.py b/nova/tests/api/openstack/compute/test_extensions.py index 2bedd69272da..726737a8bc92 100644 --- a/nova/tests/api/openstack/compute/test_extensions.py +++ b/nova/tests/api/openstack/compute/test_extensions.py @@ -176,6 +176,7 @@ class ExtensionControllerTest(ExtensionTestCase): self.ext_list = [ "AdminActions", "Aggregates", + "AssistedVolumeSnapshots", "AvailabilityZone", "Agents", "Certificates", diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index d12d6bfc6cd6..5bb401e25e0e 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -679,11 +679,20 @@ def stub_snapshot_create(self, context, volume_id, name, description): display_description=description) +def stub_compute_volume_snapshot_create(self, context, volume_id, create_info): + return {'snapshot': {'id': 100, 'volumeId': volume_id}} + + def stub_snapshot_delete(self, context, snapshot_id): if snapshot_id == '-1': raise exc.NotFound +def stub_compute_volume_snapshot_delete(self, context, volume_id, snapshot_id, + delete_info): + pass + + def stub_snapshot_get(self, context, snapshot_id): if snapshot_id == '-1': raise exc.NotFound diff --git a/nova/tests/fake_policy.py b/nova/tests/fake_policy.py index afada9433c13..a50fd2e8920b 100644 --- a/nova/tests/fake_policy.py +++ b/nova/tests/fake_policy.py @@ -279,6 +279,8 @@ policy_data = """ "compute_extension:v3:os-used-limits:tenant": "is_admin:True", "compute_extension:migrations:index": "is_admin:True", "compute_extension:v3:os-migrations:index": "is_admin:True", + "compute_extension:os-assisted-volume-snapshots:create": "", + "compute_extension:os-assisted-volume-snapshots:delete": "", "volume:create": "", "volume:get": "", diff --git a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl index e00730a53b22..b4607875cf56 100644 --- a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl +++ b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.json.tpl @@ -136,6 +136,14 @@ "namespace": "http://docs.openstack.org/compute/ext/agents/api/v2", "updated": "%(timestamp)s" }, + { + "alias": "os-assisted-volume-snapshots", + "description": "%(text)s", + "links": [], + "name": "AssistedVolumeSnapshots", + "namespace": "http://docs.openstack.org/compute/ext/assisted-volume-snapshots/api/v2", + "updated": "%(timestamp)s" + }, { "alias": "os-attach-interfaces", "description": "Attach interface support.", diff --git a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl index 81955c4b78f6..a2840f77abe6 100644 --- a/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl +++ b/nova/tests/integrated/api_samples/all_extensions/extensions-get-resp.xml.tpl @@ -228,4 +228,7 @@ %(text)s + + %(text)s + diff --git a/nova/tests/integrated/api_samples/os-assisted-volume-snapshots/snapshot-create-assisted-req.json.tpl b/nova/tests/integrated/api_samples/os-assisted-volume-snapshots/snapshot-create-assisted-req.json.tpl new file mode 100644 index 000000000000..a1b94d1c7294 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-assisted-volume-snapshots/snapshot-create-assisted-req.json.tpl @@ -0,0 +1,10 @@ +{ + "snapshot": { + "display_name": "%(snapshot_name)s", + "display_description": "%(description)s", + "volume_id": "%(volume_id)s", + "force": false, + "assisted": true, + "create_info": {} + } +} diff --git a/nova/tests/integrated/api_samples/os-assisted-volume-snapshots/snapshot-create-assisted-req.xml.tpl b/nova/tests/integrated/api_samples/os-assisted-volume-snapshots/snapshot-create-assisted-req.xml.tpl new file mode 100644 index 000000000000..45feb5077f92 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-assisted-volume-snapshots/snapshot-create-assisted-req.xml.tpl @@ -0,0 +1,9 @@ + + + %(snapshot_name)s + %(description)s + %(volume_id)s + false + true + + diff --git a/nova/tests/integrated/api_samples/os-assisted-volume-snapshots/snapshot-create-assisted-resp.json.tpl b/nova/tests/integrated/api_samples/os-assisted-volume-snapshots/snapshot-create-assisted-resp.json.tpl new file mode 100644 index 000000000000..8d4e7f570982 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-assisted-volume-snapshots/snapshot-create-assisted-resp.json.tpl @@ -0,0 +1,6 @@ +{ + "snapshot": { + "id": 100, + "volumeId": "%(uuid)s" + } +} diff --git a/nova/tests/integrated/api_samples/os-assisted-volume-snapshots/snapshot-create-assisted-resp.xml.tpl b/nova/tests/integrated/api_samples/os-assisted-volume-snapshots/snapshot-create-assisted-resp.xml.tpl new file mode 100644 index 000000000000..5da7d148b153 --- /dev/null +++ b/nova/tests/integrated/api_samples/os-assisted-volume-snapshots/snapshot-create-assisted-resp.xml.tpl @@ -0,0 +1,2 @@ + + diff --git a/nova/tests/integrated/test_api_samples.py b/nova/tests/integrated/test_api_samples.py index 5b9e20bb2a4a..4375c48d34c1 100644 --- a/nova/tests/integrated/test_api_samples.py +++ b/nova/tests/integrated/test_api_samples.py @@ -3700,6 +3700,47 @@ class SnapshotsSampleXmlTests(SnapshotsSampleJsonTests): ctype = "xml" +class AssistedVolumeSnapshotsJsonTest(ApiSampleTestBaseV2): + """Assisted volume snapshots.""" + extension_name = ("nova.api.openstack.compute.contrib." + "assisted_volume_snapshots.Assisted_volume_snapshots") + + def _create_assisted_snapshot(self, subs): + self.stubs.Set(compute_api.API, 'volume_snapshot_create', + fakes.stub_compute_volume_snapshot_create) + + response = self._do_post("os-assisted-volume-snapshots", + "snapshot-create-assisted-req", + subs) + return response + + def test_snapshots_create_assisted(self): + subs = { + 'snapshot_name': 'snap-001', + 'description': 'Daily backup', + 'volume_id': '521752a6-acf6-4b2d-bc7a-119f9148cd8c' + } + subs.update(self._get_regexes()) + response = self._create_assisted_snapshot(subs) + self._verify_response("snapshot-create-assisted-resp", + subs, response, 200) + + def test_snapshots_delete_assisted(self): + self.stubs.Set(compute_api.API, 'volume_snapshot_delete', + fakes.stub_compute_volume_snapshot_delete) + snapshot_id = '100' + response = self._do_delete( + 'os-assisted-volume-snapshots/%s?delete_info=' + '{"volume_id":"521752a6-acf6-4b2d-bc7a-119f9148cd8c"}' + % snapshot_id) + self.assertEqual(response.status, 204) + self.assertEqual(response.read(), '') + + +class AssistedVolumeSnapshotsXmlTest(AssistedVolumeSnapshotsJsonTest): + ctype = "xml" + + class VolumeAttachmentsSampleBase(ServersSampleBase): def _stub_compute_api_get_instance_bdms(self, server_id):

AltStyle によって変換されたページ (->オリジナル) /