diff --git a/doc/thinking/2026-04-26-namespace-manager-step24.md b/doc/thinking/2026-04-26-namespace-manager-step24.md new file mode 100644 index 00000000..7e584523 --- /dev/null +++ b/doc/thinking/2026-04-26-namespace-manager-step24.md @@ -0,0 +1,16 @@ +# 2026-04-26 — NamespaceManager rewrite, step 24 + +## Цель шага + +Подготовить `executor/multitenant` к subscriber adapter без смены текущего watcher behavior. + +## Что меняем + +1. Выделяем отдельный helper для прогона `AddNamespace()` по executor type-ам. +2. Оставляем `EnsureNamespaceSA()` в текущем `registerNamespace()`. +3. Добавляем unit test на успешный прогон и propagation ошибок. + +## Что НЕ меняем + +- не подключаем `NamespaceManager`; +- не меняем внешний API watcher-а. \ No newline at end of file diff --git a/pkg/executor/multitenant/ns_watcher.go b/pkg/executor/multitenant/ns_watcher.go index 0cbbf303..86fce199 100644 --- a/pkg/executor/multitenant/ns_watcher.go +++ b/pkg/executor/multitenant/ns_watcher.go @@ -61,6 +61,7 @@ package multitenant import ( "context" + "errors" "time" "go.uber.org/zap" @@ -156,6 +157,18 @@ func registerNamespace( utils.DefaultNSResolver().AddNamespace(ns) // Ensure fission-fetcher SA exists in the new namespace so pool pods can start. utils.EnsureNamespaceSA(ctx, kubernetesClient, logger, ns) + registerExecutorTypes(ctx, logger, ns, executorTypes, mgr) + logger.Info("multitenant.NSWatcher: registered namespace", zap.String("namespace", ns)) +} + +func registerExecutorTypes( + ctx context.Context, + logger *zap.Logger, + ns string, + executorTypes map[fv1.ExecutorType]executortype.ExecutorType, + mgr manager.Interface, +) error { + var joinErr error for _, et := range executorTypes { if err := et.AddNamespace(ctx, ns, mgr); err != nil { @@ -163,9 +176,10 @@ func registerNamespace( zap.String("namespace", ns), zap.Error(err), ) + joinErr = errors.Join(joinErr, err) } } - logger.Info("multitenant.NSWatcher: registered namespace", zap.String("namespace", ns)) + return joinErr } // namespaceName extracts the namespace name from an informer event object. diff --git a/pkg/executor/multitenant/ns_watcher_test.go b/pkg/executor/multitenant/ns_watcher_test.go new file mode 100644 index 00000000..d332eefc --- /dev/null +++ b/pkg/executor/multitenant/ns_watcher_test.go @@ -0,0 +1,74 @@ +package multitenant + +import ( + "context" + "errors" + "testing" + + "go.uber.org/zap" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + fv1 "github.com/fission/fission/pkg/apis/core/v1" + "github.com/fission/fission/pkg/executor/executortype" + "github.com/fission/fission/pkg/executor/fscache" + "github.com/fission/fission/pkg/utils/manager" +) + +type fakeExecutorType struct { + addCalls int + lastNamespace string + addErr error +} + +func (f *fakeExecutorType) Run(context.Context, manager.Interface) {} +func (f *fakeExecutorType) GetTypeName(context.Context) fv1.ExecutorType { return fv1.ExecutorTypePoolmgr } +func (f *fakeExecutorType) GetFuncSvc(context.Context, *fv1.Function) (*fscache.FuncSvc, error) { + return nil, nil +} +func (f *fakeExecutorType) GetFuncSvcFromCache(context.Context, *fv1.Function) (*fscache.FuncSvc, error) { + return nil, nil +} +func (f *fakeExecutorType) DumpDebugInfo(context.Context) error { return nil } +func (f *fakeExecutorType) DeleteFuncSvcFromCache(context.Context, *fscache.FuncSvc) {} +func (f *fakeExecutorType) TapService(context.Context, string) error { return nil } +func (f *fakeExecutorType) UnTapService(context.Context, *metav1.ObjectMeta, string) {} +func (f *fakeExecutorType) MarkSpecializationFailure(context.Context, *metav1.ObjectMeta) {} +func (f *fakeExecutorType) IsValid(context.Context, *fscache.FuncSvc) bool { return true } +func (f *fakeExecutorType) RefreshFuncPods(context.Context, *zap.Logger, fv1.Function) error { return nil } +func (f *fakeExecutorType) AdoptExistingResources(context.Context) {} +func (f *fakeExecutorType) CleanupOldExecutorObjects(context.Context) {} +func (f *fakeExecutorType) AddNamespace(ctx context.Context, ns string, mgr manager.Interface) error { + f.addCalls++ + f.lastNamespace = ns + return f.addErr +} + +var _ executortype.ExecutorType = (*fakeExecutorType)(nil) + +func TestRegisterExecutorTypes(t *testing.T) { + poolmgr := &fakeExecutorType{} + container := &fakeExecutorType{} + err := registerExecutorTypes(context.Background(), zap.NewNop(), "tenant-executor-a", map[fv1.ExecutorType]executortype.ExecutorType{ + fv1.ExecutorTypePoolmgr: poolmgr, + fv1.ExecutorTypeContainer: container, + }, nil) + if err != nil { + t.Fatalf("expected registration success: %v", err) + } + if poolmgr.addCalls != 1 || container.addCalls != 1 { + t.Fatalf("expected all executor types to receive AddNamespace") + } +} + +func TestRegisterExecutorTypesAggregatesErrors(t *testing.T) { + err := registerExecutorTypes(context.Background(), zap.NewNop(), "tenant-executor-a", map[fv1.ExecutorType]executortype.ExecutorType{ + fv1.ExecutorTypePoolmgr: &fakeExecutorType{addErr: errors.New("poolmgr failed")}, + fv1.ExecutorTypeContainer: &fakeExecutorType{}, + }, nil) + if err == nil { + t.Fatalf("expected aggregated error") + } + if err.Error() != "poolmgr failed" { + t.Fatalf("unexpected error: %v", err) + } +} \ No newline at end of file