Skip to content

Commit 9c708ad

Browse files
Add BOOKMARK event support to SharedInformer
Co-authored-by: brendandburns <5751682+brendandburns@users.noreply.github.com>
1 parent ef3f21c commit 9c708ad

3 files changed

Lines changed: 82 additions & 4 deletions

File tree

kubernetes/informer/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# limitations under the License.
1414

1515
from .cache import ObjectCache, _meta_namespace_key
16-
from .informer import SharedInformer, ADDED, MODIFIED, DELETED, ERROR
16+
from .informer import SharedInformer, ADDED, MODIFIED, DELETED, BOOKMARK, ERROR
1717

1818
__all__ = [
1919
"ObjectCache",
@@ -22,5 +22,6 @@
2222
"ADDED",
2323
"MODIFIED",
2424
"DELETED",
25+
"BOOKMARK",
2526
"ERROR",
2627
]

kubernetes/informer/informer.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
ADDED = "ADDED"
3636
MODIFIED = "MODIFIED"
3737
DELETED = "DELETED"
38+
BOOKMARK = "BOOKMARK"
3839
ERROR = "ERROR"
3940

4041

@@ -83,7 +84,7 @@ def __init__(
8384
self._field_selector = field_selector
8485

8586
self._cache = ObjectCache(key_func=key_func)
86-
self._handlers = {ADDED: [], MODIFIED: [], DELETED: [], ERROR: []}
87+
self._handlers = {ADDED: [], MODIFIED: [], DELETED: [], BOOKMARK: [], ERROR: []}
8788
self._handler_lock = threading.Lock()
8889

8990
self._watch = None
@@ -105,8 +106,8 @@ def add_event_handler(self, event_type, handler):
105106
Parameters
106107
----------
107108
event_type:
108-
One of :data:`ADDED`, :data:`MODIFIED`, :data:`DELETED` or
109-
:data:`ERROR`.
109+
One of :data:`ADDED`, :data:`MODIFIED`, :data:`DELETED`,
110+
:data:`BOOKMARK` or :data:`ERROR`.
110111
handler:
111112
Callable invoked with the event object (or the raw exception for
112113
ERROR events).
@@ -227,6 +228,11 @@ def _run_loop(self):
227228
elif evt_type == DELETED:
228229
self._cache._remove(obj)
229230
self._fire(DELETED, obj)
231+
elif evt_type == BOOKMARK:
232+
# BOOKMARK events carry an updated resource version but
233+
# no object state change; the Watch instance already
234+
# records the new resource_version internally.
235+
self._fire(BOOKMARK, event.get("raw_object", obj))
230236
elif evt_type == ERROR:
231237
self._fire(ERROR, obj)
232238
# Periodic resync: re-list and fire MODIFIED for all cached objects

kubernetes/test/test_informer.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from kubernetes.informer.cache import ObjectCache, _meta_namespace_key
2323
from kubernetes.informer.informer import (
2424
ADDED,
25+
BOOKMARK,
2526
DELETED,
2627
ERROR,
2728
MODIFIED,
@@ -269,6 +270,7 @@ def fake_stream(func, **kw):
269270
cached = informer.cache.get_by_key("default/mod-pod")
270271
self.assertIs(cached, pod_v2)
271272

273+
272274
def test_start_is_idempotent(self):
273275
list_func = MagicMock()
274276
list_resp = MagicMock()
@@ -289,6 +291,75 @@ def test_start_is_idempotent(self):
289291
self.assertIs(informer._thread, first_thread)
290292
informer.stop()
291293

294+
def test_bookmark_event_fires_handler(self):
295+
bookmark_obj = {"metadata": {"resourceVersion": "42"}}
296+
events = [
297+
{"type": "BOOKMARK", "object": bookmark_obj, "raw_object": bookmark_obj},
298+
]
299+
300+
received = []
301+
list_func = MagicMock()
302+
list_resp = MagicMock()
303+
list_resp.items = []
304+
list_resp.metadata = MagicMock(resource_version="1")
305+
list_func.return_value = list_resp
306+
307+
informer = SharedInformer(list_func=list_func)
308+
informer.add_event_handler(BOOKMARK, received.append)
309+
310+
with patch("kubernetes.informer.informer.Watch") as MockWatch:
311+
mock_w = MagicMock()
312+
313+
def fake_stream(func, **kw):
314+
yield from events
315+
informer._stop_event.set()
316+
317+
mock_w.stream.side_effect = fake_stream
318+
MockWatch.return_value = mock_w
319+
320+
informer.start()
321+
informer._thread.join(timeout=3)
322+
323+
self.assertEqual(len(received), 1)
324+
self.assertEqual(received[0], bookmark_obj)
325+
# Cache should be unchanged (BOOKMARK does not add/modify/delete objects)
326+
self.assertEqual(informer.cache.list(), [])
327+
328+
def test_bookmark_event_does_not_modify_cache(self):
329+
pod = _make_pod("default", "stable-pod")
330+
bookmark_obj = {"metadata": {"resourceVersion": "99"}}
331+
events = [
332+
{"type": "ADDED", "object": pod},
333+
{"type": "BOOKMARK", "object": bookmark_obj, "raw_object": bookmark_obj},
334+
]
335+
336+
list_func = MagicMock()
337+
list_resp = MagicMock()
338+
list_resp.items = []
339+
list_resp.metadata = MagicMock(resource_version="1")
340+
list_func.return_value = list_resp
341+
342+
informer = SharedInformer(list_func=list_func)
343+
344+
with patch("kubernetes.informer.informer.Watch") as MockWatch:
345+
mock_w = MagicMock()
346+
347+
def fake_stream(func, **kw):
348+
yield from events
349+
informer._stop_event.set()
350+
351+
mock_w.stream.side_effect = fake_stream
352+
MockWatch.return_value = mock_w
353+
354+
informer.start()
355+
informer._thread.join(timeout=3)
356+
357+
# BOOKMARK must not have altered the cache content
358+
cached = informer.cache.list()
359+
self.assertEqual(len(cached), 1)
360+
self.assertIs(cached[0], pod)
361+
292362

293363
if __name__ == "__main__":
294364
unittest.main()
365+

0 commit comments

Comments
 (0)