From 58640e2c583c71ab6e409039c38d3b8f1d2ccb03 Mon Sep 17 00:00:00 2001 From: Leigh B Stoller Date: Wed, 6 Jan 2016 11:12:34 -0700 Subject: [PATCH] Export linktest via the CM API. * Three actions are exported; start, stop, and status. The last is cause we have to poll to determine when linktest has actually finished or stopped. I hate all this polling. * For start, linktest can be performed synchronously, which is fine on a small experiment, but in general you want to use the async option and check back later. When using async, we return the spewlog URL to the caller so that linktest can be monitored. * A couple of minor changes to linktest itself for using a spew log. * Some simple test rspecs and a linktest.py driver. --- clientside/event/linktest/linktest_control.in | 16 +- protogeni/lib/GeniCMV2.pm.in | 158 +++++++++++++++++- protogeni/test/lantest-v2.rspec | 39 +++++ protogeni/test/linktest-v2.rspec | 4 + protogeni/test/linktest.py | 85 ++++++++++ protogeni/xmlrpc/protogeni-cm.pm.in | 1 + 6 files changed, 297 insertions(+), 6 deletions(-) create mode 100644 protogeni/test/lantest-v2.rspec create mode 100755 protogeni/test/linktest.py diff --git a/clientside/event/linktest/linktest_control.in b/clientside/event/linktest/linktest_control.in index 0b78446be..386fad578 100644 --- a/clientside/event/linktest/linktest_control.in +++ b/clientside/event/linktest/linktest_control.in @@ -1,6 +1,6 @@ #!/usr/bin/perl -wT # -# Copyright (c) 2000-2015 University of Utah and the Flux Group. +# Copyright (c) 2000-2016 University of Utah and the Flux Group. # # {{{EMULAB-LICENSE # @@ -40,17 +40,19 @@ sub usage() "-k - Kill a currently running linktest.\n". "-t - Specify timeout in seconds.\n". "-o - Specify output file for linktest results.\n". + "-e - Tell linktest to send error output to stdout or -o file\n". "-m - Send email to swapper if linktest fails.\n". "-r - Report results only, don't flag errors.\n". "-d - Turn on debugging output.\n"); exit(-1); } -my $optlist = "dkl:o:t:mfr"; +my $optlist = "dkl:o:t:mfre"; my $debug = 1; my $cancel = 0; my $sendmail = 0; my $forcerun = 0; my $reportonly = 0; +my $noerrlog = 0; my $timeout; my $level; my $output; @@ -143,6 +145,9 @@ if (defined($options{"o"})) { die("Bad data in output file: $output\n"); } } +if (defined($options{"e"})) { + $noerrlog = 1; +} if (@ARGV != 2) { usage(); } @@ -223,8 +228,8 @@ if (defined($linktest_pid) && $linktest_pid) { } } elsif ($cancel) { - die("*** $0:\n". - " Linktest is not running on experiment $pid/$eid!\n") + print "Linktest is not running on experiment $pid/$eid!\n"; + exit(0); } my @hosed = (); @@ -348,7 +353,8 @@ push(@cmdargs, ("-g", $unix_gidname)); push(@cmdargs, ("-p", $unix_pidname)); push(@cmdargs, ("-u", $dbuid)); push(@cmdargs, ("-e", "$pid/$eid")); -push(@cmdargs, ("-o", $errlog)); +push(@cmdargs, ("-o", $errlog)) + if (!$noerrlog); push(@cmdargs, "-r") if ($reportonly); diff --git a/protogeni/lib/GeniCMV2.pm.in b/protogeni/lib/GeniCMV2.pm.in index c78d85f8c..9cb4afa78 100755 --- a/protogeni/lib/GeniCMV2.pm.in +++ b/protogeni/lib/GeniCMV2.pm.in @@ -1,6 +1,6 @@ #!/usr/bin/perl -wT # -# Copyright (c) 2008-2015 University of Utah and the Flux Group. +# Copyright (c) 2008-2016 University of Utah and the Flux Group. # # {{{GENIPUBLIC-LICENSE # @@ -106,6 +106,7 @@ my $DELETEIMAGE = "$TB/sbin/delete_image"; my $WAP = "$TB/sbin/withadminprivs"; my $SHAREVLAN = "$TB/sbin/sharevlan"; my $PANIC = "$TB/sbin/panic"; +my $LINKTEST = "$TB/sbin/linktest_control"; my $XMLLINT = "/usr/local/bin/xmllint"; my $PRERENDER = "$TB/libexec/vis/prerender"; my $IMPORTER = "$TB/sbin/image_import"; @@ -4798,5 +4799,160 @@ sub Panic($) return 0; } +# +# Another Emulab specific function to run linktest and return the results. +# +sub RunLinktest($) +{ + my ($argref) = @_; + my $slice_urn = $argref->{'slice_urn'}; + my $credentials = $argref->{'credentials'}; + my $level = $argref->{'level'}; + my $action = $argref->{'action'}; + my $async = $argref->{'async'}; + my $logfile; + + if (! (defined($credentials) && defined($action) && + defined($slice_urn))) { + return GeniResponse->MalformedArgsResponse("Missing arguments"); + } + if ($action !~ /^(start|stop|status)$/) { + return GeniResponse->MalformedArgsResponse("Bad action: $action"); + } + if ($action eq "start") { + $level = 1 + if (!defined($level)); + if ($level !~ /^\d$/ || $level < 1 || $level > 4) { + return GeniResponse->MalformedArgsResponse("Bad linktest level"); + } + } + my ($credential,$speaksfor) = GeniStd::CheckCredentials($credentials); + return $credential + if (GeniResponse::IsResponse($credential)); + + my ($slice, $aggregate) = Credential2SliceAggregate($credential); + return $slice + if (defined($slice) && GeniResponse::IsResponse($slice)); + + if (! (defined($slice) && defined($aggregate))) { + return GeniResponse->Create(GENIRESPONSE_SEARCHFAILED, undef, + "Sliver does not exist"); + } + my $user = GeniCM::CreateUserFromCertificate($credential); + return $user + if (GeniResponse::IsResponse($user)); + + main::AddLogfileMetaDataFromSlice($slice); + + if ($slice_urn ne $slice->urn()) { + return GeniResponse->Create(GENIRESPONSE_FORBIDDEN(), undef, + "Credential does not match the URN"); + } + if (!defined(GeniCM::FlipToUser($slice, $user))) { + return GeniResponse->Create(GENIRESPONSE_ERROR, undef, + "FlipToUser failed"); + } + my $experiment = $slice->GetExperiment(); + if (!defined($experiment)) { + return GeniResponse->Create(GENIRESPONSE_ERROR, undef, + "No local experiment for slice"); + } + # + # When async, we are going to create a log file and return a spew + # url to the caller, who can then read the output of the linktest + # in real time. + # + if ($action eq "start" && defined($async)) { + $logfile = Logfile->Create($experiment->GetGroup()); + if (!defined($logfile)) { + return GeniResponse->Create(GENIRESPONSE_ERROR); + } + $logfile->SetPublic(1); + $logfile->Open(); + + my $mypid = main::WrapperFork(); + if ($mypid) { + # + # Wait a few seconds for immediate errors or finish. + # + sleep(5); + my $kid = waitpid($mypid, &WNOHANG); + if ($kid == $mypid) { + my $stat = $?; + if ($stat & 127) { + # died with a signal, return the signal + $stat = $stat & 127; + } else { + # else return the exit code + $stat = $stat >> 8; + } + my $output = undef; + my $fname = $logfile->filename(); + $output = `cat $fname` if (-s $fname); + $logfile->Delete(1); + print STDERR "foo\n"; + if ($stat) { + return GeniResponse->Create(GENIRESPONSE_ERROR, + undef, $output); + } + return GeniResponse->Create(GENIRESPONSE_SUCCESS, + {"status" => "stopped", + "results" => $output}, $output); + } + print STDERR $logfile->URL() . "\n"; + return GeniResponse->Create(GENIRESPONSE_SUCCESS, + {"status" => "running", + "url" => $logfile->URL()}); + } + } + my $pid = $experiment->pid(); + my $eid = $experiment->eid(); + my $output; + + if ($action eq "status") { + return GeniResponse->Create(GENIRESPONSE_SUCCESS, + {"status" => + ($experiment->linktest_pid() ? + "running" : "stopped")}); + } + elsif ($action eq "stop") { + $output = GeniUtil::ExecQuiet("$LINKTEST -k $pid $eid"); + } + else { + my $command = "$LINKTEST -l $level -e "; + $command .= "-o " . $logfile->filename() . " " + if (defined($async)); + $command .= "$pid $eid"; + + $output = GeniUtil::ExecQuiet($command); + } + my $exitval = $?; + + if (defined($async)) { + if ($exitval) { + my $fname = $logfile->filename(); + $output .= `cat $fname` if (-s $fname); + } + # + # Delay for bit then delete the log file. Need to do this a different + # way at some point. + # + $logfile->Close(); + sleep(30); + $logfile->Delete(1); + } + if ($exitval) { + print STDERR $output; + return -1 + if (defined($async)); + return GeniResponse->Create(GENIRESPONSE_ERROR, undef, $output); + } + return 0 + if (defined($async)); + return GeniResponse->Create(GENIRESPONSE_SUCCESS, + {"status" => "stopped", + "results" => $output}, $output); +} + # _Always_ make sure that this 1 is at the end of the file... 1; diff --git a/protogeni/test/lantest-v2.rspec b/protogeni/test/lantest-v2.rspec new file mode 100644 index 000000000..1f56aa466 --- /dev/null +++ b/protogeni/test/lantest-v2.rspec @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/protogeni/test/linktest-v2.rspec b/protogeni/test/linktest-v2.rspec index 44360e6d2..cf55aae62 100644 --- a/protogeni/test/linktest-v2.rspec +++ b/protogeni/test/linktest-v2.rspec @@ -17,5 +17,9 @@ http://www.protogeni.net/resources/rspec/2/request.xsd" + + diff --git a/protogeni/test/linktest.py b/protogeni/test/linktest.py new file mode 100755 index 000000000..a02458322 --- /dev/null +++ b/protogeni/test/linktest.py @@ -0,0 +1,85 @@ +#! /usr/bin/env python +# +# Copyright (c) 2008-2015 University of Utah and the Flux Group. +# +# {{{GENIPUBLIC-LICENSE +# +# GENI Public License +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and/or hardware specification (the "Work") to +# deal in the Work without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Work, and to permit persons to whom the Work +# is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Work. +# +# THE WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER DEALINGS +# IN THE WORK. +# +# }}} +# + +# +# +import sys +import pwd +import getopt +import os +import re + +ACCEPTSLICENAME=1 +dokill = 0; +action = "start" + +execfile( "test-common.py" ) + +if len(REQARGS) == 1 and REQARGS[0] == "kill": + dokill = 1 + action = "kill" +pass + +# +# Get a credential for myself, that allows me to do things at the SA. +# +mycredential = get_self_credential() +print "Got my SA credential. Looking for slice ..." + +# +# Lookup slice. +# +myslice = resolve_slice( SLICENAME, mycredential ) +print "Found the slice, asking for a credential ..." + +# +# Get the slice credential. +# +slicecred = get_slice_credential( myslice, mycredential ) +print "Got the slice credential, " + action + "ing linktest ..." + +# +# Start Linktest +# +params = {} +params["slice_urn"] = myslice["urn"] +params["credentials"] = (slicecred,) +if dokill: + params["action"] = "stop" +else: + params["action"] = "start" + params["level"] = 1 + params["async"] = 1 + pass +rval,response = do_method("cm", "RunLinktest", params, version="2.0") +if rval: + Fatal("Could not " + action + " Linktest") + pass +print str(response["value"]) diff --git a/protogeni/xmlrpc/protogeni-cm.pm.in b/protogeni/xmlrpc/protogeni-cm.pm.in index 16e453063..3ed15d417 100644 --- a/protogeni/xmlrpc/protogeni-cm.pm.in +++ b/protogeni/xmlrpc/protogeni-cm.pm.in @@ -118,6 +118,7 @@ elsif ($GENI_VERSION eq "2.0") { "AddNodes" => \&GeniCMV2::AddNodes, "DeleteNodes" => \&GeniCMV2::DeleteNodes, "Panic" => \&GeniCMV2::Panic, + "RunLinktest" => \&GeniCMV2::RunLinktest, }; } -- GitLab