1
0
Fork 0
mirror of https://github.com/kubernetes-sigs/node-feature-discovery.git synced 2024-12-14 11:57:51 +00:00

Merge pull request #653 from marquiz/devel/nodefeaturerule-crd

specify CRD for custom labeling rules
This commit is contained in:
Kubernetes Prow Robot 2021-11-17 03:45:54 -08:00 committed by GitHub
commit 55ea049ed7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 1531 additions and 561 deletions

View file

@ -102,6 +102,8 @@ generate:
go mod vendor
go generate ./cmd/... ./pkg/... ./source/...
rm -rf vendor/
controller-gen object crd output:crd:stdout paths=./pkg/apis/... > deployment/base/nfd-crds/nodefeaturerule-crd.yaml
cp deployment/base/nfd-crds/nodefeaturerule-crd.yaml deployment/helm/node-feature-discovery/manifests/
gofmt:
@$(GO_FMT) -w -l $$(find . -name '*.go')

View file

@ -0,0 +1,71 @@
apiVersion: nfd.k8s-sigs.io/v1alpha1
kind: NodeFeatureRule
metadata:
name: my-rule
spec:
rules:
# The following feature demonstrates the capabilities of the matchFeatures and
# matchAny matchers.
- name: "my feature rule"
labels:
"my-complex-feature": "my-value"
# matchFeatures implements a logical AND over feature matchers.
matchFeatures:
- feature: cpu.cpuid
matchExpressions:
AVX512F: {op: Exists}
- feature: cpu.cstate
matchExpressions:
enabled: {op: IsTrue}
- feature: cpu.pstate
matchExpressions:
no_turbo: {op: IsFalse}
scaling_governor: {op: In, value: ["performance"]}
- feature: cpu.rdt
matchExpressions:
RDTL3CA: {op: Exists}
- feature: cpu.sst
matchExpressions:
bf.enabled: {op: IsTrue}
- feature: cpu.topology
matchExpressions:
hardware_multithreading: {op: IsFalse}
- feature: kernel.config
matchExpressions:
X86: {op: Exists}
LSM: {op: InRegexp, value: ["apparmor"]}
- feature: kernel.loadedmodule
matchExpressions:
e1000e: {op: Exists}
- feature: kernel.selinux
matchExpressions:
enabled: {op: IsFalse}
- feature: kernel.version
matchExpressions:
major: {op: In, value: ["5"]}
minor: {op: Gt, value: ["10"]}
- feature: system.osrelease
matchExpressions:
ID: {op: In, value: ["fedora", "centos"]}
- feature: system.name
matchExpressions:
nodename: {op: InRegexp, value: ["^worker-X"]}
- feature: local.label
matchExpressions:
custom-feature-knob: {op: Gt, value: ["100"]}
# matchAny implements a logical IF over all listed matchers (i.e. at
# least one must match)
matchAny:
- matchFeatures:
- feature: pci.device
matchExpressions:
vendor: {op: In, value: ["8086"]}
class: {op: In, value: ["0200"]}
- feature: usb.device
matchExpressions:
vendor: {op: In, value: ["8086"]}
class: {op: In, value: ["02"]}

View file

@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- nodefeaturerule-crd.yaml

View file

@ -0,0 +1,208 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.7.0
creationTimestamp: null
name: nodefeaturerules.nfd.k8s-sigs.io
spec:
group: nfd.k8s-sigs.io
names:
kind: NodeFeatureRule
listKind: NodeFeatureRuleList
plural: nodefeaturerules
singular: nodefeaturerule
scope: Cluster
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: NodeFeatureRule resource specifies a configuration for feature-based
customization of node objects, such as node labeling.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: NodeFeatureRuleSpec describes a NodeFeatureRule.
properties:
rules:
description: Rules is a list of node customization rules.
items:
description: Rule defines a rule for node customization such as
labeling.
properties:
labels:
additionalProperties:
type: string
description: Labels to create if the rule matches.
type: object
labelsTemplate:
description: LabelsTemplate specifies a template to expand for
dynamically generating multiple labels. Data (after template
expansion) must be keys with an optional value (<key>[=<value>])
separated by newlines.
type: string
matchAny:
description: MatchAny specifies a list of matchers one of which
must match.
items:
description: MatchAnyElem specifies one sub-matcher of MatchAny.
properties:
matchFeatures:
description: MatchFeatures specifies a set of matcher
terms all of which must match.
items:
description: FeatureMatcherTerm defines requirements
against one feature set. All requirements (specified
as MatchExpressions) are evaluated against each element
in the feature set.
properties:
feature:
type: string
matchExpressions:
additionalProperties:
description: "MatchExpression specifies an expression
to evaluate against a set of input values. It
contains an operator that is applied when matching
the input and an array of values that the operator
evaluates the input against. \n NB: CreateMatchExpression
or MustCreateMatchExpression() should be used
for creating new instances. NB: Validate()
must be called if Op or Value fields are modified
or if a new instance is created from scratch
without using the helper functions."
properties:
op:
description: Op is the operator to be applied.
enum:
- In
- NotIn
- InRegexp
- Exists
- DoesNotExist
- Gt
- Lt
- GtLt
- IsTrue
- IsFalse
type: string
value:
description: Value is the list of values that
the operand evaluates the input against.
Value should be empty if the operator is
Exists, DoesNotExist, IsTrue or IsFalse.
Value should contain exactly one element
if the operator is Gt or Lt and exactly
two elements if the operator is GtLt. In
other cases Value should contain at least
one element.
items:
type: string
type: array
required:
- op
type: object
description: MatchExpressionSet contains a set of
MatchExpressions, each of which is evaluated against
a set of input values.
type: object
required:
- feature
- matchExpressions
type: object
type: array
required:
- matchFeatures
type: object
type: array
matchFeatures:
description: MatchFeatures specifies a set of matcher terms
all of which must match.
items:
description: FeatureMatcherTerm defines requirements against
one feature set. All requirements (specified as MatchExpressions)
are evaluated against each element in the feature set.
properties:
feature:
type: string
matchExpressions:
additionalProperties:
description: "MatchExpression specifies an expression
to evaluate against a set of input values. It contains
an operator that is applied when matching the input
and an array of values that the operator evaluates
the input against. \n NB: CreateMatchExpression or
MustCreateMatchExpression() should be used for creating
new instances. NB: Validate() must be called if Op
or Value fields are modified or if a new instance
is created from scratch without using the helper functions."
properties:
op:
description: Op is the operator to be applied.
enum:
- In
- NotIn
- InRegexp
- Exists
- DoesNotExist
- Gt
- Lt
- GtLt
- IsTrue
- IsFalse
type: string
value:
description: Value is the list of values that the
operand evaluates the input against. Value should
be empty if the operator is Exists, DoesNotExist,
IsTrue or IsFalse. Value should contain exactly
one element if the operator is Gt or Lt and exactly
two elements if the operator is GtLt. In other
cases Value should contain at least one element.
items:
type: string
type: array
required:
- op
type: object
description: MatchExpressionSet contains a set of MatchExpressions,
each of which is evaluated against a set of input values.
type: object
required:
- feature
- matchExpressions
type: object
type: array
name:
description: Name of the rule.
type: string
required:
- name
type: object
type: array
required:
- rules
type: object
required:
- spec
type: object
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View file

@ -0,0 +1,208 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.7.0
creationTimestamp: null
name: nodefeaturerules.nfd.k8s-sigs.io
spec:
group: nfd.k8s-sigs.io
names:
kind: NodeFeatureRule
listKind: NodeFeatureRuleList
plural: nodefeaturerules
singular: nodefeaturerule
scope: Cluster
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: NodeFeatureRule resource specifies a configuration for feature-based
customization of node objects, such as node labeling.
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
of an object. Servers should convert recognized schemas to the latest
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
type: string
kind:
description: 'Kind is a string value representing the REST resource this
object represents. Servers may infer this from the endpoint the client
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
type: string
metadata:
type: object
spec:
description: NodeFeatureRuleSpec describes a NodeFeatureRule.
properties:
rules:
description: Rules is a list of node customization rules.
items:
description: Rule defines a rule for node customization such as
labeling.
properties:
labels:
additionalProperties:
type: string
description: Labels to create if the rule matches.
type: object
labelsTemplate:
description: LabelsTemplate specifies a template to expand for
dynamically generating multiple labels. Data (after template
expansion) must be keys with an optional value (<key>[=<value>])
separated by newlines.
type: string
matchAny:
description: MatchAny specifies a list of matchers one of which
must match.
items:
description: MatchAnyElem specifies one sub-matcher of MatchAny.
properties:
matchFeatures:
description: MatchFeatures specifies a set of matcher
terms all of which must match.
items:
description: FeatureMatcherTerm defines requirements
against one feature set. All requirements (specified
as MatchExpressions) are evaluated against each element
in the feature set.
properties:
feature:
type: string
matchExpressions:
additionalProperties:
description: "MatchExpression specifies an expression
to evaluate against a set of input values. It
contains an operator that is applied when matching
the input and an array of values that the operator
evaluates the input against. \n NB: CreateMatchExpression
or MustCreateMatchExpression() should be used
for creating new instances. NB: Validate()
must be called if Op or Value fields are modified
or if a new instance is created from scratch
without using the helper functions."
properties:
op:
description: Op is the operator to be applied.
enum:
- In
- NotIn
- InRegexp
- Exists
- DoesNotExist
- Gt
- Lt
- GtLt
- IsTrue
- IsFalse
type: string
value:
description: Value is the list of values that
the operand evaluates the input against.
Value should be empty if the operator is
Exists, DoesNotExist, IsTrue or IsFalse.
Value should contain exactly one element
if the operator is Gt or Lt and exactly
two elements if the operator is GtLt. In
other cases Value should contain at least
one element.
items:
type: string
type: array
required:
- op
type: object
description: MatchExpressionSet contains a set of
MatchExpressions, each of which is evaluated against
a set of input values.
type: object
required:
- feature
- matchExpressions
type: object
type: array
required:
- matchFeatures
type: object
type: array
matchFeatures:
description: MatchFeatures specifies a set of matcher terms
all of which must match.
items:
description: FeatureMatcherTerm defines requirements against
one feature set. All requirements (specified as MatchExpressions)
are evaluated against each element in the feature set.
properties:
feature:
type: string
matchExpressions:
additionalProperties:
description: "MatchExpression specifies an expression
to evaluate against a set of input values. It contains
an operator that is applied when matching the input
and an array of values that the operator evaluates
the input against. \n NB: CreateMatchExpression or
MustCreateMatchExpression() should be used for creating
new instances. NB: Validate() must be called if Op
or Value fields are modified or if a new instance
is created from scratch without using the helper functions."
properties:
op:
description: Op is the operator to be applied.
enum:
- In
- NotIn
- InRegexp
- Exists
- DoesNotExist
- Gt
- Lt
- GtLt
- IsTrue
- IsFalse
type: string
value:
description: Value is the list of values that the
operand evaluates the input against. Value should
be empty if the operator is Exists, DoesNotExist,
IsTrue or IsFalse. Value should contain exactly
one element if the operator is Gt or Lt and exactly
two elements if the operator is GtLt. In other
cases Value should contain at least one element.
items:
type: string
type: array
required:
- op
type: object
description: MatchExpressionSet contains a set of MatchExpressions,
each of which is evaluated against a set of input values.
type: object
required:
- feature
- matchExpressions
type: object
type: array
name:
description: Name of the rule.
type: string
required:
- name
type: object
type: array
required:
- rules
type: object
required:
- spec
type: object
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []

