Add interface class for backup drivers

This fix introduces an interface class to define the structure of all backup
drivers. It also renames backup/service to backup/driver.
To be backward compatible a mapping functionality is introduced to map old
backup services to backup drivers.

Implements: blueprint refactor-backup-service

Change-Id: Ic3fca567111f4bd1b221689c73cd5c3bab4a777b
This commit is contained in:
Marc Koderer 2013-06-27 12:05:56 +02:00
parent faffe83d88
commit e2a58aa47d
12 changed files with 131 additions and 65 deletions

33
cinder/backup/driver.py Normal file
View File

@ -0,0 +1,33 @@
# Copyright (C) 2013 Deutsche Telekom AG
# 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.
#
"""Base class for all backup drivers."""
from cinder.db import base
class BackupDriver(base.Base):
def backup(self, backup, volume_file):
"""Starts a backup of a specified volume"""
raise NotImplementedError()
def restore(self, backup, volume_id, volume_file):
"""Restores a saved backup"""
raise NotImplementedError()
def delete(self, backup):
"""Deletes a saved backup"""
raise NotImplementedError()

View File

@ -15,15 +15,17 @@
"""Ceph Backup Service Implementation"""
from cinder.db import base
import os
import time
import eventlet
from oslo.config import cfg
from cinder.backup.driver import BackupDriver
from cinder import exception
from cinder.openstack.common import log as logging
from cinder import units
import cinder.volume.drivers.rbd as rbddriver
import eventlet
import os
from oslo.config import cfg
import time
try:
import rados
@ -54,11 +56,11 @@ CONF = cfg.CONF
CONF.register_opts(service_opts)
class CephBackupService(base.Base):
class CephBackupDriver(BackupDriver):
"""Backup up Cinder volumes to Ceph Object Store"""
def __init__(self, context, db_driver=None):
super(CephBackupService, self).__init__(db_driver)
super(CephBackupDriver, self).__init__(db_driver)
self.rbd = rbd
self.rados = rados
self.context = context
@ -271,5 +273,5 @@ class CephBackupService(base.Base):
LOG.debug(_("delete '%s' finished") % (backup_id))
def get_backup_service(context):
return CephBackupService(context)
def get_backup_driver(context):
return CephBackupDriver(context)

View File

