-
Notifications
You must be signed in to change notification settings - Fork 4
/
check_end2end.pl
executable file
·1375 lines (987 loc) · 39.5 KB
/
check_end2end.pl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/perl
# vim: se ts=4 et syn=perl:
# check_end2end.pl - Simple configurable end-to-end probe plugin for Nagios
#
# Copyright (C) 2016-2017 Giacomo Montagner <giacomo@entirelyunlike.net>
#
# This program is free software: you can redistribute it and/or modify
# it under the same terms as Perl itself, either Perl version 5.8.4 or,
# at your option, any later version of Perl 5 you may have available.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
#
#
# CHANGELOG:
#
# 2016-05-19T08:46:55+0200
# First release.
#
# 2016-05-24T11:13:44+0200 v1.0.1
# - Removed "Export" as parent to Monitoring::Plugin::End2end;
# - Added TODO to make steps optional
#
# 2016-05-25T09:06:44+0200 v1.0.2
# - Filled in contact/bug/copyright details in perl POD documentation
#
# 2016-05-26T14:19:26+0200 v1.1.0
# - Added support for "on_failure" configuration directive
# - Added more documentation about configuration file format
#
# 2016-05-29T03:05:41+0200 v1.2.0
# - Added -e flag to interpolate environment variables
# - Added -E flag to allow empty vars
# - Added --var flag to pass variables on command line
#
# 2016-06-10T23:06:39+0200 v1.3.0
# - Added -D flag to dump the downloaded pages to a directory
# (with debugging purposes)
#
# 2016-07-20T11:07:13+0200 v1.4.0
# - Added grep_re|grep_str options to configuration file, along with
# on_grep_failure.
#
# 2016-07-22T09:21:12+0200 v1.4.1
# - Modified default behaviour in case of pattern-matching-failed to
# a fatal error (immediate exit on match failure, unless severity
# is lowered using on_grep_failure).
#
# 2016-08-03T03:21:53+02:00 v1.5.0
# - Added support for proxy
# - Added support for basic http authentication
#
# 2017-01-17T14:03:08+01:00 v1.5.1
# - Added .html extension to the name of the files generated via the
# -D flag.
#
# 2017-01-17T15:26:33+01:00 v1.6.0
# - Added grep_err_str/grep_err_re/on_err_grep_match parameters to the
# configuration, to allow for a negative (error) string match.
#
# 2017-02-24T11:27:28+01:00 v1.6.1
# - Copyright update 2017
# - Version updates throughout documentation.
#
#
use strict;
use warnings;
use version; our $VERSION = qv(1.6.1);
use v5.010.001;
use utf8;
use File::Basename qw(basename);
use Config::General;
use Monitoring::Plugin;
use Time::HiRes qw(time);
use subs qw(
debug
);
# ------------------------------------------------------------------------------
# Globals
# ------------------------------------------------------------------------------
our $plugin_name = basename( $0 );
# ------------------------------------------------------------------------------
# Command line initialization
# ------------------------------------------------------------------------------
# This plugin's initialization - see https://metacpan.org/pod/Monitoring::Plugin
# --verbose, --help, --usage, --timeout and --host are defined automatically.
my $np = Monitoring::Plugin::End2end->new(
usage => "Usage: %s [-v|--verbose] [-t <timeout>] [-d|--debug] [-M|--manual] "
. "[-D|--dumpPages=/dump/directory] [-e|--useEnvVars] [-E|--allowEmptyVars] "
. "[-c|--critical=<threshold>] [-w|--warning=<threshold>] "
. "[-C|--totcritical=<threshold>] [-W|--totwarning=<threshold>] "
. "[--var VAR=VALUE [--var VAR2=VALUE2 [ ... ] ] ] "
. "-f|--configFile=<cfgfile>",
version => $VERSION,
blurb => "This plugin uses LWP::UserAgent to fake a website navigation"
. " as configured in the named configuration file.",
);
# Command line options
$np->add_arg(
spec => 'debug|d',
help => qq{-d, --debug\n Print debugging messages to STDERR. }
. qq{Package Data::Dumper is required for debug.},
);
$np->add_arg(
spec => 'dumpPages|D=s',
help => qq{-D, --dumpPages=/dump/directory\n Writes the output of each step }
. qq{into a file under the named destination directory. The name of the file }
. qq{will be the same as the name of the corresponding step. Path::Tiny is }
. qq{required to dump page content.},
);
$np->add_arg(
spec => 'warning|w=s',
help => qq{-w, --warning=INTEGER:INTEGER\n}
. qq{ Warning threshold for each single step.\n}
. qq{ See https://www.monitoring-plugins.org/doc/guidelines.html#THRESHOLDFORMAT }
. qq{for the threshold format, or run $plugin_name -M (requires perldoc executable).},
);
$np->add_arg(
spec => 'critical|c=s',
help => qq{-c, --critical=INTEGER:INTEGER\n}
. qq{ Critical threshold for each single step.\n}
. qq{ See https://www.monitoring-plugins.org/doc/guidelines.html#THRESHOLDFORMAT }
. qq{for the threshold format, or run $plugin_name -M (requires perldoc executable). },
);
$np->add_arg(
spec => 'totwarning|W=s',
help => qq{-W, --totwarning=INTEGER:INTEGER\n}
. qq{ Warning threshold for the whole process.\n}
. qq{ See https://www.monitoring-plugins.org/doc/guidelines.html#THRESHOLDFORMAT }
. qq{for the threshold format, or run $plugin_name -M (requires perldoc executable).},
);
$np->add_arg(
spec => 'totcritical|C=s',
help => qq{-C, --totcritical=INTEGER:INTEGER\n}
. qq{ Critical threshold for the whole process.\n}
. qq{ See https://www.monitoring-plugins.org/doc/guidelines.html#THRESHOLDFORMAT }
. qq{for the threshold format, or run $plugin_name -M (requires perldoc executable). },
);
$np->add_arg(
spec => 'configFile|f=s',
help => qq{-f, --configFile=/path/to/file.\n Configuration }
. qq{of the steps to be performed by this plugin. }
. qq{See "perldoc $plugin_name" for details con configuration format, }
. qq{or run $plugin_name -M},
);
$np->add_arg(
spec => 'useEnvVars|e',
help => qq{-e, --useEnvVars\n}
. qq{ Interpolate variables in configuration file using enviroment variables }
. qq{also. Default: NO. },
);
$np->add_arg(
spec => 'allowEmptyVars|E',
help => qq{-E, --allowEmptyVars\n}
. qq{ By default, Config::General will croak if it tries to interpolate an }
. qq{undefined variable. Use this option to turn off this behaviour.},
);
$np->add_arg(
spec => 'var=s@',
help => qq{--var <VAR=VALUE>\n}
. qq{ Specify this option (even multiple times) to pass variables to this }
. qq{plugin on the command line. These variables will be interpolated in the }
. qq{configuration file, as if they were found inside environment. This automatically }
. qq{turns on --useEnvVars flag.},
);
$np->add_arg(
spec => 'useEnvProxy|P',
help => qq{-P, --useEnvProxy\n}
. qq{ Get proxy configuration from environment variables (you can use --var to pass }
. qq{environment variables like http_proxy/https_proxy, and so on, if they're not in your }
. qq{environment already)},
);
$np->add_arg(
spec => 'manual|M',
help => qq{-M, --manual\n Show plugin manual (requires perldoc executable).},
);
# It would be easy to do this and then die() peacefully from everywhere it's
# required, but it would break any eval{} in the code from here to everywhere,
# so it cannot be done. I document it here so no one else is tempted to do
# this.
# $SIG{ __DIE__ } = sub {
# $np->plugin_die(@_);
# };
# ------------------------------------------------------------------------------
# Command line parsing
# ------------------------------------------------------------------------------
# Parse @ARGV and process standard arguments (e.g. usage, help, version)
$np->getopts;
my $opts = $np->opts();
if ($opts->manual()) {
exec(qq{\$(which perldoc) $0});
}
if ($opts->debug) {
require Data::Dumper;
*debug = sub { say STDERR "DEBUG :: ", @_; };
*ddump = sub { Data::Dumper->Dump( @_ ); };
} else {
*debug = sub { return; };
*ddump = *debug;
}
if ($opts->dumpPages) {
require Path::Tiny;
require File::Spec;
*writepage = sub {
debug qq{Dumping step "$_[0]" to }. $opts->dumpPages();
Path::Tiny::path(
File::Spec->catfile(
$opts->dumpPages(), $_[0] . ".html"
)
)->spew( $_[1] );
};
} else {
*writepage = sub { return; };
}
unless ($opts->configFile()) {
$np->plugin_die("Missing mandatory option: --configFile|-f");
}
my $useEnv = $opts->useEnvVars() || 0;
if (defined( my $vars = $opts->var() )) {
$useEnv = 1;
$np->plugin_die("Cannot parse variables passed via --var flag")
unless ref( $vars ) && ref( $vars ) eq 'ARRAY';
for my $vardef (@$vars) {
my ($name, $val) = split('=', $vardef, 2);
$np->plugin_die("Cannot parse variable definition: $vardef")
unless defined($name) && defined($val);
$ENV{$name} = $val;
}
}
# ------------------------------------------------------------------------------
# External configuration loading
# ------------------------------------------------------------------------------
# Read configuration file
my $conf = Config::General->new(
-ConfigFile => $opts->configFile(),
-InterPolateVars => 1,
-InterPolateEnv => $useEnv,
-StrictVars => ! $opts->allowEmptyVars(),
-ExtendedAccess => 1,
);
if ($conf->exists("Monitoring::Plugin::shortname")) {
$np->shortname( $conf->value("Monitoring::Plugin::shortname") );
}
# ------------------------------------------------------------------------------
# Global configurations
# ------------------------------------------------------------------------------
# Prepare user agent for the requests
my $ua = Monitoring::Plugin::End2end::UserAgent->new( $conf )
or $np->plugin_die("Error initializing UserAgent: ". $Monitoring::Plugin::End2end::UserAgent::reason);
$ua->env_proxy() if $opts->useEnvProxy();
# Get the steps from configuration
my $steps = Steps->new( $conf->hash("Step") );
# Check for thresholds before performing steps
my @step_names = $steps->list();
my $num_steps = @step_names;
my $warns = Thresholds->new( $opts->warning(), @step_names );
debug "WARNING THRESHOLDS: ", ddump([$warns], ['warns']);
my $crits = Thresholds->new( $opts->critical(), @step_names );
debug "CRITICAL THRESHOLDS: ", ddump([$crits], ['crits']);
# ------------------------------------------------------------------------------
# MAIN :: Do the check
# ------------------------------------------------------------------------------
if ($opts->timeout()) {
$SIG{ALRM} = sub {
$np->plugin_die("Operation timed out after ". $opts->timeout(). "s" );
};
alarm $opts->timeout();
}
my $totDuration = 0;
# Perform each configured step
STEP:
for my $step_name ( @step_names ) {
debug "Performing step: ", $step_name;
my $step = $steps->step( $step_name )
or $np->plugin_die("Malformed configuration file -- cannot proceed on step $step_name; error token was: ". $Step::reason);
debug "URL: ", $step->url();
debug "Data: ", ddump([ $step->data() ])
if $step->data();
debug "Method: ", $step->method();
debug "Auth: ", join(":", $step->auth_basic_credentials())
if $step->has_basic_auth;
my $response;
my $method = $step->method();
my $before = time();
if (defined( $step->data() )) {
$response = $ua->$method(
$step->url(),
$step->data(),
);
} else {
$response = $ua->$method( $step->url() );
}
my $after = time();
debug "Cookies: ", $ua->cookie_jar->as_string();
my $duration = sprintf("%.3f", $after - $before);
$totDuration += $duration;
my $warn = $warns->get( $step_name );
my $crit = $crits->get( $step_name );
# -----------------------------------------
# Process result in case of failure of step
# -----------------------------------------
if (! $response->is_success) {
my $level = $step->on_failure();
if ($level == OK) {
$np->add_ok( "Step $step_name failed (". $response->status_line(). ") but was ignored as configured" );
next STEP; # Lowered to non-fatal, go to next step
}
# Set the level
$np->raise_status( $level );
# Add the error to the final output
$np->add_status( $level, "Step $step_name failed (". $response->status_line(). ")" );
# See if error is fatal or not
if ($level < CRITICAL) {
next STEP;
} else {
last STEP;
}
}
# ----------------------------------------------
# Process result in case of success of this step
# ----------------------------------------------
writepage($step_name, $response->decoded_content() );
# Add perfdata regardless of the other conditions
$np->add_perfdata( label => "Step_${step_name}_duration", value => $duration, uom => "s", warning => $warn, critical => $crit );
# First of all, check if the result matches the NEGATIVE pattern (if provided).
# Matching the negative pattern is a fatal error, unless the error level was lowered
# using "on_err_grep_match".
if ($step->has_neg_pattern()) {
debug "Step $step_name has NEGATIVE pattern: ". $step->neg_pattern();
if ( ($response->decoded_content() =~ $step->neg_pattern()) ) {
debug "Negative pattern did match!";
my $level = $step->on_err_grep_match();
if ($level == OK) {
$np->add_ok( "Pattern <". $step->neg_pattern(). "> matched at step $step_name but ignored as configured" );
} else {
$np->raise_status( $level );
$np->add_status( $level, "Pattern <". $step->neg_pattern(). "> matched at step $step_name" );
}
# See if error is fatal or not
last STEP
if $level > WARNING;
}
}
# Second, check if the result matches the POSITIVE pattern (if provided).
# Not matching the patters is a fatal error, unless the error level was lowered
# using "on_grep_failure".
if ($step->has_pattern()) {
debug "Step $step_name has POSITIVE pattern: ". $step->pattern();
if (! ($response->decoded_content() =~ $step->pattern()) ) {
debug "Pattern did not match!";
my $level = $step->on_grep_failure();
if ($level == OK) {
$np->add_ok( "Pattern <". $step->pattern(). "> not matched at step $step_name but ignored as configured" );
} else {
$np->raise_status( $level );
$np->add_status( $level, "Pattern <". $step->pattern(). "> not matched at step $step_name" );
}
# See if error is fatal or not
last STEP
if $level > WARNING;
}
}
# Check step timing
my $status = $np->check_threshold( check => $duration, warning => $warn, critical => $crit );
if ($status == OK) {
$np->add_ok( "Step $step_name took ${duration}s" );
} elsif ($status == WARNING) {
$np->add_warning( "Step $step_name took ${duration}s > ${warn}s" );
} elsif ($status == CRITICAL) {
$np->add_critical( "Step $step_name took ${duration}s > ${crit}s" );
}
}
# Prepare for exit
my $msg = 'Check complete. ';
# Check total duration time against thresholds
my $tc = $opts->totcritical() || '';
my $tw = $opts->totwarning() || '';
$np->add_perfdata( label => "Total_duration", value => $totDuration, uom => "s", warning => $tw, critical => $tc );
my $status = $np->check_threshold( check => $totDuration, warning => $tw, critical => $tc );
if ( $status == CRITICAL ) {
$msg .= "CRITICAL: Total duration was ${totDuration}s > ${tc}s; ";
$np->raise_status( CRITICAL );
} elsif ( $status == WARNING ) {
$msg .= "WARNING Total duration was ${totDuration}s > ${tw}s; ";
$np->raise_status( WARNING );
}
# Build final message
my @crits = $np->criticals();
$msg .= "CRITICAL steps: ". join("; ", @crits). "; "
if @crits;
my @warns = $np->warnings();
$msg .= "WARNING steps: ". join("; ", @warns). "; "
if @warns;
my @oks = $np->oks();
$msg .= "Steps OK: ". join("; ", @oks). "; "
if @oks;
# Finally, exit
$np->plugin_exit( $np->status(), $msg );
###############################################################################
## Monitoring::Plugin extension
###############################################################################
package Monitoring::Plugin::End2end;
use strict;
use warnings;
use Monitoring::Plugin;
use parent qw(
Monitoring::Plugin
);
sub new {
my $class = shift;
my $self = $class->SUPER::new(@_);
$self->{_end2end_status} = 0;
$self->{_end2end_oks} = [];
$self->{_end2end_warnings} = [];
$self->{_end2end_criticals} = [];
return $self;
}
sub add_warning {
my $self = shift;
push @{ $self->{_end2end_warnings} }, @_;
$self->{_end2end_status} = WARNING
unless $self->{_end2end_status} && $self->{_end2end_status} > WARNING;
}
sub add_critical {
my $self = shift;
push @{ $self->{_end2end_criticals} }, @_;
$self->{_end2end_status} = CRITICAL
unless $self->{_end2end_status} && $self->{_end2end_status} > CRITICAL;
}
sub add_ok {
my $self = shift;
push @{ $self->{_end2end_oks} }, @_;
}
sub add_status {
my ($self, $status, $reason) = @_;
if ($status == OK) {
$self->add_ok( $reason );
} elsif ($status == WARNING) {
$self->add_warning( $reason );
} elsif ($status == CRITICAL) {
$self->add_critical( $reason );
}
}
sub status {
return $_[0]->{_end2end_status};
}
sub raise_status {
my ($self, $newStatus) = @_;
$self->{_end2end_status} = $newStatus
if $self->{_end2end_status} < $newStatus;
}
sub oks {
return wantarray ?
@{ $_[0]->{_end2end_oks} } :
$_[0]->{_end2end_oks} ;
}
sub warnings {
return wantarray ?
@{ $_[0]->{_end2end_warnings} } :
$_[0]->{_end2end_warnings} ;
}
sub criticals {
return wantarray ?
@{ $_[0]->{_end2end_criticals} } :
$_[0]->{_end2end_criticals} ;
}
###############################################################################
## DATA HANDLER
###############################################################################
package Data;
use strict;
use warnings;
use URI::URL;
sub new {
my $class = shift;
my $url = URI::URL->new("?".$_[0]);
return bless( { $url->query_form() }, $class );
}
###############################################################################
## STEP HANDLER
###############################################################################
package Step;
use strict;
use warnings;
use Monitoring::Plugin;
sub new {
my $class = shift;
unless( ref( $_[0] ) && ref( $_[0] ) eq 'HASH' ) {
our $reason = "invalid initialization parameters for Step";
return;
}
# Start from the base configuration (if present)
my $step = {};
# In case of errors, do not initialize this object
# (will cause the plugin to die with an error)
unless ( $step->{url} = delete $_[0]->{url} ) {
our $reason = "missing 'url' directive";
return;
}
# Parse binary data if present
if (defined( my $data = delete $_[0]->{binary_data})) {
unless ( $step->{data} = Data->new( $data ) ) {
our $reason = "parsing 'binary_data' failed";
return;
}
}
# Parse on_failure directive if present, otherwise force it
# to CRITICAL
if (exists( $_[0]->{on_failure} )) {
unless (defined( $step->{on_failure} = _parse( 'on_failure', delete $_[0]->{on_failure} ))) {
our $reason = "parsing 'on_failure' failed";
return;
}
} else {
$step->{on_failure} = CRITICAL;
}
# Parse grep_err_str/grep_err_re patterns if present
if (my $grep_err_re = delete $_[0]->{grep_err_re}) {
$step->{neg_pattern} = qr/$grep_err_re/;
$step->{on_err_grep_match} = CRITICAL;
$step->{has_neg_pattern} = 1;
delete $_[0]->{grep_err_str}; # grep_err_re supersedes grep_err_str
} elsif (my $grep_err_str = delete $_[0]->{grep_err_str}) {
$step->{neg_pattern} = quotemeta($grep_err_str);
$step->{on_err_grep_match} = CRITICAL;
$step->{has_neg_pattern} = 1;
}
# Parse on_err_grep_match if present
if (exists( $_[0]->{on_err_grep_match} )) {
main::debug "Parsing: ", $_[0]->{on_err_grep_match};
unless (defined( $step->{on_err_grep_match} = _parse( 'on_err_grep_match', delete $_[0]->{on_err_grep_match} ))) {
our $reason = "parsing 'on_err_grep_match' failed";
return;
}
main::debug "Parsed as: ", $step->{on_err_grep_match};
}
# Parse grep_str/grep_re patterns if present
if (my $grep_re = delete $_[0]->{grep_re}) {
$step->{pattern} = qr/$grep_re/;
$step->{on_grep_failure} = CRITICAL;
$step->{has_pattern} = 1;
delete $_[0]->{grep_str}; # grep_re supersedes grep_str
} elsif (my $grep_str = delete $_[0]->{grep_str}) {
$step->{pattern} = quotemeta($grep_str);
$step->{on_grep_failure} = CRITICAL;
$step->{has_pattern} = 1;
}
# Parse on_grep_failure if present
if (exists( $_[0]->{on_grep_failure} )) {
main::debug "Parsing: ", $_[0]->{on_grep_failure};
unless( defined( $step->{on_grep_failure} = _parse( 'on_grep_failure', delete $_[0]->{on_grep_failure} ) ) ) {
our $reason = "parsing 'on_grep_failure' failed";
return;
}
main::debug "Parsed as: ", $step->{on_grep_failure};
}
# Parse basic authentication if present
if (exists( $_[0]->{auth_basic_user} )) {
$step->{auth_basic_credentials} = [
delete $_[0]->{auth_basic_user},
delete $_[0]->{auth_basic_password} || ''
];
}
# Parse method if present, otherwise force it to "get"
$step->{method} = $_[0]->{method} ? lc( delete $_[0]->{method} ) : 'get';
return bless( $step, $class );
}
sub _parse {
# main::debug "_parse ", $_[0], " / ", $_[1];
my $parsed;
eval {
# Call OK(), WARNING(), CRITICAL() or UNKNOWN()
my $m = uc( $_[1] );
{
no strict "refs";
$parsed = $m->();
}
# main::debug "Value: $parsed";
};
if ($@) {
$Step::reason = "parsing '". $_[0]. "' failed (Caused by: $@)";
return;
}
return $parsed;
}
sub url {
return $_[0]->{url};
}
sub data {
return unless $_[0]->has_data();
# Copy data, do not return internal reference
my %data = %{ $_[0]->{data} };
return \%data;
}
sub has_data {
return exists( $_[0]->{data} ) && defined( $_[0]->{ data } );
}
sub pattern {
return $_[0]->{pattern};
}
sub neg_pattern {
return $_[0]->{neg_pattern};
}
sub has_pattern {
return $_[0]->{has_pattern};
}
sub has_neg_pattern {
return $_[0]->{has_neg_pattern};
}
sub method {
return $_[0]->{method};
}
sub on_failure {
return $_[0]->{on_failure};
}
sub on_grep_failure {
return $_[0]->{on_grep_failure};
}
sub on_err_grep_match {
return $_[0]->{on_err_grep_match};
}
sub auth_basic_credentials {
return unless $_[0]->has_basic_auth();
return @{ $_[0]->{auth_basic_credentials} };
}
sub has_basic_auth {
return (
exists( $_[0]->{auth_basic_credentials} ) and
ref( $_[0]->{auth_basic_credentials} ) eq 'ARRAY'
);
}
###############################################################################
## STEPS HANDLER
###############################################################################
package Steps;
use strict;
use warnings;
sub new {
my $class = shift;
return bless({ @_ }, $class);
}
sub step {
return Step->new( $_[0]->{ $_[1] } );
}
sub list {
return wantarray ?
sort( keys( %{ $_[0] } ) ) :
[ sort( keys( %{ $_[0] } ) ) ];
}
###############################################################################
## THRESHOLDS HANDLER
###############################################################################
package Thresholds;
sub new {
my $class = shift;
my $val = shift || '';
my @names = @_;
my %thr;
if ($val =~ /,/) {
my @thr = split(/\s*,\s*/, $val);
%thr = map { $names[ $_ ] => (defined( $thr[ $_ ] ) ? $thr[ $_ ] : '') } 0..$#names;
} else {
%thr = map { $names[ $_ ] => $val } 0..$#names;
}
return bless( \%thr, $class );
}
sub get {
return $_[0]->{ $_[1] };
}
################################################################################
# User Agent
################################################################################
package Monitoring::Plugin::End2end::UserAgent;
use HTTP::Headers;
use LWP::UserAgent;
use parent ("LWP::UserAgent");
sub new {
my $class = shift;
my $conf = shift;
my $hh = HTTP::Headers->new();
my $self = $class->SUPER::new(
agent => $conf->exists("LWP::UserAgent::agent") ? $conf->value("LWP::UserAgent::agent") : $main::plugin_name,
cookie_jar => { },
default_headers => $hh,
# TODO: be more configurable
);
$self->{_http_headers} = $hh;
$self->_export_proxy($conf)
or return;
main::debug "Proxy conf done.";
$self->_get_basic_auth($conf)
or return;
main::debug "Basic auth conf done.";
return $self;
}
sub _export_proxy {
my ($self, $conf) = @_;
return 1
unless $conf->exists("LWP::UserAgent::proxy");
# Ensure there are no conflicting proxies in the environment
for my $var (keys %ENV) {
$var =~ /_proxy$/i && delete $ENV{ $var };
}
# Export proxy configuration into environment
my $proxy = $conf->value("LWP::UserAgent::proxy");
my $user = $conf->exists("LWP::UserAgent::proxy::user") ? $conf->value("LWP::UserAgent::proxy::user") : '';
my $pass = $conf->exists("LWP::UserAgent::proxy::password") ? $conf->value("LWP::UserAgent::proxy::password") : '';
my @schemes = $conf->exists("LWP::UserAgent::proxy::schemes") ?
split(/\s*,\s*/, $conf->value("LWP::UserAgent::proxy::schemes")) :
qw(http);
for my $s (@schemes) {
$s = uc($s);
$ENV{$s."_PROXY"} = $proxy;
$ENV{$s."_PROXY_USERNAME"} = $user if $user;
$ENV{$s."_PROXY_PASSWORD"} = $pass if $pass;
main::debug "Using proxy: ". $proxy. " for scheme: ". $s;
}
$self->env_proxy();
return 1;
}
sub _get_basic_auth {
my ($self, $conf) = @_;
return 1
unless $conf->exists("HTTP::Headers::authorization_basic::user");
unless ($conf->exists("HTTP::Headers::authorization_basic::password")) {
our $reason = "No password specified for basic http authentication";
return 0;
}
$self->{_http_headers}->authorization_basic(
$conf->value("HTTP::Headers::authorization_basic::user"),
$conf->value("HTTP::Headers::authorization_basic::password")
);
return 1;
}
###############################################################################
## MANUAL
###############################################################################
=pod
=head1 NAME
check_end2end.pl - Simple configurable end-to-end probe plugin for Nagios
=head1 VERSION
This is the documentation for check_end2end.pl v1.6.1
=head1 SYNOPSYS
See check_end2end.pl -h
=head1 THE CHECK
Every step configured in the configuration file (see L<CONFIGURATION FILE
FORMAT>) is performed regardless of the fact that you specify a threshold for
that step, a global threshold, or a single thresold that will be applied to
every step, because B<every step is checked for success or failure>.
A step check is considered successful if LWP::USerAgent's C<is_success()> method
returns true; otherwise, the check is considered as failed.
A failure in one of the steps will cause the immediate exit of the plugin, with