1
package DBD::MySQL::Cluster;
8
my %ERRORS=( OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 );
9
my %ERRORCODES=( 0 => 'OK', 1 => 'WARNING', 2 => 'CRITICAL', 3 => 'UNKNOWN' );
13
our $scream = 0; # scream if something is not implemented
14
our $access = "dbi"; # how do we access the database.
15
our $my_modules_dyn_dir = ""; # where we look for self-written extensions
18
my $initerrors = undef;
21
push(@clusters, shift);
28
sub return_first_cluster() {
38
hostname => $params{hostname},
39
port => $params{port},
40
username => $params{username},
41
password => $params{password},
42
timeout => $params{timeout},
43
warningrange => $params{warningrange},
44
criticalrange => $params{criticalrange},
53
if ($self->connect(%params)) {
54
DBD::MySQL::Cluster::add_cluster($self);
65
foreach (split /\n/, $self->{show}) {
66
if (/\[(\w+)\((\w+)\)\]\s+(\d+) node/) {
68
} elsif (/id=(\d+)(.*)/) {
69
push(@{$self->{nodes}}, DBD::MySQL::Cluster::Node->new(
78
if ($params{mode} =~ /^cluster::ndbdrunning/) {
79
foreach my $node (@{$self->{nodes}}) {
80
$node->{type} eq "NDB" && $node->{status} eq "running" && $self->{ndbd_nodes}++;
81
$node->{type} eq "MGM" && $node->{status} eq "running" && $self->{ndb_mgmd_nodes}++;
82
$node->{type} eq "API" && $node->{status} eq "running" && $self->{mysqld_nodes}++;
85
printf "broken mode %s\n", $params{mode};
91
my $message = shift || "";
92
printf "%s %s\n", $message, Data::Dumper::Dumper($self);
100
if (! $self->{nagios_level}) {
101
if ($params{mode} =~ /^cluster::ndbdrunning/) {
102
foreach my $node (grep { $_->{type} eq "NDB"} @{$self->{nodes}}) {
103
next if $params{selectname} && $params{selectname} ne $_->{id};
104
if (! $node->{connected}) {
105
$self->add_nagios_critical(
106
sprintf "ndb node %d is not connected", $node->{id});
110
foreach my $node (grep { $_->{type} eq "API"} @{$self->{nodes}}) {
111
next if $params{selectname} && $params{selectname} ne $_->{id};
112
if (! $node->{connected}) {
113
$self->add_nagios_critical(
114
sprintf "api node %d is not connected", $node->{id});
119
$self->add_nagios_ok("all ndb nodes are connected");
122
$self->add_nagios_ok("all api nodes are connected");
126
$self->add_perfdata(sprintf "ndbd_nodes=%d ndb_mgmd_nodes=%d mysqld_nodes=%d",
127
$self->{ndbd_nodes}, $self->{ndb_mgmd_nodes}, $self->{mysqld_nodes});
135
my $nagiosvar = $self."::nagios";
136
my $nagioslevelvar = $self."::nagios_level";
146
$$nagioslevelvar = $ERRORS{OK},
157
$self->{nagios_level} = $ERRORS{OK},
161
sub check_thresholds {
164
my $defaultwarningrange = shift;
165
my $defaultcriticalrange = shift;
166
my $level = $ERRORS{OK};
167
$self->{warningrange} = $self->{warningrange} ?
168
$self->{warningrange} : $defaultwarningrange;
169
$self->{criticalrange} = $self->{criticalrange} ?
170
$self->{criticalrange} : $defaultcriticalrange;
171
if ($self->{warningrange} !~ /:/ && $self->{criticalrange} !~ /:/) {
172
# warning = 10, critical = 20, warn if > 10, crit if > 20
173
$level = $ERRORS{WARNING} if $value > $self->{warningrange};
174
$level = $ERRORS{CRITICAL} if $value > $self->{criticalrange};
175
} elsif ($self->{warningrange} =~ /([\d\.]+):/ &&
176
$self->{criticalrange} =~ /([\d\.]+):/) {
177
# warning = 98:, critical = 95:, warn if < 98, crit if < 95
178
$self->{warningrange} =~ /([\d\.]+):/;
179
$level = $ERRORS{WARNING} if $value < $1;
180
$self->{criticalrange} =~ /([\d\.]+):/;
181
$level = $ERRORS{CRITICAL} if $value < $1;
185
# syntax error must be reported with returncode -1
193
push(@{$self->{nagios}->{messages}->{$level}}, $message);
194
# recalc current level
195
foreach my $llevel qw(CRITICAL WARNING UNKNOWN OK) {
196
if (scalar(@{$self->{nagios}->{messages}->{$ERRORS{$llevel}}})) {
197
$self->{nagios_level} = $ERRORS{$llevel};
205
$self->add_nagios($ERRORS{OK}, $message);
208
sub add_nagios_warning {
211
$self->add_nagios($ERRORS{WARNING}, $message);
214
sub add_nagios_critical {
217
$self->add_nagios($ERRORS{CRITICAL}, $message);
220
sub add_nagios_unknown {
223
$self->add_nagios($ERRORS{UNKNOWN}, $message);
229
push(@{$self->{nagios}->{perfdata}}, $data);
235
foreach my $level (0..3) {
236
foreach (@{$child->{nagios}->{messages}->{$level}}) {
237
$self->add_nagios($level, $_);
239
#push(@{$self->{nagios}->{messages}->{$level}},
240
# @{$child->{nagios}->{messages}->{$level}});
242
push(@{$self->{nagios}->{perfdata}}, @{$child->{nagios}->{perfdata}});
246
sub calculate_result {
248
if ($ENV{NRPE_MULTILINESUPPORT} &&
249
length join(" ", @{$self->{nagios}->{perfdata}}) > 200) {
250
foreach my $level ("CRITICAL", "WARNING", "UNKNOWN", "OK") {
252
if (scalar(@{$self->{nagios}->{messages}->{$ERRORS{$level}}})) {
253
$self->{nagios_message} .=
254
"\n".join("\n", @{$self->{nagios}->{messages}->{$ERRORS{$level}}});
257
$self->{nagios_message} =~ s/^\n//g;
258
$self->{perfdata} = join("\n", @{$self->{nagios}->{perfdata}});
260
foreach my $level ("CRITICAL", "WARNING", "UNKNOWN", "OK") {
262
if (scalar(@{$self->{nagios}->{messages}->{$ERRORS{$level}}})) {
263
$self->{nagios_message} .=
264
join(", ", @{$self->{nagios}->{messages}->{$ERRORS{$level}}}).", ";
267
$self->{nagios_message} =~ s/, $//g;
268
$self->{perfdata} = join(" ", @{$self->{nagios}->{perfdata}});
270
foreach my $level ("OK", "UNKNOWN", "WARNING", "CRITICAL") {
271
if (scalar(@{$self->{nagios}->{messages}->{$ERRORS{$level}}})) {
272
$self->{nagios_level} = $ERRORS{$level};
280
if ($DBD::MySQL::Cluster::verbose) {
281
printf "%s %s\n", $msg, ref($self);
289
$self->{tic} = Time::HiRes::time();
291
use POSIX ':signal_h';
292
local $SIG{'ALRM'} = sub {
295
my $mask = POSIX::SigSet->new( SIGALRM );
296
my $action = POSIX::SigAction->new(
297
sub { die "connection timeout\n" ; }, $mask);
298
my $oldaction = POSIX::SigAction->new();
299
sigaction(SIGALRM ,$action ,$oldaction );
300
alarm($self->{timeout} - 1); # 1 second before the global unknown timeout
301
my $ndb_mgm = "ndb_mgm";
302
$params{hostname} = "127.0.0.1" if ! $params{hostname};
303
$ndb_mgm .= sprintf " --ndb-connectstring=%s", $params{hostname}
304
if $params{hostname};
305
$ndb_mgm .= sprintf ":%d", $params{port}
307
$self->{show} = `$ndb_mgm -e show 2>&1`;
309
$self->add_nagios_critical("ndb_mgm failed to execute $!");
311
$self->add_nagios_critical("ndb_mgm failed to execute $!");
312
} elsif ($? >> 8 != 0) {
313
$self->add_nagios_critical("ndb_mgm unable to connect");
315
if ($self->{show} !~ /Cluster Configuration/) {
316
$self->add_nagios_critical("got no cluster configuration");
323
$self->{errstr} = $@;
324
$self->{errstr} =~ s/at $0 .*//g;
325
chomp $self->{errstr};
326
$self->add_nagios_critical($self->{errstr});
329
$self->{tac} = Time::HiRes::time();
336
$self->{trace} = -f "/tmp/check_mysql_health.trace" ? 1 : 0;
337
if ($self->{verbose}) {
338
printf("%s: ", scalar localtime);
341
if ($self->{trace}) {
342
my $logfh = new IO::File;
343
$logfh->autoflush(1);
344
if ($logfh->open("/tmp/check_mysql_health.trace", "a")) {
345
$logfh->printf("%s: ", scalar localtime);
346
$logfh->printf($format, @_);
347
$logfh->printf("\n");
355
my $handle1 = "null";
356
my $handle2 = "null";
357
if (defined $self->{handle}) {
358
$handle1 = ref($self->{handle});
359
if (defined $self->{handle}->{handle}) {
360
$handle2 = ref($self->{handle}->{handle});
363
$self->trace(sprintf "DESTROY %s with handle %s %s", ref($self), $handle1, $handle2);
364
if (ref($self) eq "DBD::MySQL::Cluster") {
366
$self->trace(sprintf "DESTROY %s exit with handle %s %s", ref($self), $handle1, $handle2);
367
if (ref($self) eq "DBD::MySQL::Cluster") {
368
#printf "humpftata\n";
376
mkdir $params{statefilesdir} unless -d $params{statefilesdir};
377
my $statefile = sprintf "%s/%s_%s",
378
$params{statefilesdir}, $params{hostname}, $params{mode};
379
$extension .= $params{differenciator} ? "_".$params{differenciator} : "";
380
$extension .= $params{socket} ? "_".$params{socket} : "";
381
$extension .= $params{port} ? "_".$params{port} : "";
382
$extension .= $params{database} ? "_".$params{database} : "";
383
$extension .= $params{tablespace} ? "_".$params{tablespace} : "";
384
$extension .= $params{datafile} ? "_".$params{datafile} : "";
385
$extension .= $params{name} ? "_".$params{name} : "";
386
$extension =~ s/\//_/g;
387
$extension =~ s/\(/_/g;
388
$extension =~ s/\)/_/g;
389
$extension =~ s/\*/_/g;
390
$extension =~ s/\s/_/g;
391
$statefile .= $extension;
392
$statefile = lc $statefile;
393
open(STATE, ">$statefile");
394
if ((ref($params{save}) eq "HASH") && exists $params{save}->{timestamp}) {
395
$params{save}->{localtime} = scalar localtime $params{save}->{timestamp};
397
printf STATE Data::Dumper::Dumper($params{save});
399
$self->debug(sprintf "saved %s to %s",
400
Data::Dumper::Dumper($params{save}), $statefile);
407
my $statefile = sprintf "%s/%s_%s",
408
$params{statefilesdir}, $params{hostname}, $params{mode};
409
$extension .= $params{differenciator} ? "_".$params{differenciator} : "";
410
$extension .= $params{socket} ? "_".$params{socket} : "";
411
$extension .= $params{port} ? "_".$params{port} : "";
412
$extension .= $params{database} ? "_".$params{database} : "";
413
$extension .= $params{tablespace} ? "_".$params{tablespace} : "";
414
$extension .= $params{datafile} ? "_".$params{datafile} : "";
415
$extension .= $params{name} ? "_".$params{name} : "";
416
$extension =~ s/\//_/g;
417
$extension =~ s/\(/_/g;
418
$extension =~ s/\)/_/g;
419
$extension =~ s/\*/_/g;
420
$extension =~ s/\s/_/g;
421
$statefile .= $extension;
422
$statefile = lc $statefile;
423
if ( -f $statefile) {
431
$self->debug(sprintf "load %s", Data::Dumper::Dumper($VAR1));
441
my %params = %{$pparams};
443
my $last_values = $self->load_state(%params) || eval {
444
my $empty_events = {};
446
$empty_events->{$_} = 0;
448
$empty_events->{timestamp} = 0;
452
$self->{'delta_'.$_} = $self->{$_} - $last_values->{$_};
453
$self->debug(sprintf "delta_%s %f", $_, $self->{'delta_'.$_});
455
$self->{'delta_timestamp'} = time - $last_values->{timestamp};
456
$params{save} = eval {
457
my $empty_events = {};
459
$empty_events->{$_} = $self->{$_};
461
$empty_events->{timestamp} = time;
464
$self->save_state(%params);
467
sub requires_version {
470
my @instances = DBD::MySQL::Cluster::return_clusters();
471
my $instversion = $instances[0]->{version};
472
if (! $self->version_is_minimum($version)) {
473
$self->add_nagios($ERRORS{UNKNOWN},
474
sprintf "not implemented/possible for MySQL release %s", $instversion);
478
sub version_is_minimum {
479
# the current version is newer or equal
483
my @instances = DBD::MySQL::Cluster::return_clusters();
484
my @v1 = map { $_ eq "x" ? 0 : $_ } split(/\./, $version);
485
my @v2 = split(/\./, $instances[0]->{version});
486
if (scalar(@v1) > scalar(@v2)) {
487
push(@v2, (0) x (scalar(@v1) - scalar(@v2)));
488
} elsif (scalar(@v2) > scalar(@v1)) {
489
push(@v1, (0) x (scalar(@v2) - scalar(@v1)));
491
foreach my $pos (0..$#v1) {
492
if ($v2[$pos] > $v1[$pos]) {
495
} elsif ($v2[$pos] < $v1[$pos]) {
500
#printf STDERR "check if %s os minimum %s\n", join(".", @v2), join(".", @v1);
506
my @instances = DBD::MySQL::Cluster::return_clusters();
507
return (lc $instances[0]->{parallel} eq "yes") ? 1 : 0;
510
sub instance_thread {
512
my @instances = DBD::MySQL::Cluster::return_clusters();
513
return $instances[0]->{thread};
516
sub windows_cluster {
518
my @instances = DBD::MySQL::Cluster::return_clusters();
519
if ($instances[0]->{os} =~ /Win/i) {
526
sub system_vartmpdir {
528
if ($^O =~ /MSWin/) {
529
return $self->system_tmpdir();
531
return "/var/tmp/check_mysql_health";
535
sub system_oldvartmpdir {
542
if ($^O =~ /MSWin/) {
543
return $ENV{TEMP} if defined $ENV{TEMP};
544
return $ENV{TMP} if defined $ENV{TMP};
545
return File::Spec->catfile($ENV{windir}, 'Temp')
546
if defined $ENV{windir};
554
package DBD::MySQL::Cluster::Node;
558
our @ISA = qw(DBD::MySQL::Cluster);
560
my %ERRORS=( OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 );
561
my %ERRORCODES=( 0 => 'OK', 1 => 'WARNING', 2 => 'CRITICAL', 3 => 'UNKNOWN' );
567
mode => $params{mode},
568
timeout => $params{timeout},
569
type => $params{type},
571
status => $params{status},
574
$self->init(%params);
575
if ($params{type} eq "NDB") {
576
bless $self, "DBD::MySQL::Cluster::Node::NDB";
577
$self->init(%params);
585
if ($self->{status} =~ /@(\d+\.\d+\.\d+\.\d+)\s/) {
587
$self->{connected} = 1;
588
} elsif ($self->{status} =~ /accepting connect from (\d+\.\d+\.\d+\.\d+)/) {
590
$self->{connected} = 0;
592
if ($self->{status} =~ /starting,/) {
593
$self->{status} = "starting";
594
} elsif ($self->{status} =~ /shutting,/) {
595
$self->{status} = "shutting";
597
$self->{status} = $self->{connected} ? "running" : "dead";
602
package DBD::MySQL::Cluster::Node::NDB;
606
our @ISA = qw(DBD::MySQL::Cluster::Node);
608
my %ERRORS=( OK => 0, WARNING => 1, CRITICAL => 2, UNKNOWN => 3 );
609
my %ERRORCODES=( 0 => 'OK', 1 => 'WARNING', 2 => 'CRITICAL', 3 => 'UNKNOWN' );
614
if ($self->{status} =~ /Nodegroup:\s*(\d+)/) {
615
$self->{nodegroup} = $1;
617
$self->{master} = ($self->{status} =~ /Master\)/) ? 1 : 0;