startexp.in 11 KB
Newer Older
1 2
#!/usr/bin/perl -wT
use English;
3
use Getopt::Std;
4

5 6 7 8
#
# This gets invoked from the Web interface. CD into the proper directory
# and do the tb stuff.
#
9 10 11 12
# The -b (batch) argument is so that this script can be part of a batchmode
# that starts/ends experiments offline. In that case, we don't want to put
# it into the background and send email, but just want an exit status 
# returned to the batch system.
13
#
14 15 16 17 18 19 20 21
# usage: startexp [-b] <pid> <eid> <nsfile>
#
sub usage()
{
    print STDOUT "Usage: startexp [-b] <pid> <eid> <nsfile>\n";
    exit(-1);
}
my  $optlist = "b";
22

23 24 25
#
# Configure variables
#
26 27 28
my $TB       = "@prefix@";
my $DBNAME   = "@TBDBNAME@";
my $TBOPS    = "@TBOPSEMAIL@";
29
my $TBINFO   = "$TB/expinfo";
30 31 32 33 34

my $tbdir    = "$TB/bin/";
my $projroot = "/proj";
my $tbdata   = "tbdata";
my $cleanme  = 0;
35
my $batch    = 0;
36

37 38 39 40 41
#
# For debugging all this goo. Leaves the experiment directory intact,
# and placed in a subdir of the project directory.
# 
my $debug    = 1;
42

43 44 45 46 47
#
# Turn off line buffering on output
#
$| = 1;

48
#
49 50 51 52
# Untaint the path
# 
$ENV{'PATH'} = '/bin:/usr/bin';
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
53

54
#
55 56 57 58 59 60 61
# Parse command arguments. Once we return from getopts, all that should
# left are the required arguments.
#
%options = ();
if (! getopts($optlist, \%options)) {
    usage();
}
62
if (@ARGV != 3) {
63
    usage();
64
}
65 66
my $pid   = $ARGV[0];
my $eid   = $ARGV[1];
67
my $tempfile = $ARGV[2];
68 69 70
if (defined($options{"b"})) {
    $batch = $options{"b"};
}
71 72

#
73
# Untaint the arguments.
74
#
75 76
if ($pid =~ /^([-\@\w.]+)$/) {
    $pid = $1;
77 78 79 80
}
if ($eid =~ /^([-\@\w.]+)$/) {
    $eid = $1;
}
81
# Note different taint check (allow /).
82
if ($tempfile =~ /^([-\w.\/]+)$/) {
83
    $tempfile = $1;
84
}
85 86 87
else {
    die("Tainted tempfile name: $tempfile");
}
88

89
my $piddir  = "$projroot/$pid";
90 91 92 93 94
my $expdir  = "$piddir/exp";
my $eiddir  = "$expdir/$eid";
my $nsfile  = "$eid.ns";
my $irfile  = "$eid.ir";
my $repfile = "$eid.report";
95
my $tempns  = "$tempfile.$$";
96 97
my $user_name  = "Startexp Script";
my $user_email = "$TBOPS";
98 99

#
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
# Set up for querying the database.
# 
use Mysql;
my $DB = Mysql->connect("localhost", $DBNAME, "script", "none");

#
# Check to make sure the experiment record exists. To prevent mishap,
# also check to make sure the experiment is in the "not ready" state
# to prevent this record from being used to configure two sets of nodes!
# This could happen if the user invokes this script directly, and that
# is eventually what I want anyway (instead of users using the tb scripts). 
#
$query_result =
    $DB->query("SELECT expt_ready FROM experiments ".
	       "WHERE eid='$eid' and pid='$pid'");

if (! $query_result) {
    fatal("DB Error getting experiment record $pid/$eid\n");
}
if ($query_result->numrows < 1) {
    print STDOUT "No experiment record for $pid/$eid exists!\n";
    exit(1);
}
@row = $query_result->fetchrow_array();
if ($row[0]) {
    fatal("Experiment $pid/$eid is already configured!\n".
	  "You are not allowed to reconfigure experiments unless you\n".
	  "first terminate the existing experiment via the web interface.\n");
}

130
#
131
# Get some user information. 
132 133
#
$query_result =
134
    $DB->query("SELECT uid,usr_name,usr_email from users ".
135 136 137 138 139 140 141
	       "WHERE unix_uid='$EUID'");

if (! $query_result) {
    fatal("DB Error getting user information for uid $EUID\n");
}
if ($query_result->numrows < 1) {
    print STDOUT "Go Away! You do not exist in the Emulab Database.\n";
142 143 144
    exit(1);
}

145
@row = $query_result->fetchrow_array();
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164
$uid        = $row[0];
$user_name  = $row[1];
$user_email = $row[2];

#
# Verify that this person is allowed to start the experiment. Must be
# in the project membership table.
#
$query_result =
    $DB->query("SELECT pid FROM proj_memb ".
	       "WHERE uid=\"$uid\" and pid=\"$pid\"");

if (! $query_result) {
    fatal("DB Error getting project membership for uid $uid\n");
}
if ($query_result->numrows == 0) {
    print STDOUT "Go Away! You are not a member of project $pid\n";
    exit(1);
}
165 166 167 168 169

