#!/usr/bin/env perl

## ###################################################################################
## Company: Software- und Organisations-Service GmbH
## Version: 1.11.4
## Purpose: Plugin to check the availability of a JobScheduler Cluster via JOC Cockpit
## see https://kb.sos-berlin.com/x/WAOs
## Example: check_jobscheduler_with_joc.pl -j http://galadriel.sos:4446 \
##                                         -a root:root -i schedulerId 
## ###################################################################################

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

my $version = "1.11.4";
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;
my $sslCaFile;
my $sslCaPath;
#######################################################################
# 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,
    "H:s" => \$host,    "hostname:s" => \$host,
    "t:i" => \$timeout, "timeout:i"  => \$timeout,
    "p:i" => \$port,    "port:i"     => \$port,
    "a:s" => \$account, "account:s"  => \$account);


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

if (!$id) { 
	print_usage("JobScheduler 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 ($host) {
	if ( !($host =~ /([-.A-Za-z0-9]+)/) ) {
		print_usage("Invalid host: $host");
	}
	if (!$port) {
		print_usage("JobScheduler http port not specified");
	}
}

if ($port) {
	if ($port < 0 || $port > 65535) {
		print_usage("Invalid http port: $port");
	}
	if (!$host) {
		print_usage("JobScheduler host not specified");
	}
}

my $ua = LWP::UserAgent->new;
$ua->agent('check_jobscheduler/'.$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 $getClusterMemberInfo = ($host && $port);
	my $requestPath = '/joc/api/jobscheduler';
	if (!$getClusterMemberInfo) {
		$requestPath .= '/cluster/members';
	}
	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);
		if ($getClusterMemberInfo) {
			my $state = $decoded->{'jobscheduler'}{'state'};
			if ($state->{'severity'} == 0 || $state->{'severity'} == 1) {
				_report('OK', "OK: JobScheduler Master ($host:$port) is up and $state->{'_text'}\n");
			} else {
				_report('ERROR', "ERROR: JobScheduler Master ($host:$port) is $state->{'_text'}\n");
			}
		} else {
			my @masterArr = @{$decoded->{'masters'}};
			my $ok = 0;
			my %report;
			my %detailedReport = ( "RUNNING" => [], "PAUSED" => [], "STARTING" => [], "WAITING_FOR_ACTIVATION" => [], "WAITING_FOR_DATABASE" => [], "DEAD" => [], "UNREACHABLE" => [], "TERMINATING" => [] );
			my $state;
			foreach (@masterArr) {
				$state = $_->{'state'};
				if ($state->{'severity'} == 0) {
					$ok += 1;
					$report{'RUNNING'} += 1;
					push @{$detailedReport{'RUNNING'}}, $_->{'host'}.':'.$_->{'port'};
				} elsif ($state->{'_text'} eq 'WAITING_FOR_ACTIVATION') {
					$ok += 1;
					$report{'WAITING_FOR_ACTIVATION'} += 1;
					push @{$detailedReport{'WAITING_FOR_ACTIVATION'}}, $_->{'host'}.':'.$_->{'port'};
				} else {
					$report{$state->{'_text'}} += 1;
					push @{$detailedReport{$state->{'_text'}}}, $_->{'host'}.':'.$_->{'port'};
				}
			}
			my $reportStr;
			if ($detailed) {
				$reportStr = join(", ", map { (scalar(@{$detailedReport{$_}}) > 0) ? "$_(" . join(", ", @{$detailedReport{$_}}) . ")" : () } sort keys %detailedReport);
			} else {
				$reportStr = join(", ", map { "$_($report{$_})" } sort keys %report);
			}
			if (scalar(@masterArr) == 1) {
				if ($ok == 1) {
					_report('OK', "OK: JobScheduler Master ($id) is up and $state->{'_text'}\n");
				} elsif ($report{'PAUSED'} == 1) {
					_report('WARNING', "WARNING: JobScheduler Master ($id) is up but $state->{'_text'}\n");
				} else {
					_report('ERROR', "ERROR: JobScheduler Master ($id) is $state->{'_text'}\n");
				}
			} else {
				if ($ok == scalar(@masterArr)) {
					_report('OK', "OK: JobScheduler Cluster ($id) is up and $reportStr\n");
				} elsif ($report{'RUNNING'} > 0 || $report{'PAUSED'} > 0) {
					_report('WARNING', "WARNING: JobScheduler Cluster ($id) is up but $reportStr\n");
				} else {
					_report('ERROR', "ERROR: JobScheduler Cluster ($id): $reportStr\n");
				}
			}
		}
	} elsif ($res->code == 403) {
		_report('CRITICAL', 'CRITICAL: ' . $res->code . ' Forbidden! The user ' . $account . " doesn't have sufficient permissions to get 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/security/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{'jobschedulerId'} = $id;
	if ($host && $port) {
		$dataToJson{'host'} = $host;
		$dataToJson{'port'} = $port;
	}
	return $json->encode(\%dataToJson);
}


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


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

This script tries to connect to given JobScheduler Cluster with JOC Cockpit

";
   print_usage();
   print "
-j, --joc-url[=STRING]         | Url of JOC Cockpit
-i, --id[=STRING]              | Id of JobScheduler Cluster
-H, --hostname[=STRING]        | Name or IP address of a JobScheduler Master host
                               | Only required to check a specific cluster member
-p, --port[=INTEGER]           | HTTP port of a JobScheduler Master
                               | Only required to check a specific cluster member
-t, --timeout[=INTEGER]        | Optional timeout in seconds for HTTP connection
                               | to JOC Cockpit (default=$defaultTimeout)
-a, --account[=user:password]  | Account for HTTP authentication to JOC Cockpit
-d, --detailed                 | If set and the cluster has more than one Master
                               | and not all Masters are running then the message
                               | contains host:port of each Master
-h, --help                     | This help
";
}

sub print_usage () {
	my ($withExitMessage) = @_;
	if ($withExitMessage) {
		print $withExitMessage."\n";
	}
	print  "Usage: $0 -j <JOC Cockpit url> -i <JobScheduler id> -a <user:password> [-H <JobScheduler host> -p <JobScheduler port>] [-t <timeout>]\n";
	if ($withExitMessage) {
		exit 1;
	}
}

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