mirror of
https://github.com/xcat2/confluent.git
synced 2026-09-29 00:31:09 +00:00
Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c288a27dd | |||
| d9be6ae2e9 | |||
| 59789bae7d | |||
| 521be5d44d | |||
| 6fb82bbbad | |||
| 90e546bcac | |||
| 147d59cba7 | |||
| 6a6fd3184e | |||
| c532cf9ecf | |||
| 8909fb16d6 | |||
| 44d6bde3ff | |||
| 56fa13279e | |||
| 5008128d57 | |||
| ed320f4a17 | |||
| de8292f6dd | |||
| 8e071a2568 | |||
| 7d84d0a021 | |||
| c5f75bfa15 | |||
| 99d01d707f | |||
| 3e1690c860 | |||
| a3f5630535 | |||
| 8d6744947e | |||
| 21b3c89974 | |||
| 9718881c7b | |||
| fcbbdc8a8d | |||
| f03bb36dbe | |||
| 97a950b145 | |||
| a0e445d1b1 | |||
| 65629548c9 | |||
| 6cfbf4533c | |||
| 5794dd7f8c | |||
| 48fa74e7a9 | |||
| 59d5110b8f | |||
| 051d79727b | |||
| 4cdbc7807b | |||
| feecee82db | |||
| 395c0d4697 | |||
| a963a8ca35 | |||
| d95464df6f | |||
| 3f6e6d4c39 | |||
| c3176ab86a | |||
| 4dab5fc527 |
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2014 IBM Corporation
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
import optparse
|
||||
import signal
|
||||
import sys
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
import csv
|
||||
import optparse
|
||||
import signal
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
import csv
|
||||
import optparse
|
||||
import signal
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
@@ -128,4 +128,4 @@ else:
|
||||
else:
|
||||
print(res['item']['href'].replace('/', ''))
|
||||
|
||||
sys.exit(exitcode)
|
||||
sys.exit(exitcode)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015-2017 Lenovo
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015 Lenovo
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
@@ -98,6 +98,8 @@ setmode = None
|
||||
assignment = {}
|
||||
queryparms = {}
|
||||
printsys = []
|
||||
printbmc = []
|
||||
printallbmc = False
|
||||
setsys = {}
|
||||
forceset = False
|
||||
needval = None
|
||||
@@ -122,7 +124,7 @@ def _assign_value():
|
||||
|
||||
|
||||
def parse_config_line(arguments):
|
||||
global setmode, forceset, key, value, needval, candidate, path, attrib
|
||||
global setmode, printallbmc, forceset, key, value, needval, candidate, path, attrib
|
||||
for param in arguments:
|
||||
if param == 'show':
|
||||
continue # forgive muscle memory of pasu users
|
||||
@@ -154,6 +156,8 @@ def parse_config_line(arguments):
|
||||
if setmode != False:
|
||||
bailout('Cannot do set and query in same command')
|
||||
if '.' not in param:
|
||||
if param == 'bmc':
|
||||
printallbmc = True
|
||||
matchedparms = False
|
||||
for candidate in cfgpaths:
|
||||
if candidate.startswith('{0}.'.format(param)):
|
||||
@@ -172,7 +176,10 @@ def parse_config_line(arguments):
|
||||
if not matchedparms:
|
||||
printsys.append(param)
|
||||
elif param not in cfgpaths:
|
||||
printsys.append(param)
|
||||
if param.startswith('bmc.'):
|
||||
printbmc.append(param.replace('bmc.', ''))
|
||||
else:
|
||||
printsys.append(param)
|
||||
else:
|
||||
path, attrib = cfgpaths[param]
|
||||
path = '/noderange/{0}/{1}'.format(noderange, path)
|
||||
@@ -197,18 +204,26 @@ else:
|
||||
parse_config_line(args[1:])
|
||||
session = client.Command()
|
||||
rcode = 0
|
||||
if options.restoredefault and options.restoredefault.lower() in (
|
||||
if options.restoredefault:
|
||||
if options.restoredefault.lower() in (
|
||||
'sys', 'system', 'uefi', 'bios'):
|
||||
for fr in session.update(
|
||||
'/noderange/{0}/configuration/system/clear'.format(noderange),
|
||||
{'clear': True}):
|
||||
rcode |= client.printerror(fr)
|
||||
sys.exit(rcode)
|
||||
elif options.restoredefault:
|
||||
sys.stderr.write(
|
||||
'Unrecognized component to restore defaults: {0}\n'.format(
|
||||
options.restoredefault))
|
||||
sys.exit(1)
|
||||
for fr in session.update(
|
||||
'/noderange/{0}/configuration/system/clear'.format(noderange),
|
||||
{'clear': True}):
|
||||
rcode |= client.printerror(fr)
|
||||
sys.exit(rcode)
|
||||
elif options.restoredefault.lower() in (
|
||||
'bmc', 'imm', 'xcc'):
|
||||
for fr in session.update(
|
||||
'/noderange/{0}/configuration/management_controller/clear'.format(noderange),
|
||||
{'clear': True}):
|
||||
rcode |= client.printerror(fr)
|
||||
sys.exit(rcode)
|
||||
else:
|
||||
sys.stderr.write(
|
||||
'Unrecognized component to restore defaults: {0}\n'.format(
|
||||
options.restoredefault))
|
||||
sys.exit(1)
|
||||
if setmode:
|
||||
if options.exclude:
|
||||
sys.stderr.write('Cannot use exclude and assign at the same time\n')
|
||||
@@ -217,8 +232,12 @@ if setmode:
|
||||
attrnamebypath = {}
|
||||
for key in assignment:
|
||||
if key not in cfgpaths:
|
||||
path = 'configuration/system/all'
|
||||
attrib = key
|
||||
if key.startswith('bmc.'):
|
||||
path = 'configuration/management_controller/extended/all'
|
||||
attrib = key.replace('bmc.', '')
|
||||
else:
|
||||
path = 'configuration/system/all'
|
||||
attrib = key
|
||||
else:
|
||||
path, attrib = cfgpaths[key]
|
||||
if path not in updatebypath:
|
||||
@@ -250,6 +269,10 @@ else:
|
||||
NullOpt(), queryparms[path])
|
||||
if rc:
|
||||
sys.exit(rc)
|
||||
if printbmc or printallbmc:
|
||||
rcode = client.print_attrib_path(
|
||||
'/noderange/{0}/configuration/management_controller/extended/all'.format(noderange),
|
||||
session, printbmc, options, attrprefix='bmc.')
|
||||
if printsys or options.exclude:
|
||||
if printsys == 'all':
|
||||
printsys = []
|
||||
@@ -258,6 +281,7 @@ else:
|
||||
else:
|
||||
path = '/noderange/{0}/configuration/system/advanced'.format(
|
||||
noderange)
|
||||
|
||||
rcode = client.print_attrib_path(path, session, printsys,
|
||||
options)
|
||||
sys.exit(rcode)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015 Lenovo
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
@@ -55,4 +55,4 @@ for r in session.create('/noderange/', attribs):
|
||||
exitcode |= 1
|
||||
if 'created' in r:
|
||||
print('{0}: created'.format(r['created']))
|
||||
sys.exit(exitcode)
|
||||
sys.exit(exitcode)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015-2019 Lenovo
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2016-2017 Lenovo
|
||||
@@ -163,4 +163,4 @@ try:
|
||||
update_firmware(session, upfile)
|
||||
except KeyboardInterrupt:
|
||||
print('')
|
||||
sys.exit(exitcode)
|
||||
sys.exit(exitcode)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
@@ -125,4 +125,4 @@ else:
|
||||
else:
|
||||
print res['item']['href'].replace('/', '')
|
||||
|
||||
sys.exit(exitcode)
|
||||
sys.exit(exitcode)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
@@ -55,4 +55,4 @@ for r in session.create('/nodegroups/', attribs):
|
||||
exitcode |= 1
|
||||
if 'created' in r:
|
||||
print('{0}: created'.format(r['created']))
|
||||
sys.exit(exitcode)
|
||||
sys.exit(exitcode)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015-2017 Lenovo
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
@@ -49,4 +49,4 @@ for r in session.delete('/nodegroups/{0}'.format(noderange)):
|
||||
exitcode |= 1
|
||||
if 'deleted' in r:
|
||||
print('{0}: deleted'.format(r['deleted']))
|
||||
sys.exit(exitcode)
|
||||
sys.exit(exitcode)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2019 Lenovo
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015-2017 Lenovo
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015-2017 Lenovo
|
||||
@@ -31,7 +31,7 @@ if path.startswith('/opt'):
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
argparser = optparse.OptionParser(usage="Usage: %prog <noderange> [on|off]")
|
||||
argparser = optparse.OptionParser(usage="Usage: %prog <noderange> [on|off|blink]")
|
||||
(options, args) = argparser.parse_args()
|
||||
try:
|
||||
noderange = args[0]
|
||||
@@ -42,9 +42,6 @@ client.check_globbing(noderange)
|
||||
identifystate = None
|
||||
if len(sys.argv) > 2:
|
||||
identifystate = sys.argv[2]
|
||||
else:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
sys.exit(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2016-2017 Lenovo
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2019 Lenovo
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015-2017 Lenovo
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2018 Lenovo
|
||||
@@ -188,4 +188,4 @@ def main():
|
||||
sys.exit(1)
|
||||
handler(noderange, media)
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
main()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015-2017 Lenovo
|
||||
@@ -72,4 +72,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))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
@@ -49,4 +49,4 @@ for r in session.delete('/noderange/{0}'.format(noderange)):
|
||||
exitcode |= 1
|
||||
if 'deleted' in r:
|
||||
print('{0}: deleted'.format(r['deleted']))
|
||||
sys.exit(exitcode)
|
||||
sys.exit(exitcode)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2019 Lenovo
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015-2017 Lenovo
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2016-2017 Lenovo
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2016-2017 Lenovo
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015-2017 Lenovo
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015 Lenovo
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2016-2017 Lenovo
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2018 Lenovo
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2018 Lenovo
|
||||
@@ -144,4 +144,4 @@ def main():
|
||||
sys.exit(1)
|
||||
handler(noderange, media)
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
main()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2019 Lenovo
|
||||
|
||||
@@ -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.
|
||||
@@ -107,7 +107,10 @@ def printerror(res, node=None):
|
||||
|
||||
|
||||
def cprint(txt):
|
||||
print(txt)
|
||||
try:
|
||||
print(txt)
|
||||
except UnicodeEncodeError:
|
||||
print(txt.encode('utf8'))
|
||||
sys.stdout.flush()
|
||||
|
||||
def _parseserver(string):
|
||||
@@ -390,7 +393,7 @@ def printattributes(session, requestargs, showtype, nodetype, noderange, options
|
||||
return print_attrib_path(path, session, requestargs, options)
|
||||
|
||||
|
||||
def print_attrib_path(path, session, requestargs, options, rename=None):
|
||||
def print_attrib_path(path, session, requestargs, options, rename=None, attrprefix=None):
|
||||
exitcode = 0
|
||||
seenattributes = set([])
|
||||
for res in session.read(path):
|
||||
@@ -413,6 +416,8 @@ def print_attrib_path(path, session, requestargs, options, rename=None):
|
||||
printattr = rename.get(attr, attr)
|
||||
else:
|
||||
printattr = attr
|
||||
if attrprefix:
|
||||
printattr = attrprefix + printattr
|
||||
currattr = res['databynode'][node][attr]
|
||||
if show_attr(attr, requestargs, seenattributes, options):
|
||||
if 'value' in currattr:
|
||||
|
||||
@@ -25,6 +25,11 @@ try:
|
||||
except NameError:
|
||||
unicode = str
|
||||
|
||||
try:
|
||||
range = xrange
|
||||
except NameError:
|
||||
pass
|
||||
|
||||
def decodestr(value):
|
||||
ret = None
|
||||
try:
|
||||
@@ -40,7 +45,7 @@ def decodestr(value):
|
||||
|
||||
def unicode_dictvalues(dictdata):
|
||||
for key in dictdata:
|
||||
if isinstance(dictdata[key], str):
|
||||
if isinstance(dictdata[key], bytes):
|
||||
dictdata[key] = decodestr(dictdata[key])
|
||||
elif isinstance(dictdata[key], datetime):
|
||||
dictdata[key] = dictdata[key].strftime('%Y-%m-%dT%H:%M:%S')
|
||||
@@ -51,7 +56,7 @@ def unicode_dictvalues(dictdata):
|
||||
|
||||
|
||||
def _unicode_list(currlist):
|
||||
for i in xrange(len(currlist)):
|
||||
for i in range(len(currlist)):
|
||||
if isinstance(currlist[i], str):
|
||||
currlist[i] = decodestr(currlist[i])
|
||||
elif isinstance(currlist[i], dict):
|
||||
@@ -66,7 +71,7 @@ def send(handle, data):
|
||||
data = data.encode('utf-8')
|
||||
except AttributeError:
|
||||
pass
|
||||
if isinstance(data, str) or isinstance(data, unicode):
|
||||
if isinstance(data, bytes) or isinstance(data, unicode):
|
||||
# plain text, e.g. console data
|
||||
tl = len(data)
|
||||
if tl == 0:
|
||||
|
||||
@@ -24,10 +24,18 @@ a confluent server.
|
||||
%setup -n %{name}-%{version} -n %{name}-%{version}
|
||||
|
||||
%build
|
||||
python setup.py build
|
||||
%if "%{dist}" == ".el8"
|
||||
python3 setup.py build
|
||||
%else
|
||||
python2 setup.py build
|
||||
%endif
|
||||
|
||||
%install
|
||||
python setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES --install-scripts=/opt/confluent/bin --install-purelib=/opt/confluent/lib/python
|
||||
%if "%{dist}" == ".el8"
|
||||
python3 setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES --install-scripts=/opt/confluent/bin --install-purelib=/opt/confluent/lib/python
|
||||
%else
|
||||
python2 setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES --install-scripts=/opt/confluent/bin --install-purelib=/opt/confluent/lib/python
|
||||
%endif
|
||||
|
||||
|
||||
%clean
|
||||
|
||||
@@ -84,7 +84,7 @@ function _confluent_generic_completion()
|
||||
}
|
||||
_confluent_nodeidentify_completion()
|
||||
{
|
||||
COMP_CANDIDATES=("on,off -h")
|
||||
COMP_CANDIDATES=("on,off,blink -h")
|
||||
_confluent_generic_completion
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ nodeidentify(8) -- Control the identify LED of confluent nodes
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodidentify <noderange> [on|off]`
|
||||
`nodidentify <noderange> [on|off|blink]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -13,6 +13,7 @@ options are supported:
|
||||
|
||||
* `on`: Turn on the identify LED
|
||||
* `off`: Turn off the identify LED
|
||||
* `blink`: Set the identify LED to blink (when supported by the system)
|
||||
|
||||
## EXAMPLES:
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
|
||||
# This is a sample python script for going through all observed mac addresses
|
||||
# and assuming they are BMC related and printing nodeattrib commands
|
||||
|
||||
@@ -22,10 +22,10 @@ This provides the modules common for both client and server
|
||||
%setup -n %{name}-%{version} -n %{name}-%{version}
|
||||
|
||||
%build
|
||||
python setup.py build
|
||||
python2 setup.py build
|
||||
|
||||
%install
|
||||
python setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES --install-purelib=/opt/confluent/lib/python --install-scripts=/opt/confluent/bin
|
||||
python2 setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES --install-purelib=/opt/confluent/lib/python --install-scripts=/opt/confluent/bin
|
||||
|
||||
%clean
|
||||
rm -rf $RPM_BUILD_ROOT
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
|
||||
import argparse
|
||||
import errno
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2014 IBM Corporation
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
from os.path import exists
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
def get_openssl_conf_location():
|
||||
# CentOS/RHAT
|
||||
return '/etc/pki/tls/openssl.cnf'
|
||||
if exists('/etc/pki/tls/openssl.cnf'):
|
||||
return '/etc/pki/tls/openssl.cnf'
|
||||
elif exists('/etc/ssl/openssl.cnf');
|
||||
return '/etc/ssl/openssl.cnf'
|
||||
else:
|
||||
raise Exception("Cannot find openssl config file")
|
||||
|
||||
def get_ip_addresses():
|
||||
lines = subprocess.check_output('ip addr'.split(' '))
|
||||
@@ -47,4 +52,4 @@ def create_certificate():
|
||||
)
|
||||
|
||||
if __name__ == '__main__':
|
||||
create_certificate()
|
||||
create_certificate()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
@@ -82,7 +82,7 @@ elif args[0] == 'dump':
|
||||
"or -s to do encrypted backup that requires keys.json from "
|
||||
"another backup to restore.")
|
||||
sys.exit(1)
|
||||
os.umask(077)
|
||||
os.umask(0o77)
|
||||
main._initsecurity(conf.get_config())
|
||||
if not os.path.exists(dumpdir):
|
||||
os.makedirs(dumpdir)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2014 IBM Corporation
|
||||
|
||||
@@ -30,7 +30,7 @@ shopt -s extglob
|
||||
cd deb_dist/!(*.orig)/
|
||||
if [ "$OPKGNAME" = "confluent-server" ]; then
|
||||
if grep wheezy /etc/os-release; then
|
||||
sed -i 's/^\(Depends:.*\)/\1, python-confluent-client, python-lxml, python-eficompressor, python-pycryptodomex/' debian/control
|
||||
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
|
||||
fi
|
||||
|
||||
@@ -28,8 +28,9 @@ import hashlib
|
||||
import hmac
|
||||
import multiprocessing
|
||||
import confluent.userutil as userutil
|
||||
pam = None
|
||||
try:
|
||||
import PAM
|
||||
import confluent.pam as pam
|
||||
except ImportError:
|
||||
pass
|
||||
import time
|
||||
@@ -94,23 +95,6 @@ _deniedbyrole = {
|
||||
]
|
||||
}
|
||||
}
|
||||
class Credentials(object):
|
||||
def __init__(self, username, passphrase):
|
||||
self.username = username
|
||||
self.passphrase = passphrase
|
||||
self.haspam = False
|
||||
|
||||
def pam_conv(self, auth, query_list):
|
||||
# use stored credentials in a pam conversation
|
||||
self.haspam = True
|
||||
resp = []
|
||||
for query_entry in query_list:
|
||||
query, pamtype = query_entry
|
||||
if query.startswith('Password'):
|
||||
resp.append((self.passphrase, 0))
|
||||
else:
|
||||
return None
|
||||
return resp
|
||||
|
||||
|
||||
def _prune_passcache():
|
||||
@@ -119,9 +103,9 @@ def _prune_passcache():
|
||||
while True:
|
||||
curtime = time.time()
|
||||
for passent in _passcache.iterkeys():
|
||||
if passent[2] < curtime - 10:
|
||||
if passent[2] < curtime - 90:
|
||||
del _passcache[passent]
|
||||
eventlet.sleep(10)
|
||||
eventlet.sleep(90)
|
||||
|
||||
|
||||
def _get_usertenant(name, tenant=False):
|
||||
@@ -227,7 +211,6 @@ def check_user_passphrase(name, passphrase, operation=None, element=None, tenant
|
||||
# would normally make an event and wait
|
||||
# but here there's no need for that
|
||||
eventlet.sleep(0.5)
|
||||
credobj = Credentials(user, passphrase)
|
||||
cfm = configmanager.ConfigManager(tenant, username=user)
|
||||
ucfg = cfm.get_user(user)
|
||||
if ucfg is None:
|
||||
@@ -249,7 +232,6 @@ def check_user_passphrase(name, passphrase, operation=None, element=None, tenant
|
||||
# while someone is legitimately logged in
|
||||
# invalidate cache and force the slower check
|
||||
del _passcache[(user, tenant)]
|
||||
return None
|
||||
if 'cryptpass' in ucfg:
|
||||
_passchecking[(user, tenant)] = True
|
||||
# TODO(jbjohnso): WORKERPOOL
|
||||
@@ -279,22 +261,16 @@ def check_user_passphrase(name, passphrase, operation=None, element=None, tenant
|
||||
if crypt == crypted:
|
||||
_passcache[(user, tenant)] = hashlib.sha256(passphrase).digest()
|
||||
return authorize(user, element, tenant, operation)
|
||||
try:
|
||||
pammy = PAM.pam()
|
||||
pammy.start(_pamservice, user, credobj.pam_conv)
|
||||
pammy.authenticate()
|
||||
pammy.acct_mgmt()
|
||||
if pam:
|
||||
pammy = pam.pam()
|
||||
usergood = pammy.authenticate(user, passphrase)
|
||||
del pammy
|
||||
_passcache[(user, tenant)] = hashlib.sha256(passphrase).digest()
|
||||
return authorize(user, element, tenant, operation, skipuserobj=False)
|
||||
except NameError:
|
||||
pass
|
||||
except PAM.error:
|
||||
pass
|
||||
if usergood:
|
||||
_passcache[(user, tenant)] = hashlib.sha256(passphrase).digest()
|
||||
return authorize(user, element, tenant, operation, skipuserobj=False)
|
||||
eventlet.sleep(0.05) # stall even on test for existence of a username
|
||||
return None
|
||||
|
||||
|
||||
def _apply_pbkdf(passphrase, salt):
|
||||
return KDF.PBKDF2(passphrase, salt, 32, 10000,
|
||||
lambda p, s: hmac.new(p, s, hashlib.sha256).digest())
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
|
||||
|
||||
# This defines config variable to store the global configuration for confluent
|
||||
import ConfigParser
|
||||
try:
|
||||
import ConfigParser
|
||||
except ModuleNotFoundError:
|
||||
import configparser as ConfigParser
|
||||
import os
|
||||
|
||||
_config = None
|
||||
|
||||
@@ -46,7 +46,10 @@ import Cryptodome.Protocol.KDF as KDF
|
||||
from Cryptodome.Cipher import AES
|
||||
from Cryptodome.Hash import HMAC
|
||||
from Cryptodome.Hash import SHA256
|
||||
import anydbm as dbm
|
||||
try:
|
||||
import anydbm as dbm
|
||||
except ModuleNotFoundError:
|
||||
import dbm
|
||||
import ast
|
||||
import base64
|
||||
import confluent.config.attributes as allattributes
|
||||
@@ -57,7 +60,10 @@ import confluent.util
|
||||
import confluent.netutil as netutil
|
||||
import confluent.exceptions as exc
|
||||
import copy
|
||||
import cPickle
|
||||
try:
|
||||
import cPickle
|
||||
except ModuleNotFoundError:
|
||||
import pickle as cPickle
|
||||
import errno
|
||||
import eventlet
|
||||
import eventlet.event as event
|
||||
@@ -74,6 +80,10 @@ import struct
|
||||
import sys
|
||||
import threading
|
||||
import traceback
|
||||
try:
|
||||
unicode
|
||||
except NameError:
|
||||
unicode = str
|
||||
|
||||
|
||||
_masterkey = None
|
||||
@@ -462,10 +472,8 @@ def crypt_value(value,
|
||||
key = _masterkey
|
||||
iv = os.urandom(12)
|
||||
crypter = AES.new(key, AES.MODE_GCM, nonce=iv)
|
||||
try:
|
||||
cryptval, hmac = crypter.encrypt_and_digest(value)
|
||||
except TypeError:
|
||||
cryptval, hmac = crypter.encrypt_and_digest(value.encode('utf-8'))
|
||||
value = confluent.util.stringify(value).encode('utf-8')
|
||||
cryptval, hmac = crypter.encrypt_and_digest(value)
|
||||
return iv, cryptval, hmac, '\x02'
|
||||
|
||||
|
||||
@@ -474,16 +482,19 @@ def _load_dict_from_dbm(dpath, tdb):
|
||||
dbe = dbm.open(tdb, 'r')
|
||||
currdict = _cfgstore
|
||||
for elem in dpath:
|
||||
elem = confluent.util.stringify(elem)
|
||||
if elem not in currdict:
|
||||
currdict[elem] = {}
|
||||
currdict = currdict[elem]
|
||||
try:
|
||||
for tk in dbe:
|
||||
currdict[tk] = cPickle.loads(dbe[tk])
|
||||
for tk in dbe.keys():
|
||||
tks = confluent.util.stringify(tk)
|
||||
currdict[tks] = cPickle.loads(dbe[tk])
|
||||
except AttributeError:
|
||||
tk = dbe.firstkey()
|
||||
while tk != None:
|
||||
currdict[tk] = cPickle.loads(dbe[tk])
|
||||
tks = confluent.util.stringify(tk)
|
||||
currdict[tks] = cPickle.loads(dbe[tk])
|
||||
tk = dbe.nextkey(tk)
|
||||
except dbm.error:
|
||||
return
|
||||
@@ -522,13 +533,7 @@ def set_global(globalname, value, sync=True):
|
||||
"""
|
||||
if _cfgstore is None:
|
||||
init(not sync)
|
||||
try:
|
||||
globalname = globalname.encode('utf-8')
|
||||
except AttributeError:
|
||||
# We have to remove the unicode-ness of the string,
|
||||
# but if it is already bytes in python 3, then we will
|
||||
# get an attributeerror, so pass
|
||||
pass
|
||||
globalname = confluent.util.stringify(globalname)
|
||||
with _dirtylock:
|
||||
if 'dirtyglobals' not in _cfgstore:
|
||||
_cfgstore['dirtyglobals'] = set()
|
||||
@@ -785,10 +790,7 @@ def apply_pending_collective_updates():
|
||||
|
||||
|
||||
def _true_add_collective_member(name, address, fingerprint, sync=True):
|
||||
try:
|
||||
name = name.encode('utf-8')
|
||||
except AttributeError:
|
||||
pass
|
||||
name = confluent.util.stringify(name)
|
||||
if _cfgstore is None:
|
||||
init(not sync) # use not sync to avoid read from disk
|
||||
if 'collective' not in _cfgstore:
|
||||
@@ -823,8 +825,7 @@ def get_collective_member_by_address(address):
|
||||
|
||||
|
||||
def _mark_dirtykey(category, key, tenant=None):
|
||||
if type(key) in (str, unicode):
|
||||
key = key.encode('utf-8')
|
||||
key = confluent.util.stringify(key)
|
||||
with _dirtylock:
|
||||
if 'dirtykeys' not in _cfgstore:
|
||||
_cfgstore['dirtykeys'] = {}
|
||||
@@ -1145,9 +1146,9 @@ class ConfigManager(object):
|
||||
Returns an identifier that can be used to unsubscribe from these
|
||||
notifications using remove_watcher
|
||||
"""
|
||||
notifierid = random.randint(0, sys.maxint)
|
||||
notifierid = random.randint(0, sys.maxsize)
|
||||
while notifierid in self._notifierids:
|
||||
notifierid = random.randint(0, sys.maxint)
|
||||
notifierid = random.randint(0, sys.maxsize)
|
||||
self._notifierids[notifierid] = {'attriblist': []}
|
||||
if self.tenant not in self._attribwatchers:
|
||||
self._attribwatchers[self.tenant] = {}
|
||||
@@ -1186,9 +1187,9 @@ class ConfigManager(object):
|
||||
# use in case of cancellation.
|
||||
# I anticipate no more than a handful of watchers of this sort, so
|
||||
# this loop should not have to iterate too many times
|
||||
notifierid = random.randint(0, sys.maxint)
|
||||
notifierid = random.randint(0, sys.maxsize)
|
||||
while notifierid in self._notifierids:
|
||||
notifierid = random.randint(0, sys.maxint)
|
||||
notifierid = random.randint(0, sys.maxsize)
|
||||
# going to track that this is a nodecollection type watcher,
|
||||
# but there is no additional data associated.
|
||||
self._notifierids[notifierid] = set(['nodecollection'])
|
||||
@@ -1304,7 +1305,7 @@ class ConfigManager(object):
|
||||
def _true_create_usergroup(self, groupname, role="Administrator"):
|
||||
if 'usergroups' not in self._cfgstore:
|
||||
self._cfgstore['usergroups'] = {}
|
||||
groupname = groupname.encode('utf-8')
|
||||
groupname = confluent.util.stringify(groupname)
|
||||
if groupname in self._cfgstore['usergroups']:
|
||||
raise Exception("Duplicate groupname requested")
|
||||
self._cfgstore['usergroups'][groupname] = {'role': role}
|
||||
@@ -1406,7 +1407,7 @@ class ConfigManager(object):
|
||||
raise Exception("Duplicate id requested")
|
||||
if 'users' not in self._cfgstore:
|
||||
self._cfgstore['users'] = {}
|
||||
name = name.encode('utf-8')
|
||||
name = confluent.util.stringify(name)
|
||||
if name in self._cfgstore['users']:
|
||||
raise Exception("Duplicate username requested")
|
||||
self._cfgstore['users'][name] = {'id': uid}
|
||||
@@ -1664,7 +1665,7 @@ class ConfigManager(object):
|
||||
"{0} node does not exist to add to {1}".format(
|
||||
node, group))
|
||||
for group in attribmap:
|
||||
group = group.encode('utf-8')
|
||||
group = confluent.util.stringify(group)
|
||||
if group not in self._cfgstore['nodegroups']:
|
||||
self._cfgstore['nodegroups'][group] = {'nodes': set()}
|
||||
cfgobj = self._cfgstore['nodegroups'][group]
|
||||
@@ -1722,8 +1723,8 @@ class ConfigManager(object):
|
||||
attributes = realattributes
|
||||
if type(groups) in (str, unicode):
|
||||
groups = (groups,)
|
||||
for group in groups:
|
||||
group = group.encode('utf-8')
|
||||
for group in groups:
|
||||
group = confluent.util.stringify(group)
|
||||
try:
|
||||
groupentry = self._cfgstore['nodegroups'][group]
|
||||
except KeyError:
|
||||
@@ -1814,7 +1815,8 @@ class ConfigManager(object):
|
||||
'nodeattrs': {node: [attrname]},
|
||||
'callback': attribwatcher[watchkey][notifierid]
|
||||
}
|
||||
for watcher in notifdata.itervalues():
|
||||
for watcher in notifdata:
|
||||
watcher = notifdata[watcher]
|
||||
callback = watcher['callback']
|
||||
eventlet.spawn_n(_do_notifier, self, watcher, callback)
|
||||
|
||||
@@ -1828,14 +1830,15 @@ class ConfigManager(object):
|
||||
|
||||
def _true_del_nodes(self, nodes):
|
||||
if self.tenant in self._nodecollwatchers:
|
||||
for watcher in self._nodecollwatchers[self.tenant].itervalues():
|
||||
for watcher in self._nodecollwatchers[self.tenant]:
|
||||
watcher = self._nodecollwatchers[self.tenant][watcher]
|
||||
watcher(added=(), deleting=nodes, renamed=(), configmanager=self)
|
||||
changeset = {}
|
||||
for node in nodes:
|
||||
# set a reserved attribute for the sake of the change notification
|
||||
# framework to trigger on
|
||||
changeset[node] = {'_nodedeleted': 1}
|
||||
node = node.encode('utf-8')
|
||||
node = confluent.util.stringify(node)
|
||||
if node in self._cfgstore['nodes']:
|
||||
self._sync_groups_to_node(node=node, groups=[],
|
||||
changeset=changeset)
|
||||
@@ -1883,7 +1886,7 @@ class ConfigManager(object):
|
||||
realattributes.append(attrname)
|
||||
attributes = realattributes
|
||||
for node in nodes:
|
||||
node = node.encode('utf-8')
|
||||
node = confluent.util.stringify(node)
|
||||
try:
|
||||
nodek = self._cfgstore['nodes'][node]
|
||||
except KeyError:
|
||||
@@ -1954,7 +1957,8 @@ class ConfigManager(object):
|
||||
self._recalculate_expressions(cfgobj, formatter=exprmgr, node=renamemap[name], changeset=changeset)
|
||||
if self.tenant in self._nodecollwatchers:
|
||||
nodecollwatchers = self._nodecollwatchers[self.tenant]
|
||||
for watcher in nodecollwatchers.itervalues():
|
||||
for watcher in nodecollwatchers:
|
||||
watcher = nodecollwatchers[watcher]
|
||||
eventlet.spawn_n(_do_add_watcher, watcher, (), self, renamemap)
|
||||
self._bg_sync_to_file()
|
||||
|
||||
@@ -2011,7 +2015,7 @@ class ConfigManager(object):
|
||||
# first do a sanity check of the input upfront
|
||||
# this mitigates risk of arguments being partially applied
|
||||
for node in attribmap:
|
||||
node = node.encode('utf-8')
|
||||
node = confluent.util.stringify(node)
|
||||
if node == '':
|
||||
raise ValueError('"{0}" is not a valid node name'.format(node))
|
||||
if autocreate:
|
||||
@@ -2066,8 +2070,8 @@ class ConfigManager(object):
|
||||
attrname, node)
|
||||
raise ValueError(errstr)
|
||||
attribmap[node][attrname] = attrval
|
||||
for node in attribmap:
|
||||
node = node.encode('utf-8')
|
||||
for node in attribmap:
|
||||
node = confluent.util.stringify(node)
|
||||
exprmgr = None
|
||||
if node not in self._cfgstore['nodes']:
|
||||
newnodes.append(node)
|
||||
@@ -2110,7 +2114,8 @@ class ConfigManager(object):
|
||||
if newnodes:
|
||||
if self.tenant in self._nodecollwatchers:
|
||||
nodecollwatchers = self._nodecollwatchers[self.tenant]
|
||||
for watcher in nodecollwatchers.itervalues():
|
||||
for watcher in nodecollwatchers:
|
||||
watcher = nodecollwatchers[watcher]
|
||||
eventlet.spawn_n(_do_add_watcher, watcher, newnodes, self)
|
||||
self._bg_sync_to_file()
|
||||
#TODO: wait for synchronization to suceed/fail??)
|
||||
@@ -2248,7 +2253,7 @@ class ConfigManager(object):
|
||||
_cfgstore = {}
|
||||
rootpath = cls._cfgdir
|
||||
try:
|
||||
with open(os.path.join(rootpath, 'transactioncount'), 'r') as f:
|
||||
with open(os.path.join(rootpath, 'transactioncount'), 'rb') as f:
|
||||
txbytes = f.read()
|
||||
if len(txbytes) == 8:
|
||||
_txcount = struct.unpack('!Q', txbytes)[0]
|
||||
@@ -2306,7 +2311,7 @@ class ConfigManager(object):
|
||||
if statelessmode:
|
||||
return
|
||||
_mkpath(cls._cfgdir)
|
||||
with open(os.path.join(cls._cfgdir, 'transactioncount'), 'w') as f:
|
||||
with open(os.path.join(cls._cfgdir, 'transactioncount'), 'wb') as f:
|
||||
f.write(struct.pack('!Q', _txcount))
|
||||
if (fullsync or 'dirtyglobals' in _cfgstore and
|
||||
'globals' in _cfgstore):
|
||||
@@ -2417,7 +2422,9 @@ def _restore_keys(jsond, password, newpassword=None, sync=True):
|
||||
else:
|
||||
keydata = json.loads(jsond)
|
||||
cryptkey = _parse_key(keydata['cryptkey'], password)
|
||||
integritykey = _parse_key(keydata['integritykey'], password)
|
||||
integritykey = None
|
||||
if 'integritykey' in keydata:
|
||||
integritykey = _parse_key(keydata['integritykey'], password)
|
||||
conf.init_config()
|
||||
cfg = conf.get_config()
|
||||
if cfg.has_option('security', 'externalcfgkey'):
|
||||
@@ -2426,8 +2433,9 @@ def _restore_keys(jsond, password, newpassword=None, sync=True):
|
||||
newpassword = keyfile.read()
|
||||
set_global('master_privacy_key', _format_key(cryptkey,
|
||||
password=newpassword), sync)
|
||||
set_global('master_integrity_key', _format_key(integritykey,
|
||||
password=newpassword), sync)
|
||||
if integritykey:
|
||||
set_global('master_integrity_key', _format_key(integritykey,
|
||||
password=newpassword), sync)
|
||||
_masterkey = cryptkey
|
||||
_masterintegritykey = integritykey
|
||||
if sync:
|
||||
|
||||
@@ -94,7 +94,7 @@ def _utf8_normalize(data, shiftin, decoder):
|
||||
|
||||
|
||||
def pytechars2line(chars, maxlen=None):
|
||||
line = '\x1b[m' # start at default params
|
||||
line = b'\x1b[m' # start at default params
|
||||
lb = False # last bold
|
||||
li = False # last italic
|
||||
lu = False # last underline
|
||||
@@ -130,9 +130,12 @@ def pytechars2line(chars, maxlen=None):
|
||||
csi.append(7 if lr else 27)
|
||||
if csi:
|
||||
line += b'\x1b[' + b';'.join(['{0}'.format(x) for x in csi]) + b'm'
|
||||
if not hasdata and char.data.encode('utf-8').rstrip():
|
||||
if not hasdata and char.data.rstrip():
|
||||
hasdata = True
|
||||
line += char.data.encode('utf-8')
|
||||
chardata = char.data
|
||||
if not isinstance(chardata, bytes):
|
||||
chardata = chardata.encode('utf-8')
|
||||
line += chardata
|
||||
if maxlen and len >= maxlen:
|
||||
break
|
||||
len += 1
|
||||
@@ -185,7 +188,7 @@ class ConsoleHandler(object):
|
||||
if termstate & 1:
|
||||
self.appmodedetected = True
|
||||
if termstate & 2:
|
||||
self.shiftin = '0'
|
||||
self.shiftin = b'0'
|
||||
self.users = {}
|
||||
self._attribwatcher = None
|
||||
self._console = None
|
||||
@@ -210,6 +213,8 @@ class ConsoleHandler(object):
|
||||
return retrytime + (retrytime * random.random())
|
||||
|
||||
def feedbuffer(self, data):
|
||||
if not isinstance(data, bytes):
|
||||
data = data.encode('utf-8')
|
||||
try:
|
||||
self.termstream.feed(data)
|
||||
except StopIteration: # corrupt parser state, start over
|
||||
@@ -448,6 +453,7 @@ class ConsoleHandler(object):
|
||||
|
||||
def _got_disconnected(self):
|
||||
if self.connectstate != 'unconnected':
|
||||
self._console.close()
|
||||
self.connectstate = 'unconnected'
|
||||
self.log(
|
||||
logdata='console disconnected', ltype=log.DataTypes.event,
|
||||
@@ -528,14 +534,14 @@ class ConsoleHandler(object):
|
||||
elif data == '':
|
||||
# ignore empty strings from a cconsole provider
|
||||
return
|
||||
if '\x1b[?1l' in data: # request for ansi mode cursor keys
|
||||
if b'\x1b[?1l' in data: # request for ansi mode cursor keys
|
||||
self.appmodedetected = False
|
||||
if '\x1b[?1h' in data: # remember the session wants the client to use
|
||||
if b'\x1b[?1h' in data: # remember the session wants the client to use
|
||||
# 'application mode' Thus far only observed on esxi
|
||||
self.appmodedetected = True
|
||||
if '\x1b)0' in data:
|
||||
if b'\x1b)0' in data:
|
||||
# console indicates it wants access to special drawing characters
|
||||
self.shiftin = '0'
|
||||
self.shiftin = b'0'
|
||||
eventdata = 0
|
||||
if self.appmodedetected:
|
||||
eventdata |= 1
|
||||
@@ -588,20 +594,23 @@ class ConsoleHandler(object):
|
||||
if pendingbl:
|
||||
retdata += pendingbl
|
||||
pendingbl = b''
|
||||
retdata += nline + '\r\n'
|
||||
retdata += nline + b'\r\n'
|
||||
else:
|
||||
pendingbl += nline + '\r\n'
|
||||
pendingbl += nline + b'\r\n'
|
||||
if len(retdata) > 6:
|
||||
retdata = retdata[:-2] # remove the last \r\n
|
||||
retdata += b'\x1b[{0};{1}H'.format(self.buffer.cursor.y + 1,
|
||||
self.buffer.cursor.x + 1)
|
||||
cursordata = '\x1b[{0};{1}H'.format(self.buffer.cursor.y + 1,
|
||||
self.buffer.cursor.x + 1)
|
||||
if not isinstance(cursordata, bytes):
|
||||
cursordata = cursordata.encode('utf-8')
|
||||
retdata += cursordata
|
||||
if self.shiftin is not None: # detected that terminal requested a
|
||||
# shiftin character set, relay that to the terminal that cannected
|
||||
retdata += '\x1b)' + self.shiftin
|
||||
retdata += b'\x1b)' + self.shiftin
|
||||
if self.appmodedetected:
|
||||
retdata += '\x1b[?1h'
|
||||
retdata += b'\x1b[?1h'
|
||||
else:
|
||||
retdata += '\x1b[?1l'
|
||||
retdata += b'\x1b[?1l'
|
||||
return retdata, connstate
|
||||
|
||||
def write(self, data):
|
||||
|
||||
@@ -86,7 +86,10 @@ def seek_element(currplace, currkey):
|
||||
|
||||
def nested_lookup(nestdict, key):
|
||||
try:
|
||||
return reduce(seek_element, key, nestdict)
|
||||
currloc = nestdict
|
||||
for currk in key:
|
||||
currloc = seek_element(currloc, currk)
|
||||
return currloc
|
||||
except TypeError:
|
||||
raise exc.NotFoundException("Invalid element requested")
|
||||
|
||||
@@ -106,6 +109,8 @@ def load_plugins():
|
||||
for plugin in os.listdir(plugindir):
|
||||
if plugin.startswith('.'):
|
||||
continue
|
||||
if '__pycache__' in plugin:
|
||||
continue
|
||||
(plugin, plugtype) = os.path.splitext(plugin)
|
||||
if plugtype == '.sh':
|
||||
pluginmap[plugin] = shellmodule.Plugin(
|
||||
@@ -163,6 +168,10 @@ def _init_core():
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
},
|
||||
'clear': PluginRoute({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
'users': PluginCollection({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
@@ -205,6 +214,16 @@ def _init_core():
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
},
|
||||
'extended': {
|
||||
'all': PluginRoute({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
'advanced': PluginRoute({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
},
|
||||
},
|
||||
'storage': {
|
||||
'all': PluginRoute({
|
||||
@@ -384,6 +403,7 @@ def _init_core():
|
||||
|
||||
nodegroupresources = {
|
||||
'attributes': {
|
||||
'check': PluginRoute({'handler': 'attributes'}),
|
||||
'rename': PluginRoute({'handler': 'attributes'}),
|
||||
'all': PluginRoute({'handler': 'attributes'}),
|
||||
'current': PluginRoute({'handler': 'attributes'}),
|
||||
|
||||
@@ -68,6 +68,8 @@ import confluent.discovery.protocols.pxe as pxe
|
||||
#import confluent.discovery.protocols.ssdp as ssdp
|
||||
import confluent.discovery.protocols.slp as slp
|
||||
import confluent.discovery.handlers.imm as imm
|
||||
import confluent.discovery.handlers.cpstorage as cpstorage
|
||||
import confluent.discovery.handlers.tsm as tsm
|
||||
import confluent.discovery.handlers.pxe as pxeh
|
||||
import confluent.discovery.handlers.smm as smm
|
||||
import confluent.discovery.handlers.xcc as xcc
|
||||
@@ -101,6 +103,8 @@ nodehandlers = {
|
||||
'service:management-hardware.IBM:integrated-management-module2': imm,
|
||||
'pxe-client': pxeh,
|
||||
'service:io-device.Lenovo:management-module': None,
|
||||
'service:thinkagile-storage': cpstorage,
|
||||
'service:lenovo-tsm': tsm,
|
||||
}
|
||||
|
||||
servicenames = {
|
||||
@@ -109,6 +113,8 @@ servicenames = {
|
||||
'service:management-hardware.Lenovo:lenovo-xclarity-controller': 'lenovo-xcc',
|
||||
'service:management-hardware.IBM:integrated-management-module2': 'lenovo-imm2',
|
||||
'service:io-device.Lenovo:management-module': 'lenovo-switch',
|
||||
'service:thinkagile-storage': 'thinkagile-storagebmc',
|
||||
'service:lenovo-tsm': 'lenovo-tsm',
|
||||
}
|
||||
|
||||
servicebyname = {
|
||||
@@ -117,6 +123,8 @@ servicebyname = {
|
||||
'lenovo-xcc': 'service:management-hardware.Lenovo:lenovo-xclarity-controller',
|
||||
'lenovo-imm2': 'service:management-hardware.IBM:integrated-management-module2',
|
||||
'lenovo-switch': 'service:io-device.Lenovo:management-module',
|
||||
'thinkagile-storage': 'service:thinkagile-storagebmc',
|
||||
'lenovo-tsm': 'service:lenovo-tsm',
|
||||
}
|
||||
|
||||
discopool = eventlet.greenpool.GreenPool(500)
|
||||
@@ -881,7 +889,7 @@ def eval_node(cfg, handler, info, nodename, manual=False):
|
||||
handler.probe() # unicast interrogation as possible to get more data
|
||||
# switch concurrently
|
||||
# do some preconfig, for example, to bring a SMM online if applicable
|
||||
handler.preconfig()
|
||||
handler.preconfig(nodename)
|
||||
except Exception as e:
|
||||
unknown_info[info['hwaddr']] = info
|
||||
info['discostatus'] = 'unidentified'
|
||||
|
||||
@@ -26,11 +26,11 @@ ipmicommand.session.threading = eventlet.green.threading
|
||||
ipmicommand.session.socket.getaddrinfo = eventlet.support.greendns.getaddrinfo
|
||||
getaddrinfo = eventlet.support.greendns.getaddrinfo
|
||||
|
||||
DEFAULT_USER = 'USERID'
|
||||
DEFAULT_PASS = 'PASSW0RD'
|
||||
|
||||
|
||||
class NodeHandler(generic.NodeHandler):
|
||||
DEFAULT_USER = 'USERID'
|
||||
DEFAULT_PASS = 'PASSW0RD'
|
||||
|
||||
def _get_ipmicmd(self, user=None, password=None):
|
||||
priv = None
|
||||
@@ -39,9 +39,9 @@ class NodeHandler(generic.NodeHandler):
|
||||
raise pygexc.IpmiException()
|
||||
priv = 4 # manually indicate priv to avoid double-attempt
|
||||
if user is None:
|
||||
user = DEFAULT_USER
|
||||
user = self.DEFAULT_USER
|
||||
if password is None:
|
||||
password = DEFAULT_PASS
|
||||
password = self.DEFAULT_PASS
|
||||
return ipmicommand.Command(self.ipaddr, user, password,
|
||||
privlevel=priv, keepalive=False)
|
||||
|
||||
@@ -70,17 +70,17 @@ class NodeHandler(generic.NodeHandler):
|
||||
'secret.hardwaremanagementpassword', {}).get('value', None)
|
||||
try:
|
||||
ic = self._get_ipmicmd()
|
||||
passwd = DEFAULT_PASS
|
||||
passwd = self.DEFAULT_PASS
|
||||
except pygexc.IpmiException as pi:
|
||||
havecustomcreds = False
|
||||
if user is not None and user != DEFAULT_USER:
|
||||
if user is not None and user != self.DEFAULT_USER:
|
||||
havecustomcreds = True
|
||||
else:
|
||||
user = DEFAULT_USER
|
||||
if passwd is not None and passwd != DEFAULT_PASS:
|
||||
user = self.DEFAULT_USER
|
||||
if passwd is not None and passwd != self.DEFAULT_PASS:
|
||||
havecustomcreds = True
|
||||
else:
|
||||
passwd = DEFAULT_PASS
|
||||
passwd = self.DEFAULT_PASS
|
||||
if havecustomcreds:
|
||||
ic = self._get_ipmicmd(user, passwd)
|
||||
else:
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# Copyright 2019 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import confluent.discovery.handlers.bmc as bmchandler
|
||||
|
||||
class NodeHandler(bmchandler.NodeHandler):
|
||||
DEFAULT_USER = 'admin'
|
||||
DEFAULT_PASS = 'admin'
|
||||
devname = 'BMC'
|
||||
maxmacs = 2
|
||||
@@ -57,7 +57,7 @@ class NodeHandler(object):
|
||||
# serial number and uuid to flesh out data as needed
|
||||
return
|
||||
|
||||
def preconfig(self):
|
||||
def preconfig(self, possiblenode):
|
||||
return
|
||||
|
||||
def discoverable_by_switch(self, macs):
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
|
||||
import confluent.discovery.handlers.bmc as bmchandler
|
||||
import confluent.exceptions as exc
|
||||
import pyghmi.util.webclient as webclient
|
||||
import eventlet
|
||||
webclient = eventlet.import_patched('pyghmi.util.webclient')
|
||||
import struct
|
||||
import urllib
|
||||
import eventlet.support.greendns
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
# Copyright 2019 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import confluent.discovery.handlers.generic as generic
|
||||
import confluent.exceptions as exc
|
||||
import confluent.netutil as netutil
|
||||
import confluent.util as util
|
||||
import eventlet.support.greendns
|
||||
import json
|
||||
import urllib
|
||||
getaddrinfo = eventlet.support.greendns.getaddrinfo
|
||||
|
||||
webclient = eventlet.import_patched('pyghmi.util.webclient')
|
||||
|
||||
class NodeHandler(generic.NodeHandler):
|
||||
devname = 'TSM'
|
||||
DEFAULT_USER = 'USERID'
|
||||
DEFAULT_PASS = 'PASSW0RD'
|
||||
|
||||
def __init__(self, info, configmanager):
|
||||
self.trieddefault = None
|
||||
self.targuser = None
|
||||
self.curruser = None
|
||||
self.currpass = None
|
||||
self.targpass = None
|
||||
self.nodename = None
|
||||
self.csrftok = None
|
||||
self.channel = None
|
||||
self.atdefault = True
|
||||
super(NodeHandler, self).__init__(info, configmanager)
|
||||
|
||||
def validate_cert(self, certificate):
|
||||
# broadly speaking, merely checks consistency moment to moment,
|
||||
# but if https_cert gets stricter, this check means something
|
||||
fprint = util.get_fingerprint(self.https_cert)
|
||||
return util.cert_matches(fprint, certificate)
|
||||
|
||||
def _get_wc(self):
|
||||
authdata = { # start by trying factory defaults
|
||||
'username': self.DEFAULT_USER,
|
||||
'password': self.DEFAULT_PASS,
|
||||
}
|
||||
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))
|
||||
if status > 400:
|
||||
self.trieddefault = True
|
||||
if '555' in rsp:
|
||||
passchange = {
|
||||
'Password': self.targpass,
|
||||
'RetypePassword': self.targpass,
|
||||
'param': 4,
|
||||
'default_password': self.DEFAULT_PASS,
|
||||
'username': self.DEFAULT_USER
|
||||
}
|
||||
rsp, status = wc.grab_json_response_with_status('/api/reset-pass', urllib.urlencode(passchange))
|
||||
authdata['password'] = self.targpass
|
||||
rsp, status = wc.grab_json_response_with_status('/api/session', urllib.urlencode(authdata))
|
||||
self.csrftok = rsp['CSRFToken']
|
||||
self.channel = rsp['channel']
|
||||
self.curruser = self.DEFAULT_USER
|
||||
self.currpass = self.targpass
|
||||
return wc
|
||||
else:
|
||||
self.curruser = self.DEFAULT_USER
|
||||
self.currpass = self.DEFAULT_PASS
|
||||
self.csrftok = rsp['CSRFToken']
|
||||
self.channel = rsp['channel']
|
||||
return wc
|
||||
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:
|
||||
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 status != 200:
|
||||
return None
|
||||
self.curruser = self.targuser
|
||||
self.currpass = self.targpass
|
||||
self.csrftok = rsp['CSRFToken']
|
||||
self.channel = rsp['channel']
|
||||
return wc
|
||||
|
||||
def config(self, nodename):
|
||||
self.nodename = nodename
|
||||
creds = self.configmanager.get_node_attributes(
|
||||
nodename, ['secret.hardwaremanagementuser',
|
||||
'secret.hardwaremanagementpassword',
|
||||
'hardwaremanagement.manager', 'hardwaremanagement.method', 'console.method'],
|
||||
True)
|
||||
cd = creds.get(nodename, {})
|
||||
user, passwd, _ = self.get_node_credentials(
|
||||
nodename, creds, self.DEFAULT_USER, self.DEFAULT_PASS)
|
||||
self.targuser = user
|
||||
self.targpass = passwd
|
||||
wc = self._get_wc()
|
||||
wc.set_header('X-CSRFTOKEN', self.csrftok)
|
||||
curruserinfo = {}
|
||||
authupdate = False
|
||||
wc.set_header('Content-Type', 'application/json')
|
||||
if user != self.curruser:
|
||||
authupdate = True
|
||||
if not curruserinfo:
|
||||
curruserinfo = wc.grab_json_response('/api/settings/users')
|
||||
authchg = curruserinfo[1]
|
||||
authchg['name'] = user
|
||||
if passwd != self.currpass:
|
||||
authupdate = True
|
||||
if not curruserinfo:
|
||||
curruserinfo = wc.grab_json_response('/api/settings/users')
|
||||
authchg = curruserinfo[1]
|
||||
authchg['changepassword'] = 0
|
||||
authchg['password_size'] = 'bytes_20'
|
||||
authchg['password'] = passwd
|
||||
authchg['confirm_password'] = passwd
|
||||
if authupdate:
|
||||
rsp, status = wc.grab_json_response_with_status('/api/settings/users/2', authchg, method='PUT')
|
||||
if (cd.get('hardwaremanagement.method', {}).get('value', 'ipmi') != 'redfish'
|
||||
or cd.get('console.method', {}).get('value', None) == 'ipmi'):
|
||||
# IPMI must be enabled per user config
|
||||
wc.grab_json_response('/api/settings/ipmilanconfig', {
|
||||
'ipv4_enable': 1, 'ipv6_enable': 1,
|
||||
'uncheckedipv4lanEnable': 0, 'uncheckedipv6lanEnable': 0,
|
||||
'checkedipv4lanEnable': 1, 'checkedipv6lanEnable': 1})
|
||||
if ('hardwaremanagement.manager' in cd and
|
||||
cd['hardwaremanagement.manager']['value'] and
|
||||
not cd['hardwaremanagement.manager']['value'].startswith(
|
||||
'fe80::')):
|
||||
newip = cd['hardwaremanagement.manager']['value']
|
||||
newipinfo = getaddrinfo(newip, 0)[0]
|
||||
newip = newipinfo[-1][0]
|
||||
if ':' in newip:
|
||||
raise exc.NotImplementedException('IPv6 remote config TODO')
|
||||
currnet = wc.grab_json_response('/api/settings/network')
|
||||
for net in currnet:
|
||||
if net['channel_number'] == self.channel and net['lan_enable'] == 0:
|
||||
# ignore false indication and switch to 8 (dedicated)
|
||||
self.channel = 8
|
||||
if net['channel_number'] == self.channel:
|
||||
# we have found the interface to potentially manipulate
|
||||
if net['ipv4_address'] != newip:
|
||||
netconfig = netutil.get_nic_config(self.configmanager, nodename, ip=newip)
|
||||
newmask = netutil.cidr_to_mask(netconfig['prefix'])
|
||||
net['ipv4_address'] = newip
|
||||
net['ipv4_subnet'] = newmask
|
||||
if netconfig['ipv4_gateway']:
|
||||
net['ipv4_gateway'] = netconfig['ipv4_gateway']
|
||||
net['ipv4_dhcp_enable'] = 0
|
||||
rsp, status = wc.grab_json_response_with_status(
|
||||
'/api/settings/network/{0}'.format(net['id']), net, method='PUT')
|
||||
break
|
||||
elif self.ipaddr.startswith('fe80::'):
|
||||
self.configmanager.set_node_attributes(
|
||||
{nodename: {'hardwaremanagement.manager': self.ipaddr}})
|
||||
else:
|
||||
raise exc.TargetEndpointUnreachable(
|
||||
'hardwaremanagement.manager must be set to desired address (No IPv6 Link Local detected)')
|
||||
rsp, status = wc.grab_json_response_with_status('/api/session', method='DELETE')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import confluent.config.configmanager as cfm
|
||||
c = cfm.ConfigManager(None)
|
||||
import sys
|
||||
info = {'addresses': [[sys.argv[1]]] }
|
||||
print(repr(info))
|
||||
testr = NodeHandler(info, c)
|
||||
testr.config(sys.argv[2])
|
||||
@@ -16,13 +16,14 @@ import base64
|
||||
import confluent.discovery.handlers.imm as immhandler
|
||||
import confluent.netutil as netutil
|
||||
import confluent.util as util
|
||||
import errno
|
||||
import eventlet
|
||||
import eventlet.support.greendns
|
||||
import json
|
||||
import os
|
||||
import pyghmi.exceptions as pygexc
|
||||
xcc = eventlet.import_patched('pyghmi.redfish.oem.lenovo.xcc')
|
||||
import pyghmi.util.webclient as webclient
|
||||
import eventlet.green.socket as socket
|
||||
webclient = eventlet.import_patched('pyghmi.util.webclient')
|
||||
import struct
|
||||
getaddrinfo = eventlet.support.greendns.getaddrinfo
|
||||
|
||||
@@ -41,10 +42,13 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
devname = 'XCC'
|
||||
|
||||
def __init__(self, info, configmanager):
|
||||
self._xcchdlr = None
|
||||
self._wc = None
|
||||
self.nodename = None
|
||||
self.tmpnodename = None
|
||||
self.tmppasswd = None
|
||||
self._atdefaultcreds = True
|
||||
self._needpasswordchange = True
|
||||
self._currcreds = (None, None)
|
||||
super(NodeHandler, self).__init__(info, configmanager)
|
||||
|
||||
@classmethod
|
||||
@@ -53,23 +57,54 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
# This is not adequate for being satisfied
|
||||
return bool(info.get('attributes', {}))
|
||||
|
||||
def preconfig(self):
|
||||
def preconfig(self, possiblenode):
|
||||
self.tmpnodename = possiblenode
|
||||
ff = self.info.get('attributes', {}).get('enclosure-form-factor', '')
|
||||
if ff not in ('dense-computing', [u'dense-computing']):
|
||||
# skip preconfig for non-SD530 servers
|
||||
return
|
||||
self.trieddefault = None # Reset state on a preconfig attempt
|
||||
# attempt to enable SMM
|
||||
#it's normal to get a 'not supported' (193) for systems without an SMM
|
||||
# need to branch on 3.00+ firmware
|
||||
currfirm = self.info.get('attributes', {}).get('firmware-image-info', [''])[0]
|
||||
currfirm = currfirm.split(':')
|
||||
if len(currfirm) > 1:
|
||||
currfirm = float(currfirm[1])
|
||||
disableipmi = False
|
||||
if currfirm >= 3:
|
||||
# IPMI is disabled and we need it, also we need to go to *some* password
|
||||
wc = self.wc
|
||||
if not wc:
|
||||
# We cannot try to enable SMM here without risking real credentials
|
||||
# on the wire to untrusted parties
|
||||
return
|
||||
wc.grab_json_response('/api/providers/logout')
|
||||
wc.set_basic_credentials(self._currcreds[0], self._currcreds[1])
|
||||
rsp = wc.grab_json_response('/redfish/v1/Managers/1/NetworkProtocol')
|
||||
if not rsp.get('IPMI', {}).get('ProtocolEnabled', True):
|
||||
disableipmi = True
|
||||
_, _ = wc.grab_json_response_with_status(
|
||||
'/redfish/v1/Managers/1/NetworkProtocol',
|
||||
{'IPMI': {'ProtocolEnabled': True}}, method='PATCH')
|
||||
ipmicmd = None
|
||||
try:
|
||||
ipmicmd = self._get_ipmicmd()
|
||||
ipmicmd = self._get_ipmicmd(self._currcreds[0], self._currcreds[1])
|
||||
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)):
|
||||
# raise an issue if anything other than to be expected
|
||||
if disableipmi:
|
||||
_, _ = wc.grab_json_response_with_status(
|
||||
'/redfish/v1/Managers/1/NetworkProtocol',
|
||||
{'IPMI': {'ProtocolEnabled': False}}, method='PATCH')
|
||||
raise
|
||||
self.trieddefault = True
|
||||
if disableipmi:
|
||||
_, _ = wc.grab_json_response_with_status(
|
||||
'/redfish/v1/Managers/1/NetworkProtocol',
|
||||
{'IPMI': {'ProtocolEnabled': False}}, method='PATCH')
|
||||
#TODO: decide how to clean out if important
|
||||
#as it stands, this can step on itself
|
||||
#if ipmicmd:
|
||||
@@ -81,29 +116,111 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
fprint = util.get_fingerprint(self.https_cert)
|
||||
return util.cert_matches(fprint, certificate)
|
||||
|
||||
def get_webclient(self, username, password, newpassword):
|
||||
wc = self._wc.dupe()
|
||||
try:
|
||||
wc.connect()
|
||||
except socket.error as se:
|
||||
if se.errno != errno.ECONNREFUSED:
|
||||
raise
|
||||
return (None, None)
|
||||
pwdchanged = False
|
||||
adata = json.dumps({'username': username,
|
||||
'password': password
|
||||
})
|
||||
headers = {'Connection': 'keep-alive',
|
||||
'Content-Type': 'application/json'}
|
||||
wc.request('POST', '/api/login', adata, headers)
|
||||
rsp = wc.getresponse()
|
||||
if rsp.status != 200 and password == 'PASSW0RD':
|
||||
rsp.read()
|
||||
adata = json.dumps({
|
||||
'username': username,
|
||||
'password': newpassword,
|
||||
})
|
||||
headers = {'Connection': 'keep-alive',
|
||||
'Content-Type': 'application/json'}
|
||||
wc.request('POST', '/api/login', adata, headers)
|
||||
rsp = wc.getresponse()
|
||||
if rsp.status == 200:
|
||||
pwdchanged = True
|
||||
password = newpassword
|
||||
else:
|
||||
rsp.read()
|
||||
return (None, None)
|
||||
if rsp.status == 200:
|
||||
self._currcreds = (username, password)
|
||||
wc.set_basic_credentials(username, password)
|
||||
rspdata = json.loads(rsp.read())
|
||||
wc.set_header('Content-Type', 'application/json')
|
||||
wc.set_header('Authorization', 'Bearer ' + rspdata['access_token'])
|
||||
if '_csrf_token' in wc.cookies:
|
||||
wc.set_header('X-XSRF-TOKEN', wc.cookies['_csrf_token'])
|
||||
if rspdata.get('pwchg_required', None) == 'true':
|
||||
wc.request('POST', '/api/function', json.dumps(
|
||||
{'USER_UserPassChange': '1,{0}'.format(newpassword)}))
|
||||
rsp = wc.getresponse()
|
||||
rsp.read()
|
||||
if rsp.status != 200:
|
||||
return (None, None)
|
||||
self._currcreds = (username, newpassword)
|
||||
wc.set_basic_credentials(username, newpassword)
|
||||
pwdchanged = True
|
||||
if '_csrf_token' in wc.cookies:
|
||||
wc.set_header('X-XSRF-TOKEN', wc.cookies['_csrf_token'])
|
||||
return (wc, pwdchanged)
|
||||
|
||||
@property
|
||||
def wc(self):
|
||||
passwd = None
|
||||
isdefault = True
|
||||
if self._wc is None:
|
||||
self._wc = webclient.SecureHTTPConnection(
|
||||
self.ipaddr, 443, verifycallback=self.validate_cert)
|
||||
self._wc.connect()
|
||||
self._xcchdlr = xcc.OEMHandler(None, None, self._wc, False)
|
||||
if not self.trieddefault:
|
||||
self._xcchdlr.set_credentials('USERID', 'PASSW0RD')
|
||||
wc = self._xcchdlr.get_webclient()
|
||||
nodename = None
|
||||
if self.nodename:
|
||||
nodename = self.nodename
|
||||
inpreconfig = False
|
||||
elif self.tmpnodename:
|
||||
nodename = None
|
||||
inpreconfig = True
|
||||
if self._currcreds[0] is not None:
|
||||
wc, pwdchanged = self.get_webclient(self._currcreds[0], self._currcreds[1], None)
|
||||
if wc:
|
||||
return wc
|
||||
if nodename:
|
||||
creds = self.configmanager.get_node_attributes(
|
||||
nodename, ['secret.hardwaremanagementuser',
|
||||
'secret.hardwaremanagementpassword'], decrypt=True)
|
||||
user, passwd, isdefault = self.get_node_credentials(
|
||||
nodename, creds, 'USERID', 'PASSW0RD')
|
||||
if not self.trieddefault:
|
||||
if not passwd:
|
||||
# So in preconfig context, we don't have admin permission to
|
||||
# actually divulge anything to the target
|
||||
# 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
|
||||
# (TempW0rd42)
|
||||
passwd = 'TempW0rd42'
|
||||
wc, pwdchanged = self.get_webclient('USERID', 'PASSW0RD', passwd)
|
||||
if wc:
|
||||
if pwdchanged:
|
||||
if inpreconfig:
|
||||
self.tmppasswd = passwd
|
||||
else:
|
||||
self._needpasswordchange = False
|
||||
return wc
|
||||
self.trieddefault = True
|
||||
creds = self.configmanager.get_node_attributes(
|
||||
self.nodename, ['secret.hardwaremanagementuser',
|
||||
'secret.hardwaremanagementpassword'], decrypt=True)
|
||||
user, passwd, isdefault = self.get_node_credentials(
|
||||
self.nodename, creds, 'USERID', 'PASSW0RD')
|
||||
if isdefault:
|
||||
return
|
||||
self._atdefaultcreds = False
|
||||
self._xcchdlr.set_credentials(user, passwd)
|
||||
wc = self._xcchdlr.get_webclient()
|
||||
if self.tmppasswd:
|
||||
wc, _ = self.get_webclient('USERID', self.tmppasswd, passwd)
|
||||
else:
|
||||
wc, _ = self.get_webclient(user, passwd, None)
|
||||
if wc:
|
||||
return wc
|
||||
|
||||
@@ -128,7 +245,9 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
if name.lower() == 'reuse':
|
||||
ruleset['USER_GlobalMinPassReuseCycle'] = value
|
||||
try:
|
||||
self.wc.grab_json_response('/api/dataset', ruleset)
|
||||
wc = self.wc
|
||||
wc.grab_json_response('/api/dataset', ruleset)
|
||||
wc.grab_json_response('/api/providers/logout')
|
||||
except Exception as e:
|
||||
print(repr(e))
|
||||
pass
|
||||
@@ -155,12 +274,15 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
if not uid:
|
||||
raise Exception("XCC has neither the default user nor configured user")
|
||||
# The following will work if the password is force change or normal..
|
||||
wc.grab_json_response('/api/function',
|
||||
{'USER_UserPassChange': '{0},{1}'.format(uid, passwd)})
|
||||
if self._needpasswordchange and self.tmppasswd != passwd:
|
||||
wc.grab_json_response('/api/function',
|
||||
{'USER_UserPassChange': '{0},{1}'.format(uid, passwd)})
|
||||
if username != 'USERID':
|
||||
wc.grab_json_response(
|
||||
'/api/function',
|
||||
{'USER_UserModify': '{0},{1},,1,4,0,0,0,0,,8,'.format(uid, username)})
|
||||
self.tmppasswd = None
|
||||
self._currcreds = (username, passwd)
|
||||
|
||||
def _convert_sha256account(self, user, passwd, wc):
|
||||
# First check if the specified user is sha256...
|
||||
@@ -182,6 +304,7 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
tpass = base64.b64encode(os.urandom(9)) + 'Iw47$'
|
||||
userparams = "{0},6pmu0ezczzcp,{1},1,4,0,0,0,0,,8,".format(tmpuid, tpass)
|
||||
result = wc.grab_json_response('/api/function', {'USER_UserCreate': userparams})
|
||||
wc.grab_json_response('/api/providers/logout')
|
||||
adata = json.dumps({
|
||||
'username': '6pmu0ezczzcp',
|
||||
'password': tpass,
|
||||
@@ -201,11 +324,20 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
'/api/function',
|
||||
{'USER_UserPassChange': '{0},{1}'.format(tmpuid, newpass)})
|
||||
nwc.grab_json_response('/api/function', {'USER_UserDelete': "{0},{1}".format(curruser['users_user_id'], user)})
|
||||
userparams = "{0},{1},{2},1,4,0,0,0,0,,8,".format(curruser['users_user_id'], user, passwd)
|
||||
userparams = "{0},{1},{2},1,4,0,0,0,0,,8,".format(curruser['users_user_id'], user, tpass)
|
||||
nwc.grab_json_response('/api/function', {'USER_UserCreate': userparams})
|
||||
nwc.grab_json_response('/api/providers/logout')
|
||||
nwc, pwdchanged = self.get_webclient(user, tpass, passwd)
|
||||
if not pwdchanged:
|
||||
nwc.grab_json_response(
|
||||
'/api/function',
|
||||
{'USER_UserPassChange': '{0},{1}'.format(curruser['users_user_id'], passwd)})
|
||||
nwc.grab_json_response('/api/providers/logout')
|
||||
finally:
|
||||
self._wc = None
|
||||
self.wc.grab_json_response('/api/function', {'USER_UserDelete': "{0},{1}".format(tmpuid, '6pmu0ezczzcp')})
|
||||
wc = self.wc
|
||||
wc.grab_json_response('/api/function', {'USER_UserDelete': "{0},{1}".format(tmpuid, '6pmu0ezczzcp')})
|
||||
wc.grab_json_response('/api/providers/logout')
|
||||
|
||||
def config(self, nodename, reset=False):
|
||||
self.nodename = nodename
|
||||
@@ -229,8 +361,20 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
cd = self.configmanager.get_node_attributes(
|
||||
nodename, ['secret.hardwaremanagementuser',
|
||||
'secret.hardwaremanagementpassword',
|
||||
'hardwaremanagement.manager'], True)
|
||||
'hardwaremanagement.manager', 'hardwaremanagement.method', 'console.method'],
|
||||
True)
|
||||
cd = cd.get(nodename, {})
|
||||
if (cd.get('hardwaremanagement.method', {}).get('value', 'ipmi') != 'redfish'
|
||||
or cd.get('console.method', {}).get('value', None) == 'ipmi'):
|
||||
nwc = wc.dupe()
|
||||
nwc.set_basic_credentials(self._currcreds[0], self._currcreds[1])
|
||||
rsp = nwc.grab_json_response('/redfish/v1/Managers/1/NetworkProtocol')
|
||||
if not rsp.get('IPMI', {}).get('ProtocolEnabled', True):
|
||||
# User has indicated IPMI support, but XCC is currently disabled
|
||||
# change XCC to be consistent
|
||||
_, _ = nwc.grab_json_response_with_status(
|
||||
'/redfish/v1/Managers/1/NetworkProtocol',
|
||||
{'IPMI': {'ProtocolEnabled': True}}, method='PATCH')
|
||||
if ('hardwaremanagement.manager' in cd and
|
||||
cd['hardwaremanagement.manager']['value'] and
|
||||
not cd['hardwaremanagement.manager']['value'].startswith(
|
||||
@@ -242,17 +386,24 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
raise exc.NotImplementedException('IPv6 remote config TODO')
|
||||
netconfig = netutil.get_nic_config(self.configmanager, nodename, ip=newip)
|
||||
newmask = netutil.cidr_to_mask(netconfig['prefix'])
|
||||
# do not change the ipv4_config if the current config looks
|
||||
statargs = {'ENET_IPv4Ena': '1', 'ENET_IPv4AddrSource': '0', 'ENET_IPv4StaticIPAddr': newip, 'ENET_IPv4StaticIPNetMask': newmask}
|
||||
if netconfig['ipv4_gateway']:
|
||||
statargs['ENET_IPv4GatewayIPAddr'] = netconfig['ipv4_gateway']
|
||||
wc.grab_json_response('/api/dataset', statargs)
|
||||
currinfo = wc.grab_json_response('/api/providers/logoninfo')
|
||||
currip = currinfo.get('items', [{}])[0].get('ipv4_address', '')
|
||||
# do not change the ipv4_config if the current config looks right already
|
||||
if currip != newip:
|
||||
statargs = {
|
||||
'ENET_IPv4Ena': '1', 'ENET_IPv4AddrSource': '0',
|
||||
'ENET_IPv4StaticIPAddr': newip, 'ENET_IPv4StaticIPNetMask': newmask
|
||||
}
|
||||
if netconfig['ipv4_gateway']:
|
||||
statargs['ENET_IPv4GatewayIPAddr'] = netconfig['ipv4_gateway']
|
||||
wc.grab_json_response('/api/dataset', statargs)
|
||||
elif self.ipaddr.startswith('fe80::'):
|
||||
self.configmanager.set_node_attributes(
|
||||
{nodename: {'hardwaremanagement.manager': self.ipaddr}})
|
||||
else:
|
||||
raise exc.TargetEndpointUnreachable(
|
||||
'hardwaremanagement.manager must be set to desired address (No IPv6 Link Local detected)')
|
||||
wc.grab_json_response('/api/providers/logout')
|
||||
ff = self.info.get('attributes', {}).get('enclosure-form-factor', '')
|
||||
if ff not in ('dense-computing', [u'dense-computing']):
|
||||
return
|
||||
|
||||
@@ -98,7 +98,7 @@ def snoop(handler, protocol=None):
|
||||
netaddr = ':'.join(['{0:02x}'.format(x) for x in netaddr])
|
||||
optidx = 0
|
||||
try:
|
||||
optidx = rq.index('\x63\x82\x53\x63') + 4
|
||||
optidx = rq.index(b'\x63\x82\x53\x63') + 4
|
||||
except ValueError:
|
||||
continue
|
||||
uuid, arch = find_info_in_options(rq, optidx)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
# Copyright 2017-2019 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -27,6 +27,8 @@ import traceback
|
||||
_slp_services = set([
|
||||
'service:management-hardware.IBM:integrated-management-module2',
|
||||
'service:lenovo-smm',
|
||||
'service:ipmi',
|
||||
'service:lighttpd',
|
||||
'service:management-hardware.Lenovo:lenovo-xclarity-controller',
|
||||
'service:management-hardware.IBM:chassis-management-module',
|
||||
'service:management-hardware.Lenovo:chassis-management-module',
|
||||
@@ -47,7 +49,6 @@ except AttributeError:
|
||||
IPPROTO_IPV6 = 41 # Assume Windows value if socket is missing it
|
||||
|
||||
|
||||
|
||||
def _parse_slp_header(packet):
|
||||
packet = bytearray(packet)
|
||||
if len(packet) < 16 or packet[0] != 2:
|
||||
@@ -245,20 +246,24 @@ def _grab_rsps(socks, rsps, interval, xidmap):
|
||||
|
||||
def _parse_attrlist(attrstr):
|
||||
attribs = {}
|
||||
previousattrlen = None
|
||||
while attrstr:
|
||||
if len(attrstr) == previousattrlen:
|
||||
raise Exception('Looping in attrstr parsing')
|
||||
previousattrlen = len(attrstr)
|
||||
if attrstr[0] == '(':
|
||||
if ')' not in attrstr:
|
||||
if b')' not in attrstr:
|
||||
attribs['INCOMPLETE'] = True
|
||||
return attribs
|
||||
currattr = attrstr[1:attrstr.index(')')]
|
||||
if '=' not in currattr: # Not allegedly kosher, but still..
|
||||
currattr = attrstr[1:attrstr.index(b')')]
|
||||
if b'=' not in currattr: # Not allegedly kosher, but still..
|
||||
currattr = currattr.decode('utf-8')
|
||||
attribs[currattr] = None
|
||||
else:
|
||||
attrname, attrval = currattr.split('=', 1)
|
||||
attrname = attrname.decode('utf-8')
|
||||
attribs[attrname] = []
|
||||
for val in attrval.split(','):
|
||||
for val in attrval.split(b','):
|
||||
try:
|
||||
val = val.decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
@@ -272,9 +277,9 @@ def _parse_attrlist(attrstr):
|
||||
val = finalval
|
||||
if 'uuid' in attrname and len(val) == 16:
|
||||
lebytes = struct.unpack_from(
|
||||
'<IHH', buffer(val[:8]))
|
||||
'<IHH', memoryview(val[:8]))
|
||||
bebytes = struct.unpack_from(
|
||||
'>HHI', buffer(val[8:]))
|
||||
'>HHI', memoryview(val[8:]))
|
||||
val = '{0:08X}-{1:04X}-{2:04X}-{3:04X}-' \
|
||||
'{4:04X}{5:08X}'.format(
|
||||
lebytes[0], lebytes[1], lebytes[2], bebytes[0],
|
||||
@@ -282,12 +287,12 @@ def _parse_attrlist(attrstr):
|
||||
).lower()
|
||||
attribs[attrname].append(val)
|
||||
attrstr = attrstr[attrstr.index(')'):]
|
||||
elif attrstr[0] == ',':
|
||||
elif attrstr[0] == b','[0]:
|
||||
attrstr = attrstr[1:]
|
||||
elif ',' in attrstr:
|
||||
currattr = attrstr[:attrstr.index(',')]
|
||||
elif b',' in attrstr:
|
||||
currattr = attrstr[:attrstr.index(b',')]
|
||||
attribs[currattr] = None
|
||||
attrstr = attrstr[attrstr.index(','):]
|
||||
attrstr = attrstr[attrstr.index(b','):]
|
||||
else:
|
||||
currattr = attrstr
|
||||
attribs[currattr] = None
|
||||
@@ -484,6 +489,18 @@ def snoop(handler, protocol=None):
|
||||
_add_attributes(peerbymacaddress[mac])
|
||||
peerbymacaddress[mac]['hwaddr'] = mac
|
||||
peerbymacaddress[mac]['protocol'] = protocol
|
||||
if 'service:ipmi' in peerbymacaddress[mac]['services']:
|
||||
if 'service:ipmi//Athena:623' in peerbymacaddress[mac].get('urls', ()):
|
||||
peerbymacaddress[mac]['services'] = ['service:thinkagile-storage']
|
||||
else:
|
||||
continue
|
||||
if 'service:lightttpd' in peerbymacaddress[mac]['services']:
|
||||
currinf = peerbymacaddress[mac]
|
||||
curratt = currinf.get('attributes', {})
|
||||
if curratt.get('System-Manufacturing', [None])[0] == 'Lenovo' and curratt.get('type', [None])[0] == 'LenovoThinkServer':
|
||||
peerbymacaddress[mac]['services'] = ['service:lenovo-tsm']
|
||||
else:
|
||||
continue
|
||||
handler(peerbymacaddress[mac])
|
||||
except Exception as e:
|
||||
tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
|
||||
@@ -547,6 +564,11 @@ def scan(srvtypes=_slp_services, addresses=None, localonly=False):
|
||||
_grab_rsps((net, net4), rsps, 1, xidmap)
|
||||
# now to analyze and flesh out the responses
|
||||
for id in rsps:
|
||||
if 'service:ipmi' in rsps[id]['services']:
|
||||
if 'service:ipmi://Athena:623' in rsps[id]['urls']:
|
||||
rsps[id]['services'] = ['service:thinkagile-storage']
|
||||
else:
|
||||
continue
|
||||
if localonly:
|
||||
for addr in rsps[id]['addresses']:
|
||||
if 'fe80' in addr[0]:
|
||||
@@ -554,6 +576,15 @@ def scan(srvtypes=_slp_services, addresses=None, localonly=False):
|
||||
else:
|
||||
continue
|
||||
_add_attributes(rsps[id])
|
||||
if 'service:lighttpd' in rsps[id]['services']:
|
||||
currinf = rsps[id]
|
||||
curratt = currinf.get('attributes', {})
|
||||
if curratt.get('System-Manufacturing', [None])[0] == 'Lenovo' and curratt.get('type', [None])[0] == 'LenovoThinkServer':
|
||||
currinf['services'] = ['service:lenovo-tsm']
|
||||
curratt['enclosure-serial-number'] = curratt['Product-Serial']
|
||||
curratt['enclosure-machinetype-model'] = curratt['Machine-Type']
|
||||
else:
|
||||
continue
|
||||
del rsps[id]['payload']
|
||||
del rsps[id]['function']
|
||||
del rsps[id]['xid']
|
||||
|
||||
@@ -78,8 +78,13 @@ def snoop(handler, byehandler=None):
|
||||
for i4 in util.list_ips():
|
||||
ssdp4mcast = socket.inet_pton(socket.AF_INET, mcastv4addr) + \
|
||||
socket.inet_aton(i4['addr'])
|
||||
net4.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP,
|
||||
ssdp4mcast)
|
||||
try:
|
||||
net4.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP,
|
||||
ssdp4mcast)
|
||||
except socket.error as e:
|
||||
if e.errno != 98:
|
||||
# errno 98 can happen if aliased, skip for now
|
||||
raise
|
||||
net4.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
net4.bind(('', 1900))
|
||||
net6.bind(('', 1900))
|
||||
|
||||
@@ -49,6 +49,7 @@ def forward_port(sock, target, clientip, sessionid):
|
||||
continue
|
||||
try:
|
||||
client = socket.create_connection((target, 443))
|
||||
client.setsockopt(socket.IPPROTO_TCP, socket.TCP_MAXSEG, 1456)
|
||||
except Exception:
|
||||
conn.close()
|
||||
continue
|
||||
@@ -68,6 +69,7 @@ def forward_video():
|
||||
try:
|
||||
vidclient = socket.create_connection((vidtargetbypeer[cli[0]],
|
||||
3900))
|
||||
vidclient.setsockopt(socket.IPPROTO_TCP, socket.TCP_MAXSEG, 1456)
|
||||
except Exception:
|
||||
conn.close()
|
||||
continue
|
||||
|
||||
@@ -17,7 +17,10 @@
|
||||
# This SCGI server provides a http wrap to confluent api
|
||||
# It additionally manages httprequest console sessions
|
||||
import base64
|
||||
import Cookie
|
||||
try:
|
||||
import Cookie
|
||||
except ModuleNotFoundError:
|
||||
import http.cookies as Cookie
|
||||
import confluent.auth as auth
|
||||
import confluent.config.attributes as attribs
|
||||
import confluent.consoleserver as consoleserver
|
||||
@@ -39,7 +42,10 @@ import socket
|
||||
import sys
|
||||
import traceback
|
||||
import time
|
||||
import urlparse
|
||||
try:
|
||||
import urlparse
|
||||
except ModuleNotFoundError:
|
||||
import urllib.parse as urlparse
|
||||
import eventlet.wsgi
|
||||
#scgi = eventlet.import_patched('flup.server.scgi')
|
||||
tlvdata = confluent.tlvdata
|
||||
|
||||
@@ -76,6 +76,10 @@ import stat
|
||||
import struct
|
||||
import time
|
||||
import traceback
|
||||
try:
|
||||
unicode
|
||||
except NameError:
|
||||
unicode = str
|
||||
|
||||
daemonized = False
|
||||
logfull = False
|
||||
@@ -176,6 +180,8 @@ class BaseRotatingHandler(object):
|
||||
self.textfile = open(self.textpath, mode='ab')
|
||||
if self.binfile is None:
|
||||
self.binfile = open(self.binpath, mode='ab')
|
||||
if not isinstance(textrecord, bytes):
|
||||
textrecord = textrecord.encode('utf-8')
|
||||
self.textfile.write(textrecord)
|
||||
self.binfile.write(binrecord)
|
||||
self.textfile.flush()
|
||||
@@ -742,7 +748,7 @@ class Logger(object):
|
||||
pass
|
||||
|
||||
def log(self, logdata=None, ltype=None, event=0, eventdata=None):
|
||||
if type(logdata) not in (str, unicode, dict):
|
||||
if type(logdata) not in (bytes, unicode, dict):
|
||||
raise Exception("Unsupported logdata")
|
||||
if ltype is None:
|
||||
if type(logdata) == dict:
|
||||
|
||||
@@ -43,9 +43,11 @@ except ImportError:
|
||||
import confluent.discovery.core as disco
|
||||
import eventlet
|
||||
dbgif = False
|
||||
if map(int, (eventlet.__version__.split('.'))) > [0, 18]:
|
||||
try:
|
||||
import eventlet.backdoor as backdoor
|
||||
dbgif = True
|
||||
except Exception:
|
||||
pass
|
||||
havefcntl = True
|
||||
try:
|
||||
import fcntl
|
||||
|
||||
@@ -25,6 +25,11 @@ from copy import deepcopy
|
||||
from datetime import datetime
|
||||
import json
|
||||
|
||||
try:
|
||||
unicode
|
||||
except NameError:
|
||||
unicode = str
|
||||
|
||||
valid_health_values = set([
|
||||
'ok',
|
||||
'warning',
|
||||
@@ -54,7 +59,7 @@ def _htmlify_structure(indict):
|
||||
if isinstance(indict, dict):
|
||||
for key in sorted(indict):
|
||||
ret += "<li>{0}: ".format(key)
|
||||
if type(indict[key]) in (str, unicode, float, int):
|
||||
if type(indict[key]) in (bytes, unicode, float, int):
|
||||
ret += str(indict[key])
|
||||
elif isinstance(indict[key], datetime):
|
||||
ret += indict[key].strftime('%Y-%m-%dT%H:%M:%S')
|
||||
@@ -62,7 +67,7 @@ def _htmlify_structure(indict):
|
||||
ret += _htmlify_structure(indict[key])
|
||||
elif isinstance(indict, list):
|
||||
if len(indict) > 0:
|
||||
if type(indict[0]) in (str, unicode, None):
|
||||
if type(indict[0]) in (bytes, unicode, None):
|
||||
nd = []
|
||||
for datum in indict:
|
||||
if datum is None:
|
||||
@@ -156,7 +161,7 @@ class ConfluentMessage(object):
|
||||
'<input type="checkbox" name="restexplorerhonorkey" '
|
||||
'value="{1}">\r').format(valtype, key, self.desc)
|
||||
return snippet
|
||||
if (isinstance(val, bool) or isinstance(val, str) or
|
||||
if (isinstance(val, bool) or isinstance(val, bytes) or
|
||||
isinstance(val, unicode)):
|
||||
value = str(val)
|
||||
elif val is not None and 'value' in val:
|
||||
@@ -450,10 +455,11 @@ def get_input_message(path, operation, inputdata, nodes=None, multinode=False,
|
||||
elif (path[:4] == ['configuration', 'management_controller', 'ntp',
|
||||
'servers'] and operation != 'retrieve' and len(path) == 5):
|
||||
return InputNTPServer(path, nodes, inputdata)
|
||||
elif (path[:3] == ['configuration', 'system', 'all'] and
|
||||
elif (path[:3] in (['configuration', 'system', 'all'],
|
||||
['configuration', 'management_controller', 'extended']) and
|
||||
operation != 'retrieve'):
|
||||
return InputConfigChangeSet(path, inputdata, nodes, configmanager)
|
||||
elif (path[:3] == ['configuration', 'system', 'clear'] and
|
||||
elif (path[0] == 'configuration' and path[2] == 'clear' and
|
||||
operation != 'retrieve'):
|
||||
return InputConfigClear(path, inputdata, nodes, configmanager)
|
||||
elif (path[:3] == ['configuration', 'storage', 'disks'] and
|
||||
@@ -586,7 +592,7 @@ class InputConfigChangeSet(InputExpression):
|
||||
endattrs = {}
|
||||
for attr in attrs:
|
||||
origval = attrs[attr]
|
||||
if isinstance(origval, str) or isinstance(origval, unicode):
|
||||
if isinstance(origval, bytes) or isinstance(origval, unicode):
|
||||
origval = {'expression': origval}
|
||||
if 'expression' not in origval:
|
||||
endattrs[attr] = attrs[attr]
|
||||
@@ -613,7 +619,7 @@ class InputAttributes(ConfluentMessage):
|
||||
if nodes is None:
|
||||
self.attribs = inputdata
|
||||
for attrib in self.attribs:
|
||||
if type(self.attribs[attrib]) in (str, unicode):
|
||||
if type(self.attribs[attrib]) in (bytes, unicode):
|
||||
try:
|
||||
# ok, try to use format against the string
|
||||
# store back result to the attribute to
|
||||
@@ -639,7 +645,7 @@ class InputAttributes(ConfluentMessage):
|
||||
return {}
|
||||
nodeattr = deepcopy(self.nodeattribs[node])
|
||||
for attr in nodeattr:
|
||||
if type(nodeattr[attr]) in (str, unicode):
|
||||
if type(nodeattr[attr]) in (bytes, unicode):
|
||||
try:
|
||||
# as above, use format() to see if string follows
|
||||
# expression, store value back in case of escapes
|
||||
@@ -742,7 +748,7 @@ class InputCredential(ConfluentMessage):
|
||||
if len(path) == 4:
|
||||
inputdata['uid'] = path[-1]
|
||||
# if the operation is 'create' check if all fields are present
|
||||
if (isinstance(inputdata['uid'], str) and
|
||||
if (type(inputdata['uid']) in (bytes, unicode) and
|
||||
not inputdata['uid'].isdigit()):
|
||||
inputdata['uid'] = inputdata['uid']
|
||||
else:
|
||||
@@ -768,7 +774,7 @@ class InputCredential(ConfluentMessage):
|
||||
return {}
|
||||
credential = deepcopy(self.credentials[node])
|
||||
for attr in credential:
|
||||
if type(credential[attr]) in (str, unicode):
|
||||
if type(credential[attr]) in (bytes, unicode):
|
||||
try:
|
||||
# as above, use format() to see if string follows
|
||||
# expression, store value back in case of escapes
|
||||
@@ -822,6 +828,7 @@ class InputIdentifyMessage(ConfluentInputMessage):
|
||||
valid_values = set([
|
||||
'on',
|
||||
'off',
|
||||
'blink',
|
||||
])
|
||||
|
||||
keyname = 'identify'
|
||||
@@ -1357,7 +1364,7 @@ class AlertDestination(ConfluentMessage):
|
||||
|
||||
class InputAlertDestination(ConfluentMessage):
|
||||
valid_alert_params = {
|
||||
'acknowledge': lambda x: False if type(x) in (unicode,str) and x.lower() == 'false' else bool(x),
|
||||
'acknowledge': lambda x: False if type(x) in (unicode, bytes) and x.lower() == 'false' else bool(x),
|
||||
'acknowledge_timeout': lambda x: int(x) if x and x.isdigit() else None,
|
||||
'ip': lambda x: x,
|
||||
'retries': lambda x: int(x)
|
||||
@@ -1571,7 +1578,7 @@ class Attributes(ConfluentMessage):
|
||||
nkv = {}
|
||||
self.notnode = name is None
|
||||
for key in kv:
|
||||
if type(kv[key]) in (str, unicode):
|
||||
if type(kv[key]) in (bytes, unicode):
|
||||
nkv[key] = {'value': kv[key]}
|
||||
else:
|
||||
nkv[key] = kv[key]
|
||||
@@ -1682,7 +1689,7 @@ class CryptedAttributes(Attributes):
|
||||
# for now, just keep the dictionary keys and discard crypt value
|
||||
self.desc = desc
|
||||
nkv = {}
|
||||
for key in kv.iterkeys():
|
||||
for key in kv:
|
||||
nkv[key] = {'isset': False}
|
||||
try:
|
||||
if kv[key] is not None and kv[key]['cryptvalue'] != '':
|
||||
|
||||
@@ -26,7 +26,7 @@ neightime = 0
|
||||
|
||||
import re
|
||||
|
||||
_validmac = re.compile('..:..:..:..:..:..')
|
||||
_validmac = re.compile(b'..:..:..:..:..:..')
|
||||
|
||||
|
||||
def update_neigh():
|
||||
@@ -39,11 +39,11 @@ def update_neigh():
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
(neighdata, err) = ipn.communicate()
|
||||
for entry in neighdata.split('\n'):
|
||||
entry = entry.split(' ')
|
||||
for entry in neighdata.split(b'\n'):
|
||||
entry = entry.split(b' ')
|
||||
if len(entry) < 5 or not entry[4]:
|
||||
continue
|
||||
if entry[0] in ('192.168.0.100', '192.168.70.100', '192.168.70.125'):
|
||||
if entry[0] in (b'192.168.0.100', b'192.168.70.100', b'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
|
||||
|
||||
@@ -25,6 +25,11 @@ import itertools
|
||||
import pyparsing as pp
|
||||
import re
|
||||
|
||||
try:
|
||||
range = xrange
|
||||
except NameError:
|
||||
pass
|
||||
|
||||
# construct custom grammar with pyparsing
|
||||
_nodeword = pp.Word(pp.alphanums + '~^$/=-_:.*+!')
|
||||
_nodebracket = pp.QuotedString(quoteChar='[', endQuoteChar=']',
|
||||
@@ -166,7 +171,7 @@ class NodeRange(object):
|
||||
return self.failorreturn(seqrange)
|
||||
finalfmt = ''
|
||||
iterators = []
|
||||
for idx in xrange(len(leftbits)):
|
||||
for idx in range(len(leftbits)):
|
||||
if leftbits[idx] == rightbits[idx]:
|
||||
finalfmt += leftbits[idx]
|
||||
elif leftbits[idx][0] in pp.alphas:
|
||||
@@ -181,7 +186,7 @@ class NodeRange(object):
|
||||
if leftnum > rightnum:
|
||||
width = len(rightbits[idx])
|
||||
minnum = rightnum
|
||||
maxnum = leftnum + 1 # xrange goes to n-1...
|
||||
maxnum = leftnum + 1 # range goes to n-1...
|
||||
elif rightnum > leftnum:
|
||||
width = len(leftbits[idx])
|
||||
minnum = leftnum
|
||||
@@ -189,7 +194,7 @@ class NodeRange(object):
|
||||
else: # differently padded, but same number...
|
||||
return self.failorreturn(seqrange)
|
||||
numformat = '{0:0%d}' % width
|
||||
for num in xrange(minnum, maxnum):
|
||||
for num in range(minnum, maxnum):
|
||||
curseq.append(numformat.format(num))
|
||||
results = set([])
|
||||
for combo in itertools.product(*iterators):
|
||||
@@ -222,7 +227,7 @@ class NodeRange(object):
|
||||
if self.cfm is None:
|
||||
raise Exception('Verification configmanager required')
|
||||
return set(self.cfm.filter_node_attributes(element, filternodes))
|
||||
for idx in xrange(len(element)):
|
||||
for idx in range(len(element)):
|
||||
if element[idx][0] == '[':
|
||||
nodes = set([])
|
||||
for numeric in NodeRange(element[idx][1:-1]).nodes:
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
# Pulled from:
|
||||
# https://raw.githubusercontent.com/FirefighterBlu3/python-pam/fe44b334970f421635d9e373b563c9e6566613bd/pam.py
|
||||
# and https://github.com/FirefighterBlu3/python-pam/pull/16/files
|
||||
# (c) 2007 Chris AtLee <chris@atlee.ca>
|
||||
# Licensed under the MIT license:
|
||||
# http://www.opensource.org/licenses/mit-license.php
|
||||
#
|
||||
# Original author: Chris AtLee
|
||||
#
|
||||
# Modified by David Ford, 2011-12-6
|
||||
# added py3 support and encoding
|
||||
# added pam_end
|
||||
# added pam_setcred to reset credentials after seeing Leon Walker's remarks
|
||||
# added byref as well
|
||||
# use readline to prestuff the getuser input
|
||||
|
||||
'''
|
||||
PAM module for python
|
||||
|
||||
Provides an authenticate function that will allow the caller to authenticate
|
||||
a user against the Pluggable Authentication Modules (PAM) on the system.
|
||||
|
||||
Implemented using ctypes, so no compilation is necessary.
|
||||
'''
|
||||
|
||||
__all__ = ['pam']
|
||||
__version__ = '1.8.4'
|
||||
__author__ = 'David Ford <david@blue-labs.org>'
|
||||
__released__ = '2018 June 15'
|
||||
|
||||
import sys
|
||||
|
||||
from ctypes import CDLL, POINTER, Structure, CFUNCTYPE, cast, byref, sizeof
|
||||
from ctypes import c_void_p, c_size_t, c_char_p, c_char, c_int
|
||||
from ctypes import memmove
|
||||
from ctypes.util import find_library
|
||||
|
||||
class PamHandle(Structure):
|
||||
"""wrapper class for pam_handle_t pointer"""
|
||||
_fields_ = [ ("handle", c_void_p) ]
|
||||
|
||||
def __init__(self):
|
||||
Structure.__init__(self)
|
||||
self.handle = 0
|
||||
|
||||
class PamMessage(Structure):
|
||||
"""wrapper class for pam_message structure"""
|
||||
_fields_ = [ ("msg_style", c_int), ("msg", c_char_p) ]
|
||||
|
||||
def __repr__(self):
|
||||
return "<PamMessage %i '%s'>" % (self.msg_style, self.msg)
|
||||
|
||||
class PamResponse(Structure):
|
||||
"""wrapper class for pam_response structure"""
|
||||
_fields_ = [ ("resp", c_char_p), ("resp_retcode", c_int) ]
|
||||
|
||||
def __repr__(self):
|
||||
return "<PamResponse %i '%s'>" % (self.resp_retcode, self.resp)
|
||||
|
||||
conv_func = CFUNCTYPE(c_int, c_int, POINTER(POINTER(PamMessage)), POINTER(POINTER(PamResponse)), c_void_p)
|
||||
|
||||
class PamConv(Structure):
|
||||
"""wrapper class for pam_conv structure"""
|
||||
_fields_ = [ ("conv", conv_func), ("appdata_ptr", c_void_p) ]
|
||||
|
||||
# Various constants
|
||||
PAM_PROMPT_ECHO_OFF = 1
|
||||
PAM_PROMPT_ECHO_ON = 2
|
||||
PAM_ERROR_MSG = 3
|
||||
PAM_TEXT_INFO = 4
|
||||
PAM_REINITIALIZE_CRED = 8
|
||||
|
||||
libc = CDLL(find_library("c"))
|
||||
libpam = CDLL(find_library("pam"))
|
||||
|
||||
calloc = libc.calloc
|
||||
calloc.restype = c_void_p
|
||||
calloc.argtypes = [c_size_t, c_size_t]
|
||||
|
||||
# bug #6 (@NIPE-SYSTEMS), some libpam versions don't include this function
|
||||
if hasattr(libpam, 'pam_end'):
|
||||
pam_end = libpam.pam_end
|
||||
pam_end.restype = c_int
|
||||
pam_end.argtypes = [PamHandle, c_int]
|
||||
|
||||
pam_start = libpam.pam_start
|
||||
pam_start.restype = c_int
|
||||
pam_start.argtypes = [c_char_p, c_char_p, POINTER(PamConv), POINTER(PamHandle)]
|
||||
|
||||
pam_acct_mgmt = libpam.pam_acct_mgmt
|
||||
pam_acct_mgmt.restype = c_int
|
||||
pam_acct_mgmt.argtypes = [PamHandle, c_int]
|
||||
|
||||
pam_setcred = libpam.pam_setcred
|
||||
pam_setcred.restype = c_int
|
||||
pam_setcred.argtypes = [PamHandle, c_int]
|
||||
|
||||
pam_strerror = libpam.pam_strerror
|
||||
pam_strerror.restype = c_char_p
|
||||
pam_strerror.argtypes = [PamHandle, c_int]
|
||||
|
||||
pam_authenticate = libpam.pam_authenticate
|
||||
pam_authenticate.restype = c_int
|
||||
pam_authenticate.argtypes = [PamHandle, c_int]
|
||||
|
||||
class pam():
|
||||
code = 0
|
||||
reason = None
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def authenticate(self, username, password, service='login', encoding='utf-8', resetcreds=True):
|
||||
"""username and password authentication for the given service.
|
||||
|
||||
Returns True for success, or False for failure.
|
||||
|
||||
self.code (integer) and self.reason (string) are always stored and may
|
||||
be referenced for the reason why authentication failed. 0/'Success' will
|
||||
be stored for success.
|
||||
|
||||
Python3 expects bytes() for ctypes inputs. This function will make
|
||||
necessary conversions using the supplied encoding.
|
||||
|
||||
Inputs:
|
||||
username: username to authenticate
|
||||
password: password in plain text
|
||||
service: PAM service to authenticate against, defaults to 'login'
|
||||
|
||||
Returns:
|
||||
success: True
|
||||
failure: False
|
||||
"""
|
||||
|
||||
@conv_func
|
||||
def my_conv(n_messages, messages, p_response, app_data):
|
||||
"""Simple conversation function that responds to any
|
||||
prompt where the echo is off with the supplied password"""
|
||||
# Create an array of n_messages response objects
|
||||
addr = calloc(n_messages, sizeof(PamResponse))
|
||||
response = cast(addr, POINTER(PamResponse))
|
||||
p_response[0] = response
|
||||
for i in range(n_messages):
|
||||
if messages[i].contents.msg_style == PAM_PROMPT_ECHO_OFF:
|
||||
dst = calloc(len(password)+1, sizeof(c_char))
|
||||
memmove(dst, cpassword, len(password))
|
||||
response[i].resp = dst
|
||||
response[i].resp_retcode = 0
|
||||
return 0
|
||||
|
||||
# python3 ctypes prefers bytes
|
||||
if sys.version_info >= (3,):
|
||||
if isinstance(username, str): username = username.encode(encoding)
|
||||
if isinstance(password, str): password = password.encode(encoding)
|
||||
if isinstance(service, str): service = service.encode(encoding)
|
||||
else:
|
||||
if isinstance(username, unicode):
|
||||
username = username.encode(encoding)
|
||||
if isinstance(password, unicode):
|
||||
password = password.encode(encoding)
|
||||
if isinstance(service, unicode):
|
||||
service = service.encode(encoding)
|
||||
|
||||
if b'\x00' in username or b'\x00' in password or b'\x00' in service:
|
||||
self.code = 4 # PAM_SYSTEM_ERR in Linux-PAM
|
||||
self.reason = 'strings may not contain NUL'
|
||||
return False
|
||||
|
||||
# do this up front so we can safely throw an exception if there's
|
||||
# anything wrong with it
|
||||
cpassword = c_char_p(password)
|
||||
|
||||
handle = PamHandle()
|
||||
conv = PamConv(my_conv, 0)
|
||||
retval = pam_start(service, username, byref(conv), byref(handle))
|
||||
|
||||
if retval != 0:
|
||||
# This is not an authentication error, something has gone wrong starting up PAM
|
||||
self.code = retval
|
||||
self.reason = "pam_start() failed"
|
||||
return False
|
||||
|
||||
retval = pam_authenticate(handle, 0)
|
||||
auth_success = retval == 0
|
||||
|
||||
if auth_success:
|
||||
retval = pam_acct_mgmt(handle, 0)
|
||||
auth_success = retval == 0
|
||||
|
||||
if auth_success and resetcreds:
|
||||
retval = pam_setcred(handle, PAM_REINITIALIZE_CRED)
|
||||
|
||||
# store information to inform the caller why we failed
|
||||
self.code = retval
|
||||
self.reason = pam_strerror(handle, retval)
|
||||
if sys.version_info >= (3,):
|
||||
self.reason = self.reason.decode(encoding)
|
||||
|
||||
if hasattr(libpam, 'pam_end'):
|
||||
pam_end(handle, retval)
|
||||
|
||||
return auth_success
|
||||
|
||||
|
||||
def authenticate(*vargs, **dargs):
|
||||
"""
|
||||
Compatibility function for older versions of python-pam.
|
||||
"""
|
||||
return pam().authenticate(*vargs, **dargs)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import readline, getpass
|
||||
|
||||
def input_with_prefill(prompt, text):
|
||||
def hook():
|
||||
readline.insert_text(text)
|
||||
readline.redisplay()
|
||||
readline.set_pre_input_hook(hook)
|
||||
|
||||
if sys.version_info >= (3,):
|
||||
result = input(prompt)
|
||||
else:
|
||||
result = raw_input(prompt)
|
||||
|
||||
readline.set_pre_input_hook()
|
||||
return result
|
||||
|
||||
pam = pam()
|
||||
|
||||
username = input_with_prefill('Username: ', getpass.getuser())
|
||||
|
||||
# enter a valid username and an invalid/valid password, to verify both failure and success
|
||||
pam.authenticate(username, getpass.getpass())
|
||||
print('{} {}'.format(pam.code, pam.reason))
|
||||
@@ -134,7 +134,7 @@ def retrieve_nodes(nodes, element, configmanager, inputdata):
|
||||
attribute, {}).get('description', ''))
|
||||
elif element[-1] == 'current':
|
||||
for node in util.natural_sort(list(attributes)):
|
||||
for attribute in sorted(attributes[node].iterkeys()):
|
||||
for attribute in sorted(attributes[node]):
|
||||
currattr = attributes[node][attribute]
|
||||
try:
|
||||
desc = allattributes.node[attribute]['description']
|
||||
@@ -164,6 +164,20 @@ def update(nodes, element, configmanager, inputdata):
|
||||
|
||||
|
||||
def update_nodegroup(group, element, configmanager, inputdata):
|
||||
if element == 'check':
|
||||
check = inputdata.attribs
|
||||
decrypt = configmanager.decrypt
|
||||
configmanager.decrypt = True
|
||||
currinfo = configmanager.get_nodegroup_attributes(group, list(check))
|
||||
configmanager.decrypt = decrypt
|
||||
for inf in check:
|
||||
checkvalue = check[inf]
|
||||
if isinstance(checkvalue, dict):
|
||||
checkvalue = checkvalue.get('value', None)
|
||||
currvalue = currinfo.get(inf, {}).get('value')
|
||||
if checkvalue == currvalue:
|
||||
raise exc.InvalidArgumentException('Checked value matches existing value')
|
||||
return retrieve_nodegroup(group, element, configmanager, inputdata)
|
||||
if 'rename' in element:
|
||||
namemap = {}
|
||||
namemap[group] = inputdata.attribs['rename']
|
||||
@@ -171,7 +185,7 @@ def update_nodegroup(group, element, configmanager, inputdata):
|
||||
return yield_rename_resources(namemap, isnode=False)
|
||||
try:
|
||||
clearattribs = []
|
||||
for attrib in inputdata.attribs.iterkeys():
|
||||
for attrib in inputdata.attribs:
|
||||
if inputdata.attribs[attrib] is None:
|
||||
clearattribs.append(attrib)
|
||||
for attrib in clearattribs:
|
||||
@@ -221,6 +235,18 @@ def update_nodes(nodes, element, configmanager, inputdata):
|
||||
raise exc.InvalidArgumentException(
|
||||
'No action to take, noderange is empty (if trying to define '
|
||||
'group attributes, use nodegroupattrib)')
|
||||
if element[-1] == 'check':
|
||||
for node in nodes:
|
||||
check = inputdata.get_attributes(node, allattributes.node)
|
||||
currinfo = configmanager.get_node_attributes(node, list(check), decrypt=True)
|
||||
for inf in check:
|
||||
checkvalue = check[inf]
|
||||
if isinstance(checkvalue, dict):
|
||||
checkvalue = checkvalue.get('value', None)
|
||||
currvalue = currinfo.get(node, {}).get(inf, {}).get('value')
|
||||
if checkvalue == currvalue:
|
||||
raise exc.InvalidArgumentException('Checked value matches existing value')
|
||||
return retrieve(nodes, element, configmanager, inputdata)
|
||||
if 'rename' in element:
|
||||
namemap = {}
|
||||
for node in nodes:
|
||||
|
||||
@@ -383,7 +383,7 @@ def perform_requests(operator, nodes, element, cfg, inputdata, realop):
|
||||
raise datum
|
||||
if (hasattr(datum, 'kvpairs') and datum.kvpairs and
|
||||
len(datum.kvpairs) == 1):
|
||||
bundle.append((datum.kvpairs.keys()[0], datum))
|
||||
bundle.append((list(datum.kvpairs)[0], datum))
|
||||
numnodes -= 1
|
||||
else:
|
||||
yield datum
|
||||
@@ -491,8 +491,8 @@ class IpmiHandler(object):
|
||||
# raise exc.TargetEndpointUnreachable(
|
||||
# "Login process to " + connparams['bmc'] + " died")
|
||||
except socket.gaierror as ge:
|
||||
if ge[0] == -2:
|
||||
raise exc.TargetEndpointUnreachable(ge[1])
|
||||
if ge.errno == -2:
|
||||
raise exc.TargetEndpointUnreachable(ge.strerror)
|
||||
raise
|
||||
self.ipmicmd = persistent_ipmicmds[(node, tenant)]
|
||||
|
||||
@@ -623,12 +623,18 @@ class IpmiHandler(object):
|
||||
return self.handle_domain_name()
|
||||
elif self.element[1:3] == ['management_controller', 'ntp']:
|
||||
return self.handle_ntp()
|
||||
elif self.element[1:4] == ['management_controller', 'extended', 'all']:
|
||||
return self.handle_bmcconfig()
|
||||
elif self.element[1:4] == ['management_controller', 'extended', 'all']:
|
||||
return self.handle_bmcconfig(True)
|
||||
elif self.element[1:3] == ['system', 'all']:
|
||||
return self.handle_sysconfig()
|
||||
elif self.element[1:3] == ['system', 'advanced']:
|
||||
return self.handle_sysconfig(True)
|
||||
elif self.element[1:3] == ['system', 'clear']:
|
||||
return self.handle_sysconfigclear()
|
||||
elif self.element[1:3] == ['management_controller', 'clear']:
|
||||
return self.handle_bmcconfigclear()
|
||||
elif self.element[1:3] == ['management_controller', 'licenses']:
|
||||
return self.handle_licenses()
|
||||
elif self.element[1:3] == ['management_controller', 'save_licenses']:
|
||||
@@ -1296,6 +1302,9 @@ class IpmiHandler(object):
|
||||
def identify(self):
|
||||
if 'update' == self.op:
|
||||
identifystate = self.inputdata.inputbynode[self.node] == 'on'
|
||||
if self.inputdata.inputbynode[self.node] == 'blink':
|
||||
raise exc.InvalidArgumentException(
|
||||
'"blink" is not supported with ipmi')
|
||||
self.ipmicmd.set_identify(on=identifystate)
|
||||
self.output.put(msg.IdentifyState(
|
||||
node=self.node, state=self.inputdata.inputbynode[self.node]))
|
||||
@@ -1370,12 +1379,31 @@ class IpmiHandler(object):
|
||||
self.ipmicmd.set_domain_name(dn)
|
||||
return
|
||||
|
||||
def handle_bmcconfigclear(self):
|
||||
if 'read' == self.op:
|
||||
raise exc.InvalidArgumentException(
|
||||
'Cannot read the "clear" resource')
|
||||
self.ipmicmd.clear_bmc_configuration()
|
||||
|
||||
def handle_sysconfigclear(self):
|
||||
if 'read' == self.op:
|
||||
raise exc.InvalidArgumentException(
|
||||
'Cannot read the "clear" resource')
|
||||
self.ipmicmd.clear_system_configuration()
|
||||
|
||||
def handle_bmcconfig(self, advanced=False):
|
||||
if 'read' == self.op:
|
||||
try:
|
||||
self.output.put(msg.ConfigSet(
|
||||
self.node,
|
||||
self.ipmicmd.get_bmc_configuration()))
|
||||
except Exception as e:
|
||||
self.output.put(
|
||||
msg.ConfluentNodeError(self.node, str(e)))
|
||||
elif 'update' == self.op:
|
||||
self.ipmicmd.set_bmc_configuration(
|
||||
self.inputdata.get_attributes(self.node))
|
||||
|
||||
def handle_sysconfig(self, advanced=False):
|
||||
if 'read' == self.op:
|
||||
try:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 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.
|
||||
@@ -169,6 +169,12 @@ class IpmiCommandWrapper(ipmicommand.Command):
|
||||
try:
|
||||
super(IpmiCommandWrapper, self).__init__(**kwargs)
|
||||
except socket.error as se:
|
||||
if (hasattr(se, 'errno')
|
||||
and se.errno in (errno.ENETUNREACH, errno.EHOSTUNREACH, errno.EADDRNOTAVAIL)):
|
||||
if hasattr(se, 'strerror'):
|
||||
raise exc.TargetEndpointUnreachable(se.strerror)
|
||||
else:
|
||||
raise exc.TargetEndpointUnreachable(str(se))
|
||||
if isinstance(se, socket.timeout) or (len(se) > 1 and se[1] == 'EHOSTUNREACH'):
|
||||
raise exc.TargetEndpointUnreachable('timeout')
|
||||
raise
|
||||
@@ -183,10 +189,8 @@ class IpmiCommandWrapper(ipmicommand.Command):
|
||||
|
||||
def _attribschanged(self, nodeattribs, configmanager, **kwargs):
|
||||
try:
|
||||
self.ipmi_session._mark_broken()
|
||||
except AttributeError:
|
||||
# if ipmi_session doesn't already exist,
|
||||
# then do nothing
|
||||
del persistent_ipmicmds[(self.node, configmanager.tenant)]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def get_health(self):
|
||||
@@ -270,7 +274,7 @@ def perform_requests(operator, nodes, element, cfg, inputdata, realop):
|
||||
raise datum
|
||||
if (hasattr(datum, 'kvpairs') and datum.kvpairs and
|
||||
len(datum.kvpairs) == 1):
|
||||
bundle.append((datum.kvpairs.keys()[0], datum))
|
||||
bundle.append((list(datum.kvpairs)[0], datum))
|
||||
numnodes -= 1
|
||||
else:
|
||||
yield datum
|
||||
@@ -300,6 +304,11 @@ def perform_request(operator, node, element,
|
||||
try:
|
||||
return IpmiHandler(operator, node, element, configdata, inputdata,
|
||||
cfg, results, realop).handle_request()
|
||||
except socket.error as se:
|
||||
if hasattr(se, 'strerror'):
|
||||
results.put(msg.ConfluentTargetTimeout(node, se.strerror))
|
||||
else:
|
||||
results.put(msg.ConfluentTargetTimeout(node, str(se)))
|
||||
except pygexc.IpmiException as ipmiexc:
|
||||
excmsg = str(ipmiexc)
|
||||
if excmsg in ('Session no longer connected', 'timeout'):
|
||||
@@ -327,6 +336,8 @@ def perform_request(operator, node, element,
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
results.put('Done')
|
||||
if (node, cfg.tenant) in persistent_ipmicmds:
|
||||
del persistent_ipmicmds[(node, cfg.tenant)]
|
||||
|
||||
persistent_ipmicmds = {}
|
||||
|
||||
@@ -365,8 +376,8 @@ class IpmiHandler(object):
|
||||
self.loggedin = True
|
||||
self.ipmicmd = persistent_ipmicmds[(node, tenant)]
|
||||
except socket.gaierror as ge:
|
||||
if ge[0] == -2:
|
||||
raise exc.TargetEndpointUnreachable(ge[1])
|
||||
if ge.errno == -2:
|
||||
raise exc.TargetEndpointUnreachable(ge.strerror)
|
||||
raise
|
||||
self.ipmicmd = persistent_ipmicmds[(node, tenant)]
|
||||
|
||||
@@ -490,6 +501,8 @@ class IpmiHandler(object):
|
||||
return self.handle_domain_name()
|
||||
elif self.element[1:3] == ['management_controller', 'ntp']:
|
||||
return self.handle_ntp()
|
||||
elif self.element[1:4] == ['management_controller', 'extended', 'all']:
|
||||
return self.handle_bmcconfig()
|
||||
elif self.element[1:3] == ['system', 'all']:
|
||||
return self.handle_sysconfig()
|
||||
elif self.element[1:3] == ['system', 'advanced']:
|
||||
@@ -682,6 +695,10 @@ class IpmiHandler(object):
|
||||
try:
|
||||
reading = self.ipmicmd.get_sensor_reading(
|
||||
sensor['name'])
|
||||
if reading.unavailable:
|
||||
self.output.put(msg.SensorReadings([EmptySensor(
|
||||
sensor['name'])], name=self.node))
|
||||
continue
|
||||
except pygexc.IpmiException as ie:
|
||||
if ie.ipmicode == 203:
|
||||
self.output.put(msg.SensorReadings([EmptySensor(
|
||||
@@ -702,6 +719,11 @@ class IpmiHandler(object):
|
||||
try:
|
||||
reading = self.ipmicmd.get_sensor_reading(
|
||||
self.sensormap[sensorname])
|
||||
if reading.unavailable:
|
||||
self.output.put(msg.ConfluentResourceUnavailable(
|
||||
self.node, 'Unavailable'
|
||||
))
|
||||
return
|
||||
if hasattr(reading, 'health'):
|
||||
reading.health = _str_health(reading.health)
|
||||
self.output.put(
|
||||
@@ -1163,13 +1185,14 @@ class IpmiHandler(object):
|
||||
def identify(self):
|
||||
if 'update' == self.op:
|
||||
identifystate = self.inputdata.inputbynode[self.node] == 'on'
|
||||
self.ipmicmd.set_identify(on=identifystate)
|
||||
blinkstate = self.inputdata.inputbynode[self.node] == 'blink'
|
||||
self.ipmicmd.set_identify(on=identifystate, blink=blinkstate)
|
||||
self.output.put(msg.IdentifyState(
|
||||
node=self.node, state=self.inputdata.inputbynode[self.node]))
|
||||
return
|
||||
elif 'read' == self.op:
|
||||
# ipmi has identify as read-only for now
|
||||
self.output.put(msg.IdentifyState(node=self.node, state=''))
|
||||
identify = self.ipmicmd.get_identify().get('identifystate', '')
|
||||
self.output.put(msg.IdentifyState(node=self.node, state=identify))
|
||||
return
|
||||
|
||||
def power(self):
|
||||
@@ -1237,6 +1260,19 @@ class IpmiHandler(object):
|
||||
self.ipmicmd.set_domain_name(dn)
|
||||
return
|
||||
|
||||
def handle_bmcconfig(self, advanced=False):
|
||||
if 'read' == self.op:
|
||||
try:
|
||||
self.output.put(msg.ConfigSet(
|
||||
self.node,
|
||||
self.ipmicmd.get_bmc_configuration()))
|
||||
except Exception as e:
|
||||
self.output.put(
|
||||
msg.ConfluentNodeError(self.node, str(e)))
|
||||
elif 'update' == self.op:
|
||||
self.ipmicmd.set_bmc_configuration(
|
||||
self.inputdata.get_attributes(self.node))
|
||||
|
||||
def handle_sysconfigclear(self):
|
||||
if 'read' == self.op:
|
||||
raise exc.InvalidArgumentException(
|
||||
@@ -1389,5 +1425,3 @@ def delete(nodes, element, configmanager, inputdata):
|
||||
element, type='ffdc')
|
||||
return perform_requests(
|
||||
'delete', nodes, element, configmanager, inputdata, 'delete')
|
||||
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ class ExecConsole(conapi.Console):
|
||||
stdin=slave, stdout=slave,
|
||||
stderr=subprocess.PIPE, close_fds=True)
|
||||
except OSError:
|
||||
print "Unable to execute " + self.executable + " (permissions?)"
|
||||
print("Unable to execute " + self.executable + " (permissions?)")
|
||||
self.close()
|
||||
return
|
||||
os.close(slave)
|
||||
@@ -104,7 +104,7 @@ class ExecConsole(conapi.Console):
|
||||
try:
|
||||
os.close(self._master)
|
||||
except OSError:
|
||||
print "Error closing master of child process, ignoring"
|
||||
print("Error closing master of child process, ignoring")
|
||||
if self.subproc is None or self.subproc.poll() is not None:
|
||||
return
|
||||
self.subproc.terminate()
|
||||
|
||||
@@ -412,7 +412,7 @@ def _unixdomainhandler():
|
||||
except OSError: # if file does not exist, no big deal
|
||||
pass
|
||||
if not os.path.isdir("/var/run/confluent"):
|
||||
os.makedirs('/var/run/confluent', 0755)
|
||||
os.makedirs('/var/run/confluent', 0o755)
|
||||
unixsocket.bind("/var/run/confluent/api.sock")
|
||||
os.chmod("/var/run/confluent/api.sock",
|
||||
stat.S_IWOTH | stat.S_IROTH | stat.S_IWGRP |
|
||||
|
||||
@@ -18,6 +18,8 @@ def getgrouplist(name, gid, ng=32):
|
||||
_getgrouplist.argtypes = [c_char_p, c_uint, POINTER(c_uint * ng), POINTER(c_int)]
|
||||
glist = (c_uint * ng)()
|
||||
nglist = c_int(ng)
|
||||
if not isinstance(name, bytes):
|
||||
name = name.encode('utf-8')
|
||||
count = _getgrouplist(name, gid, byref(glist), byref(nglist))
|
||||
if count < 0:
|
||||
raise TooSmallException(nglist.value)
|
||||
|
||||
@@ -27,6 +27,14 @@ import socket
|
||||
import ssl
|
||||
import struct
|
||||
|
||||
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
|
||||
|
||||
def list_interface_indexes():
|
||||
# Getting the interface indexes in a portable manner
|
||||
|
||||
@@ -12,7 +12,11 @@ Group: Development/Libraries
|
||||
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
|
||||
%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
|
||||
%endif
|
||||
Vendor: Jarrod Johnson <jjohnson2@lenovo.com>
|
||||
Url: http://xcat.sf.net/
|
||||
|
||||
@@ -23,10 +27,18 @@ Server for console management and systems management aggregation
|
||||
%setup -n %{name}-%{version} -n %{name}-%{version}
|
||||
|
||||
%build
|
||||
python setup.py build
|
||||
%if "%{dist}" == ".el8"
|
||||
python3 setup.py build
|
||||
%else
|
||||
python2 setup.py build
|
||||
%endif
|
||||
|
||||
%install
|
||||
python setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES.bare --install-purelib=/opt/confluent/lib/python --install-scripts=/opt/confluent/bin
|
||||
%if "%{dist}" == ".el8"
|
||||
python3 setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES.bare --install-purelib=/opt/confluent/lib/python --install-scripts=/opt/confluent/bin
|
||||
%else
|
||||
python2 setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES.bare --install-purelib=/opt/confluent/lib/python --install-scripts=/opt/confluent/bin
|
||||
%endif
|
||||
for file in $(grep confluent/__init__.py INSTALLED_FILES.bare); do
|
||||
rm $RPM_BUILD_ROOT/$file
|
||||
done
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2014 IBM Corporation
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
#
|
||||
|
||||
@@ -60,7 +60,7 @@ case $1 in
|
||||
stop
|
||||
;;
|
||||
status)
|
||||
status -p /var/run/confluent/pid $confluent
|
||||
status_of_proc -p /var/run/confluent/pid $confluent
|
||||
;;
|
||||
esac
|
||||
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/* Copyright 2019 Lenovo */
|
||||
#include <arpa/inet.h>
|
||||
#include <crypt.h>
|
||||
#include <net/if.h>
|
||||
#include <sys/socket.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define OUI_ETHERTYPE 0x88b7
|
||||
#define MAXPACKET 1024
|
||||
#define CHDR "\xa4\x8c\xdb\x30\x01"
|
||||
|
||||
int get_interface_index(int sock, char *interface) {
|
||||
struct ifreq req;
|
||||
memset(&req, 0, sizeof(req));
|
||||
strncpy(req.ifr_name, interface, IFNAMSIZ);
|
||||
if (ioctl(sock, SIOCGIFINDEX, &req) < 0) {
|
||||
return -1;
|
||||
}
|
||||
return req.ifr_ifindex;
|
||||
}
|
||||
|
||||
unsigned char* genpasswd() {
|
||||
unsigned char * passwd;
|
||||
int urandom;
|
||||
passwd = calloc(33, sizeof(char));
|
||||
urandom = open("/dev/urandom", O_RDONLY);
|
||||
read(urandom, passwd, 32);
|
||||
close(urandom);
|
||||
for (urandom = 0; urandom < 32; urandom++) {
|
||||
passwd[urandom] = 0x30 + (passwd[urandom] >> 2);
|
||||
}
|
||||
return passwd;
|
||||
|
||||
}
|
||||
|
||||
int parse_macaddr(char* macaddr) {
|
||||
unsigned char *curr;
|
||||
unsigned char idx;
|
||||
curr = strtok(macaddr, ":-");
|
||||
idx = 0;
|
||||
|
||||
while (curr != NULL) {
|
||||
macaddr[idx++] = strtoul(curr, NULL, 16);
|
||||
curr = strtok(NULL, ":-");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
int sock;
|
||||
int iface;
|
||||
unsigned char* passwd;
|
||||
unsigned char* macaddr;
|
||||
|
||||
unsigned char buffer[MAXPACKET];
|
||||
|
||||
passwd = genpasswd();
|
||||
if (argc < 3) {
|
||||
fprintf(stderr, "Missing interface name and target MAC\n");
|
||||
exit(1);
|
||||
}
|
||||
printf("%s\n", argv[2]);
|
||||
parse_macaddr(argv[2]);
|
||||
printf("%s\n", argv[2]);
|
||||
sock = socket(AF_PACKET, SOCK_DGRAM, htons(OUI_ETHERTYPE));
|
||||
if (sock < 0) {
|
||||
fprintf(stderr, "Unable to open socket (run as root?)\n");
|
||||
exit(1);
|
||||
}
|
||||
iface = get_interface_index(sock, argv[1]);
|
||||
if (iface < 0) {
|
||||
fprintf(stderr, "Unable to find specified interface '%s'\n", argv[1]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
import pyghmi.util.webclient as webclient
|
||||
import json
|
||||
import os
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
import pyghmi.util.webclient as webclient
|
||||
import json
|
||||
import os
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python2
|
||||
import pyghmi.util.webclient as webclient
|
||||
from xml.etree.ElementTree import fromstring
|
||||
import os
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/python2
|
||||
import collections
|
||||
import os
|
||||
import struct
|
||||
|
||||
Reference in New Issue
Block a user