Revamp of the route weight system.

This commit is contained in:
Herbert Wolverson 2025-01-27 14:34:45 -06:00
parent 50cb630315
commit 3d52f2f33e
4 changed files with 132 additions and 57 deletions

View File

@ -20,7 +20,7 @@ pub async fn bus_request(requests: Vec<BusRequest>) -> Result<Vec<BusResponse>,
let stream = UnixStream::connect(BUS_SOCKET_PATH).await;
if let Err(e) = &stream {
if e.kind() == std::io::ErrorKind::NotFound {
error!("Unable to access {BUS_SOCKET_PATH}. Check that lqosd is running and you have appropriate permissions.");
//error!("Unable to access {BUS_SOCKET_PATH}. Check that lqosd is running and you have appropriate permissions.");
return Err(BusClientError::SocketNotFound);
}
}

View File

@ -53,12 +53,18 @@ fn parse_sites(sites_raw: &[Site], config: &Config) -> Vec<UispSite> {
}
fn parse_data_links(data_links_raw: &[DataLink]) -> Vec<UispDataLink> {
let data_links: Vec<UispDataLink> = data_links_raw
.iter()
.filter_map(UispDataLink::from_uisp)
.collect();
// We need to preserve symmetry, so each link is added twice
let mut data_links = Vec::with_capacity(data_links_raw.len() * 2);
for link in data_links_raw {
let uisp_link = UispDataLink::from_uisp(link);
if let Some(link) = uisp_link {
data_links.push(link.invert());
data_links.push(link);
}
}
info!(
"{} data-links have been successfully parsed",
"{} data-links have been successfully parsed (doubled)",
data_links.len()
);
data_links

View File

@ -1,3 +1,4 @@
use std::collections::{HashMap, HashSet};
use std::io::Write;
use std::sync::Arc;
use lqos_config::Config;
@ -19,77 +20,133 @@ pub fn walk_tree_for_routing(
root_site: &str,
overrides: &Vec<RouteOverride>,
) -> Result<(), UispIntegrationError> {
if let Some(root_idx) = sites.iter().position(|s| s.name == root_site) {
let mut visited = std::collections::HashSet::new();
let current_node = root_idx;
let mut natural_weights: Vec<RouteOverride> = Vec::new();
let mut dot_graph = "digraph G {\n graph [ ranksep=2.0 overlap=false ]\n".to_string();
walk_node(current_node, 10, sites, &mut visited, overrides, &mut dot_graph, &mut natural_weights);
dot_graph.push_str("}\n");
{
let graph_file = std::fs::File::create("graph.dot");
if let Ok(mut file) = graph_file {
let _ = file.write_all(dot_graph.as_bytes());
}
// Make sure that parent indices go both ways
let parent_map = sites.iter().enumerate().map(|(i,s)| (i, s.parent_indices.clone())).collect::<HashMap<usize, HashSet<usize>>>();
for (i, site) in parent_map {
for parent in site.iter() {
sites[*parent].parent_indices.insert(i);
}
if let Err(e) = write_routing_overrides_template(config, &natural_weights) {
tracing::error!("Unable to write routing overrides template: {:?}", e);
} else {
tracing::info!("Wrote routing overrides template");
}
} else {
}
// Initialize the visualization
let mut dot_graph = "digraph G {\n graph [ ranksep=2.0 overlap=false ]\n".to_string();
// Make sure we know where the root is
let Some(root_idx) = sites.iter().position(|s| s.name == root_site) else {
tracing::error!("Unable to build a path-weights graph because I can't find the root node");
return Err(UispIntegrationError::NoRootSite);
};
// Now we iterate through every node that ISN'T the root
for i in 0..sites.len() {
// Skip the root. It's not going anywhere.
if (i == root_idx) {
continue;
}
// We need to find the shortest path to the root
let parents = sites[i].parent_indices.clone();
for destination_idx in parents {
// Is there a route override?
if let Some(route_override) = overrides.iter().find(|o| o.from_site == sites[i].name && o.to_site == sites[destination_idx].name) {
sites[i].route_weights.push((destination_idx, route_override.cost));
continue;
}
// If there's a direct route, it makes sense to use it
if destination_idx == root_idx {
sites[i].route_weights.push((destination_idx, 10));
continue;
}
// There's no direct route, so we want to evaluate the shortest path
let mut visited = std::collections::HashSet::new();
visited.insert(i); // Don't go back to where we came from
let weight = find_shortest_path(
destination_idx,
root_idx,
visited,
sites,
overrides,
10,
);
if let Some(shortest) = weight {
sites[i].route_weights.push((destination_idx, shortest));
}
}
}
// Apply the lowest weight route
let site_index = sites
.iter()
.enumerate()
.map(|(i, site)| (i, site.name.clone()))
.collect::<std::collections::HashMap<usize, String>>();
for site in sites.iter_mut() {
if site.site_type != UispSiteType::Root && !site.route_weights.is_empty() {
// Sort to find the lowest exit
site.route_weights.sort_by(|a, b| a.1.cmp(&b.1));
site.selected_parent = Some(site.route_weights[0].0);
}
// Plot it
for (i,(idx, weight)) in site.route_weights.iter().enumerate() {
let from = site_index.get(&idx).unwrap().clone();
let to = site.name.clone();
if i == 0 {
dot_graph.push_str(&format!("\"{}\" -> \"{}\" [label=\"{}\" color=\"red\"] \n", from, to, weight));
} else {
dot_graph.push_str(&format!("\"{}\" -> \"{}\" [label=\"{}\"] \n", from, to, weight));
}
}
}
dot_graph.push_str("}\n");
{
let graph_file = std::fs::File::create("graph.dot");
if let Ok(mut file) = graph_file {
let _ = file.write_all(dot_graph.as_bytes());
}
}
Ok(())
}
fn walk_node(
idx: usize,
weight: u32,
fn find_shortest_path(
from_idx: usize,
root_idx: usize,
mut visited: HashSet<usize>,
sites: &mut Vec<UispSite>,
visited: &mut std::collections::HashSet<usize>,
overrides: &Vec<RouteOverride>,
dot_graph: &mut String,
natural_weights: &mut Vec<RouteOverride>,
) {
if visited.contains(&idx) {
return;
weight: u32,
) -> Option<u32> {
// Make sure we don't loop
if visited.contains(&from_idx) {
return None;
}
visited.insert(idx);
for i in 0..sites.len() {
if sites[i].parent_indices.contains(&idx) {
let from = sites[i].name.clone();
let to = sites[idx].name.clone();
if sites[idx].site_type != UispSiteType::Client && sites[i].site_type != UispSiteType::Client
{
dot_graph.push_str(&format!("\"{}\" -> \"{}\" [label=\"{}\"] \n", from, to, weight));
natural_weights.push(RouteOverride {
from_site: from.clone(),
to_site: to.clone(),
cost: weight,
});
}
if let Some(route_override) = overrides
.iter()
.find(|o| (o.from_site == from && o.to_site == to) || (o.from_site == to && o.to_site == from))
{
sites[i].route_weights.push((idx, route_override.cost));
tracing::info!("Applied route override {} - {}", route_override.from_site, route_override.to_site);
} else {
sites[i].route_weights.push((idx, weight));
}
walk_node(i, weight + 10, sites, visited, overrides, dot_graph, natural_weights);
visited.insert(from_idx);
let destinations = sites[from_idx].parent_indices.clone();
for destination_idx in destinations {
// Is there a route override?
if let Some(route_override) = overrides.iter().find(|o| o.from_site == sites[from_idx].name && o.to_site == sites[destination_idx].name) {
return Some(route_override.cost);
}
// If there's a direct route, go that way
if destination_idx == root_idx {
return Some(weight + 10);
}
// Don't go back to where we came from
if visited.contains(&destination_idx) {
continue;
}
// Calculate the route
let new_weight = find_shortest_path(destination_idx, root_idx, visited.clone(), sites, overrides, weight + 10);
if let Some(new_weight) = new_weight {
return Some(new_weight);
}
}
None
}

View File

@ -11,6 +11,18 @@ pub struct UispDataLink {
}
impl UispDataLink {
/// Inverts a data-link to provide the recripocal link.
pub fn invert(&self) -> Self {
Self {
id: self.id.clone(),
from_site_id: self.to_site_id.clone(),
to_site_id: self.from_site_id.clone(),
from_site_name: self.to_site_name.clone(),
to_site_name: self.from_site_name.clone(),
can_delete: self.can_delete,
}
}
/// Converts a UISP DataLink into a UispDataLink.
///
/// # Arguments