mirror of
https://github.com/kubernetes-sigs/node-feature-discovery.git
synced 2024-12-15 17:50:49 +00:00
Merge pull request #1633 from marquiz/devel/validate-tests
apis/nfd/validate: loosen validation of feature annotations
This commit is contained in:
commit
624c02e1e2
3 changed files with 408 additions and 10 deletions
|
@ -74,4 +74,7 @@ const (
|
||||||
|
|
||||||
// FeatureAnnotationSubNsSuffix is the suffix for allowed feature annotation sub-namespaces.
|
// FeatureAnnotationSubNsSuffix is the suffix for allowed feature annotation sub-namespaces.
|
||||||
FeatureAnnotationSubNsSuffix = "." + FeatureAnnotationNs
|
FeatureAnnotationSubNsSuffix = "." + FeatureAnnotationNs
|
||||||
|
|
||||||
|
// FeatureAnnotationValueSizeLimit is the maximum allowed length for the value of a feature annotation.
|
||||||
|
FeatureAnnotationValueSizeLimit = 1 << 10
|
||||||
)
|
)
|
||||||
|
|
|
@ -57,7 +57,7 @@ func MatchFeatures(matchFeature nfdv1alpha1.FeatureMatcher) []error {
|
||||||
var validationErr []error
|
var validationErr []error
|
||||||
|
|
||||||
for _, match := range matchFeature {
|
for _, match := range matchFeature {
|
||||||
nameSplit := strings.SplitN(match.Feature, ".", 2)
|
nameSplit := strings.Split(match.Feature, ".")
|
||||||
if len(nameSplit) != 2 {
|
if len(nameSplit) != 2 {
|
||||||
validationErr = append(validationErr, fmt.Errorf("invalid feature name %v (not <domain>.<feature>), cannot be used for templating", match.Feature))
|
validationErr = append(validationErr, fmt.Errorf("invalid feature name %v (not <domain>.<feature>), cannot be used for templating", match.Feature))
|
||||||
}
|
}
|
||||||
|
@ -155,8 +155,8 @@ func Annotation(key, value string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate annotation value
|
// Validate annotation value
|
||||||
if errs := k8svalidation.IsValidLabelValue(value); len(errs) > 0 {
|
if len(value) > nfdv1alpha1.FeatureAnnotationValueSizeLimit {
|
||||||
return fmt.Errorf("invalid value %q: %s", value, strings.Join(errs, "; "))
|
return fmt.Errorf("invalid value: too long: feature annotations must not be longer than %d characters", nfdv1alpha1.FeatureAnnotationValueSizeLimit)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -1,10 +1,28 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 The Kubernetes Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
package validate
|
package validate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/api/nfd/v1alpha1"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAnnotation(t *testing.T) {
|
func TestAnnotation(t *testing.T) {
|
||||||
|
@ -22,15 +40,21 @@ func TestAnnotation(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Invalid annotation key",
|
name: "Invalid annotation key",
|
||||||
key: "invalid-key",
|
key: "_invalid-key_",
|
||||||
|
value: "true",
|
||||||
|
want: "invalid annotation key \"_invalid-key_\":",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Denied annotation key",
|
||||||
|
key: "denied-key",
|
||||||
value: "true",
|
value: "true",
|
||||||
want: ErrUnprefixedKeysNotAllowed,
|
want: ErrUnprefixedKeysNotAllowed,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Invalid annotation value",
|
name: "Invalid annotation value",
|
||||||
key: "feature.node.kubernetes.io/feature",
|
key: "feature.node.kubernetes.io/feature",
|
||||||
value: "invalid value",
|
value: string(make([]byte, 1100)),
|
||||||
want: "invalid value \"invalid value\": ",
|
want: "invalid value: too long:",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Denied annotation key",
|
name: "Denied annotation key",
|
||||||
|
@ -52,6 +76,49 @@ func TestAnnotation(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAnnotations(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
annotations map[string]string
|
||||||
|
want []error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Empty annotations",
|
||||||
|
annotations: map[string]string{},
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid annotations",
|
||||||
|
annotations: map[string]string{
|
||||||
|
"feature.node.kubernetes.io/annotation": "true",
|
||||||
|
"vendor.io/annotation": "true",
|
||||||
|
},
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid annotations",
|
||||||
|
annotations: map[string]string{
|
||||||
|
"invalid-key": "true",
|
||||||
|
"kubernetes.io/annotation": "true",
|
||||||
|
},
|
||||||
|
want: []error{
|
||||||
|
ErrUnprefixedKeysNotAllowed,
|
||||||
|
ErrNSNotAllowed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
errs := sortErrors(Annotations(tt.annotations))
|
||||||
|
assert.Equal(t, len(tt.want), len(errs))
|
||||||
|
for i := range errs {
|
||||||
|
assert.ErrorIs(t, errs[i], tt.want[i])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestTaint(t *testing.T) {
|
func TestTaint(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -113,6 +180,71 @@ func TestTaint(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTaints(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
taints []corev1.Taint
|
||||||
|
want []error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Empty taints",
|
||||||
|
taints: []corev1.Taint{},
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid taints",
|
||||||
|
taints: []corev1.Taint{
|
||||||
|
{
|
||||||
|
Key: "feature.node.kubernetes.io/taint",
|
||||||
|
Value: "true",
|
||||||
|
Effect: corev1.TaintEffectNoSchedule,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "vendor.io/taint",
|
||||||
|
Value: "true",
|
||||||
|
Effect: corev1.TaintEffectNoExecute,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid taints",
|
||||||
|
taints: []corev1.Taint{
|
||||||
|
{
|
||||||
|
Key: "invalid-key",
|
||||||
|
Value: "true",
|
||||||
|
Effect: corev1.TaintEffectNoSchedule,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "feature.node.kubernetes.io/taint",
|
||||||
|
Value: "true",
|
||||||
|
Effect: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "feature.node.kubernetes.io/taint",
|
||||||
|
Value: "true",
|
||||||
|
Effect: "invalid-effect",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: []error{
|
||||||
|
ErrUnprefixedKeysNotAllowed,
|
||||||
|
ErrEmptyTaintEffect,
|
||||||
|
ErrInvalidTaintEffect,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
errs := Taints(tt.taints)
|
||||||
|
assert.Equal(t, len(tt.want), len(errs))
|
||||||
|
for i := range errs {
|
||||||
|
assert.ErrorIs(t, errs[i], tt.want[i])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestLabel(t *testing.T) {
|
func TestLabel(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -132,6 +264,12 @@ func TestLabel(t *testing.T) {
|
||||||
value: "true",
|
value: "true",
|
||||||
want: nil,
|
want: nil,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid label key",
|
||||||
|
key: "invalid-key:",
|
||||||
|
value: "true",
|
||||||
|
want: "invalid label key \"invalid-key:\": ",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Denied label with prefix",
|
name: "Denied label with prefix",
|
||||||
key: "kubernetes.io/label",
|
key: "kubernetes.io/label",
|
||||||
|
@ -139,8 +277,8 @@ func TestLabel(t *testing.T) {
|
||||||
want: ErrNSNotAllowed,
|
want: ErrNSNotAllowed,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Invalid label key",
|
name: "Denied label key unprefixed",
|
||||||
key: "invalid-key",
|
key: "denied-key",
|
||||||
value: "true",
|
value: "true",
|
||||||
want: ErrUnprefixedKeysNotAllowed,
|
want: ErrUnprefixedKeysNotAllowed,
|
||||||
},
|
},
|
||||||
|
@ -170,6 +308,49 @@ func TestLabel(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLabels(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
labels map[string]string
|
||||||
|
want []error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Empty labels",
|
||||||
|
labels: map[string]string{},
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid labels",
|
||||||
|
labels: map[string]string{
|
||||||
|
"feature.node.kubernetes.io/label": "true",
|
||||||
|
"vendor.io/label": "true",
|
||||||
|
},
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid labels",
|
||||||
|
labels: map[string]string{
|
||||||
|
"invalid-key": "true",
|
||||||
|
"kubernetes.io/label": "true",
|
||||||
|
},
|
||||||
|
want: []error{
|
||||||
|
ErrUnprefixedKeysNotAllowed,
|
||||||
|
ErrNSNotAllowed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
err := sortErrors(Labels(tt.labels))
|
||||||
|
assert.Equal(t, len(tt.want), len(err))
|
||||||
|
for i := range err {
|
||||||
|
assert.ErrorIs(t, err[i], tt.want[i])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestExtendedResource(t *testing.T) {
|
func TestExtendedResource(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -184,8 +365,14 @@ func TestExtendedResource(t *testing.T) {
|
||||||
want: nil,
|
want: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Invalid extended resource key",
|
name: "Invalid extended resource name",
|
||||||
key: "invalid-key",
|
key: "invalid-name~",
|
||||||
|
value: "123",
|
||||||
|
want: "invalid name \"invalid-name~\": ",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Denied extended resource key",
|
||||||
|
key: "denied-key",
|
||||||
value: "123",
|
value: "123",
|
||||||
want: ErrUnprefixedKeysNotAllowed,
|
want: ErrUnprefixedKeysNotAllowed,
|
||||||
},
|
},
|
||||||
|
@ -214,3 +401,211 @@ func TestExtendedResource(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExtendedResources(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
extendedResources map[string]string
|
||||||
|
want []error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Empty extended resources",
|
||||||
|
extendedResources: map[string]string{},
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid extended resources",
|
||||||
|
extendedResources: map[string]string{
|
||||||
|
"feature.node.kubernetes.io/extended-resource": "123",
|
||||||
|
"vendor.io/extended-resource": "456",
|
||||||
|
},
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid extended resources",
|
||||||
|
extendedResources: map[string]string{
|
||||||
|
"invalid-key": "456",
|
||||||
|
"kubernetes.io/extended-resource": "123",
|
||||||
|
},
|
||||||
|
want: []error{
|
||||||
|
ErrUnprefixedKeysNotAllowed,
|
||||||
|
ErrNSNotAllowed,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
errs := sortErrors(ExtendedResources(tt.extendedResources))
|
||||||
|
assert.Equal(t, len(tt.want), len(errs))
|
||||||
|
for i := range errs {
|
||||||
|
assert.ErrorIs(t, errs[i], tt.want[i])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMatchFeatures(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
matchFeature nfdv1alpha1.FeatureMatcher
|
||||||
|
expectedErrors []error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Empty matchFeature",
|
||||||
|
matchFeature: nfdv1alpha1.FeatureMatcher{},
|
||||||
|
expectedErrors: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid matchFeature",
|
||||||
|
matchFeature: nfdv1alpha1.FeatureMatcher{
|
||||||
|
{
|
||||||
|
Feature: "domain1.feature1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Feature: "domain2.feature2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErrors: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid matchFeature",
|
||||||
|
matchFeature: nfdv1alpha1.FeatureMatcher{
|
||||||
|
{
|
||||||
|
Feature: "invalid-feature",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Feature: "domain3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Feature: "prefix.domain.feature",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErrors: []error{
|
||||||
|
fmt.Errorf("invalid feature name invalid-feature (not <domain>.<feature>), cannot be used for templating"),
|
||||||
|
fmt.Errorf("invalid feature name domain3 (not <domain>.<feature>), cannot be used for templating"),
|
||||||
|
fmt.Errorf("invalid feature name prefix.domain.feature (not <domain>.<feature>), cannot be used for templating"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
errors := MatchFeatures(tt.matchFeature)
|
||||||
|
assert.Equal(t, tt.expectedErrors, errors)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMatchAny(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
matchAny []nfdv1alpha1.MatchAnyElem
|
||||||
|
expectedErrors []error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Empty matchAny",
|
||||||
|
matchAny: []nfdv1alpha1.MatchAnyElem{},
|
||||||
|
expectedErrors: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Valid matchAny",
|
||||||
|
matchAny: []nfdv1alpha1.MatchAnyElem{
|
||||||
|
{
|
||||||
|
MatchFeatures: nfdv1alpha1.FeatureMatcher{
|
||||||
|
{
|
||||||
|
Feature: "domain1.feature1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Feature: "domain2.feature2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MatchFeatures: nfdv1alpha1.FeatureMatcher{
|
||||||
|
{
|
||||||
|
Feature: "domain3.feature3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Feature: "domain4.feature4",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErrors: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid matchAny",
|
||||||
|
matchAny: []nfdv1alpha1.MatchAnyElem{
|
||||||
|
{
|
||||||
|
MatchFeatures: nfdv1alpha1.FeatureMatcher{
|
||||||
|
{
|
||||||
|
Feature: "invalid-feature",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Feature: "domain3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MatchFeatures: nfdv1alpha1.FeatureMatcher{
|
||||||
|
{
|
||||||
|
Feature: "domain5.feature5",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Feature: "invalid.domain.feature6",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErrors: []error{
|
||||||
|
fmt.Errorf("invalid feature name invalid-feature (not <domain>.<feature>), cannot be used for templating"),
|
||||||
|
fmt.Errorf("invalid feature name domain3 (not <domain>.<feature>), cannot be used for templating"),
|
||||||
|
fmt.Errorf("invalid feature name invalid.domain.feature6 (not <domain>.<feature>), cannot be used for templating"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
errors := MatchAny(tt.matchAny)
|
||||||
|
assert.Equal(t, tt.expectedErrors, errors)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTemplate(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
labelsTemplate string
|
||||||
|
want []error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Valid template",
|
||||||
|
labelsTemplate: "key1=value1,key2=value2",
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid template",
|
||||||
|
labelsTemplate: "{{.key1=value1,key2=value2}}",
|
||||||
|
want: []error{fmt.Errorf("invalid template: template: :1: bad character U+003D '='")},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
errs := Template(tt.labelsTemplate)
|
||||||
|
assert.Equal(t, len(tt.want), len(errs))
|
||||||
|
for i := range errs {
|
||||||
|
assert.EqualError(t, errs[i], tt.want[i].Error())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortErrors(errs []error) []error {
|
||||||
|
sort.Slice(errs, func(i, j int) bool {
|
||||||
|
return errs[i].Error() < errs[j].Error()
|
||||||
|
})
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue