Merge pull request #84721 from klueska/pr/81586-klueska

Base Infrastructure for adding Merge() API to TopologyManager Policies
This commit is contained in:
Kubernetes Prow Robot 2019-11-04 19:40:54 -08:00 committed by GitHub
commit 590cbef0f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 974 additions and 855 deletions

View File

@ -29,13 +29,6 @@ import (
"k8s.io/kubernetes/pkg/kubelet/cm/topologymanager/bitmask" "k8s.io/kubernetes/pkg/kubelet/cm/topologymanager/bitmask"
) )
func topologyHintLessThan(a topologymanager.TopologyHint, b topologymanager.TopologyHint) bool {
if a.Preferred != b.Preferred {
return a.Preferred == true
}
return a.NUMANodeAffinity.IsNarrowerThan(b.NUMANodeAffinity)
}
func TestGetTopologyHints(t *testing.T) { func TestGetTopologyHints(t *testing.T) {
testPod1 := makePod("2", "2") testPod1 := makePod("2", "2")
testContainer1 := &testPod1.Spec.Containers[0] testContainer1 := &testPod1.Spec.Containers[0]
@ -168,10 +161,10 @@ func TestGetTopologyHints(t *testing.T) {
continue continue
} }
sort.SliceStable(hints, func(i, j int) bool { sort.SliceStable(hints, func(i, j int) bool {
return topologyHintLessThan(hints[i], hints[j]) return hints[i].LessThan(hints[j])
}) })
sort.SliceStable(tc.expectedHints, func(i, j int) bool { sort.SliceStable(tc.expectedHints, func(i, j int) bool {
return topologyHintLessThan(tc.expectedHints[i], tc.expectedHints[j]) return tc.expectedHints[i].LessThan(tc.expectedHints[j])
}) })
if !reflect.DeepEqual(tc.expectedHints, hints) { if !reflect.DeepEqual(tc.expectedHints, hints) {
t.Errorf("Expected in result to be %v , got %v", tc.expectedHints, hints) t.Errorf("Expected in result to be %v , got %v", tc.expectedHints, hints)

View File

@ -44,13 +44,6 @@ func makeNUMADevice(id string, numa int) pluginapi.Device {
} }
} }
func topologyHintLessThan(a topologymanager.TopologyHint, b topologymanager.TopologyHint) bool {
if a.Preferred != b.Preferred {
return a.Preferred == true
}
return a.NUMANodeAffinity.IsNarrowerThan(b.NUMANodeAffinity)
}
func makeSocketMask(sockets ...int) bitmask.BitMask { func makeSocketMask(sockets ...int) bitmask.BitMask {
mask, _ := bitmask.NewBitMask(sockets...) mask, _ := bitmask.NewBitMask(sockets...)
return mask return mask
@ -292,10 +285,10 @@ func TestGetTopologyHints(t *testing.T) {
for r := range tc.expectedHints { for r := range tc.expectedHints {
sort.SliceStable(hints[r], func(i, j int) bool { sort.SliceStable(hints[r], func(i, j int) bool {
return topologyHintLessThan(hints[r][i], hints[r][j]) return hints[r][i].LessThan(hints[r][j])
}) })
sort.SliceStable(tc.expectedHints[r], func(i, j int) bool { sort.SliceStable(tc.expectedHints[r], func(i, j int) bool {
return topologyHintLessThan(tc.expectedHints[r][i], tc.expectedHints[r][j]) return tc.expectedHints[r][i].LessThan(tc.expectedHints[r][j])
}) })
if !reflect.DeepEqual(hints[r], tc.expectedHints[r]) { if !reflect.DeepEqual(hints[r], tc.expectedHints[r]) {
t.Errorf("%v: Expected result to be %v, got %v", tc.description, tc.expectedHints[r], hints[r]) t.Errorf("%v: Expected result to be %v, got %v", tc.description, tc.expectedHints[r], hints[r])

View File

@ -47,6 +47,7 @@ go_test(
"policy_none_test.go", "policy_none_test.go",
"policy_restricted_test.go", "policy_restricted_test.go",
"policy_single_numa_node_test.go", "policy_single_numa_node_test.go",
"policy_test.go",
"topology_manager_test.go", "topology_manager_test.go",
], ],
embed = [":go_default_library"], embed = [":go_default_library"],

View File

@ -20,10 +20,11 @@ import (
"k8s.io/kubernetes/pkg/kubelet/lifecycle" "k8s.io/kubernetes/pkg/kubelet/lifecycle"
) )
//Policy interface for Topology Manager Pod Admit Result // Policy interface for Topology Manager Pod Admit Result
type Policy interface { type Policy interface {
//Returns Policy Name // Returns Policy Name
Name() string Name() string
//Returns Pod Admit Handler Response based on hints and policy type // Returns a merged TopologyHint based on input from hint providers
CanAdmitPodResult(hint *TopologyHint) lifecycle.PodAdmitResult // and a Pod Admit Handler Response based on hints and policy type
Merge(providersHints []map[string][]TopologyHint) (TopologyHint, lifecycle.PodAdmitResult)
} }

View File

@ -17,10 +17,15 @@ limitations under the License.
package topologymanager package topologymanager
import ( import (
"k8s.io/klog"
"k8s.io/kubernetes/pkg/kubelet/cm/topologymanager/bitmask"
"k8s.io/kubernetes/pkg/kubelet/lifecycle" "k8s.io/kubernetes/pkg/kubelet/lifecycle"
) )
type bestEffortPolicy struct{} type bestEffortPolicy struct {
//List of NUMA Nodes available on the underlying machine
numaNodes []int
}
var _ Policy = &bestEffortPolicy{} var _ Policy = &bestEffortPolicy{}
@ -28,16 +33,167 @@ 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() Policy { func NewBestEffortPolicy(numaNodes []int) Policy {
return &bestEffortPolicy{} return &bestEffortPolicy{numaNodes: numaNodes}
} }
func (p *bestEffortPolicy) Name() string { func (p *bestEffortPolicy) Name() string {
return PolicyBestEffort return PolicyBestEffort
} }
func (p *bestEffortPolicy) CanAdmitPodResult(hint *TopologyHint) lifecycle.PodAdmitResult { func (p *bestEffortPolicy) canAdmitPodResult(hint *TopologyHint) lifecycle.PodAdmitResult {
return lifecycle.PodAdmitResult{ return lifecycle.PodAdmitResult{
Admit: true, Admit: true,
} }
} }
func (p *bestEffortPolicy) Merge(providersHints []map[string][]TopologyHint) (TopologyHint, lifecycle.PodAdmitResult) {
hint := mergeProvidersHints(p, p.numaNodes, providersHints)
admit := p.canAdmitPodResult(&hint)
return hint, admit
}
// Iterate over all permutations of hints in 'allProviderHints [][]TopologyHint'.
//
// This procedure is implemented as a recursive function over the set of hints
// in 'allproviderHints[i]'. It applies the function 'callback' to each
// permutation as it is found. It is the equivalent of:
//
// for i := 0; i < len(providerHints[0]); i++
// for j := 0; j < len(providerHints[1]); j++
// for k := 0; k < len(providerHints[2]); k++
// ...
// for z := 0; z < len(providerHints[-1]); z++
// permutation := []TopologyHint{
// providerHints[0][i],
// providerHints[1][j],
// providerHints[2][k],
// ...
// providerHints[-1][z]
// }
// callback(permutation)
func iterateAllProviderTopologyHints(allProviderHints [][]TopologyHint, callback func([]TopologyHint)) {
// Internal helper function to accumulate the permutation before calling the callback.
var iterate func(i int, accum []TopologyHint)
iterate = func(i int, accum []TopologyHint) {
// Base case: we have looped through all providers and have a full permutation.
if i == len(allProviderHints) {
callback(accum)
return
}
// Loop through all hints for provider 'i', and recurse to build the
// the permutation of this hint with all hints from providers 'i++'.
for j := range allProviderHints[i] {
iterate(i+1, append(accum, allProviderHints[i][j]))
}
}
iterate(0, []TopologyHint{})
}
// Merge the hints from all hint providers to find the best one.
func mergeProvidersHints(policy Policy, numaNodes []int, providersHints []map[string][]TopologyHint) TopologyHint {
// Set the default affinity as an any-numa affinity containing the list
// of NUMA Nodes available on this machine.
defaultAffinity, _ := bitmask.NewBitMask(numaNodes...)
// Loop through all hint providers and save an accumulated list of the
// hints returned by each hint provider. If no hints are provided, assume
// that provider has no preference for topology-aware allocation.
var allProviderHints [][]TopologyHint
for _, hints := range providersHints {
// If hints is nil, insert a single, preferred any-numa hint into allProviderHints.
if len(hints) == 0 {
klog.Infof("[topologymanager] Hint Provider has no preference for NUMA affinity with any resource")
allProviderHints = append(allProviderHints, []TopologyHint{{nil, true}})
continue
}
// Otherwise, accumulate the hints for each resource type into allProviderHints.
for resource := range hints {
if hints[resource] == nil {
klog.Infof("[topologymanager] Hint Provider has no preference for NUMA affinity with resource '%s'", resource)
allProviderHints = append(allProviderHints, []TopologyHint{{nil, true}})
continue
}
if len(hints[resource]) == 0 {
klog.Infof("[topologymanager] Hint Provider has no possible NUMA affinities for resource '%s'", resource)
allProviderHints = append(allProviderHints, []TopologyHint{{nil, false}})
continue
}
allProviderHints = append(allProviderHints, hints[resource])
}
}
// Iterate over all permutations of hints in 'allProviderHints'. Merge the
// hints in each permutation by taking the bitwise-and of their affinity masks.
// Return the hint with the narrowest NUMANodeAffinity of all merged
// permutations that have at least one NUMA ID set. If no merged mask can be
// found that has at least one NUMA ID set, return the 'defaultAffinity'.
bestHint := TopologyHint{defaultAffinity, false}
iterateAllProviderTopologyHints(allProviderHints, func(permutation []TopologyHint) {
// Get the NUMANodeAffinity from each hint in the permutation and see if any
// of them encode unpreferred allocations.
preferred := true
var numaAffinities []bitmask.BitMask
for _, hint := range permutation {
if hint.NUMANodeAffinity == nil {
numaAffinities = append(numaAffinities, defaultAffinity)
} else {
numaAffinities = append(numaAffinities, hint.NUMANodeAffinity)
}
if !hint.Preferred {
preferred = false
}
// Special case PolicySingleNumaNode to only prefer hints where
// all providers have a single NUMA affinity set.
if policy != nil && policy.Name() == PolicySingleNumaNode && hint.NUMANodeAffinity != nil && hint.NUMANodeAffinity.Count() > 1 {
preferred = false
}
}
// Merge the affinities using a bitwise-and operation.
mergedAffinity, _ := bitmask.NewBitMask(numaNodes...)
mergedAffinity.And(numaAffinities...)
// Build a mergedHintfrom the merged affinity mask, indicating if an
// preferred allocation was used to generate the affinity mask or not.
mergedHint := TopologyHint{mergedAffinity, preferred}
// Only consider mergedHints that result in a NUMANodeAffinity > 0 to
// replace the current bestHint.
if mergedHint.NUMANodeAffinity.Count() == 0 {
return
}
// If the current bestHint is non-preferred and the new mergedHint is
// preferred, always choose the preferred hint over the non-preferred one.
if mergedHint.Preferred && !bestHint.Preferred {
bestHint = mergedHint
return
}
// If the current bestHint is preferred and the new mergedHint is
// non-preferred, never update bestHint, regardless of mergedHint's
// narowness.
if !mergedHint.Preferred && bestHint.Preferred {
return
}
// If mergedHint and bestHint has the same preference, only consider
// mergedHints that have a narrower NUMANodeAffinity than the
// NUMANodeAffinity in the current bestHint.
if !mergedHint.NUMANodeAffinity.IsNarrowerThan(bestHint.NUMANodeAffinity) {
return
}
// In all other cases, update bestHint to the current mergedHint
bestHint = mergedHint
})
return bestHint
}

View File

@ -39,11 +39,22 @@ func TestPolicyBestEffortCanAdmitPodResult(t *testing.T) {
} }
for _, tc := range tcases { for _, tc := range tcases {
policy := NewBestEffortPolicy() numaNodes := []int{0, 1}
result := policy.CanAdmitPodResult(&tc.hint) policy := NewBestEffortPolicy(numaNodes)
result := policy.(*bestEffortPolicy).canAdmitPodResult(&tc.hint)
if result.Admit != tc.expected { if result.Admit != tc.expected {
t.Errorf("Expected Admit field in result to be %t, got %t", tc.expected, result.Admit) t.Errorf("Expected Admit field in result to be %t, got %t", tc.expected, result.Admit)
} }
} }
} }
func TestBestEffortPolicyMerge(t *testing.T) {
numaNodes := []int{0, 1}
policy := NewBestEffortPolicy(numaNodes)
tcases := commonPolicyMergeTestCases(numaNodes)
tcases = append(tcases, policy.(*bestEffortPolicy).mergeTestCases(numaNodes)...)
testPolicyMerge(policy, tcases, t)
}

View File

@ -36,8 +36,12 @@ func (p *nonePolicy) Name() string {
return PolicyNone return PolicyNone
} }
func (p *nonePolicy) CanAdmitPodResult(hint *TopologyHint) lifecycle.PodAdmitResult { func (p *nonePolicy) canAdmitPodResult(hint *TopologyHint) lifecycle.PodAdmitResult {
return lifecycle.PodAdmitResult{ return lifecycle.PodAdmitResult{
Admit: true, Admit: true,
} }
} }
func (p *nonePolicy) Merge(providersHints []map[string][]TopologyHint) (TopologyHint, lifecycle.PodAdmitResult) {
return TopologyHint{}, p.canAdmitPodResult(nil)
}

View File

@ -58,7 +58,7 @@ func TestPolicyNoneCanAdmitPodResult(t *testing.T) {
for _, tc := range tcases { for _, tc := range tcases {
policy := NewNonePolicy() policy := NewNonePolicy()
result := policy.CanAdmitPodResult(&tc.hint) result := policy.(*nonePolicy).canAdmitPodResult(&tc.hint)
if result.Admit != tc.expected { if result.Admit != tc.expected {
t.Errorf("Expected Admit field in result to be %t, got %t", tc.expected, result.Admit) t.Errorf("Expected Admit field in result to be %t, got %t", tc.expected, result.Admit)

View File

@ -20,7 +20,9 @@ import (
"k8s.io/kubernetes/pkg/kubelet/lifecycle" "k8s.io/kubernetes/pkg/kubelet/lifecycle"
) )
type restrictedPolicy struct{} type restrictedPolicy struct {
bestEffortPolicy
}
var _ Policy = &restrictedPolicy{} var _ Policy = &restrictedPolicy{}
@ -28,15 +30,15 @@ var _ Policy = &restrictedPolicy{}
const PolicyRestricted string = "restricted" const PolicyRestricted string = "restricted"
// NewRestrictedPolicy returns restricted policy. // NewRestrictedPolicy returns restricted policy.
func NewRestrictedPolicy() Policy { func NewRestrictedPolicy(numaNodes []int) Policy {
return &restrictedPolicy{} return &restrictedPolicy{bestEffortPolicy{numaNodes: numaNodes}}
} }
func (p *restrictedPolicy) Name() string { func (p *restrictedPolicy) Name() string {
return PolicyRestricted return PolicyRestricted
} }
func (p *restrictedPolicy) CanAdmitPodResult(hint *TopologyHint) lifecycle.PodAdmitResult { func (p *restrictedPolicy) canAdmitPodResult(hint *TopologyHint) lifecycle.PodAdmitResult {
if !hint.Preferred { if !hint.Preferred {
return lifecycle.PodAdmitResult{ return lifecycle.PodAdmitResult{
Admit: false, Admit: false,
@ -48,3 +50,9 @@ func (p *restrictedPolicy) CanAdmitPodResult(hint *TopologyHint) lifecycle.PodAd
Admit: true, Admit: true,
} }
} }
func (p *restrictedPolicy) Merge(providersHints []map[string][]TopologyHint) (TopologyHint, lifecycle.PodAdmitResult) {
hint := mergeProvidersHints(p, p.numaNodes, providersHints)
admit := p.canAdmitPodResult(&hint)
return hint, admit
}

View File

@ -39,8 +39,9 @@ func TestPolicyRestrictedCanAdmitPodResult(t *testing.T) {
} }
for _, tc := range tcases { for _, tc := range tcases {
policy := NewRestrictedPolicy() numaNodes := []int{0, 1}
result := policy.CanAdmitPodResult(&tc.hint) policy := NewRestrictedPolicy(numaNodes)
result := policy.(*restrictedPolicy).canAdmitPodResult(&tc.hint)
if result.Admit != tc.expected { if result.Admit != tc.expected {
t.Errorf("Expected Admit field in result to be %t, got %t", tc.expected, result.Admit) t.Errorf("Expected Admit field in result to be %t, got %t", tc.expected, result.Admit)
@ -56,3 +57,13 @@ func TestPolicyRestrictedCanAdmitPodResult(t *testing.T) {
} }
} }
} }
func TestRestrictedPolicyMerge(t *testing.T) {
numaNodes := []int{0, 1}
policy := NewRestrictedPolicy(numaNodes)
tcases := commonPolicyMergeTestCases(numaNodes)
tcases = append(tcases, policy.(*restrictedPolicy).mergeTestCases(numaNodes)...)
testPolicyMerge(policy, tcases, t)
}

View File

@ -20,7 +20,10 @@ import (
"k8s.io/kubernetes/pkg/kubelet/lifecycle" "k8s.io/kubernetes/pkg/kubelet/lifecycle"
) )
type singleNumaNodePolicy struct{} type singleNumaNodePolicy struct {
//List of NUMA Nodes available on the underlying machine
numaNodes []int
}
var _ Policy = &singleNumaNodePolicy{} var _ Policy = &singleNumaNodePolicy{}
@ -28,15 +31,15 @@ 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() Policy { func NewSingleNumaNodePolicy(numaNodes []int) Policy {
return &singleNumaNodePolicy{} return &singleNumaNodePolicy{numaNodes: numaNodes}
} }
func (p *singleNumaNodePolicy) Name() string { func (p *singleNumaNodePolicy) Name() string {
return PolicySingleNumaNode return PolicySingleNumaNode
} }
func (p *singleNumaNodePolicy) CanAdmitPodResult(hint *TopologyHint) lifecycle.PodAdmitResult { func (p *singleNumaNodePolicy) canAdmitPodResult(hint *TopologyHint) lifecycle.PodAdmitResult {
if !hint.Preferred { if !hint.Preferred {
return lifecycle.PodAdmitResult{ return lifecycle.PodAdmitResult{
Admit: false, Admit: false,
@ -48,3 +51,9 @@ func (p *singleNumaNodePolicy) CanAdmitPodResult(hint *TopologyHint) lifecycle.P
Admit: true, Admit: true,
} }
} }
func (p *singleNumaNodePolicy) Merge(providersHints []map[string][]TopologyHint) (TopologyHint, lifecycle.PodAdmitResult) {
hint := mergeProvidersHints(p, p.numaNodes, providersHints)
admit := p.canAdmitPodResult(&hint)
return hint, admit
}

View File

@ -34,8 +34,9 @@ func TestPolicySingleNumaNodeCanAdmitPodResult(t *testing.T) {
} }
for _, tc := range tcases { for _, tc := range tcases {
policy := NewSingleNumaNodePolicy() numaNodes := []int{0, 1}
result := policy.CanAdmitPodResult(&tc.hint) policy := NewSingleNumaNodePolicy(numaNodes)
result := policy.(*singleNumaNodePolicy).canAdmitPodResult(&tc.hint)
if result.Admit != tc.expected { if result.Admit != tc.expected {
t.Errorf("Expected Admit field in result to be %t, got %t", tc.expected, result.Admit) t.Errorf("Expected Admit field in result to be %t, got %t", tc.expected, result.Admit)
@ -51,3 +52,13 @@ func TestPolicySingleNumaNodeCanAdmitPodResult(t *testing.T) {
} }
} }
} }
func TestSingleNumaNodePolicyMerge(t *testing.T) {
numaNodes := []int{0, 1}
policy := NewSingleNumaNodePolicy(numaNodes)
tcases := commonPolicyMergeTestCases(numaNodes)
tcases = append(tcases, policy.(*singleNumaNodePolicy).mergeTestCases(numaNodes)...)
testPolicyMerge(policy, tcases, t)
}

View File

@ -0,0 +1,668 @@
/*
Copyright 2019 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 (
"testing"
"k8s.io/api/core/v1"
)
type policyMergeTestCase struct {
name string
hp []HintProvider
expected TopologyHint
}
func commonPolicyMergeTestCases(numaNodes []int) []policyMergeTestCase {
return []policyMergeTestCase{
{
name: "TopologyHint not set",
hp: []HintProvider{},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(numaNodes...),
Preferred: true,
},
},
{
name: "HintProvider returns empty non-nil map[string][]TopologyHint",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(numaNodes...),
Preferred: true,
},
},
{
name: "HintProvider returns -nil map[string][]TopologyHint from provider",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource": nil,
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(numaNodes...),
Preferred: true,
},
},
{
name: "HintProvider returns empty non-nil map[string][]TopologyHint from provider",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource": {},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(numaNodes...),
Preferred: false,
},
},
{
name: "Single TopologyHint with Preferred as true and NUMANodeAffinity as nil",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource": {
{
NUMANodeAffinity: nil,
Preferred: true,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(numaNodes...),
Preferred: true,
},
},
{
name: "Single TopologyHint with Preferred as false and NUMANodeAffinity as nil",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource": {
{
NUMANodeAffinity: nil,
Preferred: false,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(numaNodes...),
Preferred: false,
},
},
{
name: "Two providers, 1 hint each, same mask, both preferred 1/2",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource1": {
{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
},
},
},
&mockHintProvider{
map[string][]TopologyHint{
"resource2": {
{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
},
{
name: "Two providers, 1 hint each, same mask, both preferred 2/2",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource1": {
{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
},
},
},
&mockHintProvider{
map[string][]TopologyHint{
"resource2": {
{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
},
{
name: "Two providers, 1 hint each, no common mask",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource1": {
{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
},
},
},
&mockHintProvider{
map[string][]TopologyHint{
"resource2": {
{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(numaNodes...),
Preferred: false,
},
},
{
name: "Two providers, 1 hint each, same mask, 1 preferred, 1 not 1/2",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource1": {
{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
},
},
},
&mockHintProvider{
map[string][]TopologyHint{
"resource2": {
{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: false,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: false,
},
},
{
name: "Two providers, 1 hint each, same mask, 1 preferred, 1 not 2/2",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource1": {
{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
},
},
},
&mockHintProvider{
map[string][]TopologyHint{
"resource2": {
{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: false,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: false,
},
},
{
name: "Two providers, 1 no hints, 1 single hint preferred 1/2",
hp: []HintProvider{
&mockHintProvider{},
&mockHintProvider{
map[string][]TopologyHint{
"resource": {
{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
},
{
name: "Two providers, 1 no hints, 1 single hint preferred 2/2",
hp: []HintProvider{
&mockHintProvider{},
&mockHintProvider{
map[string][]TopologyHint{
"resource": {
{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
},
{
name: "Two providers, 1 with 2 hints, 1 with single hint matching 1/2",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource1": {
{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
},
},
},
&mockHintProvider{
map[string][]TopologyHint{
"resource2": {
{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
},
{
name: "Two providers, 1 with 2 hints, 1 with single hint matching 2/2",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource1": {
{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
},
},
},
&mockHintProvider{
map[string][]TopologyHint{
"resource2": {
{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
},
{
name: "Two providers, 1 with 2 hints, 1 with single non-preferred hint matching",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource1": {
{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
},
},
},
&mockHintProvider{
map[string][]TopologyHint{
"resource2": {
{
NUMANodeAffinity: NewTestBitMask(0, 1),
Preferred: false,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: false,
},
},
{
name: "Two providers, both with 2 hints, matching narrower preferred hint from both",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource1": {
{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
},
},
},
&mockHintProvider{
map[string][]TopologyHint{
"resource2": {
{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
{
NUMANodeAffinity: NewTestBitMask(0, 1),
Preferred: false,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
},
{
name: "Ensure less narrow preferred hints are chosen over narrower non-preferred hints",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource1": {
{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
{
NUMANodeAffinity: NewTestBitMask(0, 1),
Preferred: false,
},
},
},
},
&mockHintProvider{
map[string][]TopologyHint{
"resource2": {
{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
{
NUMANodeAffinity: NewTestBitMask(0, 1),
Preferred: false,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
},
{
name: "Multiple resources, same provider",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource1": {
{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
{
NUMANodeAffinity: NewTestBitMask(0, 1),
Preferred: false,
},
},
"resource2": {
{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
{
NUMANodeAffinity: NewTestBitMask(0, 1),
Preferred: false,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
},
}
}
func (p *bestEffortPolicy) mergeTestCases(numaNodes []int) []policyMergeTestCase {
return []policyMergeTestCase{
{
name: "Two providers, 1 hint each, 1 wider mask, both preferred 1/2",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource1": {
{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
},
},
},
&mockHintProvider{
map[string][]TopologyHint{
"resource2": {
{
NUMANodeAffinity: NewTestBitMask(0, 1),
Preferred: true,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
},
{
name: "Two providers, 1 hint each, 1 wider mask, both preferred 1/2",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource1": {
{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
},
},
},
&mockHintProvider{
map[string][]TopologyHint{
"resource2": {
{
NUMANodeAffinity: NewTestBitMask(0, 1),
Preferred: true,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
},
}
}
func (p *singleNumaNodePolicy) mergeTestCases(numaNodes []int) []policyMergeTestCase {
return []policyMergeTestCase{
{
name: "Single NUMA hint generation",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource1": {
{
NUMANodeAffinity: NewTestBitMask(0, 1),
Preferred: true,
},
},
"resource2": {
{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
{
NUMANodeAffinity: NewTestBitMask(0, 1),
Preferred: false,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: false,
},
},
{
name: "One no-preference provider",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource1": {
{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
{
NUMANodeAffinity: NewTestBitMask(0, 1),
Preferred: false,
},
},
},
},
&mockHintProvider{
nil,
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
},
}
}
func testPolicyMerge(policy Policy, tcases []policyMergeTestCase, t *testing.T) {
for _, tc := range tcases {
var providersHints []map[string][]TopologyHint
for _, provider := range tc.hp {
hints := provider.GetTopologyHints(v1.Pod{}, v1.Container{})
providersHints = append(providersHints, hints)
}
actual, _ := policy.Merge(providersHints)
if !actual.NUMANodeAffinity.IsEqual(tc.expected.NUMANodeAffinity) {
t.Errorf("%v: Expected NUMANodeAffinity in result to be %v, got %v", tc.name, tc.expected.NUMANodeAffinity, actual.NUMANodeAffinity)
}
if actual.Preferred != tc.expected.Preferred {
t.Errorf("%v: Expected Affinity preference in result to be %v, got %v", tc.name, tc.expected.Preferred, actual.Preferred)
}
}
}

View File

@ -63,8 +63,6 @@ type manager struct {
podMap map[string]string podMap map[string]string
//Topology Manager Policy //Topology Manager Policy
policy Policy policy Policy
//List of NUMA Nodes available on the underlying machine
numaNodes []int
} }
// HintProvider is an interface for components that want to collaborate to // HintProvider is an interface for components that want to collaborate to
@ -94,30 +92,32 @@ type TopologyHint struct {
Preferred bool Preferred bool
} }
// IsEqual checks if TopologyHint are equal
func (th *TopologyHint) IsEqual(topologyHint TopologyHint) bool {
if th.Preferred == topologyHint.Preferred {
if th.NUMANodeAffinity == nil || topologyHint.NUMANodeAffinity == nil {
return th.NUMANodeAffinity == topologyHint.NUMANodeAffinity
}
return th.NUMANodeAffinity.IsEqual(topologyHint.NUMANodeAffinity)
}
return false
}
// LessThan checks if TopologyHint `a` is less than TopologyHint `b`
// this means that either `a` is a preferred hint and `b` is not
// or `a` NUMANodeAffinity attribute is narrower than `b` NUMANodeAffinity attribute.
func (th *TopologyHint) LessThan(other TopologyHint) bool {
if th.Preferred != other.Preferred {
return th.Preferred == true
}
return th.NUMANodeAffinity.IsNarrowerThan(other.NUMANodeAffinity)
}
var _ Manager = &manager{} var _ Manager = &manager{}
//NewManager creates a new TopologyManager based on provided policy //NewManager creates a new TopologyManager based on provided policy
func NewManager(numaNodeInfo cputopology.NUMANodeInfo, topologyPolicyName string) (Manager, error) { func NewManager(numaNodeInfo cputopology.NUMANodeInfo, topologyPolicyName string) (Manager, error) {
klog.Infof("[topologymanager] Creating topology manager with %s policy", topologyPolicyName) klog.Infof("[topologymanager] Creating topology manager with %s policy", topologyPolicyName)
var policy Policy
switch topologyPolicyName {
case PolicyNone:
policy = NewNonePolicy()
case PolicyBestEffort:
policy = NewBestEffortPolicy()
case PolicyRestricted:
policy = NewRestrictedPolicy()
case PolicySingleNumaNode:
policy = NewSingleNumaNodePolicy()
default:
return nil, fmt.Errorf("unknown policy: \"%s\"", topologyPolicyName)
}
var numaNodes []int var numaNodes []int
for node := range numaNodeInfo { for node := range numaNodeInfo {
@ -128,6 +128,25 @@ func NewManager(numaNodeInfo cputopology.NUMANodeInfo, topologyPolicyName string
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)
} }
var policy Policy
switch topologyPolicyName {
case PolicyNone:
policy = NewNonePolicy()
case PolicyBestEffort:
policy = NewBestEffortPolicy(numaNodes)
case PolicyRestricted:
policy = NewRestrictedPolicy(numaNodes)
case PolicySingleNumaNode:
policy = NewSingleNumaNodePolicy(numaNodes)
default:
return nil, fmt.Errorf("unknown policy: \"%s\"", topologyPolicyName)
}
var hp []HintProvider var hp []HintProvider
pth := make(map[string]map[string]TopologyHint) pth := make(map[string]map[string]TopologyHint)
pm := make(map[string]string) pm := make(map[string]string)
@ -136,7 +155,6 @@ func NewManager(numaNodeInfo cputopology.NUMANodeInfo, topologyPolicyName string
podTopologyHints: pth, podTopologyHints: pth,
podMap: pm, podMap: pm,
policy: policy, policy: policy,
numaNodes: numaNodes,
} }
return manager, nil return manager, nil
@ -146,154 +164,24 @@ func (m *manager) GetAffinity(podUID string, containerName string) TopologyHint
return m.podTopologyHints[podUID][containerName] return m.podTopologyHints[podUID][containerName]
} }
// Iterate over all permutations of hints in 'allProviderHints [][]TopologyHint'. func (m *manager) accumulateProvidersHints(pod v1.Pod, container v1.Container) (providersHints []map[string][]TopologyHint) {
//
// This procedure is implemented as a recursive function over the set of hints
// in 'allproviderHints[i]'. It applies the function 'callback' to each
// permutation as it is found. It is the equivalent of:
//
// for i := 0; i < len(providerHints[0]); i++
// for j := 0; j < len(providerHints[1]); j++
// for k := 0; k < len(providerHints[2]); k++
// ...
// for z := 0; z < len(providerHints[-1]); z++
// permutation := []TopologyHint{
// providerHints[0][i],
// providerHints[1][j],
// providerHints[2][k],
// ...
// provideryHints[-1][z]
// }
// callback(permutation)
func (m *manager) iterateAllProviderTopologyHints(allProviderHints [][]TopologyHint, callback func([]TopologyHint)) {
// Internal helper function to accumulate the permutation before calling the callback.
var iterate func(i int, accum []TopologyHint)
iterate = func(i int, accum []TopologyHint) {
// Base case: we have looped through all providers and have a full permutation.
if i == len(allProviderHints) {
callback(accum)
return
}
// Loop through all hints for provider 'i', and recurse to build the
// the permutation of this hint with all hints from providers 'i++'.
for j := range allProviderHints[i] {
iterate(i+1, append(accum, allProviderHints[i][j]))
}
}
iterate(0, []TopologyHint{})
}
// Merge the hints from all hint providers to find the best one.
func (m *manager) calculateAffinity(pod v1.Pod, container v1.Container) TopologyHint {
// Set the default affinity as an any-numa affinity containing the list
// of NUMA Nodes available on this machine.
defaultAffinity, _ := bitmask.NewBitMask(m.numaNodes...)
// Loop through all hint providers and save an accumulated list of the // Loop through all hint providers and save an accumulated list of the
// hints returned by each hint provider. If no hints are provided, assume // hints returned by each hint provider.
// that provider has no preference for topology-aware allocation.
var allProviderHints [][]TopologyHint
for _, provider := range m.hintProviders { for _, provider := range m.hintProviders {
// Get the TopologyHints from a provider. // Get the TopologyHints from a provider.
hints := provider.GetTopologyHints(pod, container) hints := provider.GetTopologyHints(pod, container)
providersHints = append(providersHints, hints)
// If hints is nil, insert a single, preferred any-numa hint into allProviderHints. klog.Infof("[topologymanager] TopologyHints for pod '%v', container '%v': %v", pod.Name, container.Name, hints)
if len(hints) == 0 {
klog.Infof("[topologymanager] Hint Provider has no preference for NUMA affinity with any resource")
allProviderHints = append(allProviderHints, []TopologyHint{{nil, true}})
continue
} }
return providersHints
}
// Otherwise, accumulate the hints for each resource type into allProviderHints. // Collect Hints from hint providers and pass to policy to retrieve the best one.
for resource := range hints { func (m *manager) calculateAffinity(pod v1.Pod, container v1.Container) (TopologyHint, lifecycle.PodAdmitResult) {
if hints[resource] == nil { providersHints := m.accumulateProvidersHints(pod, container)
klog.Infof("[topologymanager] Hint Provider has no preference for NUMA affinity with resource '%s'", resource) bestHint, admit := m.policy.Merge(providersHints)
allProviderHints = append(allProviderHints, []TopologyHint{{nil, true}})
continue
}
if len(hints[resource]) == 0 {
klog.Infof("[topologymanager] Hint Provider has no possible NUMA affinities for resource '%s'", resource)
allProviderHints = append(allProviderHints, []TopologyHint{{nil, false}})
continue
}
allProviderHints = append(allProviderHints, hints[resource])
}
}
// Iterate over all permutations of hints in 'allProviderHints'. Merge the
// hints in each permutation by taking the bitwise-and of their affinity masks.
// Return the hint with the narrowest NUMANodeAffinity of all merged
// permutations that have at least one NUMA ID set. If no merged mask can be
// found that has at least one NUMA ID set, return the 'defaultAffinity'.
bestHint := TopologyHint{defaultAffinity, false}
m.iterateAllProviderTopologyHints(allProviderHints, func(permutation []TopologyHint) {
// Get the NUMANodeAffinity from each hint in the permutation and see if any
// of them encode unpreferred allocations.
preferred := true
var numaAffinities []bitmask.BitMask
for _, hint := range permutation {
if hint.NUMANodeAffinity == nil {
numaAffinities = append(numaAffinities, defaultAffinity)
} else {
numaAffinities = append(numaAffinities, hint.NUMANodeAffinity)
}
if !hint.Preferred {
preferred = false
}
// Special case PolicySingleNumaNode to only prefer hints where
// all providers have a single NUMA affinity set.
if m.policy != nil && m.policy.Name() == PolicySingleNumaNode && hint.NUMANodeAffinity != nil && hint.NUMANodeAffinity.Count() > 1 {
preferred = false
}
}
// Merge the affinities using a bitwise-and operation.
mergedAffinity, _ := bitmask.NewBitMask(m.numaNodes...)
mergedAffinity.And(numaAffinities...)
// Build a mergedHintfrom the merged affinity mask, indicating if an
// preferred allocation was used to generate the affinity mask or not.
mergedHint := TopologyHint{mergedAffinity, preferred}
// Only consider mergedHints that result in a NUMANodeAffinity > 0 to
// replace the current bestHint.
if mergedHint.NUMANodeAffinity.Count() == 0 {
return
}
// If the current bestHint is non-preferred and the new mergedHint is
// preferred, always choose the preferred hint over the non-preferred one.
if mergedHint.Preferred && !bestHint.Preferred {
bestHint = mergedHint
return
}
// If the current bestHint is preferred and the new mergedHint is
// non-preferred, never update bestHint, regardless of mergedHint's
// narowness.
if !mergedHint.Preferred && bestHint.Preferred {
return
}
// If mergedHint and bestHint has the same preference, only consider
// mergedHints that have a narrower NUMANodeAffinity than the
// NUMANodeAffinity in the current bestHint.
if !mergedHint.NUMANodeAffinity.IsNarrowerThan(bestHint.NUMANodeAffinity) {
return
}
// In all other cases, update bestHint to the current mergedHint
bestHint = mergedHint
})
klog.Infof("[topologymanager] ContainerTopologyHint: %v", bestHint) klog.Infof("[topologymanager] ContainerTopologyHint: %v", bestHint)
return bestHint, admit
return bestHint
} }
func (m *manager) AddHintProvider(h HintProvider) { func (m *manager) AddHintProvider(h HintProvider) {
@ -325,8 +213,7 @@ func (m *manager) Admit(attrs *lifecycle.PodAdmitAttributes) lifecycle.PodAdmitR
c := make(map[string]TopologyHint) c := make(map[string]TopologyHint)
for _, container := range append(pod.Spec.InitContainers, pod.Spec.Containers...) { for _, container := range append(pod.Spec.InitContainers, pod.Spec.Containers...) {
result := m.calculateAffinity(*pod, container) result, admitPod := m.calculateAffinity(*pod, container)
admitPod := m.policy.CanAdmitPodResult(&result)
if !admitPod.Admit { if !admitPod.Admit {
return admitPod return admitPod
} }

View File

@ -104,641 +104,6 @@ func TestGetAffinity(t *testing.T) {
} }
} }
func TestCalculateAffinity(t *testing.T) {
numaNodes := []int{0, 1}
tcases := []struct {
name string
hp []HintProvider
expected TopologyHint
policy Policy
}{
{
name: "TopologyHint not set",
hp: []HintProvider{},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(numaNodes...),
Preferred: true,
},
},
{
name: "HintProvider returns empty non-nil map[string][]TopologyHint",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(numaNodes...),
Preferred: true,
},
},
{
name: "HintProvider returns -nil map[string][]TopologyHint from provider",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource": nil,
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(numaNodes...),
Preferred: true,
},
},
{
name: "HintProvider returns empty non-nil map[string][]TopologyHint from provider",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource": {},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(numaNodes...),
Preferred: false,
},
},
{
name: "Single TopologyHint with Preferred as true and NUMANodeAffinity as nil",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource": {
{
NUMANodeAffinity: nil,
Preferred: true,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(numaNodes...),
Preferred: true,
},
},
{
name: "Single TopologyHint with Preferred as false and NUMANodeAffinity as nil",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource": {
{
NUMANodeAffinity: nil,
Preferred: false,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(numaNodes...),
Preferred: false,
},
},
{
name: "Two providers, 1 hint each, same mask, both preferred 1/2",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource1": {
{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
},
},
},
&mockHintProvider{
map[string][]TopologyHint{
"resource2": {
{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
},
{
name: "Two providers, 1 hint each, same mask, both preferred 2/2",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource1": {
{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
},
},
},
&mockHintProvider{
map[string][]TopologyHint{
"resource2": {
{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
},
{
name: "Two providers, 1 hint each, 1 wider mask, both preferred 1/2",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource1": {
{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
},
},
},
&mockHintProvider{
map[string][]TopologyHint{
"resource2": {
{
NUMANodeAffinity: NewTestBitMask(0, 1),
Preferred: true,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
},
{
name: "Two providers, 1 hint each, 1 wider mask, both preferred 1/2",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource1": {
{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
},
},
},
&mockHintProvider{
map[string][]TopologyHint{
"resource2": {
{
NUMANodeAffinity: NewTestBitMask(0, 1),
Preferred: true,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
},
{
name: "Two providers, 1 hint each, no common mask",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource1": {
{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
},
},
},
&mockHintProvider{
map[string][]TopologyHint{
"resource2": {
{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(numaNodes...),
Preferred: false,
},
},
{
name: "Two providers, 1 hint each, same mask, 1 preferred, 1 not 1/2",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource1": {
{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
},
},
},
&mockHintProvider{
map[string][]TopologyHint{
"resource2": {
{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: false,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: false,
},
},
{
name: "Two providers, 1 hint each, same mask, 1 preferred, 1 not 2/2",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource1": {
{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
},
},
},
&mockHintProvider{
map[string][]TopologyHint{
"resource2": {
{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: false,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: false,
},
},
{
name: "Two providers, 1 no hints, 1 single hint preferred 1/2",
hp: []HintProvider{
&mockHintProvider{},
&mockHintProvider{
map[string][]TopologyHint{
"resource": {
{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
},
{
name: "Two providers, 1 no hints, 1 single hint preferred 2/2",
hp: []HintProvider{
&mockHintProvider{},
&mockHintProvider{
map[string][]TopologyHint{
"resource": {
{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
},
{
name: "Two providers, 1 with 2 hints, 1 with single hint matching 1/2",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource1": {
{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
},
},
},
&mockHintProvider{
map[string][]TopologyHint{
"resource2": {
{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
},
{
name: "Two providers, 1 with 2 hints, 1 with single hint matching 2/2",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource1": {
{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
},
},
},
&mockHintProvider{
map[string][]TopologyHint{
"resource2": {
{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
},
{
name: "Two providers, 1 with 2 hints, 1 with single non-preferred hint matching",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource1": {
{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
},
},
},
&mockHintProvider{
map[string][]TopologyHint{
"resource2": {
{
NUMANodeAffinity: NewTestBitMask(0, 1),
Preferred: false,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: false,
},
},
{
name: "Two providers, both with 2 hints, matching narrower preferred hint from both",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource1": {
{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
},
},
},
&mockHintProvider{
map[string][]TopologyHint{
"resource2": {
{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
{
NUMANodeAffinity: NewTestBitMask(0, 1),
Preferred: false,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
},
{
name: "Ensure less narrow preferred hints are chosen over narrower non-preferred hints",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource1": {
{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
{
NUMANodeAffinity: NewTestBitMask(0, 1),
Preferred: false,
},
},
},
},
&mockHintProvider{
map[string][]TopologyHint{
"resource2": {
{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
{
NUMANodeAffinity: NewTestBitMask(0, 1),
Preferred: false,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
},
{
name: "Multiple resources, same provider",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource1": {
{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
{
NUMANodeAffinity: NewTestBitMask(0, 1),
Preferred: false,
},
},
"resource2": {
{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
{
NUMANodeAffinity: NewTestBitMask(0, 1),
Preferred: false,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
},
{
name: "Special cased PolicySingleNumaNode for single NUMA hint generation",
policy: NewSingleNumaNodePolicy(),
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource1": {
{
NUMANodeAffinity: NewTestBitMask(0, 1),
Preferred: true,
},
},
"resource2": {
{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
{
NUMANodeAffinity: NewTestBitMask(0, 1),
Preferred: false,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: false,
},
},
{
name: "Special cased PolicySingleNumaNode with one no-preference provider",
policy: NewSingleNumaNodePolicy(),
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource1": {
{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
{
NUMANodeAffinity: NewTestBitMask(1),
Preferred: true,
},
{
NUMANodeAffinity: NewTestBitMask(0, 1),
Preferred: false,
},
},
},
},
&mockHintProvider{
nil,
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(0),
Preferred: true,
},
},
}
for _, tc := range tcases {
mngr := manager{
policy: tc.policy,
hintProviders: tc.hp,
numaNodes: numaNodes,
}
actual := mngr.calculateAffinity(v1.Pod{}, v1.Container{})
if !actual.NUMANodeAffinity.IsEqual(tc.expected.NUMANodeAffinity) {
t.Errorf("Expected NUMANodeAffinity in result to be %v, got %v", tc.expected.NUMANodeAffinity, actual.NUMANodeAffinity)
}
if actual.Preferred != tc.expected.Preferred {
t.Errorf("Expected Affinity preference in result to be %v, got %v", tc.expected.Preferred, actual.Preferred)
}
}
}
func TestAddContainer(t *testing.T) { func TestAddContainer(t *testing.T) {
testCases := []struct { testCases := []struct {
name string name string
@ -835,6 +200,8 @@ func TestAddHintProvider(t *testing.T) {
} }
func TestAdmit(t *testing.T) { func TestAdmit(t *testing.T) {
numaNodes := []int{0, 1}
tcases := []struct { tcases := []struct {
name string name string
result lifecycle.PodAdmitResult result lifecycle.PodAdmitResult
@ -860,7 +227,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(), policy: NewRestrictedPolicy(numaNodes),
hp: []HintProvider{ hp: []HintProvider{
&mockHintProvider{}, &mockHintProvider{},
}, },
@ -869,7 +236,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(), policy: NewRestrictedPolicy(numaNodes),
hp: []HintProvider{ hp: []HintProvider{
&mockHintProvider{}, &mockHintProvider{},
}, },
@ -878,7 +245,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(), policy: NewBestEffortPolicy(numaNodes),
hp: []HintProvider{ hp: []HintProvider{
&mockHintProvider{ &mockHintProvider{
map[string][]TopologyHint{ map[string][]TopologyHint{
@ -900,7 +267,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(), policy: NewBestEffortPolicy(numaNodes),
hp: []HintProvider{ hp: []HintProvider{
&mockHintProvider{ &mockHintProvider{
map[string][]TopologyHint{ map[string][]TopologyHint{
@ -926,7 +293,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(), policy: NewBestEffortPolicy(numaNodes),
hp: []HintProvider{ hp: []HintProvider{
&mockHintProvider{ &mockHintProvider{
map[string][]TopologyHint{ map[string][]TopologyHint{
@ -952,7 +319,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(), policy: NewBestEffortPolicy(numaNodes),
hp: []HintProvider{ hp: []HintProvider{
&mockHintProvider{ &mockHintProvider{
map[string][]TopologyHint{ map[string][]TopologyHint{
@ -970,7 +337,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(), policy: NewRestrictedPolicy(numaNodes),
hp: []HintProvider{ hp: []HintProvider{
&mockHintProvider{ &mockHintProvider{
map[string][]TopologyHint{ map[string][]TopologyHint{
@ -992,7 +359,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(), policy: NewRestrictedPolicy(numaNodes),
hp: []HintProvider{ hp: []HintProvider{
&mockHintProvider{ &mockHintProvider{
map[string][]TopologyHint{ map[string][]TopologyHint{
@ -1014,7 +381,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(), policy: NewRestrictedPolicy(numaNodes),
hp: []HintProvider{ hp: []HintProvider{
&mockHintProvider{ &mockHintProvider{
map[string][]TopologyHint{ map[string][]TopologyHint{
@ -1040,7 +407,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(), policy: NewRestrictedPolicy(numaNodes),
hp: []HintProvider{ hp: []HintProvider{
&mockHintProvider{ &mockHintProvider{
map[string][]TopologyHint{ map[string][]TopologyHint{
@ -1066,7 +433,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(), policy: NewRestrictedPolicy(numaNodes),
hp: []HintProvider{ hp: []HintProvider{
&mockHintProvider{ &mockHintProvider{
map[string][]TopologyHint{ map[string][]TopologyHint{
@ -1084,7 +451,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(), policy: NewRestrictedPolicy(numaNodes),
hp: []HintProvider{ hp: []HintProvider{
&mockHintProvider{ &mockHintProvider{
map[string][]TopologyHint{ map[string][]TopologyHint{
@ -1105,7 +472,6 @@ func TestAdmit(t *testing.T) {
policy: tc.policy, policy: tc.policy,
podTopologyHints: make(map[string]map[string]TopologyHint), podTopologyHints: make(map[string]map[string]TopologyHint),
hintProviders: tc.hp, hintProviders: tc.hp,
numaNodes: []int{0, 1},
} }
pod := &v1.Pod{ pod := &v1.Pod{