Port assisted-volume-snapshots extension to v2.1
This patch ports assisted-volume-snapshots to v2.1 and make v2 and v2.1 share unit test cases. This patch addes a schema to do the input validation for snapshots_create The differences between v2 and v3 are described on the wiki page https://wiki.openstack.org/wiki/NovaAPIv2tov3 . Partially implements blueprint v2-on-v3-api Change-Id: I5b7be1de8ac2628a287897dcc5ca0eaf7a8957a2
This commit is contained in:
parent
90dee8d431
commit
0621c2507f
@ -0,0 +1,10 @@
|
||||
{
|
||||
"snapshot": {
|
||||
"volume_id": "521752a6-acf6-4b2d-bc7a-119f9148cd8c",
|
||||
"create_info": {
|
||||
"snapshot_id": "421752a6-acf6-4b2d-bc7a-119f9148cd8c",
|
||||
"type": "qcow2",
|
||||
"new_file": "new_file_name"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"snapshot": {
|
||||
"id": 100,
|
||||
"volumeId": "521752a6-acf6-4b2d-bc7a-119f9148cd8c"
|
||||
}
|
||||
}
|
@ -309,6 +309,9 @@
|
||||
"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",
|
||||
"compute_extension:v3:os-assisted-volume-snapshots:create": "rule:admin_api",
|
||||
"compute_extension:v3:os-assisted-volume-snapshots:delete": "rule:admin_api",
|
||||
"compute_extension:v3:os-assisted-volume-snapshots:discoverable": "",
|
||||
"compute_extension:console_auth_tokens": "rule:admin_api",
|
||||
"compute_extension:v3:os-console-auth-tokens": "rule:admin_api",
|
||||
"compute_extension:os-server-external-events:create": "rule:admin_api",
|
||||
|
@ -0,0 +1,110 @@
|
||||
# Copyright 2013 Red Hat, Inc.
|
||||
# Copyright 2014 IBM Corp.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""The Assisted volume snapshots extension."""
|
||||
|
||||
from oslo.serialization import jsonutils
|
||||
import six
|
||||
from webob import exc
|
||||
|
||||
from nova.api.openstack.compute.schemas.v3 import assisted_volume_snapshots
|
||||
from nova.api.openstack import extensions
|
||||
from nova.api.openstack import wsgi
|
||||
from nova.api import validation
|
||||
from nova import compute
|
||||
from nova import exception
|
||||
from nova.i18n import _
|
||||
from nova.openstack.common import log as logging
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
ALIAS = 'os-assisted-volume-snapshots'
|
||||
authorize = extensions.extension_authorizer('compute',
|
||||
'v3:' + ALIAS)
|
||||
|
||||
|
||||
class AssistedVolumeSnapshotsController(wsgi.Controller):
|
||||
"""The Assisted volume snapshots API controller for the OpenStack API."""
|
||||
|
||||
def __init__(self):
|
||||
self.compute_api = compute.API()
|
||||
super(AssistedVolumeSnapshotsController, self).__init__()
|
||||
|
||||
@extensions.expected_errors(400)
|
||||
@validation.schema(assisted_volume_snapshots.snapshots_create)
|
||||
def create(self, req, body):
|
||||
"""Creates a new snapshot."""
|
||||
context = req.environ['nova.context']
|
||||
authorize(context, action='create')
|
||||
|
||||
snapshot = body['snapshot']
|
||||
create_info = snapshot['create_info']
|
||||
volume_id = snapshot['volume_id']
|
||||
|
||||
LOG.audit(_("Create assisted snapshot from volume %s"), volume_id,
|
||||
context=context)
|
||||
try:
|
||||
return self.compute_api.volume_snapshot_create(context, volume_id,
|
||||
create_info)
|
||||
except (exception.VolumeBDMNotFound,
|
||||
exception.InvalidVolume) as error:
|
||||
raise exc.HTTPBadRequest(explanation=error.format_message())
|
||||
|
||||
@wsgi.response(204)
|
||||
@extensions.expected_errors((400, 404))
|
||||
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 exc.HTTPBadRequest(explanation=six.text_type(e))
|
||||
|
||||
try:
|
||||
self.compute_api.volume_snapshot_delete(context, volume_id,
|
||||
id, delete_info)
|
||||
except (exception.VolumeBDMNotFound,
|
||||
exception.InvalidVolume) as error:
|
||||
raise exc.HTTPBadRequest(explanation=error.format_message())
|
||||
except exception.NotFound as e:
|
||||
return exc.HTTPNotFound(explanation=e.format_message())
|
||||
|
||||
|
||||
class AssistedVolumeSnapshots(extensions.V3APIExtensionBase):
|
||||
"""Assisted volume snapshots."""
|
||||
|
||||
name = "AssistedVolumeSnapshots"
|
||||
alias = ALIAS
|
||||
version = 1
|
||||
|
||||
def get_resources(self):
|
||||
res = [extensions.ResourceExtension(ALIAS,
|
||||
AssistedVolumeSnapshotsController())]
|
||||
return res
|
||||
|
||||
def get_controller_extensions(self):
|
||||
"""It's an abstract function V3APIExtensionBase and the extension
|
||||
will not be loaded without it.
|
||||
"""
|
||||
return []
|
@ -0,0 +1,50 @@
|
||||
# Copyright 2014 IBM Corporation. All rights reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
snapshots_create = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'snapshot': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'volume_id': {
|
||||
'type': 'string', 'minLength': 1,
|
||||
},
|
||||
'create_info': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'snapshot_id': {
|
||||
'type': 'string', 'minLength': 1,
|
||||
},
|
||||
'type': {
|
||||
'type': 'string', 'enum': ['qcow2'],
|
||||
},
|
||||
'new_file': {
|
||||
'type': 'string', 'minLength': 1,
|
||||
},
|
||||
'id': {
|
||||
'type': 'string', 'minLength': 1,
|
||||
},
|
||||
},
|
||||
'required': ['snapshot_id', 'type', 'new_file'],
|
||||
'additionalProperties': False,
|
||||
},
|
||||
},
|
||||
'required': ['volume_id', 'create_info'],
|
||||
'additionalProperties': False,
|
||||
},
|
||||
'required': ['snapshot'],
|
||||
'additionalProperties': False,
|
||||
},
|
||||
}
|
@ -25,8 +25,10 @@ import webob
|
||||
from webob import exc
|
||||
|
||||
from nova.api.openstack.compute.contrib import assisted_volume_snapshots as \
|
||||
assisted_snaps
|
||||
assisted_snaps_v2
|
||||
from nova.api.openstack.compute.contrib import volumes
|
||||
from nova.api.openstack.compute.plugins.v3 import assisted_volume_snapshots as \
|
||||
assisted_snaps_v21
|
||||
from nova.api.openstack.compute.plugins.v3 import volumes as volumes_v3
|
||||
from nova.api.openstack import extensions
|
||||
from nova.compute import api as compute_api
|
||||
@ -1042,17 +1044,25 @@ class DeleteSnapshotTestCaseV2(DeleteSnapshotTestCaseV21):
|
||||
snapshot_cls = volumes.SnapshotController
|
||||
|
||||
|
||||
class AssistedSnapshotCreateTestCase(test.TestCase):
|
||||
def setUp(self):
|
||||
super(AssistedSnapshotCreateTestCase, self).setUp()
|
||||
class AssistedSnapshotCreateTestCaseV21(test.TestCase):
|
||||
assisted_snaps = assisted_snaps_v21
|
||||
bad_request = exception.ValidationError
|
||||
|
||||
self.controller = assisted_snaps.AssistedVolumeSnapshotsController()
|
||||
def setUp(self):
|
||||
super(AssistedSnapshotCreateTestCaseV21, self).setUp()
|
||||
|
||||
self.controller = \
|
||||
self.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': {}}}
|
||||
body = {'snapshot':
|
||||
{'volume_id': '1',
|
||||
'create_info': {'type': 'qcow2',
|
||||
'new_file': 'new_file',
|
||||
'snapshot_id': 'snapshot_id'}}}
|
||||
req.method = 'POST'
|
||||
self.controller.create(req, body=body)
|
||||
|
||||
@ -1060,15 +1070,26 @@ class AssistedSnapshotCreateTestCase(test.TestCase):
|
||||
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,
|
||||
self.assertRaises(self.bad_request, self.controller.create,
|
||||
req, body=body)
|
||||
|
||||
|
||||
class AssistedSnapshotDeleteTestCase(test.TestCase):
|
||||
def setUp(self):
|
||||
super(AssistedSnapshotDeleteTestCase, self).setUp()
|
||||
class AssistedSnapshotCreateTestCaseV2(AssistedSnapshotCreateTestCaseV21):
|
||||
assisted_snaps = assisted_snaps_v2
|
||||
bad_request = webob.exc.HTTPBadRequest
|
||||
|
||||
self.controller = assisted_snaps.AssistedVolumeSnapshotsController()
|
||||
|
||||
class AssistedSnapshotDeleteTestCaseV21(test.TestCase):
|
||||
assisted_snaps = assisted_snaps_v21
|
||||
|
||||
def _check_status(self, expected_status, res, controller_method):
|
||||
self.assertEqual(expected_status, controller_method.wsgi_code)
|
||||
|
||||
def setUp(self):
|
||||
super(AssistedSnapshotDeleteTestCaseV21, self).setUp()
|
||||
|
||||
self.controller = \
|
||||
self.assisted_snaps.AssistedVolumeSnapshotsController()
|
||||
self.stubs.Set(compute_api.API, 'volume_snapshot_delete',
|
||||
fake_compute_volume_snapshot_delete)
|
||||
|
||||
@ -1081,10 +1102,17 @@ class AssistedSnapshotDeleteTestCase(test.TestCase):
|
||||
'&'.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)
|
||||
self._check_status(204, result, self.controller.delete)
|
||||
|
||||
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')
|
||||
|
||||
|
||||
class AssistedSnapshotDeleteTestCaseV2(AssistedSnapshotDeleteTestCaseV21):
|
||||
assisted_snaps = assisted_snaps_v2
|
||||
|
||||
def _check_status(self, expected_status, res, controller_method):
|
||||
self.assertEqual(expected_status, res.status_int)
|
||||
|
@ -327,6 +327,8 @@ policy_data = """
|
||||
"compute_extension:v3:os-migrations:index": "is_admin:True",
|
||||
"compute_extension:os-assisted-volume-snapshots:create": "",
|
||||
"compute_extension:os-assisted-volume-snapshots:delete": "",
|
||||
"compute_extension:v3:os-assisted-volume-snapshots:create": "",
|
||||
"compute_extension:v3:os-assisted-volume-snapshots:delete": "",
|
||||
"compute_extension:console_auth_tokens": "is_admin:True",
|
||||
"compute_extension:v3:os-console-auth-tokens": "is_admin:True",
|
||||
"compute_extension:os-server-external-events:create": "rule:admin_api",
|
||||
|
@ -0,0 +1,10 @@
|
||||
{
|
||||
"snapshot": {
|
||||
"volume_id": "%(volume_id)s",
|
||||
"create_info": {
|
||||
"snapshot_id": "%(snapshot_id)s",
|
||||
"type": "%(type)s",
|
||||
"new_file": "%(new_file)s"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"snapshot": {
|
||||
"id": 100,
|
||||
"volumeId": "%(uuid)s"
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
# Copyright 2014 IBM Corp.
|
||||
#
|
||||
# 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 nova.compute import api as compute_api
|
||||
from nova.tests.unit.api.openstack import fakes
|
||||
from nova.tests.unit.integrated.v3 import test_servers
|
||||
|
||||
|
||||
class AssistedVolumeSnapshotsJsonTests(test_servers.ServersSampleBase):
|
||||
extension_name = "os-assisted-volume-snapshots"
|
||||
|
||||
def test_create(self):
|
||||
"""Create a volume snapshots."""
|
||||
self.stubs.Set(compute_api.API, 'volume_snapshot_create',
|
||||
fakes.stub_compute_volume_snapshot_create)
|
||||
|
||||
subs = {
|
||||
'volume_id': '521752a6-acf6-4b2d-bc7a-119f9148cd8c',
|
||||
'snapshot_id': '421752a6-acf6-4b2d-bc7a-119f9148cd8c',
|
||||
'type': 'qcow2',
|
||||
'new_file': 'new_file_name'
|
||||
}
|
||||
|
||||
response = self._do_post("os-assisted-volume-snapshots",
|
||||
"snapshot-create-assisted-req",
|
||||
subs)
|
||||
subs.update(self._get_regexes())
|
||||
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_code, 204)
|
||||
self.assertEqual(response.content, '')
|
@ -61,6 +61,7 @@ nova.api.v3.extensions =
|
||||
admin_password = nova.api.openstack.compute.plugins.v3.admin_password:AdminPassword
|
||||
agents = nova.api.openstack.compute.plugins.v3.agents:Agents
|
||||
aggregates = nova.api.openstack.compute.plugins.v3.aggregates:Aggregates
|
||||
assisted_volume_snapshots = nova.api.openstack.compute.plugins.v3.assisted_volume_snapshots:AssistedVolumeSnapshots
|
||||
attach_interfaces = nova.api.openstack.compute.plugins.v3.attach_interfaces:AttachInterfaces
|
||||
availability_zone = nova.api.openstack.compute.plugins.v3.availability_zone:AvailabilityZone
|
||||
baremetal_nodes = nova.api.openstack.compute.plugins.v3.baremetal_nodes:BareMetalNodes
|
||||
|
Loading…
Reference in New Issue
Block a user