diff --git a/SOURCES/0001-Properly-detect-errors-when-writing-backup-files.-Cl.patch b/SOURCES/0001-Properly-detect-errors-when-writing-backup-files.-Cl.patch new file mode 100644 index 0000000..ecefe7e --- /dev/null +++ b/SOURCES/0001-Properly-detect-errors-when-writing-backup-files.-Cl.patch @@ -0,0 +1,72 @@ +From 8011ea6a741d494c145b4906f7a7865c8b74c6a7 Mon Sep 17 00:00:00 2001 +From: Christian Seiler +Date: Thu, 23 Mar 2017 19:36:18 +0100 +Subject: [PATCH] Properly detect errors when writing backup files. (Closes: + #80) (#81) + +* Properly detect errors when writing backup files. (Closes: #80) + +If the backup directory does not exist, properly detect that and show a +warning message to the user, so that they don't think that their +configuration was backed up, when it fact wasn't. + +Additionally, try to automatically create the backup directory if it +does not exist. + +Signed-off-by: Christian Seiler + +* Don't automatically create backup directory if it doesn't exist. + +After discussion on the issue tracker, it was decided to not +auto-create the backup-directory if it doesn't exist yet. + +If the directory doesn't exist, the user will now see the warning. + +Signed-off-by: Christian Seiler +--- + targetcli/ui_root.py | 28 ++++++++++++++++++---------- + 1 file changed, 18 insertions(+), 10 deletions(-) + +diff --git a/targetcli/ui_root.py b/targetcli/ui_root.py +index 2d8989c..c3a8483 100644 +--- a/targetcli/ui_root.py ++++ b/targetcli/ui_root.py +@@ -75,17 +75,25 @@ class UIRoot(UINode): + backup_name = "saveconfig-" + \ + datetime.now().strftime("%Y%m%d-%H:%M:%S") + ".json" + backupfile = backup_dir + "/" + backup_name +- with ignored(IOError): ++ backup_error = None ++ try: + shutil.copy(savefile, backupfile) +- +- # Kill excess backups +- backups = sorted(glob(os.path.dirname(savefile) + "/backup/*.json")) +- files_to_unlink = list(reversed(backups))[kept_backups:] +- for f in files_to_unlink: +- os.unlink(f) +- +- self.shell.log.info("Last %d configs saved in %s." % \ +- (kept_backups, backup_dir)) ++ except IOError as ioe: ++ backup_error = ioe.strerror or "Unknown error" ++ ++ if backup_error == None: ++ # Kill excess backups ++ backups = sorted(glob(os.path.dirname(savefile) + "/backup/*.json")) ++ files_to_unlink = list(reversed(backups))[kept_backups:] ++ for f in files_to_unlink: ++ with ignored(IOError): ++ os.unlink(f) ++ ++ self.shell.log.info("Last %d configs saved in %s." % \ ++ (kept_backups, backup_dir)) ++ else: ++ self.shell.log.warning("Could not create backup file %s: %s." % \ ++ (backupfile, backup_error)) + + self.rtsroot.save_to_file(savefile) + +-- +1.8.3.1 + diff --git a/SOURCES/0002-Read-number-of-backup-files-to-keep-from-file.patch b/SOURCES/0002-Read-number-of-backup-files-to-keep-from-file.patch new file mode 100644 index 0000000..6c591e3 --- /dev/null +++ b/SOURCES/0002-Read-number-of-backup-files-to-keep-from-file.patch @@ -0,0 +1,52 @@ +From bf0fb387cef89bb0dc51cf1740ab73abf549c38e Mon Sep 17 00:00:00 2001 +From: Taylor Jakobson +Date: Mon, 23 Oct 2017 14:26:03 -0500 +Subject: [PATCH] Read number of backup files to keep from file + +Use /etc/target/targetcli.conf file to store universal configurations +that are the same for all users. +Configuration file has format of: + key = value +--- + targetcli/ui_root.py | 11 ++++++++++- + 1 file changed, 10 insertions(+), 1 deletion(-) + +diff --git a/targetcli/ui_root.py b/targetcli/ui_root.py +index c3a8483..8bc8521 100644 +--- a/targetcli/ui_root.py ++++ b/targetcli/ui_root.py +@@ -20,6 +20,7 @@ under the License. + from datetime import datetime + from glob import glob + import os ++import re + import shutil + import stat + +@@ -32,7 +33,8 @@ from .ui_node import UINode + from .ui_target import UIFabricModule + + default_save_file = "/etc/target/saveconfig.json" +-kept_backups = 10 ++universal_prefs_file = "/etc/target/targetcli.conf" ++default_kept_backups = 10 + + class UIRoot(UINode): + ''' +@@ -83,6 +85,13 @@ class UIRoot(UINode): + + if backup_error == None: + # Kill excess backups ++ try: ++ with open(universal_prefs_file) as prefs: ++ backups = [line for line in prefs.read().splitlines() if re.match('^kept_backups\s*=', line)] ++ kept_backups = int(backups[0].split('=')[1].strip()) ++ except: ++ kept_backups = default_kept_backups ++ + backups = sorted(glob(os.path.dirname(savefile) + "/backup/*.json")) + files_to_unlink = list(reversed(backups))[kept_backups:] + for f in files_to_unlink: +-- +1.8.3.1 + diff --git a/SOURCES/0003-config-defend-on-etc-target-backup-directory.patch b/SOURCES/0003-config-defend-on-etc-target-backup-directory.patch new file mode 100644 index 0000000..837f9af --- /dev/null +++ b/SOURCES/0003-config-defend-on-etc-target-backup-directory.patch @@ -0,0 +1,99 @@ +From 6349a75bd71f2f15c3acd89588321524c94676e8 Mon Sep 17 00:00:00 2001 +From: Prasanna Kumar Kalever +Date: Fri, 15 Dec 2017 15:16:28 +0530 +Subject: [PATCH] config: defend on '/etc/target/backup' directory + +Currently we do not create '/etc/target/backup/' while we expect the directory +to be presented by package (rpm). + +If for some reason '/etc/target/backup' is not available, say may be a +unintentional deletion or use of targetcli which is compiled from source, +we see below errors on up on saveconfig command, + +Case 1: No '/etc/target/backup/' dir + +$ targetcli / saveconfig +Could not create backup file /etc/target/backup/saveconfig-20171215-15:26:48.json: No such file or directory. +Configuration saved to /etc/target/saveconfig.json + +Case 2: No '/etc/target/' dir + +$ targetcli / saveconfig +Could not create backup file /etc/target/backup/saveconfig-20171215-15:27:42.json: No such file or directory. +[Errno 2] No such file or directory: '/etc/target/saveconfig.json.temp' + +This patch tries to create '/etc/target/backup' directory tree in case if it +is absent. + +Signed-off-by: Prasanna Kumar Kalever +--- + targetcli/ui_root.py | 50 +++++++++++++++++++++++++++++--------------------- + 1 file changed, 29 insertions(+), 21 deletions(-) + +diff --git a/targetcli/ui_root.py b/targetcli/ui_root.py +index 8bc8521..33bb948 100644 +--- a/targetcli/ui_root.py ++++ b/targetcli/ui_root.py +@@ -78,30 +78,38 @@ class UIRoot(UINode): + datetime.now().strftime("%Y%m%d-%H:%M:%S") + ".json" + backupfile = backup_dir + "/" + backup_name + backup_error = None +- try: +- shutil.copy(savefile, backupfile) +- except IOError as ioe: +- backup_error = ioe.strerror or "Unknown error" + +- if backup_error == None: +- # Kill excess backups ++ if not os.path.exists(backup_dir): ++ try: ++ os.makedirs(backup_dir); ++ except OSError as exe: ++ raise ExecutionError("Cannot create backup directory [%s] %s." % (backup_dir, exc.strerror)) ++ ++ if os.path.exists(savefile): + try: +- with open(universal_prefs_file) as prefs: +- backups = [line for line in prefs.read().splitlines() if re.match('^kept_backups\s*=', line)] +- kept_backups = int(backups[0].split('=')[1].strip()) +- except: +- kept_backups = default_kept_backups +- +- backups = sorted(glob(os.path.dirname(savefile) + "/backup/*.json")) +- files_to_unlink = list(reversed(backups))[kept_backups:] +- for f in files_to_unlink: +- with ignored(IOError): +- os.unlink(f) +- +- self.shell.log.info("Last %d configs saved in %s." % \ ++ shutil.copy(savefile, backupfile) ++ except IOError as ioe: ++ backup_error = ioe.strerror or "Unknown error" ++ ++ if backup_error == None: ++ # Kill excess backups ++ try: ++ with open(universal_prefs_file) as prefs: ++ backups = [line for line in prefs.read().splitlines() if re.match('^kept_backups\s*=', line)] ++ kept_backups = int(backups[0].split('=')[1].strip()) ++ except: ++ kept_backups = default_kept_backups ++ ++ backups = sorted(glob(os.path.dirname(savefile) + "/backup/*.json")) ++ files_to_unlink = list(reversed(backups))[kept_backups:] ++ for f in files_to_unlink: ++ with ignored(IOError): ++ os.unlink(f) ++ ++ self.shell.log.info("Last %d configs saved in %s." % \ + (kept_backups, backup_dir)) +- else: +- self.shell.log.warning("Could not create backup file %s: %s." % \ ++ else: ++ self.shell.log.warning("Could not create backup file %s: %s." % \ + (backupfile, backup_error)) + + self.rtsroot.save_to_file(savefile) +-- +1.8.3.1 + diff --git a/SOURCES/0004-config-backup-when-current-config-is-different-from-.patch b/SOURCES/0004-config-backup-when-current-config-is-different-from-.patch new file mode 100644 index 0000000..69ed6cc --- /dev/null +++ b/SOURCES/0004-config-backup-when-current-config-is-different-from-.patch @@ -0,0 +1,91 @@ +From 6efbd7e4c0217d1f6b46e22ec51a209439678f5a Mon Sep 17 00:00:00 2001 +From: Prasanna Kumar Kalever +Date: Mon, 18 Dec 2017 15:25:42 +0530 +Subject: [PATCH] config: backup when current config is different from recent + backup copy + +With this change, +when '/etc/target/saveconfig.json' is same as '/etc/target/backup/saveconfig-${latest-stamp}.json +we skip backing up saveconfig.json + +Signed-off-by: Prasanna Kumar Kalever +--- + targetcli/ui_root.py | 53 ++++++++++++++++++++++++++++------------------------ + 1 file changed, 29 insertions(+), 24 deletions(-) + +diff --git a/targetcli/ui_root.py b/targetcli/ui_root.py +index 33bb948..f398395 100644 +--- a/targetcli/ui_root.py ++++ b/targetcli/ui_root.py +@@ -23,6 +23,7 @@ import os + import re + import shutil + import stat ++import filecmp + + from configshell_fb import ExecutionError + from rtslib_fb import RTSRoot +@@ -85,32 +86,36 @@ class UIRoot(UINode): + except OSError as exe: + raise ExecutionError("Cannot create backup directory [%s] %s." % (backup_dir, exc.strerror)) + ++ # Only save backups if savefile exits + if os.path.exists(savefile): +- try: +- shutil.copy(savefile, backupfile) +- except IOError as ioe: +- backup_error = ioe.strerror or "Unknown error" +- +- if backup_error == None: +- # Kill excess backups ++ backed_files_list = sorted(glob(os.path.dirname(savefile) + "/backup/*.json")) ++ # Save backup if 1. backup dir is empty, or 2. savefile is differnt from recent backup copy ++ if not backed_files_list or not filecmp.cmp(backed_files_list[-1], savefile): + try: +- with open(universal_prefs_file) as prefs: +- backups = [line for line in prefs.read().splitlines() if re.match('^kept_backups\s*=', line)] +- kept_backups = int(backups[0].split('=')[1].strip()) +- except: +- kept_backups = default_kept_backups +- +- backups = sorted(glob(os.path.dirname(savefile) + "/backup/*.json")) +- files_to_unlink = list(reversed(backups))[kept_backups:] +- for f in files_to_unlink: +- with ignored(IOError): +- os.unlink(f) +- +- self.shell.log.info("Last %d configs saved in %s." % \ +- (kept_backups, backup_dir)) +- else: +- self.shell.log.warning("Could not create backup file %s: %s." % \ +- (backupfile, backup_error)) ++ shutil.copy(savefile, backupfile) ++ ++ except IOError as ioe: ++ backup_error = ioe.strerror or "Unknown error" ++ ++ if backup_error == None: ++ # Kill excess backups ++ try: ++ with open(universal_prefs_file) as prefs: ++ backups = [line for line in prefs.read().splitlines() if re.match('^kept_backups\s*=', line)] ++ kept_backups = int(backups[0].split('=')[1].strip()) ++ except: ++ kept_backups = default_kept_backups ++ ++ files_to_unlink = list(reversed(backed_files_list))[kept_backups:] ++ for f in files_to_unlink: ++ with ignored(IOError): ++ os.unlink(f) ++ ++ self.shell.log.info("Last %d configs saved in %s." % \ ++ (kept_backups, backup_dir)) ++ else: ++ self.shell.log.warning("Could not create backup file %s: %s." % \ ++ (backupfile, backup_error)) + + self.rtsroot.save_to_file(savefile) + +-- +1.8.3.1 + diff --git a/SOURCES/0005-config-rename-key-kept_backups-as-max_backup_files.patch b/SOURCES/0005-config-rename-key-kept_backups-as-max_backup_files.patch new file mode 100644 index 0000000..732b1c0 --- /dev/null +++ b/SOURCES/0005-config-rename-key-kept_backups-as-max_backup_files.patch @@ -0,0 +1,48 @@ +From bd3b2b50d741848a748830d9a1a7ee642e8d40d3 Mon Sep 17 00:00:00 2001 +From: Prasanna Kumar Kalever +Date: Tue, 19 Dec 2017 14:32:41 +0530 +Subject: [PATCH] config: rename key 'kept_backups' as 'max_backup_files' + +$ cat /etc/target/targetcli.conf +max_backup_files = 25 + +$ targetcli / saveconfig +Last 25 configs saved in /etc/target/backup. +Configuration saved to /etc/target/saveconfig.json + +Signed-off-by: Prasanna Kumar Kalever +--- + targetcli/ui_root.py | 10 +++++----- + 1 file changed, 5 insertions(+), 5 deletions(-) + +diff --git a/targetcli/ui_root.py b/targetcli/ui_root.py +index f398395..f84d33d 100644 +--- a/targetcli/ui_root.py ++++ b/targetcli/ui_root.py +@@ -101,18 +101,18 @@ class UIRoot(UINode): + # Kill excess backups + try: + with open(universal_prefs_file) as prefs: +- backups = [line for line in prefs.read().splitlines() if re.match('^kept_backups\s*=', line)] +- kept_backups = int(backups[0].split('=')[1].strip()) ++ backups = [line for line in prefs.read().splitlines() if re.match('^max_backup_files\s*=', line)] ++ max_backup_files = int(backups[0].split('=')[1].strip()) + except: +- kept_backups = default_kept_backups ++ max_backup_files = default_kept_backups + +- files_to_unlink = list(reversed(backed_files_list))[kept_backups:] ++ files_to_unlink = list(reversed(backed_files_list))[max_backup_files:] + for f in files_to_unlink: + with ignored(IOError): + os.unlink(f) + + self.shell.log.info("Last %d configs saved in %s." % \ +- (kept_backups, backup_dir)) ++ (max_backup_files, backup_dir)) + else: + self.shell.log.warning("Could not create backup file %s: %s." % \ + (backupfile, backup_error)) +-- +1.8.3.1 + diff --git a/SOURCES/0006-backup-global-option-to-tune-max-no.-of-backup-conf-.patch b/SOURCES/0006-backup-global-option-to-tune-max-no.-of-backup-conf-.patch new file mode 100644 index 0000000..3493991 --- /dev/null +++ b/SOURCES/0006-backup-global-option-to-tune-max-no.-of-backup-conf-.patch @@ -0,0 +1,78 @@ +From bf75323d7d678f01c21d0c9825bd55553727e934 Mon Sep 17 00:00:00 2001 +From: Prasanna Kumar Kalever +Date: Fri, 15 Dec 2017 17:23:25 +0530 +Subject: [PATCH] backup: global option to tune max no. of backup conf files + +Eg: +/> set global max_backup_files=1000 +Parameter max_backup_files is now '1000'. + +If 'max_backup_files' is set in /etc/target/targetcli.conf, then max value +between conf file setting and global option settings is considered. + +Signed-off-by: Prasanna Kumar Kalever +--- + scripts/targetcli | 1 + + targetcli/ui_node.py | 3 +++ + targetcli/ui_root.py | 8 +++++--- + 3 files changed, 9 insertions(+), 3 deletions(-) + +diff --git a/scripts/targetcli b/scripts/targetcli +index 5404fcd..371edbe 100755 +--- a/scripts/targetcli ++++ b/scripts/targetcli +@@ -49,6 +49,7 @@ class TargetCLI(ConfigShell): + 'auto_add_mapped_luns': True, + 'auto_cd_after_create': False, + 'auto_save_on_exit': True, ++ 'max_backup_files': '10', + 'auto_add_default_portal': True, + } + +diff --git a/targetcli/ui_node.py b/targetcli/ui_node.py +index c91dae2..a6982f1 100644 +--- a/targetcli/ui_node.py ++++ b/targetcli/ui_node.py +@@ -46,6 +46,9 @@ class UINode(ConfigNode): + self.define_config_group_param( + 'global', 'auto_add_default_portal', 'bool', + 'If true, adds a portal listening on all IPs to new targets.') ++ self.define_config_group_param( ++ 'global', 'max_backup_files', 'string', ++ 'Max no. of configurations to be backed up in /etc/target/backup/ directory.') + + def assert_root(self): + ''' +diff --git a/targetcli/ui_root.py b/targetcli/ui_root.py +index f84d33d..a54845f 100644 +--- a/targetcli/ui_root.py ++++ b/targetcli/ui_root.py +@@ -35,7 +35,6 @@ from .ui_target import UIFabricModule + + default_save_file = "/etc/target/saveconfig.json" + universal_prefs_file = "/etc/target/targetcli.conf" +-default_kept_backups = 10 + + class UIRoot(UINode): + ''' +@@ -99,12 +98,15 @@ class UIRoot(UINode): + + if backup_error == None: + # Kill excess backups ++ max_backup_files = int(self.shell.prefs['max_backup_files']) ++ + try: + with open(universal_prefs_file) as prefs: + backups = [line for line in prefs.read().splitlines() if re.match('^max_backup_files\s*=', line)] +- max_backup_files = int(backups[0].split('=')[1].strip()) ++ if max_backup_files < int(backups[0].split('=')[1].strip()): ++ max_backup_files = int(backups[0].split('=')[1].strip()) + except: +- max_backup_files = default_kept_backups ++ self.shell.log.debug("No universal prefs file '%s'." % universal_prefs_file) + + files_to_unlink = list(reversed(backed_files_list))[max_backup_files:] + for f in files_to_unlink: +-- +1.8.3.1 + diff --git a/SOURCES/0007-Fix-default-max_backup_files-in-ui_command_saveconfi.patch b/SOURCES/0007-Fix-default-max_backup_files-in-ui_command_saveconfi.patch new file mode 100644 index 0000000..aa9a488 --- /dev/null +++ b/SOURCES/0007-Fix-default-max_backup_files-in-ui_command_saveconfi.patch @@ -0,0 +1,26 @@ +From cac1f18c54c87839b4eaa812cd438dc4593c7975 Mon Sep 17 00:00:00 2001 +From: tangwenji +Date: Thu, 15 Mar 2018 14:52:20 +0800 +Subject: [PATCH] Fix default max_backup_files in ui_command_saveconfig + +Signed-off-by: tangwenji +--- + targetcli/ui_root.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/targetcli/ui_root.py b/targetcli/ui_root.py +index a54845f..54d38e3 100644 +--- a/targetcli/ui_root.py ++++ b/targetcli/ui_root.py +@@ -98,7 +98,7 @@ class UIRoot(UINode): + + if backup_error == None: + # Kill excess backups +- max_backup_files = int(self.shell.prefs['max_backup_files']) ++ max_backup_files = int(self.shell.default_prefs['max_backup_files']) + + try: + with open(universal_prefs_file) as prefs: +-- +1.8.3.1 + diff --git a/SOURCES/0008-config-add-saveconfig-command-to-StorageObject-level.patch b/SOURCES/0008-config-add-saveconfig-command-to-StorageObject-level.patch new file mode 100644 index 0000000..b018d94 --- /dev/null +++ b/SOURCES/0008-config-add-saveconfig-command-to-StorageObject-level.patch @@ -0,0 +1,196 @@ +From 9dc85912f6ff2ba77fa8b136478ae3d2776c2b5e Mon Sep 17 00:00:00 2001 +From: Prasanna Kumar Kalever +Date: Sat, 24 Feb 2018 08:38:35 +0530 +Subject: [PATCH] config: add saveconfig command to StorageObject level + +$ targetcli /backstores/user:glfs/block1 help +[...] +AVAILABLE COMMANDS +[...] + - saveconfig [savefile] + - set [group] [parameter=value...] + - status + - version + +$ targetcli /backstores/user:glfs/block1 saveconfig +Storage Object 'user:block1' config saved to /etc/target/saveconfig.json + +Signed-off-by: Prasanna Kumar Kalever +--- + targetcli/ui_backstore.py | 22 ++++++++++ + targetcli/ui_root.py | 110 ++++++++++++++++++++++++++-------------------- + 2 files changed, 85 insertions(+), 47 deletions(-) + +diff --git a/targetcli/ui_backstore.py b/targetcli/ui_backstore.py +index 5af448f..efa532f 100644 +--- a/targetcli/ui_backstore.py ++++ b/targetcli/ui_backstore.py +@@ -36,6 +36,8 @@ from rtslib_fb.utils import get_block_type + + from .ui_node import UINode, UIRTSLibNode + ++default_save_file = "/etc/target/saveconfig.json" ++ + alua_rw_params = ['alua_access_state', 'alua_access_status', + 'alua_write_metadata', 'alua_access_type', 'preferred', + 'nonop_delay_msecs', 'trans_delay_msecs', +@@ -702,6 +704,26 @@ class UIStorageObject(UIRTSLibNode): + self.shell.con.display("Backstore plugin %s %s" + % (self.rtsnode.plugin, self.rtsnode.version)) + ++ def ui_command_saveconfig(self, savefile=None): ++ ''' ++ Save configuration of this StorageObject. ++ ''' ++ so = self.rtsnode ++ rn = self.get_root() ++ ++ if not savefile: ++ savefile = default_save_file ++ ++ savefile = os.path.expanduser(savefile) ++ ++ rn._save_backups(savefile) ++ ++ rn.rtsroot.save_to_file(savefile, ++ '/backstores/' + so.plugin + '/' + so.name) ++ ++ self.shell.log.info("Storage Object '%s:%s' config saved to %s." ++ % (so.plugin, so.name, savefile)) ++ + + class UIPSCSIStorageObject(UIStorageObject): + def summary(self): +diff --git a/targetcli/ui_root.py b/targetcli/ui_root.py +index 54d38e3..09f3f1b 100644 +--- a/targetcli/ui_root.py ++++ b/targetcli/ui_root.py +@@ -62,6 +62,65 @@ class UIRoot(UINode): + if fm.wwns == None or any(fm.wwns): + UIFabricModule(fm, self) + ++ def _save_backups(self, savefile): ++ ''' ++ Take backup of config-file if needed. ++ ''' ++ # Only save backups if saving to default location ++ if savefile != default_save_file: ++ return ++ ++ backup_dir = os.path.dirname(savefile) + "/backup/" ++ backup_name = "saveconfig-" + \ ++ datetime.now().strftime("%Y%m%d-%H:%M:%S") + ".json" ++ backupfile = backup_dir + backup_name ++ backup_error = None ++ ++ if not os.path.exists(backup_dir): ++ try: ++ os.makedirs(backup_dir); ++ except OSError as exe: ++ raise ExecutionError("Cannot create backup directory [%s] %s." ++ % (backup_dir, exc.strerror)) ++ ++ # Only save backups if savefile exits ++ if not os.path.exists(savefile): ++ return ++ ++ backed_files_list = sorted(glob(os.path.dirname(savefile) + \ ++ "/backup/*.json")) ++ ++ # Save backup if backup dir is empty, or savefile is differnt from recent backup copy ++ if not backed_files_list or not filecmp.cmp(backed_files_list[-1], savefile): ++ try: ++ shutil.copy(savefile, backupfile) ++ ++ except IOError as ioe: ++ backup_error = ioe.strerror or "Unknown error" ++ ++ if backup_error == None: ++ # remove excess backups ++ max_backup_files = int(self.shell.prefs['max_backup_files']) ++ ++ try: ++ with open(universal_prefs_file) as prefs: ++ backups = [line for line in prefs.read().splitlines() if re.match('^max_backup_files\s*=', line)] ++ if max_backup_files < int(backups[0].split('=')[1].strip()): ++ max_backup_files = int(backups[0].split('=')[1].strip()) ++ except: ++ self.shell.log.debug("No universal prefs file '%s'." % universal_prefs_file) ++ ++ files_to_unlink = list(reversed(backed_files_list))[max_backup_files:] ++ for f in files_to_unlink: ++ with ignored(IOError): ++ os.unlink(f) ++ ++ self.shell.log.info("Last %d configs saved in %s." ++ % (max_backup_files, backup_dir)) ++ else: ++ self.shell.log.warning("Could not create backup file %s: %s." ++ % (backupfile, backup_error)) ++ + def ui_command_saveconfig(self, savefile=default_save_file): + ''' + Saves the current configuration to a file so that it can be restored +@@ -69,55 +128,12 @@ class UIRoot(UINode): + ''' + self.assert_root() + ++ if not savefile: ++ savefile = default_save_file ++ + savefile = os.path.expanduser(savefile) + +- # Only save backups if saving to default location +- if savefile == default_save_file: +- backup_dir = os.path.dirname(savefile) + "/backup" +- backup_name = "saveconfig-" + \ +- datetime.now().strftime("%Y%m%d-%H:%M:%S") + ".json" +- backupfile = backup_dir + "/" + backup_name +- backup_error = None +- +- if not os.path.exists(backup_dir): +- try: +- os.makedirs(backup_dir); +- except OSError as exe: +- raise ExecutionError("Cannot create backup directory [%s] %s." % (backup_dir, exc.strerror)) +- +- # Only save backups if savefile exits +- if os.path.exists(savefile): +- backed_files_list = sorted(glob(os.path.dirname(savefile) + "/backup/*.json")) +- # Save backup if 1. backup dir is empty, or 2. savefile is differnt from recent backup copy +- if not backed_files_list or not filecmp.cmp(backed_files_list[-1], savefile): +- try: +- shutil.copy(savefile, backupfile) +- +- except IOError as ioe: +- backup_error = ioe.strerror or "Unknown error" +- +- if backup_error == None: +- # Kill excess backups +- max_backup_files = int(self.shell.default_prefs['max_backup_files']) +- +- try: +- with open(universal_prefs_file) as prefs: +- backups = [line for line in prefs.read().splitlines() if re.match('^max_backup_files\s*=', line)] +- if max_backup_files < int(backups[0].split('=')[1].strip()): +- max_backup_files = int(backups[0].split('=')[1].strip()) +- except: +- self.shell.log.debug("No universal prefs file '%s'." % universal_prefs_file) +- +- files_to_unlink = list(reversed(backed_files_list))[max_backup_files:] +- for f in files_to_unlink: +- with ignored(IOError): +- os.unlink(f) +- +- self.shell.log.info("Last %d configs saved in %s." % \ +- (max_backup_files, backup_dir)) +- else: +- self.shell.log.warning("Could not create backup file %s: %s." % \ +- (backupfile, backup_error)) ++ self._save_backups(savefile) + + self.rtsroot.save_to_file(savefile) + +-- +1.8.3.1 + diff --git a/SOURCES/0009-Support-tcmu-hw-max-sectors.patch b/SOURCES/0009-Support-tcmu-hw-max-sectors.patch new file mode 100644 index 0000000..4137170 --- /dev/null +++ b/SOURCES/0009-Support-tcmu-hw-max-sectors.patch @@ -0,0 +1,43 @@ +From 2f3183bf56295a764e743d0601275c7aa5a815db Mon Sep 17 00:00:00 2001 +From: Mike Christie +Date: Wed, 29 Mar 2017 15:01:05 -0500 +Subject: [PATCH] Support tcmu hw max sectors + +Support hw_max_sectors setting for tcmu added in this patch: + +commit 3abaa2bfdb1e6bb33d38a2e82cf3bb82ec0197bf +Author: Mike Christie +Date: Wed Mar 1 23:14:39 2017 -0600 + + tcmu: allow hw_max_sectors greater than 128 +--- + targetcli/ui_backstore.py | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/targetcli/ui_backstore.py b/targetcli/ui_backstore.py +index 4c577a4..88240bb 100644 +--- a/targetcli/ui_backstore.py ++++ b/targetcli/ui_backstore.py +@@ -574,7 +574,8 @@ class UIUserBackedBackstore(UIBackstore): + print(x.get("ConfigDesc", "No description.")) + print() + +- def ui_command_create(self, name, size, cfgstring, wwn=None): ++ def ui_command_create(self, name, size, cfgstring, wwn=None, ++ hw_max_sectors=None): + ''' + Creates a User-backed storage object. + +@@ -598,7 +599,8 @@ class UIUserBackedBackstore(UIBackstore): + if not ok: + raise ExecutionError("cfgstring invalid: %s" % errmsg) + +- so = UserBackedStorageObject(name, size=size, config=config, wwn=wwn) ++ so = UserBackedStorageObject(name, size=size, config=config, wwn=wwn, ++ hw_max_sectors=hw_max_sectors) + ui_so = UIUserBackedStorageObject(so, self) + self.shell.log.info("Created user-backed storage object %s size %d." + % (name, size)) +-- +1.8.3.1 + diff --git a/SOURCES/0010-create-add-a-way-to-set-control-string.patch b/SOURCES/0010-create-add-a-way-to-set-control-string.patch new file mode 100644 index 0000000..55bae36 --- /dev/null +++ b/SOURCES/0010-create-add-a-way-to-set-control-string.patch @@ -0,0 +1,30 @@ +diff --git a/targetcli/ui_backstore.py b/targetcli/ui_backstore.py +index 3838d02..b60eba5 100644 +--- a/targetcli/ui_backstore.py ++++ b/targetcli/ui_backstore.py +@@ -577,7 +577,7 @@ class UIUserBackedBackstore(UIBackstore): + print() + + def ui_command_create(self, name, size, cfgstring, wwn=None, +- hw_max_sectors=None): ++ hw_max_sectors=None, control=None): + ''' + Creates a User-backed storage object. + +@@ -602,7 +602,7 @@ class UIUserBackedBackstore(UIBackstore): + raise ExecutionError("cfgstring invalid: %s" % errmsg) + + so = UserBackedStorageObject(name, size=size, config=config, wwn=wwn, +- hw_max_sectors=hw_max_sectors) ++ hw_max_sectors=hw_max_sectors, control=control) + ui_so = UIUserBackedStorageObject(so, self) + self.shell.log.info("Created user-backed storage object %s size %d." + % (name, size)) +@@ -633,6 +633,7 @@ class UIStorageObject(UIRTSLibNode): + 'fabric_max_sectors': ('number', 'Maximum number of sectors the fabric can transfer at once.'), + 'hw_block_size': ('number', 'Hardware block size in bytes.'), + 'hw_max_sectors': ('number', 'Maximum number of sectors the hardware can transfer at once.'), ++ 'control': ('string', 'Comma separated string of control=value tuples that will be passed to kernel control file.'), + 'hw_pi_prot_type': ('number', 'If non-zero, DIF protection is enabled on the underlying hardware.'), + 'hw_queue_depth': ('number', 'Hardware queue depth.'), + 'is_nonrot': ('number', 'If set to 1, the backstore is a non rotational device.'), diff --git a/SPECS/targetcli.spec b/SPECS/targetcli.spec index 5ec751c..04521c4 100644 --- a/SPECS/targetcli.spec +++ b/SPECS/targetcli.spec @@ -5,9 +5,19 @@ License: ASL 2.0 Group: System Environment/Libraries Summary: An administration shell for storage targets Version: 2.1.fb46 -Release: 1%{?dist} +Release: 4%{?dist} URL: https://fedorahosted.org/targetcli-fb/ Source: https://fedorahosted.org/released/targetcli-fb/%{oname}-%{version}.tar.gz +Patch0: 0001-Properly-detect-errors-when-writing-backup-files.-Cl.patch +Patch1: 0002-Read-number-of-backup-files-to-keep-from-file.patch +Patch2: 0003-config-defend-on-etc-target-backup-directory.patch +Patch3: 0004-config-backup-when-current-config-is-different-from-.patch +Patch4: 0005-config-rename-key-kept_backups-as-max_backup_files.patch +Patch5: 0006-backup-global-option-to-tune-max-no.-of-backup-conf-.patch +Patch6: 0007-Fix-default-max_backup_files-in-ui_command_saveconfi.patch +Patch7: 0008-config-add-saveconfig-command-to-StorageObject-level.patch +Patch8: 0009-Support-tcmu-hw-max-sectors.patch +Patch9: 0010-create-add-a-way-to-set-control-string.patch BuildArch: noarch BuildRequires: python-devel python-setuptools Requires: python-rtslib >= 2.1.fb41, python-configshell, python-ethtool @@ -21,6 +31,16 @@ users will also need to install and use fcoe-utils. %prep %setup -q -n %{oname}-%{version} +%patch0 -p1 +%patch1 -p1 +%patch2 -p1 +%patch3 -p1 +%patch4 -p1 +%patch5 -p1 +%patch6 -p1 +%patch7 -p1 +%patch8 -p1 +%patch9 -p1 %build %{__python} setup.py build @@ -41,6 +61,21 @@ install -m 644 targetcli.8.gz %{buildroot}%{_mandir}/man8/ %{_mandir}/man8/targetcli.8.gz %changelog +* Mon Apr 23 2018 Maurizio Lombardi - 2.1.fb46-4 +- Properly detect errors when writing backup files. (Closes: #80) (#81) +- Read number of backup files to keep from file +- config: defend on '/etc/target/backup' directory +- config: backup when current config is different from recent backup copy +- config: rename key 'kept_backups' as 'max_backup_files' +- backup: global option to tune max no. of backup conf files + +* Fri Apr 13 2018 Maurizio Lombardi - 2.1.fb46-3 +- Support tcmu hw max sectors +- create: add a way to set control string + +* Wed Apr 11 2018 Maurizio Lombardi - 2.1.fb46-2 +- Add saveconfig command to StorageObject level + * Thu Mar 2 2017 Andy Grover - 2.1.fb46-1 - New upstream version - Drop no-model-alias.patch