ZabbixFanControl

11th May 2016 at 10:36pm
Bash CodeSnippets Expect Zabbix

I have / had a problem of servers in my rack overheating. The permanent solution is of course to make the effort to get the portable air conditioner out of my storage unit and set it up in my computer room [box room]. However, that requires a bunch of physical effort.

The short term solution is to get my Zabbix monitoring server to turn on a an 18" floor standing fan whenever the ambient temperature inside the rack is substantially higher than the ambient temperature of the room, or when disk temperatures exceed 40C.

...so welcome to temporary janky solution number 1...

Zabbix has a trigger action which will touch /tmp/fan.on whenever the prerequisite temperature conditions occur.

A script run out of the crontab then takes care of the rest.

Crontab

*/3 * * * * /home/nicolaw/bin/fan.cron 2>&1 | tee -a /tmp/fan.cron.log

~/bin/fan.cron

#!/bin/bash

set -uE -o pipefail

ACTION_FILE="/tmp/fan"
STATE_LOG="/tmp/fan.log"
STALE_SECS=3600
FAN_CMD="/home/nicolaw/bin/fan"

last_known_state() {
  if grep -A 99 "Command successfully issued." "$STATE_LOG" 2>/dev/null \
      | grep -qo "State\s*:\s*ON" ; then
    echo "on"
  elif grep -A 99 "Command successfully issued." "$STATE_LOG" 2>/dev/null \
      | grep -qo "State\s*:\s*OFF" ; then
    echo "off"
  else
    echo "UNKNOWN"
    return 1
  fi
  return 0
} 

is_integer() {
  local str="$1"
  if [[ "$str" =~ ^[0-9]+$ ]] ; then
    return 0
  fi
  return 1
}

file_age() {
  local file="$1"
  local now="$(date +%s)"
  local mtime="$(stat -c %Y "$file" 2>/dev/null)"
  is_integer "$mtime" || mtime="-1"
  if is_integer "$now" ; then
    echo "$(( now - mtime ))"
    return 0
  fi
  return 1
}

is_stale() {
  local file="$1"
  local maxage="${2:-$STALE_SECS}"
  local fileage="$(file_age "$file")"
  if [[ $fileage -ge $maxage ]] ; then
    return 0
  fi
  return 1
}

fan_toggle() {
  local action="$1"
  local reason="${2:-}"
  echo -n "$(date +"%Y-%m-%d %H:%M:%S%z") Turning fan $action ${reason:+($reason) }..."
  $FAN_CMD $action > "$STATE_LOG" 2>&1
  if [[ "$(last_known_state)" == "$action" ]] ; then
    echo " DONE"
    return 0
  fi
  echo " FAILED"
  return 1
}

# Remove stale instructions
for file in $ACTION_FILE.{on,off} ; do
  [[ -e "$file" ]] && is_stale "$file" && rm -f "$file"
done

# Determine our action
ACTION=""
if [[ -e "${ACTION_FILE}.off" ]] || [[ -e "${ACTION_FILE}.on" ]] ; then
  ACTION="off"
  if [[ $(file_age "${ACTION_FILE}.on") -lt $(file_age "${ACTION_FILE}.off") ]] ; then
    ACTION="on"
  fi
fi

# Perform explicit action
if [[ -n "$ACTION" ]] ; then
  fan_toggle $ACTION "$(cat "${ACTION_FILE}.$ACTION")"; rc=$?
  [[ $rc -eq 0 ]] && rm -f "${ACTION_FILE}".{on,off}
  exit $rc

# State log is stale and fan probably still on; turn the fan off
elif  [[ -e "$STATE_LOG" ]] && is_stale "$STATE_LOG" && \
      [[ "$(last_known_state)" == "on" ]] ; then
  fan_toggle off "$STALE_SECS timeout reached"
  exit $?

# Nothing to do
else
  exit 0
fi

exit 1

~/bin/fan

#!/usr/bin/expect

set timeout 60

if {[llength $argv] != 1} {
  send_user "Usage: fan <on|off> \n"
  exit 1
}
set onoff [lindex $argv 0]
set username "my_username"
set password "my_password"
set hostname "pdu.hostname.fqdn"
set outlet "1"
set offstring "off"
set state "1"
if { [string compare $onoff  $offstring] == 0 } {
  set state "2"
}

spawn ssh -p 22 -4 -o PreferredAuthentications=password -o PasswordAuthentication=yes -o Ciphers=aes128-cbc,aes256-cbc,3des-cbc,blowfish-cbc -o MACs=hmac-sha1,hmac-md5 -o KexAlgorithms=diffie-hellman-group1-sha1 -l $username $hostname

expect "password:" { send "$password\r" }

# device manager
expect "> " { send "1\r" }

# outlet control
expect "> " { send "2\r" }

# outlet number
expect "> " { send "$outlet\r" }

# state
expect "> " { send "$state\r" }

# control outlet
expect "to cancel : " { send "YES\r" }

# continue, return and logout
expect "to continue..." { send "\r" }
expect "> " { send \033 }
expect "> " { send \033 }
expect "> " { send \033 }
expect "> " { send "4\r" }