Add os-assisted-volume-snapshots extension
Add a new API extension that exposes assisted volume snapshot capabilities. This extension is admin only by default. We expect it to only be called by Cinder. If you have your deployment set up in such a way that your adminURL is different from the public, this extension can only be loaded in the admin API instance. Cinder will pull that URL out of the service catalog to use. Part of blueprint qemu-assisted-snapshots Change-Id: I79e22ab6ef66fa16dc534a4336e766065702b2f5
This commit is contained in:
parent
87b3d3a6f0
commit
fa13644b05
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -52,6 +52,9 @@
|
||||
<extension alias="os-aggregates" updated="2012-01-12T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/aggregates/api/v1.1" name="Aggregates">
|
||||
<description>Admin-only aggregate administration.</description>
|
||||
</extension>
|
||||
<extension alias="os-assisted-volume-snapshots" updated="2013-08-15T00:00:00-00:00" namespace="http://docs.openstack.org/compute/ext/assisted-volume-snapshots/api/v2" name="AssistedVolumeSnapshots">
|
||||
<description>Assisted volume snapshots.</description>
|
||||
</extension>
|
||||
<extension alias="os-attach-interfaces" updated="2012-07-22T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/interfaces/api/v1.1" name="AttachInterfaces">
|
||||
<description>Attach interface support.</description>
|
||||
</extension>
|
||||
@ -197,9 +200,6 @@
|
||||
<extension alias="os-quota-sets" updated="2011-08-08T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/quotas-sets/api/v1.1" name="Quotas">
|
||||
<description>Quotas management support.</description>
|
||||
</extension>
|
||||
<extension alias="os-user-quotas" updated="2013-07-18T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/user_quotas/api/v1.1" name="UserQuotas">
|
||||
<description>Project user quota support.</description>
|
||||
</extension>
|
||||
<extension alias="os-rescue" updated="2011-08-18T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/rescue/api/v1.1" name="Rescue">
|
||||
<description>Instance rescue mode.</description>
|
||||
</extension>
|
||||
@ -239,6 +239,9 @@
|
||||
<extension alias="os-user-data" updated="2012-08-07T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/userdata/api/v1.1" name="UserData">
|
||||
<description>Add user_data to the Create Server v1.1 API.</description>
|
||||
</extension>
|
||||
<extension alias="os-user-quotas" updated="2013-07-18T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/user_quotas/api/v1.1" name="UserQuotas">
|
||||
<description>Project user quota support.</description>
|
||||
</extension>
|
||||
<extension alias="os-virtual-interfaces" updated="2011-08-17T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/virtual_interfaces/api/v1.1" name="VirtualInterfaces">
|
||||
<description>Virtual interface support.</description>
|
||||
</extension>
|
||||
@ -248,4 +251,4 @@
|
||||
<extension alias="os-volumes" updated="2011-03-25T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/volumes/api/v1.1" name="Volumes">
|
||||
<description>Volumes support.</description>
|
||||
</extension>
|
||||
</extensions>
|
||||
</extensions>
|
@ -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": {}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<snapshot>
|
||||
<display_name>snap-001</display_name>
|
||||
<display_description>Daily backup</display_description>
|
||||
<volume_id>521752a6-acf6-4b2d-bc7a-119f9148cd8c</volume_id>
|
||||
<force>false</force>
|
||||
<assisted>true</assisted>
|
||||
<create_info/>
|
||||
</snapshot>
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"snapshot": {
|
||||
"id": 100,
|
||||
"volumeId": "521752a6-acf6-4b2d-bc7a-119f9148cd8c"
|
||||
}
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<snapshot volumeId="521752a6-acf6-4b2d-bc7a-119f9148cd8c" id="100"/>
|
@ -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": "",
|
||||
|
110
nova/api/openstack/compute/contrib/assisted_volume_snapshots.py
Normal file
110
nova/api/openstack/compute/contrib/assisted_volume_snapshots.py
Normal file
@ -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]
|
@ -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')
|
||||
|
@ -176,6 +176,7 @@ class ExtensionControllerTest(ExtensionTestCase):
|
||||
self.ext_list = [
|
||||
"AdminActions",
|
||||
"Aggregates",
|
||||
"AssistedVolumeSnapshots",
|
||||
"AvailabilityZone",
|
||||
"Agents",
|
||||
"Certificates",
|
||||
|
@ -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
|
||||
|
@ -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": "",
|
||||
|
@ -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.",
|
||||
|
@ -228,4 +228,7 @@
|
||||
<extension alias="os-migrations" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/migrations/api/v2.0" name="Migrations">
|
||||
<description>%(text)s</description>
|
||||
</extension>
|
||||
<extension alias="os-assisted-volume-snapshots" updated="%(timestamp)s" namespace="http://docs.openstack.org/compute/ext/assisted-volume-snapshots/api/v2" name="AssistedVolumeSnapshots">
|
||||
<description>%(text)s</description>
|
||||
</extension>
|
||||
</extensions>
|
||||
|
@ -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": {}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<snapshot>
|
||||
<display_name>%(snapshot_name)s</display_name>
|
||||
<display_description>%(description)s</display_description>
|
||||
<volume_id>%(volume_id)s</volume_id>
|
||||
<force>false</force>
|
||||
<assisted>true</assisted>
|
||||
<create_info/>
|
||||
</snapshot>
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"snapshot": {
|
||||
"id": 100,
|
||||
"volumeId": "%(uuid)s"
|
||||
}
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?>
|
||||
<snapshot volumeId="521752a6-acf6-4b2d-bc7a-119f9148cd8c" id="100"/>
|
@ -3697,6 +3697,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):
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user