V3 jsonschema validation: snapshot_actions

This patch adds jsonschema validation for below snapshot_actions API
* POST /v3/{project_id}/snapshots/{snapshot_id}/action

Change-Id: I810f72777864947e7e44ddf668732e06db68c1f3
Partial-Implements: bp json-schema-validation
This commit is contained in:
Neha Alhat 2017-12-29 09:42:32 +05:30
parent 04847c42c7
commit 417f3b7331
4 changed files with 101 additions and 14 deletions

View File

@ -18,6 +18,8 @@ import webob
from cinder.api import extensions
from cinder.api.openstack import wsgi
from cinder.api.schemas import snapshot_actions
from cinder.api import validation
from cinder.i18n import _
from cinder import objects
from cinder.objects import fields
@ -31,6 +33,7 @@ class SnapshotActionsController(wsgi.Controller):
LOG.debug("SnapshotActionsController initialized")
@wsgi.action('os-update_snapshot_status')
@validation.schema(snapshot_actions.update_snapshot_status)
def _update_snapshot_status(self, req, id, body):
"""Update database fields related to status of a snapshot.
@ -41,11 +44,8 @@ class SnapshotActionsController(wsgi.Controller):
context = req.environ['cinder.context']
LOG.debug("body: %s", body)
try:
status = body['os-update_snapshot_status']['status']
except KeyError:
msg = _("'status' must be specified.")
raise webob.exc.HTTPBadRequest(explanation=msg)
status = body['os-update_snapshot_status']['status']
# Allowed state transitions
status_map = {fields.SnapshotStatus.CREATING:
@ -78,15 +78,6 @@ class SnapshotActionsController(wsgi.Controller):
progress = body['os-update_snapshot_status'].get('progress', None)
if progress:
# This is expected to be a string like '73%'
msg = _('progress must be an integer percentage')
try:
integer = int(progress[:-1])
except ValueError:
raise webob.exc.HTTPBadRequest(explanation=msg)
if integer < 0 or integer > 100 or progress[-1] != '%':
raise webob.exc.HTTPBadRequest(explanation=msg)
update_dict.update({'progress': progress})
LOG.info("Updating snapshot %(id)s with info %(dict)s",

View File

@ -0,0 +1,36 @@
# Copyright (C) 2018 NTT DATA
# 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.
"""
Schema for V3 snapshot actions API.
"""
update_snapshot_status = {
'type': 'object',
'properties': {
'os-update_snapshot_status': {
'type': 'object',
'properties': {
'status': {'type': 'string'},
'progress': {'format': 'progress'},
},
'required': ['status'],
'additionalProperties': False,
},
},
'required': ['os-update_snapshot_status'],
'additionalProperties': False,
}

View File

@ -171,6 +171,21 @@ def _validate_status(param_value):
return True
@jsonschema.FormatChecker.cls_checks('progress')
def _validate_progress(progress):
if progress:
try:
integer = int(progress[:-1])
except ValueError:
msg = _('progress must be an integer percentage')
raise exception.InvalidInput(reason=msg)
if integer < 0 or integer > 100 or progress[-1] != '%':
msg = _('progress must be an integer percentage between'
' 0 and 100')
raise exception.InvalidInput(reason=msg)
return True
@jsonschema.FormatChecker.cls_checks('base64')
def _validate_base64_format(instance):
try:

View File

@ -12,13 +12,17 @@
# License for the specific language governing permissions and limitations
# under the License.
import ddt
import mock
from oslo_serialization import jsonutils
from six.moves import http_client
import webob
from cinder.api.contrib import snapshot_actions
from cinder.api import microversions as mv
from cinder import context
from cinder import db
from cinder import exception
from cinder.objects import fields
from cinder import test
from cinder.tests.unit.api import fakes
@ -36,12 +40,14 @@ def fake_snapshot_get(context, snapshot_id):
return snapshot
@ddt.ddt
class SnapshotActionsTest(test.TestCase):
def setUp(self):
super(SnapshotActionsTest, self).setUp()
self.user_ctxt = context.RequestContext(
fake.USER_ID, fake.PROJECT_ID, auth_token=True)
self.controller = snapshot_actions.SnapshotActionsController()
@mock.patch('cinder.db.snapshot_update', autospec=True)
@mock.patch('cinder.db.sqlalchemy.api._snapshot_get',
@ -88,3 +94,42 @@ class SnapshotActionsTest(test.TestCase):
res = req.get_response(fakes.wsgi_app(
fake_auth_context=self.user_ctxt))
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
@mock.patch('cinder.db.snapshot_update', autospec=True)
@mock.patch('cinder.db.sqlalchemy.api._snapshot_get',
side_effect=fake_snapshot_get)
@mock.patch('cinder.db.snapshot_metadata_get', return_value=dict())
def test_update_snapshot_valid_progress(self, metadata_get, *args):
body = {'os-update_snapshot_status':
{'status': fields.SnapshotStatus.AVAILABLE,
'progress': '50%'}}
req = webob.Request.blank('/v2/%s/snapshots/%s/action' % (
fake.PROJECT_ID, fake.SNAPSHOT_ID))
req.method = "POST"
req.body = jsonutils.dump_as_bytes(body)
req.headers["content-type"] = "application/json"
res = req.get_response(fakes.wsgi_app(
fake_auth_context=self.user_ctxt))
self.assertEqual(http_client.ACCEPTED, res.status_int)
@ddt.data(({'os-update_snapshot_status':
{'status': fields.SnapshotStatus.AVAILABLE,
'progress': '50'}}, exception.InvalidInput),
({'os-update_snapshot_status':
{'status': fields.SnapshotStatus.AVAILABLE,
'progress': '103%'}}, exception.InvalidInput),
({'os-update_snapshot_status':
{'status': fields.SnapshotStatus.AVAILABLE,
'progress': " "}}, exception.InvalidInput),
({'os-update_snapshot_status':
{'status': fields.SnapshotStatus.AVAILABLE,
'progress': 50}}, exception.ValidationError))
@ddt.unpack
def test_update_snapshot_invalid_progress(self, body, exception_class):
req = webob.Request.blank('/v3/%s/snapshots/%s/action' % (
fake.PROJECT_ID, fake.SNAPSHOT_ID))
req.api_version_request = mv.get_api_version(mv.BASE_VERSION)
self.assertRaises(exception_class,
self.controller._update_snapshot_status,
req, body=body)