Skip to content

Commit 185b406

Browse files
committed
source/custom: add internal rule api
Add internal API for the nfd-worker custom feature source rule configuration. This API has already diverged from the NFD NodeFeatureRule API in that annotations, extended resources or taints are not supported. This patch basically copies the Rule type (and it's sub-types) from the nfdv1alpha1 package. It also adds conversion functions from the internal rule API to the "external" nfdv1alpha1 API. This is done to use the same rule matching functionality (from the nfdv1alpha1 package). One notable remark is that the feature source rule config supports some custom formatting (short forms, multi-type fields) that relies on special json/yaml unmarshalling functions that are better to nuke from the nfdv1alpha1 package (in another patch). These (legacy) syntax specialities are most probably used by nobody but let's keep them as they're already there. Unit tests to cover the custom json unmarshalling are now added.
1 parent 884edc6 commit 185b406

File tree

5 files changed

+864
-0
lines changed

5 files changed

+864
-0
lines changed

source/custom/api/conversion.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
Copyright 2023 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package api
18+
19+
import (
20+
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
21+
)
22+
23+
// convertFeaturematchertermToV1alpha1 converts the internal api type to nfdv1alpha1.
24+
func convertFeaturematchertermToV1alpha1(in *FeatureMatcherTerm, out *nfdv1alpha1.FeatureMatcherTerm) error {
25+
out.Feature = in.Feature
26+
if in.MatchExpressions != nil {
27+
inME := in.MatchExpressions
28+
outME := make(nfdv1alpha1.MatchExpressionSet, len(*inME))
29+
for key := range *inME {
30+
me := &nfdv1alpha1.MatchExpression{}
31+
if err := convertMatchexpressionToV1alpha1((*inME)[key], me); err != nil {
32+
return err
33+
}
34+
outME[key] = me
35+
}
36+
out.MatchExpressions = &outME
37+
} else {
38+
out.MatchExpressions = nil
39+
}
40+
41+
if in.MatchName != nil {
42+
out.MatchName = &nfdv1alpha1.MatchExpression{}
43+
if err := convertMatchexpressionToV1alpha1(in.MatchName, out.MatchName); err != nil {
44+
return err
45+
}
46+
} else {
47+
out.MatchName = nil
48+
}
49+
return nil
50+
}
51+
52+
// convertMatchanyelemToV1alpha1 converts the internal api type to nfdv1alpha1.
53+
func convertMatchanyelemToV1alpha1(in *MatchAnyElem, out *nfdv1alpha1.MatchAnyElem) error {
54+
if in.MatchFeatures != nil {
55+
inMF, outMF := &in.MatchFeatures, &out.MatchFeatures
56+
*outMF = make(nfdv1alpha1.FeatureMatcher, len(*inMF))
57+
for i := range *inMF {
58+
if err := convertFeaturematchertermToV1alpha1(&(*inMF)[i], &(*outMF)[i]); err != nil {
59+
return err
60+
}
61+
}
62+
} else {
63+
out.MatchFeatures = nil
64+
}
65+
return nil
66+
}
67+
68+
// convertMatchexpressionToV1alpha1 converts the internal api type to nfdv1alpha1.
69+
func convertMatchexpressionToV1alpha1(in *MatchExpression, out *nfdv1alpha1.MatchExpression) error {
70+
out.Op = nfdv1alpha1.MatchOp(in.Op)
71+
if in.Value != nil {
72+
in, out := &in.Value, &out.Value
73+
*out = make(nfdv1alpha1.MatchValue, len(*in))
74+
copy(*out, *in)
75+
} else {
76+
out.Value = nil
77+
}
78+
return nil
79+
}
80+
81+
// ConvertRuleToV1alpha1 converts the internal api type to nfdv1alpha1.
82+
func ConvertRuleToV1alpha1(in *Rule, out *nfdv1alpha1.Rule) error {
83+
out.Name = in.Name
84+
out.Labels = in.Labels
85+
out.LabelsTemplate = in.LabelsTemplate
86+
out.Vars = in.Vars
87+
out.VarsTemplate = in.VarsTemplate
88+
if in.MatchFeatures != nil {
89+
in, out := &in.MatchFeatures, &out.MatchFeatures
90+
*out = make(nfdv1alpha1.FeatureMatcher, len(*in))
91+
for i := range *in {
92+
if err := convertFeaturematchertermToV1alpha1(&(*in)[i], &(*out)[i]); err != nil {
93+
return err
94+
}
95+
}
96+
} else {
97+
out.MatchFeatures = nil
98+
}
99+
if in.MatchAny != nil {
100+
in, out := &in.MatchAny, &out.MatchAny
101+
*out = make([]nfdv1alpha1.MatchAnyElem, len(*in))
102+
for i := range *in {
103+
if err := convertMatchanyelemToV1alpha1(&(*in)[i], &(*out)[i]); err != nil {
104+
return err
105+
}
106+
}
107+
} else {
108+
out.MatchAny = nil
109+
}
110+
return nil
111+
}

source/custom/api/conversion_test.go

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
/*
2+
Copyright 2023 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package api
18+
19+
import (
20+
"testing"
21+
22+
"github.com/stretchr/testify/assert"
23+
24+
"sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
25+
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
26+
)
27+
28+
func TestRuleConversion(t *testing.T) {
29+
type TC struct {
30+
name string
31+
internal Rule
32+
external nfdv1alpha1.Rule
33+
}
34+
tcs := []TC{
35+
{
36+
name: "empty rule",
37+
internal: Rule{},
38+
external: nfdv1alpha1.Rule{},
39+
},
40+
{
41+
name: "all fields populated",
42+
internal: Rule{
43+
Name: "test rule 1",
44+
Labels: map[string]string{
45+
"label-1": "val-1",
46+
"label-2": "val-2",
47+
},
48+
LabelsTemplate: "{{ range .fake.attribute }}example.com/fake-{{ .Name }}={{ .Value }}\n{{ end }}",
49+
Vars: map[string]string{
50+
"var-a": "val-a",
51+
"var-b": "val-b",
52+
},
53+
VarsTemplate: "{{ range .fake.attribute }}fake-{{ .Name }}={{ .Value }}\n{{ end }}",
54+
MatchFeatures: FeatureMatcher{
55+
FeatureMatcherTerm{
56+
Feature: "fake.attribute",
57+
MatchExpressions: &MatchExpressionSet{
58+
"attr_1": &MatchExpression{Op: MatchIn, Value: MatchValue{"true"}},
59+
"attr_2": &MatchExpression{Op: MatchInRegexp, Value: MatchValue{"^f"}},
60+
},
61+
MatchName: &MatchExpression{Op: MatchIn, Value: MatchValue{"elem-1"}},
62+
},
63+
},
64+
MatchAny: []MatchAnyElem{
65+
{
66+
MatchFeatures: FeatureMatcher{
67+
FeatureMatcherTerm{
68+
Feature: "fake.instance",
69+
MatchExpressions: &MatchExpressionSet{
70+
"name": &MatchExpression{Op: MatchNotIn, Value: MatchValue{"instance_1"}},
71+
},
72+
MatchName: &MatchExpression{Op: MatchIn, Value: MatchValue{"elem-2"}},
73+
},
74+
},
75+
},
76+
},
77+
},
78+
external: nfdv1alpha1.Rule{
79+
Name: "test rule 1",
80+
Labels: map[string]string{
81+
"label-1": "val-1",
82+
"label-2": "val-2",
83+
},
84+
LabelsTemplate: "{{ range .fake.attribute }}example.com/fake-{{ .Name }}={{ .Value }}\n{{ end }}",
85+
Vars: map[string]string{
86+
"var-a": "val-a",
87+
"var-b": "val-b",
88+
},
89+
VarsTemplate: "{{ range .fake.attribute }}fake-{{ .Name }}={{ .Value }}\n{{ end }}",
90+
MatchFeatures: nfdv1alpha1.FeatureMatcher{
91+
nfdv1alpha1.FeatureMatcherTerm{
92+
Feature: "fake.attribute",
93+
MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
94+
"attr_1": &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchIn, Value: nfdv1alpha1.MatchValue{"true"}},
95+
"attr_2": &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchInRegexp, Value: nfdv1alpha1.MatchValue{"^f"}},
96+
},
97+
MatchName: &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchIn, Value: nfdv1alpha1.MatchValue{"elem-1"}},
98+
},
99+
},
100+
MatchAny: []nfdv1alpha1.MatchAnyElem{
101+
{
102+
MatchFeatures: nfdv1alpha1.FeatureMatcher{
103+
nfdv1alpha1.FeatureMatcherTerm{
104+
Feature: "fake.instance",
105+
MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
106+
"name": &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchNotIn, Value: nfdv1alpha1.MatchValue{"instance_1"}},
107+
},
108+
MatchName: &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchIn, Value: nfdv1alpha1.MatchValue{"elem-2"}},
109+
},
110+
},
111+
},
112+
},
113+
},
114+
},
115+
{
116+
name: "matchName is nil",
117+
internal: Rule{
118+
Name: "test rule 1",
119+
MatchFeatures: FeatureMatcher{
120+
FeatureMatcherTerm{
121+
Feature: "fake.attribute",
122+
MatchExpressions: &MatchExpressionSet{
123+
"attr_1": &MatchExpression{Op: MatchIsTrue},
124+
},
125+
MatchName: nil,
126+
},
127+
},
128+
},
129+
external: nfdv1alpha1.Rule{
130+
Name: "test rule 1",
131+
MatchFeatures: nfdv1alpha1.FeatureMatcher{
132+
nfdv1alpha1.FeatureMatcherTerm{
133+
Feature: "fake.attribute",
134+
MatchExpressions: &nfdv1alpha1.MatchExpressionSet{
135+
"attr_1": &nfdv1alpha1.MatchExpression{Op: nfdv1alpha1.MatchIsTrue},
136+
},
137+
MatchName: nil,
138+
},
139+
},
140+
},
141+
},
142+
{
143+
name: "matchExpressions is empty",
144+
internal: Rule{
145+
Name: "test rule 1",
146+
MatchFeatures: FeatureMatcher{
147+
FeatureMatcherTerm{
148+
Feature: "fake.attribute",
149+
MatchExpressions: &MatchExpressionSet{},
150+
},
151+
},
152+
},
153+
external: nfdv1alpha1.Rule{
154+
Name: "test rule 1",
155+
MatchFeatures: nfdv1alpha1.FeatureMatcher{
156+
nfdv1alpha1.FeatureMatcherTerm{
157+
Feature: "fake.attribute",
158+
MatchExpressions: &nfdv1alpha1.MatchExpressionSet{},
159+
},
160+
},
161+
},
162+
},
163+
{
164+
name: "matchExpressions is nil",
165+
internal: Rule{
166+
Name: "test rule 1",
167+
MatchFeatures: FeatureMatcher{
168+
FeatureMatcherTerm{
169+
Feature: "fake.attribute",
170+
MatchExpressions: nil,
171+
MatchName: &MatchExpression{Op: MatchInRegexp, Value: MatchValue{"^elem-"}},
172+
},
173+
},
174+
},
175+
external: nfdv1alpha1.Rule{
176+
Name: "test rule 1",
177+
MatchFeatures: nfdv1alpha1.FeatureMatcher{
178+
nfdv1alpha1.FeatureMatcherTerm{
179+
Feature: "fake.attribute",
180+
MatchExpressions: nil,
181+
MatchName: &v1alpha1.MatchExpression{Op: v1alpha1.MatchInRegexp, Value: v1alpha1.MatchValue{"^elem-"}},
182+
},
183+
},
184+
},
185+
},
186+
}
187+
188+
for _, tc := range tcs {
189+
t.Run(tc.name, func(t *testing.T) {
190+
out := nfdv1alpha1.Rule{}
191+
err := ConvertRuleToV1alpha1(&tc.internal, &out)
192+
assert.Nil(t, err)
193+
assert.Equal(t, tc.external, out)
194+
})
195+
}
196+
}

0 commit comments

Comments
 (0)