V3 jsonschema validation: Backups
This patch adds jsonschema validation for below Backups API's * POST /v3/{project_id}/backups * PUT /v3/{project_id}/backups/{backup_id} * POST /v3/{project_id}/backups/{backup_id}/restore * POST /v3/{project_id}/backups/{backup_id}/import_record Made changes to unit tests to pass body as keyword argument as wsgi calls action method [1] and passes body as keyword argument. [1] https://github.com/openstack/cinder/blob/master/cinder/api/openstack/wsgi.py#L997 Change-Id: Idd6c6be1c8bdf4dcf730f67e75a58a0329fe5259 Partial-Implements: bp json-schema-validation
This commit is contained in:
parent
b6d2ec994c
commit
1dc6fd93c2
cinder
api
backup
tests/unit/api
@ -18,6 +18,7 @@
|
||||
"""The backups api."""
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import strutils
|
||||
from six.moves import http_client
|
||||
from webob import exc
|
||||
|
||||
@ -25,10 +26,11 @@ from cinder.api import common
|
||||
from cinder.api import extensions
|
||||
from cinder.api import microversions as mv
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api.schemas import backups as backup
|
||||
from cinder.api import validation
|
||||
from cinder.api.views import backups as backup_views
|
||||
from cinder import backup as backupAPI
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
from cinder import utils
|
||||
from cinder import volume as volumeAPI
|
||||
|
||||
@ -141,29 +143,25 @@ class BackupsController(wsgi.Controller):
|
||||
# immediately
|
||||
# - maybe also do validation of swift container name
|
||||
@wsgi.response(http_client.ACCEPTED)
|
||||
@validation.schema(backup.create, '2.0', '3.42')
|
||||
@validation.schema(backup.create_backup_v343, '3.43')
|
||||
def create(self, req, body):
|
||||
"""Create a new backup."""
|
||||
LOG.debug('Creating new backup %s', body)
|
||||
self.assert_valid_body(body, 'backup')
|
||||
|
||||
context = req.environ['cinder.context']
|
||||
backup = body['backup']
|
||||
req_version = req.api_version_request
|
||||
|
||||
try:
|
||||
volume_id = backup['volume_id']
|
||||
except KeyError:
|
||||
msg = _("Incorrect request body format")
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
backup = body['backup']
|
||||
container = backup.get('container', None)
|
||||
if container:
|
||||
utils.check_string_length(container, 'Backup container',
|
||||
min_length=0, max_length=255)
|
||||
self.validate_name_and_description(backup)
|
||||
volume_id = backup['volume_id']
|
||||
|
||||
name = backup.get('name', None)
|
||||
description = backup.get('description', None)
|
||||
incremental = backup.get('incremental', False)
|
||||
force = backup.get('force', False)
|
||||
incremental = strutils.bool_from_string(backup.get(
|
||||
'incremental', False), strict=True)
|
||||
force = strutils.bool_from_string(backup.get(
|
||||
'force', False), strict=True)
|
||||
snapshot_id = backup.get('snapshot_id', None)
|
||||
metadata = backup.get('metadata', None) if req_version.matches(
|
||||
mv.BACKUP_METADATA) else None
|
||||
@ -190,11 +188,11 @@ class BackupsController(wsgi.Controller):
|
||||
return retval
|
||||
|
||||
@wsgi.response(http_client.ACCEPTED)
|
||||
@validation.schema(backup.restore)
|
||||
def restore(self, req, id, body):
|
||||
"""Restore an existing backup to a volume."""
|
||||
LOG.debug('Restoring backup %(backup_id)s (%(body)s)',
|
||||
{'backup_id': id, 'body': body})
|
||||
self.assert_valid_body(body, 'restore')
|
||||
|
||||
context = req.environ['cinder.context']
|
||||
restore = body['restore']
|
||||
@ -241,19 +239,15 @@ class BackupsController(wsgi.Controller):
|
||||
return retval
|
||||
|
||||
@wsgi.response(http_client.CREATED)
|
||||
@validation.schema(backup.import_record)
|
||||
def import_record(self, req, body):
|
||||
"""Import a backup."""
|
||||
LOG.debug('Importing record from %s.', body)
|
||||
self.assert_valid_body(body, 'backup-record')
|
||||
context = req.environ['cinder.context']
|
||||
import_data = body['backup-record']
|
||||
# Verify that body elements are provided
|
||||
try:
|
||||
backup_service = import_data['backup_service']
|
||||
backup_url = import_data['backup_url']
|
||||
except KeyError:
|
||||
msg = _("Incorrect request body format.")
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
backup_service = import_data['backup_service']
|
||||
backup_url = import_data['backup_url']
|
||||
|
||||
LOG.debug('Importing backup using %(service)s and url %(url)s.',
|
||||
{'service': backup_service, 'url': backup_url})
|
||||
|
||||
|
108
cinder/api/schemas/backups.py
Normal file
108
cinder/api/schemas/backups.py
Normal file
@ -0,0 +1,108 @@
|
||||
# Copyright (C) 2017 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 Backups API.
|
||||
|
||||
"""
|
||||
|
||||
import copy
|
||||
|
||||
from cinder.api.validation import parameter_types
|
||||
|
||||
|
||||
create = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'type': 'object',
|
||||
'backup': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'volume_id': parameter_types.uuid,
|
||||
'container': parameter_types.container,
|
||||
'description': parameter_types.description,
|
||||
'incremental': parameter_types.boolean,
|
||||
'force': parameter_types.boolean,
|
||||
'name': parameter_types.name_allow_zero_min_length,
|
||||
'snapshot_id': parameter_types.uuid,
|
||||
},
|
||||
'required': ['volume_id'],
|
||||
'additionalProperties': False,
|
||||
},
|
||||
},
|
||||
'required': ['backup'],
|
||||
'additionalProperties': False,
|
||||
}
|
||||
|
||||
|
||||
create_backup_v343 = copy.deepcopy(create)
|
||||
create_backup_v343['properties']['backup']['properties'][
|
||||
'metadata'] = parameter_types.extra_specs
|
||||
|
||||
|
||||
update = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'type': 'object',
|
||||
'backup': {
|
||||
'type': ['object', 'null'],
|
||||
'properties': {
|
||||
'name': parameter_types.name_allow_zero_min_length,
|
||||
'description': parameter_types.description,
|
||||
},
|
||||
'additionalProperties': False,
|
||||
},
|
||||
},
|
||||
'required': ['backup'],
|
||||
'additionalProperties': False,
|
||||
}
|
||||
|
||||
update_backup_v343 = copy.deepcopy(update)
|
||||
update_backup_v343['properties']['backup']['properties'][
|
||||
'metadata'] = parameter_types.extra_specs
|
||||
|
||||
restore = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'type': 'object',
|
||||
'restore': {
|
||||
'type': ['object', 'null'],
|
||||
'properties': {
|
||||
'name': parameter_types.name_allow_zero_min_length,
|
||||
'volume_id': parameter_types.uuid
|
||||
},
|
||||
'additionalProperties': False,
|
||||
},
|
||||
},
|
||||
'required': ['restore'],
|
||||
'additionalProperties': False,
|
||||
}
|
||||
|
||||
import_record = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'type': 'object',
|
||||
'backup-record': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'backup_service': parameter_types.backup_service,
|
||||
'backup_url': parameter_types.backup_url
|
||||
},
|
||||
'required': ['backup_service', 'backup_url'],
|
||||
'additionalProperties': False,
|
||||
},
|
||||
},
|
||||
'required': ['backup-record'],
|
||||
'additionalProperties': False,
|
||||
}
|
@ -16,13 +16,13 @@
|
||||
"""The backups V3 API."""
|
||||
|
||||
from oslo_log import log as logging
|
||||
from webob import exc
|
||||
|
||||
from cinder.api.contrib import backups as backups_v2
|
||||
from cinder.api import microversions as mv
|
||||
from cinder.api.openstack import wsgi
|
||||
from cinder.api.schemas import backups as backup
|
||||
from cinder.api.v3.views import backups as backup_views
|
||||
from cinder.i18n import _
|
||||
from cinder.api import validation
|
||||
from cinder.policies import backups as policy
|
||||
|
||||
|
||||
@ -35,15 +35,15 @@ class BackupsController(backups_v2.BackupsController):
|
||||
_view_builder_class = backup_views.ViewBuilder
|
||||
|
||||
@wsgi.Controller.api_version(mv.BACKUP_UPDATE)
|
||||
@validation.schema(backup.update, '3.9', '3.42')
|
||||
@validation.schema(backup.update_backup_v343, '3.43')
|
||||
def update(self, req, id, body):
|
||||
"""Update a backup."""
|
||||
context = req.environ['cinder.context']
|
||||
self.assert_valid_body(body, 'backup')
|
||||
req_version = req.api_version_request
|
||||
|
||||
backup_update = body['backup']
|
||||
|
||||
self.validate_name_and_description(backup_update)
|
||||
update_dict = {}
|
||||
if 'name' in backup_update:
|
||||
update_dict['display_name'] = backup_update.pop('name')
|
||||
@ -53,10 +53,6 @@ class BackupsController(backups_v2.BackupsController):
|
||||
if (req_version.matches(
|
||||
mv.BACKUP_METADATA) and 'metadata' in backup_update):
|
||||
update_dict['metadata'] = backup_update.pop('metadata')
|
||||
# Check no unsupported fields.
|
||||
if backup_update:
|
||||
msg = _("Unsupported fields %s.") % (", ".join(backup_update))
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
new_backup = self.backup_api.update(context, id, update_dict)
|
||||
|
||||
|
@ -175,3 +175,13 @@ uuid_allow_null = {
|
||||
|
||||
metadata_allows_null = copy.deepcopy(extra_specs)
|
||||
metadata_allows_null['type'] = ['object', 'null']
|
||||
|
||||
|
||||
container = {
|
||||
'type': ['string', 'null'], 'minLength': 0, 'maxLength': 255}
|
||||
|
||||
|
||||
backup_url = {'type': 'string', 'minLength': 1, 'format': 'base64'}
|
||||
|
||||
|
||||
backup_service = {'type': 'string', 'minLength': 0, 'maxLength': 255}
|
||||
|
@ -18,6 +18,7 @@ Internal implementation of request Body validating middleware.
|
||||
|
||||
"""
|
||||
|
||||
import base64
|
||||
import re
|
||||
|
||||
import jsonschema
|
||||
@ -124,6 +125,22 @@ def _validate_status(param_value):
|
||||
return True
|
||||
|
||||
|
||||
@jsonschema.FormatChecker.cls_checks('base64')
|
||||
def _validate_base64_format(instance):
|
||||
try:
|
||||
if isinstance(instance, six.text_type):
|
||||
instance = instance.encode('utf-8')
|
||||
base64.decodestring(instance)
|
||||
except base64.binascii.Error:
|
||||
return False
|
||||
except TypeError:
|
||||
# The name must be string type. If instance isn't string type, the
|
||||
# TypeError will be raised at here.
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class FormatChecker(jsonschema.FormatChecker):
|
||||
"""A FormatChecker can output the message from cause exception
|
||||
|
||||
|
@ -40,7 +40,6 @@ from cinder.policies import backups as policy
|
||||
import cinder.policy
|
||||
from cinder import quota
|
||||
from cinder import quota_utils
|
||||
from cinder import utils
|
||||
import cinder.volume
|
||||
from cinder.volume import utils as volume_utils
|
||||
|
||||
@ -200,7 +199,6 @@ class API(base.Base):
|
||||
force=False, snapshot_id=None, metadata=None):
|
||||
"""Make the RPC call to create a volume backup."""
|
||||
context.authorize(policy.CREATE_POLICY)
|
||||
utils.check_metadata_properties(metadata)
|
||||
volume = self.volume_api.get(context, volume_id)
|
||||
snapshot = None
|
||||
if snapshot_id:
|
||||
|
@ -21,6 +21,7 @@ import ddt
|
||||
import mock
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import timeutils
|
||||
import six
|
||||
from six.moves import http_client
|
||||
import webob
|
||||
|
||||
@ -482,10 +483,7 @@ class BackupsAPITestCase(test.TestCase):
|
||||
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
|
||||
|
||||
@mock.patch('cinder.db.service_get_all')
|
||||
@mock.patch(
|
||||
'cinder.api.openstack.wsgi.Controller.validate_name_and_description')
|
||||
def test_create_backup_json(self, mock_validate,
|
||||
_mock_service_get_all):
|
||||
def test_create_backup_json(self, _mock_service_get_all):
|
||||
_mock_service_get_all.return_value = [
|
||||
{'availability_zone': 'fake_az', 'host': 'testhost',
|
||||
'disabled': 0, 'updated_at': timeutils.utcnow(),
|
||||
@ -493,8 +491,8 @@ class BackupsAPITestCase(test.TestCase):
|
||||
|
||||
volume = utils.create_volume(self.context, size=5)
|
||||
|
||||
body = {"backup": {"display_name": "nightly001",
|
||||
"display_description":
|
||||
body = {"backup": {"name": "nightly001",
|
||||
"description":
|
||||
"Nightly Backup 03-Sep-2012",
|
||||
"volume_id": volume.id,
|
||||
"container": "nightlybackups",
|
||||
@ -514,15 +512,11 @@ class BackupsAPITestCase(test.TestCase):
|
||||
_mock_service_get_all.assert_called_once_with(mock.ANY,
|
||||
disabled=False,
|
||||
topic='cinder-backup')
|
||||
self.assertTrue(mock_validate.called)
|
||||
|
||||
volume.destroy()
|
||||
|
||||
@mock.patch('cinder.db.service_get_all')
|
||||
@mock.patch(
|
||||
'cinder.api.openstack.wsgi.Controller.validate_name_and_description')
|
||||
def test_create_backup_with_metadata(self, mock_validate,
|
||||
_mock_service_get_all):
|
||||
def test_create_backup_with_metadata(self, _mock_service_get_all):
|
||||
_mock_service_get_all.return_value = [
|
||||
{'availability_zone': 'fake_az', 'host': 'testhost',
|
||||
'disabled': 0, 'updated_at': timeutils.utcnow(),
|
||||
@ -530,8 +524,8 @@ class BackupsAPITestCase(test.TestCase):
|
||||
|
||||
volume = utils.create_volume(self.context, size=1)
|
||||
# Create a backup with metadata
|
||||
body = {"backup": {"display_name": "nightly001",
|
||||
"display_description":
|
||||
body = {"backup": {"name": "nightly001",
|
||||
"description":
|
||||
"Nightly Backup 03-Sep-2012",
|
||||
"volume_id": volume.id,
|
||||
"container": "nightlybackups",
|
||||
@ -606,8 +600,8 @@ class BackupsAPITestCase(test.TestCase):
|
||||
status=fields.BackupStatus.AVAILABLE,
|
||||
size=1, availability_zone='az1',
|
||||
host='testhost')
|
||||
body = {"backup": {"display_name": "nightly001",
|
||||
"display_description":
|
||||
body = {"backup": {"name": "nightly001",
|
||||
"description":
|
||||
"Nightly Backup 03-Sep-2012",
|
||||
"volume_id": volume.id,
|
||||
"container": "nightlybackups",
|
||||
@ -633,10 +627,7 @@ class BackupsAPITestCase(test.TestCase):
|
||||
volume.destroy()
|
||||
|
||||
@mock.patch('cinder.db.service_get_all')
|
||||
@mock.patch(
|
||||
'cinder.api.openstack.wsgi.Controller.validate_name_and_description')
|
||||
def test_create_backup_snapshot_json(self, mock_validate,
|
||||
_mock_service_get_all):
|
||||
def test_create_backup_snapshot_json(self, _mock_service_get_all):
|
||||
_mock_service_get_all.return_value = [
|
||||
{'availability_zone': 'fake_az', 'host': 'testhost',
|
||||
'disabled': 0, 'updated_at': timeutils.utcnow(),
|
||||
@ -644,8 +635,8 @@ class BackupsAPITestCase(test.TestCase):
|
||||
|
||||
volume = utils.create_volume(self.context, size=5, status='available')
|
||||
|
||||
body = {"backup": {"display_name": "nightly001",
|
||||
"display_description":
|
||||
body = {"backup": {"name": "nightly001",
|
||||
"description":
|
||||
"Nightly Backup 03-Sep-2012",
|
||||
"volume_id": volume.id,
|
||||
"container": "nightlybackups",
|
||||
@ -664,7 +655,6 @@ class BackupsAPITestCase(test.TestCase):
|
||||
_mock_service_get_all.assert_called_once_with(mock.ANY,
|
||||
disabled=False,
|
||||
topic='cinder-backup')
|
||||
self.assertTrue(mock_validate.called)
|
||||
|
||||
volume.destroy()
|
||||
|
||||
@ -727,8 +717,8 @@ class BackupsAPITestCase(test.TestCase):
|
||||
|
||||
def test_create_backup_with_non_existent_snapshot(self):
|
||||
volume = utils.create_volume(self.context, size=5, status='restoring')
|
||||
body = {"backup": {"display_name": "nightly001",
|
||||
"display_description":
|
||||
body = {"backup": {"name": "nightly001",
|
||||
"description":
|
||||
"Nightly Backup 03-Sep-2012",
|
||||
"snapshot_id": fake.SNAPSHOT_ID,
|
||||
"volume_id": volume.id,
|
||||
@ -761,17 +751,15 @@ class BackupsAPITestCase(test.TestCase):
|
||||
req.method = 'POST'
|
||||
req.environ['cinder.context'] = self.context
|
||||
req.api_version_request = api_version.APIVersionRequest()
|
||||
self.assertRaises(exception.InvalidInput,
|
||||
req.api_version_request = api_version.APIVersionRequest("2.0")
|
||||
self.assertRaises(exception.ValidationError,
|
||||
self.controller.create,
|
||||
req,
|
||||
body)
|
||||
body=body)
|
||||
|
||||
@mock.patch('cinder.db.service_get_all')
|
||||
@mock.patch(
|
||||
'cinder.api.openstack.wsgi.Controller.validate_name_and_description')
|
||||
@ddt.data(False, True)
|
||||
def test_create_backup_delta(self, backup_from_snapshot,
|
||||
mock_validate,
|
||||
_mock_service_get_all):
|
||||
_mock_service_get_all.return_value = [
|
||||
{'availability_zone': 'fake_az', 'host': 'testhost',
|
||||
@ -780,26 +768,35 @@ class BackupsAPITestCase(test.TestCase):
|
||||
|
||||
volume = utils.create_volume(self.context, size=5)
|
||||
snapshot = None
|
||||
snapshot_id = None
|
||||
if backup_from_snapshot:
|
||||
snapshot = utils.create_snapshot(self.context,
|
||||
volume.id,
|
||||
status=
|
||||
fields.SnapshotStatus.AVAILABLE)
|
||||
snapshot_id = snapshot.id
|
||||
body = {"backup": {"name": "nightly001",
|
||||
"description":
|
||||
"Nightly Backup 03-Sep-2012",
|
||||
"volume_id": volume.id,
|
||||
"container": "nightlybackups",
|
||||
"incremental": True,
|
||||
"snapshot_id": snapshot_id,
|
||||
}
|
||||
}
|
||||
else:
|
||||
body = {"backup": {"name": "nightly001",
|
||||
"description":
|
||||
"Nightly Backup 03-Sep-2012",
|
||||
"volume_id": volume.id,
|
||||
"container": "nightlybackups",
|
||||
"incremental": True,
|
||||
}
|
||||
}
|
||||
backup = utils.create_backup(self.context, volume.id,
|
||||
status=fields.BackupStatus.AVAILABLE,
|
||||
size=1, availability_zone='az1',
|
||||
host='testhost')
|
||||
body = {"backup": {"display_name": "nightly001",
|
||||
"display_description":
|
||||
"Nightly Backup 03-Sep-2012",
|
||||
"volume_id": volume.id,
|
||||
"container": "nightlybackups",
|
||||
"incremental": True,
|
||||
"snapshot_id": snapshot_id,
|
||||
}
|
||||
}
|
||||
|
||||
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
|
||||
req.method = 'POST'
|
||||
req.headers['Content-Type'] = 'application/json'
|
||||
@ -813,7 +810,6 @@ class BackupsAPITestCase(test.TestCase):
|
||||
_mock_service_get_all.assert_called_once_with(mock.ANY,
|
||||
disabled=False,
|
||||
topic='cinder-backup')
|
||||
self.assertTrue(mock_validate.called)
|
||||
|
||||
backup.destroy()
|
||||
if snapshot:
|
||||
@ -833,8 +829,8 @@ class BackupsAPITestCase(test.TestCase):
|
||||
backup = utils.create_backup(self.context, volume.id,
|
||||
availability_zone='az1', size=1,
|
||||
host='testhost')
|
||||
body = {"backup": {"display_name": "nightly001",
|
||||
"display_description":
|
||||
body = {"backup": {"name": "nightly001",
|
||||
"description":
|
||||
"Nightly Backup 03-Sep-2012",
|
||||
"volume_id": volume.id,
|
||||
"container": "nightlybackups",
|
||||
@ -872,13 +868,13 @@ class BackupsAPITestCase(test.TestCase):
|
||||
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
|
||||
self.assertEqual(http_client.BAD_REQUEST,
|
||||
res_dict['badRequest']['code'])
|
||||
self.assertEqual("Missing required element 'backup' in request body.",
|
||||
self.assertEqual("None is not of type 'object'",
|
||||
res_dict['badRequest']['message'])
|
||||
|
||||
def test_create_backup_with_body_KeyError(self):
|
||||
# omit volume_id from body
|
||||
body = {"backup": {"display_name": "nightly001",
|
||||
"display_description":
|
||||
body = {"backup": {"name": "nightly001",
|
||||
"description":
|
||||
"Nightly Backup 03-Sep-2012",
|
||||
"container": "nightlybackups",
|
||||
}
|
||||
@ -894,12 +890,12 @@ class BackupsAPITestCase(test.TestCase):
|
||||
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
|
||||
self.assertEqual(http_client.BAD_REQUEST,
|
||||
res_dict['badRequest']['code'])
|
||||
self.assertEqual('Incorrect request body format',
|
||||
res_dict['badRequest']['message'])
|
||||
self.assertIn("'volume_id' is a required property",
|
||||
res_dict['badRequest']['message'])
|
||||
|
||||
def test_create_backup_with_VolumeNotFound(self):
|
||||
body = {"backup": {"display_name": "nightly001",
|
||||
"display_description":
|
||||
body = {"backup": {"name": "nightly001",
|
||||
"description":
|
||||
"Nightly Backup 03-Sep-2012",
|
||||
"volume_id": fake.WILL_NOT_BE_FOUND_ID,
|
||||
"container": "nightlybackups",
|
||||
@ -951,8 +947,8 @@ class BackupsAPITestCase(test.TestCase):
|
||||
|
||||
volume = utils.create_volume(self.context, size=2)
|
||||
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
|
||||
body = {"backup": {"display_name": "nightly001",
|
||||
"display_description":
|
||||
body = {"backup": {"name": "nightly001",
|
||||
"description":
|
||||
"Nightly Backup 03-Sep-2012",
|
||||
"volume_id": volume.id,
|
||||
"container": "nightlybackups",
|
||||
@ -985,8 +981,8 @@ class BackupsAPITestCase(test.TestCase):
|
||||
|
||||
volume = utils.create_volume(self.context, size=5, status='available')
|
||||
|
||||
body = {"backup": {"display_name": "nightly001",
|
||||
"display_description":
|
||||
body = {"backup": {"name": "nightly001",
|
||||
"description":
|
||||
"Nightly Backup 03-Sep-2012",
|
||||
"volume_id": volume.id,
|
||||
"container": "nightlybackups",
|
||||
@ -1334,7 +1330,7 @@ class BackupsAPITestCase(test.TestCase):
|
||||
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
|
||||
self.assertEqual(http_client.BAD_REQUEST,
|
||||
res_dict['badRequest']['code'])
|
||||
self.assertEqual("Missing required element 'restore' in request body.",
|
||||
self.assertEqual("None is not of type 'object'",
|
||||
res_dict['badRequest']['message'])
|
||||
|
||||
backup.destroy()
|
||||
@ -1359,8 +1355,14 @@ class BackupsAPITestCase(test.TestCase):
|
||||
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
|
||||
self.assertEqual(http_client.BAD_REQUEST,
|
||||
res_dict['badRequest']['code'])
|
||||
self.assertEqual("Missing required element 'restore' in request body.",
|
||||
res_dict['badRequest']['message'])
|
||||
if six.PY3:
|
||||
self.assertEqual("Additional properties are not allowed "
|
||||
"('' was unexpected)",
|
||||
res_dict['badRequest']['message'])
|
||||
else:
|
||||
self.assertEqual("Additional properties are not allowed "
|
||||
"(u'' was unexpected)",
|
||||
res_dict['badRequest']['message'])
|
||||
|
||||
@mock.patch('cinder.db.service_get_all')
|
||||
@mock.patch('cinder.volume.api.API.create')
|
||||
@ -2088,8 +2090,18 @@ class BackupsAPITestCase(test.TestCase):
|
||||
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
|
||||
self.assertEqual(http_client.BAD_REQUEST,
|
||||
res_dict['badRequest']['code'])
|
||||
self.assertEqual('Incorrect request body format.',
|
||||
res_dict['badRequest']['message'])
|
||||
if six.PY3:
|
||||
self.assertEqual(
|
||||
"Invalid input for field/attribute backup-record. "
|
||||
"Value: {'backup_url': 'fake'}. 'backup_service' "
|
||||
"is a required property",
|
||||
res_dict['badRequest']['message'])
|
||||
else:
|
||||
self.assertEqual(
|
||||
"Invalid input for field/attribute backup-record. "
|
||||
"Value: {u'backup_url': u'fake'}. 'backup_service' "
|
||||
"is a required property",
|
||||
res_dict['badRequest']['message'])
|
||||
|
||||
# test with no backup_url
|
||||
req = webob.Request.blank('/v2/%s/backups/import_record' %
|
||||
@ -2104,8 +2116,18 @@ class BackupsAPITestCase(test.TestCase):
|
||||
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
|
||||
self.assertEqual(http_client.BAD_REQUEST,
|
||||
res_dict['badRequest']['code'])
|
||||
self.assertEqual('Incorrect request body format.',
|
||||
res_dict['badRequest']['message'])
|
||||
if six.PY3:
|
||||
self.assertEqual(
|
||||
"Invalid input for field/attribute backup-record. "
|
||||
"Value: {'backup_service': 'fake'}. 'backup_url' "
|
||||
"is a required property",
|
||||
res_dict['badRequest']['message'])
|
||||
else:
|
||||
self.assertEqual(
|
||||
"Invalid input for field/attribute backup-record. "
|
||||
"Value: {u'backup_service': u'fake'}. 'backup_url' "
|
||||
"is a required property",
|
||||
res_dict['badRequest']['message'])
|
||||
|
||||
# test with no backup_url and backup_url
|
||||
req = webob.Request.blank('/v2/%s/backups/import_record' %
|
||||
@ -2120,8 +2142,10 @@ class BackupsAPITestCase(test.TestCase):
|
||||
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
|
||||
self.assertEqual(http_client.BAD_REQUEST,
|
||||
res_dict['badRequest']['code'])
|
||||
self.assertEqual('Incorrect request body format.',
|
||||
res_dict['badRequest']['message'])
|
||||
self.assertEqual(
|
||||
"Invalid input for field/attribute backup-record. "
|
||||
"Value: {}. 'backup_service' is a required property",
|
||||
res_dict['badRequest']['message'])
|
||||
|
||||
def test_import_record_with_no_body(self):
|
||||
ctx = context.RequestContext(fake.USER_ID, fake.PROJECT_ID,
|
||||
@ -2139,8 +2163,7 @@ class BackupsAPITestCase(test.TestCase):
|
||||
self.assertEqual(http_client.BAD_REQUEST, res.status_int)
|
||||
self.assertEqual(http_client.BAD_REQUEST,
|
||||
res_dict['badRequest']['code'])
|
||||
self.assertEqual("Missing required element 'backup-record' in "
|
||||
"request body.",
|
||||
self.assertEqual("None is not of type 'object'",
|
||||
res_dict['badRequest']['message'])
|
||||
|
||||
@mock.patch('cinder.backup.rpcapi.BackupAPI.check_support_to_force_delete',
|
||||
|
@ -18,7 +18,6 @@
|
||||
import ddt
|
||||
import mock
|
||||
from oslo_utils import strutils
|
||||
import webob
|
||||
|
||||
from cinder.api import microversions as mv
|
||||
from cinder.api.openstack import api_version_request as api_version
|
||||
@ -66,17 +65,17 @@ class BackupsControllerAPITestCase(test.TestCase):
|
||||
def test_backup_update_with_no_body(self):
|
||||
# omit body from the request
|
||||
req = self._fake_update_request(fake.BACKUP_ID)
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.assertRaises(exception.ValidationError,
|
||||
self.controller.update,
|
||||
req, fake.BACKUP_ID, None)
|
||||
req, fake.BACKUP_ID, body=None)
|
||||
|
||||
def test_backup_update_with_unsupported_field(self):
|
||||
req = self._fake_update_request(fake.BACKUP_ID)
|
||||
body = {"backup": {"id": fake.BACKUP2_ID,
|
||||
"description": "", }}
|
||||
self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.assertRaises(exception.ValidationError,
|
||||
self.controller.update,
|
||||
req, fake.BACKUP_ID, body)
|
||||
req, fake.BACKUP_ID, body=body)
|
||||
|
||||
def test_backup_update_with_backup_not_found(self):
|
||||
req = self._fake_update_request(fake.BACKUP_ID)
|
||||
@ -87,7 +86,7 @@ class BackupsControllerAPITestCase(test.TestCase):
|
||||
body = {"backup": updates}
|
||||
self.assertRaises(exception.NotFound,
|
||||
self.controller.update,
|
||||
req, fake.BACKUP_ID, body)
|
||||
req, fake.BACKUP_ID, body=body)
|
||||
|
||||
def _create_multiple_backups_with_different_project(self):
|
||||
test_utils.create_backup(
|
||||
@ -225,7 +224,7 @@ class BackupsControllerAPITestCase(test.TestCase):
|
||||
body = {"backup": updates}
|
||||
self.controller.update(req,
|
||||
backup.id,
|
||||
body)
|
||||
body=body)
|
||||
|
||||
backup.refresh()
|
||||
self.assertEqual(new_name, backup.display_name)
|
||||
|
Loading…
x
Reference in New Issue
Block a user