Index: fedd/federation/client_lib.py
===================================================================
--- fedd/federation/client_lib.py	(revision d743d608dbdafa2f98011b33e8beaecca5e92296)
+++ fedd/federation/client_lib.py	(revision d743d608dbdafa2f98011b33e8beaecca5e92296)
@@ -0,0 +1,278 @@
+#!/usr/local/bin/python
+
+import sys
+import os
+import pwd
+
+
+from fedid import fedid
+from util import fedd_ssl_context
+from remote_service import service_caller
+from service_error import service_error
+
+from optparse import OptionParser, OptionValueError
+
+
+class client_opts(OptionParser):
+    """
+    Standatd set of options that all clients talking to fedd can probably use.
+    Client code usually specializes this.
+    """
+    def __init__(self):
+        OptionParser.__init__(self, usage="%prog [opts] (--help for details)",
+                version="0.1")
+
+        self.add_option("--cert", action="store", dest="cert",
+                type="string", help="my certificate file")
+        self.add_option( "--debug", action="count", dest="debug", 
+                default=0, help="Set debug.  Repeat for more information")
+        self.add_option("--serialize_only", action="store_true", 
+                dest="serialize_only", default=False,
+                help="Print the SOAP request that would be sent and exit")
+        self.add_option("--trusted", action="store", dest="trusted",
+                type="string", help="Trusted certificates (required)")
+        self.add_option("--url", action="store", dest="url",
+                type="string",default="https://localhost:23235",  
+                help="URL to connect to (default %default)")
+        self.add_option("--transport", action="store", type="choice",
+                choices=("xmlrpc", "soap"), default="soap",
+                help="Transport for request (xmlrpc|soap) (Default: %default)")
+        self.add_option("--trace", action="store_const", dest="tracefile", 
+                const=sys.stderr, help="Print SOAP exchange to stderr")
+
+def exit_with_fault(exc, out=sys.stderr):
+    """
+    Print an error message and exit.  exc is the RPCException that caused the
+    failure.
+    """
+    codestr = ""
+
+    if exc.errstr: codestr = "Error: %s" % exc.errstr
+    else: codestr = ""
+
+    if exc.code:
+	if isinstance(exc.code, int): code = exc.code
+	else: code = -1
+
+	if len(codestr) > 0 : codestr += " (%d)" % code
+	else: codestr = "Error Code: %d" % code
+    else:
+	code = -1
+
+    print>>out, codestr
+    print>>out, "Description: %s" % exc.desc
+    sys.exit(code)
+
+class RPCException(RuntimeError):
+    """
+    An error during the RPC exchange.  It unifies errors from both SOAP and
+    XMLPRC calls.
+    """
+    def __init__(self, desc=None, code=None, errstr=None):
+	RuntimeError.__init__(self)
+	self.desc = desc
+	if isinstance(code, int): self.code = code
+	else: self.code = -1
+	self.errstr = errstr
+
+
+def get_user_cert():
+    cert = os.path.expanduser("~/.ssl/emulab.pem")
+    if not os.access(cert, os.R_OK):
+	cert = None
+    return cert
+
+def wrangle_standard_options(opts):
+    """
+    Look for the certificate to use for the call (check for the standard emulab
+    file and any passed in.  Make sure a present cert file can be read and
+    generate a fedid from it.  Make sure that any trusted cert file can be read
+    and if the debug level is set, set the tracefile attribute as well.  If any
+    of these tests fail, raise a RuntimeError.  Otherwise return the
+    certificate file and the dedid.
+    """
+    if opts.debug > 0: opts.tracefile=sys.stderr
+
+    if opts.trusted:
+	if ( not os.access(opts.trusted, os.R_OK) ) :
+	    raise RuntimeError("Cannot read trusted certificates (%s)" \
+		    % opts.trusted)
+
+    cert = get_user_cert()
+    if opts.cert: cert = opts.cert
+
+    if cert is None:
+	raise RuntimeError("No certificate given (--cert) or found")
+
+    if os.access(cert, os.R_OK):
+	fid = fedid(file=cert)
+    else:
+	raise RuntimeError("Cannot read certificate (%s)" % cert)
+
+    return (cert, fid)
+
+def save_certfile(out_certfile, ea):
+    """
+    if the experiment authority section in ea has a certificate and the
+    out_certfile parameter has a place to put it, save the cert to the file.
+    EnvronnemtError s can come from the file operations.
+    """
+    if out_certfile and ea and 'X509' in ea:
+	f = open(out_certfile, "w")
+	print >>f, ea['X509']
+	f.close()
+
+
+def do_rpc(req_dict, url, transport, cert, trusted, tracefile=None,
+	serialize_only=False, caller=None, responseBody=None):
+    """
+    The work of sending and parsing the RPC as either XMLRPC or SOAP
+    """
+
+    if caller is None: 
+	raise RuntimeError("Must provide caller to do_rpc")
+
+    context = None
+    while context == None:
+	try:
+	    context = fedd_ssl_context(cert, trusted)
+	except Exception, e:
+	    # Yes, doing this on message type is not ideal.  The string
+	    # comes from OpenSSL, so check there is this stops working.
+	    if str(e) == "bad decrypt": 
+		print >>sys.stderr, "Bad Passphrase given."
+	    else: raise
+
+    if transport == "soap":
+	if serialize_only:
+	    print caller.serialize_soap(req_dict) 
+	    return { }
+	else:
+	    try:
+		resp = caller.call_soap_service(url, req_dict, 
+			context=context, tracefile=tracefile)
+	    except service_error, e:
+		raise RPCException(desc=e.desc, code=e.code, 
+			errstr=e.code_string())
+    elif transport == "xmlrpc":
+	if serialize_only:
+	    ser = dumps((req_dict,))
+	    print ser
+	    return { }
+	else:
+	    try:
+		resp = caller.call_xmlrpc_service(url, req_dict, 
+			context=context, tracefile=tracefile)
+	    except service_error, e:
+		raise RPCException(desc=e.desc, code=e.code, 
+			errstr=e.code_string())
+    else:
+	raise RuntimeError("Unknown RPC transport: %s" % transport)
+
+    if responseBody:
+	if responseBody in resp: return resp[responseBody]
+	else: raise RuntimeError("No body in response??")
+    else:
+	return resp
+
+def get_experiment_names(eid):
+    """
+    eid is the experimentID entry in one of a dict representing a fedd message.
+    Pull out the fedid and localname from that hash and return them as fedid,
+    localid)
+    """
+    fedid = None
+    local = None
+    for id in eid or []:
+	for k in id.keys():
+	    if k =='fedid':
+		fedid = id[k]
+	    elif k =='localname':
+		local = id[k]
+
+    return (fedid, local)
+
+class info_format:
+    def __init__(self, out=sys.stdout):
+	self.out = out
+	self.key = {
+		'vis': 'vis', 
+		'vtopo': 'vtopo',
+		'federant': 'federant',
+		'experimentdescription': 'experimentdescription',
+		'id': 'experimentID',
+		'status': 'experimentStatus',
+		'log': 'allocationLog',
+	    }
+	self.formatter = {
+		'vis':  self.print_vis_or_vtopo('vis', self.out),
+		'vtopo':  self.print_vis_or_vtopo('vtopo', self.out),
+		'federant':  self.print_xml,
+		'experimentdescription':  self.print_xml,
+		'id': self.print_id,
+		'status': self.print_string,
+		'log': self.print_string,
+	    }
+
+    def print_string(self, d):
+	"""
+	Print the string to the class output.
+	"""
+	print >>self.out, d
+
+    def print_id(self, d):
+	"""
+	d is an array of ID dicts.  Print each one to the class output.
+	"""
+	for id in d or []:
+	    for k, i in id.items():
+		print >>self.out, "%s: %s" % (k, i)
+
+    def print_xml(self, d):
+	"""
+	Very simple ugly xml formatter of the kinds of dicts that come back
+	from services.
+	"""
+	if isinstance(d, dict):
+	    for k, v in d.items():
+		print >>self.out, "<%s>" % k
+		self.print_xml(v)
+		print >>self.out, "</%s>" % k
+	elif isinstance(d, list):
+	    for x in d:
+		self.print_xml(x)
+	else:
+	    print >>self.out, d
+
+
+    class print_vis_or_vtopo:
+	"""
+	Print the retrieved data is a simple xml representation of the dict.
+	"""
+	def __init__(self, top, out=sys.stdout):
+	    self.xml = top
+	    self.out = out
+
+	def __call__(self, d, out=sys.stdout):
+	    str = "<%s>\n" % self.xml
+	    for t in ('node', 'lan'):
+		if d.has_key(t): 
+		    for x in d[t]:
+			str += "<%s>" % t
+			for k in x.keys():
+			    str += "<%s>%s</%s>" % (k, x[k],k)
+			str += "</%s>\n" % t
+	    str+= "</%s>" % self.xml
+	    print >>self.out, str
+
+    def __call__(self, d, r):
+	"""
+	Print the data of type r (one of the keys of key) to the class output.
+	"""
+	k = self.key.get(r, None)
+	if k:
+	    if k in d: self.formatter[r](d[k])
+	    else: raise RuntimeError("Bad response: no %s" %k)
+	else:
+	    raise RuntimeError("Don't understand datatype %s" %r)
+