View file

@ -0,0 +1,3 @@
{{- if .Values.nodeFeatureRule.createCRD }}
{{ .Files.Get "manifests/nodefeaturerule-crd.yaml" }}
{{- end}}

View file

@ -18,6 +18,9 @@ serviceAccount:
nameOverride: ""
fullnameOverride: ""
nodeFeatureRule:
createCRD: true
master:
instance:
extraLabelNs: []

View file

@ -5,6 +5,7 @@ namespace: node-feature-discovery
bases:
- ../../base/rbac
- ../../base/nfd-crds
- ../../base/master-worker-combined
resources:

View file

@ -5,6 +5,7 @@ namespace: node-feature-discovery
bases:
- ../../base/rbac
- ../../base/nfd-crds
- ../../base/master
- ../../base/worker-job

View file

@ -5,6 +5,7 @@ namespace: node-feature-discovery
bases:
- ../../base/rbac
- ../../base/nfd-crds
- ../../base/master
- ../../base/worker-daemonset

View file

@ -6,6 +6,7 @@ namespace: node-feature-discovery
bases:
- ../../base/rbac
- ../../base/rbac-topologyupdater
- ../../base/nfd-crds
- ../../base/master
- ../../base/worker-daemonset
- ../../base/noderesourcetopologies-crd

View file

@ -290,6 +290,7 @@ We have introduced the following Chart parameters.
| `rbac` | dict | | RBAC [parameteres](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) |
| `nameOverride` | string | | Override the name of the chart |
| `fullnameOverride` | string | | Override a default fully qualified app name |
| `nodeFeatureRule.createCRD` | bool | true | Specifies whether to create the NodeFeatureRule CRD |
##### Master pod parameters

1
go.mod
View file

