#!/usr/bin/perl -w
# 
########################################################################
# This script creates a Rackspace core ticket when run by OCUM/AIQUM 
# as part of an alert configuration.  The alert parameters are passed
# to the script as arguments.  
#
# Here's an example of the OCUM/AIQUM command & arguments:
# coreticket.pl -eventID 90294 -eventName inodes_full -eventSeverity critical -eventSourceID 5678 -eventSourceName vserver:/somevol -eventSourceType inodes -eventState new -eventArgs arg1,arg2,arg3
#
# If the script needs to be updated, the new version must be copied over
# the current one in the following location:
#
# C:\Program Files\NetApp\ocum\scriptPlugin\coreticket.pl
#
# The script can be tested manually by using the arguments from the log 
# file (C:\aiqum_script_logs\coreticket.log) for a previous alert and running the command:
#
# perl coreticket.pl -eventID ### -eventName alert_type -eventSeverity alert_severity -eventSourceID ### -eventSourceName eventsrcname -eventSourceType clusternodevserver -eventState NEW -EventArgs arg1,arg2,...
#########################################################################

require Data::Dumper;
use Data::Dumper;
require File::Basename;
use File::Basename;
use File::Path qw/make_path/;
require REST::Client;
use REST::Client;
require JSON;
use JSON;
require Net::SMTP;
use Net::SMTP;
require Sys::Hostname;
use Sys::Hostname;

my $myname = "coreticket";
my $hostname = hostname;

# core service account information:
# https://passwordsafe.corp.rackspace.com/projects/28788
$core_user = "storage_core_svc2";
$core_pass = "yrrl93\(\%APkea\$70ON\%\$";

my $um_user = "nasom";
my $um_pass = "moVBNCJp!3";

#########################################################################
# Set up some logging.
#########################################################################

$logfile = "c:/aiqum_script_logs/${myname}.log";
my $logdir = dirname($logfile);
make_path($logdir);
open (LOG, '>>', $logfile) or failure ("AIQUM core ticket failure on $hostname", "Failed opening log file: $logfile!");
print LOG "############################################################\n";
print LOG scalar localtime(), "\n";

#########################################################################
# ticket tracking file for alerts that have cleared
#########################################################################

$ticketsfile = "$logdir/${myname}_tickets.log";

#########################################################################
# Check for all ocum arguments and parse into variables.
# We can't use the Getopt::Long module because the arguments are only 
# using a single dash (-).  To top it off, the eventName arg usually 
# contains spaces and sometimes it gets passed as one argument and 
# sometimes it doesn't.  (shrug emoji)
#
# Here's an example of a typical command:
# coreticket.pl -eventID 90294 -eventName inodes_full -eventSeverity critical -eventSourceID 5678 -eventSourceName somevol -eventSourceType inodes -eventState new -eventArgs arg1,arg2,arg3
#########################################################################

print LOG "Command arguments:\n@ARGV\n";
my $lastarg = "";
my $test = 0;
my $eventID = "";
my $eventName = "";
my $eventSeverity = "";
my $eventSourceID = "";
my $eventSourceType = "";
my $eventSourceName = "";
my $eventState = "";
my $eventArgs = "";
my $extra_msg = "";

# first combine all of the arguments into one long string
$allargs = " ";
foreach $arg (@ARGV)
	{
	$allargs .= " $arg";
	}
# then split the string on the event+ arg names, but also include the arg name
@args = split /\s\-(event\w+)\s/, $allargs;

# no process the arguments serially
foreach $arg (@args)
	{
	$test = 1 if $arg =~ /^\-\-test$|^\-test$/i;
	$eventID = $arg if $lastarg =~ /eventid/i;
	$eventName = $arg if $lastarg =~ /eventname/i;
	$eventSeverity = $arg if $lastarg =~ /eventseverity/i;
	$eventSourceID = $arg if $lastarg =~ /eventsourceid/i;
	$eventSourceName = $arg if $lastarg =~ /eventsourcename/i;
	$eventSourceType = $arg if $lastarg =~ /eventsourcetype/i;
	$eventState = $arg if $lastarg =~ /eventstate/i;
	$eventArgs = $arg if $lastarg =~ /eventargs/i;
	$lastarg = $arg;
	}

