mirror of
https://github.com/kubernetes-sigs/node-feature-discovery.git
synced 2024-12-15 17:50:49 +00:00
source/pci: implement FeatureSource
Separate feature discovery and creation of feature labels in the pci source. Move pci_utils from source/internal to the source/pci package. Change the implementation of the PciID custom rule to utilize the FeatureSource interface of the pci source. Also, add minimalist unit test.
This commit is contained in:
parent
03bf94a8ad
commit
af0c683f60
4 changed files with 134 additions and 87 deletions
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2020 The Kubernetes Authors.
|
Copyright 2020-2021 The Kubernetes Authors.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -18,7 +18,10 @@ package rules
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
pciutils "sigs.k8s.io/node-feature-discovery/source/internal"
|
|
||||||
|
"sigs.k8s.io/node-feature-discovery/pkg/api/feature"
|
||||||
|
"sigs.k8s.io/node-feature-discovery/source"
|
||||||
|
"sigs.k8s.io/node-feature-discovery/source/pci"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Rule that matches on the following PCI device attributes: <class, vendor, device>
|
// Rule that matches on the following PCI device attributes: <class, vendor, device>
|
||||||
|
@ -37,40 +40,40 @@ type PciIDRule struct {
|
||||||
|
|
||||||
// Match PCI devices on provided PCI device attributes
|
// Match PCI devices on provided PCI device attributes
|
||||||
func (r *PciIDRule) Match() (bool, error) {
|
func (r *PciIDRule) Match() (bool, error) {
|
||||||
|
devs, ok := source.GetFeatureSource("pci").GetFeatures().Instances[pci.DeviceFeature]
|
||||||
|
if !ok {
|
||||||
|
return false, fmt.Errorf("cpuid information not available")
|
||||||
|
}
|
||||||
|
|
||||||
devAttr := map[string]bool{}
|
devAttr := map[string]bool{}
|
||||||
for _, attr := range []string{"class", "vendor", "device"} {
|
for _, attr := range []string{"class", "vendor", "device"} {
|
||||||
devAttr[attr] = true
|
devAttr[attr] = true
|
||||||
}
|
}
|
||||||
allDevs, err := pciutils.DetectPci(devAttr)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("failed to detect PCI devices: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, classDevs := range allDevs {
|
for _, dev := range devs.Elements {
|
||||||
for _, dev := range classDevs {
|
|
||||||
// match rule on a single device
|
// match rule on a single device
|
||||||
if r.matchDevOnRule(dev) {
|
if r.matchDevOnRule(dev) {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *PciIDRule) matchDevOnRule(dev pciutils.PciDeviceInfo) bool {
|
func (r *PciIDRule) matchDevOnRule(dev feature.InstanceFeature) bool {
|
||||||
if len(r.Class) == 0 && len(r.Vendor) == 0 && len(r.Device) == 0 {
|
if len(r.Class) == 0 && len(r.Vendor) == 0 && len(r.Device) == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(r.Class) > 0 && !in(dev["class"], r.Class) {
|
attrs := dev.Attributes
|
||||||
|
if len(r.Class) > 0 && !in(attrs["class"], r.Class) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(r.Vendor) > 0 && !in(dev["vendor"], r.Vendor) {
|
if len(r.Vendor) > 0 && !in(attrs["vendor"], r.Vendor) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(r.Device) > 0 && !in(dev["device"], r.Device) {
|
if len(r.Device) > 0 && !in(attrs["device"], r.Device) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,12 +22,15 @@ import (
|
||||||
|
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
|
"sigs.k8s.io/node-feature-discovery/pkg/api/feature"
|
||||||
|
"sigs.k8s.io/node-feature-discovery/pkg/utils"
|
||||||
"sigs.k8s.io/node-feature-discovery/source"
|
"sigs.k8s.io/node-feature-discovery/source"
|
||||||
pciutils "sigs.k8s.io/node-feature-discovery/source/internal"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const Name = "pci"
|
const Name = "pci"
|
||||||
|
|
||||||
|
const DeviceFeature = "device"
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
DeviceClassWhitelist []string `json:"deviceClassWhitelist,omitempty"`
|
DeviceClassWhitelist []string `json:"deviceClassWhitelist,omitempty"`
|
||||||
DeviceLabelFields []string `json:"deviceLabelFields,omitempty"`
|
DeviceLabelFields []string `json:"deviceLabelFields,omitempty"`
|
||||||
|
@ -41,14 +44,16 @@ func newDefaultConfig() *Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// pciSource implements the LabelSource and ConfigurableSource interfaces.
|
// pciSource implements the FeatureSource, LabelSource and ConfigurableSource interfaces.
|
||||||
type pciSource struct {
|
type pciSource struct {
|
||||||
config *Config
|
config *Config
|
||||||
|
features *feature.DomainFeatures
|
||||||
}
|
}
|
||||||
|
|
||||||
// Singleton source instance
|
// Singleton source instance
|
||||||
var (
|
var (
|
||||||
src pciSource
|
src = pciSource{config: newDefaultConfig()}
|
||||||
|
_ source.FeatureSource = &src
|
||||||
_ source.LabelSource = &src
|
_ source.LabelSource = &src
|
||||||
_ source.ConfigurableSource = &src
|
_ source.ConfigurableSource = &src
|
||||||
)
|
)
|
||||||
|
@ -77,16 +82,17 @@ func (s *pciSource) Priority() int { return 0 }
|
||||||
|
|
||||||
// GetLabels method of the LabelSource interface
|
// GetLabels method of the LabelSource interface
|
||||||
func (s *pciSource) GetLabels() (source.FeatureLabels, error) {
|
func (s *pciSource) GetLabels() (source.FeatureLabels, error) {
|
||||||
features := source.FeatureLabels{}
|
labels := source.FeatureLabels{}
|
||||||
|
features := s.GetFeatures()
|
||||||
|
|
||||||
// Construct a device label format, a sorted list of valid attributes
|
// Construct a device label format, a sorted list of valid attributes
|
||||||
deviceLabelFields := []string{}
|
deviceLabelFields := make([]string, 0)
|
||||||
configLabelFields := map[string]bool{}
|
configLabelFields := make(map[string]struct{}, len(s.config.DeviceLabelFields))
|
||||||
for _, field := range s.config.DeviceLabelFields {
|
for _, field := range s.config.DeviceLabelFields {
|
||||||
configLabelFields[field] = true
|
configLabelFields[field] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, attr := range pciutils.DefaultPciDevAttrs {
|
for _, attr := range mandatoryDevAttrs {
|
||||||
if _, ok := configLabelFields[attr]; ok {
|
if _, ok := configLabelFields[attr]; ok {
|
||||||
deviceLabelFields = append(deviceLabelFields, attr)
|
deviceLabelFields = append(deviceLabelFields, attr)
|
||||||
delete(configLabelFields, attr)
|
delete(configLabelFields, attr)
|
||||||
|
@ -97,50 +103,59 @@ func (s *pciSource) GetLabels() (source.FeatureLabels, error) {
|
||||||
for key := range configLabelFields {
|
for key := range configLabelFields {
|
||||||
keys = append(keys, key)
|
keys = append(keys, key)
|
||||||
}
|
}
|
||||||
klog.Warningf("invalid fields '%v' in deviceLabelFields, ignoring...", keys)
|
klog.Warningf("invalid fields (%s) in deviceLabelFields, ignoring...", strings.Join(keys, ", "))
|
||||||
}
|
}
|
||||||
if len(deviceLabelFields) == 0 {
|
if len(deviceLabelFields) == 0 {
|
||||||
klog.Warningf("no valid fields in deviceLabelFields defined, using the defaults")
|
klog.Warningf("no valid fields in deviceLabelFields defined, using the defaults")
|
||||||
deviceLabelFields = []string{"class", "vendor"}
|
deviceLabelFields = []string{"class", "vendor"}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read extraDevAttrs + configured or default labels. Attributes
|
|
||||||
// set to 'true' are considered must-have.
|
|
||||||
deviceAttrs := map[string]bool{}
|
|
||||||
for _, label := range pciutils.ExtraPciDevAttrs {
|
|
||||||
deviceAttrs[label] = false
|
|
||||||
}
|
|
||||||
for _, label := range deviceLabelFields {
|
|
||||||
deviceAttrs[label] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
devs, err := pciutils.DetectPci(deviceAttrs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to detect PCI devices: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate over all device classes
|
// Iterate over all device classes
|
||||||
for class, classDevs := range devs {
|
for _, dev := range features.Instances[DeviceFeature].Elements {
|
||||||
|
attrs := dev.Attributes
|
||||||
|
class := attrs["class"]
|
||||||
for _, white := range s.config.DeviceClassWhitelist {
|
for _, white := range s.config.DeviceClassWhitelist {
|
||||||
if strings.HasPrefix(class, strings.ToLower(white)) {
|
if strings.HasPrefix(string(class), strings.ToLower(white)) {
|
||||||
for _, dev := range classDevs {
|
|
||||||
devLabel := ""
|
devLabel := ""
|
||||||
for i, attr := range deviceLabelFields {
|
for i, attr := range deviceLabelFields {
|
||||||
devLabel += dev[attr]
|
devLabel += attrs[attr]
|
||||||
if i < len(deviceLabelFields)-1 {
|
if i < len(deviceLabelFields)-1 {
|
||||||
devLabel += "_"
|
devLabel += "_"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
features[devLabel+".present"] = true
|
labels[devLabel+".present"] = true
|
||||||
|
|
||||||
if _, ok := dev["sriov_totalvfs"]; ok {
|
if _, ok := attrs["sriov_totalvfs"]; ok {
|
||||||
features[devLabel+".sriov.capable"] = true
|
labels[devLabel+".sriov.capable"] = true
|
||||||
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return labels, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Discover method of the FeatureSource interface
|
||||||
|
func (s *pciSource) Discover() error {
|
||||||
|
s.features = feature.NewDomainFeatures()
|
||||||
|
|
||||||
|
devs, err := detectPci()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to detect PCI devices: %s", err.Error())
|
||||||
}
|
}
|
||||||
return features, nil
|
s.features.Instances[DeviceFeature] = feature.NewInstanceFeatures(devs)
|
||||||
|
|
||||||
|
utils.KlogDump(3, "discovered pci features:", " ", s.features)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFeatures method of the FeatureSource Interface
|
||||||
|
func (s *pciSource) GetFeatures() *feature.DomainFeatures {
|
||||||
|
if s.features == nil {
|
||||||
|
s.features = feature.NewDomainFeatures()
|
||||||
|
}
|
||||||
|
return s.features
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
36
source/pci/pci_test.go
Normal file
36
source/pci/pci_test.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 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 pci
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"sigs.k8s.io/node-feature-discovery/pkg/api/feature"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPciSource(t *testing.T) {
|
||||||
|
assert.Equal(t, src.Name(), Name)
|
||||||
|
|
||||||
|
// Check that GetLabels works with empty features
|
||||||
|
src.features = feature.NewDomainFeatures()
|
||||||
|
l, err := src.GetLabels()
|
||||||
|
|
||||||
|
assert.Nil(t, err, err)
|
||||||
|
assert.Empty(t, l)
|
||||||
|
|
||||||
|
}
|
|
@ -14,28 +14,27 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package busutils
|
package pci
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
|
"sigs.k8s.io/node-feature-discovery/pkg/api/feature"
|
||||||
"sigs.k8s.io/node-feature-discovery/source"
|
"sigs.k8s.io/node-feature-discovery/source"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PciDeviceInfo map[string]string
|
var mandatoryDevAttrs = []string{"class", "vendor", "device", "subsystem_vendor", "subsystem_device"}
|
||||||
|
var optionalDevAttrs = []string{"sriov_totalvfs"}
|
||||||
var DefaultPciDevAttrs = []string{"class", "vendor", "device", "subsystem_vendor", "subsystem_device"}
|
|
||||||
var ExtraPciDevAttrs = []string{"sriov_totalvfs"}
|
|
||||||
|
|
||||||
// Read a single PCI device attribute
|
// Read a single PCI device attribute
|
||||||
// A PCI attribute in this context, maps to the corresponding sysfs file
|
// A PCI attribute in this context, maps to the corresponding sysfs file
|
||||||
func readSinglePciAttribute(devPath string, attrName string) (string, error) {
|
func readSinglePciAttribute(devPath string, attrName string) (string, error) {
|
||||||
data, err := ioutil.ReadFile(path.Join(devPath, attrName))
|
data, err := ioutil.ReadFile(filepath.Join(devPath, attrName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to read device attribute %s: %v", attrName, err)
|
return "", fmt.Errorf("failed to read device attribute %s: %v", attrName, err)
|
||||||
}
|
}
|
||||||
|
@ -51,49 +50,43 @@ func readSinglePciAttribute(devPath string, attrName string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read information of one PCI device
|
// Read information of one PCI device
|
||||||
func readPciDevInfo(devPath string, deviceAttrSpec map[string]bool) (PciDeviceInfo, error) {
|
func readPciDevInfo(devPath string) (*feature.InstanceFeature, error) {
|
||||||
info := PciDeviceInfo{}
|
attrs := make(map[string]string)
|
||||||
|
for _, attr := range mandatoryDevAttrs {
|
||||||
for attr, must := range deviceAttrSpec {
|
|
||||||
attrVal, err := readSinglePciAttribute(devPath, attr)
|
attrVal, err := readSinglePciAttribute(devPath, attr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if must {
|
return nil, fmt.Errorf("failed to read device %s: %s", attr, err)
|
||||||
return info, fmt.Errorf("failed to read device %s: %s", attr, err)
|
|
||||||
}
|
}
|
||||||
continue
|
attrs[attr] = attrVal
|
||||||
|
|
||||||
}
|
}
|
||||||
info[attr] = attrVal
|
for _, attr := range optionalDevAttrs {
|
||||||
|
attrVal, err := readSinglePciAttribute(devPath, attr)
|
||||||
|
if err == nil {
|
||||||
|
attrs[attr] = attrVal
|
||||||
}
|
}
|
||||||
return info, nil
|
}
|
||||||
|
return feature.NewInstanceFeature(attrs), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DetectPci lists available PCI devices and retrieve device attributes.
|
// detectPci detects available PCI devices and retrieves their device attributes.
|
||||||
// deviceAttrSpec is a map which specifies which attributes to retrieve.
|
// An error is returned if reading any of the mandatory attributes fails.
|
||||||
// a false value for a specific attribute marks the attribute as optional.
|
func detectPci() ([]feature.InstanceFeature, error) {
|
||||||
// a true value for a specific attribute marks the attribute as mandatory.
|
|
||||||
// "class" attribute is considered mandatory.
|
|
||||||
// will fail if the retrieval of a mandatory attribute fails.
|
|
||||||
func DetectPci(deviceAttrSpec map[string]bool) (map[string][]PciDeviceInfo, error) {
|
|
||||||
sysfsBasePath := source.SysfsDir.Path("bus/pci/devices")
|
sysfsBasePath := source.SysfsDir.Path("bus/pci/devices")
|
||||||
devInfo := make(map[string][]PciDeviceInfo)
|
|
||||||
|
|
||||||
devices, err := ioutil.ReadDir(sysfsBasePath)
|
devices, err := ioutil.ReadDir(sysfsBasePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// "class" is a mandatory attribute, inject it to spec if needed.
|
|
||||||
deviceAttrSpec["class"] = true
|
|
||||||
|
|
||||||
// Iterate over devices
|
// Iterate over devices
|
||||||
|
devInfo := make([]feature.InstanceFeature, 0, len(devices))
|
||||||
for _, device := range devices {
|
for _, device := range devices {
|
||||||
info, err := readPciDevInfo(path.Join(sysfsBasePath, device.Name()), deviceAttrSpec)
|
info, err := readPciDevInfo(filepath.Join(sysfsBasePath, device.Name()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
klog.Error(err)
|
klog.Error(err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
class := info["class"]
|
devInfo = append(devInfo, *info)
|
||||||
devInfo[class] = append(devInfo[class], info)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return devInfo, nil
|
return devInfo, nil
|
Loading…
Reference in a new issue