#!/usr/bin/env python import sys import socket import traceback from urlparse import urlparse import pygraphviz from optparse import OptionParser from sfa.client.sfi import Sfi from sfa.util.sfalogging import logger, DEBUG import sfa.util.xmlrpcprotocol as xmlrpcprotocol def url_hostname_port (url): if url.find("://")<0: url="http://"+url parsed_url=urlparse(url) # 0(scheme) returns protocol default_port='80' if parsed_url[0]=='https': default_port='443' # 1(netloc) returns the hostname+port part parts=parsed_url[1].split(":") # just a hostname if len(parts)==1: return (url,parts[0],default_port) else: return (url,parts[0],parts[1]) ### class Interface: def __init__ (self,url): self._url=url try: (self._url,self.hostname,self.port)=url_hostname_port(url) self.ip=socket.gethostbyname(self.hostname) self.probed=False except: self.hostname="unknown" self.ip='0.0.0.0' self.port="???" # don't really try it self.probed=True self._version={} def url(self): return self._url # this is used as a key for creating graph nodes and to avoid duplicates def uid (self): return "%s:%s"%(self.ip,self.port) # connect to server and trigger GetVersion def get_version(self): if self.probed: return self._version # dummy to meet Sfi's expectations for its 'options' field class DummyOptions: pass options=DummyOptions() options.verbose=False options.timeout=10 try: client=Sfi(options) client.read_config() key_file = client.get_key_file() cert_file = client.get_cert_file(key_file) url=self.url() logger.info('issuing get version at %s'%url) logger.debug("GetVersion, using timeout=%d"%options.timeout) server=xmlrpcprotocol.get_server(url, key_file, cert_file, timeout=options.timeout, verbose=options.verbose) self._version=server.GetVersion() except: self._version={} self.probed=True return self._version @staticmethod def multi_lines_label(*lines): result='<
' + \ '
'.join(lines) + \ '
>' return result # default is for when we can't determine the type of the service # typically the server is down, or we can't authenticate, or it's too old code shapes = {"registry": "diamond", "slicemgr":"ellipse", "aggregate":"box", 'default':'plaintext'} abbrevs = {"registry": "REG", "slicemgr":"SA", "aggregate":"AM", 'default':'[unknown interface]'} # return a dictionary that translates into the node's attr def get_layout (self): layout={} ### retrieve cached GetVersion version=self.get_version() # set the href; xxx would make sense to try and 'guess' the web URL, not the API's one... layout['href']=self.url() ### set html-style label ### see http://www.graphviz.org/doc/info/shapes.html#html # if empty the service is unreachable if not version: label="offline" else: label='' try: abbrev=Interface.abbrevs[version['interface']] except: abbrev=Interface.abbrevs['default'] label += abbrev if 'hrn' in version: label += " %s"%version['hrn'] else: label += "[no hrn]" if 'code_tag' in version: label += " %s"%version['code_tag'] if 'testbed' in version: label += " (%s)"%version['testbed'] layout['label']=Interface.multi_lines_label(self.url(),label) ### set shape try: shape=Interface.shapes[version['interface']] except: shape=Interface.shapes['default'] layout['shape']=shape ### fill color to outline wrongly configured bodies if 'geni_api' not in version and 'sfa' not in version: layout['style']='filled' layout['fillcolor']='gray' return layout class SfaScan: # provide the entry points (a list of interfaces) def __init__ (self, left_to_right=False, verbose=False): self.verbose=verbose self.left_to_right=left_to_right def graph (self,entry_points): graph=pygraphviz.AGraph(directed=True) if self.left_to_right: graph.graph_attr['rankdir']='LR' self.scan(entry_points,graph) return graph # scan from the given interfaces as entry points def scan(self,interfaces,graph): if not isinstance(interfaces,list): interfaces=[interfaces] # remember node to interface mapping node2interface={} # add entry points right away using the interface uid's as a key to_scan=interfaces for i in interfaces: graph.add_node(i.uid()) node2interface[graph.get_node(i.uid())]=i scanned=[] # keep on looping until we reach a fixed point # don't worry about abels and shapes that will get fixed later on while to_scan: for interface in to_scan: # performing xmlrpc call version=interface.get_version() if self.verbose: logger.info("GetVersion at interface %s"%interface.url()) if not version: logger.info("") else: for (k,v) in version.iteritems(): if not isinstance(v,dict): logger.info("\r\t%s:%s"%(k,v)) else: logger.info(k) for (k1,v1) in v.iteritems(): logger.info("\r\t\t%s:%s"%(k1,v1)) # 'geni_api' is expected if the call succeeded at all # 'peers' is needed as well as AMs typically don't have peers if 'geni_api' in version and 'peers' in version: # proceed with neighbours for (next_name,next_url) in version['peers'].iteritems(): next_interface=Interface(next_url) # locate or create node in graph try: # if found, we're good with this one next_node=graph.get_node(next_interface.uid()) except: # otherwise, let's move on with it graph.add_node(next_interface.uid()) next_node=graph.get_node(next_interface.uid()) node2interface[next_node]=next_interface to_scan.append(next_interface) graph.add_edge(interface.uid(),next_interface.uid()) scanned.append(interface) to_scan.remove(interface) # we've scanned the whole graph, let's get the labels and shapes right for node in graph.nodes(): interface=node2interface.get(node,None) if interface: for (k,v) in interface.get_layout().iteritems(): node.attr[k]=v else: logger.error("MISSED interface with node %s"%node) default_outfiles=['sfa.png','sfa.svg','sfa.dot'] def main(): usage="%prog [options] url-entry-point(s)" parser=OptionParser(usage=usage) parser.add_option("-o","--output",action='append',dest='outfiles',default=[], help="output filenames (cumulative) - defaults are %r"%default_outfiles) parser.add_option("-l","--left-to-right",action="store_true",dest="left_to_right",default=False, help="instead of top-to-bottom") parser.add_option("-v","--verbose",action='store_true',dest='verbose',default=False, help="verbose") parser.add_option("-d","--debug",action='store_true',dest='debug',default=False, help="debug") (options,args)=parser.parse_args() if not args: parser.print_help() sys.exit(1) if not options.outfiles: options.outfiles=default_outfiles logger.enable_console() if options.debug: options.verbose=True logger.setLevel(DEBUG) scanner=SfaScan(left_to_right=options.left_to_right, verbose=options.verbose) entries = [ Interface(entry) for entry in args ] g=scanner.graph(entries) logger.info("creating layout") g.layout(prog='dot') for outfile in options.outfiles: logger.info("drawing in %s"%outfile) g.draw(outfile) logger.info("done") if __name__ == '__main__': main()