Implement shell mode scripts (multiple) and multiple commands, rearrange help
This commit is contained in:
parent
aa2fac97f5
commit
d241ce7607
121
timmy/cli.py
121
timmy/cli.py
@ -41,7 +41,7 @@ def main(argv=None):
|
|||||||
|
|
||||||
parser = argparse.ArgumentParser(description=('Parallel remote command'
|
parser = argparse.ArgumentParser(description=('Parallel remote command'
|
||||||
' execution and file'
|
' execution and file'
|
||||||
' collection tool'))
|
' manipulation tool'))
|
||||||
parser.add_argument('-c', '--conf',
|
parser.add_argument('-c', '--conf',
|
||||||
help='Path to YAML a configuration file.')
|
help='Path to YAML a configuration file.')
|
||||||
parser.add_argument('-j', '--nodes-json',
|
parser.add_argument('-j', '--nodes-json',
|
||||||
@ -49,49 +49,52 @@ def main(argv=None):
|
|||||||
' "fuel node --json". Useful to speed up'
|
' "fuel node --json". Useful to speed up'
|
||||||
' initialization, skips "fuel node" call.'))
|
' initialization, skips "fuel node" call.'))
|
||||||
parser.add_argument('-o', '--dest-file',
|
parser.add_argument('-o', '--dest-file',
|
||||||
help='Path to an output archive file.')
|
help=('Output filename for the archive in tar.gz'
|
||||||
parser.add_argument('-x', '--extended', action='store_true',
|
' format for command outputs and collected'
|
||||||
help='Execute extended commands.')
|
' files. Overrides "archives" config option.'))
|
||||||
|
parser.add_argument('--log-file', default=None,
|
||||||
|
help='Redirect Timmy log to a file.')
|
||||||
parser.add_argument('-e', '--env', type=int,
|
parser.add_argument('-e', '--env', type=int,
|
||||||
help='Env ID. Run only on specific environment.')
|
help='Env ID. Run only on specific environment.')
|
||||||
parser.add_argument('-m', '--maxthreads', type=int, default=100,
|
parser.add_argument('-R', '--role', action='append',
|
||||||
help=('Maximum simultaneous nodes for command'
|
help=('Can be specified multiple times.'
|
||||||
'execution.'))
|
' Run only on the specified role.'))
|
||||||
parser.add_argument('-l', '--logs',
|
|
||||||
help=('Collect logs from nodes. Logs are not collected'
|
|
||||||
' by default due to their size.'),
|
|
||||||
action='store_true', dest='getlogs')
|
|
||||||
parser.add_argument('-L', '--logs-maxthreads', type=int, default=100,
|
|
||||||
help='Maximum simultaneous nodes for log collection.')
|
|
||||||
parser.add_argument('--only-logs',
|
|
||||||
action='store_true',
|
|
||||||
help='Only collect logs, do not run commands.')
|
|
||||||
parser.add_argument('--log-file', default=None,
|
|
||||||
help='Output file for Timmy log.')
|
|
||||||
parser.add_argument('--fake-logs',
|
|
||||||
help='Do not collect logs, only calculate size.',
|
|
||||||
action='store_true')
|
|
||||||
parser.add_argument('-w', '--warning',
|
|
||||||
help='Sets log level to warning (default).',
|
|
||||||
action='store_true')
|
|
||||||
parser.add_argument('-v', '--verbose',
|
|
||||||
help='Be verbose.',
|
|
||||||
action='store_true')
|
|
||||||
parser.add_argument('-d', '--debug',
|
|
||||||
help='Be extremely verbose.',
|
|
||||||
action='store_true')
|
|
||||||
parser.add_argument('-C', '--command',
|
|
||||||
help=('Enables shell mode. Shell command to'
|
|
||||||
' execute. For help on shell mode, read'
|
|
||||||
' timmy/conf.py'))
|
|
||||||
parser.add_argument('-G', '--get', action='append',
|
parser.add_argument('-G', '--get', action='append',
|
||||||
help=('Enables shell mode. Can be specified multiple'
|
help=('Enables shell mode. Can be specified multiple'
|
||||||
' times. Filemask to collect via "scp -r".'
|
' times. Filemask to collect via "scp -r".'
|
||||||
' Result is placed into a folder specified'
|
' Result is placed into a folder specified'
|
||||||
' by "outdir" config option.'))
|
' by "outdir" config option.'
|
||||||
parser.add_argument('-R', '--role', action='append',
|
' For help on shell mode, read timmy/conf.py.'))
|
||||||
help=('Can be specified multiple times.'
|
parser.add_argument('-C', '--command', action='append',
|
||||||
' Run only on the specified role.'))
|
help=('Enables shell mode. Can be specified'
|
||||||
|
' multiple times. Shell command to execute.'
|
||||||
|
' For help on shell mode, read timmy/conf.py.'))
|
||||||
|
parser.add_argument('-S', '--script', action='append',
|
||||||
|
help=('Enables shell mode. Can be specified'
|
||||||
|
' multiple times. Bash script name to execute.'
|
||||||
|
' Script must be placed in "%s" folder inside a'
|
||||||
|
' path specified by "rqdir" configuration'
|
||||||
|
' parameter.' % Node.skey) +
|
||||||
|
' For help on shell mode, read timmy/conf.py.')
|
||||||
|
parser.add_argument('-P', '--put', nargs=2, action='append',
|
||||||
|
help=('Enables shell mode. Can be specified multiple'
|
||||||
|
' times. Upload filemask via"scp -r" to node(s).'
|
||||||
|
' Each argument must contain two strings -'
|
||||||
|
' source file/path/mask and dest. file/path.'
|
||||||
|
' For help on shell mode, read timmy/conf.py.'))
|
||||||
|
parser.add_argument('-l', '--logs',
|
||||||
|
help=('Collect logs from nodes. Logs are not collected'
|
||||||
|
' by default due to their size.'),
|
||||||
|
action='store_true', dest='getlogs')
|
||||||
|
parser.add_argument('--only-logs',
|
||||||
|
action='store_true',
|
||||||
|
help=('Only collect logs, do not run commands or'
|
||||||
|
' collect files.'))
|
||||||
|
parser.add_argument('--fake-logs',
|
||||||
|
help='Do not collect logs, only calculate size.',
|
||||||
|
action='store_true')
|
||||||
|
parser.add_argument('-x', '--extended', action='store_true',
|
||||||
|
help='Execute extended commands.')
|
||||||
parser.add_argument('--no-archive',
|
parser.add_argument('--no-archive',
|
||||||
help=('Do not create results archive. By default,'
|
help=('Do not create results archive. By default,'
|
||||||
' an archive with all outputs and files'
|
' an archive with all outputs and files'
|
||||||
@ -101,16 +104,25 @@ def main(argv=None):
|
|||||||
help=('Do not clean previous results. Allows'
|
help=('Do not clean previous results. Allows'
|
||||||
' accumulating results across runs.'),
|
' accumulating results across runs.'),
|
||||||
action='store_true')
|
action='store_true')
|
||||||
parser.add_argument('-P', '--put', nargs=2, action='append',
|
|
||||||
help=('Enables shell mode. Upload filemask via'
|
|
||||||
' "scp -r" to node(s). Each argument must'
|
|
||||||
'contain two strings - source file/path/mask'
|
|
||||||
' and destination.'))
|
|
||||||
parser.add_argument('-q', '--quiet',
|
parser.add_argument('-q', '--quiet',
|
||||||
help=('Print only command execution results and log'
|
help=('Print only command execution results and log'
|
||||||
' messages. Good for quick runs / "watch" wrap.'
|
' messages. Good for quick runs / "watch" wrap.'
|
||||||
' Also sets default loglevel to ERROR.'),
|
' Also sets default loglevel to ERROR.'),
|
||||||
action='store_true')
|
action='store_true')
|
||||||
|
parser.add_argument('-m', '--maxthreads', type=int, default=100,
|
||||||
|
help=('Maximum simultaneous nodes for command'
|
||||||
|
'execution.'))
|
||||||
|
parser.add_argument('-L', '--logs-maxthreads', type=int, default=100,
|
||||||
|
help='Maximum simultaneous nodes for log collection.')
|
||||||
|
parser.add_argument('-w', '--warning',
|
||||||
|
help='Sets log level to warning (default).',
|
||||||
|
action='store_true')
|
||||||
|
parser.add_argument('-v', '--verbose',
|
||||||
|
help='Be verbose.',
|
||||||
|
action='store_true')
|
||||||
|
parser.add_argument('-d', '--debug',
|
||||||
|
help='Be extremely verbose.',
|
||||||
|
action='store_true')
|
||||||
args = parser.parse_args(argv[1:])
|
args = parser.parse_args(argv[1:])
|
||||||
if args.quiet and not args.warning:
|
if args.quiet and not args.warning:
|
||||||
loglevel = logging.ERROR
|
loglevel = logging.ERROR
|
||||||
@ -124,7 +136,7 @@ def main(argv=None):
|
|||||||
level=loglevel,
|
level=loglevel,
|
||||||
format='%(asctime)s %(levelname)s %(message)s')
|
format='%(asctime)s %(levelname)s %(message)s')
|
||||||
conf = load_conf(args.conf)
|
conf = load_conf(args.conf)
|
||||||
if args.command or args.get or args.put:
|
if args.put or args.command or args.script or args.get:
|
||||||
conf['shell_mode'] = True
|
conf['shell_mode'] = True
|
||||||
if args.no_clean:
|
if args.no_clean:
|
||||||
conf['clean'] = False
|
conf['clean'] = False
|
||||||
@ -139,7 +151,15 @@ def main(argv=None):
|
|||||||
if args.put:
|
if args.put:
|
||||||
conf[Node.pkey] = args.put
|
conf[Node.pkey] = args.put
|
||||||
if args.command:
|
if args.command:
|
||||||
conf[Node.ckey] = [{'stdout': args.command}]
|
i = 0
|
||||||
|
pad = str(len(str(len(args.command))))
|
||||||
|
template = 'timmy_shell_mode_cmd_%0' + pad + 'd'
|
||||||
|
for c in args.command:
|
||||||
|
cmdname = template % i
|
||||||
|
conf[Node.ckey].append({cmdname: c})
|
||||||
|
i += 1
|
||||||
|
if args.script:
|
||||||
|
conf[Node.skey] = args.script
|
||||||
if args.get:
|
if args.get:
|
||||||
conf[Node.fkey] = args.get
|
conf[Node.fkey] = args.get
|
||||||
else:
|
else:
|
||||||
@ -191,15 +211,12 @@ def main(argv=None):
|
|||||||
print('Run complete. Node information:')
|
print('Run complete. Node information:')
|
||||||
print(nm)
|
print(nm)
|
||||||
if conf['shell_mode']:
|
if conf['shell_mode']:
|
||||||
if args.command:
|
if args.command or args.script:
|
||||||
if not args.quiet:
|
if not args.quiet:
|
||||||
print('Results:')
|
print('Results:')
|
||||||
for node in nm.nodes.values():
|
for node in nm.sorted_nodes():
|
||||||
for cmd, path in node.mapcmds.items():
|
node.print_results(node.mapcmds)
|
||||||
with open(path, 'r') as f:
|
node.print_results(node.mapscr)
|
||||||
for line in f.readlines():
|
|
||||||
print('node-%s:\t%s' %
|
|
||||||
(node.id, line.rstrip('\n')))
|
|
||||||
if nm.has(Node.fkey, Node.flkey) and not args.quiet:
|
if nm.has(Node.fkey, Node.flkey) and not args.quiet:
|
||||||
print('Outputs and files available in "%s".' % conf['outdir'])
|
print('Outputs and files available in "%s".' % conf['outdir'])
|
||||||
if all([not args.no_archive, nm.has(*Node.conf_archive_general),
|
if all([not args.no_archive, nm.has(*Node.conf_archive_general),
|
||||||
|
@ -147,6 +147,7 @@ class Node(object):
|
|||||||
ddir = os.path.join(odir, Node.ckey, cl, sn)
|
ddir = os.path.join(odir, Node.ckey, cl, sn)
|
||||||
if self.cmds:
|
if self.cmds:
|
||||||
tools.mdir(ddir)
|
tools.mdir(ddir)
|
||||||
|
self.cmds = sorted(self.cmds)
|
||||||
for c in self.cmds:
|
for c in self.cmds:
|
||||||
for cmd in c:
|
for cmd in c:
|
||||||
dfile = os.path.join(ddir, 'node-%s-%s-%s' %
|
dfile = os.path.join(ddir, 'node-%s-%s-%s' %
|
||||||
@ -169,13 +170,14 @@ class Node(object):
|
|||||||
ddir = os.path.join(odir, Node.skey, cl, sn)
|
ddir = os.path.join(odir, Node.skey, cl, sn)
|
||||||
if self.scripts:
|
if self.scripts:
|
||||||
tools.mdir(ddir)
|
tools.mdir(ddir)
|
||||||
|
self.scripts = sorted(self.scripts)
|
||||||
for scr in self.scripts:
|
for scr in self.scripts:
|
||||||
f = os.path.join(self.rqdir, Node.skey, scr)
|
f = os.path.join(self.rqdir, Node.skey, scr)
|
||||||
logging.info('node:%s(%s), exec: %s' % (self.id, self.ip, f))
|
logging.info('node:%s(%s), exec: %s' % (self.id, self.ip, f))
|
||||||
dfile = os.path.join(ddir, 'node-%s-%s-%s' %
|
dfile = os.path.join(ddir, 'node-%s-%s-%s' %
|
||||||
(self.id, self.ip, os.path.basename(f)))
|
(self.id, self.ip, os.path.basename(f)))
|
||||||
logging.info('outfile: %s' % dfile)
|
logging.info('outfile: %s' % dfile)
|
||||||
self.mapscr[os.path.basename(f)] = dfile
|
self.mapscr[scr] = dfile
|
||||||
if not fake:
|
if not fake:
|
||||||
outs, errs, code = tools.ssh_node(ip=self.ip,
|
outs, errs, code = tools.ssh_node(ip=self.ip,
|
||||||
filename=f,
|
filename=f,
|
||||||
@ -302,6 +304,14 @@ class Node(object):
|
|||||||
" node: %s, ip: %s, cmd: %s" %
|
" node: %s, ip: %s, cmd: %s" %
|
||||||
(func_name, code, self.id, self.ip, cmd))
|
(func_name, code, self.id, self.ip, cmd))
|
||||||
|
|
||||||
|
def print_results(self, result_map):
|
||||||
|
# result_map should be either mapcmds or mapscr
|
||||||
|
for cmd in sorted(result_map):
|
||||||
|
with open(result_map[cmd], 'r') as f:
|
||||||
|
for line in f.readlines():
|
||||||
|
print('node-%s:\t%s' %
|
||||||
|
(self.id, line.rstrip('\n')))
|
||||||
|
|
||||||
|
|
||||||
class NodeManager(object):
|
class NodeManager(object):
|
||||||
"""Class nodes """
|
"""Class nodes """
|
||||||
|
Loading…
x
Reference in New Issue
Block a user