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

Compare commits

...

93 Commits

Author SHA1 Message Date
Jarrod Johnson 18b6398c64 Fix XCC discovery issues
A couple of issues could occur during discover that should be
stepped over.
2020-01-23 08:44:17 -05:00
Jarrod Johnson 47b68e4258 Fix dispatch of redfish and tsmsol
redfish and tsmsol was not properly marked as
needing dispatch.
2020-01-22 15:04:43 -05:00
Jarrod Johnson 79b6d099ab Fix attribute sort with python 3
For attributes without a sortid, sort was failing with python3
2020-01-22 14:24:58 -05:00
Jarrod Johnson f1e1d9804a Use python3 for Debian and Ubuntu except for wheezy 2020-01-14 09:52:16 -05:00
Jarrod Johnson 546296ce71 Have the get_webclient always return two elements
Some scenario existed where the function could return
None and this could break code expectations that it
always returned a tuple and the first value needs
to be checked for None.
2020-01-08 10:17:06 -05:00
Jarrod Johnson 954b2dd15c Fix minor formatting issues in source 2020-01-07 09:38:40 -05:00
Jarrod Johnson a7b11d1e15 Fix closing ipmi session when not mapped
When not mapped, a noisy KeyError was occuring.  Handle
it to avoid cluttering log if the work is not needed.
2019-12-10 14:38:12 -05:00
Jarrod Johnson 3660cf18cc Fix a number of issues
For one, there were still bytes v. str for python3 issues in the
certificate exception and credential handling for smm and generic
credential lookup.

There was a python2-ism in lldp that needed to be made 2/3 agnostic
with ord() of a 'bytes' member, converting to bytearray for
normalized behavior.

The discovery core had an issue with chained smms where a set
was used which cannot take a dict, and so it is converted to a list.

If a temporary password is used but the user did not provide a permanent
password that is viable, make the error more explicit.
2019-12-10 11:54:24 -05:00
Jarrod Johnson 078afaf7d7 Make SLP more tolerant of malformed data
Unrelated devices may present invalid data
to SLP queries.  Have these conditions better tolerated.
2019-12-06 11:24:30 -05:00
Jarrod Johnson 3ab1eb7f01 Add words to nodefirmware man page
ipmi versus redfish produces some different behaviors, document
some examples in the man page.
2019-12-04 10:14:23 -05:00
Jarrod Johnson fbfdc9322b Raise errors during connection
This improves error behavior when login action
encounters an error.
2019-12-04 10:06:18 -05:00
Jarrod Johnson ef68656bde Fix nodestorage handling of error
Error messages were not properly handled if specific nodes had issues.
Properly provide the error messages.

Additionally, clarify a common uselessly vague error.
2019-12-03 10:04:34 -05:00
Jarrod Johnson 0e04b853f6 Fix common usage of noderange alerts
This fixes the usual circumstance of not having keyed input data.
2019-11-21 16:42:03 -05:00
Jarrod Johnson 63256e37b6 Remove some uses of xrange
Not friendly to python 3
2019-11-21 16:03:48 -05:00
Jarrod Johnson a1b81e978b Pull in websocket client dependency
This enables tsmsol plugin
2019-11-20 15:25:11 -05:00
Jarrod Johnson 06c53e9d89 Make the websocket module optional
Have tsmsol gracefully degrade for Wheezy, where
it is not relevant to that platform.
2019-11-20 13:53:16 -05:00
Jarrod Johnson ab0168b2ec Update man page for nodesupport
Correct typo and clarify that plugins may customize
filenames.
2019-11-20 10:48:08 -05:00
Jarrod Johnson 473bb39c54 Fix packaging omission of console plugins 2019-11-20 10:04:38 -05:00
Jarrod Johnson a48ecd8c78 Add hardware and firmware info to CNOS
CNOS devices now have nodefirmware and nodeinventory.
2019-11-15 16:08:28 -05:00
Jarrod Johnson 30398fc434 More explicit follower health checking
Be more careful about collective member health assessment,
explicitly closing a follower when it has failure and prefacing
collective commands with a check prior to performing the actual
changes.
2019-11-11 10:49:25 -05:00
Jarrod Johnson 0ed7e4eac3 Merge branch 'master' of github.com:jjohnson42/confluent 2019-11-07 11:26:04 -05:00
Jarrod Johnson 0d2999c7a8 Properly relay errors on console start
Rather than always saying console.method is not set, relay
the str of the exception to give a more accurate info.
2019-11-07 11:25:49 -05:00
Jarrod Johnson 75010ac6f0 Guard against accidental node duplication
If two nodes have same bmc, block one of them to prevent massive
fluctuation back and forth.
2019-11-07 11:10:26 -05:00
Jarrod Johnson a748cc3032 Remove incorrect check for HOSTUNREACH
This seems incorrect and further redundant with other portions
of the code.  This was causing a 'has no len()' error when encountered.
2019-11-05 13:28:12 -05:00
Jarrod Johnson 28bb143880 Recognize drive in rebuilding state
The rebuilding state was not recognized, add that state.
2019-11-04 14:16:02 -05:00
Jarrod Johnson 4b6899d4af Mark node as 'dirty' if it's group is removed
Group removal would end up with the on-disk copy not reflecting
the node membership, even though in-memory copy is fine.
Fix by adding the missing dirty mark to the entry.
2019-11-04 13:43:29 -05:00
Jarrod Johnson a175fd7345 Allow autosuffix to change passed filename
The resultant file may deviate and thus the chown
must have the updated name.
2019-10-29 16:06:51 -04:00
Jarrod Johnson 12bd473c0d Remove stray debug output 2019-10-29 15:50:48 -04:00
Jarrod Johnson a967c224fe Add TSMSOL plugin
This enables ipmi-free access to TSM SOL console
2019-10-29 15:20:40 -04:00
Jarrod Johnson 72a5f37232 Fix mid-line command completion
Use COMP_CWORD instead of -1 to dynamically decide where to
do completion and what candidates to offer
2019-10-24 15:27:17 -04:00
Jarrod Johnson 5c081a8e2c Remove SYSV init script from RPM packages
All supported RPM distros are systemd now...
2019-10-24 12:16:17 -04:00
Jarrod Johnson c0b69f64b8 Error when noderange tries to use secret
secret. cannot be used in expressions, provide
an error in such a case.
2019-10-24 12:04:47 -04:00
Jarrod Johnson f7fe38f498 Fix problem with reassign and TSM 2019-10-24 10:29:58 -04:00
Jarrod Johnson 5ae949b4eb Add UUID to TSM discovery
The UUID is now available in redfish,
use that to get the UUID.
2019-10-24 10:23:59 -04:00
Jarrod Johnson cec6918c1a Support unsupported bad disks 2019-10-23 17:02:37 -04:00
Jarrod Johnson 3df7a36c48 Fix a number of issues with maxnodes
Some commands did not function correctly
after modifications.
2019-10-23 16:47:42 -04:00
Jarrod Johnson 559e96f7ff Fix location of calling the noderange check 2019-10-23 16:27:18 -04:00
Jarrod Johnson 6f568919e1 Correct instance callout
The wrong value was called out
2019-10-23 15:57:16 -04:00
Jarrod Johnson c441739f68 Try a less jarring exit strategy
Have client not provide a developer looking trace on abort.
2019-10-23 14:41:57 -04:00
Jarrod Johnson a43361bb98 Amend the prompt for single node prompting
Remove the trailing slash
2019-10-23 14:38:45 -04:00
Jarrod Johnson 620263db3e Add maxnodes argument to potentially risky commands
This uses the client maxnodes check to double check.  Useful
for clients that want to sanity check unexpectedly large numbers of
nodes.
2019-10-23 14:20:40 -04:00
Jarrod Johnson dd096104cc Provide mechanism to prompt on large noderanges
Some commands can add options to prompt, and can have that option be
only activated on noderanges over a
certain size.
2019-10-22 15:49:09 -04:00
Jarrod Johnson 191ae762ab Use autosuffix feature of pyghmi
This produces output that is more consistent with support by vendors.
2019-10-22 15:17:31 -04:00
Jarrod Johnson f866b7379c Always relax minimum password interval on auto change
The preconfig could interfere with normal operations.
2019-10-22 10:51:41 -04:00
Jarrod Johnson aab6160d49 Fix compatibility between new and old TSM firmware 2019-10-21 16:20:54 -04:00
Jarrod Johnson e1dffe7c3a Fix compatibility with newer TSM firmware
TSM stopped accepting url encoded, switch to json.  This should work
for older and newer firmware.
2019-10-21 14:39:27 -04:00
Jarrod Johnson 97ca6dc48e Provide more detail on leader when leader is lost 2019-10-21 13:55:43 -04:00
Jarrod Johnson 1d59e1da8c Fix unicode issues in HTML api explorer 2019-10-17 15:36:51 -04:00
Jarrod Johnson 7e6b4fa4d0 Fix HTML view with unicode 2019-10-17 11:25:03 -04:00
Jarrod Johnson 186e89cd87 Fix handling autosense with python3 2019-10-17 09:47:46 -04:00
Jarrod Johnson 73d6511d9e Use python3 explicitly for building source
The source distrobution on CentOS7 does not work well
with python2.  Using python3 to build the dist for python2
and python3 fixes issues with extra data being missed in
packaging.
2019-10-16 13:26:15 -04:00
Jarrod Johnson 065ee48325 Remove non-ascii from man page 2019-10-15 14:00:03 -04:00
Jarrod Johnson 4a35cc25f0 Fix TSM setup with python3 and custom credentials 2019-10-15 11:19:04 -04:00
Jarrod Johnson ceced11ae8 Fix python3 compatibility issue 2019-10-14 16:28:32 -04:00
Jarrod Johnson 62084b3605 More fixes for python3/2 compatibility 2019-10-14 15:22:44 -04:00
Jarrod Johnson a84b88e269 Fix mistake in the expression change 2019-10-14 15:02:45 -04:00
Jarrod Johnson fc626d36ba Fix greenlet 'isAlive'
There is no 'isAlive' in a greenlet.
2019-10-14 13:59:24 -04:00
Jarrod Johnson 606a308046 Fix quorum notification in mixed 2/3 collective 2019-10-14 10:33:13 -04:00
Jarrod Johnson bed0f76dba Fix nodeshell/nodersync python3 changes 2019-10-14 09:37:30 -04:00
Jarrod Johnson 8c4d04a7cd Try utf-8 and fallback to latin1 for 2/3 collective
datetime objects are latin1 encoded from python2.

Not an issue for pure python3 or python2 collectives, but
mitigating problems in the mixed environment
2019-10-11 13:58:30 -04:00
Jarrod Johnson bfbde0f7f3 Fix PXE protocol processing under python3 2019-10-11 13:36:16 -04:00
Jarrod Johnson ba96aea2a6 Fix SR635/SR655 discovery with python3 base 2019-10-11 13:35:46 -04:00
Jarrod Johnson d9f1d6c033 Fix user/password login in python3
The forced bytes of the function was incompatible
with str oriented logic later
2019-10-11 13:21:55 -04:00
Jarrod Johnson 1dac61adca Fix python3 in show user
Showing a user was not working in python3
2019-10-11 13:16:38 -04:00
Jarrod Johnson e008932389 Fix python3 collective leader
rpc needs to be fully binary
2019-10-11 11:11:27 -04:00
Jarrod Johnson b81e5fb3ce Fix python3 issues with noderun and nodeshell 2019-10-11 09:53:01 -04:00
Jarrod Johnson e2a08e7e73 Fix bug in initial recheck scheduling
This was causing rechecktime to not be set correctly.
This caused a trace in python3, but indicates an incorrect
behavior when running under python2
2019-10-11 09:09:42 -04:00
Jarrod Johnson eb48728bc1 Have stringify replace by default
This allows easier handling of invalid decode
data.
2019-10-11 08:57:18 -04:00
Jarrod Johnson c6255fa13b Fix SMM discovery in python3 2019-10-10 17:10:41 -04:00
Jarrod Johnson 2a37d64dc9 Fix XCC discovery in python3
Python3 was picky about the strings, normalize key areas
to strings.
2019-10-10 17:03:57 -04:00
Jarrod Johnson 8101672c3a Fix discovery with python3 2019-10-10 16:49:36 -04:00
Jarrod Johnson 453c344f7f Fix audit log for non root usage with python 3 2019-10-10 16:07:45 -04:00
Jarrod Johnson 9eceda0636 Fix non-root user issue with python3 2019-10-10 16:04:23 -04:00
Jarrod Johnson f28b91499e Fix console replay with formatting
Bold and related items would trigger a problem with python3
2019-10-10 15:01:00 -04:00
Jarrod Johnson 8cab591a8b Add collective member deletion
This allows deletion of a dead member, down to deleting down to non-collective
mode.
2019-10-10 11:30:03 -04:00
Jarrod Johnson 6ce6740b77 Provide deletion of collective member 2019-10-10 08:50:42 -04:00
Jarrod Johnson 8a4a219a14 Fix two more python2 string behavior
In python3, bytes[n] is an int, but not in python2.

