#!/usr/bin/env perl

## ###################################################################################
## Company: Software- und Organisations-Service GmbH
## Version: 2.0.0 
## Purpose: Plugin to check the availability of a JS7 JobScheduler Controller via JOC Cockpit
## see https://kb.sos-berlin.com/x/ypjAAw
## Example: check_js.pl -j http://localhost:4446 \
##                      -i jobscheduler -a root:root
## ###################################################################################

use strict;
use HTTP::Request;
use LWP::UserAgent;
use Getopt::Long;
use MIME::Base64;
use JSON;
use vars qw($url $id $timeout $help $account $detailed $sslCaFile $sslCaPath $sslHostnameVerification);
use vars qw(%ERRORS &support);

my $version = "2.0.0";
my $defaultTimeout = 30;
#######################################################################
# SSL options for HTTPS
#
# You can also use PERL_LWP_SSL_CA_FILE or HTTPS_CA_FILE environment
# variables to locate the file with authority certificates or use
# PERL_LWP_SSL_CA_PATH or HTTPS_CA_DIR to locate the directory 
# containing files with authority certificates.
# Hostname verification is disabled per default!
my $withHostnameVerification = 0;
#######################################################################
# Load proxy settings from *_proxy environment variables. 
# For example HTTP_PROXY or CGI_HTTP_PROXY

our %ERROR;
%ERRORS = (
			'OK'       => 0,
			'CRITICAL' => 2,
			'ERROR'    => 2,
			'UNKNOWN'  => 9,
			'WARNING'  => 1,
);

Getopt::Long::Configure('bundling');
GetOptions
   ("h"   => \$help,      "help"      => \$help,
    "d"   => \$detailed,  "detailed"  => \$detailed,
    "j:s" => \$url,       "joc-url:s" => \$url,
    "i:s" => \$id,        "id:s"      => \$id,
    "a:s" => \$account,   "account:s" => \$account,
    "t:i" => \$timeout,   "timeout:i" => \$timeout,
    "c:s" => \$sslCaFile, "cafile:s"  => \$sslCaFile,
    "C:s" => \$sslCaPath, "capath:s"  => \$sslCaPath,
    "s"   => \$sslHostnameVerification, "hostname-verification"  => \$sslHostnameVerification);


if ($help) {print_help(); exit 0;}

if (!$id) { 
	print_usage("Controller ID not specified");
}

if (!$url) { 
	print_usage("JOC Cockpit URL not specified");
}

if ($account) {
	if ( !($account =~ /[^:]+:[^:]*/) ) {
		print_usage("Invalid account=<user:password>: $account");
	}
} else {
	print_usage("Account for http authentication in JOC Cockpit not specified");
}

if ($sslHostnameVerification) {
    $withHostnameVerification = 1;
}

my $ua = LWP::UserAgent->new;
$ua->agent('check_js7_controller/'.$version);
$ua->default_header('Accept' => 'application/json');
$ua->default_header('Content-Type' => 'application/json');
$ua->protocols_allowed( ['http', 'https'] );
$ua->parse_head(0);
$ua->timeout($timeout || $defaultTimeout);
$ua->ssl_opts(verify_hostname => $withHostnameVerification);
if ($sslCaFile) {
	$ua->ssl_opts(SSL_ca_file => $sslCaFile);
}
if ($sslCaPath) {
	$ua->ssl_opts(SSL_ca_path => $sslCaPath);
}
$ua->env_proxy;


my $accessToken = get_access_token();
$ua->default_header('X-Access-Token' => $accessToken);
$ua->default_header(':access_token' => $accessToken);
get_state();


