package nodeagent import ( "context" "time" "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" kuberecorder "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" ctrlReconcile "edge-infra.dev/pkg/k8s/runtime/controller/reconcile" "edge-infra.dev/pkg/lib/pprof" "edge-infra.dev/pkg/sds/ien" "edge-infra.dev/pkg/k8s/meta/status" "edge-infra.dev/pkg/k8s/runtime/conditions" "edge-infra.dev/pkg/k8s/runtime/controller/metrics" controllerReconciler "edge-infra.dev/pkg/k8s/runtime/controller/reconcile" "edge-infra.dev/pkg/k8s/runtime/events" "edge-infra.dev/pkg/k8s/runtime/patch" "edge-infra.dev/pkg/k8s/runtime/controller" 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" ) var ( sdsNamespace = "sds" ienctlName = "ienctl" RequeueTime = time.Minute * 2 RootPath = "/" ) // IENodeConditions defines the IENode status conditions owned by the nodeagent // and how these conditions should be considered when determining overall // readiness via the Ready condition. var IENodeConditions = controllerReconciler.Conditions{ Target: status.ReadyCondition, Owned: []string{ string(v1ien.IENController), }, Summarize: []string{ string(v1ien.IENController), }, NegativePolarity: []string{}, } // Controller represents a nodeagent controller. type Controller struct { client.Client kuberecorder.EventRecorder Log logr.Logger Name string RequeueTime time.Duration Config config.Config Conditions controllerReconciler.Conditions Metrics metrics.Metrics *v1ien.IENode } // Run registers and starts all controllers func Run(flags config.Flags, opts ...controller.Option) error { log := ctrl.Log if flags.Pprof != nil && *flags.Pprof { log.Info("serving pprof on /debug/pprof/") opts = append(opts, controller.WithPProf("/", pprof.Router())) } cmgr, err := createControllerManager(opts...) if err != nil { return err } eventRecorder := events.NewRecorder(cmgr, ctrl.Log, "nodeagent") mgr, err := registerControllers(flags, cmgr, eventRecorder) if err != nil { return err } if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { log.Error(err, "problem running manager") return err } return nil } // summarizer summarizes the results of the plugin reconciliations and patches // the status conditions to the IENode resource. func (c *Controller) Summarizer(ctx context.Context, patcher *patch.SerialPatcher, result ctrlReconcile.Result) (res ctrl.Result, recErr error) { s := controllerReconciler.NewSummarizer(patcher) return s.SummarizeAndPatch(ctx, c.IENode, controllerReconciler.WithConditions(c.Conditions), controllerReconciler.WithResult(result), controllerReconciler.WithError(recErr), controllerReconciler.WithIgnoreNotFound(), controllerReconciler.WithProcessors( controllerReconciler.RecordReconcileReq, controllerReconciler.RecordResult, ), controllerReconciler.WithFieldOwner(c.Name), controllerReconciler.WithEventRecorder(c.EventRecorder), ) } // registerControllers creates the required nodeagent controllers with their // associated plugins and registers them to the manager. func registerControllers(flags config.Flags, mgr ctrl.Manager, eventRecorder kuberecorder.EventRecorder) (ctrl.Manager, error) { cfg := config.NewConfig(mgr.GetClient(), mgr.GetAPIReader(), eventRecorder, flags) metrics := metrics.New(mgr, "nodeagent", metrics.WithSuspend(), metrics.WithCollectors( internal.NodeAgentReadinessMetric, internal.NodeAgentDurationMetric, ), ) // setup ienode controller ienctl := newIENController(mgr, cfg, metrics) if err := setupIENControllerWithManager(&ienctl, mgr); err != nil { return nil, err } // setup fw controller fwctl := newGenericController("fwctl", mgr, cfg, metrics, &v1ien.NodeFirewall{}) if err := setupNodeFirewallControllerWithManager(&fwctl, mgr); err != nil { return nil, err } // setup secret controller secretctl := newGenericController("secretctl", mgr, cfg, metrics, &corev1.Secret{}) if err := setupWithManager(&secretctl, mgr, cfg.GetFlags()); err != nil { return nil, err } // setup config map controller configctl := newGenericController("configctl", mgr, cfg, metrics, &corev1.ConfigMap{}) if err := setupWithManager(&configctl, mgr, cfg.GetFlags()); err != nil { return nil, err } return mgr, nil } // createReconcileRequests ignores the input object and returns a reconciliation // request for the IENode resource for the current node. func (c *Controller) createReconcileRequests(_ context.Context, _ client.Object) []reconcile.Request { hostname, err := ien.GetHostname() if err != nil { c.Log.Error(err, "error getting hostname for node") return []reconcile.Request{} } return []reconcile.Request{ { NamespacedName: types.NamespacedName{ Name: hostname, }, }, } } // initializeControllerMetrics returns the controller metrics to be used for the // controller, with core information about the node such as the hostname, class, // IEN version and lane details injected. func (c *Controller) initializeControllerMetrics(ctx context.Context) (*internal.ControllerMetrics, error) { hostname := c.IENode.ObjectMeta.GetName() class := string(c.IENode.Spec.Class) node := corev1.Node{} err := c.Client.Get(ctx, client.ObjectKey{Name: hostname}, &node) if err != nil { c.Log.Error(err, "error getting node info") return nil, err } ienVersion := node.ObjectMeta.Labels["feature.node.kubernetes.io/ien-version"] lane := node.ObjectMeta.Labels["node.ncr.com/lane"] return internal.CreateNewControllerMetrics(c.IENode, hostname, class, lane, ienVersion), nil } // isPluginEnabled will check if a plugin is enabled, record the plugin status func (c *Controller) isPluginEnabled(ctx context.Context, ien *v1ien.IENode, pluginName string) bool { pluginEnabled, err := c.Config.IsPluginEnabled(ctx, pluginName) if err != nil { conditions.MarkUnknown(ien, pluginName, string(v1ien.PluginFailed), "error: %v", err) } if !pluginEnabled { conditions.MarkTrue(ien, pluginName, string(v1ien.PluginDisabled), "%s", string(v1ien.Disabled)) } return pluginEnabled }