mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-22 11:21:47 +00:00
Merge pull request #112914 from PiotrProkop/topology-manager-policies-flag
node: topologymanager: Improved multi-numa alignment in Topology Manager
This commit is contained in:
commit
243ba086e7
@ -519,6 +519,7 @@ func AddKubeletConfigFlags(mainfs *pflag.FlagSet, c *kubeletconfig.KubeletConfig
|
|||||||
fs.BoolVar(&c.ProtectKernelDefaults, "protect-kernel-defaults", c.ProtectKernelDefaults, "Default kubelet behaviour for kernel tuning. If set, kubelet errors if any of kernel tunables is different than kubelet defaults.")
|
fs.BoolVar(&c.ProtectKernelDefaults, "protect-kernel-defaults", c.ProtectKernelDefaults, "Default kubelet behaviour for kernel tuning. If set, kubelet errors if any of kernel tunables is different than kubelet defaults.")
|
||||||
fs.StringVar(&c.ReservedSystemCPUs, "reserved-cpus", c.ReservedSystemCPUs, "A comma-separated list of CPUs or CPU ranges that are reserved for system and kubernetes usage. This specific list will supersede cpu counts in --system-reserved and --kube-reserved.")
|
fs.StringVar(&c.ReservedSystemCPUs, "reserved-cpus", c.ReservedSystemCPUs, "A comma-separated list of CPUs or CPU ranges that are reserved for system and kubernetes usage. This specific list will supersede cpu counts in --system-reserved and --kube-reserved.")
|
||||||
fs.StringVar(&c.TopologyManagerScope, "topology-manager-scope", c.TopologyManagerScope, "Scope to which topology hints applied. Topology Manager collects hints from Hint Providers and applies them to defined scope to ensure the pod admission. Possible values: 'container', 'pod'.")
|
fs.StringVar(&c.TopologyManagerScope, "topology-manager-scope", c.TopologyManagerScope, "Scope to which topology hints applied. Topology Manager collects hints from Hint Providers and applies them to defined scope to ensure the pod admission. Possible values: 'container', 'pod'.")
|
||||||
|
fs.Var(cliflag.NewMapStringStringNoSplit(&c.TopologyManagerPolicyOptions), "topology-manager-policy-options", "A set of key=value Topology Manager policy options to use, to fine tune their behaviour. If not supplied, keep the default behaviour.")
|
||||||
// Node Allocatable Flags
|
// Node Allocatable Flags
|
||||||
fs.Var(cliflag.NewMapStringString(&c.SystemReserved), "system-reserved", "A set of ResourceName=ResourceQuantity (e.g. cpu=200m,memory=500Mi,ephemeral-storage=1Gi) pairs that describe resources reserved for non-kubernetes components. Currently only cpu, memory and local ephemeral storage for root file system are supported. See https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ for more detail. [default=none]")
|
fs.Var(cliflag.NewMapStringString(&c.SystemReserved), "system-reserved", "A set of ResourceName=ResourceQuantity (e.g. cpu=200m,memory=500Mi,ephemeral-storage=1Gi) pairs that describe resources reserved for non-kubernetes components. Currently only cpu, memory and local ephemeral storage for root file system are supported. See https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ for more detail. [default=none]")
|
||||||
fs.Var(cliflag.NewMapStringString(&c.KubeReserved), "kube-reserved", "A set of ResourceName=ResourceQuantity (e.g. cpu=200m,memory=500Mi,ephemeral-storage=1Gi) pairs that describe resources reserved for kubernetes system components. Currently only cpu, memory and local ephemeral storage for root file system are supported. See https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ for more detail. [default=none]")
|
fs.Var(cliflag.NewMapStringString(&c.KubeReserved), "kube-reserved", "A set of ResourceName=ResourceQuantity (e.g. cpu=200m,memory=500Mi,ephemeral-storage=1Gi) pairs that describe resources reserved for kubernetes system components. Currently only cpu, memory and local ephemeral storage for root file system are supported. See https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ for more detail. [default=none]")
|
||||||
|
@ -710,6 +710,16 @@ func run(ctx context.Context, s *options.KubeletServer, kubeDeps *kubelet.Depend
|
|||||||
s.CPUManagerPolicyOptions, features.CPUManager, features.CPUManagerPolicyOptions)
|
s.CPUManagerPolicyOptions, features.CPUManager, features.CPUManagerPolicyOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var topologyManagerPolicyOptions map[string]string
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(features.TopologyManager) {
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(features.TopologyManagerPolicyOptions) {
|
||||||
|
topologyManagerPolicyOptions = s.TopologyManagerPolicyOptions
|
||||||
|
} else if s.TopologyManagerPolicyOptions != nil {
|
||||||
|
return fmt.Errorf("topology manager policy options %v require feature gates %q, %q enabled",
|
||||||
|
s.TopologyManagerPolicyOptions, features.TopologyManager, features.TopologyManagerPolicyOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
kubeDeps.ContainerManager, err = cm.NewContainerManager(
|
kubeDeps.ContainerManager, err = cm.NewContainerManager(
|
||||||
kubeDeps.Mounter,
|
kubeDeps.Mounter,
|
||||||
kubeDeps.CAdvisorInterface,
|
kubeDeps.CAdvisorInterface,
|
||||||
@ -732,17 +742,18 @@ func run(ctx context.Context, s *options.KubeletServer, kubeDeps *kubelet.Depend
|
|||||||
ReservedSystemCPUs: reservedSystemCPUs,
|
ReservedSystemCPUs: reservedSystemCPUs,
|
||||||
HardEvictionThresholds: hardEvictionThresholds,
|
HardEvictionThresholds: hardEvictionThresholds,
|
||||||
},
|
},
|
||||||
QOSReserved: *experimentalQOSReserved,
|
QOSReserved: *experimentalQOSReserved,
|
||||||
CPUManagerPolicy: s.CPUManagerPolicy,
|
CPUManagerPolicy: s.CPUManagerPolicy,
|
||||||
CPUManagerPolicyOptions: cpuManagerPolicyOptions,
|
CPUManagerPolicyOptions: cpuManagerPolicyOptions,
|
||||||
CPUManagerReconcilePeriod: s.CPUManagerReconcilePeriod.Duration,
|
CPUManagerReconcilePeriod: s.CPUManagerReconcilePeriod.Duration,
|
||||||
ExperimentalMemoryManagerPolicy: s.MemoryManagerPolicy,
|
ExperimentalMemoryManagerPolicy: s.MemoryManagerPolicy,
|
||||||
ExperimentalMemoryManagerReservedMemory: s.ReservedMemory,
|
ExperimentalMemoryManagerReservedMemory: s.ReservedMemory,
|
||||||
ExperimentalPodPidsLimit: s.PodPidsLimit,
|
ExperimentalPodPidsLimit: s.PodPidsLimit,
|
||||||
EnforceCPULimits: s.CPUCFSQuota,
|
EnforceCPULimits: s.CPUCFSQuota,
|
||||||
CPUCFSQuotaPeriod: s.CPUCFSQuotaPeriod.Duration,
|
CPUCFSQuotaPeriod: s.CPUCFSQuotaPeriod.Duration,
|
||||||
ExperimentalTopologyManagerPolicy: s.TopologyManagerPolicy,
|
ExperimentalTopologyManagerPolicy: s.TopologyManagerPolicy,
|
||||||
ExperimentalTopologyManagerScope: s.TopologyManagerScope,
|
ExperimentalTopologyManagerScope: s.TopologyManagerScope,
|
||||||
|
ExperimentalTopologyManagerPolicyOptions: topologyManagerPolicyOptions,
|
||||||
},
|
},
|
||||||
s.FailSwapOn,
|
s.FailSwapOn,
|
||||||
kubeDeps.Recorder)
|
kubeDeps.Recorder)
|
||||||
|
@ -781,6 +781,34 @@ const (
|
|||||||
// Enable resource managers to make NUMA aligned decisions
|
// Enable resource managers to make NUMA aligned decisions
|
||||||
TopologyManager featuregate.Feature = "TopologyManager"
|
TopologyManager featuregate.Feature = "TopologyManager"
|
||||||
|
|
||||||
|
// owner: @PiotrProkop
|
||||||
|
// kep: https://kep.k8s.io/3545
|
||||||
|
// alpha: v1.26
|
||||||
|
//
|
||||||
|
// Allow fine-tuning of topology manager policies with alpha options.
|
||||||
|
// This feature gate:
|
||||||
|
// - will guard *a group* of topology manager options whose quality level is alpha.
|
||||||
|
// - will never graduate to beta or stable.
|
||||||
|
TopologyManagerPolicyAlphaOptions featuregate.Feature = "TopologyManagerPolicyAlphaOptions"
|
||||||
|
|
||||||
|
// owner: @PiotrProkop
|
||||||
|
// kep: https://kep.k8s.io/3545
|
||||||
|
// alpha: v1.26
|
||||||
|
//
|
||||||
|
// Allow fine-tuning of topology manager policies with beta options.
|
||||||
|
// This feature gate:
|
||||||
|
// - will guard *a group* of topology manager options whose quality level is beta.
|
||||||
|
// - is thus *introduced* as beta
|
||||||
|
// - will never graduate to stable.
|
||||||
|
TopologyManagerPolicyBetaOptions featuregate.Feature = "TopologyManagerPolicyBetaOptions"
|
||||||
|
|
||||||
|
// owner: @PiotrProkop
|
||||||
|
// kep: https://kep.k8s.io/3545
|
||||||
|
// alpha: v1.26
|
||||||
|
//
|
||||||
|
// Allow the usage of options to fine-tune the topology manager policies.
|
||||||
|
TopologyManagerPolicyOptions featuregate.Feature = "TopologyManagerPolicyOptions"
|
||||||
|
|
||||||
// owner: @rata, @giuseppe
|
// owner: @rata, @giuseppe
|
||||||
// kep: https://kep.k8s.io/127
|
// kep: https://kep.k8s.io/127
|
||||||
// alpha: v1.25
|
// alpha: v1.25
|
||||||
@ -1050,6 +1078,12 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
|
|||||||
|
|
||||||
TopologyManager: {Default: true, PreRelease: featuregate.Beta},
|
TopologyManager: {Default: true, PreRelease: featuregate.Beta},
|
||||||
|
|
||||||
|
TopologyManagerPolicyAlphaOptions: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
|
||||||
|
TopologyManagerPolicyBetaOptions: {Default: false, PreRelease: featuregate.Beta},
|
||||||
|
|
||||||
|
TopologyManagerPolicyOptions: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
|
||||||
VolumeCapacityPriority: {Default: false, PreRelease: featuregate.Alpha},
|
VolumeCapacityPriority: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
|
||||||
UserNamespacesStatelessPodsSupport: {Default: false, PreRelease: featuregate.Alpha},
|
UserNamespacesStatelessPodsSupport: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
16
pkg/generated/openapi/zz_generated.openapi.go
generated
16
pkg/generated/openapi/zz_generated.openapi.go
generated
@ -56453,6 +56453,22 @@ func schema_k8sio_kubelet_config_v1beta1_KubeletConfiguration(ref common.Referen
|
|||||||
Format: "",
|
Format: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"topologyManagerPolicyOptions": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "TopologyManagerPolicyOptions is a set of key=value which allows to set extra options to fine tune the behaviour of the topology manager policies. Requires both the \"TopologyManager\" and \"TopologyManagerPolicyOptions\" feature gates to be enabled. Default: nil",
|
||||||
|
Type: []string{"object"},
|
||||||
|
AdditionalProperties: &spec.SchemaOrBool{
|
||||||
|
Allows: true,
|
||||||
|
Schema: &spec.Schema{
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Default: "",
|
||||||
|
Type: []string{"string"},
|
||||||
|
Format: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
"qosReserved": {
|
"qosReserved": {
|
||||||
SchemaProps: spec.SchemaProps{
|
SchemaProps: spec.SchemaProps{
|
||||||
Description: "qosReserved is a set of resource name to percentage pairs that specify the minimum percentage of a resource reserved for exclusive use by the guaranteed QoS tier. Currently supported resources: \"memory\" Requires the QOSReserved feature gate to be enabled. Default: nil",
|
Description: "qosReserved is a set of resource name to percentage pairs that specify the minimum percentage of a resource reserved for exclusive use by the guaranteed QoS tier. Currently supported resources: \"memory\" Requires the QOSReserved feature gate to be enabled. Default: nil",
|
||||||
|
@ -76,6 +76,7 @@ func Funcs(codecs runtimeserializer.CodecFactory) []interface{} {
|
|||||||
obj.NodeStatusMaxImages = 50
|
obj.NodeStatusMaxImages = 50
|
||||||
obj.TopologyManagerPolicy = kubeletconfig.NoneTopologyManagerPolicy
|
obj.TopologyManagerPolicy = kubeletconfig.NoneTopologyManagerPolicy
|
||||||
obj.TopologyManagerScope = kubeletconfig.ContainerTopologyManagerScope
|
obj.TopologyManagerScope = kubeletconfig.ContainerTopologyManagerScope
|
||||||
|
obj.TopologyManagerPolicyOptions = make(map[string]string)
|
||||||
obj.QOSReserved = map[string]string{
|
obj.QOSReserved = map[string]string{
|
||||||
"memory": "50%",
|
"memory": "50%",
|
||||||
}
|
}
|
||||||
|
@ -175,6 +175,7 @@ var (
|
|||||||
"CPUManagerReconcilePeriod.Duration",
|
"CPUManagerReconcilePeriod.Duration",
|
||||||
"TopologyManagerPolicy",
|
"TopologyManagerPolicy",
|
||||||
"TopologyManagerScope",
|
"TopologyManagerScope",
|
||||||
|
"TopologyManagerPolicyOptions[*]",
|
||||||
"QOSReserved[*]",
|
"QOSReserved[*]",
|
||||||
"CgroupDriver",
|
"CgroupDriver",
|
||||||
"CgroupRoot",
|
"CgroupRoot",
|
||||||
|
@ -241,6 +241,10 @@ type KubeletConfiguration struct {
|
|||||||
// Default: "container"
|
// Default: "container"
|
||||||
// +optional
|
// +optional
|
||||||
TopologyManagerScope string
|
TopologyManagerScope string
|
||||||
|
// TopologyManagerPolicyOptions is a set of key=value which allows to set extra options
|
||||||
|
// to fine tune the behaviour of the topology manager policies.
|
||||||
|
// Requires both the "TopologyManager" and "TopologyManagerPolicyOptions" feature gates to be enabled.
|
||||||
|
TopologyManagerPolicyOptions map[string]string
|
||||||
// Map of QoS resource reservation percentages (memory only for now).
|
// Map of QoS resource reservation percentages (memory only for now).
|
||||||
// Requires the QOSReserved feature gate to be enabled.
|
// Requires the QOSReserved feature gate to be enabled.
|
||||||
QOSReserved map[string]string
|
QOSReserved map[string]string
|
||||||
|
@ -412,6 +412,7 @@ func autoConvert_v1beta1_KubeletConfiguration_To_config_KubeletConfiguration(in
|
|||||||
out.MemoryManagerPolicy = in.MemoryManagerPolicy
|
out.MemoryManagerPolicy = in.MemoryManagerPolicy
|
||||||
out.TopologyManagerPolicy = in.TopologyManagerPolicy
|
out.TopologyManagerPolicy = in.TopologyManagerPolicy
|
||||||
out.TopologyManagerScope = in.TopologyManagerScope
|
out.TopologyManagerScope = in.TopologyManagerScope
|
||||||
|
out.TopologyManagerPolicyOptions = *(*map[string]string)(unsafe.Pointer(&in.TopologyManagerPolicyOptions))
|
||||||
out.QOSReserved = *(*map[string]string)(unsafe.Pointer(&in.QOSReserved))
|
out.QOSReserved = *(*map[string]string)(unsafe.Pointer(&in.QOSReserved))
|
||||||
out.RuntimeRequestTimeout = in.RuntimeRequestTimeout
|
out.RuntimeRequestTimeout = in.RuntimeRequestTimeout
|
||||||
out.HairpinMode = in.HairpinMode
|
out.HairpinMode = in.HairpinMode
|
||||||
@ -592,6 +593,7 @@ func autoConvert_config_KubeletConfiguration_To_v1beta1_KubeletConfiguration(in
|
|||||||
out.MemoryManagerPolicy = in.MemoryManagerPolicy
|
out.MemoryManagerPolicy = in.MemoryManagerPolicy
|
||||||
out.TopologyManagerPolicy = in.TopologyManagerPolicy
|
out.TopologyManagerPolicy = in.TopologyManagerPolicy
|
||||||
out.TopologyManagerScope = in.TopologyManagerScope
|
out.TopologyManagerScope = in.TopologyManagerScope
|
||||||
|
out.TopologyManagerPolicyOptions = *(*map[string]string)(unsafe.Pointer(&in.TopologyManagerPolicyOptions))
|
||||||
out.QOSReserved = *(*map[string]string)(unsafe.Pointer(&in.QOSReserved))
|
out.QOSReserved = *(*map[string]string)(unsafe.Pointer(&in.QOSReserved))
|
||||||
out.RuntimeRequestTimeout = in.RuntimeRequestTimeout
|
out.RuntimeRequestTimeout = in.RuntimeRequestTimeout
|
||||||
out.HairpinMode = in.HairpinMode
|
out.HairpinMode = in.HairpinMode
|
||||||
|
7
pkg/kubelet/apis/config/zz_generated.deepcopy.go
generated
7
pkg/kubelet/apis/config/zz_generated.deepcopy.go
generated
@ -211,6 +211,13 @@ func (in *KubeletConfiguration) DeepCopyInto(out *KubeletConfiguration) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
out.CPUManagerReconcilePeriod = in.CPUManagerReconcilePeriod
|
out.CPUManagerReconcilePeriod = in.CPUManagerReconcilePeriod
|
||||||
|
if in.TopologyManagerPolicyOptions != nil {
|
||||||
|
in, out := &in.TopologyManagerPolicyOptions, &out.TopologyManagerPolicyOptions
|
||||||
|
*out = make(map[string]string, len(*in))
|
||||||
|
for key, val := range *in {
|
||||||
|
(*out)[key] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
if in.QOSReserved != nil {
|
if in.QOSReserved != nil {
|
||||||
in, out := &in.QOSReserved, &out.QOSReserved
|
in, out := &in.QOSReserved, &out.QOSReserved
|
||||||
*out = make(map[string]string, len(*in))
|
*out = make(map[string]string, len(*in))
|
||||||
|
@ -133,17 +133,18 @@ type NodeConfig struct {
|
|||||||
KubeletRootDir string
|
KubeletRootDir string
|
||||||
ProtectKernelDefaults bool
|
ProtectKernelDefaults bool
|
||||||
NodeAllocatableConfig
|
NodeAllocatableConfig
|
||||||
QOSReserved map[v1.ResourceName]int64
|
QOSReserved map[v1.ResourceName]int64
|
||||||
CPUManagerPolicy string
|
CPUManagerPolicy string
|
||||||
CPUManagerPolicyOptions map[string]string
|
CPUManagerPolicyOptions map[string]string
|
||||||
ExperimentalTopologyManagerScope string
|
ExperimentalTopologyManagerScope string
|
||||||
CPUManagerReconcilePeriod time.Duration
|
CPUManagerReconcilePeriod time.Duration
|
||||||
ExperimentalMemoryManagerPolicy string
|
ExperimentalMemoryManagerPolicy string
|
||||||
ExperimentalMemoryManagerReservedMemory []kubeletconfig.MemoryReservation
|
ExperimentalMemoryManagerReservedMemory []kubeletconfig.MemoryReservation
|
||||||
ExperimentalPodPidsLimit int64
|
ExperimentalPodPidsLimit int64
|
||||||
EnforceCPULimits bool
|
EnforceCPULimits bool
|
||||||
CPUCFSQuotaPeriod time.Duration
|
CPUCFSQuotaPeriod time.Duration
|
||||||
ExperimentalTopologyManagerPolicy string
|
ExperimentalTopologyManagerPolicy string
|
||||||
|
ExperimentalTopologyManagerPolicyOptions map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
type NodeAllocatableConfig struct {
|
type NodeAllocatableConfig struct {
|
||||||
|
@ -289,6 +289,7 @@ func NewContainerManager(mountUtil mount.Interface, cadvisorInterface cadvisor.I
|
|||||||
machineInfo.Topology,
|
machineInfo.Topology,
|
||||||
nodeConfig.ExperimentalTopologyManagerPolicy,
|
nodeConfig.ExperimentalTopologyManagerPolicy,
|
||||||
nodeConfig.ExperimentalTopologyManagerScope,
|
nodeConfig.ExperimentalTopologyManagerScope,
|
||||||
|
nodeConfig.ExperimentalTopologyManagerPolicyOptions,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -162,7 +162,7 @@ func TestValidateStaticPolicyOptions(t *testing.T) {
|
|||||||
t.Run(testCase.description, func(t *testing.T) {
|
t.Run(testCase.description, func(t *testing.T) {
|
||||||
topoMgrPolicy := topologymanager.NewNonePolicy()
|
topoMgrPolicy := topologymanager.NewNonePolicy()
|
||||||
if testCase.topoMgrPolicy == topologymanager.PolicySingleNumaNode {
|
if testCase.topoMgrPolicy == topologymanager.PolicySingleNumaNode {
|
||||||
topoMgrPolicy = topologymanager.NewSingleNumaNodePolicy(nil)
|
topoMgrPolicy, _ = topologymanager.NewSingleNumaNodePolicy(&topologymanager.NUMAInfo{}, map[string]string{})
|
||||||
|
|
||||||
}
|
}
|
||||||
topoMgrStore := topologymanager.NewFakeManagerWithPolicy(topoMgrPolicy)
|
topoMgrStore := topologymanager.NewFakeManagerWithPolicy(topoMgrPolicy)
|
||||||
|
@ -35,6 +35,8 @@ type BitMask interface {
|
|||||||
IsSet(bit int) bool
|
IsSet(bit int) bool
|
||||||
AnySet(bits []int) bool
|
AnySet(bits []int) bool
|
||||||
IsNarrowerThan(mask BitMask) bool
|
IsNarrowerThan(mask BitMask) bool
|
||||||
|
IsLessThan(mask BitMask) bool
|
||||||
|
IsGreaterThan(mask BitMask) bool
|
||||||
String() string
|
String() string
|
||||||
Count() int
|
Count() int
|
||||||
GetBits() []int
|
GetBits() []int
|
||||||
@ -143,13 +145,21 @@ func (s *bitMask) IsEqual(mask BitMask) bool {
|
|||||||
// lower-numbered bits set wins out.
|
// lower-numbered bits set wins out.
|
||||||
func (s *bitMask) IsNarrowerThan(mask BitMask) bool {
|
func (s *bitMask) IsNarrowerThan(mask BitMask) bool {
|
||||||
if s.Count() == mask.Count() {
|
if s.Count() == mask.Count() {
|
||||||
if *s < *mask.(*bitMask) {
|
return s.IsLessThan(mask)
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return s.Count() < mask.Count()
|
return s.Count() < mask.Count()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsLessThan checks which bitmask has more lower-numbered bits set.
|
||||||
|
func (s *bitMask) IsLessThan(mask BitMask) bool {
|
||||||
|
return *s < *mask.(*bitMask)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsGreaterThan checks which bitmask has more higher-numbered bits set.
|
||||||
|
func (s *bitMask) IsGreaterThan(mask BitMask) bool {
|
||||||
|
return *s > *mask.(*bitMask)
|
||||||
|
}
|
||||||
|
|
||||||
// String converts mask to string
|
// String converts mask to string
|
||||||
func (s *bitMask) String() string {
|
func (s *bitMask) String() string {
|
||||||
grouping := 2
|
grouping := 2
|
||||||
|
@ -630,3 +630,75 @@ func TestIterateBitMasks(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIsLessThan(t *testing.T) {
|
||||||
|
tcases := []struct {
|
||||||
|
name string
|
||||||
|
firstMask []int
|
||||||
|
secondMask []int
|
||||||
|
expectedFirstLower bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Check which value is lower of masks with equal bits set 1/1",
|
||||||
|
firstMask: []int{0},
|
||||||
|
secondMask: []int{0},
|
||||||
|
expectedFirstLower: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Check which value is lower of masks with unequal bits set 2/1",
|
||||||
|
firstMask: []int{1},
|
||||||
|
secondMask: []int{0},
|
||||||
|
expectedFirstLower: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Check which value is lower of masks with unequal bits set 1/2",
|
||||||
|
firstMask: []int{0},
|
||||||
|
secondMask: []int{1},
|
||||||
|
expectedFirstLower: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tcases {
|
||||||
|
firstMask, _ := NewBitMask(tc.firstMask...)
|
||||||
|
secondMask, _ := NewBitMask(tc.secondMask...)
|
||||||
|
expectedFirstLower := firstMask.IsLessThan(secondMask)
|
||||||
|
if expectedFirstLower != tc.expectedFirstLower {
|
||||||
|
t.Errorf("Expected value to be %v, got %v", tc.expectedFirstLower, expectedFirstLower)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsGreaterThan(t *testing.T) {
|
||||||
|
tcases := []struct {
|
||||||
|
name string
|
||||||
|
firstMask []int
|
||||||
|
secondMask []int
|
||||||
|
expectedFirstGreater bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Check which value is greater of masks with equal bits set 1/1",
|
||||||
|
firstMask: []int{0},
|
||||||
|
secondMask: []int{0},
|
||||||
|
expectedFirstGreater: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Check which value is greater of masks with unequal bits set 2/1",
|
||||||
|
firstMask: []int{1},
|
||||||
|
secondMask: []int{0},
|
||||||
|
expectedFirstGreater: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Check which value is greater of masks with unequal bits set 1/2",
|
||||||
|
firstMask: []int{0},
|
||||||
|
secondMask: []int{1},
|
||||||
|
expectedFirstGreater: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tcases {
|
||||||
|
firstMask, _ := NewBitMask(tc.firstMask...)
|
||||||
|
secondMask, _ := NewBitMask(tc.secondMask...)
|
||||||
|
expectedFirstGreater := firstMask.IsGreaterThan(secondMask)
|
||||||
|
if expectedFirstGreater != tc.expectedFirstGreater {
|
||||||
|
t.Errorf("Expected value to be %v, got %v", tc.expectedFirstGreater, expectedFirstGreater)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
150
pkg/kubelet/cm/topologymanager/numa_info.go
Normal file
150
pkg/kubelet/cm/topologymanager/numa_info.go
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 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 topologymanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
cadvisorapi "github.com/google/cadvisor/info/v1"
|
||||||
|
"k8s.io/kubernetes/pkg/kubelet/cm/topologymanager/bitmask"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultNodeDir = "/sys/devices/system/node/"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NUMADistances [][]uint64
|
||||||
|
|
||||||
|
type NUMAInfo struct {
|
||||||
|
Nodes []int
|
||||||
|
NUMADistances NUMADistances
|
||||||
|
}
|
||||||
|
|
||||||
|
type NUMASysFs struct {
|
||||||
|
nodeDir string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNUMAInfo(topology []cadvisorapi.Node) (*NUMAInfo, error) {
|
||||||
|
return newNUMAInfo(topology, &NUMASysFs{nodeDir: defaultNodeDir})
|
||||||
|
}
|
||||||
|
|
||||||
|
func newNUMAInfo(topology []cadvisorapi.Node, sysFs *NUMASysFs) (*NUMAInfo, error) {
|
||||||
|
var numaNodes []int
|
||||||
|
distances := make([][]uint64, len(topology))
|
||||||
|
for _, node := range topology {
|
||||||
|
numaNodes = append(numaNodes, node.Id)
|
||||||
|
|
||||||
|
// Populate the NUMA distances
|
||||||
|
// For now we need to retrieve this information from sysfs.
|
||||||
|
// TODO: Update this as follows once a version of cadvisor with this commit is vendored in https://github.com/google/cadvisor/commit/24dd1de08a72cfee661f6178454db995900c0fee
|
||||||
|
// distances[node.Id] = node.Distances[:]
|
||||||
|
nodeDistance, err := sysFs.GetDistances(node.Id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error getting NUMA distances from sysfs: %w", err)
|
||||||
|
}
|
||||||
|
distances[node.Id] = nodeDistance
|
||||||
|
}
|
||||||
|
|
||||||
|
numaInfo := &NUMAInfo{
|
||||||
|
Nodes: numaNodes,
|
||||||
|
NUMADistances: distances,
|
||||||
|
}
|
||||||
|
|
||||||
|
return numaInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NUMAInfo) Narrowest(m1 bitmask.BitMask, m2 bitmask.BitMask) bitmask.BitMask {
|
||||||
|
if m1.IsNarrowerThan(m2) {
|
||||||
|
return m1
|
||||||
|
}
|
||||||
|
return m2
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *NUMAInfo) Closest(m1 bitmask.BitMask, m2 bitmask.BitMask) bitmask.BitMask {
|
||||||
|
// If the length of both bitmasks aren't the same, choose the one that is narrowest.
|
||||||
|
if m1.Count() != m2.Count() {
|
||||||
|
return n.Narrowest(m1, m2)
|
||||||
|
}
|
||||||
|
|
||||||
|
m1Distance := n.NUMADistances.CalculateAverageFor(m1)
|
||||||
|
m2Distance := n.NUMADistances.CalculateAverageFor(m2)
|
||||||
|
// If average distance is the same, take bitmask with more lower-number bits set.
|
||||||
|
if m1Distance == m2Distance {
|
||||||
|
if m1.IsLessThan(m2) {
|
||||||
|
return m1
|
||||||
|
}
|
||||||
|
return m2
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, return the bitmask with the shortest average distance between NUMA nodes.
|
||||||
|
if m1Distance < m2Distance {
|
||||||
|
return m1
|
||||||
|
}
|
||||||
|
|
||||||
|
return m2
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n NUMAInfo) DefaultAffinityMask() bitmask.BitMask {
|
||||||
|
defaultAffinity, _ := bitmask.NewBitMask(n.Nodes...)
|
||||||
|
return defaultAffinity
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d NUMADistances) CalculateAverageFor(bm bitmask.BitMask) float64 {
|
||||||
|
// This should never happen, but just in case make sure we do not divide by zero.
|
||||||
|
if bm.Count() == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var count float64 = 0
|
||||||
|
var sum float64 = 0
|
||||||
|
for _, node1 := range bm.GetBits() {
|
||||||
|
for _, node2 := range bm.GetBits() {
|
||||||
|
sum += float64(d[node1][node2])
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum / count
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s NUMASysFs) GetDistances(nodeId int) ([]uint64, error) {
|
||||||
|
distancePath := fmt.Sprintf("%s/node%d/distance", s.nodeDir, nodeId)
|
||||||
|
distance, err := ioutil.ReadFile(distancePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("problem reading %s: %w", distancePath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rawDistances := strings.TrimSpace(string(distance))
|
||||||
|
|
||||||
|
return splitDistances(rawDistances)
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitDistances(rawDistances string) ([]uint64, error) {
|
||||||
|
distances := []uint64{}
|
||||||
|
for _, distance := range strings.Split(rawDistances, " ") {
|
||||||
|
distanceUint, err := strconv.ParseUint(distance, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot convert %s to int", distance)
|
||||||
|
}
|
||||||
|
distances = append(distances, distanceUint)
|
||||||
|
}
|
||||||
|
|
||||||
|
return distances, nil
|
||||||
|
}
|
584
pkg/kubelet/cm/topologymanager/numa_info_test.go
Normal file
584
pkg/kubelet/cm/topologymanager/numa_info_test.go
Normal file
@ -0,0 +1,584 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 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 topologymanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
cadvisorapi "github.com/google/cadvisor/info/v1"
|
||||||
|
"k8s.io/kubernetes/pkg/kubelet/cm/topologymanager/bitmask"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNUMAInfo(t *testing.T) {
|
||||||
|
tcases := []struct {
|
||||||
|
name string
|
||||||
|
topology []cadvisorapi.Node
|
||||||
|
expectedNUMAInfo *NUMAInfo
|
||||||
|
expectedErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "positive test 1 node",
|
||||||
|
topology: []cadvisorapi.Node{
|
||||||
|
{
|
||||||
|
Id: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedNUMAInfo: &NUMAInfo{
|
||||||
|
Nodes: []int{0},
|
||||||
|
NUMADistances: NUMADistances{
|
||||||
|
{
|
||||||
|
10,
|
||||||
|
11,
|
||||||
|
12,
|
||||||
|
12,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "positive test 2 nodes",
|
||||||
|
topology: []cadvisorapi.Node{
|
||||||
|
{
|
||||||
|
Id: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedNUMAInfo: &NUMAInfo{
|
||||||
|
Nodes: []int{0, 1},
|
||||||
|
NUMADistances: NUMADistances{
|
||||||
|
{
|
||||||
|
10,
|
||||||
|
11,
|
||||||
|
12,
|
||||||
|
12,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
11,
|
||||||
|
10,
|
||||||
|
12,
|
||||||
|
12,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "positive test 3 nodes",
|
||||||
|
topology: []cadvisorapi.Node{
|
||||||
|
{
|
||||||
|
Id: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedNUMAInfo: &NUMAInfo{
|
||||||
|
Nodes: []int{0, 1, 2},
|
||||||
|
NUMADistances: NUMADistances{
|
||||||
|
{
|
||||||
|
10,
|
||||||
|
11,
|
||||||
|
12,
|
||||||
|
12,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
11,
|
||||||
|
10,
|
||||||
|
12,
|
||||||
|
12,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
12,
|
||||||
|
12,
|
||||||
|
10,
|
||||||
|
11,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "positive test 4 nodes",
|
||||||
|
topology: []cadvisorapi.Node{
|
||||||
|
{
|
||||||
|
Id: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Id: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedNUMAInfo: &NUMAInfo{
|
||||||
|
Nodes: []int{0, 1, 2, 3},
|
||||||
|
NUMADistances: NUMADistances{
|
||||||
|
{
|
||||||
|
10,
|
||||||
|
11,
|
||||||
|
12,
|
||||||
|
12,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
11,
|
||||||
|
10,
|
||||||
|
12,
|
||||||
|
12,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
12,
|
||||||
|
12,
|
||||||
|
10,
|
||||||
|
11,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
12,
|
||||||
|
12,
|
||||||
|
11,
|
||||||
|
10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "negative test 1 node",
|
||||||
|
topology: []cadvisorapi.Node{
|
||||||
|
{
|
||||||
|
Id: 9,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedNUMAInfo: nil,
|
||||||
|
expectedErr: fmt.Errorf("no such file or directory"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeDir, err := os.MkdirTemp("", "TestNUMAInfo")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to create temporary directory: %v", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(nodeDir)
|
||||||
|
|
||||||
|
numaDistances := map[int]string{
|
||||||
|
0: "10 11 12 12",
|
||||||
|
1: "11 10 12 12",
|
||||||
|
2: "12 12 10 11",
|
||||||
|
3: "12 12 11 10",
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, distances := range numaDistances {
|
||||||
|
numaDir := filepath.Join(nodeDir, fmt.Sprintf("node%d", i))
|
||||||
|
if err := os.Mkdir(numaDir, 0700); err != nil {
|
||||||
|
t.Fatalf("Unable to create numaDir %s: %v", numaDir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
distanceFile := filepath.Join(numaDir, "distance")
|
||||||
|
|
||||||
|
if err = os.WriteFile(distanceFile, []byte(distances), 0644); err != nil {
|
||||||
|
t.Fatalf("Unable to create test distanceFile: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stub sysFs to read from temp dir
|
||||||
|
sysFs := &NUMASysFs{nodeDir: nodeDir}
|
||||||
|
|
||||||
|
for _, tcase := range tcases {
|
||||||
|
topology, err := newNUMAInfo(tcase.topology, sysFs)
|
||||||
|
if tcase.expectedErr == nil && err != nil {
|
||||||
|
t.Fatalf("Expected err to equal nil, not %v", err)
|
||||||
|
} else if tcase.expectedErr != nil && err == nil {
|
||||||
|
t.Fatalf("Expected err to equal %v, not nil", tcase.expectedErr)
|
||||||
|
} else if tcase.expectedErr != nil {
|
||||||
|
if !strings.Contains(err.Error(), tcase.expectedErr.Error()) {
|
||||||
|
t.Errorf("Unexpected error message. Have: %s wants %s", err.Error(), tcase.expectedErr.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(topology, tcase.expectedNUMAInfo) {
|
||||||
|
t.Fatalf("Expected topology to equal %v, not %v", tcase.expectedNUMAInfo, topology)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCalculateAvgDistanceFor(t *testing.T) {
|
||||||
|
tcases := []struct {
|
||||||
|
name string
|
||||||
|
bm []int
|
||||||
|
distance [][]uint64
|
||||||
|
expectedAvg float64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "1 NUMA node",
|
||||||
|
bm: []int{
|
||||||
|
0,
|
||||||
|
},
|
||||||
|
distance: [][]uint64{
|
||||||
|
{
|
||||||
|
10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedAvg: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "2 NUMA node, 1 set in bitmask",
|
||||||
|
bm: []int{
|
||||||
|
0,
|
||||||
|
},
|
||||||
|
distance: [][]uint64{
|
||||||
|
{
|
||||||
|
10,
|
||||||
|
11,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
11,
|
||||||
|
10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedAvg: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "2 NUMA node, 2 set in bitmask",
|
||||||
|
bm: []int{
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
},
|
||||||
|
distance: [][]uint64{
|
||||||
|
{
|
||||||
|
10,
|
||||||
|
11,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
11,
|
||||||
|
10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedAvg: 10.5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "4 NUMA node, 2 set in bitmask",
|
||||||
|
bm: []int{
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
},
|
||||||
|
distance: [][]uint64{
|
||||||
|
{
|
||||||
|
10,
|
||||||
|
11,
|
||||||
|
12,
|
||||||
|
12,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
11,
|
||||||
|
10,
|
||||||
|
12,
|
||||||
|
12,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
12,
|
||||||
|
12,
|
||||||
|
10,
|
||||||
|
11,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
12,
|
||||||
|
12,
|
||||||
|
11,
|
||||||
|
10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedAvg: 11,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "4 NUMA node, 3 set in bitmask",
|
||||||
|
bm: []int{
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
3,
|
||||||
|
},
|
||||||
|
distance: [][]uint64{
|
||||||
|
{
|
||||||
|
10,
|
||||||
|
11,
|
||||||
|
12,
|
||||||
|
12,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
11,
|
||||||
|
10,
|
||||||
|
12,
|
||||||
|
12,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
12,
|
||||||
|
12,
|
||||||
|
10,
|
||||||
|
11,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
12,
|
||||||
|
12,
|
||||||
|
11,
|
||||||
|
10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedAvg: 11.11111111111111,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "0 NUMA node, 0 set in bitmask",
|
||||||
|
bm: []int{},
|
||||||
|
distance: [][]uint64{},
|
||||||
|
expectedAvg: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tcase := range tcases {
|
||||||
|
bm, err := bitmask.NewBitMask(tcase.bm...)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("no error expected got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
numaInfo := NUMAInfo{
|
||||||
|
Nodes: tcase.bm,
|
||||||
|
NUMADistances: tcase.distance,
|
||||||
|
}
|
||||||
|
|
||||||
|
result := numaInfo.NUMADistances.CalculateAverageFor(bm)
|
||||||
|
if result != tcase.expectedAvg {
|
||||||
|
t.Errorf("Expected result to equal %g, not %g", tcase.expectedAvg, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetDistances(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
expectedErr bool
|
||||||
|
expectedDistances []uint64
|
||||||
|
nodeId int
|
||||||
|
nodeExists bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "reading proper distance file",
|
||||||
|
expectedErr: false,
|
||||||
|
expectedDistances: []uint64{10, 11, 12, 13},
|
||||||
|
nodeId: 0,
|
||||||
|
nodeExists: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no distance file",
|
||||||
|
expectedErr: true,
|
||||||
|
expectedDistances: nil,
|
||||||
|
nodeId: 99,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tcase := range testCases {
|
||||||
|
t.Run(tcase.name, func(t *testing.T) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
nodeDir, err := os.MkdirTemp("", "TestGetDistances")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to create temporary directory: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer os.RemoveAll(nodeDir)
|
||||||
|
|
||||||
|
if tcase.nodeExists {
|
||||||
|
numaDir := filepath.Join(nodeDir, fmt.Sprintf("node%d", tcase.nodeId))
|
||||||
|
if err := os.Mkdir(numaDir, 0700); err != nil {
|
||||||
|
t.Fatalf("Unable to create numaDir %s: %v", numaDir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
distanceFile := filepath.Join(numaDir, "distance")
|
||||||
|
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
for i, distance := range tcase.expectedDistances {
|
||||||
|
buffer.WriteString(strconv.Itoa(int(distance)))
|
||||||
|
if i != len(tcase.expectedDistances)-1 {
|
||||||
|
buffer.WriteString(" ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = os.WriteFile(distanceFile, buffer.Bytes(), 0644); err != nil {
|
||||||
|
t.Fatalf("Unable to create test distanceFile: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sysFs := &NUMASysFs{nodeDir: nodeDir}
|
||||||
|
|
||||||
|
distances, err := sysFs.GetDistances(tcase.nodeId)
|
||||||
|
if !tcase.expectedErr && err != nil {
|
||||||
|
t.Fatalf("Expected err to equal nil, not %v", err)
|
||||||
|
} else if tcase.expectedErr && err == nil {
|
||||||
|
t.Fatalf("Expected err to equal %v, not nil", tcase.expectedErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tcase.expectedErr && !reflect.DeepEqual(distances, tcase.expectedDistances) {
|
||||||
|
t.Fatalf("Expected distances to equal %v, not %v", tcase.expectedDistances, distances)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSplitDistances(t *testing.T) {
|
||||||
|
tcases := []struct {
|
||||||
|
description string
|
||||||
|
rawDistances string
|
||||||
|
expected []uint64
|
||||||
|
expectedErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
description: "read one distance",
|
||||||
|
rawDistances: "10",
|
||||||
|
expected: []uint64{10},
|
||||||
|
expectedErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "read two distances",
|
||||||
|
rawDistances: "10 20",
|
||||||
|
expected: []uint64{10, 20},
|
||||||
|
expectedErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "can't convert negative number to uint64",
|
||||||
|
rawDistances: "10 -20",
|
||||||
|
expected: nil,
|
||||||
|
expectedErr: fmt.Errorf("cannot conver"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tcases {
|
||||||
|
result, err := splitDistances(tc.rawDistances)
|
||||||
|
|
||||||
|
if tc.expectedErr == nil && err != nil {
|
||||||
|
t.Fatalf("Expected err to equal nil, not %v", err)
|
||||||
|
} else if tc.expectedErr != nil && err == nil {
|
||||||
|
t.Fatalf("Expected err to equal %v, not nil", tc.expectedErr)
|
||||||
|
} else if tc.expectedErr != nil {
|
||||||
|
if !strings.Contains(err.Error(), tc.expectedErr.Error()) {
|
||||||
|
t.Errorf("Unexpected error message. Have: %s wants %s", err.Error(), tc.expectedErr.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(tc.expected, result) {
|
||||||
|
t.Fatalf("Expected distances to equal: %v, got: %v", tc.expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClosest(t *testing.T) {
|
||||||
|
tcases := []struct {
|
||||||
|
description string
|
||||||
|
current bitmask.BitMask
|
||||||
|
candidate bitmask.BitMask
|
||||||
|
expected string
|
||||||
|
numaInfo *NUMAInfo
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
description: "current and candidate length is not the same, current narrower",
|
||||||
|
current: NewTestBitMask(0),
|
||||||
|
candidate: NewTestBitMask(0, 2),
|
||||||
|
expected: "current",
|
||||||
|
numaInfo: &NUMAInfo{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "current and candidate length is the same, distance is the same, current more lower bits set",
|
||||||
|
current: NewTestBitMask(0, 1),
|
||||||
|
candidate: NewTestBitMask(0, 2),
|
||||||
|
expected: "current",
|
||||||
|
numaInfo: &NUMAInfo{
|
||||||
|
NUMADistances: [][]uint64{
|
||||||
|
{10, 10, 10},
|
||||||
|
{10, 10, 10},
|
||||||
|
{10, 10, 10},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "current and candidate length is the same, distance is the same, candidate more lower bits set",
|
||||||
|
current: NewTestBitMask(0, 3),
|
||||||
|
candidate: NewTestBitMask(0, 2),
|
||||||
|
expected: "candidate",
|
||||||
|
numaInfo: &NUMAInfo{
|
||||||
|
NUMADistances: [][]uint64{
|
||||||
|
{10, 10, 10, 10},
|
||||||
|
{10, 10, 10, 10},
|
||||||
|
{10, 10, 10, 10},
|
||||||
|
{10, 10, 10, 10},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "current and candidate length is the same, candidate average distance is smaller",
|
||||||
|
current: NewTestBitMask(0, 3),
|
||||||
|
candidate: NewTestBitMask(0, 1),
|
||||||
|
expected: "candidate",
|
||||||
|
numaInfo: &NUMAInfo{
|
||||||
|
NUMADistances: [][]uint64{
|
||||||
|
{10, 11, 12, 12},
|
||||||
|
{11, 10, 12, 12},
|
||||||
|
{12, 12, 10, 11},
|
||||||
|
{12, 12, 11, 10},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "current and candidate length is the same, current average distance is smaller",
|
||||||
|
current: NewTestBitMask(2, 3),
|
||||||
|
candidate: NewTestBitMask(0, 3),
|
||||||
|
expected: "current",
|
||||||
|
numaInfo: &NUMAInfo{
|
||||||
|
NUMADistances: [][]uint64{
|
||||||
|
{10, 11, 12, 12},
|
||||||
|
{11, 10, 12, 12},
|
||||||
|
{12, 12, 10, 11},
|
||||||
|
{12, 12, 11, 10},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tcases {
|
||||||
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
|
|
||||||
|
result := tc.numaInfo.Closest(tc.candidate, tc.current)
|
||||||
|
if result != tc.current && result != tc.candidate {
|
||||||
|
t.Errorf("Expected result to be either 'current' or 'candidate' hint")
|
||||||
|
}
|
||||||
|
if tc.expected == "current" && result != tc.current {
|
||||||
|
t.Errorf("Expected result to be %v, got %v", tc.current, result)
|
||||||
|
}
|
||||||
|
if tc.expected == "candidate" && result != tc.candidate {
|
||||||
|
t.Errorf("Expected result to be %v, got %v", tc.candidate, result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -33,11 +33,10 @@ type Policy interface {
|
|||||||
// Merge a TopologyHints permutation to a single hint by performing a bitwise-AND
|
// Merge a TopologyHints permutation to a single hint by performing a bitwise-AND
|
||||||
// of their affinity masks. The hint shall be preferred if all hits in the permutation
|
// of their affinity masks. The hint shall be preferred if all hits in the permutation
|
||||||
// are preferred.
|
// are preferred.
|
||||||
func mergePermutation(numaNodes []int, permutation []TopologyHint) TopologyHint {
|
func mergePermutation(defaultAffinity bitmask.BitMask, permutation []TopologyHint) TopologyHint {
|
||||||
// Get the NUMANodeAffinity from each hint in the permutation and see if any
|
// Get the NUMANodeAffinity from each hint in the permutation and see if any
|
||||||
// of them encode unpreferred allocations.
|
// of them encode unpreferred allocations.
|
||||||
preferred := true
|
preferred := true
|
||||||
defaultAffinity, _ := bitmask.NewBitMask(numaNodes...)
|
|
||||||
var numaAffinities []bitmask.BitMask
|
var numaAffinities []bitmask.BitMask
|
||||||
for _, hint := range permutation {
|
for _, hint := range permutation {
|
||||||
// Only consider hints that have an actual NUMANodeAffinity set.
|
// Only consider hints that have an actual NUMANodeAffinity set.
|
||||||
@ -127,7 +126,50 @@ func maxOfMinAffinityCounts(filteredHints [][]TopologyHint) int {
|
|||||||
return maxOfMinCount
|
return maxOfMinCount
|
||||||
}
|
}
|
||||||
|
|
||||||
func compareHints(bestNonPreferredAffinityCount int, current *TopologyHint, candidate *TopologyHint) *TopologyHint {
|
type HintMerger struct {
|
||||||
|
NUMAInfo *NUMAInfo
|
||||||
|
Hints [][]TopologyHint
|
||||||
|
// Set bestNonPreferredAffinityCount to help decide which affinity mask is
|
||||||
|
// preferred amongst all non-preferred hints. We calculate this value as
|
||||||
|
// the maximum of the minimum affinity counts supplied for any given hint
|
||||||
|
// provider. In other words, prefer a hint that has an affinity mask that
|
||||||
|
// includes all of the NUMA nodes from the provider that requires the most
|
||||||
|
// NUMA nodes to satisfy its allocation.
|
||||||
|
BestNonPreferredAffinityCount int
|
||||||
|
CompareNUMAAffinityMasks func(candidate *TopologyHint, current *TopologyHint) (best *TopologyHint)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHintMerger(numaInfo *NUMAInfo, hints [][]TopologyHint, policyName string, opts PolicyOptions) HintMerger {
|
||||||
|
compareNumaAffinityMasks := func(current, candidate *TopologyHint) *TopologyHint {
|
||||||
|
// If current and candidate bitmasks are the same, prefer current hint.
|
||||||
|
if candidate.NUMANodeAffinity.IsEqual(current.NUMANodeAffinity) {
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise compare the hints, based on the policy options provided
|
||||||
|
var best bitmask.BitMask
|
||||||
|
if (policyName != PolicySingleNumaNode) && opts.PreferClosestNUMA {
|
||||||
|
best = numaInfo.Closest(current.NUMANodeAffinity, candidate.NUMANodeAffinity)
|
||||||
|
} else {
|
||||||
|
best = numaInfo.Narrowest(current.NUMANodeAffinity, candidate.NUMANodeAffinity)
|
||||||
|
}
|
||||||
|
if best.IsEqual(current.NUMANodeAffinity) {
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
return candidate
|
||||||
|
}
|
||||||
|
|
||||||
|
merger := HintMerger{
|
||||||
|
NUMAInfo: numaInfo,
|
||||||
|
Hints: hints,
|
||||||
|
BestNonPreferredAffinityCount: maxOfMinAffinityCounts(hints),
|
||||||
|
CompareNUMAAffinityMasks: compareNumaAffinityMasks,
|
||||||
|
}
|
||||||
|
|
||||||
|
return merger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m HintMerger) compare(current *TopologyHint, candidate *TopologyHint) *TopologyHint {
|
||||||
// Only consider candidates that result in a NUMANodeAffinity > 0 to
|
// Only consider candidates that result in a NUMANodeAffinity > 0 to
|
||||||
// replace the current bestHint.
|
// replace the current bestHint.
|
||||||
if candidate.NUMANodeAffinity.Count() == 0 {
|
if candidate.NUMANodeAffinity.Count() == 0 {
|
||||||
@ -146,20 +188,18 @@ func compareHints(bestNonPreferredAffinityCount int, current *TopologyHint, cand
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the current bestHint is preferred and the candidate hint is
|
// If the current bestHint is preferred and the candidate hint is
|
||||||
// non-preferred, never update the bestHint, regardless of the
|
// non-preferred, never update the bestHint, regardless of how
|
||||||
// candidate hint's narowness.
|
// the candidate hint's affinity mask compares to the current
|
||||||
|
// hint's affinity mask.
|
||||||
if current.Preferred && !candidate.Preferred {
|
if current.Preferred && !candidate.Preferred {
|
||||||
return current
|
return current
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the current bestHint and the candidate hint are both preferred,
|
// If the current bestHint and the candidate hint are both preferred,
|
||||||
// then only consider candidate hints that have a narrower
|
// then only consider fitter NUMANodeAffinity
|
||||||
// NUMANodeAffinity than the NUMANodeAffinity in the current bestHint.
|
|
||||||
if current.Preferred && candidate.Preferred {
|
if current.Preferred && candidate.Preferred {
|
||||||
if candidate.NUMANodeAffinity.IsNarrowerThan(current.NUMANodeAffinity) {
|
return m.CompareNUMAAffinityMasks(current, candidate)
|
||||||
return candidate
|
|
||||||
}
|
|
||||||
return current
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The only case left is if the current best bestHint and the candidate
|
// The only case left is if the current best bestHint and the candidate
|
||||||
@ -173,13 +213,13 @@ func compareHints(bestNonPreferredAffinityCount int, current *TopologyHint, cand
|
|||||||
// 3. current.NUMANodeAffinity.Count() < bestNonPreferredAffinityCount
|
// 3. current.NUMANodeAffinity.Count() < bestNonPreferredAffinityCount
|
||||||
//
|
//
|
||||||
// For case (1), the current bestHint is larger than the
|
// For case (1), the current bestHint is larger than the
|
||||||
// bestNonPreferredAffinityCount, so updating to any narrower mergeHint
|
// bestNonPreferredAffinityCount, so updating to fitter mergeHint
|
||||||
// is preferred over staying where we are.
|
// is preferred over staying where we are.
|
||||||
//
|
//
|
||||||
// For case (2), the current bestHint is equal to the
|
// For case (2), the current bestHint is equal to the
|
||||||
// bestNonPreferredAffinityCount, so we would like to stick with what
|
// bestNonPreferredAffinityCount, so we would like to stick with what
|
||||||
// we have *unless* the candidate hint is also equal to
|
// we have *unless* the candidate hint is also equal to
|
||||||
// bestNonPreferredAffinityCount and it is narrower.
|
// bestNonPreferredAffinityCount and it is fitter.
|
||||||
//
|
//
|
||||||
// For case (3), the current bestHint is less than
|
// For case (3), the current bestHint is less than
|
||||||
// bestNonPreferredAffinityCount, so we would like to creep back up to
|
// bestNonPreferredAffinityCount, so we would like to creep back up to
|
||||||
@ -216,33 +256,28 @@ func compareHints(bestNonPreferredAffinityCount int, current *TopologyHint, cand
|
|||||||
// the bestNonPreferredAffinityCount.
|
// the bestNonPreferredAffinityCount.
|
||||||
//
|
//
|
||||||
// Finally, for case (3cc), we know that the current bestHint and the
|
// Finally, for case (3cc), we know that the current bestHint and the
|
||||||
// candidate hint are equal, so we simply choose the narrower of the 2.
|
// candidate hint are equal, so we simply choose the fitter of the 2.
|
||||||
|
|
||||||
// Case 1
|
// Case 1
|
||||||
if current.NUMANodeAffinity.Count() > bestNonPreferredAffinityCount {
|
if current.NUMANodeAffinity.Count() > m.BestNonPreferredAffinityCount {
|
||||||
if candidate.NUMANodeAffinity.IsNarrowerThan(current.NUMANodeAffinity) {
|
return m.CompareNUMAAffinityMasks(current, candidate)
|
||||||
return candidate
|
|
||||||
}
|
|
||||||
return current
|
|
||||||
}
|
}
|
||||||
// Case 2
|
// Case 2
|
||||||
if current.NUMANodeAffinity.Count() == bestNonPreferredAffinityCount {
|
if current.NUMANodeAffinity.Count() == m.BestNonPreferredAffinityCount {
|
||||||
if candidate.NUMANodeAffinity.Count() != bestNonPreferredAffinityCount {
|
if candidate.NUMANodeAffinity.Count() != m.BestNonPreferredAffinityCount {
|
||||||
return current
|
return current
|
||||||
}
|
}
|
||||||
if candidate.NUMANodeAffinity.IsNarrowerThan(current.NUMANodeAffinity) {
|
return m.CompareNUMAAffinityMasks(current, candidate)
|
||||||
return candidate
|
|
||||||
}
|
|
||||||
return current
|
|
||||||
}
|
}
|
||||||
// Case 3a
|
// Case 3a
|
||||||
if candidate.NUMANodeAffinity.Count() > bestNonPreferredAffinityCount {
|
if candidate.NUMANodeAffinity.Count() > m.BestNonPreferredAffinityCount {
|
||||||
return current
|
return current
|
||||||
}
|
}
|
||||||
// Case 3b
|
// Case 3b
|
||||||
if candidate.NUMANodeAffinity.Count() == bestNonPreferredAffinityCount {
|
if candidate.NUMANodeAffinity.Count() == m.BestNonPreferredAffinityCount {
|
||||||
return candidate
|
return candidate
|
||||||
}
|
}
|
||||||
|
|
||||||
// Case 3ca
|
// Case 3ca
|
||||||
if candidate.NUMANodeAffinity.Count() > current.NUMANodeAffinity.Count() {
|
if candidate.NUMANodeAffinity.Count() > current.NUMANodeAffinity.Count() {
|
||||||
return candidate
|
return candidate
|
||||||
@ -251,35 +286,27 @@ func compareHints(bestNonPreferredAffinityCount int, current *TopologyHint, cand
|
|||||||
if candidate.NUMANodeAffinity.Count() < current.NUMANodeAffinity.Count() {
|
if candidate.NUMANodeAffinity.Count() < current.NUMANodeAffinity.Count() {
|
||||||
return current
|
return current
|
||||||
}
|
}
|
||||||
|
|
||||||
// Case 3cc
|
// Case 3cc
|
||||||
if candidate.NUMANodeAffinity.IsNarrowerThan(current.NUMANodeAffinity) {
|
return m.CompareNUMAAffinityMasks(current, candidate)
|
||||||
return candidate
|
|
||||||
}
|
|
||||||
return current
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func mergeFilteredHints(numaNodes []int, filteredHints [][]TopologyHint) TopologyHint {
|
func (m HintMerger) Merge() TopologyHint {
|
||||||
// Set bestNonPreferredAffinityCount to help decide which affinity mask is
|
defaultAffinity := m.NUMAInfo.DefaultAffinityMask()
|
||||||
// preferred amongst all non-preferred hints. We calculate this value as
|
|
||||||
// the maximum of the minimum affinity counts supplied for any given hint
|
|
||||||
// provider. In other words, prefer a hint that has an affinity mask that
|
|
||||||
// includes all of the NUMA nodes from the provider that requires the most
|
|
||||||
// NUMA nodes to satisfy its allocation.
|
|
||||||
bestNonPreferredAffinityCount := maxOfMinAffinityCounts(filteredHints)
|
|
||||||
|
|
||||||
var bestHint *TopologyHint
|
var bestHint *TopologyHint
|
||||||
iterateAllProviderTopologyHints(filteredHints, func(permutation []TopologyHint) {
|
iterateAllProviderTopologyHints(m.Hints, func(permutation []TopologyHint) {
|
||||||
// Get the NUMANodeAffinity from each hint in the permutation and see if any
|
// Get the NUMANodeAffinity from each hint in the permutation and see if any
|
||||||
// of them encode unpreferred allocations.
|
// of them encode unpreferred allocations.
|
||||||
mergedHint := mergePermutation(numaNodes, permutation)
|
mergedHint := mergePermutation(defaultAffinity, permutation)
|
||||||
|
|
||||||
// Compare the current bestHint with the candidate mergedHint and
|
// Compare the current bestHint with the candidate mergedHint and
|
||||||
// update bestHint if appropriate.
|
// update bestHint if appropriate.
|
||||||
bestHint = compareHints(bestNonPreferredAffinityCount, bestHint, &mergedHint)
|
bestHint = m.compare(bestHint, &mergedHint)
|
||||||
})
|
})
|
||||||
|
|
||||||
if bestHint == nil {
|
if bestHint == nil {
|
||||||
defaultAffinity, _ := bitmask.NewBitMask(numaNodes...)
|
|
||||||
bestHint = &TopologyHint{defaultAffinity, false}
|
bestHint = &TopologyHint{defaultAffinity, false}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,8 +17,9 @@ limitations under the License.
|
|||||||
package topologymanager
|
package topologymanager
|
||||||
|
|
||||||
type bestEffortPolicy struct {
|
type bestEffortPolicy struct {
|
||||||
//List of NUMA Nodes available on the underlying machine
|
// numaInfo represents list of NUMA Nodes available on the underlying machine and distances between them
|
||||||
numaNodes []int
|
numaInfo *NUMAInfo
|
||||||
|
opts PolicyOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Policy = &bestEffortPolicy{}
|
var _ Policy = &bestEffortPolicy{}
|
||||||
@ -27,8 +28,13 @@ var _ Policy = &bestEffortPolicy{}
|
|||||||
const PolicyBestEffort string = "best-effort"
|
const PolicyBestEffort string = "best-effort"
|
||||||
|
|
||||||
// NewBestEffortPolicy returns best-effort policy.
|
// NewBestEffortPolicy returns best-effort policy.
|
||||||
func NewBestEffortPolicy(numaNodes []int) Policy {
|
func NewBestEffortPolicy(numaInfo *NUMAInfo, topologyPolicyOptions map[string]string) (Policy, error) {
|
||||||
return &bestEffortPolicy{numaNodes: numaNodes}
|
opts, err := NewPolicyOptions(topologyPolicyOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &bestEffortPolicy{numaInfo: numaInfo, opts: opts}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *bestEffortPolicy) Name() string {
|
func (p *bestEffortPolicy) Name() string {
|
||||||
@ -40,8 +46,9 @@ func (p *bestEffortPolicy) canAdmitPodResult(hint *TopologyHint) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *bestEffortPolicy) Merge(providersHints []map[string][]TopologyHint) (TopologyHint, bool) {
|
func (p *bestEffortPolicy) Merge(providersHints []map[string][]TopologyHint) (TopologyHint, bool) {
|
||||||
filteredProvidersHints := filterProvidersHints(providersHints)
|
filteredHints := filterProvidersHints(providersHints)
|
||||||
bestHint := mergeFilteredHints(p.numaNodes, filteredProvidersHints)
|
merger := NewHintMerger(p.numaInfo, filteredHints, p.Name(), p.opts)
|
||||||
|
bestHint := merger.Merge()
|
||||||
admit := p.canAdmitPodResult(&bestHint)
|
admit := p.canAdmitPodResult(&bestHint)
|
||||||
return bestHint, admit
|
return bestHint, admit
|
||||||
}
|
}
|
||||||
|
@ -39,9 +39,9 @@ func TestPolicyBestEffortCanAdmitPodResult(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tcases {
|
for _, tc := range tcases {
|
||||||
numaNodes := []int{0, 1}
|
numaInfo := commonNUMAInfoTwoNodes()
|
||||||
policy := NewBestEffortPolicy(numaNodes)
|
policy := &bestEffortPolicy{numaInfo: numaInfo}
|
||||||
result := policy.(*bestEffortPolicy).canAdmitPodResult(&tc.hint)
|
result := policy.canAdmitPodResult(&tc.hint)
|
||||||
|
|
||||||
if result != tc.expected {
|
if result != tc.expected {
|
||||||
t.Errorf("Expected result to be %t, got %t", tc.expected, result)
|
t.Errorf("Expected result to be %t, got %t", tc.expected, result)
|
||||||
@ -50,11 +50,26 @@ func TestPolicyBestEffortCanAdmitPodResult(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPolicyBestEffortMerge(t *testing.T) {
|
func TestPolicyBestEffortMerge(t *testing.T) {
|
||||||
numaNodes := []int{0, 1, 2, 3}
|
numaInfo := commonNUMAInfoFourNodes()
|
||||||
policy := NewBestEffortPolicy(numaNodes)
|
policy := &bestEffortPolicy{numaInfo: numaInfo}
|
||||||
|
|
||||||
tcases := commonPolicyMergeTestCases(numaNodes)
|
tcases := commonPolicyMergeTestCases(numaInfo.Nodes)
|
||||||
tcases = append(tcases, policy.(*bestEffortPolicy).mergeTestCases(numaNodes)...)
|
tcases = append(tcases, policy.mergeTestCases(numaInfo.Nodes)...)
|
||||||
|
tcases = append(tcases, policy.mergeTestCasesNoPolicies(numaInfo.Nodes)...)
|
||||||
|
|
||||||
|
testPolicyMerge(policy, tcases, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPolicyBestEffortMergeClosestNUMA(t *testing.T) {
|
||||||
|
numaInfo := commonNUMAInfoEightNodes()
|
||||||
|
opts := PolicyOptions{
|
||||||
|
PreferClosestNUMA: true,
|
||||||
|
}
|
||||||
|
policy := &bestEffortPolicy{numaInfo: numaInfo, opts: opts}
|
||||||
|
|
||||||
|
tcases := commonPolicyMergeTestCases(numaInfo.Nodes)
|
||||||
|
tcases = append(tcases, policy.mergeTestCases(numaInfo.Nodes)...)
|
||||||
|
tcases = append(tcases, policy.mergeTestCasesClosestNUMA(numaInfo.Nodes)...)
|
||||||
|
|
||||||
testPolicyMerge(policy, tcases, t)
|
testPolicyMerge(policy, tcases, t)
|
||||||
}
|
}
|
||||||
|
81
pkg/kubelet/cm/topologymanager/policy_options.go
Normal file
81
pkg/kubelet/cm/topologymanager/policy_options.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 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 topologymanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
|
kubefeatures "k8s.io/kubernetes/pkg/features"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PreferClosestNUMANodes string = "prefer-closest-numa-nodes"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
alphaOptions = sets.NewString(
|
||||||
|
PreferClosestNUMANodes,
|
||||||
|
)
|
||||||
|
betaOptions = sets.NewString()
|
||||||
|
stableOptions = sets.NewString()
|
||||||
|
)
|
||||||
|
|
||||||
|
func CheckPolicyOptionAvailable(option string) error {
|
||||||
|
if !alphaOptions.Has(option) && !betaOptions.Has(option) && !stableOptions.Has(option) {
|
||||||
|
return fmt.Errorf("unknown Topology Manager Policy option: %q", option)
|
||||||
|
}
|
||||||
|
|
||||||
|
if alphaOptions.Has(option) && !utilfeature.DefaultFeatureGate.Enabled(kubefeatures.TopologyManagerPolicyAlphaOptions) {
|
||||||
|
return fmt.Errorf("Topology Manager Policy Alpha-level Options not enabled, but option %q provided", option)
|
||||||
|
}
|
||||||
|
|
||||||
|
if betaOptions.Has(option) && !utilfeature.DefaultFeatureGate.Enabled(kubefeatures.TopologyManagerPolicyBetaOptions) {
|
||||||
|
return fmt.Errorf("Topology Manager Policy Beta-level Options not enabled, but option %q provided", option)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type PolicyOptions struct {
|
||||||
|
PreferClosestNUMA bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPolicyOptions(policyOptions map[string]string) (PolicyOptions, error) {
|
||||||
|
opts := PolicyOptions{}
|
||||||
|
for name, value := range policyOptions {
|
||||||
|
if err := CheckPolicyOptionAvailable(name); err != nil {
|
||||||
|
return opts, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch name {
|
||||||
|
case PreferClosestNUMANodes:
|
||||||
|
optValue, err := strconv.ParseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return opts, fmt.Errorf("bad value for option %q: %w", name, err)
|
||||||
|
}
|
||||||
|
opts.PreferClosestNUMA = optValue
|
||||||
|
default:
|
||||||
|
// this should never be reached, we already detect unknown options,
|
||||||
|
// but we keep it as further safety.
|
||||||
|
return opts, fmt.Errorf("unsupported topologymanager option: %q (%s)", name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return opts, nil
|
||||||
|
}
|
166
pkg/kubelet/cm/topologymanager/policy_options_test.go
Normal file
166
pkg/kubelet/cm/topologymanager/policy_options_test.go
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 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 topologymanager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
|
"k8s.io/component-base/featuregate"
|
||||||
|
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||||
|
pkgfeatures "k8s.io/kubernetes/pkg/features"
|
||||||
|
)
|
||||||
|
|
||||||
|
var fancyBetaOption = "fancy-new-option"
|
||||||
|
|
||||||
|
type optionAvailTest struct {
|
||||||
|
option string
|
||||||
|
featureGate featuregate.Feature
|
||||||
|
featureGateEnable bool
|
||||||
|
expectedAvailable bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewTopologyManagerOptions(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
description string
|
||||||
|
policyOptions map[string]string
|
||||||
|
featureGate featuregate.Feature
|
||||||
|
expectedErr error
|
||||||
|
expectedOptions PolicyOptions
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
description: "return TopologyManagerOptions with PreferClosestNUMA set to true",
|
||||||
|
featureGate: pkgfeatures.TopologyManagerPolicyAlphaOptions,
|
||||||
|
expectedOptions: PolicyOptions{
|
||||||
|
PreferClosestNUMA: true,
|
||||||
|
},
|
||||||
|
policyOptions: map[string]string{
|
||||||
|
PreferClosestNUMANodes: "true",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "return empty TopologyManagerOptions",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "fail to parse options",
|
||||||
|
featureGate: pkgfeatures.TopologyManagerPolicyAlphaOptions,
|
||||||
|
policyOptions: map[string]string{
|
||||||
|
PreferClosestNUMANodes: "not a boolean",
|
||||||
|
},
|
||||||
|
expectedErr: fmt.Errorf("bad value for option"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "test beta options success",
|
||||||
|
featureGate: pkgfeatures.TopologyManagerPolicyBetaOptions,
|
||||||
|
policyOptions: map[string]string{
|
||||||
|
fancyBetaOption: "true",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "test beta options success",
|
||||||
|
policyOptions: map[string]string{
|
||||||
|
fancyBetaOption: "true",
|
||||||
|
},
|
||||||
|
expectedErr: fmt.Errorf("Topology Manager Policy Beta-level Options not enabled,"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
betaOptions = sets.NewString(fancyBetaOption)
|
||||||
|
|
||||||
|
for _, tcase := range testCases {
|
||||||
|
t.Run(tcase.description, func(t *testing.T) {
|
||||||
|
if tcase.featureGate != "" {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, tcase.featureGate, true)()
|
||||||
|
}
|
||||||
|
opts, err := NewPolicyOptions(tcase.policyOptions)
|
||||||
|
if tcase.expectedErr != nil {
|
||||||
|
if !strings.Contains(err.Error(), tcase.expectedErr.Error()) {
|
||||||
|
t.Errorf("Unexpected error message. Have: %s wants %s", err.Error(), tcase.expectedErr.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts != tcase.expectedOptions {
|
||||||
|
t.Errorf("Expected TopologyManagerOptions to equal %v, not %v", tcase.expectedOptions, opts)
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPolicyDefaultsAvailable(t *testing.T) {
|
||||||
|
testCases := []optionAvailTest{
|
||||||
|
{
|
||||||
|
option: "this-option-does-not-exist",
|
||||||
|
expectedAvailable: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
option: PreferClosestNUMANodes,
|
||||||
|
expectedAvailable: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.option, func(t *testing.T) {
|
||||||
|
err := CheckPolicyOptionAvailable(testCase.option)
|
||||||
|
isEnabled := (err == nil)
|
||||||
|
if isEnabled != testCase.expectedAvailable {
|
||||||
|
t.Errorf("option %q available got=%v expected=%v", testCase.option, isEnabled, testCase.expectedAvailable)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPolicyOptionsAvailable(t *testing.T) {
|
||||||
|
testCases := []optionAvailTest{
|
||||||
|
{
|
||||||
|
option: "this-option-does-not-exist",
|
||||||
|
featureGate: pkgfeatures.TopologyManagerPolicyBetaOptions,
|
||||||
|
featureGateEnable: false,
|
||||||
|
expectedAvailable: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
option: "this-option-does-not-exist",
|
||||||
|
featureGate: pkgfeatures.TopologyManagerPolicyBetaOptions,
|
||||||
|
featureGateEnable: true,
|
||||||
|
expectedAvailable: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
option: PreferClosestNUMANodes,
|
||||||
|
featureGate: pkgfeatures.TopologyManagerPolicyAlphaOptions,
|
||||||
|
featureGateEnable: true,
|
||||||
|
expectedAvailable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
option: PreferClosestNUMANodes,
|
||||||
|
featureGate: pkgfeatures.TopologyManagerPolicyBetaOptions,
|
||||||
|
featureGateEnable: true,
|
||||||
|
expectedAvailable: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
t.Run(testCase.option, func(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, testCase.featureGate, testCase.featureGateEnable)()
|
||||||
|
err := CheckPolicyOptionAvailable(testCase.option)
|
||||||
|
isEnabled := (err == nil)
|
||||||
|
if isEnabled != testCase.expectedAvailable {
|
||||||
|
t.Errorf("option %q available got=%v expected=%v", testCase.option, isEnabled, testCase.expectedAvailable)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -26,8 +26,13 @@ var _ Policy = &restrictedPolicy{}
|
|||||||
const PolicyRestricted string = "restricted"
|
const PolicyRestricted string = "restricted"
|
||||||
|
|
||||||
// NewRestrictedPolicy returns restricted policy.
|
// NewRestrictedPolicy returns restricted policy.
|
||||||
func NewRestrictedPolicy(numaNodes []int) Policy {
|
func NewRestrictedPolicy(numaInfo *NUMAInfo, topologyPolicyOptions map[string]string) (Policy, error) {
|
||||||
return &restrictedPolicy{bestEffortPolicy{numaNodes: numaNodes}}
|
opts, err := NewPolicyOptions(topologyPolicyOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &restrictedPolicy{bestEffortPolicy{numaInfo: numaInfo, opts: opts}}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *restrictedPolicy) Name() string {
|
func (p *restrictedPolicy) Name() string {
|
||||||
@ -40,7 +45,8 @@ func (p *restrictedPolicy) canAdmitPodResult(hint *TopologyHint) bool {
|
|||||||
|
|
||||||
func (p *restrictedPolicy) Merge(providersHints []map[string][]TopologyHint) (TopologyHint, bool) {
|
func (p *restrictedPolicy) Merge(providersHints []map[string][]TopologyHint) (TopologyHint, bool) {
|
||||||
filteredHints := filterProvidersHints(providersHints)
|
filteredHints := filterProvidersHints(providersHints)
|
||||||
hint := mergeFilteredHints(p.numaNodes, filteredHints)
|
merger := NewHintMerger(p.numaInfo, filteredHints, p.Name(), p.opts)
|
||||||
admit := p.canAdmitPodResult(&hint)
|
bestHint := merger.Merge()
|
||||||
return hint, admit
|
admit := p.canAdmitPodResult(&bestHint)
|
||||||
|
return bestHint, admit
|
||||||
}
|
}
|
||||||
|
@ -30,8 +30,9 @@ func TestPolicyRestrictedName(t *testing.T) {
|
|||||||
expected: "restricted",
|
expected: "restricted",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
numaInfo := commonNUMAInfoTwoNodes()
|
||||||
for _, tc := range tcases {
|
for _, tc := range tcases {
|
||||||
policy := NewRestrictedPolicy([]int{0, 1})
|
policy := &restrictedPolicy{bestEffortPolicy{numaInfo: numaInfo, opts: PolicyOptions{}}}
|
||||||
if policy.Name() != tc.expected {
|
if policy.Name() != tc.expected {
|
||||||
t.Errorf("Expected Policy Name to be %s, got %s", tc.expected, policy.Name())
|
t.Errorf("Expected Policy Name to be %s, got %s", tc.expected, policy.Name())
|
||||||
}
|
}
|
||||||
@ -57,9 +58,9 @@ func TestPolicyRestrictedCanAdmitPodResult(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tcases {
|
for _, tc := range tcases {
|
||||||
numaNodes := []int{0, 1}
|
numaInfo := commonNUMAInfoTwoNodes()
|
||||||
policy := NewRestrictedPolicy(numaNodes)
|
policy := &restrictedPolicy{bestEffortPolicy{numaInfo: numaInfo}}
|
||||||
result := policy.(*restrictedPolicy).canAdmitPodResult(&tc.hint)
|
result := policy.canAdmitPodResult(&tc.hint)
|
||||||
|
|
||||||
if result != tc.expected {
|
if result != tc.expected {
|
||||||
t.Errorf("Expected result to be %t, got %t", tc.expected, result)
|
t.Errorf("Expected result to be %t, got %t", tc.expected, result)
|
||||||
@ -68,11 +69,23 @@ func TestPolicyRestrictedCanAdmitPodResult(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPolicyRestrictedMerge(t *testing.T) {
|
func TestPolicyRestrictedMerge(t *testing.T) {
|
||||||
numaNodes := []int{0, 1, 2, 3}
|
numaInfo := commonNUMAInfoFourNodes()
|
||||||
policy := NewRestrictedPolicy(numaNodes)
|
policy := &restrictedPolicy{bestEffortPolicy{numaInfo: numaInfo}}
|
||||||
|
|
||||||
tcases := commonPolicyMergeTestCases(numaNodes)
|
tcases := commonPolicyMergeTestCases(numaInfo.Nodes)
|
||||||
tcases = append(tcases, policy.(*restrictedPolicy).mergeTestCases(numaNodes)...)
|
tcases = append(tcases, policy.mergeTestCases(numaInfo.Nodes)...)
|
||||||
|
tcases = append(tcases, policy.mergeTestCasesNoPolicies(numaInfo.Nodes)...)
|
||||||
|
|
||||||
|
testPolicyMerge(policy, tcases, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPolicyRestrictedMergeClosestNUMA(t *testing.T) {
|
||||||
|
numaInfo := commonNUMAInfoEightNodes()
|
||||||
|
policy := &restrictedPolicy{bestEffortPolicy{numaInfo: numaInfo, opts: PolicyOptions{PreferClosestNUMA: true}}}
|
||||||
|
|
||||||
|
tcases := commonPolicyMergeTestCases(numaInfo.Nodes)
|
||||||
|
tcases = append(tcases, policy.mergeTestCases(numaInfo.Nodes)...)
|
||||||
|
tcases = append(tcases, policy.mergeTestCasesClosestNUMA(numaInfo.Nodes)...)
|
||||||
|
|
||||||
testPolicyMerge(policy, tcases, t)
|
testPolicyMerge(policy, tcases, t)
|
||||||
}
|
}
|
||||||
|
@ -16,13 +16,10 @@ limitations under the License.
|
|||||||
|
|
||||||
package topologymanager
|
package topologymanager
|
||||||
|
|
||||||
import (
|
|
||||||
"k8s.io/kubernetes/pkg/kubelet/cm/topologymanager/bitmask"
|
|
||||||
)
|
|
||||||
|
|
||||||
type singleNumaNodePolicy struct {
|
type singleNumaNodePolicy struct {
|
||||||
//List of NUMA Nodes available on the underlying machine
|
// numaInfo represents list of NUMA Nodes available on the underlying machine and distances between them
|
||||||
numaNodes []int
|
numaInfo *NUMAInfo
|
||||||
|
opts PolicyOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ Policy = &singleNumaNodePolicy{}
|
var _ Policy = &singleNumaNodePolicy{}
|
||||||
@ -31,8 +28,13 @@ var _ Policy = &singleNumaNodePolicy{}
|
|||||||
const PolicySingleNumaNode string = "single-numa-node"
|
const PolicySingleNumaNode string = "single-numa-node"
|
||||||
|
|
||||||
// NewSingleNumaNodePolicy returns single-numa-node policy.
|
// NewSingleNumaNodePolicy returns single-numa-node policy.
|
||||||
func NewSingleNumaNodePolicy(numaNodes []int) Policy {
|
func NewSingleNumaNodePolicy(numaInfo *NUMAInfo, topologyPolicyOptions map[string]string) (Policy, error) {
|
||||||
return &singleNumaNodePolicy{numaNodes: numaNodes}
|
opts, err := NewPolicyOptions(topologyPolicyOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &singleNumaNodePolicy{numaInfo: numaInfo, opts: opts}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *singleNumaNodePolicy) Name() string {
|
func (p *singleNumaNodePolicy) Name() string {
|
||||||
@ -65,10 +67,11 @@ func (p *singleNumaNodePolicy) Merge(providersHints []map[string][]TopologyHint)
|
|||||||
filteredHints := filterProvidersHints(providersHints)
|
filteredHints := filterProvidersHints(providersHints)
|
||||||
// Filter to only include don't cares and hints with a single NUMA node.
|
// Filter to only include don't cares and hints with a single NUMA node.
|
||||||
singleNumaHints := filterSingleNumaHints(filteredHints)
|
singleNumaHints := filterSingleNumaHints(filteredHints)
|
||||||
bestHint := mergeFilteredHints(p.numaNodes, singleNumaHints)
|
|
||||||
|
|
||||||
defaultAffinity, _ := bitmask.NewBitMask(p.numaNodes...)
|
merger := NewHintMerger(p.numaInfo, singleNumaHints, p.Name(), p.opts)
|
||||||
if bestHint.NUMANodeAffinity.IsEqual(defaultAffinity) {
|
bestHint := merger.Merge()
|
||||||
|
|
||||||
|
if bestHint.NUMANodeAffinity.IsEqual(p.numaInfo.DefaultAffinityMask()) {
|
||||||
bestHint = TopologyHint{nil, bestHint.Preferred}
|
bestHint = TopologyHint{nil, bestHint.Preferred}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,11 +33,11 @@ func TestPolicySingleNumaNodeCanAdmitPodResult(t *testing.T) {
|
|||||||
expected: false,
|
expected: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
numaInfo := commonNUMAInfoTwoNodes()
|
||||||
|
|
||||||
for _, tc := range tcases {
|
for _, tc := range tcases {
|
||||||
numaNodes := []int{0, 1}
|
policy := singleNumaNodePolicy{numaInfo: numaInfo, opts: PolicyOptions{}}
|
||||||
policy := NewSingleNumaNodePolicy(numaNodes)
|
result := policy.canAdmitPodResult(&tc.hint)
|
||||||
result := policy.(*singleNumaNodePolicy).canAdmitPodResult(&tc.hint)
|
|
||||||
|
|
||||||
if result != tc.expected {
|
if result != tc.expected {
|
||||||
t.Errorf("Expected result to be %t, got %t", tc.expected, result)
|
t.Errorf("Expected result to be %t, got %t", tc.expected, result)
|
||||||
@ -156,11 +156,11 @@ func TestPolicySingleNumaNodeFilterHints(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPolicySingleNumaNodeMerge(t *testing.T) {
|
func TestPolicySingleNumaNodeMerge(t *testing.T) {
|
||||||
numaNodes := []int{0, 1, 2, 3}
|
numaInfo := commonNUMAInfoFourNodes()
|
||||||
policy := NewSingleNumaNodePolicy(numaNodes)
|
policy := singleNumaNodePolicy{numaInfo: numaInfo, opts: PolicyOptions{}}
|
||||||
|
|
||||||
tcases := commonPolicyMergeTestCases(numaNodes)
|
tcases := commonPolicyMergeTestCases(numaInfo.Nodes)
|
||||||
tcases = append(tcases, policy.(*singleNumaNodePolicy).mergeTestCases(numaNodes)...)
|
tcases = append(tcases, policy.mergeTestCases(numaInfo.Nodes)...)
|
||||||
|
|
||||||
testPolicyMerge(policy, tcases, t)
|
testPolicyMerge(&policy, tcases, t)
|
||||||
}
|
}
|
||||||
|
@ -748,6 +748,11 @@ func (p *bestEffortPolicy) mergeTestCases(numaNodes []int) []policyMergeTestCase
|
|||||||
Preferred: false,
|
Preferred: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *bestEffortPolicy) mergeTestCasesNoPolicies(numaNodes []int) []policyMergeTestCase {
|
||||||
|
return []policyMergeTestCase{
|
||||||
{
|
{
|
||||||
name: "bestNonPreferredAffinityCount (5)",
|
name: "bestNonPreferredAffinityCount (5)",
|
||||||
hp: []HintProvider{
|
hp: []HintProvider{
|
||||||
@ -833,6 +838,167 @@ func (p *bestEffortPolicy) mergeTestCases(numaNodes []int) []policyMergeTestCase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *bestEffortPolicy) mergeTestCasesClosestNUMA(numaNodes []int) []policyMergeTestCase {
|
||||||
|
return []policyMergeTestCase{
|
||||||
|
{
|
||||||
|
name: "Two providers, 2 hints each, same mask (some with different bits), same preferred",
|
||||||
|
hp: []HintProvider{
|
||||||
|
&mockHintProvider{
|
||||||
|
map[string][]TopologyHint{
|
||||||
|
"resource1": {
|
||||||
|
{
|
||||||
|
NUMANodeAffinity: NewTestBitMask(0, 4),
|
||||||
|
Preferred: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
NUMANodeAffinity: NewTestBitMask(0, 2),
|
||||||
|
Preferred: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&mockHintProvider{
|
||||||
|
map[string][]TopologyHint{
|
||||||
|
"resource2": {
|
||||||
|
{
|
||||||
|
NUMANodeAffinity: NewTestBitMask(0, 4),
|
||||||
|
Preferred: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
NUMANodeAffinity: NewTestBitMask(0, 2),
|
||||||
|
Preferred: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: TopologyHint{
|
||||||
|
NUMANodeAffinity: NewTestBitMask(0, 2),
|
||||||
|
Preferred: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Two providers, 2 hints each, different mask",
|
||||||
|
hp: []HintProvider{
|
||||||
|
&mockHintProvider{
|
||||||
|
map[string][]TopologyHint{
|
||||||
|
"resource1": {
|
||||||
|
{
|
||||||
|
NUMANodeAffinity: NewTestBitMask(4),
|
||||||
|
Preferred: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
NUMANodeAffinity: NewTestBitMask(0, 2),
|
||||||
|
Preferred: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&mockHintProvider{
|
||||||
|
map[string][]TopologyHint{
|
||||||
|
"resource2": {
|
||||||
|
{
|
||||||
|
NUMANodeAffinity: NewTestBitMask(4),
|
||||||
|
Preferred: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
NUMANodeAffinity: NewTestBitMask(0, 2),
|
||||||
|
Preferred: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: TopologyHint{
|
||||||
|
NUMANodeAffinity: NewTestBitMask(4),
|
||||||
|
Preferred: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bestNonPreferredAffinityCount (5)",
|
||||||
|
hp: []HintProvider{
|
||||||
|
&mockHintProvider{
|
||||||
|
map[string][]TopologyHint{
|
||||||
|
"resource1": {
|
||||||
|
{
|
||||||
|
NUMANodeAffinity: NewTestBitMask(0, 1, 2, 3),
|
||||||
|
Preferred: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
NUMANodeAffinity: NewTestBitMask(0, 1),
|
||||||
|
Preferred: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&mockHintProvider{
|
||||||
|
map[string][]TopologyHint{
|
||||||
|
"resource2": {
|
||||||
|
{
|
||||||
|
NUMANodeAffinity: NewTestBitMask(1, 2),
|
||||||
|
Preferred: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
NUMANodeAffinity: NewTestBitMask(2, 3),
|
||||||
|
Preferred: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: TopologyHint{
|
||||||
|
NUMANodeAffinity: NewTestBitMask(2, 3),
|
||||||
|
Preferred: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bestNonPreferredAffinityCount (6)",
|
||||||
|
hp: []HintProvider{
|
||||||
|
&mockHintProvider{
|
||||||
|
map[string][]TopologyHint{
|
||||||
|
"resource1": {
|
||||||
|
{
|
||||||
|
NUMANodeAffinity: NewTestBitMask(0, 1, 2, 3),
|
||||||
|
Preferred: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
NUMANodeAffinity: NewTestBitMask(0, 1),
|
||||||
|
Preferred: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&mockHintProvider{
|
||||||
|
map[string][]TopologyHint{
|
||||||
|
"resource2": {
|
||||||
|
{
|
||||||
|
NUMANodeAffinity: NewTestBitMask(1, 2, 3),
|
||||||
|
Preferred: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
NUMANodeAffinity: NewTestBitMask(1, 2),
|
||||||
|
Preferred: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
NUMANodeAffinity: NewTestBitMask(1, 3),
|
||||||
|
Preferred: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
NUMANodeAffinity: NewTestBitMask(2, 3),
|
||||||
|
Preferred: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: TopologyHint{
|
||||||
|
NUMANodeAffinity: NewTestBitMask(2, 3),
|
||||||
|
Preferred: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (p *singleNumaNodePolicy) mergeTestCases(numaNodes []int) []policyMergeTestCase {
|
func (p *singleNumaNodePolicy) mergeTestCases(numaNodes []int) []policyMergeTestCase {
|
||||||
return []policyMergeTestCase{
|
return []policyMergeTestCase{
|
||||||
{
|
{
|
||||||
@ -1200,7 +1366,7 @@ func TestMaxOfMinAffinityCounts(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCompareHints(t *testing.T) {
|
func TestCompareHintsNarrowest(t *testing.T) {
|
||||||
tcases := []struct {
|
tcases := []struct {
|
||||||
description string
|
description string
|
||||||
bestNonPreferredAffinityCount int
|
bestNonPreferredAffinityCount int
|
||||||
@ -1387,7 +1553,11 @@ func TestCompareHints(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range tcases {
|
for _, tc := range tcases {
|
||||||
t.Run(tc.description, func(t *testing.T) {
|
t.Run(tc.description, func(t *testing.T) {
|
||||||
result := compareHints(tc.bestNonPreferredAffinityCount, tc.current, tc.candidate)
|
numaInfo := &NUMAInfo{}
|
||||||
|
merger := NewHintMerger(numaInfo, [][]TopologyHint{}, PolicyBestEffort, PolicyOptions{})
|
||||||
|
merger.BestNonPreferredAffinityCount = tc.bestNonPreferredAffinityCount
|
||||||
|
|
||||||
|
result := merger.compare(tc.current, tc.candidate)
|
||||||
if result != tc.current && result != tc.candidate {
|
if result != tc.current && result != tc.candidate {
|
||||||
t.Errorf("Expected result to be either 'current' or 'candidate' hint")
|
t.Errorf("Expected result to be either 'current' or 'candidate' hint")
|
||||||
}
|
}
|
||||||
@ -1400,3 +1570,41 @@ func TestCompareHints(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func commonNUMAInfoTwoNodes() *NUMAInfo {
|
||||||
|
return &NUMAInfo{
|
||||||
|
Nodes: []int{0, 1},
|
||||||
|
NUMADistances: NUMADistances{
|
||||||
|
{10, 11},
|
||||||
|
{11, 10},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func commonNUMAInfoFourNodes() *NUMAInfo {
|
||||||
|
return &NUMAInfo{
|
||||||
|
Nodes: []int{0, 1, 2, 3},
|
||||||
|
NUMADistances: NUMADistances{
|
||||||
|
{10, 11, 12, 12},
|
||||||
|
{11, 10, 12, 12},
|
||||||
|
{12, 12, 10, 11},
|
||||||
|
{12, 12, 11, 10},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func commonNUMAInfoEightNodes() *NUMAInfo {
|
||||||
|
return &NUMAInfo{
|
||||||
|
Nodes: []int{0, 1, 2, 3, 4, 5, 6, 7},
|
||||||
|
NUMADistances: NUMADistances{
|
||||||
|
{10, 11, 12, 12, 30, 30, 30, 30},
|
||||||
|
{11, 10, 12, 12, 30, 30, 30, 30},
|
||||||
|
{12, 12, 10, 11, 30, 30, 30, 30},
|
||||||
|
{12, 12, 11, 10, 30, 30, 30, 30},
|
||||||
|
{30, 30, 30, 30, 10, 11, 12, 12},
|
||||||
|
{30, 30, 30, 30, 11, 10, 12, 12},
|
||||||
|
{30, 30, 30, 30, 12, 12, 10, 11},
|
||||||
|
{30, 30, 30, 30, 12, 12, 13, 10},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -130,15 +130,15 @@ func (th *TopologyHint) LessThan(other TopologyHint) bool {
|
|||||||
var _ Manager = &manager{}
|
var _ Manager = &manager{}
|
||||||
|
|
||||||
// NewManager creates a new TopologyManager based on provided policy and scope
|
// NewManager creates a new TopologyManager based on provided policy and scope
|
||||||
func NewManager(topology []cadvisorapi.Node, topologyPolicyName string, topologyScopeName string) (Manager, error) {
|
func NewManager(topology []cadvisorapi.Node, topologyPolicyName string, topologyScopeName string, topologyPolicyOptions map[string]string) (Manager, error) {
|
||||||
klog.InfoS("Creating topology manager with policy per scope", "topologyPolicyName", topologyPolicyName, "topologyScopeName", topologyScopeName)
|
klog.InfoS("Creating topology manager with policy per scope", "topologyPolicyName", topologyPolicyName, "topologyScopeName", topologyScopeName)
|
||||||
|
|
||||||
var numaNodes []int
|
numaInfo, err := NewNUMAInfo(topology)
|
||||||
for _, node := range topology {
|
if err != nil {
|
||||||
numaNodes = append(numaNodes, node.Id)
|
return nil, fmt.Errorf("cannot discover NUMA topology: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if topologyPolicyName != PolicyNone && len(numaNodes) > maxAllowableNUMANodes {
|
if topologyPolicyName != PolicyNone && len(numaInfo.Nodes) > maxAllowableNUMANodes {
|
||||||
return nil, fmt.Errorf("unsupported on machines with more than %v NUMA Nodes", maxAllowableNUMANodes)
|
return nil, fmt.Errorf("unsupported on machines with more than %v NUMA Nodes", maxAllowableNUMANodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,13 +149,22 @@ func NewManager(topology []cadvisorapi.Node, topologyPolicyName string, topology
|
|||||||
policy = NewNonePolicy()
|
policy = NewNonePolicy()
|
||||||
|
|
||||||
case PolicyBestEffort:
|
case PolicyBestEffort:
|
||||||
policy = NewBestEffortPolicy(numaNodes)
|
policy, err = NewBestEffortPolicy(numaInfo, topologyPolicyOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
case PolicyRestricted:
|
case PolicyRestricted:
|
||||||
policy = NewRestrictedPolicy(numaNodes)
|
policy, err = NewRestrictedPolicy(numaInfo, topologyPolicyOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
case PolicySingleNumaNode:
|
case PolicySingleNumaNode:
|
||||||
policy = NewSingleNumaNodePolicy(numaNodes)
|
policy, err = NewSingleNumaNodePolicy(numaInfo, topologyPolicyOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unknown policy: \"%s\"", topologyPolicyName)
|
return nil, fmt.Errorf("unknown policy: \"%s\"", topologyPolicyName)
|
||||||
|
@ -22,6 +22,9 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"k8s.io/api/core/v1"
|
"k8s.io/api/core/v1"
|
||||||
|
|
||||||
|
cadvisorapi "github.com/google/cadvisor/info/v1"
|
||||||
|
|
||||||
"k8s.io/kubernetes/pkg/kubelet/cm/topologymanager/bitmask"
|
"k8s.io/kubernetes/pkg/kubelet/cm/topologymanager/bitmask"
|
||||||
"k8s.io/kubernetes/pkg/kubelet/lifecycle"
|
"k8s.io/kubernetes/pkg/kubelet/lifecycle"
|
||||||
)
|
)
|
||||||
@ -37,6 +40,8 @@ func TestNewManager(t *testing.T) {
|
|||||||
policyName string
|
policyName string
|
||||||
expectedPolicy string
|
expectedPolicy string
|
||||||
expectedError error
|
expectedError error
|
||||||
|
topologyError error
|
||||||
|
policyOptions map[string]string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
description: "Policy is set to none",
|
description: "Policy is set to none",
|
||||||
@ -63,11 +68,30 @@ func TestNewManager(t *testing.T) {
|
|||||||
policyName: "unknown",
|
policyName: "unknown",
|
||||||
expectedError: fmt.Errorf("unknown policy: \"unknown\""),
|
expectedError: fmt.Errorf("unknown policy: \"unknown\""),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
description: "Unknown policy name best-effort policy",
|
||||||
|
policyName: "best-effort",
|
||||||
|
expectedPolicy: "best-effort",
|
||||||
|
expectedError: fmt.Errorf("unknown Topology Manager Policy option:"),
|
||||||
|
policyOptions: map[string]string{
|
||||||
|
"unknown-option": "true",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Unknown policy name restricted policy",
|
||||||
|
policyName: "restricted",
|
||||||
|
expectedPolicy: "restricted",
|
||||||
|
expectedError: fmt.Errorf("unknown Topology Manager Policy option:"),
|
||||||
|
policyOptions: map[string]string{
|
||||||
|
"unknown-option": "true",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tcases {
|
for _, tc := range tcases {
|
||||||
mngr, err := NewManager(nil, tc.policyName, "container")
|
topology := []cadvisorapi.Node{}
|
||||||
|
|
||||||
|
mngr, err := NewManager(topology, tc.policyName, "container", tc.policyOptions)
|
||||||
if tc.expectedError != nil {
|
if tc.expectedError != nil {
|
||||||
if !strings.Contains(err.Error(), tc.expectedError.Error()) {
|
if !strings.Contains(err.Error(), tc.expectedError.Error()) {
|
||||||
t.Errorf("Unexpected error message. Have: %s wants %s", err.Error(), tc.expectedError.Error())
|
t.Errorf("Unexpected error message. Have: %s wants %s", err.Error(), tc.expectedError.Error())
|
||||||
@ -107,7 +131,7 @@ func TestManagerScope(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tcases {
|
for _, tc := range tcases {
|
||||||
mngr, err := NewManager(nil, "best-effort", tc.scopeName)
|
mngr, err := NewManager(nil, "best-effort", tc.scopeName, nil)
|
||||||
|
|
||||||
if tc.expectedError != nil {
|
if tc.expectedError != nil {
|
||||||
if !strings.Contains(err.Error(), tc.expectedError.Error()) {
|
if !strings.Contains(err.Error(), tc.expectedError.Error()) {
|
||||||
@ -179,7 +203,18 @@ func TestAddHintProvider(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAdmit(t *testing.T) {
|
func TestAdmit(t *testing.T) {
|
||||||
numaNodes := []int{0, 1}
|
numaInfo := &NUMAInfo{
|
||||||
|
Nodes: []int{0, 1},
|
||||||
|
NUMADistances: NUMADistances{
|
||||||
|
{10, 11},
|
||||||
|
{11, 10},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := map[string]string{}
|
||||||
|
bePolicy, _ := NewBestEffortPolicy(numaInfo, opts)
|
||||||
|
restrictedPolicy, _ := NewRestrictedPolicy(numaInfo, opts)
|
||||||
|
singleNumaPolicy, _ := NewSingleNumaNodePolicy(numaInfo, opts)
|
||||||
|
|
||||||
tcases := []struct {
|
tcases := []struct {
|
||||||
name string
|
name string
|
||||||
@ -206,7 +241,7 @@ func TestAdmit(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "QOSClass set as BestEffort. single-numa-node Policy. No Hints.",
|
name: "QOSClass set as BestEffort. single-numa-node Policy. No Hints.",
|
||||||
qosClass: v1.PodQOSBestEffort,
|
qosClass: v1.PodQOSBestEffort,
|
||||||
policy: NewRestrictedPolicy(numaNodes),
|
policy: singleNumaPolicy,
|
||||||
hp: []HintProvider{
|
hp: []HintProvider{
|
||||||
&mockHintProvider{},
|
&mockHintProvider{},
|
||||||
},
|
},
|
||||||
@ -215,7 +250,7 @@ func TestAdmit(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "QOSClass set as BestEffort. Restricted Policy. No Hints.",
|
name: "QOSClass set as BestEffort. Restricted Policy. No Hints.",
|
||||||
qosClass: v1.PodQOSBestEffort,
|
qosClass: v1.PodQOSBestEffort,
|
||||||
policy: NewRestrictedPolicy(numaNodes),
|
policy: restrictedPolicy,
|
||||||
hp: []HintProvider{
|
hp: []HintProvider{
|
||||||
&mockHintProvider{},
|
&mockHintProvider{},
|
||||||
},
|
},
|
||||||
@ -224,7 +259,7 @@ func TestAdmit(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "QOSClass set as Guaranteed. BestEffort Policy. Preferred Affinity.",
|
name: "QOSClass set as Guaranteed. BestEffort Policy. Preferred Affinity.",
|
||||||
qosClass: v1.PodQOSGuaranteed,
|
qosClass: v1.PodQOSGuaranteed,
|
||||||
policy: NewBestEffortPolicy(numaNodes),
|
policy: bePolicy,
|
||||||
hp: []HintProvider{
|
hp: []HintProvider{
|
||||||
&mockHintProvider{
|
&mockHintProvider{
|
||||||
map[string][]TopologyHint{
|
map[string][]TopologyHint{
|
||||||
@ -246,7 +281,7 @@ func TestAdmit(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "QOSClass set as Guaranteed. BestEffort Policy. More than one Preferred Affinity.",
|
name: "QOSClass set as Guaranteed. BestEffort Policy. More than one Preferred Affinity.",
|
||||||
qosClass: v1.PodQOSGuaranteed,
|
qosClass: v1.PodQOSGuaranteed,
|
||||||
policy: NewBestEffortPolicy(numaNodes),
|
policy: bePolicy,
|
||||||
hp: []HintProvider{
|
hp: []HintProvider{
|
||||||
&mockHintProvider{
|
&mockHintProvider{
|
||||||
map[string][]TopologyHint{
|
map[string][]TopologyHint{
|
||||||
@ -272,7 +307,7 @@ func TestAdmit(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "QOSClass set as Burstable. BestEffort Policy. More than one Preferred Affinity.",
|
name: "QOSClass set as Burstable. BestEffort Policy. More than one Preferred Affinity.",
|
||||||
qosClass: v1.PodQOSBurstable,
|
qosClass: v1.PodQOSBurstable,
|
||||||
policy: NewBestEffortPolicy(numaNodes),
|
policy: bePolicy,
|
||||||
hp: []HintProvider{
|
hp: []HintProvider{
|
||||||
&mockHintProvider{
|
&mockHintProvider{
|
||||||
map[string][]TopologyHint{
|
map[string][]TopologyHint{
|
||||||
@ -298,7 +333,7 @@ func TestAdmit(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "QOSClass set as Guaranteed. BestEffort Policy. No Preferred Affinity.",
|
name: "QOSClass set as Guaranteed. BestEffort Policy. No Preferred Affinity.",
|
||||||
qosClass: v1.PodQOSGuaranteed,
|
qosClass: v1.PodQOSGuaranteed,
|
||||||
policy: NewBestEffortPolicy(numaNodes),
|
policy: bePolicy,
|
||||||
hp: []HintProvider{
|
hp: []HintProvider{
|
||||||
&mockHintProvider{
|
&mockHintProvider{
|
||||||
map[string][]TopologyHint{
|
map[string][]TopologyHint{
|
||||||
@ -316,7 +351,7 @@ func TestAdmit(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "QOSClass set as Guaranteed. Restricted Policy. Preferred Affinity.",
|
name: "QOSClass set as Guaranteed. Restricted Policy. Preferred Affinity.",
|
||||||
qosClass: v1.PodQOSGuaranteed,
|
qosClass: v1.PodQOSGuaranteed,
|
||||||
policy: NewRestrictedPolicy(numaNodes),
|
policy: restrictedPolicy,
|
||||||
hp: []HintProvider{
|
hp: []HintProvider{
|
||||||
&mockHintProvider{
|
&mockHintProvider{
|
||||||
map[string][]TopologyHint{
|
map[string][]TopologyHint{
|
||||||
@ -338,7 +373,7 @@ func TestAdmit(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "QOSClass set as Burstable. Restricted Policy. Preferred Affinity.",
|
name: "QOSClass set as Burstable. Restricted Policy. Preferred Affinity.",
|
||||||
qosClass: v1.PodQOSBurstable,
|
qosClass: v1.PodQOSBurstable,
|
||||||
policy: NewRestrictedPolicy(numaNodes),
|
policy: restrictedPolicy,
|
||||||
hp: []HintProvider{
|
hp: []HintProvider{
|
||||||
&mockHintProvider{
|
&mockHintProvider{
|
||||||
map[string][]TopologyHint{
|
map[string][]TopologyHint{
|
||||||
@ -360,7 +395,7 @@ func TestAdmit(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "QOSClass set as Guaranteed. Restricted Policy. More than one Preferred affinity.",
|
name: "QOSClass set as Guaranteed. Restricted Policy. More than one Preferred affinity.",
|
||||||
qosClass: v1.PodQOSGuaranteed,
|
qosClass: v1.PodQOSGuaranteed,
|
||||||
policy: NewRestrictedPolicy(numaNodes),
|
policy: restrictedPolicy,
|
||||||
hp: []HintProvider{
|
hp: []HintProvider{
|
||||||
&mockHintProvider{
|
&mockHintProvider{
|
||||||
map[string][]TopologyHint{
|
map[string][]TopologyHint{
|
||||||
@ -386,7 +421,7 @@ func TestAdmit(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "QOSClass set as Burstable. Restricted Policy. More than one Preferred affinity.",
|
name: "QOSClass set as Burstable. Restricted Policy. More than one Preferred affinity.",
|
||||||
qosClass: v1.PodQOSBurstable,
|
qosClass: v1.PodQOSBurstable,
|
||||||
policy: NewRestrictedPolicy(numaNodes),
|
policy: restrictedPolicy,
|
||||||
hp: []HintProvider{
|
hp: []HintProvider{
|
||||||
&mockHintProvider{
|
&mockHintProvider{
|
||||||
map[string][]TopologyHint{
|
map[string][]TopologyHint{
|
||||||
@ -412,7 +447,7 @@ func TestAdmit(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "QOSClass set as Guaranteed. Restricted Policy. No Preferred affinity.",
|
name: "QOSClass set as Guaranteed. Restricted Policy. No Preferred affinity.",
|
||||||
qosClass: v1.PodQOSGuaranteed,
|
qosClass: v1.PodQOSGuaranteed,
|
||||||
policy: NewRestrictedPolicy(numaNodes),
|
policy: restrictedPolicy,
|
||||||
hp: []HintProvider{
|
hp: []HintProvider{
|
||||||
&mockHintProvider{
|
&mockHintProvider{
|
||||||
map[string][]TopologyHint{
|
map[string][]TopologyHint{
|
||||||
@ -430,7 +465,7 @@ func TestAdmit(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "QOSClass set as Burstable. Restricted Policy. No Preferred affinity.",
|
name: "QOSClass set as Burstable. Restricted Policy. No Preferred affinity.",
|
||||||
qosClass: v1.PodQOSBurstable,
|
qosClass: v1.PodQOSBurstable,
|
||||||
policy: NewRestrictedPolicy(numaNodes),
|
policy: restrictedPolicy,
|
||||||
hp: []HintProvider{
|
hp: []HintProvider{
|
||||||
&mockHintProvider{
|
&mockHintProvider{
|
||||||
map[string][]TopologyHint{
|
map[string][]TopologyHint{
|
||||||
|
@ -382,6 +382,12 @@ type KubeletConfiguration struct {
|
|||||||
// Default: "container"
|
// Default: "container"
|
||||||
// +optional
|
// +optional
|
||||||
TopologyManagerScope string `json:"topologyManagerScope,omitempty"`
|
TopologyManagerScope string `json:"topologyManagerScope,omitempty"`
|
||||||
|
// TopologyManagerPolicyOptions is a set of key=value which allows to set extra options
|
||||||
|
// to fine tune the behaviour of the topology manager policies.
|
||||||
|
// Requires both the "TopologyManager" and "TopologyManagerPolicyOptions" feature gates to be enabled.
|
||||||
|
// Default: nil
|
||||||
|
// +optional
|
||||||
|
TopologyManagerPolicyOptions map[string]string `json:"topologyManagerPolicyOptions,omitempty"`
|
||||||
// qosReserved is a set of resource name to percentage pairs that specify
|
// qosReserved is a set of resource name to percentage pairs that specify
|
||||||
// the minimum percentage of a resource reserved for exclusive use by the
|
// the minimum percentage of a resource reserved for exclusive use by the
|
||||||
// guaranteed QoS tier.
|
// guaranteed QoS tier.
|
||||||
|
@ -261,6 +261,13 @@ func (in *KubeletConfiguration) DeepCopyInto(out *KubeletConfiguration) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
out.CPUManagerReconcilePeriod = in.CPUManagerReconcilePeriod
|
out.CPUManagerReconcilePeriod = in.CPUManagerReconcilePeriod
|
||||||
|
if in.TopologyManagerPolicyOptions != nil {
|
||||||
|
in, out := &in.TopologyManagerPolicyOptions, &out.TopologyManagerPolicyOptions
|
||||||
|
*out = make(map[string]string, len(*in))
|
||||||
|
for key, val := range *in {
|
||||||
|
(*out)[key] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
if in.QOSReserved != nil {
|
if in.QOSReserved != nil {
|
||||||
in, out := &in.QOSReserved, &out.QOSReserved
|
in, out := &in.QOSReserved, &out.QOSReserved
|
||||||
*out = make(map[string]string, len(*in))
|
*out = make(map[string]string, len(*in))
|
||||||
|
Loading…
Reference in New Issue
Block a user