/
Graph.py
executable file
·338 lines (299 loc) · 11.5 KB
/
Graph.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
#!/usr/bin/env python3
from utilClasses import BiLink, Intent
import networkx as nx
from networkx.utils import pairwise
import graphUtilities
CAP_VIRTUAL = "virtual_capacity"
CAP_MAX = "max_capacity"
CAP_REMAINING = "remaining_capacity"
class Graph(nx.Graph):
hops = None
def __init__(self, file=None) -> None:
super(Graph, self).__init__()
self.hops = None
if file is not None:
self.read_edgelist(file)
def draw(self):
graphUtilities.draw(self, labels=True)
def assign_capacities(self):
for s, d in self.edges:
cap = self[s][d]["bilink"].capacity
self[s][d][CAP_MAX] = cap
self[s][d][CAP_REMAINING] = cap
def _get_capacity_key(self, use_virtual):
capacity_key = CAP_REMAINING
if use_virtual:
capacity_key = CAP_VIRTUAL
return capacity_key
def sorted_edgelist(self, node, destination, dec = False, use_virtual=False):
capacity_key = self._get_capacity_key(use_virtual)
l = []
for u in self[node]:
cost = self.hops[destination][u]
cap = self[node][u][capacity_key]
l.append((u, cap, cost))
return sorted(l, key=lambda x: (x[2], x[1]), reverse=dec)
def read_edgelist(self, file="g.graph") -> dict:
with open(file, 'r') as f:
edges = f.readlines()
for edge in edges:
if len(edge) < 2:
continue
s, d, cap = edge.split()
cap = int(cap)
self.add_edge(s, d, bilink=BiLink(s, d, cap))
self.hops = dict.fromkeys(self.nodes)
for key in self.hops:
self.hops[key] = dict.fromkeys(self.nodes, None)
return self.edges
def add_edge(self, u_of_edge, v_of_edge, **attr):
link:BiLink = attr["bilink"]
attr[CAP_MAX] = link.capacity
attr[CAP_REMAINING] = link.capacity
return super().add_edge(u_of_edge, v_of_edge, **attr)
def init_hops_from_edgelist(self):
self.hops = dict.fromkeys(self.nodes())
for key in self.hops:
self.hops[key] = dict.fromkeys(self.nodes, None)
def bfs(self, source):
q = [source]
vis = dict.fromkeys(self.nodes, False)
self.hops[source][source] = 0
cost = 0
vis[source] = True
while len(q) != 0:
size = len(q)
while size > 0:
s = q[0]
size -= 1
q = q[1:]
for d in self[s]:
if vis[d]:
continue
self.hops[source][d] = cost + 1
vis[d] = True
q.append(d)
cost += 1
return self.hops[source]
def _astar(self, source, destination, min_link, use_virtual=False):
if self._vis[source]:
return False
self._vis[source] = True
if source == destination:
return True
for d, cap, _ in self.sorted_edgelist(source, destination, use_virtual=use_virtual):
if cap < min_link:
continue
if self._vis[d]:
continue
self._path[d] = source
ret = self._astar(d, destination, min_link, use_virtual)
if ret:
return ret
self._path[d] = None
self._vis[source] = False
return False
def astar(self, source, destination, min_link, use_virtual=False):
self._vis = dict.fromkeys(self.nodes, False)
self._path = dict.fromkeys(self.nodes, None)
self.bfs(destination)
res = self._astar(source, destination, min_link, use_virtual=use_virtual)
if not res:
return None
node = destination
path = []
while node is not None:
path.append(node)
node = self._path[node]
return list(reversed(path))
def astar_greedy_alloc(self, intents):
# raise NotImplementedError("Not implemented to use virtual capacities")
intents = sorted(intents, key=lambda x: x.required_bw, reverse=True)
flows = []
self.reset_capacities(use_virtual=True)
for intent in intents:
if type(intent.src_host) is str:
source = intent.src_host
destination = intent.dst_host
else:
source = intent.src_host.switchport.device
destination = intent.dst_host.switchport.device
req = intent.required_bw
path = self.astar(source, destination, req, use_virtual=True)
if path is None:
return None # No Solution
intent.path = path.copy()
self.allocate_flow(intent, use_virtual=True)
flows.append(intent)
self.reset_capacities()
for intent in flows:
self.allocate_flow(intent)
return flows
def topk_greedy_allocate(self, intents, full_virtual=False):
intents = sorted(intents, key=lambda x: x.required_bw, reverse=True)
flows = []
self.reset_capacities(use_virtual=True)
for intent in intents:
if type(intent.src_host) is str:
source = intent.src_host
destination = intent.dst_host
else:
source = intent.src_host.switchport.device
destination = intent.dst_host.switchport.device
req = intent.required_bw
path = self.get_shortest_path(source, destination, req, use_virtual=True)
if path is None:
return None
intent.path = path.copy()
if len(path) > 1:
self.allocate_flow(intent, use_virtual=True)
flows.append(intent)
if full_virtual:
return True
self.reset_capacities()
for intent in flows:
self.allocate_flow(intent)
return flows
def filter_too_long(self, paths, required_bw, use_virtual=False):
best_path = None
min_cap = None
for path in paths:
cap = self.get_path_capacity(path, use_virtual)
if required_bw <= cap:
limit = len(path)
best_path = path
min_cap = cap
for candidate_path in paths:
if len(candidate_path) > limit:
return best_path
candidate_cap = self.get_path_capacity(candidate_path, use_virtual)
if candidate_cap < min_cap and candidate_cap >= required_bw:
best_path = candidate_path
min_cap = candidate_cap
return best_path
# TODO: Reimplement `shortest_simple_paths` to calculate capacity
def get_path_capacity(self, path, use_virtual=False):
capacity_key = self._get_capacity_key(use_virtual)
if len(path) == 1:
return 10**10
if len(path) < 2:
raise Exception("Invalid path")
u, v = path[0], path[1]
min_edge = self[u][v][capacity_key]
for u, v in pairwise(path):
min_edge = min(min_edge, self[u][v][capacity_key])
return min_edge
def get_shortest_path(self, src, dst, required_capacity, use_virtual=False):
paths = nx.shortest_simple_paths(self, src, dst)
path = self.filter_too_long(paths, required_capacity, use_virtual)
return path
def allocate_flow(self, intent:Intent, use_virtual=False):
capacity_key = self._get_capacity_key(use_virtual)
path = intent.path
req = intent.required_bw
intent_uuid = intent.id
for s, d in pairwise(path):
self[s][d][capacity_key] -= req
self[s][d]["bilink"].intents[intent_uuid] = intent
def allocate_single(self, intent: Intent):
if type(intent.src_host) is str:
source = intent.src_host
destination = intent.dst_host
else:
source = intent.src_host.switchport.device
destination = intent.dst_host.switchport.device
req = intent.required_bw
path = self.get_shortest_path(source, destination, req)
if path is None:
return None # No Solution
intent.path = path.copy()
if len(path) == 1:
return path
self.allocate_flow(intent)
return path
def allocate_single_astar(self, intent: Intent):
if type(intent.src_host) is str:
source = intent.src_host
destination = intent.dst_host
else:
source = intent.src_host.switchport.device
destination = intent.dst_host.switchport.device
req = intent.required_bw
path = self.astar(source, destination, req)
if path is None:
return None # No Solution
intent.path = path.copy()
if len(path) == 1:
return path
self.allocate_flow(intent)
return path
def reset_capacities(self, use_virtual=False):
capacity_key = self._get_capacity_key(use_virtual)
for u, v in self.edges:
self[u][v][capacity_key] = self[u][v][CAP_MAX]
if not use_virtual:
self[u][v]["bilink"].intents.clear()
def remove_edge(self, u, v, virtual=False):
try:
if virtual:
return super(Graph, self).remove_edge(u, v)
removed_intents = set()
if (u, v) in self.edges:
for intent in self[u][v]["bilink"].intents.values():
self.remove_flow(intent)
removed_intents.add(intent.id)
super(Graph, self).remove_edge(u, v)
return removed_intents
except:
return None
def remove_flow(self, intent:Intent):
for u, v in pairwise(intent.path):
self[u][v][CAP_REMAINING] += intent.required_bw
del self[u][v]["bilink"].intents[intent.id]
def find_best_solution(self, intents, new_intent_id):
temp_intents = intents.copy()
l = 0
r = intents[new_intent_id].required_bw
res = -1
while l<=r:
mid = (l + r)>>1
temp_intents[new_intent_id].required_bw = mid
can = self.topk_greedy_allocate(temp_intents.values(), full_virtual=True)
if can:
l = mid + 1
res = mid
else:
r = mid - 1
return res
def main(graph_file="g.graph", intents_file=None, online=False):
if intents_file is None:
intents = [Intent("h1", "h2", 7), Intent("3", "h1", 2)]
else:
intents = []
with(open(intents_file, "r")) as f:
lines = f.readlines()
for line in lines:
if line[0] == '#':
continue
src, dst, req = line.split()
intents.append(Intent(src, dst, req))
g = Graph(graph_file)
if not online:
can = g.topk_greedy_allocate(intents)
if can is None:
print("No Solution")
return False
for u, v in g.edges:
print(f"{u}->{v}: {g[u][v]['remaining_capacity']}")
return True
elif online:
for i, intent in enumerate(intents):
res = g.allocate_single(intent)
if res is None:
print(f"Recalculating on intent {{{i}}}")
can = g.topk_greedy_allocate(intents[:i+1])
if can is None:
print("No Solution")
return True
if __name__=="__main__":
main()