observe: key migration

This commit is contained in:
lda
2026-04-30 16:10:48 +07:00 Verified
parent 15e1640bbc
commit 181f2733b1
2 changed files with 101 additions and 16 deletions
+57
View File
@@ -179,4 +179,61 @@ mod tests {
let _ = tokio::fs::remove_file(observation_path).await; let _ = tokio::fs::remove_file(observation_path).await;
} }
#[tokio::test]
#[serial]
async fn neighbor_observation_migrates_coarse_mac_key_to_mac_ip_pair() {
let observation_path = temp_file("neighbor-observations-migrate");
let _observation_guard = EnvGuard::set(OBSERVATION_STORE_ENV, &observation_path);
let mut neighbors = std::collections::BTreeMap::new();
neighbors.insert(
"mac:aa:bb:cc:dd:ee:ff".to_string(),
ObservedNeighbor {
key: "mac:aa:bb:cc:dd:ee:ff".to_string(),
mac: Some("aa:bb:cc:dd:ee:ff".to_string()),
ip: None,
first_seen_unix: 1,
last_seen_unix: 1,
last_action: "add".to_string(),
},
);
let fixture = LocalObservationStore {
dhcp_clients: Default::default(),
neighbors,
};
tokio::fs::write(
&observation_path,
serde_json::to_string(&fixture).expect("fixture should serialize"),
)
.await
.expect("fixture should write");
observe_neighbor_event(
"update",
Some("aa:bb:cc:dd:ee:ff".parse().expect("mac should parse")),
Some("192.168.1.2".parse().expect("ip should parse")),
)
.await
.expect("observation should write");
let store = load_observation_store()
.await
.expect("observation store should read");
assert!(!store.neighbors.contains_key("mac:aa:bb:cc:dd:ee:ff"));
let row = store
.neighbors
.get("mac:aa:bb:cc:dd:ee:ff:ip:192.168.1.2")
.expect("coarse key should migrate to pair key");
assert_eq!(row.first_seen_unix, 1);
assert_eq!(row.mac.as_deref(), Some("aa:bb:cc:dd:ee:ff"));
assert_eq!(
row.ip
.expect("ip should be carried into migrated row")
.to_string(),
"192.168.1.2"
);
assert_eq!(row.last_action, "update");
let _ = tokio::fs::remove_file(observation_path).await;
}
} }
+44 -16
View File
@@ -230,26 +230,19 @@ pub async fn observe_neighbor_event(
let mac = mac.map(|value| value.to_string().to_ascii_lowercase()); let mac = mac.map(|value| value.to_string().to_ascii_lowercase());
let mut store = load_observation_store().await.unwrap_or_default(); let mut store = load_observation_store().await.unwrap_or_default();
let mut changed = false; let mut changed = false;
store let alias_keys = neighbor_observation_alias_keys(mac.as_deref(), ip, &key);
let mut row = store
.neighbors .neighbors
.entry(key.clone()) .remove(&key)
.and_modify(|row| { .or_else(|| {
if row.mac != mac alias_keys
|| row.ip != ip .iter()
|| row.last_action != action .find_map(|alias| store.neighbors.remove(alias))
|| row.last_seen_unix != now
{
row.mac = mac.clone();
row.ip = ip;
row.last_action = action.clone();
row.last_seen_unix = now;
changed = true;
}
}) })
.or_insert_with(|| { .unwrap_or_else(|| {
changed = true; changed = true;
ObservedNeighbor { ObservedNeighbor {
key, key: key.clone(),
mac: mac.clone(), mac: mac.clone(),
ip, ip,
first_seen_unix: now, first_seen_unix: now,
@@ -257,6 +250,25 @@ pub async fn observe_neighbor_event(
last_action: action.clone(), last_action: action.clone(),
} }
}); });
for alias in alias_keys {
if store.neighbors.remove(&alias).is_some() {
changed = true;
}
}
if row.key != key
|| row.mac != mac
|| row.ip != ip
|| row.last_action != action
|| row.last_seen_unix != now
{
row.key = key.clone();
row.mac = mac.clone();
row.ip = ip;
row.last_action = action.clone();
row.last_seen_unix = now;
changed = true;
}
store.neighbors.insert(key, row);
if let (Some(mac), Some(ip)) = (mac.as_deref(), ip) if let (Some(mac), Some(ip)) = (mac.as_deref(), ip)
&& !is_offline_neighbor_action(&action) && !is_offline_neighbor_action(&action)
{ {
@@ -296,6 +308,22 @@ fn neighbor_observation_key(mac: Option<MacAddr>, ip: Option<IpAddr>) -> Option<
} }
} }
fn neighbor_observation_alias_keys(
mac: Option<&str>,
ip: Option<IpAddr>,
primary_key: &str,
) -> Vec<String> {
let mut keys = Vec::with_capacity(2);
if let Some(mac) = mac {
keys.push(format!("mac:{mac}"));
}
if let Some(ip) = ip {
keys.push(format!("ip:{ip}"));
}
keys.retain(|key| key != primary_key);
keys
}
fn mark_replaced_neighbor_ips_removed( fn mark_replaced_neighbor_ips_removed(
store: &mut LocalObservationStore, store: &mut LocalObservationStore,
mac: &str, mac: &str,