Skip to content

Commit 7247eb7

Browse files
committed
add node feature discovery (nfd)
Signed-off-by: vsoch <[email protected]>
1 parent 197d719 commit 7247eb7

File tree

12 files changed

+389
-20
lines changed

12 files changed

+389
-20
lines changed

.devcontainer/Dockerfile

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ RUN apt-get update && \
2626
ENV LD_LIBRARY_PATH=/usr/lib:/usr/local/lib
2727
ENV PATH=$PATH:/workspaces/compspec-go/bin
2828

29-
RUN wget --no-check-certificate https://go.dev/dl/go1.20.10.linux-amd64.tar.gz && tar -xvf go1.20.10.linux-amd64.tar.gz && \
30-
mv go /usr/local && rm go1.20.10.linux-amd64.tar.gz
29+
RUN wget --no-check-certificate https://go.dev/dl/go1.21.7.linux-amd64.tar.gz && tar -xvf go1.21.7.linux-amd64.tar.gz && \
30+
mv go /usr/local && rm go1.21.7.linux-amd64.tar.gz
3131

3232
ENV PATH=$PATH:/usr/local/go/bin:/home/vscode/go/bin
3333

.devcontainer/devcontainer.json

+3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
"settings": {
99
"terminal.integrated.defaultProfile.linux": "bash"
1010
},
11+
"extensions": [
12+
"golang.go"
13+
],
1114
}
1215
},
1316
"postStartCommand": "git config --global --add safe.directory /workspaces/compspec-go"

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ This is a prototype compatibility checking tool. Right now our aim is to use in
1414

1515
## TODO
1616

17+
- add descriptions to sections
18+
- make extractor sections into interface
1719
- metadata namespace and exposure: someone writing a spec to create an artifact needs to know the extract namespace (and what is available) for the mapping.
18-
- create: the final step of create should be validation of the spec with the jsonSchema linked (not done yet)
1920
- tests: matrix that has several different flavors of builds, generating compspec json output to validate generation and correctness
2021
- likely we want a common configuration file to take an extraction -> check recipe
21-
- need to develop check plugin family
2222
- todo thinking around manifest.yaml that has listing of images / artifacts
2323

2424
### Extractors wanted / needed

go.mod

+44-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/supercontainers/compspec-go
22

3-
go 1.20
3+
go 1.21
44