print LOG "eventID = $eventID\n";
print LOG "eventName = $eventName\n";
print LOG "eventSeverity = $eventSeverity\n";
print LOG "eventSourceID = $eventSourceID\n";
print LOG "eventSourceName = $eventSourceName\n";
print LOG "eventSourceType = $eventSourceType\n";
print LOG "eventState = $eventState\n";
print LOG "eventArgs = $eventArgs\n";
	
unless ($eventState =~ /new/i)
	{
	if ($eventState=~ /obsolete|resolved/i)
		{
		print LOG "Event $eventID has cleared. Finding core ticket to add comment.\n";
		# read in the ticket tracking file and look for this eventID
		open (TICKETS, '<', $ticketsfile) or failure ("AIQUM core ticket failure on $hostname", "Failed opening tickets file: $ticketsfile!");
		chomp (my @tickets = <TICKETS>);
		close (TICKETS);
		my $ticketnum = "";
		foreach $line (@tickets)
			{
			if ($line =~ /^$eventID\s/)
				{
				($junk, $ticketnum) = split /\s+/, $line, 2;
				last;
				}
			}
		unless ($ticketnum =~ /^\d+\-\d+$/)
			{
			print LOG "Unable to find core ticket for event $eventID!  Aborting.\n";
			exit;
			}
		print LOG "Found core ticket $ticketnum for event $eventID.\n";
		$hostname = hostname;
		$comment = "Alert from OCUM server $hostname.\\nThe following alert condition has cleared:\\n\\n$eventSourceName $eventName";
		ticket_comment($core_user,$core_pass,$ticketnum,$comment,"True");
		#ticket_status($core_user,$core_pass,$ticketnum,"Confirm Solved");
		exit;
		}
	else
		{
		print LOG "Unknown event state.  Aborting.\n";
		exit;
		}
	}

#########################################################################
# find the cluster name from the eventSourceName argument
# The most common argument values are:
# c-######-######  (alert for failed/unassigned disks & monitoring failures)
# c-######-######-n##  (node alert for port down, etc)
# c-######-######-n##:aggrname (alert for aggr capacity)
# vs-######-######:/volname (alert for volume capacity)
# vs-######-######:volname->vs-######-######:volname (snapmirror lag alerts)
# vs-######-LLL# (vserver alert, only ransomware stuff so far)
#########################################################################

my $cluster_name = "";
if ($eventSourceName =~ /->/)
	{
	($junk,$vserver_name) = split /->/, $eventSourceName, 2;
	($vserver_name,$junk) = split /:/, $vserver_name, 2;
	print LOG "Found vserver $vserver_name from eventSourceName $eventSourceName.\n";
	$cluster_name = cluster_lookup ($um_user, $um_pass, $vserver_name);
	}
elsif ($eventSourceName =~ /:/)
	{
	($vserver_name,$junk) = split /:/, $eventSourceName, 2;
	print LOG "Found vserver $vserver_name from eventSourceName $eventSourceName.\n";
	$cluster_name = cluster_lookup ($um_user, $um_pass, $vserver_name);
	}
elsif ($eventSourceName =~ /c-\d+-\d+-n\d+/i)
	{
	($node_name,$junk) = split /:/, $eventSourceName, 2;
	print LOG "Found node $node_name from eventSourceName $eventSourceName.\n";
	$cluster_name = cluster_lookup ($um_user, $um_pass, $node_name);
	}
elsif ($eventSourceName =~ /c-\d+-\d+/)
	{	
	($cluster_name,$junk) = split /:/, $eventSourceName, 2;
	}
