#!/bin/bash -
#===============================================================================
#
#          FILE: set_bond.sh
#
#         USAGE: ./set_bond.sh
#
#   DESCRIPTION: 对网卡进行bond绑定
#
#       OPTIONS: ---
#  REQUIREMENTS: ---
#          BUGS: ---
#         NOTES: ---
#        AUTHOR: xiaobo_l (xiaobo.lin@baishancloud.com)
#  ORGANIZATION:
#       CREATED: 08/08/2018 11:18:01 AM
#      REVISION:  ---
#===============================================================================
#操作逻辑:
#	1) 出现两个以上连接的万兆网卡做绑定
#	2) 出现一个连接的万兆网卡不绑定
#	3) 只有一个连接的千兆网卡不绑定
#	4) 两个以上连接的千兆网卡做绑定
#	5) 无网卡连接不绑定

set -o nounset
set -o errexit


function bs_service() {
	set +o errexit
	if [[ -z $@ ]]; then echo "function: bs_service option error "; exit 1; fi
	local action=$1
	shift
	if [[ -z $@ ]]; then echo "function: bs_service option error "; exit 1; fi
	bs_service_el${SYS_MAJOR_VERSION} $action $1
	set -o errexit
}

function bs_service_el6() {
	if ! (chkconfig --list |grep -q $2); then
		return 0
	fi
	case ${1} in
		start)		service $2 start ;;
		stop)		service $2 stop ;;
		restart)	service $2 restart ;;
		save)		service $2 save ;;
		enable)		chkconfig $2 on ;;
		disable)	chkconfig $2 off ;;
		*) echo "function: bs_service option error "; exit 1; ;;
	esac
}

function bs_service_el7() {
	if ! (systemctl list-unit-files | grep -q $2); then
		return 0
	fi
	case ${1} in
		start)		systemctl start $2  ;;
		stop)		systemctl stop $2 ;;
		restart)	systemctl restart $2 ;;
		save)		systemctl save $2 ;;
		enable)		systemctl enable $2 ;;
		disable)	systemctl disable $2 ;;
		*) echo "function: bs_service option error "; exit 1; ;;
	esac
}



function net_interface_exist() {
	local interface=$1
	if (ip link show $interface >/dev/null 2>&1); then
		return 0
	fi
	return 1
}

function net_interface_speed() {
	local interface=$1
	local interface_speed=$(ethtool $interface |grep -oP '(?<=Speed: )\d+')
	echo $interface_speed
}

function net_interface_mac() {
	local interface=$1
	local interface_max=$(ip link show dev $interface | grep -oP '(?<=link/ether )[^\s]+')
	echo $interface_max
}

function set_master() {
	local interface=$1

	cat > /etc/sysconfig/network-scripts/ifcfg-$interface <<EOF
DEVICE=$interface
NAME=$interface
TYPE=Bond
BONDING_MASTER=yes
ONBOOT=yes
BOOTPROTO=none
IPV6INIT=yes
IPV6_AUTOCONF=no
BONDING_OPTS="mode=$bond_mode xmit_hash_policy=layer3+4 miimon=100 downdelay=200 updelay=200"
IPADDR=$main_ip
GATEWAY=$main_gateway
NETMASK=$main_ip_netmask
EOF

if [[ -n "${main_ipv6}" ]]; then
	cat >> /etc/sysconfig/network-scripts/ifcfg-$interface <<EOF
IPV6ADDR=${main_ipv6}
IPV6_DEFAULTGW=${main_ipv6_gateway}
EOF
fi
}

