"""Functions for creating or importing topologies for experiments.
To create a custom topology, create a function returning an instance of the
`IcnTopology` class. An IcnTopology is simply a subclass of a Topology class
provided by FNSS.
A valid ICN topology must have the following attributes:
* Each node must have one stack among: source, receiver, router
* The topology must have an attribute called `icr_candidates` which is a set
of router nodes on which a cache may be possibly deployed. Caches are not
deployed directly at topology creation, instead they are deployed by a
cache placement algorithm.
"""
from __future__ import division
from os import path
import networkx as nx
import fnss
from icarus.registry import register_topology_factory
__all__ = [
'IcnTopology',
'topology_tree',
'topology_path',
'topology_ring',
'topology_mesh',
'topology_geant',
'topology_tiscali',
'topology_wide',
'topology_garr',
'topology_rocketfuel_latency'
]
# Delays
# These values are suggested by this Computer Networks 2011 paper:
# http://www.cs.ucla.edu/classes/winter09/cs217/2011CN_NameRouting.pdf
# which is citing as source of this data, measurements from this IMC'06 paper:
# http://www.mpi-sws.org/~druschel/publications/ds2-imc.pdf
INTERNAL_LINK_DELAY = 2
EXTERNAL_LINK_DELAY = 34
# Path where all topologies are stored
TOPOLOGY_RESOURCES_DIR = path.abspath(path.join(path.dirname(__file__),
path.pardir, path.pardir,
'resources', 'topologies'))
[docs]class IcnTopology(fnss.Topology):
"""Class modelling an ICN topology
An ICN topology is a simple FNSS Topology with addition methods that
return sets of caching nodes, sources and receivers.
"""
[docs] def cache_nodes(self):
"""Return a dictionary mapping nodes with a cache and respective cache
size
Returns
-------
cache_nodes : dict
Dictionary mapping node identifiers and cache size
"""
return {v: self.node[v]['stack'][1]['cache_size']
for v in self
if 'stack' in self.node[v]
and 'cache_size' in self.node[v]['stack'][1]
}
[docs] def sources(self):
"""Return a set of source nodes
Returns
-------
sources : set
Set of source nodes
"""
return set(v for v in self
if 'stack' in self.node[v]
and self.node[v]['stack'][0] == 'source')
[docs] def receivers(self):
"""Return a set of receiver nodes
Returns
-------
receivers : set
Set of receiver nodes
"""
return set(v for v in self
if 'stack' in self.node[v]
and self.node[v]['stack'][0] == 'receiver')
[docs]@register_topology_factory('TREE')
def topology_tree(k, h, delay=1, **kwargs):
"""Returns a tree topology, with a source at the root, receivers at the
leafs and caches at all intermediate nodes.
Parameters
----------
h : int
The height of the tree
k : int
The branching factor of the tree
delay : float
The link delay in milliseconds
Returns
-------
topology : IcnTopology
The topology object
"""
topology = fnss.k_ary_tree_topology(k, h)
receivers = [v for v in topology.nodes_iter()
if topology.node[v]['depth'] == h]
sources = [v for v in topology.nodes_iter()
if topology.node[v]['depth'] == 0]
routers = [v for v in topology.nodes_iter()
if topology.node[v]['depth'] > 0
and topology.node[v]['depth'] < h]
topology.graph['icr_candidates'] = set(routers)
for v in sources:
fnss.add_stack(topology, v, 'source')
for v in receivers:
fnss.add_stack(topology, v, 'receiver')
for v in routers:
fnss.add_stack(topology, v, 'router')
# set weights and delays on all links
fnss.set_weights_constant(topology, 1.0)
fnss.set_delays_constant(topology, delay, 'ms')
# label links as internal
for u, v in topology.edges_iter():
topology.edge[u][v]['type'] = 'internal'
return IcnTopology(topology)
[docs]@register_topology_factory('PATH')
def topology_path(n, delay=1, **kwargs):
"""Return a path topology with a receiver on node `0` and a source at node
'n-1'
Parameters
----------
n : int (>=3)
The number of nodes
delay : float
The link delay in milliseconds
Returns
-------
topology : IcnTopology
The topology object
"""
topology = fnss.line_topology(n)
receivers = [0]
routers = range(1, n - 1)
sources = [n - 1]
topology.graph['icr_candidates'] = set(routers)
for v in sources:
fnss.add_stack(topology, v, 'source')
for v in receivers:
fnss.add_stack(topology, v, 'receiver')
for v in routers:
fnss.add_stack(topology, v, 'router')
# set weights and delays on all links
fnss.set_weights_constant(topology, 1.0)
fnss.set_delays_constant(topology, delay, 'ms')
# label links as internal or external
for u, v in topology.edges_iter():
topology.edge[u][v]['type'] = 'internal'
return IcnTopology(topology)
[docs]@register_topology_factory('RING')
def topology_ring(n, delay_int=1, delay_ext=5, **kwargs):
"""Returns a ring topology
This topology is comprised of a ring of *n* nodes. Each of these nodes is
attached to a receiver. In addition one router is attached to a source.
Therefore, this topology has in fact 2n + 1 nodes.
It models the case of a metro ring network, with many receivers and one
only source towards the core network.
Parameters
----------
n : int
The number of routers in the ring
delay_int : float
The internal link delay in milliseconds
delay_ext : float
The external link delay in milliseconds
Returns
-------
topology : IcnTopology
The topology object
"""
topology = fnss.ring_topology(n)
routers = range(n)
receivers = range(n, 2 * n)
source = 2 * n
internal_links = zip(routers, receivers)
external_links = [(routers[0], source)]
for u, v in internal_links:
topology.add_edge(u, v, type='internal')
for u, v in external_links:
topology.add_edge(u, v, type='external')
topology.graph['icr_candidates'] = set(routers)
fnss.add_stack(topology, source, 'source')
for v in receivers:
fnss.add_stack(topology, v, 'receiver')
for v in routers:
fnss.add_stack(topology, v, 'router')
# set weights and delays on all links
fnss.set_weights_constant(topology, 1.0)
fnss.set_delays_constant(topology, delay_int, 'ms', internal_links)
fnss.set_delays_constant(topology, delay_ext, 'ms', external_links)
return IcnTopology(topology)
[docs]@register_topology_factory('MESH')
def topology_mesh(n, m, delay_int=1, delay_ext=5, **kwargs):
"""Returns a ring topology
This topology is comprised of a mesh of *n* nodes. Each of these nodes is
attached to a receiver. In addition *m* router are attached each to a source.
Therefore, this topology has in fact 2n + m nodes.
Parameters
----------
n : int
The number of routers in the ring
m : int
The number of sources
delay_int : float
The internal link delay in milliseconds
delay_ext : float
The external link delay in milliseconds
Returns
-------
topology : IcnTopology
The topology object
"""
if m > n:
raise ValueError("m cannot be greater than n")
topology = fnss.full_mesh_topology(n)
routers = range(n)
receivers = range(n, 2 * n)
sources = range(2 * n, 2 * n + m)
internal_links = zip(routers, receivers)
external_links = zip(routers[:m], sources)
for u, v in internal_links:
topology.add_edge(u, v, type='internal')
for u, v in external_links:
topology.add_edge(u, v, type='external')
topology.graph['icr_candidates'] = set(routers)
for v in sources:
fnss.add_stack(topology, v, 'source')
for v in receivers:
fnss.add_stack(topology, v, 'receiver')
for v in routers:
fnss.add_stack(topology, v, 'router')
# set weights and delays on all links
fnss.set_weights_constant(topology, 1.0)
fnss.set_delays_constant(topology, delay_int, 'ms', internal_links)
fnss.set_delays_constant(topology, delay_ext, 'ms', external_links)
return IcnTopology(topology)
[docs]@register_topology_factory('GEANT')
def topology_geant(**kwargs):
"""Return a scenario based on GEANT topology
Parameters
----------
seed : int, optional
The seed used for random number generation
Returns
-------
topology : fnss.Topology
The topology object
"""
# 240 nodes in the main component
topology = fnss.parse_topology_zoo(path.join(TOPOLOGY_RESOURCES_DIR,
'Geant2012.graphml')
).to_undirected()
topology = list(nx.connected_component_subgraphs(topology))[0]
deg = nx.degree(topology)
receivers = [v for v in topology.nodes() if deg[v] == 1] # 8 nodes
icr_candidates = [v for v in topology.nodes() if deg[v] > 2] # 19 nodes
# attach sources to topology
source_attachments = [v for v in topology.nodes() if deg[v] == 2] # 13 nodes
sources = []
for v in source_attachments:
u = v + 1000 # node ID of source
topology.add_edge(v, u)
sources.append(u)
routers = [v for v in topology.nodes() if v not in sources + receivers]
# add stacks to nodes
topology.graph['icr_candidates'] = set(icr_candidates)
for v in sources:
fnss.add_stack(topology, v, 'source')
for v in receivers:
fnss.add_stack(topology, v, 'receiver')
for v in routers:
fnss.add_stack(topology, v, 'router')
# set weights and delays on all links
fnss.set_weights_constant(topology, 1.0)
fnss.set_delays_constant(topology, INTERNAL_LINK_DELAY, 'ms')
# label links as internal or external
for u, v in topology.edges_iter():
if u in sources or v in sources:
topology.edge[u][v]['type'] = 'external'
# this prevents sources to be used to route traffic
fnss.set_weights_constant(topology, 1000.0, [(u, v)])
fnss.set_delays_constant(topology, EXTERNAL_LINK_DELAY, 'ms', [(u, v)])
else:
topology.edge[u][v]['type'] = 'internal'
return IcnTopology(topology)
[docs]@register_topology_factory('TISCALI')
def topology_tiscali(**kwargs):
"""Return a scenario based on Tiscali topology, parsed from RocketFuel dataset
Parameters
----------
seed : int, optional
The seed used for random number generation
Returns
-------
topology : fnss.Topology
The topology object
"""
# 240 nodes in the main component
topology = fnss.parse_rocketfuel_isp_map(path.join(TOPOLOGY_RESOURCES_DIR,
'3257.r0.cch')
).to_undirected()
topology = list(nx.connected_component_subgraphs(topology))[0]
# degree of nodes
deg = nx.degree(topology)
# nodes with degree = 1
onedeg = [v for v in topology.nodes() if deg[v] == 1] # they are 80
# we select as caches nodes with highest degrees
# we use as min degree 6 --> 36 nodes
# If we changed min degrees, that would be the number of caches we would have:
# Min degree N caches
# 2 160
# 3 102
# 4 75
# 5 50
# 6 36
# 7 30
# 8 26
# 9 19
# 10 16
# 11 12
# 12 11
# 13 7
# 14 3
# 15 3
# 16 2
icr_candidates = [v for v in topology.nodes() if deg[v] >= 6] # 36 nodes
# sources are node with degree 1 whose neighbor has degree at least equal to 5
# we assume that sources are nodes connected to a hub
# they are 44
sources = [v for v in onedeg if deg[list(topology.edge[v].keys())[0]] > 4.5] # they are
# receivers are node with degree 1 whose neighbor has degree at most equal to 4
# we assume that receivers are nodes not well connected to the network
# they are 36
receivers = [v for v in onedeg if deg[list(topology.edge[v].keys())[0]] < 4.5]
# we set router stacks because some strategies will fail if no stacks
# are deployed
routers = [v for v in topology.nodes() if v not in sources + receivers]
# set weights and delays on all links
fnss.set_weights_constant(topology, 1.0)
fnss.set_delays_constant(topology, INTERNAL_LINK_DELAY, 'ms')
# Deploy stacks
topology.graph['icr_candidates'] = set(icr_candidates)
for v in sources:
fnss.add_stack(topology, v, 'source')
for v in receivers:
fnss.add_stack(topology, v, 'receiver')
for v in routers:
fnss.add_stack(topology, v, 'router')
# label links as internal or external
for u, v in topology.edges():
if u in sources or v in sources:
topology.edge[u][v]['type'] = 'external'
# this prevents sources to be used to route traffic
fnss.set_weights_constant(topology, 1000.0, [(u, v)])
fnss.set_delays_constant(topology, EXTERNAL_LINK_DELAY, 'ms', [(u, v)])
else:
topology.edge[u][v]['type'] = 'internal'
return IcnTopology(topology)
[docs]@register_topology_factory('WIDE')
def topology_wide(**kwargs):
"""Return a scenario based on GARR topology
Parameters
----------
seed : int, optional
The seed used for random number generation
Returns
-------
topology : fnss.Topology
The topology object
"""
topology = fnss.parse_topology_zoo(path.join(TOPOLOGY_RESOURCES_DIR, 'WideJpn.graphml')).to_undirected()
# sources are nodes representing neighbouring AS's
sources = [9, 8, 11, 13, 12, 15, 14, 17, 16, 19, 18]
# receivers are internal nodes with degree = 1
receivers = [27, 28, 3, 5, 4, 7]
# caches are all remaining nodes --> 27 caches
routers = [n for n in topology.nodes() if n not in receivers + sources]
# All routers can be upgraded to ICN functionalities
icr_candidates = routers
# set weights and delays on all links
fnss.set_weights_constant(topology, 1.0)
fnss.set_delays_constant(topology, INTERNAL_LINK_DELAY, 'ms')
# Deploy stacks
topology.graph['icr_candidates'] = set(icr_candidates)
for v in sources:
fnss.add_stack(topology, v, 'source')
for v in receivers:
fnss.add_stack(topology, v, 'receiver')
for v in routers:
fnss.add_stack(topology, v, 'router')
# label links as internal or external
for u, v in topology.edges():
if u in sources or v in sources:
topology.edge[u][v]['type'] = 'external'
# this prevents sources to be used to route traffic
fnss.set_weights_constant(topology, 1000.0, [(u, v)])
fnss.set_delays_constant(topology, EXTERNAL_LINK_DELAY, 'ms', [(u, v)])
else:
topology.edge[u][v]['type'] = 'internal'
return IcnTopology(topology)
[docs]@register_topology_factory('GARR')
def topology_garr(**kwargs):
"""Return a scenario based on GARR topology
Parameters
----------
seed : int, optional
The seed used for random number generation
Returns
-------
topology : fnss.Topology
The topology object
"""
topology = fnss.parse_topology_zoo(path.join(TOPOLOGY_RESOURCES_DIR, 'Garr201201.graphml')).to_undirected()
# sources are nodes representing neighbouring AS's
sources = [0, 2, 3, 5, 13, 16, 23, 24, 25, 27, 51, 52, 54]
# receivers are internal nodes with degree = 1
receivers = [1, 7, 8, 9, 11, 12, 19, 26, 28, 30, 32, 33, 41, 42, 43, 47, 48, 50, 53, 57, 60]
# caches are all remaining nodes --> 27 caches
routers = [n for n in topology.nodes() if n not in receivers + sources]
icr_candidates = routers
# set weights and delays on all links
fnss.set_weights_constant(topology, 1.0)
fnss.set_delays_constant(topology, INTERNAL_LINK_DELAY, 'ms')
# Deploy stacks
topology.graph['icr_candidates'] = set(icr_candidates)
for v in sources:
fnss.add_stack(topology, v, 'source')
for v in receivers:
fnss.add_stack(topology, v, 'receiver')
for v in routers:
fnss.add_stack(topology, v, 'router')
# label links as internal or external
for u, v in topology.edges():
if u in sources or v in sources:
topology.edge[u][v]['type'] = 'external'
# this prevents sources to be used to route traffic
fnss.set_weights_constant(topology, 1000.0, [(u, v)])
fnss.set_delays_constant(topology, EXTERNAL_LINK_DELAY, 'ms', [(u, v)])
else:
topology.edge[u][v]['type'] = 'internal'
return IcnTopology(topology)
@register_topology_factory('GARR_2')
def topology_garr2(**kwargs):
"""Return a scenario based on GARR topology.
Differently from plain GARR, this topology some receivers are appended to
routers and only a subset of routers which are actually on the path of some
traffic are selected to become ICN routers. These changes make this
topology more realistic.
Parameters
----------
seed : int, optional
The seed used for random number generation
Returns
-------
topology : fnss.Topology
The topology object
"""
topology = fnss.parse_topology_zoo(path.join(TOPOLOGY_RESOURCES_DIR, 'Garr201201.graphml')).to_undirected()
# sources are nodes representing neighbouring AS's
sources = [0, 2, 3, 5, 13, 16, 23, 24, 25, 27, 51, 52, 54]
# receivers are internal nodes with degree = 1
receivers = [1, 7, 8, 9, 11, 12, 19, 26, 28, 30, 32, 33, 41, 42, 43, 47, 48, 50, 53, 57, 60]
# routers are all remaining nodes --> 27 caches
routers = [n for n in topology.nodes_iter() if n not in receivers + sources]
artificial_receivers = list(range(1000, 1000 + len(routers)))
for i in range(len(routers)):
topology.add_edge(routers[i], artificial_receivers[i])
receivers += artificial_receivers
# Caches to nodes with degree > 3 (after adding artificial receivers)
degree = nx.degree(topology)
icr_candidates = [n for n in topology.nodes_iter() if degree[n] > 3.5]
# set weights and delays on all links
fnss.set_weights_constant(topology, 1.0)
fnss.set_delays_constant(topology, INTERNAL_LINK_DELAY, 'ms')
# Deploy stacks
topology.graph['icr_candidates'] = set(icr_candidates)
for v in sources:
fnss.add_stack(topology, v, 'source')
for v in receivers:
fnss.add_stack(topology, v, 'receiver')
for v in routers:
fnss.add_stack(topology, v, 'router')
# label links as internal or external
for u, v in topology.edges():
if u in sources or v in sources:
topology.edge[u][v]['type'] = 'external'
# this prevents sources to be used to route traffic
fnss.set_weights_constant(topology, 1000.0, [(u, v)])
fnss.set_delays_constant(topology, EXTERNAL_LINK_DELAY, 'ms', [(u, v)])
else:
topology.edge[u][v]['type'] = 'internal'
return IcnTopology(topology)
@register_topology_factory('GEANT_2')
def topology_geant2(**kwargs):
"""Return a scenario based on GEANT topology.
Differently from plain GEANT, this topology some receivers are appended to
routers and only a subset of routers which are actually on the path of some
traffic are selected to become ICN routers. These changes make this
topology more realistic.
Parameters
----------
seed : int, optional
The seed used for random number generation
Returns
-------
topology : fnss.Topology
The topology object
"""
# 53 nodes
topology = fnss.parse_topology_zoo(path.join(TOPOLOGY_RESOURCES_DIR,
'Geant2012.graphml')
).to_undirected()
topology = list(nx.connected_component_subgraphs(topology))[0]
deg = nx.degree(topology)
receivers = [v for v in topology.nodes() if deg[v] == 1] # 8 nodes
# attach sources to topology
source_attachments = [v for v in topology.nodes() if deg[v] == 2] # 13 nodes
sources = []
for v in source_attachments:
u = v + 1000 # node ID of source
topology.add_edge(v, u)
sources.append(u)
routers = [v for v in topology.nodes() if v not in sources + receivers]
# Put caches in nodes with top betweenness centralities
betw = nx.betweenness_centrality(topology)
routers = sorted(routers, key=lambda k: betw[k])
# Select as ICR candidates the top 50% routers for betweenness centrality
icr_candidates = routers[len(routers) // 2:]
# add stacks to nodes
topology.graph['icr_candidates'] = set(icr_candidates)
for v in sources:
fnss.add_stack(topology, v, 'source')
for v in receivers:
fnss.add_stack(topology, v, 'receiver')
for v in routers:
fnss.add_stack(topology, v, 'router')
# set weights and delays on all links
fnss.set_weights_constant(topology, 1.0)
fnss.set_delays_constant(topology, INTERNAL_LINK_DELAY, 'ms')
# label links as internal or external
for u, v in topology.edges_iter():
if u in sources or v in sources:
topology.edge[u][v]['type'] = 'external'
# this prevents sources to be used to route traffic
fnss.set_weights_constant(topology, 1000.0, [(u, v)])
fnss.set_delays_constant(topology, EXTERNAL_LINK_DELAY, 'ms', [(u, v)])
else:
topology.edge[u][v]['type'] = 'internal'
return IcnTopology(topology)
@register_topology_factory('TISCALI_2')
def topology_tiscali2(**kwargs):
"""Return a scenario based on Tiscali topology, parsed from RocketFuel dataset
Differently from plain Tiscali, this topology some receivers are appended to
routers and only a subset of routers which are actually on the path of some
traffic are selected to become ICN routers. These changes make this
topology more realistic.
Parameters
----------
seed : int, optional
The seed used for random number generation
Returns
-------
topology : fnss.Topology
The topology object
"""
# 240 nodes in the main component
topology = fnss.parse_rocketfuel_isp_map(path.join(TOPOLOGY_RESOURCES_DIR,
'3257.r0.cch')
).to_undirected()
topology = list(nx.connected_component_subgraphs(topology))[0]
# degree of nodes
deg = nx.degree(topology)
# nodes with degree = 1
onedeg = [v for v in topology.nodes() if deg[v] == 1] # they are 80
# we select as caches nodes with highest degrees
# we use as min degree 6 --> 36 nodes
# If we changed min degrees, that would be the number of caches we would have:
# Min degree N caches
# 2 160
# 3 102
# 4 75
# 5 50
# 6 36
# 7 30
# 8 26
# 9 19
# 10 16
# 11 12
# 12 11
# 13 7
# 14 3
# 15 3
# 16 2
icr_candidates = [v for v in topology.nodes() if deg[v] >= 6] # 36 nodes
# Add remove caches to adapt betweenness centrality of caches
for i in [181, 208, 211, 220, 222, 250, 257]:
icr_candidates.remove(i)
icr_candidates.extend([232, 303, 326, 363, 378])
# sources are node with degree 1 whose neighbor has degree at least equal to 5
# we assume that sources are nodes connected to a hub
# they are 44
sources = [v for v in onedeg if deg[list(topology.edge[v].keys())[0]] > 4.5] # they are
# receivers are node with degree 1 whose neighbor has degree at most equal to 4
# we assume that receivers are nodes not well connected to the network
# they are 36
receivers = [v for v in onedeg if deg[list(topology.edge[v].keys())[0]] < 4.5]
# we set router stacks because some strategies will fail if no stacks
# are deployed
routers = [v for v in topology.nodes() if v not in sources + receivers]
# set weights and delays on all links
fnss.set_weights_constant(topology, 1.0)
fnss.set_delays_constant(topology, INTERNAL_LINK_DELAY, 'ms')
# deploy stacks
topology.graph['icr_candidates'] = set(icr_candidates)
for v in sources:
fnss.add_stack(topology, v, 'source')
for v in receivers:
fnss.add_stack(topology, v, 'receiver')
for v in routers:
fnss.add_stack(topology, v, 'router')
# label links as internal or external
for u, v in topology.edges():
if u in sources or v in sources:
topology.edge[u][v]['type'] = 'external'
# this prevents sources to be used to route traffic
fnss.set_weights_constant(topology, 1000.0, [(u, v)])
fnss.set_delays_constant(topology, EXTERNAL_LINK_DELAY, 'ms', [(u, v)])
else:
topology.edge[u][v]['type'] = 'internal'
return IcnTopology(topology)
[docs]@register_topology_factory('ROCKET_FUEL')
def topology_rocketfuel_latency(asn, source_ratio=0.1, ext_delay=EXTERNAL_LINK_DELAY, **kwargs):
"""Parse a generic RocketFuel topology with annotated latencies
To each node of the parsed topology it is attached an artificial receiver
node. To the routers with highest degree it is also attached a source node.
Parameters
----------
asn : int
AS number
source_ratio : float
Ratio between number of source nodes (artificially attached) and routers
ext_delay : float
Delay on external nodes
"""
if source_ratio < 0 or source_ratio > 1:
raise ValueError('source_ratio must be comprised between 0 and 1')
f_topo = path.join(TOPOLOGY_RESOURCES_DIR, 'rocketfuel-latency', str(asn), 'latencies.intra')
topology = fnss.parse_rocketfuel_isp_latency(f_topo).to_undirected()
topology = list(nx.connected_component_subgraphs(topology))[0]
# First mark all current links as inernal
for u, v in topology.edges_iter():
topology.edge[u][v]['type'] = 'internal'
# Note: I don't need to filter out nodes with degree 1 cause they all have
# a greater degree value but we compute degree to decide where to attach sources
routers = topology.nodes()
# Source attachment
n_sources = int(source_ratio * len(routers))
sources = ['src_%d' % i for i in range(n_sources)]
deg = nx.degree(topology)
# Attach sources based on their degree purely, but they may end up quite clustered
routers = sorted(routers, key=lambda k: deg[k], reverse=True)
for i in range(len(sources)):
topology.add_edge(sources[i], routers[i], delay=ext_delay, type='external')
# Here let's try attach them via cluster
# clusters = compute_clusters(topology, n_sources, distance=None, n_iter=1000)
# source_attachments = [max(cluster, key=lambda k: deg[k]) for cluster in clusters]
# for i in range(len(sources)):
# topology.add_edge(sources[i], source_attachments[i], delay=ext_delay, type='external')
# attach artificial receiver nodes to ICR candidates
receivers = ['rec_%d' % i for i in range(len(routers))]
for i in range(len(routers)):
topology.add_edge(receivers[i], routers[i], delay=0, type='internal')
# Set weights to latency values
for u, v in topology.edges_iter():
topology.edge[u][v]['weight'] = topology.edge[u][v]['delay']
# Deploy stacks on nodes
topology.graph['icr_candidates'] = set(routers)
for v in sources:
fnss.add_stack(topology, v, 'source')
for v in receivers:
fnss.add_stack(topology, v, 'receiver')
for v in routers:
fnss.add_stack(topology, v, 'router')
return IcnTopology(topology)