| #!/bin/bash |
| # |
| # OCF resource agent which monitors state of network interface and records it |
| # as a node attribute in the CIB based on the sum of speeds of its active (up, |
| # link detected, not blocked) underlying interfaces. |
| # |
| # Copyright (c) 2011 Vladislav Bogdanov <bubble@hoster-ok.com> |
| # Partially based on 'ping' RA by Andrew Beekhof |
| # |
| # Change on 2017 by Tomer Azran <tomerazran@gmail.com>: |
| # Add "ip" parameter to detect network interface name by ip address: |
| # http://lists.clusterlabs.org/pipermail/users/2017-August/006224.html |
| # |
| # OCF instance parameters: |
| # OCF_RESKEY_name: name of attribute to set in CIB |
| # OCF_RESKEY_ip ip address to check |
| # OCF_RESKEY_iface: network interface to monitor |
| # OCF_RESKEY_bridge_ports: if not null and OCF_RESKEY_iface is a bridge, list of |
| # bridge ports to consider. |
| # Default is all ports which have designated_bridge=root_id |
| # OCF_RESKEY_weight_base: Relative weight of 1Gbps. This can be used to tune |
| # value of resulting CIB attribute. |
| # |
| # Initialization: |
| |
| : ${OCF_FUNCTIONS=${OCF_ROOT}/resource.d/heartbeat/.ocf-shellfuncs} |
| |
| # If these aren't available, we can still show help, |
| # which is all that is needed to build the man pages. |
| [ -r "${OCF_FUNCTIONS}" ] && . "${OCF_FUNCTIONS}" |
| [ -r "${OCF_FUNCTIONS_DIR}/findif.sh" ] && . "${OCF_FUNCTIONS_DIR}/findif.sh" |
| : ${OCF_SUCCESS=0} |
| |
| : ${__OCF_ACTION=$1} |
| |
| FINDIF=findif |
| |
| # Defaults |
| OCF_RESKEY_name_default="ifspeed" |
| OCF_RESKEY_bridge_ports_default="detect" |
| OCF_RESKEY_weight_base_default=1000 |
| OCF_RESKEY_dampen_default=5 |
| |
| : ${OCF_RESKEY_name=${OCF_RESKEY_name_default}} |
| : ${OCF_RESKEY_bridge_ports=${OCF_RESKEY_bridge_ports_default}} |
| : ${OCF_RESKEY_weight_base=${OCF_RESKEY_weight_base_default}} |
| : ${OCF_RESKEY_dampen=${OCF_RESKEY_dampen_default}} |
| |
| meta_data() { |
| cat <<END |
| <?xml version="1.0"?> |
| <!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd"> |
| <resource-agent name="ifspeed" version="1.0"> |
| <version>1.0</version> |
| |
| <longdesc lang="en"> |
| Every time the monitor action is run, this resource agent records (in the CIB) |
| (relative) speed of network interface it monitors. |
| |
| This RA can monitor physical interfaces, bonds, bridges, vlans and (hopefully) |
| any combination of them. |
| |
| Examples: |
| *) Bridge on top of one 10Gbps interface (eth2) and 802.3ad bonding (bond0) built |
| on two 1Gbps interfaces (eth0 and eth1). |
| *) Active-backup bonding built on top of one physical interface and one vlan on |
| another interface. |
| |
| For STP-enabled bridges this RA tries to some-how guess network topology and by |
| default looks only on ports which are connected to upstream switch. This can be |
| overridden by 'bridge_ports' parameter. Active interfaces in this case are those |
| in "forwarding" state. |
| |
| For balancing bonds this RA summs speeds of underlying "up" slave interfaces |
| (and applies coefficient 0.8 to result). |
| |
| For non-balancing bonds ('active-backup' and probably 'broadcast'), only the |
| speed of the currently active slave is used. |
| </longdesc> |
| <shortdesc lang="en">Network interface speed monitor</shortdesc> |
| |
| <parameters> |
| |
| <parameter name="name" unique="1"> |
| <longdesc lang="en"> |
| The name of the attribute to set. This is the name to be used in the constraints. |
| </longdesc> |
| <shortdesc lang="en">Attribute name</shortdesc> |
| <content type="string" default="${OCF_RESKEY_name_default}"/> |
| </parameter> |
| |
| <parameter name="iface" unique="0" required="1"> |
| <longdesc lang="en"> |
| Network interface to monitor. |
| </longdesc> |
| <shortdesc lang="en">Network interface</shortdesc> |
| <content type="string" default=""/> |
| </parameter> |
| |
| <parameter name="ip" unique="0" required="0"> |
| <longdesc lang="en"> |
| Try to detect interface name by detecting the interface that holds the IP address. |
| The IPv4 (dotted quad notation) or IPv6 address (colon hexadecimal notation) |
| example IPv4 "192.168.1.1". |
| example IPv6 "2001:db8:DC28:0:0:FC57:D4C8:1FFF". |
| </longdesc> |
| <shortdesc lang="en">IPv4 or IPv6 address</shortdesc> |
| <content type="string" default="" /> |
| </parameter> |
| |
| <parameter name="bridge_ports" unique="0"> |
| <longdesc lang="en"> |
| If not null and OCF_RESKEY_iface is a bridge, list of bridge ports to consider. |
| Default is all ports which have designated_bridge=root_id. |
| </longdesc> |
| <shortdesc lang="en">Bridge ports</shortdesc> |
| <content type="string" default="${OCF_RESKEY_bridge_ports_default}"/> |
| </parameter> |
| |
| <parameter name="weight_base" unique="0"> |
| <longdesc lang="en"> |
| Relative weight of 1Gbps in interface speed. |
| Can be used to tune how big attribute value will be. |
| </longdesc> |
| <shortdesc lang="en">Weight of 1Gbps</shortdesc> |
| <content type="integer" default="${OCF_RESKEY_weight_base_default}"/> |
| </parameter> |
| |
| <parameter name="dampen" unique="0"> |
| <longdesc lang="en"> |
| The time to wait (dampening) for further changes to occur. |
| </longdesc> |
| <shortdesc lang="en">Dampening interval</shortdesc> |
| <content type="integer" default="${OCF_RESKEY_dampen_default}"/> |
| </parameter> |
| |
| <parameter name="debug" unique="0"> |
| <longdesc lang="en"> |
| Log what have been done more verbosely. |
| </longdesc> |
| <shortdesc lang="en">Verbose logging</shortdesc> |
| <content type="string" default="false"/> |
| </parameter> |
| |
| </parameters> |
| |
| <actions> |
| <action name="start" timeout="30" /> |
| <action name="stop" timeout="30" /> |
| <action name="monitor" depth="0" timeout="30" interval="10"/> |
| <action name="meta-data" timeout="5" /> |
| <action name="validate-all" timeout="30" /> |
| </actions> |
| </resource-agent> |
| END |
| } |
| |
| usage() { |
| cat <<END |
| usage: $0 {start|stop|monitor|validate-all|meta-data} |
| |
| Expects to have a fully populated OCF RA-compliant environment set. |
| END |
| } |
| |
| start() { |
| monitor |
| if [ $? -eq $OCF_SUCCESS ]; then |
| return $OCF_SUCCESS |
| fi |
| ha_pseudo_resource ${ha_pseudo_resource_name} start |
| update |
| return $? |
| } |
| |
| stop() { |
| ha_pseudo_resource ${ha_pseudo_resource_name} stop |
| attrd_updater -D -n ${OCF_RESKEY_name} -d ${OCF_RESKEY_dampen} ${attrd_options} |
| return $OCF_SUCCESS |
| } |
| |
| monitor() { |
| local ret |
| ha_pseudo_resource ${ha_pseudo_resource_name} monitor |
| ret=$? |
| if [ ${ret} -eq $OCF_SUCCESS ] ; then |
| update |
| fi |
| return ${ret} |
| } |
| |
| # This function tries to guess nic interface by IP |
| get_nic_name_by_ip(){ |
| # $FINDIF takes its parameters from the environment. |
| # Its output is as follows: |
| # [NIC_NAME] netmask [NETMASK] broadcast [BROADCAST} |
| NICINFO=$( ${FINDIF} ) |
| rc=$? |
| if [ $rc -eq 0 ];then |
| # Get NIC_NAME part of findif function output. |
| echo "${NICINFO%% *}" |
| else |
| echo "" |
| fi |
| } |
| |
| validate() { |
| # Check the interface parameter |
| if [ -z "${OCF_RESKEY_iface}" ]; then |
| if [ -z "${OCF_RESKEY_ip}" ]; then |
| ocf_log err "Empty iface and ip parameters. Please specify either an interface name or valid ip address." |
| exit $OCF_ERR_CONFIGURED |
| else |
| ipcheck_ipv4 "${OCF_RESKEY_ip}" |
| if [ $? -eq 1 ] ; then |
| ipcheck_ipv6 "${OCF_RESKEY_ip}" |
| if [ $? -eq 1 ] ; then |
| ocf_exit_reason "ip parameter [${OCF_RESKEY_ip}] is not a valid ip address." |
| exit $OCF_ERR_CONFIGURED |
| fi |
| fi |
| fi |
| fi |
| return $OCF_SUCCESS |
| } |
| |
| iface_get_speed() { |
| local iface=$1 |
| local operstate |
| local carrier |
| local bridge_iface_speed |
| local bond_iface_speed |
| local vlan_iface_speed |
| local speed |
| |
| if [ ! -e "/sys/class/net/${iface}" ] ; then |
| echo "0" |
| elif iface_is_bridge ${iface} ; then # bridges do not have operstate |
| read carrier < "/sys/class/net/${iface}/carrier" |
| |
| if [ "${carrier}" != "1" ] ; then |
| echo "0" |
| else |
| bridge_get_speed ${iface} |
| fi |
| else |
| read operstate < "/sys/class/net/${iface}/operstate" |
| read carrier < "/sys/class/net/${iface}/carrier" |
| |
| if [ "${operstate}" != "up" ] || [ "${carrier}" != "1" ] ; then |
| echo "0" |
| elif iface_is_bond ${iface} ; then |
| bond_get_speed ${iface} |
| elif iface_is_vlan ${iface} ; then |
| iface_get_speed $( vlan_get_phy ${iface} ) |
| elif iface_is_hfi1 "${iface}" ; then |
| hfi1_get_speed "${iface}" |
| else |
| read speed < "/sys/class/net/${iface}/speed" |
| echo ${speed} |
| fi |
| fi |
| } |
| |
| iface_is_vlan() { |
| local iface=$1 |
| [ -e "/proc/net/vlan/${iface}" ] && return 0 || return 1 |
| } |
| |
| iface_is_bridge() { |
| local iface=$1 |
| [ -e "/sys/class/net/${iface}/bridge" ] && return 0 || return 1 |
| } |
| |
| iface_is_bond() { |
| local iface=$1 |
| [ -e "/sys/class/net/${iface}/bonding" ] && return 0 || return 1 |
| } |
| |
| iface_is_hfi1() { |
| local iface=$1 |
| driver=$(readlink /sys/class/net/${iface}/device/driver) |
| [[ $(basename ${driver}) =~ "hfi1" ]] && return 0 || return 1 |
| } |
| |
| vlan_get_phy() { |
| local iface=$1 |
| sed -ne "s/^${iface} .*| *//p" < /proc/net/vlan/config |
| } |
| |
| bridge_is_stp_enabled() { |
| local iface=$1 |
| local stp |
| read stp < "/sys/class/net/${iface}/bridge/stp_state" |
| [ "${stp}" = "1" ] && return 0 || return 1 |
| } |
| |
| bridge_get_root_ports() { |
| local bridge=$1 |
| local root_id |
| local root_ports="" |
| local bridge_id |
| |
| read root_id < "/sys/class/net/${bridge}/bridge/root_id" |
| |
| for port in /sys/class/net/${bridge}/brif/* ; do |
| read bridge_id < "${port}/designated_bridge" |
| if [ "${bridge_id}" = "${root_id}" ] ; then |
| root_ports="${root_ports} ${port##*/}" |
| fi |
| done |
| |
| root_ports=${root_ports# } |
| |
| if [ -n "$2" ] ; then # Record value in specified var. This expects we were called not in a sub-shell. |
| eval $2=\${root_ports} |
| else # Expect sub-shell |
| echo ${root_ports} |
| fi |
| } |
| |
| # From /inlude/linux/if_bridge.h: |
| #define BR_STATE_DISABLED 0 |
| #define BR_STATE_LISTENING 1 |
| #define BR_STATE_LEARNING 2 |
| #define BR_STATE_FORWARDING 3 |
| #define BR_STATE_BLOCKING 4 |
| |
| bridge_get_active_ports() { |
| local bridge=$1 |
| shift 1 |
| local ports="$*" |
| local active_ports="" |
| local port_state |
| local stp_state |
| local warn=0 |
| |
| bridge_is_stp_enabled ${bridge} |
| stp_state=$? |
| |
| if [ -z "${ports}" ] || [ "${ports}" = "detect" ] ; then |
| bridge_get_root_ports ${bridge} ports |
| fi |
| |
| for port in $ports ; do |
| if [ ! -e "/sys/class/net/${bridge}/brif/${port}" ] ; then |
| ocf_log warning "Port ${port} doesn't belong to bridge ${bridge}" |
| continue |
| fi |
| read port_state < "/sys/class/net/${bridge}/brif/${port}/state" |
| if [ "${port_state}" = "3" ] ; then |
| if [ -n "${active_ports}" ] && ${stp_state} ; then |
| warn=1 |
| fi |
| active_ports="${active_ports} ${port}" |
| fi |
| done |
| if [ ${warn} -eq 1 ] ; then |
| ocf_log warning "More then one upstream port in bridge '${bridge}' is in forwarding state while STP is enabled: ${active_ports}" |
| fi |
| echo "${active_ports# }" |
| } |
| |
| bridge_get_speed() { |
| local iface=$1 |
| local bridge_port_speed |
| local aggregate_speed=0 |
| |
| if ! iface_is_bridge ${iface} ; then |
| echo 0 |
| return |
| fi |
| |
| local ports=$( bridge_get_active_ports ${iface} ${OCF_RESKEY_bridge_ports} ) |
| for port in ${ports} ; do |
| : $(( aggregate_speed += $( iface_get_speed ${port} ) )) |
| done |
| if [ -n "$2" ] ; then # Record value in specified var. This expects we were called not in a sub-shell. |
| eval $2=\${aggregate_speed} |
| else # Expect sub-shell |
| echo ${aggregate_speed} |
| fi |
| } |
| |
| hfi1_get_speed() { |
| local iface=$1 |
| local hfi1_speed |
| local hfi1_value |
| local hfi1_desc |
| |
| # Currently (9/14/2017 Intel Omni Path v10.5.0.0.155) Intel doesn't have Dual/Multiple ports Host Channel Adapters |
| # and it's save to use such method to get a speed. |
| # Example of output: |
| # [root@es-host0 ~]# cat /sys/class/net/ib0/device/infiniband/*/ports/*/rate |
| # 100 Gb/sec (4X EDR) |
| read hfi1_speed hfi1_value hfi1_desc < /sys/class/net/${iface}/device/infiniband/*/ports/*/rate |
| |
| # hfi1_value always in Gb/sec, so we need to convert hfi1_speed in Mb/sec |
| echo $(( hfi1_speed * 1000 )) |
| } |
| |
| bond_get_slaves() { |
| local iface=$1 |
| local slaves |
| read slaves < "/sys/class/net/${iface}/bonding/slaves" |
| if [ -n "$2" ] ; then # Record value in specified var. This expects we were called not in a sub-shell. |
| eval $2=\${slaves} |
| else # Expect sub-shell |
| echo ${slaves} |
| fi |
| } |
| |
| bond_get_active_iface() { |
| local iface=$1 |
| local active |
| read active < "/sys/class/net/${iface}/bonding/active_slave" |
| if [ -n "$2" ] ; then # Record value in specified var. This expects we were called not in a sub-shell. |
| eval $2=\${active} |
| else # Expect sub-shell |
| echo ${active} |
| fi |
| } |
| |
| bond_is_balancing() { |
| local iface=$1 |
| read mode mode_index < "/sys/class/net/${iface}/bonding/mode" |
| case ${mode} in |
| "balance-rr"|"balance-xor"|"802.3ad"|"balance-tlb"|"balance-alb") |
| return 0 |
| ;; |
| *) |
| return 1 |
| ;; |
| esac |
| } |
| |
| bond_get_speed() { |
| local iface=$1 |
| local aggregate_speed=0 |
| local bond_slave_speed |
| local active_iface |
| local bond_slaves |
| |
| if ! iface_is_bond ${iface} ; then |
| echo 0 |
| return |
| fi |
| |
| bond_get_slaves ${iface} bond_slaves |
| |
| if bond_is_balancing ${iface} ; then |
| for slave in ${bond_slaves} ; do |
| : $(( aggregate_speed += $( iface_get_speed ${slave} ) )) |
| done |
| # Bonding is unable to get speed*n |
| : $(( aggregate_speed = aggregate_speed * 8 / 10 )) |
| else |
| bond_get_active_iface ${iface} active_iface |
| aggregate_speed=$( iface_get_speed $active_iface ) |
| fi |
| if [ -n "$2" ] ; then # Record value in specified var. This expects we were called not in a sub-shell. |
| eval $2=\${aggregate_speed} |
| else # Expect sub-shell |
| echo ${aggregate_speed} |
| fi |
| } |
| |
| update() { |
| local speed; |
| local nic=${OCF_RESKEY_iface}; |
| if [ -z "${OCF_RESKEY_iface}" ]; then |
| nic=$( get_nic_name_by_ip ) |
| if [ -z "${nic}" ];then |
| ocf_log err "Could not retrieve network interface name from ip address (${OCF_RESKEY_ip})" |
| exit $OCF_ERR_GENERIC |
| fi |
| fi |
| speed=$( iface_get_speed ${nic} ) |
| |
| : $(( score = speed * ${OCF_RESKEY_weight_base} / 1000 )) |
| if [ "$__OCF_ACTION" = "start" ] ; then |
| attrd_updater -n ${OCF_RESKEY_name} -B ${score} -d ${OCF_RESKEY_dampen} ${attrd_options} |
| else |
| attrd_updater -n ${OCF_RESKEY_name} -v ${score} -d ${OCF_RESKEY_dampen} ${attrd_options} |
| fi |
| rc=$? |
| case ${rc} in |
| 0) |
| ocf_is_true ${OCF_RESKEY_debug} && ocf_log debug "Updated ${OCF_RESKEY_name} = ${score}" |
| ;; |
| *) |
| ocf_log warn "Could not update ${OCF_RESKEY_name} = ${score}: rc=${rc}" |
| ;; |
| esac |
| return ${rc} |
| } |
| |
| case $__OCF_ACTION in |
| meta-data) |
| meta_data |
| exit $OCF_SUCCESS |
| ;; |
| usage|help) |
| usage |
| exit $OCF_SUCCESS |
| ;; |
| esac |
| |
| if [ `uname` != "Linux" ] ; then |
| ocf_log err "This RA works only on linux." |
| exit $OCF_ERR_INSTALLED |
| fi |
| |
| : ${ha_pseudo_resource_name:="ifspeed-${OCF_RESOURCE_INSTANCE}"} |
| |
| attrd_options='-q' |
| if ocf_is_true ${OCF_RESKEY_debug} ; then |
| attrd_options='' |
| fi |
| |
| validate || exit $? |
| |
| case $__OCF_ACTION in |
| start) |
| start |
| ;; |
| stop) |
| stop |
| ;; |
| monitor) |
| monitor |
| ;; |
| validate-all) |
| ;; |
| *) |
| usage |
| exit $OCF_ERR_UNIMPLEMENTED |
| ;; |
| esac |
| |
| exit $? |