From 078619cf24e89cf7d84cfcd85df48909d78a13e6 Mon Sep 17 00:00:00 2001 From: Oleg Zhurakivskyy Date: Tue, 16 Apr 2024 20:26:34 +0300 Subject: [PATCH] apis/nfd: Add Status to NodeFeature CRD Signed-off-by: Oleg Zhurakivskyy --- .../nfd/v1alpha1/fake/fake_nodefeature.go | 12 ++++++ .../typed/nfd/v1alpha1/nodefeature.go | 17 ++++++++ api/nfd/v1alpha1/types.go | 17 ++++++++ api/nfd/v1alpha1/zz_generated.deepcopy.go | 18 +++++++++ deployment/base/nfd-crds/nfd-api-crds.yaml | 18 +++++++++ .../crds/nfd-api-crds.yaml | 18 +++++++++ pkg/nfd-worker/nfd-worker.go | 40 +++++++++++++------ test/e2e/node_feature_discovery_test.go | 13 ++++++ 8 files changed, 140 insertions(+), 13 deletions(-) diff --git a/api/generated/clientset/versioned/typed/nfd/v1alpha1/fake/fake_nodefeature.go b/api/generated/clientset/versioned/typed/nfd/v1alpha1/fake/fake_nodefeature.go index f956c8217b..48b7212745 100644 --- a/api/generated/clientset/versioned/typed/nfd/v1alpha1/fake/fake_nodefeature.go +++ b/api/generated/clientset/versioned/typed/nfd/v1alpha1/fake/fake_nodefeature.go @@ -101,6 +101,18 @@ func (c *FakeNodeFeatures) Update(ctx context.Context, nodeFeature *v1alpha1.Nod return obj.(*v1alpha1.NodeFeature), 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 *FakeNodeFeatures) UpdateStatus(ctx context.Context, nodeFeature *v1alpha1.NodeFeature, opts v1.UpdateOptions) (*v1alpha1.NodeFeature, error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateSubresourceAction(nodefeaturesResource, "status", c.ns, nodeFeature), &v1alpha1.NodeFeature{}) + + if obj == nil { + return nil, err + } + return obj.(*v1alpha1.NodeFeature), err +} + // Delete takes name of the nodeFeature and deletes it. Returns an error if one occurs. func (c *FakeNodeFeatures) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { _, err := c.Fake. diff --git a/api/generated/clientset/versioned/typed/nfd/v1alpha1/nodefeature.go b/api/generated/clientset/versioned/typed/nfd/v1alpha1/nodefeature.go index f159be8670..f4a317e3d5 100644 --- a/api/generated/clientset/versioned/typed/nfd/v1alpha1/nodefeature.go +++ b/api/generated/clientset/versioned/typed/nfd/v1alpha1/nodefeature.go @@ -40,6 +40,7 @@ type NodeFeaturesGetter interface { type NodeFeatureInterface interface { Create(ctx context.Context, nodeFeature *v1alpha1.NodeFeature, opts v1.CreateOptions) (*v1alpha1.NodeFeature, error) Update(ctx context.Context, nodeFeature *v1alpha1.NodeFeature, opts v1.UpdateOptions) (*v1alpha1.NodeFeature, error) + UpdateStatus(ctx context.Context, nodeFeature *v1alpha1.NodeFeature, opts v1.UpdateOptions) (*v1alpha1.NodeFeature, 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.NodeFeature, error) @@ -135,6 +136,22 @@ func (c *nodeFeatures) Update(ctx context.Context, nodeFeature *v1alpha1.NodeFea return } +// UpdateStatus was generated because the type contains a Status member. +// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). +func (c *nodeFeatures) UpdateStatus(ctx context.Context, nodeFeature *v1alpha1.NodeFeature, opts v1.UpdateOptions) (result *v1alpha1.NodeFeature, err error) { + result = &v1alpha1.NodeFeature{} + err = c.client.Put(). + Namespace(c.ns). + Resource("nodefeatures"). + Name(nodeFeature.Name). + SubResource("status"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(nodeFeature). + Do(ctx). + Into(result) + return +} + // Delete takes name of the nodeFeature and deletes it. Returns an error if one occurs. func (c *nodeFeatures) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { return c.client.Delete(). diff --git a/api/nfd/v1alpha1/types.go b/api/nfd/v1alpha1/types.go index 66122ba97d..03c2a36e59 100644 --- a/api/nfd/v1alpha1/types.go +++ b/api/nfd/v1alpha1/types.go @@ -41,6 +41,7 @@ type NodeFeature struct { metav1.ObjectMeta `json:"metadata,omitempty"` Spec NodeFeatureSpec `json:"spec"` + Status NodeFeatureStatus `json:"status"` } // NodeFeatureSpec describes a NodeFeature object. @@ -53,6 +54,22 @@ type NodeFeatureSpec struct { Labels map[string]string `json:"labels"` } +// Status of a NodeFeature object. +type NodeFeatureStatus struct { + // UTC time when the NodeFeature object was last updated. + // +optional + LastAppliedAt metav1.Time `json:"lastAppliedAt,omitempty"` + // +optional + // Number of features discovered. + NumberOfFeatures int `json:"numberOfFeatures,omitempty"` + // +optional + // Number of errors during last feature discovery. + NumberOfFeatureErrors int `json:"numberOfFeatureErrors,omitempty"` + // +optional + // Number of labels created. + NumberOfLabels int `json:"numberOfLabels,omitempty"` +} + // Features is the collection of all discovered features. // // +protobuf=true diff --git a/api/nfd/v1alpha1/zz_generated.deepcopy.go b/api/nfd/v1alpha1/zz_generated.deepcopy.go index 94748d2ad2..485339f857 100644 --- a/api/nfd/v1alpha1/zz_generated.deepcopy.go +++ b/api/nfd/v1alpha1/zz_generated.deepcopy.go @@ -333,6 +333,7 @@ func (in *NodeFeature) DeepCopyInto(out *NodeFeature) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) return } @@ -494,6 +495,23 @@ func (in *NodeFeatureSpec) DeepCopy() *NodeFeatureSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NodeFeatureStatus) DeepCopyInto(out *NodeFeatureStatus) { + *out = *in + in.LastAppliedAt.DeepCopyInto(&out.LastAppliedAt) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeFeatureStatus. +func (in *NodeFeatureStatus) DeepCopy() *NodeFeatureStatus { + if in == nil { + return nil + } + out := new(NodeFeatureStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Rule) DeepCopyInto(out *Rule) { *out = *in diff --git a/deployment/base/nfd-crds/nfd-api-crds.yaml b/deployment/base/nfd-crds/nfd-api-crds.yaml index f0a24e725e..6b5fb910b1 100644 --- a/deployment/base/nfd-crds/nfd-api-crds.yaml +++ b/deployment/base/nfd-crds/nfd-api-crds.yaml @@ -109,8 +109,26 @@ spec: be created. type: object type: object + status: + description: Status of a NodeFeature object. + properties: + lastAppliedAt: + description: UTC time when the NodeFeature object was last updated. + format: date-time + type: string + numberOfFeatureErrors: + description: Number of errors during last feature discovery. + type: integer + numberOfFeatures: + description: Number of features discovered. + type: integer + numberOfLabels: + description: Number of labels created. + type: integer + type: object required: - spec + - status type: object served: true storage: true 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 f0a24e725e..6b5fb910b1 100644 --- a/deployment/helm/node-feature-discovery/crds/nfd-api-crds.yaml +++ b/deployment/helm/node-feature-discovery/crds/nfd-api-crds.yaml @@ -109,8 +109,26 @@ spec: be created. type: object type: object + status: + description: Status of a NodeFeature object. + properties: + lastAppliedAt: + description: UTC time when the NodeFeature object was last updated. + format: date-time + type: string + numberOfFeatureErrors: + description: Number of errors during last feature discovery. + type: integer + numberOfFeatures: + description: Number of features discovered. + type: integer + numberOfLabels: + description: Number of labels created. + type: integer + type: object required: - spec + - status type: object served: true storage: true diff --git a/pkg/nfd-worker/nfd-worker.go b/pkg/nfd-worker/nfd-worker.go index 3575479123..ac7b688ffe 100644 --- a/pkg/nfd-worker/nfd-worker.go +++ b/pkg/nfd-worker/nfd-worker.go @@ -121,18 +121,19 @@ type ConfigOverrideArgs struct { } type nfdWorker struct { - args Args - certWatch *utils.FsWatcher - clientConn *grpc.ClientConn - configFilePath string - config *NFDConfig - kubernetesNamespace string - grpcClient pb.LabelerClient - healthServer *grpc.Server - nfdClient *nfdclient.Clientset - stop chan struct{} // channel for signaling stop - featureSources []source.FeatureSource - labelSources []source.LabelSource + args Args + certWatch *utils.FsWatcher + clientConn *grpc.ClientConn + configFilePath string + config *NFDConfig + kubernetesNamespace string + grpcClient pb.LabelerClient + healthServer *grpc.Server + nfdClient *nfdclient.Clientset + stop chan struct{} // channel for signaling stop + featureSources []source.FeatureSource + numberOfFeatureSourceErrors int + labelSources []source.LabelSource } // This ticker can represent infinite and normal intervals. @@ -218,9 +219,11 @@ func (w *nfdWorker) startGrpcHealthServer(errChan chan<- error) error { // Run feature discovery. func (w *nfdWorker) runFeatureDiscovery() error { discoveryStart := time.Now() + w.numberOfFeatureSourceErrors = 0 for _, s := range w.featureSources { currentSourceStart := time.Now() if err := s.Discover(); err != nil { + w.numberOfFeatureSourceErrors++ klog.ErrorS(err, "feature discovery failed", "source", s.Name()) } klog.V(3).InfoS("feature discovery completed", "featureSource", s.Name(), "duration", time.Since(currentSourceStart)) @@ -747,6 +750,12 @@ func (m *nfdWorker) updateNodeFeatureObject(labels Labels) error { Labels: labels, }, } + nfr.Status = nfdv1alpha1.NodeFeatureStatus{ + LastAppliedAt: metav1.Time{Time: time.Now().UTC()}, + NumberOfFeatures: len(m.featureSources), + NumberOfFeatureErrors: m.numberOfFeatureSourceErrors, + NumberOfLabels: len(m.labelSources), + } nfrCreated, err := cli.NfdV1alpha1().NodeFeatures(namespace).Create(context.TODO(), nfr, metav1.CreateOptions{}) if err != nil { @@ -765,7 +774,12 @@ func (m *nfdWorker) updateNodeFeatureObject(labels Labels) error { Features: *features, Labels: labels, } - + nfrUpdated.Status = nfdv1alpha1.NodeFeatureStatus{ + LastAppliedAt: metav1.Time{Time: time.Now().UTC()}, + NumberOfFeatures: len(m.featureSources), + NumberOfFeatureErrors: m.numberOfFeatureSourceErrors, + NumberOfLabels: len(m.labelSources), + } if !apiequality.Semantic.DeepEqual(nfr, nfrUpdated) { klog.InfoS("updating NodeFeature object", "nodefeature", klog.KObj(nfr)) nfrUpdated, err = cli.NfdV1alpha1().NodeFeatures(namespace).Update(context.TODO(), nfrUpdated, metav1.UpdateOptions{}) diff --git a/test/e2e/node_feature_discovery_test.go b/test/e2e/node_feature_discovery_test.go index 12ad9a2754..9d20b16703 100644 --- a/test/e2e/node_feature_discovery_test.go +++ b/test/e2e/node_feature_discovery_test.go @@ -500,6 +500,19 @@ var _ = NFDDescribe(Label("nfd-master"), func() { } eventuallyNonControlPlaneNodes(ctx, f.ClientSet).Should(MatchLabels(expectedLabels, nodes)) + By("Verifying the CRD status") + nf, err := nfdClient.NfdV1alpha1().NodeFeatures(f.Namespace.Name).Get(ctx, targetNodeName, metav1.GetOptions{}) + Expect(err).NotTo(HaveOccurred(), "Error getting NodeFeature object: %q", err) + + expectedStatus := nfdv1alpha1.NodeFeatureStatus{ + LastAppliedAt: nf.Status.LastAppliedAt, + NumberOfFeatures: nf.Status.NumberOfFeatures, + NumberOfFeatureErrors: 0, + NumberOfLabels: len(expectedLabels[targetNodeName]) - 1, + } + isEqual := (expectedStatus == nf.Status) + Expect(isEqual).To(BeTrue()) + By("Deleting nfd-worker daemonset") err = f.ClientSet.AppsV1().DaemonSets(f.Namespace.Name).Delete(ctx, workerDS.Name, metav1.DeleteOptions{}) Expect(err).NotTo(HaveOccurred())