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