Skip to content

Commit e2c7c8f

Browse files
authored
Merge pull request #1417 from ArangoGutierrez/annotations
Discover node features as annotations
2 parents 8a5f302 + c0063be commit e2c7c8f

File tree

12 files changed

+207
-30
lines changed

12 files changed

+207
-30
lines changed

deployment/base/nfd-crds/nfd-api-crds.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,11 @@ spec:
153153
description: Rule defines a rule for node customization such as
154154
labeling.
155155
properties:
156+
annotations:
157+
additionalProperties:
158+
type: string
159+
description: Annotations to create if the rule matches.
160+
type: object
156161
extendedResources:
157162
additionalProperties:
158163
type: string

deployment/helm/node-feature-discovery/crds/nfd-api-crds.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,11 @@ spec:
153153
description: Rule defines a rule for node customization such as
154154
labeling.
155155
properties:
156+
annotations:
157+
additionalProperties:
158+
type: string
159+
description: Annotations to create if the rule matches.
160+
type: object
156161
extendedResources:
157162
additionalProperties:
158163
type: string

docs/get-started/introduction.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ sort: 1
1818
This software enables node feature discovery for Kubernetes. It detects
1919
hardware features available on each node in a Kubernetes cluster, and
2020
advertises those features using node labels and optionally node extended
21-
resources and node taints. Node Feature Discovery is compatible with any recent
22-
version of Kubernetes (v1.21+).
21+
resources, annotations and node taints. Node Feature Discovery is compatible
22+
with any recent version of Kubernetes (v1.21+).
2323

2424
NFD consists of four software components:
2525

docs/usage/customization-guide.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,54 @@ details.
594594
> labels specified in the `labels` field will override anything
595595
> originating from `labelsTemplate`.
596596

597+
#### Node Annotations
598+
599+
The `.annotations` field is a list of features to be advertised as annotations.
600+
601+
Take this rule as a referential example:
602+
603+
```yaml
604+
apiVersion: nfd.k8s-sigs.io/v1alpha1
605+
kind: NodeFeatureRule
606+
metadata:
607+
name: feature-annotations-example
608+
spec:
609+
rules:
610+
- name: "annotation-example"
611+
annotations:
612+
defaul-ns-annotation: "foo"
613+
feature.node.kubernetes.io/defaul-ns-annotation-2: "bar"
614+
custom.vendor.io/feature: "baz"
615+
matchFeatures:
616+
- feature: kernel.version
617+
matchExpressions:
618+
major: {op: Exists}
619+
```
620+
621+
This will yield into the following node annotations:
622+
623+
```yaml
624+
annotations:
625+
...
626+
feature.node.kubernetes.io/defaul-ns-annotation: "foo"
627+
feature.node.kubernetes.io/defaul-ns-annotation-2: "bar"
628+
custom.vendor.io/feature: "baz"
629+
...
630+
```
631+
632+
NFD enforces some limitations to the namespace (or prefix)/ of the annotations:
633+
634+
- `kubernetes.io/` and its sub-namespaces (like `sub.ns.kubernetes.io/`) cannot
635+
generally be used
636+
- the only exception is `feature.node.kubernetes.io/` and its sub-namespaces
637+
(like `sub.ns.feature.node.kubernetes.io`)
638+
- unprefixed names will get prefixed with `feature.node.kubernetes.io/`
639+
automatically (e.g. `foo` becomes `feature.node.kubernetes.io/foo`)
640+
641+
> **NOTE:** The `annotations` field has will only advertise features via node
642+
> annotations the features won't be advertised as node labels unless they are
643+
> specified in the `labels` field.
644+
597645
#### Taints
598646

599647
*taints* is a list of taint entries and each entry can have `key`, `value` and `effect`,

