File Coverage

blib/lib/TRD/Watch/DB.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


line stmt bran cond sub pod time code
1             package TRD::Watch::DB;
2              
3 1     1   74897 use 5.008008;
  1         5  
  1         60  
4 1     1   6 use strict;
  1         2  
  1         43  
5 1     1   22 use warnings;
  1         8  
  1         53  
6 1     1   12657 use POSIX;
  1         33031  
  1         7  
7 1     1   10536 use Carp;
  1         4  
  1         99  
8 1     1   9207 use DBI;
  1         78564  
  1         114  
9 1     1   4067 use threads ( 'exit' => 'threads_only' );
  0            
  0            
10             use Time::HiRes qw(sleep);
11             use TRD::DebugLog;
12              
13             =head1 NAME
14              
15             TRD::Warch::DB - データベースの死活監視
16              
17             =head1 VERSION
18              
19             Version 0.0.5
20              
21             =cut
22              
23             our $VERSION = '0.0.5';
24             our $default_timeout = 5; # sec
25             our $default_interval = 60; # sec
26             our $default_sql = "select TO_CHAR( sysdate, 'YYYY-MM-DD HH24:MI:SS' ) from dual";
27             our $default_db_sid = 'Oracle:emp';
28             our $default_db_user = 'sccot';
29             our $default_db_pass = 'tiger';
30              
31             =head1 SYNOPSIS
32              
33             use TRD::Watch::DB;
34             use DBI;
35              
36             my $db_th = new1 TRD:Watch::DB(
37             $name,
38             '-db_sid' => 'Oracle:emp',
39             '-db_user' => 'sccot',
40             '-db_pass' => 'tiger',
41             '-sql' => q!select TO_CHAR( sysdate, 'YYYY-MM-DD' ) from dual!,
42             '-checkfunc' => \&checkfunc,
43             '-errorfunc' => \&errorfunc,
44             '-recoverfunc' => \&recoverfunc
45             );
46             $db_th->start;
47              
48             sub checkfunc($$)
49             {
50             my( $name, $sth ) = @_;
51             my $stat = -1; # 0=normal, 0!=error
52              
53             my( $row ) = $sth->fetchrow_array();
54             if( $row=~m/^\d\d\d\d-\d\d-\d\d$/ ){
55             $stat = 0; # normal
56             } else {
57             $stat = 1; # error
58             }
59              
60             return $stat;
61             }
62              
63             sub errorfunc($$)
64             {
65             my( $name, $db_sid ) = @_;
66             &send_error_mail( 'DB', $name, $db_sid );
67             }
68              
69             sub recoverfunc($$)
70             {
71             my( $name, $db_sid ) = @_;
72             &send_recover_mail( 'DB', $name, $db_sid );
73             }
74              
75             =head1 DESCRIPTION
76              
77             DBIでアクセスできるデータベースを死活監視します。
78              
79             =head1 METHODS
80              
81             =head2 Constractor
82              
83             TRD::Watch::DBのコンストラクタです。
84              
85             =head3 $db_th = new TRD::Watch::DB( $name [, '-db_sid' => $db_sid] [, '-db_user' => $db_user] [, '-db_pass' => $db_pass] [, '-sql' => $sql] [, '-errorfunc' => \$errorfunc] [, '-recoverfunc' => \$recoverfunc] [, '-checkfunc' => \$checkfunc] [, '-timeout' => $timeout] [, '-interval' => $interval] );
86              
87             TRD::Watch::DBオブジェクトを作成するコンストラクタです。
88              
89             最初の引数は、オブジェクトの名前で、必須です。
90             その他のオプションは、省略可能です。
91              
92             =over 4
93              
94             =item '-db_sid'
95              
96             DBIに設定するデータソースを指定します。
97              
98             =item '-db_user'
99              
100             DBIに設定するユーザ名を指定します。
101              
102             =item '-db_pass'
103              
104             DBIに設定するパスワードを指定します。
105              
106             =item '-sql'
107              
108             死活監視で発行するSQL文を指定します。
109              
110             =item '-errorfunc'
111              
112             障害が発生したときに呼び出されるコールバック関数を指定します。
113              
114             =item '-recoverfunc'
115              
116             障害が回復したときに呼び出されるコールバック関数を指定します。
117              
118             =item '-checkfunc'
119              
120             監視を行うコールバック関数を指定します。
121              
122             =item '-timeout'
123              
124             監視のタイムアウトを秒で指定します。
125              
126             =item '-interval'
127              
128             死活監視の間隔を秒で指定します。
129              
130             =back
131              
132             =cut
133             #=======================================================================
134             sub new
135             {
136             my( $pkg, $name, %opt ) = @_;
137              
138             bless {
139             name => $name,
140             db_sid => exists($opt{'-db_sid'}) ? $opt{'-db_sid'} : $default_db_sid,
141             db_user => exists($opt{'-db_user'}) ? $opt{'-db_user'} : $default_db_user,
142             db_pass => exists($opt{'-db_pass'}) ? $opt{'-db_pass'} : $default_db_pass,
143             sql => exists($opt{'-sql'}) ? $opt{'-sql'} : $default_sql,
144             errorfunc => exists($opt{'-errorfunc'}) ? $opt{'-errorfunc'} : \&default_errorfunc,
145             recoverfunc => exists($opt{'-recoverfunc'}) ? $opt{'-recoverfunc'} : \&default_recuverfunc,
146             checkfunc => exists($opt{'-checkfunc'}) ? $opt{'-checkfunc'} : \&default_checkfunc,
147             timeout => exists($opt{'-timeout'}) ? $opt{'-timeout'} : $default_timeout,
148             interval => exists($opt{'-interval'}) ? $opt{'-interval'} : $default_interval,
149             sleepcron => undef,
150             pid => undef,
151             start => 0,
152             }, $pkg;
153             }
154              
155             =head2 setName( $name )
156              
157             オブジェクトの名前を設定します。
158              
159             =head3 $db_th->setName( 'test' );
160              
161             死活監視のコールバック関数が呼ばれる際に名前がわたされます。
162             死活監視が開始されていた場合、再起動されます。
163              
164             =cut
165             #=======================================================================
166             sub setName
167             {
168             my $self = shift;
169             my $name = (@_) ? shift : '';
170             $self->{'name'} = $name;
171             if( $self->{'start'} ){
172             $self->stop;
173             $self->start;
174             }
175             }
176              
177             =head2 setDbSid( $db_sid )
178              
179             DBIに指定するデータソースを設定します。
180              
181             =head3 $db_th->setDbSid( 'Oracle:emp' );
182              
183             死活監視のコールバック関数が呼ばれる際にデータソース名がわたされます。
184             死活監視が開始されていた場合、再起動されます。
185              
186             =cut
187             #=======================================================================
188             sub setDbSid
189             {
190             my $self = shift;
191             my $db_sid = (@_) ? shift : $default_db_sid;
192             $self->{'db_sid'} = $db_sid;
193             if( $self->{'start'} ){
194             $self->stop;
195             $self->start;
196             }
197             }
198              
199             =head2 setDbUser( $db_user )
200              
201             DBIに指定するユーザ名を設定します。
202              
203             =head3 $db_th->setDbUser( 'sccot' );
204              
205             死活監視が開始されていた場合、再起動されます。
206              
207             =cut
208             #=======================================================================
209             sub setDbUser
210             {
211             my $self = shift;
212             my $db_user = (@_) ? shift : $default_db_user;
213             $self->{'db_user'} = $db_user;
214             if( $self->{'start'} ){
215             $self->stop;
216             $self->start;
217             }
218             }
219              
220             =head2 setDbPassword( $db_password )
221              
222             DBIに指定するパスワードを設定します。
223              
224             =head3 $db_th->setDbPassword( 'tiger' );
225              
226             死活監視が開始されていた場合、再起動されます。
227              
228             =cut
229             #=======================================================================
230             sub setDbPassword
231             {
232             my $self = shift;
233             my $db_pass = (@_) ? shift : $default_db_pass;
234             $self->{'db_pass'} = $db_pass;
235             if( $self->{'start'} ){
236             $self->stop;
237             $self->start;
238             }
239             }
240              
241             =head2 setSql( $sql )
242              
243             死活監視で発行するSQLを設定します。
244              
245             =head3 $db_th->setSql( q!select sysdate from dual! );
246              
247             死活監視が開始されていた場合、再起動されます。
248              
249             =cut
250             #=======================================================================
251             sub setSql
252             {
253             my $self = shift;
254             my $sql = (@_) ? shift : $default_sql;
255             $self->{'sql'} = $sql;
256             if( $self->{'start'} ){
257             $self->stop;
258             $self->start;
259             }
260             }
261              
262             =head2 setWatchSleep( $name, $datetime, $sleeptime );
263              
264             死活監視を休止する時間を設定します。
265              
266             =head3 $db_th->setWatchSleep( 'cron time sleep', '59 3 * * *', 60*60 );
267              
268             設定した時間、死活監視を休止(sleep)し、計画的な障害検知を回避します。
269              
270             =over 4
271              
272             =item $name
273              
274             休止に名前を設定します。
275              
276             =item $datetime
277              
278             休止する開始時間をcronのような形式で設定します。
279             5つの空白で区切られた値を設定します。
280             詳しくはcrontabを参考にしてください。
281              
282             =item $sleeptime
283              
284             休止時間を秒で設定します。
285             死活監視が開始されていた場合、再起動されます。
286              
287             =back
288              
289             =cut
290             #=======================================================================
291             sub setWatchSleep($$$$)
292             {
293             my( $self, $name, $datetime, $sleeptime ) = @_;
294              
295             my $item = {
296             name => $name,
297             datetime => $datetime,
298             sleeptime => $sleeptime,
299             };
300             push( @{$self->{'sleepcron'}}, $item );
301              
302             if( $self->{'start'} ){
303             $self->stop;
304             $self->start;
305             }
306             }
307              
308             =head2 setTimeout( $timeout );
309              
310             監視のタイムアウトを設定します。
311              
312             =head3 $db_th->setTimeout( 30 );
313              
314             DBからタイムアウト値までに返答が来ない場合、障害として扱います。
315             死活監視が開始されていた場合、再起動されます。
316              
317             =cut
318             #=======================================================================
319             sub setTimeout
320             {
321             my $self = shift;
322             my $timeout = (@_) ? shift : $default_timeout;
323             $self->{'timeout'} = $timeout;
324             if( $self->{'start'} ){
325             $self->stop;
326             $self->start;
327             }
328             }
329              
330             =head2 setInterval( $interval )
331              
332             死活監視の間隔を設定します。
333              
334             =head3 $db_th->setInterval( 60 );
335              
336             死活監視が開始されていた場合、再起動されます。
337              
338             =cut
339             #=======================================================================
340             sub setInterval
341             {
342             my $self = shift;
343             my $interval = (@_) ? shift : $default_interval;
344             $self->{'interval'} = $interval;
345             if( $self->{'start'} ){
346             $self->stop;
347             $self->start;
348             }
349             }
350              
351             =head2 setErrorFunc( \&errorfunc )
352              
353             障害が発生したときに呼ばれるコールバック関数を指定します。
354              
355             =head3 $db_th->setErrorFunc( \&errorfunc );
356              
357             コールバック関数には死活監視の名前($name)とデータソース名($db_sid)が渡されます。
358             死活監視が開始されていた場合、再起動されます。
359              
360             =cut
361             #=======================================================================
362             sub setErrorFunc
363             {
364             my $self = shift;
365             my $func = (@_) ? shift : undef;
366             $self->{'errorfunc'} = $func;
367             if( $self->{'start'} ){
368             $self->stop;
369             $self->start;
370             }
371             }
372              
373             =head2 setRecoverFunc( \&recoverfunc )
374              
375             障害が回復したときに呼ばれるコールバック関数を設定します。
376              
377             =head3 $db_th->setRecoverFunc( \&recoverfunc );
378              
379             コールバック関数には死活監視の名前($name)とデータソース名($db_sid)が渡されます。
380             死活監視が開始されていた場合、再起動されます。
381              
382             =cut
383             #=======================================================================
384             sub setRecoverFunc
385             {
386             my $self = shift;
387             my $func = (@_) ? shift : \&default_recoverfunc;
388             $self->{'recoverfunc'} = $func;
389             if( $self->{'start'} ){
390             $self->stop;
391             $self->start;
392             }
393             }
394              
395             =head2 setCheckFunc( \&checkfunc )
396              
397             死活監視を行うコールバック関数を設定します。
398              
399             =head3 $db_th->setCheckFunc( \&checkfunc );
400              
401             コールバック関数には死活監視の名前($name)とDBIからのステートメントハンドル($sth)が渡されます。
402             死活監視が開始されていた場合、再起動されます。
403              
404             =cut
405             #=======================================================================
406             sub setCheckFunc
407             {
408             my $self = shift;
409             my $func = (@_) ? shift : undef;
410             $self->{'checkfunc'} = $func;
411             if( $self->{'start'} ){
412             $self->stop;
413             $self->start;
414             }
415             }
416              
417             =head2 start
418              
419             死活監視を開始します。
420              
421             =head3 $db_th->start();
422              
423             死活監視を開始し、checkfuncがinterval間隔で呼び出されます。
424             障害が発生するとerrorfuncが呼び出され、障害が回復するとrecoverfuncが呼び出されます。
425              
426             =cut
427             #=======================================================================
428             sub start
429             {
430             my $self = shift;
431              
432             my $stat = 1;
433             if( $self->{'start'} ){
434             dlog( "already started:". $self->{'name'} );
435             } else {
436             my $pid = threads->new( \&db_thread, $self );
437             $self->{'pid'} = $pid;
438             $self->{'start'} = 1;
439             $stat = 0;
440             sleep( 0.1 );
441             }
442              
443             return $stat;
444             }
445              
446             =head2 stop
447              
448             死活監視を停止します。
449              
450             =head3 $db_th->stop();
451              
452             死活監視を停止します。
453              
454             =cut
455             #=======================================================================
456             sub stop
457             {
458             my $self = shift;
459              
460             my $stat = 1;
461             if( !$self->{'start'} ){
462             dlog( "already stoped:". $self->{'name'} );
463             } else {
464             $self->{'pid'}->kill('TERM');
465             $self->{'pid'}->detach();
466             $self->{'pid'} = undef;
467             $self->{'start'} = 0;
468             $stat = 0;
469             sleep( 0.1 );
470             }
471              
472             return $stat;
473             }
474              
475             =head2 db_thread
476              
477             内部関数:死活監視を行うスレッド関数
478             監視はforkで起動される。
479              
480             =cut
481             #=======================================================================
482             sub db_thread
483             {
484             my $self = shift;
485             my $stat = -1;
486             my $old_stat = undef;
487              
488             $SIG{'TERM'} = sub { dlog( "term:". $self->{'name'} ); threads->exit(); };
489              
490             my $pid;
491             my $t;
492             my $func;
493             my $sleeptime = 0;
494              
495             while( 1 ){
496             if( $pid = fork ){
497             $t = 0;
498             while( 1 ){
499             if( waitpid( $pid, &POSIX::WNOHANG ) != 0 ){
500             $stat = $? >> 8;
501             last;
502             } else {
503             # running
504             if( $t > $self->{'timeout'} ){
505             # timeout
506             dlog( "timeout:". $self->{'name'} );
507             kill SIGKILL, $pid;
508             kill SIGTERM, $pid;
509             waitpid( $pid, 0 );
510             $stat = 2;
511             last;
512             }
513             }
514             sleep( 0.1 );
515             $t += 0.1;
516             }
517             } else {
518             # forked proc
519             $stat = &db( $self );
520             POSIX::_exit( $stat );
521             }
522              
523             if( defined( $old_stat ) ){
524             if( $old_stat != $stat ){
525             $func = undef;
526             if( $stat ){
527             $func = $self->{'errorfunc'};
528             } else {
529             $func = $self->{'recoverfunc'};
530             }
531             if( ref( $func ) eq 'CODE' ){
532             &{$func}( $self->{'name'}, $self->{'db_sid'} );
533             }
534             }
535             }
536             $old_stat = $stat;
537              
538             $sleeptime = ($self->{'interval'} - $t);
539             $sleeptime += &checkSleep( $self );
540             $sleeptime = 0 if( $sleeptime < 0 );
541             sleep( $sleeptime );
542             }
543              
544             return $stat;
545             }
546              
547             =head2 checkSleep
548              
549             内部関数:休止時期を取得する。
550              
551             =cut
552             #=======================================================================
553             sub checkSleep
554             {
555             my $self = shift;
556             my $cron;
557              
558             my $name;
559             my $datetime;
560             my $sleeptime;
561              
562             my( $cmin, $chour, $cday, $cmonth, $cwday );
563             my $stat = 0;
564             my $addsleep = 0;
565              
566             my( $sec, $min, $hour, $mday, $month, $year, $wday, $yday, $isdst ) =
567             localtime( time );
568              
569             $month++;
570             $year += 1900;
571              
572             foreach $cron ( @{$self->{'sleepcron'}} ){
573             $name = $cron->{'name'};
574             $datetime = $cron->{'datetime'};
575             $sleeptime = $cron->{'sleeptime'};
576              
577             if( $datetime=~m/^(.+)\s+(.+)\s+(.+)\s+(.+)\s+(.+)$/ ){
578             $cmin = $1;
579             $chour = $2;
580             $cday = $3;
581             $cmonth = $4;
582             $cwday = $5;
583              
584             $stat = 1;
585             $stat = 0 if( ( $cmin ne '*' )&&( $cmin != $min ) );
586             $stat = 0 if( ( $chour ne '*' )&&( $chour != $hour ) );
587             $stat = 0 if( ( $cday ne '*' )&&( $cday != $mday ) );
588             $stat = 0 if( ( $cmonth ne '*' )&&( $cmonth != $month ) );
589             $stat = 0 if( ( $cwday ne '*' )&&( $cwday != $wday ) );
590              
591             if( $stat ){
592             $addsleep = $sleeptime;
593             last;
594             }
595             }
596             }
597              
598             return $addsleep;
599             }
600              
601             =head2 db
602              
603             内部関数:監視を行う。
604              
605             =cut
606             #=======================================================================
607             sub db
608             {
609             my $self = shift;
610             $SIG{'KILL'} = sub { threads->exit(-1); };
611             $SIG{'TERM'} = sub { exit 2; };
612              
613             my $stat = 1;
614              
615             my $cmd;
616             my $dbh;
617             my $sth;
618             my $func;
619             my $db_val;
620             if( !$self->{'db_sid'} ){
621             $stat = 1;
622             } else {
623             $cmd = q!
624             $dbh = DBI->connect(
625             "DBI:". $self->{'db_sid'},
626             $self->{'db_user'},
627             $self->{'db_pass'},
628             {
629             RaiseError => 1
630             },
631             );
632             $sth = $dbh->prepare( $self->{'sql'} );
633             $sth->execute();
634              
635             $func = $self->{'checkfunc'};
636             if( ref( $func ) eq 'CODE' ){
637             $stat = &{$func}( $self->{'name'}, $sth );
638             } else {
639             $stat = 3;
640             }
641             $sth->finish;
642             $dbh->disconnect;
643             !;
644             eval( $cmd ); ## no crtic
645             if( $@ ne '' ){
646             $stat = 1;
647             }
648             }
649              
650             return $stat;
651             }
652              
653             =head2 default_errorfunc( $name, $db_sid )
654              
655             内部関数:デフォルトの要害発生時のコールバック関数。
656              
657             =cut
658             #=======================================================================
659             sub default_errorfunc($$)
660             {
661             my( $name, $db_sid ) = @_;
662              
663             print STDERR "DB:ERROR:${name}:${db_sid}\n";
664             }
665              
666             =head2 default_recoverfunc( $name, $db_sid )
667              
668             内部関数:デフォルトの障害回復時のコールバック関数。
669              
670             =cut
671             #=======================================================================
672             sub default_recoverfunc($$)
673             {
674             my( $name, $db_sid ) = @_;
675              
676             print STDERR "DB:RECOVER:${name}:${db_sid}\n";
677             }
678              
679             =head2 default_checkfunc( $name, $db_sid )
680              
681             内部関数:デフォルトの監視コールバック関数。
682              
683             =cut
684             #=======================================================================
685             sub default_checkfunc($$)
686             {
687             my( $name, $sth ) = @_;
688             my $stat = 1; # 障害
689              
690             my( $value ) = $sth->fetchrow_array();
691             if( $value=~m/^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d$/ ){
692             $stat = 0; # 障害なし
693             }
694              
695             return $stat;
696             }
697              
698             # Preloaded methods go here.
699              
700             1;
701             __END__