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:
Russell Bryant
2013年08月16日 20:44:37 +00:00
parent 87b3d3a6f0
commit fa13644b05

View File

@@ -128,6 +128,14 @@
"namespace": "http://docs.openstack.org/compute/ext/aggregates/api/v1.1",
"updated": "2012年01月12日T00: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月15日T00: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月08日T00: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月18日T00: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月07日T00: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月18日T00:00:00+00:00"
},
{
"alias": "os-virtual-interfaces",
"description": "Virtual interface support.",
@@ -609,4 +617,4 @@
"updated": "2011年03月25日T00:00:00+00:00"
}
]
}
}

View File

@@ -52,6 +52,9 @@
<extension alias="os-aggregates" updated="2012年01月12日T00: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月15日T00: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月22日T00: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月08日T00: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月18日T00: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月18日T00: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月07日T00: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月18日T00: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月17日T00: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月25日T00:00:00+00:00" namespace="http://docs.openstack.org/compute/ext/volumes/api/v1.1" name="Volumes">
<description>Volumes support.</description>
</extension>
</extensions>
</extensions>

View File

@@ -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": {}
}
}

View File

@@ -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>

View File

@@ -0,0 +1,6 @@
{
"snapshot": {
"id": 100,
"volumeId": "521752a6-acf6-4b2d-bc7a-119f9148cd8c"
}
}

View File

@@ -0,0 +1,2 @@
<?xml version='1.0' encoding='UTF-8'?>
<snapshot volumeId="521752a6-acf6-4b2d-bc7a-119f9148cd8c" id="100"/>

View File

@@ -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": "",

View 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月29日T00:00:00-00:00"
def get_resources(self):
resource = extensions.ResourceExtension('os-assisted-volume-snapshots',
AssistedVolumeSnapshotsController())
return [resource]

View File

@@ -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')

View File

@@ -176,6 +176,7 @@ class ExtensionControllerTest(ExtensionTestCase):
self.ext_list = [
"AdminActions",
"Aggregates",
"AssistedVolumeSnapshots",
"AvailabilityZone",
"Agents",
"Certificates",

View File

@@ -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

View File

@@ -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": "",

View File

@@ -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.",

View File

@@ -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>

View File

@@ -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": {}
}
}

View File

@@ -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>

View File

@@ -0,0 +1,6 @@
{
"snapshot": {
"id": 100,
"volumeId": "%(uuid)s"
}
}

View File

@@ -0,0 +1,2 @@
<?xml version='1.0' encoding='UTF-8'?>
<snapshot volumeId="521752a6-acf6-4b2d-bc7a-119f9148cd8c" id="100"/>

View File

@@ -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):
Reference in New Issue
openstack/nova
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.

The note is not visible to the blocked user.