Add check_puppetdb_nodes
[mirror/dsa-nagios.git] / dsa-nagios-checks / checks / check_puppetdb_nodes
1 #!/usr/bin/perl
2
3 # Copyright (c) 2014, Evgeni Golov
4 # All rights reserved.
5 #
6 # Redistribution and use in source and binary forms, with or without modification,
7 # are permitted provided that the following conditions are met:
8 #
9 # * Redistributions of source code must retain the above copyright notice, this
10 #   list of conditions and the following disclaimer.
11 #
12 # * Redistributions in binary form must reproduce the above copyright notice, this
13 #   list of conditions and the following disclaimer in the documentation and/or
14 #   other materials provided with the distribution.
15 #
16 # * Neither the name of the {organization} nor the names of its
17 #   contributors may be used to endorse or promote products derived from
18 #   this software without specific prior written permission.
19 #
20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
21 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
24 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
27 # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31 use strict;
32 use warnings;
33 use JSON;
34 use LWP;
35 use Monitoring::Plugin;
36 use Date::Parse;
37
38 my $np = Monitoring::Plugin->new(
39     usage => "Usage: %s [ -H|--hostname=<hostname>] "
40       . "[ -p|--port=<port> ] [-s] [ -w|--warning=<minutes> ] "
41       . "[ -c|--critical=<minutes> ] [ -W|--warnfails=<num> ] "
42       . "[ -C|--critfails=<num> ] [ -n|--node=<node> ]"
43       . "[ -a|--apiversion=<num> ]"
44       . "[ -i|--ignore=<list> ]",
45     shortname => 'Check last node runs from PuppetDB',
46     url       => 'https://github.com/evgeni/check_puppetdb_nodes',
47     version   => '1.0',
48     license   => 'This plugin is free software, and comes with ABSOLUTELY
49 NO WARRANTY. It may be used, redistributed and/or modified under
50 the terms of the BSD 3-clause license.',
51 );
52
53 $np->add_arg(
54     spec => 'warning|w=i',
55     help => "Exit with WARNING status if nodes did not update for "
56       . "more than INTEGER minutes (default: %s)",
57     default => 120,
58 );
59
60 $np->add_arg(
61     spec => 'critical|c=i',
62     help => "Exit with CRITICAL status if nodes did not update for "
63       . "more than INTEGER minutes (default: %s)",
64     default => 1440,
65 );
66
67 $np->add_arg(
68     spec => 'warnfails|W=i',
69     help => "Exit with WARNING status if nodes had at least INTEGER "
70       . "failures in the last run (default: %s)",
71     default => 1,
72 );
73
74 $np->add_arg(
75     spec => 'critfails|C=i',
76     help => "Exit with CRITICAL status if nodes had at least INTEGER "
77       . "failures in the last run (default: %s)",
78     default => 1,
79 );
80
81 $np->add_arg(
82     spec    => 'hostname|H=s',
83     help    => 'Hostname of the PuppetDB (default: %s)',
84     default => 'localhost',
85 );
86
87 $np->add_arg(
88     spec    => 'port|p=i',
89     help    => 'Port PuppetDB is running on (default: %s)',
90     default => 8080,
91 );
92
93 $np->add_arg(
94     spec => 'node|n=s',
95     help => 'Node name to check, if not given, all nodes will be checked',
96 );
97
98 $np->add_arg(
99     spec => 'ssl|s',
100     help => "Use HTTPS instead of HTTP",
101 );
102
103 $np->add_arg(
104     spec => 'insecure|k',
105     help => "Allow connections via HTTPS without checking certificates",
106 );
107
108 $np->add_arg(
109     spec    => 'apiversion|a=n',
110     help    => 'Specify PupppetDB API version (default: %s)',
111     default => 3,
112 );
113
114 $np->add_arg(
115     spec    => 'ignore|i=s',
116     help    => 'Node names to ignore (comma-separated list) (default: %s)',
117     default => '',
118 );
119
120 $np->getopts;
121
122 my %apiurls = (
123     3 => { 'nodes' => 'v3/nodes', 'event-counts' => 'v3/event-counts' },
124     4 => { 'nodes' => 'pdb/query/v4/nodes', 'event-counts' => 'pdb/query/v4/event-counts', 'logs' => 'pdb/query/v4/reports/{hash}/logs' },
125 );
126 if ( !exists $apiurls{$np->opts->apiversion} ) {
127     $np->nagios_exit( 'UNKNOWN', 'Unsupported PuppetDB API version ' . $np->opts->apiversion );
128 }
129
130 my @ignore_list = split( ',', $np->opts->ignore );
131
132 my $url = sprintf( 'http%s://%s:%d/',
133     defined( $np->opts->ssl ) ? 's' : '',
134     $np->opts->hostname, $np->opts->port );
135
136 my $ua = new LWP::UserAgent;
137 $ua->default_header( 'Accept' => 'application/json' );
138 if ( defined( $np->opts->insecure ) ) {
139     $ua->ssl_opts( verify_hostname => 0 ,SSL_verify_mode => 0x00);
140 }
141
142 my %parameters = ();
143 if ( defined( $np->opts->node ) ) {
144     %parameters = ( 'query' => '["=","certname","' . $np->opts->node . '"]' );
145 }
146 my $uri = URI->new( $url . $apiurls{$np->opts->apiversion}{'nodes'} );
147 $uri->query_form(%parameters);
148 my $response = $ua->get($uri);
149
150 if ( !$response->is_success ) {
151     $np->nagios_exit( 'UNKNOWN',
152         $response->code . ": " . $response->status_line );
153 }
154
155 my $data = decode_json( $response->decoded_content );
156
157 my $now = time();
158
159 if ( defined( $np->opts->node ) and !@$data ) {
160     $np->add_message( CRITICAL,
161         $np->opts->node . " not found in puppetdb\n" );
162 }
163
164 foreach my $node (@$data) {
165     my $certname          = defined($node->{'certname'}) ? $node->{'certname'} : $node->{'name'} ;
166     my $deactivated       = $node->{'deactivated'};
167     my $catalog_timestamp = $node->{'catalog_timestamp'};
168     my $report_hash       = $node->{'latest_report_hash'};
169     my $ts                = str2time($catalog_timestamp);
170
171     next if grep { $certname eq $_ } @ignore_list;
172
173     if ( !defined $deactivated and !length $catalog_timestamp ) {
174             $np->add_message( CRITICAL, 
175                     "$certname last run UNAVAILABLE\n" );
176     }
177     if ( !defined $deactivated and length $catalog_timestamp ) {
178         my $delta = ( $now - $ts );
179         if ( $delta > ( $np->opts->critical * 60 ) ) {
180             $np->add_message( CRITICAL,
181                 "$certname did not update since $catalog_timestamp\n" );
182         }
183         elsif ( $delta > ( $np->opts->warning * 60 ) ) {
184             $np->add_message( WARNING,
185                 "$certname did not update since $catalog_timestamp\n" );
186         }
187
188         my %apiparameters = (
189             3 => {
190                   'query' => '["and",["=","certname","'
191                     . $certname
192                     . '"],["=","latest-report?",true]]',
193                   'summarize-by' => 'certname',
194                   'count-by'     => 'resource',
195                  },
196             4 => {
197                 'query' => '["and",["=","certname","'
198                     . $certname
199                     . '"],["=","latest_report?",true]]',
200                 'summarize_by' => 'certname',
201                 'count_by'     => 'resource',
202                   }
203         );
204         my $uri = URI->new( $url . $apiurls{$np->opts->apiversion}{'event-counts'} );
205         $uri->query_form($apiparameters{$np->opts->apiversion});
206         $response = $ua->get($uri);
207
208         if ( $response->is_success ) {
209             my $node_data = decode_json( $response->decoded_content );
210
211             my $failures = 0;
212             if (    defined( @$node_data[0] )
213                 and defined( @$node_data[0]->{'failures'} ) )
214             {
215                 $failures = @$node_data[0]->{'failures'};
216             }
217
218             if ( $failures >= $np->opts->critfails ) {
219                 $np->add_message( CRITICAL,
220                     "$certname had $failures failures in the last run\n" );
221             }
222             elsif ( $failures >= $np->opts->warnfails ) {
223                 $np->add_message( WARNING,
224                     "$certname had $failures failures in the last run\n" );
225             }
226             elsif ( exists $apiurls{$np->opts->apiversion}{'logs'} and $report_hash) {
227                 my $apiurl = $apiurls{$np->opts->apiversion}{'logs'};
228                 $apiurl =~ s/{hash}/$report_hash/;
229                 $uri = URI->new( $url . $apiurl );
230                 $response = $ua->get($uri);
231                 if ( $response->is_success ) {
232                     my $logs = decode_json( $response->decoded_content );
233                     foreach my $log (@$logs) {
234                         my $tags = $log->{'tags'};
235                         if ( grep(/^err$/, @$tags) ) { 
236                             $np->add_message( WARNING, "$certname, $log->{'message'}" );
237                         }
238                     }
239                 }
240             }
241
242         } else {
243                 $np->nagios_exit( 'UNKNOWN', 'Unsupported query ' . $response->decoded_content);
244         }
245
246     }
247 }
248
249 my $code;
250 my $message;
251 ( $code, $message ) = $np->check_messages;
252
253 $np->nagios_exit( $code, $message );