Index: fedd/fedd_create.py
===================================================================
--- fedd/fedd_create.py	(revision bcbf5438af1a290d2df2c4d8cfaf1a9b23c1f4d2)
+++ fedd/fedd_create.py	(revision 987aaa19f783a2ad4457bbda88eb60f32245e021)
@@ -20,5 +20,5 @@
 
 from fedd_util import fedid, fedd_ssl_context, pack_soap, unpack_soap, \
-	pack_id, unpack_id
+	pack_id, unpack_id, encapsulate_binaries, decapsulate_binaries
 
 from optparse import OptionParser, OptionValueError
@@ -76,42 +76,48 @@
 		version="0.1")
 
-	self.set_defaults(url="https://localhost:23235", anonymous=False,
-		serialize_only=False, transport="soap", use_fedid=False,
-		debug=0, file=None, master=None)
-
 	self.add_option("-c","--cert", action="store", dest="cert",
 		type="string", help="my certificate file")
 	self.add_option("-d", "--debug", action="count", dest="debug", 
-		help="Set debug.  Repeat for more information")
-	self.add_option("-f", "--file", dest="file", 
-		help="experiment description file")
-	self.add_option("-F","--useFedid", action="store_true",
-		dest="use_fedid",
-		help="Use a fedid derived from my certificate as user identity")
-	self.add_option("-k", "--sshKey", action="callback", type="string", 
-		callback=add_ssh_key, callback_args=(access_keys,),
-		help="ssh key for access (can be supplied more than once")
-	self.add_option("-K", "--x509Key", action="callback", type="string", 
-		callback=add_x509_cert, callback_args=(access_keys,),
-		help="X509 certificate for access " + \
-			"(can be supplied more than once")
-	self.add_option("-m", "--master", dest="master",
-		help="Master testbed in the federation")
+		default=0, help="Set debug.  Repeat for more information")
 	self.add_option("-s", "--serializeOnly", action="store_true", 
-		dest="serialize_only",
+		dest="serialize_only", default=False,
 		help="Print the SOAP request that would be sent and exit")
 	self.add_option("-T","--trusted", action="store", dest="trusted",
 		type="string", help="Trusted certificates (required)")
 	self.add_option("-u", "--url", action="store", dest="url",
-		type="string",
+		type="string",default="https://localhost:23235",  
 		help="URL to connect to (default %default)")
