Enhance carbon-copy behavior to prevent overwriting devPresentLastScan in sync operations #1651

This commit is contained in:
Jokob @NetAlertX
2026-05-28 05:31:09 +00:00
parent 1094ef3ff7
commit f0cc4d123c
3 changed files with 45 additions and 4 deletions

View File

@@ -326,12 +326,22 @@ def main():
placeholders = ', '.join('?' for _ in insert_cols)
if sync_behavior == 'carbon-copy':
# UPSERT: on MAC conflict update all columns except devMac.
# UPSERT: on MAC conflict update all columns except devMac and
# devPresentLastScan.
# devMac is the PRIMARY KEY so it is excluded from the SET clause.
# NOTE: this raw SQL bypasses can_overwrite_field() — ALL fields
# devPresentLastScan is excluded to prevent a node's offline report
# from clobbering the hub's own scan result: if a device is online
# on the hub network but offline on a node, the raw UPSERT would
# flip devPresentLastScan = 0 every sync cycle, triggering
# Connected/Disconnected events on each scan and causing the device
# to be flagged as Flapping. Presence is owned by
# update_presence_from_CurrentScan(); the carbon-copy path respects
# that contract by leaving devPresentLastScan to the normal pipeline.
# NOTE: this raw SQL bypasses can_overwrite_field() — ALL other fields
# including USER/LOCKED-sourced ones are overwritten. Node is fully
# authoritative in this mode.
update_cols = [col for col in insert_cols if col != 'devMac']
_CARBON_COPY_SKIP = {'devMac', 'devPresentLastScan'}
update_cols = [col for col in insert_cols if col not in _CARBON_COPY_SKIP]
update_clause = ', '.join(f'{col}=excluded.{col}' for col in update_cols)
sql = (
f'INSERT INTO Devices ({columns}) VALUES ({placeholders}) '

View File

@@ -351,7 +351,8 @@ def sync_insert_devices(
placeholders = ", ".join("?" for _ in insert_cols)
if behavior == "carbon-copy":
update_cols = [col for col in insert_cols if col != "devMac"]
_CARBON_COPY_SKIP = {"devMac", "devPresentLastScan"}
update_cols = [col for col in insert_cols if col not in _CARBON_COPY_SKIP]
update_clause = ", ".join(f"{col}=excluded.{col}" for col in update_cols)
sql = (
f"INSERT INTO Devices ({columns}) VALUES ({placeholders}) "

View File

@@ -668,6 +668,36 @@ class TestSyncBehavior:
cur.execute("SELECT COUNT(*) AS cnt FROM Devices WHERE devMac = ?", ("aa:bb:cc:dd:ee:01",))
assert cur.fetchone()["cnt"] == 1
def test_carbon_copy_does_not_overwrite_devPresentLastScan(self, conn):
"""Regression: carbon-copy must NOT clobber devPresentLastScan.
Scenario: device is online on the hub (devPresentLastScan=1) but the
node reports it as offline (devPresentLastScan=0). Without the fix the
UPSERT would flip presence to 0, triggering a Device Down event on the
next scan cycle and a Connected event on the scan after that, causing
the device to accumulate enough churn events to be flagged as Flapping.
"""
cur = conn.cursor()
# Hub already knows this device and currently sees it as online.
cur.execute(
"INSERT INTO Devices (devMac, devName, devPresentLastScan) VALUES (?, ?, ?)",
("aa:bb:cc:dd:ee:01", "HubDevice", 1),
)
conn.commit()
# Node reports same MAC as offline.
device = make_device_dict(mac="aa:bb:cc:dd:ee:01", devPresentLastScan=0)
sync_insert_devices(conn, [device], behavior="carbon-copy")
cur.execute(
"SELECT devPresentLastScan FROM Devices WHERE devMac = ?",
("aa:bb:cc:dd:ee:01",),
)
row = cur.fetchone()
assert row["devPresentLastScan"] == 1, (
"carbon-copy must not overwrite devPresentLastScan with a node's offline value"
)
# ------------------------------------------------------------------
# hub-defaults — no direct write, hub pipeline handles it
# ------------------------------------------------------------------