pkg/apis/nfd/v1alpha1/annotations_labels.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,18 @@ const (
6060
// NodeTaintsAnnotation is the annotation that holds the taints that nfd-master set on the node
6161
NodeTaintsAnnotation = AnnotationNs + "/taints"
6262

63+
// FeatureAnnotationsTrackingAnnotation is the annotation that holds all feature annotations that nfd-master set on the node
64+
FeatureAnnotationsTrackingAnnotation = AnnotationNs + "/feature-annotations"
65+
6366
// NodeFeatureObjNodeNameLabel is the label that specifies which node the
6467
// NodeFeature object is targeting. Creators of NodeFeature objects must
6568
// set this label and consumers of the objects are supposed to use the
6669
// label for filtering features designated for a certain node.
6770
NodeFeatureObjNodeNameLabel = "nfd.node.kubernetes.io/node-name"
71+
72+
// FeatureAnnotationNs is the (default) namespace for feature annotations.
73+
FeatureAnnotationNs = "feature.node.kubernetes.io"
74+
75+
// FeatureAnnotationSubNsSuffix is the suffix for allowed feature annotation sub-namespaces.
76+
FeatureAnnotationSubNsSuffix = "." + FeatureAnnotationNs
6877
)

pkg/apis/nfd/v1alpha1/rule.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424

2525
corev1 "k8s.io/api/core/v1"
2626
"k8s.io/klog/v2"
27+
2728
"sigs.k8s.io/node-feature-discovery/pkg/utils"
2829
)
2930

@@ -32,6 +33,7 @@ import (
3233
type RuleOutput struct {
3334
ExtendedResources map[string]string
3435
Labels map[string]string
36+
Annotations map[string]string
3537
Vars map[string]string
3638
Taints []corev1.Taint
3739
}
@@ -101,7 +103,7 @@ func (r *Rule) Execute(features *Features) (RuleOutput, error) {
101103
vars[k] = v
102104
}
103105

104-
ret := RuleOutput{ExtendedResources: extendedResources, Labels: labels, Vars: vars, Taints: r.Taints}
106+
ret := RuleOutput{ExtendedResources: extendedResources, Labels: labels, Vars: vars, Taints: r.Taints, Annotations: r.Annotations}
105107
klog.V(2).InfoS("rule matched", "ruleName", r.Name, "ruleOutput", utils.DelayedDumper(ret))
106108
return ret, nil
107109
}

