]> git.evergreen-ils.org Git - OpenSRF.git/blob - src/perlmods/OpenSRF/System.pm
7dcf2c37467bae72cc35a80fb6c85f412d607c36
[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         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 boostrap" );
205         
206         # --- Boot the Unix servers
207         $self->launch_unix($apps);
208
209
210         _sleep();
211         sleep 2;
212
213         # --- Boot the listeners
214         $self->launch_listener($apps);
215
216         _sleep();
217
218         _log( " * System is ready..." );
219
220 #       sleep 1;
221 #       my $ps = `ps ax | grep " Open" | grep -v grep | sort -r -k5`;
222 #       print "\n --- PS --- \n$ps --- PS ---\n\n";
223
224         while( 1 ) { sleep; }
225         exit;
226 }
227         
228         
229
230 # ----------------------------------------------
231 # Bootstraps a single client connection.  
232
233 # named params are 'config_file' and 'client_name'
234 #
235 sub bootstrap_client {
236         my $self = shift;
237
238         my $con = OpenSRF::Transport::PeerHandle->retrieve;
239         if($con and $con->tcp_connected) {
240                 warn "PeerHandle is already connected in 'bootstrap_client'... returning\n";
241                 _log( "PeerHandle is already connected in 'bootstrap_client'... returning");
242                 return;
243         }
244
245         my %params = @_;
246
247         $bootstrap_config_file = 
248                 $params{config_file} || $bootstrap_config_file;
249
250         my $app = $params{client_name} || "client";
251
252
253         load_bootstrap_config();
254         OpenSRF::Utils::Logger::set_config();
255         OpenSRF::Transport::PeerHandle->construct( $app );
256
257 }
258
259 sub connected {
260         if (my $con = OpenSRF::Transport::PeerHandle->retrieve) {
261                 return 1 if ($con->tcp_connected);
262         }
263         return 0;
264 }
265
266 sub bootstrap_logger {
267         $0 = "Log Server";
268         OpenSRF::Utils::LogServer->serve();
269 }
270
271
272 # ----------------------------------------------
273 # Cycle through the known processes, reap the dead child 
274 # and put a new child in its place. (MMWWAHAHHAHAAAA!)
275
276 sub process_automation {
277
278         my $self = __PACKAGE__->instance();
279
280         foreach my $pid ( keys %{$self->pid_hash} ) {
281
282                 if( waitpid( $pid, WNOHANG ) == $pid ) {
283
284                         my $method = $self->pid_hash->{$pid};
285                         delete $self->pid_hash->{$pid};
286
287                         my $newpid =  OpenSRF::Utils::safe_fork();
288
289                         OpenSRF::Utils::Logger->debug( "Relaunching $method", ERROR );
290                         _log( "Relaunching => $method" );
291
292                         if( $newpid ) {
293                                 $self->pid_hash( $newpid, $method );
294                         }
295                         else { eval $method; exit; }
296                 }
297         }
298
299         $SIG{CHLD} = \&process_automation;
300 }
301
302
303
304 sub launch_settings {
305
306         #       XXX the $self like this and pid automation will not work with this setup....
307         my($self) = @_;
308         @OpenSRF::UnixServer::ISA = qw(OpenSRF Net::Server::PreFork);
309
310         my $pid = OpenSRF::Utils::safe_fork();
311         if( $pid ) {
312                 $self->pid_hash( $pid , "launch_settings()" );
313         }
314         else {
315                 my $apname = "opensrf.settings";
316                 #$0 = "OpenSRF App [$apname]";
317                 eval _unixserver( $apname );
318                 if($@) { die "$@\n"; }
319                 exit;
320         }
321
322         @OpenSRF::UnixServer::ISA = qw(OpenSRF);
323
324 }
325
326
327 sub launch_settings_listener {
328
329         my $self = shift;
330         my $app = "opensrf.settings";
331         my $pid = OpenSRF::Utils::safe_fork();
332         if ( $pid ) {
333                 $self->pid_hash( $pid , _listener( $app ) );
334         }
335         else {
336                 my $apname = $app;
337                 $0 = "OpenSRF listener [$apname]";
338                 eval _listener( $app );
339                 exit;
340         }
341
342 }
343
344 # ----------------------------------------------
345 # Launch the Unix Servers
346
347 sub launch_unix {
348         my( $self, $apps ) = @_;
349
350         my $client = OpenSRF::Utils::SettingsClient->new();
351
352         foreach my $app ( @$apps ) {
353
354                 next unless $app;
355                 my $lang = $client->config_value( "apps", $app, "language");
356                 next unless $lang =~ /perl/i;
357                 next if $app eq "opensrf.settings";
358
359                 _log( " * Starting UnixServer for $app..." );
360
361                 my $pid = OpenSRF::Utils::safe_fork();
362                 if( $pid ) {
363                         $self->pid_hash( $pid , _unixserver( $app ) );
364                 }
365                 else {
366                         my $apname = $app;
367                         $0 = "OpenSRF App ($apname)";
368                         eval _unixserver( $app );
369                         exit;
370                 }
371         }
372 }
373
374 # ----------------------------------------------
375 # Launch the inbound clients
376
377 sub launch_listener {
378
379         my( $self, $apps ) = @_;
380         my $client = OpenSRF::Utils::SettingsClient->new();
381
382         foreach my $app ( @$apps ) {
383
384                 next unless $app;
385                 my $lang = $client->config_value( "apps", $app, "language");
386                 next unless $lang =~ /perl/i;
387                 next if $app eq "opensrf.settings";
388
389                 _log( " * Starting Listener for $app..." );
390
391                 my $pid = OpenSRF::Utils::safe_fork();
392                 if ( $pid ) {
393                         $self->pid_hash( $pid , _listener( $app ) );
394                 }
395                 else {
396                         my $apname = $app;
397                         $0 = "OpenSRF listener [$apname]";
398                         eval _listener( $app );
399                         exit;
400                 }
401         }
402 }
403
404 # ----------------------------------------------
405
406 =head comment
407 sub launch_shell {
408
409         my $self = shift;
410
411         my $pid = OpenSRF::Utils::safe_fork();
412
413         if( $pid ) { $self->pid_hash( $pid , _shell() ); }
414         else {
415                 $0 = "System Shell";
416                 for( my $x = 0; $x != 10; $x++ ) {
417                         eval _shell();
418                         if( ! $@ ) { last; }
419                 }
420                 exit;
421         }
422 }
423 =cut
424
425
426 # ----------------------------------------------
427
428 sub pid_hash {
429         my( $self, $pid, $method ) = @_;
430         $self->{'pid_hash'}->{$pid} = $method
431                 if( $pid and $method );
432         return $self->{'pid_hash'};
433 }
434
435 # ----------------------------------------------
436 # If requested, the System can shut down.
437
438 sub killall {
439
440         $SIG{CHLD} = 'IGNORE';
441         $SIG{INT} = 'IGNORE';
442         kill( 'INT', -$$ ); #kill all in process group
443         exit;
444
445 }
446
447 # ----------------------------------------------
448 # Handle $SIG{HUP}
449 sub hupall {
450
451         _log( "HUPping brood" );
452         $SIG{CHLD} = 'IGNORE';
453         $SIG{HUP} = 'IGNORE';
454         set_config(); # reload config
455         kill( 'HUP', -$$ );
456 #       $SIG{CHLD} = \&process_automation;
457         $SIG{HUP} = sub{ instance()->hupall(); };
458 }
459
460
461 # ----------------------------------------------
462 # Log to debug, and stdout
463
464 sub _log {
465         my $string = shift;
466         OpenSRF::Utils::Logger->debug( $string, INFO );
467         print $string . "\n";
468 }
469
470 # ----------------------------------------------
471
472 sub _sleep {
473         select( undef, undef, undef, 0.3 );
474 }
475
476 1;
477
478