Index: fedd/fedd_client.py
===================================================================
--- fedd/fedd_client.py	(revision 70cdf59b23ead724d2c884f65073e46b04245675)
+++ fedd/fedd_client.py	(revision 63f374626fb62134c95b058a1b959462708a3c0d)
@@ -4,4 +4,6 @@
 import os
 import pwd
+import tempfile
+import subprocess
 
 from federation import fedid, service_error
@@ -144,4 +146,17 @@
 		type="string", help="output certificate file")
 
+class fedd_image_opts(fedd_exp_data_opts):
+    def __init__(self):
+	fedd_exp_data_opts.__init__(self)
+	self.add_option("-o", "--output", dest="outfile", type="string",
+		help="output image file")
+	self.add_option("-F", "--format", dest="format", type="choice", 
+		choices=("jpg", "png", "dot"),
+		help="Output file format override")
+	self.add_option("-P", "--program", dest="neato", default=None,
+		type="string",
+		help="Program compatible with dot (from graphviz) used to " + \
+			"render image")
+
 def exit_with_fault(dict, out=sys.stderr):
     """ Print an error message and exit.
@@ -378,4 +393,166 @@
 	    print resp_dict
 
+
+# Querying experiment data follows the same control flow regardless of the
+# specific data retrieved.  This class encapsulates that control flow.
+class image(fedd_rpc):
+    def __init__(self): 
+	"""
+	Null constructor
+	"""
+
+	fedd_rpc.__init__(self, "Vtopo")
+
+    def gen_image(self, d, file, fmt, neato):
+
+	# Open up a temporary file for dot to turn into a visualization
+	try:
+	    df, dotname = tempfile.mkstemp(prefix='fedd_client', suffix=".dot")
+	    dotfile = os.fdopen(df, 'w')
+	except IOError:
+	    raise service_error(service_error.internal,
+		    "Failed to open file in genviz")
+
+	if not neato:
+	    for f in ['/usr/bin/neato', '/usr/local/bin/neato', 
+		    '/usr/bin/dot', '/usr/local/bin/dot']:
+		if os.access(f, os.X_OK):
+		    neato = f
+		    break
+	    else:
+		sys.exit("Cannot find graph rendering program")
+
+	cmd = [neato, '-Gsplines=true']
+	if fmt != 'dot': cmd.append('-T%s' % fmt)
+	if file:
+	    cmd.append('-o')
+	    cmd.append(file)
+	cmd.append(dotname)
+
+	print >>dotfile, "graph G {"
+	print >>dotfile, '\tnode [fontname=arial,fontsize=9,label="\N"];'
+	print >>dotfile, '\tedge [fontname=arial,fontsize=9,label="\N"];\n'
+
+	for n in d.get('node', []):
+	    print >>dotfile, '\t%s [shape=box,style=filled,\\' % n['vname']
+	    print >>dotfile, '\t\tcolor=black,fillcolor="#60f8c0",regular=1]'
+
+
+	lans = { }
+	links = { }
+
+	# Collect lan members (we have to draw extra stuff for these)
+	for n in d.get('lan', []):
+	    v = n['vname']
+	    m = n['member']
+	    if m.find(':') != -1:
+		m = m[0:m.find(':')]
+	    if lans.has_key(v):
+		lans[v].append(m)
+	    elif links.has_key(v):
+		links[v].append(m)
+		if len(links[v]) > 2:
+		    lans[v] = links[v]
+		    del links[v]
+	    else:
+		links[v] = [m]
+
+	# Encode the lans and the links
+	for l in lans.keys():
+	    print >>dotfile, '\t%s [shape=ellipse, style=filled,\\' % l
+	    print >>dotfile,'\t\tcolor=black,fillcolor="#80c0f8",regular=1]'
+	    for n in lans[l]:
+		print >>dotfile, '\t%s -- %s' % (l, n)
+
+	for k, l in links.items():
+	    if len(l) == 2:
+		print >>dotfile, '\t%s -- %s [label="%s"]' % (l[0], l[1], k)
+
+	print >>dotfile, "}"
+	dotfile.close()
+
+	# Redirect the drawing program stderr
+	dev_null = open("/dev/null", "w")
+	rv = subprocess.call(cmd, stderr=dev_null)
+	os.remove(dotname)
+	dev_null.close()
+	if rv != 0:
+	    sys.exit("Error creating graph")
+
+
+
+    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_image_opts()
+
+	(opts, args) = parser.parse_args()
+
+	if opts.trusted:
+	    if ( not os.access(opts.trusted, os.R_OK) ) :
+		sys.exit("Cannot read trusted certificates (%s)" % opts.trusted)
+
+	if opts.debug > 0: opts.tracefile=sys.stderr
+
+	(user, cert) = self.get_user_info([])
+
+	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_name and opts.exp_certfile:
+	    sys.exit("Only one of --experiment_cert and " +\
+		    "--experiment_name permitted");
+
+	if opts.exp_certfile:
+	    exp_id = { 'fedid': fedid(file=opts.exp_certfile) }
+
+	if opts.exp_name:
+	    exp_id = { 'localname' : opts.exp_name }
+
+	if opts.format and opts.outfile:
+	    fmt = opts.format
+	    file = opts.outfile
+	elif not opts.format and opts.outfile:
+	    fmt = opts.outfile[-3:]
+	    if fmt not in ("png", "jpg", "dot"):
+		sys.exit("Unexpected file type and no format specified")
+	    file = opts.outfile
+	elif opts.format and not opts.outfile:
+	    fmt = opts.format
+	    file = None
+	else:
+	    fmt="dot"
+	    file = None
+
+
+	req = { 'experiment': exp_id }
+
+	try:
+	    resp_dict = self.do_rpc(req,
+		    opts.url, opts.transport, cert, opts.trusted, 
+		    serialize_only=opts.serialize_only,
+		    tracefile=opts.tracefile)
+	except self.RPCException, e:
+	    exit_with_fault(\
+		    {'desc': e.desc, 'errstr': e.errstr, 'code': e.code})
+	except RuntimeError, e:
+	    print e
+	    sys.exit("Error processing RPC: %s" % e)
+
+
+	if resp_dict.has_key('vtopo'):
+	    self.gen_image(resp_dict['vtopo'], file, fmt, opts.neato)
+	else:
+	    sys.exit("Bad response. %s" % e.message)
+
 class terminate(fedd_rpc):
     def __init__(self): 
@@ -734,4 +911,5 @@
 	'vtopo': exp_data('Vtopo'),\
 	'vis': exp_data('Vis'),\
+	'image': image(),\
 	'info': exp_data('Info'),\
 	'terminate': terminate(),\
