From 28c40db0a6201d2e5a71dea62c04541347812290 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakivskyy Date: Fri, 4 Oct 2024 14:37:47 +0300 Subject: [PATCH] nfd-master: Add status for NodeFeatureRule CRD Signed-off-by: Oleg Zhurakivskyy --- .../nfd/v1alpha1/fake/fake_nodefeaturerule.go | 12 +++ .../typed/nfd/v1alpha1/nodefeaturerule.go | 2 + api/nfd/v1alpha1/types.go | 25 +++++ api/nfd/v1alpha1/zz_generated.deepcopy.go | 78 +++++++++++++++ deployment/base/nfd-crds/nfd-api-crds.yaml | 22 +++++ .../crds/nfd-api-crds.yaml | 22 +++++ pkg/apis/nfd/nodefeaturerule/rule.go | 2 + pkg/nfd-master/nfd-master.go | 95 ++++++++++++++++++- 8 files changed, 253 insertions(+), 5 deletions(-) diff --git a/api/generated/clientset/versioned/typed/nfd/v1alpha1/fake/fake_nodefeaturerule.go b/api/generated/clientset/versioned/typed/nfd/v1alpha1/fake/fake_nodefeaturerule.go index b2916717c..22a99e162 100644 --- a/api/generated/clientset/versioned/typed/nfd/v1alpha1/fake/fake_nodefeaturerule.go +++ b/api/generated/clientset/versioned/typed/nfd/v1alpha1/fake/fake_nodefeaturerule.go @@ -99,6 +99,18 @@ func (c *FakeNodeFeatureRules) Update(ctx context.Context, nodeFeatureRule *v1al return obj.(*v1alpha1.NodeFeatureRule), err } +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *FakeNodeFeatureRules) UpdateStatus(ctx context.Context, nodeFeatureRule *v1alpha1.NodeFeatureRule, opts v1.UpdateOptions) (result *v1alpha1.NodeFeatureRule, err error) { + emptyResult := &v1alpha1.NodeFeatureRule{} + obj, err := c.Fake. + Invokes(testing.NewRootUpdateSubresourceActionWithOptions(nodefeaturerulesResource, "status", nodeFeatureRule, opts), emptyResult) + if obj == nil { + return emptyResult, err + } + return obj.(*v1alpha1.NodeFeatureRule), err +} + // Delete takes name of the nodeFeatureRule and deletes it. Returns an error if one occurs. func (c *FakeNodeFeatureRules) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { _, err := c.Fake. diff --git a/api/generated/clientset/versioned/typed/nfd/v1alpha1/nodefeaturerule.go b/api/generated/clientset/versioned/typed/nfd/v1alpha1/nodefeaturerule.go index e4a500732..364fe51e0 100644 --- a/api/generated/clientset/versioned/typed/nfd/v1alpha1/nodefeaturerule.go +++ b/api/generated/clientset/versioned/typed/nfd/v1alpha1/nodefeaturerule.go @@ -39,6 +39,8 @@ type NodeFeatureRulesGetter interface { type NodeFeatureRuleInterface interface { Create(ctx context.Context, nodeFeatureRule *v1alpha1.NodeFeatureRule, opts v1.CreateOptions) (*v1alpha1.NodeFeatureRule, error) Update(ctx context.Context, nodeFeatureRule *v1alpha1.NodeFeatureRule, opts v1.UpdateOptions) (*v1alpha1.NodeFeatureRule, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, nodeFeatureRule *v1alpha1.NodeFeatureRule, opts v1.UpdateOptions) (*v1alpha1.NodeFeatureRule, error) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.NodeFeatureRule, error) diff --git a/api/nfd/v1alpha1/types.go b/api/nfd/v1alpha1/types.go index c89efd818..d62fdee8b 100644 --- a/api/nfd/v1alpha1/types.go +++ b/api/nfd/v1alpha1/types.go @@ -122,6 +122,7 @@ type NodeFeatureRuleList struct { // customization of node objects, such as node labeling. // +kubebuilder:object:root=true // +kubebuilder:resource:scope=Cluster,shortName=nfr +// +kubebuilder:subresource:status // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +genclient // +genclient:nonNamespaced @@ -131,6 +132,8 @@ type NodeFeatureRule struct { // Spec defines the rules to be evaluated. Spec NodeFeatureRuleSpec `json:"spec"` + // +optional + Status NodeFeatureRuleStatus `json:"status,omitempty"` } // NodeFeatureRuleSpec describes a NodeFeatureRule. @@ -139,6 +142,28 @@ type NodeFeatureRuleSpec struct { Rules []Rule `json:"rules"` } +// NodeFeatureRuleStatus represents the status of a NodeFeatureRule +type NodeFeatureRuleStatus struct { + // +optional + Rules []RuleStatus `json:"rules,omitempty"` +} + +// RuleStatus contains information on matched rules and nodes +type RuleStatus struct { + Name string `json:"name"` + MatchedNodes []string `json:"matchedNodes"` +} + +// NodeFeatureRuleStatusList contains a list of NodeFeatureRuleStatus objects. +// +kubebuilder:object:root=true +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type NodeFeatureRuleStatusList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + Items []NodeFeatureRuleStatus `json:"items"` +} + // NodeFeatureGroup resource holds Node pools by featureGroup // +kubebuilder:object:root=true // +kubebuilder:resource:scope=Namespaced,shortName=nfg diff --git a/api/nfd/v1alpha1/zz_generated.deepcopy.go b/api/nfd/v1alpha1/zz_generated.deepcopy.go index 491d9866c..510a7b7d5 100644 --- a/api/nfd/v1alpha1/zz_generated.deepcopy.go +++ b/api/nfd/v1alpha1/zz_generated.deepcopy.go @@ -544,6 +544,7 @@ func (in *NodeFeatureRule) DeepCopyInto(out *NodeFeatureRule) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) return } @@ -621,6 +622,62 @@ func (in *NodeFeatureRuleSpec) DeepCopy() *NodeFeatureRuleSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NodeFeatureRuleStatus) DeepCopyInto(out *NodeFeatureRuleStatus) { + *out = *in + if in.Rules != nil { + in, out := &in.Rules, &out.Rules + *out = make([]RuleStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeFeatureRuleStatus. +func (in *NodeFeatureRuleStatus) DeepCopy() *NodeFeatureRuleStatus { + if in == nil { + return nil + } + out := new(NodeFeatureRuleStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NodeFeatureRuleStatusList) DeepCopyInto(out *NodeFeatureRuleStatusList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]NodeFeatureRuleStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeFeatureRuleStatusList. +func (in *NodeFeatureRuleStatusList) DeepCopy() *NodeFeatureRuleStatusList { + if in == nil { + return nil + } + out := new(NodeFeatureRuleStatusList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NodeFeatureRuleStatusList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NodeFeatureSpec) DeepCopyInto(out *NodeFeatureSpec) { *out = *in @@ -709,3 +766,24 @@ func (in *Rule) DeepCopy() *Rule { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RuleStatus) DeepCopyInto(out *RuleStatus) { + *out = *in + if in.MatchedNodes != nil { + in, out := &in.MatchedNodes, &out.MatchedNodes + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RuleStatus. +func (in *RuleStatus) DeepCopy() *RuleStatus { + if in == nil { + return nil + } + out := new(RuleStatus) + in.DeepCopyInto(out) + return out +} diff --git a/deployment/base/nfd-crds/nfd-api-crds.yaml b/deployment/base/nfd-crds/nfd-api-crds.yaml index 56142c6ce..8738b8354 100644 --- a/deployment/base/nfd-crds/nfd-api-crds.yaml +++ b/deployment/base/nfd-crds/nfd-api-crds.yaml @@ -703,8 +703,30 @@ spec: required: - rules type: object + status: + description: NodeFeatureRuleStatus represents the status of a NodeFeatureRule + properties: + rules: + items: + description: RuleStatus contains information on matched rules and + nodes + properties: + matchedNodes: + items: + type: string + type: array + name: + type: string + required: + - matchedNodes + - name + type: object + type: array + type: object required: - spec type: object served: true storage: true + subresources: + status: {} diff --git a/deployment/helm/node-feature-discovery/crds/nfd-api-crds.yaml b/deployment/helm/node-feature-discovery/crds/nfd-api-crds.yaml index 56142c6ce..8738b8354 100644 --- a/deployment/helm/node-feature-discovery/crds/nfd-api-crds.yaml +++ b/deployment/helm/node-feature-discovery/crds/nfd-api-crds.yaml @@ -703,8 +703,30 @@ spec: required: - rules type: object + status: + description: NodeFeatureRuleStatus represents the status of a NodeFeatureRule + properties: + rules: + items: + description: RuleStatus contains information on matched rules and + nodes + properties: + matchedNodes: + items: + type: string + type: array + name: + type: string + required: + - matchedNodes + - name + type: object + type: array + type: object required: - spec type: object served: true storage: true + subresources: + status: {} diff --git a/pkg/apis/nfd/nodefeaturerule/rule.go b/pkg/apis/nfd/nodefeaturerule/rule.go index b9a562924..edc23fc06 100644 --- a/pkg/apis/nfd/nodefeaturerule/rule.go +++ b/pkg/apis/nfd/nodefeaturerule/rule.go @@ -39,6 +39,7 @@ type RuleOutput struct { Annotations map[string]string Vars map[string]string Taints []corev1.Taint + Matched bool } // Execute the rule against a set of input features. @@ -103,6 +104,7 @@ func Execute(r *nfdv1alpha1.Rule, features *nfdv1alpha1.Features) (RuleOutput, e Annotations: maps.Clone(r.Annotations), ExtendedResources: maps.Clone(r.ExtendedResources), Taints: slices.Clone(r.Taints), + Matched: true, } klog.V(2).InfoS("rule matched", "ruleName", r.Name, "ruleOutput", utils.DelayedDumper(ret)) return ret, nil diff --git a/pkg/nfd-master/nfd-master.go b/pkg/nfd-master/nfd-master.go index 085afb76e..2467c61bd 100644 --- a/pkg/nfd-master/nfd-master.go +++ b/pkg/nfd-master/nfd-master.go @@ -634,6 +634,12 @@ func (m *nfdMaster) nfdAPIUpdateAllNodes() error { m.updaterPool.addNode(node.Name) } + err = m.updateRuleStatus() + if err != nil { + klog.ErrorS(err, "failed to update rule status") + return err + } + return nil } @@ -878,7 +884,7 @@ func (m *nfdMaster) refreshNodeFeatures(cli k8sclient.Interface, node *corev1.No labels = make(map[string]string) } - crLabels, crAnnotations, crExtendedResources, crTaints := m.processNodeFeatureRule(node.Name, features) + crLabels, crAnnotations, crExtendedResources, crTaints, _ := m.processNodeFeatureRule(node.Name, features) // Labels maps.Copy(labels, crLabels) @@ -989,9 +995,9 @@ func (m *nfdMaster) setTaints(cli k8sclient.Interface, taints []corev1.Taint, no return nil } -func (m *nfdMaster) processNodeFeatureRule(nodeName string, features *nfdv1alpha1.Features) (Labels, Annotations, ExtendedResources, []corev1.Taint) { +func (m *nfdMaster) processNodeFeatureRule(nodeName string, features *nfdv1alpha1.Features) (Labels, Annotations, ExtendedResources, []corev1.Taint, []*nfdv1alpha1.NodeFeatureRule) { if m.nfdController == nil { - return nil, nil, nil, nil + return nil, nil, nil, nil, nil } extendedResources := ExtendedResources{} @@ -1005,12 +1011,13 @@ func (m *nfdMaster) processNodeFeatureRule(nodeName string, features *nfdv1alpha if err != nil { klog.ErrorS(err, "failed to list NodeFeatureRule resources") - return nil, nil, nil, nil + return nil, nil, nil, nil, nil } // Process all rule CRs processStart := time.Now() for _, spec := range ruleSpecs { + spec.Status.Rules = []nfdv1alpha1.RuleStatus{} t := time.Now() switch { case klog.V(3).Enabled(): @@ -1042,13 +1049,91 @@ func (m *nfdMaster) processNodeFeatureRule(nodeName string, features *nfdv1alpha // Feed back rule output to features map for subsequent rules to match features.InsertAttributeFeatures(nfdv1alpha1.RuleBackrefDomain, nfdv1alpha1.RuleBackrefFeature, ruleOut.Labels) features.InsertAttributeFeatures(nfdv1alpha1.RuleBackrefDomain, nfdv1alpha1.RuleBackrefFeature, ruleOut.Vars) + + if ruleOut.Matched { + r := nfdv1alpha1.RuleStatus{ + Name: rule.Name, + MatchedNodes: []string{ + nodeName, + }, + } + spec.Status.Rules = append(spec.Status.Rules, r) + } } nfrProcessingTime.WithLabelValues(spec.Name, nodeName).Observe(time.Since(t).Seconds()) } processingTime := time.Since(processStart) klog.V(2).InfoS("processed NodeFeatureRule objects", "nodeName", nodeName, "objectCount", len(ruleSpecs), "duration", processingTime) - return labels, annotations, extendedResources, taints + return labels, annotations, extendedResources, taints, ruleSpecs +} + +func findRuleByName(ruleSpecs []*nfdv1alpha1.NodeFeatureRule, name string) *nfdv1alpha1.NodeFeatureRule { + var spec *nfdv1alpha1.NodeFeatureRule + for _, r := range ruleSpecs { + if r.Name == name { + spec = r + break + } + } + return spec +} + +func findStatusRuleByName(status *[]nfdv1alpha1.RuleStatus, name string) *nfdv1alpha1.RuleStatus { + var rule *nfdv1alpha1.RuleStatus + for _, r := range *status { + if r.Name == name { + rule = &r + break + } + } + return rule +} + +func (m *nfdMaster) updateRuleStatus() error { + nodes, err := getNodes(m.k8sClient) + if err != nil { + return err + } + + var ruleSpecs []*nfdv1alpha1.NodeFeatureRule + var outSpecs []*nfdv1alpha1.NodeFeatureRule + + for _, node := range nodes.Items { + nodeFeatures, err := m.getAndMergeNodeFeatures(node.Name) + if err != nil { + return fmt.Errorf("failed to merge NodeFeature objects for node %q: %w", node.Name, err) + } + + _, _, _, _, ruleSpecs = m.processNodeFeatureRule(node.Name, &nodeFeatures.Spec.Features) + } + + for _, spec := range ruleSpecs { + if len(spec.Status.Rules) > 0 { + s := findRuleByName(outSpecs, spec.Name) + if s != nil { + for _, r := range spec.Status.Rules { + s.Status.Rules = append(s.Status.Rules, r) + + sr := findStatusRuleByName(&s.Status.Rules, r.Name) + if sr != nil { + sr.MatchedNodes = append(sr.MatchedNodes, r.MatchedNodes...) + } + } + } else { + outSpecs = append(outSpecs, spec) + } + } + } + + for _, spec := range outSpecs { + _, err = m.nfdClient.NfdV1alpha1().NodeFeatureRules().Update(context.TODO(), spec, metav1.UpdateOptions{}) + if err != nil { + klog.ErrorS(err, "failed to update rule status", "nodefeaturerule", klog.KObj(spec)) + } + } + + return nil } // updateNodeObject ensures the Kubernetes node object is up to date,