Commit bcbc8471 authored by Leigh Stoller's avatar Leigh Stoller

First crack at a command line interface for starting Portal

experiments. Plumb through (with terminate and status) the classic
XMLRPC server so it can be run on ops or nodes. Work in progress.
parent 46c84953
......@@ -34,7 +34,7 @@ BIN_SCRIPTS = manage_profile manage_instance manage_dataset \
create_instance rungenilib ns2rspec nsgenilib.py \
rspec2genilib ns2genilib manage_reservations manage_gitrepo \
manage_images rtecheck checkprofile manage_extensions \
create_slivers searchip
create_slivers searchip start-experiment
SBIN_SCRIPTS = apt_daemon aptevent_daemon portal_xmlrpc apt_checkup \
portal_monitor apt_scheduler portal_resources \
manage_licenses manage_aggregate powder_shutdown \
......
......@@ -45,9 +45,10 @@ sub usage()
print "Usage: quickvm [-u uuid] [--site site:1=aggregate ...] <xmlfile>\n";
exit(1);
}
my @optlist = ('d', 'v', 'u=s', 'a=s', 'S', 'k=s', 'i', 't=s');
my @optlist = ('d', 'v', 'f', 'u=s', 'a=s', 'S', 'k=s', 'i', 't=s');
my $debug = 0;
my $verbose = 1;
my $foreground = 0;
my $ignorefailures = 0;
my $xmlfile;
my $webtask;
......@@ -156,6 +157,9 @@ if (defined($options{"k"})) {
if (defined($options{"d"})) {
$debug = 1;
}
if (defined($options{"f"})) {
$foreground = 1;
}
if (defined($options{"i"})) {
$ignorefailures = 1;
}
......@@ -743,7 +747,7 @@ if ($tmp) {
if (defined($webtask)) {
$webtask->required_licenses($licenses);
}
UserError("Licenses are required");
UserError("Licenses are required before you can start this experiment");
}
UserError($errmsg);
}
......@@ -1006,7 +1010,7 @@ if (defined($start_at)) {
# Hand off to create slivers script. We have to fork/system cause
# of libaudit logging.
#
if (!$debug) {
if (! ($debug || $foreground)) {
libaudit::AuditPrefork();
my $child = fork();
if ($child) {
......@@ -1029,7 +1033,7 @@ if ($?) {
#
if (!$usestitcher && $code > 0) {
AuditAbort()
if (!$debug);
if (! ($debug || $foreground));
}
exit($code);
}
......
......@@ -64,6 +64,7 @@ sub usage()
print("Usage: manage_instance openstackstats instance\n");
print("Usage: manage_instance getmanifests instance\n");
print("Usage: manage_instance warn instance\n");
print("Usage: manage_instance status instance\n");
print("Usage: manage_instance applyextensionpolicy instance\n");
exit(-1);
}
......@@ -159,6 +160,7 @@ sub StartMonitorInternal(;$);
sub DoImageTrackerStuff($$$$$$$$);
sub DoWarn();
sub DoDelete();
sub DoStatus();
sub DenyExtensionInternal($);
sub ExtendInternal($$$$$);
sub CallMethodOnAggregates($$$@);
......@@ -301,6 +303,9 @@ elsif ($action eq "applyextensionpolicy") {
elsif ($action eq "delete") {
DoDelete()
}
elsif ($action eq "status") {
DoStatus()
}
else {
usage();
}
......@@ -4947,6 +4952,17 @@ sub DoApplyExtensionPolicy()
print "Extensions are now $disabled\n";
}
#
# Display instance status.
#
sub DoStatus()
{
my $status = $instance->status();
print "Status: $status\n";
return 0;
}
#
# Write instance credentials to files.
#
......
This diff is collapsed.
......@@ -179,7 +179,13 @@ API = {
"extenddataset" : { "func" : "extenddataset",
"help" : "Extend the lease on a persistent dataset" },
"showdataset" : { "func" : "showdataset",
"help" : "Show persistent datasets" }
"help" : "Show persistent datasets" },
"startExperiment" : { "func" : "startExperiment",
"help" : "Start a Portal experiment" },
"terminateExperiment" : { "func" : "terminateExperiment",
"help" : "Terminate a Portal experiment" },
"experimentStatus" : { "func" : "experimentStatus",
"help" : "Get status for a Portal experiment" },
};
#
......@@ -2967,6 +2973,172 @@ class showdataset:
return
pass
#
# start a portal experiment
#
class startExperiment:
def __init__(self, argv=None):
self.argv = argv;
return
def apply(self):
try:
opts, req_args = getopt.getopt(self.argv, "a:p:",
[ "help", "name=", "duration=",
"project=", "site=", "start=",
"stop=", "paramset=", "refspec="]);
pass
except getopt.error, e:
print e.args[0]
self.usage();
return -1;
params = {};
for opt, val in opts:
if opt in ("-h", "--help"):
self.usage()
return 0
elif opt == "-a":
params["aggregate"] = val
pass
elif opt == "--name":
params["name"] = val;
pass
elif opt == "--duration":
params["duration"] = val;
pass
if opt in ("-p", "--project"):
params["proj"] = val;
pass
elif opt == "--start":
params["start"] = val;
pass
elif opt == "--stop":
params["stop"] = val;
pass
elif opt == "--paramset":
params["paramset"] = val;
pass
elif opt == "--refspec":
params["refspec"] = val;
pass
elif opt == "--site":
params["site"] = val;
pass
pass
# Do this after so --help is seen.
if len(req_args) != 1:
self.usage();
return -1;
params["profile"] = req_args[0]
rval,response = do_method("portal", "startExperiment", params)
return rval
def usage(self):
print "Usage: startExperiment <optons> ",
"[--site 'site:1=aggregate ...'] <profile>"
print "where:"
print " -d - Turn on debugging (run in foreground)"
print " -w - Wait mode (wait for experiment to start)"
print " -a urn - Override default aggregate URN"
print " --project - pid[,gid]: project[,group] for new experiment"
print " --project - pid[,gid]: project[,group] for new experiment"
print " --name - Optional pithy name for experiment"
print " --duration - Number of hours for initial expiration"
print " --start - Schedule experiment to start at (unix) time"
print " --stop - Schedule experiment to stop at (unix) time"
print " --paramset - uid,name of a parameter set to apply"
print " --refspec - refspec[:hash] of a repo based profile to use"
print " --site - Bind sites used in the profile"
print "profile - Either UUID or pid,name"
wrapperoptions();
return
pass
#
# Terminate a portal experiment
#
class terminateExperiment:
def __init__(self, argv=None):
self.argv = argv;
return
def apply(self):
try:
opts, req_args = getopt.getopt(self.argv, "h", [ "help"]);
pass
except getopt.error, e:
print e.args[0]
self.usage();
return -1;
params = {};
for opt, val in opts:
if opt in ("-h", "--help"):
self.usage()
return 0
pass
# Do this after so --help is seen.
if len(req_args) != 1:
self.usage();
return -1;
params["experiment"] = req_args[0]
rval,response = do_method("portal", "terminateExperiment", params)
return rval
def usage(self):
print "Usage: terminateExperiment <optons> <experiment>"
print "where:"
print "experiment - Either UUID or pid,name"
wrapperoptions();
return
pass
#
# Get status for a portal experiment
#
class experimentStatus:
def __init__(self, argv=None):
self.argv = argv;
return
def apply(self):
try:
opts, req_args = getopt.getopt(self.argv, "h", [ "help"]);
pass
except getopt.error, e:
print e.args[0]
self.usage();
return -1;
params = {};
for opt, val in opts:
if opt in ("-h", "--help"):
self.usage()
return 0
pass
# Do this after so --help is seen.
if len(req_args) != 1:
self.usage();
return -1;
params["experiment"] = req_args[0]
rval,response = do_method("portal", "experimentStatus", params)
return rval
def usage(self):
print "Usage: terminateExperiment <optons> <experiment>"
print "where:"
print "experiment - Either UUID or pid,name"
wrapperoptions();
return
pass
#
# Infer template guid from path
#
......
#!/usr/bin/perl -wT
#
# Copyright (c) 2005-2018 University of Utah and the Flux Group.
# Copyright (c) 2005-2019 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -83,6 +83,17 @@ sub isPowder($) { return $_[0]->brand() eq "powder" ? 1 : 0; }
sub isEmulab($) { return $_[0]->brand() eq "emulab" ? 1 : 0; }
sub isClassic($) { return $_[0]->brand() eq "classic" ? 1 : 0; }
sub Server($)
{
my ($self) = @_;
return ($self->isAPT() ? "www.aptlab.net" :
$self->isCloud() ? "www.cloudlab.us" :
$self->isPNet() ? "www.phantomnet.org" :
$self->isPowder() ? "www.powderwireless.net" :
$self->isEmulab() ? "@WWWHOST@" : "@WWWHOST@");
}
sub wwwBase($)
{
my ($self) = @_;
......
#! /usr/bin/env python
#
# Copyright (c) 2004-2017 University of Utah and the Flux Group.
# Copyright (c) 2004-2017, 2019 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -345,6 +345,7 @@ class EmulabServer:
self.instances["subboss"] = subboss(self);
self.instances["blob"] = blob(self);
self.instances["dataset"] = dataset(self);
self.instances["portal"] = portal(self);
return
def __getattr__(self, name):
......@@ -5982,6 +5983,199 @@ class dataset:
else:
return EmulabResponse( RESPONSE_SUCCESS, value=0, output=output )
#
# This class implements the server side of the XMLRPC interface to the Portal
#
class portal:
##
# Initialize the object. Currently only sets the objects 'VERSION' value.
#
def __init__(self, server):
self.server = server
self.readonly = server.readonly
self.uid = server.uid
self.uid_idx = server.uid_idx
self.debug = server.debug
self.VERSION = VERSION
return
##
# Echo a message, basically, prepend the host name to the parameter list.
#
# @param args The argument list to echo back.
# @return The 'msg' value with this machine's name prepended.
#
def echo(self, version, argdict):
if not argdict.has_key("str"):
return EmulabResponse(RESPONSE_BADARGS,
output="Must supply a string to echo!")
return EmulabResponse(RESPONSE_SUCCESS, 0,
socket.gethostname() + ": " + str(version)
+ " " + argdict["str"])
pass
#
# Start a portal experiment.
#
def startExperiment(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, ("proj", "profile"))
if (argerror):
return argerror
argstr = ""
profile = None
for opt, val in argdict.items():
if opt == "name":
argstr += " --name "
argstr += escapeshellarg(val)
pass
elif opt == "proj":
argstr += " --project "
argstr += escapeshellarg(val)
pass
elif opt == "duration":
argstr += " --duration "
argstr += escapeshellarg(val)
pass
elif opt == "start":
argstr += " --start "
argstr += escapeshellarg(val)
pass
elif opt == "stop":
argstr += " --stop "
argstr += escapeshellarg(val)
pass
elif opt == "paramset":
argstr += " --paramset "
argstr += escapeshellarg(val)
pass
elif opt == "refspec":
argstr += " --refspec "
argstr += escapeshellarg(val)
pass
elif opt == "aggregate":
argstr += " -a "
argstr += escapeshellarg(val)
pass
elif opt == "profile":
profile = val
pass
pass
argstr += " "
argstr += escapeshellarg(profile)
(exitval, output) = runcommand(TBDIR +
"/bin/start-experiment " + argstr)
if exitval:
return EmulabResponse(RESPONSE_ERROR, exitval >> 8, output=output)
return EmulabResponse(RESPONSE_SUCCESS, output=output)
#
# Terminate a portal experiment.
#
def terminateExperiment(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, ("experiment",))
if (argerror):
return argerror
argstr = "terminate "
experiment = None
for opt, val in argdict.items():
if opt == "experiment":
experiment = val
pass
pass
argstr += escapeshellarg(experiment)
(exitval, output) = runcommand(TBDIR +
"/bin/manage_instance " + argstr)
if exitval:
return EmulabResponse(RESPONSE_ERROR, exitval >> 8, output=output)
return EmulabResponse(RESPONSE_SUCCESS, output=output)
#
# Portal experiment status
#
def experimentStatus(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, ("experiment",))
if (argerror):
return argerror
argstr = "status "
experiment = None
for opt, val in argdict.items():
if opt == "experiment":
experiment = val
pass
pass
argstr += escapeshellarg(experiment)
# Slow down the polling.
time.sleep(3)
(exitval, output) = runcommand(TBDIR +
"/bin/manage_instance " + argstr)
if exitval:
return EmulabResponse(RESPONSE_ERROR, exitval >> 8, output=output)
return EmulabResponse(RESPONSE_SUCCESS, output=output)
#
# Utility functions
#
......
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