From 838c0920eb8e159a293e4ba3983d5a85c9fa042e Mon Sep 17 00:00:00 2001 From: tkucherera Date: Fri, 22 Mar 2024 11:37:12 -0400 Subject: [PATCH 1/3] l2traceroute --- confluent_client/doc/man/l2traceroute.ronn | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 confluent_client/doc/man/l2traceroute.ronn diff --git a/confluent_client/doc/man/l2traceroute.ronn b/confluent_client/doc/man/l2traceroute.ronn new file mode 100644 index 00000000..7e660094 --- /dev/null +++ b/confluent_client/doc/man/l2traceroute.ronn @@ -0,0 +1,34 @@ +l2traceroute(8) -- returns the layer 2 route through an Ethernet network managed by confluent given 2 end points. +============================== +## SYNOPSIS +`l2traceroute [options] ` + +## DESCRIPTION +**l2traceroute** is a command that returns the layer 2 route for the configered interfaces in nodeattrib. +It can also be used with the -i and -e options to check against specific interfaces on the endpoints. + +Note the net..switch attributes have to be set on the end points + + +## OPTIONS +* ` -e` EFACE, --eface=INTERFACE + interface to check against for the second end point +* ` -i` INTERFACE, --interface=INTERFACE + interface to check against for the first end point +* `-h`, `--help`: + Show help message and exit + + +## EXAMPLES + * Checking route between two nodes: + `# l2traceroute_client n244 n1851` + `n244 to n1851: ['switch114']` + +* Checking route from one node to multiple nodes: + `# l2traceroute_client n244 n1833,n1851` + `n244 to n1833: ['switch114', 'switch7', 'switch32', 'switch253', 'switch85', 'switch72', 'switch21', 'switch2', 'switch96', 'switch103', 'switch115'] + n244 to n1851: ['switch114']` + + + + From 466ed7496123c4808c767a272af37fd5a8814ac6 Mon Sep 17 00:00:00 2001 From: tkucherera Date: Fri, 22 Mar 2024 11:37:51 -0400 Subject: [PATCH 2/3] l2traceroute --- confluent_client/bin/l2traceroute | 154 ++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100755 confluent_client/bin/l2traceroute diff --git a/confluent_client/bin/l2traceroute b/confluent_client/bin/l2traceroute new file mode 100755 index 00000000..7b9ad4ac --- /dev/null +++ b/confluent_client/bin/l2traceroute @@ -0,0 +1,154 @@ +#!/usr/libexec/platform-python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2017 Lenovo +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__author__ = 'tkucherera' + +import optparse +import os +import signal +import sys +import subprocess + +try: + signal.signal(signal.SIGPIPE, signal.SIG_DFL) +except AttributeError: + pass +path = os.path.dirname(os.path.realpath(__file__)) +path = os.path.realpath(os.path.join(path, '..', 'lib', 'python')) +if path.startswith('/opt'): + sys.path.append(path) + +import confluent.client as client + +argparser = optparse.OptionParser( + usage="Usage: %prog -i -e ", +) +argparser.add_option('-i', '--interface', type='str', + help='interface to check path against for the start node') +argparser.add_option('-e', '--eface', type='str', + help='interface to check path against for the end node') + +(options, args) = argparser.parse_args() + + +session = client.Command() + +def get_neighbors(switch): + switch_neigbors = [] + url = 'networking/neighbors/by-switch/{0}/by-peername/'.format(switch) + for neighbor in session.read(url): + if neighbor['item']['href'].startswith('switch'): + switch = neighbor['item']['href'].strip('/') + switch_neigbors.append(switch) + return switch_neigbors + + + +def find_path(start, end, path=[]): + path = path + [start] + if start == end: + return path # If start and end are the same, return the path + + for node in get_neighbors(start): + if node not in path and node.startswith('switch'): + new_path = find_path(node, end, path) + if new_path: + return new_path # If a path is found, return it + + return None # If no path is found, return None + + + +def is_cumulus(switch): + try: + read_attrib = subprocess.check_output(['nodeattrib', switch, 'hardwaremanagement.method']) + except subprocess.CalledProcessError: + return False + for attribs in read_attrib.decode('utf-8').split('\n'): + if len(attribs.split(':')) > 1: + attrib = attribs.split(':') + if attrib[2].strip() == 'affluent': + return True + else: + return False + else: + return False + + +def host_to_switch(node, interface=None): + # first check the the node config to see what switches are connected + # if host is in rhel can use nmstate package + cummulus_switches = [] + netarg = 'net.*.switch' + if interface: + netarg = 'net.{0}.switch'.format(interface) + read_attrib = subprocess.check_output(['nodeattrib', node, netarg]) + for attribs in read_attrib.decode('utf-8').split('\n'): + attrib = attribs.split(':') + try: + if ' net.mgt.switch' in attrib or attrib[2] == '': + continue + except IndexError: + continue + switch = attrib[2].strip() + if is_cumulus(switch): + cummulus_switches.append(switch) + return cummulus_switches + +try: + start_node = args[0] + end_node = args[1] + interface = options.interface + eface = options.eface +except IndexError: + argparser.print_help() + sys.exit(1) + +def path_between_nodes(start_switches, end_switches): + for start_switch in start_switches: + for end_switch in end_switches: + if start_switch == end_switch: + return [start_switch] + else: + path = find_path(start_switch, end_switch) + if path: + return path + else: + return 'No path found' + +end_nodeslist = [] +nodelist = '/noderange/{0}/nodes/'.format(end_node) +for res in session.read(nodelist): + if 'error' in res: + sys.stderr.write(res['error'] + '\n') + exitcode = 1 + else: + elem=(res['item']['href'].replace('/', '')) + end_nodeslist.append(elem) + +start_switches = host_to_switch(start_node, interface) +for end_node in end_nodeslist: + if end_node: + end_switches = host_to_switch(end_node, eface) + path = path_between_nodes(start_switches, end_switches) + print(f'{start_node} to {end_node}: {path}') + + + + + + From 296a0e88b4962f5e93e43f954414fb3c5532b6d8 Mon Sep 17 00:00:00 2001 From: tkucherera Date: Sun, 24 Mar 2024 11:41:23 -0400 Subject: [PATCH 3/3] making the use cases more generic --- confluent_client/bin/l2traceroute | 65 ++++++++++++++-------- confluent_client/doc/man/l2traceroute.ronn | 6 +- 2 files changed, 47 insertions(+), 24 deletions(-) diff --git a/confluent_client/bin/l2traceroute b/confluent_client/bin/l2traceroute index 7b9ad4ac..e8f9705e 100755 --- a/confluent_client/bin/l2traceroute +++ b/confluent_client/bin/l2traceroute @@ -41,38 +41,44 @@ argparser.add_option('-i', '--interface', type='str', help='interface to check path against for the start node') argparser.add_option('-e', '--eface', type='str', help='interface to check path against for the end node') +argparser.add_option('-c', '--cumulus', action="store_true", dest="cumulus", + help='return layer 2 route through cumulus switches only') (options, args) = argparser.parse_args() +try: + start_node = args[0] + end_node = args[1] + interface = options.interface + eface = options.eface +except IndexError: + argparser.print_help() + sys.exit(1) session = client.Command() def get_neighbors(switch): switch_neigbors = [] - url = 'networking/neighbors/by-switch/{0}/by-peername/'.format(switch) + url = '/networking/neighbors/by-switch/{0}/by-peername/'.format(switch) for neighbor in session.read(url): - if neighbor['item']['href'].startswith('switch'): - switch = neighbor['item']['href'].strip('/') + switch = neighbor['item']['href'].strip('/') + if switch in all_switches: switch_neigbors.append(switch) return switch_neigbors - - def find_path(start, end, path=[]): path = path + [start] if start == end: return path # If start and end are the same, return the path for node in get_neighbors(start): - if node not in path and node.startswith('switch'): + if node not in path: new_path = find_path(node, end, path) if new_path: return new_path # If a path is found, return it return None # If no path is found, return None - - def is_cumulus(switch): try: read_attrib = subprocess.check_output(['nodeattrib', switch, 'hardwaremanagement.method']) @@ -92,11 +98,16 @@ def is_cumulus(switch): def host_to_switch(node, interface=None): # first check the the node config to see what switches are connected # if host is in rhel can use nmstate package - cummulus_switches = [] + if node in all_switches: + return [node] + switches = [] netarg = 'net.*.switch' if interface: netarg = 'net.{0}.switch'.format(interface) - read_attrib = subprocess.check_output(['nodeattrib', node, netarg]) + try: + read_attrib = subprocess.check_output(['nodeattrib', node, netarg]) + except subprocess.CalledProcessError: + return False for attribs in read_attrib.decode('utf-8').split('\n'): attrib = attribs.split(':') try: @@ -105,18 +116,11 @@ def host_to_switch(node, interface=None): except IndexError: continue switch = attrib[2].strip() - if is_cumulus(switch): - cummulus_switches.append(switch) - return cummulus_switches - -try: - start_node = args[0] - end_node = args[1] - interface = options.interface - eface = options.eface -except IndexError: - argparser.print_help() - sys.exit(1) + if is_cumulus(switch) and options.cumulus: + switches.append(switch) + else: + switches.append(switch) + return switches def path_between_nodes(start_switches, end_switches): for start_switch in start_switches: @@ -129,7 +133,17 @@ def path_between_nodes(start_switches, end_switches): return path else: return 'No path found' - + + +all_switches = [] +for res in session.read('/networking/neighbors/by-switch/'): + if 'error' in res: + sys.stderr.write(res['error'] + '\n') + exitcode = 1 + else: + switch = (res['item']['href'].replace('/', '')) + all_switches.append(switch) + end_nodeslist = [] nodelist = '/noderange/{0}/nodes/'.format(end_node) for res in session.read(nodelist): @@ -144,9 +158,14 @@ start_switches = host_to_switch(start_node, interface) for end_node in end_nodeslist: if end_node: end_switches = host_to_switch(end_node, eface) + if not end_switches: + print('Error: net.{0}.switch attribute is not valid') + continue path = path_between_nodes(start_switches, end_switches) print(f'{start_node} to {end_node}: {path}') +# TODO dont put switches that are connected through management interfaces. + diff --git a/confluent_client/doc/man/l2traceroute.ronn b/confluent_client/doc/man/l2traceroute.ronn index 7e660094..16318567 100644 --- a/confluent_client/doc/man/l2traceroute.ronn +++ b/confluent_client/doc/man/l2traceroute.ronn @@ -7,7 +7,9 @@ l2traceroute(8) -- returns the layer 2 route through an Ethernet network managed **l2traceroute** is a command that returns the layer 2 route for the configered interfaces in nodeattrib. It can also be used with the -i and -e options to check against specific interfaces on the endpoints. -Note the net..switch attributes have to be set on the end points + +## PREREQUISITES +**l2traceroute** the net..switch attributes have to be set on the end points if endpoint is not a switch ## OPTIONS @@ -15,6 +17,8 @@ Note the net..switch attributes have to be set on the end points interface to check against for the second end point * ` -i` INTERFACE, --interface=INTERFACE interface to check against for the first end point +* ` -c` CUMULUS, --cumulus=CUMULUS + return layer 2 route through cumulus switches only * `-h`, `--help`: Show help message and exit