package nodeagent import ( "context" "time" "maps" corev1 "k8s.io/api/core/v1" 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/handler" "sigs.k8s.io/controller-runtime/pkg/predicate" "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" "edge-infra.dev/pkg/sds/controlplaneguardian/identifier" "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" "edge-infra.dev/pkg/sds/ien/topology" ) // IENode controller type IENController struct { Controller Plugins map[string]IENPlugin } func (c *IENController) 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() ) ienode := &v1ien.IENode{} if err := c.Client.Get(ctx, req.NamespacedName, ienode); err != nil { return ctrl.Result{}, client.IgnoreNotFound(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); requeue { conditions.MarkFalse(c.IENode, string(v1ien.IENController), string(v1ien.Progressing), "%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 *IENController) reconcilePlugins(ctx context.Context, ien *v1ien.IENode) (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() res, err := plugin.Reconcile(ctx, ien, c.Config) pluginReconcileTimes[name] = time.Since(pluginReconcileStart).Seconds() switch { case err != nil: log.Error(err, "failed to run plugin") conditions.MarkFalse(ien, name, string(v1ien.PluginFailed), "%v", err) requireRequeue = true continue case res == reconcile.ResultRequeue: log.Info("requeing plugin") conditions.MarkTrue(ien, name, string(v1ien.PluginRequeuing), "%s", string(v1ien.Reconciling)) requireRequeue = true case res == reconcile.ResultSuccess: log.V(1).Info("plugin ran successfully") conditions.MarkTrue(ien, name, string(v1ien.PluginSuccessful), "%s", string(v1ien.Succeeded)) } } return requireRequeue, pluginReconcileTimes } // registers node agent conditions for IENController func (c *IENController) registerConditions() { for pluginName := range maps.Keys(c.Plugins) { IENodeConditions.Owned = append(IENodeConditions.Owned, pluginName) IENodeConditions.Summarize = append(IENodeConditions.Summarize, pluginName) } } // matchesHostnamePredicate returns a filter predicate that will only return // true and reconcile if the object name matches the hostname. func matchesHostnamePredicate(hostname string) builder.Predicates { return builder.WithPredicates(predicate.GenerationChangedPredicate{}, predicate.NewPredicateFuncs(func(obj client.Object) bool { return obj.GetName() == hostname })) } // setupIENControllerWithManager is the SetupWithManager function to use when // registering the IENode controller. func setupIENControllerWithManager(c *IENController, mgr ctrl.Manager) error { hostname, err := ien.GetHostname() if err != nil { return err } return ctrl.NewControllerManagedBy(mgr). For(&v1ien.IENode{}, matchesHostnamePredicate(hostname)). Watches( &corev1.Node{}, handler.EnqueueRequestsFromMapFunc(c.createReconcileRequests), ). Watches( &corev1.ConfigMap{}, handler.EnqueueRequestsFromMapFunc(c.createReconcileRequests), builder.WithPredicates( predicate.Or( topology.PredicateFilter(), identifier.PredicateFilter(), ), ), ). Watches( &corev1.Secret{}, handler.EnqueueRequestsFromMapFunc(c.createReconcileRequests), builder.WithPredicates(secretPredicateFilter()), ). Complete(c) } // secretPredicateFilter returns the predicate function for objects in sds namespace func secretPredicateFilter() predicate.Funcs { return predicate.NewPredicateFuncs(func(object client.Object) bool { return object.GetNamespace() == sds.Namespace }) } // Instantiates a new IENode controller func newIENController(mgr ctrl.Manager, conf config.Config, metrics metrics.Metrics) IENController { ienctl := IENController{ Plugins: IENPlugins, Controller: Controller{ Client: mgr.GetClient(), Log: ctrl.Log.WithName(ienctlName), Name: ienctlName, Config: conf, RequeueTime: RequeueTime, Conditions: IENodeConditions, Metrics: metrics, }, } ienctl.registerConditions() return ienctl }