mirror of
https://github.com/xcat2/confluent.git
synced 2026-09-29 00:31:09 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4f83d08565 | |||
| e37db1e805 |
@@ -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
-168
@@ -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))
|
||||
|
||||
|
||||
@@ -586,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())
|
||||
@@ -618,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})
|
||||
@@ -816,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
|
||||
@@ -848,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,
|
||||
@@ -856,145 +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()
|
||||
sys.exit(errcode)
|
||||
doexit = True
|
||||
sys.stdout.write('Lost connection to server')
|
||||
quitconfetty(fullexit=True)
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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 ansible.hosts
|
||||
\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()
|
||||
@@ -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':
|
||||
|
||||
@@ -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 '
|
||||
|
||||
@@ -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)
|
||||
@@ -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]))
|
||||
|
||||
@@ -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,7 +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')
|
||||
fields.append('net.bmc.gateway')
|
||||
elif datum in ('bmcuser', 'username', 'user'):
|
||||
fields.append('secret.hardwaremanagementuser')
|
||||
elif datum in ('bmcpass', 'password', 'pass'):
|
||||
@@ -141,35 +122,18 @@ 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([])
|
||||
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):
|
||||
blocking_scan(session)
|
||||
if not search_record(nodedatum, options, session):
|
||||
sys.stderr.write(
|
||||
"Could not match the following data: " +
|
||||
@@ -201,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):
|
||||
@@ -271,10 +220,6 @@ def assign_discovery(options, session):
|
||||
if abort:
|
||||
sys.exit(1)
|
||||
matches = list_matching_macs(options, session)
|
||||
if not matches:
|
||||
# Do a rescan to catch missing requested data
|
||||
blocking_scan(session)
|
||||
matches = list_matching_macs(options, session)
|
||||
if not matches:
|
||||
sys.stderr.write("No matching discovery candidates found\n")
|
||||
sys.exit(1)
|
||||
@@ -285,11 +230,6 @@ def assign_discovery(options, session):
|
||||
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():
|
||||
@@ -326,11 +266,6 @@ 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', 'rescan', 'clear'):
|
||||
parser.print_help()
|
||||
@@ -343,9 +278,9 @@ def main():
|
||||
if args[0] == 'assign':
|
||||
assign_discovery(options, session)
|
||||
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()
|
||||
@@ -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()
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -44,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()
|
||||
@@ -73,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']:
|
||||
@@ -103,5 +94,5 @@ try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
print('')
|
||||
sys.exit(exitcode)
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
@@ -87,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]")
|
||||
@@ -130,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:
|
||||
|
||||
@@ -1,84 +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>]")
|
||||
(options, args) = argparser.parse_args()
|
||||
upfile = None
|
||||
try:
|
||||
noderange = args[0]
|
||||
if len(args) > 1:
|
||||
if args[1] == 'install':
|
||||
upfile = args[2]
|
||||
else:
|
||||
components = ['all']
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
|
||||
def install_license(session, filename):
|
||||
global exitcode
|
||||
resource = '/noderange/{0}/configuration/' \
|
||||
'management_controller/licenses/'.format(noderange)
|
||||
filename = os.path.abspath(filename)
|
||||
instargs = {'filename': filename}
|
||||
for res in session.create(resource, instargs):
|
||||
pass # print(repr(res))
|
||||
show_licenses()
|
||||
|
||||
def show_licenses(session):
|
||||
global exitcode
|
||||
firmware_shown = False
|
||||
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', []):
|
||||
print('{0}: {1}'.format(node, license.get('feature',
|
||||
'Unknown')))
|
||||
|
||||
|
||||
try:
|
||||
session = client.Command()
|
||||
if upfile is None:
|
||||
show_licenses(session)
|
||||
else:
|
||||
install_license(session, upfile)
|
||||
except KeyboardInterrupt:
|
||||
print('')
|
||||
sys.exit(exitcode)
|
||||
@@ -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))
|
||||
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
@@ -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)
|
||||
|
||||
@@ -1,205 +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
|
||||
_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 setstorage(noderange, options, args):
|
||||
# pass
|
||||
|
||||
funmap = {
|
||||
'create': createstorage,
|
||||
'show': showstorage,
|
||||
# 'set': setstorage,
|
||||
'delete': deletestorage,
|
||||
'rm': deletestorage,
|
||||
}
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
argparser = OptParser(
|
||||
usage='Usage: %prog <noderange> [show|create|delete]',
|
||||
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.')
|
||||
(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()
|
||||
@@ -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()
|
||||
@@ -1 +0,0 @@
|
||||
../confluent_server/builddeb
|
||||
@@ -16,9 +16,7 @@
|
||||
# limitations under the License.
|
||||
|
||||
import anydbm as dbm
|
||||
import csv
|
||||
import errno
|
||||
import fnmatch
|
||||
import hashlib
|
||||
import os
|
||||
import shlex
|
||||
@@ -26,7 +24,6 @@ import socket
|
||||
import ssl
|
||||
import sys
|
||||
import confluent.tlvdata as tlvdata
|
||||
import confluent.sortutil as sortutil
|
||||
|
||||
SO_PASSCRED = 16
|
||||
|
||||
@@ -37,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:
|
||||
@@ -136,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)
|
||||
@@ -368,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() + '.'):
|
||||
@@ -464,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:
|
||||
@@ -553,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:
|
||||
@@ -586,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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -36,249 +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'
|
||||
|
||||
|
||||
_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" -- ${COMP_WORDS[-1]}))
|
||||
return;
|
||||
fi
|
||||
if [ $NUMARGS == 4 ] && [ ${CMPARGS[2]} == 'install' ]; then
|
||||
compopt -o default
|
||||
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_nr_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
|
||||
|
||||
|
||||
@@ -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`
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
collective(1) -- Check and manage a confluent collective
|
||||
==============================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`collective invite <server>`
|
||||
`collective join <server> [-i TOKEN]`
|
||||
`collective show`
|
||||
`collective gencert`
|
||||
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
**collective** helps manage the collective mode of confluent, where multiple
|
||||
confluent servers are linked together to act as one. For example, the procedure to set up
|
||||
a collective to run on three servers called mgt1, mgt2, and mgt3, first install and start
|
||||
confluent as usual on the three servers. On mgt1, run `collective invite mgt2` and an
|
||||
invitation token will be output. On mgt2, either run `collective join mgt1` to paste
|
||||
the token interactively, or `collective join mgt1 -i <token>`. At this point, either
|
||||
mgt1 or mgt2 can bring in mgt3. For example on mgt2 run `collective invite mgt3` and
|
||||
on mgt3 run `collective join mgt2 -i <token>`
|
||||
|
||||
This can be linked together in the following manner with ssh:
|
||||
on mgt1:
|
||||
`# ssh mgt2 collective join mgt1 -i $(collective invite mgt2)`
|
||||
|
||||
Note that a collective is only redundant with 3 or more members. The collective
|
||||
will function so long as more than half of the members are online. A collective
|
||||
of two members is supported, but without redundancy.
|
||||
|
||||
Also note that the collective leader role is dynamic, but has no impact on interacting
|
||||
with confluent. It is merely an internal role that can dynamically change depending
|
||||
on circumstances.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-i`:
|
||||
Provide the token as an argument rather than interactively.
|
||||
|
||||
## EXAMPLES
|
||||
* Inviting a server called mgt2:
|
||||
`# collective invite mgt2`
|
||||
`bWd0MkA+BNQ6XAxMXlqJJa+EQRlihL/k9xCXnasgSQXZr989Pa1/ln7G3e1Ncxx6BMzMqqreHJVkPr2FrzjNit/UgHlg`
|
||||
|
||||
* On mgt2, joining mgt1:
|
||||
`# collective join mgt1 -i bWd0MkA+BNQ6XAxMXlqJJa+EQRlihL/k9xCXnasgSQXZr989Pa1/ln7G3e1Ncxx6BMzMqqreHJVkPr2FrzjNit/UgHlg`
|
||||
`Success`
|
||||
|
||||
* Showing the collective state:
|
||||
`# collective show`
|
||||
`Quorum: True`
|
||||
`Leader: mgt1`
|
||||
`Active collective members:`
|
||||
` mgt2`
|
||||
|
||||
@@ -7,7 +7,6 @@ nodeattrib(8) -- List or change confluent nodes attributes
|
||||
`nodeattrib <noderange> [<nodeattribute1=value1> <nodeattribute2=value2> ...]`
|
||||
`nodeattrib -c <noderange> <nodeattribute1> <nodeattribute2> ...`
|
||||
`nodeattrib -e <noderange> <nodeattribute1> <nodeattribute2> ...`
|
||||
`nodeattrib -p <noderange> <nodeattribute1> <nodeattribute2> ...`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -23,9 +22,6 @@ For a full list of attributes, run `nodeattrib <node> all` against a node.
|
||||
If `-c` is specified, this will set the nodeattribute to a null value.
|
||||
This is different from setting the value to an empty string.
|
||||
|
||||
Attributes may be specified by wildcard, for example `net.*switch` will report
|
||||
all attributes that begin with `net.` and end with `switch`.
|
||||
|
||||
If the word all is specified, then all available attributes are given.
|
||||
Omitting any attribute name or the word 'all' will display only attributes
|
||||
that are currently set.
|
||||
@@ -44,8 +40,6 @@ See nodegroupattrib(8) command on how to manage attributes on a group level.
|
||||
Clear specified nodeattributes
|
||||
* `-e`, `--environment`:
|
||||
Set specified attributes based on exported environment variable of matching name. Environment variable names may be lower case or all upper case. Replace . with _ as needed (e.g. info.note may be specified as either $info_note or $INFO_NOTE
|
||||
* `-p`, `--prompt`:
|
||||
Request interactive prompting to provide values rather than the command line or environment variables.
|
||||
|
||||
## EXAMPLES
|
||||
* Listing matching nodes of a simple noderange:
|
||||
@@ -91,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.
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,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`:
|
||||
|
||||
@@ -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)
|
||||
@@ -1,11 +0,0 @@
|
||||
nodelicense(8) -- Manage license keys on BMC
|
||||
=================================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodelicense <noderange> [list|install <filename>]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
`nodelicense` shows and installs license keys on supported BMCs
|
||||
|
||||
@@ -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`:
|
||||
|
||||
@@ -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`
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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`
|
||||
|
||||
|
||||
@@ -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`
|
||||
@@ -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,4 +1,3 @@
|
||||
include pam/*
|
||||
include sysvinit/*
|
||||
include systemd/*
|
||||
include sysctl/*
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
@@ -1,63 +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/' 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 '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,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
|
||||
|
||||
@@ -158,13 +158,20 @@ def check_user_passphrase(name, passphrase, element=None, tenant=False):
|
||||
# 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:
|
||||
eventlet.sleep(0.05)
|
||||
return None
|
||||
try:
|
||||
pammy = PAM.pam()
|
||||
pammy.start(_pamservice, user, credobj.pam_conv)
|
||||
pammy.authenticate()
|
||||
pammy.acct_mgmt()
|
||||
del pammy
|
||||
return authorize(user, element, tenant, skipuserobj=False)
|
||||
except NameError:
|
||||
pass
|
||||
except PAM.error:
|
||||
if credobj.haspam:
|
||||
return None
|
||||
if (user, tenant) in _passcache:
|
||||
if hashlib.sha256(passphrase).digest() == _passcache[(user, tenant)]:
|
||||
if passphrase == _passcache[(user, tenant)]:
|
||||
return authorize(user, element, tenant)
|
||||
else:
|
||||
# In case of someone trying to guess,
|
||||
@@ -172,48 +179,38 @@ def check_user_passphrase(name, passphrase, element=None, tenant=False):
|
||||
# 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
|
||||
cfm = configmanager.ConfigManager(tenant, username=user)
|
||||
ucfg = cfm.get_user(user)
|
||||
if ucfg is None or 'cryptpass' not in ucfg:
|
||||
eventlet.sleep(0.05) # stall even on test for existence of a username
|
||||
return None
|
||||
_passchecking[(user, tenant)] = True
|
||||
# TODO(jbjohnso): WORKERPOOL
|
||||
# PBKDF2 is, by design, cpu intensive
|
||||
# throw it at the worker pool when implemented
|
||||
# maybe a distinct worker pool, wondering about starving out non-auth stuff
|
||||
salt, crypt = ucfg['cryptpass']
|
||||
# execute inside tpool to get greenthreads to give it a special thread
|
||||
# world
|
||||
#TODO(jbjohnso): util function to generically offload a call
|
||||
#such a beast could be passed into pyghmi as a way for pyghmi to
|
||||
#magically get offload of the crypto functions without having
|
||||
#to explicitly get into the eventlet tpool game
|
||||
global authworkers
|
||||
global authcleaner
|
||||
if authworkers is None:
|
||||
authworkers = multiprocessing.Pool(processes=1)
|
||||
else:
|
||||
authcleaner.cancel()
|
||||
authcleaner = eventlet.spawn_after(30, _clean_authworkers)
|
||||
crypted = eventlet.tpool.execute(_do_pbkdf, passphrase, salt)
|
||||
del _passchecking[(user, tenant)]
|
||||
eventlet.sleep(0.05) # either way, we want to stall so that client can't
|
||||
# determine failure because there is a delay, valid response will
|
||||
# delay as well
|
||||
if crypt == crypted:
|
||||
_passcache[(user, tenant)] = hashlib.sha256(passphrase).digest()
|
||||
return authorize(user, element, tenant)
|
||||
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, skipuserobj=False)
|
||||
except NameError:
|
||||
pass
|
||||
except PAM.error:
|
||||
pass
|
||||
eventlet.sleep(0.05) # stall even on test for existence of a username
|
||||
if crypt == crypted:
|
||||
_passcache[(user, tenant)] = passphrase
|
||||
return authorize(user, element, tenant)
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 '
|
||||
@@ -262,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'),
|
||||
|
||||
@@ -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,9 +94,7 @@ _hasquorum = True
|
||||
_attraliases = {
|
||||
'bmc': 'hardwaremanagement.manager',
|
||||
'bmcuser': 'secret.hardwaremanagementuser',
|
||||
'switchuser': 'secret.hardwaremanagementuser',
|
||||
'bmcpass': 'secret.hardwaremanagementpassword',
|
||||
'switchpass': 'secret.hardwaremanagementpassword',
|
||||
}
|
||||
|
||||
def _mkpath(pathname):
|
||||
@@ -181,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,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)
|
||||
|
||||
@@ -304,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()
|
||||
|
||||
@@ -642,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():
|
||||
@@ -662,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()
|
||||
|
||||
@@ -678,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):
|
||||
@@ -969,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)
|
||||
@@ -1055,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
|
||||
@@ -1264,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):
|
||||
@@ -1523,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 ValueErorr('noderange attribute must be set by itself')
|
||||
for attr in attribmap[group]:
|
||||
if attr in _attraliases:
|
||||
newattr = _attraliases[attr]
|
||||
@@ -1542,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']:
|
||||
@@ -1568,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])
|
||||
@@ -1729,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
|
||||
@@ -1813,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',
|
||||
@@ -2146,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,
|
||||
@@ -2183,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:
|
||||
@@ -2205,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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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,10 +165,6 @@ def _init_core():
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
'licenses': PluginCollection({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
'net_interfaces': PluginCollection({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
@@ -202,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'],
|
||||
@@ -229,10 +205,6 @@ def _init_core():
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
'clear': PluginRoute({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
})
|
||||
},
|
||||
},
|
||||
'_console': {
|
||||
@@ -265,10 +237,6 @@ def _init_core():
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
},
|
||||
'description': PluginRoute({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
'events': {
|
||||
'hardware': {
|
||||
'log': PluginRoute({
|
||||
@@ -370,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'}),
|
||||
},
|
||||
@@ -610,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')
|
||||
@@ -654,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:
|
||||
@@ -688,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':
|
||||
@@ -813,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:
|
||||
@@ -824,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:
|
||||
@@ -861,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
|
||||
@@ -876,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')
|
||||
|
||||
@@ -893,16 +841,8 @@ def dispatch_request(nodes, manager, element, configmanager, inputdata,
|
||||
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)):
|
||||
|
||||
@@ -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,13 +26,18 @@ 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=DEFAULT_USER, password=DEFAULT_PASS):
|
||||
def _get_ipmicmd(self, user=None, password=None):
|
||||
if user is None:
|
||||
user = self.DEFAULT_USER
|
||||
if password is None:
|
||||
password = self.DEFAULT_PASS
|
||||
return ipmicommand.Command(self.ipaddr, user, password)
|
||||
|
||||
def __init__(self, info, configmanager):
|
||||
@@ -45,13 +50,13 @@ class NodeHandler(generic.NodeHandler):
|
||||
def config(self, nodename, reset=False):
|
||||
self._bmcconfig(nodename, reset)
|
||||
|
||||
def _bmcconfig(self, nodename, reset=False, customconfig=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.
|
||||
try:
|
||||
ic = self._get_ipmicmd()
|
||||
passwd = DEFAULT_PASS
|
||||
passwd = self.DEFAULT_PASS
|
||||
except pygexc.IpmiException as pi:
|
||||
creds = self.configmanager.get_node_attributes(
|
||||
nodename,
|
||||
@@ -60,22 +65,20 @@ class NodeHandler(generic.NodeHandler):
|
||||
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
|
||||
user = self.DEFAULT_USER
|
||||
passwd = creds.get(nodename, {}).get(
|
||||
'secret.hardwaremanagementpassword', {}).get('value', None)
|
||||
if passwd is not None and passwd != DEFAULT_PASS:
|
||||
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 customconfig:
|
||||
customconfig(ic)
|
||||
currusers = ic.get_users()
|
||||
lanchan = ic.get_network_channel()
|
||||
userdata = ic.xraw_command(netfn=6, command=0x44, data=(lanchan,
|
||||
@@ -109,14 +112,9 @@ class NodeHandler(generic.NodeHandler):
|
||||
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'])
|
||||
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}})
|
||||
@@ -129,18 +127,16 @@ class NodeHandler(generic.NodeHandler):
|
||||
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)
|
||||
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)
|
||||
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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -21,12 +21,6 @@ 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
|
||||
|
||||
@@ -36,42 +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
|
||||
return certificate == self._fp
|
||||
|
||||
def set_password_policy(self, ic):
|
||||
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))
|
||||
ic.register_key_handler(self._validate_cert)
|
||||
ic.oem_init()
|
||||
ic._oem.smmhandler.wc.request('POST', '/data', apirequest)
|
||||
ic._oem.smmhandler.wc.getresponse().read()
|
||||
|
||||
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', '')
|
||||
ic = self._bmcconfig(nodename, customconfig=self.set_password_policy)
|
||||
super(NodeHandler, self).config(nodename)
|
||||
|
||||
# notes for smm:
|
||||
# POST to:
|
||||
|
||||
@@ -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.
|
||||
@@ -13,22 +13,14 @@
|
||||
# limitations under the License.
|
||||
|
||||
import confluent.discovery.handlers.imm as immhandler
|
||||
import confluent.util as util
|
||||
import pyghmi.exceptions as pygexc
|
||||
import pyghmi.ipmi.oem.lenovo.imm as imm
|
||||
|
||||
|
||||
|
||||
|
||||
class NodeHandler(immhandler.NodeHandler):
|
||||
devname = 'XCC'
|
||||
|
||||
@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']):
|
||||
@@ -49,53 +41,16 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
#if ipmicmd:
|
||||
# ipmicmd.ipmi_session.logout()
|
||||
|
||||
def validate_cert(self, certificate):
|
||||
# broadly speaking, merely checks consistency moment to moment,
|
||||
# but if https_cert gets stricter, this check means something
|
||||
fprint = util.get_fingerprint(self.https_cert)
|
||||
return util.cert_matches(fprint, certificate)
|
||||
|
||||
def set_password_policy(self, ic):
|
||||
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
|
||||
ic.register_key_handler(self.validate_cert)
|
||||
ic.oem_init()
|
||||
try:
|
||||
ic._oem.immhandler.wc.grab_json_response('/api/dataset', ruleset)
|
||||
except Exception as e:
|
||||
print(repr(e))
|
||||
pass
|
||||
|
||||
def config(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.
|
||||
dpp = self.configmanager.get_node_attributes(
|
||||
nodename, 'discovery.passwordrules')
|
||||
self.ruleset = dpp.get(nodename, {}).get(
|
||||
'discovery.passwordrules', {}).get('value', '')
|
||||
ic = self._bmcconfig(nodename, customconfig=self.set_password_policy)
|
||||
ic = self._bmcconfig(nodename)
|
||||
ff = self.info.get('attributes', {}).get('enclosure-form-factor', '')
|
||||
if ff not in ('dense-computing', [u'dense-computing']):
|
||||
return
|
||||
# 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(
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -496,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:
|
||||
@@ -509,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)
|
||||
@@ -550,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:
|
||||
@@ -789,14 +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
|
||||
sock.setsockopt(socket.SOL_TCP, 23, 5)
|
||||
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
|
||||
|
||||
@@ -73,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:
|
||||
@@ -152,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:
|
||||
@@ -768,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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -32,11 +32,6 @@ valid_health_values = set([
|
||||
'unknown',
|
||||
])
|
||||
|
||||
def simplify_name(name):
|
||||
return name.lower().replace(' ', '_').replace('/', '-').replace(
|
||||
'_-_', '-')
|
||||
|
||||
|
||||
def _htmlify_structure(indict):
|
||||
ret = "<ul>"
|
||||
if isinstance(indict, dict):
|
||||
@@ -267,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
|
||||
@@ -399,8 +376,6 @@ 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') and operation != 'retrieve':
|
||||
return InputAttributes(path, inputdata, nodes)
|
||||
elif path == ['boot', 'nextdevice'] and operation != 'retrieve':
|
||||
@@ -441,63 +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/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', 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):
|
||||
@@ -551,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)
|
||||
@@ -577,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
|
||||
@@ -616,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])
|
||||
@@ -631,35 +566,6 @@ 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
|
||||
|
||||
|
||||
@@ -783,70 +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(',')
|
||||
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
|
||||
inputdata.append(
|
||||
{'name': currname, 'size': size,
|
||||
'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,
|
||||
'raidlevel': raidlvl,
|
||||
})
|
||||
|
||||
|
||||
class InputPowerMessage(ConfluentInputMessage):
|
||||
valid_values = set([
|
||||
'on',
|
||||
@@ -1387,74 +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):
|
||||
self.kvpairs = {
|
||||
name: {
|
||||
'type': 'volume',
|
||||
'name': simplify_name(volname),
|
||||
'label': volname,
|
||||
'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',
|
||||
}
|
||||
|
||||
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
|
||||
@@ -1602,12 +1376,12 @@ class NTPServer(ConfluentMessage):
|
||||
class License(ConfluentMessage):
|
||||
readonly = True
|
||||
|
||||
def __init__(self, name=None, kvm=None, feature=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})
|
||||
kv.append({'kvm_availability': str(kvm)})
|
||||
if self.notnode:
|
||||
self.kvpairs = {'License': kv}
|
||||
else:
|
||||
|
||||
@@ -171,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:
|
||||
@@ -220,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)
|
||||
@@ -243,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
|
||||
@@ -260,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
|
||||
@@ -335,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']])
|
||||
@@ -346,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)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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':
|
||||
@@ -465,8 +457,6 @@ def handle_read_api_request(pathcomponents, configmanager):
|
||||
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.
|
||||
@@ -39,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
|
||||
@@ -52,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},
|
||||
@@ -74,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'
|
||||
@@ -99,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
|
||||
@@ -132,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:
|
||||
@@ -163,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():
|
||||
@@ -207,28 +195,10 @@ 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 updatenode.iterkeys():
|
||||
|
||||
@@ -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,58 +19,19 @@ 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
|
||||
|
||||
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
|
||||
# eventlet comes into play. It seems like sometimes on acquire,
|
||||
@@ -135,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):
|
||||
@@ -177,17 +137,6 @@ class IpmiCommandWrapper(ipmicommand.Command):
|
||||
'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(
|
||||
@@ -358,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)
|
||||
@@ -406,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'):
|
||||
@@ -421,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,
|
||||
@@ -439,8 +376,7 @@ def perform_request(operator, node, element,
|
||||
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
|
||||
@@ -449,12 +385,10 @@ 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
|
||||
@@ -509,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(
|
||||
@@ -532,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']:
|
||||
@@ -554,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(
|
||||
@@ -572,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)))
|
||||
|
||||
@@ -620,10 +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()
|
||||
raise Exception('Not implemented')
|
||||
|
||||
def decode_alert(self):
|
||||
@@ -748,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'
|
||||
@@ -876,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)
|
||||
@@ -915,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)
|
||||
@@ -931,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 '
|
||||
@@ -954,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*'):
|
||||
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']))
|
||||
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]
|
||||
@@ -1102,100 +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 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))
|
||||
|
||||
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)))
|
||||
|
||||
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))
|
||||
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()
|
||||
@@ -1343,12 +1027,6 @@ 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:
|
||||
self.output.put(msg.ConfigSet(
|
||||
@@ -1408,32 +1086,6 @@ class IpmiHandler(object):
|
||||
self.output.put(msg.License(self.node, available))
|
||||
return
|
||||
|
||||
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():
|
||||
self.output.put(msg.License(self.node, feature=lic['name']))
|
||||
else:
|
||||
index = int(licname)
|
||||
lic = list(self.ipmicmd.get_licenses())[index - 1]
|
||||
self.output.put(msg.License(self.node, feature=lic['name']))
|
||||
|
||||
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
|
||||
@@ -1454,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:
|
||||
@@ -1462,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):
|
||||
@@ -1478,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()
|
||||
@@ -1493,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)
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -151,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,
|
||||
@@ -159,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)
|
||||
@@ -204,12 +194,12 @@ 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)
|
||||
auditmsg = {
|
||||
'operation': operation,
|
||||
'target': path,
|
||||
}
|
||||
if authdata is None:
|
||||
auditmsg['allowed'] = False
|
||||
auditlog.log(auditmsg)
|
||||
@@ -217,8 +207,7 @@ def process_request(connection, request, cfm, authdata, authname, skipauth):
|
||||
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':
|
||||
@@ -249,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):
|
||||
@@ -275,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:
|
||||
@@ -304,9 +290,6 @@ 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,
|
||||
@@ -340,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,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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
function getRequest(url, success) {
|
||||
var request = new XMLHttpRequest();
|
||||
request.open('GET', url, true);
|
||||
request.setRequestHeader('Accept', 'application/json');
|
||||
request.onload = function() {
|
||||
if (this.status >= 200 && this.status <= 400) {
|
||||
success(JSON.parse(this.responseText));
|
||||
}
|
||||
};
|
||||
request.send();
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
getRequest("/confluent-api/nodes/", function( data) {
|
||||
var items = [];
|
||||
var options = [];
|
||||
var nodename = "";
|
||||
data["_links"]["item"].forEach( function( val, key ) {
|
||||
console.log(val);
|
||||
if (typeof(val) == "object") {
|
||||
nodename = val.href;
|
||||
} else {
|
||||
nodename = val;
|
||||
}
|
||||
console.log(nodename);
|
||||
nodename = nodename.replace('/', '');
|
||||
var myrow = document.createElement('div');
|
||||
myrow.innerHTML = "<button id="+nodename+">"+nodename+"</button><br>";
|
||||
document.getElementById("nodes").appendChild(myrow);
|
||||
document.getElementById(nodename).addEventListener("click", function( event ) {
|
||||
var tname = this.id;
|
||||
var url = "/confluent-api/nodes/" + tname + "/console/session";
|
||||
new ConsoleWindow(url, tname);
|
||||
});
|
||||
});
|
||||
});
|
||||
}); // end document
|
||||
@@ -0,0 +1,742 @@
|
||||
/**
|
||||
* tty.js
|
||||
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
|
||||
* Copyright 2014, IBM Corporation
|
||||
* Copyright 2014, Lenovo
|
||||
*/
|
||||
|
||||
;(function() {
|
||||
|
||||
'use strict';
|
||||
/**
|
||||
* Elements
|
||||
*/
|
||||
|
||||
var document = this.document
|
||||
, window = this
|
||||
, root
|
||||
, body
|
||||
, h1
|
||||
, open
|
||||
, lights;
|
||||
|
||||
/**
|
||||
* Helpers
|
||||
*/
|
||||
|
||||
var EventEmitter = Terminal.EventEmitter
|
||||
, inherits = Terminal.inherits
|
||||
, on = Terminal.on
|
||||
, off = Terminal.off
|
||||
, cancel = Terminal.cancel;
|
||||
|
||||
function postRequest(url, data, success) {
|
||||
var request = new XMLHttpRequest();
|
||||
request.open('POST', url, true);
|
||||
request.setRequestHeader('Content-Type', 'application/json');
|
||||
request.setRequestHeader('Accept', 'application/json');
|
||||
request.onload = function() {
|
||||
if (this.status >= 200 && this.status < 400) {
|
||||
success(JSON.parse(this.responseText));
|
||||
}
|
||||
};
|
||||
if (data) {
|
||||
request.send(JSON.stringify(data));
|
||||
} else {
|
||||
request.send("");
|
||||
}
|
||||
request = null;
|
||||
}
|
||||
/**
|
||||
* Console
|
||||
*/
|
||||
|
||||
function ConsoleWindow(consoleurl, nodename) {
|
||||
var self = this;
|
||||
|
||||
if (!(this instanceof ConsoleWindow)) {
|
||||
return new ConsoleWindow(consoleurl, nodename);
|
||||
}
|
||||
|
||||
EventEmitter.call(this);
|
||||
|
||||
var el
|
||||
, grip
|
||||
, bar
|
||||
, button
|
||||
, title;
|
||||
|
||||
el = document.createElement('div');
|
||||
el.className = 'window';
|
||||
|
||||
grip = document.createElement('div');
|
||||
grip.className = 'grip';
|
||||
|
||||
bar = document.createElement('div');
|
||||
bar.className = 'bar';
|
||||
|
||||
button = document.createElement('div');
|
||||
button.innerHTML = 'x';
|
||||
button.title = 'close';
|
||||
button.className = 'tab';
|
||||
|
||||
title = document.createElement('div');
|
||||
title.className = 'title';
|
||||
title.innerHTML = nodename;
|
||||
|
||||
this.nodename = nodename;
|
||||
this.element = el;
|
||||
this.grip = grip;
|
||||
this.bar = bar;
|
||||
this.button = button;
|
||||
this.title = title;
|
||||
this.consoleurl = consoleurl;
|
||||
|
||||
this.tabs = [];
|
||||
this.focused = null;
|
||||
|
||||
this.cols = 100; //Terminal.geometry[0];
|
||||
this.rows = 30; //Terminal.geometry[1];
|
||||
|
||||
el.appendChild(grip);
|
||||
el.appendChild(bar);
|
||||
bar.appendChild(title);
|
||||
bar.appendChild(button);
|
||||
document.body.appendChild(el);
|
||||
|
||||
//tty.windows.push(this);
|
||||
|
||||
this.createTab();
|
||||
this.focus();
|
||||
this.bind();
|
||||
|
||||
this.tabs[0].once('open', function() {
|
||||
//tty.emit('open window', self);
|
||||
self.emit('open');
|
||||
});
|
||||
}
|
||||
|
||||
inherits(ConsoleWindow, EventEmitter);
|
||||
|
||||
ConsoleWindow.prototype.bind = function() {
|
||||
var self = this
|
||||
, el = this.element
|
||||
, bar = this.bar
|
||||
, grip = this.grip
|
||||
, button = this.button
|
||||
, last = 0;
|
||||
|
||||
on(button, 'click', function(ev) {
|
||||
self.destroy();
|
||||
return cancel(ev);
|
||||
});
|
||||
|
||||
on(grip, 'mousedown', function(ev) {
|
||||
self.focus();
|
||||
self.resizing(ev);
|
||||
return cancel(ev);
|
||||
});
|
||||
|
||||
on(el, 'mousedown', function(ev) {
|
||||
if (ev.target !== el && ev.target !== bar) return;
|
||||
|
||||
self.focus();
|
||||
|
||||
cancel(ev);
|
||||
|
||||
if (new Date - last < 600) {
|
||||
return self.maximize();
|
||||
}
|
||||
last = new Date;
|
||||
|
||||
self.drag(ev);
|
||||
|
||||
return cancel(ev);
|
||||
});
|
||||
};
|
||||
|
||||
ConsoleWindow.prototype.focus = function() {
|
||||
// Restack
|
||||
var parent = this.element.parentNode;
|
||||
if (parent) {
|
||||
parent.removeChild(this.element);
|
||||
parent.appendChild(this.element);
|
||||
}
|
||||
|
||||
// Focus Foreground Tab
|
||||
this.focused.focus();
|
||||
|
||||
//tty.emit('focus window', this);
|
||||
this.emit('focus');
|
||||
};
|
||||
|
||||
ConsoleWindow.prototype.destroy = function() {
|
||||
if (this.destroyed) return;
|
||||
this.destroyed = true;
|
||||
|
||||
if (this.minimize) this.minimize();
|
||||
|
||||
//splice(tty.windows, this);
|
||||
//if (tty.windows.length) tty.windows[0].focus();
|
||||
|
||||
this.element.parentNode.removeChild(this.element);
|
||||
|
||||
this.each(function(term) {
|
||||
term.destroy();
|
||||
});
|
||||
|
||||
//tty.emit('close window', this);
|
||||
this.emit('close');
|
||||
};
|
||||
|
||||
ConsoleWindow.prototype.drag = function(ev) {
|
||||
var self = this
|
||||
, el = this.element;
|
||||
|
||||
if (this.minimize) return;
|
||||
|
||||
var drag = {
|
||||
left: el.offsetLeft,
|
||||
top: el.offsetTop,
|
||||
pageX: ev.pageX,
|
||||
pageY: ev.pageY
|
||||
};
|
||||
|
||||
el.style.opacity = '0.60';
|
||||
el.style.cursor = 'move';
|
||||
document.documentElement.style.cursor = 'move';
|
||||
|
||||
function move(ev) {
|
||||
el.style.left =
|
||||
(drag.left + ev.pageX - drag.pageX) + 'px';
|
||||
var tmptop = (drag.top + ev.pageY - drag.pageY);
|
||||
if (tmptop < 0) {
|
||||
tmptop = 0;
|
||||
}
|
||||
el.style.top = tmptop + 'px';
|
||||
}
|
||||
|
||||
function up() {
|
||||
el.style.opacity = '';
|
||||
el.style.cursor = '';
|
||||
document.documentElement.style.cursor = '';
|
||||
|
||||
off(document, 'mousemove', move);
|
||||
off(document, 'mouseup', up);
|
||||
|
||||
var ev = {
|
||||
left: el.style.left.replace(/\w+/g, ''),
|
||||
top: el.style.top.replace(/\w+/g, '')
|
||||
};
|
||||
|
||||
//tty.emit('drag window', self, ev);
|
||||
self.emit('drag', ev);
|
||||
}
|
||||
|
||||
on(document, 'mousemove', move);
|
||||
on(document, 'mouseup', up);
|
||||
};
|
||||
|
||||
ConsoleWindow.prototype.resizing = function(ev) {
|
||||
var self = this
|
||||
, el = this.element
|
||||
, term = this.focused;
|
||||
|
||||
if (this.minimize) delete this.minimize;
|
||||
|
||||
var resize = {
|
||||
w: el.clientWidth,
|
||||
h: el.clientHeight
|
||||
};
|
||||
|
||||
el.style.overflow = 'hidden';
|
||||
el.style.opacity = '0.70';
|
||||
el.style.cursor = 'se-resize';
|
||||
document.documentElement.style.cursor = 'se-resize';
|
||||
term.element.style.height = '100%';
|
||||
|
||||
function move(ev) {
|
||||
var x, y;
|
||||
y = el.offsetHeight - term.element.clientHeight;
|
||||
x = ev.pageX - el.offsetLeft;
|
||||
y = (ev.pageY - el.offsetTop) - y;
|
||||
el.style.width = x + 'px';
|
||||
el.style.height = y + 'px';
|
||||
}
|
||||
|
||||
function up() {
|
||||
var x, y;
|
||||
|
||||
x = el.clientWidth / resize.w;
|
||||
y = el.clientHeight / resize.h;
|
||||
x = (x * term.cols) | 0;
|
||||
y = (y * term.rows) | 0;
|
||||
|
||||
self.resize(x, y);
|
||||
|
||||
el.style.width = '';
|
||||
el.style.height = '';
|
||||
|
||||
el.style.overflow = '';
|
||||
el.style.opacity = '';
|
||||
el.style.cursor = '';
|
||||
document.documentElement.style.cursor = '';
|
||||
term.element.style.height = '';
|
||||
|
||||
off(document, 'mousemove', move);
|
||||
off(document, 'mouseup', up);
|
||||
}
|
||||
|
||||
on(document, 'mousemove', move);
|
||||
on(document, 'mouseup', up);
|
||||
};
|
||||
|
||||
ConsoleWindow.prototype.maximize = function() {
|
||||
if (this.minimize) return this.minimize();
|
||||
|
||||
var self = this
|
||||
, el = this.element
|
||||
, term = this.focused
|
||||
, x
|
||||
, y;
|
||||
|
||||
var m = {
|
||||
cols: term.cols,
|
||||
rows: term.rows,
|
||||
left: el.offsetLeft,
|
||||
top: el.offsetTop,
|
||||
root: root.className
|
||||
};
|
||||
|
||||
this.minimize = function() {
|
||||
delete this.minimize;
|
||||
|
||||
el.style.left = m.left + 'px';
|
||||
el.style.top = m.top + 'px';
|
||||
el.style.width = '';
|
||||
el.style.height = '';
|
||||
term.element.style.width = '';
|
||||
term.element.style.height = '';
|
||||
el.style.boxSizing = '';
|
||||
self.grip.style.display = '';
|
||||
root.className = m.root;
|
||||
|
||||
self.resize(m.cols, m.rows);
|
||||
|
||||
//tty.emit('minimize window', self);
|
||||
self.emit('minimize');
|
||||
};
|
||||
|
||||
window.scrollTo(0, 0);
|
||||
|
||||
x = root.clientWidth / term.element.offsetWidth;
|
||||
y = root.clientHeight / term.element.offsetHeight;
|
||||
x = (x * term.cols) | 0;
|
||||
y = (y * term.rows) | 0;
|
||||
|
||||
el.style.left = '0px';
|
||||
el.style.top = '0px';
|
||||
el.style.width = '100%';
|
||||
el.style.height = '100%';
|
||||
term.element.style.width = '100%';
|
||||
term.element.style.height = '100%';
|
||||
el.style.boxSizing = 'border-box';
|
||||
this.grip.style.display = 'none';
|
||||
root.className = 'maximized';
|
||||
|
||||
this.resize(x, y);
|
||||
|
||||
//tty.emit('maximize window', this);
|
||||
this.emit('maximize');
|
||||
};
|
||||
|
||||
ConsoleWindow.prototype.resize = function(cols, rows) {
|
||||
this.cols = cols;
|
||||
this.rows = rows;
|
||||
|
||||
this.each(function(term) {
|
||||
term.resize(cols, rows);
|
||||
});
|
||||
|
||||
//tty.emit('resize window', this, cols, rows);
|
||||
this.emit('resize', cols, rows);
|
||||
};
|
||||
|
||||
ConsoleWindow.prototype.each = function(func) {
|
||||
var i = this.tabs.length;
|
||||
while (i--) {
|
||||
func(this.tabs[i], i);
|
||||
}
|
||||
};
|
||||
|
||||
ConsoleWindow.prototype.createTab = function() {
|
||||
return new Tab(this, this.consoleurl);
|
||||
};
|
||||
|
||||
ConsoleWindow.prototype.highlight = function() {
|
||||
var self = this;
|
||||
|
||||
this.element.style.borderColor = 'orange';
|
||||
setTimeout(function() {
|
||||
self.element.style.borderColor = '';
|
||||
}, 200);
|
||||
|
||||
this.focus();
|
||||
};
|
||||
|
||||
ConsoleWindow.prototype.focusTab = function(next) {
|
||||
var tabs = this.tabs
|
||||
, i = indexOf(tabs, this.focused)
|
||||
, l = tabs.length;
|
||||
|
||||
if (!next) {
|
||||
if (tabs[--i]) return tabs[i].focus();
|
||||
if (tabs[--l]) return tabs[l].focus();
|
||||
} else {
|
||||
if (tabs[++i]) return tabs[i].focus();
|
||||
if (tabs[0]) return tabs[0].focus();
|
||||
}
|
||||
|
||||
return this.focused && this.focused.focus();
|
||||
};
|
||||
|
||||
ConsoleWindow.prototype.nextTab = function() {
|
||||
return this.focusTab(true);
|
||||
};
|
||||
|
||||
ConsoleWindow.prototype.previousTab = function() {
|
||||
return this.focusTab(false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Tab
|
||||
*/
|
||||
|
||||
function Tab(win, consoleurl) {
|
||||
var self = this;
|
||||
|
||||
var cols = win.cols
|
||||
, rows = win.rows;
|
||||
|
||||
Terminal.call(this, {
|
||||
cols: cols,
|
||||
rows: rows
|
||||
});
|
||||
|
||||
var button = document.createElement('div');
|
||||
button.className = 'tab';
|
||||
button.innerHTML = '\u2022';
|
||||
//win.bar.appendChild(button);
|
||||
|
||||
on(button, 'click', function(ev) {
|
||||
if (ev.ctrlKey || ev.altKey || ev.metaKey || ev.shiftKey) {
|
||||
self.destroy();
|
||||
} else {
|
||||
self.focus();
|
||||
}
|
||||
return cancel(ev);
|
||||
});
|
||||
|
||||
this.id = '';
|
||||
this.consoleurl = consoleurl;
|
||||
this.clientcount = 0;
|
||||
this.connectstate = 'unconnected';
|
||||
this.lasterror = ''
|
||||
this.window = win;
|
||||
this.button = button;
|
||||
this.element = null;
|
||||
this.process = '';
|
||||
this.open();
|
||||
this.hookKeys();
|
||||
// Now begins the code that will embarass me when I actually know my way
|
||||
// around javascript -jbjohnso
|
||||
this.sessid = '';
|
||||
this.datapending = false;
|
||||
this.waitingdata = false;
|
||||
this.sentdata = function(data, textStatus, jqXHR) {
|
||||
if (this.waitingdata) {
|
||||
postRequest(consoleurl, { session: this.sessid, bytes: this.waitingdata }, this.sentdata);
|
||||
this.waitingdata = false;
|
||||
} else {
|
||||
this.datapending = false;
|
||||
}
|
||||
}.bind(this);
|
||||
this.on('data', function(data) {
|
||||
// Send data to console from terminal
|
||||
if (this.datapending) {
|
||||
if (!this.waitingdata) {
|
||||
this.waitingdata = data;
|
||||
} else {
|
||||
this.waitingdata = this.waitingdata + data;
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.datapending = true;
|
||||
postRequest(consoleurl, { session: this.sessid, bytes: data }, this.sentdata);
|
||||
}.bind(this));
|
||||
this.gotdata = function(data, textStatus, jqXHR) {
|
||||
if ("data" in data) {
|
||||
this.write(data.data);
|
||||
}
|
||||
var updatetitle = false;
|
||||
var updateinfo = [];
|
||||
if ("connectstate" in data) {
|
||||
updatetitle = true;
|
||||
this.connectstate = data.connectstate;
|
||||
}
|
||||
if (this.connectstate != "connected") {
|
||||
updateinfo.push(this.connectstate);
|
||||
} else {
|
||||
self.lasterror = '';
|
||||
}
|
||||
if ("error" in data) {
|
||||
updatetitle = true;
|
||||
this.lasterror = data.error
|
||||
}
|
||||
if (this.lasterror != '') {
|
||||
updateinfo.push(this.lasterror);
|
||||
}
|
||||
if ("clientcount" in data) {
|
||||
updatetitle = true;
|
||||
this.clientcount = data.clientcount;
|
||||
}
|
||||
if (this.clientcount > 1) {
|
||||
updateinfo.push("clients: " + this.clientcount.toString());
|
||||
}
|
||||
if (updatetitle == true) {
|
||||
if (updateinfo.length > 0) {
|
||||
this.window.title.innerHTML = this.window.nodename + " [" + updateinfo.join() + "]";
|
||||
} else {
|
||||
this.window.title.innerHTML = this.window.nodename;
|
||||
}
|
||||
}
|
||||
postRequest(consoleurl, { session: this.sessid }, this.gotdata);
|
||||
}.bind(this);
|
||||
this.gotsession = function(data, textStatus, jqXHR) {
|
||||
this.sessid = data.session
|
||||
postRequest(consoleurl, { session: this.sessid }, this.gotdata);
|
||||
}.bind(this);
|
||||
postRequest(consoleurl, false, this.gotsession);
|
||||
|
||||
win.tabs.push(this);
|
||||
};
|
||||
|
||||
inherits(Tab, Terminal);
|
||||
|
||||
Tab.prototype._write = Tab.prototype.write;
|
||||
|
||||
Tab.prototype.write = function(data) {
|
||||
if (this.window.focused !== this) this.button.style.color = 'red';
|
||||
return this._write(data);
|
||||
};
|
||||
|
||||
Tab.prototype._focus = Tab.prototype.focus;
|
||||
|
||||
Tab.prototype.focus = function() {
|
||||
if (Terminal.focus === this) return;
|
||||
|
||||
var win = this.window;
|
||||
|
||||
// maybe move to Tab.prototype.switch
|
||||
if (win.focused !== this) {
|
||||
if (win.focused) {
|
||||
if (win.focused.element.parentNode) {
|
||||
win.focused.element.parentNode.removeChild(win.focused.element);
|
||||
}
|
||||
win.focused.button.style.fontWeight = '';
|
||||
}
|
||||
|
||||
win.element.appendChild(this.element);
|
||||
win.focused = this;
|
||||
|
||||
//win.title.innerHTML = this.process;
|
||||
this.button.style.fontWeight = 'bold';
|
||||
this.button.style.color = '';
|
||||
}
|
||||
|
||||
this._focus();
|
||||
|
||||
win.focus();
|
||||
|
||||
//tty.emit('focus tab', this);
|
||||
this.emit('focus');
|
||||
};
|
||||
|
||||
Tab.prototype._resize = Tab.prototype.resize;
|
||||
|
||||
Tab.prototype.resize = function(cols, rows) {
|
||||
//this.socket.emit('resize', this.id, cols, rows);
|
||||
this._resize(cols, rows);
|
||||
//tty.emit('resize tab', this, cols, rows);
|
||||
this.emit('resize', cols, rows);
|
||||
};
|
||||
|
||||
Tab.prototype.__destroy = Tab.prototype.destroy;
|
||||
|
||||
Tab.prototype._destroy = function() {
|
||||
if (this.destroyed) return;
|
||||
this.destroyed = true;
|
||||
|
||||
var win = this.window;
|
||||
|
||||
this.button.parentNode.removeChild(this.button);
|
||||
if (this.element.parentNode) {
|
||||
this.element.parentNode.removeChild(this.element);
|
||||
}
|
||||
|
||||
if (tty.terms[this.id]) delete tty.terms[this.id];
|
||||
splice(win.tabs, this);
|
||||
|
||||
if (win.focused === this) {
|
||||
win.previousTab();
|
||||
}
|
||||
|
||||
if (!win.tabs.length) {
|
||||
win.destroy();
|
||||
}
|
||||
|
||||
this.__destroy();
|
||||
};
|
||||
|
||||
Tab.prototype.destroy = function() {
|
||||
if (this.destroyed) return;
|
||||
//TODO: politely let server know of client closure
|
||||
this._destroy();
|
||||
//tty.emit('close tab', this);
|
||||
this.emit('close');
|
||||
};
|
||||
|
||||
Tab.prototype.hookKeys = function() {
|
||||
var self = this;
|
||||
|
||||
// Alt-[jk] to quickly swap between windows.
|
||||
this.on('key', function(key, ev) {
|
||||
if (Terminal.focusKeys === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
var offset
|
||||
, i;
|
||||
|
||||
if (key === '\x1bj') {
|
||||
offset = -1;
|
||||
} else if (key === '\x1bk') {
|
||||
offset = +1;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
i = indexOf(tty.windows, this.window) + offset;
|
||||
|
||||
this._ignoreNext();
|
||||
|
||||
if (tty.windows[i]) return tty.windows[i].highlight();
|
||||
|
||||
if (offset > 0) {
|
||||
if (tty.windows[0]) return tty.windows[0].highlight();
|
||||
} else {
|
||||
i = tty.windows.length - 1;
|
||||
if (tty.windows[i]) return tty.windows[i].highlight();
|
||||
}
|
||||
|
||||
return this.window.highlight();
|
||||
});
|
||||
|
||||
this.on('request paste', function(key) {
|
||||
this.socket.emit('request paste', function(err, text) {
|
||||
if (err) return;
|
||||
self.send(text);
|
||||
});
|
||||
});
|
||||
|
||||
this.on('request create', function() {
|
||||
this.window.createTab();
|
||||
});
|
||||
|
||||
this.on('request term', function(key) {
|
||||
if (this.window.tabs[key]) {
|
||||
this.window.tabs[key].focus();
|
||||
}
|
||||
});
|
||||
|
||||
this.on('request term next', function(key) {
|
||||
this.window.nextTab();
|
||||
});
|
||||
|
||||
this.on('request term previous', function(key) {
|
||||
this.window.previousTab();
|
||||
});
|
||||
};
|
||||
|
||||
Tab.prototype._ignoreNext = function() {
|
||||
// Don't send the next key.
|
||||
var handler = this.handler;
|
||||
this.handler = function() {
|
||||
this.handler = handler;
|
||||
};
|
||||
var showCursor = this.showCursor;
|
||||
this.showCursor = function() {
|
||||
this.showCursor = showCursor;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Program-specific Features
|
||||
*/
|
||||
|
||||
Tab.prototype._bindMouse = Tab.prototype.bindMouse;
|
||||
|
||||
Tab.prototype.bindMouse = function() {
|
||||
if (!Terminal.programFeatures) return this._bindMouse();
|
||||
|
||||
var self = this;
|
||||
|
||||
var wheelEvent = 'onmousewheel' in window
|
||||
? 'mousewheel'
|
||||
: 'DOMMouseScroll';
|
||||
|
||||
on(self.element, wheelEvent, function(ev) {
|
||||
if (self.mouseEvents) return;
|
||||
|
||||
if ((ev.type === 'mousewheel' && ev.wheelDeltaY > 0)
|
||||
|| (ev.type === 'DOMMouseScroll' && ev.detail < 0)) {
|
||||
// page up
|
||||
self.keyDown({keyCode: 33});
|
||||
} else {
|
||||
// page down
|
||||
self.keyDown({keyCode: 34});
|
||||
}
|
||||
|
||||
return cancel(ev);
|
||||
});
|
||||
|
||||
return this._bindMouse();
|
||||
};
|
||||
|
||||
/**
|
||||
* Helpers
|
||||
*/
|
||||
|
||||
function indexOf(obj, el) {
|
||||
var i = obj.length;
|
||||
while (i--) {
|
||||
if (obj[i] === el) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
function splice(obj, el) {
|
||||
var i = indexOf(obj, el);
|
||||
if (~i) obj.splice(i, 1);
|
||||
}
|
||||
|
||||
function sanitize(text) {
|
||||
if (!text) return '';
|
||||
return (text + '').replace(/[&<>]/g, '')
|
||||
}
|
||||
|
||||
this.ConsoleWindow = ConsoleWindow;
|
||||
|
||||
}).call(function() {
|
||||
return this;
|
||||
}());
|
||||
File diff suppressed because it is too large
Load Diff
-37
@@ -1,37 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
import numpy as np
|
||||
import matplotlib as mpl
|
||||
mpl.use('Agg')
|
||||
import matplotlib.pyplot as plt
|
||||
import os
|
||||
import sixel
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
class DumbWriter(sixel.SixelWriter):
|
||||
def restore_position(self, output):
|
||||
return
|
||||
|
||||
|
||||
plotdata = []
|
||||
data = sys.stdin.readline()
|
||||
while data:
|
||||
datum = float(data.split()[-1])
|
||||
plotdata.append(datum)
|
||||
data = sys.stdin.readline()
|
||||
n, bins, patches = plt.hist(plotdata, 20)
|
||||
plt.show()
|
||||
path = tempfile.mkdtemp()
|
||||
plt.savefig('{0}/histo.png'.format(path))
|
||||
writer = DumbWriter()
|
||||
writer.draw('{0}/histo.png'.format(path))
|
||||
print('Min: {3} Median: {0} Mean: {1} Max: {4} Standard Deviation: {2} Samples: {5}'.format(np.median(plotdata), np.mean(plotdata), np.std(plotdata), np.min(plotdata), np.max(plotdata), len(plotdata)))
|
||||
os.remove('{0}/histo.png'.format(path))
|
||||
os.rmdir(path)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user