@ -40,7 +40,7 @@ import StringIO
import eventlet
from oslo.config import cfg
from cinder.db import base
from cinder.backup.driver import BackupDriver
from cinder import exception
from cinder.openstack.common import log as logging
from cinder.openstack.common import timeutils
@ -83,11 +83,11 @@ CONF = cfg.CONF
CONF.register_opts(swiftbackup_service_opts)
class SwiftBackupService(base.Base):
class SwiftBackupDriver(BackupDriver):
"""Provides backup, restore and delete of backup objects within Swift."""
SERVICE_VERSION = '1.0.0'
SERVICE_VERSION_MAPPING = {'1.0.0': '_restore_v1'}
DRIVER_VERSION = '1.0.0'
DRIVER_VERSION_MAPPING = {'1.0.0': '_restore_v1'}
def _get_compressor(self, algorithm):
try:
@ -134,7 +134,7 @@ class SwiftBackupService(base.Base):
preauthtoken=self.context.auth_token,
starting_backoff=self.swift_backoff)
super(SwiftBackupService, self).__init__(db_driver)
super(SwiftBackupDriver, self).__init__(db_driver)
def _check_container_exists(self, container):
LOG.debug(_('_check_container_exists: container: %s') % container)
@ -192,7 +192,7 @@ class SwiftBackupService(base.Base):
' metadata filename: %(filename)s') %
{'container': container, 'filename': filename})
metadata = {}
metadata['version'] = self.SERVICE_VERSION
metadata['version'] = self.DRIVER_VERSION
metadata['backup_id'] = backup['id']
metadata['volume_id'] = volume_id
metadata['backup_name'] = backup['display_name']
@ -421,7 +421,7 @@ class SwiftBackupService(base.Base):
metadata_version = metadata['version']
LOG.debug(_('Restoring swift backup version %s'), metadata_version)
try:
restore_func = getattr(self, self.SERVICE_VERSION_MAPPING.get(
restore_func = getattr(self, self.DRIVER_VERSION_MAPPING.get(
metadata_version))
except TypeError:
err = (_('No support to restore swift backup version %s')
@ -467,5 +467,5 @@ class SwiftBackupService(base.Base):
LOG.debug(_('delete %s finished') % backup['id'])
def get_backup_service(context):
return SwiftBackupService(context)
def get_backup_driver(context):
return SwiftBackupDriver(context)

View File

@ -46,11 +46,17 @@ from cinder.openstack.common import log as logging
LOG = logging.getLogger(__name__)
backup_manager_opts = [
cfg.StrOpt('backup_service',
default='cinder.backup.services.swift',
help='Service to use for backups.'),
cfg.StrOpt('backup_driver',
default='cinder.backup.drivers.swift',
help='Driver to use for backups.',
deprecated_name='backup_service'),
]
# This map doesn't need to be extended in the future since it's only
# for old backup services
mapper = {'cinder.backup.services.swift': 'cinder.backup.drivers.swift',
'cinder.backup.services.ceph': 'cinder.backup.drivers.ceph'}
CONF = cfg.CONF
CONF.register_opts(backup_manager_opts)
@ -61,7 +67,7 @@ class BackupManager(manager.SchedulerDependentManager):
RPC_API_VERSION = '1.0'
def __init__(self, service_name=None, *args, **kwargs):
self.service = importutils.import_module(CONF.backup_service)
self.service = importutils.import_module(self.driver_name)
self.az = CONF.storage_availability_zone
self.volume_manager = importutils.import_object(
CONF.volume_manager)
@ -70,6 +76,19 @@ class BackupManager(manager.SchedulerDependentManager):
*args, **kwargs)
self.driver.db = self.db
@property
def driver_name(self):
"""This function maps old backup services to backup drivers."""
return self._map_service_to_driver(CONF.backup_driver)
def _map_service_to_driver(self, service):
"""Maps services to drivers."""
if service in mapper:
return mapper[service]
return service
def init_host(self):
"""Do any initialization that needs to be run if this is a
standalone service.
@ -124,7 +143,7 @@ class BackupManager(manager.SchedulerDependentManager):
{'backup_id': backup_id, 'volume_id': volume_id})
self.db.backup_update(context, backup_id, {'host': self.host,
'service':
CONF.backup_service})
self.driver_name})
expected_status = 'backing-up'
actual_status = volume['status']
@ -152,7 +171,7 @@ class BackupManager(manager.SchedulerDependentManager):
raise exception.InvalidBackup(reason=err)
try:
backup_service = self.service.get_backup_service(context)
backup_service = self.service.get_backup_driver(context)
self.driver.backup_volume(context, backup, backup_service)
except Exception as err:
with excutils.save_and_reraise_exception():
@ -210,8 +229,8 @@ class BackupManager(manager.SchedulerDependentManager):
volume['id'], volume['size'],
backup['id'], backup['size'])
backup_service = backup['service']
configured_service = CONF.backup_service
backup_service = self._map_service_to_driver(backup['service'])
configured_service = self.driver_name
if backup_service != configured_service:
err = _('restore_backup aborted, the backup service currently'
' configured [%(configured_service)s] is not the'
@ -225,7 +244,7 @@ class BackupManager(manager.SchedulerDependentManager):
raise exception.InvalidBackup(reason=err)
try:
backup_service = self.service.get_backup_service(context)
backup_service = self.service.get_backup_driver(context)
self.driver.restore_backup(context, backup, volume,
backup_service)
except Exception as err:
@ -261,9 +280,9 @@ class BackupManager(manager.SchedulerDependentManager):
'fail_reason': err})
raise exception.InvalidBackup(reason=err)
backup_service = backup['service']
backup_service = self._map_service_to_driver(backup['service'])
if backup_service is not None:
configured_service = CONF.backup_service
configured_service = self.driver_name
if backup_service != configured_service:
err = _('delete_backup aborted, the backup service currently'
' configured [%(configured_service)s] is not the'
@ -277,7 +296,7 @@ class BackupManager(manager.SchedulerDependentManager):
raise exception.InvalidBackup(reason=err)
try:
backup_service = self.service.get_backup_service(context)
backup_service = self.service.get_backup_driver(context)
backup_service.delete(backup)
except Exception as err:
with excutils.save_and_reraise_exception():

View File

@ -13,13 +13,14 @@
# License for the specific language governing permissions and limitations
# under the License.
from cinder.backup.driver import BackupDriver
from cinder.db import base
from cinder.openstack.common import log as logging
LOG = logging.getLogger(__name__)
class FakeBackupService(base.Base):
class FakeBackupService(BackupDriver):
def __init__(self, context, db_driver=None):
super(FakeBackupService, self).__init__(db_driver)
@ -37,5 +38,5 @@ class FakeBackupService(base.Base):
raise IOError('fake')
def get_backup_service(context):
def get_backup_driver(context):
return FakeBackupService(context)

View File

@ -26,7 +26,7 @@ CONF.import_opt('iscsi_num_targets', 'cinder.volume.drivers.lvm')
CONF.import_opt('policy_file', 'cinder.policy')
CONF.import_opt('volume_driver', 'cinder.volume.manager')
CONF.import_opt('xiv_proxy', 'cinder.volume.drivers.xiv')
CONF.import_opt('backup_service', 'cinder.backup.manager')
CONF.import_opt('backup_driver', 'cinder.backup.manager')
def_vol_type = 'fake_vol_type'
@ -45,4 +45,4 @@ def set_defaults(conf):
conf.set_default('sqlite_synchronous', False)
conf.set_default('policy_file', 'cinder/tests/policy.json')
conf.set_default('xiv_proxy', 'cinder.tests.test_xiv.XIVFakeProxyDriver')
conf.set_default('backup_service', 'cinder.tests.backup.fake_service')
conf.set_default('backup_driver', 'cinder.tests.backup.fake_service')

View File

@ -24,7 +24,7 @@ flags.DECLARE('iscsi_num_targets', 'cinder.volume.drivers.lvm')
flags.DECLARE('policy_file', 'cinder.policy')
flags.DECLARE('volume_driver', 'cinder.volume.manager')
flags.DECLARE('xiv_proxy', 'cinder.volume.drivers.xiv')
flags.DECLARE('backup_service', 'cinder.backup.manager')
flags.DECLARE('backup_driver', 'cinder.backup.manager')
def_vol_type = 'fake_vol_type'
@ -42,4 +42,4 @@ def set_defaults(conf):
conf.set_default('sqlite_synchronous', False)
conf.set_default('policy_file', 'cinder/tests/policy.json')
conf.set_default('xiv_proxy', 'cinder.tests.test_xiv.XIVFakeProxyDriver')
conf.set_default('backup_service', 'cinder.tests.backup.fake_service')
conf.set_default('backup_driver', 'cinder.tests.backup.fake_service')

View File

@ -77,7 +77,7 @@ class BackupTestCase(test.TestCase):
backup['container'] = container
backup['status'] = status
backup['fail_reason'] = ''
backup['service'] = CONF.backup_service
backup['service'] = CONF.backup_driver
backup['size'] = size
backup['object_count'] = object_count
return db.backup_create(self.ctxt, backup)['id']
@ -407,3 +407,14 @@ class BackupTestCase(test.TestCase):
ctxt_read_deleted = context.get_admin_context('yes')
backups = db.backup_get_all_by_host(ctxt_read_deleted, 'testhost')
self.assertEqual(len(backups), 2)
def test_backup_manager_driver_name(self):
""""Test mapping between backup services and backup drivers."""
old_setting = CONF.backup_driver
setattr(cfg.CONF, 'backup_driver', "cinder.backup.services.swift")
backup_mgr = \
importutils.import_object(CONF.backup_manager)
self.assertEqual('cinder.backup.drivers.swift',
backup_mgr.driver_name)
setattr(cfg.CONF, 'backup_driver', old_setting)

View File

@ -19,11 +19,11 @@ import os
import tempfile
import uuid
from cinder.backup.services.ceph import CephBackupService
from cinder.backup.drivers.ceph import CephBackupDriver
from cinder.tests.backup.fake_rados import mock_rados
from cinder.tests.backup.fake_rados import mock_rbd
from cinder.backup.services import ceph
from cinder.backup.drivers import ceph
from cinder import context
from cinder import db
from cinder import exception
@ -73,7 +73,7 @@ class BackupCephTestCase(test.TestCase):
self.volume_file.seek(0)
def test_get_rbd_support(self):
service = CephBackupService(self.ctxt)
service = CephBackupDriver(self.ctxt)
self.assertFalse(hasattr(service.rbd, 'RBD_FEATURE_LAYERING'))
self.assertFalse(hasattr(service.rbd, 'RBD_FEATURE_STRIPINGV2'))
@ -95,7 +95,7 @@ class BackupCephTestCase(test.TestCase):
self.assertEquals(features, 1 | 2)
def test_tranfer_data_from_rbd(self):
service = CephBackupService(self.ctxt)
service = CephBackupDriver(self.ctxt)
with tempfile.NamedTemporaryFile() as test_file:
self.volume_file.seek(0)
@ -117,7 +117,7 @@ class BackupCephTestCase(test.TestCase):
self.assertEquals(checksum.digest(), self.checksum.digest())
def test_tranfer_data_to_rbd(self):
service = CephBackupService(self.ctxt)
service = CephBackupDriver(self.ctxt)
with tempfile.NamedTemporaryFile() as test_file:
checksum = hashlib.sha256()
@ -135,7 +135,7 @@ class BackupCephTestCase(test.TestCase):
self.assertEquals(checksum.digest(), self.checksum.digest())
def test_backup_volume_from_file(self):
service = CephBackupService(self.ctxt)
service = CephBackupDriver(self.ctxt)
with tempfile.NamedTemporaryFile() as test_file:
checksum = hashlib.sha256()
@ -157,21 +157,21 @@ class BackupCephTestCase(test.TestCase):
super(BackupCephTestCase, self).tearDown()
def test_backup_error1(self):
service = CephBackupService(self.ctxt)
service = CephBackupDriver(self.ctxt)
backup = db.backup_get(self.ctxt, self.backup_id)
self._create_volume_db_entry(self.vol_id, 0)
self.assertRaises(exception.InvalidParameterValue, service.backup,
backup, self.volume_file)
def test_backup_error2(self):
service = CephBackupService(self.ctxt)
service = CephBackupDriver(self.ctxt)
backup = db.backup_get(self.ctxt, self.backup_id)
self._create_volume_db_entry(self.vol_id, 1)
self.assertRaises(exception.BackupVolumeInvalidType, service.backup,
backup, None)
def test_backup_good(self):
service = CephBackupService(self.ctxt)
service = CephBackupDriver(self.ctxt)
backup = db.backup_get(self.ctxt, self.backup_id)
self._create_volume_db_entry(self.vol_id, 1)
@ -190,7 +190,7 @@ class BackupCephTestCase(test.TestCase):
self.assertEquals(checksum.digest(), self.checksum.digest())
def test_restore(self):
service = CephBackupService(self.ctxt)
service = CephBackupDriver(self.ctxt)
self._create_volume_db_entry(self.vol_id, 1)
backup = db.backup_get(self.ctxt, self.backup_id)
@ -213,7 +213,7 @@ class BackupCephTestCase(test.TestCase):
self.assertEquals(checksum.digest(), self.checksum.digest())
def test_delete(self):
service = CephBackupService(self.ctxt)
service = CephBackupDriver(self.ctxt)
self._create_volume_db_entry(self.vol_id, 1)
backup = db.backup_get(self.ctxt, self.backup_id)

View File

@ -25,7 +25,7 @@ import zlib
from swiftclient import client as swift
from cinder.backup.services.swift import SwiftBackupService
from cinder.backup.drivers.swift import SwiftBackupDriver
from cinder import context
from cinder import db
from cinder import exception
@ -81,7 +81,7 @@ class BackupSwiftTestCase(test.TestCase):
def test_backup_uncompressed(self):
self._create_backup_db_entry()
self.flags(backup_compression_algorithm='none')
service = SwiftBackupService(self.ctxt)
service = SwiftBackupDriver(self.ctxt)
self.volume_file.seek(0)
backup = db.backup_get(self.ctxt, 123)
service.backup(backup, self.volume_file)
@ -89,7 +89,7 @@ class BackupSwiftTestCase(test.TestCase):
def test_backup_bz2(self):
self._create_backup_db_entry()
self.flags(backup_compression_algorithm='bz2')
service = SwiftBackupService(self.ctxt)
service = SwiftBackupDriver(self.ctxt)
self.volume_file.seek(0)
backup = db.backup_get(self.ctxt, 123)
service.backup(backup, self.volume_file)
@ -97,14 +97,14 @@ class BackupSwiftTestCase(test.TestCase):
def test_backup_zlib(self):
self._create_backup_db_entry()
self.flags(backup_compression_algorithm='zlib')
service = SwiftBackupService(self.ctxt)
service = SwiftBackupDriver(self.ctxt)
self.volume_file.seek(0)
backup = db.backup_get(self.ctxt, 123)
service.backup(backup, self.volume_file)
def test_backup_default_container(self):
self._create_backup_db_entry(container=None)
service = SwiftBackupService(self.ctxt)
service = SwiftBackupDriver(self.ctxt)
self.volume_file.seek(0)
backup = db.backup_get(self.ctxt, 123)
service.backup(backup, self.volume_file)
@ -114,7 +114,7 @@ class BackupSwiftTestCase(test.TestCase):
def test_backup_custom_container(self):
container_name = 'fake99'
self._create_backup_db_entry(container=container_name)
service = SwiftBackupService(self.ctxt)
service = SwiftBackupDriver(self.ctxt)
self.volume_file.seek(0)
backup = db.backup_get(self.ctxt, 123)
service.backup(backup, self.volume_file)
@ -124,7 +124,7 @@ class BackupSwiftTestCase(test.TestCase):
def test_create_backup_container_check_wraps_socket_error(self):
container_name = 'socket_error_on_head'
self._create_backup_db_entry(container=container_name)
service = SwiftBackupService(self.ctxt)
service = SwiftBackupDriver(self.ctxt)
self.volume_file.seek(0)
backup = db.backup_get(self.ctxt, 123)
self.assertRaises(exception.SwiftConnectionFailed,
@ -134,7 +134,7 @@ class BackupSwiftTestCase(test.TestCase):
def test_create_backup_put_object_wraps_socket_error(self):
container_name = 'socket_error_on_put'
self._create_backup_db_entry(container=container_name)
service = SwiftBackupService(self.ctxt)
service = SwiftBackupDriver(self.ctxt)
self.volume_file.seek(0)
backup = db.backup_get(self.ctxt, 123)
self.assertRaises(exception.SwiftConnectionFailed,
@ -143,7 +143,7 @@ class BackupSwiftTestCase(test.TestCase):
def test_restore(self):
self._create_backup_db_entry()
service = SwiftBackupService(self.ctxt)
service = SwiftBackupDriver(self.ctxt)
with tempfile.NamedTemporaryFile() as volume_file:
backup = db.backup_get(self.ctxt, 123)
@ -152,7 +152,7 @@ class BackupSwiftTestCase(test.TestCase):
def test_restore_wraps_socket_error(self):
container_name = 'socket_error_on_get'
self._create_backup_db_entry(container=container_name)
service = SwiftBackupService(self.ctxt)
service = SwiftBackupDriver(self.ctxt)
with tempfile.NamedTemporaryFile() as volume_file:
backup = db.backup_get(self.ctxt, 123)
@ -163,7 +163,7 @@ class BackupSwiftTestCase(test.TestCase):
def test_restore_unsupported_version(self):
container_name = 'unsupported_version'
self._create_backup_db_entry(container=container_name)
service = SwiftBackupService(self.ctxt)
service = SwiftBackupDriver(self.ctxt)
with tempfile.NamedTemporaryFile() as volume_file:
backup = db.backup_get(self.ctxt, 123)
@ -173,21 +173,21 @@ class BackupSwiftTestCase(test.TestCase):
def test_delete(self):
self._create_backup_db_entry()
service = SwiftBackupService(self.ctxt)
service = SwiftBackupDriver(self.ctxt)
backup = db.backup_get(self.ctxt, 123)
service.delete(backup)
def test_delete_wraps_socket_error(self):
container_name = 'socket_error_on_delete'
self._create_backup_db_entry(container=container_name)
service = SwiftBackupService(self.ctxt)
service = SwiftBackupDriver(self.ctxt)
backup = db.backup_get(self.ctxt, 123)
self.assertRaises(exception.SwiftConnectionFailed,
service.delete,
backup)
def test_get_compressor(self):
service = SwiftBackupService(self.ctxt)
service = SwiftBackupDriver(self.ctxt)
compressor = service._get_compressor('None')
self.assertEquals(compressor, None)
compressor = service._get_compressor('zlib')
@ -197,7 +197,7 @@ class BackupSwiftTestCase(test.TestCase):
self.assertRaises(ValueError, service._get_compressor, 'fake')
def test_check_container_exists(self):
service = SwiftBackupService(self.ctxt)
service = SwiftBackupDriver(self.ctxt)
exists = service._check_container_exists('fake_container')
self.assertEquals(exists, True)
exists = service._check_container_exists('missing_container')

View File

@ -323,11 +323,11 @@
#
# Service to use for backups. (string value)
#backup_service=cinder.backup.services.swift
#backup_driver=cinder.backup.drivers.swift
#
# Options defined in cinder.backup.services.swift
# Options defined in cinder.backup.drivers.swift
#
# The URL of the Swift endpoint (string value)