c0bee054070d5ed0af27a107e52664234f8c4cc7
[OpenSRF.git] / src / perlmods / OpenSRF / System.pm
1 package OpenSRF::System;
2 use strict; use warnings;
3 use OpenSRF;
4 use base 'OpenSRF';
5 use OpenSRF::Utils::Logger qw(:level);
6 use OpenSRF::Transport::Listener;
7 use OpenSRF::Transport;
8 use OpenSRF::UnixServer;
9 use OpenSRF::Utils;
10 use OpenSRF::Utils::LogServer;
11 #use OpenSRF::DOM;
12 use OpenSRF::EX qw/:try/;
13 use POSIX ":sys_wait_h";
14 use OpenSRF::Utils::Config; 
15 use OpenSRF::Utils::SettingsParser;
16 use OpenSRF::Utils::SettingsClient;
17 use OpenSRF::Application;
18 use Net::Server::PreFork;
19 use strict;
20
21 my $bootstrap_config_file;
22 sub import {
23         my( $self, $config ) = @_;
24         $bootstrap_config_file = $config;
25 }
26
27 =head2 Name/Description
28
29 OpenSRF::System
30
31 To start the system: OpenSRF::System->bootstrap();
32
33 Simple system process management and automation.  After instantiating the class, simply call
34 bootstrap() to launch the system.  Each launched process is stored as a process-id/method-name
35 pair in a local hash.  When we receive a SIG{CHILD}, we loop through this hash and relaunch
36 any child processes that may have terminated.  
37
38 Currently automated processes include launching the internal Unix Servers, launching the inbound 
39 connections for each application, and starting the system shell.
40
41
42 Note: There should be only one instance of this class
43 alive at any given time.  It is designed as a globel process handler and, hence, will cause much
44 oddness if you call the bootstrap() method twice or attempt to create two of these by trickery.
45 There is a single instance of the class created on the first call to new().  This same instance is 
46 returned on subsequent calls to new().
47
48 =cut
49
50 $| = 1;
51
52 sub DESTROY {}
53
54 # ----------------------------------------------
55
56 $SIG{INT} = sub { instance()->killall(); };
57
58 $SIG{HUP} = sub{ instance()->hupall(); };
59
60 #$SIG{CHLD} = \&process_automation;
61
62
63
64         # --- 
65         # put $instance in a closure and return it for requests to new()
66         # since there should only be one System instance running
67         # ----- 
68         my $instance;
69         sub instance { return __PACKAGE__->new(); }
70         sub new {
71                 my( $class ) = @_;
72
73                 if( ! $instance ) {
74                         $class = ref( $class ) || $class;
75                         my $self = {};
76                         $self->{'pid_hash'} = {};
77                         bless( $self, $class );
78                         $instance = $self;
79                 }
80                 return $instance;
81         }
82 }
83
84 # ----------------------------------------------
85 # Commands to execute at system launch
86
87 sub _unixserver {
88         my( $app ) = @_;
89         return "OpenSRF::UnixServer->new( '$app')->serve()";
90 }
91
92 sub _listener {
93         my( $app ) = @_;
94         return "OpenSRF::Transport::Listener->new( '$app' )->initialize()->listen()";
95 }
96
97
98 # ----------------------------------------------
99 # Boot up the system
100
101 sub load_bootstrap_config {
102
103         if(OpenSRF::Utils::Config->current) {
104                 return;
105         }
106
107         if(!$bootstrap_config_file) {
108                 die "Please provide a bootstrap config file to OpenSRF::System!\n" . 
109                         "use OpenSRF::System qw(/path/to/bootstrap_config);";
110         }
111
112         OpenSRF::Utils::Config->load( config_file => $bootstrap_config_file );
113
114         OpenSRF::Utils::JSON->register_class_hint( name => "OpenSRF::Application", hint => "method", type => "hash" );
115
116         OpenSRF::Transport->message_envelope(  "OpenSRF::Transport::SlimJabber::MessageWrapper" );
117         OpenSRF::Transport::PeerHandle->set_peer_client(  "OpenSRF::Transport::SlimJabber::PeerConnection" );
118         OpenSRF::Transport::Listener->set_listener( "OpenSRF::Transport::SlimJabber::Inbound" );
119         OpenSRF::Application->server_class('client');
120 }
121
122 sub bootstrap {
123
124         my $self = __PACKAGE__->instance();
125         load_bootstrap_config();
126         OpenSRF::Utils::Logger::set_config();
127         my $bsconfig = OpenSRF::Utils::Config->current;
128
129         # Start a process group and make me the captain
130         setpgrp( 0, 0 ); 
131         $0 = "OpenSRF System";
132
133         # -----------------------------------------------
134         # Launch the settings sever if necessary...
135         my $are_settings_server = 0;
136         if( (my $cfile = $bsconfig->bootstrap->settings_config) ) {
137                 my $parser = OpenSRF::Utils::SettingsParser->new();
138
139                 # since we're (probably) the settings server, we can go ahead and load the real config file
140                 $parser->initialize( $cfile );
141                 $OpenSRF::Utils::SettingsClient::host_config = 
142                         $parser->get_server_config($bsconfig->env->hostname);
143
144                 my $client = OpenSRF::Utils::SettingsClient->new();
145                 my $apps = $client->config_value("activeapps", "appname");
146                 if(ref($apps) ne "ARRAY") { $apps = [$apps]; }
147
148                 if(!defined($apps) || @$apps == 0) {
149                         print "No apps to load, exiting...";
150                         return;
151                 }
152
153                 for my $app (@$apps) {
154                         # verify we are a settings server and launch 
155                         if( $app eq "opensrf.settings" and 
156                                 $client->config_value("apps","opensrf.settings", "language") =~ /perl/i ) {
157
158                                 $are_settings_server = 1;
159                                 $self->launch_settings();
160                                 sleep 1;
161                                 $self->launch_settings_listener();
162                                 last;
163                         } 
164                 }
165         }
166
167         # Launch everything else
168         OpenSRF::System->bootstrap_client(client_name => "system_client");
169         my $client = OpenSRF::Utils::SettingsClient->new();
170         my $apps = $client->config_value("activeapps", "appname" );
171         if(!ref($apps)) { $apps = [$apps]; }
172
173         if(!defined($apps) || @$apps == 0) {
174                 print "No apps to load, exiting...";
175                 return;
176         }
177
178         my $server_type = $client->config_value("server_type");
179         $server_type ||= "basic";
180
181         my $con = OpenSRF::Transport::PeerHandle->retrieve;
182         if($con) {
183                 $con->disconnect;
184         }
185
186
187
188         if(  $server_type eq "prefork" ) { 
189                 $server_type = "Net::Server::PreFork"; 
190         } else { 
191                 $server_type = "Net::Server::Single"; 
192         }
193
194         _log( " * Server type: $server_type", INTERNAL );
195
196         $server_type->use;
197
198         if( $@ ) {
199                 throw OpenSRF::EX::PANIC ("Cannot set $server_type: $@" );
200         }
201
202         push @OpenSRF::UnixServer::ISA, $server_type;
203
204         _log( " * System bootstrap" );
205         
206         # --- Boot the Unix servers
207         $self->launch_unix($apps);
208
209         sleep 2;
210
211         # --- Boot the listeners
212         $self->launch_listener($apps);
213
214     sleep 1;
215
216         _log( " * System is ready..." );
217
218 #       sleep 1;
219 #       my $ps = `ps ax | grep " Open" | grep -v grep | sort -r -k5`;
220 #       print "\n --- PS --- \n$ps --- PS ---\n\n";
221
222         while( 1 ) { sleep; }
223         exit;
224 }
225         
226         
227
228 # ----------------------------------------------
229 # Bootstraps a single client connection.  
230
231 # named params are 'config_file' and 'client_name'
232 #
233 sub bootstrap_client {
234         my $self = shift;
235
236         my $con = OpenSRF::Transport::PeerHandle->retrieve;
237
238         if($con and $con->tcp_connected) {
239                 return;
240         }
241
242         my %params = @_;
243
244         $bootstrap_config_file = 
245                 $params{config_file} || $bootstrap_config_file;
246
247         my $app = $params{client_name} || "client";
248
249
250         load_bootstrap_config();
251         OpenSRF::Utils::Logger::set_config();
252         OpenSRF::Transport::PeerHandle->construct( $app );
253
254 }
255
256 sub connected {
257         if (my $con = OpenSRF::Transport::PeerHandle->retrieve) {
258                 return 1 if ($con->tcp_connected);
259         }
260         return 0;
261 }
262
263 sub bootstrap_logger {
264         $0 = "Log Server";
265         OpenSRF::Utils::LogServer->serve();
266 }
267
268
269 # ----------------------------------------------
270 # Cycle through the known processes, reap the dead child 
271 # and put a new child in its place. (MMWWAHAHHAHAAAA!)
272
273 sub process_automation {
274
275         my $self = __PACKAGE__->instance();
276
277         foreach my $pid ( keys %{$self->pid_hash} ) {
278
279                 if( waitpid( $pid, WNOHANG ) == $pid ) {
280
281                         my $method = $self->pid_hash->{$pid};
282                         delete $self->pid_hash->{$pid};
283
284                         my $newpid =  OpenSRF::Utils::safe_fork();
285
286                         OpenSRF::Utils::Logger->debug( "Relaunching $method", ERROR );
287                         _log( "Relaunching => $method" );
288
289                         if( $newpid ) {
290                                 $self->pid_hash( $newpid, $method );
291                         }
292                         else { eval $method; exit; }
293                 }
294         }
295
296         $SIG{CHLD} = \&process_automation;
297 }
298
299
300
301 sub launch_settings {
302
303         #       XXX the $self like this and pid automation will not work with this setup....
304         my($self) = @_;
305         @OpenSRF::UnixServer::ISA = qw(OpenSRF Net::Server::PreFork);
306
307         my $pid = OpenSRF::Utils::safe_fork();
308         if( $pid ) {
309                 $self->pid_hash( $pid , "launch_settings()" );
310         }
311         else {
312                 my $apname = "opensrf.settings";
313                 #$0 = "OpenSRF App [$apname]";
314                 eval _unixserver( $apname );
315                 if($@) { die "$@\n"; }
316                 exit;
317         }
318
319         @OpenSRF::UnixServer::ISA = qw(OpenSRF);
320
321 }
322
323
324 sub launch_settings_listener {
325
326         my $self = shift;
327         my $app = "opensrf.settings";
328         my $pid = OpenSRF::Utils::safe_fork();
329         if ( $pid ) {
330                 $self->pid_hash( $pid , _listener( $app ) );
331         }
332         else {
333                 my $apname = $app;
334                 $0 = "OpenSRF listener [$apname]";
335                 eval _listener( $app );
336                 exit;
337         }
338
339 }
340
341 # ----------------------------------------------
342 # Launch the Unix Servers
343
344 sub launch_unix {
345         my( $self, $apps ) = @_;
346
347         my $client = OpenSRF::Utils::SettingsClient->new();
348
349         foreach my $app ( @$apps ) {
350
351                 next unless $app;
352                 my $lang = $client->config_value( "apps", $app, "language");
353                 next unless $lang =~ /perl/i;
354                 next if $app eq "opensrf.settings";
355
356                 _log( " * Starting UnixServer for $app..." );
357
358                 my $pid = OpenSRF::Utils::safe_fork();
359                 if( $pid ) {
360                         $self->pid_hash( $pid , _unixserver( $app ) );
361                 }
362                 else {
363                         my $apname = $app;
364                         $0 = "OpenSRF App ($apname)";
365                         eval _unixserver( $app );
366                         exit;
367                 }
368         }
369 }
370
371 # ----------------------------------------------
372 # Launch the inbound clients
373
374 sub launch_listener {
375
376         my( $self, $apps ) = @_;
377         my $client = OpenSRF::Utils::SettingsClient->new();
378
379         foreach my $app ( @$apps ) {
380
381                 next unless $app;
382                 my $lang = $client->config_value( "apps", $app, "language");
383                 next unless $lang =~ /perl/i;
384                 next if $app eq "opensrf.settings";
385
386                 _log( " * Starting Listener for $app..." );
387
388                 my $pid = OpenSRF::Utils::safe_fork();
389                 if ( $pid ) {
390                         $self->pid_hash( $pid , _listener( $app ) );
391                 }
392                 else {
393                         my $apname = $app;
394                         $0 = "OpenSRF listener [$apname]";
395                         eval _listener( $app );
396                         exit;
397                 }
398         }
399 }
400
401
402 # ----------------------------------------------
403
404 sub pid_hash {
405         my( $self, $pid, $method ) = @_;
406         $self->{'pid_hash'}->{$pid} = $method
407                 if( $pid and $method );
408         return $self->{'pid_hash'};
409 }
410
411 # ----------------------------------------------
412 # If requested, the System can shut down.
413
414 sub killall {
415
416         $SIG{CHLD} = 'IGNORE';
417         $SIG{INT} = 'IGNORE';
418         kill( 'INT', -$$ ); #kill all in process group
419         exit;
420
421 }
422
423 # ----------------------------------------------
424 # Handle $SIG{HUP}
425 sub hupall {
426
427         _log( "HUPping brood" );
428         $SIG{CHLD} = 'IGNORE';
429         $SIG{HUP} = 'IGNORE';
430         kill( 'HUP', -$$ );
431 #       $SIG{CHLD} = \&process_automation;
432         $SIG{HUP} = sub{ instance()->hupall(); };
433 }
434
435
436 # ----------------------------------------------
437 # Log to debug, and stdout
438
439 sub _log {
440         my $string = shift;
441         OpenSRF::Utils::Logger->debug( $string, INFO );
442         print $string . "\n";
443 }
444
445 # ----------------------------------------------
446
447
448 1;
449
450