Hello community, here is the log from the commit of package targetcli-fb for openSUSE:Factory checked in at 2020-02-19 12:41:23 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/targetcli-fb (Old) and /work/SRC/openSUSE:Factory/.targetcli-fb.new.26092 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "targetcli-fb" Wed Feb 19 12:41:23 2020 rev:17 rq:775445 version:2.1.51 Changes: -------- --- /work/SRC/openSUSE:Factory/targetcli-fb/targetcli-fb.changes 2020-02-04 19:58:05.393487358 +0100 +++ /work/SRC/openSUSE:Factory/.targetcli-fb.new.26092/targetcli-fb.changes 2020-02-19 12:41:23.783723502 +0100 @@ -1,0 +2,48 @@ +Wed Feb 12 17:58:15 UTC 2020 - ldun...@suse.com + +- Update to version 2.1.51 from 2.1.49, which includes + the addition of a targetcli daemon, which can be used + to speed up batch processing. With patches: + * version 2.1.51 + * targetcli: depreciate the redundant '--tcp' option + * man: add daemon intro at targetcli(8) man page + * systemd-units: fix documentation + * targetclid: add man page entry + * daemon: load the prefs on every new connection + * cli: show useful hint in header area of shell in daemonized mode + * cli: provide a way to disable using daemon + * cli: show better error msg when daemon is not running + * Do not print err msg when signal closes socket. + * Handle systemd socket activation, when present. + * Close socket when receiving a signal to interrupt connection. + * Exit with success when getting a signal. + * Only return response to targetcli when bytes present + * Removed useless semicolons, as they're ignored + * Handle OSError correctly: use strerror to get string + * Tweak systemd socket-activation settings for daemon + * Handle Python 3.7 stricter binary vs. string rules. + * Fix indention for targetclid when processing output. + * version 2.1.50 + * iscsi discovery_auth enable is a number not a string + * restoreconfig: add ability to restore/reload single target or storage_object + * Fix a syntax error in some except clauses + * Remove Epydoc markup from command messages + * targetcli: serialize multiple requests + * targetcli: way to enable targetclid as default choice + * targetclid: enable socket based activation + * targetclid: add daemonize component for targetcli + * Do not remove the first digit when auto-completing the TPG tag + * Remove the extra semicolon in _save_backups + * Add emulate_pr backstore attribute + * targetcli-fb: Fix raise exception error in _save_backups + * saveconfig: compress the backup config files + This replaces targetcli-fb-2.1.49.tar.xz with + targetcli-fb-2.1.51.tar.xz, and removes the following patches, + which area already upstream: + * Add-emulate_pr-backstore-attribute.patch + * do-not-remove-the-first-digit-when-auto-completing-the-tpg-tag + * iscsi-discovery_auth-enable-is-a-number-not-a-string + * saveconfig-compress-the-backup-config-files + * targetcli-fb-fix-raise-exception-error-in-save_backups + +------------------------------------------------------------------- Old: ---- Add-emulate_pr-backstore-attribute.patch do-not-remove-the-first-digit-when-auto-completing-the-tpg-tag iscsi-discovery_auth-enable-is-a-number-not-a-string saveconfig-compress-the-backup-config-files targetcli-fb-2.1.49.tar.xz targetcli-fb-fix-raise-exception-error-in-save_backups New: ---- targetcli-fb-2.1.51.tar.xz targetclid.service targetclid.socket ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ targetcli-fb.spec ++++++ --- /var/tmp/diff_new_pack.fBnxH7/_old 2020-02-19 12:41:24.959725766 +0100 +++ /var/tmp/diff_new_pack.fBnxH7/_new 2020-02-19 12:41:24.963725774 +0100 @@ -18,7 +18,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: targetcli-fb -Version: 2.1.49 +Version: 2.1.51 Release: 0 Summary: A command shell for managing the Linux LIO kernel target License: Apache-2.0 @@ -26,6 +26,8 @@ URL: https://github.com/open-iscsi/%{name} Source: %{name}-%{version}.tar.xz Source1: %{name}.service +Source2: targetclid.socket +Source3: targetclid.service BuildRequires: %{python_module configshell-fb} BuildRequires: %{python_module devel} BuildRequires: %{python_module pyparsing} @@ -45,8 +47,8 @@ Provides: targetcli = %{version}-%{release} Provides: targetcli-fb = %{version}-%{release} %endif -Obsoletes: targetcli -Obsoletes: targetcli-fb +Obsoletes: targetcli < %{version}-%{release} +Obsoletes: targetcli-fb < %{version}-%{release} BuildArch: noarch %if 0%{?sle_version} >= 150000 # explicit Provides advertising RBD support @@ -56,11 +58,6 @@ %{?systemd_ordering} Patch1: Split-out-blockdev-readonly-state-detection-helper.patch Patch2: rbd-support.patch -Patch3: saveconfig-compress-the-backup-config-files -Patch4: targetcli-fb-fix-raise-exception-error-in-save_backups -Patch5: Add-emulate_pr-backstore-attribute.patch -Patch6: do-not-remove-the-first-digit-when-auto-completing-the-tpg-tag -Patch7: iscsi-discovery_auth-enable-is-a-number-not-a-string %python_subpackages @@ -90,11 +87,6 @@ # RBD support is dependent on LIO changes present in the SLE/Leap kernel %patch2 -p1 %endif -%patch3 -p1 -%patch4 -p1 -%patch5 -p1 -%patch6 -p1 -%patch7 -p1 %build %python_build @@ -102,48 +94,62 @@ %install %python_install %python_clone -a %{buildroot}%{_bindir}/targetcli +%python_clone -a %{buildroot}%{_bindir}/targetclid install -d -m755 %{buildroot}%{_sysconfdir}/target install -d -m755 %{buildroot}%{_sysconfdir}/target/backup install -d -m755 %{buildroot}%{_sbindir} install -D -m644 targetcli.8 %{buildroot}%{_mandir}/man8/targetcli.8 -install -D -m644 %{SOURCE1} %{buildroot}%{_unitdir}/targetcli.service +install -D -m644 targetclid.8 %{buildroot}%{_mandir}/man8/targetclid.8 +install -D -m644 %{S:1} %{buildroot}%{_unitdir}/targetcli.service +install -D -m644 %{S:2} %{buildroot}%{_unitdir}/targetclid.socket +install -D -m644 %{S:3} %{buildroot}%{_unitdir}/targetclid.service %fdupes %{buildroot} ln -s %{_sbindir}/service %{buildroot}/%{_sbindir}/rctargetcli +ln -s %{_sbindir}/service %{buildroot}/%{_sbindir}/rctargetclid %post %python_install_alternative targetcli +%python_install_alternative targetclid %postun %python_uninstall_alternative targetcli +%python_uninstall_alternative targetclid %pre -%{service_add_pre targetcli.service} +%{service_add_pre targetcli.service targetclid.socket targetclid.service} %preun -%{stop_on_removal targetcli} +%{stop_on_removal targetcld targetcli} +%{service_del_preun targetcli.service targetclid.socket targetclid.service} %post -n %{name}-common -%{service_add_post targetcli.service} +%{service_add_post targetcli.service targetclid.socket targetclid.service} %postun -n %{name}-common -%{service_del_postun targetcli.service} +%{service_del_postun targetcli.service targetclid.socket targetclid.service} %pre -n %{name}-common -%{service_add_pre targetcli.service} +%{service_add_pre targetcli.service targetclid.socket targetclid.service} %preun -n %{name}-common -%{service_del_preun targetcli.service} +%{service_del_preun targetcli.service targetclid.socket targetclid.service} %files %{python_files} %python_alternative %{_bindir}/targetcli +%python_alternative %{_bindir}/targetclid %{python_sitelib}/* %files -n %{name}-common -%doc COPYING README.md THANKS +%license COPYING +%doc README.md THANKS %dir %{_sysconfdir}/target %dir %{_sysconfdir}/target/backup %doc %{_mandir}/man8/targetcli.8%{ext_man} +%doc %{_mandir}/man8/targetclid.8%{ext_man} %{_unitdir}/targetcli.service +%{_unitdir}/targetclid.service +%{_unitdir}/targetclid.socket %{_sbindir}/rctargetcli +%{_sbindir}/rctargetclid %changelog ++++++ Split-out-blockdev-readonly-state-detection-helper.patch ++++++ --- /var/tmp/diff_new_pack.fBnxH7/_old 2020-02-19 12:41:24.983725812 +0100 +++ /var/tmp/diff_new_pack.fBnxH7/_new 2020-02-19 12:41:24.983725812 +0100 @@ -1,20 +1,22 @@ From 7374ba0e53d8e6af4abbb02bd60f35ed541b94f5 Mon Sep 17 00:00:00 2001 From: David Disseldorp <dd...@suse.de> Date: Tue, 10 Apr 2018 16:22:54 +0200 -Subject: [PATCH 3/4] Split out blockdev readonly state detection helper +Patch-mainline: never (SUSE-specific) +Subject: Split out blockdev readonly state detection helper So that it can be reused for RBD backstores. +Note: not accepted upstream, but still needed +here for our rbd stuff. (ldun...@suse.com) + Signed-off-by: David Disseldorp <dd...@suse.de> --- - targetcli/ui_backstore.py | 40 ++++++++++++++++++++-------------------- + targetcli/ui_backstore.py | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) -diff --git a/targetcli/ui_backstore.py b/targetcli/ui_backstore.py -index 546d9d2..57dedb1 100644 --- a/targetcli/ui_backstore.py +++ b/targetcli/ui_backstore.py -@@ -117,6 +117,25 @@ def complete_path(path, stat_fn): +@@ -119,6 +119,25 @@ def complete_path(path, stat_fn): return sorted(filtered, key=lambda s: '~'+s if s.endswith('/') else s) @@ -40,7 +42,7 @@ class UIALUATargetPortGroup(UIRTSLibNode): ''' -@@ -519,25 +538,6 @@ class UIBlockBackstore(UIBackstore): +@@ -536,25 +555,6 @@ class UIBlockBackstore(UIBackstore): self.so_cls = UIBlockStorageObject UIBackstore.__init__(self, 'block', parent) @@ -65,8 +67,8 @@ - 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 -@@ -548,7 +548,7 @@ class UIBlockBackstore(UIBackstore): + Creates an Block Storage object. "dev" is the path to the TYPE_DISK +@@ -565,7 +565,7 @@ class UIBlockBackstore(UIBackstore): ro_string = self.ui_eval_param(readonly, 'string', None) if ro_string == None: # attempt to detect block device readonly state via ioctl @@ -75,6 +77,3 @@ else: readonly = self.ui_eval_param(readonly, 'bool', False) --- -2.13.6 - ++++++ _service ++++++ --- /var/tmp/diff_new_pack.fBnxH7/_old 2020-02-19 12:41:24.995725835 +0100 +++ /var/tmp/diff_new_pack.fBnxH7/_new 2020-02-19 12:41:24.999725843 +0100 @@ -5,9 +5,9 @@ <param name="subdir"></param> <param name="filename">targetcli-fb</param> <param name="versionformat">@PARENT_TAG@</param> - <param name="versionrewrite-pattern">v(\d*\.\d*\.)fb(\d*)</param> + <param name="versionrewrite-pattern">v(\d*\.\d*\.)(\d*)</param> <param name="versionrewrite-replacement">\1\2</param> - <param name="revision">v2.1.fb49</param> + <param name="revision">v2.1.51</param> <param name="changesgenerate">enable</param> </service> <service name="recompress" mode="disabled"> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.fBnxH7/_old 2020-02-19 12:41:25.011725866 +0100 +++ /var/tmp/diff_new_pack.fBnxH7/_new 2020-02-19 12:41:25.011725866 +0100 @@ -1,4 +1,4 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/open-iscsi/targetcli-fb.git</param> - <param name="changesrevision">4d08771c0e6bf3cacba2ed3d3127dd10a86a7847</param></service></servicedata> \ No newline at end of file + <param name="changesrevision">06076aba7e9e9bd4a1e84bac61e85265e8075b8e</param></service></servicedata> \ No newline at end of file ++++++ targetcli-fb-2.1.49.tar.xz -> targetcli-fb-2.1.51.tar.xz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/targetcli-fb-2.1.49/daemon/targetclid new/targetcli-fb-2.1.51/daemon/targetclid --- old/targetcli-fb-2.1.49/daemon/targetclid 1970-01-01 01:00:00.000000000 +0100 +++ new/targetcli-fb-2.1.51/daemon/targetclid 2019-11-06 13:31:26.000000000 +0100 @@ -0,0 +1,276 @@ +#!/usr/bin/python + +''' +targetclid + +This file is part of targetcli-fb. +Copyright (c) 2019 by Red Hat, Inc. + +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. +''' + +from __future__ import print_function +from targetcli import UIRoot +from targetcli import __version__ as targetcli_version +from configshell_fb import ConfigShell +from os import getuid, getenv, unlink +from threading import Thread + +import sys +import socket +import struct +import fcntl +import signal +import errno + + +err = sys.stderr + +class TargetCLI: + def __init__(self): + ''' + initializer + ''' + # socket for unix communication + self.socket_path = '/var/run/targetclid.sock' + # pid file for defending on multiple daemon runs + self.pid_file = '/var/run/targetclid.pid' + + self.NoSignal = True + self.sock = None + + # shell console methods + self.shell = ConfigShell(getenv("TARGETCLI_HOME", '~/.targetcli')) + self.con = self.shell.con + self.display = self.shell.con.display + self.render = self.shell.con.render_text + + # Handle SIGINT SIGTERM SIGHUP gracefully + signal.signal(signal.SIGINT, self.signal_handler) + signal.signal(signal.SIGTERM, self.signal_handler) + signal.signal(signal.SIGHUP, self.signal_handler) + + try: + self.pfd = open(self.pid_file, 'w+') + except IOError as e: + self.display( + self.render( + "opening pidfile failed: %s" %str(e), + 'red')) + sys.exit(1) + + self.try_pidfile_lock() + + is_root = False + if getuid() == 0: + is_root = True + + try: + root_node = UIRoot(self.shell, as_root=is_root) + root_node.refresh() + except Exception as error: + self.display(self.render(str(error), 'red')) + if not is_root: + self.display(self.render("Retry as root.", 'red')) + self.pfd.close() + sys.exit(1) + + # Keep track, for later use + self.con_stdout_ = self.con._stdout + self.con_stderr_ = self.con._stderr + + + def __del__(self): + ''' + destructor + ''' + if hasattr(self, 'pfd'): + self.pfd.close() + + + def signal_handler(self, signum, frame): + ''' + signal handler + ''' + self.NoSignal = False + if self.sock: + self.sock.close() + + + def try_pidfile_lock(self): + ''' + get lock on pidfile, which is to check if targetclid is running + ''' + # check if targetclid is already running + lock = struct.pack('hhllhh', fcntl.F_WRLCK, 0, 0, 0, 0, 0) + try: + fcntl.fcntl(self.pfd, fcntl.F_SETLK, lock) + except Exception: + self.display(self.render("targetclid is already running...", 'red')) + self.pfd.close() + sys.exit(1) + + + def release_pidfile_lock(self): + ''' + release lock on pidfile + ''' + lock = struct.pack('hhllhh', fcntl.F_UNLCK, 0, 0, 0, 0, 0) + try: + fcntl.fcntl(self.pfd, fcntl.F_SETLK, lock) + except Exception as e: + self.display( + self.render( + "fcntl(UNLCK) on pidfile failed: %s" %str(e), + 'red')) + self.pfd.close() + sys.exit(1) + self.pfd.close() + + + def client_thread(self, connection): + ''' + Handle commands from client + ''' + # load the prefs + self.shell.prefs.load() + + still_listen = True + # Receive the data in small chunks and retransmit it + while still_listen: + data = connection.recv(65535) + if b'-END@OF@DATA-' in data: + connection.close() + still_listen = False + else: + self.con._stdout = self.con._stderr = f = open("/tmp/data.txt", "w") + try: + # extract multiple commands delimited with '%' + list_data = data.decode().split('%') + for cmd in list_data: + self.shell.run_cmdline(cmd) + except Exception as e: + print(str(e), file=f) # push error to stream + + # Restore + self.con._stdout = self.con_stdout_ + self.con._stderr = self.con_stderr_ + f.close() + + with open('/tmp/data.txt', 'r') as f: + output = f.read() + var = struct.pack('i', len(output)) + connection.sendall(var) # length of string + if len(output): + connection.sendall(output.encode()) # actual string + + +def usage(): + print("Usage: %s [--version|--help]" % sys.argv[0], file=err) + print(" --version\t\tPrint version", file=err) + print(" --help\t\tPrint this information", file=err) + sys.exit(0) + + +def version(): + print("%s version %s" % (sys.argv[0], targetcli_version), file=err) + sys.exit(0) + + +def usage_version(cmd): + if cmd in ("help", "--help", "-h"): + usage() + + if cmd in ("version", "--version", "-v"): + version() + + +def main(): + ''' + start targetclid + ''' + if len(sys.argv) > 1: + usage_version(sys.argv[1]) + print("unrecognized option: %s" % (sys.argv[1])) + sys.exit(-1) + + to = TargetCLI() + + if getenv('LISTEN_PID'): + # the systemd-activation path, using next available FD + fn = sys.stderr.fileno() + 1 + try: + sock = socket.fromfd(fn, socket.AF_UNIX, socket.SOCK_STREAM) + except socket.error as err: + to.display(to.render(err.strerror, 'red')) + sys.exit(1) + + # save socket so a signal can clea it up + to.sock = sock + else: + # Make sure file doesn't exist already + try: + unlink(to.socket_path) + except: + pass + + # Create a TCP/IP socket + try: + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + except socket.error as err: + to.display(to.render(err.strerror, 'red')) + sys.exit(1) + + # save socket so a signal can clea it up + to.sock = sock + + # Bind the socket path + try: + sock.bind(to.socket_path) + except socket.error as err: + to.display(to.render(err.strerror, 'red')) + sys.exit(1) + + # Listen for incoming connections + try: + sock.listen(1) + except socket.error as err: + to.display(to.render(err.strerror, 'red')) + sys.exit(1) + + while to.NoSignal: + try: + # Wait for a connection + connection, client_address = sock.accept() + except socket.error as err: + if err.errno != errno.EBADF or to.NoSignal: + to.display(to.render(err.strerror, 'red')) + break + + thread = Thread(target=to.client_thread, args=(connection,)) + thread.start() + try: + thread.join() + except: + to.display(to.render(str(error), 'red')) + + to.release_pidfile_lock() + + if not to.NoSignal: + to.display(to.render("Signal received, quiting gracefully!", 'green')) + sys.exit(0) + sys.exit(1) + + +if __name__ == "__main__": + main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/targetcli-fb-2.1.49/scripts/targetcli new/targetcli-fb-2.1.51/scripts/targetcli --- old/targetcli-fb-2.1.49/scripts/targetcli 2018-09-05 14:08:11.000000000 +0200 +++ new/targetcli-fb-2.1.51/scripts/targetcli 2019-11-06 13:31:26.000000000 +0100 @@ -24,10 +24,22 @@ from targetcli import UIRoot from rtslib_fb import RTSLibError from configshell_fb import ConfigShell, ExecutionError -import sys from targetcli import __version__ as targetcli_version +import sys +import socket +import struct +import readline +import six +import fcntl + err = sys.stderr +# lockfile for serializing multiple targetcli requests +lock_file = '/var/run/targetcli.lock' +socket_path = '/var/run/targetclid.sock' +hints = ['/', 'backstores/', 'iscsi/', 'loopback/', 'vhost/', 'xen-pvscsi/', + 'cd', 'pwd', 'ls', 'set', 'get', 'help', 'refresh', 'status', + 'clearconfig', 'restoreconfig', 'saveconfig', 'exit'] class TargetCLI(ConfigShell): default_prefs = {'color_path': 'magenta', @@ -51,14 +63,16 @@ 'auto_save_on_exit': True, 'max_backup_files': '10', 'auto_add_default_portal': True, + 'auto_use_daemon': False, } def usage(): - print("Usage: %s [--version|--help|CMD]" % sys.argv[0], file=err) + print("Usage: %s [--version|--help|CMD|--disable-daemon]" % sys.argv[0], file=err) print(" --version\t\tPrint version", file=err) print(" --help\t\tPrint this information", file=err) print(" CMD\t\t\tRun targetcli shell command and exit", file=err) print(" <nothing>\t\tEnter configuration shell", file=err) + print(" --disable-daemon\tTurn-off the global auto use daemon flag", file=err) print("See man page for more information.", file=err) sys.exit(-1) @@ -66,16 +80,153 @@ print("%s version %s" % (sys.argv[0], targetcli_version), file=err) sys.exit(0) +def usage_version(cmd): + if cmd in ("help", "--help", "-h"): + usage() + + if cmd in ("version", "--version", "-v"): + version() + +def try_op_lock(shell, lkfd): + ''' + acquire a blocking lock on lockfile, to serialize multiple requests + ''' + try: + fcntl.flock(lkfd, fcntl.LOCK_EX) # wait here until ongoing request is finished + except Exception as e: + shell.con.display( + shell.con.render_text( + "taking lock on lockfile failed: %s" %str(e), + 'red')) + sys.exit(1) + +def release_op_lock(shell, lkfd): + ''' + release blocking lock on lockfile, which can allow other requests process + ''' + try: + fcntl.flock(lkfd, fcntl.LOCK_UN) # allow other requests now + except Exception as e: + shell.con.display( + shell.con.render_text( + "unlock on lockfile failed: %s" %str(e), + 'red')) + sys.exit(1) + lkfd.close() + +def completer(text, state): + options = [x for x in hints if x.startswith(text)] + try: + return options[state] + except IndexError: + return None + +def call_daemon(shell, req): + try: + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + except socket.error as err: + shell.con.display(shell.con.render_text(err, 'red')) + sys.exit(1) + + try: + sock.connect(socket_path) + except socket.error as err: + shell.con.display(shell.con.render_text(err, 'red')) + shell.con.display( + shell.con.render_text("Currently auto_use_daemon is true, " + "hence please make sure targetclid daemon is running ...\n" + "(or)\nIncase if you wish to turn auto_use_daemon to false " + "then run '#targetcli --disable-daemon'", 'red')) + sys.exit(1) + + try: + # send request + sock.sendall(req) + except socket.error as err: + shell.con.display(shell.con.render_text(err, 'red')) + sys.exit(1) + + var = sock.recv(4) # get length of data + sending = struct.unpack('i', var) + amount_expected = sending[0] + amount_received = 0 + + # get the actual data in chunks + while amount_received < amount_expected: + data = sock.recv(1024) + amount_received += len(data) + print(data.decode(), end ="") + + sock.send(b'-END@OF@DATA-') + sock.close() + sys.exit(0) + +def get_arguments(shell): + readline.set_completer(completer) + readline.set_completer_delims('') + + if 'libedit' in readline.__doc__: + readline.parse_and_bind("bind ^I rl_complete") + else: + readline.parse_and_bind("tab: complete") + + if len(sys.argv) > 1: + command = " ".join(sys.argv[1:]) + else: + inputs = [] + shell.con.display("targetcli shell version %s\n" + "Entering targetcli batch mode for daemonized approach.\n" + "Enter multiple commands separated by newline and " + "type 'exit' to run them all in one go.\n" + % targetcli_version) + while True: + shell.con.raw_write("/> ") + command = six.moves.input() + if command.lower() == "exit": + break + inputs.append(command) + command = '%'.join(inputs) # delimit multiple commands with '%' + + if not command: + sys.exit(1) + + usage_version(command); + + return command + def main(): ''' Start the targetcli shell. ''' + shell = TargetCLI(getenv("TARGETCLI_HOME", '~/.targetcli')) + + is_root = False if getuid() == 0: is_root = True - else: - is_root = False - shell = TargetCLI(getenv("TARGETCLI_HOME", '~/.targetcli')) + try: + lkfd = open(lock_file, 'w+'); + except IOError as e: + shell.con.display( + shell.con.render_text("opening lockfile failed: %s" %str(e), + 'red')) + sys.exit(1) + + try_op_lock(shell, lkfd) + + use_daemon = False + if shell.prefs['auto_use_daemon']: + use_daemon = True + + disable_daemon=False + if len(sys.argv) > 1: + usage_version(sys.argv[1]) + if sys.argv[1] in ("disable-daemon", "--disable-daemon"): + disable_daemon=True + + if use_daemon and not disable_daemon: + call_daemon(shell, get_arguments(shell).encode()) + # does not return try: root_node = UIRoot(shell, as_root=is_root) @@ -87,14 +238,11 @@ sys.exit(-1) if len(sys.argv) > 1: - if sys.argv[1] in ("--help", "-h"): - usage() - - if sys.argv[1] in ("--version", "-v"): - version() - try: - shell.run_cmdline(" ".join(sys.argv[1:])) + if disable_daemon: + shell.run_cmdline('set global auto_use_daemon=false') + else: + shell.run_cmdline(" ".join(sys.argv[1:])) except Exception as e: print(str(e), file=sys.stderr) sys.exit(1) @@ -117,6 +265,8 @@ shell.log.info("Global pref auto_save_on_exit=true") root_node.ui_command_saveconfig() + release_op_lock(shell, lkfd) + if __name__ == "__main__": main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/targetcli-fb-2.1.49/setup.py new/targetcli-fb-2.1.51/setup.py --- old/targetcli-fb-2.1.49/setup.py 2018-09-05 14:08:11.000000000 +0200 +++ new/targetcli-fb-2.1.51/setup.py 2019-11-06 13:31:26.000000000 +0100 @@ -30,7 +30,10 @@ maintainer_email = 'agro...@redhat.com', url = 'http://github.com/open-iscsi/targetcli-fb', packages = ['targetcli'], - scripts = ['scripts/targetcli'], + scripts = [ + 'scripts/targetcli', + 'daemon/targetclid' + ], classifiers = [ "Programming Language :: Python", "Programming Language :: Python :: 3", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/targetcli-fb-2.1.49/systemd/targetclid.service new/targetcli-fb-2.1.51/systemd/targetclid.service --- old/targetcli-fb-2.1.49/systemd/targetclid.service 1970-01-01 01:00:00.000000000 +0100 +++ new/targetcli-fb-2.1.51/systemd/targetclid.service 2019-11-06 13:31:26.000000000 +0100 @@ -0,0 +1,13 @@ +[Unit] +Description=Targetcli daemon +Documentation=man:targetclid(8) +After=network.target + +[Service] +Type=simple +ExecStart=/usr/bin/targetclid +Restart=on-failure + +[Install] +WantedBy=multi-user.target +Also=targetclid.socket diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/targetcli-fb-2.1.49/systemd/targetclid.socket new/targetcli-fb-2.1.51/systemd/targetclid.socket --- old/targetcli-fb-2.1.49/systemd/targetclid.socket 1970-01-01 01:00:00.000000000 +0100 +++ new/targetcli-fb-2.1.51/systemd/targetclid.socket 2019-11-06 13:31:26.000000000 +0100 @@ -0,0 +1,9 @@ +[Unit] +Description=targetclid socket +Documentation=man:targetclid(8) + +[Socket] +ListenStream=/var/run/targetclid.sock + +[Install] +WantedBy=sockets.target diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/targetcli-fb-2.1.49/targetcli/ui_backstore.py new/targetcli-fb-2.1.51/targetcli/ui_backstore.py --- old/targetcli-fb-2.1.49/targetcli/ui_backstore.py 2018-09-05 14:08:11.000000000 +0200 +++ new/targetcli-fb-2.1.51/targetcli/ui_backstore.py 2019-11-06 13:31:26.000000000 +0100 @@ -59,14 +59,14 @@ ''' This function converts human-readable amounts of bytes to bytes. It understands the following units : - - I{B} or no unit present for Bytes - - I{k}, I{K}, I{kB}, I{KB} for kB (kilobytes) - - I{m}, I{M}, I{mB}, I{MB} for MB (megabytes) - - I{g}, I{G}, I{gB}, I{GB} for GB (gigabytes) - - I{t}, I{T}, I{tB}, I{TB} for TB (terabytes) + - B or no unit present for Bytes + - k, K, kB, KB for kB (kilobytes) + - m, M, mB, MB for MB (megabytes) + - g, G, gB, GB for GB (gigabytes) + - t, T, tB, TB for TB (terabytes) - Note: The definition of I{kilo} defaults to 1kB = 1024Bytes. - Strictly speaking, those should not be called I{kB} but I{kiB}. + Note: The definition of kilo defaults to 1kB = 1024Bytes. + Strictly speaking, those should not be called "kB" but "kiB". You can override that with the optional kilo parameter. @param hsize: The human-readable version of the Bytes amount to convert @@ -286,13 +286,13 @@ def ui_command_delete(self, name, save=None): ''' - Recursively deletes the storage object having the specified I{name}. If + Recursively deletes the storage object having the specified name. If there are LUNs using this storage object, they will be deleted too. EXAMPLE ======= - B{delete mystorage} - ------------------- + delete mystorage + ---------------- Deletes the storage object named mystorage, and all associated LUNs. ''' self.assert_root() @@ -354,7 +354,7 @@ def ui_command_create(self, name, dev): ''' Creates a PSCSI storage object, with supplied name and SCSI device. The - SCSI device I{dev} can either be a path name to the device, in which + SCSI device "dev" can either be a path name to the device, in which case it is recommended to use the /dev/disk/by-id hierarchy to have consistent naming should your physical SCSI system be modified, or an SCSI device ID in the H:C:T:L format, which is not recommended as SCSI @@ -383,17 +383,17 @@ def ui_command_create(self, name, size, nullio=None, wwn=None): ''' - Creates an RDMCP storage object. I{size} is the size of the ramdisk. + Creates an RDMCP storage object. "size" is the size of the ramdisk. SIZE SYNTAX =========== - If size is an int, it represents a number of bytes. - If size is a string, the following units can be used: - - B{B} or no unit present for bytes - - B{k}, B{K}, B{kB}, B{KB} for kB (kilobytes) - - B{m}, B{M}, B{mB}, B{MB} for MB (megabytes) - - B{g}, B{G}, B{gB}, B{GB} for GB (gigabytes) - - B{t}, B{T}, B{tB}, B{TB} for TB (terabytes) + - B or no unit present for bytes + - k, K, kB, KB for kB (kilobytes) + - m, M, mB, MB for MB (megabytes) + - g, G, gB, GB for GB (gigabytes) + - t, T, tB, TB for TB (terabytes) ''' self.assert_root() @@ -445,14 +445,14 @@ def ui_command_create(self, name, file_or_dev, size=None, write_back=None, sparse=None, wwn=None): ''' - Creates a FileIO storage object. If I{file_or_dev} is a path - to a regular file to be used as backend, then the I{size} - parameter is mandatory. Else, if I{file_or_dev} is a path to a - block device, the size parameter B{must} be ommited. If - present, I{size} is the size of the file to be used, I{file} - the path to the file or I{dev} the path to a block device. The - I{write_back} parameter is a boolean controlling write - caching. It is enabled by default. The I{sparse} parameter is + Creates a FileIO storage object. If "file_or_dev" is a path + to a regular file to be used as backend, then the "size" + parameter is mandatory. Else, if "file_or_dev" is a path to a + block device, the size parameter must be omitted. If + present, "size" is the size of the file to be used, "file" + the path to the file or "dev" the path to a block device. The + "write_back" parameter is a boolean controlling write + caching. It is enabled by default. The "sparse" parameter is only applicable when creating a new backing file. It is a boolean stating if the created file should be created as a sparse file (the default), or fully initialized. @@ -461,11 +461,11 @@ =========== - If size is an int, it represents a number of bytes. - If size is a string, the following units can be used: - - B{B} or no unit present for bytes - - B{k}, B{K}, B{kB}, B{KB} for kB (kilobytes) - - B{m}, B{M}, B{mB}, B{MB} for MB (megabytes) - - B{g}, B{G}, B{gB}, B{GB} for GB (gigabytes) - - B{t}, B{T}, B{tB}, B{TB} for TB (terabytes) + - B or no unit present for bytes + - k, K, kB, KB for kB (kilobytes) + - m, M, mB, MB for MB (megabytes) + - g, G, gB, GB for GB (gigabytes) + - t, T, tB, TB for TB (terabytes) ''' self.assert_root() @@ -557,7 +557,7 @@ 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 + Creates an Block Storage object. "dev" is the path to the TYPE_DISK block device to use. ''' self.assert_root() @@ -628,11 +628,11 @@ =========== - If size is an int, it represents a number of bytes. - If size is a string, the following units can be used: - - B{B} or no unit present for bytes - - B{k}, B{K}, B{kB}, B{KB} for kB (kilobytes) - - B{m}, B{M}, B{mB}, B{MB} for MB (megabytes) - - B{g}, B{G}, B{gB}, B{GB} for GB (gigabytes) - - B{t}, B{T}, B{tB}, B{TB} for TB (terabytes) + - B or no unit present for bytes + - k, K, kB, KB for kB (kilobytes) + - m, M, mB, MB for MB (megabytes) + - g, G, gB, GB for GB (gigabytes) + - t, T, tB, TB for TB (terabytes) ''' size = human_to_bytes(size) @@ -689,6 +689,7 @@ 'emulate_tpws': ('number', 'If set to 1, enable Thin Provisioning Write Same.'), 'emulate_ua_intlck_ctrl': ('number', 'If set to 1, enable Unit Attention Interlock.'), 'emulate_write_cache': ('number', 'If set to 1, turn on Write Cache Enable.'), + 'emulate_pr': ('number', 'If set to 1, enable SCSI Reservations.'), 'enforce_pr_isids': ('number', 'If set to 1, enforce persistent reservation ISIDs.'), 'force_pr_aptpl': ('number', 'If set to 1, force SPC-3 PR Activate Persistence across Target Power Loss operation.'), 'fabric_max_sectors': ('number', 'Maximum number of sectors the fabric can transfer at once.'), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/targetcli-fb-2.1.49/targetcli/ui_node.py new/targetcli-fb-2.1.51/targetcli/ui_node.py --- old/targetcli-fb-2.1.49/targetcli/ui_node.py 2018-09-05 14:08:11.000000000 +0200 +++ new/targetcli-fb-2.1.51/targetcli/ui_node.py 2019-11-06 13:31:26.000000000 +0100 @@ -49,6 +49,9 @@ self.define_config_group_param( 'global', 'max_backup_files', 'string', 'Max no. of configurations to be backed up in /etc/target/backup/ directory.') + self.define_config_group_param( + 'global', 'auto_use_daemon', 'bool', + 'If true, commands will be sent to targetclid.') def assert_root(self): ''' @@ -95,7 +98,7 @@ SEE ALSO ======== - B{ls} + ls ''' description, is_healthy = self.summary() self.shell.log.info("Status for %s: %s" % (self.path, description)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/targetcli-fb-2.1.49/targetcli/ui_root.py new/targetcli-fb-2.1.51/targetcli/ui_root.py --- old/targetcli-fb-2.1.49/targetcli/ui_root.py 2018-09-05 14:08:11.000000000 +0200 +++ new/targetcli-fb-2.1.51/targetcli/ui_root.py 2019-11-06 13:31:26.000000000 +0100 @@ -24,6 +24,7 @@ import shutil import stat import filecmp +import gzip from configshell_fb import ExecutionError from rtslib_fb import RTSRoot @@ -62,6 +63,38 @@ if fm.wwns == None or any(fm.wwns): UIFabricModule(fm, self) + def _compare_files(self, backupfile, savefile): + ''' + Compare backfile and saveconfig file + ''' + if (os.path.splitext(backupfile)[1] == '.gz'): + try: + with gzip.open(backupfile, 'rb') as fbkp: + fdata_bkp = fbkp.read() + except IOError as e: + self.shell.log.warning("Could not gzip open backupfile %s: %s" + % (backupfile, e.strerror)) + + else: + try: + with open(backupfile, 'rb') as fbkp: + fdata_bkp = fbkp.read() + except IOError as e: + self.shell.log.warning("Could not open backupfile %s: %s" + % (backupfile, e.strerror)) + + try: + with open(savefile, 'rb') as f: + fdata = f.read() + except IOError as e: + self.shell.log.warning("Could not open saveconfig file %s: %s" + % (savefile, e.strerror)) + + if fdata_bkp == fdata: + return True + else: + return False + def _save_backups(self, savefile): ''' Take backup of config-file if needed. @@ -72,29 +105,30 @@ backup_dir = os.path.dirname(savefile) + "/backup/" backup_name = "saveconfig-" + \ - datetime.now().strftime("%Y%m%d-%H:%M:%S") + ".json" + datetime.now().strftime("%Y%m%d-%H:%M:%S") + "-json.gz" backupfile = backup_dir + backup_name backup_error = None if not os.path.exists(backup_dir): try: - os.makedirs(backup_dir); + os.makedirs(backup_dir) except OSError as exe: raise ExecutionError("Cannot create backup directory [%s] %s." - % (backup_dir, exc.strerror)) + % (backup_dir, exe.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")) + "/backup/saveconfig-*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): + if not backed_files_list or not self._compare_files(backed_files_list[-1], savefile): try: - shutil.copy(savefile, backupfile) - + with open(savefile, 'rb') as f_in, gzip.open(backupfile, 'wb') as f_out: + shutil.copyfileobj(f_in, f_out) + f_out.flush() except IOError as ioe: backup_error = ioe.strerror or "Unknown error" @@ -139,7 +173,8 @@ self.shell.log.info("Configuration saved to %s" % savefile) - def ui_command_restoreconfig(self, savefile=default_save_file, clear_existing=False): + def ui_command_restoreconfig(self, savefile=default_save_file, clear_existing=False, + target=None, storage_object=None): ''' Restores configuration from a file. ''' @@ -151,7 +186,10 @@ self.shell.log.info("Restore file %s not found" % savefile) return - errors = self.rtsroot.restore_from_file(savefile, clear_existing) + target = self.ui_eval_param(target, 'string', None) + storage_object = self.ui_eval_param(storage_object, 'string', None) + errors = self.rtsroot.restore_from_file(savefile, clear_existing, + target, storage_object) self.refresh() @@ -202,15 +240,15 @@ PARAMETERS ========== - I{action} - --------- - The I{action} is one of: - - B{list} gives a short session list - - B{detail} gives a detailed list - - I{sid} + action ------ - You can specify an I{sid} to only list this one, + The action is one of: + - `list`` gives a short session list + - `detail` gives a detailed list + + sid + --- + You can specify an "sid" to only list this one, with or without details. SEE ALSO @@ -283,4 +321,3 @@ indent_print("(no open sessions)", base_steps) else: raise ExecutionError("no session found with sid %i" % int(sid)) - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/targetcli-fb-2.1.49/targetcli/ui_target.py new/targetcli-fb-2.1.51/targetcli/ui_target.py --- old/targetcli-fb-2.1.49/targetcli/ui_target.py 2018-09-05 14:08:11.000000000 +0200 +++ new/targetcli-fb-2.1.51/targetcli/ui_target.py 2019-11-06 13:31:26.000000000 +0100 @@ -34,7 +34,8 @@ from .ui_node import UINode, UIRTSLibNode auth_params = ('userid', 'password', 'mutual_userid', 'mutual_password') -discovery_params = auth_params + ("enable",) +int_params = ('enable',) +discovery_params = auth_params + int_params class UIFabricModule(UIRTSLibNode): ''' @@ -47,8 +48,12 @@ self.refresh() if self.rtsnode.has_feature('discovery_auth'): for param in discovery_params: - self.define_config_group_param('discovery_auth', - param, 'string') + if param in int_params: + self.define_config_group_param('discovery_auth', + param, 'number') + else: + self.define_config_group_param('discovery_auth', + param, 'string') self.refresh() # Support late params @@ -167,18 +172,18 @@ def ui_command_create(self, wwn=None): ''' - Creates a new target. The I{wwn} format depends on the transport(s) - supported by the fabric module. If the I{wwn} is ommited, then a + Creates a new target. The "wwn" format depends on the transport(s) + supported by the fabric module. If "wwn" is omitted, then a target will be created using either a randomly generated WWN of the proper type, or the first unused WWN in the list of possible WWNs if one is available. If WWNs are constrained to a list (i.e. for hardware targets addresses) and all WWNs are in use, the target creation will - fail. Use the B{info} command to get more information abour WWN type + fail. Use the `info` command to get more information abour WWN type and possible values. SEE ALSO ======== - B{info} + info ''' self.assert_root() @@ -223,12 +228,12 @@ def ui_command_delete(self, wwn): ''' - Recursively deletes the target with the specified I{wwn}, and all + Recursively deletes the target with the specified wwn, and all objects hanging under it. SEE ALSO ======== - B{create} + create ''' self.assert_root() target = Target(self.rtsnode, wwn, mode='lookup') @@ -262,7 +267,7 @@ def ui_command_info(self): ''' Displays information about the fabric module, notably the supported - transports(s) and accepted B{wwn} format(s), as long as supported + transports(s) and accepted wwn format(s), along with supported features. ''' fabric = self.rtsnode @@ -308,13 +313,13 @@ def ui_command_create(self, tag=None): ''' Creates a new Target Portal Group within the target. The - I{tag} must be a positive integer value, optionally prefaced + tag must be a positive integer value, optionally prefaced by 'tpg'. If omitted, the next available Target Portal Group Tag (TPGT) will be used. SEE ALSO ======== - B{delete} + delete ''' self.assert_root() @@ -351,12 +356,12 @@ def ui_command_delete(self, tag): ''' - Deletes the Target Portal Group with TPGT I{tag} from the target. The - I{tag} must be a positive integer matching an existing TPGT. + Deletes the Target Portal Group with TPGT "tag" from the target. The + tag must be a positive integer matching an existing TPGT. SEE ALSO ======== - B{create} + create ''' self.assert_root() if tag.startswith("tpg"): @@ -384,7 +389,7 @@ @rtype: list of str ''' if current_param == 'tag': - tags = [child.name[4:] for child in self.children] + tags = [child.name[3:] for child in self.children] completions = [tag for tag in tags if tag.startswith(text)] else: completions = [] @@ -515,7 +520,7 @@ SEE ALSO ======== - B{disable status} + disable status ''' self.assert_root() if self.rtsnode.enable: @@ -533,7 +538,7 @@ SEE ALSO ======== - B{enable status} + enable status ''' self.assert_root() if self.rtsnode.enable: @@ -582,18 +587,19 @@ def ui_command_create(self, wwn, add_mapped_luns=None): ''' - Creates a Node ACL for the initiator node with the specified I{wwn}. - The node's I{wwn} must match the expected WWN Type of the target's + Creates a Node ACL for the initiator node with the specified wwn. + The node's wwn must match the expected WWN Type of the target's fabric module. - If I{add_mapped_luns} is omitted, the global parameter - B{auto_add_mapped_luns} will be used, else B{true} or B{false} are - accepted. If B{true}, then after creating the ACL, mapped LUNs will be - automatically created for all existing LUNs. + "add_mapped_luns" can be "true" of "false". If true, then + after creating the ACL, mapped LUNs will be automatically + created for all existing LUNs. If the parameter is omitted, + the global parameter "auto_add_mapped_luns" is used. SEE ALSO ======== - B{delete} + delete + ''' self.assert_root() @@ -614,11 +620,11 @@ def ui_command_delete(self, wwn): ''' - Deletes the Node ACL with the specified I{wwn}. + Deletes the Node ACL with the specified wwn. SEE ALSO ======== - B{create} + create ''' self.assert_root() node_acl = NodeACL(self.tpg, wwn, mode='lookup') @@ -870,14 +876,14 @@ def ui_command_create(self, mapped_lun, tpg_lun_or_backstore, write_protect=None): ''' Creates a mapping to one of the TPG LUNs for the initiator referenced - by the ACL. The provided I{tpg_lun_or_backstore} will appear to that - initiator as LUN I{mapped_lun}. If the I{write_protect} flag is set to - B{1}, the initiator will not have write access to the Mapped LUN. + by the ACL. The provided "tpg_lun_or_backstore" will appear to that + initiator as LUN "mapped_lun". If the "write_protect" flag is set to + 1, the initiator will not have write access to the mapped LUN. - A storage object may also be given for the I{tpg_lun_or_backstore} parameter, + A storage object may also be given for the "tpg_lun_or_backstore" parameter, in which case the TPG LUN will be created for that backstore before mapping the LUN to the initiator. If a TPG LUN for the backstore already - exists, the Mapped LUN will map to that TPG LUN. + exists, the mapped LUN will map to that TPG LUN. Finally, a path to an existing block device or file can be given. If so, a storage object of the appropriate type is created with default parameters, @@ -885,7 +891,7 @@ SEE ALSO ======== - B{delete} + delete ''' self.assert_root() try: @@ -963,11 +969,11 @@ def ui_command_delete(self, mapped_lun): ''' - Deletes the specified I{mapped_lun}. + Deletes the specified mapped LUN. SEE ALSO ======== - B{create} + create ''' self.assert_root() for na in self.rtsnodes: @@ -1086,25 +1092,25 @@ add_mapped_luns=None): ''' Creates a new LUN in the Target Portal Group, attached to a storage - object. If the I{lun} parameter is omitted, the first available LUN in + object. If the "lun" parameter is omitted, the first available LUN in the TPG will be used. If present, it must be a number greater than 0. - Alternatively, the syntax I{lunX} where I{X} is a positive number is + Alternatively, the syntax "lunX" where "X" is a positive number is also accepted. - The I{storage_object} may be the path of an existing storage object, - i.e. B{/backstore/pscsi0/mydisk} to reference the B{mydisk} storage - object of the virtual HBA B{pscsi0}. It also may be the path to an + The "storage_object" may be the path of an existing storage object, + i.e. "/backstore/pscsi0/mydisk" to reference the "mydisk" storage + object of the virtual HBA "pscsi0". It also may be the path to an existing block device or image file, in which case a storage object will be created for it first, with default parameters. - If I{add_mapped_luns} is omitted, the global parameter - B{auto_add_mapped_luns} will be used, else B{true} or B{false} are - accepted. If B{true}, then after creating the LUN, mapped LUNs will be - automatically created for all existing node ACLs, mapping the new LUN. + "add_mapped_luns" can be "true" of "false". If true, then + after creating the ACL, mapped LUNs will be automatically + created for all existing LUNs. If the parameter is omitted, + the global parameter "auto_add_mapped_luns" is used. SEE ALSO ======== - B{delete} + delete ''' self.assert_root() @@ -1188,15 +1194,15 @@ def ui_command_delete(self, lun): ''' - Deletes the supplied LUN from the Target Portal Group. The I{lun} must + Deletes the supplied LUN from the Target Portal Group. "lun" must be a positive number matching an existing LUN. - Alternatively, the syntax I{lunX} where I{X} is a positive number is + Alternatively, the syntax "lunX" where "X" is a positive number is also accepted. SEE ALSO ======== - B{create} + create ''' self.assert_root() if lun.lower().startswith("lun"): @@ -1303,9 +1309,9 @@ def ui_command_create(self, ip_address=None, ip_port=None): ''' - Creates a Network Portal with specified I{ip_address} and - I{ip_port}. If I{ip_port} is omitted, the default port for - the target fabric will be used. If I{ip_address} is omitted, + Creates a Network Portal with the specified IP address and + port. If the port is omitted, the default port for + the target fabric will be used. If the IP address is omitted, INADDR_ANY (0.0.0.0) will be used. Choosing IN6ADDR_ANY (::0) will listen on all IPv6 interfaces @@ -1317,7 +1323,7 @@ SEE ALSO ======== - B{delete} + delete ''' self.assert_root() @@ -1379,11 +1385,11 @@ def ui_command_delete(self, ip_address, ip_port): ''' - Deletes the Network Portal with specified I{ip_address} and I{ip_port}. + Deletes the Network Portal with the specified IP address and port. SEE ALSO ======== - B{create} + create ''' self.assert_root() portal = NetworkPortal(self.tpg, self._canonicalize_ip(ip_address), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/targetcli-fb-2.1.49/targetcli/version.py new/targetcli-fb-2.1.51/targetcli/version.py --- old/targetcli-fb-2.1.49/targetcli/version.py 2018-09-05 14:08:11.000000000 +0200 +++ new/targetcli-fb-2.1.51/targetcli/version.py 2019-11-06 13:31:26.000000000 +0100 @@ -15,4 +15,4 @@ under the License. ''' -__version__ = '2.1.fb49' +__version__ = '2.1.51' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/targetcli-fb-2.1.49/targetcli.8 new/targetcli-fb-2.1.51/targetcli.8 --- old/targetcli-fb-2.1.49/targetcli.8 2018-09-05 14:08:11.000000000 +0200 +++ new/targetcli-fb-2.1.51/targetcli.8 2019-11-06 13:31:26.000000000 +0100 @@ -10,6 +10,11 @@ files, volumes, local SCSI devices, or ramdisk, and export them to remote systems via network fabrics, such as iSCSI or FCoE. .P +There is a daemon component for targetcli, which will greatly improve +the overall execution time taken by targetcli commands at scale. For +more details about switching to daemonized mode refer to targetclid(8) +man page. +.P The configuration layout is tree-based, similar to a filesystem, and is navigated in a similar manner. .SH USAGE @@ -458,8 +463,9 @@ .B /etc/target/backup/* .SH ENVIRONMENT .SS TARGETCLI_HOME -If set, this variable points to a directory that should be used instead of ~/.targetctl +If set, this variable points to a directory that should be used instead of ~/.targetcli .SH SEE ALSO +.BR targetclid (8), .BR targetctl (8), .BR tcmu-runner (8) .SH AUTHOR diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/targetcli-fb-2.1.49/targetclid.8 new/targetcli-fb-2.1.51/targetclid.8 --- old/targetcli-fb-2.1.49/targetclid.8 1970-01-01 01:00:00.000000000 +0100 +++ new/targetcli-fb-2.1.51/targetclid.8 2019-11-06 13:31:26.000000000 +0100 @@ -0,0 +1,77 @@ +.TH targetclid 8 +.SH NAME +.B targetclid +\- daemon component for targetcli +.SH DESCRIPTION +.B targetclid +is the daemon component of targetcli, which will help retain state of various +configfs object in memory, hence any new request/command can directly use the +in memory objects instead of reconstructing them by parsing through the entire +configfs files again and again for each and every single command. This will +greatly improve the overall execution time taken by targetcli commands at scale. + +.SH USAGE +.B targetclid [cmd] +.br +.B "--help" +for additional usage information. +.br +.B "--version" +for version information. +.SH QUICKSTART & EXAMPLES +.TP +To start using the daemon, one need to enable targetclid socket, +.br +$ systemctl enable targetclid.socket +.TP +If you would like to use the daemonized approach as default method then, +.br +$ targetcli set global auto_use_daemon=true +.br +$ targetcli ls +.TP +You can use batch mode for sending multiple commands in one go, +.br +$ targetcli <hit-enter> +.br +targetcli shell version 2.1.50 +.br +Entering targetcli batch mode for daemonized approach. +.br +Enter multiple commands separated by newline and type 'exit' to run them all in one go. +.br +/> ls +.br +/> pwd +.br +/> get global loglevel_file +.br +/> exit +.br +.TP +You can set preference to stop using daemonized mode even when the daemon is not running, +.br +$ targetcli --disable-daemon +.SH FILES +.B /etc/target/saveconfig.json +.br +.B /etc/target/backup/* +.br +.B /var/run/targetclid.sock +.br +.B /var/run/targetclid.pid +.SH ENVIRONMENT +.SS TARGETCLI_HOME +If set, this variable points to a directory that should be used instead of ~/.targetcli +.SH SEE ALSO +.BR targetcli (8), +.BR targetctl (8), +.BR tcmu-runner (8) +.SH AUTHOR +Written by Prasanna Kumar Kalever <prasanna.kale...@redhat.com> +.br +Man page written by Prasanna Kumar Kalever <prasanna.kale...@redhat.com> +.SH REPORTING BUGS +Report bugs via <targetcli-fb-de...@lists.fedorahosted.org> +.br +or <https://github.com/open-iscsi/targetcli-fb/issues> ++++++ targetclid.service ++++++ [Unit] Description=Targetcli daemon Documentation=man:targetclid(8) After=network.target [Service] Type=simple ExecStart=/usr/bin/targetclid Restart=on-failure [Install] WantedBy=multi-user.target Also=targetclid.socket ++++++ targetclid.socket ++++++ [Unit] Description=targetclid socket Documentation=man:targetclid(8) [Socket] ListenStream=/var/run/targetclid.sock [Install] WantedBy=sockets.target