pkg/apis/nfd/v1alpha1/types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ type Rule struct {
146146
// +optional
147147
LabelsTemplate string `json:"labelsTemplate"`
148148

149+
// Annotations to create if the rule matches.
150+
// +optional
151+
Annotations map[string]string `json:"annotations"`
152+
149153
// Vars is the variables to store if the rule matches. Variables do not
150154
// directly inflict any changes in the node object. However, they can be
151155
// referenced from other rules enabling more complex rule hierarchies,

pkg/apis/nfd/v1alpha1/zz_generated.deepcopy.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/nfd-master/nfd-master-internal_test.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,12 @@ func TestUpdateNodeObject(t *testing.T) {
138138
}
139139
sort.Strings(fakeFeatureLabelNames)
140140

141+
fakeFeatureAnnotationsNames := make([]string, 0, len(fakeFeatureLabels))
142+
for k := range fakeAnnotations {
143+
fakeFeatureAnnotationsNames = append(fakeFeatureAnnotationsNames, strings.TrimPrefix(k, nfdv1alpha1.FeatureAnnotationNs+"/"))
144+
}
145+
sort.Strings(fakeFeatureAnnotationsNames)
146+
141147
fakeExtResourceNames := make([]string, 0, len(fakeExtResources))
142148
for k := range fakeExtResources {
143149
fakeExtResourceNames = append(fakeExtResourceNames, strings.TrimPrefix(k, nfdv1alpha1.FeatureLabelNs+"/"))
@@ -162,6 +168,7 @@ func TestUpdateNodeObject(t *testing.T) {
162168
// Create a list of expected node metadata patches
163169
metadataPatches := []apihelper.JsonPatch{
164170
apihelper.NewJsonPatch("replace", "/metadata/annotations", nfdv1alpha1.AnnotationNs+"/feature-labels", strings.Join(fakeFeatureLabelNames, ",")),
171+
apihelper.NewJsonPatch("add", "/metadata/annotations", nfdv1alpha1.FeatureAnnotationsTrackingAnnotation, strings.Join(fakeFeatureAnnotationsNames, ",")),
165172
apihelper.NewJsonPatch("add", "/metadata/annotations", nfdv1alpha1.AnnotationNs+"/extended-resources", strings.Join(fakeExtResourceNames, ",")),
166173
apihelper.NewJsonPatch("remove", "/metadata/labels", nfdv1alpha1.FeatureLabelNs+"/old-feature", ""),
167174
}
@@ -176,7 +183,7 @@ func TestUpdateNodeObject(t *testing.T) {
176183
mockAPIHelper.On("GetNode", mockClient, mockNodeName).Return(mockNode, nil).Twice()
177184
mockAPIHelper.On("PatchNodeStatus", mockClient, mockNodeName, mock.MatchedBy(jsonPatchMatcher(statusPatches))).Return(nil)
178185
mockAPIHelper.On("PatchNode", mockClient, mockNodeName, mock.MatchedBy(jsonPatchMatcher(metadataPatches))).Return(nil)
179-
err := mockMaster.updateNodeObject(mockClient, mockNodeName, fakeFeatureLabels, fakeAnnotations, fakeExtResources, nil)
186+
err := mockMaster.updateNodeObject(mockClient, mockNodeName, fakeFeatureLabels, Annotations{}, fakeAnnotations, fakeExtResources, nil)
180187

181188
Convey("Error is nil", func() {
182189
So(err, ShouldBeNil)
@@ -186,7 +193,7 @@ func TestUpdateNodeObject(t *testing.T) {
186193
Convey("When I fail to update the node with feature labels", func() {
187194
expectedError := fmt.Errorf("no client is passed, client: <nil>")
188195
mockAPIHelper.On("GetClient").Return(nil, expectedError)
189-
err := mockMaster.updateNodeObject(nil, mockNodeName, fakeFeatureLabels, fakeAnnotations, fakeExtResources, nil)
196+
err := mockMaster.updateNodeObject(nil, mockNodeName, fakeFeatureLabels, Annotations{}, fakeAnnotations, fakeExtResources, nil)
190197

191198
Convey("Error is produced", func() {
192199
So(err, ShouldResemble, expectedError)
@@ -196,7 +203,7 @@ func TestUpdateNodeObject(t *testing.T) {
196203
Convey("When I fail to get a mock client while updating feature labels", func() {
197204
expectedError := fmt.Errorf("no client is passed, client: <nil>")
198205
mockAPIHelper.On("GetClient").Return(nil, expectedError)
199-
err := mockMaster.updateNodeObject(nil, mockNodeName, fakeFeatureLabels, fakeAnnotations, fakeExtResources, nil)
206+
err := mockMaster.updateNodeObject(nil, mockNodeName, fakeFeatureLabels, Annotations{}, fakeAnnotations, fakeExtResources, nil)
200207

201208
Convey("Error is produced", func() {
202209
So(err, ShouldResemble, expectedError)
@@ -207,7 +214,7 @@ func TestUpdateNodeObject(t *testing.T) {
207214
expectedError := errors.New("fake error")
208215
mockAPIHelper.On("GetClient").Return(mockClient, nil)
209216
mockAPIHelper.On("GetNode", mockClient, mockNodeName).Return(nil, expectedError).Twice()
210-
err := mockMaster.updateNodeObject(mockClient, mockNodeName, fakeFeatureLabels, fakeAnnotations, fakeExtResources, nil)
217+
err := mockMaster.updateNodeObject(mockClient, mockNodeName, fakeFeatureLabels, Annotations{}, fakeAnnotations, fakeExtResources, nil)
211218

212219
Convey("Error is produced", func() {
213220
So(err, ShouldEqual, expectedError)
@@ -220,7 +227,7 @@ func TestUpdateNodeObject(t *testing.T) {
220227
mockAPIHelper.On("GetNode", mockClient, mockNodeName).Return(mockNode, nil).Twice()
221228
mockAPIHelper.On("PatchNodeStatus", mockClient, mockNodeName, mock.MatchedBy(jsonPatchMatcher(statusPatches))).Return(nil)
222229
mockAPIHelper.On("PatchNode", mockClient, mockNodeName, mock.Anything).Return(expectedError).Twice()
223-
err := mockMaster.updateNodeObject(mockClient, mockNodeName, fakeFeatureLabels, fakeAnnotations, fakeExtResources, nil)
230+
err := mockMaster.updateNodeObject(mockClient, mockNodeName, fakeFeatureLabels, Annotations{}, fakeAnnotations, fakeExtResources, nil)
224231

225232
Convey("Error is produced", func() {
226233
So(err.Error(), ShouldEndWith, expectedError.Error())

0 commit comments

Comments
 (0)