#
# Copy the nsfile from wherever the web server stuffed it into a temporary
# file. The web server is going to delete it once this script returns.
#
170 171
if (system("/bin/cp", "$tempfile", "$tempns") != 0) {
    fatal("Could not copy $tempfile to $tempns: $!\n");
172
}
173
chmod(0770, "$tempns");
174 175 176 177 178 179

#
# The rest of this goes into the background so that the user sees
# immediate response. We will send email later when the experiment
# is actually torn down.
#
180 181 182 183 184 185 186 187 188 189 190
if (! $batch) {
    if (background()) {
	#
	# Parent exits normally
	#
	print STDOUT
	    "Experiment $pid/$eid is now configuring\n".
	    "You will be notified via email when the experiment is ".
	    "ready to use\n";
	exit(0);
    }
191 192 193 194 195 196 197 198 199
}

#
# Create a directory structure for the experiment in the project directory.
#
if (system("$TB/libexec/mkexpdir $pid $eid") != 0) {
    fatal("$tbdir/mkexpdir failed\n");
}

200 201
#
# Copy the nsfile from wherever the web server stuffed it, into the
202
# experiment directory. We leave the tempns file around till later.
203 204
#
if (! chdir("$eiddir/$tbdata")) {
205
    fatal("Could not chdir to $tbdata in $eiddir: $!\n");
206 207
}

208 209
if (system("/bin/cp", "$tempns", "$nsfile") != 0) {
    fatal("Could not copy $tempns to $eiddir/$tbdata/$nsfile: $!\n");
210 211 212 213 214
}

#
# Run the various scripts.
#
215 216
if (system("$tbdir/tbprerun -nologfile $pid $eid $nsfile") != 0) {
    fatal("tbprerun failed!\n");
217
}
218 219
# So fatal errors run tbend.
$cleanme = 1;
220

221 222
if (system("$tbdir/tbrun -nologfile $pid $eid $irfile") != 0) {
    fatal("tbrun failed!\n");
223 224
}

225 226
if (system("$tbdir/tbreport -v $pid $eid $irfile 2>&1 > $repfile") != 0) {
    fatal("tbreport failed!\n");
227 228
}

229 230 231 232 233
#
# In batchmode, send the report to stdout for the batch daemon.
#
if ($batch) {
    system("$tbdir/tbreport -v $pid $eid $irfile");
234
    print STDOUT "\n\n";
235 236
}

237
#
238 239 240 241 242 243 244 245 246
# Done! Set the ready bit in the experiment record.
# 
$query_result = $DB->query("UPDATE experiments SET expt_ready=1 ".
			   "WHERE eid='$eid' and pid='$pid'");

if (! $query_result) {
    fatal("DB Error setting ready bit in experiment record for $pid/$eid\n");
}

247
#
248 249 250 251 252 253 254 255 256 257 258 259 260
# Grab the entire experiment record for the mail message below.
#
$query_result =
    $DB->query("SELECT expt_name,expt_created,expt_expires from experiments ".
	       "WHERE eid='$eid' and pid='$pid'");

if (! $query_result) {
    fatal("DB Error getting experiment record for $pid/$eid\n");
}
@row = $query_result->fetchrow_array();
$expt_name     = $row[0];
$expt_created  = $row[1];
$expt_expires  = $row[2];
Kristin Wright's avatar
Kristin Wright committed
261

262
print STDOUT "Setup Success\n";
263

264 265 266 267 268 269 270 271 272 273 274 275 276 277
#
# Try to copy off the files for testbed information gathering
#
my $infodir = "$pid-$eid-" . `date +20%y%m%d-%H.%M.%S`;

if ($infodir =~ /^([-\@\w.]+)$/) {
    $infodir = $1;

    if (mkdir("$TBINFO/$infodir", 0770)) {
	system("cp $nsfile $irfile $TBINFO/$infodir");
	system("cp *.ptop *.top $TBINFO/$infodir");
    }
}

278 279 280 281 282 283 284 285
#
# In batch mode, just exit without sending email. Remove tempns file!
#
if ($batch) {
    unlink("$tempns");
    exit(0);
}

286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327
#
# Dump the report file and the log file to the user via email. 
#
open(MAIL, "| /usr/bin/mail ".
     "-s \"TESTBED: New Experiment Created: $pid/$eid\" ".
     "-c $TBOPS \"$user_name <$user_email>\" >/dev/null 2>&1")
    or die "Cannot start mail program: $!";

print MAIL "Your experiment `$eid' in project `$pid' is now configured.\n";
print MAIL "Here is the experiment summary detailing the nodes that were\n";
print MAIL "allocated to you. You may use the `Qualified Name' to log on\n";
print MAIL "to your nodes. See /etc/hosts on your nodes (when running\n";
print MAIL "FreeBSD, Linux, or NetBSD) for the IP name mapping on each node\n";

print MAIL "\n";
print MAIL "User:        $user_name\n";
print MAIL "EID:         $eid\n";
print MAIL "PID:         $pid\n";
print MAIL "Name:        $expt_name\n";
print MAIL "Created:     $expt_created\n";
print MAIL "Expires:     $expt_expires\n";
print MAIL "Directory:   $eiddir\n\n";