function set_slave() {
	if [[ -z $@ ]]; then
		return 0
	fi

	local bond_name=$1
	shift

	while [[ $# -ne 0 ]]
	do
		local interface=$1
		shift
		net_interface_exist $interface || error_exit "无法找到网卡 $interface"
		cat > /etc/sysconfig/network-scripts/ifcfg-$interface <<EOF
DEVICE=$interface
NAME=$bond_name-slave
TYPE=Ethernet
BOOTPROTO=none
ONBOOT=yes
MASTER=$bond_name
SLAVE=yes
EOF
	done
}

function set_sub_interface() {
	if [[ -z $@ ]]; then
		return 0
	fi

	local bond_name=$1
	shift

	while [[ $# -ne 0 ]]
	do
		local interface=$1
		shift
		local sub_addr=$(dev_get_ip secondary $interface)
		local sub_addr_prefix=$(dev_get_ip_prefix secondary $interface)
		local sub_addr_netmask=$(dev_get_ip_netmask ${sub_addr} ${sub_addr_prefix})
		local bond_sub_interface="${bond_name}:${interface##*:}"

		if [[ -f /etc/sysconfig/network-scripts/ifcfg-$interface ]]; then
			rm -f /etc/sysconfig/network-scripts/ifcfg-$interface
		fi

		cat > /etc/sysconfig/network-scripts/ifcfg-$bond_sub_interface <<EOF
DEVICE=$bond_sub_interface
NAME=$bond_sub_interface
TYPE=Ethernet
BOOTPROTO=none
ONBOOT=yes
IPADDR=$sub_addr
NETMASK=$sub_addr_netmask
EOF
	done
}


function check_env() {
	local interfaces=($(find /sys/class/net -type l -not -lname '*virtual*' -printf '%f\n' | sort -n))
	local net_10g=()
	local net_1g=()

	set +o errexit
	for i in ${interfaces[@]}
	do
		ip link set dev $i up > /dev/null 2>&1
	done
	set -o errexit

	sleep 3

	if [[ -n "$opt_bond_inf" ]]; then
		for i in ${opt_bond_inf[@]}
		do
			net_interface_exist $i || error_exit "无法找到网卡 $i"
		done

		bond_interfaces=(${opt_bond_inf[@]})
		return 0
	fi

	for i in ${interfaces[@]}
	do
		local interface_speed=$(net_interface_speed $i)
		if [[ -z "$interface_speed" ]]; then
			continue
		fi

		if [[ "$interface_speed" -gt 9000 ]]; then
			net_10g+=("$i")
		else
			net_1g+=("$i")
		fi
	done

	if [[ ${#net_10g[@]} -ge 2 ]]; then
		bond_interfaces=(${net_10g[@]})
		return 0
	fi

	if [[ ${#net_10g[@]} -eq 1 ]]; then
		bond_interfaces=(${net_10g[@]})
		show_info
		error_exit '单个10G网卡不绑定'
	fi


	if [[ ${#net_1g[@]} -eq 1 ]]; then
		bond_interfaces=(${net_1g[@]})
		show_info
		error_exit '单个1G网卡不绑定'
	fi

	if [[ ${#net_1g[@]} -eq 0 ]]; then
		error_exit '无网卡连接不绑定'
	fi

	bond_interfaces=(${net_1g[@]})
}

function show_info() {
	echo "Bonding IP:"
	printf "\t Address: $main_ip\n"
	printf "\t Netmask: $main_ip_netmask\n"
	printf "\t Gateway: $main_gateway\n"
	if [[ -n "${main_ipv6}" ]]; then
		printf "\t IPV6 Address: $main_ipv6\n"
		printf "\t IPV6 Gateway: $main_ipv6_gateway\n"
	fi
	printf "\t Data from: $main_dev\n"
	echo ""

	if [[ ${#main_sub_dev[@]} -gt 0 ]]; then
		echo "Sub Interface:"
		for i in ${main_sub_dev[@]}
		do
			local sub_addr=$(dev_get_ip secondary $i)
			local sub_addr_prefix=$(dev_get_ip_prefix secondary $i)
			local sub_addr_netmask=$(dev_get_ip_netmask ${sub_addr} ${sub_addr_prefix})
			printf "\tInterface: $i\tAddress: ${sub_addr}\tNetmask: ${sub_addr_netmask}\n"
		done
		echo ""
	fi

	echo "Bonding Ethernet List:"
	for i in ${bond_interfaces[@]}
	do
		local interface_speed=$(net_interface_speed $i)
		local interface_mac=$(net_interface_mac $i)
		printf "\tInterface: ${i}\tEther: ${interface_mac}\tSpeed: ${interface_speed}Mb/s\n"
	done
	echo ""

	printf "Bonding mode: ${bond_mode}\n"
	echo ""

	printf "run command:\n\t"
	echo "bash set_bond.sh -i '${bond_interfaces[@]}'"
	echo ""
	if [[ ${#bond_interfaces[@]} -gt 4 ]]; then
		printf "warn:\n\t"
		warn "        绑定超过4个接口, 请确认是否有误."
	fi
}

function error_exit() {
	echo -e '\033[31m'
	echo "$1"
	echo -e '\033[0m'
	exit
}

function warn() {
	echo -e '\033[31m'
	echo "$1"
	echo -e '\033[0m'
}

function yes_or_no() {
	local input_data
	local message=$1
	read -p "${message} [yes|no]: " input_data
	case "${input_data}" in
	[yY][eE][sS]|[yY])
		return 0
		;;
	[nN][oO]|[nN])
		return 1
        ;;
	*)
		error_exit "输入错误"
        ;;
	esac
}

function reload_mod() {
	local mod=$1
	if lsmod |grep -P "^${mod}\s+" -q; then
		modprobe -r $mod
		modprobe $mod
	fi
}

function run_netcard_affinity() {
	if  which netcard_affinity.py > /dev/null 2>&1; then
		netcard_affinity.py -m 2 > /dev/null 2>&1
	fi
}


function restart_network() {
	bs_service stop NetworkManager
	bs_service disable NetworkManager

	reload_mod igb
	reload_mod bonding
	bs_service restart network
	run_netcard_affinity
}

function set_bond_conf() {
	set_master $bond_name
	set_slave  $bond_name ${bond_interfaces[@]}

	if [[ ${#main_sub_dev[@]} -gt 0 ]]; then
		set_sub_interface $bond_name ${main_sub_dev[@]}
	fi

	set_interface_conf
}

function join_str() {
        local IFS="$1"
        shift
        echo "$*";
}

function set_interface_conf_igb() {
        local interfaces=$@
        local interface_igb_rss=()
        local driver_igb_filename='/etc/modprobe.d/igb.conf'
	local driver_igb_version=

        if [[ -f $driver_igb_filename ]]; then
                rm -f $driver_igb_filename
        fi

	#igb centos7有集成不用设置
        if [[ $SYS_MAJOR_VERSION == 6 ]]; then
                for i in ${interfaces[@]}
                do
			#高版本的驱动才有队列设置
			driver_igb_version=$(ethtool -i $i | grep -oP '(?<=^version: )[\w.]+')
			if [[ ! "$driver_igb_version" =~ '5.2' ]]; then
				return 0
			fi

                        interface_igb_rss+=("4")
                        echo "alias $i igb" >> $driver_igb_filename
                done
                echo "options igb RSS=$(join_str ',' ${interface_igb_rss[@]})" >> $driver_igb_filename

        fi


}

function set_interface_conf() {
        local interfaces=($(find /sys/class/net -type l -not -lname '*virtual*' -printf '%f\n' | sort -n))
        local interface_igb=()

        for i in ${interfaces[@]}
        do
                local driver=$(ethtool -i $i | grep -oP '(?<=driver: )\w+')

                if [[ "$driver" == "igb" ]]; then
                        interface_igb+=("$i")
                fi
        done

        if [[ -n "${interface_igb:-}" ]]; then
                set_interface_conf_igb ${interface_igb[@]}
        fi

}

function set_bond() {

	if [[ $opt_bond == true ]]; then
		set_bond_conf
	else
		if yes_or_no "确认绑定"; then
			set_bond_conf
		else
			exit
		fi
	fi



	if [[ $opt_skip_renetwork == true ]]; then
		return
	elif [[ $opt_renetwork == true ]]; then
		restart_network
	else
		if yes_or_no "绑定完成 确认重启网卡"; then
			restart_network
		else
			exit
		fi
	fi

}

function set_dns() {
	if [[ $opt_dns == true ]]; then
		defdns=$(cat /etc/resolv.conf|egrep '^nameserver '|awk '$2!~/^(114.114|119.29.29)\./')
		echo "$defdns" > /etc/resolv.conf
		echo -e "nameserver 119.29.29.29\nnameserver 114.114.114.114" >> /etc/resolv.conf
	fi
}

function usage() {
        echo 'help:'
	echo '    -n        Skip the DNS settings (Default=no)'
	echo '    -y        Set this bond (Default=no)'
	echo '    -r        Restart network (Default=no)'
	echo '    -s        Skip restart network (Default=no)'
	echo '    -i        Set bond interface (Default=null)'
	echo '    -d        Set the bond name (Default=bond0)'
	echo '    -a        Get the ip of this dev (Default=first found)'
        echo
        echo 'example :'
        echo '    bash ./set_bond.sh -n -y -r -i "eno1 eno2" -d "bond0" -a "eno1"'
        exit 1
}

function dev_get_ip() {
	local dev_type=$1
	local dev_name=$2
	case $dev_type in
		master)
			echo $(ip -4 address show dev ${dev_name} | grep -P "inet\s\d*.*scope\s+global\s+.*${dev_name}$" | awk '{print $2}' | awk -F'/' '{print $1}')
			;;
		ipv6_master)
			echo $(ip -6 address show dev ${dev_name} | grep -P "inet6\s\d*.*scope\s+global" | awk '{print $2}')
			;;
		secondary)
			echo $(ip -4 address show dev ${dev_name} | grep -P "inet\s\d*.*scope\s+global\s+secondary\s+.*${dev_name}$" | awk '{print $2}' | awk -F'/' '{print $1}')
			;;
	esac
}

function dev_get_ip_prefix() {
	local dev_type=$1
	local dev_name=$2
	case $dev_type in
		master)
			echo $(ip -4 address show dev ${dev_name} | grep -P "inet\s\d*.*scope\s+global\s+.*${dev_name}$" | awk '{print $2}' | awk -F'/' '{print $2}')

			;;
		secondary)
			echo $(ip -4 address show dev ${dev_name} | grep -P "inet\s\d*.*scope\s+global\s+secondary\s+.*${dev_name}$" | awk '{print $2}' | awk -F'/' '{print $2}')
			;;
	esac
}

function dev_get_ip_netmask() {
	local dev_ip=${1:-}
	local dev_ip_prefix=${2:-}
	if [[ -z ${dev_ip} || -z ${dev_ip_prefix} ]]; then
		return
	fi
	echo $(ipcalc -4 -m ${dev_ip}/${dev_ip_prefix} | awk -F'=' '{print $2}')

}

function dev_get_sub_interface() {
	local sub_interface=($(ip -4 address show dev ${main_dev} | grep -P "inet\s\d*.*scope\s+global\s+secondary\s+.*${main_dev}" | grep -oP "${main_dev}:\d+$"))
	local sub_use_interface=()
	if [[ ${#sub_interface[@]} -gt 0 ]]; then
		for i in ${sub_interface[@]}
		do
			if [[ -f /etc/sysconfig/network-scripts/ifcfg-$i ]]; then
				sub_use_interface+=($i)
			fi
		done
		if [[ ${#sub_use_interface[@]} -gt 0 ]]; then
			echo ${sub_use_interface[@]}
		fi
	fi
}


SYS_MAJOR_VERSION=$(rpm -q centos-release |grep -oP '(?<=\.el)\d+')

opt_dns=true
opt_bond=false
opt_renetwork=false
opt_skip_renetwork=false
opt_bond_inf=''
bond_name=bond0
bond_mode=2
bond_interfaces=()
main_dev=$(ip route list default 0/0 | head -n 1 |awk '{print $5}')

for o; do
    case ${o} in
    -n) shift; opt_dns=false ;;
    -y) shift; opt_bond=true ;;
    -r) shift; opt_renetwork=true ;;
    -s) shift; opt_skip_renetwork=true ;;
    -i) shift; opt_bond_inf=($1); shift ;;
    -d) shift; bond_name=$1; shift ;;
    -a) shift; main_dev=$1; shift ;;
    -m) shift; bond_mode=$1; shift ;;
    -h|--help) usage ;;
    -*) echo "Unknown option ${o}.  Try --help."; exit 1; ;;
    esac
done

main_gateway=$(ip -4 route list default 0/0 |grep -P "dev\s+${main_dev}" | head -n 1 |awk '{print $3}')
main_ip=$(dev_get_ip master ${main_dev})
main_ip_prefix=$(dev_get_ip_prefix master ${main_dev})
main_ip_netmask=$(dev_get_ip_netmask ${main_ip} ${main_ip_prefix})
main_sub_dev=($(dev_get_sub_interface))
main_ipv6=$(dev_get_ip ipv6_master ${main_dev})
main_ipv6_gateway=$(ip -6 route list default ::/0 |grep -P "dev\s+${main_dev}" | head -n 1 |awk '{print $3}')

check_env
show_info
set_bond
set_dns
