314d4ec1f4
When restoring a volume from backup, if the target volume looks like
it was auto-created as part of the restore and the backed up metadata
contains a name, then apply the name to the target volume. However,
if we are restoring to an existing volume and it has a non-default
name or we do not have a name to restore, then do not modify the
target volume metadata display_name or display_description. This is
intended to be a less drastic solution than commit 7ee80f7
introduced
back in Juno.
Change-Id: I87a62577665b5bd2a82f3305fb715ba6b24d9a78
Closes-Bug: 1539667
423 lines
18 KiB
Python
423 lines
18 KiB
Python
# Copyright 2013 Canonical Ltd.
|
|
# 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 the backup service base driver. """
|
|
|
|
import uuid
|
|
|
|
import mock
|
|
from oslo_serialization import jsonutils
|
|
|
|
from cinder.backup import driver
|
|
from cinder import context
|
|
from cinder import db
|
|
from cinder import exception
|
|
from cinder import objects
|
|
from cinder import test
|
|
from cinder.tests.unit.backup import fake_service
|
|
from cinder.volume import volume_types
|
|
|
|
_backup_db_fields = ['id', 'user_id', 'project_id',
|
|
'volume_id', 'host', 'availability_zone',
|
|
'display_name', 'display_description',
|
|
'container', 'status', 'fail_reason',
|
|
'service_metadata', 'service', 'size',
|
|
'object_count']
|
|
|
|
|
|
class BackupBaseDriverTestCase(test.TestCase):
|
|
|
|
def _create_volume_db_entry(self, id, size):
|
|
vol = {'id': id, 'size': size, 'status': 'available'}
|
|
return db.volume_create(self.ctxt, vol)['id']
|
|
|
|
def _create_backup_db_entry(self, backupid, volid, size,
|
|
userid=str(uuid.uuid4()),
|
|
projectid=str(uuid.uuid4())):
|
|
backup = {'id': backupid, 'size': size, 'volume_id': volid,
|
|
'user_id': userid, 'project_id': projectid}
|
|
return db.backup_create(self.ctxt, backup)['id']
|
|
|
|
def setUp(self):
|
|
super(BackupBaseDriverTestCase, self).setUp()
|
|
self.ctxt = context.get_admin_context()
|
|
|
|
self.volume_id = str(uuid.uuid4())
|
|
self.backup_id = str(uuid.uuid4())
|
|
|
|
self._create_backup_db_entry(self.backup_id, self.volume_id, 1)
|
|
self._create_volume_db_entry(self.volume_id, 1)
|
|
self.backup = objects.Backup.get_by_id(self.ctxt, self.backup_id)
|
|
self.driver = fake_service.FakeBackupService(self.ctxt)
|
|
|
|
def test_get_metadata(self):
|
|
json_metadata = self.driver.get_metadata(self.volume_id)
|
|
metadata = jsonutils.loads(json_metadata)
|
|
self.assertEqual(2, metadata['version'])
|
|
|
|
def test_put_metadata(self):
|
|
metadata = {'version': 1}
|
|
self.driver.put_metadata(self.volume_id, jsonutils.dumps(metadata))
|
|
|
|
def test_get_put_metadata(self):
|
|
json_metadata = self.driver.get_metadata(self.volume_id)
|
|
self.driver.put_metadata(self.volume_id, json_metadata)
|
|
|
|
def test_export_record(self):
|
|
export_record = self.driver.export_record(self.backup)
|
|
self.assertDictEqual({}, export_record)
|
|
|
|
def test_import_record(self):
|
|
export_record = {'key1': 'value1'}
|
|
self.assertIsNone(self.driver.import_record(self.backup,
|
|
export_record))
|
|
|
|
|
|
class BackupMetadataAPITestCase(test.TestCase):
|
|
|
|
def _create_volume_db_entry(self, id, size, display_name,
|
|
display_description):
|
|
vol = {'id': id, 'size': size, 'status': 'available',
|
|
'display_name': display_name,
|
|
'display_description': display_description}
|
|
return db.volume_create(self.ctxt, vol)['id']
|
|
|
|
def setUp(self):
|
|
super(BackupMetadataAPITestCase, self).setUp()
|
|
self.ctxt = context.get_admin_context()
|
|
self.volume_id = str(uuid.uuid4())
|
|
self.backup_id = str(uuid.uuid4())
|
|
self.volume_display_name = 'vol-1'
|
|
self.volume_display_description = 'test vol'
|
|
self._create_volume_db_entry(self.volume_id, 1,
|
|
self.volume_display_name,
|
|
self.volume_display_description)
|
|
self.bak_meta_api = driver.BackupMetadataAPI(self.ctxt)
|
|
|
|
def _add_metadata(self, vol_meta=False, vol_glance_meta=False):
|
|
if vol_meta:
|
|
# Add some VolumeMetadata
|
|
db.volume_metadata_update(self.ctxt, self.volume_id,
|
|
{'fee': 'fi'}, False)
|
|
db.volume_metadata_update(self.ctxt, self.volume_id,
|
|
{'fo': 'fum'}, False)
|
|
|
|
if vol_glance_meta:
|
|
# Add some GlanceMetadata
|
|
db.volume_glance_metadata_create(self.ctxt, self.volume_id,
|
|
'disk_format', 'bare')
|
|
db.volume_glance_metadata_create(self.ctxt, self.volume_id,
|
|
'container_type', 'ovf')
|
|
|
|
def test_get(self):
|
|
# Volume won't have anything other than base by default
|
|
meta = self.bak_meta_api.get(self.volume_id)
|
|
s1 = set(jsonutils.loads(meta).keys())
|
|
s2 = ['version', self.bak_meta_api.TYPE_TAG_VOL_BASE_META]
|
|
self.assertEqual(set(), s1.symmetric_difference(s2))
|
|
|
|
self._add_metadata(vol_glance_meta=True)
|
|
|
|
meta = self.bak_meta_api.get(self.volume_id)
|
|
s1 = set(jsonutils.loads(meta).keys())
|
|
s2 = ['version', self.bak_meta_api.TYPE_TAG_VOL_BASE_META,
|
|
self.bak_meta_api.TYPE_TAG_VOL_GLANCE_META]
|
|
self.assertEqual(set(), s1.symmetric_difference(s2))
|
|
|
|
self._add_metadata(vol_meta=True)
|
|
|
|
meta = self.bak_meta_api.get(self.volume_id)
|
|
s1 = set(jsonutils.loads(meta).keys())
|
|
s2 = ['version', self.bak_meta_api.TYPE_TAG_VOL_BASE_META,
|
|
self.bak_meta_api.TYPE_TAG_VOL_GLANCE_META,
|
|
self.bak_meta_api.TYPE_TAG_VOL_META]
|
|
self.assertEqual(set(), s1.symmetric_difference(s2))
|
|
|
|
def test_put(self):
|
|
meta = self.bak_meta_api.get(self.volume_id)
|
|
self.bak_meta_api.put(self.volume_id, meta)
|
|
|
|
self._add_metadata(vol_glance_meta=True)
|
|
meta = self.bak_meta_api.get(self.volume_id)
|
|
self.bak_meta_api.put(self.volume_id, meta)
|
|
|
|
self._add_metadata(vol_meta=True)
|
|
meta = self.bak_meta_api.get(self.volume_id)
|
|
self.bak_meta_api.put(self.volume_id, meta)
|
|
|
|
def test_put_invalid_version(self):
|
|
container = jsonutils.dumps({'version': 3})
|
|
self.assertRaises(exception.BackupMetadataUnsupportedVersion,
|
|
self.bak_meta_api.put, self.volume_id, container)
|
|
|
|
def test_v1_restore_factory(self):
|
|
fact = self.bak_meta_api._v1_restore_factory()
|
|
|
|
keys = [self.bak_meta_api.TYPE_TAG_VOL_BASE_META,
|
|
self.bak_meta_api.TYPE_TAG_VOL_META,
|
|
self.bak_meta_api.TYPE_TAG_VOL_GLANCE_META]
|
|
|
|
self.assertEqual(set([]),
|
|
set(keys).symmetric_difference(set(fact.keys())))
|
|
|
|
meta_container = {self.bak_meta_api.TYPE_TAG_VOL_BASE_META:
|
|
{'display_name': 'my-backed-up-volume',
|
|
'display_description': 'backed up description'},
|
|
self.bak_meta_api.TYPE_TAG_VOL_META: {},
|
|
self.bak_meta_api.TYPE_TAG_VOL_GLANCE_META: {}}
|
|
|
|
# Emulate restore to new volume
|
|
volume_id = str(uuid.uuid4())
|
|
vol_name = 'restore_backup_%s' % (self.backup_id)
|
|
self._create_volume_db_entry(volume_id, 1, vol_name, 'fake volume')
|
|
|
|
for f in fact:
|
|
func = fact[f][0]
|
|
fields = fact[f][1]
|
|
func(meta_container[f], volume_id, fields)
|
|
|
|
vol = db.volume_get(self.ctxt, volume_id)
|
|
self.assertEqual('my-backed-up-volume', vol['display_name'])
|
|
self.assertEqual('backed up description', vol['display_description'])
|
|
|
|
def test_v1_restore_factory_no_restore_name(self):
|
|
fact = self.bak_meta_api._v1_restore_factory()
|
|
|
|
keys = [self.bak_meta_api.TYPE_TAG_VOL_BASE_META,
|
|
self.bak_meta_api.TYPE_TAG_VOL_META,
|
|
self.bak_meta_api.TYPE_TAG_VOL_GLANCE_META]
|
|
|
|
self.assertEqual(set([]),
|
|
set(keys).symmetric_difference(set(fact.keys())))
|
|
|
|
meta_container = {self.bak_meta_api.TYPE_TAG_VOL_BASE_META:
|
|
{'display_name': 'my-backed-up-volume',
|
|
'display_description': 'backed up description'},
|
|
self.bak_meta_api.TYPE_TAG_VOL_META: {},
|
|
self.bak_meta_api.TYPE_TAG_VOL_GLANCE_META: {}}
|
|
for f in fact:
|
|
func = fact[f][0]
|
|
fields = fact[f][1]
|
|
func(meta_container[f], self.volume_id, fields)
|
|
|
|
vol = db.volume_get(self.ctxt, self.volume_id)
|
|
self.assertEqual(self.volume_display_name, vol['display_name'])
|
|
self.assertEqual(self.volume_display_description,
|
|
vol['display_description'])
|
|
|
|
def test_v2_restore_factory(self):
|
|
fact = self.bak_meta_api._v2_restore_factory()
|
|
|
|
keys = [self.bak_meta_api.TYPE_TAG_VOL_BASE_META,
|
|
self.bak_meta_api.TYPE_TAG_VOL_META,
|
|
self.bak_meta_api.TYPE_TAG_VOL_GLANCE_META]
|
|
|
|
self.assertEqual(set([]),
|
|
set(keys).symmetric_difference(set(fact.keys())))
|
|
|
|
volume_types.create(self.ctxt, 'faketype')
|
|
vol_type = volume_types.get_volume_type_by_name(self.ctxt, 'faketype')
|
|
|
|
meta_container = {self.bak_meta_api.TYPE_TAG_VOL_BASE_META:
|
|
{'encryption_key_id': '123',
|
|
'volume_type_id': vol_type.get('id'),
|
|
'display_name': 'vol-2',
|
|
'display_description': 'description'},
|
|
self.bak_meta_api.TYPE_TAG_VOL_META: {},
|
|
self.bak_meta_api.TYPE_TAG_VOL_GLANCE_META: {}}
|
|
|
|
for f in fact:
|
|
func = fact[f][0]
|
|
fields = fact[f][1]
|
|
func(meta_container[f], self.volume_id, fields)
|
|
|
|
vol = db.volume_get(self.ctxt, self.volume_id)
|
|
self.assertEqual(self.volume_display_name, vol['display_name'])
|
|
self.assertEqual(self.volume_display_description,
|
|
vol['display_description'])
|
|
self.assertEqual('123', vol['encryption_key_id'])
|
|
|
|
def test_restore_vol_glance_meta(self):
|
|
# Fields is an empty list for _restore_vol_glance_meta method.
|
|
fields = []
|
|
container = {}
|
|
self.bak_meta_api._save_vol_glance_meta(container, self.volume_id)
|
|
self.bak_meta_api._restore_vol_glance_meta(container, self.volume_id,
|
|
fields)
|
|
self._add_metadata(vol_glance_meta=True)
|
|
self.bak_meta_api._save_vol_glance_meta(container, self.volume_id)
|
|
self.bak_meta_api._restore_vol_glance_meta(container, self.volume_id,
|
|
fields)
|
|
|
|
def test_restore_vol_meta(self):
|
|
# Fields is an empty list for _restore_vol_meta method.
|
|
fields = []
|
|
container = {}
|
|
self.bak_meta_api._save_vol_meta(container, self.volume_id)
|
|
# Extract volume metadata from container.
|
|
metadata = container.get('volume-metadata', {})
|
|
self.bak_meta_api._restore_vol_meta(metadata, self.volume_id,
|
|
fields)
|
|
self._add_metadata(vol_meta=True)
|
|
self.bak_meta_api._save_vol_meta(container, self.volume_id)
|
|
# Extract volume metadata from container.
|
|
metadata = container.get('volume-metadata', {})
|
|
self.bak_meta_api._restore_vol_meta(metadata, self.volume_id, fields)
|
|
|
|
def test_restore_vol_base_meta(self):
|
|
# Fields is a list with 'encryption_key_id' for
|
|
# _restore_vol_base_meta method.
|
|
fields = ['encryption_key_id']
|
|
container = {}
|
|
self.bak_meta_api._save_vol_base_meta(container, self.volume_id)
|
|
self.bak_meta_api._restore_vol_base_meta(container, self.volume_id,
|
|
fields)
|
|
|
|
def _create_encrypted_volume_db_entry(self, id, type_id, encrypted):
|
|
if encrypted:
|
|
vol = {'id': id, 'size': 1, 'status': 'available',
|
|
'volume_type_id': type_id, 'encryption_key_id': 'fake_id'}
|
|
else:
|
|
vol = {'id': id, 'size': 1, 'status': 'available',
|
|
'volume_type_id': type_id, 'encryption_key_id': None}
|
|
return db.volume_create(self.ctxt, vol)['id']
|
|
|
|
def test_restore_encrypted_vol_to_different_volume_type(self):
|
|
fields = ['encryption_key_id']
|
|
container = {}
|
|
|
|
# Create an encrypted volume
|
|
enc_vol1_id = self._create_encrypted_volume_db_entry(str(uuid.uuid4()),
|
|
'enc_vol_type',
|
|
True)
|
|
|
|
# Create a second encrypted volume, of a different volume type
|
|
enc_vol2_id = self._create_encrypted_volume_db_entry(str(uuid.uuid4()),
|
|
'enc_vol_type2',
|
|
True)
|
|
|
|
# Backup the first volume and attempt to restore to the second
|
|
self.bak_meta_api._save_vol_base_meta(container, enc_vol1_id)
|
|
self.assertRaises(exception.EncryptedBackupOperationFailed,
|
|
self.bak_meta_api._restore_vol_base_meta,
|
|
container[self.bak_meta_api.TYPE_TAG_VOL_BASE_META],
|
|
enc_vol2_id, fields)
|
|
|
|
def test_restore_unencrypted_vol_to_different_volume_type(self):
|
|
fields = ['encryption_key_id']
|
|
container = {}
|
|
|
|
# Create an unencrypted volume
|
|
vol1_id = self._create_encrypted_volume_db_entry(str(uuid.uuid4()),
|
|
'vol_type1',
|
|
False)
|
|
|
|
# Create a second unencrypted volume, of a different volume type
|
|
vol2_id = self._create_encrypted_volume_db_entry(str(uuid.uuid4()),
|
|
'vol_type2',
|
|
False)
|
|
|
|
# Backup the first volume and restore to the second
|
|
self.bak_meta_api._save_vol_base_meta(container, vol1_id)
|
|
self.bak_meta_api._restore_vol_base_meta(
|
|
container[self.bak_meta_api.TYPE_TAG_VOL_BASE_META], vol2_id,
|
|
fields)
|
|
self.assertNotEqual(
|
|
db.volume_get(self.ctxt, vol1_id)['volume_type_id'],
|
|
db.volume_get(self.ctxt, vol2_id)['volume_type_id'])
|
|
|
|
def test_restore_encrypted_vol_to_same_volume_type(self):
|
|
fields = ['encryption_key_id']
|
|
container = {}
|
|
|
|
# Create an encrypted volume
|
|
enc_vol1_id = self._create_encrypted_volume_db_entry(str(uuid.uuid4()),
|
|
'enc_vol_type',
|
|
True)
|
|
|
|
# Create an encrypted volume of the same type
|
|
enc_vol2_id = self._create_encrypted_volume_db_entry(str(uuid.uuid4()),
|
|
'enc_vol_type',
|
|
True)
|
|
|
|
# Backup the first volume and restore to the second
|
|
self.bak_meta_api._save_vol_base_meta(container, enc_vol1_id)
|
|
self.bak_meta_api._restore_vol_base_meta(
|
|
container[self.bak_meta_api.TYPE_TAG_VOL_BASE_META], enc_vol2_id,
|
|
fields)
|
|
|
|
def test_restore_encrypted_vol_to_none_type_source_type_unavailable(self):
|
|
fields = ['encryption_key_id']
|
|
container = {}
|
|
enc_vol_id = self._create_encrypted_volume_db_entry(str(uuid.uuid4()),
|
|
'enc_vol_type',
|
|
True)
|
|
undef_vol_id = self._create_encrypted_volume_db_entry(
|
|
str(uuid.uuid4()), None, False)
|
|
self.bak_meta_api._save_vol_base_meta(container, enc_vol_id)
|
|
self.assertRaises(exception.EncryptedBackupOperationFailed,
|
|
self.bak_meta_api._restore_vol_base_meta,
|
|
container[self.bak_meta_api.TYPE_TAG_VOL_BASE_META],
|
|
undef_vol_id, fields)
|
|
|
|
def test_restore_encrypted_vol_to_none_type_source_type_available(self):
|
|
fields = ['encryption_key_id']
|
|
container = {}
|
|
db.volume_type_create(self.ctxt, {'id': 'enc_vol_type_id',
|
|
'name': 'enc_vol_type'})
|
|
enc_vol_id = self._create_encrypted_volume_db_entry(str(uuid.uuid4()),
|
|
'enc_vol_type_id',
|
|
True)
|
|
undef_vol_id = self._create_encrypted_volume_db_entry(
|
|
str(uuid.uuid4()), None, False)
|
|
self.bak_meta_api._save_vol_base_meta(container, enc_vol_id)
|
|
self.bak_meta_api._restore_vol_base_meta(
|
|
container[self.bak_meta_api.TYPE_TAG_VOL_BASE_META], undef_vol_id,
|
|
fields)
|
|
self.assertEqual(
|
|
db.volume_get(self.ctxt, undef_vol_id)['volume_type_id'],
|
|
db.volume_get(self.ctxt, enc_vol_id)['volume_type_id'])
|
|
|
|
def test_filter(self):
|
|
metadata = {'a': 1, 'b': 2, 'c': 3}
|
|
self.assertEqual(metadata, self.bak_meta_api._filter(metadata, []))
|
|
self.assertEqual({'b': 2}, self.bak_meta_api._filter(metadata, ['b']))
|
|
self.assertEqual({}, self.bak_meta_api._filter(metadata, ['d']))
|
|
self.assertEqual({'a': 1, 'b': 2},
|
|
self.bak_meta_api._filter(metadata, ['a', 'b']))
|
|
|
|
def test_save_vol_glance_meta(self):
|
|
container = {}
|
|
self.bak_meta_api._save_vol_glance_meta(container, self.volume_id)
|
|
|
|
def test_save_vol_meta(self):
|
|
container = {}
|
|
self.bak_meta_api._save_vol_meta(container, self.volume_id)
|
|
|
|
def test_save_vol_base_meta(self):
|
|
container = {}
|
|
self.bak_meta_api._save_vol_base_meta(container, self.volume_id)
|
|
|
|
def test_is_serializable(self):
|
|
data = {'foo': 'bar'}
|
|
if self.bak_meta_api._is_serializable(data):
|
|
jsonutils.dumps(data)
|
|
|
|
def test_is_not_serializable(self):
|
|
data = {'foo': 'bar'}
|
|
with mock.patch.object(jsonutils, 'dumps') as mock_dumps:
|
|
mock_dumps.side_effect = TypeError
|
|
self.assertFalse(self.bak_meta_api._is_serializable(data))
|
|
mock_dumps.assert_called_once_with(data)
|