Skip to content

Commit 5465f70

Browse files
author
tymuraheiev
committed
Refactored 'namespace_watcher' to handle both create and delete namespace events
1 parent b0308f2 commit 5465f70

File tree

2 files changed

+50
-167
lines changed

2 files changed

+50
-167
lines changed

src/handlers.py

+49-29
Original file line numberDiff line numberDiff line change
@@ -180,41 +180,61 @@ async def create_fn(
180180

181181

182182
@kopf.on.create('', 'v1', 'namespaces')
183-
async def namespace_watcher(logger: logging.Logger, meta: kopf.Meta, **_):
183+
@kopf.on.delete('', 'v1', 'namespaces')
184+
async def namespace_watcher(logger: logging.Logger, reason: kopf.Reason, meta: kopf.Meta, **_):
184185
"""Watch for namespace events
185186
"""
186-
new_ns = meta.name
187-
logger.debug(f'New namespace created: {new_ns} re-syncing')
188-
ns_new_list = []
189-
for cluster_secret in csecs_cache.all_cluster_secret():
190-
obj_body = cluster_secret.body
191-
name = cluster_secret.name
192-
193-
matcheddns = cluster_secret.synced_namespace
194-
195-
logger.debug(f'Old matched namespace: {matcheddns} - name: {name}')
196-
ns_new_list = get_ns_list(logger, obj_body, v1)
197-
logger.debug(f'new matched list: {ns_new_list}')
198-
if new_ns in ns_new_list:
199-
logger.debug(f'Cloning secret {name} into the new namespace {new_ns}')
187+
if reason not in ["create", "delete"]:
188+
logger.error(f'Function "namespace_watcher" was called with incorrect reason: {reason}')
189+
return
190+
191+
ns_name = meta.name
192+
logger.info(f'Namespace {"created" if reason == "create" else "deleted"}: {ns_name}. Re-syncing')
193+
194+
ns_list_new = []
195+
for cached_cluster_secret in csecs_cache.all_cluster_secret():
196+
body = cached_cluster_secret.body
197+
name = cached_cluster_secret.name
198+
ns_list_synced = cached_cluster_secret.synced_namespace
199+
ns_list_new = get_ns_list(logger, body, v1)
200+
ns_list_changed = False
201+
202+
logger.debug(f'ClusterSecret: {name}. Old matched namespaces: {ns_list_synced}')
203+
logger.debug(f'ClusterSecret: {name}. New matched namespaces: {ns_list_new}')
204+
205+
if reason == "create" and ns_name in ns_list_new:
206+
logger.info(f'Cloning secret {name} into the new namespace: {ns_name}')
200207
sync_secret(
201208
logger=logger,
202-
namespace=new_ns,
203-
body=obj_body,
209+
namespace=ns_name,
210+
body=body,
204211
v1=v1,
205212
)
206-
207-
# if there is a new matching ns, refresh cache
208-
cluster_secret.synced_namespace = ns_new_list
209-
csecs_cache.set_cluster_secret(cluster_secret)
210-
211-
# update ns_new_list on the object so then we also delete from there
212-
patch_clustersecret_status(
213-
logger=logger,
214-
name=cluster_secret.name,
215-
new_status={'create_fn': {'syncedns': ns_new_list}},
216-
custom_objects_api=custom_objects_api,
217-
)
213+
ns_list_changed = True
214+
215+
if reason == "delete" and ns_name in ns_list_synced:
216+
logger.info(f'Secret {name} removed from deleted namespace: {ns_name}')
217+
# Ensure that deleted namespace will not come in new list - on moment when this event handled by kopf the namespace in kubernetes can still exists
218+
if ns_name in ns_list_new:
219+
ns_list_new.remove(ns_name)
220+
ns_list_changed = True
221+
222+
# Update ClusterSecret only if there are changes in list of his namespaces
223+
if ns_list_changed:
224+
# Update in-memory cache
225+
cached_cluster_secret.synced_namespace = ns_list_new
226+
csecs_cache.set_cluster_secret(cached_cluster_secret)
227+
228+
# Update the list of synced namespaces in kubernetes object
229+
logger.debug(f'Patching ClusterSecret: {name}')
230+
patch_clustersecret_status(
231+
logger=logger,
232+
name=name,
233+
new_status={'create_fn': {'syncedns': ns_list_new}},
234+
custom_objects_api=custom_objects_api,
235+
)
236+
else:
237+
logger.debug(f'There are no changes in the list of namespaces for ClusterSecret: {name}')
218238

219239

220240
@kopf.on.startup()

src/tests/test_handlers.py

+1-138
Original file line numberDiff line numberDiff line change
@@ -118,144 +118,6 @@ def test_on_field_data_sync(self):
118118
{"key": "newvalue"},
119119
)
120120

