Gestionando un SAI desde OpenBSD con NUT

Historia

3 Agosto 2005: primera versión pública

Índice

Introducción

Este documento es una pequeña receta para configurar un SAI PowerMust 600 Plus en OpenBSD empleando NUT.

En primer lugar hay que quitar de /etc/boot.conf la linea que permite ver el arranque en el puerto serie (si existe), así como la posible configuración en /etc/ttys que nos da una terminal serie, sino no podremos controlar el SAI desde ahí. Esto es habitual en servidores sin teclado ni monitor, así que cuidado.

En caso de tener alguna configuración de las descritas, reiniciamos el servidor para poder usar el interfaz por el puerto RS-232 con el protocolo smart APC (según la documentacuión), sin que interfiera la configuración anterior, y nos ponemos a mirar las opciones que tenemos en OpenBSD.

Instalación de NUT

El SAI es un YUKAI PowerMust 600 Plus (600VA/360W), una opción económica y suficiente para mis necesidades. En realidad hice una mala compra, porque no comprobé compatibilidades :(. Menos mal que ha habido algo de suerte (poca).

En los ports (OpenBSD 3.6) tenemos upsd, apc-upsd y NUT. Todos productos soportan el procotolo que emplea el SAI (aparentemente), aunque solo he encontrado una página web actualizada para NUT, y además resulta que los fabricantes introducen variaciones en el protocolo... así que necesitamos un manejador específico.

Como NUT soporta mi SAI con la versión 2.0.1 y posteriores (con el driver para PowerMust 600VA Plus), pero en los ports está una versión vieja, me toca actualizar el port a mano o aplicar el parche para mi versión de NUT. He elegido esta segunda opción.

# export CVSROOT=anoncvs@anoncvs3.usa.openbsd.org:/cvs
# export CVS_RSH=/usr/bin/ssh
# cd /usr
# mkdir -p ports/sysutils
# cvs -z -q get -rOPENBSD_3_6 ports/sysutils/nut
cvs server: Updating ports/sysutils/nut
...
# cd ports/sysutils/nut/
# wget http://students.fct.unl.pt/~cer09566/nut/files/nut-1.4.2-powermust0.9.patch
# FLAVOR=no_cgi make patch
...
# cd w-nut-1.4.1-no_cgi/nut-1.4.1/
# patch -p1 < ../../nut-1.4.2-powermust0.9.patch
...
# cd ../..
(editamos pkg/PLIST y añadimos bin/powermust detrás de bin/powercom)
# FLAVOR=no_cgi make package
...

He resumido la salida de los distintos comandos, espero que aún así sea claro :P.

Al instalar el paquete nos indica que ha creado el usuario _nut, dónde ha dejado los ficheros de configuración (en /etc/nut/) y un ejemplo para poner el arranque en /etc/rc.local.

Configuración de NUT

Ahora voy a describir los pasos habituales para configurar NUT.

Necesitamos iniciar el manejador del SAI, el demonio que obtiene la información del manejador, y un monitor que realize acciones cuando haga falta (nos quedamos sin batería).

Editamos /etc/nut/ups.conf y ponemos los datos del SAI:

[bsups]
        driver=powermust
        port=/dev/tty00
        desc="Blackshell"

Comprobamos que el SAI se detecta correctamente:

# upsdrvctl -u _ups start
Network UPS Tools - UPS driver controller 1.4.1
Network UPS Tools - Mustek PowerMust UPS driver 0.9 (1.4.1)
Carlos Rodrigues (c) 2003, 2004

Serial port read timed out
Serial port read ok again
Mustek PowerMust UPS detected.

Echamos un vistazo /etc/nut/upsd.conf, aunque por defecto solo permite el acceso a localhost así que en un principio no hace falta tocar nada.

Añadimos un usuario a /etc/nut/upsd.users para posterior uso con upsmon:

[muser]
        password=pass
        allowfrom=localhost
        upsmon master

Preparamos los ficheros de configuración para el usuario que correrá upsd (en el port tiene el grupo dialer), y arrancamos el demonio:

# chown root:dialer /etc/nut/upsd.conf /etc/nut/upsd.users
# chmod 640 /etc/nut/upsd.conf /etc/nut/upsd.users
# upsd -u _ups
Network UPS Tools upsd 1.4.1
Connected to UPS [bsups]: powermust-cua00
Synchronizing...done

Ahora ya podemos empezar a hacer preguntas:

# upsc bsups@localhost
battery.charge: 100.0
battery.voltage: 13.8
battery.voltage.nominal: 12.0
driver.name: powermust
driver.parameter.port: /dev/cua00
driver.version: 1.4.1
driver.version.internal: 0.9
input.voltage: 230.5
input.voltage.fault: 230.0
input.voltage.maximum: 232.5
input.voltage.minimum: 223.0
output.frequency: 49.7
output.voltage: 230.5
output.voltage.target.battery: 230.0
ups.delay.shutdown: 2
ups.delay.start: 3
ups.load: 4.0
ups.mfr: Mustek
ups.model: PowerMust
ups.status: OL

Perfecto. Esta información es útil, pero lo que estoy buscando es que el servidor se apague cuando el SAI se quede sin baterias. Para eso necesitamos upsmon.

Su configuración está en /etc/nut/upsmon.conf y tendremos que añadir una linea para nuestro SAI:

MONITOR bsups@localhost 1 muser pass master

El resto se puede quedar por defecto, destacando SHUTDOWNCMD para indicar qué comando ejecutar para detener la máquina (sin apagarla, de eso se debe encargar el SAI, cuidado con no tener activado powerdown en nuestro /etc/rc.shutdown) .

En este punto es interesante configurar algunas notificaciones, para que se nos avise de los eventos más importantes.

Ponemos en /etc/nut/upsmon.conf la variable NOTIFYCMD a /usr/local/bin/upsnotify. Creamos un script tal que:

#!/bin/sh
# recordar hacer chmod +x
echo ${NOTIFYTYPE}: $* | mail -s "$UPSNAME NUT Notify" reidrac

Añadimos a /etc/nut/upsmon.conf las notificaciones que creamos convenientes e indicamos vía NOTIFYFLAG cómo deben informarnos:

NOTIFYMSG ONBATT "%s is on battery"
NOTIFYMSG ONLINE "%s is back online"
NOTIFYMSG LOWBATT "%s has a low battery!"
NOTIFYMSG SHUTDOWN "System is being shutdown!"

NOTIFYFLAG ONBATT SYSLOG+EXEC
NOTIFYFLAG ONLINE SYSLOG
NOTIFYFLAG LOWBATT SYSLOG+EXEC
NOTIFYFLAG SHUTDOWN SYSLOG+EXEC

Aseguramos el fichero de configuración y arrancamos upsmon:

# chown root:dialer /etc/nut/upsmon.conf
# chmod 640 /etc/nut/upsmon.conf
# upsmon -u _ups
Network UPS Tools upsmon 1.4.1
Using power down flag file /etc/killpower

UPS: bsups@localhost (master) (power value 1)

Ahora actualizo mi /etc/rc.local como sigue:

...
# nut
if [ X"${nut_ups}" != X"NO" ]; then
        echo -n " nut"

	# dentro del arranque de nut
        # para que funcione upssched
        # y para que pueda escribir upsmon
        mkdir -p /var/run/ups
        chown _ups:dialer /var/run/ups

        /usr/local/bin/upsdrvctl -u _ups start > /dev/null
        /usr/local/sbin/upsd -i 127.0.0.1 -u _ups > /dev/null 2>&1
        /usr/local/sbin/upsmon -u _ups > /dev/null 2>&1
fi
...

Y activo NUT en /etc/rc.conf.local con nut_ups="" (con "NO" lo desactivaríamos).

Apagado del SAI

Desde la documentación se recomienda que añadamos al final de /etc/rc.shutdown algo como:

# OJO: POWERDOWNFLAG en upsmon.conf indica el fichero a comprobar
if [ -f /etc/killpower ]; then
	echo "Killing the power, bye!"
        /usr/local/bin/upsdrvctl shutdown
fi

De esta forma es nuestra máquina la que indica al SAI que debe cortar la corriente. Cuando vuelva la energía, el SAI despertará y arrancará el servidor.

Hay que tener en cuenta que nuestro SAI no espera a que acabe de cerrarse el sistema, y /etc/rc.shutdown se ejecuta con todos los discos montados normalmente y todos los procesos funcionando. Apagar así no es nada saludable.

No hay una solución fácil a este problema. Mi propuesta: sincronizamos el disco y montamos todo lo necesario en modo solo lectura.

Además hay que hacer una pequeña adaptación para que funcione el script con nuestro usuario sin privilegios, incluyendo también un hack para que el shutdown funcione. El script quedaría:

# OJO: POWERDOWNFLAG debe ser /var/run/ups/killpower
if [ -f /var/run/ups/killpower ]; then
        echo "Killing the power, bye!"

	# sino paramos la instancia en funcionamiento
	# no podemos hacer shutdown
	/usr/local/bin/upsdrvctl stop
	sleep 3

        # estabilizamos el disco (cuidado si hay más particiones)
        /bin/sync
        /sbin/umount -a
        /sbin/mount -r /
        /sbin/mount -r /var
        /sbin/mount -r /tmp
        /sbin/mount -r /usr

        /usr/local/bin/upsdrvctl shutdown
fi

Con esto se minimiza el impacto del apagado ya que el disco está sincronizado y las particiones montadas en solo lectura.

Ahora solo queda probar el invento cerrando la corriente que llega al SAI (el botón de la regleta es pefecto :D), esperamos a que se descargue la batería y volvemos a dar la corriente.

Al volver a arrancar el servidor comprobaremos que se apagó normalmente (por las notificaciones, porque no aparecerá en el dmesg el peligroso mensaje: WARNING: / was not properly unmounted, etc.).

Trabajando con upssched

Hasta aquí si todo marcha bien (que gracia, como si no fuera suficientemente complicado :D). Pero resulta que mi SAI no funciona como debería (tiene dos años ya) y no llega a dar avisos para nivel de batería baja (pitido cada 0.5 segundos). En lugar de eso, se apaga... así que upsmon no llega a hacer su trabajo adecuadamente.

Vamos a utilizar upssched para que gestione un temporizador durante 2 minutos y dispare el apagado una vez pasado ese tiempo. No es una solución óptima pero nos permitirá aguantar varios apagones de ese tiempo relativamente seguidos (sin tiempo para recuperar la batería).

Cambiamos ligeramente la configuración en /etc/nut/upsmon.conf:

NOTIFYCMD /usr/local/sbin/upssched

NOTIFYMSG ONBATT "%s is on battery"
NOTIFYMSG ONLINE "%s is back online"
NOTIFYMSG LOWBATT "%s has a low battery!"
NOTIFYMSG SHUTDOWN "System is being shutdown!"

NOTIFYFLAG ONBATT SYSLOG+EXEC
NOTIFYFLAG ONLINE SYSLOG+EXEC
NOTIFYFLAG LOWBATT SYSLOG+EXEC
NOTIFYFLAG SHUTDOWN SYSLOG+EXEC

Los cambios están en negrita. Ahora editamos /etc/nut/upssched.conf:

PIPEFN /var/run/ups/upssched.pipe
LOCKFN /var/run/ups/upssched.lock
CMDSCRIPT /usr/local/bin/upssched-cmd

AT ONBATT * START-TIMER ONBATT 120
AT ONLINE * CANCEL-TIMER ONBATT
AT LOWBATT * EXECUTE LOWBATT
AT SHUTDOWN * EXECUTE SHUTDOWN

Creamos el /usr/local/bin/upssched-cmd con el siguiente contenido:

#!/bin/sh
# recordar chmod +x

case $1 in
        ONBATT)
                echo ONBATT TIMEOUT | mail -s "UPS Notify" reidrac
                /usr/local/sbin/upsmon -c fsd
                ;;
        LOWBATT)
                echo LOWBATT | mail -s "UPS Notify" reidrac
                ;;

        SHUTDOWN)
                echo SHUTDOWN | mail -s "UPS Notify" reidrac
                ;;

        *)
                logger -t upssched-cmd "Unrecognized command: $1"
                ;;
esac

De esta forma se apagará la máquina y el SAI tras 2 minutos funcionando con la batería, cancelándose este temporizador en caso de volver la corriente en ese plazo de tiempo.

Despedida

Ahora ya puedo dejar el servidor con el piloto automático e irme de vacaciones sin miedo a que un corte prolongado de electricidad fastidie alguna pieza vital del hardware de la máquina ;).

En esta receta he cubierto la configuración habitual (más algún hack), y cómo programar apagados en caso de que nuestro SAI no se comporte adecuadamente.

Copyright © 2005 Juan J. Martinez <reidrac *at* usebox.net>
Se permite la copia textual y distribución de este documento en su totalidad,
por cualquier medio, siempre y cuando se mantenga esta nota de copyright.