Skip to content

Commit 4b5d973

Browse files
committed
kubectl-nfd: Add NodeFeatureGroup dryrun, validation and test
Signed-off-by: Oleg Zhurakivskyy <oleg.zhurakivskyy@intel.com>
1 parent 3d1b0df commit 4b5d973

9 files changed

Lines changed: 206 additions & 106 deletions

File tree

cmd/kubectl-nfd/subcmd/dryrun.go

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,34 +26,34 @@ import (
2626

2727
var dryrunCmd = &cobra.Command{
2828
Use: "dryrun",
29-
Short: "Process a NodeFeatureRule file against a NodeFeature file",
30-
Long: `Process a NodeFeatureRule file against a local NodeFeature file to dry run the rule against a node before applying it to a cluster`,
29+
Short: "Process a NodeFeatureRule or NodeFeatureGroup file against a NodeFeature file",
30+
Long: `Process a NodeFeatureRule or NodeFeatureGroup file against a local NodeFeature file to dry run the rule against a node before applying it to a cluster`,
31+
PreRunE: func(cmd *cobra.Command, args []string) error {
32+
if rule == "" {
33+
return fmt.Errorf("--rule-file must be specified")
34+
}
35+
return nil
36+
},
3137
Run: func(cmd *cobra.Command, args []string) {
32-
fmt.Printf("Evaluating NodeFeatureRule %q against NodeFeature %q\n", nodefeaturerule, nodefeature)
33-
err := kubectlnfd.DryRun(nodefeaturerule, nodefeature)
34-
if len(err) > 0 {
35-
fmt.Printf("NodeFeatureRule %q is not valid for NodeFeature %q\n", nodefeaturerule, nodefeature)
36-
for _, e := range err {
38+
fmt.Printf("Evaluating %q against NodeFeature %q\n", rule, nodefeature)
39+
errs := kubectlnfd.DryRun(rule, nodefeature)
40+
if len(errs) > 0 {
41+
fmt.Printf("%q is not valid for NodeFeature %q\n", rule, nodefeature)
42+
for _, e := range errs {
3743
cmd.PrintErrln(e)
3844
}
39-
// Return non-zero exit code to indicate failure
4045
os.Exit(1)
4146
}
42-
fmt.Printf("NodeFeatureRule %q is valid for NodeFeature %q\n", nodefeaturerule, nodefeature)
47+
fmt.Printf("%q is valid for NodeFeature %q\n", rule, nodefeature)
4348
},
4449
}
4550

4651
func init() {
4752
RootCmd.AddCommand(dryrunCmd)
4853

49-
dryrunCmd.Flags().StringVarP(&nodefeaturerule, "nodefeaturerule-file", "f", "", "Path to the NodeFeatureRule file to validate")
50-
dryrunCmd.Flags().StringVarP(&nodefeature, "nodefeature-file", "n", "", "Path to the NodeFeature file to validate against")
51-
err := dryrunCmd.MarkFlagRequired("nodefeaturerule-file")
52-
if err != nil {
53-
panic(err)
54-
}
55-
err = dryrunCmd.MarkFlagRequired("nodefeature-file")
56-
if err != nil {
54+
dryrunCmd.Flags().StringVarP(&rule, "rule-file", "f", "", "Path to the NodeFeatureRule or NodeFeatureGroup file to dry run")
55+
dryrunCmd.Flags().StringVarP(&nodefeature, "nodefeature-file", "n", "", "Path to the NodeFeature file to dry run against")
56+
if err := dryrunCmd.MarkFlagRequired("nodefeature-file"); err != nil {
5757
panic(err)
5858
}
5959
}

cmd/kubectl-nfd/subcmd/root.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ import (
2424
)
2525

2626
var (
27-
// Path to the NodeFeatureRule file to validate
28-
nodefeaturerule string
27+
// Path to the NodeFeatureRule or NodeFeatureGroup file
28+
rule string
2929
// Path to the NodeFeature file to run against the NodeFeatureRule
3030
nodefeature string
3131
// Node to validate against

cmd/kubectl-nfd/subcmd/test.go

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,32 +26,34 @@ import (
2626

2727
var testCmd = &cobra.Command{
2828
Use: "test",
29-
Short: "Test a NodeFeatureRule file against a Node",
30-
Long: `Test a NodeFeatureRule file against a Node to ensure it is valid before applying it to a cluster`,
29+
Short: "Test a NodeFeatureRule or NodeFeatureGroup file against a Node",
30+
Long: `Test a NodeFeatureRule or NodeFeatureGroup file against a Node to ensure it is valid before applying it to a cluster`,
31+
PreRunE: func(cmd *cobra.Command, args []string) error {
32+
if rule == "" {
33+
return fmt.Errorf("--rule-file must be specified")
34+
}
35+
return nil
36+
},
3137
Run: func(cmd *cobra.Command, args []string) {
32-
fmt.Printf("Evaluating NodeFeatureRule against Node %s\n", node)
33-
err := kubectlnfd.Test(nodefeaturerule, node, kubeconfig)
34-
if len(err) > 0 {
35-
fmt.Printf("NodeFeatureRule is not valid for Node %s\n", node)
36-
for _, e := range err {
38+
fmt.Printf("Evaluating %s against Node %s\n", rule, node)
39+
errs := kubectlnfd.Test(rule, node, kubeconfig)
40+
if len(errs) > 0 {
41+
fmt.Printf("%s is not valid for Node %s\n", rule, node)
42+
for _, e := range errs {
3743
cmd.PrintErrln(e)
3844
}
39-
// Return non-zero exit code to indicate failure
4045
os.Exit(1)
4146
}
42-
fmt.Printf("NodeFeatureRule is valid for Node %s\n", node)
47+
fmt.Printf("%s is valid for Node %s\n", rule, node)
4348
},
4449
}
4550

4651
func init() {
4752
RootCmd.AddCommand(testCmd)
4853

49-
testCmd.Flags().StringVarP(&nodefeaturerule, "nodefeaturerule-file", "f", "", "Path to the NodeFeatureRule file to validate")
50-
testCmd.Flags().StringVarP(&node, "nodename", "n", "", "Node to validate against")
54+
testCmd.Flags().StringVarP(&rule, "rule-file", "f", "", "Path to the NodeFeatureRule or NodeFeatureGroup file to test")
55+
testCmd.Flags().StringVarP(&node, "nodename", "n", "", "Node to test against")
5156
testCmd.Flags().StringVarP(&kubeconfig, "kubeconfig", "k", "", "kubeconfig file to use")
52-
if err := testCmd.MarkFlagRequired("nodefeaturerule-file"); err != nil {
53-
panic(err)
54-
}
5557
if err := testCmd.MarkFlagRequired("nodename"); err != nil {
5658
panic(err)
5759
}

cmd/kubectl-nfd/subcmd/validate.go

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,29 +26,30 @@ import (
2626

2727
var validateCmd = &cobra.Command{
2828
Use: "validate",
29-
Short: "Validate a NodeFeatureRule file",
30-
Long: `Validate a NodeFeatureRule file to ensure it is valid before applying it to a cluster`,
29+
Short: "Validate a NodeFeatureRule or NodeFeatureGroup file",
30+
Long: `Validate a NodeFeatureRule or NodeFeatureGroup file to ensure it is valid before applying it to a cluster`,
31+
PreRunE: func(cmd *cobra.Command, args []string) error {
32+
if rule == "" {
33+
return fmt.Errorf("--rule-file must be specified")
34+
}
35+
return nil
36+
},
3137
Run: func(cmd *cobra.Command, args []string) {
32-
fmt.Printf("Validating NodeFeatureRule %s\n", nodefeaturerule)
33-
err := kubectlnfd.ValidateNFR(nodefeaturerule)
34-
if len(err) > 0 {
35-
fmt.Printf("NodeFeatureRule %s is not valid\n", nodefeaturerule)
36-
for _, e := range err {
38+
fmt.Printf("Validating %s\n", rule)
39+
errs := kubectlnfd.Validate(rule)
40+
if len(errs) > 0 {
41+
fmt.Printf("%s is not valid\n", rule)
42+
for _, e := range errs {
3743
cmd.PrintErrln(e)
3844
}
39-
// Return non-zero exit code to indicate failure
4045
os.Exit(1)
4146
}
42-
fmt.Printf("NodeFeatureRule %s is valid\n", nodefeaturerule)
47+
fmt.Printf("%s is valid\n", rule)
4348
},
4449
}
4550

4651
func init() {
4752
RootCmd.AddCommand(validateCmd)
4853

49-
validateCmd.Flags().StringVarP(&nodefeaturerule, "nodefeaturerule-file", "f", "", "Path to the NodeFeatureRule file to validate")
50-
err := validateCmd.MarkFlagRequired("nodefeaturerule-file")
51-
if err != nil {
52-
panic(err)
53-
}
54+
validateCmd.Flags().StringVarP(&rule, "rule-file", "f", "", "Path to the NodeFeatureRule or NodeFeatureGroup file to validate")
5455
}

docs/usage/kubectl-plugin.md

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ sort: 10
2222
## Overview
2323

2424
The `kubectl` plugin `kubectl nfd` can be used to validate/dryrun and test
25-
NodeFeatureRule objects. It can be installed with the following command:
25+
NodeFeatureRule and NodeFeatureGroup objects. It can be installed with the
26+
following command:
2627

2728
```bash
2829
git clone https://github.com/kubernetes-sigs/node-feature-discovery
@@ -34,37 +35,60 @@ mv ./bin/kubectl-nfd ${KUBECTL_PATH}
3435

3536
### Validate
3637

37-
The plugin can be used to validate a NodeFeatureRule object:
38+
The plugin can be used to validate a NodeFeatureRule or NodeFeatureGroup object.
39+
The kind is detected automatically from the file content:
3840

3941
```bash
40-
kubectl nfd validate -f <nodefeaturerule.yaml>
42+
kubectl nfd validate -f <nodefeaturerule-or-nodefeaturegroup.yaml>
43+
```
44+
45+
You can use the example files to try it out:
46+
47+
```bash
48+
$ kubectl nfd validate -f examples/nodefeaturegroup.yaml
49+
Validating examples/nodefeaturegroup.yaml
50+
Validating rule: kernel version
51+
Validating rule: veth kernel module
52+
examples/nodefeaturegroup.yaml is valid
4153
```
4254

4355
### Test
4456

45-
The plugin can be used to test a NodeFeatureRule object against a node:
57+
The plugin can be used to test a NodeFeatureRule or NodeFeatureGroup object
58+
against a node. The kind is detected automatically from the file content:
4659

4760
```bash
48-
kubectl nfd test -f <nodefeaturerule.yaml> -n <node-name>
61+
kubectl nfd test -f <nodefeaturerule-or-nodefeaturegroup.yaml> -n <node-name>
4962
```
5063

5164
### DryRun
5265

53-
The plugin can be used to DryRun a NodeFeatureRule object against a NodeFeature
54-
file:
66+
The plugin can be used to dry run a NodeFeatureRule or NodeFeatureGroup object
67+
against a NodeFeature file. The kind is detected automatically from the file
68+
content:
5569

5670
```bash
5771
kubectl get -n node-feature-discovery nodefeature <nodename> -o yaml > <nodefeature.yaml>
58-
kubectl nfd dryrun -f <nodefeaturerule.yaml> -n <nodefeature.yaml>
72+
kubectl nfd dryrun -f <nodefeaturerule-or-nodefeaturegroup.yaml> -n <nodefeature.yaml>
5973
```
6074

61-
Or you can use the example NodeFeature file(it is a minimal NodeFeature file):
75+
For example, using the example files:
6276

6377
```bash
6478
$ kubectl nfd dryrun -f examples/nodefeaturerule.yaml -n examples/nodefeature.yaml
65-
Evaluating NodeFeatureRule "examples/nodefeaturerule.yaml" against NodeFeature "examples/nodefeature.yaml"
79+
Evaluating "examples/nodefeaturerule.yaml" against NodeFeature "examples/nodefeature.yaml"
6680
Processing rule: my sample rule
6781
*** Labels ***
6882
vendor.io/my-sample-feature=true
69-
NodeFeatureRule "examples/nodefeaturerule.yaml" is valid for NodeFeature "examples/nodefeature.yaml"
83+
"examples/nodefeaturerule.yaml" is valid for NodeFeature "examples/nodefeature.yaml"
84+
```
85+
86+
```bash
87+
$ kubectl nfd dryrun -f examples/nodefeaturegroup.yaml -n examples/nodefeature.yaml
88+
Evaluating "examples/nodefeaturegroup.yaml" against NodeFeature "examples/nodefeature.yaml"
89+
Processing rule: kernel version
90+
Rule "kernel version" did not match
91+
Processing rule: veth kernel module
92+
Rule "veth kernel module" did not match
93+
"examples/nodefeaturegroup.yaml" is valid for NodeFeature "examples/nodefeature.yaml"
7094
```

examples/nodefeaturegroup.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,8 @@ spec:
99
- feature: kernel.version
1010
matchExpressions:
1111
major: {op: In, value: ["6"]}
12+
- name: "veth kernel module"
13+
matchFeatures:
14+
- feature: kernel.loadedmodule
15+
matchExpressions:
16+
veth: {op: Exists}

pkg/kubectl-nfd/dryrun.go

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -31,34 +31,51 @@ import (
3131
"sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/validate"
3232
)
3333

34-
func DryRun(nodefeaturerulepath, nodefeaturepath string) []error {
34+
func processNodeFeatureGroup(nodeFeatureGroup nfdv1alpha1.NodeFeatureGroup, nodeFeature nfdv1alpha1.NodeFeatureSpec) []error {
3535
var errs []error
36-
nfr := nfdv1alpha1.NodeFeatureRule{}
37-
nf := nfdv1alpha1.NodeFeature{}
3836

39-
nfrFile, err := os.ReadFile(nodefeaturerulepath)
40-
if err != nil {
41-
return []error{fmt.Errorf("error reading NodeFeatureRule file: %w", err)}
37+
for _, rule := range nodeFeatureGroup.Spec.Rules {
38+
fmt.Println("Processing rule: ", rule.Name)
39+
ruleOut, err := nodefeaturerule.ExecuteGroupRule(&rule, &nodeFeature.Features, true)
40+
if err != nil {
41+
errs = append(errs, fmt.Errorf("failed to process rule %q: %w", rule.Name, err))
42+
continue
43+
}
44+
if ruleOut.MatchStatus == nil || !ruleOut.MatchStatus.IsMatch {
45+
fmt.Printf("Rule %q did not match\n", rule.Name)
46+
continue
47+
}
48+
fmt.Printf("Rule %q matched\n", rule.Name)
49+
if len(ruleOut.Vars) > 0 {
50+
fmt.Println("***\tVars\t***")
51+
for k, v := range ruleOut.Vars {
52+
fmt.Printf("%s=%s\n", k, v)
53+
}
54+
}
4255
}
4356

44-
err = yaml.Unmarshal(nfrFile, &nfr)
45-
if err != nil {
46-
return []error{fmt.Errorf("error parsing NodeFeatureRule: %w", err)}
47-
}
57+
return errs
58+
}
4859

60+
func DryRun(resourcepath, nodefeaturepath string) []error {
4961
nfFile, err := os.ReadFile(nodefeaturepath)
5062
if err != nil {
51-
return []error{fmt.Errorf("error reading NodeFeatureRule file: %w", err)}
63+
return []error{fmt.Errorf("error reading NodeFeature file: %w", err)}
5264
}
53-
54-
err = yaml.Unmarshal(nfFile, &nf)
55-
if err != nil {
56-
return []error{fmt.Errorf("error parsing NodeFeatureRule: %w", err)}
65+
nf := nfdv1alpha1.NodeFeature{}
66+
if err = yaml.Unmarshal(nfFile, &nf); err != nil {
67+
return []error{fmt.Errorf("error parsing NodeFeature: %w", err)}
5768
}
5869

59-
errs = append(errs, processNodeFeatureRule(nfr, nf.Spec)...)
60-
61-
return errs
70+
t := parseRuleFile(resourcepath)
71+
switch o := t.(type) {
72+
case *nfdv1alpha1.NodeFeatureRule:
73+
return processNodeFeatureRule(*o, nf.Spec)
74+
case *nfdv1alpha1.NodeFeatureGroup:
75+
return processNodeFeatureGroup(*o, nf.Spec)
76+
default:
77+
return []error{fmt.Errorf("unsupported resource %v: must be NodeFeatureRule or NodeFeatureGroup", t)}
78+
}
6279
}
6380

6481
func processNodeFeatureRule(nodeFeatureRule nfdv1alpha1.NodeFeatureRule, nodeFeature nfdv1alpha1.NodeFeatureSpec) []error {

pkg/kubectl-nfd/test.go

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package kubectlnfd
1919
import (
2020
"context"
2121
"fmt"
22-
"os"
2322
"strings"
2423

2524
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -28,29 +27,24 @@ import (
2827

2928
nfdclientset "sigs.k8s.io/node-feature-discovery/api/generated/clientset/versioned"
3029
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/api/nfd/v1alpha1"
31-
32-
"sigs.k8s.io/yaml"
3330
)
3431

35-
func Test(nodefeaturerulepath, nodeName, kubeconfig string) []error {
36-
var errs []error
37-
var err error
38-
32+
func getNodeFeatures(nodeName, kubeconfig string) (*nfdv1alpha1.NodeFeatureSpec, error) {
3933
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
4034
if kubeconfig != "" {
4135
loadingRules.ExplicitPath = kubeconfig
4236
}
4337
config, err := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{}).ClientConfig()
4438
if err != nil {
45-
return []error{fmt.Errorf("error building kubeconfig: %w", err)}
39+
return nil, fmt.Errorf("error building kubeconfig: %w", err)
4640
}
4741

4842
nfdClient := nfdclientset.NewForConfigOrDie(config)
4943

5044
sel := k8sLabels.SelectorFromSet(k8sLabels.Set{nfdv1alpha1.NodeFeatureObjNodeNameLabel: nodeName})
5145
list, err := nfdClient.NfdV1alpha1().NodeFeatures(metav1.NamespaceAll).List(context.TODO(), metav1.ListOptions{LabelSelector: sel.String()})
5246
if err != nil {
53-
return []error{fmt.Errorf("failed to get NodeFeature resources for node %q: %w", nodeName, err)}
47+
return nil, fmt.Errorf("failed to get NodeFeature resources for node %q: %w", nodeName, err)
5448
}
5549
objs := list.Items
5650
names := make([]string, len(objs))
@@ -67,19 +61,24 @@ func Test(nodefeaturerulepath, nodeName, kubeconfig string) []error {
6761
s.MergeInto(features)
6862
}
6963
}
64+
return features, nil
65+
}
7066

71-
nfrFile, err := os.ReadFile(nodefeaturerulepath)
67+
// Test reads a NodeFeatureRule or NodeFeatureGroup file and evaluates it against
68+
// the NodeFeature objects of the given node. The kind is detected automatically.
69+
func Test(resourcepath, nodeName, kubeconfig string) []error {
70+
features, err := getNodeFeatures(nodeName, kubeconfig)
7271
if err != nil {
73-
return []error{fmt.Errorf("error reading NodeFeatureRule file: %w", err)}
72+
return []error{err}
7473
}
7574

76-
nfr := nfdv1alpha1.NodeFeatureRule{}
77-
err = yaml.Unmarshal(nfrFile, &nfr)
78-
if err != nil {
79-
return []error{fmt.Errorf("error parsing NodeFeatureRule: %w", err)}
75+
t := parseRuleFile(resourcepath)
76+
switch o := t.(type) {
77+
case *nfdv1alpha1.NodeFeatureRule:
78+
return processNodeFeatureRule(*o, *features)
79+
case *nfdv1alpha1.NodeFeatureGroup:
80+
return processNodeFeatureGroup(*o, *features)
81+
default:
82+
return []error{fmt.Errorf("unsupported resource %v: must be NodeFeatureRule or NodeFeatureGroup", t)}
8083
}
81-
82-
errs = append(errs, processNodeFeatureRule(nfr, *features)...)
83-
84-
return errs
8584
}

0 commit comments

Comments
 (0)