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:v3:os-migrations:discoverable": "",
|
||||||
"compute_extension:os-assisted-volume-snapshots:create": "rule:admin_api",
|
"compute_extension:os-assisted-volume-snapshots:create": "rule:admin_api",
|
||||||
"compute_extension:os-assisted-volume-snapshots:delete": "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:console_auth_tokens": "rule:admin_api",
|
||||||
"compute_extension:v3:os-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",
|
"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 webob import exc
|
||||||
|
|
||||||
from nova.api.openstack.compute.contrib import assisted_volume_snapshots as \
|
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.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.compute.plugins.v3 import volumes as volumes_v3
|
||||||
from nova.api.openstack import extensions
|
from nova.api.openstack import extensions
|
||||||
from nova.compute import api as compute_api
|
from nova.compute import api as compute_api
|
||||||
@ -1042,17 +1044,25 @@ class DeleteSnapshotTestCaseV2(DeleteSnapshotTestCaseV21):
|
|||||||
snapshot_cls = volumes.SnapshotController
|
snapshot_cls = volumes.SnapshotController
|
||||||
|
|
||||||
|
|
||||||
class AssistedSnapshotCreateTestCase(test.TestCase):
|
class AssistedSnapshotCreateTestCaseV21(test.TestCase):
|
||||||
def setUp(self):
|
assisted_snaps = assisted_snaps_v21
|
||||||
super(AssistedSnapshotCreateTestCase, self).setUp()
|
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',
|
self.stubs.Set(compute_api.API, 'volume_snapshot_create',
|
||||||
fake_compute_volume_snapshot_create)
|
fake_compute_volume_snapshot_create)
|
||||||
|
|
||||||
def test_assisted_create(self):
|
def test_assisted_create(self):
|
||||||
req = fakes.HTTPRequest.blank('/v2/fake/os-assisted-volume-snapshots')
|
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'
|
req.method = 'POST'
|
||||||
self.controller.create(req, body=body)
|
self.controller.create(req, body=body)
|
||||||
|
|
||||||
@ -1060,15 +1070,26 @@ class AssistedSnapshotCreateTestCase(test.TestCase):
|
|||||||
req = fakes.HTTPRequest.blank('/v2/fake/os-assisted-volume-snapshots')
|
req = fakes.HTTPRequest.blank('/v2/fake/os-assisted-volume-snapshots')
|
||||||
body = {'snapshot': {'volume_id': '1'}}
|
body = {'snapshot': {'volume_id': '1'}}
|
||||||
req.method = 'POST'
|
req.method = 'POST'
|
||||||
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create,
|
self.assertRaises(self.bad_request, self.controller.create,
|
||||||
req, body=body)
|
req, body=body)
|
||||||
|
|
||||||
|
|
||||||
class AssistedSnapshotDeleteTestCase(test.TestCase):
|
class AssistedSnapshotCreateTestCaseV2(AssistedSnapshotCreateTestCaseV21):
|
||||||
def setUp(self):
|
assisted_snaps = assisted_snaps_v2
|
||||||
super(AssistedSnapshotDeleteTestCase, self).setUp()
|
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',
|
self.stubs.Set(compute_api.API, 'volume_snapshot_delete',
|
||||||
fake_compute_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()]))
|
'&'.join(['%s=%s' % (k, v) for k, v in params.iteritems()]))
|
||||||
req.method = 'DELETE'
|
req.method = 'DELETE'
|
||||||
result = self.controller.delete(req, '5')
|
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):
|
def test_assisted_delete_missing_delete_info(self):
|
||||||
req = fakes.HTTPRequest.blank('/v2/fake/os-assisted-volume-snapshots')
|
req = fakes.HTTPRequest.blank('/v2/fake/os-assisted-volume-snapshots')
|
||||||
req.method = 'DELETE'
|
req.method = 'DELETE'
|
||||||
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.delete,
|
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.delete,
|
||||||
req, '5')
|
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:v3:os-migrations:index": "is_admin:True",
|
||||||
"compute_extension:os-assisted-volume-snapshots:create": "",
|
"compute_extension:os-assisted-volume-snapshots:create": "",
|
||||||
"compute_extension:os-assisted-volume-snapshots:delete": "",
|
"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:console_auth_tokens": "is_admin:True",
|
||||||
"compute_extension:v3:os-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",
|
"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
|
admin_password = nova.api.openstack.compute.plugins.v3.admin_password:AdminPassword
|
||||||
agents = nova.api.openstack.compute.plugins.v3.agents:Agents
|
agents = nova.api.openstack.compute.plugins.v3.agents:Agents
|
||||||
aggregates = nova.api.openstack.compute.plugins.v3.aggregates:Aggregates
|
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
|
attach_interfaces = nova.api.openstack.compute.plugins.v3.attach_interfaces:AttachInterfaces
|
||||||
availability_zone = nova.api.openstack.compute.plugins.v3.availability_zone:AvailabilityZone
|
availability_zone = nova.api.openstack.compute.plugins.v3.availability_zone:AvailabilityZone
|
||||||
baremetal_nodes = nova.api.openstack.compute.plugins.v3.baremetal_nodes:BareMetalNodes
|
baremetal_nodes = nova.api.openstack.compute.plugins.v3.baremetal_nodes:BareMetalNodes
|
||||||
|
Loading…
x
Reference in New Issue
Block a user