Commit 3327ba01 authored by Leigh Stoller's avatar Leigh Stoller

* Finish up the Commit From Template support.

* Export the above via the XMLRPC interface and add a wrapper function
  to the script_wrapper. This allows you do to this on ops:

	cd /proj/testbed/templates/10023/1
        Edit some files
        template_commit

  Which creates a new template, using the current directory to infer
  the template. Otherwise, provide the template GUID on the command line.
  Hmm, maybe this should be called template_modify? Either way, the
  name does not quite match

* Export template_export via the XMLRPC wrapper. This allows you to
  export a template (instance) record from the command line on ops.


	cd /proj/testbed/templates/10023/1
        template_export -i 12
        Exported to /proj/testbed/export/10000/3/12

  Which exports the template record for instance number 12. Again, the
  GUID is infered, but you can specify one on the command line. The export
  directory is printed so you know where it went. Note that export does
  *not* populate a DB on ops with the old DB data.
parent 210d1a85
......@@ -28,7 +28,7 @@ use Data::Dumper;
sub usage()
{
print(STDERR
"Usage: template_export [-q] [-s] [-e eid] <guid/vers>\n".
"Usage: template_export [-q] [-e eid] <guid/vers>\n".
"switches and arguments:\n".
"-q - be less chatty\n".
"-e <eid> - Experiment instance to commit from\n".
......@@ -128,7 +128,7 @@ if (! $template->AccessCheck($dbuid, TB_EXPT_MODIFY)) {
#
$SIG{TERM} = \&sighandler;
if ($eid) {
if (defined($eid)) {
CommitFromInstance();
}
else {
......@@ -136,6 +136,33 @@ else {
}
exit(0);
#
# Commit a template. This is basically a template modify operation.
#
sub CommitFromTemplate()
{
my $pid = $template->pid();
my $tid = $template->tid();
my $gid = $template->gid();
my $userdir = $template->path();
my $nsfile = "$userdir/archive/nsdata/nsfile.ns";
my $optarg = ($quiet ? "-q" : "");
#
# The NS file is taken from the template.
#
fatal(1, "There is no NS file in $userdir/archive/nsdata!")
if (! -e $nsfile);
#
# Do a template modify of the current template.
#
system("$modify -m $guid/$version -w $optarg -g $gid $pid $tid $nsfile");
if ($?) {
fatal($? >> 8, "Failed to modify template!");
}
}
#
# Commit from an instance.
#
......@@ -241,10 +268,6 @@ sub ParseArgs()
tbdie("Improper experiment name (id)!");
}
}
else {
tberror("Must provide an experiment ID (-e option)!");
exit(1);
}
if (defined($options{"q"})) {
$quiet = 1;
......
......@@ -148,15 +148,6 @@ if (system("$checkquota $dbuid") != 0) {
# Now parse arguments.
ParseArgs();
#
# In wait mode, block SIGINT until we spin off the background process.
#
if ($waitmode) {
$SIG{QUIT} = 'IGNORE';
$SIG{TERM} = 'IGNORE';
$SIG{INT} = 'IGNORE';
}
#
# Make sure UID is allowed to create experiments in this project.
#
......@@ -187,11 +178,17 @@ else {
if (! defined($description));
}
# Use the logonly option to audit so that we get a record mailed.
LogStart(0);
#
# In wait mode, block SIGINT until we spin off the background process.
#
if ($waitmode) {
$SIG{QUIT} = 'IGNORE';
$SIG{TERM} = 'IGNORE';
$SIG{INT} = 'IGNORE';
}
#
# Create a template record.
# Create a template record now, so we know what it is.
#
my %args = ();
......@@ -216,6 +213,78 @@ if (! ($template = Template->Create(\%args))) {
#
$justexit = 0;
# Grab stuff we need out of the template.
$guid = $template->guid();
$vers = $template->vers();
$eid = $template->eid();
#
# Use the logonly option to audit so that we get a record mailed.
#
if (my $childpid = AuditStart(1, undef, 1)) {
#
# Parent exits normally, unless in waitmode. We have to set
# justexit to make sure the END block below does not run.
#
$justexit = 1;
if (!$waitmode) {
print("Template $pid/$tid is being created.\n".
"You will be notified via email it is ready to use\n")
if (! $quiet);
exit(0);
}
print("Waiting for template $pid/$tid to be created\n")
if (! $quiet);
# Give child a chance to run.
select(undef, undef, undef, 0.25);
#
# Reset signal handlers. User can now kill this process, without
# stopping the child.
#
$SIG{TERM} = 'DEFAULT';
$SIG{QUIT} = 'DEFAULT';
$SIG{INT} = 'DEFAULT';
#
# Wait until child exits or until user gets bored and types ^C.
#
waitpid($childpid, 0);
my $exit_code = $? >> 8;
print("Done. Exited with status: $?\n")
if (! $quiet);
if ($exit_code == 0) {
# Web interface depends on this line. Bad; need another way to send
# back the newly generated guid/version.
print "Template $guid/$vers has been created\n";
}
else {
my $d = tblog_lookup_error();
print tblog_format_error($d);
}
exit $exit_code;
}
#
# We need to catch TERM so we can kill the children and do a cleanup.
#
sub handler ($) {
my ($signame) = @_;
$SIG{TERM} = 'IGNORE';
my $pgrp = getpgrp(0);
kill('TERM', -$pgrp);
sleep(1);
fatal(-1, "Caught SIG${signame}! Killing template setup ...");
}
$SIG{TERM} = \&handler;
$SIG{QUIT} = 'DEFAULT';
#
# The template gets its own directory structure.
#
......@@ -236,11 +305,6 @@ $template->NewMetadata("description", $description,
$template->NewMetadata("TID", $tid, $dbuid, "tid") == 0
or fatal(-1, "Failed to insert metadata record for description");
# Grab stuff we need out of the template.
$guid = $template->guid();
$vers = $template->vers();
$eid = $template->eid();
# Now invoke batchexp to preload the experiment. Note special -x option.
system("$batchexp ".
"-x " . ($modify ? $parent_template->eid() : "-") . " " .
......@@ -353,10 +417,6 @@ if ($modify) {
or fatal(-1, "Could not update parent template record!");
}
# Web interface depends on this line. Bad; need another way to send
# back the newly generated guid/version.
LogEnd();
print "Template $guid/$vers has been created\n";
exit(0);
#
......
......@@ -287,6 +287,9 @@ if ($spew) {
system("$TAR zcf - -C $checkout .");
cleanup();
}
else {
print "$checkout\n";
}
exit(0);
#
......
......@@ -31,7 +31,8 @@ USERLIBS = sshxmlrpc.py emulabclient.py libxmlrpc.pm
SYMLINKS = node_admin node_reboot os_load create_image node_list \
delay_config link_config savelogs portstats eventsys_control \
readycount nscheck startexp batchexp startexp swapexp endexp \
modexp expinfo node_avail tbuisp expwait
modexp expinfo node_avail tbuisp expwait template_commit \
template_export
#
# Force dependencies on the scripts so that they will be rerun through
......
......@@ -4496,6 +4496,97 @@ class template:
return EmulabResponse(RESPONSE_ERROR, exitval >> 8, output=output)
return EmulabResponse(RESPONSE_SUCCESS, output=output)
#
# Commit (modify) a template.
#
def template_commit(self, version, argdict):
if version != self.VERSION:
return EmulabResponse(RESPONSE_BADVERSION,
output="Client version mismatch!")
if self.readonly:
return EmulabResponse(RESPONSE_FORBIDDEN,
output="Insufficient privledge to invoke method")
try:
checknologins()
pass
except NoLoginsError, e:
return EmulabResponse(RESPONSE_REFUSED, output=str(e))
argerror = CheckRequiredArgs(argdict, ("guid",))
if (argerror):
return argerror
if not (re.match("^[-\w\/]*$", argdict["guid"])):
return EmulabResponse(RESPONSE_BADARGS,
output="Improperly formed arguments")
argstr = "-q"
for opt, val in argdict.items():
if opt == "exp":
argstr += " -e "
argstr += escapeshellarg(val)
pass
pass
argstr += " "
argstr += str(argdict["guid"])
(exitval, output) = runcommand(TBDIR +
"/bin/template_commit " + argstr)
if exitval:
return EmulabResponse(RESPONSE_ERROR, exitval >> 8, output=output)
return EmulabResponse(RESPONSE_SUCCESS, output=output)
#
# Export a template.
#
def export(self, version, argdict):
if version != self.VERSION:
return EmulabResponse(RESPONSE_BADVERSION,
output="Client version mismatch!")
if self.readonly:
return EmulabResponse(RESPONSE_FORBIDDEN,
output="Insufficient privledge to invoke method")
try:
checknologins()
pass
except NoLoginsError, e:
return EmulabResponse(RESPONSE_REFUSED, output=str(e))
argerror = CheckRequiredArgs(argdict, ("guid", "instance"))
if (argerror):
return argerror
if not (re.match("^[-\w\/]*$", argdict["guid"]) and
re.match("^[-\w]*$", argdict["instance"])):
return EmulabResponse(RESPONSE_BADARGS,
output="Improperly formed arguments")
argstr = "-q"
for opt, val in argdict.items():
if opt == "instance":
argstr += " -i "
argstr += escapeshellarg(val)
pass
pass
argstr += " "
argstr += str(argdict["guid"])
(exitval, output) = runcommand(TBDIR +
"/bin/template_export " + argstr)
if exitval:
return EmulabResponse(RESPONSE_ERROR, exitval >> 8, output=output)
return EmulabResponse(RESPONSE_SUCCESS, output=output)
pass
#
......
......@@ -127,6 +127,10 @@ API = {
"help" : "Wait for experiment to reach a state" },
"tipacl" : { "func" : "tipacl",
"help" : "Get console acl" },
"template_commit" : { "func" : "template_commit",
"help" : "Commit changes to template (modify)" },
"template_export" : { "func" : "template_export",
"help" : "Export template record" },
};
#
......@@ -1749,6 +1753,146 @@ class tipacl:
return
pass
#
# template_commit
#
class template_commit:
def __init__(self, argv=None):
self.argv = argv;
return
def apply(self):
try:
opts, req_args = getopt.getopt(self.argv, "we:", [ "help" ]);
pass
except getopt.error, e:
print e.args[0]
self.usage();
return -1;
guid = None
params = {}
for opt, val in opts:
if opt in ("-h", "--help"):
self.usage()
return 0
elif opt == "-w":
params["wait"] = "yes"
pass
elif opt == "-e":
params["exp"] = val
pass
pass
# Try to infer the template guid/vers from the current path.
if len(req_args) == 0:
guid = infer_guid(os.getcwd())
pass
elif len(req_args) == 1:
guid = req_args[0]
pass
if guid == None:
self.usage();
return -1
params["guid"] = guid
rval,response = do_method("template", "template_commit", params);
return rval;
def usage(self):
print "template_commit [<guid/vers>]";
print "where:";
print " -w - Wait for template to finish commit";
print " -e - Commit from specific template instance (eid)";
print " guid/vers - Commit template";
print ""
print "By default, commit runs in the background, sending you email ";
print "when the operation has completed. Use the -w option to wait";
print "in the foreground, returning exit status. Email is still sent.";
print ""
print "Environment:"
print " cwd The template GUID will be inferred from the current"
print " working directory, if it is inside the templates's"
print " directory (e.g. /proj/foo/templates/10005/18)."
wrapperoptions();
return
pass
class template_export:
def __init__(self, argv=None):
self.argv = argv;
return
def apply(self):
try:
opts, req_args = getopt.getopt(self.argv, "i:", [ "help" ]);
pass
except getopt.error, e:
print e.args[0]
self.usage();
return -1;
guid = None
params = {}
for opt, val in opts:
if opt in ("-h", "--help"):
self.usage()
return 0
elif opt == "-i":
params["instance"] = val
pass
pass
# Try to infer the template guid/vers from the current path.
if len(req_args) == 0:
guid = infer_guid(os.getcwd())
pass
elif len(req_args) == 1:
guid = req_args[0]
pass
if guid == None:
self.usage();
return -1
params["guid"] = guid
rval,response = do_method("template", "export", params);
return rval;
def usage(self):
print "template_export -i instance_id [<guid/vers>]";
print "where:";
print " -i - Export specific template instance (idx)";
print " guid/vers - Commit template";
print ""
print "Environment:"
print " cwd The template GUID will be inferred from the current"
print " working directory, if it is inside the templates's"
print " directory (e.g. /proj/foo/templates/10005/18)."
wrapperoptions();
return
pass
#
# Infer template guid from path
#
def infer_guid(path):
guid = None
vers = None
dirs = path.split(os.path.sep)
if ((len(dirs) < 6) or
(not (("proj" in dirs) and ("templates" in dirs))) or
(len(dirs) < (dirs.index("templates") + 2))):
return None
else:
guid = dirs[dirs.index("templates") + 1]
vers = dirs[dirs.index("templates") + 2]
pass
return guid + "/" + vers
#
# Process program arguments. There are two ways we could be invoked.
# 1) as the wrapper, with the first required argument the name of the script.
......
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