Add check_puppetdb_nodes
authorPeter Palfrader <peter@palfrader.org>
Fri, 30 Aug 2019 11:26:38 +0000 (13:26 +0200)
committerPeter Palfrader <peter@palfrader.org>
Fri, 30 Aug 2019 11:28:54 +0000 (13:28 +0200)
dsa-nagios-checks/checks/check_puppetdb_nodes [new file with mode: 0644]
dsa-nagios-checks/debian/changelog

diff --git a/dsa-nagios-checks/checks/check_puppetdb_nodes b/dsa-nagios-checks/checks/check_puppetdb_nodes
new file mode 100644 (file)
index 0000000..f76674e
--- /dev/null
@@ -0,0 +1,253 @@
+#!/usr/bin/perl
+
+# Copyright (c) 2014, Evgeni Golov
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice, this
+#   list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright notice, this
+#   list of conditions and the following disclaimer in the documentation and/or
+#   other materials provided with the distribution.
+#
+# * Neither the name of the {organization} nor the names of its
+#   contributors may be used to endorse or promote products derived from
+#   this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+use strict;
+use warnings;
+use JSON;
+use LWP;
+use Monitoring::Plugin;
+use Date::Parse;
+
+my $np = Monitoring::Plugin->new(
+    usage => "Usage: %s [ -H|--hostname=<hostname>] "
+      . "[ -p|--port=<port> ] [-s] [ -w|--warning=<minutes> ] "
+      . "[ -c|--critical=<minutes> ] [ -W|--warnfails=<num> ] "
+      . "[ -C|--critfails=<num> ] [ -n|--node=<node> ]"
+      . "[ -a|--apiversion=<num> ]"
+      . "[ -i|--ignore=<list> ]",
+    shortname => 'Check last node runs from PuppetDB',
+    url       => 'https://github.com/evgeni/check_puppetdb_nodes',
+    version   => '1.0',
+    license   => 'This plugin is free software, and comes with ABSOLUTELY
+NO WARRANTY. It may be used, redistributed and/or modified under
+the terms of the BSD 3-clause license.',
+);
+
+$np->add_arg(
+    spec => 'warning|w=i',
+    help => "Exit with WARNING status if nodes did not update for "
+      . "more than INTEGER minutes (default: %s)",
+    default => 120,
+);
+
+$np->add_arg(
+    spec => 'critical|c=i',
+    help => "Exit with CRITICAL status if nodes did not update for "
+      . "more than INTEGER minutes (default: %s)",
+    default => 1440,
+);
+
+$np->add_arg(
+    spec => 'warnfails|W=i',
+    help => "Exit with WARNING status if nodes had at least INTEGER "
+      . "failures in the last run (default: %s)",
+    default => 1,
+);
+
+$np->add_arg(
+    spec => 'critfails|C=i',
+    help => "Exit with CRITICAL status if nodes had at least INTEGER "
+      . "failures in the last run (default: %s)",
+    default => 1,
+);
+
+$np->add_arg(
+    spec    => 'hostname|H=s',
+    help    => 'Hostname of the PuppetDB (default: %s)',
+    default => 'localhost',
+);
+
+$np->add_arg(
+    spec    => 'port|p=i',
+    help    => 'Port PuppetDB is running on (default: %s)',
+    default => 8080,
+);
+
+$np->add_arg(
+    spec => 'node|n=s',
+    help => 'Node name to check, if not given, all nodes will be checked',
+);
+
+$np->add_arg(
+    spec => 'ssl|s',
+    help => "Use HTTPS instead of HTTP",
+);
+
+$np->add_arg(
+    spec => 'insecure|k',
+    help => "Allow connections via HTTPS without checking certificates",
+);
+
+$np->add_arg(
+    spec    => 'apiversion|a=n',
+    help    => 'Specify PupppetDB API version (default: %s)',
+    default => 3,
+);
+
+$np->add_arg(
+    spec    => 'ignore|i=s',
+    help    => 'Node names to ignore (comma-separated list) (default: %s)',
+    default => '',
+);
+
+$np->getopts;
+
+my %apiurls = (
+    3 => { 'nodes' => 'v3/nodes', 'event-counts' => 'v3/event-counts' },
+    4 => { 'nodes' => 'pdb/query/v4/nodes', 'event-counts' => 'pdb/query/v4/event-counts', 'logs' => 'pdb/query/v4/reports/{hash}/logs' },
+);
+if ( !exists $apiurls{$np->opts->apiversion} ) {
+    $np->nagios_exit( 'UNKNOWN', 'Unsupported PuppetDB API version ' . $np->opts->apiversion );
+}
+
+my @ignore_list = split( ',', $np->opts->ignore );
+
+my $url = sprintf( 'http%s://%s:%d/',
+    defined( $np->opts->ssl ) ? 's' : '',
+    $np->opts->hostname, $np->opts->port );
+
+my $ua = new LWP::UserAgent;
+$ua->default_header( 'Accept' => 'application/json' );
+if ( defined( $np->opts->insecure ) ) {
+    $ua->ssl_opts( verify_hostname => 0 ,SSL_verify_mode => 0x00);
+}
+
+my %parameters = ();
+if ( defined( $np->opts->node ) ) {
+    %parameters = ( 'query' => '["=","certname","' . $np->opts->node . '"]' );
+}
+my $uri = URI->new( $url . $apiurls{$np->opts->apiversion}{'nodes'} );
+$uri->query_form(%parameters);
+my $response = $ua->get($uri);
+
+if ( !$response->is_success ) {
+    $np->nagios_exit( 'UNKNOWN',
+        $response->code . ": " . $response->status_line );
+}
+
+my $data = decode_json( $response->decoded_content );
+
+my $now = time();
+
+if ( defined( $np->opts->node ) and !@$data ) {
+    $np->add_message( CRITICAL,
+        $np->opts->node . " not found in puppetdb\n" );
+}
+
+foreach my $node (@$data) {
+    my $certname          = defined($node->{'certname'}) ? $node->{'certname'} : $node->{'name'} ;
+    my $deactivated       = $node->{'deactivated'};
+    my $catalog_timestamp = $node->{'catalog_timestamp'};
+    my $report_hash       = $node->{'latest_report_hash'};
+    my $ts                = str2time($catalog_timestamp);
+
+    next if grep { $certname eq $_ } @ignore_list;
+
+    if ( !defined $deactivated and !length $catalog_timestamp ) {
+           $np->add_message( CRITICAL, 
+                   "$certname last run UNAVAILABLE\n" );
+    }
+    if ( !defined $deactivated and length $catalog_timestamp ) {
+        my $delta = ( $now - $ts );
+        if ( $delta > ( $np->opts->critical * 60 ) ) {
+            $np->add_message( CRITICAL,
+                "$certname did not update since $catalog_timestamp\n" );
+        }
+        elsif ( $delta > ( $np->opts->warning * 60 ) ) {
+            $np->add_message( WARNING,
+                "$certname did not update since $catalog_timestamp\n" );
+        }
+
+        my %apiparameters = (
+            3 => {
+                  'query' => '["and",["=","certname","'
+                    . $certname
+                    . '"],["=","latest-report?",true]]',
+                  'summarize-by' => 'certname',
+                  'count-by'     => 'resource',
+                 },
+            4 => {
+                'query' => '["and",["=","certname","'
+                    . $certname
+                    . '"],["=","latest_report?",true]]',
+                'summarize_by' => 'certname',
+                'count_by'     => 'resource',
+                  }
+        );
+        my $uri = URI->new( $url . $apiurls{$np->opts->apiversion}{'event-counts'} );
+        $uri->query_form($apiparameters{$np->opts->apiversion});
+        $response = $ua->get($uri);
+
+        if ( $response->is_success ) {
+            my $node_data = decode_json( $response->decoded_content );
+
+            my $failures = 0;
+            if (    defined( @$node_data[0] )
+                and defined( @$node_data[0]->{'failures'} ) )
+            {
+                $failures = @$node_data[0]->{'failures'};
+            }
+
+            if ( $failures >= $np->opts->critfails ) {
+                $np->add_message( CRITICAL,
+                    "$certname had $failures failures in the last run\n" );
+            }
+            elsif ( $failures >= $np->opts->warnfails ) {
+                $np->add_message( WARNING,
+                    "$certname had $failures failures in the last run\n" );
+            }
+            elsif ( exists $apiurls{$np->opts->apiversion}{'logs'} and $report_hash) {
+                my $apiurl = $apiurls{$np->opts->apiversion}{'logs'};
+                $apiurl =~ s/{hash}/$report_hash/;
+                $uri = URI->new( $url . $apiurl );
+                $response = $ua->get($uri);
+                if ( $response->is_success ) {
+                    my $logs = decode_json( $response->decoded_content );
+                    foreach my $log (@$logs) {
+                        my $tags = $log->{'tags'};
+                        if ( grep(/^err$/, @$tags) ) { 
+                            $np->add_message( WARNING, "$certname, $log->{'message'}" );
+                        }
+                    }
+                }
+            }
+
+        } else {
+                $np->nagios_exit( 'UNKNOWN', 'Unsupported query ' . $response->decoded_content);
+        }
+
+    }
+}
+
+my $code;
+my $message;
+( $code, $message ) = $np->check_messages;
+
+$np->nagios_exit( $code, $message );
index c7ffccd..664e04b 100644 (file)
@@ -1,6 +1,7 @@
 dsa-nagios-checks (119) UNRELEASED; urgency=medium
 
   * dsa-check-raid-sw: correctly parse resync percentages under 10%.
 dsa-nagios-checks (119) UNRELEASED; urgency=medium
 
   * dsa-check-raid-sw: correctly parse resync percentages under 10%.
+  * Add check_puppetdb_nodes.
 
  -- Peter Palfrader <weasel@debian.org>  Mon, 20 May 2019 12:52:00 +0200
 
 
  -- Peter Palfrader <weasel@debian.org>  Mon, 20 May 2019 12:52:00 +0200