Commit 78503406 authored by Leigh B. Stoller's avatar Leigh B. Stoller
Browse files

Redo the entire template library. I've been meaning to use perl

"object" and this was a good opportunity to see if they are useful and
easy enough to use. Yep they are; the code is much cleaner with many
fewer utility functions to get at stuff. I recommend this approach
from now on.

The problem is the php side, which ends up duplicating some stuff, but
in the old style. This is not so bad for the template code since I
have made it a point not to do anything but display functions in php;
all modifications are handled in the backend.
parent f8d54de3
......@@ -23,7 +23,7 @@ BIN_STUFF = power snmpit tbend tbprerun tbreport \
tbswap nseswap tarfiles_setup node_history tbrsync \
node_attributes archive_control template_create \
template_swapin template_swapout template_graph \
template_exprun template_delete
template_exprun template_delete template_metadata
SBIN_STUFF = resetvlans console_setup.proxy sched_reload named_setup \
batch_daemon exports_setup reload_daemon sched_reserve \
......@@ -53,7 +53,7 @@ LIBEXEC_STUFF = rmproj wanlinksolve wanlinkinfo \
spewrpmtar webtarfiles_setup webfrisbeekiller gentopofile \
webnodeattributes webarchive_control webtemplate_create \
webtemplate_swapin webtemplate_swapout webtemplate_exprun \
webtemplate_graph
webtemplate_graph webtemplate_metadata
LIB_STUFF = libtbsetup.pm exitonwarn.pm libtestbed.pm snmpit_intel.pm \
snmpit_cisco.pm snmpit_lib.pm snmpit_apc.pm power_rpc27.pm \
......@@ -62,7 +62,7 @@ LIB_STUFF = libtbsetup.pm exitonwarn.pm libtestbed.pm snmpit_intel.pm \
snmpit_nortel.pm \
libaudit.pm libreboot.pm libosload.pm libtestbed.py \
libadminmfs.pm libtblog.pm libtblog_simple.pm libArchive.pm \
power_mail.pm power_whol.pm libTemplates.pm
power_mail.pm power_whol.pm Template.pm
ifeq ($(SYSTEM),FreeBSD)
FBSDVERSION := $(shell uname -v | sed -e 's/FreeBSD \([0-9]\).*/FreeBSD\1/')
......
......@@ -6,7 +6,7 @@
#
# XXX Need to deal with locking at some point ...
#
package libTemplates;
package Template;
use strict;
use Exporter;
......@@ -21,11 +21,15 @@ use libdb;
use libtestbed;
use libtblog;
use English;
use overload ('""' => 'Stringify');
# Configure variables
my $TB = "@prefix@";
my $MD5 = "/sbin/md5";
# Cache of template instances to avoid regenerating them.
my %templates = ();
#
# Grab a new GUID for a template. We do not have to use it of course.
#
......@@ -61,500 +65,408 @@ sub NewGUID($)
}
#
# Check for valid template.
# Lookup a template and create a class instance to return.
#
sub TBValidExperimentTemplate($$)
sub Lookup($$$)
{
my ($guid, $version) = @_;
my ($class, $guid, $vers) = @_;
# Look in cache first
return $templates{"$guid/$vers"}
if (exists($templates{"$guid/$vers"}));
my $query_result =
DBQueryFatal("select guid from experiment_templates ".
"where guid='$guid' and vers='$version'");
return $query_result->num_rows;
}
#
# Template permission checks. Using the experiment access check stuff!
#
# Usage: TBExptTemplateAccessCheck($uid, $guid, $access_type)
# returns 0 if not allowed.
# returns 1 if allowed.
#
sub TBExptTemplateAccessCheck($$$)
{
my ($uid, $guid, $access_type) = @_;
my $mintrust;
if ($access_type < TB_EXPT_MIN ||
$access_type > TB_EXPT_MAX) {
die("*** Invalid access type: $access_type!");
}
DBQueryWarn("select * from experiment_templates ".
"where guid='$guid' and vers='$vers'");
#
# Admins do whatever they want!
#
if (TBAdmin($uid)) {
return 1;
}
$uid = MapNumericUID($uid);
return undef
if (!$query_result || !$query_result->numrows);
my $query_result =
DBQueryFatal("select pid,gid from experiment_templates where ".
"guid='$guid' limit 1");
my $self = {};
$self->{'TEMPLATE'} = $query_result->fetchrow_hashref();
# Filled lazily.
$self->{'INSTANCES'} = undef;
bless($self, $class);
if ($query_result->numrows == 0) {
return 0;
}
my @row = $query_result->fetchrow_array();
my $pid = $row[0];
my $gid = $row[1];
#
# An experiment may be destroyed by the experiment creator or the
# project/group leader.
#
if ($access_type == TB_EXPT_READINFO) {
$mintrust = PROJMEMBERTRUST_USER;
}
else {
$mintrust = PROJMEMBERTRUST_LOCALROOT;
}
#
# Either proper permission in the group, or group_root in the project.
# This lets group_roots muck with other people's experiments, including
# those in groups they do not belong to.
#
return TBMinTrust(TBGrpTrust($uid, $pid, $gid), $mintrust) ||
TBMinTrust(TBGrpTrust($uid, $pid, $pid), PROJMEMBERTRUST_GROUPROOT);
# Add to cache.
$templates{"$guid/$vers"} = $self;
return $self;
}
# accessors
sub guid($) { return ((! ref($_[0])) ? -1 : $_[0]->{'TEMPLATE'}->{'guid'}); }
sub vers($) { return ((! ref($_[0])) ? -1 : $_[0]->{'TEMPLATE'}->{'vers'}); }
sub pid($) { return ((! ref($_[0])) ? -1 : $_[0]->{'TEMPLATE'}->{'pid'}); }
sub gid($) { return ((! ref($_[0])) ? -1 : $_[0]->{'TEMPLATE'}->{'gid'}); }
sub eid($) { return ((! ref($_[0])) ? -1 : $_[0]->{'TEMPLATE'}->{'eid'}); }
sub tid($) { return ((! ref($_[0])) ? -1 : $_[0]->{'TEMPLATE'}->{'tid'}); }
#
# Create a new template record from the args provided. We assume that
# the table is locked by the caller.
#
# Usage: int NewTemplateRecord(\%args)
# Lookup a template given an experiment index.
#
sub NewTemplateRecord($)
sub LookupByExptidx($$)
{
my ($args) = @_;
my ($class, $exptidx) = @_;
my $query = "insert into experiment_templates set ".
join(",", map("$_='" . $args->{$_} . "'", keys(%{$args})));
# Use the Template Instance routine, and grab the template out of it.
my $template_instance = Template::Instance->LookupByExptidx($exptidx);
# Append some default value stuff.
$query .= ",created=now()";
return undef
if (!defined($template_instance));
return -1
if (! DBQueryWarn($query));
return 0;
return $template_instance->template();
}
#
# Update a template record
# Refresh a template instance by reloading from the DB.
#
sub UpdateTemplateRecord($$$)
sub Refresh($)
{
my ($guid, $vers, $args) = @_;
my ($self) = @_;
my $query = "update experiment_templates set ".
join(",", map("$_='" . $args->{$_} . "'", keys(%{$args})));
return -1
if (! ref($self));
$query .= " where guid='$guid' and vers='$vers'";
my $guid = $self->guid();
my $vers = $self->vers();
my $query_result =
DBQueryWarn("select * from experiment_templates ".
"where guid='$guid' and vers='$vers'");
return -1
if (! DBQueryWarn($query));
if (!$query_result || !$query_result->numrows);
$self->{'TEMPLATE'} = $query_result->fetchrow_hashref();
return 0;
}
#
# Delete a template (all tables).
# Create a new template. This installs the new record in the DB,
# and returns an instance. There is some bookkeeping along the way.
#
sub DeleteTemplateRecord($$)
sub Create($$)
{
my ($guid, $vers) = @_;
my ($class, $argref) = @_;
my ($guid, $vers);
DeleteTemplateMetadata($guid, $vers) == 0
or return -1;
# Make sure the experiment_templates table is always last, in case
# something goes wrong.
my @tables = ("experiment_templates", "experiment_template_parameters");
return undef
if (ref($class));
foreach my $table (@tables) {
if ($table eq "experiment_templates") {
DBQueryFatal("delete from $table ".
"where guid='$guid' and vers='$vers'");
}
else {
DBQueryFatal("delete from $table ".
"where parent_guid='$guid' and parent_vers='$vers'");
}
# See if this a child of an existing template.
if (defined($argref->{'parent_guid'})) {
$guid = $argref->{'parent_guid'};
}
else {
#
# Grab a new GUID before we lock other tables.
#
if (NewGUID(\$guid) < 0) {
tberror("Could not get a new GUID!");
return undef;
}
$vers = 1;
}
}
#
# Create a new template instance record from the args provided.
#
# Usage: int NewTemplateInstanceRecord(\%args, \$idx)
#
sub NewTemplateInstanceRecord($$)
{
my ($args, $pidx) = @_;
DBQueryWarn("lock tables experiments write, ".
" experiment_templates write")
or return undef;
my $query = "insert into experiment_template_instances set ".
join(",", map("$_='" . $args->{$_} . "'", keys(%{$args})));
#
# Find unused version number now that tables are locked.
#
if (! defined($vers)) {
my $query_result =
DBQueryWarn("select MAX(vers) from experiment_templates ".
"where guid='$guid'");
my $query_result = DBQueryWarn($query);
return -1
if (! $query_result);
if (!$query_result) {
DBQueryWarn("unlock tables");
return undef;
}
# Grab the insert record.
$$pidx = $query_result->insertid;
return 0;
}
$vers = ($query_result->fetchrow_array())[0];
$vers++;
}
#
# Delete a template instance record.
#
sub DeleteTemplateInstanceRecord($$$)
{
my ($guid, $vers, $idx) = @_;
# We make up an eid using the guid and version. This is the eid for the
# hidden experiment behind each template.
my $eid = "T${guid}-${vers}";
my $pid = $argref->{'pid'};
#
# Delete the run records first since they will not mean much after
# this record is gone.
# Sanity check; make sure this eid is not in use. Tables are still locked.
#
my $query_result =
DBQueryWarn("select exptidx from experiment_template_instances ".
"where parent_guid='$guid' and parent_vers='$vers' and ".
" idx='$idx'");
return -1
if (!$query_result);
while (my ($exptidx) = $query_result->fetchrow_array()) {
DBQueryWarn("delete from experiment_run_bindings ".
"where exptidx='$exptidx'")
or return -1;
DBQueryWarn("select pid,eid from experiments ".
"where eid='$eid' and pid='$pid'");
DBQueryWarn("delete from experiment_runs ".
"where exptidx='$exptidx'")
or return -1;
if (!$query_result) {
DBQueryWarn("unlock tables");
return undef;
}
if ($query_result->numrows) {
DBQueryWarn("unlock tables");
tberror("Experiment ID $eid in project $pid is already in use!");
return undef;
}
#
# Also delete the binding records for the instance.
#
DeleteTemplateInstanceBindingRecords($guid, $vers, $idx) == 0
or return -1;
my $query = "insert into experiment_templates set ".
join(",", map("$_='" . $argref->{$_} . "'", keys(%{$argref})));
# And finally the instance record.
DBQueryWarn("delete from experiment_template_instances ".
"where parent_guid='$guid' and parent_vers='$vers' and ".
" idx='$idx'")
or return -1;
# Append the rest
$query .= ",created=now(),guid='$guid',vers='$vers',eid='$eid'";
return 0;
if (! DBQueryWarn($query)) {
DBQueryWarn("unlock tables");
return undef;
}
DBQueryWarn("unlock tables");
return Template->Lookup($guid, $vers);
}
#
# Finalize a template instance record; usage is similar to above
# Stringify for output.
#
# Usage: int FinalizeTemplateInstanceRecord($guid, $vers, $exptidx, \%args)
#
sub FinalizeTemplateInstanceRecord($$$;$)
sub Stringify($)
{
my ($guid, $vers, $idx, $args) = @_;
#
# Get the runidx and do it first.
#
my $runidx;
my $exptidx;
return -1
if (TemplateInstanceInfo($idx, \$runidx, undef, \$exptidx) < 0);
if (defined($runidx)) {
return -1
if (FinalizeTemplateExerimentRunRecord($exptidx, $runidx) < 0);
}
my $query = "update experiment_template_instances set stop_time=now() ";
my ($self) = @_;
my $guid = $self->guid();
my $vers = $self->vers();
if (defined($args) && scalar(keys%{$args})) {
$query .= ",";
$query .= join(",", map("$_='" . $args->{$_} . "'", keys(%{$args})));
}
$query .= " where parent_guid='$guid' and parent_vers='$vers' and ".
" idx='$idx'";
return -1
if (! DBQueryWarn($query));
return 0;
return "[Template: $guid/$vers]";
}
#
# Update an instance record.
# Update a template record given an array reference of slot/value pairs.
#
sub UpdateTemplateInstanceRecord($$$$$)
sub Update($$)
{
my ($guid, $vers, $idx, $start_time, $args) = @_;
my ($self, $argref) = @_;
my $query = "update experiment_template_instances set ";
# Must be a real reference.
return -1
if (! ref($self));
$query .= "start_time=now() "
if ($start_time);
my $guid = $self->guid();
my $vers = $self->vers();
if (defined($args) && scalar(keys%{$args})) {
$query .= ","
if ($start_time);
$query .= join(",", map("$_='" . $args->{$_} . "'", keys(%{$args})));
}
$query .= " where parent_guid='$guid' and parent_vers='$vers' and ".
" idx='$idx'";
my $query = "update experiment_templates set ".
join(",", map("$_='" . $argref->{$_} . "'", keys(%{$argref})));
$query .= " where guid='$guid' and vers='$vers'";
return -1
if (! DBQueryWarn($query));
return 0;
return Refresh($self);
}
#
# Get info about a template run.
# Delete a template (all tables). Note that other parts of the template
# like instances must already be gone when this is called.
#
sub TemplateInstanceInfo($$;$$)
sub Delete($)
{
my ($idx, $prunidx, $peid, $pidx) = @_;
my $query_result =
DBQueryWarn("select eid,runidx,exptidx ".
" from experiment_template_instances ".
"where idx='$idx'");
my ($self) = @_;
# Must be a real reference.
return -1
if (! $query_result);
if (! ref($self));
my ($eid,$runidx,$exptidx) = $query_result->fetchrow_array();
$$prunidx = $runidx;
$$peid = $eid
if (defined($peid));
$$pidx = $exptidx
if (defined($pidx));
return 0;
}
my $guid = $self->guid();
my $vers = $self->vers();
sub TemplateInstanceInfoByExptidx($$;$$)
{
my ($exptidx, $prunidx, $peid, $pidx) = @_;
DeleteMetadata($self) == 0
or return -1;
my $query_result =
DBQueryWarn("select eid,runidx,idx ".
" from experiment_template_instances ".
"where exptidx='$exptidx'");
DeleteInputFiles($self) == 0
or return -1;
# The graph can be removed if this is the last template version.
my $query_result =
DBQueryWarn("select vers from experiment_templates ".
"where guid='$guid' and vers!='$vers'");
return -1
if (! $query_result);
my ($eid,$runidx,$idx) = $query_result->fetchrow_array();
$$prunidx = $runidx;
$$peid = $eid
if (defined($peid));
$$pidx = $idx
if (defined($pidx));
if (! $query_result->numrows) {
DBQueryWarn("delete from experiment_template_graphs ".
"where parent_guid='$guid'")
or return -1;
}
# Make sure the experiment_templates table is always last, in case
# something goes wrong.
my @tables = ("experiment_template_parameters",
"experiment_templates");
foreach my $table (@tables) {
if ($table eq "experiment_templates") {
DBQueryWarn("delete from $table ".
"where guid='$guid' and vers='$vers'")
or return -1;
}
else {
DBQueryWarn("delete from $table ".
"where parent_guid='$guid' and parent_vers='$vers'")
or return -1;
}
}
$self->{'TEMPLATE'} = undef;
return 0;
}
#
# Create a new template instance record from the args provided.
# Template permission checks. Using the experiment access check stuff.
#
# Usage: int NewTemplateInstanceRecord(\%args)
# Usage: AccessCheck($guid, $uid, $access_type)
# returns 0 if not allowed.
# returns 1 if allowed.
#
sub NewTemplateInstanceBindingRecord($)
sub AccessCheck($$$;$)
{
my ($args) = @_;
my ($self, $guid, $uid, $access_type);
my $mintrust;
#
# If called as a method, no guid argument is provided.
#
$self = shift();
my $query = "insert into experiment_template_instance_bindings set ".
join(",", map("$_='" . $args->{$_} . "'", keys(%{$args})));
if (ref($self)) {
($uid, $access_type) = @_;
}
else {
($guid, $uid, $access_type) = @_;
return -1
if (! DBQueryWarn($query));
return 0;
}
$self = Template->Lookup($guid, 1);
return 0
if (! $self);
}
if ($access_type < TB_EXPT_MIN ||
$access_type > TB_EXPT_MAX) {
tbdie("Invalid access type: $access_type!");
}
#
# Delete template instance binding records
#
sub DeleteTemplateInstanceBindingRecords($$$)
{
my ($guid, $vers, $idx) = @_;
#
# Admins do whatever they want!
#
if (TBAdmin($uid)) {
return 1;
}
$uid = MapNumericUID($uid);
DBQueryWarn("delete from experiment_template_instance_bindings ".
"where parent_guid='$guid' and parent_vers='$vers' and ".
" instance_idx='$idx'")
or return -1;
my $pid = $self->pid();
my $gid = $self->gid();
return 0;
#
# An experiment may be destroyed by the experiment creator or the
# project/group leader.
#
if ($access_type == TB_EXPT_READINFO) {
$mintrust = PROJMEMBERTRUST_USER;
}
else {
$mintrust = PROJMEMBERTRUST_LOCALROOT;
}
#
# Either proper permission in the group, or group_root in the project.
# This lets group_roots muck with other people's experiments, including
# those in groups they do not belong to.
#
return TBMinTrust(TBGrpTrust($uid, $pid, $gid), $mintrust) ||
TBMinTrust(TBGrpTrust($uid, $pid, $pid), PROJMEMBERTRUST_GROUPROOT);
}