#!/bin/bash

#------------------------------------------------------------------------
# February, 2017, Balaji Krishnamoorthy
# Copyright (c) 2017 by Cisco Systems, Inc.
# All Rights Reserved
#------------------------------------------------------------------------

# NOTE : currently this script only takes care of NCS6K (1T) system.

# This script runs PRBS test on all links of a list of planes

let MAX_PLANES=6
analyze_opt="Failed"

IPADDRESS='\<\([0-9]\{1,3\}\.\)\{3\}[0-9]\{1,3\}\>'
MGMT_NODE_LOCATION='\<\([0-9]\/RP\|F[0-3]\/SC\)[01]\>'
PLATFORM_OUTPUT="/tmp/platform_output.dat"
S3RXLIST="/tmp/s3rxlist.dat"
S2RXLIST="/tmp/s2rxlist.dat"

#
# function to print usage help
#
usage () {
    echo ""
    echo "  USAGE : $0 -p <planes> [options]" 
    echo "          -a : [failed|ok|.] analyze. Uses existing data from"
    echo "               the most recent test without rerunning test."
    echo "               (cannot be combined with -srdu)"
    echo "          -p : comma separated list of planes for which the"
    echo "               PRBS test is to be run. (mandatory input)"
    echo "          -s : number of seconds to run the test. Recommended duration is 300sec (5 minutes)"
    echo "          -r : reload all the FCs of these planes (may be combined with -s, -d or -u)"
    echo "          -d : shut down planes and links to prepare for PRBS (combined with -s or -r)"
    echo "          -u : unshut links of given planes (can be combined with -r)"
    echo "          -v : for verbose output"
    echo ""

    exit 1;
}

#
# function to analyze the results and print in a (somewhat)
# readable format. 
#
analyze_results() {
    unset bad_links_found
    for file in /tmp/PRBS.*; do
        #echo "Analyzing $file... "
        asic=$(basename $file | cut -d'.' -f2-4 | tr '.' '/')
        grep '^F' <<< "$asic" >& /dev/null
        if [ $? -eq 0 ]; then
            # S2 RX link
            where=$S2RXLIST
        else
            # S3 RX link
            where=$S3RXLIST
        fi
        while read prbs_result; do
            grep -i "PRBS ${analyze_opt}" <<< $prbs_result >& /dev/null
            if [ $? -ne 0 ]; then
                continue;
            fi
            link_num=$(grep -o '( *[0-9]*)' <<< "$prbs_result" | grep -o '\<[0-9]*\>')
            #echo "${asic}/${link_num} has failed PRBS test."
            end_to_end_link=$(grep -h "^${asic}/${link_num} " $where)
            if [ -n "$end_to_end_link" ]; then
                bad_links_found="true"
                echo "Rx link $end_to_end_link : $prbs_result"
            fi
        done < $file
    done

    if [ -z "$bad_links_found" ]; then
        echo "No noisy optic links found in fabric planes ${planes}!"
    fi
}

#
# Cleanup all temporary files...
#
cleanup () {
    [ -e /tmp/plat ] && rm /tmp/plat
    [ -e /tmp/junk ] && rm /tmp/junk
    time=$(date)
    echo "${time} : Script completed..."
    exit 0
}

trap cleanup SIGHUP SIGTERM SIGINT

