Commit 217de8ab authored by Leigh B. Stoller's avatar Leigh B. Stoller

Two somewhat related template changes.

* Reorg the CVS repo so that records and setup are toplevel modules in
  the repo, instead of directories in a single module named by the
  guid (which is redundant and annoying).

* Some changes to the spewlog stuff. It used to handle only
  experiments, but I really wanted it to handle template create and
  modify. Took a bunch of small changes to a lot of places to make
  this work correctly, but it was worth it.

  There are some changes I made that I can retrofit to the other spew
  pages to make it look a little nicer at the top of the page, to use
  less space.
parent 5c7271c9
......@@ -98,9 +98,31 @@ sub mysystem($)
#
# Lookup a template and create a class instance to return.
#
sub Lookup($$$)
sub Lookup($$;$)
{
my ($class, $guid, $vers) = @_;
my ($class, $arg1, $arg2) = @_;
my ($guid, $vers);
#
# A single arg is either an index or a "guid,vers" or "guid/vers" string.
#
if (!defined($arg2)) {
if ($arg1 =~ /^([-\w]*),([-\w]*)$/ ||
$arg1 =~ /^([-\w]*)\/([-\w]*)$/) {
$guid = $1;
$vers = $2;
}
else {
return undef;
}
}
elsif (! (($arg1 =~ /^[-\w]*$/) && ($arg2 =~ /^[-\w]*$/))) {
return undef;
}
else {
$guid = $arg1;
$vers = $arg2;
}
# Look in cache first
return $templates{"$guid/$vers"}
......@@ -147,6 +169,10 @@ sub child_vers($) {
return ((! ref($_[0])) ? -1 : $_[0]->{'TEMPLATE'}->{'child_vers'}); }
sub description($) {
return ((! ref($_[0])) ? -1 : $_[0]->{'TEMPLATE'}->{'description'}); }
sub logfile($) {
return ((! ref($_[0])) ? -1 : $_[0]->{'TEMPLATE'}->{'logfile'}); }
sub logfile_open($) {
return ((! ref($_[0])) ? -1 : $_[0]->{'TEMPLATE'}->{'logfile_open'}); }
#
# Lookup a template given an experiment index.
......@@ -417,6 +443,126 @@ sub Delete($)
return 0;
}
#
# Logfiles. This all needs to change.
#
# Open a new logfile and return its name.
#
sub CreateLogFile($$$)
{
my ($self, $prefix, $ppath) = @_;
# Must be a real reference.
return -1
if (! ref($self));
my $vers = $self->vers();
my $guid = $self->guid();
my $pid = $self->pid();
my $projroot = PROJROOT();
my $logdir = "$projroot/$pid/templates/logs";
my $logname = "$logdir/$prefix.${guid}-${vers}.log";
return -1
if (-e $logname);
return -1
if (! -d $logdir && !mkdir($logdir, 0775));
Template::mysystem("touch $logname") == 0
or return -1;
$$ppath = $logname;
return 0;
}
#
# Set the experiment to use the logfile. It becomes the "current" spew.
#
sub SetLogFile($$)
{
my ($self, $logname) = @_;
# Must be a real reference.
return -1
if (! ref($self));
return $self->Update({'logfile' => $logname});
}
#
# Get the experiment logfile.
#
sub GetLogFile($$$)
{
my ($self, $lognamep, $isopenp) = @_;
# Must be a real reference.
return -1
if (! ref($self));
# Must do this to catch updates to the logfile variables.
return -1
if ($self->Refresh());
return -1
if (! $self->logfile());
$$lognamep = $self->logfile();
$$isopenp = $self->logfile_open();
return 0;
}
#
# Mark the log as open so that the spew keeps looking for more output.
#
sub OpenLogFile($)
{
my ($self) = @_;
# Must be a real reference.
return -1
if (! ref($self));
return $self->Update({'logfile_open' => 1});
}
#
# And close it ...
#
sub CloseLogFile($)
{
my ($self) = @_;
# Must be a real reference.
return -1
if (! ref($self));
return $self->Update({'logfile_open' => 0});
}
#
# And clear it ...
#
sub ClearLogFile($)
{
my ($self) = @_;
# Must be a real reference.
return -1
if (! ref($self));
my $guid = $self->guid();
my $vers = $self->vers();
if (!DBQueryWarn("update experiment_templates set ".
"logfile=NULL,logfile_open=0 ".
"where guid='$guid' and vers='$vers'")) {
return -1;
}
return $self->Refresh();
}
#
# Template permission checks. Using the experiment access check stuff.
#
......
#!/usr/bin/perl -wT
#
# EMULAB-COPYRIGHT
# Copyright (c) 2000-2002, 2005, 2006 University of Utah and the Flux Group.
# Copyright (c) 2000-2007 University of Utah and the Flux Group.
# All rights reserved.
#
use strict;
use English;
use Getopt::Std;
#
# Spew the current log file for an experiment to stdout. This is for
# use by the web interface, so it can send the lofgile to the user in
# a web page.
# Spew the current log file for an experiment or template to stdout.
# This is for use by the web interface, so it can send the logfile to
# the user in a web page.
#
# The wrinkle is that the logfile only exists while the experiment is
# in transition, and we have to quit when the experiment is no longer in
# transition so that the web page can finish.
#
sub usage()
{
print STDOUT "Usage: spewlogfile <pid> <eid>\n".
"Spew the logfile for an experiment.\n";
print("Usage: spewlogfile -e pid,eid\n".
" spewlogfile -t guid,vers\n".
"Spew the logfile for an experiment or template.\n");
exit(-1);
}
my $optlist = "w";
my $optlist = "we:t:";
my $fromweb = 0;
#
......@@ -37,6 +36,8 @@ my $TBLOGS = "@TBLOGSEMAIL@";
my $logname;
my $isopen;
my $experiment;
my $template;
#
# Load the Testbed support stuff.
......@@ -44,6 +45,9 @@ my $isopen;
use lib "@prefix@/lib";
use libdb;
use libtestbed;
use Experiment;
use Template;
use User;
# un-taint path
$ENV{'PATH'} = '/bin:/usr/bin:/usr/local/bin';
......@@ -56,34 +60,29 @@ $| = 1;
# Parse command arguments. Once we return from getopts, all that should be
# left are the required arguments.
#
%options = ();
my %options = ();
if (! getopts($optlist, \%options)) {
usage();
}
if (defined($options{"w"})) {
$fromweb = 1;
}
if (@ARGV != 2) {
usage();
}
my $pid = $ARGV[0];
my $eid = $ARGV[1];
#
# Untaint the arguments.
#
if ($pid =~ /^([-\@\w]+)$/) {
$pid = $1;
}
else {
die("*** Bad data in pid: $pid\n");
}
if ($eid =~ /^([-\@\w]+)$/) {
$eid = $1;
if (defined($options{"e"})) {
$experiment = Experiment->Lookup($options{"e"});
if (! $experiment) {
die("*** $0:\n".
" No such experiment in the Emulab Database.\n");
}
}
else {
die("*** Bad data in eid: $eid\n");
elsif (defined($options{"t"})) {
$template = Template->Lookup($options{"t"});
if (! $template) {
die("*** $0:\n".
" No such template in the Emulab Database.\n");
}
}
usage()
if (@ARGV || !($experiment || $template));
#
# This script is setuid, so please do not run it as root. Hard to track
......@@ -95,19 +94,42 @@ if ($UID == 0) {
}
#
# Verify that this person is allowed to do this.
# Verify user and get his DB uid and other info for later.
#
if (!TBExptAccessCheck($UID, $pid, $eid, TB_EXPT_READINFO)) {
my $this_user = User->ThisUser();
if (! defined($this_user)) {
die("*** $0:\n".
" You do not have permission to view log files for $pid/$eid!\n");
" You ($UID) do not exist!");
}
#
# Get the logfile name.
# Verify that this person is allowed to do this.
#
if (! TBExptGetLogFile($pid, $eid, \$logname, \$isopen)) {
die("*** $0:\n".
" There is no logfile to view for $pid/$eid!\n");
if ($experiment) {
if (!$experiment->AccessCheck($this_user, TB_EXPT_READINFO)) {
die("*** $0:\n".
" You do not have permission to view log for $experiment!\n");
}
#
# Get the logfile name.
#
if ($experiment->GetLogFile(\$logname, \$isopen)) {
die("*** $0:\n".
" There is no logfile to view for $experiment!\n");
}
}
else {
if (!$template->AccessCheck($this_user, TB_EXPT_READINFO)) {
die("*** $0:\n".
" You do not have permission to view log for $template!\n");
}
#
# Get the logfile name.
#
if ($template->GetLogFile(\$logname, \$isopen)) {
die("*** $0:\n".
" There is no logfile to view for $template!\n");
}
}
use Fcntl;
......@@ -118,7 +140,7 @@ STDOUT->autoflush(1);
# If not an admin type, flip back to the UID now to enforce normal
# permissions.
#
if (! TBAdmin($UID)) {
if (!$this_user->IsAdmin()) {
$EUID = $UID;
}
......@@ -129,7 +151,7 @@ sysopen(LOG, $logname, O_RDONLY | O_NONBLOCK) or
#
# If an admin type, flip back to the UID now that the file is open.
#
if (TBAdmin($UID)) {
if ($this_user->IsAdmin()) {
$EUID = $UID;
}
......@@ -142,7 +164,7 @@ my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
# but not sure what else to do.
#
if ($fromweb && $isopen && $size < 1024) {
for ($i = $size; $i <= 1024; $i++) {
for (my $i = $size; $i <= 1024; $i++) {
print " ";
}
print "\n";
......@@ -154,13 +176,20 @@ if ($fromweb && $isopen && $size < 1024) {
#
while (1) {
my $tmp;
my $buf;
while (sysread(LOG, $buf, 2048)) {
print STDOUT "$buf";
}
if (! TBExptGetLogFile($pid, $eid, \$tmp, \$isopen) || !$isopen ||
$tmp ne $logname) {
last;
if ($experiment) {
last
if ($experiment->GetLogFile(\$tmp, \$isopen) ||
!$isopen || $tmp ne $logname);
}
else {
last
if ($template->GetLogFile(\$tmp, \$isopen) ||
!$isopen || $tmp ne $logname);
}
sleep(2);
}
......
......@@ -186,15 +186,15 @@ $SIG{TERM} = \&sighandler;
if (defined($repotag)) {
CommitFromRepo();
}
elsif (defined($eid)) {
CommitFromInstance();
}
else {
tbdie("Unsupported template commit operation!");
if (defined($frompath)) {
CommitFromCheckout();
}
elsif (defined($eid)) {
CommitFromInstance();
}
else {
CommitFromTemplate();
}
......@@ -372,35 +372,22 @@ sub CommitFromInstance()
#
# Start with a plain template modify of the current template.
#
system("$modify -m $guid/$version -w -g $gid $pid $tid $nsfile");
system("$modify -f $frompath ".
" -m $guid/$version -w -g $gid $pid $tid $nsfile");
if ($?) {
fatal($? >> 8, "Failed to commit instance to template!");
}
# Pick up changes to child guid/vers.
# Pick up changes to child guid/vers.
$template->Refresh();
$child_template = Template->Lookup($template->child_guid(),
$template->child_vers());
if (!defined($child_template)) {
my $child_guid = $template->child_guid();
my $child_vers = $template->child_vers();
my $child = Template->Lookup($child_guid, $child_vers);
if (!defined($child)) {
fatal(-1, "Lookup of child template failed!");
}
#
# Now "import" the datastore directory from the instance to the new
# template and commit the changes.
#
if ($child_template->ImportDataStore("$userdir/datastore") != 0) {
fatal(-1, "Could not import datastore from instance to template!");
}
#
# Commit the archive.
#
system("$archcontrol -t import commit $pid " . $child_template->eid());
if ($?) {
fatal(-1, "Could not commit archive!");
}
$child->SetDescription($description)
if (defined($description));
}
#
......
......@@ -52,6 +52,7 @@ my $pid;
my $tid;
my $gid;
my $inputfile;
my $logname;
# For modify.
my $parent_guid;
my $parent_vers;
......@@ -233,6 +234,15 @@ if ($STAMPS) {
}
}
#
# The template gets its own directory structure.
#
if (my $rval = $template->CreateDirectory()) {
$template->Delete();
tbdie("Failed to create directories for template");
}
my $template_dir = $template->path();
#
# At this point, we need to force a cleanup no matter how we exit.
# See the END block below.
......@@ -249,8 +259,15 @@ $tmpdir = "$projroot/$pid/templates/$guid/cvstmp.$$";
#
# Use the logonly option to audit so that we get a record mailed.
#
$template->CreateLogFile("template_create", \$logname) == 0 or
fatal(-1, "Could not create a logfile");
$template->SetLogFile($logname) == 0 or
fatal(-1, "Could not set the logfile to $logname");
$template->OpenLogFile() == 0 or
fatal(-1, "Could not open the logfile");
if (my $childpid =
AuditStart(LIBAUDIT_DAEMON, undef, LIBAUDIT_LOGONLY|LIBAUDIT_FANCY)) {
AuditStart(LIBAUDIT_DAEMON, $logname, LIBAUDIT_LOGONLY|LIBAUDIT_FANCY)) {
#
# Parent exits normally, unless in waitmode. We have to set
# justexit to make sure the END block below does not run.
......@@ -258,12 +275,12 @@ if (my $childpid =
$justexit = 1;
if (!$waitmode) {
print("Template $pid/$tid is being created.\n".
"You will be notified via email it is ready to use\n")
print("Template $guid/$vers is being created. Watch your email.\n")
if (! $quiet);
exit(0);
}
print("Waiting for template $pid/$tid to be created. Please be patient!\n")
print("Waiting for template $guid/$vers to be created. ".
"Please be patient!\n")
if (! $quiet);
# Give child a chance to run.
......@@ -321,14 +338,6 @@ sub handler ($) {
$SIG{TERM} = \&handler;
$SIG{QUIT} = 'DEFAULT';
#
# The template gets its own directory structure.
#
if (my $rval = $template->CreateDirectory()) {
fatal($rval, "Failed to create directories for template");
}
my $template_dir = $template->path();
#
# Set up CVS repo on ops to use as alternate interface.
#
......@@ -364,6 +373,7 @@ if (!$modify || ! -e $cvsdir) {
print CONFIG "TemplateCommit=$TB/sbin/rtag_commit\n";
close(CONFIG);
}
#
# We stick in a little cookie file so we know what template this is,
# from within the CVS repo (well, sandbox).
......@@ -421,9 +431,9 @@ if (defined($repotag)) {
$tokens[scalar(@tokens)-2] = 0;
my $branchrev = join(".", @tokens);
open(RLOG, "$RLOG -h $cvsdir/$guid/setup/.template,v |")
open(RLOG, "$RLOG -h $cvsdir/setup/.template,v |")
or fatal(-1,
"Could not run rlog on $cvsdir/$guid/setup/.template,v!");
"Could not run rlog on $cvsdir/setup/.template,v!");
my $intags = 0;
while (<RLOG>) {
if ($intags) {
......@@ -456,26 +466,26 @@ if (defined($repotag)) {
# Checkout ...
System("cd $tmpdir; $CVSBIN -d $cvsdir checkout ".
" -r $repotag $guid/setup")
" -r $repotag setup")
== 0 or fatal(-1, "Could not checkout '$repotag' from $cvsdir");
}
else {
# Checkout along trunk, no -r option cause then commit fails
System("cd $tmpdir; $CVSBIN -d $cvsdir checkout -r HEAD $guid/setup")
System("cd $tmpdir; $CVSBIN -d $cvsdir checkout -r HEAD setup")
== 0 or fatal(-1, "Could not checkout trunk from $cvsdir");
#
# Clear the default branch so that checkout gives us the trunk.
#
System("cd $tmpdir/$guid; $CVSBIN -d $cvsdir admin -b")
System("cd $tmpdir; $CVSBIN -d $cvsdir admin -b")
== 0 or fatal(-1, "Could not clear default branch in $cvsdir");
# And clear the sticky tag so later commit works.
System("cd $tmpdir/$guid/setup; $CVSBIN -d $cvsdir update -A")
System("cd $tmpdir/setup; $CVSBIN -d $cvsdir update -A")
== 0 or fatal(-1, "Could not update to head trunk from $cvsdir");
}
$inputfile = "$tmpdir/$guid/setup/tbdata/nsfile.ns";
$inputfile = "$tmpdir/setup/tbdata/nsfile.ns";
fatal(-1, "NS file missing from repo checkout!")
if (!-e $inputfile);
}
......@@ -539,7 +549,7 @@ if ($modify) {
>= 0 or fatal(-1, "Failed to commit experiment archive!");
if (defined($repotag)) {
$template->ImportDataStore("$tmpdir/$guid/setup/datastore") == 0
$template->ImportDataStore("$tmpdir/setup/datastore") == 0
or fatal(-1, "Failed to import data store");
}
elsif (defined($frompath)) {
......@@ -576,10 +586,14 @@ if (! -e "$template_dir/datastore/.ignore") {
if (!$modify || $needrepoinit) {
System("cd $template_dir; ".
"$CVSBIN -d $cvsdir import ".
" -m 'Initialize new cvs repo for template $guid' $guid/setup ".
" -m 'Initialize new cvs repo for template $guid' setup ".
" T${guid}-${vers}_import_branch T${guid}-${vers}_import")
== 0 or fatal(-1, "Could not import new template into $cvsdir");
# Create the records module with a simple mkdir. Harmless.
mkdir("$cvsdir/records", 0777) or
fatal(-1, "Could not mkdir $cvsdir/records: $!");
#
# Must advance the head past 1.1 since that is where the imports are done
# and it will confuse everything later.
......@@ -588,31 +602,37 @@ if (!$modify || $needrepoinit) {
fatal(-1, "Could not mkdir $tmpdir: $!");
# Checkout ...
System("cd $tmpdir; $CVSBIN -d $cvsdir checkout $guid")
System("cd $tmpdir; $CVSBIN -d $cvsdir checkout setup")
== 0 or fatal(-1, "Could not checkout from $cvsdir");
# Create the records directory.
System("cd $tmpdir/$guid; mkdir records; touch records/.ignore") == 0 or
fatal(-1, "Could not mkdir records directory in $tmpdir/$guid");
System("cd $tmpdir/$guid; $CVSBIN -d $cvsdir add records; ".
" $CVSBIN -d $cvsdir add records/.ignore")
== 0 or fatal(-1, "Could not cvs add $tmpdir/$guid/records!");
# Commit ...
System("cd $tmpdir; $CVSBIN -d $cvsdir commit -f -R ".
System("cd $tmpdir/setup; $CVSBIN -d $cvsdir commit -f -R ".
" -m 'Commit initial import back to head'")
== 0 or fatal(-1, "Could not commit to $cvsdir");
# No keyword subst.
System("cd $tmpdir; $CVSBIN -Q -d $cvsdir admin -kb")
System("cd $tmpdir/setup; $CVSBIN -Q -d $cvsdir admin -kb")
== 0 or fatal(-1, "Could not set -kb in $cvsdir");
#
# Now tag the CVS repo with the current guid/vers so we can find it easily.
#
System("$CVSBIN -d $cvsdir rtag -n T${guid}-${vers} $guid")
System("$CVSBIN -d $cvsdir rtag -n T${guid}-${vers} setup")
== 0 or fatal(-1, "Could not rtag initial version in $cvsdir");
# Setup the records directory.
System("cd $tmpdir; $CVSBIN -d $cvsdir checkout records")
== 0 or fatal(-1, "Could not checkout from $cvsdir");
System("cd $tmpdir/records; touch .ignore") == 0 or
fatal(-1, "Could not create $tmpdir/records/.ignore");
System("cd $tmpdir/records; $CVSBIN -d $cvsdir add .ignore")
== 0 or fatal(-1, "Could not cvs add $tmpdir/records/.ignore!");
System("cd $tmpdir/records; $CVSBIN -d $cvsdir commit -f -R ".
" -m 'Commit initial records directory'")
== 0 or fatal(-1, "Could not commit records to $cvsdir");
}
elsif ($frompath) {
#
......@@ -628,11 +648,11 @@ elsif (defined($repotag)) {
# that when the user does a checkout of the branch, it says what
# template it is (and what to template to modify on the next branch).
#
System("cp -p $template_dir/.template $tmpdir/$guid/setup") == 0
or fatal(-1, "Could not cp $template_dir/.template to $tmpdir/$guid");
System("cp -p $template_dir/.template $tmpdir/setup") == 0
or fatal(-1, "Could not cp $template_dir/.template to $tmpdir");
# Check the file in.