Commit e9607a77 authored by Leigh B. Stoller's avatar Leigh B. 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)/
......
......@@ -1713,7 +1713,7 @@ sub Create($$)
# Give it an initial start time; updated later.
$query .= ", "
if (defined($argref) && scalar(keys%{$argref}));
$query .= "start_time=now() ";
$query .= "start_time=now(),continue_time=now() ";
my $query_result = DBQueryWarn($query);
return undef
......@@ -1725,17 +1725,21 @@ sub Create($$)
return Template::Instance->LookupByID($idx);
}
# accessors
sub idx($) { return ((!ref($_[0])) ? -1 : $_[0]->{'DB'}->{'idx'}); }
sub exptidx($) { return ((!ref($_[0])) ? -1 : $_[0]->{'DB'}->{'exptidx'}); }
sub guid($) { return ((!ref($_[0])) ? -1 : $_[0]->{'DB'}->{'parent_guid'});}
sub vers($) { return ((!ref($_[0])) ? -1 : $_[0]->{'DB'}->{'parent_vers'});}
sub pid($) { return ((!ref($_[0])) ? -1 : $_[0]->{'DB'}->{'pid'}); }
sub eid($) { return ((!ref($_[0])) ? -1 : $_[0]->{'DB'}->{'eid'}); }
sub uid($) { return ((!ref($_[0])) ? -1 : $_[0]->{'DB'}->{'uid'}); }
sub runidx($) { return ((!ref($_[0])) ? -1 : $_[0]->{'DB'}->{'runidx'}); }
sub start_time { return ((!ref($_[0])) ? -1 : $_[0]->{'DB'}->{'start_time'}); }
sub stop_time { return ((!ref($_[0])) ? -1 : $_[0]->{'DB'}->{'stop_time'}); }
sub template($){ return ((!ref($_[0])) ? -1 : $_[0]->{'TEMPLATE'}); }
sub field($$) { return ((! ref($_[0])) ? -1 : $_[0]->{'DB'}->{$_[1]}); }
sub idx($) { return field($_[0], 'idx'); }
sub exptidx($) { return field($_[0], 'exptidx'); }
sub guid($) { return field($_[0], 'parent_guid');}
sub vers($) { return field($_[0], 'parent_vers');}
sub pid($) { return field($_[0], 'pid'); }
sub eid($) { return field($_[0], 'eid'); }
sub uid($) { return field($_[0], 'uid'); }
sub runidx($) { return field($_[0], 'runidx'); }
sub start_time($) { return field($_[0], 'start_time'); }
sub stop_time($) { return field($_[0], 'stop_time'); }
sub pause_time($) { return field($_[0], 'pause_time'); }
sub continue_time($) { return field($_[0], 'continue_time'); }
sub runtime($) { return field($_[0], 'runtime'); }
sub template($) { return ((!ref($_[0])) ? -1 : $_[0]->{'TEMPLATE'}); }
# The path is the path of the experiment.
sub path($) { return TBExptUserDir($_[0]->pid(), $_[0]->eid()); }
......@@ -1792,7 +1796,7 @@ sub Update($$;$)
my $idx = $self->idx();
my $query = "update experiment_template_instances set ";
$query .= "start_time=now() "
$query .= "start_time=now(),continue_time=now(),runtime=0 "
if ($start_time);
if (defined($argref) && scalar(keys%{$argref})) {
......@@ -2120,6 +2124,100 @@ sub DeleteCurrentRun($)
return Refresh($self);
}
#
# Pause and Continue time, keeping track of running time.
#
sub PauseTime($)
{
my ($self) = @_;
# Must be a real reference.
return -1
if (! ref($self));
if ($self->pause_time()) {
tbwarn("$self is already paused!\n");
return -1;
}
if (!defined($self->continue_time())) {
tbwarn("PauseTime: $self is in an inconsistent state!\n");
return -1;
}
my $idx = $self->idx();
DBQueryWarn("update experiment_template_instances set ".
" runtime=runtime + ".
" (UNIX_TIMESTAMP(now())-UNIX_TIMESTAMP(continue_time)), ".
" continue_time=NULL, ".
" pause_time=now() ".
"where idx='$idx'")
or return -1;
return Refresh($self);
}
sub ContinueTime($)
{
my ($self) = @_;
# Must be a real reference.
return -1
if (! ref($self));
if (! defined($self->pause_time())) {
tbwarn("$self is not paused!\n");
return -1;
}
if (defined($self->continue_time())) {
tbwarn("PauseTime: $self is in an inconsistent state!\n");
return -1;
}
my $idx = $self->idx();
DBQueryWarn("update experiment_template_instances set ".
" continue_time=now(), ".
" pause_time=NULL ".
"where idx='$idx'")
or return -1;
return Refresh($self);
}
sub AdjustTimeStamp($$$)
{
my ($self, $timestamp, $peventtime) = @_;
# Must be a real reference.
return -1
if (! ref($self));
my $idx = $self->idx();
if ($self->pause_time()) {
# If paused, the event runs at the difference between now() and the
# timestamp provided.
$$peventtime = $self->runtime() + (time() - $timestamp);
}
else {
# Running. Need to calculate current runtime.
my $query_result =
DBQueryWarn("select runtime + ".
" (UNIX_TIMESTAMP(now()) - ".
" UNIX_TIMESTAMP(continue_time)) ".
" from experiment_template_instances ".
"where idx='$idx'");
return -1
if (!$query_result || !$query_result->numrows);
my ($t) = $query_result->fetchrow_array();
$$peventtime = $t + (time() - $timestamp);
}
return 0;
}
#
# Start the (first) experiment run.
#
......@@ -2659,12 +2757,24 @@ sub AddEvent($$$$)
my $eid = $self->eid();
my $cmd = DBQuoteSpecial("COMMAND=$commandline");
#
# Need to adjust the timestamp to account for stopped time. This also
# converts from absolute to relative event time.
#
my $firetime;
$self->AdjustTimeStamp($timestamp, \$firetime) == 0
or return -1;
# We assume program agent events right now. Change later.
my $query_result =
DBQueryWarn("select idx from event_objecttypes where type='PROGRAM'");
DBQueryWarn("select idx from event_objecttypes ".
"where type='PROGRAM' or type='SEQUENCE' order by type");
return -1
if (!$query_result || ! $query_result->numrows);
my ($event_objecttype) = $query_result->fetchrow_array();
if (!$query_result || $query_result->numrows != 2);
my ($program_objecttype) = $query_result->fetchrow_array();
my ($sequence_objecttype) = $query_result->fetchrow_array();
# And its a start event of course.
$query_result =
......@@ -2674,18 +2784,37 @@ sub AddEvent($$$$)
my ($event_eventtype) = $query_result->fetchrow_array();
#
# Need to convert the timestamp from absolute unixtime to relative time.
# We need an event sequence to properly order sequential events.
#
my $runidx = $self->runidx();
my $exptidx = $self->exptidx();
my $sequence_name = "${vnode}_record";
$query_result =
DBQueryWarn("select UNIX_TIMESTAMP(start_time) from experiment_runs ".
"where exptidx='$exptidx' and idx='$runidx'");
DBQueryWarn("select * from virt_agents ".
"where pid='$pid' and eid='$eid' and ".
" vname='$sequence_name'");
return -1
if (!$query_result || ! $query_result->numrows);
my ($starttime) = $query_result->fetchrow_array();
my $firetime = $timestamp - $starttime;
if (!$query_result);
if (! $query_result->numrows) {
DBQueryWarn("replace into virt_agents set ".
" pid='$pid', eid='$eid', ".
" vnode='*', vname='$sequence_name', ".
" objecttype='$sequence_objecttype'")
or return -1;
if (! DBQueryWarn("insert into eventlist set ".
" pid='$pid', eid='$eid', time='0', ".
" vnode='$vnode', vname='$sequence_name', ".
" objecttype='$sequence_objecttype', ".
" eventtype='$event_eventtype', ".
" atstring='', arguments=''")) {
DBQueryWarn("delete from virt_agents ".
"where pid='$pid' and eid='$eid' and ".
" vname='$sequence_name'");
return -1;
}
}
# Make up a vname.
my $vname = "dprog" . $firetime;
......@@ -2697,9 +2826,10 @@ sub AddEvent($$$$)
DBQueryWarn("insert into eventlist set ".
" pid='$pid', eid='$eid', time='$firetime', ".
" vnode='$vnode', vname='$vname', ".
" objecttype='$event_objecttype', ".
" objecttype='$program_objecttype', ".
" eventtype='$event_eventtype', ".
" atstring='', arguments=$cmd");
" atstring='', arguments=$cmd, ".
" parent='$sequence_name'");
return -1
if (!$query_result || ! $query_result->numrows);
......@@ -2710,7 +2840,7 @@ sub AddEvent($$$$)
if (!DBQueryWarn("insert into virt_agents set ".
" pid='$pid', eid='$eid', ".
" vnode='$vnode', vname='$vname', ".
" objecttype='$event_objecttype'")) {
" objecttype='$program_objecttype'")) {
DBQueryWarn("delete from eventlist ".
"where pid='$pid' and eid='$eid' and idx='$eventidx'");
return -1;
......@@ -2736,7 +2866,7 @@ sub AddEvent($$$$)
" parent_guid='$guid', parent_vers='$vers', ".
" time='$firetime', ".
" vnode='$vnode', vname='$vname', ".
" objecttype='$event_objecttype', ".
" objecttype='$program_objecttype', ".
" eventtype='$event_eventtype', ".
" arguments=$cmd")) {
DBQueryWarn("delete from eventlist ".
......@@ -2769,7 +2899,22 @@ sub CopyTemplateEvents($)
my $pid = $self->pid();
my $eid = $self->eid();
# Need to insert a sequence to wrap these.
my $query_result =
DBQueryWarn("select idx from event_objecttypes where type='SEQUENCE'");
return -1
if (!$query_result || !$query_result->numrows);
my ($sequence_objecttype) = $query_result->fetchrow_array();
# And its a start event of course.
$query_result =
DBQueryWarn("select idx from event_eventtypes where type='START'");
return -1
if (!$query_result || ! $query_result->numrows);
my ($start_eventtype) = $query_result->fetchrow_array();
# Now process the template events.
$query_result =
DBQueryWarn("select * from experiment_template_events ".
"where parent_guid='$guid' and parent_vers='$vers'");
......@@ -2783,6 +2928,30 @@ sub CopyTemplateEvents($)
my $event_objecttype = $rowref->{"objecttype"};
my $event_eventtype = $rowref->{"eventtype"};
my $cmd = DBQuoteSpecial($rowref->{"arguments"});
my $sequence_name = "${vnode}_record";
my $agent_result =
DBQueryWarn("select * from virt_agents ".
"where pid='$pid' and eid='$eid' and ".
" vname='$sequence_name'");
return -1
if (!$agent_result);
if (! $agent_result->numrows) {
DBQueryWarn("replace into virt_agents set ".
" pid='$pid', eid='$eid', ".
" vnode='*', vname='$sequence_name', ".
" objecttype='$sequence_objecttype'")
or return -1;
DBQueryWarn("insert into eventlist set ".
" pid='$pid', eid='$eid', time='0', ".
" vnode='$vnode', vname='$sequence_name', ".
" objecttype='$sequence_objecttype', ".
" eventtype='$start_eventtype', ".
" atstring='', arguments=''")
or return -1;
}
#
# Any failure and and the instance will be killed by the
......@@ -2793,7 +2962,8 @@ sub CopyTemplateEvents($)
" vnode='$vnode', vname='$vname', ".
" objecttype='$event_objecttype', ".
" eventtype='$event_eventtype', ".
" atstring='', arguments=$cmd")
" atstring='', arguments=$cmd, ".
" parent='$sequence_name'")
or return -1;
# Need a virt_agent entry.
......@@ -2859,6 +3029,22 @@ sub WriteEnvVariables($)
return $experiment->WriteEnvVariables();
}
sub WriteProgramAgents($)
{
my ($self) = @_;
# Must be a real reference.
return -1
if (! ref($self));
my $experiment = $self->Experiment();
return -1
if (! defined($experiment));
return $experiment->WriteProgramAgents();
}
#
# Setup the environment variables for a template swapin.
#
......
......@@ -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";