clear_asic_errors_for_plane() {

    for ((item=0; item< ${#rack_dev_list[@]}; item++ )) do
        rack=$(cut -d '/' -f1 <<< "${rack_dev_list[$item]}") 
        units=$(cut -d ':' -f2 <<< "${rack_dev_list[$item]}") 
        [ ! -e /tmp/sfe.driver.$rack ] && show_cmd "show controller sfe driver rack $rack | inc Rack | save /tmp/sfe.driver.$rack" >& /dev/null
        mgmt_node=$(grep -o $MGMT_NODE_LOCATION /tmp/sfe.driver.$rack)
        for unit in $(tr ',' ' ' <<< "$units"); do
            #echo "clear asic-err SFE $unit all location $mgmt_node counter"
            show_cmd "clear asic-err SFE $unit all location $mgmt_node counter" >& /dev/null
        done
    done
}

config_controller_fabric_link_port() {
    what_to_do=$1
    yes_or_no=$2

    [ -f $LINK_SHUT_CONFIG ] && rm -f $LINK_SHUT_CONFIG

    echo "conf t" > $LINK_SHUT_CONFIG   
    for plane in $(tr ',' ' ' <<< "$planes"); do
        for rsal in $(grep "^[0-9]/FC${plane}/" $S3RXLIST | awk 'BEGIN{OFS="\n"} {print $1, $NF}'); do
            if [ "$yes_or_no" == "no" ]; then
                echo -n "no " >> $LINK_SHUT_CONFIG 
            fi
            echo "controller fabric link port ${rsal} ${what_to_do}" >> $LINK_SHUT_CONFIG   
        done
    done

    echo "commit" >> $LINK_SHUT_CONFIG   

    /opt/cisco/calvados/confd/bin/confd_cli -u root -C -N --noaaa $LINK_SHUT_CONFIG
}

reload_fabric_cards_of_plane() {
    for loc in ${s2_list[*]} ${s13_list[*]}; do
        rack=$(sed -e 's/\/FC.*//' <<< "$loc")
        slot=$(sed -e 's/.*\/FC//' <<< "$loc")
        slot=$((slot+8))
        [ ! -e /tmp/sfe.driver.$rack ] && show_cmd "show controller sfe driver rack $rack | inc Rack | save /tmp/sfe.driver.$rack" >& /dev/null
        ipaddr=$(grep -o $IPADDRESS /tmp/sfe.driver.$rack)
        sleep 1
        echo "Reloading $loc ... "
        ssh -q ${ipaddr} "/opt/cisco/calvados/sbin/ccc_reset_board -s $slot" >& /tmp/junk
        [ -n "$verbose" ] && cat /tmp/junk
        rm /tmp/junk
    done

    # wait for the FCs to go operational
    echo -n "Waiting for the cards to go operational"
    cards_not_yet_up="true"
    retry_count=0
    while [ -n "$cards_not_yet_up" -a $retry_count -lt 12 ]; do
        retry_count=$((retry_count+1))
        sleep 10
        unset cards_not_yet_up
        show_cmd "show platform | save /tmp/plat"  >& /dev/null
        for loc in ${s2_list[*]} ${s13_list[*]}; do
            grep "^${loc} .*OPERATIONAL" /tmp/plat >& /dev/null
            if [ $? -ne 0 ]; then
                cards_not_yet_up="true"
                echo -n "."
                break;
            fi
        done
    done

    echo ""

    time=$(date)
    echo "${time} : Plane ${planes} reload completed..."
}

get_card_type() {
    loc=$1
    grep "^${loc} .*OPERATIONAL" $PLATFORM_OUTPUT | awk '{print $2}' | sed -e 's/  *//'
}

prbs_operation() {
    oper=$1
    if [ "$oper" == "set" ]; then
        opt="p=3"
    else
        opt=""
    fi

    for location in ${s2_list[*]} ${s13_list[*]}; do
        rack=$(awk -F'/' '{print $1}' <<< "${location}")
        ctype=$(get_card_type ${location})
        if [[ "$rack" =~ F ]]; then
            if [ "$ctype" == "NCS-F-FC2" ]; then
                link_desc="sfi0-sfi31,sfi36-sfi67,sfi76-sfi107,sfi112-sfi143"
                let dev_count=3
            elif [ "$ctype" == "NCS4KF-FC2-C" ]; then
                link_desc="sfi0-sfi143"
                let dev_count=2
            else
                link_desc="sfi0-sfi127"
                let dev_count=3
            fi
        else
            if [ "$ctype" == "NC6-FC2-U" ]; then
                link_desc="sfi72-sfi103,sfi108-sfi139"
                let dev_count=3
            elif [ "$ctype" == "NCS4016-FC2-M" ]; then
                link_desc="sfi72-sfi95,sfi108-sfi131"
                let dev_count=3
            else
                link_desc="sfi64-sfi71,sfi78-sfi81,sfi84-sfi103,sfi110-sfi113,sfi116-sfi127"
                let dev_count=4
            fi
        fi
        for ((inst=0; inst < ${dev_count}; inst++)) do
            if [ "$oper" == "get" ]; then
                filename=$(printf "/tmp/PRBS.%s.%d" $(tr '/' '.' <<< $location) $inst)
            else
                filename=$(printf "/tmp/%s.%s.%d" $oper $(tr '/' '.' <<< $location) $inst)
            fi
            show_cmd "show controller sfe diagshell ${inst} \"phy prbs ${link_desc} ${oper} ${opt}\" location $location | save $filename" >& /dev/null
        done
    done
}

#################################################################
#################################################################
##
## Start of the script
##
#################################################################
#################################################################

time=$(date)
echo "${time} : PRBS script invoked..."

while getopts "hp:s:vra:dug" o; do
    case "${o}" in
        p)
            planes=${OPTARG};
            ;; 
        s)
            seconds=${OPTARG};
            ;; 
        d)
            shut_down_plane="true";
            ;; 
        u)
            unshut_links="true";
            ;; 
        r)
            reload_plane="true";
            ;; 
        a)
            analyze_only="true";
            analyze_opt=${OPTARG};
            ;; 
        h)
            usage;
            ;; 
        v)
            verbose="true";
            ;; 
        *)
            echo "invalid option ${o}"
            usage;
            ;; 
    esac
