Delete TSM Backup driver

TSM backup driver is not supported by vendor
for a while and deprecated in Victoria release,
so it's safe to delete it now.

Change-Id: I0ab07708843fcbb64bf932667e9b79aab259f698
This commit is contained in:
Ivan Kolodyazhny 2020-10-16 17:02:40 +03:00
parent 27959daf2b
commit cddcc6e507
7 changed files with 4 additions and 909 deletions

View File

@ -1,515 +0,0 @@
# Copyright 2013 IBM Corp
# 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.
"""Backup driver for IBM Tivoli Storage Manager (TSM).
Implementation of a backup service that uses IBM Tivoli Storage Manager (TSM)
as the backend. The driver uses TSM command line dsmc utility to
run the backup and restore operations.
This version supports backup of block devices, e.g, FC, iSCSI, local as well as
regular files.
A prerequisite for using the IBM TSM backup service is configuring the
Cinder host for using TSM.
"""
import json
import os
import stat
from oslo_concurrency import processutils
from oslo_config import cfg
from oslo_log import log as logging
from oslo_log import versionutils
from cinder.backup import driver
from cinder import exception
from cinder.i18n import _
from cinder import interface
import cinder.privsep.path
from cinder import utils
LOG = logging.getLogger(__name__)
tsm_opts = [
cfg.StrOpt('backup_tsm_volume_prefix',
default='backup',
help='Volume prefix for the backup id when backing up to TSM'),
cfg.StrOpt('backup_tsm_password',
default='password',
help='TSM password for the running username',
secret=True),
cfg.BoolOpt('backup_tsm_compression',
default=True,
help='Enable or Disable compression for backups'),
]
CONF = cfg.CONF
CONF.register_opts(tsm_opts)
VALID_BACKUP_MODES = ['image', 'file']
def _get_backup_metadata(backup, operation):
"""Return metadata persisted with backup object."""
try:
svc_dict = json.loads(backup.service_metadata)
backup_path = svc_dict.get('backup_path')
backup_mode = svc_dict.get('backup_mode')
except TypeError:
# for backwards compatibility
vol_prefix = CONF.backup_tsm_volume_prefix
backup_id = backup['id']
backup_path = utils.make_dev_path('%s-%s' %
(vol_prefix, backup_id))
backup_mode = 'image'
if backup_mode not in VALID_BACKUP_MODES:
volume_id = backup['volume_id']
backup_id = backup['id']
err = (_('%(op)s: backup %(bck_id)s, volume %(vol_id)s failed. '
'Backup object has unexpected mode. Image or file '
'backups supported, actual mode is %(vol_mode)s.')
% {'op': operation,
'bck_id': backup_id,
'vol_id': volume_id,
'vol_mode': backup_mode})
LOG.error(err)
raise exception.InvalidBackup(reason=err)
return backup_path, backup_mode
def _image_mode(backup_mode):
"""True if backup is image type."""
return backup_mode == 'image'
def _make_link(volume_path, backup_path, vol_id):
"""Create a hard link for the volume block device.
The IBM TSM client performs an image backup on a block device.
The name of the block device is the backup prefix plus the backup id
:param volume_path: real device path name for volume
:param backup_path: path name TSM will use as volume to backup
:param vol_id: id of volume to backup (for reporting)
:raises: InvalidBackup
"""
try:
cinder.privsep.path.symlink(volume_path, backup_path)
except processutils.ProcessExecutionError as exc:
err = (_('backup: %(vol_id)s failed to create device hardlink '
'from %(vpath)s to %(bpath)s.\n'
'stdout: %(out)s\n stderr: %(err)s')
% {'vol_id': vol_id,
'vpath': volume_path,
'bpath': backup_path,
'out': exc.stdout,
'err': exc.stderr})
LOG.error(err)
raise exception.InvalidBackup(reason=err)
def _create_unique_device_link(backup_id, volume_path, volume_id, bckup_mode):
"""Create a consistent hardlink for the volume block device.
Create a consistent hardlink using the backup id so TSM
will be able to backup and restore to the same block device.
:param backup_id: the backup id
:param volume_path: real path of the backup/restore device
:param volume_id: Volume id for backup or as restore target
:param bckup_mode: TSM backup mode, either 'image' or 'file'
:raises: InvalidBackup
:returns: str -- hardlink path of the volume block device
"""
if _image_mode(bckup_mode):
hardlink_path = utils.make_dev_path('%s-%s' %
(CONF.backup_tsm_volume_prefix,
backup_id))
else:
dir, volname = os.path.split(volume_path)
hardlink_path = ('%s/%s-%s' %
(dir,
CONF.backup_tsm_volume_prefix,
backup_id))
_make_link(volume_path, hardlink_path, volume_id)
return hardlink_path
def _check_dsmc_output(output, check_attrs, exact_match=True):
"""Check dsmc command line utility output.
Parse the output of the dsmc command and make sure that a given
attribute is present, and that it has the proper value.
TSM attribute has the format of "text : value".
:param output: TSM output to parse
:param check_attrs: text to identify in the output
:param exact_match: if True, the check will pass only if the parsed
value is equal to the value specified in check_attrs. If false, the
check will pass if the parsed value is greater than or equal to the
value specified in check_attrs. This is needed because for file
backups, the parent directories may also be included the first a
volume is backed up.
:returns: bool -- indicate if requited output attribute found in output
"""
parsed_attrs = {}
for line in output.split('\n'):
# parse TSM output: look for "msg : value
key, sep, val = line.partition(':')
if sep is not None and key is not None and len(val.strip()) > 0:
parsed_attrs[key] = val.strip()
for ckey, cval in check_attrs.items():
if ckey not in parsed_attrs:
return False
elif exact_match and parsed_attrs[ckey] != cval:
return False
elif not exact_match and int(parsed_attrs[ckey]) < int(cval):
return False
return True
def _get_volume_realpath(volume_file, volume_id):
"""Get the real path for the volume block device.
If the volume is not a block device or a regular file issue an
InvalidBackup exception.
:param volume_file: file object representing the volume
:param volume_id: Volume id for backup or as restore target
:raises: InvalidBackup
:returns: str -- real path of volume device
:returns: str -- backup mode to be used
"""
try:
# Get real path
volume_path = os.path.realpath(volume_file.name)
# Verify that path is a block device
volume_mode = os.stat(volume_path).st_mode
if stat.S_ISBLK(volume_mode):
backup_mode = 'image'
elif stat.S_ISREG(volume_mode):
backup_mode = 'file'
else:
err = (_('backup: %(vol_id)s failed. '
'%(path)s is unexpected file type. Block or regular '
'files supported, actual file mode is %(vol_mode)s.')
% {'vol_id': volume_id,
'path': volume_path,
'vol_mode': volume_mode})
LOG.error(err)
raise exception.InvalidBackup(reason=err)
except AttributeError:
err = (_('backup: %(vol_id)s failed. Cannot obtain real path '
'to volume at %(path)s.')
% {'vol_id': volume_id,
'path': volume_file})
LOG.error(err)
raise exception.InvalidBackup(reason=err)
except OSError:
err = (_('backup: %(vol_id)s failed. '
'%(path)s is not a file.')
% {'vol_id': volume_id,
'path': volume_path})
LOG.error(err)
raise exception.InvalidBackup(reason=err)
return volume_path, backup_mode
def _cleanup_device_hardlink(hardlink_path, volume_path, volume_id):
"""Remove the hardlink for the volume block device.
:param hardlink_path: hardlink to the volume block device
:param volume_path: real path of the backup/restore device
:param volume_id: Volume id for backup or as restore target
"""
try:
utils.execute('rm',
'-f',
hardlink_path,
run_as_root=True)
except processutils.ProcessExecutionError as exc:
LOG.error('backup: %(vol_id)s failed to remove backup hardlink '
'from %(vpath)s to %(bpath)s.\n'
'stdout: %(out)s\n stderr: %(err)s.',
{'vol_id': volume_id,
'vpath': volume_path,
'bpath': hardlink_path,
'out': exc.stdout,
'err': exc.stderr})
@interface.backupdriver
class TSMBackupDriver(driver.BackupDriver):
"""Provides backup, restore and delete of volumes backup for TSM."""
DRIVER_VERSION = '1.0.0'
def __init__(self, context, db=None):
super(TSMBackupDriver, self).__init__(context, db)
self.tsm_password = CONF.backup_tsm_password
self.volume_prefix = CONF.backup_tsm_volume_prefix
@staticmethod
def get_driver_options():
return tsm_opts
def check_for_setup_error(self):
versionutils.report_deprecated_feature(
LOG,
"Cinder TSM Backup Driver is deprecated and will be removed "
"in Wallaby release. Please, migrate you backups to a supported "
"backend.")
required_flags = ['backup_share']
for flag in required_flags:
val = getattr(CONF, flag, None)
if not val:
raise exception.InvalidConfigurationValue(option=flag,
value=val)
def _do_backup(self, backup_path, vol_id, backup_mode):
"""Perform the actual backup operation.
:param backup_path: volume path
:param vol_id: volume id
:param backup_mode: file mode of source volume; 'image' or 'file'
:raises: InvalidBackup
"""
backup_attrs = {'Total number of objects backed up': '1'}
compr_flag = 'yes' if CONF.backup_tsm_compression else 'no'
backup_cmd = ['dsmc', 'backup']
if _image_mode(backup_mode):
backup_cmd.append('image')
backup_cmd.extend(['-quiet',
'-compression=%s' % compr_flag,
'-password=%s' % self.tsm_password,
backup_path])
out, err = utils.execute(*backup_cmd,
run_as_root=True,
check_exit_code=False)
success = _check_dsmc_output(out, backup_attrs, exact_match=False)
if not success:
err = (_('backup: %(vol_id)s failed to obtain backup '
'success notification from server.\n'
'stdout: %(out)s\n stderr: %(err)s')
% {'vol_id': vol_id,
'out': out,
'err': err})
LOG.error(err)
raise exception.InvalidBackup(reason=err)
def _do_restore(self, backup_path, restore_path, vol_id, backup_mode):
"""Perform the actual restore operation.
:param backup_path: the path the backup was created from, this
identifies the backup to tsm
:param restore_path: volume path to restore into
:param vol_id: volume id
:param backup_mode: mode used to create the backup ('image' or 'file')
:raises: InvalidBackup
"""
restore_attrs = {'Total number of objects restored': '1'}
restore_cmd = ['dsmc', 'restore']
if _image_mode(backup_mode):
restore_cmd.append('image')
restore_cmd.append('-noprompt') # suppress prompt
else:
restore_cmd.append('-replace=yes') # suppress prompt
restore_cmd.extend(['-quiet',
'-password=%s' % self.tsm_password,
backup_path])
if restore_path != backup_path:
restore_cmd.append(restore_path)
out, err = utils.execute(*restore_cmd,
run_as_root=True,
check_exit_code=False)
success = _check_dsmc_output(out, restore_attrs)
if not success:
err = (_('restore: %(vol_id)s failed.\n'
'stdout: %(out)s\n stderr: %(err)s.')
% {'vol_id': vol_id,
'out': out,
'err': err})
LOG.error(err)
raise exception.InvalidBackup(reason=err)
def backup(self, backup, volume_file, backup_metadata=False):
"""Backup the given volume to TSM.
TSM performs a backup of a volume. The volume_file is used
to determine the path of the block device that TSM will back-up.
:param backup: backup information for volume
:param volume_file: file object representing the volume
:param backup_metadata: whether or not to backup volume metadata
:raises InvalidBackup:
"""
# TODO(dosaboy): this needs implementing (see backup.drivers.ceph for
# an example)
if backup_metadata:
msg = _("Volume metadata backup requested but this driver does "
"not yet support this feature.")
raise exception.InvalidBackup(reason=msg)
volume_path, backup_mode = _get_volume_realpath(volume_file,
backup.volume_id)
LOG.debug('Starting backup of volume: %(volume_id)s to TSM,'
' volume path: %(volume_path)s, mode: %(mode)s.',
{'volume_id': backup.volume_id,
'volume_path': volume_path,
'mode': backup_mode})
backup_path = _create_unique_device_link(backup.id,
volume_path,
backup.volume_id,
backup_mode)
service_metadata = {'backup_mode': backup_mode,
'backup_path': backup_path}
backup.service_metadata = json.dumps(service_metadata)
backup.save()
try:
self._do_backup(backup_path, backup.volume_id, backup_mode)
except processutils.ProcessExecutionError as exc:
err = (_('backup: %(vol_id)s failed to run dsmc '
'on %(bpath)s.\n'
'stdout: %(out)s\n stderr: %(err)s')
% {'vol_id': backup.volume_id,
'bpath': backup_path,
'out': exc.stdout,
'err': exc.stderr})
LOG.error(err)
raise exception.InvalidBackup(reason=err)
finally:
_cleanup_device_hardlink(backup_path, volume_path,
backup.volume_id)
LOG.debug('Backup %s finished.', backup.id)
def restore(self, backup, volume_id, volume_file):
"""Restore the given volume backup from TSM server.
:param backup: backup information for volume
:param volume_id: volume id
:param volume_file: file object representing the volume
:raises: InvalidBackup
"""
# backup_path is the path that was originally backed up.
backup_path, backup_mode = _get_backup_metadata(backup, 'restore')
LOG.debug('Starting restore of backup from TSM '
'to volume %(volume_id)s, '
'backup: %(backup_id)s, '
'mode: %(mode)s.',
{'volume_id': volume_id,
'backup_id': backup.id,
'mode': backup_mode})
# volume_path is the path to restore into. This may
# be different than the original volume.
volume_path, unused = _get_volume_realpath(volume_file,
volume_id)
restore_path = _create_unique_device_link(backup.id,
volume_path,
volume_id,
backup_mode)
try:
self._do_restore(backup_path, restore_path, volume_id, backup_mode)
except processutils.ProcessExecutionError as exc:
err = (_('restore: %(vol_id)s failed to run dsmc '
'on %(bpath)s.\n'
'stdout: %(out)s\n stderr: %(err)s')
% {'vol_id': volume_id,
'bpath': restore_path,
'out': exc.stdout,
'err': exc.stderr})
LOG.error(err)
raise exception.InvalidBackup(reason=err)
finally:
_cleanup_device_hardlink(restore_path, volume_path, volume_id)
LOG.debug('Restore %(backup_id)s to %(volume_id)s finished.',
{'backup_id': backup.id,
'volume_id': volume_id})
def delete_backup(self, backup):
"""Delete the given backup from TSM server.
:param backup: backup information for volume
:raises: InvalidBackup
"""
delete_attrs = {'Total number of objects deleted': '1'}
delete_path, backup_mode = _get_backup_metadata(backup, 'restore')
LOG.debug('Delete started for backup: %(backup)s, mode: %(mode)s.',
{'backup': backup.id,
'mode': backup_mode})
try:
out, err = utils.execute('dsmc',
'delete',
'backup',
'-quiet',
'-noprompt',
'-objtype=%s' % backup_mode,
'-password=%s' % self.tsm_password,
delete_path,
run_as_root=True,
check_exit_code=False)
except processutils.ProcessExecutionError as exc:
err = (_('delete: %(vol_id)s failed to run dsmc with '
'stdout: %(out)s\n stderr: %(err)s')
% {'vol_id': backup.volume_id,
'out': exc.stdout,
'err': exc.stderr})
LOG.error(err)
raise exception.InvalidBackup(reason=err)
success = _check_dsmc_output(out, delete_attrs)
if not success:
# log error if tsm cannot delete the backup object
# but do not raise exception so that cinder backup
# object can be removed.
LOG.error('delete: %(vol_id)s failed with '
'stdout: %(out)s\n stderr: %(err)s',
{'vol_id': backup.volume_id,
'out': out,
'err': err})
LOG.debug('Delete %s finished.', backup['id'])

