beginexp.php 16.1 KB
Newer Older
1 2
<?php
#
3
# Copyright (c) 2000-2007, 2012 University of Utah and the Flux Group.
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
# 
# {{{EMULAB-LICENSE
# 
# This file is part of the Emulab network testbed software.
# 
# This file is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
# 
# This file is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public
# License for more details.
# 
# You should have received a copy of the GNU Affero General Public License
# along with this file.  If not, see <http://www.gnu.org/licenses/>.
# 
# }}}
23 24 25 26
#
include("defs.php3");

#
27
# No PAGEHEADER since we spit out a Location header later. See below.
28
#
29
$errors = array();
30 31

#
32
# Helper function to send back errors.
33
#
34
function EXPERROR()
35
{
36
    global $formfields, $errors;
37

38 39 40
    SPITFORM($formfields, $errors);
    PAGEFOOTER();
    die("");
41 42 43
}

#
44
# Only known and logged in users can begin experiments.
45
#
46 47 48
$this_user = CheckLoginOrDie();
$uid       = $this_user->uid();
$isadmin   = ISADMIN();
49

50 51
# Does not return if this a request.
include("showlogfile_sup.php3");
52

53 54 55 56 57 58 59 60 61
#
# Verify page arguments.
#
$optargs = OptionalPageArguments("view_style", PAGEARG_STRING,
				 "beginexp", PAGEARG_STRING,
				 "formfields", PAGEARG_ARRAY,
				 "nsref", PAGEARG_INTEGER,
				 "guid", PAGEARG_INTEGER,
				 "copyid", PAGEARG_INTEGER);
62 63

#
64
# Handle pre-defined view styles
65
#
66 67 68 69 70
unset($view);
if (isset($view_style) && $view_style == "plab") {
    $view['hide_proj'] = $view['hide_group'] = $view['hide_swap'] =
	$view['hide_preload'] = $view['hide_batch'] = $view['hide_linktest'] =
        $view['quiet'] = $view['plab_ns_message'] = $view['plab_descr'] = 1;
71
}
72
include("beginexp_form.php3");
73 74 75

# Need this below;
$idleswaptimeout = TBGetSiteVar("idle/threshold");
76
$autoswap_max    = TBGetSiteVar("general/autoswap_max");
77

78 79 80 81
#
# See what projects the uid can create experiments in. Must be at least one.
#
$projlist = $this_user->ProjectAccessList($TB_PROJECT_CREATEEXPT);
82

83 84 85 86
if (! count($projlist)) {
    USERERROR("You do not appear to be a member of any Projects in which ".
	      "you have permission to create new experiments.", 1);
}
87 88

#
89
# On first load, display virgin form and exit.
90
#
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
if (!isset($beginexp)) {
    # Allow initial formfields data.
    if (isset($formfields))
	$defaults = $formfields;
    else
	$defaults = array();
    INITFORM($defaults, $projlist);
    PAGEFOOTER();
    return;
}
elseif (! isset($formfields)) {
    PAGEHEADER("Begin a Testbed Experiment");
    PAGEARGERROR();
}

106 107 108 109 110 111 112 113 114
#
# For the benefit of the form. Remember to pass back actual filename, not
# the php temporary file name. Note that there appears to be some kind
# of breakage, at least in opera; filename has no path.
#
if (isset($_FILES['exp_nsfile'])) {
    $formfields['exp_nsfile'] = $_FILES['exp_nsfile']['name'];
}

115
# Some local variables.
116
$nsfilelocale    = "";
117 118 119
$thensfile       = 0;
$deletensfile    = 0;
$nonsfile        = 0;
120 121
$project         = null;
$group           = null;
122 123 124 125

