#!/bin/sh

#Default bonding config
BONDING_DEFAULT=single

#Files we need
MERU_FUNCTIONS=/etc/init.d/meru-functions
OCT_CTRL=/opt/meru/bin/oct/oct-ctrl

#Bonding module parameters
BASE_NAME=eth
BASE_INDEX=6
MIIMON_TIME=500
NUM_BONDS=2

#Octeon slave interface base name
SLAVE_INT_NAME=oct

#Maximum number of Octeon interfaces
OCT_INT_MAX=4

#Bonding database info
START_DB=/opt/meru/etc/merudb.dbs
RUNNING_DB=/opt/meru/var/run/running-db/merudb.dbs
DBTABLE=MosWncVars
SEARCH_FIELD=m_Bonding

#Paths
SUBSQL_CMD=/opt/meru/bin/subsql
PATH=/sbin:/bin:/usr/sbin:/usr/bin
export PATH

# --------------------- INITIALIZATION ------------------------ #

#Sanity checks (for files that we link against)
if [ ! -f "$MERU_FUNCTIONS" ]; then
     echo "Cannot run the bonding configuration tool without $MERU_FUNCTIONS";
     exit 1
fi

if [ ! -f "$OCT_CTRL" ]; then
     echo "Cannot run the bonding configuration tool without $OCT_CTRL";
     exit 2
fi

#Import meru-functions file
. $MERU_FUNCTIONS

#And set the platform parameters
set_platform_params

#Right now we only support Octeon cards for bonding
NUM_OCTS=`$OCT_CTRL count`

if [ "$NUM_OCTS" -eq 0 ]; then
     echo "This platform does not currently support bonding."
     exit 3
fi

# ------------------------ FUNCTIONS --------------------------- #

#Prints usage information
usage()
{
     echo "Meru Networks Bonding Configuration Tool"
     echo ""
     echo "Usage:"
     echo ""
     echo "bonding -h"
     echo "bonding -?"
     echo "bonding --help                          Display this usage info."
     echo ""
     echo "bonding enable                          Turn on the bonding module and"
     echo "                                        initialize to the proper mode."
     echo ""
     echo "bonding disable                         Turn off the bonding module."
     echo ""
     echo "bonding detach                          Detaches the underlying slaves"
     echo "                                        from the bonding master.  Leaves"
     echo "                                        bonding master up."
     echo ""
     echo "bonding attach                          Attaches the underlying slaves"
     echo "                                        to the proper master devices"
     echo ""     
     echo "bonding check [single | dual]           Verifies that the bonding"
     echo "                                        module is running and configured"
     echo "                                        for the specified mode.  If not,"
     echo "                                        the configuration is repaired and"
     echo "                                        the module is started."
     echo ""
     echo "bonding [single | dual]                 Changes the bonding configuration"
     echo "                                        and begins the bonding module."
     echo ""
     echo "bonding show <full>                     Show bonding stats.  Include the"
     echo "                                        'full' option to show Rx/Tx and"
     echo "                                        Octeon physical port information."
     echo ""
     echo "IMPORTANT NOTE: BONDING IS NOT SUPPORTED ON ALL PLATFORMS.  IF YOUR PLATFORM"
     echo "IS NOT SUPPORTED, THE BONDING APPLICATION WILL EXIT OUT WITH A WARNING."
}

#Verify that the argument is either "single" or "dual"
verify_bonding_mode() {
     if [ "$1" = "single" -o "$1" = "dual" ]; then
          return 0
     else
          echo "Bonding mode [$1] is invalid."
          usage
          exit 2
     fi
}

#Distribute a command across all of the Octeon cards
distrib_command()
{
     for ((i = 0; i < NUM_OCTS; i++))
     do
          echo $1 > /proc/octeon/octeon$i
     done
}

