#!/usr/bin/env perl

## #################################################################################
## Company: Software- und Organisations-Service GmbH
## Version: 1.12.7
## Purpose: Plugin to check the availability of a JobScheduler Agent via JOC Cockpit
## see https://kb.sos-berlin.com/x/WAOs
## Example: check_jobscheduler_agent_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 @agents $timeout $help $account $detailed);
use vars qw(%ERRORS &support);
use Cwd qw(getcwd);

my $version = "1.12.7";
my $defaultTimeout = 30;
my $reportScriptBasename = "report_jobscheduler_agent.pl";
#######################################################################
# 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,
    "A:s@" => \@agents,   "agent:s@"  => \@agents,
    "t:i"  => \$timeout,  "timeout:i" => \$timeout,
    "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");
}


my $workingDir = getcwd;
my $reportScript = $workingDir . "/" . $reportScriptBasename;
undef $reportScript if ! -x $reportScript;


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_agent_state();


sub get_agent_state {
	my $req = HTTP::Request->new( POST => $url . '/joc/api/jobscheduler/agents');
	$req->content( get_post_body() );
	my $res = $ua->request( $req );
	if ($res->is_success) {
		my $decoded = decode_json($res->content);
		my @agentArr = @{$decoded->{'agents'}};
		my %report;
		my %detailedReport = ( "RUNNING" => [], "UNREACHABLE" => [], "TERMINATING" => [], "UNKNOWN_AGENT" => [] );
		foreach (@agentArr) {
			if ($_->{'state'}{'severity'} == 0 || $_->{'state'}{'severity'} == 1) {
				$report{'RUNNING'} += 1;
				push @{$detailedReport{'RUNNING'}}, $_->{'url'};
			} else {
				$report{$_->{'state'}{'_text'}} += 1;
				push @{$detailedReport{$_->{'state'}{'_text'}}}, $_->{'url'};
			}
			call_report_script_per_agent($url, $id, $_->{'url'}, $_->{'state'}{'_text'});
		}
		my $reportStr;
		if ($detailed) {
			$reportStr = join(", ", map { (scalar(@{$detailedReport{$_}}) > 0) ? "$_(" . join(", ", @{$detailedReport{$_}}) . ")" : () } sort keys %detailedReport);
		} else {
			$reportStr = join(", ", map { "$_($report{$_})" } sort keys %report);
		}
		if ($report{'RUNNING'} == scalar(@agentArr)) {
			_report('OK', "OK: All JobScheduler Agents(".$report{'RUNNING'}.") are up and RUNNING\n");
		} elsif ($report{'RUNNING'} > 0) {
			_report('WARNING', "WARNING: JobScheduler Agents: $reportStr\n");
		} else {
			_report('ERROR', "ERROR: JobScheduler Agents: $reportStr\n");
		}
	} elsif ($res->code == 403) {
		_report('CRITICAL', "CRITICAL: " . $res->code . "Forbidden! The user $account doesn't have sufficient permissions to get JobScheduler Agent information.\n");
	} elsif ($res->code == 420) {
		_report('CRITICAL', "CRITICAL: " . $res->code . ' ' . decode_json($res->content)->{'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', "CRITICAL: " . $res->code . "Login failed for user $account\n");
	} elsif ($res->code == 401 || $res->code == 403) {
		_report('CRITICAL', "CRITICAL: " . $res->code . "The user $account is not authorized to access JOC Cockpit\n");
	} else {
		_report('CRITICAL', "CRITICAL: ". $res->status_line . "\n");
	}
}


sub get_post_body {
	my $json = JSON->new;
	my %dataToJson;
	$dataToJson{'jobschedulerId'} = $id;
	if (@agents) {
		my @agentObjects = map { { 'agent' => $_ } } @agents;
		$dataToJson{'agents'} = \@agentObjects;
	}
	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 the JobScheduler Agents with JOC Cockpit

";
   print_usage();
   print "
-j, --joc-url[=STRING]         | Url of JOC Cockpit
-i, --id[=STRING]              | Id of a JobScheduler Master
-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
-A, --agent[=STRING]           | Url of an Agent, optional, 
                               | can be specified several times
-d, --detailed                 | If set and not all Agents running then the 
                               | message contains the Agent urls
-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; }
}

sub call_report_script_per_agent () {
	if (defined($reportScript)) {
		if ("MSWin32" eq "$^O") {
			system("start $reportScript /MIN", @_);
		} else {
			system("$reportScript &", @_);
		}
	}
}