Commit 03210c5f authored by Alberts S's avatar Alberts S
Browse files

Rework Improvement handling to support route flapping (wip)

parent 8a7bc268
import asyncio
import ipaddress
from datetime import datetime
from datetime import datetime, timedelta
from typing import Type
import networkx as nx
......@@ -9,6 +9,56 @@ from CapybaraNetty import CapybaraNetty
from Router import Router
class Improvement:
def __init__(self, target, path, optimized_ms, is_current_viable_path, is_current_best_path):
self.target = target
self.path = path
self.optimized_ms = optimized_ms
self.discovery_time = datetime.now()
self.__is_current_best_path = None
self.__last_best_time = None
self.__total_best_seconds = 0
self.__is_current_viable_path = None
self.is_current_best_path = is_current_best_path
self.is_current_viable_path = is_current_viable_path
@property
def is_current_best_path(self):
return self.__is_current_best_path
@is_current_best_path.setter
def is_current_best_path(self, val: bool):
self.__is_current_best_path = val
if val:
current_time = datetime.now()
if self.__last_best_time:
# increment time this obj has been the best if we are setting this as best path
self.__total_best_seconds += (current_time - self.__last_best_time).seconds
# in case there has never been last best improvement and this is a first
else:
self.__total_best_seconds = 0
self.__last_best_time = current_time
else:
self.__total_best_seconds = 0
@property
def is_current_viable_path(self):
return self.__is_current_viable_path
@is_current_viable_path.setter
def is_current_viable_path(self, val: bool):
self.__is_current_viable_path = val
@property
def total_best_seconds(self):
return self.__total_best_seconds
def __str__(self):
return f"{self.target:<16s} viable={self.is_current_viable_path:<1}, best={self.is_current_best_path:<1} for {self.total_best_seconds:<3} seconds, {self.optimized_ms:<3}ms [{'->'.join(self.path)}] "
class ControllerStatic(CapybaraNetty):
def __init__(self, routers):
super().__init__()
......@@ -17,17 +67,14 @@ class ControllerStatic(CapybaraNetty):
self.external_targets_for_optimizations = []
self.optimization_threshold_ms = 0
self.daemon_interval = self.config["controller_interval"]
self.route_stickiness_time = self.config["controller_static_route_stickiness_time"]
self.G = nx.Graph()
async def run(self):
if len(self.pinger_data) > 0:
G = nx.Graph()
G = self.get_latency_graph(G)
self.find_viable_paths(G)
current_best_routes = await self.get_viable_external_routes(G)
await self.install_viable_external_routes(current_best_routes)
# print(G)
# G_simple = await self.get_host_latency_overview()
self.G = self.get_latency_graph(self.G)
self.find_viable_paths(self.G)
print("a")
def get_latency_graph(self, G: nx.Graph):
for result in self.pinger_data:
......@@ -41,6 +88,40 @@ class ControllerStatic(CapybaraNetty):
G.nodes[dest]["obj"] = result["to_obj"]
return G
# Create new or update existing graph node path
def upsert_node_paths(
self, G: nx.graph, node_name, target, path, optimized_ms, is_current_viable_path, is_current_best_path
):
G.nodes[node_name]["improved_path"] = True
G.nodes[node_name].setdefault("improved_path_info", [])
node_improved_path_info = G.nodes[node_name]["improved_path_info"]
path_is_already_known = False
improved_path: Improvement
for improved_path in node_improved_path_info:
if improved_path.target == target:
if improved_path.path == path:
path_is_already_known = True
improved_path.optimized_ms = optimized_ms
improved_path.is_current_viable_path = is_current_viable_path
improved_path.is_current_best_path = is_current_best_path
else:
improved_path.is_current_best_path = False
if not path_is_already_known:
node_improved_path_info.append(
Improvement(
target=target,
path=path,
optimized_ms=optimized_ms,
is_current_viable_path=is_current_viable_path,
is_current_best_path=is_current_best_path,
)
)
def is_improvement_viable_route(self, improvement: Improvement):
pass
def find_viable_paths(self, G):
def filter_node(n1):
return G.nodes[n1].get("type") == "router" or (G.nodes[n1].get("type") == "target" and n1 == target)
......@@ -58,28 +139,53 @@ class ControllerStatic(CapybaraNetty):
and path[-1] in self.external_targets_for_optimizations
):
optimized_ms = G[key][path[0]]["weight"] - lengths[key]
if optimized_ms < self.optimization_threshold_ms:
self.__logger.debug(f"IGNORED OPTIMIZATION: {optimized_ms:<3d}ms {'->'.join(path[::-1])}")
is_current_viable_path = optimized_ms > self.optimization_threshold_ms
if is_current_viable_path:
self.__logger.info(f"VIABLE OPTIMIZATION: {optimized_ms:<3d}ms {'->'.join(path[::-1])}")
else:
# External targets
for node in path:
G.nodes[node]["improved_path"] = True
G.nodes[node].setdefault("improved_path_info", []).append(
self.get_improvement_obj(target=target, path=path[::-1], optimization_ms=optimized_ms)
)
# Internal targets
# On the current node we must have the interface IPs for next nodes in hops
for current_node in path:
if G.nodes[current_node]["type"] == "router":
for next_node in path[path.index(current_node) + 1 :]:
internal_source = str(G.nodes[next_node]["obj"].get_default_dummy_interface_ip().ip)
G.nodes[current_node]["improved_path_info"].append(
self.get_improvement_obj(
target=internal_source, path=path, optimization_ms=optimized_ms
)
)
self.__logger.debug(f"IGNORED OPTIMIZATION: {optimized_ms:<3d}ms {'->'.join(path[::-1])}")
self.__logger.info(f"VIABLE OPTIMIZATION: {optimized_ms:<3d}ms {'->'.join(path[::-1])}")
# External targets
for node in path:
self.upsert_node_paths(
G,
node,
target=target,
path=path[::-1],
optimized_ms=optimized_ms,
is_current_viable_path=is_current_viable_path,
is_current_best_path=True,
)
# Internal targets
# On the current node we must have the interface IPs for next nodes in hops
for current_node in path:
if G.nodes[current_node]["type"] == "router":
for next_node in path[path.index(current_node) + 1 :]:
internal_source = str(G.nodes[next_node]["obj"].get_default_dummy_interface_ip().ip)
self.upsert_node_paths(
G,
current_node,
target=internal_source,
path=path,
optimized_ms=optimized_ms,
is_current_viable_path=is_current_viable_path,
is_current_best_path=True,
)
else:
# if a node becomes inactive - we still need to upsert it and make sure its not marked as best path anymore
for node in path:
self.upsert_node_paths(
G,
node,
target=target,
path=path[::-1],
optimized_ms=0,
is_current_viable_path=False,
is_current_best_path=False,
)
# How do we handle internal routes?
return G
async def get_viable_external_routes(self, G):
......@@ -138,10 +244,6 @@ class ControllerStatic(CapybaraNetty):
return asyncio.gather(*coroutines)
@staticmethod
def get_improvement_obj(target, path, optimization_ms):
return {"target": target, "path": path, "optimization_ms": optimization_ms}
async def run_daemon(self, pinger_data_fn, routers_fn):
while True:
self.optimization_threshold_ms = self.config["optimization_threshold_ms"]
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment