#! /usr/bin/env perl
##**************************************************************
##
## Copyright (C) 1990-2007, Condor Team, Computer Sciences Department,
## University of Wisconsin-Madison, WI.
## 
## Licensed under the Apache License, Version 2.0 (the "License"); you
## may not use this file except in compliance with the License.  You may
## obtain a copy of the License at
## 
##    http://www.apache.org/licenses/LICENSE-2.0
## 
## Unless required by applicable law or agreed to in writing, software
## distributed under the License is distributed on an "AS IS" BASIS,
## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
## See the License for the specific language governing permissions and
## limitations under the License.
##
##**************************************************************

use CondorTest;
use CondorUtils;
use Check::SimpleJob;
use Check::CondorLog;


########################################################
#
# Windows and linux differ in the MASTER is shown with
#	shared port but on linux the shared port daemon owns it
#  should also check number of listening sockets based
#  on superuser port on or off. Collector only when superuser port 
#  on and schedd normally one but two when super port open
#
########################################################
my @listeningdaemons = ();
my %expectedcounts = ();
my %superexpectedcounts = ();
my $mixedmode = 0;
my $debugmode = 0;


# get the actual name of the collector exe, because that's what netstat will show.
my $collector_exe = "condor_collector";
my $collector_path = `condor_config_val collector`; fullchomp($collector_path);
if ($collector_path =~ /(condor_[a-z\.]+)$/) { $collector_exe = $1; }
print "collector_exe=$collector_exe\n";

my $ipv4 = `condor_config_val IPV4_ADDRESS`;
fullchomp($ipv4);
print "IPV4_ADDRESS=$ipv4\n";
my $ipv6 = `condor_config_val IPV6_ADDRESS`;
fullchomp($ipv6);
print "IPV6=$ipv6\n";

if (length($ipv4) > 1) {
	print "adding 1 to $mixedmode since ipv4 address is $ipv4.\n";
	$mixedmode += 1;
}

if (length($ipv6) > 1) {
	print "adding 1 to $mixedmode since ipv6 address is $ipv6.\n";
	$mixedmode += 1;
}

print "Mixedmode:$mixedmode\n";
	
if(is_windows()) {
	# on windows, we match this up with names from the PID files
	@listeningdaemons = (
		"condor_schedd",
		"condor_collector",
		"MASTER",
	);
	%expectedcounts = (
		MASTER=>1,
		condor_schedd=>1,
	);
	%superexpectedcounts = (
		MASTER=>1,
		condor_schedd=>2,
		condor_collector=>1,
	);
} else {
	@listeningdaemons = (
		"condor_schedd",
		"$collector_exe",
		"condor_shared_port",
	);
	if($mixedmode == 2) {
		print "Adjusting listeners for mixed mode, ie 2 shared port listeners\n";
		%expectedcounts = (
			condor_shared_port=>2,
			condor_schedd=>1,
		);
		%superexpectedcounts = (
			condor_shared_port=>2,
			condor_schedd=>2,
			condor_collector=>1,
		);
	} else {
		%expectedcounts = (
			condor_shared_port=>1,
			condor_schedd=>1,
		);
		%superexpectedcounts = (
			condor_shared_port=>1,
			condor_schedd=>2,
			condor_collector=>1,
		);
	}
}

my %piddaemonnames = ();
my %piddaemonlistencount = ();
my @netstat_result = ();
my @alllines = ();
my $phase = "non-super";

