Controlling Daemons
From lcfg-ngeneric version 1.6.0 onwards there is a new
Service
method interface. This makes it trivial to call an action for a service from a component, and have the action handled in a standard way, whatever init daemon is in use.
Background
The purpose of many LCFG components is to configure and control the behaviour of daemons which provide services (e.g. dns, sshd or httpd) on a machine. On Linux, the way in which daemons are controlled varies depending on which init daemon is being used. The traditional approach is that of SysVinit where each daemon has a shell script in the
/etc/init.d
directory. More recently the Upstart init daemon has been used. It supports old-style SysVinit scripts as well as providing a
service
command (this is used on SL6). The latest init daemon is Systemd which is used on Fedora and RHEL7. This uses the
systemctl
command to control services and does not support old-style init scripts.
Each individual component author has been left to write their own code to control daemons. This has resulted in a wide range of approaches. Some components have the paths to the init scripts hardwired into the code; some have them as resources. This is nearly all incompatible with use of Systemd. As well as not working on modern systems this approach clearly wastes developer time, as the same problems have to be solved repeatedly, and it probably leads to a lot of copy/paste of code and the opportunity for errors as a result.
The ideal solution is to have a single interface which solves the problem in a generic way that minimises the amount of code a component author has to write to control daemons.
Selection of init system
The selection of the init system is done on the client by examining LCFG Sysinfo resources. Each platform has a
sysinfo.init
resource with the name of the init system (e.g.
sysvinit
,
systemd
or
launchd
). For some init systems (e.g. upstart and Systemd) there is also a
sysinfo.path_init_tool
resource which gives the full path to the command (e.g.
service
or
systemctl
) which should be used to control the daemon. On platforms which support SysVinit scripts there is also a
sysinfo.path_initd
resource which gives the location of the scripts directory. Primarily these resources are designed for use in the Service method but they are intended to be more generally useful - there will be situations in which a component will need to control daemons directly or make other decisions based on the init system. These Sysinfo values can be queried in the standard way:
In Perl:
my $sysinfo = $self->GetSysInfo();
my $init = $sysinfo->get_info('init');
my $init_tool = $sysinfo->get_path('init_tool')
or in Shell:
GetSysInfo 'INIT'
GetSysPath 'INIT_TOOL'
The MacOSX platform is a special case. When the
sysinfo.init
resource is set to
launchd
the Service method will be a silent no-op. This is because it is rarely necessary or possible to signal daemons on MacOSX.
If the
sysinfo.init
resource is set to any value other than one of (
sysv
,
upstart
,
systemd
or
launchd
) an error will be thrown by the Service method.
Debugging and Error Logging
If the debug option has been set for a component then the Service method will print a debug message with details of the daemon, action, any arguments and the init daemon selected. It will look like this:
Service: '$service', Action: '$action', Args: '$args', Init: '$init'
Note that enabling debugging does
NOT disable the calling of the action it just generates additional output.
If an error occurs when the action is called, then the Service method will log (to the component log file) any output to stdout or stderr, and print an error message describing what failed. This means that it is rarely necessary to handle the output in the component code itself. Note that the method will
NOT fail - it will just log an error message. If you need to fail you should check the returned status in your component code and fail when necessary. Note that it is mostly a bad idea to fail when starting or configuring a component, because this can leave it in an unstarted state from which it is difficult to automatically recover.
Implementations
The functionality of the Service method implementations in Shell and Perl are fairly similar, with Perl having a few additional features:
Perl
The
LCFG::Component
object has a
Service
method which can be used in a method (e.g.
Configure
) like this:
my $ok = $self->Service( $daemon_name, $action, @args, \%options );
or
my ( $ok, $out, $err ) = $self->Service( $daemon_name, $action, @args, \%options );
The command requires the name of the daemon (e.g.
sshd
) and the action to be called (e.g.
stop
). The
@args
list and
%options
are optional arguments. The options hash can be used to specify a timeout in seconds after which the method will fail if the action has not completed.
If the method is called in scalar context then only the status will be returned. This will be a true value if the command succeeded and false if it failed.
If the method is called in array context, then along with the status, anything sent to stdout or stderr when the command is called will also be returned.
A few examples:
my $ok = $self->Service( 'sshd', 'reload' );
will reload the SSH daemon. Or
my ( $ok, $out, $err ) = $self->Service( 'httpd', 'stop', { timeout => 20 } );
if ( !$ok ) {
$self->Fail("Failed to stop apache");
}
will stop the apache daemon, or fail if it did not stop within 20 seconds. It also captures any output sent to stdout or stderr.
The implementation of the Service method is provided by the
LCFG::Utils::Service
module. This can be used from any Perl script (not just LCFG components) to call service methods in a platform-independent manner.
Shell
The Shell implementation does not provide a facility to capture stdout or stderr. It is called like this:
Service sshd reload
or
Service httpd stop
if [ $? -ne 0 ] ; then
Fail "Failed to stop apache"
fi
If necessary a list of command line arguments can also be passed into the Service method.
If a timeout is required then it can be specified using a
--timeout
option. For example:
Service sshd stop --timeout 10
Note that, as usual, the success of a function in Shell is indicated by an exit status of zero, and any non-zero value indicates a failure (this is the opposite of the success code returned in Perl).
When the command is executed the file-descriptors 11 and 12 (which are used in the LCFG ngeneric framework) are closed to avoid daemons keeping log files open.
The Service function is implemented using the
lcfg-service
script which is itself a wrapper around the
LCFG::Utils::Service
Perl module. That script can be used from any script or the command-line (not just LCFG components) to call service methods in a platform-independent manner.
-- Main.squinney - 2014-05-23