Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring of image compatibility node validator #2028

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 82 additions & 72 deletions pkg/client-nfd/compat/node-validator/node-validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package nodevalidator

import (
"context"
"fmt"
"slices"
"sort"

Expand Down Expand Up @@ -62,12 +63,12 @@ func New(opts ...NodeValidatorOpts) nodeValidator {
func (nv *nodeValidator) Execute(ctx context.Context) ([]*CompatibilityStatus, error) {
spec, err := nv.artifactClient.FetchCompatibilitySpec(ctx)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to fetch compatibility spec: %w", err)
}

for _, s := range nv.sources {
if err := s.Discover(); err != nil {
return nil, err
return nil, fmt.Errorf("error during discovery of source %s: %w", s.Name(), err)
}
}
features := source.GetAllFeatures()
Expand All @@ -84,7 +85,7 @@ func (nv *nodeValidator) Execute(ctx context.Context) ([]*CompatibilityStatus, e
if err != nil {
return nil, err
}
compat.Rules = append(compat.Rules, evaluateRuleStatus(&r, ruleOut.MatchStatus))
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)
Expand All @@ -96,91 +97,100 @@ func (nv *nodeValidator) Execute(ctx context.Context) ([]*CompatibilityStatus, e
return compats, nil
}

func evaluateRuleStatus(rule *nfdv1alpha1.Rule, matchStatus *nodefeaturerule.MatchStatus) ProcessedRuleStatus {
var matchedFeatureTerms nfdv1alpha1.FeatureMatcher
func (nv *nodeValidator) evaluateRuleStatus(rule *nfdv1alpha1.Rule, matchStatus *nodefeaturerule.MatchStatus) ProcessedRuleStatus {
out := ProcessedRuleStatus{Name: rule.Name, IsMatch: matchStatus.IsMatch}

evaluateFeatureMatcher := func(featureMatcher, matchedFeatureTerms nfdv1alpha1.FeatureMatcher) []MatchedExpression {
out := []MatchedExpression{}
for _, term := range featureMatcher {
if term.MatchExpressions != nil {
for name, exp := range *term.MatchExpressions {
isMatch := false

// Check if the expression matches
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,
})
}
}
matchedFeatureTerms := nfdv1alpha1.FeatureMatcher{}
if m := matchStatus.MatchFeatureStatus; m != nil {
matchedFeatureTerms = m.MatchedFeaturesTerms
}
out.MatchedExpressions = nv.matchFeatureExpressions(rule.MatchFeatures, matchedFeatureTerms)

if term.MatchName != nil {
isMatch := false
for _, processedTerm := range matchStatus.MatchedFeaturesTerms {
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
}
}
out = append(out, MatchedExpression{
Feature: term.Feature,
Name: "",
Expression: term.MatchName,
MatcherType: MatchNameType,
IsMatch: isMatch,
})
}
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})
}

// 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
}

return out
}
func (nv *nodeValidator) matchFeatureExpressions(featureMatcher, matchedFeatureTerms nfdv1alpha1.FeatureMatcher) []MatchedExpression {
var out []MatchedExpression

if matchFeatures := rule.MatchFeatures; matchFeatures != nil {
if matchStatus.MatchFeatureStatus != nil {
matchedFeatureTerms = matchStatus.MatchFeatureStatus.MatchedFeaturesTerms
for _, term := range featureMatcher {
if term.MatchExpressions != nil {
out = append(out, nv.matchExpressions(term, matchedFeatureTerms)...)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

continue to next loop if matchExpressions has been appended? not rely on if term.MatchExpressions != nil then term.MatchName must be nil

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's allowed to define matchExpressions and matchName at the same time. Both cases have to be checked before the next iteration.

if term.MatchName != nil {
out = append(out, nv.matchName(term, matchedFeatureTerms))
}
out.MatchedExpressions = evaluateFeatureMatcher(matchFeatures, matchedFeatureTerms)
}

for i, matchAnyElem := range rule.MatchAny {
if matchStatus.MatchAny[i].MatchedFeaturesTerms != nil {
matchedFeatureTerms = matchStatus.MatchAny[i].MatchedFeaturesTerms
// 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
}
matchedExpressions := evaluateFeatureMatcher(matchAnyElem.MatchFeatures, matchedFeatureTerms)
out.MatchedAny = append(out.MatchedAny, MatchAnyElem{MatchedExpressions: matchedExpressions})
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)
Expand Down
Loading