kubernetes/pkg/kubelet/cm/topologymanager/policy_test.go
Kevin Klues 155562dd2e Fix bug in TopologyManager with merging hints when NUM_NUMA > 2
Before this fix, hint permutations such as:

	permutation: [{11 true} {0101 true}]

Could result in merged hints of:

	mergedHint: {01 true}

This was possible because both hints in the permutation container a "preferred"
allocation (i.e. the full set of NUMA nodes set in the affinity bitmask are
*required* to satisfy the allocation). With this in place, the simplified logic
we had simply kept the merged hint as preferred as well.

However, what we really want is to ensure that the merged hint is only
preferred if *true* alignment of all resources is possible (i.e. if all hints
in the permutation are preferred AND their affinities are exactly equal).

The only exception to this is if *no* topology information is provided by a
given hint provider. In this case, we assume alignment doesn't matter and only
consider the resources that actually have hints provided for them.

This changes the semantics of permutations of the form:

	permutation: [{111 true} {011 true}]

To now result in the merged hint of:

	mergedHint: {011 false}

Instead of:

	mergedHint: {011 true}

This is arguably how it should always have been though (because a hint should
not be preferred if true alignment isn't possible), and two tests have had to
change to accomodate these new semantics.

This commit changes the merge function to implement the updated logic, adds a
test to verify it is functioning correctly, and updates the two tests mentioned
above to adjust to the new semantics.

Signed-off-by: Kevin Klues <kklues@nvidia.com>
2022-02-10 22:07:51 +00:00

908 lines
19 KiB
Go

/*
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 (
"reflect"
"testing"
"k8s.io/api/core/v1"
)
type policyMergeTestCase struct {
name string
hp []HintProvider
expected TopologyHint
}
func commonPolicyMergeTestCases(numaNodes []int) []policyMergeTestCase {
return []policyMergeTestCase{
{
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 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, 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, 2 hints each, same mask (some with different bits), same preferred",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource1": {
{
NUMANodeAffinity: NewTestBitMask(0, 1),
Preferred: true,
},
{
NUMANodeAffinity: NewTestBitMask(0, 2),
Preferred: true,
},
},
},
},
&mockHintProvider{
map[string][]TopologyHint{
"resource2": {
{
NUMANodeAffinity: NewTestBitMask(0, 1),
Preferred: true,
},
{
NUMANodeAffinity: NewTestBitMask(0, 2),
Preferred: true,
},
},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: NewTestBitMask(0, 1),
Preferred: true,
},
},
{
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, 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 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: false,
},
},
{
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, 1 hint each, 1 wider mask, both preferred 2/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: false,
},
},
}
}
func (p *singleNumaNodePolicy) mergeTestCases(numaNodes []int) []policyMergeTestCase {
return []policyMergeTestCase{
{
name: "TopologyHint not set",
hp: []HintProvider{},
expected: TopologyHint{
NUMANodeAffinity: nil,
Preferred: true,
},
},
{
name: "HintProvider returns empty non-nil map[string][]TopologyHint",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{},
},
},
expected: TopologyHint{
NUMANodeAffinity: nil,
Preferred: true,
},
},
{
name: "HintProvider returns -nil map[string][]TopologyHint from provider",
hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource": nil,
},
},
},
expected: TopologyHint{
NUMANodeAffinity: nil,
Preferred: true,
},
},
{
name: "HintProvider returns empty non-nil map[string][]TopologyHint from provider", hp: []HintProvider{
&mockHintProvider{
map[string][]TopologyHint{
"resource": {},
},
},
},
expected: TopologyHint{
NUMANodeAffinity: nil,
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: nil,
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: nil,
Preferred: false,
},
},
{
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: nil,
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: nil,
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: nil,
Preferred: false,
},
},
{
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: nil,
Preferred: false,
},
},
{
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: nil,
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 !reflect.DeepEqual(actual, tc.expected) {
t.Errorf("%v: Expected Topology Hint to be %v, got %v:", tc.name, tc.expected, actual)
}
}
}