mirror of
https://github.com/xcat2/confluent.git
synced 2024-11-28 20:39:40 +00:00
9757cd1ae3
This provides a much better experience when a typo or other mistake has a profile that is not actionable.
175 lines
7.4 KiB
Python
Executable File
175 lines
7.4 KiB
Python
Executable File
#!/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 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 import 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 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.network and not args.prepareonly:
|
|
return rc
|
|
return 0
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main(sys.argv[1:]))
|