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

Compare commits

..

2 Commits

Author SHA1 Message Date
Jarrod Johnson 4f83d08565 Add discovery handler for ThinkAgile storage 2019-08-22 14:44:24 -04:00
Jarrod Johnson e37db1e805 Amend the message on collective failure 2018-08-17 16:46:42 -04:00
110 changed files with 7461 additions and 7447 deletions
+1 -12
View File
@@ -35,9 +35,6 @@ 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')
@@ -51,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()
@@ -68,7 +63,7 @@ else:
def print_current():
if options.diff:
grouped.print_deviants(skipmodal=options.skipcommon, count=options.count,
reverse=options.reverse, basenode=options.base)
reverse=options.reverse)
else:
grouped.print_all(skipmodal=options.skipcommon,
count=options.count,
@@ -85,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:
+123 -177
View File
@@ -2,7 +2,7 @@
# 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:
@@ -90,11 +89,6 @@ netserver = None
laststate = {}
class BailOut(Exception):
def __init__(self, errorcode=0):
self.errorcode = errorcode
def print_help():
print("confetty provides a filesystem like interface to confluent. "
"Navigation is done using the same commands as would be used in a "
@@ -359,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':
@@ -440,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
@@ -488,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))
@@ -504,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):
@@ -593,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())
@@ -625,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})
@@ -823,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
@@ -855,6 +823,45 @@ def server_connect():
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,
@@ -863,147 +870,86 @@ def check_power_state():
return
if sys.stdout.isatty():
import readline
def main():
global inconsole
try:
server_connect()
except EOFError, KeyboardInterrupt:
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:
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 (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
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/env python
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/env python
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/env python
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()
+3 -18
View File
@@ -17,7 +17,6 @@
__author__ = 'alin37'
from getpass import getpass
import optparse
import os
import signal
@@ -48,8 +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')
(options, args) = argparser.parse_args()
@@ -70,24 +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
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':
@@ -126,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)
-3
View File
@@ -35,9 +35,6 @@ 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 '
+54 -93
View File
@@ -19,7 +19,6 @@
import os
import signal
import optparse
import shlex
import sys
try:
@@ -43,13 +42,11 @@ 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 '
'that are non default')
argparser.add_option('-b', '--batch', dest='batch', metavar='settings.batch',
default=False, help='Provide settings in a batch file')
argparser.add_option('-d', '--detail', dest='detail',
action='store_true', default=False,
help='Provide verbose information as available, such as '
@@ -63,11 +60,6 @@ argparser.add_option('-a', '--advanced', dest='advanced',
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')
(options, args) = argparser.parse_args()
cfgpaths = {
@@ -121,94 +113,63 @@ def _assign_value():
assignment[key] = value
def parse_config_line(arguments):
global setmode, forceset, key, value, needval, candidate, path, attrib
for param in arguments:
if param == 'show':
continue # forgive muscle memory of pasu users
if param == 'set':
for param in args[1:]:
if param == 'show':
continue # forgive muscle memory of pasu users
if param == 'set':
setmode = True
forceset = True
continue
if needval:
key = needval
value = param
_assign_value()
continue
if '=' in param or param[-1] == ':' or forceset:
if setmode is None:
setmode = True
forceset = True
continue
if needval:
key = needval
needval = None
value = param
if setmode != True:
bailout('Cannot do set and query in same command')
if '=' in param:
key, _, value = param.partition('=')
_assign_value()
continue
if '=' in param or param[-1] == ':' or forceset:
if setmode is None:
setmode = True
if setmode != True:
bailout('Cannot do set and query in same command')
if '=' in param:
key, _, value = param.partition('=')
_assign_value()
elif param[-1] == ':':
needval = param[:-1]
else:
needval = param
elif param[-1] == ':':
needval = param[:-1]
else:
if setmode is None:
setmode = False
if setmode != False:
bailout('Cannot do set and query in same command')
if '.' not in param:
matchedparms = False
for candidate in cfgpaths:
if candidate.startswith('{0}.'.format(param)):
matchedparms = True
if not options.exclude:
path, attrib = cfgpaths[candidate]
path = '/noderange/{0}/{1}'.format(noderange, path)
if path not in queryparms:
queryparms[path] = {}
queryparms[path][attrib] = candidate
else:
try:
del queryparms[path]
except KeyError:
pass
if not matchedparms:
printsys.append(param)
elif param not in cfgpaths:
needval = param
else:
if setmode is None:
setmode = False
if setmode != False:
bailout('Cannot do set and query in same command')
if '.' not in param:
matchedparms = False
for candidate in cfgpaths:
if candidate.startswith('{0}.'.format(param)):
matchedparms = True
if not options.exclude:
path, attrib = cfgpaths[candidate]
path = '/noderange/{0}/{1}'.format(noderange, path)
if path not in queryparms:
queryparms[path] = {}
queryparms[path][attrib] = candidate
else:
try:
del queryparms[path]
except KeyError:
pass
if not matchedparms:
printsys.append(param)
else:
path, attrib = cfgpaths[param]
path = '/noderange/{0}/{1}'.format(noderange, path)
if path not in queryparms:
queryparms[path] = {}
queryparms[path][attrib] = param
if options.batch:
printsys = []
argfile = open(options.batch, 'r')
argset = argfile.readline()
while argset:
try:
argset = argset[:argset.index('#')]
except ValueError:
pass
argset = argset.strip()
if argset:
parse_config_line(shlex.split(argset))
argset = argfile.readline()
else:
parse_config_line(args[1:])
elif param not in cfgpaths:
printsys.append(param)
else:
path, attrib = cfgpaths[param]
path = '/noderange/{0}/{1}'.format(noderange, path)
if path not in queryparms:
queryparms[path] = {}
queryparms[path][attrib] = param
session = client.Command()
rcode = 0
if options.restoredefault and 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:
sys.stderr.write(
'Unrecognized component to restore defaults: {0}\n'.format(
options.restoredefault))
sys.exit(1)
if setmode:
if options.exclude:
sys.stderr.write('Cannot use exclude and assign at the same time\n')
@@ -253,11 +214,11 @@ else:
if printsys or options.exclude:
if printsys == 'all':
printsys = []
if (options.comparedefault or printsys == []) and not options.advanced:
if printsys == [] and not options.advanced:
path = '/noderange/{0}/configuration/system/all'.format(noderange)
else:
path = '/noderange/{0}/configuration/system/advanced'.format(
noderange)
rcode = client.print_attrib_path(path, session, printsys,
options)
sys.exit(rcode)
sys.exit(rcode)
+2 -45
View File
@@ -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,45 +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
for node in sortutil.natural_sort(nodes):
if initial:
initial = False
subprocess.call(
['tmux', 'new-session', '-d', '-s',
'nodeconsole_{0}'.format(os.getpid()), '-x', '800', '-y',
'800', '{0} -m 5 start /nodes/{1}/console/session'.format(
confettypath, node)])
else:
subprocess.call(['tmux', 'select-pane', '-t', str(pane)])
subprocess.call(['tmux', 'set-option', 'pane-border-status', 'top'], stderr=null)
pane += 1
subprocess.call(
['tmux', 'split', '-h',
'{0} -m 5 start /nodes/{1}/console/session'.format(
confettypath, node)])
subprocess.call(['tmux', 'select-layout', 'tiled'], stdout=null)
subprocess.call(['tmux', 'select-pane', '-t', '0'])
subprocess.call(['tmux', 'set-option', 'pane-border-status', 'top'], stderr=null)
os.execlp('tmux', 'tmux', 'attach', '-t', 'nodeconsole_{0}'.format(
os.getpid()))
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]))
+37 -111
View File
@@ -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,10 +181,8 @@ def clear_discovery(options, session):
else:
print(repr(res))
def list_matching_macs(options, session, node=None):
def list_matching_macs(options, session):
path = '/discovery/'
if node:
path += 'by-node/{0}/'.format(node)
if options.model:
path += 'by-model/{0}/'.format(options.model)
if options.serial:
@@ -263,25 +205,21 @@ def list_matching_macs(options, session, node=None):
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)
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)
matches = list_matching_macs(options, session)
if not matches:
sys.stderr.write("No matching discovery candidates found\n")
sys.exit(1)
@@ -292,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():
@@ -333,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()
@@ -349,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()
+4 -10
View File
@@ -1,7 +1,7 @@
#!/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,8 +33,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 [options] noderange [clear]")
@@ -65,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:
@@ -80,10 +77,7 @@ 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:
@@ -104,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))
+7 -14
View File
@@ -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,8 +55,7 @@ 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')
(options, args) = argparser.parse_args()
@@ -67,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()
@@ -151,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()
+3 -18
View File
@@ -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':
-62
View File
@@ -1,62 +0,0 @@
#!/usr/bin/env python
# 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()
-63
View File
@@ -1,63 +0,0 @@
#!/usr/bin/env python
# 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()
+3 -13
View File
@@ -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)
+9 -19
View File
@@ -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)
-146
View File
@@ -1,146 +0,0 @@
#!/usr/bin/python
# 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>]")
(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
if not os.path.exists(filename):
sys.stderr.write('Unable to locate requested file {0}\n'.format(
filename))
sys.exit(404)
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)
if not os.path.exists(filename):
sys.stderr.write('Unable to locate specified directory {0}\n'.format(
filename))
sys.exit(404)
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))
else:
sys.stderr.write('{0}: {1}', 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:
delete_license(session, delete)
else:
show_licenses(session)
except KeyboardInterrupt:
print('')
sys.exit(exitcode)
+1 -1
View File
@@ -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)
+1 -2
View File
@@ -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
-52
View File
@@ -1,52 +0,0 @@
#!/usr/bin/env python
# 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))
-153
View File
@@ -1,153 +0,0 @@
#!/usr/bin/env python
# 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('-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
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'].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] += 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] += 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()
+4 -12
View File
@@ -39,13 +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')
# 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()
@@ -87,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':
@@ -95,10 +93,7 @@ def run():
pernodeout[node] = []
pernodeout[node].append(data)
else:
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']
@@ -112,10 +107,7 @@ def run():
run_cmdv(node, cmdv, all, pipedesc)
for node in sortutil.natural_sort(pernodeout):
for line in pernodeout[node]:
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)
+6 -9
View File
@@ -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(
@@ -150,7 +148,7 @@ def sensorpass(showout=True, appendtime=False):
showval += ' @' + time.strftime(
'%Y-%m-%dT%H:%M:%S')
print(u'{0}: {1}:{2}'.format(
node, sensedata['name'], showval).encode('utf8'))
node, sensedata['name'], showval).encode('utf-8'))
sys.stdout.flush()
return resultdata
@@ -158,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.') +
@@ -185,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():
@@ -223,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)
+3 -3
View File
@@ -53,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()
@@ -65,4 +65,4 @@ else:
bootmode = 'uefi'
sys.exit(session.simple_noderange_command(noderange, '/boot/nextdevice', bootdev,
bootmode=bootmode,
persistent=options.persist))
persistent=options.persist))
+3 -11
View File
@@ -39,13 +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')
# 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()
@@ -96,10 +94,7 @@ def run():
pernodeout[node] = []
pernodeout[node].append(data)
else:
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']
@@ -113,10 +108,7 @@ def run():
run_cmdv(node, cmdv, all, pipedesc)
for node in sortutil.natural_sort(pernodeout):
for line in pernodeout[node]:
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)
-224
View File
@@ -1,224 +0,0 @@
#!/usr/bin/python
# 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', {}):
if node not in storagebynode:
storagebynode[node] = {'disks': [], 'arrays': [],
'volumes': []}
curr = e['databynode'][node]
storagebynode[node][curr['type'] + 's'].append(curr)
for node in storagebynode:
for disk in sorted(storagebynode[node]['disks'],
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()
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()
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()
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.')
(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()
-147
View File
@@ -1,147 +0,0 @@
#!/usr/bin/python
# 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):
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 = {}
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')
(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)
if __name__ == '__main__':
main()
-167
View File
@@ -1,167 +0,0 @@
#!/usr/bin/env python
# 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
+13 -92
View File
@@ -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
@@ -40,58 +34,6 @@ _attraliases = {
}
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
if 'errorcode' in res:
@@ -139,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)
@@ -325,19 +265,14 @@ 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:
try:
replace = raw_input(
"MISMATCHED CERTIFICATE DATA, ACCEPT NEW? (y/n):")
except NameError:
replace = input(
"MISMATCHED CERTIFICATE DATA, ACCEPT NEW? (y/n):")
replace = raw_input(
"MISMATCHED CERTIFICATE DATA, ACCEPT NEW? (y/n):")
if replace not in ('y', 'Y'):
raise Exception("BAD CERTIFICATE")
cprint('Adding new key for %s:%s' % (server, port))
@@ -376,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() + '.'):
@@ -401,7 +336,7 @@ def print_attrib_path(path, session, requestargs, options, rename=None):
for node in sorted(res['databynode']):
for attr, val in sorted(
res['databynode'][node].items(),
key=lambda k: k[1].get('sortid', k[0]) if isinstance(k[1], dict) else k[0]):
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
@@ -472,12 +407,9 @@ def print_attrib_path(path, session, requestargs, options, rename=None):
(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:
@@ -486,14 +418,11 @@ def print_attrib_path(path, session, requestargs, options, rename=None):
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:
@@ -564,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:
@@ -597,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:
@@ -663,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]
-2
View File
@@ -34,8 +34,6 @@ def decodestr(value):
ret = value.decode('cp437')
except UnicodeDecodeError:
ret = value
except AttributeError:
return value
return ret
def unicode_dictvalues(dictdata):
-252
View File
@@ -36,256 +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=${#CMPARGS[@]}
if [ "${COMP_WORDS[-1]}" == '' ]; then
NUMARGS=$((NUMARGS+1))
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[-1]}))
fi
if [ $NUMARGS -lt 3 ]; then
_confluent_nr_completion
return;
fi
}
_confluent_nodeidentify_completion()
{
COMP_CANDIDATES=("on,off -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[-1]}))
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[-1]}))
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[-1]}))
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[-1]}))
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[-1]}))
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[-1]}
INPUT=${INPUT##*,-}
INPUT=${INPUT##*,}
INPUT=${INPUT##*@}
PREFIX=""
if [ "$INPUT" != "${COMP_WORDS[-1]}" ]; then
PREFIX=${COMP_WORDS[-1]}
PREFIX=$(echo $PREFIX | sed -e 's/,[^,@-]*$/,/' -e 's/,-[^,@]*$/,-/' -e 's/@[^,@]*/@/')
fi
COMPREPLY=($(compgen -W "$(nodelist | sed -e s/^/$PREFIX/)" -- "${COMP_WORDS[-1]}"))
}
_confluent_nr_completion()
{
CMPARGS=($COMP_LINE)
NUMARGS=${#CMPARGS[@]}
if [ "${COMP_WORDS[-1]}" == '' ]; then
NUMARGS=$((NUMARGS+1))
fi
_confluent_get_args
if [ $NUMARGS -gt 2 ]; then
return;
fi
INPUT=${COMP_WORDS[-1]}
INPUT=${INPUT##*,-}
INPUT=${INPUT##*,}
INPUT=${INPUT##*@}
PREFIX=""
if [ "$INPUT" != "${COMP_WORDS[-1]}" ]; then
PREFIX=${COMP_WORDS[-1]}
PREFIX=$(echo $PREFIX | sed -e 's/,[^,@-]*$/,/' -e 's/,-[^,@]*$/,-/' -e 's/@[^,@]*/@/')
fi
#COMPREPLY=($(compgen -W "$(confetty show /nodegroups|sed -e 's/\///' -e s/^/$PREFIX/;nodelist | sed -e s/^/$PREFIX/)" -- "${COMP_WORDS[-1]}"))
COMPREPLY=($(compgen -W "$(confetty show /nodegroups|sed -e 's/\///' -e s/^/$PREFIX/;nodelist | sed -e s/^/$PREFIX/)" -- "${COMP_WORDS[-1]}"))
}
_confluent_ng_completion()
{
_confluent_get_args
if [ $NUMARGS -gt 2 ]; then
return;
fi
INPUT=${COMP_WORDS[-1]}
INPUT=${INPUT##*,-}
INPUT=${INPUT##*,}
INPUT=${INPUT##*@}
PREFIX=""
if [ "$INPUT" != "${COMP_WORDS[-1]}" ]; then
PREFIX=${COMP_WORDS[-1]}
PREFIX=$(echo $PREFIX | sed -e 's/,[^,@-]*$/,/' -e 's/,-[^,@]*$/,-/' -e 's/@[^,@]*/@/')
fi
COMPREPLY=($(compgen -W "$(confetty show /nodegroups|sed -e 's/\///' -e s/^/$PREFIX/)" -- "${COMP_WORDS[-1]}"))
}
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`
-12
View File
@@ -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,12 +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)
@@ -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 -10
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
@@ -30,14 +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.
## EXAMPLES
* Showing the current IP configuration of noderange BMC/IMM/XCC:
+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
@@ -75,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`:
+1 -1
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
@@ -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
@@ -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)
-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`:
-3
View File
@@ -36,9 +36,6 @@ 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`
+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`
-29
View File
@@ -1,29 +0,0 @@
nodesupport(8) -- Utilities for interacting with vendor support
=================================================================
## SYNOPSIS
`nodesupport <noderange> servicedata <directory or filename>`
## DESCRIPTION
`nodesupport` provides capabilities associated with interactiong 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.
## 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`
+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/*
+5 -6
View File
@@ -66,9 +66,8 @@ def join_collective(server, invitation):
'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))))
print(res.get('collective',
{'status': 'Unknown response: ' + repr(res)})['status'])
def show_collective():
@@ -101,11 +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')
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()
-50
View File
@@ -1,50 +0,0 @@
import shutil
import socket
import subprocess
import tempfile
def get_openssl_conf_location():
# CentOS/RHAT
return '/etc/pki/tls/openssl.cnf'
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()
-64
View File
@@ -1,64 +0,0 @@
#!/bin/bash
cd `dirname $0`
PKGNAME=$(basename $(pwd))
DPKGNAME=$(basename $(pwd) | sed -e s/_/-/)
OPKGNAME=$(basename $(pwd) | sed -e s/_/-/)
if grep wheezy /etc/os-release; then
DPKGNAME=python-$DPKGNAME
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
python setup.py sdist > /dev/null 2>&1
py2dsc 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/' debian/control
else
sed -i 's/^\(Depends:.*\)/\1, confluent-client, python-lxml, python-eficompressor, python-pycryptodome, python-dateutil/' 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 -7
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.
@@ -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
+51 -132
View File
@@ -23,11 +23,9 @@ 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 confluent.userutil as userutil
try:
import PAM
except ImportError:
@@ -41,59 +39,7 @@ _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
@@ -166,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.
@@ -228,70 +158,59 @@ def check_user_passphrase(name, passphrase, operation=None, element=None, tenant
# but here there's no need for that
eventlet.sleep(0.5)
credobj = Credentials(user, passphrase)
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
if (user, tenant) in _passcache:
if hashlib.sha256(passphrase).digest() == _passcache[(user, tenant)]:
return authorize(user, element, tenant, operation=operation)
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)]
return None
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
# 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)
try:
pammy = PAM.pam()
pammy.start(_pamservice, user, credobj.pam_conv)
pammy.authenticate()
pammy.acct_mgmt()
del pammy
_passcache[(user, tenant)] = hashlib.sha256(passphrase).digest()
return authorize(user, element, tenant, operation, skipuserobj=False)
return authorize(user, element, tenant, skipuserobj=False)
except NameError:
pass
except PAM.error:
pass
eventlet.sleep(0.05) # stall even on test for existence of a username
if credobj.haspam:
return None
if (user, tenant) in _passcache:
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)]
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)] = passphrase
return authorize(user, element, tenant)
return None
+28 -124
View File
@@ -18,14 +18,12 @@ 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
try:
import OpenSSL.crypto as crypto
@@ -35,41 +33,38 @@ 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:
with cfginitlock:
tlvdata.recv(remote) # the banner
tlvdata.recv(remote) # authpassed... 0..
if name is None:
@@ -82,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:
@@ -99,22 +88,13 @@ 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)
@@ -154,22 +134,11 @@ def connect_to_leader(cert=None, name=None, leader=None):
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 has disappeared, restarting '
'collective membership', '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):
@@ -322,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:
@@ -353,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, '
@@ -367,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 follower.isAlive()):
# 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})
@@ -411,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,
@@ -423,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()
@@ -484,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
@@ -525,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():
@@ -560,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
@@ -579,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,13 +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.'),
'validvalues': ('ssh', 'ipmi'),
'the managed node.')
},
# 'virtualization.host': {
# 'description': ('Hypervisor where this node does/should reside'),
+142 -385
View File
@@ -1,7 +1,7 @@
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.
@@ -82,7 +82,6 @@ _dirtylock = threading.RLock()
_leaderlock = gthread.RLock()
_synclock = threading.RLock()
_rpclock = gthread.RLock()
_initlock = gthread.RLock()
_followerlocks = {}
_config_areas = ('nodegroups', 'nodes', 'usergroups', 'users')
tracelog = None
@@ -95,11 +94,8 @@ _hasquorum = True
_attraliases = {
'bmc': 'hardwaremanagement.manager',
'bmcuser': 'secret.hardwaremanagementuser',
'switchuser': 'secret.hardwaremanagementuser',
'bmcpass': 'secret.hardwaremanagementpassword',
'switchpass': 'secret.hardwaremanagementpassword',
}
_validroles = ('Administrator', 'Operator', 'Monitor')
def _mkpath(pathname):
try:
@@ -145,13 +141,10 @@ def _parse_key(keydata, password=None):
if keydata.startswith('*unencrypted:'):
return base64.b64decode(keydata[13:])
elif password:
cryptbits = [base64.b64decode(x)
salt, iv, crypt, hmac = [base64.b64decode(x)
for x in keydata.split('!')]
salt, iv, crypt, hmac = cryptbits[:4]
privkey, integkey = _derive_keys(password, salt)
if len(cryptbits) > 4:
integkey = None
return decrypt_value(cryptbits[1:], privkey, integkey)
return decrypt_value([iv, crypt, hmac], privkey, integkey)
raise(exc.LockedCredentials(
"Passphrase protected secret requires password"))
@@ -160,7 +153,7 @@ def _format_key(key, password=None):
if password is not None:
salt = os.urandom(32)
privkey, integkey = _derive_keys(password, salt)
cval = crypt_value(key, key=privkey) # , integritykey=integkey)
cval = crypt_value(key, key=privkey, integritykey=integkey)
return {"passphraseprotected": (salt,) + cval}
else:
return {"unencryptedvalue": key}
@@ -173,24 +166,6 @@ def _do_notifier(cfg, watcher, callback):
logException()
def _rpc_master_del_usergroup(tenant, name):
ConfigManager(tenant).del_usergroup(name)
def _rpc_del_usergroup(tenant, name):
ConfigManager(tenant)._true_del_usergroup(name)
def _rpc_master_set_usergroup(tenant, name, attributemap):
ConfigManager(tenant).set_user(name, attributemap)
def _rpc_set_usergroup(tenant, name, attributemap):
ConfigManager(tenant)._true_set_user(name, attributemap)
def _rpc_master_set_user(tenant, name, attributemap):
ConfigManager(tenant).set_user(name, attributemap)
@@ -203,14 +178,6 @@ def _rpc_master_set_node_attributes(tenant, attribmap, autocreate):
ConfigManager(tenant).set_node_attributes(attribmap, autocreate)
def _rpc_master_rename_nodes(tenant, renamemap):
ConfigManager(tenant).rename_nodes(renamemap)
def _rpc_master_rename_nodegroups(tenant, renamemap):
ConfigManager(tenant).rename_nodegroups(renamemap)
def _rpc_master_clear_node_attributes(tenant, nodes, attributes):
ConfigManager(tenant).clear_node_attributes(nodes, attributes)
@@ -242,19 +209,9 @@ def _rpc_del_user(tenant, name):
def _rpc_master_create_user(tenant, *args):
ConfigManager(tenant).create_user(*args)
def _rpc_master_create_usergroup(tenant, *args):
ConfigManager(tenant).create_usergroup(*args)
def _rpc_create_user(tenant, *args):
ConfigManager(tenant)._true_create_user(*args)
def _rpc_create_usergroup(tenant, *args):
ConfigManager(tenant)._true_create_usergroup(*args)
def _rpc_master_del_groups(tenant, groups):
ConfigManager(tenant).del_groups(groups)
@@ -274,14 +231,6 @@ def _rpc_set_node_attributes(tenant, attribmap, autocreate):
ConfigManager(tenant)._true_set_node_attributes(attribmap, autocreate)
def _rpc_rename_nodes(tenant, renamemap):
ConfigManager(tenant)._true_rename_nodes(renamemap)
def _rpc_rename_nodegroups(tenant, renamemap):
ConfigManager(tenant)._true_rename_nodegroups(renamemap)
def _rpc_set_group_attributes(tenant, attribmap, autocreate):
ConfigManager(tenant)._true_set_group_attributes(attribmap, autocreate)
@@ -336,9 +285,9 @@ def logException():
event=confluent.log.Events.stacktrace)
def _do_add_watcher(watcher, added, configmanager, renamed=()):
def _do_add_watcher(watcher, added, configmanager):
try:
watcher(added=added, deleting=(), renamed=renamed, configmanager=configmanager)
watcher(added=added, deleting=[], configmanager=configmanager)
except Exception:
logException()
@@ -359,11 +308,11 @@ def init_masterkey(password=None, autogen=True):
if cfgn:
_masterintegritykey = _get_protected_key(cfgn, password,
'master_integrity_key')
#elif autogen:
# _masterintegritykey = os.urandom(64)
# set_global('master_integrity_key', _format_key(
# _masterintegritykey,
# password=password))
elif autogen:
_masterintegritykey = os.urandom(64)
set_global('master_integrity_key', _format_key(
_masterintegritykey,
password=password))
def _push_rpc(stream, payload):
@@ -376,34 +325,25 @@ def _push_rpc(stream, payload):
def decrypt_value(cryptvalue,
key=None,
integritykey=None):
# for future reference, if cryptvalue len == 3, then cbc+hmac, 4 includes version
iv, cipherdata, hmac = cryptvalue[:3]
iv, cipherdata, hmac = cryptvalue
if key is None and integritykey is None:
if _masterkey is None:
if _masterkey is None or _masterintegritykey is None:
init_masterkey(autogen=False)
key = _masterkey
integritykey = _masterintegritykey
if len(cryptvalue) == 3:
check_hmac = HMAC.new(integritykey, cipherdata, SHA256).digest()
if hmac != check_hmac:
check_hmac = HMAC.new(integritykey, cipherdata + iv, SHA256).digest()
if hmac != check_hmac:
raise Exception("bad HMAC value on crypted value")
decrypter = AES.new(key, AES.MODE_CBC, iv)
value = decrypter.decrypt(cipherdata)
padsize = ord(value[-1])
pad = value[-padsize:]
# Note that I cannot grasp what could be done with a subliminal
# channel in padding in this case, but check the padding anyway
for padbyte in pad:
if ord(padbyte) != padsize:
raise Exception("bad padding in encrypted value")
return value[0:-padsize]
else:
decrypter = AES.new(key, AES.MODE_GCM, nonce=iv)
value = decrypter.decrypt(cipherdata)
decrypter.verify(hmac)
return value
check_hmac = HMAC.new(integritykey, cipherdata, SHA256).digest()
if hmac != check_hmac:
raise Exception("bad HMAC value on crypted value")
decrypter = AES.new(key, AES.MODE_CBC, iv)
value = decrypter.decrypt(cipherdata)
padsize = ord(value[-1])
pad = value[-padsize:]
# Note that I cannot grasp what could be done with a subliminal
# channel in padding in this case, but check the padding anyway
for padbyte in pad:
if ord(padbyte) != padsize:
raise Exception("bad padding in encrypted value")
return value[0:-padsize]
def fixup_attribute(attrname, attrval):
@@ -455,18 +395,22 @@ def crypt_value(value,
# encrypt given value
# PKCS7 is the padding scheme to employ, if no padded needed, pad with 16
# check HMAC prior to attempting decrypt
hmac = None
if key is None:
if _masterkey is None:
if key is None or integritykey is None:
if _masterkey is None or _masterintegritykey is None:
init_masterkey()
key = _masterkey
iv = os.urandom(12)
crypter = AES.new(key, AES.MODE_GCM, nonce=iv)
integritykey = _masterintegritykey
iv = os.urandom(16)
crypter = AES.new(key, AES.MODE_CBC, iv)
neededpad = 16 - (len(value) % 16)
pad = chr(neededpad) * neededpad
value += pad
try:
cryptval, hmac = crypter.encrypt_and_digest(value)
cryptval = crypter.encrypt(value)
except TypeError:
cryptval, hmac = crypter.encrypt_and_digest(value.encode('utf-8'))
return iv, cryptval, hmac, '\x02'
cryptval = crypter.encrypt(value.encode('utf-8'))
hmac = HMAC.new(integritykey, cryptval, SHA256).digest()
return iv, cryptval, hmac
def _load_dict_from_dbm(dpath, tdb):
@@ -679,7 +623,7 @@ def rollback_clear():
_cfgstore = _oldcfgstore
_oldtxcount = 0
_oldcfgstore = None
ConfigManager.wait_for_sync(True)
ConfigManager._bg_sync_to_file()
def clear_configuration():
@@ -699,13 +643,12 @@ def commit_clear():
global _oldcfgstore
_oldcfgstore = None
_oldtxcount = 0
with _synclock:
todelete = ('transactioncount', 'globals', 'collective') + _config_areas
for cfg in todelete:
try:
os.remove(os.path.join(ConfigManager._cfgdir, cfg))
except OSError as oe:
pass
todelete = _config_areas + ('globals', 'collective', 'transactioncount')
for cfg in todelete:
try:
os.remove(os.path.join(ConfigManager._cfgdir, cfg))
except OSError as oe:
pass
ConfigManager.wait_for_sync(True)
ConfigManager._bg_sync_to_file()
@@ -715,43 +658,41 @@ cfgleader = None
def follow_channel(channel):
global _txcount
global _hasquorum
try:
stop_leading()
stop_following(channel)
lh = StreamHandler(channel)
stop_leading()
stop_following(channel)
lh = StreamHandler(channel)
msg = lh.get_next_msg()
while msg:
sz = struct.unpack('!Q', msg)[0]
if sz != 0:
rpc = ''
while len(rpc) < sz:
nrpc = channel.recv(sz - len(rpc))
if not nrpc:
raise Exception('Truncated message error')
rpc += nrpc
rpc = cPickle.loads(rpc)
if 'txcount' in rpc:
_txcount = rpc['txcount']
if 'function' in rpc:
try:
globals()[rpc['function']](*rpc['args'])
except Exception as e:
print(repr(e))
if 'xid' in rpc and rpc['xid']:
if rpc.get('exc', None):
_pendingchangesets[rpc['xid']].send_exception(rpc['exc'])
else:
_pendingchangesets[rpc['xid']].send()
if 'quorum' in rpc:
_hasquorum = rpc['quorum']
_push_rpc(channel, b'') # use null as ACK
msg = lh.get_next_msg()
while msg:
sz = struct.unpack('!Q', msg)[0]
if sz != 0:
rpc = ''
while len(rpc) < sz:
nrpc = channel.recv(sz - len(rpc))
if not nrpc:
raise Exception('Truncated message error')
rpc += nrpc
rpc = cPickle.loads(rpc)
if 'txcount' in rpc:
_txcount = rpc['txcount']
if 'function' in rpc:
try:
globals()[rpc['function']](*rpc['args'])
except Exception as e:
print(repr(e))
if 'xid' in rpc and rpc['xid']:
if rpc.get('exc', None):
_pendingchangesets[rpc['xid']].send_exception(rpc['exc'])
else:
_pendingchangesets[rpc['xid']].send()
if 'quorum' in rpc:
_hasquorum = rpc['quorum']
_push_rpc(channel, b'') # use null as ACK
msg = lh.get_next_msg()
finally:
# mark the connection as broken
if cfgstreams:
stop_following(None)
else:
stop_following(True)
# mark the connection as broken
if cfgstreams:
stop_following(None)
else:
stop_following(True)
def add_collective_member(name, address, fingerprint):
@@ -1006,46 +947,38 @@ class ConfigManager(object):
_nodecollwatchers = {}
_notifierids = {}
@property
def _cfgstore(self):
if self.tenant is None:
return _cfgstore['main']
return _cfgstore['tenant'][self.tenant]
def __init__(self, tenant, decrypt=False, username=None):
global _cfgstore
with _initlock:
if _cfgstore is None:
init()
self.decrypt = decrypt
self.current_user = username
if tenant is None:
self.tenant = None
if 'main' not in _cfgstore:
_cfgstore['main'] = {}
self._bg_sync_to_file()
if 'nodegroups' not in self._cfgstore: # This can happen during a clear... it seams... and if so it messes up...
self._cfgstore['nodegroups'] = {'everything': {'nodes': set()}}
_mark_dirtykey('nodegroups', 'everything', self.tenant)
self._bg_sync_to_file()
if 'nodes' not in self._cfgstore:
self._cfgstore['nodes'] = {}
self._bg_sync_to_file()
return
elif 'tenant' not in _cfgstore:
_cfgstore['tenant'] = {tenant: {}}
if _cfgstore is None:
init()
self.decrypt = decrypt
self.current_user = username
if tenant is None:
self.tenant = None
if 'main' not in _cfgstore:
_cfgstore['main'] = {}
self._bg_sync_to_file()
elif tenant not in _cfgstore['tenant']:
_cfgstore['tenant'][tenant] = {}
self._bg_sync_to_file()
self.tenant = tenant
self._cfgstore = _cfgstore['main']
if 'nodegroups' not in self._cfgstore:
self._cfgstore['nodegroups'] = {'everything': {}}
_mark_dirtykey('nodegroups', 'everything', self.tenant)
self._cfgstore['nodegroups'] = {'everything': {'nodes': set()}}
self._bg_sync_to_file()
if 'nodes' not in self._cfgstore:
self._cfgstore['nodes'] = {}
self._bg_sync_to_file()
return
elif 'tenant' not in _cfgstore:
_cfgstore['tenant'] = {tenant: {}}
self._bg_sync_to_file()
self.wait_for_sync()
elif tenant not in _cfgstore['tenant']:
_cfgstore['tenant'][tenant] = {}
self._bg_sync_to_file()
self.tenant = tenant
self._cfgstore = _cfgstore['tenant'][tenant]
if 'nodegroups' not in self._cfgstore:
self._cfgstore['nodegroups'] = {'everything': {}}
if 'nodes' not in self._cfgstore:
self._cfgstore['nodes'] = {}
self._bg_sync_to_file()
def get_collective_member(self, name):
return get_collective_member(name)
@@ -1092,32 +1025,24 @@ class ConfigManager(object):
raise Exception('Invalid Expression')
for node in nodes:
try:
currvals = [self._cfgstore['nodes'][node][attribute]['value']]
currval = self._cfgstore['nodes'][node][attribute]['value']
except KeyError:
# Let's treat 'not set' as being an empty string for this path
currvals = list(
[self._cfgstore['nodes'][node][x].get('value', '')
for x in fnmatch.filter(self._cfgstore['nodes'][node], attribute)])
currvals.append('')
for currval in currvals:
if exmatch:
if yieldmatches:
if exmatch.search(currval):
yield node
break
else:
if not exmatch.search(currval):
yield node
break
currval = ''
if exmatch:
if yieldmatches:
if exmatch.search(currval):
yield node
else:
if yieldmatches:
if match == currval:
yield node
break
else:
if match != currval:
yield node
break
if not exmatch.search(currval):
yield node
else:
if yieldmatches:
if match == currval:
yield node
else:
if match != currval:
yield node
def filter_nodenames(self, expression, nodes=None):
"""Filter nodenames by regular expression
@@ -1219,12 +1144,6 @@ class ConfigManager(object):
except KeyError:
return []
def list_usergroups(self):
try:
return list(self._cfgstore['usergroups'])
except KeyError:
return []
def get_user(self, name):
"""Get user information from DB
@@ -1262,46 +1181,12 @@ class ConfigManager(object):
:param groupname: the name of teh group to modify
:param attributemap: The mapping of keys to values to set
"""
if cfgleader:
return exec_on_leader('_rpc_master_set_usergroup', self.tenant,
groupname, attributemap)
if cfgstreams:
exec_on_followers('_rpc_set_usergroup', self.tenant, groupname,
attributemap)
self._true_set_usergroup(groupname, attributemap)
def _true_set_usergroup(self, groupname, attributemap):
for attribute in attributemap:
if attribute == 'role':
therole = None
for candrole in _validroles:
if candrole.lower().startswith(attributemap[attribute].lower()):
therole = candrole
if therole not in _validroles:
raise ValueError(
'Unrecognized role "{0}" (valid roles: {1})'.format(attributemap[attribute], ','.join(_validroles)))
attributemap[attribute] = therole
self._cfgstore['usergroups'][groupname][attribute] = attributemap[attribute]
self._cfgstore['usergroups'][attribute] = attributemap[attribute]
_mark_dirtykey('usergroups', groupname, self.tenant)
self._bg_sync_to_file()
def create_usergroup(self, groupname, role="Administrator"):
"""Create a new user
:param groupname: The name of the user group
:param role: The role the user should be considered. Can be
"Administrator" or "Technician", defaults to
"Administrator"
"""
if cfgleader:
return exec_on_leader('_rpc_master_create_usergroup', self.tenant,
groupname, role)
if cfgstreams:
exec_on_followers('_rpc_create_usergroup', self.tenant, groupname,
role)
self._true_create_usergroup(groupname, role)
def _true_create_usergroup(self, groupname, role="Administrator"):
if 'usergroups' not in self._cfgstore:
self._cfgstore['usergroups'] = {}
groupname = groupname.encode('utf-8')
@@ -1309,20 +1194,6 @@ class ConfigManager(object):
raise Exception("Duplicate groupname requested")
self._cfgstore['usergroups'][groupname] = {'role': role}
_mark_dirtykey('usergroups', groupname, self.tenant)
self._bg_sync_to_file()
def del_usergroup(self, name):
if cfgleader:
return exec_on_leader('_rpc_master_del_usergroup', self.tenant, name)
if cfgstreams:
exec_on_followers('_rpc_del_usergroup', self.tenant, name)
self._true_del_usergroup(name)
def _true_del_usergroup(self, name):
if name in self._cfgstore['usergroups']:
del self._cfgstore['usergroups'][name]
_mark_dirtykey('usergroups', name, self.tenant)
self._bg_sync_to_file()
def set_user(self, name, attributemap):
"""Set user attribute(s)
@@ -1340,15 +1211,6 @@ class ConfigManager(object):
def _true_set_user(self, name, attributemap):
user = self._cfgstore['users'][name]
for attribute in attributemap:
if attribute == 'role':
therole = None
for candrole in _validroles:
if candrole.lower().startswith(attributemap[attribute].lower()):
therole = candrole
if therole not in _validroles:
raise ValueError(
'Unrecognized role "{0}" (valid roles: {1})'.format(attributemap[attribute], ','.join(_validroles)))
attributemap[attribute] = therole
if attribute == 'password':
salt = os.urandom(8)
#TODO: WORKERPOOL, offload password set to a worker
@@ -1364,9 +1226,9 @@ class ConfigManager(object):
def del_user(self, name):
if cfgleader:
return exec_on_leader('_rpc_master_del_user', self.tenant, name)
return exec_on_leader('_rpc_master_del_user', name)
if cfgstreams:
exec_on_followers('_rpc_del_user', self.tenant, name)
exec_on_followers('_rpc_del_user', name)
self._true_del_user(name)
def _true_del_user(self, name):
@@ -1623,9 +1485,6 @@ class ConfigManager(object):
newattr = _attraliases[attr]
attribmap[group][newattr] = attribmap[group][attr]
del attribmap[group][attr]
if 'noderange' in attribmap[group]:
if len(attribmap[group]) > 1:
raise ValueError('noderange attribute must be set by itself')
for attr in attribmap[group]:
if attr in _attraliases:
newattr = _attraliases[attr]
@@ -1642,20 +1501,17 @@ class ConfigManager(object):
currnodes = list(self.get_nodegroup_attributes(
group, ['nodes']).get('nodes', []))
if attribmap[group][attr].get('prepend', False):
newnodes = noderange.NodeRange(attribmap[group][attr][
'prepend'], config=self).nodes
attribmap[group][attr] = list(
newnodes) + currnodes
newnodes = attribmap[group][attr][
'prepend'].split(',')
attribmap[group][attr] = newnodes + currnodes
elif attribmap[group][attr].get('remove', False):
delnodes = noderange.NodeRange(
attribmap[group][attr]['remove'],
config=self).nodes
delnodes = attribmap[group][attr][
'remove'].split(',')
attribmap[group][attr] = [
x for x in currnodes if x not in delnodes]
if not isinstance(attribmap[group][attr], list):
if type(attribmap[group][attr]) is unicode or type(attribmap[group][attr]) is str:
attribmap[group][attr] = noderange.NodeRange(
attribmap[group][attr], config=self).nodes
attribmap[group][attr]=attribmap[group][attr].split(",")
else:
raise ValueError("nodes attribute on group must be list")
for node in attribmap[group]['nodes']:
@@ -1668,13 +1524,6 @@ class ConfigManager(object):
if group not in self._cfgstore['nodegroups']:
self._cfgstore['nodegroups'][group] = {'nodes': set()}
cfgobj = self._cfgstore['nodegroups'][group]
if 'noderange' in attribmap[group] and attribmap[group]['noderange']:
if cfgobj['nodes']:
raise ValueError('Cannot set both nodes and noderange on group')
if set(cfgobj) - set(['noderange', 'nodes']):
raise ValueError('Cannot set noderange on a group with attributes')
elif 'noderange' in cfgobj and cfgobj['noderange']:
raise ValueError('Attributes cannot be set on a group with a noderange')
for attr in attribmap[group]:
if attr == 'nodes':
newdict = set(attribmap[group][attr])
@@ -1829,7 +1678,7 @@ class ConfigManager(object):
def _true_del_nodes(self, nodes):
if self.tenant in self._nodecollwatchers:
for watcher in self._nodecollwatchers[self.tenant].itervalues():
watcher(added=(), deleting=nodes, renamed=(), configmanager=self)
watcher(added=[], deleting=nodes, configmanager=self)
changeset = {}
for node in nodes:
# set a reserved attribute for the sake of the change notification
@@ -1913,86 +1762,6 @@ class ConfigManager(object):
attribmap[node]['groups'] = []
self.set_node_attributes(attribmap, autocreate=True)
def rename_nodes(self, renamemap):
if cfgleader:
return exec_on_leader('_rpc_master_rename_nodes', self.tenant,
renamemap)
if cfgstreams:
exec_on_followers('_rpc_rename_nodes', self.tenant, renamemap)
self._true_rename_nodes(renamemap)
def _true_rename_nodes(self, renamemap):
oldnames = set(renamemap)
exprmgr = None
currnodes = set(self._cfgstore['nodes'])
missingnodes = oldnames - currnodes
if missingnodes:
raise ValueError(
'The following nodes to rename do not exist: {0}'.format(
','.join(missingnodes)))
newnames = set([])
for name in renamemap:
newnames.add(renamemap[name])
if newnames & currnodes:
raise ValueError(
'The following requested new names conflict with existing nodes: {0}'.format(
','.join(newnames & currnodes)))
for name in renamemap:
self._cfgstore['nodes'][renamemap[name]] = self._cfgstore['nodes'][name]
del self._cfgstore['nodes'][name]
_mark_dirtykey('nodes', name, self.tenant)
_mark_dirtykey('nodes', renamemap[name], self.tenant)
for group in self._cfgstore['nodes'][renamemap[name]].get('groups', []):
self._cfgstore['nodegroups'][group]['nodes'].discard(name)
self._cfgstore['nodegroups'][group]['nodes'].add(renamemap[name])
_mark_dirtykey('nodegroups', group, self.tenant)
cfgobj = self._cfgstore['nodes'][renamemap[name]]
node = renamemap[name]
changeset = {}
if exprmgr is None:
exprmgr = _ExpressionFormat(cfgobj, node)
self._recalculate_expressions(cfgobj, formatter=exprmgr, node=renamemap[name], changeset=changeset)
if self.tenant in self._nodecollwatchers:
nodecollwatchers = self._nodecollwatchers[self.tenant]
for watcher in nodecollwatchers.itervalues():
eventlet.spawn_n(_do_add_watcher, watcher, (), self, renamemap)
self._bg_sync_to_file()
def rename_nodegroups(self, renamemap):
if cfgleader:
return exec_on_leader('_rpc_master_rename_nodegroups', self.tenant, renamemap)
if cfgstreams:
exec_on_followers('_rpc_rename_nodegroups', self.tenant, renamemap)
self._true_rename_groups(renamemap)
def _true_rename_groups(self, renamemap):
oldnames = set(renamemap)
currgroups = set(self._cfgstore['nodegroups'])
missinggroups = oldnames - currgroups
if missinggroups:
raise ValueError(
'The following groups to rename do not exist: {0}'.format(
','.join(missinggroups)))
newnames = set([])
for name in renamemap:
newnames.add(renamemap[name])
if newnames & currgroups:
raise ValueError(
'The following requested new names conflict with existing groups: {0}'.format(
','.join(newnames & currgroups)))
for name in renamemap:
self._cfgstore['nodegroups'][renamemap[name]] = self._cfgstore['nodegroups'][name]
del self._cfgstore['nodegroups'][name]
_mark_dirtykey('nodegroups', name, self.tenant)
_mark_dirtykey('nodegroups', renamemap[name], self.tenant)
for node in self._cfgstore['nodegroups'][renamemap[name]].get('nodes', []):
lidx = self._cfgstore['nodes'][node]['groups'].index(name)
self._cfgstore['nodes'][node]['groups'][lidx] = renamemap[name]
_mark_dirtykey('nodes', node, self.tenant)
self._bg_sync_to_file()
def set_node_attributes(self, attribmap, autocreate=False):
if cfgleader: # currently config slave to another
return exec_on_leader('_rpc_master_set_node_attributes',
@@ -2177,9 +1946,6 @@ class ConfigManager(object):
self.set_node_attributes(tmpconfig[confarea], True)
elif confarea == 'nodegroups':
self.set_group_attributes(tmpconfig[confarea], True)
elif confarea == 'usergroups':
for usergroup in tmpconfig[confarea]:
self.create_usergroup(usergroup)
elif confarea == 'users':
for user in tmpconfig[confarea]:
uid = tmpconfig[confarea].get('id', None)
@@ -2249,9 +2015,7 @@ class ConfigManager(object):
rootpath = cls._cfgdir
try:
with open(os.path.join(rootpath, 'transactioncount'), 'r') as f:
txbytes = f.read()
if len(txbytes) == 8:
_txcount = struct.unpack('!Q', txbytes)[0]
_txcount = struct.unpack('!Q', f.read())[0]
except IOError:
pass
_load_dict_from_dbm(['collective'], os.path.join(rootpath,
@@ -2286,13 +2050,9 @@ class ConfigManager(object):
if statelessmode:
return
with cls._syncstate:
if (cls._syncrunning and cls._cfgwriter is not None and
cls._cfgwriter.isAlive()):
if cls._syncrunning:
cls._writepending = True
return
if cls._syncrunning: # This suggests an unclean write attempt,
# do a fullsync as a recovery
fullsync = True
cls._syncrunning = True
# if the thread is exiting, join it to let it close, just in case
if cls._cfgwriter is not None:
@@ -2308,9 +2068,8 @@ class ConfigManager(object):
_mkpath(cls._cfgdir)
with open(os.path.join(cls._cfgdir, 'transactioncount'), 'w') as f:
f.write(struct.pack('!Q', _txcount))
if (fullsync or 'dirtyglobals' in _cfgstore and
'globals' in _cfgstore):
if fullsync: # globals is not a given to be set..
if fullsync or 'dirtyglobals' in _cfgstore:
if fullsync:
dirtyglobals = _cfgstore['globals']
else:
with _dirtylock:
@@ -2435,7 +2194,7 @@ def _restore_keys(jsond, password, newpassword=None, sync=True):
def _dump_keys(password, dojson=True):
if _masterkey is None:
if _masterkey is None or _masterintegritykey is None:
init_masterkey()
cryptkey = _format_key(_masterkey, password=password)
if 'passphraseprotected' in cryptkey:
@@ -2444,16 +2203,14 @@ def _dump_keys(password, dojson=True):
else:
cryptkey = '*unencrypted:{0}'.format(base64.b64encode(
cryptkey['unencryptedvalue']))
keydata = {'cryptkey': cryptkey}
if _masterintegritykey is not None:
integritykey = _format_key(_masterintegritykey, password=password)
if 'passphraseprotected' in integritykey:
integritykey = '!'.join(map(base64.b64encode,
integritykey['passphraseprotected']))
else:
integritykey = '*unencrypted:{0}'.format(base64.b64encode(
integritykey['unencryptedvalue']))
keydata['integritykey'] = integritykey
integritykey = _format_key(_masterintegritykey, password=password)
if 'passphraseprotected' in integritykey:
integritykey = '!'.join(map(base64.b64encode,
integritykey['passphraseprotected']))
else:
integritykey = '*unencrypted:{0}'.format(base64.b64encode(
integritykey['unencryptedvalue']))
keydata = {'cryptkey': cryptkey, 'integritykey': integritykey}
if dojson:
return json.dumps(keydata, sort_keys=True, indent=4, separators=(',', ': '))
return keydata
+20 -66
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.
@@ -145,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
@@ -309,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):
@@ -364,33 +359,16 @@ class ConsoleHandler(object):
except (exc.NotImplementedException, exc.NotFoundException):
self._console = None
except:
if _tracelog:
_tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
event=log.Events.stacktrace)
else:
print(traceback.format_exc())
_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[The console.method attribute for this node is '
'not configured,\r\nset it to a valid value for console '
'function]')
self._send_rcpts(
'\x1bc\x1b[2J\x1b[1;1H[The console.method attribute for this node is '
'not configured,\r\nset it to a valid value for console '
'function]')
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
@@ -402,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()
@@ -544,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()
@@ -621,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):
@@ -652,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
@@ -674,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
@@ -688,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()
@@ -730,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
},
@@ -771,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
@@ -797,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):
@@ -807,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
@@ -839,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
+15 -136
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
@@ -124,7 +123,7 @@ def load_plugins():
rootcollections = ['discovery/', 'events/', 'networking/',
'noderange/', 'nodes/', 'nodegroups/', 'usergroups/' , 'users/', 'version']
'noderange/', 'nodes/', 'nodegroups/', 'users/', 'version']
class PluginRoute(object):
@@ -144,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'}),
@@ -167,14 +165,6 @@ def _init_core():
'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',
@@ -206,24 +196,6 @@ def _init_core():
}),
},
},
'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'],
@@ -233,10 +205,6 @@ def _init_core():
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'clear': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
})
},
},
'_console': {
@@ -269,10 +237,6 @@ def _init_core():
'default': 'ipmi',
}),
},
'description': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
'events': {
'hardware': {
'log': PluginRoute({
@@ -374,17 +338,10 @@ def _init_core():
},
},
'support': {
'servicedata': PluginCollection({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
},
}
nodegroupresources = {
'attributes': {
'rename': PluginRoute({'handler': 'attributes'}),
'all': PluginRoute({'handler': 'attributes'}),
'current': PluginRoute({'handler': 'attributes'}),
},
@@ -400,33 +357,13 @@ def create_user(inputdata, configmanager):
configmanager.create_user(username, attributemap=inputdata)
def create_usergroup(inputdata, configmanager):
try:
groupname = inputdata['name']
del inputdata['name']
except (KeyError, ValueError):
raise exc.InvalidArgumentException()
configmanager.create_usergroup(groupname)
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 = {}
@@ -443,10 +380,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):
@@ -479,10 +412,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
@@ -642,15 +571,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')
@@ -686,7 +606,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:
@@ -710,11 +630,7 @@ def handle_dispatch(connection, cert, dispatch, peername):
def _forward_rsp(connection, res):
try:
r = pickle.dumps(res)
except TypeError:
r = pickle.dumps(Exception(
'Cannot serialize error, check collective.manager error logs for details' + str(res)))
r = pickle.dumps(res)
rlen = len(r)
if not rlen:
return
@@ -724,8 +640,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':
@@ -849,8 +763,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:
@@ -860,8 +773,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:
@@ -897,8 +813,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
@@ -912,8 +826,6 @@ def addtoqueue(theq, fun, kwargs):
else:
for pv in result:
theq.put(pv)
except Exception as e:
theq.put(e)
finally:
theq.put('theend')
@@ -923,22 +835,14 @@ def dispatch_request(nodes, manager, element, configmanager, inputdata,
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)):
@@ -1041,31 +945,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
+9 -59
View File
@@ -63,13 +63,13 @@
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.slp as slp
import confluent.discovery.handlers.imm as imm
import confluent.discovery.handlers.pxe as pxeh
import confluent.discovery.handlers.smm as smm
import confluent.discovery.handlers.cpstorage as cpstorage
import confluent.discovery.handlers.xcc as xcc
import confluent.exceptions as exc
import confluent.log as log
@@ -87,8 +87,6 @@ import eventlet.greenpool
import eventlet.semaphore
autosensors = set()
scanner = None
class nesteddict(dict):
def __missing__(self, key):
@@ -100,7 +98,7 @@ nodehandlers = {
'service:management-hardware.Lenovo:lenovo-xclarity-controller': xcc,
'service:management-hardware.IBM:integrated-management-module2': imm,
'pxe-client': pxeh,
'service:io-device.Lenovo:management-module': None,
'service:io-device.Lenovo:management-module': cpstorage,
}
servicenames = {
@@ -212,8 +210,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):
@@ -386,7 +382,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')
@@ -397,15 +392,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)
@@ -429,8 +417,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))]
@@ -473,9 +459,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]:
@@ -589,11 +572,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:
@@ -640,9 +618,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(
@@ -712,10 +687,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
@@ -1056,14 +1027,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:
@@ -1129,7 +1092,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
@@ -1138,18 +1100,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()
@@ -1189,11 +1143,7 @@ def _periodic_recheck(configmanager):
def rescan():
_map_unique_ids()
global scanner
if scanner:
return
else:
scanner = eventlet.spawn(slp.active_scan, safe_detected, slp)
eventlet.spawn_n(slp.active_scan, safe_detected)
def start_detection():
@@ -1222,8 +1172,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 = {}
@@ -1245,7 +1195,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,21 @@ 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 = DEFAULT_USER
user = self.DEFAULT_USER
if password is None:
password = DEFAULT_PASS
return ipmicommand.Command(self.ipaddr, user, password,
privlevel=priv, keepalive=False)
password = self.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 +50,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 = DEFAULT_PASS
passwd = self.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 != DEFAULT_USER:
if user is not None and user != self.DEFAULT_USER:
havecustomcreds = True
else:
user = DEFAULT_USER
if passwd is not None and passwd != DEFAULT_PASS:
user = self.DEFAULT_USER
passwd = creds.get(nodename, {}).get(
'secret.hardwaremanagementpassword', {}).get('value', None)
if passwd is not None and passwd != self.DEFAULT_PASS:
havecustomcreds = True
else:
passwd = DEFAULT_PASS
passwd = self.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 +98,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 +157,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
@@ -0,0 +1,21 @@
# 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
class NodeHandler(bmchandler.NodeHandler):
DEFAULT_USER = 'admin'
DEFAULT_PASS = 'admin'
devname = 'BMC'
maxmacs = 2
@@ -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
@@ -68,23 +62,6 @@ class NodeHandler(object):
def _savecert(self, certificate):
self._fp = certificate
return True
def get_node_credentials(self, nodename, creds, defuser, defpass):
user = creds.get(nodename, {}).get(
'secret.hardwaremanagementuser', {}).get('value', None)
havecustomcreds = False
if user is not None and user != defuser:
havecustomcreds = True
else:
user = defuser
passwd = creds.get(nodename, {}).get(
'secret.hardwaremanagementpassword', {}).get('value', None)
if passwd is not None and passwd != defpass:
havecustomcreds = True
else:
passwd = defpass
return user, passwd, not havecustomcreds
@property
def cert_fail_reason(self):
@@ -15,17 +15,12 @@
import confluent.discovery.handlers.bmc as bmchandler
import pyghmi.exceptions as pygexc
import pyghmi.ipmi.private.util as pygutil
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
@@ -43,7 +38,7 @@ class NodeHandler(bmchandler.NodeHandler):
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
@@ -13,15 +13,7 @@
# limitations under the License.
import confluent.discovery.handlers.bmc as bmchandler
import confluent.exceptions as exc
import pyghmi.util.webclient as webclient
import struct
import urllib
import eventlet.support.greendns
import confluent.netutil as netutil
getaddrinfo = eventlet.support.greendns.getaddrinfo
from xml.etree.ElementTree import fromstring
def fixuuid(baduuid):
# SMM dumps it out in hex
@@ -34,7 +26,7 @@ def fixuuid(baduuid):
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
@@ -44,147 +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 = 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', urllib.urlencode(authdata), headers)
rsp = wc.getresponse()
rspdata = rsp.read()
if 'authResult>0' not in rspdata:
# default credentials are refused, try with the actual
authdata['user'] = username
authdata['password'] = password
wc.request('POST', '/data/login', urllib.urlencode(authdata), headers)
rsp = wc.getresponse()
rspdata = 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', urllib.urlencode(passwdchange))
rsp = wc.getresponse()
rspdata = rsp.read()
authdata['password'] = password
wc.request('POST', '/data/login', urllib.urlencode(authdata), headers)
rsp = wc.getresponse()
rspdata = 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', urllib.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 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:
@@ -199,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,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,52 +12,19 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import base64
import confluent.discovery.handlers.imm as immhandler
import confluent.netutil as netutil
import confluent.util as util
import eventlet
import eventlet.support.greendns
import json
import os
import pyghmi.exceptions as pygexc
xcc = eventlet.import_patched('pyghmi.redfish.oem.lenovo.xcc')
import pyghmi.util.webclient as 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 = struct.pack('<IHH', *[int(x, 16) for x in uuidprefix]).encode('hex')
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._xcchdlr = None
self._wc = None
self.nodename = None
self._atdefaultcreds = True
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):
ff = self.info.get('attributes', {}).get('enclosure-form-factor', '')
if ff not in ('dense-computing', [u'dense-computing']):
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
ipmicmd = None
@@ -69,196 +36,27 @@ class NodeHandler(immhandler.NodeHandler):
'Incorrect password' not in str(e)):
# raise an issue if anything other than to be expected
raise
self.trieddefault = True
#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)
@property
def wc(self):
if self._wc is None:
self._wc = webclient.SecureHTTPConnection(
self.ipaddr, 443, verifycallback=self.validate_cert)
self._wc.connect()
self._xcchdlr = xcc.OEMHandler(None, None, self._wc, False)
if not self.trieddefault:
self._xcchdlr.set_credentials('USERID', 'PASSW0RD')
wc = self._xcchdlr.get_webclient()
if wc:
return wc
self.trieddefault = True
creds = self.configmanager.get_node_attributes(
self.nodename, ['secret.hardwaremanagementuser',
'secret.hardwaremanagementpassword'], decrypt=True)
user, passwd, isdefault = self.get_node_credentials(
self.nodename, creds, 'USERID', 'PASSW0RD')
if isdefault:
return
self._atdefaultcreds = False
self._xcchdlr.set_credentials(user, passwd)
wc = self._xcchdlr.get_webclient()
if wc:
return wc
def set_password_policy(self):
ruleset = {'USER_GlobalMinPassChgInt': '0'}
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'):
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:
self.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..
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)})
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
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})
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, passwd)
nwc.grab_json_response('/api/function', {'USER_UserCreate': userparams})
finally:
self._wc = None
self.wc.grab_json_response('/api/function', {'USER_UserDelete': "{0},{1}".format(tmpuid, '6pmu0ezczzcp')})
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')
self.ruleset = 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()
if self._atdefaultcreds:
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'], True)
cd = cd.get(nodename, {})
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'])
# do not change the ipv4_config if the current config looks
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)')
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(
@@ -267,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
@@ -72,7 +72,7 @@ def find_info_in_options(rq, optidx):
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
@@ -255,7 +255,7 @@ def _parse_attrlist(attrstr):
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(','):
@@ -295,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:
@@ -309,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
@@ -335,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):
@@ -364,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:
@@ -397,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
@@ -406,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')
@@ -483,14 +469,13 @@ def snoop(handler, protocol=None):
peerbymacaddress[mac]['xid'] = 1
_add_attributes(peerbymacaddress[mac])
peerbymacaddress[mac]['hwaddr'] = mac
peerbymacaddress[mac]['protocol'] = protocol
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']:
@@ -501,7 +486,6 @@ def active_scan(handler, protocol=None):
break
known_peers.add(addr)
else:
scanned['protocol'] = protocol
handler(scanned)
@@ -167,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:
@@ -175,10 +174,7 @@ 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:
yield peerdata[nid]
@@ -198,8 +194,6 @@ def _parse_ssdp(peer, rsp, peerdata):
if code == '200':
if nid in peerdata:
peerdatum = peerdata[nid]
if peer not in peerdatum['peers']:
peerdatum['peers'].append(peer)
else:
peerdatum = {
'peers': [peer],
-1
View File
@@ -106,7 +106,6 @@ class PubkeyInvalid(ConfluentException):
super(PubkeyInvalid, self).__init__(self, text)
self.fingerprint = fingerprint
self.attrname = attribname
self.message = text
bodydata = {'message': text,
'event': event,
'fingerprint': fingerprint,
+4 -26
View File
@@ -19,31 +19,22 @@
# 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' and not os.path.exists(filename):
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})
return
if type == 'ffdc' and os.path.isdir(filename):
filename += '/' + node + '.svcdata'
try:
if type == 'firmware':
completion = handler(filename, progress=updateobj.handle_progress,
@@ -52,9 +43,6 @@ def execupdate(handler, filename, updateobj, type, owner, node):
completion = handler(filename, progress=updateobj.handle_progress)
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 ' \
@@ -62,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:
@@ -113,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:
@@ -134,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'
+19 -37
View File
@@ -269,9 +269,6 @@ def _authorize_request(env, operation):
name = ''
sessionid = None
cookie = Cookie.SimpleCookie()
element = env['PATH_INFO']
if element.startswith('/sessions/current/'):
element = None
if 'HTTP_COOKIE' in env:
#attempt to use the cookie. If it matches
cc = RobustCookie()
@@ -293,7 +290,7 @@ def _authorize_request(env, operation):
httpsessions[sessionid]['expiry'] = time.time() + 90
name = httpsessions[sessionid]['name']
authdata = auth.authorize(
name, element=element, operation=operation,
name, element=None,
skipuserobj=httpsessions[sessionid]['skipuserobject'])
if (not authdata) and 'HTTP_AUTHORIZATION' in env:
if env['PATH_INFO'] == '/sessions/current/logout':
@@ -306,10 +303,8 @@ def _authorize_request(env, operation):
return ('logout',)
name, passphrase = base64.b64decode(
env['HTTP_AUTHORIZATION'].replace('Basic ', '')).split(':', 1)
authdata = auth.check_user_passphrase(name, passphrase, operation=operation, element=element)
if authdata is False:
return {'code': 403}
elif not authdata:
authdata = auth.check_user_passphrase(name, passphrase, element=None)
if not authdata:
return {'code': 401}
sessid = util.randomstring(32)
while sessid in httpsessions:
@@ -346,10 +341,15 @@ def _authorize_request(env, operation):
if 'csrftoken' in httpsessions[sessid]:
authinfo['authtoken'] = httpsessions[sessid]['csrftoken']
return authinfo
elif authdata is None:
return {'code': 401}
else:
return {'code': 403}
return {'code': 401}
# TODO(jbjohnso): actually evaluate the request for authorization
# In theory, the x509 or http auth stuff will get translated and then
# passed on to the core authorization function in an appropriate form
# expresses return in the form of http code
# 401 if there is no known identity
# 403 if valid identity, but no access
# going to run 200 just to get going for now
def _pick_mimetype(env):
@@ -384,11 +384,11 @@ def resourcehandler(env, start_response):
try:
for rsp in resourcehandler_backend(env, start_response):
yield rsp
except Exception as e:
except:
tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
event=log.Events.stacktrace)
start_response('500 - ' + str(e), [])
yield '500 - ' + str(e)
start_response('500 - Internal Server Error', [])
yield '500 - Internal Server Error'
return
@@ -429,7 +429,7 @@ def resourcehandler_backend(env, start_response):
return
if authorized['code'] == 403:
start_response('403 Forbidden', badauth)
yield 'Forbidden'
yield 'authorization failed'
return
if authorized['code'] != 200:
raise Exception("Unrecognized code from auth engine")
@@ -469,10 +469,6 @@ def resourcehandler_backend(env, start_response):
funport = forwarder.get_port(targip, env['HTTP_X_FORWARDED_FOR'],
authorized['sessionid'])
host = env['HTTP_X_FORWARDED_HOST']
if ']' in host:
host = host.split(']')[0] + ']'
elif ':' in host:
host = host.rsplit(':', 1)[0]
url = 'https://{0}:{1}/'.format(host, funport)
start_response('302', [('Location', url)])
yield 'Our princess is in another castle!'
@@ -500,8 +496,6 @@ def resourcehandler_backend(env, start_response):
skipreplay = False
if 'skipreplay' in querydict and querydict['skipreplay']:
skipreplay = True
width = querydict.get('width', 80)
height = querydict.get('height', 24)
datacallback = None
async = None
if 'HTTP_CONFLUENTASYNCID' in env:
@@ -513,13 +507,13 @@ def resourcehandler_backend(env, start_response):
consession = shellserver.ShellSession(
node=nodename, configmanager=cfgmgr,
username=authorized['username'], skipreplay=skipreplay,
datacallback=datacallback, width=width, height=height
datacallback=datacallback
)
else:
consession = consoleserver.ConsoleSession(
node=nodename, configmanager=cfgmgr,
username=authorized['username'], skipreplay=skipreplay,
datacallback=datacallback, width=width, height=height
datacallback=datacallback
)
except exc.NotFoundException:
start_response("404 Not found", headers)
@@ -554,9 +548,6 @@ def resourcehandler_backend(env, start_response):
elif 'action' in querydict:
if querydict['action'] == 'break':
consolesessions[querydict['session']]['session'].send_break()
elif querydict['action'] == 'resize':
consolesessions[querydict['session']]['session'].resize(
width=querydict['width'], height=querydict['height'])
elif querydict['action'] == 'reopen':
consolesessions[querydict['session']]['session'].reopen()
else:
@@ -793,17 +784,8 @@ def serve(bind_host, bind_port):
'Failed to open HTTP due to busy port, trying again in'
' a second\n')
eventlet.sleep(1)
# TCP_FASTOPEN
try:
sock.setsockopt(socket.SOL_TCP, 23, 5)
except Exception:
pass # we gave it our best shot there
try:
eventlet.wsgi.server(sock, resourcehandler, log=False, log_output=False,
debug=False, socket_timeout=60)
except TypeError:
# Older eventlet in place, skip arguments it does not understand
eventlet.wsgi.server(sock, resourcehandler, log=False, debug=False)
eventlet.wsgi.server(sock, resourcehandler, log=False, log_output=False,
debug=False, socket_timeout=60)
class HttpApi(object):
@@ -41,11 +41,6 @@ class Console(object):
the remote console"""
return
def resize(self, width, height):
"""This function is responsible for relaying resize request from
client terminal to remote console"""
return
def ping(self):
"""This function is a hint to the console plugin that now would be a
nice time to assess health of console connection. Plugins that see
+36 -64
View File
@@ -51,15 +51,11 @@
# - leading bit reserved, 0 for now
# - length of metadata record 7 bits
# - type of data referenced by this entry (one byte), currently:
# 0=text event, 1=json, 2=console data, 3=event
# 0=text event, 1=json, 2=console data
# - offset into the text log to begin (4 bytes)
# - length of data referenced by this entry (2 bytes)
# - UTC timestamp of this entry in seconds since epoch (unsigned 32 bit)
# - Event type (per 'Events' class below)
# - Event data (per event, currently used by connect/disconnect to represent
# single or multiple connections by user and for 'appmode' and 'shiftin'
# status for console
# - 2 reserved bytes
# - UTC timestamp of this entry in seconds since epoch (unsigned 32 bit?)
# - CRC32 over the record
# (a future extended version might include suport for Forward Secure Sealing
# or other fields)
@@ -77,8 +73,6 @@ import struct
import time
import traceback
daemonized = False
logfull = False
try:
from fcntl import flock, LOCK_EX, LOCK_UN, LOCK_SH
except ImportError:
@@ -156,34 +150,21 @@ class BaseRotatingHandler(object):
Output the record to the file, catering for rollover as described
in doRollover().
"""
global logfull
try:
rolling_type = self.shouldRollover(binrecord, textrecord)
if rolling_type:
flock(self.textfile, LOCK_UN)
return self.doRollover(rolling_type)
return None
except (IOError, OSError) as e:
if not daemonized:
raise
logfull = True
rolling_type = self.shouldRollover(binrecord, textrecord)
if rolling_type:
flock(self.textfile, LOCK_UN)
return self.doRollover(rolling_type)
return None
def emit(self, binrecord, textrecord):
global logfull
try:
if self.textfile is None:
self.textfile = open(self.textpath, mode='ab')
if self.binfile is None:
self.binfile = open(self.binpath, mode='ab')
self.textfile.write(textrecord)
self.binfile.write(binrecord)
self.textfile.flush()
self.binfile.flush()
except (IOError, OSError) as e:
if not daemonized:
raise
logfull = True
if self.textfile is None:
self.textfile = open(self.textpath, mode='ab')
if self.binfile is None:
self.binfile = open(self.binpath, mode='ab')
self.textfile.write(textrecord)
self.binfile.write(binrecord)
self.textfile.flush()
self.binfile.flush()
def get_textfile_offset(self, data_len):
if self.textfile is None:
@@ -580,35 +561,28 @@ class Logger(object):
textdate = time.strftime(
'%b %d %H:%M:%S ', time.localtime(tstamp))
flock(textfile, LOCK_EX)
try:
offset = textfile.tell() + len(textdate)
datalen = len(data)
eventaux = entry[4]
if eventaux is None:
eventaux = 0
# metadata length is always 16 for this code at the moment
binrecord = struct.pack(
">BBIHIBBH", 16, ltype, offset, datalen, tstamp, evtdata,
eventaux, 0)
if self.isconsole:
if ltype == 2:
textrecord = data
else:
textrecord = textdate + data + ']'
offset = textfile.tell() + len(textdate)
datalen = len(data)
eventaux = entry[4]
if eventaux is None:
eventaux = 0
# metadata length is always 16 for this code at the moment
binrecord = struct.pack(
">BBIHIBBH", 16, ltype, offset, datalen, tstamp, evtdata,
eventaux, 0)
if self.isconsole:
if ltype == 2:
textrecord = data
else:
textrecord = textdate + data
if not textrecord.endswith('\n'):
textrecord += '\n'
files = self.handler.try_emit(binrecord, textrecord)
except struct.error:
files = self.handler.doRollover(RollingTypes.size_rolling)
finally:
try:
flock(textfile, LOCK_UN)
except Exception:
pass
textrecord = textdate + data + ']'
else:
textrecord = textdate + data
if not textrecord.endswith('\n'):
textrecord += '\n'
files = self.handler.try_emit(binrecord, textrecord)
if not files:
self.handler.emit(binrecord, textrecord)
flock(textfile, LOCK_UN)
else:
# Log the rolling event at first, then log the last data
# which cause the rolling event.
@@ -779,13 +753,11 @@ globaleventlog = None
tracelog = None
def log(logdata=None, ltype=None, event=0, eventdata=None, flush=False):
def log(logdata=None, ltype=None, event=0, eventdata=None):
global globaleventlog
if globaleventlog is None:
globaleventlog = Logger('events')
globaleventlog.log(logdata, ltype, event, eventdata)
if flush:
globaleventlog.writedata()
def logtrace():
global tracelog
+2 -8
View File
@@ -72,7 +72,7 @@ def _daemonize():
os.setsid()
thispid = os.fork()
if thispid > 0:
print('confluent server starting as pid {0}'.format(thispid))
print 'confluent server starting as pid %d' % thispid
os._exit(0)
os.closerange(0, 2)
os.umask(63)
@@ -81,7 +81,6 @@ def _daemonize():
os.dup2(0, 2)
sys.stdout = log.Logger('stdout', buffered=False)
sys.stderr = log.Logger('stderr', buffered=False)
log.daemonized = True
def _updatepidfile():
@@ -225,11 +224,6 @@ def run():
except:
doexit()
raise
try:
log.log({'info': 'Confluent management service starting'}, flush=True)
except (OSError, IOError) as e:
print(repr(e))
sys.exit(1)
_daemonize()
if havefcntl:
_updatepidfile()
@@ -237,7 +231,7 @@ def run():
signal.signal(signal.SIGTERM, terminate)
collective.startup()
if dbgif:
oumask = os.umask(0o077)
oumask = os.umask(0077)
try:
os.remove('/var/run/confluent/dbg.sock')
except OSError:
+29 -316
View File
@@ -1,7 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 IBM Corporation
# 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.
@@ -20,7 +20,6 @@
# format. This is also how different data formats are supported
import confluent.exceptions as exc
import confluent.config.configmanager as cfm
import confluent.config.conf as cfgfile
from copy import deepcopy
from datetime import datetime
import json
@@ -33,22 +32,6 @@ valid_health_values = set([
'unknown',
])
passcomplexity = cfgfile.get_option('policy', 'passwordcomplexity')
passminlength = cfgfile.get_option('policy', 'passwordminlength')
if passminlength:
passminlength = int(passminlength)
else:
passminlength = 0
if passcomplexity:
passcomplexity = int(passcomplexity)
else:
passcomplexity = 0
def simplify_name(name):
return name.lower().replace(' ', '_').replace('/', '-').replace(
'_-_', '-')
def _htmlify_structure(indict):
ret = "<ul>"
if isinstance(indict, dict):
@@ -279,24 +262,6 @@ class CreatedResource(ConfluentMessage):
pass
class RenamedResource(ConfluentMessage):
notnode = True
readonly = True
def __init__(self, oldname, newname):
self.kvpairs = {'oldname': oldname, 'newname': newname}
def strip_node(self, node):
pass
class RenamedNode(ConfluentMessage):
def __init__(self, name, rename):
self.desc = 'New Name'
kv = {'rename': {'value': rename}}
self.kvpairs = {name: kv}
class AssignedResource(ConfluentMessage):
notnode = True
readonly = True
@@ -411,9 +376,7 @@ def get_input_message(path, operation, inputdata, nodes=None, multinode=False,
return InputReseatMessage(path, nodes, inputdata)
elif path == ['attributes', 'expression']:
return InputExpression(path, inputdata, nodes)
elif path == ['attributes', 'rename']:
return InputConfigChangeSet(path, inputdata, nodes, configmanager)
elif path[0] in ('attributes', 'users', 'usergroups') and operation != 'retrieve':
elif path[0] in ('attributes', 'users') and operation != 'retrieve':
return InputAttributes(path, inputdata, nodes)
elif path == ['boot', 'nextdevice'] and operation != 'retrieve':
return InputBootDevice(path, nodes, inputdata)
@@ -453,65 +416,27 @@ def get_input_message(path, operation, inputdata, nodes=None, multinode=False,
elif (path[:3] == ['configuration', 'system', 'all'] and
operation != 'retrieve'):
return InputConfigChangeSet(path, inputdata, nodes, configmanager)
elif (path[:3] == ['configuration', 'system', 'clear'] and
operation != 'retrieve'):
return InputConfigClear(path, inputdata, nodes, configmanager)
elif (path[:3] == ['configuration', 'storage', 'disks'] and
operation != 'retrieve'):
return InputDisk(path, nodes, inputdata)
elif (path[:3] == ['configuration', 'storage', 'volumes'] and
operation in ('update', 'create')):
return InputVolumes(path, nodes, inputdata)
elif 'inventory/firmware/updates/active' in '/'.join(path) and inputdata:
return InputFirmwareUpdate(path, nodes, inputdata, configmanager)
return InputFirmwareUpdate(path, nodes, inputdata)
elif '/'.join(path).startswith('media/detach'):
return DetachMedia(path, nodes, inputdata)
elif '/'.join(path).startswith('media/') and inputdata:
return InputMedia(path, nodes, inputdata, configmanager)
elif '/'.join(path).startswith('support/servicedata') and inputdata:
return InputMedia(path, nodes, inputdata, configmanager)
elif '/'.join(path).startswith('configuration/management_controller/save_licenses') and inputdata:
return InputMedia(path, nodes, inputdata, configmanager)
elif '/'.join(path).startswith(
'configuration/management_controller/licenses') and inputdata:
return InputLicense(path, nodes, inputdata, configmanager)
return InputMedia(path, nodes, inputdata)
elif inputdata:
raise exc.InvalidArgumentException(
'No known input handler for request')
class InputFirmwareUpdate(ConfluentMessage):
def __init__(self, path, nodes, inputdata, configmanager):
self._filename = inputdata.get('filename', inputdata.get('url', inputdata.get('dirname', None)))
def __init__(self, path, nodes, inputdata):
self.filename = inputdata.get('filename', inputdata.get('url', None))
self.bank = inputdata.get('bank', None)
self.nodes = nodes
self.filebynode = {}
self._complexname = False
for expanded in configmanager.expand_attrib_expression(
nodes, self._filename):
node, value = expanded
if value != self._filename:
self._complexname = True
self.filebynode[node] = value
@property
def filename(self):
if self._complexname:
raise Exception('User requested substitutions, but code is '
'written against old api, code must be fixed or '
'skip {} expansion')
return self._filename
def nodefile(self, node):
return self.filebynode[node]
class InputMedia(InputFirmwareUpdate):
# Use InputFirmwareUpdate
pass
class InputLicense(InputFirmwareUpdate):
pass
class DetachMedia(ConfluentMessage):
def __init__(self, path, nodes, inputdata):
@@ -524,10 +449,6 @@ class Media(ConfluentMessage):
def __init__(self, node, media):
self.kvpairs = {node: {'name': media.name, 'url': media.url}}
class SavedFile(ConfluentMessage):
def __init__(self, node, file):
self.kvpairs = {node: {'filename': file}}
class InputAlertData(ConfluentMessage):
def __init__(self, path, inputdata, nodes=None):
@@ -569,14 +490,9 @@ class InputExpression(ConfluentMessage):
nodeattr = deepcopy(self.nodeattribs[node])
return nodeattr
class InputConfigClear(ConfluentMessage):
def __init__(self, path, inputdata, nodes=None, configmanager=None):
if not inputdata:
raise exc.InvalidArgumentException('no request data provided')
if 'clear' not in inputdata or not inputdata['clear']:
raise exc.InvalidArgumentException('Input must be {"clear":true}')
class InputConfigChangeSet(InputExpression):
# For now, this is identical to InputExpression, later it may
# internalize formula expansion, but not now..
def __init__(self, path, inputdata, nodes=None, configmanager=None):
self.cfm = configmanager
super(InputConfigChangeSet, self).__init__(path, inputdata, nodes)
@@ -595,6 +511,7 @@ class InputConfigChangeSet(InputExpression):
[node], attrs[attr]))[0][1]
return endattrs
class InputAttributes(ConfluentMessage):
# This is particularly designed for attributes, where a simple string
# should become either a string value or a dict with {'expression':} to
@@ -634,7 +551,7 @@ class InputAttributes(ConfluentMessage):
for node in nodes:
self.nodeattribs[node] = inputdata
def get_attributes(self, node, validattrs=None):
def get_attributes(self, node):
if node not in self.nodeattribs:
return {}
nodeattr = deepcopy(self.nodeattribs[node])
@@ -649,82 +566,15 @@ class InputAttributes(ConfluentMessage):
# an expression string will error if format() done
# use that as cue to put it into config as an expr
nodeattr[attr] = {'expression': nodeattr[attr]}
if validattrs and 'validvalues' in validattrs.get(attr, []):
if (nodeattr[attr] and
nodeattr[attr] not in validattrs[attr]['validvalues']):
raise exc.InvalidArgumentException(
'Attribute {0} does not accept value {1} (valid values would be {2})'.format(
attr, nodeattr[attr], ','.join(validattrs[attr]['validvalues'])))
elif validattrs and 'validlist' in validattrs.get(attr, []) and nodeattr[attr]:
req = nodeattr[attr].split(',')
for v in req:
if v and v not in validattrs[attr]['validlist']:
raise exc.InvalidArgumentException(
'Attribute {0} does not accept list member '
'{1} (valid values would be {2})'.format(
attr, v, ','.join(
validattrs[attr]['validlist'])))
elif validattrs and 'validlistkeys' in validattrs.get(attr, []) and nodeattr[attr]:
req = nodeattr[attr].split(',')
for v in req:
if '=' not in v:
raise exc.InvalidArgumentException(
'Passed key {0} requires a parameter'.format(v))
v = v.split('=', 1)[0]
if v and v not in validattrs[attr]['validlistkeys']:
raise exc.InvalidArgumentException(
'Attribute {0} does not accept key {1} (valid values would be {2})'.format(
attr, v, ','.join(
validattrs[attr]['validlistkeys'])
)
)
return nodeattr
def checkPassword(password, username):
lowercase = set('abcdefghijklmnopqrstuvwxyz')
uppercase = set('abcdefghijklmnopqrstuvwxyz'.upper())
numbers = set('0123456789')
special = set('`~!@#$%^&*()-_=+[{]};:"/?.>,<' + "'")
if len(password) < passminlength:
raise exc.InvalidArgumentException('Password must be at least {0} characters long'.format(passminlength))
if not isinstance(passcomplexity, int) or passcomplexity < 1:
return
if not bool(set(password.lower()) & lowercase): # rule 1
raise exc.InvalidArgumentException('Password must contain at least one letter')
if passcomplexity < 2:
return
thepass = set(password)
if not bool(thepass & numbers): # rule 2
raise exc.InvalidArgumentException('Password must contain at least one number')
if passcomplexity < 3:
return
classes = 0
for charclass in (lowercase, uppercase, special):
if bool(thepass & charclass):
classes += 1
if classes < 2:
raise exc.InvalidArgumentException('Password must contain at least two of upper case letter, lower case letter, and/or special character')
if passcomplexity < 4:
return
if username and password in (username, username[::-1]): # rule 4
raise exc.InvalidArgumentException('Password must not be similar to username')
if passcomplexity < 5:
return
for char in thepass:
if char * 3 in password:
raise exc.InvalidArgumentException('Password must not contain any of the same character repeated 3 times')
class InputCredential(ConfluentMessage):
valid_privilege_levels = set([
'callback',
'user',
'ReadOnly',
'operator',
'Operator',
'administrator',
'Administrator',
'proprietary',
'no_access',
])
@@ -742,21 +592,33 @@ class InputCredential(ConfluentMessage):
if len(path) == 4:
inputdata['uid'] = path[-1]
# if the operation is 'create' check if all fields are present
missingattrs = []
for attrname in ('uid', 'privilege_level', 'username', 'password'):
if attrname not in inputdata:
missingattrs.append(attrname)
if missingattrs:
raise exc.InvalidArgumentException(
'Required fields missing: {0}'.format(','.join(missingattrs)))
if (isinstance(inputdata['uid'], str) and
not inputdata['uid'].isdigit()):
inputdata['uid'] = inputdata['uid']
raise exc.InvalidArgumentException('uid must be a number')
else:
inputdata['uid'] = int(inputdata['uid'])
if ('privilege_level' in inputdata and
inputdata['privilege_level'] not in self.valid_privilege_levels):
raise exc.InvalidArgumentException('privilege_level is not one of '
+ ','.join(self.valid_privilege_levels))
if 'username' in inputdata and len(inputdata['username']) > 16:
raise exc.InvalidArgumentException(
'name must be less than or = 16 chars')
if 'password' in inputdata and len(inputdata['password']) > 20:
raise exc.InvalidArgumentException('password has limit of 20 chars')
if ('enabled' in inputdata and
inputdata['enabled'] not in self.valid_enabled_values):
raise exc.InvalidArgumentException('valid values for enabled are '
+ 'yes and no')
if 'password' in inputdata and (passcomplexity or passminlength):
checkPassword(inputdata['password'], inputdata.get('username', None))
if nodes is None:
raise exc.InvalidArgumentException(
'This only supports per-node input')
@@ -827,81 +689,6 @@ class InputIdentifyMessage(ConfluentInputMessage):
keyname = 'identify'
class InputDisk(ConfluentInputMessage):
valid_values = set([
'jbod',
'unconfigured',
'hotspare',
])
keyname = 'state'
class InputVolumes(ConfluentInputMessage):
def __init__(self, path, nodes, inputdata):
self.inputbynode = {}
self.stripped = False
if not inputdata:
raise exc.InvalidArgumentException('missing input data')
if isinstance(inputdata, dict):
volnames = [None]
if len(path) == 6:
volnames = path[-1]
volnames = inputdata.get('name', volnames)
if not isinstance(volnames, list):
volnames = volnames.split(',')
sizes = inputdata.get('size', [None])
if not isinstance(sizes, list):
sizes = sizes.split(',')
stripsizes = inputdata.get('stripsizes', [None])
if not isinstance(stripsizes, list):
stripsizes = stripsizes.split(',')
disks = inputdata.get('disks', [])
if not disks:
raise exc.InvalidArgumentException(
'disks are currently required to create a volume')
raidlvl = inputdata.get('raidlevel', None)
inputdata = []
for size in sizes:
if volnames:
currname = volnames.pop(0)
else:
currname = None
if stripsizes:
currstripsize = stripsizes.pop(0)
if currstripsize:
currstripsize = int(currstripsize)
else:
currstripsize = None
inputdata.append(
{'name': currname, 'size': size,
'stripsize': currstripsize,
'disks': disks,
'raidlevel': raidlvl})
for node in nodes:
self.inputbynode[node] = []
for input in inputdata:
volname = None
if len(path) == 6:
volname = path[-1]
volname = input.get('name', volname)
if not volname:
volname = None
volsize = input.get('size', None)
if isinstance(input['disks'], list):
disks = input['disks']
else:
disks = input['disks'].split(',')
raidlvl = input.get('raidlevel', None)
for node in nodes:
self.inputbynode[node].append({'name': volname,
'size': volsize,
'disks': disks,
'stripsize': input.get('stripsize', None),
'raidlevel': raidlvl,
})
class InputPowerMessage(ConfluentInputMessage):
valid_values = set([
'on',
@@ -1247,9 +1034,7 @@ class EventCollection(ConfluentMessage):
'event': event.get('event', None),
'severity': event['severity'],
'timestamp': event.get('timestamp', None),
'message': event.get('message', None),
'record_id': event.get('record_id', None),
'log_id': event.get('log_id', None),
}
if event['severity'] not in valid_health_values:
raise exc.NotImplementedException(
@@ -1303,15 +1088,14 @@ class AsyncSession(ConfluentMessage):
self.kvpairs = {'asyncid': id}
class User(ConfluentMessage):
def __init__(self, uid, username, privilege_level, name=None, expiration=None):
def __init__(self, uid, username, privilege_level, name=None):
self.desc = 'foo'
self.stripped = False
self.notnode = name is None
kvpairs = {'username': {'value': username},
'password': {'value': '', 'type': 'password'},
'privilege_level': {'value': privilege_level},
'enabled': {'value': ''},
'expiration': {'value': expiration},
'enabled': {'value': ''}
}
if self.notnode:
self.kvpairs = kvpairs
@@ -1330,7 +1114,6 @@ class UserCollection(ConfluentMessage):
entry = {
'uid': user['uid'],
'username': user['name'],
'expiration': user.get('expiration', None),
'privilege_level': user['access']['privilege_level']
}
userlist.append(entry)
@@ -1446,76 +1229,6 @@ class KeyValueData(ConfluentMessage):
else:
self.kvpairs = {name: kvdata}
class Array(ConfluentMessage):
def __init__(self, name, disks=None, raid=None, volumes=None,
id=None, capacity=None, available=None):
self.kvpairs = {
name: {
'type': 'array',
'disks': disks,
'raid': raid,
'id': id,
'volumes': volumes,
'capacity': capacity,
'available': available,
}
}
class Volume(ConfluentMessage):
def __init__(self, name, volname, size, state, array, stripsize=None):
self.kvpairs = {
name: {
'type': 'volume',
'name': simplify_name(volname),
'label': volname,
'stripsize': stripsize,
'size': size,
'state': state,
'array': array,
}
}
class Disk(ConfluentMessage):
valid_states = set([
'jbod',
'unconfigured',
'hotspare',
'online',
])
state_aliases = {
'unconfigured good': 'unconfigured',
'global hot spare': 'hotspare',
'dedicated hot spare': 'hotspare',
}
def _normalize_state(self, instate):
newstate = instate.lower()
if newstate in self.valid_states:
return newstate
elif newstate in self.state_aliases:
return self.state_aliases[newstate]
raise Exception("Unknown state {0}".format(instate))
def __init__(self, name, label=None, description=None,
diskid=None, state=None, serial=None, fru=None,
array=None):
state = self._normalize_state(state)
self.kvpairs = {
name: {
'type': 'disk',
'name': simplify_name(label),
'label': label,
'description': description,
'diskid': diskid,
'state': state,
'serial': serial,
'fru': fru,
'array': array,
}
}
class LEDStatus(ConfluentMessage):
readonly = True
@@ -1663,12 +1376,12 @@ class NTPServer(ConfluentMessage):
class License(ConfluentMessage):
readonly = True
def __init__(self, name=None, kvm=None, feature=None, state=None):
def __init__(self, name=None, kvm=None):
self.notnode = name is None
self.desc = 'License'
kv = []
kv.append({'kvm_availability': str(kvm), 'feature': feature, 'state': state})
kv.append({'kvm_availability': str(kvm)})
if self.notnode:
self.kvpairs = {'License': kv}
else:
-13
View File
@@ -24,19 +24,6 @@ import eventlet.support.greendns
getaddrinfo = eventlet.support.greendns.getaddrinfo
def mask_to_cidr(mask):
maskn = socket.inet_pton(socket.AF_INET, mask)
maskn = struct.unpack('!I', maskn)[0]
cidr = 32
while maskn & 0b1 == 0 and cidr > 0:
cidr -= 1
maskn >>= 1
return cidr
def cidr_to_mask(cidr):
return socket.inet_ntop(
socket.AF_INET, struct.pack('!I', (2**32 - 1) ^ (2**(32 - cidr) - 1)))
def ip_on_same_subnet(first, second, prefix):
addrinf = socket.getaddrinfo(first, None, 0, socket.SOCK_STREAM)[0]
fam = addrinf[0]
+15 -31
View File
@@ -1,6 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2016-2019 Lenovo
# 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.
@@ -105,11 +105,7 @@ def close_enough(fuzz, literal):
if fuzz == literal:
return True
fuzz = '^' + fuzz.replace('-', '[/: -]') + '$'
try:
matcher = re.compile(fuzz)
except Exception:
raise exc.InvalidArgumentException(
'Invalid regular expression specified')
matcher = re.compile(fuzz)
return bool(matcher.match(literal))
@@ -175,7 +171,7 @@ def _extract_neighbor_data_b(args):
args are carried as a tuple, because of eventlet convenience
"""
switch, password, user, force = args[:4]
switch, password, user, force = args
vintage = _neighdata.get(switch, {}).get('!!vintage', 0)
now = util.monotonic_time()
if vintage > (now - 60) and not force:
@@ -224,19 +220,17 @@ def _extract_neighbor_data_b(args):
_neighdata[switch] = lldpdata
def update_switch_data(switch, configmanager, force=False, retexc=False):
def update_switch_data(switch, configmanager, force=False):
switchcreds = netutil.get_switchcreds(configmanager, (switch,))[0]
ndr = _extract_neighbor_data(switchcreds + (force, retexc))
if retexc and isinstance(ndr, Exception):
raise ndr
_extract_neighbor_data(switchcreds + (force,))
return _neighdata.get(switch, {})
def update_neighbors(configmanager, force=False, retexc=False):
return _update_neighbors_backend(configmanager, force, retexc)
def update_neighbors(configmanager, force=False):
return _update_neighbors_backend(configmanager, force)
def _update_neighbors_backend(configmanager, force, retexc):
def _update_neighbors_backend(configmanager, force):
global _neighdata
global _neighbypeerid
vintage = _neighdata.get('!!vintage', 0)
@@ -247,7 +241,7 @@ def _update_neighbors_backend(configmanager, force, retexc):
_neighbypeerid = {'!!vintage': now}
switches = netutil.list_switches(configmanager)
switchcreds = netutil.get_switchcreds(configmanager, switches)
switchcreds = [ x + (force, retexc) for x in switchcreds]
switchcreds = [ x + (force,) for x in switchcreds]
pool = GreenPool(64)
for ans in pool.imap(_extract_neighbor_data, switchcreds):
yield ans
@@ -264,15 +258,9 @@ def _extract_neighbor_data(args):
return
try:
with _updatelocks[switch]:
return _extract_neighbor_data_b(args)
except Exception as e:
yieldexc = False
if len(args) >= 5:
yieldexc = args[4]
if yieldexc:
return e
else:
log.logtrace()
_extract_neighbor_data_b(args)
except Exception:
log.logtrace()
if __name__ == '__main__':
# a quick one-shot test, args are switch and snmpv1 string for now
@@ -339,9 +327,7 @@ def _handle_neighbor_query(pathcomponents, configmanager):
# guaranteed
if (parms['by-peerid'] not in _neighbypeerid and
_neighbypeerid.get('!!vintage', 0) < util.monotonic_time() - 60):
for x in update_neighbors(configmanager, retexc=True):
if isinstance(x, Exception):
raise x
list(update_neighbors(configmanager))
if parms['by-peerid'] not in _neighbypeerid:
raise exc.NotFoundException('No matching peer known')
return _dump_neighbordatum(_neighbypeerid[parms['by-peerid']])
@@ -350,11 +336,9 @@ def _handle_neighbor_query(pathcomponents, configmanager):
if listrequested not in multi_selectors | single_selectors:
raise exc.NotFoundException('{0} is not found'.format(listrequested))
if 'by-switch' in parms:
update_switch_data(parms['by-switch'], configmanager, retexc=True)
update_switch_data(parms['by-switch'], configmanager)
else:
for x in update_neighbors(configmanager, retexc=True):
if isinstance(x, Exception):
raise x
list(update_neighbors(configmanager))
return list_info(parms, listrequested)
+12 -34
View File
@@ -1,6 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2016-2019 Lenovo
# 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.
@@ -47,7 +47,6 @@ import eventlet.semaphore
import re
_macmap = {}
_apimacmap = {}
_macsbyswitch = {}
_nodesbymac = {}
_switchportmap = {}
@@ -212,7 +211,7 @@ def _map_switch_backend(args):
maccounts[ifname] = 1
else:
maccounts[ifname] += 1
newmacs = {}
_macsbyswitch[switch] = {}
for mac in mactobridge:
# We want to merge it so that when a mac appears in multiple
# places, it is captured.
@@ -224,10 +223,10 @@ def _map_switch_backend(args):
_macmap[mac].append((switch, ifname, maccounts[ifname]))
else:
_macmap[mac] = [(switch, ifname, maccounts[ifname])]
if ifname in newmacs:
newmacs[ifname].append(mac)
if ifname in _macsbyswitch[switch]:
_macsbyswitch[switch][ifname].append(mac)
else:
newmacs[ifname] = [mac]
_macsbyswitch[switch][ifname] = [mac]
nodename = _nodelookup(switch, ifname)
if nodename is not None:
if mac in _nodesbymac and _nodesbymac[mac][0] != nodename:
@@ -239,7 +238,6 @@ def _map_switch_backend(args):
_nodesbymac[mac] = (None, None)
else:
_nodesbymac[mac] = (nodename, maccounts[ifname])
_macsbyswitch[switch] = newmacs
switchbackoff = 30
@@ -297,7 +295,6 @@ def _finish_update(completions):
def _full_updatemacmap(configmanager):
global vintage
global _apimacmap
global _macmap
global _nodesbymac
global _switchportmap
@@ -310,17 +307,16 @@ def _full_updatemacmap(configmanager):
_macmap = {}
_nodesbymac = {}
_switchportmap = {}
_macsbyswitch = {}
if configmanager.tenant is not None:
raise exc.ForbiddenRequest(
'Network topology not available to tenants')
# here's a list of switches... need to add nodes that are switches
nodelocations = configmanager.get_node_attributes(
configmanager.list_nodes(), ('type', 'net*.switch', 'net*.switchport'))
configmanager.list_nodes(), ('net*.switch', 'net*.switchport'))
switches = set([])
for node in nodelocations:
cfg = nodelocations[node]
if cfg.get('type', {}).get('value', None) == 'switch':
switches.add(node)
for attr in cfg:
if not attr.endswith('.switch') or 'value' not in cfg[attr]:
continue
@@ -344,15 +340,11 @@ def _full_updatemacmap(configmanager):
_switchportmap[curswitch][portname] = None
else:
_switchportmap[curswitch][portname] = node
for switch in list(_macsbyswitch):
if switch not in switches:
del _macsbyswitch[switch]
switchauth = get_switchcreds(configmanager, switches)
pool = GreenPool(64)
for ans in pool.imap(_map_switch, switchauth):
vintage = util.monotonic_time()
yield ans
_apimacmap = _macmap
endtime = util.monotonic_time()
duration = endtime - start
duration = duration * 15 # wait 15 times as long as it takes to walk
@@ -380,7 +372,7 @@ def handle_api_request(configmanager, inputdata, operation, pathcomponents):
if (operation in ('update', 'create') and
pathcomponents == ['networking', 'macs', 'rescan']):
if inputdata != {'rescan': 'start'}:
raise exc.InvalidArgumentException('Input must be rescan=start')
raise exc.InvalidArgumentException()
eventlet.spawn_n(rescan, configmanager)
return [msg.KeyValueData({'rescan': 'started'})]
raise exc.NotImplementedException(
@@ -432,7 +424,7 @@ def handle_read_api_request(pathcomponents, configmanager):
elif pathcomponents[2] == 'by-mac':
if len(pathcomponents) == 3:
return [msg.ChildCollection(x.replace(':', '-'))
for x in sorted(list(_apimacmap))]
for x in sorted(list(_macmap))]
elif len(pathcomponents) == 4:
return dump_macinfo(pathcomponents[-1])
elif pathcomponents[2] == 'by-switch':
@@ -458,27 +450,13 @@ def handle_read_api_request(pathcomponents, configmanager):
portname = portname.replace('-', '/')
maclist = _macsbyswitch[switchname][portname]
except KeyError:
foundsomemacs = False
if switchname in _macsbyswitch:
try:
matcher = re.compile(portname)
except Exception:
raise exc.InvalidArgumentException('Invalid regular expression specified')
maclist = []
for actualport in _macsbyswitch[switchname]:
if bool(matcher.match(actualport)):
foundsomemacs = True
maclist = maclist + _macsbyswitch[switchname][actualport]
if not foundsomemacs:
raise exc.NotFoundException('No known macs for switch {0} '
'port {1}'.format(switchname,
portname))
raise exc.NotFoundException('No known macs for switch {0} '
'port {1}'.format(switchname,
portname))
return [msg.ChildCollection(x.replace(':', '-'))
for x in sorted(maclist)]
if len(pathcomponents) == 8:
return dump_macinfo(pathcomponents[-1])
elif pathcomponents[2] == 'rescan':
return [msg.KeyValueData({'scanning': mapupdating.locked()})]
raise exc.NotFoundException('Unrecognized path {0}'.format(
'/'.join(pathcomponents)))
@@ -42,12 +42,10 @@ def get_switchcreds(configmanager, switches):
def list_switches(configmanager):
nodelocations = configmanager.get_node_attributes(
configmanager.list_nodes(), ('type', 'net*.switch', 'net*.switchport'))
configmanager.list_nodes(), ('net*.switch', 'net*.switchport'))
switches = set([])
for node in nodelocations:
cfg = nodelocations[node]
if cfg.get('type', {}).get('value', None) == 'switch':
switches.add(node)
for attr in cfg:
if not attr.endswith('.switch') or 'value' not in cfg[attr]:
continue
@@ -1,5 +1,5 @@
# Copyright 2014 IBM Corporation
# 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.
@@ -17,7 +17,6 @@ import confluent.exceptions as exc
import confluent.messages as msg
import confluent.config.attributes as allattributes
import confluent.util as util
from fnmatch import fnmatch
def retrieve(nodes, element, configmanager, inputdata):
@@ -40,7 +39,6 @@ def retrieve_nodegroup(nodegroup, element, configmanager, inputdata):
if element == 'all':
theattrs = set(allattributes.node).union(set(grpcfg))
theattrs.add('nodes')
theattrs.add('noderange')
for attribute in sorted(theattrs):
if attribute == 'groups':
continue
@@ -53,10 +51,6 @@ def retrieve_nodegroup(nodegroup, element, configmanager, inputdata):
val = grpcfg[attribute]
else:
val = {'value': None}
if attribute == 'noderange':
val['desc'] = 'The noderange this group is expanded ' \
'to when used in noderange, exclusive with static ' \
'nodes'
if attribute.startswith('secret.'):
yield msg.CryptedAttributes(
kv={attribute: val},
@@ -75,8 +69,6 @@ def retrieve_nodegroup(nodegroup, element, configmanager, inputdata):
for attribute in sorted(list(grpcfg)):
currattr = grpcfg[attribute]
if attribute == 'nodes':
if not currattr:
continue
desc = 'The nodes belonging to this group'
elif attribute == 'noderange':
desc = 'A dynamic noderange that this group refers to in noderange expansion'
@@ -100,15 +92,15 @@ def retrieve_nodegroup(nodegroup, element, configmanager, inputdata):
kv={attribute: currattr},
desc=desc)
else:
print(attribute)
print(repr(currattr))
print attribute
print repr(currattr)
raise Exception("BUGGY ATTRIBUTE FOR NODEGROUP")
def retrieve_nodes(nodes, element, configmanager, inputdata):
attributes = configmanager.get_node_attributes(nodes)
if element[-1] == 'all':
for node in util.natural_sort(nodes):
for node in nodes:
theattrs = set(allattributes.node).union(set(attributes[node]))
for attribute in sorted(theattrs):
if attribute in attributes[node]: # have a setting for it
@@ -133,7 +125,7 @@ def retrieve_nodes(nodes, element, configmanager, inputdata):
allattributes.node.get(
attribute, {}).get('description', ''))
elif element[-1] == 'current':
for node in util.natural_sort(list(attributes)):
for node in sorted(list(attributes)):
for attribute in sorted(attributes[node].iterkeys()):
currattr = attributes[node][attribute]
try:
@@ -149,8 +141,8 @@ def retrieve_nodes(nodes, element, configmanager, inputdata):
yield msg.ListAttributes(
node, {attribute: currattr}, desc)
else:
print(attribute)
print(repr(currattr))
print attribute
print repr(currattr)
raise Exception("BUGGY ATTRIBUTE FOR NODE")
@@ -164,11 +156,6 @@ def update(nodes, element, configmanager, inputdata):
def update_nodegroup(group, element, configmanager, inputdata):
if 'rename' in element:
namemap = {}
namemap[group] = inputdata.attribs['rename']
configmanager.rename_nodegroups(namemap)
return yield_rename_resources(namemap, isnode=False)
try:
clearattribs = []
for attrib in inputdata.attribs.iterkeys():
@@ -208,44 +195,18 @@ def create(nodes, element, configmanager, inputdata):
if nodes is not None and element[-1] == 'expression':
return _expand_expression(nodes, configmanager, inputdata)
def yield_rename_resources(namemap, isnode):
for node in namemap:
if isnode:
yield msg.RenamedNode(node, namemap[node])
else:
yield msg.RenamedResource(node, namemap[node])
def update_nodes(nodes, element, configmanager, inputdata):
updatedict = {}
if not nodes:
raise exc.InvalidArgumentException(
'No action to take, noderange is empty (if trying to define '
'group attributes, use nodegroupattrib)')
if 'rename' in element:
namemap = {}
for node in nodes:
rename = inputdata.get_attributes(node)
namemap[node] = rename['rename']
configmanager.rename_nodes(namemap)
return yield_rename_resources(namemap, isnode=True)
for node in nodes:
updatenode = inputdata.get_attributes(node, allattributes.node)
updatenode = inputdata.get_attributes(node)
clearattribs = []
if updatenode:
for attrib in list(updatenode):
for attrib in updatenode.iterkeys():
if updatenode[attrib] is None:
del updatenode[attrib]
if attrib in allattributes.node or attrib.startswith('custom.') or attrib.startswith('net.'):
clearattribs.append(attrib)
else:
foundattrib = False
for candattrib in allattributes.node:
if fnmatch(candattrib, attrib):
clearattribs.append(candattrib)
foundattrib = True
if not foundattrib:
raise exc.InvalidArgumentException("No attribute matches '" + attrib + "' (try wildcard if trying to clear a group)")
clearattribs.append(attrib)
if len(clearattribs) > 0:
for attrib in clearattribs:
del updatenode[attrib]
configmanager.clear_node_attributes([node], clearattribs)
updatedict[node] = updatenode
try:
@@ -1,147 +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.
#Noncritical:
# - One or more temperature sensors is in the warning range;
# - A panic dump exists in flash.
#Critical:
# - One or more temperature sensors is in the failure range;
# - One or more fans are running < 100 RPM;
# - One power supply is off.
import eventlet
import eventlet.queue as queue
import confluent.exceptions as exc
webclient = eventlet.import_patched('pyghmi.util.webclient')
import confluent.messages as msg
import confluent.util as util
class SwitchSensor(object):
def __init__(self, name, states, value=None, health=None):
self.name = name
self.value = value
self.states = states
self.health = health
def cnos_login(node, configmanager, creds):
wc = webclient.SecureHTTPConnection(node, port=443, verifycallback=util.TLSCertVerifier(
configmanager, node, 'pubkeys.tls_hardwaremanager').verify_cert)
wc.set_basic_credentials(creds[node]['secret.hardwaremanagementuser']['value'], creds[node]['secret.hardwaremanagementpassword']['value'])
wc.request('GET', '/nos/api/login/')
rsp = wc.getresponse()
body = rsp.read()
if rsp.status == 401: # CNOS gives 401 on first attempt...
wc.request('GET', '/nos/api/login/')
rsp = wc.getresponse()
body = rsp.read()
if rsp.status >= 200 and rsp.status < 300:
return wc
raise exc.TargetEndpointBadCredentials('Unable to authenticate')
def update(nodes, element, configmanager, inputdata):
for node in nodes:
yield msg.ConfluentNodeError(node, 'Not Implemented')
def delete(nodes, element, configmanager, inputdata):
for node in nodes:
yield msg.ConfluentNodeError(node, 'Not Implemented')
def create(nodes, element, configmanager, inputdata):
for node in nodes:
yield msg.ConfluentNodeError(node, 'Not Implemented')
def retrieve(nodes, element, configmanager, inputdata):
results = queue.LightQueue()
workers = set([])
if element == ['power', 'state']:
for node in nodes:
yield msg.PowerState(node=node, state='on')
return
elif element == ['health', 'hardware']:
creds = configmanager.get_node_attributes(
nodes, ['secret.hardwaremanagementuser', 'secret.hardwaremanagementpassword'], decrypt=True)
for node in nodes:
workers.add(eventlet.spawn(retrieve_health, configmanager, creds,
node, results))
else:
for node in nodes:
yield msg.ConfluentNodeError(node, 'Not Implemented')
return
currtimeout = 10
while workers:
try:
datum = results.get(10)
while datum:
if datum:
yield datum
datum = results.get_nowait()
except queue.Empty:
pass
eventlet.sleep(0.001)
for t in list(workers):
if t.dead:
workers.discard(t)
try:
while True:
datum = results.get_nowait()
if datum:
yield datum
except queue.Empty:
pass
def retrieve_health(configmanager, creds, node, results):
wc = cnos_login(node, configmanager, creds)
hinfo = wc.grab_json_response('/nos/api/sysinfo/globalhealthstatus')
summary = hinfo['status'].lower()
if summary == 'noncritical':
summary = 'warning'
results.put(msg.HealthSummary(summary, name=node))
state = None
badreadings = []
if summary != 'ok': # temperature or dump or fans or psu
wc.grab_json_response('/nos/api/sysinfo/panic_dump')
switchinfo = wc.grab_json_response('/nos/api/sysinfo/panic_dump')
if switchinfo:
badreadings.append(
SwitchSensor('Panicdump', ['Present'], health='warning'))
switchinfo = wc.grab_json_response('/nos/api/sysinfo/temperatures')
for temp in switchinfo:
if temp == 'Temperature threshold':
continue
if switchinfo[temp]['State'] != 'OK':
temphealth = switchinfo[temp]['State'].lower()
if temphealth == 'noncritical':
temphealth = 'warning'
tempval = switchinfo[temp]['Temp']
badreadings.append(
SwitchSensor(temp, [], value=tempval, health=temphealth))
switchinfo = wc.grab_json_response('/nos/api/sysinfo/fans')
for fan in switchinfo:
if switchinfo[fan]['speed-rpm'] < 100:
badreadings.append(
SwitchSensor(fan, [], value=switchinfo[fan]['speed-rpm'],
health='critical'))
switchinfo = wc.grab_json_response('/nos/api/sysinfo/power')
for psu in switchinfo:
if switchinfo[psu]['State'] != 'Normal ON':
psuname = switchinfo[psu]['Name']
badreadings.append(
SwitchSensor(psuname, states=[switchinfo[psu]['State']],
health='critical'))
results.put(msg.SensorReadings(badreadings, name=node))
@@ -14,7 +14,6 @@
import confluent.core as core
import confluent.messages as msg
import pyghmi.exceptions as pygexc
import confluent.exceptions as exc
def update(nodes, element, configmanager, inputdata):
emebs = configmanager.get_node_attributes(
@@ -37,5 +36,3 @@ def update(nodes, element, configmanager, inputdata):
yield rsp
except pygexc.UnsupportedFunctionality as uf:
yield msg.ConfluentNodeError(node, str(uf))
except exc.TargetEndpointUnreachable as uf:
yield msg.ConfluentNodeError(node, str(uf))
@@ -1,5 +1,5 @@
# 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.
@@ -19,59 +19,18 @@ import confluent.firmwaremanager as firmwaremanager
import confluent.interface.console as conapi
import confluent.messages as msg
import confluent.util as util
import copy
import errno
import eventlet
import eventlet.event
import eventlet.green.threading as threading
import eventlet.greenpool as greenpool
import eventlet.queue as queue
import eventlet.support.greendns
from fnmatch import fnmatch
import os
import pyghmi.constants as pygconstants
import pyghmi.exceptions as pygexc
import pyghmi.storage as storage
console = eventlet.import_patched('pyghmi.ipmi.console')
ipmicommand = eventlet.import_patched('pyghmi.ipmi.command')
import socket
import ssl
import traceback
if not hasattr(ssl, 'SSLEOFError'):
ssl.SSLEOFError = None
pci_cache = {}
def get_dns_txt(qstring):
return eventlet.support.greendns.resolver.query(
qstring, 'TXT')[0].strings[0].replace('i=', '')
def get_pci_text_from_ids(subdevice, subvendor, device, vendor):
fqpi = '{0}.{1}.{2}.{3}'.format(subdevice, subvendor, device, vendor)
if fqpi in pci_cache:
return pci_cache[fqpi]
vendorstr = None
try:
vendorstr = get_dns_txt('{0}.pci.id.ucw.cz'.format(subvendor))
except Exception:
try:
vendorstr = get_dns_txt('{0}.pci.id.ucw.cz'.format(vendor))
except Exception:
pass
devstr = None
try:
devstr = get_dns_txt(fqpi + '.pci.id.ucw.cz')
except Exception:
try:
devstr = get_dns_txt('{0}.{1}.pci.id.ucw.cz'.format(
device, vendor))
except Exception:
pass
if vendorstr and devstr:
pci_cache[fqpi] = vendorstr, devstr
return vendorstr, devstr
# There is something not right with the RLocks used in pyghmi when
@@ -137,8 +96,7 @@ def hex2bin(hexstring):
def simplify_name(name):
return name.lower().replace(' ', '_').replace('/', '-').replace(
'_-_', '-')
return name.lower().replace(' ', '_').replace('/', '-')
def sanitize_invdata(indata):
@@ -171,27 +129,14 @@ class IpmiCommandWrapper(ipmicommand.Command):
def __init__(self, node, cfm, **kwargs):
self.cfm = cfm
self.node = node
self.sensormap = {}
self._inhealth = False
self._lasthealth = None
kwargs['keepalive'] = False
self._attribwatcher = cfm.watch_attributes(
(node,), ('secret.hardwaremanagementuser', 'collective.manager',
'secret.hardwaremanagementpassword', 'secret.ipmikg',
'hardwaremanagement.manager'), self._attribschanged)
super(self.__class__, self).__init__(**kwargs)
self.setup_confluent_keyhandler()
try:
os.makedirs('/var/cache/confluent/ipmi/')
except OSError as e:
if e.errno != errno.EEXIST or not os.path.isdir(
'/var/cache/confluent/ipmi/'):
raise
try:
self.set_sdr_cachedir('/var/cache/confluent/ipmi/')
except Exception:
pass
def setup_confluent_keyhandler(self):
self.register_key_handler(util.TLSCertVerifier(
@@ -233,6 +178,7 @@ def _ipmi_evtloop():
waiter = _ipmiwaiters.pop()
waiter.send()
except: # TODO(jbjohnso): log the trace into the log
import traceback
traceback.print_exc()
@@ -361,40 +307,28 @@ class IpmiConsole(conapi.Console):
self.solconnection.send_break()
def perform_requests(operator, nodes, element, cfg, inputdata, realop):
def perform_requests(operator, nodes, element, cfg, inputdata):
cryptit = cfg.decrypt
cfg.decrypt = True
configdata = cfg.get_node_attributes(nodes, _configattributes)
cfg.decrypt = cryptit
resultdata = queue.LightQueue()
livingthreads = set([])
numnodes = len(nodes)
for node in nodes:
livingthreads.add(_ipmiworkers.spawn(
perform_request, operator, node, element, configdata, inputdata,
cfg, resultdata, realop))
cfg, resultdata))
while livingthreads:
try:
bundle = []
datum = resultdata.get(timeout=10)
while datum:
if datum != 'Done':
if isinstance(datum, Exception):
raise datum
if (hasattr(datum, 'kvpairs') and datum.kvpairs and
len(datum.kvpairs) == 1):
bundle.append((datum.kvpairs.keys()[0], datum))
numnodes -= 1
else:
yield datum
timeout = 0.1 if numnodes else 0.001
datum = resultdata.get(timeout=timeout)
yield datum
datum = resultdata.get_nowait()
except queue.Empty:
pass
finally:
for datum in sorted(
bundle, key=lambda x: util.naturalize_string(x[0])):
yield datum[1]
for t in list(livingthreads):
if t.dead:
livingthreads.discard(t)
@@ -409,10 +343,10 @@ def perform_requests(operator, nodes, element, cfg, inputdata, realop):
def perform_request(operator, node, element,
configdata, inputdata, cfg, results, realop):
configdata, inputdata, cfg, results):
try:
return IpmiHandler(operator, node, element, configdata, inputdata,
cfg, results, realop).handle_request()
cfg, results).handle_request()
except pygexc.IpmiException as ipmiexc:
excmsg = str(ipmiexc)
if excmsg in ('Session no longer connected', 'timeout'):
@@ -424,8 +358,8 @@ def perform_request(operator, node, element,
results.put(msg.ConfluentTargetTimeout(node, str(tu)))
except ssl.SSLEOFError:
results.put(msg.ConfluentNodeError(
node, 'Unable to communicate with the https server on '
'the target BMC'))
self.node, 'Unable to communicate with the https server on '
'the target BMC'))
except exc.PubkeyInvalid:
results.put(msg.ConfluentNodeError(
node,
@@ -434,16 +368,16 @@ def perform_request(operator, node, element,
except pygexc.InvalidParameterValue as e:
results.put(msg.ConfluentNodeError(node, str(e)))
except Exception as e:
results.put(msg.ConfluentNodeError(node, 'Unexpected Error: {0}'.format(str(e))))
traceback.print_exc()
results.put(e)
raise
finally:
results.put('Done')
persistent_ipmicmds = {}
class IpmiHandler(object):
def __init__(self, operation, node, element, cfd, inputdata, cfg, output,
realop):
def __init__(self, operation, node, element, cfd, inputdata, cfg, output):
self.sensormap = {}
self.invmap = {}
self.output = output
self.sensorcategory = None
@@ -451,23 +385,20 @@ class IpmiHandler(object):
self.error = None
eventlet.sleep(0)
self.cfg = cfd[node]
self.current_user = cfg.current_user
self.loggedin = False
self.node = node
self.element = element
self.op = operation
self.realop = realop
connparams = get_conn_params(node, self.cfg)
self.ipmicmd = None
self.inputdata = inputdata
self.tenant = cfg.tenant
tenant = cfg.tenant
while ((node, tenant) not in persistent_ipmicmds or
if ((node, tenant) not in persistent_ipmicmds or
not persistent_ipmicmds[(node, tenant)].ipmi_session.logged or
persistent_ipmicmds[(node, tenant)].ipmi_session.broken):
try:
persistent_ipmicmds[(node, tenant)].close_confluent()
persistent_ipmicmds[(node, tenant)].ipmi_session._mark_broken()
except KeyError: # was no previous session
pass
try:
@@ -479,17 +410,13 @@ class IpmiHandler(object):
ipmisess = persistent_ipmicmds[(node, tenant)].ipmi_session
begin = util.monotonic_time()
while ((not (ipmisess.broken or self.loggedin)) and
while ((not (self.broken or self.loggedin)) and
(util.monotonic_time() - begin) < 30):
ipmisess.wait_for_rsp(31 - (util.monotonic_time() - begin))
if self.broken or self.loggedin:
break
cfd = cfg.get_node_attributes(node, _configattributes, decrypt=True)
self.cfg = cfd[node]
connparams = get_conn_params(node, self.cfg)
ipmisess._mark_broken()
# raise exc.TargetEndpointUnreachable(
# "Login process to " + connparams['bmc'] + " died")
if not (self.broken or self.loggedin):
ipmisess._mark_broken()
raise exc.TargetEndpointUnreachable(
"Login process to " + connparams['bmc'] + " died")
except socket.gaierror as ge:
if ge[0] == -2:
raise exc.TargetEndpointUnreachable(ge[1])
@@ -516,10 +443,6 @@ class IpmiHandler(object):
self.output.put(msg.ConfluentTargetTimeout(
self.node, self.error))
return
elif 'Invalid Session ID' in self.error:
self.output.put(msg.ConfluentTargetTimeout(
self.node, 'Temporary Login Error'))
return
elif ('Unauthorized' in self.error or
'Incorrect password' in self.error):
self.output.put(
@@ -539,8 +462,6 @@ class IpmiHandler(object):
self.identify()
elif self.element[0] == 'sensors':
self.handle_sensors()
elif self.element[:2] == ['configuration', 'storage']:
self.handle_storage()
elif self.element[0] == 'configuration':
self.handle_configuration()
elif self.element[:3] == ['inventory', 'firmware', 'updates']:
@@ -561,16 +482,12 @@ class IpmiHandler(object):
self.decode_alert()
elif self.element == ['console', 'license']:
self.handle_license()
elif self.element == ['support', 'servicedata']:
self.handle_servicedata_fetch()
elif self.element == ['description']:
self.handle_description()
else:
raise Exception('Not Implemented')
def handle_update(self):
u = firmwaremanager.Updater(self.node, self.ipmicmd.update_firmware,
self.inputdata.nodefile(self.node), self.tenant,
self.inputdata.filename, self.tenant,
bank=self.inputdata.bank)
self.output.put(
msg.CreatedResource(
@@ -579,23 +496,14 @@ class IpmiHandler(object):
def handle_media_upload(self):
u = firmwaremanager.Updater(self.node, self.ipmicmd.upload_media,
self.inputdata.nodefile(self.node), self.tenant,
self.inputdata.filename, self.tenant,
type='mediaupload')
self.output.put(msg.CreatedResource(
'nodes/{0}/media/uploads/{1}'.format(self.node, u.name)))
def handle_servicedata_fetch(self):
u = firmwaremanager.Updater(
self.node, self.ipmicmd.get_diagnostic_data,
self.inputdata.nodefile(self.node), self.tenant, type='ffdc',
owner=self.current_user)
self.output.put(msg.CreatedResource(
'nodes/{0}/support/servicedata/{1}'.format(self.node, u.name)))
def handle_attach_media(self):
try:
self.ipmicmd.attach_remote_media(self.inputdata.nodefile(
self.node))
self.ipmicmd.attach_remote_media(self.inputdata.filename)
except pygexc.UnsupportedFunctionality as uf:
self.output.put(msg.ConfluentNodeError(self.node, str(uf)))
@@ -627,12 +535,6 @@ class IpmiHandler(object):
return self.handle_sysconfig()
elif self.element[1:3] == ['system', 'advanced']:
return self.handle_sysconfig(True)
elif self.element[1:3] == ['system', 'clear']:
return self.handle_sysconfigclear()
elif self.element[1:3] == ['management_controller', 'licenses']:
return self.handle_licenses()
elif self.element[1:3] == ['management_controller', 'save_licenses']:
return self.save_licenses()
raise Exception('Not implemented')
def decode_alert(self):
@@ -749,7 +651,6 @@ class IpmiHandler(object):
uid=data['uid'],
username=data['name'],
privilege_level=data['access']['privilege_level'],
expiration=data['expiration'],
name=self.node))
return
elif self.op == 'update':
@@ -758,16 +659,14 @@ class IpmiHandler(object):
if 'username' in user:
self.ipmicmd.set_user_name(uid=user['uid'],
name=user['username'])
if 'privilege_level' in user:
self.ipmicmd.set_user_access(uid=user['uid'],
privilege_level=user['privilege_level'])
if 'password' in user:
self.ipmicmd.set_user_password(uid=user['uid'],
password=user['password'])
self.ipmicmd.set_user_password(uid=user['uid'],
mode='enable', password=user['password'])
if 'privilege_level' in user:
self.ipmicmd.set_user_access(uid=user['uid'],
privilege_level=user[
'privilege_level'])
if 'enabled' in user:
if user['enabled'] == 'yes':
mode = 'enable'
@@ -808,7 +707,7 @@ class IpmiHandler(object):
sensors = self.ipmicmd.get_sensor_descriptions()
for sensor in sensors:
resourcename = sensor['name']
self.ipmicmd.sensormap[simplify_name(resourcename)] = resourcename
self.sensormap[simplify_name(resourcename)] = resourcename
def read_sensors(self, sensorname):
if sensorname == 'all':
@@ -829,16 +728,15 @@ class IpmiHandler(object):
readings.append(reading)
self.output.put(msg.SensorReadings(readings, name=self.node))
else:
if sensorname not in self.ipmicmd.sensormap:
self.make_sensor_map()
if sensorname not in self.ipmicmd.sensormap:
self.make_sensor_map()
if sensorname not in self.sensormap:
self.output.put(
msg.ConfluentTargetNotFound(self.node,
'Sensor not found'))
return
try:
reading = self.ipmicmd.get_sensor_reading(
self.ipmicmd.sensormap[sensorname])
self.sensormap[sensorname])
if hasattr(reading, 'health'):
reading.health = _str_health(reading.health)
self.output.put(
@@ -887,9 +785,6 @@ class IpmiHandler(object):
'Extended information unavailable, mismatch detected between '
'target certificate fingerprint and '
'pubkeys.tls_hardwaremanager attribute')
except pygexc.TemporaryError as e:
errorneeded = msg.ConfluentNodeError(
self.node, str(e))
self.output.put(msg.Firmware(items, self.node))
if errorneeded:
self.output.put(errorneeded)
@@ -926,14 +821,12 @@ class IpmiHandler(object):
if component == 'all':
for invdata in self.ipmicmd.get_inventory():
if invdata[1] is None:
newinf = {'present': False, 'information': None,
'name': invdata[0]}
newinf = {'present': False, 'information': None}
else:
sanitize_invdata(invdata[1])
newinf = {'present': True, 'information': invdata[1]}
newinf['name'] = invdata[1].get('name', invdata[0])
self.add_invitem(invitems, newinf)
newinf['name'] = invdata[0]
invitems.append(newinf)
else:
self.make_inventory_map()
compname = self.invmap.get(component, None)
@@ -942,13 +835,12 @@ class IpmiHandler(object):
return
invdata = self.ipmicmd.get_inventory_of_component(compname)
if invdata is None:
newinf = {'present': False, 'information': None,
'name': compname}
newinf = {'present': False, 'information': None}
else:
sanitize_invdata(invdata)
newinf = {'present': True, 'information': invdata,
'name': invdata.get('name', compname)}
self.add_invitem(invitems, newinf)
newinf = {'present': True, 'information': invdata}
newinf['name'] = compname
invitems.append(newinf)
except ssl.SSLEOFError:
errorneeded = msg.ConfluentNodeError(
self.node, 'Unable to communicate with the https server on '
@@ -965,131 +857,6 @@ class IpmiHandler(object):
if errorneeded:
self.output.put(errorneeded)
def add_invitem(self, invitems, newinf):
if newinf.get('information', None) and 'name' in newinf['information']:
newinf = copy.deepcopy(newinf)
del newinf['information']['name']
if (fnmatch(newinf['name'], 'Adapter ??:??:??') or fnmatch(
newinf['name'], 'PCIeGen? x*') or not newinf['name']):
myinf = newinf.get('information', {})
sdid = myinf.get('PCI Subsystem Device ID', None)
svid = myinf.get('PCI Subsystem Vendor ID', None)
did = myinf.get('PCI Device ID', None)
vid = myinf.get('PCI Vendor ID', None)
vstr, dstr = get_pci_text_from_ids(sdid, svid, did, vid)
if vstr:
newinf['information']['PCI Vendor'] = vstr
if dstr:
newinf['name'] = dstr
invitems.append(newinf)
def handle_storage(self):
if self.element[-1] == '':
self.element = self.element[:-1]
storelem = self.element[2:]
if 'read' == self.op:
return self._show_storage(storelem)
elif 'update' == self.realop:
return self._update_storage(storelem)
elif 'delete' == self.op:
return self._delete_storage(storelem)
elif 'create' == self.realop:
return self._create_storage(storelem)
def _delete_storage(self, storelem):
if len(storelem) < 2 or storelem[0] != 'volumes':
raise exc.InvalidArgumentException('Must target a specific volume')
volname = storelem[-1]
curr = self.ipmicmd.get_storage_configuration()
volumes = []
volsfound = False
toremove = storage.ConfigSpec(arrays=[storage.Array(volumes=volumes)])
for pool in curr.arrays:
for vol in pool.volumes:
if simplify_name(vol.name) == volname:
volsfound = True
volumes.append(vol)
if not volsfound:
self.output.put(msg.ConfluentTargetNotFound(
self.node, "No volume named '{0}' found".format(volname)))
return
self.ipmicmd.remove_storage_configuration(toremove)
self.output.put(msg.DeletedResource(volname))
def _create_storage(self, storelem):
if 'volumes' not in storelem:
raise exc.InvalidArgumentException('Can only create volumes')
vols = []
thedisks = None
currcfg = self.ipmicmd.get_storage_configuration()
currnames = []
for arr in currcfg.arrays:
arrname = '{0}-{1}'.format(*arr.id)
for vol in arr.volumes:
currnames.append(vol.name)
disks = []
vols = []
vol = self.inputdata.inputbynode[self.node][0]
raidlvl = vol['raidlevel']
for disk in currcfg.disks:
if simplify_name(disk.name) in vol['disks']:
disks.append(disk)
elif (disk.status == 'Unconfigured Good' and
vol['disks'][0] in ('remainder', 'rest')):
disks.append(disk)
elif vol['disks'][0] == 'all':
disks.append(disk)
for vol in self.inputdata.inputbynode[self.node]:
if thedisks and thedisks != vol['disks']:
raise exc.InvalidArgumentException(
'Not currently supported to create multiple arrays '
'in a single request')
if raidlvl and vol['raidlevel'] != raidlvl:
raise exc.InvalidArgumentException('Cannot mix raid levels in '
'a single array')
vols.append(storage.Volume(name=vol['name'], size=vol['size'], stripsize=vol['stripsize']))
newcfg = storage.ConfigSpec(
arrays=(storage.Array(raid=raidlvl, disks=disks, volumes=vols),))
self.ipmicmd.apply_storage_configuration(newcfg)
for vol in self.inputdata.inputbynode[self.node]:
if vol['name'] is None:
newcfg = self.ipmicmd.get_storage_configuration()
for arr in newcfg.arrays:
arrname = '{0}-{1}'.format(*arr.id)
for vol in arr.volumes:
if vol.name not in currnames:
self.output.put(
msg.Volume(self.node, vol.name, vol.size,
vol.status, arrname))
return
else:
self._show_storage(storelem[:1] + [vol['name']])
def _update_storage(self, storelem):
if storelem[0] == 'disks':
if len(storelem) == 1:
raise exc.InvalidArgumentException('Must target a disk')
self.set_disk(storelem[-1],
self.inputdata.inputbynode[self.node])
self._show_storage(storelem)
def _show_storage(self, storelem):
if storelem[0] == 'disks':
if len(storelem) == 1:
return self.list_disks()
return self.show_disk(storelem[1])
elif storelem[0] == 'arrays':
if len(storelem) == 1:
return self.list_arrays()
return self.show_array(storelem[1])
elif storelem[0] == 'volumes':
if len(storelem) == 1:
return self.list_volumes()
return self.show_volume(storelem[1])
elif storelem[0] == 'all':
return self._show_all_storage()
def handle_sensors(self):
if self.element[-1] == '':
self.element = self.element[:-1]
@@ -1113,116 +880,6 @@ class IpmiHandler(object):
return True
return False
def set_disk(self, name, state):
scfg = self.ipmicmd.get_storage_configuration()
for disk in scfg.disks:
if (name == 'all' or simplify_name(disk.name) == name or
disk == name):
disk.status = state
self.ipmicmd.apply_storage_configuration(
storage.ConfigSpec(disks=scfg.disks))
def _show_all_storage(self):
scfg = self.ipmicmd.get_storage_configuration()
for disk in scfg.disks:
self.output.put(
msg.Disk(self.node, disk.name, disk.description,
disk.id, disk.status, disk.serial,
disk.fru))
for arr in scfg.arrays:
for disk in arr.disks:
self.output.put(
msg.Disk(self.node, disk.name, disk.description,
disk.id, disk.status, disk.serial,
disk.fru, array='{0}-{1}'.format(*arr.id)))
for disk in arr.hotspares:
self.output.put(
msg.Disk(self.node, disk.name, disk.description,
disk.id, disk.status, disk.serial,
disk.fru, array='{0}-{1}'.format(*arr.id)))
for arr in scfg.arrays:
arrname = '{0}-{1}'.format(*arr.id)
self._detail_array(arr, arrname, True)
def show_disk(self, name):
scfg = self.ipmicmd.get_storage_configuration()
for disk in scfg.disks:
if simplify_name(disk.name) == name or disk == name:
self.output.put(
msg.Disk(self.node, disk.name, disk.description,
disk.id, disk.status, disk.serial,
disk.fru))
for arr in scfg.arrays:
arrname = '{0}-{1}'.format(*arr.id)
for disk in arr.disks:
if (name == 'all' or simplify_name(disk.name) == name or
disk == name):
self.output.put(
msg.Disk(self.node, disk.name, disk.description,
disk.id, disk.status, disk.serial,
disk.fru, arrname))
for disk in arr.hotspares:
if (name == 'all' or simplify_name(disk.name) == name or
disk == name):
self.output.put(
msg.Disk(self.node, disk.name, disk.description,
disk.id, disk.status, disk.serial,
disk.fru, arrname))
def list_disks(self):
scfg = self.ipmicmd.get_storage_configuration()
for disk in scfg.disks:
self.output.put(msg.ChildCollection(simplify_name(disk.name)))
for arr in scfg.arrays:
for disk in arr.disks:
self.output.put(msg.ChildCollection(simplify_name(disk.name)))
for disk in arr.hotspares:
self.output.put(msg.ChildCollection(simplify_name(disk.name)))
def list_arrays(self):
scfg = self.ipmicmd.get_storage_configuration()
for arr in scfg.arrays:
self.output.put(msg.ChildCollection('{0}-{1}'.format(*arr.id)))
def show_array(self, name):
scfg = self.ipmicmd.get_storage_configuration()
for arr in scfg.arrays:
arrname = '{0}-{1}'.format(*arr.id)
if arrname == name:
self._detail_array(arr, arrname)
def _detail_array(self, arr, arrname, detailvol=False):
vols = []
for vol in arr.volumes:
vols.append(simplify_name(vol.name))
disks = []
for disk in arr.disks:
disks.append(simplify_name(disk.name))
for disk in arr.hotspares:
disks.append(simplify_name(disk.name))
self.output.put(msg.Array(self.node, disks, arr.raid,
vols, arrname, arr.capacity,
arr.available_capacity))
if detailvol:
for vol in arr.volumes:
self.output.put(msg.Volume(self.node, vol.name, vol.size,
vol.status, arrname))
def show_volume(self, name):
scfg = self.ipmicmd.get_storage_configuration()
for arr in scfg.arrays:
arrname = '{0}-{1}'.format(*arr.id)
for vol in arr.volumes:
if name == simplify_name(vol.name):
self.output.put(msg.Volume(self.node, vol.name, vol.size,
vol.status, arrname))
def list_volumes(self):
scfg = self.ipmicmd.get_storage_configuration()
for arr in scfg.arrays:
for vol in arr.volumes:
self.output.put(msg.ChildCollection(simplify_name(vol.name)))
def list_sensors(self):
try:
sensors = self.ipmicmd.get_sensor_descriptions()
@@ -1370,21 +1027,11 @@ class IpmiHandler(object):
self.ipmicmd.set_domain_name(dn)
return
def handle_sysconfigclear(self):
if 'read' == self.op:
raise exc.InvalidArgumentException(
'Cannot read the "clear" resource')
self.ipmicmd.clear_system_configuration()
def handle_sysconfig(self, advanced=False):
if 'read' == self.op:
try:
self.output.put(msg.ConfigSet(
self.node, self.ipmicmd.get_system_configuration(
hideadvanced=not advanced)))
except Exception as e:
self.output.put(
msg.ConfluentNodeError(self.node, str(e)))
self.output.put(msg.ConfigSet(
self.node, self.ipmicmd.get_system_configuration(
hideadvanced=not advanced)))
elif 'update' == self.op:
self.ipmicmd.set_system_configuration(
self.inputdata.get_attributes(self.node))
@@ -1439,42 +1086,6 @@ class IpmiHandler(object):
self.output.put(msg.License(self.node, available))
return
def save_licenses(self):
directory = self.inputdata.nodefile(self.node)
for saved in self.ipmicmd.save_licenses(directory):
self.output.put(msg.SavedFile(self.node, saved))
def handle_licenses(self):
if self.element[-1] == '':
self.element = self.element[:-1]
if self.op in ('create', 'update'):
self.ipmicmd.apply_license(self.inputdata.nodefile(self.node))
if len(self.element) == 3:
self.output.put(msg.ChildCollection('all'))
i = 1
for lic in self.ipmicmd.get_licenses():
self.output.put(msg.ChildCollection(str(i)))
i += 1
return
licname = self.element[3]
if licname == 'all':
for lic in self.ipmicmd.get_licenses():
if self.op == 'delete':
self.ipmicmd.delete_license(lic['name'])
else:
self.output.put(msg.License(self.node, feature=lic['name'], state=lic.get('state', 'Active')))
else:
index = int(licname)
lic = list(self.ipmicmd.get_licenses())[index - 1]
if self.op == 'delete':
self.ipmicmd.delete_license(lic['name'])
else:
self.output.put(msg.License(self.node, feature=lic['name'], state=lic.get('state', 'Active')))
def handle_description(self):
dsc = self.ipmicmd.get_description()
self.output.put(msg.KeyValueData(dsc, self.node))
def _str_health(health):
if isinstance(health, str):
return health
@@ -1495,7 +1106,7 @@ def initthread():
_ipmithread = eventlet.spawn(_ipmi_evtloop)
def create(nodes, element, configmanager, inputdata, realop='create'):
def create(nodes, element, configmanager, inputdata):
initthread()
if element == ['_console', 'session']:
if len(nodes) > 1:
@@ -1503,12 +1114,12 @@ def create(nodes, element, configmanager, inputdata, realop='create'):
return IpmiConsole(nodes[0], configmanager)
else:
return perform_requests(
'update', nodes, element, configmanager, inputdata, realop)
'update', nodes, element, configmanager, inputdata)
def update(nodes, element, configmanager, inputdata):
initthread()
return create(nodes, element, configmanager, inputdata, 'update')
return create(nodes, element, configmanager, inputdata)
def retrieve(nodes, element, configmanager, inputdata):
@@ -1519,12 +1130,8 @@ def retrieve(nodes, element, configmanager, inputdata):
elif '/'.join(element).startswith('media/uploads'):
return firmwaremanager.list_updates(nodes, configmanager.tenant,
element, 'mediaupload')
elif '/'.join(element).startswith('support/servicedata'):
return firmwaremanager.list_updates(nodes, configmanager.tenant,
element, 'ffdc')
else:
return perform_requests('read', nodes, element, configmanager,
inputdata, 'read')
return perform_requests('read', nodes, element, configmanager, inputdata)
def delete(nodes, element, configmanager, inputdata):
initthread()
@@ -1534,9 +1141,5 @@ def delete(nodes, element, configmanager, inputdata):
elif '/'.join(element).startswith('media/uploads'):
return firmwaremanager.remove_updates(nodes, configmanager.tenant,
element, type='mediaupload')
elif '/'.join(element).startswith('support/servicedata'):
return firmwaremanager.remove_updates(nodes, configmanager.tenant,
element, type='ffdc')
return perform_requests(
'delete', nodes, element, configmanager, inputdata, 'delete')
'delete', nodes, element, configmanager, inputdata)
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2015-2018 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.
@@ -22,26 +22,9 @@
import confluent.exceptions as cexc
import confluent.interface.console as conapi
import confluent.log as log
try:
import cryptography
except ImportError:
# Using older, non-crypography based paramiko
cryptography = None
import eventlet
import hashlib
import sys
sys.modules['gssapi'] = None
paramiko = eventlet.import_patched('paramiko')
warnhostkey = False
if cryptography and cryptography.__version__.split('.') < ['1', '5']:
# older cryptography with paramiko breaks most key support except
# ed25519
warnhostkey = True
paramiko.transport.Transport._preferred_keys = filter(
lambda x: 'ed25519' in x,
paramiko.transport.Transport._preferred_keys)
class HostKeyHandler(paramiko.client.MissingHostKeyPolicy):
@@ -84,18 +67,8 @@ class SshShell(conapi.Console):
self.nodeconfig = config
self.username = username
self.password = password
self.connected = False
self.width = 80
self.height = 24
self.inputmode = 0 # 0 = username, 1 = password...
def resize(self, width, height):
self.width = width
self.height = height
if not self.connected:
return
self.shell.resize_pty(width=width, height=height)
def recvdata(self):
while self.connected:
pendingdata = self.shell.recv(8192)
@@ -137,64 +110,14 @@ class SshShell(conapi.Console):
self.username = ''
self.password = ''
self.datacallback('\r\nlogin as: ')
return
except cexc.PubkeyInvalid as pi:
self.keyaction = ''
self.candidatefprint = pi.fingerprint
self.datacallback(pi.message)
self.keyattrname = pi.attrname
self.datacallback('\r\nNew fingerprint: ' + pi.fingerprint)
self.inputmode = -1
self.datacallback('\r\nEnter "disconnect" or "accept": ')
return
except paramiko.SSHException as pi:
self.inputmode = -2
warn = str(pi)
if warnhostkey:
warn += ' (Older cryptography package on this host only ' \
'works with ed25519, check ssh startup on target ' \
'and permissions on /etc/ssh/*key)\r\n' \
'Press Enter to close...'
self.datacallback('\r\n' + warn)
return
self.inputmode = 2
self.connected = True
self.shell = self.ssh.invoke_shell(width=self.width,
height=self.height)
self.shell = self.ssh.invoke_shell()
self.rxthread = eventlet.spawn(self.recvdata)
def write(self, data):
if self.inputmode == -2:
self.datacallback(conapi.ConsoleEvent.Disconnect)
return
elif self.inputmode == -1:
while len(data) and data[0] == b'\x7f' and len(self.keyaction):
self.datacallback('\b \b') # erase previously echoed value
self.keyaction = self.keyaction[:-1]
data = data[1:]
while len(data) and data[0] == b'\x7f':
data = data[1:]
while b'\x7f' in data:
delidx = data.index(b'\x7f')
data = data[:delidx - 1] + data[delidx + 1:]
self.keyaction += data
if '\r' in self.keyaction:
action = self.keyaction.split('\r')[0]
if action.lower() == 'accept':
self.nodeconfig.set_node_attributes(
{self.node:
{self.keyattrname: self.candidatefprint}})
self.datacallback('\r\n')
self.logon()
elif action.lower() == 'disconnect':
self.datacallback(conapi.ConsoleEvent.Disconnect)
else:
self.keyaction = ''
self.datacallback('\r\nEnter "disconnect" or "accept": ')
elif len(data) > 0:
self.datacallback(data)
elif self.inputmode == 0:
if self.inputmode == 0:
while len(data) and data[0] == b'\x7f' and len(self.username):
self.datacallback('\b \b') # erase previously echoed value
self.username = self.username[:-1]
+4 -5
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.
@@ -87,13 +87,12 @@ class ShellSession(consoleserver.ConsoleSession):
"""
def __init__(self, node, configmanager, username, datacallback=None,
skipreplay=False, sessionid=None, width=80, height=24):
skipreplay=False, sessionid=None):
self.sessionid = sessionid
self.configmanager = configmanager
self.node = node
super(ShellSession, self).__init__(node, configmanager, username,
datacallback, skipreplay,
width=width, height=height)
datacallback, skipreplay)
def connect_session(self):
global activesessions
@@ -106,7 +105,7 @@ class ShellSession(consoleserver.ConsoleSession):
self.sessionid += 1
self.sessionid = str(self.sessionid)
if self.sessionid not in activesessions[(tenant, self.node, self.username)]:
activesessions[(tenant, self.node, self.username)][self.sessionid] = _ShellHandler(self.node, self.configmanager, width=self.width, height=self.height)
activesessions[(tenant, self.node, self.username)][self.sessionid] = _ShellHandler(self.node, self.configmanager)
self.conshdl = activesessions[(self.configmanager.tenant, self.node, self.username)][self.sessionid]
def destroy(self):
+5 -12
View File
@@ -79,32 +79,25 @@ class Session(object):
# overriden, but some devices only support DES)
tp = _get_transport(self.server)
ctx = snmp.ContextData(self.context)
resolvemib = False
if '::' in oid:
resolvemib = True
mib, field = oid.split('::')
obj = snmp.ObjectType(snmp.ObjectIdentity(mib, field))
else:
obj = snmp.ObjectType(snmp.ObjectIdentity(oid))
walking = snmp.bulkCmd(self.eng, self.authdata, tp, ctx, 0, 10, obj,
lexicographicMode=False, lookupMib=resolvemib)
lexicographicMode=False)
try:
for rsp in walking:
errstr, errnum, erridx, answers = rsp
if errstr:
errstr = str(errstr)
finerr = errstr + ' while trying to connect to ' \
'{0}'.format(self.server)
if errstr in ('Unknown USM user', 'unknownUserName',
'wrongDigest', 'Wrong SNMP PDU digest'):
raise exc.TargetEndpointBadCredentials(finerr)
if errstr in ('unknownUserName', 'wrongDigest'):
raise exc.TargetEndpointBadCredentials(errstr)
# need to do bad credential versus timeout
raise exc.TargetEndpointUnreachable(finerr)
raise exc.TargetEndpointUnreachable(errstr)
elif errnum:
raise exc.ConfluentException(errnum.prettyPrint() +
' while trying to connect to '
'{0}'.format(self.server))
raise exc.ConfluentException(errnum.prettyPrint())
for ans in answers:
if not obj[0].isPrefixOf(ans[0]):
# PySNMP returns leftovers in a bulk command
+17 -38
View File
@@ -1,7 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 IBM Corporation
# Copyright 2015-2018 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.
@@ -75,14 +75,6 @@ except ImportError:
plainsocket = None
def _should_authlog(path, operation):
if (operation == 'retrieve' and
('/sensors/' in path or '/health/' in path or
'/power/state' in path or '/nodes/' == path or
(path.startswith('/noderange/') and path.endswith('/nodes/')))):
return False
return True
class ClientConsole(object):
def __init__(self, client):
self.client = client
@@ -120,15 +112,13 @@ def sessionhdl(connection, authname, skipauth=False, cert=None):
cfm = configmanager.ConfigManager(tenant=None, username=authname)
elif authname:
authdata = auth.authorize(authname, element=None)
if authdata:
if authdata is not None:
cfm = authdata[1]
authenticated = True
send_data(connection, "Confluent -- v0 --")
while not authenticated: # prompt for name and passphrase
send_data(connection, {'authpassed': 0})
response = tlvdata.recv(connection)
if not response:
return
if 'collective' in response:
return collective.handle_connection(connection, cert,
response['collective'])
@@ -145,7 +135,7 @@ def sessionhdl(connection, authname, skipauth=False, cert=None):
# element path, that authorization will need to be called
# per request the user makes
authdata = auth.check_user_passphrase(authname, passphrase)
if not authdata:
if authdata is None:
auditlog.log(
{'operation': 'connect', 'user': authname, 'allowed': False})
else:
@@ -153,7 +143,7 @@ def sessionhdl(connection, authname, skipauth=False, cert=None):
cfm = authdata[1]
send_data(connection, {'authpassed': 1})
request = tlvdata.recv(connection)
if request and 'collective' in request and skipauth:
if 'collective' in request and skipauth:
if not libssl:
tlvdata.send(
connection,
@@ -161,9 +151,7 @@ def sessionhdl(connection, authname, skipauth=False, cert=None):
'python-pyopenssl installed or has an '
'incorrect version installed '
'(e.g. pyOpenSSL would need to be '
'replaced with python-pyopenssl). '
'Restart confluent after updating '
'the dependency.'}})
'replaced with python-pyopenssl)'}})
return
return collective.handle_connection(connection, None, request['collective'],
local=True)
@@ -182,11 +170,11 @@ def sessionhdl(connection, authname, skipauth=False, cert=None):
send_data(connection, {'_requestdone': 1})
except SystemExit:
sys.exit(0)
except Exception as e:
except:
tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
event=log.Events.stacktrace)
send_data(connection, {'errorcode': 500,
'error': 'Unexpected error - ' + str(e)})
'error': 'Unexpected error'})
send_data(connection, {'_requestdone': 1})
request = tlvdata.recv(connection)
@@ -206,21 +194,20 @@ def process_request(connection, request, cfm, authdata, authname, skipauth):
path = request['path']
params = request.get('parameters', {})
hdlr = None
auditmsg = {
'operation': operation,
'target': path,
}
if not skipauth:
authdata = auth.authorize(authdata[2], path, authdata[3], operation)
if not authdata:
auditmsg = {
'operation': operation,
'target': path,
}
if authdata is None:
auditmsg['allowed'] = False
auditlog.log(auditmsg)
raise exc.ForbiddenRequest()
auditmsg['user'] = authdata[2]
if authdata[3] is not None:
auditmsg['tenant'] = authdata[3]
auditmsg['allowed'] = True
if _should_authlog(path, operation):
auditmsg['allowed'] = True
auditlog.log(auditmsg)
try:
if operation == 'start':
@@ -251,9 +238,7 @@ def start_proxy_term(connection, cert, request):
ccons = ClientConsole(connection)
consession = consoleserver.ConsoleSession(
node=request['node'], configmanager=cfm, username=request['user'],
datacallback=ccons.sendall, skipreplay=request['skipreplay'],
direct=False, width=request.get('width', 80), height=request.get(
'height', 24))
datacallback=ccons.sendall, skipreplay=request['skipreplay'])
term_interact(None, None, ccons, None, connection, consession, None)
def start_term(authname, cfm, connection, params, path, authdata, skipauth):
@@ -277,8 +262,7 @@ def start_term(authname, cfm, connection, params, path, authdata, skipauth):
consession = shellserver.ShellSession(
node=node, configmanager=cfm, username=authname,
datacallback=ccons.sendall, skipreplay=skipreplay,
sessionid=sessionid, width=params.get('width', 80),
height=params.get('height', 24))
sessionid=sessionid)
else:
raise exc.InvalidArgumentException('Invalid path {0}'.format(path))
if consession is None:
@@ -306,19 +290,16 @@ def term_interact(authdata, authname, ccons, cfm, connection, consession,
elif data['operation'] == 'reopen':
consession.reopen()
continue
elif data['operation'] == 'resize':
consession.resize(width=data['width'], height=data['height'])
continue
else:
try:
process_request(connection, data, cfm, authdata, authname,
skipauth)
except Exception as e:
except Exception:
tracelog.log(traceback.format_exc(),
ltype=log.DataTypes.event,
event=log.Events.stacktrace)
send_data(connection, {'errorcode': 500,
'error': 'Unexpected error - ' + str(e)})
'error': 'Unexpected error'})
send_data(connection, {'_requestdone': 1})
continue
if not data:
@@ -342,8 +323,6 @@ def _tlshandler(bind_host, bind_port):
raise
sys.stderr.write('TLS Socket in use, retrying in 1 second\n')
eventlet.sleep(1)
# Enable TCP_FASTOPEN
plainsocket.setsockopt(socket.SOL_TCP, 23, 5)
plainsocket.listen(5)
while (1): # TODO: exithook
cnn, addr = plainsocket.accept()
-40
View File
@@ -1,40 +0,0 @@
from ctypes import *
from ctypes.util import find_library
import grp
import pwd
import os
libc = cdll.LoadLibrary(find_library('libc'))
_getgrouplist = libc.getgrouplist
_getgrouplist.restype = c_int32
class TooSmallException(Exception):
def __init__(self, count):
self.count = count
super(TooSmallException, self).__init__()
def getgrouplist(name, gid, ng=32):
_getgrouplist.argtypes = [c_char_p, c_uint, POINTER(c_uint * ng), POINTER(c_int)]
glist = (c_uint * ng)()
nglist = c_int(ng)
count = _getgrouplist(name, gid, byref(glist), byref(nglist))
if count < 0:
raise TooSmallException(nglist.value)
for gidx in range(count):
gent = glist[gidx]
yield grp.getgrgid(gent).gr_name
def grouplist(username):
pent = pwd.getpwnam(username)
try:
groups = getgrouplist(pent.pw_name, pent.pw_gid)
except TooSmallException as e:
groups = getgrouplist(pent.pw_name, pent.pw_gid, e.count)
return list(groups)
if __name__ == '__main__':
import sys
print(repr(grouplist(sys.argv[1])))
+1 -4
View File
@@ -12,7 +12,7 @@ Group: Development/Libraries
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot
Prefix: %{_prefix}
BuildArch: noarch
Requires: python-pyghmi >= 1.0.34, python-eventlet, python-greenlet, python-pycryptodomex >= 3.4.7, confluent_client, python-pyparsing, python-paramiko, python-dns, python-netifaces, python2-pyasn1 >= 0.2.3, python-pysnmp >= 4.3.4, python-pyte, python-lxml, python-eficompressor, python-setuptools, python-dateutil
Requires: python-pyghmi >= 1.0.34, python-eventlet, python-greenlet, python-pycryptodomex >= 3.4.7, confluent_client, python-pyparsing, python-paramiko, python-dns, python-netifaces, python2-pyasn1 >= 0.2.3, python-pysnmp >= 4.3.4, python-pyte, python-lxml, python-eficompressor, python-setuptools
Vendor: Jarrod Johnson <jjohnson2@lenovo.com>
Url: http://xcat.sf.net/
@@ -40,9 +40,6 @@ true
%post
sysctl -p /usr/lib/sysctl.d/confluent.conf >& /dev/null
if [ -x /usr/bin/systemctl ]; then /usr/bin/systemctl try-restart confluent >& /dev/null; fi
if [ ! -e /etc/pam.d/confluent ]; then
ln -s /etc/pam.d/sshd /etc/pam.d/confluent
fi
true
%clean
+22
View File
@@ -0,0 +1,22 @@
[metadata]
name = confluent_server
summary = Confluent systems management daemon
description-file =
README.txt
author = Jarrod Johnson
author-email = jbjohnso@us.ibm.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]
packages =
confluent
confluent/interface
confluent/config
+1 -1
View File
@@ -4,7 +4,7 @@ Description=Confluent hardware manager
[Service]
Type=forking
#PIDFile=/var/run/confluent/pid
PIDFile=/var/run/confluent/pid
ExecStart=/opt/confluent/bin/confluent
ExecStop=/opt/confluent/bin/confetty shutdown /
Restart=on-failure

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