#
# Project:
#
126
if (!isset($formfields["exp_pid"]) || $formfields["exp_pid"] == "") {
127 128
    $errors["Project"] = "Not Selected";
}
129
elseif (!TBvalid_pid($formfields["exp_pid"])) {
130 131
    $errors["Project"] = TBFieldErrorString();
}
132
elseif (! ($project = Project::Lookup($formfields["exp_pid"]))) {
133 134
    $errors["Project"] = "No such project";
}
135 136 137 138 139 140 141 142 143 144 145
else {
    #
    # Group: If none specified, then use default group (see below).
    # Project must be valid to do this.
    #
    if (isset($formfields["exp_gid"]) && $formfields["exp_gid"] != "") {
	if (!TBvalid_gid($formfields["exp_gid"])) {
	    $errors["Group"] = TBFieldErrorString();
	}
	elseif (! ($group = Group::LookupByPidGid($formfields["exp_pid"],
						  $formfields["exp_gid"]))) {
146 147
	    $errors["Group"] = "Group '" . $formfields["exp_gid"] .
		"' is not in project '" . $formfields["exp_pid"]. "'";
148
	}
149
    }
150 151
    else {
	$group = $project->DefaultGroup();
152 153
    }
}
154 155 156
if (count($errors)) {
    EXPERROR();
}
157 158 159 160

#
# EID:
#
161
if (!isset($formfields["exp_id"]) || $formfields["exp_id"] == "") {
162 163
    $errors["Experiment Name"] = "Missing Field";
}
164
elseif (!TBvalid_eid($formfields["exp_id"])) {
165 166
    $errors["Experiment Name"] = TBFieldErrorString();
}
167
elseif ($project && $project->LookupExperiment($formfields["exp_id"])) {
168 169 170 171 172 173
    $errors["Experiment Name"] = "Already in use";
}

#
# Description:
# 
174 175
if (!isset($formfields["exp_description"]) ||
    $formfields["exp_description"] == "") {
176 177
    $errors["Description"] = "Missing Field";
}
178
elseif (!TBvalid_description($formfields["exp_description"])) {
179 180 181 182 183 184 185
    $errors["Description"] = TBFieldErrorString();
}

#
# NS File. There is a bunch of stuff here for Netbuild, which uses the
# beginexp form as a backend. Switch to XML interface someday ...
#
186 187 188
if (isset($formfields["guid"])) {
    if ($formfields["guid"] == "" ||
	!preg_match("/^\d+$/", $formfields["guid"])) {
189
	$errors["NS File GUID"] = "Invalid characters";
190
    }
191
}
192
if (isset($formfields['copyid'])) {
193
    if ($formfields["copyid"] == "" ||
194 195 196 197 198
	!preg_match("/^[-\w,:]*$/", $formfields['copyid'])) {
	$errors["Copy ID"] = "Invalid characters";
    }
    $nsfilelocale = "copyid";
}
199 200 201
elseif (isset($formfields["nsref"])) {
    if ($formfields["nsref"] == "" ||
	!preg_match("/^\d+$/", $formfields["nsref"])) {
202 203
	$errors["NS File Reference"] = "Invalid characters";
    }
204 205
    $nsfilelocale = "nsref";
}
206 207 208
elseif (isset($formfields["exp_localnsfile"]) &&
	$formfields["exp_localnsfile"] != "") {
    if (!preg_match("/^([-\@\w\.\/]+)$/", $formfields["exp_localnsfile"])) {
209 210
	$errors["Server NS File"] = "Pathname includes illegal characters";
    }
211
    elseif (! VALIDUSERPATH($formfields["exp_localnsfile"])) {
212 213
	$errors["Server NS File"] =
		"Must reside in one of: $TBVALIDDIRS";
214 215 216
    }
    $nsfilelocale = "local";
}
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
elseif (isset($_FILES['exp_nsfile']) && $_FILES['exp_nsfile']['size'] != 0) {
    if ($_FILES['exp_nsfile']['size'] > (1024 * 500)) {
	$errors["Local NS File"] = "Too big!";
    }
    elseif ($_FILES['exp_nsfile']['name'] == "") {
	$errors["Local NS File"] =
	    "Local filename does not appear to be valid";
    }
    elseif ($_FILES['exp_nsfile']['tmp_name'] == "") {
	$errors["Local NS File"] =
	    "Temp filename does not appear to be valid";
    }
    elseif (!preg_match("/^([-\@\w\.\/]+)$/",
			$_FILES['exp_nsfile']['tmp_name'])) {
	$errors["Local NS File"] = "Temp path includes illegal characters";
    }
    $nsfilelocale = "upload";
}
235 236
elseif (isset($formfields["exp_nsfile_contents"]) &&
	$formfields["exp_nsfile_contents"] != "") {
237 238 239 240 241 242 243 244 245 246 247
    #
    # The NS file is encoded inline. We will write it to a tempfile below
    # once all other checks passed.
    #
    $nsfilelocale = "inline";
}
else {
    #
    # I am going to allow shell experiments to be created (No NS file),
    # but only by admin types.
    #
248
    if (! ISADMIN()) {
249 250 251 252 253 254 255 256
	$errors["NS File"] = "You must provide an NS file";
    }
}

