diff --git a/controllers/nodefeaturediscovery_controller.go b/controllers/nodefeaturediscovery_controller.go index f561187d..2a30caa0 100644 --- a/controllers/nodefeaturediscovery_controller.go +++ b/controllers/nodefeaturediscovery_controller.go @@ -37,29 +37,51 @@ import ( nfdv1 "github.com/kubernetes-sigs/node-feature-discovery-operator/api/v1" ) +// log is used to set the logger with a name that describes the actions of +// functions and arguments in this file var log = logf.Log.WithName("controller_nodefeaturediscovery") +// nfd is an NFD object that will be used to initialize the NFD operator var nfd NFD // NodeFeatureDiscoveryReconciler reconciles a NodeFeatureDiscovery object type NodeFeatureDiscoveryReconciler struct { + + // Client interface to communicate with the API server. Reconciler needs this for + // fetching objects. client.Client - Log logr.Logger - Scheme *runtime.Scheme - Recorder record.EventRecorder + + // Log is used to log the reconciliation. Every controller needs this. + Log logr.Logger + + // Scheme is used by the kubebuilder library to set OwnerReferences. Every + // controller needs this. + Scheme *runtime.Scheme + + // Recorder defines interfaces for working with OCP event recorders. This + // field is needed by the operator in order for the operator to write events. + Recorder record.EventRecorder + + // AssetsDir defines the directory with assets under the operator image AssetsDir string } -// SetupWithManager sets up the controller with the Manager. +// SetupWithManager sets up the controller with a specified manager responsible for +// initializing shared dependencies (like caches and clients) func (r *NodeFeatureDiscoveryReconciler) SetupWithManager(mgr ctrl.Manager) error { - // we want to initate reconcile loop only on spec change of the object + // The predicate package is used by the controller to filter events before + // they are sent to event handlers. Use it to initate the reconcile loop only + // on a spec change of the runtime object. p := predicate.Funcs{ UpdateFunc: func(e event.UpdateEvent) bool { return validateUpdateEvent(&e) }, } + // Create a new controller. "For" specifies the type of object being + // reconciled whereas "Owns" specify the types of objects being + // generated and "Complete" specifies the reconciler object. return ctrl.NewControllerManagedBy(mgr). For(&nfdv1.NodeFeatureDiscovery{}). Owns(&appsv1.DaemonSet{}, builder.WithPredicates(p)). @@ -70,6 +92,8 @@ func (r *NodeFeatureDiscoveryReconciler) SetupWithManager(mgr ctrl.Manager) erro Complete(r) } +// validateUpdateEvent looks at an update event and returns true or false +// depending on whether the update event has runtime objects to update. func validateUpdateEvent(e *event.UpdateEvent) bool { if e.ObjectOld == nil { klog.Error("Update event has no old runtime object to update") @@ -112,22 +136,25 @@ func validateUpdateEvent(e *event.UpdateEvent) bool { // +kubebuilder:rbac:groups=monitoring.coreos.com,resources=prometheusrules,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete -// Reconcile is part of the main kubernetes reconciliation loop which aims to -// move the current state of the cluster closer to the desired state. +// Reconcile is part of the main kubernetes reconciliation loop which aims +// to move the current state of the cluster closer to the desired state. func (r *NodeFeatureDiscoveryReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = r.Log.WithValues("nodefeaturediscovery", req.NamespacedName) - // Fetch the NodeFeatureDiscovery instance + // Fetch the NodeFeatureDiscovery instance on the cluster r.Log.Info("Fetch the NodeFeatureDiscovery instance") instance := &nfdv1.NodeFeatureDiscovery{} err := r.Get(ctx, req.NamespacedName, instance) - // Error reading the object - requeue the request. + + // If an error occurs because "r.Get" cannot get the NFD instance + // (e.g., due to timeouts, aborts, etc. defined by ctx), the + // request likely needs to be requeued. if err != nil { // handle deletion of resource if k8serrors.IsNotFound(err) { // Request object not found, could have been deleted after reconcile request. - // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. - // Return and don't requeue + // Owned objects are automatically garbage collected. For additional cleanup + // logic use finalizers. Return and don't requeue. r.Log.Info("resource has been deleted", "req", req.Name, "got", instance.Name) return ctrl.Result{Requeue: false}, nil } @@ -136,11 +163,10 @@ func (r *NodeFeatureDiscoveryReconciler) Reconcile(ctx context.Context, req ctrl return ctrl.Result{Requeue: true}, err } - // apply components r.Log.Info("Ready to apply components") - nfd.init(r, instance) + // Run through all control functions, return an error on any NotReady resource. for { err := nfd.step() if err != nil { diff --git a/controllers/nodefeaturediscovery_controls.go b/controllers/nodefeaturediscovery_controls.go index c903a7be..a599252c 100644 --- a/controllers/nodefeaturediscovery_controls.go +++ b/controllers/nodefeaturediscovery_controls.go @@ -33,6 +33,8 @@ import ( type controlFunc []func(n NFD) (ResourceStatus, error) +// ResourceStatus defines the status of the resource as being +// Ready or NotReady type ResourceStatus int const ( @@ -42,6 +44,8 @@ const ( defaultServicePort int = 12000 ) +// String implements the fmt.Stringer interface and returns describes +// ResourceStatus as a string. func (s ResourceStatus) String() string { names := [...]string{ "Ready", @@ -53,14 +57,24 @@ func (s ResourceStatus) String() string { return names[s] } +// Namespace checks if the Namespace for NFD exists and creates it +// if it doesn't exist func Namespace(n NFD) (ResourceStatus, error) { + // state represents the resource's 'control' function index state := n.idx + + // It is assumed that the index has already been verified to be a + // Namespace object, so let's get the resource's Namespace object obj := n.resources[state].Namespace + // found states if the Namespace was found found := &corev1.Namespace{} logger := log.WithValues("Namespace", obj.Name, "Namespace", "Cluster") + // Look for the Namespace to see if it exists, and if so, check if + // it's Ready/NotReady. If the Namespace does not exist, then + // attempt to create it logger.Info("Looking for") err := n.rec.Client.Get(context.TODO(), types.NamespacedName{Namespace: obj.Namespace, Name: obj.Name}, found) if err != nil && errors.IsNotFound(err) { @@ -80,22 +94,37 @@ func Namespace(n NFD) (ResourceStatus, error) { return Ready, nil } +// ServiceAccount checks the readiness of the NFD ServiceAccount and creates it if it doesn't exist func ServiceAccount(n NFD) (ResourceStatus, error) { + // state represents the resource's 'control' function index state := n.idx + + // It is assumed that the index has already been verified to be a + // ServiceAccount object, so let's get the resource's ServiceAccount + // object obj := n.resources[state].ServiceAccount + // It is also assumed that our service account has a defined Namespace obj.SetNamespace(n.ins.GetNamespace()) + // found states if the ServiceAccount was found found := &corev1.ServiceAccount{} logger := log.WithValues("ServiceAccount", obj.Name, "Namespace", obj.Namespace) logger.Info("Looking for") + // SetControllerReference sets the owner as a Controller OwnerReference + // and is used for garbage collection of the controlled object. It is + // also used to reconcile the owner object on changes to the controlled + // object. If we cannot set the owner, then return NotReady if err := controllerutil.SetControllerReference(n.ins, &obj, n.rec.Scheme); err != nil { return NotReady, err } + // Look for the ServiceAccount to see if it exists, and if so, check if + // it's Ready/NotReady. If the ServiceAccount does not exist, then + // attempt to create it err := n.rec.Client.Get(context.TODO(), types.NamespacedName{Namespace: obj.Namespace, Name: obj.Name}, found) if err != nil && errors.IsNotFound(err) { logger.Info("Not found, creating ") @@ -114,16 +143,26 @@ func ServiceAccount(n NFD) (ResourceStatus, error) { return Ready, nil } +// ClusterRole checks if the ClusterRole exists, and creates it if it doesn't func ClusterRole(n NFD) (ResourceStatus, error) { + // state represents the resource's 'control' function index state := n.idx + + // It is assumed that the index has already been verified to be a + // ClusterRole object, so let's get the resource's ClusterRole + // object obj := n.resources[state].ClusterRole + // found states if the ClusterRole was found found := &rbacv1.ClusterRole{} logger := log.WithValues("ClusterRole", obj.Name, "Namespace", obj.Namespace) logger.Info("Looking for") + // Look for the ClusterRole to see if it exists, and if so, check + // if it's Ready/NotReady. If the ClusterRole does not exist, then + // attempt to create it err := n.rec.Client.Get(context.TODO(), types.NamespacedName{Namespace: "", Name: obj.Name}, found) if err != nil && errors.IsNotFound(err) { logger.Info("Not found, creating") @@ -137,6 +176,7 @@ func ClusterRole(n NFD) (ResourceStatus, error) { return NotReady, err } + // If we found the ClusterRole, let's attempt to update it logger.Info("Found, updating") err = n.rec.Client.Update(context.TODO(), &obj) if err != nil { @@ -146,18 +186,30 @@ func ClusterRole(n NFD) (ResourceStatus, error) { return Ready, nil } +// ClusterRoleBinding checks if a ClusterRoleBinding exists and creates one if it doesn't func ClusterRoleBinding(n NFD) (ResourceStatus, error) { + // state represents the resource's 'control' function index state := n.idx + + // It is assumed that the index has already been verified to be a + // ClusterRoleBinding object, so let's get the resource's + // ClusterRoleBinding object obj := n.resources[state].ClusterRoleBinding + // found states if the ClusterRoleBinding was found found := &rbacv1.ClusterRoleBinding{} logger := log.WithValues("ClusterRoleBinding", obj.Name, "Namespace", obj.Namespace) + // It is also assumed that our ClusterRoleBinding has a defined + // Namespace obj.Subjects[0].Namespace = n.ins.GetNamespace() logger.Info("Looking for") + // Look for the ClusterRoleBinding to see if it exists, and if so, + // check if it's Ready/NotReady. If the ClusterRoleBinding does not + // exist, then attempt to create it err := n.rec.Client.Get(context.TODO(), types.NamespacedName{Namespace: "", Name: obj.Name}, found) if err != nil && errors.IsNotFound(err) { logger.Info("Not found, creating") @@ -171,6 +223,7 @@ func ClusterRoleBinding(n NFD) (ResourceStatus, error) { return NotReady, err } + // If we found the ClusterRoleBinding, let's attempt to update it logger.Info("Found, updating") err = n.rec.Client.Update(context.TODO(), &obj) if err != nil { @@ -179,22 +232,37 @@ func ClusterRoleBinding(n NFD) (ResourceStatus, error) { return Ready, nil } + +// Role checks if a Role exists and creates a Role if it doesn't func Role(n NFD) (ResourceStatus, error) { + // state represents the resource's 'control' function index state := n.idx + + // It is assumed that the index has already been verified to be a + // Role object, so let's get the resource's Role object obj := n.resources[state].Role + // The Namespace should already be defined, so let's set the + // namespace to the namespace defined in the Role object obj.SetNamespace(n.ins.GetNamespace()) + // found states if the Role was found found := &rbacv1.Role{} logger := log.WithValues("Role", obj.Name, "Namespace", obj.Namespace) logger.Info("Looking for") + // SetControllerReference sets the owner as a Controller OwnerReference + // and is used for garbage collection of the controlled object. It is + // also used to reconcile the owner object on changes to the controlled + // object. If we cannot set the owner, then return NotReady if err := controllerutil.SetControllerReference(n.ins, &obj, n.rec.Scheme); err != nil { return NotReady, err } + // Look for the Role to see if it exists, and if so, check if it's + // Ready/NotReady. If the Role does not exist, then attempt to create it err := n.rec.Client.Get(context.TODO(), types.NamespacedName{Namespace: obj.Namespace, Name: obj.Name}, found) if err != nil && errors.IsNotFound(err) { logger.Info("Not found, creating") @@ -208,6 +276,7 @@ func Role(n NFD) (ResourceStatus, error) { return NotReady, err } + // If we found the Role, let's attempt to update it logger.Info("Found, updating") err = n.rec.Client.Update(context.TODO(), &obj) if err != nil { @@ -217,22 +286,37 @@ func Role(n NFD) (ResourceStatus, error) { return Ready, nil } +// RoleBinding checks if a RoleBinding exists and creates a RoleBinding if it doesn't func RoleBinding(n NFD) (ResourceStatus, error) { + // state represents the resource's 'control' function index state := n.idx + + // It is assumed that the index has already been verified to be a + // RoleBinding object, so let's get the resource's RoleBinding + // object obj := n.resources[state].RoleBinding + // The Namespace should already be defined, so let's set the + // namespace to the namespace defined in the obj.SetNamespace(n.ins.GetNamespace()) + // found states if the RoleBinding was found found := &rbacv1.RoleBinding{} logger := log.WithValues("RoleBinding", obj.Name, "Namespace", obj.Namespace) logger.Info("Looking for") + // SetControllerReference sets the owner as a Controller OwnerReference + // and is used for garbage collection of the controlled object. It is + // also used to reconcile the owner object on changes to the controlled if err := controllerutil.SetControllerReference(n.ins, &obj, n.rec.Scheme); err != nil { return NotReady, err } + // Look for the RoleBinding to see if it exists, and if so, check if + // it's Ready/NotReady. If the RoleBinding does not exist, then attempt + // to create it err := n.rec.Client.Get(context.TODO(), types.NamespacedName{Namespace: obj.Namespace, Name: obj.Name}, found) if err != nil && errors.IsNotFound(err) { logger.Info("Not found, creating") @@ -246,6 +330,7 @@ func RoleBinding(n NFD) (ResourceStatus, error) { return NotReady, err } + // If we found the RoleBinding, let's attempt to update it logger.Info("Found, updating") err = n.rec.Client.Update(context.TODO(), &obj) if err != nil { @@ -255,26 +340,41 @@ func RoleBinding(n NFD) (ResourceStatus, error) { return Ready, nil } +// ConfigMap checks if a ConfigMap exists and creates one if it doesn't func ConfigMap(n NFD) (ResourceStatus, error) { + // state represents the resource's 'control' function index state := n.idx + + // It is assumed that the index has already been verified to be a + // ConfigMap object, so let's get the resource's ConfigMap object obj := n.resources[state].ConfigMap + // The Namespace should already be defined, so let's set the + // namespace to the namespace defined in the ConfigMap object obj.SetNamespace(n.ins.GetNamespace()) // Update ConfigMap obj.ObjectMeta.Name = "nfd-worker" obj.Data["nfd-worker-conf"] = n.ins.Spec.WorkerConfig.ConfigData + // found states if the ConfigMap was found found := &corev1.ConfigMap{} logger := log.WithValues("ConfigMap", obj.Name, "Namespace", obj.Namespace) logger.Info("Looking for") + // SetControllerReference sets the owner as a Controller OwnerReference + // and is used for garbage collection of the controlled object. It is + // also used to reconcile the owner object on changes to the controlled + // object. If we cannot set the owner, then return NotReady if err := controllerutil.SetControllerReference(n.ins, &obj, n.rec.Scheme); err != nil { return NotReady, err } + // Look for the ConfigMap to see if it exists, and if so, check if it's + // Ready/NotReady. If the ConfigMap does not exist, then attempt to create + // it err := n.rec.Client.Get(context.TODO(), types.NamespacedName{Namespace: obj.Namespace, Name: obj.Name}, found) if err != nil && errors.IsNotFound(err) { logger.Info("Not found, creating") @@ -288,6 +388,7 @@ func ConfigMap(n NFD) (ResourceStatus, error) { return NotReady, err } + // If we found the ConfigMap, let's attempt to update it logger.Info("Found, updating") err = n.rec.Client.Update(context.TODO(), &obj) if err != nil { @@ -297,29 +398,42 @@ func ConfigMap(n NFD) (ResourceStatus, error) { return Ready, nil } +// DaemonSet checks the readiness of a DaemonSet and creates one if it doesn't exist func DaemonSet(n NFD) (ResourceStatus, error) { + // state represents the resource's 'control' function index state := n.idx + + // It is assumed that the index has already been verified to be a + // DaemonSet object, so let's get the resource's DaemonSet object obj := n.resources[state].DaemonSet - // update the image + // Update the NFD operand image obj.Spec.Template.Spec.Containers[0].Image = n.ins.Spec.Operand.ImagePath() - // update image pull policy + // Update the image pull policy if n.ins.Spec.Operand.ImagePullPolicy != "" { obj.Spec.Template.Spec.Containers[0].ImagePullPolicy = n.ins.Spec.Operand.ImagePolicy(n.ins.Spec.Operand.ImagePullPolicy) } - // update nfd-master service port + // Update nfd-master service port if obj.ObjectMeta.Name == "nfd-master" { var args []string port := defaultServicePort + + // If the operand service port has already been defined, + // then set "port" to the defined port. Otherwise, it is + // ok to just use the defaultServicePort value if n.ins.Spec.Operand.ServicePort != 0 { port = n.ins.Spec.Operand.ServicePort } + + // Now that the port has been determined, append it to + // the list of args args = append(args, fmt.Sprintf("--port=%d", port)) - // check if running as instance + // Check if running as instance. If not, then it is + // expected that n.ins.Spec.Instance will return "" // https://kubernetes-sigs.github.io/node-feature-discovery/v0.8/advanced/master-commandline-reference.html#-instance if n.ins.Spec.Instance != "" { args = append(args, fmt.Sprintf("--instance=%s", n.ins.Spec.Instance)) @@ -337,20 +451,33 @@ func DaemonSet(n NFD) (ResourceStatus, error) { args = append(args, fmt.Sprintf("--label-whitelist=%s", n.ins.Spec.LabelWhiteList)) } + // Set the args based on the port that was determined + // and the instance that was determined obj.Spec.Template.Spec.Containers[0].Args = args } + // Set namespace based on the NFD namespace. (And again, + // it is assumed that the Namespace has already been + // determined before this function was called.) obj.SetNamespace(n.ins.GetNamespace()) + // found states if the DaemonSet was found found := &appsv1.DaemonSet{} logger := log.WithValues("DaemonSet", obj.Name, "Namespace", obj.Namespace) logger.Info("Looking for") + // SetControllerReference sets the owner as a Controller OwnerReference + // and is used for garbage collection of the controlled object. It is + // also used to reconcile the owner object on changes to the controlled + // object. If we cannot set the owner, then return NotReady if err := controllerutil.SetControllerReference(n.ins, &obj, n.rec.Scheme); err != nil { return NotReady, err } + // Look for the DaemonSet to see if it exists, and if so, check if it's + // Ready/NotReady. If the DaemonSet does not exist, then attempt to + // create it err := n.rec.Client.Get(context.TODO(), types.NamespacedName{Namespace: obj.Namespace, Name: obj.Name}, found) if err != nil && errors.IsNotFound(err) { logger.Info("Not found, creating") @@ -364,6 +491,7 @@ func DaemonSet(n NFD) (ResourceStatus, error) { return NotReady, err } + // If we found the DaemonSet, let's attempt to update it logger.Info("Found, updating") err = n.rec.Client.Update(context.TODO(), &obj) if err != nil { @@ -373,12 +501,19 @@ func DaemonSet(n NFD) (ResourceStatus, error) { return Ready, nil } +// Service checks if a Service exists and creates one if it doesn't exist func Service(n NFD) (ResourceStatus, error) { + // state represents the resource's 'control' function index state := n.idx + + // It is assumed that the index has already been verified to be a + // Service object, so let's get the resource's Service object obj := n.resources[state].Service - // update ports + // Update ports for the Service. If the service port has already + // been defined, then that value should be used. Otherwise, just + // use the defaultServicePort's value. if n.ins.Spec.Operand.ServicePort != 0 { obj.Spec.Ports[0].Port = int32(n.ins.Spec.Operand.ServicePort) obj.Spec.Ports[0].TargetPort = intstr.FromInt(n.ins.Spec.Operand.ServicePort) @@ -387,17 +522,28 @@ func Service(n NFD) (ResourceStatus, error) { obj.Spec.Ports[0].TargetPort = intstr.FromInt(defaultServicePort) } + // Set namespace based on the NFD namespace. (And again, + // it is assumed that the Namespace has already been + // determined before this function was called.) obj.SetNamespace(n.ins.GetNamespace()) + // found states if the Service was found found := &corev1.Service{} logger := log.WithValues("Service", obj.Name, "Namespace", obj.Namespace) logger.Info("Looking for") + // SetControllerReference sets the owner as a Controller OwnerReference + // and is used for garbage collection of the controlled object. It is + // also used to reconcile the owner object on changes to the controlled + // object. If we cannot set the owner, then return NotReady if err := controllerutil.SetControllerReference(n.ins, &obj, n.rec.Scheme); err != nil { return NotReady, err } + // Look for the Service to see if it exists, and if so, check if it's + // Ready/NotReady. If the Service does not exist, then attempt to create + // it err := n.rec.Client.Get(context.TODO(), types.NamespacedName{Namespace: obj.Namespace, Name: obj.Name}, found) if err != nil && errors.IsNotFound(err) { logger.Info("Not found, creating") @@ -413,10 +559,16 @@ func Service(n NFD) (ResourceStatus, error) { logger.Info("Found, updating") + // Copy the Service object required := obj.DeepCopy() + + // Set the resource version based on what we found when searching + // for the existing Service. Do the same for ClusterIP required.ResourceVersion = found.ResourceVersion required.Spec.ClusterIP = found.Spec.ClusterIP + // If we found the Service, let's attempt to update it with the + // resource version and cluster IP that was just found err = n.rec.Client.Update(context.TODO(), required) if err != nil { @@ -426,19 +578,29 @@ func Service(n NFD) (ResourceStatus, error) { return Ready, nil } +// SecurityContextConstraints checks if a SecurityContextConstraints exists and +// creates one if it doesn't exist func SecurityContextConstraints(n NFD) (ResourceStatus, error) { + // state represents the resource's 'control' function index state := n.idx + + // It is assumed that the index has already been verified to be an + // scc object, so let's get the resource's scc object obj := n.resources[state].SecurityContextConstraints // Set the correct namespace for SCC when installed in non default namespace obj.Users[0] = "system:serviceaccount:" + n.ins.GetNamespace() + ":" + obj.GetName() + // found states if the scc was found found := &secv1.SecurityContextConstraints{} logger := log.WithValues("SecurityContextConstraints", obj.Name, "Namespace", "default") logger.Info("Looking for") + // Look for the scc to see if it exists, and if so, check if it's + // Ready/NotReady. If the scc does not exist, then attempt to create + // it err := n.rec.Client.Get(context.TODO(), types.NamespacedName{Namespace: "", Name: obj.Name}, found) if err != nil && errors.IsNotFound(err) { logger.Info("Not found, creating") @@ -454,6 +616,8 @@ func SecurityContextConstraints(n NFD) (ResourceStatus, error) { logger.Info("Found, updating") + // If we found the scc, let's attempt to update it with the resource + // version we found required := obj.DeepCopy() required.ResourceVersion = found.ResourceVersion diff --git a/controllers/nodefeaturediscovery_resources.go b/controllers/nodefeaturediscovery_resources.go index 70583fd4..7dda831f 100644 --- a/controllers/nodefeaturediscovery_resources.go +++ b/controllers/nodefeaturediscovery_resources.go @@ -32,6 +32,7 @@ import ( "k8s.io/kubectl/pkg/scheme" ) +// assetsFromFile is the content of an asset file as raw data type assetsFromFile []byte // Resources holds objects owned by NFD @@ -58,7 +59,10 @@ func Add3dpartyResourcesToScheme(scheme *runtime.Scheme) error { return nil } +// filePathWalkDir finds all non-directory files under the given path recursively, +// i.e. including its subdirectories func filePathWalkDir(root string) ([]string, error) { + var files []string err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { if !info.IsDir() { @@ -69,34 +73,49 @@ func filePathWalkDir(root string) ([]string, error) { return files, err } +// getAssetsFrom recursively reads all manifest files under a given path func getAssetsFrom(path string) []assetsFromFile { + // All assets (manifests) as raw data manifests := []assetsFromFile{} assets := path + + // For the given path, find a list of all the files files, err := filePathWalkDir(assets) if err != nil { panic(err) } + + // For each file in the 'files' list, read the file + // and store its contents in 'manifests' for _, file := range files { buffer, err := ioutil.ReadFile(file) if err != nil { panic(err) } + manifests = append(manifests, buffer) } return manifests } func addResourcesControls(path string) (Resources, controlFunc) { + + // Information about the manifest res := Resources{} + + // A list of control functions for checking the status of a resource ctrl := controlFunc{} + // Get the list of manifests from the given path manifests := getAssetsFrom(path) + // s and reg are used later on to parse the manifest YAML s := json.NewYAMLSerializer(json.DefaultMetaFactory, scheme.Scheme, scheme.Scheme) reg, _ := regexp.Compile(`\b(\w*kind:\w*)\B.*\b`) + // Append the appropriate control function depending on the kind for _, m := range manifests { kind := reg.FindString(string(m)) slce := strings.Split(kind, ":") @@ -153,6 +172,7 @@ func addResourcesControls(path string) (Resources, controlFunc) { return res, ctrl } +// panicIfError panics in case of an error func panicIfError(err error) { if err != nil { panic(err) diff --git a/controllers/nodefeaturediscovery_state.go b/controllers/nodefeaturediscovery_state.go index c9184ac7..739d6ee7 100644 --- a/controllers/nodefeaturediscovery_state.go +++ b/controllers/nodefeaturediscovery_state.go @@ -22,21 +22,37 @@ import ( nfdv1 "github.com/kubernetes-sigs/node-feature-discovery-operator/api/v1" ) -// NFD holds the needed information to watch from the Controller +// NFD holds the needed information to watch from the Controller. type NFD struct { + + // resources contains information about NFD's resources. resources []Resources - controls []controlFunc - rec *NodeFeatureDiscoveryReconciler - ins *nfdv1.NodeFeatureDiscovery - idx int + + // controls contains a list of functions for determining if a NFD resource is ready + controls []controlFunc + + // rec represents the NFD reconciler struct used for reconciliation + rec *NodeFeatureDiscoveryReconciler + + // ins is the NodeFeatureDiscovery struct that contains the Schema + // for the nodefeaturediscoveries API + ins *nfdv1.NodeFeatureDiscovery + + // idx is the index that is used to step through the 'controls' list + // and is set to 0 upon calling 'init()' + idx int } +// addState finds resources in a given path and adds them and their control +// functions to the NFD instance. func (n *NFD) addState(path string) { res, ctrl := addResourcesControls(path) n.controls = append(n.controls, ctrl) n.resources = append(n.resources, res) } +// init initializes an NFD object by populating the fields before +// attempting to run any kind of check. func (n *NFD) init( r *NodeFeatureDiscoveryReconciler, i *nfdv1.NodeFeatureDiscovery, @@ -50,7 +66,11 @@ func (n *NFD) init( } } +// step performs one step of the resource reconciliation loop, iterating over +// one set of resource control functions n order to determine if the related +// resources are ready. func (n *NFD) step() error { + for _, fs := range n.controls[n.idx] { stat, err := fs(*n) if err != nil { @@ -60,10 +80,13 @@ func (n *NFD) step() error { return errors.New("ResourceNotReady") } } + + // Increment the index to handle the next set of control functions n.idx = n.idx + 1 return nil } +// last checks if all control functions have been processed. func (n *NFD) last() bool { return n.idx == len(n.controls) } diff --git a/main.go b/main.go index e9ba03c8..982607aa 100644 --- a/main.go +++ b/main.go @@ -35,11 +35,17 @@ import ( ) var ( - scheme = runtime.NewScheme() + // scheme holds a new scheme for the operator + scheme = runtime.NewScheme() + + // setupLog will be used for logging the operator "setup" process so that users know + // what parts of the logging are associated with the setup of the manager and + // controller setupLog = ctrl.Log.WithName("setup") ) func init() { + //Set up the Go client and NFD schemes. Panic on errors. utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(nfdkubernetesiov1.AddToScheme(scheme)) @@ -47,15 +53,26 @@ func init() { } func main() { + var metricsAddr string var enableLeaderElection bool var probeAddr string - flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") - flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") + + // Setup CLI arguments + flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the Prometheus "+ + "metric endpoint binds to for scraping NFD resource usage data.") + flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe "+ + "endpoint binds to for determining liveness, readiness, and configuration of"+ + "operator pods.") flag.BoolVar(&enableLeaderElection, "leader-elect", false, "Enable leader election for controller manager. "+ "Enabling this will ensure there is only one active controller manager.") + + // opts is created using zap to set the operator's logging opts := zap.Options{ + // This mode makes DPanic-level logs panic instead of just logging error events as + // errors. The settings are then bound to the CLI flag args and the flag args are + // then parsed. Development: true, } opts.BindFlags(flag.CommandLine) @@ -63,6 +80,7 @@ func main() { ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + // Create a new manager to manage the operator mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, MetricsBindAddress: metricsAddr, @@ -71,6 +89,7 @@ func main() { LeaderElection: enableLeaderElection, LeaderElectionID: "39f5e5c3.nodefeaturediscoveries.nfd.kubernetes.io", }) + if err != nil { setupLog.Error(err, "unable to start manager") os.Exit(1) @@ -86,15 +105,21 @@ func main() { } // +kubebuilder:scaffold:builder + // Next, add a Healthz checker to the manager. Healthz is a health and liveness package + // that the operator will use to periodically check the health of its pods, etc. if err := mgr.AddHealthzCheck("health", healthz.Ping); err != nil { setupLog.Error(err, "unable to set up health check") os.Exit(1) } + + // Now add a ReadyZ checker to the manager as well. It is important to ensure that the + // API server's readiness is checked when the operator is installed and running. if err := mgr.AddReadyzCheck("check", healthz.Ping); err != nil { setupLog.Error(err, "unable to set up ready check") os.Exit(1) } + // Register signal handler for SIGINT and SIGTERM to terminate the manager setupLog.Info("starting manager") if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { setupLog.Error(err, "problem running manager")