From 7b6ff841882b5c39a8e9a4c5d1d29113aef09ee0 Mon Sep 17 00:00:00 2001 From: Naeel Date: Sun, 26 Apr 2026 16:27:20 +0300 Subject: [PATCH] layer1: harden namespace watcher logging path --- .../2026-04-26-namespace-manager-step52.md | 17 ++++++++ pkg/utils/namespace_manager.go | 14 +++++++ pkg/utils/namespace_manager_test.go | 41 +++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 doc/thinking/2026-04-26-namespace-manager-step52.md diff --git a/doc/thinking/2026-04-26-namespace-manager-step52.md b/doc/thinking/2026-04-26-namespace-manager-step52.md new file mode 100644 index 00000000..1ce47091 --- /dev/null +++ b/doc/thinking/2026-04-26-namespace-manager-step52.md @@ -0,0 +1,17 @@ +# 2026-04-26 — NamespaceManager rewrite, step 52 + +## Цель шага + +Убрать хрупкость общего watcher path, где `nil` logger мог привести к panic на error/info ветках. + +## Что изменено + +1. Введена централизованная нормализация logger-а к `zap.NewNop()`. +2. Hardening применён к prepare/run/start и watcher event handlers. +3. Добавлены regression tests на nil-logger path. + +## Почему это важно + +- это уже runtime hardening, а не декоративный cleanup; +- общий helper layer стал безопаснее для повторного использования; +- поведение watcher-ов не меняется, меняется только устойчивость logging path. \ No newline at end of file diff --git a/pkg/utils/namespace_manager.go b/pkg/utils/namespace_manager.go index a538dd66..4a7fbd59 100644 --- a/pkg/utils/namespace_manager.go +++ b/pkg/utils/namespace_manager.go @@ -119,7 +119,15 @@ func NewDefaultManagedNamespaceWatcherConfig(component string, subscriber Namesp } } +func namespaceManagerLogger(logger *zap.Logger) *zap.Logger { + if logger != nil { + return logger + } + return zap.NewNop() +} + func PrepareManagedNamespaceWatcher(ctx context.Context, logger *zap.Logger, config ManagedNamespaceWatcherConfig) (NamespaceManager, k8sCache.ResourceEventHandlerFuncs, error) { + logger = namespaceManagerLogger(logger) strategy := config.RemovalStrategy if strategy == "" { strategy = NamespaceRemovalStrategyTrackOnly @@ -131,6 +139,7 @@ func PrepareManagedNamespaceWatcher(ctx context.Context, logger *zap.Logger, con } func RunManagedNamespaceWatcher(ctx context.Context, logger *zap.Logger, kubeClient kubernetes.Interface, mgr managerPkg.Interface, config ManagedNamespaceWatcherConfig) (NamespaceManager, error) { + logger = namespaceManagerLogger(logger) manager, handlers, err := PrepareManagedNamespaceWatcher(ctx, logger, config) StartManagedNamespaceWatcher(ctx, logger, config.Component, kubeClient, mgr, handlers) LogNamespaceManagerSummary(logger, config.Component+": started namespace watcher", manager.Summary()) @@ -187,6 +196,7 @@ func RecordNamespaceRemoval(manager NamespaceManager, obj interface{}, source Na } func HandleWatcherNamespaceAdd(ctx context.Context, logger *zap.Logger, component string, manager NamespaceManager, namespace *corev1.Namespace) { + logger = namespaceManagerLogger(logger) if namespace == nil || namespace.Name == "" { return } @@ -198,6 +208,7 @@ func HandleWatcherNamespaceAdd(ctx context.Context, logger *zap.Logger, componen } func HandleWatcherNamespaceUpdate(ctx context.Context, logger *zap.Logger, component string, manager NamespaceManager, oldNamespace *corev1.Namespace, newNamespace *corev1.Namespace, strategy NamespaceRemovalStrategy) { + logger = namespaceManagerLogger(logger) if newNamespace == nil { return } @@ -220,6 +231,7 @@ func HandleWatcherNamespaceDelete(ctx context.Context, logger *zap.Logger, compo } func HandleWatcherNamespaceRemoval(ctx context.Context, logger *zap.Logger, component string, manager NamespaceManager, obj interface{}, strategy NamespaceRemovalStrategy) { + logger = namespaceManagerLogger(logger) record, ok := RecordNamespaceRemoval(manager, obj, NamespaceSourceWatcher, time.Now().UTC()) if !ok { return @@ -240,6 +252,7 @@ func HandleWatcherNamespaceRemoval(ctx context.Context, logger *zap.Logger, comp } func NewNamespaceWatcherEventHandlers(ctx context.Context, logger *zap.Logger, component string, manager NamespaceManager, strategy NamespaceRemovalStrategy) k8sCache.ResourceEventHandlerFuncs { + logger = namespaceManagerLogger(logger) return k8sCache.ResourceEventHandlerFuncs{ AddFunc: func(obj interface{}) { namespace, ok := obj.(*corev1.Namespace) @@ -261,6 +274,7 @@ func NewNamespaceWatcherEventHandlers(ctx context.Context, logger *zap.Logger, c } func StartManagedNamespaceWatcher(ctx context.Context, logger *zap.Logger, component string, kubeClient kubernetes.Interface, mgr managerPkg.Interface, handlers k8sCache.ResourceEventHandlerFuncs) { + logger = namespaceManagerLogger(logger) factory := k8sInformers.NewSharedInformerFactoryWithOptions( kubeClient, 30*time.Minute, diff --git a/pkg/utils/namespace_manager_test.go b/pkg/utils/namespace_manager_test.go index 5a2835bc..0d8e1275 100644 --- a/pkg/utils/namespace_manager_test.go +++ b/pkg/utils/namespace_manager_test.go @@ -407,6 +407,28 @@ func TestHandleWatcherNamespaceAddLogsSummary(t *testing.T) { } } +func TestHandleWatcherNamespaceAddWithNilLogger(t *testing.T) { + manager := NewNamespaceManager() + router := &testNamespaceSubscriber{name: "router", addErr: errors.New("add failed")} + manager.Subscribe(router) + namespace := &corev1.Namespace{} + namespace.Name = "tenant-a" + namespace.Labels = map[string]string{ManagedNamespaceLabelKey: ManagedNamespaceLabelValue} + + HandleWatcherNamespaceAdd(context.Background(), nil, "router.NSWatcher", manager, namespace) + + record, ok := manager.Get("tenant-a") + if !ok { + t.Fatalf("expected tenant-a to exist after add handling") + } + if record.Phase != NamespacePhaseFailed { + t.Fatalf("expected failed phase after subscriber error, got %s", record.Phase) + } + if router.addCalls != 1 { + t.Fatalf("expected failing subscriber to be called once") + } +} + func TestHandleWatcherNamespaceRemovalDispatch(t *testing.T) { manager := NewNamespaceManager() router := &testNamespaceSubscriber{name: "router"} @@ -554,6 +576,25 @@ func TestRunManagedNamespaceWatcherLogsStartedSummary(t *testing.T) { } } +func TestRunManagedNamespaceWatcherWithNilLogger(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + router := &testNamespaceSubscriber{name: "router"} + + manager, err := RunManagedNamespaceWatcher(ctx, nil, k8sfake.NewSimpleClientset(), managerPkg.New(), ManagedNamespaceWatcherConfig{ + Component: "router.NSWatcher", + Namespaces: []string{"tenant-a"}, + RemovalStrategy: NamespaceRemovalStrategyTrackOnly, + Subscriber: router, + }) + if err != nil { + t.Fatalf("expected managed namespace watcher run success: %v", err) + } + if !reflect.DeepEqual([]string{"tenant-a"}, manager.Snapshot()) { + t.Fatalf("expected watcher manager snapshot to contain bootstrapped namespace") + } +} + func TestNewDefaultManagedNamespaceWatcherConfig(t *testing.T) { resolver := DefaultNSResolver() original := resolver.FissionResourceNS