mirror of
https://github.com/kubernetes-sigs/node-feature-discovery.git
synced 2025-03-05 16:27:05 +00:00
Extend NFR code with MatchStatus and introduce failFast strategy.
MatchStatus provides details about successful expressions and their results, which are the matched host features. Additionally, a new flag controls rule processing behavior: it can either stop at the first error or continue processing all expressions and rules. Signed-off-by: Marcin Franczyk <marcin0franczyk@gmail.com>
This commit is contained in:
parent
0188aade60
commit
51bbbe202d
9 changed files with 291 additions and 126 deletions
48
api/image-compatibility/v1alpha1/spec.go
Normal file
48
api/image-compatibility/v1alpha1/spec.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
Copyright 2024 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 v1alpha1
|
||||
|
||||
import (
|
||||
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/api/nfd/v1alpha1"
|
||||
)
|
||||
|
||||
// ArtifactType is a type of OCI artifact that contains image compatibility metadata.
|
||||
const (
|
||||
ArtifactType = "application/vnd.nfd.image-compatibility.v1alpha1"
|
||||
Version = "v1alpha1"
|
||||
)
|
||||
|
||||
// Spec represents image compatibility metadata.
|
||||
type Spec struct {
|
||||
// Version of the spec.
|
||||
Version string `json:"version"`
|
||||
// Compatibilities contains list of compatibility sets.
|
||||
Compatibilties []Compatibility `json:"compatibilities"`
|
||||
}
|
||||
|
||||
// Compatibility represents image compatibility metadata
|
||||
// that describe the image requirements for the host and OS.
|
||||
type Compatibility struct {
|
||||
// Rules represents a list of Node Feature Rules.
|
||||
Rules []nfdv1alpha1.Rule `json:"rules"`
|
||||
// Weight indicates the priority of the compatibility set.
|
||||
Weight int `json:"weight,omitempty"`
|
||||
// Tag enables grouping or distinguishing between compatibility sets.
|
||||
Tag string `json:"tag,omitempty"`
|
||||
// Description of the compatibility set.
|
||||
Description string `json:"description,omitempty"`
|
||||
}
|
|
@ -17,6 +17,8 @@ limitations under the License.
|
|||
package v1alpha1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
@ -298,6 +300,13 @@ type MatchExpression struct {
|
|||
Value MatchValue `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
func (m MatchExpression) String() string {
|
||||
if len(m.Value) < 1 {
|
||||
return fmt.Sprintf("{op: %q}", m.Op)
|
||||
}
|
||||
return fmt.Sprintf("{op: %q, value: %q}", m.Op, m.Value)
|
||||
}
|
||||
|
||||
// MatchOp is the match operator that is applied on values when evaluating a
|
||||
// MatchExpression.
|
||||
// +kubebuilder:validation:Enum="In";"NotIn";"InRegexp";"Exists";"DoesNotExist";"Gt";"Lt";"GtLt";"IsTrue";"IsFalse"
|
||||
|
|
|
@ -116,7 +116,7 @@ bar: { op: Exists }
|
|||
t.Fatal("failed to parse data of test case")
|
||||
}
|
||||
|
||||
res, out, err := api.MatchGetKeys(mes, tc.input)
|
||||
res, out, _, err := api.MatchGetKeys(mes, tc.input)
|
||||
tc.result(t, res)
|
||||
assert.Equal(t, tc.output, out)
|
||||
tc.err(t, err)
|
||||
|
@ -183,12 +183,12 @@ baz: { op: Gt, value: ["10"] }
|
|||
t.Fatal("failed to parse data of test case")
|
||||
}
|
||||
|
||||
res, out, err := api.MatchGetValues(mes, tc.input)
|
||||
res, out, _, err := api.MatchGetValues(mes, tc.input, true)
|
||||
tc.result(t, res)
|
||||
assert.Equal(t, tc.output, out)
|
||||
tc.err(t, err)
|
||||
|
||||
res, err = api.MatchValues(mes, tc.input)
|
||||
res, _, err = api.MatchValues(mes, tc.input, true)
|
||||
tc.result(t, res)
|
||||
tc.err(t, err)
|
||||
})
|
||||
|
@ -248,12 +248,12 @@ bar: { op: Lt, value: ["10"] }
|
|||
t.Fatal("failed to parse data of test case")
|
||||
}
|
||||
|
||||
res, out, err := api.MatchGetInstances(mes, tc.input)
|
||||
res, out, _, err := api.MatchGetInstances(mes, tc.input, true)
|
||||
assert.Equal(t, tc.output, out)
|
||||
tc.result(t, res)
|
||||
tc.err(t, err)
|
||||
|
||||
res, err = api.MatchInstances(mes, tc.input)
|
||||
res, err = api.MatchInstances(mes, tc.input, true)
|
||||
tc.result(t, res)
|
||||
tc.err(t, err)
|
||||
})
|
||||
|
@ -745,7 +745,7 @@ func TestMatchMulti(t *testing.T) {
|
|||
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
res, out, err := api.MatchMulti(tc.mes, tc.inputKeys, tc.inputValues, tc.inputInstances)
|
||||
res, out, _, err := api.MatchMulti(tc.mes, tc.inputKeys, tc.inputValues, tc.inputInstances, true)
|
||||
if tc.expectErr {
|
||||
assert.NotNil(t, err)
|
||||
} else {
|
||||
|
|
|
@ -29,6 +29,13 @@ import (
|
|||
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/api/nfd/v1alpha1"
|
||||
)
|
||||
|
||||
const (
|
||||
// MatchedKeyName is the name of the matched flag/attribute element.
|
||||
MatchedKeyName = "Name"
|
||||
// MatchedKeyValue is the value of the matched attribute element.
|
||||
MatchedKeyValue = "Value"
|
||||
)
|
||||
|
||||
var matchOps = map[nfdv1alpha1.MatchOp]struct{}{
|
||||
nfdv1alpha1.MatchAny: {},
|
||||
nfdv1alpha1.MatchIn: {},
|
||||
|
@ -202,16 +209,16 @@ func MatchKeyNames(m *nfdv1alpha1.MatchExpression, keys map[string]nfdv1alpha1.N
|
|||
if match, err := evaluateMatchExpression(m, true, k); err != nil {
|
||||
return false, nil, err
|
||||
} else if match {
|
||||
ret = append(ret, MatchedElement{"Name": k})
|
||||
ret = append(ret, MatchedElement{MatchedKeyName: k})
|
||||
}
|
||||
}
|
||||
// Sort for reproducible output
|
||||
sort.Slice(ret, func(i, j int) bool { return ret[i]["Name"] < ret[j]["Name"] })
|
||||
sort.Slice(ret, func(i, j int) bool { return ret[i][MatchedKeyName] < ret[j][MatchedKeyName] })
|
||||
|
||||
if klogV3 := klog.V(3); klogV3.Enabled() {
|
||||
mk := make([]string, len(ret))
|
||||
for i, v := range ret {
|
||||
mk[i] = v["Name"]
|
||||
mk[i] = v[MatchedKeyName]
|
||||
}
|
||||
mkMsg := strings.Join(mk, ", ")
|
||||
|
||||
|
@ -238,16 +245,16 @@ func MatchValueNames(m *nfdv1alpha1.MatchExpression, values map[string]string) (
|
|||
if match, err := evaluateMatchExpression(m, true, k); err != nil {
|
||||
return false, nil, err
|
||||
} else if match {
|
||||
ret = append(ret, MatchedElement{"Name": k, "Value": v})
|
||||
ret = append(ret, MatchedElement{MatchedKeyName: k, MatchedKeyValue: v})
|
||||
}
|
||||
}
|
||||
// Sort for reproducible output
|
||||
sort.Slice(ret, func(i, j int) bool { return ret[i]["Name"] < ret[j]["Name"] })
|
||||
sort.Slice(ret, func(i, j int) bool { return ret[i][MatchedKeyName] < ret[j][MatchedKeyName] })
|
||||
|
||||
if klogV3 := klog.V(3); klogV3.Enabled() {
|
||||
mk := make([]string, len(ret))
|
||||
for i, v := range ret {
|
||||
mk[i] = v["Name"]
|
||||
mk[i] = v[MatchedKeyName]
|
||||
}
|
||||
mkMsg := strings.Join(mk, ", ")
|
||||
|
||||
|
@ -278,7 +285,7 @@ func MatchInstanceAttributeNames(m *nfdv1alpha1.MatchExpression, instances []nfd
|
|||
|
||||
// MatchKeys evaluates the MatchExpressionSet against a set of keys.
|
||||
func MatchKeys(m *nfdv1alpha1.MatchExpressionSet, keys map[string]nfdv1alpha1.Nil) (bool, error) {
|
||||
matched, _, err := MatchGetKeys(m, keys)
|
||||
matched, _, _, err := MatchGetKeys(m, keys)
|
||||
return matched, err
|
||||
}
|
||||
|
||||
|
@ -288,56 +295,65 @@ type MatchedElement map[string]string
|
|||
// MatchGetKeys evaluates the MatchExpressionSet against a set of keys and
|
||||
// returns all matched keys or nil if no match was found. Note that an empty
|
||||
// MatchExpressionSet returns a match with an empty slice of matched features.
|
||||
func MatchGetKeys(m *nfdv1alpha1.MatchExpressionSet, keys map[string]nfdv1alpha1.Nil) (bool, []MatchedElement, error) {
|
||||
ret := make([]MatchedElement, 0, len(*m))
|
||||
func MatchGetKeys(m *nfdv1alpha1.MatchExpressionSet, keys map[string]nfdv1alpha1.Nil) (bool, []MatchedElement, *nfdv1alpha1.MatchExpressionSet, error) {
|
||||
matchedElements := make([]MatchedElement, 0, len(*m))
|
||||
matchedExpressions := make(nfdv1alpha1.MatchExpressionSet)
|
||||
|
||||
for n, e := range *m {
|
||||
match, err := evaluateMatchExpressionKeys(e, n, keys)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
return false, nil, nil, err
|
||||
}
|
||||
if !match {
|
||||
return false, nil, nil
|
||||
return false, nil, nil, nil
|
||||
}
|
||||
ret = append(ret, MatchedElement{"Name": n})
|
||||
matchedElements = append(matchedElements, MatchedElement{MatchedKeyName: n})
|
||||
matchedExpressions[n] = e
|
||||
}
|
||||
// Sort for reproducible output
|
||||
sort.Slice(ret, func(i, j int) bool { return ret[i]["Name"] < ret[j]["Name"] })
|
||||
return true, ret, nil
|
||||
sort.Slice(matchedElements, func(i, j int) bool { return matchedElements[i][MatchedKeyName] < matchedElements[j][MatchedKeyName] })
|
||||
return true, matchedElements, &matchedExpressions, nil
|
||||
}
|
||||
|
||||
// MatchValues evaluates the MatchExpressionSet against a set of key-value pairs.
|
||||
func MatchValues(m *nfdv1alpha1.MatchExpressionSet, values map[string]string) (bool, error) {
|
||||
matched, _, err := MatchGetValues(m, values)
|
||||
return matched, err
|
||||
func MatchValues(m *nfdv1alpha1.MatchExpressionSet, values map[string]string, failFast bool) (bool, *nfdv1alpha1.MatchExpressionSet, error) {
|
||||
matched, _, matchedExpressions, err := MatchGetValues(m, values, failFast)
|
||||
return matched, matchedExpressions, err
|
||||
}
|
||||
|
||||
// MatchGetValues evaluates the MatchExpressionSet against a set of key-value
|
||||
// pairs and returns all matched key-value pairs. Note that an empty
|
||||
// MatchExpressionSet returns a match with an empty slice of matched features.
|
||||
func MatchGetValues(m *nfdv1alpha1.MatchExpressionSet, values map[string]string) (bool, []MatchedElement, error) {
|
||||
ret := make([]MatchedElement, 0, len(*m))
|
||||
func MatchGetValues(m *nfdv1alpha1.MatchExpressionSet, values map[string]string, failFast bool) (bool, []MatchedElement, *nfdv1alpha1.MatchExpressionSet, error) {
|
||||
matchedElements := make([]MatchedElement, 0, len(*m))
|
||||
matchedExpressions := make(nfdv1alpha1.MatchExpressionSet)
|
||||
isMatch := true
|
||||
|
||||
for n, e := range *m {
|
||||
match, err := evaluateMatchExpressionValues(e, n, values)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
return false, nil, nil, err
|
||||
}
|
||||
if !match {
|
||||
return false, nil, nil
|
||||
if match {
|
||||
matchedElements = append(matchedElements, MatchedElement{MatchedKeyName: n, MatchedKeyValue: values[n]})
|
||||
matchedExpressions[n] = e
|
||||
} else {
|
||||
if failFast {
|
||||
return false, nil, nil, nil
|
||||
}
|
||||
isMatch = false
|
||||
}
|
||||
ret = append(ret, MatchedElement{"Name": n, "Value": values[n]})
|
||||
}
|
||||
// Sort for reproducible output
|
||||
sort.Slice(ret, func(i, j int) bool { return ret[i]["Name"] < ret[j]["Name"] })
|
||||
return true, ret, nil
|
||||
sort.Slice(matchedElements, func(i, j int) bool { return matchedElements[i][MatchedKeyName] < matchedElements[j][MatchedKeyName] })
|
||||
return isMatch, matchedElements, &matchedExpressions, nil
|
||||
}
|
||||
|
||||
// MatchInstances evaluates the MatchExpressionSet against a set of instance
|
||||
// features, each of which is an individual set of key-value pairs
|
||||
// (attributes).
|
||||
func MatchInstances(m *nfdv1alpha1.MatchExpressionSet, instances []nfdv1alpha1.InstanceFeature) (bool, error) {
|
||||
isMatch, _, err := MatchGetInstances(m, instances)
|
||||
func MatchInstances(m *nfdv1alpha1.MatchExpressionSet, instances []nfdv1alpha1.InstanceFeature, failFast bool) (bool, error) {
|
||||
isMatch, _, _, err := MatchGetInstances(m, instances, failFast)
|
||||
return isMatch, err
|
||||
}
|
||||
|
||||
|
@ -346,17 +362,28 @@ func MatchInstances(m *nfdv1alpha1.MatchExpressionSet, instances []nfdv1alpha1.I
|
|||
// (attributes). Returns a boolean that reports whether the expression matched.
|
||||
// Also, returns a slice containing all matching instances. An empty (non-nil)
|
||||
// slice is returned if no matching instances were found.
|
||||
func MatchGetInstances(m *nfdv1alpha1.MatchExpressionSet, instances []nfdv1alpha1.InstanceFeature) (bool, []MatchedElement, error) {
|
||||
ret := []MatchedElement{}
|
||||
func MatchGetInstances(m *nfdv1alpha1.MatchExpressionSet, instances []nfdv1alpha1.InstanceFeature, failFast bool) (bool, []MatchedElement, *nfdv1alpha1.MatchExpressionSet, error) {
|
||||
var (
|
||||
match bool
|
||||
err error
|
||||
expressionSet *nfdv1alpha1.MatchExpressionSet
|
||||
)
|
||||
matchedElements := []MatchedElement{}
|
||||
matchedExpressions := &nfdv1alpha1.MatchExpressionSet{}
|
||||
|
||||
for _, i := range instances {
|
||||
if match, err := MatchValues(m, i.Attributes); err != nil {
|
||||
return false, nil, err
|
||||
if match, expressionSet, err = MatchValues(m, i.Attributes, failFast); err != nil {
|
||||
return false, nil, nil, err
|
||||
} else if match {
|
||||
ret = append(ret, i.Attributes)
|
||||
matchedElements = append(matchedElements, i.Attributes)
|
||||
}
|
||||
if expressionSet != nil {
|
||||
for name, exp := range *expressionSet {
|
||||
(*matchedExpressions)[name] = exp
|
||||
}
|
||||
}
|
||||
}
|
||||
return len(ret) > 0, ret, nil
|
||||
return len(matchedElements) > 0, matchedElements, matchedExpressions, nil
|
||||
}
|
||||
|
||||
// MatchMulti evaluates a MatchExpressionSet against key, value and instance
|
||||
|
@ -365,8 +392,9 @@ func MatchGetInstances(m *nfdv1alpha1.MatchExpressionSet, instances []nfdv1alpha
|
|||
// handled separately as the way of evaluating match expressions is different.
|
||||
// This function is written to handle "multi-type" features where one feature
|
||||
// (say "cpu.cpuid") contains multiple types (flag, attribute and/or instance).
|
||||
func MatchMulti(m *nfdv1alpha1.MatchExpressionSet, keys map[string]nfdv1alpha1.Nil, values map[string]string, instances []nfdv1alpha1.InstanceFeature) (bool, []MatchedElement, error) {
|
||||
func MatchMulti(m *nfdv1alpha1.MatchExpressionSet, keys map[string]nfdv1alpha1.Nil, values map[string]string, instances []nfdv1alpha1.InstanceFeature, failFast bool) (bool, []MatchedElement, *nfdv1alpha1.MatchExpressionSet, error) {
|
||||
matchedElems := []MatchedElement{}
|
||||
matchedExpressions := nfdv1alpha1.MatchExpressionSet{}
|
||||
isMatch := false
|
||||
|
||||
// Keys and values are handled as a union, it is enough to find a match in
|
||||
|
@ -384,14 +412,19 @@ func MatchMulti(m *nfdv1alpha1.MatchExpressionSet, keys map[string]nfdv1alpha1.N
|
|||
if keys != nil {
|
||||
matchK, err = evaluateMatchExpressionKeys(e, n, keys)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
return false, nil, nil, err
|
||||
}
|
||||
if matchK {
|
||||
matchedElems = append(matchedElems, MatchedElement{"Name": n})
|
||||
matchedElems = append(matchedElems, MatchedElement{MatchedKeyName: n})
|
||||
matchedExpressions[n] = e
|
||||
} else if e.Op == nfdv1alpha1.MatchDoesNotExist {
|
||||
// DoesNotExist is special in that both "keys" and "values" should match (i.e. the name is not found in either of them).
|
||||
isMatch = false
|
||||
if !failFast {
|
||||
continue
|
||||
}
|
||||
matchedElems = []MatchedElement{}
|
||||
matchedExpressions = nfdv1alpha1.MatchExpressionSet{}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -399,39 +432,53 @@ func MatchMulti(m *nfdv1alpha1.MatchExpressionSet, keys map[string]nfdv1alpha1.N
|
|||
if values != nil {
|
||||
matchV, err = evaluateMatchExpressionValues(e, n, values)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
return false, nil, nil, err
|
||||
}
|
||||
if matchV {
|
||||
matchedElems = append(matchedElems, MatchedElement{"Name": n, "Value": values[n]})
|
||||
matchedElems = append(matchedElems, MatchedElement{MatchedKeyName: n, MatchedKeyValue: values[n]})
|
||||
matchedExpressions[n] = e
|
||||
} else if e.Op == nfdv1alpha1.MatchDoesNotExist {
|
||||
// DoesNotExist is special in that both "keys" and "values" should match (i.e. the name is not found in either of them).
|
||||
isMatch = false
|
||||
if !failFast {
|
||||
continue
|
||||
}
|
||||
matchedElems = []MatchedElement{}
|
||||
matchedExpressions = nfdv1alpha1.MatchExpressionSet{}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !matchK && !matchV {
|
||||
isMatch = false
|
||||
if !failFast {
|
||||
continue
|
||||
}
|
||||
matchedElems = []MatchedElement{}
|
||||
matchedExpressions = nfdv1alpha1.MatchExpressionSet{}
|
||||
break
|
||||
}
|
||||
}
|
||||
// Sort for reproducible output
|
||||
sort.Slice(matchedElems, func(i, j int) bool { return matchedElems[i]["Name"] < matchedElems[j]["Name"] })
|
||||
sort.Slice(matchedElems, func(i, j int) bool { return matchedElems[i][MatchedKeyName] < matchedElems[j][MatchedKeyName] })
|
||||
|
||||
// Instances are handled separately as the logic is fundamentally different
|
||||
// from keys and values and cannot be combined with them. We want to find
|
||||
// instance(s) that match all match expressions. I.e. the set of all match
|
||||
// expressions are evaluated against every instance separately.
|
||||
ma, me, err := MatchGetInstances(m, instances)
|
||||
ma, melems, mexps, err := MatchGetInstances(m, instances, failFast)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
return false, nil, nil, err
|
||||
}
|
||||
isMatch = isMatch || ma
|
||||
matchedElems = append(matchedElems, me...)
|
||||
matchedElems = append(matchedElems, melems...)
|
||||
if mexps != nil {
|
||||
for k, v := range *mexps {
|
||||
matchedExpressions[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return isMatch, matchedElems, nil
|
||||
return isMatch, matchedElems, &matchedExpressions, nil
|
||||
}
|
||||
|
||||
// MatchNamesMulti evaluates the MatchExpression against the names of key,
|
||||
|
@ -440,12 +487,11 @@ func MatchMulti(m *nfdv1alpha1.MatchExpressionSet, keys map[string]nfdv1alpha1.N
|
|||
// types (flag, attribute and/or instance).
|
||||
func MatchNamesMulti(m *nfdv1alpha1.MatchExpression, keys map[string]nfdv1alpha1.Nil, values map[string]string, instances []nfdv1alpha1.InstanceFeature) (bool, []MatchedElement, error) {
|
||||
ret := []MatchedElement{}
|
||||
|
||||
for k := range keys {
|
||||
if match, err := evaluateMatchExpression(m, true, k); err != nil {
|
||||
return false, nil, err
|
||||
} else if match {
|
||||
ret = append(ret, MatchedElement{"Name": k})
|
||||
ret = append(ret, MatchedElement{MatchedKeyName: k})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -453,12 +499,12 @@ func MatchNamesMulti(m *nfdv1alpha1.MatchExpression, keys map[string]nfdv1alpha1
|
|||
if match, err := evaluateMatchExpression(m, true, k); err != nil {
|
||||
return false, nil, err
|
||||
} else if match {
|
||||
ret = append(ret, MatchedElement{"Name": k, "Value": v})
|
||||
ret = append(ret, MatchedElement{MatchedKeyName: k, MatchedKeyValue: v})
|
||||
}
|
||||
}
|
||||
|
||||
// Sort for reproducible output
|
||||
sort.Slice(ret, func(i, j int) bool { return ret[i]["Name"] < ret[j]["Name"] })
|
||||
sort.Slice(ret, func(i, j int) bool { return ret[i][MatchedKeyName] < ret[j][MatchedKeyName] })
|
||||
|
||||
_, me, err := MatchInstanceAttributeNames(m, instances)
|
||||
if err != nil {
|
||||
|
@ -469,7 +515,7 @@ func MatchNamesMulti(m *nfdv1alpha1.MatchExpression, keys map[string]nfdv1alpha1
|
|||
if klogV3 := klog.V(3); klogV3.Enabled() {
|
||||
mk := make([]string, len(ret))
|
||||
for i, v := range ret {
|
||||
mk[i] = v["Name"]
|
||||
mk[i] = v[MatchedKeyName]
|
||||
}
|
||||
mkMsg := strings.Join(mk, ", ")
|
||||
|
||||
|
|
|
@ -31,6 +31,31 @@ import (
|
|||
"sigs.k8s.io/node-feature-discovery/pkg/utils"
|
||||
)
|
||||
|
||||
// MatchStatus represents the status of a processed rule.
|
||||
// It includes information about successful expressions and their results, which are the matched host features.
|
||||
// For example, for the expression: cpu.cpuid: {op: "InRegexp", value: ["^AVX"]},
|
||||
// the result could include matched host features such as AVX, AVX2, AVX512 etc.
|
||||
// +k8s:deepcopy-gen=false
|
||||
type MatchStatus struct {
|
||||
*MatchFeatureStatus
|
||||
|
||||
// IsMatch informes whether a rule succeeded or failed.
|
||||
IsMatch bool
|
||||
// MatchAny represents an array of logical OR conditions between MatchFeatureStatus entries.
|
||||
MatchAny []*MatchFeatureStatus
|
||||
}
|
||||
|
||||
// MatchFeatureStatus represents a matched expression
|
||||
// with its result, which is matched host features.
|
||||
// +k8s:deepcopy-gen=false
|
||||
type MatchFeatureStatus struct {
|
||||
// MatchedFeatures represents the features matched on the host,
|
||||
// which is a result of the FeatureMatcher.
|
||||
MatchedFeatures matchedFeatures
|
||||
// MatchedFeaturesTerms represents the expressions that successfully matched on the host.
|
||||
MatchedFeaturesTerms nfdv1alpha1.FeatureMatcher
|
||||
}
|
||||
|
||||
// RuleOutput contains the output out rule execution.
|
||||
// +k8s:deepcopy-gen=false
|
||||
type RuleOutput struct {
|
||||
|
@ -39,56 +64,69 @@ type RuleOutput struct {
|
|||
Annotations map[string]string
|
||||
Vars map[string]string
|
||||
Taints []corev1.Taint
|
||||
MatchStatus *MatchStatus
|
||||
}
|
||||
|
||||
// Execute the rule against a set of input features.
|
||||
func Execute(r *nfdv1alpha1.Rule, features *nfdv1alpha1.Features) (RuleOutput, error) {
|
||||
func Execute(r *nfdv1alpha1.Rule, features *nfdv1alpha1.Features, failFast bool) (RuleOutput, error) {
|
||||
var (
|
||||
matchStatus MatchStatus
|
||||
isMatch bool
|
||||
err error
|
||||
)
|
||||
labels := make(map[string]string)
|
||||
vars := make(map[string]string)
|
||||
|
||||
if len(r.MatchAny) > 0 {
|
||||
if n := len(r.MatchAny); n > 0 {
|
||||
matchStatus.MatchAny = make([]*MatchFeatureStatus, 0, n)
|
||||
// Logical OR over the matchAny matchers
|
||||
matched := false
|
||||
var (
|
||||
featureStatus *MatchFeatureStatus
|
||||
matched bool
|
||||
)
|
||||
for _, matcher := range r.MatchAny {
|
||||
if isMatch, matches, err := evaluateMatchAnyElem(&matcher, features); err != nil {
|
||||
if matched, featureStatus, err = evaluateMatchAnyElem(&matcher, features, failFast); err != nil {
|
||||
return RuleOutput{}, err
|
||||
} else if isMatch {
|
||||
matched = true
|
||||
klog.V(4).InfoS("matchAny matched", "ruleName", r.Name, "matchedFeatures", utils.DelayedDumper(matches))
|
||||
} else if matched {
|
||||
isMatch = true
|
||||
klog.V(4).InfoS("matchAny matched", "ruleName", r.Name, "matchedFeatures", utils.DelayedDumper(featureStatus.MatchedFeatures))
|
||||
|
||||
if r.LabelsTemplate == "" && r.VarsTemplate == "" {
|
||||
if r.LabelsTemplate == "" && r.VarsTemplate == "" && failFast {
|
||||
// there's no need to evaluate other matchers in MatchAny
|
||||
// if there are no templates to be executed on them - so
|
||||
// short-circuit and stop on first match here
|
||||
break
|
||||
}
|
||||
|
||||
if err := executeLabelsTemplate(r, matches, labels); err != nil {
|
||||
if err := executeLabelsTemplate(r, featureStatus.MatchedFeatures, labels); err != nil {
|
||||
return RuleOutput{}, err
|
||||
}
|
||||
if err := executeVarsTemplate(r, matches, vars); err != nil {
|
||||
if err := executeVarsTemplate(r, featureStatus.MatchedFeatures, vars); err != nil {
|
||||
return RuleOutput{}, err
|
||||
}
|
||||
}
|
||||
|
||||
matchStatus.MatchAny = append(matchStatus.MatchAny, featureStatus)
|
||||
}
|
||||
if !matched {
|
||||
|
||||
if !isMatch {
|
||||
klog.V(2).InfoS("rule did not match", "ruleName", r.Name)
|
||||
return RuleOutput{}, nil
|
||||
return RuleOutput{MatchStatus: &matchStatus}, nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(r.MatchFeatures) > 0 {
|
||||
if isMatch, matches, err := evaluateFeatureMatcher(&r.MatchFeatures, features); err != nil {
|
||||
if isMatch, matchStatus.MatchFeatureStatus, err = evaluateFeatureMatcher(&r.MatchFeatures, features, failFast); err != nil {
|
||||
return RuleOutput{}, err
|
||||
} else if !isMatch {
|
||||
klog.V(2).InfoS("rule did not match", "ruleName", r.Name)
|
||||
return RuleOutput{}, nil
|
||||
return RuleOutput{MatchStatus: &matchStatus}, nil
|
||||
} else {
|
||||
klog.V(4).InfoS("matchFeatures matched", "ruleName", r.Name, "matchedFeatures", utils.DelayedDumper(matches))
|
||||
if err := executeLabelsTemplate(r, matches, labels); err != nil {
|
||||
klog.V(4).InfoS("matchFeatures matched", "ruleName", r.Name, "matchedFeatures", utils.DelayedDumper(matchStatus.MatchedFeatures))
|
||||
if err := executeLabelsTemplate(r, matchStatus.MatchedFeatures, labels); err != nil {
|
||||
return RuleOutput{}, err
|
||||
}
|
||||
if err := executeVarsTemplate(r, matches, vars); err != nil {
|
||||
if err := executeVarsTemplate(r, matchStatus.MatchedFeatures, vars); err != nil {
|
||||
return RuleOutput{}, err
|
||||
}
|
||||
}
|
||||
|
@ -96,6 +134,7 @@ func Execute(r *nfdv1alpha1.Rule, features *nfdv1alpha1.Features) (RuleOutput, e
|
|||
|
||||
maps.Copy(labels, r.Labels)
|
||||
maps.Copy(vars, r.Vars)
|
||||
matchStatus.IsMatch = true
|
||||
|
||||
ret := RuleOutput{
|
||||
Labels: labels,
|
||||
|
@ -103,6 +142,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),
|
||||
MatchStatus: &matchStatus,
|
||||
}
|
||||
klog.V(2).InfoS("rule matched", "ruleName", r.Name, "ruleOutput", utils.DelayedDumper(ret))
|
||||
return ret, nil
|
||||
|
@ -110,12 +150,12 @@ func Execute(r *nfdv1alpha1.Rule, features *nfdv1alpha1.Features) (RuleOutput, e
|
|||
|
||||
// ExecuteGroupRule executes the GroupRule against a set of input features, and return true if the
|
||||
// rule matches.
|
||||
func ExecuteGroupRule(r *nfdv1alpha1.GroupRule, features *nfdv1alpha1.Features) (bool, error) {
|
||||
func ExecuteGroupRule(r *nfdv1alpha1.GroupRule, features *nfdv1alpha1.Features, failFast bool) (bool, error) {
|
||||
matched := false
|
||||
if len(r.MatchAny) > 0 {
|
||||
// Logical OR over the matchAny matchers
|
||||
for _, matcher := range r.MatchAny {
|
||||
if isMatch, matches, err := evaluateMatchAnyElem(&matcher, features); err != nil {
|
||||
if isMatch, matches, err := evaluateMatchAnyElem(&matcher, features, failFast); err != nil {
|
||||
return false, err
|
||||
} else if isMatch {
|
||||
matched = true
|
||||
|
@ -131,7 +171,7 @@ func ExecuteGroupRule(r *nfdv1alpha1.GroupRule, features *nfdv1alpha1.Features)
|
|||
}
|
||||
|
||||
if len(r.MatchFeatures) > 0 {
|
||||
if isMatch, _, err := evaluateFeatureMatcher(&r.MatchFeatures, features); err != nil {
|
||||
if isMatch, _, err := evaluateFeatureMatcher(&r.MatchFeatures, features, failFast); err != nil {
|
||||
return false, err
|
||||
} else if !isMatch {
|
||||
klog.V(2).InfoS("rule did not match", "ruleName", r.Name)
|
||||
|
@ -187,12 +227,18 @@ type matchedFeatures map[string]domainMatchedFeatures
|
|||
|
||||
type domainMatchedFeatures map[string][]MatchedElement
|
||||
|
||||
func evaluateMatchAnyElem(e *nfdv1alpha1.MatchAnyElem, features *nfdv1alpha1.Features) (bool, matchedFeatures, error) {
|
||||
return evaluateFeatureMatcher(&e.MatchFeatures, features)
|
||||
func evaluateMatchAnyElem(e *nfdv1alpha1.MatchAnyElem, features *nfdv1alpha1.Features, failFast bool) (bool, *MatchFeatureStatus, error) {
|
||||
return evaluateFeatureMatcher(&e.MatchFeatures, features, failFast)
|
||||
}
|
||||
|
||||
func evaluateFeatureMatcher(m *nfdv1alpha1.FeatureMatcher, features *nfdv1alpha1.Features) (bool, matchedFeatures, error) {
|
||||
matches := make(matchedFeatures, len(*m))
|
||||
func evaluateFeatureMatcher(m *nfdv1alpha1.FeatureMatcher, features *nfdv1alpha1.Features, failFast bool) (bool, *MatchFeatureStatus, error) {
|
||||
var (
|
||||
isMatch = true
|
||||
isTermMatch = true
|
||||
)
|
||||
status := &MatchFeatureStatus{
|
||||
MatchedFeatures: make(matchedFeatures, len(*m)),
|
||||
}
|
||||
|
||||
// Logical AND over the terms
|
||||
for _, term := range *m {
|
||||
|
@ -207,14 +253,17 @@ func evaluateFeatureMatcher(m *nfdv1alpha1.FeatureMatcher, features *nfdv1alpha1
|
|||
|
||||
dom := nameSplit[0]
|
||||
nam := nameSplit[1]
|
||||
if _, ok := matches[dom]; !ok {
|
||||
matches[dom] = make(domainMatchedFeatures)
|
||||
if _, ok := status.MatchedFeatures[dom]; !ok {
|
||||
status.MatchedFeatures[dom] = make(domainMatchedFeatures)
|
||||
}
|
||||
|
||||
var isMatch = true
|
||||
var matchedElems []MatchedElement
|
||||
var matchedExpressions *nfdv1alpha1.MatchExpressionSet
|
||||
var err error
|
||||
|
||||
matchedFeatureTerm := nfdv1alpha1.FeatureMatcherTerm{
|
||||
Feature: featureName,
|
||||
}
|
||||
fF, okF := features.Flags[featureName]
|
||||
fA, okA := features.Attributes[featureName]
|
||||
fI, okI := features.Instances[featureName]
|
||||
|
@ -223,24 +272,37 @@ func evaluateFeatureMatcher(m *nfdv1alpha1.FeatureMatcher, features *nfdv1alpha1
|
|||
}
|
||||
|
||||
if term.MatchExpressions != nil {
|
||||
isMatch, matchedElems, err = MatchMulti(term.MatchExpressions, fF.Elements, fA.Elements, fI.Elements)
|
||||
isTermMatch, matchedElems, matchedExpressions, err = MatchMulti(term.MatchExpressions, fF.Elements, fA.Elements, fI.Elements, failFast)
|
||||
matchedFeatureTerm.MatchExpressions = matchedExpressions
|
||||
}
|
||||
|
||||
if err == nil && isMatch && term.MatchName != nil {
|
||||
if err == nil && isTermMatch && term.MatchName != nil {
|
||||
var meTmp []MatchedElement
|
||||
isMatch, meTmp, err = MatchNamesMulti(term.MatchName, fF.Elements, fA.Elements, fI.Elements)
|
||||
isTermMatch, meTmp, err = MatchNamesMulti(term.MatchName, fF.Elements, fA.Elements, fI.Elements)
|
||||
matchedElems = append(matchedElems, meTmp...)
|
||||
// MatchName has only one expression, in this case it's enough to check the isTermMatch flag
|
||||
// to judge if the expression succeeded on the host.
|
||||
if isTermMatch {
|
||||
matchedFeatureTerm.MatchName = term.MatchName
|
||||
}
|
||||
}
|
||||
|
||||
matches[dom][nam] = append(matches[dom][nam], matchedElems...)
|
||||
status.MatchedFeatures[dom][nam] = append(status.MatchedFeatures[dom][nam], matchedElems...)
|
||||
if matchedFeatureTerm.MatchName != nil || (matchedFeatureTerm.MatchExpressions != nil && len(*matchedFeatureTerm.MatchExpressions) > 0) {
|
||||
status.MatchedFeaturesTerms = append(status.MatchedFeaturesTerms, matchedFeatureTerm)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
} else if !isMatch {
|
||||
return false, nil, nil
|
||||
} else if !isTermMatch {
|
||||
if !failFast {
|
||||
isMatch = false
|
||||
} else {
|
||||
return false, status, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return true, matches, nil
|
||||
return isMatch, status, nil
|
||||
}
|
||||
|
||||
type templateHelper struct {
|
||||
|
|
|
@ -49,22 +49,22 @@ func TestRule(t *testing.T) {
|
|||
}
|
||||
|
||||
// Test totally empty features
|
||||
m, err := Execute(r1, f)
|
||||
m, err := Execute(r1, f, true)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, r1.Labels, m.Labels, "empty matcher should have matched empty features")
|
||||
|
||||
_, err = Execute(r2, f)
|
||||
_, err = Execute(r2, f, true)
|
||||
assert.Error(t, err, "matching against a missing feature should have returned an error")
|
||||
|
||||
// Test properly initialized empty features
|
||||
f = nfdv1alpha1.NewFeatures()
|
||||
|
||||
m, err = Execute(r1, f)
|
||||
m, err = Execute(r1, f, true)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, r1.Labels, m.Labels, "empty matcher should have matched empty features")
|
||||
assert.Empty(t, r1.Vars, "vars should be empty")
|
||||
|
||||
_, err = Execute(r2, f)
|
||||
_, err = Execute(r2, f, true)
|
||||
assert.Error(t, err, "matching against a missing feature type should have returned an error")
|
||||
|
||||
// Test empty feature sets
|
||||
|
@ -72,11 +72,11 @@ func TestRule(t *testing.T) {
|
|||
f.Attributes["domain-1.vf-1"] = nfdv1alpha1.NewAttributeFeatures(nil)
|
||||
f.Instances["domain-1.if-1"] = nfdv1alpha1.NewInstanceFeatures()
|
||||
|
||||
m, err = Execute(r1, f)
|
||||
m, err = Execute(r1, f, true)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, r1.Labels, m.Labels, "empty matcher should have matched empty features")
|
||||
|
||||
m, err = Execute(r2, f)
|
||||
m, err = Execute(r2, f, true)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Nil(t, m.Labels, "unexpected match")
|
||||
|
||||
|
@ -85,7 +85,7 @@ func TestRule(t *testing.T) {
|
|||
f.Attributes["domain-1.vf-1"].Elements["key-1"] = "val-x"
|
||||
f.Instances["domain-1.if-1"] = nfdv1alpha1.NewInstanceFeatures(*nfdv1alpha1.NewInstanceFeature(map[string]string{"attr-1": "val-x"}))
|
||||
|
||||
m, err = Execute(r1, f)
|
||||
m, err = Execute(r1, f, true)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, r1.Labels, m.Labels, "empty matcher should have matched empty features")
|
||||
|
||||
|
@ -96,17 +96,17 @@ func TestRule(t *testing.T) {
|
|||
MatchExpressions: &nfdv1alpha1.MatchExpressionSet{},
|
||||
},
|
||||
}
|
||||
m, err = Execute(r1, f)
|
||||
m, err = Execute(r1, f, true)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, r1.Labels, m.Labels, "empty match expression set mathces anything")
|
||||
|
||||
// Match "key" features
|
||||
m, err = Execute(r2, f)
|
||||
m, err = Execute(r2, f, true)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Nil(t, m.Labels, "keys should not have matched")
|
||||
|
||||
f.Flags["domain-1.kf-1"].Elements["key-1"] = nfdv1alpha1.Nil{}
|
||||
m, err = Execute(r2, f)
|
||||
m, err = Execute(r2, f, true)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, r2.Labels, m.Labels, "keys should have matched")
|
||||
assert.Equal(t, r2.Vars, m.Vars, "vars should be present")
|
||||
|
@ -123,12 +123,12 @@ func TestRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}
|
||||
m, err = Execute(r3, f)
|
||||
m, err = Execute(r3, f, true)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Nil(t, m.Labels, "values should not have matched")
|
||||
|
||||
f.Attributes["domain-1.vf-1"].Elements["key-1"] = "val-1"
|
||||
m, err = Execute(r3, f)
|
||||
m, err = Execute(r3, f, true)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, r3.Labels, m.Labels, "values should have matched")
|
||||
|
||||
|
@ -144,12 +144,12 @@ func TestRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}
|
||||
m, err = Execute(r3, f)
|
||||
m, err = Execute(r3, f, true)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Nil(t, m.Labels, "instances should not have matched")
|
||||
|
||||
f.Instances["domain-1.if-1"].Elements[0].Attributes["attr-1"] = "val-1"
|
||||
m, err = Execute(r3, f)
|
||||
m, err = Execute(r3, f, true)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, r3.Labels, m.Labels, "instances should have matched")
|
||||
|
||||
|
@ -173,7 +173,7 @@ func TestRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}
|
||||
m, err = Execute(r3, f2)
|
||||
m, err = Execute(r3, f2, true)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, r3.Labels, m.Labels, "key in multi-type feature should have matched")
|
||||
|
||||
|
@ -185,7 +185,7 @@ func TestRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}
|
||||
m, err = Execute(r3, f2)
|
||||
m, err = Execute(r3, f2, true)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, r3.Labels, m.Labels, "attribute in multi-type feature should have matched")
|
||||
|
||||
|
@ -197,7 +197,7 @@ func TestRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}
|
||||
m, err = Execute(r3, f2)
|
||||
m, err = Execute(r3, f2, true)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, r3.Labels, m.Labels, "attribute in multi-type feature should have matched")
|
||||
|
||||
|
@ -210,7 +210,7 @@ func TestRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}
|
||||
m, err = Execute(r3, f2)
|
||||
m, err = Execute(r3, f2, true)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, r3.Labels, m.Labels, "features in multi-type feature should have matched flags and attributes")
|
||||
|
||||
|
@ -222,7 +222,7 @@ func TestRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}
|
||||
m, err = Execute(r3, f2)
|
||||
m, err = Execute(r3, f2, true)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, r3.Labels, m.Labels, "features in multi-type feature should have matched instance")
|
||||
|
||||
|
@ -244,12 +244,12 @@ func TestRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}
|
||||
m, err = Execute(r3, f)
|
||||
m, err = Execute(r3, f, true)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Nil(t, m.Labels, "instances should not have matched")
|
||||
|
||||
(*r3.MatchFeatures[0].MatchExpressions)["key-1"] = newMatchExpression(nfdv1alpha1.MatchIn, "val-1")
|
||||
m, err = Execute(r3, f)
|
||||
m, err = Execute(r3, f, true)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, r3.Labels, m.Labels, "instances should have matched")
|
||||
|
||||
|
@ -266,7 +266,7 @@ func TestRule(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}
|
||||
m, err = Execute(r3, f)
|
||||
m, err = Execute(r3, f, true)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Nil(t, m.Labels, "instances should not have matched")
|
||||
|
||||
|
@ -282,7 +282,7 @@ func TestRule(t *testing.T) {
|
|||
},
|
||||
})
|
||||
(*r3.MatchFeatures[0].MatchExpressions)["key-1"] = newMatchExpression(nfdv1alpha1.MatchIn, "val-1")
|
||||
m, err = Execute(r3, f)
|
||||
m, err = Execute(r3, f, true)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, r3.Labels, m.Labels, "instances should have matched")
|
||||
}
|
||||
|
@ -433,12 +433,12 @@ var-2=
|
|||
"kf-foo": "true",
|
||||
}
|
||||
|
||||
m, err := Execute(r1, f)
|
||||
m, err := Execute(r1, f, true)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, expectedLabels, m.Labels, "instances should have matched")
|
||||
assert.Equal(t, expectedVars, m.Vars, "instances should have matched")
|
||||
|
||||
m, err = Execute(r3, f)
|
||||
m, err = Execute(r3, f, true)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, expectedLabels, m.Labels, "instances should have matched")
|
||||
assert.Equal(t, expectedVars, m.Vars, "instances should have matched")
|
||||
|
@ -464,7 +464,7 @@ var-2=
|
|||
"mf-key-d": "found",
|
||||
}
|
||||
expectedVars = map[string]string{}
|
||||
m, err = Execute(r3, f)
|
||||
m, err = Execute(r3, f, true)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, expectedLabels, m.Labels, "instances should have matched")
|
||||
assert.Equal(t, expectedVars, m.Vars, "instances should have matched")
|
||||
|
@ -486,32 +486,32 @@ var-2=
|
|||
}
|
||||
|
||||
r2.LabelsTemplate = "foo=bar"
|
||||
m, err = Execute(r2, f)
|
||||
m, err = Execute(r2, f, true)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, map[string]string{"foo": "bar"}, m.Labels, "instances should have matched")
|
||||
assert.Empty(t, m.Vars)
|
||||
|
||||
r2.LabelsTemplate = "foo"
|
||||
_, err = Execute(r2, f)
|
||||
_, err = Execute(r2, f, true)
|
||||
assert.Error(t, err)
|
||||
|
||||
r2.LabelsTemplate = "{{"
|
||||
_, err = Execute(r2, f)
|
||||
_, err = Execute(r2, f, true)
|
||||
assert.Error(t, err)
|
||||
|
||||
r2.LabelsTemplate = ""
|
||||
r2.VarsTemplate = "bar=baz"
|
||||
m, err = Execute(r2, f)
|
||||
m, err = Execute(r2, f, true)
|
||||
assert.Nil(t, err)
|
||||
assert.Empty(t, m.Labels)
|
||||
assert.Equal(t, map[string]string{"bar": "baz"}, m.Vars, "instances should have matched")
|
||||
|
||||
r2.VarsTemplate = "bar"
|
||||
_, err = Execute(r2, f)
|
||||
_, err = Execute(r2, f, true)
|
||||
assert.Error(t, err)
|
||||
|
||||
r2.VarsTemplate = "{{"
|
||||
_, err = Execute(r2, f)
|
||||
_, err = Execute(r2, f, true)
|
||||
assert.Error(t, err)
|
||||
|
||||
//
|
||||
|
@ -535,7 +535,7 @@ var-2=
|
|||
"key-5": "",
|
||||
}
|
||||
|
||||
m, err = Execute(r4, f)
|
||||
m, err = Execute(r4, f, true)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, expectedLabels, m.Labels, "instances should have matched")
|
||||
|
||||
|
@ -549,7 +549,7 @@ var-2=
|
|||
},
|
||||
}
|
||||
|
||||
m, err = Execute(r4, f)
|
||||
m, err = Execute(r4, f, true)
|
||||
assert.Nilf(t, err, "unexpected error: %v", err)
|
||||
assert.Equal(t, map[string]string(nil), m.Labels, "instances should have matched")
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ func processNodeFeatureRule(nodeFeatureRule nfdv1alpha1.NodeFeatureRule, nodeFea
|
|||
|
||||
for _, rule := range nodeFeatureRule.Spec.Rules {
|
||||
fmt.Println("Processing rule: ", rule.Name)
|
||||
ruleOut, err := nodefeaturerule.Execute(&rule, &nodeFeature.Features)
|
||||
ruleOut, err := nodefeaturerule.Execute(&rule, &nodeFeature.Features, true)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to process rule: %q - %w", rule.Name, err))
|
||||
continue
|
||||
|
|
|
@ -794,7 +794,7 @@ func (m *nfdMaster) nfdAPIUpdateNodeFeatureGroup(nfdClient nfdclientset.Interfac
|
|||
nodeGroupValidator := make(map[string]bool)
|
||||
for _, rule := range nodeFeatureGroup.Spec.Rules {
|
||||
for _, feature := range nodeFeaturesList {
|
||||
match, err := nodefeaturerule.ExecuteGroupRule(&rule, &feature.Spec.Features)
|
||||
match, err := nodefeaturerule.ExecuteGroupRule(&rule, &feature.Spec.Features, true)
|
||||
if err != nil {
|
||||
klog.ErrorS(err, "failed to evaluate rule", "ruleName", rule.Name)
|
||||
continue
|
||||
|
@ -1018,7 +1018,7 @@ func (m *nfdMaster) processNodeFeatureRule(nodeName string, features *nfdv1alpha
|
|||
klog.InfoS("executing NodeFeatureRule", "nodefeaturerule", klog.KObj(spec), "nodeName", nodeName)
|
||||
}
|
||||
for _, rule := range spec.Spec.Rules {
|
||||
ruleOut, err := nodefeaturerule.Execute(&rule, features)
|
||||
ruleOut, err := nodefeaturerule.Execute(&rule, features, true)
|
||||
if err != nil {
|
||||
klog.ErrorS(err, "failed to process rule", "ruleName", rule.Name, "nodefeaturerule", klog.KObj(spec), "nodeName", nodeName)
|
||||
nfrProcessingErrors.Inc()
|
||||
|
|
|
@ -93,7 +93,7 @@ func (s *customSource) GetLabels() (source.FeatureLabels, error) {
|
|||
klog.V(2).InfoS("resolving custom features", "configuration", utils.DelayedDumper(allFeatureConfig))
|
||||
// Iterate over features
|
||||
for _, rule := range allFeatureConfig {
|
||||
ruleOut, err := nodefeaturerule.Execute(&rule, features)
|
||||
ruleOut, err := nodefeaturerule.Execute(&rule, features, true)
|
||||
if err != nil {
|
||||
klog.ErrorS(err, "failed to execute rule")
|
||||
continue
|
||||
|
|
Loading…
Add table
Reference in a new issue