1
0
mirror of https://github.com/CumulusNetworks/ifupdown2.git synced 2024-05-06 15:54:50 +00:00
Files
CumulusNetworks-ifupdown2/pkg/gvgen.py
roopa 37c0543d34 More fixes and changes
Ticket: CM-1438
Reviewed By:
Testing Done: unit tested with all kinds of interfaces

some high level changes
- moved ipv4/ipv6 address handling in a single module. dhcp
into a separate module.
- new link 'up' module
- igmp fixes
- many other fixes
2014-01-30 22:36:41 -08:00

616 lines
20 KiB
Python

#!/usr/bin/python
# -*- coding: utf-8 -*-
# $Id: gvgen.py 13085 2008-02-25 16:11:50Z toady $
"""
GvGen - Generate dot file to be processed by graphviz
Copyright (c) 2007-2008 INL
Written by Sebastien Tricaud <sebastien.tricaud@inl.fr>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
"""
from sys import stdout
gvgen_version = "0.9"
debug = 0
debug_tree_unroll = 0
class GvGen:
"""
Graphviz dot language Generation Class
For example of usage, please see the __main__ function
"""
def __init__(self, legend_name=None, options="compound=true;"): # allow links between clusters
self.max_line_width = 10
self.max_arrow_width = 2
self.line_factor = 1
self.arrow_factor = 0.5
self.initial_line_width = 1.2
self.initial_arrow_width = 0.8
self.options = options
self.__id = 0
self.__nodes = []
self.__links = []
self.__browse_level = 0 # Stupid depth level for self.browse
self.__opened_braces = [] # We count opened clusters
self.fd=stdout # File descriptor to output dot
self.padding_str=" " # Left padding to make children and parent look nice
self.__styles = {}
self.__default_style = []
self.smart_mode = 0 # Disabled by default
# The graph has a legend
if legend_name:
self.options = self.options + "rankdir=LR;"
self.legend = self.newItem(legend_name)
def __node_new(self, name, parent=None, distinct=None):
"""
Create a new node in the data structure
@name: Name of the node, that will be the graphviz label
@parent: The node parent
@distinct: if true, will not create and node that has the same name
Returns: The node created
"""
# We first check for distincts
if distinct:
if self.__nodes:
for e in self.__nodes:
props = e['properties']
if props['label'] == name:
# We found the label name matching, we return -1
return -1
# We now insert into gvgen datastructure
self.__id += 1
node = {'id': self.__id, # Internal ID
'lock': 0, # When the node is written, it is locked to avoid further references
'parent': parent, # Node parent for easy graphviz clusters
'style':None, # Style that GvGen allow you to create
'properties': { # Custom graphviz properties you can add, which will overide previously defined styles
'label': name
}
}
# Parents should be sorted first
if parent:
self.__nodes.insert(1, node)
else:
self.__nodes.append(node)
return node
def __link_smart(self, link):
"""
Creates a smart link if smart_mode activated:
if a -> b exists, and we now add a <- b,
instead of doing: a -> b
<-
we do: a <-> b
"""
linkfrom = self.__link_exists(link['from_node'], link['to_node'])
linkto = self.__link_exists(link['to_node'], link['from_node'])
if self.smart_mode:
if linkto:
self.__links.remove(linkto)
self.propertyAppend(link, "dir", "both")
pw = self.propertyGet(linkfrom, "penwidth")
if pw:
pw = float(pw)
pw += self.line_factor
if pw < self.max_line_width:
self.propertyAppend(linkfrom, "penwidth", str(pw))
else:
self.propertyAppend(link, "penwidth", str(self.initial_line_width))
aw = self.propertyGet(linkfrom, "arrowsize")
if aw:
aw = float(aw)
if aw < self.max_arrow_width:
aw += self.arrow_factor
self.propertyAppend(linkfrom, "arrowsize", str(aw))
else:
self.propertyAppend(link, "arrowsize", str(self.initial_arrow_width))
if not linkfrom:
self.__links.append(link)
def __link_new(self, from_node, to_node, label = None, cl_from_node=None, cl_to_node=None):
"""
Creates a link between two nodes
@from_node: The node the link comes from
@to_node: The node the link goes to
Returns: The link created
"""
link = {'from_node': from_node,
'to_node': to_node,
'style':None, # Style that GvGen allow you to create
'properties': {}, # Custom graphviz properties you can add, which will overide previously defined styles
'cl_from_node':None, # When linking from a cluster, the link appears from this node
'cl_to_node':None, # When linking to a cluster, the link appears to go to this node
}
if label:
link['properties']['label'] = label
if cl_from_node:
link['cl_from_node'] = cl_from_node
if cl_to_node:
link['cl_to_node'] = cl_to_node
# We let smart link work for us
self.__link_smart(link)
return link
def __link_exists(self, from_node, to_node):
"""
Find if a link exists
@from_node: The node the link comes from
@to_node: The node the link goes to
Returns: true if the given link already exists
"""
for link in self.__links:
if link['from_node'] == from_node and link['to_node'] == to_node:
return link
return None
def __has_children(self, parent):
"""
Find children to a given parent
Returns the children list
"""
children_list = []
for e in self.__nodes:
if e['parent'] == parent:
children_list.append(e)
return children_list
def newItem(self, name, parent=None, distinct=None):
node = self.__node_new(name, parent, distinct)
return node
def newLink(self, src, dst, label=None, cl_src=None, cl_dst=None):
"""
Link two existing nodes with each other
"""
return self.__link_new(src, dst, label, cl_src, cl_dst)
def debug(self):
for e in self.__nodes:
print "element = " + str(e['id'])
def collectLeaves(self, parent):
"""
Collect every leaf sharing the same parent
"""
cl = []
for e in self.__nodes:
if e['parent'] == parent:
cl.append(e)
return cl
def collectUnlockedLeaves(self, parent):
"""
Collect every leaf sharing the same parent
unless it is locked
"""
cl = []
for e in self.__nodes:
if e['parent'] == parent:
if not e['lock']:
cl.append(e)
return cl
def lockNode(self, node):
node['lock'] = 1
#
# Start: styles management
#
def styleAppend(self, stylename, key, val):
if stylename not in self.__styles:
self.__styles[stylename] = []
self.__styles[stylename].append([key, val])
def styleApply(self, stylename, node_or_link):
node_or_link['style'] = stylename
def styleDefaultAppend(self, key, val):
self.__default_style.append([key, val])
#
# End: styles management
#
#
# Start: properties management
#
def propertiesAsStringGet(self, node, props):
"""
Get the properties string according to parent/children
props is the properties dictionnary
"""
allProps = {}
#
# Default style come first, they can then be overriden
#
if self.__default_style:
allProps.update(self.__default_style)
#
# First, we build the styles
#
if node['style']:
stylename = node['style']
allProps.update(self.__styles[stylename])
#
# Now we build the properties:
# remember they override styles
#
allProps.update(props)
if self.__has_children(node):
propStringList = ["%s=\"%s\";\n" % (k, v) for k, v in allProps.iteritems()]
properties = ''.join(propStringList)
else:
if props:
propStringList = ["%s=\"%s\"" % (k, v) for k, v in allProps.iteritems()]
properties = '[' + ','.join(propStringList) + ']'
else:
properties = ''
return properties
def propertiesLinkAsStringGet(self, link):
has_props = 0
props = {}
if link['style']:
stylename = link['style']
# Build the properties string for node
props.update(self.__styles[stylename])
props.update(link['properties'])
properties = ''
if props:
properties += ','.join(["%s=\"%s\"" % (str(k),str(val)) for k, val in props.iteritems()])
return properties
def propertyForeachLinksAppend(self, node, key, val):
for l in self.__links:
if l['from_node'] == node:
props = l['properties']
props[key] = val
def propertyAppend(self, node_or_link, key, val):
"""
Append a property to the wanted node or link
mynode = newItem(\"blah\")
Ex. propertyAppend(mynode, \"color\", \"red\")
"""
props = node_or_link['properties']
props[key] = val
def propertyGet(self, node_or_link, key):
"""
Get the value of a given property
Ex. prop = propertyGet(node, \"color\")
"""
try:
props = node_or_link['properties']
return props[key]
except:
return None
def propertyRemove(self, node_or_link, key):
"""
Remove a property to the wanted node or link
mynode = newItem(\"blah\")
Ex. propertyRemove(mynode, \"color\")
"""
props = node_or_link['properties']
del props[key]
#
# End: Properties management
#
#
# For a good legend, the graph must have
# rankdir=LR property set.
#
def legendAppend(self, legendstyle, legenddescr, labelin=None):
if labelin:
item = self.newItem(legenddescr, self.legend)
self.styleApply(legendstyle, item)
else:
style = self.newItem("", self.legend)
descr = self.newItem(legenddescr, self.legend)
self.styleApply(legendstyle, style)
link = self.newLink(style,descr)
self.propertyAppend(link, "dir", "none")
self.propertyAppend(link, "style", "invis")
self.propertyAppend(descr,"shape","plaintext")
def tree_debug(self, level, node, children):
if children:
print "(level:%d) Eid:%d has children (%s)" % (level,node['id'],str(children))
else:
print "Eid:"+str(node['id'])+" has no children"
#
# Core function that outputs the data structure tree into dot language
#
def tree(self, level, node, children):
"""
Core function to output dot which sorts out parents and children
and do it in the right order
"""
if debug:
print "/* Grabed node = %s*/" % str(node['id'])
if node['lock'] == 1: # The node is locked, nothing should be printed
if debug:
print "/* The node (%s) is locked */" % str(node['id'])
if self.__opened_braces:
self.fd.write(level * self.padding_str)
self.fd.write("}\n")
self.__opened_braces.pop()
return
props = node['properties']
if children:
node['lock'] = 1
self.fd.write(level * self.padding_str)
self.fd.write(self.padding_str + "subgraph cluster%d {\n" % node['id'])
properties = self.propertiesAsStringGet(node, props)
self.fd.write(level * self.padding_str)
self.fd.write(self.padding_str + "%s" % properties)
self.__opened_braces.append([node,level])
else:
# We grab appropriate properties
properties = self.propertiesAsStringGet(node, props)
# We get the latest opened elements
if self.__opened_braces:
last_cluster,last_level = self.__opened_braces[-1]
else:
last_cluster = None
last_level = 0
if debug:
if node['parent']:
parent_str = str(node['parent']['id'])
else:
parent_str = 'None'
if last_cluster:
last_cluster_str = str(last_cluster['id'])
else:
last_cluster_str = 'None'
print "/* e[parent] = %s, last_cluster = %s, last_level = %d, opened_braces: %s */" % (parent_str, last_cluster_str,last_level,str(self.__opened_braces))
# Write children/parent with properties
if node['parent']:
if node['parent'] != last_cluster:
while node['parent'] < last_cluster:
last_cluster,last_level = self.__opened_braces[-1]
if node['parent'] == last_cluster:
last_level += 1
# We browse any property to build a string
self.fd.write(last_level * self.padding_str)
self.fd.write(self.padding_str + "node%d %s;\n" % (node['id'], properties))
node['lock'] = 1
else:
self.fd.write(last_level * self.padding_str)
self.fd.write(self.padding_str + "}\n")
self.__opened_braces.pop()
else:
self.fd.write(level * self.padding_str)
self.fd.write(self.padding_str + "node%d %s;\n" % (node['id'], properties) )
node['lock'] = 1
cl = self.collectUnlockedLeaves(node['parent'])
for l in cl:
props = l['properties']
properties = self.propertiesAsStringGet(l, props)
self.fd.write(last_level * self.padding_str)
self.fd.write(self.padding_str + self.padding_str + "node%d %s;\n" % (l['id'], properties))
node['lock'] = 1
self.lockNode(l)
self.fd.write(level * self.padding_str + "}\n")
self.__opened_braces.pop()
else:
self.fd.write(self.padding_str + "node%d %s;\n" % (node['id'], properties))
node['lock'] = 1
def browse(self, node, cb):
"""
Browse nodes in a tree and calls cb providing node parameters
"""
children = self.__has_children(node)
if children:
cb(self.__browse_level, node, str(children))
for c in children:
self.__browse_level += 1
self.browse(c, cb)
else:
cb(self.__browse_level, node, None)
self.__browse_level = 0
# if debug:
# print "This node is not a child: " + str(node)
def dotLinks(self, node):
"""
Write links between nodes
"""
for l in self.__links:
if l['from_node'] == node:
# Check if we link form a cluster
children = self.__has_children(node)
if children:
if l['cl_from_node']:
src = l['cl_from_node']['id']
else:
src = children[0]['id']
cluster_src = node['id']
else:
src = node['id']
cluster_src = ''
# Check if we link to a cluster
children = self.__has_children(l['to_node'])
if children:
if l['cl_to_node']:
dst = l['cl_to_node']['id']
else:
dst = children[0]['id']
cluster_dst = l['to_node']['id']
else:
dst = l['to_node']['id']
cluster_dst = ''
self.fd.write("node%d->node%d" % (src, dst))
props = self.propertiesLinkAsStringGet(l)
# Build new properties if we link from or to a cluster
if cluster_src:
if props:
props += ','
props += "ltail=cluster%d" % cluster_src
if cluster_dst:
if props:
props += ','
props += "lhead=cluster%d" % cluster_dst
if props:
self.fd.write(" [%s]" % props)
self.fd.write(";\n")
def dot(self, name=None, fd=stdout):
"""
Translates the datastructure into dot
"""
try:
self.fd = fd
self.fd.write("/* Generated by GvGen v.%s (http://software.inl.fr/trac/wiki/GvGen) */\n\n" % (gvgen_version))
if name is None:
self.fd.write("digraph G {\n")
else:
self.fd.write("digraph %s {\n" %name)
if self.options:
self.fd.write(self.options+"\n")
# We write parents and children in order
for e in self.__nodes:
if debug_tree_unroll:
self.browse(e, self.tree_debug)
else:
self.browse(e, self.tree)
# We write the connection between nodes
for e in self.__nodes:
self.dotLinks(e)
# We put all the nodes belonging to the parent
self.fd.write("}\n")
finally:
# Remove our reference to file descriptor
self.fd = None
if __name__ == "__main__":
graph = GvGen()
graph.smart_mode = 1
graph.styleDefaultAppend("color","blue")
parents = graph.newItem("Parents")
father = graph.newItem("Bob", parents)
mother = graph.newItem("Alice", parents)
children = graph.newItem("Children")
child1 = graph.newItem("Carol", children)
child2 = graph.newItem("Eve", children)
child3 = graph.newItem("Isaac", children)
postman = graph.newItem("Postman")
graph.newLink(father,child1)
graph.newLink(child1, father)
graph.newLink(father, child1)
graph.newLink(father,child2)
graph.newLink(mother,child2)
myl = graph.newLink(mother,child1)
graph.newLink(mother,child3)
graph.newLink(postman,child3,"Email is safer")
graph.newLink(parents, postman) # Cluster link
graph.propertyForeachLinksAppend(parents, "color", "blue")
graph.propertyForeachLinksAppend(father, "label", "My big link")
graph.propertyForeachLinksAppend(father, "color", "red")
graph.propertyAppend(postman, "color", "red")
graph.propertyAppend(postman, "fontcolor", "white")
graph.styleAppend("link", "label", "mylink")
graph.styleAppend("link", "color", "green")
graph.styleApply("link", myl)
graph.propertyAppend(myl, "arrowhead", "empty")
graph.styleAppend("Post", "color", "blue")
graph.styleAppend("Post", "style", "filled")
graph.styleAppend("Post", "shape", "rectangle")
graph.styleApply("Post", postman)
graph.dot()