6cfecade9e
This patch refactors the sqlalchemy DB methods related to the service to consolidate the methods into the minimum number that is actually needed, to reduce the number of DB calls per operation, and to reduce the data retrieved from the DB server. For method consolidation the patch removes `service_get_by_host_and_topic`, `service_get_by_args`, `service_get_all_by_topic`, and `service_get_all_by_binary`, and includes the functionality provided by those methods in `service_get` and `service_get_all` methods. To reduce the number of DB calls we won't retrieve the service from the DB when deleting or updating a service. And to reduce the data retrieved from the DB we filter UP nodes in the DB instead of locally. This patch is related to the efforts to support HA A/A in c-vol nodes. Job distribution patches are dependent on this one. Specs: https://review.openstack.org/327283 Implements: blueprint cinder-volume-active-active-support Change-Id: I5c453dcb5c38301721c3017ba8e782c0fdf850e6
2091 lines
96 KiB
Python
2091 lines
96 KiB
Python
# Copyright (C) 2012 Hewlett-Packard Development Company, L.P.
|
|
# 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.
|
|
|
|
"""
|
|
Tests for Backup code.
|
|
"""
|
|
|
|
import ddt
|
|
import mock
|
|
from oslo_serialization import jsonutils
|
|
from oslo_utils import timeutils
|
|
import webob
|
|
|
|
from cinder.api.contrib import backups
|
|
# needed for stubs to work
|
|
import cinder.backup
|
|
from cinder.backup import api as backup_api
|
|
from cinder import context
|
|
from cinder import db
|
|
from cinder import exception
|
|
from cinder.i18n import _
|
|
from cinder import objects
|
|
from cinder.objects import fields
|
|
from cinder import test
|
|
from cinder.tests.unit.api import fakes
|
|
from cinder.tests.unit import fake_constants as fake
|
|
from cinder.tests.unit import utils
|
|
# needed for stubs to work
|
|
import cinder.volume
|
|
|
|
NUM_ELEMENTS_IN_BACKUP = 17
|
|
|
|
|
|
@ddt.ddt
|
|
class BackupsAPITestCase(test.TestCase):
|
|
"""Test Case for backups API."""
|
|
|
|
def setUp(self):
|
|
super(BackupsAPITestCase, self).setUp()
|
|
self.volume_api = cinder.volume.API()
|
|
self.backup_api = cinder.backup.API()
|
|
self.context = context.get_admin_context()
|
|
self.context.project_id = fake.PROJECT_ID
|
|
self.context.user_id = fake.USER_ID
|
|
self.user_context = context.RequestContext(
|
|
fake.USER_ID, fake.PROJECT_ID, auth_token=True)
|
|
self.controller = backups.BackupsController()
|
|
self.patch('cinder.objects.service.Service._get_minimum_version',
|
|
return_value=None)
|
|
|
|
@staticmethod
|
|
def _create_backup(volume_id=fake.VOLUME_ID,
|
|
display_name='test_backup',
|
|
display_description='this is a test backup',
|
|
container='volumebackups',
|
|
status=fields.BackupStatus.CREATING,
|
|
incremental=False,
|
|
parent_id=None,
|
|
size=0, object_count=0, host='testhost',
|
|
num_dependent_backups=0,
|
|
snapshot_id=None,
|
|
data_timestamp=None):
|
|
"""Create a backup object."""
|
|
backup = {}
|
|
backup['volume_id'] = volume_id
|
|
backup['user_id'] = fake.USER_ID
|
|
backup['project_id'] = fake.PROJECT_ID
|
|
backup['host'] = host
|
|
backup['availability_zone'] = 'az1'
|
|
backup['display_name'] = display_name
|
|
backup['display_description'] = display_description
|
|
backup['container'] = container
|
|
backup['status'] = status
|
|
backup['fail_reason'] = ''
|
|
backup['size'] = size
|
|
backup['object_count'] = object_count
|
|
backup['incremental'] = incremental
|
|
backup['parent_id'] = parent_id
|
|
backup['num_dependent_backups'] = num_dependent_backups
|
|
backup['snapshot_id'] = snapshot_id
|
|
backup['data_timestamp'] = data_timestamp
|
|
backup = db.backup_create(context.get_admin_context(), backup)
|
|
if not snapshot_id:
|
|
db.backup_update(context.get_admin_context(),
|
|
backup['id'],
|
|
{'data_timestamp': backup['created_at']})
|
|
return backup['id']
|
|
|
|
@staticmethod
|
|
def _get_backup_attrib(backup_id, attrib_name):
|
|
return db.backup_get(context.get_admin_context(),
|
|
backup_id)[attrib_name]
|
|
|
|
@ddt.data(False, True)
|
|
def test_show_backup(self, backup_from_snapshot):
|
|
volume_id = utils.create_volume(self.context, size=5,
|
|
status='creating').id
|
|
snapshot = None
|
|
snapshot_id = None
|
|
if backup_from_snapshot:
|
|
snapshot = utils.create_snapshot(self.context,
|
|
volume_id)
|
|
snapshot_id = snapshot.id
|
|
backup_id = self._create_backup(volume_id,
|
|
snapshot_id=snapshot_id)
|
|
req = webob.Request.blank('/v2/%s/backups/%s' % (
|
|
fake.PROJECT_ID, backup_id))
|
|
req.method = 'GET'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
self.assertEqual(200, res.status_int)
|
|
self.assertEqual('az1', res_dict['backup']['availability_zone'])
|
|
self.assertEqual('volumebackups', res_dict['backup']['container'])
|
|
self.assertEqual('this is a test backup',
|
|
res_dict['backup']['description'])
|
|
self.assertEqual('test_backup', res_dict['backup']['name'])
|
|
self.assertEqual(backup_id, res_dict['backup']['id'])
|
|
self.assertEqual(0, res_dict['backup']['object_count'])
|
|
self.assertEqual(0, res_dict['backup']['size'])
|
|
self.assertEqual(fields.BackupStatus.CREATING,
|
|
res_dict['backup']['status'])
|
|
self.assertEqual(volume_id, res_dict['backup']['volume_id'])
|
|
self.assertFalse(res_dict['backup']['is_incremental'])
|
|
self.assertFalse(res_dict['backup']['has_dependent_backups'])
|
|
self.assertEqual(snapshot_id, res_dict['backup']['snapshot_id'])
|
|
self.assertIn('updated_at', res_dict['backup'])
|
|
|
|
if snapshot:
|
|
snapshot.destroy()
|
|
db.backup_destroy(context.get_admin_context(), backup_id)
|
|
db.volume_destroy(context.get_admin_context(), volume_id)
|
|
|
|
def test_show_backup_with_backup_NotFound(self):
|
|
req = webob.Request.blank('/v2/%s/backups/%s' % (
|
|
fake.PROJECT_ID, fake.WILL_NOT_BE_FOUND_ID))
|
|
req.method = 'GET'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(404, res.status_int)
|
|
self.assertEqual(404, res_dict['itemNotFound']['code'])
|
|
self.assertEqual('Backup %s could not be found.' %
|
|
fake.WILL_NOT_BE_FOUND_ID,
|
|
res_dict['itemNotFound']['message'])
|
|
|
|
def test_list_backups_json(self):
|
|
backup_id1 = self._create_backup()
|
|
backup_id2 = self._create_backup()
|
|
backup_id3 = self._create_backup()
|
|
|
|
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
|
|
req.method = 'GET'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(200, res.status_int)
|
|
self.assertEqual(3, len(res_dict['backups'][0]))
|
|
self.assertEqual(backup_id3, res_dict['backups'][0]['id'])
|
|
self.assertEqual('test_backup', res_dict['backups'][0]['name'])
|
|
self.assertEqual(3, len(res_dict['backups'][1]))
|
|
self.assertEqual(backup_id2, res_dict['backups'][1]['id'])
|
|
self.assertEqual('test_backup', res_dict['backups'][1]['name'])
|
|
self.assertEqual(3, len(res_dict['backups'][2]))
|
|
self.assertEqual(backup_id1, res_dict['backups'][2]['id'])
|
|
self.assertEqual('test_backup', res_dict['backups'][2]['name'])
|
|
|
|
db.backup_destroy(context.get_admin_context(), backup_id3)
|
|
db.backup_destroy(context.get_admin_context(), backup_id2)
|
|
db.backup_destroy(context.get_admin_context(), backup_id1)
|
|
|
|
def test_list_backups_with_limit(self):
|
|
backup_id1 = self._create_backup()
|
|
backup_id2 = self._create_backup()
|
|
backup_id3 = self._create_backup()
|
|
|
|
req = webob.Request.blank('/v2/%s/backups?limit=2' % fake.PROJECT_ID)
|
|
req.method = 'GET'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(200, res.status_int)
|
|
self.assertEqual(2, len(res_dict['backups']))
|
|
self.assertEqual(3, len(res_dict['backups'][0]))
|
|
self.assertEqual(backup_id3, res_dict['backups'][0]['id'])
|
|
self.assertEqual('test_backup', res_dict['backups'][0]['name'])
|
|
self.assertEqual(3, len(res_dict['backups'][1]))
|
|
self.assertEqual(backup_id2, res_dict['backups'][1]['id'])
|
|
self.assertEqual('test_backup', res_dict['backups'][1]['name'])
|
|
|
|
db.backup_destroy(context.get_admin_context(), backup_id3)
|
|
db.backup_destroy(context.get_admin_context(), backup_id2)
|
|
db.backup_destroy(context.get_admin_context(), backup_id1)
|
|
|
|
def test_list_backups_with_offset_out_of_range(self):
|
|
url = '/v2/%s/backups?offset=252452434242342434' % fake.PROJECT_ID
|
|
req = webob.Request.blank(url)
|
|
req.method = 'GET'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
self.assertEqual(400, res.status_int)
|
|
|
|
def test_list_backups_with_marker(self):
|
|
backup_id1 = self._create_backup()
|
|
backup_id2 = self._create_backup()
|
|
backup_id3 = self._create_backup()
|
|
url = '/v2/%s/backups?marker=%s' % (fake.PROJECT_ID, backup_id3)
|
|
req = webob.Request.blank(url)
|
|
req.method = 'GET'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(200, res.status_int)
|
|
self.assertEqual(2, len(res_dict['backups']))
|
|
self.assertEqual(3, len(res_dict['backups'][0]))
|
|
self.assertEqual(backup_id2, res_dict['backups'][0]['id'])
|
|
self.assertEqual('test_backup', res_dict['backups'][0]['name'])
|
|
self.assertEqual(3, len(res_dict['backups'][1]))
|
|
self.assertEqual(backup_id1, res_dict['backups'][1]['id'])
|
|
self.assertEqual('test_backup', res_dict['backups'][1]['name'])
|
|
|
|
db.backup_destroy(context.get_admin_context(), backup_id3)
|
|
db.backup_destroy(context.get_admin_context(), backup_id2)
|
|
db.backup_destroy(context.get_admin_context(), backup_id1)
|
|
|
|
def test_list_backups_with_limit_and_marker(self):
|
|
backup_id1 = self._create_backup()
|
|
backup_id2 = self._create_backup()
|
|
backup_id3 = self._create_backup()
|
|
|
|
url = ('/v2/%s/backups?limit=1&marker=%s' % (fake.PROJECT_ID,
|
|
backup_id3))
|
|
req = webob.Request.blank(url)
|
|
req.method = 'GET'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(200, res.status_int)
|
|
self.assertEqual(1, len(res_dict['backups']))
|
|
self.assertEqual(3, len(res_dict['backups'][0]))
|
|
self.assertEqual(backup_id2, res_dict['backups'][0]['id'])
|
|
self.assertEqual('test_backup', res_dict['backups'][0]['name'])
|
|
|
|
db.backup_destroy(context.get_admin_context(), backup_id3)
|
|
db.backup_destroy(context.get_admin_context(), backup_id2)
|
|
db.backup_destroy(context.get_admin_context(), backup_id1)
|
|
|
|
def test_list_backups_detail_json(self):
|
|
backup_id1 = self._create_backup()
|
|
backup_id2 = self._create_backup()
|
|
backup_id3 = self._create_backup()
|
|
|
|
req = webob.Request.blank('/v2/%s/backups/detail' % fake.PROJECT_ID)
|
|
req.method = 'GET'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
req.headers['Accept'] = 'application/json'
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(200, res.status_int)
|
|
self.assertEqual(NUM_ELEMENTS_IN_BACKUP, len(res_dict['backups'][0]))
|
|
self.assertEqual('az1', res_dict['backups'][0]['availability_zone'])
|
|
self.assertEqual('volumebackups',
|
|
res_dict['backups'][0]['container'])
|
|
self.assertEqual('this is a test backup',
|
|
res_dict['backups'][0]['description'])
|
|
self.assertEqual('test_backup',
|
|
res_dict['backups'][0]['name'])
|
|
self.assertEqual(backup_id3, res_dict['backups'][0]['id'])
|
|
self.assertEqual(0, res_dict['backups'][0]['object_count'])
|
|
self.assertEqual(0, res_dict['backups'][0]['size'])
|
|
self.assertEqual(fields.BackupStatus.CREATING,
|
|
res_dict['backups'][0]['status'])
|
|
self.assertEqual(fake.VOLUME_ID, res_dict['backups'][0]['volume_id'])
|
|
self.assertIn('updated_at', res_dict['backups'][0])
|
|
|
|
self.assertEqual(NUM_ELEMENTS_IN_BACKUP, len(res_dict['backups'][1]))
|
|
self.assertEqual('az1', res_dict['backups'][1]['availability_zone'])
|
|
self.assertEqual('volumebackups',
|
|
res_dict['backups'][1]['container'])
|
|
self.assertEqual('this is a test backup',
|
|
res_dict['backups'][1]['description'])
|
|
self.assertEqual('test_backup',
|
|
res_dict['backups'][1]['name'])
|
|
self.assertEqual(backup_id2, res_dict['backups'][1]['id'])
|
|
self.assertEqual(0, res_dict['backups'][1]['object_count'])
|
|
self.assertEqual(0, res_dict['backups'][1]['size'])
|
|
self.assertEqual(fields.BackupStatus.CREATING,
|
|
res_dict['backups'][1]['status'])
|
|
self.assertEqual(fake.VOLUME_ID, res_dict['backups'][1]['volume_id'])
|
|
self.assertIn('updated_at', res_dict['backups'][1])
|
|
|
|
self.assertEqual(NUM_ELEMENTS_IN_BACKUP, len(res_dict['backups'][2]))
|
|
self.assertEqual('az1', res_dict['backups'][2]['availability_zone'])
|
|
self.assertEqual('volumebackups', res_dict['backups'][2]['container'])
|
|
self.assertEqual('this is a test backup',
|
|
res_dict['backups'][2]['description'])
|
|
self.assertEqual('test_backup',
|
|
res_dict['backups'][2]['name'])
|
|
self.assertEqual(backup_id1, res_dict['backups'][2]['id'])
|
|
self.assertEqual(0, res_dict['backups'][2]['object_count'])
|
|
self.assertEqual(0, res_dict['backups'][2]['size'])
|
|
self.assertEqual(fields.BackupStatus.CREATING,
|
|
res_dict['backups'][2]['status'])
|
|
self.assertEqual(fake.VOLUME_ID, res_dict['backups'][2]['volume_id'])
|
|
self.assertIn('updated_at', res_dict['backups'][2])
|
|
|
|
db.backup_destroy(context.get_admin_context(), backup_id3)
|
|
db.backup_destroy(context.get_admin_context(), backup_id2)
|
|
db.backup_destroy(context.get_admin_context(), backup_id1)
|
|
|
|
def test_list_backups_detail_using_filters(self):
|
|
backup_id1 = self._create_backup(display_name='test2')
|
|
backup_id2 = self._create_backup(status=fields.BackupStatus.AVAILABLE)
|
|
backup_id3 = self._create_backup(volume_id=fake.VOLUME3_ID)
|
|
|
|
req = webob.Request.blank('/v2/%s/backups/detail?name=test2' %
|
|
fake.PROJECT_ID)
|
|
req.method = 'GET'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
req.headers['Accept'] = 'application/json'
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(1, len(res_dict['backups']))
|
|
self.assertEqual(200, res.status_int)
|
|
self.assertEqual(backup_id1, res_dict['backups'][0]['id'])
|
|
|
|
req = webob.Request.blank('/v2/%s/backups/detail?status=available' %
|
|
fake.PROJECT_ID)
|
|
req.method = 'GET'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
req.headers['Accept'] = 'application/json'
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(1, len(res_dict['backups']))
|
|
self.assertEqual(200, res.status_int)
|
|
self.assertEqual(backup_id2, res_dict['backups'][0]['id'])
|
|
|
|
req = webob.Request.blank('/v2/%s/backups/detail?volume_id=%s' % (
|
|
fake.PROJECT_ID, fake.VOLUME3_ID))
|
|
req.method = 'GET'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
req.headers['Accept'] = 'application/json'
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(1, len(res_dict['backups']))
|
|
self.assertEqual(200, res.status_int)
|
|
self.assertEqual(backup_id3, res_dict['backups'][0]['id'])
|
|
|
|
db.backup_destroy(context.get_admin_context(), backup_id3)
|
|
db.backup_destroy(context.get_admin_context(), backup_id2)
|
|
db.backup_destroy(context.get_admin_context(), backup_id1)
|
|
|
|
def test_list_backups_detail_with_limit_and_sort_args(self):
|
|
backup_id1 = self._create_backup()
|
|
backup_id2 = self._create_backup()
|
|
backup_id3 = self._create_backup()
|
|
url = ('/v2/%s/backups/detail?limit=2&sort_key=created_at'
|
|
'&sort_dir=desc' % fake.PROJECT_ID)
|
|
req = webob.Request.blank(url)
|
|
req.method = 'GET'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(200, res.status_int)
|
|
self.assertEqual(2, len(res_dict['backups']))
|
|
self.assertEqual(NUM_ELEMENTS_IN_BACKUP, len(res_dict['backups'][0]))
|
|
self.assertEqual(backup_id3, res_dict['backups'][0]['id'])
|
|
self.assertEqual(NUM_ELEMENTS_IN_BACKUP, len(res_dict['backups'][1]))
|
|
self.assertEqual(backup_id2, res_dict['backups'][1]['id'])
|
|
|
|
db.backup_destroy(context.get_admin_context(), backup_id3)
|
|
db.backup_destroy(context.get_admin_context(), backup_id2)
|
|
db.backup_destroy(context.get_admin_context(), backup_id1)
|
|
|
|
def test_list_backups_detail_with_marker(self):
|
|
backup_id1 = self._create_backup()
|
|
backup_id2 = self._create_backup()
|
|
backup_id3 = self._create_backup()
|
|
|
|
url = ('/v2/%s/backups/detail?marker=%s' % (
|
|
fake.PROJECT_ID, backup_id3))
|
|
req = webob.Request.blank(url)
|
|
req.method = 'GET'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(200, res.status_int)
|
|
self.assertEqual(2, len(res_dict['backups']))
|
|
self.assertEqual(NUM_ELEMENTS_IN_BACKUP, len(res_dict['backups'][0]))
|
|
self.assertEqual(backup_id2, res_dict['backups'][0]['id'])
|
|
self.assertEqual(NUM_ELEMENTS_IN_BACKUP, len(res_dict['backups'][1]))
|
|
self.assertEqual(backup_id1, res_dict['backups'][1]['id'])
|
|
|
|
db.backup_destroy(context.get_admin_context(), backup_id3)
|
|
db.backup_destroy(context.get_admin_context(), backup_id2)
|
|
db.backup_destroy(context.get_admin_context(), backup_id1)
|
|
|
|
def test_list_backups_detail_with_limit_and_marker(self):
|
|
backup_id1 = self._create_backup()
|
|
backup_id2 = self._create_backup()
|
|
backup_id3 = self._create_backup()
|
|
|
|
url = ('/v2/%s/backups/detail?limit=1&marker=%s' % (
|
|
fake.PROJECT_ID, backup_id3))
|
|
req = webob.Request.blank(url)
|
|
req.method = 'GET'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(200, res.status_int)
|
|
self.assertEqual(1, len(res_dict['backups']))
|
|
self.assertEqual(NUM_ELEMENTS_IN_BACKUP, len(res_dict['backups'][0]))
|
|
self.assertEqual(backup_id2, res_dict['backups'][0]['id'])
|
|
|
|
db.backup_destroy(context.get_admin_context(), backup_id3)
|
|
db.backup_destroy(context.get_admin_context(), backup_id2)
|
|
db.backup_destroy(context.get_admin_context(), backup_id1)
|
|
|
|
def test_list_backups_detail_with_offset_out_of_range(self):
|
|
url = ('/v2/%s/backups/detail?offset=234534543657634523' %
|
|
fake.PROJECT_ID)
|
|
req = webob.Request.blank(url)
|
|
req.method = 'GET'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
self.assertEqual(400, 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):
|
|
_mock_service_get_all.return_value = [
|
|
{'availability_zone': 'fake_az', 'host': 'testhost',
|
|
'disabled': 0, 'updated_at': timeutils.utcnow()}]
|
|
|
|
volume_id = utils.create_volume(self.context, size=5).id
|
|
|
|
body = {"backup": {"display_name": "nightly001",
|
|
"display_description":
|
|
"Nightly Backup 03-Sep-2012",
|
|
"volume_id": volume_id,
|
|
"container": "nightlybackups",
|
|
}
|
|
}
|
|
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
|
|
req.method = 'POST'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(202, res.status_int)
|
|
self.assertIn('id', res_dict['backup'])
|
|
_mock_service_get_all.assert_called_once_with(mock.ANY,
|
|
disabled=False,
|
|
topic='cinder-backup')
|
|
self.assertTrue(mock_validate.called)
|
|
|
|
db.volume_destroy(context.get_admin_context(), volume_id)
|
|
|
|
@mock.patch('cinder.db.service_get_all')
|
|
def test_create_backup_inuse_no_force(self,
|
|
_mock_service_get_all):
|
|
_mock_service_get_all.return_value = [
|
|
{'availability_zone': 'fake_az', 'host': 'testhost',
|
|
'disabled': 0, 'updated_at': timeutils.utcnow()}]
|
|
|
|
volume_id = utils.create_volume(self.context, size=5,
|
|
status='in-use').id
|
|
|
|
body = {"backup": {"display_name": "nightly001",
|
|
"display_description":
|
|
"Nightly Backup 03-Sep-2012",
|
|
"volume_id": volume_id,
|
|
"container": "nightlybackups",
|
|
}
|
|
}
|
|
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
|
|
req.method = 'POST'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(400, res.status_int)
|
|
self.assertEqual(400, res_dict['badRequest']['code'])
|
|
self.assertIsNotNone(res_dict['badRequest']['message'])
|
|
|
|
db.volume_destroy(context.get_admin_context(), volume_id)
|
|
|
|
@mock.patch('cinder.db.service_get_all')
|
|
def test_create_backup_inuse_force(self, _mock_service_get_all):
|
|
_mock_service_get_all.return_value = [
|
|
{'availability_zone': 'fake_az', 'host': 'testhost',
|
|
'disabled': 0, 'updated_at': timeutils.utcnow()}]
|
|
|
|
volume_id = utils.create_volume(self.context, size=5,
|
|
status='in-use').id
|
|
backup_id = self._create_backup(volume_id,
|
|
status=fields.BackupStatus.AVAILABLE)
|
|
body = {"backup": {"display_name": "nightly001",
|
|
"display_description":
|
|
"Nightly Backup 03-Sep-2012",
|
|
"volume_id": volume_id,
|
|
"container": "nightlybackups",
|
|
"force": True,
|
|
}
|
|
}
|
|
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
|
|
req.method = 'POST'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(202, res.status_int)
|
|
self.assertIn('id', res_dict['backup'])
|
|
_mock_service_get_all.assert_called_once_with(mock.ANY,
|
|
disabled=False,
|
|
topic='cinder-backup')
|
|
|
|
db.backup_destroy(context.get_admin_context(), backup_id)
|
|
db.volume_destroy(context.get_admin_context(), volume_id)
|
|
|
|
@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):
|
|
_mock_service_get_all.return_value = [
|
|
{'availability_zone': 'fake_az', 'host': 'testhost',
|
|
'disabled': 0, 'updated_at': timeutils.utcnow()}]
|
|
|
|
volume_id = utils.create_volume(self.context, size=5,
|
|
status='available').id
|
|
|
|
body = {"backup": {"display_name": "nightly001",
|
|
"display_description":
|
|
"Nightly Backup 03-Sep-2012",
|
|
"volume_id": volume_id,
|
|
"container": "nightlybackups",
|
|
}
|
|
}
|
|
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
|
|
req.method = 'POST'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
|
|
res_dict = jsonutils.loads(res.body)
|
|
self.assertEqual(202, res.status_int)
|
|
self.assertIn('id', res_dict['backup'])
|
|
_mock_service_get_all.assert_called_once_with(mock.ANY,
|
|
disabled=False,
|
|
topic='cinder-backup')
|
|
self.assertTrue(mock_validate.called)
|
|
|
|
db.volume_destroy(context.get_admin_context(), volume_id)
|
|
|
|
def test_create_backup_snapshot_with_inconsistent_volume(self):
|
|
volume_id = utils.create_volume(self.context, size=5,
|
|
status='available').id
|
|
volume_id2 = utils.create_volume(self.context, size=5,
|
|
status='available').id
|
|
snapshot_id = utils.create_snapshot(self.context,
|
|
volume_id,
|
|
status='available')['id']
|
|
|
|
self.addCleanup(db.volume_destroy,
|
|
self.context.elevated(),
|
|
volume_id)
|
|
self.addCleanup(db.volume_destroy,
|
|
self.context.elevated(),
|
|
volume_id2)
|
|
self.addCleanup(db.snapshot_destroy,
|
|
self.context.elevated(),
|
|
snapshot_id)
|
|
body = {"backup": {"display_name": "nightly001",
|
|
"display_description":
|
|
"Nightly Backup 03-Sep-2012",
|
|
"volume_id": volume_id2,
|
|
"snapshot_id": snapshot_id,
|
|
"container": "nightlybackups",
|
|
}
|
|
}
|
|
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
|
|
req.method = 'POST'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
|
|
res_dict = jsonutils.loads(res.body)
|
|
self.assertEqual(400, res.status_int)
|
|
self.assertIsNotNone(res_dict['badRequest']['message'])
|
|
|
|
def test_create_backup_with_invalid_snapshot(self):
|
|
volume_id = utils.create_volume(self.context, size=5,
|
|
status='available').id
|
|
snapshot_id = utils.create_snapshot(self.context, volume_id,
|
|
status='error')['id']
|
|
body = {"backup": {"display_name": "nightly001",
|
|
"display_description":
|
|
"Nightly Backup 03-Sep-2012",
|
|
"snapshot_id": snapshot_id,
|
|
"volume_id": volume_id,
|
|
}
|
|
}
|
|
self.addCleanup(db.volume_destroy,
|
|
self.context.elevated(),
|
|
volume_id)
|
|
self.addCleanup(db.snapshot_destroy,
|
|
self.context.elevated(),
|
|
snapshot_id)
|
|
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
|
|
req.method = 'POST'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
|
|
res_dict = jsonutils.loads(res.body)
|
|
self.assertEqual(400, res.status_int)
|
|
self.assertEqual(400, res_dict['badRequest']['code'])
|
|
self.assertIsNotNone(res_dict['badRequest']['message'])
|
|
|
|
def test_create_backup_with_non_existent_snapshot(self):
|
|
volume_id = utils.create_volume(self.context, size=5,
|
|
status='restoring').id
|
|
body = {"backup": {"display_name": "nightly001",
|
|
"display_description":
|
|
"Nightly Backup 03-Sep-2012",
|
|
"snapshot_id": fake.SNAPSHOT_ID,
|
|
"volume_id": volume_id,
|
|
}
|
|
}
|
|
self.addCleanup(db.volume_destroy,
|
|
self.context.elevated(),
|
|
volume_id)
|
|
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
|
|
req.method = 'POST'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
|
|
res_dict = jsonutils.loads(res.body)
|
|
self.assertEqual(404, res.status_int)
|
|
self.assertEqual(404, res_dict['itemNotFound']['code'])
|
|
self.assertIsNotNone(res_dict['itemNotFound']['message'])
|
|
|
|
def test_create_backup_with_invalid_container(self):
|
|
volume_id = utils.create_volume(self.context, size=5,
|
|
status='available').id
|
|
body = {"backup": {"display_name": "nightly001",
|
|
"display_description": "Nightly Backup 03-Sep-2012",
|
|
"volume_id": volume_id,
|
|
"container": "a" * 256
|
|
}
|
|
}
|
|
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
|
|
req.method = 'POST'
|
|
req.environ['cinder.context'] = self.context
|
|
self.assertRaises(exception.InvalidInput,
|
|
self.controller.create,
|
|
req,
|
|
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',
|
|
'disabled': 0, 'updated_at': timeutils.utcnow()}]
|
|
|
|
volume_id = utils.create_volume(self.context, size=5).id
|
|
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
|
|
backup_id = self._create_backup(volume_id,
|
|
status=fields.BackupStatus.AVAILABLE)
|
|
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'
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(202, res.status_int)
|
|
self.assertIn('id', res_dict['backup'])
|
|
_mock_service_get_all.assert_called_once_with(mock.ANY,
|
|
disabled=False,
|
|
topic='cinder-backup')
|
|
self.assertTrue(mock_validate.called)
|
|
|
|
db.backup_destroy(context.get_admin_context(), backup_id)
|
|
if snapshot:
|
|
snapshot.destroy()
|
|
db.volume_destroy(context.get_admin_context(), volume_id)
|
|
|
|
@mock.patch('cinder.db.service_get_all')
|
|
def test_create_incremental_backup_invalid_status(
|
|
self, _mock_service_get_all):
|
|
_mock_service_get_all.return_value = [
|
|
{'availability_zone': 'fake_az', 'host': 'testhost',
|
|
'disabled': 0, 'updated_at': timeutils.utcnow()}]
|
|
|
|
volume_id = utils.create_volume(self.context, size=5).id
|
|
|
|
backup_id = self._create_backup(volume_id)
|
|
body = {"backup": {"display_name": "nightly001",
|
|
"display_description":
|
|
"Nightly Backup 03-Sep-2012",
|
|
"volume_id": volume_id,
|
|
"container": "nightlybackups",
|
|
"incremental": True,
|
|
}
|
|
}
|
|
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
|
|
req.method = 'POST'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(400, res_dict['badRequest']['code'])
|
|
self.assertEqual('Invalid backup: The parent backup must be '
|
|
'available for incremental backup.',
|
|
res_dict['badRequest']['message'])
|
|
|
|
db.backup_destroy(context.get_admin_context(), backup_id)
|
|
db.volume_destroy(context.get_admin_context(), volume_id)
|
|
|
|
def test_create_backup_with_no_body(self):
|
|
# omit body from the request
|
|
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
|
|
req.body = jsonutils.dump_as_bytes(None)
|
|
req.method = 'POST'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
req.headers['Accept'] = 'application/json'
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(400, res.status_int)
|
|
self.assertEqual(400, res_dict['badRequest']['code'])
|
|
self.assertEqual("Missing required element 'backup' in request body.",
|
|
res_dict['badRequest']['message'])
|
|
|
|
def test_create_backup_with_body_KeyError(self):
|
|
# omit volume_id from body
|
|
body = {"backup": {"display_name": "nightly001",
|
|
"display_description":
|
|
"Nightly Backup 03-Sep-2012",
|
|
"container": "nightlybackups",
|
|
}
|
|
}
|
|
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
|
|
req.method = 'POST'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(400, res.status_int)
|
|
self.assertEqual(400, res_dict['badRequest']['code'])
|
|
self.assertEqual('Incorrect request body format',
|
|
res_dict['badRequest']['message'])
|
|
|
|
def test_create_backup_with_VolumeNotFound(self):
|
|
body = {"backup": {"display_name": "nightly001",
|
|
"display_description":
|
|
"Nightly Backup 03-Sep-2012",
|
|
"volume_id": fake.WILL_NOT_BE_FOUND_ID,
|
|
"container": "nightlybackups",
|
|
}
|
|
}
|
|
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
|
|
req.method = 'POST'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(404, res.status_int)
|
|
self.assertEqual(404, res_dict['itemNotFound']['code'])
|
|
self.assertEqual('Volume %s could not be found.' %
|
|
fake.WILL_NOT_BE_FOUND_ID,
|
|
res_dict['itemNotFound']['message'])
|
|
|
|
def test_create_backup_with_InvalidVolume(self):
|
|
# need to create the volume referenced below first
|
|
volume_id = utils.create_volume(self.context, size=5,
|
|
status='restoring').id
|
|
body = {"backup": {"display_name": "nightly001",
|
|
"display_description":
|
|
"Nightly Backup 03-Sep-2012",
|
|
"volume_id": volume_id,
|
|
"container": "nightlybackups",
|
|
}
|
|
}
|
|
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
|
|
req.method = 'POST'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(400, res.status_int)
|
|
self.assertEqual(400, res_dict['badRequest']['code'])
|
|
|
|
@mock.patch('cinder.db.service_get_all')
|
|
def test_create_backup_WithOUT_enabled_backup_service(
|
|
self,
|
|
_mock_service_get_all):
|
|
# need an enabled backup service available
|
|
_mock_service_get_all.return_value = []
|
|
|
|
volume_id = utils.create_volume(self.context, size=2).id
|
|
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
|
|
body = {"backup": {"display_name": "nightly001",
|
|
"display_description":
|
|
"Nightly Backup 03-Sep-2012",
|
|
"volume_id": volume_id,
|
|
"container": "nightlybackups",
|
|
}
|
|
}
|
|
req.method = 'POST'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
req.headers['Accept'] = 'application/json'
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
self.assertEqual(500, res.status_int)
|
|
self.assertEqual(500, res_dict['computeFault']['code'])
|
|
self.assertEqual('Service cinder-backup could not be found.',
|
|
res_dict['computeFault']['message'])
|
|
|
|
volume = self.volume_api.get(context.get_admin_context(), volume_id)
|
|
self.assertEqual('available', volume['status'])
|
|
|
|
@mock.patch('cinder.db.service_get_all')
|
|
def test_create_incremental_backup_invalid_no_full(
|
|
self, _mock_service_get_all):
|
|
_mock_service_get_all.return_value = [
|
|
{'availability_zone': 'fake_az', 'host': 'testhost',
|
|
'disabled': 0, 'updated_at': timeutils.utcnow()}]
|
|
|
|
volume_id = utils.create_volume(self.context, size=5,
|
|
status='available').id
|
|
|
|
body = {"backup": {"display_name": "nightly001",
|
|
"display_description":
|
|
"Nightly Backup 03-Sep-2012",
|
|
"volume_id": volume_id,
|
|
"container": "nightlybackups",
|
|
"incremental": True,
|
|
}
|
|
}
|
|
req = webob.Request.blank('/v2/%s/backups' % fake.PROJECT_ID)
|
|
req.method = 'POST'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(400, res_dict['badRequest']['code'])
|
|
self.assertEqual('Invalid backup: No backups available to do '
|
|
'an incremental backup.',
|
|
res_dict['badRequest']['message'])
|
|
|
|
db.volume_destroy(context.get_admin_context(), volume_id)
|
|
|
|
@mock.patch('cinder.db.service_get_all')
|
|
def test_is_backup_service_enabled(self, _mock_service_get_all):
|
|
|
|
testhost = 'test_host'
|
|
alt_host = 'strange_host'
|
|
empty_service = []
|
|
# service host not match with volume's host
|
|
host_not_match = [{'availability_zone': 'fake_az', 'host': alt_host,
|
|
'disabled': 0, 'updated_at': timeutils.utcnow()}]
|
|
# service az not match with volume's az
|
|
az_not_match = [{'availability_zone': 'strange_az', 'host': testhost,
|
|
'disabled': 0, 'updated_at': timeutils.utcnow()}]
|
|
# service disabled
|
|
disabled_service = []
|
|
|
|
# dead service that last reported at 20th century
|
|
dead_service = [{'availability_zone': 'fake_az', 'host': alt_host,
|
|
'disabled': 0, 'updated_at': '1989-04-16 02:55:44'}]
|
|
|
|
# first service's host not match but second one works.
|
|
multi_services = [{'availability_zone': 'fake_az', 'host': alt_host,
|
|
'disabled': 0, 'updated_at': timeutils.utcnow()},
|
|
{'availability_zone': 'fake_az', 'host': testhost,
|
|
'disabled': 0, 'updated_at': timeutils.utcnow()}]
|
|
|
|
# Setup mock to run through the following service cases
|
|
_mock_service_get_all.side_effect = [empty_service,
|
|
host_not_match,
|
|
az_not_match,
|
|
disabled_service,
|
|
dead_service,
|
|
multi_services]
|
|
|
|
volume_id = utils.create_volume(self.context, size=2,
|
|
host=testhost).id
|
|
volume = self.volume_api.get(context.get_admin_context(), volume_id)
|
|
|
|
# test empty service
|
|
self.assertEqual(False,
|
|
self.backup_api._is_backup_service_enabled(
|
|
volume['availability_zone'],
|
|
testhost))
|
|
|
|
# test host not match service
|
|
self.assertEqual(False,
|
|
self.backup_api._is_backup_service_enabled(
|
|
volume['availability_zone'],
|
|
testhost))
|
|
|
|
# test az not match service
|
|
self.assertEqual(False,
|
|
self.backup_api._is_backup_service_enabled(
|
|
volume['availability_zone'],
|
|
testhost))
|
|
|
|
# test disabled service
|
|
self.assertEqual(False,
|
|
self.backup_api._is_backup_service_enabled(
|
|
volume['availability_zone'],
|
|
testhost))
|
|
|
|
# test dead service
|
|
self.assertEqual(False,
|
|
self.backup_api._is_backup_service_enabled(
|
|
volume['availability_zone'],
|
|
testhost))
|
|
|
|
# test multi services and the last service matches
|
|
self.assertTrue(self.backup_api._is_backup_service_enabled(
|
|
volume['availability_zone'],
|
|
testhost))
|
|
|
|
@mock.patch('cinder.db.service_get_all')
|
|
def test_get_available_backup_service(self, _mock_service_get_all):
|
|
_mock_service_get_all.return_value = [
|
|
{'availability_zone': 'az1', 'host': 'testhost1',
|
|
'disabled': 0, 'updated_at': timeutils.utcnow()},
|
|
{'availability_zone': 'az2', 'host': 'testhost2',
|
|
'disabled': 0, 'updated_at': timeutils.utcnow()},
|
|
{'availability_zone': 'az2', 'host': 'testhost3',
|
|
'disabled': 0, 'updated_at': timeutils.utcnow()}, ]
|
|
actual_host = self.backup_api._get_available_backup_service_host(
|
|
None, 'az1')
|
|
self.assertEqual('testhost1', actual_host)
|
|
actual_host = self.backup_api._get_available_backup_service_host(
|
|
'testhost2', 'az2')
|
|
self.assertIn(actual_host, ['testhost2', 'testhost3'])
|
|
actual_host = self.backup_api._get_available_backup_service_host(
|
|
'testhost4', 'az1')
|
|
self.assertEqual('testhost1', actual_host)
|
|
|
|
@mock.patch('cinder.db.service_get_all')
|
|
def test_get_available_backup_service_with_same_host(
|
|
self, _mock_service_get_all):
|
|
_mock_service_get_all.return_value = [
|
|
{'availability_zone': 'az1', 'host': 'testhost1',
|
|
'disabled': 0, 'updated_at': timeutils.utcnow()},
|
|
{'availability_zone': 'az2', 'host': 'testhost2',
|
|
'disabled': 0, 'updated_at': timeutils.utcnow()}, ]
|
|
self.override_config('backup_use_same_host', True)
|
|
actual_host = self.backup_api._get_available_backup_service_host(
|
|
None, 'az1')
|
|
self.assertEqual('testhost1', actual_host)
|
|
actual_host = self.backup_api._get_available_backup_service_host(
|
|
'testhost2', 'az2')
|
|
self.assertEqual('testhost2', actual_host)
|
|
self.assertRaises(exception.ServiceNotFound,
|
|
self.backup_api._get_available_backup_service_host,
|
|
'testhost4', 'az1')
|
|
|
|
@mock.patch('cinder.db.service_get_all')
|
|
def test_delete_backup_available(self, _mock_service_get_all):
|
|
_mock_service_get_all.return_value = [
|
|
{'availability_zone': 'az1', 'host': 'testhost',
|
|
'disabled': 0, 'updated_at': timeutils.utcnow()}]
|
|
backup_id = self._create_backup(status=fields.BackupStatus.AVAILABLE)
|
|
req = webob.Request.blank('/v2/%s/backups/%s' % (
|
|
fake.PROJECT_ID, backup_id))
|
|
req.method = 'DELETE'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
|
|
self.assertEqual(202, res.status_int)
|
|
self.assertEqual(fields.BackupStatus.DELETING,
|
|
self._get_backup_attrib(backup_id, 'status'))
|
|
|
|
db.backup_destroy(context.get_admin_context(), backup_id)
|
|
|
|
@mock.patch('cinder.db.service_get_all')
|
|
def test_delete_delta_backup(self,
|
|
_mock_service_get_all):
|
|
_mock_service_get_all.return_value = [
|
|
{'availability_zone': 'az1', 'host': 'testhost',
|
|
'disabled': 0, 'updated_at': timeutils.utcnow()}]
|
|
backup_id = self._create_backup(status=fields.BackupStatus.AVAILABLE)
|
|
delta_id = self._create_backup(status=fields.BackupStatus.AVAILABLE,
|
|
incremental=True)
|
|
req = webob.Request.blank('/v2/%s/backups/%s' % (
|
|
fake.PROJECT_ID, delta_id))
|
|
req.method = 'DELETE'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
|
|
self.assertEqual(202, res.status_int)
|
|
self.assertEqual(fields.BackupStatus.DELETING,
|
|
self._get_backup_attrib(delta_id, 'status'))
|
|
|
|
db.backup_destroy(context.get_admin_context(), delta_id)
|
|
db.backup_destroy(context.get_admin_context(), backup_id)
|
|
|
|
@mock.patch('cinder.db.service_get_all')
|
|
def test_delete_backup_error(self,
|
|
_mock_service_get_all):
|
|
_mock_service_get_all.return_value = [
|
|
{'availability_zone': 'az1', 'host': 'testhost',
|
|
'disabled': 0, 'updated_at': timeutils.utcnow()}]
|
|
backup_id = self._create_backup(status=fields.BackupStatus.ERROR)
|
|
req = webob.Request.blank('/v2/%s/backups/%s' % (
|
|
fake.PROJECT_ID, backup_id))
|
|
req.method = 'DELETE'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
|
|
self.assertEqual(202, res.status_int)
|
|
self.assertEqual(fields.BackupStatus.DELETING,
|
|
self._get_backup_attrib(backup_id, 'status'))
|
|
|
|
db.backup_destroy(context.get_admin_context(), backup_id)
|
|
|
|
def test_delete_backup_with_backup_NotFound(self):
|
|
req = webob.Request.blank('/v2/%s/backups/%s' % (
|
|
fake.PROJECT_ID, fake.WILL_NOT_BE_FOUND_ID))
|
|
req.method = 'DELETE'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(404, res.status_int)
|
|
self.assertEqual(404, res_dict['itemNotFound']['code'])
|
|
self.assertEqual('Backup %s could not be found.' %
|
|
fake.WILL_NOT_BE_FOUND_ID,
|
|
res_dict['itemNotFound']['message'])
|
|
|
|
def test_delete_backup_with_InvalidBackup(self):
|
|
backup_id = self._create_backup()
|
|
req = webob.Request.blank('/v2/%s/backups/%s' % (
|
|
fake.PROJECT_ID, backup_id))
|
|
req.method = 'DELETE'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(400, res.status_int)
|
|
self.assertEqual(400, res_dict['badRequest']['code'])
|
|
self.assertEqual('Invalid backup: Backup status must be '
|
|
'available or error',
|
|
res_dict['badRequest']['message'])
|
|
|
|
db.backup_destroy(context.get_admin_context(), backup_id)
|
|
|
|
@mock.patch('cinder.db.service_get_all')
|
|
def test_delete_backup_with_InvalidBackup2(self,
|
|
_mock_service_get_all):
|
|
_mock_service_get_all.return_value = [
|
|
{'availability_zone': 'az1', 'host': 'testhost',
|
|
'disabled': 0, 'updated_at': timeutils.utcnow()}]
|
|
volume_id = utils.create_volume(self.context, size=5).id
|
|
backup_id = self._create_backup(volume_id,
|
|
status=fields.BackupStatus.AVAILABLE)
|
|
delta_backup_id = self._create_backup(
|
|
status=fields.BackupStatus.AVAILABLE, incremental=True,
|
|
parent_id=backup_id)
|
|
|
|
req = webob.Request.blank('/v2/%s/backups/%s' % (
|
|
fake.PROJECT_ID, backup_id))
|
|
req.method = 'DELETE'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
self.assertEqual(400, res.status_int)
|
|
self.assertEqual(400, res_dict['badRequest']['code'])
|
|
self.assertEqual('Invalid backup: Incremental backups '
|
|
'exist for this backup.',
|
|
res_dict['badRequest']['message'])
|
|
|
|
db.backup_destroy(context.get_admin_context(), delta_backup_id)
|
|
db.backup_destroy(context.get_admin_context(), backup_id)
|
|
|
|
@mock.patch('cinder.db.service_get_all')
|
|
def test_delete_backup_service_down(self,
|
|
_mock_service_get_all):
|
|
_mock_service_get_all.return_value = [
|
|
{'availability_zone': 'az1', 'host': 'testhost',
|
|
'disabled': 0, 'updated_at': '1775-04-19 05:00:00'}]
|
|
backup_id = self._create_backup(status='available')
|
|
req = webob.Request.blank('/v2/%s/backups/%s' % (
|
|
fake.PROJECT_ID, backup_id))
|
|
req.method = 'DELETE'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
|
|
self.assertEqual(404, res.status_int)
|
|
|
|
db.backup_destroy(context.get_admin_context(), backup_id)
|
|
|
|
@mock.patch('cinder.backup.api.API._get_available_backup_service_host')
|
|
def test_restore_backup_volume_id_specified_json(
|
|
self, _mock_get_backup_host):
|
|
_mock_get_backup_host.return_value = 'testhost'
|
|
backup_id = self._create_backup(status=fields.BackupStatus.AVAILABLE)
|
|
# need to create the volume referenced below first
|
|
volume_name = 'test1'
|
|
volume_id = utils.create_volume(self.context,
|
|
size=5,
|
|
display_name = volume_name).id
|
|
|
|
body = {"restore": {"volume_id": volume_id, }}
|
|
req = webob.Request.blank('/v2/%s/backups/%s/restore' % (
|
|
fake.PROJECT_ID, backup_id))
|
|
req.method = 'POST'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(202, res.status_int)
|
|
self.assertEqual(backup_id, res_dict['restore']['backup_id'])
|
|
self.assertEqual(volume_id, res_dict['restore']['volume_id'])
|
|
self.assertEqual(volume_name, res_dict['restore']['volume_name'])
|
|
|
|
def test_restore_backup_with_no_body(self):
|
|
# omit body from the request
|
|
backup_id = self._create_backup(status=fields.BackupStatus.AVAILABLE)
|
|
|
|
req = webob.Request.blank('/v2/%s/backups/%s/restore' % (
|
|
fake.PROJECT_ID, backup_id))
|
|
req.body = jsonutils.dump_as_bytes(None)
|
|
req.method = 'POST'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
req.headers['Accept'] = 'application/json'
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(400, res.status_int)
|
|
self.assertEqual(400, res_dict['badRequest']['code'])
|
|
self.assertEqual("Missing required element 'restore' in request body.",
|
|
res_dict['badRequest']['message'])
|
|
|
|
db.backup_destroy(context.get_admin_context(), backup_id)
|
|
|
|
def test_restore_backup_with_body_KeyError(self):
|
|
# omit restore from body
|
|
backup_id = self._create_backup(status=fields.BackupStatus.AVAILABLE)
|
|
|
|
req = webob.Request.blank('/v2/%s/backups/%s/restore' % (
|
|
fake.PROJECT_ID, backup_id))
|
|
body = {"": {}}
|
|
req.method = 'POST'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
req.headers['Accept'] = 'application/json'
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(400, res.status_int)
|
|
self.assertEqual(400, res_dict['badRequest']['code'])
|
|
self.assertEqual("Missing required element 'restore' in request body.",
|
|
res_dict['badRequest']['message'])
|
|
|
|
@mock.patch('cinder.db.service_get_all')
|
|
@mock.patch('cinder.volume.api.API.create')
|
|
def test_restore_backup_volume_id_unspecified(
|
|
self, _mock_volume_api_create, _mock_service_get_all):
|
|
# intercept volume creation to ensure created volume
|
|
# has status of available
|
|
def fake_volume_api_create(context, size, name, description):
|
|
volume_id = utils.create_volume(self.context, size=size).id
|
|
return db.volume_get(context, volume_id)
|
|
|
|
_mock_service_get_all.return_value = [
|
|
{'availability_zone': 'az1', 'host': 'testhost',
|
|
'disabled': 0, 'updated_at': timeutils.utcnow()}]
|
|
_mock_volume_api_create.side_effect = fake_volume_api_create
|
|
|
|
backup_id = self._create_backup(size=5,
|
|
status=fields.BackupStatus.AVAILABLE)
|
|
|
|
body = {"restore": {}}
|
|
req = webob.Request.blank('/v2/%s/backups/%s/restore' % (
|
|
fake.PROJECT_ID, backup_id))
|
|
req.method = 'POST'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(202, res.status_int)
|
|
self.assertEqual(backup_id, res_dict['restore']['backup_id'])
|
|
|
|
@mock.patch('cinder.db.service_get_all')
|
|
@mock.patch('cinder.volume.api.API.create')
|
|
def test_restore_backup_name_specified(self,
|
|
_mock_volume_api_create,
|
|
_mock_service_get_all):
|
|
# Intercept volume creation to ensure created volume
|
|
# has status of available
|
|
def fake_volume_api_create(context, size, name, description):
|
|
volume_id = utils.create_volume(self.context, size=size,
|
|
display_name=name).id
|
|
return db.volume_get(context, volume_id)
|
|
|
|
_mock_volume_api_create.side_effect = fake_volume_api_create
|
|
_mock_service_get_all.return_value = [
|
|
{'availability_zone': 'az1', 'host': 'testhost',
|
|
'disabled': 0, 'updated_at': timeutils.utcnow()}]
|
|
|
|
backup_id = self._create_backup(size=5,
|
|
status=fields.BackupStatus.AVAILABLE)
|
|
|
|
body = {"restore": {'name': 'vol-01'}}
|
|
req = webob.Request.blank('/v2/%s/backups/%s/restore' %
|
|
(fake.PROJECT_ID, backup_id))
|
|
req.method = 'POST'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
description = 'auto-created_from_restore_from_backup'
|
|
# Assert that we have indeed passed on the name parameter
|
|
_mock_volume_api_create.assert_called_once_with(
|
|
mock.ANY,
|
|
5,
|
|
body['restore']['name'],
|
|
description)
|
|
|
|
self.assertEqual(202, res.status_int)
|
|
self.assertEqual(backup_id, res_dict['restore']['backup_id'])
|
|
|
|
@mock.patch('cinder.backup.api.API._get_available_backup_service_host')
|
|
def test_restore_backup_name_volume_id_specified(
|
|
self, _mock_get_backup_host):
|
|
_mock_get_backup_host.return_value = 'testhost'
|
|
backup_id = self._create_backup(size=5,
|
|
status=fields.BackupStatus.AVAILABLE)
|
|
orig_vol_name = "vol-00"
|
|
volume_id = utils.create_volume(self.context, size=5,
|
|
display_name=orig_vol_name).id
|
|
body = {"restore": {'name': 'vol-01', 'volume_id': volume_id}}
|
|
req = webob.Request.blank('/v2/%s/backups/%s/restore' % (
|
|
fake.PROJECT_ID, backup_id))
|
|
req.method = 'POST'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(202, res.status_int)
|
|
self.assertEqual(backup_id, res_dict['restore']['backup_id'])
|
|
self.assertEqual(volume_id, res_dict['restore']['volume_id'])
|
|
restored_vol = db.volume_get(self.context,
|
|
res_dict['restore']['volume_id'])
|
|
# Ensure that the original volume name wasn't overridden
|
|
self.assertEqual(orig_vol_name, restored_vol['display_name'])
|
|
|
|
@mock.patch('cinder.backup.API.restore')
|
|
def test_restore_backup_with_InvalidInput(self,
|
|
_mock_volume_api_restore):
|
|
|
|
msg = _("Invalid input")
|
|
_mock_volume_api_restore.side_effect = \
|
|
exception.InvalidInput(reason=msg)
|
|
|
|
backup_id = self._create_backup(status=fields.BackupStatus.AVAILABLE)
|
|
# need to create the volume referenced below first
|
|
volume_id = utils.create_volume(self.context, size=0).id
|
|
body = {"restore": {"volume_id": volume_id, }}
|
|
req = webob.Request.blank('/v2/%s/backups/%s/restore' % (
|
|
fake.PROJECT_ID, backup_id))
|
|
|
|
req.method = 'POST'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(400, res.status_int)
|
|
self.assertEqual(400, res_dict['badRequest']['code'])
|
|
self.assertEqual('Invalid input received: Invalid input',
|
|
res_dict['badRequest']['message'])
|
|
|
|
def test_restore_backup_with_InvalidVolume(self):
|
|
backup_id = self._create_backup(status=fields.BackupStatus.AVAILABLE)
|
|
# need to create the volume referenced below first
|
|
volume_id = utils.create_volume(self.context, size=5,
|
|
status='attaching').id
|
|
|
|
body = {"restore": {"volume_id": volume_id, }}
|
|
req = webob.Request.blank('/v2/%s/backups/%s/restore' % (
|
|
fake.PROJECT_ID, backup_id))
|
|
req.method = 'POST'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(400, res.status_int)
|
|
self.assertEqual(400, res_dict['badRequest']['code'])
|
|
self.assertEqual('Invalid volume: Volume to be restored to must '
|
|
'be available',
|
|
res_dict['badRequest']['message'])
|
|
|
|
db.volume_destroy(context.get_admin_context(), volume_id)
|
|
db.backup_destroy(context.get_admin_context(), backup_id)
|
|
|
|
def test_restore_backup_with_InvalidBackup(self):
|
|
backup_id = self._create_backup(status=fields.BackupStatus.RESTORING)
|
|
# need to create the volume referenced below first
|
|
volume_id = utils.create_volume(self.context, size=5).id
|
|
|
|
body = {"restore": {"volume_id": volume_id, }}
|
|
req = webob.Request.blank('/v2/%s/backups/%s/restore' % (
|
|
fake.PROJECT_ID, backup_id))
|
|
req.method = 'POST'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(400, res.status_int)
|
|
self.assertEqual(400, res_dict['badRequest']['code'])
|
|
self.assertEqual('Invalid backup: Backup status must be available',
|
|
res_dict['badRequest']['message'])
|
|
|
|
db.volume_destroy(context.get_admin_context(), volume_id)
|
|
db.backup_destroy(context.get_admin_context(), backup_id)
|
|
|
|
def test_restore_backup_with_BackupNotFound(self):
|
|
# need to create the volume referenced below first
|
|
volume_id = utils.create_volume(self.context, size=5).id
|
|
|
|
body = {"restore": {"volume_id": volume_id, }}
|
|
req = webob.Request.blank('/v2/%s/backups/%s/restore' %
|
|
(fake.PROJECT_ID, fake.WILL_NOT_BE_FOUND_ID))
|
|
req.method = 'POST'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(404, res.status_int)
|
|
self.assertEqual(404, res_dict['itemNotFound']['code'])
|
|
self.assertEqual('Backup %s could not be found.' %
|
|
fake.WILL_NOT_BE_FOUND_ID,
|
|
res_dict['itemNotFound']['message'])
|
|
|
|
db.volume_destroy(context.get_admin_context(), volume_id)
|
|
|
|
def test_restore_backup_with_VolumeNotFound(self):
|
|
backup_id = self._create_backup(status=fields.BackupStatus.AVAILABLE)
|
|
|
|
body = {"restore": {"volume_id": fake.WILL_NOT_BE_FOUND_ID, }}
|
|
req = webob.Request.blank('/v2/%s/backups/%s/restore' % (
|
|
fake.PROJECT_ID, backup_id))
|
|
req.method = 'POST'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(404, res.status_int)
|
|
self.assertEqual(404, res_dict['itemNotFound']['code'])
|
|
self.assertEqual('Volume %s could not be found.' %
|
|
fake.WILL_NOT_BE_FOUND_ID,
|
|
res_dict['itemNotFound']['message'])
|
|
|
|
db.backup_destroy(context.get_admin_context(), backup_id)
|
|
|
|
@mock.patch('cinder.backup.API.restore')
|
|
def test_restore_backup_with_VolumeSizeExceedsAvailableQuota(
|
|
self,
|
|
_mock_backup_restore):
|
|
|
|
_mock_backup_restore.side_effect = \
|
|
exception.VolumeSizeExceedsAvailableQuota(requested='2',
|
|
consumed='2',
|
|
quota='3')
|
|
|
|
backup_id = self._create_backup(status=fields.BackupStatus.AVAILABLE)
|
|
# need to create the volume referenced below first
|
|
volume_id = utils.create_volume(self.context, size=5).id
|
|
|
|
body = {"restore": {"volume_id": volume_id, }}
|
|
req = webob.Request.blank('/v2/%s/backups/%s/restore' % (
|
|
fake.PROJECT_ID, backup_id))
|
|
|
|
req.method = 'POST'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(413, res.status_int)
|
|
self.assertEqual(413, res_dict['overLimit']['code'])
|
|
self.assertEqual('Requested volume or snapshot exceeds allowed '
|
|
'gigabytes quota. Requested 2G, quota is 3G and '
|
|
'2G has been consumed.',
|
|
res_dict['overLimit']['message'])
|
|
|
|
@mock.patch('cinder.backup.API.restore')
|
|
def test_restore_backup_with_VolumeLimitExceeded(self,
|
|
_mock_backup_restore):
|
|
|
|
_mock_backup_restore.side_effect = \
|
|
exception.VolumeLimitExceeded(allowed=1)
|
|
|
|
backup_id = self._create_backup(status=fields.BackupStatus.AVAILABLE)
|
|
# need to create the volume referenced below first
|
|
volume_id = utils.create_volume(self.context, size=5).id
|
|
|
|
body = {"restore": {"volume_id": volume_id, }}
|
|
req = webob.Request.blank('/v2/%s/backups/%s/restore' % (
|
|
fake.PROJECT_ID, backup_id))
|
|
|
|
req.method = 'POST'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(413, res.status_int)
|
|
self.assertEqual(413, res_dict['overLimit']['code'])
|
|
self.assertEqual("Maximum number of volumes allowed (1) exceeded for"
|
|
" quota 'volumes'.", res_dict['overLimit']['message'])
|
|
|
|
def test_restore_backup_to_undersized_volume(self):
|
|
backup_size = 10
|
|
backup_id = self._create_backup(status=fields.BackupStatus.AVAILABLE,
|
|
size=backup_size)
|
|
# need to create the volume referenced below first
|
|
volume_size = 5
|
|
volume_id = utils.create_volume(self.context, size=volume_size).id
|
|
|
|
body = {"restore": {"volume_id": volume_id, }}
|
|
req = webob.Request.blank('/v2/%s/backups/%s/restore' % (
|
|
fake.PROJECT_ID, backup_id))
|
|
req.method = 'POST'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(400, res.status_int)
|
|
self.assertEqual(400, res_dict['badRequest']['code'])
|
|
self.assertEqual('Invalid volume: volume size %d is too '
|
|
'small to restore backup of size %d.'
|
|
% (volume_size, backup_size),
|
|
res_dict['badRequest']['message'])
|
|
|
|
db.volume_destroy(context.get_admin_context(), volume_id)
|
|
db.backup_destroy(context.get_admin_context(), backup_id)
|
|
|
|
@mock.patch('cinder.backup.api.API._get_available_backup_service_host')
|
|
def test_restore_backup_to_oversized_volume(self, _mock_get_backup_host):
|
|
backup_id = self._create_backup(status=fields.BackupStatus.AVAILABLE,
|
|
size=10)
|
|
_mock_get_backup_host.return_value = 'testhost'
|
|
# need to create the volume referenced below first
|
|
volume_name = 'test1'
|
|
volume_id = utils.create_volume(self.context,
|
|
size=15,
|
|
display_name = volume_name).id
|
|
|
|
body = {"restore": {"volume_id": volume_id, }}
|
|
req = webob.Request.blank('/v2/%s/backups/%s/restore' % (
|
|
fake.PROJECT_ID, backup_id))
|
|
req.method = 'POST'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(202, res.status_int)
|
|
self.assertEqual(backup_id, res_dict['restore']['backup_id'])
|
|
self.assertEqual(volume_id, res_dict['restore']['volume_id'])
|
|
self.assertEqual(volume_name, res_dict['restore']['volume_name'])
|
|
|
|
db.volume_destroy(context.get_admin_context(), volume_id)
|
|
db.backup_destroy(context.get_admin_context(), backup_id)
|
|
|
|
@mock.patch('cinder.backup.rpcapi.BackupAPI.restore_backup')
|
|
@mock.patch('cinder.backup.api.API._get_available_backup_service_host')
|
|
def test_restore_backup_with_different_host(self, _mock_get_backup_host,
|
|
mock_restore_backup):
|
|
volume_name = 'test1'
|
|
backup_id = self._create_backup(status=fields.BackupStatus.AVAILABLE,
|
|
size=10, host='HostA')
|
|
volume_id = utils.create_volume(self.context, size=10,
|
|
host='HostB@BackendB#PoolB',
|
|
display_name=volume_name).id
|
|
|
|
_mock_get_backup_host.return_value = 'testhost'
|
|
body = {"restore": {"volume_id": volume_id, }}
|
|
req = webob.Request.blank('/v2/%s/backups/%s/restore' % (
|
|
fake.PROJECT_ID, backup_id))
|
|
req.method = 'POST'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(202, res.status_int)
|
|
self.assertEqual(backup_id, res_dict['restore']['backup_id'])
|
|
self.assertEqual(volume_id, res_dict['restore']['volume_id'])
|
|
self.assertEqual(volume_name, res_dict['restore']['volume_name'])
|
|
mock_restore_backup.assert_called_once_with(mock.ANY, u'testhost',
|
|
mock.ANY, volume_id)
|
|
# Manually check if restore_backup was called with appropriate backup.
|
|
self.assertEqual(backup_id, mock_restore_backup.call_args[0][2].id)
|
|
|
|
db.volume_destroy(context.get_admin_context(), volume_id)
|
|
db.backup_destroy(context.get_admin_context(), backup_id)
|
|
|
|
def test_export_record_as_non_admin(self):
|
|
backup_id = self._create_backup(status=fields.BackupStatus.AVAILABLE,
|
|
size=10)
|
|
req = webob.Request.blank('/v2/%s/backups/%s/export_record' % (
|
|
fake.PROJECT_ID, backup_id))
|
|
req.method = 'GET'
|
|
req.headers['content-type'] = 'application/json'
|
|
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
# request is not authorized
|
|
self.assertEqual(403, res.status_int)
|
|
|
|
@mock.patch('cinder.backup.api.API._get_available_backup_service_host')
|
|
@mock.patch('cinder.backup.rpcapi.BackupAPI.export_record')
|
|
def test_export_backup_record_id_specified_json(self,
|
|
_mock_export_record_rpc,
|
|
_mock_get_backup_host):
|
|
backup_id = self._create_backup(status=fields.BackupStatus.AVAILABLE,
|
|
size=10)
|
|
ctx = context.RequestContext(fake.USER_ID, fake.PROJECT_ID,
|
|
is_admin=True)
|
|
backup_service = 'fake'
|
|
backup_url = 'fake'
|
|
_mock_export_record_rpc.return_value = \
|
|
{'backup_service': backup_service,
|
|
'backup_url': backup_url}
|
|
_mock_get_backup_host.return_value = 'testhost'
|
|
req = webob.Request.blank('/v2/%s/backups/%s/export_record' % (
|
|
fake.PROJECT_ID, backup_id))
|
|
req.method = 'GET'
|
|
req.headers['content-type'] = 'application/json'
|
|
|
|
res = req.get_response(fakes.wsgi_app(fake_auth_context=ctx))
|
|
res_dict = jsonutils.loads(res.body)
|
|
# verify that request is successful
|
|
self.assertEqual(200, res.status_int)
|
|
self.assertEqual(backup_service,
|
|
res_dict['backup-record']['backup_service'])
|
|
self.assertEqual(backup_url,
|
|
res_dict['backup-record']['backup_url'])
|
|
db.backup_destroy(context.get_admin_context(), backup_id)
|
|
|
|
def test_export_record_with_bad_backup_id(self):
|
|
|
|
ctx = context.RequestContext(fake.USER_ID, fake.PROJECT_ID,
|
|
is_admin=True)
|
|
backup_id = fake.WILL_NOT_BE_FOUND_ID
|
|
req = webob.Request.blank('/v2/%s/backups/%s/export_record' %
|
|
(fake.PROJECT_ID, backup_id))
|
|
req.method = 'GET'
|
|
req.headers['content-type'] = 'application/json'
|
|
|
|
res = req.get_response(fakes.wsgi_app(fake_auth_context=ctx))
|
|
res_dict = jsonutils.loads(res.body)
|
|
self.assertEqual(404, res.status_int)
|
|
self.assertEqual(404, res_dict['itemNotFound']['code'])
|
|
self.assertEqual('Backup %s could not be found.' % backup_id,
|
|
res_dict['itemNotFound']['message'])
|
|
|
|
def test_export_record_for_unavailable_backup(self):
|
|
|
|
backup_id = self._create_backup(status=fields.BackupStatus.RESTORING)
|
|
ctx = context.RequestContext(fake.USER_ID, fake.PROJECT_ID,
|
|
is_admin=True)
|
|
req = webob.Request.blank('/v2/%s/backups/%s/export_record' %
|
|
(fake.PROJECT_ID, backup_id))
|
|
req.method = 'GET'
|
|
req.headers['content-type'] = 'application/json'
|
|
|
|
res = req.get_response(fakes.wsgi_app(fake_auth_context=ctx))
|
|
res_dict = jsonutils.loads(res.body)
|
|
self.assertEqual(400, res.status_int)
|
|
self.assertEqual(400, res_dict['badRequest']['code'])
|
|
self.assertEqual('Invalid backup: Backup status must be available '
|
|
'and not restoring.',
|
|
res_dict['badRequest']['message'])
|
|
db.backup_destroy(context.get_admin_context(), backup_id)
|
|
|
|
@mock.patch('cinder.backup.api.API._get_available_backup_service_host')
|
|
@mock.patch('cinder.backup.rpcapi.BackupAPI.export_record')
|
|
def test_export_record_with_unavailable_service(self,
|
|
_mock_export_record_rpc,
|
|
_mock_get_backup_host):
|
|
msg = 'fake unavailable service'
|
|
_mock_export_record_rpc.side_effect = \
|
|
exception.InvalidBackup(reason=msg)
|
|
_mock_get_backup_host.return_value = 'testhost'
|
|
backup_id = self._create_backup(status=fields.BackupStatus.AVAILABLE)
|
|
ctx = context.RequestContext(fake.USER_ID, fake.PROJECT_ID,
|
|
is_admin=True)
|
|
req = webob.Request.blank('/v2/%s/backups/%s/export_record' %
|
|
(fake.PROJECT_ID, backup_id))
|
|
req.method = 'GET'
|
|
req.headers['content-type'] = 'application/json'
|
|
|
|
res = req.get_response(fakes.wsgi_app(fake_auth_context=ctx))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(400, res.status_int)
|
|
self.assertEqual(400, res_dict['badRequest']['code'])
|
|
self.assertEqual('Invalid backup: %s' % msg,
|
|
res_dict['badRequest']['message'])
|
|
db.backup_destroy(context.get_admin_context(), backup_id)
|
|
|
|
def test_import_record_as_non_admin(self):
|
|
backup_service = 'fake'
|
|
backup_url = 'fake'
|
|
req = webob.Request.blank('/v2/%s/backups/import_record' %
|
|
fake.PROJECT_ID)
|
|
body = {'backup-record': {'backup_service': backup_service,
|
|
'backup_url': backup_url}}
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
req.method = 'POST'
|
|
req.headers['content-type'] = 'application/json'
|
|
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
# request is not authorized
|
|
self.assertEqual(403, res.status_int)
|
|
|
|
@mock.patch('cinder.backup.api.API._list_backup_hosts')
|
|
@mock.patch('cinder.backup.rpcapi.BackupAPI.import_record')
|
|
def test_import_record_volume_id_specified_json(self,
|
|
_mock_import_record_rpc,
|
|
_mock_list_services):
|
|
utils.replace_obj_loader(self, objects.Backup)
|
|
project_id = fake.PROJECT_ID
|
|
backup_service = 'fake'
|
|
ctx = context.RequestContext(fake.USER_ID, project_id, is_admin=True)
|
|
backup = objects.Backup(ctx, id=fake.BACKUP_ID, user_id=fake.USER_ID,
|
|
project_id=project_id,
|
|
status=fields.BackupStatus.AVAILABLE)
|
|
backup_url = backup.encode_record()
|
|
_mock_import_record_rpc.return_value = None
|
|
_mock_list_services.return_value = [backup_service]
|
|
|
|
req = webob.Request.blank('/v2/%s/backups/import_record' %
|
|
fake.PROJECT_ID)
|
|
body = {'backup-record': {'backup_service': backup_service,
|
|
'backup_url': backup_url}}
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
req.method = 'POST'
|
|
req.headers['content-type'] = 'application/json'
|
|
|
|
res = req.get_response(fakes.wsgi_app(fake_auth_context=ctx))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
# verify that request is successful
|
|
self.assertEqual(201, res.status_int)
|
|
self.assertIn('id', res_dict['backup'])
|
|
self.assertEqual(fake.BACKUP_ID, res_dict['backup']['id'])
|
|
|
|
# Verify that entry in DB is as expected
|
|
db_backup = objects.Backup.get_by_id(ctx, fake.BACKUP_ID)
|
|
self.assertEqual(ctx.project_id, db_backup.project_id)
|
|
self.assertEqual(ctx.user_id, db_backup.user_id)
|
|
self.assertEqual(backup_api.IMPORT_VOLUME_ID, db_backup.volume_id)
|
|
self.assertEqual(fields.BackupStatus.CREATING, db_backup.status)
|
|
|
|
@mock.patch('cinder.backup.api.API._list_backup_hosts')
|
|
@mock.patch('cinder.backup.rpcapi.BackupAPI.import_record')
|
|
def test_import_record_volume_id_exists_deleted(self,
|
|
_mock_import_record_rpc,
|
|
_mock_list_services):
|
|
ctx = context.RequestContext(fake.USER_ID, fake.PROJECT_ID,
|
|
is_admin=True)
|
|
utils.replace_obj_loader(self, objects.Backup)
|
|
|
|
# Original backup belonged to a different user_id and project_id
|
|
backup = objects.Backup(ctx, id=fake.BACKUP_ID, user_id=fake.USER2_ID,
|
|
project_id=fake.PROJECT2_ID,
|
|
status=fields.BackupStatus.AVAILABLE)
|
|
backup_url = backup.encode_record()
|
|
|
|
# Deleted DB entry has project_id and user_id set to fake
|
|
backup_id = self._create_backup(fake.VOLUME_ID,
|
|
status=fields.BackupStatus.DELETED)
|
|
backup_service = 'fake'
|
|
_mock_import_record_rpc.return_value = None
|
|
_mock_list_services.return_value = [backup_service]
|
|
|
|
req = webob.Request.blank('/v2/%s/backups/import_record' %
|
|
fake.PROJECT_ID)
|
|
body = {'backup-record': {'backup_service': backup_service,
|
|
'backup_url': backup_url}}
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
req.method = 'POST'
|
|
req.headers['content-type'] = 'application/json'
|
|
|
|
res = req.get_response(fakes.wsgi_app(fake_auth_context=ctx))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
# verify that request is successful
|
|
self.assertEqual(201, res.status_int)
|
|
self.assertIn('id', res_dict['backup'])
|
|
self.assertEqual(fake.BACKUP_ID, res_dict['backup']['id'])
|
|
|
|
# Verify that entry in DB is as expected, with new project and user_id
|
|
db_backup = objects.Backup.get_by_id(ctx, fake.BACKUP_ID)
|
|
self.assertEqual(ctx.project_id, db_backup.project_id)
|
|
self.assertEqual(ctx.user_id, db_backup.user_id)
|
|
self.assertEqual(backup_api.IMPORT_VOLUME_ID, db_backup.volume_id)
|
|
self.assertEqual(fields.BackupStatus.CREATING, db_backup.status)
|
|
|
|
db.backup_destroy(context.get_admin_context(), backup_id)
|
|
|
|
@mock.patch('cinder.backup.api.API._list_backup_hosts')
|
|
def test_import_record_with_no_backup_services(self,
|
|
_mock_list_services):
|
|
ctx = context.RequestContext(fake.USER_ID, fake.PROJECT_ID,
|
|
is_admin=True)
|
|
backup_service = 'fake'
|
|
backup_url = 'fake'
|
|
_mock_list_services.return_value = []
|
|
|
|
req = webob.Request.blank('/v2/%s/backups/import_record' %
|
|
fake.PROJECT_ID)
|
|
body = {'backup-record': {'backup_service': backup_service,
|
|
'backup_url': backup_url}}
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
req.method = 'POST'
|
|
req.headers['content-type'] = 'application/json'
|
|
|
|
res = req.get_response(fakes.wsgi_app(fake_auth_context=ctx))
|
|
res_dict = jsonutils.loads(res.body)
|
|
self.assertEqual(500, res.status_int)
|
|
self.assertEqual(500, res_dict['computeFault']['code'])
|
|
self.assertEqual('Service %s could not be found.'
|
|
% backup_service,
|
|
res_dict['computeFault']['message'])
|
|
|
|
@mock.patch('cinder.backup.api.API._list_backup_hosts')
|
|
def test_import_backup_with_wrong_backup_url(self, _mock_list_services):
|
|
ctx = context.RequestContext(fake.USER_ID, fake.PROJECT_ID,
|
|
is_admin=True)
|
|
backup_service = 'fake'
|
|
backup_url = 'fake'
|
|
_mock_list_services.return_value = ['no-match1', 'no-match2']
|
|
req = webob.Request.blank('/v2/%s/backups/import_record' %
|
|
fake.PROJECT_ID)
|
|
body = {'backup-record': {'backup_service': backup_service,
|
|
'backup_url': backup_url}}
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
req.method = 'POST'
|
|
req.headers['content-type'] = 'application/json'
|
|
|
|
res = req.get_response(fakes.wsgi_app(fake_auth_context=ctx))
|
|
res_dict = jsonutils.loads(res.body)
|
|
self.assertEqual(400, res.status_int)
|
|
self.assertEqual(400, res_dict['badRequest']['code'])
|
|
self.assertEqual("Invalid input received: Can't parse backup record.",
|
|
res_dict['badRequest']['message'])
|
|
|
|
@mock.patch('cinder.backup.api.API._list_backup_hosts')
|
|
def test_import_backup_with_existing_backup_record(self,
|
|
_mock_list_services):
|
|
ctx = context.RequestContext(fake.USER_ID, fake.PROJECT_ID,
|
|
is_admin=True)
|
|
backup_id = self._create_backup(fake.VOLUME_ID)
|
|
backup_service = 'fake'
|
|
backup = objects.Backup.get_by_id(ctx, backup_id)
|
|
backup_url = backup.encode_record()
|
|
_mock_list_services.return_value = ['no-match1', 'no-match2']
|
|
req = webob.Request.blank('/v2/%s/backups/import_record' %
|
|
fake.PROJECT_ID)
|
|
body = {'backup-record': {'backup_service': backup_service,
|
|
'backup_url': backup_url}}
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
req.method = 'POST'
|
|
req.headers['content-type'] = 'application/json'
|
|
|
|
res = req.get_response(fakes.wsgi_app(fake_auth_context=ctx))
|
|
res_dict = jsonutils.loads(res.body)
|
|
self.assertEqual(400, res.status_int)
|
|
self.assertEqual(400, res_dict['badRequest']['code'])
|
|
self.assertEqual('Invalid backup: Backup already exists in database.',
|
|
res_dict['badRequest']['message'])
|
|
|
|
db.backup_destroy(context.get_admin_context(), backup_id)
|
|
|
|
@mock.patch('cinder.backup.api.API._list_backup_hosts')
|
|
@mock.patch('cinder.backup.rpcapi.BackupAPI.import_record')
|
|
def test_import_backup_with_missing_backup_services(self,
|
|
_mock_import_record,
|
|
_mock_list_services):
|
|
ctx = context.RequestContext(fake.USER_ID, fake.PROJECT_ID,
|
|
is_admin=True)
|
|
backup_id = self._create_backup(fake.VOLUME_ID,
|
|
status=fields.BackupStatus.DELETED)
|
|
backup_service = 'fake'
|
|
backup = objects.Backup.get_by_id(ctx, backup_id)
|
|
backup_url = backup.encode_record()
|
|
_mock_list_services.return_value = ['no-match1', 'no-match2']
|
|
_mock_import_record.side_effect = \
|
|
exception.ServiceNotFound(service_id='fake')
|
|
req = webob.Request.blank('/v2/%s/backups/import_record' %
|
|
fake.PROJECT_ID)
|
|
body = {'backup-record': {'backup_service': backup_service,
|
|
'backup_url': backup_url}}
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
req.method = 'POST'
|
|
req.headers['content-type'] = 'application/json'
|
|
|
|
res = req.get_response(fakes.wsgi_app(fake_auth_context=ctx))
|
|
res_dict = jsonutils.loads(res.body)
|
|
self.assertEqual(500, res.status_int)
|
|
self.assertEqual(500, res_dict['computeFault']['code'])
|
|
self.assertEqual('Service %s could not be found.' % backup_service,
|
|
res_dict['computeFault']['message'])
|
|
|
|
db.backup_destroy(context.get_admin_context(), backup_id)
|
|
|
|
def test_import_record_with_missing_body_elements(self):
|
|
ctx = context.RequestContext(fake.USER_ID, fake.PROJECT_ID,
|
|
is_admin=True)
|
|
backup_service = 'fake'
|
|
backup_url = 'fake'
|
|
|
|
# test with no backup_service
|
|
req = webob.Request.blank('/v2/%s/backups/import_record' %
|
|
fake.PROJECT_ID)
|
|
body = {'backup-record': {'backup_url': backup_url}}
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
req.method = 'POST'
|
|
req.headers['content-type'] = 'application/json'
|
|
res = req.get_response(fakes.wsgi_app(fake_auth_context=ctx))
|
|
res_dict = jsonutils.loads(res.body)
|
|
self.assertEqual(400, res.status_int)
|
|
self.assertEqual(400, res_dict['badRequest']['code'])
|
|
self.assertEqual('Incorrect request body format.',
|
|
res_dict['badRequest']['message'])
|
|
|
|
# test with no backup_url
|
|
req = webob.Request.blank('/v2/%s/backups/import_record' %
|
|
fake.PROJECT_ID)
|
|
body = {'backup-record': {'backup_service': backup_service}}
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
req.method = 'POST'
|
|
req.headers['content-type'] = 'application/json'
|
|
|
|
res = req.get_response(fakes.wsgi_app(fake_auth_context=ctx))
|
|
res_dict = jsonutils.loads(res.body)
|
|
self.assertEqual(400, res.status_int)
|
|
self.assertEqual(400, res_dict['badRequest']['code'])
|
|
self.assertEqual('Incorrect request body format.',
|
|
res_dict['badRequest']['message'])
|
|
|
|
# test with no backup_url and backup_url
|
|
req = webob.Request.blank('/v2/%s/backups/import_record' %
|
|
fake.PROJECT_ID)
|
|
body = {'backup-record': {}}
|
|
req.body = jsonutils.dump_as_bytes(body)
|
|
req.method = 'POST'
|
|
req.headers['content-type'] = 'application/json'
|
|
|
|
res = req.get_response(fakes.wsgi_app(fake_auth_context=ctx))
|
|
res_dict = jsonutils.loads(res.body)
|
|
self.assertEqual(400, res.status_int)
|
|
self.assertEqual(400, res_dict['badRequest']['code'])
|
|
self.assertEqual('Incorrect request body format.',
|
|
res_dict['badRequest']['message'])
|
|
|
|
def test_import_record_with_no_body(self):
|
|
ctx = context.RequestContext(fake.USER_ID, fake.PROJECT_ID,
|
|
is_admin=True)
|
|
|
|
req = webob.Request.blank('/v2/%s/backups/import_record' %
|
|
fake.PROJECT_ID)
|
|
req.body = jsonutils.dump_as_bytes(None)
|
|
req.method = 'POST'
|
|
req.headers['content-type'] = 'application/json'
|
|
|
|
res = req.get_response(fakes.wsgi_app(fake_auth_context=ctx))
|
|
res_dict = jsonutils.loads(res.body)
|
|
# verify that request is successful
|
|
self.assertEqual(400, res.status_int)
|
|
self.assertEqual(400, res_dict['badRequest']['code'])
|
|
self.assertEqual("Missing required element 'backup-record' in "
|
|
"request body.",
|
|
res_dict['badRequest']['message'])
|
|
|
|
@mock.patch('cinder.backup.rpcapi.BackupAPI.check_support_to_force_delete',
|
|
return_value=False)
|
|
def test_force_delete_with_not_supported_operation(self,
|
|
mock_check_support):
|
|
backup_id = self._create_backup(status=fields.BackupStatus.AVAILABLE)
|
|
backup = self.backup_api.get(self.context, backup_id)
|
|
self.assertRaises(exception.NotSupportedOperation,
|
|
self.backup_api.delete, self.context, backup, True)
|
|
|
|
@ddt.data(False, True)
|
|
def test_show_incremental_backup(self, backup_from_snapshot):
|
|
volume_id = utils.create_volume(self.context, size=5).id
|
|
parent_backup_id = self._create_backup(
|
|
volume_id, status=fields.BackupStatus.AVAILABLE,
|
|
num_dependent_backups=1)
|
|
backup_id = self._create_backup(volume_id,
|
|
status=fields.BackupStatus.AVAILABLE,
|
|
incremental=True,
|
|
parent_id=parent_backup_id,
|
|
num_dependent_backups=1)
|
|
snapshot = None
|
|
snapshot_id = None
|
|
if backup_from_snapshot:
|
|
snapshot = utils.create_snapshot(self.context,
|
|
volume_id)
|
|
snapshot_id = snapshot.id
|
|
child_backup_id = self._create_backup(
|
|
volume_id, status=fields.BackupStatus.AVAILABLE, incremental=True,
|
|
parent_id=backup_id, snapshot_id=snapshot_id)
|
|
|
|
req = webob.Request.blank('/v2/%s/backups/%s' % (
|
|
fake.PROJECT_ID, backup_id))
|
|
req.method = 'GET'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(200, res.status_int)
|
|
self.assertTrue(res_dict['backup']['is_incremental'])
|
|
self.assertTrue(res_dict['backup']['has_dependent_backups'])
|
|
self.assertIsNone(res_dict['backup']['snapshot_id'])
|
|
|
|
req = webob.Request.blank('/v2/%s/backups/%s' % (
|
|
fake.PROJECT_ID, parent_backup_id))
|
|
req.method = 'GET'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(200, res.status_int)
|
|
self.assertFalse(res_dict['backup']['is_incremental'])
|
|
self.assertTrue(res_dict['backup']['has_dependent_backups'])
|
|
self.assertIsNone(res_dict['backup']['snapshot_id'])
|
|
|
|
req = webob.Request.blank('/v2/%s/backups/%s' % (
|
|
fake.PROJECT_ID, child_backup_id))
|
|
req.method = 'GET'
|
|
req.headers['Content-Type'] = 'application/json'
|
|
res = req.get_response(fakes.wsgi_app(
|
|
fake_auth_context=self.user_context))
|
|
res_dict = jsonutils.loads(res.body)
|
|
|
|
self.assertEqual(200, res.status_int)
|
|
self.assertTrue(res_dict['backup']['is_incremental'])
|
|
self.assertFalse(res_dict['backup']['has_dependent_backups'])
|
|
self.assertEqual(snapshot_id, res_dict['backup']['snapshot_id'])
|
|
|
|
db.backup_destroy(context.get_admin_context(), child_backup_id)
|
|
db.backup_destroy(context.get_admin_context(), backup_id)
|
|
db.backup_destroy(context.get_admin_context(), parent_backup_id)
|
|
if snapshot:
|
|
snapshot.destroy()
|
|
db.volume_destroy(context.get_admin_context(), volume_id)
|