mirror of
https://github.com/xcat2/confluent.git
synced 2026-09-29 08:41:00 +00:00
Compare commits
307 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| da400b11d0 | |||
| d9042e5d4c | |||
| e443aa6530 | |||
| 8993f80b13 | |||
| 8e00f3a795 | |||
| 1f6fa7636b | |||
| d86bacf4d8 | |||
| 7230ec1306 | |||
| 00d5416737 | |||
| b22073e106 | |||
| 03369c5d5e | |||
| 0b30b92bc9 | |||
| dd90af1414 | |||
| 9e13724037 | |||
| bbe839ea5d | |||
| ff7bdcc16e | |||
| 6cd961786d | |||
| 18fa8f0b41 | |||
| 6e5375918d | |||
| 046c1e8ec4 | |||
| eb48f2c1f7 | |||
| d25ac000f4 | |||
| a47bdf4181 | |||
| caf41345a1 | |||
| 3d6a1881c8 | |||
| 5083977664 | |||
| 53821d98e6 | |||
| fdb8ef6919 | |||
| b08bf6ae97 | |||
| 3318b3cda7 | |||
| e97f768170 | |||
| 9eacb4e3f0 | |||
| 77bce756cd | |||
| 2decaac8ea | |||
| e0a62a2533 | |||
| f8d2fd8f46 | |||
| 7f18f5fde0 | |||
| f07371c756 | |||
| 179fcd9dcf | |||
| e79a5ecd12 | |||
| 2faf187d2d | |||
| 703d8f273f | |||
| db0bc41441 | |||
| 97effaae01 | |||
| 0a5424fbb2 | |||
| 3571947332 | |||
| 72289f89cb | |||
| bf90431101 | |||
| 784758fa25 | |||
| 6c5536c840 | |||
| 64ebc9b16f | |||
| 213b027f62 | |||
| e9ddbb8785 | |||
| 28eb1180c7 | |||
| 892d01d054 | |||
| 949609ce64 | |||
| 21b99728d7 | |||
| 79b4482298 | |||
| 40f700c46d | |||
| 652d9c0a55 | |||
| 20d7eb62f4 | |||
| 34623d5bbd | |||
| 0e7072a256 | |||
| 298b9bcfff | |||
| ddfdae6bf0 | |||
| b77a97db43 | |||
| 1e1d8dc000 | |||
| 4adb2adc12 | |||
| 0e9e5da102 | |||
| 777c419cb8 | |||
| 96916b57ae | |||
| 40ccf9d325 | |||
| 6bd36580ba | |||
| 37f12296b8 | |||
| cd9eefa100 | |||
| 2c72931641 | |||
| 0731bfe29a | |||
| 667a1030b4 | |||
| 4c6e3fcc4f | |||
| baa4d8bd1a | |||
| 816445e4b6 | |||
| 8cce35dbcb | |||
| dabb93fc04 | |||
| 120b70f7ab | |||
| fb626f7713 | |||
| 41c22d9e66 | |||
| 6b6b7f06b7 | |||
| 4595c417ee | |||
| f5fea79ee4 | |||
| 58ab997105 | |||
| 819318b763 | |||
| d47dfd8a73 | |||
| bcfc6966c3 | |||
| e1cb97e836 | |||
| 7f2df84167 | |||
| ce1a5b464f | |||
| 4fb41a991e | |||
| bd735e8902 | |||
| 7dcbd75561 | |||
| 4ec06f8940 | |||
| 60b91d5202 | |||
| 4ce382ced2 | |||
| d85fddd555 | |||
| c150202585 | |||
| 8cde65a9e6 | |||
| d1abace7da | |||
| 412166922a | |||
| 5e34ebe525 | |||
| 8274167298 | |||
| f5e6461fd2 | |||
| c9ffe77582 | |||
| 60ec9c7c51 | |||
| 1d04405099 | |||
| 16db3f1b6a | |||
| a1b0ab15c5 | |||
| 2a1794ddad | |||
| 7704523410 | |||
| 4cc57acea9 | |||
| 70aa1fd114 | |||
| ba27207b83 | |||
| be12f163a5 | |||
| 20e00b266a | |||
| ae03ca9fd8 | |||
| 1812e16559 | |||
| be971fd5e7 | |||
| 8675929a16 | |||
| a2d440d83b | |||
| e0d6d6b18c | |||
| e47038b2dd | |||
| a5e84e2eb0 | |||
| 6f633e0bfb | |||
| 2112fd71f5 | |||
| d83fb76254 | |||
| 064514d6ec | |||
| 3e8271afa8 | |||
| 3980be4e3a | |||
| 67d61fc803 | |||
| 5f3f073e9c | |||
| 439f4c7709 | |||
| 3b8d0ef40a | |||
| 26a8ba53bd | |||
| c9192ab170 | |||
| 00598578a6 | |||
| 74740e01b1 | |||
| 0034309fe1 | |||
| 0b7f2a45e9 | |||
| 261783f971 | |||
| 9f8feb971d | |||
| 2959fa7e33 | |||
| 0b40b876c0 | |||
| 6952f7cab9 | |||
| c2c81b0167 | |||
| ac0626a51c | |||
| 9effdee15a | |||
| 65580b1d60 | |||
| 9c41031df3 | |||
| 740cccd82e | |||
| 344bb1229c | |||
| 935acc2a10 | |||
| cd841fb43e | |||
| 447a3b3ffe | |||
| 108c9d3e9d | |||
| cc98290d68 | |||
| 1fce78ed39 | |||
| 5bc4d20eef | |||
| 5dbd345a49 | |||
| 62839b76b7 | |||
| 848745bb4a | |||
| f68a8c80bb | |||
| 7f185db64f | |||
| 2cd2e170e0 | |||
| 0df75d3102 | |||
| 5b8aebaa3e | |||
| 0577c9a99c | |||
| 12ffdf9504 | |||
| c2a4913c78 | |||
| f34770529e | |||
| 0336a07f17 | |||
| b57dbcd89b | |||
| 7948cd1bb2 | |||
| 049a728be1 | |||
| 42c775a7a4 | |||
| 9cf074e1a2 | |||
| 631509a32a | |||
| 4e4fbeb907 | |||
| b92fddc647 | |||
| 79474ea9fb | |||
| 639a7352ad | |||
| 4708630bac | |||
| a0be58c9cb | |||
| 71e89c005b | |||
| 1dc8fc973d | |||
| a517436f71 | |||
| ed34b30992 | |||
| 8ca1553c33 | |||
| 02266a4be6 | |||
| 70e5d1dc52 | |||
| 191726776c | |||
| c99d446a63 | |||
| e3318ec5ef | |||
| cbded58fc2 | |||
| 005877d3eb | |||
| 8497c28ebe | |||
| 2d76c0aefc | |||
| 2e5ec9264f | |||
| 9734aa65bf | |||
| 46ed0eb38a | |||
| 85d2a0e6aa | |||
| 5f88067134 | |||
| eb63a6fcda | |||
| 8be3c2a80e | |||
| 2ce26cbb59 | |||
| 44808fd343 | |||
| 467fea7d8e | |||
| 39463ddcb7 | |||
| cdb3801993 | |||
| af478816e9 | |||
| 79e57a442d | |||
| 38d6354725 | |||
| b2ba28f0fb | |||
| 9bf8d65df1 | |||
| 7c048c9c54 | |||
| 9fbf500b6c | |||
| 147be0c92c | |||
| 54583c604f | |||
| de41a665b2 | |||
| 98981f00a9 | |||
| 074a8debf4 | |||
| a4d66cdbc9 | |||
| 2270e64148 | |||
| 0e99d99051 | |||
| b0ddc66e06 | |||
| 364a17f31a | |||
| 04d18c4b22 | |||
| 44330f2351 | |||
| 117cf71065 | |||
| 44b58d58ae | |||
| b5a924bdb6 | |||
| d1fd17200f | |||
| b035bd6c1e | |||
| 02e2cb1673 | |||
| 53954426a1 | |||
| d02c4e51f9 | |||
| 26e9db263d | |||
| ae56085f95 | |||
| 125160e0f5 | |||
| 2f251dd05a | |||
| 88b2b49d3b | |||
| 1fb5c0c177 | |||
| 9fdccf8371 | |||
| f5d7fdcb58 | |||
| b639720190 | |||
| 347dbd8cd1 | |||
| 204b47d174 | |||
| d738a4c61f | |||
| 6ebbd91592 | |||
| 17f587252e | |||
| 5f1a2ed69c | |||
| 0668b90ba9 | |||
| fa4a759bc1 | |||
| 890e23f2d9 | |||
| e08a79b067 | |||
| 0dc42567c2 | |||
| 94a3e83b04 | |||
| 7f71d01c2d | |||
| bd31358649 | |||
| be054689f8 | |||
| 4fada9b931 | |||
| 795e4c1273 | |||
| 9174b46f66 | |||
| 1f3c93d7ee | |||
| c85d02d889 | |||
| 8c7c2026d9 | |||
| e847a5b871 | |||
| c022466bf8 | |||
| e03e9e0736 | |||
| 0caa7b5519 | |||
| c5f1e60414 | |||
| 1f5c7208b8 | |||
| 3edba0f8bb | |||
| 786bf00521 | |||
| b944357071 | |||
| 59a7b190d5 | |||
| bd364ded70 | |||
| da74830353 | |||
| fff0488f89 | |||
| 63431deffb | |||
| a1757571b4 | |||
| 3ecb7aaf83 | |||
| 1ee166ea4e | |||
| 8e15dee11d | |||
| 3610b16a6c | |||
| 4d50007377 | |||
| d3e2af0215 | |||
| cff71ad95b | |||
| edeef69ae9 | |||
| 4505e582b2 | |||
| cf4fda2f7d | |||
| 770559040e | |||
| 711061347c | |||
| 25169c2838 | |||
| 9685870c14 | |||
| 8221760fbd | |||
| 673785f576 | |||
| 51ae476077 | |||
| 8f7199bce6 | |||
| d282db0c02 |
@@ -1,86 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# 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 is a utility to bring xcoll (plus enhancements) to confluent
|
||||
# the core engine is textgroup.py, this simply provides a CLI to use
|
||||
# generically
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.textgroup as tg
|
||||
import confluent.client
|
||||
|
||||
argparser = optparse.OptionParser(usage="Usage: <other command> | %prog [options]")
|
||||
argparser.add_option('-a', '--abbreviate', action='store_true',
|
||||
help='Attempt to use confluent server to shorten noderanges')
|
||||
argparser.add_option('-d', '--diff', action='store_true',
|
||||
help='Show what differs between most common '
|
||||
'output group and others')
|
||||
argparser.add_option('-w', '--watch', action='store_true',
|
||||
help='Show intermediate results while running')
|
||||
argparser.add_option('-s', '--skipcommon', action='store_true',
|
||||
help='Do not print most common result, only non modal '
|
||||
'groups, useful when combined with -d')
|
||||
argparser.add_option('-c', '--count', action='store_true',
|
||||
help='Also display count of nodes in a given group')
|
||||
argparser.add_option('-r', '--reverse', action='store_true',
|
||||
help='Reverse sort order to show biggest output group '
|
||||
'last')
|
||||
(options, args) = argparser.parse_args()
|
||||
if sys.stdin.isatty():
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
grouped = tg.GroupedData()
|
||||
|
||||
if options.abbreviate:
|
||||
grouped = tg.GroupedData(confluent.client.Command())
|
||||
else:
|
||||
grouped = tg.GroupedData()
|
||||
|
||||
def print_current():
|
||||
if options.diff:
|
||||
grouped.print_deviants(skipmodal=options.skipcommon, count=options.count,
|
||||
reverse=options.reverse)
|
||||
else:
|
||||
grouped.print_all(skipmodal=options.skipcommon,
|
||||
count=options.count,
|
||||
reverse=options.reverse)
|
||||
sys.stdout.flush()
|
||||
|
||||
fullline = sys.stdin.readline()
|
||||
while fullline:
|
||||
for line in fullline.split('\n'):
|
||||
if not line:
|
||||
continue
|
||||
if ': ' not in line:
|
||||
line = 'UNKNOWN: ' + line
|
||||
grouped.add_line(*line.split(': ', 1))
|
||||
if options.watch:
|
||||
sys.stdout.write('\x1b[2J\x1b[;H') # clear screen
|
||||
print_current()
|
||||
fullline = sys.stdin.readline()
|
||||
if not options.watch:
|
||||
print_current()
|
||||
|
||||
@@ -76,7 +76,6 @@ import confluent.tlvdata as tlvdata
|
||||
import confluent.client as client
|
||||
|
||||
conserversequence = '\x05c' # ctrl-e, c
|
||||
clearpowermessage = False
|
||||
|
||||
oldtcattr = None
|
||||
fd = sys.stdin
|
||||
@@ -116,7 +115,6 @@ def print_help():
|
||||
|
||||
|
||||
def updatestatus(stateinfo={}):
|
||||
global powerstate, powertime, clearpowermessage
|
||||
status = consolename
|
||||
info = []
|
||||
for statekey in stateinfo:
|
||||
@@ -131,15 +129,6 @@ def updatestatus(stateinfo={}):
|
||||
# error will be repeated if relevant
|
||||
# avoid keeping it around as stale
|
||||
del laststate['error']
|
||||
if 'state' in stateinfo: # currently only read power means anything
|
||||
newpowerstate = stateinfo['state']['value']
|
||||
if newpowerstate != powerstate and newpowerstate == 'off':
|
||||
sys.stdout.write("\x1b[2J\x1b[;H[powered off]\r\n")
|
||||
clearpowermessage = True
|
||||
if newpowerstate == 'on' and clearpowermessage:
|
||||
sys.stdout.write("\x1b[2J\x1b[;H")
|
||||
clearpowermessage = False
|
||||
powerstate = newpowerstate
|
||||
if 'clientcount' in laststate and laststate['clientcount'] != 1:
|
||||
info.append('clients: %d' % laststate['clientcount'])
|
||||
if 'bufferage' in stateinfo and stateinfo['bufferage'] is not None:
|
||||
@@ -154,7 +143,7 @@ def updatestatus(stateinfo={}):
|
||||
info.append(time.strftime('%H:%M', time.localtime(showtime)))
|
||||
if info:
|
||||
status += ' [' + ','.join(info) + ']'
|
||||
if os.environ.get('TERM', '') not in ('linux'):
|
||||
if os.environ['TERM'] not in ('linux'):
|
||||
sys.stdout.write('\x1b]0;console: %s\x07' % status)
|
||||
sys.stdout.flush()
|
||||
|
||||
@@ -186,7 +175,7 @@ def recurse_format(datum, levels=0):
|
||||
|
||||
|
||||
def prompt():
|
||||
if os.environ.get('TERM', '') not in ('linux'):
|
||||
if os.environ['TERM'] not in ('linux'):
|
||||
sys.stdout.write('\x1b]0;confetty: %s\x07' % target)
|
||||
try:
|
||||
return raw_input(target + ' -> ')
|
||||
@@ -295,17 +284,14 @@ def print_result(res):
|
||||
if 'databynode' in res:
|
||||
print_result(res['databynode'])
|
||||
return
|
||||
for key in sorted(res):
|
||||
for key in res.iterkeys():
|
||||
notes = []
|
||||
if res[key] is None:
|
||||
attrstr = '%s=""' % key
|
||||
elif type(res[key]) == list:
|
||||
attrstr = '%s=%s' % (key, recurse_format(res[key]))
|
||||
elif not isinstance(res[key], dict):
|
||||
try:
|
||||
print '{0}: {1}'.format(key, res[key])
|
||||
except UnicodeEncodeError:
|
||||
print '{0}: {1}'.format(key, repr(res[key]))
|
||||
print '{0}: {1}'.format(key, res[key])
|
||||
continue
|
||||
elif 'value' in res[key] and res[key]['value'] is not None:
|
||||
attrstr = '%s="%s"' % (key, res[key]['value'])
|
||||
@@ -351,7 +337,7 @@ def do_command(command, server):
|
||||
return
|
||||
argv[0] = argv[0].lower()
|
||||
if argv[0] == 'exit':
|
||||
if os.environ.get('TERM', '') not in ('linux'):
|
||||
if os.environ['TERM'] not in ('linux'):
|
||||
sys.stdout.write('\x1b]0;\x07')
|
||||
sys.exit(0)
|
||||
elif argv[0] in ('help', '?'):
|
||||
@@ -474,11 +460,8 @@ def createresource(args):
|
||||
if keydata is None:
|
||||
return
|
||||
targpath = fullpath_target(resname)
|
||||
if targpath.startswith('/noderange//'):
|
||||
collection = targpath
|
||||
else:
|
||||
collection, _, resname = targpath.rpartition('/')
|
||||
keydata['name'] = resname
|
||||
collection, _, resname = targpath.rpartition('/')
|
||||
keydata['name'] = resname
|
||||
makecall(session.create, (collection, keydata))
|
||||
|
||||
|
||||
@@ -600,7 +583,7 @@ def quitconfetty(code=0, fullexit=False, fixterm=True):
|
||||
# Request default color scheme, to undo potential weirdness of terminal
|
||||
sys.stdout.write('\x1b[m')
|
||||
if fullexit:
|
||||
if os.environ.get('TERM', '') not in ('linux'):
|
||||
if os.environ['TERM'] not in ('linux'):
|
||||
sys.stdout.write('\x1b]0;\x07')
|
||||
sys.exit(code)
|
||||
else:
|
||||
@@ -726,23 +709,8 @@ def conserver_command(filehandle, localcommand):
|
||||
else:
|
||||
print("Unknown power state.]\r")
|
||||
|
||||
check_power_state()
|
||||
elif localcommand[0] == 'r':
|
||||
sys.stdout.write('\x1b7\x1b[999;999H\x1b[6n')
|
||||
sys.stdout.flush()
|
||||
reply = ''
|
||||
while 'R' not in reply:
|
||||
try:
|
||||
reply += sys.stdin.read(1)
|
||||
except IOError:
|
||||
pass
|
||||
reply = reply.replace('\x1b[', '')
|
||||
reply = reply.replace('R', '')
|
||||
height, width = reply.split(';')
|
||||
sys.stdout.write('\x1b8')
|
||||
sys.stdout.flush()
|
||||
print('sending stty commands]')
|
||||
return 'stty columns {0}\rstty rows {1}\r'.format(width, height)
|
||||
#check_power_state()
|
||||
|
||||
elif localcommand[0] == '?':
|
||||
print("help]\r")
|
||||
print(". exit console\r")
|
||||
@@ -753,7 +721,6 @@ def conserver_command(filehandle, localcommand):
|
||||
print("pbs boot to setup\r")
|
||||
print("pbn boot to network\r")
|
||||
print("pb<ent> boot to default\r")
|
||||
print("r send stty command to resize terminal\r")
|
||||
print("<cr> abort command\r")
|
||||
elif localcommand[0] == '\x0d':
|
||||
print("ignored]\r")
|
||||
@@ -857,17 +824,27 @@ powertime = None
|
||||
|
||||
|
||||
def check_power_state():
|
||||
tlvdata.send(
|
||||
session.connection,
|
||||
{'operation': 'retrieve',
|
||||
'path': '/nodes/' + consolename + '/power/state'})
|
||||
return
|
||||
global powerstate, powertime
|
||||
for rsp in session.read('/nodes/' + consolename + '/power/state'):
|
||||
if type(rsp) == dict and 'state' in rsp:
|
||||
newpowerstate = rsp['state']['value']
|
||||
powertime = time.time()
|
||||
if newpowerstate != powerstate and newpowerstate == 'off':
|
||||
sys.stdout.write("\x1b[2J\x1b[;H[powered off]\r\n")
|
||||
powerstate = newpowerstate
|
||||
elif type(rsp) == dict and '_requestdone' in rsp:
|
||||
break
|
||||
elif type(rsp) == dict:
|
||||
updatestatus(rsp)
|
||||
else:
|
||||
sys.stdout.write(rsp)
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
while inconsole or not doexit:
|
||||
if inconsole:
|
||||
rdylist, _, _ = select.select(
|
||||
(sys.stdin, session.connection), (), (), 10)
|
||||
(sys.stdin, session.connection), (), (), 60)
|
||||
for fh in rdylist:
|
||||
if fh == session.connection:
|
||||
# this only should get called in the
|
||||
@@ -883,9 +860,6 @@ while inconsole or not doexit:
|
||||
updatestatus(data)
|
||||
continue
|
||||
if data is not None:
|
||||
if clearpowermessage:
|
||||
sys.stdout.write("\x1b[2J\x1b[;H")
|
||||
clearpowermessage = False
|
||||
try:
|
||||
sys.stdout.write(data)
|
||||
except IOError: # Some times circumstances are bad
|
||||
@@ -928,10 +902,8 @@ while inconsole or not doexit:
|
||||
tlvdata.send(session.connection, myinput)
|
||||
except IOError:
|
||||
pass
|
||||
if powerstate is None or powertime < time.time() - 10: # Check powerstate every 10 seconds
|
||||
powertime = time.time()
|
||||
powerstate = True
|
||||
check_power_state()
|
||||
#if powerstate is None or powertime < time.time() - 60: # Check powerstate every 60 seconds
|
||||
# check_power_state()
|
||||
else:
|
||||
currcommand = prompt()
|
||||
try:
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
#!/bin/sh
|
||||
# This will take a given directory and make a 'big floppy image'
|
||||
# out of it, suitable for nodemedia upload.
|
||||
|
||||
if [ -z "$1" -o -z "$2" ]; then
|
||||
echo "Usage: $0 <directory> <imagefile>"
|
||||
exit 1
|
||||
fi
|
||||
# Get the needed payload side
|
||||
SIZE=$(du -sB 512 $1|awk '{print $1}')
|
||||
ENTRIES=$(find $1 |wc -l)
|
||||
# Also, each file and directory has overhead, pad size by 32kb per file,
|
||||
# which should be overkill but other values proved to be inadequate
|
||||
# A deeper understanding of VFAT would probably allow for more precise value
|
||||
SIZE=$((SIZE + ENTRIES * 64))
|
||||
dd if=/dev/zero of=$2 bs=512 count=$SIZE
|
||||
# Make a big single sided floppy with many many tracks, saves on complex math
|
||||
mformat -i $2 -d 1 -t $SIZE -s 1 -h 1 ::
|
||||
mcopy -i $2 -s $1/* ::
|
||||
@@ -37,14 +37,10 @@ import confluent.client as client
|
||||
argparser = optparse.OptionParser(
|
||||
usage='''\n %prog [-b] noderange [list of attributes] \
|
||||
\n %prog -c noderange <list of attributes> \
|
||||
\n %prog -e noderange <attribute names to set> \
|
||||
\n %prog noderange attribute1=value1 attribute2=value,...
|
||||
\n ''')
|
||||
argparser.add_option('-b', '--blame', action='store_true',
|
||||
help='Show information about how attributes inherited')
|
||||
argparser.add_option('-e', '--environment', action='store_true',
|
||||
help='Set attributes, but from environment variable of '
|
||||
'same name')
|
||||
argparser.add_option('-c', '--clear', action='store_true',
|
||||
help='Clear attributes')
|
||||
(options, args) = argparser.parse_args()
|
||||
@@ -59,7 +55,6 @@ try:
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
|
||||
@@ -67,11 +62,7 @@ exitcode = 0
|
||||
nodetype="noderange"
|
||||
|
||||
if len(args) > 1:
|
||||
if "=" in args[1] or options.clear or options.environment:
|
||||
if "=" in args[1] and options.clear:
|
||||
print("Can not clear and set at the same time!")
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
if "=" in args[1] or options.clear:
|
||||
exitcode=client.updateattrib(session,args,nodetype, noderange, options)
|
||||
try:
|
||||
# setting user output to what the user inputs
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015-2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
argparser = optparse.OptionParser(usage="Usage: %prog <noderange>")
|
||||
(options, args) = argparser.parse_args()
|
||||
try:
|
||||
noderange = args[0]
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
|
||||
errorNodes = set([])
|
||||
|
||||
success = session.simple_noderange_command(noderange, 'configuration/management_controller/reset', 'reset', key='state', errnodes=errorNodes) # = 0 if successful
|
||||
|
||||
# Determine which nodes were successful and print them
|
||||
|
||||
allNodes = set([])
|
||||
|
||||
for node in session.read('/noderange/{0}/nodes/'.format(noderange)):
|
||||
allNodes.add(node['item']['href'].replace("/", ""))
|
||||
|
||||
goodNodes = allNodes - errorNodes
|
||||
|
||||
for node in goodNodes:
|
||||
print node + ": BMC Reset Successful"
|
||||
|
||||
|
||||
sys.exit(success)
|
||||
@@ -49,7 +49,6 @@ except IndexError:
|
||||
'Usage: {0} <noderange> [default|cd|network|setup|hd]\n'.format(
|
||||
sys.argv[0]))
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
bootdev = None
|
||||
if len(sys.argv) > 2:
|
||||
bootdev = sys.argv[2]
|
||||
@@ -62,12 +61,11 @@ if options.biosmode:
|
||||
else:
|
||||
bootmode = 'uefi'
|
||||
|
||||
errnodes = set([])
|
||||
rc = session.simple_noderange_command(noderange, '/boot/nextdevice', bootdev,
|
||||
bootmode=bootmode,
|
||||
persistent=options.persist,
|
||||
errnodes=errnodes)
|
||||
if errnodes:
|
||||
noderange = noderange + ',-(' + ','.join(errnodes) + ')'
|
||||
rc |= session.simple_noderange_command(noderange, '/power/state', 'boot')
|
||||
sys.exit(rc)
|
||||
persistent=options.persist)
|
||||
|
||||
if rc:
|
||||
sys.exit(rc)
|
||||
else:
|
||||
sys.exit(session.simple_noderange_command(noderange, '/power/state', 'boot'))
|
||||
@@ -1,213 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# 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 os
|
||||
import signal
|
||||
import optparse
|
||||
import sys
|
||||
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
except AttributeError:
|
||||
pass
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
class NullOpt(object):
|
||||
blame = None
|
||||
clear = None
|
||||
|
||||
|
||||
def bailout(msg, code=1):
|
||||
sys.stderr.write(msg + '\n')
|
||||
sys.exit(code)
|
||||
|
||||
|
||||
argparser = optparse.OptionParser()
|
||||
argparser.add_option('-c', '--comparedefault', dest='comparedefault',
|
||||
action='store_true', default=False,
|
||||
help='Compare given settings to default or list settings '
|
||||
'that are non default')
|
||||
argparser.add_option('-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('-x', '--exclude', dest='exclude',
|
||||
action='store_true', default=False,
|
||||
help='Treat positional arguments as items to not '
|
||||
'examine, compare, or restore default')
|
||||
(options, args) = argparser.parse_args()
|
||||
|
||||
cfgpaths = {
|
||||
'bmc.ipv4_address': (
|
||||
'configuration/management_controller/net_interfaces/management',
|
||||
'ipv4_address'),
|
||||
'bmc.ipv4_method': (
|
||||
'configuration/management_controller/net_interfaces/management',
|
||||
'ipv4_configuration'),
|
||||
'bmc.ipv4_gateway': (
|
||||
'configuration/management_controller/net_interfaces/management',
|
||||
'ipv4_gateway'),
|
||||
}
|
||||
|
||||
autodeps = {
|
||||
'bmc.ipv4_address': (('bmc.ipv4_method', 'static'),)
|
||||
}
|
||||
|
||||
try:
|
||||
noderange = args[0]
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
setmode = None
|
||||
assignment = {}
|
||||
queryparms = {}
|
||||
printsys = []
|
||||
setsys = {}
|
||||
forceset = False
|
||||
needval = None
|
||||
|
||||
if len(args) == 1 or options.exclude:
|
||||
if not options.exclude:
|
||||
printsys = 'all'
|
||||
for candidate in cfgpaths:
|
||||
path, attrib = cfgpaths[candidate]
|
||||
path = '/noderange/{0}/{1}'.format(noderange, path)
|
||||
if path not in queryparms:
|
||||
queryparms[path] = {}
|
||||
queryparms[path][attrib] = candidate
|
||||
|
||||
|
||||
def _assign_value():
|
||||
if key not in cfgpaths:
|
||||
setsys[key] = value
|
||||
for depkey, depval in autodeps.get(key, []):
|
||||
assignment[depkey] = depval
|
||||
assignment[key] = value
|
||||
|
||||
|
||||
for param in args[1:]:
|
||||
if param == 'show':
|
||||
continue # forgive muscle memory of pasu users
|
||||
if param == 'set':
|
||||
setmode = True
|
||||
forceset = True
|
||||
continue
|
||||
if needval:
|
||||
key = needval
|
||||
value = param
|
||||
_assign_value()
|
||||
continue
|
||||
if '=' in param or param[-1] == ':' or forceset:
|
||||
if setmode is None:
|
||||
setmode = True
|
||||
if setmode != True:
|
||||
bailout('Cannot do set and query in same command')
|
||||
if '=' in param:
|
||||
key, _, value = param.partition('=')
|
||||
_assign_value()
|
||||
elif param[-1] == ':':
|
||||
needval = param[:-1]
|
||||
else:
|
||||
needval = param
|
||||
else:
|
||||
if setmode is None:
|
||||
setmode = False
|
||||
if setmode != False:
|
||||
bailout('Cannot do set and query in same command')
|
||||
if '.' not in param:
|
||||
matchedparms = False
|
||||
for candidate in cfgpaths:
|
||||
if candidate.startswith('{0}.'.format(param)):
|
||||
matchedparms = True
|
||||
if not options.exclude:
|
||||
path, attrib = cfgpaths[candidate]
|
||||
path = '/noderange/{0}/{1}'.format(noderange, path)
|
||||
if path not in queryparms:
|
||||
queryparms[path] = {}
|
||||
queryparms[path][attrib] = candidate
|
||||
else:
|
||||
try:
|
||||
del queryparms[path]
|
||||
except KeyError:
|
||||
pass
|
||||
if not matchedparms:
|
||||
printsys.append(param)
|
||||
elif param not in cfgpaths:
|
||||
printsys.append(param)
|
||||
else:
|
||||
path, attrib = cfgpaths[param]
|
||||
path = '/noderange/{0}/{1}'.format(noderange, path)
|
||||
if path not in queryparms:
|
||||
queryparms[path] = {}
|
||||
queryparms[path][attrib] = param
|
||||
session = client.Command()
|
||||
rcode = 0
|
||||
if setmode:
|
||||
if options.exclude:
|
||||
sys.stderr.write('Cannot use exclude and assign at the same time\n')
|
||||
sys.exit(1)
|
||||
updatebypath = {}
|
||||
attrnamebypath = {}
|
||||
for key in assignment:
|
||||
if key not in cfgpaths:
|
||||
path = 'configuration/system/all'
|
||||
attrib = key
|
||||
else:
|
||||
path, attrib = cfgpaths[key]
|
||||
if path not in updatebypath:
|
||||
updatebypath[path] = {}
|
||||
attrnamebypath[path] = {}
|
||||
updatebypath[path][attrib] = assignment[key]
|
||||
attrnamebypath[path][attrib] = key
|
||||
# well, we want to expand things..
|
||||
# check ipv4, if requested change method to static
|
||||
for path in updatebypath:
|
||||
for fr in session.update('/noderange/{0}/{1}'.format(noderange, path),
|
||||
updatebypath[path]):
|
||||
for node in fr['databynode']:
|
||||
r = fr['databynode'][node]
|
||||
if 'error' in r:
|
||||
sys.stderr.write(node + ': ' + r['error'] + '\n')
|
||||
if 'errorcode' in r:
|
||||
rcode |= r['errorcode']
|
||||
if 'value' not in r:
|
||||
continue
|
||||
keyval = r['value']
|
||||
key, val = keyval.split('=')
|
||||
if key in attrnamebypath[path]:
|
||||
key = attrnamebypath[path][key]
|
||||
print('{0}: {1}: {2}'.format(node, key, val))
|
||||
else:
|
||||
for path in queryparms:
|
||||
if options.comparedefault:
|
||||
continue
|
||||
client.print_attrib_path(path, session, list(queryparms[path]),
|
||||
NullOpt(), queryparms[path])
|
||||
if printsys or options.exclude:
|
||||
if printsys == 'all':
|
||||
printsys = []
|
||||
path = '/noderange/{0}/configuration/system/all'.format(noderange)
|
||||
client.print_attrib_path(path, session, printsys,
|
||||
options)
|
||||
sys.exit(rcode)
|
||||
@@ -1,58 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
argparser = optparse.OptionParser(
|
||||
usage='''\n %prog noderange attribute1=value1 attribute2=value,...
|
||||
\n ''')
|
||||
(options, args) = argparser.parse_args()
|
||||
requestargs=None
|
||||
try:
|
||||
noderange = args[0]
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
attribs = {'name': noderange}
|
||||
for arg in args[1:]:
|
||||
key, val = arg.split('=')
|
||||
attribs[key] = val
|
||||
for r in session.create('/noderange/', attribs):
|
||||
if 'error' in r:
|
||||
sys.stderr.write(r['error'] + '\n')
|
||||
exitcode |= 1
|
||||
if 'created' in r:
|
||||
print('{0}: created'.format(r['created']))
|
||||
sys.exit(exitcode)
|
||||
@@ -1,286 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# 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 csv
|
||||
import optparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
tabformat = '{0:>15}|{1:>15}|{2:>15}|{3:>36}|{4:>17}|{5:>12}|{6:>48}'
|
||||
columns = ['Node', 'Model', 'Serial', 'UUID', 'Mac Address', 'Type',
|
||||
'Current IP Addresses']
|
||||
delimit = ['-' * 15, '-' * 15, '-' * 15, '-' * 36, '-' * 17, '-' * 12,
|
||||
'-' * 48]
|
||||
|
||||
|
||||
def dumpmacs(procinfo):
|
||||
return ','.join(procinfo['macs']) # + procinfo.get('relatedmacs', []))
|
||||
|
||||
|
||||
def print_disco(options, session, currmac):
|
||||
procinfo = {}
|
||||
for tmpinfo in session.read('/discovery/by-mac/{0}'.format(currmac)):
|
||||
|
||||
procinfo.update(tmpinfo)
|
||||
record = [procinfo['nodename'], procinfo['modelnumber'],
|
||||
procinfo['serialnumber'], procinfo['uuid'], dumpmacs(procinfo),
|
||||
','.join(procinfo['types']),
|
||||
','.join(sorted(procinfo['ipaddrs']))]
|
||||
if options.csv:
|
||||
csv.writer(sys.stdout).writerow(record)
|
||||
else:
|
||||
print(tabformat.format(*record))
|
||||
|
||||
|
||||
def process_header(header):
|
||||
# normalize likely header titles
|
||||
fields = []
|
||||
broken = False
|
||||
for datum in header:
|
||||
datum = datum.lower()
|
||||
if datum.startswith('node') or datum.startswith('name'):
|
||||
fields.append('node')
|
||||
elif datum in ('nodegroup', 'nodegroups', 'group', 'groups'):
|
||||
fields.append('groups')
|
||||
elif datum.startswith('mac') or datum.startswith('ether'):
|
||||
fields.append('mac')
|
||||
elif datum.startswith('serial') or datum in ('sn', 's/n'):
|
||||
fields.append('serial')
|
||||
elif datum == 'uuid':
|
||||
fields.append('uuid')
|
||||
elif datum in ('bmc', 'imm', 'xcc'):
|
||||
fields.append('hardwaremanagement.manager')
|
||||
elif datum in ('bmc gateway', 'xcc gateway', 'imm gateway'):
|
||||
fields.append('net.bmc.gateway')
|
||||
elif datum in ('bmcuser', 'username', 'user'):
|
||||
fields.append('secret.hardwaremanagementuser')
|
||||
elif datum in ('bmcpass', 'password', 'pass'):
|
||||
fields.append('secret.hardwaremanagementpassword')
|
||||
else:
|
||||
print("Unrecognized column name {0}".format(datum))
|
||||
broken = True
|
||||
if broken:
|
||||
sys.exit(1)
|
||||
return tuple(fields)
|
||||
|
||||
|
||||
def datum_complete(datum):
|
||||
if 'node' not in datum or not datum['node']:
|
||||
sys.stderr.write('Nodename is a required field')
|
||||
return False
|
||||
provided = set(datum)
|
||||
required = set(['serial', 'uuid', 'mac'])
|
||||
for field in provided & required:
|
||||
if datum[field]:
|
||||
break
|
||||
else:
|
||||
sys.stderr.write('One of the fields "Serial Number", "UUID", or '
|
||||
'"MAC Address" must be provided')
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
searchkeys = set(['mac', 'serial', 'uuid'])
|
||||
|
||||
|
||||
def search_record(datum, options, session):
|
||||
for searchkey in searchkeys:
|
||||
options.__dict__[searchkey] = None
|
||||
for searchkey in searchkeys & set(datum):
|
||||
options.__dict__[searchkey] = datum[searchkey]
|
||||
return list(list_matching_macs(options, session))
|
||||
|
||||
|
||||
def datum_to_attrib(datum):
|
||||
for key in ('serial', 'uuid', 'mac'):
|
||||
try:
|
||||
del datum[key]
|
||||
except KeyError:
|
||||
pass
|
||||
datum['name'] = datum['node']
|
||||
del datum['node']
|
||||
return datum
|
||||
|
||||
def import_csv(options, session):
|
||||
nodedata = []
|
||||
with open(options.importfile, 'r') as datasrc:
|
||||
records = csv.reader(datasrc)
|
||||
fields = process_header(next(records))
|
||||
for record in records:
|
||||
currfields = list(fields)
|
||||
nodedatum = {}
|
||||
for datum in record:
|
||||
nodedatum[currfields.pop(0)] = datum
|
||||
if not datum_complete(nodedatum):
|
||||
sys.exit(1)
|
||||
if not search_record(nodedatum, options, session):
|
||||
sys.stderr.write(
|
||||
"Could not match the following data: " +
|
||||
repr(nodedatum) + '\n')
|
||||
sys.exit(1)
|
||||
nodedata.append(nodedatum)
|
||||
for datum in nodedata:
|
||||
maclist = search_record(datum, options, session)
|
||||
datum = datum_to_attrib(datum)
|
||||
nodename = datum['name']
|
||||
for res in session.create('/nodes/', datum):
|
||||
if 'error' in res:
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
continue
|
||||
elif 'created' in res:
|
||||
print('Defined ' + res['created'])
|
||||
else:
|
||||
print(repr(res))
|
||||
for mac in maclist:
|
||||
for res in session.update('/discovery/by-mac/{0}'.format(mac),
|
||||
{'node': nodename}):
|
||||
if 'error' in res:
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
continue
|
||||
elif 'assigned' in res:
|
||||
print('Discovered ' + res['assigned'])
|
||||
else:
|
||||
print(repr(res))
|
||||
|
||||
|
||||
def list_discovery(options, session):
|
||||
if options.csv:
|
||||
csv.writer(sys.stdout).writerow(columns)
|
||||
else:
|
||||
print(tabformat.format(*columns))
|
||||
print(tabformat.format(*delimit))
|
||||
for mac in list_matching_macs(options, session):
|
||||
print_disco(options, session, mac)
|
||||
|
||||
def clear_discovery(options, session):
|
||||
for mac in list_matching_macs(options, session):
|
||||
for res in session.delete('/discovery/by-mac/{0}'.format(mac)):
|
||||
if 'deleted' in res:
|
||||
print('Cleared info for {0}'.format(res['deleted']))
|
||||
else:
|
||||
print(repr(res))
|
||||
|
||||
def list_matching_macs(options, session):
|
||||
path = '/discovery/'
|
||||
if options.model:
|
||||
path += 'by-model/{0}/'.format(options.model)
|
||||
if options.serial:
|
||||
path += 'by-serial/{0}/'.format(options.serial)
|
||||
if options.uuid:
|
||||
path += 'by-uuid/{0}/'.format(options.uuid)
|
||||
if options.type:
|
||||
path += 'by-type/{0}/'.format(options.type)
|
||||
if options.state:
|
||||
if options.state == 'unknown':
|
||||
options.state = 'unidentified'
|
||||
path += 'by-state/{0}/'.format(options.state).lower()
|
||||
if options.mac:
|
||||
path += 'by-mac/{0}'.format(options.mac)
|
||||
result = list(session.read(path))[0]
|
||||
if 'error' in result:
|
||||
return []
|
||||
return [options.mac.replace(':', '-')]
|
||||
else:
|
||||
path += 'by-mac/'
|
||||
return [x['item']['href'] for x in session.read(path)]
|
||||
|
||||
def assign_discovery(options, session):
|
||||
abort = False
|
||||
if options.importfile:
|
||||
return import_csv(options, session)
|
||||
if not (options.serial or options.uuid or options.mac):
|
||||
sys.stderr.write(
|
||||
"UUID (-u), serial (-s), or ether address (-e) required for "
|
||||
"assignment\n")
|
||||
abort = True
|
||||
if not options.node:
|
||||
sys.stderr.write("Node (-n) must be specified for assignment\n")
|
||||
abort = True
|
||||
if abort:
|
||||
sys.exit(1)
|
||||
matches = list_matching_macs(options, session)
|
||||
if not matches:
|
||||
sys.stderr.write("No matching discovery candidates found\n")
|
||||
sys.exit(1)
|
||||
for res in session.update('/discovery/by-mac/{0}'.format(matches[0]),
|
||||
{'node': options.node}):
|
||||
if 'assigned' in res:
|
||||
print('Assigned: {0}'.format(res['assigned']))
|
||||
else:
|
||||
print(repr(res))
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
parser = optparse.OptionParser(
|
||||
usage='Usage: %prog [list|assign|rescan|clear] [options]')
|
||||
# -a for 'address' maybe?
|
||||
# order by
|
||||
# show state (discovered or..
|
||||
# nodediscover approve?
|
||||
# flush to clear old data out? (e.g. no good way to age pxe data)
|
||||
# also delete discovery datum... more targeted
|
||||
# defect: -t lenovo-imm returns all
|
||||
parser.add_option('-m', '--model', dest='model',
|
||||
help='Operate with nodes matching the specified model '
|
||||
'number', metavar='MODEL')
|
||||
parser.add_option('-d', '--discoverystate', dest='state',
|
||||
help='The discovery state of the entries (discovered, '
|
||||
'identified, and unidentified)')
|
||||
parser.add_option('-s', '--serial', dest='serial',
|
||||
help='Operate against the system matching the specified '
|
||||
'serial number', metavar='SERIAL')
|
||||
parser.add_option('-u', '--uuid', dest='uuid',
|
||||
help='Operate against the system matching the specified '
|
||||
'UUID', metavar='UUID')
|
||||
parser.add_option('-n', '--node', help='Operate with the given nodename')
|
||||
parser.add_option('-e', '--ethaddr', dest='mac',
|
||||
help='Operate against the system with the specified MAC '
|
||||
'address', metavar='MAC')
|
||||
parser.add_option('-t', '--type', dest='type',
|
||||
help='Operate against the system of the specified type',
|
||||
metavar='TYPE')
|
||||
parser.add_option('-c', '--csv', dest='csv',
|
||||
help='Use CSV formatted output', action='store_true')
|
||||
parser.add_option('-i', '--import', dest='importfile',
|
||||
help='Import bulk assignment data from given CSV file',
|
||||
metavar='IMPORT.CSV')
|
||||
(options, args) = parser.parse_args()
|
||||
if len(args) == 0 or args[0] not in ('list', 'assign', 'rescan', 'clear'):
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
session = client.Command()
|
||||
if args[0] == 'list':
|
||||
list_discovery(options, session)
|
||||
if args[0] == 'clear':
|
||||
clear_discovery(options, session)
|
||||
if args[0] == 'assign':
|
||||
assign_discovery(options, session)
|
||||
if args[0] == 'rescan':
|
||||
list(session.update('/discovery/rescan', {'rescan': 'start'}))
|
||||
print("Rescan initiated")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -15,7 +15,6 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import codecs
|
||||
from datetime import datetime as dt
|
||||
import optparse
|
||||
import os
|
||||
@@ -33,8 +32,6 @@ if path.startswith('/opt'):
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
|
||||
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: %prog [options] noderange [clear]")
|
||||
(options, args) = argparser.parse_args()
|
||||
@@ -43,17 +40,11 @@ try:
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
|
||||
deletemode = False
|
||||
if len(sys.argv) > 3:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
if len(sys.argv) == 3:
|
||||
if sys.argv[2] == 'clear':
|
||||
deletemode = True
|
||||
else:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
|
||||
@@ -19,7 +19,6 @@ import optparse
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
import time
|
||||
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
@@ -31,7 +30,7 @@ if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
import confluent.screensqueeze as sq
|
||||
|
||||
|
||||
exitcode = 0
|
||||
|
||||
@@ -64,107 +63,28 @@ def printfirm(node, prefix, data):
|
||||
version = ' '.join(builddesc)
|
||||
print('{0}: {1}: {2}'.format(node, prefix, version))
|
||||
|
||||
components = ['all']
|
||||
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: %prog <noderange> [update [--backup <file>]]|[<components>]")
|
||||
argparser.add_option('-b', '--backup', action='store_true',
|
||||
help='Target a backup bank rather than primary')
|
||||
argparser = optparse.OptionParser(usage="Usage: %prog <noderange>")
|
||||
(options, args) = argparser.parse_args()
|
||||
upfile = None
|
||||
try:
|
||||
noderange = args[0]
|
||||
if len(args) > 1:
|
||||
if args[1] == 'update':
|
||||
upfile = args[2]
|
||||
else:
|
||||
components = []
|
||||
for arg in args[1:]:
|
||||
components += arg.split(',')
|
||||
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
|
||||
def get_update_progress(session, url):
|
||||
for res in session.read(url):
|
||||
status = res['phase']
|
||||
percent = res['progress']
|
||||
detail = res['detail']
|
||||
if status == 'error':
|
||||
text = 'error!'
|
||||
else:
|
||||
text = '{0}: {1:3.0f}%'.format(status, percent)
|
||||
return text, status, detail
|
||||
|
||||
def update_firmware(session, filename):
|
||||
global exitcode
|
||||
output = sq.ScreenPrinter(noderange, session)
|
||||
nodeurls = {}
|
||||
filename = os.path.abspath(filename)
|
||||
resource = '/noderange/{0}/inventory/firmware/updates/active'.format(
|
||||
noderange)
|
||||
upargs = {'filename': filename}
|
||||
if options.backup:
|
||||
upargs['bank'] = 'backup'
|
||||
noderrs = {}
|
||||
for res in session.create(resource, upargs):
|
||||
if 'created' not in res:
|
||||
for nodename in res.get('databynode', ()):
|
||||
output.set_output(nodename, 'error!')
|
||||
noderrs[nodename] = res['databynode'][nodename].get(
|
||||
'error', 'Unknown Error')
|
||||
continue
|
||||
watchurl = res['created']
|
||||
currnode = watchurl.split('/')[1]
|
||||
nodeurls[currnode] = '/' + watchurl
|
||||
while nodeurls:
|
||||
for node in list(nodeurls):
|
||||
progress, status, err = get_update_progress(
|
||||
session, nodeurls[node])
|
||||
if status == 'error':
|
||||
exitcode = 1
|
||||
noderrs[node] = err
|
||||
if status in ('error', 'complete', 'pending'):
|
||||
list(session.delete(nodeurls[node]))
|
||||
del nodeurls[node]
|
||||
output.set_output(node, progress)
|
||||
time.sleep(2)
|
||||
allerrnodes = ','.join(noderrs)
|
||||
if noderrs:
|
||||
sys.stderr.write(
|
||||
'Nodes had errors updating ({0})!\n'.format(allerrnodes))
|
||||
for node in noderrs:
|
||||
sys.stderr.write('{0}: {1}\n'.format(node, noderrs[node]))
|
||||
|
||||
def show_firmware(session):
|
||||
firmware_shown = False
|
||||
for component in components:
|
||||
for res in session.read(
|
||||
'/noderange/{0}/inventory/firmware/all/{1}'.format(
|
||||
noderange, component)):
|
||||
printerror(res)
|
||||
if 'databynode' not in res:
|
||||
continue
|
||||
for node in res['databynode']:
|
||||
printerror(res['databynode'][node], node)
|
||||
if 'firmware' not in res['databynode'][node]:
|
||||
continue
|
||||
for inv in res['databynode'][node]['firmware']:
|
||||
for prefix in inv:
|
||||
firmware_shown = True
|
||||
printfirm(node, prefix, inv[prefix])
|
||||
if not firmware_shown:
|
||||
argparser.print_help()
|
||||
|
||||
|
||||
try:
|
||||
session = client.Command()
|
||||
if upfile is None:
|
||||
show_firmware(session)
|
||||
else:
|
||||
update_firmware(session, upfile)
|
||||
for res in session.read('/noderange/{0}/inventory/firmware/all/all'.format(
|
||||
noderange)):
|
||||
printerror(res)
|
||||
if 'databynode' not in res:
|
||||
continue
|
||||
for node in res['databynode']:
|
||||
printerror(res['databynode'][node], node)
|
||||
if 'firmware' not in res['databynode'][node]:
|
||||
continue
|
||||
for inv in res['databynode'][node]['firmware']:
|
||||
for prefix in inv:
|
||||
printfirm(node, prefix, inv[prefix])
|
||||
except KeyboardInterrupt:
|
||||
print('')
|
||||
sys.exit(exitcode)
|
||||
@@ -36,15 +36,11 @@ import confluent.client as client
|
||||
argparser = optparse.OptionParser(
|
||||
usage='''\n %prog [options] \
|
||||
\n %prog [options] nodegroup [list of attributes] \
|
||||
\n %prog [options] nodegroup nodes=value1,value2 \
|
||||
\n %prog -e nodegroup <attribute names to set> \
|
||||
\n %prog [options] nodegroup nodes=value1,value2
|
||||
\n %prog [options] nodegroup group=value1,value2 \
|
||||
\n %prog [options] nodegroup group=value1,value2
|
||||
\n ''')
|
||||
argparser.add_option('-b', '--blame', action='store_true',
|
||||
help='Show information about how attributes inherited')
|
||||
argparser.add_option('-e', '--environment', action='store_true',
|
||||
help='Set attributes, but from environment variable of '
|
||||
'same name')
|
||||
argparser.add_option('-c', '--clear', action='store_true',
|
||||
help='Clear variables')
|
||||
(options, args) = argparser.parse_args()
|
||||
@@ -60,7 +56,6 @@ try:
|
||||
nodelist = '/{0}/{1}/'.format(nodetype,nodegroups)
|
||||
except IndexError:
|
||||
nodelist = '/nodegroups/'
|
||||
client.check_globbing(nodegroups)
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
|
||||
@@ -68,10 +63,6 @@ exitcode = 0
|
||||
|
||||
if len(args) > 1:
|
||||
showtype = 'all'
|
||||
if "=" in args[1] and options.clear:
|
||||
print("Can not clear and set at the same time!")
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
exitcode=client.updateattrib(session,args,nodetype, nodegroups, options)
|
||||
try:
|
||||
# setting user output to what the user inputs
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
argparser = optparse.OptionParser(
|
||||
usage='''\n %prog noderange attribute1=value1 attribute2=value,...
|
||||
\n ''')
|
||||
(options, args) = argparser.parse_args()
|
||||
requestargs=None
|
||||
try:
|
||||
noderange = args[0]
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
attribs = {'name': noderange}
|
||||
for arg in args[1:]:
|
||||
key, val = arg.split('=')
|
||||
attribs[key] = val
|
||||
for r in session.create('/nodegroups/', attribs):
|
||||
if 'error' in r:
|
||||
sys.stderr.write(r['error'] + '\n')
|
||||
exitcode |= 1
|
||||
if 'created' in r:
|
||||
print('{0}: created'.format(r['created']))
|
||||
sys.exit(exitcode)
|
||||
@@ -1,52 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
argparser = optparse.OptionParser(
|
||||
usage='''\n %prog noderange
|
||||
\n ''')
|
||||
(options, args) = argparser.parse_args()
|
||||
if len(args) != 1:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
noderange = args[0]
|
||||
client.check_globbing(noderange)
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
for r in session.delete('/nodegroups/{0}'.format(noderange)):
|
||||
if 'error' in r:
|
||||
sys.stderr.write(r['error'] + '\n')
|
||||
exitcode |= 1
|
||||
if 'deleted' in r:
|
||||
print('{0}: deleted'.format(r['deleted']))
|
||||
sys.exit(exitcode)
|
||||
@@ -41,7 +41,6 @@ try:
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -38,13 +38,13 @@ try:
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
|
||||
identifystate = None
|
||||
if len(sys.argv) > 2:
|
||||
identifystate = sys.argv[2]
|
||||
else:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
sys.exit(
|
||||
|
||||
@@ -15,10 +15,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import codecs
|
||||
import optparse
|
||||
import os
|
||||
import re
|
||||
import signal
|
||||
import sys
|
||||
|
||||
@@ -33,10 +31,6 @@ if path.startswith('/opt'):
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
|
||||
|
||||
filters = []
|
||||
|
||||
|
||||
def pretty(text):
|
||||
if text == 'pcislot':
|
||||
@@ -46,17 +40,14 @@ def pretty(text):
|
||||
return text
|
||||
|
||||
def print_mem_info(node, prefix, meminfo):
|
||||
capacity = meminfo['capacity_mb'] / 1024
|
||||
memdescfmt = '{0}GB PC'
|
||||
if meminfo['memory_type'] == 'DDR3 SDRAM':
|
||||
memdescfmt += '3-{1} '
|
||||
elif meminfo['memory_type'] == 'DDR4 SDRAM':
|
||||
memdescfmt += '4-{1} '
|
||||
else:
|
||||
print('{0}: {1}: Unrecognized Memory'.format(node, prefix))
|
||||
return
|
||||
if meminfo['ecc']:
|
||||
memdescfmt += 'ECC '
|
||||
capacity = meminfo['capacity_mb'] / 1024
|
||||
memdescfmt += meminfo['module_type']
|
||||
memdesc = memdescfmt.format(capacity, meminfo['speed'])
|
||||
print('{0}: {1} description: {2}'.format(node, prefix, memdesc))
|
||||
@@ -85,37 +76,19 @@ def printerror(res, node=None):
|
||||
exitcode = 1
|
||||
|
||||
|
||||
url = '/noderange/{0}/inventory/hardware/all/all'
|
||||
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: %prog <noderange> [serial|model|uuid|mac]")
|
||||
argparser = optparse.OptionParser(usage="Usage: %prog <noderange>")
|
||||
(options, args) = argparser.parse_args()
|
||||
try:
|
||||
noderange = sys.argv[1]
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
if len(args) > 1:
|
||||
if args[1] == 'firm':
|
||||
os.execlp('nodefirmware', 'nodefirmware', noderange)
|
||||
else:
|
||||
url = '/noderange/{0}/inventory/hardware/all/system'
|
||||
for arg in args:
|
||||
for arg in arg.split(','):
|
||||
if arg == 'serial':
|
||||
filters.append(re.compile('serial number'))
|
||||
elif arg == 'model':
|
||||
filters.append(re.compile('^model'))
|
||||
filters.append(re.compile('product name'))
|
||||
elif arg == 'uuid':
|
||||
filters.append(re.compile('uuid'))
|
||||
elif arg == 'mac':
|
||||
filters.append(re.compile('mac address'))
|
||||
url = '/noderange/{0}/inventory/hardware/all/all'
|
||||
if len(args) > 1 and args[1] == 'firm':
|
||||
os.execlp('nodefirmware', 'nodefirmware', noderange)
|
||||
try:
|
||||
session = client.Command()
|
||||
for res in session.read(url.format(noderange)):
|
||||
for res in session.read('/noderange/{0}/inventory/hardware/all/all'.format(
|
||||
noderange)):
|
||||
printerror(res)
|
||||
if 'databynode' not in res:
|
||||
continue
|
||||
@@ -126,8 +99,7 @@ try:
|
||||
for inv in res['databynode'][node]['inventory']:
|
||||
prefix = inv['name']
|
||||
if not inv['present']:
|
||||
if not filters:
|
||||
print '{0}: {1}: Not Present'.format(node, prefix)
|
||||
print '{0}: {1}: Not Present'.format(node, prefix)
|
||||
continue
|
||||
info = inv['information']
|
||||
info.pop('board_extra', None)
|
||||
@@ -135,16 +107,9 @@ try:
|
||||
info.pop('chassis_extra', None)
|
||||
info.pop('product_extra', None)
|
||||
if 'memory_type' in info:
|
||||
if not filters:
|
||||
print_mem_info(node, prefix, info)
|
||||
print_mem_info(node, prefix, info)
|
||||
continue
|
||||
for datum in info:
|
||||
if filters:
|
||||
for filter in filters:
|
||||
if filter.match(datum.lower()):
|
||||
break
|
||||
else:
|
||||
continue
|
||||
if info[datum] is None:
|
||||
continue
|
||||
print(u'{0}: {1} {2}: {3}'.format(node, prefix,
|
||||
|
||||
@@ -47,7 +47,6 @@ def main():
|
||||
nodelist = '/noderange/{0}/nodes/'.format(noderange)
|
||||
except IndexError:
|
||||
nodelist = '/nodes/'
|
||||
client.check_globbing(noderange)
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
showtype='all'
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2018 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
import time
|
||||
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
except AttributeError:
|
||||
pass
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
import confluent.screensqueeze as sq
|
||||
|
||||
exitcode = 0
|
||||
|
||||
|
||||
def get_update_progress(session, url):
|
||||
for res in session.read(url):
|
||||
status = res['phase']
|
||||
percent = res['progress']
|
||||
detail = res['detail']
|
||||
if status == 'error':
|
||||
text = 'error!'
|
||||
else:
|
||||
text = '{0}: {1:3.0f}%'.format(status, percent)
|
||||
return text, status, detail
|
||||
|
||||
|
||||
def printerror(res, node=None):
|
||||
global exitcode
|
||||
if 'errorcode' in res:
|
||||
exitcode = res['errorcode']
|
||||
if 'error' in res:
|
||||
if node:
|
||||
sys.stderr.write('{0}: {1}\n'.format(node, res['error']))
|
||||
else:
|
||||
sys.stderr.write('{0}\n'.format(res['error']))
|
||||
if 'errorcode' not in res:
|
||||
exitcode = 1
|
||||
for node in res.get('databynode', ()):
|
||||
printerror(res['databynode'][node], node)
|
||||
|
||||
|
||||
def attach_media(noderange, media):
|
||||
global exitcode
|
||||
session = client.Command()
|
||||
if ':' not in media:
|
||||
sys.stderr.write('Full URL required for attach\n')
|
||||
sys.exit(1)
|
||||
resource = '/noderange/{0}/media/attach'.format(noderange)
|
||||
for res in session.update(resource, {'url': media}):
|
||||
printerror(res)
|
||||
list_media(noderange, media)
|
||||
|
||||
|
||||
def list_media(noderange, media):
|
||||
session = client.Command()
|
||||
resource = '/noderange/{0}/media/current'.format(noderange)
|
||||
for res in session.read(resource):
|
||||
printerror(res)
|
||||
for node in res.get('databynode', []):
|
||||
url = res['databynode'][node].get('url', None)
|
||||
name = res['databynode'][node].get('name', None)
|
||||
if url and not res['databynode'][node].get('secure', False):
|
||||
name += ' (insecure)'
|
||||
if not name:
|
||||
continue
|
||||
print('{0}: {1}'.format(node, url + '/' + name if url else name))
|
||||
|
||||
|
||||
def detach_media(noderange, media):
|
||||
global exitcode
|
||||
session = client.Command()
|
||||
resource = '/noderange/{0}/media/detach'.format(noderange)
|
||||
for res in session.update(resource, {'detachall': 1}):
|
||||
printerror(res)
|
||||
|
||||
|
||||
def upload_media(noderange, media):
|
||||
global exitcode
|
||||
if not os.path.exists(media):
|
||||
sys.stderr.write('Unable to locate requested file {0}\n'.format(
|
||||
media))
|
||||
sys.exit(404)
|
||||
session = client.Command()
|
||||
output = sq.ScreenPrinter(noderange, session)
|
||||
filename = os.path.abspath(media)
|
||||
resource = '/noderange/{0}/media/uploads/'.format(noderange)
|
||||
upargs = {'filename': filename}
|
||||
noderrs = {}
|
||||
nodeurls = {}
|
||||
for res in session.create(resource, upargs):
|
||||
if 'created' not in res:
|
||||
for nodename in res.get('databynode', ()):
|
||||
output.set_output(nodename, 'error!')
|
||||
noderrs[nodename] = res['databynode'][nodename].get(
|
||||
'error', 'Unknown Error')
|
||||
continue
|
||||
watchurl = res['created']
|
||||
currnode = watchurl.split('/')[1]
|
||||
nodeurls[currnode] = '/' + watchurl
|
||||
while nodeurls:
|
||||
for node in list(nodeurls):
|
||||
progress, status, err = get_update_progress(
|
||||
session, nodeurls[node])
|
||||
if status == 'error':
|
||||
exitcode = 1
|
||||
noderrs[node] = err
|
||||
if status in ('error', 'complete', 'pending'):
|
||||
list(session.delete(nodeurls[node]))
|
||||
del nodeurls[node]
|
||||
output.set_output(node, progress)
|
||||
time.sleep(2)
|
||||
allerrnodes = ','.join(noderrs)
|
||||
if noderrs:
|
||||
sys.stderr.write(
|
||||
'Nodes had errors receiving media ({0})!\n'.format(allerrnodes))
|
||||
for node in noderrs:
|
||||
sys.stderr.write('{0}: {1}\n'.format(node, noderrs[node]))
|
||||
list_media(noderange, media)
|
||||
|
||||
funmap = {
|
||||
'upload': upload_media,
|
||||
'attach': attach_media,
|
||||
'detachall': detach_media,
|
||||
'list': list_media,
|
||||
}
|
||||
|
||||
|
||||
class OptParser(optparse.OptionParser):
|
||||
|
||||
def format_epilog(self, formatter):
|
||||
return self.expand_prog_name(self.epilog)
|
||||
|
||||
def main():
|
||||
argparser = OptParser(
|
||||
usage="Usage: %prog <noderange> [list|upload|attach|detachall] "
|
||||
"<filename>|all|<url>",
|
||||
epilog='\nupload will take the specified file and upload it to the '
|
||||
'BMC.\n\n'
|
||||
'attach will instruct the BMC to connect a remote media to the '
|
||||
'specified url.\n\ndetachall will remove *ALL* uploaded and '
|
||||
'attached urls from the BMC\n\nlist shows currently mounted '
|
||||
'media.\n\nSee `man %prog` for more info.\n')
|
||||
(options, args) = argparser.parse_args()
|
||||
media = None
|
||||
try:
|
||||
noderange = args[0]
|
||||
operation = args[1]
|
||||
arglength = 2
|
||||
if operation in ('attach', 'upload'):
|
||||
media = args[2]
|
||||
arglength = 3
|
||||
if len(args) > arglength:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
try:
|
||||
handler = funmap[operation]
|
||||
except KeyError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
handler(noderange, media)
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -34,16 +34,13 @@ import confluent.client as client
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: %prog [options] noderange "
|
||||
"([status|on|off|shutdown|boot|reset])")
|
||||
argparser.add_option('-p', '--showprevious', dest='previous',
|
||||
action='store_true', default=False,
|
||||
help='Show previous power state')
|
||||
(options, args) = argparser.parse_args()
|
||||
try:
|
||||
noderange = args[0]
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
|
||||
setstate = None
|
||||
if len(sys.argv) > 2:
|
||||
if setstate == 'softoff':
|
||||
@@ -57,19 +54,5 @@ if setstate not in (None, 'on', 'off', 'shutdown', 'boot', 'reset'):
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
session.add_precede_key('oldstate')
|
||||
|
||||
if options.previous:
|
||||
# get previous states
|
||||
prev = {}
|
||||
for rsp in session.read("/noderange/{0}/power/state".format(noderange)):
|
||||
# gets previous (current) states
|
||||
|
||||
databynode = rsp["databynode"]
|
||||
|
||||
for node in databynode:
|
||||
prev[node] = databynode[node]["state"]["value"]
|
||||
|
||||
# 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))
|
||||
@@ -1,52 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
argparser = optparse.OptionParser(
|
||||
usage='''\n %prog noderange
|
||||
\n ''')
|
||||
(options, args) = argparser.parse_args()
|
||||
if len(args) != 1:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
noderange = args[0]
|
||||
client.check_globbing(noderange)
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
for r in session.delete('/noderange/{0}'.format(noderange)):
|
||||
if 'error' in r:
|
||||
sys.stderr.write(r['error'] + '\n')
|
||||
exitcode |= 1
|
||||
if 'deleted' in r:
|
||||
print('{0}: deleted'.format(r['deleted']))
|
||||
sys.exit(exitcode)
|
||||
@@ -1,62 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015-2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
argparser = optparse.OptionParser(usage="Usage: %prog <noderange>")
|
||||
(options, args) = argparser.parse_args()
|
||||
try:
|
||||
noderange = args[0]
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
|
||||
errorNodes = set([])
|
||||
|
||||
success = session.simple_noderange_command(noderange, 'power/reseat', 'reseat', key='reseat', errnodes=errorNodes) # = 0 if successful
|
||||
|
||||
# Determine which nodes were successful and print them
|
||||
|
||||
allNodes = set([])
|
||||
|
||||
for node in session.read('/noderange/{0}/nodes/'.format(noderange)):
|
||||
allNodes.add(node['item']['href'].replace("/", ""))
|
||||
|
||||
goodNodes = allNodes - errorNodes
|
||||
|
||||
for node in goodNodes:
|
||||
print node + ": Reseat successful"
|
||||
|
||||
|
||||
sys.exit(success)
|
||||
@@ -34,25 +34,21 @@ if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
import confluent.sortutil as sortutil
|
||||
|
||||
|
||||
def run():
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: %prog noderange commandexpression",
|
||||
epilog="Expressions are the same as in attributes, e.g. "
|
||||
"'ipmitool -H {hardwaremanagement.manager}' will be expanded.")
|
||||
argparser.add_option('-f', '-c', '--count', type='int', default=168,
|
||||
help='Number of commands to run at a time')
|
||||
concurrentprocs = 168
|
||||
# among other things, FD_SETSIZE limits. Besides, spawning too many
|
||||
# processes can be unkind for the unaware on memory pressure and such...
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: %prog node commandexpression",
|
||||
epilog="Expressions are the same as in attributes, e.g. "
|
||||
"'ipmitool -H {hardwaremanagement.manager}' will be expanded.")
|
||||
argparser.disable_interspersed_args()
|
||||
(options, args) = argparser.parse_args()
|
||||
if len(args) < 2:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
concurrentprocs = options.count
|
||||
client.check_globbing(args[0])
|
||||
c = client.Command()
|
||||
cmdstr = " ".join(args[1:])
|
||||
|
||||
@@ -60,14 +56,10 @@ def run():
|
||||
all = set([])
|
||||
pipedesc = {}
|
||||
pendingexecs = deque()
|
||||
exitcode = 0
|
||||
|
||||
for exp in c.create('/noderange/{0}/attributes/expression'.format(args[0]),
|
||||
{'expression': cmdstr}):
|
||||
if 'error' in exp:
|
||||
sys.stderr.write(exp['error'] + '\n')
|
||||
exitcode |= exp.get('errorcode', 1)
|
||||
ex = exp.get('databynode', ())
|
||||
ex = exp['databynode']
|
||||
for node in ex:
|
||||
cmd = ex[node]['value'].encode('utf-8')
|
||||
cmdv = shlex.split(cmd)
|
||||
@@ -76,39 +68,28 @@ def run():
|
||||
run_cmdv(node, cmdv, all, pipedesc)
|
||||
else:
|
||||
pendingexecs.append((node, cmdv))
|
||||
if not all or exitcode:
|
||||
sys.exit(exitcode)
|
||||
|
||||
exitcode = 0
|
||||
rdy, _, _ = select.select(all, [], [], 10)
|
||||
while all:
|
||||
pernodeout = {}
|
||||
for r in rdy:
|
||||
data = r.readline()
|
||||
desc = pipedesc[r]
|
||||
node = desc['node']
|
||||
data = True
|
||||
while data and select.select([r], [], [], 0):
|
||||
data = r.readline()
|
||||
if data:
|
||||
if desc['type'] == 'stdout':
|
||||
if node not in pernodeout:
|
||||
pernodeout[node] = []
|
||||
pernodeout[node].append(data)
|
||||
else:
|
||||
sys.stderr.write('{0}: {1}'.format(node, data))
|
||||
sys.stderr.flush()
|
||||
if data:
|
||||
node = desc['node']
|
||||
if desc['type'] == 'stdout':
|
||||
sys.stdout.write('{0}: {1}'.format(node,data))
|
||||
else:
|
||||
pop = desc['popen']
|
||||
ret = pop.poll()
|
||||
if ret is not None:
|
||||
exitcode = exitcode | ret
|
||||
all.discard(r)
|
||||
r.close()
|
||||
if desc['type'] == 'stdout' and pendingexecs:
|
||||
node, cmdv = pendingexecs.popleft()
|
||||
run_cmdv(node, cmdv, all, pipedesc)
|
||||
for node in sortutil.natural_sort(pernodeout):
|
||||
for line in pernodeout[node]:
|
||||
sys.stdout.write('{0}: {1}'.format(node, line))
|
||||
sys.stdout.flush()
|
||||
sys.stderr.write('{0}: {1}'.format(node, data))
|
||||
else:
|
||||
pop = desc['popen']
|
||||
ret = pop.poll()
|
||||
if ret is not None:
|
||||
exitcode = exitcode | ret
|
||||
all.discard(r)
|
||||
if desc['type'] == 'stdout' and pendingexecs:
|
||||
node, cmdv = pendingexecs.popleft()
|
||||
run_cmdv(node, cmdv, all, pipedesc)
|
||||
if all:
|
||||
rdy, _, _ = select.select(all, [], [], 10)
|
||||
sys.exit(exitcode)
|
||||
@@ -126,4 +107,4 @@ def run_cmdv(node, cmdv, all, pipedesc):
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
run()
|
||||
run()
|
||||
@@ -36,7 +36,6 @@ import confluent.client as client
|
||||
|
||||
sensorcollections = {
|
||||
'all': 'sensors/hardware/all/all',
|
||||
'energy': 'sensors/hardware/energy/all',
|
||||
'temperature': 'sensors/hardware/temperature/all',
|
||||
'temp': 'sensors/hardware/temperature/all',
|
||||
'power': 'sensors/hardware/power/all',
|
||||
@@ -65,7 +64,6 @@ if options.numreadings:
|
||||
options.interval = 1
|
||||
try:
|
||||
noderange = args[0]
|
||||
client.check_globbing(noderange)
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
@@ -80,6 +78,7 @@ for sensorgroup in args[1:]:
|
||||
sensors.append('sensors/hardware/all/' + sensor)
|
||||
if not sensors:
|
||||
sensors = ['sensors/hardware/all/all']
|
||||
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
sensorheaders = {}
|
||||
@@ -126,8 +125,6 @@ def sensorpass(showout=True, appendtime=False):
|
||||
if showout:
|
||||
if sensedata['value'] is None:
|
||||
showval = ''
|
||||
elif isinstance(sensedata['value'], float):
|
||||
showval = u' {0:.5f} '.format(sensedata['value'])
|
||||
else:
|
||||
showval = u' {0} '.format(sensedata['value'])
|
||||
if sensedata['units'] not in (None, u''):
|
||||
@@ -149,7 +146,6 @@ def sensorpass(showout=True, appendtime=False):
|
||||
'%Y-%m-%dT%H:%M:%S')
|
||||
print(u'{0}: {1}:{2}'.format(
|
||||
node, sensedata['name'], showval).encode('utf-8'))
|
||||
sys.stdout.flush()
|
||||
return resultdata
|
||||
|
||||
|
||||
|
||||
@@ -48,7 +48,6 @@ argparser.add_option('-u', '--uefi', dest='uefi', action='store_true',
|
||||
|
||||
try:
|
||||
noderange = args[0]
|
||||
client.check_globbing(noderange)
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
@@ -19,6 +19,7 @@ from collections import deque
|
||||
import optparse
|
||||
import os
|
||||
import select
|
||||
import shlex
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
@@ -33,26 +34,21 @@ if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
import confluent.sortutil as sortutil
|
||||
|
||||
|
||||
def run():
|
||||
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: %prog noderange commandexpression",
|
||||
epilog="Expressions are the same as in attributes, e.g. "
|
||||
"'ipmitool -H {hardwaremanagement.manager}' will be expanded.")
|
||||
argparser.add_option('-f', '-c', '--count', type='int', default=168,
|
||||
help='Number of commands to run at a time')
|
||||
concurrentprocs = 168
|
||||
# among other things, FD_SETSIZE limits. Besides, spawning too many
|
||||
# processes can be unkind for the unaware on memory pressure and such...
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: %prog node commandexpression",
|
||||
epilog="Expressions are the same as in attributes, e.g. "
|
||||
"'ipmitool -H {hardwaremanagement.manager}' will be expanded.")
|
||||
argparser.disable_interspersed_args()
|
||||
(options, args) = argparser.parse_args()
|
||||
if len(args) < 2:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(args[0])
|
||||
concurrentprocs = options.count
|
||||
c = client.Command()
|
||||
cmdstr = " ".join(args[1:])
|
||||
|
||||
@@ -60,56 +56,40 @@ def run():
|
||||
all = set([])
|
||||
pipedesc = {}
|
||||
pendingexecs = deque()
|
||||
exitcode = 0
|
||||
|
||||
|
||||
for exp in c.create('/noderange/{0}/attributes/expression'.format(args[0]),
|
||||
{'expression': cmdstr}):
|
||||
if 'error' in exp:
|
||||
sys.stderr.write(exp['error'] + '\n')
|
||||
exitcode |= exp.get('errorcode', 1)
|
||||
ex = exp.get('databynode', ())
|
||||
ex = exp['databynode']
|
||||
for node in ex:
|
||||
cmd = ex[node]['value'].encode('utf-8')
|
||||
cmdv = ['ssh', node, cmd]
|
||||
cmdv = ['ssh', node] + shlex.split(cmd)
|
||||
if currprocs < concurrentprocs:
|
||||
currprocs += 1
|
||||
run_cmdv(node, cmdv, all, pipedesc)
|
||||
else:
|
||||
pendingexecs.append((node, cmdv))
|
||||
if not all or exitcode:
|
||||
sys.exit(exitcode)
|
||||
|
||||
exitcode = 0
|
||||
rdy, _, _ = select.select(all, [], [], 10)
|
||||
while all:
|
||||
pernodeout = {}
|
||||
for r in rdy:
|
||||
data = r.readline()
|
||||
desc = pipedesc[r]
|
||||
node = desc['node']
|
||||
data = True
|
||||
while data and select.select([r], [], [], 0):
|
||||
data = r.readline()
|
||||
if data:
|
||||
if desc['type'] == 'stdout':
|
||||
if node not in pernodeout:
|
||||
pernodeout[node] = []
|
||||
pernodeout[node].append(data)
|
||||
else:
|
||||
sys.stderr.write('{0}: {1}'.format(node, data))
|
||||
sys.stderr.flush()
|
||||
if data:
|
||||
node = desc['node']
|
||||
if desc['type'] == 'stdout':
|
||||
sys.stdout.write('{0}: {1}'.format(node,data))
|
||||
else:
|
||||
pop = desc['popen']
|
||||
ret = pop.poll()
|
||||
if ret is not None:
|
||||
exitcode = exitcode | ret
|
||||
all.discard(r)
|
||||
r.close()
|
||||
if desc['type'] == 'stdout' and pendingexecs:
|
||||
node, cmdv = pendingexecs.popleft()
|
||||
run_cmdv(node, cmdv, all, pipedesc)
|
||||
for node in sortutil.natural_sort(pernodeout):
|
||||
for line in pernodeout[node]:
|
||||
sys.stdout.write('{0}: {1}'.format(node, line))
|
||||
sys.stdout.flush()
|
||||
sys.stderr.write('{0}: {1}'.format(node, data))
|
||||
else:
|
||||
pop = desc['popen']
|
||||
ret = pop.poll()
|
||||
if ret is not None:
|
||||
exitcode = exitcode | ret
|
||||
all.discard(r)
|
||||
if desc['type'] == 'stdout' and pendingexecs:
|
||||
node, cmdv = pendingexecs.popleft()
|
||||
run_cmdv(node, cmdv, all, pipedesc)
|
||||
if all:
|
||||
rdy, _, _ = select.select(all, [], [], 10)
|
||||
sys.exit(exitcode)
|
||||
@@ -127,4 +107,4 @@ def run_cmdv(node, cmdv, all, pipedesc):
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
run()
|
||||
run()
|
||||
@@ -1,7 +1,7 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2014 IBM Corporation
|
||||
# Copyright 2015-2018 Lenovo
|
||||
# Copyright 2015-2016 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -19,7 +19,6 @@ import anydbm as dbm
|
||||
import errno
|
||||
import hashlib
|
||||
import os
|
||||
import shlex
|
||||
import socket
|
||||
import ssl
|
||||
import sys
|
||||
@@ -27,15 +26,6 @@ import confluent.tlvdata as tlvdata
|
||||
|
||||
SO_PASSCRED = 16
|
||||
|
||||
_attraliases = {
|
||||
'bmc': 'hardwaremanagement.manager',
|
||||
'bmcuser': 'secret.hardwaremanagementuser',
|
||||
'bmcpass': 'secret.hardwaremanagementpassword',
|
||||
}
|
||||
|
||||
def cprint(txt):
|
||||
print(txt)
|
||||
sys.stdout.flush()
|
||||
|
||||
def _parseserver(string):
|
||||
if ']:' in string:
|
||||
@@ -53,10 +43,8 @@ def _parseserver(string):
|
||||
|
||||
class Command(object):
|
||||
def __init__(self, server=None):
|
||||
self._prevdict = None
|
||||
self._prevkeyname = None
|
||||
self.connection = None
|
||||
self._currnoderange = None
|
||||
if server is None:
|
||||
if 'CONFLUENT_HOST' in os.environ:
|
||||
self.serverloc = os.environ['CONFLUENT_HOST']
|
||||
@@ -89,13 +77,8 @@ class Command(object):
|
||||
def add_precede_key(self, keyname):
|
||||
self._prevkeyname = keyname
|
||||
|
||||
def add_precede_dict(self, dict):
|
||||
self._prevdict = dict
|
||||
|
||||
def handle_results(self, ikey, rc, res, errnodes=None):
|
||||
def handle_results(self, ikey, rc, res):
|
||||
if 'error' in res:
|
||||
if errnodes is not None:
|
||||
errnodes.add(self._currnoderange)
|
||||
sys.stderr.write('Error: {0}\n'.format(res['error']))
|
||||
if 'errorcode' in res:
|
||||
return res['errorcode']
|
||||
@@ -106,8 +89,6 @@ class Command(object):
|
||||
res = res['databynode']
|
||||
for node in res:
|
||||
if 'error' in res[node]:
|
||||
if errnodes is not None:
|
||||
errnodes.add(node)
|
||||
sys.stderr.write('{0}: Error: {1}\n'.format(
|
||||
node, res[node]['error']))
|
||||
if 'errorcode' in res[node]:
|
||||
@@ -122,19 +103,15 @@ class Command(object):
|
||||
else:
|
||||
val = repr(res[node][ikey])
|
||||
if self._prevkeyname and self._prevkeyname in res[node]:
|
||||
cprint('{0}: {2}->{1}'.format(
|
||||
print('{0}: {2}->{1}'.format(
|
||||
node, val, res[node][self._prevkeyname]['value']))
|
||||
elif self._prevdict and node in self._prevdict:
|
||||
cprint('{0}: {2}->{1}'.format(
|
||||
node, val, self._prevdict[node]))
|
||||
else:
|
||||
cprint('{0}: {1}'.format(node, val))
|
||||
print('{0}: {1}'.format(node, val))
|
||||
return rc
|
||||
|
||||
def simple_noderange_command(self, noderange, resource, input=None,
|
||||
key=None, errnodes=None, **kwargs):
|
||||
key=None, **kwargs):
|
||||
try:
|
||||
self._currnoderange = noderange
|
||||
rc = 0
|
||||
if resource[0] == '/':
|
||||
resource = resource[1:]
|
||||
@@ -146,16 +123,15 @@ class Command(object):
|
||||
if input is None:
|
||||
for res in self.read('/noderange/{0}/{1}'.format(
|
||||
noderange, resource)):
|
||||
rc = self.handle_results(ikey, rc, res, errnodes)
|
||||
rc = self.handle_results(ikey, rc, res)
|
||||
else:
|
||||
kwargs[ikey] = input
|
||||
for res in self.update('/noderange/{0}/{1}'.format(
|
||||
noderange, resource), kwargs):
|
||||
rc = self.handle_results(ikey, rc, res, errnodes)
|
||||
self._currnoderange = None
|
||||
rc = self.handle_results(ikey, rc, res)
|
||||
return rc
|
||||
except KeyboardInterrupt:
|
||||
cprint('')
|
||||
print('')
|
||||
return 0
|
||||
|
||||
def simple_nodegroups_command(self, noderange, resource, input=None, key=None, **kwargs):
|
||||
@@ -179,7 +155,7 @@ class Command(object):
|
||||
rc = self.handle_results(ikey, rc, res)
|
||||
return rc
|
||||
except KeyboardInterrupt:
|
||||
cprint('')
|
||||
print('')
|
||||
return 0
|
||||
|
||||
def read(self, path, parameters=None):
|
||||
@@ -261,7 +237,7 @@ class Command(object):
|
||||
"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))
|
||||
print 'Adding new key for %s:%s' % (server, port)
|
||||
khf[hostid] = fingerprint
|
||||
|
||||
|
||||
@@ -295,59 +271,42 @@ def attrrequested(attr, attrlist, seenattributes):
|
||||
truename = candidate
|
||||
if candidate.startswith('hm'):
|
||||
candidate = candidate.replace('hm', 'hardwaremanagement', 1)
|
||||
if candidate in _attraliases:
|
||||
candidate = _attraliases[candidate]
|
||||
if candidate.lower() == attr.lower():
|
||||
if candidate == attr:
|
||||
seenattributes.add(truename)
|
||||
return True
|
||||
elif attr.lower().startswith(candidate.lower() + '.'):
|
||||
elif attr.startswith(candidate + '.'):
|
||||
seenattributes.add(truename)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
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 print_attrib_path(path, session, requestargs, options, rename=None):
|
||||
exitcode = 0
|
||||
seenattributes = set([])
|
||||
for res in session.read(path):
|
||||
for res in session.read('/{0}/{1}/attributes/{2}'.format(nodetype, noderange, showtype)):
|
||||
if 'error' in res:
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
exitcode = 1
|
||||
continue
|
||||
for node in sorted(res['databynode']):
|
||||
for attr, val in sorted(
|
||||
res['databynode'][node].items(),
|
||||
key=lambda (k, v): v.get('sortid', k) if isinstance(v, dict) else k):
|
||||
for node in res['databynode']:
|
||||
for attr in res['databynode'][node]:
|
||||
seenattributes.add(attr)
|
||||
if rename:
|
||||
printattr = rename.get(attr, attr)
|
||||
else:
|
||||
printattr = attr
|
||||
currattr = res['databynode'][node][attr]
|
||||
if show_attr(attr, requestargs, seenattributes, options):
|
||||
if (requestargs is None or requestargs == [] or attrrequested(attr, requestargs, seenattributes)):
|
||||
if 'value' in currattr:
|
||||
if currattr['value'] is not None:
|
||||
val = currattr['value']
|
||||
if isinstance(val, list):
|
||||
val = ','.join(val)
|
||||
attrout = '{0}: {1}: {2}'.format(
|
||||
node, printattr, val)
|
||||
node, attr, currattr['value'])
|
||||
else:
|
||||
attrout = '{0}: {1}:'.format(node, printattr)
|
||||
attrout = '{0}: {1}:'.format(node, attr)
|
||||
elif 'isset' in currattr:
|
||||
if currattr['isset']:
|
||||
attrout = '{0}: {1}: ********'.format(node,
|
||||
printattr)
|
||||
attrout = '{0}: {1}: ********'.format(node, attr)
|
||||
else:
|
||||
attrout = '{0}: {1}:'.format(node, printattr)
|
||||
attrout = '{0}: {1}:'.format(node, attr)
|
||||
elif 'broken' in currattr:
|
||||
attrout = '{0}: {1}: *ERROR* BROKEN EXPRESSION: ' \
|
||||
'{2}'.format(node, printattr,
|
||||
'{2}'.format(node, attr,
|
||||
currattr['broken'])
|
||||
elif isinstance(currattr, list) or isinstance(currattr, tuple):
|
||||
attrout = '{0}: {1}: {2}'.format(node, attr, ','.join(map(str, currattr)))
|
||||
@@ -355,14 +314,11 @@ def print_attrib_path(path, session, requestargs, options, rename=None):
|
||||
dictout = []
|
||||
for k, v in currattr.items:
|
||||
dictout.append("{0}={1}".format(k, v))
|
||||
attrout = '{0}: {1}: {2}'.format(node, printattr, ','.join(map(str, dictout)))
|
||||
attrout = '{0}: {1}: {2}'.format(node, attr, ','.join(map(str, dictout)))
|
||||
else:
|
||||
cprint("CODE ERROR" + repr(attr))
|
||||
try:
|
||||
blame = options.blame
|
||||
except AttributeError:
|
||||
blame = False
|
||||
if blame or 'broken' in currattr:
|
||||
print ("CODE ERROR" + repr(attr))
|
||||
|
||||
if options.blame or 'broken' in currattr:
|
||||
blamedata = []
|
||||
if 'inheritedfrom' in currattr:
|
||||
blamedata.append('inherited from group {0}'.format(
|
||||
@@ -374,36 +330,7 @@ def print_attrib_path(path, session, requestargs, options, rename=None):
|
||||
currattr['expression']))
|
||||
if blamedata:
|
||||
attrout += ' (' + ', '.join(blamedata) + ')'
|
||||
try:
|
||||
comparedefault = options.comparedefault
|
||||
except AttributeError:
|
||||
comparedefault = False
|
||||
if comparedefault:
|
||||
try:
|
||||
exclude = options.exclude
|
||||
except AttributeError:
|
||||
exclude = False
|
||||
if ((requestargs and not exclude) or
|
||||
(currattr.get('default', None) is not None and
|
||||
currattr.get('value', None) is not None and
|
||||
currattr['value'] != currattr['default'])):
|
||||
cprint('{0}: {1}: {2} (Default: {3})'.format(
|
||||
node, printattr, currattr['value'],
|
||||
currattr['default']))
|
||||
else:
|
||||
|
||||
try:
|
||||
details = options.detail
|
||||
except AttributeError:
|
||||
details = False
|
||||
if details:
|
||||
if currattr.get('help', None):
|
||||
attrout += ' (Help: {0})'.format(
|
||||
currattr['help'].encode('utf-8'))
|
||||
if currattr.get('possible', None):
|
||||
attrout += ' (Choices: {0})'.format(
|
||||
','.join(currattr['possible']))
|
||||
cprint(attrout)
|
||||
print attrout
|
||||
if not exitcode:
|
||||
if requestargs:
|
||||
for attr in requestargs:
|
||||
@@ -413,19 +340,6 @@ def print_attrib_path(path, session, requestargs, options, rename=None):
|
||||
return exitcode
|
||||
|
||||
|
||||
def show_attr(attr, requestargs, seenattributes, options):
|
||||
try:
|
||||
reverse = options.exclude
|
||||
except AttributeError:
|
||||
reverse = False
|
||||
if requestargs is None or requestargs == []:
|
||||
return True
|
||||
processattr = attrrequested(attr, requestargs, seenattributes)
|
||||
if reverse:
|
||||
processattr = not processattr
|
||||
return processattr
|
||||
|
||||
|
||||
def printgroupattributes(session, requestargs, showtype, nodetype, noderange, options):
|
||||
exitcode = 0
|
||||
seenattributes = set([])
|
||||
@@ -463,8 +377,8 @@ def printgroupattributes(session, requestargs, showtype, nodetype, noderange, op
|
||||
dictout.append("{0}={1}".format(k, v))
|
||||
attrout = '{0}: {1}: {2}'.format(noderange, attr, ','.join(map(str, dictout)))
|
||||
else:
|
||||
cprint("CODE ERROR" + repr(attr))
|
||||
cprint(attrout)
|
||||
print ("CODE ERROR" + repr(attr))
|
||||
print attrout
|
||||
if not exitcode:
|
||||
if requestargs:
|
||||
for attr in requestargs:
|
||||
@@ -487,81 +401,28 @@ def updateattrib(session, updateargs, nodetype, noderange, options):
|
||||
exitcode = res['errorcode']
|
||||
sys.stderr.write('Error: ' + res['error'] + '\n')
|
||||
sys.exit(exitcode)
|
||||
elif hasattr(options, 'environment') and options.environment:
|
||||
for key in updateargs[1:]:
|
||||
key = key.replace('.', '_')
|
||||
value = os.environ.get(
|
||||
key, os.environ[key.upper()])
|
||||
# Let's do one pass to make sure that there's not a usage problem
|
||||
for key in updateargs[1:]:
|
||||
key = key.replace('.', '_')
|
||||
value = os.environ.get(
|
||||
key, os.environ[key.upper()])
|
||||
if (nodetype == "nodegroups"):
|
||||
exitcode = session.simple_nodegroups_command(noderange,
|
||||
'attributes/all',
|
||||
value, key)
|
||||
else:
|
||||
exitcode = session.simple_noderange_command(noderange,
|
||||
'attributes/all',
|
||||
value, key)
|
||||
sys.exit(exitcode)
|
||||
else:
|
||||
if "=" in updateargs[1]:
|
||||
try:
|
||||
for val in updateargs[1:]:
|
||||
val = val.split('=')
|
||||
if val[0][-1] in (',', '-', '^'):
|
||||
key = val[0][:-1]
|
||||
if val[0][-1] == ',':
|
||||
value = {'prepend': val[1]}
|
||||
elif val[0][-1] in ('-', '^'):
|
||||
value = {'remove': val[1]}
|
||||
if len(updateargs[1:]) > 1:
|
||||
for val in updateargs[1:]:
|
||||
val = val.split('=')
|
||||
if (nodetype == "nodegroups"):
|
||||
exitcode = session.simple_nodegroups_command(noderange, 'attributes/all'.format(noderange),
|
||||
val[1],val[0])
|
||||
else:
|
||||
exitcode = session.simple_noderange_command(noderange, 'attributes/all'.format(noderange),
|
||||
val[1], val[0])
|
||||
else:
|
||||
val = updateargs[1].split('=')
|
||||
if nodetype == "nodegroups" :
|
||||
exitcode = session.simple_nodegroups_command(noderange, 'attributes/all'.format(noderange),
|
||||
val[1], val[0])
|
||||
else:
|
||||
key = val[0]
|
||||
value = val[1]
|
||||
if (nodetype == "nodegroups"):
|
||||
exitcode = session.simple_nodegroups_command(noderange, 'attributes/all',
|
||||
value, key)
|
||||
else:
|
||||
exitcode = session.simple_noderange_command(noderange, 'attributes/all',
|
||||
value, key)
|
||||
exitcode = session.simple_noderange_command(noderange, 'attributes/all'.format(noderange),
|
||||
val[1], val[0])
|
||||
except:
|
||||
sys.stderr.write('Error: {0} not a valid expression\n'.format(str(updateargs[1:])))
|
||||
exitcode = 1
|
||||
sys.exit(exitcode)
|
||||
return exitcode
|
||||
|
||||
|
||||
# So we try to prevent bad things from happening when globbing
|
||||
# We tried to head this off at the shell, but the various solutions would end
|
||||
# up breaking the shell in various ways (breaking pipe capability if using
|
||||
# DEBUG, breaking globbing if in pipe, etc)
|
||||
# Then we tried to parse the original commandline instead, however shlex isn't
|
||||
# going to parse full bourne language (e.g. knowing that '|' and '>' and
|
||||
# a world of other things would not be in our command line
|
||||
# so finally, just make sure the noderange appears verbatim in the command line
|
||||
# if we glob to something, then bash will change noderange and this should
|
||||
# detect it and save the user from tragedy
|
||||
def check_globbing(noderange):
|
||||
if not os.path.exists(noderange):
|
||||
return True
|
||||
rawargs = os.environ.get('CURRENT_CMDLINE', None)
|
||||
if rawargs:
|
||||
rawargs = shlex.split(rawargs)
|
||||
for arg in rawargs:
|
||||
if arg.startswith('$'):
|
||||
arg = arg[1:]
|
||||
if arg.endswith(';'):
|
||||
arg = arg[:-1]
|
||||
arg = os.environ.get(arg, '$' + arg)
|
||||
if arg.startswith(noderange):
|
||||
break
|
||||
else:
|
||||
sys.stderr.write(
|
||||
'Shell glob conflict detected, specified target "{0}" '
|
||||
'not in command line, but is a file. You can use "set -f" in '
|
||||
'bash or change directories such that there is no filename '
|
||||
'that would conflict.'
|
||||
'\n'.format(noderange))
|
||||
sys.exit(1)
|
||||
@@ -1,85 +0,0 @@
|
||||
# Copyright 2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# 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 fcntl
|
||||
import sys
|
||||
import struct
|
||||
import termios
|
||||
|
||||
def get_screenwidth():
|
||||
return struct.unpack('hh', fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ,
|
||||
b'....'))[1]
|
||||
class ScreenPrinter(object):
|
||||
|
||||
def __init__(self, noderange, client, textlen=4):
|
||||
self.squeeze = sys.stdout.isatty()
|
||||
self.textlen = textlen
|
||||
self.noderange = noderange
|
||||
self.client = client
|
||||
self.nodeoutput = {}
|
||||
self.nodelist = []
|
||||
maxlen = 0
|
||||
for ans in self.client.read('/noderange/{0}/nodes/'.format(noderange)):
|
||||
if 'error' in ans:
|
||||
sys.stderr.write(ans['error'])
|
||||
continue
|
||||
nodename = ans['item']['href'][:-1]
|
||||
if len(nodename) > maxlen:
|
||||
maxlen = len(nodename)
|
||||
self.nodelist.append(nodename)
|
||||
self.nodeoutput[nodename] = ''
|
||||
self.nodenamelen = maxlen
|
||||
self.textlen = textlen
|
||||
self.fieldwidth = maxlen + textlen + 1 # 1 for column
|
||||
|
||||
def set_output(self, node, text):
|
||||
self.nodeoutput[node] = text
|
||||
if len(text) >= self.textlen:
|
||||
self.textlen = len(text) + 1
|
||||
self.fieldwidth = self.textlen + self.nodenamelen + 1
|
||||
self.drawscreen()
|
||||
|
||||
def drawscreen(self):
|
||||
if self.squeeze:
|
||||
currwidth = get_screenwidth()
|
||||
numfields = currwidth // self.fieldwidth
|
||||
fieldformat = '{{0:>{0}}}:{{1:{1}}}'.format(self.nodenamelen,
|
||||
self.textlen)
|
||||
sys.stdout.write('\x1b[2J\x1b[;H') # clear screen
|
||||
else:
|
||||
numfields = 1
|
||||
fieldformat = '{0}: {1}'
|
||||
currfields = 0
|
||||
for node in self.nodelist:
|
||||
if currfields >= numfields:
|
||||
sys.stdout.write('\n')
|
||||
currfields = 1
|
||||
else:
|
||||
currfields += 1
|
||||
sys.stdout.write(fieldformat.format(node, self.nodeoutput[node]))
|
||||
sys.stdout.write('\n')
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import confluent.client as client
|
||||
c = client.Command()
|
||||
p = ScreenPrinter('n1-n13', c)
|
||||
p.set_output('n3', 'Upload: 67%')
|
||||
|
||||
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2014 IBM Corporation
|
||||
# Copyright 2015-2016 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# 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 re
|
||||
|
||||
numregex = re.compile('([0-9]+)')
|
||||
|
||||
|
||||
def naturalize_string(key):
|
||||
"""Analyzes string in a human way to enable natural sort
|
||||
|
||||
:param key: The node name to analyze
|
||||
:returns: A structure that can be consumed by 'sorted'
|
||||
"""
|
||||
return [int(text) if text.isdigit() else text.lower()
|
||||
for text in re.split(numregex, key)]
|
||||
|
||||
|
||||
def natural_sort(iterable):
|
||||
"""Return a sort using natural sort if possible
|
||||
|
||||
:param iterable:
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
return sorted(iterable, key=naturalize_string)
|
||||
except TypeError:
|
||||
# The natural sort attempt failed, fallback to ascii sort
|
||||
return sorted(iterable)
|
||||
@@ -1,217 +0,0 @@
|
||||
# Copyright 2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# 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 difflib
|
||||
import re
|
||||
import sys
|
||||
|
||||
try:
|
||||
range = xrange
|
||||
except NameError:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
numregex = re.compile('([0-9]+)')
|
||||
def humanify_nodename(nodename):
|
||||
"""Analyzes nodename in a human way to enable natural sort
|
||||
|
||||
:param nodename: The node name to analyze
|
||||
:returns: A structure that can be consumed by 'sorted'
|
||||
"""
|
||||
return [int(text) if text.isdigit() else text.lower()
|
||||
for text in re.split(numregex, nodename)]
|
||||
|
||||
|
||||
def _colorize_line(orig, mask):
|
||||
highlighted = False
|
||||
newline = orig[0]
|
||||
for i in range(1, len(orig)):
|
||||
if i > len(mask) - 1:
|
||||
if highlighted:
|
||||
newline += '\x1b[0m'
|
||||
newline += orig[i:]
|
||||
break
|
||||
if highlighted and mask[i] == ' ':
|
||||
highlighted = False
|
||||
newline += '\x1b[0m'
|
||||
elif not highlighted and mask[i] != ' ':
|
||||
highlighted = True
|
||||
newline += '\x1b[31m'
|
||||
newline += orig[i]
|
||||
newline += '\x1b[0m'
|
||||
return newline
|
||||
|
||||
|
||||
def near_diff(idx, diffdata):
|
||||
maxidx = idx + 3
|
||||
if maxidx > len(diffdata) -1:
|
||||
maxidx = len(diffdata) - 1
|
||||
idx = idx - 3
|
||||
if idx < 0:
|
||||
idx = 0
|
||||
while idx <= maxidx:
|
||||
if len(diffdata[idx]) > 0 and diffdata[idx][0] in ('+', '-'):
|
||||
return True
|
||||
idx += 1
|
||||
return False
|
||||
|
||||
|
||||
def colordiff(first, second):
|
||||
diffdata = list(difflib.ndiff(first, second))
|
||||
quiet = True
|
||||
for i in range(len(diffdata)):
|
||||
if i < len(diffdata) - 1 and diffdata[i + 1].startswith('?'):
|
||||
if quiet:
|
||||
quiet = False
|
||||
yield '@@'
|
||||
yield _colorize_line(diffdata[i], diffdata[i + 1])
|
||||
elif not diffdata[i].startswith('?') and near_diff(i, diffdata):
|
||||
if quiet:
|
||||
quiet = False
|
||||
yield '@@'
|
||||
yield diffdata[i]
|
||||
elif not diffdata[i].startswith('?'):
|
||||
quiet = True
|
||||
|
||||
|
||||
|
||||
class GroupedData(object):
|
||||
'''A post processor to sort and compare per-node data
|
||||
|
||||
:param confluentconnection: If given, will attempt to use the connection to abbreviate noderanges
|
||||
'''
|
||||
|
||||
def __init__(self, confluentconnection=None):
|
||||
self.bynode = {}
|
||||
self.byoutput = {}
|
||||
self.header = {}
|
||||
self.client = confluentconnection
|
||||
|
||||
def generate_byoutput(self):
|
||||
self.byoutput = {}
|
||||
for n in self.bynode:
|
||||
output = '\n'.join(self.bynode[n])
|
||||
if output not in self.byoutput:
|
||||
self.byoutput[output] = set([n])
|
||||
else:
|
||||
self.byoutput[output].add(n)
|
||||
|
||||
def add_line(self, node, line):
|
||||
if node not in self.bynode:
|
||||
self.bynode[node] = [line]
|
||||
else:
|
||||
self.bynode[node].append(line)
|
||||
|
||||
def get_group_text(self, nodes):
|
||||
if self.client:
|
||||
headerkey = ','.join(sorted(nodes))
|
||||
if headerkey in self.header:
|
||||
return self.header[headerkey]
|
||||
noderange = ''
|
||||
for reply in self.client.create('/noderange//abbreviate',
|
||||
{'nodes': sorted(nodes)}):
|
||||
noderange = reply['noderange']
|
||||
self.header[headerkey] = noderange
|
||||
return noderange
|
||||
else:
|
||||
return ','.join(sorted(nodes, key=humanify_nodename))
|
||||
|
||||
def print_all(self, output=sys.stdout, skipmodal=False, reverse=False,
|
||||
count=False):
|
||||
self.generate_byoutput()
|
||||
modaloutput = None
|
||||
ismodal = True
|
||||
|
||||
if reverse:
|
||||
outdatalist = sorted(
|
||||
self.byoutput, key=lambda x: [len(self.byoutput[x]),
|
||||
humanify_nodename(
|
||||
self.get_group_text(
|
||||
self.byoutput[x]
|
||||
))])
|
||||
else:
|
||||
outdatalist = sorted(
|
||||
self.byoutput, key=lambda x: [0 - len(self.byoutput[x]),
|
||||
humanify_nodename(
|
||||
self.get_group_text(
|
||||
self.byoutput[x]
|
||||
))])
|
||||
if reverse and skipmodal:
|
||||
# if reversed, the last is biggest and should be skipped if modal
|
||||
outdatalist = outdatalist[:-1]
|
||||
for outdata in outdatalist:
|
||||
if not reverse and skipmodal:
|
||||
# If big first, this makes skipmodal skip first
|
||||
skipmodal = False
|
||||
continue
|
||||
currout = '====================================\n'
|
||||
currout += self.get_group_text(self.byoutput[outdata])
|
||||
currout += '\n====================================\n'
|
||||
if count:
|
||||
currout += 'Count: {0}'.format(len(list(
|
||||
self.byoutput[outdata])))
|
||||
currout += '\n====================================\n'
|
||||
currout += outdata
|
||||
currout += '\n\n'
|
||||
output.write(currout)
|
||||
output.flush()
|
||||
|
||||
def print_deviants(self, output=sys.stdout, skipmodal=False, reverse=False,
|
||||
count=False):
|
||||
self.generate_byoutput()
|
||||
modaloutput = None
|
||||
ismodal = True
|
||||
revoutput = []
|
||||
for outdata in sorted(
|
||||
self.byoutput, key=lambda x: [0 - len(self.byoutput[x]),
|
||||
humanify_nodename(
|
||||
self.get_group_text(
|
||||
self.byoutput[x]
|
||||
))]):
|
||||
if modaloutput is None:
|
||||
modaloutput = outdata
|
||||
if skipmodal:
|
||||
skipmodal = False
|
||||
ismodal = False
|
||||
continue
|
||||
currout = '====================================\n'
|
||||
currout += self.get_group_text(self.byoutput[outdata])
|
||||
currout += '\n====================================\n'
|
||||
if count:
|
||||
currout += 'Count: {0}'.format(len(list(
|
||||
self.byoutput[outdata])))
|
||||
currout += '\n====================================\n'
|
||||
if ismodal:
|
||||
ismodal = False
|
||||
currout += outdata
|
||||
else:
|
||||
currout += '\n'.join(colordiff(modaloutput.split('\n'),
|
||||
outdata.split('\n')))
|
||||
currout += '\n\n'
|
||||
if reverse:
|
||||
revoutput.append(currout)
|
||||
else:
|
||||
output.write(currout)
|
||||
for currout in reversed(revoutput):
|
||||
output.write(currout)
|
||||
output.flush()
|
||||
|
||||
if __name__ == '__main__':
|
||||
groupoutput = GroupedData()
|
||||
for line in sys.stdin.read().split('\n'):
|
||||
if not line:
|
||||
continue
|
||||
groupoutput.add_line(*line.split(': ', 1))
|
||||
groupoutput.print_deviants()
|
||||
@@ -2,38 +2,3 @@ PATH=/opt/confluent/bin:$PATH
|
||||
export PATH
|
||||
MANPATH=/opt/confluent/share/man:$MANPATH
|
||||
export MANPATH
|
||||
# The aliases below are to signify that file globbing is unwelcome at the shell
|
||||
# this avoids a problem if a user does a noderange like 'n[21-33] and there is a file
|
||||
# in the directory like 'n3' that causes the parameter to change and target a totally
|
||||
# different node
|
||||
# Unfortunately in bourne shell, we cannot reliably ensure a prepended set-f
|
||||
# and an appended set +f are both run. alias seems to be the only mechanism
|
||||
# that can intervene before glob expansion, but it lacks power.
|
||||
# putting it into a function to append is all well and good, *except* that
|
||||
# if doing something like 'nodepower compute|grep' causes set -f to execute
|
||||
# in current shell, and the function to be in a subshell and leaves globbing
|
||||
# disabled in the parent shell. Instead, store the current command in a
|
||||
# variable and use that to check for misglobbed noderanges, which was the goal
|
||||
alias nodeattrib='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodeattrib'
|
||||
alias nodebmcreset='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodebmcreset'
|
||||
alias nodeboot='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodeboot'
|
||||
alias nodeconfig='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodeconfig'
|
||||
alias nodeconsole='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodeconsole'
|
||||
alias nodedefine='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodedefine'
|
||||
alias nodeeventlog='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodeeventlog'
|
||||
alias nodefirmware='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodefirmware'
|
||||
alias nodegroupattrib='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodegroupattrib'
|
||||
alias nodegroupdefine='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodegroupdefine'
|
||||
alias nodegroupremove='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodegroupremove'
|
||||
alias nodehealth='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodehealth'
|
||||
alias nodeidentify='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodeidentify'
|
||||
alias nodeinventory='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodeinventory'
|
||||
alias nodelist='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodelist'
|
||||
alias nodemedia='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodemedia'
|
||||
alias nodepower='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodepower'
|
||||
alias noderemove='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; noderemove'
|
||||
alias nodereseat='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodereseat'
|
||||
alias noderun='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; noderun'
|
||||
alias nodesensors='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodesensors'
|
||||
alias nodesetboot='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodesetboot'
|
||||
alias nodeshell='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodeshell'
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
collate(1) -- Organize text input by node
|
||||
==============================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`<other command> | collate [-a] [-d] [-w] [-s] [-c] [-r]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
**collate** takes input in the form of <nodename>: <data> and groups
|
||||
the output for each <nodename> together, and checks for identical data to further group nodes together.
|
||||
Output groups are sorted in descending size such that the topmost group is the largest, and the
|
||||
last output group is the smallest. In the event of equally sized output groups,
|
||||
the groups are sorted alphanumerically by their header, and each header has
|
||||
node and group names sorted alphanumerically.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-a`, `--abbreviate`:
|
||||
Use confluent to attempt to shorten the noderange. This can help identify
|
||||
when output differs along a telling group boundary. For example, if
|
||||
output suggests a large number of nodes are unreachable, abbreviate
|
||||
showing 'rack1' as being unreachable may make more obvious a possible cause.
|
||||
|
||||
* `-d`, `--diff`:
|
||||
Express all but the most common result group in terms of diff from
|
||||
the most common result group
|
||||
|
||||
* `-w`, `--watch`:
|
||||
Update results dynamically as data becomes available, rather than
|
||||
waiting for the command to fully complete.
|
||||
|
||||
* `-s`, `--skipcommon`:
|
||||
Suppress printing of the most common result text group. This is used to
|
||||
focus on stray output against a well known and expected result.
|
||||
|
||||
* `-c`, `--count`:
|
||||
Print a count of the number of nodes in an output group under the
|
||||
noderange.
|
||||
|
||||
* `-r`, `--reverse`:
|
||||
Rather than starting with most common to least common, start with
|
||||
the least common and print the most common last.
|
||||
|
||||
## EXAMPLES
|
||||
* Organizing power state of multiple nodes:
|
||||
`# nodepower n1-n12 | collate`
|
||||
`====================================`
|
||||
`n1,n2,n3,n4,n7,n8,n9,n10,n11,n12`
|
||||
`====================================`
|
||||
`on`
|
||||
` `
|
||||
`====================================`
|
||||
`n5,n6`
|
||||
`====================================`
|
||||
`off`
|
||||
|
||||
* Using diff to detect distinct UEFI configuration
|
||||
|
||||
`# pasu n1-n4 show Processors|collate -d -s`
|
||||
`====================================`
|
||||
`n3`
|
||||
`====================================`
|
||||
`@@`
|
||||
` Processors.ProcessorPerformanceStates=Enable`
|
||||
` Processors.C-States=Enable`
|
||||
` Processors.PackageACPIC-StateLimit=ACPI C3`
|
||||
`- Processors.C1EnhancedMode=Enable`
|
||||
`+ Processors.C1EnhancedMode=Disable`
|
||||
`- Processors.Hyper-Threading=Enable`
|
||||
`+ Processors.Hyper-Threading=Disable`
|
||||
` Processors.ExecuteDisableBit=Enable`
|
||||
` Processors.IntelVirtualizationTechnology=Enable`
|
||||
` `
|
||||
`====================================`
|
||||
`n1`
|
||||
`====================================`
|
||||
`@@`
|
||||
` Processors.ProcessorPerformanceStates=Enable`
|
||||
` Processors.C-States=Enable`
|
||||
` Processors.PackageACPIC-StateLimit=ACPI C3`
|
||||
`- Processors.C1EnhancedMode=Enable`
|
||||
`+ Processors.C1EnhancedMode=Disable`
|
||||
` Processors.Hyper-Threading=Enable`
|
||||
` Processors.ExecuteDisableBit=Enable`
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ nodeattrib(8) -- List or change confluent nodes attributes
|
||||
`nodeattrib [-b] <noderange> [<nodeattribute>...]`
|
||||
`nodeattrib <noderange> [<nodeattribute1=value1> <nodeattribute2=value2> ...]`
|
||||
`nodeattrib -c <noderange> <nodeattribute1> <nodeattribute2> ...`
|
||||
`nodeattrib -e <noderange> <nodeattribute1> <nodeattribute2> ...`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -22,9 +21,6 @@ For a full list of attributes, run `nodeattrib <node> all` against a node.
|
||||
If `-c` is specified, this will set the nodeattribute to a null valid.
|
||||
This is different from setting the value to an empty string.
|
||||
|
||||
For the `groups` attribute, it is possible to add a group by doing
|
||||
`groups,=<newgroup>`` and to remove by doing `groups^=<oldgroup>`
|
||||
|
||||
Note that `nodeattrib <group>` will likely not provide the expected behavior.
|
||||
See nodegroupattrib(8) command on how to manage attributes on a group level.
|
||||
|
||||
@@ -34,8 +30,6 @@ See nodegroupattrib(8) command on how to manage attributes on a group level.
|
||||
Annotate inherited and expression based attributes to show their base value.
|
||||
* `-c`, `--clear`:
|
||||
Clear specified nodeattributes
|
||||
* `-e`, `--environment`:
|
||||
Set specified attributes based on exported environment variable of matching name. Environment variable names may be lower case or all upper case. Replace . with _ as needed (e.g. info.note may be specified as either $info_note or $INFO_NOTE
|
||||
|
||||
## EXAMPLES
|
||||
* Listing matching nodes of a simple noderange:
|
||||
|
||||
@@ -39,7 +39,7 @@ number of operators at the end to indicate formatting changes.
|
||||
|
||||
Another common element to pull into an expression is the node name in whole:
|
||||
|
||||
`hardwaremanagement.manager={node}-imm`
|
||||
`hardwaremanagement.manager={nodename}-imm`
|
||||
|
||||
Additionally other attributes may be pulled in:
|
||||
|
||||
@@ -47,7 +47,7 @@ Additionally other attributes may be pulled in:
|
||||
|
||||
Multiple expressions are permissible within a single attribute:
|
||||
|
||||
`hardwaremanagement.manager={node}-{hardwaremanagement.method}`
|
||||
`hardwaremanagement.manager={nodename}-{hardwaremanagement.method}`
|
||||
|
||||
A note to developers: in general the API layer will automatically recognize a
|
||||
generic set attribute to string with expression syntax and import it as an
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
nodebmcreset(8) -- Reset management controller
|
||||
=========================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodebmcreset <noderange>`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
`nodebmcreset` allows you to reset the management controller of the specified noderange
|
||||
|
||||
## EXAMPLES:
|
||||
|
||||
* Reset the management controller for nodes n1 through n4:
|
||||
`# nodebmcreset n1-n4`
|
||||
`n1: BMC Reset Successful`
|
||||
`n2: BMC Reset Successful`
|
||||
`n3: BMC Reset Successful`
|
||||
`n4: BMC Reset Successful`
|
||||
@@ -1,50 +0,0 @@
|
||||
nodeconfig(8) -- Show or change node configuration
|
||||
==================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodeconfig <noderange> [options] [<configuration>..]`
|
||||
`nodeconfig <noderange> [options] [<configuration=value>..]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
**nodeconfig** manages the configuration of nodes managed by confluent.
|
||||
Rather than manipulating the confluent database, this actually modifies the
|
||||
running configuration on the node firmware. Calling without '=' will show the
|
||||
current value, and '=' will change the value. Network information can be
|
||||
given as a node expression, as documented in the man page for nodeattribexpressions(5).
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-c`, `--comparedefault`:
|
||||
Take the given settings and compare against default value, if available. If
|
||||
no configuration values are specified, it will show only those that differ.
|
||||
If combined with `-x`, will show all differing values except those indicated
|
||||
by `-x`
|
||||
|
||||
* `-x`, `--exclude`:
|
||||
Rather than listing only the specified configuration parameters, list all
|
||||
attributes except for the specified ones
|
||||
|
||||
* `-d`, `--detail`:
|
||||
Provide detailed data as available. This can include help text and valid
|
||||
values for a setting.
|
||||
|
||||
|
||||
## EXAMPLES
|
||||
* Showing the current IP configuration of noderange BMC/IMM/XCC:
|
||||
`# nodeconfig s3,s4 bmc`
|
||||
`s3: bmc.ipv4_address: 172.30.254.193/16`
|
||||
`s3: bmc.ipv4_method: DHCP`
|
||||
`s3: bmc.ipv4_gateway: 172.30.0.6`
|
||||
`s4: bmc.ipv4_address: 172.30.254.192/16`
|
||||
`s4: bmc.ipv4_method: DHCP`
|
||||
`s4: bmc.ipv4_gateway: 172.30.0.6`
|
||||
|
||||
* Changing nodes `s3` and `s4` to have the ip addressess 10.1.2.3 and 10.1.2.4 with a 16 bit subnet mask:
|
||||
`# nodeconfig s3,s4 bmc.ipv4_address=10.1.2.{n1}/16`
|
||||
|
||||
## SEE ALSO
|
||||
|
||||
nodeattribexpressions(5)
|
||||
|
||||
@@ -11,11 +11,6 @@ text or serial console of a system. Exiting is done by hitting `Ctrl-e`, then `
|
||||
then `.`. Note that console output by default is additionally logged to
|
||||
`/var/log/confluent/consoles/`**NODENAME**.
|
||||
|
||||
When the console connection to the target is broken, then confluent on backend
|
||||
will initiate an automatic retry interval that is randomized between 2 and 4 minutes.
|
||||
The reopen escape sequence below requests an immediate retry, as does connecting
|
||||
a new session.
|
||||
|
||||
## ESCAPE SEQUENCE COMMANDS
|
||||
|
||||
While connected to a console, a number of commands may be performed through escape
|
||||
@@ -47,10 +42,6 @@ keystroke will be interpreted as a command. The following commands are availabl
|
||||
* `pbn`:
|
||||
[Power Boot Network]
|
||||
Request immediate boot to network
|
||||
* `r`:
|
||||
[send Resize]
|
||||
This queries the current terminal and sends stty commands to advertise the user termineal
|
||||
size to the remote console
|
||||
* `?`:
|
||||
Get a list of supported commands
|
||||
* `<ent>`:
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
nodedefine(8) -- Define new confluent nodes
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodedefine <noderange> [nodeattribute1=value1> <nodeattribute2=value2> ...]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
`nodedefine` allows the definition of new nodes for the confluent management
|
||||
system. It has the same syntax as `nodeattrib(8)`, and the commands differ in
|
||||
that `nodeattrib(8)` will error if a node does not exist.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
* Define two racks of nodes, named r{rack}u{u}:
|
||||
`# nodedefine r1u1-r2u4`
|
||||
`r1u4: created`
|
||||
`r1u1: created`
|
||||
`r1u2: created`
|
||||
`r1u3: created`
|
||||
`r2u4: created`
|
||||
`r2u3: created`
|
||||
`r2u2: created`
|
||||
`r2u1: created`
|
||||
|
||||
## SEE ALSO
|
||||
|
||||
noderange(5), nodeattribexpressions(8)
|
||||
@@ -1,130 +0,0 @@
|
||||
nodediscover(8) -- List or manage confluent node discovery
|
||||
=========================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodediscover rescan`
|
||||
`nodediscover [options] list`
|
||||
`nodediscover [options] assign`
|
||||
`nodediscover [options] rescan`
|
||||
`nodediscover [options] clear`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
**nodediscover** provides streamlined access to the confluent discovery data
|
||||
and assignment. Nodes are detected through either an active scan (as occurs
|
||||
at service startup and on request by nodediscover rescan) or through passive
|
||||
detection (as a target comes online, it may attempt to register with the
|
||||
network).
|
||||
|
||||
**nodediscover list** provides the currently known data in tabular format. The
|
||||
data may be filtered by various parameters, as denoted in the options below.
|
||||
|
||||
**nodediscover assign** performs manual discovery, assigning an entry to a node
|
||||
identity or, using `-i`, using a csv file to assign nodes all at once. For
|
||||
example, a spreadsheet of serial numbers to desired node names could be used.
|
||||
|
||||
**nodediscover rescan** requests the server to do an active sweep for new
|
||||
devices. Generally every effort is made to passively detect devices as they
|
||||
become available (as they boot or are plugged in), however sometimes an active
|
||||
scan is the best approach to catch something that appears to be missing.
|
||||
|
||||
**nodedsicover clear** requests the server forget about a selection of
|
||||
detected device. It takes the same arguments as **nodediscover list**.
|
||||
|
||||
## CSV FORMAT
|
||||
|
||||
The CSV format used by nodediscover consists of one header describing the
|
||||
columns followed by the data. The available columns are:
|
||||
|
||||
* node: The name desired for the node in confluent
|
||||
* groups: A comma delimited list of groups to put the node into (using normal CSV escape rules for the commas)
|
||||
* mac: The mac address of the node
|
||||
* serial: The serial number of the node
|
||||
* uuid: The uuid of the node
|
||||
* bmc: The name or ip address that should be assigned to the BMC, regardless of current address
|
||||
* bmc_gateway: IP address of gateway, if desired
|
||||
* bmcuser: The desired username for the BMC to have as administrator
|
||||
* bmcpass: The desired password for the BMC to require
|
||||
|
||||
Note that node is the only mandatory field. To identify the systems, one of
|
||||
mac, serial, or uuid should be specified, it is pointless to provide more than
|
||||
one of these columns. Other attributes if not provided may be defined through
|
||||
nodeattrib or group inherited. It is possible to define nodes without ever
|
||||
providing a BMC ip, in which case IPv6 will be used automatically if possible.
|
||||
|
||||
One example of a valid CSV file would be:
|
||||
node,serial,bmc,bmcuser,bmcpass
|
||||
n1,06DPMDF,172.30.204.1,admin,Passw0rd12
|
||||
n2,J30002HG,172.30.204.2,admin,Passw0rd12
|
||||
|
||||
Which would use the serial number to assign the name and other three values to
|
||||
the nodes.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-m MODEL`, `--model=MODEL`:
|
||||
Operate with nodes matching the specified model number
|
||||
* `-s SERIAL`, `--serial=SERIAL`:
|
||||
Operate against the system matching the specified
|
||||
serial number
|
||||
* `-u UUID`, `--uuid=UUID`:
|
||||
Operate against the system matching the specified UUID
|
||||
* `-n NODE`, `--node=NODE`:
|
||||
Operate with the given nodename
|
||||
* `-e MAC`, `--ethaddr=MAC`:
|
||||
Operate against the system with the specified MAC
|
||||
address
|
||||
* `-t TYPE`, `--type=TYPE`:
|
||||
Operate against the system of the specified type
|
||||
* `-c`, `--csv`:
|
||||
Use CSV formatted output
|
||||
* `-i IMPORT.CSV`, `--import=IMPORT.CSV`:
|
||||
Import bulk assignment data from given CSV file
|
||||
* `-d STATE`, `--discoverystate=STATE`:
|
||||
Indicate devices with a particular state. The states are listed below
|
||||
* discovered: The device has been identified and has also had discovery
|
||||
activities performed, including any relevant certificate
|
||||
exchanges and deploying user and network configuration.
|
||||
* identified: The device has been identified as to what node it is
|
||||
supposed to be, however no active changes to the attributes
|
||||
or device configuration has been performed. This is
|
||||
generally discovery awaiting approval due to
|
||||
discovery.policy specifying a strict security model.
|
||||
* unidentified: A device has been sensed, but no node identity has been
|
||||
established at all. It provides data that can be used
|
||||
for nodediscover assign, as well as current IP addresses
|
||||
that can be used for manual efforts as possible.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
* Listing all detected Lenovo IMMv2 systems on a local network:
|
||||
`# nodediscover list -t lenovo-imm2`
|
||||
` Node| Model| Serial| UUID| Mac Address| Type| Current IP Addresses`
|
||||
`---------------|---------------|---------------|------------------------------------|-----------------|------------|------------------------------------------------`
|
||||
` r2| 5463AC1| 06DPMDF|5f7133b8-c8cb-11e4-99a9-40f2e9b91018|40:f2:e9:b9:10:1d| lenovo-imm2| 172.30.204.1,fe80::42f2:e9ff:feb9:101d%eth1`
|
||||
` | 7906AC1| 06PBX15|e98d483d-2759-11e1-8ffd-5cf3fc11249c|5c:f3:fc:11:24:9f| lenovo-imm2| 172.30.3.12,fe80::5ef3:fcff:fe11:249f%eth1`
|
||||
` n1| 8737AC1| 23XXH41|14dd3ba6-5c38-11e1-931a-5cf3fc6e4680|5c:f3:fc:6e:13:e1| lenovo-imm2| 172.30.3.1,fe80::5ef3:fcff:fe6e:13e1%eth1`
|
||||
` n7| 8737AC1| 23XXH32|79d2ce28-5cd5-11e1-8c86-5cf3fc6e46b0|5c:f3:fc:6e:13:f9| lenovo-imm2| 172.30.3.7,fe80::5ef3:fcff:fe6e:13f9%eth1`
|
||||
` n8| 8737AC1| 23XXH49|551a8438-5cd5-11e1-8d6c-5cf3fc6e4708|5c:f3:fc:6e:14:25| lenovo-imm2| 172.30.3.8,fe80::5ef3:fcff:fe6e:1425%eth1`
|
||||
` n3| 8737AC1| 23XXH30|1dd7f7b3-5da5-11e1-baf0-5cf3fc6e4738|5c:f3:fc:6e:14:3d| lenovo-imm2| 172.30.3.3,fe80::5ef3:fcff:fe6e:143d%eth1`
|
||||
` n4| 8737AC1| 23XXH35|45b81dae-5d9b-11e1-8337-5cf3fc6e4858|5c:f3:fc:6e:14:cd| lenovo-imm2| 172.30.3.4,fe80::5ef3:fcff:fe6e:14cd%eth1`
|
||||
` n11| 8737AC1| 23XXH12|31d90128-5c37-11e1-bdb7-5cf3fc6e4920|5c:f3:fc:6e:15:31| lenovo-imm2| 172.30.3.11,fe80::5ef3:fcff:fe6e:1531%eth1`
|
||||
` n13| 8737AC1| 23XXH44|e23a138a-5cd3-11e1-8f3d-5cf3fc6e4950|5c:f3:fc:6e:15:49| lenovo-imm2| 172.30.3.13,fe80::5ef3:fcff:fe6e:1549%eth1`
|
||||
` | 8737AC1| 23XXH29|5cd1216b-5c37-11e1-ba0c-5cf3fc6e49c8|5c:f3:fc:6e:15:85| lenovo-imm2| 172.30.3.9,fe80::5ef3:fcff:fe6e:1585%eth1`
|
||||
` | 8737AC1| 23ZYT44|f4bf48ca-71f0-11e1-b274-5cf3fc6e4f10|5c:f3:fc:6e:18:29| lenovo-imm2| 172.30.3.10,fe80::5ef3:fcff:fe6e:1829%eth1`
|
||||
` hpcedr| 7915AC1| 06DRHL5|a64e3014-d7e3-11e1-8d21-6cae8b1dff32|6c:ae:8b:1d:ff:36| lenovo-imm2| 172.30.254.250,fe80::6eae:8bff:fe1d:ff36%eth1`
|
||||
` | 8737AC1| 06YRWC3|3af85a51-7efd-11e3-8599-000af7482e00|6c:ae:8b:32:cb:c5| lenovo-imm2| 172.30.3.6,fe80::6eae:8bff:fe32:cbc5%eth1`
|
||||
` | 8737AC1| 06YRWB7|b230f62e-7efd-11e3-9773-000af7482980|6c:ae:8b:32:cd:01| lenovo-imm2| 172.30.3.5,fe80::6eae:8bff:fe32:cd01%eth1`
|
||||
` | 8737AC1| 06YRWC7|09586005-7efe-11e3-9f03-000af7482df0|6c:ae:8b:32:cd:a5| lenovo-imm2| 172.30.3.2,fe80::6eae:8bff:fe32:cda5%eth1`
|
||||
|
||||
* Manually assign a single node according to serial number:
|
||||
`[root@odin ~]# nodediscover assign -s 06PBX15 -n n12`
|
||||
`Assigned: n12`
|
||||
|
||||
* Bulk execute discovery based on spreadsheet:
|
||||
`[root@odin ~]# nodediscover assign -i import.csv`
|
||||
`Defined r2`
|
||||
`Discovered r2`
|
||||
`Defined c1`
|
||||
`Discovered c1`
|
||||
@@ -4,23 +4,10 @@ nodefirmware(8) -- Report firmware information on confluent nodes
|
||||
## SYNOPSIS
|
||||
|
||||
`nodefirmware <noderange>`
|
||||
`nodefirmware <noderange> <components>|core`
|
||||
`nodefirmware <noderange> update [--backup] <filename>`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
`nodefirmware` reports and updates various firmware on nodes. By default it
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
* `error`: The attempted update encountered an error that prevented successful install. Nodes experiencing this state will be reported at the end and more detail on the error will be given
|
||||
* `pending`: The firmware update process has completed, but the firmware will not be active until the relevant component next resets. Generally speaking, for UEFI update the system will need a reboot, and for BMC updates, the `nodebmcreset` command will begin the process to activate the firmware.
|
||||
* `complete`: The firmware update process has completed and activation has proceeded. Note that while the activation process has commenced, the component may still be in the process of rebooting when nodefirmware exits.
|
||||
`nodefirmware` reports various pieces of firmware on confluent nodes.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
nodegroupdefine(8) -- Define new confluent node group
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodegroupdefine <groupname> [nodeattribute1=value1> <nodeattribute2=value2> ...]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
`nodegroupdefine` allows the definition of a new node for the confluent management
|
||||
service. It may only define a single group name at a time.
|
||||
It has the same syntax as `nodegroupattrib(8)`, and the commands differ in
|
||||
that `nodegroupattrib(8)` will error if a node group does not exist.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
* Create a group called `compute`:
|
||||
`# nodegroupdefine compute`
|
||||
`compute: created`
|
||||
|
||||
|
||||
## SEE ALSO
|
||||
|
||||
nodeattribexpressions(8), nodegroupattrib(8), nodegroupremove(8)
|
||||
@@ -1,18 +0,0 @@
|
||||
nodegroupremove(8) -- Remove a nodegroup from the confluent database
|
||||
====================================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodegroupremove <noderange>`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
`nodegroupremove` simply removes the given single nodegroup from the confluent database.
|
||||
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
* Remove group called testgroup
|
||||
`# nodegroupremove testgroup`
|
||||
`testgroup: deleted`
|
||||
|
||||
@@ -3,15 +3,13 @@ nodeinventory(8) -- Get hardware inventory of confluent node
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodeinventory <noderange> [serial|model|uuid|mac]`
|
||||
`nodeinventory <noderange>`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
`nodeinventory` pulls information about hardware of a node. This includes
|
||||
information such as adapters, serial numbers, processors, and memory modules,
|
||||
as supported by the platforms hardware management implementation. It accepts
|
||||
arguments such as serial or model or others as listed above to filter
|
||||
output to specific data.
|
||||
as supported by the platforms hardware management implementation.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
@@ -106,16 +104,3 @@ output to specific data.
|
||||
`r1: Broadcom NetXtreme Gigabit Ethernet Controller MAC Address 4: 40:f2:e9:af:45:a3`
|
||||
`r1: Broadcom NetXtreme Gigabit Ethernet Controller PCI slot: 1b:00`
|
||||
`r1: Broadcom NetXtreme Gigabit Ethernet Controller location: Onboard`
|
||||
|
||||
* Reporting just the mac addresses of a node named r2:
|
||||
`# nodeinventory r2 mac`
|
||||
`r2: System MAC Address 1: 40:f2:e9:b9:10:18`
|
||||
`r2: System MAC Address 2: 40:f2:e9:b9:10:19`
|
||||
`r2: System MAC Address 3: 40:f2:e9:b9:10:1a`
|
||||
`r2: System MAC Address 4: 40:f2:e9:b9:10:1b`
|
||||
|
||||
* Getting the model number and serial number of a node named s2:
|
||||
`# nodeinventory stark2 serial model`
|
||||
`s2: System Product name: LENOVO THINKSYSTEM SD530 SERVER`
|
||||
`s2: System Serial Number: DEV0000002`
|
||||
`s2: System Model: 7X2104Z028`
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
nodemedia(8) -- Manage server remote media
|
||||
=========================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodemedia <noderange> [attach|detachall|list|upload] [options] <media>`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
**nodemedia** manages the remote media functionality of supported BMCs.
|
||||
|
||||
`list` shows all the current remote media the BMCs of the noderange are
|
||||
providing to the host platform. The string (insecure) is appended to URLs that
|
||||
are mounted in an insecure fashion. http is insecure, and https is also
|
||||
insecure when no meaningful certificate validation is performed. Currently
|
||||
there is no action that can change this, and this is purely informational. A
|
||||
future version of software may provide a means to increase security of attached
|
||||
remote media.
|
||||
|
||||
`detachall` removes all the currently provided media to the host. This unlinks
|
||||
remote media from urls and deletes uploaded media from the BMC.
|
||||
|
||||
`upload` takes the given media image and uploads it to the BMC. This causes
|
||||
the remote media to reside internally to the system without having to go
|
||||
to the network after the upload. This is more constrained, for example the
|
||||
Lenovo xClarity Controller has a limit of 50 megabytes, but it has zero ongoing
|
||||
load on the media source.
|
||||
|
||||
`attach` takes a URL to a remote media as an argument, and has the given
|
||||
BMCs map a virtual USB device to that url. Content is loaded on demand, and
|
||||
as such that URL is referenced potentially once for every IO operation that
|
||||
the host platform attempts.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-c`:
|
||||
Specify the maximum number of instances to run concurrently.
|
||||
|
||||
## EXAMPLES
|
||||
* Listing currently mounted media:
|
||||
`# nodemedia s1-s4 list`
|
||||
`s1: boot.img`
|
||||
`s2: boot.img`
|
||||
`s4: boot.img`
|
||||
|
||||
* Uploading a small boot image to the BMC:
|
||||
`# nodemedia s1-s4 upload boot.img`
|
||||
`s1:complete: 100% s2:complete: 100% s3:complete: 100% s4:complete: 100%`
|
||||
`s1: boot.img`
|
||||
`s4: boot.img`
|
||||
`s2: boot.img`
|
||||
|
||||
* Attaching a larger ISO for on-demand access:
|
||||
`# nodemedia s1,s4 attach http://172.30.0.6/install/rhel74.iso`
|
||||
`s4: http://172.30.0.6/install/rhel74.iso (insecure)`
|
||||
`s1: http://172.30.0.6/install/rhel74.iso (insecure)`
|
||||
@@ -1,57 +0,0 @@
|
||||
noderange(5) -- Indicate a target set of nodes in confluent
|
||||
=========================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`<noderange>`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
<noderange> is a syntax that can be used in most Confluent commands to conveniently specify a list of nodes. The result is that the command will be applied to a range of nodes, often in parallel.
|
||||
|
||||
## EXAMPLES:
|
||||
|
||||
The simplest noderange is a single node name:
|
||||
`n1`
|
||||
|
||||
Nodes and groups may be used interchangeably. The following may be used in any context where n1 would be accepted as a noderange:
|
||||
`rack1`
|
||||
|
||||
Commonly, there is a desire to target a range of elements. There are a few identically behaving syntaxes for the purpose.
|
||||
`n1:n20`
|
||||
`n1-n20`
|
||||
`n[1-20]`
|
||||
|
||||
Note that numbers may be zero padded or not, it will automatically detect the padding amount and adjust members of the range accordingly. Ranges also can understand multiple numeric values changing:
|
||||
`r[1-3]u[01-10]`
|
||||
`r1u01-r3u10`
|
||||
|
||||
Ranges can also be applied to group names, and all above syntaxes are compatible:
|
||||
`rack1-rack10`
|
||||
`rack[1-10]`
|
||||
|
||||
Also, regular expressions may be used to indicate nodes with names matching certain patterns:
|
||||
`~r1u..`
|
||||
|
||||
The other major noderange primitive is indicating nodes by some attribute value:
|
||||
`location.rack=7`
|
||||
|
||||
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:
|
||||
`rack1,-n2`
|
||||
`compute,-rack1`
|
||||
`compute,-location.row=12`
|
||||
|
||||
To indicate nodes that match multiple selections at once (set intersection), the @ symbol may be used:
|
||||
`compute@rack1`
|
||||
`location.rack=10@compute`
|
||||
|
||||
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:
|
||||
`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.
|
||||
@@ -1,25 +0,0 @@
|
||||
noderemove(8) -- Remove nodes from the confluent management service
|
||||
===================================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`noderemove <noderange>`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
`noderemove` simply removes the given noderange from the confluent database.
|
||||
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
* Remove two racks each with 4 nodes:
|
||||
`# noderemove r1u1-r2u4`
|
||||
`r1u4: deleted`
|
||||
`r1u1: deleted`
|
||||
`r1u2: deleted`
|
||||
`r1u3: deleted`
|
||||
`r2u4: deleted`
|
||||
`r2u3: deleted`
|
||||
`r2u2: deleted`
|
||||
`r2u1: deleted`
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
nodereseat(8) -- Request a reseat of a node
|
||||
============================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodereseat <noderange>`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
`nodereseat` requests the enclosure manager of the current node to reseat that
|
||||
node's slot. This should be equivalent to removing the system entirely from
|
||||
the chassis and putting it back in, but without actually having to do so.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
* Reseating the node `s1`:
|
||||
`# nodereseat s1`
|
||||
`s1: Reseat successful`
|
||||
|
||||
@@ -4,7 +4,7 @@ nodesetboot(8) -- Check or set next boot device for noderange
|
||||
## SYNOPSIS
|
||||
|
||||
`nodesetboot <noderange>`
|
||||
`nodesetboot [options] <noderange> [default|cd|network|setup|hd]`
|
||||
`nodesetboot [options] <noderang> [default|cd|network|setup|hd]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
|
||||
@@ -3,19 +3,13 @@ nodeshell(8) -- Execute command on many nodes in a noderange through ssh
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodeshell <noderange> [options] <command to execute on each node>`
|
||||
`nodeshell <noderange> <command to execute on each node>`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
Allows execution of a command on many nodes in parallel. Like noderun(8), it
|
||||
accepts and interpolates confluent attribute expressions as documented in
|
||||
nodeattribexpressions(5). `nodeshell` provides stdout as stdout and stderr
|
||||
as stderr, unlike psh which combines all stdout and stderr into stdout.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-c`:
|
||||
Specify the maximum number of instances to run concurrently.
|
||||
nodeattribexpressions(5).
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
#!/bin/sh
|
||||
cd `dirname $0`/doc/man
|
||||
mkdir -p ../../man/man1
|
||||
mkdir -p ../../man/man5
|
||||
mkdir -p ../../man/man8
|
||||
ronn -r *.ronn
|
||||
mv *.1 ../../man/man1/
|
||||
mv *.5 ../../man/man5/
|
||||
mv *.8 ../../man/man8/
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import os
|
||||
data_files = [('/etc/profile.d', ['confluent_env.sh', 'confluent_env.csh'])]
|
||||
try:
|
||||
scriptlist = ['bin/{0}'.format(d) for d in os.listdir('bin/')]
|
||||
data_files.append(('/opt/confluent/share/man/man1', ['man/man1/' + x for x in os.listdir('man/man1')]))
|
||||
data_files.append(('/opt/confluent/share/man/man5', ['man/man5/' + x for x in os.listdir('man/man5')]))
|
||||
data_files.append(('/opt/confluent/share/man/man8', ['man/man8/' + x for x in os.listdir('man/man8')]))
|
||||
except OSError:
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
include sysvinit/*
|
||||
include systemd/*
|
||||
include sysctl/*
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
__version__ = "1.5.0.dev395.ggacb3a20"
|
||||
|
||||
@@ -279,17 +279,12 @@ node = {
|
||||
'description': 'The method used to perform operations such as power '
|
||||
'control, get sensor data, get inventory, and so on. '
|
||||
},
|
||||
'enclosure.bay': {
|
||||
'description': 'The bay in the enclosure, if any',
|
||||
# 'appliesto': ['system'],
|
||||
},
|
||||
'enclosure.extends': {
|
||||
'description': 'When using an extendable enclosure, this is the node '
|
||||
'representing the manager that is one closer to the '
|
||||
'uplink.',
|
||||
},
|
||||
'enclosure.manager': {
|
||||
'description': "The management device for this node's chassis",
|
||||
# 'appliesto': ['system'],
|
||||
},
|
||||
'enclosure.bay': {
|
||||
'description': 'The bay in the enclosure, if any',
|
||||
# 'appliesto': ['system'],
|
||||
},
|
||||
|
||||
@@ -298,39 +293,17 @@ node = {
|
||||
#IBM Flex)''',
|
||||
# 'appliesto': ['system'],
|
||||
# },
|
||||
'id.model': {
|
||||
'description': 'The model number of a node. In scenarios where there '
|
||||
'is both a name and a model number, it is generally '
|
||||
'expected that this would be the generally more '
|
||||
'specific model number.'
|
||||
},
|
||||
'id.serial': {
|
||||
'description': 'The manufacturer serial number of node',
|
||||
},
|
||||
# 'id.serial': {
|
||||
# 'description': 'The manufacturer serial number of node',
|
||||
# },
|
||||
'id.uuid': {
|
||||
'description': 'The UUID of the node as presented in DMI.',
|
||||
},
|
||||
# For single interface mac collection, net.bootable suffices
|
||||
# For multiple interface mac collection, we perhaps add an address field and use subnet affinity as a clue
|
||||
# to disambiguate multiple addresses for external provisioning
|
||||
# For internal provisioning, the UUID matters rather than the MAC, though subnet affinity may
|
||||
# still be a factor to select the appropriate static config to send down. In such a case bonding
|
||||
# is hopefully more likely as that's a bit easier.
|
||||
# Start with the first case and only document that, the other thoughts can be future items if they turn up
|
||||
'net.bootable': {
|
||||
'type': bool,
|
||||
'description': 'Whether or not the indicated network interface is to be used for booting. This is used by '
|
||||
'the discovery process to decide where to place the mac address of a detected PXE nic.',
|
||||
},
|
||||
'net.ipv4_gateway': {
|
||||
'description': 'The IPv4 gateway to use if applicable. As is the '
|
||||
'case for other net attributes, net.eth0.ipv4_gateway '
|
||||
'and similar is accepted.'
|
||||
},
|
||||
'net.hwaddr': {
|
||||
'description': 'The hardware address, aka MAC address of the interface indicated, generally populated by the '
|
||||
'PXE discovery mechanism'
|
||||
},
|
||||
# 'net.pxe': { 'description': 'Whether pxe will be used on this interface'
|
||||
# TODO(jjohnson2): Above being 'true' will control whether mac addresses
|
||||
# are stored in this nics attribute on pxe-client discovery, since
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2014 IBM Corporation
|
||||
# Copyright 2015-2018 Lenovo
|
||||
# Copyright 2015 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -67,7 +67,6 @@ import base64
|
||||
import confluent.config.attributes as allattributes
|
||||
import confluent.config.conf as conf
|
||||
import confluent.log
|
||||
import confluent.noderange as noderange
|
||||
import confluent.util
|
||||
import confluent.exceptions as exc
|
||||
import copy
|
||||
@@ -91,14 +90,6 @@ _masterintegritykey = None
|
||||
_dirtylock = threading.RLock()
|
||||
_config_areas = ('nodegroups', 'nodes', 'usergroups', 'users')
|
||||
tracelog = None
|
||||
statelessmode = False
|
||||
_cfgstore = None
|
||||
|
||||
_attraliases = {
|
||||
'bmc': 'hardwaremanagement.manager',
|
||||
'bmcuser': 'secret.hardwaremanagementuser',
|
||||
'bmcpass': 'secret.hardwaremanagementpassword',
|
||||
}
|
||||
|
||||
def _mkpath(pathname):
|
||||
try:
|
||||
@@ -232,48 +223,25 @@ def decrypt_value(cryptvalue,
|
||||
return value[0:-padsize]
|
||||
|
||||
|
||||
def fixup_attribute(attrname, attrval):
|
||||
# Normalize some data, for example strings and numbers to bool
|
||||
attrname = _get_valid_attrname(attrname)
|
||||
if attrname not in allattributes.node: # no fixup possible
|
||||
return attrval
|
||||
if 'type' in allattributes.node[attrname] and not isinstance(attrval, allattributes.node[attrname]['type']):
|
||||
if (allattributes.node[attrname]['type'] == bool and
|
||||
(isinstance(attrval, str) or isinstance(attrval, unicode))):
|
||||
return attrval.lower() in ('true', '1', 'y', 'yes', 'enable', 'enabled')
|
||||
return attrval
|
||||
|
||||
|
||||
def attribute_is_invalid(attrname, attrval):
|
||||
if attrname.startswith('custom.'):
|
||||
# No type checking or name checking is provided for custom,
|
||||
# it's not possible
|
||||
return False
|
||||
attrname = _get_valid_attrname(attrname)
|
||||
if attrname not in allattributes.node:
|
||||
# Otherwise, it must be in the allattributes key list
|
||||
return True
|
||||
if 'type' in allattributes.node[attrname]:
|
||||
if not isinstance(attrval, allattributes.node[attrname]['type']):
|
||||
# it is valid if it is {'value': actualvalue}
|
||||
if (isinstance(attrval, dict) and 'value' in attrval and
|
||||
isinstance(attrval['value'],
|
||||
allattributes.node[attrname]['type'])):
|
||||
return False
|
||||
# provide type checking for attributes with a specific type
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _get_valid_attrname(attrname):
|
||||
if attrname.startswith('net.'):
|
||||
# For net.* attribtues, split on the dots and put back together
|
||||
# longer term we might want a generic approach, but
|
||||
# right now it's just net. attributes
|
||||
netattrparts = attrname.split('.')
|
||||
attrname = netattrparts[0] + '.' + netattrparts[-1]
|
||||
return attrname
|
||||
|
||||
if attrname not in allattributes.node:
|
||||
# Otherwise, it must be in the allattributes key list
|
||||
return True
|
||||
if 'type' in allattributes.node[attrname]:
|
||||
if not isinstance(attrval, allattributes.node[attrname]['type']):
|
||||
# provide type checking for attributes with a specific type
|
||||
return True
|
||||
return False
|
||||
|
||||
def crypt_value(value,
|
||||
key=None,
|
||||
@@ -291,10 +259,7 @@ def crypt_value(value,
|
||||
neededpad = 16 - (len(value) % 16)
|
||||
pad = chr(neededpad) * neededpad
|
||||
value += pad
|
||||
try:
|
||||
cryptval = crypter.encrypt(value)
|
||||
except TypeError:
|
||||
cryptval = crypter.encrypt(value.encode('utf-8'))
|
||||
cryptval = crypter.encrypt(value)
|
||||
hmac = HMAC.new(integritykey, cryptval, SHA256).digest()
|
||||
return iv, cryptval, hmac
|
||||
|
||||
@@ -308,7 +273,7 @@ def _load_dict_from_dbm(dpath, tdb):
|
||||
currdict[elem] = {}
|
||||
currdict = currdict[elem]
|
||||
try:
|
||||
for tk in dbe:
|
||||
for tk in dbe.iterkeys():
|
||||
currdict[tk] = cPickle.loads(dbe[tk])
|
||||
except AttributeError:
|
||||
tk = dbe.firstkey()
|
||||
@@ -332,8 +297,6 @@ def get_global(globalname):
|
||||
|
||||
:param globalname: The global parameter name to read
|
||||
"""
|
||||
if _cfgstore is None:
|
||||
init()
|
||||
try:
|
||||
return _cfgstore['globals'][globalname]
|
||||
except KeyError:
|
||||
@@ -350,8 +313,6 @@ def set_global(globalname, value):
|
||||
:param globalname: The global parameter name to store
|
||||
:param value: The value to set the global parameter to.
|
||||
"""
|
||||
if _cfgstore is None:
|
||||
init()
|
||||
with _dirtylock:
|
||||
if 'dirtyglobals' not in _cfgstore:
|
||||
_cfgstore['dirtyglobals'] = set()
|
||||
@@ -422,27 +383,20 @@ class _ExpressionFormat(string.Formatter):
|
||||
return node.n
|
||||
elif isinstance(node, ast.Attribute):
|
||||
#ok, we have something with a dot
|
||||
left = node
|
||||
key = ''
|
||||
while isinstance(left, ast.Attribute):
|
||||
# Loop through, to handle multi dot expressions
|
||||
# such as 'net.pxe.hwaddr'
|
||||
key = '.' + left.attr + key
|
||||
left = left.value
|
||||
key = left.id + key
|
||||
if (not key.startswith('custom.') and
|
||||
_get_valid_attrname(key) not in allattributes.node):
|
||||
raise ValueError(
|
||||
'{0} is not a valid attribute name'.format(key))
|
||||
val = self._expand_attribute(key)
|
||||
return val['value'] if val and 'value' in val else ""
|
||||
left = node.value.id
|
||||
right = node.attr
|
||||
key = left + '.' + right
|
||||
if '_expressionkeys' not in self._nodeobj:
|
||||
self._nodeobj['_expressionkeys'] = set([key])
|
||||
else:
|
||||
self._nodeobj['_expressionkeys'].add(key)
|
||||
val = _decode_attribute(key, self._nodeobj,
|
||||
formatter=self)
|
||||
return val['value'] if 'value' in val else ""
|
||||
elif isinstance(node, ast.Name):
|
||||
var = node.id
|
||||
if var in ('node', 'nodename'):
|
||||
if var == 'nodename':
|
||||
return self._nodename
|
||||
if var in _attraliases:
|
||||
val = self._expand_attribute(_attraliases[var])
|
||||
return val['value'] if 'value' in val else ""
|
||||
mg = re.match(self.posmatch, var)
|
||||
if mg:
|
||||
idx = int(mg.group(1))
|
||||
@@ -451,12 +405,13 @@ class _ExpressionFormat(string.Formatter):
|
||||
return int(self._numbers[idx - 1])
|
||||
else:
|
||||
if var in self._nodeobj:
|
||||
val = self._expand_attribute(var)
|
||||
return val['value'] if val and 'value' in val else ""
|
||||
elif (not var.startswith('custom.') and
|
||||
_get_valid_attrname(var) not in allattributes.node):
|
||||
raise ValueError(
|
||||
'{0} is not a valid attribute name'.format(var))
|
||||
if '_expressionkeys' not in self._nodeobj:
|
||||
self._nodeobj['_expressionkeys'] = set([var])
|
||||
else:
|
||||
self._nodeobj['_expressionkeys'].add(var)
|
||||
val = _decode_attribute(var, self._nodeobj,
|
||||
formatter=self)
|
||||
return val['value'] if 'value' in val else ""
|
||||
elif isinstance(node, ast.BinOp):
|
||||
optype = type(node.op)
|
||||
if optype not in self._supported_ops:
|
||||
@@ -465,15 +420,6 @@ class _ExpressionFormat(string.Formatter):
|
||||
return op(int(self._handle_ast_node(node.left)),
|
||||
int(self._handle_ast_node(node.right)))
|
||||
|
||||
def _expand_attribute(self, key):
|
||||
if '_expressionkeys' not in self._nodeobj:
|
||||
self._nodeobj['_expressionkeys'] = set([key])
|
||||
else:
|
||||
self._nodeobj['_expressionkeys'].add(key)
|
||||
val = _decode_attribute(key, self._nodeobj,
|
||||
formatter=self)
|
||||
return val
|
||||
|
||||
|
||||
def _decode_attribute(attribute, nodeobj, formatter=None, decrypt=False):
|
||||
if attribute not in nodeobj:
|
||||
@@ -527,7 +473,7 @@ def hook_new_configmanagers(callback):
|
||||
#TODO(jbjohnso): actually live up to the promise of ongoing callbacks
|
||||
callback(ConfigManager(None))
|
||||
try:
|
||||
for tenant in _cfgstore['tenant']:
|
||||
for tenant in _cfgstore['tenant'].iterkeys():
|
||||
callback(ConfigManager(tenant))
|
||||
except KeyError:
|
||||
pass
|
||||
@@ -549,8 +495,6 @@ class ConfigManager(object):
|
||||
|
||||
def __init__(self, tenant, decrypt=False, username=None):
|
||||
global _cfgstore
|
||||
if _cfgstore is None:
|
||||
init()
|
||||
self.decrypt = decrypt
|
||||
self.current_user = username
|
||||
if tenant is None:
|
||||
@@ -653,7 +597,7 @@ class ConfigManager(object):
|
||||
def watch_attributes(self, nodes, attributes, callback):
|
||||
"""
|
||||
Watch a list of attributes for changes on a list of nodes. The
|
||||
attributes may be literal, or a filename style wildcard like
|
||||
attributes may be literal, or a filename style wildcard like
|
||||
'net*.switch'
|
||||
|
||||
:param nodes: An iterable of node names to be watching
|
||||
@@ -733,7 +677,7 @@ class ConfigManager(object):
|
||||
|
||||
def list_users(self):
|
||||
try:
|
||||
return list(self._cfgstore['users'])
|
||||
return self._cfgstore['users'].iterkeys()
|
||||
except KeyError:
|
||||
return []
|
||||
|
||||
@@ -775,7 +719,7 @@ class ConfigManager(object):
|
||||
:param attributemap: The mapping of keys to values to set
|
||||
"""
|
||||
|
||||
for attribute in attributemap:
|
||||
for attribute in attributemap.iterkeys():
|
||||
self._cfgstore['usergroups'][attribute] = attributemap[attribute]
|
||||
_mark_dirtykey('usergroups', groupname, self.tenant)
|
||||
|
||||
@@ -858,23 +802,19 @@ class ConfigManager(object):
|
||||
def is_nodegroup(self, nodegroup):
|
||||
return nodegroup in self._cfgstore['nodegroups']
|
||||
|
||||
def get_groups(self, sizesort=False):
|
||||
if sizesort:
|
||||
return reversed(
|
||||
sorted(self._cfgstore['nodegroups'], key=lambda x: len(
|
||||
self._cfgstore['nodegroups'][x]['nodes'])))
|
||||
return iter(self._cfgstore['nodegroups'])
|
||||
def get_groups(self):
|
||||
return self._cfgstore['nodegroups'].iterkeys()
|
||||
|
||||
def list_nodes(self):
|
||||
try:
|
||||
return iter(self._cfgstore['nodes'])
|
||||
return self._cfgstore['nodes'].iterkeys()
|
||||
except KeyError:
|
||||
return []
|
||||
|
||||
def get_nodegroup_attributes(self, nodegroup, attributes=()):
|
||||
cfgnodeobj = self._cfgstore['nodegroups'][nodegroup]
|
||||
if not attributes:
|
||||
attributes = cfgnodeobj
|
||||
attributes = cfgnodeobj.iterkeys()
|
||||
nodeobj = {}
|
||||
for attribute in attributes:
|
||||
if attribute.startswith('_'):
|
||||
@@ -933,7 +873,7 @@ class ConfigManager(object):
|
||||
groupcfg = self._cfgstore['nodegroups'][group]
|
||||
except KeyError: # something did not exist, nothing to do
|
||||
return
|
||||
for attrib in groupcfg:
|
||||
for attrib in groupcfg.iterkeys():
|
||||
self._do_inheritance(nodecfg, attrib, node, changeset)
|
||||
_addchange(changeset, node, attrib)
|
||||
|
||||
@@ -987,7 +927,7 @@ class ConfigManager(object):
|
||||
return
|
||||
|
||||
def _sync_groups_to_node(self, groups, node, changeset):
|
||||
for group in self._cfgstore['nodegroups']:
|
||||
for group in self._cfgstore['nodegroups'].iterkeys():
|
||||
if group not in groups:
|
||||
if node in self._cfgstore['nodegroups'][group]['nodes']:
|
||||
self._cfgstore['nodegroups'][group]['nodes'].discard(node)
|
||||
@@ -1004,7 +944,7 @@ class ConfigManager(object):
|
||||
self._node_added_to_group(node, group, changeset)
|
||||
|
||||
def _sync_nodes_to_group(self, nodes, group, changeset):
|
||||
for node in self._cfgstore['nodes']:
|
||||
for node in self._cfgstore['nodes'].iterkeys():
|
||||
if node not in nodes and 'groups' in self._cfgstore['nodes'][node]:
|
||||
if group in self._cfgstore['nodes'][node]['groups']:
|
||||
self._cfgstore['nodes'][node]['groups'].remove(group)
|
||||
@@ -1025,49 +965,17 @@ class ConfigManager(object):
|
||||
|
||||
def set_group_attributes(self, attribmap, autocreate=False):
|
||||
changeset = {}
|
||||
for group in attribmap:
|
||||
for group in attribmap.iterkeys():
|
||||
if group == '':
|
||||
raise ValueError('"{0}" is not a valid group name'.format(
|
||||
group))
|
||||
if autocreate:
|
||||
try:
|
||||
noderange._parser.parseString(
|
||||
'({0})'.format(group)).asList()
|
||||
except noderange.pp.ParseException as pe:
|
||||
raise ValueError('"{0}" is not a valid group name'.format(
|
||||
group))
|
||||
if not autocreate and group not in self._cfgstore['nodegroups']:
|
||||
raise ValueError("{0} group does not exist".format(group))
|
||||
for attr in attribmap[group]:
|
||||
# first do a pass to normalize out any aliased attribute names
|
||||
if attr in _attraliases:
|
||||
newattr = _attraliases[attr]
|
||||
attribmap[group][newattr] = attribmap[group][attr]
|
||||
del attribmap[group][attr]
|
||||
for attr in attribmap[group]:
|
||||
if attr in _attraliases:
|
||||
newattr = _attraliases[attr]
|
||||
attribmap[group][newattr] = attribmap[group][attr]
|
||||
del attribmap[group][attr]
|
||||
if attr not in ('nodes', 'noderange'):
|
||||
attrval = fixup_attribute(attr, attribmap[group][attr])
|
||||
if attribute_is_invalid(attr, attrval):
|
||||
errstr = "{0} attribute is invalid".format(attr)
|
||||
raise ValueError(errstr)
|
||||
attribmap[group][attr] = attrval
|
||||
for attr in attribmap[group].iterkeys():
|
||||
if (attr not in ('nodes', 'noderange') and
|
||||
attribute_is_invalid(attr, attribmap[group][attr])):
|
||||
raise ValueError("{0} attribute is invalid".format(attr))
|
||||
if attr == 'nodes':
|
||||
if isinstance(attribmap[group][attr], dict):
|
||||
currnodes = list(self.get_nodegroup_attributes(
|
||||
group, ['nodes']).get('nodes', []))
|
||||
if attribmap[group][attr].get('prepend', False):
|
||||
newnodes = attribmap[group][attr][
|
||||
'prepend'].split(',')
|
||||
attribmap[group][attr] = newnodes + currnodes
|
||||
elif attribmap[group][attr].get('remove', False):
|
||||
delnodes = attribmap[group][attr][
|
||||
'remove'].split(',')
|
||||
attribmap[group][attr] = [
|
||||
x for x in currnodes if x not in delnodes]
|
||||
if not isinstance(attribmap[group][attr], list):
|
||||
if type(attribmap[group][attr]) is unicode or type(attribmap[group][attr]) is str:
|
||||
attribmap[group][attr]=attribmap[group][attr].split(",")
|
||||
@@ -1078,17 +986,16 @@ class ConfigManager(object):
|
||||
raise ValueError(
|
||||
"{0} node does not exist to add to {1}".format(
|
||||
node, group))
|
||||
for group in attribmap:
|
||||
for group in attribmap.iterkeys():
|
||||
group = group.encode('utf-8')
|
||||
if group not in self._cfgstore['nodegroups']:
|
||||
self._cfgstore['nodegroups'][group] = {'nodes': set()}
|
||||
cfgobj = self._cfgstore['nodegroups'][group]
|
||||
for attr in attribmap[group]:
|
||||
for attr in attribmap[group].iterkeys():
|
||||
if attr == 'nodes':
|
||||
newdict = set(attribmap[group][attr])
|
||||
elif (isinstance(attribmap[group][attr], str) or
|
||||
isinstance(attribmap[group][attr], unicode) or
|
||||
isinstance(attribmap[group][attr], bool)):
|
||||
isinstance(attribmap[group][attr], unicode)):
|
||||
newdict = {'value': attribmap[group][attr]}
|
||||
else:
|
||||
newdict = attribmap[group][attr]
|
||||
@@ -1112,13 +1019,6 @@ class ConfigManager(object):
|
||||
|
||||
def clear_group_attributes(self, groups, attributes):
|
||||
changeset = {}
|
||||
realattributes = []
|
||||
for attrname in list(attributes):
|
||||
if attrname in _attraliases:
|
||||
realattributes.append(_attraliases[attrname])
|
||||
else:
|
||||
realattributes.append(attrname)
|
||||
attributes = realattributes
|
||||
if type(groups) in (str, unicode):
|
||||
groups = (groups,)
|
||||
for group in groups:
|
||||
@@ -1250,13 +1150,6 @@ class ConfigManager(object):
|
||||
def clear_node_attributes(self, nodes, attributes):
|
||||
# accumulate all changes into a changeset and push in one go
|
||||
changeset = {}
|
||||
realattributes = []
|
||||
for attrname in list(attributes):
|
||||
if attrname in _attraliases:
|
||||
realattributes.append(_attraliases[attrname])
|
||||
else:
|
||||
realattributes.append(attrname)
|
||||
attributes = realattributes
|
||||
for node in nodes:
|
||||
node = node.encode('utf-8')
|
||||
try:
|
||||
@@ -1283,7 +1176,7 @@ class ConfigManager(object):
|
||||
self._bg_sync_to_file()
|
||||
|
||||
def add_node_attributes(self, attribmap):
|
||||
for node in attribmap:
|
||||
for node in attribmap.iterkeys():
|
||||
if 'groups' not in attribmap[node]:
|
||||
attribmap[node]['groups'] = []
|
||||
self.set_node_attributes(attribmap, autocreate=True)
|
||||
@@ -1296,47 +1189,22 @@ class ConfigManager(object):
|
||||
changeset = {}
|
||||
# first do a sanity check of the input upfront
|
||||
# this mitigates risk of arguments being partially applied
|
||||
for node in attribmap:
|
||||
for node in attribmap.iterkeys():
|
||||
node = node.encode('utf-8')
|
||||
if node == '':
|
||||
raise ValueError('"{0}" is not a valid node name'.format(node))
|
||||
if autocreate:
|
||||
try:
|
||||
noderange._parser.parseString(
|
||||
'({0})'.format(node)).asList()
|
||||
except noderange.pp.ParseException as pe:
|
||||
raise ValueError(
|
||||
'"{0}" is not a valid node name'.format(node))
|
||||
if autocreate is False and node not in self._cfgstore['nodes']:
|
||||
raise ValueError("node {0} does not exist".format(node))
|
||||
for attrname in list(attribmap[node]):
|
||||
if attrname in _attraliases:
|
||||
truename = _attraliases[attrname]
|
||||
attribmap[node][truename] = attribmap[node][attrname]
|
||||
del attribmap[node][attrname]
|
||||
for attrname in attribmap[node]:
|
||||
for attrname in attribmap[node].iterkeys():
|
||||
attrval = attribmap[node][attrname]
|
||||
try:
|
||||
if (allattributes.node[attrname]['type'] == 'list' and
|
||||
type(attrval) in (str, unicode)):
|
||||
type(attrval) in (str, unicode)):
|
||||
attrval = attrval.split(",")
|
||||
except KeyError:
|
||||
pass
|
||||
if attrname == 'groups':
|
||||
if isinstance(attribmap[node]['groups'], dict):
|
||||
currgroups = self.get_node_attributes(
|
||||
node, 'groups').get(node, {}).get('groups', [])
|
||||
if attribmap[node]['groups'].get('prepend', False):
|
||||
newgroups = attribmap[node]['groups'][
|
||||
'prepend'].split(',')
|
||||
attribmap[node]['groups'] = newgroups + currgroups
|
||||
elif attribmap[node]['groups'].get('remove', False):
|
||||
delgroups = attribmap[node]['groups'][
|
||||
'remove'].split(',')
|
||||
newgroups = [
|
||||
x for x in currgroups if x not in delgroups]
|
||||
attribmap[node]['groups'] = newgroups
|
||||
elif type(attribmap[node]['groups']) != list:
|
||||
if type(attribmap[node]['groups']) != list:
|
||||
attribmap[node]['groups']=attribmap[node]['groups'].split(",")
|
||||
for group in attribmap[node]['groups']:
|
||||
if group not in self._cfgstore['nodegroups']:
|
||||
@@ -1345,14 +1213,11 @@ class ConfigManager(object):
|
||||
if ('everything' in self._cfgstore['nodegroups'] and
|
||||
'everything' not in attribmap[node]['groups']):
|
||||
attribmap[node]['groups'].append('everything')
|
||||
else:
|
||||
attrval = fixup_attribute(attrname, attrval)
|
||||
if attribute_is_invalid(attrname, attrval):
|
||||
errstr = "{0} attribute on node {1} is invalid".format(
|
||||
attrname, node)
|
||||
raise ValueError(errstr)
|
||||
attribmap[node][attrname] = attrval
|
||||
for node in attribmap:
|
||||
elif attribute_is_invalid(attrname, attrval):
|
||||
errstr = "{0} attribute on node {1} is invalid".format(
|
||||
attrname, node)
|
||||
raise ValueError(errstr)
|
||||
for node in attribmap.iterkeys():
|
||||
node = node.encode('utf-8')
|
||||
exprmgr = None
|
||||
if node not in self._cfgstore['nodes']:
|
||||
@@ -1360,10 +1225,9 @@ class ConfigManager(object):
|
||||
self._cfgstore['nodes'][node] = {}
|
||||
cfgobj = self._cfgstore['nodes'][node]
|
||||
recalcexpressions = False
|
||||
for attrname in attribmap[node]:
|
||||
for attrname in attribmap[node].iterkeys():
|
||||
if (isinstance(attribmap[node][attrname], str) or
|
||||
isinstance(attribmap[node][attrname], unicode) or
|
||||
isinstance(attribmap[node][attrname], bool)):
|
||||
isinstance(attribmap[node][attrname], unicode)):
|
||||
newdict = {'value': attribmap[node][attrname]}
|
||||
else:
|
||||
newdict = attribmap[node][attrname]
|
||||
@@ -1447,9 +1311,7 @@ class ConfigManager(object):
|
||||
pass
|
||||
# Now we have to iterate through each fixed up element, using the
|
||||
# set attribute to flesh out inheritence and expressions
|
||||
_cfgstore['main']['idmap'] = {}
|
||||
for confarea in _config_areas:
|
||||
self._cfgstore[confarea] = {}
|
||||
if confarea not in tmpconfig:
|
||||
continue
|
||||
if confarea == 'nodes':
|
||||
@@ -1485,10 +1347,10 @@ class ConfigManager(object):
|
||||
if confarea not in self._cfgstore:
|
||||
continue
|
||||
dumpdata[confarea] = {}
|
||||
for element in self._cfgstore[confarea]:
|
||||
for element in self._cfgstore[confarea].iterkeys():
|
||||
dumpdata[confarea][element] = \
|
||||
copy.deepcopy(self._cfgstore[confarea][element])
|
||||
for attribute in self._cfgstore[confarea][element]:
|
||||
for attribute in self._cfgstore[confarea][element].iterkeys():
|
||||
if 'inheritedfrom' in dumpdata[confarea][element][attribute]:
|
||||
del dumpdata[confarea][element][attribute]
|
||||
elif (attribute == 'cryptpass' or
|
||||
@@ -1546,8 +1408,6 @@ class ConfigManager(object):
|
||||
|
||||
@classmethod
|
||||
def _bg_sync_to_file(cls):
|
||||
if statelessmode:
|
||||
return
|
||||
with cls._syncstate:
|
||||
if cls._syncrunning:
|
||||
cls._writepending = True
|
||||
@@ -1561,8 +1421,6 @@ class ConfigManager(object):
|
||||
|
||||
@classmethod
|
||||
def _sync_to_file(cls):
|
||||
if statelessmode:
|
||||
return
|
||||
if 'dirtyglobals' in _cfgstore:
|
||||
with _dirtylock:
|
||||
dirtyglobals = copy.deepcopy(_cfgstore['dirtyglobals'])
|
||||
@@ -1583,7 +1441,7 @@ class ConfigManager(object):
|
||||
with _dirtylock:
|
||||
currdirt = copy.deepcopy(_cfgstore['dirtykeys'])
|
||||
del _cfgstore['dirtykeys']
|
||||
for tenant in currdirt:
|
||||
for tenant in currdirt.iterkeys():
|
||||
dkdict = currdirt[tenant]
|
||||
if tenant is None:
|
||||
pathname = cls._cfgdir
|
||||
@@ -1591,7 +1449,7 @@ class ConfigManager(object):
|
||||
else:
|
||||
pathname = os.path.join(cls._cfgdir, 'tenants', tenant)
|
||||
currdict = _cfgstore['tenant'][tenant]
|
||||
for category in dkdict:
|
||||
for category in dkdict.iterkeys():
|
||||
_mkpath(pathname)
|
||||
dbf = dbm.open(os.path.join(pathname, category), 'c', 384) # 0600
|
||||
try:
|
||||
@@ -1614,7 +1472,7 @@ class ConfigManager(object):
|
||||
return cls._sync_to_file()
|
||||
|
||||
def _recalculate_expressions(self, cfgobj, formatter, node, changeset):
|
||||
for key in cfgobj:
|
||||
for key in cfgobj.iterkeys():
|
||||
if not isinstance(cfgobj[key], dict):
|
||||
continue
|
||||
if 'expression' in cfgobj[key]:
|
||||
@@ -1708,15 +1566,11 @@ def dump_db_to_directory(location, password, redact=None):
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def init(stateless=False):
|
||||
global _cfgstore
|
||||
if stateless:
|
||||
_cfgstore = {}
|
||||
return
|
||||
try:
|
||||
ConfigManager._read_from_path()
|
||||
except IOError:
|
||||
_cfgstore = {}
|
||||
|
||||
try:
|
||||
ConfigManager._read_from_path()
|
||||
except IOError:
|
||||
_cfgstore = {}
|
||||
|
||||
|
||||
# some unit tests worth implementing:
|
||||
|
||||
@@ -145,7 +145,6 @@ class ConsoleHandler(object):
|
||||
self._dologging = True
|
||||
self._isondemand = False
|
||||
self.error = None
|
||||
self._retrytime = 0
|
||||
self.cfgmgr = configmanager
|
||||
self.node = node
|
||||
self.connectstate = 'unconnected'
|
||||
@@ -191,16 +190,6 @@ class ConsoleHandler(object):
|
||||
self.connectstate = 'connecting'
|
||||
eventlet.spawn(self._connect)
|
||||
|
||||
def _get_retry_time(self):
|
||||
clustsize = len(self.cfgmgr._cfgstore['nodes'])
|
||||
self._retrytime = self._retrytime * 2 + 1
|
||||
if self._retrytime > 120:
|
||||
self._retrytime = 120
|
||||
retrytime = clustsize * 0.05 * self._retrytime
|
||||
if retrytime > 120:
|
||||
retrytime = 120
|
||||
return retrytime + (retrytime * random.random())
|
||||
|
||||
def feedbuffer(self, data):
|
||||
try:
|
||||
self.termstream.feed(data)
|
||||
@@ -219,12 +208,11 @@ class ConsoleHandler(object):
|
||||
self._isondemand = False
|
||||
elif 'console.logging' not in attrvalue[self.node]:
|
||||
self._isondemand = False
|
||||
else:
|
||||
if (attrvalue[self.node]['console.logging']['value'] not in (
|
||||
'full', '', 'buffer')):
|
||||
self._isondemand = True
|
||||
if (attrvalue[self.node]['console.logging']['value']) in ('none', 'memory'):
|
||||
self._dologging = False
|
||||
elif (attrvalue[self.node]['console.logging']['value'] not in (
|
||||
'full', '')):
|
||||
self._isondemand = True
|
||||
elif (attrvalue[self.node]['console.logging']['value']) == 'none':
|
||||
self._dologging = False
|
||||
|
||||
def get_buffer_age(self):
|
||||
"""Return age of buffered data
|
||||
@@ -259,7 +247,7 @@ class ConsoleHandler(object):
|
||||
return
|
||||
else:
|
||||
self._ondemand()
|
||||
if logvalue in ('none', 'memory'):
|
||||
if logvalue == 'none':
|
||||
self._dologging = False
|
||||
if not self._isondemand or self.livesessions:
|
||||
eventlet.spawn(self._connect)
|
||||
@@ -323,7 +311,7 @@ class ConsoleHandler(object):
|
||||
self._console = plugin.handle_path(
|
||||
self._plugin_path.format(self.node),
|
||||
"create", self.cfgmgr)
|
||||
except (exc.NotImplementedException, exc.NotFoundException):
|
||||
except exc.NotImplementedException:
|
||||
self._console = None
|
||||
except:
|
||||
_tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
|
||||
@@ -354,7 +342,7 @@ class ConsoleHandler(object):
|
||||
self.connectstate = 'unconnected'
|
||||
self._send_rcpts({'connectstate': self.connectstate,
|
||||
'error': self.error})
|
||||
retrytime = self._get_retry_time()
|
||||
retrytime = 120 + (120 * random.random())
|
||||
if not self.reconnect:
|
||||
self.reconnect = eventlet.spawn_after(retrytime, self._connect)
|
||||
return
|
||||
@@ -364,7 +352,7 @@ class ConsoleHandler(object):
|
||||
self.connectstate = 'unconnected'
|
||||
self._send_rcpts({'connectstate': self.connectstate,
|
||||
'error': self.error})
|
||||
retrytime = self._get_retry_time()
|
||||
retrytime = 120 + (120 * random.random())
|
||||
if not self.reconnect:
|
||||
self.reconnect = eventlet.spawn_after(retrytime, self._connect)
|
||||
return
|
||||
@@ -376,7 +364,7 @@ class ConsoleHandler(object):
|
||||
self.connectstate = 'unconnected'
|
||||
self._send_rcpts({'connectstate': self.connectstate,
|
||||
'error': self.error})
|
||||
retrytime = self._get_retry_time()
|
||||
retrytime = 120 + (120 * random.random())
|
||||
if not self.reconnect:
|
||||
self.reconnect = eventlet.spawn_after(retrytime, self._connect)
|
||||
return
|
||||
@@ -384,7 +372,6 @@ class ConsoleHandler(object):
|
||||
|
||||
def _got_connected(self):
|
||||
self.connectstate = 'connected'
|
||||
self._retrytime = 0
|
||||
self.log(
|
||||
logdata='console connected', ltype=log.DataTypes.event,
|
||||
event=log.Events.consoleconnect)
|
||||
|
||||
@@ -172,13 +172,7 @@ def _init_core():
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
},
|
||||
},
|
||||
'system': {
|
||||
'all': PluginRoute({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
},
|
||||
}
|
||||
},
|
||||
'_console': {
|
||||
'session': PluginRoute({
|
||||
@@ -191,11 +185,6 @@ def _init_core():
|
||||
'handler': 'ssh',
|
||||
}),
|
||||
},
|
||||
'_enclosure': {
|
||||
'reseat_bay': PluginRoute(
|
||||
{'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi'}),
|
||||
},
|
||||
'shell': {
|
||||
# another special case similar to console
|
||||
'sessions': PluginCollection({
|
||||
@@ -222,10 +211,6 @@ def _init_core():
|
||||
}),
|
||||
},
|
||||
},
|
||||
#'forward': {
|
||||
# # Another dummy value, currently only for the gui
|
||||
# 'web': None,
|
||||
#},
|
||||
'health': {
|
||||
'hardware': PluginRoute({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
@@ -248,39 +233,13 @@ def _init_core():
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
'updates': {
|
||||
'active': PluginCollection({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
'media': {
|
||||
'uploads': PluginCollection({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
'attach': PluginRoute({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
'detach': PluginRoute({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
'current': PluginRoute({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
|
||||
},
|
||||
'power': {
|
||||
'state': PluginRoute({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
'reseat': PluginRoute({'handler': 'enclosure'}),
|
||||
},
|
||||
'sensors': {
|
||||
'hardware': {
|
||||
@@ -288,10 +247,6 @@ def _init_core():
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
'energy': PluginCollection({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
'temperature': PluginCollection({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
@@ -371,12 +326,10 @@ def iterate_collections(iterable, forcecollection=True):
|
||||
|
||||
|
||||
def iterate_resources(fancydict):
|
||||
for resource in fancydict:
|
||||
for resource in fancydict.iterkeys():
|
||||
if resource.startswith("_"):
|
||||
continue
|
||||
if resource == 'abbreviate':
|
||||
pass
|
||||
elif not isinstance(fancydict[resource], PluginRoute): # a resource
|
||||
if not isinstance(fancydict[resource], PluginRoute): # a resource
|
||||
resource += '/'
|
||||
yield msg.ChildCollection(resource)
|
||||
|
||||
@@ -410,8 +363,7 @@ def delete_node_collection(collectionpath, configmanager, isnoderange):
|
||||
def enumerate_nodegroup_collection(collectionpath, configmanager):
|
||||
nodegroup = collectionpath[1]
|
||||
if not configmanager.is_nodegroup(nodegroup):
|
||||
raise exc.NotFoundException(
|
||||
'Invalid nodegroup: {0} not found'.format(nodegroup))
|
||||
raise exc.NotFoundException("Invalid element requested")
|
||||
del collectionpath[0:2]
|
||||
collection = nested_lookup(nodegroupresources, collectionpath)
|
||||
return iterate_resources(collection)
|
||||
@@ -431,7 +383,6 @@ def enumerate_node_collection(collectionpath, configmanager):
|
||||
collection = nested_lookup(noderesources, collectionpath[2:])
|
||||
if len(collectionpath) == 2 and collectionpath[0] == 'noderange':
|
||||
collection['nodes'] = {}
|
||||
collection['abbreviate'] = {}
|
||||
if not isinstance(collection, dict):
|
||||
raise exc.NotFoundException("Invalid element requested")
|
||||
return iterate_resources(collection)
|
||||
@@ -544,16 +495,6 @@ class BadPlugin(object):
|
||||
self.node, self.plugin + ' is not a supported plugin')
|
||||
|
||||
|
||||
def abbreviate_noderange(configmanager, inputdata, operation):
|
||||
if operation != 'create':
|
||||
raise exc.InvalidArgumentException('Must be a create with nodes in list')
|
||||
if 'nodes' not in inputdata:
|
||||
raise exc.InvalidArgumentException('Must be given list of nodes under key named nodes')
|
||||
if isinstance(inputdata['nodes'], str) or isinstance(inputdata['nodes'], unicode):
|
||||
inputdata['nodes'] = inputdata['nodes'].split(',')
|
||||
return (msg.KeyValueData({'noderange': noderange.ReverseNodeRange(inputdata['nodes'], configmanager).noderange}),)
|
||||
|
||||
|
||||
def handle_node_request(configmanager, inputdata, operation,
|
||||
pathcomponents, autostrip=True):
|
||||
iscollection = False
|
||||
@@ -566,8 +507,6 @@ def handle_node_request(configmanager, inputdata, operation,
|
||||
# facility anyway
|
||||
isnoderange = False
|
||||
pathcomponents = pathcomponents[2:]
|
||||
elif len(pathcomponents) == 3 and pathcomponents[2] == 'abbreviate':
|
||||
return abbreviate_noderange(configmanager, inputdata, operation)
|
||||
else:
|
||||
isnoderange = True
|
||||
else:
|
||||
@@ -576,8 +515,7 @@ def handle_node_request(configmanager, inputdata, operation,
|
||||
nodeorrange = pathcomponents[1]
|
||||
if not isnoderange and not configmanager.is_node(nodeorrange):
|
||||
raise exc.NotFoundException("Invalid Node")
|
||||
if isnoderange and not (len(pathcomponents) == 3 and
|
||||
pathcomponents[2] == 'abbreviate'):
|
||||
if isnoderange:
|
||||
try:
|
||||
nodes = noderange.NodeRange(nodeorrange, configmanager).nodes
|
||||
except Exception as e:
|
||||
@@ -636,8 +574,7 @@ def handle_node_request(configmanager, inputdata, operation,
|
||||
passvalues = []
|
||||
plugroute = routespec.routeinfo
|
||||
inputdata = msg.get_input_message(
|
||||
pathcomponents, operation, inputdata, nodes, isnoderange,
|
||||
configmanager)
|
||||
pathcomponents, operation, inputdata, nodes, isnoderange)
|
||||
if 'handler' in plugroute: # fixed handler definition, easy enough
|
||||
if isinstance(plugroute['handler'], str):
|
||||
hfunc = getattr(pluginmap[plugroute['handler']], operation)
|
||||
@@ -744,8 +681,7 @@ def handle_path(path, operation, configmanager, inputdata=None, autostrip=True):
|
||||
except IndexError: # it's just users/
|
||||
if operation == 'create':
|
||||
inputdata = msg.get_input_message(
|
||||
pathcomponents, operation, inputdata,
|
||||
configmanager=configmanager)
|
||||
pathcomponents, operation, inputdata)
|
||||
create_user(inputdata.attribs, configmanager)
|
||||
return iterate_collections(configmanager.list_users(),
|
||||
forcecollection=False)
|
||||
@@ -757,8 +693,7 @@ def handle_path(path, operation, configmanager, inputdata=None, autostrip=True):
|
||||
return delete_user(user, configmanager)
|
||||
elif operation == 'update':
|
||||
inputdata = msg.get_input_message(
|
||||
pathcomponents, operation, inputdata,
|
||||
configmanager=configmanager)
|
||||
pathcomponents, operation, inputdata)
|
||||
update_user(user, inputdata.attribs, configmanager)
|
||||
return show_user(user, configmanager)
|
||||
elif pathcomponents[0] == 'events':
|
||||
|
||||
@@ -61,7 +61,6 @@
|
||||
# retry until uppercase, lowercase, digit, and symbol all present)
|
||||
# - Apply defined configuration to endpoint
|
||||
|
||||
import base64
|
||||
import confluent.config.configmanager as cfm
|
||||
import confluent.discovery.protocols.pxe as pxe
|
||||
#import confluent.discovery.protocols.ssdp as ssdp
|
||||
@@ -76,13 +75,9 @@ import confluent.messages as msg
|
||||
import confluent.networking.macmap as macmap
|
||||
import confluent.noderange as noderange
|
||||
import confluent.util as util
|
||||
import eventlet
|
||||
import traceback
|
||||
webclient = eventlet.import_patched('pyghmi.util.webclient')
|
||||
|
||||
|
||||
import eventlet
|
||||
import eventlet.greenpool
|
||||
import eventlet.semaphore
|
||||
|
||||
class nesteddict(dict):
|
||||
@@ -96,7 +91,6 @@ nodehandlers = {
|
||||
'service:management-hardware.Lenovo:lenovo-xclarity-controller': xcc,
|
||||
'service:management-hardware.IBM:integrated-management-module2': imm,
|
||||
'pxe-client': pxeh,
|
||||
'service:io-device.Lenovo:management-module': None,
|
||||
}
|
||||
|
||||
servicenames = {
|
||||
@@ -104,7 +98,6 @@ servicenames = {
|
||||
'service:lenovo-smm': 'lenovo-smm',
|
||||
'service:management-hardware.Lenovo:lenovo-xclarity-controller': 'lenovo-xcc',
|
||||
'service:management-hardware.IBM:integrated-management-module2': 'lenovo-imm2',
|
||||
'service:io-device.Lenovo:management-module': 'lenovo-switch',
|
||||
}
|
||||
|
||||
servicebyname = {
|
||||
@@ -112,11 +105,7 @@ servicebyname = {
|
||||
'lenovo-smm': 'service:lenovo-smm',
|
||||
'lenovo-xcc': 'service:management-hardware.Lenovo:lenovo-xclarity-controller',
|
||||
'lenovo-imm2': 'service:management-hardware.IBM:integrated-management-module2',
|
||||
'lenovo-switch': 'service:io-device.Lenovo:management-module',
|
||||
}
|
||||
|
||||
discopool = eventlet.greenpool.GreenPool(500)
|
||||
runningevals = {}
|
||||
# Passive-only auto-detection protocols:
|
||||
# PXE
|
||||
|
||||
@@ -148,60 +137,17 @@ runningevals = {}
|
||||
known_info = {}
|
||||
known_services = {}
|
||||
known_serials = {}
|
||||
known_uuids = nesteddict()
|
||||
known_nodes = nesteddict()
|
||||
unknown_info = {}
|
||||
pending_nodes = {}
|
||||
pending_by_uuid = {}
|
||||
|
||||
|
||||
def enrich_pxe_info(info):
|
||||
sn = None
|
||||
mn = None
|
||||
nodename = info.get('nodename', None)
|
||||
uuid = info.get('uuid', '')
|
||||
if not uuid_is_valid(uuid):
|
||||
return info
|
||||
for mac in known_uuids.get(uuid, {}):
|
||||
if not sn and 'serialnumber' in known_uuids[uuid][mac]:
|
||||
info['serialnumber'] = known_uuids[uuid][mac]['serialnumber']
|
||||
if not mn and 'modelnumber' in known_uuids[uuid][mac]:
|
||||
info['modelnumber'] = known_uuids[uuid][mac]['modelnumber']
|
||||
if nodename is None and 'nodename' in known_uuids[uuid][mac]:
|
||||
info['nodename'] = known_uuids[uuid][mac]['nodename']
|
||||
|
||||
|
||||
|
||||
def uuid_is_valid(uuid):
|
||||
if not uuid:
|
||||
return False
|
||||
return uuid.lower() not in ('00000000-0000-0000-0000-000000000000',
|
||||
'ffffffff-ffff-ffff-ffff-ffffffffffff',
|
||||
'00112233-4455-6677-8899-aabbccddeeff',
|
||||
'20202020-2020-2020-2020-202020202020')
|
||||
|
||||
|
||||
def send_discovery_datum(info):
|
||||
addresses = info.get('addresses', [])
|
||||
if info['handler'] == pxeh:
|
||||
enrich_pxe_info(info)
|
||||
yield msg.KeyValueData({'nodename': info.get('nodename', '')})
|
||||
yield msg.KeyValueData({'ipaddrs': [x[0] for x in addresses]})
|
||||
sn = info.get('serialnumber', '')
|
||||
mn = info.get('modelnumber', '')
|
||||
uuid = info.get('uuid', '')
|
||||
if uuid:
|
||||
relatedmacs = []
|
||||
for mac in known_uuids.get(uuid, {}):
|
||||
if mac and mac != info.get('hwaddr', ''):
|
||||
relatedmacs.append(mac)
|
||||
if relatedmacs:
|
||||
yield msg.KeyValueData({'relatedmacs': relatedmacs})
|
||||
yield msg.KeyValueData({'serialnumber': sn})
|
||||
yield msg.KeyValueData({'modelnumber': mn})
|
||||
yield msg.KeyValueData({'uuid': uuid})
|
||||
if 'enclosure.bay' in info:
|
||||
yield msg.KeyValueData({'bay': int(info['enclosure.bay'])})
|
||||
yield msg.KeyValueData({'serialnumber': info.get('serialnumber', '')})
|
||||
yield msg.KeyValueData({'modelnumber': info.get('modelnumber', '')})
|
||||
yield msg.KeyValueData({'macs': [info.get('hwaddr', '')]})
|
||||
types = []
|
||||
for infotype in info.get('services', []):
|
||||
@@ -216,7 +162,6 @@ def _info_matches(info, criteria):
|
||||
node = criteria.get('by-node', None)
|
||||
serial = criteria.get('by-serial', None)
|
||||
status = criteria.get('by-state', None)
|
||||
uuid = criteria.get('by-uuid', None)
|
||||
if model and info.get('modelnumber', None) != model:
|
||||
return False
|
||||
if devtype and devtype not in info.get('services', []):
|
||||
@@ -227,8 +172,6 @@ def _info_matches(info, criteria):
|
||||
return False
|
||||
if status and info.get('discostatus', None) != status:
|
||||
return False
|
||||
if uuid and info.get('uuid', None) != uuid:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
@@ -250,14 +193,6 @@ def list_matching_serials(criteria):
|
||||
if _info_matches(info, criteria):
|
||||
yield msg.ChildCollection(serial + '/')
|
||||
|
||||
def list_matching_uuids(criteria):
|
||||
for uuid in sorted(list(known_uuids)):
|
||||
for mac in known_uuids[uuid]:
|
||||
info = known_uuids[uuid][mac]
|
||||
if _info_matches(info, criteria):
|
||||
yield msg.ChildCollection(uuid + '/')
|
||||
break
|
||||
|
||||
|
||||
def list_matching_states(criteria):
|
||||
return [msg.ChildCollection(x) for x in ('discovered/', 'identified/',
|
||||
@@ -303,19 +238,18 @@ list_info = {
|
||||
'by-model': list_matching_models,
|
||||
'by-mac': list_matching_macs,
|
||||
'by-state': list_matching_states,
|
||||
'by-uuid': list_matching_uuids,
|
||||
}
|
||||
|
||||
multi_selectors = set([
|
||||
'by-type',
|
||||
'by-model',
|
||||
'by-state',
|
||||
'by-uuid',
|
||||
])
|
||||
|
||||
|
||||
node_selectors = set([
|
||||
'by-node',
|
||||
#'by-uuid',
|
||||
'by-serial',
|
||||
])
|
||||
|
||||
@@ -338,7 +272,7 @@ def _parameterize_path(pathcomponents):
|
||||
if key not in validselectors:
|
||||
raise exc.NotFoundException('{0} is not valid here'.format(key))
|
||||
if key == 'by-type':
|
||||
keyparams[key] = servicebyname.get(val, '!!!!invalid-type')
|
||||
keyparams[key] = servicebyname.get(val, None)
|
||||
else:
|
||||
keyparams[key] = val
|
||||
validselectors.discard(key)
|
||||
@@ -359,37 +293,24 @@ def handle_api_request(configmanager, inputdata, operation, pathcomponents):
|
||||
raise exc.InvalidArgumentException()
|
||||
rescan()
|
||||
return (msg.KeyValueData({'rescan': 'started'}),)
|
||||
elif operation in ('update', 'create'):
|
||||
elif (operation in ('update', 'create')):
|
||||
if 'node' not in inputdata:
|
||||
raise exc.InvalidArgumentException('Missing node name in input')
|
||||
mac = _get_mac_from_query(pathcomponents)
|
||||
_, queryparms, _, _ = _parameterize_path(pathcomponents[1:])
|
||||
if 'by-mac' not in queryparms:
|
||||
raise exc.InvalidArgumentException('Must target using "by-mac"')
|
||||
mac = queryparms['by-mac'].replace('-', ':')
|
||||
if mac not in known_info:
|
||||
raise exc.NotFoundException('{0} not found'.format(mac))
|
||||
info = known_info[mac]
|
||||
if info['handler'] is None:
|
||||
raise exc.NotImplementedException(
|
||||
'Unable to {0} to {1}'.format(operation,
|
||||
'/'.join(pathcomponents)))
|
||||
handler = info['handler'].NodeHandler(info, configmanager)
|
||||
eval_node(configmanager, handler, info, inputdata['node'],
|
||||
manual=True)
|
||||
return [msg.AssignedResource(inputdata['node'])]
|
||||
elif operation == 'delete':
|
||||
mac = _get_mac_from_query(pathcomponents)
|
||||
del known_info[mac]
|
||||
return [msg.DeletedResource(mac)]
|
||||
raise exc.NotImplementedException(
|
||||
'Unable to {0} to {1}'.format(operation, '/'.join(pathcomponents)))
|
||||
|
||||
|
||||
def _get_mac_from_query(pathcomponents):
|
||||
_, queryparms, _, _ = _parameterize_path(pathcomponents[1:])
|
||||
if 'by-mac' not in queryparms:
|
||||
raise exc.InvalidArgumentException('Must target using "by-mac"')
|
||||
mac = queryparms['by-mac'].replace('-', ':')
|
||||
if mac not in known_info:
|
||||
raise exc.NotFoundException('{0} not found'.format(mac))
|
||||
return mac
|
||||
|
||||
|
||||
def handle_read_api_request(pathcomponents):
|
||||
# TODO(jjohnson2): This should be more generalized...
|
||||
# odd indexes into components are 'by-'*, even indexes
|
||||
@@ -423,14 +344,6 @@ def detected_models():
|
||||
|
||||
|
||||
def _recheck_nodes(nodeattribs, configmanager):
|
||||
if rechecklock.locked():
|
||||
# if already in progress, don't run again
|
||||
# it may make sense to schedule a repeat, but will try the easier and less redundant way first
|
||||
return
|
||||
with rechecklock:
|
||||
return _recheck_nodes_backend(nodeattribs, configmanager)
|
||||
|
||||
def _recheck_nodes_backend(nodeattribs, configmanager):
|
||||
global rechecker
|
||||
_map_unique_ids(nodeattribs)
|
||||
# for the nodes whose attributes have changed, consider them as potential
|
||||
@@ -451,29 +364,18 @@ def _recheck_nodes_backend(nodeattribs, configmanager):
|
||||
# policy or hadn't been able to verify key
|
||||
for nodename in pending_nodes:
|
||||
info = pending_nodes[nodename]
|
||||
try:
|
||||
if info['handler'] is None:
|
||||
next
|
||||
handler = info['handler'].NodeHandler(info, configmanager)
|
||||
discopool.spawn_n(eval_node, configmanager, handler, info, nodename)
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
log.log({'error': 'Unexpected error during discovery of {0}, check debug '
|
||||
'logs'.format(nodename)})
|
||||
handler = info['handler'].NodeHandler(info, configmanager)
|
||||
eventlet.spawn_n(eval_node, configmanager, handler, info, nodename)
|
||||
|
||||
|
||||
def _recheck_single_unknown(configmanager, mac):
|
||||
info = unknown_info.get(mac, None)
|
||||
_recheck_single_unknown_info(configmanager, info)
|
||||
|
||||
|
||||
def _recheck_single_unknown_info(configmanager, info):
|
||||
global rechecker
|
||||
global rechecktime
|
||||
if not info or info['handler'] is None:
|
||||
info = unknown_info.get(mac, None)
|
||||
if not info:
|
||||
return
|
||||
if info['handler'] != pxeh and not info.get('addresses', None):
|
||||
#log.log({'info': 'Missing address information in ' + repr(info)})
|
||||
log.log({'info': 'Missing address information in ' + repr(info)})
|
||||
return
|
||||
handler = info['handler'].NodeHandler(info, configmanager)
|
||||
if handler.https_supported and not handler.https_cert:
|
||||
@@ -484,8 +386,9 @@ def _recheck_single_unknown_info(configmanager, info):
|
||||
''.format(
|
||||
handler.devname, info['hwaddr'], handler.ipaddr
|
||||
)})
|
||||
# addresses data is bad, delete the offending ip
|
||||
info['addresses'] = [x for x in info.get('addresses', []) if x != handler.ipaddr]
|
||||
# addresses data is bad, clear it, to force repair next
|
||||
# opportunity
|
||||
info['addresses'] = []
|
||||
# TODO(jjohnson2): rescan due to bad peer addr data?
|
||||
# not just wait around for the next announce
|
||||
return
|
||||
@@ -495,15 +398,15 @@ def _recheck_single_unknown_info(configmanager, info):
|
||||
'https, will examine later'.format(
|
||||
handler.devname, info['hwaddr'], handler.ipaddr
|
||||
)})
|
||||
if rechecker is not None and rechecktime > util.monotonic_time() + 300:
|
||||
if rechecker is not None and rechecktime > util.monotonic_time() + 60:
|
||||
rechecker.cancel()
|
||||
# if cancel did not result in dead, then we are in progress
|
||||
if rechecker is None or rechecker.dead:
|
||||
rechecktime = util.monotonic_time() + 300
|
||||
rechecker = eventlet.spawn_after(300, _periodic_recheck,
|
||||
rechecktime = util.monotonic_time() + 60
|
||||
rechecker = eventlet.spawn_after(60, _periodic_recheck,
|
||||
configmanager)
|
||||
return
|
||||
nodename, info['maccount'] = get_nodename(configmanager, handler, info)
|
||||
nodename = get_nodename(configmanager, handler, info)
|
||||
if nodename:
|
||||
if handler.https_supported:
|
||||
dp = configmanager.get_node_attributes([nodename],
|
||||
@@ -515,32 +418,28 @@ def _recheck_single_unknown_info(configmanager, info):
|
||||
known_nodes[nodename][info['hwaddr']] = info
|
||||
info['discostatus'] = 'discovered'
|
||||
return # already known, no need for more
|
||||
discopool.spawn_n(eval_node, configmanager, handler, info, nodename)
|
||||
eventlet.spawn_n(eval_node, configmanager, handler, info, nodename)
|
||||
|
||||
|
||||
def safe_detected(info):
|
||||
if 'hwaddr' not in info or not info['hwaddr']:
|
||||
return
|
||||
if info['hwaddr'] in runningevals:
|
||||
# Do not evaluate the same mac multiple times at once
|
||||
return
|
||||
runningevals[info['hwaddr']] = discopool.spawn(eval_detected, info)
|
||||
eventlet.spawn_n(eval_detected, info)
|
||||
|
||||
|
||||
def eval_detected(info):
|
||||
try:
|
||||
detected(info)
|
||||
return detected(info)
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
del runningevals[info['hwaddr']]
|
||||
|
||||
|
||||
def detected(info):
|
||||
global rechecker
|
||||
global rechecktime
|
||||
if 'hwaddr' not in info:
|
||||
return # For now, require hwaddr field to proceed
|
||||
# later, manual and CMM discovery may act on SN and/or UUID
|
||||
for service in info['services']:
|
||||
if service in nodehandlers:
|
||||
if nodehandlers.get(service, None):
|
||||
if service not in known_services:
|
||||
known_services[service] = set([])
|
||||
handler = nodehandlers[service]
|
||||
@@ -549,7 +448,7 @@ def detected(info):
|
||||
else: # no nodehandler, ignore for now
|
||||
return
|
||||
try:
|
||||
snum = info['attributes']['enclosure-serial-number'][0].strip()
|
||||
snum = info['attributes']['enclosure-serial-number'][0].rstrip()
|
||||
if snum:
|
||||
info['serialnumber'] = snum
|
||||
known_serials[info['serialnumber']] = info
|
||||
@@ -588,39 +487,34 @@ def detected(info):
|
||||
return
|
||||
known_info[info['hwaddr']] = info
|
||||
cfg = cfm.ConfigManager(None)
|
||||
if handler:
|
||||
handler = handler.NodeHandler(info, cfg)
|
||||
handler.scan()
|
||||
uuid = info.get('uuid', None)
|
||||
if uuid_is_valid(uuid):
|
||||
known_uuids[uuid][info['hwaddr']] = info
|
||||
if handler and handler.https_supported and not handler.https_cert:
|
||||
handler = handler.NodeHandler(info, cfg)
|
||||
if handler.https_supported and not handler.https_cert:
|
||||
if handler.cert_fail_reason == 'unreachable':
|
||||
log.log(
|
||||
{
|
||||
'info': '{0} with hwaddr {1} is not reachable by https '
|
||||
'at address {2}'.format(
|
||||
'info': '{0} with hwaddr {1} is not reachable at {2}'
|
||||
''.format(
|
||||
handler.devname, info['hwaddr'], handler.ipaddr
|
||||
)})
|
||||
info['addresses'] = [x for x in info.get('addresses', []) if x != handler.ipaddr]
|
||||
info['addresses'] = []
|
||||
return
|
||||
log.log(
|
||||
{'info': '{0} with hwaddr {1} at address {2} is not yet running '
|
||||
'https, will examine later'.format(
|
||||
handler.devname, info['hwaddr'], handler.ipaddr
|
||||
)})
|
||||
if rechecker is not None and rechecktime > util.monotonic_time() + 300:
|
||||
if rechecker is not None and rechecktime > util.monotonic_time() + 60:
|
||||
rechecker.cancel()
|
||||
if rechecker is None or rechecker.dead:
|
||||
rechecktime = util.monotonic_time() + 300
|
||||
rechecker = eventlet.spawn_after(300, _periodic_recheck, cfg)
|
||||
rechecktime = util.monotonic_time() + 60
|
||||
rechecker = eventlet.spawn_after(60, _periodic_recheck, cfg)
|
||||
unknown_info[info['hwaddr']] = info
|
||||
info['discostatus'] = 'unidentfied'
|
||||
#TODO, eventlet spawn after to recheck sooner, or somehow else
|
||||
# influence periodic recheck to shorten delay?
|
||||
return
|
||||
nodename, info['maccount'] = get_nodename(cfg, handler, info)
|
||||
if nodename and handler and handler.https_supported:
|
||||
nodename = get_nodename(cfg, handler, info)
|
||||
if nodename and handler.https_supported:
|
||||
dp = cfg.get_node_attributes([nodename],
|
||||
('pubkeys.tls_hardwaremanager',))
|
||||
lastfp = dp.get(nodename, {}).get('pubkeys.tls_hardwaremanager',
|
||||
@@ -633,9 +527,9 @@ def detected(info):
|
||||
#TODO(jjohnson2): We might have to get UUID for certain searches...
|
||||
#for now defer probe until inside eval_node. We might not have
|
||||
#a nodename without probe in the future.
|
||||
if nodename and handler:
|
||||
if nodename:
|
||||
eval_node(cfg, handler, info, nodename)
|
||||
elif handler:
|
||||
else:
|
||||
log.log(
|
||||
{'info': 'Detected unknown {0} with hwaddr {1} at '
|
||||
'address {2}'.format(
|
||||
@@ -645,182 +539,31 @@ def detected(info):
|
||||
unknown_info[info['hwaddr']] = info
|
||||
|
||||
|
||||
|
||||
def b64tohex(b64str):
|
||||
bd = base64.b64decode(b64str)
|
||||
return ''.join(['{0:02x}'.format(ord(x)) for x in bd])
|
||||
|
||||
|
||||
def get_enclosure_chain_head(nodename, cfg):
|
||||
ne = True
|
||||
members = [nodename]
|
||||
while ne:
|
||||
ne = cfg.get_node_attributes(
|
||||
nodename, 'enclosure.extends').get(nodename, {}).get(
|
||||
'enclosure.extends', {}).get('value', None)
|
||||
if not ne:
|
||||
return nodename
|
||||
if ne in members:
|
||||
raise exc.InvalidArgumentException(
|
||||
'Circular chain that includes ' + nodename)
|
||||
nodename = ne
|
||||
members.append(nodename)
|
||||
return nodename
|
||||
|
||||
|
||||
def get_chained_smm_name(nodename, cfg, handler, nl=None, checkswitch=True):
|
||||
# nodename is the head of the chain, cfg is a configmanager, handler
|
||||
# is the handler of the current candidate, nl is optional indication
|
||||
# of the next link in the chain, checkswitch can disable the switch
|
||||
# search if not indicated by current situation
|
||||
# returns the new name and whether it has been securely validated or not
|
||||
# first we check to see if directly connected
|
||||
mycert = handler.https_cert
|
||||
if checkswitch:
|
||||
fprints = macmap.get_node_fingerprints(nodename, cfg)
|
||||
for fprint in fprints:
|
||||
if util.cert_matches(fprint[0], mycert):
|
||||
# ok we have a direct match, it is this node
|
||||
return nodename, fprint[1]
|
||||
# ok, unable to get it, need to traverse the chain from the beginning
|
||||
if not nl:
|
||||
nl = list(cfg.filter_node_attributes(
|
||||
'enclosure.extends=' + nodename))
|
||||
while nl:
|
||||
if len(nl) != 1:
|
||||
raise exc.InvalidArgumentException('Multiple enclosures trying to '
|
||||
'extend a single enclosure')
|
||||
cd = cfg.get_node_attributes(nodename, ['hardwaremanagement.manager',
|
||||
'pubkeys.tls_hardwaremanager'])
|
||||
smmaddr = cd[nodename]['hardwaremanagement.manager']['value']
|
||||
pkey = cd[nodename].get('pubkeys.tls_hardwaremanager', {}).get(
|
||||
'value', None)
|
||||
if pkey:
|
||||
cv = util.TLSCertVerifier(
|
||||
cfg, nodename, 'pubkeys.tls_hardwaremanager').verify_cert
|
||||
for fprint in get_smm_neighbor_fingerprints(smmaddr, cv):
|
||||
if util.cert_matches(fprint, mycert):
|
||||
# a trusted chain member vouched for the cert
|
||||
# so it's validated
|
||||
return nl[0], True
|
||||
# advance down the chain by one and try again
|
||||
nodename = nl[0]
|
||||
nl = list(cfg.filter_node_attributes(
|
||||
'enclosure.extends=' + nodename))
|
||||
return None, False
|
||||
|
||||
|
||||
def get_smm_neighbor_fingerprints(smmaddr, cv):
|
||||
if ':' in smmaddr:
|
||||
smmaddr = '[{0}]'.format(smmaddr)
|
||||
wc = webclient.SecureHTTPConnection(smmaddr, verifycallback=cv)
|
||||
neighs = wc.grab_json_response('/scripts/neighdata.json')
|
||||
for idx in (4, 5):
|
||||
if 'sha256' not in neighs[idx]:
|
||||
continue
|
||||
yield 'sha256$' + b64tohex(neighs[idx]['sha256'])
|
||||
|
||||
|
||||
def get_nodename(cfg, handler, info):
|
||||
nodename = None
|
||||
maccount = None
|
||||
info['verified'] = False
|
||||
if not handler:
|
||||
return None, None
|
||||
if handler.https_supported:
|
||||
currcert = handler.https_cert
|
||||
if not currcert:
|
||||
info['discofailure'] = 'nohttps'
|
||||
return None, None
|
||||
currprint = util.get_fingerprint(currcert, 'sha256')
|
||||
nodename = nodes_by_fprint.get(currprint, None)
|
||||
if not nodename:
|
||||
# Try SHA512 as well
|
||||
currprint = util.get_fingerprint(currcert)
|
||||
nodename = nodes_by_fprint.get(currprint, None)
|
||||
if not handler.https_supported:
|
||||
curruuid = info['uuid']
|
||||
nodename = nodes_by_uuid.get(curruuid, None)
|
||||
if nodename is None:
|
||||
# TODO: if there are too many matches on port for a
|
||||
# given type, error! Can't just arbitarily limit,
|
||||
# shared nic with vms is possible and valid
|
||||
nodename = macmap.find_node_by_mac(info['hwaddr'], cfg)
|
||||
return nodename
|
||||
currcert = handler.https_cert
|
||||
if not currcert:
|
||||
info['discofailure'] = 'nohttps'
|
||||
return None
|
||||
currprint = util.get_fingerprint(currcert)
|
||||
nodename = nodes_by_fprint.get(currprint, None)
|
||||
if not nodename:
|
||||
curruuid = info.get('uuid', None)
|
||||
if uuid_is_valid(curruuid):
|
||||
nodename = nodes_by_uuid.get(curruuid, None)
|
||||
if nodename is None:
|
||||
_map_unique_ids()
|
||||
nodename = nodes_by_uuid.get(curruuid, None)
|
||||
if not nodename:
|
||||
# Ok, see if it is something with a chassis-uuid and discover by
|
||||
# chassis
|
||||
nodename = get_nodename_from_enclosures(cfg, info)
|
||||
if not nodename and handler.devname == 'SMM':
|
||||
nodename = get_nodename_from_chained_smms(cfg, handler, info)
|
||||
if not nodename: # as a last resort, search switches for info
|
||||
# This is the slowest potential operation, so we hope for the
|
||||
# best to occur prior to this
|
||||
nodename, macinfo = macmap.find_nodeinfo_by_mac(info['hwaddr'], cfg)
|
||||
maccount = macinfo['maccount']
|
||||
if nodename:
|
||||
if handler.devname == 'SMM':
|
||||
nl = list(cfg.filter_node_attributes(
|
||||
'enclosure.extends=' + nodename))
|
||||
if nl:
|
||||
# We found an SMM, and it's in a chain per configuration
|
||||
# we need to ask the switch for the fingerprint to see
|
||||
# if we have a match or not
|
||||
newnodename, v = get_chained_smm_name(nodename, cfg,
|
||||
handler, nl)
|
||||
if newnodename:
|
||||
# while this started by switch, it was disambiguated
|
||||
info['verified'] = v
|
||||
return newnodename, None
|
||||
if (nodename and
|
||||
not handler.discoverable_by_switch(macinfo['maccount'])):
|
||||
if handler.devname == 'SMM':
|
||||
errorstr = 'Attempt to discover SMM by switch, but chained ' \
|
||||
'topology or incorrect net attributes detected, ' \
|
||||
'which is not compatible with switch discovery ' \
|
||||
'of SMM, nodename would have been ' \
|
||||
'{0}'.format(nodename)
|
||||
log.log({'error': errorstr})
|
||||
return None, None
|
||||
return nodename, maccount
|
||||
|
||||
|
||||
def get_nodename_from_chained_smms(cfg, handler, info):
|
||||
nodename = None
|
||||
for fprint in get_smm_neighbor_fingerprints(
|
||||
handler.ipaddr, lambda x: True):
|
||||
if fprint in nodes_by_fprint:
|
||||
# need to chase the whole chain
|
||||
# to support either direction
|
||||
chead = get_enclosure_chain_head(nodes_by_fprint[fprint],
|
||||
cfg)
|
||||
newnodename, v = get_chained_smm_name(
|
||||
chead, cfg, handler, checkswitch=False)
|
||||
if newnodename:
|
||||
info['verified'] = v
|
||||
nodename = newnodename
|
||||
return nodename
|
||||
|
||||
|
||||
def get_nodename_from_enclosures(cfg, info):
|
||||
nodename = None
|
||||
cuuid = info.get('attributes', {}).get('chassis-uuid', [None])[0]
|
||||
if cuuid and cuuid in nodes_by_uuid:
|
||||
encl = nodes_by_uuid[cuuid]
|
||||
bay = info.get('enclosure.bay', None)
|
||||
if bay:
|
||||
tnl = cfg.filter_node_attributes('enclosure.manager=' + encl)
|
||||
tnl = list(
|
||||
cfg.filter_node_attributes('enclosure.bay={0}'.format(bay),
|
||||
tnl))
|
||||
if len(tnl) == 1:
|
||||
# This is not a secure assurance, because it's by
|
||||
# uuid instead of a key
|
||||
nodename = tnl[0]
|
||||
nodename = macmap.find_node_by_mac(info['hwaddr'], cfg)
|
||||
return nodename
|
||||
|
||||
|
||||
def eval_node(cfg, handler, info, nodename, manual=False):
|
||||
try:
|
||||
handler.probe() # unicast interrogation as possible to get more data
|
||||
# for now, we search switch only, ideally we search cmm, smm, and
|
||||
# switch concurrently
|
||||
# do some preconfig, for example, to bring a SMM online if applicable
|
||||
handler.preconfig()
|
||||
@@ -851,7 +594,6 @@ def eval_node(cfg, handler, info, nodename, manual=False):
|
||||
if not handler.is_enclosure and nl:
|
||||
# The specified node is an enclosure (has nodes mapped to it), but
|
||||
# what we are talking to is *not* an enclosure
|
||||
# might be ambiguous, need to match chassis-uuid as well..
|
||||
if 'enclosure.bay' not in info:
|
||||
unknown_info[info['hwaddr']] = info
|
||||
info['discostatus'] = 'unidentified'
|
||||
@@ -863,38 +605,11 @@ def eval_node(cfg, handler, info, nodename, manual=False):
|
||||
raise exc.InvalidArgumentException(errorstr)
|
||||
log.log({'error': errorstr})
|
||||
return
|
||||
enl = list(cfg.filter_node_attributes('enclosure.extends=' + nodename))
|
||||
if enl:
|
||||
# ambiguous SMM situation according to the configuration, we need
|
||||
# to match uuid
|
||||
encuuid = info['attributes'].get('chasis-uuid', None)
|
||||
if encuuid:
|
||||
enl = list(cfg.filter_node_attributes('id.uuid=' + encuuid))
|
||||
if len(enl) != 1:
|
||||
# errorstr = 'No SMM by given UUID known, *yet*'
|
||||
# if manual:
|
||||
# raise exc.InvalidArgumentException(errorstr)
|
||||
# log.log({'error': errorstr})
|
||||
if encuuid in pending_by_uuid:
|
||||
pending_by_uuid[encuuid].add(info)
|
||||
else:
|
||||
pending_by_uuid[encuuid] = set([info])
|
||||
return
|
||||
# We found the real smm, replace the list with the actual smm
|
||||
# to continue
|
||||
nl = list(cfg.filter_node_attributes(
|
||||
'enclosure.manager=' + enl[0]))
|
||||
else:
|
||||
errorstr = 'Chained SMM configuration with older XCC, ' \
|
||||
'unable to perform zero power discovery'
|
||||
if manual:
|
||||
raise exc.InvalidArgumentException(errorstr)
|
||||
log.log({'error': errorstr})
|
||||
return
|
||||
# search for nodes fitting our description using filters
|
||||
# lead with the most specific to have a small second pass
|
||||
nl = list(cfg.filter_node_attributes(
|
||||
'enclosure.bay={0}'.format(info['enclosure.bay']), nl))
|
||||
nl = cfg.filter_node_attributes(
|
||||
'enclosure.bay=' + info['enclosure.bay'], nl)
|
||||
nl = list(nl)
|
||||
if len(nl) != 1:
|
||||
info['discofailure'] = 'ambigconfig'
|
||||
if len(nl):
|
||||
@@ -921,29 +636,6 @@ def eval_node(cfg, handler, info, nodename, manual=False):
|
||||
pending_nodes[nodename] = info
|
||||
else:
|
||||
# we can and did accurately discover by switch or in enclosure
|
||||
# but... is this really ok? could be on an upstream port or
|
||||
# erroneously put in the enclosure with no nodes yet
|
||||
# so first, see if the candidate node is a chain host
|
||||
if info['maccount']:
|
||||
# discovery happened through switch
|
||||
nl = list(cfg.filter_node_attributes(
|
||||
'enclosure.extends=' + nodename))
|
||||
if nl:
|
||||
# The candidate nodename is the head of a chain, we must
|
||||
# validate the smm certificate by the switch
|
||||
macmap.get_node_fingerprint(nodename, cfg)
|
||||
util.handler.cert_matches(fprint, handler.https_cert)
|
||||
return
|
||||
if (info['maccount'] and
|
||||
not handler.discoverable_by_switch(info['maccount'])):
|
||||
errorstr = 'The detected node {0} was detected using switch, ' \
|
||||
'however the relevant port has too many macs learned ' \
|
||||
'for this type of device ({1}) to be discovered by ' \
|
||||
'switch.'.format(nodename, handler.devname)
|
||||
if manual:
|
||||
raise exc.InvalidArgumentException(errorstr)
|
||||
log.log({'error': errorstr})
|
||||
return
|
||||
if not discover_node(cfg, handler, info, nodename, manual):
|
||||
pending_nodes[nodename] = info
|
||||
|
||||
@@ -958,20 +650,14 @@ def discover_node(cfg, handler, info, nodename, manual):
|
||||
'pubkeys.tls_hardwaremanager'))
|
||||
policy = dp.get(nodename, {}).get('discovery.policy', {}).get(
|
||||
'value', None)
|
||||
if policy is None:
|
||||
policy = ''
|
||||
policies = set(policy.split(','))
|
||||
lastfp = dp.get(nodename, {}).get('pubkeys.tls_hardwaremanager',
|
||||
{}).get('value', None)
|
||||
# TODO(jjohnson2): permissive requires we guarantee storage of
|
||||
# the pubkeys, which is deferred for a little bit
|
||||
# Also, 'secure', when we have the needed infrastructure done
|
||||
# in some product or another.
|
||||
curruuid = info.get('uuid', False)
|
||||
if 'pxe' in policies and info['handler'] == pxeh:
|
||||
return do_pxe_discovery(cfg, handler, info, manual, nodename, policies)
|
||||
elif ('permissive' in policies and handler.https_supported and lastfp and
|
||||
not util.cert_matches(lastfp, handler.https_cert) and not manual):
|
||||
if (policy == 'permissive' and handler.https_supported and lastfp and
|
||||
not manual):
|
||||
info['discofailure'] = 'fingerprint'
|
||||
log.log({'info': 'Detected replacement of {0} with existing '
|
||||
'fingerprint and permissive discovery policy, not '
|
||||
@@ -979,18 +665,33 @@ def discover_node(cfg, handler, info, nodename, manual):
|
||||
'pubkeys.tls_hardwaremanager attribute is cleared '
|
||||
'first'.format(nodename)})
|
||||
return False # With a permissive policy, do not discover new
|
||||
elif policies & set(('open', 'permissive')) or manual:
|
||||
elif policy in ('open', 'permissive') or manual:
|
||||
info['nodename'] = nodename
|
||||
if info['handler'] == pxeh:
|
||||
return do_pxe_discovery(cfg, handler, info, manual, nodename, policies)
|
||||
if not handler.https_supported:
|
||||
# use uuid based scheme in lieu of tls cert, ideally only
|
||||
# for stateless 'discovery' targets like pxe, where data does not
|
||||
# change
|
||||
if info['uuid'] in known_pxe_uuids:
|
||||
return True
|
||||
uuidinfo = cfg.get_node_attributes(nodename, 'id.uuid')
|
||||
known_pxe_uuids[info['uuid']] = nodename
|
||||
# TODO(jjohnson2): This is messing with the attrib database
|
||||
# so it should only be possible if policy is 'open'
|
||||
#
|
||||
if manual or policy == 'open':
|
||||
olduuid = uuidinfo.get(nodename, {}).get('id.uuid', None)
|
||||
if 'uuid' in info and info['uuid'] != olduuid:
|
||||
cfg.set_node_attributes(
|
||||
{nodename: {'id.uuid': info['uuid']}})
|
||||
log.log({'info': 'Detected {0} ({1} with mac {2})'.format(
|
||||
nodename, handler.devname, info['hwaddr'])})
|
||||
return True
|
||||
elif manual or not util.cert_matches(lastfp, handler.https_cert):
|
||||
# only 'discover' if it is not the same as last time
|
||||
try:
|
||||
handler.config(nodename)
|
||||
except Exception as e:
|
||||
info['discofailure'] = 'bug'
|
||||
if manual:
|
||||
raise
|
||||
log.log(
|
||||
{'error':
|
||||
'Error encountered trying to set up {0}, {1}'.format(
|
||||
@@ -1000,20 +701,14 @@ def discover_node(cfg, handler, info, nodename, manual):
|
||||
newnodeattribs = {}
|
||||
if 'uuid' in info:
|
||||
newnodeattribs['id.uuid'] = info['uuid']
|
||||
if 'serialnumber' in info:
|
||||
newnodeattribs['id.serial'] = info['serialnumber']
|
||||
if 'modelnumber' in info:
|
||||
newnodeattribs['id.model'] = info['modelnumber']
|
||||
if handler.https_cert:
|
||||
newnodeattribs['pubkeys.tls_hardwaremanager'] = \
|
||||
util.get_fingerprint(handler.https_cert, 'sha256')
|
||||
util.get_fingerprint(handler.https_cert)
|
||||
if newnodeattribs:
|
||||
cfg.set_node_attributes({nodename: newnodeattribs})
|
||||
log.log({'info': 'Discovered {0} ({1})'.format(nodename,
|
||||
handler.devname)})
|
||||
info['discostatus'] = 'discovered'
|
||||
for i in pending_by_uuid.get(curruuid, []):
|
||||
eventlet.spawn_n(_recheck_single_unknown_info, cfg, i)
|
||||
return True
|
||||
log.log({'info': 'Detected {0}, but discovery.policy is not set to a '
|
||||
'value allowing discovery (open or permissive)'.format(
|
||||
@@ -1022,39 +717,6 @@ def discover_node(cfg, handler, info, nodename, manual):
|
||||
return False
|
||||
|
||||
|
||||
def do_pxe_discovery(cfg, handler, info, manual, nodename, policies):
|
||||
# use uuid based scheme in lieu of tls cert, ideally only
|
||||
# for stateless 'discovery' targets like pxe, where data does not
|
||||
# change
|
||||
uuidinfo = cfg.get_node_attributes(nodename, ['id.uuid', 'id.serial', 'id.model', 'net*.bootable'])
|
||||
if manual or policies & set(('open', 'pxe')):
|
||||
enrich_pxe_info(info)
|
||||
attribs = {}
|
||||
olduuid = uuidinfo.get(nodename, {}).get('id.uuid', None)
|
||||
uuid = info.get('uuid', None)
|
||||
if uuid and uuid != olduuid:
|
||||
attribs['id.uuid'] = info['uuid']
|
||||
sn = info.get('serialnumber', None)
|
||||
mn = info.get('modelnumber', None)
|
||||
if sn and sn != uuidinfo.get(nodename, {}).get('id.serial', None):
|
||||
attribs['id.serial'] = sn
|
||||
if mn and mn != uuidinfo.get(nodename, {}).get('id.model', None):
|
||||
attribs['id.model'] = mn
|
||||
for attrname in uuidinfo.get(nodename, {}):
|
||||
if attrname.endswith('.bootable') and uuidinfo[nodename][attrname].get('value', None):
|
||||
newattrname = attrname[:-8] + 'hwaddr'
|
||||
attribs[newattrname] = info['hwaddr']
|
||||
if attribs:
|
||||
cfg.set_node_attributes({nodename: attribs})
|
||||
if info['uuid'] in known_pxe_uuids:
|
||||
return True
|
||||
if uuid_is_valid(info['uuid']):
|
||||
known_pxe_uuids[info['uuid']] = nodename
|
||||
log.log({'info': 'Detected {0} ({1} with mac {2})'.format(
|
||||
nodename, handler.devname, info['hwaddr'])})
|
||||
return True
|
||||
|
||||
|
||||
attribwatcher = None
|
||||
nodeaddhandler = None
|
||||
needaddhandled = False
|
||||
@@ -1080,7 +742,7 @@ def newnodes(added, deleting, configmanager):
|
||||
attribwatcher = configmanager.watch_attributes(
|
||||
allnodes, ('discovery.policy', 'net*.switch',
|
||||
'hardwaremanagement.manager', 'net*.switchport', 'id.uuid',
|
||||
'pubkeys.tls_hardwaremanager', 'net*.bootable'), _recheck_nodes)
|
||||
'pubkeys.tls_hardwaremanager'), _recheck_nodes)
|
||||
if nodeaddhandler:
|
||||
needaddhandled = True
|
||||
else:
|
||||
@@ -1096,18 +758,21 @@ def _periodic_recheck(configmanager):
|
||||
global rechecker
|
||||
global rechecktime
|
||||
rechecker = None
|
||||
try:
|
||||
_recheck_nodes((), configmanager)
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
log.log({'error': 'Unexpected error during discovery, check debug '
|
||||
'logs'})
|
||||
# if rechecker is set, it means that an accelerated schedule
|
||||
# for rechecker was requested in the course of recheck_nodes
|
||||
if rechecker is None:
|
||||
rechecktime = util.monotonic_time() + 900
|
||||
rechecker = eventlet.spawn_after(900, _periodic_recheck,
|
||||
configmanager)
|
||||
# There shouldn't be anything causing this to double up, but just in case
|
||||
# use a semaphore to absolutely guarantee this doesn't multiply
|
||||
with rechecklock:
|
||||
try:
|
||||
_recheck_nodes((), configmanager)
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
log.log({'error': 'Unexpected error during discovery, check debug '
|
||||
'logs'})
|
||||
# if rechecker is set, it means that an accelerated schedule
|
||||
# for rechecker was requested in the course of recheck_nodes
|
||||
if rechecker is None:
|
||||
rechecktime = util.monotonic_time() + 900
|
||||
rechecker = eventlet.spawn_after(900, _periodic_recheck,
|
||||
configmanager)
|
||||
|
||||
|
||||
def rescan():
|
||||
@@ -1155,33 +820,31 @@ def _map_unique_ids(nodes=None):
|
||||
uuid_by_nodes = {}
|
||||
fprint_by_nodes = {}
|
||||
for uuid in nodes_by_uuid:
|
||||
if not uuid_is_valid():
|
||||
continue
|
||||
node = nodes_by_uuid[uuid]
|
||||
if node in bigmap:
|
||||
uuid_by_nodes[node] = uuid
|
||||
for fprint in nodes_by_fprint:
|
||||
node = nodes_by_fprint[fprint]
|
||||
if node in bigmap:
|
||||
fprint_by_nodes[node] = fprint
|
||||
fprint_by_nodes[node] =fprint
|
||||
for node in bigmap:
|
||||
if node in uuid_by_nodes:
|
||||
del nodes_by_uuid[uuid_by_nodes[node]]
|
||||
if node in fprint_by_nodes:
|
||||
del nodes_by_fprint[fprint_by_nodes[node]]
|
||||
uuid = bigmap[node].get('id.uuid', {}).get('value', None)
|
||||
if uuid_is_valid(uuid):
|
||||
if uuid:
|
||||
nodes_by_uuid[uuid] = node
|
||||
fprint = bigmap[node].get(
|
||||
'pubkeys.tls_hardwaremanager', {}).get('value', None)
|
||||
if fprint:
|
||||
nodes_by_fprint[fprint] = node
|
||||
for uuid in known_pxe_uuids:
|
||||
if uuid_is_valid(uuid) and uuid not in nodes_by_uuid:
|
||||
if uuid not in nodes_by_uuid:
|
||||
nodes_by_uuid[uuid] = known_pxe_uuids[uuid]
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
start_detection()
|
||||
while True:
|
||||
eventlet.sleep(30)
|
||||
eventlet.sleep(30)
|
||||
@@ -43,9 +43,6 @@ class NodeHandler(generic.NodeHandler):
|
||||
# TODO(jjohnson2): probe serial number and uuid
|
||||
|
||||
def config(self, nodename, reset=False):
|
||||
self._bmcconfig(nodename, reset)
|
||||
|
||||
def _bmcconfig(self, nodename, reset=False):
|
||||
# TODO(jjohnson2): set ip parameters, user/pass, alert cfg maybe
|
||||
# In general, try to use https automation, to make it consistent
|
||||
# between hypothetical secure path and today.
|
||||
@@ -91,8 +88,7 @@ class NodeHandler(generic.NodeHandler):
|
||||
if ('secret.hardwaremanagementuser' not in cd or
|
||||
'secret.hardwaremanagementpassword' not in cd):
|
||||
raise exc.TargetEndpointBadCredentials(
|
||||
'secret.hardwaremanagementuser and/or '
|
||||
'secret.hardwaremanagementpassword was not configured')
|
||||
'Missing user and/or password')
|
||||
if ('hardwaremanagement.manager' in cd and
|
||||
cd['hardwaremanagement.manager']['value'] and
|
||||
not cd['hardwaremanagement.manager']['value'].startswith(
|
||||
@@ -154,4 +150,4 @@ class NodeHandler(generic.NodeHandler):
|
||||
raise
|
||||
if reset:
|
||||
ic.reset_bmc()
|
||||
return ic
|
||||
return
|
||||
|
||||
@@ -19,31 +19,21 @@ webclient = eventlet.import_patched('pyghmi.util.webclient')
|
||||
class NodeHandler(object):
|
||||
https_supported = True
|
||||
is_enclosure = False
|
||||
devname = ''
|
||||
maxmacs = 2 # reasonable default, allowing for common scenario of
|
||||
# shared nic in theory, but blocking enclosure managers
|
||||
# and uplink ports
|
||||
|
||||
def __init__(self, info, configmanager):
|
||||
self._certfailreason = None
|
||||
self._fp = None
|
||||
self.info = info
|
||||
self.configmanager = configmanager
|
||||
targsa = [None]
|
||||
targsa = None
|
||||
# first let us prefer LLA if possible, since that's most stable
|
||||
for sa in info['addresses']:
|
||||
if sa[0].startswith('fe80'):
|
||||
targsa = sa
|
||||
break
|
||||
else:
|
||||
if info.get('addresses', False):
|
||||
targsa = info['addresses'][0]
|
||||
targsa = info['addresses'][0]
|
||||
self.ipaddr = targsa[0]
|
||||
|
||||
def scan(self):
|
||||
# Do completely passive things to enhance data.
|
||||
# Probe is permitted to for example attempt a login
|
||||
# scan *only* does what it can without a login attempt
|
||||
return
|
||||
|
||||
def probe(self):
|
||||
@@ -54,10 +44,9 @@ class NodeHandler(object):
|
||||
def preconfig(self):
|
||||
return
|
||||
|
||||
def discoverable_by_switch(self, macs):
|
||||
# Given the number of macs sharing the port, is this handler
|
||||
# appropriate?
|
||||
return macs <= self.maxmacs
|
||||
@property
|
||||
def discoverable_by_switch(self):
|
||||
return True
|
||||
|
||||
def _savecert(self, certificate):
|
||||
self._fp = certificate
|
||||
|
||||
@@ -15,71 +15,32 @@
|
||||
import confluent.discovery.handlers.bmc as bmchandler
|
||||
import pyghmi.exceptions as pygexc
|
||||
import pyghmi.ipmi.private.util as pygutil
|
||||
import string
|
||||
import struct
|
||||
|
||||
|
||||
class NodeHandler(bmchandler.NodeHandler):
|
||||
devname = 'IMM'
|
||||
|
||||
def scan(self):
|
||||
slpattrs = self.info.get('attributes', {})
|
||||
self.isdense = False
|
||||
try:
|
||||
ff = slpattrs.get('enclosure-form-factor', [''])[0]
|
||||
except IndexError:
|
||||
return
|
||||
wronguuid = slpattrs.get('node-uuid', [''])[0]
|
||||
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 = uuidprefix[:8] + '-' + uuidprefix[8:12] + '-' + \
|
||||
uuidprefix[12:16]
|
||||
self.info['uuid'] = uuidprefix + '-' + '-'.join(
|
||||
wronguuid.split('-')[3:])
|
||||
self.info['uuid'] = string.lower(self.info['uuid'])
|
||||
if ff not in ('dense-computing', 'BC2'):
|
||||
# do not probe unless it's a dense platform
|
||||
return
|
||||
self.isdense = True
|
||||
slot = int(slpattrs.get('slot', ['0'])[0])
|
||||
if slot != 0:
|
||||
self.info['enclosure.bay'] = slot
|
||||
|
||||
def probe(self):
|
||||
if self.info.get('enclosure.bay', 0) == 0:
|
||||
self.scan()
|
||||
if self.info.get('enclosure.bay', 0) != 0:
|
||||
# scan has already populated info
|
||||
return
|
||||
ff = self.info.get('attributes', {}).get('enclosure-form-factor', '')
|
||||
if ff != 'dense-computing':
|
||||
return
|
||||
try:
|
||||
# we are a dense platform, but the SLP data did not give us slot
|
||||
# attempt to probe using IPMI
|
||||
ipmicmd = self._get_ipmicmd()
|
||||
guiddata = ipmicmd.xraw_command(netfn=6, command=8)
|
||||
self.info['uuid'] = pygutil.decode_wireformat_uuid(
|
||||
guiddata['data']).lower()
|
||||
guiddata['data'])
|
||||
ipmicmd.oem_init()
|
||||
bayid = ipmicmd._oem.immhandler.get_property(
|
||||
'/v2/cmm/sp/7')
|
||||
if not bayid:
|
||||
return
|
||||
self.info['enclosure.bay'] = int(bayid)
|
||||
smmid = ipmicmd._oem.immhandler.get_property(
|
||||
'/v2/ibmc/smm/chassis/uuid')
|
||||
if not smmid:
|
||||
return
|
||||
smmid = smmid.lower().replace(' ', '')
|
||||
smmid = '{0}-{1}-{2}-{3}-{4}'.format(smmid[:8], smmid[8:12],
|
||||
smmid[12:16], smmid[16:20],
|
||||
smmid[20:])
|
||||
self.info['enclosure.uuid'] = smmid
|
||||
self.info['enclosure.type'] = 'smm'
|
||||
#
|
||||
self.info['enclosure.bay'] = bayid
|
||||
# enclosure.bay only happens for Flex, nextscale doesn't do it
|
||||
# this way
|
||||
except pygexc.IpmiException as ie:
|
||||
print(repr(ie))
|
||||
raise
|
||||
|
||||
|
||||
# TODO(jjohnson2): web based init config for future prevalidated cert scheme
|
||||
# def config(self, nodename):
|
||||
# return
|
||||
|
||||
|
||||
@@ -13,28 +13,10 @@
|
||||
# limitations under the License.
|
||||
|
||||
import confluent.discovery.handlers.bmc as bmchandler
|
||||
import struct
|
||||
|
||||
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(
|
||||
'hex')
|
||||
uuid = (a[:8], a[8:12], a[12:16], baduuid[16:20], baduuid[20:])
|
||||
return '-'.join(uuid).lower()
|
||||
|
||||
class NodeHandler(bmchandler.NodeHandler):
|
||||
is_enclosure = True
|
||||
devname = 'SMM'
|
||||
maxmacs = 5 # support an enclosure, but try to avoid catching daisy chain
|
||||
|
||||
def scan(self):
|
||||
# the UUID is in a weird order, fix it up to match
|
||||
# ipmi return and property value
|
||||
uuid = self.info.get('attributes', {}).get('uuid', None)
|
||||
if uuid:
|
||||
uuid = fixuuid(uuid[0])
|
||||
self.info['uuid'] = uuid
|
||||
|
||||
def config(self, nodename):
|
||||
# SMM for now has to reset to assure configuration applies
|
||||
|
||||
@@ -12,19 +12,41 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import confluent.discovery.handlers.imm as immhandler
|
||||
import confluent.discovery.handlers.bmc as bmchandler
|
||||
import pyghmi.exceptions as pygexc
|
||||
import pyghmi.ipmi.oem.lenovo.imm as imm
|
||||
import pyghmi.ipmi.private.util as pygutil
|
||||
|
||||
|
||||
|
||||
class NodeHandler(immhandler.NodeHandler):
|
||||
class NodeHandler(bmchandler.NodeHandler):
|
||||
devname = 'XCC'
|
||||
|
||||
def probe(self):
|
||||
try:
|
||||
ipmicmd = self._get_ipmicmd()
|
||||
guiddata = ipmicmd.xraw_command(netfn=6, command=8)
|
||||
self.info['uuid'] = pygutil.decode_wireformat_uuid(
|
||||
guiddata['data'])
|
||||
ipmicmd.oem_init()
|
||||
bayid = ipmicmd._oem.immhandler.get_property(
|
||||
'/v2/cmm/sp/7')
|
||||
if not bayid:
|
||||
return
|
||||
self.info['enclosure.bay'] = bayid
|
||||
smmid = ipmicmd._oem.immhandler.get_property(
|
||||
'/v2/ibmc/smm/chassis/uuid')
|
||||
if not smmid:
|
||||
return
|
||||
smmid = smmid.lower().replace(' ', '')
|
||||
smmid = '{0}-{1}-{2}-{3}-{4}'.format(smmid[:8], smmid[8:12],
|
||||
smmid[12:16], smmid[16:20],
|
||||
smmid[20:])
|
||||
self.info['enclosure.uuid'] = smmid
|
||||
self.info['enclosure.type'] = 'smm'
|
||||
except pygexc.IpmiException as ie:
|
||||
print(repr(ie))
|
||||
raise
|
||||
|
||||
def preconfig(self):
|
||||
ff = self.info.get('attributes', {}).get('enclosure-form-factor', '')
|
||||
if ff not in ('dense-computing', [u'dense-computing']):
|
||||
return
|
||||
# attempt to enable SMM
|
||||
#it's normal to get a 'not supported' (193) for systems without an SMM
|
||||
ipmicmd = None
|
||||
@@ -32,8 +54,7 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
ipmicmd = self._get_ipmicmd()
|
||||
ipmicmd.xraw_command(netfn=0x3a, command=0xf1, data=(1,))
|
||||
except pygexc.IpmiException as e:
|
||||
if (e.ipmicode != 193 and 'Unauthorized name' not in str(e) and
|
||||
'Incorrect password' not in str(e)):
|
||||
if e.ipmicode != 193:
|
||||
# raise an issue if anything other than to be expected
|
||||
raise
|
||||
#TODO: decide how to clean out if important
|
||||
@@ -41,30 +62,6 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
#if ipmicmd:
|
||||
# ipmicmd.ipmi_session.logout()
|
||||
|
||||
def config(self, nodename, reset=False):
|
||||
# TODO(jjohnson2): set ip parameters, user/pass, alert cfg maybe
|
||||
# In general, try to use https automation, to make it consistent
|
||||
# between hypothetical secure path and today.
|
||||
ic = self._bmcconfig(nodename)
|
||||
ff = self.info.get('attributes', {}).get('enclosure-form-factor', '')
|
||||
if ff not in ('dense-computing', [u'dense-computing']):
|
||||
return
|
||||
# Ok, we can get the enclosure uuid now..
|
||||
ic.oem_init()
|
||||
enclosureuuid = ic._oem.immhandler.get_property(
|
||||
'/v2/ibmc/smm/chassis/uuid')
|
||||
enclosureuuid = ic._oem.immhandler.get_property(
|
||||
'/v2/ibmc/smm/chassis/uuid')
|
||||
if enclosureuuid:
|
||||
enclosureuuid = imm.fixup_uuid(enclosureuuid).lower()
|
||||
em = self.configmanager.get_node_attributes(nodename,
|
||||
'enclosure.manager')
|
||||
em = em.get(nodename, {}).get('enclosure.manager', {}).get(
|
||||
'value', None)
|
||||
# ok, set the uuid of the manager...
|
||||
if em:
|
||||
self.configmanager.set_node_attributes(
|
||||
{em: {'id.uuid': enclosureuuid}})
|
||||
|
||||
# TODO(jjohnson2): web based init config for future prevalidated cert scheme
|
||||
# def config(self, nodename):
|
||||
|
||||
@@ -37,7 +37,7 @@ def decode_uuid(rawguid):
|
||||
lebytes = struct.unpack_from('<IHH', buffer(rawguid[:8]))
|
||||
bebytes = struct.unpack_from('>HHI', buffer(rawguid[8:]))
|
||||
return '{0:08X}-{1:04X}-{2:04X}-{3:04X}-{4:04X}{5:08X}'.format(
|
||||
lebytes[0], lebytes[1], lebytes[2], bebytes[0], bebytes[1], bebytes[2]).lower()
|
||||
lebytes[0], lebytes[1], lebytes[2], bebytes[0], bebytes[1], bebytes[2])
|
||||
|
||||
|
||||
def find_info_in_options(rq, optidx):
|
||||
@@ -92,7 +92,7 @@ def snoop(handler):
|
||||
rq = bytearray(rq)
|
||||
if rq[0] == 1: # Boot request
|
||||
addrlen = rq[2]
|
||||
if addrlen > 16 or addrlen == 0:
|
||||
if addrlen > 16: # max address size in bootp is 16 bytes
|
||||
continue
|
||||
netaddr = rq[28:28+addrlen]
|
||||
netaddr = ':'.join(['{0:02x}'.format(x) for x in netaddr])
|
||||
|
||||
@@ -28,9 +28,6 @@ _slp_services = set([
|
||||
'service:management-hardware.IBM:integrated-management-module2',
|
||||
'service:lenovo-smm',
|
||||
'service:management-hardware.Lenovo:lenovo-xclarity-controller',
|
||||
'service:management-hardware.IBM:chassis-management-module',
|
||||
'service:management-hardware.Lenovo:chassis-management-module',
|
||||
'service:io-device.Lenovo:management-module',
|
||||
])
|
||||
|
||||
# SLP has a lot of ambition that was unfulfilled in practice.
|
||||
@@ -41,12 +38,6 @@ srvreqfooter = b'\x00\x07DEFAULT\x00\x00\x00\x00'
|
||||
# which is defined in RFC 3059, used to indicate support for that capability
|
||||
attrlistext = b'\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
|
||||
try:
|
||||
IPPROTO_IPV6 = socket.IPPROTO_IPV6
|
||||
except AttributeError:
|
||||
IPPROTO_IPV6 = 41 # Assume Windows value if socket is missing it
|
||||
|
||||
|
||||
|
||||
def _parse_slp_header(packet):
|
||||
packet = bytearray(packet)
|
||||
@@ -206,7 +197,7 @@ def _find_srvtype(net, net4, srvtype, addresses, xid):
|
||||
v6addrs.append(('ff02::1:' + v6hash, 427, 0, 0))
|
||||
for idx in util.list_interface_indexes():
|
||||
# IPv6 multicast is by index, so lead with that
|
||||
net.setsockopt(IPPROTO_IPV6, socket.IPV6_MULTICAST_IF, idx)
|
||||
net.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF, idx)
|
||||
for sa in v6addrs:
|
||||
try:
|
||||
net.sendto(data, sa)
|
||||
@@ -222,13 +213,7 @@ def _find_srvtype(net, net4, srvtype, addresses, xid):
|
||||
bcast = i4['broadcast']
|
||||
net4.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF,
|
||||
socket.inet_aton(addr))
|
||||
try:
|
||||
net4.sendto(data, ('239.255.255.253', 427))
|
||||
except socket.error as se:
|
||||
# On occasion, multicasting may be disabled
|
||||
# tolerate this scenario and move on
|
||||
if se.errno != 101:
|
||||
raise
|
||||
net4.sendto(data, ('239.255.255.253', 427))
|
||||
net4.sendto(data, (bcast, 427))
|
||||
|
||||
|
||||
@@ -279,7 +264,7 @@ def _parse_attrlist(attrstr):
|
||||
'{4:04X}{5:08X}'.format(
|
||||
lebytes[0], lebytes[1], lebytes[2], bebytes[0],
|
||||
bebytes[1], bebytes[2]
|
||||
).lower()
|
||||
)
|
||||
attribs[attrname].append(val)
|
||||
attrstr = attrstr[attrstr.index(')'):]
|
||||
elif attrstr[0] == ',':
|
||||
@@ -393,14 +378,14 @@ def snoop(handler):
|
||||
"""
|
||||
active_scan(handler)
|
||||
net = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
net.setsockopt(IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
|
||||
net.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
|
||||
slpg = socket.inet_pton(socket.AF_INET6, 'ff01::123')
|
||||
slpg2 = socket.inet_pton(socket.AF_INET6, 'ff02::123')
|
||||
for i6idx in util.list_interface_indexes():
|
||||
mreq = slpg + struct.pack('=I', i6idx)
|
||||
net.setsockopt(IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
|
||||
net.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
|
||||
mreq = slpg2 + struct.pack('=I', i6idx)
|
||||
net.setsockopt(IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
|
||||
net.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
|
||||
net4 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
net.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
net4.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
@@ -450,15 +435,8 @@ def snoop(handler):
|
||||
# ignore for now
|
||||
known_peers.discard(peer)
|
||||
continue
|
||||
# we want to prioritize the very well known services
|
||||
svcs = []
|
||||
for svc in q:
|
||||
if svc in _slp_services:
|
||||
svcs.insert(0, svc)
|
||||
else:
|
||||
svcs.append(svc)
|
||||
peerbymacaddress[mac] = {
|
||||
'services': svcs,
|
||||
'services': q,
|
||||
'addresses': [peer],
|
||||
}
|
||||
newmacs.add(mac)
|
||||
@@ -484,7 +462,7 @@ def active_scan(handler):
|
||||
handler(scanned)
|
||||
|
||||
|
||||
def scan(srvtypes=_slp_services, addresses=None, localonly=False):
|
||||
def scan(srvtypes=_slp_services, addresses=None):
|
||||
"""Find targets providing matching requested srvtypes
|
||||
|
||||
This is a generator that will iterate over respondants to the SrvType
|
||||
@@ -499,15 +477,13 @@ def scan(srvtypes=_slp_services, addresses=None, localonly=False):
|
||||
"""
|
||||
net = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
net4 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
# increase RCVBUF to max, mitigate chance of
|
||||
# TODO: increase RCVBUF to max, mitigate chance of
|
||||
# failure due to full buffer.
|
||||
net.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 16777216)
|
||||
net4.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 16777216)
|
||||
# SLP is very poor at scanning large counts and managing it, so we
|
||||
# must make the best of it
|
||||
# Some platforms/config default to IPV6ONLY, we are doing IPv4
|
||||
# too, so force it
|
||||
#net.setsockopt(IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
|
||||
net.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
|
||||
# we are going to do broadcast, so allow that...
|
||||
initxid = random.randint(0, 32768)
|
||||
xididx = 0
|
||||
@@ -526,12 +502,6 @@ def scan(srvtypes=_slp_services, addresses=None, localonly=False):
|
||||
_grab_rsps((net, net4), rsps, 1, xidmap)
|
||||
# now to analyze and flesh out the responses
|
||||
for id in rsps:
|
||||
if localonly:
|
||||
for addr in rsps[id]['addresses']:
|
||||
if 'fe80' in addr[0]:
|
||||
break
|
||||
else:
|
||||
continue
|
||||
_add_attributes(rsps[id])
|
||||
del rsps[id]['payload']
|
||||
del rsps[id]['function']
|
||||
@@ -542,4 +512,4 @@ def scan(srvtypes=_slp_services, addresses=None, localonly=False):
|
||||
if __name__ == '__main__':
|
||||
def testsnoop(a):
|
||||
print(repr(a))
|
||||
snoop(testsnoop)
|
||||
snoop(testsnoop)
|
||||
@@ -1,7 +1,7 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2014 IBM Corporation
|
||||
# Copyright 2015-2017 Lenovo
|
||||
# Copyright 2015 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -21,86 +21,79 @@ import json
|
||||
|
||||
class ConfluentException(Exception):
|
||||
apierrorcode = 500
|
||||
_apierrorstr = 'Unexpected Error'
|
||||
apierrorstr = 'Unexpected Error'
|
||||
|
||||
def get_error_body(self):
|
||||
errstr = ' - '.join((self._apierrorstr, str(self)))
|
||||
errstr = ' - '.join((self.apierrorstr, str(self)))
|
||||
return json.dumps({'error': errstr })
|
||||
|
||||
@property
|
||||
def apierrorstr(self):
|
||||
if str(self):
|
||||
return self._apierrorstr + ' - ' + str(self)
|
||||
return self._apierrorstr
|
||||
|
||||
|
||||
class NotFoundException(ConfluentException):
|
||||
# Something that could be construed as a name was not found
|
||||
# basically, picture an http error code 404
|
||||
apierrorcode = 404
|
||||
_apierrorstr = 'Target not found'
|
||||
apierrorstr = 'Request path not recognized'
|
||||
|
||||
|
||||
class InvalidArgumentException(ConfluentException):
|
||||
# Something from the remote client wasn't correct
|
||||
# like http code 400
|
||||
apierrorcode = 400
|
||||
_apierrorstr = 'Bad Request'
|
||||
apierrorstr = 'Bad Request'
|
||||
|
||||
|
||||
class TargetEndpointUnreachable(ConfluentException):
|
||||
# A target system was unavailable. For example, a BMC
|
||||
# was unreachable. http code 504
|
||||
apierrorcode = 504
|
||||
_apierrorstr = 'Unreachable Target'
|
||||
apierrorstr = 'Unreachable Target'
|
||||
|
||||
|
||||
class TargetEndpointBadCredentials(ConfluentException):
|
||||
# target was reachable, but authentication/authorization
|
||||
# failed
|
||||
apierrorcode = 502
|
||||
_apierrorstr = 'Bad Credentials'
|
||||
apierrorstr = 'Bad Credentials'
|
||||
|
||||
|
||||
class LockedCredentials(ConfluentException):
|
||||
# A request was performed that required a credential, but the credential
|
||||
# store is locked
|
||||
_apierrorstr = 'Credential store locked'
|
||||
apierrorstr = 'Credential store locked'
|
||||
|
||||
|
||||
class ForbiddenRequest(ConfluentException):
|
||||
# The client request is not allowed by authorization engine
|
||||
apierrorcode = 403
|
||||
_apierrorstr = 'Forbidden'
|
||||
apierrorstr = 'Forbidden'
|
||||
|
||||
|
||||
class NotImplementedException(ConfluentException):
|
||||
# The current configuration/plugin is unable to perform
|
||||
# the requested task. http code 501
|
||||
apierrorcode = 501
|
||||
_apierrorstr = '501 - Not Implemented'
|
||||
apierrorstr = '501 - Not Implemented'
|
||||
|
||||
|
||||
|
||||
class GlobalConfigError(ConfluentException):
|
||||
# The configuration in the global config file is not right
|
||||
_apierrorstr = 'Global configuration contains an error'
|
||||
apierrorstr = 'Global configuration contains an error'
|
||||
|
||||
|
||||
class TargetResourceUnavailable(ConfluentException):
|
||||
# This is meant for scenarios like asking to read a sensor that is
|
||||
# currently unavailable. This may be a persistent or transient state
|
||||
apierrocode = 503
|
||||
_apierrorstr = 'Target Resource Unavailable'
|
||||
apierrorstr = 'Target Resource Unavailable'
|
||||
|
||||
class PubkeyInvalid(ConfluentException):
|
||||
apierrorcode = 502
|
||||
_apierrorstr = '502 - Invalid certificate or key on target'
|
||||
apierrorstr = '502 - Invalid certificate or key on target'
|
||||
|
||||
def __init__(self, text, certificate, fingerprint, attribname, event):
|
||||
super(PubkeyInvalid, self).__init__(self, text)
|
||||
self.fingerprint = fingerprint
|
||||
self.attrname = attribname
|
||||
bodydata = {'message': text,
|
||||
'event': event,
|
||||
'fingerprint': fingerprint,
|
||||
@@ -113,7 +106,7 @@ class PubkeyInvalid(ConfluentException):
|
||||
|
||||
class LoggedOut(ConfluentException):
|
||||
apierrorcode = 401
|
||||
_apierrorstr = '401 - Logged out'
|
||||
apierrorstr = '401 - Logged out'
|
||||
|
||||
def get_error_body(self):
|
||||
return '{"loggedout": 1}'
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# 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.
|
||||
|
||||
# provide managing firmware update process and firmware repository if/when
|
||||
# the time comes
|
||||
|
||||
import confluent.exceptions as exc
|
||||
import confluent.messages as msg
|
||||
import eventlet
|
||||
|
||||
updatesbytarget = {}
|
||||
uploadsbytarget = {}
|
||||
updatepool = eventlet.greenpool.GreenPool(256)
|
||||
|
||||
|
||||
def execupdate(handler, filename, updateobj, type):
|
||||
try:
|
||||
if type == 'firmware':
|
||||
completion = handler(filename, progress=updateobj.handle_progress,
|
||||
bank=updateobj.bank)
|
||||
else:
|
||||
completion = handler(filename, progress=updateobj.handle_progress)
|
||||
if completion is None:
|
||||
completion = 'complete'
|
||||
updateobj.handle_progress({'phase': completion, 'progress': 100.0})
|
||||
except exc.PubkeyInvalid as pi:
|
||||
errstr = 'Certificate mismatch detected, does not match value in ' \
|
||||
'attribute {0}'.format(pi.attrname)
|
||||
updateobj.handle_progress({'phase': 'error', 'progress': 0.0,
|
||||
'detail': errstr})
|
||||
except Exception as e:
|
||||
updateobj.handle_progress({'phase': 'error', 'progress': 0.0,
|
||||
'detail': str(e)})
|
||||
|
||||
class Updater(object):
|
||||
def __init__(self, node, handler, filename, tenant=None, name=None,
|
||||
bank=None, type='firmware'):
|
||||
self.bank = bank
|
||||
self.node = node
|
||||
self.phase = 'initializing'
|
||||
self.detail = ''
|
||||
self.percent = 0.0
|
||||
self.updateproc = updatepool.spawn(execupdate, handler, filename,
|
||||
self, type)
|
||||
if type == 'firmware':
|
||||
myparty = updatesbytarget
|
||||
elif type == 'mediaupload':
|
||||
myparty = uploadsbytarget
|
||||
if (node, tenant) not in myparty:
|
||||
myparty[(node, tenant)] = {}
|
||||
if name is None:
|
||||
name = 1
|
||||
while '{0}'.format(name) in myparty[(node, tenant)]:
|
||||
name += 1
|
||||
self.name = '{0}'.format(name)
|
||||
myparty[(node, tenant)][self.name] = self
|
||||
|
||||
def handle_progress(self, progress):
|
||||
self.phase = progress.get('phase', 'unknown')
|
||||
self.percent = float(progress.get('progress', 100.0))
|
||||
self.detail = progress.get('detail', '')
|
||||
|
||||
def cancel(self):
|
||||
self.updateproc.kill()
|
||||
|
||||
@property
|
||||
def progress(self):
|
||||
return {'phase': self.phase, 'progress': self.percent,
|
||||
'detail': self.detail}
|
||||
|
||||
|
||||
def remove_updates(nodes, tenant, element, type='firmware'):
|
||||
if len(element) < 5 and element[:2] != ['media', 'uploads']:
|
||||
raise exc.InvalidArgumentException()
|
||||
upid = element[-1]
|
||||
if type == 'firmware':
|
||||
myparty = updatesbytarget
|
||||
else:
|
||||
myparty = uploadsbytarget
|
||||
for node in nodes:
|
||||
try:
|
||||
upd = myparty[(node, tenant)][upid]
|
||||
except KeyError:
|
||||
raise exc.NotFoundException('No active update matches request')
|
||||
upd.cancel()
|
||||
del myparty[(node, tenant)][upid]
|
||||
yield msg.DeletedResource(
|
||||
'nodes/{0}/inventory/firmware/updates/active/{1}'.format(
|
||||
node, upid))
|
||||
|
||||
|
||||
def list_updates(nodes, tenant, element, type='firmware'):
|
||||
showmode = False
|
||||
if type == 'mediaupload':
|
||||
myparty = uploadsbytarget
|
||||
verb = 'upload'
|
||||
else:
|
||||
myparty = updatesbytarget
|
||||
verb = 'update'
|
||||
if type == 'firmware':
|
||||
specificlen = 4
|
||||
else:
|
||||
specificlen = 2
|
||||
if len(element) > specificlen:
|
||||
showmode = True
|
||||
upid = element[-1]
|
||||
for node in nodes:
|
||||
if showmode:
|
||||
try:
|
||||
updater = myparty[(node, tenant)][upid]
|
||||
except KeyError:
|
||||
raise exc.NotFoundException(
|
||||
'No matching {0} process found'.format(verb))
|
||||
yield msg.KeyValueData(updater.progress, name=node)
|
||||
else:
|
||||
for updateid in myparty.get((node, tenant), {}):
|
||||
yield msg.ChildCollection(updateid)
|
||||
@@ -1,119 +0,0 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# 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 handles port forwarding for web interfaces on management devices
|
||||
#It will also hijack port 3900 and do best effort..
|
||||
|
||||
import eventlet
|
||||
import eventlet.green.select as select
|
||||
import eventlet.green.socket as socket
|
||||
forwardersbyclient = {}
|
||||
relaysbysession = {}
|
||||
sessionsbyip = {}
|
||||
ipsbysession = {}
|
||||
sockhandler = {}
|
||||
vidtargetbypeer = {}
|
||||
vidforwarder = None
|
||||
|
||||
def handle_connection(incoming, outgoing):
|
||||
while True:
|
||||
r, _, _ = select.select((incoming, outgoing), (), (), 60)
|
||||
for mysock in r:
|
||||
data = mysock.recv(32768)
|
||||
if not data:
|
||||
return
|
||||
if mysock == incoming:
|
||||
outgoing.sendall(data)
|
||||
elif mysock == outgoing:
|
||||
incoming.sendall(data)
|
||||
|
||||
|
||||
def forward_port(sock, target, clientip, sessionid):
|
||||
while True:
|
||||
conn, cli = sock.accept()
|
||||
if cli[0] != clientip:
|
||||
conn.close()
|
||||
continue
|
||||
try:
|
||||
client = socket.create_connection((target, 443))
|
||||
except Exception:
|
||||
conn.close()
|
||||
continue
|
||||
if sessionid not in relaysbysession:
|
||||
relaysbysession[sessionid] = {}
|
||||
relaysbysession[sessionid][eventlet.spawn(
|
||||
handle_connection, conn, client)] = conn
|
||||
|
||||
|
||||
def forward_video():
|
||||
sock = eventlet.listen(('::', 3900, 0, 0), family=socket.AF_INET6)
|
||||
while True:
|
||||
conn, cli = sock.accept()
|
||||
if cli[0] not in vidtargetbypeer or not sessionsbyip.get(cli[0], None):
|
||||
conn.close()
|
||||
continue
|
||||
try:
|
||||
vidclient = socket.create_connection((vidtargetbypeer[cli[0]],
|
||||
3900))
|
||||
except Exception:
|
||||
conn.close()
|
||||
continue
|
||||
eventlet.spawn_n(handle_connection, conn, vidclient)
|
||||
|
||||
|
||||
def close_session(sessionid):
|
||||
for addr in forwardersbyclient.get(sessionid, []):
|
||||
killsock = forwardersbyclient[sessionid][addr]
|
||||
sockhandler[killsock].kill()
|
||||
del sockhandler[killsock]
|
||||
killsock.close()
|
||||
if sessionid in forwardersbyclient:
|
||||
del forwardersbyclient[sessionid]
|
||||
for clip in ipsbysession.get(sessionid, ()):
|
||||
sessionsbyip[clip].discard(sessionid)
|
||||
if sessionid in ipsbysession:
|
||||
del ipsbysession[sessionid]
|
||||
for relay in list(relaysbysession.get(sessionid, ())):
|
||||
conn = relaysbysession[sessionid][relay]
|
||||
relay.kill()
|
||||
conn.close()
|
||||
if sessionid in relaysbysession:
|
||||
del relaysbysession[sessionid]
|
||||
|
||||
|
||||
def get_port(addr, clientip, sessionid):
|
||||
global vidforwarder
|
||||
if socket.getaddrinfo(clientip, 0)[0][0] == socket.AF_INET:
|
||||
clientip = '::ffff:' + clientip
|
||||
if sessionid not in ipsbysession:
|
||||
ipsbysession[sessionid] = set([])
|
||||
if clientip not in sessionsbyip:
|
||||
sessionsbyip[clientip] = set([])
|
||||
sessionsbyip[clientip].add(sessionid)
|
||||
ipsbysession[sessionid].add(clientip)
|
||||
if sessionid not in forwardersbyclient:
|
||||
forwardersbyclient[sessionid] = {}
|
||||
if addr not in forwardersbyclient[sessionid]:
|
||||
newsock = eventlet.listen(('::', 0, 0, 0),
|
||||
family=socket.AF_INET6)
|
||||
forwardersbyclient[sessionid][addr] = newsock
|
||||
sockhandler[newsock] = eventlet.spawn(forward_port, newsock, addr,
|
||||
clientip, sessionid)
|
||||
if not vidforwarder:
|
||||
vidforwarder = eventlet.spawn(forward_video)
|
||||
vidtargetbypeer[clientip] = addr
|
||||
return forwardersbyclient[sessionid][addr].getsockname()[1]
|
||||
|
||||
@@ -21,7 +21,6 @@ import Cookie
|
||||
import confluent.auth as auth
|
||||
import confluent.config.attributes as attribs
|
||||
import confluent.consoleserver as consoleserver
|
||||
import confluent.forwarder as forwarder
|
||||
import confluent.exceptions as exc
|
||||
import confluent.log as log
|
||||
import confluent.messages
|
||||
@@ -152,7 +151,6 @@ def _sessioncleaner():
|
||||
if httpsessions[session]['expiry'] < currtime:
|
||||
targsessions.append(session)
|
||||
for session in targsessions:
|
||||
forwarder.close_session(session)
|
||||
del httpsessions[session]
|
||||
targsessions = []
|
||||
for session in consolesessions:
|
||||
@@ -217,17 +215,11 @@ def _should_skip_authlog(env):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _csrf_exempt(path):
|
||||
# first a get of info to get CSRF key, also '/forward/web' to enable
|
||||
# the popup ability to just forward
|
||||
return path == '/sessions/current/info' or path.endswith('/forward/web')
|
||||
|
||||
|
||||
def _csrf_valid(env, session):
|
||||
# This could be simplified into a statement, but this is more readable
|
||||
# to have it broken out
|
||||
if env['REQUEST_METHOD'] == 'GET' and _csrf_exempt(env['PATH_INFO']):
|
||||
if (env['REQUEST_METHOD'] == 'GET' and
|
||||
env['PATH_INFO'] == '/sessions/current/info'):
|
||||
# Provide a web client a safe hook to request the CSRF token
|
||||
# This means that we consider GET of /sessions/current/info to be
|
||||
# a safe thing to inflict via CSRF, since CORS should prevent
|
||||
@@ -284,7 +276,6 @@ def _authorize_request(env, operation):
|
||||
targets.append(mythread)
|
||||
for mythread in targets:
|
||||
eventlet.greenthread.kill(mythread)
|
||||
forwarder.close_session(sessionid)
|
||||
del httpsessions[sessionid]
|
||||
return ('logout',)
|
||||
httpsessions[sessionid]['expiry'] = time.time() + 90
|
||||
@@ -455,24 +446,6 @@ def resourcehandler_backend(env, start_response):
|
||||
start_response('{0} {1}'.format(e.apierrorcode, e.apierrorstr),
|
||||
headers)
|
||||
yield e.get_error_body()
|
||||
elif (env['PATH_INFO'].endswith('/forward/web') and
|
||||
env['PATH_INFO'].startswith('/nodes/')):
|
||||
prefix, _, _ = env['PATH_INFO'].partition('/forward/web')
|
||||
_, _, nodename = prefix.rpartition('/')
|
||||
hm = cfgmgr.get_node_attributes(nodename, 'hardwaremanagement.manager')
|
||||
targip = hm.get(nodename, {}).get(
|
||||
'hardwaremanagement.manager', {}).get('value', None)
|
||||
if not targip:
|
||||
start_response('404 Not Found', headers)
|
||||
yield 'No hardwaremanagemnet.manager defined for node'
|
||||
return
|
||||
funport = forwarder.get_port(targip, env['HTTP_X_FORWARDED_FOR'],
|
||||
authorized['sessionid'])
|
||||
host = env['HTTP_X_FORWARDED_HOST']
|
||||
url = 'https://{0}:{1}/'.format(host, funport)
|
||||
start_response('302', [('Location', url)])
|
||||
yield 'Our princess is in another castle!'
|
||||
return
|
||||
elif (operation == 'create' and ('/console/session' in env['PATH_INFO'] or
|
||||
'/shell/sessions/' in env['PATH_INFO'])):
|
||||
#hard bake JSON into this path, do not support other incarnations
|
||||
|
||||
@@ -379,7 +379,7 @@ class TimedAndSizeRotatingFileHandler(BaseRotatingHandler):
|
||||
|
||||
def _sizeRoll(self):
|
||||
self.close()
|
||||
for i in range(self.backupCount - 1, 0, -1):
|
||||
for i in range(self.sizeRollingCount, 0, -1):
|
||||
sbfn = "%s.%d" % (self.binpath, i)
|
||||
dbfn = "%s.%d" % (self.binpath, i + 1)
|
||||
stfn = "%s.%d" % (self.textpath, i)
|
||||
@@ -392,6 +392,8 @@ class TimedAndSizeRotatingFileHandler(BaseRotatingHandler):
|
||||
if os.path.exists(dtfn):
|
||||
os.remove(dtfn)
|
||||
os.rename(stfn, dtfn)
|
||||
# size rolling happens, add statistics count
|
||||
self.sizeRollingCount += 1
|
||||
dbfn = self.binpath + ".1"
|
||||
dtfn = self.textpath + ".1"
|
||||
if os.path.exists(dbfn):
|
||||
@@ -402,18 +404,8 @@ class TimedAndSizeRotatingFileHandler(BaseRotatingHandler):
|
||||
os.rename(self.binpath, dbfn)
|
||||
if os.path.exists(self.textpath):
|
||||
os.rename(self.textpath, dtfn)
|
||||
self._deleteFilesForSizeRolling()
|
||||
return dbfn, dtfn
|
||||
|
||||
def _deleteFilesForSizeRolling(self):
|
||||
for i in range(self.sizeRollingCount, self.backupCount -1, -1):
|
||||
dbfn = "%s.%d" % (self.binpath, i)
|
||||
dtfn = "%s.%d" % (self.textpath, i)
|
||||
if os.path.exists(dbfn):
|
||||
os.remove(dbfn)
|
||||
if os.path.exists(dtfn):
|
||||
os.remove(dtfn)
|
||||
|
||||
def _timeRoll(self):
|
||||
self.close()
|
||||
# get the time that this sequence started at and make it a TimeTuple
|
||||
@@ -432,7 +424,8 @@ class TimedAndSizeRotatingFileHandler(BaseRotatingHandler):
|
||||
addend = -3600
|
||||
timeTuple = time.localtime(t + addend)
|
||||
|
||||
for i in range(self.backupCount - 1, 0, -1):
|
||||
# if size rolling files exist
|
||||
for i in range(self.sizeRollingCount, 0, -1):
|
||||
sbfn = "%s.%d" % ( self.binpath, i)
|
||||
dbfn = "%s.%s.%d" % (
|
||||
self.binpath, time.strftime(self.suffix, timeTuple),i)
|
||||
@@ -448,17 +441,19 @@ class TimedAndSizeRotatingFileHandler(BaseRotatingHandler):
|
||||
os.remove(dtfn)
|
||||
os.rename(stfn, dtfn)
|
||||
|
||||
# As time rolling happens, reset statistics count
|
||||
self.sizeRollingCount = 0
|
||||
dbfn = self.binpath + "." + time.strftime(self.suffix, timeTuple)
|
||||
odbfn = dbfn
|
||||
dtfn = self.textpath + "." + time.strftime(self.suffix, timeTuple)
|
||||
odtfn = dtfn
|
||||
append=1
|
||||
while os.path.exists(dbfn):
|
||||
dbfn = odbfn + '.{0}'.format(append)
|
||||
dbfn = odbfn + '.{}'.format(append)
|
||||
append += 1
|
||||
append=1
|
||||
while os.path.exists(dtfn):
|
||||
dtfn = odtfn + '.{0}'.format(append)
|
||||
dtfn = odtfn + '.{}'.format(append)
|
||||
append += 1
|
||||
if os.path.exists(self.binpath):
|
||||
os.rename(self.binpath, dbfn)
|
||||
@@ -545,12 +540,6 @@ class Logger(object):
|
||||
tstamp = entry[1]
|
||||
data = entry[2]
|
||||
evtdata = entry[3]
|
||||
if len(data) > 65535:
|
||||
# our max log entry is 65k, take only the first 65k and put
|
||||
# rest back on as a continuation
|
||||
entry[2] = data[65535:]
|
||||
self.logentries.appendleft(entry)
|
||||
data = data[:65535]
|
||||
textdate = ''
|
||||
if self.isconsole and ltype != 2:
|
||||
textdate = time.strftime(
|
||||
|
||||
@@ -163,7 +163,7 @@ def dumptrace(signalname, frame):
|
||||
continue
|
||||
if not o:
|
||||
continue
|
||||
ht.write('Thread trace: ({0})\n'.format(id(o)))
|
||||
ht.write('Thread trace:\n')
|
||||
ht.write(''.join(traceback.format_stack(o.gr_frame)))
|
||||
ht.close()
|
||||
|
||||
@@ -204,10 +204,7 @@ def setlimits():
|
||||
|
||||
def run():
|
||||
setlimits()
|
||||
try:
|
||||
signal.signal(signal.SIGUSR1, dumptrace)
|
||||
except AttributeError:
|
||||
pass # silly windows
|
||||
signal.signal(signal.SIGUSR1, dumptrace)
|
||||
if havefcntl:
|
||||
_checkpidfile()
|
||||
conf.init_config()
|
||||
@@ -234,12 +231,9 @@ def run():
|
||||
os.remove('/var/run/confluent/dbg.sock')
|
||||
except OSError:
|
||||
pass # We are not expecting the file to exist
|
||||
try:
|
||||
dbgsock = eventlet.listen("/var/run/confluent/dbg.sock",
|
||||
family=socket.AF_UNIX)
|
||||
eventlet.spawn_n(backdoor.backdoor_server, dbgsock)
|
||||
except AttributeError:
|
||||
pass # Windows...
|
||||
dbgsock = eventlet.listen("/var/run/confluent/dbg.sock",
|
||||
family=socket.AF_UNIX)
|
||||
eventlet.spawn_n(backdoor.backdoor_server, dbgsock)
|
||||
os.umask(oumask)
|
||||
http_bind_host, http_bind_port = _get_connector_config('http')
|
||||
sock_bind_host, sock_bind_port = _get_connector_config('socket')
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
# Things are defined here to 'encourage' developers to coordinate information
|
||||
# format. This is also how different data formats are supported
|
||||
import confluent.exceptions as exc
|
||||
import confluent.config.configmanager as cfm
|
||||
from copy import deepcopy
|
||||
from datetime import datetime
|
||||
import json
|
||||
@@ -35,7 +34,7 @@ valid_health_values = set([
|
||||
def _htmlify_structure(indict):
|
||||
ret = "<ul>"
|
||||
if isinstance(indict, dict):
|
||||
for key in sorted(indict):
|
||||
for key in indict.iterkeys():
|
||||
ret += "<li>{0}: ".format(key)
|
||||
if type(indict[key]) in (str, unicode, float, int):
|
||||
ret += str(indict[key])
|
||||
@@ -77,7 +76,7 @@ class ConfluentMessage(object):
|
||||
datasource = self.kvpairs
|
||||
else:
|
||||
datasource = {'databynode': self.kvpairs}
|
||||
jsonsnippet = json.dumps(datasource, sort_keys=True, separators=(',', ':'))[1:-1]
|
||||
jsonsnippet = json.dumps(datasource, separators=(',', ':'))[1:-1]
|
||||
return jsonsnippet
|
||||
|
||||
def raw(self):
|
||||
@@ -139,10 +138,7 @@ class ConfluentMessage(object):
|
||||
'<input type="checkbox" name="restexplorerhonorkey" '
|
||||
'value="{1}">\r').format(valtype, key, self.desc)
|
||||
return snippet
|
||||
if (isinstance(val, bool) or isinstance(val, str) or
|
||||
isinstance(val, unicode)):
|
||||
value = str(val)
|
||||
elif val is not None and 'value' in val:
|
||||
if val is not None and 'value' in val:
|
||||
value = val['value']
|
||||
if 'inheritedfrom' in val:
|
||||
notes.append('Inherited from %s' % val['inheritedfrom'])
|
||||
@@ -166,7 +162,7 @@ class ConfluentMessage(object):
|
||||
notes.append('Inherited from %s' % val['inheritedfrom'])
|
||||
value = '********'
|
||||
if self.readonly:
|
||||
snippet += "{0}: {1}<br>".format(key, value)
|
||||
snippet += "{0}: {1}".format(key, value)
|
||||
else:
|
||||
snippet += (key + ":" +
|
||||
'<input type="{0}" name="{1}" value="{2}" '
|
||||
@@ -247,25 +243,13 @@ class DeletedResource(ConfluentMessage):
|
||||
def __init__(self, resource):
|
||||
self.kvpairs = {'deleted': resource}
|
||||
|
||||
def strip_node(self, node):
|
||||
pass
|
||||
|
||||
|
||||
class CreatedResource(ConfluentMessage):
|
||||
notnode = True
|
||||
readonly = True
|
||||
|
||||
def __init__(self, resource):
|
||||
self.kvpairs = {'created': resource}
|
||||
|
||||
def strip_node(self, node):
|
||||
pass
|
||||
|
||||
|
||||
class AssignedResource(ConfluentMessage):
|
||||
notnode = True
|
||||
readonly = True
|
||||
|
||||
def __init__(self, resource):
|
||||
self.kvpairs = {'assigned': resource}
|
||||
|
||||
@@ -367,13 +351,9 @@ class ChildCollection(LinkRelation):
|
||||
# InputMCI
|
||||
# InputDomainName
|
||||
# InputNTPServer
|
||||
def get_input_message(path, operation, inputdata, nodes=None, multinode=False,
|
||||
configmanager=None):
|
||||
def get_input_message(path, operation, inputdata, nodes=None, multinode=False):
|
||||
if path[0] == 'power' and path[1] == 'state' and operation != 'retrieve':
|
||||
return InputPowerMessage(path, nodes, inputdata)
|
||||
elif (path in (['power', 'reseat'], ['_enclosure', 'reseat_bay']) and
|
||||
operation != 'retrieve'):
|
||||
return InputReseatMessage(path, nodes, inputdata)
|
||||
elif path == ['attributes', 'expression']:
|
||||
return InputExpression(path, inputdata, nodes)
|
||||
elif path[0] in ('attributes', 'users') and operation != 'retrieve':
|
||||
@@ -399,8 +379,7 @@ def get_input_message(path, operation, inputdata, nodes=None, multinode=False,
|
||||
return InputMCI(path, nodes, inputdata)
|
||||
elif (path[:4] == ['configuration', 'management_controller',
|
||||
'net_interfaces', 'management'] and operation != 'retrieve'):
|
||||
return InputNetworkConfiguration(path, nodes, inputdata,
|
||||
configmanager)
|
||||
return InputNetworkConfiguration(path, nodes, inputdata)
|
||||
elif (path[:3] == ['configuration', 'management_controller', 'domain_name']
|
||||
and operation != 'retrieve'):
|
||||
return InputDomainName(path, nodes, inputdata)
|
||||
@@ -410,41 +389,9 @@ def get_input_message(path, operation, inputdata, nodes=None, multinode=False,
|
||||
elif (path[:4] == ['configuration', 'management_controller', 'ntp',
|
||||
'servers'] and operation != 'retrieve' and len(path) == 5):
|
||||
return InputNTPServer(path, nodes, inputdata)
|
||||
elif (path[:3] == ['configuration', 'system', 'all'] and
|
||||
operation != 'retrieve'):
|
||||
return InputConfigChangeSet(path, inputdata, nodes, configmanager)
|
||||
elif 'inventory/firmware/updates/active' in '/'.join(path) and inputdata:
|
||||
return InputFirmwareUpdate(path, nodes, inputdata)
|
||||
elif '/'.join(path).startswith('media/detach'):
|
||||
return DetachMedia(path, nodes, inputdata)
|
||||
elif '/'.join(path).startswith('media/') and inputdata:
|
||||
return InputMedia(path, nodes, inputdata)
|
||||
elif inputdata:
|
||||
raise exc.InvalidArgumentException(
|
||||
'No known input handler for request')
|
||||
raise exc.InvalidArgumentException()
|
||||
|
||||
class InputFirmwareUpdate(ConfluentMessage):
|
||||
|
||||
def __init__(self, path, nodes, inputdata):
|
||||
self.filename = inputdata.get('filename', inputdata.get('url', None))
|
||||
self.bank = inputdata.get('bank', None)
|
||||
self.nodes = nodes
|
||||
|
||||
class InputMedia(InputFirmwareUpdate):
|
||||
# Use InputFirmwareUpdate
|
||||
pass
|
||||
|
||||
|
||||
class DetachMedia(ConfluentMessage):
|
||||
def __init__(self, path, nodes, inputdata):
|
||||
if 'detachall' not in inputdata:
|
||||
raise exc.InvalidArgumentException('Currently only supporting'
|
||||
'{"detachall": 1}')
|
||||
|
||||
|
||||
class Media(ConfluentMessage):
|
||||
def __init__(self, node, media):
|
||||
self.kvpairs = {node: {'name': media.name, 'url': media.url}}
|
||||
|
||||
class InputAlertData(ConfluentMessage):
|
||||
|
||||
@@ -473,13 +420,23 @@ class InputExpression(ConfluentMessage):
|
||||
# so that it can make it intact to the pertinent configmanager function
|
||||
def __init__(self, path, inputdata, nodes=None):
|
||||
self.nodeattribs = {}
|
||||
nestedmode = False
|
||||
if not inputdata:
|
||||
raise exc.InvalidArgumentException('no request data provided')
|
||||
if nodes is None:
|
||||
self.attribs = inputdata
|
||||
return
|
||||
for node in nodes:
|
||||
self.nodeattribs[node] = inputdata
|
||||
if node in inputdata:
|
||||
nestedmode = True
|
||||
self.nodeattribs[node] = inputdata[node]
|
||||
if nestedmode:
|
||||
for key in inputdata:
|
||||
if key not in nodes:
|
||||
raise exc.InvalidArgumentException
|
||||
else:
|
||||
for node in nodes:
|
||||
self.nodeattribs[node] = inputdata
|
||||
|
||||
def get_attributes(self, node):
|
||||
if node not in self.nodeattribs:
|
||||
@@ -487,27 +444,6 @@ class InputExpression(ConfluentMessage):
|
||||
nodeattr = deepcopy(self.nodeattribs[node])
|
||||
return nodeattr
|
||||
|
||||
class InputConfigChangeSet(InputExpression):
|
||||
# For now, this is identical to InputExpression, later it may
|
||||
# internalize formula expansion, but not now..
|
||||
def __init__(self, path, inputdata, nodes=None, configmanager=None):
|
||||
self.cfm = configmanager
|
||||
super(InputConfigChangeSet, self).__init__(path, inputdata, nodes)
|
||||
|
||||
def get_attributes(self, node):
|
||||
attrs = super(InputConfigChangeSet, self).get_attributes(node)
|
||||
endattrs = {}
|
||||
for attr in attrs:
|
||||
origval = attrs[attr]
|
||||
if isinstance(origval, str) or isinstance(origval, unicode):
|
||||
origval = {'expression': origval}
|
||||
if 'expression' not in origval:
|
||||
endattrs[attr] = attrs[attr]
|
||||
else:
|
||||
endattrs[attr] = list(self.cfm.expand_attrib_expression(
|
||||
[node], attrs[attr]))[0][1]
|
||||
return endattrs
|
||||
|
||||
|
||||
class InputAttributes(ConfluentMessage):
|
||||
# This is particularly designed for attributes, where a simple string
|
||||
@@ -515,13 +451,14 @@ class InputAttributes(ConfluentMessage):
|
||||
# preserve the client provided expression for posterity, rather than
|
||||
# immediate consumption.
|
||||
# for things like node configuration or similar, a different class is
|
||||
# appropriate since it needs to immediately expand an expression.
|
||||
# appropriate since it nedes to immediately expand an expression.
|
||||
# with that class, the 'InputExpression' and calling code in attributes.py
|
||||
# might be deprecated in favor of the generic expression expander
|
||||
# and a small function in attributes.py to reflect the expansion back
|
||||
# to the client
|
||||
def __init__(self, path, inputdata, nodes=None):
|
||||
self.nodeattribs = {}
|
||||
nestedmode = False
|
||||
if not inputdata:
|
||||
raise exc.InvalidArgumentException('no request data provided')
|
||||
if nodes is None:
|
||||
@@ -546,7 +483,16 @@ class InputAttributes(ConfluentMessage):
|
||||
'expression': self.attribs[attrib]}
|
||||
return
|
||||
for node in nodes:
|
||||
self.nodeattribs[node] = inputdata
|
||||
if node in inputdata:
|
||||
nestedmode = True
|
||||
self.nodeattribs[node] = inputdata[node]
|
||||
if nestedmode:
|
||||
for key in inputdata:
|
||||
if key not in nodes:
|
||||
raise exc.InvalidArgumentException
|
||||
else:
|
||||
for node in nodes:
|
||||
self.nodeattribs[node] = inputdata
|
||||
|
||||
def get_attributes(self, node):
|
||||
if node not in self.nodeattribs:
|
||||
@@ -583,6 +529,7 @@ class InputCredential(ConfluentMessage):
|
||||
|
||||
def __init__(self, path, inputdata, nodes=None):
|
||||
self.credentials = {}
|
||||
nestedmode = False
|
||||
if not inputdata:
|
||||
raise exc.InvalidArgumentException('no request data provided')
|
||||
|
||||
@@ -657,7 +604,7 @@ class ConfluentInputMessage(ConfluentMessage):
|
||||
if self.keyname not in datum:
|
||||
raise exc.InvalidArgumentException(
|
||||
'missing {0} argument'.format(self.keyname))
|
||||
elif not self.is_valid_key(datum[self.keyname]):
|
||||
elif datum[self.keyname] not in self.valid_values:
|
||||
raise exc.InvalidArgumentException(
|
||||
datum[self.keyname] + ' is not one of ' +
|
||||
','.join(self.valid_values))
|
||||
@@ -667,15 +614,13 @@ class ConfluentInputMessage(ConfluentMessage):
|
||||
if self.keyname not in datum:
|
||||
raise exc.InvalidArgumentException(
|
||||
'missing {0} argument'.format(self.keyname))
|
||||
elif not self.is_valid_key(datum[self.keyname]):
|
||||
elif datum[self.keyname] not in self.valid_values:
|
||||
raise exc.InvalidArgumentException(datum[self.keyname] +
|
||||
' is not one of ' +
|
||||
','.join(self.valid_values))
|
||||
for node in nodes:
|
||||
self.inputbynode[node] = datum[self.keyname]
|
||||
|
||||
def is_valid_key(self, key):
|
||||
return key in self.valid_values
|
||||
|
||||
class InputIdentifyMessage(ConfluentInputMessage):
|
||||
valid_values = set([
|
||||
@@ -699,16 +644,6 @@ class InputPowerMessage(ConfluentInputMessage):
|
||||
def powerstate(self, node):
|
||||
return self.inputbynode[node]
|
||||
|
||||
class InputReseatMessage(ConfluentInputMessage):
|
||||
valid_values = set([
|
||||
'reseat',
|
||||
])
|
||||
|
||||
keyname = 'reseat'
|
||||
|
||||
def is_valid_key(self, key):
|
||||
return key in self.valid_values or isinstance(key, int)
|
||||
|
||||
|
||||
class InputBMCReset(ConfluentInputMessage):
|
||||
valid_values = set([
|
||||
@@ -740,7 +675,7 @@ class InputMCI(ConfluentInputMessage):
|
||||
|
||||
|
||||
class InputNetworkConfiguration(ConfluentInputMessage):
|
||||
def __init__(self, path, nodes, inputdata, configmanager=None):
|
||||
def __init__(self, path, nodes, inputdata):
|
||||
self.inputbynode = {}
|
||||
self.stripped = False
|
||||
if not inputdata:
|
||||
@@ -764,27 +699,8 @@ class InputNetworkConfiguration(ConfluentInputMessage):
|
||||
if nodes is None:
|
||||
raise exc.InvalidArgumentException(
|
||||
'This only supports per-node input')
|
||||
nodeattrmap = {}
|
||||
for attr in inputdata:
|
||||
try:
|
||||
if inputdata[attr] is not None:
|
||||
inputdata[attr].format()
|
||||
except (KeyError, IndexError):
|
||||
nodeattrmap[attr] = {}
|
||||
for expanded in configmanager.expand_attrib_expression(
|
||||
nodes, inputdata[attr]):
|
||||
node, value = expanded
|
||||
nodeattrmap[attr][node] = value
|
||||
if not nodeattrmap:
|
||||
for node in nodes:
|
||||
self.inputbynode[node] = inputdata
|
||||
return
|
||||
# an expression was encountered
|
||||
for node in nodes:
|
||||
self.inputbynode[node] = deepcopy(inputdata)
|
||||
for attr in self.inputbynode[node]:
|
||||
if attr in nodeattrmap:
|
||||
self.inputbynode[node][attr] = nodeattrmap[attr][node]
|
||||
self.inputbynode[node] = inputdata
|
||||
|
||||
def netconfig(self, node):
|
||||
return self.inputbynode[node]
|
||||
@@ -939,13 +855,6 @@ class IdentifyState(ConfluentChoiceMessage):
|
||||
keyname = 'identify'
|
||||
|
||||
|
||||
class ReseatResult(ConfluentChoiceMessage):
|
||||
valid_values = set([
|
||||
'success',
|
||||
])
|
||||
keyname = 'reseat'
|
||||
|
||||
|
||||
class PowerState(ConfluentChoiceMessage):
|
||||
valid_values = set([
|
||||
'on',
|
||||
@@ -1261,7 +1170,7 @@ class Attributes(ConfluentMessage):
|
||||
self.desc = desc
|
||||
nkv = {}
|
||||
self.notnode = name is None
|
||||
for key in kv:
|
||||
for key in kv.iterkeys():
|
||||
if type(kv[key]) in (str, unicode):
|
||||
nkv[key] = {'value': kv[key]}
|
||||
else:
|
||||
@@ -1273,8 +1182,6 @@ class Attributes(ConfluentMessage):
|
||||
name: nkv
|
||||
}
|
||||
|
||||
class ConfigSet(Attributes):
|
||||
pass
|
||||
|
||||
class ListAttributes(ConfluentMessage):
|
||||
def __init__(self, name=None, kv=None, desc=''):
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
# this will implement noderange grammar
|
||||
|
||||
|
||||
import confluent.exceptions as exc
|
||||
import codecs
|
||||
import struct
|
||||
import eventlet.green.socket as socket
|
||||
@@ -84,7 +83,7 @@ def get_nic_config(configmanager, node, ip=None, mac=None):
|
||||
cfgdata['prefix'] = prefixlen
|
||||
for setting in nodenetattribs:
|
||||
gw = nodenetattribs[setting].get('value', None)
|
||||
if gw is None or not gw:
|
||||
if gw is None:
|
||||
continue
|
||||
if ip_on_same_subnet(ip, gw, prefixlen):
|
||||
cfgdata['ipv4_gateway'] = gw
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2016, 2017 Lenovo
|
||||
# Copyright 2016 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -30,24 +30,13 @@
|
||||
|
||||
# Provides support for viewing and processing lldp data for switches
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
import confluent.config.configmanager as cfm
|
||||
import base64
|
||||
import confluent.exceptions as exc
|
||||
import confluent.log as log
|
||||
import confluent.messages as msg
|
||||
import confluent.snmputil as snmp
|
||||
import confluent.networking.netutil as netutil
|
||||
import confluent.util as util
|
||||
import eventlet
|
||||
from eventlet.greenpool import GreenPool
|
||||
import eventlet.semaphore
|
||||
import re
|
||||
|
||||
# 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)
|
||||
# 1.0.8802.1.1.2.1.3.7.1.4 - Lookup of LLDP index id to description
|
||||
# Yet another fun fact, the LLDP port index frequent
|
||||
# does *not* map to ifName, like a sane
|
||||
@@ -81,11 +70,6 @@ import re
|
||||
# # 1.0.8802.1.1.2.1.4.1.1.10 - SysDesc - good stuff
|
||||
|
||||
|
||||
_neighdata = {}
|
||||
_neighbypeerid = {}
|
||||
_updatelocks = {}
|
||||
_chassisidbyswitch = {}
|
||||
|
||||
def lenovoname(idx, desc):
|
||||
if desc.isdigit():
|
||||
return 'Ethernet' + str(idx)
|
||||
@@ -95,166 +79,48 @@ nameoverrides = [
|
||||
(re.compile('20301\..*'), lenovoname),
|
||||
]
|
||||
|
||||
# Lenovo chassis id rule is match only first 5 bytes for a match.....
|
||||
|
||||
def _api_sanitize_string(source):
|
||||
source = source.strip()
|
||||
return source.replace(':', '-').replace('/', '-')
|
||||
|
||||
def close_enough(fuzz, literal):
|
||||
if fuzz == literal:
|
||||
return True
|
||||
fuzz = '^' + fuzz.replace('-', '[/: -]') + '$'
|
||||
matcher = re.compile(fuzz)
|
||||
return bool(matcher.match(literal))
|
||||
|
||||
|
||||
def _lldpdesc_to_ifname(switchid, idx, desc):
|
||||
for tform in nameoverrides:
|
||||
if tform[0].match(switchid):
|
||||
desc = tform[1](idx, desc)
|
||||
return desc.strip().strip('\x00')
|
||||
return desc
|
||||
|
||||
|
||||
def _dump_neighbordatum(info):
|
||||
return [msg.KeyValueData(info)]
|
||||
|
||||
def b64tohex(b64str):
|
||||
bd = base64.b64decode(b64str)
|
||||
return ''.join(['{0:02x}'.format(ord(x)) for x in bd])
|
||||
|
||||
def get_fingerprint(switch, port, configmanager, portmatch):
|
||||
update_switch_data(switch, configmanager)
|
||||
for neigh in _neighbypeerid:
|
||||
info = _neighbypeerid[neigh]
|
||||
if neigh == '!!vintage' or info.get('switch', None) != switch:
|
||||
continue
|
||||
if 'peersha256fingerprint' not in info:
|
||||
continue
|
||||
if info.get('switch', None) != switch:
|
||||
continue
|
||||
if portmatch(info.get('port'), port):
|
||||
return ('sha256$' + b64tohex(info['peersha256fingerprint']),
|
||||
info.get('verified', False))
|
||||
return None, False
|
||||
|
||||
|
||||
def _extract_extended_desc(info, source, integritychecked):
|
||||
source = str(source)
|
||||
info['verified'] = bool(integritychecked)
|
||||
if source.startswith('Lenovo SMM;'):
|
||||
info['peerdescription'] = 'Lenovo SMM'
|
||||
if ';S2=' in source:
|
||||
info['peersha256fingerprint'] = source.replace('Lenovo SMM;S2=',
|
||||
'')
|
||||
else:
|
||||
info['peerdescription'] = source
|
||||
|
||||
def sanitize(val):
|
||||
val = str(val)
|
||||
for x in val.strip('\x00'):
|
||||
if ord(x) < 32 or ord(x) > 128:
|
||||
val = ':'.join(['{0:02x}'.format(ord(x)) for x in str(val)])
|
||||
break
|
||||
return val
|
||||
|
||||
def _init_lldp(data, iname, idx, idxtoportid, switch):
|
||||
if iname not in data:
|
||||
data[iname] = {'port': iname, 'portid': str(idxtoportid[idx]),
|
||||
'chassisid': str(_chassisidbyswitch[switch])}
|
||||
|
||||
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
|
||||
vintage = _neighdata.get(switch, {}).get('!!vintage', 0)
|
||||
now = util.monotonic_time()
|
||||
if vintage > (now - 60) and not force:
|
||||
return
|
||||
switch, password, user = args
|
||||
conn = snmp.Session(switch, password, user)
|
||||
sid = None
|
||||
lldpdata = {'!!vintage': now}
|
||||
lldpdata = {}
|
||||
for sysid in conn.walk('1.3.6.1.2.1.1.2'):
|
||||
sid = str(sysid[1][6:])
|
||||
idxtoifname = {}
|
||||
idxtoportid = {}
|
||||
_chassisidbyswitch[switch] = list(conn.walk('1.0.8802.1.1.2.1.3.2'))[0][1]
|
||||
for oidindex in conn.walk('1.0.8802.1.1.2.1.3.7.1.3'):
|
||||
idx = oidindex[0][-1]
|
||||
idxtoportid[idx] = sanitize(oidindex[1])
|
||||
for oidindex in conn.walk('1.0.8802.1.1.2.1.3.7.1.4'):
|
||||
idx = oidindex[0][-1]
|
||||
idxtoifname[idx] = _lldpdesc_to_ifname(sid, idx, str(oidindex[1]))
|
||||
for remotedesc in conn.walk('1.0.8802.1.1.2.1.4.1.1.10'):
|
||||
iname = idxtoifname[remotedesc[0][-2]]
|
||||
_init_lldp(lldpdata, iname, remotedesc[0][-2], idxtoportid, switch)
|
||||
_extract_extended_desc(lldpdata[iname], remotedesc[1], user)
|
||||
lldpdata[iname] = {'description': str(remotedesc[1])}
|
||||
for remotename in conn.walk('1.0.8802.1.1.2.1.4.1.1.9'):
|
||||
iname = idxtoifname[remotename[0][-2]]
|
||||
_init_lldp(lldpdata, iname, remotename[0][-2], idxtoportid, switch)
|
||||
lldpdata[iname]['peername'] = str(remotename[1])
|
||||
for remotename in conn.walk('1.0.8802.1.1.2.1.4.1.1.7'):
|
||||
iname = idxtoifname[remotename[0][-2]]
|
||||
_init_lldp(lldpdata, iname, remotename[0][-2], idxtoportid, switch)
|
||||
lldpdata[iname]['peerportid'] = sanitize(remotename[1])
|
||||
if iname not in lldpdata:
|
||||
lldpdata[iname] = {}
|
||||
lldpdata[iname]['name'] = str(remotename[1])
|
||||
for remoteid in conn.walk('1.0.8802.1.1.2.1.4.1.1.5'):
|
||||
iname = idxtoifname[remoteid[0][-2]]
|
||||
_init_lldp(lldpdata, iname, remoteid[0][-2], idxtoportid, switch)
|
||||
lldpdata[iname]['peerchassisid'] = sanitize(remoteid[1])
|
||||
for entry in lldpdata:
|
||||
if entry == '!!vintage':
|
||||
continue
|
||||
entry = lldpdata[entry]
|
||||
entry['switch'] = switch
|
||||
peerid = '{0}.{1}'.format(
|
||||
entry.get('peerchassisid', '').replace(':', '-').replace('/', '-'),
|
||||
entry.get('peerportid', '').replace(':', '-').replace('/', '-'))
|
||||
entry['peerid'] = peerid
|
||||
_neighbypeerid[peerid] = entry
|
||||
_neighdata[switch] = lldpdata
|
||||
|
||||
|
||||
def update_switch_data(switch, configmanager, force=False):
|
||||
switchcreds = netutil.get_switchcreds(configmanager, (switch,))[0]
|
||||
_extract_neighbor_data(switchcreds + (force,))
|
||||
return _neighdata.get(switch, {})
|
||||
|
||||
|
||||
def update_neighbors(configmanager, force=False):
|
||||
return _update_neighbors_backend(configmanager, force)
|
||||
|
||||
|
||||
def _update_neighbors_backend(configmanager, force):
|
||||
global _neighdata
|
||||
global _neighbypeerid
|
||||
vintage = _neighdata.get('!!vintage', 0)
|
||||
now = util.monotonic_time()
|
||||
if vintage > (now - 60) and not force:
|
||||
return
|
||||
_neighdata = {'!!vintage': now}
|
||||
_neighbypeerid = {'!!vintage': now}
|
||||
switches = netutil.list_switches(configmanager)
|
||||
switchcreds = netutil.get_switchcreds(configmanager, switches)
|
||||
switchcreds = [ x + (force,) for x in switchcreds]
|
||||
pool = GreenPool(64)
|
||||
for ans in pool.imap(_extract_neighbor_data, switchcreds):
|
||||
yield ans
|
||||
if iname not in lldpdata:
|
||||
lldpdata[iname] = {}
|
||||
lldpdata[iname]['chassisid'] = str(remoteid[1])
|
||||
print(repr(lldpdata))
|
||||
|
||||
|
||||
def _extract_neighbor_data(args):
|
||||
# single switch neighbor data update
|
||||
switch = args[0]
|
||||
if switch not in _updatelocks:
|
||||
_updatelocks[switch] = eventlet.semaphore.Semaphore()
|
||||
if _updatelocks[switch].locked():
|
||||
while _updatelocks[switch].locked():
|
||||
eventlet.sleep(1)
|
||||
return
|
||||
try:
|
||||
with _updatelocks[switch]:
|
||||
_extract_neighbor_data_b(args)
|
||||
_extract_neighbor_data_b(args)
|
||||
except Exception:
|
||||
log.logtrace()
|
||||
|
||||
@@ -262,85 +128,4 @@ if __name__ == '__main__':
|
||||
# a quick one-shot test, args are switch and snmpv1 string for now
|
||||
# (should do three argument form for snmpv3 test
|
||||
import sys
|
||||
_extract_neighbor_data((sys.argv[1], sys.argv[2], None, True))
|
||||
print(repr(_neighdata))
|
||||
|
||||
|
||||
multi_selectors = set(['by-switch', 'by-peername', 'by-peerportid',
|
||||
'by-peerchassisid', 'by-chassisid', 'by-port',
|
||||
'by-portid'])
|
||||
single_selectors = set(['by-peerid'])
|
||||
|
||||
def _parameterize_path(pathcomponents):
|
||||
listrequested = False
|
||||
childcoll = True
|
||||
if len(pathcomponents) % 2 == 1:
|
||||
listrequested = pathcomponents[-1]
|
||||
pathcomponents = pathcomponents[:-1]
|
||||
pathit = iter(pathcomponents)
|
||||
keyparams = {}
|
||||
validselectors = multi_selectors | single_selectors
|
||||
for key, val in zip(pathit, pathit):
|
||||
if key not in validselectors:
|
||||
raise exc.NotFoundException('{0} is not valid here'.format(key))
|
||||
keyparams[key] = val
|
||||
validselectors.discard(key)
|
||||
if key == 'by-switch':
|
||||
validselectors.add('by-port')
|
||||
if key in single_selectors:
|
||||
childcoll = False
|
||||
validselectors = set([])
|
||||
return validselectors, keyparams, listrequested, childcoll
|
||||
|
||||
def list_info(parms, requestedparameter):
|
||||
#{u'by-switch': u'r8e1', u'by-port': u'e'}
|
||||
#by-peerport
|
||||
suffix = '/' if requestedparameter in multi_selectors else ''
|
||||
results = set([])
|
||||
requestedparameter = requestedparameter.replace('by-', '')
|
||||
for info in _neighbypeerid:
|
||||
if info == '!!vintage':
|
||||
continue
|
||||
info = _neighbypeerid[info]
|
||||
for mk in parms:
|
||||
mk = mk.replace('by-', '')
|
||||
if mk not in info:
|
||||
continue
|
||||
if (not close_enough(parms['by-' + mk], info[mk]) or
|
||||
requestedparameter not in info):
|
||||
break
|
||||
else:
|
||||
candidate = info[requestedparameter]
|
||||
candidate = candidate.strip()
|
||||
if candidate != '':
|
||||
results.add(_api_sanitize_string(candidate))
|
||||
return [msg.ChildCollection(x + suffix) for x in util.natural_sort(results)]
|
||||
|
||||
def _handle_neighbor_query(pathcomponents, configmanager):
|
||||
choices, parms, listrequested, childcoll = _parameterize_path(
|
||||
pathcomponents)
|
||||
if not childcoll: # this means it's a single entry with by-peerid
|
||||
# guaranteed
|
||||
if (parms['by-peerid'] not in _neighbypeerid and
|
||||
_neighbypeerid.get('!!vintage', 0) < util.monotonic_time() - 60):
|
||||
list(update_neighbors(configmanager))
|
||||
if parms['by-peerid'] not in _neighbypeerid:
|
||||
raise exc.NotFoundException('No matching peer known')
|
||||
return _dump_neighbordatum(_neighbypeerid[parms['by-peerid']])
|
||||
if not listrequested: # the query is for currently valid choices
|
||||
return [msg.ChildCollection(x + '/') for x in sorted(list(choices))]
|
||||
if listrequested not in multi_selectors | single_selectors:
|
||||
raise exc.NotFoundException('{0} is not found'.format(listrequested))
|
||||
if 'by-switch' in parms:
|
||||
update_switch_data(parms['by-switch'], configmanager)
|
||||
else:
|
||||
list(update_neighbors(configmanager))
|
||||
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())
|
||||
_extract_neighbor_data((sys.argv[1], sys.argv[2]))
|
||||
|
||||
@@ -30,8 +30,6 @@
|
||||
|
||||
# this module will provide mac to switch and full 'ifName' label
|
||||
# This functionality is restricted to the null tenant
|
||||
from confluent.networking.lldp import _handle_neighbor_query, get_fingerprint
|
||||
from confluent.networking.netutil import get_switchcreds, list_switches, get_portnamemap
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
@@ -50,7 +48,6 @@ _macmap = {}
|
||||
_macsbyswitch = {}
|
||||
_nodesbymac = {}
|
||||
_switchportmap = {}
|
||||
_neighdata = {}
|
||||
vintage = None
|
||||
|
||||
|
||||
@@ -182,7 +179,20 @@ def _map_switch_backend(args):
|
||||
except ValueError:
|
||||
# ifidx might be '', skip in such a case
|
||||
continue
|
||||
ifnamemap = get_portnamemap(conn)
|
||||
ifnamemap = {}
|
||||
havenames = False
|
||||
for vb in conn.walk('1.3.6.1.2.1.31.1.1.1.1'):
|
||||
ifidx, ifname = vb
|
||||
if not ifname:
|
||||
continue
|
||||
havenames = True
|
||||
ifidx = int(str(ifidx).rsplit('.', 1)[1])
|
||||
ifnamemap[ifidx] = str(ifname)
|
||||
if not havenames:
|
||||
for vb in conn.walk( '1.3.6.1.2.1.2.2.1.2'):
|
||||
ifidx, ifname = vb
|
||||
ifidx = int(str(ifidx).rsplit('.', 1)[1])
|
||||
ifnamemap[ifidx] = str(ifname)
|
||||
maccounts = {}
|
||||
bridgetoifvalid = False
|
||||
for mac in mactobridge:
|
||||
@@ -229,34 +239,29 @@ def _map_switch_backend(args):
|
||||
_macsbyswitch[switch][ifname] = [mac]
|
||||
nodename = _nodelookup(switch, ifname)
|
||||
if nodename is not None:
|
||||
if mac in _nodesbymac and _nodesbymac[mac][0] != nodename:
|
||||
if mac in _nodesbymac and _nodesbymac[mac] != 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)
|
||||
' switch topology values'.format(nodename,
|
||||
_nodesbymac[mac]
|
||||
)})
|
||||
_nodesbymac[mac] = None
|
||||
else:
|
||||
_nodesbymac[mac] = (nodename, maccounts[ifname])
|
||||
_nodesbymac[mac] = nodename
|
||||
|
||||
|
||||
switchbackoff = 30
|
||||
|
||||
|
||||
def find_nodeinfo_by_mac(mac, configmanager):
|
||||
def find_node_by_mac(mac, configmanager):
|
||||
now = util.monotonic_time()
|
||||
if vintage and (now - vintage) < 90 and mac in _nodesbymac:
|
||||
return _nodesbymac[mac][0], {'maccount': _nodesbymac[mac][1]}
|
||||
return _nodesbymac[mac]
|
||||
# do not actually sweep switches more than once every 30 seconds
|
||||
# however, if there is an update in progress, wait on it
|
||||
for _ in update_macmap(configmanager,
|
||||
vintage and (now - vintage) < switchbackoff):
|
||||
for _ in update_macmap(configmanager, vintage and (now - vintage) < 30):
|
||||
if mac in _nodesbymac:
|
||||
return _nodesbymac[mac][0], {'maccount': _nodesbymac[mac][1]}
|
||||
return _nodesbymac[mac]
|
||||
# If update_mac bailed out, still check one last time
|
||||
if mac in _nodesbymac:
|
||||
return _nodesbymac[mac][0], {'maccount': _nodesbymac[mac][1]}
|
||||
return None, {'maccount': 0}
|
||||
return _nodesbymac.get(mac, None)
|
||||
|
||||
|
||||
mapupdating = eventlet.semaphore.Semaphore()
|
||||
@@ -287,20 +292,16 @@ def update_macmap(configmanager, impatient=False):
|
||||
eventlet.spawn_n(_finish_update, completions)
|
||||
raise
|
||||
|
||||
|
||||
def _finish_update(completions):
|
||||
for _ in completions:
|
||||
pass
|
||||
|
||||
|
||||
def _full_updatemacmap(configmanager):
|
||||
global vintage
|
||||
global _macmap
|
||||
global _nodesbymac
|
||||
global _switchportmap
|
||||
global _macsbyswitch
|
||||
global switchbackoff
|
||||
start = util.monotonic_time()
|
||||
with mapupdating:
|
||||
vintage = util.monotonic_time()
|
||||
# Clear all existing entries
|
||||
@@ -311,7 +312,6 @@ def _full_updatemacmap(configmanager):
|
||||
if configmanager.tenant is not None:
|
||||
raise exc.ForbiddenRequest(
|
||||
'Network topology not available to tenants')
|
||||
# here's a list of switches... need to add nodes that are switches
|
||||
nodelocations = configmanager.get_node_attributes(
|
||||
configmanager.list_nodes(), ('net*.switch', 'net*.switchport'))
|
||||
switches = set([])
|
||||
@@ -340,18 +340,28 @@ def _full_updatemacmap(configmanager):
|
||||
_switchportmap[curswitch][portname] = None
|
||||
else:
|
||||
_switchportmap[curswitch][portname] = node
|
||||
switchauth = get_switchcreds(configmanager, switches)
|
||||
pool = GreenPool(64)
|
||||
switchcfg = configmanager.get_node_attributes(
|
||||
switches, ('secret.hardwaremanagementuser', 'secret.snmpcommunity',
|
||||
'secret.hardwaremanagementpassword'), decrypt=True)
|
||||
switchauth = []
|
||||
for switch in switches:
|
||||
if not switch:
|
||||
continue
|
||||
switchparms = switchcfg.get(switch, {})
|
||||
user = None
|
||||
password = switchparms.get(
|
||||
'secret.snmpcommunity', {}).get('value', None)
|
||||
if not password:
|
||||
password = switchparms.get(
|
||||
'secret.hardwaremanagementpassword', {}).get('value',
|
||||
'public')
|
||||
user = switchparms.get(
|
||||
'secret.hardwaremanagementuser', {}).get('value', None)
|
||||
switchauth.append((switch, password, user))
|
||||
pool = GreenPool()
|
||||
for ans in pool.imap(_map_switch, switchauth):
|
||||
vintage = util.monotonic_time()
|
||||
yield ans
|
||||
endtime = util.monotonic_time()
|
||||
duration = endtime - start
|
||||
duration = duration * 15 # wait 15 times as long as it takes to walk
|
||||
# avoid spending a large portion of the time hitting switches with snmp
|
||||
# requests
|
||||
if duration > switchbackoff:
|
||||
switchbackoff = duration
|
||||
|
||||
|
||||
def _dump_locations(info, macaddr, nodename=None):
|
||||
@@ -368,7 +378,7 @@ def _dump_locations(info, macaddr, nodename=None):
|
||||
|
||||
def handle_api_request(configmanager, inputdata, operation, pathcomponents):
|
||||
if operation == 'retrieve':
|
||||
return handle_read_api_request(pathcomponents, configmanager)
|
||||
return handle_read_api_request(pathcomponents)
|
||||
if (operation in ('update', 'create') and
|
||||
pathcomponents == ['networking', 'macs', 'rescan']):
|
||||
if inputdata != {'rescan': 'start'}:
|
||||
@@ -380,44 +390,20 @@ def handle_api_request(configmanager, inputdata, operation, pathcomponents):
|
||||
operation, '/'.join(pathcomponents)))
|
||||
|
||||
|
||||
def get_node_fingerprints(nodename, configmanager):
|
||||
cfg = configmanager.get_node_attributes(nodename, ['net*.switch',
|
||||
'net*.switchport'])
|
||||
for attrkey in cfg[nodename]:
|
||||
if attrkey.endswith('switch'):
|
||||
switch = cfg[nodename][attrkey]['value']
|
||||
port = cfg[nodename][attrkey + 'port']['value']
|
||||
yield get_fingerprint(switch, port, configmanager,
|
||||
_namesmatch)
|
||||
|
||||
|
||||
def handle_read_api_request(pathcomponents, configmanager):
|
||||
def handle_read_api_request(pathcomponents):
|
||||
# TODO(jjohnson2): discovery core.py api handler design, apply it here
|
||||
# to make this a less tangled mess as it gets extended
|
||||
if len(pathcomponents) == 1:
|
||||
return [msg.ChildCollection('macs/'),
|
||||
msg.ChildCollection('neighbors/')]
|
||||
elif pathcomponents[1] == 'neighbors':
|
||||
if len(pathcomponents) == 3 and pathcomponents[-1] == 'by-switch':
|
||||
return [msg.ChildCollection(x + '/')
|
||||
for x in list_switches(configmanager)]
|
||||
else:
|
||||
return _handle_neighbor_query(pathcomponents[2:], configmanager)
|
||||
return [msg.ChildCollection('macs/')]
|
||||
elif len(pathcomponents) == 2:
|
||||
if pathcomponents[-1] == 'macs':
|
||||
return [msg.ChildCollection(x) for x in (# 'by-node/',
|
||||
'by-mac/', 'by-switch/',
|
||||
'rescan')]
|
||||
elif pathcomponents[-1] == 'neighbors':
|
||||
return [msg.ChildCollection('by-switch/')]
|
||||
else:
|
||||
raise exc.NotFoundException(
|
||||
'Unknown networking resource {0}'.format(pathcomponents[-1]))
|
||||
return [msg.ChildCollection(x) for x in (# 'by-node/',
|
||||
'by-mac/', 'by-switch/',
|
||||
'rescan')]
|
||||
if False and pathcomponents[2] == 'by-node':
|
||||
# TODO: should be list of node names, and then under that 'by-mac'
|
||||
if len(pathcomponents) == 3:
|
||||
return [msg.ChildCollection(x.replace(':', '-'))
|
||||
for x in util.natural_sort(list(_nodesbymac))]
|
||||
for x in sorted(list(_nodesbymac))]
|
||||
elif len(pathcomponents) == 4:
|
||||
macaddr = pathcomponents[-1].replace('-', ':')
|
||||
return dump_macinfo(macaddr)
|
||||
@@ -430,7 +416,8 @@ def handle_read_api_request(pathcomponents, configmanager):
|
||||
elif pathcomponents[2] == 'by-switch':
|
||||
if len(pathcomponents) == 3:
|
||||
return [msg.ChildCollection(x + '/')
|
||||
for x in list_switches(configmanager)]
|
||||
for x in sorted(list(_macsbyswitch))]
|
||||
|
||||
if len(pathcomponents) == 4:
|
||||
return [msg.ChildCollection('by-port/')]
|
||||
if len(pathcomponents) == 5:
|
||||
@@ -439,7 +426,7 @@ def handle_read_api_request(pathcomponents, configmanager):
|
||||
raise exc.NotFoundException(
|
||||
'No known macs for switch {0}'.format(switchname))
|
||||
return [msg.ChildCollection(x.replace('/', '-') + '/')
|
||||
for x in util.natural_sort(list(_macsbyswitch[switchname]))]
|
||||
for x in sorted(list(_macsbyswitch[switchname]))]
|
||||
if len(pathcomponents) == 6:
|
||||
return [msg.ChildCollection('by-mac/')]
|
||||
if len(pathcomponents) == 7:
|
||||
@@ -468,7 +455,7 @@ def dump_macinfo(macaddr):
|
||||
raise exc.NotFoundException(
|
||||
'{0} not found in mac table of '
|
||||
'any known switches'.format(macaddr))
|
||||
return _dump_locations(info, macaddr, _nodesbymac.get(macaddr, (None,))[0])
|
||||
return _dump_locations(info, macaddr, _nodesbymac.get(macaddr, None))
|
||||
|
||||
|
||||
def rescan(cfg):
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
import confluent.util as util
|
||||
|
||||
|
||||
def get_switchcreds(configmanager, switches):
|
||||
switchcfg = configmanager.get_node_attributes(
|
||||
switches, ('secret.hardwaremanagementuser', 'secret.snmpcommunity',
|
||||
'secret.hardwaremanagementpassword'), decrypt=True)
|
||||
switchauth = []
|
||||
for switch in switches:
|
||||
if not switch:
|
||||
continue
|
||||
switchparms = switchcfg.get(switch, {})
|
||||
user = None
|
||||
password = switchparms.get(
|
||||
'secret.snmpcommunity', {}).get('value', None)
|
||||
if not password:
|
||||
password = switchparms.get(
|
||||
'secret.hardwaremanagementpassword', {}).get('value',
|
||||
'public')
|
||||
user = switchparms.get(
|
||||
'secret.hardwaremanagementuser', {}).get('value', None)
|
||||
if not user:
|
||||
user = None
|
||||
switchauth.append((switch, password, user))
|
||||
return switchauth
|
||||
|
||||
|
||||
def list_switches(configmanager):
|
||||
nodelocations = configmanager.get_node_attributes(
|
||||
configmanager.list_nodes(), ('net*.switch', 'net*.switchport'))
|
||||
switches = set([])
|
||||
for node in nodelocations:
|
||||
cfg = nodelocations[node]
|
||||
for attr in cfg:
|
||||
if not attr.endswith('.switch') or 'value' not in cfg[attr]:
|
||||
continue
|
||||
curswitch = cfg[attr].get('value', None)
|
||||
if not curswitch:
|
||||
continue
|
||||
switches.add(curswitch)
|
||||
return util.natural_sort(switches)
|
||||
|
||||
|
||||
def get_portnamemap(conn):
|
||||
ifnamemap = {}
|
||||
havenames = False
|
||||
for vb in conn.walk('1.3.6.1.2.1.31.1.1.1.1'):
|
||||
ifidx, ifname = vb
|
||||
if not ifname:
|
||||
continue
|
||||
havenames = True
|
||||
ifidx = int(str(ifidx).rsplit('.', 1)[1])
|
||||
ifnamemap[ifidx] = str(ifname)
|
||||
if not havenames:
|
||||
for vb in conn.walk('1.3.6.1.2.1.2.2.1.2'):
|
||||
ifidx, ifname = vb
|
||||
ifidx = int(str(ifidx).rsplit('.', 1)[1])
|
||||
ifnamemap[ifidx] = str(ifname)
|
||||
return ifnamemap
|
||||
@@ -38,9 +38,6 @@ _parser = pp.nestedExpr(content=_grammar)
|
||||
_numextractor = pp.OneOrMore(pp.Word(pp.alphas + '-') | pp.Word(pp.nums))
|
||||
|
||||
numregex = re.compile('([0-9]+)')
|
||||
|
||||
lastnoderange = None
|
||||
|
||||
def humanify_nodename(nodename):
|
||||
"""Analyzes nodename in a human way to enable natural sort
|
||||
|
||||
@@ -51,42 +48,6 @@ def humanify_nodename(nodename):
|
||||
for text in re.split(numregex, nodename)]
|
||||
|
||||
|
||||
class ReverseNodeRange(object):
|
||||
"""Abbreviate a set of nodes to a shorter noderange representation
|
||||
|
||||
:param nodes: List of nodes as a list, tuple, etc.
|
||||
:param config: Config manager
|
||||
"""
|
||||
|
||||
def __init__(self, nodes, config):
|
||||
self.cfm = config
|
||||
self.nodes = set(nodes)
|
||||
|
||||
|
||||
@property
|
||||
def noderange(self):
|
||||
subsetgroups = []
|
||||
for group in self.cfm.get_groups(sizesort=True):
|
||||
if lastnoderange:
|
||||
for nr in lastnoderange:
|
||||
if lastnoderange[nr] - self.nodes:
|
||||
continue
|
||||
if self.nodes - lastnoderange[nr]:
|
||||
continue
|
||||
return nr
|
||||
nl = set(
|
||||
self.cfm.get_nodegroup_attributes(group).get('nodes', []))
|
||||
if len(nl) > len(self.nodes) or not nl:
|
||||
continue
|
||||
if not nl - self.nodes:
|
||||
subsetgroups.append(group)
|
||||
self.nodes -= nl
|
||||
if not self.nodes:
|
||||
break
|
||||
return ','.join(sorted(subsetgroups) + sorted(self.nodes))
|
||||
|
||||
|
||||
|
||||
# TODO: pagination operators <pp.nums and >pp.nums for begin and end respective
|
||||
class NodeRange(object):
|
||||
"""Iterate over a noderange
|
||||
@@ -96,7 +57,6 @@ class NodeRange(object):
|
||||
"""
|
||||
|
||||
def __init__(self, noderange, config=None):
|
||||
global lastnoderange
|
||||
self.beginpage = None
|
||||
self.endpage = None
|
||||
self.cfm = config
|
||||
@@ -110,7 +70,6 @@ class NodeRange(object):
|
||||
self._noderange = set(self.cfm.list_nodes())
|
||||
else:
|
||||
self._noderange = self._evaluate(elements)
|
||||
lastnoderange = {noderange: set(self._noderange)}
|
||||
|
||||
@property
|
||||
def nodes(self):
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# Copyright 2014 IBM Corporation
|
||||
# Copyright 2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -16,7 +15,6 @@
|
||||
import confluent.exceptions as exc
|
||||
import confluent.messages as msg
|
||||
import confluent.config.attributes as allattributes
|
||||
import confluent.util as util
|
||||
|
||||
|
||||
def retrieve(nodes, element, configmanager, inputdata):
|
||||
@@ -28,13 +26,7 @@ def retrieve(nodes, element, configmanager, inputdata):
|
||||
|
||||
|
||||
def retrieve_nodegroup(nodegroup, element, configmanager, inputdata):
|
||||
try:
|
||||
grpcfg = configmanager.get_nodegroup_attributes(nodegroup)
|
||||
except KeyError:
|
||||
if not configmanager.is_nodegroup(nodegroup):
|
||||
raise exc.NotFoundException(
|
||||
'Invalid nodegroup: {0} not found'.format(nodegroup))
|
||||
raise
|
||||
grpcfg = configmanager.get_nodegroup_attributes(nodegroup)
|
||||
if element == 'all':
|
||||
theattrs = set(allattributes.node).union(set(grpcfg))
|
||||
theattrs.add('nodes')
|
||||
@@ -176,16 +168,8 @@ def _expand_expression(nodes, configmanager, inputdata):
|
||||
expression = expression['expression']
|
||||
if type(expression) is dict:
|
||||
expression = expression['expression']
|
||||
pernodeexpressions = {}
|
||||
try:
|
||||
for expanded in configmanager.expand_attrib_expression(nodes,
|
||||
expression):
|
||||
pernodeexpressions[expanded[0]] = expanded[1]
|
||||
for node in util.natural_sort(pernodeexpressions):
|
||||
yield msg.KeyValueData({'value': pernodeexpressions[node]}, node)
|
||||
except ValueError as e:
|
||||
raise exc.InvalidArgumentException(str(e))
|
||||
|
||||
for expanded in configmanager.expand_attrib_expression(nodes, expression):
|
||||
yield msg.KeyValueData({'value': expanded[1]}, expanded[0])
|
||||
|
||||
|
||||
def create(nodes, element, configmanager, inputdata):
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
# Copyright 2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
import confluent.core as core
|
||||
import confluent.messages as msg
|
||||
import pyghmi.exceptions as pygexc
|
||||
|
||||
def update(nodes, element, configmanager, inputdata):
|
||||
emebs = configmanager.get_node_attributes(
|
||||
nodes, (u'enclosure.manager', u'enclosure.bay'))
|
||||
for node in nodes:
|
||||
try:
|
||||
em = emebs[node]['enclosure.manager']['value']
|
||||
eb = emebs[node]['enclosure.bay']['value']
|
||||
except KeyError:
|
||||
yield msg.ConfluentNodeError(
|
||||
node,
|
||||
'Reseat is only supported on servers in an enclosure, and '
|
||||
'with enclosure.manager and enclosure.bay defined')
|
||||
continue
|
||||
try:
|
||||
for rsp in core.handle_path(
|
||||
'/nodes/{0}/_enclosure/reseat_bay'.format(em),
|
||||
'update', configmanager,
|
||||
inputdata={'reseat': int(eb)}):
|
||||
yield rsp
|
||||
except pygexc.UnsupportedFunctionality as uf:
|
||||
yield msg.ConfluentNodeError(node, str(uf))
|
||||
@@ -1,5 +1,5 @@
|
||||
# Copyright 2014 IBM Corporation
|
||||
# Copyright 2015-2017 Lenovo
|
||||
# Copyright 2015-2016 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -15,7 +15,6 @@
|
||||
|
||||
import atexit
|
||||
import confluent.exceptions as exc
|
||||
import confluent.firmwaremanager as firmwaremanager
|
||||
import confluent.interface.console as conapi
|
||||
import confluent.messages as msg
|
||||
import confluent.util as util
|
||||
@@ -30,30 +29,9 @@ import pyghmi.exceptions as pygexc
|
||||
console = eventlet.import_patched('pyghmi.ipmi.console')
|
||||
ipmicommand = eventlet.import_patched('pyghmi.ipmi.command')
|
||||
import socket
|
||||
import ssl
|
||||
|
||||
|
||||
# There is something not right with the RLocks used in pyghmi when
|
||||
# eventlet comes into play. It seems like sometimes on acquire,
|
||||
# it calls _get_ident and it isn't the id(greenlet) and so
|
||||
# a thread deadlocks itself due to identity crisis?
|
||||
# However, since we are not really threaded, the operations being protected
|
||||
# are not actually dangerously multiplexed... so we can replace with
|
||||
# a null context manager for now
|
||||
class NullLock(object):
|
||||
|
||||
def donothing(self, *args, **kwargs):
|
||||
return 1
|
||||
|
||||
__enter__ = donothing
|
||||
__exit__ = donothing
|
||||
acquire = donothing
|
||||
release = donothing
|
||||
|
||||
console.session.select = eventlet.green.select
|
||||
console.session.threading = eventlet.green.threading
|
||||
console.session.WAITING_SESSIONS = NullLock()
|
||||
console.session.KEEPALIVE_SESSIONS = NullLock()
|
||||
console.session.socket.getaddrinfo = eventlet.support.greendns.getaddrinfo
|
||||
|
||||
|
||||
@@ -70,8 +48,7 @@ _ipmiwaiters = []
|
||||
|
||||
sensor_categories = {
|
||||
'temperature': frozenset(['Temperature']),
|
||||
'energy': frozenset(['Energy']),
|
||||
'power': frozenset(['Power', 'Current']),
|
||||
'power': frozenset(['Current', 'Battery']),
|
||||
'fans': frozenset(['Fan', 'Cooling Device']),
|
||||
}
|
||||
|
||||
@@ -136,7 +113,6 @@ class IpmiCommandWrapper(ipmicommand.Command):
|
||||
'secret.hardwaremanagementpassword', 'secret.ipmikg',
|
||||
'hardwaremanagement.manager'), self._attribschanged)
|
||||
super(self.__class__, self).__init__(**kwargs)
|
||||
self.setup_confluent_keyhandler()
|
||||
|
||||
def setup_confluent_keyhandler(self):
|
||||
self.register_key_handler(util.TLSCertVerifier(
|
||||
@@ -269,7 +245,6 @@ class IpmiConsole(conapi.Console):
|
||||
password=self.password,
|
||||
kg=self.kg, force=True,
|
||||
iohandler=self.handle_data)
|
||||
self.solconnection.outputlock = NullLock()
|
||||
while not self.solconnection.connected and not self.broken:
|
||||
w = eventlet.event.Event()
|
||||
_ipmiwaiters.append(w)
|
||||
@@ -318,8 +293,6 @@ def perform_requests(operator, nodes, element, cfg, inputdata):
|
||||
datum = resultdata.get(timeout=10)
|
||||
while datum:
|
||||
if datum != 'Done':
|
||||
if isinstance(datum, Exception):
|
||||
raise datum
|
||||
yield datum
|
||||
datum = resultdata.get_nowait()
|
||||
except queue.Empty:
|
||||
@@ -351,19 +324,9 @@ def perform_request(operator, node, element,
|
||||
raise
|
||||
except exc.TargetEndpointUnreachable as tu:
|
||||
results.put(msg.ConfluentTargetTimeout(node, str(tu)))
|
||||
except ssl.SSLEOFError:
|
||||
results.put(msg.ConfluentNodeError(
|
||||
self.node, 'Unable to communicate with the https server on '
|
||||
'the target BMC'))
|
||||
except exc.PubkeyInvalid:
|
||||
results.put(msg.ConfluentNodeError(
|
||||
node,
|
||||
'Mismatch detected between target certificate fingerprint '
|
||||
'and pubkeys.tls_hardwaremanager attribute'))
|
||||
except pygexc.InvalidParameterValue as e:
|
||||
results.put(msg.ConfluentNodeError(node, str(e)))
|
||||
except Exception as e:
|
||||
results.put(e)
|
||||
results.put(msg.ConfluentNodeError(
|
||||
node, 'IPMI PluginException (see stderr log): ' + str(e)))
|
||||
raise
|
||||
finally:
|
||||
results.put('Done')
|
||||
@@ -387,7 +350,6 @@ class IpmiHandler(object):
|
||||
connparams = get_conn_params(node, self.cfg)
|
||||
self.ipmicmd = None
|
||||
self.inputdata = inputdata
|
||||
self.tenant = cfg.tenant
|
||||
tenant = cfg.tenant
|
||||
if ((node, tenant) not in persistent_ipmicmds or
|
||||
not persistent_ipmicmds[(node, tenant)].ipmi_session.logged):
|
||||
@@ -401,7 +363,6 @@ class IpmiHandler(object):
|
||||
userid=connparams['username'],
|
||||
password=connparams['passphrase'], kg=connparams['kg'],
|
||||
port=connparams['port'], onlogon=self.logged)
|
||||
|
||||
ipmisess = persistent_ipmicmds[(node, tenant)].ipmi_session
|
||||
begin = util.monotonic_time()
|
||||
while ((not (self.broken or self.loggedin)) and
|
||||
@@ -427,6 +388,7 @@ class IpmiHandler(object):
|
||||
else:
|
||||
self.ipmicmd = ipmicmd
|
||||
self.loggedin = True
|
||||
self.ipmicmd.setup_confluent_keyhandler()
|
||||
|
||||
def handle_request(self):
|
||||
if self.broken:
|
||||
@@ -445,8 +407,6 @@ class IpmiHandler(object):
|
||||
raise Exception(self.error)
|
||||
if self.element == ['power', 'state']:
|
||||
self.power()
|
||||
elif self.element == ['_enclosure', 'reseat_bay']:
|
||||
self.reseat_bay()
|
||||
elif self.element == ['boot', 'nextdevice']:
|
||||
self.bootdevice()
|
||||
elif self.element == ['health', 'hardware']:
|
||||
@@ -457,18 +417,8 @@ class IpmiHandler(object):
|
||||
self.handle_sensors()
|
||||
elif self.element[0] == 'configuration':
|
||||
self.handle_configuration()
|
||||
elif self.element[:3] == ['inventory', 'firmware', 'updates']:
|
||||
self.handle_update()
|
||||
elif self.element[0] == 'inventory':
|
||||
self.handle_inventory()
|
||||
elif self.element == ['media', 'attach']:
|
||||
self.handle_attach_media()
|
||||
elif self.element == ['media', 'detach']:
|
||||
self.handle_detach_media()
|
||||
elif self.element == ['media', 'uploads']:
|
||||
self.handle_media_upload()
|
||||
elif self.element == ['media', 'current']:
|
||||
self.handle_list_media()
|
||||
elif self.element == ['events', 'hardware', 'log']:
|
||||
self.do_eventlog()
|
||||
elif self.element == ['events', 'hardware', 'decode']:
|
||||
@@ -478,35 +428,6 @@ class IpmiHandler(object):
|
||||
else:
|
||||
raise Exception('Not Implemented')
|
||||
|
||||
def handle_update(self):
|
||||
u = firmwaremanager.Updater(self.node, self.ipmicmd.update_firmware,
|
||||
self.inputdata.filename, self.tenant,
|
||||
bank=self.inputdata.bank)
|
||||
self.output.put(
|
||||
msg.CreatedResource(
|
||||
'nodes/{0}/inventory/firmware/updates/active/{1}'.format(
|
||||
self.node, u.name)))
|
||||
|
||||
def handle_media_upload(self):
|
||||
u = firmwaremanager.Updater(self.node, self.ipmicmd.upload_media,
|
||||
self.inputdata.filename, self.tenant,
|
||||
type='mediaupload')
|
||||
self.output.put(msg.CreatedResource(
|
||||
'nodes/{0}/media/uploads/{1}'.format(self.node, u.name)))
|
||||
|
||||
def handle_attach_media(self):
|
||||
try:
|
||||
self.ipmicmd.attach_remote_media(self.inputdata.filename)
|
||||
except pygexc.UnsupportedFunctionality as uf:
|
||||
self.output.put(msg.ConfluentNodeError(self.node, str(uf)))
|
||||
|
||||
def handle_detach_media(self):
|
||||
self.ipmicmd.detach_remote_media()
|
||||
|
||||
def handle_list_media(self):
|
||||
for media in self.ipmicmd.list_media():
|
||||
self.output.put(msg.Media(self.node, media))
|
||||
|
||||
def handle_configuration(self):
|
||||
if self.element[1:3] == ['management_controller', 'alerts']:
|
||||
return self.handle_alerts()
|
||||
@@ -522,8 +443,6 @@ class IpmiHandler(object):
|
||||
return self.handle_domain_name()
|
||||
elif self.element[1:3] == ['management_controller', 'ntp']:
|
||||
return self.handle_ntp()
|
||||
elif self.element[1:3] == ['system', 'all']:
|
||||
return self.handle_sysconfig()
|
||||
raise Exception('Not implemented')
|
||||
|
||||
def decode_alert(self):
|
||||
@@ -746,27 +665,10 @@ class IpmiHandler(object):
|
||||
|
||||
def read_firmware(self, component):
|
||||
items = []
|
||||
errorneeded = False
|
||||
try:
|
||||
complist = () if component == 'all' else (component,)
|
||||
for id, data in self.ipmicmd.get_firmware(complist):
|
||||
if (component in ('core', 'all') or
|
||||
component == simplify_name(id)):
|
||||
items.append({id: data})
|
||||
except ssl.SSLEOFError:
|
||||
errorneeded = msg.ConfluentNodeError(
|
||||
self.node, 'Unable to communicate with the https server on '
|
||||
'the target BMC while trying to read extended '
|
||||
'information')
|
||||
except exc.PubkeyInvalid:
|
||||
errorneeded = msg.ConfluentNodeError(
|
||||
self.node,
|
||||
'Extended information unavailable, mismatch detected between '
|
||||
'target certificate fingerprint and '
|
||||
'pubkeys.tls_hardwaremanager attribute')
|
||||
for id, data in self.ipmicmd.get_firmware():
|
||||
if component == 'all' or component == simplify_name(id):
|
||||
items.append({id: data})
|
||||
self.output.put(msg.Firmware(items, self.node))
|
||||
if errorneeded:
|
||||
self.output.put(errorneeded)
|
||||
|
||||
def handle_inventory(self):
|
||||
if self.element[1] == 'firmware':
|
||||
@@ -794,47 +696,32 @@ class IpmiHandler(object):
|
||||
self.output.put(msg.LEDStatus(led_categories, self.node))
|
||||
|
||||
def read_inventory(self, component):
|
||||
errorneeded = False
|
||||
try:
|
||||
invitems = []
|
||||
if component == 'all':
|
||||
for invdata in self.ipmicmd.get_inventory():
|
||||
if invdata[1] is None:
|
||||
newinf = {'present': False, 'information': None}
|
||||
else:
|
||||
sanitize_invdata(invdata[1])
|
||||
newinf = {'present': True, 'information': invdata[1]}
|
||||
newinf['name'] = invdata[0]
|
||||
invitems.append(newinf)
|
||||
else:
|
||||
self.make_inventory_map()
|
||||
compname = self.invmap.get(component, None)
|
||||
if compname is None:
|
||||
self.output.put(msg.ConfluentTargetNotFound())
|
||||
return
|
||||
invdata = self.ipmicmd.get_inventory_of_component(compname)
|
||||
if invdata is None:
|
||||
invitems = []
|
||||
if component == 'all':
|
||||
for invdata in self.ipmicmd.get_inventory():
|
||||
if invdata[1] is None:
|
||||
newinf = {'present': False, 'information': None}
|
||||
else:
|
||||
sanitize_invdata(invdata)
|
||||
newinf = {'present': True, 'information': invdata}
|
||||
newinf['name'] = compname
|
||||
sanitize_invdata(invdata[1])
|
||||
newinf = {'present': True, 'information': invdata[1]}
|
||||
newinf['name'] = invdata[0]
|
||||
invitems.append(newinf)
|
||||
except ssl.SSLEOFError:
|
||||
errorneeded = msg.ConfluentNodeError(
|
||||
self.node, 'Unable to communicate with the https server on '
|
||||
'the target BMC while trying to read extended '
|
||||
'information')
|
||||
except exc.PubkeyInvalid:
|
||||
errorneeded = msg.ConfluentNodeError(
|
||||
self.node,
|
||||
'Extended information unavailable, mismatch detected between '
|
||||
'target certificate fingerprint and '
|
||||
'pubkeys.tls_hardwaremanager attribute')
|
||||
else:
|
||||
self.make_inventory_map()
|
||||
compname = self.invmap.get(component, None)
|
||||
if compname is None:
|
||||
self.output.put(msg.ConfluentTargetNotFound())
|
||||
return
|
||||
invdata = self.ipmicmd.get_inventory_of_component(compname)
|
||||
if invdata is None:
|
||||
newinf = {'present': False, 'information': None}
|
||||
else:
|
||||
sanitize_invdata(invdata)
|
||||
newinf = {'present': True, 'information': invdata}
|
||||
newinf['name'] = compname
|
||||
invitems.append(newinf)
|
||||
newinvdata = {'inventory': invitems}
|
||||
self.output.put(msg.KeyValueData(newinvdata, self.node))
|
||||
if errorneeded:
|
||||
self.output.put(errorneeded)
|
||||
|
||||
def handle_sensors(self):
|
||||
if self.element[-1] == '':
|
||||
@@ -889,14 +776,6 @@ class IpmiHandler(object):
|
||||
else:
|
||||
raise exc.InvalidArgumentException('health is read-only')
|
||||
|
||||
def reseat_bay(self):
|
||||
bay = self.inputdata.inputbynode[self.node]
|
||||
try:
|
||||
self.ipmicmd.reseat_bay(bay)
|
||||
self.output.put(msg.ReseatResult(self.node, 'success'))
|
||||
except pygexc.UnsupportedFunctionality as uf:
|
||||
self.output.put(uf)
|
||||
|
||||
def bootdevice(self):
|
||||
if 'read' == self.op:
|
||||
bootdev = self.ipmicmd.get_bootdev()
|
||||
@@ -996,14 +875,6 @@ class IpmiHandler(object):
|
||||
self.ipmicmd.set_domain_name(dn)
|
||||
return
|
||||
|
||||
def handle_sysconfig(self):
|
||||
if 'read' == self.op:
|
||||
self.output.put(msg.ConfigSet(
|
||||
self.node, self.ipmicmd.get_system_configuration()))
|
||||
elif 'update' == self.op:
|
||||
self.ipmicmd.set_system_configuration(
|
||||
self.inputdata.get_attributes(self.node))
|
||||
|
||||
def handle_ntp(self):
|
||||
if self.element[3] == 'enabled':
|
||||
if 'read' == self.op:
|
||||
@@ -1092,22 +963,9 @@ def update(nodes, element, configmanager, inputdata):
|
||||
|
||||
def retrieve(nodes, element, configmanager, inputdata):
|
||||
initthread()
|
||||
if '/'.join(element).startswith('inventory/firmware/updates/active'):
|
||||
return firmwaremanager.list_updates(nodes, configmanager.tenant,
|
||||
element)
|
||||
elif '/'.join(element).startswith('media/uploads'):
|
||||
return firmwaremanager.list_updates(nodes, configmanager.tenant,
|
||||
element, 'mediaupload')
|
||||
else:
|
||||
return perform_requests('read', nodes, element, configmanager, inputdata)
|
||||
return perform_requests('read', nodes, element, configmanager, inputdata)
|
||||
|
||||
def delete(nodes, element, configmanager, inputdata):
|
||||
initthread()
|
||||
if '/'.join(element).startswith('inventory/firmware/updates/active'):
|
||||
return firmwaremanager.remove_updates(nodes, configmanager.tenant,
|
||||
element)
|
||||
elif '/'.join(element).startswith('media/uploads'):
|
||||
return firmwaremanager.remove_updates(nodes, configmanager.tenant,
|
||||
element, type='mediaupload')
|
||||
return perform_requests(
|
||||
'delete', nodes, element, configmanager, inputdata)
|
||||
|
||||
@@ -59,9 +59,8 @@ class Session(object):
|
||||
# SNMP v2c
|
||||
self.authdata = snmp.CommunityData(secret, mpModel=1)
|
||||
else:
|
||||
self.authdata = snmp.UsmUserData(
|
||||
username, authKey=secret, privKey=secret,
|
||||
authProtocol=snmp.usmHMACSHAAuthProtocol)
|
||||
self.authdata = snmp.UsmUserData(username, authKey=secret,
|
||||
privKey=secret)
|
||||
self.eng = snmp.SnmpEngine()
|
||||
|
||||
def walk(self, oid):
|
||||
|
||||
@@ -22,8 +22,6 @@ import confluent.log as log
|
||||
import hashlib
|
||||
import netifaces
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
import struct
|
||||
|
||||
|
||||
@@ -33,22 +31,13 @@ def list_interface_indexes():
|
||||
# For now be linux specific
|
||||
try:
|
||||
for iface in os.listdir('/sys/class/net/'):
|
||||
if not os.path.exists('/sys/class/net/{0}/ifindex'.format(iface)):
|
||||
continue
|
||||
ifile = open('/sys/class/net/{0}/ifindex'.format(iface), 'r')
|
||||
intidx = int(ifile.read())
|
||||
ifile.close()
|
||||
yield intidx
|
||||
except (IOError, OSError):
|
||||
# Probably situation is non-Linux, just do limited support for
|
||||
# such platforms until other people come along
|
||||
for iface in netifaces.interfaces():
|
||||
addrinfo = netifaces.ifaddresses(iface).get(socket.AF_INET6, [])
|
||||
for addr in addrinfo:
|
||||
v6addr = addr.get('addr', '').partition('%')[2]
|
||||
if v6addr:
|
||||
yield(int(v6addr))
|
||||
break
|
||||
# such platforms until other people come alonge
|
||||
return
|
||||
|
||||
|
||||
@@ -100,11 +89,9 @@ def monotonic_time():
|
||||
|
||||
|
||||
def get_fingerprint(certificate, algo='sha512'):
|
||||
if algo == 'sha256':
|
||||
return 'sha256$' + hashlib.sha256(certificate).hexdigest()
|
||||
elif algo == 'sha512':
|
||||
return 'sha512$' + hashlib.sha512(certificate).hexdigest()
|
||||
raise Exception('Unsupported fingerprint algorithm ' + algo)
|
||||
if algo != 'sha512':
|
||||
raise Exception("TODO: Non-sha512")
|
||||
return 'sha512$' + hashlib.sha512(certificate).hexdigest()
|
||||
|
||||
|
||||
def cert_matches(fingerprint, certificate):
|
||||
@@ -112,8 +99,8 @@ def cert_matches(fingerprint, certificate):
|
||||
return False
|
||||
algo, _, fp = fingerprint.partition('$')
|
||||
newfp = None
|
||||
if algo in ('sha512', 'sha256'):
|
||||
newfp = get_fingerprint(certificate, algo)
|
||||
if algo == 'sha512':
|
||||
newfp = get_fingerprint(certificate)
|
||||
return newfp and fingerprint == newfp
|
||||
|
||||
|
||||
@@ -124,11 +111,11 @@ class TLSCertVerifier(object):
|
||||
self.fieldname = fieldname
|
||||
|
||||
def verify_cert(self, certificate):
|
||||
fingerprint = get_fingerprint(certificate)
|
||||
storedprint = self.cfm.get_node_attributes(self.node, (self.fieldname,)
|
||||
)
|
||||
if (self.fieldname not in storedprint[self.node] or
|
||||
storedprint[self.node][self.fieldname]['value'] == ''):
|
||||
# no stored value, check policy for next action
|
||||
if self.fieldname not in storedprint[self.node]: # no stored value, check
|
||||
# policy for next action
|
||||
newpolicy = self.cfm.get_node_attributes(self.node,
|
||||
('pubkeys.addpolicy',))
|
||||
if ('pubkeys.addpolicy' in newpolicy[self.node] and
|
||||
@@ -136,46 +123,19 @@ class TLSCertVerifier(object):
|
||||
newpolicy[self.node]['pubkeys.addpolicy']['value'] == 'manual'):
|
||||
# manual policy means always raise unless a match is set
|
||||
# manually
|
||||
fingerprint = get_fingerprint(certificate, 'sha256')
|
||||
raise cexc.PubkeyInvalid('New certificate detected',
|
||||
certificate, fingerprint,
|
||||
self.fieldname, 'newkey')
|
||||
# since the policy is not manual, go ahead and add new key
|
||||
# after logging to audit log
|
||||
fingerprint = get_fingerprint(certificate, 'sha256')
|
||||
auditlog = log.Logger('audit')
|
||||
auditlog.log({'node': self.node, 'event': 'certautoadd',
|
||||
'fingerprint': fingerprint})
|
||||
self.cfm.set_node_attributes(
|
||||
{self.node: {self.fieldname: fingerprint}})
|
||||
return True
|
||||
elif cert_matches(storedprint[self.node][self.fieldname]['value'],
|
||||
certificate):
|
||||
elif storedprint[self.node][self.fieldname]['value'] == fingerprint:
|
||||
return True
|
||||
fingerprint = get_fingerprint(certificate, 'sha256')
|
||||
raise cexc.PubkeyInvalid(
|
||||
'Mismatched certificate detected', certificate, fingerprint,
|
||||
self.fieldname, 'mismatch')
|
||||
|
||||
numregex = re.compile('([0-9]+)')
|
||||
|
||||
def naturalize_string(key):
|
||||
"""Analyzes string in a human way to enable natural sort
|
||||
|
||||
:param nodename: The node name to analyze
|
||||
:returns: A structure that can be consumed by 'sorted'
|
||||
"""
|
||||
return [int(text) if text.isdigit() else text.lower()
|
||||
for text in re.split(numregex, key)]
|
||||
|
||||
def natural_sort(iterable):
|
||||
"""Return a sort using natural sort if possible
|
||||
|
||||
:param iterable:
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
return sorted(iterable, key=naturalize_string)
|
||||
except TypeError:
|
||||
# The natural sort attempt failed, fallback to ascii sort
|
||||
return sorted(iterable)
|
||||
|
||||
@@ -12,7 +12,7 @@ Group: Development/Libraries
|
||||
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot
|
||||
Prefix: %{_prefix}
|
||||
BuildArch: noarch
|
||||
Requires: python-pyghmi >= 1.0.34, python-eventlet, python-greenlet, python-crypto >= 2.6.1, confluent_client, pyparsing, python-paramiko, python-dns, python-netifaces, python2-pyasn1 >= 0.2.3, python-pysnmp >= 4.3.4, python-pyte, python-lxml, python-eficompressor
|
||||
Requires: python-pyghmi, python-eventlet, python-greenlet, python-crypto >= 2.6.1, confluent_client, pyparsing, python-paramiko, python-dns, python-netifaces, python2-pyasn1, python-pysnmp, python-pyte
|
||||
Vendor: Jarrod Johnson <jjohnson2@lenovo.com>
|
||||
Url: http://xcat.sf.net/
|
||||
|
||||
@@ -33,12 +33,7 @@ done
|
||||
grep -v confluent/__init__.py INSTALLED_FILES.bare > INSTALLED_FILES
|
||||
cat INSTALLED_FILES
|
||||
|
||||
%triggerin -- python-pyghmi
|
||||
if [ -x /usr/bin/systemctl ]; then /usr/bin/systemctl try-restart confluent >& /dev/null; fi
|
||||
true
|
||||
|
||||
%post
|
||||
sysctl -p /usr/lib/sysctl.d/confluent.conf >& /dev/null
|
||||
if [ -x /usr/bin/systemctl ]; then /usr/bin/systemctl try-restart confluent >& /dev/null; fi
|
||||
true
|
||||
|
||||
|
||||
@@ -19,28 +19,12 @@
|
||||
# Note that this script has a high chance of breaking confluent, so
|
||||
# do not be surprised if confluent crashes as you exit...
|
||||
|
||||
import atexit
|
||||
import os
|
||||
import select
|
||||
import socket
|
||||
import readline
|
||||
import sys
|
||||
import threading
|
||||
|
||||
historypath = os.path.expanduser("~/.confluentdbghistory")
|
||||
|
||||
def save_history():
|
||||
import readline
|
||||
try:
|
||||
readline.write_history_file(historypath)
|
||||
except:
|
||||
pass
|
||||
|
||||
if os.path.exists(historypath):
|
||||
readline.set_history_length(1000)
|
||||
readline.read_history_file(historypath)
|
||||
|
||||
atexit.register(save_history)
|
||||
readline.parse_and_bind('tab: complete')
|
||||
conn = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
conn.connect('/var/run/confluent/dbg.sock')
|
||||
@@ -61,10 +45,7 @@ class GetInput(threading.Thread):
|
||||
inputthread = GetInput()
|
||||
inputthread.start()
|
||||
while True:
|
||||
try:
|
||||
r, _, _ = select.select((conn,), (), (), 0.1)
|
||||
except select.error:
|
||||
continue
|
||||
r, _, _ = select.select((conn,), (), (), 0.1)
|
||||
if conn in r:
|
||||
sys.stdout.write(conn.recv(1))
|
||||
if pendingoutput is not None:
|
||||
|
||||
@@ -27,7 +27,7 @@ with open(sys.argv[1]) as tracefile:
|
||||
if currtrace not in threadtraces:
|
||||
threadtraces[currtrace] = 0
|
||||
threadtraces[currtrace] += 1
|
||||
currtrace = ''
|
||||
currtrace = line
|
||||
elif currtrace is not None:
|
||||
currtrace += line + '\n'
|
||||
for trace in sorted(threadtraces, key=lambda x: threadtraces[x]):
|
||||
|
||||
@@ -20,7 +20,6 @@ setup(
|
||||
'pyghmi>=0.6.5'],
|
||||
scripts=['bin/confluent', 'bin/confluentdbutil'],
|
||||
data_files=[('/etc/init.d', ['sysvinit/confluent']),
|
||||
('/usr/lib/sysctl.d', ['sysctl/confluent.conf']),
|
||||
('/usr/lib/systemd/system', ['systemd/confluent.service']),
|
||||
('/opt/confluent/lib/python/confluent/plugins/console/', [])],
|
||||
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
# Increase available receive buffers for discovery scans
|
||||
net.core.rmem_max = 2097152
|
||||
@@ -7,7 +7,6 @@ Type=forking
|
||||
PIDFile=/var/run/confluent/pid
|
||||
ExecStart=/opt/confluent/bin/confluent
|
||||
ExecStop=/opt/confluent/bin/confetty shutdown /
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
Reference in New Issue
Block a user