#!/usr/bin/perl
#
#  reliably stop/start 'servicemix' service on master
#  
#  Note: - file location isn't strictly defined
#        - Linux version
#        - run from user apl:
#          sudo /opt/sarch/smix/bin/smxctl start|stop|status

use File::Basename qw(dirname);
use Net::OpenSSH;
use Expect;
use strict;
use warnings;
use Log::Log4perl;

# Load configuration
my $dirname = dirname(__FILE__);
open (ICF,"$dirname/../../base/etc/install.conf") || die("Cannot read install.conf\n");
open (ECF,"$dirname/../../base/etc/env.conf")     || die("Cannot read env.conf\n");
map {$ENV{$1}=$2 if /^(\w+)=(\S+)/} grep {/^\w+=\S+/} (<ICF>,<ECF>);
close ICF;
close ECF;

# Globals
my $APL="$ENV{APL}" || "/opt/sarch";
my $APL_VAR = $ENV{APL_VAR} || "/var/sarch";
my $SMX_HOME = $ENV{SMX_HOME} || "/opt/apache/servicemix";
my $SMX_INST = "$SMX_HOME/instances/instance.properties"; # Instance properties
my $SMX_USER = 'smx'; # ServiceMix user
my $SMX_PASSWD = '';
my $SMX_RESTART_TIMEOUT = 15; # seconds to enforce on "restart" command
my $SSH_TIMEOUT = 10;
my $USAGE = "Usage: smxctl <install|start|stop|status|uninstall>\n";
my $CMD = $ARGV[0];
my $RETVAL = 0;
$| = 1;	# Disable buffering for STDOUT;

my $LOG_CFG_FILENAME = "$APL/common/etc/logger_smix.conf"; # logger settings config file
my $LOG_CFG_REFRESH_INTERVAL = 60;                         # logger config refresh interval
Log::Log4perl::init_and_watch($LOG_CFG_FILENAME, $LOG_CFG_REFRESH_INTERVAL);
my $log = Log::Log4perl::get_logger('SMIXCTL');

#enforce_smxctl_is_singular_and_running();

main();

#-------------------------------------------------------------------
# Routines

sub enforce_smxctl_is_singular_and_running {
    my $cmd = "ps ax | grep smxctl|grep -v grep|grep -v $$";
    # at first: let's do a dry-run
    my $ret = `$cmd`;
    return if !$ret;
    # now we know things are getting ugly
    $log->error("Anotehr 'smxctl' is already running: \n$ret") ;
    # let's kill all other "smxctl" instances
    `$cmd . "| cut -c 1-6 | xargs kill -9";`;
    # now, let's restart servicemix itself, as it is highly likely is damaged
    $log->error("Restarting ServiceMix");
    
    eval {
        local $SIG{ALRM} = sub { die "alarm\n" };
        alarm($SMX_RESTART_TIMEOUT);
        $log->warn('Issuing "servicemix restart" command');
        $ret = `/etc/init.d/servicemix restart`;
        $log->warn("Received from 'servicemix restart': \n$ret");
        alarm(0);
    };
    # things are LIKELY bad
    if ($@){
        $log->error("Received from timeout forcer: $@");
        if ($@ eq "alarm\n") {
            # now we KNOW that even "restart" command does not work, so we need to force it
            $log->warn("Terminating servicemix by 'kill -9'");
            system("ps ax|grep '/opt/apache/servicemix'|grep -v grep|cut -c 1-6|xargs kill -9");
            $log->warn("Starting servicemix:\n".`/etc/init.d/servicemix start`);
        }
    }
    
}

sub find_smx_instances {
    my @pids;
    my $out = `ps -u $SMX_USER -o pid,command | grep java | grep org.apache.karaf.shell.wrapper.Main | cut -c 1-6`;
    @pids = $out =~ /^\s*(\d+)\D/mg if $out;
	$log->error("ServiceMix instance not found") unless @pids;
    return @pids;
}