sub get_state {
	my $requestPath = '/joc/api/controllers';
	my $req = HTTP::Request->new( POST => $url . $requestPath);
	$req->content( get_post_body() );
	my $res = $ua->request( $req );

	if ($res->is_success) {
		my $body = $res->content;
		my $decoded = decode_json($res->content);
		my @controllerArr = @{$decoded->{'controllers'}};
		my $ok = 0;
		my $state;
        my $message;
        my $noActiveMemberFound = 1;
        my $resultState = 0; # 0=OK, 1-99=WARNING, >100=ERROR

		foreach (@controllerArr) {
            $message = $message . ", role=(" .  $_->{'role'} . "):" ;

            if (scalar(@controllerArr) > 1) {
                $state = $_->{'clusterNodeState'};

                if ($state->{'severity'} == 0) {
                    $noActiveMemberFound = 0;
                }

                $message = $message . " clusterNodeState=(" . $state->{'_text'} . ")";
                
                if ($detailed) {
                    $message = $message ." clusterUrl=(" . $_->{'clusterUrl'} . ")" . " isCoupled=(" . $_->{'isCoupled'} . ")";
                }
                
                if ($state->{'severity'} != 0 && $state->{'severity'} != 1) {
                    $resultState += 1 + (100*$noActiveMemberFound);
                }
                
                if (!$_->{'isCoupled'}) {
                    $resultState += 1;
                } else {
                    $ok += 1;
                }
            }

            $state = $_->{'componentState'};
            $message = $message . " componentState=(" . $state->{'_text'} . ")";

            if ($state->{'severity'} != 0 && $state->{'severity'} != 1) {
                $resultState += 1 + (100*$noActiveMemberFound);
            }

            $state = $_->{'connectionState'};
            $message = $message . " connectionState=(" . $state->{'_text'} . ")";

            if ($state->{'severity'} != 0 && $state->{'severity'} != 1) {
                $resultState += 1 + (100*$noActiveMemberFound);
            }
		}

        if (scalar(@controllerArr) == 0) {
            _report('ERROR', "ERROR: JS7 JobScheduler Controller unknown: id=($id)$message\n");
        } elsif ($resultState == 0) {
            _report('OK', "OK: JS7 JobScheduler Controller healthy: id=($id)$message\n");
        } elsif ($resultState < 100) {
            _report('WARNING', "WARNING: JS7 JobScheduler Controller unhealthy: id=($id)$message\n");
        } else {
            _report('ERROR', "ERROR: JS7 JobScheduler Controller inoperable: id=($id)$message\n");
        }
	} elsif ($res->code == 403) {
		_report('CRITICAL', 'CRITICAL: ' . $res->code . ' Forbidden! The user account ' . $account . " has no permissions to receive JS7 JobScheduler information.\n");
	} elsif ($res->code == 420) {
		my $error = decode_json($res->content)->{'error'};
		if ($error->{'code'} eq 'JOC-404') {
			_report('WARNING', 'WARNING: ' . $res->code . ' ' . $error->{'message'} . "\n");
		} else {
			_report('CRITICAL', 'CRITICAL: ' . $res->code . ' ' . $error->{'message'} . "\n");
		}
	} else {
		_report('CRITICAL', 'CRITICAL: ' . $res->status_line . "\n");
	}
}


sub get_access_token {
	my $req = HTTP::Request->new( POST => $url . '/joc/api/authentication/login');
	$req->header( 'Authorization' => "Basic ".encode_base64($account) );
	my $res = $ua->request( $req );
	$account =~ s/([^:]+:)[^:]*/$1***/s;
	if ($res->is_success) {
		return $res->header('X-Access-Token');
	} elsif ($res->code == 420) {
		_report('CRITICAL', $res->code . ' Login failed for user ' . $account . "\n");
	} elsif ($res->code == 401 || $res->code == 403) {
		_report('CRITICAL', $res->code . ' The user ' . $account . " is not authorized to access JOC Cockpit\n");
	} else {
		_report('CRITICAL', $res->status_line . "\n");
	}
}


sub get_post_body {
	my $json = JSON->new;
	my %dataToJson;
	$dataToJson{'controllerId'} = $id;
	return $json->encode(\%dataToJson);
}


sub logout {
	if ($accessToken) {
		my $req = HTTP::Request->new( POST => $url . '/joc/api/authentication/logout');
		$ua->request( $req );
	}
}


sub print_help () {
	my $year = 1900 + (localtime)[5];
	my $copyRight = "Copyright (c) 2021";
	if ($year > 2017) {
		$copyRight .= ' - ' . $year;
	}
	print $0. "\n";
	print "$copyRight SOS GmbH, info\@sos-berlin.com

This script tries to connect to the given JS7 JobScheduler Controller with JOC Cockpit

";
   print_usage();
   print "
-j, --joc-url[=STRING]         | URL of JOC Cockpit
-i, --id[=STRING]              | Controller ID of JS7 JobScheduler Cluster
-a, --account[=user:password]  | Account for HTTP authentication to JOC Cockpit
-t, --timeout[=INTEGER]        | Optional timeout in seconds for HTTP(S) connection
                               | to JOC Cockpit (default=$defaultTimeout)
-c, --cafile[=STRING]          | Set location of an SSL certificate file or CA bundle file 
-C, --capath[=STRING]          | Set path that includes SSL certificate files or CA bundle files
-s, --hostname-verification    | If set apply SSL hostname verification (default=$withHostnameVerification)
-d, --detailed                 | If set then status information for each Controller instance is returned
-h, --help                     | This help
";
}

sub print_usage () {
	my ($withExitMessage) = @_;
	if ($withExitMessage) {
		print $withExitMessage."\n";
	}
	print  "Usage: $0 -j <JOC Cockpit URL> -i <Controller ID> -a <user:password>\n";
	if ($withExitMessage) {
		exit 1;
	}
}

sub _report { 
	print $_[1];
	logout();
	if (defined($ERRORS{$_[0]})) { exit $ERRORS{$_[0]}; }
	else { exit 0; }
}