rws_gateway_api/gateway_api/
addresses.rs1use super::types::{GatewaySpecAddress, GatewayStatusAddress};
19
20pub const SENTINEL: &str = "testing.x-k8s.io/sentinel";
22pub const CLUSTER: &str = "Cluster";
24
25#[derive(Debug, PartialEq)]
28pub struct Assignment {
29 pub addresses: Vec<GatewayStatusAddress>,
30 pub condition: (bool, &'static str, String),
31}
32
33pub fn routability_supported(routability: &str) -> bool {
35 matches!(routability, "" | CLUSTER | SENTINEL)
36}
37
38pub fn requested_cluster_ip(spec: &[GatewaySpecAddress]) -> Option<&str> {
40 spec.iter().find(|a| a.routability.as_deref() == Some(CLUSTER) && !a.value.is_empty() && is_ip(a)).map(|a| a.value.as_str())
41}
42
43fn is_ip(address: &GatewaySpecAddress) -> bool {
44 address.address_type.as_deref().unwrap_or("IPAddress") == "IPAddress"
45}
46
47pub fn assign(spec: &[GatewaySpecAddress], load_balancer: &[GatewayStatusAddress], cluster_ips: &[String]) -> Assignment {
49 if spec.is_empty() {
50 let addresses = load_balancer.iter().map(|a| GatewayStatusAddress { routability: Some(String::new()), ..a.clone() }).collect();
51 return Assignment { addresses, condition: (true, "AddressesAssigned", "All addresses are assigned".to_string()) };
52 }
53 let mut used: Vec<String> = Vec::new();
54 let mut addresses = Vec::new();
55 let mut unsatisfied = Vec::new();
56 for entry in spec {
57 let routability = entry.routability.clone().unwrap_or_default();
58 let candidates: Vec<(Option<String>, String)> = match routability.as_str() {
59 _ if !is_ip(entry) => Vec::new(),
60 "" | SENTINEL => load_balancer.iter().map(|a| (a.address_type.clone(), a.value.clone())).collect(),
61 CLUSTER => cluster_ips.iter().map(|ip| (Some("IPAddress".to_string()), ip.clone())).collect(),
62 _ => Vec::new(),
63 };
64 let found = candidates.into_iter().find(|(_, value)| !used.contains(value) && (entry.value.is_empty() || *value == entry.value));
65 match found {
66 Some((address_type, value)) => {
67 used.push(value.clone());
68 addresses.push(GatewayStatusAddress { address_type, value, routability: Some(routability) });
69 }
70 None => unsatisfied.push(describe(entry)),
71 }
72 }
73 let condition = if unsatisfied.is_empty() {
74 (true, "AddressesAssigned", "All addresses are assigned".to_string())
75 } else if addresses.is_empty() {
76 (false, "NotAssigned", format!("Not assigned: {}", unsatisfied.join(", ")))
77 } else {
78 (false, "PartiallyAssigned", format!("Not assigned: {}", unsatisfied.join(", ")))
79 };
80 Assignment { addresses, condition }
81}
82
83fn describe(entry: &GatewaySpecAddress) -> String {
84 let value = if entry.value.is_empty() { "any address" } else { entry.value.as_str() };
85 match entry.routability.as_deref().unwrap_or("") {
86 "" => value.to_string(),
87 routability => format!("{value} ({routability})"),
88 }
89}
90
91#[cfg(test)]
92mod tests;