Skip to content

Commit 5a8fef1

Browse files
Resync now does a full re-list; add test_resync_period_triggers_full_list
Co-authored-by: brendandburns <5751682+brendandburns@users.noreply.github.com>
1 parent 90c2b02 commit 5a8fef1

2 files changed

Lines changed: 47 additions & 3 deletions

File tree

kubernetes/informer/informer.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -248,14 +248,22 @@ def _run_loop(self):
248248
self._fire(BOOKMARK, event.get("raw_object", obj))
249249
elif evt_type == ERROR:
250250
self._fire(ERROR, obj)
251-
# Periodic resync: re-list and fire MODIFIED for all cached objects
251+
# Periodic resync: full re-list from the API server, then
252+
# fire MODIFIED for every cached object so reconciliation
253+
# loops receive a fresh notification.
252254
if (
253255
self._resync_period > 0
254256
and (time.monotonic() - last_resync) >= self._resync_period
255257
):
256258
logger.debug("Informer resync triggered")
257-
for cached_obj in self._cache.list():
258-
self._fire(MODIFIED, cached_obj)
259+
try:
260+
self._initial_list()
261+
except Exception as exc:
262+
logger.exception("Error during resync list; continuing")
263+
self._fire(ERROR, exc)
264+
else:
265+
for cached_obj in self._cache.list():
266+
self._fire(MODIFIED, cached_obj)
259267
last_resync = time.monotonic()
260268
except ApiException as exc:
261269
if exc.status == 410:

kubernetes/test/test_informer.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,42 @@ def fake_stream(func, **kw):
359359
self.assertEqual(len(cached), 1)
360360
self.assertIs(cached[0], pod)
361361

362+
def test_resync_period_triggers_full_list(self):
363+
"""A full List call must be made to the API server on every resync_period."""
364+
pod = _make_pod("default", "resync-pod")
365+
366+
list_func = MagicMock()
367+
list_resp = MagicMock()
368+
list_resp.items = [pod]
369+
list_resp.metadata = MagicMock(resource_version="5")
370+
list_func.return_value = list_resp
371+
372+
informer = SharedInformer(list_func=list_func, resync_period=60)
373+
374+
with patch("kubernetes.informer.informer.Watch") as MockWatch, \
375+
patch("kubernetes.informer.informer.time") as mock_time:
376+
# Sequence of time.monotonic() calls:
377+
# 1. last_resync = time.monotonic() → 0.0
378+
# 2. (time.monotonic() - last_resync) check → 61.0 (triggers resync)
379+
# 3. last_resync = time.monotonic() → 61.0 (reset after resync)
380+
mock_time.monotonic.side_effect = [0.0, 61.0, 61.0]
381+
382+
mock_w = MagicMock()
383+
mock_w.resource_version = "5"
384+
385+
def fake_stream(func, **kw):
386+
yield {"type": "ADDED", "object": pod}
387+
informer._stop_event.set()
388+
389+
mock_w.stream.side_effect = fake_stream
390+
MockWatch.return_value = mock_w
391+
392+
informer.start()
393+
informer._thread.join(timeout=3)
394+
395+
# list_func called once for the initial list + once for the resync = 2
396+
self.assertEqual(list_func.call_count, 2)
397+
362398
def test_resource_version_stored_from_watch(self):
363399
"""After the watch stream ends the latest RV is preserved for reconnect."""
364400
pod = _make_pod("default", "rv-pod")

0 commit comments

Comments
 (0)