sub stop {
    $log->info("Stop Stratus bundles");

    return unless find_smx_instances;

    $RETVAL = exec_smx_command('features:uninstall Stratus');
    $log->error('Failed to stop Stratus bundles') unless $RETVAL;
}



sub start {
	$log->info("Start Stratus bundles");

	return unless find_smx_instances;

        $RETVAL = exec_smx_command('features:install Stratus');
	$log->error('Failed to start stratus bundles') unless $RETVAL;
}


sub status {
    $RETVAL = 1 if find_smx_instances;
    print $RETVAL ? "run\n" : "stop\n";
}

sub service_pid {  # service based status info
  my $status=`/etc/init.d/servicemix status`;
  if($status=~/is\s+running\s+\((\d+)\)/) {  # smx is running
    return $1;                                # return PID
  }
  return "DOWN"
}


#---------------------------------------------------------------
# clean start for service mix:
#  - stop service mix as service
#  - stop with kill (service pid) if still running
#  - stop with kill karaf if  still running
#  - remove all from servicemix/data/
#  - start service mix (service)
#
# set RETAVL=1 if success otherwise RETVAL=0 (FAIL)
sub cleanstart {
   my ($dropData) = @_;
   $log->info("CleanStart: let it stop normally");

   eval {
   	local $SIG{ALRM} = sub { die "ALARM" };
   	local $SIG{INT} = 'IGNORE';
   	alarm 15;   
   	system("/etc/init.d/servicemix stop");
   };
   alarm 0;
   foreach my $sig (15,15,9) {  # killing smx based on service pid
   	my $pid=service_pid;
   	last if $pid eq 'DOWN';
   	$log->info("CleanStart: kill -$sig $pid\n");
   	kill $sig => $pid;
   	sleep 2; 
   }
   foreach my $sig (15,15,9) {  # killing smx based on java pid
     my @pids=find_smx_instances;
     last if not @pids;
     foreach my $pid (@pids) {
        $log->info("CleanStart: Kill -$sig $pid\n");
        kill $sig => $pid;
        sleep 2;
     }
   }
   return ($RETVAL=0) if find_smx_instances; # return and set FAIL
   if ($dropData) {
      $log->info('CleanStart: cleaning data');
      system("rm -rf $SMX_HOME/data/activemq $SMX_HOME/data/cache $SMX_HOME/data/generated-bundles $SMX_HOME/data/pax-web-jsp $SMX_HOME/data/security");
   }
   $log->info('CleanStart: restarting');
   system("/etc/init.d/servicemix start");
   $RETVAL=1;
}


sub getFeaturesFile {
	my ($aplRole, $featuresFile);

	if (-f "$APL_VAR/installed") {
	        $aplRole = $ENV{APL_ROLE} || die "APL_ROLE is not defined\n";
	} else {
	        $aplRole = -f "$APL_VAR/conf/master/s_master" ? 'MASTER' : 'NODE';
	}
	
	if ($aplRole eq 'MASTER') {
		$featuresFile = "$APL/smix/etc/master-features.xml";
	} elsif ($aplRole eq 'NODE') {
		$featuresFile = "$APL/smix/etc/node-features.xml";
	} else {
		$log->error("Failed to identify master or node");
		return undef;
	}

	if (-f $featuresFile) {
		return $featuresFile;
	} else {
		$log->error("Features file $featuresFile not found");
		return undef;
	}
}


sub is_installed {
   my ($ret,$list)=exec_smx_command('features:list|grep -i Stratus');
   return 1 if $list=~/installed/;
   return 0;           # not installed
}


sub install {
	$log->info("Install Stratus bundles");

	my $featuresFile = getFeaturesFile();
	return unless $featuresFile;

        system("rm -f $SMX_HOME/deploy/master-features.xml");
        system("rm -f $SMX_HOME/deploy/node-features.xml");

        system("cp $featuresFile $SMX_HOME/deploy");
        my $featuresFileBase = `basename $featuresFile`;
        system("chmod 644 $SMX_HOME/deploy/$featuresFileBase");

        $RETVAL = 1;
}