$on_execute = sub {
	# use netstat to find open ports
	if(is_windows()) {
		$cmd = "netstat -ano"
	} else {
		$cmd = "netstat -tlpn";
	}
	my $status = runCondorTool($cmd,\@alllines,2,{emit_output=>0});
	if(is_windows()) {
		# process name not in windows netstat output
		@netstat_result = @alllines;
	} else {
		foreach my $line (@alllines) {
			fullchomp($line);
			if($line =~ /condor/) {
				push @netstat_result, $line;
			}
		}
	}

	my $netstatlength = @netstat_result;

	print "Show netstat capture ($netstatlength lines)\n";
	foreach my $line (@netstat_result) {
		fullchomp($line);
		print "nstat: $line\n"
	}

	if (is_windows()) {
		system("tasklist | grep condor");
	} else {
		system("ps -ef | grep condor");
	}
	system("condor_who -daemons -quick");

	CondorTest::CreatePidsFile();

	# allow netstat to fail as some nmi platforms (macos_10.4) don't support it
	if($netstatlength == 0) {
		CondorTest::debug("Warning: 'netstat -tlpn | grep condor' failed, skipping tcp port checks\n",1);
		return;
	} else {
		#print "We have <$netstatlength> lines of netstat info\n";
	}

	# get the log directory for this test
	my $logdir = `condor_config_val log`;
	$logdir =~ s/\012+$//;
	$logdir =~ s/\015+$//;

	# get the pids associated with this personal condor
	my $pid = $logdir . "/PIDS";
	my %familypids = ();
	open(PID_FILE, "<$pid") || die "Can't open pid file $pid.";

	# get just the pid from each line in file
	my @pid_file_contents = <PID_FILE>;
	my @pids = ();
	foreach(@pid_file_contents) {
		chomp;
		print "Processing PIDs <$_>\n";
		#my @temp = split(/[\ \n]/, $_);
		my @temp = split(/\s/, $_);
		push(@pids, $temp[0]);
		print "Adding <$temp[0]> to pid family\n";
		# be able to test that a listener is part of this personal condor
		$familypids{$temp[0]} = 1;
		$piddaemonlistencount{$temp[0]} = 0;
		$piddaemonnames{$temp[0]} = $temp[1];
	}

	# get the pid for the shadow
    my $shadow_log_location = `condor_config_val SHADOW_log`;
	open(SHADOW_LOG, "<$shadow_log_location") || die "Can't open shadow log ($shadow_log_location).";

	my @shadow_log_contents = <SHADOW_LOG>;
	foreach(@shadow_log_contents) {
		chomp;
		# get line with shadow's pid
		if(index($_, "PID = ", 0) != -1) {
			my $r = rindex($_, " ");
			my $shadow_pid = substr($_, $r+1);
			push(@pids, $shadow_pid);
			$familypids{$shadow_pid} = 1;
			$piddaemonlistencount{$shadow_pid} = 0;
			$piddaemonnames{$shadow_pid} = "condor_shadow";
			print "added shadow pid <$shadow_pid>\n";
			last;
		}
	}
	close(SHADOW_LOG);

	# get the pid for the shared_port daemon
    my $shared_port_log_location = `condor_config_val shared_port_log`;
	open(SHARED_PORT_LOG, "<$shared_port_log_location") || die "Can't open shared_port log ($shared_port_log_location).";

	my @shared_port_log_contents = <SHARED_PORT_LOG>;
	foreach(@shared_port_log_contents) {
		chomp;
		# get line with shared_port's pid
		if(index($_, "PID = ", 0) != -1) {
			my $r = rindex($_, " ");
			my $shared_port_pid = substr($_, $r+1);
			push(@pids, $shared_port_pid);
			$familypids{$shared_port_pid} = 1;
			$piddaemonlistencount{$shared_port_pid} = 0;
			$piddaemonnames{$shared_port_pid} = "condor_shared_port";
			print "added shared_port pid <$shared_port_pid>\n";
			last;
		}
	}
	close(SHARED_PORT_LOG);
	print "look at contents of hash piddaemonnames\n";
	foreach my $key (sort keys %piddaemonnames) {
		print "Pid $key is $piddaemonnames{$key}\n";
	}

	CondorTest::debug("pids - @pids\n", 1);
	print "pids - @pids\n";

	foreach my $daemon (@listeningdaemons)
	{
		print "Good Daemon <$daemon>\n";
	}
	# check that only schedd, collector, shared_port have open listening tcp ports
	my $answer = "";
	my $found_listening_daemon = 0;
	my $unexpected_listening_daemon = 1;
	foreach my $candidate (@netstat_result) {
		fullchomp($candidate);
		print "considering <$candidate>\n";
		if(is_windows()) {

			if($candidate =~ /^.*LISTENING\s+(\d+)\s*$/) {
				#print "Yes $1\n";
				my $listeningpid =  $1;
				#print "PID - $listeningpid\n";
				if(exists $familypids{$listeningpid}) {
					$piddaemonlistencount{$listeningpid} += 1;
					#print "In my family <$listeningpid>\n";
					$answer = IsValidListener($listeningpid);
					if($answer eq "yes"){
						print "ok listening pid member:$listeningpid/$piddaemonnames{$listeningpid}\n";
						$found_listening_daemon = 1;
					} else {
						print "BAD listening pid member:$listeningpid/$piddaemonnames{$listeningpid}\n";
						if(is_windows()) {
							#if($piddaemonnames{$listeningpid} eq "MASTER") {
							#} else {
								$unexpected_listening_daemon = 0;
							#}
						} else {
							$unexpected_listening_daemon = 0;
						}
					}
				}
			}
		} elsif($candidate =~ /^.*LISTEN\s+(\d+\/\w+)\s*$/) {
			#print "Yes $1\n";
			my @pdpairing = split /\//, $1;
			print "PID - $pdpairing[0]\n";
			if(exists $familypids{$pdpairing[0]}) {
				print "In my family <$pdpairing[1]>\n";
				if(defined $pdpairing[0]) {
					print "pdpairing[0]:$pdpairing[0]\n";
				} else {
					print "pdpairing[0] not defined\n";
				}
				if(defined $pdpairing[1]) {
					print "pdpairing[1]:$pdpairing[1]\n";
				} else {
					print "pdpairing[1] not defined\n";
				}
				print "\n";
				$piddaemonlistencount{$pdpairing[0]} += 1;
				print "Listen count for $pdpairing[0] set to $piddaemonlistencount{$pdpairing[0]}\n";
				$answer = IsValidListener($pdpairing[1]);
				if($answer eq "yes"){
					print "ok listening pid member <$pdpairing[1]>\n";
					$found_listening_daemon = 1;
				} else {
					print "BAD listening pid member <$pdpairing[1]>\n";
					$unexpected_listening_daemon = 0;
				}
			}
		} else {
			print "Regx failed <$candidate>\n";
		}
	}

   	CondorTest::RegisterResult( $found_listening_daemon,
		(check_name => "Found expected daemon with open listening tcp port",
		test_name => "lib_shared_port-check-ports_van") );
	CondorTest::RegisterResult( $unexpected_listening_daemon, 
		(check_name => "No unexpected daemons with open listening tcp ports",
		test_name => "lib_shared_port-check-ports_van") );
};

