2
0
mirror of https://github.com/xcat2/confluent.git synced 2026-09-29 00:31:09 +00:00

Compare commits

..

7 Commits

Author SHA1 Message Date
Jarrod Johnson 188a1b411a Add batch support to nodeconfig 2018-08-02 16:53:56 -04:00
Jarrod Johnson 432b899a9d Merge branch 'master' into 18c 2018-08-02 16:41:26 -04:00
Jarrod Johnson d3ee71eb5e Merge branch 'master' into 18c 2018-07-30 09:33:43 -04:00
Jarrod Johnson 6f002b3d91 Merge branch 'master' into 18c 2018-07-26 14:18:48 -04:00
Jarrod Johnson 8e01686d4a Merge branch 'master' into 18c 2018-07-24 17:29:12 -04:00
Jarrod Johnson ddd3dea92d Merge branch 'master' into 18c 2018-07-24 16:50:17 -04:00
Jarrod Johnson 6f5481f007 Have collective coalesce watched updates
Particularly chatty output can make collate be unreasonable in
low quality terminals and links.  Throttle to about 4 times a second.
2018-07-24 15:20:46 -04:00
146 changed files with 7828 additions and 10157 deletions
+1 -4
View File
@@ -1,4 +1 @@
*.pyc
.*.
confluent_client/man/man*
.*.sw*
.idea
-24
View File
@@ -1,24 +0,0 @@
#!/usr/bin/python
import os
import sys
path = os.path.dirname(os.path.realpath(__file__))
try:
sys.path.remove(path)
except Exception:
pass
path = os.path.realpath(os.path.join(path, '..', 'confluent_server'))
sys.path.append(path)
import confluent.config.attributes as attr
import shutil
shutil.copyfile('doc/man/nodeattrib.ronn.tmpl', 'doc/man/nodeattrib.ronn')
shutil.copyfile('doc/man/nodegroupattrib.ronn.tmpl', 'doc/man/nodegroupattrib.ronn')
with open('doc/man/nodeattrib.ronn', 'a') as outf:
for field in sorted(attr.node):
outf.write('\n* `{0}`:\n {1}\n'.format(field, attr.node[field]['description']))
with open('doc/man/nodegroupattrib.ronn', 'a') as outf:
for field in sorted(attr.node):
outf.write('\n* `{0}`:\n {1}\n'.format(field, attr.node[field]['description']))
+2 -18
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
@@ -35,16 +35,11 @@ import confluent.client
argparser = optparse.OptionParser(usage="Usage: <other command> | %prog [options]")
argparser.add_option('-a', '--abbreviate', action='store_true',
help='Attempt to use confluent server to shorten noderanges')
argparser.add_option('-b', '--base',
help='Use given node as reference for comparison when '
'using -d, instead of using the most common result')
argparser.add_option('-d', '--diff', action='store_true',
help='Show what differs between most common '
'output group and others')
argparser.add_option('-w', '--watch', action='store_true',
help='Show intermediate results while running')
argparser.add_option('-g', '--groupcount', action='store_true',
help='Show count of output groups rather than the actual output')
argparser.add_option('-s', '--skipcommon', action='store_true',
help='Do not print most common result, only non modal '
'groups, useful when combined with -d')
@@ -53,8 +48,6 @@ argparser.add_option('-c', '--count', action='store_true',
argparser.add_option('-r', '--reverse', action='store_true',
help='Reverse sort order to show biggest output group '
'last')
argparser.add_option('-l', '--log', action='store', type='string', dest='log',
help='Log each output to file, using {node} as a placeholder for node.')
(options, args) = argparser.parse_args()
if sys.stdin.isatty():
argparser.print_help()
@@ -70,10 +63,7 @@ else:
def print_current():
if options.diff:
grouped.print_deviants(skipmodal=options.skipcommon, count=options.count,
reverse=options.reverse, basenode=options.base)
elif options.groupcount:
grouped.generate_byoutput()
print(len(grouped.byoutput))
reverse=options.reverse)
else:
grouped.print_all(skipmodal=options.skipcommon,
count=options.count,
@@ -90,12 +80,6 @@ while fullline:
continue
if ': ' not in line:
line = 'UNKNOWN: ' + line
if options.log:
node, output = line.split(': ', 1)
currlog = options.log.format(node=node, nodename=node)
with open(currlog, mode='a') as log:
log.write(output + '\n')
continue
grouped.add_line(*line.split(': ', 1))
if options.watch:
if not holdoff:
+133 -200
View File
@@ -1,8 +1,8 @@
#!/usr/bin/python2
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 IBM Corporation
# Copyright 2015-2018 Lenovo
# Copyright 2015-2016 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -49,7 +49,6 @@ import select
import shlex
import signal
import socket
import struct
import sys
import time
try:
@@ -76,11 +75,6 @@ import confluent.termhandler as termhandler
import confluent.tlvdata as tlvdata
import confluent.client as client
try:
unicode
except NameError:
unicode = str
conserversequence = '\x05c' # ctrl-e, c
clearpowermessage = False
@@ -94,15 +88,6 @@ except NameError:
netserver = None
laststate = {}
try:
input = raw_input
except NameError:
pass
class BailOut(Exception):
def __init__(self, errorcode=0):
self.errorcode = errorcode
def print_help():
print("confetty provides a filesystem like interface to confluent. "
@@ -204,9 +189,9 @@ def prompt():
if os.environ.get('TERM', '') not in ('linux'):
sys.stdout.write('\x1b]0;confetty: %s\x07' % target)
try:
return input(target + ' -> ')
return raw_input(target + ' -> ')
except KeyboardInterrupt:
print("")
print ""
return ""
except EOFError: # ctrl-d
print("exit")
@@ -304,11 +289,8 @@ currchildren = None
def print_result(res):
global exitcode
if 'errorcode' in res or 'error' in res:
print(res['error'])
if 'errorcode' in res:
exitcode |= res['errorcode']
print res['error']
return
if 'databynode' in res:
print_result(res['databynode'])
@@ -321,9 +303,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'])
@@ -336,7 +318,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
@@ -371,7 +353,7 @@ def do_command(command, server):
if argv[0] == 'exit':
if os.environ.get('TERM', '') not in ('linux'):
sys.stdout.write('\x1b]0;\x07')
raise Bailout()
sys.exit(0)
elif argv[0] in ('help', '?'):
return print_help()
elif argv[0] == 'cd':
@@ -435,10 +417,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')
@@ -452,10 +434,6 @@ def do_command(command, server):
currconsole = targpath
startrequest = {'operation': 'start', 'path': targpath,
'parameters': {}}
height, width = struct.unpack(
'hh', fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ, b'....'))[:2]
startrequest['parameters']['width'] = width
startrequest['parameters']['height'] = height
for param in argv[2:]:
(parmkey, parmval) = param.split("=")
startrequest['parameters'][parmkey] = parmval
@@ -500,8 +478,7 @@ def createresource(args):
collection = targpath
else:
collection, _, resname = targpath.rpartition('/')
if 'name' not in keydata:
keydata['name'] = resname
keydata['name'] = resname
makecall(session.create, (collection, keydata))
@@ -516,13 +493,6 @@ def makecall(callout, args):
if 'errorcode' in response:
exitcode = response['errorcode']
sys.stderr.write('Error: ' + response['error'] + '\n')
if 'databynode' in response:
lresponse = response['databynode']
for node in lresponse:
if 'errorcode' in lresponse[node]:
exitcode = lresponse[node]['errorcode']
if 'error' in lresponse[node]:
sys.stderr.write('{0}: Error - {1}\n'.format(node, lresponse[node]['error']))
def clearvalues(resource, attribs):
@@ -605,20 +575,11 @@ def fullpath_target(currpath, forcepath=False):
ntarget += '/'
return ntarget
def do_resize(a, b):
if not inconsole:
return
height, width = struct.unpack(
'hh', fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ, b'....'))[:2]
tlvdata.send(session.connection, {'operation': 'resize', 'width': width,
'height': height})
def startconsole(nodename):
global inconsole
global consolename
global didconsole
signal.signal(signal.SIGWINCH, do_resize)
didconsole = True
consolename = nodename
tty.setraw(sys.stdin.fileno())
@@ -637,12 +598,11 @@ def quitconfetty(code=0, fullexit=False, fixterm=True):
if oldtcattr is not None:
termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, oldtcattr)
# Request default color scheme, to undo potential weirdness of terminal
if sys.stdout.isatty():
sys.stdout.write('\x1b[m')
sys.stdout.write('\x1b[m')
if fullexit:
if sys.stdout.isatty() and os.environ.get('TERM', '') not in ('linux'):
if os.environ.get('TERM', '') not in ('linux'):
sys.stdout.write('\x1b]0;\x07')
raise BailOut(code)
sys.exit(code)
else:
tlvdata.send(session.connection, {'operation': 'stop',
'path': currconsole})
@@ -835,10 +795,6 @@ parser.add_option("-s", "--server", dest="netserver",
parser.add_option("-c", "--control", dest="controlpath",
help="Path to offer terminal control",
metavar="PATH")
parser.add_option(
'-m', '--mintime', default=0,
help='Minimum time to run or else pause for input (used to keep a '
'terminal from closing quickly on error)')
opts, shellargs = parser.parse_args()
username = None
@@ -863,10 +819,49 @@ def server_connect():
passphrase = os.environ['CONFLUENT_PASSPHRASE']
session.authenticate(username, passphrase)
while not session.authenticated:
username = input("Name: ")
username = raw_input("Name: ")
passphrase = getpass.getpass("Passphrase: ")
session.authenticate(username, passphrase)
try:
server_connect()
except EOFError, KeyboardInterrupt:
sys.exit(0)
except socket.gaierror:
sys.stderr.write('Could not connect to confluent\n')
sys.exit(1)
# clear on start can help with readable of TUI, but it
# can be annoying, so for now don't do it.
# sys.stdout.write('\x1b[H\x1b[J')
# sys.stdout.flush()
if sys.stdout.isatty():
import readline
readline.parse_and_bind("tab: complete")
readline.parse_and_bind("set bell-style none")
dl = readline.get_completer_delims().replace('-', '')
readline.set_completer_delims(dl)
readline.set_completer(completer)
doexit = False
inconsole = False
pendingcommand = ""
session_node = get_session_node(shellargs)
if session_node is not None:
consoleonly = True
do_command("start /nodes/%s/console/session" % session_node, netserver)
doexit = True
elif shellargs:
command = " ".join(shellargs)
do_command(command, netserver)
quitconfetty(fullexit=True, fixterm=False)
powerstate = None
powertime = None
def check_power_state():
tlvdata.send(
session.connection,
@@ -875,148 +870,86 @@ def check_power_state():
return
if sys.stdout.isatty():
import readline
def main():
global inconsole
try:
server_connect()
except (EOFError, KeyboardInterrupt) as _:
raise BailOut(0)
except socket.gaierror:
sys.stderr.write('Could not connect to confluent\n')
raise BailOut(1)
# clear on start can help with readable of TUI, but it
# can be annoying, so for now don't do it.
# sys.stdout.write('\x1b[H\x1b[J')
# sys.stdout.flush()
global powerstate, powertime, clearpowermessage
if sys.stdout.isatty():
readline.parse_and_bind("tab: complete")
readline.parse_and_bind("set bell-style none")
dl = readline.get_completer_delims().replace('-', '')
readline.set_completer_delims(dl)
readline.set_completer(completer)
doexit = False
inconsole = False
pendingcommand = ""
session_node = get_session_node(shellargs)
if session_node is not None:
consoleonly = True
do_command("start /nodes/%s/console/session" % session_node, netserver)
doexit = True
elif shellargs:
command = " ".join(shellargs)
do_command(command, netserver)
quitconfetty(fullexit=True, fixterm=False)
powerstate = None
powertime = None
while inconsole or not doexit:
if inconsole:
try:
rdylist, _, _ = select.select(
(sys.stdin, session.connection), (), (), 10)
except select.error:
rdylist = ()
for fh in rdylist:
if fh == session.connection:
# this only should get called in the
# case of a console session
# each command should slurp up all relevant
# recv potential
#fh.read()
while inconsole or not doexit:
if inconsole:
try:
rdylist, _, _ = select.select(
(sys.stdin, session.connection), (), (), 10)
except select.error:
rdylist = ()
for fh in rdylist:
if fh == session.connection:
# this only should get called in the
# case of a console session
# each command should slurp up all relevant
# recv potential
#fh.read()
try:
data = tlvdata.recv(fh)
except Exception:
data = None
if type(data) == dict:
updatestatus(data)
continue
if data is not None:
if clearpowermessage:
sys.stdout.write("\x1b[2J\x1b[;H")
clearpowermessage = False
try:
data = tlvdata.recv(fh)
except Exception:
data = None
if type(data) == dict:
updatestatus(data)
continue
if data is not None:
data = client.stringify(data)
if clearpowermessage:
sys.stdout.write("\x1b[2J\x1b[;H")
clearpowermessage = False
try:
sys.stdout.write(data)
except IOError: # Some times circumstances are bad
# resort to byte at a time...
for d in data:
sys.stdout.write(d)
now = time.time()
if ('showtime' not in laststate or
(now // 60) != laststate['showtime'] // 60):
# don't bother churning if minute does not change
laststate['showtime'] = now
updatestatus()
sys.stdout.flush()
else:
deadline = 5
connected = False
while not connected and deadline > 0:
try:
server_connect()
connected = True
except Exception:
pass
if not connected:
time.sleep(1)
deadline -=1
if connected:
do_command(
"start /nodes/%s/console/session skipreplay=True" % consolename,
netserver)
else:
doexit = True
inconsole = False
sys.stdout.write("\r\n[remote disconnected]\r\n")
break
sys.stdout.write(data)
except IOError: # Some times circumstances are bad
# resort to byte at a time...
for d in data:
sys.stdout.write(d)
now = time.time()
if ('showtime' not in laststate or
(now // 60) != laststate['showtime'] // 60):
# don't bother churning if minute does not change
laststate['showtime'] = now
updatestatus()
sys.stdout.flush()
else:
try:
myinput = fh.read()
myinput = check_escape_seq(myinput, fh)
if myinput:
tlvdata.send(session.connection, myinput)
except IOError:
pass
if powerstate is None or powertime < time.time() - 10: # Check powerstate every 10 seconds
powertime = time.time()
powerstate = True
check_power_state()
else:
currcommand = prompt()
deadline = 5
connected = False
while not connected and deadline > 0:
try:
server_connect()
connected = True
except (socket.gaierror, socket.error):
pass
if not connected:
time.sleep(1)
deadline -=1
if connected:
do_command(
"start /nodes/%s/console/session skipreplay=True" % consolename,
netserver)
else:
doexit = True
inconsole = False
sys.stdout.write("\r\n[remote disconnected]\r\n")
break
else:
try:
myinput = fh.read()
myinput = check_escape_seq(myinput, fh)
if myinput:
tlvdata.send(session.connection, myinput)
except IOError:
pass
if powerstate is None or powertime < time.time() - 10: # Check powerstate every 10 seconds
powertime = time.time()
powerstate = True
check_power_state()
else:
currcommand = prompt()
try:
do_command(currcommand, netserver)
except socket.error:
try:
server_connect()
do_command(currcommand, netserver)
except socket.error:
try:
server_connect()
do_command(currcommand, netserver)
except socket.error:
doexit = True
sys.stdout.write('Lost connection to server')
quitconfetty(fullexit=True)
if __name__ == '__main__':
errcode = 0
deadline = 0
if opts.mintime:
deadline = os.times()[4] + float(opts.mintime)
try:
main()
except BailOut as e:
errcode = e.errorcode
finally:
if deadline and os.times()[4] < deadline:
sys.stderr.write('[Exited early, hit enter to continue]')
sys.stdin.readline()
if errcode == 0:
errcode = exitcode
sys.exit(errcode)
doexit = True
sys.stdout.write('Lost connection to server')
quitconfetty(fullexit=True)
-71
View File
@@ -1,71 +0,0 @@
#!/usr/bin/python2
import optparse
import signal
import sys
import os
try:
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except AttributeError:
pass
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
import confluent.sortutil as sortutil
def lookupdata(data, key):
ret = data.get(key, {}).get('value', '')
if ret is None:
ret = ''
return ret
def main():
argparser = optparse.OptionParser(
usage='''\n %prog noderange -o ansible.hosts
\n ''')
argparser.add_option('-o', '--output',
help='Ansible hosts file')
(options, args) = argparser.parse_args()
try:
noderange = args[0]
except IndexError:
argparser.print_help()
sys.exit(1)
if not options.output:
sys.stderr.write('Output file must be specified by -o\n')
sys.exit(1)
sess = client.Command()
databynode = {}
for res in sess.read('/noderange/{0}/attributes/all'.format(noderange)):
for node in res.get('databynode', {}):
if node not in databynode:
databynode[node] = {}
databynode[node].update(res['databynode'][node])
nodesbygroup = {}
with open(options.output, 'w') as importfile:
needempty = False
for node in sortutil.natural_sort(databynode):
data = databynode[node]
if not data.get('groups', []):
importfile.write(node + '\n')
needempty = True
for g in data.get('groups', []):
if g not in nodesbygroup:
nodesbygroup[g] = set([node])
else:
nodesbygroup[g].add(node)
if needempty:
importfile.write('\n')
for group in sortutil.natural_sort(nodesbygroup):
importfile.write('[{0}]\n'.format(group))
for node in sortutil.natural_sort(nodesbygroup[group]):
importfile.write('{0}\n'.format(node))
importfile.write('\n')
if __name__ == '__main__':
main()
-76
View File
@@ -1,76 +0,0 @@
#!/usr/bin/python2
import csv
import optparse
import signal
import sys
import os
try:
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except AttributeError:
pass
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
import confluent.sortutil as sortutil
headers = '# Type,Serial Number,Current IP,Current username,Current password,New password,Recovery password,Switch enable password,New IPv4,IPv4 Subnet mask,IPv4 Default gateway,IPv4 DNS1,IPv4 DNS2,New IPv6,IPv6 Prefix,IPv6 Default gateway,IPv6 DNS1,IPv6 DNS2,Domain,Host name,Display name,Rack,Lowest Rack Unit,Height,Force,Stored credentials ID,Managed authentication,Group Name'.split(',')
def lookupdata(data, key):
ret = data.get(key, {}).get('value', '')
if ret is None:
ret = ''
return ret
def main():
argparser = optparse.OptionParser(
usage='''\n %prog noderange -o bulkimport.csv
\n ''')
argparser.add_option('-o', '--output',
help='File to write for bulk import into xClarity Administrator')
(options, args) = argparser.parse_args()
try:
noderange = args[0]
except IndexError:
argparser.print_help()
sys.exit(1)
if not options.output:
sys.stderr.write('Output file must be specified by -o\n')
sys.exit(1)
if 'XCCUSER' not in os.environ or 'XCCPASS' not in os.environ:
sys.stderr.write('Must specify XCCUSER and XCCPASS in environment variables\n')
sys.exit(1)
xccuser = os.environ['XCCUSER']
xccpass = os.environ['XCCPASS']
sess = client.Command()
databynode = {}
for res in sess.read('/noderange/{0}/attributes/all'.format(noderange)):
for node in res.get('databynode', {}):
if node not in databynode:
databynode[node] = {}
databynode[node].update(res['databynode'][node])
for res in sess.read(
'/noderange/{0}/configuration/management_controller/net_interfaces/management'.format(
noderange)):
for node in res.get('databynode', {}):
currip = res['databynode'][node].get('ipv4_address', {}).get(
'value', '')
if currip:
databynode[node]['hardwaremanagement.manager'] = {
'value': currip.split('/', 1)[0]}
with open(options.output, 'w') as importfile:
bulkimport = csv.writer(importfile)
bulkimport.writerow(headers)
for node in sortutil.natural_sort(databynode):
data = databynode[node]
row = ['server', lookupdata(data, 'id.serial'), lookupdata(data, 'hardwaremanagement.manager'), xccuser, xccpass, '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', node, lookupdata(data, 'location.rack'), lookupdata(data, 'location.u'), '', '', '','', '']
bulkimport.writerow(row)
if __name__ == '__main__':
main()
-103
View File
@@ -1,103 +0,0 @@
#!/usr/bin/python2
import csv
import optparse
import signal
import sys
import os
try:
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except AttributeError:
pass
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
import confluent.sortutil as sortutil
def lookupdata(data, key):
ret = data.get(key, {}).get('value', '')
if ret is None:
ret = ''
return ret
def main():
argparser = optparse.OptionParser(
usage='''\n %prog noderange -o xcatnodes.def
\n ''')
argparser.add_option('-o', '--output',
help='xCAT stanza file')
argparser.add_option('-m', '--macs',
help='xCAT macs.csv to write')
(options, args) = argparser.parse_args()
try:
noderange = args[0]
except IndexError:
argparser.print_help()
sys.exit(1)
if not (options.output or options.macs):
sys.stderr.write('Output file must be specified by -o or -m\n')
sys.exit(1)
sess = client.Command()
databynode = {}
for res in sess.read('/noderange/{0}/attributes/all'.format(noderange)):
for node in res.get('databynode', {}):
if node not in databynode:
databynode[node] = {}
databynode[node].update(res['databynode'][node])
if options.output:
with open(options.output, 'w') as importfile:
for node in sortutil.natural_sort(databynode):
xcatattrs = collect_attribute_data(databynode, node)
importfile.write('{0}:\n'.format(node))
importfile.write(' objtype=node\n')
importfile.write(' arch=x86_64\n')
importfile.write(' netboot=xnba\n')
importfile.write(' mgt=ipmi\n')
for attr in xcatattrs:
if xcatattrs[attr] is None:
continue
importfile.write(' {0}={1}\n'.format(attr,
xcatattrs[attr]))
importfile.write('\n')
if options.macs:
with open(options.macs, 'w') as importfile:
macsv = csv.writer(importfile)
macsv.writerow(['#node', 'mac'])
for node in sortutil.natural_sort(databynode):
xcatattrs = collect_attribute_data(databynode, node)
macsv.writerow([node, xcatattrs['mac']])
def collect_attribute_data(databynode, node):
data = databynode[node]
xcatattrs = {}
xcatattrs['groups'] = ','.join(data.get('groups', []))
xcatattrs['bmc'] = data.get('hardwaremanagement.manager', {}).get(
'value', None)
xcatattrs['mpa'] = data.get('enclosure.manager', {}).get(
'value', None)
xcatattrs['slotid'] = data.get('enclosure.bay', {}).get(
'value', None)
gotmac = False
for key in data:
if key.startswith('net.') and 'hwaddr' in key:
currmac = data[key].get('value', None)
if currmac:
if gotmac:
sys.stderr.write(
'Ignoring {0} and using only {1} for mac, '
'multiple macs not supported by '
'confluent2xcat\n'.format(key, gotmac))
continue
gotmac = key
xcatattrs['mac'] = currmac
return xcatattrs
if __name__ == '__main__':
main()
+5 -24
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
@@ -17,7 +17,6 @@
__author__ = 'alin37'
from getpass import getpass
import optparse
import os
import signal
@@ -48,11 +47,6 @@ argparser.add_option('-e', '--environment', action='store_true',
'same name')
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()
@@ -73,25 +67,12 @@ exitcode = 0
nodetype="noderange"
if len(args) > 1:
if "=" in args[1] or options.clear or options.environment or options.prompt:
if "=" in args[1] or options.clear or options.environment:
if "=" in args[1] and options.clear:
print("Can not clear and set at the same time!")
argparser.print_help()
sys.exit(1)
argassign = None
if options.prompt:
argassign = {}
for arg in args[1:]:
oneval = 1
twoval = 2
while oneval != twoval:
oneval = getpass('Enter value for {0}: '.format(arg))
twoval = getpass('Confirm value for {0}: '.format(arg))
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)
exitcode=client.updateattrib(session,args,nodetype, noderange, options)
try:
# setting user output to what the user inputs
if args[1] == 'all':
@@ -130,6 +111,6 @@ else:
sys.stderr.write(res['error'] + '\n')
exitcode = 1
else:
print(res['item']['href'].replace('/', ''))
print res['item']['href'].replace('/', '')
sys.exit(exitcode)
sys.exit(exitcode)
+3 -7
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2015-2017 Lenovo
@@ -32,8 +32,6 @@ 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]
@@ -45,10 +43,8 @@ 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
if success != 0:
sys.exit(success)
# Determine which nodes were successful and print them
@@ -60,7 +56,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)
+3 -11
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2015 Lenovo
@@ -35,17 +35,10 @@ argparser = optparse.OptionParser()
argparser.add_option('-b', '--bios', dest='biosmode',
action='store_true', default=False,
help='Request BIOS style boot (rather than UEFI)')
argparser.add_option('-u', '--uefi', dest='uefimode',
action='store_true', default=False,
help='Request UEFI style boot (rather than BIOS)')
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()
@@ -58,8 +51,8 @@ except IndexError:
sys.exit(1)
client.check_globbing(noderange)
bootdev = None
if len(args) > 1:
bootdev = args[1]
if len(sys.argv) > 2:
bootdev = sys.argv[2]
if bootdev in ('net', 'pxe'):
bootdev = 'network'
session = client.Command()
@@ -70,7 +63,6 @@ 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,
+10 -90
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
@@ -43,7 +43,7 @@ def bailout(msg, code=1):
sys.exit(code)
argparser = optparse.OptionParser(usage="Usage: %prog [options] noderange [option|option=value]")
argparser = optparse.OptionParser()
argparser.add_option('-c', '--comparedefault', dest='comparedefault',
action='store_true', default=False,
help='Compare given settings to default or list settings '
@@ -54,30 +54,10 @@ argparser.add_option('-d', '--detail', dest='detail',
action='store_true', default=False,
help='Provide verbose information as available, such as '
'help text and possible valid values')
argparser.add_option('-e', '--extra', dest='extra',
action='store_true', default=False,
help='Access extra configuration. Extra configuration is generally '
'reserved for unpopular or redundant options that may be slow to '
'read. Notably the IMM category on Lenovo settings is considered '
'to be extra configuration')
argparser.add_option('-x', '--exclude', dest='exclude',
action='store_true', default=False,
help='Treat positional arguments as items to not '
'examine, compare, or restore default')
argparser.add_option('-a', '--advanced', dest='advanced',
action='store_true', default=False,
help='Include advanced settings, which are normally not '
'intended to be used without direction from the '
'relevant server vendor.')
argparser.add_option('-r', '--restoredefault', default=False,
dest='restoredefault', metavar="COMPONENT",
help='Restore the configuration of the node '
'to factory default for given component. '
'Currently 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 = {
@@ -108,9 +88,6 @@ setmode = None
assignment = {}
queryparms = {}
printsys = []
printbmc = []
printextbmc = []
printallbmc = False
setsys = {}
forceset = False
needval = None
@@ -135,7 +112,7 @@ def _assign_value():
def parse_config_line(arguments):
global setmode, printallbmc, forceset, key, value, needval, candidate, path, attrib
global setmode, forceset, key, value, needval, candidate, path, attrib
for param in arguments:
if param == 'show':
continue # forgive muscle memory of pasu users
@@ -145,7 +122,6 @@ def parse_config_line(arguments):
continue
if needval:
key = needval
needval = None
value = param
_assign_value()
continue
@@ -167,8 +143,6 @@ def parse_config_line(arguments):
if setmode != False:
bailout('Cannot do set and query in same command')
if '.' not in param:
if param == 'bmc':
printallbmc = True
matchedparms = False
for candidate in cfgpaths:
if candidate.startswith('{0}.'.format(param)):
@@ -184,19 +158,10 @@ def parse_config_line(arguments):
del queryparms[path]
except KeyError:
pass
if param.lower() == 'imm':
printextbmc.append(param)
options.extra = True
elif not matchedparms:
if not matchedparms:
printsys.append(param)
elif param not in cfgpaths:
if param.startswith('bmc.'):
printbmc.append(param.replace('bmc.', ''))
elif param.lower().startswith('imm'):
options.extra = True
printextbmc.append(param)
else:
printsys.append(param)
printsys.append(param)
else:
path, attrib = cfgpaths[param]
path = '/noderange/{0}/{1}'.format(noderange, path)
@@ -209,41 +174,13 @@ if options.batch:
argfile = open(options.batch, 'r')
argset = argfile.readline()
while argset:
try:
argset = argset[:argset.index('#')]
except ValueError:
pass
argset = argset.strip()
if argset:
parse_config_line(shlex.split(argset))
parse_config_line(shlex.split(argset))
argset = argfile.readline()
else:
parse_config_line(args[1:])
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(
'/noderange/{0}/configuration/system/clear'.format(noderange),
{'clear': True}):
rcode |= client.printerror(fr)
sys.exit(rcode)
elif options.restoredefault.lower() in (
'bmc', 'imm', 'xcc'):
for fr in session.update(
'/noderange/{0}/configuration/management_controller/clear'.format(noderange),
{'clear': True}):
rcode |= client.printerror(fr)
sys.exit(rcode)
else:
sys.stderr.write(
'Unrecognized component to restore defaults: {0}\n'.format(
options.restoredefault))
sys.exit(1)
if setmode:
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)
@@ -251,12 +188,8 @@ if setmode:
attrnamebypath = {}
for key in assignment:
if key not in cfgpaths:
if key.startswith('bmc.'):
path = 'configuration/management_controller/extended/all'
attrib = key.replace('bmc.', '')
else:
path = 'configuration/system/all'
attrib = key
path = 'configuration/system/all'
attrib = key
else:
path, attrib = cfgpaths[key]
if path not in updatebypath:
@@ -288,23 +221,10 @@ else:
NullOpt(), queryparms[path])
if rc:
sys.exit(rc)
if printsys == 'all' or printextbmc or printbmc or printallbmc:
if printbmc or not printextbmc:
rcode = client.print_attrib_path(
'/noderange/{0}/configuration/management_controller/extended/all'.format(noderange),
session, printbmc, options, attrprefix='bmc.')
if options.extra:
rcode |= client.print_attrib_path(
'/noderange/{0}/configuration/management_controller/extended/extra'.format(noderange),
session, printextbmc, options)
if printsys or options.exclude:
if printsys == 'all':
printsys = []
if (options.comparedefault or printsys == []) and not options.advanced:
path = '/noderange/{0}/configuration/system/all'.format(noderange)
else:
path = '/noderange/{0}/configuration/system/advanced'.format(
noderange)
path = '/noderange/{0}/configuration/system/all'.format(noderange)
rcode = client.print_attrib_path(path, session, printsys,
options)
sys.exit(rcode)
sys.exit(rcode)
+3 -47
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2015 Lenovo
@@ -17,14 +17,7 @@
import optparse
import os
import subprocess
import sys
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
import confluent.sortutil as sortutil
confettypath = os.path.join(os.path.dirname(sys.argv[0]), 'confetty')
argparser = optparse.OptionParser(
@@ -33,46 +26,9 @@ argparser = optparse.OptionParser(
"ctrl-'e', then release ctrl, then 'c', then '?' for a full list. "
"For example, ctrl-'e', then 'c', then '.' will exit the current "
"console")
argparser.add_option('-t', '--tile', action='store_true', default=False,
help='Tile console windows in the terminal')
(options, args) = argparser.parse_args()
if len(args) != 1:
argparser.print_help()
sys.exit(1)
if options.tile:
null = open('/dev/null', 'w')
nodes = []
sess = client.Command()
for res in sess.read('/noderange/{0}/nodes/'.format(args[0])):
node = res.get('item', {}).get('href', '/').replace('/', '')
if not node:
sys.stderr.write(res.get('error', repr(res)) + '\n')
sys.exit(1)
nodes.append(node)
initial = True
pane = 0
sessname = 'nodeconsole_{0}'.format(os.getpid())
for node in sortutil.natural_sort(nodes):
panename = '{0}:{1}'.format(sessname, pane)
if initial:
initial = False
subprocess.call(
['tmux', 'new-session', '-d', '-s',
sessname, '-x', '800', '-y',
'800', '{0} -m 5 start /nodes/{1}/console/session'.format(
confettypath, node)])
else:
subprocess.call(['tmux', 'select-pane', '-t', sessname])
subprocess.call(['tmux', 'set-option', '-t', panename, 'pane-border-status', 'top'], stderr=null)
subprocess.call(
['tmux', 'split', '-h', '-t', sessname,
'{0} -m 5 start /nodes/{1}/console/session'.format(
confettypath, node)])
subprocess.call(['tmux', 'select-layout', '-t', sessname, 'tiled'], stdout=null)
pane += 1
subprocess.call(['tmux', 'select-pane', '-t', sessname])
subprocess.call(['tmux', 'set-option', '-t', panename, 'pane-border-status', 'top'], stderr=null)
os.execlp('tmux', 'tmux', 'attach', '-t', sessname)
else:
os.execl(confettypath, confettypath, 'start',
'/nodes/{0}/console/session'.format(args[0]))
os.execl(confettypath, confettypath, 'start',
'/nodes/{0}/console/session'.format(args[0]))
+2 -2
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
@@ -55,4 +55,4 @@ for r in session.create('/noderange/', attribs):
exitcode |= 1
if 'created' in r:
print('{0}: created'.format(r['created']))
sys.exit(exitcode)
sys.exit(exitcode)
+38 -114
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
@@ -19,7 +19,6 @@ import csv
import optparse
import os
import sys
import time
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
@@ -27,49 +26,31 @@ if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
import confluent.sortutil as sortutil
defcolumns = ['Node', 'Model', 'Serial', 'UUID', 'Mac Address', 'Type',
'Current IP Addresses']
columnmapping = {
'Node': 'nodename',
'Model': 'modelnumber',
'Serial': 'serialnumber',
'UUID': 'uuid',
'Type': 'types',
'Current IP Addresses': 'ipaddrs',
'IP': 'ipaddrs',
'Bay': 'bay',
'Mac Address': 'macs',
'Mac': 'macs',
'Switch': 'switch',
'Port': 'port',
'Advertised IP': 'otheripaddrs',
'Other IP': 'otheripaddrs',
}
tabformat = '{0:>15}|{1:>15}|{2:>15}|{3:>36}|{4:>17}|{5:>12}|{6:>48}'
columns = ['Node', 'Model', 'Serial', 'UUID', 'Mac Address', 'Type',
'Current IP Addresses']
delimit = ['-' * 15, '-' * 15, '-' * 15, '-' * 36, '-' * 17, '-' * 12,
'-' * 48]
def print_disco(options, session, currmac, outhandler, columns):
def dumpmacs(procinfo):
return ','.join(procinfo['macs']) # + procinfo.get('relatedmacs', []))
def print_disco(options, session, currmac):
procinfo = {}
for tmpinfo in session.read('/discovery/by-mac/{0}'.format(currmac)):
procinfo.update(tmpinfo)
if 'Switch' in columns or 'Port' in columns:
for tmpinfo in session.read(
'/networking/macs/by-mac/{0}'.format(currmac)):
if 'ports' in tmpinfo:
# The api sorts so that the most specific available value
# is last
procinfo.update(tmpinfo['ports'][-1])
record = []
for col in columns:
rawcol = columnmapping[col]
rawval = procinfo.get(rawcol, '')
if isinstance(rawval, list):
record.append(','.join(rawval))
else:
record.append(str(rawval))
outhandler.add_row(record)
record = [procinfo['nodename'], procinfo['modelnumber'],
procinfo['serialnumber'], procinfo['uuid'], dumpmacs(procinfo),
','.join(procinfo['types']),
','.join(sorted(procinfo['ipaddrs']))]
if options.csv:
csv.writer(sys.stdout).writerow(record)
else:
print(tabformat.format(*record))
def process_header(header):
@@ -91,9 +72,7 @@ def process_header(header):
elif datum in ('bmc', 'imm', 'xcc'):
fields.append('hardwaremanagement.manager')
elif datum in ('bmc gateway', 'xcc gateway', 'imm gateway'):
fields.append('net.bmc.ipv4_gateway')
elif datum in ('bmc_gateway', 'xcc_gateway', 'imm_gateway'):
fields.append('net.bmc.ipv4_gateway')
fields.append('net.bmc.gateway')
elif datum in ('bmcuser', 'username', 'user'):
fields.append('secret.hardwaremanagementuser')
elif datum in ('bmcpass', 'password', 'pass'):
@@ -143,44 +122,24 @@ def datum_to_attrib(datum):
del datum['node']
return datum
unique_fields = frozenset(['serial', 'mac', 'uuid'])
def import_csv(options, session):
nodedata = []
unique_data = {}
with open(options.importfile, 'r') as datasrc:
records = csv.reader(datasrc)
fields = process_header(next(records))
for field in fields:
if field in unique_fields:
unique_data[field] = set([])
broken = False
for record in records:
currfields = list(fields)
nodedatum = {}
for datum in record:
currfield = currfields.pop(0)
if currfield in unique_fields:
if datum in unique_data[currfield]:
sys.stderr.write(
"Import contains duplicate values "
"({0} with value {1}\n".format(currfield, datum)
)
sys.exit(1)
unique_data[currfield].add(datum)
nodedatum[currfield] = datum
nodedatum[currfields.pop(0)] = datum
if not datum_complete(nodedatum):
sys.exit(1)
if not search_record(nodedatum, options, session) and not broken:
blocking_scan(session)
if not search_record(nodedatum, options, session):
sys.stderr.write(
"Could not match the following data: " +
repr(nodedatum) + '\n')
broken = True
sys.exit(1)
nodedata.append(nodedatum)
if broken:
sys.exit(1)
for datum in nodedata:
maclist = search_record(datum, options, session)
datum = datum_to_attrib(datum)
@@ -206,28 +165,13 @@ def import_csv(options, session):
def list_discovery(options, session):
orderby = None
if options.fields:
columns = []
for field in options.fields.split(','):
for cdt in columnmapping:
if cdt.lower().replace(
' ', '') == field.lower().replace(' ', ''):
columns.append(cdt)
else:
columns = defcolumns
if options.order:
for field in columns:
if options.order.lower() == field.lower():
orderby = field
outhandler = client.Tabulator(columns)
for mac in list_matching_macs(options, session):
print_disco(options, session, mac, outhandler, columns)
if options.csv:
outhandler.write_csv(sys.stdout, orderby)
csv.writer(sys.stdout).writerow(columns)
else:
for row in outhandler.get_table(orderby):
print(row)
print(tabformat.format(*columns))
print(tabformat.format(*delimit))
for mac in list_matching_macs(options, session):
print_disco(options, session, mac)
def clear_discovery(options, session):
for mac in list_matching_macs(options, session):
@@ -237,12 +181,8 @@ def clear_discovery(options, session):
else:
print(repr(res))
def list_matching_macs(options, session, node=None, checknode=True):
def list_matching_macs(options, session):
path = '/discovery/'
if node:
path += 'by-node/{0}/'.format(node)
elif checknode and options.node:
path += 'by-node/{0}/'.format(options.node)
if options.model:
path += 'by-model/{0}/'.format(options.model)
if options.serial:
@@ -265,25 +205,21 @@ def list_matching_macs(options, session, node=None, checknode=True):
path += 'by-mac/'
return [x['item']['href'] for x in session.read(path)]
def assign_discovery(options, session, needid=True):
def assign_discovery(options, session):
abort = False
if options.importfile:
return import_csv(options, session)
if not options.node:
sys.stderr.write("Node (-n) must be specified for assignment\n")
abort = True
if needid and not (options.serial or options.uuid or options.mac):
if not (options.serial or options.uuid or options.mac):
sys.stderr.write(
"UUID (-u), serial (-s), or ether address (-e) required for "
"assignment\n")
abort = True
if not options.node:
sys.stderr.write("Node (-n) must be specified for assignment\n")
abort = True
if abort:
sys.exit(1)
matches = list_matching_macs(options, session, None if needid else options.node, False)
if not matches:
# Do a rescan to catch missing requested data
blocking_scan(session)
matches = list_matching_macs(options, session, None if needid else options.node, False)
matches = list_matching_macs(options, session)
if not matches:
sys.stderr.write("No matching discovery candidates found\n")
sys.exit(1)
@@ -294,11 +230,6 @@ def assign_discovery(options, session, needid=True):
else:
print(repr(res))
def blocking_scan(session):
list(session.update('/discovery/rescan', {'rescan': 'start'}))
while(list(session.read('/discovery/rescan'))[0].get('scanning', False)):
time.sleep(0.5)
list(session.update('/networking/macs/rescan', {'rescan': 'start'}))
def main():
@@ -335,13 +266,8 @@ def main():
parser.add_option('-i', '--import', dest='importfile',
help='Import bulk assignment data from given CSV file',
metavar='IMPORT.CSV')
parser.add_option('-f', '--fields', dest='fields',
help='Select fields for output',
metavar='FIELDS')
parser.add_option('-o', '--order', dest='order',
help='Order output by given field', metavar='ORDER')
(options, args) = parser.parse_args()
if len(args) == 0 or args[0] not in ('list', 'assign', 'reassign', 'rescan', 'clear'):
if len(args) == 0 or args[0] not in ('list', 'assign', 'rescan', 'clear'):
parser.print_help()
sys.exit(1)
session = client.Command()
@@ -351,12 +277,10 @@ def main():
clear_discovery(options, session)
if args[0] == 'assign':
assign_discovery(options, session)
if args[0] == 'reassign':
assign_discovery(options, session, False)
if args[0] == 'rescan':
blocking_scan(session)
print("Rescan complete")
list(session.update('/discovery/rescan', {'rescan': 'start'}))
print("Rescan initiated")
if __name__ == '__main__':
main()
main()
+8 -19
View File
@@ -1,7 +1,7 @@
#!/usr/bin/python2
#!/usr/bin/python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2015-2019 Lenovo
# Copyright 2015-2017 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -33,15 +33,10 @@ if path.startswith('/opt'):
import confluent.client as client
if sys.version_info[0] < 3:
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
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]
@@ -50,11 +45,11 @@ except IndexError:
sys.exit(1)
client.check_globbing(noderange)
deletemode = False
if len(args) > 2:
if len(sys.argv) > 3:
argparser.print_help()
sys.exit(1)
if len(args) == 2:
if args[1] == 'clear':
if len(sys.argv) == 3:
if sys.argv[2] == 'clear':
deletemode = True
else:
argparser.print_help()
@@ -69,8 +64,6 @@ def format_event(evt):
display = dt.strptime(evt['timestamp'], '%Y-%m-%dT%H:%M:%S')
retparts.append(display.strftime('%m/%d/%Y %H:%M:%S'))
dscparts = []
if evt.get('log_id', None):
retparts.append(evt['log_id'] + ':')
if 'component_type' in evt and evt['component_type'] is not None:
dscparts.append(evt['component_type'])
if 'component' in evt and evt['component'] is not None:
@@ -84,15 +77,11 @@ def format_event(evt):
pass
dscparts.append(evttext)
retparts.append(' - '.join(dscparts))
msg = evt.get('message')
if not msg:
msg = ''
return ' '.join(retparts) + msg
return ' '.join(retparts)
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)):
@@ -109,4 +98,4 @@ for rsp in func('/noderange/{0}/events/hardware/log'.format(noderange)):
if 'events' in thisdata:
evtdata = thisdata['events']
for evt in evtdata:
print('{0}: {1}'.format(node, format_event(evt)))
print '{0}: {1}'.format(node, format_event(evt))
+9 -21
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2016-2017 Lenovo
@@ -37,14 +37,14 @@ exitcode = 0
def printfirm(node, prefix, data):
if 'model' in data and data['model']:
if 'model' in data:
prefix += ' ' + data['model']
builddesc = []
if 'build' in data and data['build']:
if 'build' in data:
builddesc.append(data['build'])
if 'date' in data and data['date']:
if 'date' in data:
builddesc.append(data['date'])
if 'version' in data and data['version']:
if 'version' in data:
version = data['version']
if builddesc:
version += ' ({0})'.format(' '.join(builddesc))
@@ -55,14 +55,9 @@ def printfirm(node, prefix, data):
components = ['all']
argparser = optparse.OptionParser(
usage="Usage: "
"%prog <noderange> [list][update [--backup <file>]]|[<components>]")
usage="Usage: %prog <noderange> [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:
@@ -71,15 +66,9 @@ try:
if args[1] == 'update':
upfile = args[2]
else:
if args[1] == 'list':
comps = args[2:]
else:
comps = args[1:]
components = []
for arg in comps:
for arg in args[1:]:
components += arg.split(',')
if not components:
components = ['all']
except IndexError:
argparser.print_help()
@@ -99,7 +88,6 @@ 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)
@@ -156,7 +144,7 @@ def show_firmware(session):
for prefix in inv:
firmware_shown = True
printfirm(node, prefix, inv[prefix])
if not firmware_shown and not exitcode:
if not firmware_shown:
argparser.print_help()
@@ -168,4 +156,4 @@ try:
update_firmware(session, upfile)
except KeyboardInterrupt:
print('')
sys.exit(exitcode)
sys.exit(exitcode)
+7 -22
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
@@ -17,7 +17,6 @@
__author__ = 'alin37'
from getpass import getpass
import optparse
import os
import signal
@@ -36,9 +35,9 @@ import confluent.client as client
argparser = optparse.OptionParser(
usage='''\n %prog [options] \
\n %prog [options] nodegroup [list of attributes|all] \
\n %prog [options] nodegroup [list of attributes] \
\n %prog [options] nodegroup nodes=value1,value2 \
\n %prog -e nodegroup <attribute names to set> \
\n %prog -e nodegroup <attribute names to set> \
\n %prog [options] nodegroup nodes=value1,value2
\n ''')
argparser.add_option('-b', '--blame', action='store_true',
@@ -48,8 +47,6 @@ argparser.add_option('-e', '--environment', action='store_true',
'same name')
argparser.add_option('-c', '--clear', action='store_true',
help='Clear variables')
argparser.add_option('-p', '--prompt', action='store_true',
help='Prompt for attribute values interactively')
(options, args) = argparser.parse_args()
@@ -75,19 +72,7 @@ if len(args) > 1:
print("Can not clear and set at the same time!")
argparser.print_help()
sys.exit(1)
argassign = None
if options.prompt:
argassign = {}
for arg in args[1:]:
oneval = 1
twoval = 2
while oneval != twoval:
oneval = getpass('Enter value for {0}: '.format(arg))
twoval = getpass('Confirm value for {0}: '.format(arg))
if oneval != twoval:
print('Values did not match.')
argassign[arg] = twoval
exitcode=client.updateattrib(session,args,nodetype, nodegroups, options, argassign)
exitcode=client.updateattrib(session,args,nodetype, nodegroups, options)
try:
# setting user output to what the user inputs
if args[1] == 'all':
@@ -97,7 +82,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 +108,6 @@ else:
sys.stderr.write(res['error'] + '\n')
exitcode = 1
else:
print(res['item']['href'].replace('/', ''))
print res['item']['href'].replace('/', '')
sys.exit(exitcode)
sys.exit(exitcode)
+2 -2
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
@@ -55,4 +55,4 @@ for r in session.create('/nodegroups/', attribs):
exitcode |= 1
if 'created' in r:
print('{0}: created'.format(r['created']))
sys.exit(exitcode)
sys.exit(exitcode)
-62
View File
@@ -1,62 +0,0 @@
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2015-2017 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.
__author__ = 'jjohnson2,alin37,andywray'
import optparse
import os
import signal
import sys
try:
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except AttributeError:
pass
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
def main():
argparser = optparse.OptionParser(
usage="Usage: %prog\n")
(options, args) = argparser.parse_args()
noderange=""
nodelist=""
nodelist = '/nodegroups/'
session = client.Command()
exitcode = 0
showtype='all'
requestargs=args[1:]
nodetype='noderange'
if len(args) > 0:
argparser.print_help()
sys.exit(1)
else:
for res in session.read(nodelist):
if 'error' in res:
sys.stderr.write(res['error'] + '\n')
exitcode = 1
else:
print(res['item']['href'].replace('/', ''))
sys.exit(exitcode)
if __name__ == '__main__':
main()
+2 -2
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
@@ -49,4 +49,4 @@ for r in session.delete('/nodegroups/{0}'.format(noderange)):
exitcode |= 1
if 'deleted' in r:
print('{0}: deleted'.format(r['deleted']))
sys.exit(exitcode)
sys.exit(exitcode)
-63
View File
@@ -1,63 +0,0 @@
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 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.
__author__ = 'jjohnson2,alin37,andywray'
import optparse
import os
import signal
import sys
try:
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except AttributeError:
pass
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
def main():
argparser = optparse.OptionParser(
usage="Usage: %prog <group> <new group name>\n")
(options, args) = argparser.parse_args()
noderange=""
nodelist=""
nodelist = '/nodegroups/'
session = client.Command()
exitcode = 0
requestargs=args[1:]
nodetype='noderange'
if len(args) != 2:
argparser.print_help()
sys.exit(1)
else:
for res in session.update(
'/nodegroups/{0}/attributes/rename'.format(args[0]),
{'rename': args[1]}):
if 'error' in res:
sys.stderr.write(res['error'] + '\n')
exitcode = 1
else:
print('{0}: {1}'.format(res['oldname'], res['newname']))
sys.exit(exitcode)
if __name__ == '__main__':
main()
+4 -14
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2015-2017 Lenovo
@@ -32,8 +32,7 @@ if path.startswith('/opt'):
import confluent.client as client
if sys.version_info[0] < 3:
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
argparser = optparse.OptionParser(usage="Usage: %prog <noderange>")
(options, args) = argparser.parse_args()
@@ -45,13 +44,6 @@ except IndexError:
client.check_globbing(noderange)
codemappings = {
'ok': 0,
'warning': 1,
'critical': 2,
'failed': 2,
}
def main():
global session, exitcode, healthbynode, healthexplanations, health, node, sensor, explanation
session = client.Command()
@@ -74,11 +66,9 @@ def main():
if 'error' in health[node]:
sys.stderr.write('{0}: Error: {1}\n'.format(
node, health[node]['error']))
exitcode = 3
exitcode |= 1
if 'health' in health[node]:
healthbynode[node] = health[node]['health']['value']
if codemappings[healthbynode[node]] > exitcode:
exitcode = codemappings[healthbynode[node]]
if 'sensors' in health[node]:
healthexplanations[node] = []
for sensor in health[node]['sensors']:
@@ -104,5 +94,5 @@ try:
main()
except KeyboardInterrupt:
print('')
sys.exit(exitcode)
sys.exit(0)
+5 -2
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2015-2017 Lenovo
@@ -31,7 +31,7 @@ if path.startswith('/opt'):
import confluent.client as client
argparser = optparse.OptionParser(usage="Usage: %prog <noderange> [on|off|blink]")
argparser = optparse.OptionParser(usage="Usage: %prog <noderange> [on|off]")
(options, args) = argparser.parse_args()
try:
noderange = args[0]
@@ -42,6 +42,9 @@ client.check_globbing(noderange)
identifystate = None
if len(sys.argv) > 2:
identifystate = sys.argv[2]
else:
argparser.print_help()
sys.exit(1)
session = client.Command()
exitcode = 0
sys.exit(
+10 -20
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2016-2017 Lenovo
@@ -34,8 +34,7 @@ if path.startswith('/opt'):
import confluent.client as client
if sys.version_info[0] < 3:
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
filters = []
@@ -51,17 +50,15 @@ def print_mem_info(node, prefix, meminfo):
memdescfmt = '{0}GB PC'
if meminfo['memory_type'] == 'DDR3 SDRAM':
memdescfmt += '3-{1} '
elif 'DDR4' in meminfo['memory_type']:
elif meminfo['memory_type'] == 'DDR4 SDRAM':
memdescfmt += '4-{1} '
else:
print('{0}: {1}: Unrecognized Memory'.format(node, prefix))
return
if meminfo.get('ecc', False):
if meminfo['ecc']:
memdescfmt += 'ECC '
capacity = meminfo['capacity_mb'] / 1024
modtype = meminfo.get('module_type', None)
if modtype:
memdescfmt += modtype
memdescfmt += meminfo['module_type']
memdesc = memdescfmt.format(capacity, meminfo['speed'])
print('{0}: {1} description: {2}'.format(node, prefix, memdesc))
print('{0}: {1} manufacturer: {2}'.format(
@@ -69,11 +66,10 @@ def print_mem_info(node, prefix, meminfo):
print('{0}: {1} model: {2}'.format(node, prefix, meminfo['model']))
print('{0}: {1} serial number: {2}'.format(node, prefix,
meminfo['serial']))
if 'manufacture_date' in meminfo:
print('{0}: {1} manufacture date: {2}'.format(node, prefix,
meminfo['manufacture_date']))
print('{0}: {1} manufacture location: {2}'.format(
node, prefix, meminfo['manufacture_location']))
print('{0}: {1} manufacture date: {2}'.format(node, prefix,
meminfo['manufacture_date']))
print('{0}: {1} manufacture location: {2}'.format(
node, prefix, meminfo['manufacture_location']))
exitcode = 0
@@ -91,7 +87,6 @@ def printerror(res, node=None):
url = '/noderange/{0}/inventory/hardware/all/all'
usedprefixes = set([])
argparser = optparse.OptionParser(
usage="Usage: %prog <noderange> [serial|model|uuid|mac]")
@@ -134,11 +129,6 @@ try:
continue
for inv in res['databynode'][node]['inventory']:
prefix = inv['name']
idx = 2
while (node, prefix) in usedprefixes:
prefix = '{0} {1}'.format(inv['name'], idx)
idx += 1
usedprefixes.add((node, prefix))
if not inv['present']:
if not filters:
if options.json:
@@ -146,7 +136,7 @@ try:
databynode[node] = {}
databynode[node][prefix] = inv
else:
print('{0}: {1}: Not Present'.format(node, prefix))
print '{0}: {1}: Not Present'.format(node, prefix)
continue
info = inv['information']
info.pop('board_extra', None)
-145
View File
@@ -1,145 +0,0 @@
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 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.
import optparse
import os
import signal
import sys
import time
try:
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except AttributeError:
pass
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
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
delete = False
try:
noderange = args[0]
if len(args) > 1:
if args[1] == 'install':
upfile = args[2]
elif args[1] == 'save':
downdir = args[2]
elif args[1] == 'delete':
delete = args[2]
elif args[1] != 'list':
argparser.print_help()
sys.exit(1)
except IndexError:
argparser.print_help()
sys.exit(1)
client.check_globbing(noderange)
def install_license(session, filename):
global exitcode
resource = '/noderange/{0}/configuration/' \
'management_controller/licenses/'.format(noderange)
filename = os.path.abspath(filename)
instargs = {'filename': filename}
for res in session.create(resource, instargs):
for node in res.get('databynode', []):
if 'error' in res['databynode'][node]:
sys.stderr.write('{0}: {1}\n'.format(
node, res['databynode'][node]['error']))
sys.exit(res['databynode'][node].get('errorcode', 1))
show_licenses(session)
def save_licenses(session, dirname):
global exitcode
resource = '/noderange/{0}/configuration/' \
'management_controller/save_licenses'.format(noderange)
filename = os.path.abspath(dirname)
instargs = {'dirname': filename}
for res in session.create(resource, instargs):
for node in res.get('databynode', {}):
fname = res['databynode'][node].get('filename', None)
if fname:
print('{0}: Saved license to {1}'.format(node, fname))
elif 'error' in res['databynode'][node]:
sys.stderr.write('{0}: {1}\n'.format(node, res['databynode'][node]['error']))
else:
sys.stderr.write('{0}: {1}\n'.format(node, repr(res['databynode'][node])))
def show_licenses(session):
global exitcode
for res in session.read(
'/noderange/{0}/configuration/management_controller/licenses/'
'all'.format(noderange)):
for node in res.get('databynode', {}):
for license in res['databynode'][node].get('License', []):
msg = '{0}: {1}'.format(node, license.get('feature',
'Unknown'))
if license.get('state', 'Active') != 'Active':
msg += ' ({0})'.format(license['state'])
print(msg)
def delete_license(session, licname):
global exitcode
licstodel = []
for res in list(session.read(
'/noderange/{0}/configuration/management_controller/licenses/'
'all'.format(noderange))):
for node in res.get('databynode', {}):
for license in res['databynode'][node].get('License', []):
if license.get('feature', None) == licname:
prefix = '/nodes/{0}/configuration/management_controller/licenses/'.format(node)
for currlic in list(session.read(prefix)):
currlic = currlic.get('item', {}).get('href', 'all')
if currlic == 'all':
continue
currname = list(session.read(prefix + currlic))[0]
currname = currname.get('License', [{}])[0].get('feature', None)
if currname == licname:
list(session.delete(prefix + currlic))
show_licenses(session)
try:
session = client.Command()
if upfile:
install_license(session, upfile)
elif downdir:
save_licenses(session, downdir)
elif delete:
session.stop_if_noderange_over(noderange, options.maxnodes)
delete_license(session, delete)
else:
show_licenses(session)
except KeyboardInterrupt:
print('')
sys.exit(exitcode)
+2 -2
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2015-2017 Lenovo
@@ -61,7 +61,7 @@ def main():
sys.stderr.write(res['error'] + '\n')
exitcode = 1
else:
print(res['item']['href'].replace('/', ''))
print res['item']['href'].replace('/', '')
sys.exit(exitcode)
+3 -4
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2018 Lenovo
@@ -83,8 +83,7 @@ def list_media(noderange, media):
for node in res.get('databynode', []):
url = res['databynode'][node].get('url', None)
name = res['databynode'][node].get('name', None)
if (url and not url.startswith('file:') and
not res['databynode'][node].get('secure', False)):
if url and not res['databynode'][node].get('secure', False):
name += ' (insecure)'
if not name:
continue
@@ -188,4 +187,4 @@ def main():
sys.exit(1)
handler(noderange, media)
if __name__ == '__main__':
main()
main()
+2 -7
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2015-2017 Lenovo
@@ -37,11 +37,6 @@ 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]
@@ -77,4 +72,4 @@ if options.previous:
# add dictionary to session
session.add_precede_dict(prev)
sys.exit(session.simple_noderange_command(noderange, '/power/state', setstate, promptover=options.maxnodes))
sys.exit(session.simple_noderange_command(noderange, '/power/state', setstate))
+2 -7
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
@@ -35,10 +35,6 @@ 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()
@@ -47,11 +43,10 @@ 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')
exitcode |= 1
if 'deleted' in r:
print('{0}: deleted'.format(r['deleted']))
sys.exit(exitcode)
sys.exit(exitcode)
-52
View File
@@ -1,52 +0,0 @@
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 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.
import optparse
import os
import signal
import sys
try:
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except AttributeError:
pass
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
argparser = optparse.OptionParser(usage="Usage: %prog <noderange> <newname>")
(options, args) = argparser.parse_args()
try:
noderange = args[0]
except IndexError:
argparser.print_help()
sys.exit(1)
client.check_globbing(noderange)
identifystate = None
if len(sys.argv) > 2:
newname = sys.argv[2]
else:
argparser.print_help()
sys.exit(1)
session = client.Command()
exitcode = 0
sys.exit(
session.simple_noderange_command(noderange, 'attributes/rename', newname))
+3 -7
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2015-2017 Lenovo
@@ -32,10 +32,6 @@ 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]
@@ -47,7 +43,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
@@ -60,7 +56,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)
-159
View File
@@ -1,159 +0,0 @@
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2016-2017 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.
from collections import deque
import optparse
import os
import select
import shlex
import signal
import subprocess
import sys
try:
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except AttributeError:
pass
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
import confluent.screensqueeze as sq
import confluent.sortutil as sortutil
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
# processes can be unkind for the unaware on memory pressure and such...
argparser.disable_interspersed_args()
(options, args) = argparser.parse_args()
if len(args) < 2 or ':' not in args[-1]:
argparser.print_help()
sys.exit(1)
concurrentprocs = options.count
noderange, targpath = args[-1].split(':', 1)
client.check_globbing(noderange)
c = client.Command()
cmdstr = ' '.join(args[:-1])
cmdstr = 'rsync -av --info=progress2 ' + cmdstr
cmdstr += ' {node}:' + targpath
currprocs = 0
all = set([])
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}):
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']
if not isinstance(cmd, bytes) and not isinstance(cmd, str):
cmd = cmd.encode('utf-8')
cmdv = shlex.split(cmd)
if currprocs < concurrentprocs:
currprocs += 1
run_cmdv(node, cmdv, all, pipedesc)
else:
pendingexecs.append((node, cmdv))
if not all or exitcode:
sys.exit(exitcode)
rdy, _, _ = select.select(all, [], [], 10)
nodeerrs = {}
pernodeout = {}
pernodefile = {}
output = sq.ScreenPrinter(noderange, c)
while all:
for r in rdy:
desc = pipedesc[r]
node = desc['node']
data = True
while data and select.select([r], [], [], 0)[0]:
data = r.read(1)
if data:
if desc['type'] == 'stdout':
if node not in pernodeout:
pernodeout[node] = ''
pernodeout[node] += client.stringify(data)
if '\n' in pernodeout[node]:
currout, pernodeout[node] = pernodeout[node].split('\n', 1)
if currout:
pernodefile[node] = os.path.basename(currout)
if '\r' in pernodeout[node]:
currout, pernodeout[node] = pernodeout[node].split('\r', 1)
if currout:
currout = currout.split()
try:
currout = currout[1]
output.set_output(node, '{0}:{1}'.format(pernodefile[node], currout))
except IndexError:
pernodefile = currout[0]
pass
else:
output.set_output(node, 'error!')
if node not in nodeerrs:
nodeerrs[node] = ''
nodeerrs[node] += client.stringify(data)
else:
pop = desc['popen']
ret = pop.poll()
if ret is not None:
exitcode = exitcode | ret
all.discard(r)
r.close()
if node not in nodeerrs:
output.set_output(node, 'complete')
if desc['type'] == 'stdout' and pendingexecs:
node, cmdv = pendingexecs.popleft()
run_cmdv(node, cmdv, all, pipedesc)
if all:
rdy, _, _ = select.select(all, [], [], 10)
for node in nodeerrs:
for line in nodeerrs[node].split('\n'):
sys.stderr.write('{0}: {1}\n'.format(node, line))
sys.exit(exitcode)
def run_cmdv(node, cmdv, all, pipedesc):
nopen = subprocess.Popen(
cmdv, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
pipedesc[nopen.stdout] = {'node': node, 'popen': nopen,
'type': 'stdout'}
pipedesc[nopen.stderr] = {'node': node, 'popen': nopen,
'type': 'stderr'}
all.add(nopen.stdout)
all.add(nopen.stderr)
if __name__ == '__main__':
run()
+7 -23
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2016-2017 Lenovo
@@ -39,17 +39,11 @@ import confluent.sortutil as sortutil
def run():
argparser = optparse.OptionParser(
usage="Usage: %prog [options] noderange commandexpression",
usage="Usage: %prog noderange commandexpression",
epilog="Expressions are the same as in attributes, e.g. "
"'ipmitool -H {hardwaremanagement.manager}' will be expanded.")
argparser.add_option('-f', '-c', '--count', type='int', default=168,
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()
@@ -67,7 +61,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:
@@ -75,9 +69,7 @@ def run():
exitcode |= exp.get('errorcode', 1)
ex = exp.get('databynode', ())
for node in ex:
cmd = ex[node]['value']
if not isinstance(cmd, bytes) and not isinstance(cmd, str):
cmd = cmd.encode('utf-8')
cmd = ex[node]['value'].encode('utf-8')
cmdv = shlex.split(cmd)
if currprocs < concurrentprocs:
currprocs += 1
@@ -93,7 +85,7 @@ def run():
desc = pipedesc[r]
node = desc['node']
data = True
while data and select.select([r], [], [], 0)[0]:
while data and select.select([r], [], [], 0):
data = r.readline()
if data:
if desc['type'] == 'stdout':
@@ -101,11 +93,7 @@ def run():
pernodeout[node] = []
pernodeout[node].append(data)
else:
data = client.stringify(data)
if options.nonodeprefix:
sys.stderr.write(data)
else:
sys.stderr.write('{0}: {1}'.format(node, data))
sys.stderr.write('{0}: {1}'.format(node, data))
sys.stderr.flush()
else:
pop = desc['popen']
@@ -119,11 +107,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:
sys.stdout.write('{0}: {1}'.format(node, line))
sys.stdout.write('{0}: {1}'.format(node, line))
sys.stdout.flush()
if all:
rdy, _, _ = select.select(all, [], [], 10)
+9 -19
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2015-2017 Lenovo
@@ -114,8 +114,7 @@ def sensorpass(showout=True, appendtime=False):
continue
for redundant_state in ('Non-Critical', 'Critical'):
try:
if sensedata.get('states', False):
sensedata['states'].remove(redundant_state)
sensedata['states'].remove(redundant_state)
except ValueError:
pass
resultdata[node][sensedata['name']] = sensedata
@@ -133,12 +132,11 @@ def sensorpass(showout=True, appendtime=False):
showval = u' {0} '.format(sensedata['value'])
if sensedata['units'] not in (None, u''):
showval += sensedata['units']
if sensedata.get('health', 'ok') != 'ok':
if sensedata['health'] != 'ok':
datadescription = [sensedata['health']]
else:
datadescription = []
if sensedata.get('states', False):
datadescription.extend(sensedata['states'])
datadescription.extend(sensedata['states'])
if datadescription:
if showval == '':
showval += u' {0}'.format(
@@ -149,11 +147,8 @@ def sensorpass(showout=True, appendtime=False):
if appendtime:
showval += ' @' + time.strftime(
'%Y-%m-%dT%H:%M:%S')
printval = u'{0}: {1}:{2}'.format(
node, sensedata['name'], showval)
if not isinstance(printval, str):
printval = printval.encode('utf-8')
print(printval)
print(u'{0}: {1}:{2}'.format(
node, sensedata['name'], showval).encode('utf-8'))
sys.stdout.flush()
return resultdata
@@ -161,7 +156,7 @@ def sensorpass(showout=True, appendtime=False):
def format_csv(csvwriter, orderedsensors, resdata, showtime=True):
for nodekey in resdata:
if showtime:
if isinstance(showtime, int):
if showtime.is_integer():
rowdata = [time.strftime('%Y-%m-%dT%H:%M:%S'), nodekey]
else:
rowdata = [time.strftime('%Y-%m-%dT%H:%M:%S.') +
@@ -188,7 +183,6 @@ def format_csv(csvwriter, orderedsensors, resdata, showtime=True):
except KeyError:
rowdata.append('N/A')
csvwriter.writerow(rowdata)
sys.stdout.flush()
def main():
@@ -202,11 +196,7 @@ def main():
orderedsensors.append(name)
orderedsensors.sort()
for name in orderedsensors:
headername = sensorheaders[name]
if (not isinstance(headername, str) and
not isinstance(headername, bytes)):
headername = headername.encode('utf-8')
headernames.append(headername)
headernames.append(sensorheaders[name].encode('utf-8'))
if options.csv:
linebyline = False
csvwriter = csv.writer(sys.stdout)
@@ -230,7 +220,7 @@ def main():
sys.exit(exitcode)
sleeptime = nextstart - os.times()[4]
if sleeptime > 0:
time.sleep(sleeptime)
time.sleep(nextstart - os.times()[4])
else:
if options.csv:
format_csv(csvwriter, orderedsensors, resdata, showtime=False)
+5 -9
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2015 Lenovo
@@ -43,10 +43,7 @@ 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:
@@ -56,8 +53,8 @@ except IndexError:
argparser.print_help()
sys.exit(1)
bootdev = None
if len(args) > 1:
bootdev = args[1]
if len(sys.argv) > 2:
bootdev = sys.argv[2]
if bootdev in ('net', 'pxe'):
bootdev = 'network'
session = client.Command()
@@ -66,7 +63,6 @@ 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))
persistent=options.persist))
+7 -23
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2016-2017 Lenovo
@@ -39,17 +39,11 @@ import confluent.sortutil as sortutil
def run():
argparser = optparse.OptionParser(
usage="Usage: %prog [options] noderange commandexpression",
usage="Usage: %prog noderange commandexpression",
epilog="Expressions are the same as in attributes, e.g. "
"'ipmitool -H {hardwaremanagement.manager}' will be expanded.")
argparser.add_option('-f', '-c', '--count', type='int', default=168,
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()
@@ -59,7 +53,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
@@ -68,7 +62,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:
@@ -76,9 +70,7 @@ def run():
exitcode |= exp.get('errorcode', 1)
ex = exp.get('databynode', ())
for node in ex:
cmd = ex[node]['value']
if not isinstance(cmd, str) and not isinstance(cmd, bytes):
cmd = cmd.encode('utf-8')
cmd = ex[node]['value'].encode('utf-8')
cmdv = ['ssh', node, cmd]
if currprocs < concurrentprocs:
currprocs += 1
@@ -102,11 +94,7 @@ def run():
pernodeout[node] = []
pernodeout[node].append(data)
else:
data = client.stringify(data)
if options.nonodeprefix:
sys.stderr.write(data)
else:
sys.stderr.write('{0}: {1}'.format(node, data))
sys.stderr.write('{0}: {1}'.format(node, data))
sys.stderr.flush()
else:
pop = desc['popen']
@@ -120,11 +108,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:
sys.stdout.write('{0}: {1}'.format(node, line))
sys.stdout.write('{0}: {1}'.format(node, line))
sys.stdout.flush()
if all:
rdy, _, _ = select.select(all, [], [], 10)
-237
View File
@@ -1,237 +0,0 @@
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2018 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import optparse
import os
import signal
import sys
try:
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except AttributeError:
pass
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
exitcode = 0
class OptParser(optparse.OptionParser):
def format_epilog(self, formatter):
return self.expand_prog_name(self.epilog)
def mbtohuman(mb):
if mb > 1000000:
return '{0:.3f} TB'.format(mb/1000000.0)
if mb > 1000:
return '{0:.3f} GB'.format(mb/1000.0)
return '{0:.3f} MB'.format(mb)
def showstorage(noderange, options, args):
global exitcode
session = client.Command()
disks = {}
arrays = {}
volumes = {}
scfg = session.read('/noderange/{0}/configuration/storage/all'.format(
noderange))
_print_cfg(scfg)
def _print_cfg(scfg):
global exitcode
storagebynode = {}
for e in scfg:
if 'error' in e:
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': []}
storagebynode[node][curr['type'] + 's'].append(curr)
for node in storagebynode:
for disk in sorted(storagebynode[node]['disks'],
key=lambda x: x['name']):
print('{0}: Disk {1} Description: {2}'.format(
node, disk['name'], disk['description']))
print('{0}: Disk {1} State: {2}'.format(node, disk['name'],
disk['state']))
print('{0}: Disk {1} FRU: {2}'.format(node, disk['name'],
disk['fru']))
print('{0}: Disk {1} Serial Number: {2}'.format(node, disk['name'],
disk['serial']))
if disk['array']:
print('{0}: Disk {1} Array: {2}'.format(node, disk['name'],
disk['array']))
for arr in storagebynode[node]['arrays']:
print('{0}: Array {1} Available Capacity: {2}'.format(
node, arr['id'], mbtohuman(arr['available'])))
print('{0}: Array {1} Total Capacity: {2}'.format(
node, arr['id'], mbtohuman(arr['capacity'])))
print('{0}: Array {1} RAID: {2}'.format(node, arr['id'],
arr['raid']))
print('{0}: Array {1} Disks: {2}'.format(node, arr['id'], ','.join(
arr['disks'])))
print(
'{0}: Array {1} Volumes: {2}'.format(node, arr['id'], ','.join(
arr['volumes'])))
for vol in storagebynode[node]['volumes']:
print('{0}: Volume {1}: Size: {2}'.format(node, vol['name'],
mbtohuman(vol['size'])))
print('{0}: Volume {1}: State: {2}'.format(node, vol['name'],
vol['state']))
print('{0}: Volume {1}: Array {2}'.format(node, vol['name'],
vol['array']))
def createstorage(noderange, options, args):
if options.raidlevel is None or options.disks is None:
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)
parms = {'disks': options.disks, 'raidlevel': options.raidlevel,
'name': names}
if options.size:
parms['size'] = options.size
if options.stripsizes:
parms['stripsizes'] = options.stripsizes
_print_cfg(session.create(
'/noderange/{0}/configuration/storage/volumes/{1}'.format(
noderange, names), parms))
def deletestorage(noderange, options, args):
if options.name is None:
if len(args) == 1:
names = args[0]
else:
sys.stderr.write('-n is required to indicate volume(s) to delete\n')
sys.exit(1)
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)):
if 'deleted' in rsp:
print('Deleted: {0}'.format(rsp['deleted']))
elif 'databynode' in rsp:
for node in rsp['databynode']:
if 'error' in rsp['databynode'][node]:
sys.stderr.write('{0}: {1}\n'.format(
node, rsp['databynode'][node]['error']))
else:
sys.stderr.write('{0}: {1}\n'.format(
node, repr(rsp['databynode'][node])))
else:
print(repr(rsp))
def setdisk(noderange, options, args):
if options.disks is None:
if len(args):
names = args.pop(0)
else:
sys.stderr.write('-d is required to indicate disk to modify\n')
sys.exit(1)
else:
names = options.disks
if not len(args) or args[0] not in ('hotspare', 'jbod', 'unconfigured'):
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)
funmap = {
'create': createstorage,
'show': showstorage,
'diskset': setdisk,
'delete': deletestorage,
'rm': deletestorage,
}
def main():
argparser = OptParser(
usage='Usage: %prog <noderange> [show|create|delete|diskset]',
epilog='',
)
argparser.add_option('-r', '--raidlevel', type='int',
help='RAID level to use when creating an array')
argparser.add_option('-d', '--disks', type='str',
help='Comma separated list of disks to use, or the '
'word "rest" to indicate use of all available '
'disks')
argparser.add_option('-s', '--size', type='str',
help='Comma separated list of sizes to use when '
'creating volumes. The sizes may be absolute '
'size (e.g. 16gb), percentage (10%) or the word '
'"rest" to use remaining capacity, default '
'behavior is to use all capacity to make a '
'volume')
argparser.add_option('-n', '--name', type='str',
help='Comma separated list of names to use when '
'naming volumes, or selecting a volume for '
'delete. Default behavior is to use '
'implementation provided default names.')
argparser.add_option('-z', '--stripsizes', type='str',
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')
try:
noderange = args[0]
operation = args[1]
except IndexError:
argparser.print_help()
sys.exit(1)
client.check_globbing(noderange)
try:
handler = funmap[operation]
except KeyError:
argparser.print_help()
sys.exit(1)
handler(noderange, options, args[2:])
if __name__ == '__main__':
main()
-152
View File
@@ -1,152 +0,0 @@
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2018 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import optparse
import os
import signal
import sys
import time
try:
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
except AttributeError:
pass
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
if path.startswith('/opt'):
sys.path.append(path)
import confluent.client as client
import confluent.screensqueeze as sq
exitcode = 0
def get_update_progress(session, url):
for res in session.read(url):
status = res['phase']
percent = res['progress']
detail = res['detail']
if status == 'error':
text = 'error!'
else:
text = '{0}: {1:3.0f}%'.format(status, percent)
return text, status, detail
def printerror(res, node=None):
global exitcode
if 'errorcode' in res:
exitcode = res['errorcode']
if 'error' in res:
if node:
sys.stderr.write('{0}: {1}\n'.format(node, res['error']))
else:
sys.stderr.write('{0}\n'.format(res['error']))
if 'errorcode' not in res:
exitcode = 1
for node in res.get('databynode', ()):
printerror(res['databynode'][node], node)
def download_servicedata(noderange, media, options):
global exitcode
session = client.Command()
output = sq.ScreenPrinter(noderange, session)
filename = os.path.abspath(media)
resource = '/noderange/{0}/support/servicedata/'.format(noderange)
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', ()):
output.set_output(nodename, 'error!')
noderrs[nodename] = res['databynode'][nodename].get(
'error', 'Unknown Error')
continue
watchurl = res['created']
currnode = watchurl.split('/')[1]
nodeurls[currnode] = '/' + watchurl
while nodeurls:
for node in list(nodeurls):
progress, status, err = get_update_progress(
session, nodeurls[node])
if status == 'error':
exitcode = 1
noderrs[node] = err
if status in ('error', 'complete', 'pending'):
list(session.delete(nodeurls[node]))
del nodeurls[node]
output.set_output(node, progress)
time.sleep(2)
allerrnodes = ','.join(noderrs)
if noderrs:
sys.stderr.write(
'Nodes had errors retrieving service data ({0})!\n'.format(allerrnodes))
for node in noderrs:
sys.stderr.write('{0}: {1}\n'.format(node, noderrs[node]))
funmap = {
'servicedata': download_servicedata,
}
class OptParser(optparse.OptionParser):
def format_epilog(self, formatter):
return self.expand_prog_name(self.epilog)
def main():
argparser = OptParser(
usage="Usage: %prog <noderange> servicedata "
"<filename>",
epilog='\nservicedata will save service data to the given '
'directory. It is saved to the location on the relevant '
'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:
noderange = args[0]
operation = args[1]
arglength = 2
if operation == 'servicedata':
media = args[2]
arglength = 3
if len(args) > arglength:
argparser.print_help()
sys.exit(1)
except IndexError:
argparser.print_help()
sys.exit(1)
client.check_globbing(noderange)
try:
handler = funmap[operation]
except KeyError:
argparser.print_help()
sys.exit(1)
handler(noderange, media, options)
if __name__ == '__main__':
main()
-167
View File
@@ -1,167 +0,0 @@
#!/usr/bin/python2
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 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.
import argparse
import csv
import fcntl
import io
import numpy as np
import os
import sixel
import subprocess
import sys
class DumbWriter(sixel.SixelWriter):
def restore_position(self, output):
return
def plot(gui, output, plotdata, bins):
import matplotlib as mpl
if not gui:
mpl.use('Agg')
import matplotlib.pyplot as plt
n, bins, patches = plt.hist(plotdata, bins)
plt.show()
if not gui:
if output:
tdata = output
else:
tdata = io.BytesIO()
plt.savefig(tdata)
if not gui and not output:
writer = DumbWriter()
writer.draw(tdata)
return n, bins
def textplot(plotdata, bins):
n, bins = np.histogram(plotdata, bins)
labels = []
for bin in bins:
labels.append('{0:0.1f}'.format(bin))
width = 80
# Since this will be primarily piped into, hard to get
# terminal width
labelwidth = 0
for lab in labels:
if len(lab) > labelwidth:
labelwidth = len(lab)
width -= (labelwidth) + 1
labelfmt = '{{0:>{0}s}}|'.format(labelwidth)
maxn = 0.0
for lgth in n:
if lgth > maxn:
maxn = float(lgth)
for i in range(len(n)):
print(labelfmt.format(labels[i]) + '=' * int(np.round((n[i]/maxn) * width)))
return n, bins
histogram = False
aparser = argparse.ArgumentParser(description='Quick access to common statistics')
aparser.add_argument('-c', type=int, default=0, help='Column number to analyze (default is last column)')
aparser.add_argument('-d', default=None, help='Value used to separate columns')
aparser.add_argument('-x', default=False, action='store_true', help='Output histogram in sixel format')
aparser.add_argument('-s', default=0, help='Number of header lines to skip before processing')
aparser.add_argument('-g', default=False, action='store_true', help='Open histogram in separate graphical window')
aparser.add_argument('-o', default=None, help='Output histogram to the specified filename in PNG format')
aparser.add_argument('-t', default=False, action='store_true', help='Output a histogram in text format')
aparser.add_argument('-v', default=False, action='store_true', help='Attempt to list nodes relevant to each histogram bar (requires -s, -o, or -t)')
aparser.add_argument('-b', type=int, default=10, help='Number of bins to use in histogram (default is 10)')
args = aparser.parse_args(sys.argv[1:])
plotdata = []
headlines = int(args.s)
while headlines >= 0:
data = sys.stdin.readline()
headlines -= 1
if args.d:
delimiter = args.d
else:
if '\t' in data:
delimiter = '\t'
elif ' ' in data:
delimiter = ' '
elif ',' in data:
delimiter = ','
else:
delimiter = ' ' # handle single column
data = list(csv.reader([data], delimiter=delimiter))[0]
nodebydatum = {}
idx = args.c - 1
autoidx = False
while data:
node = None
if ':' in data[0]:
node, data[0] = data[0].split(':', 1)
else:
node = data[0]
if idx == -1 and not autoidx:
while not autoidx:
try:
datum = float(data[idx])
except ValueError:
idx -= 1
continue
except IndexError:
sys.stderr.write('Unable to identify a numerical column\n')
sys.exit(1)
autoidx = True
else:
datum = float(data[idx])
if node:
if datum in nodebydatum:
nodebydatum[datum].add(node)
else:
nodebydatum[datum] = set([node])
plotdata.append(datum)
data = sys.stdin.readline()
data = list(csv.reader([data], delimiter=delimiter))[0]
n = None
if args.g or args.o or args.x:
n, bins = plot(args.g, args.o, plotdata, bins=args.b)
if args.t:
n, bins = textplot(plotdata, bins=args.b)
print('Samples: {5} Min: {3} Median: {0} Mean: {1} Max: {4} StandardDeviation: {2} Sum: {6}'.format(np.median(plotdata), np.mean(plotdata), np.std(plotdata), np.min(plotdata), np.max(plotdata), len(plotdata), np.sum(plotdata)))
if args.v and n is not None and nodebydatum:
print('')
currbin = bins[0]
bins = bins[1:]
currbinmembers = []
for datum in sorted(nodebydatum):
if datum > bins[0]:
nextbin = None
endbin = bins[0]
while len(bins) and bins[0] < datum:
nextbin = bins[0]
bins = bins[1:]
if not nextbin:
nextbin = np.max(plotdata)
print('Entries between {0} and {1}'.format(currbin, endbin))
currbin = nextbin
print('-' * 80)
print(','.join(sorted(currbinmembers)))
print('')
print('')
currbinmembers = []
for node in nodebydatum[datum]:
currbinmembers.append(node)
if currbinmembers:
print('Entries between {0} and {1}'.format(currbin, np.max(plotdata)))
print('-' * 80)
print(','.join(sorted(currbinmembers)))
print('')
print('')
-1
View File
@@ -1 +0,0 @@
../confluent_server/builddeb
+18 -141
View File
@@ -1,7 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 IBM Corporation
# Copyright 2015-2019 Lenovo
# Copyright 2015-2018 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -15,13 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
try:
import anydbm as dbm
except ImportError:
import dbm
import csv
import anydbm as dbm
import errno
import fnmatch
import hashlib
import os
import shlex
@@ -29,7 +24,6 @@ import socket
import ssl
import sys
import confluent.tlvdata as tlvdata
import confluent.sortutil as sortutil
SO_PASSCRED = 16
@@ -39,71 +33,6 @@ _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):
self.headers = headers
self.rows = []
def add_row(self, row):
self.rows.append(row)
def get_table(self, order=None):
i = 0
fmtstr = ''
separator = []
for head in self.headers:
if order and order == head:
order = i
neededlen = len(head)
for row in self.rows:
if len(row[i]) > neededlen:
neededlen = len(row[i])
separator.append('-' * (neededlen + 1))
fmtstr += '{{{0}:>{1}}}|'.format(i, neededlen + 1)
i = i + 1
fmtstr = fmtstr[:-1]
yield fmtstr.format(*self.headers)
yield fmtstr.format(*separator)
if order is not None:
for row in sorted(
self.rows,
key=lambda x: sortutil.naturalize_string(x[order])):
yield fmtstr.format(*row)
else:
for row in self.rows:
yield fmtstr.format(*row)
def write_csv(self, output, order=None):
output = csv.writer(output)
output.writerow(self.headers)
i = 0
for head in self.headers:
if order and order == head:
order = i
i = i + 1
if order is not None:
for row in sorted(
self.rows,
key=lambda x: sortutil.naturalize_string(x[order])):
output.writerow(row)
else:
for row in self.rows:
output.writerow(row)
def printerror(res, node=None):
exitcode = 0
@@ -120,10 +49,7 @@ def printerror(res, node=None):
def cprint(txt):
try:
print(txt)
except UnicodeEncodeError:
print(txt.encode('utf8'))
print(txt)
sys.stdout.flush()
def _parseserver(string):
@@ -155,8 +81,6 @@ class Command(object):
self.serverloc = server
if os.path.isabs(self.serverloc) and os.path.exists(self.serverloc):
self._connect_unix()
elif self.serverloc == '/var/run/confluent/api.sock':
raise Exception('Confluent service is not available')
else:
self._connect_tls()
tlvdata.recv(self.connection)
@@ -223,7 +147,7 @@ class Command(object):
return rc
def simple_noderange_command(self, noderange, resource, input=None,
key=None, errnodes=None, promptover=None, **kwargs):
key=None, errnodes=None, **kwargs):
try:
self._currnoderange = noderange
rc = 0
@@ -239,7 +163,6 @@ 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):
@@ -249,33 +172,6 @@ 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:
@@ -369,14 +265,13 @@ class Command(object):
if knownhosts:
certdata = self.connection.getpeercert(binary_form=True)
fingerprint = 'sha512$' + hashlib.sha512(certdata).hexdigest()
fingerprint = fingerprint.encode('utf-8')
hostid = '@'.join((port, server))
khf = dbm.open(os.path.join(clientcfgdir, "knownhosts"), 'c', 384)
if hostid in khf:
if fingerprint == khf[hostid]:
return
else:
replace = input(
replace = raw_input(
"MISMATCHED CERTIFICATE DATA, ACCEPT NEW? (y/n):")
if replace not in ('y', 'Y'):
raise Exception("BAD CERTIFICATE")
@@ -416,7 +311,7 @@ def attrrequested(attr, attrlist, seenattributes):
candidate = candidate.replace('hm', 'hardwaremanagement', 1)
if candidate in _attraliases:
candidate = _attraliases[candidate]
if fnmatch.fnmatch(attr.lower(), candidate.lower()):
if candidate.lower() == attr.lower():
seenattributes.add(truename)
return True
elif attr.lower().startswith(candidate.lower() + '.'):
@@ -429,12 +324,8 @@ 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):
def print_attrib_path(path, session, requestargs, options, rename=None):
exitcode = 0
seenattributes = set([])
for res in session.read(path):
@@ -443,7 +334,9 @@ 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=_sort_attrib):
for attr, val in sorted(
res['databynode'][node].items(),
key=lambda (k, v): v.get('sortid', k) if isinstance(v, dict) else k):
if attr == 'error':
sys.stderr.write('{0}: Error: {1}\n'.format(node, val))
continue
@@ -455,8 +348,6 @@ def print_attrib_path(path, session, requestargs, options, rename=None, attrpref
printattr = rename.get(attr, attr)
else:
printattr = attr
if attrprefix:
printattr = attrprefix + printattr
currattr = res['databynode'][node][attr]
if show_attr(attr, requestargs, seenattributes, options):
if 'value' in currattr:
@@ -516,12 +407,9 @@ def print_attrib_path(path, session, requestargs, options, rename=None, attrpref
(currattr.get('default', None) is not None and
currattr.get('value', None) is not None and
currattr['value'] != currattr['default'])):
cval = ','.join(currattr['value']) if isinstance(
currattr['value'], list) else currattr['value']
dval = ','.join(currattr['default']) if isinstance(
currattr['default'], list) else currattr['default']
cprint('{0}: {1}: {2} (Default: {3})'.format(
node, printattr, cval, dval))
node, printattr, currattr['value'],
currattr['default']))
else:
try:
@@ -530,14 +418,11 @@ def print_attrib_path(path, session, requestargs, options, rename=None, attrpref
details = False
if details:
if currattr.get('help', None):
attrout += u' (Help: {0})'.format(
currattr['help'])
attrout += ' (Help: {0})'.format(
currattr['help'].encode('utf-8'))
if currattr.get('possible', None):
try:
attrout += u' (Choices: {0})'.format(
','.join(currattr['possible']))
except TypeError:
pass
attrout += ' (Choices: {0})'.format(
','.join(currattr['possible']))
cprint(attrout)
if not exitcode:
if requestargs:
@@ -608,7 +493,7 @@ def printgroupattributes(session, requestargs, showtype, nodetype, noderange, op
exitcode = 1
return exitcode
def updateattrib(session, updateargs, nodetype, noderange, options, dictassign=None):
def updateattrib(session, updateargs, nodetype, noderange, options):
# update attribute
exitcode = 0
if options.clear:
@@ -641,14 +526,6 @@ def updateattrib(session, updateargs, nodetype, noderange, options, dictassign=N
'attributes/all',
value, key)
sys.exit(exitcode)
elif dictassign:
for key in dictassign:
if nodetype == 'nodegroups':
exitcode = session.simple_nodegroups_command(
noderange, 'attributes/all', dictassign[key], key)
else:
exitcode = session.simple_noderange_command(
noderange, 'attributes/all', dictassign[key], key)
else:
if "=" in updateargs[1]:
try:
@@ -707,4 +584,4 @@ def check_globbing(noderange):
'bash or change directories such that there is no filename '
'that would conflict.'
'\n'.format(noderange))
sys.exit(1)
sys.exit(1)
@@ -44,8 +44,6 @@ class ScreenPrinter(object):
self.fieldwidth = maxlen + textlen + 1 # 1 for column
def set_output(self, node, text):
if self.nodeoutput[node] == text:
return
self.nodeoutput[node] = text
if len(text) >= self.textlen:
self.textlen = len(text) + 1
+2 -7
View File
@@ -169,18 +169,13 @@ class GroupedData(object):
output.flush()
def print_deviants(self, output=sys.stdout, skipmodal=False, reverse=False,
count=False, basenode=None):
count=False):
self.generate_byoutput()
modaloutput = None
ismodal = True
revoutput = []
if basenode:
for checkout in self.byoutput:
if basenode in self.byoutput[checkout]:
modaloutput = checkout
for outdata in sorted(
self.byoutput, key=lambda x: [0 if modaloutput == x else 1,
0 - len(self.byoutput[x]),
self.byoutput, key=lambda x: [0 - len(self.byoutput[x]),
humanify_nodename(
self.get_group_text(
self.byoutput[x]
+3 -10
View File
@@ -25,11 +25,6 @@ try:
except NameError:
unicode = str
try:
range = xrange
except NameError:
pass
def decodestr(value):
ret = None
try:
@@ -39,13 +34,11 @@ def decodestr(value):
ret = value.decode('cp437')
except UnicodeDecodeError:
ret = value
except AttributeError:
return value
return ret
def unicode_dictvalues(dictdata):
for key in dictdata:
if isinstance(dictdata[key], bytes):
if isinstance(dictdata[key], str):
dictdata[key] = decodestr(dictdata[key])
elif isinstance(dictdata[key], datetime):
dictdata[key] = dictdata[key].strftime('%Y-%m-%dT%H:%M:%S')
@@ -56,7 +49,7 @@ def unicode_dictvalues(dictdata):
def _unicode_list(currlist):
for i in range(len(currlist)):
for i in xrange(len(currlist)):
if isinstance(currlist[i], str):
currlist[i] = decodestr(currlist[i])
elif isinstance(currlist[i], dict):
@@ -71,7 +64,7 @@ def send(handle, data):
data = data.encode('utf-8')
except AttributeError:
pass
if isinstance(data, bytes) or isinstance(data, unicode):
if isinstance(data, str) or isinstance(data, unicode):
# plain text, e.g. console data
tl = len(data)
if tl == 0:
+2 -10
View File
@@ -24,18 +24,10 @@ a confluent server.
%setup -n %{name}-%{version} -n %{name}-%{version}
%build
%if "%{dist}" == ".el8"
python3 setup.py build
%else
python2 setup.py build
%endif
python setup.py build
%install
%if "%{dist}" == ".el8"
python3 setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES --install-scripts=/opt/confluent/bin --install-purelib=/opt/confluent/lib/python
%else
python2 setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES --install-scripts=/opt/confluent/bin --install-purelib=/opt/confluent/lib/python
%endif
python setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES --install-scripts=/opt/confluent/bin --install-purelib=/opt/confluent/lib/python
%clean
-246
View File
@@ -36,250 +36,4 @@ alias nodereseat='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export C
alias noderun='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; noderun'
alias nodesensors='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodesensors'
alias nodesetboot='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodesetboot'
alias nodestorage='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodestorage'
alias nodeshell='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodeshell'
alias nodelicense='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodelicense'
# Do not continue for non-bash shells, the rest of this sets up bash completion functions
[ -z "$BASH_VERSION" -o -z "$PS1" ] && return
_confluent_get_args()
{
CMPARGS=($COMP_LINE)
NUMARGS=$((COMP_CWORD+1))
if [ "${COMP_WORDS[COMP_CWORD]}" == '' ]; then
CMPARGS+=("")
fi
GENNED=""
for CAND in ${COMP_CANDIDATES[@]}; do
candarray=(${CAND//,/ })
matched=0
for c in "${candarray[@]}"; do
for arg in "${CMPARGS[@]}"; do
if [ "$arg" = "$c" ]; then
matched=1
break
fi
done
done
if [ 0 = $matched ]; then
for c in "${candarray[@]}"; do
GENNED+=" $c"
done
fi
done
}
function _confluent_generic_completion()
{
_confluent_get_args
if [ $NUMARGS -ge 3 ] && [ ! -z "$GENNED" ]; then
COMPREPLY=($(compgen -W "$GENNED" -- ${COMP_WORDS[COMP_CWORD]}))
fi
if [ $NUMARGS -lt 3 ]; then
_confluent_nr_completion
return;
fi
}
_confluent_nodeidentify_completion()
{
COMP_CANDIDATES=("on,off,blink -h")
_confluent_generic_completion
}
_confluent_nodesetboot_completion()
{
COMP_CANDIDATES=("default,cd,network,setup,hd -h -b -p")
_confluent_generic_completion
}
_confluent_nodepower_completion()
{
COMP_CANDIDATES=("boot,off,on,status -h -p")
_confluent_generic_completion
}
_confluent_nodemedia_completion()
{
COMP_CANDIDATES=("list,upload,attach,detachall -h")
_confluent_get_args
if [ $NUMARGS -gt 3 ] && [ ${CMPARGS[-2]} == 'upload' ]; then
compopt -o default
COMPREPLY=()
return
fi
if [ $NUMARGS -ge 3 ] && [ ! -z "$GENNED" ]; then
COMPREPLY=($(compgen -W "$GENNED" -- ${COMP_WORDS[COMP_CWORD]}))
return;
fi
if [ $NUMARGS -lt 3 ]; then
_confluent_nr_completion
return;
fi
}
_confluent_nodefirmware_completion()
{
_confluent_get_args
if [ $NUMARGS == 3 ]; then
COMPREPLY=($(compgen -W "list update" -- ${COMP_WORDS[COMP_CWORD]}))
return;
fi
if [ $NUMARGS -gt 3 ] && [ ${CMPARGS[2]} == 'update' ]; then
compopt -o default
COMPREPLY=()
return
fi
if [ $NUMARGS -lt 3 ]; then
_confluent_nr_completion
return;
fi
}
_confluent_nodeshell_completion()
{
_confluent_get_args
if [ $NUMARGS == 3 ]; then
COMPREPLY=($(compgen -c -- ${COMP_WORDS[COMP_CWORD]}))
return
fi
if [ $NUMARGS -gt 3 ]; then
compopt -o default
COMPREPLY=()
return
fi
if [ $NUMARGS -lt 3 ]; then
_confluent_nr_completion
return;
fi
}
_confluent_nodelicense_completion()
{
_confluent_get_args
if [ $NUMARGS == 3 ]; then
COMPREPLY=($(compgen -W "install list save delete" -- ${COMP_WORDS[COMP_CWORD]}))
return;
fi
if [ $NUMARGS == 4 ] && [ ${CMPARGS[2]} == 'install' ]; then
compopt -o default
COMPREPLY=()
return
fi
if [ $NUMARGS == 4 ] && [ ${CMPARGS[2]} == 'save' ]; then
compopt -o dirnames
COMPREPLY=()
return
fi
if [ $NUMARGS -lt 3 ]; then
_confluent_nr_completion
return
fi
}
_confluent_nodesupport_completion()
{
_confluent_get_args
if [ $NUMARGS == 3 ]; then
COMPREPLY=($(compgen -W "servicedata" -- ${COMP_WORDS[COMP_CWORD]}))
return;
fi
if [ $NUMARGS == 4 ] && [ ${CMPARGS[2]} == 'servicedata' ]; then
compopt -o dirnames
COMPREPLY=()
return
fi
if [ $NUMARGS -lt 3 ]; then
_confluent_nr_completion
return
fi
}
_confluent_nodeattrib_completion()
{
COMP_CANDIDATES=$(nodeattrib '~.>1' all | awk '{print $2}'|sed -e 's/://')
_confluent_generic_completion
}
_confluent_nn_completion()
{
_confluent_get_args
if [ $NUMARGS -gt 2 ]; then
return;
fi
INPUT=${COMP_WORDS[COMP_CWORD]}
INPUT=${INPUT##*,-}
INPUT=${INPUT##*,}
INPUT=${INPUT##*@}
PREFIX=""
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[COMP_CWORD]}"))
}
_confluent_nr_completion()
{
CMPARGS=($COMP_LINE)
_confluent_get_args
if [ $NUMARGS -gt 2 ]; then
return;
fi
INPUT=${COMP_WORDS[COMP_CWORD]}
INPUT=${INPUT##*,-}
INPUT=${INPUT##*,}
INPUT=${INPUT##*@}
PREFIX=""
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[COMP_CWORD]}"))
}
_confluent_ng_completion()
{
_confluent_get_args
if [ $NUMARGS -gt 2 ]; then
return;
fi
INPUT=${COMP_WORDS[COMP_CWORD]}
INPUT=${INPUT##*,-}
INPUT=${INPUT##*,}
INPUT=${INPUT##*@}
PREFIX=""
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[COMP_CWORD]}"))
}
complete -F _confluent_nodeattrib_completion nodeattrib
complete -F _confluent_nodeattrib_completion nodegroupattrib
complete -F _confluent_nr_completion nodebmcreset
complete -F _confluent_nodesetboot_completion nodeboot
complete -F _confluent_nr_completion nodeconfig
complete -F _confluent_nn_completion nodeconsole
complete -F _confluent_nr_completion nodeeventlog
complete -F _confluent_nodefirmware_completion nodefirmware
complete -F _confluent_ng_completion nodegroupattrib
complete -F _confluent_ng_completion nodegroupremove
complete -F _confluent_nr_completion nodehealth
complete -F _confluent_nodeidentify_completion nodeidentify
complete -F _confluent_nr_completion nodeinventory
complete -F _confluent_nodeattrib_completion nodelist
complete -F _confluent_nodemedia_completion nodemedia
complete -F _confluent_nodepower_completion nodepower
complete -F _confluent_nr_completion noderemove
complete -F _confluent_nr_completion nodereseat
complete -F _confluent_nodeshell_completion noderun
complete -F _confluent_nr_completion nodesensors
complete -F _confluent_nodesetboot_completion nodesetboot
complete -F _confluent_nodeshell_completion nodeshell
complete -F _confluent_nodesupport_completion nodesupport
complete -F _confluent_nodelicense_completion nodelicense
+2 -6
View File
@@ -3,7 +3,7 @@ collate(1) -- Organize text input by node
## SYNOPSIS
`<other command> | collate [-a] [-d] [-w] [-s] [-c] [-r] [-l lognametemplate]`
`<other command> | collate [-a] [-d] [-w] [-s] [-c] [-r]`
## DESCRIPTION
@@ -26,10 +26,6 @@ node and group names sorted alphanumerically.
Express all but the most common result group in terms of diff from
the most common result group
* `-l`, `--log`:
Save output per node to individual log files, replacing {node} in the name
with the nodename of each
* `-w`, `--watch`:
Update results dynamically as data becomes available, rather than
waiting for the command to fully complete.
@@ -89,4 +85,4 @@ node and group names sorted alphanumerically.
` Processors.ExecuteDisableBit=Enable`
-55
View File
@@ -1,55 +0,0 @@
collective(1) -- Check and manage a confluent collective
==============================
## SYNOPSIS
`collective invite <server>`
`collective join <server> [-i TOKEN]`
`collective show`
`collective gencert`
## DESCRIPTION
**collective** helps manage the collective mode of confluent, where multiple
confluent servers are linked together to act as one. For example, the procedure to set up
a collective to run on three servers called mgt1, mgt2, and mgt3, first install and start
confluent as usual on the three servers. On mgt1, run `collective invite mgt2` and an
invitation token will be output. On mgt2, either run `collective join mgt1` to paste
the token interactively, or `collective join mgt1 -i <token>`. At this point, either
mgt1 or mgt2 can bring in mgt3. For example on mgt2 run `collective invite mgt3` and
on mgt3 run `collective join mgt2 -i <token>`
This can be linked together in the following manner with ssh:
on mgt1:
`# ssh mgt2 collective join mgt1 -i $(collective invite mgt2)`
Note that a collective is only redundant with 3 or more members. The collective
will function so long as more than half of the members are online. A collective
of two members is supported, but without redundancy.
Also note that the collective leader role is dynamic, but has no impact on interacting
with confluent. It is merely an internal role that can dynamically change depending
on circumstances.
## OPTIONS
* `-i`:
Provide the token as an argument rather than interactively.
## EXAMPLES
* Inviting a server called mgt2:
`# collective invite mgt2`
`bWd0MkA+BNQ6XAxMXlqJJa+EQRlihL/k9xCXnasgSQXZr989Pa1/ln7G3e1Ncxx6BMzMqqreHJVkPr2FrzjNit/UgHlg`
* On mgt2, joining mgt1:
`# collective join mgt1 -i bWd0MkA+BNQ6XAxMXlqJJa+EQRlihL/k9xCXnasgSQXZr989Pa1/ln7G3e1Ncxx6BMzMqqreHJVkPr2FrzjNit/UgHlg`
`Success`
* Showing the collective state:
`# collective show`
`Quorum: True`
`Leader: mgt1`
`Active collective members:`
` mgt2`
@@ -7,7 +7,6 @@ nodeattrib(8) -- List or change confluent nodes attributes
`nodeattrib <noderange> [<nodeattribute1=value1> <nodeattribute2=value2> ...]`
`nodeattrib -c <noderange> <nodeattribute1> <nodeattribute2> ...`
`nodeattrib -e <noderange> <nodeattribute1> <nodeattribute2> ...`
`nodeattrib -p <noderange> <nodeattribute1> <nodeattribute2> ...`
## DESCRIPTION
@@ -23,9 +22,6 @@ For a full list of attributes, run `nodeattrib <node> all` against a node.
If `-c` is specified, this will set the nodeattribute to a null value.
This is different from setting the value to an empty string.
Attributes may be specified by wildcard, for example `net.*switch` will report
all attributes that begin with `net.` and end with `switch`.
If the word all is specified, then all available attributes are given.
Omitting any attribute name or the word 'all' will display only attributes
that are currently set.
@@ -44,8 +40,6 @@ See nodegroupattrib(8) command on how to manage attributes on a group level.
Clear specified nodeattributes
* `-e`, `--environment`:
Set specified attributes based on exported environment variable of matching name. Environment variable names may be lower case or all upper case. Replace . with _ as needed (e.g. info.note may be specified as either $info_note or $INFO_NOTE
* `-p`, `--prompt`:
Request interactive prompting to provide values rather than the command line or environment variables.
## EXAMPLES
* Listing matching nodes of a simple noderange:
@@ -91,14 +85,6 @@ See nodegroupattrib(8) command on how to manage attributes on a group level.
`n1: console.method: `
`n2: console.method: `
* List all switches that a node is described as connected to:
`# nodeattrib d1 net.*switch`
`d1: net.mgt.switch: mgtswitch1`
`d1: net.pxe.switch: pxeswitch1`
`d1: net.switch:`
## SEE ALSO
nodegroupattrib(8), nodeattribexpressions(5)
## ATTRIBUTES
@@ -9,18 +9,6 @@ expression to generate the value.
An expression will contain some directives wrapped in `{}` characters. Within
`{}` are a number of potential substitute values and operations.
Note that syntax of expressions can have overlap with the shell syntax.
For example:
`$ echo (n2)`
`-bash: syntax error near unexpected token `n2'`
In such a case, it helps to quote the expression to allow it to be passed:
`$ echo '(n2)'`
`(n2)`
The most common operation is to extract a number from the nodename. These
values are available as n1, n2, etc. So for example attributes for a node named
b1o2r3u4 would have {n1} as 1, {n2} as 2, {n3} as 3, and {n4} as 4.
+2 -34
View File
@@ -4,44 +4,12 @@ nodeboot(8) -- Reboot a confluent node to a specific device
## SYNOPSIS
`nodeboot <noderange>`
`nodeboot [options] <noderange>` [default|cd|network|setup|hd]
`nodeboot <noderange>` [net|setup]
## DESCRIPTION
**nodeboot** reboots nodes in a noderange. If an additional argument is given,
it sets the node to specifically boot to that as the next boot. This
performs an immediate reboot without waiting for the OS. To set the boot
device without inducing a reboot, see the `nodesetboot` command.
## OPTIONS
* `-b`, `--bios`:
For a system that supports both BIOS and UEFI style boot, request BIOS style
boot if supported (some platforms will UEFI boot with this flag anyway).
* `-u`, `--uefi`:
This flag does nothing, it is for command compatibility with xCAT's rsetboot
* `-p`, `--persist`:
For a system that supports it, mark the boot override to persist rather than
be a one time change. Many systems do not support this functionality.
* `default`:
Request a normal default boot with no particular device override
* `cd`:
Request boot from media. Note that this can include physical CD,
remote media mounted as CD/DVD, and detachable hard disks drives such as usb
key devices.
* `network`:
Request boot to network
* `setup`:
Request to enter the firmware configuration menu (e.g. F1 setup) on next boot.
* `hd`:
Boot straight to hard disk drive
it sets the node to specifically boot to that as the next boot.
## EXAMPLES
* Booting n3 and n4 to the default boot behavior:
+2 -12
View File
@@ -3,8 +3,8 @@ nodeconfig(8) -- Show or change node configuration
## SYNOPSIS
`nodeconfig <noderange> [options] [<configuration>..]`
`nodeconfig <noderange> [options] [<configuration=value>..]`
`nodeconfig <noderange> [options] [<configuration>..]`
`nodeconfig <noderange> [options] [<configuration=value>..]`
## DESCRIPTION
@@ -22,11 +22,6 @@ given as a node expression, as documented in the man page for nodeattribexpressi
If combined with `-x`, will show all differing values except those indicated
by `-x`
* `-e`, `--extra`:
Read settings that are generally not needed, but may be slow to retrieve.
Notably this includes the IMM category of Lenovo systems. The most popular
IMM settings are available through faster 'bmc' attributes.
* `-x`, `--exclude`:
Rather than listing only the specified configuration parameters, list all
attributes except for the specified ones
@@ -35,11 +30,6 @@ given as a node expression, as documented in the man page for nodeattribexpressi
Provide detailed data as available. This can include help text and valid
values for a setting.
* `-r`, `--restoredefault`:
Request that the specified component of the targeted nodes will have its
configuration reset to default. Currently the only component implemented
is uefi.
* `-b`, '--batch':
Provide arguments as lines of a file, rather than the command line.
+1 -7
View File
@@ -2,7 +2,7 @@ nodeconsole(8) -- Open a console to a confluent node
=====================================================
## SYNOPSIS
`nodeconsole [options] <noderange>`
`nodeconsole <node>`
## DESCRIPTION
@@ -16,12 +16,6 @@ will initiate an automatic retry interval that is randomized between 2 and 4 min
The reopen escape sequence below requests an immediate retry, as does connecting
a new session.
## OPTIONS
* `-t`, `--tile`:
Use tmux to arrange consoles of the given noderange into a tiled layout on
the terminal screen
## ESCAPE SEQUENCE COMMANDS
While connected to a console, a number of commands may be performed through escape
-1
View File
@@ -1,5 +1,4 @@
nodedefine(8) -- Define new confluent nodes
===================================================================
## SYNOPSIS
@@ -23,9 +23,6 @@ data may be filtered by various parameters, as denoted in the options below.
**nodediscover assign** performs manual discovery, assigning an entry to a node
identity or, using `-i`, using a csv file to assign nodes all at once. For
example, a spreadsheet of serial numbers to desired node names could be used.
Note that if you see that the host is unreachable, it may be due to the IP
address on the endpoint having changed since last detected. In such a case, it
may help to **clear** and try **assign** again.
**nodediscover rescan** requests the server to do an active sweep for new
devices. Generally every effort is made to passively detect devices as they
@@ -78,20 +75,6 @@ the nodes.
* `-e MAC`, `--ethaddr=MAC`:
Operate against the system with the specified MAC
address
* `-f FIELDS`, `--fields=FIELDS`:
Request a custom set of fields. The available fields are:
Node: The node name if a correlation has been identified
Model: The model number
Serial: The serial number
UUID: The UUID as it should appear in DMI
Type: Device type (e.g. lenovo-xcc, pxe-client, etc)
IP: The confirmed working IP addresses associated with the record
Mac: Mac address of the relevant network interface
Switch: The nearest detected switch to the entry
Port: Port of the switch that most closely connects to the network interface
Advertised IP: IP addresses that may not have been confirmed, but are advertised
* `-o ORDER`, `--order=ORDER`:
Order output by given field. Field names are the same as documented in the -f argument.
* `-t TYPE`, `--type=TYPE`:
Operate against the system of the specified type
* `-c`, `--csv`:
+2 -6
View File
@@ -4,7 +4,7 @@ nodefirmware(8) -- Report firmware information on confluent nodes
## SYNOPSIS
`nodefirmware <noderange>`
`nodefirmware <noderange> list|<components>|core`
`nodefirmware <noderange> <components>|core`
`nodefirmware <noderange> update [--backup] <filename>`
## DESCRIPTION
@@ -13,11 +13,7 @@ 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. 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).
operation.
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:
@@ -7,7 +7,6 @@ nodegroupattrib(8) -- List or change confluent nodegroup attributes
`nodegroupattrib <group> [<nodeattribute>...]`
`nodegroupattrib <group> [<nodeattribute1=value1> <nodeattribute2=value2> ...]`
`nodegroupattrib <group> [-c] [<nodeattribute1> <nodeattribute2=value2> ...]`
`nodeattrib -p <noderange> <nodeattribute1> <nodeattribute2> ...`
## DESCRIPTION
@@ -41,5 +40,3 @@ the attributes are set on the node versus a group to which a node belongs.
## SEE ALSO
nodeattrib(8), nodeattribexpressions(5)
## ATTRIBUTES
@@ -1,5 +1,4 @@
nodegroupdefine(8) -- Define new confluent node group
===================================================================
## SYNOPSIS
@@ -7,7 +6,7 @@ nodegroupdefine(8) -- Define new confluent node group
## DESCRIPTION
`nodegroupdefine` allows the definition of a new nodegroup for the confluent management
`nodegroupdefine` allows the definition of a new node for the confluent management
service. It may only define a single group name at a time.
It has the same syntax as `nodegroupattrib(8)`, and the commands differ in
that `nodegroupattrib(8)` will error if a node group does not exist.
@@ -1,14 +0,0 @@
nodegrouplist(8) -- List the defined confluent nodegroups
===================================================================
## SYNOPSIS
`nodegrouplist`
## DESCRIPTION
`nodegrouplist` lists the currently defined groups in confluent.
## SEE ALSO
nodeattrib(8), nodeattribexpressions(5), nodegroupattrib(8)
+1 -2
View File
@@ -3,7 +3,7 @@ nodeidentify(8) -- Control the identify LED of confluent nodes
## SYNOPSIS
`nodidentify <noderange> [on|off|blink]`
`nodidentify <noderange> [on|off]`
## DESCRIPTION
@@ -13,7 +13,6 @@ options are supported:
* `on`: Turn on the identify LED
* `off`: Turn off the identify LED
* `blink`: Set the identify LED to blink (when supported by the system)
## EXAMPLES:
-17
View File
@@ -1,17 +0,0 @@
nodelicense(8) -- Manage license keys on BMC
=================================================================
## SYNOPSIS
`nodelicense <noderange> [list|install <filename>|delete <license>|save <directory>]`
## DESCRIPTION
`nodelicense` manages license keys on supported BMCs. Without an argument, the command
lists currently installed license. Using `delete` will remove the specified license name
from th eBMC. The `save` subcommand will take the passed directory (which may be in the form
of /path/to/{node}/ to have the node name substituted for each node) and back up installed licenses
to that directory. The `install` command will take the specified filename and install. The filename
argument may be of the form xcc_fod_0034_7X21{id.serial}.key to have the serial number substituted
to allow unique licenses to be specified in a single command.
-3
View File
@@ -17,9 +17,6 @@ displayed. If `-b` is specified, it will also display information on
how inherited and expression based attributes are defined. There is more
information on node attributes in nodeattributes(5) man page.
Attributes may be specified by wildcard, for example `net.*switch` will report
all attributes that begin with `net.` and end with `switch`.
## OPTIONS
* `-b`, `--blame`:
+2 -5
View File
@@ -36,13 +36,10 @@ Also, regular expressions may be used to indicate nodes with names matching cert
The other major noderange primitive is indicating nodes by some attribute value:
`location.rack=7`
The attribute name may use a wildcard:
`net.*switch=switch1`
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 +51,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.
-21
View File
@@ -1,21 +0,0 @@
nodersync(8) -- Run rsync in parallel against a noderange
=========================================================================
## SYNOPSIS
`nodersync <file/directorylist> <noderange>:<destination>`
## DESCRIPTION
Supervises execution of rsync to push files or a directory tree to the specified
noderange. This will present progress as percentage for all nodes.
## OPTIONS
* `-m`:
Specify maximum number of nodes for noderange max.
* `-c`:
Specify how many rsync executions to do concurrently. If noderange
exceeds the count, then excess nodes will wait until one of the
active count completes.
+1 -4
View File
@@ -30,10 +30,7 @@ control.
* `-p`, `--persist`:
For a system that supports it, mark the boot override to persist rather than
be a one time change. Many systems do not support this functionality.
* `-u`, `--uefi`:
This flag does nothing, it is for command compatibility with xCAT's rsetboot
* `default`:
Request a normal default boot with no particular device override
-65
View File
@@ -1,65 +0,0 @@
nodestorage(8) -- Examine/Modify storage configuration of a node
============================================
## SYNOPSIS
`nodestorage <noderange> [show|create|delete] [options]`
## DESCRIPTION
`nodestorage` provides access to the remote storage configuration of
the noderange.
## OPTIONS
* `-r` **RAIDLEVEL**, `--raidlevel`=**RAIDLEVEL**:
RAID level to use when creating an array
* `-d` **DISKS**, `--disks`=**DISKS**:
Comma separated list of disks to use, or the word "rest" to
indicate use of all available disks
* `-s` **SIZE**, `--size`=**SIZE**:
Comma separated list of sizes to use when creating
volumes. The sizes may be absolute size (e.g. 16gb),
percentage (10%) or the word "rest" to use remaining
capacity, default behavior is to use all capacity to
make a volume
* `-n` **NAME**, `--name`=**NAME**:
Comma separated list of names to use when naming
volumes, or selecting a volume for delete. Default
behavior is to use implementation provided default
## EXAMPLES
* Deleting the volume `somedata`:
`$ nodestorage d5 delete somedata`
`Deleted: somedata`
* Creating a raid5 of 4 disks and a volume named `somedata`:
`$ nodestorage d5 create -r 5 -d drive0,drive_1,drive_2,drive_3 -n somedata`
`d5: Volume somedata: Size: 1.905 TB`
`d5: Volume somedata: State: Optimal`
`d5: Volume somedata: Array 1-2`
* Showing current storage configuration of `d3`:
`$ nodestorage d3`
`d3: Disk m.2-0 Description: 128GB M.2 SATA SSD`
`d3: Disk m.2-0 State: online`
`d3: Disk m.2-0 FRU: 00LF428`
`d3: Disk m.2-0 Serial Number: H6B80054`
`d3: Disk m.2-0 Array: 0-0`
`d3: Disk m.2-1 Description: 128GB M.2 SATA SSD`
`d3: Disk m.2-1 State: online`
`d3: Disk m.2-1 FRU: 00LF428`
`d3: Disk m.2-1 Serial Number: H6B80059`
`d3: Disk m.2-1 Array: 0-0`
`d3: Array 0-0 Available Capacity: 0.000 MB`
`d3: Array 0-0 Total Capacity: 131.072 GB`
`d3: Array 0-0 RAID: RAID 1`
`d3: Array 0-0 Disks: m.2-0,m.2-1`
`d3: Array 0-0 Volumes: new_vd`
`d3: Volume new_vd: Size: 122.040 GB`
`d3: Volume new_vd: State: Optimal`
`d3: Volume new_vd: Array 0-0`
-32
View File
@@ -1,32 +0,0 @@
nodesupport(8) -- Utilities for interacting with vendor support
=================================================================
## SYNOPSIS
`nodesupport <noderange> servicedata <directory or filename>`
## DESCRIPTION
`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).
Note that the file will be downloaded to the confluent server that actually
connects to the managed system, so it will download to the remote system if running
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
`# nodesupport d1 servicedata svcdata.out`
`d1:initializing: 15%`
* Download support data from multiple nodes to a directory
`# nodesupport d1-d4 servicedata service/`
`d1:initializing: 0% d2:initializing: 0% d3:initializing: 0% d4:initializing: 0%`
`# ls service/`
`d1.svcdata d2.svcdata d3.svcdata d4.svcdata`
-2
View File
@@ -1,6 +1,4 @@
#!/bin/sh
cd `dirname $0`
python3 addattribs.py || python2 addattribs.py
cd `dirname $0`/doc/man
mkdir -p ../../man/man1
mkdir -p ../../man/man5
@@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/env python
# This is a sample python script for going through all observed mac addresses
# and assuming they are BMC related and printing nodeattrib commands
+2 -2
View File
@@ -22,10 +22,10 @@ This provides the modules common for both client and server
%setup -n %{name}-%{version} -n %{name}-%{version}
%build
python2 setup.py build
python setup.py build
%install
python2 setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES --install-purelib=/opt/confluent/lib/python --install-scripts=/opt/confluent/bin
python setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES --install-purelib=/opt/confluent/lib/python --install-scripts=/opt/confluent/bin
%clean
rm -rf $RPM_BUILD_ROOT
+17
View File
@@ -0,0 +1,17 @@
[metadata]
name = confluent_common
summary = Confluent Common Libraries
description-file =
README.txt
author = Jarrod Johnson
author-email = jjohnson2@lenovo.com
home-page = http://xcat.sf.net/
classifier =
Intended Audience :: Information Technology
Intended Audience :: System Administrators
License :: OSI Approved :: Apache Software License
Operating System :: POSIX :: Linux
Programming Language :: Python :: 2.6
Programming Language :: Python :: 2.7
[files]
-1
View File
@@ -1,4 +1,3 @@
include pam/*
include sysvinit/*
include systemd/*
include sysctl/*
+8 -36
View File
@@ -1,9 +1,8 @@
#!/usr/bin/python2
#!/usr/bin/env python
import argparse
import errno
import os
import pwd
import socket
import subprocess
import sys
@@ -23,7 +22,7 @@ except NameError:
pass
def make_certificate():
umask = os.umask(0o77)
umask = os.umask(0077)
try:
os.makedirs('/etc/confluent/cfg')
except OSError as e:
@@ -41,12 +40,6 @@ def make_certificate():
'/etc/confluent/srvcert.pem -subj /CN='
'{0}'.format(socket.gethostname()).split(' ')):
raise Exception('Error generating certificate')
try:
uid = pwd.getpwnam('confluent').pw_uid
os.chown('/etc/confluent/privkey.pem', uid, -1)
os.chown('/etc/confluent/srvcert.pem', uid, -1)
except KeyError:
pass
print('Certificate generated successfully')
os.umask(umask)
@@ -68,36 +61,19 @@ def join_collective(server, invitation):
make_certificate()
s = client.Command().connection
while not invitation:
invitation = input('Paste the invitation here: ')
invitation = raw_input('Paste the invitation here: ')
tlvdata.send(s, {'collective': {'operation': 'join',
'invitation': invitation,
'server': server}})
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 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)
print(res.get('collective',
{'status': 'Unknown response: ' + repr(res)})['status'])
def show_collective():
s = client.Command().connection
tlvdata.send(s, {'collective': {'operation': 'show'}})
res = tlvdata.recv(s)
if 'error' in res:
print(res['error'])
return
if 'error' in res['collective']:
print(res['collective']['error'])
return
@@ -124,13 +100,11 @@ def main():
sl = sp.add_parser('show', help='Show information about the collective')
ic = sp.add_parser('invite', help='Generate a invitation to allow a new '
'confluent instance to join as a '
'collective member. Run collective invite -h for more information')
'collective member')
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 = sp.add_parser('join', help='Join a collective')
jc.add_argument('server', help='A server currently in the collective')
jc.add_argument('-i', help='Invitation provided by runniing invite on an '
'existing collective member')
cmdset = a.parse_args()
@@ -142,8 +116,6 @@ 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()
+2 -2
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 IBM Corporation
@@ -32,7 +32,7 @@ import confluent.main
import multiprocessing
if __name__ == '__main__':
multiprocessing.freeze_support()
confluent.main.run(sys.argv)
confluent.main.run()
#except:
# pass
#p.disable()
-55
View File
@@ -1,55 +0,0 @@
from os.path import exists
import shutil
import socket
import subprocess
import tempfile
def get_openssl_conf_location():
if exists('/etc/pki/tls/openssl.cnf'):
return '/etc/pki/tls/openssl.cnf'
elif exists('/etc/ssl/openssl.cnf'):
return '/etc/ssl/openssl.cnf'
else:
raise Exception("Cannot find openssl config file")
def get_ip_addresses():
lines = subprocess.check_output('ip addr'.split(' '))
for line in lines.split('\n'):
if line.startswith(' inet6 '):
line = line.replace(' inet6 ', '').split('/')[0]
if line.startswith('fe80::'):
continue
if line == '::1':
continue
elif line.startswith(' inet '):
line = line.replace(' inet ', '').split('/')[0]
if line == '127.0.0.1':
continue
if line.startswith('169.254.'):
continue
else:
continue
yield line
def create_certificate():
shortname = socket.gethostname().split('.')[0]
longname = socket.getfqdn()
subprocess.check_call(
'openssl ecparam -name secp384r1 -genkey -out privkey.pem'.split(' '))
san = ['IP:{0}'.format(x) for x in get_ip_addresses()]
san.append('DNS:{0}'.format(shortname))
san.append('DNS:{0}'.format(longname))
san = ','.join(san)
sslcfg = get_openssl_conf_location()
tmpconfig = tempfile.mktemp()
shutil.copy2(sslcfg, tmpconfig)
with open(tmpconfig, 'a') as cfgfile:
cfgfile.write('\n[SAN]\nsubjectAltName={0}'.format(san))
subprocess.check_call(
'openssl req -new -x509 -key privkey.pem -days 7300 -out cert.pem '
'-subj /CN={0} -extensions SAN '
'-config {1}'.format(longname, tmpconfig).split(' ')
)
if __name__ == '__main__':
create_certificate()
+2 -2
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 Lenovo
@@ -82,7 +82,7 @@ elif args[0] == 'dump':
"or -s to do encrypted backup that requires keys.json from "
"another backup to restore.")
sys.exit(1)
os.umask(0o77)
os.umask(077)
main._initsecurity(conf.get_config())
if not os.path.exists(dumpdir):
os.makedirs(dumpdir)
+1 -1
View File
@@ -1,4 +1,4 @@
#!/usr/bin/python2
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 IBM Corporation
-68
View File
@@ -1,68 +0,0 @@
#!/bin/bash
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=""
fi
cd ..
mkdir -p /tmp/confluent # $DPKGNAME
cp -a * .git /tmp/confluent # $DPKGNAME
cd /tmp/confluent/$PKGNAME
if [ -x ./makeman ]; then
./makeman
fi
./makesetup
VERSION=`cat VERSION`
cat > setup.cfg << EOF
[install]
install-purelib=/opt/confluent/lib/python
install-scripts=/opt/confluent/bin
[sdist_dsc]
package=$DPKGNAME
EOF
$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, python-msgpack/' debian/control
else
sed -i 's/^\(Depends:.*\)/\1, confluent-client, python3-lxml, python3-eficompressor, python3-pycryptodome, python3-websocket, python3-msgpack/' debian/control
fi
if grep wheezy /etc/os-release; then
echo 'confluent_client python-confluent-client' >> debian/pydist-overrides
else
echo 'confluent_client confluent-client' >> debian/pydist-overrides
fi
fi
head -n -1 debian/control > debian/control1
mv debian/control1 debian/control
echo 'export PYBUILD_INSTALL_ARGS=--install-lib=/opt/confluent/lib/python' >> debian/rules
#echo 'Provides: python-'$DPKGNAME >> debian/control
#echo 'Conflicts: python-'$DPKGNAME >> debian/control
#echo 'Replaces: python-'$DPKGNAME' (<<2)' >> debian/control
#echo 'Breaks: python-'$DPKGNAME' (<<2)' >> debian/control
dpkg-buildpackage -rfakeroot -uc -us -i
if [ $? -ne 0 ]; then
echo "[ERROR] rpmbuild returned non-zero, run: rpmbuild -ba ~/rpmbuild/SPECS/$PKGNAME.spec"
exit 1
else
cd -
# Clean up the generated files in this directory
rm -rf $PKGNAME.egg-info dist setup.py
rm -rf $(find deb_dist -mindepth 1 -maxdepth 1 -type d)
if [ ! -z "$1" ]; then
mv deb_dist/* $1/
fi
fi
exit 0
+1 -1
View File
@@ -6,7 +6,7 @@ fi
./makesetup
VERSION=`cat VERSION`
PKGNAME=$(basename $(pwd))
python3 setup.py sdist > /dev/null 2>&1
python 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:
+4 -10
View File
@@ -1,6 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2016-2018 Lenovo
# Copyright 2016 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -64,12 +64,12 @@ class AsyncTermRelation(object):
# Need to keep an association of term object to async
# This allows the async handler to know the context of
# outgoing data to provide to calling code
def __init__(self, termid, asynchdl):
self.asynchdl = asynchdl
def __init__(self, termid, async):
self.async = async
self.termid = termid
def got_data(self, data):
self.asynchdl.add(self.termid, data)
self.async.add(self.termid, data)
class AsyncSession(object):
@@ -83,8 +83,6 @@ class AsyncSession(object):
self.reaper = eventlet.spawn_after(15, self.destroy)
def add(self, requestid, rsp):
if self.responses is None:
return
self.responses.append((requestid, rsp))
if self._evt:
self._evt.send()
@@ -109,7 +107,6 @@ class AsyncSession(object):
for console in self.consoles:
_consolesessions[console]['session'].destroy()
self.consoles = None
self.responses = None
del _asyncsessions[self.asyncid]
def run_handler(self, handler, requestid):
@@ -169,9 +166,6 @@ def handle_async(env, querydict, threadset):
currsess = AsyncSession()
yield messages.AsyncSession(currsess.asyncid)
return
if querydict['asyncid'] not in _asyncsessions:
raise exc.InvalidArgumentException(
'Invalid or expired async id')
mythreadid = greenlet.getcurrent()
threadset.add(mythreadid)
loggedout = None
+86 -164
View File
@@ -23,17 +23,11 @@ import confluent.config.configmanager as configmanager
import eventlet
import eventlet.tpool
import Cryptodome.Protocol.KDF as KDF
from fnmatch import fnmatch
import hashlib
import hmac
import multiprocessing
import os
import pwd
import confluent.userutil as userutil
import confluent.util as util
pam = None
try:
import confluent.pam as pam
import PAM
except ImportError:
pass
import time
@@ -45,59 +39,35 @@ _passchecking = {}
authworkers = None
authcleaner = None
_allowedbyrole = {
'Operator': {
'retrieve': ['*'],
'create': [
'/noderange/',
'/nodes/',
'/node*/media/uploads/',
'/node*/inventory/firmware/updates/*',
'/node*/suppport/servicedata*',
'/node*/attributes/expression',
'/nodes/*/console/session*',
'/nodes/*/shell/sessions*',
'/node*/configuration/*',
],
'update': [
'/discovery/*',
'/networking/macs/rescan',
'/node*/power/state',
'/node*/power/reseat',
'/node*/attributes/*',
'/node*/media/*tach',
'/node*/boot/nextdevice',
'/node*/identify',
'/node*/configuration/*',
],
'start': [
'/nodes/*/console/session*',
'/nodes/*/shell/sessions*',
],
'delete': [
'/discovery/*',
'/node*',
],
},
'Monitor': {
'retrieve': [
'/node*/health/hardware',
'/node*/power/state',
'/node*/sensors/*',
'/nodes/',
'/',
],
}
}
_deniedbyrole = {
# This supersedes the above and is only consulted after the allowed has happened
'Operator': {
'update': [
'/node*/configuration/management_controller/users/*',
]
}
}
class Credentials(object):
def __init__(self, username, passphrase):
self.username = username
self.passphrase = passphrase
self.haspam = False
def pam_conv(self, auth, query_list):
# use stored credentials in a pam conversation
self.haspam = True
resp = []
for query_entry in query_list:
query, pamtype = query_entry
if query.startswith('Password'):
resp.append((self.passphrase, 0))
else:
return None
return resp
def _prune_passcache():
# This function makes sure we don't remember a passphrase in memory more
# than 10 seconds
while True:
curtime = time.time()
for passent in _passcache.iterkeys():
if passent[2] < curtime - 10:
del _passcache[passent]
eventlet.sleep(10)
def _get_usertenant(name, tenant=False):
@@ -111,13 +81,11 @@ 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 b'/' in name: # tenant scoped name
tenant, user = name.split(b'/', 1)
elif '/' in name: # tenant scoped name
tenant, user = name.split('/', 1)
elif configmanager.is_tenant(name):
# the account is the implicit tenant owner account
user = name
@@ -125,9 +93,6 @@ 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
@@ -147,37 +112,21 @@ def authorize(name, element, tenant=False, operation='create',
and the relevant ConfigManager object for the context of the
request.
"""
# skipuserobj is a leftover from the now abandoned plan to use pam session
# to do authorization and authentication. Now confluent always does authorization
# even if pam does authentication.
if operation not in ('create', 'start', 'update', 'retrieve', 'delete', None):
return False
if operation not in ('create', 'start', 'update', 'retrieve', 'delete'):
return None
user, tenant = _get_usertenant(name, tenant)
if tenant is not None and not configmanager.is_tenant(tenant):
return False
return None
manager = configmanager.ConfigManager(tenant, username=user)
if skipuserobj:
return None, manager, user, tenant, skipuserobj
userobj = manager.get_user(user)
if not userobj:
for group in userutil.grouplist(user):
userobj = manager.get_usergroup(group)
if userobj:
break
if userobj: # returning
role = userobj.get('role', 'Administrator')
if element and role != 'Administrator':
for rule in _allowedbyrole.get(role, {}).get(operation, []):
if fnmatch(element, rule):
break
else:
return False
for rule in _deniedbyrole.get(role, {}).get(operation, []):
if fnmatch(element, rule):
return False
return userobj, manager, user, tenant, skipuserobj
return False
return None
def check_user_passphrase(name, passphrase, operation=None, element=None, tenant=False):
def check_user_passphrase(name, passphrase, element=None, tenant=False):
"""Check a a login name and passphrase for authenticity and authorization
The function combines authentication and authorization into one function.
@@ -208,90 +157,63 @@ def check_user_passphrase(name, passphrase, operation=None, element=None, tenant
# would normally make an event and wait
# but here there's no need for that
eventlet.sleep(0.5)
cfm = configmanager.ConfigManager(tenant, username=user)
ucfg = cfm.get_user(user)
if ucfg is None:
try:
for group in userutil.grouplist(user):
ucfg = cfm.get_usergroup(group)
if ucfg:
break
except KeyError:
pass
if ucfg is None:
eventlet.sleep(0.05)
return None
credobj = Credentials(user, passphrase)
try:
pammy = PAM.pam()
pammy.start(_pamservice, user, credobj.pam_conv)
pammy.authenticate()
pammy.acct_mgmt()
del pammy
return authorize(user, element, tenant, skipuserobj=False)
except NameError:
pass
except PAM.error:
if credobj.haspam:
return None
if (user, tenant) in _passcache:
if hashlib.sha256(passphrase).digest() == _passcache[(user, tenant)]:
return authorize(user, element, tenant, operation=operation)
if passphrase == _passcache[(user, tenant)]:
return authorize(user, element, tenant)
else:
# In case of someone trying to guess,
# while someone is legitimately logged in
# invalidate cache and force the slower check
del _passcache[(user, tenant)]
if 'cryptpass' in ucfg:
_passchecking[(user, tenant)] = True
# TODO(jbjohnso): WORKERPOOL
# PBKDF2 is, by design, cpu intensive
# throw it at the worker pool when implemented
# maybe a distinct worker pool, wondering about starving out non-auth stuff
salt, crypt = ucfg['cryptpass']
# execute inside tpool to get greenthreads to give it a special thread
# world
# TODO(jbjohnso): util function to generically offload a call
# such a beast could be passed into pyghmi as a way for pyghmi to
# magically get offload of the crypto functions without having
# to explicitly get into the eventlet tpool game
global authworkers
global authcleaner
if authworkers is None:
authworkers = multiprocessing.Pool(processes=1)
else:
authcleaner.cancel()
authcleaner = eventlet.spawn_after(30, _clean_authworkers)
crypted = eventlet.tpool.execute(_do_pbkdf, passphrase, salt)
del _passchecking[(user, tenant)]
eventlet.sleep(
0.05) # either way, we want to stall so that client can't
return None
cfm = configmanager.ConfigManager(tenant, username=user)
ucfg = cfm.get_user(user)
if ucfg is None or 'cryptpass' not in ucfg:
eventlet.sleep(0.05) # stall even on test for existence of a username
return None
_passchecking[(user, tenant)] = True
# TODO(jbjohnso): WORKERPOOL
# PBKDF2 is, by design, cpu intensive
# throw it at the worker pool when implemented
# maybe a distinct worker pool, wondering about starving out non-auth stuff
salt, crypt = ucfg['cryptpass']
# execute inside tpool to get greenthreads to give it a special thread
# world
#TODO(jbjohnso): util function to generically offload a call
#such a beast could be passed into pyghmi as a way for pyghmi to
#magically get offload of the crypto functions without having
#to explicitly get into the eventlet tpool game
global authworkers
global authcleaner
if authworkers is None:
authworkers = multiprocessing.Pool(processes=1)
else:
authcleaner.cancel()
authcleaner = eventlet.spawn_after(30, _clean_authworkers)
crypted = eventlet.tpool.execute(_do_pbkdf, passphrase, salt)
del _passchecking[(user, tenant)]
eventlet.sleep(0.05) # either way, we want to stall so that client can't
# determine failure because there is a delay, valid response will
# delay as well
if crypt == crypted:
_passcache[(user, tenant)] = hashlib.sha256(passphrase).digest()
return authorize(user, element, tenant, operation)
if pam:
pwe = None
try:
pwe = pwd.getpwnam(user)
except KeyError:
#pam won't work if the user doesn't exist, don't go further
eventlet.sleep(0.05) # stall even on test for existence of a username
return None
if os.getuid() != 0:
# confluent is running with reduced privilege, however, pam_unix refuses
# to let a non-0 user check anothers password.
# We will fork and the child will assume elevated privilege to
# get unix_chkpwd helper to enable checking /etc/shadow
pid = os.fork()
if not pid:
usergood = False
try:
# we change to the uid we are trying to authenticate as, because
# pam_unix uses unix_chkpwd which reque
os.setuid(pwe.pw_uid)
usergood = pam.authenticate(user, passphrase, service=_pamservice)
finally:
os._exit(0 if usergood else 1)
usergood = os.waitpid(pid, 0)[1] == 0
else:
# We are running as root, we don't need to fork in order to authenticate the
# user
usergood = pam.authenticate(user, passphrase, service=_pamservice)
if usergood:
_passcache[(user, tenant)] = hashlib.sha256(passphrase).digest()
return authorize(user, element, tenant, operation, skipuserobj=False)
eventlet.sleep(0.05) # stall even on test for existence of a username
if crypt == crypted:
_passcache[(user, tenant)] = passphrase
return authorize(user, element, tenant)
return None
def _apply_pbkdf(passphrase, salt):
return KDF.PBKDF2(passphrase, salt, 32, 10000,
lambda p, s: hmac.new(p, s, hashlib.sha256).digest())
@@ -309,4 +231,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])
+36 -152
View File
@@ -18,16 +18,13 @@ import base64
import confluent.collective.invites as invites
import confluent.config.configmanager as cfm
import confluent.exceptions as exc
import confluent.log as log
import confluent.tlvdata as tlvdata
import confluent.util as util
import eventlet
import eventlet.green.socket as socket
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:
@@ -36,45 +33,39 @@ except ImportError:
crypto = None
currentleader = None
cfginitlock = None
follower = None
retrythread = None
class ContextBool(object):
def __init__(self):
self.active = False
self.mylock = threading.RLock()
def __enter__(self):
self.active = True
self.mylock.__enter__()
def __exit__(self, exc_type, exc_val, exc_tb):
self.active = False
self.mylock.__exit__(exc_type, exc_val, exc_tb)
connecting = ContextBool()
leader_init = ContextBool()
def connect_to_leader(cert=None, name=None, leader=None):
global currentleader
global cfginitlock
global follower
if cfginitlock is None:
cfginitlock = threading.RLock()
if leader is None:
leader = currentleader
log.log({'info': 'Attempting connection to leader {0}'.format(leader),
'subsystem': 'collective'})
try:
remote = connect_to_collective(cert, leader)
except socket.error as e:
log.log({'error': 'Collective connection attempt to {0} failed: {1}'
''.format(leader, str(e)),
'subsystem': 'collective'})
except socket.error:
return False
with connecting:
with cfm._initlock:
banner = tlvdata.recv(remote) # the banner
vers = banner.split()[2]
if vers != b'v2':
raise Exception('This instance only supports protocol 2, synchronize versions between collective members')
with cfginitlock:
tlvdata.recv(remote) # the banner
tlvdata.recv(remote) # authpassed... 0..
if name is None:
name = get_myname()
@@ -86,16 +77,10 @@ def connect_to_leader(cert=None, name=None, leader=None):
return False
if 'error' in keydata:
if 'backoff' in keydata:
log.log({
'info': 'Collective initialization in progress on '
'{0}'.format(leader),
'subsystem': 'collective'})
return False
eventlet.spawn_after(random.random(), connect_to_leader,
cert, name, leader)
return True
if 'leader' in keydata:
log.log(
{'info': 'Prospective leader {0} has redirected this '
'member to {1}'.format(leader, keydata['leader']),
'subsystem': 'collective'})
ldrc = cfm.get_collective_member_by_address(
keydata['leader'])
if ldrc and ldrc['name'] == name:
@@ -103,27 +88,18 @@ def connect_to_leader(cert=None, name=None, leader=None):
return connect_to_leader(name=name,
leader=keydata['leader'])
if 'txcount' in keydata:
log.log({'info':
'Prospective leader {0} has inferior '
'transaction count, becoming leader'
''.format(leader), 'subsystem': 'collective',
'subsystem': 'collective'})
return become_leader(remote)
print(keydata['error'])
return False
if follower is not None:
follower.kill()
cfm.stop_following()
follower = None
if follower:
follower.kill()
cfm.stop_following()
follower = None
log.log({'info': 'Following leader {0}'.format(leader),
'subsystem': 'collective'})
colldata = tlvdata.recv(remote)
globaldata = tlvdata.recv(remote)
dbi = tlvdata.recv(remote)
dbsize = dbi['dbsize']
dbjson = b''
dbjson = ''
while (len(dbjson) < dbsize):
ndata = remote.recv(dbsize - len(dbjson))
if not ndata:
@@ -152,28 +128,17 @@ def connect_to_leader(cert=None, name=None, leader=None):
raise
currentleader = leader
#spawn this as a thread...
follower = eventlet.spawn(follow_leader, remote, leader)
follower = eventlet.spawn(follow_leader, remote)
return True
def follow_leader(remote, leader):
def follow_leader(remote):
global currentleader
cleanexit = False
try:
cfm.follow_channel(remote)
except greenlet.GreenletExit:
cleanexit = True
finally:
if cleanexit:
log.log({'info': 'Previous following cleanly closed',
'subsystem': 'collective'})
return
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
eventlet.spawn_n(start_collective)
cfm.follow_channel(remote)
# The leader has folded, time to startup again...
cfm.stop_following()
currentleader = None
eventlet.spawn_n(start_collective)
def connect_to_collective(cert, member):
@@ -212,7 +177,8 @@ def handle_connection(connection, cert, request, local=False):
else:
if not local:
return
if operation in ('show', 'delete'):
if 'show' == operation:
if not list(cfm.list_collective()):
tlvdata.send(connection,
{'collective': {'error': 'Collective mode not '
@@ -249,23 +215,7 @@ def handle_connection(connection, cert, request, local=False):
collinfo['quorum'] = True
except exc.DegradedCollective:
collinfo['quorum'] = False
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()
tlvdata.send(connection, {'collective': collinfo})
return
if 'invite' == operation:
try:
@@ -286,8 +236,7 @@ def handle_connection(connection, cert, request, local=False):
invitation = request['invitation']
try:
invitation = base64.b64decode(invitation)
name, invitation = invitation.split(b'@', 1)
name = util.stringify(name)
name, invitation = invitation.split('@', 1)
except Exception:
tlvdata.send(
connection,
@@ -342,8 +291,6 @@ def handle_connection(connection, cert, request, local=False):
f = open('/etc/confluent/cfg/myname', 'w')
f.write(name)
f.close()
log.log({'info': 'Connecting to collective due to join',
'subsystem': 'collective'})
eventlet.spawn_n(connect_to_leader, rsp['collective'][
'fingerprint'], name)
if 'enroll' == operation:
@@ -373,11 +320,6 @@ def handle_connection(connection, cert, request, local=False):
if 'assimilate' == operation:
drone = request['name']
droneinfo = cfm.get_collective_member(drone)
if not droneinfo:
tlvdata.send(connection,
{'error': 'Unrecognized leader, '
'redo invitation process'})
return
if not util.cert_matches(droneinfo['fingerprint'], cert):
tlvdata.send(connection,
{'error': 'Invalid certificate, '
@@ -387,22 +329,8 @@ def handle_connection(connection, cert, request, local=False):
tlvdata.send(connection,
{'error': 'Refusing to be assimilated by inferior'
'transaction count',
'txcount': cfm._txcount,})
'txcount': cfm._txcount})
return
if connecting.active:
# don't try to connect while actively already trying to connect
tlvdata.send(connection, {'status': 0})
connection.close()
return
if (currentleader == connection.getpeername()[0] and
follower and not follower.dead):
# if we are happily following this leader already, don't stir
# the pot
tlvdata.send(connection, {'status': 0})
connection.close()
return
log.log({'info': 'Connecting in response to assimilation',
'subsystem': 'collective'})
eventlet.spawn_n(connect_to_leader, None, None,
leader=connection.getpeername()[0])
tlvdata.send(connection, {'status': 0})
@@ -431,11 +359,6 @@ def handle_connection(connection, cert, request, local=False):
connection.close()
return
myself = connection.getsockname()[0]
if connecting.active:
tlvdata.send(connection, {'error': 'Connecting right now',
'backoff': True})
connection.close()
return
if myself != get_leader(connection):
tlvdata.send(
connection,
@@ -443,14 +366,17 @@ def handle_connection(connection, cert, request, local=False):
'in another castle', 'leader': currentleader})
connection.close()
return
if connecting.active:
tlvdata.send(connection, {'error': 'Connecting right now',
'backoff': True})
connection.close()
return
if request['txcount'] > cfm._txcount:
retire_as_leader()
tlvdata.send(connection,
{'error': 'Client has higher tranasaction count, '
'should assimilate me, connecting..',
'txcount': cfm._txcount})
log.log({'info': 'Connecting to leader due to superior '
'transaction count', 'subsystem': collective})
eventlet.spawn_n(connect_to_leader, None, None,
connection.getpeername()[0])
connection.close()
@@ -504,36 +430,11 @@ def try_assimilate(drone):
tlvdata.recv(remote) # the banner
tlvdata.recv(remote) # authpassed... 0..
answer = tlvdata.recv(remote)
if not answer:
log.log(
{'error':
'No answer from {0} while trying to assimilate'.format(
drone),
'subsystem': 'collective'})
return
if 'txcount' in answer:
log.log({'info': 'Deferring to {0} due to transaction count'.format(
drone), 'subsystem': 'collective'})
if answer and 'error' in answer:
connect_to_leader(None, None, leader=remote.getpeername()[0])
return
if 'error' in answer:
log.log({
'error': 'Error encountered while attempting to '
'assimilate {0}: {1}'.format(drone, answer['error']),
'subsystem': 'collective'})
return
log.log({'info': 'Assimilated {0} into collective'.format(drone),
'subsystem': 'collective'})
def get_leader(connection):
if currentleader is None or connection.getpeername()[0] == currentleader:
if currentleader is None:
msg = 'Becoming leader as no leader known'
else:
msg = 'Becoming leader because {0} attempted to connect and it ' \
'is current leader'.format(currentleader)
log.log({'info': msg, 'subsystem': 'collective'})
become_leader(connection)
return currentleader
@@ -545,34 +446,27 @@ def retire_as_leader():
def become_leader(connection):
global currentleader
global follower
global retrythread
log.log({'info': 'Becoming leader of collective',
'subsystem': 'collective'})
if follower:
follower.kill()
cfm.stop_following()
follower = None
if retrythread:
retrythread.cancel()
retrythread = None
currentleader = connection.getsockname()[0]
skipaddr = connection.getpeername()[0]
myname = get_myname()
skipem = set(cfm.cfgstreams)
skipem.add(currentleader)
skipem.add(skipaddr)
for member in cfm.list_collective():
dronecandidate = cfm.get_collective_member(member)['address']
if dronecandidate in skipem or member == myname:
if dronecandidate in (currentleader, skipaddr) or member == myname:
continue
eventlet.spawn_n(try_assimilate, dronecandidate)
def startup():
global cfginitlock
members = list(cfm.list_collective())
if len(members) < 2:
# Not in collective mode, return
return
if cfginitlock is None:
cfginitlock = threading.RLock()
eventlet.spawn_n(start_collective)
def start_collective():
@@ -580,15 +474,7 @@ def start_collective():
global retrythread
if follower:
follower.kill()
cfm.stop_following()
follower = None
try:
if cfm.cfgstreams:
cfm.check_quorum()
# Do not start if we have quorum and are leader
return
except exc.DegradedCollective:
pass
if leader_init.active: # do not start trying to connect if we are
# xmitting data to a follower
return
@@ -599,8 +485,6 @@ def start_collective():
if cfm.cfgleader is None:
cfm.stop_following(True)
ldrcandidate = cfm.get_collective_member(member)['address']
log.log({'info': 'Performing startup attempt to {0}'.format(
ldrcandidate), 'subsystem': 'collective'})
if connect_to_leader(name=myname, leader=ldrcandidate):
break
else:
@@ -1,7 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 IBM Corporation
# Copyright 2015-2019 Lenovo
# Copyright 2015 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -93,10 +93,9 @@ node = {
'description': ('List of static groups for which this node is '
'considered a member'),
},
'type': {
'description': ('Classification of node as server or switch'),
'validvalues': ('switch', 'server'),
},
#'type': {
# 'description': ('Classification of node as system, vm, etc')
#},
#'id': {
# 'description': ('Numeric identifier for node')
#},
@@ -157,14 +156,6 @@ node = {
'indicates candidate managers, either for '
'high availability or load balancing purposes.')
},
'discovery.passwordrules': {
'description': 'Any specified rules shall be configured on the BMC '
'upon discovery. "expiration=no,loginfailures=no,complexity=no,reuse=no" '
'would disable password expiration, login failures '
'triggering a lockout, password complexity requirements,'
'and any restrictions around reusing an old password.',
'validlistkeys': ('expiration', 'loginfailures', 'complexity', 'reuse'),
},
'discovery.policy': {
'description': 'Policy to use for auto-configuration of discovered '
'and identified nodes. Valid values are "manual", '
@@ -174,7 +165,6 @@ node = {
'so long as the node has no existing public key. '
'"open" allows discovery even if a known public key '
'is already stored',
'validlist': ('manual', 'permissive', 'pxe', 'open'),
},
'info.note': {
'description': 'A field used for administrators to make arbitrary '
@@ -183,9 +173,6 @@ node = {
'freeform text data without concern for issues in how '
'the server will process it.',
},
'location.height': {
'description': 'Height in RU of the system (defaults to query the systems)',
},
'location.room': {
'description': 'Room description for the node',
},
@@ -265,14 +252,11 @@ node = {
'console.logging': {
'description': ('Indicate logging level to apply to console. Valid '
'values are currently "full", "interactive", and '
'"none". Defaults to "full".'),
'validvalues': ('full', 'interactive', 'none'),
'"none". Defaults to "full".')
},
'console.method': {
'description': ('Indicate the method used to access the console of '
'the managed node. If not specified, then console '
'is disabled'),
'validvalues': ('ssh', 'ipmi', 'tsmsol'),
'the managed node.')
},
# 'virtualization.host': {
# 'description': ('Hypervisor where this node does/should reside'),
@@ -302,7 +286,6 @@ node = {
'hardwaremanagement.method': {
'description': 'The method used to perform operations such as power '
'control, get sensor data, get inventory, and so on. '
'ipmi is used if not specified.'
},
'enclosure.bay': {
'description': 'The bay in the enclosure, if any',
+1 -4
View File
@@ -16,10 +16,7 @@
# This defines config variable to store the global configuration for confluent
try:
import ConfigParser
except ModuleNotFoundError:
import configparser as ConfigParser
import ConfigParser
import os
_config = None
File diff suppressed because it is too large Load Diff
+39 -98
View File
@@ -1,7 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 IBM Corporation
# Copyright 2017-2018 Lenovo
# Copyright 2017 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -94,7 +94,7 @@ def _utf8_normalize(data, shiftin, decoder):
def pytechars2line(chars, maxlen=None):
line = b'\x1b[m' # start at default params
line = '\x1b[m' # start at default params
lb = False # last bold
li = False # last italic
lu = False # last underline
@@ -106,7 +106,7 @@ def pytechars2line(chars, maxlen=None):
len = 1
for charidx in range(maxlen):
char = chars[charidx]
csi = bytearray([])
csi = []
if char.fg != lfg:
csi.append(30 + pytecolors2ansi[char.fg])
lfg = char.fg
@@ -129,13 +129,10 @@ 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).encode('utf-8') for x in csi]) + b'm'
if not hasdata and char.data.rstrip():
line += b'\x1b[' + b';'.join(['{0}'.format(x) for x in csi]) + b'm'
if not hasdata and char.data.encode('utf-8').rstrip():
hasdata = True
chardata = char.data
if not isinstance(chardata, bytes):
chardata = chardata.encode('utf-8')
line += chardata
line += char.data.encode('utf-8')
if maxlen and len >= maxlen:
break
len += 1
@@ -148,10 +145,8 @@ class ConsoleHandler(object):
_genwatchattribs = frozenset(('console.method', 'console.logging',
'collective.manager'))
def __init__(self, node, configmanager, width=80, height=24):
def __init__(self, node, configmanager):
self.clearpending = False
self.clearerror = False
self.initsize = (width, height)
self._dologging = True
self._is_local = True
self._isondemand = False
@@ -188,7 +183,7 @@ class ConsoleHandler(object):
if termstate & 1:
self.appmodedetected = True
if termstate & 2:
self.shiftin = b'0'
self.shiftin = '0'
self.users = {}
self._attribwatcher = None
self._console = None
@@ -213,8 +208,6 @@ class ConsoleHandler(object):
return retrytime + (retrytime * random.random())
def feedbuffer(self, data):
if not isinstance(data, bytes):
data = data.encode('utf-8')
try:
self.termstream.feed(data)
except StopIteration: # corrupt parser state, start over
@@ -314,11 +307,8 @@ class ConsoleHandler(object):
def clearbuffer(self):
self.feedbuffer(
'\x1bc[No data has been received from the remote console since ' \
'connecting. This could\r\nbe due to having the console.logging ' \
'attribute set to none or interactive,\r\nserial console not ' \
'being enabled or incorrectly configured in the OS or\r\nfirmware, ' \
'or the console simply not having any output since last connection]')
'\x1bc[no replay buffer due to console.logging attribute set to '
'none or interactive,\r\nconnection loss, or service restart]')
self.clearpending = True
def _detach(self):
@@ -362,40 +352,23 @@ 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 Exception as e:
strerror = str(e)
if _tracelog:
_tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
event=log.Events.stacktrace)
else:
print(traceback.format_exc())
except:
_tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
event=log.Events.stacktrace)
if not isinstance(self._console, conapi.Console):
self.clearbuffer()
self.connectstate = 'unconnected'
self.error = 'misconfigured'
self._send_rcpts({'connectstate': self.connectstate,
'error': self.error})
self.feedbuffer(
'\x1bc\x1b[2J\x1b[1;1H[{0}]'.format(strerror))
self._send_rcpts(
'\x1bc\x1b[2J\x1b[1;1H[{0}]'.format(strerror))
self.clearerror = True
return
if self.clearerror:
self.clearerror = False
self.clearbuffer()
self._send_rcpts(b'\x1bc\x1b[2J\x1b[1;1H')
self.send_break = self._console.send_break
self.resize = self._console.resize
if self._attribwatcher:
self.cfgmgr.remove_watcher(self._attribwatcher)
self._attribwatcher = None
@@ -407,7 +380,6 @@ class ConsoleHandler(object):
self._attribwatcher = self.cfgmgr.watch_attributes(
(self.node,), attribstowatch, self._attribschanged)
try:
self.resize(width=self.initsize[0], height=self.initsize[1])
self._console.connect(self.get_console_output)
except exc.TargetEndpointBadCredentials:
self.clearbuffer()
@@ -453,7 +425,6 @@ class ConsoleHandler(object):
def _got_disconnected(self):
if self.connectstate != 'unconnected':
self._console.close()
self.connectstate = 'unconnected'
self.log(
logdata='console disconnected', ltype=log.DataTypes.event,
@@ -531,19 +502,17 @@ class ConsoleHandler(object):
if data == conapi.ConsoleEvent.Disconnect:
self._got_disconnected()
return
elif data in (b'', u''):
elif data == '':
# 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
if '\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
if '\x1b[?1h' in data: # remember the session wants the client to use
# 'application mode' Thus far only observed on esxi
self.appmodedetected = True
if b'\x1b)0' in data:
if '\x1b)0' in data:
# console indicates it wants access to special drawing characters
self.shiftin = b'0'
self.shiftin = '0'
eventdata = 0
if self.appmodedetected:
eventdata |= 1
@@ -552,11 +521,10 @@ class ConsoleHandler(object):
# TODO: analyze buffer for registered events, examples:
# panics
# certificate signing request
if self.clearpending or self.clearerror:
if self.clearpending:
self.clearpending = False
self.clearerror = False
self.feedbuffer(b'\x1bc\x1b[2J\x1b[1;1H')
self._send_rcpts(b'\x1bc\x1b[2J\x1b[1;1H')
self.feedbuffer(b'\x1bc')
self._send_rcpts(b'\x1bc')
self._send_rcpts(_utf8_normalize(data, self.shiftin, self.utf8decoder))
self.log(data, eventdata=eventdata)
self.lasttime = util.monotonic_time()
@@ -596,30 +564,25 @@ class ConsoleHandler(object):
if pendingbl:
retdata += pendingbl
pendingbl = b''
retdata += nline + b'\r\n'
retdata += nline + '\r\n'
else:
pendingbl += nline + b'\r\n'
pendingbl += nline + '\r\n'
if len(retdata) > 6:
retdata = retdata[:-2] # remove the last \r\n
cursordata = '\x1b[{0};{1}H'.format(self.buffer.cursor.y + 1,
self.buffer.cursor.x + 1)
if not isinstance(cursordata, bytes):
cursordata = cursordata.encode('utf-8')
retdata += cursordata
retdata += b'\x1b[{0};{1}H'.format(self.buffer.cursor.y + 1,
self.buffer.cursor.x + 1)
if self.shiftin is not None: # detected that terminal requested a
# shiftin character set, relay that to the terminal that cannected
retdata += b'\x1b)' + self.shiftin
retdata += '\x1b)' + self.shiftin
if self.appmodedetected:
retdata += b'\x1b[?1h'
retdata += '\x1b[?1h'
else:
retdata += b'\x1b[?1l'
retdata += '\x1b[?1l'
return retdata, connstate
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,
@@ -634,14 +597,11 @@ def disconnect_node(node, configmanager):
del _handled_consoles[consk]
def _nodechange(added, deleting, renamed, configmanager):
for node in deleting:
disconnect_node(node, configmanager)
for node in renamed:
disconnect_node(node, configmanager)
connect_node(renamed[node], configmanager)
def _nodechange(added, deleting, configmanager):
for node in added:
connect_node(node, configmanager)
for node in deleting:
disconnect_node(node, configmanager)
def _start_tenant_sessions(cfm):
@@ -665,20 +625,17 @@ def start_console_sessions():
configmodule.hook_new_configmanagers(_start_tenant_sessions)
def connect_node(node, configmanager, username=None, direct=True, width=80,
height=24):
def connect_node(node, configmanager, username=None):
attrval = configmanager.get_node_attributes(node, 'collective.manager')
myc = attrval.get(node, {}).get('collective.manager', {}).get(
'value', None)
myname = collective.get_myname()
if myc and myc != collective.get_myname() and direct:
if myc and myc != collective.get_myname():
minfo = configmodule.get_collective_member(myc)
return ProxyConsole(node, minfo, myname, configmanager, username,
width, height)
return ProxyConsole(node, minfo, myname, configmanager, username)
consk = (node, configmanager.tenant)
if consk not in _handled_consoles:
_handled_consoles[consk] = ConsoleHandler(node, configmanager, width,
height)
_handled_consoles[consk] = ConsoleHandler(node, configmanager)
return _handled_consoles[consk]
# A stub console handler that just passes through to a remote confluent
@@ -687,10 +644,8 @@ def connect_node(node, configmanager, username=None, direct=True, width=80,
class ProxyConsole(object):
_genwatchattribs = frozenset(('collective.manager',))
def __init__(self, node, managerinfo, myname, configmanager, user, width,
height):
def __init__(self, node, managerinfo, myname, configmanager, user):
self.skipreplay = True
self.initsize = (width, height)
self.managerinfo = managerinfo
self.myname = myname
self.cfm = configmanager
@@ -701,7 +656,6 @@ class ProxyConsole(object):
self._attribwatcher = configmanager.watch_attributes(
(self.node,), self._genwatchattribs, self._attribschanged)
def _attribschanged(self, nodeattribs, configmanager, **kwargs):
if self.clisession:
self.clisession.detach()
@@ -743,8 +697,6 @@ class ProxyConsole(object):
'tenant': self.cfm.tenant,
'node': self.node,
'skipreplay': self.skipreplay,
'width': self.initsize[0],
'height': self.initsize[1],
#TODO(jjohnson2): declare myself as a proxy,
#facilitate redirect rather than relay on manager change
},
@@ -784,10 +736,6 @@ class ProxyConsole(object):
def reopen(self):
tlvdata.send(self.remote, {'operation': 'reopen'})
def resize(self, width, height):
tlvdata.send(self.remote, {'operation': 'resize', 'width': width,
'height': height})
# this represents some api view of a console handler. This handles things like
# holding the caller specific queue data, for example, when http api should be
@@ -810,9 +758,10 @@ class ConsoleSession(object):
'get_next_output' non-functional
:param skipreplay: If true, will skip the attempt to redraw the screen
"""
connector = connect_node
def __init__(self, node, configmanager, username, datacallback=None,
skipreplay=False, direct=True, width=80, height=24):
skipreplay=False):
self.registered = False
self.tenant = configmanager.tenant
if not configmanager.is_node(node):
@@ -820,10 +769,6 @@ class ConsoleSession(object):
self.username = username
self.node = node
self.configmanager = configmanager
self.direct = direct # true if client is directly connected versus
# relay
self.width = width
self.height = height
self.connect_session()
self.registered = True
self._evt = None
@@ -852,16 +797,12 @@ class ConsoleSession(object):
between console and shell.
"""
self.conshdl = connect_node(self.node, self.configmanager,
self.username, self.direct, self.width,
self.height)
self.username)
def send_break(self):
"""Send break to remote system
"""
self.conshdl.send_break()
def resize(self, width, height):
self.conshdl.resize(width, height)
def get_buffer_age(self):
"""Get the age in seconds of the buffered data
+35 -215
View File
@@ -1,7 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 IBM Corporation
# Copyright 2015-2018 Lenovo
# Copyright 2015-2017 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -35,7 +35,6 @@
import confluent
import confluent.alerts as alerts
import confluent.log as log
import confluent.tlvdata as tlvdata
import confluent.config.attributes as attrscheme
import confluent.config.configmanager as cfm
@@ -60,14 +59,17 @@ import eventlet.greenpool as greenpool
import eventlet.green.ssl as ssl
import eventlet.queue as queue
import itertools
import msgpack
import os
try:
import cPickle as pickle
except ImportError:
import pickle
import socket
import struct
import sys
pluginmap = {}
dispatch_plugins = (b'ipmi', u'ipmi', b'redfish', u'redfish', b'tsmsol', u'tsmsol')
dispatch_plugins = (b'ipmi', u'ipmi')
def seek_element(currplace, currkey):
@@ -83,10 +85,7 @@ def seek_element(currplace, currkey):
def nested_lookup(nestdict, key):
try:
currloc = nestdict
for currk in key:
currloc = seek_element(currloc, currk)
return currloc
return reduce(seek_element, key, nestdict)
except TypeError:
raise exc.NotFoundException("Invalid element requested")
@@ -106,8 +105,6 @@ def load_plugins():
for plugin in os.listdir(plugindir):
if plugin.startswith('.'):
continue
if '__pycache__' in plugin:
continue
(plugin, plugtype) = os.path.splitext(plugin)
if plugtype == '.sh':
pluginmap[plugin] = shellmodule.Plugin(
@@ -126,7 +123,7 @@ def load_plugins():
rootcollections = ['discovery/', 'events/', 'networking/',
'noderange/', 'nodes/', 'nodegroups/', 'usergroups/' , 'users/', 'version']
'noderange/', 'nodes/', 'nodegroups/', 'users/', 'version']
class PluginRoute(object):
@@ -146,7 +143,6 @@ def _init_core():
# be enumerated in any collection
noderesources = {
'attributes': {
'rename': PluginRoute({'handler': 'attributes'}),
'all': PluginRoute({'handler': 'attributes'}),
'current': PluginRoute({'handler': 'attributes'}),
'expression': PluginRoute({'handler': 'attributes'}),
@@ -165,22 +161,10 @@ def _init_core():
'default': 'ipmi',
}),
},
'clear': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'users': PluginCollection({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'licenses': PluginCollection({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'save_licenses': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'net_interfaces': PluginCollection({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
@@ -211,52 +195,12 @@ def _init_core():
'default': 'ipmi',
}),
},
'extended': {
'all': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'extra': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'advanced': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
},
},
'storage': {
'all': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'arrays': PluginCollection({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'disks': PluginCollection({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'volumes': PluginCollection({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
})
},
'system': {
'all': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'advanced': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'clear': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
})
},
},
'_console': {
@@ -289,10 +233,6 @@ def _init_core():
'default': 'ipmi',
}),
},
'description': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'events': {
'hardware': {
'log': PluginRoute({
@@ -394,18 +334,10 @@ def _init_core():
},
},
'support': {
'servicedata': PluginCollection({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
},
}
nodegroupresources = {
'attributes': {
'check': PluginRoute({'handler': 'attributes'}),
'rename': PluginRoute({'handler': 'attributes'}),
'all': PluginRoute({'handler': 'attributes'}),
'current': PluginRoute({'handler': 'attributes'}),
},
@@ -416,46 +348,22 @@ def create_user(inputdata, configmanager):
try:
username = inputdata['name']
del inputdata['name']
role = inputdata['role']
del inputdata['role']
except (KeyError, ValueError):
raise exc.InvalidArgumentException('Missing user name or role')
configmanager.create_user(username, role, attributemap=inputdata)
raise exc.InvalidArgumentException()
configmanager.create_user(username, attributemap=inputdata)
def create_usergroup(inputdata, configmanager):
try:
groupname = inputdata['name']
role = inputdata['role']
del inputdata['name']
del inputdata['role']
except (KeyError, ValueError):
raise exc.InvalidArgumentException("Missing user name or role")
configmanager.create_usergroup(groupname, role)
def update_usergroup(groupname, attribmap, configmanager):
try:
configmanager.set_usergroup(groupname, attribmap)
except ValueError as e:
raise exc.InvalidArgumentException(str(e))
def update_user(name, attribmap, configmanager):
try:
configmanager.set_user(name, attribmap)
except ValueError as e:
raise exc.InvalidArgumentException(str(e))
except ValueError:
raise exc.InvalidArgumentException()
def show_usergroup(groupname, configmanager):
groupinfo = configmanager.get_usergroup(groupname)
for attr in groupinfo:
yield msg.Attributes(kv={attr: groupinfo[attr]})
def show_user(name, configmanager):
userobj = configmanager.get_user(name)
rv = {}
for attr in attrscheme.user:
for attr in attrscheme.user.iterkeys():
rv[attr] = None
if attr == 'password':
if 'cryptpass' in userobj:
@@ -468,10 +376,6 @@ def show_user(name, configmanager):
rv[attr] = userobj[attr]
yield msg.Attributes(kv={attr: rv[attr]},
desc=attrscheme.user[attr]['description'])
if 'role' in userobj:
yield msg.Attributes(kv={'role': userobj['role']})
def stripnode(iterablersp, node):
@@ -504,10 +408,6 @@ def delete_user(user, configmanager):
configmanager.del_user(user)
yield msg.DeletedResource(user)
def delete_usergroup(usergroup, configmanager):
configmanager.del_usergroup(usergroup)
yield msg.DeletedResource(usergroup)
def delete_nodegroup_collection(collectionpath, configmanager):
if len(collectionpath) == 2: # just the nodegroup
@@ -667,15 +567,6 @@ class BadPlugin(object):
self.node, self.plugin + ' is not a supported plugin')
class BadCollective(object):
def __init__(self, node):
self.node = node
def error(self, *args, **kwargs):
yield msg.ConfluentNodeError(
self.node, 'collective mode is active, but collective.manager '
'is not set for this node')
def abbreviate_noderange(configmanager, inputdata, operation):
if operation != 'create':
raise exc.InvalidArgumentException('Must be a create with nodes in list')
@@ -692,22 +583,13 @@ def handle_dispatch(connection, cert, dispatch, peername):
cfm.get_collective_member(peername)['fingerprint'], cert):
connection.close()
return
if dispatch[0:2] != b'\x01\x03': # magic value to indicate msgpack
# We only support msgpack now
# The magic should preclude any pickle, as the first byte can never be
# under 0x20 or so.
connection.close()
return
dispatch = msgpack.unpackb(dispatch[2:], raw=False)
dispatch = pickle.loads(dispatch)
configmanager = cfm.ConfigManager(dispatch['tenant'])
nodes = dispatch['nodes']
inputdata = dispatch['inputdata']
operation = dispatch['operation']
pathcomponents = dispatch['path']
routespec = nested_lookup(noderesources, pathcomponents)
inputdata = msg.get_input_message(
pathcomponents, operation, inputdata, nodes, dispatch['isnoderange'],
configmanager)
plugroute = routespec.routeinfo
plugpath = None
nodesbyhandler = {}
@@ -720,7 +602,7 @@ def handle_dispatch(connection, cert, dispatch, peername):
plugpath = nodeattr[node][attrname]['value']
elif 'default' in plugroute:
plugpath = plugroute['default']
if plugpath:
if plugpath is not None:
try:
hfunc = getattr(pluginmap[plugpath], operation)
except KeyError:
@@ -744,19 +626,7 @@ def handle_dispatch(connection, cert, dispatch, peername):
def _forward_rsp(connection, res):
try:
r = res.serialize()
except AttributeError:
if isinstance(res, Exception):
r = msgpack.packb(['Exception', str(res)], use_bin_type=False)
else:
r = msgpack.packb(
['Exception', 'Unable to serialize response ' + repr(res)],
use_bin_type=False)
except Exception as e:
r = msgpack.packb(
['Exception', 'Unable to serialize response ' + repr(res) + ' due to ' + str(e)],
use_bin_type=False)
r = pickle.dumps(res)
rlen = len(r)
if not rlen:
return
@@ -766,8 +636,6 @@ def _forward_rsp(connection, res):
def handle_node_request(configmanager, inputdata, operation,
pathcomponents, autostrip=True):
if log.logfull:
raise exc.TargetResourceUnavailable('Filesystem full, free up space and restart confluent service')
iscollection = False
routespec = None
if pathcomponents[0] == 'noderange':
@@ -847,7 +715,7 @@ def handle_node_request(configmanager, inputdata, operation,
del pathcomponents[0:2]
passvalues = queue.Queue()
plugroute = routespec.routeinfo
msginputdata = msg.get_input_message(
inputdata = msg.get_input_message(
pathcomponents, operation, inputdata, nodes, isnoderange,
configmanager)
if 'handler' in plugroute: # fixed handler definition, easy enough
@@ -858,7 +726,7 @@ def handle_node_request(configmanager, inputdata, operation,
passvalue = hfunc(
nodes=nodes, element=pathcomponents,
configmanager=configmanager,
inputdata=msginputdata)
inputdata=inputdata)
if isnoderange:
return passvalue
elif isinstance(passvalue, console.Console):
@@ -891,8 +759,7 @@ def handle_node_request(configmanager, inputdata, operation,
continue
elif list(cfm.list_collective()):
badcollnodes.append(node)
continue
if plugpath:
if plugpath is not None:
try:
hfunc = getattr(pluginmap[plugpath], operation)
except KeyError:
@@ -902,8 +769,11 @@ def handle_node_request(configmanager, inputdata, operation,
nodesbyhandler[hfunc].append(node)
else:
nodesbyhandler[hfunc] = [node]
for bn in badcollnodes:
nodesbyhandler[BadCollective(bn).error] = [bn]
if badcollnodes:
raise exc.ConfluentException(
'collective management active, '
'collective.manager must be set for {0}'.format(
','.join(badcollnodes)))
workers = greenpool.GreenPool()
numworkers = 0
for hfunc in nodesbyhandler:
@@ -911,13 +781,13 @@ def handle_node_request(configmanager, inputdata, operation,
workers.spawn(addtoqueue, passvalues, hfunc, {'nodes': nodesbyhandler[hfunc],
'element': pathcomponents,
'configmanager': configmanager,
'inputdata': msginputdata})
'inputdata': inputdata})
for manager in nodesbymanager:
numworkers += 1
workers.spawn(addtoqueue, passvalues, dispatch_request, {
'nodes': nodesbymanager[manager], 'manager': manager,
'element': pathcomponents, 'configmanager': configmanager,
'inputdata': inputdata, 'operation': operation, 'isnoderange': isnoderange})
'inputdata': inputdata, 'operation': operation})
if isnoderange or not autostrip:
return iterate_queue(numworkers, passvalues)
else:
@@ -939,8 +809,6 @@ def iterate_queue(numworkers, passvalues, strip=False):
if nv == 'theend':
completions += 1
else:
if isinstance(nv, Exception):
raise nv
if strip and not isinstance(nv, console.Console):
nv.strip_node(strip)
yield nv
@@ -954,51 +822,33 @@ def addtoqueue(theq, fun, kwargs):
else:
for pv in result:
theq.put(pv)
except Exception as e:
theq.put(e)
finally:
theq.put('theend')
def dispatch_request(nodes, manager, element, configmanager, inputdata,
operation, isnoderange):
operation):
a = configmanager.get_collective_member(manager)
try:
remote = socket.create_connection((a['address'], 13001))
remote.settimeout(180)
remote.settimeout(90)
remote = ssl.wrap_socket(remote, cert_reqs=ssl.CERT_NONE,
keyfile='/etc/confluent/privkey.pem',
certfile='/etc/confluent/srvcert.pem')
except Exception:
for node in nodes:
if a:
yield msg.ConfluentResourceUnavailable(
node, 'Collective member {0} is unreachable'.format(
a['name']))
else:
yield msg.ConfluentResourceUnavailable(
node,
'"{0}" is not recognized as a collective member'.format(
manager))
yield msg.ConfluentResourceUnavailable(
node, 'Collective member {0} is unreachable'.format(a['name']))
return
if not util.cert_matches(a['fingerprint'], remote.getpeercert(
binary_form=True)):
raise Exception("Invalid certificate on peer")
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)
tlvdata.recv(remote)
myname = collective.get_myname()
dreq = b'\x01\x03' + msgpack.packb(
{'name': myname, 'nodes': list(nodes),
'path': element,'tenant': configmanager.tenant,
'operation': operation, 'inputdata': inputdata, 'isnoderange': isnoderange}, use_bin_type=False)
dreq = pickle.dumps({'name': myname, 'nodes': list(nodes),
'path': element,'tenant': configmanager.tenant,
'operation': operation, 'inputdata': inputdata})
tlvdata.send(remote, {'dispatch': {'name': myname, 'length': len(dreq)}})
remote.sendall(dreq)
while True:
@@ -1045,14 +895,9 @@ def dispatch_request(nodes, manager, element, configmanager, inputdata,
a['name']))
return
rsp += nrsp
try:
rsp = msg.msg_deserialize(rsp)
except Exception:
rsp = exc.deserialize_exc(rsp)
rsp = pickle.loads(rsp)
if isinstance(rsp, Exception):
raise rsp
if not rsp:
raise Exception('Error in cross-collective serialize/deserialze, see remote logs')
yield rsp
@@ -1096,31 +941,6 @@ def handle_path(path, operation, configmanager, inputdata=None, autostrip=True):
configmanager, inputdata, operation, pathcomponents)
elif pathcomponents[0] == 'version':
return (msg.Attributes(kv={'version': confluent.__version__}),)
elif pathcomponents[0] == 'usergroups':
# TODO: when non-administrator accounts exist,
# they must only be allowed to see their own user
try:
usergroup = pathcomponents[1]
except IndexError: # it's just users/
if operation == 'create':
inputdata = msg.get_input_message(
pathcomponents, operation, inputdata,
configmanager=configmanager)
create_usergroup(inputdata.attribs, configmanager)
return iterate_collections(configmanager.list_usergroups(),
forcecollection=False)
if usergroup not in configmanager.list_usergroups():
raise exc.NotFoundException("Invalid usergroup %s" % usergroup)
if operation == 'retrieve':
return show_usergroup(usergroup, configmanager)
elif operation == 'delete':
return delete_usergroup(usergroup, configmanager)
elif operation == 'update':
inputdata = msg.get_input_message(
pathcomponents, operation, inputdata,
configmanager=configmanager)
update_usergroup(usergroup, inputdata.attribs, configmanager)
return show_usergroup(usergroup, configmanager)
elif pathcomponents[0] == 'users':
# TODO: when non-administrator accounts exist,
# they must only be allowed to see their own user
+13 -97
View File
@@ -63,13 +63,10 @@
import base64
import confluent.config.configmanager as cfm
import confluent.collective.manager as collective
import confluent.discovery.protocols.pxe as pxe
import confluent.discovery.protocols.ssdp as ssdp
#import confluent.discovery.protocols.ssdp as ssdp
import confluent.discovery.protocols.slp as slp
import confluent.discovery.handlers.imm as imm
import confluent.discovery.handlers.cpstorage as cpstorage
import confluent.discovery.handlers.tsm as tsm
import confluent.discovery.handlers.pxe as pxeh
import confluent.discovery.handlers.smm as smm
import confluent.discovery.handlers.xcc as xcc
@@ -89,13 +86,6 @@ import eventlet.greenpool
import eventlet.semaphore
autosensors = set()
scanner = None
try:
unicode
except NameError:
unicode = str
class nesteddict(dict):
def __missing__(self, key):
@@ -107,35 +97,23 @@ nodehandlers = {
'service:management-hardware.Lenovo:lenovo-xclarity-controller': xcc,
'service:management-hardware.IBM:integrated-management-module2': imm,
'pxe-client': pxeh,
'onie-switch': None,
'cumulus-switch': None,
'service:io-device.Lenovo:management-module': None,
'service:thinkagile-storage': cpstorage,
'service:lenovo-tsm': tsm,
}
servicenames = {
'pxe-client': 'pxe-client',
'onie-switch': 'onie-switch',
'cumulus-switch': 'cumulus-switch',
'service:lenovo-smm': 'lenovo-smm',
'service:management-hardware.Lenovo:lenovo-xclarity-controller': 'lenovo-xcc',
'service:management-hardware.IBM:integrated-management-module2': 'lenovo-imm2',
'service:io-device.Lenovo:management-module': 'lenovo-switch',
'service:thinkagile-storage': 'thinkagile-storagebmc',
'service:lenovo-tsm': 'lenovo-tsm',
}
servicebyname = {
'pxe-client': 'pxe-client',
'onie-switch': 'onie-switch',
'cumulus-switch': 'cumulus-switch',
'lenovo-smm': 'service:lenovo-smm',
'lenovo-xcc': 'service:management-hardware.Lenovo:lenovo-xclarity-controller',
'lenovo-imm2': 'service:management-hardware.IBM:integrated-management-module2',
'lenovo-switch': 'service:io-device.Lenovo:management-module',
'thinkagile-storage': 'service:thinkagile-storagebmc',
'lenovo-tsm': 'service:lenovo-tsm',
}
discopool = eventlet.greenpool.GreenPool(500)
@@ -231,8 +209,6 @@ def send_discovery_datum(info):
if infotype in servicenames:
types.append(servicenames[infotype])
yield msg.KeyValueData({'types': types})
if 'otheraddresses' in info:
yield msg.KeyValueData({'otheripaddrs': list(info['otheraddresses'])})
def _info_matches(info, criteria):
@@ -382,7 +358,7 @@ def handle_autosense_config(operation, inputdata):
yield msg.KeyValueData({'enabled': autosense})
elif operation == 'update':
enabled = inputdata['enabled']
if type(enabled) in (unicode, bytes):
if type(enabled) in (unicode, str):
enabled = enabled.lower() in ('true', '1', 'y', 'yes', 'enable',
'enabled')
if autosense == enabled:
@@ -405,7 +381,6 @@ def handle_api_request(configmanager, inputdata, operation, pathcomponents):
raise exc.InvalidArgumentException()
rescan()
return (msg.KeyValueData({'rescan': 'started'}),)
elif operation in ('update', 'create'):
if 'node' not in inputdata:
raise exc.InvalidArgumentException('Missing node name in input')
@@ -416,15 +391,8 @@ def handle_api_request(configmanager, inputdata, operation, pathcomponents):
'Unable to {0} to {1}'.format(operation,
'/'.join(pathcomponents)))
handler = info['handler'].NodeHandler(info, configmanager)
try:
eval_node(configmanager, handler, info, inputdata['node'],
manual=True)
except Exception as e:
# or... incorrect passworod provided..
if 'Incorrect password' in str(e) or 'Unauthorized name' in str(e):
return [msg.ConfluentTargetInvalidCredentials(
inputdata['node'])]
raise
eval_node(configmanager, handler, info, inputdata['node'],
manual=True)
return [msg.AssignedResource(inputdata['node'])]
elif operation == 'delete':
mac = _get_mac_from_query(pathcomponents)
@@ -448,8 +416,6 @@ def handle_read_api_request(pathcomponents):
# TODO(jjohnson2): This should be more generalized...
# odd indexes into components are 'by-'*, even indexes
# starting at 2 are parameters to previous index
if pathcomponents == ['discovery', 'rescan']:
return (msg.KeyValueData({'scanning': bool(scanner)}),)
subcats, queryparms, indexof, coll = _parameterize_path(pathcomponents[1:])
if len(pathcomponents) == 1:
dirlist = [msg.ChildCollection(x + '/') for x in sorted(list(subcats))]
@@ -492,9 +458,6 @@ def _recheck_nodes_backend(nodeattribs, configmanager):
_map_unique_ids(nodeattribs)
# for the nodes whose attributes have changed, consider them as potential
# strangers
if nodeattribs:
macmap.vintage = 0 # expire current mac map data, in case
# the attributes changed impacted the result
for node in nodeattribs:
if node in known_nodes:
for somemac in known_nodes[node]:
@@ -608,11 +571,6 @@ def detected(info):
break
else: # no nodehandler, ignore for now
return
if (handler and not handler.NodeHandler.adequate(info) and
info.get('protocol', None)):
eventlet.spawn_after(10, info['protocol'].fix_info, info,
safe_detected)
return
try:
snum = info['attributes']['enclosure-serial-number'][0].strip()
if snum:
@@ -659,9 +617,6 @@ def detected(info):
uuid = info.get('uuid', None)
if uuid_is_valid(uuid):
known_uuids[uuid][info['hwaddr']] = info
info['otheraddresses'] = set([])
for i4addr in info.get('attributes', {}).get('ipv4-address', []):
info['otheraddresses'].add(i4addr)
if handler and handler.https_supported and not handler.https_cert:
if handler.cert_fail_reason == 'unreachable':
log.log(
@@ -716,8 +671,7 @@ def detected(info):
def b64tohex(b64str):
bd = base64.b64decode(b64str)
bd = bytearray(bd)
return ''.join(['{0:02x}'.format(x) for x in bd])
return ''.join(['{0:02x}'.format(ord(x)) for x in bd])
def get_enclosure_chain_head(nodename, cfg):
@@ -732,10 +686,6 @@ def get_enclosure_chain_head(nodename, cfg):
if ne in members:
raise exc.InvalidArgumentException(
'Circular chain that includes ' + nodename)
if not cfg.is_node(ne):
raise exc.InvalidArgumentException(
'{0} is chained to nonexistent node {1} '.format(
nodename, ne))
nodename = ne
members.append(nodename)
return nodename
@@ -901,7 +851,7 @@ def eval_node(cfg, handler, info, nodename, manual=False):
handler.probe() # unicast interrogation as possible to get more data
# switch concurrently
# do some preconfig, for example, to bring a SMM online if applicable
handler.preconfig(nodename)
handler.preconfig()
except Exception as e:
unknown_info[info['hwaddr']] = info
info['discostatus'] = 'unidentified'
@@ -955,9 +905,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].append(info)
pending_by_uuid[encuuid].add(info)
else:
pending_by_uuid[encuuid] = [info]
pending_by_uuid[encuuid] = set([info])
return
# We found the real smm, replace the list with the actual smm
# to continue
@@ -1076,14 +1026,6 @@ def discover_node(cfg, handler, info, nodename, manual):
traceback.print_exc()
return False
newnodeattribs = {}
if cfm.list_collective():
# We are in a collective, check collective.manager
cmc = cfg.get_node_attributes(nodename, 'collective.manager')
cm = cmc.get(nodename, {}).get('collective.manager', {}).get('value', None)
if not cm:
# Node is being discovered in collective, but no collective.manager, default
# to the collective member actually able to execute the discovery
newnodeattribs['collective.manager'] = collective.get_myname()
if 'uuid' in info:
newnodeattribs['id.uuid'] = info['uuid']
if 'serialnumber' in info:
@@ -1100,10 +1042,6 @@ 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(
@@ -1153,7 +1091,6 @@ needaddhandled = False
def _handle_nodelist_change(configmanager):
global needaddhandled
global nodeaddhandler
macmap.vintage = 0 # the current mac map is probably inaccurate
_recheck_nodes((), configmanager)
if needaddhandled:
needaddhandled = False
@@ -1162,18 +1099,10 @@ def _handle_nodelist_change(configmanager):
nodeaddhandler = None
def newnodes(added, deleting, renamed, configmanager):
def newnodes(added, deleting, configmanager):
global attribwatcher
global needaddhandled
global nodeaddhandler
alldeleting = set(deleting) | set(renamed)
for node in alldeleting:
if node not in known_nodes:
continue
for mac in known_nodes[node]:
if mac in known_info:
del known_info[mac]
del known_nodes[node]
_map_unique_ids()
configmanager.remove_watcher(attribwatcher)
allnodes = configmanager.list_nodes()
@@ -1213,25 +1142,12 @@ def _periodic_recheck(configmanager):
def rescan():
_map_unique_ids()
global scanner
if scanner:
return
else:
scanner = eventlet.spawn(blocking_scan)
eventlet.spawn_n(slp.active_scan, safe_detected)
def blocking_scan():
global scanner
slpscan = eventlet.spawn(slp.active_scan, safe_detected, slp)
ssdpscan = eventlet.spawn(ssdp.active_scan, safe_detected, ssdp)
slpscan.wait()
ssdpscan.wait()
scanner = None
def start_detection():
global attribwatcher
global rechecker
global rechecktime
_map_unique_ids()
cfg = cfm.ConfigManager(None)
allnodes = cfg.list_nodes()
@@ -1255,8 +1171,8 @@ def stop_autosense():
autosensors.discard(watcher)
def start_autosense():
autosensors.add(eventlet.spawn(slp.snoop, safe_detected, slp))
autosensors.add(eventlet.spawn(pxe.snoop, safe_detected, pxe))
autosensors.add(eventlet.spawn(slp.snoop, safe_detected))
autosensors.add(eventlet.spawn(pxe.snoop, safe_detected))
nodes_by_fprint = {}
@@ -1278,7 +1194,7 @@ def _map_unique_ids(nodes=None):
uuid_by_nodes = {}
fprint_by_nodes = {}
for uuid in nodes_by_uuid:
if not uuid_is_valid(uuid):
if not uuid_is_valid():
continue
node = nodes_by_uuid[uuid]
if node in bigmap:
@@ -26,27 +26,16 @@ ipmicommand.session.threading = eventlet.green.threading
ipmicommand.session.socket.getaddrinfo = eventlet.support.greendns.getaddrinfo
getaddrinfo = eventlet.support.greendns.getaddrinfo
DEFAULT_USER = 'USERID'
DEFAULT_PASS = 'PASSW0RD'
class NodeHandler(generic.NodeHandler):
DEFAULT_USER = 'USERID'
DEFAULT_PASS = 'PASSW0RD'
def _get_ipmicmd(self, user=None, password=None):
priv = None
if user is None or password is None:
if self.trieddefault:
raise pygexc.IpmiException()
priv = 4 # manually indicate priv to avoid double-attempt
if user is None:
user = self.DEFAULT_USER
if password is None:
password = self.DEFAULT_PASS
return ipmicommand.Command(self.ipaddr, user, password,
privlevel=priv, keepalive=False)
def _get_ipmicmd(self, user=DEFAULT_USER, password=DEFAULT_PASS):
return ipmicommand.Command(self.ipaddr, user, password)
def __init__(self, info, configmanager):
self.trieddefault = None
super(NodeHandler, self).__init__(info, configmanager)
def probe(self):
@@ -56,37 +45,35 @@ class NodeHandler(generic.NodeHandler):
def config(self, nodename, reset=False):
self._bmcconfig(nodename, reset)
def _bmcconfig(self, nodename, reset=False, customconfig=None, vc=None):
def _bmcconfig(self, nodename, reset=False):
# TODO(jjohnson2): set ip parameters, user/pass, alert cfg maybe
# In general, try to use https automation, to make it consistent
# between hypothetical secure path and today.
creds = self.configmanager.get_node_attributes(
nodename,
['secret.hardwaremanagementuser',
'secret.hardwaremanagementpassword'], decrypt=True)
user = creds.get(nodename, {}).get(
'secret.hardwaremanagementuser', {}).get('value', None)
passwd = creds.get(nodename, {}).get(
'secret.hardwaremanagementpassword', {}).get('value', None)
try:
ic = self._get_ipmicmd()
passwd = self.DEFAULT_PASS
passwd = DEFAULT_PASS
except pygexc.IpmiException as pi:
creds = self.configmanager.get_node_attributes(
nodename,
['secret.hardwaremanagementuser',
'secret.hardwaremanagementpassword'], decrypt=True)
user = creds.get(nodename, {}).get(
'secret.hardwaremanagementuser', {}).get('value', None)
havecustomcreds = False
if user is not None and user != self.DEFAULT_USER:
if user is not None and user != DEFAULT_USER:
havecustomcreds = True
else:
user = self.DEFAULT_USER
if passwd is not None and passwd != self.DEFAULT_PASS:
user = DEFAULT_USER
passwd = creds.get(nodename, {}).get(
'secret.hardwaremanagementpassword', {}).get('value', None)
if passwd is not None and passwd != DEFAULT_PASS:
havecustomcreds = True
else:
passwd = self.DEFAULT_PASS
passwd = DEFAULT_PASS
if havecustomcreds:
ic = self._get_ipmicmd(user, passwd)
else:
raise
if vc:
ic.register_key_handler(vc)
currusers = ic.get_users()
lanchan = ic.get_network_channel()
userdata = ic.xraw_command(netfn=6, command=0x44, data=(lanchan,
@@ -106,33 +93,45 @@ class NodeHandler(generic.NodeHandler):
raise exc.TargetEndpointBadCredentials(
'secret.hardwaremanagementuser and/or '
'secret.hardwaremanagementpassword was not configured')
if ('hardwaremanagement.manager' in cd and
cd['hardwaremanagement.manager']['value'] and
not cd['hardwaremanagement.manager']['value'].startswith(
'fe80::')):
newip = cd['hardwaremanagement.manager']['value']
newipinfo = getaddrinfo(newip, 0)[0]
# This getaddrinfo is repeated in get_nic_config, could be
# optimized, albeit with a more convoluted api..
newip = newipinfo[-1][0]
if ':' in newip:
raise exc.NotImplementedException('IPv6 remote config TODO')
netconfig = netutil.get_nic_config(cfg, nodename, ip=newip)
plen = netconfig['prefix']
newip = '{0}/{1}'.format(newip, plen)
ic.set_net_configuration(ipv4_address=newip,
ipv4_configuration='static',
ipv4_gateway=netconfig['ipv4_gateway'])
elif self.ipaddr.startswith('fe80::'):
cfg.set_node_attributes(
{nodename: {'hardwaremanagement.manager': self.ipaddr}})
else:
raise exc.TargetEndpointUnreachable(
'hardwaremanagement.manager must be set to desired address')
newuser = cd['secret.hardwaremanagementuser']['value']
newpass = cd['secret.hardwaremanagementpassword']['value']
for uid in currusers:
if currusers[uid]['name'] == newuser:
# Use existing account that has been created
newuserslot = uid
if newpass != passwd: # don't mess with existing if no change
ic.set_user_password(newuserslot, password=newpass)
ic = self._get_ipmicmd(user, passwd)
if vc:
ic.register_key_handler(vc)
break
else:
newuserslot = lockedusers + 1
if newuserslot < 2:
newuserslot = 2
if newpass != passwd: # don't mess with existing if no change
ic.set_user_password(newuserslot, password=newpass)
ic.set_user_name(newuserslot, newuser)
if havecustomcreds:
ic = self._get_ipmicmd(user, passwd)
if vc:
ic.register_key_handler(vc)
#We are remote operating on the account we are
#using, no need to try to set user access
#ic.set_user_access(newuserslot, lanchan,
# privilege_level='administrator')
ic.set_user_access(newuserslot, lanchan,
privilege_level='administrator')
if newpass != passwd: # don't mess with existing if no change
ic.set_user_password(newuserslot, password=newpass)
# Now to zap others
for uid in currusers:
if uid != newuserslot:
@@ -153,36 +152,6 @@ class NodeHandler(generic.NodeHandler):
# name...
# the user will remain, but that is life
raise
if customconfig:
customconfig(ic)
if ('hardwaremanagement.manager' in cd and
cd['hardwaremanagement.manager']['value'] and
not cd['hardwaremanagement.manager']['value'].startswith(
'fe80::')):
newip = cd['hardwaremanagement.manager']['value']
newipinfo = getaddrinfo(newip, 0)[0]
# This getaddrinfo is repeated in get_nic_config, could be
# optimized, albeit with a more convoluted api..
newip = newipinfo[-1][0]
if ':' in newip:
raise exc.NotImplementedException('IPv6 remote config TODO')
netconfig = netutil.get_nic_config(cfg, nodename, ip=newip)
plen = netconfig['prefix']
newip = '{0}/{1}'.format(newip, plen)
currcfg = ic.get_net_configuration()
if currcfg['ipv4_address'] != newip:
# do not change the ipv4_config if the current config looks
# like it is already accurate
ic.set_net_configuration(ipv4_address=newip,
ipv4_configuration='static',
ipv4_gateway=netconfig[
'ipv4_gateway'])
elif self.ipaddr.startswith('fe80::'):
cfg.set_node_attributes(
{nodename: {'hardwaremanagement.manager': self.ipaddr}})
else:
raise exc.TargetEndpointUnreachable(
'hardwaremanagement.manager must be set to desired address')
if reset:
ic.reset_bmc()
return ic
@@ -1,70 +0,0 @@
# Copyright 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.
import confluent.discovery.handlers.bmc as bmchandler
import eventlet
import confluent.util as util
try:
from urllib import urlencode
except ImportError:
from urllib.parse import urlencode
webclient = eventlet.import_patched('pyghmi.util.webclient')
class NodeHandler(bmchandler.NodeHandler):
DEFAULT_USER = 'admin'
DEFAULT_PASS = 'admin'
devname = 'BMC'
maxmacs = 2
def validate_cert(self, certificate):
# broadly speaking, merely checks consistency moment to moment,
# but if https_cert gets stricter, this check means something
fprint = util.get_fingerprint(self.https_cert)
return util.cert_matches(fprint, certificate)
def get_webclient(self, user, passwd, newuser, newpass):
wc = webclient.SecureHTTPConnection(self.ipaddr, 443,
verifycallback=self.validate_cert)
wc.connect()
authdata = urlencode({'username': user, 'password': passwd,
'weblogsign': 1})
res = wc.grab_json_response_with_status('/api/session', authdata)
if res[1] == 200:
if res[0].get('force_password', 1) == 0:
# Need to handle password change
passchange = {
'Password': newpass,
'RetypePassword': newpass,
'param': 4,
'username': 'admin',
'privilege': 4,
}
passchange = urlencode(passchange)
rsp = wc.grab_json_response_with_status('/api/reset-pass',
passchange)
rsp = wc.grab_json_response_with_status('/api/session',
method='DELETE')
def config(self, nodename, reset=False):
self.nodename = nodename
creds = self.configmanager.get_node_attributes(
self.nodename, ['secret.hardwaremanagementuser',
'secret.hardwaremanagementpassword'],
decrypt=True)
user, passwd, isdefault = self.get_node_credentials(
nodename, creds, 'admin', 'admin')
if not isdefault:
self.get_webclient(self.DEFAULT_USER, self.DEFAULT_PASS, user,
passwd)
self._bmcconfig(nodename, False)
@@ -40,12 +40,6 @@ class NodeHandler(object):
targsa = info['addresses'][0]
self.ipaddr = targsa[0]
@classmethod
def adequate(cls, info):
# Check if the referenced info is really enough, if false, a rescan
# may occur against the target in a short while
return True
def scan(self):
# Do completely passive things to enhance data.
# Probe is permitted to for example attempt a login
@@ -57,7 +51,7 @@ class NodeHandler(object):
# serial number and uuid to flesh out data as needed
return
def preconfig(self, possiblenode):
def preconfig(self):
return
def discoverable_by_switch(self, macs):
@@ -69,27 +63,6 @@ class NodeHandler(object):
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:
passwd = defpass
return user, passwd, not havecustomcreds
@property
def cert_fail_reason(self):
if self._certfailreason == 1:
@@ -12,22 +12,15 @@
# 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 string
import struct
class NodeHandler(bmchandler.NodeHandler):
devname = 'IMM'
@classmethod
def adequate(cls, info):
# We can sometimes receive a partially initialized SLP packet
# This is not adequate for being satisfied
return bool(info.get('attributes', {}))
def scan(self):
slpattrs = self.info.get('attributes', {})
self.isdense = False
@@ -39,14 +32,13 @@ class NodeHandler(bmchandler.NodeHandler):
if wronguuid:
# we need to fix the first three portions of the uuid
uuidprefix = wronguuid.split('-')[:3]
uuidprefix = codecs.encode(struct.pack(
'<IHH', *[int(x, 16) for x in uuidprefix]), 'hex')
uuidprefix = util.stringify(uuidprefix)
uuidprefix = struct.pack(
'<IHH', *[int(x, 16) for x in uuidprefix]).encode('hex')
uuidprefix = uuidprefix[:8] + '-' + uuidprefix[8:12] + '-' + \
uuidprefix[12:16]
self.info['uuid'] = uuidprefix + '-' + '-'.join(
wronguuid.split('-')[3:])
self.info['uuid'] = self.info['uuid'].lower()
self.info['uuid'] = string.lower(self.info['uuid'])
if ff not in ('dense-computing', 'BC2'):
# do not probe unless it's a dense platform
return
@@ -12,36 +12,21 @@
# 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
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
def fixuuid(baduuid):
# SMM dumps it out in hex
uuidprefix = (baduuid[:8], baduuid[8:12], baduuid[12:16])
a = codecs.encode(struct.pack('<IHH', *[int(x, 16) for x in uuidprefix]),
a = struct.pack('<IHH', *[int(x, 16) for x in uuidprefix]).encode(
'hex')
a = util.stringify(a)
uuid = (a[:8], a[8:12], a[12:16], baduuid[16:20], baduuid[20:])
return '-'.join(uuid).lower()
class NodeHandler(bmchandler.NodeHandler):
is_enclosure = True
devname = 'SMM'
maxmacs = 6 # support an enclosure, but try to avoid catching daisy chain
maxmacs = 5 # support an enclosure, but try to avoid catching daisy chain
def scan(self):
# the UUID is in a weird order, fix it up to match
@@ -51,151 +36,9 @@ class NodeHandler(bmchandler.NodeHandler):
uuid = fixuuid(uuid[0])
self.info['uuid'] = uuid
def _validate_cert(self, certificate):
# Assumption is by the time we call config, that discovery core has
# vetted self._fp. Our job here then is just to make sure that
# the currect connection matches the previously saved cert
if not self._fp: # circumstances are that we haven't validated yet
self._fp = certificate
return certificate == self._fp
def _webconfigrules(self, wc):
rules = []
for rule in self.ruleset.split(','):
if '=' not in rule:
continue
name, value = rule.split('=')
if value.lower() in ('no', 'none', 'disable', 'disabled'):
value = '0'
if name.lower() in ('expiry', 'expiration'):
rules.append('passwordDurationDays:' + value)
warndays = '5' if int(value) > 5 else value
rules.append('passwordExpireWarningDays:' + warndays)
if name.lower() in ('lockout', 'loginfailures'):
rules.append('passwordFailAllowdNum:' + value)
if name.lower() == 'reuse':
rules.append('passwordReuseCheckNum:' + value)
if rules:
apirequest = 'set={0}'.format(','.join(rules))
wc.request('POST', '/data', apirequest)
wc.getresponse().read()
def _webconfignet(self, wc, nodename):
cfg = self.configmanager
cd = cfg.get_node_attributes(
nodename, ['hardwaremanagement.manager'])
smmip = cd.get(nodename, {}).get('hardwaremanagement.manager', {}).get('value', None)
if smmip and ':' not in smmip:
smmip = getaddrinfo(smmip, 0)[0]
smmip = smmip[-1][0]
if smmip and ':' in smmip:
raise exc.NotImplementedException('IPv6 not supported')
netconfig = netutil.get_nic_config(cfg, nodename, ip=smmip)
netmask = netutil.cidr_to_mask(netconfig['prefix'])
setdata = 'set=ifIndex:0,v4DHCPEnabled:0,v4IPAddr:{0},v4NetMask:{1}'.format(smmip, netmask)
gateway = netconfig.get('ipv4_gateway', None)
if gateway:
setdata += ',v4Gateway:{0}'.format(gateway)
wc.request('POST', '/data', setdata)
rsp = wc.getresponse()
rspdata = util.stringify(rsp.read())
if '<statusCode>0' not in rspdata:
raise Exception("Error configuring SMM Network")
return
if smmip and ':' in smmip and not smmip.startswith('fe80::'):
raise exc.NotImplementedException('IPv6 configuration TODO')
if self.ipaddr.startswith('fe80::'):
cfg.set_node_attributes(
{nodename: {'hardwaremanagement.manager': self.ipaddr}})
def _webconfigcreds(self, username, password):
wc = webclient.SecureHTTPConnection(self.ipaddr, 443, verifycallback=self._validate_cert)
wc.connect()
authdata = { # start by trying factory defaults
'user': 'USERID',
'password': 'PASSW0RD',
}
headers = {'Connection': 'keep-alive', 'Content-Type': 'application/x-www-form-urlencoded'}
wc.request('POST', '/data/login', urlencode(authdata), headers)
rsp = wc.getresponse()
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', urlencode(authdata), headers)
rsp = wc.getresponse()
rspdata = util.stringify(rsp.read())
if 'renew_account' in rspdata:
raise Exception('Configured password has expired')
if 'authResult>0' not in rspdata:
raise Exception('Unknown username/password on SMM')
tokens = fromstring(rspdata)
st2 = tokens.findall('st2')[0].text
wc.set_header('ST2', st2)
return wc
if 'renew_account' in rspdata:
passwdchange = {'oripwd': 'PASSW0RD', 'newpwd': password}
tokens = fromstring(rspdata)
st2 = tokens.findall('st2')[0].text
wc.set_header('ST2', st2)
wc.request('POST', '/data/changepwd', urlencode(passwdchange))
rsp = wc.getresponse()
rspdata = rsp.read()
authdata['password'] = password
wc.request('POST', '/data/login', urlencode(authdata), headers)
rsp = wc.getresponse()
rspdata = util.stringify(rsp.read())
if 'authResult>0' in rspdata:
tokens = fromstring(rspdata)
st2 = tokens.findall('st2')[0].text
wc.set_header('ST2', st2)
if username == 'USERID':
return wc
wc.request('POST', '/data', 'set=user(2,1,{0},511,,4,15,0)'.format(username))
rsp = wc.getresponse()
rspdata = rsp.read()
wc.request('POST', '/data/logout')
rsp = wc.getresponse()
rspdata = rsp.read()
authdata['user'] = username
wc.request('POST', '/data/login', urlencode(authdata, headers))
rsp = wc.getresponse()
rspdata = rsp.read()
tokens = fromstring(rspdata)
st2 = tokens.findall('st2')[0].text
wc.set_header('ST2', st2)
return wc
def config(self, nodename):
# SMM for now has to reset to assure configuration applies
dpp = self.configmanager.get_node_attributes(
nodename, 'discovery.passwordrules')
self.ruleset = dpp.get(nodename, {}).get(
'discovery.passwordrules', {}).get('value', '')
creds = self.configmanager.get_node_attributes(
nodename,
['secret.hardwaremanagementuser',
'secret.hardwaremanagementpassword'], decrypt=True)
username = creds.get(nodename, {}).get(
'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':
# We must avoid hitting the web interface due to forced password change, best effert
self._bmcconfig(nodename)
else:
# Switch to full web based configuration, to mitigate risks with the SMM
wc = self._webconfigcreds(username, passwd)
self._webconfigrules(wc)
self._webconfignet(wc, nodename)
super(NodeHandler, self).config(nodename)
# notes for smm:
# POST to:
@@ -210,4 +53,4 @@ class NodeHandler(bmchandler.NodeHandler):
# with body user=USERID&password=Passw0rd!4321
# yields:
# <?xml version="1.0" encoding="UTF-8"?><root> <status>ok</status> <authResult>0</authResult> <forwardUrl>index.html</forwardUrl> </root>
# note forwardUrl, if password change needed, will indicate something else
# note forwardUrl, if password change needed, will indicate something else
@@ -1,221 +0,0 @@
# Copyright 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.
import confluent.discovery.handlers.generic as generic
import confluent.exceptions as exc
import confluent.netutil as netutil
import confluent.util as util
import eventlet.support.greendns
import json
try:
from urllib import urlencode
except ImportError:
from urllib.parse import urlencode
getaddrinfo = eventlet.support.greendns.getaddrinfo
webclient = eventlet.import_patched('pyghmi.util.webclient')
class NodeHandler(generic.NodeHandler):
devname = 'TSM'
DEFAULT_USER = 'USERID'
DEFAULT_PASS = 'PASSW0RD'
def __init__(self, info, configmanager):
self.trieddefault = None
self.targuser = None
self.curruser = None
self.currpass = None
self.targpass = None
self.nodename = None
self.csrftok = None
self.channel = None
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
fprint = util.get_fingerprint(self.https_cert)
return util.cert_matches(fprint, certificate)
def _get_wc(self):
authdata = { # start by trying factory defaults
'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:
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 = {
'Password': self.targpass,
'RetypePassword': self.targpass,
'param': 4,
'default_password': self.DEFAULT_PASS,
'username': self.DEFAULT_USER
}
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
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
self.currpass = self.targpass
return wc
else:
self.curruser = self.DEFAULT_USER
self.currpass = self.DEFAULT_PASS
self.csrftok = rsp['CSRFToken']
self.channel = rsp['channel']
return wc
if self.curruser:
authdata['username'] = self.curruser
authdata['password'] = self.currpass
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
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
self.currpass = self.targpass
self.csrftok = rsp['CSRFToken']
self.channel = rsp['channel']
return wc
def config(self, nodename):
self.nodename = nodename
creds = self.configmanager.get_node_attributes(
nodename, ['secret.hardwaremanagementuser',
'secret.hardwaremanagementpassword',
'hardwaremanagement.manager', 'hardwaremanagement.method', 'console.method'],
True)
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()
wc.set_header('X-CSRFTOKEN', self.csrftok)
curruserinfo = {}
authupdate = False
wc.set_header('Content-Type', 'application/json')
if user != self.curruser:
authupdate = True
if not curruserinfo:
curruserinfo = wc.grab_json_response('/api/settings/users')
authchg = curruserinfo[1]
authchg['name'] = user
if passwd != self.currpass:
authupdate = True
if not curruserinfo:
curruserinfo = wc.grab_json_response('/api/settings/users')
authchg = curruserinfo[1]
authchg['changepassword'] = 0
authchg['password_size'] = 'bytes_20'
authchg['password'] = passwd
authchg['confirm_password'] = passwd
if authupdate:
rsp, status = wc.grab_json_response_with_status('/api/settings/users/2', authchg, method='PUT')
if (cd.get('hardwaremanagement.method', {}).get('value', 'ipmi') != 'redfish'
or cd.get('console.method', {}).get('value', None) == 'ipmi'):
# IPMI must be enabled per user config
wc.grab_json_response('/api/settings/ipmilanconfig', {
'ipv4_enable': 1, 'ipv6_enable': 1,
'uncheckedipv4lanEnable': 0, 'uncheckedipv6lanEnable': 0,
'checkedipv4lanEnable': 1, 'checkedipv6lanEnable': 1})
if ('hardwaremanagement.manager' in cd and
cd['hardwaremanagement.manager']['value'] and
not cd['hardwaremanagement.manager']['value'].startswith(
'fe80::')):
newip = cd['hardwaremanagement.manager']['value']
newipinfo = getaddrinfo(newip, 0)[0]
newip = newipinfo[-1][0]
if ':' in newip:
raise exc.NotImplementedException('IPv6 remote config TODO')
currnet = wc.grab_json_response('/api/settings/network')
for net in currnet:
if net['channel_number'] == self.channel and net['lan_enable'] == 0:
# ignore false indication and switch to 8 (dedicated)
self.channel = 8
if net['channel_number'] == self.channel:
# we have found the interface to potentially manipulate
if net['ipv4_address'] != newip:
netconfig = netutil.get_nic_config(self.configmanager, nodename, ip=newip)
newmask = netutil.cidr_to_mask(netconfig['prefix'])
net['ipv4_address'] = newip
net['ipv4_subnet'] = newmask
if netconfig['ipv4_gateway']:
net['ipv4_gateway'] = netconfig['ipv4_gateway']
net['ipv4_dhcp_enable'] = 0
rsp, status = wc.grab_json_response_with_status(
'/api/settings/network/{0}'.format(net['id']), net, method='PUT')
break
elif self.ipaddr.startswith('fe80::'):
self.configmanager.set_node_attributes(
{nodename: {'hardwaremanagement.manager': self.ipaddr}})
else:
raise exc.TargetEndpointUnreachable(
'hardwaremanagement.manager must be set to desired address (No IPv6 Link Local detected)')
rsp, status = wc.grab_json_response_with_status('/api/session', method='DELETE')
if __name__ == '__main__':
import confluent.config.configmanager as cfm
c = cfm.ConfigManager(None)
import sys
info = {'addresses': [[sys.argv[1]]] }
print(repr(info))
testr = NodeHandler(info, c)
testr.config(sys.argv[2])
@@ -1,4 +1,4 @@
# Copyright 2017-2019 Lenovo
# Copyright 2017 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -12,417 +12,51 @@
# See the License for the specific language governing permissions and
# 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
import eventlet
import eventlet.support.greendns
import json
import os
import pyghmi.exceptions as pygexc
import eventlet.green.socket as socket
webclient = eventlet.import_patched('pyghmi.util.webclient')
import struct
getaddrinfo = eventlet.support.greendns.getaddrinfo
def fixup_uuid(uuidprop):
baduuid = ''.join(uuidprop.split())
uuidprefix = (baduuid[:8], baduuid[8:12], baduuid[12:16])
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()
import pyghmi.ipmi.oem.lenovo.imm as imm
class NodeHandler(immhandler.NodeHandler):
devname = 'XCC'
def __init__(self, info, configmanager):
self._wc = None
self.nodename = None
self.tmpnodename = None
self.tmppasswd = None
self._atdefaultcreds = True
self._needpasswordchange = True
self._currcreds = (None, None)
super(NodeHandler, self).__init__(info, configmanager)
@classmethod
def adequate(cls, info):
# We can sometimes receive a partially initialized SLP packet
# This is not adequate for being satisfied
return bool(info.get('attributes', {}))
def preconfig(self, possiblenode):
self.tmpnodename = possiblenode
def preconfig(self):
ff = self.info.get('attributes', {}).get('enclosure-form-factor', '')
if ff not in ('dense-computing', [u'dense-computing']):
# skip preconfig for non-SD530 servers
return
self.trieddefault = None # Reset state on a preconfig attempt
# attempt to enable SMM
#it's normal to get a 'not supported' (193) for systems without an SMM
# need to branch on 3.00+ firmware
currfirm = self.info.get('attributes', {}).get('firmware-image-info', [''])[0]
currfirm = currfirm.split(':')
if len(currfirm) > 1:
currfirm = float(currfirm[1])
disableipmi = False
if currfirm >= 3:
# IPMI is disabled and we need it, also we need to go to *some* password
wc = self.wc
if not wc:
# We cannot try to enable SMM here without risking real credentials
# on the wire to untrusted parties
return
wc.grab_json_response('/api/providers/logout')
wc.set_basic_credentials(self._currcreds[0], self._currcreds[1])
rsp = wc.grab_json_response('/redfish/v1/Managers/1/NetworkProtocol')
if not rsp.get('IPMI', {}).get('ProtocolEnabled', True):
disableipmi = True
_, _ = wc.grab_json_response_with_status(
'/redfish/v1/Managers/1/NetworkProtocol',
{'IPMI': {'ProtocolEnabled': True}}, method='PATCH')
ipmicmd = None
try:
ipmicmd = self._get_ipmicmd(self._currcreds[0], self._currcreds[1])
ipmicmd = self._get_ipmicmd()
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) and
str(e) != 'Session no longer connected'):
'Incorrect password' not in str(e)):
# raise an issue if anything other than to be expected
if disableipmi:
_, _ = wc.grab_json_response_with_status(
'/redfish/v1/Managers/1/NetworkProtocol',
{'IPMI': {'ProtocolEnabled': False}}, method='PATCH')
raise
self.trieddefault = True
if disableipmi:
_, _ = wc.grab_json_response_with_status(
'/redfish/v1/Managers/1/NetworkProtocol',
{'IPMI': {'ProtocolEnabled': False}}, method='PATCH')
#TODO: decide how to clean out if important
#as it stands, this can step on itself
#if ipmicmd:
# ipmicmd.ipmi_session.logout()
def validate_cert(self, certificate):
# broadly speaking, merely checks consistency moment to moment,
# but if https_cert gets stricter, this check means something
fprint = util.get_fingerprint(self.https_cert)
return util.cert_matches(fprint, certificate)
def get_webclient(self, username, password, newpassword):
wc = self._wc.dupe()
try:
wc.connect()
except socket.error as se:
if se.errno != errno.ECONNREFUSED:
raise
return (None, None)
pwdchanged = False
adata = json.dumps({'username': util.stringify(username),
'password': util.stringify(password)
})
headers = {'Connection': 'keep-alive',
'Content-Type': 'application/json'}
wc.request('POST', '/api/login', adata, headers)
rsp = wc.getresponse()
if rsp.status != 200 and password == 'PASSW0RD':
rsp.read()
adata = json.dumps({
'username': username,
'password': newpassword,
})
headers = {'Connection': 'keep-alive',
'Content-Type': 'application/json'}
wc.request('POST', '/api/login', adata, headers)
rsp = wc.getresponse()
if rsp.status == 200:
pwdchanged = True
password = newpassword
else:
rsp.read()
return (None, None)
if rsp.status == 200:
self._currcreds = (username, password)
wc.set_basic_credentials(username, password)
rspdata = json.loads(rsp.read())
wc.set_header('Content-Type', 'application/json')
wc.set_header('Authorization', 'Bearer ' + rspdata['access_token'])
if '_csrf_token' in wc.cookies:
wc.set_header('X-XSRF-TOKEN', wc.cookies['_csrf_token'])
if rspdata.get('pwchg_required', None) == 'true':
wc.request('POST', '/api/function', json.dumps(
{'USER_UserPassChange': '1,{0}'.format(newpassword)}))
rsp = wc.getresponse()
rsp.read()
if rsp.status != 200:
return (None, None)
self._currcreds = (username, newpassword)
wc.set_basic_credentials(username, newpassword)
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
wc = self.wc
self.set_password_policy('', wc)
return (wc, pwdchanged)
return (None, None)
@property
def wc(self):
passwd = None
isdefault = True
if self._wc is None:
self._wc = webclient.SecureHTTPConnection(
self.ipaddr, 443, verifycallback=self.validate_cert)
self._wc.connect()
nodename = None
if self.nodename:
nodename = self.nodename
inpreconfig = False
elif self.tmpnodename:
nodename = None
inpreconfig = True
if self._currcreds[0] is not None:
wc, pwdchanged = self.get_webclient(self._currcreds[0], self._currcreds[1], None)
if wc:
return wc
if nodename:
creds = self.configmanager.get_node_attributes(
nodename, ['secret.hardwaremanagementuser',
'secret.hardwaremanagementpassword'], decrypt=True)
user, passwd, isdefault = self.get_node_credentials(
nodename, creds, 'USERID', 'PASSW0RD')
if not self.trieddefault:
if not passwd:
# So in preconfig context, we don't have admin permission to
# actually divulge anything to the target
# 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
# (TempW0rd42)
passwd = 'TempW0rd42'
wc, pwdchanged = self.get_webclient('USERID', 'PASSW0RD', passwd)
if wc:
if pwdchanged:
if inpreconfig:
self.tmppasswd = passwd
else:
self._needpasswordchange = False
return wc
self.trieddefault = True
if isdefault:
return
self._atdefaultcreds = False
if self.tmppasswd:
wc, _ = self.get_webclient('USERID', self.tmppasswd, passwd)
else:
wc, _ = self.get_webclient(user, passwd, None)
if wc:
return wc
def set_password_policy(self, strruleset, wc):
ruleset = {'USER_GlobalMinPassChgInt': '0'}
for rule in strruleset.split(','):
if '=' not in rule:
continue
name, value = rule.split('=')
if value.lower() in ('no', 'none', 'disable', 'disabled'):
value = '0'
if name.lower() in ('expiry', 'expiration'):
ruleset['USER_GlobalPassExpPeriod'] = value
if int(value) < 5:
ruleset['USER_GlobalPassExpWarningPeriod'] = value
if name.lower() in ('lockout', 'loginfailures'):
if value.lower() in ('no', 'none', 'disable', 'disabled'):
value = '0'
ruleset['USER_GlobalMaxLoginFailures'] = value
if name.lower() == 'complexity':
ruleset['USER_GlobalPassComplexRequired'] = value
if name.lower() == 'reuse':
ruleset['USER_GlobalMinPassReuseCycle'] = value
try:
wc.grab_json_response('/api/dataset', ruleset)
except Exception as e:
print(repr(e))
pass
def _get_next_userid(self, wc):
userinfo = wc.grab_json_response('/api/dataset/imm_users')
userinfo = userinfo['items'][0]['users']
for user in userinfo:
if user['users_user_name'] == '':
return user['users_user_id']
def _setup_xcc_account(self, username, passwd, wc):
userinfo = wc.grab_json_response('/api/dataset/imm_users')
uid = None
for user in userinfo['items'][0]['users']:
if user['users_user_name'] == username:
uid = user['users_user_id']
break
else:
for user in userinfo['items'][0]['users']:
if user['users_user_name'] == 'USERID':
uid = user['users_user_id']
break
if not uid:
raise Exception("XCC has neither the default user nor configured user")
# The following will work if the password is force change or normal..
if self._needpasswordchange and self.tmppasswd != passwd:
wc.grab_json_response('/api/function',
{'USER_UserPassChange': '{0},{1}'.format(uid, passwd)})
if username != 'USERID':
wc.grab_json_response(
'/api/function',
{'USER_UserModify': '{0},{1},,1,4,0,0,0,0,,8,'.format(uid, username)})
self.tmppasswd = None
self._currcreds = (username, passwd)
def _convert_sha256account(self, user, passwd, wc):
# First check if the specified user is sha256...
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
break
if curruser.get('users_pass_is_sha256', 0):
self._wc = None
wc = self.wc
nwc = wc.dupe()
# Have to convert it for being useful with most Lenovo automation tools
# This requires deleting the account entirely and trying again
tmpuid = self._get_next_userid(wc)
try:
tpass = base64.b64encode(os.urandom(9)) + 'Iw47$'
userparams = "{0},6pmu0ezczzcp,{1},1,4,0,0,0,0,,8,".format(tmpuid, tpass)
result = wc.grab_json_response('/api/function', {'USER_UserCreate': userparams})
wc.grab_json_response('/api/providers/logout')
adata = json.dumps({
'username': '6pmu0ezczzcp',
'password': tpass,
})
headers = {'Connection': 'keep-alive', 'Content-Type': 'application/json'}
nwc.request('POST', '/api/login', adata, headers)
rsp = nwc.getresponse()
if rsp.status == 200:
rspdata = json.loads(rsp.read())
nwc.set_header('Content-Type', 'application/json')
nwc.set_header('Authorization', 'Bearer ' + rspdata['access_token'])
if '_csrf_token' in wc.cookies:
nwc.set_header('X-XSRF-TOKEN', wc.cookies['_csrf_token'])
if rspdata.get('reason', False):
newpass = base64.b64encode(os.urandom(9)) + 'q4J$'
nwc.grab_json_response(
'/api/function',
{'USER_UserPassChange': '{0},{1}'.format(tmpuid, newpass)})
nwc.grab_json_response('/api/function', {'USER_UserDelete': "{0},{1}".format(curruser['users_user_id'], user)})
userparams = "{0},{1},{2},1,4,0,0,0,0,,8,".format(curruser['users_user_id'], user, tpass)
nwc.grab_json_response('/api/function', {'USER_UserCreate': userparams})
nwc.grab_json_response('/api/providers/logout')
nwc, pwdchanged = self.get_webclient(user, tpass, passwd)
if not pwdchanged:
nwc.grab_json_response(
'/api/function',
{'USER_UserPassChange': '{0},{1}'.format(curruser['users_user_id'], passwd)})
nwc.grab_json_response('/api/providers/logout')
finally:
self._wc = None
wc = self.wc
wc.grab_json_response('/api/function', {'USER_UserDelete': "{0},{1}".format(tmpuid, '6pmu0ezczzcp')})
wc.grab_json_response('/api/providers/logout')
def config(self, nodename, reset=False):
self.nodename = nodename
# TODO(jjohnson2): set ip parameters, user/pass, alert cfg maybe
# In general, try to use https automation, to make it consistent
# between hypothetical secure path and today.
dpp = self.configmanager.get_node_attributes(
nodename, 'discovery.passwordrules')
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(strruleset, wc)
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)
cd = self.configmanager.get_node_attributes(
nodename, ['secret.hardwaremanagementuser',
'secret.hardwaremanagementpassword',
'hardwaremanagement.manager', 'hardwaremanagement.method', 'console.method'],
True)
cd = cd.get(nodename, {})
if (cd.get('hardwaremanagement.method', {}).get('value', 'ipmi') != 'redfish'
or cd.get('console.method', {}).get('value', None) == 'ipmi'):
nwc = wc.dupe()
nwc.set_basic_credentials(self._currcreds[0], self._currcreds[1])
rsp = nwc.grab_json_response('/redfish/v1/Managers/1/NetworkProtocol')
if not rsp.get('IPMI', {}).get('ProtocolEnabled', True):
# User has indicated IPMI support, but XCC is currently disabled
# change XCC to be consistent
_, _ = nwc.grab_json_response_with_status(
'/redfish/v1/Managers/1/NetworkProtocol',
{'IPMI': {'ProtocolEnabled': True}}, method='PATCH')
if ('hardwaremanagement.manager' in cd and
cd['hardwaremanagement.manager']['value'] and
not cd['hardwaremanagement.manager']['value'].startswith(
'fe80::')):
newip = cd['hardwaremanagement.manager']['value']
newipinfo = getaddrinfo(newip, 0)[0]
newip = newipinfo[-1][0]
if ':' in newip:
raise exc.NotImplementedException('IPv6 remote config TODO')
netconfig = netutil.get_nic_config(self.configmanager, nodename, ip=newip)
newmask = netutil.cidr_to_mask(netconfig['prefix'])
currinfo = wc.grab_json_response('/api/providers/logoninfo')
currip = currinfo.get('items', [{}])[0].get('ipv4_address', '')
# do not change the ipv4_config if the current config looks right already
if currip != newip:
statargs = {
'ENET_IPv4Ena': '1', 'ENET_IPv4AddrSource': '0',
'ENET_IPv4StaticIPAddr': newip, 'ENET_IPv4StaticIPNetMask': newmask
}
if netconfig['ipv4_gateway']:
statargs['ENET_IPv4GatewayIPAddr'] = netconfig['ipv4_gateway']
wc.grab_json_response('/api/dataset', statargs)
elif self.ipaddr.startswith('fe80::'):
self.configmanager.set_node_attributes(
{nodename: {'hardwaremanagement.manager': self.ipaddr}})
else:
raise exc.TargetEndpointUnreachable(
'hardwaremanagement.manager must be set to desired address (No IPv6 Link Local detected)')
wc.grab_json_response('/api/providers/logout')
ic = self._bmcconfig(nodename)
ff = self.info.get('attributes', {}).get('enclosure-form-factor', '')
if ff not in ('dense-computing', [u'dense-computing']):
return
enclosureuuid = self.info.get('attributes', {}).get('chassis-uuid', [None])[0]
# Ok, we can get the enclosure uuid now..
ic.oem_init()
enclosureuuid = ic._oem.immhandler.get_property(
'/v2/ibmc/smm/chassis/uuid')
enclosureuuid = ic._oem.immhandler.get_property(
'/v2/ibmc/smm/chassis/uuid')
if enclosureuuid:
enclosureuuid = enclosureuuid.lower()
enclosureuuid = imm.fixup_uuid(enclosureuuid).lower()
em = self.configmanager.get_node_attributes(nodename,
'enclosure.manager')
em = em.get(nodename, {}).get('enclosure.manager', {}).get(
@@ -431,3 +65,8 @@ class NodeHandler(immhandler.NodeHandler):
if em:
self.configmanager.set_node_attributes(
{em: {'id.uuid': enclosureuuid}})
# TODO(jjohnson2): web based init config for future prevalidated cert scheme
# def config(self, nodename):
# return
@@ -30,67 +30,33 @@ pxearchs = {
'\x00\x07': 'uefi-x64',
'\x00\x09': 'uefi-x64',
'\x00\x0b': 'uefi-aarch64',
'\x00\x10': 'uefi-httpboot',
}
def stringify(value):
string = bytes(value)
if not isinstance(string, str):
string = string.decode('utf8')
return string
def decode_uuid(rawguid):
lebytes = struct.unpack_from('<IHH', rawguid[:8])
bebytes = struct.unpack_from('>HHI', rawguid[8:])
lebytes = struct.unpack_from('<IHH', buffer(rawguid[:8]))
bebytes = struct.unpack_from('>HHI', buffer(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()
def _decode_ocp_vivso(rq, idx, size):
end = idx + size
vivso = {'service-type': 'onie-switch'}
while idx < end:
if rq[idx] == 3:
vivso['machine'] = stringify(rq[idx + 2:idx + 2 + rq[idx + 1]])
elif rq[idx] == 4:
vivso['arch'] = stringify(rq[idx + 2:idx + 2 + rq[idx + 1]])
elif rq[idx] == 5:
vivso['revision'] = stringify(rq[idx + 2:idx + 2 + rq[idx + 1]])
idx += rq[idx + 1] + 2
return '', None, vivso
def find_info_in_options(rq, optidx):
uuid = None
arch = None
vivso = None
ztpurlrequested = False
iscumulus = False
try:
while uuid is None or arch is None:
if rq[optidx] == 53: # DHCP message type
# we want only length 1 and only discover (type 1)
if rq[optidx + 1] != 1 or rq[optidx + 2] != 1:
return uuid, arch, vivso
return uuid, arch
optidx += 3
elif rq[optidx] == 55:
if 239 in rq[optidx + 2:optidx + 2 + rq[optidx + 1]]:
ztpurlrequested = True
optidx += rq[optidx + 1] + 2
elif rq[optidx] == 60:
vci = stringify(rq[optidx + 2:optidx + 2 + rq[optidx + 1]])
if vci.startswith('cumulus-linux'):
iscumulus = True
arch = vci.replace('cumulus-linux', '').strip()
optidx += rq[optidx + 1] + 2
elif rq[optidx] == 97:
if rq[optidx + 1] != 17:
# 16 bytes of uuid and one reserved byte
return uuid, arch, vivso
return uuid, arch
if rq[optidx + 2] != 0: # the reserved byte should be zero,
# anything else would be a new spec that we don't know yet
return uuid, arch, vivso
return uuid, arch
uuid = decode_uuid(rq[optidx + 3:optidx + 19])
optidx += 19
elif rq[optidx] == 93:
@@ -100,22 +66,13 @@ def find_info_in_options(rq, optidx):
if archraw in pxearchs:
arch = pxearchs[archraw]
optidx += 4
elif rq[optidx] == 125:
#vivso = rq[optidx + 2:optidx + 2 + rq[optidx + 1]]
if rq[optidx + 2:optidx + 6] == b'\x00\x00\xa6\x7f': # OCP
return _decode_ocp_vivso(rq, optidx + 7, rq[optidx + 6])
optidx += rq[optidx + 1] + 2
else:
optidx += rq[optidx + 1] + 2
except IndexError:
pass
if not vivso and iscumulus and ztpurlrequested:
if not uuid:
uuid = ''
vivso = {'service-type': 'cumulus-switch', 'arch': arch}
return uuid, arch, vivso
return uuid, arch
return uuid, arch
def snoop(handler, protocol=None):
def snoop(handler):
#TODO(jjohnson2): ipv6 socket and multicast for DHCPv6, should that be
#prominent
#TODO(jjohnson2): IP_PKTINFO, recvmsg to get the destination ip, per
@@ -141,17 +98,10 @@ def snoop(handler, protocol=None):
netaddr = ':'.join(['{0:02x}'.format(x) for x in netaddr])
optidx = 0
try:
optidx = rq.index(b'\x63\x82\x53\x63') + 4
optidx = rq.index('\x63\x82\x53\x63') + 4
except ValueError:
continue
uuid, arch, vivso = find_info_in_options(rq, optidx)
if vivso:
# info['modelnumber'] = info['attributes']['enclosure-machinetype-model'][0]
handler({'hwaddr': netaddr, 'uuid': uuid,
'architecture': vivso.get('arch', ''),
'services': (vivso['service-type'],),
'attributes': {'enclosure-machinetype-model': [vivso.get('machine', '')]}})
continue
uuid, arch = find_info_in_options(rq, optidx)
if uuid is None:
continue
# We will fill out service to have something to byte into,
@@ -1,6 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017-2019 Lenovo
# Copyright 2017 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -27,8 +27,6 @@ import traceback
_slp_services = set([
'service:management-hardware.IBM:integrated-management-module2',
'service:lenovo-smm',
'service:ipmi',
'service:lighttpd',
'service:management-hardware.Lenovo:lenovo-xclarity-controller',
'service:management-hardware.IBM:chassis-management-module',
'service:management-hardware.Lenovo:chassis-management-module',
@@ -49,6 +47,7 @@ except AttributeError:
IPPROTO_IPV6 = 41 # Assume Windows value if socket is missing it
def _parse_slp_header(packet):
packet = bytearray(packet)
if len(packet) < 16 or packet[0] != 2:
@@ -84,8 +83,6 @@ 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
@@ -236,42 +233,36 @@ def _find_srvtype(net, net4, srvtype, addresses, xid):
def _grab_rsps(socks, rsps, interval, xidmap):
r = None
res = select.select(socks, (), (), interval)
if res:
r = res[0]
r, _, _ = select.select(socks, (), (), interval)
while r:
for s in r:
(rsp, peer) = s.recvfrom(9000)
neighutil.refresh_neigh()
_parse_slp_packet(rsp, peer, rsps, xidmap)
res = select.select(socks, (), (), interval)
if not res:
r = None
else:
r = res[0]
r, _, _ = select.select(socks, (), (), interval)
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 ')' not in attrstr:
attribs['INCOMPLETE'] = True
return attribs
currattr = attrstr[1:attrstr.index(')')]
if '=' not in currattr: # Not allegedly kosher, but still..
currattr = currattr.decode('utf-8')
attribs[currattr] = None
else:
attrname, attrval = currattr.split('=', 1)
attrname, attrval = currattr.split('=')
attrname = attrname.decode('utf-8')
attribs[attrname] = []
for val in attrval.split(','):
try:
val = val.decode('utf-8')
except UnicodeDecodeError:
val = '*DECODEERROR*'
if val[:3] == '\\FF': # we should make this bytes
finalval = bytearray([])
for bnum in attrval[3:].split('\\'):
@@ -281,9 +272,9 @@ def _parse_attrlist(attrstr):
val = finalval
if 'uuid' in attrname and len(val) == 16:
lebytes = struct.unpack_from(
'<IHH', memoryview(val[:8]))
'<IHH', buffer(val[:8]))
bebytes = struct.unpack_from(
'>HHI', memoryview(val[8:]))
'>HHI', buffer(val[8:]))
val = '{0:08X}-{1:04X}-{2:04X}-{3:04X}-' \
'{4:04X}{5:08X}'.format(
lebytes[0], lebytes[1], lebytes[2], bebytes[0],
@@ -291,7 +282,7 @@ def _parse_attrlist(attrstr):
).lower()
attribs[attrname].append(val)
attrstr = attrstr[attrstr.index(')'):]
elif attrstr[0] == ','[0]:
elif attrstr[0] == ',':
attrstr = attrstr[1:]
elif ',' in attrstr:
currattr = attrstr[:attrstr.index(',')]
@@ -304,11 +295,9 @@ def _parse_attrlist(attrstr):
return attribs
def _parse_attrs(data, parsed, xid=None):
def _parse_attrs(data, parsed):
headinfo = _parse_slp_header(data)
if xid is None:
xid = parsed['xid']
if headinfo['function'] != 7 or headinfo['xid'] != xid:
if headinfo['function'] != 7 or headinfo['xid'] != parsed['xid']:
return
payload = headinfo['payload']
if struct.unpack('!H', bytes(payload[:2]))[0] != 0:
@@ -318,18 +307,8 @@ def _parse_attrs(data, parsed, xid=None):
parsed['attributes'] = _parse_attrlist(attrstr)
def fix_info(info, handler):
if '_attempts' not in info:
info['_attempts'] = 10
if info['_attempts'] == 0:
return
info['_attempts'] -= 1
_add_attributes(info)
handler(info)
def _add_attributes(parsed):
xid = parsed.get('xid', 42)
attrq = _generate_attr_request(parsed['services'][0], xid)
attrq = _generate_attr_request(parsed['services'][0], parsed['xid'])
target = None
# prefer reaching out to an fe80 if present, to be highly robust
# in face of network changes
@@ -344,14 +323,13 @@ def _add_attributes(parsed):
else:
net = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
net.settimeout(1.0)
net.connect(target)
except socket.error:
return
net.sendall(attrq)
rsp = net.recv(8192)
net.close()
_parse_attrs(rsp, parsed, xid)
_parse_attrs(rsp, parsed)
def query_srvtypes(target):
@@ -373,7 +351,6 @@ def query_srvtypes(target):
while tries and not connected:
tries -= 1
try:
net.settimeout(1.0)
net.connect(target)
connected = True
except socket.error:
@@ -406,7 +383,7 @@ def rescan(handler):
handler(scanned)
def snoop(handler, protocol=None):
def snoop(handler):
"""Watch for SLP activity
handler will be called with a dictionary of relevant attributes
@@ -415,7 +392,7 @@ def snoop(handler, protocol=None):
:return:
"""
tracelog = log.Logger('trace')
active_scan(handler, protocol)
active_scan(handler)
net = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
net.setsockopt(IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
slpg = socket.inet_pton(socket.AF_INET6, 'ff01::123')
@@ -492,28 +469,13 @@ def snoop(handler, protocol=None):
peerbymacaddress[mac]['xid'] = 1
_add_attributes(peerbymacaddress[mac])
peerbymacaddress[mac]['hwaddr'] = mac
peerbymacaddress[mac]['protocol'] = protocol
for srvurl in peerbymacaddress[mac].get('urls', ()):
if len(srvurl) > 4:
srvurl = srvurl[:-3]
if srvurl.endswith('://Athena:'):
continue
if 'service:ipmi' in peerbymacaddress[mac]['services']:
continue
if 'service:lightttpd' in peerbymacaddress[mac]['services']:
currinf = peerbymacaddress[mac]
curratt = currinf.get('attributes', {})
if curratt.get('System-Manufacturing', [None])[0] == 'Lenovo' and curratt.get('type', [None])[0] == 'LenovoThinkServer':
peerbymacaddress[mac]['services'] = ['service:lenovo-tsm']
else:
continue
handler(peerbymacaddress[mac])
except Exception as e:
tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
event=log.Events.stacktrace)
def active_scan(handler, protocol=None):
def active_scan(handler):
known_peers = set([])
for scanned in scan():
for addr in scanned['addresses']:
@@ -524,7 +486,6 @@ def active_scan(handler, protocol=None):
break
known_peers.add(addr)
else:
scanned['protocol'] = protocol
handler(scanned)
@@ -570,13 +531,6 @@ def scan(srvtypes=_slp_services, addresses=None, localonly=False):
_grab_rsps((net, net4), rsps, 1, xidmap)
# now to analyze and flesh out the responses
for id in rsps:
for srvurl in rsps[id].get('urls', ()):
if len(srvurl) > 4:
srvurl = srvurl[:-3]
if srvurl.endswith('://Athena:'):
continue
if 'service:ipmi' in rsps[id]['services']:
continue
if localonly:
for addr in rsps[id]['addresses']:
if 'fe80' in addr[0]:
@@ -584,15 +538,6 @@ def scan(srvtypes=_slp_services, addresses=None, localonly=False):
else:
continue
_add_attributes(rsps[id])
if 'service:lighttpd' in rsps[id]['services']:
currinf = rsps[id]
curratt = currinf.get('attributes', {})
if curratt.get('System-Manufacturing', [None])[0] == 'Lenovo' and curratt.get('type', [None])[0] == 'LenovoThinkServer':
currinf['services'] = ['service:lenovo-tsm']
curratt['enclosure-serial-number'] = curratt['Product-Serial']
curratt['enclosure-machinetype-model'] = curratt['Machine-Type']
else:
continue
del rsps[id]['payload']
del rsps[id]['function']
del rsps[id]['xid']
@@ -32,10 +32,6 @@ import confluent.neighutil as neighutil
import confluent.util as util
import eventlet.green.select as select
import eventlet.green.socket as socket
try:
from eventlet.green.urllib.request import urlopen
except (ImportError, AssertionError):
from eventlet.green.urllib2 import urlopen
import struct
mcastv4addr = '239.255.255.250'
@@ -49,20 +45,6 @@ smsg = ('M-SEARCH * HTTP/1.1\r\n'
'MX: 3\r\n\r\n')
def active_scan(handler, protocol=None):
known_peers = set([])
for scanned in scan(['urn:dmtf-org:service:redfish-rest:1']):
for addr in scanned['addresses']:
ip = addr[0].partition('%')[0] # discard scope if present
if ip not in neighutil.neightable:
continue
if addr in known_peers:
break
known_peers.add(addr)
else:
scanned['protocol'] = protocol
handler(scanned)
def scan(services, target=None):
for service in services:
for rply in _find_service(service, target):
@@ -96,13 +78,8 @@ def snoop(handler, byehandler=None):
for i4 in util.list_ips():
ssdp4mcast = socket.inet_pton(socket.AF_INET, mcastv4addr) + \
socket.inet_aton(i4['addr'])
try:
net4.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP,
ssdp4mcast)
except socket.error as e:
if e.errno != 98:
# errno 98 can happen if aliased, skip for now
raise
net4.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP,
ssdp4mcast)
net4.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
net4.bind(('', 1900))
net6.bind(('', 1900))
@@ -127,11 +104,11 @@ def snoop(handler, byehandler=None):
known_peers.add(peer)
newmacs.add(mac)
if mac in peerbymacaddress:
peerbymacaddress[mac]['addresses'].append(peer)
peerbymacaddress[mac]['peers'].append(peer)
else:
peerbymacaddress[mac] = {
'hwaddr': mac,
'addresses': [peer],
'peers': [peer],
}
peerdata = peerbymacaddress[mac]
for headline in rsp[1:]:
@@ -190,7 +167,6 @@ def _find_service(service, target):
net4.sendto(smsg.format(bcast, service), (bcast, 1900))
# SSDP by spec encourages responses to spread out over a 3 second interval
# hence we must be a bit more patient
deadline = util.monotonic_time() + 4
r, _, _ = select.select((net4, net6), (), (), 4)
peerdata = {}
while r:
@@ -198,17 +174,9 @@ def _find_service(service, target):
(rsp, peer) = s.recvfrom(9000)
neighutil.refresh_neigh()
_parse_ssdp(peer, rsp, peerdata)
timeout = deadline - util.monotonic_time()
if timeout < 0:
timeout = 0
r, _, _ = select.select((net4, net6), (), (), timeout)
r, _, _ = select.select((net4, net6), (), (), 4)
for nid in peerdata:
for url in peerdata[nid].get('urls', ()):
if url.endswith('/desc.tmpl'):
info = urlopen(url).read()
if '<friendlyName>Athena</friendlyName>' in info:
peerdata[nid]['services'] = ['service:thinkagile-storage']
yield peerdata[nid]
yield peerdata[nid]
def _parse_ssdp(peer, rsp, peerdata):
@@ -226,11 +194,9 @@ def _parse_ssdp(peer, rsp, peerdata):
if code == '200':
if nid in peerdata:
peerdatum = peerdata[nid]
if peer not in peerdatum['addresses']:
peerdatum['addresses'].append(peer)
else:
peerdatum = {
'addresses': [peer],
'peers': [peer],
'hwaddr': mac,
}
peerdata[nid] = peerdatum
+1 -24
View File
@@ -17,17 +17,7 @@
import base64
import json
import msgpack
def deserialize_exc(msg):
excd = msgpack.unpackb(msg, raw=False)
if excd[0] == 'Exception':
return Exception(excd[1])
if excd[0] not in globals():
return Exception('Cannot deserialize: {0}'.format(repr(excd)))
if not issubclass(excd[0], ConfluentException):
return Exception('Cannot deserialize: {0}'.format(repr(excd)))
return globals(excd[0])(*excd[1])
class ConfluentException(Exception):
apierrorcode = 500
@@ -37,10 +27,6 @@ class ConfluentException(Exception):
errstr = ' - '.join((self._apierrorstr, str(self)))
return json.dumps({'error': errstr })
def serialize(self):
return msgpack.packb([self.__class__.__name__, [str(self)]],
use_bin_type=False)
@property
def apierrorstr(self):
if str(self):
@@ -118,24 +104,15 @@ class PubkeyInvalid(ConfluentException):
def __init__(self, text, certificate, fingerprint, attribname, event):
super(PubkeyInvalid, self).__init__(self, text)
self.myargs = (text, certificate, fingerprint, attribname, event)
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': certtxt}
'certificate': base64.b64encode(certificate)}
self.errorbody = json.dumps(bodydata)
def serialize(self):
return msgpack.packb([self.__class__.__name__, self.myargs],
use_bin_type=False)
def get_error_body(self):
return self.errorbody
+8 -50
View File
@@ -19,62 +19,30 @@
# the time comes
import confluent.exceptions as exc
import confluent.log as log
import confluent.messages as msg
import eventlet
import os
import pwd
import socket
import traceback
updatesbytarget = {}
uploadsbytarget = {}
downloadsbytarget = {}
updatepool = eventlet.greenpool.GreenPool(256)
_tracelog = None
def execupdate(handler, filename, updateobj, type, owner, node):
global _tracelog
if type != 'ffdc':
errstr = False
if not os.path.exists(filename):
errstr = '{0} does not appear to exist on {1}'.format(
filename, socket.gethostname())
elif not os.access(filename, os.R_OK):
errstr = '{0} is not readable by confluent on {1} (ensure confluent user or group can access file and parent directories)'.format(
filename, socket.gethostname())
if errstr:
updateobj.handle_progress({'phase': 'error', 'progress': 0.0,
'detail': errstr})
return
if type == 'ffdc' and os.path.isdir(filename):
filename += '/' + node
if 'type' == 'ffdc':
errstr = False
if os.path.exists(filename):
errstr = '{0} already exists on {1}, cannot overwrite'.format(
filename, socket.gethostname())
elif not os.access(os.path.dirname(filename), os.W_OK):
errstr = '{0} directory not writable by confluent user/group on {1}, check the directory and parent directory ownership and permissions'.format(filename, socket.gethostname())
if errstr:
updateobj.handle_progress({'phase': 'error', 'progress': 0.0,
'detail': errstr})
return
def execupdate(handler, filename, updateobj, type):
if not os.path.exists(filename):
errstr = '{0} does not appear to exist on {1}'.format(
filename, socket.gethostname())
updateobj.handle_progress({'phase': 'error', 'progress': 0.0,
'detail': errstr})
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:
pwent = pwd.getpwnam(owner)
os.chown(filename, pwent.pw_uid, pwent.pw_gid)
updateobj.handle_progress({'phase': completion, 'progress': 100.0})
except exc.PubkeyInvalid as pi:
errstr = 'Certificate mismatch detected, does not match value in ' \
@@ -82,28 +50,23 @@ def execupdate(handler, filename, updateobj, type, owner, node):
updateobj.handle_progress({'phase': 'error', 'progress': 0.0,
'detail': errstr})
except Exception as e:
if _tracelog is None:
_tracelog = log.Logger('trace')
_tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event, event=log.Events.stacktrace)
updateobj.handle_progress({'phase': 'error', 'progress': 0.0,
'detail': str(e)})
class Updater(object):
def __init__(self, node, handler, filename, tenant=None, name=None,
bank=None, type='firmware', owner=None):
bank=None, type='firmware'):
self.bank = bank
self.node = node
self.phase = 'initializing'
self.detail = ''
self.percent = 0.0
self.updateproc = updatepool.spawn(execupdate, handler, filename,
self, type, owner, node)
self, type)
if type == 'firmware':
myparty = updatesbytarget
elif type == 'mediaupload':
myparty = uploadsbytarget
elif type == 'ffdc':
myparty = downloadsbytarget
if (node, tenant) not in myparty:
myparty[(node, tenant)] = {}
if name is None:
@@ -133,8 +96,6 @@ def remove_updates(nodes, tenant, element, type='firmware'):
upid = element[-1]
if type == 'firmware':
myparty = updatesbytarget
elif type == 'ffdc':
myparty = downloadsbytarget
else:
myparty = uploadsbytarget
for node in nodes:
@@ -154,9 +115,6 @@ def list_updates(nodes, tenant, element, type='firmware'):
if type == 'mediaupload':
myparty = uploadsbytarget
verb = 'upload'
elif type == 'ffdc':
verb = 'download'
myparty = downloadsbytarget
else:
myparty = updatesbytarget
verb = 'update'

Some files were not shown because too many files have changed in this diff Show More