6 from urlparse import urlparse
10 from optparse import OptionParser
12 from sfa.client.sfi import Sfi
13 from sfa.util.sfalogging import sfa_logger,sfa_logger_goes_to_console
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()
68 key_file = client.get_key_file()
69 cert_file = client.get_cert_file(key_file)
71 sfa_logger().info('issuing get version at %s'%url)
72 server=xmlrpcprotocol.get_server(url, key_file, cert_file, options)
73 self._version=server.GetVersion()
75 # traceback.print_exc()
81 def multi_lines_label(*lines):
82 result='<<TABLE BORDER="0" CELLBORDER="0"><TR><TD>' + \
83 '</TD></TR><TR><TD>'.join(lines) + \
85 # print 'multilines=',result
88 # default is for when we can't determine the type of the service
89 # typically the server is down, or we can't authenticate, or it's too old code
90 shapes = {"registry": "diamond", "slicemgr":"ellipse", "aggregate":"box", 'default':'plaintext'}
91 abbrevs = {"registry": "REG", "slicemgr":"SA", "aggregate":"AM", 'default':'[unknown interface]'}
93 # return a dictionary that translates into the node's attr
94 def get_layout (self):
96 ### retrieve cached GetVersion
97 version=self.get_version()
98 # set the href; xxx would make sense to try and 'guess' the web URL, not the API's one...
99 layout['href']=self.url()
100 ### set html-style label
101 ### see http://www.graphviz.org/doc/info/shapes.html#html
102 # if empty the service is unreachable
107 try: abbrev=Interface.abbrevs[version['interface']]
108 except: abbrev=Interface.abbrevs['default']
110 if 'hrn' in version: label += " %s"%version['hrn']
111 else: label += "[no hrn]"
112 if 'code_tag' in version:
113 label += " %s"%version['code_tag']
114 if 'testbed' in version:
115 label += " (%s)"%version['testbed']
116 layout['label']=Interface.multi_lines_label(self.url(),label)
118 try: shape=Interface.shapes[version['interface']]
119 except: shape=Interface.shapes['default']
120 layout['shape']=shape
121 ### fill color to outline wrongly configured bodies
122 if 'geni_api' not in version and 'sfa' not in version:
123 layout['style']='filled'
124 layout['fillcolor']='gray'
129 # provide the entry points (a list of interfaces)
130 def __init__ (self, left_to_right=False, verbose=False):
132 self.left_to_right=left_to_right
134 def graph (self,entry_points):
135 graph=pygraphviz.AGraph(directed=True)
136 if self.left_to_right:
137 graph.graph_attr['rankdir']='LR'
138 self.scan(entry_points,graph)
141 # scan from the given interfaces as entry points
142 def scan(self,interfaces,graph):
143 if not isinstance(interfaces,list):
144 interfaces=[interfaces]
146 # remember node to interface mapping
148 # add entry points right away using the interface uid's as a key
151 graph.add_node(i.uid())
152 node2interface[graph.get_node(i.uid())]=i
154 # keep on looping until we reach a fixed point
155 # don't worry about abels and shapes that will get fixed later on
157 for interface in to_scan:
158 # performing xmlrpc call
159 version=interface.get_version()
161 sfa_logger().info("GetVersion at interface %s"%interface.url())
163 sfa_logger().info("<EMPTY GetVersion(); offline or cannot authenticate>")
165 for (k,v) in version.iteritems():
166 if not isinstance(v,dict):
167 sfa_logger().info("\r\t%s:%s"%(k,v))
170 for (k1,v1) in v.iteritems():
171 sfa_logger().info("\r\t\t%s:%s"%(k1,v1))
172 # 'geni_api' is expected if the call succeeded at all
173 # 'peers' is needed as well as AMs typically don't have peers
174 if 'geni_api' in version and 'peers' in version:
175 # proceed with neighbours
176 for (next_name,next_url) in version['peers'].items():
177 next_interface=Interface(next_url)
178 # locate or create node in graph
180 # if found, we're good with this one
181 next_node=graph.get_node(next_interface.uid())
183 # otherwise, let's move on with it
184 graph.add_node(next_interface.uid())
185 next_node=graph.get_node(next_interface.uid())
186 node2interface[next_node]=next_interface
187 to_scan.append(next_interface)
188 graph.add_edge(interface.uid(),next_interface.uid())
189 scanned.append(interface)
190 to_scan.remove(interface)
191 # we've scanned the whole graph, let's get the labels and shapes right
192 for node in graph.nodes():
193 interface=node2interface.get(node,None)
195 for (k,v) in interface.get_layout().items():
198 sfa_logger().error("MISSED interface with node %s"%node)
201 default_outfiles=['sfa.png','sfa.svg','sfa.dot']
204 sfa_logger_goes_to_console()
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 sfa_logger().info("creating layout")
224 for outfile in options.outfiles:
225 sfa_logger().info("drawing in %s"%outfile)
227 sfa_logger().info("done")
229 if __name__ == '__main__':