blob: b9e2d4e07e7929509b126a614f770a7a459b5ee4 [file] [log] [blame]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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.
package TestDriverCurl;
###########################################################################
# Class: TestDriver
# A base class for TestDrivers.
#
use TestDriverFactory;
use TestReport;
use File::Path;
use IPC::Run;
use Data::Dump qw(dump);
use JSON;
use HTTP::Daemon;
use HTTP::Status;
use Data::Compare;
use strict;
use English;
use Storable qw(dclone);
use File::Glob ':glob';
my $passedStr = 'passed';
my $failedStr = 'failed';
my $abortedStr = 'aborted';
my $skippedStr = 'skipped';
my $dependStr = 'failed_dependency';
##############################################################################
# Sub: printResults
# Static function, can be used by test_harness.pl
# Print the results so far, given the testStatuses hash.
#
# Paramaters:
# testStatuses - reference to hash of test status results.
# log - reference to file handle to print results to.
# prefix - A title to prefix to the results
#
# Returns:
# None.
#
sub printResults
{
my ($testStatuses, $log, $prefix) = @_;
my ($pass, $fail, $abort, $depend, $skipped) = (0, 0, 0, 0, 0);
foreach (keys(%$testStatuses)) {
($testStatuses->{$_} eq $passedStr) && $pass++;
($testStatuses->{$_} eq $failedStr) && $fail++;
($testStatuses->{$_} eq $abortedStr) && $abort++;
($testStatuses->{$_} eq $dependStr) && $depend++;
($testStatuses->{$_} eq $skippedStr) && $skipped++;
}
my $msg = "$prefix, PASSED: $pass FAILED: $fail SKIPPED: $skipped ABORTED: $abort "
. "FAILED DEPENDENCY: $depend";
print $log "$msg\n";
print "$msg\n";
}
##############################################################################
# Sub: printGroupResultsXml
# Print the results for the group using junit xml schema using values from the testStatuses hash.
#
# Paramaters:
# $report - the report object to use to generate the report
# $groupName - the name of the group to report totals for
# $testStatuses - the hash containing the results for the tests run so far
# $totalDuration- The total time it took to run the group of tests
#
# Returns:
# None.
#
sub printGroupResultsXml
{
my ( $report, $groupName, $testStatuses, $totalDuration) = @_;
$totalDuration=0 if ( !$totalDuration );
my ($pass, $fail, $abort, $depend) = (0, 0, 0, 0);
foreach my $key (keys(%$testStatuses)) {
if ( $key =~ /^$groupName/ ) {
($testStatuses->{$key} eq $passedStr) && $pass++;
($testStatuses->{$key} eq $failedStr) && $fail++;
($testStatuses->{$key} eq $abortedStr) && $abort++;
($testStatuses->{$key} eq $dependStr) && $depend++;
}
}
my $total= $pass + $fail + $abort;
$report->totals( $groupName, $total, $fail, $abort, $totalDuration );
}
##############################################################################
# Sub: new
# Constructor, should only be called by TestDriverFactory.
#
# Paramaters:
# None
#
# Returns:
# None.
sub new
{
my $proto = shift;
my $class = ref($proto) || $proto;
my $self = {};
bless($self, $class);
$self->{'wrong_execution_mode'} = "_xyz_wrong_execution_mode_zyx_";
return $self;
}
##############################################################################
# Sub: globalSetup
# Set up before any tests are run. This gives each individual driver a chance to do
# setup. This function will only be called once, before all the tests are
# run. A driver need not implement it. It is a virtual function.
#
# Paramaters:
# globalHash - Top level hash from config file (does not have any group
# or test information in it).
# log - log file handle
#
# Returns:
# None
#
sub globalSetup
{
my ($self, $globalHash, $log) = @_;
my $subName = (caller(0))[3];
# Setup the output path
my $me = `whoami`;
chomp $me;
$globalHash->{'runid'} = $me . "." . time;
# if "-ignore false" was provided on the command line,
# it means do run tests even when marked as 'ignore'
if (defined($globalHash->{'ignore'}) && $globalHash->{'ignore'} eq 'false') {
$self->{'ignore'} = 'false';
}
if (! defined $globalHash->{'localpathbase'}) {
$globalHash->{'localpathbase'} = '/tmp';
}
$globalHash->{'outpath'} = $globalHash->{'outpathbase'} . "/" . $globalHash->{'runid'} . "/";
$globalHash->{'localpath'} = $globalHash->{'localpathbase'} . "/" . $globalHash->{'runid'} . "/";
$globalHash->{'webhdfs_url'} = $ENV{'WEBHDFS_URL'};
$globalHash->{'templeton_url'} = $ENV{'TEMPLETON_URL'};
$globalHash->{'current_user'} = $ENV{'USER_NAME'};
$globalHash->{'current_group_user'} = $ENV{'GROUP_USER_NAME'};
$globalHash->{'current_other_user'} = $ENV{'OTHER_USER_NAME'};
$globalHash->{'current_group'} = $ENV{'GROUP_NAME'};
$globalHash->{'keytab_dir'} = $ENV{'KEYTAB_DIR'};
$globalHash->{'inpdir_local'} = $ENV{'TH_INPDIR_LOCAL'};
$globalHash->{'inpdir_hdfs'} = $ENV{'TH_INPDIR_HDFS'};
$globalHash->{'is_secure_mode'} = $ENV{'SECURE_MODE'};
# add libexec location to the path
if (defined($ENV{'PATH'})) {
$ENV{'PATH'} = $globalHash->{'scriptPath'} . ":" . $ENV{'PATH'};
} else {
$ENV{'PATH'} = $globalHash->{'scriptPath'};
}
IPC::Run::run(['mkdir', '-p', $globalHash->{'localpath'}], \undef, $log, $log) or
die "Cannot create localpath directory " . $globalHash->{'localpath'} .
" " . "$ERRNO\n";
# Create the temporary directory
IPC::Run::run(['mkdir', '-p', $globalHash->{'tmpPath'}], \undef, $log, $log) or
die "Cannot create temporary directory " . $globalHash->{'tmpPath'} .
" " . "$ERRNO\n";
}
###############################################################################
# Sub: globalCleanup
# Clean up after all tests have run. This gives each individual driver a chance to do
# cleanup. This function will only be called once, after all the tests are
# run. A driver need not implement it. It is a virtual function.
#
# Paramaters:
# globalHash - Top level hash from config file (does not have any group
# or test information in it).
# log - log file handle
#
# Returns:
# None
sub globalCleanup
{
my ($self, $globalHash, $log) = @_;
IPC::Run::run(['rm', '-rf', $globalHash->{'tmpPath'}], \undef, $log, $log) or
warn "Cannot remove temporary directory " . $globalHash->{'tmpPath'} .
" " . "$ERRNO\n";
}
###############################################################################
# Sub: runTest
# Run a test. This is a pure virtual function.
#
# Parameters:
# testcmd - reference to hash with meta tags and command to run tests.
# Interpretation of the tags and the command are up to the subclass.
# log - reference to a stream pointer for the logs
#
# Returns:
# @returns reference to hash. Contents of hash are defined by the subclass.
#
sub runTest
{
my ($self, $testCmd, $log) = @_;
my $subName = (caller(0))[3];
# Handle the various methods of running used in
# the original TestDrivers
if ( $testCmd->{'url'} ) {
return $self->runCurlCmd( $testCmd, $log);
} else {
die "$subName FATAL Did not find a testCmd that " .
"I know how to handle : " . $testCmd->{'Curl'};
}
}
###############################################################################
sub replaceParameters
{
my ($self, $testCmd, $aPfix, $log) = @_;
my $url = $testCmd->{$aPfix . 'url'};
$url =~ s/:WEBHDFS_URL:/$testCmd->{'webhdfs_url'}/g;
$url =~ s/:TEMPLETON_URL:/$testCmd->{'templeton_url'}/g;
$url = $self->replaceParametersInArg($url, $testCmd, $log);
$testCmd->{$aPfix . 'url'} = $url;
$testCmd->{$aPfix . 'upload_file'} =
$self->replaceParametersInArg($testCmd->{$aPfix . 'upload_file'}, $testCmd, $log);
$testCmd->{$aPfix . 'user_name'} =
$self->replaceParametersInArg($testCmd->{$aPfix . 'user_name'}, $testCmd, $log);
if (defined $testCmd->{$aPfix . 'post_options'}) {
my @options = @{$testCmd->{$aPfix . 'post_options'}};
my @new_options = ();
foreach my $option (@options) {
$option = $self->replaceParametersInArg($option, $testCmd, $log);
push @new_options, ($option);
}
$testCmd->{$aPfix . 'post_options'} = \@new_options;
}
if (defined $testCmd->{$aPfix . 'json_field_substr_match'}) {
my $json_matches = $testCmd->{$aPfix . 'json_field_substr_match'};
my @keys = keys %{$json_matches};
foreach my $key (@keys) {
my $new_value = $self->replaceParametersInArg($json_matches->{$key}, $testCmd, $log);
$json_matches->{$key} = $new_value;
}
}
}
###############################################################################
sub replaceParametersInArg
{
my ($self, $arg, $testCmd, $log) = @_;
if(! defined $arg){
return $arg;
}
my $outdir = $testCmd->{'outpath'} . $testCmd->{'group'} . "_" . $testCmd->{'num'};
$arg =~ s/:UNAME:/$testCmd->{'current_user'}/g;
$arg =~ s/:UNAME_GROUP:/$testCmd->{'current_group_user'}/g;
$arg =~ s/:UNAME_OTHER:/$testCmd->{'current_other_user'}/g;
$arg =~ s/:UGROUP:/$testCmd->{'current_group'}/g;
$arg =~ s/:OUTDIR:/$outdir/g;
$arg =~ s/:INPDIR_HDFS:/$testCmd->{'inpdir_hdfs'}/g;
$arg =~ s/:INPDIR_LOCAL:/$testCmd->{'inpdir_local'}/g;
$arg =~ s/:TNUM:/$testCmd->{'num'}/g;
return $arg;
}
###############################################################################
sub getBaseCurlCmd(){
my ($self) = @_;
my @curl_cmd = ("curl", '--silent','--show-error', '-H','Expect:');
if (defined $ENV{'SOCKS_PROXY'}) {
push @curl_cmd, ('--socks5-hostname', $ENV{'SOCKS_PROXY'});
}
return @curl_cmd;
}
###############################################################################
sub runCurlCmd(){
my ($self, $testCmd, $log) = @_;
if (defined $testCmd->{'upload_file'}) {
return $self->upload_file($testCmd,$log);
} else {
#if there are setup steps, run them first
if (defined $testCmd->{'setup'}) {
my $i = 0;
foreach my $setupCmd (@{$testCmd->{'setup'}}){
$i++;
print $log "\nRUNNING SETUP COMMAND: $i\n";
my $pfix = "setup_${i}_";
my $setupTestCmd = $self->createSetupCmd($testCmd, $setupCmd, $pfix, $log);
my $setupResult = $self->execCurlCmd($setupTestCmd, $pfix, $log);
#if status code is set in setup, check if it matches results
if(defined $setupTestCmd->{"${pfix}status_code"}){
$self->checkResStatusCode($setupResult, $setupTestCmd->{"${pfix}status_code"}, $log);
}
}
}
return $self->execCurlCmd($testCmd, "", $log);
}
}
###############################################################################
sub createSetupCmd(){
my ($self, $testCmd, $setupCmd, $pfix, $log) = @_;
my $newTestCmd = dclone ($testCmd);
for my $key (keys %$setupCmd){
$newTestCmd->{$pfix . $key} = $setupCmd->{$key};
}
return $newTestCmd;
}
###############################################################################
sub upload_file(){
my ($self, $testCmd, $log) = @_;
$testCmd->{'method'} = 'PUT';
my $result = $self->execCurlCmd($testCmd, "", $log);
my $checkRes = $self->checkResStatusCode($result, 100, $log);
if ($checkRes == 0) {
#fail
return 0;
}
my $header = $result->{'header_fields'};
#final url where the file should be stored
my $location = $header->{'Location'};
$testCmd->{'url'} = $location;
$result = $self->execCurlCmd($testCmd, "", $log);
return $result;
}
###############################################################################
sub execCurlCmd(){
my ($self, $testCmd, $argPrefix, $log) = @_;
my @curl_cmd = $self->getBaseCurlCmd();
# Set up file locations
my $subName = (caller(0))[3];
my $filePrefix = $testCmd->{'localpath'} . $testCmd->{'group'} . "_" . $argPrefix . $testCmd->{'num'};
my $cmd_body = $filePrefix . ".cmd_body";
#results
my $res_header = $filePrefix . ".res_header";
my $res_body = $filePrefix . ".res_body";
my $outdir = $filePrefix . ".out";
my $stdoutfile = "$outdir/stdout";
my $stderrfile = "$outdir/stderr";
mkpath( [ $outdir ] , 0, 0755) if ( ! -e outdir );
if ( ! -e $outdir ) {
print $log "$0.$subName FATAL could not mkdir $outdir\n";
die "$0.$subName FATAL could not mkdir $outdir\n";
}
$self->replaceParameters($testCmd, $argPrefix, $log );
my $method = $testCmd->{ $argPrefix . 'method'};
my $url = $testCmd->{ $argPrefix . 'url'};
my @options = ();
if (defined $testCmd->{$argPrefix . 'post_options'}) {
@options = @{$testCmd->{$argPrefix . 'post_options'}};
}
#handle authentication based on secure mode
my $user_name = $testCmd->{ $argPrefix . 'user_name' };
if (defined $testCmd->{'is_secure_mode'} && $testCmd->{'is_secure_mode'} =~ /y.*/i) {
push @curl_cmd, ('--negotiate', '-u', ':');
#if keytab dir is defined, look for a keytab file for user and do a kinit
if(defined $testCmd->{'keytab_dir'} && defined $user_name){
$user_name =~ /(.*?)(\/|$)/;
my $just_uname = $1; #uname without principal
my $keytab_dir = $testCmd->{'keytab_dir'};
print $log "regex " . "${keytab_dir}/*${just_uname}\.*keytab";
my @files = bsd_glob( "${keytab_dir}/*${just_uname}\.*keytab" );
if(scalar @files == 0){
die "Could not find keytab file for user $user_name in $keytab_dir";
} elsif(scalar @files > 1){
die "More than one keytab file found for user $user_name in $keytab_dir";
}
my @cmd = ('kinit', '-k', '-t', $files[0], $user_name);
print $log "Command @cmd";
IPC::Run::run(\@cmd, \undef, $log, $log) or
die "Could not kinit as $user_name using " . $files[0] . " $ERRNO";
}
} else {
#if mode is unsecure
if (defined $user_name) {
my $user_param = "user.name=${user_name}";
if ($method eq 'POST' ) {
push @options, $user_param;
} else {
if ($url =~ /\?/) {
#has some parameters in url
$url = $url . '&' . $user_param;
} else {
$url = $url . '?' . $user_param;
}
}
}
}
if (defined $testCmd->{'format_header'}) {
push @curl_cmd, ('-H', $testCmd->{'format_header'});
}
if (defined $testCmd->{$argPrefix . 'format_header'}) {
push @curl_cmd, ('-H', $testCmd->{$argPrefix . 'format_header'});
}
if (defined $testCmd->{$argPrefix . 'upload_file'}) {
push @curl_cmd, ('-T', $testCmd->{$argPrefix . 'upload_file'});
}
# if(!defined $testCmd->{'post_options'}){
# $testCmd->{'post_options'} = \();
# }
if (defined $testCmd->{$argPrefix . 'check_call_back'}) {
my $d = HTTP::Daemon->new || die;
$testCmd->{'http_daemon'} = $d;
$testCmd->{'callback_url'} = $d->url . 'templeton/$jobId';
push @curl_cmd, ('-d', 'callback=' . $testCmd->{'callback_url'});
# push ${testCmd->{'post_options'}}, ('callback=' . $testCmd->{'callback_url'});
# #my @options = @{$testCmd->{'post_options'}};
# print $log "post options @options\n";
}
foreach my $option (@options) {
push @curl_cmd, ('-d', $option);
}
push @curl_cmd, ("-X", $method, "-o", $res_body, "-D", $res_header);
push @curl_cmd, ($url);
print $log "$0:$subName Going to run command : " . join (' ', @curl_cmd);
print $log "\n";
my %result;
my $out;
my $err;
IPC::Run::run(\@curl_cmd, \undef, $out, $err)
or die "Failed running curl cmd " . join ' ', @curl_cmd;
$result{'rc'} = $? >> 8;
$result{'stderr'} = $err;
$result{'stdout'} = $out;
$result{'body'} = `cat $res_body`;
my @full_header = `cat $res_header`;
$result{'full_header'} = join '\n', @full_header;
#find the final http status code
for my $line ( @full_header){
if($line =~ /.*(HTTP\/1.1)\s+(\S+)/){
$result{'status_code'} = $2;
}
}
my %header_field;
foreach my $line (@full_header) {
chomp $line;
$line =~ /(.*?)\s*:\s*(.*)/;
if (defined $1 && defined $2 ) {
$header_field{$1} = $2;
}
}
$result{'header_fields'} = \%header_field;
print $log "result : " . dump(%result);
#dump(%result);
return \%result;
}
###############################################################################
# Sub: generateBenchmark
# Generate benchmark results. This is a pure virtual function.
#
# Parameters:
# benchmarkcmd - reference to hash with meta tags and command to
# generate benchmark. Interpretation of the tags and the command are up to
# log - reference to a stream pointer for the logs
# the subclass.
#
# Returns:
# @returns reference to hash. Contents of hash are defined by the subclass.
#
sub generateBenchmark
{
my %result;
return \%result;
}
###############################################################################
# Sub: compare
# Compare the results of the test run with the generated benchmark results.
# This is a pure virtual function.
#
# Parameters:
# benchmarkcmd - reference to hash with meta tags and command to
# testResult - reference to hash returned by runTest.
# benchmarkResult - reference to hash returned by generateBenchmark.
# log - reference to a stream pointer for the logs
# testHash - reference to hash with meta tags and commands
#
# Returns:
# @returns reference true if results are the same, false otherwise. "the
# same" is defined by the subclass.
#
sub compare
{
my ($self, $testResult, $benchmarkResult, $log, $testCmd) = @_;
my $subName = (caller(0))[3];
my $result = 1; # until proven wrong...
if (defined $testCmd->{'status_code'}) {
my $res = $self->checkResStatusCode($testResult, $testCmd->{'status_code'}, $log);
if ($res == 0) {
$result = 0;
}
}
my $json_hash;
my %json_info;
if (defined $testCmd->{'json_field_substr_match'} || $testCmd->{'json_field_match_object'}) {
my $json = new JSON;
$json_hash = $json->utf8->decode($testResult->{'body'});
my $json_matches = $testCmd->{'json_field_substr_match'};
my $json_matches_object = $testCmd->{'json_field_match_object'};
%json_info = %$json_hash;
if (defined $json_info{'info'}) {
%json_info = %{$json_info{'info'}};
}
print $log "\n\n json_info";
print $log dump(%json_info);
print $log "\n\n";
if (defined $json_hash->{'id'}) {
print STDERR "jobid " . $json_hash->{'id'} . "\n";
$json_info{'id'} = $json_hash->{'id'};
}
if(defined $json_matches->{'location_perms'} || defined $json_matches->{'location_group'}){
$self->setLocationPermGroup(\%json_info, $testCmd, $log);
}
foreach my $key (keys %$json_matches) {
my $json_field_val = $json_info{$key};
if( (ref($json_field_val) && ! UNIVERSAL::isa($json_field_val,'SCALAR')) ||
(!ref($json_field_val) && ! UNIVERSAL::isa(\$json_field_val,'SCALAR')) ){
#flatten the object into a string
$json_field_val = dump($json_field_val);
}
my $regex_expected_value = $json_matches->{$key};
print $log "Comparing $key: $json_field_val with regex /$regex_expected_value/\n";
if ($json_field_val !~ /$regex_expected_value/s) {
print $log "$0::$subName INFO check failed:"
. " json pattern check failed. For field "
. "$key, regex <" . $regex_expected_value
. "> did not match the result <" . $json_field_val
. ">\n";
$result = 0;
}
}
foreach my $key (keys %$json_matches_object) {
my $json_field_val = $json_info{$key};
my $regex_expected_obj = $json->utf8->decode($json_matches_object->{$key});
print $log "Comparing $key: " . dump($json_field_val) . ",expected value: " . dump($regex_expected_obj);
if (!Compare($json_field_val, $regex_expected_obj)) {
print $log "$0::$subName INFO check failed:"
. " json compare failed. For field "
. "$key, regex <" . dump($regex_expected_obj)
. "> did not match the result <" . dump($json_field_val)
. ">\n";
$result = 0;
}
}
}
#kill it if there is a request to kill
if($testCmd->{'kill_job_timeout'}){
sleep $testCmd->{'kill_job_timeout'};
my $jobid = $json_hash->{'id'};
if (!defined $jobid) {
print $log "$0::$subName INFO check failed: "
. "no jobid (id field)found in result";
$result = 0;
} else {
$self->killJob($testCmd, $jobid, $log);
}
}
#try to get the call back url request until timeout
if ($result == 1 && defined $testCmd->{'check_call_back'}) {
my $d = $testCmd->{'http_daemon'};
$d->timeout(300); #wait for 5 mins
my $url_requested;
$testCmd->{'callback_url'} =~ s/\$jobId/$json_hash->{'id'}/g;
print $log "Expanded callback url : <" . $testCmd->{'callback_url'} . ">\n";
do{
print $log "Waiting for call back url request\n";
if (my $c = $d->accept) {
if (my $r = $c->get_request) {
my $durl = $d->url;
chop $durl;
$url_requested = $durl . $r->uri->path ;
print $log "Got request at url <" . $url_requested . ">\n";
$c->send_status_line(200);
$c->close;
}
undef($c);
} else {
print $log "Timeout on wait on call back url" . "\n";
$result = 0;
}
}while (defined $url_requested && $url_requested ne $testCmd->{'callback_url'});
$d->close;
if (!defined $url_requested || $url_requested ne $testCmd->{'callback_url'}) {
print $log "failed to recieve request on call back url";
$result = 0;
}
}
if (defined $testCmd->{'check_job_created'} || defined $testCmd->{'check_job_complete'}) {
my $jobid = $json_hash->{'id'};
if (!defined $jobid) {
print $log "$0::$subName INFO check failed: "
. "no jobid (id field)found in result";
$result = 0;
} else {
my $jobResult = $self->getJobResult($testCmd, $jobid, $log);
my $json = new JSON;
my $res_hash = $json->utf8->decode($jobResult->{'body'});
if (! defined $res_hash->{'status'}) {
print $log "$0::$subName INFO check failed: "
. "jobresult not defined ";
$result = 0;
}
if ($testCmd->{'check_job_complete'}) {
my $jobComplete;
my $NUM_RETRIES = 60;
my $SLEEP_BETWEEN_RETRIES = 5;
#first wait for job completion
while ($NUM_RETRIES-- > 0) {
$jobComplete = $res_hash->{'status'}->{'jobComplete'};
if (defined $jobComplete && lc($jobComplete) eq "true") {
last;
}
sleep $SLEEP_BETWEEN_RETRIES;
$jobResult = $self->getJobResult($testCmd, $jobid, $log);
$json = new JSON;
$res_hash = $json->utf8->decode($jobResult->{'body'});
}
if ( (!defined $jobComplete) || lc($jobComplete) ne "true") {
print $log "$0::$subName INFO check failed: "
. " timeout on wait for job completion ";
$result = 0;
} else {
# job has completed, check the runState value
my $runState = $res_hash->{'status'}->{'runState'};
my $runStateVal = $self->getRunStateNum($testCmd->{'check_job_complete'});
if ( (!defined $runState) || $runState ne $runStateVal) {
print $log "check_job_complete failed. got runState $runState, expected $runStateVal";
$result = 0;
}
}
}
}
}
return $result;
}
###############################################################################
sub setLocationPermGroup{
my ($self, $job_info, $testCmd, $log) = @_;
my $location = $job_info->{'location'};
$location =~ /hdfs.*:\d+(\/.*)\/(.*)/;
my $dir = $1;
my $file = $2;
my $testCmdBasics = $self->copyTestBasicConfig($testCmd);
$testCmdBasics->{'method'} = 'GET';
$testCmdBasics->{'num'} = $testCmdBasics->{'num'} . "_checkFile";
$testCmdBasics->{'url'} = ':WEBHDFS_URL:/webhdfs/v1'
. $dir . '?op=LISTSTATUS';
my $result = $self->execCurlCmd($testCmdBasics, "", $log);
my $json = new JSON;
my $json_hash = $json->utf8->decode($result->{'body'});
my @filestatuses = @{$json_hash->{'FileStatuses'}->{'FileStatus'}};
foreach my $filestatus (@filestatuses){
if($filestatus->{'pathSuffix'} eq $file){
$job_info->{'location_perms'} = numPermToStringPerm($filestatus->{'permission'});
$job_info->{'location_group'} = $filestatus->{'group'};
$job_info->{'location_owner'} = $filestatus->{'owner'};
last;
}
}
}
###############################################################################
#convert decimal string to binary string
sub dec2bin {
my $decimal = shift;
my $binary = unpack("B32", pack("N", $decimal));
$binary =~ s/^0+(?=\d)//; # remove leading zeros
return $binary;
}
###############################################################################
#convert single digit of the numeric permission format to string format
sub digitPermToStringPerm{
my $numPerm = shift;
my $binaryPerm = dec2bin($numPerm);
my $stringPerm = "";
if($binaryPerm =~ /1\d\d$/){
$stringPerm .= "r";
}else{
$stringPerm .= "-";
}
if($binaryPerm =~ /\d1\d$/){
$stringPerm .= "w";
}else{
$stringPerm .= "-";
}
if($binaryPerm =~ /\d\d1$/){
$stringPerm .= "x";
}else{
$stringPerm .= "-";
}
}
###############################################################################
# convert numeric permission format to string format
sub numPermToStringPerm{
my $numPerm = shift;
$numPerm =~ /(\d)(\d)(\d)$/;
return digitPermToStringPerm($1)
. digitPermToStringPerm($2)
. digitPermToStringPerm($3);
}
###############################################################################
sub getRunStateNum{
my ($self, $job_complete_state) = @_;
if (lc($job_complete_state) eq 'success') {
return 2;
} elsif (lc($job_complete_state) eq 'failure') {
return 3;
} elsif (lc($job_complete_state) eq 'killed') {
return 5;
}
}
###############################################################################
sub getJobResult{
my ($self, $testCmd, $jobid, $log) = @_;
my $testCmdBasics = $self->copyTestBasicConfig($testCmd);
$testCmdBasics->{'method'} = 'GET';
$testCmdBasics->{'num'} = $testCmdBasics->{'num'} . "_jobStatusCheck";
$testCmdBasics->{'url'} = ':TEMPLETON_URL:/templeton/v1/queue/'
. $jobid . '?' . "user.name=:UNAME:" ;
return $self->execCurlCmd($testCmdBasics, "", $log);
}
###############################################################################
sub killJob{
my ($self, $testCmd, $jobid, $log) = @_;
my $testCmdBasics = $self->copyTestBasicConfig($testCmd);
$testCmdBasics->{'method'} = 'DELETE';
$testCmdBasics->{'num'} = $testCmdBasics->{'num'} . "_killJob";
$testCmdBasics->{'url'} = ':TEMPLETON_URL:/templeton/v1/queue/'
. $jobid . '?' . "user.name=:UNAME:" ;
return $self->execCurlCmd($testCmdBasics, "", $log);
}
###############################################################################
#Copy test config essential for running a sub command
sub copyTestBasicConfig{
my ($self, $testCmd) = @_;
my %testCmdBasics;
foreach my $key (keys %$testCmd) {
if ($key ne 'method'
&& $key ne 'url'
&& $key ne 'upload_file'
&& $key ne 'post_options'
) {
$testCmdBasics{$key} = $testCmd->{$key};
}
}
# $testCmdBasics{'localpath'} = $testCmd->{'localpath'};
# $testCmdBasics{'group'} = $testCmd->{'group'};
# $testCmdBasics{'num'} = $testCmd->{'num'};
return \%testCmdBasics;
}
###############################################################################
sub checkResStatusCode{
my ($self, $testResult, $e_status_code, $log) = @_;
my $subName = (caller(0))[3];
# print STDERR "expected " . $e_status_code . " was " . $testResult->{'status_code'};
if (!defined $testResult->{'status_code'} ||
$testResult->{'status_code'} != $e_status_code) {
print $log "$0::$subName INFO Check failed: status_code " .
"$e_status_code expected, test returned " .
"<$testResult->{'status_code'}>\n";
return 0;
}
return 1;
}
###############################################################################
# Sub: recordResults
# Record results of the test run. The required fields are filled in by the
# test harness. This call gives an individual driver a chance to fill in
# additional fields of cmd, cmd_id, expected_results, and actual_results.
# this function does not have to be implemened.
# This is a virtual function.
#
# Parameters:
# status - status of test passing, true or false
# testResult - reference to hash returned by runTest.
# benchmarkResult - reference to hash returned by generateBenchmark.
# dbinfo - reference to hash that will be used to fill in db.
# log - reference to hash that will be used to fill in db.
#
# Returns:
# None
#
sub recordResults
{
}
###############################################################################
# Sub: cleanup
# Clean up after a test. This gives each individual driver a chance to do
# cleanup. A driver need not implement it. It is a virtual function.
#
# Parameters:
# status - status of test passing, true or false
# testHash - reference to hash that was passed to runTest() and
# generateBenchmark().
# testResult - reference to hash returned by runTest.
# benchmarkResult - reference to hash returned by generateBenchmark.
# log - reference to a stream pointer for the logs
#
# Returns:
# None
#
sub cleanup
{
}
###############################################################################
# Sub: run
# Run all the tests in the configuration file.
#
# Parameters:
# testsToRun - reference to array of test groups and ids to run
# testsToMatch - reference to array of test groups and ids to match.
# If a test group_num matches any of these regular expressions it will be run.
# cfg - reference to contents of cfg file
# log - reference to a stream pointer for the logs
# dbh - reference database connection
# testStatuses- reference to hash of test statuses
# confFile - config file name
# startat - test to start at.
# logname - name of the xml log for reporting results
#
# Returns:
# @returns nothing
# failed.
#
sub run
{
my ($self, $testsToRun, $testsToMatch, $cfg, $log, $dbh, $testStatuses,
$confFile, $startat, $logname ) = @_;
my $subName = (caller(0))[3];
my $msg="";
my $duration=0;
my $totalDuration=0;
my $groupDuration=0;
my $sawstart = !(defined $startat);
# Rather than make each driver handle our multi-level cfg, we'll flatten
# the hashes into one for it.
my %globalHash;
my $runAll = ((scalar(@$testsToRun) == 0) && (scalar(@$testsToMatch) == 0));
# Read the global keys
foreach (keys(%$cfg)) {
next if $_ eq 'groups';
$globalHash{$_} = $cfg->{$_};
}
$globalHash{$_} = $cfg->{$_};
# Do the global setup
$self->globalSetup(\%globalHash, $log);
my $report=0;
# my $properties= new Properties(0, $globalHash{'propertiesFile'});
my %groupExecuted;
foreach my $group (@{$cfg->{'groups'}}) {
print $log "INFO $subName at ".__LINE__.": Running TEST GROUP(".$group->{'name'}.")\n";
my %groupHash = %globalHash;
$groupHash{'group'} = $group->{'name'};
# Read the group keys
foreach (keys(%$group)) {
next if $_ eq 'tests';
$groupHash{$_} = $group->{$_};
}
# Run each test
foreach my $test (@{$group->{'tests'}}) {
# Check if we're supposed to run this one or not.
if (!$runAll) {
# check if we are supposed to run this test or not.
my $foundIt = 0;
foreach (@$testsToRun) {
if (/^$groupHash{'group'}(_[0-9]+)?$/) {
if (not defined $1) {
# In this case it's just the group name, so we'll
# run every test in the group
$foundIt = 1;
last;
} else {
# maybe, it at least matches the group
my $num = "_" . $test->{'num'};
if ($num eq $1) {
$foundIt = 1;
last;
}
}
}
}
foreach (@$testsToMatch) {
my $protoName = $groupHash{'group'} . "_" . $test->{'num'};
if ($protoName =~ /$_/) {
if (not defined $1) {
# In this case it's just the group name, so we'll
# run every test in the group
$foundIt = 1;
last;
} else {
# maybe, it at least matches the group
my $num = "_" . $test->{'num'};
if ($num eq $1) {
$foundIt = 1;
last;
}
}
}
}
next unless $foundIt;
}
# This is a test, so run it.
my %testHash = %groupHash;
foreach (keys(%$test)) {
$testHash{$_} = $test->{$_};
}
my $testName = $testHash{'group'} . "_" . $testHash{'num'};
# if ( $groupExecuted{ $group->{'name'} }== 0 ){
# $groupExecuted{ $group->{'name'} }=1;
#
# my $xmlDir= $globalHash{'localxmlpathbase'}."/run".$globalHash->{'UID'};
# mkpath( [ $xmlDir ] , 1, 0777) if ( ! -e $xmlDir );
#
# my $filename = $group->{'name'}.".xml";
# $report = new TestReport ( $properties, "$xmlDir/$filename" );
# $report->purge();
# }
# Check that ignore isn't set for this file, group, or test
if (defined $testHash{'ignore'}) {
print $log "Ignoring test $testName, ignore message: " .
$testHash{'ignore'} . "\n";
next;
}
# Have we not reached the starting point yet?
if (!$sawstart) {
if ($testName eq $startat) {
$sawstart = 1;
} else {
next;
}
}
# Check that this test doesn't depend on an earlier test or tests
# that failed. Don't abort if that test wasn't run, just assume the
# user knew what they were doing and set it up right.
my $skipThisOne = 0;
foreach (keys(%testHash)) {
if (/^depends_on/ && defined($testStatuses->{$testHash{$_}}) &&
$testStatuses->{$testHash{$_}} ne $passedStr) {
print $log "Skipping test $testName, it depended on " .
"$testHash{$_} which returned a status of " .
"$testStatuses->{$testHash{$_}}\n";
$testStatuses->{$testName} = $dependStr;
$skipThisOne = 1;
last;
}
}
if ($skipThisOne) {
printResults($testStatuses, $log, "Results so far");
next;
}
print $log "\n******************************************************\n";
print $log "\nTEST: $confFile::$testName\n";
print $log "******************************************************\n";
print $log "Beginning test $testName at " . time . "\n";
my %dbinfo = (
'testrun_id' => $testHash{'trid'},
'test_type' => $testHash{'driver'},
#'test_file' => $testHash{'file'},
'test_file' => $confFile,
'test_group' => $testHash{'group'},
'test_num' => $testHash{'num'},
);
my $beginTime = time;
my $endTime = 0;
my ($testResult, $benchmarkResult);
eval {
$testResult = $self->runTest(\%testHash, $log);
$endTime = time;
$benchmarkResult = $self->generateBenchmark(\%testHash, $log);
my $result =
$self->compare($testResult, $benchmarkResult, $log, \%testHash);
$msg = "INFO: $subName() at ".__LINE__.":Test $testName";
if ($result eq $self->{'wrong_execution_mode'}) {
$msg .= " SKIPPED";
$testStatuses->{$testName} = $skippedStr;
} elsif ($result) {
$msg .= " SUCCEEDED";
$testStatuses->{$testName} = $passedStr;
} else {
$msg .= " FAILED";
$testStatuses->{$testName} = $failedStr;
}
$msg= "$msg at " . time . "\n";
#print $msg;
print $log $msg;
$duration = $endTime - $beginTime;
$dbinfo{'duration'} = $duration;
$self->recordResults($result, $testResult
, $benchmarkResult, \%dbinfo, $log);
};
if ($@) {
$msg= "ERROR $subName at : ".__LINE__." Failed to run test $testName <$@>\n";
#print $msg;
print $log $msg;
$testStatuses->{$testName} = $abortedStr;
$dbinfo{'duration'} = $duration;
}
eval {
$dbinfo{'status'} = $testStatuses->{$testName};
if ($dbh) {
$dbh->insertTestCase(\%dbinfo);
}
};
if ($@) {
chomp $@;
warn "Failed to insert test case info, error <$@>\n";
}
$self->cleanup($testStatuses->{$testName}, \%testHash, $testResult,
$benchmarkResult, $log);
#$report->testcase( $group->{'name'}, $testName, $duration, $msg, $testStatuses->{$testName}, $testResult ) if ( $report );
$report->testcase( $group->{'name'}, $testName, $duration, $msg, $testStatuses->{$testName} ) if ( $report );
$groupDuration = $groupDuration + $duration;
$totalDuration = $totalDuration + $duration;
printResults( $testStatuses, $log, "Results so far" );
}
if ( $report ) {
$report->systemOut( $logname, $group->{'name'});
printGroupResultsXml( $report, $group->{'name'}, $testStatuses, $groupDuration );
}
$report = 0;
$groupDuration=0;
}
# Do the global cleanup
$self->globalCleanup(\%globalHash, $log);
}
# TODO These should be removed
sub tmpIPCRun(){
my $self = shift;
my $subName = (caller(0))[3];
my $runningSubName= shift;
my $refCmd = shift;
my @cmd = @$refCmd;
my $log = shift;
my $msg = shift;
print $log "$0::$subName INFO Running ( @cmd )\n";
my $result= `@cmd`;
if ( $@ ) {
my $msg= "$0::$subName FATAL Failed to run from $runningSubName $msg < $@ >\n$result\n";
print $log $msg;
die "$msg";
}
return $?;
}
sub tmpIPCRunSplitStdoe {
my $self = shift;
my $subName = (caller(0))[3];
my $runningSubName= shift;
my $refCmd = shift;
my @cmd = @$refCmd;
my $dir = shift;
my $log = shift;
my $msg = shift;
my $die = shift;
my $failed = 0;
my $outfilename = $dir."out.tmp";
my $errfilename = $dir."err.tmp";
print $log "$0::$subName INFO Running from $runningSubName ( @cmd 1>$outfilename 2>$errfilename )\n";
#make sure files are writeable
open( TMP, ">$outfilename" ) || die "$0::$subName FATAL: Cannot open $outfilename for writing\n";
close( TMP );
open( TMP, ">$errfilename" ) || die "$0::$subName FATAL: Cannot open $errfilename for writing\n";
close( TMP );
#RUN CMD
my $msg;
print $log `@cmd 1>$outfilename 2>$errfilename`;
my $failed=0;
if ( $@ ) {
$msg= "$0::$subName FATAL < $@ >\n";
$failed++;
}
#READ FILES
my $stdout="";
my $stderr="";;
open( TMP, "$outfilename" ) || die "$0::$subName FATAL: Cannot open $outfilename for reading\n";
while ( <TMP> ) {
$stdout .= $_;
}
close( TMP );
open( TMP, "$errfilename" ) || die "$0::$subName FATAL: Cannot open $errfilename for reading\n";
while ( <TMP> ) {
$stderr .= $_;
}
close( TMP );
#DIE IF Test Failed, otherwise return stdout and stderr
if ( $failed ) {
$msg = "$0::$subName FATAL: Faied from $runningSubName \nSTDOUT:" . $stdout . "\nSTDERR:" . $stderr . "\n" if ( $failed );
print $log "$msg";
die $msg if ( $die != "1" ); #die by defaultast
return ( -1, $stdout, $stderr );
}
return ( $?, $stdout, $stderr);
}
sub tmpIPCRunJoinStdoe {
my $self = shift;
my $subName = (caller(0))[3];
my $runningSubName= shift;
my $refCmd = shift;
my @cmd = @$refCmd;
my $outfilename= shift;
my $log = shift;
my $msg = shift;
my $die = shift;
#make sure files are writeable
open( TMP, ">$outfilename" ) || die "$0::$subName FATAL: Cannot open $outfilename for writing\n";
close( TMP );
#RUN CMD
my $msg;
my $failed=0;
print $log "$0::$subName INFO Running ( @cmd 2>&1$outfilename 2>/dev/null )\n";
print $log `@cmd 2>&1 > $outfilename 2>/dev/null`;
if ( $@ ) {
$failed++;
$msg= "$0::$subName FATAL < $@ >\n";
}
#READ FILES
my $stdoe="";
open( TMP, "$outfilename" ) || die "$0::$subName FATAL: Cannot open $outfilename for reading\n";
while ( <TMP> ) {
$stdoe .= $_;
}
close( TMP );
if ( $failed ) {
print $log "$msg";
die $msg if ( $die != "1" ); #die by default
return ( -1 );
}
return ( $? );
}
1;