121-
def test_on_field_data_ns_deleted(self):
122-
"""Don't fail the sync if one of the namespaces was deleted.
123-
"""
124-
125-
mock_v1 = Mock()
126-
127-
def read_namespaced_secret(name, namespace, **kwargs):
128-
if namespace == "myns2":
129-
# Old data in the namespaced secret of the myns namespace.
130-
return V1Secret(
131-
api_version='v1',
132-
data={"key": "oldvalue"},
133-
kind='Secret',
134-
metadata=create_secret_metadata(
135-
name="mysecret",
136-
namespace="myns2",
137-
),
138-
type="Opaque",
139-
)
140-
else:
141-
# Deleted namespace.
142-
raise ApiException(status=404, reason="Not Found")
143-
144-
mock_v1.read_namespaced_secret = read_namespaced_secret
145-
146-
create_namespaced_secret_called_count_for_ns2 = 0
147-
148-
def create_namespaced_secret(namespace, body, **kwargs):
149-
if namespace == "myns2":
150-
nonlocal create_namespaced_secret_called_count_for_ns2
151-
create_namespaced_secret_called_count_for_ns2 += 1
152-
else:
153-
# Deleted namespace.
154-
raise ApiException(status=404, reason="Not Found")
155-
156-
mock_v1.create_namespaced_secret = create_namespaced_secret
157-
158-
replace_namespaced_secret_called_count_for_ns2 = 0
159-
160-
def replace_namespaced_secret(name, namespace, body, **kwargs):
161-
if namespace == "myns2":
162-
nonlocal replace_namespaced_secret_called_count_for_ns2
163-
replace_namespaced_secret_called_count_for_ns2 += 1
164-
self.assertEqual(name, csec.name)
165-
166-
# Namespaced secret should be updated with the new data.
167-
self.assertEqual(
168-
body.data,
169-
{"key": "newvalue"},
170-
)
171-
172-
return V1Secret(
173-
api_version='v1',
174-
data=body.data,
175-
kind='Secret',
176-
metadata=create_secret_metadata(
177-
name="mysecret",
178-
namespace="myns2",
179-
),
180-
type="Opaque",
181-
)
182-
else:
183-
# Deleted namespace.
184-
raise ApiException(status=404, reason="Not Found")
185-
186-
mock_v1.replace_namespaced_secret = replace_namespaced_secret
187-
188-
def read_namespace(name, **kwargs):
189-
if name != "myns2":
190-
# Deleted namespace.
191-
raise ApiException(status=404, reason="Not Found")
192-
193-
mock_v1.read_namespace = read_namespace
194-
195-
patch_clustersecret_status = Mock()
196-
patch_clustersecret_status.return_value = {
197-
"metadata": {"name": "mysecret", "uid": "mysecretuid"},
198-
"data": {"key": "newvalue"},
199-
"status": {"create_fn": {"syncedns": ["myns2"]}},
200-
}
201-
202-
# Old data in the cache.
203-
csec = BaseClusterSecret(
204-
uid="mysecretuid",
205-
name="mysecret",
206-
namespace="",
207-
body={
208-
"metadata": {"name": "mysecret", "uid": "mysecretuid"},
209-
"data": {"key": "oldvalue"},
210-
"status": {"create_fn": {"syncedns": ["myns1", "myns2"]}},
211-
},
212-
synced_namespace=["myns1", "myns2"],
213-
)
214-
215-
csecs_cache.set_cluster_secret(csec)
216-
217-
# New data coming into the callback.
218-
new_body = {
219-
"metadata": {"name": "mysecret", "uid": "mysecretuid"},
220-
"data": {"key": "newvalue"},
221-
"status": {"create_fn": {"syncedns": ["myns1", "myns2"]}},
222-
}
223-
224-
with patch("handlers.v1", mock_v1), \
225-
patch("handlers.patch_clustersecret_status", patch_clustersecret_status):
226-
on_field_data(
227-
old={"key": "oldvalue"},
228-
new={"key": "newvalue"},
229-
body=new_body,
230-
meta=kopf.Meta({"metadata": {"name": "mysecret"}}),
231-
name="mysecret",
232-
namespace=None,
233-
uid="mysecretuid",
234-
logger=self.logger,
235-
)
236-
237-
# Namespaced secret should be updated with the new data.
238-
self.assertEqual(replace_namespaced_secret_called_count_for_ns2, 1)
239-
self.assertEqual(create_namespaced_secret_called_count_for_ns2, 0)
240-
241-
# The namespace should be deleted from the syncedns status of the clustersecret.
242-
patch_clustersecret_status.assert_called_once_with(
243-
logger=self.logger,
244-
name=csec.name,
245-
new_status={'create_fn': {'syncedns': ["myns2"]}},
246-
custom_objects_api=custom_objects_api,
247-
)
248-
249-
# Namespace should be deleted from the cache.
250-
self.assertEqual(
251-
csecs_cache.get_cluster_secret("mysecretuid").body.get("status"),
252-
{"create_fn": {"syncedns": ["myns2"]}},
253-
)
254-
self.assertEqual(
255-
csecs_cache.get_cluster_secret("mysecretuid").synced_namespace,
256-
["myns2"],
257-
)
258-
259121
def test_create_fn(self):
260122
"""Namespace name must be correct in the cache.
261123
"""
@@ -324,6 +186,7 @@ def test_ns_create(self):
324186
asyncio.run(
325187
namespace_watcher(
326188
logger=self.logger,
189+
reason=kopf.Reason("create"),
327190
meta=kopf.Meta({"metadata": {"name": "myns"}}),
328191
)
329192
)

0 commit comments

Comments
 (0)