elsif ($eventSourceName =~ /^vs-\d+-[A-Z][A-Z][A-Z]\d+$/ || $eventSourceName =~ /^vs-\d+-\d+$/)
	{
	$vserver_name = $eventSourceName;
	print LOG "Found vserver $vserver_name from eventSourceName $eventSourceName.\n";
	$cluster_name = cluster_lookup ($um_user, $um_pass, $vserver_name);
	}
else
	{
	print LOG "Unknown eventSourceName format! : $eventSourceName\n";
	}
	
unless  ($cluster_name =~ /\d\d\d\d+-\d\d\d\d\d+/)
	{
	print LOG "Failed to find cluster name from eventSourceName $eventSourceName!\n";
	send_email ("AIQUM core ticket script error on $hostname", "Failed to find cluster name from eventSourceName $eventSourceName!\nReview log file at $logfile.");
	}
#########################################################################
# Parse the eventSourceName argument for core account/device and store in a hash.
#########################################################################
%devices = ();
@sourceNames = split /\->/, $eventSourceName;
foreach $name (@sourceNames)
	{
	# special shared nas customer device parsing for volume alerts (OLD vserver naming convention)
	if ($name =~ /vs\-\d+:\/vol_\d+$/)
		{
		($account, $device) = split /:\/vol_/, $name, 2;
		($junk, $account ) = split /\-/, $account, 2;
		print LOG "Found account number = $account and device = $device from $name\n";
		push @{$devices{$account}}, $device unless grep (/$device/, @{$devices{$account}});
		next;
		}
	# special shared nas customer device parsing for volume alerts (NEW vserver naming convention for replication)
	elsif ($name =~ /vs\-\d+\-[A-z][A-z][A-z]\d:\/vol_\d+$/)
		{
		($account, $device) = split /:\/vol_/, $name, 2;
		($junk, $account, $junk ) = split /\-/, $account, 3;
		print LOG "Found account number = $account and device = $device from $name\n";
		push @{$devices{$account}}, $device unless grep (/$device/, @{$devices{$account}});
		next;
		}
	# special shared nas customer device parsing for snapmirror alerts (NEW vserver naming convention for replication)
	elsif ($name =~ /vs\-\d+\-[A-z][A-z][A-z]\d:vol_\d+$/)
		{
		($account, $device) = split /:vol_/, $name, 2;
		($junk, $account, $junk ) = split /\-/, $account, 3;
		print LOG "Found account number = $account and device = $device from $name\n";
		push @{$devices{$account}}, $device unless grep (/$device/, @{$devices{$account}});
		next;
		}
	# standard naming convention for dedicated clusters (vserver, node, & cluster names)
	elsif ($name =~ /\d\d\d\d+[_-]\d\d\d\d\d+/)
		{
		($junk,$account,$device,$junk) = split /(\d\d\d\d+)[_-](\d\d\d\d\d+)/, $name, 4;
		print LOG "Found account number = $account and device = $device from $name\n";
		push @{$devices{$account}}, $device unless grep (/$device/, @{$devices{$account}});
		next;
		}
	# special shared nas customer vserver name parsing for vserver only alerts (ransomware)
	elsif ($name =~ /^vs\-\d+\-[A-Z][A-Z][A-Z]\d$/)
		{
		($junk, $account, $junk) = split /-/, $name, 3;
		print LOG "Found account number = $account from $name.\n";
		next;
		}
	}
# add the cluster device to the hash unless this is sNAS/RFS
if ($cluster_name =~ /\d\d\d\d+[\-_]\d\d\d\d+/ && $cluster_name !~ /3176425|rfs|snas/i)
	{
	($junk,$account,$device,$junk) = split /(\d\d\d\d+)[\-_](\d\d\d\d+)/, $cluster_name, 4;
	push @{$devices{$account}}, $device unless grep (/$device/, @{$devices{$account}});
	}

