1
0
Fork 0
mirror of https://github.com/kubernetes-sigs/node-feature-discovery.git synced 2025-03-28 02:37:11 +00:00

Merge pull request #334 from mythi/custom-adds

custom: Add rules for kconfig and cpuid
This commit is contained in:
Kubernetes Prow Robot 2020-09-14 06:04:58 -07:00 committed by GitHub
commit f0ef38b84e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 324 additions and 103 deletions

View file

@ -390,6 +390,36 @@ loadedKMod : [<kernel module>, ...]
Matching is done by performing logical _AND_ for each provided Element, i.e the Rule will match if all provided Elements (kernel modules) are loaded
in the system.
##### CpuId Rule
###### Nomenclature
```
Element :A CPUID flag
```
The Rule allows matching the available CPUID flags in the system against a provided list of Elements.
###### Format
```yaml
cpuId : [<CPUID flag string>, ...]
```
Matching is done by performing logical _AND_ for each provided Element, i.e the Rule will match if all provided Elements (CPUID flag strings) are available
in the system.
##### Kconfig Rule
###### Nomenclature
```
Element :A Kconfig option
```
The Rule allows matching the kconfig options in the system against a provided list of Elements.
###### Format
```yaml
kConfig: [<kernel config option ('y' or 'm') or '=<value>'>, ...]
```
Matching is done by performing logical _AND_ for each provided Element, i.e the Rule will match if all provided Elements (kernel config options) are enabled ('y' or 'm') or matching '=<value>'.
in the kernel.
#### Example
```yaml
@ -419,6 +449,14 @@ custom:
- pciId:
vendor: ["15b3"]
device: ["1014", "1017"]
- name: "my.kernel.featureneedscpu"
matchOn:
- kConfig: ["KVM_INTEL"]
- cpuId: ["VMX"]
- name: "my.kernel.modulecompiler"
matchOn:
- kConfig: ["GCC_VERSION=100101"]
loadedKMod: ["kmod1"]
```
__In the example above:__
@ -434,6 +472,9 @@ with a PCI vendor ID of `15b3` _AND_ PCI device ID of `1014` _or_ `1017`.
- A node would contain the label: `feature.node.kubernetes.io/custom-my.accumulated.feature=true`
if `some_kmod1` _AND_ `some_kmod2` kernel modules are loaded __OR__ the node contains a PCI device
with a PCI vendor ID of `15b3` _AND_ PCI device ID of `1014` _OR_ `1017`.
- A node would contain the label: `feature.node.kubernetes.io/custom-my.kernel.featureneedscpu=true`
if `KVM_INTEL` kernel config is enabled __AND__ the node CPU supports `VMX` virtual machine extensions
- A node would contain the label: `feature.node.kubernetes.io/custom-my.kernel.modulecompiler=true` if the in-tree `kmod1` kernel module is loaded __AND__ it's built with `GCC_VERSION=100101`.
#### Statically defined features

View file