sub uninstall {
    $log->info("Unistall Stratus bundles");

    my $featuresFile = getFeaturesFile();
    return unless $featuresFile;

    my $featuresFileBase = `basename $featuresFile`;
    system("rm -f $SMX_HOME/deploy/$featuresFileBase");

    $RETVAL = 1;
}


sub listBundles {
    return exec_smx_command('osgi:list|grep -i Stratus; osgi:start-level');
}


sub list {
    my $list;
    ($RETVAL,$list) = listBundles;
    print $list;
}


sub extstatus {
    my @smx;
    my ($cmd_ok,$list);
    my %status;
    @smx = find_smx_instances;
    if (not @smx) {
	$status{STATUS} = "stop";
    }
    elsif (@smx > 1) {
        $status{STATUS} = "duplicate";
    }
    else {
	($cmd_ok,$list) = listBundles;
	unless ($cmd_ok) {
	    $status{STATUS} = "fail";
	    $status{MESSAGE} = "Connection failed";
	}
	else {
	    $status{FAILED_BUNDLES} = "";
	    $status{STATUS} = "run";
	    my $cnt = 0;
	    my $inact = 0;
	    my $failed = 0;
	    foreach my $line (split /\n/, $list) {
	    	if ($line =~ /^Level (\d+)/) {;
		    	$status{LEVEL} = $1;
		    	next;
		}

		my ($id,$state,$status_bp,$status_spring) = 
		    $line =~ /^\[\s*(\d+)\s*\]\s+ # ID
		               \[\s*(\w+)\s*\]\s+ # State
		               \[\s*(\w*)\s*\]\s+ # Blueprint Status
		               \[\s*(\w*)\s*\]\s+ # Spring Status
		             /x;
		next if not $id;
		$cnt++;
		$inact++ if not $state or $state !~ /^Active$/i;
		$failed++ if $status_bp and $status_bp=~/^Fail/i or 
		             $status_spring and $status_spring=~/^Fail/i;

		if(not $state or $state !~ /^Active$/i or $status_bp and $status_bp=~/^Fail/i or
                             $status_spring and $status_spring=~/^Fail/i) {
		    $status{FAILED_BUNDLES}.="$id,";
		}
	    }

            $status{FAILED_BUNDLES} = substr($status{FAILED_BUNDLES},0,length($status{FAILED_BUNDLES})-1);

	    if ($cnt == 0) {
		$status{BUNDLE_STATUS} = "fail";
		$status{MESSAGE} = "No OSGi bundles installed";
	    }
	    elsif ($inact > 0) {
		$status{BUNDLE_STATUS} = "fail";
		$status{MESSAGE} = "Inactive OSGi bundles found";
	    }
	    elsif ($failed > 0) {
	        $status{BUNDLE_STATUS} = "fail";
	        $status{MESSAGE} = "Bundles in failure state found";
	    }
	    else {
		$status{BUNDLE_STATUS} = "ok";
	    }
	}
    }
    
    while (my ($key,$val) = each %status) {
	print "$key=$val\n";
    }
    $RETVAL = 1;
}

sub kill_siblings {
    system("ps ax | grep smxctl | grep -v $$ | grep -v grep | cut -c 1-6 | xargs kill -9 &>/dev/null");
    system("/usr/bin/pkill -f karaf-client &>/dev/null");
    return 1;
}

sub restart_om {
    return exec_smx_command('osgi:restart ObjectManagement ');
}

