9
9
from intent_deployer .hosts import Hosts
10
10
from intent_deployer .links import Links
11
11
from intent_deployer .switches import Switches
12
+ from intents .utils import OnosRoute
12
13
13
14
14
15
@dataclass
15
- class Intent :
16
+ class IntentPath :
16
17
src : str
17
18
dst : str
18
19
switch_path : list [str ]
@@ -21,23 +22,49 @@ class Intent:
21
22
def path (self ):
22
23
return [self .src , * self .switch_path , self .dst ]
23
24
24
- def as_tuple (self ) -> tuple [tuple [str , str ], Intent ]:
25
+ def as_tuple (self ) -> tuple [tuple [str , str ], IntentPath ]:
25
26
return ((self .src , self .dst ), self )
26
27
27
28
@classmethod
28
- def parse (cls , data : dict [str , Any ]) -> Intent :
29
- src , * switch_path , dst = cast (list [str ], data ["paths" ][ 0 ][ " path" ])
29
+ def parse (cls , data : dict [str , Any ]) -> IntentPath :
30
+ src , * switch_path , dst = cast (list [str ], data ["path" ])
30
31
src = Hosts .name_from_mac (src .removesuffix ("/None" ))
31
32
dst = Hosts .name_from_mac (dst .removesuffix ("/None" ))
32
33
switch_path = [Switches .name_from_id (s ) for s in switch_path ]
33
34
34
35
return cls (src , dst , switch_path )
35
36
37
+ def as_onos_route (self , hosts : Hosts , switches : Switches ) -> OnosRoute :
38
+ src , dst = hosts .get_host_id (self .src ), hosts .get_host_id (self .dst )
39
+ switch_path = cast (list [str ], [switches .get_switch_id (s ) for s in self .switch_path ])
40
+
41
+ return OnosRoute (key = f"{ src } { dst } " , route = [src , * switch_path , dst ])
42
+
43
+
44
+ @dataclass
45
+ class Intent :
46
+ """A point-to-point intent or whatever"""
47
+
48
+ paths : list [IntentPath ]
49
+
50
+ @classmethod
51
+ def parse (cls , data : dict [str , Any ]) -> Intent :
52
+ paths = [IntentPath .parse (x ) for x in data ["paths" ]]
53
+
54
+ return cls (paths )
55
+
56
+ def as_onos_intent (self , hosts : Hosts , switches : Switches ) -> list [OnosRoute ]:
57
+ return [p .as_onos_route (hosts , switches ) for p in self .paths ]
58
+
59
+
60
+ # TODO: keep track of bandwidth
61
+ # TODO: move drain part of this to the drain intent?
62
+
36
63
37
64
@dataclass
38
65
class Topology :
39
66
network : networkx .Graph
40
- intents : dict [ tuple [ str , str ], Intent ]
67
+ intents : list [ Intent ]
41
68
42
69
@classmethod
43
70
async def from_current_state (cls ) -> Topology :
@@ -51,8 +78,43 @@ async def from_current_state(cls) -> Topology:
51
78
json = {"api_key" : config .api_key },
52
79
)
53
80
resp .raise_for_status ()
54
- intents = dict (
55
- Intent .parse (i ).as_tuple () for i in resp .json ()["routingList" ]
56
- )
81
+ intents = [Intent .parse (i ) for i in resp .json ()["routingList" ]]
57
82
58
83
return cls (network , intents )
84
+
85
+ @staticmethod
86
+ def _perform_reroute (
87
+ network : networkx .Graph , path : list [str ], drain_node : str
88
+ ) -> IntentPath :
89
+ """Reroute a `path` to avoid a node.
90
+
91
+ This currently splits the path on the `drain_node` we want to drain from, then
92
+ finds the shortest path between the two sides avoiding `drain_node`
93
+
94
+ `network` should be the network *without* `drain_node`.
95
+ """
96
+ node_idx = path .index (drain_node )
97
+ lhs , rhs = path [:node_idx ], path [node_idx + 1 :]
98
+
99
+ # TODO: we need to detect when it's impossible to reroute, or if a host
100
+ # will be dropped, etc and warn/cancel the intent
101
+
102
+ reroute_path = networkx .shortest_path (network , lhs [- 1 ], rhs [0 ])
103
+ reroute_path = cast (list [str ], reroute_path )
104
+ new_path = lhs [:- 1 ] + reroute_path + rhs [1 :]
105
+ return IntentPath (src = lhs [0 ], dst = rhs [- 1 ], switch_path = new_path [1 :- 1 ])
106
+
107
+ def drain_node (self , node : str ) -> Intent :
108
+ """Construct an intent that drains traffic from a node."""
109
+
110
+ without_node : networkx .Graph = self .network .copy ()
111
+ without_node .remove_node (node )
112
+
113
+ new_intent_paths = [
114
+ Topology ._perform_reroute (without_node , intent_path .path , node )
115
+ for intent in self .intents
116
+ for intent_path in intent .paths
117
+ if node in intent_path .path
118
+ ]
119
+
120
+ return Intent (paths = new_intent_paths )
0 commit comments