Skip to content

Commit

Permalink
scheduler: expose reasons when no possible numa hint
Browse files Browse the repository at this point in the history
Signed-off-by: wangjianyu.wjy <[email protected]>
  • Loading branch information
wangjianyu.wjy committed Dec 25, 2024
1 parent a62dd49 commit ca6ce83
Show file tree
Hide file tree
Showing 8 changed files with 67 additions and 36 deletions.
24 changes: 14 additions & 10 deletions pkg/scheduler/frameworkext/topologymanager/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ import (
"github.com/koordinator-sh/koordinator/pkg/scheduler/frameworkext/schedulingphase"
)

const (
ErrUnsatisfiedNUMAResource = "Unsatisfied NUMA %s"
)

type Interface interface {
Admit(ctx context.Context, cycleState *framework.CycleState, pod *corev1.Pod, nodeName string, numaNodes []int, policyType apiext.NUMATopologyPolicy, exclusivePolicy apiext.NumaTopologyExclusive, allNUMANodeStatus []apiext.NumaNodeStatus) *framework.Status
}
Expand Down Expand Up @@ -68,13 +72,13 @@ func (m *topologyManager) Admit(ctx context.Context, cycleState *framework.Cycle
extensionPointBeingExecuted := schedulingphase.GetExtensionPointBeingExecuted(cycleState)
klog.V(5).Infof("extensionPointBeingExecuted: %v, bestHint: %v, nodeName: %v, pod: %v", extensionPointBeingExecuted, bestHint, nodeName, pod.Name)
if !ok || extensionPointBeingExecuted == schedulingphase.PostFilter {
var admit bool
bestHint, admit = m.calculateAffinity(ctx, cycleState, policy, pod, nodeName, exclusivePolicy, allNUMANodeStatus)
klog.V(4).Infof("Best TopologyHint for (pod: %v): %+v on node %s, policy %s, exclusivePolicy %s, admit %v",
klog.KObj(pod), bestHint, nodeName, policy, exclusivePolicy, admit)
bestHint, admit, reasons := m.calculateAffinity(ctx, cycleState, policy, pod, nodeName, exclusivePolicy, allNUMANodeStatus)
klog.V(4).Infof("Best TopologyHint for (pod: %v): %+v on node %s, policy %s, exclusivePolicy %s, admit %v, reasons %v",
klog.KObj(pod), bestHint, nodeName, policy, exclusivePolicy, admit, reasons)
if !admit {
return framework.NewStatus(framework.Unschedulable, "node(s) NUMA Topology affinity error")
return framework.NewStatus(framework.Unschedulable, reasons...)
}
// TODO 如果上面的 Affinity 确认可以分配出来,这里看起来没必要再调用了
status := m.allocateResources(ctx, cycleState, bestHint, pod, nodeName)
if !status.IsSuccess() {
return status
Expand All @@ -89,15 +93,15 @@ func (m *topologyManager) Admit(ctx context.Context, cycleState *framework.Cycle
return nil
}

func (m *topologyManager) calculateAffinity(ctx context.Context, cycleState *framework.CycleState, policy Policy, pod *corev1.Pod, nodeName string, exclusivePolicy apiext.NumaTopologyExclusive, allNUMANodeStatus []apiext.NumaNodeStatus) (NUMATopologyHint, bool) {
func (m *topologyManager) calculateAffinity(ctx context.Context, cycleState *framework.CycleState, policy Policy, pod *corev1.Pod, nodeName string, exclusivePolicy apiext.NumaTopologyExclusive, allNUMANodeStatus []apiext.NumaNodeStatus) (NUMATopologyHint, bool, []string) {
providersHints := m.accumulateProvidersHints(ctx, cycleState, pod, nodeName)
bestHint, admit := policy.Merge(providersHints, exclusivePolicy, allNUMANodeStatus)
bestHint, admit, reasons := policy.Merge(providersHints, exclusivePolicy, allNUMANodeStatus)
if !checkExclusivePolicy(bestHint, exclusivePolicy, allNUMANodeStatus) {
klog.V(5).Infof("bestHint violated the exclusivePolicy requirement: bestHint: %v, policy: %v, numaStatus: %v, nodeName: %v, pod: %v",
bestHint, exclusivePolicy, allNUMANodeStatus, nodeName, pod.Name)
}
klog.V(5).Infof("PodTopologyHint: %v", bestHint)
return bestHint, admit
return bestHint, admit, reasons
}

func (m *topologyManager) accumulateProvidersHints(ctx context.Context, cycleState *framework.CycleState, pod *corev1.Pod, nodeName string) []map[string][]NUMATopologyHint {
Expand All @@ -106,9 +110,9 @@ func (m *topologyManager) accumulateProvidersHints(ctx context.Context, cycleSta
hintProviders := m.hintProviderFactory.GetNUMATopologyHintProvider()
for _, provider := range hintProviders {
// Get the TopologyHints for a Pod from a provider.
hints, _ := provider.GetPodTopologyHints(ctx, cycleState, pod, nodeName)
hints, status := provider.GetPodTopologyHints(ctx, cycleState, pod, nodeName)
providersHints = append(providersHints, hints)
klog.V(4).Infof("TopologyHints for pod '%v' by provider %T: %v on node: %v", klog.KObj(pod), provider, hints, nodeName)
klog.V(4).Infof("TopologyHints for pod '%v' by provider %T: %v on node: %v, status: %s/%s", klog.KObj(pod), provider, hints, nodeName, status.Code, status.Message())
}
return providersHints
}
Expand Down
34 changes: 26 additions & 8 deletions pkg/scheduler/frameworkext/topologymanager/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ limitations under the License.
package topologymanager

import (
"fmt"

"k8s.io/klog/v2"

apiext "github.com/koordinator-sh/koordinator/apis/extension"
Expand All @@ -28,7 +30,7 @@ type Policy interface {
// Name returns Policy Name
Name() string
// Merge returns a merged NUMATopologyHint based on input from hint providers
Merge(providersHints []map[string][]NUMATopologyHint, exclusivePolicy apiext.NumaTopologyExclusive, allNUMANodeStatus []apiext.NumaNodeStatus) (NUMATopologyHint, bool)
Merge(providersHints []map[string][]NUMATopologyHint, exclusivePolicy apiext.NumaTopologyExclusive, allNUMANodeStatus []apiext.NumaNodeStatus) (NUMATopologyHint, bool, []string)
}

// NUMATopologyHint is a struct containing the NUMANodeAffinity for a Container
Expand Down Expand Up @@ -125,40 +127,53 @@ func mergePermutation(numaNodes []int, permutation []NUMATopologyHint) NUMATopol
satisfied = ((len(numaAffinities) == 0) || maxNUMANodeNum == mergedAffinity.Count()) && satisfied
// Build a mergedHint from the merged affinity mask, indicating if an
// preferred allocation was used to generate the affinity mask or not.
return NUMATopologyHint{mergedAffinity, !satisfied, preferred, 0}
return NUMATopologyHint{
NUMANodeAffinity: mergedAffinity,
Unsatisfied: !satisfied,
Preferred: preferred,
}
}

func filterProvidersHints(providersHints []map[string][]NUMATopologyHint) [][]NUMATopologyHint {
func filterProvidersHints(providersHints []map[string][]NUMATopologyHint) ([][]NUMATopologyHint, []string) {
// 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 [][]NUMATopologyHint
var reasons []string
for _, hints := range providersHints {
// If hints is nil, insert a single, preferred any-numa hint into allProviderHints.
if len(hints) == 0 {
klog.V(5).Infof("[topologymanager] Hint Provider has no preference for NUMA affinity with any resource")
allProviderHints = append(allProviderHints, []NUMATopologyHint{{nil, false, true, 0}})
allProviderHints = append(allProviderHints, []NUMATopologyHint{{
Preferred: true,
}})
continue
}

// Otherwise, accumulate the hints for each resource type into allProviderHints.
for resource := range hints {
if hints[resource] == nil {
klog.V(5).Infof("[topologymanager] Hint Provider has no preference for NUMA affinity with resource '%s'", resource)
allProviderHints = append(allProviderHints, []NUMATopologyHint{{nil, false, true, 0}})
allProviderHints = append(allProviderHints, []NUMATopologyHint{{
Preferred: true,
}})
continue
}

if len(hints[resource]) == 0 {
klog.V(5).Infof("[topologymanager] Hint Provider has no possible NUMA affinities for resource '%s'", resource)
allProviderHints = append(allProviderHints, []NUMATopologyHint{{nil, true, false, 0}})
allProviderHints = append(allProviderHints, []NUMATopologyHint{{
Unsatisfied: true,
Preferred: false,
}})
reasons = append(reasons, fmt.Sprintf(ErrUnsatisfiedNUMAResource, resource))
continue
}

allProviderHints = append(allProviderHints, hints[resource])
}
}
return allProviderHints
return allProviderHints, reasons
}

func mergeFilteredHints(numaNodes []int, filteredHints [][]NUMATopologyHint, exclusivePolicy apiext.NumaTopologyExclusive, allNUMANodeStatus []apiext.NumaNodeStatus) NUMATopologyHint {
Expand All @@ -169,11 +184,14 @@ func mergeFilteredHints(numaNodes []int, filteredHints [][]NUMATopologyHint, exc
// Set the bestHint to return from this function as {nil false}.
// This will only be returned if no better hint can be found when
// merging hints from each hint provider.
bestHint := NUMATopologyHint{defaultAffinity, false, false, 0}
bestHint := NUMATopologyHint{
NUMANodeAffinity: defaultAffinity,
}
iterateAllProviderTopologyHints(filteredHints, func(permutation []NUMATopologyHint) {
// Get the NUMANodeAffinity from each hint in the permutation and see if any
// of them encode unpreferred allocations.
mergedHint := mergePermutation(numaNodes, permutation)

// Only consider mergedHints that result in a NUMANodeAffinity > 0 to
// replace the current bestHint.
if mergedHint.NUMANodeAffinity.Count() == 0 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,16 @@ func (p *bestEffortPolicy) canAdmitPodResult(hint *NUMATopologyHint) bool {
return true
}

func (p *bestEffortPolicy) Merge(providersHints []map[string][]NUMATopologyHint, exclusivePolicy apiext.NumaTopologyExclusive, allNUMANodeStatus []apiext.NumaNodeStatus) (NUMATopologyHint, bool) {
filteredProvidersHints := filterProvidersHints(providersHints)
func (p *bestEffortPolicy) Merge(providersHints []map[string][]NUMATopologyHint, exclusivePolicy apiext.NumaTopologyExclusive, allNUMANodeStatus []apiext.NumaNodeStatus) (NUMATopologyHint, bool, []string) {
filteredProvidersHints, _ := filterProvidersHints(providersHints)
bestHint := mergeFilteredHints(p.numaNodes, filteredProvidersHints, exclusivePolicy, allNUMANodeStatus)
// 如果 bestHint 不是一个所有资源都可分的 numa affinity,则应该返回 bestHint 为亲和所有 NUMANode,即放弃任何 NUMA 倾向
if bestHint.Unsatisfied {
affinityAllNUMANodes, _ := bitmask.NewBitMask(p.numaNodes...)
bestHint = NUMATopologyHint{
NUMANodeAffinity: affinityAllNUMANodes,
Unsatisfied: false,
Preferred: bestHint.Preferred,
Score: 0,
}
}
admit := p.canAdmitPodResult(&bestHint)
return bestHint, admit
return bestHint, admit, nil
}
4 changes: 2 additions & 2 deletions pkg/scheduler/frameworkext/topologymanager/policy_none.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,6 @@ func (p *nonePolicy) canAdmitPodResult(hint *NUMATopologyHint) bool {
return true
}

func (p *nonePolicy) Merge(providersHints []map[string][]NUMATopologyHint, exclusivePolicy apiext.NumaTopologyExclusive, allNUMANodeStatus []apiext.NumaNodeStatus) (NUMATopologyHint, bool) {
return NUMATopologyHint{}, p.canAdmitPodResult(nil)
func (p *nonePolicy) Merge(providersHints []map[string][]NUMATopologyHint, exclusivePolicy apiext.NumaTopologyExclusive, allNUMANodeStatus []apiext.NumaNodeStatus) (NUMATopologyHint, bool, []string) {
return NUMATopologyHint{}, p.canAdmitPodResult(nil), nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func TestPolicyNoneMerge(t *testing.T) {

for _, tc := range tcases {
policy := NewNonePolicy()
result, admit := policy.Merge(tc.providersHints, apiext.NumaTopologyExclusivePreferred, []apiext.NumaNodeStatus{})
result, admit, _ := policy.Merge(tc.providersHints, apiext.NumaTopologyExclusivePreferred, []apiext.NumaNodeStatus{})
if !result.IsEqual(tc.expectedHint) || admit != tc.expectedAdmit {
t.Errorf("Test Case: %s: Expected merge hint to be %v, got %v", tc.name, tc.expectedHint, result)
}
Expand Down
15 changes: 11 additions & 4 deletions pkg/scheduler/frameworkext/topologymanager/policy_restricted.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ limitations under the License.

package topologymanager

import apiext "github.com/koordinator-sh/koordinator/apis/extension"
import (
apiext "github.com/koordinator-sh/koordinator/apis/extension"
"github.com/koordinator-sh/koordinator/pkg/util/bitmask"
)

type restrictedPolicy struct {
bestEffortPolicy
Expand All @@ -41,9 +44,13 @@ func (p *restrictedPolicy) canAdmitPodResult(hint *NUMATopologyHint) bool {
return hint.Preferred
}

func (p *restrictedPolicy) Merge(providersHints []map[string][]NUMATopologyHint, exclusivePolicy apiext.NumaTopologyExclusive, allNUMANodeStatus []apiext.NumaNodeStatus) (NUMATopologyHint, bool) {
filteredHints := filterProvidersHints(providersHints)
func (p *restrictedPolicy) Merge(providersHints []map[string][]NUMATopologyHint, exclusivePolicy apiext.NumaTopologyExclusive, allNUMANodeStatus []apiext.NumaNodeStatus) (NUMATopologyHint, bool, []string) {
filteredHints, reasons := filterProvidersHints(providersHints)
if len(reasons) != 0 {
affinityAllNUMANodes, _ := bitmask.NewBitMask(p.numaNodes...)
return NUMATopologyHint{NUMANodeAffinity: affinityAllNUMANodes}, false, reasons
}
hint := mergeFilteredHints(p.numaNodes, filteredHints, exclusivePolicy, allNUMANodeStatus)
admit := p.canAdmitPodResult(&hint)
return hint, admit
return hint, admit, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,22 @@ func filterSingleNumaHints(allResourcesHints [][]NUMATopologyHint) [][]NUMATopol
return filteredResourcesHints
}

func (p *singleNumaNodePolicy) Merge(providersHints []map[string][]NUMATopologyHint, exclusivePolicy apiext.NumaTopologyExclusive, allNUMANodeStatus []apiext.NumaNodeStatus) (NUMATopologyHint, bool) {
filteredHints := filterProvidersHints(providersHints)
func (p *singleNumaNodePolicy) Merge(providersHints []map[string][]NUMATopologyHint, exclusivePolicy apiext.NumaTopologyExclusive, allNUMANodeStatus []apiext.NumaNodeStatus) (NUMATopologyHint, bool, []string) {
filteredHints, reasons := filterProvidersHints(providersHints)
if len(reasons) != 0 {
return NUMATopologyHint{}, false, reasons
}
// Filter to only include don't care and hints with a single NUMA node.
singleNumaHints := filterSingleNumaHints(filteredHints)
bestHint := mergeFilteredHints(p.numaNodes, singleNumaHints, exclusivePolicy, allNUMANodeStatus)

defaultAffinity, _ := bitmask.NewBitMask(p.numaNodes...)
if bestHint.NUMANodeAffinity.IsEqual(defaultAffinity) {
bestHint = NUMATopologyHint{nil, false, bestHint.Preferred, 0}
bestHint = NUMATopologyHint{
Preferred: bestHint.Preferred,
}
}

admit := p.canAdmitPodResult(&bestHint)
return bestHint, admit
return bestHint, admit, nil
}
2 changes: 1 addition & 1 deletion pkg/scheduler/frameworkext/topologymanager/policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1238,7 +1238,7 @@ func testPolicyMerge(policy Policy, tcases []policyMergeTestCase, t *testing.T)
providersHints = append(providersHints, hints)
}

actual, _ := policy.Merge(providersHints, extension.NumaTopologyExclusivePreferred, []extension.NumaNodeStatus{})
actual, _, _ := policy.Merge(providersHints, extension.NumaTopologyExclusivePreferred, []extension.NumaNodeStatus{})
if !reflect.DeepEqual(actual, tc.expected) {
t.Errorf("%v: Expected Topology Hint to be %v, got %v:", tc.name, tc.expected, actual)
}
Expand Down

0 comments on commit ca6ce83

Please sign in to comment.