done

if [ -n "$analyze_only" ]; then
    analyze_results
    exit 0;
fi

if [ -z "$planes" ]; then
    echo "Planes not supplied!!"
    usage;
    exit 0;
fi
plane_=$(tr ',' '_' <<< "$planes")

PLANE_CONFIG_OUTPUT="/tmp/plane_config.${plane_}.dat"
PLANE_SHUT_CONFIG="/tmp/plane_shut.${plane_}.dat"
LINK_SHUT_CONFIG="/tmp/link_shut.${plane_}.dat"

#
# collect all necessary CLI outputs from the router
#
if [ ! -e $PLATFORM_OUTPUT ]; then
    show_cmd "show platform | save $PLATFORM_OUTPUT"  >& /dev/null
fi

if [ ! -e $S3RXLIST ]; then
    show_cmd "show controller fabric link port s3 rx all | exc NC | save $S3RXLIST"  >& /dev/null
fi
if [ ! -e $S2RXLIST ]; then
    show_cmd "show controller fabric link port s2 rx all | exc NC | save $S2RXLIST"  >& /dev/null
fi

if [ ! -e $PLANE_CONFIG_OUTPUT ]; then
    touch $PLANE_CONFIG_OUTPUT

    for plane in $(tr ',' ' ' <<< "$planes"); do
        show_cmd "show running-config controller fabric plane ${plane} | append $PLANE_CONFIG_OUTPUT"  >& /dev/null
    done
fi

# Get the list of fabric cards which are ready to run the PRBS test.
# First get the list of fabric cards that are part of this plane and then filter 
# out the ones that are not operational.

let count=0
declare -a s2_list

# build the list of S2 nodes
for loc in $(grep -oi 'F[0-3]/FC[0-9]\{1,2\}' $PLANE_CONFIG_OUTPUT); do
    grep -i "^${loc} .*OPERATIONAL" $PLATFORM_OUTPUT >& /dev/null
    if [ $? -eq 0 ]; then
        s2_list[$count]=$(tr [:lower:] [:upper:] <<< "$loc")
        count=$((count+1))
    fi
done

let count=0
declare -a s13_list

# build the list of S13 nodes
for plane in $(tr ',' ' ' <<< "$planes"); do
    for loc in $(grep "^[0-9]/FC${plane} .*OPERATIONAL" $PLATFORM_OUTPUT | grep -o "[0-9]/FC${plane}"); do 
        s13_list[$count]=$loc
        count=$((count+1))
    done
done



[ -n "$verbose" ] && echo "Running PRBS between FE devices of ${s2_list[*]} ${s13_list[*]}"

if [ -n "${shut_down_plane}" ]; then
    
    if [ -n "${shut_down_plane}" ]; then
        echo "Shutting plane ${planes} down!!"
    
        [ -f $PLANE_SHUT_CONFIG ] && rm -f $PLANE_SHUT_CONFIG
    
        echo "conf t" > $PLANE_SHUT_CONFIG   
        for plane in $(tr ',' ' ' <<< "$planes"); do
            echo "controller fabric plane ${plane} shutdown" >> $PLANE_SHUT_CONFIG   
        done
        echo "commit" >> $PLANE_SHUT_CONFIG   
    
        /opt/cisco/calvados/confd/bin/confd_cli -u root -C -N --noaaa $PLANE_SHUT_CONFIG
    fi

    config_controller_fabric_link_port "maintenance" "yes"
    #
    # Disable the "expected" interrupts on all devices
    # 
    time=$(date)
    echo "${time}: Shutting down plane ${planes} completed..."
fi

if [ -n "$seconds" ]; then

    clear_asic_errors_for_plane "disable"
    time=$(date)
    echo "${time} : Plane ${planes} interrupt logs cleared..."

    prbs_operation "set"
    sleep 3
    prbs_operation "get"

    time=$(date)
    echo "${time} : PRBS test on plane ${planes} started..."
    
    sleep $seconds

    rm -f /tmp/PRBS.*

    prbs_operation "get"
    #
    # Analyse the result... 
    #
    time=$(date)
    echo "${time} : PRBS test on plane ${planes} done. Analysing results..."

    analyze_results
fi

if [ -n "$unshut_links" ]; then
    prbs_operation "clear"
    config_controller_fabric_link_port "maintenance" "no"
fi

if [ -n "$reload_plane" ]; then
    reload_fabric_cards_of_plane
fi

cleanup

