diff --git a/README.md b/README.md index 6bf08c662..383b794bb 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,7 @@ the only label value published for features is the string `"true"`._ "feature.node.kubernetes.io/node-feature-discovery.version": "v0.3.0", "feature.node.kubernetes.io/nfd-cpuid-": "true", "feature.node.kubernetes.io/nfd-iommu-": "true", + "feature.node.kubernetes.io/kernel-config.": "true", "feature.node.kubernetes.io/nfd-kernel-version.": "", "feature.node.kubernetes.io/nfd-memory-": "true", "feature.node.kubernetes.io/nfd-network-": "true", @@ -171,12 +172,16 @@ such as restricting discovered features with the --label-whitelist option._ ### Kernel Features -| Feature | Attribute | Description | -| ------- | --------- | ------------------------------------------------------ | -| version | full | Full kernel version as reported by `/proc/sys/kernel/osrelease` (e.g. '4.5.6-7-g123abcde') -|
| major | First component of the kernel version (e.g. '4') -|
| minor | Second component of the kernel version (e.g. '5') -|
| revision | Third component of the kernel version (e.g. '6') +| Feature | Attribute | Description | +| ------- | ----------- | ---------------------------------------------------- | +| config | NO_HZ | Kernel config option is enabled +|
| NO_HZ_FULL | +|
| NO_HZ_IDLE | +|
| PREEMPT | +| version | full | Full kernel version as reported by `/proc/sys/kernel/osrelease` (e.g. '4.5.6-7-g123abcde') +|
| major | First component of the kernel version (e.g. '4') +|
| minor | Second component of the kernel version (e.g. '5') +|
| revision | Third component of the kernel version (e.g. '6') ### Local (User-specific Features) diff --git a/node-feature-discovery-daemonset.yaml.template b/node-feature-discovery-daemonset.yaml.template index 4b5c8b97a..c286a3d7c 100644 --- a/node-feature-discovery-daemonset.yaml.template +++ b/node-feature-discovery-daemonset.yaml.template @@ -26,9 +26,15 @@ spec: args: - "--sleep-interval=60s" volumeMounts: + - name: host-boot + mountPath: "/host-boot" + readOnly: true - name: host-sys mountPath: "/host-sys" volumes: + - name: host-boot + hostPath: + path: "/boot" - name: host-sys hostPath: path: "/sys" diff --git a/node-feature-discovery-job.yaml.template b/node-feature-discovery-job.yaml.template index cbc7a4e49..c0670a453 100644 --- a/node-feature-discovery-job.yaml.template +++ b/node-feature-discovery-job.yaml.template @@ -28,10 +28,16 @@ spec: - containerPort: 7156 hostPort: 7156 volumeMounts: + - name: host-boot + mountPath: "/host-boot" + readOnly: true - name: host-sys mountPath: "/host-sys" restartPolicy: Never volumes: + - name: host-boot + hostPath: + path: "/boot" - name: host-sys hostPath: path: "/sys" diff --git a/source/kernel/kernel.go b/source/kernel/kernel.go index bdde2c3a9..b308f6638 100644 --- a/source/kernel/kernel.go +++ b/source/kernel/kernel.go @@ -17,14 +17,27 @@ limitations under the License. package kernel import ( + "bytes" + "compress/gzip" "io/ioutil" + "log" + "os" "regexp" "strings" - "github.com/golang/glog" "github.com/kubernetes-incubator/node-feature-discovery/source" ) +// Default kconfig flags +var defaultKconfigFlags = [...]string{ + "NO_HZ", + "NO_HZ_IDLE", + "NO_HZ_FULL", + "PREEMPT", +} + +var logger = log.New(os.Stderr, "", log.LstdFlags) + // Implement FeatureSource interface type Source struct{} @@ -36,12 +49,26 @@ func (s Source) Discover() (source.Features, error) { // Read kernel version version, err := parseVersion() if err != nil { - glog.Errorf("Failed to get kernel version: %v", err) + logger.Printf("ERROR: Failed to get kernel version: %s", err) } else { for key := range version { features["version."+key] = version[key] } } + + // Read kconfig + kconfig, err := parseKconfig() + if err != nil { + logger.Printf("ERROR: Failed to read kconfig: %s", err) + } + + // Check flags + for _, flag := range defaultKconfigFlags { + if _, ok := kconfig[flag]; ok { + features["config."+flag] = true + } + } + return features, nil } @@ -70,3 +97,64 @@ func parseVersion() (map[string]string, error) { return version, nil } + +// Read gzipped kernel config +func readKconfigGzip(filename string) ([]byte, error) { + // Open file for reading + f, err := os.Open(filename) + if err != nil { + return nil, err + } + defer f.Close() + + // Uncompress data + r, err := gzip.NewReader(f) + if err != nil { + return nil, err + } + defer r.Close() + + return ioutil.ReadAll(r) +} + +// Read kconfig into a map +func parseKconfig() (map[string]bool, error) { + kconfig := map[string]bool{} + raw := []byte(nil) + + // First, try to read from /proc as this is the most reliable source + raw, err := readKconfigGzip("/proc/config.gz") + if err != nil { + logger.Printf("ERROR: Failed to read /proc/config.gz: %s", err) + } + + // Last, try to read from /boot/ + if raw == nil { + // Get kernel version + unameRaw, err := ioutil.ReadFile("/proc/sys/kernel/osrelease") + uname := strings.TrimSpace(string(unameRaw)) + if err != nil { + return nil, err + } + // Read kconfig + raw, err = ioutil.ReadFile("/host-boot/config-" + uname) + if err != nil { + return nil, err + } + } + + // Regexp for matching kconfig flags + re := regexp.MustCompile(`^CONFIG_(?P\w+)=(?P.+)`) + + // Process data, line-by-line + lines := bytes.Split(raw, []byte("\n")) + for _, line := range lines { + if m := re.FindStringSubmatch(string(line)); m != nil { + if m[2] == "y" || m[2] == "m" { + kconfig[m[1]] = true + } + } + } + + return kconfig, nil +}