if (open(IN, "$repfile")) {
    while (<IN>) {
	print MAIL "$_";
    }
    close(IN);
}

print MAIL "\n\n---------\n\n";

print MAIL "Here is the log of the configuration process.\n";
print MAIL "If you have any questions or problems, please include the\n";
print MAIL "output below in your message to $TBOPS\n\n";

if (open(IN, "$logname")) {
    while (<IN>) {
	print MAIL "$_";
    }
    close(IN);
}
328 329 330 331 332 333 334 335 336 337

if (open(IN, "$nsfile")) {
    print MAIL "\n\n---------\n\n";
    print MAIL "Here is the NS file\n\n";

    while (<IN>) {
	print MAIL "$_";
    }
    close(IN);
}
338 339
close(MAIL);

340
unlink("$tempns");
341
unlink("$logname");
342 343
exit 0;

344 345
sub fatal()
{
346 347 348 349 350 351 352 353 354 355
    my($mesg) = $_[0];

    print STDOUT "$mesg\n";
    print STDOUT "Cleaning up ...\n";

    #
    # If we got far enough to allocate nodes, must run tbend.
    #
    if ($cleanme) {
	tbendit();
356
    }
357 358 359 360 361 362 363 364 365

    #
    # Now we can remove all trace from the DB since it failed.
    # 
    $query_result =
	$DB->query("DELETE from experiments WHERE eid='$eid' and pid='$pid'");

    if (! $query_result) {
	print STDOUT "DB Error deleting experiment record for $pid/$eid\n";
366
    }
367

Leigh B. Stoller's avatar
Leigh B. Stoller committed
368

369 370 371 372
    #
    # In batch mode, exit. Make sure to delete tempns file. 
    #
    if ($batch) {
Leigh B. Stoller's avatar
Leigh B. Stoller committed
373 374 375
	if (chdir($expdir)) {
	    system("/bin/mv", "-f", "$eid", "$eid-$PID");
	}
376 377 378 379
	unlink("$tempns");
	exit(-1);
    }

380 381 382 383 384 385
    #
    # Send a message to the testbed list. Append the logfile if it got
    # that far.
    # 
    open(MAIL, "| /usr/bin/mail ".
	 "-s \"TESTBED: Experiment Configure Failure $pid/$eid\" ".
386
	 "-c $TBOPS \"$user_name <$user_email>\" >/dev/null 2>&1")
387 388 389 390
	or die "Cannot start mail program: $!";

    print MAIL $mesg;

391
    if (open(IN, "$tempns")) {
392 393 394 395 396 397
	print MAIL "\n\n---------\n\n";

	while (<IN>) {
	    print MAIL "$_";
	}
	close(IN);
398
	unlink("$tempns");
399 400
    }

401 402 403
    if (open(IN, "$logname")) {
	print MAIL "\n\n---------\n\n";
	
404
	while (<IN>) {
405
	    print MAIL "$_";
406 407 408
	}
	close(IN);
    }
Leigh B. Stoller's avatar
Leigh B. Stoller committed
409 410 411 412 413 414 415 416 417

    if (open(IN, "$eiddir/assign.log")) {
	print MAIL "\n\n---------\n\n";
	
	while (<IN>) {
	    print MAIL "$_";
	}
	close(IN);
    }
418 419
    close(MAIL);

420
    unlink("$tempns");
421
    unlink("$logname");
Leigh B. Stoller's avatar
Leigh B. Stoller committed
422 423 424
    if (chdir($expdir)) {
	system("/bin/mv", "-f", "$eid", "$eid-$PID");
    }
425
    exit(-1);
426
}
427 428 429 430 431 432 433

#
# If tbprerun finishes, but tbrun fails, lets do a tbend to make sure
# the nodes and vlans are released.
# 
sub tbendit()
{
434 435
	print STDOUT "Running tbend with arguments: -nologfile $pid $eid\n";
	if (system("$tbdir/tbend -nologfile $pid $eid") != 0) {
436 437 438
	    print STDOUT "tbend failed!\n";
	}
}
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475

#
# Put ourselves into the background so that caller sees immediate response.
# Mail notification will happen later.
# 
sub background()
{
    $mypid = fork();
    if ($mypid) {
	return $mypid;
    }

    #
    # We have to disconnect from the caller by redirecting both STDIN and
    # STDOUT away from the pipe. Otherwise the caller (the web server) will
    # continue to wait even though the parent has exited. 
    #
    open(STDIN, "< /dev/null") or
	die("opening /dev/null for STDIN: $!");

    #
    # Create a temporary name for a log file and untaint it.
    #
    $logname = `mktemp /tmp/start-$pid-$eid.XXXXXX`;

    # Note different taint check (allow /).
    if ($logname =~ /^([-\@\w.\/]+)$/) {
	$logname = $1;
    } else {
	die "Bad data in $logname";
    }

    open(STDERR, ">> $logname") or die("opening $logname for STDERR: $!");
    open(STDOUT, ">> $logname") or die("opening $logname for STDOUT: $!");

    return 0;
}