$on_exitedsuccess = sub {
	# verify connection requests made via shared port server for each daemon
	CondorLog::RunCheck(
		daemon => "SHARED_PORT",
		match_regexp => "SharedPortServer: request from COLLECTOR"
	);
	CondorLog::RunCheck(
		daemon => "SHARED_PORT",
		match_regexp => "SharedPortServer: request from SCHEDD"
	);
	CondorLog::RunCheck(
		daemon => "SHARED_PORT",
		match_regexp => "SharedPortServer: request from NEGOTIATOR"
	);
	CondorLog::RunCheck(
		daemon => "SHARED_PORT",
		match_regexp => "SharedPortServer: request from STARTD"
	);

};

# COLLECTOR_SUPER_ADDRESS_FILE = $(LOG)\.collector_address.super
# SCHEDD_SUPER_ADDRESS_FILE = $(LOG)\.schedd_address.super
# CREDD_SUPER_USERS=
# QUEUE_SUPER_USERS = CONDOR, system

my $append_condor_config = '
  USE_SHARED_PORT = True
  COLLECTOR_SUPER_ADDRESS_FILE = 
  SCHEDD_SUPER_ADDRESS_FILE =
  DAEMON_LIST = MASTER,SHARED_PORT,SCHEDD,COLLECTOR,NEGOTIATOR,STARTD

  SHARED_PORT_DEBUG = D_COMMAND D_FULLDEBUG
';

CondorTest::StartCondorWithParams(
    append_condor_config => $append_condor_config
);

my $testname = "lib_shared_port-check-ports_van";


$result = SimpleJob::RunCheck(
	test_name => $testname,
    on_execute => $on_execute,
	on_success => $on_exitedsuccess,
    queue_sz => 1,
    duration => 10,
    timeout => 600,
    append_submit_commands => $append_submit_commands
);