#Set BONDING_CONF from the database file.
get_db_conf()
{
     #Check our DB files.  Use the running DB first, then startup, then just use default
     if [ -f $RUNNING_DB ]; then
          DBFILE=$RUNNING_DB
     elif [ -f $START_DB ]; then
          DBFILE=$START_DB

          echo "open '$DBFILE'; show tables;" | $SUBSQL_CMD 2>&1 | grep MosWncVars 2>&1 > /dev/null
          if [ $? -eq 0 ]; then
               NUMWNCVAR=`echo "open '$DBFILE'; show tables;" | $SUBSQL_CMD 2>&1 | grep MosWncVars | awk '{print $5}'`
               if [ $NUMWNCVAR -eq 0 ]; then
                    DBFILE=${START_DB}.bak
               fi
          else
               DBFILE=${START_DB}.bak
          fi

     fi

     if [ ! -f $DBFILE ]; then
          BONDING_CONF=$BONDING_DEFAULT
          return
     fi 

     SEARCH_TEXT=`echo "open '$DBFILE'; select * from $DBTABLE;" | $SUBSQL_CMD`

     FIELD_LIST=`echo "$SEARCH_TEXT" | grep $SEARCH_FIELD | sed "s/>> //"`
     VALUE_LIST=`echo "$SEARCH_TEXT" | grep -A 1 $SEARCH_FIELD | sed "/^>>/d"`

     #Get the index of the SEARCH_FIELD
     field_index=0
     field_name=$SEARCH_FIELD
     field_len=${#field_name}
     field_list=$FIELD_LIST
     find_failed=0
     emergency_break=100

     #Iterate through the table fields to find our match
     while true; do

          #End of list, or emergency break
          if [ ${#field_list} -eq 0 -o $emergency_break -eq $field_index ]; then
               find_failed=1
               break
          fi

          #We found it!
          if [ "${field_list:0:$field_len}" = "$field_name" ]; then
               break
          fi
          
          #Go to the next field and increment index
          next_space=`expr index "$field_list" " "`
          field_list=${field_list:$next_space}          
          field_index=`expr $field_index + 1`

     done

     #If we failed to the field, set to default
     if [ $find_failed -eq 1 ]; then
          BONDING_CONF=$BONDING_DEFAULT
          return
     fi
     
     #Clean up the value list so that its easily parsable
     value_list=`echo "$VALUE_LIST" | sed "s/(//" | sed "s/)//" | sed "s/'[^']*'/s/g" | sed "s/-[^,]*/n/g" | sed "s/,//g"`

     #For each index that we pass, strip out the value
     cur_index=0
     while [ $cur_index -ne $field_index ]; do
          value_list=`echo "$value_list" | sed "s/[^ ]* //"`
          cur_index=`expr $cur_index + 1`
     done

     #Check if we're the last value or not
     sp_ind=`expr index "$value_list" " "`

     #If we're the last value, just return.  Otherwise we have to extract our value from the beginning of the list
     if [ $sp_ind -eq 0 ]; then
          final_val=$value_list
     else
          sp_ind=`expr $sp_ind - 1`
          final_val=${value_list:0:$sp_ind}
     fi

     #final_val is our value!
     if [ $final_val -eq 0 ]; then
          BONDING_CONF="single"
     else
          BONDING_CONF="dual"
     fi
}

#Attach the slaves to the proper masters
bonding_attach()
{
     #Link the slave devices to the masters
     for ((b = 0; b < NUM_BONDS; b++))
     do
          INT_INDEX=`expr $b + $BASE_INDEX`
          MASTER_INTERFACE="${BASE_NAME}${INT_INDEX}"
          SLAVE_INTERFACES=""
          
          for ((s = 0; s < NUM_OCTS; s++))
          do
               SLAVE_INTERFACE="${SLAVE_INT_NAME}${s}_${b}"
               ifconfig $SLAVE_INTERFACE up
               TMP_STR="${SLAVE_INTERFACES} ${SLAVE_INTERFACE}"
               SLAVE_INTERFACES="$TMP_STR"
          done

          echo "Bonding master interface ${MASTER_INTERFACE} to slaves:${SLAVE_INTERFACES}"
          ifenslave $MASTER_INTERFACE $SLAVE_INTERFACES
     done
     
     #Set the bonding mode
     get_db_conf
     bonding_setmode $BONDING_CONF
}

#Remove the slaves from their masters, but leave the masters up
bonding_detach()
{
     #Remove slave devices from the masters
     for ((b = 0; b < NUM_BONDS; b++))
     do
          INT_INDEX=`expr $b + $BASE_INDEX`
          MASTER_INTERFACE="${BASE_NAME}${INT_INDEX}"
          
          for ((s = 0; s < NUM_OCTS; s++))
          do
               SLAVE_INTERFACE="${SLAVE_INT_NAME}${s}_${b}"
               echo "Detaching slave interface ${SLAVE_INTERFACE} from ${MASTER_INTERFACE}"
               ifenslave -d $MASTER_INTERFACE $SLAVE_INTERFACE
          done
     done
}

#Turn on the bonding module and configure it
#Assumes that the slave interfaces have already been initialized
bonding_enable()
{
     #Check that the bonding module does not already exist
     lsmod bonding | grep "bonding" 2>&1 > /dev/null && {
          echo "Bonding is already enabled."
          exit 2
     }

     #And install the module
     modprobe bonding bond_ifname=$BASE_NAME bond_base_index=$BASE_INDEX max_bonds=$NUM_BONDS miimon=$MIIMON_TIME persistent_bond=1 persistent_mac=1 2>&1 > /dev/null || { 
          echo "Bonding module failed to install"; exit 3; 
     }

     #Bring up the master devices
     for ((b = 0; b < NUM_BONDS; b++))
     do
          INT_INDEX=`expr $b + $BASE_INDEX`
          MASTER_INTERFACE="${BASE_NAME}${INT_INDEX}"
          
          ifconfig $MASTER_INTERFACE up
     done

     #Attach the slaves
     bonding_attach
}

#Turn off the bonding module
bonding_disable()
{
     #Check that the module exists
     lsmod bonding | grep "bonding" 2>&1 > /dev/null || {
          echo "Bonding is not enabled."
          exit 2
     }     

     #Now unlink the slave devices from the masters
     for ((b = 0; b < NUM_BONDS; b++))
     do
          INT_INDEX=`expr $b + $BASE_INDEX`
          MASTER_INTERFACE="${BASE_NAME}${INT_INDEX}"
          
          echo "Bonding master interface ${MASTER_INTERFACE} coming down."
          ifconfig $MASTER_INTERFACE down
     done
     
     #And uninstall the module
     rmmod bonding || { 
          echo "Bonding module failed to unload"; exit 3; 
     }

}

#Force into a specific bonding mode and change the config file
bonding_setmode()
{
     verify_bonding_mode $1

     if [ "$1" = "single" ]; then
          distrib_command "bonding_scheme=4"
     else
          distrib_command "bonding_scheme=2"
     fi

     echo "Bonding mode set to: $1."

     # Sleep is required because kernel bonding miimon is 500ms interval
     # Give the kernel time to see the new interface links
     sleep 1
}

#Grab interface send/receive
get_intf_stats()
{
     if_line=`cat /proc/net/dev | grep "$1"`
     find_colon=`expr index "$if_line" ":"`
     rest_of_line="${if_line:$find_colon}"
     
     INTF_RX_BYTES=`echo "$rest_of_line" | awk '{ print $1 }'`
     INTF_RX_PACKETS=`echo "$rest_of_line" | awk '{ print $2 }'`

     INTF_TX_BYTES=`echo "$rest_of_line" | awk '{ print $9 }'`
     INTF_TX_PACKETS=`echo "$rest_of_line" | awk '{ print $10 }'` 
}

#Prints Octeon physical port information (from the Octeon proc entries)
# $1 = octeon id
# $2 = bonding interface
# $3 = slave interface bonding coordinates
print_oct_phys_info()
{
     PROC_INFO=`cat /proc/octeon/metrics${1}`
     phys_index=0
     phys_count=`echo "$PROC_INFO" | grep "$2 phys_intr_[[:digit:]]_bonded_to" | wc -l | awk '{ print $1 }'`

     echo "      Slave Interface $3 num physical interfaces   = ${phys_count}"
     echo ""

     for (( octport = 0; octport < OCT_INT_MAX; octport++ ))
     do
          # Scan through the physical ports to see which are bonded to this interface
          echo $PROC_INFO | grep "$2 phys_intr_${octport}_bonded_to" 2>&1 > /dev/null && {
               phys_coord="${3}-${phys_index}"
               echo "        Physical Interface ${phys_coord} location           = Card $1 / Port $octport"

               link_speed=`echo "$PROC_INFO" | grep "phys_intr_${octport}_speed" | awk '{ print $1 }'`
               echo "        Physical Interface ${phys_coord} link speed         = ${link_speed} Mbps"

               rx_bytes=`echo "$PROC_INFO" | grep "phys_intr_${octport}_bytes_inbound" | awk '{ print $1 }'`
               tx_bytes=`echo "$PROC_INFO" | grep "phys_intr_${octport}_bytes_outbound" | awk '{ print $1 }'`
               rx_packets=`echo "$PROC_INFO" | grep "phys_intr_${octport}_packets_inbound" | awk '{ print $1 }'`
               tx_packets=`echo "$PROC_INFO" | grep "phys_intr_${octport}_packets_outbound" | awk '{ print $1 }'`
               echo "        Physical Interface ${phys_coord} bytes   (Rx / Tx)  = ${rx_bytes} / ${tx_bytes}"
               echo "        Physical Interface ${phys_coord} packets (Rx / Tx)  = ${rx_packets} / ${tx_packets}"
               echo ""


               phys_index=`expr ${phys_index} + 1`
          }
     done
}

#Display bonding status
bonding_stats()
{
     lsmod | grep "bonding" 2>&1 > /dev/null || {
     echo "Current bonding configuration                       = disabled"
          exit 0
     }

     get_db_conf     
     echo ""
     echo "Current bonding configuration                       = $BONDING_CONF"
     echo ""
     
     master_if_count=0
     for master_if_file in `ls /proc/net/bonding/*`
     do
          if [ "$BONDING_CONF" = "single" -a $master_if_count -eq 1 ]; then
               break;
          fi
         
          master_if_name=`basename $master_if_file`
          echo "Master bonding interface ${master_if_count}:"

          if [ "$1" = "full" ]; then
               get_intf_stats $master_if_name
               echo "  Master interface ${master_if_count} bytes   (Rx / Tx)              = $INTF_RX_BYTES / $INTF_TX_BYTES"
               echo "  Master interface ${master_if_count} packets (Rx / Tx)              = $INTF_RX_PACKETS / $INTF_TX_PACKETS"
          fi

          slave_if_names=`cat $master_if_file | grep "Slave Interface"`
          slave_if_total=`cat $master_if_file | grep "Slave Interface" | wc -l`
          slave_if_count=0

          echo "  Master interface ${master_if_count} num slave interfaces           = `expr $slave_if_total`"
          echo ""

          # Setting IFS here allows us to 'for' by line rather than by token
          IFS=$'\012'
          for slave_if_line in $slave_if_names
          do
               slave_if_name=`echo "$slave_if_line" | awk '{ print $3 }'`
               slave_if_coord="${master_if_count}-${slave_if_count}"
               echo "    Slave interface $slave_if_coord:"

               slave_link_status="down"
               ifconfig | grep "${slave_if_name}" -A 1 | grep "RUNNING" 2>&1 > /dev/null && { slave_link_status="up"; }
               echo "    Slave interface $slave_if_coord link status                 = ${slave_link_status}"

               if [ "$1" = "full" ]; then
                    get_intf_stats $slave_if_name
                    echo "      Slave interface ${slave_if_coord} bytes   (Rx / Tx)         = $INTF_RX_BYTES / $INTF_TX_BYTES"
                    echo "      Slave interface ${slave_if_coord} packets (Rx / Tx)         = $INTF_RX_PACKETS / $INTF_TX_PACKETS"
                    if [ "${slave_if_name:0:3}" = "oct" ]; then
                         print_oct_phys_info ${slave_if_name:3:1} ${slave_if_name:5:1} ${slave_if_coord} 
                    fi
               fi
                    
               echo ""               
               slave_if_count=`expr $slave_if_count + 1`
          done
          IFS=$' \t\n'

          master_if_count=`expr ${master_if_count} + 1`
     done

     # Display a friendly note to IOSCLI users that there is an additional option
     if [ "$1" = "ioscli" ]; then
          echo "* Use 'show bonding full' to display more details."
     fi
}

# ----------------------- COMMAND LINE PARSING --------------------------- #

#Deal with command line parameters
case "$1" in 
     "-h" | "-?" | "--help" )
          usage
          exit 1
          ;;

     "enable")
          bonding_enable
          exit 0
          ;;

     "disable")
          bonding_disable
          exit 0
          ;;

     "attach")
          bonding_attach
          exit 0
          ;;

     "detach")
          bonding_detach
          exit 0
          ;;

     "check")
          bonding_setmode $2
          exit 0
          ;;

     "single")
          bonding_setmode "single"
          exit 0
          ;;

     "dual")
          bonding_setmode "dual"
          exit 0
          ;;
     "show" | "stats")
          bonding_stats $2
          exit 0
          ;;
     *)
          usage
          exit 1
          ;;
esac