sub exec_smx_command {
    my $cmd = shift;
    my $conn_str = smx_conn_str();
	$log->debug("servicemix conn str: $conn_str");

    my $ssh = Net::OpenSSH->new($conn_str, batch_mode => 1, timeout => $SSH_TIMEOUT , master_opts => [-o => "StrictHostKeyChecking=no", -o => "UserKnownHostsFile=/dev/null"]);
    my ($out, $err) = $ssh->capture2($cmd);
    my $ok = $ssh->error ? 0 : 1;
	if (!$ok) {
		print "err=".$ssh->error . "; stdout=$out; stderr=$err";
		$log->error("err=".$ssh->error . "; stdout=$out; stderr=$err");
	} else {
		if ($out =~ /error/i || $out =~ /fail/i || $err) {
			$log->warn("stdout=$out; stderr=$err");
		}
	}
    return wantarray ? ($ok,$out,$err,$ssh->error) : $ok;
}


sub smx_conn_str {
	if (! $SMX_PASSWD) {
		#
		# retreive password from config file
		#
		my %conf;
		open (USERS,"$SMX_HOME/etc/users.properties") || die("Cannot read $SMX_HOME/etc/users.properties");
		map {$conf{$1}=$2 if /^(\w+)=(\S+)/} grep {/^\w+=\S+/} (<USERS>);
		close USERS;

		die ("$SMX_USER user not found") unless defined ($conf{$SMX_USER});

		my $smxGroup;
		($SMX_PASSWD, $smxGroup) = split(',', $conf{$SMX_USER});

		die ("Failed to get $SMX_USER password") unless $SMX_PASSWD;
	}

	return "$SMX_USER:$SMX_PASSWD\@localhost:8101";
}


sub announce {
    print "$_[0]:" . "\t" x 5;
}

sub complete {
    print $RETVAL?"[  OK  ]\n" : "[  FAILED  ]\n";
}

sub restart_bundles {
    my $bundles = shift;

    $bundles =~ s/,/ /g;

    $RETVAL = exec_smx_command("restart $bundles");
    $log->warn("Restarted bundles: $bundles");

}

sub keyscan {
	my $found = `/usr/bin/ssh-keygen -F localhost | grep -P 'found.+DSA'`;
	return 1 if $found;
	my $key = `/usr/bin/ssh-keyscan -t dsa -p 8101 localhost 2>/dev/null`;
	return 0 if $?;
	if ($key and $key =~ /ssh-dss/) {
		if (open FH, ">>/root/.ssh/known_hosts") {
			print FH $key;
			close FH;
			return 1;
		}
		else {
			return 0;
		}
	}
	else {
		return 0;
	}
}

sub main {
    die $USAGE unless defined $CMD;
    die "ServiceMix is not installed!\n" unless -d $SMX_HOME;
    die "ServiceMix user is not defined\n" unless $SMX_USER;
    for($CMD) {
        /^start$/  && do {
			announce("Starting Stratus ServiceMix bundles");
			start;
			complete;
			last };
        /^restart$/  && do { cleanstart(0);  last };
        /^cleanstart$/  && do { cleanstart(1);  last };
        /^stop$/   && do {
			announce("Stopping Stratus ServiceMix bundles");
			stop;
			complete;
			last };
        /^status$/ && do { status; last };
        /^extstatus$/ && do { extstatus; last };
        /^install$/ && do {
			announce("Deploying components into ServiceMix");
			install;
			complete;
			last };
        /^uninstall$/ && do {
			announce("Uninstall Stratus ServiceMix bundles");
			uninstall;
			complete;
			last };
        /^list$/ && do { list; last };
        /^is_installed$/ && do { $RETVAL=is_installed; last };
        /^kill_siblings/ && do { $RETVAL=kill_siblings; last };
        /^restart_om/ && do { $RETVAL=restart_om; last };
	/^restart_bundles=([\d\,]+)$/ && do { $RETVAL=restart_bundles($1); last };
	/^keyscan/ && do { $RETVAL=keyscan; last };
	die $USAGE;
    }

    exit 1 - $RETVAL;
}
