package nodeagent import ( "context" "strings" "time" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/predicate" "maps" "edge-infra.dev/pkg/k8s/runtime/conditions" "edge-infra.dev/pkg/k8s/runtime/controller/metrics" "edge-infra.dev/pkg/k8s/runtime/controller/reconcile" "edge-infra.dev/pkg/k8s/runtime/patch" "edge-infra.dev/pkg/sds/ien" v1ien "edge-infra.dev/pkg/sds/ien/k8s/apis/v1" "edge-infra.dev/pkg/sds/ien/k8s/controllers/nodeagent/config" "edge-infra.dev/pkg/sds/ien/k8s/controllers/nodeagent/internal" ) // Generic v1 controller (secrets, configmaps) type GenericController struct { Controller client.Object Plugins map[string]Plugin } func (c *GenericController) Reconcile(ctx context.Context, req ctrl.Request) (res ctrl.Result, recErr error) { var ( result reconcile.Result = reconcile.ResultEmpty requeue bool reconcileDuration internal.ReconcileTimeSet reconcileStartAt = time.Now() ) if err := c.Client.Get(ctx, req.NamespacedName, c.Object); err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) } ienode, err := c.Config.GetHostIENode(ctx) if err != nil { return ctrl.Result{RequeueAfter: c.RequeueTime}, err } c.IENode = ienode metrics, err := c.initializeControllerMetrics(ctx) if err != nil { return ctrl.Result{RequeueAfter: c.RequeueTime}, err } patcher := patch.NewSerialPatcher(c.IENode, c.Client) defer func() { res, recErr = c.Summarizer(ctx, patcher, result) metrics.RecordMetrics(reconcileDuration, time.Since(reconcileStartAt).Seconds()) }() conditions.MarkFalse(c.IENode, string(v1ien.IENController), string(v1ien.Progressing), "%s", string(v1ien.Reconciling)) if requeue, reconcileDuration = c.reconcilePlugins(ctx, c.IENode, c.Object); requeue { conditions.MarkFalse(c.IENode, string(v1ien.IENController), string(v1ien.Failed), "%s", string(v1ien.Requeueing)) result = reconcile.ResultRequeue return } conditions.MarkTrue(c.IENode, string(v1ien.IENController), string(v1ien.Successful), "%s", string(v1ien.Succeeded)) return res, recErr } // reconcilePlugins iterates through all attached plugins and invokes them. func (c *GenericController) reconcilePlugins(ctx context.Context, ien *v1ien.IENode, object client.Object) (bool, internal.ReconcileTimeSet) { //nolint: dupl requireRequeue := false pluginReconcileTimes := make(internal.ReconcileTimeSet) for name, plugin := range c.Plugins { log := ctrl.LoggerFrom(ctx) log = log.WithValues("plugin", name) // check if plugin is enabled in config map if !c.isPluginEnabled(ctx, ien, name) { continue } pluginReconcileStart := time.Now() err := plugin.Reconcile(ctx, object, c.Config) pluginReconcileTimes[name] = time.Since(pluginReconcileStart).Seconds() if err != nil { log.Error(err, "failed to run plugin") conditions.MarkFalse(ien, name, string(v1ien.PluginFailed), "%v", err) requireRequeue = true continue } conditions.MarkTrue(ien, name, string(v1ien.PluginSuccessful), "%s", string(v1ien.Succeeded)) log.V(1).Info("plugin ran successfully") } return requireRequeue, pluginReconcileTimes } // registers node agent conditions for GenericController func (c *GenericController) registerConditions() { for pluginName := range maps.Keys(c.Plugins) { IENodeConditions.Owned = append(IENodeConditions.Owned, pluginName) IENodeConditions.Summarize = append(IENodeConditions.Summarize, pluginName) } } // setupNodeFirewallControllerWithManager is the SetupWithManager function to // use when registering the NodeFirewall controller. func setupNodeFirewallControllerWithManager(c *GenericController, mgr ctrl.Manager) error { hostname, err := ien.GetHostname() if err != nil { return err } return ctrl.NewControllerManagedBy(mgr). For(&v1ien.NodeFirewall{}, builder.WithPredicates( predicate.GenerationChangedPredicate{}, predicate.NewPredicateFuncs(func(obj client.Object) bool { return strings.Contains(obj.GetName(), hostname) }))). Complete(c) } // setupWithManager is the generic SetupWithManager function to use when // registering a controller that just needs to reconcile on the it's defined // controller Object in the 'sds' namespace. func setupWithManager(c *GenericController, mgr ctrl.Manager, flags config.Flags) error { if *flags.WatchAllNamespaces { return ctrl.NewControllerManagedBy(mgr). For(c.Object, builder.WithPredicates(predicate.ResourceVersionChangedPredicate{})). Complete(c) } return ctrl.NewControllerManagedBy(mgr). For(c.Object, builder.WithPredicates(predicate.ResourceVersionChangedPredicate{})). WithEventFilter(predicate.NewPredicateFuncs(func(obj client.Object) bool { return obj.GetNamespace() == sdsNamespace })). Complete(c) } // Instantiates a new generic controller func newGenericController(name string, mgr ctrl.Manager, conf config.Config, metrics metrics.Metrics, object client.Object) GenericController { ctl := GenericController{ Plugins: GenericPlugins, Object: object, Controller: Controller{ Client: mgr.GetClient(), Log: ctrl.Log.WithName(name), Name: name, Config: conf, RequeueTime: RequeueTime, Conditions: IENodeConditions, Metrics: metrics, }, } ctl.registerConditions() return ctl }