mirror of
https://github.com/xcat2/confluent.git
synced 2026-09-04 20:17:58 +00:00
5d7fadf615
nodesensors and nodeconfig printed an error and exited 0, so `nodesensors n1 && next-step` ran next-step after the read it guarded had already failed. nodesensors had three of these: the per-node branch never set the exit code, the top-level branch beside it read `exitcode |= exitcode`, and a normal return from main() fell off the end of the file. nodeconfig accumulates with |= all through its read path except the last line, which assigned, so a failed bmc configuration read was discarded by the system read after it. The fixed branches now match how nodehealth and client.py spell the same thing.
336 lines
13 KiB
Python
Executable File
336 lines
13 KiB
Python
Executable File
#!/usr/bin/python3
|
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
# Copyright 2025 Lenovo
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import asyncio
|
|
import os
|
|
import signal
|
|
import optparse
|
|
import shlex
|
|
import sys
|
|
|
|
try:
|
|
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
|
except AttributeError:
|
|
pass
|
|
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.asynclient as client
|
|
|
|
class NullOpt(object):
|
|
blame = None
|
|
clear = None
|
|
|
|
|
|
def bailout(msg, code=1):
|
|
sys.stderr.write(msg + '\n')
|
|
sys.exit(code)
|
|
|
|
|
|
argparser = optparse.OptionParser(usage="Usage: %prog [options] <noderange> [setting|setting=value]")
|
|
argparser.add_option('-c', '--comparedefault', dest='comparedefault',
|
|
action='store_true', default=False,
|
|
help='Compare given settings to default or list settings '
|
|
'that are non default')
|
|
argparser.add_option('-b', '--batch', dest='batch', metavar='settings.batch',
|
|
default=False, help='Provide settings in a batch file')
|
|
argparser.add_option('-d', '--detail', dest='detail',
|
|
action='store_true', default=False,
|
|
help='Provide verbose information as available, such as '
|
|
'help text and possible valid values')
|
|
argparser.add_option('-e', '--extra', dest='extra',
|
|
action='store_true', default=False,
|
|
help='Access extra configuration. Extra configuration is generally '
|
|
'reserved for unpopular or redundant options that may be slow to '
|
|
'read. Notably the IMM category on Lenovo settings is considered '
|
|
'to be extra configuration')
|
|
argparser.add_option('-x', '--exclude', dest='exclude',
|
|
action='store_true', default=False,
|
|
help='Treat named settings as items to not '
|
|
'examine, compare, or restore default')
|
|
argparser.add_option('-a', '--advanced', dest='advanced',
|
|
action='store_true', default=False,
|
|
help='Include advanced settings, which are normally not '
|
|
'intended to be used without direction from the '
|
|
'relevant server vendor.')
|
|
argparser.add_option('-r', '--restoredefault', default=False,
|
|
dest='restoredefault', metavar="COMPONENT",
|
|
help='Restore the configuration of the node '
|
|
'to factory default for given component. '
|
|
'Currently "uefi" and "bmc" are supported components')
|
|
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 = {
|
|
'bmc.ipv4_address': (
|
|
'configuration/management_controller/net_interfaces/management',
|
|
'ipv4_address'),
|
|
'bmc.ipv4_method': (
|
|
'configuration/management_controller/net_interfaces/management',
|
|
'ipv4_configuration'),
|
|
'bmc.ipv4_gateway': (
|
|
'configuration/management_controller/net_interfaces/management',
|
|
'ipv4_gateway'),
|
|
'bmc.static_ipv6_addresses': (
|
|
'configuration/management_controller/net_interfaces/management',
|
|
'static_v6_addresses'),
|
|
'bmc.static_ipv6_gateway': (
|
|
'configuration/management_controller/net_interfaces/management',
|
|
'static_v6_gateway'),
|
|
'bmc.vlan_id': (
|
|
'configuration/management_controller/net_interfaces/management',
|
|
'vlan_id'),
|
|
'bmc.mac_address': (
|
|
'configuration/management_controller/net_interfaces/management',
|
|
'hw_addr'),
|
|
'bmc.hostname': (
|
|
'configuration/management_controller/hostname', 'hostname'),
|
|
}
|
|
|
|
autodeps = {
|
|
'bmc.ipv4_address': (('bmc.ipv4_method', 'static'),)
|
|
}
|
|
|
|
try:
|
|
noderange = args[0]
|
|
except IndexError:
|
|
argparser.print_help()
|
|
sys.exit(1)
|
|
client.check_globbing(noderange)
|
|
setmode = None
|
|
assignment = {}
|
|
queryparms = {}
|
|
printsys = []
|
|
printbmc = []
|
|
printextbmc = []
|
|
printallbmc = False
|
|
setsys = {}
|
|
forceset = False
|
|
needval = None
|
|
|
|
if len(args) == 1 or options.exclude:
|
|
if not options.exclude:
|
|
printsys = 'all'
|
|
for candidate in cfgpaths:
|
|
path, attrib = cfgpaths[candidate]
|
|
path = '/noderange/{0}/{1}'.format(noderange, path)
|
|
if path not in queryparms:
|
|
queryparms[path] = {}
|
|
queryparms[path][attrib] = candidate
|
|
|
|
|
|
def _assign_value():
|
|
if key not in cfgpaths:
|
|
setsys[key] = value
|
|
for depkey, depval in autodeps.get(key, []):
|
|
assignment[depkey] = depval
|
|
assignment[key] = value
|
|
|
|
|
|
def parse_config_line(arguments, single=False):
|
|
global setmode, printallbmc, forceset, key, value, needval, candidate, path, attrib
|
|
for pidx in range(0, len(arguments)):
|
|
param = arguments[pidx]
|
|
if param == 'show':
|
|
continue # forgive muscle memory of pasu users
|
|
if param == 'set':
|
|
setmode = True
|
|
forceset = True
|
|
continue
|
|
if needval:
|
|
key = needval
|
|
needval = None
|
|
if single:
|
|
value = ' '.join(arguments[pidx:])
|
|
_assign_value()
|
|
break
|
|
else:
|
|
value = param
|
|
_assign_value()
|
|
continue
|
|
if '=' in param or param[-1] == ':' or forceset:
|
|
if setmode is None:
|
|
setmode = True
|
|
if not setmode:
|
|
bailout('Cannot do set and query in same command: Query detected but "{0}" appears to be set'.format(param))
|
|
if '=' in param:
|
|
key, _, value = param.partition('=')
|
|
_assign_value()
|
|
elif param[-1] == ':':
|
|
needval = param[:-1]
|
|
else:
|
|
needval = param
|
|
else:
|
|
if setmode is None:
|
|
setmode = False
|
|
if setmode:
|
|
bailout('Cannot do set and query in same command: Set mode detected but "{0}" appears to be a query'.format(param))
|
|
if '.' not in param:
|
|
if param == 'bmc':
|
|
printallbmc = True
|
|
matchedparms = False
|
|
for candidate in cfgpaths:
|
|
if candidate.startswith('{0}.'.format(param)):
|
|
matchedparms = True
|
|
if not options.exclude:
|
|
path, attrib = cfgpaths[candidate]
|
|
path = '/noderange/{0}/{1}'.format(noderange, path)
|
|
if path not in queryparms:
|
|
queryparms[path] = {}
|
|
queryparms[path][attrib] = candidate
|
|
else:
|
|
try:
|
|
del queryparms[path]
|
|
except KeyError:
|
|
pass
|
|
if param.lower() == 'imm':
|
|
printextbmc.append(param)
|
|
options.extra = True
|
|
elif not matchedparms:
|
|
printsys.append(param)
|
|
elif param not in cfgpaths:
|
|
if param.startswith('bmc.'):
|
|
printbmc.append(param.replace('bmc.', ''))
|
|
elif param.lower().startswith('imm'):
|
|
options.extra = True
|
|
printextbmc.append(param)
|
|
else:
|
|
printsys.append(param)
|
|
else:
|
|
path, attrib = cfgpaths[param]
|
|
path = '/noderange/{0}/{1}'.format(noderange, path)
|
|
if path not in queryparms:
|
|
queryparms[path] = {}
|
|
queryparms[path][attrib] = param
|
|
|
|
async def main():
|
|
global printsys
|
|
if options.batch:
|
|
printsys = []
|
|
argfile = open(options.batch, 'r')
|
|
argset = argfile.readline()
|
|
while argset:
|
|
try:
|
|
argset = argset[:argset.index('#')]
|
|
except ValueError:
|
|
pass
|
|
argset = argset.strip()
|
|
if argset:
|
|
parse_config_line(shlex.split(argset), single=True)
|
|
argset = argfile.readline()
|
|
else:
|
|
parse_config_line(args[1:])
|
|
session = client.Command()
|
|
rcode = 0
|
|
if options.restoredefault:
|
|
await session.stop_if_noderange_over(noderange, options.maxnodes)
|
|
if options.restoredefault.lower() in (
|
|
'sys', 'system', 'uefi', 'bios'):
|
|
async for fr in session.update(
|
|
'/noderange/{0}/configuration/system/clear'.format(noderange),
|
|
{'clear': True}):
|
|
rcode |= client.printerror(fr)
|
|
sys.exit(rcode)
|
|
elif options.restoredefault.lower() in (
|
|
'bmc', 'imm', 'xcc'):
|
|
async for fr in session.update(
|
|
'/noderange/{0}/configuration/management_controller/clear'.format(noderange),
|
|
{'clear': True}):
|
|
rcode |= client.printerror(fr)
|
|
sys.exit(rcode)
|
|
else:
|
|
sys.stderr.write(
|
|
'Unrecognized component to restore defaults: {0}\n'.format(
|
|
options.restoredefault))
|
|
sys.exit(1)
|
|
if setmode:
|
|
await 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)
|
|
updatebypath = {}
|
|
attrnamebypath = {}
|
|
for key in assignment:
|
|
if key not in cfgpaths:
|
|
if key.startswith('bmc.'):
|
|
path = 'configuration/management_controller/extended/all'
|
|
attrib = key.replace('bmc.', '')
|
|
else:
|
|
path = 'configuration/system/all'
|
|
attrib = key
|
|
else:
|
|
path, attrib = cfgpaths[key]
|
|
if path not in updatebypath:
|
|
updatebypath[path] = {}
|
|
attrnamebypath[path] = {}
|
|
updatebypath[path][attrib] = assignment[key]
|
|
attrnamebypath[path][attrib] = key
|
|
# well, we want to expand things..
|
|
# check ipv4, if requested change method to static
|
|
for path in updatebypath:
|
|
async for fr in session.update('/noderange/{0}/{1}'.format(noderange, path),
|
|
updatebypath[path]):
|
|
rcode |= client.printerror(fr)
|
|
for node in fr.get('databynode', []):
|
|
r = fr['databynode'][node]
|
|
if 'value' not in r:
|
|
continue
|
|
keyval = r['value']
|
|
key, val = keyval.split('=')
|
|
if key in attrnamebypath[path]:
|
|
key = attrnamebypath[path][key]
|
|
print('{0}: {1}: {2}'.format(node, key, val))
|
|
else:
|
|
for path in queryparms:
|
|
if options.comparedefault:
|
|
continue
|
|
rcode |= await client.print_attrib_path(path, session, list(queryparms[path]),
|
|
NullOpt(), queryparms[path])
|
|
if printsys == 'all' or printextbmc or printbmc or printallbmc:
|
|
if printbmc or not printextbmc:
|
|
rcode |= await client.print_attrib_path(
|
|
'/noderange/{0}/configuration/management_controller/extended/all'.format(noderange),
|
|
session, printbmc, options, attrprefix='bmc.')
|
|
if options.extra:
|
|
if options.advanced:
|
|
rcode |= await client.print_attrib_path(
|
|
'/noderange/{0}/configuration/management_controller/extended/extra_advanced'.format(noderange),
|
|
session, printextbmc, options)
|
|
else:
|
|
rcode |= await client.print_attrib_path(
|
|
'/noderange/{0}/configuration/management_controller/extended/extra'.format(noderange),
|
|
session, printextbmc, options)
|
|
if printsys or options.exclude:
|
|
if printsys == 'all':
|
|
printsys = []
|
|
if (options.comparedefault or printsys == []) and not options.advanced:
|
|
path = '/noderange/{0}/configuration/system/all'.format(noderange)
|
|
else:
|
|
path = '/noderange/{0}/configuration/system/advanced'.format(
|
|
noderange)
|
|
rcode |= await client.print_attrib_path(path, session, printsys,
|
|
options)
|
|
sys.exit(rcode)
|
|
|
|
if __name__ == '__main__':
|
|
asyncio.run(main())
|