layer1: formalize namespace removal strategy
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
# 2026-04-26 — NamespaceManager rewrite, step 40
|
||||
|
||||
## Цель шага
|
||||
|
||||
Зафиксировать lifecycle policy для namespace removal в коде явно, а не только комментариями и log-сообщениями.
|
||||
|
||||
## Что меняем
|
||||
|
||||
1. Добавляем `NamespaceRemovalStrategy`.
|
||||
2. Поддерживаем два режима:
|
||||
- `track-only`
|
||||
- `dispatch-remove`
|
||||
3. Общие watcher handlers принимают strategy.
|
||||
4. Текущий production flow использует `track-only`.
|
||||
5. Добавляем unit tests на оба режима.
|
||||
|
||||
## Что НЕ меняем
|
||||
|
||||
- не включаем реальный remove dispatch в watcher-ах;
|
||||
- не меняем runtime cleanup policy по умолчанию.
|
||||
@@ -60,7 +60,7 @@ func StartNSWatcher(
|
||||
}
|
||||
},
|
||||
DeleteFunc: func(obj interface{}) {
|
||||
utils.HandleWatcherNamespaceDelete(logger, "buildermgr.NSWatcher", nsManager, obj)
|
||||
utils.HandleWatcherNamespaceDelete(ctx, logger, "buildermgr.NSWatcher", nsManager, obj, utils.NamespaceRemovalStrategyTrackOnly)
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ func StartNSWatcher(
|
||||
}
|
||||
},
|
||||
DeleteFunc: func(obj interface{}) {
|
||||
utils.HandleWatcherNamespaceDelete(logger, "multitenant.NSWatcher", nsManager, obj)
|
||||
utils.HandleWatcherNamespaceDelete(ctx, logger, "multitenant.NSWatcher", nsManager, obj, utils.NamespaceRemovalStrategyTrackOnly)
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ func StartNSWatcher(
|
||||
}
|
||||
},
|
||||
DeleteFunc: func(obj interface{}) {
|
||||
utils.HandleWatcherNamespaceDelete(logger, "router.NSWatcher", nsManager, obj)
|
||||
utils.HandleWatcherNamespaceDelete(ctx, logger, "router.NSWatcher", nsManager, obj, utils.NamespaceRemovalStrategyTrackOnly)
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -152,9 +152,7 @@ func HandleWatcherNamespaceUpdate(ctx context.Context, logger *zap.Logger, compo
|
||||
return
|
||||
}
|
||||
if NamespaceBecameUnmanaged(oldNamespace, newNamespace) {
|
||||
_, _ = RecordNamespaceRemoval(manager, newNamespace, NamespaceSourceWatcher, time.Now().UTC())
|
||||
logger.Info(component+": namespace removed from manager state; runtime registrations kept",
|
||||
zap.String("namespace", newNamespace.Name))
|
||||
HandleWatcherNamespaceRemoval(ctx, logger, component, manager, newNamespace, NamespaceRemovalStrategyTrackOnly)
|
||||
return
|
||||
}
|
||||
if !IsManagedNamespace(newNamespace.Labels) {
|
||||
@@ -165,12 +163,25 @@ func HandleWatcherNamespaceUpdate(ctx context.Context, logger *zap.Logger, compo
|
||||
}
|
||||
}
|
||||
|
||||
func HandleWatcherNamespaceDelete(logger *zap.Logger, component string, manager NamespaceManager, obj interface{}) {
|
||||
func HandleWatcherNamespaceDelete(ctx context.Context, logger *zap.Logger, component string, manager NamespaceManager, obj interface{}, strategy NamespaceRemovalStrategy) {
|
||||
HandleWatcherNamespaceRemoval(ctx, logger, component, manager, obj, strategy)
|
||||
}
|
||||
|
||||
func HandleWatcherNamespaceRemoval(ctx context.Context, logger *zap.Logger, component string, manager NamespaceManager, obj interface{}, strategy NamespaceRemovalStrategy) {
|
||||
record, ok := RecordNamespaceRemoval(manager, obj, NamespaceSourceWatcher, time.Now().UTC())
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
logger.Info(component+": namespace deleted from manager state; runtime registrations kept",
|
||||
if strategy == NamespaceRemovalStrategyDispatchRemove {
|
||||
if _, _, err := manager.DispatchRemove(ctx, record.Name); err != nil {
|
||||
logger.Error(component+": DispatchRemove failed", zap.String("namespace", record.Name), zap.Error(err))
|
||||
return
|
||||
}
|
||||
logger.Info(component+": namespace removal dispatched through subscribers",
|
||||
zap.String("namespace", record.Name))
|
||||
return
|
||||
}
|
||||
logger.Info(component+": namespace removed from manager state; runtime registrations kept",
|
||||
zap.String("namespace", record.Name))
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,13 @@ const (
|
||||
NamespaceEventResync NamespaceEventType = "resync"
|
||||
)
|
||||
|
||||
type NamespaceRemovalStrategy string
|
||||
|
||||
const (
|
||||
NamespaceRemovalStrategyTrackOnly NamespaceRemovalStrategy = "track-only"
|
||||
NamespaceRemovalStrategyDispatchRemove NamespaceRemovalStrategy = "dispatch-remove"
|
||||
)
|
||||
|
||||
type NamespacePartState struct {
|
||||
State string
|
||||
LastError string
|
||||
|
||||
@@ -145,3 +145,12 @@ func TestNamespaceEventFromObject(t *testing.T) {
|
||||
t.Fatalf("expected empty name for unsupported object")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNamespaceRemovalStrategyValues(t *testing.T) {
|
||||
if NamespaceRemovalStrategyTrackOnly != "track-only" {
|
||||
t.Fatalf("unexpected track-only strategy value")
|
||||
}
|
||||
if NamespaceRemovalStrategyDispatchRemove != "dispatch-remove" {
|
||||
t.Fatalf("unexpected dispatch-remove strategy value")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,13 +357,32 @@ func TestHandleWatcherNamespaceAddUpdateDelete(t *testing.T) {
|
||||
t.Fatalf("expected update handler to dispatch resync")
|
||||
}
|
||||
|
||||
HandleWatcherNamespaceDelete(logger, "router.NSWatcher", manager, k8sCache.DeletedFinalStateUnknown{Obj: namespace})
|
||||
HandleWatcherNamespaceDelete(context.Background(), logger, "router.NSWatcher", manager, k8sCache.DeletedFinalStateUnknown{Obj: namespace}, NamespaceRemovalStrategyTrackOnly)
|
||||
record, ok := manager.Get("tenant-a")
|
||||
if !ok || record.Phase != NamespacePhaseRemoved {
|
||||
t.Fatalf("expected delete handler to mark namespace removed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleWatcherNamespaceRemovalDispatch(t *testing.T) {
|
||||
manager := NewNamespaceManager()
|
||||
router := &testNamespaceSubscriber{name: "router"}
|
||||
manager.Subscribe(router)
|
||||
logger := zap.NewNop()
|
||||
namespace := &corev1.Namespace{}
|
||||
namespace.Name = "tenant-a"
|
||||
manager.Upsert(NamespaceEvent{Type: NamespaceEventAdd, Name: "tenant-a", Source: NamespaceSourceWatcher})
|
||||
|
||||
HandleWatcherNamespaceRemoval(context.Background(), logger, "router.NSWatcher", manager, namespace, NamespaceRemovalStrategyDispatchRemove)
|
||||
record, ok := manager.Get("tenant-a")
|
||||
if !ok || record.Phase != NamespacePhaseRemoved {
|
||||
t.Fatalf("expected dispatch removal to keep removed record")
|
||||
}
|
||||
if router.removeCalls != 1 {
|
||||
t.Fatalf("expected remove subscriber to be called once")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNamespaceManagerDispatchAdd(t *testing.T) {
|
||||
manager := NewNamespaceManager()
|
||||
manager.Upsert(NamespaceEvent{Type: NamespaceEventAdd, Name: "tenant-a", Source: NamespaceSourceWatcher})
|
||||
|
||||
Reference in New Issue
Block a user