multi-tenant: EnsureNamespaceSA + ns_watcher SA provisioning (v8)

This commit is contained in:
Naeel
2026-04-26 07:41:46 +03:00
parent 82e1ff76a5
commit 161de70576
24 changed files with 970 additions and 1 deletions
+132
View File
@@ -0,0 +1,132 @@
import re
# ── envwatcher.go ─────────────────────────────────────────────────────────────
with open("/home/naeel/terra/fission-src/pkg/buildermgr/envwatcher.go") as f:
src = f.read()
# добавляем genInformer import если нет
if "genInformer" not in src:
src = src.replace(
'"github.com/fission/fission/pkg/generated/clientset/versioned"',
'"github.com/fission/fission/pkg/generated/clientset/versioned"\n\t'
'genInformer "github.com/fission/fission/pkg/generated/informers/externalversions"',
1
)
# добавляем fmt если нет
if '"fmt"' not in src:
src = src.replace('"context"', '"context"\n\t"fmt"', 1)
addon = r'''
// AddNamespace dynamically registers a new namespace in environmentWatcher.
// Creates a per-NS Environment informer. Safe to call repeatedly — deduplicates via nsResolver.
func (envw *environmentWatcher) AddNamespace(ctx context.Context, ns string, mgr manager.Interface) {
if !envw.nsResolver.AddNamespace(ns) {
return // already registered
}
envw.logger.Info("buildermgr.envWatcher.AddNamespace: setting up informer", zap.String("namespace", ns))
factory := genInformer.NewFilteredSharedInformerFactory(envw.fissionClient, 30*time.Minute, ns, nil)
envInf := factory.Core().V1().Environments().Informer()
_, err := envInf.AddEventHandler(k8sCache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
envObj := obj.(*fv1.Environment)
envw.AddUpdateBuilder(ctx, envObj)
},
UpdateFunc: func(oldObj interface{}, newObj interface{}) {
oldEnvObj := oldObj.(*fv1.Environment)
newEnvObj := newObj.(*fv1.Environment)
if oldEnvObj.ResourceVersion == newEnvObj.ResourceVersion {
return
}
envw.AddUpdateBuilder(ctx, newEnvObj)
},
DeleteFunc: func(obj interface{}) {
envObj, ok := obj.(*fv1.Environment)
if !ok {
return
}
envw.deleteBuilder(ctx, envObj)
},
})
if err != nil {
envw.logger.Error("buildermgr.envWatcher.AddNamespace: add handler failed",
zap.String("namespace", ns), zap.Error(fmt.Errorf("%w", err)))
return
}
envw.envWatchInformer[ns] = envInf
mgr.AddInformers(ctx, map[string]k8sCache.SharedIndexInformer{ns: envInf})
envw.logger.Info("buildermgr.envWatcher.AddNamespace: done", zap.String("namespace", ns))
}
'''
with open("/home/naeel/terra/fission-src/pkg/buildermgr/envwatcher.go", "w") as f:
f.write(src + addon)
print("envwatcher.go: done")
# ── pkgwatcher.go ─────────────────────────────────────────────────────────────
with open("/home/naeel/terra/fission-src/pkg/buildermgr/pkgwatcher.go") as f:
src = f.read()
# добавляем genInformer import если нет
if "genInformer" not in src:
src = src.replace(
'"github.com/fission/fission/pkg/generated/clientset/versioned"',
'"github.com/fission/fission/pkg/generated/clientset/versioned"\n\t'
'genInformer "github.com/fission/fission/pkg/generated/informers/externalversions"',
1
)
# добавляем fmt если нет
if '"fmt"' not in src:
src = src.replace('"context"', '"context"\n\t"fmt"', 1)
# Добавляем k8sInformers если нет
if "k8sInformers" not in src:
src = src.replace(
'"k8s.io/client-go/kubernetes"',
'"k8s.io/client-go/kubernetes"\n\tk8sInformers "k8s.io/client-go/informers"',
1
)
addon2 = r'''
// AddNamespace dynamically registers a new namespace in packageWatcher.
// Creates per-NS Package and Pod informers. Safe to call repeatedly.
func (pkgw *packageWatcher) AddNamespace(ctx context.Context, ns string, mgr manager.Interface) {
if !pkgw.nsResolver.AddNamespace(ns) {
return // already registered
}
pkgw.logger.Info("buildermgr.pkgWatcher.AddNamespace: setting up informers", zap.String("namespace", ns))
// Package informer
fissionFactory := genInformer.NewFilteredSharedInformerFactory(pkgw.fissionClient, 30*time.Minute, ns, nil)
pkgInf := fissionFactory.Core().V1().Packages().Informer()
_, err := pkgInf.AddEventHandler(pkgw.packageInformerHandler(ctx))
if err != nil {
pkgw.logger.Error("buildermgr.pkgWatcher.AddNamespace: pkg handler failed",
zap.String("namespace", ns), zap.Error(fmt.Errorf("%w", err)))
return
}
// Pod informer for build logs
podFactory := k8sInformers.NewSharedInformerFactoryWithOptions(pkgw.k8sClient, 30*time.Minute,
k8sInformers.WithNamespace(ns))
podInf := podFactory.Core().V1().Pods().Informer()
pkgw.pkgInformer[ns] = pkgInf
pkgw.podInformer[ns] = podInf
mgr.AddInformers(ctx, map[string]k8sCache.SharedIndexInformer{
ns + "/pkg": pkgInf,
ns + "/pod": podInf,
})
pkgw.logger.Info("buildermgr.pkgWatcher.AddNamespace: done", zap.String("namespace", ns))
}
'''
with open("/home/naeel/terra/fission-src/pkg/buildermgr/pkgwatcher.go", "w") as f:
f.write(src + addon2)
print("pkgwatcher.go: done")
+4
View File
@@ -74,5 +74,9 @@ func Start(ctx context.Context, clientGen crd.ClientGeneratorInterface, logger *
if err != nil {
return err
}
// Multi-tenant: watch namespaces labeled fission.io/managed=true
StartNSWatcher(ctx, logger, kubernetesClient, envWatcher, pkgWatcher, mgr)
return nil
}
+45
View File
@@ -39,6 +39,7 @@ import (
"github.com/fission/fission/pkg/executor/util"
fetcherConfig "github.com/fission/fission/pkg/fetcher/config"
"github.com/fission/fission/pkg/generated/clientset/versioned"
genInformer "github.com/fission/fission/pkg/generated/informers/externalversions"
"github.com/fission/fission/pkg/utils"
"github.com/fission/fission/pkg/utils/manager"
)
@@ -498,3 +499,47 @@ func (envw *environmentWatcher) createBuilderDeployment(ctx context.Context, env
return deployment, nil
}
// AddNamespace dynamically registers a new namespace in environmentWatcher.
// Creates a per-NS Environment informer. Safe to call repeatedly — deduplicates via nsResolver.
func (envw *environmentWatcher) AddNamespace(ctx context.Context, ns string, mgr manager.Interface) {
if !envw.nsResolver.AddNamespace(ns) {
return // already registered
}
envw.logger.Info("buildermgr.envWatcher.AddNamespace: setting up informer", zap.String("namespace", ns))
factory := genInformer.NewFilteredSharedInformerFactory(envw.fissionClient, 30*time.Minute, ns, nil)
envInf := factory.Core().V1().Environments().Informer()
_, err := envInf.AddEventHandler(k8sCache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
envObj := obj.(*fv1.Environment)
envw.AddUpdateBuilder(ctx, envObj)
},
UpdateFunc: func(oldObj interface{}, newObj interface{}) {
oldEnvObj := oldObj.(*fv1.Environment)
newEnvObj := newObj.(*fv1.Environment)
if oldEnvObj.ResourceVersion == newEnvObj.ResourceVersion {
return
}
envw.AddUpdateBuilder(ctx, newEnvObj)
},
DeleteFunc: func(obj interface{}) {
envObj, ok := obj.(*fv1.Environment)
if !ok {
return
}
envw.DeleteBuilder(ctx, envObj)
},
})
if err != nil {
envw.logger.Error("buildermgr.envWatcher.AddNamespace: add handler failed",
zap.String("namespace", ns), zap.Error(fmt.Errorf("%w", err)))
return
}
envw.envWatchInformer[ns] = envInf
mgr.AddInformers(ctx, map[string]k8sCache.SharedIndexInformer{ns: envInf})
factory.Start(ctx.Done())
envw.logger.Info("buildermgr.envWatcher.AddNamespace: done", zap.String("namespace", ns))
}
+80
View File
@@ -0,0 +1,80 @@
// Package buildermgr — NSWatcher for multi-tenant mode.
//
// Listens for Namespaces labeled fission.io/managed=true and calls
// AddNamespace on envWatcher and packageWatcher so they pick up
// Environments and Packages in new tenant namespaces without a restart.
package buildermgr
import (
"context"
"time"
"go.uber.org/zap"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8sInformers "k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
k8sCache "k8s.io/client-go/tools/cache"
"github.com/fission/fission/pkg/utils/manager"
)
const builderManagedNSLabel = "fission.io/managed"
// StartNSWatcher watches for Namespaces labeled fission.io/managed=true
// and immediately registers per-NS informers in envWatcher and pkgWatcher.
func StartNSWatcher(
ctx context.Context,
logger *zap.Logger,
kubeClient kubernetes.Interface,
envw *environmentWatcher,
pkgw *packageWatcher,
mgr manager.Interface,
) {
factory := k8sInformers.NewSharedInformerFactoryWithOptions(
kubeClient,
30*time.Minute,
k8sInformers.WithTweakListOptions(func(opts *metav1.ListOptions) {
opts.LabelSelector = builderManagedNSLabel + "=true"
}),
)
nsInformer := factory.Core().V1().Namespaces().Informer()
_, _ = nsInformer.AddEventHandler(k8sCache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
ns := builderNSName(obj)
if ns == "" {
return
}
envw.AddNamespace(ctx, ns, mgr)
pkgw.AddNamespace(ctx, ns, mgr)
},
UpdateFunc: func(_, newObj interface{}) {
nsObj, ok := newObj.(*corev1.Namespace)
if !ok || nsObj.Labels[builderManagedNSLabel] != "true" {
return
}
envw.AddNamespace(ctx, nsObj.Name, mgr)
pkgw.AddNamespace(ctx, nsObj.Name, mgr)
},
})
mgr.Add(ctx, func(ctx context.Context) {
logger.Info("buildermgr.NSWatcher: started",
zap.String("label", builderManagedNSLabel+"=true"))
factory.Start(ctx.Done())
factory.WaitForCacheSync(ctx.Done())
logger.Info("buildermgr.NSWatcher: cache synced — watching for new namespaces")
<-ctx.Done()
logger.Info("buildermgr.NSWatcher: stopped")
})
}
func builderNSName(obj interface{}) string {
nsObj, ok := obj.(*corev1.Namespace)
if !ok {
return ""
}
return nsObj.Name
}
+38
View File
@@ -25,6 +25,7 @@ import (
apiv1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8sInformers "k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
k8sCache "k8s.io/client-go/tools/cache"
@@ -32,6 +33,7 @@ import (
"github.com/fission/fission/pkg/cache"
"github.com/fission/fission/pkg/crd"
"github.com/fission/fission/pkg/generated/clientset/versioned"
genInformer "github.com/fission/fission/pkg/generated/informers/externalversions"
"github.com/fission/fission/pkg/utils"
"github.com/fission/fission/pkg/utils/manager"
"github.com/fission/fission/pkg/utils/metrics"
@@ -329,3 +331,39 @@ func setInitialBuildStatus(ctx context.Context, fissionClient versioned.Interfac
// TODO: use UpdateStatus to update status
return fissionClient.CoreV1().Packages(pkg.Namespace).Update(ctx, pkg, metav1.UpdateOptions{})
}
// AddNamespace dynamically registers a new namespace in packageWatcher.
// Creates per-NS Package and Pod informers. Safe to call repeatedly.
func (pkgw *packageWatcher) AddNamespace(ctx context.Context, ns string, mgr manager.Interface) {
if !pkgw.nsResolver.AddNamespace(ns) {
return // already registered
}
pkgw.logger.Info("buildermgr.pkgWatcher.AddNamespace: setting up informers", zap.String("namespace", ns))
// Package informer
fissionFactory := genInformer.NewFilteredSharedInformerFactory(pkgw.fissionClient, 30*time.Minute, ns, nil)
pkgInf := fissionFactory.Core().V1().Packages().Informer()
_, err := pkgInf.AddEventHandler(pkgw.packageInformerHandler(ctx))
if err != nil {
pkgw.logger.Error("buildermgr.pkgWatcher.AddNamespace: pkg handler failed",
zap.String("namespace", ns), zap.Error(fmt.Errorf("%w", err)))
return
}
// Pod informer for build logs
podFactory := k8sInformers.NewSharedInformerFactoryWithOptions(pkgw.k8sClient, 30*time.Minute,
k8sInformers.WithNamespace(ns))
podInf := podFactory.Core().V1().Pods().Informer()
pkgw.pkgInformer[ns] = pkgInf
pkgw.podInformer[ns] = podInf
mgr.AddInformers(ctx, map[string]k8sCache.SharedIndexInformer{
ns + "/pkg": pkgInf,
ns + "/pod": podInf,
})
fissionFactory.Start(ctx.Done())
podFactory.Start(ctx.Done())
pkgw.logger.Info("buildermgr.pkgWatcher.AddNamespace: done", zap.String("namespace", ns))
}