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

Checkpoint some stuff before I make a bunch of incompatable changes

brought on by too much thinking and not enough hacking.
parent 37d9aa28
......@@ -12,10 +12,11 @@ UNIFIED = @UNIFIED_BOSS_AND_OPS@
include $(OBJDIR)/Makeconf
BIN_SCRIPTS = sshxmlrpc_client.py
BIN_SCRIPTS = sshxmlrpc_client.py client.py
SBIN_SCRIPTS = sshxmlrpc_server.py
LIB_STUFF = sshxmlrpc.py emulabserver.py emulabclient.py
LIBEXEC_STUFF = webxmlrpc
WWW_STUFF = xmlapi.php3
#
# These are the ones installed on plastic (users, control, etc).
......@@ -33,8 +34,14 @@ include $(TESTBED_SRCDIR)/GNUmakerules
install: $(addprefix $(INSTALL_BINDIR)/, $(BIN_SCRIPTS)) \
$(addprefix $(INSTALL_SBINDIR)/, $(SBIN_SCRIPTS)) \
$(addprefix $(INSTALL_LIBEXECDIR)/, $(LIBEXEC_STUFF)) \
$(addprefix $(INSTALL_WWWDIR)/, $(WWW_STUFF)) \
$(addprefix $(INSTALL_LIBDIR)/, $(LIB_STUFF))
$(INSTALL_WWWDIR)/%: %
@echo "Installing $<"
-mkdir -p $(patsubst %/,%,$(dir $@))
$(INSTALL_DATA) $(subst $$,\$$,$<) $(subst $$,\$$,$@)
#
# Control node installation (okay, plastic)
#
......
......@@ -9,6 +9,7 @@ import socket
import os
import popen2
import getopt
import string
MAXNSFILESIZE = (1024 * 512)
......@@ -17,21 +18,32 @@ MAXNSFILESIZE = (1024 * 512)
# 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.
# server, and I do not want to depend on the "Fault" structure that XML
# defines for return values.
#
RESPONSE_SUCCESS = 0
RESPONSE_BADARGS = 1
RESPONSE_ERROR = 2
RESPONSE_FORBIDDEN = 3
RESPONSE_BADVERSION = 4
RESPONSE_SERVERERROR = 5
RESPONSE_TOOBIG = 6
class ResponseBlock:
def __init__(self, exitval, output):
self.exitval = exitval
self.output = output
def __init__(self, code, value=0, output=None):
self.code = code
self.value = value
self.output = output
return
#
# This class implements the client side of the XMLRPC interface to Emulab.
# We do not try to do any serious argument processing here, but just pass
# it all through to the server. This may not be ideal, but I do not want to
# do argument processing and checking in multiple places! This whole approach
# will need another look at some point, but the goal right now is to get
# something that looks like an RPC interface running quickly.
# it all through to the server.
#
# This is intended to serve as a demonstration of how to use the RPC
# interface, and to provide a test harness (via sshxmlrpc_client.py)
# for the server.
#
class emulabclient:
#
......@@ -76,7 +88,7 @@ class emulabclient:
# Dict that is passed along.
#
argdict = {};
opt_args, req_args = getopt.getopt(arglist, "iE:g:e:p:S:L:a:l:fw")
opt_args, req_args = getopt.getopt(arglist, "iE:g:S:L:a:l:fw")
for opt, val in opt_args:
if opt in ("-i"):
argdict["batchmode"] = 0
......@@ -87,12 +99,6 @@ class emulabclient:
elif opt in ("-g"):
argdict["gid"] = val
pass
elif opt in ("-e"):
argdict["eid"] = val
pass
elif opt in ("-p"):
argdict["pid"] = val
pass
elif opt in ("-S"):
argdict["swapable"] = 0
argdict["noswap_reason"] = val
......@@ -115,8 +121,20 @@ class emulabclient:
pass
pass
if len(req_args) == 1:
nsfilename = req_args[0]
#
# Next two args must be pid and eid. This differs from the batchexp
# command line interface, but is consistent with swapexp and endexp.
#
if len(req_args) < 2 or len(req_args) > 3:
print "batchexp: Must provide pid, eid, and optional NS file!"
return -1
argdict["pid"] = req_args[0]
argdict["eid"] = req_args[1]
# Optional NS file.
if len(req_args) == 3:
nsfilename = req_args[2]
nsfilestr = readnsfile(nsfilename, self.debug)
# Watch for error reading NS file.
......@@ -135,7 +153,13 @@ class emulabclient:
print response["output"]
pass
return response["exitval"]
if response["code"] != RESPONSE_SUCCESS:
if response["value"]:
return response["value"]
else:
return response["code"]
pass
return RESPONSE_SUCCESS
#
# startexp is an alias for batchexp.
......@@ -205,7 +229,13 @@ class emulabclient:
print response["output"]
pass
return response["exitval"]
if response["code"] != RESPONSE_SUCCESS:
if response["value"]:
return response["value"]
else:
return response["code"]
pass
return RESPONSE_SUCCESS
#
# endexp.
......@@ -243,7 +273,13 @@ class emulabclient:
print response["output"]
pass
return response["exitval"]
if response["code"] != RESPONSE_SUCCESS:
if response["value"]:
return response["value"]
else:
return response["code"]
pass
return RESPONSE_SUCCESS
#
# nscheck. Syntax check an NS file. The wrinkle is that like above, we
......@@ -276,11 +312,16 @@ class emulabclient:
print response["output"]
pass
return response["exitval"]
if response["code"] != RESPONSE_SUCCESS:
if response["value"]:
return response["value"]
else:
return response["code"]
pass
return RESPONSE_SUCCESS
#
# nscheck. Syntax check an NS file. The wrinkle is that like above, we
# need to pakcage up the NS file and pass it inline.
# create_image. Create a disk image from a node.
#
def create_image(self, *args):
# The args are read-only, but I want a mutable version.
......@@ -315,7 +356,77 @@ class emulabclient:
print response["output"]
pass
return response["exitval"]
if response["code"] != RESPONSE_SUCCESS:
if response["value"]:
return response["value"]
else:
return response["code"]
pass
return RESPONSE_SUCCESS
#
# delay_config. Alter link characteristics.
#
def delay_config(self, *args):
# The args are read-only, but I want a mutable version.
arglist = [a for a in args];
if len(arglist) == 0:
print "create_image: Must provide some arguments!"
return -1
#
# Parse args. We do not try to do any checking, but rather just
# convert them to functional arguments and place them into the
# Dict that is passed along.
#
argdict = {};
opt_args, req_args = getopt.getopt(arglist, "ms:")
for opt, val in opt_args:
if opt in ("-m"):
argdict["modify_exp"] = 1
pass
if opt in ("-s"):
argdict["srcvnode"] = val
pass
pass
if len(req_args) < 4:
print "delay_config: Must provide pid, eid, link, param=value ...!"
return -1
argdict["pid"] = req_args.pop(0)
argdict["eid"] = req_args.pop(0)
argdict["link"] = req_args.pop(0)
#
# The params are supplied as a separate dictionary. It is up to the
# server to reject ones it does not like.
#
params = {}
for param in req_args:
plist = string.split(param, "=")
if len(plist) != 2:
print "delay_config: Parameters are of the form: param=value!"
return -1
params[plist[0]] = plist[1]
pass
argdict["params"] = params
response = self.server.delay_config(self.VERSION, argdict)
if len(response["output"]):
print response["output"]
pass
if response["code"] != RESPONSE_SUCCESS:
if response["value"]:
return response["value"]
else:
return response["code"]
pass
return RESPONSE_SUCCESS
pass
......
......@@ -13,7 +13,7 @@ import time
sys.path.append("@prefix@/lib")
import libdb
from libtestbed import SENDMAIL, TBOPS
from emulabclient import ResponseBlock
from emulabclient import *
# Configure variables
TBDIR = "@prefix@"
......@@ -40,8 +40,13 @@ class emulabserver:
# @return The 'msg' value with this machine's name prepended.
#
def echo(self, version, argdict):
return ResponseBlock(0, socket.gethostname() + ": " + str(version)
+ " " + str(argdict["list"]))
if not argdict.has_key("str"):
return ResponseBlock(RESPONSE_BADARGS,
output="Must supply a string to echo!");
return ResponseBlock(RESPONSE_SUCCESS, 0,
socket.gethostname() + ": " + str(version)
+ " " + argdict["str"])
#
# Start an experiment using batchexp. We get the NS file inline, which
......@@ -49,9 +54,15 @@ class emulabserver:
#
def batchexp(self, version, argdict):
if version != self.VERSION:
return ResponseBlock(-1, "Client version mismatch!");
return ResponseBlock(RESPONSE_BADVERSION,
output="Client version mismatch!");
pass
if not (argdict.has_key("pid") and
argdict.has_key("eid")):
return ResponseBlock(RESPONSE_BADARGS,
output="Must supply pid and eid!");
nsfilename = None
argstr = ""
......@@ -63,19 +74,19 @@ class emulabserver:
pass
elif opt in ("description"):
argstr += " -E "
argstr += val
argstr += escapeshellarg(val)
pass
elif opt in ("gid"):
argstr += " -g "
argstr += val
argstr += escapeshellarg(val)
pass
elif opt in ("eid"):
argstr += " -e "
argstr += val
argstr += escapeshellarg(val)
pass
elif opt in ("pid"):
argstr += " -p "
argstr += val
argstr += escapeshellarg(val)
pass
elif opt in ("idleswap"):
if opt == 0:
......@@ -88,18 +99,18 @@ class emulabserver:
elif opt in ("idleswap"):
if opt == 0:
argstr += " -L "
argstr += argdict["noidleswap_reason"]
argstr += escapeshellarg(argdict["noidleswap_reason"])
pass
else:
argstr += " -l "
argstr += val
argstr += escapeshellarg(val)
pass
pass
elif opt in ("noidleswap_reason"):
pass
elif opt in ("autoswap"):
argstr += " -a "
argstr += val
argstr += escapeshellarg(val)
pass
elif opt in ("frontend"):
argstr += " -f "
......@@ -109,27 +120,37 @@ class emulabserver:
pass
elif opt in ("nsfilepath"):
# Backend script will verify this local path.
nsfilename = val
nsfilename = escapeshellarg(val)
pass
elif opt in ("nsfilestr"):
nsfilestr = val
if len(nsfilestr) > (1024 * 512):
return ResponseBlock(-1, "NS File way too big!");
return ResponseBlock(RESPONSE_TOOBIG,
output="NS File way too big!");
(nsfp, nsfilename) = writensfile(nsfilestr)
if not nsfilename:
return ResponseBlock(-1, "Server Error")
return ResponseBlock(RESPONSE_SERVERERROR,
output="Server Error")
pass
pass
if nsfilename:
argstr += " "
argstr += nsfilename
argstr += " " + nsfilename
pass
(exitval, output) = runcommand(TBDIR + "/bin/batchexp " + argstr)
return ResponseBlock(exitval >> 8, output)
if exitval:
return ResponseBlock(RESPONSE_ERROR, exitval >> 8, output=output)
return ResponseBlock(RESPONSE_SUCCESS, output=output)
#
# startexp is an alias for batchexp.
#
def startexp(self, version, argdict):
return self.batchexp(version, argdict)
#
# swap an experiment using swapexp. We get the NS file inline, which
......@@ -137,7 +158,8 @@ class emulabserver:
#
def swapexp(self, version, argdict):
if version != self.VERSION:
return ResponseBlock(-1, "Client version mismatch!");
return ResponseBlock(RESPONSE_BADVERSION,
output="Client version mismatch!");
pass
nsfilename = None
......@@ -155,46 +177,51 @@ class emulabserver:
pass
elif opt in ("swapop"):
argstr += " -s "
argstr += val
argstr += escapeshellarg(val)
pass
elif opt in ("nsfilepath"):
# Backend script will verify this local path.
nsfilename = val
nsfilename = escapeshellarg(val)
pass
elif opt in ("nsfilestr"):
nsfilestr = val
if len(nsfilestr) > (1024 * 512):
return ResponseBlock(-1, "NS File way too big!");
return ResponseBlock(RESPONSE_TOOBIG,
output="NS File way too big!");
(nsfp, nsfilename) = writensfile(nsfilestr)
if not nsfilename:
return ResponseBlock(-1, "Server Error")
return ResponseBlock(RESPONSE_SERVERERROR,
output="Server Error")
pass
pass
if argdict.has_key("pid"):
argstr += " "
argstr += argdict["pid"]
pass
if argdict.has_key("eid"):
argstr += " "
argstr += argdict["eid"]
pass
if not (argdict.has_key("pid") and
argdict.has_key("eid")):
return ResponseBlock(RESPONSE_BADARGS,
output="Must supply pid and eid!");
argstr += " " + escapeshellarg(argdict["pid"])
argstr += " " + escapeshellarg(argdict["eid"])
if nsfilename:
argstr += " "
argstr += nsfilename
argstr += " " + nsfilename
pass
(exitval, output) = runcommand(TBDIR + "/bin/swapexp " + argstr)
return ResponseBlock(exitval >> 8, output)
if exitval:
return ResponseBlock(RESPONSE_ERROR, exitval >> 8, output=output)
return ResponseBlock(RESPONSE_SUCCESS, output=output)
#
# end an experiment using endexp.
#
def endexp(self, version, argdict):
if version != self.VERSION:
return ResponseBlock(-1, "Client version mismatch!");
return ResponseBlock(RESPONSE_BADVERSION,
output="Client version mismatch!");
pass
argstr = ""
......@@ -205,24 +232,27 @@ class emulabserver:
pass
pass
if argdict.has_key("pid"):
argstr += " "
argstr += argdict["pid"]
pass
if argdict.has_key("eid"):
argstr += " "
argstr += argdict["eid"]
pass
if not (argdict.has_key("pid") and
argdict.has_key("eid")):
return ResponseBlock(RESPONSE_BADARGS,
output="Must supply pid and eid!");
argstr += " " + escapeshellarg(argdict["pid"])
argstr += " " + escapeshellarg(argdict["eid"])
(exitval, output) = runcommand(TBDIR + "/bin/endexp " + argstr)
return ResponseBlock(exitval >> 8, output)
if exitval:
return ResponseBlock(RESPONSE_ERROR, exitval >> 8, output=output)
return ResponseBlock(RESPONSE_SUCCESS, output=output)
#
# nscheck an NS file.
#
def nscheck(self, version, argdict):
if version != self.VERSION:
return ResponseBlock(-1, "Client version mismatch!");
return ResponseBlock(RESPONSE_BADVERSION,
output="Client version mismatch!");
pass
argstr = ""
......@@ -231,28 +261,36 @@ class emulabserver:
nsfilestr = argdict["nsfilestr"]
if len(nsfilestr) > (1024 * 512):
return ResponseBlock(-1, "NS File way too big!");
return ResponseBlock(RESPONSE_TOOBIG,
output="NS File way too big!");
(nsfp, nsfilename) = writensfile(nsfilestr)
if not nsfilename:
return ResponseBlock(-1, "Server Error")
return ResponseBlock(RESPONSE_SERVERERROR, output="Server Error")
argstr += nsfilename
pass
elif argdict.has_key("nsfilepath"):
# Backend script will verify this local path.
argstr += escapeshellarg(argdict["nsfilepath"])
pass
# Backend script will verify this local path.
argstr += escapeshellarg(argdict["nsfilepath"])
pass
else:
return ResponseBlock(RESPONSE_BADARGS,
output="Must supply an NS file to check!");
(exitval, output) = runcommand(TBDIR + "/bin/nscheck " + argstr)
return ResponseBlock(exitval >> 8, output)
if exitval:
return ResponseBlock(RESPONSE_ERROR, exitval >> 8, output=output)
return ResponseBlock(RESPONSE_SUCCESS, output=output)
#
# create_image.
#
def create_image(self, version, argdict):
if version != self.VERSION:
return ResponseBlock(-1, "Client version mismatch!");
return ResponseBlock(RESPONSE_BADVERSION,
output="Client version mismatch!");
pass
argstr = ""
......@@ -264,17 +302,61 @@ class emulabserver:
pass
pass
if argdict.has_key("imageid"):
argstr += " "
argstr += argdict["imageid"]
if not (argdict.has_key("imageid") and
argdict.has_key("nodeid")):
return ResponseBlock(RESPONSE_BADARGS,
output="Must supply imageid and nodeid!");
argstr += " " + escapeshellarg(argdict["imageid"])
argstr += " " + escapeshellarg(argdict["nodeid"])
(exitval, output) = runcommand(TBDIR + "/bin/create_image " + argstr)
if exitval:
return ResponseBlock(RESPONSE_ERROR, exitval >> 8, output=output)
return ResponseBlock(RESPONSE_SUCCESS, output=output)
#
# create_image.
#
def delay_config(self, version, argdict):
if version != self.VERSION:
return ResponseBlock(RESPONSE_BADVERSION,
output="Client version mismatch!");
pass
if argdict.has_key("nodeid"):
argstr += " "
argstr += argdict["nodeid"]
argstr = ""
for opt, val in argdict.items():
if opt in ("modify_exp"):
argstr += " -m "
pass
elif opt in ("srcvnode"):
argstr += " -s "
argstr += escapeshellarg(val)
pass
pass
(exitval, output) = runcommand(TBDIR + "/bin/create_image " + argstr)
return ResponseBlock(exitval >> 8, output)
if not (argdict.has_key("pid") and
argdict.has_key("eid") and
argdict.has_key("link") and
argdict.has_key("params")):
return ResponseBlock(RESPONSE_BADARGS,
output="Must supply pid, eid, link, params!");
argstr += " " + escapeshellarg(argdict["pid"])
argstr += " " + escapeshellarg(argdict["eid"])
argstr += " " + escapeshellarg(argdict["link"])
for opt, val in argdict["params"].items():
argstr += " " + opt + "=" + val
pass
(exitval, output) = runcommand(TBDIR + "/bin/delay_config " + argstr)
if exitval:
return ResponseBlock(RESPONSE_ERROR, exitval >> 8, output=output)
return ResponseBlock(RESPONSE_SUCCESS, output=output)
pass
......
......@@ -30,6 +30,8 @@ class SSHTransport:
pass
self.user, self.realhost = urllib.splituser(host)
print self.user + " " + self.realhost + " " + handler