Commit e1799526 authored by yvonneanne's avatar yvonneanne
Browse files

initial commit

parent 5cf0816f
# fast-failover
This repository contains the source code for the experiments in the following three papers
Code for papers from INFOCOM 2019, DSN 2019, SRDS 2019, and future work
\ No newline at end of file
[SRDS 2019: Improved Fast Rerouting Using Postprocessing]{https://www.univie.ac.at/ct/stefan/srds19failover.pdf}
[DSN 2019: Bonsai: Efficient Fast Failover Routing Using Small Arborescences]{https://www.univie.ac.at/ct/stefan/dsn19.pdf}
[Infocom 2019: CASA: Congestion and Stretch Aware Static Fast Rerouting]{https://www.univie.ac.at/ct/stefan/infocom2019e.pdf}
by Klaus-Tycho Foerster, Andrzej Kamisinski, Yvonne-Anne Pignolet, Stefan Schmid, Gilles Tredan
We are indebted to Ilya Nikolaevskiy, Aalto University, Finland, on whose source code for [this paper](
http://www.dia.uniroma3.it/~compunet/www/docs/chiesa/Resiliency-ToN.pdf) we based our implementation.
If you use this code, please cite the corresponding paper(s).
## Bibtex
@INPROCEEDINGS{srds19foerster,
author = {Klaus-Tycho Foerster and Andrzej Kamisinski and Yvonne-Anne Pignolet and Stefan Schmid and Gilles Tredan},
title = {Improved Fast Rerouting Using Postprocessing},
booktitle = {Proc. 38th International Symposium on Reliable Distributed Systems (SRDS)},
year = {2019},
}
@INPROCEEDINGS{dsn19foerster,
author = {Klaus-Tycho Foerster and Andrzej Kamisinski and
Yvonne-Anne Pignolet and Stefan Schmid and Gilles Tredan},
title = {Bonsai: Efficient Fast Failover Routing Using Small Arborescences},
booktitle = {Proc. 49th IEEE/IFIP International Conference on Dependable Systems and Networks (DSN)},
year = {2019},
}
@INPROCEEDINGS{infocom19foerster,
author = {Klaus-Tycho Foerster and Yvonne-Anne Pignolet and Stefan Schmid and Gilles Tredan},
title = {CASA: Congestion and Stretch Aware Static Fast Rerouting},
booktitle = {Proc. IEE INFOCOM},
year = {2019},
}
## Overview
* benchmark_graphs: directory to be filled with network topologies used in the experiments
* results: directory to which csv and other output files are written
* arborescence.py: arborescence decomposition and helper algorithms
* routing_stats.py: routing algorithms, simulation and statistic framework
* objective_function_experiments.py: objective functions, independence and SRLG experiments
* srds2019_experiments.py: experiments for SRDS 2019 paper
* dsn2019_experiments.py: experiments for DSN 2019 paper
* infocom2019_experiments.py: experiments for Infocom 2019 paper
For some experiments topologies from [Rocketfuel]{https://research.cs.washington.edu/networking/rocketfuel/} and the [Internet topology zoo]{http://www.topology-zoo.org/} networks need to downloaded and copied into the benchmark_graphs directory.
To run the experiments for the SRDS paper, execute the corresponding python file:
```
python srds2019_experiments.py
```
With additional arguments the experiments can be customised (see main function of the python file). E.g.,
```
python srds2019_experiments.py all 6 1
```
executes 1 repetition of all SRDS experiments with seed 6. Similarly, the experiments for the other papers can be executed. In case of questions please send an email to Yvonne-Anne Pignolet, ya at last name dot ch.
\ No newline at end of file
import os
import sys
import networkx as nx
from networkx import minimum_edge_cut as cut
from networkx.algorithms.flow import shortest_augmenting_path
from networkx.algorithms.connectivity import build_auxiliary_edge_connectivity
from networkx.algorithms.flow import build_residual_network
import numpy as np
import matplotlib.pyplot as plt
import itertools
from itertools import combinations
import random
import heapq
from heapq import heappush, heappop
import time
swappy = []
# set up associated data structures for k arborescences
# an edge that does not belong to any arborescences has its
# 'arb' attribute set to -1
def init_k_graph(k, n):
g = nx.random_regular_graph(k, n).to_directed()
while nx.edge_connectivity(g) < k:
g = nx.random_regular_graph(k, n).to_directed()
for (u, v) in g.edges():
g[u][v]['arb'] = -1
g.graph['k'] = k
return g
# reset the arb attribute for all edges to -1, i.e., no arborescence assigned yet
def reset_arb_attribute(g):
for (u, v) in g.edges():
g[u][v]['arb'] = -1
# given a graph g and edge (u1, v1) and edge (u2,v2) swap the arborescences
# they belong to (will crash if the edges dont belong to the graph)
def swap(g, u1, v1, u2, v2):
i1 = g[u1][v1]['arb']
i2 = g[u2][v2]['arb']
g[u1][v1]['arb'] = i2
g[u2][v2]['arb'] = i1
# given a graph g and a minimum degree min, nodes with degree two are contracted into a link
# recursively and then all nodes with a degree < min are removed
# from g recursively until the graph is empty or all remaining nodes have at
# least degree min
def trim_merge(g, min):
while True:
while True:
rem = []
for v in g.nodes():
if g.degree(v) == 2:
neighbors = list(g.neighbors(v))
for i in range(len(neighbors)-1):
g.add_edge(neighbors[0], neighbors[i+1])
rem.append(v)
if len(rem) == 0:
break
g.remove_nodes_from(rem)
rem = []
for v in g.nodes():
if g.degree(v) < min:
rem.append(v)
if len(rem) == 0:
break
g.remove_nodes_from(rem)
return g
# given a graph g and a minimum degree min_degree, nodes with a lower degree are removed
# from g and their ex-neighbors are connected recursively until the graph is
# empty or all remaining nodes have at least degree min_degree
def trim2(g, min_degree):
while True:
rem = []
for v in g.nodes():
if g.degree(v) < min_degree:
rem.append(v)
for u in g[v]:
for u1 in g[v]:
g.add_edge(u, u1)
break
g.remove_nodes_from(rem)
if len(rem) == 0:
break
return g
# given graph g return the ith arborescence as a digraph
def get_arborescence(g, i):
a = nx.DiGraph()
a.graph['root'] = g.graph['root']
for (u, v) in g.edges():
index = g[u][v]['arb']
if index != i:
continue
a.add_edge(u, v)
return a
# given a graph return the arborescences in a dictionary with indices as keys
def get_arborescence_dict(g):
arbs = {}
for (u, v) in g.edges():
index = g[u][v]['arb']
if index not in arbs:
arbs[index] = nx.DiGraph()
arbs[index].graph['root'] = g.graph['root']
arbs[index].graph['index'] = index
arbs[index].add_edge(u, v)
return arbs
# given a graph return a list of its arborescences
def get_arborescence_list(g):
arbs = get_arborescence_dict(g)
return [arbs[i] for i in range(len(arbs)-1)]
# given a graph g, check if the associated graphs derived from the arb attribute
# are indeed acyclic
def is_arborescence_decomposition(g):
arbs = get_arborescence_list(g)
for a in arbs:
if not nx.is_directed_acyclic_graph(a):
return False
return True
# return the number of paths in two trees that don't share an edge when
# traversing them towards the root
def num_independent_paths(T1, T2, root):
SP1 = nx.shortest_path(T1, target=root)
SP2 = nx.shortest_path(T2, target=root)
count = 0
for v in T1.nodes():
if v in SP1 and v in SP2 and set(SP1[v][1:-1]).isdisjoint(set(SP2[v][1:-1])):
count += 1
return count
# return the number of indepependent paths in all pairs of
# arborescences associated with this graph g
def num_independent_paths_in_arbs(g):
root = g.graph['root']
T = get_arborescence_dict(g)
n = len(g.nodes())
count = 0
for T1, T2 in itertools.combinations(T.values(), 2):
if root in T1.nodes() and root in T2.nodes():
count += num_independent_paths(T1, T2, root)
else:
return 0
return count
# check the swappable condition (see DSN paper for background)
# given two edges outgoing from u to v1 and v2, vi should not be on the shortest
# path to the root on (u,vj)'s arborescence starting from u
def swappable(u, v1, v2, T1, T2):
return not in_sp_to_r(T1, v2, u) and not in_sp_to_r(T2, v1, u)
# return true if v is on the shortest path to the root node starting from u on g
def in_sp_to_r(g, v, u):
if g.graph['index'] == -1:
return False
return (v in nx.shortest_path(g, u, g.graph['root']))
# swap greedily while a pair of nodes with a better objective function evaluation
# after the swap can be found
def greedy_swap_minobj(g, obj):
arbs = get_arborescence_dict(g)
o = obj(g)
new_o = 0
test = [(u, v) for (u, v) in g.edges() if u != g.graph['root']]
random.shuffle(test)
swapped = True
count = 0
while swapped:
swapped = False
#print('count, o', count,o)
for (u, v1) in test:
for v2 in [nbr for nbr in g[u] if nbr != v1]:
# print(u, v1, v2, g[u][v1]['arb'], g[u][v2]['arb'])
if swappable(u, v1, v2, arbs[g[u][v1]['arb']], arbs[g[u][v2]['arb']]):
# print('swappable')
swap(g, u, v1, u, v2)
new_o = obj(g)
if new_o < o:
# print('new o', new_o)
# sys.exit()
o = new_o
swapped = True
count += 1
else:
swap(g, u, v1, u, v2)
return count
# return the stretch of the arborescence with index i on g (how much longer the
# path to the root is in the arborescence than in the original graph)
def stretch_index(g, index):
arbs = get_arborescence_list(g)
dist = nx.shortest_path_length(g, target=g.graph['root'])
distA = {}
if g.graph['root'] in arbs[index].nodes():
distA = nx.shortest_path_length(arbs[index], target=g.graph['root'])
else:
return float("inf")
stretch_vector = []
for v in g.nodes():
if v != g.graph['root']:
stretch = -1
if v in arbs[index].nodes() and v in distA:
stretch = max(stretch, distA[v]-dist[v])
else:
return float("inf")
stretch_vector.append(stretch)
return max(stretch_vector)
# return the stretch of the arborence with the largest stretch
def stretch(g):
stretch_vector = []
for index in range(g.graph['k']):
stretch_vector.append(stretch_index(g, index))
return max(stretch_vector)
# return the longest path to the root in all arborescences
def depth(g):
arbs = get_arborescence_list(g)
distA = [{} for index in range(len(arbs))]
for index in range(len(arbs)):
if g.graph['root'] in arbs[index].nodes():
distA[index] = nx.shortest_path_length(
arbs[index], target=g.graph['root'])
else:
return float("inf")
depth_vector = []
for v in g.nodes():
if v != g.graph['root']:
depth = -1
for index in range(len(arbs)):
if v in arbs[index].nodes() and v in distA[index]:
depth = max(depth, distA[index][v])
else:
return float("inf")
depth_vector.append(depth)
return max(depth_vector)
# return a list containing the arborescences of g in order of their depth
def total_arb_order_depth(g):
arbs = get_arborescence_list(g)
h = []
for index in range(len(arbs)):
heappush(h, (index, nx.eccentricity(g, v=g.graph['root'])))
arb_order = []
while len(h) > 0:
(dist, index) = heappop(h)
arb_order.append(arbs[index])
return arb_order
# return a list containing the arborences of g in order of their stretch
def total_arb_order_stretch(g):
arbs = get_arborescence_list(g)
h = []
for index in range(len(arbs)):
heappush(h, (index, nx.eccentricity(g, v=g.graph['root'])))
arb_order = []
while len(h) > 0:
(dist, index) = heappop(h)
arb_order.append(arbs[index])
return arb_order
# return a dictionary that lists the arborescence indices in shortest path order
# for each node
def arb_order(g):
arbs = get_arborescence_list(g)
distA = [{} for index in range(len(arbs))]
for index in range(len(arbs)):
if g.graph['root'] in arbs[index].nodes():
distA[index] = nx.shortest_path_length(
arbs[index], target=g.graph['root'])
else:
return float("inf")
arb_order_dict = {}
for v in g.nodes():
if v != g.graph['root']:
h = []
dist_dict = {index: distA[index][v] for index in range(len(arbs))}
for k, v in dist_dict:
heappush(h, (v, k))
arb_order_dict[v] = []
while len(h) > 0:
(dist, index) = heappop(h)
arb_order_dict[v].append(index)
return arb_order_dict
# return the nodes belonging to arborence i
def nodes_index(g, i):
return set([u for (u, v, d) in g.edges(data=True) if d['arb'] == i or u == g.graph['root']])
# return the outgoing edges for node v with arborescence i
def outgoing_edges_index(g, v, i):
return [u for u in g[v] if g[v][u]['arb'] == i]
# return the number of nodes that have one outgoing edge in each arborescence
def num_complete_nodes(g):
complete = 0
for v in g.nodes():
c = True
for i in range(g.graph['k']):
if v != g.graph['root'] and len(outgoing_edges_index(g, v, i)) != 1:
c = False
if c:
complete += 1
return complete
# return true iff there is exactly one outgoing edge from node for each
# arborescence
def is_complete_node(g, node):
for i in range(g.graph['k']):
if len(outgoing_edges_index(g, node, i)) != 1:
return False
else:
return True
# return length of shortest path between u and v on the indexth arborescence of g
def shortest_path_length(g, index, u, v):
arbs = get_arborescence_dict(g)
return nx.shortest_path_length(arbs[index], u, v)
# save png of arborescence embeddings
def drawArborescences(g, pngname="results/weighted_graph.png"):
plt.clf()
k = g.graph['k']
edge_labels = {i: {} for i in range(k)}
edge_labels[-1] = {}
for e in g.edges():
arb = g[e[0]][e[1]]['arb']
edge_labels[arb][(e[0], e[1])] = ""
colors = ['b', 'g', 'r', 'c', 'm', 'y', 'pink', 'olive',
'brown', 'orange', 'darkgreen', 'navy', 'purple']
if 'pos' not in g.graph:
g.graph['pos'] = nx.spring_layout(g)
pos = g.graph['pos']
nx.draw_networkx_labels(g, pos)
nodes = list(g.nodes)
node_colors = {v: 'gray' for v in nodes}
for node in nodes:
if is_complete_node(g, node):
node_colors[node] = 'black'
color_list = [node_colors[v] for v in nodes]
nx.draw_networkx_nodes(g, pos, nodelist=nodes, alpha=0.6,
node_color=color_list, node_size=2)
for j in range(k):
edge_j = [(u, v) for (u, v, d) in g.edges(data=True) if d['arb'] == j]
nx.draw_networkx_labels(g, pos)
nx.draw_networkx_edges(g, pos, edgelist=edge_j,
width=1, alpha=0.5, edge_color=colors[j])
plt.axis('off')
plt.savefig(pngname) # save as png
plt.close()
for j in range(k):
edge_j = [(u, v) for (u, v, d) in g.edges(data=True) if d['arb'] == j]
nx.draw_networkx_labels(g, pos)
nx.draw_networkx_edges(g, pos, edgelist=edge_j, width=1,
alpha=0.5, edge_color=colors[j]) # , arrowsize=20)
plt.savefig(pngname+str(j)+'.png') # save as png
# return best edges to swap for stretch in g
def find_best_swap(g):
s = stretch(g)
test = [(u, v) for (u, v) in g.edges() if u != g.graph['root']]
e1 = None
e2 = None
for (u1, v1) in test:
for (u2, v2) in test:
if (v1 != v2) or (u1 != u2):
swap(g, u1, v1, u2, v2)
new_s = stretch(g)
if new_s < s:
s = new_s
e1 = (u1, v1)
e2 = (u2, v2)
swap(g, u1, v1, u2, v2)
return e1, e2
# recursively swap best edges found (with respect to stretch) in g
def best_swap(g):
(e1, e2) = find_best_swap(g)
while e1 != None:
swap(g, e1[0], e1[1], e2[0], e2[1])
(e1, e2) = find_best_swap(g)
# return the edge connectivity of g between s and t
def TestCut(g, s, t):
return nx.edge_connectivity(g, s, t)
# return a random arborescence rooted at the root
def FindRandomTree(g, k):
T = nx.DiGraph()
T.add_node(g.graph['root'])
R = {g.graph['root']}
dist = dict()
dist[g.graph['root']] = 0
# heap of all border edges in form [(edge metric, (e[0], e[1])),...]
hi = []
preds = sorted(g.predecessors(
g.graph['root']), key=lambda k: random.random())
for x in preds:
hi.append((0, (x, g.graph['root'])))
if k > 1:
continue
while len(hi) > 0: # len(h) > 0:
(d, e) = random.choice(hi)
hi.remove((d, e))
g.remove_edge(*e)
if e[0] not in R and (k == 1 or TestCut(g, e[0], g.graph['root']) >= k-1):
dist[e[0]] = d+1
R.add(e[0])
preds = sorted(g.predecessors(e[0]), key=lambda k: random.random())
for x in preds:
if x not in R:
hi.append((d+1, (x, e[0])))
T.add_edge(*e)
else:
g.add_edge(*e)
if len(R) < len(g.nodes()):
print("Couldn't find next edge for tree with root %s" % str(r))
sys.stdout.flush()
return T
# associate random trees as arborescences with g
def RandomTrees(g):
gg = g.to_directed()
K = g.graph['k']
k = K
while k > 0:
T = FindRandomTree(gg, k)
if T is None:
return None
for (u, v) in T.edges():
g[u][v]['arb'] = K-k
gg.remove_edges_from(T.edges())
k = k-1
# compute the k^th arborescence of g greedily
def FindTree(g, k):
T = nx.DiGraph()
T.add_node(g.graph['root'])
R = {g.graph['root']}
dist = dict()
dist[g.graph['root']] = 0
# heap of all border edges in form [(edge metric, (e[0], e[1])),...]
h = []
preds = sorted(g.predecessors(
g.graph['root']), key=lambda k: random.random())
for x in preds:
heappush(h, (0, (x, g.graph['root'])))
if k > 1:
continue
while len(h) > 0:
(d, e) = heappop(h)
g.remove_edge(*e)
if e[0] not in R and (k == 1 or TestCut(g, e[0], g.graph['root']) >= k-1):
dist[e[0]] = d+1
R.add(e[0])
preds = sorted(g.predecessors(e[0]), key=lambda k: random.random())
for x in preds:
if x not in R:
heappush(h, (d+1, (x, e[0])))
T.add_edge(*e)
else:
g.add_edge(*e)
if len(R) < len(g.nodes()):
print(
"Couldn't find next edge for tree with g.graph['root']")
sys.stdout.flush()
return T
# associate a greedy arborescence decomposition with g
def Trees(g):
reset_arb_attribute(g)
gg = g.to_directed()
K = g.graph['k']
k = K
while k > 0:
T = FindTree(gg, k)
if T is None:
return None
for (u, v) in T.edges():
g[u][v]['arb'] = K-k
gg.remove_edges_from(T.edges())
k = k-1
# XXX