diff --git a/deployment/components/common/worker-mounts.yaml b/deployment/components/common/worker-mounts.yaml index 665cae07c..274b0374d 100644 --- a/deployment/components/common/worker-mounts.yaml +++ b/deployment/components/common/worker-mounts.yaml @@ -4,6 +4,9 @@ - name: host-boot hostPath: path: "/boot" + - name: host-proc-swaps + hostPath: + path: "/proc/swaps" - name: host-os-release hostPath: path: "/etc/os-release" @@ -38,6 +41,9 @@ - name: host-sys mountPath: "/host-sys" readOnly: true + - name: host-proc-swaps + mountPath: "/host-proc/swaps" + readOnly: true - name: host-usr-lib mountPath: "/host-usr/lib" readOnly: true diff --git a/deployment/helm/node-feature-discovery/templates/worker.yaml b/deployment/helm/node-feature-discovery/templates/worker.yaml index f49f9bd64..1b2beff2e 100644 --- a/deployment/helm/node-feature-discovery/templates/worker.yaml +++ b/deployment/helm/node-feature-discovery/templates/worker.yaml @@ -87,6 +87,9 @@ spec: - name: host-lib mountPath: "/host-lib" readOnly: true + - name: host-proc-swaps + mountPath: "/host-proc/swaps" + readOnly: true {{- if .Values.worker.mountUsrSrc }} - name: host-usr-src mountPath: "/host-usr/src" @@ -122,6 +125,9 @@ spec: - name: host-lib hostPath: path: "/lib" + - name: host-proc/swaps + hostPath: + path: "/proc/swaps" {{- if .Values.worker.mountUsrSrc }} - name: host-usr-src hostPath: diff --git a/docs/usage/customization-guide.md b/docs/usage/customization-guide.md index 6fecdb805..906384364 100644 --- a/docs/usage/customization-guide.md +++ b/docs/usage/customization-guide.md @@ -966,6 +966,8 @@ The following features are available for matching: | **`memory.numa`** | attribute | | | NUMA nodes | | | | **`is_numa`** | bool | `true` if NUMA architecture, `false` otherwise | | | | **`node_count`** | int | Number of NUMA nodes | +| **`memory.swap`** | attribute | | | Swap enabled on node | +| | | **`enabled`** | bool | `true` if swap partition detected, `false` otherwise | | **`network.device`** | instance | | | Physical (non-virtual) network interfaces present in the system | | | | **`name`** | string | Name of the network interface | | | | **``** | string | Sysfs network interface attribute, available attributes: `operstate`, `speed`, `sriov_numvfs`, `sriov_totalvfs` | diff --git a/docs/usage/features.md b/docs/usage/features.md index 5d869610a..afbe71c18 100644 --- a/docs/usage/features.md +++ b/docs/usage/features.md @@ -177,6 +177,7 @@ configuration options for details. | **`memory-numa`** | true | Multiple memory nodes i.e. NUMA architecture detected | | **`memory-nv.present`** | true | NVDIMM device(s) are present | | **`memory-nv.dax`** | true | NVDIMM region(s) configured in DAX mode are present | +| **`memory-swap.enabled`** | true | Swap is enabled on the node | ### Network diff --git a/pkg/utils/hostpath/hostpath.go b/pkg/utils/hostpath/hostpath.go index 557e33590..fddc83ce1 100644 --- a/pkg/utils/hostpath/hostpath.go +++ b/pkg/utils/hostpath/hostpath.go @@ -34,6 +34,8 @@ var ( VarDir = HostDir(pathPrefix + "var") // LibDir is where the /lib directory of the system to be inspected is located LibDir = HostDir(pathPrefix + "lib") + // ProcDir is where the /proc directory of the system to be inspected is located + ProcDir = HostDir(pathPrefix + "proc") ) // HostDir is a helper for handling host system directories diff --git a/source/memory/memory.go b/source/memory/memory.go index dbeb6b0ee..da8803fff 100644 --- a/source/memory/memory.go +++ b/source/memory/memory.go @@ -40,6 +40,9 @@ const NvFeature = "nv" // NumaFeature is the name of the feature set that holds all NUMA related features. const NumaFeature = "numa" +// SwapFeature is the name of the feature set that holds all Swap related features +const SwapFeature = "swap" + // memorySource implements the FeatureSource and LabelSource interfaces. type memorySource struct { features *nfdv1alpha1.Features @@ -68,6 +71,11 @@ func (s *memorySource) GetLabels() (source.FeatureLabels, error) { labels["numa"] = true } + // Swap + if isSwap, ok := features.Attributes[SwapFeature].Elements["enabled"]; ok && isSwap == "true" { + labels["swap"] = true + } + // NVDIMM if len(features.Instances[NvFeature].Elements) > 0 { labels["nv.present"] = true @@ -93,6 +101,13 @@ func (s *memorySource) Discover() error { s.features.Attributes[NumaFeature] = nfdv1alpha1.AttributeFeatureSet{Elements: numa} } + // Detect Swap + if swap, err := detectSwap(); err != nil { + klog.ErrorS(err, "failed to detect Swap nodes") + } else { + s.features.Attributes[SwapFeature] = nfdv1alpha1.AttributeFeatureSet{Elements: swap} + } + // Detect NVDIMM if nv, err := detectNv(); err != nil { klog.ErrorS(err, "failed to detect nvdimm devices") @@ -113,6 +128,20 @@ func (s *memorySource) GetFeatures() *nfdv1alpha1.Features { return s.features } +// detectSwap detects Swap node information +func detectSwap() (map[string]string, error) { + procBasePath := hostpath.ProcDir.Path("swaps") + lines, err := getNumberOfLinesFromFile(procBasePath) + if err != nil { + return nil, fmt.Errorf("failed to read swaps file: %w", err) + } + // /proc/swaps has a header row + // If there is more than a header then we assume we have swap. + return map[string]string{ + "enabled": strconv.FormatBool(lines > 1), + }, nil +} + // detectNuma detects NUMA node information func detectNuma() (map[string]string, error) { sysfsBasePath := hostpath.SysfsDir.Path("bus/node/devices") @@ -166,6 +195,14 @@ func readNdDeviceInfo(path string) nfdv1alpha1.InstanceFeature { return *nfdv1alpha1.NewInstanceFeature(attrs) } +func getNumberOfLinesFromFile(path string) (int, error) { + data, err := os.ReadFile(path) + if err != nil { + return 0, err + } + return len(strings.Split(string(data), "\n")), nil +} + func init() { source.Register(&src) } diff --git a/source/memory/memory_test.go b/source/memory/memory_test.go index 8cb3e92a7..c5edc1b36 100644 --- a/source/memory/memory_test.go +++ b/source/memory/memory_test.go @@ -31,5 +31,33 @@ func TestMemorySource(t *testing.T) { assert.Nil(t, err, err) assert.Empty(t, l) - +} + +func TestGetNumberofLinesFromFile(t *testing.T) { + type testCase struct { + path string + expectedLines int + expectErr bool + } + tc := []testCase{ + { + path: "testdata/swap", + expectedLines: 2, + }, + { + path: "testdata/noswap", + expectedLines: 1, + }, + { + path: "file_not_exist", + expectErr: true, + }, + } + for _, tc := range tc { + actual, err := getNumberOfLinesFromFile(tc.path) + if tc.expectErr { + assert.NotNil(t, err, "should get an error") + } + assert.Equal(t, tc.expectedLines, actual, "lines should match") + } } diff --git a/source/memory/testdata/noswap b/source/memory/testdata/noswap new file mode 100644 index 000000000..37a6cc05c --- /dev/null +++ b/source/memory/testdata/noswap @@ -0,0 +1 @@ +Filename Type Size Used Priority \ No newline at end of file diff --git a/source/memory/testdata/swap b/source/memory/testdata/swap new file mode 100644 index 000000000..6ffbd4070 --- /dev/null +++ b/source/memory/testdata/swap @@ -0,0 +1,2 @@ +Filename Type Size Used Priority +dummyfile partition 65555 0 -1 \ No newline at end of file