mirror of
https://github.com/xcat2/confluent.git
synced 2026-01-09 01:22:30 +00:00
This allows users to opt into disabling setting further profile changes. Nodes may be 'unlocked' (normal), 'autolock' (will lock on next completion), or 'locked' (unable to change the pending OS profile)
189 lines
8.2 KiB
Python
Executable File
189 lines
8.2 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 args.clear and args.profile:
|
|
sys.stderr.write(
|
|
'The -c/--clear option should not be used with a profile, '
|
|
'it is a request to not deploy any profile, and will clear '
|
|
'whatever the current profile is without being specified\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)
|
|
lockednodes = []
|
|
for lockinfo in c.read('/noderange/{0}/deployment/lock'.format(args.noderange)):
|
|
for node in lockinfo.get('databynode', {}):
|
|
lockstate = lockinfo['databynode'][node]['lock']['value']
|
|
if lockstate == 'locked':
|
|
lockednodes.append(node)
|
|
if lockednodes:
|
|
sys.stderr.write('Requested noderange has nodes with locked deployment: ' + ','.join(lockednodes))
|
|
sys.stderr.write('\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')
|
|
return 0
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main(sys.argv[1:]))
|