#########################################################################
# special section for handling EMS event alerts
#########################################################################
if ($eventName =~ /EMS\s+received/)
	{
	$vserver_name = "";
	$device = "";
	($junk, $temp) = split /=/, $eventArgs, 2;
	($eventName, $ticket_body) = split /:/, $temp, 2;
	($extra_msg, $junk) = split /,/, $ticket_body, 2;
	if ($ticket_body =~ /\svs\-\d+\-[A-Z][A-Z][A-Z]\d\s/)
		{
		($junk, $vserver_name, $junk) = split /\s(vs\-\d+\-[A-Z][A-Z][A-Z]\d)\s/, $ticket_body, 3;
		($junk, $account, $junk) = split /-/, $vserver_name, 3;
		print LOG "Found vserver $vserver_name for account $account from EMS alert arguments.\n";
		}
	if ($ticket_body =~ /\svol_\d+\s/)
		{
		($junk, $device, $junk) = split /\svol_(\d+)\s/, $ticket_body, 3;
		print LOG "Found device $device from EMS alert arguments.\n";
		}
	if ($account =~ /\d+/ && $device =~ /\d+/)
		{
		%devices = ();
		push (@{$devices{$account}}, $device);
		}
	}
#########################################################################
# Verify if we have devices for the ticket, and abort if the test flag is present
#########################################################################

# not sure if we even need devices to create a ticket?
#unless (scalar(keys(%devices)))
#	{
#	failure ("AIQUM core ticket script failure on $hostname","No devices found from eventSourceName: $eventSourceName!\nReview log file at $logfile.");
#	exit;
#	}

if ($test)
	{
	print LOG "Event was a test.  Aborting.\n";
	exit;
	}
	
#########################################################################
# make a comma-separated list of the devices to create a core ticket via ctkapi
#########################################################################

$hostname = hostname;
$comment = "Alert from OCUM server $hostname.\\nPlease investigate the following alert:\\n\\n$eventSourceName $eventName\\n\\n$extra_msg";

$devicelist="";
foreach $account (keys(%devices))
	{
	print LOG "Account $account devices: @{$devices{$account}}\n";
	foreach $device (@{$devices{$account}})
		{
		if ($devicelist eq "")
			{
			$devicelist = $device;
			}
		elsif ($devicelist =~ /$device/)
			{
			# skip because this device is already on the list
			next;
			}
		else
			{
			$devicelist .= ",$device";
			}
		}
	}
print LOG "Account $account device list: $devicelist\n";
my $ticketnum = ticket_create($core_user,$core_pass,$account,$devicelist,634,30942,1,$eventName,$comment,"True");
open (TICKETS, '>>', $ticketsfile) or failure ("AIQUM core ticket failure on $hostname", "Failed opening tickets file: $ticketsfile!");
print TICKETS "$eventID $ticketnum\n";
close (TICKETS);

exit;

