mirror of
https://github.com/xcat2/confluent.git
synced 2026-09-29 00:31:09 +00:00
Compare commits
182 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7593e9960c | |||
| bc03da47af | |||
| bd39171611 | |||
| ed050b37e1 | |||
| 8d1d19d9a8 | |||
| 017f3fb372 | |||
| 46518f890b | |||
| 7e86a72872 | |||
| 2567503662 | |||
| 6b56181a52 | |||
| c18ce50138 | |||
| a0684520d8 | |||
| 374aa49016 | |||
| 0b95daa30d | |||
| d33365195b | |||
| 3429173c27 | |||
| f6c44922f8 | |||
| a86d962984 | |||
| 9ee29aabe1 | |||
| a413f321fe | |||
| f2bd796c2a | |||
| bf31c4872f | |||
| 634e5a8944 | |||
| 67e3530d16 | |||
| 3c26beda1d | |||
| e2d0e49fc7 | |||
| da5a34c2e4 | |||
| 3629cb8ee7 | |||
| 8233e0a5bd | |||
| eae7b3bd80 | |||
| 868367e052 | |||
| 6289cfaac4 | |||
| f6d4fef5e6 | |||
| b1b7ec4d50 | |||
| c0cd6de4f7 | |||
| 4437e81e04 | |||
| 6a12af1242 | |||
| 9879a83a10 | |||
| cce6b824de | |||
| ce1cb952e8 | |||
| c6812274e4 | |||
| 7cd7068dd7 | |||
| 48f0330568 | |||
| 66e1d17d28 | |||
| 7480494432 | |||
| 49c00bfbb7 | |||
| 201985dd0e | |||
| 1aee19997a | |||
| 3bc366bef4 | |||
| 4c83a1a04e | |||
| cfae28a869 | |||
| 44e6a72847 | |||
| 006fdc8280 | |||
| 895b5264f6 | |||
| 0b577af1ca | |||
| ff0b1bba7f | |||
| 0badd9e5b4 | |||
| c02064f0a5 | |||
| c1b82d8163 | |||
| 0d5fa7a98a | |||
| 968efe719a | |||
| 7a63ca8759 | |||
| a24866c2df | |||
| c666b11138 | |||
| 22f6198f60 | |||
| c99d01dffc | |||
| 8d0028a1de | |||
| bb9c2297c9 | |||
| 91fa5bd1eb | |||
| ac9609c40d | |||
| 0c4cb49c20 | |||
| 4be4100014 | |||
| 9f7c8c69f2 | |||
| c35f7d99f7 | |||
| 445950d02a | |||
| cf72cf2d8c | |||
| 0652a7321b | |||
| 4c8ba92856 | |||
| 09582d7597 | |||
| 8a9e9aa7b3 | |||
| b766e7b0ee | |||
| 92699e47f2 | |||
| 2aa9910d83 | |||
| 18b6398c64 | |||
| 47b68e4258 | |||
| 79b6d099ab | |||
| 604ebcde3b | |||
| b4b733a573 | |||
| 3bf083deb3 | |||
| 9d770632ce | |||
| 79afd174c9 | |||
| 13a0bf4fbe | |||
| f1e1d9804a | |||
| 546296ce71 | |||
| 954b2dd15c | |||
| a7b11d1e15 | |||
| 3660cf18cc | |||
| 078afaf7d7 | |||
| 3ab1eb7f01 | |||
| fbfdc9322b | |||
| ef68656bde | |||
| 0e04b853f6 | |||
| 63256e37b6 | |||
| a1b81e978b | |||
| 06c53e9d89 | |||
| ab0168b2ec | |||
| 473bb39c54 | |||
| a48ecd8c78 | |||
| 30398fc434 | |||
| 0ed7e4eac3 | |||
| 0d2999c7a8 | |||
| 75010ac6f0 | |||
| a748cc3032 | |||
| 28bb143880 | |||
| 4b6899d4af | |||
| a175fd7345 | |||
| 12bd473c0d | |||
| a967c224fe | |||
| 72a5f37232 | |||
| 5c081a8e2c | |||
| c0b69f64b8 | |||
| f7fe38f498 | |||
| 5ae949b4eb | |||
| cec6918c1a | |||
| 3df7a36c48 | |||
| 559e96f7ff | |||
| 6f568919e1 | |||
| c441739f68 | |||
| a43361bb98 | |||
| 620263db3e | |||
| dd096104cc | |||
| 191ae762ab | |||
| f866b7379c | |||
| aab6160d49 | |||
| e1dffe7c3a | |||
| 97ca6dc48e | |||
| 1d59e1da8c | |||
| 7e6b4fa4d0 | |||
| 186e89cd87 | |||
| 73d6511d9e | |||
| 065ee48325 | |||
| 4a35cc25f0 | |||
| ceced11ae8 | |||
| 62084b3605 | |||
| a84b88e269 | |||
| fc626d36ba | |||
| 606a308046 | |||
| bed0f76dba | |||
| 8c4d04a7cd | |||
| bfbde0f7f3 | |||
| ba96aea2a6 | |||
| d9f1d6c033 | |||
| 1dac61adca | |||
| e008932389 | |||
| b81e5fb3ce | |||
| e2a08e7e73 | |||
| eb48728bc1 | |||
| c6255fa13b | |||
| 2a37d64dc9 | |||
| 8101672c3a | |||
| 453c344f7f | |||
| 9eceda0636 | |||
| f28b91499e | |||
| 8cab591a8b | |||
| 6ce6740b77 | |||
| 8a4a219a14 | |||
| a39d45d03c | |||
| bc85d93cf4 | |||
| 1e963106fe | |||
| a9f0e345db | |||
| 5353b479d9 | |||
| dbc6747c38 | |||
| 0edd1efe0d | |||
| 8e87f5b9e5 | |||
| 0633b2ca67 | |||
| c1953bdad3 | |||
| 578ba06aa3 | |||
| 0975881d3b | |||
| 8fc3b7c9c0 | |||
| 6ea6ebd80e | |||
| 744f8899f7 | |||
| 74f18d5571 |
+4
-1
@@ -1 +1,4 @@
|
||||
.idea
|
||||
*.pyc
|
||||
.*.
|
||||
confluent_client/man/man*
|
||||
.*.sw*
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/python
|
||||
import os
|
||||
import sys
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
try:
|
||||
sys.path.remove(path)
|
||||
except Exception:
|
||||
pass
|
||||
path = os.path.realpath(os.path.join(path, '..', 'confluent_server'))
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.config.attributes as attr
|
||||
import shutil
|
||||
|
||||
shutil.copyfile('doc/man/nodeattrib.ronn.tmpl', 'doc/man/nodeattrib.ronn')
|
||||
shutil.copyfile('doc/man/nodegroupattrib.ronn.tmpl', 'doc/man/nodegroupattrib.ronn')
|
||||
with open('doc/man/nodeattrib.ronn', 'a') as outf:
|
||||
for field in sorted(attr.node):
|
||||
outf.write('\n* `{0}`:\n {1}\n'.format(field, attr.node[field]['description']))
|
||||
with open('doc/man/nodegroupattrib.ronn', 'a') as outf:
|
||||
for field in sorted(attr.node):
|
||||
outf.write('\n* `{0}`:\n {1}\n'.format(field, attr.node[field]['description']))
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@ argparser.add_option('-d', '--diff', action='store_true',
|
||||
'output group and others')
|
||||
argparser.add_option('-w', '--watch', action='store_true',
|
||||
help='Show intermediate results while running')
|
||||
argparser.add_option('-g', '--groupcount', action='store_true',
|
||||
help='Show count of output groups rather than the actual output')
|
||||
argparser.add_option('-s', '--skipcommon', action='store_true',
|
||||
help='Do not print most common result, only non modal '
|
||||
'groups, useful when combined with -d')
|
||||
@@ -69,6 +71,9 @@ def print_current():
|
||||
if options.diff:
|
||||
grouped.print_deviants(skipmodal=options.skipcommon, count=options.count,
|
||||
reverse=options.reverse, basenode=options.base)
|
||||
elif options.groupcount:
|
||||
grouped.generate_byoutput()
|
||||
print(len(grouped.byoutput))
|
||||
else:
|
||||
grouped.print_all(skipmodal=options.skipcommon,
|
||||
count=options.count,
|
||||
|
||||
@@ -76,6 +76,11 @@ import confluent.termhandler as termhandler
|
||||
import confluent.tlvdata as tlvdata
|
||||
import confluent.client as client
|
||||
|
||||
try:
|
||||
unicode
|
||||
except NameError:
|
||||
unicode = str
|
||||
|
||||
conserversequence = '\x05c' # ctrl-e, c
|
||||
clearpowermessage = False
|
||||
|
||||
@@ -89,6 +94,10 @@ except NameError:
|
||||
netserver = None
|
||||
laststate = {}
|
||||
|
||||
try:
|
||||
input = raw_input
|
||||
except NameError:
|
||||
pass
|
||||
|
||||
class BailOut(Exception):
|
||||
def __init__(self, errorcode=0):
|
||||
@@ -195,9 +204,9 @@ def prompt():
|
||||
if os.environ.get('TERM', '') not in ('linux'):
|
||||
sys.stdout.write('\x1b]0;confetty: %s\x07' % target)
|
||||
try:
|
||||
return raw_input(target + ' -> ')
|
||||
return input(target + ' -> ')
|
||||
except KeyboardInterrupt:
|
||||
print ""
|
||||
print("")
|
||||
return ""
|
||||
except EOFError: # ctrl-d
|
||||
print("exit")
|
||||
@@ -295,8 +304,11 @@ currchildren = None
|
||||
|
||||
|
||||
def print_result(res):
|
||||
global exitcode
|
||||
if 'errorcode' in res or 'error' in res:
|
||||
print res['error']
|
||||
print(res['error'])
|
||||
if 'errorcode' in res:
|
||||
exitcode |= res['errorcode']
|
||||
return
|
||||
if 'databynode' in res:
|
||||
print_result(res['databynode'])
|
||||
@@ -309,9 +321,9 @@ def print_result(res):
|
||||
attrstr = '%s=%s' % (key, recurse_format(res[key]))
|
||||
elif not isinstance(res[key], dict):
|
||||
try:
|
||||
print '{0}: {1}'.format(key, res[key])
|
||||
print('{0}: {1}'.format(key, res[key]))
|
||||
except UnicodeEncodeError:
|
||||
print '{0}: {1}'.format(key, repr(res[key]))
|
||||
print('{0}: {1}'.format(key, repr(res[key])))
|
||||
continue
|
||||
elif 'value' in res[key] and res[key]['value'] is not None:
|
||||
attrstr = '%s="%s"' % (key, res[key]['value'])
|
||||
@@ -324,7 +336,7 @@ def print_result(res):
|
||||
else:
|
||||
sys.stdout.write('{0}: '.format(key))
|
||||
if isinstance(res[key], str) or isinstance(res[key], unicode):
|
||||
print res[key]
|
||||
print(res[key])
|
||||
else:
|
||||
print_result(res[key])
|
||||
continue
|
||||
@@ -423,10 +435,10 @@ def do_command(command, server):
|
||||
for res in session.read(targpath):
|
||||
if 'item' in res: # a link relation
|
||||
if type(res['item']) == dict:
|
||||
print res['item']["href"]
|
||||
print(res['item']["href"])
|
||||
else:
|
||||
for item in res['item']:
|
||||
print item["href"]
|
||||
print(item["href"])
|
||||
else: # generic attributes to list
|
||||
if 'error' in res:
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
@@ -851,7 +863,7 @@ def server_connect():
|
||||
passphrase = os.environ['CONFLUENT_PASSPHRASE']
|
||||
session.authenticate(username, passphrase)
|
||||
while not session.authenticated:
|
||||
username = raw_input("Name: ")
|
||||
username = input("Name: ")
|
||||
passphrase = getpass.getpass("Passphrase: ")
|
||||
session.authenticate(username, passphrase)
|
||||
|
||||
@@ -871,7 +883,7 @@ def main():
|
||||
global inconsole
|
||||
try:
|
||||
server_connect()
|
||||
except EOFError, KeyboardInterrupt:
|
||||
except (EOFError, KeyboardInterrupt) as _:
|
||||
raise BailOut(0)
|
||||
except socket.gaierror:
|
||||
sys.stderr.write('Could not connect to confluent\n')
|
||||
@@ -929,6 +941,7 @@ def main():
|
||||
updatestatus(data)
|
||||
continue
|
||||
if data is not None:
|
||||
data = client.stringify(data)
|
||||
if clearpowermessage:
|
||||
sys.stdout.write("\x1b[2J\x1b[;H")
|
||||
clearpowermessage = False
|
||||
@@ -952,7 +965,7 @@ def main():
|
||||
try:
|
||||
server_connect()
|
||||
connected = True
|
||||
except (socket.gaierror, socket.error):
|
||||
except Exception:
|
||||
pass
|
||||
if not connected:
|
||||
time.sleep(1)
|
||||
|
||||
@@ -50,6 +50,9 @@ argparser.add_option('-c', '--clear', action='store_true',
|
||||
help='Clear attributes')
|
||||
argparser.add_option('-p', '--prompt', action='store_true',
|
||||
help='Prompt for attribute values interactively')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Prompt if trying to set attributes on more '
|
||||
'than specified number of nodes')
|
||||
(options, args) = argparser.parse_args()
|
||||
|
||||
|
||||
@@ -87,6 +90,7 @@ if len(args) > 1:
|
||||
if oneval != twoval:
|
||||
print('Values did not match.')
|
||||
argassign[arg] = twoval
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
exitcode=client.updateattrib(session,args,nodetype, noderange, options, argassign)
|
||||
try:
|
||||
# setting user output to what the user inputs
|
||||
|
||||
@@ -32,6 +32,8 @@ if path.startswith('/opt'):
|
||||
import confluent.client as client
|
||||
|
||||
argparser = optparse.OptionParser(usage="Usage: %prog <noderange>")
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Number of nodes to affect before prompting for confirmation')
|
||||
(options, args) = argparser.parse_args()
|
||||
try:
|
||||
noderange = args[0]
|
||||
@@ -43,8 +45,10 @@ session = client.Command()
|
||||
exitcode = 0
|
||||
|
||||
errorNodes = set([])
|
||||
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
success = session.simple_noderange_command(noderange, 'configuration/management_controller/reset', 'reset', key='state', errnodes=errorNodes) # = 0 if successful
|
||||
if success != 0:
|
||||
sys.exit(success)
|
||||
|
||||
# Determine which nodes were successful and print them
|
||||
|
||||
@@ -56,7 +60,7 @@ for node in session.read('/noderange/{0}/nodes/'.format(noderange)):
|
||||
goodNodes = allNodes - errorNodes
|
||||
|
||||
for node in goodNodes:
|
||||
print node + ": BMC Reset Successful"
|
||||
print(node + ": BMC Reset Successful")
|
||||
|
||||
|
||||
sys.exit(success)
|
||||
|
||||
@@ -42,6 +42,10 @@ argparser.add_option('-p', '--persist', dest='persist', action='store_true',
|
||||
default=False,
|
||||
help='Request the boot device be persistent rather than '
|
||||
'one time')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to boot, '
|
||||
'prompting if over the threshold')
|
||||
|
||||
(options, args) = argparser.parse_args()
|
||||
|
||||
@@ -54,8 +58,8 @@ except IndexError:
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
bootdev = None
|
||||
if len(sys.argv) > 2:
|
||||
bootdev = sys.argv[2]
|
||||
if len(args) > 1:
|
||||
bootdev = args[1]
|
||||
if bootdev in ('net', 'pxe'):
|
||||
bootdev = 'network'
|
||||
session = client.Command()
|
||||
@@ -66,6 +70,7 @@ else:
|
||||
bootmode = 'uefi'
|
||||
|
||||
errnodes = set([])
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
rc = session.simple_noderange_command(noderange, '/boot/nextdevice', bootdev,
|
||||
bootmode=bootmode,
|
||||
persistent=options.persist,
|
||||
|
||||
@@ -54,6 +54,12 @@ argparser.add_option('-d', '--detail', dest='detail',
|
||||
action='store_true', default=False,
|
||||
help='Provide verbose information as available, such as '
|
||||
'help text and possible valid values')
|
||||
argparser.add_option('-e', '--extra', dest='extra',
|
||||
action='store_true', default=False,
|
||||
help='Access extra configuration. Extra configuration is generally '
|
||||
'reserved for unpopular or redundant options that may be slow to '
|
||||
'read. Notably the IMM category on Lenovo settings is considered '
|
||||
'to be extra configuration')
|
||||
argparser.add_option('-x', '--exclude', dest='exclude',
|
||||
action='store_true', default=False,
|
||||
help='Treat positional arguments as items to not '
|
||||
@@ -68,6 +74,10 @@ argparser.add_option('-r', '--restoredefault', default=False,
|
||||
help='Restore the configuration of the node '
|
||||
'to factory default for given component. '
|
||||
'Currently only uefi is supported')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to configure, '
|
||||
'prompting if over the threshold')
|
||||
(options, args) = argparser.parse_args()
|
||||
|
||||
cfgpaths = {
|
||||
@@ -99,6 +109,7 @@ assignment = {}
|
||||
queryparms = {}
|
||||
printsys = []
|
||||
printbmc = []
|
||||
printextbmc = []
|
||||
printallbmc = False
|
||||
setsys = {}
|
||||
forceset = False
|
||||
@@ -173,11 +184,17 @@ def parse_config_line(arguments):
|
||||
del queryparms[path]
|
||||
except KeyError:
|
||||
pass
|
||||
if not matchedparms:
|
||||
if param.lower() == 'imm':
|
||||
printextbmc.append(param)
|
||||
options.extra = True
|
||||
elif not matchedparms:
|
||||
printsys.append(param)
|
||||
elif param not in cfgpaths:
|
||||
if param.startswith('bmc.'):
|
||||
printbmc.append(param.replace('bmc.', ''))
|
||||
elif param.lower().startswith('imm'):
|
||||
options.extra = True
|
||||
printextbmc.append(param)
|
||||
else:
|
||||
printsys.append(param)
|
||||
else:
|
||||
@@ -205,6 +222,7 @@ else:
|
||||
session = client.Command()
|
||||
rcode = 0
|
||||
if options.restoredefault:
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
if options.restoredefault.lower() in (
|
||||
'sys', 'system', 'uefi', 'bios'):
|
||||
for fr in session.update(
|
||||
@@ -225,6 +243,7 @@ if options.restoredefault:
|
||||
options.restoredefault))
|
||||
sys.exit(1)
|
||||
if setmode:
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
if options.exclude:
|
||||
sys.stderr.write('Cannot use exclude and assign at the same time\n')
|
||||
sys.exit(1)
|
||||
@@ -269,10 +288,15 @@ else:
|
||||
NullOpt(), queryparms[path])
|
||||
if rc:
|
||||
sys.exit(rc)
|
||||
if printbmc or printallbmc:
|
||||
rcode = client.print_attrib_path(
|
||||
'/noderange/{0}/configuration/management_controller/extended/all'.format(noderange),
|
||||
session, printbmc, options, attrprefix='bmc.')
|
||||
if printsys == 'all' or printextbmc or printbmc or printallbmc:
|
||||
if printbmc or not printextbmc:
|
||||
rcode = client.print_attrib_path(
|
||||
'/noderange/{0}/configuration/management_controller/extended/all'.format(noderange),
|
||||
session, printbmc, options, attrprefix='bmc.')
|
||||
if options.extra:
|
||||
rcode |= client.print_attrib_path(
|
||||
'/noderange/{0}/configuration/management_controller/extended/extra'.format(noderange),
|
||||
session, printextbmc, options)
|
||||
if printsys or options.exclude:
|
||||
if printsys == 'all':
|
||||
printsys = []
|
||||
@@ -281,7 +305,6 @@ else:
|
||||
else:
|
||||
path = '/noderange/{0}/configuration/system/advanced'.format(
|
||||
noderange)
|
||||
|
||||
rcode = client.print_attrib_path(path, session, printsys,
|
||||
options)
|
||||
sys.exit(rcode)
|
||||
|
||||
@@ -51,27 +51,28 @@ if options.tile:
|
||||
nodes.append(node)
|
||||
initial = True
|
||||
pane = 0
|
||||
sessname = 'nodeconsole_{0}'.format(os.getpid())
|
||||
for node in sortutil.natural_sort(nodes):
|
||||
panename = '{0}:{1}'.format(sessname, pane)
|
||||
if initial:
|
||||
initial = False
|
||||
subprocess.call(
|
||||
['tmux', 'new-session', '-d', '-s',
|
||||
'nodeconsole_{0}'.format(os.getpid()), '-x', '800', '-y',
|
||||
sessname, '-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', 'select-pane', '-t', sessname])
|
||||
subprocess.call(['tmux', 'set-option', '-t', panename, 'pane-border-status', 'top'], stderr=null)
|
||||
subprocess.call(
|
||||
['tmux', 'split', '-h',
|
||||
['tmux', 'split', '-h', '-t', sessname,
|
||||
'{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()))
|
||||
subprocess.call(['tmux', 'select-layout', '-t', sessname, 'tiled'], stdout=null)
|
||||
pane += 1
|
||||
subprocess.call(['tmux', 'select-pane', '-t', sessname])
|
||||
subprocess.call(['tmux', 'set-option', '-t', panename, 'pane-border-status', 'top'], stderr=null)
|
||||
os.execlp('tmux', 'tmux', 'attach', '-t', sessname)
|
||||
else:
|
||||
os.execl(confettypath, confettypath, 'start',
|
||||
'/nodes/{0}/console/session'.format(args[0]))
|
||||
|
||||
@@ -237,10 +237,12 @@ def clear_discovery(options, session):
|
||||
else:
|
||||
print(repr(res))
|
||||
|
||||
def list_matching_macs(options, session, node=None):
|
||||
def list_matching_macs(options, session, node=None, checknode=True):
|
||||
path = '/discovery/'
|
||||
if node:
|
||||
path += 'by-node/{0}/'.format(node)
|
||||
elif checknode and options.node:
|
||||
path += 'by-node/{0}/'.format(options.node)
|
||||
if options.model:
|
||||
path += 'by-model/{0}/'.format(options.model)
|
||||
if options.serial:
|
||||
@@ -277,11 +279,11 @@ def assign_discovery(options, session, needid=True):
|
||||
abort = True
|
||||
if abort:
|
||||
sys.exit(1)
|
||||
matches = list_matching_macs(options, session, None if needid else options.node)
|
||||
matches = list_matching_macs(options, session, None if needid else options.node, False)
|
||||
if not matches:
|
||||
# Do a rescan to catch missing requested data
|
||||
blocking_scan(session)
|
||||
matches = list_matching_macs(options, session, None if needid else options.node)
|
||||
matches = list_matching_macs(options, session, None if needid else options.node, False)
|
||||
if not matches:
|
||||
sys.stderr.write("No matching discovery candidates found\n")
|
||||
sys.exit(1)
|
||||
|
||||
@@ -38,6 +38,10 @@ if sys.version_info[0] < 3:
|
||||
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: %prog [options] noderange [clear]")
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to clear if clearing log, '
|
||||
'prompting if over the threshold')
|
||||
(options, args) = argparser.parse_args()
|
||||
try:
|
||||
noderange = args[0]
|
||||
@@ -46,11 +50,11 @@ except IndexError:
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
deletemode = False
|
||||
if len(sys.argv) > 3:
|
||||
if len(args) > 2:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
if len(sys.argv) == 3:
|
||||
if sys.argv[2] == 'clear':
|
||||
if len(args) == 2:
|
||||
if args[1] == 'clear':
|
||||
deletemode = True
|
||||
else:
|
||||
argparser.print_help()
|
||||
@@ -88,6 +92,7 @@ def format_event(evt):
|
||||
|
||||
if deletemode:
|
||||
func = session.delete
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
else:
|
||||
func = session.read
|
||||
for rsp in func('/noderange/{0}/events/hardware/log'.format(noderange)):
|
||||
|
||||
@@ -59,6 +59,10 @@ argparser = optparse.OptionParser(
|
||||
"%prog <noderange> [list][update [--backup <file>]]|[<components>]")
|
||||
argparser.add_option('-b', '--backup', action='store_true',
|
||||
help='Target a backup bank rather than primary')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='When updating, prompt if more than the specified '
|
||||
'number of servers will be affected')
|
||||
|
||||
(options, args) = argparser.parse_args()
|
||||
upfile = None
|
||||
try:
|
||||
@@ -95,6 +99,7 @@ def get_update_progress(session, url):
|
||||
|
||||
def update_firmware(session, filename):
|
||||
global exitcode
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
output = sq.ScreenPrinter(noderange, session)
|
||||
nodeurls = {}
|
||||
filename = os.path.abspath(filename)
|
||||
|
||||
@@ -97,7 +97,7 @@ if len(args) > 1:
|
||||
|
||||
requestargs=args[1:]
|
||||
except Exception as e:
|
||||
print str(e)
|
||||
print(str(e))
|
||||
|
||||
if exitcode != 0:
|
||||
sys.exit(exitcode)
|
||||
@@ -123,6 +123,6 @@ else:
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
exitcode = 1
|
||||
else:
|
||||
print res['item']['href'].replace('/', '')
|
||||
print(res['item']['href'].replace('/', ''))
|
||||
|
||||
sys.exit(exitcode)
|
||||
|
||||
@@ -37,6 +37,10 @@ exitcode = 0
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: "
|
||||
"%prog <noderange> [list][install <file>|save <directory>|delete <name>]")
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to delete licenses from, '
|
||||
'prompting if over the threshold')
|
||||
(options, args) = argparser.parse_args()
|
||||
upfile = None
|
||||
downdir = None
|
||||
@@ -52,7 +56,7 @@ try:
|
||||
delete = args[2]
|
||||
elif args[1] != 'list':
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
sys.exit(1)
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
@@ -61,10 +65,6 @@ client.check_globbing(noderange)
|
||||
|
||||
def install_license(session, filename):
|
||||
global exitcode
|
||||
if not os.path.exists(filename):
|
||||
sys.stderr.write('Unable to locate requested file {0}\n'.format(
|
||||
filename))
|
||||
sys.exit(404)
|
||||
resource = '/noderange/{0}/configuration/' \
|
||||
'management_controller/licenses/'.format(noderange)
|
||||
filename = os.path.abspath(filename)
|
||||
@@ -83,18 +83,16 @@ def save_licenses(session, dirname):
|
||||
resource = '/noderange/{0}/configuration/' \
|
||||
'management_controller/save_licenses'.format(noderange)
|
||||
filename = os.path.abspath(dirname)
|
||||
if not os.path.exists(filename):
|
||||
sys.stderr.write('Unable to locate specified directory {0}\n'.format(
|
||||
filename))
|
||||
sys.exit(404)
|
||||
instargs = {'dirname': filename}
|
||||
for res in session.create(resource, instargs):
|
||||
for node in res.get('databynode', {}):
|
||||
fname = res['databynode'][node].get('filename', None)
|
||||
if fname:
|
||||
print('{0}: Saved license to {1}'.format(node, fname))
|
||||
elif 'error' in res['databynode'][node]:
|
||||
sys.stderr.write('{0}: {1}\n'.format(node, res['databynode'][node]['error']))
|
||||
else:
|
||||
sys.stderr.write('{0}: {1}', node, repr(res['databynode'][node]))
|
||||
sys.stderr.write('{0}: {1}\n'.format(node, repr(res['databynode'][node])))
|
||||
|
||||
|
||||
def show_licenses(session):
|
||||
@@ -138,6 +136,7 @@ try:
|
||||
elif downdir:
|
||||
save_licenses(session, downdir)
|
||||
elif delete:
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
delete_license(session, delete)
|
||||
else:
|
||||
show_licenses(session)
|
||||
|
||||
@@ -37,6 +37,11 @@ argparser = optparse.OptionParser(
|
||||
argparser.add_option('-p', '--showprevious', dest='previous',
|
||||
action='store_true', default=False,
|
||||
help='Show previous power state')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to change power state, '
|
||||
'prompting if over the threshold')
|
||||
|
||||
(options, args) = argparser.parse_args()
|
||||
try:
|
||||
noderange = args[0]
|
||||
@@ -72,4 +77,4 @@ if options.previous:
|
||||
# add dictionary to session
|
||||
session.add_precede_dict(prev)
|
||||
|
||||
sys.exit(session.simple_noderange_command(noderange, '/power/state', setstate))
|
||||
sys.exit(session.simple_noderange_command(noderange, '/power/state', setstate, promptover=options.maxnodes))
|
||||
@@ -35,6 +35,10 @@ import confluent.client as client
|
||||
argparser = optparse.OptionParser(
|
||||
usage='''\n %prog noderange
|
||||
\n ''')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to delete, '
|
||||
'prompting if over the threshold')
|
||||
(options, args) = argparser.parse_args()
|
||||
if len(args) != 1:
|
||||
argparser.print_help()
|
||||
@@ -43,6 +47,7 @@ noderange = args[0]
|
||||
client.check_globbing(noderange)
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
for r in session.delete('/noderange/{0}'.format(noderange)):
|
||||
if 'error' in r:
|
||||
sys.stderr.write(r['error'] + '\n')
|
||||
|
||||
@@ -32,6 +32,10 @@ if path.startswith('/opt'):
|
||||
import confluent.client as client
|
||||
|
||||
argparser = optparse.OptionParser(usage="Usage: %prog <noderange>")
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to reseat, '
|
||||
'prompting if over the threshold')
|
||||
(options, args) = argparser.parse_args()
|
||||
try:
|
||||
noderange = args[0]
|
||||
@@ -43,7 +47,7 @@ session = client.Command()
|
||||
exitcode = 0
|
||||
|
||||
errorNodes = set([])
|
||||
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
success = session.simple_noderange_command(noderange, 'power/reseat', 'reseat', key='reseat', errnodes=errorNodes) # = 0 if successful
|
||||
|
||||
# Determine which nodes were successful and print them
|
||||
@@ -56,7 +60,7 @@ for node in session.read('/noderange/{0}/nodes/'.format(noderange)):
|
||||
goodNodes = allNodes - errorNodes
|
||||
|
||||
for node in goodNodes:
|
||||
print node + ": Reseat successful"
|
||||
print(node + ": Reseat successful")
|
||||
|
||||
|
||||
sys.exit(success)
|
||||
|
||||
@@ -42,6 +42,10 @@ def run():
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: %prog location noderange:location",
|
||||
)
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to run rsync to, '
|
||||
'prompting if over the threshold')
|
||||
argparser.add_option('-f', '-c', '--count', type='int', default=168,
|
||||
help='Number of nodes to concurrently rsync')
|
||||
# among other things, FD_SETSIZE limits. Besides, spawning too many
|
||||
@@ -55,7 +59,7 @@ def run():
|
||||
noderange, targpath = args[-1].split(':', 1)
|
||||
client.check_globbing(noderange)
|
||||
c = client.Command()
|
||||
cmdstr = " ".join(args[:-1])
|
||||
cmdstr = ' '.join(args[:-1])
|
||||
cmdstr = 'rsync -av --info=progress2 ' + cmdstr
|
||||
cmdstr += ' {node}:' + targpath
|
||||
|
||||
@@ -64,15 +68,17 @@ def run():
|
||||
pipedesc = {}
|
||||
pendingexecs = deque()
|
||||
exitcode = 0
|
||||
|
||||
c.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
for exp in c.create('/noderange/{0}/attributes/expression'.format(noderange),
|
||||
{'expression': cmdstr}):
|
||||
{'expression': cmdstr}):
|
||||
if 'error' in exp:
|
||||
sys.stderr.write(exp['error'] + '\n')
|
||||
exitcode |= exp.get('errorcode', 1)
|
||||
ex = exp.get('databynode', ())
|
||||
for node in ex:
|
||||
cmd = ex[node]['value'].encode('utf-8')
|
||||
cmd = ex[node]['value']
|
||||
if not isinstance(cmd, bytes) and not isinstance(cmd, str):
|
||||
cmd = cmd.encode('utf-8')
|
||||
cmdv = shlex.split(cmd)
|
||||
if currprocs < concurrentprocs:
|
||||
currprocs += 1
|
||||
@@ -98,7 +104,7 @@ def run():
|
||||
if desc['type'] == 'stdout':
|
||||
if node not in pernodeout:
|
||||
pernodeout[node] = ''
|
||||
pernodeout[node] += data
|
||||
pernodeout[node] += client.stringify(data)
|
||||
if '\n' in pernodeout[node]:
|
||||
currout, pernodeout[node] = pernodeout[node].split('\n', 1)
|
||||
if currout:
|
||||
@@ -117,7 +123,7 @@ def run():
|
||||
output.set_output(node, 'error!')
|
||||
if node not in nodeerrs:
|
||||
nodeerrs[node] = ''
|
||||
nodeerrs[node] += data
|
||||
nodeerrs[node] += client.stringify(data)
|
||||
else:
|
||||
pop = desc['popen']
|
||||
ret = pop.poll()
|
||||
|
||||
@@ -46,6 +46,10 @@ def run():
|
||||
help='Number of commands to run at a time')
|
||||
argparser.add_option('-n', '--nonodeprefix', action='store_true',
|
||||
help='Do not prefix output with node names')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to run the command with, '
|
||||
'prompting if over the threshold')
|
||||
# among other things, FD_SETSIZE limits. Besides, spawning too many
|
||||
# processes can be unkind for the unaware on memory pressure and such...
|
||||
argparser.disable_interspersed_args()
|
||||
@@ -63,7 +67,7 @@ def run():
|
||||
pipedesc = {}
|
||||
pendingexecs = deque()
|
||||
exitcode = 0
|
||||
|
||||
c.stop_if_noderange_over(args[0], options.maxnodes)
|
||||
for exp in c.create('/noderange/{0}/attributes/expression'.format(args[0]),
|
||||
{'expression': cmdstr}):
|
||||
if 'error' in exp:
|
||||
@@ -71,7 +75,9 @@ def run():
|
||||
exitcode |= exp.get('errorcode', 1)
|
||||
ex = exp.get('databynode', ())
|
||||
for node in ex:
|
||||
cmd = ex[node]['value'].encode('utf-8')
|
||||
cmd = ex[node]['value']
|
||||
if not isinstance(cmd, bytes) and not isinstance(cmd, str):
|
||||
cmd = cmd.encode('utf-8')
|
||||
cmdv = shlex.split(cmd)
|
||||
if currprocs < concurrentprocs:
|
||||
currprocs += 1
|
||||
@@ -95,6 +101,7 @@ def run():
|
||||
pernodeout[node] = []
|
||||
pernodeout[node].append(data)
|
||||
else:
|
||||
data = client.stringify(data)
|
||||
if options.nonodeprefix:
|
||||
sys.stderr.write(data)
|
||||
else:
|
||||
@@ -112,6 +119,7 @@ def run():
|
||||
run_cmdv(node, cmdv, all, pipedesc)
|
||||
for node in sortutil.natural_sort(pernodeout):
|
||||
for line in pernodeout[node]:
|
||||
line = client.stringify(line)
|
||||
if options.nonodeprefix:
|
||||
sys.stdout.write(line)
|
||||
else:
|
||||
|
||||
@@ -149,8 +149,11 @@ def sensorpass(showout=True, appendtime=False):
|
||||
if appendtime:
|
||||
showval += ' @' + time.strftime(
|
||||
'%Y-%m-%dT%H:%M:%S')
|
||||
print(u'{0}: {1}:{2}'.format(
|
||||
node, sensedata['name'], showval).encode('utf8'))
|
||||
printval = u'{0}: {1}:{2}'.format(
|
||||
node, sensedata['name'], showval)
|
||||
if not isinstance(printval, str):
|
||||
printval = printval.encode('utf-8')
|
||||
print(printval)
|
||||
sys.stdout.flush()
|
||||
return resultdata
|
||||
|
||||
@@ -199,7 +202,11 @@ def main():
|
||||
orderedsensors.append(name)
|
||||
orderedsensors.sort()
|
||||
for name in orderedsensors:
|
||||
headernames.append(sensorheaders[name].encode('utf-8'))
|
||||
headername = sensorheaders[name]
|
||||
if (not isinstance(headername, str) and
|
||||
not isinstance(headername, bytes)):
|
||||
headername = headername.encode('utf-8')
|
||||
headernames.append(headername)
|
||||
if options.csv:
|
||||
linebyline = False
|
||||
csvwriter = csv.writer(sys.stdout)
|
||||
|
||||
@@ -43,7 +43,10 @@ argparser.add_option('-p', '--persist', dest='persist', action='store_true',
|
||||
argparser.add_option('-u', '--uefi', dest='uefi', action='store_true',
|
||||
default=True,
|
||||
help='Request UEFI style boot (rather than BIOS)')
|
||||
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to modify next boot device, '
|
||||
'prompting if over the threshold')
|
||||
(options, args) = argparser.parse_args()
|
||||
|
||||
try:
|
||||
@@ -63,6 +66,7 @@ if options.biosmode:
|
||||
bootmode = 'bios'
|
||||
else:
|
||||
bootmode = 'uefi'
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
sys.exit(session.simple_noderange_command(noderange, '/boot/nextdevice', bootdev,
|
||||
bootmode=bootmode,
|
||||
persistent=options.persist))
|
||||
|
||||
@@ -46,6 +46,10 @@ def run():
|
||||
help='Number of commands to run at a time')
|
||||
argparser.add_option('-n', '--nonodeprefix', action='store_true',
|
||||
help='Do not prefix output with node names')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to run remote ssh command to, '
|
||||
'prompting if over the threshold')
|
||||
# among other things, FD_SETSIZE limits. Besides, spawning too many
|
||||
# processes can be unkind for the unaware on memory pressure and such...
|
||||
argparser.disable_interspersed_args()
|
||||
@@ -55,7 +59,7 @@ def run():
|
||||
sys.exit(1)
|
||||
client.check_globbing(args[0])
|
||||
concurrentprocs = options.count
|
||||
c = client.Command()
|
||||
c = client.Command()
|
||||
cmdstr = " ".join(args[1:])
|
||||
|
||||
currprocs = 0
|
||||
@@ -64,7 +68,7 @@ def run():
|
||||
pendingexecs = deque()
|
||||
exitcode = 0
|
||||
|
||||
|
||||
c.stop_if_noderange_over(args[0], options.maxnodes)
|
||||
for exp in c.create('/noderange/{0}/attributes/expression'.format(args[0]),
|
||||
{'expression': cmdstr}):
|
||||
if 'error' in exp:
|
||||
@@ -72,7 +76,9 @@ def run():
|
||||
exitcode |= exp.get('errorcode', 1)
|
||||
ex = exp.get('databynode', ())
|
||||
for node in ex:
|
||||
cmd = ex[node]['value'].encode('utf-8')
|
||||
cmd = ex[node]['value']
|
||||
if not isinstance(cmd, str) and not isinstance(cmd, bytes):
|
||||
cmd = cmd.encode('utf-8')
|
||||
cmdv = ['ssh', node, cmd]
|
||||
if currprocs < concurrentprocs:
|
||||
currprocs += 1
|
||||
@@ -96,6 +102,7 @@ def run():
|
||||
pernodeout[node] = []
|
||||
pernodeout[node].append(data)
|
||||
else:
|
||||
data = client.stringify(data)
|
||||
if options.nonodeprefix:
|
||||
sys.stderr.write(data)
|
||||
else:
|
||||
@@ -113,6 +120,7 @@ def run():
|
||||
run_cmdv(node, cmdv, all, pipedesc)
|
||||
for node in sortutil.natural_sort(pernodeout):
|
||||
for line in pernodeout[node]:
|
||||
line = client.stringify(line)
|
||||
if options.nonodeprefix:
|
||||
sys.stdout.write(line)
|
||||
else:
|
||||
|
||||
@@ -63,10 +63,16 @@ def _print_cfg(scfg):
|
||||
sys.stderr.write(e['error'] + '\n')
|
||||
exitcode = e.get('errorcode', 1)
|
||||
for node in e.get('databynode', {}):
|
||||
curr = e['databynode'][node]
|
||||
if 'error' in curr:
|
||||
if 'no available drives' in curr['error']:
|
||||
curr['error'] += ' (drives must be in unconfigured state to be available, they must not be in jbod or online state)'
|
||||
sys.stderr.write('{0}: {1}\n'.format(node, curr['error']))
|
||||
exitcode = curr.get('errorcode', 1)
|
||||
continue
|
||||
if node not in storagebynode:
|
||||
storagebynode[node] = {'disks': [], 'arrays': [],
|
||||
'volumes': []}
|
||||
curr = e['databynode'][node]
|
||||
storagebynode[node][curr['type'] + 's'].append(curr)
|
||||
for node in storagebynode:
|
||||
for disk in sorted(storagebynode[node]['disks'],
|
||||
@@ -108,6 +114,7 @@ def createstorage(noderange, options, args):
|
||||
sys.stderr.write('-r and -d are required arguments to create array\n')
|
||||
sys.exit(1)
|
||||
session = client.Command()
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
names = options.name
|
||||
if names is None:
|
||||
names = ''.join(args)
|
||||
@@ -132,6 +139,7 @@ def deletestorage(noderange, options, args):
|
||||
else:
|
||||
names = options.name
|
||||
session = client.Command()
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
for rsp in session.delete(
|
||||
'/noderange/{0}/configuration/storage/volumes/{1}'.format(
|
||||
noderange, names)):
|
||||
@@ -162,6 +170,7 @@ def setdisk(noderange, options, args):
|
||||
sys.stderr.write('diskset requires valid state as argument (hotspare, jbod, unconfigured)\n')
|
||||
sys.exit(1)
|
||||
session = client.Command()
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
scfg = session.update('/noderange/{0}/configuration/storage/disks/{1}'.format(noderange, names), {'state': args[0]})
|
||||
_print_cfg(scfg)
|
||||
|
||||
@@ -202,6 +211,10 @@ def main():
|
||||
help='Comma separated list of stripsizes to use when creating volumes. '
|
||||
'This value is in kilobytes. The default behavior is to allow the '
|
||||
'storage controller to decide.')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to configure storage on, '
|
||||
'prompting if over the threshold')
|
||||
(options, args) = argparser.parse_args()
|
||||
if len(args) == 1:
|
||||
args.append('show')
|
||||
|
||||
@@ -64,7 +64,7 @@ def printerror(res, node=None):
|
||||
|
||||
|
||||
|
||||
def download_servicedata(noderange, media):
|
||||
def download_servicedata(noderange, media, options):
|
||||
global exitcode
|
||||
session = client.Command()
|
||||
output = sq.ScreenPrinter(noderange, session)
|
||||
@@ -73,6 +73,7 @@ def download_servicedata(noderange, media):
|
||||
upargs = {'filename': filename}
|
||||
noderrs = {}
|
||||
nodeurls = {}
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
for res in session.create(resource, upargs):
|
||||
if 'created' not in res:
|
||||
for nodename in res.get('databynode', ()):
|
||||
@@ -121,6 +122,10 @@ def main():
|
||||
'management server (the confluent server if running remote, '
|
||||
'and the collective.manager if in collective)\n'
|
||||
'\n\nSee `man %prog` for more info.\n')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to download diagnostic data from, '
|
||||
'prompting if over the threshold')
|
||||
(options, args) = argparser.parse_args()
|
||||
media = None
|
||||
try:
|
||||
@@ -142,6 +147,6 @@ def main():
|
||||
except KeyError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
handler(noderange, media)
|
||||
handler(noderange, media, options)
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@@ -39,6 +39,19 @@ _attraliases = {
|
||||
'bmcpass': 'secret.hardwaremanagementpassword',
|
||||
}
|
||||
|
||||
try:
|
||||
input = raw_input
|
||||
except NameError:
|
||||
pass
|
||||
|
||||
def stringify(instr):
|
||||
# Normalize unicode and bytes to 'str', correcting for
|
||||
# current python version
|
||||
if isinstance(instr, bytes) and not isinstance(instr, str):
|
||||
return instr.decode('utf-8')
|
||||
elif not isinstance(instr, bytes) and not isinstance(instr, str):
|
||||
return instr.encode('utf-8')
|
||||
return instr
|
||||
|
||||
class Tabulator(object):
|
||||
def __init__(self, headers):
|
||||
@@ -210,7 +223,7 @@ class Command(object):
|
||||
return rc
|
||||
|
||||
def simple_noderange_command(self, noderange, resource, input=None,
|
||||
key=None, errnodes=None, **kwargs):
|
||||
key=None, errnodes=None, promptover=None, **kwargs):
|
||||
try:
|
||||
self._currnoderange = noderange
|
||||
rc = 0
|
||||
@@ -226,6 +239,7 @@ class Command(object):
|
||||
noderange, resource)):
|
||||
rc = self.handle_results(ikey, rc, res, errnodes)
|
||||
else:
|
||||
self.stop_if_noderange_over(noderange, promptover)
|
||||
kwargs[ikey] = input
|
||||
for res in self.update('/noderange/{0}/{1}'.format(
|
||||
noderange, resource), kwargs):
|
||||
@@ -235,6 +249,33 @@ class Command(object):
|
||||
except KeyboardInterrupt:
|
||||
cprint('')
|
||||
return 0
|
||||
|
||||
def stop_if_noderange_over(self, noderange, maxnodes):
|
||||
if maxnodes is None:
|
||||
return
|
||||
nsize = self.get_noderange_size(noderange)
|
||||
if nsize > maxnodes:
|
||||
if nsize == 1:
|
||||
nodename = list(self.read(
|
||||
'/noderange/{0}/nodes/'.format(noderange)))[0].get('item', {}).get('href', None)
|
||||
nodename = nodename[:-1]
|
||||
p = input('Command is about to affect node {0}, continue (y/n)? '.format(nodename))
|
||||
else:
|
||||
p = input('Command is about to affect {0} nodes, continue (y/n)? '.format(nsize))
|
||||
if p.lower() != 'y':
|
||||
sys.stderr.write('Aborting at user request\n')
|
||||
sys.exit(1)
|
||||
raise Exception("Aborting at user request")
|
||||
|
||||
|
||||
def get_noderange_size(self, noderange):
|
||||
numnodes = 0
|
||||
for node in self.read('/noderange/{0}/nodes/'.format(noderange)):
|
||||
if node.get('item', {}).get('href', None):
|
||||
numnodes += 1
|
||||
else:
|
||||
raise Exception("Error trying to size noderange {0}".format(noderange))
|
||||
return numnodes
|
||||
|
||||
def simple_nodegroups_command(self, noderange, resource, input=None, key=None, **kwargs):
|
||||
try:
|
||||
@@ -335,12 +376,8 @@ class Command(object):
|
||||
if fingerprint == khf[hostid]:
|
||||
return
|
||||
else:
|
||||
try:
|
||||
replace = raw_input(
|
||||
"MISMATCHED CERTIFICATE DATA, ACCEPT NEW? (y/n):")
|
||||
except NameError:
|
||||
replace = input(
|
||||
"MISMATCHED CERTIFICATE DATA, ACCEPT NEW? (y/n):")
|
||||
replace = input(
|
||||
"MISMATCHED CERTIFICATE DATA, ACCEPT NEW? (y/n):")
|
||||
if replace not in ('y', 'Y'):
|
||||
raise Exception("BAD CERTIFICATE")
|
||||
cprint('Adding new key for %s:%s' % (server, port))
|
||||
@@ -392,6 +429,10 @@ def printattributes(session, requestargs, showtype, nodetype, noderange, options
|
||||
path = '/{0}/{1}/attributes/{2}'.format(nodetype, noderange, showtype)
|
||||
return print_attrib_path(path, session, requestargs, options)
|
||||
|
||||
def _sort_attrib(k):
|
||||
if isinstance(k[1], dict) and k[1].get('sortid', None) is not None:
|
||||
return k[1]['sortid']
|
||||
return k[0]
|
||||
|
||||
def print_attrib_path(path, session, requestargs, options, rename=None, attrprefix=None):
|
||||
exitcode = 0
|
||||
@@ -402,9 +443,7 @@ def print_attrib_path(path, session, requestargs, options, rename=None, attrpref
|
||||
exitcode = 1
|
||||
continue
|
||||
for node in sorted(res['databynode']):
|
||||
for attr, val in sorted(
|
||||
res['databynode'][node].items(),
|
||||
key=lambda k: k[1].get('sortid', k[0]) if isinstance(k[1], dict) else k[0]):
|
||||
for attr, val in sorted(res['databynode'][node].items(), key=_sort_attrib):
|
||||
if attr == 'error':
|
||||
sys.stderr.write('{0}: Error: {1}\n'.format(node, val))
|
||||
continue
|
||||
|
||||
@@ -46,9 +46,8 @@ alias nodelicense='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export
|
||||
_confluent_get_args()
|
||||
{
|
||||
CMPARGS=($COMP_LINE)
|
||||
NUMARGS=${#CMPARGS[@]}
|
||||
if [ "${COMP_WORDS[-1]}" == '' ]; then
|
||||
NUMARGS=$((NUMARGS+1))
|
||||
NUMARGS=$((COMP_CWORD+1))
|
||||
if [ "${COMP_WORDS[COMP_CWORD]}" == '' ]; then
|
||||
CMPARGS+=("")
|
||||
fi
|
||||
GENNED=""
|
||||
@@ -75,7 +74,7 @@ function _confluent_generic_completion()
|
||||
{
|
||||
_confluent_get_args
|
||||
if [ $NUMARGS -ge 3 ] && [ ! -z "$GENNED" ]; then
|
||||
COMPREPLY=($(compgen -W "$GENNED" -- ${COMP_WORDS[-1]}))
|
||||
COMPREPLY=($(compgen -W "$GENNED" -- ${COMP_WORDS[COMP_CWORD]}))
|
||||
fi
|
||||
if [ $NUMARGS -lt 3 ]; then
|
||||
_confluent_nr_completion
|
||||
@@ -111,7 +110,7 @@ _confluent_nodemedia_completion()
|
||||
return
|
||||
fi
|
||||
if [ $NUMARGS -ge 3 ] && [ ! -z "$GENNED" ]; then
|
||||
COMPREPLY=($(compgen -W "$GENNED" -- ${COMP_WORDS[-1]}))
|
||||
COMPREPLY=($(compgen -W "$GENNED" -- ${COMP_WORDS[COMP_CWORD]}))
|
||||
return;
|
||||
fi
|
||||
if [ $NUMARGS -lt 3 ]; then
|
||||
@@ -124,7 +123,7 @@ _confluent_nodefirmware_completion()
|
||||
{
|
||||
_confluent_get_args
|
||||
if [ $NUMARGS == 3 ]; then
|
||||
COMPREPLY=($(compgen -W "list update" -- ${COMP_WORDS[-1]}))
|
||||
COMPREPLY=($(compgen -W "list update" -- ${COMP_WORDS[COMP_CWORD]}))
|
||||
return;
|
||||
fi
|
||||
if [ $NUMARGS -gt 3 ] && [ ${CMPARGS[2]} == 'update' ]; then
|
||||
@@ -142,7 +141,7 @@ _confluent_nodeshell_completion()
|
||||
{
|
||||
_confluent_get_args
|
||||
if [ $NUMARGS == 3 ]; then
|
||||
COMPREPLY=($(compgen -c -- ${COMP_WORDS[-1]}))
|
||||
COMPREPLY=($(compgen -c -- ${COMP_WORDS[COMP_CWORD]}))
|
||||
return
|
||||
fi
|
||||
if [ $NUMARGS -gt 3 ]; then
|
||||
@@ -160,7 +159,7 @@ _confluent_nodelicense_completion()
|
||||
{
|
||||
_confluent_get_args
|
||||
if [ $NUMARGS == 3 ]; then
|
||||
COMPREPLY=($(compgen -W "install list save delete" -- ${COMP_WORDS[-1]}))
|
||||
COMPREPLY=($(compgen -W "install list save delete" -- ${COMP_WORDS[COMP_CWORD]}))
|
||||
return;
|
||||
fi
|
||||
if [ $NUMARGS == 4 ] && [ ${CMPARGS[2]} == 'install' ]; then
|
||||
@@ -183,7 +182,7 @@ _confluent_nodesupport_completion()
|
||||
{
|
||||
_confluent_get_args
|
||||
if [ $NUMARGS == 3 ]; then
|
||||
COMPREPLY=($(compgen -W "servicedata" -- ${COMP_WORDS[-1]}))
|
||||
COMPREPLY=($(compgen -W "servicedata" -- ${COMP_WORDS[COMP_CWORD]}))
|
||||
return;
|
||||
fi
|
||||
if [ $NUMARGS == 4 ] && [ ${CMPARGS[2]} == 'servicedata' ]; then
|
||||
@@ -210,41 +209,36 @@ _confluent_nn_completion()
|
||||
if [ $NUMARGS -gt 2 ]; then
|
||||
return;
|
||||
fi
|
||||
INPUT=${COMP_WORDS[-1]}
|
||||
INPUT=${COMP_WORDS[COMP_CWORD]}
|
||||
INPUT=${INPUT##*,-}
|
||||
INPUT=${INPUT##*,}
|
||||
INPUT=${INPUT##*@}
|
||||
PREFIX=""
|
||||
if [ "$INPUT" != "${COMP_WORDS[-1]}" ]; then
|
||||
PREFIX=${COMP_WORDS[-1]}
|
||||
if [ "$INPUT" != "${COMP_WORDS[COMP_CWORD]}" ]; then
|
||||
PREFIX=${COMP_WORDS[COMP_CWORD]}
|
||||
PREFIX=$(echo $PREFIX | sed -e 's/,[^,@-]*$/,/' -e 's/,-[^,@]*$/,-/' -e 's/@[^,@]*/@/')
|
||||
fi
|
||||
|
||||
COMPREPLY=($(compgen -W "$(nodelist | sed -e s/^/$PREFIX/)" -- "${COMP_WORDS[-1]}"))
|
||||
COMPREPLY=($(compgen -W "$(nodelist | sed -e s/^/$PREFIX/)" -- "${COMP_WORDS[COMP_CWORD]}"))
|
||||
}
|
||||
_confluent_nr_completion()
|
||||
{
|
||||
CMPARGS=($COMP_LINE)
|
||||
NUMARGS=${#CMPARGS[@]}
|
||||
if [ "${COMP_WORDS[-1]}" == '' ]; then
|
||||
NUMARGS=$((NUMARGS+1))
|
||||
fi
|
||||
_confluent_get_args
|
||||
if [ $NUMARGS -gt 2 ]; then
|
||||
return;
|
||||
fi
|
||||
INPUT=${COMP_WORDS[-1]}
|
||||
INPUT=${COMP_WORDS[COMP_CWORD]}
|
||||
INPUT=${INPUT##*,-}
|
||||
INPUT=${INPUT##*,}
|
||||
INPUT=${INPUT##*@}
|
||||
PREFIX=""
|
||||
if [ "$INPUT" != "${COMP_WORDS[-1]}" ]; then
|
||||
PREFIX=${COMP_WORDS[-1]}
|
||||
if [ "$INPUT" != "${COMP_WORDS[COMP_CWORD]}" ]; then
|
||||
PREFIX=${COMP_WORDS[COMP_CWORD]}
|
||||
PREFIX=$(echo $PREFIX | sed -e 's/,[^,@-]*$/,/' -e 's/,-[^,@]*$/,-/' -e 's/@[^,@]*/@/')
|
||||
fi
|
||||
|
||||
#COMPREPLY=($(compgen -W "$(confetty show /nodegroups|sed -e 's/\///' -e s/^/$PREFIX/;nodelist | sed -e s/^/$PREFIX/)" -- "${COMP_WORDS[-1]}"))
|
||||
COMPREPLY=($(compgen -W "$(confetty show /nodegroups|sed -e 's/\///' -e s/^/$PREFIX/;nodelist | sed -e s/^/$PREFIX/)" -- "${COMP_WORDS[-1]}"))
|
||||
COMPREPLY=($(compgen -W "$(confetty show /nodegroups|sed -e 's/\///' -e s/^/$PREFIX/;nodelist | sed -e s/^/$PREFIX/)" -- "${COMP_WORDS[COMP_CWORD]}"))
|
||||
}
|
||||
_confluent_ng_completion()
|
||||
{
|
||||
@@ -252,17 +246,17 @@ _confluent_ng_completion()
|
||||
if [ $NUMARGS -gt 2 ]; then
|
||||
return;
|
||||
fi
|
||||
INPUT=${COMP_WORDS[-1]}
|
||||
INPUT=${COMP_WORDS[COMP_CWORD]}
|
||||
INPUT=${INPUT##*,-}
|
||||
INPUT=${INPUT##*,}
|
||||
INPUT=${INPUT##*@}
|
||||
PREFIX=""
|
||||
if [ "$INPUT" != "${COMP_WORDS[-1]}" ]; then
|
||||
PREFIX=${COMP_WORDS[-1]}
|
||||
if [ "$INPUT" != "${COMP_WORDS[COMP_CWORD]}" ]; then
|
||||
PREFIX=${COMP_WORDS[COMP_CWORD]}
|
||||
PREFIX=$(echo $PREFIX | sed -e 's/,[^,@-]*$/,/' -e 's/,-[^,@]*$/,-/' -e 's/@[^,@]*/@/')
|
||||
fi
|
||||
|
||||
COMPREPLY=($(compgen -W "$(confetty show /nodegroups|sed -e 's/\///' -e s/^/$PREFIX/)" -- "${COMP_WORDS[-1]}"))
|
||||
COMPREPLY=($(compgen -W "$(confetty show /nodegroups|sed -e 's/\///' -e s/^/$PREFIX/)" -- "${COMP_WORDS[COMP_CWORD]}"))
|
||||
}
|
||||
complete -F _confluent_nodeattrib_completion nodeattrib
|
||||
complete -F _confluent_nodeattrib_completion nodegroupattrib
|
||||
|
||||
+2
@@ -100,3 +100,5 @@ See nodegroupattrib(8) command on how to manage attributes on a group level.
|
||||
## SEE ALSO
|
||||
|
||||
nodegroupattrib(8), nodeattribexpressions(5)
|
||||
|
||||
## ATTRIBUTES
|
||||
@@ -22,6 +22,11 @@ given as a node expression, as documented in the man page for nodeattribexpressi
|
||||
If combined with `-x`, will show all differing values except those indicated
|
||||
by `-x`
|
||||
|
||||
* `-e`, `--extra`:
|
||||
Read settings that are generally not needed, but may be slow to retrieve.
|
||||
Notably this includes the IMM category of Lenovo systems. The most popular
|
||||
IMM settings are available through faster 'bmc' attributes.
|
||||
|
||||
* `-x`, `--exclude`:
|
||||
Rather than listing only the specified configuration parameters, list all
|
||||
attributes except for the specified ones
|
||||
|
||||
@@ -13,7 +13,11 @@ nodefirmware(8) -- Report firmware information on confluent nodes
|
||||
will retrieve all firmware, but can be directed to fetch specific firmware by
|
||||
calling out the name of the firmware (e.g. uefi or xcc) or request reading only
|
||||
core firmware firmware by using the word 'core', which is generally a quicker
|
||||
operation.
|
||||
operation. Different hardwaremanagement.method indicated plugins may have
|
||||
different capabilities available. For example, the 'core' distinction may
|
||||
not be relevant to redfish. Additionally, the Lenovo XCC makes certain
|
||||
information available over IPMI that is not otherwise available (for example
|
||||
the FPGA version where applicable).
|
||||
|
||||
In the update form, it accepts a single file and attempts to update it using
|
||||
the out of band facilities. Firmware updates can end in one of three states:
|
||||
|
||||
+2
@@ -41,3 +41,5 @@ the attributes are set on the node versus a group to which a node belongs.
|
||||
## SEE ALSO
|
||||
|
||||
nodeattrib(8), nodeattribexpressions(5)
|
||||
|
||||
## ATTRIBUTES
|
||||
@@ -42,7 +42,7 @@ The attribute name may use a wildcard:
|
||||
Commas can be used to indicate multiple nodes, and can mix and match any of the above primitives. The following can be a valid single noderange, combining any and all members of each comma separated component
|
||||
`n1,n2,rack1,storage,location.rack=9,~s1..,n20-n30`
|
||||
|
||||
Exclusions can be done by prepending a ‘-‘ before a portion of a noderange:
|
||||
Exclusions can be done by prepending a '-' before a portion of a noderange:
|
||||
`rack1,-n2`
|
||||
`compute,-rack1`
|
||||
`compute,-location.row=12`
|
||||
@@ -54,7 +54,7 @@ To indicate nodes that match multiple selections at once (set intersection), the
|
||||
For complex expressions, () may be used to indicate order of expanding the noderange to be explicit
|
||||
`rack1,-(console.logging=full@compute)`
|
||||
|
||||
Noderange syntax can also indicate ‘pagination’, or separating the nodes into well defined chunks. > is used to indicate how many nodes to display at a time, and < is used to indicate how many nodes to skip into a noderange:
|
||||
Noderange syntax can also indicate 'pagination', or separating the nodes into well defined chunks. > is used to indicate how many nodes to display at a time, and < is used to indicate how many nodes to skip into a noderange:
|
||||
`rack1>3<6`
|
||||
|
||||
The above would show the seventh through ninth nodes of the rack1 group. Like all other noderange operations, this may be combined with any of the above, but must appear as the very last operation. Ordering is done with a natural sort.
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
nodersync(8) -- Run rsync in parallel against a noderange
|
||||
=========================================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodersync <file/directorylist> <noderange>:<destination>`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
Supervises execution of rsync to push files or a directory tree to the specified
|
||||
noderange. This will present progress as percentage for all nodes.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-m`:
|
||||
Specify maximum number of nodes for noderange max.
|
||||
|
||||
* `-c`:
|
||||
Specify how many rsync executions to do concurrently. If noderange
|
||||
exceeds the count, then excess nodes will wait until one of the
|
||||
active count completes.
|
||||
@@ -7,7 +7,7 @@ nodesupport(8) -- Utilities for interacting with vendor support
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
`nodesupport` provides capabilities associated with interactiong with support.
|
||||
`nodesupport` provides capabilities associated with interacting with support.
|
||||
Currently it only has the `servicedata` subcommand. `servicedata` takes
|
||||
an argument that is either a directory name (that can be used for a single node
|
||||
or multiple nodes) or a file name (only to be used with single node noderange).
|
||||
@@ -16,6 +16,9 @@ connects to the managed system, so it will download to the remote system if runn
|
||||
remotely and will download to the collective.manager indicated system if
|
||||
running in collective mode.
|
||||
|
||||
Note that due to vendor filename requirements, any filename may have vendor
|
||||
specific suffixes added to any file produced.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
* Download support data from a single node to a specific filename
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#!/bin/sh
|
||||
cd `dirname $0`
|
||||
python3 addattribs.py || python2 addattribs.py
|
||||
cd `dirname $0`/doc/man
|
||||
mkdir -p ../../man/man1
|
||||
mkdir -p ../../man/man5
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import argparse
|
||||
import errno
|
||||
import os
|
||||
import pwd
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
@@ -22,7 +23,7 @@ except NameError:
|
||||
pass
|
||||
|
||||
def make_certificate():
|
||||
umask = os.umask(0077)
|
||||
umask = os.umask(0o77)
|
||||
try:
|
||||
os.makedirs('/etc/confluent/cfg')
|
||||
except OSError as e:
|
||||
@@ -40,6 +41,12 @@ def make_certificate():
|
||||
'/etc/confluent/srvcert.pem -subj /CN='
|
||||
'{0}'.format(socket.gethostname()).split(' ')):
|
||||
raise Exception('Error generating certificate')
|
||||
try:
|
||||
uid = pwd.getpwnam('confluent').pw_uid
|
||||
os.chown('/etc/confluent/privkey.pem', uid, -1)
|
||||
os.chown('/etc/confluent/srvcert.pem', uid, -1)
|
||||
except KeyError:
|
||||
pass
|
||||
print('Certificate generated successfully')
|
||||
os.umask(umask)
|
||||
|
||||
@@ -61,7 +68,7 @@ def join_collective(server, invitation):
|
||||
make_certificate()
|
||||
s = client.Command().connection
|
||||
while not invitation:
|
||||
invitation = raw_input('Paste the invitation here: ')
|
||||
invitation = input('Paste the invitation here: ')
|
||||
tlvdata.send(s, {'collective': {'operation': 'join',
|
||||
'invitation': invitation,
|
||||
'server': server}})
|
||||
@@ -69,12 +76,28 @@ def join_collective(server, invitation):
|
||||
res = res.get('collective',
|
||||
{'status': 'Unknown response: ' + repr(res)})
|
||||
print(res.get('status', res.get('error', repr(res))))
|
||||
if 'error' in res:
|
||||
sys.exit(1)
|
||||
|
||||
def delete_member(name):
|
||||
s = client.Command().connection
|
||||
tlvdata.send(s, {'collective': {'operation': 'delete',
|
||||
'member': name}})
|
||||
res = tlvdata.recv(s)
|
||||
res = res.get('collective',
|
||||
{'status': 'Unknown response: ' + repr(res)})
|
||||
print(res.get('status', res.get('error', repr(res))))
|
||||
if 'error' in res:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def show_collective():
|
||||
s = client.Command().connection
|
||||
tlvdata.send(s, {'collective': {'operation': 'show'}})
|
||||
res = tlvdata.recv(s)
|
||||
if 'error' in res:
|
||||
print(res['error'])
|
||||
return
|
||||
if 'error' in res['collective']:
|
||||
print(res['collective']['error'])
|
||||
return
|
||||
@@ -104,6 +127,8 @@ def main():
|
||||
'collective member. Run collective invite -h for more information')
|
||||
ic.add_argument('name', help='Name of server to invite to join the '
|
||||
'collective')
|
||||
dc = sp.add_parser('delete', help='Delete a member of a collective')
|
||||
dc.add_argument('name', help='Name of server to delete from collective')
|
||||
jc = sp.add_parser('join', help='Join a collective. Run collective join -h for more information')
|
||||
jc.add_argument('server', help='Existing collective member that ran invite and generated a token')
|
||||
jc.add_argument('-i', help='Invitation provided by runniing invite on an '
|
||||
@@ -117,6 +142,8 @@ def main():
|
||||
join_collective(cmdset.server, cmdset.i)
|
||||
elif cmdset.command == 'show':
|
||||
show_collective()
|
||||
elif cmdset.command == 'delete':
|
||||
delete_member(cmdset.name)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@@ -32,7 +32,7 @@ import confluent.main
|
||||
import multiprocessing
|
||||
if __name__ == '__main__':
|
||||
multiprocessing.freeze_support()
|
||||
confluent.main.run()
|
||||
confluent.main.run(sys.argv)
|
||||
#except:
|
||||
# pass
|
||||
#p.disable()
|
||||
|
||||
@@ -7,7 +7,7 @@ import tempfile
|
||||
def get_openssl_conf_location():
|
||||
if exists('/etc/pki/tls/openssl.cnf'):
|
||||
return '/etc/pki/tls/openssl.cnf'
|
||||
elif exists('/etc/ssl/openssl.cnf');
|
||||
elif exists('/etc/ssl/openssl.cnf'):
|
||||
return '/etc/ssl/openssl.cnf'
|
||||
else:
|
||||
raise Exception("Cannot find openssl config file")
|
||||
|
||||
@@ -3,8 +3,12 @@ cd `dirname $0`
|
||||
PKGNAME=$(basename $(pwd))
|
||||
DPKGNAME=$(basename $(pwd) | sed -e s/_/-/)
|
||||
OPKGNAME=$(basename $(pwd) | sed -e s/_/-/)
|
||||
PYEXEC=python3
|
||||
DSCARGS="--with-python3=True --with-python2=False"
|
||||
if grep wheezy /etc/os-release; then
|
||||
DPKGNAME=python-$DPKGNAME
|
||||
PYEXEC=python
|
||||
DSCARGS=""
|
||||
fi
|
||||
cd ..
|
||||
mkdir -p /tmp/confluent # $DPKGNAME
|
||||
@@ -24,15 +28,15 @@ install-scripts=/opt/confluent/bin
|
||||
package=$DPKGNAME
|
||||
EOF
|
||||
|
||||
python setup.py sdist > /dev/null 2>&1
|
||||
py2dsc dist/*.tar.gz
|
||||
$PYEXEC setup.py sdist > /dev/null 2>&1
|
||||
py2dsc $DSCARGS dist/*.tar.gz
|
||||
shopt -s extglob
|
||||
cd deb_dist/!(*.orig)/
|
||||
if [ "$OPKGNAME" = "confluent-server" ]; then
|
||||
if grep wheezy /etc/os-release; then
|
||||
sed -i 's/^\(Depends:.*\)/\1, python-confluent-client, python-lxml, python-eficompressor, python-pycryptodomex, python-dateutil, python-pyopenssl/' debian/control
|
||||
sed -i 's/^\(Depends:.*\)/\1, python-confluent-client, python-lxml, python-eficompressor, python-pycryptodomex, python-dateutil, python-pyopenssl, python-msgpack/' debian/control
|
||||
else
|
||||
sed -i 's/^\(Depends:.*\)/\1, confluent-client, python-lxml, python-eficompressor, python-pycryptodome, python-dateutil/' debian/control
|
||||
sed -i 's/^\(Depends:.*\)/\1, confluent-client, python3-lxml, python3-eficompressor, python3-pycryptodome, python3-websocket, python3-msgpack/' debian/control
|
||||
fi
|
||||
if grep wheezy /etc/os-release; then
|
||||
echo 'confluent_client python-confluent-client' >> debian/pydist-overrides
|
||||
|
||||
@@ -6,7 +6,7 @@ fi
|
||||
./makesetup
|
||||
VERSION=`cat VERSION`
|
||||
PKGNAME=$(basename $(pwd))
|
||||
python setup.py sdist > /dev/null 2>&1
|
||||
python3 setup.py sdist > /dev/null 2>&1
|
||||
cp dist/*.tar.gz ~/rpmbuild/SOURCES
|
||||
sed -e 's/#VERSION#/'$VERSION/ $PKGNAME.spec.tmpl > ~/rpmbuild/SPECS/$PKGNAME.spec
|
||||
rpmbuild -ba ~/rpmbuild/SPECS/$PKGNAME.spec 2> /dev/null |grep ^Wrote:
|
||||
|
||||
@@ -64,12 +64,12 @@ class AsyncTermRelation(object):
|
||||
# Need to keep an association of term object to async
|
||||
# This allows the async handler to know the context of
|
||||
# outgoing data to provide to calling code
|
||||
def __init__(self, termid, async):
|
||||
self.async = async
|
||||
def __init__(self, termid, asynchdl):
|
||||
self.asynchdl = asynchdl
|
||||
self.termid = termid
|
||||
|
||||
def got_data(self, data):
|
||||
self.async.add(self.termid, data)
|
||||
self.asynchdl.add(self.termid, data)
|
||||
|
||||
|
||||
class AsyncSession(object):
|
||||
|
||||
@@ -27,7 +27,10 @@ from fnmatch import fnmatch
|
||||
import hashlib
|
||||
import hmac
|
||||
import multiprocessing
|
||||
import os
|
||||
import pwd
|
||||
import confluent.userutil as userutil
|
||||
import confluent.util as util
|
||||
pam = None
|
||||
try:
|
||||
import confluent.pam as pam
|
||||
@@ -57,9 +60,9 @@ _allowedbyrole = {
|
||||
'/node*/configuration/*',
|
||||
],
|
||||
'update': [
|
||||
'/discovery/*',
|
||||
'/discovery/*',
|
||||
'/networking/macs/rescan',
|
||||
'/node*/power/state',
|
||||
'/node*/power/state',
|
||||
'/node*/power/reseat',
|
||||
'/node*/attributes/*',
|
||||
'/node*/media/*tach',
|
||||
@@ -97,17 +100,6 @@ _deniedbyrole = {
|
||||
}
|
||||
|
||||
|
||||
def _prune_passcache():
|
||||
# This function makes sure we don't remember a passphrase in memory more
|
||||
# than 10 seconds
|
||||
while True:
|
||||
curtime = time.time()
|
||||
for passent in _passcache.iterkeys():
|
||||
if passent[2] < curtime - 90:
|
||||
del _passcache[passent]
|
||||
eventlet.sleep(90)
|
||||
|
||||
|
||||
def _get_usertenant(name, tenant=False):
|
||||
"""_get_usertenant
|
||||
|
||||
@@ -119,11 +111,13 @@ def _get_usertenant(name, tenant=False):
|
||||
administrator account a tenant gets.
|
||||
Otherwise, just assume a user in the default tenant
|
||||
"""
|
||||
if not isinstance(name, bytes):
|
||||
name = name.encode('utf-8')
|
||||
if not isinstance(tenant, bool):
|
||||
# if not boolean, it must be explicit tenant
|
||||
user = name
|
||||
elif '/' in name: # tenant scoped name
|
||||
tenant, user = name.split('/', 1)
|
||||
elif b'/' in name: # tenant scoped name
|
||||
tenant, user = name.split(b'/', 1)
|
||||
elif configmanager.is_tenant(name):
|
||||
# the account is the implicit tenant owner account
|
||||
user = name
|
||||
@@ -131,6 +125,9 @@ def _get_usertenant(name, tenant=False):
|
||||
else: # assume it is a non-tenant user account
|
||||
user = name
|
||||
tenant = None
|
||||
user = util.stringify(user)
|
||||
if tenant:
|
||||
tenant = util.stringify(tenant)
|
||||
yield user
|
||||
yield tenant
|
||||
|
||||
@@ -262,12 +259,36 @@ def check_user_passphrase(name, passphrase, operation=None, element=None, tenant
|
||||
_passcache[(user, tenant)] = hashlib.sha256(passphrase).digest()
|
||||
return authorize(user, element, tenant, operation)
|
||||
if pam:
|
||||
pammy = pam.pam()
|
||||
usergood = pammy.authenticate(user, passphrase)
|
||||
del pammy
|
||||
pwe = None
|
||||
try:
|
||||
pwe = pwd.getpwnam(user)
|
||||
except KeyError:
|
||||
#pam won't work if the user doesn't exist, don't go further
|
||||
eventlet.sleep(0.05) # stall even on test for existence of a username
|
||||
return None
|
||||
if os.getuid() != 0:
|
||||
# confluent is running with reduced privilege, however, pam_unix refuses
|
||||
# to let a non-0 user check anothers password.
|
||||
# We will fork and the child will assume elevated privilege to
|
||||
# get unix_chkpwd helper to enable checking /etc/shadow
|
||||
pid = os.fork()
|
||||
if not pid:
|
||||
usergood = False
|
||||
try:
|
||||
# we change to the uid we are trying to authenticate as, because
|
||||
# pam_unix uses unix_chkpwd which reque
|
||||
os.setuid(pwe.pw_uid)
|
||||
usergood = pam.authenticate(user, passphrase, service=_pamservice)
|
||||
finally:
|
||||
os._exit(0 if usergood else 1)
|
||||
usergood = os.waitpid(pid, 0)[1] == 0
|
||||
else:
|
||||
# We are running as root, we don't need to fork in order to authenticate the
|
||||
# user
|
||||
usergood = pam.authenticate(user, passphrase, service=_pamservice)
|
||||
if usergood:
|
||||
_passcache[(user, tenant)] = hashlib.sha256(passphrase).digest()
|
||||
return authorize(user, element, tenant, operation, skipuserobj=False)
|
||||
return authorize(user, element, tenant, operation, skipuserobj=False)
|
||||
eventlet.sleep(0.05) # stall even on test for existence of a username
|
||||
return None
|
||||
|
||||
@@ -288,4 +309,4 @@ def _do_pbkdf(passphrase, salt):
|
||||
# compute. However, we do want to wait for result, so we have
|
||||
# one of the exceedingly rare sort of circumstances where 'apply'
|
||||
# actually makes sense
|
||||
return authworkers.apply(_apply_pbkdf, [passphrase, salt])
|
||||
return authworkers.apply(_apply_pbkdf, [passphrase, salt])
|
||||
|
||||
@@ -27,6 +27,7 @@ import eventlet.green.ssl as ssl
|
||||
import eventlet.green.threading as threading
|
||||
import greenlet
|
||||
import random
|
||||
import sys
|
||||
try:
|
||||
import OpenSSL.crypto as crypto
|
||||
except ImportError:
|
||||
@@ -70,7 +71,10 @@ def connect_to_leader(cert=None, name=None, leader=None):
|
||||
return False
|
||||
with connecting:
|
||||
with cfm._initlock:
|
||||
tlvdata.recv(remote) # the banner
|
||||
banner = tlvdata.recv(remote) # the banner
|
||||
vers = banner.split()[2]
|
||||
if vers != b'v2':
|
||||
raise Exception('This instance only supports protocol 2, synchronize versions between collective members')
|
||||
tlvdata.recv(remote) # authpassed... 0..
|
||||
if name is None:
|
||||
name = get_myname()
|
||||
@@ -119,7 +123,7 @@ def connect_to_leader(cert=None, name=None, leader=None):
|
||||
globaldata = tlvdata.recv(remote)
|
||||
dbi = tlvdata.recv(remote)
|
||||
dbsize = dbi['dbsize']
|
||||
dbjson = ''
|
||||
dbjson = b''
|
||||
while (len(dbjson) < dbsize):
|
||||
ndata = remote.recv(dbsize - len(dbjson))
|
||||
if not ndata:
|
||||
@@ -148,11 +152,11 @@ def connect_to_leader(cert=None, name=None, leader=None):
|
||||
raise
|
||||
currentleader = leader
|
||||
#spawn this as a thread...
|
||||
follower = eventlet.spawn(follow_leader, remote)
|
||||
follower = eventlet.spawn(follow_leader, remote, leader)
|
||||
return True
|
||||
|
||||
|
||||
def follow_leader(remote):
|
||||
def follow_leader(remote, leader):
|
||||
global currentleader
|
||||
cleanexit = False
|
||||
try:
|
||||
@@ -164,8 +168,8 @@ def follow_leader(remote):
|
||||
log.log({'info': 'Previous following cleanly closed',
|
||||
'subsystem': 'collective'})
|
||||
return
|
||||
log.log({'info': 'Current leader has disappeared, restarting '
|
||||
'collective membership', 'subsystem': 'collective'})
|
||||
log.log({'info': 'Current leader ({0}) has disappeared, restarting '
|
||||
'collective membership'.format(leader), 'subsystem': 'collective'})
|
||||
# The leader has folded, time to startup again...
|
||||
cfm.stop_following()
|
||||
currentleader = None
|
||||
@@ -208,8 +212,7 @@ def handle_connection(connection, cert, request, local=False):
|
||||
else:
|
||||
if not local:
|
||||
return
|
||||
|
||||
if 'show' == operation:
|
||||
if operation in ('show', 'delete'):
|
||||
if not list(cfm.list_collective()):
|
||||
tlvdata.send(connection,
|
||||
{'collective': {'error': 'Collective mode not '
|
||||
@@ -246,7 +249,23 @@ def handle_connection(connection, cert, request, local=False):
|
||||
collinfo['quorum'] = True
|
||||
except exc.DegradedCollective:
|
||||
collinfo['quorum'] = False
|
||||
tlvdata.send(connection, {'collective': collinfo})
|
||||
if operation == 'show':
|
||||
tlvdata.send(connection, {'collective': collinfo})
|
||||
elif operation == 'delete':
|
||||
todelete = request['member']
|
||||
if (todelete == collinfo['leader'] or
|
||||
todelete in collinfo['active']):
|
||||
tlvdata.send(connection, {'collective':
|
||||
{'error': '{0} is still active, stop the confluent service to remove it'.format(todelete)}})
|
||||
return
|
||||
if todelete not in collinfo['offline']:
|
||||
tlvdata.send(connection, {'collective':
|
||||
{'error': '{0} is not a recognized collective member'.format(todelete)}})
|
||||
return
|
||||
cfm.del_collective_member(todelete)
|
||||
tlvdata.send(connection,
|
||||
{'collective': {'status': 'Successfully deleted {0}'.format(todelete)}})
|
||||
connection.close()
|
||||
return
|
||||
if 'invite' == operation:
|
||||
try:
|
||||
@@ -267,7 +286,8 @@ def handle_connection(connection, cert, request, local=False):
|
||||
invitation = request['invitation']
|
||||
try:
|
||||
invitation = base64.b64decode(invitation)
|
||||
name, invitation = invitation.split('@', 1)
|
||||
name, invitation = invitation.split(b'@', 1)
|
||||
name = util.stringify(name)
|
||||
except Exception:
|
||||
tlvdata.send(
|
||||
connection,
|
||||
@@ -375,7 +395,7 @@ def handle_connection(connection, cert, request, local=False):
|
||||
connection.close()
|
||||
return
|
||||
if (currentleader == connection.getpeername()[0] and
|
||||
follower and follower.isAlive()):
|
||||
follower and not follower.dead):
|
||||
# if we are happily following this leader already, don't stir
|
||||
# the pot
|
||||
tlvdata.send(connection, {'status': 0})
|
||||
|
||||
@@ -270,8 +270,9 @@ node = {
|
||||
},
|
||||
'console.method': {
|
||||
'description': ('Indicate the method used to access the console of '
|
||||
'the managed node.'),
|
||||
'validvalues': ('ssh', 'ipmi'),
|
||||
'the managed node. If not specified, then console '
|
||||
'is disabled'),
|
||||
'validvalues': ('ssh', 'ipmi', 'tsmsol'),
|
||||
},
|
||||
# 'virtualization.host': {
|
||||
# 'description': ('Hypervisor where this node does/should reside'),
|
||||
@@ -301,6 +302,7 @@ node = {
|
||||
'hardwaremanagement.method': {
|
||||
'description': 'The method used to perform operations such as power '
|
||||
'control, get sensor data, get inventory, and so on. '
|
||||
'ipmi is used if not specified.'
|
||||
},
|
||||
'enclosure.bay': {
|
||||
'description': 'The bay in the enclosure, if any',
|
||||
|
||||
@@ -71,6 +71,7 @@ import eventlet.green.select as select
|
||||
import eventlet.green.threading as gthread
|
||||
import fnmatch
|
||||
import json
|
||||
import msgpack
|
||||
import operator
|
||||
import os
|
||||
import random
|
||||
@@ -309,12 +310,12 @@ def check_quorum():
|
||||
def exec_on_leader(function, *args):
|
||||
if isinstance(cfgleader, bool):
|
||||
raise exc.DegradedCollective()
|
||||
xid = os.urandom(8)
|
||||
xid = confluent.util.stringify(base64.b64encode(os.urandom(8)))
|
||||
while xid in _pendingchangesets:
|
||||
xid = os.urandom(8)
|
||||
xid = confluent.util.stringify(base64.b64encode(os.urandom(8)))
|
||||
_pendingchangesets[xid] = event.Event()
|
||||
rpcpayload = cPickle.dumps({'function': function, 'args': args,
|
||||
'xid': xid})
|
||||
rpcpayload = msgpack.packb({'function': function, 'args': args,
|
||||
'xid': xid}, use_bin_type=False)
|
||||
rpclen = len(rpcpayload)
|
||||
cfgleader.sendall(struct.pack('!Q', rpclen))
|
||||
cfgleader.sendall(rpcpayload)
|
||||
@@ -324,15 +325,24 @@ def exec_on_leader(function, *args):
|
||||
|
||||
|
||||
def exec_on_followers(fnname, *args):
|
||||
global _txcount
|
||||
pushes = eventlet.GreenPool()
|
||||
# Check health of collective prior to attempting
|
||||
for _ in pushes.starmap(
|
||||
_push_rpc, [(cfgstreams[s], b'') for s in cfgstreams]):
|
||||
pass
|
||||
if len(cfgstreams) < (len(_cfgstore['collective']) // 2):
|
||||
# the leader counts in addition to registered streams
|
||||
raise exc.DegradedCollective()
|
||||
exec_on_followers_unconditional(fnname, *args)
|
||||
|
||||
|
||||
def exec_on_followers_unconditional(fnname, *args):
|
||||
global _txcount
|
||||
pushes = eventlet.GreenPool()
|
||||
_txcount += 1
|
||||
payload = cPickle.dumps({'function': fnname, 'args': args,
|
||||
'txcount': _txcount})
|
||||
for res in pushes.starmap(
|
||||
payload = msgpack.packb({'function': fnname, 'args': args,
|
||||
'txcount': _txcount}, use_bin_type=False)
|
||||
for _ in pushes.starmap(
|
||||
_push_rpc, [(cfgstreams[s], payload) for s in cfgstreams]):
|
||||
pass
|
||||
|
||||
@@ -378,9 +388,15 @@ def init_masterkey(password=None, autogen=True):
|
||||
|
||||
def _push_rpc(stream, payload):
|
||||
with _rpclock:
|
||||
stream.sendall(struct.pack('!Q', len(payload)))
|
||||
if len(payload):
|
||||
stream.sendall(payload)
|
||||
try:
|
||||
stream.sendall(struct.pack('!Q', len(payload)))
|
||||
if len(payload):
|
||||
stream.sendall(payload)
|
||||
return True
|
||||
except Exception:
|
||||
logException()
|
||||
del cfgstreams[stream]
|
||||
stream.close()
|
||||
|
||||
|
||||
def decrypt_value(cryptvalue,
|
||||
@@ -401,12 +417,12 @@ def decrypt_value(cryptvalue,
|
||||
raise Exception("bad HMAC value on crypted value")
|
||||
decrypter = AES.new(key, AES.MODE_CBC, iv)
|
||||
value = decrypter.decrypt(cipherdata)
|
||||
padsize = ord(value[-1])
|
||||
padsize = bytearray(value)[-1]
|
||||
pad = value[-padsize:]
|
||||
# Note that I cannot grasp what could be done with a subliminal
|
||||
# channel in padding in this case, but check the padding anyway
|
||||
for padbyte in pad:
|
||||
if ord(padbyte) != padsize:
|
||||
for padbyte in bytearray(pad):
|
||||
if padbyte != padsize:
|
||||
raise Exception("bad padding in encrypted value")
|
||||
return value[0:-padsize]
|
||||
else:
|
||||
@@ -474,7 +490,7 @@ def crypt_value(value,
|
||||
crypter = AES.new(key, AES.MODE_GCM, nonce=iv)
|
||||
value = confluent.util.stringify(value).encode('utf-8')
|
||||
cryptval, hmac = crypter.encrypt_and_digest(value)
|
||||
return iv, cryptval, hmac, '\x02'
|
||||
return iv, cryptval, hmac, b'\x02'
|
||||
|
||||
|
||||
def _load_dict_from_dbm(dpath, tdb):
|
||||
@@ -565,11 +581,18 @@ def relay_slaved_requests(name, listener):
|
||||
lh = StreamHandler(listener)
|
||||
_hasquorum = len(cfgstreams) >= (
|
||||
len(_cfgstore['collective']) // 2)
|
||||
payload = cPickle.dumps({'quorum': _hasquorum})
|
||||
for _ in pushes.starmap(
|
||||
_push_rpc,
|
||||
[(cfgstreams[s], payload) for s in cfgstreams]):
|
||||
pass
|
||||
_newquorum = None
|
||||
while _hasquorum != _newquorum:
|
||||
if _newquorum is not None:
|
||||
_hasquorum = _newquorum
|
||||
payload = msgpack.packb({'quorum': _hasquorum}, use_bin_type=False)
|
||||
for _ in pushes.starmap(
|
||||
_push_rpc,
|
||||
[(cfgstreams[s], payload) for s in cfgstreams]):
|
||||
pass
|
||||
_newquorum = len(cfgstreams) >= (
|
||||
len(_cfgstore['collective']) // 2)
|
||||
_hasquorum = _newquorum
|
||||
if _hasquorum and _pending_collective_updates:
|
||||
apply_pending_collective_updates()
|
||||
msg = lh.get_next_msg()
|
||||
@@ -578,21 +601,27 @@ def relay_slaved_requests(name, listener):
|
||||
raise Exception("Unexpected loss of node in followers: " + name)
|
||||
sz = struct.unpack('!Q', msg)[0]
|
||||
if sz != 0:
|
||||
rpc = ''
|
||||
rpc = b''
|
||||
while len(rpc) < sz:
|
||||
nrpc = listener.recv(sz - len(rpc))
|
||||
if not nrpc:
|
||||
raise Exception('Truncated client error')
|
||||
rpc += nrpc
|
||||
rpc = cPickle.loads(rpc)
|
||||
rpc = msgpack.unpackb(rpc, raw=False)
|
||||
exc = None
|
||||
if not (rpc['function'].startswith('_rpc_') or rpc['function'].endswith('_collective_member')):
|
||||
raise Exception('Unsupported function {0} called'.format(rpc['function']))
|
||||
try:
|
||||
globals()[rpc['function']](*rpc['args'])
|
||||
except ValueError as ve:
|
||||
exc = ['ValueError', str(ve)]
|
||||
except Exception as e:
|
||||
exc = e
|
||||
exc = ['Exception', str(e)]
|
||||
if 'xid' in rpc:
|
||||
_push_rpc(listener, cPickle.dumps({'xid': rpc['xid'],
|
||||
'exc': exc}))
|
||||
res = _push_rpc(listener, msgpack.packb({'xid': rpc['xid'],
|
||||
'exc': exc}, use_bin_type=False))
|
||||
if not res:
|
||||
break
|
||||
try:
|
||||
msg = lh.get_next_msg()
|
||||
except Exception:
|
||||
@@ -609,7 +638,7 @@ def relay_slaved_requests(name, listener):
|
||||
if cfgstreams:
|
||||
_hasquorum = len(cfgstreams) >= (
|
||||
len(_cfgstore['collective']) // 2)
|
||||
payload = cPickle.dumps({'quorum': _hasquorum})
|
||||
payload = msgpack.packb({'quorum': _hasquorum}, use_bin_type=False)
|
||||
for _ in pushes.starmap(
|
||||
_push_rpc,
|
||||
[(cfgstreams[s], payload) for s in cfgstreams]):
|
||||
@@ -637,7 +666,9 @@ class StreamHandler(object):
|
||||
if confluent.util.monotonic_time() > self.expiry:
|
||||
return None
|
||||
if confluent.util.monotonic_time() > self.keepalive:
|
||||
_push_rpc(self.sock, b'') # nulls are a keepalive
|
||||
res = _push_rpc(self.sock, b'') # nulls are a keepalive
|
||||
if not res:
|
||||
return None
|
||||
self.keepalive = confluent.util.monotonic_time() + 20
|
||||
self.expiry = confluent.util.monotonic_time() + 60
|
||||
msg = self.sock.recv(8)
|
||||
@@ -728,28 +759,37 @@ def follow_channel(channel):
|
||||
while msg:
|
||||
sz = struct.unpack('!Q', msg)[0]
|
||||
if sz != 0:
|
||||
rpc = ''
|
||||
rpc = b''
|
||||
while len(rpc) < sz:
|
||||
nrpc = channel.recv(sz - len(rpc))
|
||||
if not nrpc:
|
||||
raise Exception('Truncated message error')
|
||||
rpc += nrpc
|
||||
rpc = cPickle.loads(rpc)
|
||||
rpc = msgpack.unpackb(rpc, raw=False)
|
||||
if 'txcount' in rpc:
|
||||
_txcount = rpc['txcount']
|
||||
if 'function' in rpc:
|
||||
if not (rpc['function'].startswith('_true') or rpc['function'].startswith('_rpc')):
|
||||
raise Exception("Received unsupported function call: {0}".format(rpc['function']))
|
||||
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'])
|
||||
exctype, excstr = rpc['exc']
|
||||
if exctype == 'ValueError':
|
||||
exc = ValueError(excstr)
|
||||
else:
|
||||
exc = Exception(excstr)
|
||||
_pendingchangesets[rpc['xid']].send_exception(exc)
|
||||
else:
|
||||
_pendingchangesets[rpc['xid']].send()
|
||||
if 'quorum' in rpc:
|
||||
_hasquorum = rpc['quorum']
|
||||
_push_rpc(channel, b'') # use null as ACK
|
||||
res = _push_rpc(channel, b'') # use null as ACK
|
||||
if not res:
|
||||
break
|
||||
msg = lh.get_next_msg()
|
||||
finally:
|
||||
# mark the connection as broken
|
||||
@@ -766,6 +806,34 @@ def add_collective_member(name, address, fingerprint):
|
||||
exec_on_followers('_true_add_collective_member', name, address, fingerprint)
|
||||
_true_add_collective_member(name, address, fingerprint)
|
||||
|
||||
def del_collective_member(name):
|
||||
if cfgleader and not isinstance(cfgleader, bool):
|
||||
return exec_on_leader('del_collective_member', name)
|
||||
if cfgstreams:
|
||||
exec_on_followers_unconditional('_true_del_collective_member', name)
|
||||
_true_del_collective_member(name)
|
||||
|
||||
def _true_del_collective_member(name, sync=True):
|
||||
global cfgleader
|
||||
name = confluent.util.stringify(name)
|
||||
if _cfgstore is None:
|
||||
return
|
||||
if 'collective' not in _cfgstore:
|
||||
return
|
||||
if name not in _cfgstore['collective']:
|
||||
return
|
||||
del _cfgstore['collective'][name]
|
||||
with _dirtylock:
|
||||
if 'collectivedirty' not in _cfgstore:
|
||||
_cfgstore['collectivedirty'] = set([])
|
||||
_cfgstore['collectivedirty'].add(name)
|
||||
if len(_cfgstore['collective']) < 2:
|
||||
del _cfgstore['collective']
|
||||
cfgleader = None
|
||||
if sync:
|
||||
ConfigManager._bg_sync_to_file()
|
||||
|
||||
|
||||
_pending_collective_updates = {}
|
||||
|
||||
|
||||
@@ -1091,6 +1159,8 @@ class ConfigManager(object):
|
||||
attribute, match = expression.split('=')
|
||||
else:
|
||||
raise Exception('Invalid Expression')
|
||||
if attribute.startswith('secret.'):
|
||||
raise Exception('Filter by secret attributes is not supported')
|
||||
for node in nodes:
|
||||
try:
|
||||
currvals = [self._cfgstore['nodes'][node][attribute]['value']]
|
||||
@@ -1335,7 +1405,7 @@ class ConfigManager(object):
|
||||
return exec_on_leader('_rpc_master_set_user', self.tenant, name,
|
||||
attributemap)
|
||||
if cfgstreams:
|
||||
exec_on_followers('_rpc_set_user', self.tenant, name)
|
||||
exec_on_followers('_rpc_set_user', self.tenant, name, attributemap)
|
||||
self._true_set_user(name, attributemap)
|
||||
|
||||
def _true_set_user(self, name, attributemap):
|
||||
@@ -1415,9 +1485,10 @@ class ConfigManager(object):
|
||||
self._cfgstore['users'][name]['displayname'] = displayname
|
||||
_cfgstore['main']['idmap'][uid] = {
|
||||
'tenant': self.tenant,
|
||||
'username': name
|
||||
'username': name,
|
||||
'role': role,
|
||||
}
|
||||
if attributemap is not None:
|
||||
if attributemap:
|
||||
self._true_set_user(name, attributemap)
|
||||
_mark_dirtykey('users', name, self.tenant)
|
||||
_mark_dirtykey('idmap', uid)
|
||||
@@ -1580,6 +1651,7 @@ class ConfigManager(object):
|
||||
if group in self._cfgstore['nodes'][node]['groups']:
|
||||
self._cfgstore['nodes'][node]['groups'].remove(group)
|
||||
self._node_removed_from_group(node, group, changeset)
|
||||
_mark_dirtykey('nodes', node, self.tenant)
|
||||
for node in nodes:
|
||||
if node not in self._cfgstore['nodes']:
|
||||
self._cfgstore['nodes'][node] = {'groups': [group]}
|
||||
@@ -1821,6 +1893,8 @@ class ConfigManager(object):
|
||||
eventlet.spawn_n(_do_notifier, self, watcher, callback)
|
||||
|
||||
def del_nodes(self, nodes):
|
||||
if isinstance(nodes, set):
|
||||
nodes = list(nodes) # msgpack can't handle set
|
||||
if cfgleader: # slaved to a collective
|
||||
return exec_on_leader('_rpc_master_del_nodes', self.tenant,
|
||||
nodes)
|
||||
@@ -2233,7 +2307,7 @@ class ConfigManager(object):
|
||||
target = dumpdata[confarea][element][attribute]['cryptvalue']
|
||||
cryptval = []
|
||||
for value in target:
|
||||
cryptval.append(base64.b64encode(value))
|
||||
cryptval.append(confluent.util.stringify(base64.b64encode(value)))
|
||||
if attribute == 'cryptpass':
|
||||
dumpdata[confarea][element][attribute] = '!'.join(cryptval)
|
||||
else:
|
||||
@@ -2326,31 +2400,37 @@ class ConfigManager(object):
|
||||
for globalkey in dirtyglobals:
|
||||
if globalkey in _cfgstore['globals']:
|
||||
globalf[globalkey] = \
|
||||
cPickle.dumps(_cfgstore['globals'][globalkey])
|
||||
cPickle.dumps(_cfgstore['globals'][globalkey], protocol=cPickle.HIGHEST_PROTOCOL)
|
||||
else:
|
||||
if globalkey in globalf:
|
||||
del globalf[globalkey]
|
||||
finally:
|
||||
globalf.close()
|
||||
if fullsync or 'collectivedirty' in _cfgstore:
|
||||
collectivef = dbm.open(os.path.join(cls._cfgdir, "collective"),
|
||||
'c', 384)
|
||||
try:
|
||||
if fullsync:
|
||||
colls = _cfgstore['collective']
|
||||
else:
|
||||
with _dirtylock:
|
||||
colls = copy.deepcopy(_cfgstore['collectivedirty'])
|
||||
del _cfgstore['collectivedirty']
|
||||
for coll in colls:
|
||||
if coll in _cfgstore['collective']:
|
||||
collectivef[coll] = cPickle.dumps(
|
||||
_cfgstore['collective'][coll])
|
||||
if len(_cfgstore.get('collective', ())) > 1:
|
||||
collectivef = dbm.open(os.path.join(cls._cfgdir, "collective"),
|
||||
'c', 384)
|
||||
try:
|
||||
if fullsync:
|
||||
colls = _cfgstore['collective']
|
||||
else:
|
||||
if coll in collectivef:
|
||||
del globalf[coll]
|
||||
finally:
|
||||
collectivef.close()
|
||||
with _dirtylock:
|
||||
colls = copy.deepcopy(_cfgstore['collectivedirty'])
|
||||
del _cfgstore['collectivedirty']
|
||||
for coll in colls:
|
||||
if coll in _cfgstore['collective']:
|
||||
collectivef[coll] = cPickle.dumps(
|
||||
_cfgstore['collective'][coll], protocol=cPickle.HIGHEST_PROTOCOL)
|
||||
else:
|
||||
if coll in collectivef:
|
||||
del collectivef[coll]
|
||||
finally:
|
||||
collectivef.close()
|
||||
else:
|
||||
try:
|
||||
os.remove(os.path.join(cls._cfgdir, "collective"))
|
||||
except OSError:
|
||||
pass
|
||||
if fullsync:
|
||||
pathname = cls._cfgdir
|
||||
currdict = _cfgstore['main']
|
||||
@@ -2359,7 +2439,7 @@ class ConfigManager(object):
|
||||
dbf = dbm.open(os.path.join(pathname, category), 'c', 384) # 0600
|
||||
try:
|
||||
for ck in currdict[category]:
|
||||
dbf[ck] = cPickle.dumps(currdict[category][ck])
|
||||
dbf[ck] = cPickle.dumps(currdict[category][ck], protocol=cPickle.HIGHEST_PROTOCOL)
|
||||
finally:
|
||||
dbf.close()
|
||||
elif 'dirtykeys' in _cfgstore:
|
||||
@@ -2383,7 +2463,7 @@ class ConfigManager(object):
|
||||
if ck in dbf:
|
||||
del dbf[ck]
|
||||
else:
|
||||
dbf[ck] = cPickle.dumps(currdict[category][ck])
|
||||
dbf[ck] = cPickle.dumps(currdict[category][ck], protocol=cPickle.HIGHEST_PROTOCOL)
|
||||
finally:
|
||||
dbf.close()
|
||||
willrun = False
|
||||
@@ -2447,20 +2527,21 @@ def _dump_keys(password, dojson=True):
|
||||
init_masterkey()
|
||||
cryptkey = _format_key(_masterkey, password=password)
|
||||
if 'passphraseprotected' in cryptkey:
|
||||
cryptkey = '!'.join(map(base64.b64encode,
|
||||
cryptkey['passphraseprotected']))
|
||||
cryptkey = '!'.join(
|
||||
[confluent.util.stringify(base64.b64encode(x))
|
||||
for x in cryptkey['passphraseprotected']])
|
||||
else:
|
||||
cryptkey = '*unencrypted:{0}'.format(base64.b64encode(
|
||||
cryptkey['unencryptedvalue']))
|
||||
cryptkey = '*unencrypted:{0}'.format(confluent.util.stringify(base64.b64encode(
|
||||
cryptkey['unencryptedvalue'])))
|
||||
keydata = {'cryptkey': cryptkey}
|
||||
if _masterintegritykey is not None:
|
||||
integritykey = _format_key(_masterintegritykey, password=password)
|
||||
if 'passphraseprotected' in integritykey:
|
||||
integritykey = '!'.join(map(base64.b64encode,
|
||||
integritykey['passphraseprotected']))
|
||||
integritykey = '!'.join([confluent.util.stringify(base64.b64encode(x)) for x in
|
||||
integritykey['passphraseprotected']])
|
||||
else:
|
||||
integritykey = '*unencrypted:{0}'.format(base64.b64encode(
|
||||
integritykey['unencryptedvalue']))
|
||||
integritykey = '*unencrypted:{0}'.format(confluent.util.stringify(base64.b64encode(
|
||||
integritykey['unencryptedvalue'])))
|
||||
keydata['integritykey'] = integritykey
|
||||
if dojson:
|
||||
return json.dumps(keydata, sort_keys=True, indent=4, separators=(',', ': '))
|
||||
|
||||
@@ -106,7 +106,7 @@ def pytechars2line(chars, maxlen=None):
|
||||
len = 1
|
||||
for charidx in range(maxlen):
|
||||
char = chars[charidx]
|
||||
csi = []
|
||||
csi = bytearray([])
|
||||
if char.fg != lfg:
|
||||
csi.append(30 + pytecolors2ansi[char.fg])
|
||||
lfg = char.fg
|
||||
@@ -129,7 +129,7 @@ def pytechars2line(chars, maxlen=None):
|
||||
lr = char.reverse
|
||||
csi.append(7 if lr else 27)
|
||||
if csi:
|
||||
line += b'\x1b[' + b';'.join(['{0}'.format(x) for x in csi]) + b'm'
|
||||
line += b'\x1b[' + b';'.join(['{0}'.format(x).encode('utf-8') for x in csi]) + b'm'
|
||||
if not hasdata and char.data.rstrip():
|
||||
hasdata = True
|
||||
chardata = char.data
|
||||
@@ -362,13 +362,17 @@ class ConsoleHandler(object):
|
||||
if self.reconnect:
|
||||
self.reconnect.cancel()
|
||||
self.reconnect = None
|
||||
strerror = ('The console.method attribute for this node is '
|
||||
'not configured,\r\nset it to a valid value for console '
|
||||
'function')
|
||||
try:
|
||||
self._console = list(plugin.handle_path(
|
||||
self._plugin_path.format(self.node),
|
||||
"create", self.cfgmgr))[0]
|
||||
except (exc.NotImplementedException, exc.NotFoundException):
|
||||
self._console = None
|
||||
except:
|
||||
except Exception as e:
|
||||
strerror = str(e)
|
||||
if _tracelog:
|
||||
_tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
|
||||
event=log.Events.stacktrace)
|
||||
@@ -381,13 +385,9 @@ class ConsoleHandler(object):
|
||||
self._send_rcpts({'connectstate': self.connectstate,
|
||||
'error': self.error})
|
||||
self.feedbuffer(
|
||||
'\x1bc\x1b[2J\x1b[1;1H[The console.method attribute for this node is '
|
||||
'not configured,\r\nset it to a valid value for console '
|
||||
'function]')
|
||||
'\x1bc\x1b[2J\x1b[1;1H[{0}]'.format(strerror))
|
||||
self._send_rcpts(
|
||||
'\x1bc\x1b[2J\x1b[1;1H[The console.method attribute for this node is '
|
||||
'not configured,\r\nset it to a valid value for console '
|
||||
'function]')
|
||||
'\x1bc\x1b[2J\x1b[1;1H[{0}]'.format(strerror))
|
||||
self.clearerror = True
|
||||
return
|
||||
if self.clearerror:
|
||||
@@ -531,9 +531,11 @@ class ConsoleHandler(object):
|
||||
if data == conapi.ConsoleEvent.Disconnect:
|
||||
self._got_disconnected()
|
||||
return
|
||||
elif data == '':
|
||||
elif data in (b'', u''):
|
||||
# ignore empty strings from a cconsole provider
|
||||
return
|
||||
if not isinstance(data, bytes):
|
||||
data = data.encode('utf-8')
|
||||
if b'\x1b[?1l' in data: # request for ansi mode cursor keys
|
||||
self.appmodedetected = False
|
||||
if b'\x1b[?1h' in data: # remember the session wants the client to use
|
||||
@@ -616,6 +618,8 @@ class ConsoleHandler(object):
|
||||
def write(self, data):
|
||||
if self.connectstate == 'connected':
|
||||
try:
|
||||
if isinstance(data, str) and not isinstance(data, bytes):
|
||||
data = data.encode('utf-8')
|
||||
self._console.write(data)
|
||||
except Exception:
|
||||
_tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
|
||||
|
||||
@@ -60,17 +60,14 @@ import eventlet.greenpool as greenpool
|
||||
import eventlet.green.ssl as ssl
|
||||
import eventlet.queue as queue
|
||||
import itertools
|
||||
import msgpack
|
||||
import os
|
||||
try:
|
||||
import cPickle as pickle
|
||||
except ImportError:
|
||||
import pickle
|
||||
import socket
|
||||
import struct
|
||||
import sys
|
||||
|
||||
pluginmap = {}
|
||||
dispatch_plugins = (b'ipmi', u'ipmi')
|
||||
dispatch_plugins = (b'ipmi', u'ipmi', b'redfish', u'redfish', b'tsmsol', u'tsmsol')
|
||||
|
||||
|
||||
def seek_element(currplace, currkey):
|
||||
@@ -219,10 +216,14 @@ def _init_core():
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
'extra': PluginRoute({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
'advanced': PluginRoute({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
'storage': {
|
||||
@@ -415,18 +416,22 @@ def create_user(inputdata, configmanager):
|
||||
try:
|
||||
username = inputdata['name']
|
||||
del inputdata['name']
|
||||
role = inputdata['role']
|
||||
del inputdata['role']
|
||||
except (KeyError, ValueError):
|
||||
raise exc.InvalidArgumentException()
|
||||
configmanager.create_user(username, attributemap=inputdata)
|
||||
raise exc.InvalidArgumentException('Missing user name or role')
|
||||
configmanager.create_user(username, role, attributemap=inputdata)
|
||||
|
||||
|
||||
def create_usergroup(inputdata, configmanager):
|
||||
try:
|
||||
groupname = inputdata['name']
|
||||
role = inputdata['role']
|
||||
del inputdata['name']
|
||||
del inputdata['role']
|
||||
except (KeyError, ValueError):
|
||||
raise exc.InvalidArgumentException()
|
||||
configmanager.create_usergroup(groupname)
|
||||
raise exc.InvalidArgumentException("Missing user name or role")
|
||||
configmanager.create_usergroup(groupname, role)
|
||||
|
||||
|
||||
def update_usergroup(groupname, attribmap, configmanager):
|
||||
@@ -450,7 +455,7 @@ def show_usergroup(groupname, configmanager):
|
||||
def show_user(name, configmanager):
|
||||
userobj = configmanager.get_user(name)
|
||||
rv = {}
|
||||
for attr in attrscheme.user.iterkeys():
|
||||
for attr in attrscheme.user:
|
||||
rv[attr] = None
|
||||
if attr == 'password':
|
||||
if 'cryptpass' in userobj:
|
||||
@@ -687,13 +692,22 @@ def handle_dispatch(connection, cert, dispatch, peername):
|
||||
cfm.get_collective_member(peername)['fingerprint'], cert):
|
||||
connection.close()
|
||||
return
|
||||
dispatch = pickle.loads(dispatch)
|
||||
if dispatch[0:2] != b'\x01\x03': # magic value to indicate msgpack
|
||||
# We only support msgpack now
|
||||
# The magic should preclude any pickle, as the first byte can never be
|
||||
# under 0x20 or so.
|
||||
connection.close()
|
||||
return
|
||||
dispatch = msgpack.unpackb(dispatch[2:], raw=False)
|
||||
configmanager = cfm.ConfigManager(dispatch['tenant'])
|
||||
nodes = dispatch['nodes']
|
||||
inputdata = dispatch['inputdata']
|
||||
operation = dispatch['operation']
|
||||
pathcomponents = dispatch['path']
|
||||
routespec = nested_lookup(noderesources, pathcomponents)
|
||||
inputdata = msg.get_input_message(
|
||||
pathcomponents, operation, inputdata, nodes, dispatch['isnoderange'],
|
||||
configmanager)
|
||||
plugroute = routespec.routeinfo
|
||||
plugpath = None
|
||||
nodesbyhandler = {}
|
||||
@@ -731,10 +745,18 @@ def handle_dispatch(connection, cert, dispatch, peername):
|
||||
|
||||
def _forward_rsp(connection, res):
|
||||
try:
|
||||
r = pickle.dumps(res)
|
||||
except TypeError:
|
||||
r = pickle.dumps(Exception(
|
||||
'Cannot serialize error, check collective.manager error logs for details' + str(res)))
|
||||
r = res.serialize()
|
||||
except AttributeError:
|
||||
if isinstance(res, Exception):
|
||||
r = msgpack.packb(['Exception', str(res)], use_bin_type=False)
|
||||
else:
|
||||
r = msgpack.packb(
|
||||
['Exception', 'Unable to serialize response ' + repr(res)],
|
||||
use_bin_type=False)
|
||||
except Exception as e:
|
||||
r = msgpack.packb(
|
||||
['Exception', 'Unable to serialize response ' + repr(res) + ' due to ' + str(e)],
|
||||
use_bin_type=False)
|
||||
rlen = len(r)
|
||||
if not rlen:
|
||||
return
|
||||
@@ -825,7 +847,7 @@ def handle_node_request(configmanager, inputdata, operation,
|
||||
del pathcomponents[0:2]
|
||||
passvalues = queue.Queue()
|
||||
plugroute = routespec.routeinfo
|
||||
inputdata = msg.get_input_message(
|
||||
msginputdata = msg.get_input_message(
|
||||
pathcomponents, operation, inputdata, nodes, isnoderange,
|
||||
configmanager)
|
||||
if 'handler' in plugroute: # fixed handler definition, easy enough
|
||||
@@ -836,7 +858,7 @@ def handle_node_request(configmanager, inputdata, operation,
|
||||
passvalue = hfunc(
|
||||
nodes=nodes, element=pathcomponents,
|
||||
configmanager=configmanager,
|
||||
inputdata=inputdata)
|
||||
inputdata=msginputdata)
|
||||
if isnoderange:
|
||||
return passvalue
|
||||
elif isinstance(passvalue, console.Console):
|
||||
@@ -889,13 +911,13 @@ def handle_node_request(configmanager, inputdata, operation,
|
||||
workers.spawn(addtoqueue, passvalues, hfunc, {'nodes': nodesbyhandler[hfunc],
|
||||
'element': pathcomponents,
|
||||
'configmanager': configmanager,
|
||||
'inputdata': inputdata})
|
||||
'inputdata': msginputdata})
|
||||
for manager in nodesbymanager:
|
||||
numworkers += 1
|
||||
workers.spawn(addtoqueue, passvalues, dispatch_request, {
|
||||
'nodes': nodesbymanager[manager], 'manager': manager,
|
||||
'element': pathcomponents, 'configmanager': configmanager,
|
||||
'inputdata': inputdata, 'operation': operation})
|
||||
'inputdata': inputdata, 'operation': operation, 'isnoderange': isnoderange})
|
||||
if isnoderange or not autostrip:
|
||||
return iterate_queue(numworkers, passvalues)
|
||||
else:
|
||||
@@ -939,7 +961,7 @@ def addtoqueue(theq, fun, kwargs):
|
||||
|
||||
|
||||
def dispatch_request(nodes, manager, element, configmanager, inputdata,
|
||||
operation):
|
||||
operation, isnoderange):
|
||||
a = configmanager.get_collective_member(manager)
|
||||
try:
|
||||
remote = socket.create_connection((a['address'], 13001))
|
||||
@@ -963,12 +985,20 @@ def dispatch_request(nodes, manager, element, configmanager, inputdata,
|
||||
if not util.cert_matches(a['fingerprint'], remote.getpeercert(
|
||||
binary_form=True)):
|
||||
raise Exception("Invalid certificate on peer")
|
||||
tlvdata.recv(remote)
|
||||
banner = tlvdata.recv(remote)
|
||||
vers = banner.split()[2]
|
||||
if vers == b'v0':
|
||||
pvers = 2
|
||||
elif vers == b'v1':
|
||||
pvers = 4
|
||||
if sys.version_info[0] < 3:
|
||||
pvers = 2
|
||||
tlvdata.recv(remote)
|
||||
myname = collective.get_myname()
|
||||
dreq = pickle.dumps({'name': myname, 'nodes': list(nodes),
|
||||
'path': element,'tenant': configmanager.tenant,
|
||||
'operation': operation, 'inputdata': inputdata})
|
||||
dreq = b'\x01\x03' + msgpack.packb(
|
||||
{'name': myname, 'nodes': list(nodes),
|
||||
'path': element,'tenant': configmanager.tenant,
|
||||
'operation': operation, 'inputdata': inputdata, 'isnoderange': isnoderange}, use_bin_type=False)
|
||||
tlvdata.send(remote, {'dispatch': {'name': myname, 'length': len(dreq)}})
|
||||
remote.sendall(dreq)
|
||||
while True:
|
||||
@@ -1015,9 +1045,14 @@ def dispatch_request(nodes, manager, element, configmanager, inputdata,
|
||||
a['name']))
|
||||
return
|
||||
rsp += nrsp
|
||||
rsp = pickle.loads(rsp)
|
||||
try:
|
||||
rsp = msg.msg_deserialize(rsp)
|
||||
except Exception:
|
||||
rsp = exc.deserialize_exc(rsp)
|
||||
if isinstance(rsp, Exception):
|
||||
raise rsp
|
||||
if not rsp:
|
||||
raise Exception('Error in cross-collective serialize/deserialze, see remote logs')
|
||||
yield rsp
|
||||
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ import base64
|
||||
import confluent.config.configmanager as cfm
|
||||
import confluent.collective.manager as collective
|
||||
import confluent.discovery.protocols.pxe as pxe
|
||||
#import confluent.discovery.protocols.ssdp as ssdp
|
||||
import confluent.discovery.protocols.ssdp as ssdp
|
||||
import confluent.discovery.protocols.slp as slp
|
||||
import confluent.discovery.handlers.imm as imm
|
||||
import confluent.discovery.handlers.cpstorage as cpstorage
|
||||
@@ -91,6 +91,11 @@ import eventlet.semaphore
|
||||
autosensors = set()
|
||||
scanner = None
|
||||
|
||||
try:
|
||||
unicode
|
||||
except NameError:
|
||||
unicode = str
|
||||
|
||||
class nesteddict(dict):
|
||||
|
||||
def __missing__(self, key):
|
||||
@@ -102,6 +107,8 @@ nodehandlers = {
|
||||
'service:management-hardware.Lenovo:lenovo-xclarity-controller': xcc,
|
||||
'service:management-hardware.IBM:integrated-management-module2': imm,
|
||||
'pxe-client': pxeh,
|
||||
'onie-switch': None,
|
||||
'cumulus-switch': None,
|
||||
'service:io-device.Lenovo:management-module': None,
|
||||
'service:thinkagile-storage': cpstorage,
|
||||
'service:lenovo-tsm': tsm,
|
||||
@@ -109,6 +116,8 @@ nodehandlers = {
|
||||
|
||||
servicenames = {
|
||||
'pxe-client': 'pxe-client',
|
||||
'onie-switch': 'onie-switch',
|
||||
'cumulus-switch': 'cumulus-switch',
|
||||
'service:lenovo-smm': 'lenovo-smm',
|
||||
'service:management-hardware.Lenovo:lenovo-xclarity-controller': 'lenovo-xcc',
|
||||
'service:management-hardware.IBM:integrated-management-module2': 'lenovo-imm2',
|
||||
@@ -119,6 +128,8 @@ servicenames = {
|
||||
|
||||
servicebyname = {
|
||||
'pxe-client': 'pxe-client',
|
||||
'onie-switch': 'onie-switch',
|
||||
'cumulus-switch': 'cumulus-switch',
|
||||
'lenovo-smm': 'service:lenovo-smm',
|
||||
'lenovo-xcc': 'service:management-hardware.Lenovo:lenovo-xclarity-controller',
|
||||
'lenovo-imm2': 'service:management-hardware.IBM:integrated-management-module2',
|
||||
@@ -371,7 +382,7 @@ def handle_autosense_config(operation, inputdata):
|
||||
yield msg.KeyValueData({'enabled': autosense})
|
||||
elif operation == 'update':
|
||||
enabled = inputdata['enabled']
|
||||
if type(enabled) in (unicode, str):
|
||||
if type(enabled) in (unicode, bytes):
|
||||
enabled = enabled.lower() in ('true', '1', 'y', 'yes', 'enable',
|
||||
'enabled')
|
||||
if autosense == enabled:
|
||||
@@ -705,7 +716,8 @@ def detected(info):
|
||||
|
||||
def b64tohex(b64str):
|
||||
bd = base64.b64decode(b64str)
|
||||
return ''.join(['{0:02x}'.format(ord(x)) for x in bd])
|
||||
bd = bytearray(bd)
|
||||
return ''.join(['{0:02x}'.format(x) for x in bd])
|
||||
|
||||
|
||||
def get_enclosure_chain_head(nodename, cfg):
|
||||
@@ -943,9 +955,9 @@ def eval_node(cfg, handler, info, nodename, manual=False):
|
||||
# raise exc.InvalidArgumentException(errorstr)
|
||||
# log.log({'error': errorstr})
|
||||
if encuuid in pending_by_uuid:
|
||||
pending_by_uuid[encuuid].add(info)
|
||||
pending_by_uuid[encuuid].append(info)
|
||||
else:
|
||||
pending_by_uuid[encuuid] = set([info])
|
||||
pending_by_uuid[encuuid] = [info]
|
||||
return
|
||||
# We found the real smm, replace the list with the actual smm
|
||||
# to continue
|
||||
@@ -1088,6 +1100,10 @@ def discover_node(cfg, handler, info, nodename, manual):
|
||||
info['discostatus'] = 'discovered'
|
||||
for i in pending_by_uuid.get(curruuid, []):
|
||||
eventlet.spawn_n(_recheck_single_unknown_info, cfg, i)
|
||||
try:
|
||||
del pending_by_uuid[curruuid]
|
||||
except KeyError:
|
||||
pass
|
||||
return True
|
||||
log.log({'info': 'Detected {0}, but discovery.policy is not set to a '
|
||||
'value allowing discovery (open or permissive)'.format(
|
||||
@@ -1201,12 +1217,21 @@ def rescan():
|
||||
if scanner:
|
||||
return
|
||||
else:
|
||||
scanner = eventlet.spawn(slp.active_scan, safe_detected, slp)
|
||||
scanner = eventlet.spawn(blocking_scan)
|
||||
|
||||
|
||||
def blocking_scan():
|
||||
global scanner
|
||||
slpscan = eventlet.spawn(slp.active_scan, safe_detected, slp)
|
||||
ssdpscan = eventlet.spawn(ssdp.active_scan, safe_detected, ssdp)
|
||||
slpscan.wait()
|
||||
ssdpscan.wait()
|
||||
scanner = None
|
||||
|
||||
def start_detection():
|
||||
global attribwatcher
|
||||
global rechecker
|
||||
global rechecktime
|
||||
_map_unique_ids()
|
||||
cfg = cfm.ConfigManager(None)
|
||||
allnodes = cfg.list_nodes()
|
||||
|
||||
@@ -13,9 +13,58 @@
|
||||
# limitations under the License.
|
||||
|
||||
import confluent.discovery.handlers.bmc as bmchandler
|
||||
import eventlet
|
||||
import confluent.util as util
|
||||
try:
|
||||
from urllib import urlencode
|
||||
except ImportError:
|
||||
from urllib.parse import urlencode
|
||||
webclient = eventlet.import_patched('pyghmi.util.webclient')
|
||||
|
||||
class NodeHandler(bmchandler.NodeHandler):
|
||||
DEFAULT_USER = 'admin'
|
||||
DEFAULT_PASS = 'admin'
|
||||
devname = 'BMC'
|
||||
maxmacs = 2
|
||||
|
||||
def validate_cert(self, certificate):
|
||||
# broadly speaking, merely checks consistency moment to moment,
|
||||
# but if https_cert gets stricter, this check means something
|
||||
fprint = util.get_fingerprint(self.https_cert)
|
||||
return util.cert_matches(fprint, certificate)
|
||||
|
||||
def get_webclient(self, user, passwd, newuser, newpass):
|
||||
wc = webclient.SecureHTTPConnection(self.ipaddr, 443,
|
||||
verifycallback=self.validate_cert)
|
||||
wc.connect()
|
||||
authdata = urlencode({'username': user, 'password': passwd,
|
||||
'weblogsign': 1})
|
||||
res = wc.grab_json_response_with_status('/api/session', authdata)
|
||||
if res[1] == 200:
|
||||
if res[0].get('force_password', 1) == 0:
|
||||
# Need to handle password change
|
||||
passchange = {
|
||||
'Password': newpass,
|
||||
'RetypePassword': newpass,
|
||||
'param': 4,
|
||||
'username': 'admin',
|
||||
'privilege': 4,
|
||||
}
|
||||
passchange = urlencode(passchange)
|
||||
rsp = wc.grab_json_response_with_status('/api/reset-pass',
|
||||
passchange)
|
||||
rsp = wc.grab_json_response_with_status('/api/session',
|
||||
method='DELETE')
|
||||
|
||||
def config(self, nodename, reset=False):
|
||||
self.nodename = nodename
|
||||
creds = self.configmanager.get_node_attributes(
|
||||
self.nodename, ['secret.hardwaremanagementuser',
|
||||
'secret.hardwaremanagementpassword'],
|
||||
decrypt=True)
|
||||
user, passwd, isdefault = self.get_node_credentials(
|
||||
nodename, creds, 'admin', 'admin')
|
||||
if not isdefault:
|
||||
self.get_webclient(self.DEFAULT_USER, self.DEFAULT_PASS, user,
|
||||
passwd)
|
||||
self._bmcconfig(nodename, False)
|
||||
|
||||
@@ -68,17 +68,21 @@ class NodeHandler(object):
|
||||
def _savecert(self, certificate):
|
||||
self._fp = certificate
|
||||
return True
|
||||
|
||||
|
||||
def get_node_credentials(self, nodename, creds, defuser, defpass):
|
||||
user = creds.get(nodename, {}).get(
|
||||
'secret.hardwaremanagementuser', {}).get('value', None)
|
||||
havecustomcreds = False
|
||||
if user and not isinstance(user, str):
|
||||
user = user.decode('utf8')
|
||||
if user is not None and user != defuser:
|
||||
havecustomcreds = True
|
||||
else:
|
||||
user = defuser
|
||||
passwd = creds.get(nodename, {}).get(
|
||||
'secret.hardwaremanagementpassword', {}).get('value', None)
|
||||
if passwd and not isinstance(passwd, str):
|
||||
passwd = passwd.decode('utf8')
|
||||
if passwd is not None and passwd != defpass:
|
||||
havecustomcreds = True
|
||||
else:
|
||||
|
||||
@@ -12,9 +12,11 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import codecs
|
||||
import confluent.discovery.handlers.bmc as bmchandler
|
||||
import pyghmi.exceptions as pygexc
|
||||
import pyghmi.ipmi.private.util as pygutil
|
||||
import confluent.util as util
|
||||
import struct
|
||||
|
||||
class NodeHandler(bmchandler.NodeHandler):
|
||||
@@ -37,8 +39,9 @@ class NodeHandler(bmchandler.NodeHandler):
|
||||
if wronguuid:
|
||||
# we need to fix the first three portions of the uuid
|
||||
uuidprefix = wronguuid.split('-')[:3]
|
||||
uuidprefix = struct.pack(
|
||||
'<IHH', *[int(x, 16) for x in uuidprefix]).encode('hex')
|
||||
uuidprefix = codecs.encode(struct.pack(
|
||||
'<IHH', *[int(x, 16) for x in uuidprefix]), 'hex')
|
||||
uuidprefix = util.stringify(uuidprefix)
|
||||
uuidprefix = uuidprefix[:8] + '-' + uuidprefix[8:12] + '-' + \
|
||||
uuidprefix[12:16]
|
||||
self.info['uuid'] = uuidprefix + '-' + '-'.join(
|
||||
|
||||
@@ -12,14 +12,19 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import codecs
|
||||
import confluent.discovery.handlers.bmc as bmchandler
|
||||
import confluent.exceptions as exc
|
||||
import eventlet
|
||||
webclient = eventlet.import_patched('pyghmi.util.webclient')
|
||||
import struct
|
||||
import urllib
|
||||
try:
|
||||
from urllib import urlencode
|
||||
except ImportError:
|
||||
from urllib.parse import urlencode
|
||||
import eventlet.support.greendns
|
||||
import confluent.netutil as netutil
|
||||
import confluent.util as util
|
||||
getaddrinfo = eventlet.support.greendns.getaddrinfo
|
||||
|
||||
from xml.etree.ElementTree import fromstring
|
||||
@@ -27,8 +32,9 @@ from xml.etree.ElementTree import fromstring
|
||||
def fixuuid(baduuid):
|
||||
# SMM dumps it out in hex
|
||||
uuidprefix = (baduuid[:8], baduuid[8:12], baduuid[12:16])
|
||||
a = struct.pack('<IHH', *[int(x, 16) for x in uuidprefix]).encode(
|
||||
a = codecs.encode(struct.pack('<IHH', *[int(x, 16) for x in uuidprefix]),
|
||||
'hex')
|
||||
a = util.stringify(a)
|
||||
uuid = (a[:8], a[8:12], a[12:16], baduuid[16:20], baduuid[20:])
|
||||
return '-'.join(uuid).lower()
|
||||
|
||||
@@ -92,7 +98,7 @@ class NodeHandler(bmchandler.NodeHandler):
|
||||
setdata += ',v4Gateway:{0}'.format(gateway)
|
||||
wc.request('POST', '/data', setdata)
|
||||
rsp = wc.getresponse()
|
||||
rspdata = rsp.read()
|
||||
rspdata = util.stringify(rsp.read())
|
||||
if '<statusCode>0' not in rspdata:
|
||||
raise Exception("Error configuring SMM Network")
|
||||
return
|
||||
@@ -110,16 +116,16 @@ class NodeHandler(bmchandler.NodeHandler):
|
||||
'password': 'PASSW0RD',
|
||||
}
|
||||
headers = {'Connection': 'keep-alive', 'Content-Type': 'application/x-www-form-urlencoded'}
|
||||
wc.request('POST', '/data/login', urllib.urlencode(authdata), headers)
|
||||
wc.request('POST', '/data/login', urlencode(authdata), headers)
|
||||
rsp = wc.getresponse()
|
||||
rspdata = rsp.read()
|
||||
rspdata = util.stringify(rsp.read())
|
||||
if 'authResult>0' not in rspdata:
|
||||
# default credentials are refused, try with the actual
|
||||
authdata['user'] = username
|
||||
authdata['password'] = password
|
||||
wc.request('POST', '/data/login', urllib.urlencode(authdata), headers)
|
||||
wc.request('POST', '/data/login', urlencode(authdata), headers)
|
||||
rsp = wc.getresponse()
|
||||
rspdata = rsp.read()
|
||||
rspdata = util.stringify(rsp.read())
|
||||
if 'renew_account' in rspdata:
|
||||
raise Exception('Configured password has expired')
|
||||
if 'authResult>0' not in rspdata:
|
||||
@@ -133,13 +139,13 @@ class NodeHandler(bmchandler.NodeHandler):
|
||||
tokens = fromstring(rspdata)
|
||||
st2 = tokens.findall('st2')[0].text
|
||||
wc.set_header('ST2', st2)
|
||||
wc.request('POST', '/data/changepwd', urllib.urlencode(passwdchange))
|
||||
wc.request('POST', '/data/changepwd', urlencode(passwdchange))
|
||||
rsp = wc.getresponse()
|
||||
rspdata = rsp.read()
|
||||
authdata['password'] = password
|
||||
wc.request('POST', '/data/login', urllib.urlencode(authdata), headers)
|
||||
wc.request('POST', '/data/login', urlencode(authdata), headers)
|
||||
rsp = wc.getresponse()
|
||||
rspdata = rsp.read()
|
||||
rspdata = util.stringify(rsp.read())
|
||||
if 'authResult>0' in rspdata:
|
||||
tokens = fromstring(rspdata)
|
||||
st2 = tokens.findall('st2')[0].text
|
||||
@@ -153,7 +159,7 @@ class NodeHandler(bmchandler.NodeHandler):
|
||||
rsp = wc.getresponse()
|
||||
rspdata = rsp.read()
|
||||
authdata['user'] = username
|
||||
wc.request('POST', '/data/login', urllib.urlencode(authdata, headers))
|
||||
wc.request('POST', '/data/login', urlencode(authdata, headers))
|
||||
rsp = wc.getresponse()
|
||||
rspdata = rsp.read()
|
||||
tokens = fromstring(rspdata)
|
||||
@@ -175,6 +181,10 @@ class NodeHandler(bmchandler.NodeHandler):
|
||||
'secret.hardwaremanagementuser', {}).get('value', 'USERID')
|
||||
passwd = creds.get(nodename, {}).get(
|
||||
'secret.hardwaremanagementpassword', {}).get('value', 'PASSW0RD')
|
||||
if not isinstance(username, str):
|
||||
username = username.decode('utf8')
|
||||
if not isinstance(passwd, str):
|
||||
passwd = passwd.decode('utf8')
|
||||
if passwd == 'PASSW0RD' and self.ruleset:
|
||||
raise Exception('Cannot support default password and setting password rules at same time')
|
||||
if passwd == 'PASSW0RD':
|
||||
|
||||
@@ -18,7 +18,11 @@ import confluent.netutil as netutil
|
||||
import confluent.util as util
|
||||
import eventlet.support.greendns
|
||||
import json
|
||||
import urllib
|
||||
try:
|
||||
from urllib import urlencode
|
||||
except ImportError:
|
||||
from urllib.parse import urlencode
|
||||
|
||||
getaddrinfo = eventlet.support.greendns.getaddrinfo
|
||||
|
||||
webclient = eventlet.import_patched('pyghmi.util.webclient')
|
||||
@@ -40,6 +44,13 @@ class NodeHandler(generic.NodeHandler):
|
||||
self.atdefault = True
|
||||
super(NodeHandler, self).__init__(info, configmanager)
|
||||
|
||||
def scan(self):
|
||||
c = webclient.SecureHTTPConnection(self.ipaddr, 443, verifycallback=self.validate_cert)
|
||||
i = c.grab_json_response('/redfish/v1/')
|
||||
uuid = i.get('UUID', None)
|
||||
if uuid:
|
||||
self.info['uuid'] = uuid
|
||||
|
||||
def validate_cert(self, certificate):
|
||||
# broadly speaking, merely checks consistency moment to moment,
|
||||
# but if https_cert gets stricter, this check means something
|
||||
@@ -51,10 +62,19 @@ class NodeHandler(generic.NodeHandler):
|
||||
'username': self.DEFAULT_USER,
|
||||
'password': self.DEFAULT_PASS,
|
||||
}
|
||||
wc = webclient.SecureHTTPConnection(self.ipaddr, 443, verifycallback=self.validate_cert)
|
||||
wc.set_header('Content-Type', 'application/json')
|
||||
authmode = 0
|
||||
if not self.trieddefault:
|
||||
wc = webclient.SecureHTTPConnection(self.ipaddr, 443, verifycallback=self.validate_cert)
|
||||
rsp, status = wc.grab_json_response_with_status('/api/session', urllib.urlencode(authdata))
|
||||
rsp, status = wc.grab_json_response_with_status('/api/session', authdata)
|
||||
if status == 403:
|
||||
wc.set_header('Content-Type', 'application/x-www-form-urlencoded')
|
||||
authmode = 1
|
||||
rsp, status = wc.grab_json_response_with_status('/api/session', urlencode(authdata))
|
||||
else:
|
||||
authmode = 2
|
||||
if status > 400:
|
||||
rsp = util.stringify(rsp)
|
||||
self.trieddefault = True
|
||||
if '555' in rsp:
|
||||
passchange = {
|
||||
@@ -64,9 +84,15 @@ class NodeHandler(generic.NodeHandler):
|
||||
'default_password': self.DEFAULT_PASS,
|
||||
'username': self.DEFAULT_USER
|
||||
}
|
||||
rsp, status = wc.grab_json_response_with_status('/api/reset-pass', urllib.urlencode(passchange))
|
||||
if authmode == 2:
|
||||
rsp, status = wc.grab_json_response_with_status('/api/reset-pass', passchange)
|
||||
else:
|
||||
rsp, status = wc.grab_json_response_with_status('/api/reset-pass', urlencode(passchange))
|
||||
authdata['password'] = self.targpass
|
||||
rsp, status = wc.grab_json_response_with_status('/api/session', urllib.urlencode(authdata))
|
||||
if authmode == 2:
|
||||
rsp, status = wc.grab_json_response_with_status('/api/session', authdata)
|
||||
else:
|
||||
rsp, status = wc.grab_json_response_with_status('/api/session', urlencode(authdata))
|
||||
self.csrftok = rsp['CSRFToken']
|
||||
self.channel = rsp['channel']
|
||||
self.curruser = self.DEFAULT_USER
|
||||
@@ -81,15 +107,23 @@ class NodeHandler(generic.NodeHandler):
|
||||
if self.curruser:
|
||||
authdata['username'] = self.curruser
|
||||
authdata['password'] = self.currpass
|
||||
rsp, status = wc.grab_json_response_with_status('/api/session', urllib.urlencode(authdata))
|
||||
if rsp.status != 200:
|
||||
if authmode != 1:
|
||||
rsp, status = wc.grab_json_response_with_status('/api/session', authdata)
|
||||
if authmode == 1 or status == 403:
|
||||
wc.set_header('Content-Type', 'application/x-www-form-urlencoded')
|
||||
rsp, status = wc.grab_json_response_with_status('/api/session', urlencode(authdata))
|
||||
if status != 200:
|
||||
return None
|
||||
self.csrftok = rsp['CSRFToken']
|
||||
self.channel = rsp['channel']
|
||||
return wc
|
||||
authdata['username'] = self.targuser
|
||||
authdata['password'] = self.targpass
|
||||
rsp, status = wc.grab_json_response_with_status('/api/session', urllib.urlencode(authdata))
|
||||
if authmode != 1:
|
||||
rsp, status = wc.grab_json_response_with_status('/api/session', authdata)
|
||||
if authmode == 1 or status == 403:
|
||||
wc.set_header('Content-Type', 'application/x-www-form-urlencoded')
|
||||
rsp, status = wc.grab_json_response_with_status('/api/session', urlencode(authdata))
|
||||
if status != 200:
|
||||
return None
|
||||
self.curruser = self.targuser
|
||||
@@ -108,6 +142,8 @@ class NodeHandler(generic.NodeHandler):
|
||||
cd = creds.get(nodename, {})
|
||||
user, passwd, _ = self.get_node_credentials(
|
||||
nodename, creds, self.DEFAULT_USER, self.DEFAULT_PASS)
|
||||
user = util.stringify(user)
|
||||
passwd = util.stringify(passwd)
|
||||
self.targuser = user
|
||||
self.targpass = passwd
|
||||
wc = self._get_wc()
|
||||
|
||||
@@ -13,7 +13,9 @@
|
||||
# limitations under the License.
|
||||
|
||||
import base64
|
||||
import codecs
|
||||
import confluent.discovery.handlers.imm as immhandler
|
||||
import confluent.exceptions as exc
|
||||
import confluent.netutil as netutil
|
||||
import confluent.util as util
|
||||
import errno
|
||||
@@ -31,7 +33,8 @@ getaddrinfo = eventlet.support.greendns.getaddrinfo
|
||||
def fixup_uuid(uuidprop):
|
||||
baduuid = ''.join(uuidprop.split())
|
||||
uuidprefix = (baduuid[:8], baduuid[8:12], baduuid[12:16])
|
||||
a = struct.pack('<IHH', *[int(x, 16) for x in uuidprefix]).encode('hex')
|
||||
a = codecs.encode(struct.pack('<IHH', *[int(x, 16) for x in uuidprefix]), 'hex')
|
||||
a = util.stringify(a)
|
||||
uuid = (a[:8], a[8:12], a[12:16], baduuid[16:20], baduuid[20:])
|
||||
return '-'.join(uuid).upper()
|
||||
|
||||
@@ -93,7 +96,8 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
ipmicmd.xraw_command(netfn=0x3a, command=0xf1, data=(1,))
|
||||
except pygexc.IpmiException as e:
|
||||
if (e.ipmicode != 193 and 'Unauthorized name' not in str(e) and
|
||||
'Incorrect password' not in str(e)):
|
||||
'Incorrect password' not in str(e) and
|
||||
str(e) != 'Session no longer connected'):
|
||||
# raise an issue if anything other than to be expected
|
||||
if disableipmi:
|
||||
_, _ = wc.grab_json_response_with_status(
|
||||
@@ -125,8 +129,8 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
raise
|
||||
return (None, None)
|
||||
pwdchanged = False
|
||||
adata = json.dumps({'username': username,
|
||||
'password': password
|
||||
adata = json.dumps({'username': util.stringify(username),
|
||||
'password': util.stringify(password)
|
||||
})
|
||||
headers = {'Connection': 'keep-alive',
|
||||
'Content-Type': 'application/json'}
|
||||
@@ -168,7 +172,13 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
pwdchanged = True
|
||||
if '_csrf_token' in wc.cookies:
|
||||
wc.set_header('X-XSRF-TOKEN', wc.cookies['_csrf_token'])
|
||||
if pwdchanged:
|
||||
# Remove the minimum change interval, to allow sane
|
||||
# password changes after provisional changes
|
||||
wc = self.wc
|
||||
self.set_password_policy('', wc)
|
||||
return (wc, pwdchanged)
|
||||
return (None, None)
|
||||
|
||||
@property
|
||||
def wc(self):
|
||||
@@ -202,7 +212,7 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
# however the target *will* demand a new password... if it's currently
|
||||
# PASSW0RD
|
||||
# use TempW0rd42 to avoid divulging a real password on the line
|
||||
# This is replacing one well known password (PASSW0RD) with another
|
||||
# This is replacing one well known password (PASSW0RD) with another
|
||||
# (TempW0rd42)
|
||||
passwd = 'TempW0rd42'
|
||||
wc, pwdchanged = self.get_webclient('USERID', 'PASSW0RD', passwd)
|
||||
@@ -224,9 +234,9 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
if wc:
|
||||
return wc
|
||||
|
||||
def set_password_policy(self):
|
||||
def set_password_policy(self, strruleset, wc):
|
||||
ruleset = {'USER_GlobalMinPassChgInt': '0'}
|
||||
for rule in self.ruleset.split(','):
|
||||
for rule in strruleset.split(','):
|
||||
if '=' not in rule:
|
||||
continue
|
||||
name, value = rule.split('=')
|
||||
@@ -245,9 +255,7 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
if name.lower() == 'reuse':
|
||||
ruleset['USER_GlobalMinPassReuseCycle'] = value
|
||||
try:
|
||||
wc = self.wc
|
||||
wc.grab_json_response('/api/dataset', ruleset)
|
||||
wc.grab_json_response('/api/providers/logout')
|
||||
except Exception as e:
|
||||
print(repr(e))
|
||||
pass
|
||||
@@ -289,6 +297,8 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
userinfo = wc.grab_json_response('/api/dataset/imm_users')
|
||||
curruser = None
|
||||
uid = None
|
||||
user = util.stringify(user)
|
||||
passwd = util.stringify(passwd)
|
||||
for userent in userinfo['items'][0]['users']:
|
||||
if userent['users_user_name'] == user:
|
||||
curruser = userent
|
||||
@@ -346,15 +356,18 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
# between hypothetical secure path and today.
|
||||
dpp = self.configmanager.get_node_attributes(
|
||||
nodename, 'discovery.passwordrules')
|
||||
self.ruleset = dpp.get(nodename, {}).get(
|
||||
strruleset = dpp.get(nodename, {}).get(
|
||||
'discovery.passwordrules', {}).get('value', '')
|
||||
wc = self.wc
|
||||
creds = self.configmanager.get_node_attributes(
|
||||
self.nodename, ['secret.hardwaremanagementuser',
|
||||
'secret.hardwaremanagementpassword'], decrypt=True)
|
||||
user, passwd, isdefault = self.get_node_credentials(nodename, creds, 'USERID', 'PASSW0RD')
|
||||
self.set_password_policy()
|
||||
self.set_password_policy(strruleset, wc)
|
||||
if self._atdefaultcreds:
|
||||
if isdefault and self.tmppasswd:
|
||||
raise Exception(
|
||||
'Request to use default credentials, but refused by target after it has been changed to {0}'.format(self.tmppasswd))
|
||||
if not isdefault:
|
||||
self._setup_xcc_account(user, passwd, wc)
|
||||
self._convert_sha256account(user, passwd, wc)
|
||||
|
||||
@@ -30,33 +30,67 @@ pxearchs = {
|
||||
'\x00\x07': 'uefi-x64',
|
||||
'\x00\x09': 'uefi-x64',
|
||||
'\x00\x0b': 'uefi-aarch64',
|
||||
'\x00\x10': 'uefi-httpboot',
|
||||
}
|
||||
|
||||
|
||||
def stringify(value):
|
||||
string = bytes(value)
|
||||
if not isinstance(string, str):
|
||||
string = string.decode('utf8')
|
||||
return string
|
||||
|
||||
def decode_uuid(rawguid):
|
||||
lebytes = struct.unpack_from('<IHH', buffer(rawguid[:8]))
|
||||
bebytes = struct.unpack_from('>HHI', buffer(rawguid[8:]))
|
||||
lebytes = struct.unpack_from('<IHH', rawguid[:8])
|
||||
bebytes = struct.unpack_from('>HHI', rawguid[8:])
|
||||
return '{0:08X}-{1:04X}-{2:04X}-{3:04X}-{4:04X}{5:08X}'.format(
|
||||
lebytes[0], lebytes[1], lebytes[2], bebytes[0], bebytes[1], bebytes[2]).lower()
|
||||
|
||||
|
||||
def _decode_ocp_vivso(rq, idx, size):
|
||||
end = idx + size
|
||||
vivso = {'service-type': 'onie-switch'}
|
||||
while idx < end:
|
||||
if rq[idx] == 3:
|
||||
vivso['machine'] = stringify(rq[idx + 2:idx + 2 + rq[idx + 1]])
|
||||
elif rq[idx] == 4:
|
||||
vivso['arch'] = stringify(rq[idx + 2:idx + 2 + rq[idx + 1]])
|
||||
elif rq[idx] == 5:
|
||||
vivso['revision'] = stringify(rq[idx + 2:idx + 2 + rq[idx + 1]])
|
||||
idx += rq[idx + 1] + 2
|
||||
return '', None, vivso
|
||||
|
||||
|
||||
def find_info_in_options(rq, optidx):
|
||||
uuid = None
|
||||
arch = None
|
||||
vivso = None
|
||||
ztpurlrequested = False
|
||||
iscumulus = False
|
||||
try:
|
||||
while uuid is None or arch is None:
|
||||
if rq[optidx] == 53: # DHCP message type
|
||||
# we want only length 1 and only discover (type 1)
|
||||
if rq[optidx + 1] != 1 or rq[optidx + 2] != 1:
|
||||
return uuid, arch
|
||||
return uuid, arch, vivso
|
||||
optidx += 3
|
||||
elif rq[optidx] == 55:
|
||||
if 239 in rq[optidx + 2:optidx + 2 + rq[optidx + 1]]:
|
||||
ztpurlrequested = True
|
||||
optidx += rq[optidx + 1] + 2
|
||||
elif rq[optidx] == 60:
|
||||
vci = stringify(rq[optidx + 2:optidx + 2 + rq[optidx + 1]])
|
||||
if vci.startswith('cumulus-linux'):
|
||||
iscumulus = True
|
||||
arch = vci.replace('cumulus-linux', '').strip()
|
||||
optidx += rq[optidx + 1] + 2
|
||||
elif rq[optidx] == 97:
|
||||
if rq[optidx + 1] != 17:
|
||||
# 16 bytes of uuid and one reserved byte
|
||||
return uuid, arch
|
||||
return uuid, arch, vivso
|
||||
if rq[optidx + 2] != 0: # the reserved byte should be zero,
|
||||
# anything else would be a new spec that we don't know yet
|
||||
return uuid, arch
|
||||
return uuid, arch, vivso
|
||||
uuid = decode_uuid(rq[optidx + 3:optidx + 19])
|
||||
optidx += 19
|
||||
elif rq[optidx] == 93:
|
||||
@@ -66,11 +100,20 @@ def find_info_in_options(rq, optidx):
|
||||
if archraw in pxearchs:
|
||||
arch = pxearchs[archraw]
|
||||
optidx += 4
|
||||
elif rq[optidx] == 125:
|
||||
#vivso = rq[optidx + 2:optidx + 2 + rq[optidx + 1]]
|
||||
if rq[optidx + 2:optidx + 6] == b'\x00\x00\xa6\x7f': # OCP
|
||||
return _decode_ocp_vivso(rq, optidx + 7, rq[optidx + 6])
|
||||
optidx += rq[optidx + 1] + 2
|
||||
else:
|
||||
optidx += rq[optidx + 1] + 2
|
||||
except IndexError:
|
||||
return uuid, arch
|
||||
return uuid, arch
|
||||
pass
|
||||
if not vivso and iscumulus and ztpurlrequested:
|
||||
if not uuid:
|
||||
uuid = ''
|
||||
vivso = {'service-type': 'cumulus-switch', 'arch': arch}
|
||||
return uuid, arch, vivso
|
||||
|
||||
def snoop(handler, protocol=None):
|
||||
#TODO(jjohnson2): ipv6 socket and multicast for DHCPv6, should that be
|
||||
@@ -101,7 +144,14 @@ def snoop(handler, protocol=None):
|
||||
optidx = rq.index(b'\x63\x82\x53\x63') + 4
|
||||
except ValueError:
|
||||
continue
|
||||
uuid, arch = find_info_in_options(rq, optidx)
|
||||
uuid, arch, vivso = find_info_in_options(rq, optidx)
|
||||
if vivso:
|
||||
# info['modelnumber'] = info['attributes']['enclosure-machinetype-model'][0]
|
||||
handler({'hwaddr': netaddr, 'uuid': uuid,
|
||||
'architecture': vivso.get('arch', ''),
|
||||
'services': (vivso['service-type'],),
|
||||
'attributes': {'enclosure-machinetype-model': [vivso.get('machine', '')]}})
|
||||
continue
|
||||
if uuid is None:
|
||||
continue
|
||||
# We will fill out service to have something to byte into,
|
||||
|
||||
@@ -84,6 +84,8 @@ def _parse_SrvRply(parsed):
|
||||
:return:
|
||||
"""
|
||||
payload = parsed['payload']
|
||||
if len(payload) < 4:
|
||||
return
|
||||
ecode, ucount = struct.unpack('!HH', bytes(payload[0:4]))
|
||||
if ecode:
|
||||
parsed['errorcode'] = ecode
|
||||
@@ -234,40 +236,42 @@ def _find_srvtype(net, net4, srvtype, addresses, xid):
|
||||
|
||||
|
||||
def _grab_rsps(socks, rsps, interval, xidmap):
|
||||
r, _, _ = select.select(socks, (), (), interval)
|
||||
r = None
|
||||
res = select.select(socks, (), (), interval)
|
||||
if res:
|
||||
r = res[0]
|
||||
while r:
|
||||
for s in r:
|
||||
(rsp, peer) = s.recvfrom(9000)
|
||||
neighutil.refresh_neigh()
|
||||
_parse_slp_packet(rsp, peer, rsps, xidmap)
|
||||
r, _, _ = select.select(socks, (), (), interval)
|
||||
res = select.select(socks, (), (), interval)
|
||||
if not res:
|
||||
r = None
|
||||
else:
|
||||
r = res[0]
|
||||
|
||||
|
||||
|
||||
def _parse_attrlist(attrstr):
|
||||
attribs = {}
|
||||
previousattrlen = None
|
||||
attrstr = util.stringify(attrstr)
|
||||
while attrstr:
|
||||
if len(attrstr) == previousattrlen:
|
||||
raise Exception('Looping in attrstr parsing')
|
||||
previousattrlen = len(attrstr)
|
||||
if attrstr[0] == '(':
|
||||
if b')' not in attrstr:
|
||||
if ')' not in attrstr:
|
||||
attribs['INCOMPLETE'] = True
|
||||
return attribs
|
||||
currattr = attrstr[1:attrstr.index(b')')]
|
||||
if b'=' not in currattr: # Not allegedly kosher, but still..
|
||||
currattr = currattr.decode('utf-8')
|
||||
currattr = attrstr[1:attrstr.index(')')]
|
||||
if '=' not in currattr: # Not allegedly kosher, but still..
|
||||
attribs[currattr] = None
|
||||
else:
|
||||
attrname, attrval = currattr.split('=', 1)
|
||||
attrname = attrname.decode('utf-8')
|
||||
attribs[attrname] = []
|
||||
for val in attrval.split(b','):
|
||||
try:
|
||||
val = val.decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
val = '*DECODEERROR*'
|
||||
for val in attrval.split(','):
|
||||
if val[:3] == '\\FF': # we should make this bytes
|
||||
finalval = bytearray([])
|
||||
for bnum in attrval[3:].split('\\'):
|
||||
@@ -287,12 +291,12 @@ def _parse_attrlist(attrstr):
|
||||
).lower()
|
||||
attribs[attrname].append(val)
|
||||
attrstr = attrstr[attrstr.index(')'):]
|
||||
elif attrstr[0] == b','[0]:
|
||||
elif attrstr[0] == ','[0]:
|
||||
attrstr = attrstr[1:]
|
||||
elif b',' in attrstr:
|
||||
currattr = attrstr[:attrstr.index(b',')]
|
||||
elif ',' in attrstr:
|
||||
currattr = attrstr[:attrstr.index(',')]
|
||||
attribs[currattr] = None
|
||||
attrstr = attrstr[attrstr.index(b','):]
|
||||
attrstr = attrstr[attrstr.index(','):]
|
||||
else:
|
||||
currattr = attrstr
|
||||
attribs[currattr] = None
|
||||
@@ -489,11 +493,13 @@ def snoop(handler, protocol=None):
|
||||
_add_attributes(peerbymacaddress[mac])
|
||||
peerbymacaddress[mac]['hwaddr'] = mac
|
||||
peerbymacaddress[mac]['protocol'] = protocol
|
||||
if 'service:ipmi' in peerbymacaddress[mac]['services']:
|
||||
if 'service:ipmi//Athena:623' in peerbymacaddress[mac].get('urls', ()):
|
||||
peerbymacaddress[mac]['services'] = ['service:thinkagile-storage']
|
||||
else:
|
||||
for srvurl in peerbymacaddress[mac].get('urls', ()):
|
||||
if len(srvurl) > 4:
|
||||
srvurl = srvurl[:-3]
|
||||
if srvurl.endswith('://Athena:'):
|
||||
continue
|
||||
if 'service:ipmi' in peerbymacaddress[mac]['services']:
|
||||
continue
|
||||
if 'service:lightttpd' in peerbymacaddress[mac]['services']:
|
||||
currinf = peerbymacaddress[mac]
|
||||
curratt = currinf.get('attributes', {})
|
||||
@@ -564,11 +570,13 @@ def scan(srvtypes=_slp_services, addresses=None, localonly=False):
|
||||
_grab_rsps((net, net4), rsps, 1, xidmap)
|
||||
# now to analyze and flesh out the responses
|
||||
for id in rsps:
|
||||
if 'service:ipmi' in rsps[id]['services']:
|
||||
if 'service:ipmi://Athena:623' in rsps[id]['urls']:
|
||||
rsps[id]['services'] = ['service:thinkagile-storage']
|
||||
else:
|
||||
for srvurl in rsps[id].get('urls', ()):
|
||||
if len(srvurl) > 4:
|
||||
srvurl = srvurl[:-3]
|
||||
if srvurl.endswith('://Athena:'):
|
||||
continue
|
||||
if 'service:ipmi' in rsps[id]['services']:
|
||||
continue
|
||||
if localonly:
|
||||
for addr in rsps[id]['addresses']:
|
||||
if 'fe80' in addr[0]:
|
||||
|
||||
@@ -32,6 +32,10 @@ import confluent.neighutil as neighutil
|
||||
import confluent.util as util
|
||||
import eventlet.green.select as select
|
||||
import eventlet.green.socket as socket
|
||||
try:
|
||||
from eventlet.green.urllib.request import urlopen
|
||||
except (ImportError, AssertionError):
|
||||
from eventlet.green.urllib2 import urlopen
|
||||
import struct
|
||||
|
||||
mcastv4addr = '239.255.255.250'
|
||||
@@ -45,6 +49,20 @@ smsg = ('M-SEARCH * HTTP/1.1\r\n'
|
||||
'MX: 3\r\n\r\n')
|
||||
|
||||
|
||||
def active_scan(handler, protocol=None):
|
||||
known_peers = set([])
|
||||
for scanned in scan(['urn:dmtf-org:service:redfish-rest:1']):
|
||||
for addr in scanned['addresses']:
|
||||
ip = addr[0].partition('%')[0] # discard scope if present
|
||||
if ip not in neighutil.neightable:
|
||||
continue
|
||||
if addr in known_peers:
|
||||
break
|
||||
known_peers.add(addr)
|
||||
else:
|
||||
scanned['protocol'] = protocol
|
||||
handler(scanned)
|
||||
|
||||
def scan(services, target=None):
|
||||
for service in services:
|
||||
for rply in _find_service(service, target):
|
||||
@@ -109,11 +127,11 @@ def snoop(handler, byehandler=None):
|
||||
known_peers.add(peer)
|
||||
newmacs.add(mac)
|
||||
if mac in peerbymacaddress:
|
||||
peerbymacaddress[mac]['peers'].append(peer)
|
||||
peerbymacaddress[mac]['addresses'].append(peer)
|
||||
else:
|
||||
peerbymacaddress[mac] = {
|
||||
'hwaddr': mac,
|
||||
'peers': [peer],
|
||||
'addresses': [peer],
|
||||
}
|
||||
peerdata = peerbymacaddress[mac]
|
||||
for headline in rsp[1:]:
|
||||
@@ -185,7 +203,12 @@ def _find_service(service, target):
|
||||
timeout = 0
|
||||
r, _, _ = select.select((net4, net6), (), (), timeout)
|
||||
for nid in peerdata:
|
||||
yield peerdata[nid]
|
||||
for url in peerdata[nid].get('urls', ()):
|
||||
if url.endswith('/desc.tmpl'):
|
||||
info = urlopen(url).read()
|
||||
if '<friendlyName>Athena</friendlyName>' in info:
|
||||
peerdata[nid]['services'] = ['service:thinkagile-storage']
|
||||
yield peerdata[nid]
|
||||
|
||||
|
||||
def _parse_ssdp(peer, rsp, peerdata):
|
||||
@@ -203,11 +226,11 @@ 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)
|
||||
if peer not in peerdatum['addresses']:
|
||||
peerdatum['addresses'].append(peer)
|
||||
else:
|
||||
peerdatum = {
|
||||
'peers': [peer],
|
||||
'addresses': [peer],
|
||||
'hwaddr': mac,
|
||||
}
|
||||
peerdata[nid] = peerdatum
|
||||
|
||||
@@ -17,7 +17,17 @@
|
||||
|
||||
import base64
|
||||
import json
|
||||
import msgpack
|
||||
|
||||
def deserialize_exc(msg):
|
||||
excd = msgpack.unpackb(msg, raw=False)
|
||||
if excd[0] == 'Exception':
|
||||
return Exception(excd[1])
|
||||
if excd[0] not in globals():
|
||||
return Exception('Cannot deserialize: {0}'.format(repr(excd)))
|
||||
if not issubclass(excd[0], ConfluentException):
|
||||
return Exception('Cannot deserialize: {0}'.format(repr(excd)))
|
||||
return globals(excd[0])(*excd[1])
|
||||
|
||||
class ConfluentException(Exception):
|
||||
apierrorcode = 500
|
||||
@@ -27,6 +37,10 @@ class ConfluentException(Exception):
|
||||
errstr = ' - '.join((self._apierrorstr, str(self)))
|
||||
return json.dumps({'error': errstr })
|
||||
|
||||
def serialize(self):
|
||||
return msgpack.packb([self.__class__.__name__, [str(self)]],
|
||||
use_bin_type=False)
|
||||
|
||||
@property
|
||||
def apierrorstr(self):
|
||||
if str(self):
|
||||
@@ -104,16 +118,24 @@ class PubkeyInvalid(ConfluentException):
|
||||
|
||||
def __init__(self, text, certificate, fingerprint, attribname, event):
|
||||
super(PubkeyInvalid, self).__init__(self, text)
|
||||
self.myargs = (text, certificate, fingerprint, attribname, event)
|
||||
self.fingerprint = fingerprint
|
||||
self.attrname = attribname
|
||||
self.message = text
|
||||
certtxt = base64.b64encode(certificate)
|
||||
if not isinstance(certtxt, str):
|
||||
certtxt = certtxt.decode('utf8')
|
||||
bodydata = {'message': text,
|
||||
'event': event,
|
||||
'fingerprint': fingerprint,
|
||||
'fingerprintfield': attribname,
|
||||
'certificate': base64.b64encode(certificate)}
|
||||
'certificate': certtxt}
|
||||
self.errorbody = json.dumps(bodydata)
|
||||
|
||||
def serialize(self):
|
||||
return msgpack.packb([self.__class__.__name__, self.myargs],
|
||||
use_bin_type=False)
|
||||
|
||||
def get_error_body(self):
|
||||
return self.errorbody
|
||||
|
||||
|
||||
@@ -36,20 +36,40 @@ _tracelog = None
|
||||
|
||||
def execupdate(handler, filename, updateobj, type, owner, node):
|
||||
global _tracelog
|
||||
if type != 'ffdc' and 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':
|
||||
errstr = False
|
||||
if not os.path.exists(filename):
|
||||
errstr = '{0} does not appear to exist on {1}'.format(
|
||||
filename, socket.gethostname())
|
||||
elif not os.access(filename, os.R_OK):
|
||||
errstr = '{0} is not readable by confluent on {1} (ensure confluent user or group can access file and parent directories)'.format(
|
||||
filename, socket.gethostname())
|
||||
if errstr:
|
||||
updateobj.handle_progress({'phase': 'error', 'progress': 0.0,
|
||||
'detail': errstr})
|
||||
return
|
||||
if type == 'ffdc' and os.path.isdir(filename):
|
||||
filename += '/' + node + '.svcdata'
|
||||
filename += '/' + node
|
||||
if 'type' == 'ffdc':
|
||||
errstr = False
|
||||
if os.path.exists(filename):
|
||||
errstr = '{0} already exists on {1}, cannot overwrite'.format(
|
||||
filename, socket.gethostname())
|
||||
elif not os.access(os.path.dirname(filename), os.W_OK):
|
||||
errstr = '{0} directory not writable by confluent user/group on {1}, check the directory and parent directory ownership and permissions'.format(filename, socket.gethostname())
|
||||
if errstr:
|
||||
updateobj.handle_progress({'phase': 'error', 'progress': 0.0,
|
||||
'detail': errstr})
|
||||
return
|
||||
try:
|
||||
if type == 'firmware':
|
||||
completion = handler(filename, progress=updateobj.handle_progress,
|
||||
bank=updateobj.bank)
|
||||
else:
|
||||
completion = handler(filename, progress=updateobj.handle_progress)
|
||||
if type == 'ffdc' and completion:
|
||||
filename = completion
|
||||
completion = None
|
||||
if completion is None:
|
||||
completion = 'complete'
|
||||
if owner:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2014 IBM Corporation
|
||||
# Copyright 2015-2016 Lenovo
|
||||
# Copyright 2015-2019 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -80,7 +80,7 @@ def group_creation_resources():
|
||||
yield confluent.messages.ListAttributes(kv={'nodes': []},
|
||||
desc='Nodes to add to the group'
|
||||
).html() + '<br>\n'
|
||||
for attr in sorted(attribs.node.iterkeys()):
|
||||
for attr in sorted(attribs.node):
|
||||
if attr == 'groups':
|
||||
continue
|
||||
if attr.startswith("secret."):
|
||||
@@ -101,7 +101,7 @@ def group_creation_resources():
|
||||
def node_creation_resources():
|
||||
yield confluent.messages.Attributes(
|
||||
kv={'name': None}, desc="Name of the node").html() + '<br>'
|
||||
for attr in sorted(attribs.node.iterkeys()):
|
||||
for attr in sorted(attribs.node):
|
||||
if attr.startswith("secret."):
|
||||
yield confluent.messages.CryptedAttributes(
|
||||
kv={attr: None},
|
||||
@@ -132,7 +132,7 @@ def user_creation_resources():
|
||||
'description': (''),
|
||||
},
|
||||
}
|
||||
for attr in sorted(credential.iterkeys()):
|
||||
for attr in sorted(credential):
|
||||
if attr == "password":
|
||||
yield confluent.messages.CryptedAttributes(
|
||||
kv={attr: None},
|
||||
@@ -182,7 +182,7 @@ def _get_query_dict(env, reqbody, reqtype):
|
||||
if reqbody is not None:
|
||||
if "application/x-www-form-urlencoded" in reqtype:
|
||||
pbody = urlparse.parse_qs(reqbody, True)
|
||||
for ky in pbody.iterkeys():
|
||||
for ky in pbody:
|
||||
if len(pbody[ky]) > 1: # e.g. REST explorer
|
||||
na = [i for i in pbody[ky] if i != '']
|
||||
qdict[ky] = na
|
||||
@@ -190,7 +190,7 @@ def _get_query_dict(env, reqbody, reqtype):
|
||||
qdict[ky] = pbody[ky][0]
|
||||
elif 'application/json' in reqtype:
|
||||
pbody = json.loads(reqbody)
|
||||
for key in pbody.iterkeys():
|
||||
for key in pbody:
|
||||
qdict[key] = pbody[key]
|
||||
if 'restexplorerhonorkey' in qdict:
|
||||
nqdict = {}
|
||||
@@ -311,7 +311,7 @@ def _authorize_request(env, operation):
|
||||
return {'code': 401}
|
||||
return ('logout',)
|
||||
name, passphrase = base64.b64decode(
|
||||
env['HTTP_AUTHORIZATION'].replace('Basic ', '')).split(':', 1)
|
||||
env['HTTP_AUTHORIZATION'].replace('Basic ', '')).split(b':', 1)
|
||||
authdata = auth.check_user_passphrase(name, passphrase, operation=operation, element=element)
|
||||
if authdata is False:
|
||||
return {'code': 403}
|
||||
@@ -325,14 +325,14 @@ def _authorize_request(env, operation):
|
||||
'inflight': set([])}
|
||||
if 'HTTP_CONFLUENTAUTHTOKEN' in env:
|
||||
httpsessions[sessid]['csrftoken'] = util.randomstring(32)
|
||||
cookie['confluentsessionid'] = sessid
|
||||
cookie['confluentsessionid'] = util.stringify(sessid)
|
||||
cookie['confluentsessionid']['secure'] = 1
|
||||
cookie['confluentsessionid']['httponly'] = 1
|
||||
cookie['confluentsessionid']['path'] = '/'
|
||||
skiplog = _should_skip_authlog(env)
|
||||
if authdata:
|
||||
auditmsg = {
|
||||
'user': name,
|
||||
'user': util.stringify(name),
|
||||
'operation': operation,
|
||||
'target': env['PATH_INFO'],
|
||||
}
|
||||
@@ -344,7 +344,7 @@ def _authorize_request(env, operation):
|
||||
if authdata[3] is not None:
|
||||
auditmsg['tenant'] = authdata[3]
|
||||
authinfo['tenant'] = authdata[3]
|
||||
auditmsg['user'] = authdata[2]
|
||||
auditmsg['user'] = util.stringify(authdata[2])
|
||||
if sessid is not None:
|
||||
authinfo['sessionid'] = sessid
|
||||
if not skiplog:
|
||||
@@ -452,6 +452,8 @@ def resourcehandler_backend(env, start_response):
|
||||
httpsessions[authorized['sessionid']]['inflight'])):
|
||||
pagecontent += rsp
|
||||
start_response("200 OK", headers)
|
||||
if not isinstance(pagecontent, bytes):
|
||||
pagecontent = pagecontent.encode('utf-8')
|
||||
yield pagecontent
|
||||
return
|
||||
except exc.ConfluentException as e:
|
||||
@@ -497,7 +499,7 @@ def resourcehandler_backend(env, start_response):
|
||||
auditmsg = {
|
||||
'operation': 'start',
|
||||
'target': env['PATH_INFO'],
|
||||
'user': authorized['username'],
|
||||
'user': util.stringify(authorized['username']),
|
||||
}
|
||||
if 'tenant' in authorized:
|
||||
auditmsg['tenant'] = authorized['tenant']
|
||||
@@ -509,10 +511,10 @@ def resourcehandler_backend(env, start_response):
|
||||
width = querydict.get('width', 80)
|
||||
height = querydict.get('height', 24)
|
||||
datacallback = None
|
||||
async = None
|
||||
asynchdl = None
|
||||
if 'HTTP_CONFLUENTASYNCID' in env:
|
||||
async = confluent.asynchttp.get_async(env, querydict)
|
||||
termrel = async.set_term_relation(env)
|
||||
asynchdl = confluent.asynchttp.get_async(env, querydict)
|
||||
termrel = asynchdl.set_term_relation(env)
|
||||
datacallback = termrel.got_data
|
||||
try:
|
||||
if shellsession:
|
||||
@@ -535,8 +537,8 @@ def resourcehandler_backend(env, start_response):
|
||||
start_response("500 Internal Server Error", headers)
|
||||
return
|
||||
sessid = _assign_consessionid(consession)
|
||||
if async:
|
||||
async.add_console_session(sessid)
|
||||
if asynchdl:
|
||||
asynchdl.add_console_session(sessid)
|
||||
start_response('200 OK', headers)
|
||||
yield '{"session":"%s","data":""}' % sessid
|
||||
return
|
||||
@@ -632,6 +634,7 @@ def resourcehandler_backend(env, start_response):
|
||||
sessinfo = {'username': authorized['username']}
|
||||
if 'authtoken' in authorized:
|
||||
sessinfo['authtoken'] = authorized['authtoken']
|
||||
tlvdata.unicode_dictvalues(sessinfo)
|
||||
yield json.dumps(sessinfo)
|
||||
return
|
||||
resource = '.' + url[url.rindex('/'):]
|
||||
@@ -653,6 +656,8 @@ def resourcehandler_backend(env, start_response):
|
||||
for datum in _assemble_json(hdlr, resource, url, extension):
|
||||
pagecontent += datum
|
||||
start_response('200 OK', headers)
|
||||
if not isinstance(pagecontent, bytes):
|
||||
pagecontent = pagecontent.encode('utf-8')
|
||||
yield pagecontent
|
||||
except exc.ConfluentException as e:
|
||||
if ((not isinstance(e, exc.LockedCredentials)) and
|
||||
@@ -737,7 +742,7 @@ def _assemble_json(responses, resource=None, url=None, extension=None):
|
||||
for rsp in responses:
|
||||
if isinstance(rsp, confluent.messages.LinkRelation):
|
||||
haldata = rsp.raw()
|
||||
for hk in haldata.iterkeys():
|
||||
for hk in haldata:
|
||||
if 'href' in haldata[hk]:
|
||||
if isinstance(haldata[hk]['href'], int):
|
||||
haldata[hk]['href'] = str(haldata[hk]['href'])
|
||||
@@ -753,7 +758,7 @@ def _assemble_json(responses, resource=None, url=None, extension=None):
|
||||
links[hk] = haldata[hk]
|
||||
else:
|
||||
rsp = rsp.raw()
|
||||
for dk in rsp.iterkeys():
|
||||
for dk in rsp:
|
||||
if dk in rspdata:
|
||||
if isinstance(rspdata[dk], list):
|
||||
if isinstance(rsp[dk], list):
|
||||
@@ -772,8 +777,8 @@ def _assemble_json(responses, resource=None, url=None, extension=None):
|
||||
rspdata[dk] = rsp[dk]
|
||||
rspdata["_links"] = links
|
||||
tlvdata.unicode_dictvalues(rspdata)
|
||||
yield json.dumps(
|
||||
rspdata, sort_keys=True, indent=4, ensure_ascii=False).encode('utf-8')
|
||||
yield util.stringify(json.dumps(
|
||||
rspdata, sort_keys=True, indent=4, ensure_ascii=False).encode('utf-8'))
|
||||
|
||||
|
||||
def serve(bind_host, bind_port):
|
||||
|
||||
@@ -600,7 +600,11 @@ class Logger(object):
|
||||
if ltype == 2:
|
||||
textrecord = data
|
||||
else:
|
||||
textrecord = textdate + data + ']'
|
||||
if not isinstance(textdate, bytes):
|
||||
textdate = textdate.encode('utf-8')
|
||||
if not isinstance(data, bytes):
|
||||
data = data.encode('utf-8')
|
||||
textrecord = textdate + data + b']'
|
||||
else:
|
||||
textrecord = textdate + data
|
||||
if not textrecord.endswith('\n'):
|
||||
@@ -798,4 +802,4 @@ def logtrace():
|
||||
if tracelog is None:
|
||||
tracelog = Logger('trace', buffered=False)
|
||||
tracelog.log(traceback.format_exc(), ltype=DataTypes.event,
|
||||
event=Events.stacktrace)
|
||||
event=Events.stacktrace)
|
||||
|
||||
@@ -77,13 +77,16 @@ def _daemonize():
|
||||
print('confluent server starting as pid {0}'.format(thispid))
|
||||
os._exit(0)
|
||||
os.closerange(0, 2)
|
||||
os.umask(63)
|
||||
os.open(os.devnull, os.O_RDWR)
|
||||
os.dup2(0, 1)
|
||||
os.dup2(0, 2)
|
||||
log.daemonized = True
|
||||
|
||||
|
||||
def _redirectoutput():
|
||||
os.umask(63)
|
||||
sys.stdout = log.Logger('stdout', buffered=False)
|
||||
sys.stderr = log.Logger('stderr', buffered=False)
|
||||
log.daemonized = True
|
||||
|
||||
|
||||
def _updatepidfile():
|
||||
@@ -206,7 +209,7 @@ def setlimits():
|
||||
pass
|
||||
|
||||
|
||||
def run():
|
||||
def run(args):
|
||||
setlimits()
|
||||
try:
|
||||
signal.signal(signal.SIGUSR1, dumptrace)
|
||||
@@ -232,7 +235,10 @@ def run():
|
||||
except (OSError, IOError) as e:
|
||||
print(repr(e))
|
||||
sys.exit(1)
|
||||
_daemonize()
|
||||
if '-f' not in args:
|
||||
_daemonize()
|
||||
if '-o' not in args:
|
||||
_redirectoutput()
|
||||
if havefcntl:
|
||||
_updatepidfile()
|
||||
signal.signal(signal.SIGINT, terminate)
|
||||
|
||||
@@ -23,6 +23,8 @@ import confluent.config.configmanager as cfm
|
||||
import confluent.config.conf as cfgfile
|
||||
from copy import deepcopy
|
||||
from datetime import datetime
|
||||
import confluent.util as util
|
||||
import msgpack
|
||||
import json
|
||||
|
||||
try:
|
||||
@@ -59,7 +61,9 @@ def _htmlify_structure(indict):
|
||||
if isinstance(indict, dict):
|
||||
for key in sorted(indict):
|
||||
ret += "<li>{0}: ".format(key)
|
||||
if type(indict[key]) in (bytes, unicode, float, int):
|
||||
if type(indict[key]) in (bytes, unicode):
|
||||
ret += util.stringify(indict[key])
|
||||
if type(indict[key]) in (float, int):
|
||||
ret += str(indict[key])
|
||||
elif isinstance(indict[key], datetime):
|
||||
ret += indict[key].strftime('%Y-%m-%dT%H:%M:%S')
|
||||
@@ -73,7 +77,7 @@ def _htmlify_structure(indict):
|
||||
if datum is None:
|
||||
nd.append('')
|
||||
else:
|
||||
nd.append(datum)
|
||||
nd.append(util.stringify(datum))
|
||||
ret += ",".join(nd)
|
||||
else:
|
||||
for v in indict:
|
||||
@@ -81,6 +85,13 @@ def _htmlify_structure(indict):
|
||||
return ret + '</ul>'
|
||||
|
||||
|
||||
def msg_deserialize(packed):
|
||||
m = msgpack.unpackb(packed, raw=False)
|
||||
cls = globals()[m[0]]
|
||||
if issubclass(cls, ConfluentMessage) or issubclass(cls, ConfluentNodeError):
|
||||
return cls(*m[1:])
|
||||
raise Exception("Unknown shenanigans")
|
||||
|
||||
class ConfluentMessage(object):
|
||||
apicode = 200
|
||||
readonly = False
|
||||
@@ -102,6 +113,15 @@ class ConfluentMessage(object):
|
||||
jsonsnippet = json.dumps(datasource, sort_keys=True, separators=(',', ':'))[1:-1]
|
||||
return jsonsnippet
|
||||
|
||||
def serialize(self):
|
||||
msg = [self.__class__.__name__]
|
||||
msg.extend(self.myargs)
|
||||
return msgpack.packb(msg, use_bin_type=False)
|
||||
|
||||
@classmethod
|
||||
def deserialize(cls, data):
|
||||
return cls(*data)
|
||||
|
||||
def raw(self):
|
||||
"""Return pythonic representation of the response.
|
||||
|
||||
@@ -126,15 +146,16 @@ class ConfluentMessage(object):
|
||||
return self._generic_html_value(self.kvpairs)
|
||||
if not self.stripped:
|
||||
htmlout = ''
|
||||
for node in self.kvpairs.iterkeys():
|
||||
for node in self.kvpairs:
|
||||
htmlout += '{0}:{1}\n'.format(
|
||||
node, self._generic_html_value(self.kvpairs[node]))
|
||||
return htmlout
|
||||
|
||||
def _generic_html_value(self, pairs):
|
||||
snippet = ""
|
||||
for key in pairs.iterkeys():
|
||||
for key in pairs:
|
||||
val = pairs[key]
|
||||
key = util.stringify(key)
|
||||
value = self.defaultvalue
|
||||
if isinstance(val, dict) and 'type' in val:
|
||||
valtype = val['type']
|
||||
@@ -207,6 +228,15 @@ class ConfluentNodeError(object):
|
||||
self.node = node
|
||||
self.error = errorstr
|
||||
|
||||
def serialize(self):
|
||||
return msgpack.packb(
|
||||
[self.__class__.__name__, self.node, self.error],
|
||||
use_bin_type=False)
|
||||
|
||||
@classmethod
|
||||
def deserialize(cls, data):
|
||||
return cls(*data)
|
||||
|
||||
def raw(self):
|
||||
return {'databynode': {self.node: {'errorcode': self.apicode,
|
||||
'error': self.error}}}
|
||||
@@ -217,7 +247,7 @@ class ConfluentNodeError(object):
|
||||
def strip_node(self, node):
|
||||
# NOTE(jjohnson2): For single node errors, raise exception to
|
||||
# trigger what a developer of that medium would expect
|
||||
raise Exception(self.error)
|
||||
raise Exception('{0}: {1}'.format(self.node, self.error))
|
||||
|
||||
|
||||
class ConfluentResourceUnavailable(ConfluentNodeError):
|
||||
@@ -256,9 +286,9 @@ class ConfluentTargetNotFound(ConfluentNodeError):
|
||||
|
||||
class ConfluentTargetInvalidCredentials(ConfluentNodeError):
|
||||
apicode = 502
|
||||
def __init__(self, node):
|
||||
def __init__(self, node, errstr='bad credentials'):
|
||||
self.node = node
|
||||
self.error = 'bad credentials'
|
||||
self.error = errstr
|
||||
|
||||
def strip_node(self, node):
|
||||
raise exc.TargetEndpointBadCredentials
|
||||
@@ -267,6 +297,7 @@ class ConfluentTargetInvalidCredentials(ConfluentNodeError):
|
||||
class DeletedResource(ConfluentMessage):
|
||||
notnode = True
|
||||
def __init__(self, resource):
|
||||
self.myargs = [resource]
|
||||
self.kvpairs = {'deleted': resource}
|
||||
|
||||
def strip_node(self, node):
|
||||
@@ -278,6 +309,7 @@ class CreatedResource(ConfluentMessage):
|
||||
readonly = True
|
||||
|
||||
def __init__(self, resource):
|
||||
self.myargs = [resource]
|
||||
self.kvpairs = {'created': resource}
|
||||
|
||||
def strip_node(self, node):
|
||||
@@ -289,6 +321,7 @@ class RenamedResource(ConfluentMessage):
|
||||
readonly = True
|
||||
|
||||
def __init__(self, oldname, newname):
|
||||
self.myargs = (oldname, newname)
|
||||
self.kvpairs = {'oldname': oldname, 'newname': newname}
|
||||
|
||||
def strip_node(self, node):
|
||||
@@ -297,6 +330,7 @@ class RenamedResource(ConfluentMessage):
|
||||
|
||||
class RenamedNode(ConfluentMessage):
|
||||
def __init__(self, name, rename):
|
||||
self.myargs = (name, rename)
|
||||
self.desc = 'New Name'
|
||||
kv = {'rename': {'value': rename}}
|
||||
self.kvpairs = {name: kv}
|
||||
@@ -307,13 +341,16 @@ class AssignedResource(ConfluentMessage):
|
||||
readonly = True
|
||||
|
||||
def __init__(self, resource):
|
||||
self.myargs = [resource]
|
||||
self.kvpairs = {'assigned': resource}
|
||||
|
||||
|
||||
class ConfluentChoiceMessage(ConfluentMessage):
|
||||
valid_values = set()
|
||||
valid_paramset = {}
|
||||
|
||||
def __init__(self, node, state):
|
||||
self.myargs = (node, state)
|
||||
self.stripped = False
|
||||
self.kvpairs = {
|
||||
node: {
|
||||
@@ -326,15 +363,16 @@ class ConfluentChoiceMessage(ConfluentMessage):
|
||||
return self._create_option(self.kvpairs)
|
||||
else:
|
||||
htmlout = ''
|
||||
for node in self.kvpairs.iterkeys():
|
||||
for node in self.kvpairs:
|
||||
htmlout += '{0}:{1}\n'.format(
|
||||
node, self._create_option(self.kvpairs[node]))
|
||||
return htmlout
|
||||
|
||||
def _create_option(self, pairdata):
|
||||
snippet = ''
|
||||
for key in pairdata.iterkeys():
|
||||
for key in pairdata:
|
||||
val = pairdata[key]
|
||||
key = util.stringify(key)
|
||||
snippet += key + ':<select name="%s">' % key
|
||||
valid_values = self.valid_values
|
||||
if key in self.valid_paramset:
|
||||
@@ -386,6 +424,7 @@ class LinkRelation(ConfluentMessage):
|
||||
|
||||
class ChildCollection(LinkRelation):
|
||||
def __init__(self, collname, candelete=False):
|
||||
self.myargs = (collname, candelete)
|
||||
self.rel = 'item'
|
||||
self.href = collname
|
||||
self.candelete = candelete
|
||||
@@ -506,9 +545,21 @@ class InputFirmwareUpdate(ConfluentMessage):
|
||||
raise Exception('User requested substitutions, but code is '
|
||||
'written against old api, code must be fixed or '
|
||||
'skip {} expansion')
|
||||
if self.filebynode[node].startswith('/etc/confluent'):
|
||||
raise Exception(
|
||||
'File transfer with /etc/confluent is not supported')
|
||||
if self.filebynode[node].startswith('/var/log/confluent'):
|
||||
raise Exception(
|
||||
'File transfer with /var/log/confluent is not supported')
|
||||
return self._filename
|
||||
|
||||
def nodefile(self, node):
|
||||
if self.filebynode[node].startswith('/etc/confluent'):
|
||||
raise Exception(
|
||||
'File transfer with /etc/confluent is not supported')
|
||||
if self.filebynode[node].startswith('/var/log/confluent'):
|
||||
raise Exception(
|
||||
'File transfer with /var/log/confluent is not supported')
|
||||
return self.filebynode[node]
|
||||
|
||||
class InputMedia(InputFirmwareUpdate):
|
||||
@@ -527,11 +578,15 @@ class DetachMedia(ConfluentMessage):
|
||||
|
||||
|
||||
class Media(ConfluentMessage):
|
||||
def __init__(self, node, media):
|
||||
self.kvpairs = {node: {'name': media.name, 'url': media.url}}
|
||||
def __init__(self, node, media=None, rawmedia=None):
|
||||
if media:
|
||||
rawmedia = {'name': media.name, 'url': media.url}
|
||||
self.myargs = (node, None, rawmedia)
|
||||
self.kvpairs = {node: rawmedia}
|
||||
|
||||
class SavedFile(ConfluentMessage):
|
||||
def __init__(self, node, file):
|
||||
self.myargs = (node, file)
|
||||
self.kvpairs = {node: {'filename': file}}
|
||||
|
||||
class InputAlertData(ConfluentMessage):
|
||||
@@ -1103,6 +1158,7 @@ class BootDevice(ConfluentChoiceMessage):
|
||||
}
|
||||
|
||||
def __init__(self, node, device, bootmode='unspecified', persistent=False):
|
||||
self.myargs = (node, device, bootmode, persistent)
|
||||
if device not in self.valid_values:
|
||||
raise Exception("Invalid boot device argument passed in:" +
|
||||
repr(device))
|
||||
@@ -1201,10 +1257,10 @@ class PowerState(ConfluentChoiceMessage):
|
||||
|
||||
def __init__(self, node, state, oldstate=None):
|
||||
super(PowerState, self).__init__(node, state)
|
||||
self.myargs = (node, state, oldstate)
|
||||
if oldstate is not None:
|
||||
self.kvpairs[node]['oldstate'] = {'value': oldstate}
|
||||
|
||||
|
||||
class BMCReset(ConfluentChoiceMessage):
|
||||
valid_values = set([
|
||||
'reset',
|
||||
@@ -1220,13 +1276,13 @@ class NTPEnabled(ConfluentChoiceMessage):
|
||||
|
||||
def __init__(self, node, enabled):
|
||||
self.stripped = False
|
||||
self.myargs = (node, enabled)
|
||||
self.kvpairs = {
|
||||
node: {
|
||||
'state': {'value': str(enabled)},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class EventCollection(ConfluentMessage):
|
||||
"""A collection of events
|
||||
|
||||
@@ -1246,6 +1302,8 @@ class EventCollection(ConfluentMessage):
|
||||
def __init__(self, events=(), name=None):
|
||||
eventdata = []
|
||||
self.notnode = name is None
|
||||
self.myname = name
|
||||
self.myargs = (eventdata, name)
|
||||
for event in events:
|
||||
entry = {
|
||||
'id': event.get('id', None),
|
||||
@@ -1273,6 +1331,10 @@ class AsyncCompletion(ConfluentMessage):
|
||||
self.stripped = True
|
||||
self.notnode = True
|
||||
|
||||
@classmethod
|
||||
def deserialize(cls):
|
||||
raise Exception("Not supported")
|
||||
|
||||
def raw(self):
|
||||
return {'_requestdone': True}
|
||||
|
||||
@@ -1283,6 +1345,10 @@ class AsyncMessage(ConfluentMessage):
|
||||
self.notnode = True
|
||||
self.msgpair = pair
|
||||
|
||||
@classmethod
|
||||
def deserialize(cls):
|
||||
raise Exception("Not supported")
|
||||
|
||||
def raw(self):
|
||||
rsp = self.msgpair[1]
|
||||
rspdict = None
|
||||
@@ -1314,6 +1380,7 @@ class User(ConfluentMessage):
|
||||
self.desc = 'foo'
|
||||
self.stripped = False
|
||||
self.notnode = name is None
|
||||
self.myargs = (uid, username, privilege_level, name, expiration)
|
||||
kvpairs = {'username': {'value': username},
|
||||
'password': {'value': '', 'type': 'password'},
|
||||
'privilege_level': {'value': privilege_level},
|
||||
@@ -1333,7 +1400,11 @@ class UserCollection(ConfluentMessage):
|
||||
self.notnode = name is None
|
||||
self.desc = 'list of users'
|
||||
userlist = []
|
||||
self.myargs = (userlist, name)
|
||||
for user in users:
|
||||
if 'username' in user: # processing an already translated dict
|
||||
userlist.append(user)
|
||||
continue
|
||||
entry = {
|
||||
'uid': user['uid'],
|
||||
'username': user['name'],
|
||||
@@ -1347,8 +1418,10 @@ class UserCollection(ConfluentMessage):
|
||||
self.kvpairs = {name: {'users': userlist}}
|
||||
|
||||
|
||||
|
||||
class AlertDestination(ConfluentMessage):
|
||||
def __init__(self, ip, acknowledge=False, acknowledge_timeout=None, retries=0, name=None):
|
||||
self.myargs = (ip, acknowledge, acknowledge_timeout, retries, name)
|
||||
self.desc = 'foo'
|
||||
self.stripped = False
|
||||
self.notnode = name is None
|
||||
@@ -1374,6 +1447,8 @@ class InputAlertDestination(ConfluentMessage):
|
||||
self.alertcfg = {}
|
||||
if multinode: # keys are node names
|
||||
for node in inputdata:
|
||||
if not isinstance(inputdata[node], dict):
|
||||
break
|
||||
self.alertcfg[node] = inputdata[node]
|
||||
for key in inputdata[node]:
|
||||
if key not in self.valid_alert_params:
|
||||
@@ -1386,7 +1461,8 @@ class InputAlertDestination(ConfluentMessage):
|
||||
else:
|
||||
self.alertcfg[node][key] = \
|
||||
self.valid_alert_params[key](inputdata[node][key])
|
||||
else:
|
||||
else:
|
||||
return
|
||||
for key in inputdata:
|
||||
if key not in self.valid_alert_params:
|
||||
raise exc.InvalidArgumentException(
|
||||
@@ -1410,7 +1486,11 @@ class SensorReadings(ConfluentMessage):
|
||||
def __init__(self, sensors=(), name=None):
|
||||
readings = []
|
||||
self.notnode = name is None
|
||||
self.myargs = (readings, name)
|
||||
for sensor in sensors:
|
||||
if isinstance(sensor, dict):
|
||||
readings.append(sensor)
|
||||
continue
|
||||
sensordict = {'name': sensor.name}
|
||||
if hasattr(sensor, 'value'):
|
||||
sensordict['value'] = sensor.value
|
||||
@@ -1435,6 +1515,13 @@ class Firmware(ConfluentMessage):
|
||||
readonly = True
|
||||
|
||||
def __init__(self, data, name):
|
||||
for datum in data:
|
||||
for component in datum:
|
||||
for field in datum[component]:
|
||||
tdatum = datum[component]
|
||||
if isinstance(tdatum[field], datetime):
|
||||
tdatum[field] = tdatum[field].strftime('%Y-%m-%dT%H:%M:%S')
|
||||
self.myargs = (data, name)
|
||||
self.notnode = name is None
|
||||
self.desc = 'Firmware information'
|
||||
if self.notnode:
|
||||
@@ -1447,6 +1534,7 @@ class KeyValueData(ConfluentMessage):
|
||||
readonly = True
|
||||
|
||||
def __init__(self, kvdata, name=None):
|
||||
self.myargs = (kvdata, name)
|
||||
self.notnode = name is None
|
||||
if self.notnode:
|
||||
self.kvpairs = kvdata
|
||||
@@ -1456,6 +1544,7 @@ class KeyValueData(ConfluentMessage):
|
||||
class Array(ConfluentMessage):
|
||||
def __init__(self, name, disks=None, raid=None, volumes=None,
|
||||
id=None, capacity=None, available=None):
|
||||
self.myargs = (name, disks, raid, volumes, id, capacity, available)
|
||||
self.kvpairs = {
|
||||
name: {
|
||||
'type': 'array',
|
||||
@@ -1470,6 +1559,7 @@ class Array(ConfluentMessage):
|
||||
|
||||
class Volume(ConfluentMessage):
|
||||
def __init__(self, name, volname, size, state, array, stripsize=None):
|
||||
self.myargs = (name, volname, size, state, array, stripsize)
|
||||
self.kvpairs = {
|
||||
name: {
|
||||
'type': 'volume',
|
||||
@@ -1484,12 +1574,15 @@ class Volume(ConfluentMessage):
|
||||
|
||||
class Disk(ConfluentMessage):
|
||||
valid_states = set([
|
||||
'fault',
|
||||
'jbod',
|
||||
'unconfigured',
|
||||
'hotspare',
|
||||
'rebuilding',
|
||||
'online',
|
||||
])
|
||||
state_aliases = {
|
||||
'unconfigured bad': 'fault',
|
||||
'unconfigured good': 'unconfigured',
|
||||
'global hot spare': 'hotspare',
|
||||
'dedicated hot spare': 'hotspare',
|
||||
@@ -1507,6 +1600,8 @@ class Disk(ConfluentMessage):
|
||||
def __init__(self, name, label=None, description=None,
|
||||
diskid=None, state=None, serial=None, fru=None,
|
||||
array=None):
|
||||
self.myargs = (name, label, description, diskid, state,
|
||||
serial, fru, array)
|
||||
state = self._normalize_state(state)
|
||||
self.kvpairs = {
|
||||
name: {
|
||||
@@ -1528,6 +1623,7 @@ class LEDStatus(ConfluentMessage):
|
||||
readonly = True
|
||||
|
||||
def __init__(self, data, name):
|
||||
self.myargs = (data, name)
|
||||
self.notnode = name is None
|
||||
self.desc = 'led status'
|
||||
|
||||
@@ -1542,6 +1638,7 @@ class NetworkConfiguration(ConfluentMessage):
|
||||
|
||||
def __init__(self, name=None, ipv4addr=None, ipv4gateway=None,
|
||||
ipv4cfgmethod=None, hwaddr=None):
|
||||
self.myargs = (name, ipv4addr, ipv4gateway, ipv4cfgmethod, hwaddr)
|
||||
self.notnode = name is None
|
||||
self.stripped = False
|
||||
|
||||
@@ -1562,6 +1659,7 @@ class HealthSummary(ConfluentMessage):
|
||||
valid_values = valid_health_values
|
||||
|
||||
def __init__(self, health, name=None):
|
||||
self.myargs = (health, name)
|
||||
self.stripped = False
|
||||
self.notnode = name is None
|
||||
if health not in self.valid_values:
|
||||
@@ -1574,6 +1672,7 @@ class HealthSummary(ConfluentMessage):
|
||||
|
||||
class Attributes(ConfluentMessage):
|
||||
def __init__(self, name=None, kv=None, desc=''):
|
||||
self.myargs = (name, kv, desc)
|
||||
self.desc = desc
|
||||
nkv = {}
|
||||
self.notnode = name is None
|
||||
@@ -1594,6 +1693,7 @@ class ConfigSet(Attributes):
|
||||
|
||||
class ListAttributes(ConfluentMessage):
|
||||
def __init__(self, name=None, kv=None, desc=''):
|
||||
self.myargs = (name, kv, desc)
|
||||
self.desc = desc
|
||||
self.notnode = name is None
|
||||
if self.notnode:
|
||||
@@ -1604,6 +1704,7 @@ class ListAttributes(ConfluentMessage):
|
||||
|
||||
class MCI(ConfluentMessage):
|
||||
def __init__(self, name=None, mci=None):
|
||||
self.myargs = (name, mci)
|
||||
self.notnode = name is None
|
||||
self.desc = 'BMC identifier'
|
||||
|
||||
@@ -1616,6 +1717,7 @@ class MCI(ConfluentMessage):
|
||||
|
||||
class Hostname(ConfluentMessage):
|
||||
def __init__(self, name=None, hostname=None):
|
||||
self.myargs = (name, hostname)
|
||||
self.notnode = name is None
|
||||
self.desc = 'BMC hostname'
|
||||
|
||||
@@ -1627,6 +1729,7 @@ class Hostname(ConfluentMessage):
|
||||
|
||||
class DomainName(ConfluentMessage):
|
||||
def __init__(self, name=None, dn=None):
|
||||
self.myargs = (name, dn)
|
||||
self.notnode = name is None
|
||||
self.desc = 'BMC domain name'
|
||||
|
||||
@@ -1641,6 +1744,7 @@ class NTPServers(ConfluentMessage):
|
||||
readonly = True
|
||||
|
||||
def __init__(self, name=None, servers=None):
|
||||
self.myargs = (name, servers)
|
||||
self.notnode = name is None
|
||||
self.desc = 'NTP Server'
|
||||
|
||||
@@ -1655,6 +1759,7 @@ class NTPServers(ConfluentMessage):
|
||||
|
||||
class NTPServer(ConfluentMessage):
|
||||
def __init__(self, name=None, server=None):
|
||||
self.myargs = (name, server)
|
||||
self.notnode = name is None
|
||||
self.desc = 'NTP Server'
|
||||
|
||||
@@ -1671,6 +1776,7 @@ class License(ConfluentMessage):
|
||||
readonly = True
|
||||
|
||||
def __init__(self, name=None, kvm=None, feature=None, state=None):
|
||||
self.myargs = (name, kvm, feature, state)
|
||||
self.notnode = name is None
|
||||
self.desc = 'License'
|
||||
|
||||
@@ -1686,6 +1792,7 @@ class CryptedAttributes(Attributes):
|
||||
defaulttype = 'password'
|
||||
|
||||
def __init__(self, name=None, kv=None, desc=''):
|
||||
self.myargs = (name, kv, desc)
|
||||
# for now, just keep the dictionary keys and discard crypt value
|
||||
self.desc = desc
|
||||
nkv = {}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
# Ultimately, this should use AF_NETLINK, but in the interest of time,
|
||||
# use ip neigh for the moment
|
||||
|
||||
import confluent.util as util
|
||||
import eventlet.green.subprocess as subprocess
|
||||
import os
|
||||
|
||||
@@ -26,7 +27,7 @@ neightime = 0
|
||||
|
||||
import re
|
||||
|
||||
_validmac = re.compile(b'..:..:..:..:..:..')
|
||||
_validmac = re.compile('..:..:..:..:..:..')
|
||||
|
||||
|
||||
def update_neigh():
|
||||
@@ -39,11 +40,12 @@ def update_neigh():
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
(neighdata, err) = ipn.communicate()
|
||||
for entry in neighdata.split(b'\n'):
|
||||
entry = entry.split(b' ')
|
||||
neighdata = util.stringify(neighdata)
|
||||
for entry in neighdata.split('\n'):
|
||||
entry = entry.split(' ')
|
||||
if len(entry) < 5 or not entry[4]:
|
||||
continue
|
||||
if entry[0] in (b'192.168.0.100', b'192.168.70.100', b'192.168.70.125'):
|
||||
if entry[0] in ('192.168.0.100', '192.168.70.100', '192.168.70.125'):
|
||||
# Note that these addresses are common static ip addresses
|
||||
# that are hopelessly ambiguous if there are many
|
||||
# so ignore such entries and move on
|
||||
|
||||
@@ -44,7 +44,7 @@ import eventlet
|
||||
from eventlet.greenpool import GreenPool
|
||||
import eventlet.semaphore
|
||||
import re
|
||||
|
||||
webclient = eventlet.import_patched('pyghmi.util.webclient')
|
||||
# The interesting OIDs are:
|
||||
# lldpLocChassisId - to cross reference (1.0.8802.1.1.2.1.3.2.0)
|
||||
# lldpLocPortId - for cross referencing.. (1.0.8802.1.1.2.1.3.7.1.3)
|
||||
@@ -85,6 +85,7 @@ _neighdata = {}
|
||||
_neighbypeerid = {}
|
||||
_updatelocks = {}
|
||||
_chassisidbyswitch = {}
|
||||
_noaffluent = set([])
|
||||
|
||||
def lenovoname(idx, desc):
|
||||
if desc.isdigit():
|
||||
@@ -125,7 +126,8 @@ def _dump_neighbordatum(info):
|
||||
|
||||
def b64tohex(b64str):
|
||||
bd = base64.b64decode(b64str)
|
||||
return ''.join(['{0:02x}'.format(ord(x)) for x in bd])
|
||||
bd = bytearray(bd)
|
||||
return ''.join(['{0:02x}'.format(x) for x in bd])
|
||||
|
||||
def get_fingerprint(switch, port, configmanager, portmatch):
|
||||
update_switch_data(switch, configmanager)
|
||||
@@ -170,21 +172,58 @@ def _init_lldp(data, iname, idx, idxtoportid, switch):
|
||||
data[iname] = {'port': iname, 'portid': str(idxtoportid[idx]),
|
||||
'chassisid': _chassisidbyswitch[switch]}
|
||||
|
||||
def _extract_neighbor_data_affluent(switch, user, password, cfm, lldpdata):
|
||||
kv = util.TLSCertVerifier(cfm, switch,
|
||||
'pubkeys.tls_hardwaremanager').verify_cert
|
||||
wc = webclient.SecureHTTPConnection(
|
||||
switch, 443, verifycallback=kv, timeout=5)
|
||||
wc.set_basic_credentials(user, password)
|
||||
neighdata = wc.grab_json_response('/affluent/lldp/all')
|
||||
chassisid = neighdata['chassis']['id']
|
||||
_chassisidbyswitch[switch] = chassisid,
|
||||
for record in neighdata['neighbors']:
|
||||
localport = record['localport']
|
||||
peerid = '{0}.{1}'.format(
|
||||
record.get('peerchassisid', '').replace(':', '-').replace('/', '-'),
|
||||
record.get('peerportid', '').replace(':', '-').replace('/', '-'),
|
||||
)
|
||||
portdata = {
|
||||
'verified': True, # It is over TLS after all
|
||||
'peerdescription': record.get('peerdescription', None),
|
||||
'peerchassisid': record['peerchassisid'],
|
||||
'peername': record['peername'],
|
||||
'switch': switch,
|
||||
'chassisid': chassisid,
|
||||
'portid': record['localport'],
|
||||
'peerportid': record['peerportid'],
|
||||
'port': record['localport'],
|
||||
'peerid': peerid,
|
||||
}
|
||||
_neighbypeerid[peerid] = portdata
|
||||
lldpdata[localport] = portdata
|
||||
neighdata[switch] = lldpdata
|
||||
|
||||
|
||||
def _extract_neighbor_data_b(args):
|
||||
"""Build LLDP data about elements connected to switch
|
||||
|
||||
args are carried as a tuple, because of eventlet convenience
|
||||
"""
|
||||
switch, password, user, force = args[:4]
|
||||
switch, password, user, cfm, force = args[:5]
|
||||
vintage = _neighdata.get(switch, {}).get('!!vintage', 0)
|
||||
now = util.monotonic_time()
|
||||
if vintage > (now - 60) and not force:
|
||||
return
|
||||
lldpdata = {'!!vintage': now}
|
||||
try:
|
||||
return _extract_neighbor_data_affluent(switch, user, password, cfm, lldpdata)
|
||||
except Exception:
|
||||
pass
|
||||
conn = snmp.Session(switch, password, user)
|
||||
sid = None
|
||||
lldpdata = {'!!vintage': now}
|
||||
for sysid in conn.walk('1.3.6.1.2.1.1.2'):
|
||||
sid = str(sysid[1][6:])
|
||||
_noaffluent.add(switch)
|
||||
idxtoifname = {}
|
||||
idxtoportid = {}
|
||||
_chassisidbyswitch[switch] = sanitize(list(
|
||||
@@ -267,8 +306,8 @@ def _extract_neighbor_data(args):
|
||||
return _extract_neighbor_data_b(args)
|
||||
except Exception as e:
|
||||
yieldexc = False
|
||||
if len(args) >= 5:
|
||||
yieldexc = args[4]
|
||||
if len(args) >= 6:
|
||||
yieldexc = args[5]
|
||||
if yieldexc:
|
||||
return e
|
||||
else:
|
||||
@@ -357,10 +396,3 @@ def _handle_neighbor_query(pathcomponents, configmanager):
|
||||
raise x
|
||||
return list_info(parms, listrequested)
|
||||
|
||||
|
||||
def _list_interfaces(switchname, configmanager):
|
||||
switchcreds = get_switchcreds(configmanager, (switchname,))
|
||||
switchcreds = switchcreds[0]
|
||||
conn = snmp.Session(*switchcreds)
|
||||
ifnames = netutil.get_portnamemap(conn)
|
||||
return util.natural_sort(ifnames.values())
|
||||
@@ -45,13 +45,16 @@ from eventlet.greenpool import GreenPool
|
||||
import eventlet
|
||||
import eventlet.semaphore
|
||||
import re
|
||||
webclient = eventlet.import_patched('pyghmi.util.webclient')
|
||||
|
||||
|
||||
noaffluent = set([])
|
||||
|
||||
_macmap = {}
|
||||
_apimacmap = {}
|
||||
_macsbyswitch = {}
|
||||
_nodesbymac = {}
|
||||
_switchportmap = {}
|
||||
_neighdata = {}
|
||||
vintage = None
|
||||
|
||||
|
||||
@@ -127,6 +130,36 @@ def _nodelookup(switch, ifname):
|
||||
return None
|
||||
|
||||
|
||||
def _affluent_map_switch(args):
|
||||
switch, password, user, cfm = args
|
||||
kv = util.TLSCertVerifier(cfm, switch,
|
||||
'pubkeys.tls_hardwaremanager').verify_cert
|
||||
wc = webclient.SecureHTTPConnection(
|
||||
switch, 443, verifycallback=kv, timeout=5)
|
||||
wc.set_basic_credentials(user, password)
|
||||
macs = wc.grab_json_response('/affluent/macs/by-port')
|
||||
_macsbyswitch[switch] = macs
|
||||
|
||||
for iface in macs:
|
||||
nummacs = len(macs[iface])
|
||||
for mac in macs[iface]:
|
||||
if mac in _macmap:
|
||||
_macmap[mac].append((switch, iface, nummacs))
|
||||
else:
|
||||
_macmap[mac] = [(switch, iface, nummacs)]
|
||||
nodename = _nodelookup(switch, iface)
|
||||
if nodename is not None:
|
||||
if mac in _nodesbymac and _nodesbymac[mac][0] != nodename:
|
||||
# For example, listed on both a real edge port
|
||||
# and by accident a trunk port
|
||||
log.log({'error': '{0} and {1} described by ambiguous'
|
||||
' switch topology values'.format(
|
||||
nodename, _nodesbymac[mac][0])})
|
||||
_nodesbymac[mac] = (None, None)
|
||||
else:
|
||||
_nodesbymac[mac] = (nodename, nummacs)
|
||||
|
||||
|
||||
def _map_switch_backend(args):
|
||||
"""Manipulate portions of mac address map relevant to a given switch
|
||||
"""
|
||||
@@ -144,13 +177,18 @@ def _map_switch_backend(args):
|
||||
# fallback if ifName is empty
|
||||
#
|
||||
global _macmap
|
||||
if len(args) == 3:
|
||||
switch, password, user = args
|
||||
if len(args) == 4:
|
||||
switch, password, user, cfm = args
|
||||
if not user:
|
||||
user = None
|
||||
else:
|
||||
switch, password = args
|
||||
user = None
|
||||
if switch not in noaffluent:
|
||||
try:
|
||||
return _affluent_map_switch(args)
|
||||
except Exception:
|
||||
pass
|
||||
haveqbridge = False
|
||||
mactobridge = {}
|
||||
conn = snmp.Session(switch, password, user)
|
||||
@@ -164,6 +202,7 @@ def _map_switch_backend(args):
|
||||
*([int(x) for x in oid[-6:]])
|
||||
)
|
||||
mactobridge[macaddr] = int(bridgeport)
|
||||
noaffluent.add(switch)
|
||||
if not haveqbridge:
|
||||
for vb in conn.walk('1.3.6.1.2.1.17.4.3.1.2'):
|
||||
oid, bridgeport = vb
|
||||
|
||||
@@ -36,7 +36,7 @@ def get_switchcreds(configmanager, switches):
|
||||
'secret.hardwaremanagementuser', {}).get('value', None)
|
||||
if not user:
|
||||
user = None
|
||||
switchauth.append((switch, password, user))
|
||||
switchauth.append((switch, password, user, configmanager))
|
||||
return switchauth
|
||||
|
||||
|
||||
|
||||
@@ -162,7 +162,7 @@ class NodeRange(object):
|
||||
pieces = seqrange.split(delimiter)
|
||||
if len(pieces) % 2 != 0:
|
||||
return self.failorreturn(seqrange)
|
||||
halflen = len(pieces) / 2
|
||||
halflen = len(pieces) // 2
|
||||
left = delimiter.join(pieces[:halflen])
|
||||
right = delimiter.join(pieces[halflen:])
|
||||
leftbits = _numextractor.parseString(left).asList()
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015-2019 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
# This plugin provides an ssh implementation comforming to the 'console'
|
||||
# specification. consoleserver or shellserver would be equally likely
|
||||
# to use this.
|
||||
|
||||
import confluent.exceptions as cexc
|
||||
import confluent.interface.console as conapi
|
||||
import confluent.log as log
|
||||
import confluent.util as util
|
||||
import pyghmi.exceptions as pygexc
|
||||
import pyghmi.redfish.command as rcmd
|
||||
import eventlet
|
||||
import eventlet.green.ssl as ssl
|
||||
try:
|
||||
websocket = eventlet.import_patched('websocket')
|
||||
wso = websocket.WebSocket
|
||||
except Exception:
|
||||
wso = object
|
||||
|
||||
def get_conn_params(node, configdata):
|
||||
if 'secret.hardwaremanagementuser' in configdata:
|
||||
username = configdata['secret.hardwaremanagementuser']['value']
|
||||
else:
|
||||
username = 'USERID'
|
||||
if 'secret.hardwaremanagementpassword' in configdata:
|
||||
passphrase = configdata['secret.hardwaremanagementpassword']['value']
|
||||
else:
|
||||
passphrase = 'PASSW0RD' # for lack of a better guess
|
||||
if 'hardwaremanagement.manager' in configdata:
|
||||
bmc = configdata['hardwaremanagement.manager']['value']
|
||||
else:
|
||||
bmc = node
|
||||
return {
|
||||
'username': username,
|
||||
'passphrase': passphrase,
|
||||
'bmc': bmc,
|
||||
}
|
||||
_configattributes = ('secret.hardwaremanagementuser',
|
||||
'secret.hardwaremanagementpassword',
|
||||
'hardwaremanagement.manager')
|
||||
|
||||
class WrappedWebSocket(wso):
|
||||
|
||||
def set_verify_callback(self, callback):
|
||||
self._certverify = callback
|
||||
|
||||
def connect(self, url, **options):
|
||||
add_tls = url.startswith('wss://')
|
||||
if add_tls:
|
||||
hostname, port, resource, _ = websocket._url.parse_url(url)
|
||||
if hostname[0] != '[' and ':' in hostname:
|
||||
hostname = '[{0}]'.format(hostname)
|
||||
if resource[0] != '/':
|
||||
resource = '/{0}'.format(resource)
|
||||
url = 'ws://{0}:443{1}'.format(hostname,resource)
|
||||
else:
|
||||
return super(WrappedWebSocket, self).connect(url, **options)
|
||||
self.sock_opt.timeout = options.get('timeout', self.sock_opt.timeout)
|
||||
self.sock, addrs = websocket._http.connect(url, self.sock_opt, websocket._http.proxy_info(**options),
|
||||
options.pop('socket', None))
|
||||
self.sock = ssl.wrap_socket(self.sock, cert_reqs=ssl.CERT_NONE)
|
||||
# The above is supersedeed by the _certverify, which provides
|
||||
# known-hosts style cert validaiton
|
||||
bincert = self.sock.getpeercert(binary_form=True)
|
||||
if not self._certverify(bincert):
|
||||
raise pygexc.UnrecognizedCertificate('Unknown certificate', bincert)
|
||||
try:
|
||||
self.handshake_response = websocket._handshake.handshake(self.sock, *addrs, **options)
|
||||
if self.handshake_response.status in websocket._handshake.SUPPORTED_REDIRECT_STATUSES:
|
||||
options['redirect_limit'] = options.pop('redirect_limit', 3) - 1
|
||||
if options['redirect_limit'] < 0:
|
||||
raise Exception('Redirect limit hit')
|
||||
url = self.handshake_response.headers['location']
|
||||
self.sock.close()
|
||||
return self.connect(url, **options)
|
||||
self.connected = True
|
||||
except:
|
||||
if self.sock:
|
||||
self.sock.close()
|
||||
self.sock = None
|
||||
raise
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class TsmConsole(conapi.Console):
|
||||
|
||||
def __init__(self, node, config):
|
||||
self.node = node
|
||||
self.ws = None
|
||||
configdata = config.get_node_attributes([node], _configattributes, decrypt=True)
|
||||
connparams = get_conn_params(node, configdata[node])
|
||||
self.username = connparams['username']
|
||||
self.password = connparams['passphrase']
|
||||
self.bmc = connparams['bmc']
|
||||
self.origbmc = connparams['bmc']
|
||||
if ':' in self.bmc:
|
||||
self.bmc = '[{0}]'.format(self.bmc)
|
||||
self.datacallback = None
|
||||
self.nodeconfig = config
|
||||
self.connected = False
|
||||
|
||||
|
||||
def recvdata(self):
|
||||
while self.connected:
|
||||
pendingdata = self.ws.recv()
|
||||
if pendingdata == '':
|
||||
self.datacallback(conapi.ConsoleEvent.Disconnect)
|
||||
return
|
||||
self.datacallback(pendingdata)
|
||||
|
||||
def connect(self, callback):
|
||||
self.datacallback = callback
|
||||
rc = rcmd.Command(self.origbmc, self.username,
|
||||
self.password,
|
||||
verifycallback=lambda x: True)
|
||||
wc = rc.oem.wc
|
||||
bmc = self.bmc
|
||||
if '%' in self.bmc:
|
||||
prefix = self.bmc.split('%')[0]
|
||||
bmc = prefix + ']'
|
||||
self.ws = WrappedWebSocket(host=bmc)
|
||||
kv = util.TLSCertVerifier(
|
||||
self.nodeconfig, self.node, 'pubkeys.tls_hardwaremanager').verify_cert
|
||||
self.ws.set_verify_callback(kv)
|
||||
self.ws.connect('wss://{0}/sol?CSRFTOKEN={1}'.format(self.bmc, rc.oem.csrftok), host=bmc, cookie='QSESSIONID={0}'.format(wc.cookies['QSESSIONID']))
|
||||
self.connected = True
|
||||
eventlet.spawn_n(self.recvdata)
|
||||
return
|
||||
|
||||
def write(self, data):
|
||||
self.ws.send(data)
|
||||
|
||||
def close(self):
|
||||
if self.ws:
|
||||
self.ws.close()
|
||||
self.connected = False
|
||||
self.datacallback = None
|
||||
|
||||
def create(nodes, element, configmanager, inputdata):
|
||||
if len(nodes) == 1:
|
||||
return TsmConsole(nodes[0], configmanager)
|
||||
@@ -0,0 +1,153 @@
|
||||
|
||||
# Copyright 2019-2020 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 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
|
||||
|
||||
class WebClient(object):
|
||||
def __init__(self, node, configmanager, creds):
|
||||
self.node = node
|
||||
self.wc = webclient.SecureHTTPConnection(node, port=443, verifycallback=util.TLSCertVerifier(
|
||||
configmanager, node, 'pubkeys.tls_hardwaremanager').verify_cert)
|
||||
self.wc.set_basic_credentials(creds[node]['secret.hardwaremanagementuser']['value'], creds[node]['secret.hardwaremanagementpassword']['value'])
|
||||
|
||||
def fetch(self, url, results):
|
||||
rsp, status = self.wc.grab_json_response_with_status(url)
|
||||
if status == 401:
|
||||
results.put(msg.ConfluentTargetInvalidCredentials(self.node, 'Unable to authenticate'))
|
||||
return {}
|
||||
elif status != 200:
|
||||
results.put(msg.ConfluentNodeError(self.node, 'Unknown error: ' + rsp + ' while retrieving ' + url))
|
||||
return {}
|
||||
return rsp
|
||||
|
||||
|
||||
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 _run_method(method, workers, results, configmanager, nodes, element):
|
||||
creds = configmanager.get_node_attributes(
|
||||
nodes, ['secret.hardwaremanagementuser', 'secret.hardwaremanagementpassword'], decrypt=True)
|
||||
for node in nodes:
|
||||
workers.add(eventlet.spawn(method, configmanager, creds,
|
||||
node, results, element))
|
||||
|
||||
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']:
|
||||
_run_method(retrieve_health, workers, results, configmanager, nodes, element)
|
||||
elif element[:3] == ['inventory', 'hardware', 'all']:
|
||||
_run_method(retrieve_inventory, workers, results, configmanager, nodes, element)
|
||||
elif element[:3] == ['inventory', 'firmware', 'all']:
|
||||
_run_method(retrieve_firmware, workers, results, configmanager, nodes, element)
|
||||
elif element == ['sensors', 'hardware', 'all']:
|
||||
_run_method(list_sensors, workers, results, configmanager, nodes, element)
|
||||
elif element[:3] == ['sensors', 'hardware', 'all']:
|
||||
_run_method(retrieve_sensors, workers, results, configmanager, nodes, element)
|
||||
else:
|
||||
for node in nodes:
|
||||
yield msg.ConfluentNodeError(node, 'Not Implemented')
|
||||
return
|
||||
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_inventory(configmanager, creds, node, results, element):
|
||||
if len(element) == 3:
|
||||
results.put(msg.ChildCollection('all'))
|
||||
results.put(msg.ChildCollection('system'))
|
||||
return
|
||||
wc = WebClient(node, configmanager, creds)
|
||||
invinfo = wc.fetch('/affluent/inventory/hardware/all', results)
|
||||
if invinfo:
|
||||
results.put(msg.KeyValueData(invinfo, node))
|
||||
|
||||
|
||||
def retrieve_firmware(configmanager, creds, node, results, element):
|
||||
if len(element) == 3:
|
||||
results.put(msg.ChildCollection('all'))
|
||||
return
|
||||
wc = WebClient(node, configmanager, creds)
|
||||
fwinfo = wc.fetch('/affluent/inventory/firmware/all', results)
|
||||
if fwinfo:
|
||||
results.put(msg.Firmware(fwinfo, node))
|
||||
|
||||
def list_sensors(configmanager, creds, node, results, element):
|
||||
wc = WebClient(node, configmanager, creds)
|
||||
sensors = wc.fetch('/affluent/sensors/hardware/all', results)
|
||||
for sensor in sensors['item']:
|
||||
results.put(msg.ChildCollection(sensor))
|
||||
|
||||
def retrieve_sensors(configmanager, creds, node, results, element):
|
||||
wc = WebClient(node, configmanager, creds)
|
||||
sensors = wc.fetch('/affluent/sensors/hardware/all/{0}'.format(element[-1]), results)
|
||||
if sensors:
|
||||
results.put(msg.SensorReadings(sensors['sensors'], node))
|
||||
|
||||
|
||||
|
||||
def retrieve_health(configmanager, creds, node, results, element):
|
||||
wc = WebClient(node, configmanager, creds)
|
||||
hinfo = wc.fetch('/affluent/health', results)
|
||||
if hinfo:
|
||||
results.put(msg.HealthSummary(hinfo.get('health', 'unknown'), name=node))
|
||||
results.put(msg.SensorReadings(hinfo.get('sensors', []), name=node))
|
||||
@@ -78,6 +78,18 @@ def retrieve(nodes, element, configmanager, inputdata):
|
||||
for node in nodes:
|
||||
workers.add(eventlet.spawn(retrieve_health, configmanager, creds,
|
||||
node, results))
|
||||
elif element[:3] == ['inventory', 'hardware', 'all']:
|
||||
creds = configmanager.get_node_attributes(
|
||||
nodes, ['secret.hardwaremanagementuser', 'secret.hardwaremanagementpassword'], decrypt=True)
|
||||
for node in nodes:
|
||||
workers.add(eventlet.spawn(retrieve_inventory, configmanager,
|
||||
creds, node, results, element))
|
||||
elif element[:3] == ['inventory', 'firmware', 'all']:
|
||||
creds = configmanager.get_node_attributes(
|
||||
nodes, ['secret.hardwaremanagementuser', 'secret.hardwaremanagementpassword'], decrypt=True)
|
||||
for node in nodes:
|
||||
workers.add(eventlet.spawn(retrieve_firmware, configmanager,
|
||||
creds, node, results, element))
|
||||
else:
|
||||
for node in nodes:
|
||||
yield msg.ConfluentNodeError(node, 'Not Implemented')
|
||||
@@ -105,6 +117,44 @@ def retrieve(nodes, element, configmanager, inputdata):
|
||||
pass
|
||||
|
||||
|
||||
def retrieve_inventory(configmanager, creds, node, results, element):
|
||||
if len(element) == 3:
|
||||
results.put(msg.ChildCollection('all'))
|
||||
results.put(msg.ChildCollection('system'))
|
||||
return
|
||||
wc = cnos_login(node, configmanager, creds)
|
||||
sysinfo = wc.grab_json_response('/nos/api/sysinfo/inventory')
|
||||
invinfo = {
|
||||
'inventory': [{
|
||||
'name': 'System',
|
||||
'present': True,
|
||||
'information': {
|
||||
'Product name': sysinfo['Model'],
|
||||
'Serial Number': sysinfo['Electronic Serial Number'],
|
||||
'Board Serial Number': sysinfo['Serial Number'],
|
||||
'Manufacturer': 'Lenovo',
|
||||
'Model': sysinfo['Machine Type Model'],
|
||||
'FRU Number': sysinfo['FRU'].strip(),
|
||||
}
|
||||
}]
|
||||
}
|
||||
results.put(msg.KeyValueData(invinfo, node))
|
||||
|
||||
|
||||
def retrieve_firmware(configmanager, creds, node, results, element):
|
||||
if len(element) == 3:
|
||||
results.put(msg.ChildCollection('all'))
|
||||
return
|
||||
wc = cnos_login(node, configmanager, creds)
|
||||
sysinfo = wc.grab_json_response('/nos/api/sysinfo/inventory')
|
||||
items = [{
|
||||
'Software': {'version': sysinfo['Software Revision']},
|
||||
},
|
||||
{
|
||||
'BIOS': {'version': sysinfo['BIOS Revision']},
|
||||
}]
|
||||
results.put(msg.Firmware(items, node))
|
||||
|
||||
def retrieve_health(configmanager, creds, node, results):
|
||||
wc = cnos_login(node, configmanager, creds)
|
||||
hinfo = wc.grab_json_response('/nos/api/sysinfo/globalhealthstatus')
|
||||
|
||||
@@ -29,6 +29,7 @@ import eventlet.queue as queue
|
||||
import eventlet.support.greendns
|
||||
from fnmatch import fnmatch
|
||||
import os
|
||||
import pwd
|
||||
import pyghmi.constants as pygconstants
|
||||
import pyghmi.exceptions as pygexc
|
||||
import pyghmi.storage as storage
|
||||
@@ -42,6 +43,11 @@ import traceback
|
||||
if not hasattr(ssl, 'SSLEOFError'):
|
||||
ssl.SSLEOFError = None
|
||||
|
||||
try:
|
||||
range = xrange
|
||||
except NameError:
|
||||
pass
|
||||
|
||||
pci_cache = {}
|
||||
|
||||
def get_dns_txt(qstring):
|
||||
@@ -131,7 +137,7 @@ def hex2bin(hexstring):
|
||||
if len(hexvals) < 2:
|
||||
hexvals = hexstring.split(' ')
|
||||
if len(hexvals) < 2:
|
||||
hexvals = [hexstring[i:i+2] for i in xrange(0, len(hexstring), 2)]
|
||||
hexvals = [hexstring[i:i+2] for i in range(0, len(hexstring), 2)]
|
||||
bytedata = [int(i, 16) for i in hexvals]
|
||||
return bytearray(bytedata)
|
||||
|
||||
@@ -278,6 +284,7 @@ def _donothing(data):
|
||||
|
||||
class IpmiConsole(conapi.Console):
|
||||
configattributes = frozenset(_configattributes)
|
||||
bmctonodemapping = {}
|
||||
|
||||
def __init__(self, node, config):
|
||||
self.error = None
|
||||
@@ -295,10 +302,21 @@ class IpmiConsole(conapi.Console):
|
||||
self.bmc = connparams['bmc']
|
||||
self.port = connparams['port']
|
||||
self.connected = False
|
||||
# ok, is self.bmc unique among nodes already
|
||||
# Cannot actually create console until 'connect', when we get callback
|
||||
if (self.bmc in self.bmctonodemapping and
|
||||
self.bmctonodemapping[self.bmc] != node):
|
||||
raise Exception(
|
||||
"Duplicate hardwaremanagement.manager attribute for {0} and {1}".format(
|
||||
node, self.bmctonodemapping[self.bmc]))
|
||||
self.bmctonodemapping[self.bmc] = node
|
||||
|
||||
def __del__(self):
|
||||
self.solconnection = None
|
||||
try:
|
||||
del self.bmctonodemapping[self.bmc]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def handle_data(self, data):
|
||||
if type(data) == dict:
|
||||
@@ -584,9 +602,13 @@ class IpmiHandler(object):
|
||||
self.output.put(msg.CreatedResource(
|
||||
'nodes/{0}/media/uploads/{1}'.format(self.node, u.name)))
|
||||
|
||||
def get_diags(self, savefile, progress):
|
||||
return self.ipmicmd.get_diagnostic_data(
|
||||
savefile, progress=progress, autosuffix=True)
|
||||
|
||||
def handle_servicedata_fetch(self):
|
||||
u = firmwaremanager.Updater(
|
||||
self.node, self.ipmicmd.get_diagnostic_data,
|
||||
self.node, self.get_diags,
|
||||
self.inputdata.nodefile(self.node), self.tenant, type='ffdc',
|
||||
owner=self.current_user)
|
||||
self.output.put(msg.CreatedResource(
|
||||
@@ -625,8 +647,10 @@ class IpmiHandler(object):
|
||||
return self.handle_ntp()
|
||||
elif self.element[1:4] == ['management_controller', 'extended', 'all']:
|
||||
return self.handle_bmcconfig()
|
||||
elif self.element[1:4] == ['management_controller', 'extended', 'all']:
|
||||
elif self.element[1:4] == ['management_controller', 'extended', 'advanced']:
|
||||
return self.handle_bmcconfig(True)
|
||||
elif self.element[1:4] == ['management_controller', 'extended', 'extra']:
|
||||
return self.handle_bmcconfig(True, extended=True)
|
||||
elif self.element[1:3] == ['system', 'all']:
|
||||
return self.handle_sysconfig()
|
||||
elif self.element[1:3] == ['system', 'advanced']:
|
||||
@@ -658,7 +682,7 @@ class IpmiHandler(object):
|
||||
if len(self.element) == 4:
|
||||
# A list of destinations
|
||||
maxdest = self.ipmicmd.get_alert_destination_count()
|
||||
for alertidx in xrange(0, maxdest + 1):
|
||||
for alertidx in range(0, maxdest + 1):
|
||||
self.output.put(msg.ChildCollection(alertidx))
|
||||
return
|
||||
elif len(self.element) == 5:
|
||||
@@ -832,6 +856,10 @@ class IpmiHandler(object):
|
||||
raise
|
||||
if hasattr(reading, 'health'):
|
||||
reading.health = _str_health(reading.health)
|
||||
if hasattr(reading, 'unavailable') and reading.unavailable:
|
||||
self.output.put(msg.SensorReadings([EmptySensor(
|
||||
reading.name)], name=self.node))
|
||||
continue
|
||||
readings.append(reading)
|
||||
self.output.put(msg.SensorReadings(readings, name=self.node))
|
||||
else:
|
||||
@@ -847,9 +875,13 @@ class IpmiHandler(object):
|
||||
self.ipmicmd.sensormap[sensorname])
|
||||
if hasattr(reading, 'health'):
|
||||
reading.health = _str_health(reading.health)
|
||||
self.output.put(
|
||||
msg.SensorReadings([reading],
|
||||
name=self.node))
|
||||
if hasattr(reading, 'unavailable') and reading.unavailable:
|
||||
self.output.put(msg.SensorReadings([EmptySensor(
|
||||
reading.name)], name=self.node))
|
||||
else:
|
||||
self.output.put(
|
||||
msg.SensorReadings([reading],
|
||||
name=self.node))
|
||||
except pygexc.IpmiException as ie:
|
||||
if ie.ipmicode == 203:
|
||||
self.output.put(msg.ConfluentResourceUnavailable(
|
||||
@@ -1391,12 +1423,14 @@ class IpmiHandler(object):
|
||||
'Cannot read the "clear" resource')
|
||||
self.ipmicmd.clear_system_configuration()
|
||||
|
||||
def handle_bmcconfig(self, advanced=False):
|
||||
def handle_bmcconfig(self, advanced=False, extended=False):
|
||||
if 'read' == self.op:
|
||||
try:
|
||||
self.output.put(msg.ConfigSet(
|
||||
self.node,
|
||||
self.ipmicmd.get_bmc_configuration()))
|
||||
if extended:
|
||||
bmccfg = self.ipmicmd.get_extended_bmc_configuration()
|
||||
else:
|
||||
bmccfg = self.ipmicmd.get_bmc_configuration()
|
||||
self.output.put(msg.ConfigSet(self.node, bmccfg))
|
||||
except Exception as e:
|
||||
self.output.put(
|
||||
msg.ConfluentNodeError(self.node, str(e)))
|
||||
@@ -1469,14 +1503,33 @@ class IpmiHandler(object):
|
||||
|
||||
def save_licenses(self):
|
||||
directory = self.inputdata.nodefile(self.node)
|
||||
checkdir = directory
|
||||
if not os.access(directory, os.W_OK):
|
||||
raise exc.InvalidArgumentException(
|
||||
'The confluent system user/group is unable to write to '
|
||||
'directory {0}, check ownership and permissions'.format(
|
||||
checkdir))
|
||||
for saved in self.ipmicmd.save_licenses(directory):
|
||||
try:
|
||||
pwent = pwd.getpwnam(self.current_user)
|
||||
os.chown(saved, pwent.pw_uid, pwent.pw_gid)
|
||||
except KeyError:
|
||||
pass
|
||||
self.output.put(msg.SavedFile(self.node, saved))
|
||||
|
||||
def handle_licenses(self):
|
||||
if self.element[-1] == '':
|
||||
self.element = self.element[:-1]
|
||||
if self.op in ('create', 'update'):
|
||||
self.ipmicmd.apply_license(self.inputdata.nodefile(self.node))
|
||||
filename = self.inputdata.nodefile(self.node)
|
||||
if not os.access(filename, os.R_OK):
|
||||
errstr = ('{0} is not readable by confluent on {1} '
|
||||
'(ensure confluent user or group can access file '
|
||||
'and parent directories)').format(
|
||||
filename, socket.gethostname())
|
||||
self.output.put(msg.ConfluentNodeError(self.node, errstr))
|
||||
return
|
||||
self.ipmicmd.apply_license(filename)
|
||||
if len(self.element) == 3:
|
||||
self.output.put(msg.ChildCollection('all'))
|
||||
i = 1
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
|
||||
# Copyright 2019-2020 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 eventlet
|
||||
import eventlet.queue as queue
|
||||
import confluent.exceptions as exc
|
||||
try:
|
||||
from urllib.parse import urlencode
|
||||
except ImportError:
|
||||
from urllib import urlencode
|
||||
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
|
||||
|
||||
class WebClient(object):
|
||||
def __init__(self, node, configmanager, creds):
|
||||
self.node = node
|
||||
self.wc = webclient.SecureHTTPConnection(node, port=443, verifycallback=util.TLSCertVerifier(
|
||||
configmanager, node, 'pubkeys.tls_hardwaremanager').verify_cert)
|
||||
authdata = urlencode({
|
||||
'f_user_id': creds[node]['secret.hardwaremanagementuser']['value'],
|
||||
'f_password': creds[node]['secret.hardwaremanagementpassword']['value'],
|
||||
})
|
||||
self.wc.grab_json_response_with_status(
|
||||
'/admin/launch?script=rh&template=login&action=login', authdata)
|
||||
if 'session' not in self.wc.cookies:
|
||||
raise exc.TargetEndpointBadCredentials()
|
||||
|
||||
def run_cmd(self, cmd):
|
||||
res = self.wc.grab_json_response('/admin/launch?script=json')
|
||||
if res.get('status', None) == 'ERROR':
|
||||
raise Exception('Failure trying to run {0}: {1}'.format(cmd,
|
||||
repr(res)))
|
||||
return res.get('data', {})
|
||||
|
||||
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 _run_method(method, workers, results, configmanager, nodes, element):
|
||||
creds = configmanager.get_node_attributes(
|
||||
nodes, ['secret.hardwaremanagementuser', 'secret.hardwaremanagementpassword'], decrypt=True)
|
||||
for node in nodes:
|
||||
workers.add(eventlet.spawn(method, configmanager, creds,
|
||||
node, results, element))
|
||||
|
||||
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']:
|
||||
_run_method(retrieve_health, workers, results, configmanager, nodes, element)
|
||||
elif element[:3] == ['inventory', 'hardware', 'all']:
|
||||
_run_method(retrieve_inventory, workers, results, configmanager, nodes, element)
|
||||
elif element[:3] == ['inventory', 'firmware', 'all']:
|
||||
_run_method(retrieve_firmware, workers, results, configmanager, nodes, element)
|
||||
elif element == ['sensors', 'hardware', 'all']:
|
||||
_run_method(list_sensors, workers, results, configmanager, nodes, element)
|
||||
elif element[:3] == ['sensors', 'hardware', 'all']:
|
||||
_run_method(retrieve_sensors, workers, results, configmanager, nodes, element)
|
||||
else:
|
||||
for node in nodes:
|
||||
yield msg.ConfluentNodeError(node, 'Not Implemented')
|
||||
return
|
||||
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_inventory(configmanager, creds, node, results, element):
|
||||
if len(element) == 3:
|
||||
results.put(msg.ChildCollection('all'))
|
||||
results.put(msg.ChildCollection('system'))
|
||||
return
|
||||
wc = WebClient(node, configmanager, creds)
|
||||
invinfo = wc.fetch('/affluent/inventory/hardware/all', results)
|
||||
if invinfo:
|
||||
results.put(msg.KeyValueData(invinfo, node))
|
||||
|
||||
|
||||
def retrieve_firmware(configmanager, creds, node, results, element):
|
||||
if len(element) == 3:
|
||||
results.put(msg.ChildCollection('all'))
|
||||
return
|
||||
wc = WebClient(node, configmanager, creds)
|
||||
fwinfo = wc.fetch('/affluent/inventory/firmware/all', results)
|
||||
if fwinfo:
|
||||
results.put(msg.Firmware(fwinfo, node))
|
||||
|
||||
def list_sensors(configmanager, creds, node, results, element):
|
||||
wc = WebClient(node, configmanager, creds)
|
||||
sensors = wc.fetch('/affluent/sensors/hardware/all', results)
|
||||
for sensor in sensors['item']:
|
||||
results.put(msg.ChildCollection(sensor))
|
||||
|
||||
def retrieve_sensors(configmanager, creds, node, results, element):
|
||||
wc = WebClient(node, configmanager, creds)
|
||||
sensors = wc.fetch('/affluent/sensors/hardware/all/{0}'.format(element[-1]), results)
|
||||
if sensors:
|
||||
results.put(msg.SensorReadings(sensors['sensors'], node))
|
||||
|
||||
|
||||
|
||||
def retrieve_health(configmanager, creds, node, results, element):
|
||||
wc = WebClient(node, configmanager, creds)
|
||||
hinfo = wc.fetch('/affluent/health', results)
|
||||
if hinfo:
|
||||
results.put(msg.HealthSummary(hinfo.get('health', 'unknown'), name=node))
|
||||
results.put(msg.SensorReadings(hinfo.get('sensors', []), name=node))
|
||||
@@ -27,6 +27,7 @@ import eventlet.queue as queue
|
||||
import eventlet.support.greendns
|
||||
from fnmatch import fnmatch
|
||||
import os
|
||||
import pwd
|
||||
import pyghmi.constants as pygconstants
|
||||
import pyghmi.exceptions as pygexc
|
||||
import pyghmi.storage as storage
|
||||
@@ -175,12 +176,15 @@ class IpmiCommandWrapper(ipmicommand.Command):
|
||||
raise exc.TargetEndpointUnreachable(se.strerror)
|
||||
else:
|
||||
raise exc.TargetEndpointUnreachable(str(se))
|
||||
if isinstance(se, socket.timeout) or (len(se) > 1 and se[1] == 'EHOSTUNREACH'):
|
||||
if isinstance(se, socket.timeout):
|
||||
raise exc.TargetEndpointUnreachable('timeout')
|
||||
raise
|
||||
except pygexc.PyghmiException as pe:
|
||||
if 'Access Denied' in str(pe):
|
||||
raise exc.TargetEndpointBadCredentials()
|
||||
if 'Redfish not ready' in str(pe):
|
||||
raise exc.TargetEndpointUnreachable('Redfish not yet ready')
|
||||
raise
|
||||
|
||||
def close_confluent(self):
|
||||
if self._attribwatcher:
|
||||
@@ -462,9 +466,13 @@ class IpmiHandler(object):
|
||||
self.output.put(msg.CreatedResource(
|
||||
'nodes/{0}/media/uploads/{1}'.format(self.node, u.name)))
|
||||
|
||||
def get_diags(self, savefile, progress):
|
||||
return self.ipmicmd.get_diagnostic_data(
|
||||
savefile, progress=progress, autosuffix=True)
|
||||
|
||||
def handle_servicedata_fetch(self):
|
||||
u = firmwaremanager.Updater(
|
||||
self.node, self.ipmicmd.get_diagnostic_data,
|
||||
self.node, self.get_diags,
|
||||
self.inputdata.nodefile(self.node), self.tenant, type='ffdc',
|
||||
owner=self.current_user)
|
||||
self.output.put(msg.CreatedResource(
|
||||
@@ -1340,13 +1348,31 @@ class IpmiHandler(object):
|
||||
|
||||
def save_licenses(self):
|
||||
directory = self.inputdata.nodefile(self.node)
|
||||
if not os.access(directory, os.W_OK):
|
||||
raise exc.InvalidArgumentException(
|
||||
'The confluent system user/group is unable to write to '
|
||||
'directory {0}, check ownership and permissions'.format(
|
||||
directory))
|
||||
for saved in self.ipmicmd.save_licenses(directory):
|
||||
try:
|
||||
pwent = pwd.getpwnam(self.current_user)
|
||||
os.chown(saved, pwent.pw_uid, pwent.pw_gid)
|
||||
except KeyError:
|
||||
pass
|
||||
self.output.put(msg.SavedFile(self.node, saved))
|
||||
|
||||
def handle_licenses(self):
|
||||
if self.element[-1] == '':
|
||||
self.element = self.element[:-1]
|
||||
if self.op in ('create', 'update'):
|
||||
filename = self.inputdata.nodefile(self.node)
|
||||
if not os.access(filename, os.R_OK):
|
||||
errstr = ('{0} is not readable by confluent on {1} '
|
||||
'(ensure confluent user or group can access file '
|
||||
'and parent directories)').format(
|
||||
filename, socket.gethostname())
|
||||
self.output.put(msg.ConfluentNodeError(self.node, errstr))
|
||||
return
|
||||
self.ipmicmd.apply_license(self.inputdata.nodefile(self.node))
|
||||
if len(self.element) == 3:
|
||||
self.output.put(msg.ChildCollection('all'))
|
||||
|
||||
@@ -77,7 +77,7 @@ class HostKeyHandler(paramiko.client.MissingHostKeyPolicy):
|
||||
|
||||
class SshShell(conapi.Console):
|
||||
|
||||
def __init__(self, node, config, username='', password=''):
|
||||
def __init__(self, node, config, username=b'', password=b''):
|
||||
self.node = node
|
||||
self.ssh = None
|
||||
self.datacallback = None
|
||||
@@ -127,15 +127,15 @@ class SshShell(conapi.Console):
|
||||
look_for_keys=False)
|
||||
except paramiko.AuthenticationException:
|
||||
self.inputmode = 0
|
||||
self.username = ''
|
||||
self.password = ''
|
||||
self.username = b''
|
||||
self.password = b''
|
||||
self.datacallback('\r\nlogin as: ')
|
||||
return
|
||||
except paramiko.ssh_exception.NoValidConnectionsError as e:
|
||||
self.datacallback(str(e))
|
||||
self.inputmode = 0
|
||||
self.username = ''
|
||||
self.password = ''
|
||||
self.username = b''
|
||||
self.password = b''
|
||||
self.datacallback('\r\nlogin as: ')
|
||||
return
|
||||
except cexc.PubkeyInvalid as pi:
|
||||
@@ -205,9 +205,9 @@ class SshShell(conapi.Console):
|
||||
delidx = data.index(b'\x7f')
|
||||
data = data[:delidx - 1] + data[delidx + 1:]
|
||||
self.username += data
|
||||
if '\r' in self.username:
|
||||
self.username, self.password = self.username.split('\r')[:2]
|
||||
lastdata = data.split('\r')[0]
|
||||
if b'\r' in self.username:
|
||||
self.username, self.password = self.username.split(b'\r')[:2]
|
||||
lastdata = data.split(b'\r')[0]
|
||||
if lastdata != '':
|
||||
self.datacallback(lastdata)
|
||||
self.datacallback('\r\nEnter password: ')
|
||||
@@ -223,9 +223,9 @@ class SshShell(conapi.Console):
|
||||
delidx = data.index(b'\x7f')
|
||||
data = data[:delidx - 1] + data[delidx + 1:]
|
||||
self.password += data
|
||||
if '\r' in self.password:
|
||||
self.password = self.password.split('\r')[0]
|
||||
self.datacallback('\r\n')
|
||||
if b'\r' in self.password:
|
||||
self.password = self.password.split(b'\r')[0]
|
||||
self.datacallback(b'\r\n')
|
||||
self.logon()
|
||||
else:
|
||||
self.shell.sendall(data)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2014 IBM Corporation
|
||||
# Copyright 2015-2018 Lenovo
|
||||
# Copyright 2015-2019 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -123,7 +123,8 @@ def sessionhdl(connection, authname, skipauth=False, cert=None):
|
||||
if authdata:
|
||||
cfm = authdata[1]
|
||||
authenticated = True
|
||||
send_data(connection, "Confluent -- v0 --")
|
||||
# version 0 == original, version 1 == pickle3 allowed, 2 = pickle forbidden, msgpack allowed
|
||||
send_data(connection, "Confluent -- v2 --")
|
||||
while not authenticated: # prompt for name and passphrase
|
||||
send_data(connection, {'authpassed': 0})
|
||||
response = tlvdata.recv(connection)
|
||||
@@ -153,20 +154,25 @@ 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 not libssl:
|
||||
if request and 'collective' in request:
|
||||
if skipauth:
|
||||
if not libssl:
|
||||
tlvdata.send(
|
||||
connection,
|
||||
{'collective': {'error': 'Server either does not have '
|
||||
'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.'}})
|
||||
return
|
||||
return collective.handle_connection(connection, None, request['collective'],
|
||||
local=True)
|
||||
else:
|
||||
tlvdata.send(
|
||||
connection,
|
||||
{'collective': {'error': 'Server either does not have '
|
||||
'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.'}})
|
||||
return
|
||||
return collective.handle_connection(connection, None, request['collective'],
|
||||
local=True)
|
||||
connection,
|
||||
{'collective': {'error': 'collective management commands may only be used by root'}})
|
||||
while request is not None:
|
||||
try:
|
||||
process_request(
|
||||
@@ -221,6 +227,7 @@ def process_request(connection, request, cfm, authdata, authname, skipauth):
|
||||
auditmsg['tenant'] = authdata[3]
|
||||
auditmsg['allowed'] = True
|
||||
if _should_authlog(path, operation):
|
||||
tlvdata.unicode_dictvalues(auditmsg)
|
||||
auditlog.log(auditmsg)
|
||||
try:
|
||||
if operation == 'start':
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from ctypes import *
|
||||
from ctypes.util import find_library
|
||||
import confluent.util as util
|
||||
import grp
|
||||
import pwd
|
||||
import os
|
||||
@@ -29,6 +30,7 @@ def getgrouplist(name, gid, ng=32):
|
||||
|
||||
|
||||
def grouplist(username):
|
||||
username = util.stringify(username)
|
||||
pent = pwd.getpwnam(username)
|
||||
try:
|
||||
groups = getgrouplist(pent.pw_name, pent.pw_gid)
|
||||
|
||||
@@ -31,7 +31,7 @@ def stringify(instr):
|
||||
# Normalize unicode and bytes to 'str', correcting for
|
||||
# current python version
|
||||
if isinstance(instr, bytes) and not isinstance(instr, str):
|
||||
return instr.decode('utf-8')
|
||||
return instr.decode('utf-8', errors='replace')
|
||||
elif not isinstance(instr, bytes) and not isinstance(instr, str):
|
||||
return instr.encode('utf-8')
|
||||
return instr
|
||||
@@ -75,11 +75,11 @@ def randomstring(length=20):
|
||||
|
||||
:param length: The number of characters to produce, defaults to 20
|
||||
"""
|
||||
chunksize = length / 4
|
||||
chunksize = length // 4
|
||||
if length % 4 > 0:
|
||||
chunksize += 1
|
||||
strval = base64.urlsafe_b64encode(os.urandom(chunksize * 3))
|
||||
return strval[0:length-1]
|
||||
return stringify(strval[0:length-1])
|
||||
|
||||
|
||||
def securerandomnumber(low=0, high=4294967295):
|
||||
@@ -109,7 +109,7 @@ def monotonic_time():
|
||||
|
||||
|
||||
def get_certificate_from_file(certfile):
|
||||
cert = open(certfile, 'rb').read()
|
||||
cert = open(certfile, 'r').read()
|
||||
inpemcert = False
|
||||
prunedcert = ''
|
||||
for line in cert.split('\n'):
|
||||
|
||||
@@ -13,9 +13,9 @@ BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot
|
||||
Prefix: %{_prefix}
|
||||
BuildArch: noarch
|
||||
%if "%{dist}" == ".el8"
|
||||
Requires: python3-pyghmi >= 1.0.34, python3-eventlet, python3-greenlet, python3-pycryptodomex >= 3.4.7, confluent_client, python3-pyparsing, python3-paramiko, python3-dns, python3-netifaces, python3-pyasn1 >= 0.2.3, python3-pysnmp >= 4.3.4, python3-pyte, python3-lxml, python3-eficompressor, python3-setuptools, python3-dateutil, python3-enum34, python3-asn1crypto, python3-ipaddress, python3-cffi, python3-pyOpenSSL
|
||||
Requires: python3-pyghmi >= 1.0.34, python3-eventlet, python3-greenlet, python3-pycryptodomex >= 3.4.7, confluent_client, python3-pyparsing, python3-paramiko, python3-dns, python3-netifaces, python3-pyasn1 >= 0.2.3, python3-pysnmp >= 4.3.4, python3-pyte, python3-lxml, python3-eficompressor, python3-setuptools, python3-dateutil, python3-enum34, python3-asn1crypto, python3-cffi, python3-pyOpenSSL, python3-monotonic, python3-websocket-client python3-msgpack
|
||||
%else
|
||||
Requires: python-pyghmi >= 1.0.34, python-eventlet, python-greenlet, python-pycryptodomex >= 3.4.7, confluent_client, python-pyparsing, python-paramiko, python-dns, python-netifaces, python2-pyasn1 >= 0.2.3, python-pysnmp >= 4.3.4, python-pyte, python-lxml, python-eficompressor, python-setuptools, python-dateutil
|
||||
Requires: python-pyghmi >= 1.0.34, python-eventlet, python-greenlet, python-pycryptodomex >= 3.4.7, confluent_client, python-pyparsing, python-paramiko, python-dns, python-netifaces, python2-pyasn1 >= 0.2.3, python-pysnmp >= 4.3.4, python-pyte, python-lxml, python-eficompressor, python-setuptools, python-dateutil, python2-websocket-client python2-msgpack
|
||||
%endif
|
||||
Vendor: Jarrod Johnson <jjohnson2@lenovo.com>
|
||||
Url: http://xcat.sf.net/
|
||||
@@ -42,16 +42,41 @@ python2 setup.py install --single-version-externally-managed -O1 --root=$RPM_BUI
|
||||
for file in $(grep confluent/__init__.py INSTALLED_FILES.bare); do
|
||||
rm $RPM_BUILD_ROOT/$file
|
||||
done
|
||||
grep -v confluent/__init__.py INSTALLED_FILES.bare > INSTALLED_FILES
|
||||
grep -v confluent/__init__.py INSTALLED_FILES.bare | grep -v etc/init.d/confluent > INSTALLED_FILES
|
||||
rm $RPM_BUILD_ROOT/etc/init.d/confluent
|
||||
rmdir $RPM_BUILD_ROOT/etc/init.d
|
||||
rmdir $RPM_BUILD_ROOT/etc
|
||||
# Only do non-root confluent if systemd of the platform supports it
|
||||
systemd-analyze verify $RPM_BUILD_ROOT/usr/lib/systemd/system/confluent.service 2>&1 | grep "'AmbientCapabilities'" > /dev/null && sed -e 's/User=.*//' -e 's/Group=.*//' -e 's/AmbientCapabilities=.*//' -i $RPM_BUILD_ROOT/usr/lib/systemd/system/confluent.service
|
||||
cat INSTALLED_FILES
|
||||
|
||||
%triggerin -- python-pyghmi
|
||||
%triggerin -- python-pyghmi, python3-pyghmi, python2-pyghmi
|
||||
if [ -x /usr/bin/systemctl ]; then /usr/bin/systemctl try-restart confluent >& /dev/null; fi
|
||||
true
|
||||
|
||||
%pre
|
||||
getent group confluent > /dev/null || /usr/sbin/groupadd -r confluent
|
||||
getent passwd confluent > /dev/null || /usr/sbin/useradd -r -g confluent -d /var/lib/confluent -s /sbin/nologin confluent
|
||||
mkdir -p /etc/confluent /var/lib/confluent /var/log/confluent /var/cache/confluent
|
||||
chown -R confluent:confluent /etc/confluent /var/lib/confluent /var/log/confluent /var/cache/confluent
|
||||
|
||||
%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
|
||||
NEEDCHOWN=0
|
||||
NEEDSTART=0
|
||||
find /etc/confluent -uid 0 | egrep '.*' > /dev/null && NEEDCHOWN=1
|
||||
find /var/log/confluent -uid 0 | egrep '.*' > /dev/null && NEEDCHOWN=1
|
||||
find /var/run/confluent -uid 0 | egrep '.*' > /dev/null && NEEDCHOWN=1
|
||||
find /var/cache/confluent -uid 0 | egrep '.*' > /dev/null && NEEDCHOWN=1
|
||||
if [ $NEEDCHOWN = 1 ]; then
|
||||
if systemctl is-active confluent > /dev/null; then
|
||||
NEEDSTART=1
|
||||
systemctl stop confluent
|
||||
fi
|
||||
chown -R confluent:confluent /etc/confluent /var/lib/confluent /var/log/confluent /var/cache/confluent
|
||||
fi
|
||||
systemctl daemon-reload
|
||||
if systemctl is-active confluent > /dev/null || [ $NEEDSTART = 1 ]; then /usr/bin/systemctl restart confluent >& /dev/null; fi
|
||||
if [ ! -e /etc/pam.d/confluent ]; then
|
||||
ln -s /etc/pam.d/sshd /etc/pam.d/confluent
|
||||
fi
|
||||
|
||||
@@ -14,6 +14,7 @@ setup(
|
||||
'confluent/discovery/handlers/',
|
||||
'confluent/networking/',
|
||||
'confluent/plugins/hardwaremanagement/',
|
||||
'confluent/plugins/console/',
|
||||
'confluent/plugins/shell/',
|
||||
'confluent/collective/',
|
||||
'confluent/plugins/configuration/'],
|
||||
|
||||
@@ -1,13 +1,25 @@
|
||||
# IBM(c) 2015 Apache 2.0
|
||||
# Lenovo(c) 2020 Apache 2.0
|
||||
[Unit]
|
||||
Description=Confluent hardware manager
|
||||
Description=Confluent hardware manager
|
||||
|
||||
[Service]
|
||||
Type=forking
|
||||
#PIDFile=/var/run/confluent/pid
|
||||
RuntimeDirectory=confluent
|
||||
StateDirectory=confluent
|
||||
CacheDirectory=confluent
|
||||
LogsDirectory=confluent
|
||||
ConfigurationDirectory=confluent
|
||||
ExecStart=/opt/confluent/bin/confluent
|
||||
ExecStop=/opt/confluent/bin/confetty shutdown /
|
||||
Restart=on-failure
|
||||
AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_SETUID CAP_SETGID CAP_CHOWN
|
||||
User=confluent
|
||||
Group=confluent
|
||||
DevicePolicy=closed
|
||||
ProtectControlGroups=true
|
||||
ProtectSystem=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
Reference in New Issue
Block a user