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

Compare commits

..

26 Commits

Author SHA1 Message Date
Jarrod Johnson 45c13a3d46 Merge branch 'master' into nodesearch 2019-10-17 13:46:23 -04:00
Jarrod Johnson a04eea6927 Merge branch 'master' into nodesearch 2019-10-17 09:31:01 -04:00
Jarrod Johnson f46939b7ec Add missing comma to api attributes 2019-10-16 13:50:23 -04:00
Jarrod Johnson 17a8ab3211 Use python3 explicitly for building source
The source distrobution on CentOS7 does not work well
with python2.  Using python3 to build the dist for python2
and python3 fixes issues with extra data being missed in
packaging.
2019-10-16 13:25:21 -04:00
Jarrod Johnson 0fd4c3b2f7 Merge branch 'master' into nodesearch 2019-10-16 13:24:11 -04:00
Jarrod Johnson 829a5b08c1 Merge branch 'master' into nodesearch 2019-09-06 10:09:48 -04:00
Jarrod Johnson aa059c6a4d Amend some formatting 2019-08-12 16:19:35 -04:00
Jarrod Johnson 2e03b662ea Add a credential server implelmentation
This implements the api.armed logic and storing key when
using the clortho credential agent.
2019-08-12 15:31:24 -04:00
Jarrod Johnson 55a0aab548 Add node api key and arming
This is the groundwork for having node authentication.
The intent is for calling code to modify api.armed if
the administrator wants to opt into a one-time set of
credential.

This design as is currently does not fit a stateless
deploy model.  That may suggest an additional manual step
for a fully stateless model.  Alternatively adding support
for credential persistence through sealing to a node's TPM,
which would allow more freely retrievable node credentials.
2019-08-12 15:28:00 -04:00
Jarrod Johnson aaf5aebff7 Fix for tokens with null bytes
Since the server may employ the full range of byte values
in the echo token, use that length and the buffer to avoid
nulls truncating the token.
2019-08-12 15:24:51 -04:00
Jarrod Johnson c1abeaff04 Convert concept to IP based
This leaves the door open for routing if supported.

The server shall restrict IP_TTL to denote
acceptable distance from the manager to accept.
2019-08-09 16:43:48 -04:00
Jarrod Johnson 37d4543d24 Merge branch 'master' into nodesearch 2019-08-08 16:18:21 -04:00
Jarrod Johnson d13e286609 Merge branch 'master' into nodesearch 2019-07-30 14:46:43 -04:00
Jarrod Johnson a7c93627e2 Merge branch 'master' into nodesearch 2019-07-23 08:18:44 -04:00
Jarrod Johnson 6e5cfe69a8 Merge branch 'master' into nodesearch 2019-07-22 09:41:45 -04:00
Jarrod Johnson 626f1c16e2 Merge branch 'nodesearch' of github.com:jjohnson42/confluent into nodesearch 2019-07-19 14:10:13 -04:00
Jarrod Johnson 43480c2e3b Fix nodesensors -n with csv 2019-07-19 14:09:51 -04:00
Jarrod Johnson b91693a973 Merge branch 'master' into nodesearch 2019-07-17 13:05:20 -04:00
Jarrod Johnson da8b4d00d8 Merge branch 'master' into nodesearch 2019-07-16 18:46:52 -04:00
Jarrod Johnson b7b7fd82eb ECHO a packet back to manager
In a later phase, we will want assurances
that the neighbor table was populated.
Since here we have the sockaddr handy,
it makes a lot of sense to take the
opportunity to blind fire a packet back.
No reply is expected, just enough to
trigger arp/neighbor solicitation.
2019-07-16 07:51:24 -04:00
Jarrod Johnson 3789e43f35 Merge branch 'master' into nodesearch 2019-07-16 07:32:00 -04:00
Jarrod Johnson a325dcb423 Merge branch 'nodesearch' of github.com:jjohnson42/confluent into nodesearch 2019-07-15 11:09:29 -04:00
Jarrod Johnson c8d0009dac Avoid copernicus printout of more duplicate data
Separate v4 and v6 results for better chance of success in
dropping duplicate packets.
2019-07-15 11:09:20 -04:00
Jarrod Johnson 79f5dce6dc Implement node search for confluent
This is a viable client to find and get ones node identity.

Node credentials are a separate concern, to be handled later.
2019-07-15 11:09:20 -04:00
Jarrod Johnson 54f36e259f Avoid copernicus printout of more duplicate data
Separate v4 and v6 results for better chance of success in
dropping duplicate packets.
2019-07-15 10:30:36 -04:00
Jarrod Johnson acf67a6c81 Implement node search for confluent
This is a viable client to find and get ones node identity.

