diff --git a/confluent_client/bin/l2traceroute b/confluent_client/bin/l2traceroute new file mode 100755 index 00000000..e8f9705e --- /dev/null +++ b/confluent_client/bin/l2traceroute @@ -0,0 +1,173 @@ +#!/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') +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) + for neighbor in session.read(url): + 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: + 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 + if node in all_switches: + return [node] + switches = [] + netarg = 'net.*.switch' + if interface: + netarg = 'net.{0}.switch'.format(interface) + 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: + if ' net.mgt.switch' in attrib or attrib[2] == '': + continue + except IndexError: + continue + switch = attrib[2].strip() + 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: + 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' + + +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): + 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) + 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 new file mode 100644 index 00000000..16318567 --- /dev/null +++ b/confluent_client/doc/man/l2traceroute.ronn @@ -0,0 +1,38 @@ +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. + + +## PREREQUISITES +**l2traceroute** the net..switch attributes have to be set on the end points if endpoint is not a switch + + +## 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 +* ` -c` CUMULUS, --cumulus=CUMULUS + return layer 2 route through cumulus switches only +* `-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']` + + + +