55
require (
66
github.com/akamensky/argparse v1.4.0
@@ -11,15 +11,57 @@ require (
1111
github.com/scylladb/go-set v1.0.2
1212
golang.org/x/sys v0.16.0
1313
oras.land/oras-go/v2 v2.3.1
14+
sigs.k8s.io/node-feature-discovery v0.15.1
1415
sigs.k8s.io/yaml v1.4.0
1516
)
1617

1718
require (
19+
github.com/beorn7/perks v1.0.1 // indirect
20+
github.com/cespare/xxhash/v2 v2.2.0 // indirect
1821
github.com/containerd/log v0.1.0 // indirect
22+
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
23+
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
24+
github.com/davecgh/go-spew v1.1.1 // indirect
25+
github.com/fsnotify/fsnotify v1.7.0 // indirect
26+
github.com/go-logr/logr v1.3.0 // indirect
27+
github.com/godbus/dbus/v5 v5.1.0 // indirect
28+
github.com/gogo/protobuf v1.3.2 // indirect
29+
github.com/golang/protobuf v1.5.3 // indirect
30+
github.com/google/gofuzz v1.2.0 // indirect
31+
github.com/json-iterator/go v1.1.12 // indirect
32+
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
1933
github.com/mattn/go-runewidth v0.0.15 // indirect
34+
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
35+
github.com/moby/sys/mountinfo v0.6.2 // indirect
36+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
37+
github.com/modern-go/reflect2 v1.0.2 // indirect
2038
github.com/opencontainers/go-digest v1.0.0 // indirect
39+
github.com/opencontainers/runc v1.1.10 // indirect
40+
github.com/opencontainers/runtime-spec v1.1.0-rc.2 // indirect
41+
github.com/pmezard/go-difflib v1.0.0 // indirect
42+
github.com/prometheus/client_golang v1.16.0 // indirect
43+
github.com/prometheus/client_model v0.4.0 // indirect
44+
github.com/prometheus/common v0.44.0 // indirect
45+
github.com/prometheus/procfs v0.10.1 // indirect
2146
github.com/rivo/uniseg v0.2.0 // indirect
2247
github.com/sirupsen/logrus v1.9.3 // indirect
23-
golang.org/x/sync v0.4.0 // indirect
48+
github.com/stretchr/objx v0.5.0 // indirect
49+
github.com/stretchr/testify v1.8.4 // indirect
50+
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb // indirect
51+
golang.org/x/net v0.19.0 // indirect
52+
golang.org/x/sync v0.5.0 // indirect
53+
golang.org/x/text v0.14.0 // indirect
54+
google.golang.org/grpc v1.59.0 // indirect
55+
google.golang.org/protobuf v1.31.0 // indirect
56+
gopkg.in/inf.v0 v0.9.1 // indirect
57+
gopkg.in/yaml.v2 v2.4.0 // indirect
58+
gopkg.in/yaml.v3 v3.0.1 // indirect
2459
gotest.tools/v3 v3.5.1 // indirect
60+
k8s.io/api v0.29.0 // indirect
61+
k8s.io/apimachinery v0.29.0 // indirect
62+
k8s.io/klog/v2 v2.110.1 // indirect
63+
k8s.io/kubernetes v1.29.0 // indirect
64+
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
65+
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
66+
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
2567
)

go.sum

+137-4
Large diffs are not rendered by default.

pkg/extractor/extractor.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
// a validate function to typically check that the plugin is valid
1212
type Extractor interface {
1313
Name() string
14+
Description() string
1415
Extract(interface{}) (ExtractorData, error)
1516
Validate() bool
1617
Sections() []string
@@ -42,7 +43,7 @@ func (e *ExtractorData) ToJson() (string, error) {
4243
return string(b), err
4344
}
4445

45-
// An extractor section corresponds to a named group of annotations
46+
// An extractor section corresponds to a named group of attributes
4647
type ExtractorSection map[string]string
4748

4849
// Extractors is a lookup of registered extractors by name

plugins/extractors/kernel/kernel.go

+5
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99

1010
const (
1111
ExtractorName = "kernel"
12+
ExtractorDescription = "generic kernel extractor"
1213
KernelBootSection = "boot"
1314
KernelConfigSection = "config"
1415
KernelModulesSection = "modules"
@@ -22,6 +23,10 @@ type KernelExtractor struct {
2223
sections []string
2324
}
2425

26+
func (e KernelExtractor) Description() string {
27+
return ExtractorDescription
28+
}
29+
2530
func (e KernelExtractor) Sections() []string {
2631
return e.sections
2732
}

plugins/extractors/library/library.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ import (
88
)
99

1010
const (
11-
ExtractorName = "library"
12-
MPISection = "mpi"
11+
ExtractorName = "library"
12+
ExtractorDescription = "generic library extractor"
13+
MPISection = "mpi"
1314
)
1415

1516
var (
@@ -28,6 +29,10 @@ func (e LibraryExtractor) Sections() []string {
2829
return e.sections
2930
}
3031

32+
func (e LibraryExtractor) Description() string {
33+
return ExtractorDescription
34+
}
35+
3136
// Validate ensures that the sections provided are in the list we know
3237
func (e LibraryExtractor) Validate() bool {
3338
invalids, valid := utils.StringArrayIsSubset(e.sections, validSections)

plugins/extractors/nfd/nfd.go

+155
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package nfd
2+
3+
import (
4+
"fmt"
5+
6+
source "sigs.k8s.io/node-feature-discovery/source"
7+
8+
// Note that "fake" is removed from here
9+
_ "sigs.k8s.io/node-feature-discovery/source/cpu"
10+
_ "sigs.k8s.io/node-feature-discovery/source/custom"
11+
_ "sigs.k8s.io/node-feature-discovery/source/kernel"
12+
_ "sigs.k8s.io/node-feature-discovery/source/local"
13+
_ "sigs.k8s.io/node-feature-discovery/source/memory"
14+
_ "sigs.k8s.io/node-feature-discovery/source/network"
15+
_ "sigs.k8s.io/node-feature-discovery/source/pci"
16+
_ "sigs.k8s.io/node-feature-discovery/source/storage"
17+
_ "sigs.k8s.io/node-feature-discovery/source/system"
18+
_ "sigs.k8s.io/node-feature-discovery/source/usb"
19+
20+
"github.com/supercontainers/compspec-go/pkg/extractor"
21+
"github.com/supercontainers/compspec-go/pkg/utils"
22+
)
23+
24+
const (
25+
ExtractorName = "nfd"
26+
ExtractorDescription = "node feature discovery"
27+
28+
// Each of these corresponds to a source
29+
CPUSection = "cpu"
30+
31+
// TODO can we do a check that this is desired / enabled before running?
32+
CustomSection = "custom"
33+
KernelSection = "kernel"
34+
LocalSection = "local"
35+
MemorySection = "memory"
36+
NetworkSection = "network"
37+
PCISection = "pci"
38+
StorageSection = "storage"
39+
SystemSection = "system"
40+
USBSection = "usb"
41+
)
42+
43+
var (
44+
validSections = []string{
45+
CPUSection,
46+
CustomSection,
47+
KernelSection,
48+
LocalSection,
49+
MemorySection,
50+
NetworkSection,
51+
PCISection,
52+
StorageSection,
53+
SystemSection,
54+
USBSection,
55+
}
56+
)
57+
58+
// NFDExtractor is an extractor for node feature discovery
59+
type NFDExtractor struct {
60+
sections []string
61+
}
62+
63+
func (e NFDExtractor) Name() string {
64+
return ExtractorName
65+
}
66+
67+
func (e NFDExtractor) Sections() []string {
68+
return e.sections
69+
}
70+
71+
func (e NFDExtractor) Description() string {
72+
return ExtractorDescription
73+
}
74+
75+
// Validate ensures that the sections provided are in the list we know
76+
func (e NFDExtractor) Validate() bool {
77+
invalids, valid := utils.StringArrayIsSubset(e.sections, validSections)
78+
for _, invalid := range invalids {
79+
fmt.Printf("Sections %s is not known for extractor plugin %s\n", invalid, e.Name())
80+
}
81+
return valid
82+
}
83+
84+
// Extract returns system metadata, for a set of named sections
85+
func (e NFDExtractor) Extract(interface{}) (extractor.ExtractorData, error) {
86+
87+
sections := map[string]extractor.ExtractorSection{}
88+
data := extractor.ExtractorData{}
89+
90+
// Get all registered feature sources
91+
sources := source.GetAllFeatureSources()
92+
93+
// Only extract the sections we asked for
94+
for _, name := range e.sections {
95+
discovery, ok := sources[name]
96+
97+
// This should not happen
98+
if !ok {
99+
fmt.Printf("%s is not a known feature source %s\n", name)
100+
continue
101+
}
102+
err := discovery.Discover()
103+
if err != nil {
104+
fmt.Printf("Issue discovering features for %s\n", discovery.Name())
105+
continue
106+
}
107+
108+
// Create a new section for the <name> group
109+
// For each of the below, "fs" is a feature set
110+
// AttributeFeatureSet
111+
section := extractor.ExtractorSection{}
112+
features := discovery.GetFeatures()
113+
for k, fs := range features.Attributes {
114+
for fName, feature := range fs.Elements {
115+
uid := fmt.Sprintf("%s.%s", k, fName)
116+
section[uid] = feature
117+
}
118+
}
119+
120+
// FlagFeatureSet
121+
// Note that the second value to feature is v1alpha.Nil
122+
// I think this acts as a flag, double check
123+
for k, fs := range features.Flags {
124+
for feature, _ := range fs.Elements {
125+
uid := fmt.Sprintf("%s.%s", k, feature)
126+
section[uid] = "true"
127+
}
128+
}
129+
130+
// InstanceFeatureSet
131+
for k, fs := range features.Instances {
132+
for idx, feature := range fs.Elements {
133+
for fName, attr := range feature.Attributes {
134+
uid := fmt.Sprintf("%s.%d.%s", k, idx, fName)
135+
section[uid] = attr
136+
}
137+
}
138+
}
139+
sections[name] = section
140+
}
141+
data.Sections = sections
142+
return data, nil
143+
}
144+
145+
// NewPlugin validates and returns a new kernel plugin
146+
func NewPlugin(sections []string) (extractor.Extractor, error) {
147+
if len(sections) == 0 {
148+
sections = validSections
149+
}
150+
e := NFDExtractor{sections: sections}
151+
if !e.Validate() {
152+
return nil, fmt.Errorf("plugin %s is not valid\n", e.Name())
153+
}
154+
return e, nil
155+
}

plugins/extractors/system/system.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import (
88
)
99

1010
const (
11-
ExtractorName = "system"
11+
ExtractorName = "system"
12+
ExtractorDescription = "generic system extractor"
1213

1314
// Just cores, etc.
1415
CPUSection = "cpu"
@@ -29,6 +30,10 @@ func (e SystemExtractor) Name() string {
2930
return ExtractorName
3031
}
3132

33+
func (e SystemExtractor) Description() string {
34+
return ExtractorDescription
35+
}
36+
3237
func (e SystemExtractor) Sections() []string {
3338
return e.sections
3439
}

plugins/list.go

+10-2
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,24 @@ func (r *PluginsRequest) List() error {
1818

1919
// keep count of plugins (just extractors for now)
2020
count := 0
21+
extractorCount := 0
2122

2223
// TODO add description column
2324
for _, p := range *r {
24-
for _, section := range p.Extractor.Sections() {
25+
extractorCount += 1
26+
for i, section := range p.Extractor.Sections() {
27+
28+
// Add the extractor plugin description only for first in the list
29+
if i == 0 {
30+
t.AppendSeparator()
31+
t.AppendRow(table.Row{p.Extractor.Description(), "", "", ""})
32+
}
2533
count += 1
2634
t.AppendRow([]interface{}{"", "extractor", p.Name, section})
2735
}
2836
}
2937
t.AppendSeparator()
30-
t.AppendFooter(table.Row{"Total", count, "", ""})
38+
t.AppendFooter(table.Row{"Total", "", extractorCount, count})
3139
t.SetStyle(table.StyleColoredCyanWhiteOnBlack)
3240
t.Render()
3341
return nil

0 commit comments

Comments
 (0)