174 changes: 162 additions & 12 deletions scripts/targetcli
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,22 @@ from os import getuid, getenv
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',
Expand All @@ -51,31 +63,170 @@ class TargetCLI(ConfigShell):
'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)

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 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)
Expand All @@ -87,14 +238,11 @@ def main():
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)
Expand All @@ -117,6 +265,8 @@ def main():
shell.log.info("Global pref auto_save_on_exit=true")
root_node.ui_command_saveconfig()

release_op_lock(shell, lkfd)


if __name__ == "__main__":
main()
5 changes: 4 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@
maintainer_email = 'agrover@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",
Expand Down
13 changes: 13 additions & 0 deletions systemd/targetclid.service
Original file line number Diff line number Diff line change
@@ -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
9 changes: 9 additions & 0 deletions systemd/targetclid.socket
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[Unit]
Description=targetclid socket
Documentation=man:targetclid(8)

[Socket]
ListenStream=/var/run/targetclid.sock

[Install]
WantedBy=sockets.target
8 changes: 7 additions & 1 deletion targetcli.8
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ administrator to assign local storage resources backed by either
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
Expand Down Expand Up @@ -458,8 +463,9 @@ the userid and password for full-feature phase for this ACL.
.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
Expand Down
75 changes: 38 additions & 37 deletions targetcli/ui_backstore.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,14 @@ def human_to_bytes(hsize, kilo=1024):
'''
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)
Note: The definition of I{kilo} defaults to 1kB = 1024Bytes.
Strictly speaking, those should not be called I{kB} but I{kiB}.
- 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 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
Expand Down Expand Up @@ -286,13 +286,13 @@ def summary(self):

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()
Expand Down Expand Up @@ -354,7 +354,7 @@ def __init__(self, parent):
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
Expand Down Expand Up @@ -383,17 +383,17 @@ def __init__(self, parent):

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()

Expand Down Expand Up @@ -445,14 +445,14 @@ def _create_file(self, filename, size, sparse=True):
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.
Expand All @@ -461,11 +461,11 @@ def ui_command_create(self, name, file_or_dev, size=None, write_back=None,
===========
- 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()

Expand Down Expand Up @@ -557,7 +557,7 @@ def _ui_block_ro_check(self, dev):

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()
Expand Down Expand Up @@ -628,11 +628,11 @@ def ui_command_create(self, name, size, cfgstring, wwn=None,
===========
- 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)
Expand Down Expand Up @@ -689,6 +689,7 @@ class UIStorageObject(UIRTSLibNode):
'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.'),
Expand Down
5 changes: 4 additions & 1 deletion targetcli/ui_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ def __init__(self, name, parent=None, shell=None):
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):
'''
Expand Down Expand Up @@ -95,7 +98,7 @@ def ui_command_status(self):
SEE ALSO
========
B{ls}
ls
'''
description, is_healthy = self.summary()
self.shell.log.info("Status for %s: %s" % (self.path, description))
Expand Down
73 changes: 55 additions & 18 deletions targetcli/ui_root.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import shutil
import stat
import filecmp
import gzip

from configshell_fb import ExecutionError
from rtslib_fb import RTSRoot
Expand Down Expand Up @@ -62,6 +63,38 @@ def refresh(self):
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.
Expand All @@ -72,29 +105,30 @@ def _save_backups(self, savefile):

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"

Expand Down Expand Up @@ -139,7 +173,8 @@ def ui_command_saveconfig(self, savefile=default_save_file):

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.
'''
Expand All @@ -151,7 +186,10 @@ def ui_command_restoreconfig(self, savefile=default_save_file, clear_existing=Fa
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()

Expand Down Expand Up @@ -202,15 +240,15 @@ def ui_command_sessions(self, action="list", sid=None):
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
Expand Down Expand Up @@ -283,4 +321,3 @@ def print_session(session):
indent_print("(no open sessions)", base_steps)
else:
raise ExecutionError("no session found with sid %i" % int(sid))

114 changes: 60 additions & 54 deletions targetcli/ui_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
'''
Expand All @@ -47,8 +48,12 @@ def __init__(self, fabric_module, parent):
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
Expand Down Expand Up @@ -167,18 +172,18 @@ def summary(self):

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()

Expand Down Expand Up @@ -223,12 +228,12 @@ def ui_complete_create(self, parameters, text, current_param):

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')
Expand Down Expand Up @@ -262,7 +267,7 @@ def ui_complete_delete(self, parameters, text, current_param):
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
Expand Down Expand Up @@ -308,13 +313,13 @@ def summary(self):
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()

Expand Down Expand Up @@ -351,12 +356,12 @@ def ui_command_create(self, tag=None):

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"):
Expand Down Expand Up @@ -384,7 +389,7 @@ def ui_complete_delete(self, parameters, text, current_param):
@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 = []
Expand Down Expand Up @@ -515,7 +520,7 @@ def ui_command_enable(self):
SEE ALSO
========
B{disable status}
disable status
'''
self.assert_root()
if self.rtsnode.enable:
Expand All @@ -533,7 +538,7 @@ def ui_command_disable(self):
SEE ALSO
========
B{enable status}
enable status
'''
self.assert_root()
if self.rtsnode.enable:
Expand Down Expand Up @@ -582,18 +587,19 @@ def summary(self):

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()

Expand All @@ -614,11 +620,11 @@ def ui_command_create(self, wwn, add_mapped_luns=None):

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')
Expand Down Expand Up @@ -870,22 +876,22 @@ def summary(self):
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,
followed by the TPG LUN and the Mapped LUN.
SEE ALSO
========
B{delete}
delete
'''
self.assert_root()
try:
Expand Down Expand Up @@ -963,11 +969,11 @@ def ui_complete_create(self, parameters, text, current_param):

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:
Expand Down Expand Up @@ -1086,25 +1092,25 @@ def ui_command_create(self, storage_object, lun=None,
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()

Expand Down Expand Up @@ -1188,15 +1194,15 @@ def ui_complete_create(self, parameters, text, current_param):

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"):
Expand Down Expand Up @@ -1303,9 +1309,9 @@ def _canonicalize_ip(self, ip_address):

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
Expand All @@ -1317,7 +1323,7 @@ def ui_command_create(self, ip_address=None, ip_port=None):
SEE ALSO
========
B{delete}
delete
'''
self.assert_root()

Expand Down Expand Up @@ -1379,11 +1385,11 @@ def list_eth_ips():

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),
Expand Down
2 changes: 1 addition & 1 deletion targetcli/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@
under the License.
'''

__version__ = '2.1.fb49'
__version__ = '2.1.51'
77 changes: 77 additions & 0 deletions targetclid.8
Original file line number Diff line number Diff line change
@@ -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.kalever@redhat.com>
.br
Man page written by Prasanna Kumar Kalever <prasanna.kalever@redhat.com>
.SH REPORTING BUGS
Report bugs via <targetcli-fb-devel@lists.fedorahosted.org>
.br
or <https://github.com/open-iscsi/targetcli-fb/issues>