Source code for fnss.topologies.topology

"""
Provides basic functions and classes for operating on network topologies.
"""
import xml.etree.ElementTree as ET
import networkx as nx
from fnss.util import _xml_type, _xml_cast_type, _xml_indent


__all__ = ['Topology', 
           'DirectedTopology',
           'od_pairs_from_topology',
           'fan_in_out_capacities',
           'read_topology',
           'write_topology'
           ]


class BaseTopology(object):
    """
    Base class for generic topology. Provides utility methods for listing nodes
    and edge properties. 
    """
    
    def capacities(self):
        """
        Return a dictionary of all link capacities, keyed by link
        
        Returns
        -------
        capacities : dict
            A dictionary of link capacity, keyed by link
        """
        return nx.get_edge_attributes(self, 'capacity')
    
    def delays(self):
        """
        Return a dictionary of all link delays, keyed by link
        
        Returns
        -------
        delays : dict
            A dictionary of link delays, keyed by link
        """
        return nx.get_edge_attributes(self, 'delay')
    
    def weights(self):
        """
        Return a dictionary of all link weights, keyed by link
        """
        return nx.get_edge_attributes(self, 'weight')
    
    def buffers(self):
        """
        Return a dictionary of all buffer sizes, keyed by interface

        Returns
        -------
        buffers : dict
            A dictionary of buffer sizes, keyed by interface. The interface is
            a tuple (u, v) which is the link to which the the interface is
            outputting
        """
        return nx.get_edge_attributes(self, 'buffer')
    
    def stacks(self):
        """
        Return a dictionary of all node stacks, keyed by node

        Returns
        -------
        stacks : dict
            A dictionary of all node stacks, keyed by node. Each node stack is
            a tuple (name, properties) where name is the stack name and
            properties is a the dictionary
        """
        return nx.get_node_attributes(self, 'stack')
    
    def applications(self):
        """
        Return a dictionary of all applications deployed, keyed by node
        
        Returns
        -------
        applications : dict
            A dictionary of all applications deployed, keyed by node.
        """
        return nx.get_node_attributes(self, 'application')


