2020-05-20 20:21:08 +00:00
#!/usr/bin/python3
2020-08-25 19:47:06 +00:00
2020-05-20 20:21:08 +00:00
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
2020-08-25 19:47:06 +00:00
import confluent.sortutil as sortutil
2020-08-24 20:05:58 +00:00
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
2020-05-20 20:21:08 +00:00
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
2020-08-24 20:05:58 +00:00
2020-05-20 20:21:08 +00:00
def setpending(nr, profile, cli):
2023-01-13 17:54:21 +00:00
args = {'deployment.pendingprofile': profile, 'deployment.state': '', 'deployment.state_detail': ''}
2020-08-24 20:05:58 +00:00
if not profile.startswith('genesis-'):
args['deployment.stagedprofile'] = ''
args['deployment.profile'] = ''
2020-05-20 20:21:08 +00:00
for rsp in cli.update('/noderange/{0}/attributes/current'.format(nr),
2020-08-24 20:05:58 +00:00
args):
2020-05-20 20:21:08 +00:00
pass
2020-08-25 18:27:46 +00:00
def clearpending(nr, cli):
2020-08-24 20:05:58 +00:00
for rsp in cli.update('/noderange/{0}/attributes/current'.format(nr),
{'deployment.pendingprofile': ''}):
pass
2020-05-20 20:21:08 +00:00
def main(args):
ap = argparse.ArgumentParser(description='Deploy OS to nodes')
2020-08-25 13:58:58 +00:00
ap.add_argument('-c', '--clear', help='Clear any pending deployment action', action='store_true')
2020-07-01 18:01:28 +00:00
ap.add_argument('-n', '--network', help='Initiate deployment over PXE/HTTP', action='store_true')
2020-08-26 16:04:00 +00:00
ap.add_argument('-p', '--prepareonly', help='Prepare only, skip any interaction with a BMC associated with this deployment action', action='store_true')
2020-05-20 20:21:08 +00:00
ap.add_argument('-m', '--maxnodes', help='Specifiy a maximum nodes to be deployed')
ap.add_argument('noderange', help='Set of nodes to deploy')
2020-08-25 18:05:43 +00:00
ap.add_argument('profile', nargs='?', help='Profile name to deploy')
2020-08-25 18:29:23 +00:00
args, extra = ap.parse_known_args(args)
2020-08-25 18:27:46 +00:00
if args.profile is None and len(extra) == 1:
args.profile = extra[0]
extra = extra[1:]
2020-08-26 16:04:00 +00:00
if args.profile and not args.network:
sys.stderr.write('-n is a required argument currently to perform an install, optionally with -p\n')
2020-08-25 19:47:06 +00:00
return 1
2020-08-26 20:10:59 +00:00
if not args.profile and args.network:
2021-01-13 21:43:41 +00:00
sys.stderr.write('Both noderange and a profile name are required arguments to request a network deployment\n')
2020-08-26 20:10:59 +00:00
return 1
2020-08-25 18:27:46 +00:00
if extra:
sys.stderr.write('Unrecognized arguments: ' + repr(extra) + '\n')
2020-05-20 20:21:08 +00:00
c = client.Command()
c.stop_if_noderange_over(args.noderange, args.maxnodes)
2022-03-16 18:57:46 +00:00
errnodes = set([])
2022-12-01 19:09:25 +00:00
for rsp in c.read('/noderange/{0}/nodes/'.format(args.noderange)):
if 'error' in rsp:
sys.stderr.write(rsp['error'] + '\n')
sys.exit(1)
2022-03-16 18:57:46 +00:00
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')
2020-08-24 20:05:58 +00:00
if args.clear:
cleararm(args.noderange, c)
clearpending(args.noderange, c)
2020-08-25 19:47:06 +00:00
elif args.profile:
2021-08-09 13:49:10 +00:00
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)
2020-08-24 20:05:58 +00:00
armonce(args.noderange, c)
setpending(args.noderange, args.profile, c)
2020-08-25 19:47:06 +00:00
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]:
2023-01-13 18:02:17 +00:00
if attr in ('deployment.pendingprofile', 'deployment.apiarmed', 'deployment.stagedprofile', 'deployment.profile', 'deployment.state', 'deployment.state_detail'):
2020-08-25 19:47:06 +00:00
databynode[node][attr] = dbn[node][attr].get('value', '')
for node in sortutil.natural_sort(databynode):
profile = databynode[node].get('deployment.pendingprofile', '')
2020-08-25 20:01:50 +00:00
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'
2020-08-25 19:47:06 +00:00
armed = databynode[node].get('deployment.apiarmed', '')
if armed in ('once', 'continuous'):
armed = ' (node authentication armed)'
else:
armed = ''
2023-01-13 18:02:17 +00:00
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))
2020-08-25 19:47:06 +00:00
sys.exit(0)
2020-08-26 16:04:00 +00:00
if args.network and not args.prepareonly:
2020-07-01 18:01:28 +00:00
return rc
2020-05-20 20:21:08 +00:00
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))