Advanced usage and scheduling notes
===================================

//////////////////////////////////////////////////////////////////////////////
// You can find a rendered variant of this document on the web at
// https://networkupstools.org/docs/user-manual.chunked/Advanced_usage_scheduling_notes.html
//////////////////////////////////////////////////////////////////////////////

upsmon can call out to a helper script or program when the device changes
state.  The example upsmon.conf has a full list of which state changes
are available -- ONLINE, ONBATT, LOWBATT, and more.

There are two options, that will be presented in details:

- the simple approach: create your own helper, and manage all events and actions
yourself,
- the advanced approach: use the NUT provided helper, called 'upssched'.


The simple approach, using your own script
------------------------------------------

How it works relative to upsmon
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Your command will be called with the full text of the message as one argument.

For the default values, refer to the sample upsmon.conf file.

The environment string NOTIFYTYPE will contain the type string of whatever
caused this event to happen -- ONLINE, ONBATT, LOWBATT, ...

Making this some sort of shell script might be a good idea, but the helper can
be in any programming or scripting language.

NOTE: Remember that your helper must be *executable*. If you are using a script,
make sure the execution flags are set.

For more information, refer to linkman:upsmon[8] and
linkman:upsmon.conf[5] manual pages.

Setting up everything
~~~~~~~~~~~~~~~~~~~~~

- Set EXEC flags on various things in linkman:upsmon.conf[5]:
+
	NOTIFYFLAG ONBATT EXEC
	NOTIFYFLAG ONLINE EXEC
+
If you want other things like WALL or SYSLOG to happen, just add them:
+
	NOTIFYFLAG ONBATT EXEC+WALL+SYSLOG
+
You get the idea.

- Tell upsmon where your script is

	NOTIFYCMD /path/to/my/script

- Make a simple script like this at that location:

	#! /bin/bash
	echo "$*" | sendmail -F"ups@mybox" bofh@pager.example.com

- Restart upsmon, pull the plug, and see what happens.

That approach is bare-bones, but you should get the text content of the
alert in the body of the message, since upsmon passes the alert text
(from NOTIFYMSG) as an argument.

Using more advanced features
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Your helper script will be run with a few environment variables set.

- UPSNAME: the name of the system that generated the change.
+
This will be one of your identifiers from the MONITOR lines in upsmon.conf.

- NOTIFYTYPE: this will be ONLINE, ONBATT, or whatever event took place which
made upsmon call your script.

You can use these to do different things based on which system has
changed state.  You could have it only send pages for an important
system while totally ignoring a known trouble spot, for example.

Suppressing notify storms
~~~~~~~~~~~~~~~~~~~~~~~~~

upsmon will call your script every time an event happens that has the EXEC flag
set.  This means a quick power failure that lasts mere seconds might generate a
notification storm.  To suppress this sort of annoyance, use upssched as your
NOTIFYCMD program, and configure it to call your command after a timer has
elapsed.


The advanced approach, using upssched
-------------------------------------

upssched is a helper for upsmon that will invoke commands for you at some
interval relative to a UPS event.  It can be used to send pages, mail out
notices about things, or even shut down the box early.

There will be examples scattered throughout.  Change them to suit your
pathnames, UPS locations, and so forth.

How upssched works relative to upsmon
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

When an event occurs, upsmon will call whatever you specify as a 'NOTIFYCMD'
in your upsmon.conf, if you also enable the 'EXEC' in your 'NOTIFYFLAGS'.  In
this case, we want upsmon to call upssched as the notifier, since it will
be doing all the work for us.  So, in the upsmon.conf:

	NOTIFYCMD /usr/local/ups/sbin/upssched

Then we want upsmon to actually _use_ it for the notify events, so again
in the upsmon.conf we set the flags:

	NOTIFYFLAG ONLINE SYSLOG+EXEC
	NOTIFYFLAG ONBATT SYSLOG+WALL+EXEC
	NOTIFYFLAG LOWBATT SYSLOG+WALL+EXEC
	... and so on.

For the purposes of this document I will only use those three, but you can set
the flags for any of the valid notify types.

Setting up your upssched.conf
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Once upsmon has been configured with the NOTIFYCMD and EXEC flags, you're
ready to deal with the upssched.conf details.  In this file, you specify
just what will happen when a given event occurs on a particular UPS.

First you need to define the name of the script or program that will
handle timers that trigger.  This is your CMDSCRIPT, and needs to be above
any AT defines.  There's an example provided with the program, so we'll
use that here:

	CMDSCRIPT /usr/local/ups/bin/upssched-cmd

Then you have to define the variables PIPEFN and LOCKFN; the former
sets the file name of the FIFO that will pass communications between
processes to start and stop timers, while the latter sets the file name
for a temporary file created by upssched in order to avoid a race condition
under some circumstances. Please see the relevant comments in upssched.conf
for additional information and advice about these variables.

Now you can tell your CMDSCRIPT what to do when it is called by upsmon.

The big picture
^^^^^^^^^^^^^^^

The design in a nutshell is:

	upsmon ---> calls upssched ---> calls your CMDSCRIPT

Ultimately, the CMDSCRIPT does the actual useful work,  whether that's
initiating an early shutdown with 'upsmon -c fsd', sending a page by
calling sendmail, or opening a subspace channel to V'ger.

Establishing timers
^^^^^^^^^^^^^^^^^^^

