use std::path::Path;
use gunnar_grpc::topology::Facts;
#[cfg(feature = "wg-client")]
fn front_principals() -> std::collections::BTreeMap<String, String> {
crate::fed_principals::OFFERED_PRINCIPALS
.iter()
.map(|(offer, id, _)| {
let service = match *offer {
crate::fed_principals::OFFER_MONETIZE_IDENTITY => gunnar_grpc::topology::SERVICE_MONETIZE,
crate::fed_principals::OFFER_CONSOLE_PUBKEY | crate::fed_principals::OFFER_GUEST_PUBKEY => gunnar_grpc::topology::SERVICE_CONSOLE,
_ => gunnar_grpc::topology::ROLE_FRONT,
};
(id.to_string(), service.to_string())
})
.collect()
}
#[cfg(feature = "wg-client")]
pub fn from_reply(reply: &nordisk_pairing::join::JoinReply) -> Facts {
use nordisk_pairing::Role;
let me = reply.me.name.clone();
let address = match (reply.role, reply.primary_utility) {
(Some(Role::Primary), Some(utility)) => utility.to_string(),
_ => reply.me.overlay.to_string(),
};
let peers = reply
.peers
.iter()
.filter(|p| p.name != me)
.map(|p| {
let mut hosts = Vec::new();
if let Some(u) = p.utility {
hosts.push(u.to_string());
}
if p.role == Role::Primary {
if let Some(u) = reply.primary_utility {
hosts.push(u.to_string());
}
}
hosts.push(p.overlay.to_string());
if let Some(e) = &p.endpoint {
hosts.push(gunnar_grpc::topology::host_of(e));
}
hosts.dedup();
gunnar_grpc::topology::Peer { name: p.name.clone(), role: p.role.as_str().to_owned(), hosts }
})
.collect();
Facts {
estate: reply.estate.clone(),
me,
address,
peers,
front_principals: front_principals(),
record: gunnar_grpc::topology::Record::from_feed(&reply.feed),
..Facts::default()
}
}
pub fn facts(root: &Path) -> Facts {
#[cfg(feature = "wg-client")]
let mut facts = crate::fed_at_rest::read(root, crate::pairing::JOINED_REL)
.and_then(|t| nordisk_pairing::join::JoinReply::parse(&t).ok())
.map(|reply| from_reply(&reply))
.unwrap_or_else(|| Facts { front_principals: front_principals(), ..Facts::default() });
#[cfg(not(feature = "wg-client"))]
let mut facts = Facts::default();
let fs = crate::stores::filesystem_use("data root", root);
if fs.measured && fs.total_bytes > 0 {
facts.disk = Some((fs.total_bytes, fs.total_bytes.saturating_sub(fs.free_bytes)));
}
facts
}
#[cfg(all(test, feature = "wg-client"))]
mod tests {
use super::*;
use nordisk_pairing::join::{Assigned, JoinReply, Peer as PairPeer};
use nordisk_pairing::Role;
use std::net::Ipv4Addr;
fn peer(name: &str, role: Role, overlay: [u8; 4], utility: Option<[u8; 4]>, endpoint: Option<&str>) -> PairPeer {
PairPeer {
name: name.into(),
role,
tunnel_pubkey: String::new(),
overlay: Ipv4Addr::from(overlay),
endpoint: endpoint.map(str::to_owned),
utility: utility.map(Ipv4Addr::from),
}
}
#[test]
fn the_feed_names_this_box_and_its_peers() {
let reply = JoinReply {
estate: "test2.gunnar.rs".into(),
me: Assigned { name: "vm2".into(), overlay: Ipv4Addr::new(10, 99, 0, 2) },
cert_pem: None,
role: Some(Role::Primary),
primary_overlay: Some(Ipv4Addr::new(10, 99, 0, 2)),
primary_utility: Some(Ipv4Addr::new(10, 13, 8, 121)),
peers: vec![
peer("front", Role::Front, [10, 99, 0, 1], None, Some("95.111.211.189:51820")),
peer("vm2", Role::Primary, [10, 99, 0, 2], None, None),
peer("vm3", Role::Twin, [10, 99, 0, 3], Some([10, 13, 8, 122]), None),
],
feed: Default::default(),
};
let f = from_reply(&reply);
assert_eq!((f.estate.as_str(), f.me.as_str(), f.address.as_str()), ("test2.gunnar.rs", "vm2", "10.13.8.121"));
assert_eq!(f.record, None, "a feed with no estate record is no record");
let names: Vec<_> = f.peers.iter().map(|p| (p.name.as_str(), p.role.as_str())).collect();
assert_eq!(names, [("front", "front"), ("vm3", "twin")], "this box is not its own peer");
assert_eq!(f.peers[0].hosts, ["10.99.0.1", "95.111.211.189"]);
assert_eq!(f.peers[1].hosts, ["10.13.8.122", "10.99.0.3"]);
assert_eq!(f.front_principals.get("gunnar-ui").map(String::as_str), Some("console"));
assert_eq!(f.front_principals.get("monetize").map(String::as_str), Some("monetize"));
}
#[test]
fn an_unpaired_box_knows_itself_and_its_disk() {
let dir = tempfile::tempdir().unwrap();
let f = facts(dir.path());
assert!(f.estate.is_empty() && f.peers.is_empty());
assert!(f.disk.is_some_and(|(total, _)| total > 0));
}
#[test]
fn the_feed_carries_the_installers_estate_record() {
use gunnar_grpc::topology::{Record, RecordBox};
let record = Record {
variant: "gunnar-twin".into(),
versions: vec![("gunnar".into(), "abc".into())],
boxes: ["front", "main", "twin"]
.iter()
.map(|n| RecordBox { name: n.to_string(), laid_as: n.to_string(), utility: format!("10.0.0.{}", n.len()), ..Default::default() })
.collect(),
};
let reply = JoinReply {
estate: "e".into(),
me: Assigned { name: "main".into(), overlay: Ipv4Addr::new(10, 99, 0, 2) },
cert_pem: None,
role: Some(Role::Primary),
primary_overlay: None,
primary_utility: None,
peers: vec![],
feed: record.to_feed(),
};
let reply = JoinReply::parse(&reply.render().unwrap()).unwrap();
assert_eq!(from_reply(&reply).record, Some(record));
}
}