[docs]class Topology(nx.Graph, BaseTopology): """ Base class for undirected topology Parameters ---------- G : NetworkX Graph, optional A graph that can be used to initialize the topology **attr : attributes Attributes of the graph """ def __init__(self, G=None, **attr): """ Initialize the topology """ super(Topology, self).__init__(data=G, **attr)
[docs] def copy(self): """ Return a copy of the topology. Returns ------- topology : Topology A copy of the topology. See Also -------- to_directed: return a directed copy of the topology. Notes ----- This makes a complete copy of the topology including all of the node or edge attributes. Examples -------- >>> topo = Topology() >>> topo.add_path([0,1,2,3]) >>> copied_topo = topo.copy() """ return Topology(super(Topology, self).copy())
[docs] def subgraph(self, nbunch): """ Return the subgraph induced on nodes in nbunch. The induced subgraph of the graph contains the nodes in nbunch and the edges between those nodes. Parameters ---------- nbunch : list, iterable A container of nodes which will be iterated through once. Returns ------- topology : Topology A subgraph of the graph with the same edge attributes. Notes ----- The graph, edge or node attributes just point to the original graph. So changes to the node or edge structure will not be reflected in the original graph while changes to the attributes will. To create a subgraph with its own copy of the edge/node attributes use: Topology(G.subgraph(nbunch)) If edge attributes are containers, a deep copy can be obtained using: G.subgraph(nbunch).copy() For an inplace reduction of a graph to a subgraph you can remove nodes: G.remove_nodes_from([ n in G if n not in set(nbunch)]) Examples -------- >>> topo = Topology() >>> topo.add_path([0,1,2,3]) >>> topo2 = topo.subgraph([0,1,2]) >>> topo2.edges() [(0, 1), (1, 2)] """ return Topology(super(Topology, self).subgraph(nbunch))
[docs] def to_directed(self): """ Return a directed representation of the topology. Returns ------- topology : DirectedTopology A directed topology with the same name, same nodes, and with each edge (u,v,data) replaced by two directed edges (u,v,data) and (v,u,data). Notes ----- This returns a "deepcopy" of the edge, node, and graph attributes which attempts to completely copy all of the data and references. This is in contrast to the similar D=DirectedTopology(G) which returns a shallow copy of the data. See the Python copy module for more information on shallow and deep copies, http://docs.python.org/library/copy.html. Examples -------- >>> topo = Topology() >>> topo.add_path([0,1]) >>> topo2 = topo.to_directed() >>> topo2.edges() [(0, 1), (1, 0)] If already directed, return a (deep) copy >>> topo = DirectedTopology() >>> topo.add_path([0,1]) >>> topo2 = topo.to_directed() >>> topo2.edges() [(0, 1)] """ return DirectedTopology(super(Topology, self).to_directed())
[docs] def to_undirected(self): """ Return an undirected copy of the topology. Returns ------- topology : Topology A undirected copy of the topology. See Also -------- copy, add_edge, add_edges_from Notes ----- This returns a "deepcopy" of the edge, node, and graph attributes which attempts to completely copy all of the data and references. This is in contrast to the similar G=Topology(D) which returns a shallow copy of the data. See the Python copy module for more information on shallow and deep copies, http://docs.python.org/library/copy.html. Examples -------- >>> topo = Topology() # or MultiGraph, etc >>> topo.add_path([0,1]) >>> topo2 = topo.to_directed() >>> topo2.edges() [(0, 1), (1, 0)] >>> topo3 = topo2.to_undirected() >>> topo3.edges() [(0, 1)] """ return Topology(super(Topology, self).to_undirected())
[docs]class DirectedTopology(nx.DiGraph, BaseTopology): """ Base class for directed topology """ def __init__(self, G=None, **attr): """ Initialize the topology """ super(DirectedTopology, self).__init__(data=G, **attr)
[docs] def copy(self): """ Return a copy of the topology. Returns ------- topology : DirectedTopology A copy of the topology. See Also -------- to_undirected: return a undirected copy of the topology. Notes ----- This makes a complete copy of the topology including all of the node or edge attributes. Examples -------- >>> topo = DirectedTopology() >>> topo.add_path([0,1,2,3]) >>> copied_topo = topo.copy() """ return DirectedTopology(super(DirectedTopology, self).copy())
[docs] def subgraph(self, nbunch): """ Return the subgraph induced on nodes in nbunch. The induced subgraph of the graph contains the nodes in nbunch and the edges between those nodes. Parameters ---------- nbunch : list, iterable A container of nodes which will be iterated through once. Returns ------- topology : DirectedTopology A subgraph of the graph with the same edge attributes. Notes ----- The graph, edge or node attributes just point to the original graph. So changes to the node or edge structure will not be reflected in the original graph while changes to the attributes will. To create a subgraph with its own copy of the edge/node attributes use: Topology(G.subgraph(nbunch)) If edge attributes are containers, a deep copy can be obtained using: G.subgraph(nbunch).copy() For an inplace reduction of a graph to a subgraph you can remove nodes: G.remove_nodes_from([ n in G if n not in set(nbunch)]) Examples -------- >>> topo = Topology() >>> topo.add_path([0,1,2,3]) >>> topo2 = topo.subgraph([0,1,2]) >>> topo2.edges() [(0, 1), (1, 2)] """ return DirectedTopology(super(DirectedTopology, self).subgraph(nbunch))
[docs] def to_directed(self): """ Return a directed representation of the topology. Returns ------- topology : DirectedTopology A directed topology with the same name, same nodes, and with each edge (u,v,data) replaced by two directed edges (u,v,data) and (v,u,data). Notes ----- This returns a "deepcopy" of the edge, node, and graph attributes which attempts to completely copy all of the data and references. This is in contrast to the similar D=DirectedTopology(G) which returns a shallow copy of the data. See the Python copy module for more information on shallow and deep copies, http://docs.python.org/library/copy.html. Examples -------- >>> topo = Topology() >>> topo.add_path([0,1]) >>> topo2 = topo.to_directed() >>> topo2.edges() [(0, 1), (1, 0)] If already directed, return a (deep) copy >>> topo = DirectedTopology() >>> topo.add_path([0,1]) >>> topo2 = topo.to_directed() >>> topo2.edges() [(0, 1)] """ return DirectedTopology(super(DirectedTopology, self).to_directed())
[docs] def to_undirected(self): """ Return an undirected copy of the topology. Returns ------- topology : Topology A undirected copy of the topology. See Also -------- copy, add_edge, add_edges_from Notes ----- This returns a "deepcopy" of the edge, node, and graph attributes which attempts to completely copy all of the data and references. This is in contrast to the similar G=Topology(D) which returns a shallow copy of the data. See the Python copy module for more information on shallow and deep copies, http://docs.python.org/library/copy.html. Examples -------- >>> topo = Topology() # or MultiGraph, etc >>> topo.add_path([0,1]) >>> topo2 = topo.to_directed() >>> topo2.edges() [(0, 1), (1, 0)] >>> topo3 = topo2.to_undirected() >>> topo3.edges() [(0, 1)] """ return Topology(super(DirectedTopology, self).to_undirected())
[docs]def od_pairs_from_topology(topology): """ Calculate all possible origin-destination pairs of graph topology. This function does not simply calculate all possible pairs of topology nodes. Instead, it only returns pairs of nodes connected by at least a path. Parameters ---------- topology : Topology or DirectedTopology The topology whose OD pairs are calculated Returns ------- od_pair : list List containing all origin destination tuples. Examples -------- >>> import fnss >>> topology = fnss.ring_topology(3) >>> fnss.od_pairs_from_topology(topology) [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)] """ if topology.is_directed(): routes = nx.all_pairs_shortest_path_length(topology) return [(o, d) for o in routes for d in routes[o] if o != d] else: conn_components = nx.connected_components(topology) return [(o, d) for G in conn_components for o in G for d in G if o !=d]
[docs]def fan_in_out_capacities(topology): """ Calculate fan-in and fan-out capacities for all nodes of the topology. The fan-in capacity of a node is the sum of capacities of all incoming links, while the fan-out capacity is the sum of capacities of all outgoing links. Parameters ---------- topology : Topology The topology object whose fan-in and fan-out capacities are calculated, This topology must be annotated with link capacities. Returns ------- fan_in_out_capacities : tuple (fan_in, fan_out) A tuple of two dictionaries, representing, respectively the fan-in and fan-out capacities keyed by node ids Examples -------- >>> import fnss >>> topology = fnss.star_topology(3) >>> fnss.set_capacities_constant(topology, 10, 'Mbps') >>> in_cap, out_cap = fnss.fan_in_out_capacities(topology) >>> in_cap {0: 30, 1: 10, 2: 10, 3: 10} >>> out_cap {0: 30, 1: 10, 2: 10, 3: 10} """ if not topology.is_directed(): topology = topology.to_directed() fan_in = {} fan_out = {} for node in topology.nodes(): node_fan_in = 0 node_fan_out = 0 for predecessor in topology.predecessors(node): node_fan_in += topology.edge[predecessor][node]['capacity'] for successor in topology.successors(node): node_fan_out += topology.edge[node][successor]['capacity'] fan_in[node] = node_fan_in fan_out[node] = node_fan_out return fan_in, fan_out
[docs]def read_topology(path, encoding='utf-8'): """ Read a topology from an XML file and returns either a Topology or a DirectedTopology object Parameters ---------- path : str The path of the topology XML file to parse encoding : str, optional The encoding of the file Returns ------- topology: Topology or DirectedTopology """ tree = ET.parse(path) head = tree.getroot() topology = Topology() if head.attrib['linkdefault'] == 'undirected' \ else DirectedTopology() for prop in head.findall('property'): name = prop.attrib['name'] value = _xml_cast_type(prop.attrib['type'], prop.text) topology.graph[name] = value for node in head.findall('node'): v = _xml_cast_type(node.attrib['id.type'], node.attrib['id']) topology.add_node(v) for prop in node.findall('property'): name = prop.attrib['name'] value = _xml_cast_type(prop.attrib['type'], prop.text) topology.node[v][name] = value if len(node.findall('stack')) > 0: if len(node.findall('stack')) > 1: raise ET.ParseError('Invalid topology. ' \ 'A node has more than one stack.') stack = node.findall('stack')[0] stack_name = _xml_cast_type(stack.attrib['name.type'], stack.attrib['name']) stack_props = {} for prop in stack.findall('property'): name = prop.attrib['name'] value = _xml_cast_type(prop.attrib['type'], prop.text) stack_props[name] = value topology.node[v]['stack'] = (stack_name, stack_props) if len(node.findall('application')) > 0: topology.node[v]['application'] = {} for application in node.findall('application'): app_name = _xml_cast_type(application.attrib['name.type'], application.attrib['name']) app_props = {} for prop in application.findall('property'): name = prop.attrib['name'] value = _xml_cast_type(prop.attrib['type'], prop.text) app_props[name] = value topology.node[v]['application'][app_name] = app_props for edge in head.findall('link'): u = _xml_cast_type(edge.find('from').attrib['type'], edge.find('from').text) v = _xml_cast_type(edge.find('to').attrib['type'], edge.find('to').text) topology.add_edge(u, v) for prop in edge.findall('property'): name = prop.attrib['name'] value = _xml_cast_type(prop.attrib['type'], prop.text) topology.edge[u][v][name] = value return topology
[docs]def write_topology(topology, path, encoding='utf-8', prettyprint=True): """ Writes a topology object on an XML file Parameters ---------- topology : Topology The topology object to write path : str The file ob which the topology will be written encoding : str, optional The encoding of the target file prettyprint : bool, optional Indent the XML code in the output file """ head = ET.Element("topology") head.attrib['linkdefault'] = 'directed' if topology.is_directed() \ else 'undirected' for name, value in topology.graph.items(): prop = ET.SubElement(head, "property") prop.attrib['name'] = name prop.attrib['type'] = _xml_type(value) prop.text = str(value) for v in topology.nodes(): node = ET.SubElement(head, "node") node.attrib['id'] = str(v) node.attrib['id.type'] = _xml_type(v) for name, value in topology.node[v].items(): if name is 'stack': stack_name, stack_props = topology.node[v]['stack'] stack = ET.SubElement(node, "stack") stack.attrib['name'] = stack_name stack.attrib['name.type'] = _xml_type(stack_name) for prop_name, prop_value in stack_props.items(): prop = ET.SubElement(stack, "property") prop.attrib['name'] = prop_name prop.attrib['type'] = _xml_type(prop_value) prop.text = str(prop_value) elif name is 'application': for application_name, application_props in \ topology.node[v]['application'].items(): application = ET.SubElement(node, "application") application.attrib['name'] = application_name application.attrib['name.type'] = \ _xml_type(application_name) for prop_name, prop_value in application_props.items(): prop = ET.SubElement(application, "property") prop.attrib['name'] = prop_name prop.attrib['type'] = _xml_type(prop_value) prop.text = str(prop_value) else: prop = ET.SubElement(node, "property") prop.attrib['name'] = name prop.attrib['type'] = _xml_type(value) prop.text = str(value) for u, v in topology.edges(): link = ET.SubElement(head, "link") from_node = ET.SubElement(link, "from") from_node.attrib['type'] = _xml_type(u) from_node.text = str(u) to_node = ET.SubElement(link, "to") to_node.attrib['type'] = _xml_type(v) to_node.text = str(v) for name, value in topology.edge[u][v].items(): prop = ET.SubElement(link, "property") prop.attrib['name'] = name prop.attrib['type'] = _xml_type(value) prop.text = str(value) if prettyprint: _xml_indent(head) ET.ElementTree(head).write(path, encoding=encoding)