use std::sync::Arc;
use rustlavel_gateway::Locate;
use crate::client::Discovery;
use crate::resolve::{Balance, RoundRobin};
pub struct Balancer {
discovery: Discovery,
balance: Arc<dyn Balance>,
}
impl Balancer {
pub fn new(discovery: Discovery) -> Balancer {
Balancer { discovery, balance: Arc::new(RoundRobin::new()) }
}
pub fn balancing(mut self, balance: Arc<dyn Balance>) -> Balancer {
self.balance = balance;
self
}
}
impl Locate for Balancer {
fn base_for<'a>(
&'a self,
service: &'a str,
) -> std::pin::Pin<Box<dyn Future<Output = Option<String>> + Send + 'a>> {
Box::pin(async move {
let instances = match self.discovery.lookup(service).await {
Ok(instances) => instances,
Err(_) => return None,
};
self.balance.pick(&instances).map(|instance| instance.url())
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::eureka;
use crate::instance::Instance;
use rustlavel_client::{Client as Http, Fake, FakeResponse};
fn balancer_over(instances: Vec<Instance>) -> Balancer {
let http = Http::new().faking(
Fake::new()
.on("/eureka/apps/ORDERS", FakeResponse::json(eureka::application_json("ORDERS", &instances))),
);
Balancer::new(Discovery::new(vec!["http://registry:8761".into()]).http(http))
}
#[tokio::test]
async fn a_service_name_becomes_an_instance_address() {
let balancer = balancer_over(vec![Instance::new("orders", "10.0.0.7", 8080)]);
assert_eq!(balancer.base_for("ORDERS").await.as_deref(), Some("http://10.0.0.7:8080"));
}
#[tokio::test]
async fn successive_requests_are_spread_across_the_instances() {
let balancer = balancer_over(vec![
Instance::new("orders", "10.0.0.1", 8080),
Instance::new("orders", "10.0.0.2", 8080),
]);
let first = balancer.base_for("ORDERS").await;
let second = balancer.base_for("ORDERS").await;
assert_ne!(first, second, "every request went to the same instance");
}
#[tokio::test]
async fn a_service_with_nothing_registered_locates_nothing() {
assert!(balancer_over(vec![]).base_for("ORDERS").await.is_none());
}
#[tokio::test]
async fn an_unreachable_registry_locates_nothing() {
let balancer = Balancer::new(
Discovery::new(vec!["http://registry:8761".into()]).http(Http::new().faking(Fake::new())),
);
assert!(balancer.base_for("ORDERS").await.is_none());
}
}