ValidateListenCounts();

$phase = "super";
%piddaemonnames = ();
%piddaemonlistencount = ();
@netstat_result = ();
@alllines = ();

my $append_condor_config = '
  COLLECTOR_SUPER_ADDRESS_FILE = $(LOG)\.collector_address.super
  SCHEDD_SUPER_ADDRESS_FILE = $(LOG)\.schedd_address.super
  USE_SHARED_PORT = True
  DAEMON_LIST = MASTER,SHARED_PORT,SCHEDD,COLLECTOR,NEGOTIATOR,STARTD

  SHARED_PORT_DEBUG = D_COMMAND D_FULLDEBUG
';

CondorTest::StartCondorWithParams(
    append_condor_config => $append_condor_config
);

my $testname = "lib_shared_port-check-ports_van";


$result = SimpleJob::RunCheck(
	test_name => $testname,
    on_execute => $on_execute,
	on_success => $on_exitedsuccess,
    queue_sz => 1,
    duration => 10,
    timeout => 600,
    append_submit_commands => $append_submit_commands
);

print "shared port settings\n";
if($result == 1) {
    print "ok\n";
} else {
    print "bad\n";
}

ValidateListenCounts();

CondorTest::EndTest();
exit(0);

sub ValidateListenCounts {
	my $valpid = $$;
	print "Entering ValidateListenCounts !!!!!!!!!!!!!!!!-$valpid-\n";
				foreach my $key (sort keys %piddaemonnames) {
					print "Pid $key is $piddaemonnames{$key}\n";
				}
	my $listencount = 0;
	my $expectedlistencount = 0;
	foreach my $pid (sort keys %piddaemonnames) {
		# find listen count with pid and 
		# expected count with name
		$listencount = $piddaemonlistencount{$pid};
		if($phase ne "super") {
			print "setting ports for non-super case using:$expectedcounts{$piddaemonnames{$pid}}\n";
			if(exists $expectedcounts{$piddaemonnames{$pid}}) {
				print "expected counts pid:$pid, name:$piddaemonnames{$pid} expected count:$expectedcounts{$piddaemonnames{$pid}}\n";
				$expectedlistencount = $expectedcounts{$piddaemonnames{$pid}};
				print "expectedlistencount set to:$expectedlistencount\n";
			} else {
				$expectedlistencount = 0;
				print "expectedlistencount set to:$expectedlistencount\n";
			}
		} else {
			print "setting ports for super case using:$superexpectedcounts{$piddaemonnames{$pid}}\n";
			if(exists $superexpectedcounts{$piddaemonnames{$pid}}) {
				print "expected counts pid:$pid, name:$piddaemonnames{$pid} expected count:$superexpectedcounts{$piddaemonnames{$pid}}\n";
				$expectedlistencount = $superexpectedcounts{$piddaemonnames{$pid}};
				print "expectedlistencount set to:$expectedlistencount\n";
			} else {
				$expectedlistencount = 0;
				print "expectedlistencount set to:$expectedlistencount\n";
			}
		}
		print "Daemon:$piddaemonnames{$pid} expected listens:$expectedlistencount actual listens:$listencount\n";
		if($listencount != $expectedlistencount) {
			print "Daemon:$piddaemonnames{$pid} expected listens:$expectedlistencount actual listens:$listencount\n";
			RegisterResult(0,"test_name", "$testname");
		} else {
			RegisterResult(1,"test_name", "$testname");
		}
	}
}

sub IsValidListener
{
	my $partialdaemonname = shift;
	my $savepid = $partialdaemonname;
	if(is_windows()) {
		#print "IsValidListener: savedpid is $savepid\n";
		# in windows pid passed in and must be mapped to name
		$partialdaemonname = $piddaemonnames{$savepid};
		if(! exists $piddaemonnames{$savepid}) {
			print "pid $savepid has no name\n";
		}
		print "PID:$savepid belongs to $partialdaemonname\n";
	}
	foreach my $gooddaemon (@listeningdaemons) {
		if(index($gooddaemon,$partialdaemonname,0) != -1) {
			#print "$partialdaemonname is $gooddaemon\n";
			return("yes");
		}
	}
	return("no");
}
