#!/usr/bin/python3
# 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 <start_node> -i <interface> <end_node> -e <eface>",
)
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):
        try:
            switch = neighbor['item']['href'].strip('/')
        except:
            continue
        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. 


    



