From e4c0808d4e511a718ea7e01a6b5734e1652f0ed4 Mon Sep 17 00:00:00 2001 From: "Dane.Rushton" Date: Tue, 21 Oct 2025 10:52:04 +1300 Subject: [PATCH 1/2] Replicate behaviour of Finalize on Modify to also occur on Add events. This is because when the Operator starts, all objects are Adds, including those already in the Terminating state. --- .../Watcher/ResourceWatcher{TEntity}.cs | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/KubeOps.Operator/Watcher/ResourceWatcher{TEntity}.cs b/src/KubeOps.Operator/Watcher/ResourceWatcher{TEntity}.cs index 0ac7b364..70ab029d 100644 --- a/src/KubeOps.Operator/Watcher/ResourceWatcher{TEntity}.cs +++ b/src/KubeOps.Operator/Watcher/ResourceWatcher{TEntity}.cs @@ -143,20 +143,30 @@ protected virtual async Task OnEventAsync(WatchEventType type, TEntity entity, C switch (type) { case WatchEventType.Added: - cachedGeneration = await _entityCache.TryGetAsync(entity.Uid(), token: cancellationToken); - - if (!cachedGeneration.HasValue) - { - // Only perform reconciliation if the entity was not already in the cache. - await _entityCache.SetAsync(entity.Uid(), entity.Generation() ?? 0, token: cancellationToken); - await ReconcileModificationAsync(entity, cancellationToken); - } - else + switch (entity) { - logger.LogDebug( - """Received ADDED event for entity "{Kind}/{Name}" which was already in the cache. Skip event.""", - entity.Kind, - entity.Name()); + case { Metadata.DeletionTimestamp: null }: + cachedGeneration = await _entityCache.TryGetAsync(entity.Uid(), token: cancellationToken); + + if (!cachedGeneration.HasValue) + { + // Only perform reconciliation if the entity was not already in the cache. + await _entityCache.SetAsync(entity.Uid(), entity.Generation() ?? 0, token: cancellationToken); + await ReconcileModificationAsync(entity, cancellationToken); + } + else + { + logger.LogDebug( + """Received ADDED event for entity "{Kind}/{Name}" which was already in the cache. Skip event.""", + entity.Kind, + entity.Name()); + } + + break; + + case { Metadata: { DeletionTimestamp: not null, Finalizers.Count: > 0 } }: + await ReconcileFinalizersSequentialAsync(entity, cancellationToken); + break; } break; From f450cc7b2c5c9295927abcb6ae17a678f996b3b4 Mon Sep 17 00:00:00 2001 From: "Dane.Rushton" Date: Wed, 29 Oct 2025 09:25:42 +1300 Subject: [PATCH 2/2] Rework to reduce duplicated code. --- .../Watcher/ResourceWatcher{TEntity}.cs | 78 ++++++++----------- 1 file changed, 33 insertions(+), 45 deletions(-) diff --git a/src/KubeOps.Operator/Watcher/ResourceWatcher{TEntity}.cs b/src/KubeOps.Operator/Watcher/ResourceWatcher{TEntity}.cs index 70ab029d..d88f3b66 100644 --- a/src/KubeOps.Operator/Watcher/ResourceWatcher{TEntity}.cs +++ b/src/KubeOps.Operator/Watcher/ResourceWatcher{TEntity}.cs @@ -140,62 +140,50 @@ protected virtual async Task OnEventAsync(WatchEventType type, TEntity entity, C { MaybeValue cachedGeneration; + // Make sure Finalizers are running if Termination has began. + if (type != WatchEventType.Deleted && entity.Metadata.DeletionTimestamp is not null && entity.Metadata.Finalizers.Count > 0) + { + await ReconcileFinalizersSequentialAsync(entity, cancellationToken); + return; + } + switch (type) { case WatchEventType.Added: - switch (entity) - { - case { Metadata.DeletionTimestamp: null }: - cachedGeneration = await _entityCache.TryGetAsync(entity.Uid(), token: cancellationToken); - - if (!cachedGeneration.HasValue) - { - // Only perform reconciliation if the entity was not already in the cache. - await _entityCache.SetAsync(entity.Uid(), entity.Generation() ?? 0, token: cancellationToken); - await ReconcileModificationAsync(entity, cancellationToken); - } - else - { - logger.LogDebug( - """Received ADDED event for entity "{Kind}/{Name}" which was already in the cache. Skip event.""", - entity.Kind, - entity.Name()); - } + cachedGeneration = await _entityCache.TryGetAsync(entity.Uid(), token: cancellationToken); - break; - - case { Metadata: { DeletionTimestamp: not null, Finalizers.Count: > 0 } }: - await ReconcileFinalizersSequentialAsync(entity, cancellationToken); - break; + if (!cachedGeneration.HasValue) + { + // Only perform reconciliation if the entity was not already in the cache. + await _entityCache.SetAsync(entity.Uid(), entity.Generation() ?? 0, token: cancellationToken); + await ReconcileModificationAsync(entity, cancellationToken); + } + else + { + logger.LogDebug( + """Received ADDED event for entity "{Kind}/{Name}" which was already in the cache. Skip event.""", + entity.Kind, + entity.Name()); } break; case WatchEventType.Modified: - switch (entity) - { - case { Metadata.DeletionTimestamp: null }: - cachedGeneration = await _entityCache.TryGetAsync(entity.Uid(), token: cancellationToken); - - // Check if entity spec has changed through "Generation" value increment. Skip reconcile if not changed. - if (cachedGeneration.HasValue && cachedGeneration >= entity.Generation()) - { - logger.LogDebug( - """Entity "{Kind}/{Name}" modification did not modify generation. Skip event.""", - entity.Kind, - entity.Name()); - return; - } - - // update cached generation since generation now changed - await _entityCache.SetAsync(entity.Uid(), entity.Generation() ?? 1, token: cancellationToken); - await ReconcileModificationAsync(entity, cancellationToken); + cachedGeneration = await _entityCache.TryGetAsync(entity.Uid(), token: cancellationToken); - break; - case { Metadata: { DeletionTimestamp: not null, Finalizers.Count: > 0 } }: - await ReconcileFinalizersSequentialAsync(entity, cancellationToken); - break; + // Check if entity spec has changed through "Generation" value increment. Skip reconcile if not changed. + if (cachedGeneration.HasValue && cachedGeneration >= entity.Generation()) + { + logger.LogDebug( + """Entity "{Kind}/{Name}" modification did not modify generation. Skip event.""", + entity.Kind, + entity.Name()); + return; } + // update cached generation since generation now changed + await _entityCache.SetAsync(entity.Uid(), entity.Generation() ?? 1, token: cancellationToken); + await ReconcileModificationAsync(entity, cancellationToken); + break; case WatchEventType.Deleted: await ReconcileDeletionAsync(entity, cancellationToken);