From 8ea532053e4c125222b5bc768c79ca6b429255eb Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Fri, 1 Jun 2018 15:53:52 -0400 Subject: [PATCH 1/9] Add guardrail around slp snoop If a program error should befall our poor slp service, log the issue and carry on. --- .../confluent/discovery/protocols/slp.py | 102 +++++++++--------- 1 file changed, 53 insertions(+), 49 deletions(-) diff --git a/confluent_server/confluent/discovery/protocols/slp.py b/confluent_server/confluent/discovery/protocols/slp.py index 53bbf4f2..b1086220 100644 --- a/confluent_server/confluent/discovery/protocols/slp.py +++ b/confluent_server/confluent/discovery/protocols/slp.py @@ -14,6 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import confluent.log as log import confluent.neighutil as neighutil import confluent.util as util import os @@ -21,8 +22,6 @@ import random import eventlet.green.select as select import eventlet.green.socket as socket import struct -import subprocess - _slp_services = set([ 'service:management-hardware.IBM:integrated-management-module2', @@ -391,6 +390,7 @@ def snoop(handler): :param handler: :return: """ + tracelog = log.Logger('trace') active_scan(handler) net = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) net.setsockopt(IPPROTO_IPV6, socket.IPV6_V6ONLY, 1) @@ -420,54 +420,58 @@ def snoop(handler): net4.bind(('', 427)) while True: - newmacs = set([]) - r, _, _ = select.select((net, net4), (), (), 60) - # clear known_peers and peerbymacaddress - # to avoid stale info getting in... - # rely upon the select(0.2) to catch rapid fire and aggregate ip - # addresses that come close together - # calling code needs to understand deeper context, as snoop - # will now yield dupe info over time - known_peers = set([]) - peerbymacaddress = {} - neighutil.update_neigh() - while r: - for s in r: - (rsp, peer) = s.recvfrom(9000) - ip = peer[0].partition('%')[0] - if ip not in neighutil.neightable: - continue - if peer in known_peers: - continue - known_peers.add(peer) - mac = neighutil.neightable[ip] - if mac in peerbymacaddress: - peerbymacaddress[mac]['addresses'].append(peer) - else: - q = query_srvtypes(peer) - if not q or not q[0]: - # SLP might have started and not ready yet - # ignore for now - known_peers.discard(peer) + try: + newmacs = set([]) + r, _, _ = select.select((net, net4), (), (), 60) + # clear known_peers and peerbymacaddress + # to avoid stale info getting in... + # rely upon the select(0.2) to catch rapid fire and aggregate ip + # addresses that come close together + # calling code needs to understand deeper context, as snoop + # will now yield dupe info over time + known_peers = set([]) + peerbymacaddress = {} + neighutil.update_neigh() + while r: + for s in r: + (rsp, peer) = s.recvfrom(9000) + ip = peer[0].partition('%')[0] + if ip not in neighutil.neightable: continue - # we want to prioritize the very well known services - svcs = [] - for svc in q: - if svc in _slp_services: - svcs.insert(0, svc) - else: - svcs.append(svc) - peerbymacaddress[mac] = { - 'services': svcs, - 'addresses': [peer], - } - newmacs.add(mac) - r, _, _ = select.select((net, net4), (), (), 0.2) - for mac in newmacs: - peerbymacaddress[mac]['xid'] = 1 - _add_attributes(peerbymacaddress[mac]) - peerbymacaddress[mac]['hwaddr'] = mac - handler(peerbymacaddress[mac]) + if peer in known_peers: + continue + known_peers.add(peer) + mac = neighutil.neightable[ip] + if mac in peerbymacaddress: + peerbymacaddress[mac]['addresses'].append(peer) + else: + q = query_srvtypes(peer) + if not q or not q[0]: + # SLP might have started and not ready yet + # ignore for now + known_peers.discard(peer) + continue + # we want to prioritize the very well known services + svcs = [] + for svc in q: + if svc in _slp_services: + svcs.insert(0, svc) + else: + svcs.append(svc) + peerbymacaddress[mac] = { + 'services': svcs, + 'addresses': [peer], + } + newmacs.add(mac) + r, _, _ = select.select((net, net4), (), (), 0.2) + for mac in newmacs: + peerbymacaddress[mac]['xid'] = 1 + _add_attributes(peerbymacaddress[mac]) + peerbymacaddress[mac]['hwaddr'] = mac + handler(peerbymacaddress[mac]) + except Exception as e: + tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event, + event=log.Events.stacktrace) def active_scan(handler): From 8fc29d1b467a286d154e463ae5e00037dbfd1e54 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Fri, 1 Jun 2018 16:09:13 -0400 Subject: [PATCH 2/9] Correctly allow manual discovery through ambiguous situation Manual discovery may 'catch' some incidental data from auto-discovery. Since the operation is manual, trust the user rather than assume the user is confused. --- confluent_server/confluent/discovery/core.py | 37 ++++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/confluent_server/confluent/discovery/core.py b/confluent_server/confluent/discovery/core.py index 645d533c..4773a703 100644 --- a/confluent_server/confluent/discovery/core.py +++ b/confluent_server/confluent/discovery/core.py @@ -953,26 +953,25 @@ def eval_node(cfg, handler, info, nodename, manual=False): # but... is this really ok? could be on an upstream port or # erroneously put in the enclosure with no nodes yet # so first, see if the candidate node is a chain host - if info.get('maccount', False): - # discovery happened through switch - nl = list(cfg.filter_node_attributes( - 'enclosure.extends=' + nodename)) - if nl: - # The candidate nodename is the head of a chain, we must - # validate the smm certificate by the switch - macmap.get_node_fingerprint(nodename, cfg) - util.handler.cert_matches(fprint, handler.https_cert) + if not manual: + if info.get('maccount', False): + # discovery happened through switch + nl = list(cfg.filter_node_attributes( + 'enclosure.extends=' + nodename)) + if nl: + # The candidate nodename is the head of a chain, we must + # validate the smm certificate by the switch + macmap.get_node_fingerprint(nodename, cfg) + util.handler.cert_matches(fprint, handler.https_cert) + return + if (info.get('maccount', False) and + not handler.discoverable_by_switch(info['maccount'])): + errorstr = 'The detected node {0} was detected using switch, ' \ + 'however the relevant port has too many macs learned ' \ + 'for this type of device ({1}) to be discovered by ' \ + 'switch.'.format(nodename, handler.devname) + log.log({'error': errorstr}) return - if (info.get('maccount', False) and - not handler.discoverable_by_switch(info['maccount'])): - errorstr = 'The detected node {0} was detected using switch, ' \ - 'however the relevant port has too many macs learned ' \ - 'for this type of device ({1}) to be discovered by ' \ - 'switch.'.format(nodename, handler.devname) - if manual: - raise exc.InvalidArgumentException(errorstr) - log.log({'error': errorstr}) - return if not discover_node(cfg, handler, info, nodename, manual): pending_nodes[nodename] = info From e8cea66a8591ac7bda868caf8fb263a8b0ba83d6 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Fri, 1 Jun 2018 16:25:38 -0400 Subject: [PATCH 3/9] Add timeout on httpapi socket Clients that fail to send any data, or keep a persistent socket open without using it are killed off. --- confluent_server/confluent/httpapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/confluent_server/confluent/httpapi.py b/confluent_server/confluent/httpapi.py index 81fb878d..2a4d2fe6 100644 --- a/confluent_server/confluent/httpapi.py +++ b/confluent_server/confluent/httpapi.py @@ -785,7 +785,7 @@ def serve(bind_host, bind_port): ' a second\n') eventlet.sleep(1) eventlet.wsgi.server(sock, resourcehandler, log=False, log_output=False, - debug=False) + debug=False, socket_timeout=60) class HttpApi(object): From a7a4ede580114ae02a8497886e225127319371fc Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Fri, 1 Jun 2018 16:48:19 -0400 Subject: [PATCH 4/9] Fix confusing nodeconfig error handling Properly react to error conditions --- confluent_client/bin/nodeconfig | 10 ++++++---- confluent_client/confluent/client.py | 6 ++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/confluent_client/bin/nodeconfig b/confluent_client/bin/nodeconfig index 76a0fd72..07f6431f 100755 --- a/confluent_client/bin/nodeconfig +++ b/confluent_client/bin/nodeconfig @@ -202,12 +202,14 @@ else: for path in queryparms: if options.comparedefault: continue - client.print_attrib_path(path, session, list(queryparms[path]), - NullOpt(), queryparms[path]) + rc = client.print_attrib_path(path, session, list(queryparms[path]), + NullOpt(), queryparms[path]) + if rc: + sys.exit(rc) if printsys or options.exclude: if printsys == 'all': printsys = [] path = '/noderange/{0}/configuration/system/all'.format(noderange) - client.print_attrib_path(path, session, printsys, - options) + rcode = client.print_attrib_path(path, session, printsys, + options) sys.exit(rcode) \ No newline at end of file diff --git a/confluent_client/confluent/client.py b/confluent_client/confluent/client.py index c7d4b569..6b3ae129 100644 --- a/confluent_client/confluent/client.py +++ b/confluent_client/confluent/client.py @@ -338,6 +338,12 @@ def print_attrib_path(path, session, requestargs, options, rename=None): for attr, val in sorted( res['databynode'][node].items(), key=lambda (k, v): v.get('sortid', k) if isinstance(v, dict) else k): + if attr == 'error': + sys.stderr.write('{0}: Error: {1}\n'.format(node, val)) + continue + if attr == 'errorcode': + exitcode |= val + continue seenattributes.add(attr) if rename: printattr = rename.get(attr, attr) From cdb20c0302a3e108ce742007a5657cc73cc35fd3 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Tue, 5 Jun 2018 13:05:42 -0400 Subject: [PATCH 5/9] Fix missing import of traceback --- confluent_server/confluent/discovery/protocols/slp.py | 1 + 1 file changed, 1 insertion(+) diff --git a/confluent_server/confluent/discovery/protocols/slp.py b/confluent_server/confluent/discovery/protocols/slp.py index b1086220..7f13f990 100644 --- a/confluent_server/confluent/discovery/protocols/slp.py +++ b/confluent_server/confluent/discovery/protocols/slp.py @@ -22,6 +22,7 @@ import random import eventlet.green.select as select import eventlet.green.socket as socket import struct +import traceback _slp_services = set([ 'service:management-hardware.IBM:integrated-management-module2', From 5c12dc2cba952395c0480d8b6a984a760e125402 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Fri, 8 Jun 2018 10:15:38 -0400 Subject: [PATCH 6/9] Do not require exactly TLSv1.0 This was breaking TLSv1.2. --- confluent_client/confluent/client.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/confluent_client/confluent/client.py b/confluent_client/confluent/client.py index 6b3ae129..2cc92996 100644 --- a/confluent_client/confluent/client.py +++ b/confluent_client/confluent/client.py @@ -261,8 +261,7 @@ class Command(object): certreqs = ssl.CERT_NONE knownhosts = True self.connection = ssl.wrap_socket(self.connection, ca_certs=cacert, - cert_reqs=certreqs, - ssl_version=ssl.PROTOCOL_TLSv1) + cert_reqs=certreqs) if knownhosts: certdata = self.connection.getpeercert(binary_form=True) fingerprint = 'sha512$' + hashlib.sha512(certdata).hexdigest() From 4906d6e9c43d69ddeb1ecb3efeff040815f4dc0e Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Fri, 11 May 2018 13:49:54 -0400 Subject: [PATCH 7/9] Add --json to nodeinventory Have nodeinventory have an option to output in json. --- confluent_client/bin/nodeinventory | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/confluent_client/bin/nodeinventory b/confluent_client/bin/nodeinventory index 1cc99e72..d7a1acb7 100755 --- a/confluent_client/bin/nodeinventory +++ b/confluent_client/bin/nodeinventory @@ -16,6 +16,7 @@ # limitations under the License. import codecs +import json import optparse import os import re @@ -89,9 +90,10 @@ url = '/noderange/{0}/inventory/hardware/all/all' argparser = optparse.OptionParser( usage="Usage: %prog [serial|model|uuid|mac]") +argparser.add_option('-j', '--json', action='store_true', help='Output JSON') (options, args) = argparser.parse_args() try: - noderange = sys.argv[1] + noderange = args[0] except IndexError: argparser.print_help() sys.exit(1) @@ -114,6 +116,8 @@ if len(args) > 1: filters.append(re.compile('mac address')) url = '/noderange/{0}/inventory/hardware/all/all' try: + if options.json: + databynode = {} session = client.Command() for res in session.read(url.format(noderange)): printerror(res) @@ -121,6 +125,10 @@ try: continue for node in res['databynode']: printerror(res['databynode'][node], node) + if options.json: + databynode[node] = dict(databynode.get(node, {}), + **res['databynode'][node]) + continue if 'inventory' not in res['databynode'][node]: continue for inv in res['databynode'][node]['inventory']: @@ -150,6 +158,9 @@ try: print(u'{0}: {1} {2}: {3}'.format(node, prefix, pretty(datum), info[datum])) + if options.json: + print(json.dumps(databynode, sort_keys=True, indent=4, + separators=(',', ': '))) except KeyboardInterrupt: print('') -sys.exit(exitcode) \ No newline at end of file +sys.exit(exitcode) From efd5732682144c38d90e1c184114f5de801eb999 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Fri, 11 May 2018 14:18:04 -0400 Subject: [PATCH 8/9] Amend json output Have the nodeinventory json output in a bit more directly useful format, rather than regarding the API structured JSON... --- confluent_client/bin/nodeinventory | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/confluent_client/bin/nodeinventory b/confluent_client/bin/nodeinventory index d7a1acb7..9cef8549 100755 --- a/confluent_client/bin/nodeinventory +++ b/confluent_client/bin/nodeinventory @@ -125,16 +125,12 @@ try: continue for node in res['databynode']: printerror(res['databynode'][node], node) - if options.json: - databynode[node] = dict(databynode.get(node, {}), - **res['databynode'][node]) - continue if 'inventory' not in res['databynode'][node]: continue for inv in res['databynode'][node]['inventory']: prefix = inv['name'] if not inv['present']: - if not filters: + if not filters and not options.json: print '{0}: {1}: Not Present'.format(node, prefix) continue info = inv['information'] @@ -144,6 +140,11 @@ try: info.pop('product_extra', None) if 'memory_type' in info: if not filters: + if options.json: + if node not in databynode: + databynode[node] = {} + databynode[node][prefix] = inv + continue print_mem_info(node, prefix, info) continue for datum in info: @@ -155,6 +156,11 @@ try: continue if info[datum] is None: continue + if options.json: + if node not in databynode: + databynode[node] = {} + databynode[node][prefix] = inv + break print(u'{0}: {1} {2}: {3}'.format(node, prefix, pretty(datum), info[datum])) From b877a95645de1949d0be8f50efff6f0001ee30d7 Mon Sep 17 00:00:00 2001 From: Jarrod Johnson Date: Fri, 11 May 2018 14:24:47 -0400 Subject: [PATCH 9/9] Include absent devices in the json of nodeinventory --- confluent_client/bin/nodeinventory | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/confluent_client/bin/nodeinventory b/confluent_client/bin/nodeinventory index 9cef8549..9c8a6f2b 100755 --- a/confluent_client/bin/nodeinventory +++ b/confluent_client/bin/nodeinventory @@ -130,8 +130,13 @@ try: for inv in res['databynode'][node]['inventory']: prefix = inv['name'] if not inv['present']: - if not filters and not options.json: - print '{0}: {1}: Not Present'.format(node, prefix) + if not filters: + if options.json: + if node not in databynode: + databynode[node] = {} + databynode[node][prefix] = inv + else: + print '{0}: {1}: Not Present'.format(node, prefix) continue info = inv['information'] info.pop('board_extra', None)