// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 //go:build linux package numalib import ( "os" "testing" "github.com/hashicorp/nomad/client/lib/idset" "github.com/hashicorp/nomad/client/lib/numalib/hw" "github.com/shoenig/test/must" ) // badSysData are example values from sysfs on unsupported platforms, e.g., // containers, virtualization guests func badSysData(path string) ([]byte, error) { return map[string][]byte{ "/sys/devices/system/node/online": []byte("0"), "/sys/devices/system/cpu/online": []byte("1,3"), // cpuOnline data indicates 2 CPU IDs online: 1 and 3 "/sys/devices/system/node/node0/distance": []byte("10"), "/sys/devices/system/node/node0/cpulist": []byte("0-3"), // cpuList data indicates 4 CPU cores available on node0 (can't be true) "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq": []byte("3500000"), "/sys/devices/system/cpu/cpu0/cpufreq/base_frequency": []byte("2100000"), "/sys/devices/system/cpu/cpu0/topology/physical_package_id": []byte("0"), "/sys/devices/system/cpu/cpu0/topology/thread_siblings_list": []byte("0,2"), "/sys/devices/system/cpu/cpu1/cpufreq/cpuinfo_max_freq": []byte("3500000"), "/sys/devices/system/cpu/cpu1/cpufreq/base_frequency": []byte("2100000"), "/sys/devices/system/cpu/cpu1/topology/physical_package_id": []byte("0"), "/sys/devices/system/cpu/cpu1/topology/thread_siblings_list": []byte("1,3"), "/sys/devices/system/cpu/cpu2/cpufreq/cpuinfo_max_freq": []byte("3500000"), "/sys/devices/system/cpu/cpu2/cpufreq/base_frequency": []byte("2100000"), "/sys/devices/system/cpu/cpu2/topology/physical_package_id": []byte("0"), "/sys/devices/system/cpu/cpu2/topology/thread_siblings_list": []byte("0,2"), "/sys/devices/system/cpu/cpu3/cpufreq/cpuinfo_max_freq": []byte("3500000"), "/sys/devices/system/cpu/cpu3/cpufreq/base_frequency": []byte("2100000"), "/sys/devices/system/cpu/cpu3/topology/physical_package_id": []byte("0"), "/sys/devices/system/cpu/cpu3/topology/thread_siblings_list": []byte("1,3"), }[path], nil } func goodSysData(path string) ([]byte, error) { return map[string][]byte{ "/sys/devices/system/node/online": []byte("0-1"), "/sys/devices/system/cpu/online": []byte("0-3"), "/sys/devices/system/node/node0/distance": []byte("10"), "/sys/devices/system/node/node0/cpulist": []byte("0-3"), "/sys/devices/system/node/node1/distance": []byte("10"), "/sys/devices/system/node/node1/cpulist": []byte("0-3"), "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq": []byte("3500000"), "/sys/devices/system/cpu/cpu0/cpufreq/base_frequency": []byte("2100000"), "/sys/devices/system/cpu/cpu0/topology/physical_package_id": []byte("0"), "/sys/devices/system/cpu/cpu0/topology/thread_siblings_list": []byte("0,2"), "/sys/devices/system/cpu/cpu1/cpufreq/cpuinfo_max_freq": []byte("3500000"), "/sys/devices/system/cpu/cpu1/cpufreq/base_frequency": []byte("2100000"), "/sys/devices/system/cpu/cpu1/topology/physical_package_id": []byte("0"), "/sys/devices/system/cpu/cpu1/topology/thread_siblings_list": []byte("1,3"), "/sys/devices/system/cpu/cpu2/cpufreq/cpuinfo_max_freq": []byte("3500000"), "/sys/devices/system/cpu/cpu2/cpufreq/base_frequency": []byte("2100000"), "/sys/devices/system/cpu/cpu2/topology/physical_package_id": []byte("0"), "/sys/devices/system/cpu/cpu2/topology/thread_siblings_list": []byte("0,2"), "/sys/devices/system/cpu/cpu3/cpufreq/cpuinfo_max_freq": []byte("3500000"), "/sys/devices/system/cpu/cpu3/cpufreq/base_frequency": []byte("2100000"), "/sys/devices/system/cpu/cpu3/topology/physical_package_id": []byte("0"), "/sys/devices/system/cpu/cpu3/topology/thread_siblings_list": []byte("1,3"), }[path], nil } func goodSysDataAMD(path string) ([]byte, error) { return map[string][]byte{ "/sys/devices/system/node/online": []byte("0-1"), "/sys/devices/system/cpu/online": []byte("0-3"), "/sys/devices/system/node/node0/distance": []byte("10"), "/sys/devices/system/node/node0/cpulist": []byte("0-3"), "/sys/devices/system/node/node1/distance": []byte("10"), "/sys/devices/system/node/node1/cpulist": []byte("0-3"), "/sys/devices/system/cpu/cpu0/acpi_cppc/nominal_freq": []byte("2450"), "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq": []byte("3500000"), "/sys/devices/system/cpu/cpu0/cpufreq/scaling_driver": []byte("acpi-cpufreq"), "/sys/devices/system/cpu/cpu0/topology/physical_package_id": []byte("0"), "/sys/devices/system/cpu/cpu0/topology/thread_siblings_list": []byte("0,2"), "/sys/devices/system/cpu/cpu1/acpi_cppc/nominal_freq": []byte("2450"), "/sys/devices/system/cpu/cpu1/cpufreq/cpuinfo_max_freq": []byte("3500000"), "/sys/devices/system/cpu/cpu1/cpufreq/scaling_driver": []byte("acpi-cpufreq"), "/sys/devices/system/cpu/cpu1/topology/physical_package_id": []byte("0"), "/sys/devices/system/cpu/cpu1/topology/thread_siblings_list": []byte("1,3"), "/sys/devices/system/cpu/cpu2/acpi_cppc/nominal_freq": []byte("2450"), "/sys/devices/system/cpu/cpu2/cpufreq/cpuinfo_max_freq": []byte("3500000"), "/sys/devices/system/cpu/cpu2/cpufreq/scaling_driver": []byte("acpi-cpufreq"), "/sys/devices/system/cpu/cpu2/topology/physical_package_id": []byte("0"), "/sys/devices/system/cpu/cpu2/topology/thread_siblings_list": []byte("0,2"), "/sys/devices/system/cpu/cpu3/acpi_cppc/nominal_freq": []byte("2450"), "/sys/devices/system/cpu/cpu3/cpufreq/cpuinfo_max_freq": []byte("3500000"), "/sys/devices/system/cpu/cpu3/cpufreq/scaling_driver": []byte("acpi-cpufreq"), "/sys/devices/system/cpu/cpu3/topology/physical_package_id": []byte("0"), "/sys/devices/system/cpu/cpu3/topology/thread_siblings_list": []byte("1,3"), }[path], nil } func TestSysfs_discoverOnline(t *testing.T) { st := MockTopology(&idset.Set[hw.NodeID]{}, SLIT{}, []Core{}) goodIDSet := idset.From[hw.NodeID]([]uint8{0, 1}) oneNode := idset.From[hw.NodeID]([]uint8{0}) tests := []struct { name string readerFunc pathReaderFn expectedIDSet *idset.Set[hw.NodeID] }{ {"lxc values", badSysData, oneNode}, {"good values", goodSysData, goodIDSet}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { sy := &Sysfs{} sy.discoverOnline(st, tt.readerFunc) must.Eq(t, tt.expectedIDSet, st.GetNodes()) }) } } func TestSysfs_discoverCosts(t *testing.T) { st := MockTopology(idset.Empty[hw.NodeID](), SLIT{}, []Core{}) twoNodes := idset.From[hw.NodeID]([]uint8{1, 3}) tests := []struct { name string nodeIDs *idset.Set[hw.NodeID] readerFunc pathReaderFn expectedDistances SLIT }{ {"empty node IDs", idset.Empty[hw.NodeID](), os.ReadFile, SLIT{}}, {"two nodes and bad sys data", twoNodes, badSysData, SLIT{ []Cost{0, 0}, []Cost{0, 0}, }}, {"two nodes and good sys data", twoNodes, goodSysData, SLIT{ []Cost{0, 0}, []Cost{10, 0}, }}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { sy := &Sysfs{} st.SetNodes(tt.nodeIDs) sy.discoverCosts(st, tt.readerFunc) must.Eq(t, tt.expectedDistances, st.Distances) }) } } func TestSysfs_discoverCores(t *testing.T) { st := MockTopology(idset.Empty[hw.NodeID](), SLIT{}, []Core{}) oneNode := idset.From[hw.NodeID]([]uint8{0}) twoNodes := idset.From[hw.NodeID]([]uint8{1, 3}) tests := []struct { name string nodeIDs *idset.Set[hw.NodeID] readerFunc pathReaderFn expectedTopology *Topology }{ {"empty core and node IDs", idset.Empty[hw.NodeID](), os.ReadFile, &Topology{}}, {"empty node IDs", idset.Empty[hw.NodeID](), goodSysData, &Topology{}}, // issue#19372 {"one node and bad sys data", oneNode, badSysData, &Topology{ nodeIDs: oneNode, Nodes: oneNode.Slice(), Cores: []Core{ { SocketID: 0, NodeID: 0, ID: 0, Grade: Performance, BaseSpeed: 2100, MaxSpeed: 3500, }, { SocketID: 0, NodeID: 0, ID: 1, Grade: Performance, BaseSpeed: 2100, MaxSpeed: 3500, }, }, }}, {"two nodes and good sys data", twoNodes, goodSysData, &Topology{ nodeIDs: twoNodes, Nodes: twoNodes.Slice(), Cores: []Core{ { SocketID: 1, NodeID: 0, ID: 0, Grade: Performance, BaseSpeed: 2100, MaxSpeed: 3500, }, { SocketID: 1, NodeID: 0, ID: 1, Grade: Performance, BaseSpeed: 2100, MaxSpeed: 3500, }, { SocketID: 1, NodeID: 0, ID: 2, Grade: Performance, BaseSpeed: 2100, MaxSpeed: 3500, }, { SocketID: 1, NodeID: 0, ID: 3, Grade: Performance, BaseSpeed: 2100, MaxSpeed: 3500, }, }, }}, {"two nodes and good sys AMD data", twoNodes, goodSysDataAMD, &Topology{ nodeIDs: twoNodes, Nodes: twoNodes.Slice(), Cores: []Core{ { SocketID: 1, NodeID: 0, ID: 0, Grade: Performance, BaseSpeed: 2450, MaxSpeed: 3500, }, { SocketID: 1, NodeID: 0, ID: 1, Grade: Performance, BaseSpeed: 2450, MaxSpeed: 3500, }, { SocketID: 1, NodeID: 0, ID: 2, Grade: Performance, BaseSpeed: 2450, MaxSpeed: 3500, }, { SocketID: 1, NodeID: 0, ID: 3, Grade: Performance, BaseSpeed: 2450, MaxSpeed: 3500, }, }, }}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { sy := &Sysfs{} st.SetNodes(tt.nodeIDs) sy.discoverCores(st, tt.readerFunc) must.Eq(t, tt.expectedTopology, st) }) } }