1
0
Fork 0
mirror of https://github.com/kubernetes-sigs/node-feature-discovery.git synced 2025-03-19 06:43:10 +00:00
node-feature-discovery/pkg/client-nfd/compat/node-validator/node-validator.go
Marcin Franczyk 99d63d3d33 Refactoring of image compatibility node validator.
Additionally, testcases have been separated into distinct
functions for better readability.

Signed-off-by: Marcin Franczyk <marcin0franczyk@gmail.com>
2025-02-03 10:11:36 +01:00

220 lines
6.9 KiB
Go

/*
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 nodevalidator
import (
"context"
"fmt"
"slices"
"sort"
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/api/nfd/v1alpha1"
"sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/nodefeaturerule"
artifactcli "sigs.k8s.io/node-feature-discovery/pkg/client-nfd/compat/artifact-client"
"sigs.k8s.io/node-feature-discovery/source"
// register sources
_ "sigs.k8s.io/node-feature-discovery/source/cpu"
_ "sigs.k8s.io/node-feature-discovery/source/kernel"
_ "sigs.k8s.io/node-feature-discovery/source/memory"
_ "sigs.k8s.io/node-feature-discovery/source/network"
_ "sigs.k8s.io/node-feature-discovery/source/pci"
_ "sigs.k8s.io/node-feature-discovery/source/storage"
_ "sigs.k8s.io/node-feature-discovery/source/system"
_ "sigs.k8s.io/node-feature-discovery/source/usb"
)
// Args holds command line arguments.
type Args struct {
Tags []string
}
type nodeValidator struct {
args Args
artifactClient artifactcli.ArtifactClient
sources map[string]source.FeatureSource
}
// New builds a node validator with specified options.
func New(opts ...NodeValidatorOpts) nodeValidator {
n := nodeValidator{}
for _, opt := range opts {
opt.apply(&n)
}
return n
}
// Execute pulls the compatibility artifact to compare described features with the ones discovered on the node.
func (nv *nodeValidator) Execute(ctx context.Context) ([]*CompatibilityStatus, error) {
spec, err := nv.artifactClient.FetchCompatibilitySpec(ctx)
if err != nil {
return nil, fmt.Errorf("failed to fetch compatibility spec: %w", err)
}
for _, s := range nv.sources {
if err := s.Discover(); err != nil {
return nil, fmt.Errorf("error during discovery of source %s: %w", s.Name(), err)
}
}
features := source.GetAllFeatures()
compats := []*CompatibilityStatus{}
for _, c := range spec.Compatibilties {
if len(nv.args.Tags) > 0 && !slices.Contains(nv.args.Tags, c.Tag) {
continue
}
compat := newCompatibilityStatus(&c)
for _, r := range c.Rules {
ruleOut, err := nodefeaturerule.Execute(&r, features, false)
if err != nil {
return nil, err
}
compat.Rules = append(compat.Rules, nv.evaluateRuleStatus(&r, ruleOut.MatchStatus))
// Add the 'rule.matched' feature for backreference functionality
features.InsertAttributeFeatures(nfdv1alpha1.RuleBackrefDomain, nfdv1alpha1.RuleBackrefFeature, ruleOut.Labels)
features.InsertAttributeFeatures(nfdv1alpha1.RuleBackrefDomain, nfdv1alpha1.RuleBackrefFeature, ruleOut.Vars)
}
compats = append(compats, &compat)
}
return compats, nil
}
func (nv *nodeValidator) evaluateRuleStatus(rule *nfdv1alpha1.Rule, matchStatus *nodefeaturerule.MatchStatus) ProcessedRuleStatus {
out := ProcessedRuleStatus{Name: rule.Name, IsMatch: matchStatus.IsMatch}
matchedFeatureTerms := nfdv1alpha1.FeatureMatcher{}
if m := matchStatus.MatchFeatureStatus; m != nil {
matchedFeatureTerms = m.MatchedFeaturesTerms
}
out.MatchedExpressions = nv.matchFeatureExpressions(rule.MatchFeatures, matchedFeatureTerms)
for i, matchAnyElem := range rule.MatchAny {
matchedFeatureTermsAny := nfdv1alpha1.FeatureMatcher{}
if t := matchStatus.MatchAny[i].MatchedFeaturesTerms; t != nil {
matchedFeatureTermsAny = t
}
matchedExpressions := nv.matchFeatureExpressions(matchAnyElem.MatchFeatures, matchedFeatureTermsAny)
out.MatchedAny = append(out.MatchedAny, MatchAnyElem{MatchedExpressions: matchedExpressions})
}
return out
}
func (nv *nodeValidator) matchFeatureExpressions(featureMatcher, matchedFeatureTerms nfdv1alpha1.FeatureMatcher) []MatchedExpression {
var out []MatchedExpression
for _, term := range featureMatcher {
if term.MatchExpressions != nil {
out = append(out, nv.matchExpressions(term, matchedFeatureTerms)...)
}
if term.MatchName != nil {
out = append(out, nv.matchName(term, matchedFeatureTerms))
}
}
// For reproducible output sort by name, feature, expression.
sort.Slice(out, func(i, j int) bool {
if out[i].Feature != out[j].Feature {
return out[i].Feature < out[j].Feature
}
if out[i].Name != out[j].Name {
return out[i].Name < out[j].Name
}
return out[i].Expression.String() < out[j].Expression.String()
})
return out
}
func (nodeValidator) matchExpressions(term nfdv1alpha1.FeatureMatcherTerm, matchedFeatureTerms nfdv1alpha1.FeatureMatcher) []MatchedExpression {
var out []MatchedExpression
for name, exp := range *term.MatchExpressions {
isMatch := false
for _, processedTerm := range matchedFeatureTerms {
if term.Feature != processedTerm.Feature || processedTerm.MatchExpressions == nil {
continue
}
pexp, ok := (*processedTerm.MatchExpressions)[name]
if isMatch = ok && exp.Op == pexp.Op && slices.Equal(exp.Value, pexp.Value); isMatch {
break
}
}
out = append(out, MatchedExpression{
Feature: term.Feature,
Name: name,
Expression: exp,
MatcherType: MatchExpressionType,
IsMatch: isMatch,
})
}
return out
}
func (nodeValidator) matchName(term nfdv1alpha1.FeatureMatcherTerm, matchedFeatureTerms nfdv1alpha1.FeatureMatcher) MatchedExpression {
isMatch := false
for _, processedTerm := range matchedFeatureTerms {
if term.Feature != processedTerm.Feature || processedTerm.MatchName == nil {
continue
}
isMatch = term.MatchName.Op == processedTerm.MatchName.Op && slices.Equal(term.MatchName.Value, processedTerm.MatchName.Value)
if isMatch {
break
}
}
return MatchedExpression{
Feature: term.Feature,
Name: "",
Expression: term.MatchName,
MatcherType: MatchNameType,
IsMatch: isMatch,
}
}
// NodeValidatorOpts applies certain options to the node validator.
type NodeValidatorOpts interface {
apply(*nodeValidator)
}
type nodeValidatorOpt struct {
f func(*nodeValidator)
}
func (o *nodeValidatorOpt) apply(nv *nodeValidator) {
o.f(nv)
}
// WithArgs applies command line arguments to the node validator object.
func WithArgs(args *Args) NodeValidatorOpts {
return &nodeValidatorOpt{f: func(nv *nodeValidator) { nv.args = *args }}
}
// WithArtifactClient applies the client for all artifact operations.
func WithArtifactClient(cli artifactcli.ArtifactClient) NodeValidatorOpts {
return &nodeValidatorOpt{f: func(nv *nodeValidator) { nv.artifactClient = cli }}
}
// WithSources applies the list of enabled feature sources.
func WithSources(sources map[string]source.FeatureSource) NodeValidatorOpts {
return &nodeValidatorOpt{f: func(nv *nodeValidator) { nv.sources = sources }}
}