6 from urlparse import urlparse
10 from optparse import OptionParser
12 from sfa.client.sfi import Sfi
13 from sfa.util.sfalogging import logger
14 import sfa.util.xmlrpcprotocol as xmlrpcprotocol
16 def url_hostname_port (url):
19 parsed_url=urlparse(url)
20 # 0(scheme) returns protocol
22 if parsed_url[0]=='https': default_port='443'
23 # 1(netloc) returns the hostname+port part
24 parts=parsed_url[1].split(":")
27 return (url,parts[0],default_port)
29 return (url,parts[0],parts[1])
34 def __init__ (self,url):
37 (self._url,self.hostname,self.port)=url_hostname_port(url)
38 self.ip=socket.gethostbyname(self.hostname)
41 # traceback.print_exc()
42 self.hostname="unknown"
52 # this is used as a key for creating graph nodes and to avoid duplicates
54 return "%s:%s"%(self.ip,self.port)
56 # connect to server and trigger GetVersion
57 def get_version(self):
60 # dummy to meet Sfi's expectations for its 'options' field
63 options=DummyOptions()
69 key_file = client.get_key_file()
70 cert_file = client.get_cert_file(key_file)
72 logger.info('issuing get version at %s'%url)
73 server=xmlrpcprotocol.get_server(url, key_file, cert_file, timeout=options.timeout, verbose=options.verbose)
74 self._version=server.GetVersion()
76 # traceback.print_exc()
82 def multi_lines_label(*lines):
83 result='<<TABLE BORDER="0" CELLBORDER="0"><TR><TD>' + \
84 '</TD></TR><TR><TD>'.join(lines) + \
86 # print 'multilines=',result
89 # default is for when we can't determine the type of the service
90 # typically the server is down, or we can't authenticate, or it's too old code
91 shapes = {"registry": "diamond", "slicemgr":"ellipse", "aggregate":"box", 'default':'plaintext'}
92 abbrevs = {"registry": "REG", "slicemgr":"SA", "aggregate":"AM", 'default':'[unknown interface]'}
94 # return a dictionary that translates into the node's attr
95 def get_layout (self):
97 ### retrieve cached GetVersion
98 version=self.get_version()
99 # set the href; xxx would make sense to try and 'guess' the web URL, not the API's one...
100 layout['href']=self.url()
101 ### set html-style label
102 ### see http://www.graphviz.org/doc/info/shapes.html#html
103 # if empty the service is unreachable
108 try: abbrev=Interface.abbrevs[version['interface']]
109 except: abbrev=Interface.abbrevs['default']
111 if 'hrn' in version: label += " %s"%version['hrn']
112 else: label += "[no hrn]"
113 if 'code_tag' in version:
114 label += " %s"%version['code_tag']
115 if 'testbed' in version:
116 label += " (%s)"%version['testbed']
117 layout['label']=Interface.multi_lines_label(self.url(),label)
119 try: shape=Interface.shapes[version['interface']]
120 except: shape=Interface.shapes['default']
121 layout['shape']=shape
122 ### fill color to outline wrongly configured bodies
123 if 'geni_api' not in version and 'sfa' not in version:
124 layout['style']='filled'
125 layout['fillcolor']='gray'
130 # provide the entry points (a list of interfaces)
131 def __init__ (self, left_to_right=False, verbose=False):
133 self.left_to_right=left_to_right
135 def graph (self,entry_points):
136 graph=pygraphviz.AGraph(directed=True)
137 if self.left_to_right:
138 graph.graph_attr['rankdir']='LR'
139 self.scan(entry_points,graph)
142 # scan from the given interfaces as entry points
143 def scan(self,interfaces,graph):
144 if not isinstance(interfaces,list):
145 interfaces=[interfaces]
147 # remember node to interface mapping
149 # add entry points right away using the interface uid's as a key
152 graph.add_node(i.uid())
153 node2interface[graph.get_node(i.uid())]=i
155 # keep on looping until we reach a fixed point
156 # don't worry about abels and shapes that will get fixed later on
158 for interface in to_scan:
159 # performing xmlrpc call
160 version=interface.get_version()
162 logger.info("GetVersion at interface %s"%interface.url())
164 logger.info("<EMPTY GetVersion(); offline or cannot authenticate>")
166 for (k,v) in version.iteritems():
167 if not isinstance(v,dict):
168 logger.info("\r\t%s:%s"%(k,v))
171 for (k1,v1) in v.iteritems():
172 logger.info("\r\t\t%s:%s"%(k1,v1))
173 # 'geni_api' is expected if the call succeeded at all
174 # 'peers' is needed as well as AMs typically don't have peers
175 if 'geni_api' in version and 'peers' in version:
176 # proceed with neighbours
177 for (next_name,next_url) in version['peers'].iteritems():
178 next_interface=Interface(next_url)
179 # locate or create node in graph
181 # if found, we're good with this one
182 next_node=graph.get_node(next_interface.uid())
184 # otherwise, let's move on with it
185 graph.add_node(next_interface.uid())
186 next_node=graph.get_node(next_interface.uid())
187 node2interface[next_node]=next_interface
188 to_scan.append(next_interface)
189 graph.add_edge(interface.uid(),next_interface.uid())
190 scanned.append(interface)
191 to_scan.remove(interface)
192 # we've scanned the whole graph, let's get the labels and shapes right
193 for node in graph.nodes():
194 interface=node2interface.get(node,None)
196 for (k,v) in interface.get_layout().iteritems():
199 logger.error("MISSED interface with node %s"%node)
202 default_outfiles=['sfa.png','sfa.svg','sfa.dot']
205 usage="%prog [options] url-entry-point(s)"
206 parser=OptionParser(usage=usage)
207 parser.add_option("-o","--output",action='append',dest='outfiles',default=[],
208 help="output filenames (cumulative) - defaults are %r"%default_outfiles)
209 parser.add_option("-l","--left-to-right",action="store_true",dest="left_to_right",default=False,
210 help="instead of top-to-bottom")
211 parser.add_option("-v","--verbose",action='store_true',dest='verbose',default=False,
213 (options,args)=parser.parse_args()
217 if not options.outfiles:
218 options.outfiles=default_outfiles
219 scanner=SfaScan(left_to_right=options.left_to_right, verbose=options.verbose)
220 entries = [ Interface(entry) for entry in args ]
221 g=scanner.graph(entries)
222 logger.info("creating layout")
224 for outfile in options.outfiles:
225 logger.info("drawing in %s"%outfile)
229 if __name__ == '__main__':