#
# Swappable
# Any of these which are not "1" become "0".
#
257 258 259
if (!isset($formfields["exp_swappable"]) ||
    strcmp($formfields["exp_swappable"], "1")) {
    $formfields["exp_swappable"] = 0;
260

261 262
    if (!isset($formfields["exp_noswap_reason"]) ||
        !strcmp($formfields["exp_noswap_reason"], "")) {
263

264
        if (! ISADMIN()) {
265 266 267
	    $errors["Not Swappable"] = "No justification provided";
        }
	else {
268
	    $formfields["exp_noswap_reason"] = "ADMIN";
269 270
        }
    }
271
    elseif (!TBvalid_description($formfields["exp_noswap_reason"])) {
272 273 274 275
	$errors["Not Swappable"] = TBFieldErrorString();
    }
}
else {
276 277
    $formfields["exp_swappable"]     = 1;
    $formfields["exp_noswap_reason"] = "";
278 279
}

280 281 282
if (!isset($formfields["exp_idleswap"]) ||
    strcmp($formfields["exp_idleswap"], "1")) {
    $formfields["exp_idleswap"] = 0;
283

284 285
    if (!isset($formfields["exp_noidleswap_reason"]) ||
	!strcmp($formfields["exp_noidleswap_reason"], "")) {
286
	if (! ISADMIN()) {
287 288 289
	    $errors["Not Idle-Swappable"] = "No justification provided";
	}
	else {
290
	    $formfields["exp_noidleswap_reason"] = "ADMIN";
291 292
	}
    }
293
    elseif (!TBvalid_description($formfields["exp_noidleswap_reason"])) {
294 295 296 297
	$errors["Not Idle-Swappable"] = TBFieldErrorString();
    }
}
else {
298 299
    $formfields["exp_idleswap"]          = 1;
    $formfields["exp_noidleswap_reason"] = "";
300 301 302
}

# Proper idleswap timeout must be provided.
303 304 305 306
if (!isset($formfields["exp_idleswap_timeout"]) ||
    !preg_match("/^[\d]+$/", $formfields["exp_idleswap_timeout"]) ||
    ($formfields["exp_idleswap_timeout"] + 0) <= 0 ||
    ($formfields["exp_idleswap_timeout"] + 0) > $idleswaptimeout) {
307 308 309 310 311
    $errors["Idleswap"] = "Invalid time provided - ".
	"must be non-zero and less than $idleswaptimeout";
}


312 313
if (!isset($formfields["exp_autoswap"]) ||
    strcmp($formfields["exp_autoswap"], "1")) {
314 315 316 317
    if (!ISADMIN()) {
	$errors["Max. Duration"] =
	    "You must ask testbed operations to disable this";
    }
318
    $formfields["exp_autoswap"] = 0;
319 320
}
else {
321
    $formfields["exp_autoswap"] = 1;
322
    
323 324 325
    if (!isset($formfields["exp_autoswap_timeout"]) ||
	!preg_match("/^[\d]+$/", $formfields["exp_idleswap_timeout"]) ||
	($formfields["exp_autoswap_timeout"] + 0) <= 0) {
326 327
	$errors["Max. Duration"] = "No or invalid time provided";
    }
328
    # The user can override autoswap timeout, but limit unless an admin.
329 330
    if ($formfields["exp_autoswap_timeout"] > $autoswap_max && !ISADMIN()) {
	$errors["Max. Duration"] = "$autoswap_max hours maximum - ".
331 332
	    "you must ask testbed operations for more";
    }
333 334
}

335 336 337
#
# Linktest option
# 
338 339 340
if (isset($formfields["exp_linktest"]) && $formfields["exp_linktest"] != "") {
    if (!preg_match("/^[\d]+$/", $formfields["exp_linktest"]) ||
	$formfields["exp_linktest"] < 0 || $formfields["exp_linktest"] > 4) {
341 342 343 344
	$errors["Linktest Option"] = "Invalid level selection";
    }
}

