File Coverage

blib/lib/Siebel/Srvrmgr/Daemon/Heavy.pm
Criterion Covered Total %
statement 162 228 71.0
branch 45 106 42.4
condition 9 24 37.5
subroutine 26 26 100.0
pod 2 2 100.0
total 244 386 63.2


line stmt bran cond sub pod time code
1             package Siebel::Srvrmgr::Daemon::Heavy;
2              
3             =pod
4              
5             =head1 NAME
6              
7             Siebel::Srvrmgr::Daemon::Heavy - subclass that reuses srvrmgr program instance for long periods
8              
9             =head1 SYNOPSIS
10              
11             use Siebel::Srvrmgr::Daemon::Heavy;
12              
13             my $daemon = Siebel::Srvrmgr::Daemon::Heavy->new(
14             {
15             time_zone => 'America/Sao_Paulo',
16             commands => [
17             Siebel::Srvrmgr::Daemon::Command->new(
18             command => 'load preferences',
19             action => 'LoadPreferences'
20             ),
21             Siebel::Srvrmgr::Daemon::Command->new(
22             command => 'list comp type',
23             action => 'ListCompTypes',
24             params => [$comp_types_file]
25             ),
26             Siebel::Srvrmgr::Daemon::Command->new(
27             command => 'list comp',
28             action => 'ListComps',
29             params => [$comps_file]
30             ),
31             Siebel::Srvrmgr::Daemon::Command->new(
32             command => 'list comp def',
33             action => 'ListCompDef',
34             params => [$comps_defs_file]
35             )
36             ]
37             }
38             );
39             $daemon->run($connection);
40              
41              
42             =head1 DESCRIPTION
43              
44             This class extends L<Siebel::Srvmrgr::Daemon>. By "Heavy" you should understand as more complex code to be able to deal with a large number of commands
45             of C<srvrmgr> that will be submitted to a Siebel Enterprise with a short time between them.
46              
47             This class is indicated to be used in scenarios where several commands need to be executed in a short time interval: it will connect to srvrmgr by using
48             IPC for communication between the processes and once connected, the srvrmgr session will be reused as many times as desired instead of following the
49             sequence of connect -> run commands -> disconnect.
50              
51             The sessions are not "interactive" from the user point of view but the usage of this class enable the adoption of some logic to change how the commands will
52             be executed or even generate commands on the fly.
53              
54             Since it uses Perl IPC, this class may suffer from good support in OS plataforms that are not UNIX-like. Be sure to check out tests results of the distribution
55             before trying to use it.
56              
57             =cut
58              
59 3     3   328272 use Moose 2.0401;
  3         1499619  
  3         26  
60 3     3   23577 use namespace::autoclean 0.13;
  3         22439  
  3         18  
61 3     3   1453 use Siebel::Srvrmgr::Daemon::Condition;
  3         48544  
  3         189  
62 3     3   1455 use Siebel::Srvrmgr::Daemon::ActionFactory;
  3         12  
  3         130  
63             use Siebel::Srvrmgr::Regexes
64 3     3   1386 qw(SRVRMGR_PROMPT LOAD_PREF_RESP SIEBEL_ERROR ROWS_RETURNED);
  3         8  
  3         257  
65 3     3   998 use Siebel::Srvrmgr::Daemon::Command;
  3         14  
  3         131  
66 3     3   1299 use POSIX;
  3         18123  
  3         31  
67 3     3   9241 use Scalar::Util qw(openhandle);
  3         9  
  3         204  
68 3     3   22 use Config;
  3         7  
  3         128  
69 3     3   1123 use Siebel::Srvrmgr::IPC qw(safe_open3);
  3         9  
  3         186  
70 3     3   927 use IO::Select;
  3         5338  
  3         155  
71 3     3   1234 use Encode;
  3         30937  
  3         304  
72 3     3   30 use Carp qw(longmess);
  3         10  
  3         159  
73 3     3   1216 use Siebel::Srvrmgr;
  3         10  
  3         94  
74 3     3   18 use Data::Dumper;
  3         6  
  3         160  
75 3     3   18 use Try::Tiny 0.27;
  3         62  
  3         10190  
