Files
nomad/client/lib/numalib/detect_linux_test.go

277 lines
10 KiB
Go

// 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)
})
}
}