From 620263db3ee64486e28c8dbc2fbccb179872791f Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Wed, 23 Oct 2019 14:20:40 -0400 Subject: [PATCH] Add maxnodes argument to potentially risky commands This uses the client maxnodes check to double check. Useful for clients that want to sanity check unexpectedly large numbers of nodes. --- confluent_client/bin/nodeattrib | 4 ++++ confluent_client/bin/nodebmcreset | 2 ++ confluent_client/bin/nodeboot | 5 +++++ confluent_client/bin/nodeconfig | 6 ++++++ confluent_client/bin/nodeeventlog | 5 +++++ confluent_client/bin/nodefirmware | 5 +++++ confluent_client/bin/nodelicense | 7 ++++++- confluent_client/bin/nodepower | 7 ++++++- confluent_client/bin/noderemove | 5 +++++ confluent_client/bin/nodereseat | 6 +++++- confluent_client/bin/nodersync | 6 +++++- confluent_client/bin/noderun | 6 +++++- confluent_client/bin/nodesetboot | 6 +++++- confluent_client/bin/nodeshell | 8 ++++++-- confluent_client/bin/nodestorage | 7 +++++++ confluent_client/bin/nodesupport | 9 +++++++-- confluent_client/confluent/client.py | 12 +++++++++--- 17 files changed, 93 insertions(+), 13 deletions(-) diff --git a/confluent_client/bin/nodeattrib b/confluent_client/bin/nodeattrib index 3489bb8b..ff672115 100755 --- a/confluent_client/bin/nodeattrib +++ b/confluent_client/bin/nodeattrib @@ -50,6 +50,9 @@ argparser.add_option('-c', '--clear', action='store_true', help='Clear attributes') argparser.add_option('-p', '--prompt', action='store_true', help='Prompt for attribute values interactively') +argparser.add_option('-m', '--maxnodes', type='int', + help='Prompt if trying to set attributes on more ' + 'than specified number of nodes') (options, args) = argparser.parse_args() @@ -87,6 +90,7 @@ if len(args) > 1: if oneval != twoval: print('Values did not match.') argassign[arg] = twoval + client.stop_if_noderange_over(noderange, options.maxnodes) exitcode=client.updateattrib(session,args,nodetype, noderange, options, argassign) try: # setting user output to what the user inputs diff --git a/confluent_client/bin/nodebmcreset b/confluent_client/bin/nodebmcreset index 7a2bd018..57c15c58 100755 --- a/confluent_client/bin/nodebmcreset +++ b/confluent_client/bin/nodebmcreset @@ -32,6 +32,8 @@ if path.startswith('/opt'): import confluent.client as client argparser = optparse.OptionParser(usage="Usage: %prog ") +argparser.add_option('-m', '--maxnodes', type='int', + help='Number of nodes to affect before prompting for confirmation') (options, args) = argparser.parse_args() try: noderange = args[0] diff --git a/confluent_client/bin/nodeboot b/confluent_client/bin/nodeboot index 73c44b7b..8a7bec03 100755 --- a/confluent_client/bin/nodeboot +++ b/confluent_client/bin/nodeboot @@ -42,6 +42,10 @@ argparser.add_option('-p', '--persist', dest='persist', action='store_true', default=False, help='Request the boot device be persistent rather than ' 'one time') +argparser.add_option('-m', '--maxnodes', type='int', + help='Specify a maximum number of ' + 'nodes to boot, ' + 'prompting if over the threshold') (options, args) = argparser.parse_args() @@ -66,6 +70,7 @@ else: bootmode = 'uefi' errnodes = set([]) +session.stop_if_noderange_over(noderange, options.maxnodes) rc = session.simple_noderange_command(noderange, '/boot/nextdevice', bootdev, bootmode=bootmode, persistent=options.persist, diff --git a/confluent_client/bin/nodeconfig b/confluent_client/bin/nodeconfig index 89e65702..3a74d1f2 100755 --- a/confluent_client/bin/nodeconfig +++ b/confluent_client/bin/nodeconfig @@ -68,6 +68,10 @@ argparser.add_option('-r', '--restoredefault', default=False, help='Restore the configuration of the node ' 'to factory default for given component. ' 'Currently only uefi is supported') +argparser.add_option('-m', '--maxnodes', type='int', + help='Specify a maximum number of ' + 'nodes to configure, ' + 'prompting if over the threshold') (options, args) = argparser.parse_args() cfgpaths = { @@ -205,6 +209,7 @@ else: session = client.Command() rcode = 0 if options.restoredefault: + session.stop_if_noderange_over(noderange, options.maxnodes) if options.restoredefault.lower() in ( 'sys', 'system', 'uefi', 'bios'): for fr in session.update( @@ -225,6 +230,7 @@ if options.restoredefault: options.restoredefault)) sys.exit(1) if setmode: + session.stop_if_noderange_over(noderange, options.maxnodes) if options.exclude: sys.stderr.write('Cannot use exclude and assign at the same time\n') sys.exit(1) diff --git a/confluent_client/bin/nodeeventlog b/confluent_client/bin/nodeeventlog index 46d08604..c07b59f3 100755 --- a/confluent_client/bin/nodeeventlog +++ b/confluent_client/bin/nodeeventlog @@ -38,6 +38,10 @@ if sys.version_info[0] < 3: argparser = optparse.OptionParser( usage="Usage: %prog [options] noderange [clear]") +argparser.add_option('-m', '--maxnodes', type='int', + help='Specify a maximum number of ' + 'nodes to clear if clearing log, ' + 'prompting if over the threshold') (options, args) = argparser.parse_args() try: noderange = args[0] @@ -51,6 +55,7 @@ if len(sys.argv) > 3: sys.exit(1) if len(sys.argv) == 3: if sys.argv[2] == 'clear': + session.stop_if_noderange_over(noderange, options.maxnodes) deletemode = True else: argparser.print_help() diff --git a/confluent_client/bin/nodefirmware b/confluent_client/bin/nodefirmware index f251f9cf..b7c05b15 100755 --- a/confluent_client/bin/nodefirmware +++ b/confluent_client/bin/nodefirmware @@ -59,6 +59,10 @@ argparser = optparse.OptionParser( "%prog [list][update [--backup ]]|[]") argparser.add_option('-b', '--backup', action='store_true', help='Target a backup bank rather than primary') +argparser.add_option('-m', '--maxnodes', type='int', + help='When updating, prompt if more than the specified ' + 'number of servers will be affected') + (options, args) = argparser.parse_args() upfile = None try: @@ -95,6 +99,7 @@ def get_update_progress(session, url): def update_firmware(session, filename): global exitcode + session.stop_if_noderange_over(noderange, options.maxnodes) output = sq.ScreenPrinter(noderange, session) nodeurls = {} filename = os.path.abspath(filename) diff --git a/confluent_client/bin/nodelicense b/confluent_client/bin/nodelicense index b602e733..d574b686 100755 --- a/confluent_client/bin/nodelicense +++ b/confluent_client/bin/nodelicense @@ -37,6 +37,10 @@ exitcode = 0 argparser = optparse.OptionParser( usage="Usage: " "%prog [list][install |save |delete ]") +argparser.add_option('-m', '--maxnodes', type='int', + help='Specify a maximum number of ' + 'nodes to delete licenses from, ' + 'prompting if over the threshold') (options, args) = argparser.parse_args() upfile = None downdir = None @@ -52,7 +56,7 @@ try: delete = args[2] elif args[1] != 'list': argparser.print_help() - sys.exit(1) + sys.exit(1) except IndexError: argparser.print_help() sys.exit(1) @@ -138,6 +142,7 @@ try: elif downdir: save_licenses(session, downdir) elif delete: + session.stop_if_noderange_over(noderange, options.maxnodes) delete_license(session, delete) else: show_licenses(session) diff --git a/confluent_client/bin/nodepower b/confluent_client/bin/nodepower index 13b5e5d5..1caccdac 100755 --- a/confluent_client/bin/nodepower +++ b/confluent_client/bin/nodepower @@ -37,6 +37,11 @@ argparser = optparse.OptionParser( argparser.add_option('-p', '--showprevious', dest='previous', action='store_true', default=False, help='Show previous power state') +argparser.add_option('-m', '--maxnodes', type='int', + help='Specify a maximum number of ' + 'nodes to change power state, ' + 'prompting if over the threshold') + (options, args) = argparser.parse_args() try: noderange = args[0] @@ -72,4 +77,4 @@ if options.previous: # add dictionary to session session.add_precede_dict(prev) -sys.exit(session.simple_noderange_command(noderange, '/power/state', setstate)) +sys.exit(session.simple_noderange_command(noderange, '/power/state', setstate, promptover=options.maxnodes)) \ No newline at end of file diff --git a/confluent_client/bin/noderemove b/confluent_client/bin/noderemove index 7cde4247..f94afee0 100755 --- a/confluent_client/bin/noderemove +++ b/confluent_client/bin/noderemove @@ -35,6 +35,10 @@ import confluent.client as client argparser = optparse.OptionParser( usage='''\n %prog noderange \n ''') +argparser.add_option('-m', '--maxnodes', type='int', + help='Specify a maximum number of ' + 'nodes to delete, ' + 'prompting if over the threshold') (options, args) = argparser.parse_args() if len(args) != 1: argparser.print_help() @@ -43,6 +47,7 @@ noderange = args[0] client.check_globbing(noderange) session = client.Command() exitcode = 0 +session.stop_if_noderange_over(noderange, options.maxnodes) for r in session.delete('/noderange/{0}'.format(noderange)): if 'error' in r: sys.stderr.write(r['error'] + '\n') diff --git a/confluent_client/bin/nodereseat b/confluent_client/bin/nodereseat index 68dcc25f..72e3b848 100755 --- a/confluent_client/bin/nodereseat +++ b/confluent_client/bin/nodereseat @@ -32,6 +32,10 @@ if path.startswith('/opt'): import confluent.client as client argparser = optparse.OptionParser(usage="Usage: %prog ") +argparser.add_option('-m', '--maxnodes', type='int', + help='Specify a maximum number of ' + 'nodes to reseat, ' + 'prompting if over the threshold') (options, args) = argparser.parse_args() try: noderange = args[0] @@ -43,7 +47,7 @@ session = client.Command() exitcode = 0 errorNodes = set([]) - +session.stop_if_noderange_over(noderange, options.maxnodes) success = session.simple_noderange_command(noderange, 'power/reseat', 'reseat', key='reseat', errnodes=errorNodes) # = 0 if successful # Determine which nodes were successful and print them diff --git a/confluent_client/bin/nodersync b/confluent_client/bin/nodersync index 3f31c64f..5545ffb5 100755 --- a/confluent_client/bin/nodersync +++ b/confluent_client/bin/nodersync @@ -42,6 +42,10 @@ def run(): argparser = optparse.OptionParser( usage="Usage: %prog location noderange:location", ) + argparser.add_option('-m', '--maxnodes', type='int', + help='Specify a maximum number of ' + 'nodes to run rsync to, ' + 'prompting if over the threshold') argparser.add_option('-f', '-c', '--count', type='int', default=168, help='Number of nodes to concurrently rsync') # among other things, FD_SETSIZE limits. Besides, spawning too many @@ -64,7 +68,7 @@ def run(): pipedesc = {} pendingexecs = deque() exitcode = 0 - + c.stop_if_noderange_over(noderange, options.maxnodes) for exp in c.create('/noderange/{0}/attributes/expression'.format(noderange), {'expression': cmdstr}): if 'error' in exp: diff --git a/confluent_client/bin/noderun b/confluent_client/bin/noderun index 0ecd7626..73bbc80e 100755 --- a/confluent_client/bin/noderun +++ b/confluent_client/bin/noderun @@ -46,6 +46,10 @@ def run(): help='Number of commands to run at a time') argparser.add_option('-n', '--nonodeprefix', action='store_true', help='Do not prefix output with node names') + argparser.add_option('-m', '--maxnodes', type='int', + help='Specify a maximum number of ' + 'nodes to run the command with, ' + 'prompting if over the threshold') # among other things, FD_SETSIZE limits. Besides, spawning too many # processes can be unkind for the unaware on memory pressure and such... argparser.disable_interspersed_args() @@ -63,7 +67,7 @@ def run(): pipedesc = {} pendingexecs = deque() exitcode = 0 - + c.stop_if_noderange_over(args[0], options.maxnodes) for exp in c.create('/noderange/{0}/attributes/expression'.format(args[0]), {'expression': cmdstr}): if 'error' in exp: diff --git a/confluent_client/bin/nodesetboot b/confluent_client/bin/nodesetboot index 9963208e..cb596148 100755 --- a/confluent_client/bin/nodesetboot +++ b/confluent_client/bin/nodesetboot @@ -43,7 +43,10 @@ argparser.add_option('-p', '--persist', dest='persist', action='store_true', argparser.add_option('-u', '--uefi', dest='uefi', action='store_true', default=True, help='Request UEFI style boot (rather than BIOS)') - +argparser.add_option('-m', '--maxnodes', type='int', + help='Specify a maximum number of ' + 'nodes to modify next boot device, ' + 'prompting if over the threshold') (options, args) = argparser.parse_args() try: @@ -63,6 +66,7 @@ if options.biosmode: bootmode = 'bios' else: bootmode = 'uefi' +session.stop_if_noderange_over(noderange, options.maxnodes) sys.exit(session.simple_noderange_command(noderange, '/boot/nextdevice', bootdev, bootmode=bootmode, persistent=options.persist)) diff --git a/confluent_client/bin/nodeshell b/confluent_client/bin/nodeshell index 6d1fe138..8899a013 100755 --- a/confluent_client/bin/nodeshell +++ b/confluent_client/bin/nodeshell @@ -46,6 +46,10 @@ def run(): help='Number of commands to run at a time') argparser.add_option('-n', '--nonodeprefix', action='store_true', help='Do not prefix output with node names') + argparser.add_option('-m', '--maxnodes', type='int', + help='Specify a maximum number of ' + 'nodes to run remote ssh command to, ' + 'prompting if over the threshold') # among other things, FD_SETSIZE limits. Besides, spawning too many # processes can be unkind for the unaware on memory pressure and such... argparser.disable_interspersed_args() @@ -55,7 +59,7 @@ def run(): sys.exit(1) client.check_globbing(args[0]) concurrentprocs = options.count - c = client.Command() + c = client.Command() cmdstr = " ".join(args[1:]) currprocs = 0 @@ -64,7 +68,7 @@ def run(): pendingexecs = deque() exitcode = 0 - + c.stop_if_noderange_over(args[0], options.maxnodes) for exp in c.create('/noderange/{0}/attributes/expression'.format(args[0]), {'expression': cmdstr}): if 'error' in exp: diff --git a/confluent_client/bin/nodestorage b/confluent_client/bin/nodestorage index e1170d15..b6d536c1 100644 --- a/confluent_client/bin/nodestorage +++ b/confluent_client/bin/nodestorage @@ -108,6 +108,7 @@ def createstorage(noderange, options, args): sys.stderr.write('-r and -d are required arguments to create array\n') sys.exit(1) session = client.Command() + session.stop_if_noderange_over(noderange, options.maxnodes) names = options.name if names is None: names = ''.join(args) @@ -132,6 +133,7 @@ def deletestorage(noderange, options, args): else: names = options.name session = client.Command() + session.stop_if_noderange_over(noderange, options.maxnodes) for rsp in session.delete( '/noderange/{0}/configuration/storage/volumes/{1}'.format( noderange, names)): @@ -162,6 +164,7 @@ def setdisk(noderange, options, args): sys.stderr.write('diskset requires valid state as argument (hotspare, jbod, unconfigured)\n') sys.exit(1) session = client.Command() + session.stop_if_noderange_over(noderange, options.maxnodes) scfg = session.update('/noderange/{0}/configuration/storage/disks/{1}'.format(noderange, names), {'state': args[0]}) _print_cfg(scfg) @@ -202,6 +205,10 @@ def main(): help='Comma separated list of stripsizes to use when creating volumes. ' 'This value is in kilobytes. The default behavior is to allow the ' 'storage controller to decide.') + argparser.add_option('-m', '--maxnodes', type='int', + help='Specify a maximum number of ' + 'nodes to configure storage on, ' + 'prompting if over the threshold') (options, args) = argparser.parse_args() if len(args) == 1: args.append('show') diff --git a/confluent_client/bin/nodesupport b/confluent_client/bin/nodesupport index 135a16df..545ef85e 100644 --- a/confluent_client/bin/nodesupport +++ b/confluent_client/bin/nodesupport @@ -64,7 +64,7 @@ def printerror(res, node=None): -def download_servicedata(noderange, media): +def download_servicedata(noderange, media, options): global exitcode session = client.Command() output = sq.ScreenPrinter(noderange, session) @@ -73,6 +73,7 @@ def download_servicedata(noderange, media): upargs = {'filename': filename} noderrs = {} nodeurls = {} + session.stop_if_noderange_over(noderange, options.maxnodes) for res in session.create(resource, upargs): if 'created' not in res: for nodename in res.get('databynode', ()): @@ -121,6 +122,10 @@ def main(): 'management server (the confluent server if running remote, ' 'and the collective.manager if in collective)\n' '\n\nSee `man %prog` for more info.\n') + argparser.add_option('-m', '--maxnodes', type='int', + help='Specify a maximum number of ' + 'nodes to download diagnostic data from, ' + 'prompting if over the threshold') (options, args) = argparser.parse_args() media = None try: @@ -142,6 +147,6 @@ def main(): except KeyError: argparser.print_help() sys.exit(1) - handler(noderange, media) + handler(noderange, media, options) if __name__ == '__main__': main() diff --git a/confluent_client/confluent/client.py b/confluent_client/confluent/client.py index 7ae7b24c..f1011952 100644 --- a/confluent_client/confluent/client.py +++ b/confluent_client/confluent/client.py @@ -239,8 +239,7 @@ class Command(object): noderange, resource)): rc = self.handle_results(ikey, rc, res, errnodes) else: - if promptover is not None: - self.stop_if_noderange_over(noderange, promptover) + self.stop_if_noderange_over(noderange, promptover) kwargs[ikey] = input for res in self.update('/noderange/{0}/{1}'.format( noderange, resource), kwargs): @@ -252,9 +251,16 @@ class Command(object): return 0 def stop_if_noderange_over(self, noderange, maxnodes): + if maxnodes is None: + return nsize = self.get_noderange_size(noderange) if nsize > maxnodes: - p = input('Command is about to affect {0} nodes, continue (y/n)?'.format(nsize)) + if nsize == 1: + nodename = list(self.read( + '/noderange/{0}/nodes/'.format(noderange)))[0].get('item', {}).get('href', None) + p = input('Command is about to affect node {0}, continue (y/n)? '.format(nodename)) + else: + p = input('Command is about to affect {0} nodes, continue (y/n)? '.format(nsize)) if p.lower() != 'y': raise Exception("Aborting at user request")