2 # ---------------------------------------------------------------
3 # Copyright (C) 2008-2013 Georgia Public Library Service
4 # Copyright (C) 2013 Equinox Software, Inc
5 # Bill Erickson <berick@esilibrary.com>
7 # This program is free software; you can redistribute it and/or
8 # modify it under the terms of the GNU General Public License
9 # as published by the Free Software Foundation; either version 2
10 # of the License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 # ---------------------------------------------------------------
17 use strict; use warnings;
19 use Net::Domain qw/hostfqdn/;
20 use POSIX qw/setsid :sys_wait_h/;
21 use OpenSRF::Utils::Logger q/$logger/;
23 use OpenSRF::Transport::PeerHandle;
24 use OpenSRF::Utils::SettingsClient;
25 use OpenSRF::Transport::Listener;
27 use OpenSRF::Utils::Config;
29 my $opt_service = undef;
30 my $opt_config = "@CONF_DIR@/opensrf_core.xml";
31 my $opt_pid_dir = "@PID_DIR@/run/opensrf";
32 my $opt_no_daemon = 0;
33 my $opt_settings_pause = 0;
34 my $opt_localhost = 0;
36 my $opt_shutdown_graceful = 0;
37 my $opt_shutdown_fast = 0;
38 my $opt_shutdown_immediate = 0;
39 my $opt_shutdown_graceful_all = 0;
40 my $opt_shutdown_fast_all = 0;
41 my $opt_shutdown_immediate_all = 0;
42 my $opt_kill_with_fire = 0;
43 my $opt_signal = ''; # signal name
44 my $opt_signal_all = 0;
45 my $opt_signal_timeout = 30;
49 my $opt_start_all = 0;
51 my $opt_restart_all = 0;
52 my $opt_force_clean_process = 0;
57 my $hostname = $ENV{OSRF_HOSTNAME} || hostfqdn();
60 'service=s' => \$opt_service,
61 'config=s' => \$opt_config,
62 'pid-dir=s' => \$opt_pid_dir,
63 'no-daemon' => \$opt_no_daemon,
64 'settings-startup-pause=i' => \$opt_settings_pause,
65 'localhost' => \$opt_localhost,
67 'quiet' => \$opt_quiet,
68 'graceful-shutdown' => \$opt_shutdown_graceful,
69 'fast-shutdown' => \$opt_shutdown_fast,
70 'immediate-shutdown' => \$opt_shutdown_immediate,
71 'graceful-shutdown-all' => \$opt_shutdown_graceful_all,
72 'fast-shutdown-all' => \$opt_shutdown_fast_all,
73 'immediate-shutdown-all' => \$opt_shutdown_immediate_all,
74 'kill-with-fire' => \$opt_kill_with_fire,
75 'force-clean-process' => \$opt_force_clean_process,
76 'signal-timeout' => \$opt_signal_timeout,
77 'signal=s' => \$opt_signal,
78 'signal-all' => \$opt_signal_all,
79 'start' => \$opt_start,
81 'start-all' => \$opt_start_all,
82 'stop-all' => \$opt_stop_all,
83 'restart' => \$opt_restart,
84 'restart-all' => \$opt_restart_all
88 $hostname = 'localhost';
89 $ENV{OSRF_HOSTNAME} = $hostname;
92 my $C_COMMAND = "opensrf-c -c $opt_config -x opensrf -p $opt_pid_dir -h $hostname";
93 my $PY_COMMAND = "opensrf.py -f $opt_config -p $opt_pid_dir ". ($opt_localhost ? '-l' : '');
99 my @pids = get_service_pids_from_file($service);
102 # no PID files exist. see if the service is running anyway
104 @pids = get_service_pids_from_ps($service);
106 msg("cannot signal $service : no pid file or running process");
111 for my $pid (@pids) {
112 if (kill($signal, $pid) == 0) { # no process was signaled.
113 msg("cannot signal $service: process $pid is not running");
114 my $pidfile = get_pid_file($service);
115 unlink $pidfile if $pidfile;
119 msg("sending $signal signal to pid=$pid $service");
125 # returns 2 if a process should have gone away but did not
126 # in the case of multiple PIDs (e.g. router), return the
127 # status of any failures, but not the successes.
130 my @pids = get_service_pids_from_file($service);
133 for my $pid (@pids) {
135 # to determine whether a process has died, we have to send
136 # a no-op signal to the PID and check the success of that signal
138 for my $i (1..$opt_signal_timeout) {
139 $sig_count = kill(0, $pid);
140 last unless $sig_count;
145 msg("timed out waiting on $service pid=$pid to die");
150 # cleanup successful. remove the PID file
151 my $pidfile = get_pid_file($service);
152 unlink $pidfile if $pidfile;
160 return "$opt_pid_dir/$service.pid";
163 # services usually only have 1 pid, but the router will have at least 2
164 sub get_service_pids_from_file {
166 my $pid_file = get_pid_file($service);
167 return () unless -e $pid_file;
168 my @pids = `cat $pid_file`;
169 s/^\s*|\n//g for @pids;
173 sub get_service_pids_from_ps {
176 my $ps = ($service eq 'router') ?
177 "ps ax | grep 'OpenSRF Router'" :
178 "ps ax | grep 'OpenSRF Listener \\[$service\\]'";
180 $ps .= " | grep -v grep | sed 's/^\\s*//' | cut -d' ' -f1";
182 s/^\s*|\n//g for @pids;
189 sub do_start_router {
190 `opensrf_router $opt_config routers`;
192 sleep 2; # give the router time to fork
193 my @pids = `ps -C opensrf_router -o pid=`;
194 s/^\s*|\n//g for @pids;
196 my $pidfile = get_pid_file('router');
197 open(PF, '>', $pidfile) or die "Cannot open $pidfile: $!\n";
200 msg("starting service pid=$_ router");
206 # stop a specific service
208 my ($service, @signals) = @_;
209 @signals = qw/TERM INT KILL/ unless @signals;
210 for my $sig (@signals) {
211 last unless do_signal($service, $sig) == 2;
217 OpenSRF::System->bootstrap_client(config_file => $opt_config);
218 die "Unable to bootstrap client for requests\n"
219 unless OpenSRF::Transport::PeerHandle->retrieve;
221 load_settings(); # load the settings config if we can
223 my $sclient = OpenSRF::Utils::SettingsClient->new;
224 my $apps = $sclient->config_value("activeapps", "appname");
226 # disconnect the top-level network handle
227 OpenSRF::Transport::PeerHandle->retrieve->disconnect;
230 $apps = [$apps] unless ref $apps;
231 for my $app (@$apps) {
232 if (!$sclient->config_value('apps', $app)) {
233 msg("Service '$app' is listed for this host, ".
234 "but there is no configuration for it in $opt_config");
237 my $lang = $sclient->config_value('apps', $app, 'language') || '';
238 if ($lang =~ /perl/i) {
239 push(@perl_services, $app);
241 push(@nonperl_services, {service => $app, lang => $lang});
248 # start a specific service
252 my @pf_pids = get_service_pids_from_file($service);
253 my @ps_pids = get_service_pids_from_ps($service);
255 if (@pf_pids) { # had pidfile
258 msg("service $service already running : @ps_pids");
261 } else { # stale pidfile
263 my $pidfile = get_pid_file($service);
264 msg("removing stale pid file $pidfile");
268 } elsif (@ps_pids) { # orphan process
270 if ($opt_force_clean_process) {
271 msg("service $service pid=@ps_pids is running with no pidfile");
272 do_signal($service, 'KILL');
274 msg("service $service pid=@ps_pids is running with no pidfile! ".
275 "use --force-clean-process to automatically kill orphan processes");
280 return do_start_router() if $service eq 'router';
282 load_settings() if $service eq 'opensrf.settings';
284 if(grep { $_ eq $service } @perl_services) {
285 return unless do_daemon($service);
286 OpenSRF::System->run_service($service, $opt_pid_dir);
289 # note: we don't daemonize non-perl services, but instead
290 # assume the controller for other languages manages that.
291 my ($svc) = grep { $_->{service} eq $service } @nonperl_services;
293 if ($svc->{lang} =~ /c/i) {
294 system("$C_COMMAND -a start -s $service");
296 } elsif ($svc->{lang} =~ /python/i) {
297 system("$PY_COMMAND -a start -s $service");
303 msg("$service is not configured to run on $hostname");
308 msg("starting all services for $hostname");
311 if(grep {$_ eq 'opensrf.settings'} @perl_services) {
312 do_start('opensrf.settings');
313 # in batch mode, give opensrf.settings plenty of time to start
314 # before any non-Perl services try to connect
315 sleep $opt_settings_pause if $opt_settings_pause;
318 # start Perl services
319 for my $service (@perl_services) {
320 do_start($service) unless $service eq 'opensrf.settings';
323 # start each non-perl service individually instead of using the native
324 # start-all command. this allows us to test for existing pid files
325 # and/or running processes on each service before starting.
326 # it also means each service has to connect-fetch_setting-disconnect
327 # from jabber, which makes startup slightly slower than native start-all
328 do_start($_->{service}) for @nonperl_services;
333 # signal a single service
337 return do_signal_all($signal, $service);
340 # returns the list of running services based on presence of PID files.
341 # the 'router' service is not included by deault, since it's
342 # usually treated special.
343 sub get_service_list_from_files {
344 my $include_router = shift;
345 my @services = `ls $opt_pid_dir/*.pid 2> /dev/null`;
346 s/^\s*|\n//g for @services;
347 s|.*/(.*)\.pid$|$1| for @services;
348 return @services if $include_router;
349 return grep { $_ ne 'router' } @services;
353 my ($signal, @services) = @_;
354 @services = get_service_list_from_files() unless @services;
356 do_signal_send($_, $signal) for @services;
358 # if user passed a know non-shutdown signal, we're done.
359 return if $signal =~ /HUP|USR1|USR2/;
361 do_signal_wait($_) for @services;
364 # pull all opensrf listener and drone PIDs from 'ps',
365 # kill them all, and remove all pid files
366 sub do_kill_with_fire {
367 msg("killing with fire");
369 my @pids = get_running_pids();
371 next unless $_ =~ /\d+/;
372 my $proc = `ps -p $_ -o cmd=`;
374 msg("killing with fire pid=$_ $proc");
378 # remove all of the pid files
379 my @files = `ls $opt_pid_dir/*.pid 2> /dev/null`;
380 s/^\s*|\n//g for @files;
382 msg("removing pid file $_");
387 sub get_running_pids {
390 # start with the listeners, then drones, then routers
392 "ps ax | grep 'OpenSRF Listener' ",
393 "ps ax | grep 'OpenSRF Drone' ",
394 "ps ax | grep 'OpenSRF Router' "
397 $_ .= "| grep -v grep | sed 's/^\\s*//' | cut -d' ' -f1" for @greps;
399 for my $grep (@greps) {
401 s/^\s*|\n//g for @spids;
402 push (@pids, @spids);
408 sub clear_stale_pids {
409 my @pidfile_services = get_service_list_from_files(1);
410 my @running_pids = get_running_pids();
412 for my $svc (@pidfile_services) {
413 my @pids = get_service_pids_from_file($svc);
414 for my $pid (@pids) {
415 next if grep { $_ eq $pid } @running_pids;
416 my $pidfile = get_pid_file($svc);
417 msg("removing stale pid file $pidfile");
426 msg("stopping all services for $hostname");
428 my @services = get_service_list_from_files();
429 @signals = qw/TERM INT KILL/ unless @signals;
431 for my $signal (@signals) {
434 # send the signal to all PIDs
435 do_signal_send($_, $signal) for @services;
437 # then wait for them to go away
438 for my $service (@services) {
439 push(@redo, $service) if do_signal_wait($service) == 2;
443 last unless @services;
446 # graceful shutdown requires the presence of the router, so stop the
447 # router last. See if it's running first to avoid unnecessary warnings.
448 do_stop('router', $signals[0]) if get_service_pids_from_file('router');
453 # daemonize us. return true if we're the child, false if parent
455 return 1 if $opt_no_daemon;
457 my $pid_file = get_pid_file($service);
458 #exit if OpenSRF::Utils::safe_fork();
459 return 0 if OpenSRF::Utils::safe_fork();
460 msg("starting service pid=$$ $service");
466 open STDIN, '</dev/null';
467 open STDOUT, '>/dev/null';
468 open STDERR, '>/dev/null';
469 `echo $$ > $pid_file`;
473 # parses the local settings file
475 my $conf = OpenSRF::Utils::Config->current;
476 my $cfile = $conf->bootstrap->settings_config;
477 return unless $cfile;
478 my $parser = OpenSRF::Utils::SettingsParser->new();
479 $parser->initialize( $cfile );
480 $OpenSRF::Utils::SettingsClient::host_config =
481 $parser->get_server_config($conf->env->hostname);
486 print "* $m\n" unless $opt_quiet;
492 Usage: perl $0 --pid-dir @TMP@ --config @CONF_DIR@/opensrf_core.xml --service opensrf.settings --start
494 --config <file> [default: @CONF_DIR@/opensrf_core.xml]
495 OpenSRF configuration file
497 --pid-dir <dir> [default: @PID_DIR@/run/opensrf]
498 Directory where process-specific PID files are kept
500 --settings-startup-pause
501 How long to give the opensrf.settings server to start up when running
502 in batch mode (start_all). The purpose is to give plenty of time for
503 the settings server to be up and active before any non-Perl services
507 Force the hostname to be 'localhost', instead of the fully qualified
508 domain name for the machine.
511 Specifies which OpenSRF service to control
514 Do not print informational messages to STDOUT
517 Do not detach and run as a daemon process. Useful for debugging.
518 Only works for Perl services and only when starting a single service.
521 Print this help message
523 ==== starting services =====
526 Start the router and all services
529 Start the service specified by --service
532 Restart the router and all services
535 Restart the service specified by --service
537 --force-clean-process
538 When starting a service, if a service process is already running
539 but no pidfile exists, kill the service process before starting
542 ==== stopping services =====
545 Stop the router and all services. Services are sent the TERM signal,
546 followed by the INT signal, followed by the KILL signal. With each
547 iteration, the script pauses up to --signal-timeout seconds waiting
548 for each process to die before sending the next signal.
551 Stop the service specified by --service. See also --stop-all.
552 If the requested service does not have a matching PID file, an
553 attempt to locate the PID via 'ps' will be made.
555 --graceful-shutdown-all
556 Send TERM signal to all services + router
559 Send TERM signal to the service specified by --service
562 Send INT signal to all services + router
565 Send INT signal to the service specified by --service
567 --immediate-shutdown-all
568 Send KILL signal to all services + router
571 Send KILL signal to the service specified by --service
574 Send KILL signal to all running services + routers, regardless of
575 the presence of a PID file, and remove all PID files indiscriminately.
577 ==== signaling services =====
580 Send signal to all services
583 Name of signal to send. If --signal-all is not specified, the
584 signal will be sent to the service specified by --service.
587 Seconds to wait for a process to die after sending a shutdown signal.
588 All signals except HUP, USR1, and USR2 are assumed to be shutdown signals.
595 do_init() and do_start($opt_service) if $opt_start;
596 do_init() and do_stop($opt_service) and do_start($opt_service) if $opt_restart;
597 do_init() and do_start_all() if $opt_start_all;
598 do_init() and do_stop_all() and do_start_all() if $opt_restart_all;
601 do_stop($opt_service) if $opt_stop;
602 do_stop_all() if $opt_stop_all;
603 do_stop($opt_service, 'TERM') if $opt_shutdown_graceful;
604 do_stop($opt_service, 'INT') if $opt_shutdown_fast;
605 do_stop($opt_service, 'KILL') if $opt_shutdown_immediate;
606 do_stop_all('TERM') if $opt_shutdown_graceful_all;
607 do_stop_all('INT') if $opt_shutdown_fast_all;
608 do_stop_all('KILL') if $opt_shutdown_immediate_all;
609 do_kill_with_fire() if $opt_kill_with_fire;
612 do_signal($opt_service, $opt_signal) if $opt_signal;
613 do_signal_all($opt_signal) if $opt_signal_all;
615 # show help if no action was requested
616 do_help() if $opt_help or not (
625 $opt_shutdown_graceful or
626 $opt_shutdown_graceful_all or
627 $opt_shutdown_fast or
628 $opt_shutdown_fast_all or
629 $opt_shutdown_immediate or
630 $opt_shutdown_immediate_all or