Skip to content

Commit 5270e09

Browse files
Codexclaude
andcommitted
fix(controller): read config status from DB in reconciliation
The _reconcile_modified_status method was reading config.status from the cached device object (get_device uses cache_memoize with a 30-day TTL). When the config status changed to "modified" after the device was cached, the reconciliation would see the stale cached status and skip the reconciliation. Fix: query Config directly from the database using Config.objects.get() instead of accessing device.config, which may return cached state. Also add a test that verifies reconciliation works even when the device object is served from cache. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 59e6512 commit 5270e09

File tree

2 files changed

+40
-1
lines changed

2 files changed

+40
-1
lines changed

openwisp_controller/config/controller/views.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,10 +187,21 @@ def _reconcile_modified_status(device):
187187
"modified" state for longer than the grace period and the device
188188
is actively polling (proven by this very checksum request), we
189189
set the status to "applied".
190+
191+
Note: the device object may come from cache_memoize (30-day TTL),
192+
so we must read config status fresh from the database to avoid
193+
acting on stale cached state.
190194
"""
191195
from django.utils import timezone
192196

193-
config = device.config
197+
from ..models import Config
198+
199+
# Read config fresh from DB to bypass any cached state on the
200+
# device object (get_device is cached with a 30-day TTL).
201+
try:
202+
config = Config.objects.get(device=device)
203+
except Config.DoesNotExist:
204+
return
194205
if config.status != "modified":
195206
return
196207
grace = DeviceChecksumView._STATUS_RECONCILE_GRACE_SECONDS

openwisp_controller/config/tests/test_controller.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,34 @@ def test_device_checksum_no_reconcile_within_grace_period(self):
326326
c.refresh_from_db()
327327
self.assertEqual(c.status, "modified")
328328

329+
def test_device_checksum_reconcile_bypasses_device_cache(self):
330+
"""
331+
Reconciliation must read config status from DB, not from
332+
the cached device object (get_device has a 30-day cache TTL).
333+
"""
334+
from datetime import timedelta
335+
336+
from django.utils import timezone
337+
338+
d = self._create_device_config()
339+
c = d.config
340+
url = reverse("controller:device_checksum", args=[d.pk])
341+
342+
# First request populates the get_device cache
343+
response = self.client.get(url, {"key": d.key})
344+
self.assertEqual(response.status_code, 200)
345+
346+
# Now set status to modified and backdate past grace period
347+
c.set_status_modified()
348+
Config.objects.filter(pk=c.pk).update(
349+
modified=timezone.now() - timedelta(seconds=600)
350+
)
351+
352+
# Second request should reconcile even though device is cached
353+
response = self.client.get(url, {"key": d.key})
354+
self.assertEqual(response.status_code, 200)
355+
c.refresh_from_db()
356+
self.assertEqual(c.status, "applied")
329357

330358
def test_device_checksum_bad_uuid(self):
331359
d = self._create_device_config()

0 commit comments

Comments
 (0)