Commit 0c587f68 authored by Leigh B. Stoller's avatar Leigh B. Stoller

Checkpoint latest working code in case anyone wants to take a looksie.

parent 838b119b
......@@ -14,7 +14,8 @@ include $(OBJDIR)/Makeconf
BIN_SCRIPTS = sshxmlrpc_client.py
SBIN_SCRIPTS = sshxmlrpc_server.py
LIB_STUFF = sshxmlrpc.py emulabserver.py
LIB_STUFF = sshxmlrpc.py emulabserver.py emulabclient.py
LIBEXEC_STUFF = webxmlrpc
#
# These are the ones installed on plastic (users, control, etc).
......@@ -25,12 +26,13 @@ USERBINS = sshxmlrpc_client.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)
all: $(BIN_SCRIPTS) $(SBIN_SCRIPTS) $(LIB_STUFF) $(LIBEXEC_STUFF)
include $(TESTBED_SRCDIR)/GNUmakerules
install: $(addprefix $(INSTALL_BINDIR)/, $(BIN_SCRIPTS)) \
$(addprefix $(INSTALL_SBINDIR)/, $(SBIN_SCRIPTS)) \
$(addprefix $(INSTALL_LIBEXECDIR)/, $(LIBEXEC_STUFF)) \
$(addprefix $(INSTALL_LIBDIR)/, $(LIB_STUFF))
#
......
#! /usr/bin/env python
#
# EMULAB-COPYRIGHT
# Copyright (c) 2004 University of Utah and the Flux Group.
# All rights reserved.
#
import sys
import socket
import os
import popen2
import getopt
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.
#
class ResponseBlock:
def __init__(self, exitval, output):
self.exitval = exitval
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.
#
class emulabclient:
#
# Initialize the object. The server argument is intended to
# make this class independent of how we talk to the server. It just needs
# to export the same interface as the xmlrpclib ServerProxy class.
#
def __init__(self, server, debug):
self.VERSION = 0.1
self.server = server
self.debug = debug
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, *args):
argdict = {};
argdict["list"] = args
response = self.server.echo(self.VERSION, argdict)
print response["output"]
return 0
#
# Start an experiment using batchexp. The wrinkle is that we have to
# package up the NS file and pass it inline.
#
def batchexp(self, *args):
# The args are read-only, but I want a mutable version.
arglist = [a for a in args];
if len(arglist) == 0:
print "batchexp: 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, "iE:g:e:p:S:L:a:l:fw")
for opt, val in opt_args:
if opt in ("-i"):
argdict["batchmode"] = 0
pass
elif opt in ("-E"):
argdict["description"] = val
pass
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
pass
elif opt in ("-L"):
argdict["idleswap"] = 0
argdict["noidleswap_reason"] = val
pass
elif opt in ("-a"):
argdict["autoswap"] = val
pass
elif opt in ("-l"):
argdict["idleswap"] = val
pass
elif opt in ("-f"):
argdict["frontend"] = 1
pass
elif opt in ("-w"):
argdict["waitmode"] = 1
pass
pass
if len(req_args) == 1:
nsfilename = req_args[0]
nsfilestr = readnsfile(nsfilename, self.debug)
# Watch for error reading NS file.
if not nsfilestr:
return -1
if len(nsfilestr) > MAXNSFILESIZE:
print "batchexp: NS file too big; server will reject it."
return -1
argdict["nsfilestr"] = nsfilestr
pass
response = self.server.batchexp(self.VERSION, argdict)
if len(response["output"]):
print response["output"]
pass
return response["exitval"]
#
# startexp is an alias for batchexp.
#
def startexp(self, *args):
return self.batchexp(*args)
#
# swapexp. Also does swapmod. The wrinkle is that like above, we need to
# pakcage up the NS file and pass it inline. The difference is that the
# NS file is optional, dependent on the previous options; swapmod only.
#
def swapexp(self, *args):
# The args are read-only, but I want a mutable version.
arglist = [a for a in args];
if len(arglist) == 0:
print "swapexp: 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, "wres:")
for opt, val in opt_args:
if opt in ("-w"):
argdict["waitmode"] = 1
pass
elif opt in ("-r"):
argdict["reboot_nodes"] = 1
pass
elif opt in ("-e"):
argdict["restart_eventsys"] = 1
pass
elif opt in ("-s"):
argdict["swapop"] = val
pass
pass
if len(req_args) < 2 or len(req_args) > 3:
print "swapexp: Must provide pid and eid and an optiona NS file!"
return -1
argdict["pid"] = req_args[0]
argdict["eid"] = req_args[1]
if len(req_args) == 3:
nsfilename = req_args[2]
nsfilestr = readnsfile(nsfilename, self.debug)
# Watch for error reading NS file.
if not nsfilestr:
return -1
if len(nsfilestr) > MAXNSFILESIZE:
print "batchexp: NS file too big; server will reject it."
return -1
argdict["nsfilestr"] = nsfilestr
pass
response = self.server.swapexp(self.VERSION, argdict)
if len(response["output"]):
print response["output"]
pass
return response["exitval"]
#
# endexp.
#
def endexp(self, *args):
# The args are read-only, but I want a mutable version.
arglist = [a for a in args];
if len(arglist) == 0:
print "endexp: 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, "w")
for opt, val in opt_args:
if opt in ("-w"):
argdict["waitmode"] = 1
pass
pass
if len(req_args) != 2:
print "swapexp: Must provide pid and eid!"
return -1
argdict["pid"] = req_args[0]
argdict["eid"] = req_args[1]
response = self.server.endexp(self.VERSION, argdict)
if len(response["output"]):
print response["output"]
pass
return response["exitval"]
#
# nscheck. Syntax check an NS file. The wrinkle is that like above, we
# need to pakcage up the NS file and pass it inline.
##
def nscheck(self, *args):
# The args are read-only, but I want a mutable version.
arglist = [a for a in args];
if len(arglist) != 1:
print "nscheck: Must provide an NS file!"
return -1
argdict = {};
nsfilename = arglist[0]
nsfilestr = readnsfile(nsfilename, self.debug)
# Watch for error reading NS file.
if not nsfilestr:
return -1
if len(nsfilestr) > MAXNSFILESIZE:
print "nscheck: NS file too big; server will reject it."
return -1
argdict["nsfilestr"] = nsfilestr
response = self.server.nscheck(self.VERSION, argdict)
if len(response["output"]):
print response["output"]
pass
return response["exitval"]
#
# nscheck. Syntax check an NS file. The wrinkle is that like above, we
# need to pakcage up the NS file and pass it inline.
#
def create_image(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, "p")
for opt, val in opt_args:
if opt in ("-p"):
argdict["pid"] = val
pass
pass
if len(req_args) != 2:
print "create_image: Must provide imageid and nodeid!"
return -1
argdict["imageid"] = req_args[0]
argdict["nodeid"] = req_args[1]
response = self.server.create_image(self.VERSION, argdict)
if len(response["output"]):
print response["output"]
pass
return response["exitval"]
pass
#
# Utility functions
#
#
# Read an nsfile and return a single string.
#
def readnsfile(nsfilename, debug):
nsfilestr = ""
try:
fp = os.open(nsfilename, os.O_RDONLY)
while True:
str = os.read(fp, 1024)
if not str:
break
nsfilestr = nsfilestr + str
pass
os.close(fp)
except:
if debug:
print "%s:%s" % (sys.exc_type, sys.exc_value)
pass
print "batchexp: Cannot read NS file '" + nsfilename + "'"
return None
pass
return nsfilestr
#! /usr/bin/env python
#
# EMULAB-COPYRIGHT
# Copyright (c) 2004 University of Utah and the Flux Group.
# All rights reserved.
#
import sys
import socket
import os
import popen2
import tempfile
import time
sys.path.append("@prefix@/lib")
import libdb
from libtestbed import SENDMAIL, TBOPS
from emulabclient import ResponseBlock
# Configure variables
TBDIR = "@prefix@"
#
# This class implements the server side of the XMLRPC interface to Emulab.
#
# Arguments passed as a Dictionary. This converts to a XML "struct"
# which in Perl/PHP/Ruby would be a hash. So, a client written in
# pretty much any language should be able to talk to this class.
#
class emulabserver:
##
# Initialize the object. Currently only sets the objects 'VERSION' value.
#
def __init__(self):
self.VERSION = 0.1
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):
return ResponseBlock(0, socket.gethostname() + ": " + str(version)
+ " " + str(argdict["list"]))
#
# Start an experiment using batchexp. We get the NS file inline, which
# we have to write to a temp file first.
#
def batchexp(self, version, argdict):
if version != self.VERSION:
return ResponseBlock(-1, "Client version mismatch!");
pass
nsfilename = None
argstr = ""
for opt, val in argdict.items():
if opt in ("batchmode"):
if val == 0:
argstr += " -i"
pass
pass
elif opt in ("description"):
argstr += " -E "
argstr += val
pass
elif opt in ("gid"):
argstr += " -g "
argstr += val
pass
elif opt in ("eid"):
argstr += " -e "
argstr += val
pass
elif opt in ("pid"):
argstr += " -p "
argstr += val
pass
elif opt in ("idleswap"):
if opt == 0:
argstr += " -S "
argstr += argdict["noswap_reason"]
pass
pass
elif opt in ("noswap_reason"):
pass
elif opt in ("idleswap"):
if opt == 0:
argstr += " -L "
argstr += argdict["noidleswap_reason"]
pass
else:
argstr += " -l "
argstr += val
pass
pass
elif opt in ("noidleswap_reason"):
pass
elif opt in ("autoswap"):
argstr += " -a "
argstr += val
pass
elif opt in ("frontend"):
argstr += " -f "
pass
elif opt in ("waitmode"):
argstr += " -w "
pass
elif opt in ("nsfilepath"):
# Backend script will verify this local path.
nsfilename = val
pass
elif opt in ("nsfilestr"):
nsfilestr = val
if len(nsfilestr) > (1024 * 512):
return ResponseBlock(-1, "NS File way too big!");
(nsfp, nsfilename) = writensfile(nsfilestr)
if not nsfilename:
return ResponseBlock(-1, "Server Error")
pass
pass
if nsfilename:
argstr += " "
argstr += nsfilename
pass
(exitval, output) = runcommand(TBDIR + "/bin/batchexp " + argstr)
return ResponseBlock(exitval >> 8, 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):
if version != self.VERSION:
return ResponseBlock(-1, "Client version mismatch!");
pass
nsfilename = None
argstr = ""
for opt, val in argdict.items():
if opt in ("waitmode"):
argstr += " -w "
pass
elif opt in ("reboot_nodes"):
argstr += " -r "
pass
elif opt in ("restart_eventsys"):
argstr += " -e "
pass
elif opt in ("swapop"):
argstr += " -s "
argstr += val
pass
elif opt in ("nsfilepath"):
# Backend script will verify this local path.
nsfilename = val
pass
elif opt in ("nsfilestr"):
nsfilestr = val
if len(nsfilestr) > (1024 * 512):
return ResponseBlock(-1, "NS File way too big!");
(nsfp, nsfilename) = writensfile(nsfilestr)
if not nsfilename:
return ResponseBlock(-1, "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 nsfilename:
argstr += " "
argstr += nsfilename
pass
(exitval, output) = runcommand(TBDIR + "/bin/swapexp " + argstr)
return ResponseBlock(exitval >> 8, output)
#
# end an experiment using endexp.
#
def endexp(self, version, argdict):
if version != self.VERSION:
return ResponseBlock(-1, "Client version mismatch!");
pass
argstr = ""
for opt, val in argdict.items():
if opt in ("waitmode"):
argstr += " -w "
pass
pass
if argdict.has_key("pid"):
argstr += " "
argstr += argdict["pid"]
pass
if argdict.has_key("eid"):
argstr += " "
argstr += argdict["eid"]
pass
(exitval, output) = runcommand(TBDIR + "/bin/endexp " + argstr)
return ResponseBlock(exitval >> 8, output)
#
# nscheck an NS file.
#
def nscheck(self, version, argdict):
if version != self.VERSION:
return ResponseBlock(-1, "Client version mismatch!");
pass
argstr = ""
if argdict.has_key("nsfilestr"):
nsfilestr = argdict["nsfilestr"]
if len(nsfilestr) > (1024 * 512):
return ResponseBlock(-1, "NS File way too big!");
(nsfp, nsfilename) = writensfile(nsfilestr)
if not nsfilename:
return ResponseBlock(-1, "Server Error")
argstr += nsfilename
pass
elif argdict.has_key("nsfilepath"):
# Backend script will verify this local path.
argstr += escapeshellarg(argdict["nsfilepath"])
pass
(exitval, output) = runcommand(TBDIR + "/bin/nscheck " + argstr)
return ResponseBlock(exitval >> 8, output)
#
# create_image.
#
def create_image(self, version, argdict):
if version != self.VERSION:
return ResponseBlock(-1, "Client version mismatch!");
pass
argstr = ""
for opt, val in argdict.items():
if opt in ("pid"):
argstr += " -p "
argstr += escapeshellarg(val)
pass
pass
if argdict.has_key("imageid"):
argstr += " "
argstr += argdict["imageid"]
pass
if argdict.has_key("nodeid"):
argstr += " "
argstr += argdict["nodeid"]
pass
(exitval, output) = runcommand(TBDIR + "/bin/create_image " + argstr)
return ResponseBlock(exitval >> 8, output)
pass
#
# Utility functions
#
#
# escapeshellarg() adds single quotes around a string and quotes/escapes any
# existing single quotes allowing string to be passed directly to a shell
# function and having it be treated as a single safe argument.
#
def escapeshellarg(s):
s2 = ""
for c in s:
if c == '\'':
s2 = s2 + '\'\\\''
s2 = s2 + c
return '\'' + s2 + '\''
#
# Run a command. args is a list of strings to pass as arguments to cmd.
# Return the exitcode and the output as a tuple.
#
def runcommand(cmd):
child = popen2.Popen4(cmd)
output = child.fromchild.read(8192)
return (child.wait(), output)
def writensfile(str):
tempfile.tempdir = "/var/tmp"
try:
fp = tempfile.NamedTemporaryFile(prefix="php")