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:
Eli Qiao 2014-10-15 11:32:54 +08:00
parent 90dee8d431
commit 0621c2507f
11 changed files with 290 additions and 12 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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