From cddcc6e5073ec891b4d7aa4191fab56a05c74089 Mon Sep 17 00:00:00 2001 From: Ivan Kolodyazhny Date: Fri, 16 Oct 2020 17:02:40 +0300 Subject: [PATCH] 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 --- cinder/backup/drivers/tsm.py | 515 ------------------ cinder/opts.py | 2 - .../unit/backup/drivers/test_backup_tsm.py | 354 ------------ .../block-storage/backup-drivers.rst | 1 - .../backup/tsm-backup-driver.rst | 34 -- etc/cinder/rootwrap.d/volume.filters | 3 - ...te-tsm-backup-driver-725e33f7c213fd50.yaml | 4 + 7 files changed, 4 insertions(+), 909 deletions(-) delete mode 100644 cinder/backup/drivers/tsm.py delete mode 100644 cinder/tests/unit/backup/drivers/test_backup_tsm.py delete mode 100644 doc/source/configuration/block-storage/backup/tsm-backup-driver.rst create mode 100644 releasenotes/notes/delete-tsm-backup-driver-725e33f7c213fd50.yaml diff --git a/cinder/backup/drivers/tsm.py b/cinder/backup/drivers/tsm.py deleted file mode 100644 index d386c87e4db..00000000000 --- a/cinder/backup/drivers/tsm.py +++ /dev/null @@ -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']) diff --git a/cinder/opts.py b/cinder/opts.py index 7440452c8a6..05236b03c61 100644 --- a/cinder/opts.py +++ b/cinder/opts.py @@ -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], diff --git a/cinder/tests/unit/backup/drivers/test_backup_tsm.py b/cinder/tests/unit/backup/drivers/test_backup_tsm.py deleted file mode 100644 index c16edd79dd9..00000000000 --- a/cinder/tests/unit/backup/drivers/test_backup_tsm.py +++ /dev/null @@ -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) diff --git a/doc/source/configuration/block-storage/backup-drivers.rst b/doc/source/configuration/block-storage/backup-drivers.rst index 9f81efc5a1e..8f86e0e8f49 100644 --- a/doc/source/configuration/block-storage/backup-drivers.rst +++ b/doc/source/configuration/block-storage/backup-drivers.rst @@ -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. diff --git a/doc/source/configuration/block-storage/backup/tsm-backup-driver.rst b/doc/source/configuration/block-storage/backup/tsm-backup-driver.rst deleted file mode 100644 index 9f7631a4b17..00000000000 --- a/doc/source/configuration/block-storage/backup/tsm-backup-driver.rst +++ /dev/null @@ -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 diff --git a/etc/cinder/rootwrap.d/volume.filters b/etc/cinder/rootwrap.d/volume.filters index 7f6511b8a6f..69f1aa7465c 100644 --- a/etc/cinder/rootwrap.d/volume.filters +++ b/etc/cinder/rootwrap.d/volume.filters @@ -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 diff --git a/releasenotes/notes/delete-tsm-backup-driver-725e33f7c213fd50.yaml b/releasenotes/notes/delete-tsm-backup-driver-725e33f7c213fd50.yaml new file mode 100644 index 00000000000..88b73e2fd1c --- /dev/null +++ b/releasenotes/notes/delete-tsm-backup-driver-725e33f7c213fd50.yaml @@ -0,0 +1,4 @@ +--- +upgrade: + - | + TSM backup driver is removed. Please, migrate your backups before upgrade.