#########################################################################
# subroutine to create a core ticket
#########################################################################
sub ticket_create
{
my $username = shift;
my $password = shift;
my $account = shift;
my $devicelist = shift;
my $queue = shift;
my $subcategory = shift;
my $severity = shift;
my $subject = shift;
my $comment = shift;
my $private = shift;
my $source = 10;
my $hostname = hostname;

# Authenticate to core and create an auth header
my $client = REST::Client->new();
$body = "{\"password\": \"$password\"}";
$client->POST("https://ws.core.rackspace.com/ctkapi/login/$username", $body);
if ($client->responseCode() ne '200')
	{
	print LOG "Authentication failed!\n";
	print LOG Dumper($client->responseContent());
	failure ("AIQUM core ticket script failure on $hostname", "Core authentication failure for $subject!\nReview log file at $logfile.");
	exit;
	}
print LOG "Authentication successful.\n";

$auth_hashref = decode_json($client->responseContent());
$token = $auth_hashref->{authtoken};
%auth_header = ( 'X-auth' => $token);

# Now create a ticket
#####################################################

#$account = 1645000;
#$queue = 634;  # 634=Storage-File Alerts
#$subcategory = 30942; # 30942=Hardware/Software Issue
#$source = 10; # 10=Other,3=Rackspace Employee
#$severity = 1; # 1=Normal
#$subject = "Test Subject";
#$comment = "Test Comment";
#$devicelist = "689212"; # comma-separated list of device numbers
#$private = "True"; # or False for public 1st comment


# new JSON body with keyword args instead of ordered args:
$body = <<"END_OF_JSON";
    [
        {
            "class": "Account.Account",
            "load_arg": $account,
            "method": "addTicket",
            "args": [],
            "keyword_args": {
                "queue": $queue,
                "subcategory": $subcategory,
                "source": $source,
                "severity": $severity,
                "subject": "$subject",
                "text": "$comment",
				"computer_list": [$devicelist],
				"is_private_message": "$private"
				},
            "result_map": {
                "status": "status.id",
                "account": "account.number",
                "number": "number"
            }
        }
    ]
END_OF_JSON

#print LOG "$body\n";

$client->POST("https://ws.core.rackspace.com/ctkapi/query", $body, \%auth_header);

if ($client->responseCode() ne '200')
	{
	print LOG "Unknown core failure!\n";
	print LOG Dumper($client->responseContent());
	failure ("AIQUM core ticket script failure on $hostname", "Unknown core failure!\nReview log file at $logfile.");
	exit;
	}
	
print LOG "Ticket creation successful.\n";
my $ticketdata = from_json($client->responseContent());
my $ticketnum = $ticketdata->[0]->{"result"}->{"number"};
print LOG "Core ticket: $ticketnum\n";
unless ($ticketnum =~ /^\d+\-\d+$/)
	{
	print LOG "Failed to find ticket number from core response!\n";
	print LOG Dumper(from_json($client->responseContent()));
	failure ("AIQUM core ticket script failure on $hostname", "Failed to find ticket number from core response!\nReview log file at $logfile.");
	exit;
	}
return($ticketnum);
}

#########################################################################
# subroutine to comment a core ticket
#########################################################################
sub ticket_comment
{
my $username = shift;
my $password = shift;
my $ticketnum = shift;
my $comment = shift;
my $private = shift;
my $source = 10;
my $hostname = hostname;

# Authenticate to core and create an auth header
my $client = REST::Client->new();
$body = "{\"password\": \"$password\"}";
$client->POST("https://ws.core.rackspace.com/ctkapi/login/$username", $body);
if ($client->responseCode() ne '200')
	{
	print LOG "Authentication failed!\n";
	print LOG Dumper($client->responseContent());
	failure ("AIQUM core ticket script failure on $hostname", "Core authentication failure!\nReview log file at $logfile.");
	exit;
	}
print LOG "Authentication successful.\n";

$auth_hashref = decode_json($client->responseContent());
$token = $auth_hashref->{authtoken};
%auth_header = ( 'X-auth' => $token);


# new JSON with keyword args
$body = <<"END_OF_JSON";
    [
        {
            "class": "Ticket.Ticket",
			"load_arg": "$ticketnum",
            "method": "addMessage",
            "args": [],
            "keyword_args": {
                "text": "$comment",
                "source": $source,
				"private": "$private"
				}
        }
    ]
END_OF_JSON

#print LOG "JSON body:\n$body\n";

$client->POST("https://ws.core.rackspace.com/ctkapi/query", $body, \%auth_header);

if ($client->responseCode() ne '200')
	{
	print LOG "Unknown core failure!\n";
	print LOG Dumper($client->responseContent());
	failure ("AIQUM core ticket failure on $hostname", "Unknown core failure!\nReview log file at $logfile.");
	exit;
	}
	
print LOG "Ticket comment successful.\n";
my $ticketdata = from_json($client->responseContent());
#print LOG Dumper($ticketdata);
}