Sidestep by doing bytearray() for both, which is consistent
between the two.
2019-10-09 15:06:23 -04:00
Jarrod Johnson a39d45d03c Fix some more python3 issues 2019-10-09 13:42:17 -04:00
Jarrod Johnson bc85d93cf4 More Py3 fixes 2019-10-09 11:20:03 -04:00
Jarrod Johnson 1e963106fe Fix unicode issues with web api 2019-10-09 11:03:30 -04:00
Jarrod Johnson a9f0e345db Another set of python3 fixes 2019-10-08 17:10:56 -04:00
Jarrod Johnson 5353b479d9 More python3 fixes 2019-10-08 13:46:44 -04:00
Jarrod Johnson dbc6747c38 Fix nodebmcreset for python3 2019-10-08 13:43:53 -04:00
Jarrod Johnson 0edd1efe0d Fix python3 dump_keys
When doing format on base64
output, it must be explicitly
coerced into native string
format.
2019-10-08 13:21:34 -04:00
Jarrod Johnson 8e87f5b9e5 Fix python3 issue with base64 and str 2019-10-08 11:20:27 -04:00
Jarrod Johnson 0633b2ca67 Fix syntax error in code 2019-10-08 10:59:44 -04:00
Jarrod Johnson c1953bdad3 Another set of python 3 compatibility
Numerous issues arose, particularly
when participating in a mixed
collective.
2019-10-08 10:45:43 -04:00
Jarrod Johnson 578ba06aa3 Fix python3 problem with octal 2019-10-08 09:06:15 -04:00
Jarrod Johnson 0975881d3b Add monotonic dependency 2019-10-07 17:06:45 -04:00
Jarrod Johnson 8fc3b7c9c0 Implement cross-python collective compat
This enables cross-version compatibility
for a collective.
2019-10-07 15:41:38 -04:00
Jarrod Johnson 6ea6ebd80e Remove superfluous dependency 2019-10-04 16:49:51 -04:00
Jarrod Johnson 744f8899f7 Merge branch 'py3' 2019-10-04 10:57:07 -04:00
Jarrod Johnson 74f18d5571 Client side py3 changes 2019-10-04 10:37:48 -04:00
59 changed files with 950 additions and 295 deletions
+15 -10
View File
@@ -89,6 +89,10 @@ except NameError:
netserver = None
laststate = {}
try:
input = raw_input
except NameError:
pass
class BailOut(Exception):
def __init__(self, errorcode=0):
@@ -195,9 +199,9 @@ def prompt():
if os.environ.get('TERM', '') not in ('linux'):
sys.stdout.write('\x1b]0;confetty: %s\x07' % target)
try:
return raw_input(target + ' -> ')
return input(target + ' -> ')
except KeyboardInterrupt:
print ""
print("")
return ""
except EOFError: # ctrl-d
print("exit")
@@ -296,7 +300,7 @@ currchildren = None
def print_result(res):
if 'errorcode' in res or 'error' in res:
print res['error']
print(res['error'])
return
if 'databynode' in res:
print_result(res['databynode'])
@@ -309,9 +313,9 @@ def print_result(res):
attrstr = '%s=%s' % (key, recurse_format(res[key]))
elif not isinstance(res[key], dict):
try:
print '{0}: {1}'.format(key, res[key])
print('{0}: {1}'.format(key, res[key]))
except UnicodeEncodeError:
print '{0}: {1}'.format(key, repr(res[key]))
print('{0}: {1}'.format(key, repr(res[key])))
continue
elif 'value' in res[key] and res[key]['value'] is not None:
attrstr = '%s="%s"' % (key, res[key]['value'])
@@ -324,7 +328,7 @@ def print_result(res):
else:
sys.stdout.write('{0}: '.format(key))
if isinstance(res[key], str) or isinstance(res[key], unicode):
print res[key]
print(res[key])
else:
print_result(res[key])
continue
@@ -423,10 +427,10 @@ def do_command(command, server):
for res in session.read(targpath):
if 'item' in res: # a link relation
if type(res['item']) == dict:
print res['item']["href"]
print(res['item']["href"])
else:
for item in res['item']:
print item["href"]
print(item["href"])
else: # generic attributes to list
if 'error' in res:
sys.stderr.write(res['error'] + '\n')
@@ -851,7 +855,7 @@ def server_connect():
passphrase = os.environ['CONFLUENT_PASSPHRASE']
session.authenticate(username, passphrase)
while not session.authenticated:
username = raw_input("Name: ")
username = input("Name: ")
passphrase = getpass.getpass("Passphrase: ")
session.authenticate(username, passphrase)
@@ -871,7 +875,7 @@ def main():
global inconsole
try:
server_connect()
except EOFError, KeyboardInterrupt:
except (EOFError, KeyboardInterrupt) as _:
raise BailOut(0)
except socket.gaierror:
sys.stderr.write('Could not connect to confluent\n')
@@ -929,6 +933,7 @@ def main():
updatestatus(data)
continue
if data is not None:
data = client.stringify(data)
if clearpowermessage:
sys.stdout.write("\x1b[2J\x1b[;H")
clearpowermessage = False
+4
View File
@@ -50,6 +50,9 @@ argparser.add_option('-c', '--clear', action='store_true',
help='Clear attributes')
argparser.add_option('-p', '--prompt', action='store_true',
help='Prompt for attribute values interactively')
argparser.add_option('-m', '--maxnodes', type='int',
help='Prompt if trying to set attributes on more '
'than specified number of nodes')
(options, args) = argparser.parse_args()
@@ -87,6 +90,7 @@ if len(args) > 1:
if oneval != twoval:
print('Values did not match.')
argassign[arg] = twoval
session.stop_if_noderange_over(noderange, options.maxnodes)
exitcode=client.updateattrib(session,args,nodetype, noderange, options, argassign)
try:
# setting user output to what the user inputs
+4 -2
View File
@@ -32,6 +32,8 @@ if path.startswith('/opt'):
import confluent.client as client
argparser = optparse.OptionParser(usage="Usage: %prog <noderange>")
argparser.add_option('-m', '--maxnodes', type='int',
help='Number of nodes to affect before prompting for confirmation')
(options, args) = argparser.parse_args()
try:
noderange = args[0]
@@ -43,7 +45,7 @@ session = client.Command()
exitcode = 0
errorNodes = set([])
session.stop_if_noderange_over(noderange, options.maxnodes)
success = session.simple_noderange_command(noderange, 'configuration/management_controller/reset', 'reset', key='state', errnodes=errorNodes) # = 0 if successful
# Determine which nodes were successful and print them
@@ -56,7 +58,7 @@ for node in session.read('/noderange/{0}/nodes/'.format(noderange)):
goodNodes = allNodes - errorNodes
for node in goodNodes:
print node + ": BMC Reset Successful"
print(node + ": BMC Reset Successful")
sys.exit(success)
+5
View File
@@ -42,6 +42,10 @@ argparser.add_option('-p', '--persist', dest='persist', action='store_true',
default=False,
help='Request the boot device be persistent rather than '
'one time')
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to boot, '
'prompting if over the threshold')
(options, args) = argparser.parse_args()
@@ -66,6 +70,7 @@ else:
bootmode = 'uefi'
errnodes = set([])
session.stop_if_noderange_over(noderange, options.maxnodes)
rc = session.simple_noderange_command(noderange, '/boot/nextdevice', bootdev,
bootmode=bootmode,
persistent=options.persist,
+6
View File
@@ -68,6 +68,10 @@ argparser.add_option('-r', '--restoredefault', default=False,
help='Restore the configuration of the node '
'to factory default for given component. '
'Currently only uefi is supported')
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to configure, '
'prompting if over the threshold')
(options, args) = argparser.parse_args()
cfgpaths = {
@@ -205,6 +209,7 @@ else:
session = client.Command()
rcode = 0
if options.restoredefault:
session.stop_if_noderange_over(noderange, options.maxnodes)
if options.restoredefault.lower() in (
'sys', 'system', 'uefi', 'bios'):
for fr in session.update(
@@ -225,6 +230,7 @@ if options.restoredefault:
options.restoredefault))
sys.exit(1)
if setmode:
session.stop_if_noderange_over(noderange, options.maxnodes)
if options.exclude:
sys.stderr.write('Cannot use exclude and assign at the same time\n')
sys.exit(1)
+8 -3
View File
@@ -38,6 +38,10 @@ if sys.version_info[0] < 3:
argparser = optparse.OptionParser(
usage="Usage: %prog [options] noderange [clear]")
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to clear if clearing log, '
'prompting if over the threshold')
(options, args) = argparser.parse_args()
try:
noderange = args[0]
@@ -46,11 +50,11 @@ except IndexError:
sys.exit(1)
client.check_globbing(noderange)
deletemode = False
if len(sys.argv) > 3:
if len(args) > 2:
argparser.print_help()
sys.exit(1)
if len(sys.argv) == 3:
if sys.argv[2] == 'clear':
if len(args) == 2:
if args[1] == 'clear':
deletemode = True
else:
argparser.print_help()
@@ -88,6 +92,7 @@ def format_event(evt):
if deletemode:
func = session.delete
session.stop_if_noderange_over(noderange, options.maxnodes)
else:
func = session.read
for rsp in func('/noderange/{0}/events/hardware/log'.format(noderange)):
+5
View File
@@ -59,6 +59,10 @@ argparser = optparse.OptionParser(
"%prog <noderange> [list][update [--backup <file>]]|[<components>]")
argparser.add_option('-b', '--backup', action='store_true',
help='Target a backup bank rather than primary')
argparser.add_option('-m', '--maxnodes', type='int',
help='When updating, prompt if more than the specified '
'number of servers will be affected')
(options, args) = argparser.parse_args()
upfile = None
try:
@@ -95,6 +99,7 @@ def get_update_progress(session, url):
def update_firmware(session, filename):
global exitcode
session.stop_if_noderange_over(noderange, options.maxnodes)
output = sq.ScreenPrinter(noderange, session)
nodeurls = {}
filename = os.path.abspath(filename)
+2 -2
View File
@@ -97,7 +97,7 @@ if len(args) > 1:
requestargs=args[1:]
except Exception as e:
print str(e)
print(str(e))
if exitcode != 0:
sys.exit(exitcode)
@@ -123,6 +123,6 @@ else:
sys.stderr.write(res['error'] + '\n')
exitcode = 1
else:
print res['item']['href'].replace('/', '')
print(res['item']['href'].replace('/', ''))
sys.exit(exitcode)
+6 -1
View File
@@ -37,6 +37,10 @@ exitcode = 0
argparser = optparse.OptionParser(
usage="Usage: "
"%prog <noderange> [list][install <file>|save <directory>|delete <name>]")
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to delete licenses from, '
'prompting if over the threshold')
(options, args) = argparser.parse_args()
upfile = None
downdir = None
@@ -52,7 +56,7 @@ try:
delete = args[2]
elif args[1] != 'list':
argparser.print_help()
sys.exit(1)
sys.exit(1)
except IndexError:
argparser.print_help()
sys.exit(1)
@@ -138,6 +142,7 @@ try:
elif downdir:
save_licenses(session, downdir)
elif delete:
session.stop_if_noderange_over(noderange, options.maxnodes)
delete_license(session, delete)
else:
show_licenses(session)
+6 -1
View File
@@ -37,6 +37,11 @@ argparser = optparse.OptionParser(
argparser.add_option('-p', '--showprevious', dest='previous',
action='store_true', default=False,
help='Show previous power state')
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to change power state, '
'prompting if over the threshold')
(options, args) = argparser.parse_args()
try:
noderange = args[0]
@@ -72,4 +77,4 @@ if options.previous:
# add dictionary to session
session.add_precede_dict(prev)
sys.exit(session.simple_noderange_command(noderange, '/power/state', setstate))
sys.exit(session.simple_noderange_command(noderange, '/power/state', setstate, promptover=options.maxnodes))
+5
View File
@@ -35,6 +35,10 @@ import confluent.client as client
argparser = optparse.OptionParser(
usage='''\n %prog noderange
\n ''')
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to delete, '
'prompting if over the threshold')
(options, args) = argparser.parse_args()
if len(args) != 1:
argparser.print_help()
@@ -43,6 +47,7 @@ noderange = args[0]
client.check_globbing(noderange)
session = client.Command()
exitcode = 0
session.stop_if_noderange_over(noderange, options.maxnodes)
for r in session.delete('/noderange/{0}'.format(noderange)):
if 'error' in r:
sys.stderr.write(r['error'] + '\n')
+6 -2
View File
@@ -32,6 +32,10 @@ if path.startswith('/opt'):
import confluent.client as client
argparser = optparse.OptionParser(usage="Usage: %prog <noderange>")
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to reseat, '
'prompting if over the threshold')
(options, args) = argparser.parse_args()
try:
noderange = args[0]
@@ -43,7 +47,7 @@ session = client.Command()
exitcode = 0
errorNodes = set([])
session.stop_if_noderange_over(noderange, options.maxnodes)
success = session.simple_noderange_command(noderange, 'power/reseat', 'reseat', key='reseat', errnodes=errorNodes) # = 0 if successful
# Determine which nodes were successful and print them
@@ -56,7 +60,7 @@ for node in session.read('/noderange/{0}/nodes/'.format(noderange)):
goodNodes = allNodes - errorNodes
for node in goodNodes:
print node + ": Reseat successful"
print(node + ": Reseat successful")
sys.exit(success)
+12 -6
View File
@@ -42,6 +42,10 @@ def run():
argparser = optparse.OptionParser(
usage="Usage: %prog location noderange:location",
)
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to run rsync to, '
'prompting if over the threshold')
argparser.add_option('-f', '-c', '--count', type='int', default=168,
help='Number of nodes to concurrently rsync')
# among other things, FD_SETSIZE limits. Besides, spawning too many
@@ -55,7 +59,7 @@ def run():
noderange, targpath = args[-1].split(':', 1)
client.check_globbing(noderange)
c = client.Command()
cmdstr = " ".join(args[:-1])
cmdstr = ' '.join(args[:-1])
cmdstr = 'rsync -av --info=progress2 ' + cmdstr
cmdstr += ' {node}:' + targpath
@@ -64,15 +68,17 @@ def run():
pipedesc = {}
pendingexecs = deque()
exitcode = 0
c.stop_if_noderange_over(noderange, options.maxnodes)
for exp in c.create('/noderange/{0}/attributes/expression'.format(noderange),
{'expression': cmdstr}):
{'expression': cmdstr}):
if 'error' in exp:
sys.stderr.write(exp['error'] + '\n')
exitcode |= exp.get('errorcode', 1)
ex = exp.get('databynode', ())
for node in ex:
cmd = ex[node]['value'].encode('utf-8')
cmd = ex[node]['value']
if not isinstance(cmd, bytes) and not isinstance(cmd, str):
cmd = cmd.encode('utf-8')
cmdv = shlex.split(cmd)
if currprocs < concurrentprocs:
currprocs += 1
@@ -98,7 +104,7 @@ def run():
if desc['type'] == 'stdout':
if node not in pernodeout:
pernodeout[node] = ''
pernodeout[node] += data
pernodeout[node] += client.stringify(data)
if '\n' in pernodeout[node]:
currout, pernodeout[node] = pernodeout[node].split('\n', 1)
if currout:
@@ -117,7 +123,7 @@ def run():
output.set_output(node, 'error!')
if node not in nodeerrs:
nodeerrs[node] = ''
nodeerrs[node] += data
nodeerrs[node] += client.stringify(data)
else:
pop = desc['popen']
ret = pop.poll()
+10 -2
View File
@@ -46,6 +46,10 @@ def run():
help='Number of commands to run at a time')
argparser.add_option('-n', '--nonodeprefix', action='store_true',
help='Do not prefix output with node names')
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to run the command with, '
'prompting if over the threshold')
# among other things, FD_SETSIZE limits. Besides, spawning too many
# processes can be unkind for the unaware on memory pressure and such...
argparser.disable_interspersed_args()
@@ -63,7 +67,7 @@ def run():
pipedesc = {}
pendingexecs = deque()
exitcode = 0
c.stop_if_noderange_over(args[0], options.maxnodes)
for exp in c.create('/noderange/{0}/attributes/expression'.format(args[0]),
{'expression': cmdstr}):
if 'error' in exp:
@@ -71,7 +75,9 @@ def run():
exitcode |= exp.get('errorcode', 1)
ex = exp.get('databynode', ())
for node in ex:
cmd = ex[node]['value'].encode('utf-8')
cmd = ex[node]['value']
if not isinstance(cmd, bytes) and not isinstance(cmd, str):
cmd = cmd.encode('utf-8')
cmdv = shlex.split(cmd)
if currprocs < concurrentprocs:
currprocs += 1
@@ -95,6 +101,7 @@ def run():
pernodeout[node] = []
pernodeout[node].append(data)
else:
data = client.stringify(data)
if options.nonodeprefix:
sys.stderr.write(data)
else:
@@ -112,6 +119,7 @@ def run():
run_cmdv(node, cmdv, all, pipedesc)
for node in sortutil.natural_sort(pernodeout):
for line in pernodeout[node]:
line = client.stringify(line)
if options.nonodeprefix:
sys.stdout.write(line)
else:
+10 -3
View File
@@ -149,8 +149,11 @@ def sensorpass(showout=True, appendtime=False):
if appendtime:
showval += ' @' + time.strftime(
'%Y-%m-%dT%H:%M:%S')
print(u'{0}: {1}:{2}'.format(
node, sensedata['name'], showval).encode('utf8'))
printval = u'{0}: {1}:{2}'.format(
node, sensedata['name'], showval)
if not isinstance(printval, str):
printval = printval.encode('utf-8')
print(printval)
sys.stdout.flush()
return resultdata
@@ -199,7 +202,11 @@ def main():
orderedsensors.append(name)
orderedsensors.sort()
for name in orderedsensors:
headernames.append(sensorheaders[name].encode('utf-8'))
headername = sensorheaders[name]
if (not isinstance(headername, str) and
not isinstance(headername, bytes)):
headername = headername.encode('utf-8')
headernames.append(headername)
if options.csv:
linebyline = False
csvwriter = csv.writer(sys.stdout)
+5 -1
View File
@@ -43,7 +43,10 @@ argparser.add_option('-p', '--persist', dest='persist', action='store_true',
argparser.add_option('-u', '--uefi', dest='uefi', action='store_true',
default=True,
help='Request UEFI style boot (rather than BIOS)')
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to modify next boot device, '
'prompting if over the threshold')
(options, args) = argparser.parse_args()
try:
@@ -63,6 +66,7 @@ if options.biosmode:
bootmode = 'bios'
else:
bootmode = 'uefi'
session.stop_if_noderange_over(noderange, options.maxnodes)
sys.exit(session.simple_noderange_command(noderange, '/boot/nextdevice', bootdev,
bootmode=bootmode,
persistent=options.persist))
+11 -3
View File
@@ -46,6 +46,10 @@ def run():
help='Number of commands to run at a time')
argparser.add_option('-n', '--nonodeprefix', action='store_true',
help='Do not prefix output with node names')
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to run remote ssh command to, '
'prompting if over the threshold')
# among other things, FD_SETSIZE limits. Besides, spawning too many
# processes can be unkind for the unaware on memory pressure and such...
argparser.disable_interspersed_args()
@@ -55,7 +59,7 @@ def run():
sys.exit(1)
client.check_globbing(args[0])
concurrentprocs = options.count
c = client.Command()
c = client.Command()
cmdstr = " ".join(args[1:])
currprocs = 0
@@ -64,7 +68,7 @@ def run():
pendingexecs = deque()
exitcode = 0
c.stop_if_noderange_over(args[0], options.maxnodes)
for exp in c.create('/noderange/{0}/attributes/expression'.format(args[0]),
{'expression': cmdstr}):
if 'error' in exp:
@@ -72,7 +76,9 @@ def run():
exitcode |= exp.get('errorcode', 1)
ex = exp.get('databynode', ())
for node in ex:
cmd = ex[node]['value'].encode('utf-8')
cmd = ex[node]['value']
if not isinstance(cmd, str) and not isinstance(cmd, bytes):
cmd = cmd.encode('utf-8')
cmdv = ['ssh', node, cmd]
if currprocs < concurrentprocs:
currprocs += 1
@@ -96,6 +102,7 @@ def run():
pernodeout[node] = []
pernodeout[node].append(data)
else:
data = client.stringify(data)
if options.nonodeprefix:
sys.stderr.write(data)
else:
@@ -113,6 +120,7 @@ def run():
run_cmdv(node, cmdv, all, pipedesc)
for node in sortutil.natural_sort(pernodeout):
for line in pernodeout[node]:
line = client.stringify(line)
if options.nonodeprefix:
sys.stdout.write(line)
else:
+14 -1
View File
@@ -63,10 +63,16 @@ def _print_cfg(scfg):
sys.stderr.write(e['error'] + '\n')
exitcode = e.get('errorcode', 1)
for node in e.get('databynode', {}):
curr = e['databynode'][node]
if 'error' in curr:
if 'no available drives' in curr['error']:
curr['error'] += ' (drives must be in unconfigured state to be available, they must not be in jbod or online state)'
sys.stderr.write('{0}: {1}\n'.format(node, curr['error']))
exitcode = curr.get('errorcode', 1)
continue
if node not in storagebynode:
storagebynode[node] = {'disks': [], 'arrays': [],
'volumes': []}
curr = e['databynode'][node]
storagebynode[node][curr['type'] + 's'].append(curr)
for node in storagebynode:
for disk in sorted(storagebynode[node]['disks'],
@@ -108,6 +114,7 @@ def createstorage(noderange, options, args):
sys.stderr.write('-r and -d are required arguments to create array\n')
sys.exit(1)
session = client.Command()
session.stop_if_noderange_over(noderange, options.maxnodes)
names = options.name
if names is None:
names = ''.join(args)
@@ -132,6 +139,7 @@ def deletestorage(noderange, options, args):
else:
names = options.name
session = client.Command()
session.stop_if_noderange_over(noderange, options.maxnodes)
for rsp in session.delete(
'/noderange/{0}/configuration/storage/volumes/{1}'.format(
noderange, names)):
@@ -162,6 +170,7 @@ def setdisk(noderange, options, args):
sys.stderr.write('diskset requires valid state as argument (hotspare, jbod, unconfigured)\n')
sys.exit(1)
session = client.Command()
session.stop_if_noderange_over(noderange, options.maxnodes)
scfg = session.update('/noderange/{0}/configuration/storage/disks/{1}'.format(noderange, names), {'state': args[0]})
_print_cfg(scfg)
@@ -202,6 +211,10 @@ def main():
help='Comma separated list of stripsizes to use when creating volumes. '
'This value is in kilobytes. The default behavior is to allow the '
'storage controller to decide.')
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to configure storage on, '
'prompting if over the threshold')
(options, args) = argparser.parse_args()
if len(args) == 1:
args.append('show')
+7 -2
View File
@@ -64,7 +64,7 @@ def printerror(res, node=None):
def download_servicedata(noderange, media):
def download_servicedata(noderange, media, options):
global exitcode
session = client.Command()
output = sq.ScreenPrinter(noderange, session)
@@ -73,6 +73,7 @@ def download_servicedata(noderange, media):
upargs = {'filename': filename}
noderrs = {}
nodeurls = {}
session.stop_if_noderange_over(noderange, options.maxnodes)
for res in session.create(resource, upargs):
if 'created' not in res:
for nodename in res.get('databynode', ()):
@@ -121,6 +122,10 @@ def main():
'management server (the confluent server if running remote, '
'and the collective.manager if in collective)\n'
'\n\nSee `man %prog` for more info.\n')
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to download diagnostic data from, '
'prompting if over the threshold')
(options, args) = argparser.parse_args()
media = None
try:
@@ -142,6 +147,6 @@ def main():
except KeyError:
argparser.print_help()
sys.exit(1)
handler(noderange, media)
handler(noderange, media, options)
if __name__ == '__main__':
main()
+49 -10
View File
@@ -39,6 +39,19 @@ _attraliases = {
'bmcpass': 'secret.hardwaremanagementpassword',
}
try:
input = raw_input
except NameError:
pass
def stringify(instr):
# Normalize unicode and bytes to 'str', correcting for
# current python version
if isinstance(instr, bytes) and not isinstance(instr, str):
return instr.decode('utf-8')
elif not isinstance(instr, bytes) and not isinstance(instr, str):
return instr.encode('utf-8')
return instr
class Tabulator(object):
def __init__(self, headers):
@@ -210,7 +223,7 @@ class Command(object):
return rc
def simple_noderange_command(self, noderange, resource, input=None,
key=None, errnodes=None, **kwargs):
key=None, errnodes=None, promptover=None, **kwargs):
try:
self._currnoderange = noderange
rc = 0
@@ -226,6 +239,7 @@ class Command(object):
noderange, resource)):
rc = self.handle_results(ikey, rc, res, errnodes)
else:
self.stop_if_noderange_over(noderange, promptover)
kwargs[ikey] = input
for res in self.update('/noderange/{0}/{1}'.format(
noderange, resource), kwargs):
@@ -235,6 +249,33 @@ class Command(object):
except KeyboardInterrupt:
cprint('')
return 0
def stop_if_noderange_over(self, noderange, maxnodes):
if maxnodes is None:
return
nsize = self.get_noderange_size(noderange)
if nsize > maxnodes:
if nsize == 1:
nodename = list(self.read(
'/noderange/{0}/nodes/'.format(noderange)))[0].get('item', {}).get('href', None)
nodename = nodename[:-1]
p = input('Command is about to affect node {0}, continue (y/n)? '.format(nodename))
else:
p = input('Command is about to affect {0} nodes, continue (y/n)? '.format(nsize))
if p.lower() != 'y':
sys.stderr.write('Aborting at user request\n')
sys.exit(1)
raise Exception("Aborting at user request")
def get_noderange_size(self, noderange):
numnodes = 0
for node in self.read('/noderange/{0}/nodes/'.format(noderange)):
if node.get('item', {}).get('href', None):
numnodes += 1
else:
raise Exception("Error trying to size noderange {0}".format(noderange))
return numnodes
def simple_nodegroups_command(self, noderange, resource, input=None, key=None, **kwargs):
try:
@@ -335,12 +376,8 @@ class Command(object):
if fingerprint == khf[hostid]:
return
else:
try:
replace = raw_input(
"MISMATCHED CERTIFICATE DATA, ACCEPT NEW? (y/n):")
except NameError:
replace = input(
"MISMATCHED CERTIFICATE DATA, ACCEPT NEW? (y/n):")
replace = input(
"MISMATCHED CERTIFICATE DATA, ACCEPT NEW? (y/n):")
if replace not in ('y', 'Y'):
raise Exception("BAD CERTIFICATE")
cprint('Adding new key for %s:%s' % (server, port))
@@ -392,6 +429,10 @@ def printattributes(session, requestargs, showtype, nodetype, noderange, options
path = '/{0}/{1}/attributes/{2}'.format(nodetype, noderange, showtype)
return print_attrib_path(path, session, requestargs, options)
def _sort_attrib(k):
if isinstance(k[1], dict) and k[1].get('sortid', None) is not None:
return k[1]['sortid']
return k[0]
def print_attrib_path(path, session, requestargs, options, rename=None, attrprefix=None):
exitcode = 0
@@ -402,9 +443,7 @@ def print_attrib_path(path, session, requestargs, options, rename=None, attrpref
exitcode = 1
continue
for node in sorted(res['databynode']):
for attr, val in sorted(
res['databynode'][node].items(),
key=lambda k: k[1].get('sortid', k[0]) if isinstance(k[1], dict) else k[0]):
for attr, val in sorted(res['databynode'][node].items(), key=_sort_attrib):
if attr == 'error':
sys.stderr.write('{0}: Error: {1}\n'.format(node, val))
continue
+20 -26
View File
@@ -46,9 +46,8 @@ alias nodelicense='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export
_confluent_get_args()
{
CMPARGS=($COMP_LINE)
NUMARGS=${#CMPARGS[@]}
if [ "${COMP_WORDS[-1]}" == '' ]; then
NUMARGS=$((NUMARGS+1))
NUMARGS=$((COMP_CWORD+1))
if [ "${COMP_WORDS[COMP_CWORD]}" == '' ]; then
CMPARGS+=("")
fi
GENNED=""
@@ -75,7 +74,7 @@ function _confluent_generic_completion()
{
_confluent_get_args
if [ $NUMARGS -ge 3 ] && [ ! -z "$GENNED" ]; then
COMPREPLY=($(compgen -W "$GENNED" -- ${COMP_WORDS[-1]}))
COMPREPLY=($(compgen -W "$GENNED" -- ${COMP_WORDS[COMP_CWORD]}))
fi
if [ $NUMARGS -lt 3 ]; then
_confluent_nr_completion
@@ -111,7 +110,7 @@ _confluent_nodemedia_completion()
return
fi
if [ $NUMARGS -ge 3 ] && [ ! -z "$GENNED" ]; then
COMPREPLY=($(compgen -W "$GENNED" -- ${COMP_WORDS[-1]}))
COMPREPLY=($(compgen -W "$GENNED" -- ${COMP_WORDS[COMP_CWORD]}))
return;
fi
if [ $NUMARGS -lt 3 ]; then
@@ -124,7 +123,7 @@ _confluent_nodefirmware_completion()
{
_confluent_get_args
if [ $NUMARGS == 3 ]; then
COMPREPLY=($(compgen -W "list update" -- ${COMP_WORDS[-1]}))
COMPREPLY=($(compgen -W "list update" -- ${COMP_WORDS[COMP_CWORD]}))
return;
fi
if [ $NUMARGS -gt 3 ] && [ ${CMPARGS[2]} == 'update' ]; then
@@ -142,7 +141,7 @@ _confluent_nodeshell_completion()
{
_confluent_get_args
if [ $NUMARGS == 3 ]; then
COMPREPLY=($(compgen -c -- ${COMP_WORDS[-1]}))
COMPREPLY=($(compgen -c -- ${COMP_WORDS[COMP_CWORD]}))
return
fi
if [ $NUMARGS -gt 3 ]; then
@@ -160,7 +159,7 @@ _confluent_nodelicense_completion()
{
_confluent_get_args
if [ $NUMARGS == 3 ]; then
COMPREPLY=($(compgen -W "install list save delete" -- ${COMP_WORDS[-1]}))
COMPREPLY=($(compgen -W "install list save delete" -- ${COMP_WORDS[COMP_CWORD]}))
return;
fi
if [ $NUMARGS == 4 ] && [ ${CMPARGS[2]} == 'install' ]; then
@@ -183,7 +182,7 @@ _confluent_nodesupport_completion()
{
_confluent_get_args
if [ $NUMARGS == 3 ]; then
COMPREPLY=($(compgen -W "servicedata" -- ${COMP_WORDS[-1]}))
COMPREPLY=($(compgen -W "servicedata" -- ${COMP_WORDS[COMP_CWORD]}))
return;
fi
if [ $NUMARGS == 4 ] && [ ${CMPARGS[2]} == 'servicedata' ]; then
@@ -210,41 +209,36 @@ _confluent_nn_completion()
if [ $NUMARGS -gt 2 ]; then
return;
fi
INPUT=${COMP_WORDS[-1]}
INPUT=${COMP_WORDS[COMP_CWORD]}
INPUT=${INPUT##*,-}
INPUT=${INPUT##*,}
INPUT=${INPUT##*@}
PREFIX=""
if [ "$INPUT" != "${COMP_WORDS[-1]}" ]; then
PREFIX=${COMP_WORDS[-1]}
if [ "$INPUT" != "${COMP_WORDS[COMP_CWORD]}" ]; then
PREFIX=${COMP_WORDS[COMP_CWORD]}
PREFIX=$(echo $PREFIX | sed -e 's/,[^,@-]*$/,/' -e 's/,-[^,@]*$/,-/' -e 's/@[^,@]*/@/')
fi
COMPREPLY=($(compgen -W "$(nodelist | sed -e s/^/$PREFIX/)" -- "${COMP_WORDS[-1]}"))
COMPREPLY=($(compgen -W "$(nodelist | sed -e s/^/$PREFIX/)" -- "${COMP_WORDS[COMP_CWORD]}"))
}
_confluent_nr_completion()
{
CMPARGS=($COMP_LINE)
NUMARGS=${#CMPARGS[@]}
if [ "${COMP_WORDS[-1]}" == '' ]; then
NUMARGS=$((NUMARGS+1))
fi
_confluent_get_args
if [ $NUMARGS -gt 2 ]; then
return;
fi
INPUT=${COMP_WORDS[-1]}
INPUT=${COMP_WORDS[COMP_CWORD]}
INPUT=${INPUT##*,-}
INPUT=${INPUT##*,}
INPUT=${INPUT##*@}
PREFIX=""
if [ "$INPUT" != "${COMP_WORDS[-1]}" ]; then
PREFIX=${COMP_WORDS[-1]}
if [ "$INPUT" != "${COMP_WORDS[COMP_CWORD]}" ]; then
PREFIX=${COMP_WORDS[COMP_CWORD]}
PREFIX=$(echo $PREFIX | sed -e 's/,[^,@-]*$/,/' -e 's/,-[^,@]*$/,-/' -e 's/@[^,@]*/@/')
fi
#COMPREPLY=($(compgen -W "$(confetty show /nodegroups|sed -e 's/\///' -e s/^/$PREFIX/;nodelist | sed -e s/^/$PREFIX/)" -- "${COMP_WORDS[-1]}"))
COMPREPLY=($(compgen -W "$(confetty show /nodegroups|sed -e 's/\///' -e s/^/$PREFIX/;nodelist | sed -e s/^/$PREFIX/)" -- "${COMP_WORDS[-1]}"))
COMPREPLY=($(compgen -W "$(confetty show /nodegroups|sed -e 's/\///' -e s/^/$PREFIX/;nodelist | sed -e s/^/$PREFIX/)" -- "${COMP_WORDS[COMP_CWORD]}"))
}
_confluent_ng_completion()
{
@@ -252,17 +246,17 @@ _confluent_ng_completion()
if [ $NUMARGS -gt 2 ]; then
return;
fi
INPUT=${COMP_WORDS[-1]}
INPUT=${COMP_WORDS[COMP_CWORD]}
INPUT=${INPUT##*,-}
INPUT=${INPUT##*,}
INPUT=${INPUT##*@}
PREFIX=""
if [ "$INPUT" != "${COMP_WORDS[-1]}" ]; then
PREFIX=${COMP_WORDS[-1]}
if [ "$INPUT" != "${COMP_WORDS[COMP_CWORD]}" ]; then
PREFIX=${COMP_WORDS[COMP_CWORD]}
PREFIX=$(echo $PREFIX | sed -e 's/,[^,@-]*$/,/' -e 's/,-[^,@]*$/,-/' -e 's/@[^,@]*/@/')
fi
COMPREPLY=($(compgen -W "$(confetty show /nodegroups|sed -e 's/\///' -e s/^/$PREFIX/)" -- "${COMP_WORDS[-1]}"))
COMPREPLY=($(compgen -W "$(confetty show /nodegroups|sed -e 's/\///' -e s/^/$PREFIX/)" -- "${COMP_WORDS[COMP_CWORD]}"))
}
complete -F _confluent_nodeattrib_completion nodeattrib
complete -F _confluent_nodeattrib_completion nodegroupattrib
+5 -1
View File
@@ -13,7 +13,11 @@ nodefirmware(8) -- Report firmware information on confluent nodes
will retrieve all firmware, but can be directed to fetch specific firmware by
calling out the name of the firmware (e.g. uefi or xcc) or request reading only
core firmware firmware by using the word 'core', which is generally a quicker
operation.
operation. Different hardwaremanagement.method indicated plugins may have
different capabilities available. For example, the 'core' distinction may
not be relevant to redfish. Additionally, the Lenovo XCC makes certain
information available over IPMI that is not otherwise available (for example
the FPGA version where applicable).
In the update form, it accepts a single file and attempts to update it using
the out of band facilities. Firmware updates can end in one of three states:
+2 -2
View File
@@ -42,7 +42,7 @@ The attribute name may use a wildcard:
Commas can be used to indicate multiple nodes, and can mix and match any of the above primitives. The following can be a valid single noderange, combining any and all members of each comma separated component
`n1,n2,rack1,storage,location.rack=9,~s1..,n20-n30`
Exclusions can be done by prepending a ‘-‘ before a portion of a noderange:
Exclusions can be done by prepending a '-' before a portion of a noderange:
`rack1,-n2`
`compute,-rack1`
`compute,-location.row=12`
@@ -54,7 +54,7 @@ To indicate nodes that match multiple selections at once (set intersection), the
For complex expressions, () may be used to indicate order of expanding the noderange to be explicit
`rack1,-(console.logging=full@compute)`
Noderange syntax can also indicate ‘pagination’, or separating the nodes into well defined chunks. > is used to indicate how many nodes to display at a time, and < is used to indicate how many nodes to skip into a noderange:
Noderange syntax can also indicate 'pagination', or separating the nodes into well defined chunks. > is used to indicate how many nodes to display at a time, and < is used to indicate how many nodes to skip into a noderange:
`rack1>3<6`
The above would show the seventh through ninth nodes of the rack1 group. Like all other noderange operations, this may be combined with any of the above, but must appear as the very last operation. Ordering is done with a natural sort.
+4 -1
View File
@@ -7,7 +7,7 @@ nodesupport(8) -- Utilities for interacting with vendor support
## DESCRIPTION
`nodesupport` provides capabilities associated with interactiong with support.
`nodesupport` provides capabilities associated with interacting with support.
Currently it only has the `servicedata` subcommand. `servicedata` takes
an argument that is either a directory name (that can be used for a single node
or multiple nodes) or a file name (only to be used with single node noderange).
@@ -16,6 +16,9 @@ connects to the managed system, so it will download to the remote system if runn
remotely and will download to the collective.manager indicated system if
running in collective mode.
Note that due to vendor filename requirements, any filename may have vendor
specific suffixes added to any file produced.
## EXAMPLES
* Download support data from a single node to a specific filename
+19 -2
View File
@@ -22,7 +22,7 @@ except NameError:
pass
def make_certificate():
umask = os.umask(0077)
umask = os.umask(0o77)
try:
os.makedirs('/etc/confluent/cfg')
except OSError as e:
@@ -61,7 +61,7 @@ def join_collective(server, invitation):
make_certificate()
s = client.Command().connection
while not invitation:
invitation = raw_input('Paste the invitation here: ')
invitation = input('Paste the invitation here: ')
tlvdata.send(s, {'collective': {'operation': 'join',
'invitation': invitation,
'server': server}})
@@ -69,6 +69,19 @@ def join_collective(server, invitation):
res = res.get('collective',
{'status': 'Unknown response: ' + repr(res)})
print(res.get('status', res.get('error', repr(res))))
if 'error' in res:
sys.exit(1)
def delete_member(name):
s = client.Command().connection
tlvdata.send(s, {'collective': {'operation': 'delete',
'member': name}})
res = tlvdata.recv(s)
res = res.get('collective',
{'status': 'Unknown response: ' + repr(res)})
print(res.get('status', res.get('error', repr(res))))
if 'error' in res:
sys.exit(1)
def show_collective():
@@ -104,6 +117,8 @@ def main():
'collective member. Run collective invite -h for more information')
ic.add_argument('name', help='Name of server to invite to join the '
'collective')
dc = sp.add_parser('delete', help='Delete a member of a collective')
dc.add_argument('name', help='Name of server to delete from collective')
jc = sp.add_parser('join', help='Join a collective. Run collective join -h for more information')
jc.add_argument('server', help='Existing collective member that ran invite and generated a token')
jc.add_argument('-i', help='Invitation provided by runniing invite on an '
@@ -117,6 +132,8 @@ def main():
join_collective(cmdset.server, cmdset.i)
elif cmdset.command == 'show':
show_collective()
elif cmdset.command == 'delete':
delete_member(cmdset.name)
if __name__ == '__main__':
main()
+7 -3
View File
@@ -3,8 +3,12 @@ cd `dirname $0`
PKGNAME=$(basename $(pwd))
DPKGNAME=$(basename $(pwd) | sed -e s/_/-/)
OPKGNAME=$(basename $(pwd) | sed -e s/_/-/)
PYEXEC=python3
DSCARGS="--with-python3=True --with-python2=False"
if grep wheezy /etc/os-release; then
DPKGNAME=python-$DPKGNAME
PYEXEC=python
DSCARGS="--with-python3=False --with-python2=True"
fi
cd ..
mkdir -p /tmp/confluent # $DPKGNAME
@@ -24,15 +28,15 @@ install-scripts=/opt/confluent/bin
package=$DPKGNAME
EOF
python setup.py sdist > /dev/null 2>&1
py2dsc dist/*.tar.gz
$PYEXEC setup.py sdist > /dev/null 2>&1
py2dsc $DSCARGS dist/*.tar.gz
shopt -s extglob
cd deb_dist/!(*.orig)/
if [ "$OPKGNAME" = "confluent-server" ]; then
if grep wheezy /etc/os-release; then
sed -i 's/^\(Depends:.*\)/\1, python-confluent-client, python-lxml, python-eficompressor, python-pycryptodomex, python-dateutil, python-pyopenssl/' debian/control
else
sed -i 's/^\(Depends:.*\)/\1, confluent-client, python-lxml, python-eficompressor, python-pycryptodome, python-dateutil/' debian/control
sed -i 's/^\(Depends:.*\)/\1, confluent-client, python3-lxml, python3-eficompressor, python3-pycryptodome, python3-websocket/' debian/control
fi
if grep wheezy /etc/os-release; then
echo 'confluent_client python-confluent-client' >> debian/pydist-overrides
+1 -1
View File
@@ -6,7 +6,7 @@ fi
./makesetup
VERSION=`cat VERSION`
PKGNAME=$(basename $(pwd))
python setup.py sdist > /dev/null 2>&1
python3 setup.py sdist > /dev/null 2>&1
cp dist/*.tar.gz ~/rpmbuild/SOURCES
sed -e 's/#VERSION#/'$VERSION/ $PKGNAME.spec.tmpl > ~/rpmbuild/SPECS/$PKGNAME.spec
rpmbuild -ba ~/rpmbuild/SPECS/$PKGNAME.spec 2> /dev/null |grep ^Wrote:
+11 -5
View File
@@ -28,6 +28,7 @@ import hashlib
import hmac
import multiprocessing
import confluent.userutil as userutil
import confluent.util as util
pam = None
try:
import confluent.pam as pam
@@ -57,9 +58,9 @@ _allowedbyrole = {
'/node*/configuration/*',
],
'update': [
'/discovery/*',
'/discovery/*',
'/networking/macs/rescan',
'/node*/power/state',
'/node*/power/state',
'/node*/power/reseat',
'/node*/attributes/*',
'/node*/media/*tach',
@@ -119,11 +120,13 @@ def _get_usertenant(name, tenant=False):
administrator account a tenant gets.
Otherwise, just assume a user in the default tenant
"""
if not isinstance(name, bytes):
name = name.encode('utf-8')
if not isinstance(tenant, bool):
# if not boolean, it must be explicit tenant
user = name
elif '/' in name: # tenant scoped name
tenant, user = name.split('/', 1)
elif b'/' in name: # tenant scoped name
tenant, user = name.split(b'/', 1)
elif configmanager.is_tenant(name):
# the account is the implicit tenant owner account
user = name
@@ -131,6 +134,9 @@ def _get_usertenant(name, tenant=False):
else: # assume it is a non-tenant user account
user = name
tenant = None
user = util.stringify(user)
if tenant:
tenant = util.stringify(tenant)
yield user
yield tenant
@@ -288,4 +294,4 @@ def _do_pbkdf(passphrase, salt):
# compute. However, we do want to wait for result, so we have
# one of the exceedingly rare sort of circumstances where 'apply'
# actually makes sense
return authworkers.apply(_apply_pbkdf, [passphrase, salt])
return authworkers.apply(_apply_pbkdf, [passphrase, salt])
@@ -27,6 +27,7 @@ import eventlet.green.ssl as ssl
import eventlet.green.threading as threading
import greenlet
import random
import sys
try:
import OpenSSL.crypto as crypto
except ImportError:
@@ -70,11 +71,22 @@ def connect_to_leader(cert=None, name=None, leader=None):
return False
with connecting:
with cfm._initlock:
tlvdata.recv(remote) # the banner
banner = tlvdata.recv(remote) # the banner
vers = banner.split()[2]
pvers = 0
reqver = 4
if vers == b'v0':
pvers = 2
elif vers == b'v1':
pvers = 4
if sys.version_info[0] < 3:
pvers = 2
reqver = 2
tlvdata.recv(remote) # authpassed... 0..
if name is None:
name = get_myname()
tlvdata.send(remote, {'collective': {'operation': 'connect',
'protover': reqver,
'name': name,
'txcount': cfm._txcount}})
keydata = tlvdata.recv(remote)
@@ -119,7 +131,7 @@ def connect_to_leader(cert=None, name=None, leader=None):
globaldata = tlvdata.recv(remote)
dbi = tlvdata.recv(remote)
dbsize = dbi['dbsize']
dbjson = ''
dbjson = b''
while (len(dbjson) < dbsize):
ndata = remote.recv(dbsize - len(dbjson))
if not ndata:
@@ -148,15 +160,15 @@ def connect_to_leader(cert=None, name=None, leader=None):
raise
currentleader = leader
#spawn this as a thread...
follower = eventlet.spawn(follow_leader, remote)
follower = eventlet.spawn(follow_leader, remote, pvers, leader)
return True
def follow_leader(remote):
def follow_leader(remote, proto, leader):
global currentleader
cleanexit = False
try:
cfm.follow_channel(remote)
cfm.follow_channel(remote, proto)
except greenlet.GreenletExit:
cleanexit = True
finally:
@@ -164,8 +176,8 @@ def follow_leader(remote):
log.log({'info': 'Previous following cleanly closed',
'subsystem': 'collective'})
return
log.log({'info': 'Current leader has disappeared, restarting '
'collective membership', 'subsystem': 'collective'})
log.log({'info': 'Current leader ({0}) has disappeared, restarting '
'collective membership'.format(leader), 'subsystem': 'collective'})
# The leader has folded, time to startup again...
cfm.stop_following()
currentleader = None
@@ -208,8 +220,7 @@ def handle_connection(connection, cert, request, local=False):
else:
if not local:
return
if 'show' == operation:
if operation in ('show', 'delete'):
if not list(cfm.list_collective()):
tlvdata.send(connection,
{'collective': {'error': 'Collective mode not '
@@ -246,7 +257,23 @@ def handle_connection(connection, cert, request, local=False):
collinfo['quorum'] = True
except exc.DegradedCollective:
collinfo['quorum'] = False
tlvdata.send(connection, {'collective': collinfo})
if operation == 'show':
tlvdata.send(connection, {'collective': collinfo})
elif operation == 'delete':
todelete = request['member']
if (todelete == collinfo['leader'] or
todelete in collinfo['active']):
tlvdata.send(connection, {'collective':
{'error': '{0} is still active, stop the confluent service to remove it'.format(todelete)}})
return
if todelete not in collinfo['offline']:
tlvdata.send(connection, {'collective':
{'error': '{0} is not a recognized collective member'.format(todelete)}})
return
cfm.del_collective_member(todelete)
tlvdata.send(connection,
{'collective': {'status': 'Successfully deleted {0}'.format(todelete)}})
connection.close()
return
if 'invite' == operation:
try:
@@ -267,7 +294,8 @@ def handle_connection(connection, cert, request, local=False):
invitation = request['invitation']
try:
invitation = base64.b64decode(invitation)
name, invitation = invitation.split('@', 1)
name, invitation = invitation.split(b'@', 1)
name = util.stringify(name)
except Exception:
tlvdata.send(
connection,
@@ -375,7 +403,7 @@ def handle_connection(connection, cert, request, local=False):
connection.close()
return
if (currentleader == connection.getpeername()[0] and
follower and follower.isAlive()):
follower and not follower.dead):
# if we are happily following this leader already, don't stir
# the pot
tlvdata.send(connection, {'status': 0})
@@ -402,6 +430,7 @@ def handle_connection(connection, cert, request, local=False):
tlvdata.send(connection, collinfo)
if 'connect' == operation:
drone = request['name']
folver = request.get('protover', 2)
droneinfo = cfm.get_collective_member(drone)
if not (droneinfo and util.cert_matches(droneinfo['fingerprint'],
cert)):
@@ -450,7 +479,7 @@ def handle_connection(connection, cert, request, local=False):
connection.sendall(cfgdata)
#tlvdata.send(connection, {'tenants': 0}) # skip the tenants for now,
# so far unused anyway
if not cfm.relay_slaved_requests(drone, connection):
if not cfm.relay_slaved_requests(drone, connection, folver):
if not retrythread: # start a recovery if everyone else seems
# to have disappeared
retrythread = eventlet.spawn_after(30 + random.random(),
@@ -271,7 +271,7 @@ node = {
'console.method': {
'description': ('Indicate the method used to access the console of '
'the managed node.'),
'validvalues': ('ssh', 'ipmi'),
'validvalues': ('ssh', 'ipmi', 'tsmsol'),
},
# 'virtualization.host': {
# 'description': ('Hypervisor where this node does/should reside'),
@@ -101,6 +101,10 @@ _cfgstore = None
_pendingchangesets = {}
_txcount = 0
_hasquorum = True
if sys.version_info[0] >= 3:
lowestver = 4
else:
lowestver = 2
_attraliases = {
'bmc': 'hardwaremanagement.manager',
@@ -309,12 +313,12 @@ def check_quorum():
def exec_on_leader(function, *args):
if isinstance(cfgleader, bool):
raise exc.DegradedCollective()
xid = os.urandom(8)
xid = confluent.util.stringify(base64.b64encode(os.urandom(8)))
while xid in _pendingchangesets:
xid = os.urandom(8)
xid = confluent.util.stringify(base64.b64encode(os.urandom(8)))
_pendingchangesets[xid] = event.Event()
rpcpayload = cPickle.dumps({'function': function, 'args': args,
'xid': xid})
'xid': xid}, protocol=cfgproto)
rpclen = len(rpcpayload)
cfgleader.sendall(struct.pack('!Q', rpclen))
cfgleader.sendall(rpcpayload)
@@ -324,15 +328,24 @@ def exec_on_leader(function, *args):
def exec_on_followers(fnname, *args):
global _txcount
pushes = eventlet.GreenPool()
# Check health of collective prior to attempting
for _ in pushes.starmap(
_push_rpc, [(cfgstreams[s], b'') for s in cfgstreams]):
pass
if len(cfgstreams) < (len(_cfgstore['collective']) // 2):
# the leader counts in addition to registered streams
raise exc.DegradedCollective()
exec_on_followers_unconditional(fnname, *args)
def exec_on_followers_unconditional(fnname, *args):
global _txcount
pushes = eventlet.GreenPool()
_txcount += 1
payload = cPickle.dumps({'function': fnname, 'args': args,
'txcount': _txcount})
for res in pushes.starmap(
'txcount': _txcount}, protocol=lowestver)
for _ in pushes.starmap(
_push_rpc, [(cfgstreams[s], payload) for s in cfgstreams]):
pass
@@ -378,9 +391,15 @@ def init_masterkey(password=None, autogen=True):
def _push_rpc(stream, payload):
with _rpclock:
stream.sendall(struct.pack('!Q', len(payload)))
if len(payload):
stream.sendall(payload)
try:
stream.sendall(struct.pack('!Q', len(payload)))
if len(payload):
stream.sendall(payload)
return True
except Exception:
logException()
del cfgstreams[stream]
stream.close()
def decrypt_value(cryptvalue,
@@ -401,12 +420,12 @@ def decrypt_value(cryptvalue,
raise Exception("bad HMAC value on crypted value")
decrypter = AES.new(key, AES.MODE_CBC, iv)
value = decrypter.decrypt(cipherdata)
padsize = ord(value[-1])
padsize = bytearray(value)[-1]
pad = value[-padsize:]
# Note that I cannot grasp what could be done with a subliminal
# channel in padding in this case, but check the padding anyway
for padbyte in pad:
if ord(padbyte) != padsize:
for padbyte in bytearray(pad):
if padbyte != padsize:
raise Exception("bad padding in encrypted value")
return value[0:-padsize]
else:
@@ -474,7 +493,7 @@ def crypt_value(value,
crypter = AES.new(key, AES.MODE_GCM, nonce=iv)
value = confluent.util.stringify(value).encode('utf-8')
cryptval, hmac = crypter.encrypt_and_digest(value)
return iv, cryptval, hmac, '\x02'
return iv, cryptval, hmac, b'\x02'
def _load_dict_from_dbm(dpath, tdb):
@@ -546,9 +565,14 @@ def set_global(globalname, value, sync=True):
ConfigManager._bg_sync_to_file()
cfgstreams = {}
def relay_slaved_requests(name, listener):
def relay_slaved_requests(name, listener, vers):
global cfgleader
global _hasquorum
global lowestver
if vers > 2 and sys.version_info[0] < 3:
vers = 2
if vers < lowestver:
lowestver = vers
pushes = eventlet.GreenPool()
if name not in _followerlocks:
_followerlocks[name] = gthread.RLock()
@@ -565,11 +589,18 @@ def relay_slaved_requests(name, listener):
lh = StreamHandler(listener)
_hasquorum = len(cfgstreams) >= (
len(_cfgstore['collective']) // 2)
payload = cPickle.dumps({'quorum': _hasquorum})
for _ in pushes.starmap(
_push_rpc,
[(cfgstreams[s], payload) for s in cfgstreams]):
pass
_newquorum = None
while _hasquorum != _newquorum:
if _newquorum is not None:
_hasquorum = _newquorum
payload = cPickle.dumps({'quorum': _hasquorum}, protocol=lowestver)
for _ in pushes.starmap(
_push_rpc,
[(cfgstreams[s], payload) for s in cfgstreams]):
pass
_newquorum = len(cfgstreams) >= (
len(_cfgstore['collective']) // 2)
_hasquorum = _newquorum
if _hasquorum and _pending_collective_updates:
apply_pending_collective_updates()
msg = lh.get_next_msg()
@@ -578,7 +609,7 @@ def relay_slaved_requests(name, listener):
raise Exception("Unexpected loss of node in followers: " + name)
sz = struct.unpack('!Q', msg)[0]
if sz != 0:
rpc = ''
rpc = b''
while len(rpc) < sz:
nrpc = listener.recv(sz - len(rpc))
if not nrpc:
@@ -591,8 +622,10 @@ def relay_slaved_requests(name, listener):
except Exception as e:
exc = e
if 'xid' in rpc:
_push_rpc(listener, cPickle.dumps({'xid': rpc['xid'],
'exc': exc}))
res = _push_rpc(listener, cPickle.dumps({'xid': rpc['xid'],
'exc': exc}, protocol=vers))
if not res:
break
try:
msg = lh.get_next_msg()
except Exception:
@@ -609,7 +642,7 @@ def relay_slaved_requests(name, listener):
if cfgstreams:
_hasquorum = len(cfgstreams) >= (
len(_cfgstore['collective']) // 2)
payload = cPickle.dumps({'quorum': _hasquorum})
payload = cPickle.dumps({'quorum': _hasquorum}, protocol=lowestver)
for _ in pushes.starmap(
_push_rpc,
[(cfgstreams[s], payload) for s in cfgstreams]):
@@ -637,7 +670,9 @@ class StreamHandler(object):
if confluent.util.monotonic_time() > self.expiry:
return None
if confluent.util.monotonic_time() > self.keepalive:
_push_rpc(self.sock, b'') # nulls are a keepalive
res = _push_rpc(self.sock, b'') # nulls are a keepalive
if not res:
return None
self.keepalive = confluent.util.monotonic_time() + 20
self.expiry = confluent.util.monotonic_time() + 60
msg = self.sock.recv(8)
@@ -649,15 +684,19 @@ class StreamHandler(object):
self.sock = None
def stop_following(replacement=None):
def stop_following(replacement=None, proto=2):
with _leaderlock:
global cfgleader
global cfgproto
if cfgleader and not isinstance(cfgleader, bool):
try:
cfgleader.close()
except Exception:
pass
cfgleader = replacement
if proto > 2 and sys.version_info[0] < 3:
proto = 2
cfgproto = proto
def stop_leading():
for stream in list(cfgstreams):
@@ -715,20 +754,21 @@ def commit_clear():
ConfigManager._bg_sync_to_file()
cfgleader = None
cfgproto = 2
def follow_channel(channel):
def follow_channel(channel, proto=2):
global _txcount
global _hasquorum
try:
stop_leading()
stop_following(channel)
stop_following(channel, proto)
lh = StreamHandler(channel)
msg = lh.get_next_msg()
while msg:
sz = struct.unpack('!Q', msg)[0]
if sz != 0:
rpc = ''
rpc = b''
while len(rpc) < sz:
nrpc = channel.recv(sz - len(rpc))
if not nrpc:
@@ -749,7 +789,9 @@ def follow_channel(channel):
_pendingchangesets[rpc['xid']].send()
if 'quorum' in rpc:
_hasquorum = rpc['quorum']
_push_rpc(channel, b'') # use null as ACK
res = _push_rpc(channel, b'') # use null as ACK
if not res:
break
msg = lh.get_next_msg()
finally:
# mark the connection as broken
@@ -766,6 +808,34 @@ def add_collective_member(name, address, fingerprint):
exec_on_followers('_true_add_collective_member', name, address, fingerprint)
_true_add_collective_member(name, address, fingerprint)
def del_collective_member(name):
if cfgleader and not isinstance(cfgleader, bool):
return exec_on_leader('del_collective_member', name)
if cfgstreams:
exec_on_followers_unconditional('_true_del_collective_member', name)
_true_del_collective_member(name)
def _true_del_collective_member(name, sync=True):
global cfgleader
name = confluent.util.stringify(name)
if _cfgstore is None:
return
if 'collective' not in _cfgstore:
return
if name not in _cfgstore['collective']:
return
del _cfgstore['collective'][name]
with _dirtylock:
if 'collectivedirty' not in _cfgstore:
_cfgstore['collectivedirty'] = set([])
_cfgstore['collectivedirty'].add(name)
if len(_cfgstore['collective']) < 2:
del _cfgstore['collective']
cfgleader = None
if sync:
ConfigManager._bg_sync_to_file()
_pending_collective_updates = {}
@@ -1091,6 +1161,8 @@ class ConfigManager(object):
attribute, match = expression.split('=')
else:
raise Exception('Invalid Expression')
if attribute.startswith('secret.'):
raise Exception('Filter by secret attributes is not supported')
for node in nodes:
try:
currvals = [self._cfgstore['nodes'][node][attribute]['value']]
@@ -1580,6 +1652,7 @@ class ConfigManager(object):
if group in self._cfgstore['nodes'][node]['groups']:
self._cfgstore['nodes'][node]['groups'].remove(group)
self._node_removed_from_group(node, group, changeset)
_mark_dirtykey('nodes', node, self.tenant)
for node in nodes:
if node not in self._cfgstore['nodes']:
self._cfgstore['nodes'][node] = {'groups': [group]}
@@ -2233,7 +2306,7 @@ class ConfigManager(object):
target = dumpdata[confarea][element][attribute]['cryptvalue']
cryptval = []
for value in target:
cryptval.append(base64.b64encode(value))
cryptval.append(confluent.util.stringify(base64.b64encode(value)))
if attribute == 'cryptpass':
dumpdata[confarea][element][attribute] = '!'.join(cryptval)
else:
@@ -2326,31 +2399,37 @@ class ConfigManager(object):
for globalkey in dirtyglobals:
if globalkey in _cfgstore['globals']:
globalf[globalkey] = \
cPickle.dumps(_cfgstore['globals'][globalkey])
cPickle.dumps(_cfgstore['globals'][globalkey], protocol=cPickle.HIGHEST_PROTOCOL)
else:
if globalkey in globalf:
del globalf[globalkey]
finally:
globalf.close()
if fullsync or 'collectivedirty' in _cfgstore:
collectivef = dbm.open(os.path.join(cls._cfgdir, "collective"),
'c', 384)
try:
if fullsync:
colls = _cfgstore['collective']
else:
with _dirtylock:
colls = copy.deepcopy(_cfgstore['collectivedirty'])
del _cfgstore['collectivedirty']
for coll in colls:
if coll in _cfgstore['collective']:
collectivef[coll] = cPickle.dumps(
_cfgstore['collective'][coll])
if len(_cfgstore.get('collective', ())) > 1:
collectivef = dbm.open(os.path.join(cls._cfgdir, "collective"),
'c', 384)
try:
if fullsync:
colls = _cfgstore['collective']
else:
if coll in collectivef:
del globalf[coll]
finally:
collectivef.close()
with _dirtylock:
colls = copy.deepcopy(_cfgstore['collectivedirty'])
del _cfgstore['collectivedirty']
for coll in colls:
if coll in _cfgstore['collective']:
collectivef[coll] = cPickle.dumps(
_cfgstore['collective'][coll], protocol=cPickle.HIGHEST_PROTOCOL)
else:
if coll in collectivef:
del collectivef[coll]
finally:
collectivef.close()
else:
try:
os.remove(os.path.join(cls._cfgdir, "collective"))
except OSError:
pass
if fullsync:
pathname = cls._cfgdir
currdict = _cfgstore['main']
@@ -2359,7 +2438,7 @@ class ConfigManager(object):
dbf = dbm.open(os.path.join(pathname, category), 'c', 384) # 0600
try:
for ck in currdict[category]:
dbf[ck] = cPickle.dumps(currdict[category][ck])
dbf[ck] = cPickle.dumps(currdict[category][ck], protocol=cPickle.HIGHEST_PROTOCOL)
finally:
dbf.close()
elif 'dirtykeys' in _cfgstore:
@@ -2383,7 +2462,7 @@ class ConfigManager(object):
if ck in dbf:
del dbf[ck]
else:
dbf[ck] = cPickle.dumps(currdict[category][ck])
dbf[ck] = cPickle.dumps(currdict[category][ck], protocol=cPickle.HIGHEST_PROTOCOL)
finally:
dbf.close()
willrun = False
@@ -2447,20 +2526,21 @@ def _dump_keys(password, dojson=True):
init_masterkey()
cryptkey = _format_key(_masterkey, password=password)
if 'passphraseprotected' in cryptkey:
cryptkey = '!'.join(map(base64.b64encode,
cryptkey['passphraseprotected']))
cryptkey = '!'.join(
[confluent.util.stringify(base64.b64encode(x))
for x in cryptkey['passphraseprotected']])
else:
cryptkey = '*unencrypted:{0}'.format(base64.b64encode(
cryptkey['unencryptedvalue']))
cryptkey = '*unencrypted:{0}'.format(confluent.util.stringify(base64.b64encode(
cryptkey['unencryptedvalue'])))
keydata = {'cryptkey': cryptkey}
if _masterintegritykey is not None:
integritykey = _format_key(_masterintegritykey, password=password)
if 'passphraseprotected' in integritykey:
integritykey = '!'.join(map(base64.b64encode,
integritykey['passphraseprotected']))
integritykey = '!'.join([confluent.util.stringify(base64.b64encode(x)) for x in
integritykey['passphraseprotected']])
else:
integritykey = '*unencrypted:{0}'.format(base64.b64encode(
integritykey['unencryptedvalue']))
integritykey = '*unencrypted:{0}'.format(confluent.util.stringify(base64.b64encode(
integritykey['unencryptedvalue'])))
keydata['integritykey'] = integritykey
if dojson:
return json.dumps(keydata, sort_keys=True, indent=4, separators=(',', ': '))
+14 -10
View File
@@ -106,7 +106,7 @@ def pytechars2line(chars, maxlen=None):
len = 1
for charidx in range(maxlen):
char = chars[charidx]
csi = []
csi = bytearray([])
if char.fg != lfg:
csi.append(30 + pytecolors2ansi[char.fg])
lfg = char.fg
@@ -129,7 +129,7 @@ def pytechars2line(chars, maxlen=None):
lr = char.reverse
csi.append(7 if lr else 27)
if csi:
line += b'\x1b[' + b';'.join(['{0}'.format(x) for x in csi]) + b'm'
line += b'\x1b[' + b';'.join(['{0}'.format(x).encode('utf-8') for x in csi]) + b'm'
if not hasdata and char.data.rstrip():
hasdata = True
chardata = char.data
@@ -362,13 +362,17 @@ class ConsoleHandler(object):
if self.reconnect:
self.reconnect.cancel()
self.reconnect = None
strerror = ('The console.method attribute for this node is '
'not configured,\r\nset it to a valid value for console '
'function')
try:
self._console = list(plugin.handle_path(
self._plugin_path.format(self.node),
"create", self.cfgmgr))[0]
except (exc.NotImplementedException, exc.NotFoundException):
self._console = None
except:
except Exception as e:
strerror = str(e)
if _tracelog:
_tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
event=log.Events.stacktrace)
@@ -381,13 +385,9 @@ class ConsoleHandler(object):
self._send_rcpts({'connectstate': self.connectstate,
'error': self.error})
self.feedbuffer(
'\x1bc\x1b[2J\x1b[1;1H[The console.method attribute for this node is '
'not configured,\r\nset it to a valid value for console '
'function]')
'\x1bc\x1b[2J\x1b[1;1H[{0}]'.format(strerror))
self._send_rcpts(
'\x1bc\x1b[2J\x1b[1;1H[The console.method attribute for this node is '
'not configured,\r\nset it to a valid value for console '
'function]')
'\x1bc\x1b[2J\x1b[1;1H[{0}]'.format(strerror))
self.clearerror = True
return
if self.clearerror:
@@ -531,9 +531,11 @@ class ConsoleHandler(object):
if data == conapi.ConsoleEvent.Disconnect:
self._got_disconnected()
return
elif data == '':
elif data in (b'', u''):
# ignore empty strings from a cconsole provider
return
if not isinstance(data, bytes):
data = data.encode('utf-8')
if b'\x1b[?1l' in data: # request for ansi mode cursor keys
self.appmodedetected = False
if b'\x1b[?1h' in data: # remember the session wants the client to use
@@ -616,6 +618,8 @@ class ConsoleHandler(object):
def write(self, data):
if self.connectstate == 'connected':
try:
if isinstance(data, str) and not isinstance(data, bytes):
data = data.encode('utf-8')
self._console.write(data)
except Exception:
_tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
+28 -12
View File
@@ -63,14 +63,16 @@ import itertools
import os
try:
import cPickle as pickle
pargs = {}
except ImportError:
import pickle
pargs = {'encoding': 'utf-8'}
import socket
import struct
import sys
pluginmap = {}
dispatch_plugins = (b'ipmi', u'ipmi')
dispatch_plugins = (b'ipmi', u'ipmi', b'redfish', u'redfish', b'tsmsol', u'tsmsol')
def seek_element(currplace, currkey):
@@ -222,7 +224,7 @@ def _init_core():
'advanced': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
}),
},
},
'storage': {
@@ -450,7 +452,7 @@ def show_usergroup(groupname, configmanager):
def show_user(name, configmanager):
userobj = configmanager.get_user(name)
rv = {}
for attr in attrscheme.user.iterkeys():
for attr in attrscheme.user:
rv[attr] = None
if attr == 'password':
if 'cryptpass' in userobj:
@@ -687,7 +689,10 @@ def handle_dispatch(connection, cert, dispatch, peername):
cfm.get_collective_member(peername)['fingerprint'], cert):
connection.close()
return
dispatch = pickle.loads(dispatch)
pversion = 0
if bytearray(dispatch)[0] == 0x80:
pversion = bytearray(dispatch)[1]
dispatch = pickle.loads(dispatch, **pargs)
configmanager = cfm.ConfigManager(dispatch['tenant'])
nodes = dispatch['nodes']
inputdata = dispatch['inputdata']
@@ -723,18 +728,18 @@ def handle_dispatch(connection, cert, dispatch, peername):
configmanager=configmanager,
inputdata=inputdata))
for res in itertools.chain(*passvalues):
_forward_rsp(connection, res)
_forward_rsp(connection, res, pversion)
except Exception as res:
_forward_rsp(connection, res)
_forward_rsp(connection, res, pversion)
connection.sendall('\x00\x00\x00\x00\x00\x00\x00\x00')
def _forward_rsp(connection, res):
def _forward_rsp(connection, res, pversion):
try:
r = pickle.dumps(res)
r = pickle.dumps(res, protocol=pversion)
except TypeError:
r = pickle.dumps(Exception(
'Cannot serialize error, check collective.manager error logs for details' + str(res)))
'Cannot serialize error, check collective.manager error logs for details' + str(res)), protocol=pversion)
rlen = len(r)
if not rlen:
return
@@ -963,12 +968,20 @@ def dispatch_request(nodes, manager, element, configmanager, inputdata,
if not util.cert_matches(a['fingerprint'], remote.getpeercert(
binary_form=True)):
raise Exception("Invalid certificate on peer")
tlvdata.recv(remote)
banner = tlvdata.recv(remote)
vers = banner.split()[2]
if vers == b'v0':
pvers = 2
elif vers == b'v1':
pvers = 4
if sys.version_info[0] < 3:
pvers = 2
tlvdata.recv(remote)
myname = collective.get_myname()
dreq = pickle.dumps({'name': myname, 'nodes': list(nodes),
'path': element,'tenant': configmanager.tenant,
'operation': operation, 'inputdata': inputdata})
'operation': operation, 'inputdata': inputdata},
protocol=pvers)
tlvdata.send(remote, {'dispatch': {'name': myname, 'length': len(dreq)}})
remote.sendall(dreq)
while True:
@@ -1015,7 +1028,10 @@ def dispatch_request(nodes, manager, element, configmanager, inputdata,
a['name']))
return
rsp += nrsp
rsp = pickle.loads(rsp)
try:
rsp = pickle.loads(rsp, **pargs)
except UnicodeDecodeError:
rsp = pickle.loads(rsp, encoding='latin1')
if isinstance(rsp, Exception):
raise rsp
yield rsp
+15 -4
View File
@@ -91,6 +91,11 @@ import eventlet.semaphore
autosensors = set()
scanner = None
try:
unicode
except NameError:
unicode = str
class nesteddict(dict):
def __missing__(self, key):
@@ -371,7 +376,7 @@ def handle_autosense_config(operation, inputdata):
yield msg.KeyValueData({'enabled': autosense})
elif operation == 'update':
enabled = inputdata['enabled']
if type(enabled) in (unicode, str):
if type(enabled) in (unicode, bytes):
enabled = enabled.lower() in ('true', '1', 'y', 'yes', 'enable',
'enabled')
if autosense == enabled:
@@ -705,7 +710,8 @@ def detected(info):
def b64tohex(b64str):
bd = base64.b64decode(b64str)
return ''.join(['{0:02x}'.format(ord(x)) for x in bd])
bd = bytearray(bd)
return ''.join(['{0:02x}'.format(x) for x in bd])
def get_enclosure_chain_head(nodename, cfg):
@@ -943,9 +949,9 @@ def eval_node(cfg, handler, info, nodename, manual=False):
# raise exc.InvalidArgumentException(errorstr)
# log.log({'error': errorstr})
if encuuid in pending_by_uuid:
pending_by_uuid[encuuid].add(info)
pending_by_uuid[encuuid].append(info)
else:
pending_by_uuid[encuuid] = set([info])
pending_by_uuid[encuuid] = [info]
return
# We found the real smm, replace the list with the actual smm
# to continue
@@ -1088,6 +1094,10 @@ def discover_node(cfg, handler, info, nodename, manual):
info['discostatus'] = 'discovered'
for i in pending_by_uuid.get(curruuid, []):
eventlet.spawn_n(_recheck_single_unknown_info, cfg, i)
try:
del pending_by_uuid[curruuid]
except KeyError:
pass
return True
log.log({'info': 'Detected {0}, but discovery.policy is not set to a '
'value allowing discovery (open or permissive)'.format(
@@ -1207,6 +1217,7 @@ def rescan():
def start_detection():
global attribwatcher
global rechecker
global rechecktime
_map_unique_ids()
cfg = cfm.ConfigManager(None)
allnodes = cfg.list_nodes()
@@ -68,17 +68,21 @@ class NodeHandler(object):
def _savecert(self, certificate):
self._fp = certificate
return True
def get_node_credentials(self, nodename, creds, defuser, defpass):
user = creds.get(nodename, {}).get(
'secret.hardwaremanagementuser', {}).get('value', None)
havecustomcreds = False
if user and not isinstance(user, str):
user = user.decode('utf8')
if user is not None and user != defuser:
havecustomcreds = True
else:
user = defuser
passwd = creds.get(nodename, {}).get(
'secret.hardwaremanagementpassword', {}).get('value', None)
if passwd and not isinstance(passwd, str):
passwd = passwd.decode('utf8')
if passwd is not None and passwd != defpass:
havecustomcreds = True
else:
@@ -12,9 +12,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import codecs
import confluent.discovery.handlers.bmc as bmchandler
import pyghmi.exceptions as pygexc
import pyghmi.ipmi.private.util as pygutil
import confluent.util as util
import struct
class NodeHandler(bmchandler.NodeHandler):
@@ -37,8 +39,9 @@ class NodeHandler(bmchandler.NodeHandler):
if wronguuid:
# we need to fix the first three portions of the uuid
uuidprefix = wronguuid.split('-')[:3]
uuidprefix = struct.pack(
'<IHH', *[int(x, 16) for x in uuidprefix]).encode('hex')
uuidprefix = codecs.encode(struct.pack(
'<IHH', *[int(x, 16) for x in uuidprefix]), 'hex')
uuidprefix = util.stringify(uuidprefix)
uuidprefix = uuidprefix[:8] + '-' + uuidprefix[8:12] + '-' + \
uuidprefix[12:16]
self.info['uuid'] = uuidprefix + '-' + '-'.join(
@@ -12,14 +12,19 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import codecs
import confluent.discovery.handlers.bmc as bmchandler
import confluent.exceptions as exc
import eventlet
webclient = eventlet.import_patched('pyghmi.util.webclient')
import struct
import urllib
try:
from urllib import urlencode
except ImportError:
from urllib.parse import urlencode
import eventlet.support.greendns
import confluent.netutil as netutil
import confluent.util as util
getaddrinfo = eventlet.support.greendns.getaddrinfo
from xml.etree.ElementTree import fromstring
@@ -27,8 +32,9 @@ from xml.etree.ElementTree import fromstring
def fixuuid(baduuid):
# SMM dumps it out in hex
uuidprefix = (baduuid[:8], baduuid[8:12], baduuid[12:16])
a = struct.pack('<IHH', *[int(x, 16) for x in uuidprefix]).encode(
a = codecs.encode(struct.pack('<IHH', *[int(x, 16) for x in uuidprefix]),
'hex')
a = util.stringify(a)
uuid = (a[:8], a[8:12], a[12:16], baduuid[16:20], baduuid[20:])
return '-'.join(uuid).lower()
@@ -92,7 +98,7 @@ class NodeHandler(bmchandler.NodeHandler):
setdata += ',v4Gateway:{0}'.format(gateway)
wc.request('POST', '/data', setdata)
rsp = wc.getresponse()
rspdata = rsp.read()
rspdata = util.stringify(rsp.read())
if '<statusCode>0' not in rspdata:
raise Exception("Error configuring SMM Network")
return
@@ -110,16 +116,16 @@ class NodeHandler(bmchandler.NodeHandler):
'password': 'PASSW0RD',
}
headers = {'Connection': 'keep-alive', 'Content-Type': 'application/x-www-form-urlencoded'}
wc.request('POST', '/data/login', urllib.urlencode(authdata), headers)
wc.request('POST', '/data/login', urlencode(authdata), headers)
rsp = wc.getresponse()
rspdata = rsp.read()
rspdata = util.stringify(rsp.read())
if 'authResult>0' not in rspdata:
# default credentials are refused, try with the actual
authdata['user'] = username
authdata['password'] = password
wc.request('POST', '/data/login', urllib.urlencode(authdata), headers)
wc.request('POST', '/data/login', urlencode(authdata), headers)
rsp = wc.getresponse()
rspdata = rsp.read()
rspdata = util.stringify(rsp.read())
if 'renew_account' in rspdata:
raise Exception('Configured password has expired')
if 'authResult>0' not in rspdata:
@@ -133,13 +139,13 @@ class NodeHandler(bmchandler.NodeHandler):
tokens = fromstring(rspdata)
st2 = tokens.findall('st2')[0].text
wc.set_header('ST2', st2)
wc.request('POST', '/data/changepwd', urllib.urlencode(passwdchange))
wc.request('POST', '/data/changepwd', urlencode(passwdchange))
rsp = wc.getresponse()
rspdata = rsp.read()
authdata['password'] = password
wc.request('POST', '/data/login', urllib.urlencode(authdata), headers)
wc.request('POST', '/data/login', urlencode(authdata), headers)
rsp = wc.getresponse()
rspdata = rsp.read()
rspdata = util.stringify(rsp.read())
if 'authResult>0' in rspdata:
tokens = fromstring(rspdata)
st2 = tokens.findall('st2')[0].text
@@ -153,7 +159,7 @@ class NodeHandler(bmchandler.NodeHandler):
rsp = wc.getresponse()
rspdata = rsp.read()
authdata['user'] = username
wc.request('POST', '/data/login', urllib.urlencode(authdata, headers))
wc.request('POST', '/data/login', urlencode(authdata, headers))
rsp = wc.getresponse()
rspdata = rsp.read()
tokens = fromstring(rspdata)
@@ -175,6 +181,10 @@ class NodeHandler(bmchandler.NodeHandler):
'secret.hardwaremanagementuser', {}).get('value', 'USERID')
passwd = creds.get(nodename, {}).get(
'secret.hardwaremanagementpassword', {}).get('value', 'PASSW0RD')
if not isinstance(username, str):
username = username.decode('utf8')
if not isinstance(passwd, str):
passwd = passwd.decode('utf8')
if passwd == 'PASSW0RD' and self.ruleset:
raise Exception('Cannot support default password and setting password rules at same time')
if passwd == 'PASSW0RD':
@@ -18,7 +18,11 @@ import confluent.netutil as netutil
import confluent.util as util
import eventlet.support.greendns
import json
import urllib
try:
from urllib import urlencode
except ImportError:
from urllib.parse import urlencode
getaddrinfo = eventlet.support.greendns.getaddrinfo
webclient = eventlet.import_patched('pyghmi.util.webclient')
@@ -40,6 +44,13 @@ class NodeHandler(generic.NodeHandler):
self.atdefault = True
super(NodeHandler, self).__init__(info, configmanager)
def scan(self):
c = webclient.SecureHTTPConnection(self.ipaddr, 443, verifycallback=self.validate_cert)
i = c.grab_json_response('/redfish/v1/')
uuid = i.get('UUID', None)
if uuid:
self.info['uuid'] = uuid
def validate_cert(self, certificate):
# broadly speaking, merely checks consistency moment to moment,
# but if https_cert gets stricter, this check means something
@@ -51,10 +62,19 @@ class NodeHandler(generic.NodeHandler):
'username': self.DEFAULT_USER,
'password': self.DEFAULT_PASS,
}
wc = webclient.SecureHTTPConnection(self.ipaddr, 443, verifycallback=self.validate_cert)
wc.set_header('Content-Type', 'application/json')
authmode = 0
if not self.trieddefault:
wc = webclient.SecureHTTPConnection(self.ipaddr, 443, verifycallback=self.validate_cert)
rsp, status = wc.grab_json_response_with_status('/api/session', urllib.urlencode(authdata))
rsp, status = wc.grab_json_response_with_status('/api/session', authdata)
if status == 403:
wc.set_header('Content-Type', 'application/x-www-form-urlencoded')
authmode = 1
rsp, status = wc.grab_json_response_with_status('/api/session', urlencode(authdata))
else:
authmode = 2
if status > 400:
rsp = util.stringify(rsp)
self.trieddefault = True
if '555' in rsp:
passchange = {
@@ -64,9 +84,15 @@ class NodeHandler(generic.NodeHandler):
'default_password': self.DEFAULT_PASS,
'username': self.DEFAULT_USER
}
rsp, status = wc.grab_json_response_with_status('/api/reset-pass', urllib.urlencode(passchange))
if authmode == 2:
rsp, status = wc.grab_json_response_with_status('/api/reset-pass', passchange)
else:
rsp, status = wc.grab_json_response_with_status('/api/reset-pass', urlencode(passchange))
authdata['password'] = self.targpass
rsp, status = wc.grab_json_response_with_status('/api/session', urllib.urlencode(authdata))
if authmode == 2:
rsp, status = wc.grab_json_response_with_status('/api/session', authdata)
else:
rsp, status = wc.grab_json_response_with_status('/api/session', urlencode(authdata))
self.csrftok = rsp['CSRFToken']
self.channel = rsp['channel']
self.curruser = self.DEFAULT_USER
@@ -81,15 +107,23 @@ class NodeHandler(generic.NodeHandler):
if self.curruser:
authdata['username'] = self.curruser
authdata['password'] = self.currpass
rsp, status = wc.grab_json_response_with_status('/api/session', urllib.urlencode(authdata))
if rsp.status != 200:
if authmode != 1:
rsp, status = wc.grab_json_response_with_status('/api/session', authdata)
if authmode == 1 or status == 403:
wc.set_header('Content-Type', 'application/x-www-form-urlencoded')
rsp, status = wc.grab_json_response_with_status('/api/session', urlencode(authdata))
if status != 200:
return None
self.csrftok = rsp['CSRFToken']
self.channel = rsp['channel']
return wc
authdata['username'] = self.targuser
authdata['password'] = self.targpass
rsp, status = wc.grab_json_response_with_status('/api/session', urllib.urlencode(authdata))
if authmode != 1:
rsp, status = wc.grab_json_response_with_status('/api/session', authdata)
if authmode == 1 or status == 403:
wc.set_header('Content-Type', 'application/x-www-form-urlencoded')
rsp, status = wc.grab_json_response_with_status('/api/session', urlencode(authdata))
if status != 200:
return None
self.curruser = self.targuser
@@ -108,6 +142,8 @@ class NodeHandler(generic.NodeHandler):
cd = creds.get(nodename, {})
user, passwd, _ = self.get_node_credentials(
nodename, creds, self.DEFAULT_USER, self.DEFAULT_PASS)
user = util.stringify(user)
passwd = util.stringify(passwd)
self.targuser = user
self.targpass = passwd
wc = self._get_wc()
@@ -13,7 +13,9 @@
# limitations under the License.
import base64
import codecs
import confluent.discovery.handlers.imm as immhandler
import confluent.exceptions as exc
import confluent.netutil as netutil
import confluent.util as util
import errno
@@ -31,7 +33,8 @@ getaddrinfo = eventlet.support.greendns.getaddrinfo
def fixup_uuid(uuidprop):
baduuid = ''.join(uuidprop.split())
uuidprefix = (baduuid[:8], baduuid[8:12], baduuid[12:16])
a = struct.pack('<IHH', *[int(x, 16) for x in uuidprefix]).encode('hex')
a = codecs.encode(struct.pack('<IHH', *[int(x, 16) for x in uuidprefix]), 'hex')
a = util.stringify(a)
uuid = (a[:8], a[8:12], a[12:16], baduuid[16:20], baduuid[20:])
return '-'.join(uuid).upper()
@@ -93,7 +96,8 @@ class NodeHandler(immhandler.NodeHandler):
ipmicmd.xraw_command(netfn=0x3a, command=0xf1, data=(1,))
except pygexc.IpmiException as e:
if (e.ipmicode != 193 and 'Unauthorized name' not in str(e) and
'Incorrect password' not in str(e)):
'Incorrect password' not in str(e) and
str(e) != 'Session no longer connected'):
# raise an issue if anything other than to be expected
if disableipmi:
_, _ = wc.grab_json_response_with_status(
@@ -125,8 +129,8 @@ class NodeHandler(immhandler.NodeHandler):
raise
return (None, None)
pwdchanged = False
adata = json.dumps({'username': username,
'password': password
adata = json.dumps({'username': util.stringify(username),
'password': util.stringify(password)
})
headers = {'Connection': 'keep-alive',
'Content-Type': 'application/json'}
@@ -168,7 +172,12 @@ class NodeHandler(immhandler.NodeHandler):
pwdchanged = True
if '_csrf_token' in wc.cookies:
wc.set_header('X-XSRF-TOKEN', wc.cookies['_csrf_token'])
if pwdchanged:
# Remove the minimum change interval, to allow sane
# password changes after provisional changes
self.set_password_policy('')
return (wc, pwdchanged)
return (None, None)
@property
def wc(self):
@@ -202,7 +211,7 @@ class NodeHandler(immhandler.NodeHandler):
# however the target *will* demand a new password... if it's currently
# PASSW0RD
# use TempW0rd42 to avoid divulging a real password on the line
# This is replacing one well known password (PASSW0RD) with another
# This is replacing one well known password (PASSW0RD) with another
# (TempW0rd42)
passwd = 'TempW0rd42'
wc, pwdchanged = self.get_webclient('USERID', 'PASSW0RD', passwd)
@@ -224,9 +233,9 @@ class NodeHandler(immhandler.NodeHandler):
if wc:
return wc
def set_password_policy(self):
def set_password_policy(self, strruleset):
ruleset = {'USER_GlobalMinPassChgInt': '0'}
for rule in self.ruleset.split(','):
for rule in strruleset.split(','):
if '=' not in rule:
continue
name, value = rule.split('=')
@@ -289,6 +298,8 @@ class NodeHandler(immhandler.NodeHandler):
userinfo = wc.grab_json_response('/api/dataset/imm_users')
curruser = None
uid = None
user = util.stringify(user)
passwd = util.stringify(passwd)
for userent in userinfo['items'][0]['users']:
if userent['users_user_name'] == user:
curruser = userent
@@ -346,15 +357,18 @@ class NodeHandler(immhandler.NodeHandler):
# between hypothetical secure path and today.
dpp = self.configmanager.get_node_attributes(
nodename, 'discovery.passwordrules')
self.ruleset = dpp.get(nodename, {}).get(
strruleset = dpp.get(nodename, {}).get(
'discovery.passwordrules', {}).get('value', '')
wc = self.wc
creds = self.configmanager.get_node_attributes(
self.nodename, ['secret.hardwaremanagementuser',
'secret.hardwaremanagementpassword'], decrypt=True)
user, passwd, isdefault = self.get_node_credentials(nodename, creds, 'USERID', 'PASSW0RD')
self.set_password_policy()
self.set_password_policy(strruleset)
if self._atdefaultcreds:
if isdefault and self.tmppasswd:
raise Exception(
'Request to use default credentials, but refused by target after it has been changed to {0}'.format(self.tmppasswd))
if not isdefault:
self._setup_xcc_account(user, passwd, wc)
self._convert_sha256account(user, passwd, wc)
@@ -34,8 +34,8 @@ pxearchs = {
def decode_uuid(rawguid):
lebytes = struct.unpack_from('<IHH', buffer(rawguid[:8]))
bebytes = struct.unpack_from('>HHI', buffer(rawguid[8:]))
lebytes = struct.unpack_from('<IHH', rawguid[:8])
bebytes = struct.unpack_from('>HHI', rawguid[8:])
return '{0:08X}-{1:04X}-{2:04X}-{3:04X}-{4:04X}{5:08X}'.format(
lebytes[0], lebytes[1], lebytes[2], bebytes[0], bebytes[1], bebytes[2]).lower()
@@ -84,6 +84,8 @@ def _parse_SrvRply(parsed):
:return:
"""
payload = parsed['payload']
if len(payload) < 4:
return
ecode, ucount = struct.unpack('!HH', bytes(payload[0:4]))
if ecode:
parsed['errorcode'] = ecode
@@ -234,40 +236,42 @@ def _find_srvtype(net, net4, srvtype, addresses, xid):
def _grab_rsps(socks, rsps, interval, xidmap):
r, _, _ = select.select(socks, (), (), interval)
r = None
res = select.select(socks, (), (), interval)
if res:
r = res[0]
while r:
for s in r:
(rsp, peer) = s.recvfrom(9000)
neighutil.refresh_neigh()
_parse_slp_packet(rsp, peer, rsps, xidmap)
r, _, _ = select.select(socks, (), (), interval)
res = select.select(socks, (), (), interval)
if not res:
r = None
else:
r = res[0]
def _parse_attrlist(attrstr):
attribs = {}
previousattrlen = None
attrstr = util.stringify(attrstr)
while attrstr:
if len(attrstr) == previousattrlen:
raise Exception('Looping in attrstr parsing')
previousattrlen = len(attrstr)
if attrstr[0] == '(':
if b')' not in attrstr:
if ')' not in attrstr:
attribs['INCOMPLETE'] = True
return attribs
currattr = attrstr[1:attrstr.index(b')')]
if b'=' not in currattr: # Not allegedly kosher, but still..
currattr = currattr.decode('utf-8')
currattr = attrstr[1:attrstr.index(')')]
if '=' not in currattr: # Not allegedly kosher, but still..
attribs[currattr] = None
else:
attrname, attrval = currattr.split('=', 1)
attrname = attrname.decode('utf-8')
attribs[attrname] = []
for val in attrval.split(b','):
try:
val = val.decode('utf-8')
except UnicodeDecodeError:
val = '*DECODEERROR*'
for val in attrval.split(','):
if val[:3] == '\\FF': # we should make this bytes
finalval = bytearray([])
for bnum in attrval[3:].split('\\'):
@@ -287,12 +291,12 @@ def _parse_attrlist(attrstr):
).lower()
attribs[attrname].append(val)
attrstr = attrstr[attrstr.index(')'):]
elif attrstr[0] == b','[0]:
elif attrstr[0] == ','[0]:
attrstr = attrstr[1:]
elif b',' in attrstr:
currattr = attrstr[:attrstr.index(b',')]
elif ',' in attrstr:
currattr = attrstr[:attrstr.index(',')]
attribs[currattr] = None
attrstr = attrstr[attrstr.index(b','):]
attrstr = attrstr[attrstr.index(','):]
else:
currattr = attrstr
attribs[currattr] = None
@@ -565,7 +569,7 @@ def scan(srvtypes=_slp_services, addresses=None, localonly=False):
# now to analyze and flesh out the responses
for id in rsps:
if 'service:ipmi' in rsps[id]['services']:
if 'service:ipmi://Athena:623' in rsps[id]['urls']:
if 'service:ipmi://Athena:623' in rsps[id].get('urls', ''):
rsps[id]['services'] = ['service:thinkagile-storage']
else:
continue
+4 -1
View File
@@ -107,11 +107,14 @@ class PubkeyInvalid(ConfluentException):
self.fingerprint = fingerprint
self.attrname = attribname
self.message = text
certtxt = base64.b64encode(certificate)
if not isinstance(certtxt, str):
certtxt = certtxt.decode('utf8')
bodydata = {'message': text,
'event': event,
'fingerprint': fingerprint,
'fingerprintfield': attribname,
'certificate': base64.b64encode(certificate)}
'certificate': certtxt}
self.errorbody = json.dumps(bodydata)
def get_error_body(self):
@@ -43,13 +43,16 @@ def execupdate(handler, filename, updateobj, type, owner, node):
'detail': errstr})
return
if type == 'ffdc' and os.path.isdir(filename):
filename += '/' + node + '.svcdata'
filename += '/' + node
try:
if type == 'firmware':
completion = handler(filename, progress=updateobj.handle_progress,
bank=updateobj.bank)
else:
completion = handler(filename, progress=updateobj.handle_progress)
if type == 'ffdc' and completion:
filename = completion
completion = None
if completion is None:
completion = 'complete'
if owner:
+20 -15
View File
@@ -1,7 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 IBM Corporation
# Copyright 2015-2016 Lenovo
# Copyright 2015-2019 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -80,7 +80,7 @@ def group_creation_resources():
yield confluent.messages.ListAttributes(kv={'nodes': []},
desc='Nodes to add to the group'
).html() + '<br>\n'
for attr in sorted(attribs.node.iterkeys()):
for attr in sorted(attribs.node):
if attr == 'groups':
continue
if attr.startswith("secret."):
@@ -101,7 +101,7 @@ def group_creation_resources():
def node_creation_resources():
yield confluent.messages.Attributes(
kv={'name': None}, desc="Name of the node").html() + '<br>'
for attr in sorted(attribs.node.iterkeys()):
for attr in sorted(attribs.node):
if attr.startswith("secret."):
yield confluent.messages.CryptedAttributes(
kv={attr: None},
@@ -132,7 +132,7 @@ def user_creation_resources():
'description': (''),
},
}
for attr in sorted(credential.iterkeys()):
for attr in sorted(credential):
if attr == "password":
yield confluent.messages.CryptedAttributes(
kv={attr: None},
@@ -182,7 +182,7 @@ def _get_query_dict(env, reqbody, reqtype):
if reqbody is not None:
if "application/x-www-form-urlencoded" in reqtype:
pbody = urlparse.parse_qs(reqbody, True)
for ky in pbody.iterkeys():
for ky in pbody:
if len(pbody[ky]) > 1: # e.g. REST explorer
na = [i for i in pbody[ky] if i != '']
qdict[ky] = na
@@ -190,7 +190,7 @@ def _get_query_dict(env, reqbody, reqtype):
qdict[ky] = pbody[ky][0]
elif 'application/json' in reqtype:
pbody = json.loads(reqbody)
for key in pbody.iterkeys():
for key in pbody:
qdict[key] = pbody[key]
if 'restexplorerhonorkey' in qdict:
nqdict = {}
@@ -311,7 +311,7 @@ def _authorize_request(env, operation):
return {'code': 401}
return ('logout',)
name, passphrase = base64.b64decode(
env['HTTP_AUTHORIZATION'].replace('Basic ', '')).split(':', 1)
env['HTTP_AUTHORIZATION'].replace('Basic ', '')).split(b':', 1)
authdata = auth.check_user_passphrase(name, passphrase, operation=operation, element=element)
if authdata is False:
return {'code': 403}
@@ -325,14 +325,14 @@ def _authorize_request(env, operation):
'inflight': set([])}
if 'HTTP_CONFLUENTAUTHTOKEN' in env:
httpsessions[sessid]['csrftoken'] = util.randomstring(32)
cookie['confluentsessionid'] = sessid
cookie['confluentsessionid'] = util.stringify(sessid)
cookie['confluentsessionid']['secure'] = 1
cookie['confluentsessionid']['httponly'] = 1
cookie['confluentsessionid']['path'] = '/'
skiplog = _should_skip_authlog(env)
if authdata:
auditmsg = {
'user': name,
'user': util.stringify(name),
'operation': operation,
'target': env['PATH_INFO'],
}
@@ -344,7 +344,7 @@ def _authorize_request(env, operation):
if authdata[3] is not None:
auditmsg['tenant'] = authdata[3]
authinfo['tenant'] = authdata[3]
auditmsg['user'] = authdata[2]
auditmsg['user'] = util.stringify(authdata[2])
if sessid is not None:
authinfo['sessionid'] = sessid
if not skiplog:
@@ -452,6 +452,8 @@ def resourcehandler_backend(env, start_response):
httpsessions[authorized['sessionid']]['inflight'])):
pagecontent += rsp
start_response("200 OK", headers)
if not isinstance(pagecontent, bytes):
pagecontent = pagecontent.encode('utf-8')
yield pagecontent
return
except exc.ConfluentException as e:
@@ -497,7 +499,7 @@ def resourcehandler_backend(env, start_response):
auditmsg = {
'operation': 'start',
'target': env['PATH_INFO'],
'user': authorized['username'],
'user': util.stringify(authorized['username']),
}
if 'tenant' in authorized:
auditmsg['tenant'] = authorized['tenant']
@@ -632,6 +634,7 @@ def resourcehandler_backend(env, start_response):
sessinfo = {'username': authorized['username']}
if 'authtoken' in authorized:
sessinfo['authtoken'] = authorized['authtoken']
tlvdata.unicode_dictvalues(sessinfo)
yield json.dumps(sessinfo)
return
resource = '.' + url[url.rindex('/'):]
@@ -653,6 +656,8 @@ def resourcehandler_backend(env, start_response):
for datum in _assemble_json(hdlr, resource, url, extension):
pagecontent += datum
start_response('200 OK', headers)
if not isinstance(pagecontent, bytes):
pagecontent = pagecontent.encode('utf-8')
yield pagecontent
except exc.ConfluentException as e:
if ((not isinstance(e, exc.LockedCredentials)) and
@@ -737,7 +742,7 @@ def _assemble_json(responses, resource=None, url=None, extension=None):
for rsp in responses:
if isinstance(rsp, confluent.messages.LinkRelation):
haldata = rsp.raw()
for hk in haldata.iterkeys():
for hk in haldata:
if 'href' in haldata[hk]:
if isinstance(haldata[hk]['href'], int):
haldata[hk]['href'] = str(haldata[hk]['href'])
@@ -753,7 +758,7 @@ def _assemble_json(responses, resource=None, url=None, extension=None):
links[hk] = haldata[hk]
else:
rsp = rsp.raw()
for dk in rsp.iterkeys():
for dk in rsp:
if dk in rspdata:
if isinstance(rspdata[dk], list):
if isinstance(rsp[dk], list):
@@ -772,8 +777,8 @@ def _assemble_json(responses, resource=None, url=None, extension=None):
rspdata[dk] = rsp[dk]
rspdata["_links"] = links
tlvdata.unicode_dictvalues(rspdata)
yield json.dumps(
rspdata, sort_keys=True, indent=4, ensure_ascii=False).encode('utf-8')
yield util.stringify(json.dumps(
rspdata, sort_keys=True, indent=4, ensure_ascii=False).encode('utf-8'))
def serve(bind_host, bind_port):
+6 -2
View File
@@ -600,7 +600,11 @@ class Logger(object):
if ltype == 2:
textrecord = data
else:
textrecord = textdate + data + ']'
if not isinstance(textdate, bytes):
textdate = textdate.encode('utf-8')
if not isinstance(data, bytes):
data = data.encode('utf-8')
textrecord = textdate + data + b']'
else:
textrecord = textdate + data
if not textrecord.endswith('\n'):
@@ -798,4 +802,4 @@ def logtrace():
if tracelog is None:
tracelog = Logger('trace', buffered=False)
tracelog.log(traceback.format_exc(), ltype=DataTypes.event,
event=Events.stacktrace)
event=Events.stacktrace)
+18 -7
View File
@@ -23,6 +23,7 @@ import confluent.config.configmanager as cfm
import confluent.config.conf as cfgfile
from copy import deepcopy
from datetime import datetime
import confluent.util as util
import json
try:
@@ -59,7 +60,9 @@ def _htmlify_structure(indict):
if isinstance(indict, dict):
for key in sorted(indict):
ret += "<li>{0}: ".format(key)
if type(indict[key]) in (bytes, unicode, float, int):
if type(indict[key]) in (bytes, unicode):
ret += util.stringify(indict[key])
if type(indict[key]) in (float, int):
ret += str(indict[key])
elif isinstance(indict[key], datetime):
ret += indict[key].strftime('%Y-%m-%dT%H:%M:%S')
@@ -73,7 +76,7 @@ def _htmlify_structure(indict):
if datum is None:
nd.append('')
else:
nd.append(datum)
nd.append(util.stringify(datum))
ret += ",".join(nd)
else:
for v in indict:
@@ -126,15 +129,16 @@ class ConfluentMessage(object):
return self._generic_html_value(self.kvpairs)
if not self.stripped:
htmlout = ''
for node in self.kvpairs.iterkeys():
for node in self.kvpairs:
htmlout += '{0}:{1}\n'.format(
node, self._generic_html_value(self.kvpairs[node]))
return htmlout
def _generic_html_value(self, pairs):
snippet = ""
for key in pairs.iterkeys():
for key in pairs:
val = pairs[key]
key = util.stringify(key)
value = self.defaultvalue
if isinstance(val, dict) and 'type' in val:
valtype = val['type']
@@ -326,15 +330,16 @@ class ConfluentChoiceMessage(ConfluentMessage):
return self._create_option(self.kvpairs)
else:
htmlout = ''
for node in self.kvpairs.iterkeys():
for node in self.kvpairs:
htmlout += '{0}:{1}\n'.format(
node, self._create_option(self.kvpairs[node]))
return htmlout
def _create_option(self, pairdata):
snippet = ''
for key in pairdata.iterkeys():
for key in pairdata:
val = pairdata[key]
key = util.stringify(key)
snippet += key + ':<select name="%s">' % key
valid_values = self.valid_values
if key in self.valid_paramset:
@@ -1374,6 +1379,8 @@ class InputAlertDestination(ConfluentMessage):
self.alertcfg = {}
if multinode: # keys are node names
for node in inputdata:
if not isinstance(inputdata[node], dict):
break
self.alertcfg[node] = inputdata[node]
for key in inputdata[node]:
if key not in self.valid_alert_params:
@@ -1386,7 +1393,8 @@ class InputAlertDestination(ConfluentMessage):
else:
self.alertcfg[node][key] = \
self.valid_alert_params[key](inputdata[node][key])
else:
else:
return
for key in inputdata:
if key not in self.valid_alert_params:
raise exc.InvalidArgumentException(
@@ -1484,12 +1492,15 @@ class Volume(ConfluentMessage):
class Disk(ConfluentMessage):
valid_states = set([
'fault',
'jbod',
'unconfigured',
'hotspare',
'rebuilding',
'online',
])
state_aliases = {
'unconfigured bad': 'fault',
'unconfigured good': 'unconfigured',
'global hot spare': 'hotspare',
'dedicated hot spare': 'hotspare',
+6 -4
View File
@@ -18,6 +18,7 @@
# Ultimately, this should use AF_NETLINK, but in the interest of time,
# use ip neigh for the moment
import confluent.util as util
import eventlet.green.subprocess as subprocess
import os
@@ -26,7 +27,7 @@ neightime = 0
import re
_validmac = re.compile(b'..:..:..:..:..:..')
_validmac = re.compile('..:..:..:..:..:..')
def update_neigh():
@@ -39,11 +40,12 @@ def update_neigh():
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
(neighdata, err) = ipn.communicate()
for entry in neighdata.split(b'\n'):
entry = entry.split(b' ')
neighdata = util.stringify(neighdata)
for entry in neighdata.split('\n'):
entry = entry.split(' ')
if len(entry) < 5 or not entry[4]:
continue
if entry[0] in (b'192.168.0.100', b'192.168.70.100', b'192.168.70.125'):
if entry[0] in ('192.168.0.100', '192.168.70.100', '192.168.70.125'):
# Note that these addresses are common static ip addresses
# that are hopelessly ambiguous if there are many
# so ignore such entries and move on
@@ -125,7 +125,8 @@ def _dump_neighbordatum(info):
def b64tohex(b64str):
bd = base64.b64decode(b64str)
return ''.join(['{0:02x}'.format(ord(x)) for x in bd])
bd = bytearray(bd)
return ''.join(['{0:02x}'.format(x) for x in bd])
def get_fingerprint(switch, port, configmanager, portmatch):
update_switch_data(switch, configmanager)
+1 -1
View File
@@ -162,7 +162,7 @@ class NodeRange(object):
pieces = seqrange.split(delimiter)
if len(pieces) % 2 != 0:
return self.failorreturn(seqrange)
halflen = len(pieces) / 2
halflen = len(pieces) // 2
left = delimiter.join(pieces[:halflen])
right = delimiter.join(pieces[halflen:])
leftbits = _numextractor.parseString(left).asList()
@@ -0,0 +1,160 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2015-2019 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# This plugin provides an ssh implementation comforming to the 'console'
# specification. consoleserver or shellserver would be equally likely
# to use this.
import confluent.exceptions as cexc
import confluent.interface.console as conapi
import confluent.log as log
import confluent.util as util
import pyghmi.exceptions as pygexc
import pyghmi.redfish.command as rcmd
import eventlet
import eventlet.green.ssl as ssl
try:
websocket = eventlet.import_patched('websocket')
wso = websocket.WebSocket
except Exception:
wso = object
def get_conn_params(node, configdata):
if 'secret.hardwaremanagementuser' in configdata:
username = configdata['secret.hardwaremanagementuser']['value']
else:
username = 'USERID'
if 'secret.hardwaremanagementpassword' in configdata:
passphrase = configdata['secret.hardwaremanagementpassword']['value']
else:
passphrase = 'PASSW0RD' # for lack of a better guess
if 'hardwaremanagement.manager' in configdata:
bmc = configdata['hardwaremanagement.manager']['value']
else:
bmc = node
return {
'username': username,
'passphrase': passphrase,
'bmc': bmc,
}
_configattributes = ('secret.hardwaremanagementuser',
'secret.hardwaremanagementpassword',
'hardwaremanagement.manager')
class WrappedWebSocket(wso):
def set_verify_callback(self, callback):
self._certverify = callback
def connect(self, url, **options):
add_tls = url.startswith('wss://')
if add_tls:
hostname, port, resource, _ = websocket._url.parse_url(url)
if hostname[0] != '[' and ':' in hostname:
hostname = '[{0}]'.format(hostname)
if resource[0] != '/':
resource = '/{0}'.format(resource)
url = 'ws://{0}:443{1}'.format(hostname,resource)
else:
return super(WrappedWebSocket, self).connect(url, **options)
self.sock_opt.timeout = options.get('timeout', self.sock_opt.timeout)
self.sock, addrs = websocket._http.connect(url, self.sock_opt, websocket._http.proxy_info(**options),
options.pop('socket', None))
self.sock = ssl.wrap_socket(self.sock, cert_reqs=ssl.CERT_NONE)
# The above is supersedeed by the _certverify, which provides
# known-hosts style cert validaiton
bincert = self.sock.getpeercert(binary_form=True)
if not self._certverify(bincert):
raise pygexc.UnrecognizedCertificate('Unknown certificate', bincert)
try:
self.handshake_response = websocket._handshake.handshake(self.sock, *addrs, **options)
if self.handshake_response.status in websocket._handshake.SUPPORTED_REDIRECT_STATUSES:
options['redirect_limit'] = options.pop('redirect_limit', 3) - 1
if options['redirect_limit'] < 0:
raise Exception('Redirect limit hit')
url = self.handshake_response.headers['location']
self.sock.close()
return self.connect(url, **options)
self.connected = True
except:
if self.sock:
self.sock.close()
self.sock = None
raise
class TsmConsole(conapi.Console):
def __init__(self, node, config):
self.node = node
self.ws = None
configdata = config.get_node_attributes([node], _configattributes, decrypt=True)
connparams = get_conn_params(node, configdata[node])
self.username = connparams['username']
self.password = connparams['passphrase']
self.bmc = connparams['bmc']
self.origbmc = connparams['bmc']
if ':' in self.bmc:
self.bmc = '[{0}]'.format(self.bmc)
self.datacallback = None
self.nodeconfig = config
self.connected = False
def recvdata(self):
while self.connected:
pendingdata = self.ws.recv()
if pendingdata == '':
self.datacallback(conapi.ConsoleEvent.Disconnect)
return
self.datacallback(pendingdata)
def connect(self, callback):
self.datacallback = callback
rc = rcmd.Command(self.origbmc, self.username,
self.password,
verifycallback=lambda x: True)
wc = rc.oem.wc
bmc = self.bmc
if '%' in self.bmc:
prefix = self.bmc.split('%')[0]
bmc = prefix + ']'
self.ws = WrappedWebSocket(host=bmc)
kv = util.TLSCertVerifier(
self.nodeconfig, self.node, 'pubkeys.tls_hardwaremanager').verify_cert
self.ws.set_verify_callback(kv)
self.ws.connect('wss://{0}/sol?CSRFTOKEN={1}'.format(self.bmc, rc.oem.csrftok), host=bmc, cookie='QSESSIONID={0}'.format(wc.cookies['QSESSIONID']))
self.connected = True
eventlet.spawn_n(self.recvdata)
return
def write(self, data):
self.ws.send(data)
def close(self):
if self.ws:
self.ws.close()
self.connected = False
self.datacallback = None
def create(nodes, element, configmanager, inputdata):
if len(nodes) == 1:
return TsmConsole(nodes[0], configmanager)
@@ -78,6 +78,18 @@ def retrieve(nodes, element, configmanager, inputdata):
for node in nodes:
workers.add(eventlet.spawn(retrieve_health, configmanager, creds,
node, results))
elif element[:3] == ['inventory', 'hardware', 'all']:
creds = configmanager.get_node_attributes(
nodes, ['secret.hardwaremanagementuser', 'secret.hardwaremanagementpassword'], decrypt=True)
for node in nodes:
workers.add(eventlet.spawn(retrieve_inventory, configmanager,
creds, node, results, element))
elif element[:3] == ['inventory', 'firmware', 'all']:
creds = configmanager.get_node_attributes(
nodes, ['secret.hardwaremanagementuser', 'secret.hardwaremanagementpassword'], decrypt=True)
for node in nodes:
workers.add(eventlet.spawn(retrieve_firmware, configmanager,
creds, node, results, element))
else:
for node in nodes:
yield msg.ConfluentNodeError(node, 'Not Implemented')
@@ -105,6 +117,44 @@ def retrieve(nodes, element, configmanager, inputdata):
pass
def retrieve_inventory(configmanager, creds, node, results, element):
if len(element) == 3:
results.put(msg.ChildCollection('all'))
results.put(msg.ChildCollection('system'))
return
wc = cnos_login(node, configmanager, creds)
sysinfo = wc.grab_json_response('/nos/api/sysinfo/inventory')
invinfo = {
'inventory': [{
'name': 'System',
'present': True,
'information': {
'Product name': sysinfo['Model'],
'Serial Number': sysinfo['Electronic Serial Number'],
'Board Serial Number': sysinfo['Serial Number'],
'Manufacturer': 'Lenovo',
'Model': sysinfo['Machine Type Model'],
'FRU Number': sysinfo['FRU'].strip(),
}
}]
}
results.put(msg.KeyValueData(invinfo, node))
def retrieve_firmware(configmanager, creds, node, results, element):
if len(element) == 3:
results.put(msg.ChildCollection('all'))
return
wc = cnos_login(node, configmanager, creds)
sysinfo = wc.grab_json_response('/nos/api/sysinfo/inventory')
items = [{
'Software': {'version': sysinfo['Software Revision']},
},
{
'BIOS': {'version': sysinfo['BIOS Revision']},
}]
results.put(msg.Firmware(items, node))
def retrieve_health(configmanager, creds, node, results):
wc = cnos_login(node, configmanager, creds)
hinfo = wc.grab_json_response('/nos/api/sysinfo/globalhealthstatus')
@@ -42,6 +42,11 @@ import traceback
if not hasattr(ssl, 'SSLEOFError'):
ssl.SSLEOFError = None
try:
range = xrange
except NameError:
pass
pci_cache = {}
def get_dns_txt(qstring):
@@ -131,7 +136,7 @@ def hex2bin(hexstring):
if len(hexvals) < 2:
hexvals = hexstring.split(' ')
if len(hexvals) < 2:
hexvals = [hexstring[i:i+2] for i in xrange(0, len(hexstring), 2)]
hexvals = [hexstring[i:i+2] for i in range(0, len(hexstring), 2)]
bytedata = [int(i, 16) for i in hexvals]
return bytearray(bytedata)
@@ -278,6 +283,7 @@ def _donothing(data):
class IpmiConsole(conapi.Console):
configattributes = frozenset(_configattributes)
bmctonodemapping = {}
def __init__(self, node, config):
self.error = None
@@ -295,10 +301,21 @@ class IpmiConsole(conapi.Console):
self.bmc = connparams['bmc']
self.port = connparams['port']
self.connected = False
# ok, is self.bmc unique among nodes already
# Cannot actually create console until 'connect', when we get callback
if (self.bmc in self.bmctonodemapping and
self.bmctonodemapping[self.bmc] != node):
raise Exception(
"Duplicate hardwaremanagement.manager attribute for {0} and {1}".format(
node, self.bmctonodemapping[self.bmc]))
self.bmctonodemapping[self.bmc] = node
def __del__(self):
self.solconnection = None
try:
del self.bmctonodemapping[self.bmc]
except KeyError:
pass
def handle_data(self, data):
if type(data) == dict:
@@ -584,9 +601,13 @@ class IpmiHandler(object):
self.output.put(msg.CreatedResource(
'nodes/{0}/media/uploads/{1}'.format(self.node, u.name)))
def get_diags(self, savefile, progress):
return self.ipmicmd.get_diagnostic_data(
savefile, progress=progress, autosuffix=True)
def handle_servicedata_fetch(self):
u = firmwaremanager.Updater(
self.node, self.ipmicmd.get_diagnostic_data,
self.node, self.get_diags,
self.inputdata.nodefile(self.node), self.tenant, type='ffdc',
owner=self.current_user)
self.output.put(msg.CreatedResource(
@@ -658,7 +679,7 @@ class IpmiHandler(object):
if len(self.element) == 4:
# A list of destinations
maxdest = self.ipmicmd.get_alert_destination_count()
for alertidx in xrange(0, maxdest + 1):
for alertidx in range(0, maxdest + 1):
self.output.put(msg.ChildCollection(alertidx))
return
elif len(self.element) == 5:
@@ -175,12 +175,15 @@ class IpmiCommandWrapper(ipmicommand.Command):
raise exc.TargetEndpointUnreachable(se.strerror)
else:
raise exc.TargetEndpointUnreachable(str(se))
if isinstance(se, socket.timeout) or (len(se) > 1 and se[1] == 'EHOSTUNREACH'):
if isinstance(se, socket.timeout):
raise exc.TargetEndpointUnreachable('timeout')
raise
except pygexc.PyghmiException as pe:
if 'Access Denied' in str(pe):
raise exc.TargetEndpointBadCredentials()
if 'Redfish not ready' in str(pe):
raise exc.TargetEndpointUnreachable('Redfish not yet ready')
raise
def close_confluent(self):
if self._attribwatcher:
@@ -462,9 +465,13 @@ class IpmiHandler(object):
self.output.put(msg.CreatedResource(
'nodes/{0}/media/uploads/{1}'.format(self.node, u.name)))
def get_diags(self, savefile, progress):
return self.ipmicmd.get_diagnostic_data(
savefile, progress=progress, autosuffix=True)
def handle_servicedata_fetch(self):
u = firmwaremanager.Updater(
self.node, self.ipmicmd.get_diagnostic_data,
self.node, self.get_diags,
self.inputdata.nodefile(self.node), self.tenant, type='ffdc',
owner=self.current_user)
self.output.put(msg.CreatedResource(
+11 -11
View File
@@ -77,7 +77,7 @@ class HostKeyHandler(paramiko.client.MissingHostKeyPolicy):
class SshShell(conapi.Console):
def __init__(self, node, config, username='', password=''):
def __init__(self, node, config, username=b'', password=b''):
self.node = node
self.ssh = None
self.datacallback = None
@@ -127,15 +127,15 @@ class SshShell(conapi.Console):
look_for_keys=False)
except paramiko.AuthenticationException:
self.inputmode = 0
self.username = ''
self.password = ''
self.username = b''
self.password = b''
self.datacallback('\r\nlogin as: ')
return
except paramiko.ssh_exception.NoValidConnectionsError as e:
self.datacallback(str(e))
self.inputmode = 0
self.username = ''
self.password = ''
self.username = b''
self.password = b''
self.datacallback('\r\nlogin as: ')
return
except cexc.PubkeyInvalid as pi:
@@ -205,9 +205,9 @@ class SshShell(conapi.Console):
delidx = data.index(b'\x7f')
data = data[:delidx - 1] + data[delidx + 1:]
self.username += data
if '\r' in self.username:
self.username, self.password = self.username.split('\r')[:2]
lastdata = data.split('\r')[0]
if b'\r' in self.username:
self.username, self.password = self.username.split(b'\r')[:2]
lastdata = data.split(b'\r')[0]
if lastdata != '':
self.datacallback(lastdata)
self.datacallback('\r\nEnter password: ')
@@ -223,9 +223,9 @@ class SshShell(conapi.Console):
delidx = data.index(b'\x7f')
data = data[:delidx - 1] + data[delidx + 1:]
self.password += data
if '\r' in self.password:
self.password = self.password.split('\r')[0]
self.datacallback('\r\n')
if b'\r' in self.password:
self.password = self.password.split(b'\r')[0]
self.datacallback(b'\r\n')
self.logon()
else:
self.shell.sendall(data)
+4 -2
View File
@@ -1,7 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 IBM Corporation
# Copyright 2015-2018 Lenovo
# Copyright 2015-2019 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -123,7 +123,8 @@ def sessionhdl(connection, authname, skipauth=False, cert=None):
if authdata:
cfm = authdata[1]
authenticated = True
send_data(connection, "Confluent -- v0 --")
# version 0 == original, version 1 == pickle3 allowed
send_data(connection, "Confluent -- v{0} --".format(sys.version_info[0] - 2))
while not authenticated: # prompt for name and passphrase
send_data(connection, {'authpassed': 0})
response = tlvdata.recv(connection)
@@ -221,6 +222,7 @@ def process_request(connection, request, cfm, authdata, authname, skipauth):
auditmsg['tenant'] = authdata[3]
auditmsg['allowed'] = True
if _should_authlog(path, operation):
tlvdata.unicode_dictvalues(auditmsg)
auditlog.log(auditmsg)
try:
if operation == 'start':
+2
View File
@@ -1,5 +1,6 @@
from ctypes import *
from ctypes.util import find_library
import confluent.util as util
import grp
import pwd
import os
@@ -29,6 +30,7 @@ def getgrouplist(name, gid, ng=32):
def grouplist(username):
username = util.stringify(username)
pent = pwd.getpwnam(username)
try:
groups = getgrouplist(pent.pw_name, pent.pw_gid)
+4 -4
View File
@@ -31,7 +31,7 @@ def stringify(instr):
# Normalize unicode and bytes to 'str', correcting for
# current python version
if isinstance(instr, bytes) and not isinstance(instr, str):
return instr.decode('utf-8')
return instr.decode('utf-8', errors='replace')
elif not isinstance(instr, bytes) and not isinstance(instr, str):
return instr.encode('utf-8')
return instr
@@ -75,11 +75,11 @@ def randomstring(length=20):
:param length: The number of characters to produce, defaults to 20
"""
chunksize = length / 4
chunksize = length // 4
if length % 4 > 0:
chunksize += 1
strval = base64.urlsafe_b64encode(os.urandom(chunksize * 3))
return strval[0:length-1]
return stringify(strval[0:length-1])
def securerandomnumber(low=0, high=4294967295):
@@ -109,7 +109,7 @@ def monotonic_time():
def get_certificate_from_file(certfile):
cert = open(certfile, 'rb').read()
cert = open(certfile, 'r').read()
inpemcert = False
prunedcert = ''
for line in cert.split('\n'):
+6 -3
View File
@@ -13,9 +13,9 @@ BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot
Prefix: %{_prefix}
BuildArch: noarch
%if "%{dist}" == ".el8"
Requires: python3-pyghmi >= 1.0.34, python3-eventlet, python3-greenlet, python3-pycryptodomex >= 3.4.7, confluent_client, python3-pyparsing, python3-paramiko, python3-dns, python3-netifaces, python3-pyasn1 >= 0.2.3, python3-pysnmp >= 4.3.4, python3-pyte, python3-lxml, python3-eficompressor, python3-setuptools, python3-dateutil, python3-enum34, python3-asn1crypto, python3-ipaddress, python3-cffi, python3-pyOpenSSL
Requires: python3-pyghmi >= 1.0.34, python3-eventlet, python3-greenlet, python3-pycryptodomex >= 3.4.7, confluent_client, python3-pyparsing, python3-paramiko, python3-dns, python3-netifaces, python3-pyasn1 >= 0.2.3, python3-pysnmp >= 4.3.4, python3-pyte, python3-lxml, python3-eficompressor, python3-setuptools, python3-dateutil, python3-enum34, python3-asn1crypto, python3-cffi, python3-pyOpenSSL, python3-monotonic, python3-websocket-client
%else
Requires: python-pyghmi >= 1.0.34, python-eventlet, python-greenlet, python-pycryptodomex >= 3.4.7, confluent_client, python-pyparsing, python-paramiko, python-dns, python-netifaces, python2-pyasn1 >= 0.2.3, python-pysnmp >= 4.3.4, python-pyte, python-lxml, python-eficompressor, python-setuptools, python-dateutil
Requires: python-pyghmi >= 1.0.34, python-eventlet, python-greenlet, python-pycryptodomex >= 3.4.7, confluent_client, python-pyparsing, python-paramiko, python-dns, python-netifaces, python2-pyasn1 >= 0.2.3, python-pysnmp >= 4.3.4, python-pyte, python-lxml, python-eficompressor, python-setuptools, python-dateutil, python2-websocket-client
%endif
Vendor: Jarrod Johnson <jjohnson2@lenovo.com>
Url: http://xcat.sf.net/
@@ -42,7 +42,10 @@ python2 setup.py install --single-version-externally-managed -O1 --root=$RPM_BUI
for file in $(grep confluent/__init__.py INSTALLED_FILES.bare); do
rm $RPM_BUILD_ROOT/$file
done
grep -v confluent/__init__.py INSTALLED_FILES.bare > INSTALLED_FILES
grep -v confluent/__init__.py INSTALLED_FILES.bare | grep -v etc/init.d/confluent > INSTALLED_FILES
rm $RPM_BUILD_ROOT/etc/init.d/confluent
rmdir $RPM_BUILD_ROOT/etc/init.d
rmdir $RPM_BUILD_ROOT/etc
cat INSTALLED_FILES
%triggerin -- python-pyghmi
+1
View File
@@ -14,6 +14,7 @@ setup(
'confluent/discovery/handlers/',
'confluent/networking/',
'confluent/plugins/hardwaremanagement/',
'confluent/plugins/console/',
'confluent/plugins/shell/',
'confluent/collective/',
'confluent/plugins/configuration/'],