Commit e9607a77 authored by Leigh Stoller's avatar Leigh Stoller

More work on "recording" template events.

* New version of template_record just for ops, since so much is
  different about ops, not bothering to maintain a single version.

* Various fixes to how the recorded events are stored and reconstituted.
  The big fix is to wrap them in a sequence to that they get fired
  properly (waiting for completion of previous event in recording).

* New buttons to Pause and Continue event time, which is used when
  adding recorded events. This allows users to pause time while they
  "think" so when an event is recorded, the thinking time is not actually
  in the timeline. Eventually hope to figure this out automatically, but
  that will take some real, uh, thinking.

* Add a new event editor (linked off the template page) that allows
  you to delete and change the recordings. Note that you can only edit
  the events at the template level; you cannot edit the events of an
  instance (swapped in experiment), and you can only edit the recorded
  events, not any other events. Not sure its useful to be able to do
  either of these yet, but probably not too hard to add at some point.
parent e868d5cc
......@@ -26,12 +26,15 @@ include $(TESTBED_SRCDIR)/GNUmakerules
install: $(INSTALL_SBINDIR)/split-image.sh
@$(MAKE) -C imagezip install
@$(MAKE) -C frisbee.redux install
$(INSTALL_PROGRAM) $(SRCDIR)/template_record \
$(INSTALL_DIR)/opsdir/bin/template_record
-mkdir -p $(INSTALL_DIR)/opsdir/man/man1
$(INSTALL) -m 644 $(SRCDIR)/install-tarfile.1 \
$(INSTALL_DIR)/opsdir/man/man1/install-tarfile.1
control-install:
@$(MAKE) -C imagezip install
$(INSTALL_PROGRAM) $(SRCDIR)/template_record $(LBINDIR)/template_record
client:
ifeq ($(SYSTEM),FreeBSD)
......@@ -47,6 +50,7 @@ client-install: client
-mkdir -p $(DESTDIR)$(CLIENT_MANDIR)/man1
$(INSTALL) -m 644 $(SRCDIR)/install-tarfile.1 $(DESTDIR)$(CLIENT_MANDIR)/man1/install-tarfile.1
$(INSTALL_PROGRAM) $(SRCDIR)/install-rpm $(LBINDIR)/install-rpm
$(INSTALL_PROGRAM) $(SRCDIR)/template_record $(LBINDIR)/template_record
ifeq ($(SYSTEM),FreeBSD)
$(INSTALL_PROGRAM) $(SRCDIR)/create-image $(LBINDIR)/
$(INSTALL_PROGRAM) $(SRCDIR)/create-swapimage $(LBINDIR)/
......
This diff is collapsed.
......@@ -31,7 +31,7 @@ sub usage()
"Usage: template_exprun [-q] [-w] [-r <runid>] ".
"-a <action> -e <eid> [-p <pid> | <guid/vers>]\n".
"switches and arguments:\n".
"-a <action> - start or stop\n".
"-a <action> - pause, continue, start or stop\n".
"-w - wait for run to start\n".
"-s - save DB contents at end of run; default is clean\n".
"-q - be less chatty\n".
......@@ -232,7 +232,16 @@ if ($experiment->state() ne EXPTSTATE_ACTIVE()) {
exit(1);
}
if ($action eq "start" && !defined($runid)) {
#
# Pause and Continue are easy
#
if ($action eq "pause") {
exit($instance->PauseTime());
}
elsif ($action eq "continue") {
exit($instance->ContinueTime());
}
elsif ($action eq "start" && !defined($runid)) {
if ($instance->NewRunID(\$runid) < 0) {
tbdie("Could not determine a new runid; please use the -r option!");
}
......@@ -462,7 +471,13 @@ if ($doswapmod) {
fatal($? >> 8, "Swap modify failed!")
if ($?);
$instance->Refresh();
$instance->Refresh();
# This has to be redone since the batchexp will have written
# incomplete data.
print "Writing program agent info ...\n";
$instance->WriteProgramAgents() == 0
or fatal(-1, "Could not write program agent info");
}
#
......@@ -601,7 +616,8 @@ sub ParseArgs()
if (defined($options{"a"})) {
$action = $options{"a"};
if ($action ne "start" and $action ne "stop") {
if ($action ne "start" && $action ne "stop" &&
$action ne "pause" && $action ne "continue") {
tbdie("Improper -a argument: $action.");
}
# Need the equiv of a taint check.
......
......@@ -554,6 +554,11 @@ print "Writing environment strings ...\n";
$instance->WriteEnvVariables() == 0
or fatal(-1, "Could not write environment strings for program agents");
# This has to be redone since the batchexp will have written incomplete data.
print "Writing program agent info ...\n";
$instance->WriteProgramAgents() == 0
or fatal(-1, "Could not write program agent info");
#
# Now do the swapin (or it gets queued if a batch experiment).
#
......
......@@ -554,6 +554,11 @@ print "Writing environment strings ...\n";
$instance->WriteEnvVariables() == 0
or fatal(-1, "Could not write environment strings for program agents");
# This has to be redone since the batchexp will have written incomplete data.
print "Writing program agent info ...\n";
$instance->WriteProgramAgents() == 0
or fatal(-1, "Could not write program agent info");
#
# Now do the swapin (or it gets queued if a batch experiment).
#
......
......@@ -15,7 +15,8 @@ include $(OBJDIR)/Makeconf
SUBDIRS = nsgen
BIN_SCRIPTS = delay_config sshtb create_image node_admin link_config \
setdest loghole webcopy linkmon_ctl snmp-if-deref.sh
setdest loghole webcopy linkmon_ctl snmp-if-deref.sh \
template_record
SBIN_SCRIPTS = vlandiff vlansync withadminprivs export_tables cvsupd.pl \
eventping grantnodetype import_commitlog dhcpd_wrapper \
opsreboot deletenode node_statewait grabwebcams \
......@@ -40,6 +41,8 @@ install: $(addprefix $(INSTALL_BINDIR)/, $(BIN_SCRIPTS)) \
$(addprefix $(INSTALL_LIBEXECDIR)/, $(LIBEXEC_SCRIPTS)) \
$(addprefix $(INSTALL_DIR)/opsdir/sbin/, $(CTRLSBIN_SCRIPTS)) \
subdir-install
$(INSTALL_PROGRAM) template_record \
$(INSTALL_DIR)/opsdir/bin/template_record
$(INSTALL_PROGRAM) loghole $(INSTALL_DIR)/opsdir/bin/loghole
-mkdir -p $(INSTALL_DIR)/opsdir/man/man1
$(INSTALL) -m 0644 $(SRCDIR)/loghole.1 \
......
#!/usr/bin/perl -w
#
# EMULAB-COPYRIGHT
# Copyright (c) 2006 University of Utah and the Flux Group.
# All rights reserved.
#
use English;
use Getopt::Std;
#
# Do some analysis.
#
sub usage()
{
print STDOUT "Usage: template_record -e <pid/eid> <script> [args ...]\n";
exit(-1);
}
my $optlist = "-e:";
# Configure variables.
my $TB = "@prefix@";
# Locals.
my $pid;
my $eid;
#
# Turn off line buffering on output
#
$| = 1;
#
# Parse command arguments. Once we return from getopts, all that should be
# left are the required arguments.
#
%options = ();
if (! getopts($optlist, \%options)) {
usage();
}
if (defined($options{"e"})) {
($pid,$eid) = split(/\//, $options{"e"});
}
else {
#
# See if we can infer the pid/eid from the path.
#
if (`pwd` =~ /^(?:\/[-\w]+)*\/proj\/([-\w]+)\/exp\/([-\w]+)/) {
$pid = $1;
$eid = $2;
print "Using $pid/$eid\n";
}
else {
usage();
}
}
if (@ARGV < 1) {
usage();
}
my $scriptname = shift(@ARGV);
#
# Grab the user environment variables.
#
open(USRENV, "/proj/$pid/exp/$eid/tbdata/environment")
or die("Could not open user environment file!\n");
while (<USRENV>) {
if ($_ =~ /^(.*)=(.*)$/) {
$ENV{$1} = $2;
}
}
#
# Run the analysis.
# If it completes okay then schedule an event for the current time.
#
my $now = time();
my $childpid = fork();
if (! $childpid) {
#
# Child runs command
#
exec $scriptname, @ARGV;
die("Could not exec $scriptname\n");
}
#
# Wait until child exits or until user gets bored and types ^C.
#
waitpid($childpid, 0);
if ($?) {
exit(-1);
}
#
# Okay, ssh over to ops to run the xmlrpc client. SHould be installed locally.
#
system("$TB/bin/sslxmlrpc_client.py -m template addprogevent ".
" proj=$pid exp=$eid vnode='ops' when=$now ".
" cmd='$scriptname @ARGV'");
exit 0;
......@@ -377,6 +377,17 @@ if ($expstate) {
"template_exprun.php?action=start&guid=$guid".
"&version=$vers&eid=$exp_eid");
if ($instance->pause_time()) {
WRITESUBMENUBUTTON("Continue Experiment RunTime",
"template_exprun.php?action=continue&guid=$guid".
"&version=$vers&eid=$exp_eid");
}
else {
WRITESUBMENUBUTTON("Pause Experiment Runtime",
"template_exprun.php?action=pause&guid=$guid".
"&version=$vers&eid=$exp_eid");
}
WRITESUBMENUBUTTON("Create New Template",
"template_commit.php?&guid=$guid".
"&version=$vers&exptidx=$expindex");
......
......@@ -864,6 +864,69 @@ class Template
$foo = $row[0] + 1;
return "${tid}-V${foo}";
}
#
# Return array of template events, ordered by time.
#
function EventList(&$eventlist) {
$eventlist = array();
$guid = $this->guid();
$vers = $this->vers();
$query_result =
DBQueryFatal("select * from experiment_template_events ".
"where parent_guid='$guid' and ".
" parent_vers='$vers' ".
"order by time");
$i = 0;
while ($row = mysql_fetch_array($query_result)) {
$eventlist[$i++] = $row;
}
return 0;
}
function EventCount() {
$guid = $this->guid();
$vers = $this->vers();
$query_result =
DBQueryFatal("select count(*) from experiment_template_events ".
"where parent_guid='$guid' and ".
" parent_vers='$vers' ");
$row = mysql_fetch_array($query_result);
$count = $row[0];
return $count;
}
function DeleteEvent($vname) {
$guid = $this->guid();
$vers = $this->vers();
DBQueryFatal("delete from experiment_template_events ".
"where parent_guid='$guid' and ".
" parent_vers='$vers' and ".
" vname='$vname'");
return 0;
}
function ModifyEvent($vname, $changes) {
$guid = $this->guid();
$vers = $this->vers();
$sets = array();
while (list ($key, $value) = each ($changes)) {
$value = addslashes($value);
$sets[] = "$key='$value'";
}
DBQueryFatal("update experiment_template_events set ".
implode(",", $sets) . " ".
"where parent_guid='$guid' and ".
" parent_vers='$vers' and ".
" vname='$vname'");
return 0;
}
}
#
......@@ -944,6 +1007,10 @@ class TemplateInstance
return (is_null($this->instance) ? -1 :
$this->instance['stop_time']);
}
function pause_time() {
return (is_null($this->instance) ? -1 :
$this->instance['pause_time']);
}
function template() {
return (is_null($this->instance) ? -1 : $this->template);
}
......
<?php
#
# EMULAB-COPYRIGHT
# Copyright (c) 2006 University of Utah and the Flux Group.
# All rights reserved.
#
include("defs.php3");
include_once("template_defs.php");
#
# Only known and logged in users.
#
$uid = GETLOGIN();
LOGGEDINORDIE($uid);
$isadmin = ISADMIN($uid);
#
# Spit the form out using the array of data.
#
function SPITFORM($template, $formfields, $errors)
{
PAGEHEADER("Edit Template Events");
if ($template->EventList($eventlist) != 0) {
TBERROR("Could not get eventlist for template!", 1);
}
$guid = $template->guid();
$version = $template->vers();
echo "<font size=+2>Template <b>";
echo MakeLink("template", "guid=$guid&version=$version", "$guid/$version");
echo "</b>";
echo "</b></font>\n";
echo "<br>\n";
echo "<center>\n";
$template->Show();
echo "</center>\n";
echo "<br>\n";
if (!count($eventlist)) {
echo "<center>";
echo "<font size=+1 color=red>This template has no events!</font>\n";
echo "</center>\n";
return;
}
$guid = $template->guid();
$version = $template->vers();
if ($errors) {
echo "<table class=nogrid
align=center border=0 cellpadding=6 cellspacing=0>
<tr>
<th align=center colspan=2>
<font size=+1 color=red>
&nbsp;Oops, please fix the following errors!&nbsp;
</font>
</td>
</tr>\n";
while (list ($name, $message) = each ($errors)) {
echo "<tr>
<td align=right>
<font color=red>$name:&nbsp;</font></td>
<td align=left>
<font color=red>$message</font></td>
</tr>\n";
}
echo "</table><br>\n";
}
echo "<form action=template_editevents.php?guid=$guid&version=$version ".
" method=post>\n";
echo "<table align=center border=1>\n";
echo "<tr>";
echo "<th align=center>Delete</th>\n";
echo "<th align=center>Time</th>\n";
# echo "<th align=center>Name</th>\n";
echo "<th align=center>Node</th>\n";
echo "<th align=center>Args</th>\n";
echo "</tr><tr></tr>\n";
#
# Show a table of events with a delete button named by the event.
#
while (list ($index, $dbrow) = each ($eventlist)) {
$vname = $dbrow["vname"];
$vnode = $dbrow["vnode"];
$delete_name = "delete_${vname}";
$time_name = "time_${vname}";
$args_name = "args_${vname}";
echo "<tr>";
echo "<td align=center><input type=checkbox value=checked
name=\"formfields[$delete_name]\"
" . $formfields[$delete_name] . ">
</td>\n";
echo "<td class='pad4'>
<input type=text
name=\"formfields[$time_name]\"
value=\"" . $formfields[$time_name] . "\"
size=10
maxlength=10>
</td>\n";
# echo "<td class='pad4'>$vname</td>\n";
echo "<td class='pad4'>$vnode</td>\n";
echo "<td class='pad4'>
<input type=text
name=\"formfields[$args_name]\"
value=\"" . $formfields[$args_name] . "\"
size=64
maxlength=1024>
</td>\n";
echo "</tr>\n";
}
echo "<tr>
<td class='pad4' align=center colspan=4>
<b><input type=submit name=save value='Save Changes'></b>
</td>
</tr>
</form>
</table>\n";
}
#
# Verify page arguments.
#
if (!isset($guid) ||
strcmp($guid, "") == 0) {
USERERROR("You must provide a template GUID.", 1);
}
if (!isset($version) ||
strcmp($version, "") == 0) {
USERERROR("You must provide a template version number", 1);
}
if (!TBvalid_guid($guid)) {
PAGEARGERROR("Invalid characters in GUID!");
}
if (!TBvalid_integer($version)) {
PAGEARGERROR("Invalid characters in version!");
}
#
# Check to make sure this is a valid template and enough permission.
#
$template = Template::Lookup($guid, $version);
if (!$template) {
USERERROR("The experiment template $guid/$version is not a valid ".
"experiment template!", 1);
}
if (! $template->AccessCheck($uid, $TB_EXPT_UPDATE)) {
USERERROR("You do not have permission to instantiate experiment template ".
"$guid/$version!", 1);
}
# Event list master, used below.
if ($template->EventList($eventlist) != 0) {
TBERROR("Could not get eventlist for template!", 1);
}
#
# On first load, display virgin form and exit.
#
if (!isset($save)) {
$defaults = array();
while (list ($index, $dbrow) = each ($eventlist)) {
$vname = $dbrow["vname"];
$time = $dbrow["time"];
$arguments = $dbrow["arguments"];
$delete_name = "delete_${vname}";
$time_name = "time_${vname}";
$args_name = "args_${vname}";
$defaults[$time_name] = $time;
$defaults[$args_name] = $arguments;
}
SPITFORM($template, $defaults, 0);
PAGEFOOTER();
return;
}
elseif (! isset($formfields)) {
PAGEARGERROR();
}
#
# Okay, validate form arguments.
#
$errors = array();
$deletes = array();
$changes = array();
#
# Use the master event list from the template to validate.
#
while (list ($index, $dbrow) = each ($eventlist)) {
$vname = $dbrow["vname"];
$time = $dbrow["time"];
$args = $dbrow["args"];
$delete_name = "delete_${vname}";
$time_name = "time_${vname}";
$args_name = "args_${vname}";
# Delete button
if (isset($formfields[$delete_name]) &&
$formfields[$delete_name] == "checked") {
$deletes[$vname] = $vname;
continue;
}
# Modify the time.
if (isset($formfields[$time_name]) && $formfields[$time_name] != "$time") {
$newtime = $formfields[$time_name];
if (!TBvalid_float($newtime)) {
$errors["$vname time"] = TBFieldErrorString();
}
elseif ($newtime < 0) {
$errors["$vname time"] = "Cannot be less then zero";
}
else {
if (!isset($changes[$vname])) {
$changes[$vname] = array();
}
$changes[$vname]["time"] = $newtime;
}
}
# Modify the arguments
if (isset($formfields[$args_name]) && $formfields[$args_name] != "$args") {
$newargs = $formfields[$args_name];
if (! TBcheck_dbslot($newargs, 'eventlist','arguments',
TBDB_CHECKDBSLOT_WARN|TBDB_CHECKDBSLOT_ERROR)) {
$errors["$vname time"] = TBFieldErrorString();
}
else {
if (!isset($changes[$vname])) {
$changes[$vname] = array();
}
$changes[$vname]["arguments"] = $newargs;
}
}
}
if (count($errors)) {
SPITFORM($template, $formfields, $errors);
PAGEFOOTER();
exit(1);
}
#
# Do the deletes and changes.
#
reset($eventlist);
while (list ($index, $dbrow) = each ($eventlist)) {
$vname = $dbrow["vname"];
if (isset($deletes[$vname])) {
$template->DeleteEvent($vname);
continue;
}
if (isset($changes[$vname])) {
$template->ModifyEvent($vname, $changes[$vname]);
continue;
}
}
# Zap back to this page.
header("Location: template_editevents.php?guid=$guid&version=$version");
......@@ -102,6 +102,76 @@ function DOIT($instance, $action, $command_options)
STARTLOG($pid, $eid);
}
#
# Run the script backend
#
function DOTIME($instance, $action)
{
global $guid, $version, $pid, $gid, $eid, $uid;
$message = "";
if ($action == "pause") {
PAGEHEADER("Pause Experiment Time");
$message = "Pausing experiment runtime";
}
else {
PAGEHEADER("Continue Experiment Time");
$message = "Continuing experiment runtime";
}
$command_options = "-e $eid -a $action ";
#
# Grab the unix GID for running scripts.
#
TBGroupUnixInfo($pid, $gid, $unix_gid, $unix_name);
#
# Avoid SIGPROF in child.
#
set_time_limit(0);
echo "<font size=+2>Template <b>" .
MakeLink("template",
"guid=$guid&version=$version", "$guid/$version") .
"</b>, Instance <b>" .
MakeLink("project", "pid=$pid", $pid) . "/" .
MakeLink("experiment", "pid=$pid&eid=$eid", $eid);
echo "</b></font>\n";
echo "<br><br>\n";
echo "<script type='text/javascript' language='javascript' ".
" src='template_sup.js'>\n";
echo "</script>\n";
STARTBUSY($message);
#
# Run the backend script.
#
$retval = SUEXEC($uid, "$pid,$unix_gid",
"webtemplate_exprun $command_options $guid/$version",
SUEXEC_ACTION_IGNORE);
CLEARBUSY();
#
# 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) {
SUEXECERROR(SUEXEC_ACTION_CONTINUE);
}
# User error. Tell user and exit.
if ($retval) {
SUEXECERROR(SUEXEC_ACTION_USERERROR);
return;
}
PAGEREPLACE("showexp.php3?pid=$pid&eid=$eid");
}
#
# Spit the form out using the array of data.
#
......@@ -327,6 +397,12 @@ if (isset($action) && $action == "stop") {
PAGEFOOTER();
return;
}
elseif (isset($action) && ($action == "pause" || $action == "continue")) {
# Run the backend script.
DOTIME($instance, $action, "");
PAGEFOOTER();
return;
}
elseif (!isset($exprun)) {
#
# On first load, display virgin form and exit.
......
......@@ -291,6 +291,11 @@ WRITESUBMENUBUTTON("Add Metadata",
"template_metadata.php?action=add&".
"guid=$guid&version=$version");
if ($template->EventCount() > 0) {
WRITESUBMENUBUTTON("Edit Template Events",
"template_editevents.php?guid=$guid&version=$version");
}
# We show the user the datastore for the template; the rest of it is not important.
WRITESUBMENUBUTTON("Template Archive",
"archive_view.php3/$exptidx/trunk?exptidx=$exptidx");
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment