Commit 87dd2e60 authored by Leigh Stoller's avatar Leigh Stoller

The panic button ...

* tbsetup/panic.in: New backend script to implement the panic button
  feature. When used, it will cut the severe the connection to the
  firewall node by using snmpit to disable the port. Sets the panic
  bit (and date) in the experiments table, and changes the state of
  the experiment from "active" to "paniced" to ensure that the
  experiment cannot be messed with (swapped out or modified). Sends
  email to tbops when the panic button is pressed.

  Used with -r option, reverses the above. State is set back to
  active, the panic bit is cleared, and the port is renabled with
  snmpit.

* tbsetup/tbswap.in: During swapout, a firewalled experiment that has
  been paniced will get a cleaning; The nodes are powered off, then
  the osids for all the nodes are reset (with os_select) so that they
  will boot the MFS, and then the nodes are powered on. Then the
  control network is turned back on, and then I wait for the nodes to
  reboot (this is simply cause we do not record in the DB that a node
  is turned off, and if I do not wait, the reload daemon will end
  hitting the power button again if they do not reboot in time. We can
  fix this later.

  I am not planning to apply this to general firewalled experiments
  yet as the power cycling is going to be hard on the nodes, so would
  rather that we at least have a 1/2 baked plan before we do that.

* www/showexp.php3: If experiment is firewalled, show the Panic
  Button, linked to the panic button web script. If the experiment has
  already had the panic button pressed, show a big warning message and
  explain that user must talk to tbops to swap the experiment out.
  Also fiddle with menu options so that the terminate link is gone,
  and the swap link is visible only in admin mode. In other words, only
  an admin person can swap an experiment once it is paniced. And of
  course, an admin person can the backend panic script above with the
  -r option, but thats not something to be done lightly.

* db/libdb.pm.in: Add "paniced" as an experiment state (EXPTSTATE_PANICED).
  Add utility functions: TBExptSetPanicBit(), TBExptGetPanicBit(), and
  TBExptClearPanicBit().

* tbsetup/swapexp.in: Minor state fiddling so that an experiment can
  be swapped while in paniced state, but only when in admin mode. Also
  clear the panic bit when experiment is swapped out.

* www/dbdefs.php3.in: Add "paniced" as an experiment state. Add a
  utility function TBExptFirewall() to see if experiment is firewalled.

* www/panicbutton.php3: New web script to invoke the backend panic
  script mentioned above, after the usual confirm song and dance.

* www/panicbutton.gif: New gif of a red panic button that I stole off
  the net. If anyone has sees/has a better one, feel free to replace
  this one.

* utils/node_statewait.in: Add -s option so that I can pass in the
  state I want to wait for (used from tbswap above to wait for nodes
  to reach ISUP after power on).
parent 9e7b2287
......@@ -1542,6 +1542,7 @@ outfiles="$outfiles Makeconf GNUmakefile \
tbsetup/plab/plabdiscover tbsetup/plab/etc/netbed_files/GNUmakefile \
tbsetup/ipassign/GNUmakefile tbsetup/ipassign/src/GNUmakefile \
tbsetup/ipassign/ipassign_wrapper tbsetup/assign_prepass \
tbsetup/panic tbsetup/webpanic \
tip/GNUmakefile \
tmcd/GNUmakefile tmcd/tmcd.restart \
tmcd/common/GNUmakefile tmcd/common/config/GNUmakefile \
......
......@@ -572,6 +572,7 @@ outfiles="$outfiles Makeconf GNUmakefile \
tbsetup/plab/plabdiscover tbsetup/plab/etc/netbed_files/GNUmakefile \
tbsetup/ipassign/GNUmakefile tbsetup/ipassign/src/GNUmakefile \
tbsetup/ipassign/ipassign_wrapper tbsetup/assign_prepass \
tbsetup/panic tbsetup/webpanic \
tip/GNUmakefile \
tmcd/GNUmakefile tmcd/tmcd.restart \
tmcd/common/GNUmakefile tmcd/common/config/GNUmakefile \
......
......@@ -67,7 +67,7 @@ use vars qw(@ISA @EXPORT);
DBLIMIT_NSFILESIZE NODERELOADPENDING_EID
EXPTSTATE_NEW EXPTSTATE_PRERUN EXPTSTATE_SWAPPED EXPTSTATE_SWAPPING
EXPTSTATE_ACTIVATING EXPTSTATE_ACTIVE
EXPTSTATE_ACTIVATING EXPTSTATE_ACTIVE EXPTSTATE_PANICED
EXPTSTATE_TERMINATING EXPTSTATE_TERMINATED EXPTSTATE_QUEUED
EXPTSTATE_MODIFY_PARSE EXPTSTATE_MODIFY_REPARSE EXPTSTATE_MODIFY_RESWAP
EXPTSTATE_RESTARTING
......@@ -200,6 +200,8 @@ use vars qw(@ISA @EXPORT);
TBExptMinMaxNodes TBExptSecurityLevel TBExptIDX
TBDB_SECLEVEL_GREEN TBDB_SECLEVEL_YELLOW
TBDB_SECLEVEL_ORANGE TBDB_SECLEVEL_RED
TBExptSetPanicBit TBExptGetPanicBit TBExptClearPanicBit
);
# Must come after package declaration!
......@@ -336,6 +338,7 @@ sub EXPTSTATE_QUEUED() { "queued"; }
sub EXPTSTATE_SWAPPING() { "swapping"; }
sub EXPTSTATE_ACTIVATING() { "activating"; }
sub EXPTSTATE_ACTIVE() { "active"; }
sub EXPTSTATE_PANICED() { "paniced"; }
sub EXPTSTATE_TERMINATING() { "terminating"; }
sub EXPTSTATE_TERMINATED() { "ended"; }
sub EXPTSTATE_MODIFY_PARSE() { "modify_parse"; }
......@@ -3776,6 +3779,46 @@ sub TBNodeFirewall ($$$) {
return 1;
}
#
# Set the paniced bit for an experiment.
#
sub TBExptSetPanicBit($$) {
my ($pid, $eid) = @_;
return DBQueryWarn("update experiments set ".
" paniced=1,panic_date=now() ".
"where pid='$pid' and eid='$eid'");
}
#
# Clear the panic bit.
#
sub TBExptClearPanicBit($$) {
my ($pid, $eid) = @_;
return DBQueryWarn("update experiments set ".
" paniced=0,panic_date=NULL ".
"where pid='$pid' and eid='$eid'");
}
#
# Get the value of the paniced bit.
#
sub TBExptGetPanicBit($$$) {
my ($pid, $eid, $panicp) = @_;
my $query_result =
DBQueryWarn("select paniced,panic_date from experiments ".
"where pid='$pid' and eid='$eid'");
if (!$query_result || $query_result->num_rows == 0) {
return 0;
}
my @row = $query_result->fetchrow_array();
$$panicp = $row[0];
return 1;
}
#
# Issue a DB query. Argument is a string. Returns the actual query object, so
# it is up to the caller to test it. I would not for one moment view this
......
......@@ -28,14 +28,14 @@ SBIN_STUFF = resetvlans console_setup.proxy sched_reload named_setup \
exports_setup.proxy vnode_setup eventsys_start \
sfskey_update sfskey_update.proxy rmuser idleswap \
newnode_reboot savelogs.proxy eventsys.proxy \
elabinelab snmpit.proxy
elabinelab snmpit.proxy panic
CTRLBIN_STUFF = console_setup.proxy exports_setup.proxy sfskey_update.proxy \
savelogs.proxy eventsys.proxy
LIBEXEC_STUFF = rmproj wanlinksolve wanlinkinfo \
os_setup mkexpdir console_setup webnscheck webreport \
webendexp webbatchexp \
webendexp webbatchexp webpanic \
assign_wrapper assign_prepass ptopgen webnodeupdate \
webdelay_config \
webrmgroup webswapexp webnodecontrol \
......
#!/usr/bin/perl -wT
#
# EMULAB-COPYRIGHT
# Copyright (c) 2000-2004 University of Utah and the Flux Group.
# All rights reserved.
#
use English;
use Getopt::Std;
use POSIX qw(isatty setsid);
#
# Press the panic button. Also invoked from web interface.
#
sub usage()
{
print(STDERR
"Usage: panicbutton [-r] <pid> <eid>\n".
"switches and arguments:\n".
"-r - Reset panic state (admin people only)\n".
"<pid> - The project the experiment belongs to\n".
"<eid> - The experiment name (id)\n");
exit(-1);
}
my $optlist = "r";
my $reset = 0;
sub Fatal($);
#
# Exit codes are important; they tell the web page what has happened so
# it can say something useful to the user. Fatal errors are mostly done
# with die(), but expected errors use this routine. At some point we will
# use the DB to communicate the actual error.
#
# $status < 0 - Fatal error. Something went wrong we did not expect.
# $status = 0 - Termination is proceeding in the background. Notified later.
# $status > 0 - Expected error. User not allowed for some reason.
#
sub ExitWithStatus($$)
{
my ($status, $message) = @_;
if ($status < 0) {
die("*** $0:\n".
" $message\n");
}
else {
print STDERR "$message\n";
}
exit($status);
}
#
# Configure variables
#
my $TB = "@prefix@";
my $TBOPS = "@TBOPSEMAIL@";
#
# Testbed Support libraries
#
use lib "@prefix@/lib";
use libdb;
use libtestbed;
# Be careful not to exit on transient error; 0 means infinite retry.
$libdb::DBQUERY_MAXTRIES = 0;
my $snmpit = "$TB/bin/snmpit";
my $dbuid;
my $user_name;
my $user_email;
#
# Untaint the path
#
$ENV{'PATH'} = '/bin:/usr/bin';
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
#
# Turn off line buffering on output
#
$| = 1;
#
# Parse command arguments. Once we return from getopts, all that should
# left are the required arguments.
#
%options = ();
if (! getopts($optlist, \%options)) {
usage();
}
if (@ARGV != 2) {
usage();
}
my $pid = $ARGV[0];
my $eid = $ARGV[1];
if (defined($options{"r"})) {
$reset = 1;
}
#
# Untaint the arguments.
#
if ($pid =~ /^([-\w\.]+)$/) {
$pid = $1;
}
else {
die("Tainted argument $pid!\n");
}
if ($eid =~ /^([-\w\.]+)$/) {
$eid = $1;
}
else {
die("Tainted argument $eid!\n");
}
#
# See if the experiment is firewalled. Error if not.
#
my $firewall;
my $firewalled = TBExptFirewall($pid, $eid, \$firewall);
if (!$firewalled) {
die("*** $0:\n".
" Experiment $pid/$eid is not firewalled!\n");
}
#
# Verify user and get his DB uid.
#
if (! UNIX2DBUID($UID, \$dbuid)) {
die("*** $0:\n".
" You do not exist in the Emulab Database.\n");
}
#
# Get email info for user.
#
if (! UserDBInfo($dbuid, \$user_name, \$user_email)) {
die("*** $0:\n".
" Cannot determine your name and email address.\n");
}
#
# Verify that this person is allowed to press the panic button.
# Note that any script down the line has to do an admin check also.
#
if ($UID && !TBAdmin($UID) &&
!TBExptAccessCheck($dbuid, $pid, $eid, TB_EXPT_MODIFY)) {
die("*** $0:\n".
" You do not have permission to end this experiment!\n");
}
#
# We have to protect against trying to end an experiment that is currently
# in the process of being terminated. We use a "wrapper" state (actually
# a timestamp so we can say when termination was requested) since
# terminating consists of a couple of different experiment states down inside
# the tb scripts.
#
DBQueryFatal("lock tables experiments write");
$query_result =
DBQueryFatal("SELECT * FROM experiments WHERE eid='$eid' and pid='$pid'");
if (! $query_result->numrows) {
die("*** $0:\n".
" No such experiment $pid/$eid exists!\n");
}
my %hashrow = $query_result->fetchhash();
my $expt_head_login = $hashrow{'expt_head_uid'};
my $estate = $hashrow{'state'};
#
# Called from user (via web interface).
#
if ($reset) {
ExitWithStatus(1, "Experiment $pid/$eid is not paniced!\n")
if ($estate ne EXPTSTATE_PANICED);
}
else {
ExitWithStatus(1, "Experiment $pid/$eid is not active!\n")
if (! ($estate eq EXPTSTATE_ACTIVE ||
$estate eq EXPTSTATE_ACTIVATING ||
$estate eq EXPTSTATE_SWAPPING));
}
#
# Change experiment state and lock it.
#
TBLockExp($pid, $eid, ($reset ? EXPTSTATE_ACTIVE : EXPTSTATE_PANICED));
DBQueryFatal("unlock tables");
#
# XXX - At this point a failure is going to leave things in an
# inconsistent state. Be sure to call fatal() only since we are
# going into the background, and we have to send email since no
# one is going to see printed error messages (output goes into the
# log file, which will be sent along in the email).
#
#
# Get email address of the experiment head, which may be different than
# the person who is actually terminating the experiment, since its polite
# to let the original creator know whats going on.
#
my $expt_head_name;
my $expt_head_email;
if (! UserDBInfo($expt_head_login, \$expt_head_name, \$expt_head_email)) {
print "*** WARNING: ".
"Could not determine name/email for $expt_head_login.\n";
$expt_head_name = "TBOPS";
$expt_head_email = $TBOPS;
}
$query_result =
DBQueryFatal("select card1 from wires ".
"where node_id1='$firewall' AND type='Control'");
if (!$query_result->numrows) {
fatal("Could not determine firewall port for $pid/$eid!");
}
my ($port) = $query_result->fetchrow_array();
#
# Call snmpit.
#
if ($reset) {
system("$snmpit -e ${firewall}:${port}");
if ($?) {
fatal("snmpit exited with $?!");
}
TBExptClearPanicBit($pid, $eid);
print "Panic situation has been cleared!\n";
}
else {
system("$snmpit -d ${firewall}:${port}");
if ($?) {
fatal("snmpit exited with $?!");
}
TBExptSetPanicBit($pid, $eid);
print "Panic Button has been pressed!\n";
}
TBUnLockExp($pid, $eid);
#
# Send email notification to user *and* to tbops.
#
SENDMAIL("$user_name <$user_email>",
"Panic Button ". ($reset ? "Cleared" : "Pressed") .
" for Experiment $pid/$eid",
"$dbuid has " . ($reset ? "cleared" : "pressed") .
" the panic button for experiment $pid/$eid",
"$user_name <$user_email>",
"Cc: $expt_head_name <$expt_head_email>\n".
"Bcc: $TBOPS");
exit 0;
sub fatal($)
{
my($mesg) = $_[0];
#
# Send a message to the testbed list. Append the logfile.
#
SENDMAIL("$user_name <$user_email>",
"Panic Button Failure for Experiment $pid/$eid",
"$dbuid ". ($reset ? "cleared" : "pressed") .
" the panic button for experiment $pid/$eid,\n".
"BUT there was a failure!\n\n".
"$mesg\n",
"$user_name <$user_email>",
"Cc: $expt_head_name <$expt_head_email>\n".
"Bcc: $TBOPS");
die("*** $0:\n".
" $mesg\n");
}
......@@ -107,6 +107,7 @@ my @row;
my $action;
my $nextswapstate;
my $termswapstate;
my $isadmin = 0;
#
# Untaint the path
......@@ -270,12 +271,13 @@ if (! UserDBInfo($dbuid, \$user_name, \$user_email)) {
die("*** $0:\n".
" Cannot determine your name and email address.\n");
}
$isadmin = TBAdmin($UID);
#
# Verify that this person can muck with the experiment.
# Note that any script down the line has to do an admin check also.
#
if ($UID && !TBAdmin($UID) &&
if ($UID && !$isadmin &&
!TBExptAccessCheck($dbuid, $pid, $eid, TB_EXPT_DESTROY)) {
die("*** $0:\n".
" You do not have permission to swap or modify this experiment!\n");
......@@ -470,6 +472,17 @@ else {
"or modify the experiment.\n")
if ($canceled);
#
# Cannot swapmod an active elabinelab experiment, yet.
#
ExitWithStatus(1,
"Experiment $pid/$eid is an active ElabInElab.\n".
"You cannot modify this type of experiment while it\n".
"is swapped in. We hope to support this soon.\n")
if ($inout eq "modify" &&
($elabinelab || defined($elabinelab_eid)) &&
$estate ne EXPTSTATE_SWAPPED());
#
# Check the state for the various operations.
#
......@@ -484,12 +497,25 @@ else {
};
/^out$/i && do {
if ($estate ne EXPTSTATE_ACTIVE() &&
$estate ne EXPTSTATE_PANICED() &&
$estate ne EXPTSTATE_ACTIVATING()) {
ExitWithStatus(1,
"Experiment $pid/$eid is not swapped in ".
"or activating!\n");
}
#
# Must be an admin person to swap out an experiment that
# has had its panic button pressed.
#
if ($estate eq EXPTSTATE_PANICED() && !$isadmin) {
ExitWithStatus(1,
"Experiment $pid/$eid had its panic ".
"button pressed!\n".
"Only a testbed administrator can swap ".
"this experiment out.");
}
if ($estate eq EXPTSTATE_ACTIVATING()) {
#
# All we can do is set the cancel flag and hope that
......@@ -743,6 +769,7 @@ if ($inout eq "out") {
}
SetExpState($pid, $eid, EXPTSTATE_SWAPPED)
or fatal("Failed to set experiment state to " . EXPTSTATE_SWAPPED());
TBExptClearPanicBit($pid, $eid);
}
elsif ($inout eq "in") {
my $optarg = "";
......
......@@ -416,15 +416,98 @@ sub doSwapout($) {
}
if ($firewalled) {
# XXX put all nodes into admin mode
print STDERR "Confining firewalled nodes.\n";
TBDebugTimeStamp("moving nodes to purgatory");
#
# If the panic button was pressed, put all nodes into admin
# mode and power them off. After the firewall is torn down,
# we can power them back up.
#
my @nodes = ExpNodes($pid, $eid, 1);
my $ADMINOSID = TB_OSID_FREEBSD_MFS;
my $paniced;
TBExptGetPanicBit($pid, $eid, \$paniced);
if ($paniced) {
print STDERR "Powering down nodes.\n";
TBDebugTimeStamp("Powering down nodes");
system("power off @nodes");
if ($?) {
#
# If an error, cannot continue. Must leave firewall in
# place.
#
print STDERR "*** Failed to power nodes off! Stopping.\n";
return 1;
}
#
# XXX: node_admin should take a list of nodes, or pid,eid.
# Instead, call os_select directly for now.
#
# Clear any one-shot boots and partition boots.
#
print STDERR "Changing OSID to admin MFS.\n";
TBDebugTimeStamp("Changing OSID to admin MFS");
# Order matters cause os_select sillyness.
if (system("os_select -c -1 @nodes") ||
system("os_select -t $ADMINOSID @nodes") ||
system("os_select -c @nodes")) {
#
# If an error, cannot continue. Must leave firewall in
# place.
#
print STDERR "*** Failed to reset OSIDs! Stopping.\n";
return 1;
}
#
# Now we can power up the nodes again.
#
print STDERR "Powering up nodes.\n";
TBDebugTimeStamp("Powering up nodes");
system("power on @nodes");
if ($?) {
#
# If an error, cannot continue. Must leave firewall in
# place. Eventually, we can continue past this, if we
# know what nodes failed. But for now we have to do the
# ISUP test to make sure nodes really got into the MFS.
#
print STDERR "*** Failed to power on nodes! Stopping.\n";
return 1;
}
}
#
# Once all nodes are safely in the admin MFS, we can take
# down the firewall
#
doFW($pid, $eid, FWTEARDOWN);
if ($paniced) {
#
# Now wait for ISUP. I do this here cause the reload daemon
# will power cycle the nodes again if its reboot fails, and
# that will happen if the nodes are not back into the MFS in
# time. THe right thing to do is to store the power state in
# the DB, and have the reload daemon turn the nodes back on.
#
print STDERR "Waiting for nodes to boot the MFS.\n";
TBDebugTimeStamp("Waiting for nodes to boot the MFS");
system("node_statewait -s " . TBDB_NODESTATE_ISUP . " @nodes");
if ($?) {
#
# Okay to continue; reload daemon will probably send
# email later when the reload fails.
#
print STDERR "*** Some nodes failed to reboot. Continuing\n";
}
}
}
#
......@@ -1050,6 +1133,7 @@ sub doFW($$$) {
#
my $fwsetupstr1 = "snmpit $cnetstack -m $fwvlanname $portlist";
my $fwsetupstr2 = "snmpit $cnetstack -T $fwport $cnetvlanname $fwvlanname";
my $fwtakedownstr0 = "snmpit $cnetstack -e $fwport";
my $fwtakedownstr1 = "snmpit $cnetstack -m $cnetvlanname $portlist";
my $fwtakedownstr2 = "snmpit $cnetstack -o $fwvlanname";
my $fwtakedownstr3 = "snmpit $cnetstack -U $fwport";
......@@ -1102,8 +1186,14 @@ sub doFW($$$) {
# Record VLAN info now that everything is done
TBSetExptFirewallVlan($pid, $eid, $fwvid, $fwvlan);
} else {
TBDebugTimeStamp("snmpit firewall teardown: VLAN");
TBDebugTimeStamp("snmpit re-enable fw control port: $fwport");
my $failed = 0;
if (system($fwtakedownstr0)) {
print STDERR
"*** Could not re-enable firewall control port $fwport!\n";
$failed = 1;
}
TBDebugTimeStamp("snmpit firewall teardown: VLAN");
if (system($fwtakedownstr1)) {
print STDERR
"*** Could not return $portlist to Control VLAN!\n";
......
#!/usr/bin/perl -w
#
# EMULAB-COPYRIGHT
# Copyright (c) 2004 University of Utah and the Flux Group.
# All rights reserved.
#
use English;
#
# This gets invoked from the Web interface. Simply a wrapper ...
#
#
# Configure variables
#
my $TB = "@prefix@";
#
# Run the real thing, and never return.
#
exec "$TB/sbin/panic", @ARGV;
die("webpanic: Could not exec panic: $!");
......@@ -14,11 +14,13 @@ use Getopt::Std;
#
sub usage()
{
print STDOUT "Usage: node_statewait [-t timeout] [-a] | [node ...]\n";
print STDOUT
"Usage: node_statewait [-s state] [-t timeout] [-a] | [node ...]\n";
exit(-1);
}
my $optlist = "at:";
my $optlist = "at:s:";
my $timeout = 60 * 6;
my $state = TBDB_NODESTATE_PXEWAIT;
#
# Configure variables
......@@ -50,6 +52,9 @@ if (! getopts($optlist, \%options)) {
if (defined($options{"t"})) {
$timeout = $options{"t"};
}
if (defined($options{"s"})) {
$state = $options{"s"};
}
#
# All testnodes, or just some nodes.
......@@ -90,7 +95,7 @@ foreach my $node (sort(@nodes)) {
#
# Skip if something failed earlier.
#
if (!TBNodeStateWait($node, TBDB_NODESTATE_PXEWAIT, $waitstart, $timeout)) {
if (!TBNodeStateWait($node, $state, $waitstart, $timeout)) {
print STDOUT "nodewait ($node): Success\n";
next;
}
......
......@@ -130,6 +130,7 @@ $TB_EXPTSTATE_SWAPPING = "swapping";
$TB_EXPTSTATE_SWAPPED = "swapped";
$TB_EXPTSTATE_ACTIVATING = "activating";
$TB_EXPTSTATE_ACTIVE = "active";
$TB_EXPTSTATE_PANICED = "paniced";
$TB_EXPTSTATE_QUEUED = "queued";
# Interfaces roles.
......@@ -1851,6 +1852,25 @@ function TBPlabAvail() {
return $types;
}
#
# Is an experiment firewalled.
#
function TBExptFirewall($pid, $eid) {
#
# Short form: is there a firewall?
# Only check the firewalls table so that we can be called for a swapped
# experiment (swapped experiments don't have reserved table info).
#
$query_result =
DBQueryWarn("SELECT eid FROM firewalls ".
"WHERE pid='$pid' and eid='$eid' ".
"AND type LIKE '%-vlan'");
if (!$query_result || !mysql_num_rows($query_result))
return 0;
return 1;
}
#
# DB Interface.
#
......
<?php
#
# EMULAB-COPYRIGHT
# Copyright (c) 2000-2004 University of Utah and the Flux Group.
# All rights reserved.
#
include("defs.php3");
include("showstuff.php3");
#
# Only known and logged in users.
#
$uid = GETLOGIN();
LOGGEDINORDIE($uid);
#
# Must provide the EID!
#
if (!isset($pid) ||
strcmp($pid, "") == 0) {
USERERROR("The project ID was not provided!", 1);
}
if (!isset($eid) ||
strcmp($eid, "") == 0) {
USERERROR("The experiment ID was not provided!", 1);
}
$exp_eid = $eid;
$exp_pid = $pid;
# Canceled operation redirects back to showexp page. See below.
if ($canceled) {
header("Location: showexp.php3?pid=$pid&eid=$eid");
return;
}
#
# Standard Testbed Header, after checking for cancel above.
#
PAGEHEADER("Press the Panic Button!");
#
# Check to make sure thats this is a valid PID/EID, while getting the
# experiment GID.
#
if (! TBExptGroup($exp_pid, $exp_eid, $exp_gid)) {
USERERROR("The experiment $exp_eid is not a valid experiment ".
"in project $exp_pid.", 1);
}
#
# Verify permissions.
#
if (! TBExptAccessCheck($uid, $exp_pid, $exp_eid, $TB_EXPT_MODIFY)) {
USERERROR("You do not have permission to press the panic button for ".
"experiment $exp_eid!", 1);
}
echo "<font size=+2>Experiment <b>".
"<a href='showproject.php3?pid=$exp_pid'>$exp_pid</a>/".
"<a href='showexp.php3?pid=$exp_pid&eid=$exp_eid'>$exp_eid</a>".
"</b></font>\n";
#
# We run this twice. The first time we are checking for a confirmation
# by putting up a form. The next time through the confirmation will be
# set. Or, the user can hit the cancel button, in which case redirect the
# browser back up a level.
#