@ -87,7 +87,6 @@ require (
github.com/go-ozzo/ozzo-validation v3.5.0+incompatible // indirect
github.com/godbus/dbus/v5 v5.0.4 // indirect
github.com/gofrs/uuid v4.0.0+incompatible // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/mock v1.4.4 // indirect
github.com/google/cadvisor v0.39.2 // indirect

View file

@ -0,0 +1,20 @@
/*
Copyright 2021 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.
*/
// +k8s:deepcopy-gen=package
// +kubebuilder:object:generate=true
// +groupName=nfd.k8s-sigs.io
package v1alpha1

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package expression
package v1alpha1
import (
"encoding/json"
@ -29,80 +29,6 @@ import (
"sigs.k8s.io/node-feature-discovery/pkg/api/feature"
)
// MatchExpressionSet contains a set of MatchExpressions, each of which is
// evaluated against a set of input values.
type MatchExpressionSet map[string]*MatchExpression
// MatchExpression specifies an expression to evaluate against a set of input
// values. It contains an operator that is applied when matching the input and
// an array of values that the operator evaluates the input against.
// NB: CreateMatchExpression or MustCreateMatchExpression() should be used for
// creating new instances.
// NB: Validate() must be called if Op or Value fields are modified or if a new
// instance is created from scratch without using the helper functions.
type MatchExpression struct {
// Op is the operator to be applied.
Op MatchOp
// Value is the list of values that the operand evaluates the input
// against. Value should be empty if the operator is Exists, DoesNotExist,
// IsTrue or IsFalse. Value should contain exactly one element if the
// operator is Gt or Lt and exactly two elements if the operator is GtLt.
// In other cases Value should contain at least one element.
Value MatchValue `json:",omitempty"`
// valueRe caches compiled regexps for "InRegexp" operator
valueRe []*regexp.Regexp
}
// MatchOp is the match operator that is applied on values when evaluating a
// MatchExpression.
type MatchOp string
// MatchValue is the list of values associated with a MatchExpression.
type MatchValue []string
const (
// MatchAny returns always true.
MatchAny MatchOp = ""
// MatchIn returns true if any of the values stored in the expression is
// equal to the input.
MatchIn MatchOp = "In"
// MatchIn returns true if none of the values in the expression are equal
// to the input.
MatchNotIn MatchOp = "NotIn"
// MatchInRegexp treats values of the expression as regular expressions and
// returns true if any of them matches the input.
MatchInRegexp MatchOp = "InRegexp"
// MatchExists returns true if the input is valid. The expression must not
// have any values.
MatchExists MatchOp = "Exists"
// MatchDoesNotExist returns true if the input is not valid. The expression
// must not have any values.
MatchDoesNotExist MatchOp = "DoesNotExist"
// MatchGt returns true if the input is greater than the value of the
// expression (number of values in the expression must be exactly one).
// Both the input and value must be integer numbers, otherwise an error is
// returned.
MatchGt MatchOp = "Gt"
// MatchLt returns true if the input is less than the value of the
// expression (number of values in the expression must be exactly one).
// Both the input and value must be integer numbers, otherwise an error is
// returned.
MatchLt MatchOp = "Lt"
// MatchGtLt returns true if the input is between two values, i.e. greater
// than the first value and less than the second value of the expression
// (number of values in the expression must be exactly two). Both the input
// and values must be integer numbers, otherwise an error is returned.
MatchGtLt MatchOp = "GtLt"
// MatchIsTrue returns true if the input holds the value "true". The
// expression must not have any values.
MatchIsTrue MatchOp = "IsTrue"
// MatchIsTrue returns true if the input holds the value "false". The
// expression must not have any values.
MatchIsFalse MatchOp = "IsFalse"
)
var matchOps = map[MatchOp]struct{}{
MatchAny: struct{}{},
MatchIn: struct{}{},
@ -117,6 +43,18 @@ var matchOps = map[MatchOp]struct{}{
MatchIsFalse: struct{}{},
}
type valueRegexpCache []*regexp.Regexp
// NewMatchExpressionSet returns a new MatchExpressionSet instance.
func NewMatchExpressionSet() *MatchExpressionSet {
return &MatchExpressionSet{Expressions: make(Expressions)}
}
// Len returns the number of expressions.
func (e *Expressions) Len() int {
return len(*e)
}
// CreateMatchExpression creates a new MatchExpression instance. Returns an
// error if validation fails.
func CreateMatchExpression(op MatchOp, values ...string) (*MatchExpression, error) {
@ -376,7 +314,7 @@ func (m *MatchExpression) UnmarshalJSON(data []byte) error {
// MatchKeys evaluates the MatchExpressionSet against a set of keys.
func (m *MatchExpressionSet) MatchKeys(keys map[string]feature.Nil) (bool, error) {
for n, e := range *m {
for n, e := range (*m).Expressions {
match, err := e.MatchKeys(n, keys)
if err != nil {
return false, err
@ -390,7 +328,7 @@ func (m *MatchExpressionSet) MatchKeys(keys map[string]feature.Nil) (bool, error
// MatchValues evaluates the MatchExpressionSet against a set of key-value pairs.
func (m *MatchExpressionSet) MatchValues(values map[string]string) (bool, error) {
for n, e := range *m {
for n, e := range (*m).Expressions {
match, err := e.MatchValues(n, values)
if err != nil {
return false, err
@ -418,7 +356,7 @@ func (m *MatchExpressionSet) MatchInstances(instances []feature.InstanceFeature)
// UnmarshalJSON implements the Unmarshaler interface of "encoding/json".
func (m *MatchExpressionSet) UnmarshalJSON(data []byte) error {
*m = make(MatchExpressionSet)
*m = *NewMatchExpressionSet()
names := make([]string, 0)
if err := json.Unmarshal(data, &names); err == nil {
@ -426,9 +364,9 @@ func (m *MatchExpressionSet) UnmarshalJSON(data []byte) error {
for _, name := range names {
split := strings.SplitN(name, "=", 2)
if len(split) == 1 {
(*m)[split[0]] = newMatchExpression(MatchExists)
(*m).Expressions[split[0]] = newMatchExpression(MatchExists)
} else {
(*m)[split[0]] = newMatchExpression(MatchIn, split[1])
(*m).Expressions[split[0]] = newMatchExpression(MatchIn, split[1])
}
}
} else {
@ -439,9 +377,9 @@ func (m *MatchExpressionSet) UnmarshalJSON(data []byte) error {
} else {
for k, v := range expressions {
if v != nil {
(*m)[k] = v
(*m).Expressions[k] = v
} else {
(*m)[k] = newMatchExpression(MatchExists)
(*m).Expressions[k] = newMatchExpression(MatchExists)
}
}
}
@ -496,3 +434,22 @@ func (m *MatchValue) UnmarshalJSON(data []byte) error {
return nil
}
// DeepCopy supplements the auto-generated code
func (in *valueRegexpCache) DeepCopy() *valueRegexpCache {
if in == nil {
return nil
}
out := new(valueRegexpCache)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is a stub to augment the auto-generated code
//nolint:staticcheck // re.Copy is deprecated but we want to use it here
func (in *valueRegexpCache) DeepCopyInto(out *valueRegexpCache) {
*out = make(valueRegexpCache, len(*in))
for i, re := range *in {
(*out)[i] = re.Copy()
}
}

View file

@ -0,0 +1,439 @@
/*
Copyright 2021 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_test
import (
"testing"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/yaml"
"sigs.k8s.io/node-feature-discovery/pkg/api/feature"
api "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
)
type BoolAssertionFuncf func(assert.TestingT, bool, string, ...interface{}) bool
type ValueAssertionFuncf func(assert.TestingT, interface{}, string, ...interface{}) bool
func TestCreateMatchExpression(t *testing.T) {
type V = api.MatchValue
type TC struct {
op api.MatchOp
values V
err ValueAssertionFuncf
}
tcs := []TC{
{op: api.MatchAny, err: assert.Nilf}, // #0
{op: api.MatchAny, values: V{"1"}, err: assert.NotNilf},
{op: api.MatchIn, err: assert.NotNilf},
{op: api.MatchIn, values: V{"1"}, err: assert.Nilf},
{op: api.MatchIn, values: V{"1", "2", "3", "4"}, err: assert.Nilf},
{op: api.MatchNotIn, err: assert.NotNilf},
{op: api.MatchNotIn, values: V{"1"}, err: assert.Nilf},
{op: api.MatchNotIn, values: V{"1", "2"}, err: assert.Nilf},
{op: api.MatchInRegexp, err: assert.NotNilf},
{op: api.MatchInRegexp, values: V{"1"}, err: assert.Nilf},
{op: api.MatchInRegexp, values: V{"()", "2", "3"}, err: assert.Nilf},
{op: api.MatchInRegexp, values: V{"("}, err: assert.NotNilf},
{op: api.MatchExists, err: assert.Nilf},
{op: api.MatchExists, values: V{"1"}, err: assert.NotNilf},
{op: api.MatchDoesNotExist, err: assert.Nilf},
{op: api.MatchDoesNotExist, values: V{"1"}, err: assert.NotNilf},
{op: api.MatchGt, err: assert.NotNilf},
{op: api.MatchGt, values: V{"1"}, err: assert.Nilf},
{op: api.MatchGt, values: V{"-10"}, err: assert.Nilf},
{op: api.MatchGt, values: V{"1", "2"}, err: assert.NotNilf},
{op: api.MatchGt, values: V{""}, err: assert.NotNilf},
{op: api.MatchLt, err: assert.NotNilf},
{op: api.MatchLt, values: V{"1"}, err: assert.Nilf},
{op: api.MatchLt, values: V{"-1"}, err: assert.Nilf},
{op: api.MatchLt, values: V{"1", "2", "3"}, err: assert.NotNilf},
{op: api.MatchLt, values: V{"a"}, err: assert.NotNilf},
{op: api.MatchGtLt, err: assert.NotNilf},
{op: api.MatchGtLt, values: V{"1"}, err: assert.NotNilf},
{op: api.MatchGtLt, values: V{"1", "2"}, err: assert.Nilf},
{op: api.MatchGtLt, values: V{"2", "1"}, err: assert.NotNilf},
{op: api.MatchGtLt, values: V{"1", "2", "3"}, err: assert.NotNilf},
{op: api.MatchGtLt, values: V{"a", "2"}, err: assert.NotNilf},
{op: api.MatchIsTrue, err: assert.Nilf},
{op: api.MatchIsTrue, values: V{"1"}, err: assert.NotNilf},
{op: api.MatchIsFalse, err: assert.Nilf},
{op: api.MatchIsFalse, values: V{"1", "2"}, err: assert.NotNilf},
}
for i, tc := range tcs {
_, err := api.CreateMatchExpression(tc.op, tc.values...)
tc.err(t, err, "test case #%d (%v) failed", i, tc)
}
}
func TestMatch(t *testing.T) {
type V = api.MatchValue
type TC struct {
op api.MatchOp
values V
input interface{}
valid bool
result BoolAssertionFuncf
err ValueAssertionFuncf
}
tcs := []TC{
{op: api.MatchAny, result: assert.Truef, err: assert.Nilf},
{op: api.MatchAny, input: "2", valid: false, result: assert.Truef, err: assert.Nilf},
{op: api.MatchIn, values: V{"1"}, input: "2", valid: false, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchIn, values: V{"1"}, input: "2", valid: true, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchIn, values: V{"1", "2", "3"}, input: "2", valid: false, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchIn, values: V{"1", "2", "3"}, input: "2", valid: true, result: assert.Truef, err: assert.Nilf},
{op: api.MatchNotIn, values: V{"2"}, input: 2, valid: false, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchNotIn, values: V{"1"}, input: 2, valid: true, result: assert.Truef, err: assert.Nilf},
{op: api.MatchNotIn, values: V{"1", "2", "3"}, input: "2", valid: false, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchNotIn, values: V{"1", "2", "3"}, input: "2", valid: true, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchInRegexp, values: V{"val-[0-9]$"}, input: "val-1", valid: false, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchInRegexp, values: V{"val-[0-9]$"}, input: "val-1", valid: true, result: assert.Truef, err: assert.Nilf},
{op: api.MatchInRegexp, values: V{"val-[0-9]$"}, input: "val-12", valid: true, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchInRegexp, values: V{"val-[0-9]$", "al-[1-9]"}, input: "val-12", valid: true, result: assert.Truef, err: assert.Nilf},
{op: api.MatchExists, input: nil, valid: false, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchExists, input: nil, valid: true, result: assert.Truef, err: assert.Nilf},
{op: api.MatchDoesNotExist, input: false, valid: false, result: assert.Truef, err: assert.Nilf},
{op: api.MatchDoesNotExist, input: false, valid: true, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchGt, values: V{"2"}, input: 3, valid: false, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchGt, values: V{"2"}, input: 2, valid: true, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchGt, values: V{"2"}, input: 3, valid: true, result: assert.Truef, err: assert.Nilf},
{op: api.MatchGt, values: V{"-10"}, input: -3, valid: true, result: assert.Truef, err: assert.Nilf},
{op: api.MatchGt, values: V{"2"}, input: "3a", valid: true, result: assert.Falsef, err: assert.NotNilf},
{op: api.MatchLt, values: V{"2"}, input: "1", valid: false, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchLt, values: V{"2"}, input: "2", valid: true, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchLt, values: V{"-10"}, input: -3, valid: true, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchLt, values: V{"2"}, input: "1", valid: true, result: assert.Truef, err: assert.Nilf},
{op: api.MatchLt, values: V{"2"}, input: "1.0", valid: true, result: assert.Falsef, err: assert.NotNilf},
{op: api.MatchGtLt, values: V{"1", "10"}, input: "1", valid: false, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchGtLt, values: V{"1", "10"}, input: "1", valid: true, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchGtLt, values: V{"1", "10"}, input: "10", valid: true, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchGtLt, values: V{"1", "10"}, input: "2", valid: true, result: assert.Truef, err: assert.Nilf},
{op: api.MatchGtLt, values: V{"1", "10"}, input: "1.0", valid: true, result: assert.Falsef, err: assert.NotNilf},
{op: api.MatchIsTrue, input: true, valid: false, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchIsTrue, input: true, valid: true, result: assert.Truef, err: assert.Nilf},
{op: api.MatchIsTrue, input: false, valid: true, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchIsFalse, input: "false", valid: false, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchIsFalse, input: "false", valid: true, result: assert.Truef, err: assert.Nilf},
{op: api.MatchIsFalse, input: "true", valid: true, result: assert.Falsef, err: assert.Nilf},
}
for i, tc := range tcs {
me := api.MustCreateMatchExpression(tc.op, tc.values...)
res, err := me.Match(tc.valid, tc.input)
tc.result(t, res, "test case #%d (%v) failed", i, tc)
tc.err(t, err, "test case #%d (%v) failed", i, tc)
}
// Check some special error cases separately because MustCreateMatch panics
tcs = []TC{
{op: api.MatchGt, values: V{"3.0"}, input: 1, valid: true},
{op: api.MatchLt, values: V{"0x2"}, input: 1, valid: true},
{op: api.MatchGtLt, values: V{"1", "str"}, input: 1, valid: true},
{op: "non-existent-op", values: V{"1"}, input: 1, valid: true},
}
for i, tc := range tcs {
me := api.MatchExpression{Op: tc.op, Value: tc.values}
res, err := me.Match(tc.valid, tc.input)
assert.Falsef(t, res, "err test case #%d (%v) failed", i, tc)
assert.NotNilf(t, err, "err test case #%d (%v) failed", i, tc)
}
}
func TestMatchKeys(t *testing.T) {
type V = api.MatchValue
type I = map[string]feature.Nil
type TC struct {
op api.MatchOp
values V
name string
input I
result BoolAssertionFuncf
err ValueAssertionFuncf
}
tcs := []TC{
{op: api.MatchAny, result: assert.Truef, err: assert.Nilf},
{op: api.MatchExists, name: "foo", input: nil, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchExists, name: "foo", input: I{"bar": {}}, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchExists, name: "foo", input: I{"bar": {}, "foo": {}}, result: assert.Truef, err: assert.Nilf},
{op: api.MatchDoesNotExist, name: "foo", input: nil, result: assert.Truef, err: assert.Nilf},
{op: api.MatchDoesNotExist, name: "foo", input: I{}, result: assert.Truef, err: assert.Nilf},
{op: api.MatchDoesNotExist, name: "foo", input: I{"bar": {}}, result: assert.Truef, err: assert.Nilf},
{op: api.MatchDoesNotExist, name: "foo", input: I{"bar": {}, "foo": {}}, result: assert.Falsef, err: assert.Nilf},
// All other ops should return an error
{op: api.MatchIn, values: V{"foo"}, name: "foo", result: assert.Falsef, err: assert.NotNilf},
{op: api.MatchNotIn, values: V{"foo"}, name: "foo", result: assert.Falsef, err: assert.NotNilf},
{op: api.MatchInRegexp, values: V{"foo"}, name: "foo", result: assert.Falsef, err: assert.NotNilf},
{op: api.MatchGt, values: V{"1"}, name: "foo", result: assert.Falsef, err: assert.NotNilf},
{op: api.MatchLt, values: V{"1"}, name: "foo", result: assert.Falsef, err: assert.NotNilf},
{op: api.MatchGtLt, values: V{"1", "10"}, name: "foo", result: assert.Falsef, err: assert.NotNilf},
{op: api.MatchIsTrue, name: "foo", result: assert.Falsef, err: assert.NotNilf},
{op: api.MatchIsFalse, name: "foo", result: assert.Falsef, err: assert.NotNilf},
}
for i, tc := range tcs {
me := api.MustCreateMatchExpression(tc.op, tc.values...)
res, err := me.MatchKeys(tc.name, tc.input)
tc.result(t, res, "test case #%d (%v) failed", i, tc)
tc.err(t, err, "test case #%d (%v) failed", i, tc)
}
}
func TestMatchValues(t *testing.T) {
type V = []string
type I = map[string]string
type TC struct {
op api.MatchOp
values V
name string
input I
result BoolAssertionFuncf
err ValueAssertionFuncf
}
tcs := []TC{
{op: api.MatchAny, result: assert.Truef, err: assert.Nilf},
{op: api.MatchIn, values: V{"1", "2"}, name: "foo", input: I{"bar": "2"}, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchIn, values: V{"1", "2"}, name: "foo", input: I{"foo": "3"}, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchIn, values: V{"1", "2"}, name: "foo", input: I{"foo": "2"}, result: assert.Truef, err: assert.Nilf},
{op: api.MatchNotIn, values: V{"1", "2"}, name: "foo", input: I{"bar": "2"}, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchNotIn, values: V{"1", "2"}, name: "foo", input: I{"foo": "3"}, result: assert.Truef, err: assert.Nilf},
{op: api.MatchNotIn, values: V{"1", "2"}, name: "foo", input: I{"foo": "2"}, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchInRegexp, values: V{"1", "2"}, name: "foo", input: I{"bar": "2"}, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchInRegexp, values: V{"1", "[0-8]"}, name: "foo", input: I{"foo": "9"}, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchInRegexp, values: V{"1", "[0-8]"}, name: "foo", input: I{"foo": "2"}, result: assert.Truef, err: assert.Nilf},
{op: api.MatchExists, name: "foo", input: I{"bar": "1"}, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchExists, name: "foo", input: I{"foo": "1"}, result: assert.Truef, err: assert.Nilf},
{op: api.MatchDoesNotExist, name: "foo", input: nil, result: assert.Truef, err: assert.Nilf},
{op: api.MatchDoesNotExist, name: "foo", input: I{"foo": "1"}, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchGt, values: V{"2"}, name: "foo", input: I{"bar": "3"}, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchGt, values: V{"2"}, name: "foo", input: I{"bar": "3", "foo": "2"}, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchGt, values: V{"2"}, name: "foo", input: I{"bar": "3", "foo": "3"}, result: assert.Truef, err: assert.Nilf},
{op: api.MatchGt, values: V{"2"}, name: "foo", input: I{"bar": "str", "foo": "str"}, result: assert.Falsef, err: assert.NotNilf},
{op: api.MatchLt, values: V{"2"}, name: "foo", input: I{"bar": "1"}, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchLt, values: V{"2"}, name: "foo", input: I{"bar": "1", "foo": "2"}, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchLt, values: V{"2"}, name: "foo", input: I{"bar": "1", "foo": "1"}, result: assert.Truef, err: assert.Nilf},
{op: api.MatchLt, values: V{"2"}, name: "foo", input: I{"bar": "str", "foo": "str"}, result: assert.Falsef, err: assert.NotNilf},
{op: api.MatchGtLt, values: V{"-10", "10"}, name: "foo", input: I{"bar": "1"}, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchGtLt, values: V{"-10", "10"}, name: "foo", input: I{"bar": "1", "foo": "11"}, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchGtLt, values: V{"-10", "10"}, name: "foo", input: I{"bar": "1", "foo": "-11"}, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchGtLt, values: V{"-10", "10"}, name: "foo", input: I{"bar": "1", "foo": "1"}, result: assert.Truef, err: assert.Nilf},
{op: api.MatchGtLt, values: V{"-10", "10"}, name: "foo", input: I{"bar": "str", "foo": "str"}, result: assert.Falsef, err: assert.NotNilf},
{op: api.MatchIsTrue, name: "foo", result: assert.Falsef, err: assert.Nilf},
{op: api.MatchIsTrue, name: "foo", input: I{"foo": "1"}, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchIsTrue, name: "foo", input: I{"foo": "true"}, result: assert.Truef, err: assert.Nilf},
{op: api.MatchIsFalse, name: "foo", input: I{"foo": "true"}, result: assert.Falsef, err: assert.Nilf},
{op: api.MatchIsFalse, name: "foo", input: I{"foo": "false"}, result: assert.Truef, err: assert.Nilf},
}
for i, tc := range tcs {
me := api.MustCreateMatchExpression(tc.op, tc.values...)
res, err := me.MatchValues(tc.name, tc.input)
tc.result(t, res, "test case #%d (%v) failed", i, tc)
tc.err(t, err, "test case #%d (%v) failed", i, tc)
}
}
func TestMESMatchKeys(t *testing.T) {
type I = map[string]feature.Nil
type TC struct {
mes string
input I
result BoolAssertionFuncf
err ValueAssertionFuncf
}
tcs := []TC{
{result: assert.Truef, err: assert.Nilf},
{input: I{"foo": {}}, result: assert.Truef, err: assert.Nilf},
{mes: `
foo: { op: DoesNotExist }
bar: { op: Exists }
`,
input: I{"bar": {}, "baz": {}},
result: assert.Truef, err: assert.Nilf},
{mes: `
foo: { op: DoesNotExist }
bar: { op: Exists }
`,
input: I{"foo": {}, "bar": {}, "baz": {}},
result: assert.Falsef, err: assert.Nilf},
{mes: `
foo: { op: In, value: ["bar"] }
bar: { op: Exists }
`,
input: I{"bar": {}, "baz": {}},
result: assert.Falsef, err: assert.NotNilf},
}
for i, tc := range tcs {
mes := &api.MatchExpressionSet{}
if err := yaml.Unmarshal([]byte(tc.mes), mes); err != nil {
t.Fatalf("failed to parse data of test case #%d (%v): %v", i, tc, err)
}
res, err := mes.MatchKeys(tc.input)
tc.result(t, res, "test case #%d (%v) failed", i, tc)
tc.err(t, err, "test case #%d (%v) failed", i, tc)
}
}
func TestMESMatchValues(t *testing.T) {
type I = map[string]string
type TC struct {
mes string
input I
result BoolAssertionFuncf
err ValueAssertionFuncf
}
tcs := []TC{
{result: assert.Truef, err: assert.Nilf},
{input: I{"foo": "bar"}, result: assert.Truef, err: assert.Nilf},
{mes: `
foo: { op: Exists }
bar: { op: In, value: ["val", "wal"] }
baz: { op: Gt, value: ["10"] }
`,
input: I{"bar": "val"},
result: assert.Falsef, err: assert.Nilf},
{mes: `
foo: { op: Exists }
bar: { op: In, value: ["val", "wal"] }
baz: { op: Gt, value: ["10"] }
`,
input: I{"foo": "1", "bar": "val", "baz": "123"},
result: assert.Truef, err: assert.Nilf},
{mes: `
foo: { op: Exists }
bar: { op: In, value: ["val"] }
baz: { op: Gt, value: ["10"] }
`,
input: I{"foo": "1", "bar": "val", "baz": "123.0"},
result: assert.Falsef, err: assert.NotNilf},
}
for i, tc := range tcs {
mes := &api.MatchExpressionSet{}
if err := yaml.Unmarshal([]byte(tc.mes), mes); err != nil {
t.Fatalf("failed to parse data of test case #%d (%v): %v", i, tc, err)
}
res, err := mes.MatchValues(tc.input)
tc.result(t, res, "test case #%d (%v) failed", i, tc)
tc.err(t, err, "test case #%d (%v) failed", i, tc)
}
}
func TestMESMatchInstances(t *testing.T) {
type I = feature.InstanceFeature
type A = map[string]string
type TC struct {
mes string
input []I
result BoolAssertionFuncf
err ValueAssertionFuncf
}
tcs := []TC{
{result: assert.Falsef, err: assert.Nilf}, // nil instances -> false
{input: []I{}, result: assert.Falsef, err: assert.Nilf}, // zero instances -> false
{input: []I{I{Attributes: A{}}}, result: assert.Truef, err: assert.Nilf}, // one "empty" instance
{mes: `
foo: { op: Exists }
bar: { op: Lt, value: ["10"] }
`,
input: []I{I{Attributes: A{"foo": "1"}}, I{Attributes: A{"bar": "1"}}},
result: assert.Falsef, err: assert.Nilf},
{mes: `
foo: { op: Exists }
bar: { op: Lt, value: ["10"] }
`,
input: []I{I{Attributes: A{"foo": "1"}}, I{Attributes: A{"foo": "2", "bar": "1"}}},
result: assert.Truef, err: assert.Nilf},
{mes: `
bar: { op: Lt, value: ["10"] }
`,
input: []I{I{Attributes: A{"foo": "1"}}, I{Attributes: A{"bar": "0x1"}}},
result: assert.Falsef, err: assert.NotNilf},
}
for i, tc := range tcs {
mes := &api.MatchExpressionSet{}
if err := yaml.Unmarshal([]byte(tc.mes), mes); err != nil {
t.Fatalf("failed to parse data of test case #%d (%v): %v", i, tc, err)
}
res, err := mes.MatchInstances(tc.input)
tc.result(t, res, "test case #%d (%v) failed", i, tc)
tc.err(t, err, "test case #%d (%v) failed", i, tc)
}
}

View file

@ -0,0 +1,173 @@
/*
Copyright 2021 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 (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// NodeFeatureRuleList contains a list of NodeFeatureRule objects.
// +kubebuilder:object:root=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type NodeFeatureRuleList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata"`
Items []NodeFeatureRule `json:"items"`
}
// NodeFeatureRule resource specifies a configuration for feature-based
// customization of node objects, such as node labeling.
// +kubebuilder:object:root=true
// +kubebuilder:resource:scope=Cluster
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type NodeFeatureRule struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec NodeFeatureRuleSpec `json:"spec"`
}
// NodeFeatureRuleSpec describes a NodeFeatureRule.
type NodeFeatureRuleSpec struct {
// Rules is a list of node customization rules.
Rules []Rule `json:"rules"`
}
// Rule defines a rule for node customization such as labeling.
type Rule struct {
// Name of the rule.
Name string `json:"name"`
// Labels to create if the rule matches.
// +optional
Labels map[string]string `json:"labels"`
// LabelsTemplate specifies a template to expand for dynamically generating
// multiple labels. Data (after template expansion) must be keys with an
// optional value (<key>[=<value>]) separated by newlines.
// +optional
LabelsTemplate string `json:"labelsTemplate"`
// MatchFeatures specifies a set of matcher terms all of which must match.
// +optional
MatchFeatures FeatureMatcher `json:"matchFeatures"`
// MatchAny specifies a list of matchers one of which must match.
// +optional
MatchAny []MatchAnyElem `json:"matchAny"`
}
// MatchAnyElem specifies one sub-matcher of MatchAny.
type MatchAnyElem struct {
// MatchFeatures specifies a set of matcher terms all of which must match.
MatchFeatures FeatureMatcher `json:"matchFeatures"`
}
// FeatureMatcher specifies a set of feature matcher terms (i.e. per-feature
// matchers), all of which must match.
type FeatureMatcher []FeatureMatcherTerm
// FeatureMatcherTerm defines requirements against one feature set. All
// requirements (specified as MatchExpressions) are evaluated against each
// element in the feature set.
type FeatureMatcherTerm struct {
Feature string `json:"feature"`
MatchExpressions MatchExpressionSet `json:"matchExpressions"`
}
// MatchExpressionSet contains a set of MatchExpressions, each of which is
// evaluated against a set of input values.
type MatchExpressionSet struct {
Expressions `json:",inline"`
}
// Expressions is a helper type to work around issues with k8s deepcopy-gen
type Expressions map[string]*MatchExpression
// MatchExpression specifies an expression to evaluate against a set of input
// values. It contains an operator that is applied when matching the input and
// an array of values that the operator evaluates the input against.
//
// NB: CreateMatchExpression or MustCreateMatchExpression() should be used for
// creating new instances.
// NB: Validate() must be called if Op or Value fields are modified or if a new
// instance is created from scratch without using the helper functions.
type MatchExpression struct {
// Op is the operator to be applied.
Op MatchOp `json:"op"`
// Value is the list of values that the operand evaluates the input
// against. Value should be empty if the operator is Exists, DoesNotExist,
// IsTrue or IsFalse. Value should contain exactly one element if the
// operator is Gt or Lt and exactly two elements if the operator is GtLt.
// In other cases Value should contain at least one element.
// +optional
Value MatchValue `json:"value,omitempty"`
// valueRe caches compiled regexps for "InRegexp" operator
valueRe valueRegexpCache `json:"-"`
}
// 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"
type MatchOp string
// MatchValue is the list of values associated with a MatchExpression.
type MatchValue []string
const (
// MatchAny returns always true.
MatchAny MatchOp = ""
// MatchIn returns true if any of the values stored in the expression is
// equal to the input.
MatchIn MatchOp = "In"
// MatchIn returns true if none of the values in the expression are equal
// to the input.
MatchNotIn MatchOp = "NotIn"
// MatchInRegexp treats values of the expression as regular expressions and
// returns true if any of them matches the input.
MatchInRegexp MatchOp = "InRegexp"
// MatchExists returns true if the input is valid. The expression must not
// have any values.
MatchExists MatchOp = "Exists"
// MatchDoesNotExist returns true if the input is not valid. The expression
// must not have any values.
MatchDoesNotExist MatchOp = "DoesNotExist"
// MatchGt returns true if the input is greater than the value of the
// expression (number of values in the expression must be exactly one).
// Both the input and value must be integer numbers, otherwise an error is
// returned.
MatchGt MatchOp = "Gt"
// MatchLt returns true if the input is less than the value of the
// expression (number of values in the expression must be exactly one).
// Both the input and value must be integer numbers, otherwise an error is
// returned.
MatchLt MatchOp = "Lt"
// MatchGtLt returns true if the input is between two values, i.e. greater
// than the first value and less than the second value of the expression
// (number of values in the expression must be exactly two). Both the input
// and values must be integer numbers, otherwise an error is returned.
MatchGtLt MatchOp = "GtLt"
// MatchIsTrue returns true if the input holds the value "true". The
// expression must not have any values.
MatchIsTrue MatchOp = "IsTrue"
// MatchIsTrue returns true if the input holds the value "false". The
// expression must not have any values.
MatchIsFalse MatchOp = "IsFalse"
)

View file

@ -0,0 +1,284 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
// Code generated by controller-gen. DO NOT EDIT.
package v1alpha1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in Expressions) DeepCopyInto(out *Expressions) {
{
in := &in
*out = make(Expressions, len(*in))
for key, val := range *in {
var outVal *MatchExpression
if val == nil {
(*out)[key] = nil
} else {
in, out := &val, &outVal
*out = new(MatchExpression)
(*in).DeepCopyInto(*out)
}
(*out)[key] = outVal
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Expressions.
func (in Expressions) DeepCopy() Expressions {
if in == nil {
return nil
}
out := new(Expressions)
in.DeepCopyInto(out)
return *out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in FeatureMatcher) DeepCopyInto(out *FeatureMatcher) {
{
in := &in
*out = make(FeatureMatcher, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FeatureMatcher.
func (in FeatureMatcher) DeepCopy() FeatureMatcher {
if in == nil {
return nil
}
out := new(FeatureMatcher)
in.DeepCopyInto(out)
return *out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *FeatureMatcherTerm) DeepCopyInto(out *FeatureMatcherTerm) {
*out = *in
in.MatchExpressions.DeepCopyInto(&out.MatchExpressions)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FeatureMatcherTerm.
func (in *FeatureMatcherTerm) DeepCopy() *FeatureMatcherTerm {
if in == nil {
return nil
}
out := new(FeatureMatcherTerm)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MatchAnyElem) DeepCopyInto(out *MatchAnyElem) {
*out = *in
if in.MatchFeatures != nil {
in, out := &in.MatchFeatures, &out.MatchFeatures
*out = make(FeatureMatcher, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MatchAnyElem.
func (in *MatchAnyElem) DeepCopy() *MatchAnyElem {
if in == nil {
return nil
}
out := new(MatchAnyElem)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MatchExpression) DeepCopyInto(out *MatchExpression) {
*out = *in
if in.Value != nil {
in, out := &in.Value, &out.Value
*out = make(MatchValue, len(*in))
copy(*out, *in)
}
in.valueRe.DeepCopyInto(&out.valueRe)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MatchExpression.
func (in *MatchExpression) DeepCopy() *MatchExpression {
if in == nil {
return nil
}
out := new(MatchExpression)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MatchExpressionSet) DeepCopyInto(out *MatchExpressionSet) {
*out = *in
if in.Expressions != nil {
in, out := &in.Expressions, &out.Expressions
*out = make(Expressions, len(*in))
for key, val := range *in {
var outVal *MatchExpression
if val == nil {
(*out)[key] = nil
} else {
in, out := &val, &outVal
*out = new(MatchExpression)
(*in).DeepCopyInto(*out)
}
(*out)[key] = outVal
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MatchExpressionSet.
func (in *MatchExpressionSet) DeepCopy() *MatchExpressionSet {
if in == nil {
return nil
}
out := new(MatchExpressionSet)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in MatchValue) DeepCopyInto(out *MatchValue) {
{
in := &in
*out = make(MatchValue, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MatchValue.
func (in MatchValue) DeepCopy() MatchValue {
if in == nil {
return nil
}
out := new(MatchValue)
in.DeepCopyInto(out)
return *out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NodeFeatureRule) DeepCopyInto(out *NodeFeatureRule) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeFeatureRule.
func (in *NodeFeatureRule) DeepCopy() *NodeFeatureRule {
if in == nil {
return nil
}
out := new(NodeFeatureRule)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *NodeFeatureRule) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NodeFeatureRuleList) DeepCopyInto(out *NodeFeatureRuleList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]NodeFeatureRule, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeFeatureRuleList.
func (in *NodeFeatureRuleList) DeepCopy() *NodeFeatureRuleList {
if in == nil {
return nil
}
out := new(NodeFeatureRuleList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *NodeFeatureRuleList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NodeFeatureRuleSpec) DeepCopyInto(out *NodeFeatureRuleSpec) {
*out = *in
if in.Rules != nil {
in, out := &in.Rules, &out.Rules
*out = make([]Rule, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeFeatureRuleSpec.
func (in *NodeFeatureRuleSpec) DeepCopy() *NodeFeatureRuleSpec {
if in == nil {
return nil
}
out := new(NodeFeatureRuleSpec)
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
if in.Labels != nil {
in, out := &in.Labels, &out.Labels
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
if in.MatchFeatures != nil {
in, out := &in.MatchFeatures, &out.MatchFeatures
*out = make(FeatureMatcher, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.MatchAny != nil {
in, out := &in.MatchAny, &out.MatchAny
*out = make([]MatchAnyElem, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Rule.
func (in *Rule) DeepCopy() *Rule {
if in == nil {
return nil
}
out := new(Rule)
in.DeepCopyInto(out)
return out
}

View file

@ -26,9 +26,9 @@ import (
"sigs.k8s.io/yaml"
"sigs.k8s.io/node-feature-discovery/pkg/api/feature"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
"sigs.k8s.io/node-feature-discovery/pkg/utils"
"sigs.k8s.io/node-feature-discovery/source"
"sigs.k8s.io/node-feature-discovery/source/custom/expression"
"sigs.k8s.io/node-feature-discovery/source/custom/rules"
)
@ -65,7 +65,7 @@ type FeatureMatcher []FeatureMatcherTerm
type FeatureMatcherTerm struct {
Feature string
MatchExpressions expression.MatchExpressionSet
MatchExpressions nfdv1alpha1.MatchExpressionSet
}
type config []CustomRule

View file

@ -21,7 +21,7 @@ import (
"github.com/stretchr/testify/assert"
"sigs.k8s.io/node-feature-discovery/pkg/api/feature"
"sigs.k8s.io/node-feature-discovery/source/custom/expression"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
)
func TestRule(t *testing.T) {
@ -31,8 +31,12 @@ func TestRule(t *testing.T) {
Labels: map[string]string{"label-1": "label-val-1"},
MatchFeatures: FeatureMatcher{
FeatureMatcherTerm{
Feature: "domain-1.kf-1",
MatchExpressions: expression.MatchExpressionSet{"key-1": expression.MustCreateMatchExpression(expression.MatchExists)},
Feature: "domain-1.kf-1",
MatchExpressions: nfdv1alpha1.MatchExpressionSet{
Expressions: nfdv1alpha1.Expressions{
"key-1": nfdv1alpha1.MustCreateMatchExpression(nfdv1alpha1.MatchExists),
},
},
},
},
}
@ -94,8 +98,12 @@ func TestRule(t *testing.T) {
Labels: map[string]string{"label-3": "label-val-3", "empty": ""},
MatchFeatures: FeatureMatcher{
FeatureMatcherTerm{
Feature: "domain-1.vf-1",
MatchExpressions: expression.MatchExpressionSet{"key-1": expression.MustCreateMatchExpression(expression.MatchIn, "val-1")},
Feature: "domain-1.vf-1",
MatchExpressions: nfdv1alpha1.MatchExpressionSet{
Expressions: nfdv1alpha1.Expressions{
"key-1": nfdv1alpha1.MustCreateMatchExpression(nfdv1alpha1.MatchIn, "val-1"),
},
},
},
},
}
@ -113,8 +121,12 @@ func TestRule(t *testing.T) {
Labels: map[string]string{"label-4": "label-val-4"},
MatchFeatures: FeatureMatcher{
FeatureMatcherTerm{
Feature: "domain-1.if-1",
MatchExpressions: expression.MatchExpressionSet{"attr-1": expression.MustCreateMatchExpression(expression.MatchIn, "val-1")},
Feature: "domain-1.if-1",
MatchExpressions: nfdv1alpha1.MatchExpressionSet{
Expressions: nfdv1alpha1.Expressions{
"attr-1": nfdv1alpha1.MustCreateMatchExpression(nfdv1alpha1.MatchIn, "val-1"),
},
},
},
},
}
@ -132,12 +144,20 @@ func TestRule(t *testing.T) {
Labels: map[string]string{"label-5": "label-val-5"},
MatchFeatures: FeatureMatcher{
FeatureMatcherTerm{
Feature: "domain-1.vf-1",
MatchExpressions: expression.MatchExpressionSet{"key-1": expression.MustCreateMatchExpression(expression.MatchIn, "val-x")},
Feature: "domain-1.vf-1",
MatchExpressions: nfdv1alpha1.MatchExpressionSet{
Expressions: nfdv1alpha1.Expressions{
"key-1": nfdv1alpha1.MustCreateMatchExpression(nfdv1alpha1.MatchIn, "val-x"),
},
},
},
FeatureMatcherTerm{
Feature: "domain-1.if-1",
MatchExpressions: expression.MatchExpressionSet{"attr-1": expression.MustCreateMatchExpression(expression.MatchIn, "val-1")},
Feature: "domain-1.if-1",
MatchExpressions: nfdv1alpha1.MatchExpressionSet{
Expressions: nfdv1alpha1.Expressions{
"attr-1": nfdv1alpha1.MustCreateMatchExpression(nfdv1alpha1.MatchIn, "val-1"),
},
},
},
},
}
@ -145,7 +165,7 @@ func TestRule(t *testing.T) {
assert.Nilf(t, err, "unexpected error: %v", err)
assert.Nil(t, m, "instances should not have matched")
r5.MatchFeatures[0].MatchExpressions["key-1"] = expression.MustCreateMatchExpression(expression.MatchIn, "val-1")
r5.MatchFeatures[0].MatchExpressions.Expressions["key-1"] = nfdv1alpha1.MustCreateMatchExpression(nfdv1alpha1.MatchIn, "val-1")
m, err = r5.execute(f)
assert.Nilf(t, err, "unexpected error: %v", err)
assert.Equal(t, r5.Labels, m, "instances should have matched")
@ -155,8 +175,12 @@ func TestRule(t *testing.T) {
MatchAnyElem{
MatchFeatures: FeatureMatcher{
FeatureMatcherTerm{
Feature: "domain-1.kf-1",
MatchExpressions: expression.MatchExpressionSet{"key-na": expression.MustCreateMatchExpression(expression.MatchExists)},
Feature: "domain-1.kf-1",
MatchExpressions: nfdv1alpha1.MatchExpressionSet{
Expressions: nfdv1alpha1.Expressions{
"key-na": nfdv1alpha1.MustCreateMatchExpression(nfdv1alpha1.MatchExists),
},
},
},
},
},
@ -169,12 +193,16 @@ func TestRule(t *testing.T) {
MatchAnyElem{
MatchFeatures: FeatureMatcher{
FeatureMatcherTerm{
Feature: "domain-1.kf-1",
MatchExpressions: expression.MatchExpressionSet{"key-1": expression.MustCreateMatchExpression(expression.MatchExists)},
Feature: "domain-1.kf-1",
MatchExpressions: nfdv1alpha1.MatchExpressionSet{
Expressions: nfdv1alpha1.Expressions{
"key-1": nfdv1alpha1.MustCreateMatchExpression(nfdv1alpha1.MatchExists),
},
},
},
},
})
r5.MatchFeatures[0].MatchExpressions["key-1"] = expression.MustCreateMatchExpression(expression.MatchIn, "val-1")
r5.MatchFeatures[0].MatchExpressions.Expressions["key-1"] = nfdv1alpha1.MustCreateMatchExpression(nfdv1alpha1.MatchIn, "val-1")
m, err = r5.execute(f)
assert.Nilf(t, err, "unexpected error: %v", err)
assert.Equal(t, r5.Labels, m, "instances should have matched")

View file

@ -1,439 +0,0 @@
/*
Copyright 2021 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 expression_test
import (
"testing"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/yaml"
"sigs.k8s.io/node-feature-discovery/pkg/api/feature"
e "sigs.k8s.io/node-feature-discovery/source/custom/expression"
)
type BoolAssertionFuncf func(assert.TestingT, bool, string, ...interface{}) bool
type ValueAssertionFuncf func(assert.TestingT, interface{}, string, ...interface{}) bool
func TestCreateMatchExpression(t *testing.T) {
type V = e.MatchValue
type TC struct {
op e.MatchOp
values V
err ValueAssertionFuncf
}
tcs := []TC{
{op: e.MatchAny, err: assert.Nilf}, // #0
{op: e.MatchAny, values: V{"1"}, err: assert.NotNilf},
{op: e.MatchIn, err: assert.NotNilf},
{op: e.MatchIn, values: V{"1"}, err: assert.Nilf},
{op: e.MatchIn, values: V{"1", "2", "3", "4"}, err: assert.Nilf},
{op: e.MatchNotIn, err: assert.NotNilf},
{op: e.MatchNotIn, values: V{"1"}, err: assert.Nilf},
{op: e.MatchNotIn, values: V{"1", "2"}, err: assert.Nilf},
{op: e.MatchInRegexp, err: assert.NotNilf},
{op: e.MatchInRegexp, values: V{"1"}, err: assert.Nilf},
{op: e.MatchInRegexp, values: V{"()", "2", "3"}, err: assert.Nilf},
{op: e.MatchInRegexp, values: V{"("}, err: assert.NotNilf},
{op: e.MatchExists, err: assert.Nilf},
{op: e.MatchExists, values: V{"1"}, err: assert.NotNilf},
{op: e.MatchDoesNotExist, err: assert.Nilf},
{op: e.MatchDoesNotExist, values: V{"1"}, err: assert.NotNilf},
{op: e.MatchGt, err: assert.NotNilf},
{op: e.MatchGt, values: V{"1"}, err: assert.Nilf},
{op: e.MatchGt, values: V{"-10"}, err: assert.Nilf},
{op: e.MatchGt, values: V{"1", "2"}, err: assert.NotNilf},
{op: e.MatchGt, values: V{""}, err: assert.NotNilf},
{op: e.MatchLt, err: assert.NotNilf},
{op: e.MatchLt, values: V{"1"}, err: assert.Nilf},
{op: e.MatchLt, values: V{"-1"}, err: assert.Nilf},
{op: e.MatchLt, values: V{"1", "2", "3"}, err: assert.NotNilf},
{op: e.MatchLt, values: V{"a"}, err: assert.NotNilf},
{op: e.MatchGtLt, err: assert.NotNilf},
{op: e.MatchGtLt, values: V{"1"}, err: assert.NotNilf},
{op: e.MatchGtLt, values: V{"1", "2"}, err: assert.Nilf},
{op: e.MatchGtLt, values: V{"2", "1"}, err: assert.NotNilf},
{op: e.MatchGtLt, values: V{"1", "2", "3"}, err: assert.NotNilf},
{op: e.MatchGtLt, values: V{"a", "2"}, err: assert.NotNilf},
{op: e.MatchIsTrue, err: assert.Nilf},
{op: e.MatchIsTrue, values: V{"1"}, err: assert.NotNilf},
{op: e.MatchIsFalse, err: assert.Nilf},
{op: e.MatchIsFalse, values: V{"1", "2"}, err: assert.NotNilf},
}
for i, tc := range tcs {
_, err := e.CreateMatchExpression(tc.op, tc.values...)
tc.err(t, err, "test case #%d (%v) failed", i, tc)
}
}
func TestMatch(t *testing.T) {
type V = e.MatchValue
type TC struct {
op e.MatchOp
values V
input interface{}
valid bool
result BoolAssertionFuncf
err ValueAssertionFuncf
}
tcs := []TC{
{op: e.MatchAny, result: assert.Truef, err: assert.Nilf},
{op: e.MatchAny, input: "2", valid: false, result: assert.Truef, err: assert.Nilf},
{op: e.MatchIn, values: V{"1"}, input: "2", valid: false, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchIn, values: V{"1"}, input: "2", valid: true, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchIn, values: V{"1", "2", "3"}, input: "2", valid: false, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchIn, values: V{"1", "2", "3"}, input: "2", valid: true, result: assert.Truef, err: assert.Nilf},
{op: e.MatchNotIn, values: V{"2"}, input: 2, valid: false, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchNotIn, values: V{"1"}, input: 2, valid: true, result: assert.Truef, err: assert.Nilf},
{op: e.MatchNotIn, values: V{"1", "2", "3"}, input: "2", valid: false, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchNotIn, values: V{"1", "2", "3"}, input: "2", valid: true, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchInRegexp, values: V{"val-[0-9]$"}, input: "val-1", valid: false, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchInRegexp, values: V{"val-[0-9]$"}, input: "val-1", valid: true, result: assert.Truef, err: assert.Nilf},
{op: e.MatchInRegexp, values: V{"val-[0-9]$"}, input: "val-12", valid: true, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchInRegexp, values: V{"val-[0-9]$", "al-[1-9]"}, input: "val-12", valid: true, result: assert.Truef, err: assert.Nilf},
{op: e.MatchExists, input: nil, valid: false, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchExists, input: nil, valid: true, result: assert.Truef, err: assert.Nilf},
{op: e.MatchDoesNotExist, input: false, valid: false, result: assert.Truef, err: assert.Nilf},
{op: e.MatchDoesNotExist, input: false, valid: true, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchGt, values: V{"2"}, input: 3, valid: false, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchGt, values: V{"2"}, input: 2, valid: true, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchGt, values: V{"2"}, input: 3, valid: true, result: assert.Truef, err: assert.Nilf},
{op: e.MatchGt, values: V{"-10"}, input: -3, valid: true, result: assert.Truef, err: assert.Nilf},
{op: e.MatchGt, values: V{"2"}, input: "3a", valid: true, result: assert.Falsef, err: assert.NotNilf},
{op: e.MatchLt, values: V{"2"}, input: "1", valid: false, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchLt, values: V{"2"}, input: "2", valid: true, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchLt, values: V{"-10"}, input: -3, valid: true, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchLt, values: V{"2"}, input: "1", valid: true, result: assert.Truef, err: assert.Nilf},
{op: e.MatchLt, values: V{"2"}, input: "1.0", valid: true, result: assert.Falsef, err: assert.NotNilf},
{op: e.MatchGtLt, values: V{"1", "10"}, input: "1", valid: false, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchGtLt, values: V{"1", "10"}, input: "1", valid: true, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchGtLt, values: V{"1", "10"}, input: "10", valid: true, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchGtLt, values: V{"1", "10"}, input: "2", valid: true, result: assert.Truef, err: assert.Nilf},
{op: e.MatchGtLt, values: V{"1", "10"}, input: "1.0", valid: true, result: assert.Falsef, err: assert.NotNilf},
{op: e.MatchIsTrue, input: true, valid: false, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchIsTrue, input: true, valid: true, result: assert.Truef, err: assert.Nilf},
{op: e.MatchIsTrue, input: false, valid: true, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchIsFalse, input: "false", valid: false, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchIsFalse, input: "false", valid: true, result: assert.Truef, err: assert.Nilf},
{op: e.MatchIsFalse, input: "true", valid: true, result: assert.Falsef, err: assert.Nilf},
}
for i, tc := range tcs {
me := e.MustCreateMatchExpression(tc.op, tc.values...)
res, err := me.Match(tc.valid, tc.input)
tc.result(t, res, "test case #%d (%v) failed", i, tc)
tc.err(t, err, "test case #%d (%v) failed", i, tc)
}
// Check some special error cases separately because MustCreateMatch panics
tcs = []TC{
{op: e.MatchGt, values: V{"3.0"}, input: 1, valid: true},
{op: e.MatchLt, values: V{"0x2"}, input: 1, valid: true},
{op: e.MatchGtLt, values: V{"1", "str"}, input: 1, valid: true},
{op: "non-existent-op", values: V{"1"}, input: 1, valid: true},
}
for i, tc := range tcs {
me := e.MatchExpression{Op: tc.op, Value: tc.values}
res, err := me.Match(tc.valid, tc.input)
assert.Falsef(t, res, "err test case #%d (%v) failed", i, tc)
assert.NotNilf(t, err, "err test case #%d (%v) failed", i, tc)
}
}
func TestMatchKeys(t *testing.T) {
type V = e.MatchValue
type I = map[string]feature.Nil
type TC struct {
op e.MatchOp
values V
name string
input I
result BoolAssertionFuncf
err ValueAssertionFuncf
}
tcs := []TC{
{op: e.MatchAny, result: assert.Truef, err: assert.Nilf},
{op: e.MatchExists, name: "foo", input: nil, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchExists, name: "foo", input: I{"bar": {}}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchExists, name: "foo", input: I{"bar": {}, "foo": {}}, result: assert.Truef, err: assert.Nilf},
{op: e.MatchDoesNotExist, name: "foo", input: nil, result: assert.Truef, err: assert.Nilf},
{op: e.MatchDoesNotExist, name: "foo", input: I{}, result: assert.Truef, err: assert.Nilf},
{op: e.MatchDoesNotExist, name: "foo", input: I{"bar": {}}, result: assert.Truef, err: assert.Nilf},
{op: e.MatchDoesNotExist, name: "foo", input: I{"bar": {}, "foo": {}}, result: assert.Falsef, err: assert.Nilf},
// All other ops should return an error
{op: e.MatchIn, values: V{"foo"}, name: "foo", result: assert.Falsef, err: assert.NotNilf},
{op: e.MatchNotIn, values: V{"foo"}, name: "foo", result: assert.Falsef, err: assert.NotNilf},
{op: e.MatchInRegexp, values: V{"foo"}, name: "foo", result: assert.Falsef, err: assert.NotNilf},
{op: e.MatchGt, values: V{"1"}, name: "foo", result: assert.Falsef, err: assert.NotNilf},
{op: e.MatchLt, values: V{"1"}, name: "foo", result: assert.Falsef, err: assert.NotNilf},
{op: e.MatchGtLt, values: V{"1", "10"}, name: "foo", result: assert.Falsef, err: assert.NotNilf},
{op: e.MatchIsTrue, name: "foo", result: assert.Falsef, err: assert.NotNilf},
{op: e.MatchIsFalse, name: "foo", result: assert.Falsef, err: assert.NotNilf},
}
for i, tc := range tcs {
me := e.MustCreateMatchExpression(tc.op, tc.values...)
res, err := me.MatchKeys(tc.name, tc.input)
tc.result(t, res, "test case #%d (%v) failed", i, tc)
tc.err(t, err, "test case #%d (%v) failed", i, tc)
}
}
func TestMatchValues(t *testing.T) {
type V = []string
type I = map[string]string
type TC struct {
op e.MatchOp
values V
name string
input I
result BoolAssertionFuncf
err ValueAssertionFuncf
}
tcs := []TC{
{op: e.MatchAny, result: assert.Truef, err: assert.Nilf},
{op: e.MatchIn, values: V{"1", "2"}, name: "foo", input: I{"bar": "2"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchIn, values: V{"1", "2"}, name: "foo", input: I{"foo": "3"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchIn, values: V{"1", "2"}, name: "foo", input: I{"foo": "2"}, result: assert.Truef, err: assert.Nilf},
{op: e.MatchNotIn, values: V{"1", "2"}, name: "foo", input: I{"bar": "2"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchNotIn, values: V{"1", "2"}, name: "foo", input: I{"foo": "3"}, result: assert.Truef, err: assert.Nilf},
{op: e.MatchNotIn, values: V{"1", "2"}, name: "foo", input: I{"foo": "2"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchInRegexp, values: V{"1", "2"}, name: "foo", input: I{"bar": "2"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchInRegexp, values: V{"1", "[0-8]"}, name: "foo", input: I{"foo": "9"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchInRegexp, values: V{"1", "[0-8]"}, name: "foo", input: I{"foo": "2"}, result: assert.Truef, err: assert.Nilf},
{op: e.MatchExists, name: "foo", input: I{"bar": "1"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchExists, name: "foo", input: I{"foo": "1"}, result: assert.Truef, err: assert.Nilf},
{op: e.MatchDoesNotExist, name: "foo", input: nil, result: assert.Truef, err: assert.Nilf},
{op: e.MatchDoesNotExist, name: "foo", input: I{"foo": "1"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchGt, values: V{"2"}, name: "foo", input: I{"bar": "3"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchGt, values: V{"2"}, name: "foo", input: I{"bar": "3", "foo": "2"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchGt, values: V{"2"}, name: "foo", input: I{"bar": "3", "foo": "3"}, result: assert.Truef, err: assert.Nilf},
{op: e.MatchGt, values: V{"2"}, name: "foo", input: I{"bar": "str", "foo": "str"}, result: assert.Falsef, err: assert.NotNilf},
{op: e.MatchLt, values: V{"2"}, name: "foo", input: I{"bar": "1"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchLt, values: V{"2"}, name: "foo", input: I{"bar": "1", "foo": "2"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchLt, values: V{"2"}, name: "foo", input: I{"bar": "1", "foo": "1"}, result: assert.Truef, err: assert.Nilf},
{op: e.MatchLt, values: V{"2"}, name: "foo", input: I{"bar": "str", "foo": "str"}, result: assert.Falsef, err: assert.NotNilf},
{op: e.MatchGtLt, values: V{"-10", "10"}, name: "foo", input: I{"bar": "1"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchGtLt, values: V{"-10", "10"}, name: "foo", input: I{"bar": "1", "foo": "11"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchGtLt, values: V{"-10", "10"}, name: "foo", input: I{"bar": "1", "foo": "-11"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchGtLt, values: V{"-10", "10"}, name: "foo", input: I{"bar": "1", "foo": "1"}, result: assert.Truef, err: assert.Nilf},
{op: e.MatchGtLt, values: V{"-10", "10"}, name: "foo", input: I{"bar": "str", "foo": "str"}, result: assert.Falsef, err: assert.NotNilf},
{op: e.MatchIsTrue, name: "foo", result: assert.Falsef, err: assert.Nilf},
{op: e.MatchIsTrue, name: "foo", input: I{"foo": "1"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchIsTrue, name: "foo", input: I{"foo": "true"}, result: assert.Truef, err: assert.Nilf},
{op: e.MatchIsFalse, name: "foo", input: I{"foo": "true"}, result: assert.Falsef, err: assert.Nilf},
{op: e.MatchIsFalse, name: "foo", input: I{"foo": "false"}, result: assert.Truef, err: assert.Nilf},
}
for i, tc := range tcs {
me := e.MustCreateMatchExpression(tc.op, tc.values...)
res, err := me.MatchValues(tc.name, tc.input)
tc.result(t, res, "test case #%d (%v) failed", i, tc)
tc.err(t, err, "test case #%d (%v) failed", i, tc)
}
}
func TestMESMatchKeys(t *testing.T) {
type I = map[string]feature.Nil
type TC struct {
mes string
input I
result BoolAssertionFuncf
err ValueAssertionFuncf
}
tcs := []TC{
{result: assert.Truef, err: assert.Nilf},
{input: I{"foo": {}}, result: assert.Truef, err: assert.Nilf},
{mes: `
foo: { op: DoesNotExist }
bar: { op: Exists }
`,
input: I{"bar": {}, "baz": {}},
result: assert.Truef, err: assert.Nilf},
{mes: `
foo: { op: DoesNotExist }
bar: { op: Exists }
`,
input: I{"foo": {}, "bar": {}, "baz": {}},
result: assert.Falsef, err: assert.Nilf},
{mes: `
foo: { op: In, value: ["bar"] }
bar: { op: Exists }
`,
input: I{"bar": {}, "baz": {}},
result: assert.Falsef, err: assert.NotNilf},
}
for i, tc := range tcs {
mes := &e.MatchExpressionSet{}
if err := yaml.Unmarshal([]byte(tc.mes), mes); err != nil {
t.Fatalf("failed to parse data of test case #%d (%v): %v", i, tc, err)
}
res, err := mes.MatchKeys(tc.input)
tc.result(t, res, "test case #%d (%v) failed", i, tc)
tc.err(t, err, "test case #%d (%v) failed", i, tc)
}
}
func TestMESMatchValues(t *testing.T) {
type I = map[string]string
type TC struct {
mes string
input I
result BoolAssertionFuncf
err ValueAssertionFuncf
}
tcs := []TC{
{result: assert.Truef, err: assert.Nilf},
{input: I{"foo": "bar"}, result: assert.Truef, err: assert.Nilf},
{mes: `
foo: { op: Exists }
bar: { op: In, value: ["val", "wal"] }
baz: { op: Gt, value: ["10"] }
`,
input: I{"bar": "val"},
result: assert.Falsef, err: assert.Nilf},
{mes: `
foo: { op: Exists }
bar: { op: In, value: ["val", "wal"] }
baz: { op: Gt, value: ["10"] }
`,
input: I{"foo": "1", "bar": "val", "baz": "123"},
result: assert.Truef, err: assert.Nilf},
{mes: `
foo: { op: Exists }
bar: { op: In, value: ["val"] }
baz: { op: Gt, value: ["10"] }
`,
input: I{"foo": "1", "bar": "val", "baz": "123.0"},
result: assert.Falsef, err: assert.NotNilf},
}
for i, tc := range tcs {
mes := &e.MatchExpressionSet{}
if err := yaml.Unmarshal([]byte(tc.mes), mes); err != nil {
t.Fatalf("failed to parse data of test case #%d (%v): %v", i, tc, err)
}
res, err := mes.MatchValues(tc.input)
tc.result(t, res, "test case #%d (%v) failed", i, tc)
tc.err(t, err, "test case #%d (%v) failed", i, tc)
}
}
func TestMESMatchInstances(t *testing.T) {
type I = feature.InstanceFeature
type A = map[string]string
type TC struct {
mes string
input []I
result BoolAssertionFuncf
err ValueAssertionFuncf
}
tcs := []TC{
{result: assert.Falsef, err: assert.Nilf}, // nil instances -> false
{input: []I{}, result: assert.Falsef, err: assert.Nilf}, // zero instances -> false
{input: []I{I{Attributes: A{}}}, result: assert.Truef, err: assert.Nilf}, // one "empty" instance
{mes: `
foo: { op: Exists }
bar: { op: Lt, value: ["10"] }
`,
input: []I{I{Attributes: A{"foo": "1"}}, I{Attributes: A{"bar": "1"}}},
result: assert.Falsef, err: assert.Nilf},
{mes: `
foo: { op: Exists }
bar: { op: Lt, value: ["10"] }
`,
input: []I{I{Attributes: A{"foo": "1"}}, I{Attributes: A{"foo": "2", "bar": "1"}}},
result: assert.Truef, err: assert.Nilf},
{mes: `
bar: { op: Lt, value: ["10"] }
`,
input: []I{I{Attributes: A{"foo": "1"}}, I{Attributes: A{"bar": "0x1"}}},
result: assert.Falsef, err: assert.NotNilf},
}
for i, tc := range tcs {
mes := &e.MatchExpressionSet{}
if err := yaml.Unmarshal([]byte(tc.mes), mes); err != nil {
t.Fatalf("failed to parse data of test case #%d (%v): %v", i, tc, err)
}
res, err := mes.MatchInstances(tc.input)
tc.result(t, res, "test case #%d (%v) failed", i, tc)
tc.err(t, err, "test case #%d (%v) failed", i, tc)
}
}

View file

@ -19,14 +19,14 @@ package rules
import (
"fmt"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
"sigs.k8s.io/node-feature-discovery/source"
"sigs.k8s.io/node-feature-discovery/source/cpu"
"sigs.k8s.io/node-feature-discovery/source/custom/expression"
)
// CpuIDRule implements Rule for the custom source
type CpuIDRule struct {
expression.MatchExpressionSet
nfdv1alpha1.MatchExpressionSet
}
func (r *CpuIDRule) Match() (bool, error) {

View file

@ -19,14 +19,14 @@ package rules
import (
"fmt"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
"sigs.k8s.io/node-feature-discovery/source"
"sigs.k8s.io/node-feature-discovery/source/custom/expression"
"sigs.k8s.io/node-feature-discovery/source/kernel"
)
// KconfigRule implements Rule for the custom source
type KconfigRule struct {
expression.MatchExpressionSet
nfdv1alpha1.MatchExpressionSet
}
func (r *KconfigRule) Match() (bool, error) {

View file

@ -19,14 +19,14 @@ package rules
import (
"fmt"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
"sigs.k8s.io/node-feature-discovery/source"
"sigs.k8s.io/node-feature-discovery/source/custom/expression"
"sigs.k8s.io/node-feature-discovery/source/kernel"
)
// LoadedKModRule matches loaded kernel modules in the system
type LoadedKModRule struct {
expression.MatchExpressionSet
nfdv1alpha1.MatchExpressionSet
}
// Match loaded kernel modules on provided list of kernel modules

View file

@ -20,14 +20,14 @@ import (
"encoding/json"
"fmt"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
"sigs.k8s.io/node-feature-discovery/source"
"sigs.k8s.io/node-feature-discovery/source/custom/expression"
"sigs.k8s.io/node-feature-discovery/source/system"
)
// NodenameRule matches on nodenames configured in a ConfigMap
type NodenameRule struct {
expression.MatchExpression
nfdv1alpha1.MatchExpression
}
func (r *NodenameRule) Match() (bool, error) {
@ -43,8 +43,8 @@ func (r *NodenameRule) UnmarshalJSON(data []byte) error {
return err
}
// Force regexp matching
if r.Op == expression.MatchIn {
r.Op = expression.MatchInRegexp
if r.Op == nfdv1alpha1.MatchIn {
r.Op = nfdv1alpha1.MatchInRegexp
}
// We need to run Validate() because operator forcing above
return r.Validate()

View file

@ -19,13 +19,13 @@ package rules
import (
"fmt"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
"sigs.k8s.io/node-feature-discovery/source"
"sigs.k8s.io/node-feature-discovery/source/custom/expression"
"sigs.k8s.io/node-feature-discovery/source/pci"
)
type PciIDRule struct {
expression.MatchExpressionSet
nfdv1alpha1.MatchExpressionSet
}
// Match PCI devices on provided PCI device attributes

View file

@ -19,13 +19,13 @@ package rules
import (
"fmt"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
"sigs.k8s.io/node-feature-discovery/source"
"sigs.k8s.io/node-feature-discovery/source/custom/expression"
"sigs.k8s.io/node-feature-discovery/source/usb"
)
type UsbIDRule struct {
expression.MatchExpressionSet
nfdv1alpha1.MatchExpressionSet
}
// Match USB devices on provided USB device attributes

View file

@ -17,7 +17,7 @@ limitations under the License.
package custom
import (
"sigs.k8s.io/node-feature-discovery/source/custom/expression"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/v1alpha1"
"sigs.k8s.io/node-feature-discovery/source/custom/rules"
)
@ -31,8 +31,10 @@ func getStaticFeatureConfig() []CustomRule {
MatchOn: []LegacyMatcher{
{
PciID: &rules.PciIDRule{
MatchExpressionSet: expression.MatchExpressionSet{
"vendor": expression.MustCreateMatchExpression(expression.MatchIn, "15b3"),
MatchExpressionSet: nfdv1alpha1.MatchExpressionSet{
Expressions: nfdv1alpha1.Expressions{
"vendor": nfdv1alpha1.MustCreateMatchExpression(nfdv1alpha1.MatchIn, "15b3"),
},
},
},
},
@ -45,9 +47,11 @@ func getStaticFeatureConfig() []CustomRule {
MatchOn: []LegacyMatcher{
{
LoadedKMod: &rules.LoadedKModRule{
MatchExpressionSet: expression.MatchExpressionSet{
"ib_uverbs": expression.MustCreateMatchExpression(expression.MatchExists),
"rdma_ucm": expression.MustCreateMatchExpression(expression.MatchExists),
MatchExpressionSet: nfdv1alpha1.MatchExpressionSet{
Expressions: nfdv1alpha1.Expressions{
"ib_uverbs": nfdv1alpha1.MustCreateMatchExpression(nfdv1alpha1.MatchExists),
"rdma_ucm": nfdv1alpha1.MustCreateMatchExpression(nfdv1alpha1.MatchExists),
},
},
},
},