@ -21,6 +21,7 @@ import (
"log"
"sigs.k8s.io/node-feature-discovery/source"
"sigs.k8s.io/node-feature-discovery/source/internal/cpuidutils"
)
// Configuration file options
@ -119,7 +120,7 @@ func (s *Source) Discover() (source.Features, error) {
}
// Detect CPUID
cpuidFlags := getCpuidFlags()
cpuidFlags := cpuidutils.GetCpuidFlags()
for _, f := range cpuidFlags {
if s.cpuidFilter.unmask(f) {
features["cpuid."+f] = true

View file

@ -17,7 +17,6 @@ limitations under the License.
package custom
import (
"fmt"
"log"
"sigs.k8s.io/node-feature-discovery/source"
@ -29,6 +28,8 @@ type MatchRule struct {
PciID *rules.PciIDRule `json:"pciId,omitempty"`
UsbID *rules.UsbIDRule `json:"usbId,omitempty"`
LoadedKMod *rules.LoadedKModRule `json:"loadedKMod,omitempty"`
CpuID *rules.CpuIDRule `json:"cpuId,omitempty"`
Kconfig *rules.KconfigRule `json:"kConfig,omitempty"`
}
type FeatureSpec struct {
@ -76,7 +77,8 @@ func (s Source) Discover() (source.Features, error) {
for _, customFeature := range allFeatureConfig {
featureExist, err := s.discoverFeature(customFeature)
if err != nil {
return features, fmt.Errorf("failed to discover feature: %s. %s", customFeature.Name, err.Error())
log.Printf("ERROR: failed to discover feature: %q: %s", customFeature.Name, err.Error())
continue
}
if featureExist {
features[customFeature.Name] = true
@ -119,6 +121,26 @@ func (s Source) discoverFeature(feature FeatureSpec) (bool, error) {
continue
}
}
// cpuid rule
if rule.CpuID != nil {
match, err := rule.CpuID.Match()
if err != nil {
return false, err
}
if !match {
continue
}
}
// kconfig rule
if rule.Kconfig != nil {
match, err := rule.Kconfig.Match()
if err != nil {
return false, err
}
if !match {
continue
}
}
return true, nil
}
return false, nil

View file

@ -0,0 +1,42 @@
/*
Copyright 2020 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 rules
import (
"sigs.k8s.io/node-feature-discovery/source/internal/cpuidutils"
)
// CpuIDRule implements Rule
type CpuIDRule []string
var cpuIdFlags map[string]struct{}
func (cpuids *CpuIDRule) Match() (bool, error) {
for _, f := range *cpuids {
if _, ok := cpuIdFlags[f]; !ok {
return false, nil
}
}
return true, nil
}
func init() {
cpuIdFlags = make(map[string]struct{})
for _, f := range cpuidutils.GetCpuidFlags() {
cpuIdFlags[f] = struct{}{}
}
}

View file

@ -0,0 +1,51 @@
/*
Copyright 2020 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 rules
import (
"fmt"
"sigs.k8s.io/node-feature-discovery/source/internal/kernelutils"
)
// KconfigRule implements Rule
type KconfigRule []string
var kConfigs map[string]struct{}
func (kconfigs *KconfigRule) Match() (bool, error) {
for _, f := range *kconfigs {
if _, ok := kConfigs[f]; !ok {
return false, nil
}
}
return true, nil
}
func init() {
kConfigs = make(map[string]struct{})
kconfig, err := kernelutils.ParseKconfig("")
if err == nil {
for k, v := range kconfig {
if v != "true" {
kConfigs[fmt.Sprintf("%s=%s", k, v)] = struct{}{}
} else {
kConfigs[k] = struct{}{}
}
}
}
}

View file

@ -14,13 +14,13 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package cpu
package cpuidutils
import (
"github.com/klauspost/cpuid"
)
// Discover returns feature names for all the supported CPU features.
func getCpuidFlags() []string {
func GetCpuidFlags() []string {
return cpuid.CPU.Features.Strings()
}

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package cpu
package cpuidutils
/*
#include <sys/auxv.h>
@ -90,7 +90,7 @@ var flagNames_arm = map[uint64]string{
CPU_ARM_FEATURE_CRC32: "CRC32",
}
func getCpuidFlags() []string {
func GetCpuidFlags() []string {
r := make([]string, 0, 20)
hwcap := uint64(C.gethwcap())
for i := uint(0); i < 64; i++ {

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package cpu
package cpuidutils
/*
#include <sys/auxv.h>
@ -80,7 +80,7 @@ var flagNames_arm64 = map[uint64]string{
CPU_ARM64_FEATURE_SVE: "SVE",
}
func getCpuidFlags() []string {
func GetCpuidFlags() []string {
r := make([]string, 0, 20)
hwcap := uint64(C.gethwcap())
for i := uint(0); i < 64; i++ {

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package cpu
package cpuidutils
/*
#include <sys/auxv.h>
@ -126,7 +126,7 @@ var flag2Names_ppc64le = map[uint64]string{
PPC_FEATURE2_HTM_NO_SUSPEND: "HTM-NO-SUSPEND",
}
func getCpuidFlags() []string {
func GetCpuidFlags() []string {
r := make([]string, 0, 30)
hwcap := uint64(C.gethwcap())
hwcap2 := uint64(C.gethwcap2())

View file

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package cpu
package cpuidutils
/*
#include <sys/auxv.h>
@ -71,7 +71,7 @@ var flagNames_s390x = map[uint64]string{
HWCAP_S390_DFLT: "DFLT",
}
func getCpuidFlags() []string {
func GetCpuidFlags() []string {
r := make([]string, 0, 20)
hwcap := uint64(C.gethwcap())
for i := uint(0); i < 64; i++ {

View file

@ -0,0 +1,121 @@
/*
Copyright 2018-2020 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 kernelutils
import (
"bytes"
"compress/gzip"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"regexp"
"strings"
"k8s.io/apimachinery/pkg/util/validation"
"sigs.k8s.io/node-feature-discovery/source"
)
// 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(configPath string) (map[string]string, error) {
kconfig := map[string]string{}
raw := []byte(nil)
var err error
var searchPaths []string
kVer, err := GetKernelVersion()
if err != nil {
searchPaths = []string{
"/proc/config.gz",
"/usr/src/linux/.config",
}
} else {
// from k8s.io/system-validator used by kubeadm
// preflight checks
searchPaths = []string{
"/proc/config.gz",
"/usr/src/linux-" + kVer + "/.config",
"/usr/src/linux/.config",
"/usr/lib/modules/" + kVer + "/config",
"/usr/lib/ostree-boot/config-" + kVer,
"/usr/lib/kernel/config-" + kVer,
"/usr/src/linux-headers-" + kVer + "/.config",
"/lib/modules/" + kVer + "/build/.config",
source.BootDir.Path("config-" + kVer),
}
}
for _, path := range append([]string{configPath}, searchPaths...) {
if len(path) > 0 {
if ".gz" == filepath.Ext(path) {
if raw, err = readKconfigGzip(path); err == nil {
break
}
} else {
if raw, err = ioutil.ReadFile(path); err == nil {
break
}
}
}
}
if raw == nil {
return nil, fmt.Errorf("Failed to read kernel config from %+v:", append([]string{configPath}, searchPaths...))
}
// Regexp for matching kconfig flags
re := regexp.MustCompile(`^CONFIG_(?P<flag>\w+)=(?P<value>.+)`)
// 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"
} else {
value := strings.Trim(m[2], `"`)
if len(value) > validation.LabelValueMaxLength {
log.Printf("WARNING: ignoring kconfig option '%s': value exceeds max length of %d characters", m[1], validation.LabelValueMaxLength)
continue
}
kconfig[m[1]] = value
}
}
}
return kconfig, nil
}

View file

@ -0,0 +1,30 @@
/*
Copyright 2018-2020 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 kernelutils
import (
"io/ioutil"
"strings"
)
func GetKernelVersion() (string, error) {
unameRaw, err := ioutil.ReadFile("/proc/sys/kernel/osrelease")
if err != nil {
return "", err
}
return strings.TrimSpace(string(unameRaw)), nil
}

View file

@ -17,16 +17,11 @@ limitations under the License.
package kernel
import (
"bytes"
"compress/gzip"
"io/ioutil"
"log"
"os"
"regexp"
"strings"
"k8s.io/apimachinery/pkg/util/validation"
"sigs.k8s.io/node-feature-discovery/source"
"sigs.k8s.io/node-feature-discovery/source/internal/kernelutils"
)
// Configuration file options
@ -85,7 +80,7 @@ func (s *Source) Discover() (source.Features, error) {
}
// Read kconfig
kconfig, err := s.parseKconfig()
kconfig, err := kernelutils.ParseKconfig(s.config.KconfigFile)
if err != nil {
log.Printf("ERROR: Failed to read kconfig: %s", err)
}
@ -111,14 +106,11 @@ func (s *Source) Discover() (source.Features, error) {
func parseVersion() (map[string]string, error) {
version := map[string]string{}
// Open file for reading
raw, err := ioutil.ReadFile("/proc/sys/kernel/osrelease")
full, err := kernelutils.GetKernelVersion()
if err != nil {
return nil, err
}
full := strings.TrimSpace(string(raw))
// Replace forbidden symbols
fullRegex := regexp.MustCompile("[^-A-Za-z0-9_.]")
full = fullRegex.ReplaceAllString(full, "_")
@ -137,82 +129,3 @@ 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 (s *Source) parseKconfig() (map[string]string, error) {
kconfig := map[string]string{}
raw := []byte(nil)
var err error
// First, try kconfig specified in the config file
if len(s.config.KconfigFile) > 0 {
raw, err = ioutil.ReadFile(s.config.KconfigFile)
if err != nil {
log.Printf("ERROR: Failed to read kernel config from %s: %s", s.config.KconfigFile, err)
}
}
// Then, try to read from /proc
if raw == nil {
raw, err = readKconfigGzip("/proc/config.gz")
if err != nil {
log.Printf("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(source.BootDir.Path("config-" + uname))
if err != nil {
return nil, err
}
}
// Regexp for matching kconfig flags
re := regexp.MustCompile(`^CONFIG_(?P<flag>\w+)=(?P<value>.+)`)
// 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"
} else {
value := strings.Trim(m[2], `"`)
if len(value) > validation.LabelValueMaxLength {
log.Printf("WARNING: ignoring kconfig option '%s': value exceeds max length of %d characters", m[1], validation.LabelValueMaxLength)
continue
}
kconfig[m[1]] = value
}
}
}
return kconfig, nil
}