observe: key migration
This commit is contained in:
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user