-	self.add_option("-U", "--username", action="store", dest="user",
-		type="string", help="Use this username instead of the uid")
 	self.add_option("-x","--transport", action="store", type="choice",
-		choices=("xmlrpc", "soap"),
+		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")
 
+class fedd_create_opts(fedd_client_opts):
+    def __init__(self, access_keys, add_key_callback=None, 
+	    add_cert_callback=None):
+	fedd_client_opts.__init__(self)
+	self.add_option("-e", "--experiment_cert", dest="out_certfile",
+		type="string", help="output certificate file")
+	self.add_option("-F","--useFedid", action="store_true",
+		dest="use_fedid", default=False,
+		help="Use a fedid derived from my certificate as user identity")
+	self.add_option("-f", "--file", dest="file", 
+		help="experiment description file")
+	if add_key_callback:
+	    self.add_option("-k", "--sshKey", action="callback", type="string", 
+		    callback=add_key_callback, callback_args=(access_keys,),
+		    help="ssh key for access (can be supplied more than once")
+	if add_cert_callback:
+	    self.add_option("-K", "--x509Key", action="callback",
+		    type="string", callback=add_cert_callback,
+		    callback_args=(access_keys,),
+		    help="X509 certificate for access " + \
+			"(can be supplied more than once")
+	self.add_option("-m", "--master", dest="master",
+		help="Master testbed in the federation")
+	self.add_option("-U", "--username", action="store", dest="user",
+		type="string", help="Use this username instead of the uid")
+
 def exit_with_fault(dict, out=sys.stderr):
     """ Print an error message and exit.
@@ -133,173 +139,386 @@
     sys.exit(dict.get('code', 20))
 
-def add_ssh_key(option, opt_str, value, parser, access_keys):
-    try:
-	access_keys.append(access_method(file=value,
-	    type=access_method.type_ssh))
-    except IOError, (errno, strerror):
-	raise OptionValueError("Cannot generate sshPubkey from %s: %s (%d)" %
-		(value,strerror,errno))
-
-def add_x509_cert(option, opt_str, value, parser, access_keys):
-    try:
-	access_keys.append(access_method(file=value,
-	    type=access_method.type_x509))
-    except IOError, (errno, strerror):
-	raise OptionValueError("Cannot read x509 cert from %s: %s (%d)" %
-		(value,strerror,errno))
-
-def get_user_info(access_keys):
-    pw = pwd.getpwuid(os.getuid());
-    try_cert=None
-    user = None
-
-    if pw != None:
-	user = pw[0]
-	try_cert = "%s/.ssl/emulab.pem" % pw[5];
-	if not os.access(try_cert, os.R_OK):
-	    try_cert = None
-	if len(access_keys) == 0:
-	    for k in ["%s/.ssh/id_rsa.pub", "%s/.ssh/id_dsa.pub", 
-		    "%s/.ssh/identity.pub"]:
-		try_key = k % pw[5];
-		if os.access(try_key, os.R_OK):
-		    access_keys.append(access_method(file=try_key,
-			type=access_method.type_ssh))
-		    break
-    return (user, try_cert)
-
-access_keys = []
-
-# Process the options using the customized option parser defined above
-parser = fedd_client_opts()
-
-(opts, args) = parser.parse_args()
-
-if opts.trusted != None:
-    if ( not os.access(opts.trusted, os.R_OK) ) :
-	sys.exit("Cannot read trusted certificates (%s)" % opts.trusted)
+class fedd_exp_data_opts(fedd_client_opts):
+    def __init__(self):
+	fedd_client_opts.__init__(self)
+	self.add_option("-e", "--experiment_cert", dest="exp_certfile",
+		type="string", help="output certificate file")
+
+# Querying experiment data follows the same control flow regardless of the
+# specific data retrieved.  This class encapsulates that control flow.
+class exp_data:
+    def __init__(self, op): 
+	"""
+	Specialize the class for the type of data requested (op)
+	"""
+	if op =='vtopo':
+	    self.RequestMessage = VtopoRequestMessage;
+	    self.ResponseMessage = VtopoResponseMessage;
+	    self.RequestBody="VtopoRequestBody"
+	    self.ResponseBody="VtopoResponseBody"
+	    self.method = "Vtopo"
+	    self.key="vtopo"
+	    self.xml='experiment'
+	elif op == 'vis':
+	    self.RequestMessage = VisRequestMessage;
+	    self.ResponseMessage = VisResponseMessage;
+	    self.RequestBody="VisRequestBody"
+	    self.ResponseBody="VisResponseBody"
+	    self.method = "Vis"
+	    self.key="vis"
+	    self.xml='vis'
+	else:
+	    raise TypeError("Bad op: %s" % op)
+
+    def print_xml(self, d, out=sys.stdout):
+	"""
+	Print the retrieved data is a simple xml representation of the dict.
+	"""
+	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 >>out, str
+
+    def __call__(self):
+	"""
+	The control flow.  Compose the request and print the response.
+	"""
+	# Process the options using the customized option parser defined above
+	parser = fedd_exp_data_opts()
+
+	(opts, args) = parser.parse_args()
+
+	if opts.trusted != None:
+	    if ( not os.access(opts.trusted, os.R_OK) ) :
+		sys.exit("Cannot read trusted certificates (%s)" % opts.trusted)
+	else:
+	    parser.error("--trusted is required")
+
+	if opts.debug > 0: opts.tracefile=sys.stderr
+
+	if opts.cert != None: cert = opts.cert
+
+	if cert == None:
+	    sys.exit("No certificate given (--cert) or found")
+
+	if os.access(cert, os.R_OK):
+	    fid = fedid(file=cert)
+	else:
+	    sys.exit("Cannot read certificate (%s)" % cert)
+
+	if opts.exp_certfile:
+	    exp_fedid = fedid(file=opts.exp_certfile)
+	else:
+	    sys.exit("Experiment certfile required")
+
+	context = None
+	while context == None:
+	    try:
+		context = fedd_ssl_context(cert, opts.trusted)
+	    except SSL.SSLError, 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
+
+	msg = { 'experiment': { 'fedid': exp_fedid } }
+
+	if opts.debug > 1: print >>sys.stderr, msg
+
+	if opts.transport == "soap":
+	    loc = feddServiceLocator();
+	    port = loc.getfeddPortType(opts.url,
+		    transport=M2Crypto.httpslib.HTTPSConnection, 
+		    transdict={ 'ssl_context' : context },
+		    tracefile=opts.tracefile)
+
+	    req = self.RequestMessage()
+
+	    set_req = getattr(req, "set_element_%s" % self.RequestBody, None)
+
+	    set_req(pack_soap(req, self.RequestBody, msg))
+
+	    if opts.serialize_only:
+		sw = SoapWriter()
+		sw.serialize(req)
+		print str(sw)
+		sys.exit(0)
+
+	    try:
+		method_call = getattr(port, self.method, None)
+		resp = method_call(req)
+	    except ZSI.ParseException, e:
+		sys.exit("Malformed response (XMLPRC?): %s" % e)
+	    except ZSI.FaultException, e:
+		resp = e.fault.detail[0]
+
+	    if resp:
+		resp_call = getattr(resp, "get_element_%s" %self.ResponseBody,
+			None)
+		if resp_call: 
+		    resp_body = resp_call()
+		    if ( resp_body != None): 
+			try:
+			    resp_dict = unpack_soap(resp_body)
+			    if opts.debug > 1: print >>sys.stderr, resp_dict
+			    if resp_dict.has_key(self.key):
+				self.print_xml(resp_dict[self.key])
+			except RuntimeError, e:
+			    sys.exit("Bad response. %s" % e.message)
+		elif 'get_element_FeddFaultBody' in dir(resp): 
+		    resp_body = resp.get_element_FeddFaultBody()
+		    if resp_body != None: 
+			exit_with_fault(unpack_soap(resp_body))
+		else: sys.exit("No body in response!?")
+	    else: sys.exit("No response?!?")
+	elif opts.transport == "xmlrpc":
+	    if opts.serialize_only:
+		ser = dumps((msg,))
+		print ser
+		sys.exit(0)
+
+	    transport = SSL_Transport(context)
+	    port = ServerProxy(opts.url, transport=transport)
+
+	    try:
+		method_call = getattr(port, self.method, None)
+		resp = method_call(
+			encapsulate_binaries({ self.RequestBody: msg},\
+			    ('fedid',)))
+	    except Error, e:
+		resp = { 'FeddFaultBody': \
+			{ 'errstr' : e.faultCode, 'desc' : e.faultString } }
+	    if resp:
+		if resp.has_key(self.ResponseBody): 
+		    try:
+			resp_dict = resp[self.ResponseBody]
+			if opts.debug > 1: print >>sys.stderr, resp_dict
+			if resp_dict.has_key(self.key):
+			    self.print_xml(resp_dict[self.key])
+		    except RuntimeError, e:
+			sys.exit("Bad response. %s" % e.messgae)
+		elif resp.has_key('FeddFaultBody'):
+		    exit_with_fault(resp['FeddFaultBody'])
+		else: sys.exit("No body in response!?")
+	    else: sys.exit("No response?!?")
+
+class create:
+    def __init__(self): pass
+
+    def add_ssh_key(self, option, opt_str, value, parser, access_keys):
+	try:
+	    access_keys.append(access_method(file=value,
+		type=access_method.type_ssh))
+	except IOError, (errno, strerror):
+	    raise OptionValueError("Cannot generate sshPubkey from %s: "\
+		    "%s (%d)" % (value,strerror,errno))
+
+    def add_x509_cert(self, option, opt_str, value, parser, access_keys):
+	try:
+	    access_keys.append(access_method(file=value,
+		type=access_method.type_x509))
+	except IOError, (errno, strerror):
+	    raise OptionValueError("Cannot read x509 cert from %s: %s (%d)" %
+		    (value,strerror,errno))
+
+    def get_user_info(self, access_keys):
+	pw = pwd.getpwuid(os.getuid());
+	try_cert=None
+	user = None
+
+	if pw != None:
+	    user = pw[0]
+	    try_cert = "%s/.ssl/emulab.pem" % pw[5];
+	    if not os.access(try_cert, os.R_OK):
+		try_cert = None
+	    if len(access_keys) == 0:
+		for k in ["%s/.ssh/id_rsa.pub", "%s/.ssh/id_dsa.pub", 
+			"%s/.ssh/identity.pub"]:
+		    try_key = k % pw[5];
+		    if os.access(try_key, os.R_OK):
+			access_keys.append(access_method(file=try_key,
+			    type=access_method.type_ssh))
+			break
+	return (user, try_cert)
+
+    def __call__(self):
+	access_keys = []
+	# Process the options using the customized option parser defined above
+	parser = fedd_create_opts(access_keys, self.add_ssh_key,
+		self.add_x509_cert)
+
+	(opts, args) = parser.parse_args()
+
+	if opts.trusted != None:
+	    if ( not os.access(opts.trusted, os.R_OK) ) :
+		sys.exit("Cannot read trusted certificates (%s)" % opts.trusted)
+	else:
+	    parser.error("--trusted is required")
+
+	if opts.debug > 0: opts.tracefile=sys.stderr
+
+	(user, cert) = self.get_user_info(access_keys)
+
+	if opts.user: user = opts.user
+
+	if opts.cert != None: cert = opts.cert
+
+	if cert == None:
+	    sys.exit("No certificate given (--cert) or found")
+
+	if os.access(cert, os.R_OK):
+	    fid = fedid(file=cert)
+	    if opts.use_fedid == True:
+		user = fid
+	else:
+	    sys.exit("Cannot read certificate (%s)" % cert)
+
+	if opts.file:
+	    exp_desc = ""
+	    try:
+		f = open(opts.file, 'r')
+		for line in f:
+		    exp_desc += line
+		f.close()
+	    except IOError:
+		sys.exit("Cannot read description file (%s)" %opts.file)
+	else:
+	    sys.exit("Must specify an experiment description (--file)")
+
+	if not opts.master:
+	    sys.exit("Must specify a master testbed (--master)")
+
+	out_certfile = opts.out_certfile
+
+	context = None
+	while context == None:
+	    try:
+		context = fedd_ssl_context(cert, opts.trusted)
+	    except SSL.SSLError, 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
+
+	msg = {
+		'experimentdescription': exp_desc,
+		'master': opts.master,
+		'user' : [ {\
+			'userID': pack_id(user), \
+			'access': [ { a.type: a.buf } for a in access_keys]\
+			} ]
+		}
+
+	if opts.debug > 1: print >>sys.stderr, msg
+
+	if opts.transport == "soap":
+	    loc = feddServiceLocator();
+	    port = loc.getfeddPortType(opts.url,
+		    transport=M2Crypto.httpslib.HTTPSConnection, 
+		    transdict={ 'ssl_context' : context },
+		    tracefile=opts.tracefile)
+
+	    req = CreateRequestMessage()
+
+	    req.set_element_CreateRequestBody(
+		    pack_soap(req, "CreateRequestBody", msg))
+
+	    if opts.serialize_only:
+		sw = SoapWriter()
+		sw.serialize(req)
+		print str(sw)
+		sys.exit(0)
+
+	    try:
+		resp = port.Create(req)
+	    except ZSI.ParseException, e:
+		sys.exit("Malformed response (XMLPRC?): %s" % e)
+	    except ZSI.FaultException, e:
+		resp = e.fault.detail[0]
+
+	    if resp:
+		if 'get_element_CreateResponseBody' in dir(resp): 
+		    resp_body = resp.get_element_CreateResponseBody()
+		    if ( resp_body != None): 
+			try:
+			    resp_dict = unpack_soap(resp_body)
+			    if opts.debug > 1: print >>sys.stderr, resp_dict
+			    ea = resp_dict.get('experimentAccess', None)
+			    if out_certfile and ea and ea.has_key('X509'):
+				try:
+				    f = open(out_certfile, "w")
+				    print >>f, ea['X509']
+				    f.close()
+				except IOError:
+				    sys.exit('Could not write to %s' % \
+					    out_certfile)
+			except RuntimeError, e:
+			    sys.exit("Bad response. %s" % e.message)
+		elif 'get_element_FeddFaultBody' in dir(resp): 
+		    resp_body = resp.get_element_FeddFaultBody()
+		    if resp_body != None: 
+			exit_with_fault(unpack_soap(resp_body))
+		else: sys.exit("No body in response!?")
+	    else: sys.exit("No response?!?")
+	elif opts.transport == "xmlrpc":
+	    if opts.serialize_only:
+		ser = dumps((msg,))
+		print ser
+		sys.exit(0)
+
+	    transport = SSL_Transport(context)
+	    port = ServerProxy(opts.url, transport=transport)
+
+	    try:
+		resp = port.Create({ 'CreateRequestBody': msg})
+	    except Error, e:
+		resp = { 'FeddFaultBody': \
+			{ 'errstr' : e.faultCode, 'desc' : e.faultString } }
+	    if resp:
+		if resp.has_key('CreateResponseBody'): 
+		    try:
+			resp_dict = resp['CreateResponseBody']
+			decapsulate_binaries(resp_dict, ('fedid',))
+			if opts.debug > 1: print >>sys.stderr, resp_dict
+			ea = resp_dict.get('experimentAccess', None)
+			if out_certfile and ea and ea.has_key('X509'):
+			    try:
+				f = open(out_certfile, "w")
+				print >>f, ea['X509']
+				f.close()
+			    except IOError:
+				sys.exit('Could not write to %s' % out_certfile)
+		    except RuntimeError, e:
+			sys.exit("Bad response. %s" % e.messgae)
+		elif resp.has_key('FeddFaultBody'):
+		    exit_with_fault(resp['FeddFaultBody'])
+		else: sys.exit("No body in response!?")
+
+	    else: sys.exit("No response?!?")
+
+valid_cmds = ['create']
+
+f = None
+if sys.argv[1] == 'create':
+    del sys.argv[1]
+    f = create()
+elif sys.argv[1] == 'vtopo':
+    del sys.argv[1]
+    f = exp_data('vtopo')
+elif sys.argv[1] == 'vis':
+    del sys.argv[1]
+    f = exp_data('vis')
 else:
-    parser.error("--trusted is required")
-
-if opts.debug > 0: opts.tracefile=sys.stderr
-
-(user, cert) = get_user_info(access_keys)
-
-if opts.user: user = opts.user
-
-if opts.cert != None: cert = opts.cert
-
-if cert == None:
-    sys.exit("No certificate given (--cert) or found")
-
-if os.access(cert, os.R_OK):
-    fid = fedid(file=cert)
-    if opts.use_fedid == True:
-	user = fid
-else:
-    sys.exit("Cannot read certificate (%s)" % cert)
-
-if opts.file:
-    exp_desc = ""
-    try:
-	f = open(opts.file, 'r')
-	for line in f:
-	    exp_desc += line
-	f.close()
-    except IOError:
-	sys.exit("Cannot read description file (%s)" %opts.file)
-else:
-    sys.exit("Must specify an experiment description (--file)")
-
-if not opts.master:
-    sys.exit("Must specify a master testbed (--master)")
-
-
-context = None
-while context == None:
-    try:
-	context = fedd_ssl_context(cert, opts.trusted)
-    except SSL.SSLError, 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
-
-msg = {
-	'experimentdescription': exp_desc,
-	'master': opts.master,
-	'user' : [ {\
-		'userID': pack_id(user), \
-		'access': [ { a.type: a.buf } for a in access_keys]\
-		} ]
-	}
-
-if opts.debug > 1: print >>sys.stderr, msg
-
-if opts.transport == "soap":
-    loc = feddServiceLocator();
-    port = loc.getfeddPortType(opts.url,
-	    transport=M2Crypto.httpslib.HTTPSConnection, 
-	    transdict={ 'ssl_context' : context },
-	    tracefile=opts.tracefile)
-
-    req = CreateRequestMessage()
-
-    req.set_element_CreateRequestBody(
-	    pack_soap(req, "CreateRequestBody", msg))
-
-    if opts.serialize_only:
-	sw = SoapWriter()
-	sw.serialize(req)
-	print str(sw)
-	sys.exit(0)
-
-    try:
-	resp = port.Create(req)
-    except ZSI.ParseException, e:
-	sys.exit("Malformed response (XMLPRC?): %s" % e)
-    except ZSI.FaultException, e:
-	resp = e.fault.detail[0]
-
-    if resp:
-	if 'get_element_CreateResponseBody' in dir(resp): 
-	    resp_body = resp.get_element_CreateResponseBody()
-	    if ( resp_body != None): 
-		try:
-		    resp_dict = unpack_soap(resp_body)
-		    if opts.debug > 1: print >>sys.stderr, resp_dict
-		except RuntimeError, e:
-		    sys.exit("Bad response. %s" % e.message)
-	elif 'get_element_FeddFaultBody' in dir(resp): 
-	    resp_body = resp.get_element_FeddFaultBody()
-	    if resp_body != None: 
-		exit_with_fault(unpack_soap(resp_body))
-	else: sys.exit("No body in response!?")
-    else: sys.exit("No response?!?")
-elif opts.transport == "xmlrpc":
-    if opts.serialize_only:
-	ser = dumps((msg,))
-	print ser
-	sys.exit(0)
-
-    transport = SSL_Transport(context)
-    port = ServerProxy(opts.url, transport=transport)
-
-    try:
-	resp = port.Create({ 'CreateRequestBody': msg})
-    except Error, e:
-	resp = { 'FeddFaultBody': \
-		{ 'errstr' : e.faultCode, 'desc' : e.faultString } }
-    if resp:
-	if resp.has_key('CreateResponseBody'): 
-	    try:
-		resp_dict = resp['CreateResponseBody']
-		if opts.debug > 1: print >>sys.stderr, resp_dict
-	    except RuntimeError, e:
-		sys.exit("Bad response. %s" % e.messgae)
-	elif resp.has_key('FeddFaultBody'):
-	    exit_with_fault(resp['FeddFaultBody'])
-	else: sys.exit("No body in response!?")
-
-    else: sys.exit("No response?!?")
+    sys.exit("Bad command: %s.  Valid ones are: %s" % \
+	    (sys.argv[1], ", ".join(valid_cmds)))
+
+if f: f()
+else: sys.exit("Null function?!?")