345 346 347 348
#
# If any errors, stop now. pid/eid/gid must be okay before continuing.
#
if (count($errors)) {
349
    EXPERROR();
350 351
}

352 353 354 355 356 357
$exp_desc    = escapeshellarg($formfields["exp_description"]);
$exp_pid     = $formfields["exp_pid"];
$exp_gid     = ((isset($formfields["exp_gid"]) &&
		 $formfields["exp_gid"] != "") ?
		$formfields["exp_gid"] : $exp_pid);
$exp_id      = $formfields["exp_id"];
358
$extragroups = "";
359 360 361 362

#
# Verify permissions. We do this here since pid/eid/gid could be bogus above.
#
363
if (! $group->AccessCheck($this_user, $TB_PROJECT_CREATEEXPT)) {
364
    $errors["Project/Group"] = "Not enough permission to create experiment";
365
    EXPERROR();
366 367 368 369 370
}

#
# Figure out the NS file to give to the script. Eventually we will allow
# it to come inline as an XML argument.
371 372
#
if ($nsfilelocale == "copyid") {
373 374 375
    if (preg_match("/^([-\w]+),([-\w]+)$/", $formfields['copyid'], $matches)) {
	$copypid = $matches[1];
	$copyeid = $matches[2];
376 377 378 379 380 381
	$okay    = 0;

	#
	# Project level check if not a current experiment.
	#
	if (($experiment = Experiment::LookupByPidEid($copypid, $copyeid))) {
Leigh Stoller's avatar
Leigh Stoller committed
382
	    $okay = $experiment->AccessCheck($this_user, $TB_EXPT_READINFO);
383 384
	}
	elseif (($project = Project::Lookup($copypid))) {
Leigh Stoller's avatar
Leigh Stoller committed
385
	    $okay = $project->AccessCheck($this_user, $TB_PROJECT_READINFO);
386
	}
387

388
	if (! $okay) {
389 390
	    $errors["Project/Group"] =
		"Not enough permission to copy experiment $copypid/$copyeid";
391
	    EXPERROR();
392 393 394 395 396
	}
	if ($copypid != $exp_pid)
	    $extragroups = ",$copypid";
    }
    
397
    $thensfile = "-c " . escapeshellarg($formfields['copyid']);
398 399
}
elseif ($nsfilelocale == "local") {
400 401 402 403 404 405
    #
    # No way to tell from here if this file actually exists, since
    # the web server runs as user nobody. The startexp script checks
    # for the file so the user will get immediate feedback if the filename
    # is bogus.
    #
406
    $thensfile = $formfields["exp_localnsfile"];
407 408
}
elseif ($nsfilelocale == "nsref") {
409
    $nsref = $formfields["nsref"];
410
    
411 412
    if (isset($formfields["guid"])) {
	$guid      = $formfields["guid"];
413 414 415 416 417
	$thensfile = "/tmp/$guid-$nsref.nsfile";
    }
    else {
	$thensfile = "/tmp/$uid-$nsref.nsfile";
    }
418 419
    if (! file_exists($thensfile)) {
	$errors["NS File"] = "Temp file no longer exists on server";
420
	EXPERROR();
421
    }
422 423
    $deletensfile = 1;
}
424 425
elseif ($nsfilelocale == "upload") {
    $thensfile = $_FILES['exp_nsfile']['tmp_name'];
426 427 428 429 430 431
    chmod($thensfile, 0666);
}
else {
    $nonsfile = 1;
}

432 433 434
# Okay, we can spit back a header now that there is no worry of redirect.
PAGEHEADER("Begin a Testbed Experiment");

435 436 437 438 439
#
# Convert other arguments to script parameters.
#
$exp_swappable = "";

440
# Experiments are swappable by default; supply reason if noswap requested.
441 442 443
if ($formfields["exp_swappable"] == "0") {
    $exp_swappable .= " -S " .
	escapeshellarg($formfields["exp_noswap_reason"]);
444 445
}

446 447
if ($formfields["exp_autoswap"] == "1") {
    $exp_swappable .= " -a " . (60 * $formfields["exp_autoswap_timeout"]);
448 449
}