#########################################################################
# subroutine to change ticket status
#########################################################################
sub ticket_status
{
my $username = shift;
my $password = shift;
my $ticketnum = shift;
my $status = shift;
my $hostname = hostname;

# Authenticate to core and create an auth header
my $client = REST::Client->new();
$body = "{\"password\": \"$password\"}";
$client->POST("https://ws.core.rackspace.com/ctkapi/login/$username", $body);
if ($client->responseCode() ne '200')
	{
	print LOG "Core authentication failed!\n";
	print LOG Dumper($client->responseContent());
	failure ("AIQUM core ticket script failure on $hostname", "Core authentication failure!\nReview log file at $logfile.");
	exit;
	}
print LOG "Authentication successful.\n";

$auth_hashref = decode_json($client->responseContent());
$token = $auth_hashref->{authtoken};
%auth_header = ( 'X-auth' => $token);


# new JSON with keyword args
$body = <<"END_OF_JSON";
    [
        {
            "class": "Ticket.Ticket",
			"load_arg": "$ticketnum",
            "method": "setStatusByName",
            "args": [],
            "keyword_args": {
				"status_name": "$status"
				}
        }
    ]
END_OF_JSON

#print LOG "JSON body:\n$body\n";

$client->POST("https://ws.core.rackspace.com/ctkapi/query", $body, \%auth_header);

if ($client->responseCode() ne '200')
	{
	print LOG "Unknown core failure!\n";
	print LOG Dumper($client->responseContent());
	failure ("AIQUM core ticket script failure on $hostname", "Unknown core failure!\nReview log file at $logfile.");
	exit;
	}
	
print LOG "Ticket status change successful.\n";
my $ticketdata = from_json($client->responseContent());
#print LOG Dumper($ticketdata);
}
#####################################################################
# send an email alert, log the error, and terminate
#####################################################################
sub failure
{
my $subject = shift;
my $message = shift;
print LOG "Unrecoverable error!: $message\n";
send_email ($subject, $message);
die ("Unrecoverable error!: $message\n");
}
#####################################################################
# subroutine to send an email message for complete failures and then exit
#####################################################################
sub send_email
{
my $subject = shift;
my $body = shift;
my $mailhost = "72.32.127.70";
my $mail_from = "page.nasengineers\@rackspace.com";
my $mail_to = "page.nasengineers\@rackspace.com";
my $hostname = hostname;

my $smtp = Net::SMTP->new($mailhost, Timeout => 60);

$smtp->mail($mail_from);
if ($smtp->to($mail_to)) 
	{
	$smtp->data();
	$smtp->datasend("Subject: $subject\n");
	$smtp->datasend("\n");
	$smtp->datasend("$body\n");
	$smtp->dataend();
	} 
else 
	{
	print LOG "SMTP Error: ", $smtp->message();
	}
$smtp->quit;
}
###############################################################################
# lookup a cluster name from node or vserver using um cli command output
###############################################################################
sub cluster_lookup
{
my $user = shift;
my $pass = shift;
my $lookup = shift;
my $cluster = "";
my $um_out = "";
my $hostname = hostname;
print LOG "Searching for cluster associated with $lookup.\n";
$um_out = `um cli login -u $user -p $pass & um svm list & um cluster node list`;
print LOG "UM cli login failed!\n" unless $um_out =~ /login successful/i;
# commenting out this alert because main sends an alert if no cluster name is found 
#send_email ("AIQUM core ticket script error on $hostname", "UM cli login failed!\nReview log file at $logfile.") unless $um_out =~ /login successful/i;
my @um_out = split /\n/, $um_out;
foreach $line (@um_out)
	{
	chomp ($line);
	#print "$line" if $debug;
	if ($line =~ /$lookup/i)
		{
		($junk, $junk, $cluster, $junk) = split /\s+/, $line, 4;
		print LOG "Found cluster $cluster for $lookup.\n";
		return ($cluster);
		}
	}
}