From ab05261175a360220deb0cb9f9ceafc3760b2b1d Mon Sep 17 00:00:00 2001 From: Nikolay Komarevskiy Date: Wed, 22 Jan 2025 10:24:49 +0100 Subject: [PATCH] chore: refactor to RoutesStats --- ic-agent/src/agent/route_provider.rs | 40 ++++++++++++------- .../dynamic_routing/dynamic_route_provider.rs | 20 +++++----- .../snapshot/latency_based_routing.rs | 38 ++++++++++-------- .../snapshot/round_robin_routing.rs | 11 +++-- .../snapshot/routing_snapshot.rs | 14 +++---- 5 files changed, 70 insertions(+), 53 deletions(-) diff --git a/ic-agent/src/agent/route_provider.rs b/ic-agent/src/agent/route_provider.rs index 77225a64..276d4aa8 100644 --- a/ic-agent/src/agent/route_provider.rs +++ b/ic-agent/src/agent/route_provider.rs @@ -36,6 +36,22 @@ const ICP0_SUB_DOMAIN: &str = ".icp0.io"; const ICP_API_SUB_DOMAIN: &str = ".icp-api.io"; const LOCALHOST_SUB_DOMAIN: &str = ".localhost"; +/// Statistical info about routing urls. +#[derive(Debug, PartialEq)] +pub struct RoutesStats { + /// Total number of existing routes (both healthy and unhealthy). + total: usize, + /// Number of currently healthy routes, or None if health status information is unavailable. + /// The specific criteria for what constitutes a "healthy" route is implementation dependent. + healthy: Option, +} + +impl RoutesStats { + fn new(total: usize, healthy: Option) -> Self { + Self { total, healthy } + } +} + /// A [`RouteProvider`] for dynamic generation of routing urls. pub trait RouteProvider: std::fmt::Debug + Send + Sync { /// Generates the next routing URL based on the internal routing logic. @@ -52,14 +68,8 @@ pub trait RouteProvider: std::fmt::Debug + Send + Sync { /// fewer are available. fn n_ordered_routes(&self, n: usize) -> Result, AgentError>; - /// Returns the total number of routes and healthy routes as a tuple. - /// - /// - First element is the total number of routes available (both healthy and unhealthy) - /// - Second element is the number of currently healthy routes, or None if health status information is unavailable - /// - /// A healthy route is one that is available and ready to receive traffic. - /// The specific criteria for what constitutes a "healthy" route is implementation dependent. - fn routes_stats(&self) -> (usize, Option); + /// Returns statistics about the total number of existing routes and the number of healthy routes. + fn routes_stats(&self) -> RoutesStats; } /// A simple implementation of the [`RouteProvider`] which produces an even distribution of the urls from the input ones. @@ -104,8 +114,8 @@ impl RouteProvider for RoundRobinRouteProvider { Ok(urls) } - fn routes_stats(&self) -> (usize, Option) { - (self.routes.len(), None) + fn routes_stats(&self) -> RoutesStats { + RoutesStats::new(self.routes.len(), None) } } @@ -146,8 +156,8 @@ impl RouteProvider for Url { fn n_ordered_routes(&self, _: usize) -> Result, AgentError> { Ok(vec![self.route()?]) } - fn routes_stats(&self) -> (usize, Option) { - (1, None) + fn routes_stats(&self) -> RoutesStats { + RoutesStats::new(1, None) } } @@ -231,7 +241,7 @@ impl RouteProvider for DynamicRouteProvider { fn n_ordered_routes(&self, n: usize) -> Result, AgentError> { self.inner.n_ordered_routes(n) } - fn routes_stats(&self) -> (usize, Option) { + fn routes_stats(&self) -> RoutesStats { self.inner.routes_stats() } } @@ -289,8 +299,8 @@ impl RouteProvider for UrlUntilReady { self.url.route() } } - fn routes_stats(&self) -> (usize, Option) { - (1, None) + fn routes_stats(&self) -> RoutesStats { + RoutesStats::new(1, None) } } diff --git a/ic-agent/src/agent/route_provider/dynamic_routing/dynamic_route_provider.rs b/ic-agent/src/agent/route_provider/dynamic_routing/dynamic_route_provider.rs index dca44562..10bef122 100644 --- a/ic-agent/src/agent/route_provider/dynamic_routing/dynamic_route_provider.rs +++ b/ic-agent/src/agent/route_provider/dynamic_routing/dynamic_route_provider.rs @@ -23,7 +23,7 @@ use crate::{ snapshot::routing_snapshot::RoutingSnapshot, type_aliases::AtomicSwap, }, - RouteProvider, + RouteProvider, RoutesStats, }, HttpService, }, @@ -187,9 +187,9 @@ where Ok(urls) } - fn routes_stats(&self) -> (usize, Option) { + fn routes_stats(&self) -> RoutesStats { let snapshot = self.routing_snapshot.load(); - snapshot.nodes_stats() + snapshot.routes_stats() } } @@ -296,7 +296,7 @@ mod tests { assert_routed_domains, route_n_times, NodeHealthCheckerMock, NodesFetcherMock, }, }, - RouteProvider, + RouteProvider, RoutesStats, }, Agent, AgentError, }; @@ -422,7 +422,7 @@ mod tests { tokio::time::sleep(snapshot_update_duration).await; let routed_domains = route_n_times(6, Arc::clone(&route_provider)); assert_routed_domains(routed_domains, vec![node_1.domain()], 6); - assert_eq!(route_provider.routes_stats(), (1, Some(1))); + assert_eq!(route_provider.routes_stats(), RoutesStats::new(1, Some(1))); // Test 2: multiple route() calls return 3 different domains with equal fairness (repetition). // Two healthy nodes are added to the topology. @@ -437,7 +437,7 @@ mod tests { vec![node_1.domain(), node_2.domain(), node_3.domain()], 2, ); - assert_eq!(route_provider.routes_stats(), (3, Some(3))); + assert_eq!(route_provider.routes_stats(), RoutesStats::new(3, Some(3))); // Test 3: multiple route() calls return 2 different domains with equal fairness (repetition). // One node is set to unhealthy. @@ -445,7 +445,7 @@ mod tests { tokio::time::sleep(snapshot_update_duration).await; let routed_domains = route_n_times(6, Arc::clone(&route_provider)); assert_routed_domains(routed_domains, vec![node_1.domain(), node_3.domain()], 3); - assert_eq!(route_provider.routes_stats(), (3, Some(2))); + assert_eq!(route_provider.routes_stats(), RoutesStats::new(3, Some(2))); // Test 4: multiple route() calls return 3 different domains with equal fairness (repetition). // Unhealthy node is set back to healthy. @@ -457,7 +457,7 @@ mod tests { vec![node_1.domain(), node_2.domain(), node_3.domain()], 2, ); - assert_eq!(route_provider.routes_stats(), (3, Some(3))); + assert_eq!(route_provider.routes_stats(), RoutesStats::new(3, Some(3))); // Test 5: multiple route() calls return 3 different domains with equal fairness (repetition). // One healthy node is added, but another one goes unhealthy. @@ -476,7 +476,7 @@ mod tests { vec![node_2.domain(), node_3.domain(), node_4.domain()], 2, ); - assert_eq!(route_provider.routes_stats(), (4, Some(3))); + assert_eq!(route_provider.routes_stats(), RoutesStats::new(4, Some(3))); // Test 6: multiple route() calls return a single domain=api1.com. // One node is set to unhealthy and one is removed from the topology. @@ -485,7 +485,7 @@ mod tests { tokio::time::sleep(snapshot_update_duration).await; let routed_domains = route_n_times(3, Arc::clone(&route_provider)); assert_routed_domains(routed_domains, vec![node_2.domain()], 3); - assert_eq!(route_provider.routes_stats(), (3, Some(1))); + assert_eq!(route_provider.routes_stats(), RoutesStats::new(3, Some(1))); } #[tokio::test] diff --git a/ic-agent/src/agent/route_provider/dynamic_routing/snapshot/latency_based_routing.rs b/ic-agent/src/agent/route_provider/dynamic_routing/snapshot/latency_based_routing.rs index 87b3b37e..96f965ac 100644 --- a/ic-agent/src/agent/route_provider/dynamic_routing/snapshot/latency_based_routing.rs +++ b/ic-agent/src/agent/route_provider/dynamic_routing/snapshot/latency_based_routing.rs @@ -5,8 +5,11 @@ use std::{ use rand::Rng; -use crate::agent::route_provider::dynamic_routing::{ - health_check::HealthCheckStatus, node::Node, snapshot::routing_snapshot::RoutingSnapshot, +use crate::agent::route_provider::{ + dynamic_routing::{ + health_check::HealthCheckStatus, node::Node, snapshot::routing_snapshot::RoutingSnapshot, + }, + RoutesStats, }; // Determines the size of the sliding window used for storing latencies and availabilities of nodes. @@ -323,8 +326,8 @@ impl RoutingSnapshot for LatencyRoutingSnapshot { true } - fn nodes_stats(&self) -> (usize, Option) { - (self.existing_nodes.len(), Some(self.healthy_nodes.len())) + fn routes_stats(&self) -> RoutesStats { + RoutesStats::new(self.existing_nodes.len(), Some(self.healthy_nodes.len())) } } @@ -335,15 +338,18 @@ mod tests { time::Duration, }; - use crate::agent::route_provider::dynamic_routing::{ - health_check::HealthCheckStatus, - node::Node, - snapshot::{ - latency_based_routing::{ - compute_score, weighted_sample, LatencyRoutingSnapshot, NodeWithMetrics, + use crate::agent::route_provider::{ + dynamic_routing::{ + health_check::HealthCheckStatus, + node::Node, + snapshot::{ + latency_based_routing::{ + compute_score, weighted_sample, LatencyRoutingSnapshot, NodeWithMetrics, + }, + routing_snapshot::RoutingSnapshot, }, - routing_snapshot::RoutingSnapshot, }, + RoutesStats, }; #[test] @@ -356,7 +362,7 @@ mod tests { assert!(!snapshot.has_nodes()); assert!(snapshot.next_node().is_none()); assert!(snapshot.next_n_nodes(1).is_empty()); - assert_eq!(snapshot.nodes_stats(), (0, Some(0))); + assert_eq!(snapshot.routes_stats(), RoutesStats::new(0, Some(0))); } #[test] @@ -372,7 +378,7 @@ mod tests { assert!(snapshot.nodes_with_metrics.is_empty()); assert!(!snapshot.has_nodes()); assert!(snapshot.next_node().is_none()); - assert_eq!(snapshot.nodes_stats(), (0, Some(0))); + assert_eq!(snapshot.routes_stats(), RoutesStats::new(0, Some(0))); } #[test] @@ -384,7 +390,7 @@ mod tests { let node = Node::new("api1.com").unwrap(); let health = HealthCheckStatus::new(Some(Duration::from_secs(1))); snapshot.existing_nodes.insert(node.clone()); - assert_eq!(snapshot.nodes_stats(), (1, Some(0))); + assert_eq!(snapshot.routes_stats(), RoutesStats::new(1, Some(0))); // Check first update let is_updated = snapshot.update_node(&node, health); assert!(is_updated); @@ -392,7 +398,7 @@ mod tests { let node_with_metrics = snapshot.nodes_with_metrics.first().unwrap(); assert_eq!(node_with_metrics.score, (2.0 / 1.0) / 2.0); assert_eq!(snapshot.next_node().unwrap(), node); - assert_eq!(snapshot.nodes_stats(), (1, Some(1))); + assert_eq!(snapshot.routes_stats(), RoutesStats::new(1, Some(1))); // Check second update let health = HealthCheckStatus::new(Some(Duration::from_secs(2))); let is_updated = snapshot.update_node(&node, health); @@ -415,7 +421,7 @@ mod tests { assert_eq!(snapshot.nodes_with_metrics.len(), 1); assert_eq!(snapshot.existing_nodes.len(), 1); assert!(snapshot.next_node().is_none()); - assert_eq!(snapshot.nodes_stats(), (1, Some(0))); + assert_eq!(snapshot.routes_stats(), RoutesStats::new(1, Some(0))); } #[test] diff --git a/ic-agent/src/agent/route_provider/dynamic_routing/snapshot/round_robin_routing.rs b/ic-agent/src/agent/route_provider/dynamic_routing/snapshot/round_robin_routing.rs index 28c06fdf..e9418adf 100644 --- a/ic-agent/src/agent/route_provider/dynamic_routing/snapshot/round_robin_routing.rs +++ b/ic-agent/src/agent/route_provider/dynamic_routing/snapshot/round_robin_routing.rs @@ -6,8 +6,11 @@ use std::{ }, }; -use crate::agent::route_provider::dynamic_routing::{ - health_check::HealthCheckStatus, node::Node, snapshot::routing_snapshot::RoutingSnapshot, +use crate::agent::route_provider::{ + dynamic_routing::{ + health_check::HealthCheckStatus, node::Node, snapshot::routing_snapshot::RoutingSnapshot, + }, + RoutesStats, }; /// Routing snapshot, which samples nodes in a round-robin fashion. @@ -107,8 +110,8 @@ impl RoutingSnapshot for RoundRobinRoutingSnapshot { } } - fn nodes_stats(&self) -> (usize, Option) { - (self.existing_nodes.len(), Some(self.healthy_nodes.len())) + fn routes_stats(&self) -> RoutesStats { + RoutesStats::new(self.existing_nodes.len(), Some(self.healthy_nodes.len())) } } diff --git a/ic-agent/src/agent/route_provider/dynamic_routing/snapshot/routing_snapshot.rs b/ic-agent/src/agent/route_provider/dynamic_routing/snapshot/routing_snapshot.rs index 89191137..b45fe585 100644 --- a/ic-agent/src/agent/route_provider/dynamic_routing/snapshot/routing_snapshot.rs +++ b/ic-agent/src/agent/route_provider/dynamic_routing/snapshot/routing_snapshot.rs @@ -1,6 +1,9 @@ use std::fmt::Debug; -use crate::agent::route_provider::dynamic_routing::{health_check::HealthCheckStatus, node::Node}; +use crate::agent::route_provider::{ + dynamic_routing::{health_check::HealthCheckStatus, node::Node}, + RoutesStats, +}; /// A trait for interacting with the snapshot of nodes (routing table). pub trait RoutingSnapshot: Send + Sync + Clone + Debug { @@ -15,11 +18,6 @@ pub trait RoutingSnapshot: Send + Sync + Clone + Debug { fn sync_nodes(&mut self, nodes: &[Node]) -> bool; /// Updates the health status of a specific node, returning `true` if the node was found and updated. fn update_node(&mut self, node: &Node, health: HealthCheckStatus) -> bool; - /// Returns the total number of nodes and healthy nodes as a tuple. - /// - /// - First element is the total number of nodes available (both healthy and unhealthy) - /// - Second element is the number of currently healthy nodes, or None if health status information is unavailable - /// - /// The specific criteria for what constitutes a "healthy" node is implementation dependent. - fn nodes_stats(&self) -> (usize, Option); + /// Returns statistics about the routes (nodes). + fn routes_stats(&self) -> RoutesStats; }