450
# Experiments are idle swapped by default; supply reason if noidleswap requested.
451 452
if ($formfields["exp_idleswap"] == "1") {
    $exp_swappable .= " -l " . (60 * $formfields["exp_idleswap_timeout"]);
453
}
454
else {
455 456
    $exp_swappable .= " -L " .
	escapeshellarg($formfields["exp_noidleswap_reason"]);
457
}
458 459 460 461

$exp_batched   = 0;
$exp_preload   = 0;
$batcharg      = "-i";
462
$linktestarg   = "";
463

464 465
if (isset($formfields["exp_batched"]) &&
    strcmp($formfields["exp_batched"], "Yep") == 0) {
466 467 468
    $exp_batched   = 1;
    $batcharg      = "";
}
469 470
if (isset($formfields["exp_preload"]) &&
    strcmp($formfields["exp_preload"], "Yep") == 0) {
471 472 473
    $exp_preload   = 1;
    $batcharg     .= " -f";
}
474 475
if (isset($formfields["exp_savedisk"]) &&
    strcmp($formfields["exp_savedisk"], "Yep") == 0) {
476 477
    $batcharg     .= " -s";
}
478 479
if (isset($formfields["exp_linktest"]) && $formfields["exp_linktest"] != "") {
    $linktestarg   = "-t " . $formfields["exp_linktest"];
480
}
481 482 483 484

#
# Grab the unix GID for running scripts.
#
485 486
$unix_pgid = $project->unix_gid();
$unix_ggid = $group->unix_gid();
487 488 489 490 491 492 493 494

#
# Run the backend script.
#
# Avoid SIGPROF in child.
#
set_time_limit(0);

495 496
STARTBUSY("Starting Experiment");

497
$retval = SUEXEC($uid, "$unix_pgid,$unix_ggid" . $extragroups ,
498
		 "webbatchexp $batcharg -E $exp_desc $exp_swappable ".
499
		 "$linktestarg -p $exp_pid -g $exp_gid -e $exp_id ".
500 501
		 ($nonsfile ? "" : "$thensfile"),
		 SUEXEC_ACTION_IGNORE);
502
HIDEBUSY();
503 504 505 506 507 508 509 510 511 512

if ($deletensfile) {
    unlink($thensfile);
}

#
# Fatal Error. Report to the user, even though there is not much he can
# do with the error. Also reports to tbops.
# 
if ($retval < 0) {
513
    SUEXECERROR(SUEXEC_ACTION_CONTINUE);
514 515 516 517
}

# User error. Tell user and exit.
if ($retval) {
518 519 520 521 522
    echo "<br>";
    echo "<blockquote><pre>$suexec_output<pre></blockquote>";

    PAGEFOOTER();
    exit();
523 524
}

525
# Display a useful message.
526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554
$message = "";
if ($nonsfile) {
    $message =
         "Since you did not provide an NS script, no nodes have been
          allocated. You will not be able to modify or swap this experiment,
          nor do most other neat things you can do with a real experiment.";
}
elseif ($exp_preload) {
    $message = 
         "Since you are only pre-loading the experiment, this will typically
          take less than one minute. If you do not receive email notification
          within a reasonable amount of time, please contact $TBMAILADDR.";
}
elseif ($exp_batched) {
    $message = 
         "Batch Mode experiments will be run when enough resources become
          available. This might happen immediately, or it may take hours
	  or days. You will be notified via email when the experiment has
          been run. If you do not receive email notification within a
          reasonable amount of time, please contact $TBMAILADDR.";
}
else {
    $message = 
         "You will be notified via email when the experiment has been fully
	  configured and you are able to proceed. This typically takes less
          than 10 minutes, depending on the number of nodes you have requested.
          If you do not receive email notification within a reasonable amount
          of time, please contact $TBMAILADDR.";
}
555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574

# Map to the actual experiment and show the log.
if (($experiment = Experiment::LookupByPidEid($formfields["exp_pid"],
					      $formfields["exp_id"]))) {
    echo $experiment->PageHeader();
    echo "<br>\n";
    echo "<b>Starting experiment configuration!</b> " . $message;
    echo "<br><br>\n";
    STARTLOG($experiment);
}
else {
    echo "<br>\n";
    echo "<b>Starting experiment configuration!</b> " . $message;
    echo "<br>\n";
}
						
#
# Standard Testbed Footer
#
PAGEFOOTER();
575
?>