View File

@ -39,7 +39,6 @@ from cinder.backup.drivers import glusterfs as cinder_backup_drivers_glusterfs
from cinder.backup.drivers import nfs as cinder_backup_drivers_nfs
from cinder.backup.drivers import posix as cinder_backup_drivers_posix
from cinder.backup.drivers import swift as cinder_backup_drivers_swift
from cinder.backup.drivers import tsm as cinder_backup_drivers_tsm
from cinder.backup import manager as cinder_backup_manager
from cinder.cmd import backup as cinder_cmd_backup
from cinder.cmd import volume as cinder_cmd_volume
@ -218,7 +217,6 @@ def list_opts():
cinder_backup_drivers_nfs.nfsbackup_service_opts,
cinder_backup_drivers_posix.posixbackup_service_opts,
cinder_backup_drivers_swift.swiftbackup_service_opts,
cinder_backup_drivers_tsm.tsm_opts,
cinder_backup_manager.backup_manager_opts,
cinder_cmd_backup.backup_cmd_opts,
[cinder_cmd_volume.cluster_opt],

View File

@ -1,354 +0,0 @@
# Copyright 2013 IBM Corp
# 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 volume backup to IBM Tivoli Storage Manager (TSM)."""
import json
import posix
from unittest import mock
from oslo_concurrency import processutils as putils
from oslo_utils import timeutils
from cinder.backup.drivers import tsm
from cinder import context
from cinder import db
from cinder import exception
from cinder import objects
from cinder.tests.unit import fake_constants as fake
from cinder.tests.unit import test
SIM = None
VOLUME_PATH = '/dev/null'
class TSMBackupSimulator(object):
"""Simulates TSM dsmc command.
The simulator simulates the execution of the 'dsmc' command.
This allows the TSM backup test to succeed even if TSM is not installed.
"""
def __init__(self):
self._backup_list = {}
self._hardlinks = []
self._next_cmd_error = {
'backup': '',
}
self._intro_msg = ('IBM Tivoli Storage Manager\n'
'Command Line Backup-Archive Client Interface\n'
'...\n\n')
def _cmd_backup(self, **kwargs):
# simulates the execution of the dsmc backup command
ret_msg = self._intro_msg
path = kwargs['path']
ret_msg += ('Image backup of volume \'%s\'\n\n'
'Total number of objects inspected: 1\n'
% path)
if self._next_cmd_error['backup'] == 'fail':
ret_msg += ('ANS1228E Sending of object \'%s\' '
'failed\n' % path)
ret_msg += ('ANS1063E The specified path is not a valid file '
'system or logical volume name.')
self._next_cmd_error['backup'] = ''
retcode = 12
else:
ret_msg += 'Total number of objects backed up: 1'
if path not in self._backup_list:
self._backup_list[path] = []
else:
self._backup_list[path][-1]['active'] = False
date = timeutils.utcnow()
datestr = date.strftime("%m/%d/%Y %H:%M:%S")
self._backup_list[path].append({'date': datestr, 'active': True})
retcode = 0
return (ret_msg, '', retcode)
def _backup_exists(self, path):
if path not in self._backup_list:
return ('ANS4000E Error processing \'%s\': file space does '
'not exist.' % path)
return 'OK'
def _cmd_restore(self, **kwargs):
ret_msg = self._intro_msg
path = kwargs['path']
exists = self._backup_exists(path)
if exists == 'OK':
ret_msg += ('Total number of objects restored: 1\n'
'Total number of objects failed: 0')
retcode = 0
else:
ret_msg += exists
retcode = 12
return (ret_msg, '', retcode)
def _cmd_delete(self, **kwargs):
# simulates the execution of the dsmc delete command
ret_msg = self._intro_msg
path = kwargs['path']
exists = self._backup_exists(path)
if exists == 'OK':
ret_msg += ('Total number of objects deleted: 1\n'
'Total number of objects failed: 0')
retcode = 0
index = len(self._backup_list[path]) - 1
del self._backup_list[path][index]
if not len(self._backup_list[path]):
del self._backup_list[path]
else:
ret_msg += exists
retcode = 12
return (ret_msg, '', retcode)
def _cmd_to_dict(self, arg_list):
"""Convert command for kwargs (assumes a properly formed command)."""
ret = {'cmd': arg_list[0],
'type': arg_list[1],
'path': arg_list[-1]}
for i in range(2, len(arg_list) - 1):
arg = arg_list[i].split('=')
if len(arg) == 1:
ret[arg[0]] = True
else:
ret[arg[0]] = arg[1]
return ret
def _exec_dsmc_cmd(self, cmd):
"""Simulates the execution of the dsmc command."""
cmd_switch = {'backup': self._cmd_backup,
'restore': self._cmd_restore,
'delete': self._cmd_delete}
kwargs = self._cmd_to_dict(cmd)
if kwargs['cmd'] != 'dsmc' or kwargs['type'] not in cmd_switch:
raise putils.ProcessExecutionError(exit_code=1,
stdout='',
stderr='Not dsmc command',
cmd=' '.join(cmd))
out, err, ret = cmd_switch[kwargs['type']](**kwargs)
return (out, err, ret)
def exec_cmd(self, cmd):
"""Simulates the execution of dsmc, rm, and ln commands."""
if cmd[0] == 'dsmc':
out, err, ret = self._exec_dsmc_cmd(cmd)
elif cmd[0] == 'ln':
dest = cmd[2]
out = ''
if dest in self._hardlinks:
err = ('ln: failed to create hard link `%s\': '
'File exists' % dest)
ret = 1
else:
self._hardlinks.append(dest)
err = ''
ret = 0
elif cmd[0] == 'rm':
dest = cmd[2]
out = ''
if dest not in self._hardlinks:
err = ('rm: cannot remove `%s\': No such file or '
'directory' % dest)
ret = 1
else:
index = self._hardlinks.index(dest)
del self._hardlinks[index]
err = ''
ret = 0
else:
raise putils.ProcessExecutionError(exit_code=1,
stdout='',
stderr='Unsupported command',
cmd=' '.join(cmd))
return (out, err, ret)
def error_injection(self, cmd, error):
self._next_cmd_error[cmd] = error
def fake_exec(*cmd, **kwargs):
# Support only bool
check_exit_code = kwargs.pop('check_exit_code', True)
global SIM
out, err, ret = SIM.exec_cmd(cmd)
if ret and check_exit_code:
raise putils.ProcessExecutionError(
exit_code=-1,
stdout=out,
stderr=err,
cmd=' '.join(cmd))
return (out, err)
def fake_stat_image(path):
# Simulate stat to return the mode of a block device
# make sure that st_mode (the first in the sequence(
# matches the mode of a block device
return posix.stat_result((25008, 5753, 5, 1, 0, 6, 0,
1375881199, 1375881197, 1375881197))
def fake_stat_file(path):
# Simulate stat to return the mode of a block device
# make sure that st_mode (the first in the sequence(
# matches the mode of a block device
return posix.stat_result((33188, 5753, 5, 1, 0, 6, 0,
1375881199, 1375881197, 1375881197))
def fake_stat_illegal(path):
# Simulate stat to return the mode of a block device
# make sure that st_mode (the first in the sequence(
# matches the mode of a block device
return posix.stat_result((17407, 5753, 5, 1, 0, 6, 0,
1375881199, 1375881197, 1375881197))
@mock.patch('cinder.utils.execute', fake_exec)
class BackupTSMTestCase(test.TestCase):
def setUp(self):
super(BackupTSMTestCase, self).setUp()
global SIM
SIM = TSMBackupSimulator()
self.sim = SIM
self.ctxt = context.get_admin_context()
self.driver = tsm.TSMBackupDriver(self.ctxt)
def _create_volume_db_entry(self, volume_id):
vol = {'id': volume_id,
'size': 1,
'status': 'available',
'volume_type_id': self.vt['id']}
return db.volume_create(self.ctxt, vol)['id']
def _create_backup_db_entry(self, backup_id, mode):
if mode == 'file':
backup_path = VOLUME_PATH
else:
backup_path = '/dev/backup-%s' % backup_id
service_metadata = json.dumps({'backup_mode': mode,
'backup_path': backup_path})
backup = {'id': backup_id,
'size': 1,
'container': 'test-container',
'volume_id': fake.VOLUME_ID,
'service_metadata': service_metadata,
'user_id': fake.USER_ID,
'project_id': fake.PROJECT_ID,
}
return db.backup_create(self.ctxt, backup)['id']
@mock.patch.object(tsm.os, 'stat', fake_stat_image)
@mock.patch('cinder.privsep.path.symlink')
def test_backup_image(self, mock_symlink):
volume_id = fake.VOLUME_ID
mode = 'image'
self._create_volume_db_entry(volume_id)
backup_id1 = fake.BACKUP_ID
backup_id2 = fake.BACKUP2_ID
backup_id3 = fake.BACKUP3_ID
self._create_backup_db_entry(backup_id1, mode)
self._create_backup_db_entry(backup_id2, mode)
self._create_backup_db_entry(backup_id3, mode)
with open(VOLUME_PATH, 'w+') as volume_file:
# Create two backups of the volume
backup1 = objects.Backup.get_by_id(self.ctxt, backup_id1)
self.driver.backup(backup1, volume_file)
backup2 = objects.Backup.get_by_id(self.ctxt, backup_id2)
self.driver.backup(backup2, volume_file)
# Create a backup that fails
fail_back = objects.Backup.get_by_id(self.ctxt, backup_id3)
self.sim.error_injection('backup', 'fail')
self.assertRaises(exception.InvalidBackup,
self.driver.backup, fail_back, volume_file)
# Try to restore one, then the other
self.driver.restore(backup1, volume_id, volume_file)
self.driver.restore(backup2, volume_id, volume_file)
# Delete both backups
self.driver.delete_backup(backup2)
self.driver.delete_backup(backup1)
@mock.patch.object(tsm.os, 'stat', fake_stat_file)
@mock.patch('cinder.privsep.path.symlink')
def test_backup_file(self, mock_symlink):
volume_id = fake.VOLUME_ID
mode = 'file'
self._create_volume_db_entry(volume_id)
self._create_backup_db_entry(fake.BACKUP_ID, mode)
self._create_backup_db_entry(fake.BACKUP2_ID, mode)
with open(VOLUME_PATH, 'w+') as volume_file:
# Create two backups of the volume
backup1 = objects.Backup.get_by_id(self.ctxt, fake.BACKUP_ID)
self.driver.backup(backup1, volume_file)
backup2 = objects.Backup.get_by_id(self.ctxt, fake.BACKUP2_ID)
self.driver.backup(backup2, volume_file)
# Create a backup that fails
self._create_backup_db_entry(fake.BACKUP3_ID, mode)
fail_back = objects.Backup.get_by_id(self.ctxt, fake.BACKUP3_ID)
self.sim.error_injection('backup', 'fail')
self.assertRaises(exception.InvalidBackup,
self.driver.backup, fail_back, volume_file)
# Try to restore one, then the other
self.driver.restore(backup1, volume_id, volume_file)
self.driver.restore(backup2, volume_id, volume_file)
# Delete both backups
self.driver.delete_backup(backup1)
self.driver.delete_backup(backup2)
@mock.patch.object(tsm.os, 'stat', fake_stat_illegal)
def test_backup_invalid_mode(self):
volume_id = fake.VOLUME_ID
mode = 'illegal'
self._create_volume_db_entry(volume_id)
self._create_backup_db_entry(fake.BACKUP_ID, mode)
with open(VOLUME_PATH, 'w+') as volume_file:
# Create two backups of the volume
backup1 = objects.Backup.get_by_id(self.ctxt, fake.BACKUP_ID)
self.assertRaises(exception.InvalidBackup,
self.driver.backup, backup1, volume_file)
self.assertRaises(exception.InvalidBackup,
self.driver.restore,
backup1,
volume_id,
volume_file)
self.assertRaises(exception.InvalidBackup,
self.driver.delete_backup, backup1)

