#!/usr/bin/python3 import argparse import os import sys path = os.path.dirname(os.path.realpath(__file__)) path = os.path.realpath(os.path.join(path, '..', 'lib', 'python')) if path.startswith('/opt'): sys.path.append(path) import confluent.client as client import confluent.sortutil as sortutil def cleararm(nr, cli): nodes = set([]) for rsp in cli.read('/noderange/{0}/attributes/current'.format(nr)): for node in rsp.get('databynode', {}): nodeinfo = rsp['databynode'][node] for attr in nodeinfo: if attr == 'deployment.apiarmed': curr = nodeinfo[attr].get('value', '') if curr == 'continuous': nodes.add(node) noderange = nr if nodes: noderange += ',-({0})'.format(','.join(nodes)) for rsp in cli.update('/noderange/{0}/attributes/current'.format(noderange), {'deployment.apiarmed': ''}): pass def armonce(nr, cli): nodes = set([]) for rsp in cli.read('/noderange/{0}/attributes/current'.format(nr)): for node in rsp.get('databynode', {}): nodeinfo = rsp['databynode'][node] for attr in nodeinfo: if attr == 'deployment.apiarmed': curr = nodeinfo[attr].get('value', '') if curr == 'continuous': nodes.add(node) noderange = nr if nodes: noderange += ',-({0})'.format(','.join(nodes)) for rsp in cli.update('/noderange/{0}/attributes/current'.format(noderange), {'deployment.apiarmed': 'once'}): pass def setpending(nr, profile, cli): args = {'deployment.pendingprofile': profile, 'deployment.state': '', 'deployment.state_detail': ''} if not profile.startswith('genesis-'): args['deployment.stagedprofile'] = '' args['deployment.profile'] = '' for rsp in cli.update('/noderange/{0}/attributes/current'.format(nr), args): pass def clearpending(nr, cli): for rsp in cli.update('/noderange/{0}/attributes/current'.format(nr), {'deployment.pendingprofile': ''}): pass def main(args): ap = argparse.ArgumentParser(description='Deploy OS to nodes') ap.add_argument('-c', '--clear', help='Clear any pending deployment action', action='store_true') ap.add_argument('-n', '--network', help='Initiate deployment over PXE/HTTP', action='store_true') ap.add_argument('-p', '--prepareonly', help='Prepare only, skip any interaction with a BMC associated with this deployment action', action='store_true') ap.add_argument('-m', '--maxnodes', help='Specifiy a maximum nodes to be deployed') ap.add_argument('noderange', help='Set of nodes to deploy') ap.add_argument('profile', nargs='?', help='Profile name to deploy') args, extra = ap.parse_known_args(args) if args.profile is None and len(extra) == 1: args.profile = extra[0] extra = extra[1:] if args.profile and not args.network: sys.stderr.write('-n is a required argument currently to perform an install, optionally with -p\n') return 1 if not args.profile and args.network: sys.stderr.write('Both noderange and a profile name are required arguments to request a network deployment\n') return 1 if extra: sys.stderr.write('Unrecognized arguments: ' + repr(extra) + '\n') c = client.Command() c.stop_if_noderange_over(args.noderange, args.maxnodes) errnodes = set([]) for rsp in c.read('/noderange/{0}/nodes/'.format(args.noderange)): if 'error' in rsp: sys.stderr.write(rsp['error'] + '\n') sys.exit(1) if not args.clear and args.network and not args.prepareonly: rc = c.simple_noderange_command(args.noderange, '/boot/nextdevice', 'network', bootmode='uefi', persistent=False, errnodes=errnodes) if errnodes: sys.stderr.write( 'Unable to set boot device for following nodes: {0}\n'.format( ','.join(errnodes))) return 1 rc |= c.simple_noderange_command(args.noderange, '/power/state', 'boot') if args.clear: cleararm(args.noderange, c) clearpending(args.noderange, c) elif args.profile: profnames = [] for prof in c.read('/deployment/profiles/'): profname = prof.get('item', {}).get('href', None) if profname: profname = profname.replace('/', '') profnames.append(profname) if profname == args.profile: break else: sys.stderr.write('The specified profile "{}" is not an available profile\n'.format(args.profile)) if profnames: sys.stderr.write('The following profiles are available:\n') for profname in profnames: sys.stderr.write(' ' + profname + '\n') else: sys.stderr.write('No deployment profiles available, try osdeploy fiimport or imgutil capture\n') sys.exit(1) armonce(args.noderange, c) setpending(args.noderange, args.profile, c) else: databynode = {} for r in c.read('/noderange/{0}/attributes/current'.format(args.noderange)): dbn = r.get('databynode', {}) for node in dbn: if node not in databynode: databynode[node] = {} for attr in dbn[node]: if attr in ('deployment.pendingprofile', 'deployment.apiarmed', 'deployment.stagedprofile', 'deployment.profile', 'deployment.state', 'deployment.state_detail'): databynode[node][attr] = dbn[node][attr].get('value', '') for node in sortutil.natural_sort(databynode): profile = databynode[node].get('deployment.pendingprofile', '') if profile: profile = 'pending: {}'.format(profile) else: profile = databynode[node].get('deployment.stagedprofile', '') if profile: profile = 'staged: {}'.format(profile) else: profile = databynode[node].get('deployment.profile', '') if profile: profile = 'completed: {}'.format(profile) else: profile= 'No profile pending or applied' armed = databynode[node].get('deployment.apiarmed', '') if armed in ('once', 'continuous'): armed = ' (node authentication armed)' else: armed = '' stateinfo = '' deploymentstate = databynode[node].get('deployment.state', '') if deploymentstate: statedetails = databynode[node].get('deployment.state_detail', '') if statedetails: stateinfo = '{}: {}'.format(deploymentstate, statedetails) else: stateinfo = deploymentstate if stateinfo: print('{0}: {1} ({2})'.format(node, profile, stateinfo)) else: print('{0}: {1}{2}'.format(node, profile, armed)) sys.exit(0) if args.network and not args.prepareonly: return rc return 0 if __name__ == '__main__': sys.exit(main(sys.argv[1:]))