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