Let's say that you want to receive a notification when any UPS has been running
on battery for 30 seconds.  Create a handler that starts a 30 second timer
for an ONBATT condition.

	AT ONBATT * START-TIMER onbattwarn 30

This means "when any UPS (the *) goes on battery, start a timer called
onbattwarn that will trigger in 30 seconds".  We'll come back to the
onbattwarn part in a moment.  Right now we need to make sure that we
don't trigger that timer if the UPS happens to come back before the
time is up.  In essence, if it goes back on line, we need to cancel it.
So, let's tell upssched that.

	AT ONLINE * CANCEL-TIMER onbattwarn

NOTE: Timers are pure in-memory mechanisms, specific to upssched.  Conversely
to other mechanisms found in NUT, such as upsmon->POWERDOWNFLAG, there is no
file created on the filesystem.

Executing commands immediately
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

As an example, consider the scenario where a UPS goes onto battery power.
However, the users are not informed until 30 seconds later -- using a timer as
described above. Whilst this may let the *logged in* users know that the UPS
is on battery power, it does not inform any users subsequently logging in. To
enable this we could, at the same time, create a file which is read and
displayed to any user trying to login whilst the UPS is on battery power. If
the UPS comes back onto utility power within 30 seconds, then we can cancel
the timer and remove the file, as described above. However, if the UPS comes
back onto utility power say 5 minutes later then we do not want to use any
timers but we still want to remove the file. To do this we could use:

	AT ONLINE * EXECUTE ups-back-on-power

This means that when upsmon detects that the UPS is back on utility power it
will signal upssched. Upssched will see the above command and simply pass
'ups-back-on-power' as an argument directly to CMDSCRIPT. This occurs
immediately, there are no timers involved.

Writing the command script handler
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

OK, now that upssched knows how the timers are supposed to work, let's
give it something to do when one actually triggers.  The name of the
example timer is onbattwarn, so that's the argument that will be passed
into your CMDSCRIPT when it triggers.  This means we need to do some
shell script writing to deal with that input.

--------------------------------------------------------------------------------

	#! /bin/sh

	case $1 in
		onbattwarn)
			# Send a notification mail
			echo "The UPS has been on battery for awhile" \
			| mail -s"UPS monitor" bofh@pager.example.com
			# Create a flag-file on the filesystem, for your own processing
			/usr/bin/touch /some/path/ups-on-battery
			;;
		ups-back-on-power)
			# Delete the flag-file on the filesystem
			/bin/rm -f /some/path/ups-on-battery
			;;
		*)
			logger -t upssched-cmd "Unrecognized command: $1"
			;;
	esac

--------------------------------------------------------------------------------

This is a very simple script example, but it shows how you can test for
the presence of a given trigger.  With multiple ATs creating various timer
names, you will need to test for each possibility and handle it according
to your desires.

NOTE: You can invoke just about anything from inside the CMDSCRIPT.  It doesn't
need to be a shell script, either -- that's just an example.  If you want to
write a program that will parse argv[1] and deal with the  possibilities, that
will work too.


Early Shutdowns
~~~~~~~~~~~~~~~

One thing that gets requested a lot is early shutdowns in upsmon.  With
upssched, you can now have this functionality.  Just set a timer for some
length of time at ONBATT which will invoke a shutdown command if it elapses.
Just be sure to cancel this timer if you go back ONLINE before then.

The best way to do this is to use the upsmon callback feature.  You can
make upsmon set the "forced shutdown" (FSD) flag on the upsd so your
secondary systems shut down early too.  Just do something like this
in your CMDSCRIPT:

	/sbin/upsmon -c fsd

NOTE: The path to `upsmon` must be provided. The default for an installation
built from sources is `/usr/local/ups` (so `/usr/local/ups/sbin/upsmon`),
while packaged installations will generally comply to
link:http://refspecs.linuxfoundation.org/fhs.shtml[FHS -- Filesystem Hierarchy Standard]
(so `/sbin/upsmon`).

It's not a good idea to call your system's shutdown routine directly
from the CMDSCRIPT, since there's no synchronization with the secondary
systems hooked to the same UPS.  FSD is the primary's way of saying
"we're shutting down *now* like it or not, so you'd better get ready".


Background
~~~~~~~~~~

This program was written primarily to fulfill the requests of users for
the early shutdown scenario.  The "outboard" design of the program
(relative to upsmon) was intended to reduce the load on the average
system.  Most people don't have the requirement of shutting down after n
seconds on battery, since the usual OB+LB testing is sufficient.

This program was created separately so those people don't have to spend
CPU time and RAM on something that will never be used in their
environments.

The design of the timer handler is also geared towards minimizing impact.
It will come and go from the process list as necessary.  When a new timer
is started, a process will be forked to actually watch the clock and
eventually start the CMDSCRIPT.  When a timer triggers, it is removed from
the queue.  Canceling a timer will also remove it from the queue.  When
no timers are present in the queue, the background process exits.

This means that you will only see upssched running when one of two things
is happening:

 1. There's a timer of some sort currently running
 2. upsmon just called it, and you managed to catch the brief instance

The final optimization handles the possibility of trying to cancel a timer
when there's none running.  If there's no process already running, there
are no timers to cancel, and furthermore there is no need to start a
clock-watcher.  As a result, it skips that step and exits sooner.
