Hello community, here is the log from the commit of package targetcli-fb for openSUSE:Factory checked in at 2018-10-22 11:23:11 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/targetcli-fb (Old) and /work/SRC/openSUSE:Factory/.targetcli-fb.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "targetcli-fb" Mon Oct 22 11:23:11 2018 rev:10 rq:643051 version:2.1.49 Changes: -------- --- /work/SRC/openSUSE:Factory/targetcli-fb/targetcli-fb.changes 2018-04-22 14:40:49.226404083 +0200 +++ /work/SRC/openSUSE:Factory/.targetcli-fb.new/targetcli-fb.changes 2018-10-22 11:23:14.883161582 +0200 @@ -1,0 +2,33 @@ +Thu Oct 18 19:50:11 UTC 2018 - opensuse-packag...@opensuse.org + +- Update to version 2.1.49: + * version 2.1.fb49 + * targetcli-fb: Add support for media change + * fix the parameter of define_config_group_param + * saveconfig: handle backups with block-level delete + * saveconfig: way for block-level save with delete command + * create: add a way to set control string + * fix amount of backup files in backup dir + * config: add saveconfig command to StorageObject level + * Allow to customize a home directory + * Fix default max_backup_files in ui_command_saveconfig + * MappedLuns and Luns max number is not the same anymore + * Use signed char instead of char + * version 2.1.fb48 + * remove wrong exit code from targetcli --version + * backup: global option to tune max no. of backup conf files + * config: rename key 'kept_backups' as 'max_backup_files' + * config: backup when current config is different from recent backup copy + * config: defend on '/etc/target/backup' directory + * Auto-detect readonly state for iblock devices + * Read number of backup files to keep from file + * skip refreshing user backed storage object when it is null + * Replace dbus-python with GObject Introspection + This replaces targetcli-fb-2.1.47.tar.xz with targetcli-fb-2.1.49.tar.xz, + and removes the following patches: + * Auto-detect-readonly-state-for-iblock-devices.patch + * Use-signed-char-instead-of-char.patch + * targetcli-only-save-old-config-if-present.patch + and updates the SPEC file. + +------------------------------------------------------------------- Old: ---- Auto-detect-readonly-state-for-iblock-devices.patch Use-signed-char-instead-of-char.patch targetcli-fb-2.1.47.tar.xz targetcli-only-save-old-config-if-present.patch New: ---- targetcli-fb-2.1.49.tar.xz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ targetcli-fb.spec ++++++ --- /var/tmp/diff_new_pack.WQ4ImA/_old 2018-10-22 11:23:15.455161007 +0200 +++ /var/tmp/diff_new_pack.WQ4ImA/_new 2018-10-22 11:23:15.455161007 +0200 @@ -18,7 +18,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: targetcli-fb -Version: 2.1.47 +Version: 2.1.49 Release: 0 Summary: A command shell for managing the Linux LIO kernel target License: Apache-2.0 @@ -54,11 +54,8 @@ Obsoletes: targetcli-rbd < %{version} %endif %{?systemd_requires} -Patch1: Auto-detect-readonly-state-for-iblock-devices.patch -Patch2: Use-signed-char-instead-of-char.patch -Patch3: Split-out-blockdev-readonly-state-detection-helper.patch -Patch4: rbd-support.patch -Patch5: targetcli-only-save-old-config-if-present.patch +Patch1: Split-out-blockdev-readonly-state-detection-helper.patch +Patch2: rbd-support.patch %python_subpackages @@ -84,13 +81,10 @@ %prep %setup -q %patch1 -p1 -%patch2 -p1 -%patch3 -p1 %if 0%{?sle_version} == 150000 # RBD support is dependent on LIO changes present in the SLE/Leap kernel -%patch4 -p1 +%patch2 -p1 %endif -%patch5 -p1 %build %python_build ++++++ _service ++++++ --- /var/tmp/diff_new_pack.WQ4ImA/_old 2018-10-22 11:23:15.483160979 +0200 +++ /var/tmp/diff_new_pack.WQ4ImA/_new 2018-10-22 11:23:15.483160979 +0200 @@ -7,7 +7,7 @@ <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v(\d*\.\d*\.)fb(\d*)</param> <param name="versionrewrite-replacement">\1\2</param> - <param name="revision">v2.1.fb47</param> + <param name="revision">v2.1.fb49</param> <param name="changesgenerate">enable</param> </service> <service name="recompress" mode="disabled"> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.WQ4ImA/_old 2018-10-22 11:23:15.499160963 +0200 +++ /var/tmp/diff_new_pack.WQ4ImA/_new 2018-10-22 11:23:15.499160963 +0200 @@ -1,4 +1,4 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/open-iscsi/targetcli-fb.git</param> - <param name="changesrevision">ee32a2493eaccd9352cc596b9e3387960cca48fc</param></service></servicedata> \ No newline at end of file + <param name="changesrevision">4d08771c0e6bf3cacba2ed3d3127dd10a86a7847</param></service></servicedata> \ No newline at end of file ++++++ rbd-support.patch ++++++ --- /var/tmp/diff_new_pack.WQ4ImA/_old 2018-10-22 11:23:15.511160951 +0200 +++ /var/tmp/diff_new_pack.WQ4ImA/_new 2018-10-22 11:23:15.511160951 +0200 @@ -9,14 +9,12 @@ [dd...@suse.de: accept and propagate wwn parameter] Reviewed-by: David Disseldorp <dd...@suse.de> --- - targetcli/ui_backstore.py | 59 +++++++++++++++++++++++++++++++++++++++++++++++ + targetcli/ui_backstore.py | 59 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) -diff --git a/targetcli/ui_backstore.py b/targetcli/ui_backstore.py -index 57dedb1..d576122 100644 --- a/targetcli/ui_backstore.py +++ b/targetcli/ui_backstore.py -@@ -29,6 +29,7 @@ import dbus +@@ -29,6 +29,7 @@ import stat from configshell_fb import ExecutionError from rtslib_fb import BlockStorageObject, FileIOStorageObject from rtslib_fb import PSCSIStorageObject, RDMCPStorageObject, UserBackedStorageObject @@ -24,7 +22,7 @@ from rtslib_fb import ALUATargetPortGroup from rtslib_fb import RTSLibError from rtslib_fb import RTSRoot -@@ -269,6 +270,7 @@ class UIBackstores(UINode): +@@ -281,6 +282,7 @@ class UIBackstores(UINode): UIRDMCPBackstore(self) UIFileIOBackstore(self) UIBlockBackstore(self) @@ -32,7 +30,7 @@ for name, iface, prop_dict in self._user_backstores(): UIUserBackedBackstore(self, name, iface, prop_dict) -@@ -572,6 +574,48 @@ class UIBlockBackstore(UIBackstore): +@@ -589,6 +591,48 @@ class UIBlockBackstore(UIBackstore): completions = [completions[0] + ' '] return completions @@ -81,7 +79,7 @@ class UIUserBackedBackstore(UIBackstore): ''' -@@ -739,6 +783,21 @@ class UIBlockStorageObject(UIStorageObject): +@@ -791,6 +835,21 @@ class UIBlockStorageObject(UIStorageObje return ("%s (%s) %s%s %s" % (so.udev_path, bytes_to_human(so.size), ro_str, wb_str, so.status), True) @@ -103,6 +101,3 @@ class UIUserBackedStorageObject(UIStorageObject): def summary(self): --- -2.13.6 - ++++++ targetcli-fb-2.1.47.tar.xz -> targetcli-fb-2.1.49.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/targetcli-fb-2.1.47/scripts/targetcli new/targetcli-fb-2.1.49/scripts/targetcli --- old/targetcli-fb-2.1.47/scripts/targetcli 2017-08-15 23:35:32.000000000 +0200 +++ new/targetcli-fb-2.1.49/scripts/targetcli 2018-09-05 14:08:11.000000000 +0200 @@ -20,7 +20,7 @@ from __future__ import print_function -from os import getuid +from os import getuid, getenv from targetcli import UIRoot from rtslib_fb import RTSLibError from configshell_fb import ConfigShell, ExecutionError @@ -49,6 +49,7 @@ 'auto_add_mapped_luns': True, 'auto_cd_after_create': False, 'auto_save_on_exit': True, + 'max_backup_files': '10', 'auto_add_default_portal': True, } @@ -63,7 +64,7 @@ def version(): print("%s version %s" % (sys.argv[0], targetcli_version), file=err) - sys.exit(-1) + sys.exit(0) def main(): ''' @@ -74,7 +75,7 @@ else: is_root = False - shell = TargetCLI('~/.targetcli') + shell = TargetCLI(getenv("TARGETCLI_HOME", '~/.targetcli')) try: root_node = UIRoot(shell, as_root=is_root) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/targetcli-fb-2.1.47/targetcli/ui_backstore.py new/targetcli-fb-2.1.49/targetcli/ui_backstore.py --- old/targetcli-fb-2.1.47/targetcli/ui_backstore.py 2017-08-15 23:35:32.000000000 +0200 +++ new/targetcli-fb-2.1.49/targetcli/ui_backstore.py 2018-09-05 14:08:11.000000000 +0200 @@ -17,11 +17,14 @@ under the License. ''' +from gi.repository import Gio import glob import os +import fcntl +import array +import struct import re import stat -import dbus from configshell_fb import ExecutionError from rtslib_fb import BlockStorageObject, FileIOStorageObject @@ -33,6 +36,8 @@ 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', @@ -128,7 +133,7 @@ self.define_config_group_param("alua", param, 'string') for param in alua_ro_params: - self.define_config_group_param("alua", param, 'string', False) + self.define_config_group_param("alua", param, 'string', writable=False) def ui_getgroup_alua(self, alua_attr): return getattr(self.rtsnode, alua_attr) @@ -229,16 +234,26 @@ tcmu-runner (or other daemon providing the same service) exposes a DBus ObjectManager-based iface to find handlers it supports. ''' - bus = dbus.SystemBus() + bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None) try: - mgr_obj = bus.get_object('org.kernel.TCMUService1', '/org/kernel/TCMUService1') - mgr_iface = dbus.Interface(mgr_obj, 'org.freedesktop.DBus.ObjectManager') - - for k,v in mgr_iface.GetManagedObjects().items(): - tcmu_obj = bus.get_object('org.kernel.TCMUService1', k) - tcmu_iface = dbus.Interface(tcmu_obj, dbus_interface='org.kernel.TCMUService1') + mgr_iface = Gio.DBusProxy.new_sync(bus, + Gio.DBusProxyFlags.NONE, + None, + 'org.kernel.TCMUService1', + '/org/kernel/TCMUService1', + 'org.freedesktop.DBus.ObjectManager', + None) + + for k, v in mgr_iface.GetManagedObjects().items(): + tcmu_iface = Gio.DBusProxy.new_sync(bus, + Gio.DBusProxyFlags.NONE, + None, + 'org.kernel.TCMUService1', + k, + 'org.kernel.TCMUService1', + None) yield (k[k.rfind("/")+1:], tcmu_iface, v) - except dbus.DBusException as e: + except Exception as e: return def refresh(self): @@ -269,7 +284,7 @@ def summary(self): return ("Storage Objects: %d" % len(self._children), None) - def ui_command_delete(self, name): + def ui_command_delete(self, name, save=None): ''' Recursively deletes the storage object having the specified I{name}. If there are LUNs using this storage object, they will be deleted too. @@ -286,7 +301,12 @@ except ValueError: raise ExecutionError("No storage object named %s." % name) - child.rtsnode.delete() + save = self.ui_eval_param(save, 'bool', False) + if save: + rn = self.get_root() + rn._save_backups(default_save_file) + + child.rtsnode.delete(save=save) self.remove_child(child) self.shell.log.info("Deleted storage object %s." % name) @@ -516,6 +536,25 @@ self.so_cls = UIBlockStorageObject UIBackstore.__init__(self, 'block', parent) + def _ui_block_ro_check(self, dev): + BLKROGET=0x0000125E + try: + f = os.open(dev, os.O_RDONLY) + except (OSError, IOError): + raise ExecutionError("Could not open %s" % dev) + # ioctl returns an int. Provision a buffer for it + buf = array.array('b', [0] * 4) + try: + fcntl.ioctl(f, BLKROGET, buf) + except (OSError, IOError): + os.close(f) + return False + + os.close(f) + if struct.unpack('I', buf)[0] == 0: + return False + return True + def ui_command_create(self, name, dev, readonly=None, wwn=None): ''' Creates an Block Storage object. I{dev} is the path to the TYPE_DISK @@ -523,7 +562,13 @@ ''' self.assert_root() - readonly = self.ui_eval_param(readonly, 'bool', False) + ro_string = self.ui_eval_param(readonly, 'string', None) + if ro_string == None: + # attempt to detect block device readonly state via ioctl + readonly = self._ui_block_ro_check(dev) + else: + readonly = self.ui_eval_param(readonly, 'bool', False) + wwn = self.ui_eval_param(wwn, 'string', None) so = BlockStorageObject(name, dev, readonly=readonly, wwn=wwn) @@ -559,7 +604,7 @@ def refresh(self): self._children = set([]) for so in RTSRoot().storage_objects: - if so.plugin == 'user': + if so.plugin == 'user' and so.config: idx = so.config.find("/") handler = so.config[:idx] if handler == self.handler: @@ -575,7 +620,7 @@ 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. @@ -595,13 +640,14 @@ config = self.handler + "/" + cfgstring - ok, errmsg = self.iface.CheckConfig(config) + ok, errmsg = self.iface.CheckConfig('(s)', config) if not ok: raise ExecutionError("cfgstring invalid: %s" % errmsg) try: so = UserBackedStorageObject(name, size=size, config=config, - wwn=wwn, hw_max_sectors=hw_max_sectors) + wwn=wwn, hw_max_sectors=hw_max_sectors, + control=control) except: raise ExecutionError("UserBackedStorageObject creation failed.") @@ -610,6 +656,19 @@ % (name, size)) return self.new_node(ui_so) + def ui_command_changemedium(self, name, size, cfgstring): + size = human_to_bytes(size) + config = self.handler + "/" + cfgstring + + try: + rc, errmsg = self.iface.ChangeMedium('(sts)', name, size, config) + except Exception as e: + raise ExecutionError("ChangeMedium failed: %s" % e) + else: + if rc == 0: + self.shell.log.info("Medium Changed.") + else: + raise ExecutionError("ChangeMedium failed: %s" % errmsg) class UIStorageObject(UIRTSLibNode): ''' @@ -635,6 +694,7 @@ '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.'), @@ -664,6 +724,26 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/targetcli-fb-2.1.47/targetcli/ui_node.py new/targetcli-fb-2.1.49/targetcli/ui_node.py --- old/targetcli-fb-2.1.47/targetcli/ui_node.py 2017-08-15 23:35:32.000000000 +0200 +++ new/targetcli-fb-2.1.49/targetcli/ui_node.py 2018-09-05 14:08:11.000000000 +0200 @@ -46,6 +46,9 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/targetcli-fb-2.1.47/targetcli/ui_root.py new/targetcli-fb-2.1.49/targetcli/ui_root.py --- old/targetcli-fb-2.1.47/targetcli/ui_root.py 2017-08-15 23:35:32.000000000 +0200 +++ new/targetcli-fb-2.1.49/targetcli/ui_root.py 2018-09-05 14:08:11.000000000 +0200 @@ -20,8 +20,10 @@ from datetime import datetime from glob import glob import os +import re import shutil import stat +import filecmp from configshell_fb import ExecutionError from rtslib_fb import RTSRoot @@ -32,7 +34,7 @@ from .ui_target import UIFabricModule default_save_file = "/etc/target/saveconfig.json" -kept_backups = 10 +universal_prefs_file = "/etc/target/targetcli.conf" class UIRoot(UINode): ''' @@ -60,40 +62,78 @@ if fm.wwns == None or any(fm.wwns): UIFabricModule(fm, self) - def ui_command_saveconfig(self, savefile=default_save_file): + def _save_backups(self, savefile): ''' - Saves the current configuration to a file so that it can be restored - on next boot. + Take backup of config-file if needed. ''' - self.assert_root() + # Only save backups if saving to default location + if savefile != default_save_file: + return - savefile = os.path.expanduser(savefile) + 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 - # 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 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: - # Kill excess backups - backups = sorted(glob(os.path.dirname(savefile) + "/backup/*.json")) - files_to_unlink = list(reversed(backups))[kept_backups:] + # 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 - 1:] 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)) + 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.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 + on next boot. + ''' + self.assert_root() + + if not savefile: + savefile = default_save_file + + savefile = os.path.expanduser(savefile) + + self._save_backups(savefile) self.rtsroot.save_to_file(savefile) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/targetcli-fb-2.1.47/targetcli/ui_target.py new/targetcli-fb-2.1.49/targetcli/ui_target.py --- old/targetcli-fb-2.1.47/targetcli/ui_target.py 2017-08-15 23:35:32.000000000 +0200 +++ new/targetcli-fb-2.1.49/targetcli/ui_target.py 2018-09-05 14:08:11.000000000 +0200 @@ -1141,7 +1141,7 @@ existing_mluns = [mlun.mapped_lun for mlun in acl.mapped_luns] if mapped_lun in existing_mluns: mapped_lun = None - for possible_mlun in six.moves.range(LUN.MAX_LUN): + for possible_mlun in six.moves.range(MappedLUN.MAX_LUN): if possible_mlun not in existing_mluns: mapped_lun = possible_mlun break diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/targetcli-fb-2.1.47/targetcli/version.py new/targetcli-fb-2.1.49/targetcli/version.py --- old/targetcli-fb-2.1.47/targetcli/version.py 2017-08-15 23:35:32.000000000 +0200 +++ new/targetcli-fb-2.1.49/targetcli/version.py 2018-09-05 14:08:11.000000000 +0200 @@ -15,4 +15,4 @@ under the License. ''' -__version__ = '2.1.fb47' +__version__ = '2.1.fb49' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/targetcli-fb-2.1.47/targetcli.8 new/targetcli-fb-2.1.49/targetcli.8 --- old/targetcli-fb-2.1.47/targetcli.8 2017-08-15 23:35:32.000000000 +0200 +++ new/targetcli-fb-2.1.49/targetcli.8 2018-09-05 14:08:11.000000000 +0200 @@ -456,6 +456,9 @@ .B /etc/target/saveconfig.json .br .B /etc/target/backup/* +.SH ENVIRONMENT +.SS TARGETCLI_HOME +If set, this variable points to a directory that should be used instead of ~/.targetctl .SH SEE ALSO .BR targetctl (8), .BR tcmu-runner (8)