Node credentials are a separate concern, to be handled later.
2019-07-12 17:04:14 -04:00
47 changed files with 417 additions and 673 deletions
-4
View File
@@ -50,9 +50,6 @@ argparser.add_option('-c', '--clear', action='store_true',
help='Clear attributes')
argparser.add_option('-p', '--prompt', action='store_true',
help='Prompt for attribute values interactively')
argparser.add_option('-m', '--maxnodes', type='int',
help='Prompt if trying to set attributes on more '
'than specified number of nodes')
(options, args) = argparser.parse_args()
@@ -90,7 +87,6 @@ if len(args) > 1:
if oneval != twoval:
print('Values did not match.')
argassign[arg] = twoval
session.stop_if_noderange_over(noderange, options.maxnodes)
exitcode=client.updateattrib(session,args,nodetype, noderange, options, argassign)
try:
# setting user output to what the user inputs
+1 -3
View File
@@ -32,8 +32,6 @@ if path.startswith('/opt'):
import confluent.client as client
argparser = optparse.OptionParser(usage="Usage: %prog <noderange>")
argparser.add_option('-m', '--maxnodes', type='int',
help='Number of nodes to affect before prompting for confirmation')
(options, args) = argparser.parse_args()
try:
noderange = args[0]
@@ -45,7 +43,7 @@ session = client.Command()
exitcode = 0
errorNodes = set([])
session.stop_if_noderange_over(noderange, options.maxnodes)
success = session.simple_noderange_command(noderange, 'configuration/management_controller/reset', 'reset', key='state', errnodes=errorNodes) # = 0 if successful
# Determine which nodes were successful and print them
-5
View File
@@ -42,10 +42,6 @@ argparser.add_option('-p', '--persist', dest='persist', action='store_true',
default=False,
help='Request the boot device be persistent rather than '
'one time')
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to boot, '
'prompting if over the threshold')
(options, args) = argparser.parse_args()
@@ -70,7 +66,6 @@ else:
bootmode = 'uefi'
errnodes = set([])
session.stop_if_noderange_over(noderange, options.maxnodes)
rc = session.simple_noderange_command(noderange, '/boot/nextdevice', bootdev,
bootmode=bootmode,
persistent=options.persist,
-6
View File
@@ -68,10 +68,6 @@ argparser.add_option('-r', '--restoredefault', default=False,
help='Restore the configuration of the node '
'to factory default for given component. '
'Currently only uefi is supported')
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to configure, '
'prompting if over the threshold')
(options, args) = argparser.parse_args()
cfgpaths = {
@@ -209,7 +205,6 @@ else:
session = client.Command()
rcode = 0
if options.restoredefault:
session.stop_if_noderange_over(noderange, options.maxnodes)
if options.restoredefault.lower() in (
'sys', 'system', 'uefi', 'bios'):
for fr in session.update(
@@ -230,7 +225,6 @@ if options.restoredefault:
options.restoredefault))
sys.exit(1)
if setmode:
session.stop_if_noderange_over(noderange, options.maxnodes)
if options.exclude:
sys.stderr.write('Cannot use exclude and assign at the same time\n')
sys.exit(1)
+3 -8
View File
@@ -38,10 +38,6 @@ if sys.version_info[0] < 3:
argparser = optparse.OptionParser(
usage="Usage: %prog [options] noderange [clear]")
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to clear if clearing log, '
'prompting if over the threshold')
(options, args) = argparser.parse_args()
try:
noderange = args[0]
@@ -50,11 +46,11 @@ except IndexError:
sys.exit(1)
client.check_globbing(noderange)
deletemode = False
if len(args) > 2:
if len(sys.argv) > 3:
argparser.print_help()
sys.exit(1)
if len(args) == 2:
if args[1] == 'clear':
if len(sys.argv) == 3:
if sys.argv[2] == 'clear':
deletemode = True
else:
argparser.print_help()
@@ -92,7 +88,6 @@ def format_event(evt):
if deletemode:
func = session.delete
session.stop_if_noderange_over(noderange, options.maxnodes)
else:
func = session.read
for rsp in func('/noderange/{0}/events/hardware/log'.format(noderange)):
-5
View File
@@ -59,10 +59,6 @@ argparser = optparse.OptionParser(
"%prog <noderange> [list][update [--backup <file>]]|[<components>]")
argparser.add_option('-b', '--backup', action='store_true',
help='Target a backup bank rather than primary')
argparser.add_option('-m', '--maxnodes', type='int',
help='When updating, prompt if more than the specified '
'number of servers will be affected')
(options, args) = argparser.parse_args()
upfile = None
try:
@@ -99,7 +95,6 @@ def get_update_progress(session, url):
def update_firmware(session, filename):
global exitcode
session.stop_if_noderange_over(noderange, options.maxnodes)
output = sq.ScreenPrinter(noderange, session)
nodeurls = {}
filename = os.path.abspath(filename)
+1 -6
View File
@@ -37,10 +37,6 @@ exitcode = 0
argparser = optparse.OptionParser(
usage="Usage: "
"%prog <noderange> [list][install <file>|save <directory>|delete <name>]")
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to delete licenses from, '
'prompting if over the threshold')
(options, args) = argparser.parse_args()
upfile = None
downdir = None
@@ -56,7 +52,7 @@ try:
delete = args[2]
elif args[1] != 'list':
argparser.print_help()
sys.exit(1)
sys.exit(1)
except IndexError:
argparser.print_help()
sys.exit(1)
@@ -142,7 +138,6 @@ try:
elif downdir:
save_licenses(session, downdir)
elif delete:
session.stop_if_noderange_over(noderange, options.maxnodes)
delete_license(session, delete)
else:
show_licenses(session)
+1 -6
View File
@@ -37,11 +37,6 @@ argparser = optparse.OptionParser(
argparser.add_option('-p', '--showprevious', dest='previous',
action='store_true', default=False,
help='Show previous power state')
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to change power state, '
'prompting if over the threshold')
(options, args) = argparser.parse_args()
try:
noderange = args[0]
@@ -77,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, promptover=options.maxnodes))
sys.exit(session.simple_noderange_command(noderange, '/power/state', setstate))
-5
View File
@@ -35,10 +35,6 @@ import confluent.client as client
argparser = optparse.OptionParser(
usage='''\n %prog noderange
\n ''')
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to delete, '
'prompting if over the threshold')
(options, args) = argparser.parse_args()
if len(args) != 1:
argparser.print_help()
@@ -47,7 +43,6 @@ noderange = args[0]
client.check_globbing(noderange)
session = client.Command()
exitcode = 0
session.stop_if_noderange_over(noderange, options.maxnodes)
for r in session.delete('/noderange/{0}'.format(noderange)):
if 'error' in r:
sys.stderr.write(r['error'] + '\n')
+1 -5
View File
@@ -32,10 +32,6 @@ if path.startswith('/opt'):
import confluent.client as client
argparser = optparse.OptionParser(usage="Usage: %prog <noderange>")
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to reseat, '
'prompting if over the threshold')
(options, args) = argparser.parse_args()
try:
noderange = args[0]
@@ -47,7 +43,7 @@ session = client.Command()
exitcode = 0
errorNodes = set([])
session.stop_if_noderange_over(noderange, options.maxnodes)
success = session.simple_noderange_command(noderange, 'power/reseat', 'reseat', key='reseat', errnodes=errorNodes) # = 0 if successful
# Determine which nodes were successful and print them
+2 -6
View File
@@ -42,10 +42,6 @@ def run():
argparser = optparse.OptionParser(
usage="Usage: %prog location noderange:location",
)
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to run rsync to, '
'prompting if over the threshold')
argparser.add_option('-f', '-c', '--count', type='int', default=168,
help='Number of nodes to concurrently rsync')
# among other things, FD_SETSIZE limits. Besides, spawning too many
@@ -68,7 +64,7 @@ def run():
pipedesc = {}
pendingexecs = deque()
exitcode = 0
c.stop_if_noderange_over(noderange, options.maxnodes)
for exp in c.create('/noderange/{0}/attributes/expression'.format(noderange),
{'expression': cmdstr}):
if 'error' in exp:
@@ -104,7 +100,7 @@ def run():
if desc['type'] == 'stdout':
if node not in pernodeout:
pernodeout[node] = ''
pernodeout[node] += client.stringify(data)
pernodeout[node] += stringify(data)
if '\n' in pernodeout[node]:
currout, pernodeout[node] = pernodeout[node].split('\n', 1)
if currout:
+1 -5
View File
@@ -46,10 +46,6 @@ def run():
help='Number of commands to run at a time')
argparser.add_option('-n', '--nonodeprefix', action='store_true',
help='Do not prefix output with node names')
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to run the command with, '
'prompting if over the threshold')
# among other things, FD_SETSIZE limits. Besides, spawning too many
# processes can be unkind for the unaware on memory pressure and such...
argparser.disable_interspersed_args()
@@ -67,7 +63,7 @@ def run():
pipedesc = {}
pendingexecs = deque()
exitcode = 0
c.stop_if_noderange_over(args[0], options.maxnodes)
for exp in c.create('/noderange/{0}/attributes/expression'.format(args[0]),
{'expression': cmdstr}):
if 'error' in exp:
+1 -5
View File
@@ -43,10 +43,7 @@ argparser.add_option('-p', '--persist', dest='persist', action='store_true',
argparser.add_option('-u', '--uefi', dest='uefi', action='store_true',
default=True,
help='Request UEFI style boot (rather than BIOS)')
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to modify next boot device, '
'prompting if over the threshold')
(options, args) = argparser.parse_args()
try:
@@ -66,7 +63,6 @@ if options.biosmode:
bootmode = 'bios'
else:
bootmode = 'uefi'
session.stop_if_noderange_over(noderange, options.maxnodes)
sys.exit(session.simple_noderange_command(noderange, '/boot/nextdevice', bootdev,
bootmode=bootmode,
persistent=options.persist))
+2 -6
View File
@@ -46,10 +46,6 @@ def run():
help='Number of commands to run at a time')
argparser.add_option('-n', '--nonodeprefix', action='store_true',
help='Do not prefix output with node names')
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to run remote ssh command to, '
'prompting if over the threshold')
# among other things, FD_SETSIZE limits. Besides, spawning too many
# processes can be unkind for the unaware on memory pressure and such...
argparser.disable_interspersed_args()
@@ -59,7 +55,7 @@ def run():
sys.exit(1)
client.check_globbing(args[0])
concurrentprocs = options.count
c = client.Command()
c = client.Command()
cmdstr = " ".join(args[1:])
currprocs = 0
@@ -68,7 +64,7 @@ def run():
pendingexecs = deque()
exitcode = 0
c.stop_if_noderange_over(args[0], options.maxnodes)
for exp in c.create('/noderange/{0}/attributes/expression'.format(args[0]),
{'expression': cmdstr}):
if 'error' in exp:
+1 -14
View File
@@ -63,16 +63,10 @@ def _print_cfg(scfg):
sys.stderr.write(e['error'] + '\n')
exitcode = e.get('errorcode', 1)
for node in e.get('databynode', {}):
curr = e['databynode'][node]
if 'error' in curr:
if 'no available drives' in curr['error']:
curr['error'] += ' (drives must be in unconfigured state to be available, they must not be in jbod or online state)'
sys.stderr.write('{0}: {1}\n'.format(node, curr['error']))
exitcode = curr.get('errorcode', 1)
continue
if node not in storagebynode:
storagebynode[node] = {'disks': [], 'arrays': [],
'volumes': []}
curr = e['databynode'][node]
storagebynode[node][curr['type'] + 's'].append(curr)
for node in storagebynode:
for disk in sorted(storagebynode[node]['disks'],
@@ -114,7 +108,6 @@ def createstorage(noderange, options, args):
sys.stderr.write('-r and -d are required arguments to create array\n')
sys.exit(1)
session = client.Command()
session.stop_if_noderange_over(noderange, options.maxnodes)
names = options.name
if names is None:
names = ''.join(args)
@@ -139,7 +132,6 @@ def deletestorage(noderange, options, args):
else:
names = options.name
session = client.Command()
session.stop_if_noderange_over(noderange, options.maxnodes)
for rsp in session.delete(
'/noderange/{0}/configuration/storage/volumes/{1}'.format(
noderange, names)):
@@ -170,7 +162,6 @@ def setdisk(noderange, options, args):
sys.stderr.write('diskset requires valid state as argument (hotspare, jbod, unconfigured)\n')
sys.exit(1)
session = client.Command()
session.stop_if_noderange_over(noderange, options.maxnodes)
scfg = session.update('/noderange/{0}/configuration/storage/disks/{1}'.format(noderange, names), {'state': args[0]})
_print_cfg(scfg)
@@ -211,10 +202,6 @@ def main():
help='Comma separated list of stripsizes to use when creating volumes. '
'This value is in kilobytes. The default behavior is to allow the '
'storage controller to decide.')
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to configure storage on, '
'prompting if over the threshold')
(options, args) = argparser.parse_args()
if len(args) == 1:
args.append('show')
+2 -7
View File
@@ -64,7 +64,7 @@ def printerror(res, node=None):
def download_servicedata(noderange, media, options):
def download_servicedata(noderange, media):
global exitcode
session = client.Command()
output = sq.ScreenPrinter(noderange, session)
@@ -73,7 +73,6 @@ def download_servicedata(noderange, media, options):
upargs = {'filename': filename}
noderrs = {}
nodeurls = {}
session.stop_if_noderange_over(noderange, options.maxnodes)
for res in session.create(resource, upargs):
if 'created' not in res:
for nodename in res.get('databynode', ()):
@@ -122,10 +121,6 @@ def main():
'management server (the confluent server if running remote, '
'and the collective.manager if in collective)\n'
'\n\nSee `man %prog` for more info.\n')
argparser.add_option('-m', '--maxnodes', type='int',
help='Specify a maximum number of '
'nodes to download diagnostic data from, '
'prompting if over the threshold')
(options, args) = argparser.parse_args()
media = None
try:
@@ -147,6 +142,6 @@ def main():
except KeyError:
argparser.print_help()
sys.exit(1)
handler(noderange, media, options)
handler(noderange, media)
if __name__ == '__main__':
main()
+10 -40
View File
@@ -39,10 +39,6 @@ _attraliases = {
'bmcpass': 'secret.hardwaremanagementpassword',
}
try:
input = raw_input
except NameError:
pass
def stringify(instr):
# Normalize unicode and bytes to 'str', correcting for
@@ -223,7 +219,7 @@ class Command(object):
return rc
def simple_noderange_command(self, noderange, resource, input=None,
key=None, errnodes=None, promptover=None, **kwargs):
key=None, errnodes=None, **kwargs):
try:
self._currnoderange = noderange
rc = 0
@@ -239,7 +235,6 @@ class Command(object):
noderange, resource)):
rc = self.handle_results(ikey, rc, res, errnodes)
else:
self.stop_if_noderange_over(noderange, promptover)
kwargs[ikey] = input
for res in self.update('/noderange/{0}/{1}'.format(
noderange, resource), kwargs):
@@ -249,33 +244,6 @@ class Command(object):
except KeyboardInterrupt:
cprint('')
return 0
def stop_if_noderange_over(self, noderange, maxnodes):
if maxnodes is None:
return
nsize = self.get_noderange_size(noderange)
if nsize > maxnodes:
if nsize == 1:
nodename = list(self.read(
'/noderange/{0}/nodes/'.format(noderange)))[0].get('item', {}).get('href', None)
nodename = nodename[:-1]
p = input('Command is about to affect node {0}, continue (y/n)? '.format(nodename))
else:
p = input('Command is about to affect {0} nodes, continue (y/n)? '.format(nsize))
if p.lower() != 'y':
sys.stderr.write('Aborting at user request\n')
sys.exit(1)
raise Exception("Aborting at user request")
def get_noderange_size(self, noderange):
numnodes = 0
for node in self.read('/noderange/{0}/nodes/'.format(noderange)):
if node.get('item', {}).get('href', None):
numnodes += 1
else:
raise Exception("Error trying to size noderange {0}".format(noderange))
return numnodes
def simple_nodegroups_command(self, noderange, resource, input=None, key=None, **kwargs):
try:
@@ -376,8 +344,12 @@ class Command(object):
if fingerprint == khf[hostid]:
return
else:
replace = input(
"MISMATCHED CERTIFICATE DATA, ACCEPT NEW? (y/n):")
try:
replace = raw_input(
"MISMATCHED CERTIFICATE DATA, ACCEPT NEW? (y/n):")
except NameError:
replace = input(
"MISMATCHED CERTIFICATE DATA, ACCEPT NEW? (y/n):")
if replace not in ('y', 'Y'):
raise Exception("BAD CERTIFICATE")
cprint('Adding new key for %s:%s' % (server, port))
@@ -429,10 +401,6 @@ def printattributes(session, requestargs, showtype, nodetype, noderange, options
path = '/{0}/{1}/attributes/{2}'.format(nodetype, noderange, showtype)
return print_attrib_path(path, session, requestargs, options)
def _sort_attrib(k):
if isinstance(k[1], dict) and k[1].get('sortid', None) is not None:
return k[1]['sortid']
return k[0]
def print_attrib_path(path, session, requestargs, options, rename=None, attrprefix=None):
exitcode = 0
@@ -443,7 +411,9 @@ def print_attrib_path(path, session, requestargs, options, rename=None, attrpref
exitcode = 1
continue
for node in sorted(res['databynode']):
for attr, val in sorted(res['databynode'][node].items(), key=_sort_attrib):
for attr, val in sorted(
res['databynode'][node].items(),
key=lambda k: k[1].get('sortid', k[0]) if isinstance(k[1], dict) else k[0]):
if attr == 'error':
sys.stderr.write('{0}: Error: {1}\n'.format(node, val))
continue
+26 -20
View File
@@ -46,8 +46,9 @@ alias nodelicense='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export
_confluent_get_args()
{
CMPARGS=($COMP_LINE)
NUMARGS=$((COMP_CWORD+1))
if [ "${COMP_WORDS[COMP_CWORD]}" == '' ]; then
NUMARGS=${#CMPARGS[@]}
if [ "${COMP_WORDS[-1]}" == '' ]; then
NUMARGS=$((NUMARGS+1))
CMPARGS+=("")
fi
GENNED=""
@@ -74,7 +75,7 @@ function _confluent_generic_completion()
{
_confluent_get_args
if [ $NUMARGS -ge 3 ] && [ ! -z "$GENNED" ]; then
COMPREPLY=($(compgen -W "$GENNED" -- ${COMP_WORDS[COMP_CWORD]}))
COMPREPLY=($(compgen -W "$GENNED" -- ${COMP_WORDS[-1]}))
fi
if [ $NUMARGS -lt 3 ]; then
_confluent_nr_completion
@@ -110,7 +111,7 @@ _confluent_nodemedia_completion()
return
fi
if [ $NUMARGS -ge 3 ] && [ ! -z "$GENNED" ]; then
COMPREPLY=($(compgen -W "$GENNED" -- ${COMP_WORDS[COMP_CWORD]}))
COMPREPLY=($(compgen -W "$GENNED" -- ${COMP_WORDS[-1]}))
return;
fi
if [ $NUMARGS -lt 3 ]; then
@@ -123,7 +124,7 @@ _confluent_nodefirmware_completion()
{
_confluent_get_args
if [ $NUMARGS == 3 ]; then
COMPREPLY=($(compgen -W "list update" -- ${COMP_WORDS[COMP_CWORD]}))
COMPREPLY=($(compgen -W "list update" -- ${COMP_WORDS[-1]}))
return;
fi
if [ $NUMARGS -gt 3 ] && [ ${CMPARGS[2]} == 'update' ]; then
@@ -141,7 +142,7 @@ _confluent_nodeshell_completion()
{
_confluent_get_args
if [ $NUMARGS == 3 ]; then
COMPREPLY=($(compgen -c -- ${COMP_WORDS[COMP_CWORD]}))
COMPREPLY=($(compgen -c -- ${COMP_WORDS[-1]}))
return
fi
if [ $NUMARGS -gt 3 ]; then
@@ -159,7 +160,7 @@ _confluent_nodelicense_completion()
{
_confluent_get_args
if [ $NUMARGS == 3 ]; then
COMPREPLY=($(compgen -W "install list save delete" -- ${COMP_WORDS[COMP_CWORD]}))
COMPREPLY=($(compgen -W "install list save delete" -- ${COMP_WORDS[-1]}))
return;
fi
if [ $NUMARGS == 4 ] && [ ${CMPARGS[2]} == 'install' ]; then
@@ -182,7 +183,7 @@ _confluent_nodesupport_completion()
{
_confluent_get_args
if [ $NUMARGS == 3 ]; then
COMPREPLY=($(compgen -W "servicedata" -- ${COMP_WORDS[COMP_CWORD]}))
COMPREPLY=($(compgen -W "servicedata" -- ${COMP_WORDS[-1]}))
return;
fi
if [ $NUMARGS == 4 ] && [ ${CMPARGS[2]} == 'servicedata' ]; then
@@ -209,36 +210,41 @@ _confluent_nn_completion()
if [ $NUMARGS -gt 2 ]; then
return;
fi
INPUT=${COMP_WORDS[COMP_CWORD]}
INPUT=${COMP_WORDS[-1]}
INPUT=${INPUT##*,-}
INPUT=${INPUT##*,}
INPUT=${INPUT##*@}
PREFIX=""
if [ "$INPUT" != "${COMP_WORDS[COMP_CWORD]}" ]; then
PREFIX=${COMP_WORDS[COMP_CWORD]}
if [ "$INPUT" != "${COMP_WORDS[-1]}" ]; then
PREFIX=${COMP_WORDS[-1]}
PREFIX=$(echo $PREFIX | sed -e 's/,[^,@-]*$/,/' -e 's/,-[^,@]*$/,-/' -e 's/@[^,@]*/@/')
fi
COMPREPLY=($(compgen -W "$(nodelist | sed -e s/^/$PREFIX/)" -- "${COMP_WORDS[COMP_CWORD]}"))
COMPREPLY=($(compgen -W "$(nodelist | sed -e s/^/$PREFIX/)" -- "${COMP_WORDS[-1]}"))
}
_confluent_nr_completion()
{
CMPARGS=($COMP_LINE)
NUMARGS=${#CMPARGS[@]}
if [ "${COMP_WORDS[-1]}" == '' ]; then
NUMARGS=$((NUMARGS+1))
fi
_confluent_get_args
if [ $NUMARGS -gt 2 ]; then
return;
fi
INPUT=${COMP_WORDS[COMP_CWORD]}
INPUT=${COMP_WORDS[-1]}
INPUT=${INPUT##*,-}
INPUT=${INPUT##*,}
INPUT=${INPUT##*@}
PREFIX=""
if [ "$INPUT" != "${COMP_WORDS[COMP_CWORD]}" ]; then
PREFIX=${COMP_WORDS[COMP_CWORD]}
if [ "$INPUT" != "${COMP_WORDS[-1]}" ]; then
PREFIX=${COMP_WORDS[-1]}
PREFIX=$(echo $PREFIX | sed -e 's/,[^,@-]*$/,/' -e 's/,-[^,@]*$/,-/' -e 's/@[^,@]*/@/')
fi
COMPREPLY=($(compgen -W "$(confetty show /nodegroups|sed -e 's/\///' -e s/^/$PREFIX/;nodelist | sed -e s/^/$PREFIX/)" -- "${COMP_WORDS[COMP_CWORD]}"))
#COMPREPLY=($(compgen -W "$(confetty show /nodegroups|sed -e 's/\///' -e s/^/$PREFIX/;nodelist | sed -e s/^/$PREFIX/)" -- "${COMP_WORDS[-1]}"))
COMPREPLY=($(compgen -W "$(confetty show /nodegroups|sed -e 's/\///' -e s/^/$PREFIX/;nodelist | sed -e s/^/$PREFIX/)" -- "${COMP_WORDS[-1]}"))
}
_confluent_ng_completion()
{
@@ -246,17 +252,17 @@ _confluent_ng_completion()
if [ $NUMARGS -gt 2 ]; then
return;
fi
INPUT=${COMP_WORDS[COMP_CWORD]}
INPUT=${COMP_WORDS[-1]}
INPUT=${INPUT##*,-}
INPUT=${INPUT##*,}
INPUT=${INPUT##*@}
PREFIX=""
if [ "$INPUT" != "${COMP_WORDS[COMP_CWORD]}" ]; then
PREFIX=${COMP_WORDS[COMP_CWORD]}
if [ "$INPUT" != "${COMP_WORDS[-1]}" ]; then
PREFIX=${COMP_WORDS[-1]}
PREFIX=$(echo $PREFIX | sed -e 's/,[^,@-]*$/,/' -e 's/,-[^,@]*$/,-/' -e 's/@[^,@]*/@/')
fi
COMPREPLY=($(compgen -W "$(confetty show /nodegroups|sed -e 's/\///' -e s/^/$PREFIX/)" -- "${COMP_WORDS[COMP_CWORD]}"))
COMPREPLY=($(compgen -W "$(confetty show /nodegroups|sed -e 's/\///' -e s/^/$PREFIX/)" -- "${COMP_WORDS[-1]}"))
}
complete -F _confluent_nodeattrib_completion nodeattrib
complete -F _confluent_nodeattrib_completion nodegroupattrib
+1 -5
View File
@@ -13,11 +13,7 @@ nodefirmware(8) -- Report firmware information on confluent nodes
will retrieve all firmware, but can be directed to fetch specific firmware by
calling out the name of the firmware (e.g. uefi or xcc) or request reading only
core firmware firmware by using the word 'core', which is generally a quicker
operation. Different hardwaremanagement.method indicated plugins may have
different capabilities available. For example, the 'core' distinction may
not be relevant to redfish. Additionally, the Lenovo XCC makes certain
information available over IPMI that is not otherwise available (for example
the FPGA version where applicable).
operation.
In the update form, it accepts a single file and attempts to update it using
the out of band facilities. Firmware updates can end in one of three states:
+1 -4
View File
@@ -7,7 +7,7 @@ nodesupport(8) -- Utilities for interacting with vendor support
## DESCRIPTION
`nodesupport` provides capabilities associated with interacting with support.
`nodesupport` provides capabilities associated with interactiong with support.
Currently it only has the `servicedata` subcommand. `servicedata` takes
an argument that is either a directory name (that can be used for a single node
or multiple nodes) or a file name (only to be used with single node noderange).
@@ -16,9 +16,6 @@ connects to the managed system, so it will download to the remote system if runn
remotely and will download to the collective.manager indicated system if
running in collective mode.
Note that due to vendor filename requirements, any filename may have vendor
specific suffixes added to any file produced.
## EXAMPLES
* Download support data from a single node to a specific filename
+3 -7
View File
@@ -3,12 +3,8 @@ cd `dirname $0`
PKGNAME=$(basename $(pwd))
DPKGNAME=$(basename $(pwd) | sed -e s/_/-/)
OPKGNAME=$(basename $(pwd) | sed -e s/_/-/)
PYEXEC=python3
DSCARGS="--with-python3=True --with-python2=False"
if grep wheezy /etc/os-release; then
DPKGNAME=python-$DPKGNAME
PYEXEC=python
DSCARGS="--with-python3=False --with-python2=True"
fi
cd ..
mkdir -p /tmp/confluent # $DPKGNAME
@@ -28,15 +24,15 @@ install-scripts=/opt/confluent/bin
package=$DPKGNAME
EOF
$PYEXEC setup.py sdist > /dev/null 2>&1
py2dsc $DSCARGS dist/*.tar.gz
python setup.py sdist > /dev/null 2>&1
py2dsc dist/*.tar.gz
shopt -s extglob
cd deb_dist/!(*.orig)/
if [ "$OPKGNAME" = "confluent-server" ]; then
if grep wheezy /etc/os-release; then
sed -i 's/^\(Depends:.*\)/\1, python-confluent-client, python-lxml, python-eficompressor, python-pycryptodomex, python-dateutil, python-pyopenssl/' debian/control
else
sed -i 's/^\(Depends:.*\)/\1, confluent-client, python3-lxml, python3-eficompressor, python3-pycryptodome, python3-websocket/' debian/control
sed -i 's/^\(Depends:.*\)/\1, confluent-client, python-lxml, python-eficompressor, python-pycryptodome, python-dateutil/' debian/control
fi
if grep wheezy /etc/os-release; then
echo 'confluent_client python-confluent-client' >> debian/pydist-overrides
+2 -2
View File
@@ -58,9 +58,9 @@ _allowedbyrole = {
'/node*/configuration/*',
],
'update': [
'/discovery/*',
'/discovery/*',
'/networking/macs/rescan',
'/node*/power/state',
'/node*/power/state',
'/node*/power/reseat',
'/node*/attributes/*',
'/node*/media/*tach',
@@ -160,11 +160,11 @@ def connect_to_leader(cert=None, name=None, leader=None):
raise
currentleader = leader
#spawn this as a thread...
follower = eventlet.spawn(follow_leader, remote, pvers, leader)
follower = eventlet.spawn(follow_leader, remote, pvers)
return True
def follow_leader(remote, proto, leader):
def follow_leader(remote, proto):
global currentleader
cleanexit = False
try:
@@ -176,8 +176,8 @@ def follow_leader(remote, proto, leader):
log.log({'info': 'Previous following cleanly closed',
'subsystem': 'collective'})
return
log.log({'info': 'Current leader ({0}) has disappeared, restarting '
'collective membership'.format(leader), 'subsystem': 'collective'})
log.log({'info': 'Current leader has disappeared, restarting '
'collective membership', 'subsystem': 'collective'})
# The leader has folded, time to startup again...
cfm.stop_following()
currentleader = None
@@ -97,6 +97,15 @@ node = {
'description': ('Classification of node as server or switch'),
'validvalues': ('switch', 'server'),
},
'api.key': {
'description': ('Crypt of api key for self api requests by node'),
},
'api.armed': {
'description': ('Indicates whether an insecure api key request is allowed. '
'The format is an expiration time in ISO8601 format. When '
'the indicated time passes or the first time a node claims '
'the key, key grants will not be allowed.'),
},
#'id': {
# 'description': ('Numeric identifier for node')
#},
@@ -271,7 +280,7 @@ node = {
'console.method': {
'description': ('Indicate the method used to access the console of '
'the managed node.'),
'validvalues': ('ssh', 'ipmi', 'tsmsol'),
'validvalues': ('ssh', 'ipmi'),
},
# 'virtualization.host': {
# 'description': ('Hypervisor where this node does/should reside'),
@@ -328,11 +328,6 @@ def exec_on_leader(function, *args):
def exec_on_followers(fnname, *args):
pushes = eventlet.GreenPool()
# Check health of collective prior to attempting
for _ in pushes.starmap(
_push_rpc, [(cfgstreams[s], b'') for s in cfgstreams]):
pass
if len(cfgstreams) < (len(_cfgstore['collective']) // 2):
# the leader counts in addition to registered streams
raise exc.DegradedCollective()
@@ -391,15 +386,9 @@ def init_masterkey(password=None, autogen=True):
def _push_rpc(stream, payload):
with _rpclock:
try:
stream.sendall(struct.pack('!Q', len(payload)))
if len(payload):
stream.sendall(payload)
return True
except Exception:
logException()
del cfgstreams[stream]
stream.close()
stream.sendall(struct.pack('!Q', len(payload)))
if len(payload):
stream.sendall(payload)
def decrypt_value(cryptvalue,
@@ -589,18 +578,11 @@ def relay_slaved_requests(name, listener, vers):
lh = StreamHandler(listener)
_hasquorum = len(cfgstreams) >= (
len(_cfgstore['collective']) // 2)
_newquorum = None
while _hasquorum != _newquorum:
if _newquorum is not None:
_hasquorum = _newquorum
payload = cPickle.dumps({'quorum': _hasquorum}, protocol=lowestver)
for _ in pushes.starmap(
_push_rpc,
[(cfgstreams[s], payload) for s in cfgstreams]):
pass
_newquorum = len(cfgstreams) >= (
len(_cfgstore['collective']) // 2)
_hasquorum = _newquorum
payload = cPickle.dumps({'quorum': _hasquorum}, protocol=lowestver)
for _ in pushes.starmap(
_push_rpc,
[(cfgstreams[s], payload) for s in cfgstreams]):
pass
if _hasquorum and _pending_collective_updates:
apply_pending_collective_updates()
msg = lh.get_next_msg()
@@ -622,10 +604,8 @@ def relay_slaved_requests(name, listener, vers):
except Exception as e:
exc = e
if 'xid' in rpc:
res = _push_rpc(listener, cPickle.dumps({'xid': rpc['xid'],
_push_rpc(listener, cPickle.dumps({'xid': rpc['xid'],
'exc': exc}, protocol=vers))
if not res:
break
try:
msg = lh.get_next_msg()
except Exception:
@@ -670,9 +650,7 @@ class StreamHandler(object):
if confluent.util.monotonic_time() > self.expiry:
return None
if confluent.util.monotonic_time() > self.keepalive:
res = _push_rpc(self.sock, b'') # nulls are a keepalive
if not res:
return None
_push_rpc(self.sock, b'') # nulls are a keepalive
self.keepalive = confluent.util.monotonic_time() + 20
self.expiry = confluent.util.monotonic_time() + 60
msg = self.sock.recv(8)
@@ -789,9 +767,7 @@ def follow_channel(channel, proto=2):
_pendingchangesets[rpc['xid']].send()
if 'quorum' in rpc:
_hasquorum = rpc['quorum']
res = _push_rpc(channel, b'') # use null as ACK
if not res:
break
_push_rpc(channel, b'') # use null as ACK
msg = lh.get_next_msg()
finally:
# mark the connection as broken
@@ -1161,8 +1137,6 @@ class ConfigManager(object):
attribute, match = expression.split('=')
else:
raise Exception('Invalid Expression')
if attribute.startswith('secret.'):
raise Exception('Filter by secret attributes is not supported')
for node in nodes:
try:
currvals = [self._cfgstore['nodes'][node][attribute]['value']]
@@ -1652,7 +1626,6 @@ class ConfigManager(object):
if group in self._cfgstore['nodes'][node]['groups']:
self._cfgstore['nodes'][node]['groups'].remove(group)
self._node_removed_from_group(node, group, changeset)
_mark_dirtykey('nodes', node, self.tenant)
for node in nodes:
if node not in self._cfgstore['nodes']:
self._cfgstore['nodes'][node] = {'groups': [group]}
+7 -7
View File
@@ -362,17 +362,13 @@ class ConsoleHandler(object):
if self.reconnect:
self.reconnect.cancel()
self.reconnect = None
strerror = ('The console.method attribute for this node is '
'not configured,\r\nset it to a valid value for console '
'function')
try:
self._console = list(plugin.handle_path(
self._plugin_path.format(self.node),
"create", self.cfgmgr))[0]
except (exc.NotImplementedException, exc.NotFoundException):
self._console = None
except Exception as e:
strerror = str(e)
except:
if _tracelog:
_tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
event=log.Events.stacktrace)
@@ -385,9 +381,13 @@ class ConsoleHandler(object):
self._send_rcpts({'connectstate': self.connectstate,
'error': self.error})
self.feedbuffer(
'\x1bc\x1b[2J\x1b[1;1H[{0}]'.format(strerror))
'\x1bc\x1b[2J\x1b[1;1H[The console.method attribute for this node is '
'not configured,\r\nset it to a valid value for console '
'function]')
self._send_rcpts(
'\x1bc\x1b[2J\x1b[1;1H[{0}]'.format(strerror))
'\x1bc\x1b[2J\x1b[1;1H[The console.method attribute for this node is '
'not configured,\r\nset it to a valid value for console '
'function]')
self.clearerror = True
return
if self.clearerror:
+2 -2
View File
@@ -72,7 +72,7 @@ import struct
import sys
pluginmap = {}
dispatch_plugins = (b'ipmi', u'ipmi', b'redfish', u'redfish', b'tsmsol', u'tsmsol')
dispatch_plugins = (b'ipmi', u'ipmi')
def seek_element(currplace, currkey):
@@ -224,7 +224,7 @@ def _init_core():
'advanced': PluginRoute({
'pluginattrs': ['hardwaremanagement.method'],
'default': 'ipmi',
}),
}),
},
},
'storage': {
+87
View File
@@ -0,0 +1,87 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2019 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import confluent.config.configmanager as cfm
import datetime
import eventlet
import eventlet.green.socket as socket
import eventlet.greenpool
import os
class CredServer(object):
def __init__(self, bindhost='::', bindport=301, ttl=1):
self.srv = socket.socket(socket.AF_INET6)
self.srv.setsockopt(socket.SOL_IP, socket.IP_TTL, ttl)
self.srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.srv.bind((bindhost, bindport))
self.srv.listen(32)
self.gpool = eventlet.greenpool.GreenPool(256)
self.cfm = cfm.ConfigManager(None)
self.runtime = eventlet.spawn(self.listen)
def listen(self):
while True:
client, info = self.srv.accept()
if info[1] > 1023:
client.close()
continue
self.gpool.spawn_n(self.handle_client, client)
def handle_client(self, client):
client.send('\xc2\xd1-\xa8\x80\xd8j\xba')
tlv = bytearray(client.recv(2))
if tlv[0] != 1:
client.close()
return
nodename = client.recv(tlv[1])
tlv = bytearray(client.recv(2))
apiarmed = self.cfm.get_node_attributes(nodename, 'api.armed')
apiarmed = apiarmed.get(nodename, {}).get('api.armed', {}).get('value', None)
if not apiarmed:
client.close()
return
now = datetime.datetime.utcnow()
expiry = datetime.datetime.strptime(apiarmed, "%Y-%m-%dT%H:%M:%SZ")
if now > expiry:
self.cfm.set_node_attributes({nodename: {'api.armed': ''}})
client.close()
return
client.send(b'\x02\x20')
rttoken = os.urandom(32)
client.send(rttoken)
client.send('\x00\x00')
tlv = bytearray(client.recv(2))
if tlv[0] != 3:
client.close()
return
echotoken = client.recv(tlv[1])
if echotoken != rttoken:
client.close()
return
tlv = bytearray(client.recv(2))
if tlv[0] != 4:
client.close()
return
echotoken = client.recv(tlv[1])
self.cfm.set_node_attributes({nodename: {'api.key': echotoken, 'api.armed': ''}})
client.recv(2) # drain end of message
client.send('\x05\x00') # report success
client.close()
if __name__ == '__main__':
a = CredServer()
while True:
eventlet.sleep(86400)
+6 -9
View File
@@ -65,7 +65,7 @@ import base64
import confluent.config.configmanager as cfm
import confluent.collective.manager as collective
import confluent.discovery.protocols.pxe as pxe
#import confluent.discovery.protocols.ssdp as ssdp
import confluent.discovery.protocols.ssdp as ssdp
import confluent.discovery.protocols.slp as slp
import confluent.discovery.handlers.imm as imm
import confluent.discovery.handlers.cpstorage as cpstorage
@@ -871,6 +871,8 @@ def get_nodename_from_chained_smms(cfg, handler, info):
nodename = newnodename
return nodename
def get_node_by_uuid(uuid):
return nodes_by_uuid.get(uuid, None)
def get_nodename_from_enclosures(cfg, info):
nodename = None
@@ -949,9 +951,9 @@ def eval_node(cfg, handler, info, nodename, manual=False):
# raise exc.InvalidArgumentException(errorstr)
# log.log({'error': errorstr})
if encuuid in pending_by_uuid:
pending_by_uuid[encuuid].append(info)
pending_by_uuid[encuuid].add(info)
else:
pending_by_uuid[encuuid] = [info]
pending_by_uuid[encuuid] = set([info])
return
# We found the real smm, replace the list with the actual smm
# to continue
@@ -1094,10 +1096,6 @@ def discover_node(cfg, handler, info, nodename, manual):
info['discostatus'] = 'discovered'
for i in pending_by_uuid.get(curruuid, []):
eventlet.spawn_n(_recheck_single_unknown_info, cfg, i)
try:
del pending_by_uuid[curruuid]
except KeyError:
pass
return True
log.log({'info': 'Detected {0}, but discovery.policy is not set to a '
'value allowing discovery (open or permissive)'.format(
@@ -1232,8 +1230,7 @@ def start_detection():
if rechecker is None:
rechecktime = util.monotonic_time() + 900
rechecker = eventlet.spawn_after(900, _periodic_recheck, cfg)
# eventlet.spawn_n(ssdp.snoop, safe_detected)
eventlet.spawn_n(ssdp.snoop, None, None, ssdp, get_node_by_uuid)
def stop_autosense():
for watcher in list(autosensors):
@@ -68,21 +68,17 @@ class NodeHandler(object):
def _savecert(self, certificate):
self._fp = certificate
return True
def get_node_credentials(self, nodename, creds, defuser, defpass):
user = creds.get(nodename, {}).get(
'secret.hardwaremanagementuser', {}).get('value', None)
havecustomcreds = False
if user and not isinstance(user, str):
user = user.decode('utf8')
if user is not None and user != defuser:
havecustomcreds = True
else:
user = defuser
passwd = creds.get(nodename, {}).get(
'secret.hardwaremanagementpassword', {}).get('value', None)
if passwd and not isinstance(passwd, str):
passwd = passwd.decode('utf8')
if passwd is not None and passwd != defpass:
havecustomcreds = True
else:
@@ -98,7 +98,7 @@ class NodeHandler(bmchandler.NodeHandler):
setdata += ',v4Gateway:{0}'.format(gateway)
wc.request('POST', '/data', setdata)
rsp = wc.getresponse()
rspdata = util.stringify(rsp.read())
rspdata = rsp.read()
if '<statusCode>0' not in rspdata:
raise Exception("Error configuring SMM Network")
return
@@ -145,7 +145,7 @@ class NodeHandler(bmchandler.NodeHandler):
authdata['password'] = password
wc.request('POST', '/data/login', urlencode(authdata), headers)
rsp = wc.getresponse()
rspdata = util.stringify(rsp.read())
rspdata = rsp.read()
if 'authResult>0' in rspdata:
tokens = fromstring(rspdata)
st2 = tokens.findall('st2')[0].text
@@ -181,10 +181,6 @@ class NodeHandler(bmchandler.NodeHandler):
'secret.hardwaremanagementuser', {}).get('value', 'USERID')
passwd = creds.get(nodename, {}).get(
'secret.hardwaremanagementpassword', {}).get('value', 'PASSW0RD')
if not isinstance(username, str):
username = username.decode('utf8')
if not isinstance(passwd, str):
passwd = passwd.decode('utf8')
if passwd == 'PASSW0RD' and self.ruleset:
raise Exception('Cannot support default password and setting password rules at same time')
if passwd == 'PASSW0RD':
@@ -22,7 +22,6 @@ try:
from urllib import urlencode
except ImportError:
from urllib.parse import urlencode
getaddrinfo = eventlet.support.greendns.getaddrinfo
webclient = eventlet.import_patched('pyghmi.util.webclient')
@@ -44,13 +43,6 @@ class NodeHandler(generic.NodeHandler):
self.atdefault = True
super(NodeHandler, self).__init__(info, configmanager)
def scan(self):
c = webclient.SecureHTTPConnection(self.ipaddr, 443, verifycallback=self.validate_cert)
i = c.grab_json_response('/redfish/v1/')
uuid = i.get('UUID', None)
if uuid:
self.info['uuid'] = uuid
def validate_cert(self, certificate):
# broadly speaking, merely checks consistency moment to moment,
# but if https_cert gets stricter, this check means something
@@ -62,17 +54,9 @@ class NodeHandler(generic.NodeHandler):
'username': self.DEFAULT_USER,
'password': self.DEFAULT_PASS,
}
wc = webclient.SecureHTTPConnection(self.ipaddr, 443, verifycallback=self.validate_cert)
wc.set_header('Content-Type', 'application/json')
authmode = 0
if not self.trieddefault:
rsp, status = wc.grab_json_response_with_status('/api/session', authdata)
if status == 403:
wc.set_header('Content-Type', 'application/x-www-form-urlencoded')
authmode = 1
rsp, status = wc.grab_json_response_with_status('/api/session', urlencode(authdata))
else:
authmode = 2
wc = webclient.SecureHTTPConnection(self.ipaddr, 443, verifycallback=self.validate_cert)
rsp, status = wc.grab_json_response_with_status('/api/session', urlencode(authdata))
if status > 400:
rsp = util.stringify(rsp)
self.trieddefault = True
@@ -84,15 +68,9 @@ class NodeHandler(generic.NodeHandler):
'default_password': self.DEFAULT_PASS,
'username': self.DEFAULT_USER
}
if authmode == 2:
rsp, status = wc.grab_json_response_with_status('/api/reset-pass', passchange)
else:
rsp, status = wc.grab_json_response_with_status('/api/reset-pass', urlencode(passchange))
rsp, status = wc.grab_json_response_with_status('/api/reset-pass', urlencode(passchange))
authdata['password'] = self.targpass
if authmode == 2:
rsp, status = wc.grab_json_response_with_status('/api/session', authdata)
else:
rsp, status = wc.grab_json_response_with_status('/api/session', urlencode(authdata))
rsp, status = wc.grab_json_response_with_status('/api/session', urlencode(authdata))
self.csrftok = rsp['CSRFToken']
self.channel = rsp['channel']
self.curruser = self.DEFAULT_USER
@@ -107,23 +85,15 @@ class NodeHandler(generic.NodeHandler):
if self.curruser:
authdata['username'] = self.curruser
authdata['password'] = self.currpass
if authmode != 1:
rsp, status = wc.grab_json_response_with_status('/api/session', authdata)
if authmode == 1 or status == 403:
wc.set_header('Content-Type', 'application/x-www-form-urlencoded')
rsp, status = wc.grab_json_response_with_status('/api/session', urlencode(authdata))
if status != 200:
rsp, status = wc.grab_json_response_with_status('/api/session', 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
if authmode != 1:
rsp, status = wc.grab_json_response_with_status('/api/session', authdata)
if authmode == 1 or status == 403:
wc.set_header('Content-Type', 'application/x-www-form-urlencoded')
rsp, status = wc.grab_json_response_with_status('/api/session', urlencode(authdata))
rsp, status = wc.grab_json_response_with_status('/api/session', urlencode(authdata))
if status != 200:
return None
self.curruser = self.targuser
@@ -15,7 +15,6 @@
import base64
import codecs
import confluent.discovery.handlers.imm as immhandler
import confluent.exceptions as exc
import confluent.netutil as netutil
import confluent.util as util
import errno
@@ -96,8 +95,7 @@ class NodeHandler(immhandler.NodeHandler):
ipmicmd.xraw_command(netfn=0x3a, command=0xf1, data=(1,))
except pygexc.IpmiException as e:
if (e.ipmicode != 193 and 'Unauthorized name' not in str(e) and
'Incorrect password' not in str(e) and
str(e) != 'Session no longer connected'):
'Incorrect password' not in str(e)):
# raise an issue if anything other than to be expected
if disableipmi:
_, _ = wc.grab_json_response_with_status(
@@ -172,12 +170,7 @@ class NodeHandler(immhandler.NodeHandler):
pwdchanged = True
if '_csrf_token' in wc.cookies:
wc.set_header('X-XSRF-TOKEN', wc.cookies['_csrf_token'])
if pwdchanged:
# Remove the minimum change interval, to allow sane
# password changes after provisional changes
self.set_password_policy('')
return (wc, pwdchanged)
return (None, None)
@property
def wc(self):
@@ -211,7 +204,7 @@ class NodeHandler(immhandler.NodeHandler):
# however the target *will* demand a new password... if it's currently
# PASSW0RD
# use TempW0rd42 to avoid divulging a real password on the line
# This is replacing one well known password (PASSW0RD) with another
# This is replacing one well known password (PASSW0RD) with another
# (TempW0rd42)
passwd = 'TempW0rd42'
wc, pwdchanged = self.get_webclient('USERID', 'PASSW0RD', passwd)
@@ -233,9 +226,9 @@ class NodeHandler(immhandler.NodeHandler):
if wc:
return wc
def set_password_policy(self, strruleset):
def set_password_policy(self):
ruleset = {'USER_GlobalMinPassChgInt': '0'}
for rule in strruleset.split(','):
for rule in self.ruleset.split(','):
if '=' not in rule:
continue
name, value = rule.split('=')
@@ -357,18 +350,15 @@ class NodeHandler(immhandler.NodeHandler):
# between hypothetical secure path and today.
dpp = self.configmanager.get_node_attributes(
nodename, 'discovery.passwordrules')
strruleset = dpp.get(nodename, {}).get(
self.ruleset = dpp.get(nodename, {}).get(
'discovery.passwordrules', {}).get('value', '')
wc = self.wc
creds = self.configmanager.get_node_attributes(
self.nodename, ['secret.hardwaremanagementuser',
'secret.hardwaremanagementpassword'], decrypt=True)
user, passwd, isdefault = self.get_node_credentials(nodename, creds, 'USERID', 'PASSW0RD')
self.set_password_policy(strruleset)
self.set_password_policy()
if self._atdefaultcreds:
if isdefault and self.tmppasswd:
raise Exception(
'Request to use default credentials, but refused by target after it has been changed to {0}'.format(self.tmppasswd))
if not isdefault:
self._setup_xcc_account(user, passwd, wc)
self._convert_sha256account(user, passwd, wc)
@@ -84,8 +84,6 @@ def _parse_SrvRply(parsed):
:return:
"""
payload = parsed['payload']
if len(payload) < 4:
return
ecode, ucount = struct.unpack('!HH', bytes(payload[0:4]))
if ecode:
parsed['errorcode'] = ecode
@@ -236,20 +234,13 @@ def _find_srvtype(net, net4, srvtype, addresses, xid):
def _grab_rsps(socks, rsps, interval, xidmap):
r = None
res = select.select(socks, (), (), interval)
if res:
r = res[0]
r, _, _ = select.select(socks, (), (), interval)
while r:
for s in r:
(rsp, peer) = s.recvfrom(9000)
neighutil.refresh_neigh()
_parse_slp_packet(rsp, peer, rsps, xidmap)
res = select.select(socks, (), (), interval)
if not res:
r = None
else:
r = res[0]
r, _, _ = select.select(socks, (), (), interval)
@@ -569,7 +560,7 @@ def scan(srvtypes=_slp_services, addresses=None, localonly=False):
# now to analyze and flesh out the responses
for id in rsps:
if 'service:ipmi' in rsps[id]['services']:
if 'service:ipmi://Athena:623' in rsps[id].get('urls', ''):
if 'service:ipmi://Athena:623' in rsps[id]['urls']:
rsps[id]['services'] = ['service:thinkagile-storage']
else:
continue
@@ -30,9 +30,11 @@
import confluent.neighutil as neighutil
import confluent.util as util
import confluent.log as log
import eventlet.green.select as select
import eventlet.green.socket as socket
import struct
import traceback
mcastv4addr = '239.255.255.250'
mcastv6addr = 'ff02::c'
@@ -51,7 +53,7 @@ def scan(services, target=None):
yield rply
def snoop(handler, byehandler=None):
def snoop(handler, byehandler=None, protocol=None, uuidlookup=None):
"""Watch for SSDP notify messages
The handler shall be called on any service coming online.
@@ -67,6 +69,7 @@ def snoop(handler, byehandler=None):
# Normally, I like using v6/v4 agnostic socket. However, since we are
# dabbling in multicast wizardry here, such sockets can cause big problems,
# so we will have two distinct sockets
tracelog = log.Logger('trace')
known_peers = set([])
net6 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
net6.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
@@ -90,50 +93,73 @@ def snoop(handler, byehandler=None):
net6.bind(('', 1900))
peerbymacaddress = {}
while True:
newmacs = set([])
machandlers = {}
r, _, _ = select.select((net4, net6), (), (), 60)
neighutil.update_neigh()
while r:
for s in r:
(rsp, peer) = s.recvfrom(9000)
rsp = rsp.split('\r\n')
method, _, _ = rsp[0].split(' ', 2)
if method == 'NOTIFY':
ip = peer[0].partition('%')[0]
if ip not in neighutil.neightable:
continue
if peer in known_peers:
continue
mac = neighutil.neightable[ip]
known_peers.add(peer)
newmacs.add(mac)
if mac in peerbymacaddress:
peerbymacaddress[mac]['peers'].append(peer)
else:
peerbymacaddress[mac] = {
'hwaddr': mac,
'peers': [peer],
}
peerdata = peerbymacaddress[mac]
try:
newmacs = set([])
machandlers = {}
r, _, _ = select.select((net4, net6), (), (), 60)
neighutil.update_neigh()
while r:
for s in r:
(rsp, peer) = s.recvfrom(9000)
rsp = rsp.split('\r\n')
method, _, _ = rsp[0].split(' ', 2)
if method == 'NOTIFY':
ip = peer[0].partition('%')[0]
if ip not in neighutil.neightable:
continue
if peer in known_peers:
continue
mac = neighutil.neightable[ip]
known_peers.add(peer)
newmacs.add(mac)
if mac in peerbymacaddress:
peerbymacaddress[mac]['peers'].append(peer)
else:
peerbymacaddress[mac] = {
'hwaddr': mac,
'peers': [peer],
}
peerdata = peerbymacaddress[mac]
for headline in rsp[1:]:
if not headline:
continue
header, _, value = headline.partition(':')
header = header.strip()
value = value.strip()
if header == 'NT':
peerdata['service'] = value
elif header == 'NTS':
if value == 'ssdp:byebye':
machandlers[mac] = byehandler
elif value == 'ssdp:alive':
machandlers[mac] = None # handler
elif method == 'M-SEARCH':
if not uuidlookup:
continue
#ip = peer[0].partition('%')[0]
for headline in rsp[1:]:
if not headline:
continue
header, _, value = headline.partition(':')
header = header.strip()
value = value.strip()
if header == 'NT':
peerdata['service'] = value
elif header == 'NTS':
if value == 'ssdp:byebye':
machandlers[mac] = byehandler
elif value == 'ssdp:alive':
machandlers[mac] = handler
r, _, _ = select.select((net4, net6), (), (), 0.1)
for mac in newmacs:
thehandler = machandlers.get(mac, None)
if thehandler:
thehandler(peerbymacaddress[mac])
headline = headline.partition(':')
if len(headline) < 3:
continue
if headline[0] == 'ST' and headline[-1].startswith(' urn:xcat.org:service:confluent:'):
for query in headline[-1].split('/'):
if query.startswith('uuid='):
curruuid = query.split('=', 1)[1].lower()
node = uuidlookup(curruuid)
if not node:
break
reply = 'HTTP/1.1 200 OK\r\nNODENAME: {0}'.format(node)
s.sendto(reply, peer)
r, _, _ = select.select((net4, net6), (), (), 0.2)
for mac in newmacs:
thehandler = machandlers.get(mac, None)
if thehandler:
thehandler(peerbymacaddress[mac])
except Exception:
tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
event=log.Events.stacktrace)
def _find_service(service, target):
+1 -4
View File
@@ -107,14 +107,11 @@ class PubkeyInvalid(ConfluentException):
self.fingerprint = fingerprint
self.attrname = attribname
self.message = text
certtxt = base64.b64encode(certificate)
if not isinstance(certtxt, str):
certtxt = certtxt.decode('utf8')
bodydata = {'message': text,
'event': event,
'fingerprint': fingerprint,
'fingerprintfield': attribname,
'certificate': certtxt}
'certificate': base64.b64encode(certificate)}
self.errorbody = json.dumps(bodydata)
def get_error_body(self):
@@ -43,16 +43,13 @@ def execupdate(handler, filename, updateobj, type, owner, node):
'detail': errstr})
return
if type == 'ffdc' and os.path.isdir(filename):
filename += '/' + node
filename += '/' + node + '.svcdata'
try:
if type == 'firmware':
completion = handler(filename, progress=updateobj.handle_progress,
bank=updateobj.bank)
else:
completion = handler(filename, progress=updateobj.handle_progress)
if type == 'ffdc' and completion:
filename = completion
completion = None
if completion is None:
completion = 'complete'
if owner:
+2 -10
View File
@@ -76,7 +76,7 @@ def _htmlify_structure(indict):
if datum is None:
nd.append('')
else:
nd.append(util.stringify(datum))
nd.append(datum)
ret += ",".join(nd)
else:
for v in indict:
@@ -138,7 +138,6 @@ class ConfluentMessage(object):
snippet = ""
for key in pairs:
val = pairs[key]
key = util.stringify(key)
value = self.defaultvalue
if isinstance(val, dict) and 'type' in val:
valtype = val['type']
@@ -339,7 +338,6 @@ class ConfluentChoiceMessage(ConfluentMessage):
snippet = ''
for key in pairdata:
val = pairdata[key]
key = util.stringify(key)
snippet += key + ':<select name="%s">' % key
valid_values = self.valid_values
if key in self.valid_paramset:
@@ -1379,8 +1377,6 @@ class InputAlertDestination(ConfluentMessage):
self.alertcfg = {}
if multinode: # keys are node names
for node in inputdata:
if not isinstance(inputdata[node], dict):
break
self.alertcfg[node] = inputdata[node]
for key in inputdata[node]:
if key not in self.valid_alert_params:
@@ -1393,8 +1389,7 @@ class InputAlertDestination(ConfluentMessage):
else:
self.alertcfg[node][key] = \
self.valid_alert_params[key](inputdata[node][key])
else:
return
else:
for key in inputdata:
if key not in self.valid_alert_params:
raise exc.InvalidArgumentException(
@@ -1492,15 +1487,12 @@ class Volume(ConfluentMessage):
class Disk(ConfluentMessage):
valid_states = set([
'fault',
'jbod',
'unconfigured',
'hotspare',
'rebuilding',
'online',
])
state_aliases = {
'unconfigured bad': 'fault',
'unconfigured good': 'unconfigured',
'global hot spare': 'hotspare',
'dedicated hot spare': 'hotspare',
@@ -125,8 +125,7 @@ def _dump_neighbordatum(info):
def b64tohex(b64str):
bd = base64.b64decode(b64str)
bd = bytearray(bd)
return ''.join(['{0:02x}'.format(x) for x in bd])
return ''.join(['{0:02x}'.format(ord(x)) for x in bd])
def get_fingerprint(switch, port, configmanager, portmatch):
update_switch_data(switch, configmanager)
@@ -1,160 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2015-2019 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# This plugin provides an ssh implementation comforming to the 'console'
# specification. consoleserver or shellserver would be equally likely
# to use this.
import confluent.exceptions as cexc
import confluent.interface.console as conapi
import confluent.log as log
import confluent.util as util
import pyghmi.exceptions as pygexc
import pyghmi.redfish.command as rcmd
import eventlet
import eventlet.green.ssl as ssl
try:
websocket = eventlet.import_patched('websocket')
wso = websocket.WebSocket
except Exception:
wso = object
def get_conn_params(node, configdata):
if 'secret.hardwaremanagementuser' in configdata:
username = configdata['secret.hardwaremanagementuser']['value']
else:
username = 'USERID'
if 'secret.hardwaremanagementpassword' in configdata:
passphrase = configdata['secret.hardwaremanagementpassword']['value']
else:
passphrase = 'PASSW0RD' # for lack of a better guess
if 'hardwaremanagement.manager' in configdata:
bmc = configdata['hardwaremanagement.manager']['value']
else:
bmc = node
return {
'username': username,
'passphrase': passphrase,
'bmc': bmc,
}
_configattributes = ('secret.hardwaremanagementuser',
'secret.hardwaremanagementpassword',
'hardwaremanagement.manager')
class WrappedWebSocket(wso):
def set_verify_callback(self, callback):
self._certverify = callback
def connect(self, url, **options):
add_tls = url.startswith('wss://')
if add_tls:
hostname, port, resource, _ = websocket._url.parse_url(url)
if hostname[0] != '[' and ':' in hostname:
hostname = '[{0}]'.format(hostname)
if resource[0] != '/':
resource = '/{0}'.format(resource)
url = 'ws://{0}:443{1}'.format(hostname,resource)
else:
return super(WrappedWebSocket, self).connect(url, **options)
self.sock_opt.timeout = options.get('timeout', self.sock_opt.timeout)
self.sock, addrs = websocket._http.connect(url, self.sock_opt, websocket._http.proxy_info(**options),
options.pop('socket', None))
self.sock = ssl.wrap_socket(self.sock, cert_reqs=ssl.CERT_NONE)
# The above is supersedeed by the _certverify, which provides
# known-hosts style cert validaiton
bincert = self.sock.getpeercert(binary_form=True)
if not self._certverify(bincert):
raise pygexc.UnrecognizedCertificate('Unknown certificate', bincert)
try:
self.handshake_response = websocket._handshake.handshake(self.sock, *addrs, **options)
if self.handshake_response.status in websocket._handshake.SUPPORTED_REDIRECT_STATUSES:
options['redirect_limit'] = options.pop('redirect_limit', 3) - 1
if options['redirect_limit'] < 0:
raise Exception('Redirect limit hit')
url = self.handshake_response.headers['location']
self.sock.close()
return self.connect(url, **options)
self.connected = True
except:
if self.sock:
self.sock.close()
self.sock = None
raise
class TsmConsole(conapi.Console):
def __init__(self, node, config):
self.node = node
self.ws = None
configdata = config.get_node_attributes([node], _configattributes, decrypt=True)
connparams = get_conn_params(node, configdata[node])
self.username = connparams['username']
self.password = connparams['passphrase']
self.bmc = connparams['bmc']
self.origbmc = connparams['bmc']
if ':' in self.bmc:
self.bmc = '[{0}]'.format(self.bmc)
self.datacallback = None
self.nodeconfig = config
self.connected = False
def recvdata(self):
while self.connected:
pendingdata = self.ws.recv()
if pendingdata == '':
self.datacallback(conapi.ConsoleEvent.Disconnect)
return
self.datacallback(pendingdata)
def connect(self, callback):
self.datacallback = callback
rc = rcmd.Command(self.origbmc, self.username,
self.password,
verifycallback=lambda x: True)
wc = rc.oem.wc
bmc = self.bmc
if '%' in self.bmc:
prefix = self.bmc.split('%')[0]
bmc = prefix + ']'
self.ws = WrappedWebSocket(host=bmc)
kv = util.TLSCertVerifier(
self.nodeconfig, self.node, 'pubkeys.tls_hardwaremanager').verify_cert
self.ws.set_verify_callback(kv)
self.ws.connect('wss://{0}/sol?CSRFTOKEN={1}'.format(self.bmc, rc.oem.csrftok), host=bmc, cookie='QSESSIONID={0}'.format(wc.cookies['QSESSIONID']))
self.connected = True
eventlet.spawn_n(self.recvdata)
return
def write(self, data):
self.ws.send(data)
def close(self):
if self.ws:
self.ws.close()
self.connected = False
self.datacallback = None
def create(nodes, element, configmanager, inputdata):
if len(nodes) == 1:
return TsmConsole(nodes[0], configmanager)
@@ -78,18 +78,6 @@ def retrieve(nodes, element, configmanager, inputdata):
for node in nodes:
workers.add(eventlet.spawn(retrieve_health, configmanager, creds,
node, results))
elif element[:3] == ['inventory', 'hardware', 'all']:
creds = configmanager.get_node_attributes(
nodes, ['secret.hardwaremanagementuser', 'secret.hardwaremanagementpassword'], decrypt=True)
for node in nodes:
workers.add(eventlet.spawn(retrieve_inventory, configmanager,
creds, node, results, element))
elif element[:3] == ['inventory', 'firmware', 'all']:
creds = configmanager.get_node_attributes(
nodes, ['secret.hardwaremanagementuser', 'secret.hardwaremanagementpassword'], decrypt=True)
for node in nodes:
workers.add(eventlet.spawn(retrieve_firmware, configmanager,
creds, node, results, element))
else:
for node in nodes:
yield msg.ConfluentNodeError(node, 'Not Implemented')
@@ -117,44 +105,6 @@ def retrieve(nodes, element, configmanager, inputdata):
pass
def retrieve_inventory(configmanager, creds, node, results, element):
if len(element) == 3:
results.put(msg.ChildCollection('all'))
results.put(msg.ChildCollection('system'))
return
wc = cnos_login(node, configmanager, creds)
sysinfo = wc.grab_json_response('/nos/api/sysinfo/inventory')
invinfo = {
'inventory': [{
'name': 'System',
'present': True,
'information': {
'Product name': sysinfo['Model'],
'Serial Number': sysinfo['Electronic Serial Number'],
'Board Serial Number': sysinfo['Serial Number'],
'Manufacturer': 'Lenovo',
'Model': sysinfo['Machine Type Model'],
'FRU Number': sysinfo['FRU'].strip(),
}
}]
}
results.put(msg.KeyValueData(invinfo, node))
def retrieve_firmware(configmanager, creds, node, results, element):
if len(element) == 3:
results.put(msg.ChildCollection('all'))
return
wc = cnos_login(node, configmanager, creds)
sysinfo = wc.grab_json_response('/nos/api/sysinfo/inventory')
items = [{
'Software': {'version': sysinfo['Software Revision']},
},
{
'BIOS': {'version': sysinfo['BIOS Revision']},
}]
results.put(msg.Firmware(items, node))
def retrieve_health(configmanager, creds, node, results):
wc = cnos_login(node, configmanager, creds)
hinfo = wc.grab_json_response('/nos/api/sysinfo/globalhealthstatus')
@@ -42,11 +42,6 @@ import traceback
if not hasattr(ssl, 'SSLEOFError'):
ssl.SSLEOFError = None
try:
range = xrange
except NameError:
pass
pci_cache = {}
def get_dns_txt(qstring):
@@ -136,7 +131,7 @@ def hex2bin(hexstring):
if len(hexvals) < 2:
hexvals = hexstring.split(' ')
if len(hexvals) < 2:
hexvals = [hexstring[i:i+2] for i in range(0, len(hexstring), 2)]
hexvals = [hexstring[i:i+2] for i in xrange(0, len(hexstring), 2)]
bytedata = [int(i, 16) for i in hexvals]
return bytearray(bytedata)
@@ -283,7 +278,6 @@ def _donothing(data):
class IpmiConsole(conapi.Console):
configattributes = frozenset(_configattributes)
bmctonodemapping = {}
def __init__(self, node, config):
self.error = None
@@ -301,21 +295,10 @@ class IpmiConsole(conapi.Console):
self.bmc = connparams['bmc']
self.port = connparams['port']
self.connected = False
# ok, is self.bmc unique among nodes already
# Cannot actually create console until 'connect', when we get callback
if (self.bmc in self.bmctonodemapping and
self.bmctonodemapping[self.bmc] != node):
raise Exception(
"Duplicate hardwaremanagement.manager attribute for {0} and {1}".format(
node, self.bmctonodemapping[self.bmc]))
self.bmctonodemapping[self.bmc] = node
def __del__(self):
self.solconnection = None
try:
del self.bmctonodemapping[self.bmc]
except KeyError:
pass
def handle_data(self, data):
if type(data) == dict:
@@ -601,13 +584,9 @@ class IpmiHandler(object):
self.output.put(msg.CreatedResource(
'nodes/{0}/media/uploads/{1}'.format(self.node, u.name)))
def get_diags(self, savefile, progress):
return self.ipmicmd.get_diagnostic_data(
savefile, progress=progress, autosuffix=True)
def handle_servicedata_fetch(self):
u = firmwaremanager.Updater(
self.node, self.get_diags,
self.node, self.ipmicmd.get_diagnostic_data,
self.inputdata.nodefile(self.node), self.tenant, type='ffdc',
owner=self.current_user)
self.output.put(msg.CreatedResource(
@@ -679,7 +658,7 @@ class IpmiHandler(object):
if len(self.element) == 4:
# A list of destinations
maxdest = self.ipmicmd.get_alert_destination_count()
for alertidx in range(0, maxdest + 1):
for alertidx in xrange(0, maxdest + 1):
self.output.put(msg.ChildCollection(alertidx))
return
elif len(self.element) == 5:
@@ -175,15 +175,12 @@ class IpmiCommandWrapper(ipmicommand.Command):
raise exc.TargetEndpointUnreachable(se.strerror)
else:
raise exc.TargetEndpointUnreachable(str(se))
if isinstance(se, socket.timeout):
if isinstance(se, socket.timeout) or (len(se) > 1 and se[1] == 'EHOSTUNREACH'):
raise exc.TargetEndpointUnreachable('timeout')
raise
except pygexc.PyghmiException as pe:
if 'Access Denied' in str(pe):
raise exc.TargetEndpointBadCredentials()
if 'Redfish not ready' in str(pe):
raise exc.TargetEndpointUnreachable('Redfish not yet ready')
raise
def close_confluent(self):
if self._attribwatcher:
@@ -465,13 +462,9 @@ class IpmiHandler(object):
self.output.put(msg.CreatedResource(
'nodes/{0}/media/uploads/{1}'.format(self.node, u.name)))
def get_diags(self, savefile, progress):
return self.ipmicmd.get_diagnostic_data(
savefile, progress=progress, autosuffix=True)
def handle_servicedata_fetch(self):
u = firmwaremanager.Updater(
self.node, self.get_diags,
self.node, self.ipmicmd.get_diagnostic_data,
self.inputdata.nodefile(self.node), self.tenant, type='ffdc',
owner=self.current_user)
self.output.put(msg.CreatedResource(
+3 -6
View File
@@ -13,9 +13,9 @@ BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot
Prefix: %{_prefix}
BuildArch: noarch
%if "%{dist}" == ".el8"
Requires: python3-pyghmi >= 1.0.34, python3-eventlet, python3-greenlet, python3-pycryptodomex >= 3.4.7, confluent_client, python3-pyparsing, python3-paramiko, python3-dns, python3-netifaces, python3-pyasn1 >= 0.2.3, python3-pysnmp >= 4.3.4, python3-pyte, python3-lxml, python3-eficompressor, python3-setuptools, python3-dateutil, python3-enum34, python3-asn1crypto, python3-cffi, python3-pyOpenSSL, python3-monotonic, python3-websocket-client
Requires: python3-pyghmi >= 1.0.34, python3-eventlet, python3-greenlet, python3-pycryptodomex >= 3.4.7, confluent_client, python3-pyparsing, python3-paramiko, python3-dns, python3-netifaces, python3-pyasn1 >= 0.2.3, python3-pysnmp >= 4.3.4, python3-pyte, python3-lxml, python3-eficompressor, python3-setuptools, python3-dateutil, python3-enum34, python3-asn1crypto, python3-cffi, python3-pyOpenSSL, python3-monotonic
%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, python2-websocket-client
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/
@@ -42,10 +42,7 @@ python2 setup.py install --single-version-externally-managed -O1 --root=$RPM_BUI
for file in $(grep confluent/__init__.py INSTALLED_FILES.bare); do
rm $RPM_BUILD_ROOT/$file
done
grep -v confluent/__init__.py INSTALLED_FILES.bare | grep -v etc/init.d/confluent > INSTALLED_FILES
rm $RPM_BUILD_ROOT/etc/init.d/confluent
rmdir $RPM_BUILD_ROOT/etc/init.d
rmdir $RPM_BUILD_ROOT/etc
grep -v confluent/__init__.py INSTALLED_FILES.bare > INSTALLED_FILES
cat INSTALLED_FILES
%triggerin -- python-pyghmi
-1
View File
@@ -14,7 +14,6 @@ setup(
'confluent/discovery/handlers/',
'confluent/networking/',
'confluent/plugins/hardwaremanagement/',
'confluent/plugins/console/',
'confluent/plugins/shell/',
'confluent/collective/',
'confluent/plugins/configuration/'],
+85 -44
View File
@@ -2,84 +2,125 @@
#include <arpa/inet.h>
#include <crypt.h>
#include <net/if.h>
#include <netdb.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/time.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;
}
static const char cryptalpha[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./";
unsigned char* genpasswd() {
unsigned char* genpasswd(int len) {
unsigned char * passwd;
int urandom;
passwd = calloc(33, sizeof(char));
passwd = calloc(len + 1, sizeof(char));
urandom = open("/dev/urandom", O_RDONLY);
read(urandom, passwd, 32);
read(urandom, passwd, len);
close(urandom);
for (urandom = 0; urandom < 32; urandom++) {
passwd[urandom] = 0x30 + (passwd[urandom] >> 2);
for (urandom = 0; urandom < len; urandom++) {
passwd[urandom] = cryptalpha[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 currlen, currtype;
unsigned char* passwd;
unsigned char* cryptedpass;
unsigned char* macaddr;
struct timeval timeout;
struct addrinfo hints;
struct addrinfo *addrs;
struct addrinfo *curr;
struct sockaddr_in net4bind;
struct sockaddr_in net6bind;
unsigned char buffer[MAXPACKET];
memset(&hints, 0, sizeof(struct addrinfo));
memset(&net4bind, 0, sizeof(struct sockaddr_in));
memset(&net6bind, 0, sizeof(struct sockaddr_in));
memset(&buffer, 0, MAXPACKET);
memset(&timeout, 0, sizeof(struct timeval));
timeout.tv_sec = 10;
net4bind.sin_port = htons(302);
net6bind.sin_port = htons(302);
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
passwd = genpasswd();
passwd = genpasswd(32);
memset(buffer, 0, MAXPACKET);
strncpy(buffer, "$5$", 3);
cryptedpass = genpasswd(8);
strncpy(buffer + 3, cryptedpass, 8);
free(cryptedpass);
cryptedpass = crypt(passwd, buffer);
if (argc < 3) {
fprintf(stderr, "Missing interface name and target MAC\n");
fprintf(stderr, "Missing node name and manager\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");
sock = getaddrinfo(argv[2], "301", &hints, &addrs);
if (sock != 0) {
fprintf(stderr, "Error trying to resolve %s\n", argv[2]);
exit(1);
}
iface = get_interface_index(sock, argv[1]);
if (iface < 0) {
fprintf(stderr, "Unable to find specified interface '%s'\n", argv[1]);
for (curr = addrs; curr != NULL; curr = curr->ai_next) {
sock = socket(curr->ai_family, curr->ai_socktype, curr->ai_protocol);
if (sock < 0) continue;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int));
if (curr->ai_family == AF_INET) {
bind(sock, (struct sockaddr*)&net4bind, sizeof(struct sockaddr_in));
} else if (curr->ai_family == AF_INET6) {
bind(sock, (struct sockaddr*)&net6bind, sizeof(struct sockaddr_in6));
} else {
continue;
}
if (connect(sock, curr->ai_addr, curr->ai_addrlen) == 0) break;
}
if (curr == NULL) {
fprintf(stderr, "Unable to reach %s\n", argv[2]);
exit(1);
}
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
freeaddrinfo(addrs);
read(sock, buffer, 8);
if (memcmp(buffer, "\xc2\xd1-\xa8\x80\xd8j\xba", 8) != 0) {
fprintf(stderr, "Unrecognized server\n");
exit(1);
}
dprintf(sock, "\x01%c%s", strlen(argv[1]), argv[1]);
write(sock, "\x00\x00", 2);
memset(buffer, 0, MAXPACKET);
read(sock, buffer, 2);
while (buffer[0] != 255) {
currtype = buffer[0];
currlen = buffer[1];
memset(buffer, 0, MAXPACKET);
if (currlen) {
read(sock, buffer, currlen); // Max is 255, well under MAX_PACKET
}
if (currtype == 2) {
dprintf(sock, "\x03%c", currlen);
write(sock, buffer, currlen);
dprintf(sock, "\x04%c%s", strlen(cryptedpass), cryptedpass);
write(sock, "\x00\x00", 2);
} else if (currtype == 5) {
printf(passwd);
printf("\n");
exit(0);
}
buffer[0] = 255;
read(sock, buffer, 2);
}
fprintf(stderr, "Password was not accepted\n");
exit(1);
}
+46 -6
View File
@@ -68,7 +68,11 @@ int main(int argc, char* argv[]) {
struct sockaddr_in6 addr, dst;
struct sockaddr_in addr4, dst4;
char msg[1024];
char *nodenameidx;
char nodename[1024];
char lastnodename[1024];
char lastmsg[1024];
char last6msg[1024];
int ifidx, offset;
fd_set rfds;
struct timeval tv;
@@ -80,6 +84,10 @@ int main(int argc, char* argv[]) {
memset(&addr, 0, sizeof(addr));
memset(&dst, 0, sizeof(dst));
memset(&dst4, 0, sizeof(dst4));
memset(nodename, 0, 1024);
memset(lastnodename, 0, 1024);
memset(lastmsg, 0, 1024);
memset(last6msg, 0, 1024);
addr.sin6_family = AF_INET6;
addr.sin6_addr = in6addr_any;
addr.sin6_port = htons(190);
@@ -141,20 +149,52 @@ int main(int argc, char* argv[]) {
if (ifidx == -1) perror("Unable to select");
if (ifidx) {
if (FD_ISSET(n4, &rfds)) {
recvfrom(n4, msg, 1024, 0, (struct sockaddr *)&dst4, &dst4size);
memset(msg, 0, 1024);
/* Deny packet access to the last 24 bytes to assure null */
recvfrom(n4, msg, 1000, 0, (struct sockaddr *)&dst4, &dst4size);
if (nodenameidx = strstr(msg, "NODENAME: ")) {
nodenameidx += 10;
strncpy(nodename, nodenameidx, 1024);
nodenameidx = strstr(nodenameidx, "\r");
if (nodenameidx) { nodenameidx[0] = 0; }
if (strncmp(lastnodename, nodename, 1024) != 0) {
printf("NODENAME: %s\n", nodename);
strncpy(lastnodename, nodename, 1024);
}
}
memset(msg, 0, 1024);
inet_ntop(dst4.sin_family, &dst4.sin_addr, msg, dst4size);
/* Take measure from printing out the same ip twice in a row */
if (strncmp(lastmsg, msg, 1024) != 0) {
printf("%s\n", msg);
sendto(n4, "PING", 4, 0, (const struct sockaddr *)&dst4, dst4size);
printf("MANAGER: %s\n", msg);
strncpy(lastmsg, msg, 1024);
}
}
if (FD_ISSET(ns, &rfds)) {
recvfrom(ns, msg, 1024, 0, (struct sockaddr *)&dst, &dstsize);
memset(msg, 0, 1024);
/* Deny packet access to the last 24 bytes to assure null */
recvfrom(ns, msg, 1000, 0, (struct sockaddr *)&dst, &dstsize);
if (nodenameidx = strstr(msg, "NODENAME: ")) {
nodenameidx += 10;
strncpy(nodename, nodenameidx, 1024);
nodenameidx = strstr(nodenameidx, "\r");
if (nodenameidx) { nodenameidx[0] = 0; }
if (strncmp(lastnodename, nodename, 1024) != 0) {
printf("NODENAME: %s\n", nodename);
strncpy(lastnodename, nodename, 1024);
}
}
memset(msg, 0, 1024);
inet_ntop(dst.sin6_family, &dst.sin6_addr, msg, dstsize);
if (strncmp(lastmsg, msg, 1024) != 0) {
printf("%s\n", msg);
strncpy(lastmsg, msg, 1024);
if (strncmp(last6msg, msg, 1024) != 0) {
sendto(ns, "PING", 4, 0, (const struct sockaddr *)&dst, dstsize);
printf("MANAGER: %s", msg);
if (strncmp(msg, "fe80::", 6) == 0) {
printf("%%%u", dst.sin6_scope_id);
}
printf("\n");
strncpy(last6msg, msg, 1024);
}
}
}