76              
77             our $VERSION = '0.29'; # VERSION
78              
79             extends 'Siebel::Srvrmgr::Daemon';
80             with 'Siebel::Srvrmgr::Daemon::Connection';
81              
82             our $SIG_INT = 0;
83             our $SIG_PIPE = 0;
84             our $SIG_ALARM = 0;
85              
86             # :TODO :16/08/2013 19:02:24:: add statistics for daemon, like number of runs and average of used buffer for each command
87              
88             =pod
89              
90             =head1 ATTRIBUTES
91              
92             This class has additional attributes besides those from parent class.
93              
94             =head2 maximum_retries
95              
96             The maximum times this class wil retry to launch a new process of srvrmgr if the previous one failed for any reason. This is intented to implement
97             robustness to the process.
98              
99             =cut
100              
101             has maximum_retries => (
102             isa => 'Int',
103             is => 'ro',
104             reader => 'get_max_retries',
105             writer => '_set_max_retries',
106             default => 5
107             );
108              
109             =head2 retries
110              
111             The number of retries of launching a new srvrmgr process. If this value reaches the value defined for C<maximum_retries>, the instance of Siebel::Srvrmgr::Daemon
112             will quit execution returning an error code.
113              
114             =cut
115              
116             has retries => (
117             isa => 'Int',
118             is => 'ro',
119             reader => 'get_retries',
120             writer => '_set_retries',
121             default => 0
122             );
123              
124             =head2 write_fh
125              
126             A filehandle reference to the C<srvrmgr> STDIN. This is a read-only attribute.
127              
128             =cut
129              
130             has write_fh => (
131             isa => 'FileHandle',
132             is => 'ro',
133             writer => '_set_write',
134             reader => 'get_write'
135             );
136              
137             =pod
138              
139             =head2 read_fh
140              
141             A filehandle reference to the C<srvrmgr> STDOUT.
142              
143             This is a read-only attribute.
144              
145             =cut
146              
147             has read_fh => (
148             isa => 'FileHandle',
149             is => 'ro',
150             writer => '_set_read',
151             reader => 'get_read'
152             );
153              
154             =pod
155              
156             =head2 error_fh
157              
158             A filehandle reference to the C<srvrmgr> STDERR.
159              
160             This is a read-only attribute.
161              
162             =cut
163              
164             has error_fh => (
165             isa => 'FileHandle',
166             is => 'ro',
167             writer => '_set_error',
168             reader => 'get_error'
169             );
170              
171             =pod
172              
173             =head2 read_timeout
174              
175             The timeout for trying to read from child process handlers in seconds. It defaults to 0.5 second.
176              
177             Changing this value may help improving performance, but should be used with care.
178              
179             =cut
180              
181             has read_timeout => (
182             isa => 'Num',
183             is => 'rw',
184             writer => 'set_read_timeout',
185             reader => 'get_read_timeout',
186             default => 0.5
187             );
188              
189             =pod
190              
191             =head2 child_pid
192              
193             An integer presenting the process id (PID) of the process created by the OS when the C<srvrmgr> program is executed.
194              
195             This is a read-only attribute.
196              
197             =cut
198              
199             has child_pid => (
200             isa => 'Int',
201             is => 'ro',
202             writer => '_set_pid',
203             reader => 'get_pid',
204             clearer => 'clear_pid',
205             predicate => 'has_pid',
206             trigger => \&_add_retry
207             );
208              
209             =head2 last_exec_cmd
210              
211             This is a string representing the last command submitted to the C<srvrmgr> program. The default value for it is an
212             empty string (meaning that no command was submitted yet).
213              
214             =cut
215              
216             has last_exec_cmd => (
217             isa => 'Str',
218             is => 'ro',
219             default => '',
220             reader => 'get_last_cmd',
221             writer => '_set_last_cmd'
222             );
223              
224             =pod
225              
226             =head2 params_stack
227              
228             This is an array reference with the stack of params passed to the respective class. It is maintained automatically by the class so the attribute is read-only.
229              
230             =cut
231              
232             has params_stack => (
233             isa => 'ArrayRef',
234             is => 'ro',
235             writer => '_set_params_stack',
236             reader => 'get_params_stack'
237             );
238              
239             =pod
240              
241             =head2 action_stack
242              
243             This is an array reference with the stack of actions to be taken. It is maintained automatically by the class, so the attribute is read-only.
244              
245             =cut
246              
247             has action_stack => (
248             isa => 'ArrayRef',
249             is => 'ro',
250             writer => '_set_action_stack',
251             reader => 'get_action_stack'
252             );
253              
254             =head2 ipc_buffer_size
255              
256             A integer describing the size of the buffer used to read output from srvrmgr program by using IPC.
257              
258             It defaults to 32768 bytes, but it can be adjusted to improve performance (lowering CPU usage by increasing memory utilization).
259              
260             Increase of this attribute should be considered experimental.
261              
262             =cut
263              
264             has ipc_buffer_size => (
265             isa => 'Int',
266             is => 'rw',
267             reader => 'get_buffer_size',
268             writer => 'set_buffer_size',
269             default => 32768
270             );
271              
272             =head2 srvrmgr_prompt
273              
274             An string representing the prompt recovered from srvrmgr program. The value of this attribute is set automatically during srvrmgr execution.
275              
276             =cut
277              
278             has srvrmgr_prompt =>
279             ( isa => 'Str', is => 'ro', reader => 'get_prompt', writer => '_set_prompt' );
280              
281             =head1 METHODS
282              
283             =cut
284              
285             sub _add_retry {
286 2     2   9 my ( $self, $new, $old ) = @_;
287              
288             # if $old is undefined, this is the first call to run method
289 2 50       11 unless ( defined($old) ) {
290 2         9 return 0;
291             }
292             else {
293              
294 0 0       0 unless ( $new == $old ) {
295 0         0 $self->_set_retries( $self->get_retries() + 1 );
296 0         0 return 1;
297             }
298             else {
299 0         0 return 0;
300             }
301              
302             }
303              
304             }
305              
306             =pod
307              
308             =head2 BUILD
309              
310             This methods calls C<clear_pid> just to have a sane setting on C<child_pid> attribute.
311              
312             =cut
313              
314             sub BUILD {
315 3     3 1 10 my $self = shift;
316 3         122 $self->clear_pid();
317             }
318              
319             =pod
320              
321             =head2 get_retries
322              
323             Getter for the C<retries> attribute.
324              
325             =head2 get_max_retries
326              
327             Getter for the C<max_retries> attribute.
328              
329             =head2 clear_pid
330              
331             Clears the defined PID associated with the child process that executes srvrmgr. This is usually associated with calling C<close_child>.
332              
333             Beware that this is different then removing the child process or even C<undef> the attribute. This just controls a flag that the attribute C<child_pid>
334             is defined or not. See L<Moose> attributes for details.
335              
336             =head2 has_pid
337              
338             Returns true or false if the C<child_pid> is defined. Beware that this is different then checking if there is an integer associated with C<child_pid>
339             attribute: this method might return false even though the old PID associated with C<child_pid> is still available. See L<Moose> attributes for details.
340              
341             =head2 get_prompt
342              
343             Returns the content of the attribute C<srvrmgr_prompt>.
344              
345             =head2 get_buffer_size
346              
347             Returns the value of the attribute C<ipc_buffer_size>.
348              
349             =head2 set_buffer_size
350              
351             Sets the attribute C<ipc_buffer_size>. Expects an integer as parameter, multiple of 1024.
352              
353             =head2 get_write
354              
355             Returns the file handle of STDIN from the process executing the srvrmgr program based on the value of the attribute C<write_fh>.
356              
357             =head2 get_read
358              
359             Returns the file handle of STDOUT from the process executing the srvrmgr program based on the value of the attribute C<read_fh>.
360              
361             =head2 get_error
362              
363             Returns the file handle of STDERR from the process executing the srvrmgr program based on the value of the attribute C<error_fh>.
364              
365             =head2 get_pid
366              
367             Returns the content of C<pid> attribute as an integer.
368              
369             =head2 get_last_cmd
370              
371             Returns the content of the attribute C<last_cmd> as a string.
372              
373             =head2 get_params_stack
374              
375             Returns the content of the attribute C<params_stack>.
376              
377             =cut
378              
379             override '_setup_commands' => sub {
380             my $self = shift;
381             super();
382             my $cmds_ref = $self->get_commands();
383             my @cmd;
384             my @actions;
385             my @params;
386              
387             foreach my $cmd ( @{$cmds_ref} ) {
388             push( @cmd, $cmd->get_command() );
389             push( @actions, $cmd->get_action() );
390             push( @params, $cmd->get_params() );
391             }
392              
393             $self->_set_cmd_stack( \@cmd );
394             $self->_set_action_stack( \@actions );
395             $self->_set_params_stack( \@params );
396             return 1;
397             };
398              
399             =pod
400              
401             =head2 run
402              
403             This method will try to connect to a Siebel Enterprise through C<srvrmgr> program (if it is the first time the method is invoke) or reuse an already open
404             connection to submit the commands and respective actions defined during object creation. The path to the program is check and if it does not exists the
405             method will issue an warning message and immediatly returns false.
406              
407             Those operations will be executed in a loop as long the C<check> method from the class L<Siebel::Srvrmgr::Daemon::Condition> returns true.
408              
409             =cut
410              
411             # :WORKAROUND:10/05/2013 15:23:52:: using a state machine with FSA::Rules is difficult here because it is necessary to loop over output from
412             # srvrmgr but the program will hang if there is no output left to be read from srvrmgr.
413              
414             override 'run' => sub {
415             my ($self) = @_;
416             super();
417             my $logger;
418             my $temp;
419             my $ignore_output = 0;
420             my ( $read_h, $write_h, $error_h );
421              
422             unless ( $self->has_pid() ) {
423             confess( $self->get_conn->get_bin()
424             . ' returned un unrecoverable error, aborting execution' )
425             unless ( $self->_create_child );
426              
427             # :WORKAROUND:31/07/2013 14:42:33:: must initialize the Log::Log4perl after forking the srvrmgr to avoid sharing filehandles
428             $logger = Siebel::Srvrmgr->gimme_logger( blessed($self) );
429              
430             }
431             else {
432             $logger = Siebel::Srvrmgr->gimme_logger( blessed($self) );
433             $logger->info( 'Reusing PID ', $self->get_pid() )
434             if ( $logger->is_debug() );
435             $ignore_output = 1;
436             }
437              
438             $logger->info('Starting run method');
439             my @input_buffer;
440             my $timeout = $self->get_read_timeout; # avoid multiple method invocations
441              
442             # :TODO :06/08/2013 19:13:47:: create condition as a hidden attribute of this class
443             my $condition = Siebel::Srvrmgr::Daemon::Condition->new(
444             {
445             total_commands => scalar( @{ $self->get_commands() } ),
446             cmd_sent => 0
447             }
448             );
449              
450             my $parser = $self->create_parser();
451             my $select = IO::Select->new();
452             my $data_ref = $self->_manage_handlers($select);
453              
454             # :WARNING:16-07-2014 11:35:13:: cannot using SRVRMGR_PROMPT regex because it is too restrictive
455             # since we are reading a stream here. The regex is a copy of SRVRMGR_PROMPT without the "^" at the beginning
456             my $prompt_regex = qr/srvrmgr(\:[\w\_\-]+)?>\s(.*)?$/;
457              
458             # :WARNING:22-03-2016 23:21:53:: configuration of EOL is obscure but possible in Siebel. The hardcode values might
459             # be a problem
460             # :TODO:22-03-2016 23:22:29:: add more attributes to take care of it, with default values
461             my $CR = "\o{15}";
462             my $LF = "\o{12}";
463             my $eol_regex = qr/$CR$LF$/;
464             my $buffer_size = $self->get_buffer_size();
465              
466             if ( $logger->is_debug() ) {
467             $logger->debug( 'Setting '
468             . $timeout
469             . ' seconds for read srvrmgr output time out' );
470             $logger->debug("sysread buffer size is $buffer_size");
471             my $assert = 'Input record separator is ';
472              
473             SWITCH: {
474              
475             if ( $/ eq $CR ) {
476             $logger->debug( $assert . 'CR' );
477             last SWITCH;
478             }
479             if ( $/ eq ("$CR$LF") ) {
480             $logger->debug( $assert . 'CRLF' );
481             last SWITCH;
482             }
483             if ( $/ eq $LF ) {
484             $logger->debug( $assert . 'LF' );
485             last SWITCH;
486             }
487             else {
488             $logger->debug("Unknown input record separator: [$/]");
489             }
490              
491             }
492              
493             }
494              
495             do {
496              
497             exit if ($SIG_INT);
498              
499             # :TODO:18-10-2013:arfreitas: move all code inside the while block to a different method to help and clean up lexicals
500             while ( my @ready = $select->can_read($timeout) ) {
501              
502             foreach my $fh (@ready) {
503             my $fh_name = fileno($fh);
504             $logger->debug("Reading filehandle $fh_name")
505             if ( $logger->is_debug() );
506              
507             unless (( defined( $data_ref->{$fh_name}->{bytes} ) )
508             and ( $data_ref->{$fh_name}->{bytes} > 0 ) )
509             {
510             $data_ref->{$fh_name}->{bytes} =
511             sysread( $fh, $data_ref->{$fh_name}->{data},
512             $buffer_size );
513             }
514             else {
515             $logger->info(
516             'Caught part of a record, repeating sysread with offset'
517             ) if ( $logger->is_info() );
518              
519             # Like all Perl character operations, length() normally deals in
520             # logical characters, not physical bytes. For how many bytes a
521             # string encoded as UTF-8 would take up, use
522             # "length(Encode::encode_utf8(EXPR))" (you'll have to "use Encode"
523             # first). See Encode and perlunicode.
524             my $offset =
525             length(
526             Encode::encode_utf8( $data_ref->{$fh_name}->{data} ) );
527             $logger->debug("Offset is $offset")
528             if ( $logger->is_debug() );
529             $data_ref->{$fh_name}->{bytes} =
530             sysread( $fh, $data_ref->{$fh_name}->{data},
531             $buffer_size, $offset );
532             }
533              
534             unless ( defined( $data_ref->{$fh_name}->{bytes} ) ) {
535             $logger->fatal( 'sysread returned an error: ' . $! );
536             $self->_check_child();
537             $logger->logdie( 'sysreading from '
538             . $data_ref->{$fh_name}->{type}
539             . " returned an unrecoverable error: $!" );
540             }
541             else {
542              
543             if ( $logger->is_debug() ) {
544             $logger->debug( 'Read '
545             . $data_ref->{$fh_name}->{bytes}
546             . ' bytes from '
547             . $data_ref->{$fh_name}->{type} );
548             }
549              
550             if ( $data_ref->{$fh_name}->{bytes} == 0 ) {
551             $logger->warn(
552             'got EOF from ' . $data_ref->{$fh_name}->{type} );
553             $select->remove($fh);
554             next;
555             }
556              
557             unless ( ( $data_ref->{$fh_name}->{data} =~ $eol_regex )
558             or ( $data_ref->{$fh_name}->{data} =~ $prompt_regex ) )
559             {
560              
561             $logger->debug(
562             "Buffer data does not ends with CRLF or prompt, needs to read more from handle.\n"
563             . 'Buffer is ['
564             . $data_ref->{$fh_name}->{data}
565             . ']' )
566             if ( $logger->is_debug() );
567              
568             }
569             else {
570              
571             # data is ready to go
572              
573             $self->normalize_eol( \$data_ref->{$fh_name}->{data} );
574              
575             if ( $data_ref->{$fh_name}->{type} eq 'STDOUT' ) {
576              
577             # :WORKAROUND:14/08/2013 18:40:46:: necessary to empty the stdout for possible (useless) information hanging in the buffer, but
578             # this information must be discarded since is from the previous processed command submitted
579             # :TODO :14/08/2013 18:41:43:: check why such information is not being recovered in the previous execution
580             $self->_process_stdout(
581             \$data_ref->{$fh_name}->{data},
582             \@input_buffer, $condition )
583             unless ($ignore_output);
584              
585             }
586             elsif ( $data_ref->{$fh_name}->{type} eq 'STDERR' ) {
587              
588             $self->_process_stderr(
589             \$data_ref->{$fh_name}->{data} );
590              
591             }
592             else {
593             $logger->logdie(
594             'Somehow got a filehandle I dont know about!: Type is'
595             . $data_ref->{$fh_name}->{type} );
596             }
597              
598             $data_ref->{$fh_name}->{bytes} = 0;
599             $data_ref->{$fh_name}->{data} = undef;
600             }
601              
602             }
603              
604             } # end of foreach block
605              
606             } # end of while block
607              
608             # below is the place for a Action object
609             if ( scalar(@input_buffer) >= 1 ) {
610             $self->_check_error( \@input_buffer, 0 );
611              
612             # :TRICKY:5/1/2012 17:43:58:: copy params to avoid operations that erases the parameters due passing an array reference and messing with it
613             my @params;
614             map { push( @params, $_ ) }
615             @{ $self->get_params_stack()->[ $condition->get_cmd_counter() ] };
616             my $class =
617             $self->get_action_stack()->[ $condition->get_cmd_counter() ];
618              
619             if ( $logger->is_debug() ) {
620             $logger->debug(
621             "Creating Siebel::Srvrmgr::Daemon::Action subclass $class instance"
622             );
623             }
624              
625             my $action = Siebel::Srvrmgr::Daemon::ActionFactory->create(
626             $class,
627             {
628             parser => $parser,
629             params => \@params
630             }
631             );
632              
633             # :TODO :16/08/2013 19:03:30:: move this log statement to Siebel::Srvrmgr::Daemon::Action
634             if ( $logger->is_debug() ) {
635             $logger->debug('Lines from buffer sent for parsing');
636              
637             foreach my $line (@input_buffer) {
638             $logger->debug($line);
639             }
640              
641             $logger->debug('End of lines from buffer sent for parsing');
642             }
643              
644             # :WORKAROUND:16/08/2013 18:54:51:: exceptions from validating output are not being seen
645             # :TODO :16/08/2013 18:55:18:: start using Try::Tiny to use exceptions for known problems
646             try {
647             $condition->set_output_used( $action->do( \@input_buffer ) );
648             }
649             catch {
650             $logger->logdie($_);
651             };
652              
653             $logger->debug( 'Is output used? ' . $condition->is_output_used() )
654             if ( $logger->is_debug() );
655             @input_buffer = ();
656              
657             }
658             else {
659             $logger->warn(
660             'The internal buffer is empty: check out if the read_timeout is not too low'
661             );
662             }
663              
664             $logger->debug('Finished processing buffer') if ( $logger->is_debug() );
665              
666             # begin of session, sending command to the prompt
667             unless ( $condition->is_cmd_sent() or $condition->is_last_cmd() ) {
668             $logger->debug('Preparing to execute command')
669             if ( $logger->is_debug() );
670             $condition->add_cmd_counter()
671             if ( $condition->can_increment() );
672             my $cmd = $self->get_cmd_stack()->[ $condition->get_cmd_counter() ];
673             $self->_submit_cmd( $cmd, $logger );
674             $ignore_output = 0;
675              
676             # srvrmgr.exe of Siebel 7.5.3.17 does not echo command printed to the input file handle
677             # this is necessary to give a hint to the parser about the command submitted
678              
679             if ( defined( $self->get_prompt() ) ) {
680             push( @input_buffer, $self->get_prompt() . $cmd );
681             $self->_set_last_cmd( $self->get_prompt() . $cmd );
682             }
683             else {
684             $logger->logdie(
685             "prompt was not defined from read output, cannot continue. Input buffer was: \n"
686             . Dumper(@input_buffer) );
687             }
688              
689             $condition->set_output_used(0);
690             $condition->set_cmd_sent(1);
691             }
692             else {
693              
694             if ( $logger->is_debug() ) {
695             $logger->debug('Not yet read to execute a command');
696             $logger->debug(
697             'Condition max_cmd_idx = ' . $condition->max_cmd_idx() );
698             $logger->debug(
699             'Condition is_cmd_sent = ' . $condition->is_cmd_sent() );
700             }
701              
702             }
703              
704             # :TODO :31/07/2013 16:43:15:: Condition class should have their own logger
705             # it is not possible to call check() twice because of the invocation of reduce_total_cmd() by check()
706             # if the Daemon has only one command, it will enter in a loop invoking srvrmgr everytime without doing
707             # nothing with it's output
708             $temp = $condition->check();
709              
710             $logger->info( 'Continue executing? ' . $temp )
711             if ( $logger->is_info() );
712              
713             } while ($temp);
714              
715             $self->_set_child_runs( $self->get_child_runs() + 1 );
716             $logger->debug( 'child_runs = ' . $self->get_child_runs() )
717             if ( $logger->is_debug() );
718             $logger->info('Exiting run sub');
719             return 1;
720             };
721              
722             sub _manage_handlers {
723 17     17   65 my ( $self, $select ) = @_;
724 17         147 my $logger = Siebel::Srvrmgr->gimme_logger( blessed($self) );
725              
726             # to keep data from both handles while looping over them
727 17         690 my %data;
728 17         68 my @handlers_order = (qw(STDOUT STDERR));
729 17         48 my $counter = 0;
730              
731 17         820 foreach my $fh ( $self->get_read(), $self->get_error() ) {
732 34         129 my $fh_name = fileno($fh);
733 34         215 $data{$fh_name} = {
734             type => $handlers_order[$counter],
735             bytes => 0,
736             data => undef
737             };
738 34         191 $select->add($fh);
739              
740 34 50       1931 if ( $logger->is_debug() ) {
741              
742 0 0       0 if ( openhandle($fh) ) {
743 0         0 $logger->debug(
744             "file handler for $counter is available, with fileno = $fh_name "
745             );
746             }
747             else {
748 0         0 $logger->debug(
749             "file handler for $counter is NOT available, with fileno = $fh_name "
750             );
751             }
752              
753             }
754              
755 34         288 $counter++;
756             }
757              
758 17         120 return \%data;
759             }
760              
761             sub _create_child {
762 2     2   7 my ($self) = @_;
763 2         31 my $logger = Siebel::Srvrmgr->gimme_logger( blessed($self) );
764              
765 2 50       175 if ( $self->get_retries() >= $self->get_max_retries() ) {
766 0         0 $logger->fatal( 'Maximum retries to spawn srvrmgr reached: '
767             . $self->get_max_retries() );
768 0         0 $logger->warn(
769             'Application will exit with an error return code. Please review log for errors'
770             );
771 0         0 exit(1);
772             }
773 2         84 my $conn = $self->get_conn;
774              
775             # :WORKAROUND:09/01/2017 22:52:54:: on Windows is not configured as executable by default
776 2 50       178 if ( $Config{osname} eq 'MSWin32' ) {
777 0 0       0 $logger->logdie(
778             'Cannot find program ' . $conn->get_bin() . ' to execute' )
779             unless ( -e $conn->get_bin() );
780             }
781             else {
782 2 50 33     89 $logger->logdie(
783             'Cannot find program ' . $conn->get_bin() . ' to execute' )
784             unless ( -e $conn->get_bin() && -x _ );
785             }
786              
787 2         19 my $params_ref = $conn->get_params;
788 2         24 $self->_define_params($params_ref);
789 2         14 my ( $pid, $write_h, $read_h, $error_h ) = safe_open3($params_ref);
790              
791             # submit the password, avoiding exposing it in the command line as a parameter
792 2         14361 syswrite $write_h, ( $conn->get_password . "\n" );
793 2         133 $self->_set_pid($pid);
794 2         108 $self->_set_write($write_h);
795 2         99 $self->_set_read($read_h);
796 2         95 $self->_set_error($error_h);
797              
798 2 50       57 if ( $logger->is_debug() ) {
799             $logger->debug( 'Forked srvrmgr with the following parameters: '
800 0         0 . join( ' ', @{$params_ref} ) );
  0         0  
801 0         0 $logger->debug( 'child PID is ' . $pid );
802 0         0 $logger->debug( 'IPC buffer size is ' . $self->get_buffer_size() );
803             }
804              
805 2         57 $logger->info('Started srvrmgr');
806              
807 2 50       48 unless ( $self->_check_child() ) {
808 0         0 return 0;
809             }
810             else {
811 2         135 $self->_set_child_runs(0);
812 2         87 return 1;
813             }
814             }
815              
816             sub _process_stderr {
817 2 50   2   13 exit if ($SIG_INT);
818 2         9 my ( $self, $data_ref ) = @_;
819              
820 2         20 my $logger = Siebel::Srvrmgr->gimme_logger( blessed($self) );
821              
822 2 50       98 if ( defined($$data_ref) ) {
823              
824 2         15 foreach my $line ( split( "\n", $$data_ref ) ) {
825 2 50       11 exit if ($SIG_INT);
826 2         15 $self->_check_error( $line, 1 );
827             }
828              
829             }
830             else {
831 0         0 $logger->warn('Received empty buffer to read');
832             }
833              
834             }
835              
836             sub _process_stdout {
837              
838             # :TODO :07/08/2013 15:12:17:: should this be controlled in instances? or should it be global to the class?
839 28 50 33 28   249 exit if ( $SIG_INT or $SIG_PIPE );
840 28         137 my ( $self, $data_ref, $buffer_ref, $condition ) = @_;
841 28         298 my $logger = Siebel::Srvrmgr->gimme_logger( blessed($self) );
842              
843             # :TODO :09/08/2013 19:35:30:: review and remove assigning the compiled regexes to scalar (probably unecessary)
844 28         1902 my $prompt_regex = SRVRMGR_PROMPT;
845 28         170 my $load_pref_regex = LOAD_PREF_RESP;
846 28 50       145 $logger->debug("Raw content is [$$data_ref]") if $logger->is_debug();
847              
848 28         1516 foreach my $line ( split( "\n", $$data_ref ) ) {
849 1436 50 33     7946 exit if ( $SIG_INT or $SIG_PIPE );
850              
851 1436 50       4863 if ( $logger->is_debug() ) {
852              
853 0 0       0 if ( defined($line) ) {
854 0         0 $logger->debug("Recovered line [$line]");
855             }
856             else {
857 0         0 $logger->debug("Recovered line with undefined content");
858             }
859              
860             }
861              
862 1436         14984 $self->_check_error( $line, 0 );
863              
864             SWITCH: {
865              
866             # :TRICKY:29/06/2011 21:23:11:: bufferization in srvrmgr.exe ruins the day: the prompt will never come out unless a little push is given
867             # :TODO :03/09/2013 12:11:27:: check if a print with an empty line is not required here
868 1436 100       5519 if ( $line =~ ROWS_RETURNED ) {
  1436         4890  
869              
870             # parsers will consider the lines below
871 21         70 push( @{$buffer_ref}, $line );
  21         94  
872 21         116 last SWITCH;
873             }
874              
875             # prompt was returned, end of output
876             # first execution should bring only informations about Siebel
877 1415 100       7280 if ( $line =~ /$prompt_regex/ ) {
878              
879 28 50       2022 unless ( defined( $self->get_prompt() ) ) {
    100          
880 2         107 $self->_set_prompt($line);
881 2 50       17 $logger->info("defined prompt with [$line]")
882             if ( $logger->is_info() );
883              
884             # if prompt was undefined, that means that this is might be rest of output of previous command
885             # and thus can be safely ignored
886 2 50       29 if ( @{$buffer_ref} ) {
  2         11  
887              
888 2 50       20 if ( $buffer_ref->[0] eq '' ) {
889 0         0 $logger->debug("Ignoring output [$line]");
890 0         0 $condition->set_cmd_sent(0);
891 0         0 @{$buffer_ref} = ();
  0         0  
892             }
893              
894             }
895              
896             }
897 0         0 elsif ( scalar( @{$buffer_ref} ) < 1 ) { # no command submitted
  26         136  
898 0         0 $condition->set_cmd_sent(0);
899             }
900             else {
901              
902 26 50 33     66 unless (( scalar( @{$buffer_ref} ) >= 1 )
  26   33     1580  
903             and ( $buffer_ref->[0] eq $self->get_last_cmd() )
904             and $condition->is_cmd_sent() )
905             {
906 0         0 $condition->set_cmd_sent(0);
907             }
908              
909             }
910              
911 28         115 push( @{$buffer_ref}, $line );
  28         193  
912             }
913              
914             # no prompt detection, keep reading output from srvrmgr
915 1387         3146 else { push( @{$buffer_ref}, $line ); }
  1387         5407  
916              
917             }
918              
919             }
920              
921             }
922              
923             sub _check_child {
924 2     2   12 my $self = shift;
925 2         77 my $logger = Siebel::Srvrmgr->gimme_logger( blessed($self) );
926              
927 2 50       252 if ( $self->has_pid() ) {
928              
929             # :WORKAROUND:19/4/2012 19:38:04:: somehow the child process of srvrmgr has to be waited for one second and receive one kill 0 signal before
930             # it dies when something goes wrong
931 2         90 kill 0, $self->get_pid();
932              
933 2 50       106 unless ( kill 0, $self->get_pid() ) {
934 0         0 $logger->fatal( $self->get_bin()
935             . " process returned a fatal error: ${^CHILD_ERROR_NATIVE}" );
936 0         0 $logger->fatal( $? . ' child exit status = ' . ( $? >> 8 ) );
937 0         0 $self->close_child($logger);
938 0         0 return 0;
939             }
940             else {
941 2         18 return 1;
942             }
943              
944             # try to read immediatly from stderr if possible
945 0 0       0 if ( openhandle( $self->get_error() ) ) {
946 0         0 my $error;
947 0         0 my $select = IO::Select->new();
948 0         0 $select->add( $self->get_error() );
949              
950 0         0 while ( my $fh = $select->can_read( $self->get_read_timeout() ) ) {
951 0         0 my $buffer;
952 0         0 my $read = sysread( $fh, $buffer, $self->get_buffer_size() );
953              
954 0 0       0 if ( defined($read) ) {
955              
956 0 0       0 if ( $read > 0 ) {
957 0         0 $error .= $buffer;
958 0         0 next;
959             }
960             else {
961 0         0 $logger->debug(
962             'Reached EOF while trying to get error messages');
963             }
964              
965             }
966             else {
967 0         0 $logger->warn(
968             'Could not sysread the STDERR from srvrmgr process: '
969             . $! );
970 0         0 last;
971             }
972              
973             } # end of while block
974              
975 0 0       0 $self->_process_stderr( \$error ) if ( defined($error) );
976              
977             }
978             else {
979 0         0 $logger->fatal('Error pipe from child is closed');
980             }
981              
982 0 0       0 $logger->fatal('Read pipe from child is closed')
983             unless ( openhandle( $self->get_read() ) );
984 0 0       0 $logger->fatal('Write pipe from child is closed')
985             unless ( openhandle( $self->get_write() ) );
986              
987             } # end of if has_pid
988             else {
989 0         0 return 0;
990             }
991              
992             }
993              
994             sub _my_cleanup {
995 3     3   12 my $self = shift;
996 3         26 my $logger = Siebel::Srvrmgr->gimme_logger( blessed($self) );
997              
998 3 100 66     241 if ( $self->has_pid() and ( $self->get_pid() =~ /\d+/ ) ) {
999 1         11 $self->close_child();
1000             }
1001             else {
1002              
1003 2 50       10 if ( $logger->is_info() ) {
1004 0         0 $logger->info('No child process to terminate');
1005             }
1006              
1007             }
1008              
1009 3         31 return 1;
1010             }
1011              
1012             sub _submit_cmd {
1013 28     28   133 my ( $self, $cmd ) = @_;
1014 28         416 my $logger = Siebel::Srvrmgr->gimme_logger( blessed($self) );
1015 28         3589 my $bytes = syswrite $self->get_write(), "$cmd\n";
1016              
1017 28 50       174 if ( defined($bytes) ) {
1018              
1019 28 50       147 if ( $logger->is_debug() ) {
1020 0         0 $logger->debug("Submitted $cmd, wrote $bytes bytes");
1021             }
1022              
1023             }
1024             else {
1025 0         0 $logger->logdie(
1026             'A failure occurred when trying to submit ' . $cmd . ': ' . $! );
1027             }
1028              
1029 28         299 return 1;
1030             }
1031              
1032             =pod
1033              
1034             =head2 close_child
1035              
1036             Finishes the child process associated with the execution of srvrmgr program, if the child's PID is available. Besides, this automatically calls C<clear_pid>.
1037              
1038             First this methods tries to submit the C<exit> command to srvrmgr, hoping to terminate the connection with the Siebel Enterprise. After that, the
1039             handles associated with the child will be closed. If after that the PID is still running, the method will call C<waitpid> in non-blocking mode.
1040              
1041             For MS Windows OS, this might not be sufficient: the PID will be checked again after C<waitpid>, and if it is still running, this method will try to use
1042             C<kill 9> to eliminate the process.
1043              
1044             If the child process is terminated successfully, this method returns true. If there is no PID associated with the Daemon instance, this method will return false.
1045              
1046             =cut
1047              
1048             sub close_child {
1049 3     3 1 77 my $self = shift;
1050 3         36 my $logger = Siebel::Srvrmgr->gimme_logger( blessed($self) );
1051              
1052 3 100       374 if ( $self->has_pid() ) {
1053              
1054 2 100       15 if ( $logger->is_warn() ) {
1055 1         61 $logger->warn( 'Trying to close child PID ' . $self->get_pid() );
1056             }
1057              
1058 2 50 33     858 if ( ( openhandle( $self->get_write() ) )
      33        
1059             and ( not($SIG_PIPE) )
1060             and ( not($SIG_ALARM) ) )
1061             {
1062              
1063 2         19 $self->_submit_cmd('exit');
1064              
1065 2 50       12 if ( $logger->is_debug() ) {
1066 0         0 $logger->debug('Submitted exit command to srvrmgr');
1067             }
1068              
1069             }
1070             else {
1071 0         0 $logger->warn('write_fh is already closed');
1072             }
1073              
1074 2         42 for ( 1 .. 4 ) {
1075              
1076 8         8001587 sleep 1;
1077              
1078 8 50       760 if ( kill( 0, $self->get_pid() ) ) {
1079 8         85 $logger->debug('child process is still there');
1080             }
1081             else {
1082 0         0 last;
1083             }
1084              
1085             }
1086              
1087 2 50       140 if ( kill 0, $self->get_pid() ) {
1088              
1089 2 50       16 if ( $logger->is_debug() ) {
1090 0         0 $logger->debug(
1091             'srvrmgr is still running, trying waitpid on it');
1092             }
1093              
1094 2         122 my $ret = waitpid( $self->get_pid(), 0 );
1095              
1096             SWITCH: {
1097              
1098 2 50       9 if ( $ret == $self->get_pid() ) {
  2         103  
1099              
1100             # :WORKAROUND:14/08/2013 17:44:00:: for Windows, not using shutdown when creating the socketpair causes the application to not
1101             # exit with waitpid. Using waitpid without non-blocking mode just blocks the application to finish
1102 2 50       49 if ( $Config{osname} eq 'MSWin32' ) {
1103              
1104 0 0       0 if ( kill 0, $self->get_pid() ) {
1105 0         0 $logger->warn(
1106             'child is still running even after waitpid: last attempt with "kill 9"'
1107             );
1108 0         0 kill 9, $self->get_pid();
1109             }
1110              
1111             }
1112              
1113 2 50       19 $logger->info('Child process finished successfully')
1114             if ( $logger->is_info() );
1115 2         27 last SWITCH;
1116             }
1117              
1118 0 0       0 if ( $ret == -1 ) {
1119 0 0       0 $logger->info(
1120             'No such PID ' . $self->get_pid() . ' to kill' )
1121             if ( $logger->is_info() );
1122 0         0 last SWITCH;
1123              
1124             }
1125             else {
1126              
1127 0 0       0 if ( $logger->is_warn() ) {
1128 0         0 $logger->warn(
1129             "Could not kill the child process, child status = $?, child error = "
1130             . ${^CHILD_ERROR_NATIVE} );
1131             }
1132              
1133             }
1134              
1135             }
1136              
1137             }
1138             else {
1139 0         0 $logger->warn('Child process is already gone');
1140             }
1141              
1142 2         180 $self->clear_pid();
1143 2         16 return 1;
1144              
1145             }
1146             else {
1147 1 50       10 $logger->info('Has no child PID available to terminate')
1148             if ( $logger->is_info() );
1149 1         19 return 0;
1150             }
1151              
1152             }
1153              
1154             =pod
1155              
1156             =head1 BACKGROUND EXECUTION
1157              
1158             If you're in a UNIX-like OS, you might want to execute some code with this class as a background process. This can be easily done with:
1159              
1160             nohup ~/perl5/perlbrew/perls/perl-5.16.3/bin/perl ~/my_script.pl
1161             CRTL+Z
1162             bg 1
1163              
1164             In this example, the Perl interpreter was located in C<~/perl5/perlbrew/perls/perl-5.16.3/bin/perl> but of course you location might be different.
1165              
1166             =head1 CAVEATS
1167              
1168             This class is still considered experimental and should be used with care. Tests with MS Windows (and the nature of doing IPC within the plataform) makes it difficult do use this class in Microsoft OS's.
1169              
1170             The C<srvrmgr> program uses buffering, which makes difficult to read the generated output as expected.
1171              
1172             =head1 SEE ALSO
1173              
1174             =over
1175              
1176             =item *
1177              
1178             L<IPC::Open3>
1179              
1180             =item *
1181              
1182             L<Siebel::Srvrmgr:::Daemon>
1183              
1184             =item *
1185              
1186             L<Siebel::Srvrmgr::Daemon::Condition>
1187              
1188             =item *
1189              
1190             L<Siebel::Srvrmgr::Daemon::Command>
1191              
1192             =item *
1193              
1194             L<Siebel::Srvrmgr::Daemon::ActionFactory>
1195              
1196             =item *
1197              
1198             L<Siebel::Srvrmgr::Regexes>
1199              
1200             =item *
1201              
1202             L<POSIX>
1203              
1204             =item *
1205              
1206             L<Siebel::Srvrmgr::Daemon::IPC>
1207              
1208             =back
1209              
1210             =head1 AUTHOR
1211              
1212             Alceu Rodrigues de Freitas Junior, E<lt>arfreitas@cpan.orgE<gt>.
1213              
1214             =head1 COPYRIGHT AND LICENSE
1215              
1216             This software is copyright (c) 2012 of Alceu Rodrigues de Freitas Junior, E<lt>arfreitas@cpan.orgE<gt>
1217              
1218             This file is part of Siebel Monitoring Tools.
1219              
1220             Siebel Monitoring Tools is free software: you can redistribute it and/or modify
1221             it under the terms of the GNU General Public License as published by
1222             the Free Software Foundation, either version 3 of the License, or
1223             (at your option) any later version.
1224              
1225             Siebel Monitoring Tools is distributed in the hope that it will be useful,
1226             but WITHOUT ANY WARRANTY; without even the implied warranty of
1227             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1228             GNU General Public License for more details.
1229              
1230             You should have received a copy of the GNU General Public License
1231             along with Siebel Monitoring Tools. If not, see L<http://www.gnu.org/licenses/>.
1232              
1233             =cut
1234              
1235             __PACKAGE__->meta->make_immutable;
1236              
1237             1;
1238