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

First version that people can probably play with safely, I think.

parent d8fad8b2
......@@ -22,7 +22,7 @@ include Makeconf
#
SUBDIRS = lib db assign @optional_subdirs@ ipod security sensors \
pxe tbsetup account tmcd utils www tip capture ipod vis \
sensors os install/newnode_sshkeys
sensors os xmlrpc install/newnode_sshkeys
all: all-subdirs
......@@ -64,6 +64,7 @@ ops-install:
@$(MAKE) -C tbsetup control-install
@$(MAKE) -C utils control-install
@$(MAKE) -C event control-install
@$(MAKE) -C xmlrpc control-install
install-mkdirs:
-mkdir -p $(INSTALL_TOPDIR)/opsdir
......
......@@ -1462,7 +1462,9 @@ outfiles="$outfiles Makeconf GNUmakefile \
$eventfiles \
$winfiles \
apache/GNUmakefile apache/httpd.conf \
xmlrpc/GNUmakefile \
xmlrpc/GNUmakefile xmlrpc/emulabclient.py xmlrpc/emulabserver.py \
xmlrpc/sshxmlrpc_client.py xmlrpc/sshxmlrpc_server.py \
xmlrpc/webxmlrpc \
install/ops-install install/boss-install \
install/newnode_sshkeys/GNUmakefile "
......
......@@ -507,7 +507,9 @@ outfiles="$outfiles Makeconf GNUmakefile \
$eventfiles \
$winfiles \
apache/GNUmakefile apache/httpd.conf \
xmlrpc/GNUmakefile \
xmlrpc/GNUmakefile xmlrpc/emulabclient.py xmlrpc/emulabserver.py \
xmlrpc/sshxmlrpc_client.py xmlrpc/sshxmlrpc_server.py \
xmlrpc/webxmlrpc \
install/ops-install install/boss-install \
install/newnode_sshkeys/GNUmakefile "
......
......@@ -17,24 +17,30 @@ SBIN_SCRIPTS = sshxmlrpc_server.py
LIB_STUFF = sshxmlrpc.py emulabserver.py emulabclient.py
LIBEXEC_STUFF = webxmlrpc
WWW_STUFF = xmlrpcapi.php3
DOWNLOAD_STUFF = sshxmlrpc.py sshxmlrpc_client.py emulabclient.py
DEBUG_STUFF = experiment
#
# These are the ones installed on plastic (users, control, etc).
#
USERBINS = sshxmlrpc_client.py
CLIENTBIN = sshxmlrpc_client.py
USERLIBS = sshxmlrpc.py emulabclient.py
#
# Force dependencies on the scripts so that they will be rerun through
# configure if the .in file is changed.
#
all: $(BIN_SCRIPTS) $(SBIN_SCRIPTS) $(LIB_STUFF) $(LIBEXEC_STUFF)
all: $(BIN_SCRIPTS) $(SBIN_SCRIPTS) $(LIB_STUFF) $(LIBEXEC_STUFF) \
$(DOWNLOAD_STUFF) $(DEBUG_STUFF)
include $(TESTBED_SRCDIR)/GNUmakerules
install: $(addprefix $(INSTALL_BINDIR)/, $(BIN_SCRIPTS)) \
$(addprefix $(INSTALL_SBINDIR)/, $(SBIN_SCRIPTS)) \
$(addprefix $(INSTALL_SBINDIR)/xmlrpc/, $(DEBUG_STUFF)) \
$(addprefix $(INSTALL_LIBEXECDIR)/, $(LIBEXEC_STUFF)) \
$(addprefix $(INSTALL_WWWDIR)/, $(WWW_STUFF)) \
$(addprefix $(INSTALL_WWWDIR)/downloads/xmlrpc/, $(DOWNLOAD_STUFF)) \
$(addprefix $(INSTALL_LIBDIR)/, $(LIB_STUFF))
$(INSTALL_WWWDIR)/%: %
......@@ -42,19 +48,20 @@ $(INSTALL_WWWDIR)/%: %
-mkdir -p $(patsubst %/,%,$(dir $@))
$(INSTALL_DATA) $(subst $$,\$$,$<) $(subst $$,\$$,$@)
$(INSTALL_WWWDIR)/downloads/xmlrpc/%: %
@echo "Installing $<"
-mkdir -p $(patsubst %/,%,$(dir $@))
$(INSTALL_DATA) $(subst $$,\$$,$<) $(subst $$,\$$,$@)
$(INSTALL_SBINDIR)/xmlrpc/%: %
@echo "Installing $<"
-mkdir -p $(INSTALL_SBINDIR)/xmlrpc
$(INSTALL_PROGRAM) $< $@
#
# Control node installation (okay, plastic)
#
ifneq ($(UNIFIED),1)
LINKS= cd $(INSTALL_BINDIR) && \
list='$(USERBINS)'; for file in $$list; do \
rm -f $$file; \
ln -s plasticwrap $$file; \
done;
endif
control-install:
$(LINKS)
control-install: $(addprefix $(INSTALL_LIBDIR)/, $(USERLIBS)) \
$(addprefix $(INSTALL_BINDIR)/, $(CLIENTBIN))
clean:
......@@ -15,11 +15,17 @@ MAXNSFILESIZE = (1024 * 512)
#
# This class defines a simple structure to return back to the caller.
# It includes the exit status of the command, and any output that it
# wants to send back. Why not return a tuple? Well, it appears that the
# python xmlrpc library requires that a singleton be returned from the
# server, and I do not want to depend on the "Fault" structure that XML
# defines for return values.
# It includes a basic response code (success, failure, badargs, etc),
# as well as a return "value" which can be any valid datatype that can
# be represented in XML (int, string, hash, float, etc). You can also
# send back some output (a string with embedded newlines) to print out
# to the user.
#
# Why not return a tuple? Well, it appears that the python xmlrpc
# library requires that a singleton be returned from the server.
#
# Note that XMLRPC does not actually return a "class" to the caller; It gets
# converted to a hashed array (Python Dictionary).
#
RESPONSE_SUCCESS = 0
RESPONSE_BADARGS = 1
......@@ -29,11 +35,11 @@ RESPONSE_BADVERSION = 4
RESPONSE_SERVERERROR = 5
RESPONSE_TOOBIG = 6
class ResponseBlock:
class EmulabResponse:
def __init__(self, code, value=0, output=None):
self.code = code
self.value = value
self.output = output
self.code = code # A RESPONSE code
self.value = value # A return value; any valid XML type.
self.output = output # Pithy output to print
return
#
......
......@@ -41,10 +41,10 @@ class emulabserver:
#
def echo(self, version, argdict):
if not argdict.has_key("str"):
return ResponseBlock(RESPONSE_BADARGS,
return EmulabResponse(RESPONSE_BADARGS,
output="Must supply a string to echo!");
return ResponseBlock(RESPONSE_SUCCESS, 0,
return EmulabResponse(RESPONSE_SUCCESS, 0,
socket.gethostname() + ": " + str(version)
+ " " + argdict["str"])
......@@ -54,21 +54,21 @@ class emulabserver:
#
def batchexp(self, version, argdict):
if version != self.VERSION:
return ResponseBlock(RESPONSE_BADVERSION,
return EmulabResponse(RESPONSE_BADVERSION,
output="Client version mismatch!");
pass
if not (argdict.has_key("pid") and
argdict.has_key("eid")):
return ResponseBlock(RESPONSE_BADARGS,
return EmulabResponse(RESPONSE_BADARGS,
output="Must supply pid and eid!");
nsfilename = None
argstr = ""
for opt, val in argdict.items():
if opt in ("batchmode"):
if val == 0:
if opt in ("batch"):
if val == "false":
argstr += " -i"
pass
pass
......@@ -88,8 +88,8 @@ class emulabserver:
argstr += " -p "
argstr += escapeshellarg(val)
pass
elif opt in ("idleswap"):
if opt == 0:
elif opt in ("swappable"):
if val == "false":
argstr += " -S "
argstr += argdict["noswap_reason"]
pass
......@@ -97,7 +97,7 @@ class emulabserver:
elif opt in ("noswap_reason"):
pass
elif opt in ("idleswap"):
if opt == 0:
if val == 0:
argstr += " -L "
argstr += escapeshellarg(argdict["noidleswap_reason"])
pass
......@@ -112,11 +112,15 @@ class emulabserver:
argstr += " -a "
argstr += escapeshellarg(val)
pass
elif opt in ("frontend"):
argstr += " -f "
elif opt in ("noswapin"):
if val == "true":
argstr += " -f "
pass
pass
elif opt in ("waitmode"):
argstr += " -w "
elif opt in ("wait"):
if val == "true":
argstr += " -w "
pass
pass
elif opt in ("nsfilepath"):
# Backend script will verify this local path.
......@@ -126,12 +130,12 @@ class emulabserver:
nsfilestr = val
if len(nsfilestr) > (1024 * 512):
return ResponseBlock(RESPONSE_TOOBIG,
return EmulabResponse(RESPONSE_TOOBIG,
output="NS File way too big!");
(nsfp, nsfilename) = writensfile(nsfilestr)
if not nsfilename:
return ResponseBlock(RESPONSE_SERVERERROR,
return EmulabResponse(RESPONSE_SERVERERROR,
output="Server Error")
pass
pass
......@@ -142,9 +146,9 @@ class emulabserver:
(exitval, output) = runcommand(TBDIR + "/bin/batchexp " + argstr)
if exitval:
return ResponseBlock(RESPONSE_ERROR, exitval >> 8, output=output)
return EmulabResponse(RESPONSE_ERROR, exitval >> 8, output=output)
return ResponseBlock(RESPONSE_SUCCESS, output=output)
return EmulabResponse(RESPONSE_SUCCESS, output=output)
#
# startexp is an alias for batchexp.
......@@ -152,13 +156,48 @@ class emulabserver:
def startexp(self, version, argdict):
return self.batchexp(version, argdict)
#
# swap an experiment using swapexp.
#
def swapexp(self, version, argdict):
if version != self.VERSION:
return EmulabResponse(RESPONSE_BADVERSION,
output="Client version mismatch!");
pass
argstr = ""
for opt, val in argdict.items():
if opt in ("wait"):
if val == "true":
argstr += " -w "
pass
pass
pass
if not (argdict.has_key("pid") and
argdict.has_key("eid") and
argdict.has_key("direction")):
return EmulabResponse(RESPONSE_BADARGS,
output="Must supply pid, eid and direction!");
argstr += " -s " + escapeshellarg(argdict["direction"])
argstr += " " + escapeshellarg(argdict["pid"])
argstr += " " + escapeshellarg(argdict["eid"])
(exitval, output) = runcommand(TBDIR + "/bin/swapexp " + argstr)
if exitval:
return EmulabResponse(RESPONSE_ERROR, exitval >> 8, output=output)
return EmulabResponse(RESPONSE_SUCCESS, output=output)
#
# swap an experiment using swapexp. We get the NS file inline, which
# we have to write to a temp file first.
#
def swapexp(self, version, argdict):
def modify(self, version, argdict):
if version != self.VERSION:
return ResponseBlock(RESPONSE_BADVERSION,
return EmulabResponse(RESPONSE_BADVERSION,
output="Client version mismatch!");
pass
......@@ -166,18 +205,20 @@ class emulabserver:
argstr = ""
for opt, val in argdict.items():
if opt in ("waitmode"):
argstr += " -w "
if opt in ("wait"):
if val == "true":
argstr += " -w "
pass
pass
elif opt in ("reboot_nodes"):
argstr += " -r "
elif opt in ("reboot"):
if val == "true":
argstr += " -r "
pass
pass
elif opt in ("restart_eventsys"):
argstr += " -e "
pass
elif opt in ("swapop"):
argstr += " -s "
argstr += escapeshellarg(val)
if val == "true":
argstr += " -e "
pass
pass
elif opt in ("nsfilepath"):
# Backend script will verify this local path.
......@@ -187,21 +228,22 @@ class emulabserver:
nsfilestr = val
if len(nsfilestr) > (1024 * 512):
return ResponseBlock(RESPONSE_TOOBIG,
return EmulabResponse(RESPONSE_TOOBIG,
output="NS File way too big!");
(nsfp, nsfilename) = writensfile(nsfilestr)
if not nsfilename:
return ResponseBlock(RESPONSE_SERVERERROR,
return EmulabResponse(RESPONSE_SERVERERROR,
output="Server Error")
pass
pass
if not (argdict.has_key("pid") and
argdict.has_key("eid")):
return ResponseBlock(RESPONSE_BADARGS,
return EmulabResponse(RESPONSE_BADARGS,
output="Must supply pid and eid!");
argstr += " -s modify"
argstr += " " + escapeshellarg(argdict["pid"])
argstr += " " + escapeshellarg(argdict["eid"])
......@@ -211,30 +253,32 @@ class emulabserver:
(exitval, output) = runcommand(TBDIR + "/bin/swapexp " + argstr)
if exitval:
return ResponseBlock(RESPONSE_ERROR, exitval >> 8, output=output)
return EmulabResponse(RESPONSE_ERROR, exitval >> 8, output=output)
return ResponseBlock(RESPONSE_SUCCESS, output=output)
return EmulabResponse(RESPONSE_SUCCESS, output=output)
#
# end an experiment using endexp.
#
def endexp(self, version, argdict):
if version != self.VERSION:
return ResponseBlock(RESPONSE_BADVERSION,
return EmulabResponse(RESPONSE_BADVERSION,
output="Client version mismatch!");
pass
argstr = ""
for opt, val in argdict.items():
if opt in ("waitmode"):
argstr += " -w "
if opt in ("wait"):
if val == "true":
argstr += " -w "
pass
pass
pass
if not (argdict.has_key("pid") and
argdict.has_key("eid")):
return ResponseBlock(RESPONSE_BADARGS,
return EmulabResponse(RESPONSE_BADARGS,
output="Must supply pid and eid!");
argstr += " " + escapeshellarg(argdict["pid"])
......@@ -242,16 +286,16 @@ class emulabserver:
(exitval, output) = runcommand(TBDIR + "/bin/endexp " + argstr)
if exitval:
return ResponseBlock(RESPONSE_ERROR, exitval >> 8, output=output)
return EmulabResponse(RESPONSE_ERROR, exitval >> 8, output=output)
return ResponseBlock(RESPONSE_SUCCESS, output=output)
return EmulabResponse(RESPONSE_SUCCESS, output=output)
#
# nscheck an NS file.
#
def nscheck(self, version, argdict):
if version != self.VERSION:
return ResponseBlock(RESPONSE_BADVERSION,
return EmulabResponse(RESPONSE_BADVERSION,
output="Client version mismatch!");
pass
......@@ -261,12 +305,12 @@ class emulabserver:
nsfilestr = argdict["nsfilestr"]
if len(nsfilestr) > (1024 * 512):
return ResponseBlock(RESPONSE_TOOBIG,
return EmulabResponse(RESPONSE_TOOBIG,
output="NS File way too big!");
(nsfp, nsfilename) = writensfile(nsfilestr)
if not nsfilename:
return ResponseBlock(RESPONSE_SERVERERROR, output="Server Error")
return EmulabResponse(RESPONSE_SERVERERROR, output="Server Error")
argstr += nsfilename
pass
......@@ -275,21 +319,21 @@ class emulabserver:
argstr += escapeshellarg(argdict["nsfilepath"])
pass
else:
return ResponseBlock(RESPONSE_BADARGS,
return EmulabResponse(RESPONSE_BADARGS,
output="Must supply an NS file to check!");
(exitval, output) = runcommand(TBDIR + "/bin/nscheck " + argstr)
if exitval:
return ResponseBlock(RESPONSE_ERROR, exitval >> 8, output=output)
return EmulabResponse(RESPONSE_ERROR, exitval >> 8, output=output)
return ResponseBlock(RESPONSE_SUCCESS, output=output)
return EmulabResponse(RESPONSE_SUCCESS, output=output)
#
# create_image.
#
def create_image(self, version, argdict):
if version != self.VERSION:
return ResponseBlock(RESPONSE_BADVERSION,
return EmulabResponse(RESPONSE_BADVERSION,
output="Client version mismatch!");
pass
......@@ -304,7 +348,7 @@ class emulabserver:
if not (argdict.has_key("imageid") and
argdict.has_key("nodeid")):
return ResponseBlock(RESPONSE_BADARGS,
return EmulabResponse(RESPONSE_BADARGS,
output="Must supply imageid and nodeid!");
argstr += " " + escapeshellarg(argdict["imageid"])
......@@ -312,16 +356,16 @@ class emulabserver:
(exitval, output) = runcommand(TBDIR + "/bin/create_image " + argstr)
if exitval:
return ResponseBlock(RESPONSE_ERROR, exitval >> 8, output=output)
return EmulabResponse(RESPONSE_ERROR, exitval >> 8, output=output)
return ResponseBlock(RESPONSE_SUCCESS, output=output)
return EmulabResponse(RESPONSE_SUCCESS, output=output)
#
# create_image.
#
def delay_config(self, version, argdict):
if version != self.VERSION:
return ResponseBlock(RESPONSE_BADVERSION,
return EmulabResponse(RESPONSE_BADVERSION,
output="Client version mismatch!");
pass
......@@ -329,7 +373,9 @@ class emulabserver:
for opt, val in argdict.items():
if opt in ("modify_exp"):
argstr += " -m "
if val == "true":
argstr += " -m "
pass
pass
elif opt in ("srcvnode"):
argstr += " -s "
......@@ -341,7 +387,7 @@ class emulabserver:
argdict.has_key("eid") and
argdict.has_key("link") and
argdict.has_key("params")):
return ResponseBlock(RESPONSE_BADARGS,
return EmulabResponse(RESPONSE_BADARGS,
output="Must supply pid, eid, link, params!");
argstr += " " + escapeshellarg(argdict["pid"])
......@@ -354,9 +400,9 @@ class emulabserver:
(exitval, output) = runcommand(TBDIR + "/bin/delay_config " + argstr)
if exitval:
return ResponseBlock(RESPONSE_ERROR, exitval >> 8, output=output)
return EmulabResponse(RESPONSE_ERROR, exitval >> 8, output=output)
return ResponseBlock(RESPONSE_SUCCESS, output=output)
return EmulabResponse(RESPONSE_SUCCESS, output=output)
pass
......
#!/bin/sh
DIR=@prefix@/sbin/
exec $DIR/sshxmlrpc_server.py experiment
......@@ -172,9 +172,12 @@ class SSHTransport:
# @param verbose unused.
# @return The value returned
#
def request(self, host, handler, request_body, verbose=0):
def request(self, host, handler, request_body, verbose=0, path=None):
# Strip the leading slash in the handler, if there is one.
if handler.startswith('/'):
if path:
handler = path + handler
pass
elif handler.startswith('/'):
handler = handler[1:]
pass
......@@ -347,7 +350,7 @@ class SSHServerProxy:
# @param encoding Content encoding.
# @param verbose unused.
#
def __init__(self, uri, transport=None, encoding=None, verbose=0):
def __init__(self, uri, transport=None, encoding=None, verbose=0, path=None):
type, uri = urllib.splittype(uri)
if type not in ("ssh", ):
raise IOError, "unsupported XML-RPC protocol"
......@@ -362,6 +365,7 @@ class SSHServerProxy:
self.__encoding = encoding
self.__verbose = verbose
self.__path = path
return
##
......@@ -379,7 +383,8 @@ class SSHServerProxy:
self.__host,
self.__handler,
request,
verbose=self.__verbose
verbose=self.__verbose,
path=self.__path
)
# ... ensure there was a valid reply.
......
......@@ -32,6 +32,10 @@ module = "experiment"
# Debugging output.
debug = 0
# For admin people.
SERVER_PATH = "/usr/testbed/sbin/"
path = None
##
# Print the usage statement to stdout.
#
......@@ -74,7 +78,7 @@ def do_method(server, method_and_args):
#
params = {}
for param in method_and_args:
plist = string.split(param, "=")
plist = string.split(param, "=", 1)
if len(plist) != 2:
print "Parameters are of the form: param=value!"
return -1
......@@ -89,7 +93,7 @@ def do_method(server, method_and_args):
response = apply(meth, meth_args)
#
# Parse the Response, which is a Dictionary. See ResponseBlock in the
# Parse the Response, which is a Dictionary. See EmulabResponse in the
# emulabclient.py module. The XML standard converts classes to a plain
# Dictionary, hence the code below.
#
......@@ -116,7 +120,7 @@ def do_method(server, method_and_args):
try:
# Parse the options,
opts, req_args = getopt.getopt(sys.argv[1:],
"dhVs:l:",
"dhVs:l:a",
[ "help", "version", "server=", "login=", ])
# ... act on them appropriately, and
for opt, val in opts:
......@@ -137,9 +141,12 @@ try:
elif opt in ("-m", "--module"):
module = val
pass
elif opt in ("-d"):
elif opt in ("-d", "--debug"):
debug = 1
pass
elif opt in ("-a", "--admin"):
path = SERVER_PATH
pass
pass
pass
except getopt.error, e:
......@@ -150,7 +157,7 @@ except getopt.error, e:
# Get a handle on the server,
server = SSHServerProxy("ssh://" + login_id + "@" + xmlrpc_server +
"/xmlrpc/" + module)
"/xmlrpc/" + module, path=path)
if len(req_args):
# Method and args are on the command line.
......@@ -160,7 +167,7 @@ else:
try:
while True:
line = raw_input("$ ")
tokens = line.split(" ", 1)
tokens = line.split(" ")
if len(tokens) >= 1 and len(tokens[0]) > 0:
try:
print str(do_method(server, tokens))
......
......@@ -61,11 +61,11 @@ commands from the shell. For example:
which says to create an experiment called "myeid" in the "mypid" project,
swap it in immediately, wait for the exit status (instead of running
asynchronously), passing inline the contents of nsfile.ns in your home dir.
By default, the client will contact the RPC server at <tt><?php echo