View File

@ -13,7 +13,6 @@ Backup drivers
backup/posix-backup-driver.rst
backup/swift-backup-driver.rst
backup/gcs-backup-driver.rst
backup/tsm-backup-driver.rst
This section describes how to configure the cinder-backup service and
its drivers.

View File

@ -1,34 +0,0 @@
========================================
IBM Tivoli Storage Manager backup driver
========================================
The IBM Tivoli Storage Manager (TSM) backup driver enables performing
volume backups to a TSM server.
The TSM client should be installed and configured on the machine running
the cinder-backup service. See the IBM Tivoli Storage Manager
Backup-Archive Client Installation and User's Guide for details on
installing the TSM client.
To enable the IBM TSM backup driver, include the following option in
``cinder.conf``:
.. code-block:: ini
backup_driver = cinder.backup.drivers.tsm.TSMBackupDriver
The following configuration options are available for the TSM backup
driver.
.. config-table::
:config-target: IBM Tivoli Storage Manager backup driver
cinder.backup.drivers.tsm
This example shows the default options for the TSM backup driver.
.. code-block:: ini
backup_tsm_volume_prefix = backup
backup_tsm_password = password
backup_tsm_compression = True

View File

@ -128,9 +128,6 @@ mmdelfileset: CommandFilter, mmdelfileset, root
# cinder/volume/drivers/ibm/ibmnas.py
find_maxdepth_inum: RegExpFilter, find, root, find, ^[/]*([^/\0]+(/+)?)*$, -maxdepth, \d+, -ignore_readdir_race, -inum, \d+, -print0, -quit
#cinder/backup/services/tsm.py
dsmc:CommandFilter,/usr/bin/dsmc,root
# cinder/volume/drivers/vzstorage.py
pstorage-mount: CommandFilter, pstorage-mount, root
pstorage: CommandFilter, pstorage, root

View File

@ -0,0 +1,4 @@
---
upgrade:
- |
TSM backup driver is removed. Please, migrate your backups before upgrade.