mirror of
https://github.com/xcat2/confluent.git
synced 2026-09-29 16:50:57 +00:00
Compare commits
93 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 18b6398c64 | |||
| 47b68e4258 | |||
| 79b6d099ab | |||
| f1e1d9804a | |||
| 546296ce71 | |||
| 954b2dd15c | |||
| a7b11d1e15 | |||
| 3660cf18cc | |||
| 078afaf7d7 | |||
| 3ab1eb7f01 | |||
| fbfdc9322b | |||
| ef68656bde | |||
| 0e04b853f6 | |||
| 63256e37b6 | |||
| a1b81e978b | |||
| 06c53e9d89 | |||
| ab0168b2ec | |||
| 473bb39c54 | |||
| a48ecd8c78 | |||
| 30398fc434 | |||
| 0ed7e4eac3 | |||
| 0d2999c7a8 | |||
| 75010ac6f0 | |||
| a748cc3032 | |||
| 28bb143880 | |||
| 4b6899d4af | |||
| a175fd7345 | |||
| 12bd473c0d | |||
| a967c224fe | |||
| 72a5f37232 | |||
| 5c081a8e2c | |||
| c0b69f64b8 | |||
| f7fe38f498 | |||
| 5ae949b4eb | |||
| cec6918c1a | |||
| 3df7a36c48 | |||
| 559e96f7ff | |||
| 6f568919e1 | |||
| c441739f68 | |||
| a43361bb98 | |||
| 620263db3e | |||
| dd096104cc | |||
| 191ae762ab | |||
| f866b7379c | |||
| aab6160d49 | |||
| e1dffe7c3a | |||
| 97ca6dc48e | |||
| 1d59e1da8c | |||
| 7e6b4fa4d0 | |||
| 186e89cd87 | |||
| 73d6511d9e | |||
| 065ee48325 | |||
| 4a35cc25f0 | |||
| ceced11ae8 | |||
| 62084b3605 | |||
| a84b88e269 | |||
| fc626d36ba | |||
| 606a308046 | |||
| bed0f76dba | |||
| 8c4d04a7cd | |||
| bfbde0f7f3 | |||
| ba96aea2a6 | |||
| d9f1d6c033 | |||
| 1dac61adca | |||
| e008932389 | |||
| b81e5fb3ce | |||
| e2a08e7e73 | |||
| eb48728bc1 | |||
| c6255fa13b | |||
| 2a37d64dc9 | |||
| 8101672c3a | |||
| 453c344f7f | |||
| 9eceda0636 | |||
| f28b91499e | |||
| 8cab591a8b | |||
| 6ce6740b77 | |||
| 8a4a219a14 | |||
| a39d45d03c | |||
| bc85d93cf4 | |||
| 1e963106fe | |||
| a9f0e345db | |||
| 5353b479d9 | |||
| dbc6747c38 | |||
| 0edd1efe0d | |||
| 8e87f5b9e5 | |||
| 0633b2ca67 | |||
| c1953bdad3 | |||
| 578ba06aa3 | |||
| 0975881d3b | |||
| 8fc3b7c9c0 | |||
| 6ea6ebd80e | |||
| 744f8899f7 | |||
| 74f18d5571 |
@@ -89,6 +89,10 @@ except NameError:
|
||||
netserver = None
|
||||
laststate = {}
|
||||
|
||||
try:
|
||||
input = raw_input
|
||||
except NameError:
|
||||
pass
|
||||
|
||||
class BailOut(Exception):
|
||||
def __init__(self, errorcode=0):
|
||||
@@ -195,9 +199,9 @@ def prompt():
|
||||
if os.environ.get('TERM', '') not in ('linux'):
|
||||
sys.stdout.write('\x1b]0;confetty: %s\x07' % target)
|
||||
try:
|
||||
return raw_input(target + ' -> ')
|
||||
return input(target + ' -> ')
|
||||
except KeyboardInterrupt:
|
||||
print ""
|
||||
print("")
|
||||
return ""
|
||||
except EOFError: # ctrl-d
|
||||
print("exit")
|
||||
@@ -296,7 +300,7 @@ currchildren = None
|
||||
|
||||
def print_result(res):
|
||||
if 'errorcode' in res or 'error' in res:
|
||||
print res['error']
|
||||
print(res['error'])
|
||||
return
|
||||
if 'databynode' in res:
|
||||
print_result(res['databynode'])
|
||||
@@ -309,9 +313,9 @@ def print_result(res):
|
||||
attrstr = '%s=%s' % (key, recurse_format(res[key]))
|
||||
elif not isinstance(res[key], dict):
|
||||
try:
|
||||
print '{0}: {1}'.format(key, res[key])
|
||||
print('{0}: {1}'.format(key, res[key]))
|
||||
except UnicodeEncodeError:
|
||||
print '{0}: {1}'.format(key, repr(res[key]))
|
||||
print('{0}: {1}'.format(key, repr(res[key])))
|
||||
continue
|
||||
elif 'value' in res[key] and res[key]['value'] is not None:
|
||||
attrstr = '%s="%s"' % (key, res[key]['value'])
|
||||
@@ -324,7 +328,7 @@ def print_result(res):
|
||||
else:
|
||||
sys.stdout.write('{0}: '.format(key))
|
||||
if isinstance(res[key], str) or isinstance(res[key], unicode):
|
||||
print res[key]
|
||||
print(res[key])
|
||||
else:
|
||||
print_result(res[key])
|
||||
continue
|
||||
@@ -423,10 +427,10 @@ def do_command(command, server):
|
||||
for res in session.read(targpath):
|
||||
if 'item' in res: # a link relation
|
||||
if type(res['item']) == dict:
|
||||
print res['item']["href"]
|
||||
print(res['item']["href"])
|
||||
else:
|
||||
for item in res['item']:
|
||||
print item["href"]
|
||||
print(item["href"])
|
||||
else: # generic attributes to list
|
||||
if 'error' in res:
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
@@ -851,7 +855,7 @@ def server_connect():
|
||||
passphrase = os.environ['CONFLUENT_PASSPHRASE']
|
||||
session.authenticate(username, passphrase)
|
||||
while not session.authenticated:
|
||||
username = raw_input("Name: ")
|
||||
username = input("Name: ")
|
||||
passphrase = getpass.getpass("Passphrase: ")
|
||||
session.authenticate(username, passphrase)
|
||||
|
||||
@@ -871,7 +875,7 @@ def main():
|
||||
global inconsole
|
||||
try:
|
||||
server_connect()
|
||||
except EOFError, KeyboardInterrupt:
|
||||
except (EOFError, KeyboardInterrupt) as _:
|
||||
raise BailOut(0)
|
||||
except socket.gaierror:
|
||||
sys.stderr.write('Could not connect to confluent\n')
|
||||
@@ -929,6 +933,7 @@ def main():
|
||||
updatestatus(data)
|
||||
continue
|
||||
if data is not None:
|
||||
data = client.stringify(data)
|
||||
if clearpowermessage:
|
||||
sys.stdout.write("\x1b[2J\x1b[;H")
|
||||
clearpowermessage = False
|
||||
|
||||
@@ -50,6 +50,9 @@ argparser.add_option('-c', '--clear', action='store_true',
|
||||
help='Clear attributes')
|
||||
argparser.add_option('-p', '--prompt', action='store_true',
|
||||
help='Prompt for attribute values interactively')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Prompt if trying to set attributes on more '
|
||||
'than specified number of nodes')
|
||||
(options, args) = argparser.parse_args()
|
||||
|
||||
|
||||
@@ -87,6 +90,7 @@ if len(args) > 1:
|
||||
if oneval != twoval:
|
||||
print('Values did not match.')
|
||||
argassign[arg] = twoval
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
exitcode=client.updateattrib(session,args,nodetype, noderange, options, argassign)
|
||||
try:
|
||||
# setting user output to what the user inputs
|
||||
|
||||
@@ -32,6 +32,8 @@ if path.startswith('/opt'):
|
||||
import confluent.client as client
|
||||
|
||||
argparser = optparse.OptionParser(usage="Usage: %prog <noderange>")
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Number of nodes to affect before prompting for confirmation')
|
||||
(options, args) = argparser.parse_args()
|
||||
try:
|
||||
noderange = args[0]
|
||||
@@ -43,7 +45,7 @@ session = client.Command()
|
||||
exitcode = 0
|
||||
|
||||
errorNodes = set([])
|
||||
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
success = session.simple_noderange_command(noderange, 'configuration/management_controller/reset', 'reset', key='state', errnodes=errorNodes) # = 0 if successful
|
||||
|
||||
# Determine which nodes were successful and print them
|
||||
@@ -56,7 +58,7 @@ for node in session.read('/noderange/{0}/nodes/'.format(noderange)):
|
||||
goodNodes = allNodes - errorNodes
|
||||
|
||||
for node in goodNodes:
|
||||
print node + ": BMC Reset Successful"
|
||||
print(node + ": BMC Reset Successful")
|
||||
|
||||
|
||||
sys.exit(success)
|
||||
|
||||
@@ -42,6 +42,10 @@ argparser.add_option('-p', '--persist', dest='persist', action='store_true',
|
||||
default=False,
|
||||
help='Request the boot device be persistent rather than '
|
||||
'one time')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to boot, '
|
||||
'prompting if over the threshold')
|
||||
|
||||
(options, args) = argparser.parse_args()
|
||||
|
||||
@@ -66,6 +70,7 @@ else:
|
||||
bootmode = 'uefi'
|
||||
|
||||
errnodes = set([])
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
rc = session.simple_noderange_command(noderange, '/boot/nextdevice', bootdev,
|
||||
bootmode=bootmode,
|
||||
persistent=options.persist,
|
||||
|
||||
@@ -68,6 +68,10 @@ argparser.add_option('-r', '--restoredefault', default=False,
|
||||
help='Restore the configuration of the node '
|
||||
'to factory default for given component. '
|
||||
'Currently only uefi is supported')
|
||||
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 = {
|
||||
@@ -205,6 +209,7 @@ else:
|
||||
session = client.Command()
|
||||
rcode = 0
|
||||
if options.restoredefault:
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
if options.restoredefault.lower() in (
|
||||
'sys', 'system', 'uefi', 'bios'):
|
||||
for fr in session.update(
|
||||
@@ -225,6 +230,7 @@ if options.restoredefault:
|
||||
options.restoredefault))
|
||||
sys.exit(1)
|
||||
if setmode:
|
||||
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)
|
||||
|
||||
@@ -38,6 +38,10 @@ if sys.version_info[0] < 3:
|
||||
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: %prog [options] noderange [clear]")
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to clear if clearing log, '
|
||||
'prompting if over the threshold')
|
||||
(options, args) = argparser.parse_args()
|
||||
try:
|
||||
noderange = args[0]
|
||||
@@ -46,11 +50,11 @@ except IndexError:
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
deletemode = False
|
||||
if len(sys.argv) > 3:
|
||||
if len(args) > 2:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
if len(sys.argv) == 3:
|
||||
if sys.argv[2] == 'clear':
|
||||
if len(args) == 2:
|
||||
if args[1] == 'clear':
|
||||
deletemode = True
|
||||
else:
|
||||
argparser.print_help()
|
||||
@@ -88,6 +92,7 @@ def format_event(evt):
|
||||
|
||||
if deletemode:
|
||||
func = session.delete
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
else:
|
||||
func = session.read
|
||||
for rsp in func('/noderange/{0}/events/hardware/log'.format(noderange)):
|
||||
|
||||
@@ -59,6 +59,10 @@ argparser = optparse.OptionParser(
|
||||
"%prog <noderange> [list][update [--backup <file>]]|[<components>]")
|
||||
argparser.add_option('-b', '--backup', action='store_true',
|
||||
help='Target a backup bank rather than primary')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='When updating, prompt if more than the specified '
|
||||
'number of servers will be affected')
|
||||
|
||||
(options, args) = argparser.parse_args()
|
||||
upfile = None
|
||||
try:
|
||||
@@ -95,6 +99,7 @@ def get_update_progress(session, url):
|
||||
|
||||
def update_firmware(session, filename):
|
||||
global exitcode
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
output = sq.ScreenPrinter(noderange, session)
|
||||
nodeurls = {}
|
||||
filename = os.path.abspath(filename)
|
||||
|
||||
@@ -97,7 +97,7 @@ if len(args) > 1:
|
||||
|
||||
requestargs=args[1:]
|
||||
except Exception as e:
|
||||
print str(e)
|
||||
print(str(e))
|
||||
|
||||
if exitcode != 0:
|
||||
sys.exit(exitcode)
|
||||
@@ -123,6 +123,6 @@ else:
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
exitcode = 1
|
||||
else:
|
||||
print res['item']['href'].replace('/', '')
|
||||
print(res['item']['href'].replace('/', ''))
|
||||
|
||||
sys.exit(exitcode)
|
||||
|
||||
@@ -37,6 +37,10 @@ exitcode = 0
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: "
|
||||
"%prog <noderange> [list][install <file>|save <directory>|delete <name>]")
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to delete licenses from, '
|
||||
'prompting if over the threshold')
|
||||
(options, args) = argparser.parse_args()
|
||||
upfile = None
|
||||
downdir = None
|
||||
@@ -52,7 +56,7 @@ try:
|
||||
delete = args[2]
|
||||
elif args[1] != 'list':
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
sys.exit(1)
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
@@ -138,6 +142,7 @@ try:
|
||||
elif downdir:
|
||||
save_licenses(session, downdir)
|
||||
elif delete:
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
delete_license(session, delete)
|
||||
else:
|
||||
show_licenses(session)
|
||||
|
||||
@@ -37,6 +37,11 @@ argparser = optparse.OptionParser(
|
||||
argparser.add_option('-p', '--showprevious', dest='previous',
|
||||
action='store_true', default=False,
|
||||
help='Show previous power state')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to change power state, '
|
||||
'prompting if over the threshold')
|
||||
|
||||
(options, args) = argparser.parse_args()
|
||||
try:
|
||||
noderange = args[0]
|
||||
@@ -72,4 +77,4 @@ if options.previous:
|
||||
# add dictionary to session
|
||||
session.add_precede_dict(prev)
|
||||
|
||||
sys.exit(session.simple_noderange_command(noderange, '/power/state', setstate))
|
||||
sys.exit(session.simple_noderange_command(noderange, '/power/state', setstate, promptover=options.maxnodes))
|
||||
@@ -35,6 +35,10 @@ import confluent.client as client
|
||||
argparser = optparse.OptionParser(
|
||||
usage='''\n %prog noderange
|
||||
\n ''')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to delete, '
|
||||
'prompting if over the threshold')
|
||||
(options, args) = argparser.parse_args()
|
||||
if len(args) != 1:
|
||||
argparser.print_help()
|
||||
@@ -43,6 +47,7 @@ noderange = args[0]
|
||||
client.check_globbing(noderange)
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
for r in session.delete('/noderange/{0}'.format(noderange)):
|
||||
if 'error' in r:
|
||||
sys.stderr.write(r['error'] + '\n')
|
||||
|
||||
@@ -32,6 +32,10 @@ if path.startswith('/opt'):
|
||||
import confluent.client as client
|
||||
|
||||
argparser = optparse.OptionParser(usage="Usage: %prog <noderange>")
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to reseat, '
|
||||
'prompting if over the threshold')
|
||||
(options, args) = argparser.parse_args()
|
||||
try:
|
||||
noderange = args[0]
|
||||
@@ -43,7 +47,7 @@ session = client.Command()
|
||||
exitcode = 0
|
||||
|
||||
errorNodes = set([])
|
||||
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
success = session.simple_noderange_command(noderange, 'power/reseat', 'reseat', key='reseat', errnodes=errorNodes) # = 0 if successful
|
||||
|
||||
# Determine which nodes were successful and print them
|
||||
@@ -56,7 +60,7 @@ for node in session.read('/noderange/{0}/nodes/'.format(noderange)):
|
||||
goodNodes = allNodes - errorNodes
|
||||
|
||||
for node in goodNodes:
|
||||
print node + ": Reseat successful"
|
||||
print(node + ": Reseat successful")
|
||||
|
||||
|
||||
sys.exit(success)
|
||||
|
||||
@@ -42,6 +42,10 @@ def run():
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: %prog location noderange:location",
|
||||
)
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to run rsync to, '
|
||||
'prompting if over the threshold')
|
||||
argparser.add_option('-f', '-c', '--count', type='int', default=168,
|
||||
help='Number of nodes to concurrently rsync')
|
||||
# among other things, FD_SETSIZE limits. Besides, spawning too many
|
||||
@@ -55,7 +59,7 @@ def run():
|
||||
noderange, targpath = args[-1].split(':', 1)
|
||||
client.check_globbing(noderange)
|
||||
c = client.Command()
|
||||
cmdstr = " ".join(args[:-1])
|
||||
cmdstr = ' '.join(args[:-1])
|
||||
cmdstr = 'rsync -av --info=progress2 ' + cmdstr
|
||||
cmdstr += ' {node}:' + targpath
|
||||
|
||||
@@ -64,15 +68,17 @@ def run():
|
||||
pipedesc = {}
|
||||
pendingexecs = deque()
|
||||
exitcode = 0
|
||||
|
||||
c.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
for exp in c.create('/noderange/{0}/attributes/expression'.format(noderange),
|
||||
{'expression': cmdstr}):
|
||||
{'expression': cmdstr}):
|
||||
if 'error' in exp:
|
||||
sys.stderr.write(exp['error'] + '\n')
|
||||
exitcode |= exp.get('errorcode', 1)
|
||||
ex = exp.get('databynode', ())
|
||||
for node in ex:
|
||||
cmd = ex[node]['value'].encode('utf-8')
|
||||
cmd = ex[node]['value']
|
||||
if not isinstance(cmd, bytes) and not isinstance(cmd, str):
|
||||
cmd = cmd.encode('utf-8')
|
||||
cmdv = shlex.split(cmd)
|
||||
if currprocs < concurrentprocs:
|
||||
currprocs += 1
|
||||
@@ -98,7 +104,7 @@ def run():
|
||||
if desc['type'] == 'stdout':
|
||||
if node not in pernodeout:
|
||||
pernodeout[node] = ''
|
||||
pernodeout[node] += data
|
||||
pernodeout[node] += client.stringify(data)
|
||||
if '\n' in pernodeout[node]:
|
||||
currout, pernodeout[node] = pernodeout[node].split('\n', 1)
|
||||
if currout:
|
||||
@@ -117,7 +123,7 @@ def run():
|
||||
output.set_output(node, 'error!')
|
||||
if node not in nodeerrs:
|
||||
nodeerrs[node] = ''
|
||||
nodeerrs[node] += data
|
||||
nodeerrs[node] += client.stringify(data)
|
||||
else:
|
||||
pop = desc['popen']
|
||||
ret = pop.poll()
|
||||
|
||||
@@ -46,6 +46,10 @@ def run():
|
||||
help='Number of commands to run at a time')
|
||||
argparser.add_option('-n', '--nonodeprefix', action='store_true',
|
||||
help='Do not prefix output with node names')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to run the command with, '
|
||||
'prompting if over the threshold')
|
||||
# among other things, FD_SETSIZE limits. Besides, spawning too many
|
||||
# processes can be unkind for the unaware on memory pressure and such...
|
||||
argparser.disable_interspersed_args()
|
||||
@@ -63,7 +67,7 @@ def run():
|
||||
pipedesc = {}
|
||||
pendingexecs = deque()
|
||||
exitcode = 0
|
||||
|
||||
c.stop_if_noderange_over(args[0], options.maxnodes)
|
||||
for exp in c.create('/noderange/{0}/attributes/expression'.format(args[0]),
|
||||
{'expression': cmdstr}):
|
||||
if 'error' in exp:
|
||||
@@ -71,7 +75,9 @@ def run():
|
||||
exitcode |= exp.get('errorcode', 1)
|
||||
ex = exp.get('databynode', ())
|
||||
for node in ex:
|
||||
cmd = ex[node]['value'].encode('utf-8')
|
||||
cmd = ex[node]['value']
|
||||
if not isinstance(cmd, bytes) and not isinstance(cmd, str):
|
||||
cmd = cmd.encode('utf-8')
|
||||
cmdv = shlex.split(cmd)
|
||||
if currprocs < concurrentprocs:
|
||||
currprocs += 1
|
||||
@@ -95,6 +101,7 @@ def run():
|
||||
pernodeout[node] = []
|
||||
pernodeout[node].append(data)
|
||||
else:
|
||||
data = client.stringify(data)
|
||||
if options.nonodeprefix:
|
||||
sys.stderr.write(data)
|
||||
else:
|
||||
@@ -112,6 +119,7 @@ def run():
|
||||
run_cmdv(node, cmdv, all, pipedesc)
|
||||
for node in sortutil.natural_sort(pernodeout):
|
||||
for line in pernodeout[node]:
|
||||
line = client.stringify(line)
|
||||
if options.nonodeprefix:
|
||||
sys.stdout.write(line)
|
||||
else:
|
||||
|
||||
@@ -149,8 +149,11 @@ def sensorpass(showout=True, appendtime=False):
|
||||
if appendtime:
|
||||
showval += ' @' + time.strftime(
|
||||
'%Y-%m-%dT%H:%M:%S')
|
||||
print(u'{0}: {1}:{2}'.format(
|
||||
node, sensedata['name'], showval).encode('utf8'))
|
||||
printval = u'{0}: {1}:{2}'.format(
|
||||
node, sensedata['name'], showval)
|
||||
if not isinstance(printval, str):
|
||||
printval = printval.encode('utf-8')
|
||||
print(printval)
|
||||
sys.stdout.flush()
|
||||
return resultdata
|
||||
|
||||
@@ -199,7 +202,11 @@ def main():
|
||||
orderedsensors.append(name)
|
||||
orderedsensors.sort()
|
||||
for name in orderedsensors:
|
||||
headernames.append(sensorheaders[name].encode('utf-8'))
|
||||
headername = sensorheaders[name]
|
||||
if (not isinstance(headername, str) and
|
||||
not isinstance(headername, bytes)):
|
||||
headername = headername.encode('utf-8')
|
||||
headernames.append(headername)
|
||||
if options.csv:
|
||||
linebyline = False
|
||||
csvwriter = csv.writer(sys.stdout)
|
||||
|
||||
@@ -43,7 +43,10 @@ argparser.add_option('-p', '--persist', dest='persist', action='store_true',
|
||||
argparser.add_option('-u', '--uefi', dest='uefi', action='store_true',
|
||||
default=True,
|
||||
help='Request UEFI style boot (rather than BIOS)')
|
||||
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to modify next boot device, '
|
||||
'prompting if over the threshold')
|
||||
(options, args) = argparser.parse_args()
|
||||
|
||||
try:
|
||||
@@ -63,6 +66,7 @@ if options.biosmode:
|
||||
bootmode = 'bios'
|
||||
else:
|
||||
bootmode = 'uefi'
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
sys.exit(session.simple_noderange_command(noderange, '/boot/nextdevice', bootdev,
|
||||
bootmode=bootmode,
|
||||
persistent=options.persist))
|
||||
|
||||
@@ -46,6 +46,10 @@ def run():
|
||||
help='Number of commands to run at a time')
|
||||
argparser.add_option('-n', '--nonodeprefix', action='store_true',
|
||||
help='Do not prefix output with node names')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to run remote ssh command to, '
|
||||
'prompting if over the threshold')
|
||||
# among other things, FD_SETSIZE limits. Besides, spawning too many
|
||||
# processes can be unkind for the unaware on memory pressure and such...
|
||||
argparser.disable_interspersed_args()
|
||||
@@ -55,7 +59,7 @@ def run():
|
||||
sys.exit(1)
|
||||
client.check_globbing(args[0])
|
||||
concurrentprocs = options.count
|
||||
c = client.Command()
|
||||
c = client.Command()
|
||||
cmdstr = " ".join(args[1:])
|
||||
|
||||
currprocs = 0
|
||||
@@ -64,7 +68,7 @@ def run():
|
||||
pendingexecs = deque()
|
||||
exitcode = 0
|
||||
|
||||
|
||||
c.stop_if_noderange_over(args[0], options.maxnodes)
|
||||
for exp in c.create('/noderange/{0}/attributes/expression'.format(args[0]),
|
||||
{'expression': cmdstr}):
|
||||
if 'error' in exp:
|
||||
@@ -72,7 +76,9 @@ def run():
|
||||
exitcode |= exp.get('errorcode', 1)
|
||||
ex = exp.get('databynode', ())
|
||||
for node in ex:
|
||||
cmd = ex[node]['value'].encode('utf-8')
|
||||
cmd = ex[node]['value']
|
||||
if not isinstance(cmd, str) and not isinstance(cmd, bytes):
|
||||
cmd = cmd.encode('utf-8')
|
||||
cmdv = ['ssh', node, cmd]
|
||||
if currprocs < concurrentprocs:
|
||||
currprocs += 1
|
||||
@@ -96,6 +102,7 @@ def run():
|
||||
pernodeout[node] = []
|
||||
pernodeout[node].append(data)
|
||||
else:
|
||||
data = client.stringify(data)
|
||||
if options.nonodeprefix:
|
||||
sys.stderr.write(data)
|
||||
else:
|
||||
@@ -113,6 +120,7 @@ def run():
|
||||
run_cmdv(node, cmdv, all, pipedesc)
|
||||
for node in sortutil.natural_sort(pernodeout):
|
||||
for line in pernodeout[node]:
|
||||
line = client.stringify(line)
|
||||
if options.nonodeprefix:
|
||||
sys.stdout.write(line)
|
||||
else:
|
||||
|
||||
@@ -63,10 +63,16 @@ def _print_cfg(scfg):
|
||||
sys.stderr.write(e['error'] + '\n')
|
||||
exitcode = e.get('errorcode', 1)
|
||||
for node in e.get('databynode', {}):
|
||||
curr = e['databynode'][node]
|
||||
if 'error' in curr:
|
||||
if 'no available drives' in curr['error']:
|
||||
curr['error'] += ' (drives must be in unconfigured state to be available, they must not be in jbod or online state)'
|
||||
sys.stderr.write('{0}: {1}\n'.format(node, curr['error']))
|
||||
exitcode = curr.get('errorcode', 1)
|
||||
continue
|
||||
if node not in storagebynode:
|
||||
storagebynode[node] = {'disks': [], 'arrays': [],
|
||||
'volumes': []}
|
||||
curr = e['databynode'][node]
|
||||
storagebynode[node][curr['type'] + 's'].append(curr)
|
||||
for node in storagebynode:
|
||||
for disk in sorted(storagebynode[node]['disks'],
|
||||
@@ -108,6 +114,7 @@ def createstorage(noderange, options, args):
|
||||
sys.stderr.write('-r and -d are required arguments to create array\n')
|
||||
sys.exit(1)
|
||||
session = client.Command()
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
names = options.name
|
||||
if names is None:
|
||||
names = ''.join(args)
|
||||
@@ -132,6 +139,7 @@ def deletestorage(noderange, options, args):
|
||||
else:
|
||||
names = options.name
|
||||
session = client.Command()
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
for rsp in session.delete(
|
||||
'/noderange/{0}/configuration/storage/volumes/{1}'.format(
|
||||
noderange, names)):
|
||||
@@ -162,6 +170,7 @@ def setdisk(noderange, options, args):
|
||||
sys.stderr.write('diskset requires valid state as argument (hotspare, jbod, unconfigured)\n')
|
||||
sys.exit(1)
|
||||
session = client.Command()
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
scfg = session.update('/noderange/{0}/configuration/storage/disks/{1}'.format(noderange, names), {'state': args[0]})
|
||||
_print_cfg(scfg)
|
||||
|
||||
@@ -202,6 +211,10 @@ def main():
|
||||
help='Comma separated list of stripsizes to use when creating volumes. '
|
||||
'This value is in kilobytes. The default behavior is to allow the '
|
||||
'storage controller to decide.')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to configure storage on, '
|
||||
'prompting if over the threshold')
|
||||
(options, args) = argparser.parse_args()
|
||||
if len(args) == 1:
|
||||
args.append('show')
|
||||
|
||||
@@ -64,7 +64,7 @@ def printerror(res, node=None):
|
||||
|
||||
|
||||
|
||||
def download_servicedata(noderange, media):
|
||||
def download_servicedata(noderange, media, options):
|
||||
global exitcode
|
||||
session = client.Command()
|
||||
output = sq.ScreenPrinter(noderange, session)
|
||||
@@ -73,6 +73,7 @@ def download_servicedata(noderange, media):
|
||||
upargs = {'filename': filename}
|
||||
noderrs = {}
|
||||
nodeurls = {}
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
for res in session.create(resource, upargs):
|
||||
if 'created' not in res:
|
||||
for nodename in res.get('databynode', ()):
|
||||
@@ -121,6 +122,10 @@ def main():
|
||||
'management server (the confluent server if running remote, '
|
||||
'and the collective.manager if in collective)\n'
|
||||
'\n\nSee `man %prog` for more info.\n')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to download diagnostic data from, '
|
||||
'prompting if over the threshold')
|
||||
(options, args) = argparser.parse_args()
|
||||
media = None
|
||||
try:
|
||||
@@ -142,6 +147,6 @@ def main():
|
||||
except KeyError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
handler(noderange, media)
|
||||
handler(noderange, media, options)
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@@ -39,6 +39,19 @@ _attraliases = {
|
||||
'bmcpass': 'secret.hardwaremanagementpassword',
|
||||
}
|
||||
|
||||
try:
|
||||
input = raw_input
|
||||
except NameError:
|
||||
pass
|
||||
|
||||
def stringify(instr):
|
||||
# Normalize unicode and bytes to 'str', correcting for
|
||||
# current python version
|
||||
if isinstance(instr, bytes) and not isinstance(instr, str):
|
||||
return instr.decode('utf-8')
|
||||
elif not isinstance(instr, bytes) and not isinstance(instr, str):
|
||||
return instr.encode('utf-8')
|
||||
return instr
|
||||
|
||||
class Tabulator(object):
|
||||
def __init__(self, headers):
|
||||
@@ -210,7 +223,7 @@ class Command(object):
|
||||
return rc
|
||||
|
||||
def simple_noderange_command(self, noderange, resource, input=None,
|
||||
key=None, errnodes=None, **kwargs):
|
||||
key=None, errnodes=None, promptover=None, **kwargs):
|
||||
try:
|
||||
self._currnoderange = noderange
|
||||
rc = 0
|
||||
@@ -226,6 +239,7 @@ class Command(object):
|
||||
noderange, resource)):
|
||||
rc = self.handle_results(ikey, rc, res, errnodes)
|
||||
else:
|
||||
self.stop_if_noderange_over(noderange, promptover)
|
||||
kwargs[ikey] = input
|
||||
for res in self.update('/noderange/{0}/{1}'.format(
|
||||
noderange, resource), kwargs):
|
||||
@@ -235,6 +249,33 @@ class Command(object):
|
||||
except KeyboardInterrupt:
|
||||
cprint('')
|
||||
return 0
|
||||
|
||||
def stop_if_noderange_over(self, noderange, maxnodes):
|
||||
if maxnodes is None:
|
||||
return
|
||||
nsize = self.get_noderange_size(noderange)
|
||||
if nsize > maxnodes:
|
||||
if nsize == 1:
|
||||
nodename = list(self.read(
|
||||
'/noderange/{0}/nodes/'.format(noderange)))[0].get('item', {}).get('href', None)
|
||||
nodename = nodename[:-1]
|
||||
p = input('Command is about to affect node {0}, continue (y/n)? '.format(nodename))
|
||||
else:
|
||||
p = input('Command is about to affect {0} nodes, continue (y/n)? '.format(nsize))
|
||||
if p.lower() != 'y':
|
||||
sys.stderr.write('Aborting at user request\n')
|
||||
sys.exit(1)
|
||||
raise Exception("Aborting at user request")
|
||||
|
||||
|
||||
def get_noderange_size(self, noderange):
|
||||
numnodes = 0
|
||||
for node in self.read('/noderange/{0}/nodes/'.format(noderange)):
|
||||
if node.get('item', {}).get('href', None):
|
||||
numnodes += 1
|
||||
else:
|
||||
raise Exception("Error trying to size noderange {0}".format(noderange))
|
||||
return numnodes
|
||||
|
||||
def simple_nodegroups_command(self, noderange, resource, input=None, key=None, **kwargs):
|
||||
try:
|
||||
@@ -335,12 +376,8 @@ class Command(object):
|
||||
if fingerprint == khf[hostid]:
|
||||
return
|
||||
else:
|
||||
try:
|
||||
replace = raw_input(
|
||||
"MISMATCHED CERTIFICATE DATA, ACCEPT NEW? (y/n):")
|
||||
except NameError:
|
||||
replace = input(
|
||||
"MISMATCHED CERTIFICATE DATA, ACCEPT NEW? (y/n):")
|
||||
replace = input(
|
||||
"MISMATCHED CERTIFICATE DATA, ACCEPT NEW? (y/n):")
|
||||
if replace not in ('y', 'Y'):
|
||||
raise Exception("BAD CERTIFICATE")
|
||||
cprint('Adding new key for %s:%s' % (server, port))
|
||||
@@ -392,6 +429,10 @@ def printattributes(session, requestargs, showtype, nodetype, noderange, options
|
||||
path = '/{0}/{1}/attributes/{2}'.format(nodetype, noderange, showtype)
|
||||
return print_attrib_path(path, session, requestargs, options)
|
||||
|
||||
def _sort_attrib(k):
|
||||
if isinstance(k[1], dict) and k[1].get('sortid', None) is not None:
|
||||
return k[1]['sortid']
|
||||
return k[0]
|
||||
|
||||
def print_attrib_path(path, session, requestargs, options, rename=None, attrprefix=None):
|
||||
exitcode = 0
|
||||
@@ -402,9 +443,7 @@ def print_attrib_path(path, session, requestargs, options, rename=None, attrpref
|
||||
exitcode = 1
|
||||
continue
|
||||
for node in sorted(res['databynode']):
|
||||
for attr, val in sorted(
|
||||
res['databynode'][node].items(),
|
||||
key=lambda k: k[1].get('sortid', k[0]) if isinstance(k[1], dict) else k[0]):
|
||||
for attr, val in sorted(res['databynode'][node].items(), key=_sort_attrib):
|
||||
if attr == 'error':
|
||||
sys.stderr.write('{0}: Error: {1}\n'.format(node, val))
|
||||
continue
|
||||
|
||||
@@ -46,9 +46,8 @@ alias nodelicense='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export
|
||||
_confluent_get_args()
|
||||
{
|
||||
CMPARGS=($COMP_LINE)
|
||||
NUMARGS=${#CMPARGS[@]}
|
||||
if [ "${COMP_WORDS[-1]}" == '' ]; then
|
||||
NUMARGS=$((NUMARGS+1))
|
||||
NUMARGS=$((COMP_CWORD+1))
|
||||
if [ "${COMP_WORDS[COMP_CWORD]}" == '' ]; then
|
||||
CMPARGS+=("")
|
||||
fi
|
||||
GENNED=""
|
||||
@@ -75,7 +74,7 @@ function _confluent_generic_completion()
|
||||
{
|
||||
_confluent_get_args
|
||||
if [ $NUMARGS -ge 3 ] && [ ! -z "$GENNED" ]; then
|
||||
COMPREPLY=($(compgen -W "$GENNED" -- ${COMP_WORDS[-1]}))
|
||||
COMPREPLY=($(compgen -W "$GENNED" -- ${COMP_WORDS[COMP_CWORD]}))
|
||||
fi
|
||||
if [ $NUMARGS -lt 3 ]; then
|
||||
_confluent_nr_completion
|
||||
@@ -111,7 +110,7 @@ _confluent_nodemedia_completion()
|
||||
return
|
||||
fi
|
||||
if [ $NUMARGS -ge 3 ] && [ ! -z "$GENNED" ]; then
|
||||
COMPREPLY=($(compgen -W "$GENNED" -- ${COMP_WORDS[-1]}))
|
||||
COMPREPLY=($(compgen -W "$GENNED" -- ${COMP_WORDS[COMP_CWORD]}))
|
||||
return;
|
||||
fi
|
||||
if [ $NUMARGS -lt 3 ]; then
|
||||
@@ -124,7 +123,7 @@ _confluent_nodefirmware_completion()
|
||||
{
|
||||
_confluent_get_args
|
||||
if [ $NUMARGS == 3 ]; then
|
||||
COMPREPLY=($(compgen -W "list update" -- ${COMP_WORDS[-1]}))
|
||||
COMPREPLY=($(compgen -W "list update" -- ${COMP_WORDS[COMP_CWORD]}))
|
||||
return;
|
||||
fi
|
||||
if [ $NUMARGS -gt 3 ] && [ ${CMPARGS[2]} == 'update' ]; then
|
||||
@@ -142,7 +141,7 @@ _confluent_nodeshell_completion()
|
||||
{
|
||||
_confluent_get_args
|
||||
if [ $NUMARGS == 3 ]; then
|
||||
COMPREPLY=($(compgen -c -- ${COMP_WORDS[-1]}))
|
||||
COMPREPLY=($(compgen -c -- ${COMP_WORDS[COMP_CWORD]}))
|
||||
return
|
||||
fi
|
||||
if [ $NUMARGS -gt 3 ]; then
|
||||
@@ -160,7 +159,7 @@ _confluent_nodelicense_completion()
|
||||
{
|
||||
_confluent_get_args
|
||||
if [ $NUMARGS == 3 ]; then
|
||||
COMPREPLY=($(compgen -W "install list save delete" -- ${COMP_WORDS[-1]}))
|
||||
COMPREPLY=($(compgen -W "install list save delete" -- ${COMP_WORDS[COMP_CWORD]}))
|
||||
return;
|
||||
fi
|
||||
if [ $NUMARGS == 4 ] && [ ${CMPARGS[2]} == 'install' ]; then
|
||||
@@ -183,7 +182,7 @@ _confluent_nodesupport_completion()
|
||||
{
|
||||
_confluent_get_args
|
||||
if [ $NUMARGS == 3 ]; then
|
||||
COMPREPLY=($(compgen -W "servicedata" -- ${COMP_WORDS[-1]}))
|
||||
COMPREPLY=($(compgen -W "servicedata" -- ${COMP_WORDS[COMP_CWORD]}))
|
||||
return;
|
||||
fi
|
||||
if [ $NUMARGS == 4 ] && [ ${CMPARGS[2]} == 'servicedata' ]; then
|
||||
@@ -210,41 +209,36 @@ _confluent_nn_completion()
|
||||
if [ $NUMARGS -gt 2 ]; then
|
||||
return;
|
||||
fi
|
||||
INPUT=${COMP_WORDS[-1]}
|
||||
INPUT=${COMP_WORDS[COMP_CWORD]}
|
||||
INPUT=${INPUT##*,-}
|
||||
INPUT=${INPUT##*,}
|
||||
INPUT=${INPUT##*@}
|
||||
PREFIX=""
|
||||
if [ "$INPUT" != "${COMP_WORDS[-1]}" ]; then
|
||||
PREFIX=${COMP_WORDS[-1]}
|
||||
if [ "$INPUT" != "${COMP_WORDS[COMP_CWORD]}" ]; then
|
||||
PREFIX=${COMP_WORDS[COMP_CWORD]}
|
||||
PREFIX=$(echo $PREFIX | sed -e 's/,[^,@-]*$/,/' -e 's/,-[^,@]*$/,-/' -e 's/@[^,@]*/@/')
|
||||
fi
|
||||
|
||||
COMPREPLY=($(compgen -W "$(nodelist | sed -e s/^/$PREFIX/)" -- "${COMP_WORDS[-1]}"))
|
||||
COMPREPLY=($(compgen -W "$(nodelist | sed -e s/^/$PREFIX/)" -- "${COMP_WORDS[COMP_CWORD]}"))
|
||||
}
|
||||
_confluent_nr_completion()
|
||||
{
|
||||
CMPARGS=($COMP_LINE)
|
||||
NUMARGS=${#CMPARGS[@]}
|
||||
if [ "${COMP_WORDS[-1]}" == '' ]; then
|
||||
NUMARGS=$((NUMARGS+1))
|
||||
fi
|
||||
_confluent_get_args
|
||||
if [ $NUMARGS -gt 2 ]; then
|
||||
return;
|
||||
fi
|
||||
INPUT=${COMP_WORDS[-1]}
|
||||
INPUT=${COMP_WORDS[COMP_CWORD]}
|
||||
INPUT=${INPUT##*,-}
|
||||
INPUT=${INPUT##*,}
|
||||
INPUT=${INPUT##*@}
|
||||
PREFIX=""
|
||||
if [ "$INPUT" != "${COMP_WORDS[-1]}" ]; then
|
||||
PREFIX=${COMP_WORDS[-1]}
|
||||
if [ "$INPUT" != "${COMP_WORDS[COMP_CWORD]}" ]; then
|
||||
PREFIX=${COMP_WORDS[COMP_CWORD]}
|
||||
PREFIX=$(echo $PREFIX | sed -e 's/,[^,@-]*$/,/' -e 's/,-[^,@]*$/,-/' -e 's/@[^,@]*/@/')
|
||||
fi
|
||||
|
||||
#COMPREPLY=($(compgen -W "$(confetty show /nodegroups|sed -e 's/\///' -e s/^/$PREFIX/;nodelist | sed -e s/^/$PREFIX/)" -- "${COMP_WORDS[-1]}"))
|
||||
COMPREPLY=($(compgen -W "$(confetty show /nodegroups|sed -e 's/\///' -e s/^/$PREFIX/;nodelist | sed -e s/^/$PREFIX/)" -- "${COMP_WORDS[-1]}"))
|
||||
COMPREPLY=($(compgen -W "$(confetty show /nodegroups|sed -e 's/\///' -e s/^/$PREFIX/;nodelist | sed -e s/^/$PREFIX/)" -- "${COMP_WORDS[COMP_CWORD]}"))
|
||||
}
|
||||
_confluent_ng_completion()
|
||||
{
|
||||
@@ -252,17 +246,17 @@ _confluent_ng_completion()
|
||||
if [ $NUMARGS -gt 2 ]; then
|
||||
return;
|
||||
fi
|
||||
INPUT=${COMP_WORDS[-1]}
|
||||
INPUT=${COMP_WORDS[COMP_CWORD]}
|
||||
INPUT=${INPUT##*,-}
|
||||
INPUT=${INPUT##*,}
|
||||
INPUT=${INPUT##*@}
|
||||
PREFIX=""
|
||||
if [ "$INPUT" != "${COMP_WORDS[-1]}" ]; then
|
||||
PREFIX=${COMP_WORDS[-1]}
|
||||
if [ "$INPUT" != "${COMP_WORDS[COMP_CWORD]}" ]; then
|
||||
PREFIX=${COMP_WORDS[COMP_CWORD]}
|
||||
PREFIX=$(echo $PREFIX | sed -e 's/,[^,@-]*$/,/' -e 's/,-[^,@]*$/,-/' -e 's/@[^,@]*/@/')
|
||||
fi
|
||||
|
||||
COMPREPLY=($(compgen -W "$(confetty show /nodegroups|sed -e 's/\///' -e s/^/$PREFIX/)" -- "${COMP_WORDS[-1]}"))
|
||||
COMPREPLY=($(compgen -W "$(confetty show /nodegroups|sed -e 's/\///' -e s/^/$PREFIX/)" -- "${COMP_WORDS[COMP_CWORD]}"))
|
||||
}
|
||||
complete -F _confluent_nodeattrib_completion nodeattrib
|
||||
complete -F _confluent_nodeattrib_completion nodegroupattrib
|
||||
|
||||
@@ -13,7 +13,11 @@ nodefirmware(8) -- Report firmware information on confluent nodes
|
||||
will retrieve all firmware, but can be directed to fetch specific firmware by
|
||||
calling out the name of the firmware (e.g. uefi or xcc) or request reading only
|
||||
core firmware firmware by using the word 'core', which is generally a quicker
|
||||
operation.
|
||||
operation. Different hardwaremanagement.method indicated plugins may have
|
||||
different capabilities available. For example, the 'core' distinction may
|
||||
not be relevant to redfish. Additionally, the Lenovo XCC makes certain
|
||||
information available over IPMI that is not otherwise available (for example
|
||||
the FPGA version where applicable).
|
||||
|
||||
In the update form, it accepts a single file and attempts to update it using
|
||||
the out of band facilities. Firmware updates can end in one of three states:
|
||||
|
||||
@@ -42,7 +42,7 @@ The attribute name may use a wildcard:
|
||||
Commas can be used to indicate multiple nodes, and can mix and match any of the above primitives. The following can be a valid single noderange, combining any and all members of each comma separated component
|
||||
`n1,n2,rack1,storage,location.rack=9,~s1..,n20-n30`
|
||||
|
||||
Exclusions can be done by prepending a ‘-‘ before a portion of a noderange:
|
||||
Exclusions can be done by prepending a '-' before a portion of a noderange:
|
||||
`rack1,-n2`
|
||||
`compute,-rack1`
|
||||
`compute,-location.row=12`
|
||||
@@ -54,7 +54,7 @@ To indicate nodes that match multiple selections at once (set intersection), the
|
||||
For complex expressions, () may be used to indicate order of expanding the noderange to be explicit
|
||||
`rack1,-(console.logging=full@compute)`
|
||||
|
||||
Noderange syntax can also indicate ‘pagination’, or separating the nodes into well defined chunks. > is used to indicate how many nodes to display at a time, and < is used to indicate how many nodes to skip into a noderange:
|
||||
Noderange syntax can also indicate 'pagination', or separating the nodes into well defined chunks. > is used to indicate how many nodes to display at a time, and < is used to indicate how many nodes to skip into a noderange:
|
||||
`rack1>3<6`
|
||||
|
||||
The above would show the seventh through ninth nodes of the rack1 group. Like all other noderange operations, this may be combined with any of the above, but must appear as the very last operation. Ordering is done with a natural sort.
|
||||
|
||||
@@ -7,7 +7,7 @@ nodesupport(8) -- Utilities for interacting with vendor support
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
`nodesupport` provides capabilities associated with interactiong with support.
|
||||
`nodesupport` provides capabilities associated with interacting with support.
|
||||
Currently it only has the `servicedata` subcommand. `servicedata` takes
|
||||
an argument that is either a directory name (that can be used for a single node
|
||||
or multiple nodes) or a file name (only to be used with single node noderange).
|
||||
@@ -16,6 +16,9 @@ connects to the managed system, so it will download to the remote system if runn
|
||||
remotely and will download to the collective.manager indicated system if
|
||||
running in collective mode.
|
||||
|
||||
Note that due to vendor filename requirements, any filename may have vendor
|
||||
specific suffixes added to any file produced.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
* Download support data from a single node to a specific filename
|
||||
|
||||
@@ -22,7 +22,7 @@ except NameError:
|
||||
pass
|
||||
|
||||
def make_certificate():
|
||||
umask = os.umask(0077)
|
||||
umask = os.umask(0o77)
|
||||
try:
|
||||
os.makedirs('/etc/confluent/cfg')
|
||||
except OSError as e:
|
||||
@@ -61,7 +61,7 @@ def join_collective(server, invitation):
|
||||
make_certificate()
|
||||
s = client.Command().connection
|
||||
while not invitation:
|
||||
invitation = raw_input('Paste the invitation here: ')
|
||||
invitation = input('Paste the invitation here: ')
|
||||
tlvdata.send(s, {'collective': {'operation': 'join',
|
||||
'invitation': invitation,
|
||||
'server': server}})
|
||||
@@ -69,6 +69,19 @@ def join_collective(server, invitation):
|
||||
res = res.get('collective',
|
||||
{'status': 'Unknown response: ' + repr(res)})
|
||||
print(res.get('status', res.get('error', repr(res))))
|
||||
if 'error' in res:
|
||||
sys.exit(1)
|
||||
|
||||
def delete_member(name):
|
||||
s = client.Command().connection
|
||||
tlvdata.send(s, {'collective': {'operation': 'delete',
|
||||
'member': name}})
|
||||
res = tlvdata.recv(s)
|
||||
res = res.get('collective',
|
||||
{'status': 'Unknown response: ' + repr(res)})
|
||||
print(res.get('status', res.get('error', repr(res))))
|
||||
if 'error' in res:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def show_collective():
|
||||
@@ -104,6 +117,8 @@ def main():
|
||||
'collective member. Run collective invite -h for more information')
|
||||
ic.add_argument('name', help='Name of server to invite to join the '
|
||||
'collective')
|
||||
dc = sp.add_parser('delete', help='Delete a member of a collective')
|
||||
dc.add_argument('name', help='Name of server to delete from collective')
|
||||
jc = sp.add_parser('join', help='Join a collective. Run collective join -h for more information')
|
||||
jc.add_argument('server', help='Existing collective member that ran invite and generated a token')
|
||||
jc.add_argument('-i', help='Invitation provided by runniing invite on an '
|
||||
@@ -117,6 +132,8 @@ def main():
|
||||
join_collective(cmdset.server, cmdset.i)
|
||||
elif cmdset.command == 'show':
|
||||
show_collective()
|
||||
elif cmdset.command == 'delete':
|
||||
delete_member(cmdset.name)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@@ -3,8 +3,12 @@ cd `dirname $0`
|
||||
PKGNAME=$(basename $(pwd))
|
||||
DPKGNAME=$(basename $(pwd) | sed -e s/_/-/)
|
||||
OPKGNAME=$(basename $(pwd) | sed -e s/_/-/)
|
||||
PYEXEC=python3
|
||||
DSCARGS="--with-python3=True --with-python2=False"
|
||||
if grep wheezy /etc/os-release; then
|
||||
DPKGNAME=python-$DPKGNAME
|
||||
PYEXEC=python
|
||||
DSCARGS="--with-python3=False --with-python2=True"
|
||||
fi
|
||||
cd ..
|
||||
mkdir -p /tmp/confluent # $DPKGNAME
|
||||
@@ -24,15 +28,15 @@ install-scripts=/opt/confluent/bin
|
||||
package=$DPKGNAME
|
||||
EOF
|
||||
|
||||
python setup.py sdist > /dev/null 2>&1
|
||||
py2dsc dist/*.tar.gz
|
||||
$PYEXEC setup.py sdist > /dev/null 2>&1
|
||||
py2dsc $DSCARGS dist/*.tar.gz
|
||||
shopt -s extglob
|
||||
cd deb_dist/!(*.orig)/
|
||||
if [ "$OPKGNAME" = "confluent-server" ]; then
|
||||
if grep wheezy /etc/os-release; then
|
||||
sed -i 's/^\(Depends:.*\)/\1, python-confluent-client, python-lxml, python-eficompressor, python-pycryptodomex, python-dateutil, python-pyopenssl/' debian/control
|
||||
else
|
||||
sed -i 's/^\(Depends:.*\)/\1, confluent-client, python-lxml, python-eficompressor, python-pycryptodome, python-dateutil/' debian/control
|
||||
sed -i 's/^\(Depends:.*\)/\1, confluent-client, python3-lxml, python3-eficompressor, python3-pycryptodome, python3-websocket/' debian/control
|
||||
fi
|
||||
if grep wheezy /etc/os-release; then
|
||||
echo 'confluent_client python-confluent-client' >> debian/pydist-overrides
|
||||
|
||||
@@ -6,7 +6,7 @@ fi
|
||||
./makesetup
|
||||
VERSION=`cat VERSION`
|
||||
PKGNAME=$(basename $(pwd))
|
||||
python setup.py sdist > /dev/null 2>&1
|
||||
python3 setup.py sdist > /dev/null 2>&1
|
||||
cp dist/*.tar.gz ~/rpmbuild/SOURCES
|
||||
sed -e 's/#VERSION#/'$VERSION/ $PKGNAME.spec.tmpl > ~/rpmbuild/SPECS/$PKGNAME.spec
|
||||
rpmbuild -ba ~/rpmbuild/SPECS/$PKGNAME.spec 2> /dev/null |grep ^Wrote:
|
||||
|
||||
@@ -28,6 +28,7 @@ import hashlib
|
||||
import hmac
|
||||
import multiprocessing
|
||||
import confluent.userutil as userutil
|
||||
import confluent.util as util
|
||||
pam = None
|
||||
try:
|
||||
import confluent.pam as pam
|
||||
@@ -57,9 +58,9 @@ _allowedbyrole = {
|
||||
'/node*/configuration/*',
|
||||
],
|
||||
'update': [
|
||||
'/discovery/*',
|
||||
'/discovery/*',
|
||||
'/networking/macs/rescan',
|
||||
'/node*/power/state',
|
||||
'/node*/power/state',
|
||||
'/node*/power/reseat',
|
||||
'/node*/attributes/*',
|
||||
'/node*/media/*tach',
|
||||
@@ -119,11 +120,13 @@ def _get_usertenant(name, tenant=False):
|
||||
administrator account a tenant gets.
|
||||
Otherwise, just assume a user in the default tenant
|
||||
"""
|
||||
if not isinstance(name, bytes):
|
||||
name = name.encode('utf-8')
|
||||
if not isinstance(tenant, bool):
|
||||
# if not boolean, it must be explicit tenant
|
||||
user = name
|
||||
elif '/' in name: # tenant scoped name
|
||||
tenant, user = name.split('/', 1)
|
||||
elif b'/' in name: # tenant scoped name
|
||||
tenant, user = name.split(b'/', 1)
|
||||
elif configmanager.is_tenant(name):
|
||||
# the account is the implicit tenant owner account
|
||||
user = name
|
||||
@@ -131,6 +134,9 @@ def _get_usertenant(name, tenant=False):
|
||||
else: # assume it is a non-tenant user account
|
||||
user = name
|
||||
tenant = None
|
||||
user = util.stringify(user)
|
||||
if tenant:
|
||||
tenant = util.stringify(tenant)
|
||||
yield user
|
||||
yield tenant
|
||||
|
||||
@@ -288,4 +294,4 @@ def _do_pbkdf(passphrase, salt):
|
||||
# compute. However, we do want to wait for result, so we have
|
||||
# one of the exceedingly rare sort of circumstances where 'apply'
|
||||
# actually makes sense
|
||||
return authworkers.apply(_apply_pbkdf, [passphrase, salt])
|
||||
return authworkers.apply(_apply_pbkdf, [passphrase, salt])
|
||||
|
||||
@@ -27,6 +27,7 @@ import eventlet.green.ssl as ssl
|
||||
import eventlet.green.threading as threading
|
||||
import greenlet
|
||||
import random
|
||||
import sys
|
||||
try:
|
||||
import OpenSSL.crypto as crypto
|
||||
except ImportError:
|
||||
@@ -70,11 +71,22 @@ def connect_to_leader(cert=None, name=None, leader=None):
|
||||
return False
|
||||
with connecting:
|
||||
with cfm._initlock:
|
||||
tlvdata.recv(remote) # the banner
|
||||
banner = tlvdata.recv(remote) # the banner
|
||||
vers = banner.split()[2]
|
||||
pvers = 0
|
||||
reqver = 4
|
||||
if vers == b'v0':
|
||||
pvers = 2
|
||||
elif vers == b'v1':
|
||||
pvers = 4
|
||||
if sys.version_info[0] < 3:
|
||||
pvers = 2
|
||||
reqver = 2
|
||||
tlvdata.recv(remote) # authpassed... 0..
|
||||
if name is None:
|
||||
name = get_myname()
|
||||
tlvdata.send(remote, {'collective': {'operation': 'connect',
|
||||
'protover': reqver,
|
||||
'name': name,
|
||||
'txcount': cfm._txcount}})
|
||||
keydata = tlvdata.recv(remote)
|
||||
@@ -119,7 +131,7 @@ def connect_to_leader(cert=None, name=None, leader=None):
|
||||
globaldata = tlvdata.recv(remote)
|
||||
dbi = tlvdata.recv(remote)
|
||||
dbsize = dbi['dbsize']
|
||||
dbjson = ''
|
||||
dbjson = b''
|
||||
while (len(dbjson) < dbsize):
|
||||
ndata = remote.recv(dbsize - len(dbjson))
|
||||
if not ndata:
|
||||
@@ -148,15 +160,15 @@ def connect_to_leader(cert=None, name=None, leader=None):
|
||||
raise
|
||||
currentleader = leader
|
||||
#spawn this as a thread...
|
||||
follower = eventlet.spawn(follow_leader, remote)
|
||||
follower = eventlet.spawn(follow_leader, remote, pvers, leader)
|
||||
return True
|
||||
|
||||
|
||||
def follow_leader(remote):
|
||||
def follow_leader(remote, proto, leader):
|
||||
global currentleader
|
||||
cleanexit = False
|
||||
try:
|
||||
cfm.follow_channel(remote)
|
||||
cfm.follow_channel(remote, proto)
|
||||
except greenlet.GreenletExit:
|
||||
cleanexit = True
|
||||
finally:
|
||||
@@ -164,8 +176,8 @@ def follow_leader(remote):
|
||||
log.log({'info': 'Previous following cleanly closed',
|
||||
'subsystem': 'collective'})
|
||||
return
|
||||
log.log({'info': 'Current leader has disappeared, restarting '
|
||||
'collective membership', 'subsystem': 'collective'})
|
||||
log.log({'info': 'Current leader ({0}) has disappeared, restarting '
|
||||
'collective membership'.format(leader), 'subsystem': 'collective'})
|
||||
# The leader has folded, time to startup again...
|
||||
cfm.stop_following()
|
||||
currentleader = None
|
||||
@@ -208,8 +220,7 @@ def handle_connection(connection, cert, request, local=False):
|
||||
else:
|
||||
if not local:
|
||||
return
|
||||
|
||||
if 'show' == operation:
|
||||
if operation in ('show', 'delete'):
|
||||
if not list(cfm.list_collective()):
|
||||
tlvdata.send(connection,
|
||||
{'collective': {'error': 'Collective mode not '
|
||||
@@ -246,7 +257,23 @@ def handle_connection(connection, cert, request, local=False):
|
||||
collinfo['quorum'] = True
|
||||
except exc.DegradedCollective:
|
||||
collinfo['quorum'] = False
|
||||
tlvdata.send(connection, {'collective': collinfo})
|
||||
if operation == 'show':
|
||||
tlvdata.send(connection, {'collective': collinfo})
|
||||
elif operation == 'delete':
|
||||
todelete = request['member']
|
||||
if (todelete == collinfo['leader'] or
|
||||
todelete in collinfo['active']):
|
||||
tlvdata.send(connection, {'collective':
|
||||
{'error': '{0} is still active, stop the confluent service to remove it'.format(todelete)}})
|
||||
return
|
||||
if todelete not in collinfo['offline']:
|
||||
tlvdata.send(connection, {'collective':
|
||||
{'error': '{0} is not a recognized collective member'.format(todelete)}})
|
||||
return
|
||||
cfm.del_collective_member(todelete)
|
||||
tlvdata.send(connection,
|
||||
{'collective': {'status': 'Successfully deleted {0}'.format(todelete)}})
|
||||
connection.close()
|
||||
return
|
||||
if 'invite' == operation:
|
||||
try:
|
||||
@@ -267,7 +294,8 @@ def handle_connection(connection, cert, request, local=False):
|
||||
invitation = request['invitation']
|
||||
try:
|
||||
invitation = base64.b64decode(invitation)
|
||||
name, invitation = invitation.split('@', 1)
|
||||
name, invitation = invitation.split(b'@', 1)
|
||||
name = util.stringify(name)
|
||||
except Exception:
|
||||
tlvdata.send(
|
||||
connection,
|
||||
@@ -375,7 +403,7 @@ def handle_connection(connection, cert, request, local=False):
|
||||
connection.close()
|
||||
return
|
||||
if (currentleader == connection.getpeername()[0] and
|
||||
follower and follower.isAlive()):
|
||||
follower and not follower.dead):
|
||||
# if we are happily following this leader already, don't stir
|
||||
# the pot
|
||||
tlvdata.send(connection, {'status': 0})
|
||||
@@ -402,6 +430,7 @@ def handle_connection(connection, cert, request, local=False):
|
||||
tlvdata.send(connection, collinfo)
|
||||
if 'connect' == operation:
|
||||
drone = request['name']
|
||||
folver = request.get('protover', 2)
|
||||
droneinfo = cfm.get_collective_member(drone)
|
||||
if not (droneinfo and util.cert_matches(droneinfo['fingerprint'],
|
||||
cert)):
|
||||
@@ -450,7 +479,7 @@ def handle_connection(connection, cert, request, local=False):
|
||||
connection.sendall(cfgdata)
|
||||
#tlvdata.send(connection, {'tenants': 0}) # skip the tenants for now,
|
||||
# so far unused anyway
|
||||
if not cfm.relay_slaved_requests(drone, connection):
|
||||
if not cfm.relay_slaved_requests(drone, connection, folver):
|
||||
if not retrythread: # start a recovery if everyone else seems
|
||||
# to have disappeared
|
||||
retrythread = eventlet.spawn_after(30 + random.random(),
|
||||
|
||||
@@ -271,7 +271,7 @@ node = {
|
||||
'console.method': {
|
||||
'description': ('Indicate the method used to access the console of '
|
||||
'the managed node.'),
|
||||
'validvalues': ('ssh', 'ipmi'),
|
||||
'validvalues': ('ssh', 'ipmi', 'tsmsol'),
|
||||
},
|
||||
# 'virtualization.host': {
|
||||
# 'description': ('Hypervisor where this node does/should reside'),
|
||||
|
||||
@@ -101,6 +101,10 @@ _cfgstore = None
|
||||
_pendingchangesets = {}
|
||||
_txcount = 0
|
||||
_hasquorum = True
|
||||
if sys.version_info[0] >= 3:
|
||||
lowestver = 4
|
||||
else:
|
||||
lowestver = 2
|
||||
|
||||
_attraliases = {
|
||||
'bmc': 'hardwaremanagement.manager',
|
||||
@@ -309,12 +313,12 @@ def check_quorum():
|
||||
def exec_on_leader(function, *args):
|
||||
if isinstance(cfgleader, bool):
|
||||
raise exc.DegradedCollective()
|
||||
xid = os.urandom(8)
|
||||
xid = confluent.util.stringify(base64.b64encode(os.urandom(8)))
|
||||
while xid in _pendingchangesets:
|
||||
xid = os.urandom(8)
|
||||
xid = confluent.util.stringify(base64.b64encode(os.urandom(8)))
|
||||
_pendingchangesets[xid] = event.Event()
|
||||
rpcpayload = cPickle.dumps({'function': function, 'args': args,
|
||||
'xid': xid})
|
||||
'xid': xid}, protocol=cfgproto)
|
||||
rpclen = len(rpcpayload)
|
||||
cfgleader.sendall(struct.pack('!Q', rpclen))
|
||||
cfgleader.sendall(rpcpayload)
|
||||
@@ -324,15 +328,24 @@ def exec_on_leader(function, *args):
|
||||
|
||||
|
||||
def exec_on_followers(fnname, *args):
|
||||
global _txcount
|
||||
pushes = eventlet.GreenPool()
|
||||
# Check health of collective prior to attempting
|
||||
for _ in pushes.starmap(
|
||||
_push_rpc, [(cfgstreams[s], b'') for s in cfgstreams]):
|
||||
pass
|
||||
if len(cfgstreams) < (len(_cfgstore['collective']) // 2):
|
||||
# the leader counts in addition to registered streams
|
||||
raise exc.DegradedCollective()
|
||||
exec_on_followers_unconditional(fnname, *args)
|
||||
|
||||
|
||||
def exec_on_followers_unconditional(fnname, *args):
|
||||
global _txcount
|
||||
pushes = eventlet.GreenPool()
|
||||
_txcount += 1
|
||||
payload = cPickle.dumps({'function': fnname, 'args': args,
|
||||
'txcount': _txcount})
|
||||
for res in pushes.starmap(
|
||||
'txcount': _txcount}, protocol=lowestver)
|
||||
for _ in pushes.starmap(
|
||||
_push_rpc, [(cfgstreams[s], payload) for s in cfgstreams]):
|
||||
pass
|
||||
|
||||
@@ -378,9 +391,15 @@ def init_masterkey(password=None, autogen=True):
|
||||
|
||||
def _push_rpc(stream, payload):
|
||||
with _rpclock:
|
||||
stream.sendall(struct.pack('!Q', len(payload)))
|
||||
if len(payload):
|
||||
stream.sendall(payload)
|
||||
try:
|
||||
stream.sendall(struct.pack('!Q', len(payload)))
|
||||
if len(payload):
|
||||
stream.sendall(payload)
|
||||
return True
|
||||
except Exception:
|
||||
logException()
|
||||
del cfgstreams[stream]
|
||||
stream.close()
|
||||
|
||||
|
||||
def decrypt_value(cryptvalue,
|
||||
@@ -401,12 +420,12 @@ def decrypt_value(cryptvalue,
|
||||
raise Exception("bad HMAC value on crypted value")
|
||||
decrypter = AES.new(key, AES.MODE_CBC, iv)
|
||||
value = decrypter.decrypt(cipherdata)
|
||||
padsize = ord(value[-1])
|
||||
padsize = bytearray(value)[-1]
|
||||
pad = value[-padsize:]
|
||||
# Note that I cannot grasp what could be done with a subliminal
|
||||
# channel in padding in this case, but check the padding anyway
|
||||
for padbyte in pad:
|
||||
if ord(padbyte) != padsize:
|
||||
for padbyte in bytearray(pad):
|
||||
if padbyte != padsize:
|
||||
raise Exception("bad padding in encrypted value")
|
||||
return value[0:-padsize]
|
||||
else:
|
||||
@@ -474,7 +493,7 @@ def crypt_value(value,
|
||||
crypter = AES.new(key, AES.MODE_GCM, nonce=iv)
|
||||
value = confluent.util.stringify(value).encode('utf-8')
|
||||
cryptval, hmac = crypter.encrypt_and_digest(value)
|
||||
return iv, cryptval, hmac, '\x02'
|
||||
return iv, cryptval, hmac, b'\x02'
|
||||
|
||||
|
||||
def _load_dict_from_dbm(dpath, tdb):
|
||||
@@ -546,9 +565,14 @@ def set_global(globalname, value, sync=True):
|
||||
ConfigManager._bg_sync_to_file()
|
||||
|
||||
cfgstreams = {}
|
||||
def relay_slaved_requests(name, listener):
|
||||
def relay_slaved_requests(name, listener, vers):
|
||||
global cfgleader
|
||||
global _hasquorum
|
||||
global lowestver
|
||||
if vers > 2 and sys.version_info[0] < 3:
|
||||
vers = 2
|
||||
if vers < lowestver:
|
||||
lowestver = vers
|
||||
pushes = eventlet.GreenPool()
|
||||
if name not in _followerlocks:
|
||||
_followerlocks[name] = gthread.RLock()
|
||||
@@ -565,11 +589,18 @@ def relay_slaved_requests(name, listener):
|
||||
lh = StreamHandler(listener)
|
||||
_hasquorum = len(cfgstreams) >= (
|
||||
len(_cfgstore['collective']) // 2)
|
||||
payload = cPickle.dumps({'quorum': _hasquorum})
|
||||
for _ in pushes.starmap(
|
||||
_push_rpc,
|
||||
[(cfgstreams[s], payload) for s in cfgstreams]):
|
||||
pass
|
||||
_newquorum = None
|
||||
while _hasquorum != _newquorum:
|
||||
if _newquorum is not None:
|
||||
_hasquorum = _newquorum
|
||||
payload = cPickle.dumps({'quorum': _hasquorum}, protocol=lowestver)
|
||||
for _ in pushes.starmap(
|
||||
_push_rpc,
|
||||
[(cfgstreams[s], payload) for s in cfgstreams]):
|
||||
pass
|
||||
_newquorum = len(cfgstreams) >= (
|
||||
len(_cfgstore['collective']) // 2)
|
||||
_hasquorum = _newquorum
|
||||
if _hasquorum and _pending_collective_updates:
|
||||
apply_pending_collective_updates()
|
||||
msg = lh.get_next_msg()
|
||||
@@ -578,7 +609,7 @@ def relay_slaved_requests(name, listener):
|
||||
raise Exception("Unexpected loss of node in followers: " + name)
|
||||
sz = struct.unpack('!Q', msg)[0]
|
||||
if sz != 0:
|
||||
rpc = ''
|
||||
rpc = b''
|
||||
while len(rpc) < sz:
|
||||
nrpc = listener.recv(sz - len(rpc))
|
||||
if not nrpc:
|
||||
@@ -591,8 +622,10 @@ def relay_slaved_requests(name, listener):
|
||||
except Exception as e:
|
||||
exc = e
|
||||
if 'xid' in rpc:
|
||||
_push_rpc(listener, cPickle.dumps({'xid': rpc['xid'],
|
||||
'exc': exc}))
|
||||
res = _push_rpc(listener, cPickle.dumps({'xid': rpc['xid'],
|
||||
'exc': exc}, protocol=vers))
|
||||
if not res:
|
||||
break
|
||||
try:
|
||||
msg = lh.get_next_msg()
|
||||
except Exception:
|
||||
@@ -609,7 +642,7 @@ def relay_slaved_requests(name, listener):
|
||||
if cfgstreams:
|
||||
_hasquorum = len(cfgstreams) >= (
|
||||
len(_cfgstore['collective']) // 2)
|
||||
payload = cPickle.dumps({'quorum': _hasquorum})
|
||||
payload = cPickle.dumps({'quorum': _hasquorum}, protocol=lowestver)
|
||||
for _ in pushes.starmap(
|
||||
_push_rpc,
|
||||
[(cfgstreams[s], payload) for s in cfgstreams]):
|
||||
@@ -637,7 +670,9 @@ class StreamHandler(object):
|
||||
if confluent.util.monotonic_time() > self.expiry:
|
||||
return None
|
||||
if confluent.util.monotonic_time() > self.keepalive:
|
||||
_push_rpc(self.sock, b'') # nulls are a keepalive
|
||||
res = _push_rpc(self.sock, b'') # nulls are a keepalive
|
||||
if not res:
|
||||
return None
|
||||
self.keepalive = confluent.util.monotonic_time() + 20
|
||||
self.expiry = confluent.util.monotonic_time() + 60
|
||||
msg = self.sock.recv(8)
|
||||
@@ -649,15 +684,19 @@ class StreamHandler(object):
|
||||
self.sock = None
|
||||
|
||||
|
||||
def stop_following(replacement=None):
|
||||
def stop_following(replacement=None, proto=2):
|
||||
with _leaderlock:
|
||||
global cfgleader
|
||||
global cfgproto
|
||||
if cfgleader and not isinstance(cfgleader, bool):
|
||||
try:
|
||||
cfgleader.close()
|
||||
except Exception:
|
||||
pass
|
||||
cfgleader = replacement
|
||||
if proto > 2 and sys.version_info[0] < 3:
|
||||
proto = 2
|
||||
cfgproto = proto
|
||||
|
||||
def stop_leading():
|
||||
for stream in list(cfgstreams):
|
||||
@@ -715,20 +754,21 @@ def commit_clear():
|
||||
ConfigManager._bg_sync_to_file()
|
||||
|
||||
cfgleader = None
|
||||
cfgproto = 2
|
||||
|
||||
|
||||
def follow_channel(channel):
|
||||
def follow_channel(channel, proto=2):
|
||||
global _txcount
|
||||
global _hasquorum
|
||||
try:
|
||||
stop_leading()
|
||||
stop_following(channel)
|
||||
stop_following(channel, proto)
|
||||
lh = StreamHandler(channel)
|
||||
msg = lh.get_next_msg()
|
||||
while msg:
|
||||
sz = struct.unpack('!Q', msg)[0]
|
||||
if sz != 0:
|
||||
rpc = ''
|
||||
rpc = b''
|
||||
while len(rpc) < sz:
|
||||
nrpc = channel.recv(sz - len(rpc))
|
||||
if not nrpc:
|
||||
@@ -749,7 +789,9 @@ def follow_channel(channel):
|
||||
_pendingchangesets[rpc['xid']].send()
|
||||
if 'quorum' in rpc:
|
||||
_hasquorum = rpc['quorum']
|
||||
_push_rpc(channel, b'') # use null as ACK
|
||||
res = _push_rpc(channel, b'') # use null as ACK
|
||||
if not res:
|
||||
break
|
||||
msg = lh.get_next_msg()
|
||||
finally:
|
||||
# mark the connection as broken
|
||||
@@ -766,6 +808,34 @@ def add_collective_member(name, address, fingerprint):
|
||||
exec_on_followers('_true_add_collective_member', name, address, fingerprint)
|
||||
_true_add_collective_member(name, address, fingerprint)
|
||||
|
||||
def del_collective_member(name):
|
||||
if cfgleader and not isinstance(cfgleader, bool):
|
||||
return exec_on_leader('del_collective_member', name)
|
||||
if cfgstreams:
|
||||
exec_on_followers_unconditional('_true_del_collective_member', name)
|
||||
_true_del_collective_member(name)
|
||||
|
||||
def _true_del_collective_member(name, sync=True):
|
||||
global cfgleader
|
||||
name = confluent.util.stringify(name)
|
||||
if _cfgstore is None:
|
||||
return
|
||||
if 'collective' not in _cfgstore:
|
||||
return
|
||||
if name not in _cfgstore['collective']:
|
||||
return
|
||||
del _cfgstore['collective'][name]
|
||||
with _dirtylock:
|
||||
if 'collectivedirty' not in _cfgstore:
|
||||
_cfgstore['collectivedirty'] = set([])
|
||||
_cfgstore['collectivedirty'].add(name)
|
||||
if len(_cfgstore['collective']) < 2:
|
||||
del _cfgstore['collective']
|
||||
cfgleader = None
|
||||
if sync:
|
||||
ConfigManager._bg_sync_to_file()
|
||||
|
||||
|
||||
_pending_collective_updates = {}
|
||||
|
||||
|
||||
@@ -1091,6 +1161,8 @@ class ConfigManager(object):
|
||||
attribute, match = expression.split('=')
|
||||
else:
|
||||
raise Exception('Invalid Expression')
|
||||
if attribute.startswith('secret.'):
|
||||
raise Exception('Filter by secret attributes is not supported')
|
||||
for node in nodes:
|
||||
try:
|
||||
currvals = [self._cfgstore['nodes'][node][attribute]['value']]
|
||||
@@ -1580,6 +1652,7 @@ class ConfigManager(object):
|
||||
if group in self._cfgstore['nodes'][node]['groups']:
|
||||
self._cfgstore['nodes'][node]['groups'].remove(group)
|
||||
self._node_removed_from_group(node, group, changeset)
|
||||
_mark_dirtykey('nodes', node, self.tenant)
|
||||
for node in nodes:
|
||||
if node not in self._cfgstore['nodes']:
|
||||
self._cfgstore['nodes'][node] = {'groups': [group]}
|
||||
@@ -2233,7 +2306,7 @@ class ConfigManager(object):
|
||||
target = dumpdata[confarea][element][attribute]['cryptvalue']
|
||||
cryptval = []
|
||||
for value in target:
|
||||
cryptval.append(base64.b64encode(value))
|
||||
cryptval.append(confluent.util.stringify(base64.b64encode(value)))
|
||||
if attribute == 'cryptpass':
|
||||
dumpdata[confarea][element][attribute] = '!'.join(cryptval)
|
||||
else:
|
||||
@@ -2326,31 +2399,37 @@ class ConfigManager(object):
|
||||
for globalkey in dirtyglobals:
|
||||
if globalkey in _cfgstore['globals']:
|
||||
globalf[globalkey] = \
|
||||
cPickle.dumps(_cfgstore['globals'][globalkey])
|
||||
cPickle.dumps(_cfgstore['globals'][globalkey], protocol=cPickle.HIGHEST_PROTOCOL)
|
||||
else:
|
||||
if globalkey in globalf:
|
||||
del globalf[globalkey]
|
||||
finally:
|
||||
globalf.close()
|
||||
if fullsync or 'collectivedirty' in _cfgstore:
|
||||
collectivef = dbm.open(os.path.join(cls._cfgdir, "collective"),
|
||||
'c', 384)
|
||||
try:
|
||||
if fullsync:
|
||||
colls = _cfgstore['collective']
|
||||
else:
|
||||
with _dirtylock:
|
||||
colls = copy.deepcopy(_cfgstore['collectivedirty'])
|
||||
del _cfgstore['collectivedirty']
|
||||
for coll in colls:
|
||||
if coll in _cfgstore['collective']:
|
||||
collectivef[coll] = cPickle.dumps(
|
||||
_cfgstore['collective'][coll])
|
||||
if len(_cfgstore.get('collective', ())) > 1:
|
||||
collectivef = dbm.open(os.path.join(cls._cfgdir, "collective"),
|
||||
'c', 384)
|
||||
try:
|
||||
if fullsync:
|
||||
colls = _cfgstore['collective']
|
||||
else:
|
||||
if coll in collectivef:
|
||||
del globalf[coll]
|
||||
finally:
|
||||
collectivef.close()
|
||||
with _dirtylock:
|
||||
colls = copy.deepcopy(_cfgstore['collectivedirty'])
|
||||
del _cfgstore['collectivedirty']
|
||||
for coll in colls:
|
||||
if coll in _cfgstore['collective']:
|
||||
collectivef[coll] = cPickle.dumps(
|
||||
_cfgstore['collective'][coll], protocol=cPickle.HIGHEST_PROTOCOL)
|
||||
else:
|
||||
if coll in collectivef:
|
||||
del collectivef[coll]
|
||||
finally:
|
||||
collectivef.close()
|
||||
else:
|
||||
try:
|
||||
os.remove(os.path.join(cls._cfgdir, "collective"))
|
||||
except OSError:
|
||||
pass
|
||||
if fullsync:
|
||||
pathname = cls._cfgdir
|
||||
currdict = _cfgstore['main']
|
||||
@@ -2359,7 +2438,7 @@ class ConfigManager(object):
|
||||
dbf = dbm.open(os.path.join(pathname, category), 'c', 384) # 0600
|
||||
try:
|
||||
for ck in currdict[category]:
|
||||
dbf[ck] = cPickle.dumps(currdict[category][ck])
|
||||
dbf[ck] = cPickle.dumps(currdict[category][ck], protocol=cPickle.HIGHEST_PROTOCOL)
|
||||
finally:
|
||||
dbf.close()
|
||||
elif 'dirtykeys' in _cfgstore:
|
||||
@@ -2383,7 +2462,7 @@ class ConfigManager(object):
|
||||
if ck in dbf:
|
||||
del dbf[ck]
|
||||
else:
|
||||
dbf[ck] = cPickle.dumps(currdict[category][ck])
|
||||
dbf[ck] = cPickle.dumps(currdict[category][ck], protocol=cPickle.HIGHEST_PROTOCOL)
|
||||
finally:
|
||||
dbf.close()
|
||||
willrun = False
|
||||
@@ -2447,20 +2526,21 @@ def _dump_keys(password, dojson=True):
|
||||
init_masterkey()
|
||||
cryptkey = _format_key(_masterkey, password=password)
|
||||
if 'passphraseprotected' in cryptkey:
|
||||
cryptkey = '!'.join(map(base64.b64encode,
|
||||
cryptkey['passphraseprotected']))
|
||||
cryptkey = '!'.join(
|
||||
[confluent.util.stringify(base64.b64encode(x))
|
||||
for x in cryptkey['passphraseprotected']])
|
||||
else:
|
||||
cryptkey = '*unencrypted:{0}'.format(base64.b64encode(
|
||||
cryptkey['unencryptedvalue']))
|
||||
cryptkey = '*unencrypted:{0}'.format(confluent.util.stringify(base64.b64encode(
|
||||
cryptkey['unencryptedvalue'])))
|
||||
keydata = {'cryptkey': cryptkey}
|
||||
if _masterintegritykey is not None:
|
||||
integritykey = _format_key(_masterintegritykey, password=password)
|
||||
if 'passphraseprotected' in integritykey:
|
||||
integritykey = '!'.join(map(base64.b64encode,
|
||||
integritykey['passphraseprotected']))
|
||||
integritykey = '!'.join([confluent.util.stringify(base64.b64encode(x)) for x in
|
||||
integritykey['passphraseprotected']])
|
||||
else:
|
||||
integritykey = '*unencrypted:{0}'.format(base64.b64encode(
|
||||
integritykey['unencryptedvalue']))
|
||||
integritykey = '*unencrypted:{0}'.format(confluent.util.stringify(base64.b64encode(
|
||||
integritykey['unencryptedvalue'])))
|
||||
keydata['integritykey'] = integritykey
|
||||
if dojson:
|
||||
return json.dumps(keydata, sort_keys=True, indent=4, separators=(',', ': '))
|
||||
|
||||
@@ -106,7 +106,7 @@ def pytechars2line(chars, maxlen=None):
|
||||
len = 1
|
||||
for charidx in range(maxlen):
|
||||
char = chars[charidx]
|
||||
csi = []
|
||||
csi = bytearray([])
|
||||
if char.fg != lfg:
|
||||
csi.append(30 + pytecolors2ansi[char.fg])
|
||||
lfg = char.fg
|
||||
@@ -129,7 +129,7 @@ def pytechars2line(chars, maxlen=None):
|
||||
lr = char.reverse
|
||||
csi.append(7 if lr else 27)
|
||||
if csi:
|
||||
line += b'\x1b[' + b';'.join(['{0}'.format(x) for x in csi]) + b'm'
|
||||
line += b'\x1b[' + b';'.join(['{0}'.format(x).encode('utf-8') for x in csi]) + b'm'
|
||||
if not hasdata and char.data.rstrip():
|
||||
hasdata = True
|
||||
chardata = char.data
|
||||
@@ -362,13 +362,17 @@ class ConsoleHandler(object):
|
||||
if self.reconnect:
|
||||
self.reconnect.cancel()
|
||||
self.reconnect = None
|
||||
strerror = ('The console.method attribute for this node is '
|
||||
'not configured,\r\nset it to a valid value for console '
|
||||
'function')
|
||||
try:
|
||||
self._console = list(plugin.handle_path(
|
||||
self._plugin_path.format(self.node),
|
||||
"create", self.cfgmgr))[0]
|
||||
except (exc.NotImplementedException, exc.NotFoundException):
|
||||
self._console = None
|
||||
except:
|
||||
except Exception as e:
|
||||
strerror = str(e)
|
||||
if _tracelog:
|
||||
_tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
|
||||
event=log.Events.stacktrace)
|
||||
@@ -381,13 +385,9 @@ class ConsoleHandler(object):
|
||||
self._send_rcpts({'connectstate': self.connectstate,
|
||||
'error': self.error})
|
||||
self.feedbuffer(
|
||||
'\x1bc\x1b[2J\x1b[1;1H[The console.method attribute for this node is '
|
||||
'not configured,\r\nset it to a valid value for console '
|
||||
'function]')
|
||||
'\x1bc\x1b[2J\x1b[1;1H[{0}]'.format(strerror))
|
||||
self._send_rcpts(
|
||||
'\x1bc\x1b[2J\x1b[1;1H[The console.method attribute for this node is '
|
||||
'not configured,\r\nset it to a valid value for console '
|
||||
'function]')
|
||||
'\x1bc\x1b[2J\x1b[1;1H[{0}]'.format(strerror))
|
||||
self.clearerror = True
|
||||
return
|
||||
if self.clearerror:
|
||||
@@ -531,9 +531,11 @@ class ConsoleHandler(object):
|
||||
if data == conapi.ConsoleEvent.Disconnect:
|
||||
self._got_disconnected()
|
||||
return
|
||||
elif data == '':
|
||||
elif data in (b'', u''):
|
||||
# ignore empty strings from a cconsole provider
|
||||
return
|
||||
if not isinstance(data, bytes):
|
||||
data = data.encode('utf-8')
|
||||
if b'\x1b[?1l' in data: # request for ansi mode cursor keys
|
||||
self.appmodedetected = False
|
||||
if b'\x1b[?1h' in data: # remember the session wants the client to use
|
||||
@@ -616,6 +618,8 @@ class ConsoleHandler(object):
|
||||
def write(self, data):
|
||||
if self.connectstate == 'connected':
|
||||
try:
|
||||
if isinstance(data, str) and not isinstance(data, bytes):
|
||||
data = data.encode('utf-8')
|
||||
self._console.write(data)
|
||||
except Exception:
|
||||
_tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
|
||||
|
||||
@@ -63,14 +63,16 @@ import itertools
|
||||
import os
|
||||
try:
|
||||
import cPickle as pickle
|
||||
pargs = {}
|
||||
except ImportError:
|
||||
import pickle
|
||||
pargs = {'encoding': 'utf-8'}
|
||||
import socket
|
||||
import struct
|
||||
import sys
|
||||
|
||||
pluginmap = {}
|
||||
dispatch_plugins = (b'ipmi', u'ipmi')
|
||||
dispatch_plugins = (b'ipmi', u'ipmi', b'redfish', u'redfish', b'tsmsol', u'tsmsol')
|
||||
|
||||
|
||||
def seek_element(currplace, currkey):
|
||||
@@ -222,7 +224,7 @@ def _init_core():
|
||||
'advanced': PluginRoute({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
'storage': {
|
||||
@@ -450,7 +452,7 @@ def show_usergroup(groupname, configmanager):
|
||||
def show_user(name, configmanager):
|
||||
userobj = configmanager.get_user(name)
|
||||
rv = {}
|
||||
for attr in attrscheme.user.iterkeys():
|
||||
for attr in attrscheme.user:
|
||||
rv[attr] = None
|
||||
if attr == 'password':
|
||||
if 'cryptpass' in userobj:
|
||||
@@ -687,7 +689,10 @@ def handle_dispatch(connection, cert, dispatch, peername):
|
||||
cfm.get_collective_member(peername)['fingerprint'], cert):
|
||||
connection.close()
|
||||
return
|
||||
dispatch = pickle.loads(dispatch)
|
||||
pversion = 0
|
||||
if bytearray(dispatch)[0] == 0x80:
|
||||
pversion = bytearray(dispatch)[1]
|
||||
dispatch = pickle.loads(dispatch, **pargs)
|
||||
configmanager = cfm.ConfigManager(dispatch['tenant'])
|
||||
nodes = dispatch['nodes']
|
||||
inputdata = dispatch['inputdata']
|
||||
@@ -723,18 +728,18 @@ def handle_dispatch(connection, cert, dispatch, peername):
|
||||
configmanager=configmanager,
|
||||
inputdata=inputdata))
|
||||
for res in itertools.chain(*passvalues):
|
||||
_forward_rsp(connection, res)
|
||||
_forward_rsp(connection, res, pversion)
|
||||
except Exception as res:
|
||||
_forward_rsp(connection, res)
|
||||
_forward_rsp(connection, res, pversion)
|
||||
connection.sendall('\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
|
||||
|
||||
def _forward_rsp(connection, res):
|
||||
def _forward_rsp(connection, res, pversion):
|
||||
try:
|
||||
r = pickle.dumps(res)
|
||||
r = pickle.dumps(res, protocol=pversion)
|
||||
except TypeError:
|
||||
r = pickle.dumps(Exception(
|
||||
'Cannot serialize error, check collective.manager error logs for details' + str(res)))
|
||||
'Cannot serialize error, check collective.manager error logs for details' + str(res)), protocol=pversion)
|
||||
rlen = len(r)
|
||||
if not rlen:
|
||||
return
|
||||
@@ -963,12 +968,20 @@ def dispatch_request(nodes, manager, element, configmanager, inputdata,
|
||||
if not util.cert_matches(a['fingerprint'], remote.getpeercert(
|
||||
binary_form=True)):
|
||||
raise Exception("Invalid certificate on peer")
|
||||
tlvdata.recv(remote)
|
||||
banner = tlvdata.recv(remote)
|
||||
vers = banner.split()[2]
|
||||
if vers == b'v0':
|
||||
pvers = 2
|
||||
elif vers == b'v1':
|
||||
pvers = 4
|
||||
if sys.version_info[0] < 3:
|
||||
pvers = 2
|
||||
tlvdata.recv(remote)
|
||||
myname = collective.get_myname()
|
||||
dreq = pickle.dumps({'name': myname, 'nodes': list(nodes),
|
||||
'path': element,'tenant': configmanager.tenant,
|
||||
'operation': operation, 'inputdata': inputdata})
|
||||
'operation': operation, 'inputdata': inputdata},
|
||||
protocol=pvers)
|
||||
tlvdata.send(remote, {'dispatch': {'name': myname, 'length': len(dreq)}})
|
||||
remote.sendall(dreq)
|
||||
while True:
|
||||
@@ -1015,7 +1028,10 @@ def dispatch_request(nodes, manager, element, configmanager, inputdata,
|
||||
a['name']))
|
||||
return
|
||||
rsp += nrsp
|
||||
rsp = pickle.loads(rsp)
|
||||
try:
|
||||
rsp = pickle.loads(rsp, **pargs)
|
||||
except UnicodeDecodeError:
|
||||
rsp = pickle.loads(rsp, encoding='latin1')
|
||||
if isinstance(rsp, Exception):
|
||||
raise rsp
|
||||
yield rsp
|
||||
|
||||
@@ -91,6 +91,11 @@ import eventlet.semaphore
|
||||
autosensors = set()
|
||||
scanner = None
|
||||
|
||||
try:
|
||||
unicode
|
||||
except NameError:
|
||||
unicode = str
|
||||
|
||||
class nesteddict(dict):
|
||||
|
||||
def __missing__(self, key):
|
||||
@@ -371,7 +376,7 @@ def handle_autosense_config(operation, inputdata):
|
||||
yield msg.KeyValueData({'enabled': autosense})
|
||||
elif operation == 'update':
|
||||
enabled = inputdata['enabled']
|
||||
if type(enabled) in (unicode, str):
|
||||
if type(enabled) in (unicode, bytes):
|
||||
enabled = enabled.lower() in ('true', '1', 'y', 'yes', 'enable',
|
||||
'enabled')
|
||||
if autosense == enabled:
|
||||
@@ -705,7 +710,8 @@ def detected(info):
|
||||
|
||||
def b64tohex(b64str):
|
||||
bd = base64.b64decode(b64str)
|
||||
return ''.join(['{0:02x}'.format(ord(x)) for x in bd])
|
||||
bd = bytearray(bd)
|
||||
return ''.join(['{0:02x}'.format(x) for x in bd])
|
||||
|
||||
|
||||
def get_enclosure_chain_head(nodename, cfg):
|
||||
@@ -943,9 +949,9 @@ def eval_node(cfg, handler, info, nodename, manual=False):
|
||||
# raise exc.InvalidArgumentException(errorstr)
|
||||
# log.log({'error': errorstr})
|
||||
if encuuid in pending_by_uuid:
|
||||
pending_by_uuid[encuuid].add(info)
|
||||
pending_by_uuid[encuuid].append(info)
|
||||
else:
|
||||
pending_by_uuid[encuuid] = set([info])
|
||||
pending_by_uuid[encuuid] = [info]
|
||||
return
|
||||
# We found the real smm, replace the list with the actual smm
|
||||
# to continue
|
||||
@@ -1088,6 +1094,10 @@ def discover_node(cfg, handler, info, nodename, manual):
|
||||
info['discostatus'] = 'discovered'
|
||||
for i in pending_by_uuid.get(curruuid, []):
|
||||
eventlet.spawn_n(_recheck_single_unknown_info, cfg, i)
|
||||
try:
|
||||
del pending_by_uuid[curruuid]
|
||||
except KeyError:
|
||||
pass
|
||||
return True
|
||||
log.log({'info': 'Detected {0}, but discovery.policy is not set to a '
|
||||
'value allowing discovery (open or permissive)'.format(
|
||||
@@ -1207,6 +1217,7 @@ def rescan():
|
||||
def start_detection():
|
||||
global attribwatcher
|
||||
global rechecker
|
||||
global rechecktime
|
||||
_map_unique_ids()
|
||||
cfg = cfm.ConfigManager(None)
|
||||
allnodes = cfg.list_nodes()
|
||||
|
||||
@@ -68,17 +68,21 @@ class NodeHandler(object):
|
||||
def _savecert(self, certificate):
|
||||
self._fp = certificate
|
||||
return True
|
||||
|
||||
|
||||
def get_node_credentials(self, nodename, creds, defuser, defpass):
|
||||
user = creds.get(nodename, {}).get(
|
||||
'secret.hardwaremanagementuser', {}).get('value', None)
|
||||
havecustomcreds = False
|
||||
if user and not isinstance(user, str):
|
||||
user = user.decode('utf8')
|
||||
if user is not None and user != defuser:
|
||||
havecustomcreds = True
|
||||
else:
|
||||
user = defuser
|
||||
passwd = creds.get(nodename, {}).get(
|
||||
'secret.hardwaremanagementpassword', {}).get('value', None)
|
||||
if passwd and not isinstance(passwd, str):
|
||||
passwd = passwd.decode('utf8')
|
||||
if passwd is not None and passwd != defpass:
|
||||
havecustomcreds = True
|
||||
else:
|
||||
|
||||
@@ -12,9 +12,11 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import codecs
|
||||
import confluent.discovery.handlers.bmc as bmchandler
|
||||
import pyghmi.exceptions as pygexc
|
||||
import pyghmi.ipmi.private.util as pygutil
|
||||
import confluent.util as util
|
||||
import struct
|
||||
|
||||
class NodeHandler(bmchandler.NodeHandler):
|
||||
@@ -37,8 +39,9 @@ class NodeHandler(bmchandler.NodeHandler):
|
||||
if wronguuid:
|
||||
# we need to fix the first three portions of the uuid
|
||||
uuidprefix = wronguuid.split('-')[:3]
|
||||
uuidprefix = struct.pack(
|
||||
'<IHH', *[int(x, 16) for x in uuidprefix]).encode('hex')
|
||||
uuidprefix = codecs.encode(struct.pack(
|
||||
'<IHH', *[int(x, 16) for x in uuidprefix]), 'hex')
|
||||
uuidprefix = util.stringify(uuidprefix)
|
||||
uuidprefix = uuidprefix[:8] + '-' + uuidprefix[8:12] + '-' + \
|
||||
uuidprefix[12:16]
|
||||
self.info['uuid'] = uuidprefix + '-' + '-'.join(
|
||||
|
||||
@@ -12,14 +12,19 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import codecs
|
||||
import confluent.discovery.handlers.bmc as bmchandler
|
||||
import confluent.exceptions as exc
|
||||
import eventlet
|
||||
webclient = eventlet.import_patched('pyghmi.util.webclient')
|
||||
import struct
|
||||
import urllib
|
||||
try:
|
||||
from urllib import urlencode
|
||||
except ImportError:
|
||||
from urllib.parse import urlencode
|
||||
import eventlet.support.greendns
|
||||
import confluent.netutil as netutil
|
||||
import confluent.util as util
|
||||
getaddrinfo = eventlet.support.greendns.getaddrinfo
|
||||
|
||||
from xml.etree.ElementTree import fromstring
|
||||
@@ -27,8 +32,9 @@ from xml.etree.ElementTree import fromstring
|
||||
def fixuuid(baduuid):
|
||||
# SMM dumps it out in hex
|
||||
uuidprefix = (baduuid[:8], baduuid[8:12], baduuid[12:16])
|
||||
a = struct.pack('<IHH', *[int(x, 16) for x in uuidprefix]).encode(
|
||||
a = codecs.encode(struct.pack('<IHH', *[int(x, 16) for x in uuidprefix]),
|
||||
'hex')
|
||||
a = util.stringify(a)
|
||||
uuid = (a[:8], a[8:12], a[12:16], baduuid[16:20], baduuid[20:])
|
||||
return '-'.join(uuid).lower()
|
||||
|
||||
@@ -92,7 +98,7 @@ class NodeHandler(bmchandler.NodeHandler):
|
||||
setdata += ',v4Gateway:{0}'.format(gateway)
|
||||
wc.request('POST', '/data', setdata)
|
||||
rsp = wc.getresponse()
|
||||
rspdata = rsp.read()
|
||||
rspdata = util.stringify(rsp.read())
|
||||
if '<statusCode>0' not in rspdata:
|
||||
raise Exception("Error configuring SMM Network")
|
||||
return
|
||||
@@ -110,16 +116,16 @@ class NodeHandler(bmchandler.NodeHandler):
|
||||
'password': 'PASSW0RD',
|
||||
}
|
||||
headers = {'Connection': 'keep-alive', 'Content-Type': 'application/x-www-form-urlencoded'}
|
||||
wc.request('POST', '/data/login', urllib.urlencode(authdata), headers)
|
||||
wc.request('POST', '/data/login', urlencode(authdata), headers)
|
||||
rsp = wc.getresponse()
|
||||
rspdata = rsp.read()
|
||||
rspdata = util.stringify(rsp.read())
|
||||
if 'authResult>0' not in rspdata:
|
||||
# default credentials are refused, try with the actual
|
||||
authdata['user'] = username
|
||||
authdata['password'] = password
|
||||
wc.request('POST', '/data/login', urllib.urlencode(authdata), headers)
|
||||
wc.request('POST', '/data/login', urlencode(authdata), headers)
|
||||
rsp = wc.getresponse()
|
||||
rspdata = rsp.read()
|
||||
rspdata = util.stringify(rsp.read())
|
||||
if 'renew_account' in rspdata:
|
||||
raise Exception('Configured password has expired')
|
||||
if 'authResult>0' not in rspdata:
|
||||
@@ -133,13 +139,13 @@ class NodeHandler(bmchandler.NodeHandler):
|
||||
tokens = fromstring(rspdata)
|
||||
st2 = tokens.findall('st2')[0].text
|
||||
wc.set_header('ST2', st2)
|
||||
wc.request('POST', '/data/changepwd', urllib.urlencode(passwdchange))
|
||||
wc.request('POST', '/data/changepwd', urlencode(passwdchange))
|
||||
rsp = wc.getresponse()
|
||||
rspdata = rsp.read()
|
||||
authdata['password'] = password
|
||||
wc.request('POST', '/data/login', urllib.urlencode(authdata), headers)
|
||||
wc.request('POST', '/data/login', urlencode(authdata), headers)
|
||||
rsp = wc.getresponse()
|
||||
rspdata = rsp.read()
|
||||
rspdata = util.stringify(rsp.read())
|
||||
if 'authResult>0' in rspdata:
|
||||
tokens = fromstring(rspdata)
|
||||
st2 = tokens.findall('st2')[0].text
|
||||
@@ -153,7 +159,7 @@ class NodeHandler(bmchandler.NodeHandler):
|
||||
rsp = wc.getresponse()
|
||||
rspdata = rsp.read()
|
||||
authdata['user'] = username
|
||||
wc.request('POST', '/data/login', urllib.urlencode(authdata, headers))
|
||||
wc.request('POST', '/data/login', urlencode(authdata, headers))
|
||||
rsp = wc.getresponse()
|
||||
rspdata = rsp.read()
|
||||
tokens = fromstring(rspdata)
|
||||
@@ -175,6 +181,10 @@ class NodeHandler(bmchandler.NodeHandler):
|
||||
'secret.hardwaremanagementuser', {}).get('value', 'USERID')
|
||||
passwd = creds.get(nodename, {}).get(
|
||||
'secret.hardwaremanagementpassword', {}).get('value', 'PASSW0RD')
|
||||
if not isinstance(username, str):
|
||||
username = username.decode('utf8')
|
||||
if not isinstance(passwd, str):
|
||||
passwd = passwd.decode('utf8')
|
||||
if passwd == 'PASSW0RD' and self.ruleset:
|
||||
raise Exception('Cannot support default password and setting password rules at same time')
|
||||
if passwd == 'PASSW0RD':
|
||||
|
||||
@@ -18,7 +18,11 @@ import confluent.netutil as netutil
|
||||
import confluent.util as util
|
||||
import eventlet.support.greendns
|
||||
import json
|
||||
import urllib
|
||||
try:
|
||||
from urllib import urlencode
|
||||
except ImportError:
|
||||
from urllib.parse import urlencode
|
||||
|
||||
getaddrinfo = eventlet.support.greendns.getaddrinfo
|
||||
|
||||
webclient = eventlet.import_patched('pyghmi.util.webclient')
|
||||
@@ -40,6 +44,13 @@ class NodeHandler(generic.NodeHandler):
|
||||
self.atdefault = True
|
||||
super(NodeHandler, self).__init__(info, configmanager)
|
||||
|
||||
def scan(self):
|
||||
c = webclient.SecureHTTPConnection(self.ipaddr, 443, verifycallback=self.validate_cert)
|
||||
i = c.grab_json_response('/redfish/v1/')
|
||||
uuid = i.get('UUID', None)
|
||||
if uuid:
|
||||
self.info['uuid'] = uuid
|
||||
|
||||
def validate_cert(self, certificate):
|
||||
# broadly speaking, merely checks consistency moment to moment,
|
||||
# but if https_cert gets stricter, this check means something
|
||||
@@ -51,10 +62,19 @@ class NodeHandler(generic.NodeHandler):
|
||||
'username': self.DEFAULT_USER,
|
||||
'password': self.DEFAULT_PASS,
|
||||
}
|
||||
wc = webclient.SecureHTTPConnection(self.ipaddr, 443, verifycallback=self.validate_cert)
|
||||
wc.set_header('Content-Type', 'application/json')
|
||||
authmode = 0
|
||||
if not self.trieddefault:
|
||||
wc = webclient.SecureHTTPConnection(self.ipaddr, 443, verifycallback=self.validate_cert)
|
||||
rsp, status = wc.grab_json_response_with_status('/api/session', urllib.urlencode(authdata))
|
||||
rsp, status = wc.grab_json_response_with_status('/api/session', authdata)
|
||||
if status == 403:
|
||||
wc.set_header('Content-Type', 'application/x-www-form-urlencoded')
|
||||
authmode = 1
|
||||
rsp, status = wc.grab_json_response_with_status('/api/session', urlencode(authdata))
|
||||
else:
|
||||
authmode = 2
|
||||
if status > 400:
|
||||
rsp = util.stringify(rsp)
|
||||
self.trieddefault = True
|
||||
if '555' in rsp:
|
||||
passchange = {
|
||||
@@ -64,9 +84,15 @@ class NodeHandler(generic.NodeHandler):
|
||||
'default_password': self.DEFAULT_PASS,
|
||||
'username': self.DEFAULT_USER
|
||||
}
|
||||
rsp, status = wc.grab_json_response_with_status('/api/reset-pass', urllib.urlencode(passchange))
|
||||
if authmode == 2:
|
||||
rsp, status = wc.grab_json_response_with_status('/api/reset-pass', passchange)
|
||||
else:
|
||||
rsp, status = wc.grab_json_response_with_status('/api/reset-pass', urlencode(passchange))
|
||||
authdata['password'] = self.targpass
|
||||
rsp, status = wc.grab_json_response_with_status('/api/session', urllib.urlencode(authdata))
|
||||
if authmode == 2:
|
||||
rsp, status = wc.grab_json_response_with_status('/api/session', authdata)
|
||||
else:
|
||||
rsp, status = wc.grab_json_response_with_status('/api/session', urlencode(authdata))
|
||||
self.csrftok = rsp['CSRFToken']
|
||||
self.channel = rsp['channel']
|
||||
self.curruser = self.DEFAULT_USER
|
||||
@@ -81,15 +107,23 @@ class NodeHandler(generic.NodeHandler):
|
||||
if self.curruser:
|
||||
authdata['username'] = self.curruser
|
||||
authdata['password'] = self.currpass
|
||||
rsp, status = wc.grab_json_response_with_status('/api/session', urllib.urlencode(authdata))
|
||||
if rsp.status != 200:
|
||||
if authmode != 1:
|
||||
rsp, status = wc.grab_json_response_with_status('/api/session', authdata)
|
||||
if authmode == 1 or status == 403:
|
||||
wc.set_header('Content-Type', 'application/x-www-form-urlencoded')
|
||||
rsp, status = wc.grab_json_response_with_status('/api/session', urlencode(authdata))
|
||||
if status != 200:
|
||||
return None
|
||||
self.csrftok = rsp['CSRFToken']
|
||||
self.channel = rsp['channel']
|
||||
return wc
|
||||
authdata['username'] = self.targuser
|
||||
authdata['password'] = self.targpass
|
||||
rsp, status = wc.grab_json_response_with_status('/api/session', urllib.urlencode(authdata))
|
||||
if authmode != 1:
|
||||
rsp, status = wc.grab_json_response_with_status('/api/session', authdata)
|
||||
if authmode == 1 or status == 403:
|
||||
wc.set_header('Content-Type', 'application/x-www-form-urlencoded')
|
||||
rsp, status = wc.grab_json_response_with_status('/api/session', urlencode(authdata))
|
||||
if status != 200:
|
||||
return None
|
||||
self.curruser = self.targuser
|
||||
@@ -108,6 +142,8 @@ class NodeHandler(generic.NodeHandler):
|
||||
cd = creds.get(nodename, {})
|
||||
user, passwd, _ = self.get_node_credentials(
|
||||
nodename, creds, self.DEFAULT_USER, self.DEFAULT_PASS)
|
||||
user = util.stringify(user)
|
||||
passwd = util.stringify(passwd)
|
||||
self.targuser = user
|
||||
self.targpass = passwd
|
||||
wc = self._get_wc()
|
||||
|
||||
@@ -13,7 +13,9 @@
|
||||
# limitations under the License.
|
||||
|
||||
import base64
|
||||
import codecs
|
||||
import confluent.discovery.handlers.imm as immhandler
|
||||
import confluent.exceptions as exc
|
||||
import confluent.netutil as netutil
|
||||
import confluent.util as util
|
||||
import errno
|
||||
@@ -31,7 +33,8 @@ getaddrinfo = eventlet.support.greendns.getaddrinfo
|
||||
def fixup_uuid(uuidprop):
|
||||
baduuid = ''.join(uuidprop.split())
|
||||
uuidprefix = (baduuid[:8], baduuid[8:12], baduuid[12:16])
|
||||
a = struct.pack('<IHH', *[int(x, 16) for x in uuidprefix]).encode('hex')
|
||||
a = codecs.encode(struct.pack('<IHH', *[int(x, 16) for x in uuidprefix]), 'hex')
|
||||
a = util.stringify(a)
|
||||
uuid = (a[:8], a[8:12], a[12:16], baduuid[16:20], baduuid[20:])
|
||||
return '-'.join(uuid).upper()
|
||||
|
||||
@@ -93,7 +96,8 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
ipmicmd.xraw_command(netfn=0x3a, command=0xf1, data=(1,))
|
||||
except pygexc.IpmiException as e:
|
||||
if (e.ipmicode != 193 and 'Unauthorized name' not in str(e) and
|
||||
'Incorrect password' not in str(e)):
|
||||
'Incorrect password' not in str(e) and
|
||||
str(e) != 'Session no longer connected'):
|
||||
# raise an issue if anything other than to be expected
|
||||
if disableipmi:
|
||||
_, _ = wc.grab_json_response_with_status(
|
||||
@@ -125,8 +129,8 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
raise
|
||||
return (None, None)
|
||||
pwdchanged = False
|
||||
adata = json.dumps({'username': username,
|
||||
'password': password
|
||||
adata = json.dumps({'username': util.stringify(username),
|
||||
'password': util.stringify(password)
|
||||
})
|
||||
headers = {'Connection': 'keep-alive',
|
||||
'Content-Type': 'application/json'}
|
||||
@@ -168,7 +172,12 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
pwdchanged = True
|
||||
if '_csrf_token' in wc.cookies:
|
||||
wc.set_header('X-XSRF-TOKEN', wc.cookies['_csrf_token'])
|
||||
if pwdchanged:
|
||||
# Remove the minimum change interval, to allow sane
|
||||
# password changes after provisional changes
|
||||
self.set_password_policy('')
|
||||
return (wc, pwdchanged)
|
||||
return (None, None)
|
||||
|
||||
@property
|
||||
def wc(self):
|
||||
@@ -202,7 +211,7 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
# however the target *will* demand a new password... if it's currently
|
||||
# PASSW0RD
|
||||
# use TempW0rd42 to avoid divulging a real password on the line
|
||||
# This is replacing one well known password (PASSW0RD) with another
|
||||
# This is replacing one well known password (PASSW0RD) with another
|
||||
# (TempW0rd42)
|
||||
passwd = 'TempW0rd42'
|
||||
wc, pwdchanged = self.get_webclient('USERID', 'PASSW0RD', passwd)
|
||||
@@ -224,9 +233,9 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
if wc:
|
||||
return wc
|
||||
|
||||
def set_password_policy(self):
|
||||
def set_password_policy(self, strruleset):
|
||||
ruleset = {'USER_GlobalMinPassChgInt': '0'}
|
||||
for rule in self.ruleset.split(','):
|
||||
for rule in strruleset.split(','):
|
||||
if '=' not in rule:
|
||||
continue
|
||||
name, value = rule.split('=')
|
||||
@@ -289,6 +298,8 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
userinfo = wc.grab_json_response('/api/dataset/imm_users')
|
||||
curruser = None
|
||||
uid = None
|
||||
user = util.stringify(user)
|
||||
passwd = util.stringify(passwd)
|
||||
for userent in userinfo['items'][0]['users']:
|
||||
if userent['users_user_name'] == user:
|
||||
curruser = userent
|
||||
@@ -346,15 +357,18 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
# between hypothetical secure path and today.
|
||||
dpp = self.configmanager.get_node_attributes(
|
||||
nodename, 'discovery.passwordrules')
|
||||
self.ruleset = dpp.get(nodename, {}).get(
|
||||
strruleset = dpp.get(nodename, {}).get(
|
||||
'discovery.passwordrules', {}).get('value', '')
|
||||
wc = self.wc
|
||||
creds = self.configmanager.get_node_attributes(
|
||||
self.nodename, ['secret.hardwaremanagementuser',
|
||||
'secret.hardwaremanagementpassword'], decrypt=True)
|
||||
user, passwd, isdefault = self.get_node_credentials(nodename, creds, 'USERID', 'PASSW0RD')
|
||||
self.set_password_policy()
|
||||
self.set_password_policy(strruleset)
|
||||
if self._atdefaultcreds:
|
||||
if isdefault and self.tmppasswd:
|
||||
raise Exception(
|
||||
'Request to use default credentials, but refused by target after it has been changed to {0}'.format(self.tmppasswd))
|
||||
if not isdefault:
|
||||
self._setup_xcc_account(user, passwd, wc)
|
||||
self._convert_sha256account(user, passwd, wc)
|
||||
|
||||
@@ -34,8 +34,8 @@ pxearchs = {
|
||||
|
||||
|
||||
def decode_uuid(rawguid):
|
||||
lebytes = struct.unpack_from('<IHH', buffer(rawguid[:8]))
|
||||
bebytes = struct.unpack_from('>HHI', buffer(rawguid[8:]))
|
||||
lebytes = struct.unpack_from('<IHH', rawguid[:8])
|
||||
bebytes = struct.unpack_from('>HHI', rawguid[8:])
|
||||
return '{0:08X}-{1:04X}-{2:04X}-{3:04X}-{4:04X}{5:08X}'.format(
|
||||
lebytes[0], lebytes[1], lebytes[2], bebytes[0], bebytes[1], bebytes[2]).lower()
|
||||
|
||||
|
||||
@@ -84,6 +84,8 @@ def _parse_SrvRply(parsed):
|
||||
:return:
|
||||
"""
|
||||
payload = parsed['payload']
|
||||
if len(payload) < 4:
|
||||
return
|
||||
ecode, ucount = struct.unpack('!HH', bytes(payload[0:4]))
|
||||
if ecode:
|
||||
parsed['errorcode'] = ecode
|
||||
@@ -234,40 +236,42 @@ def _find_srvtype(net, net4, srvtype, addresses, xid):
|
||||
|
||||
|
||||
def _grab_rsps(socks, rsps, interval, xidmap):
|
||||
r, _, _ = select.select(socks, (), (), interval)
|
||||
r = None
|
||||
res = select.select(socks, (), (), interval)
|
||||
if res:
|
||||
r = res[0]
|
||||
while r:
|
||||
for s in r:
|
||||
(rsp, peer) = s.recvfrom(9000)
|
||||
neighutil.refresh_neigh()
|
||||
_parse_slp_packet(rsp, peer, rsps, xidmap)
|
||||
r, _, _ = select.select(socks, (), (), interval)
|
||||
res = select.select(socks, (), (), interval)
|
||||
if not res:
|
||||
r = None
|
||||
else:
|
||||
r = res[0]
|
||||
|
||||
|
||||
|
||||
def _parse_attrlist(attrstr):
|
||||
attribs = {}
|
||||
previousattrlen = None
|
||||
attrstr = util.stringify(attrstr)
|
||||
while attrstr:
|
||||
if len(attrstr) == previousattrlen:
|
||||
raise Exception('Looping in attrstr parsing')
|
||||
previousattrlen = len(attrstr)
|
||||
if attrstr[0] == '(':
|
||||
if b')' not in attrstr:
|
||||
if ')' not in attrstr:
|
||||
attribs['INCOMPLETE'] = True
|
||||
return attribs
|
||||
currattr = attrstr[1:attrstr.index(b')')]
|
||||
if b'=' not in currattr: # Not allegedly kosher, but still..
|
||||
currattr = currattr.decode('utf-8')
|
||||
currattr = attrstr[1:attrstr.index(')')]
|
||||
if '=' not in currattr: # Not allegedly kosher, but still..
|
||||
attribs[currattr] = None
|
||||
else:
|
||||
attrname, attrval = currattr.split('=', 1)
|
||||
attrname = attrname.decode('utf-8')
|
||||
attribs[attrname] = []
|
||||
for val in attrval.split(b','):
|
||||
try:
|
||||
val = val.decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
val = '*DECODEERROR*'
|
||||
for val in attrval.split(','):
|
||||
if val[:3] == '\\FF': # we should make this bytes
|
||||
finalval = bytearray([])
|
||||
for bnum in attrval[3:].split('\\'):
|
||||
@@ -287,12 +291,12 @@ def _parse_attrlist(attrstr):
|
||||
).lower()
|
||||
attribs[attrname].append(val)
|
||||
attrstr = attrstr[attrstr.index(')'):]
|
||||
elif attrstr[0] == b','[0]:
|
||||
elif attrstr[0] == ','[0]:
|
||||
attrstr = attrstr[1:]
|
||||
elif b',' in attrstr:
|
||||
currattr = attrstr[:attrstr.index(b',')]
|
||||
elif ',' in attrstr:
|
||||
currattr = attrstr[:attrstr.index(',')]
|
||||
attribs[currattr] = None
|
||||
attrstr = attrstr[attrstr.index(b','):]
|
||||
attrstr = attrstr[attrstr.index(','):]
|
||||
else:
|
||||
currattr = attrstr
|
||||
attribs[currattr] = None
|
||||
@@ -565,7 +569,7 @@ def scan(srvtypes=_slp_services, addresses=None, localonly=False):
|
||||
# now to analyze and flesh out the responses
|
||||
for id in rsps:
|
||||
if 'service:ipmi' in rsps[id]['services']:
|
||||
if 'service:ipmi://Athena:623' in rsps[id]['urls']:
|
||||
if 'service:ipmi://Athena:623' in rsps[id].get('urls', ''):
|
||||
rsps[id]['services'] = ['service:thinkagile-storage']
|
||||
else:
|
||||
continue
|
||||
|
||||
@@ -107,11 +107,14 @@ class PubkeyInvalid(ConfluentException):
|
||||
self.fingerprint = fingerprint
|
||||
self.attrname = attribname
|
||||
self.message = text
|
||||
certtxt = base64.b64encode(certificate)
|
||||
if not isinstance(certtxt, str):
|
||||
certtxt = certtxt.decode('utf8')
|
||||
bodydata = {'message': text,
|
||||
'event': event,
|
||||
'fingerprint': fingerprint,
|
||||
'fingerprintfield': attribname,
|
||||
'certificate': base64.b64encode(certificate)}
|
||||
'certificate': certtxt}
|
||||
self.errorbody = json.dumps(bodydata)
|
||||
|
||||
def get_error_body(self):
|
||||
|
||||
@@ -43,13 +43,16 @@ def execupdate(handler, filename, updateobj, type, owner, node):
|
||||
'detail': errstr})
|
||||
return
|
||||
if type == 'ffdc' and os.path.isdir(filename):
|
||||
filename += '/' + node + '.svcdata'
|
||||
filename += '/' + node
|
||||
try:
|
||||
if type == 'firmware':
|
||||
completion = handler(filename, progress=updateobj.handle_progress,
|
||||
bank=updateobj.bank)
|
||||
else:
|
||||
completion = handler(filename, progress=updateobj.handle_progress)
|
||||
if type == 'ffdc' and completion:
|
||||
filename = completion
|
||||
completion = None
|
||||
if completion is None:
|
||||
completion = 'complete'
|
||||
if owner:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2014 IBM Corporation
|
||||
# Copyright 2015-2016 Lenovo
|
||||
# Copyright 2015-2019 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -80,7 +80,7 @@ def group_creation_resources():
|
||||
yield confluent.messages.ListAttributes(kv={'nodes': []},
|
||||
desc='Nodes to add to the group'
|
||||
).html() + '<br>\n'
|
||||
for attr in sorted(attribs.node.iterkeys()):
|
||||
for attr in sorted(attribs.node):
|
||||
if attr == 'groups':
|
||||
continue
|
||||
if attr.startswith("secret."):
|
||||
@@ -101,7 +101,7 @@ def group_creation_resources():
|
||||
def node_creation_resources():
|
||||
yield confluent.messages.Attributes(
|
||||
kv={'name': None}, desc="Name of the node").html() + '<br>'
|
||||
for attr in sorted(attribs.node.iterkeys()):
|
||||
for attr in sorted(attribs.node):
|
||||
if attr.startswith("secret."):
|
||||
yield confluent.messages.CryptedAttributes(
|
||||
kv={attr: None},
|
||||
@@ -132,7 +132,7 @@ def user_creation_resources():
|
||||
'description': (''),
|
||||
},
|
||||
}
|
||||
for attr in sorted(credential.iterkeys()):
|
||||
for attr in sorted(credential):
|
||||
if attr == "password":
|
||||
yield confluent.messages.CryptedAttributes(
|
||||
kv={attr: None},
|
||||
@@ -182,7 +182,7 @@ def _get_query_dict(env, reqbody, reqtype):
|
||||
if reqbody is not None:
|
||||
if "application/x-www-form-urlencoded" in reqtype:
|
||||
pbody = urlparse.parse_qs(reqbody, True)
|
||||
for ky in pbody.iterkeys():
|
||||
for ky in pbody:
|
||||
if len(pbody[ky]) > 1: # e.g. REST explorer
|
||||
na = [i for i in pbody[ky] if i != '']
|
||||
qdict[ky] = na
|
||||
@@ -190,7 +190,7 @@ def _get_query_dict(env, reqbody, reqtype):
|
||||
qdict[ky] = pbody[ky][0]
|
||||
elif 'application/json' in reqtype:
|
||||
pbody = json.loads(reqbody)
|
||||
for key in pbody.iterkeys():
|
||||
for key in pbody:
|
||||
qdict[key] = pbody[key]
|
||||
if 'restexplorerhonorkey' in qdict:
|
||||
nqdict = {}
|
||||
@@ -311,7 +311,7 @@ def _authorize_request(env, operation):
|
||||
return {'code': 401}
|
||||
return ('logout',)
|
||||
name, passphrase = base64.b64decode(
|
||||
env['HTTP_AUTHORIZATION'].replace('Basic ', '')).split(':', 1)
|
||||
env['HTTP_AUTHORIZATION'].replace('Basic ', '')).split(b':', 1)
|
||||
authdata = auth.check_user_passphrase(name, passphrase, operation=operation, element=element)
|
||||
if authdata is False:
|
||||
return {'code': 403}
|
||||
@@ -325,14 +325,14 @@ def _authorize_request(env, operation):
|
||||
'inflight': set([])}
|
||||
if 'HTTP_CONFLUENTAUTHTOKEN' in env:
|
||||
httpsessions[sessid]['csrftoken'] = util.randomstring(32)
|
||||
cookie['confluentsessionid'] = sessid
|
||||
cookie['confluentsessionid'] = util.stringify(sessid)
|
||||
cookie['confluentsessionid']['secure'] = 1
|
||||
cookie['confluentsessionid']['httponly'] = 1
|
||||
cookie['confluentsessionid']['path'] = '/'
|
||||
skiplog = _should_skip_authlog(env)
|
||||
if authdata:
|
||||
auditmsg = {
|
||||
'user': name,
|
||||
'user': util.stringify(name),
|
||||
'operation': operation,
|
||||
'target': env['PATH_INFO'],
|
||||
}
|
||||
@@ -344,7 +344,7 @@ def _authorize_request(env, operation):
|
||||
if authdata[3] is not None:
|
||||
auditmsg['tenant'] = authdata[3]
|
||||
authinfo['tenant'] = authdata[3]
|
||||
auditmsg['user'] = authdata[2]
|
||||
auditmsg['user'] = util.stringify(authdata[2])
|
||||
if sessid is not None:
|
||||
authinfo['sessionid'] = sessid
|
||||
if not skiplog:
|
||||
@@ -452,6 +452,8 @@ def resourcehandler_backend(env, start_response):
|
||||
httpsessions[authorized['sessionid']]['inflight'])):
|
||||
pagecontent += rsp
|
||||
start_response("200 OK", headers)
|
||||
if not isinstance(pagecontent, bytes):
|
||||
pagecontent = pagecontent.encode('utf-8')
|
||||
yield pagecontent
|
||||
return
|
||||
except exc.ConfluentException as e:
|
||||
@@ -497,7 +499,7 @@ def resourcehandler_backend(env, start_response):
|
||||
auditmsg = {
|
||||
'operation': 'start',
|
||||
'target': env['PATH_INFO'],
|
||||
'user': authorized['username'],
|
||||
'user': util.stringify(authorized['username']),
|
||||
}
|
||||
if 'tenant' in authorized:
|
||||
auditmsg['tenant'] = authorized['tenant']
|
||||
@@ -632,6 +634,7 @@ def resourcehandler_backend(env, start_response):
|
||||
sessinfo = {'username': authorized['username']}
|
||||
if 'authtoken' in authorized:
|
||||
sessinfo['authtoken'] = authorized['authtoken']
|
||||
tlvdata.unicode_dictvalues(sessinfo)
|
||||
yield json.dumps(sessinfo)
|
||||
return
|
||||
resource = '.' + url[url.rindex('/'):]
|
||||
@@ -653,6 +656,8 @@ def resourcehandler_backend(env, start_response):
|
||||
for datum in _assemble_json(hdlr, resource, url, extension):
|
||||
pagecontent += datum
|
||||
start_response('200 OK', headers)
|
||||
if not isinstance(pagecontent, bytes):
|
||||
pagecontent = pagecontent.encode('utf-8')
|
||||
yield pagecontent
|
||||
except exc.ConfluentException as e:
|
||||
if ((not isinstance(e, exc.LockedCredentials)) and
|
||||
@@ -737,7 +742,7 @@ def _assemble_json(responses, resource=None, url=None, extension=None):
|
||||
for rsp in responses:
|
||||
if isinstance(rsp, confluent.messages.LinkRelation):
|
||||
haldata = rsp.raw()
|
||||
for hk in haldata.iterkeys():
|
||||
for hk in haldata:
|
||||
if 'href' in haldata[hk]:
|
||||
if isinstance(haldata[hk]['href'], int):
|
||||
haldata[hk]['href'] = str(haldata[hk]['href'])
|
||||
@@ -753,7 +758,7 @@ def _assemble_json(responses, resource=None, url=None, extension=None):
|
||||
links[hk] = haldata[hk]
|
||||
else:
|
||||
rsp = rsp.raw()
|
||||
for dk in rsp.iterkeys():
|
||||
for dk in rsp:
|
||||
if dk in rspdata:
|
||||
if isinstance(rspdata[dk], list):
|
||||
if isinstance(rsp[dk], list):
|
||||
@@ -772,8 +777,8 @@ def _assemble_json(responses, resource=None, url=None, extension=None):
|
||||
rspdata[dk] = rsp[dk]
|
||||
rspdata["_links"] = links
|
||||
tlvdata.unicode_dictvalues(rspdata)
|
||||
yield json.dumps(
|
||||
rspdata, sort_keys=True, indent=4, ensure_ascii=False).encode('utf-8')
|
||||
yield util.stringify(json.dumps(
|
||||
rspdata, sort_keys=True, indent=4, ensure_ascii=False).encode('utf-8'))
|
||||
|
||||
|
||||
def serve(bind_host, bind_port):
|
||||
|
||||
@@ -600,7 +600,11 @@ class Logger(object):
|
||||
if ltype == 2:
|
||||
textrecord = data
|
||||
else:
|
||||
textrecord = textdate + data + ']'
|
||||
if not isinstance(textdate, bytes):
|
||||
textdate = textdate.encode('utf-8')
|
||||
if not isinstance(data, bytes):
|
||||
data = data.encode('utf-8')
|
||||
textrecord = textdate + data + b']'
|
||||
else:
|
||||
textrecord = textdate + data
|
||||
if not textrecord.endswith('\n'):
|
||||
@@ -798,4 +802,4 @@ def logtrace():
|
||||
if tracelog is None:
|
||||
tracelog = Logger('trace', buffered=False)
|
||||
tracelog.log(traceback.format_exc(), ltype=DataTypes.event,
|
||||
event=Events.stacktrace)
|
||||
event=Events.stacktrace)
|
||||
|
||||
@@ -23,6 +23,7 @@ import confluent.config.configmanager as cfm
|
||||
import confluent.config.conf as cfgfile
|
||||
from copy import deepcopy
|
||||
from datetime import datetime
|
||||
import confluent.util as util
|
||||
import json
|
||||
|
||||
try:
|
||||
@@ -59,7 +60,9 @@ def _htmlify_structure(indict):
|
||||
if isinstance(indict, dict):
|
||||
for key in sorted(indict):
|
||||
ret += "<li>{0}: ".format(key)
|
||||
if type(indict[key]) in (bytes, unicode, float, int):
|
||||
if type(indict[key]) in (bytes, unicode):
|
||||
ret += util.stringify(indict[key])
|
||||
if type(indict[key]) in (float, int):
|
||||
ret += str(indict[key])
|
||||
elif isinstance(indict[key], datetime):
|
||||
ret += indict[key].strftime('%Y-%m-%dT%H:%M:%S')
|
||||
@@ -73,7 +76,7 @@ def _htmlify_structure(indict):
|
||||
if datum is None:
|
||||
nd.append('')
|
||||
else:
|
||||
nd.append(datum)
|
||||
nd.append(util.stringify(datum))
|
||||
ret += ",".join(nd)
|
||||
else:
|
||||
for v in indict:
|
||||
@@ -126,15 +129,16 @@ class ConfluentMessage(object):
|
||||
return self._generic_html_value(self.kvpairs)
|
||||
if not self.stripped:
|
||||
htmlout = ''
|
||||
for node in self.kvpairs.iterkeys():
|
||||
for node in self.kvpairs:
|
||||
htmlout += '{0}:{1}\n'.format(
|
||||
node, self._generic_html_value(self.kvpairs[node]))
|
||||
return htmlout
|
||||
|
||||
def _generic_html_value(self, pairs):
|
||||
snippet = ""
|
||||
for key in pairs.iterkeys():
|
||||
for key in pairs:
|
||||
val = pairs[key]
|
||||
key = util.stringify(key)
|
||||
value = self.defaultvalue
|
||||
if isinstance(val, dict) and 'type' in val:
|
||||
valtype = val['type']
|
||||
@@ -326,15 +330,16 @@ class ConfluentChoiceMessage(ConfluentMessage):
|
||||
return self._create_option(self.kvpairs)
|
||||
else:
|
||||
htmlout = ''
|
||||
for node in self.kvpairs.iterkeys():
|
||||
for node in self.kvpairs:
|
||||
htmlout += '{0}:{1}\n'.format(
|
||||
node, self._create_option(self.kvpairs[node]))
|
||||
return htmlout
|
||||
|
||||
def _create_option(self, pairdata):
|
||||
snippet = ''
|
||||
for key in pairdata.iterkeys():
|
||||
for key in pairdata:
|
||||
val = pairdata[key]
|
||||
key = util.stringify(key)
|
||||
snippet += key + ':<select name="%s">' % key
|
||||
valid_values = self.valid_values
|
||||
if key in self.valid_paramset:
|
||||
@@ -1374,6 +1379,8 @@ class InputAlertDestination(ConfluentMessage):
|
||||
self.alertcfg = {}
|
||||
if multinode: # keys are node names
|
||||
for node in inputdata:
|
||||
if not isinstance(inputdata[node], dict):
|
||||
break
|
||||
self.alertcfg[node] = inputdata[node]
|
||||
for key in inputdata[node]:
|
||||
if key not in self.valid_alert_params:
|
||||
@@ -1386,7 +1393,8 @@ class InputAlertDestination(ConfluentMessage):
|
||||
else:
|
||||
self.alertcfg[node][key] = \
|
||||
self.valid_alert_params[key](inputdata[node][key])
|
||||
else:
|
||||
else:
|
||||
return
|
||||
for key in inputdata:
|
||||
if key not in self.valid_alert_params:
|
||||
raise exc.InvalidArgumentException(
|
||||
@@ -1484,12 +1492,15 @@ class Volume(ConfluentMessage):
|
||||
|
||||
class Disk(ConfluentMessage):
|
||||
valid_states = set([
|
||||
'fault',
|
||||
'jbod',
|
||||
'unconfigured',
|
||||
'hotspare',
|
||||
'rebuilding',
|
||||
'online',
|
||||
])
|
||||
state_aliases = {
|
||||
'unconfigured bad': 'fault',
|
||||
'unconfigured good': 'unconfigured',
|
||||
'global hot spare': 'hotspare',
|
||||
'dedicated hot spare': 'hotspare',
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
# Ultimately, this should use AF_NETLINK, but in the interest of time,
|
||||
# use ip neigh for the moment
|
||||
|
||||
import confluent.util as util
|
||||
import eventlet.green.subprocess as subprocess
|
||||
import os
|
||||
|
||||
@@ -26,7 +27,7 @@ neightime = 0
|
||||
|
||||
import re
|
||||
|
||||
_validmac = re.compile(b'..:..:..:..:..:..')
|
||||
_validmac = re.compile('..:..:..:..:..:..')
|
||||
|
||||
|
||||
def update_neigh():
|
||||
@@ -39,11 +40,12 @@ def update_neigh():
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
(neighdata, err) = ipn.communicate()
|
||||
for entry in neighdata.split(b'\n'):
|
||||
entry = entry.split(b' ')
|
||||
neighdata = util.stringify(neighdata)
|
||||
for entry in neighdata.split('\n'):
|
||||
entry = entry.split(' ')
|
||||
if len(entry) < 5 or not entry[4]:
|
||||
continue
|
||||
if entry[0] in (b'192.168.0.100', b'192.168.70.100', b'192.168.70.125'):
|
||||
if entry[0] in ('192.168.0.100', '192.168.70.100', '192.168.70.125'):
|
||||
# Note that these addresses are common static ip addresses
|
||||
# that are hopelessly ambiguous if there are many
|
||||
# so ignore such entries and move on
|
||||
|
||||
@@ -125,7 +125,8 @@ def _dump_neighbordatum(info):
|
||||
|
||||
def b64tohex(b64str):
|
||||
bd = base64.b64decode(b64str)
|
||||
return ''.join(['{0:02x}'.format(ord(x)) for x in bd])
|
||||
bd = bytearray(bd)
|
||||
return ''.join(['{0:02x}'.format(x) for x in bd])
|
||||
|
||||
def get_fingerprint(switch, port, configmanager, portmatch):
|
||||
update_switch_data(switch, configmanager)
|
||||
|
||||
@@ -162,7 +162,7 @@ class NodeRange(object):
|
||||
pieces = seqrange.split(delimiter)
|
||||
if len(pieces) % 2 != 0:
|
||||
return self.failorreturn(seqrange)
|
||||
halflen = len(pieces) / 2
|
||||
halflen = len(pieces) // 2
|
||||
left = delimiter.join(pieces[:halflen])
|
||||
right = delimiter.join(pieces[halflen:])
|
||||
leftbits = _numextractor.parseString(left).asList()
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015-2019 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.
|
||||
|
||||
|
||||
# This plugin provides an ssh implementation comforming to the 'console'
|
||||
# specification. consoleserver or shellserver would be equally likely
|
||||
# to use this.
|
||||
|
||||
import confluent.exceptions as cexc
|
||||
import confluent.interface.console as conapi
|
||||
import confluent.log as log
|
||||
import confluent.util as util
|
||||
import pyghmi.exceptions as pygexc
|
||||
import pyghmi.redfish.command as rcmd
|
||||
import eventlet
|
||||
import eventlet.green.ssl as ssl
|
||||
try:
|
||||
websocket = eventlet.import_patched('websocket')
|
||||
wso = websocket.WebSocket
|
||||
except Exception:
|
||||
wso = object
|
||||
|
||||
def get_conn_params(node, configdata):
|
||||
if 'secret.hardwaremanagementuser' in configdata:
|
||||
username = configdata['secret.hardwaremanagementuser']['value']
|
||||
else:
|
||||
username = 'USERID'
|
||||
if 'secret.hardwaremanagementpassword' in configdata:
|
||||
passphrase = configdata['secret.hardwaremanagementpassword']['value']
|
||||
else:
|
||||
passphrase = 'PASSW0RD' # for lack of a better guess
|
||||
if 'hardwaremanagement.manager' in configdata:
|
||||
bmc = configdata['hardwaremanagement.manager']['value']
|
||||
else:
|
||||
bmc = node
|
||||
return {
|
||||
'username': username,
|
||||
'passphrase': passphrase,
|
||||
'bmc': bmc,
|
||||
}
|
||||
_configattributes = ('secret.hardwaremanagementuser',
|
||||
'secret.hardwaremanagementpassword',
|
||||
'hardwaremanagement.manager')
|
||||
|
||||
class WrappedWebSocket(wso):
|
||||
|
||||
def set_verify_callback(self, callback):
|
||||
self._certverify = callback
|
||||
|
||||
def connect(self, url, **options):
|
||||
add_tls = url.startswith('wss://')
|
||||
if add_tls:
|
||||
hostname, port, resource, _ = websocket._url.parse_url(url)
|
||||
if hostname[0] != '[' and ':' in hostname:
|
||||
hostname = '[{0}]'.format(hostname)
|
||||
if resource[0] != '/':
|
||||
resource = '/{0}'.format(resource)
|
||||
url = 'ws://{0}:443{1}'.format(hostname,resource)
|
||||
else:
|
||||
return super(WrappedWebSocket, self).connect(url, **options)
|
||||
self.sock_opt.timeout = options.get('timeout', self.sock_opt.timeout)
|
||||
self.sock, addrs = websocket._http.connect(url, self.sock_opt, websocket._http.proxy_info(**options),
|
||||
options.pop('socket', None))
|
||||
self.sock = ssl.wrap_socket(self.sock, cert_reqs=ssl.CERT_NONE)
|
||||
# The above is supersedeed by the _certverify, which provides
|
||||
# known-hosts style cert validaiton
|
||||
bincert = self.sock.getpeercert(binary_form=True)
|
||||
if not self._certverify(bincert):
|
||||
raise pygexc.UnrecognizedCertificate('Unknown certificate', bincert)
|
||||
try:
|
||||
self.handshake_response = websocket._handshake.handshake(self.sock, *addrs, **options)
|
||||
if self.handshake_response.status in websocket._handshake.SUPPORTED_REDIRECT_STATUSES:
|
||||
options['redirect_limit'] = options.pop('redirect_limit', 3) - 1
|
||||
if options['redirect_limit'] < 0:
|
||||
raise Exception('Redirect limit hit')
|
||||
url = self.handshake_response.headers['location']
|
||||
self.sock.close()
|
||||
return self.connect(url, **options)
|
||||
self.connected = True
|
||||
except:
|
||||
if self.sock:
|
||||
self.sock.close()
|
||||
self.sock = None
|
||||
raise
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class TsmConsole(conapi.Console):
|
||||
|
||||
def __init__(self, node, config):
|
||||
self.node = node
|
||||
self.ws = None
|
||||
configdata = config.get_node_attributes([node], _configattributes, decrypt=True)
|
||||
connparams = get_conn_params(node, configdata[node])
|
||||
self.username = connparams['username']
|
||||
self.password = connparams['passphrase']
|
||||
self.bmc = connparams['bmc']
|
||||
self.origbmc = connparams['bmc']
|
||||
if ':' in self.bmc:
|
||||
self.bmc = '[{0}]'.format(self.bmc)
|
||||
self.datacallback = None
|
||||
self.nodeconfig = config
|
||||
self.connected = False
|
||||
|
||||
|
||||
def recvdata(self):
|
||||
while self.connected:
|
||||
pendingdata = self.ws.recv()
|
||||
if pendingdata == '':
|
||||
self.datacallback(conapi.ConsoleEvent.Disconnect)
|
||||
return
|
||||
self.datacallback(pendingdata)
|
||||
|
||||
def connect(self, callback):
|
||||
self.datacallback = callback
|
||||
rc = rcmd.Command(self.origbmc, self.username,
|
||||
self.password,
|
||||
verifycallback=lambda x: True)
|
||||
wc = rc.oem.wc
|
||||
bmc = self.bmc
|
||||
if '%' in self.bmc:
|
||||
prefix = self.bmc.split('%')[0]
|
||||
bmc = prefix + ']'
|
||||
self.ws = WrappedWebSocket(host=bmc)
|
||||
kv = util.TLSCertVerifier(
|
||||
self.nodeconfig, self.node, 'pubkeys.tls_hardwaremanager').verify_cert
|
||||
self.ws.set_verify_callback(kv)
|
||||
self.ws.connect('wss://{0}/sol?CSRFTOKEN={1}'.format(self.bmc, rc.oem.csrftok), host=bmc, cookie='QSESSIONID={0}'.format(wc.cookies['QSESSIONID']))
|
||||
self.connected = True
|
||||
eventlet.spawn_n(self.recvdata)
|
||||
return
|
||||
|
||||
def write(self, data):
|
||||
self.ws.send(data)
|
||||
|
||||
def close(self):
|
||||
if self.ws:
|
||||
self.ws.close()
|
||||
self.connected = False
|
||||
self.datacallback = None
|
||||
|
||||
def create(nodes, element, configmanager, inputdata):
|
||||
if len(nodes) == 1:
|
||||
return TsmConsole(nodes[0], configmanager)
|
||||
@@ -78,6 +78,18 @@ def retrieve(nodes, element, configmanager, inputdata):
|
||||
for node in nodes:
|
||||
workers.add(eventlet.spawn(retrieve_health, configmanager, creds,
|
||||
node, results))
|
||||
elif element[:3] == ['inventory', 'hardware', 'all']:
|
||||
creds = configmanager.get_node_attributes(
|
||||
nodes, ['secret.hardwaremanagementuser', 'secret.hardwaremanagementpassword'], decrypt=True)
|
||||
for node in nodes:
|
||||
workers.add(eventlet.spawn(retrieve_inventory, configmanager,
|
||||
creds, node, results, element))
|
||||
elif element[:3] == ['inventory', 'firmware', 'all']:
|
||||
creds = configmanager.get_node_attributes(
|
||||
nodes, ['secret.hardwaremanagementuser', 'secret.hardwaremanagementpassword'], decrypt=True)
|
||||
for node in nodes:
|
||||
workers.add(eventlet.spawn(retrieve_firmware, configmanager,
|
||||
creds, node, results, element))
|
||||
else:
|
||||
for node in nodes:
|
||||
yield msg.ConfluentNodeError(node, 'Not Implemented')
|
||||
@@ -105,6 +117,44 @@ def retrieve(nodes, element, configmanager, inputdata):
|
||||
pass
|
||||
|
||||
|
||||
def retrieve_inventory(configmanager, creds, node, results, element):
|
||||
if len(element) == 3:
|
||||
results.put(msg.ChildCollection('all'))
|
||||
results.put(msg.ChildCollection('system'))
|
||||
return
|
||||
wc = cnos_login(node, configmanager, creds)
|
||||
sysinfo = wc.grab_json_response('/nos/api/sysinfo/inventory')
|
||||
invinfo = {
|
||||
'inventory': [{
|
||||
'name': 'System',
|
||||
'present': True,
|
||||
'information': {
|
||||
'Product name': sysinfo['Model'],
|
||||
'Serial Number': sysinfo['Electronic Serial Number'],
|
||||
'Board Serial Number': sysinfo['Serial Number'],
|
||||
'Manufacturer': 'Lenovo',
|
||||
'Model': sysinfo['Machine Type Model'],
|
||||
'FRU Number': sysinfo['FRU'].strip(),
|
||||
}
|
||||
}]
|
||||
}
|
||||
results.put(msg.KeyValueData(invinfo, node))
|
||||
|
||||
|
||||
def retrieve_firmware(configmanager, creds, node, results, element):
|
||||
if len(element) == 3:
|
||||
results.put(msg.ChildCollection('all'))
|
||||
return
|
||||
wc = cnos_login(node, configmanager, creds)
|
||||
sysinfo = wc.grab_json_response('/nos/api/sysinfo/inventory')
|
||||
items = [{
|
||||
'Software': {'version': sysinfo['Software Revision']},
|
||||
},
|
||||
{
|
||||
'BIOS': {'version': sysinfo['BIOS Revision']},
|
||||
}]
|
||||
results.put(msg.Firmware(items, node))
|
||||
|
||||
def retrieve_health(configmanager, creds, node, results):
|
||||
wc = cnos_login(node, configmanager, creds)
|
||||
hinfo = wc.grab_json_response('/nos/api/sysinfo/globalhealthstatus')
|
||||
|
||||
@@ -42,6 +42,11 @@ import traceback
|
||||
if not hasattr(ssl, 'SSLEOFError'):
|
||||
ssl.SSLEOFError = None
|
||||
|
||||
try:
|
||||
range = xrange
|
||||
except NameError:
|
||||
pass
|
||||
|
||||
pci_cache = {}
|
||||
|
||||
def get_dns_txt(qstring):
|
||||
@@ -131,7 +136,7 @@ def hex2bin(hexstring):
|
||||
if len(hexvals) < 2:
|
||||
hexvals = hexstring.split(' ')
|
||||
if len(hexvals) < 2:
|
||||
hexvals = [hexstring[i:i+2] for i in xrange(0, len(hexstring), 2)]
|
||||
hexvals = [hexstring[i:i+2] for i in range(0, len(hexstring), 2)]
|
||||
bytedata = [int(i, 16) for i in hexvals]
|
||||
return bytearray(bytedata)
|
||||
|
||||
@@ -278,6 +283,7 @@ def _donothing(data):
|
||||
|
||||
class IpmiConsole(conapi.Console):
|
||||
configattributes = frozenset(_configattributes)
|
||||
bmctonodemapping = {}
|
||||
|
||||
def __init__(self, node, config):
|
||||
self.error = None
|
||||
@@ -295,10 +301,21 @@ class IpmiConsole(conapi.Console):
|
||||
self.bmc = connparams['bmc']
|
||||
self.port = connparams['port']
|
||||
self.connected = False
|
||||
# ok, is self.bmc unique among nodes already
|
||||
# Cannot actually create console until 'connect', when we get callback
|
||||
if (self.bmc in self.bmctonodemapping and
|
||||
self.bmctonodemapping[self.bmc] != node):
|
||||
raise Exception(
|
||||
"Duplicate hardwaremanagement.manager attribute for {0} and {1}".format(
|
||||
node, self.bmctonodemapping[self.bmc]))
|
||||
self.bmctonodemapping[self.bmc] = node
|
||||
|
||||
def __del__(self):
|
||||
self.solconnection = None
|
||||
try:
|
||||
del self.bmctonodemapping[self.bmc]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def handle_data(self, data):
|
||||
if type(data) == dict:
|
||||
@@ -584,9 +601,13 @@ class IpmiHandler(object):
|
||||
self.output.put(msg.CreatedResource(
|
||||
'nodes/{0}/media/uploads/{1}'.format(self.node, u.name)))
|
||||
|
||||
def get_diags(self, savefile, progress):
|
||||
return self.ipmicmd.get_diagnostic_data(
|
||||
savefile, progress=progress, autosuffix=True)
|
||||
|
||||
def handle_servicedata_fetch(self):
|
||||
u = firmwaremanager.Updater(
|
||||
self.node, self.ipmicmd.get_diagnostic_data,
|
||||
self.node, self.get_diags,
|
||||
self.inputdata.nodefile(self.node), self.tenant, type='ffdc',
|
||||
owner=self.current_user)
|
||||
self.output.put(msg.CreatedResource(
|
||||
@@ -658,7 +679,7 @@ class IpmiHandler(object):
|
||||
if len(self.element) == 4:
|
||||
# A list of destinations
|
||||
maxdest = self.ipmicmd.get_alert_destination_count()
|
||||
for alertidx in xrange(0, maxdest + 1):
|
||||
for alertidx in range(0, maxdest + 1):
|
||||
self.output.put(msg.ChildCollection(alertidx))
|
||||
return
|
||||
elif len(self.element) == 5:
|
||||
|
||||
@@ -175,12 +175,15 @@ class IpmiCommandWrapper(ipmicommand.Command):
|
||||
raise exc.TargetEndpointUnreachable(se.strerror)
|
||||
else:
|
||||
raise exc.TargetEndpointUnreachable(str(se))
|
||||
if isinstance(se, socket.timeout) or (len(se) > 1 and se[1] == 'EHOSTUNREACH'):
|
||||
if isinstance(se, socket.timeout):
|
||||
raise exc.TargetEndpointUnreachable('timeout')
|
||||
raise
|
||||
except pygexc.PyghmiException as pe:
|
||||
if 'Access Denied' in str(pe):
|
||||
raise exc.TargetEndpointBadCredentials()
|
||||
if 'Redfish not ready' in str(pe):
|
||||
raise exc.TargetEndpointUnreachable('Redfish not yet ready')
|
||||
raise
|
||||
|
||||
def close_confluent(self):
|
||||
if self._attribwatcher:
|
||||
@@ -462,9 +465,13 @@ class IpmiHandler(object):
|
||||
self.output.put(msg.CreatedResource(
|
||||
'nodes/{0}/media/uploads/{1}'.format(self.node, u.name)))
|
||||
|
||||
def get_diags(self, savefile, progress):
|
||||
return self.ipmicmd.get_diagnostic_data(
|
||||
savefile, progress=progress, autosuffix=True)
|
||||
|
||||
def handle_servicedata_fetch(self):
|
||||
u = firmwaremanager.Updater(
|
||||
self.node, self.ipmicmd.get_diagnostic_data,
|
||||
self.node, self.get_diags,
|
||||
self.inputdata.nodefile(self.node), self.tenant, type='ffdc',
|
||||
owner=self.current_user)
|
||||
self.output.put(msg.CreatedResource(
|
||||
|
||||
@@ -77,7 +77,7 @@ class HostKeyHandler(paramiko.client.MissingHostKeyPolicy):
|
||||
|
||||
class SshShell(conapi.Console):
|
||||
|
||||
def __init__(self, node, config, username='', password=''):
|
||||
def __init__(self, node, config, username=b'', password=b''):
|
||||
self.node = node
|
||||
self.ssh = None
|
||||
self.datacallback = None
|
||||
@@ -127,15 +127,15 @@ class SshShell(conapi.Console):
|
||||
look_for_keys=False)
|
||||
except paramiko.AuthenticationException:
|
||||
self.inputmode = 0
|
||||
self.username = ''
|
||||
self.password = ''
|
||||
self.username = b''
|
||||
self.password = b''
|
||||
self.datacallback('\r\nlogin as: ')
|
||||
return
|
||||
except paramiko.ssh_exception.NoValidConnectionsError as e:
|
||||
self.datacallback(str(e))
|
||||
self.inputmode = 0
|
||||
self.username = ''
|
||||
self.password = ''
|
||||
self.username = b''
|
||||
self.password = b''
|
||||
self.datacallback('\r\nlogin as: ')
|
||||
return
|
||||
except cexc.PubkeyInvalid as pi:
|
||||
@@ -205,9 +205,9 @@ class SshShell(conapi.Console):
|
||||
delidx = data.index(b'\x7f')
|
||||
data = data[:delidx - 1] + data[delidx + 1:]
|
||||
self.username += data
|
||||
if '\r' in self.username:
|
||||
self.username, self.password = self.username.split('\r')[:2]
|
||||
lastdata = data.split('\r')[0]
|
||||
if b'\r' in self.username:
|
||||
self.username, self.password = self.username.split(b'\r')[:2]
|
||||
lastdata = data.split(b'\r')[0]
|
||||
if lastdata != '':
|
||||
self.datacallback(lastdata)
|
||||
self.datacallback('\r\nEnter password: ')
|
||||
@@ -223,9 +223,9 @@ class SshShell(conapi.Console):
|
||||
delidx = data.index(b'\x7f')
|
||||
data = data[:delidx - 1] + data[delidx + 1:]
|
||||
self.password += data
|
||||
if '\r' in self.password:
|
||||
self.password = self.password.split('\r')[0]
|
||||
self.datacallback('\r\n')
|
||||
if b'\r' in self.password:
|
||||
self.password = self.password.split(b'\r')[0]
|
||||
self.datacallback(b'\r\n')
|
||||
self.logon()
|
||||
else:
|
||||
self.shell.sendall(data)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2014 IBM Corporation
|
||||
# Copyright 2015-2018 Lenovo
|
||||
# Copyright 2015-2019 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -123,7 +123,8 @@ def sessionhdl(connection, authname, skipauth=False, cert=None):
|
||||
if authdata:
|
||||
cfm = authdata[1]
|
||||
authenticated = True
|
||||
send_data(connection, "Confluent -- v0 --")
|
||||
# version 0 == original, version 1 == pickle3 allowed
|
||||
send_data(connection, "Confluent -- v{0} --".format(sys.version_info[0] - 2))
|
||||
while not authenticated: # prompt for name and passphrase
|
||||
send_data(connection, {'authpassed': 0})
|
||||
response = tlvdata.recv(connection)
|
||||
@@ -221,6 +222,7 @@ def process_request(connection, request, cfm, authdata, authname, skipauth):
|
||||
auditmsg['tenant'] = authdata[3]
|
||||
auditmsg['allowed'] = True
|
||||
if _should_authlog(path, operation):
|
||||
tlvdata.unicode_dictvalues(auditmsg)
|
||||
auditlog.log(auditmsg)
|
||||
try:
|
||||
if operation == 'start':
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from ctypes import *
|
||||
from ctypes.util import find_library
|
||||
import confluent.util as util
|
||||
import grp
|
||||
import pwd
|
||||
import os
|
||||
@@ -29,6 +30,7 @@ def getgrouplist(name, gid, ng=32):
|
||||
|
||||
|
||||
def grouplist(username):
|
||||
username = util.stringify(username)
|
||||
pent = pwd.getpwnam(username)
|
||||
try:
|
||||
groups = getgrouplist(pent.pw_name, pent.pw_gid)
|
||||
|
||||
@@ -31,7 +31,7 @@ def stringify(instr):
|
||||
# Normalize unicode and bytes to 'str', correcting for
|
||||
# current python version
|
||||
if isinstance(instr, bytes) and not isinstance(instr, str):
|
||||
return instr.decode('utf-8')
|
||||
return instr.decode('utf-8', errors='replace')
|
||||
elif not isinstance(instr, bytes) and not isinstance(instr, str):
|
||||
return instr.encode('utf-8')
|
||||
return instr
|
||||
@@ -75,11 +75,11 @@ def randomstring(length=20):
|
||||
|
||||
:param length: The number of characters to produce, defaults to 20
|
||||
"""
|
||||
chunksize = length / 4
|
||||
chunksize = length // 4
|
||||
if length % 4 > 0:
|
||||
chunksize += 1
|
||||
strval = base64.urlsafe_b64encode(os.urandom(chunksize * 3))
|
||||
return strval[0:length-1]
|
||||
return stringify(strval[0:length-1])
|
||||
|
||||
|
||||
def securerandomnumber(low=0, high=4294967295):
|
||||
@@ -109,7 +109,7 @@ def monotonic_time():
|
||||
|
||||
|
||||
def get_certificate_from_file(certfile):
|
||||
cert = open(certfile, 'rb').read()
|
||||
cert = open(certfile, 'r').read()
|
||||
inpemcert = False
|
||||
prunedcert = ''
|
||||
for line in cert.split('\n'):
|
||||
|
||||
@@ -13,9 +13,9 @@ BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot
|
||||
Prefix: %{_prefix}
|
||||
BuildArch: noarch
|
||||
%if "%{dist}" == ".el8"
|
||||
Requires: python3-pyghmi >= 1.0.34, python3-eventlet, python3-greenlet, python3-pycryptodomex >= 3.4.7, confluent_client, python3-pyparsing, python3-paramiko, python3-dns, python3-netifaces, python3-pyasn1 >= 0.2.3, python3-pysnmp >= 4.3.4, python3-pyte, python3-lxml, python3-eficompressor, python3-setuptools, python3-dateutil, python3-enum34, python3-asn1crypto, python3-ipaddress, python3-cffi, python3-pyOpenSSL
|
||||
Requires: python3-pyghmi >= 1.0.34, python3-eventlet, python3-greenlet, python3-pycryptodomex >= 3.4.7, confluent_client, python3-pyparsing, python3-paramiko, python3-dns, python3-netifaces, python3-pyasn1 >= 0.2.3, python3-pysnmp >= 4.3.4, python3-pyte, python3-lxml, python3-eficompressor, python3-setuptools, python3-dateutil, python3-enum34, python3-asn1crypto, python3-cffi, python3-pyOpenSSL, python3-monotonic, python3-websocket-client
|
||||
%else
|
||||
Requires: python-pyghmi >= 1.0.34, python-eventlet, python-greenlet, python-pycryptodomex >= 3.4.7, confluent_client, python-pyparsing, python-paramiko, python-dns, python-netifaces, python2-pyasn1 >= 0.2.3, python-pysnmp >= 4.3.4, python-pyte, python-lxml, python-eficompressor, python-setuptools, python-dateutil
|
||||
Requires: python-pyghmi >= 1.0.34, python-eventlet, python-greenlet, python-pycryptodomex >= 3.4.7, confluent_client, python-pyparsing, python-paramiko, python-dns, python-netifaces, python2-pyasn1 >= 0.2.3, python-pysnmp >= 4.3.4, python-pyte, python-lxml, python-eficompressor, python-setuptools, python-dateutil, python2-websocket-client
|
||||
%endif
|
||||
Vendor: Jarrod Johnson <jjohnson2@lenovo.com>
|
||||
Url: http://xcat.sf.net/
|
||||
@@ -42,7 +42,10 @@ python2 setup.py install --single-version-externally-managed -O1 --root=$RPM_BUI
|
||||
for file in $(grep confluent/__init__.py INSTALLED_FILES.bare); do
|
||||
rm $RPM_BUILD_ROOT/$file
|
||||
done
|
||||
grep -v confluent/__init__.py INSTALLED_FILES.bare > INSTALLED_FILES
|
||||
grep -v confluent/__init__.py INSTALLED_FILES.bare | grep -v etc/init.d/confluent > INSTALLED_FILES
|
||||
rm $RPM_BUILD_ROOT/etc/init.d/confluent
|
||||
rmdir $RPM_BUILD_ROOT/etc/init.d
|
||||
rmdir $RPM_BUILD_ROOT/etc
|
||||
cat INSTALLED_FILES
|
||||
|
||||
%triggerin -- python-pyghmi
|
||||
|
||||
@@ -14,6 +14,7 @@ setup(
|
||||
'confluent/discovery/handlers/',
|
||||
'confluent/networking/',
|
||||
'confluent/plugins/hardwaremanagement/',
|
||||
'confluent/plugins/console/',
|
||||
'confluent/plugins/shell/',
|
||||
'confluent/collective/',
|
||||
'confluent/plugins/configuration/'],
|
||||
|
||||
Reference in New Issue
Block a user