diff --git a/pkg/scheduler/framework/plugins/podtopologyspread/common.go b/pkg/scheduler/framework/plugins/podtopologyspread/common.go index 6f5898fe4e3..5896aad65ff 100644 --- a/pkg/scheduler/framework/plugins/podtopologyspread/common.go +++ b/pkg/scheduler/framework/plugins/podtopologyspread/common.go @@ -27,11 +27,6 @@ import ( "k8s.io/utils/ptr" ) -type topologyPair struct { - key string - value string -} - // topologySpreadConstraint is an internal version for v1.TopologySpreadConstraint // and where the selector is parsed. // Fields are exported for comparison during testing. diff --git a/pkg/scheduler/framework/plugins/podtopologyspread/filtering.go b/pkg/scheduler/framework/plugins/podtopologyspread/filtering.go index 11322a4cc2d..0d95c16ba67 100644 --- a/pkg/scheduler/framework/plugins/podtopologyspread/filtering.go +++ b/pkg/scheduler/framework/plugins/podtopologyspread/filtering.go @@ -19,6 +19,7 @@ package podtopologyspread import ( "context" "fmt" + "maps" "math" v1 "k8s.io/api/core/v1" @@ -31,7 +32,7 @@ import ( const preFilterStateKey = "PreFilter" + Name // preFilterState computed at PreFilter and used at Filter. -// It combines TpKeyToCriticalPaths and TpPairToMatchNum to represent: +// It combines CriticalPaths and TpValueToMatchNum to represent: // (1) critical paths where the least pods are matched on each spread constraint. // (2) number of pods matched on each spread constraint. // A nil preFilterState denotes it's not set at all (in PreFilter phase); @@ -39,29 +40,23 @@ const preFilterStateKey = "PreFilter" + Name // Fields are exported for comparison during testing. type preFilterState struct { Constraints []topologySpreadConstraint - // We record 2 critical paths instead of all critical paths here. - // criticalPaths[0].MatchNum always holds the minimum matching number. - // criticalPaths[1].MatchNum is always greater or equal to criticalPaths[0].MatchNum, but + // CriticalPaths is a slice indexed by constraint index. + // Per each entry, we record 2 critical paths instead of all critical paths. + // CriticalPaths[i][0].MatchNum always holds the minimum matching number. + // CriticalPaths[i][1].MatchNum is always greater or equal to CriticalPaths[i][0].MatchNum, but // it's not guaranteed to be the 2nd minimum match number. - TpKeyToCriticalPaths map[string]*criticalPaths - // TpKeyToDomainsNum is keyed with topologyKey, and valued with the number of domains. - TpKeyToDomainsNum map[string]int - // TpPairToMatchNum is keyed with topologyPair, and valued with the number of matching pods. - TpPairToMatchNum map[topologyPair]int + CriticalPaths []*criticalPaths + // TpValueToMatchNum is a slice indexed by constraint index. + // Each entry is keyed with topology value, and valued with the number of matching pods. + TpValueToMatchNum []map[string]int } // minMatchNum returns the global minimum for the calculation of skew while taking MinDomains into account. -func (s *preFilterState) minMatchNum(tpKey string, minDomains int32) (int, error) { - paths, ok := s.TpKeyToCriticalPaths[tpKey] - if !ok { - return 0, fmt.Errorf("failed to retrieve path by topology key") - } +func (s *preFilterState) minMatchNum(constraintID int, minDomains int32) (int, error) { + paths := s.CriticalPaths[constraintID] minMatchNum := paths[0].MatchNum - domainsNum, ok := s.TpKeyToDomainsNum[tpKey] - if !ok { - return 0, fmt.Errorf("failed to retrieve the number of domains by topology key") - } + domainsNum := len(s.TpValueToMatchNum[constraintID]) if domainsNum < int(minDomains) { // When the number of eligible domains with matching topology keys is less than `minDomains`, @@ -79,17 +74,15 @@ func (s *preFilterState) Clone() framework.StateData { } copy := preFilterState{ // Constraints are shared because they don't change. - Constraints: s.Constraints, - TpKeyToCriticalPaths: make(map[string]*criticalPaths, len(s.TpKeyToCriticalPaths)), - // The number of domains does not change as a result of AddPod/RemovePod methods on PreFilter Extensions - TpKeyToDomainsNum: s.TpKeyToDomainsNum, - TpPairToMatchNum: make(map[topologyPair]int, len(s.TpPairToMatchNum)), + Constraints: s.Constraints, + CriticalPaths: make([]*criticalPaths, len(s.CriticalPaths)), + TpValueToMatchNum: make([]map[string]int, len(s.TpValueToMatchNum)), } - for tpKey, paths := range s.TpKeyToCriticalPaths { - copy.TpKeyToCriticalPaths[tpKey] = &criticalPaths{paths[0], paths[1]} + for i, paths := range s.CriticalPaths { + copy.CriticalPaths[i] = &criticalPaths{paths[0], paths[1]} } - for tpPair, matchNum := range s.TpPairToMatchNum { - copy.TpPairToMatchNum[tpPair] = matchNum + for i, tpMap := range s.TpValueToMatchNum { + copy.TpValueToMatchNum[i] = maps.Clone(tpMap) } return © } @@ -200,7 +193,7 @@ func (pl *PodTopologySpread) updateWithPod(s *preFilterState, updatedPod, preemp } podLabelSet := labels.Set(updatedPod.Labels) - for _, constraint := range s.Constraints { + for i, constraint := range s.Constraints { if !constraint.Selector.Matches(podLabelSet) { continue } @@ -210,10 +203,9 @@ func (pl *PodTopologySpread) updateWithPod(s *preFilterState, updatedPod, preemp continue } - k, v := constraint.TopologyKey, node.Labels[constraint.TopologyKey] - pair := topologyPair{key: k, value: v} - s.TpPairToMatchNum[pair] += delta - s.TpKeyToCriticalPaths[k].update(v, s.TpPairToMatchNum[pair]) + v := node.Labels[constraint.TopologyKey] + s.TpValueToMatchNum[i][v] += delta + s.CriticalPaths[i].update(v, s.TpValueToMatchNum[i][v]) } } @@ -232,6 +224,12 @@ func getPreFilterState(cycleState *framework.CycleState) (*preFilterState, error return s, nil } +type topologyCount struct { + topologyValue string + constraintID int + count int +} + // calPreFilterState computes preFilterState describing how pods are spread on topologies. func (pl *PodTopologySpread) calPreFilterState(ctx context.Context, pod *v1.Pod) (*preFilterState, error) { constraints, err := pl.getConstraints(pod) @@ -248,15 +246,18 @@ func (pl *PodTopologySpread) calPreFilterState(ctx context.Context, pod *v1.Pod) } s := preFilterState{ - Constraints: constraints, - TpKeyToCriticalPaths: make(map[string]*criticalPaths, len(constraints)), - TpPairToMatchNum: make(map[topologyPair]int, sizeHeuristic(len(allNodes), constraints)), + Constraints: constraints, + CriticalPaths: make([]*criticalPaths, len(constraints)), + TpValueToMatchNum: make([]map[string]int, len(constraints)), + } + for i := 0; i < len(constraints); i++ { + s.TpValueToMatchNum[i] = make(map[string]int, sizeHeuristic(len(allNodes), constraints[i])) } - tpCountsByNode := make([]map[topologyPair]int, len(allNodes)) + tpCountsByNode := make([][]topologyCount, len(allNodes)) requiredNodeAffinity := nodeaffinity.GetRequiredNodeAffinity(pod) - processNode := func(i int) { - nodeInfo := allNodes[i] + processNode := func(n int) { + nodeInfo := allNodes[n] node := nodeInfo.Node() if !pl.enableNodeInclusionPolicyInPodTopologySpread { @@ -272,38 +273,39 @@ func (pl *PodTopologySpread) calPreFilterState(ctx context.Context, pod *v1.Pod) return } - tpCounts := make(map[topologyPair]int, len(constraints)) - for _, c := range constraints { + tpCounts := make([]topologyCount, 0, len(constraints)) + for i, c := range constraints { if pl.enableNodeInclusionPolicyInPodTopologySpread && !c.matchNodeInclusionPolicies(pod, node, requiredNodeAffinity) { continue } - pair := topologyPair{key: c.TopologyKey, value: node.Labels[c.TopologyKey]} + value := node.Labels[c.TopologyKey] count := countPodsMatchSelector(nodeInfo.Pods, c.Selector, pod.Namespace) - tpCounts[pair] = count + tpCounts = append(tpCounts, topologyCount{ + topologyValue: value, + constraintID: i, + count: count, + }) } - tpCountsByNode[i] = tpCounts + tpCountsByNode[n] = tpCounts } pl.parallelizer.Until(ctx, len(allNodes), processNode, pl.Name()) for _, tpCounts := range tpCountsByNode { - for tp, count := range tpCounts { - s.TpPairToMatchNum[tp] += count + // tpCounts might not hold all the constraints, so index can't be used here as constraintID. + for _, tpCount := range tpCounts { + s.TpValueToMatchNum[tpCount.constraintID][tpCount.topologyValue] += tpCount.count } } - s.TpKeyToDomainsNum = make(map[string]int, len(constraints)) - for tp := range s.TpPairToMatchNum { - s.TpKeyToDomainsNum[tp.key]++ - } - // calculate min match for each topology pair + // calculate min match for each constraint and topology value for i := 0; i < len(constraints); i++ { - key := constraints[i].TopologyKey - s.TpKeyToCriticalPaths[key] = newCriticalPaths() - } - for pair, num := range s.TpPairToMatchNum { - s.TpKeyToCriticalPaths[pair.key].update(pair.value, num) + s.CriticalPaths[i] = newCriticalPaths() + + for value, num := range s.TpValueToMatchNum[i] { + s.CriticalPaths[i].update(value, num) + } } return &s, nil @@ -325,7 +327,7 @@ func (pl *PodTopologySpread) Filter(ctx context.Context, cycleState *framework.C logger := klog.FromContext(ctx) podLabelSet := labels.Set(pod.Labels) - for _, c := range s.Constraints { + for i, c := range s.Constraints { tpKey := c.TopologyKey tpVal, ok := node.Labels[c.TopologyKey] if !ok { @@ -335,9 +337,9 @@ func (pl *PodTopologySpread) Filter(ctx context.Context, cycleState *framework.C // judging criteria: // 'existing matching num' + 'if self-match (1 or 0)' - 'global minimum' <= 'maxSkew' - minMatchNum, err := s.minMatchNum(tpKey, c.MinDomains) + minMatchNum, err := s.minMatchNum(i, c.MinDomains) if err != nil { - logger.Error(err, "Internal error occurred while retrieving value precalculated in PreFilter", "topologyKey", tpKey, "paths", s.TpKeyToCriticalPaths) + logger.Error(err, "Internal error occurred while retrieving value precalculated in PreFilter", "topologyKey", tpKey, "paths", s.CriticalPaths[i]) continue } @@ -346,11 +348,7 @@ func (pl *PodTopologySpread) Filter(ctx context.Context, cycleState *framework.C selfMatchNum = 1 } - pair := topologyPair{key: tpKey, value: tpVal} - matchNum := 0 - if tpCount, ok := s.TpPairToMatchNum[pair]; ok { - matchNum = tpCount - } + matchNum := s.TpValueToMatchNum[i][tpVal] skew := matchNum + selfMatchNum - minMatchNum if skew > int(c.MaxSkew) { logger.V(5).Info("Node failed spreadConstraint: matchNum + selfMatchNum - minMatchNum > maxSkew", "node", klog.KObj(node), "topologyKey", tpKey, "matchNum", matchNum, "selfMatchNum", selfMatchNum, "minMatchNum", minMatchNum, "maxSkew", c.MaxSkew) @@ -361,11 +359,9 @@ func (pl *PodTopologySpread) Filter(ctx context.Context, cycleState *framework.C return nil } -func sizeHeuristic(nodes int, constraints []topologySpreadConstraint) int { - for _, c := range constraints { - if c.TopologyKey == v1.LabelHostname { - return nodes - } +func sizeHeuristic(nodes int, constraint topologySpreadConstraint) int { + if constraint.TopologyKey == v1.LabelHostname { + return nodes } return 0 } diff --git a/pkg/scheduler/framework/plugins/podtopologyspread/filtering_test.go b/pkg/scheduler/framework/plugins/podtopologyspread/filtering_test.go index 13fa8011929..e007f75abcb 100644 --- a/pkg/scheduler/framework/plugins/podtopologyspread/filtering_test.go +++ b/pkg/scheduler/framework/plugins/podtopologyspread/filtering_test.go @@ -105,14 +105,11 @@ func TestPreFilterState(t *testing.T) { NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, }, }, - TpKeyToDomainsNum: map[string]int{"zone": 2}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone1", 0}, {"zone2", 0}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone1"}: 0, - {key: "zone", value: "zone2"}: 0, - }, + CriticalPaths: []*criticalPaths{{{"zone1", 0}, {"zone2", 0}}}, + TpValueToMatchNum: []map[string]int{{ + "zone1": 0, + "zone2": 0, + }}, }, }, { @@ -144,14 +141,11 @@ func TestPreFilterState(t *testing.T) { NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, }, }, - TpKeyToDomainsNum: map[string]int{"zone": 2}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone2", 2}, {"zone1", 3}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone1"}: 3, - {key: "zone", value: "zone2"}: 2, - }, + CriticalPaths: []*criticalPaths{{{"zone2", 2}, {"zone1", 3}}}, + TpValueToMatchNum: []map[string]int{{ + "zone1": 3, + "zone2": 2, + }}, }, }, { @@ -183,14 +177,11 @@ func TestPreFilterState(t *testing.T) { NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, }, }, - TpKeyToDomainsNum: map[string]int{"zone": 2}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone2", 0}, {"zone1", 0}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone1"}: 0, - {key: "zone", value: "zone2"}: 0, - }, + CriticalPaths: []*criticalPaths{{{"zone2", 0}, {"zone1", 0}}}, + TpValueToMatchNum: []map[string]int{{ + "zone1": 0, + "zone2": 0, + }}, }, }, { @@ -224,15 +215,12 @@ func TestPreFilterState(t *testing.T) { NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, }, }, - TpKeyToDomainsNum: map[string]int{"zone": 3}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone3", 0}, {"zone2", 2}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone1"}: 3, - {key: "zone", value: "zone2"}: 2, - {key: "zone", value: "zone3"}: 0, - }, + CriticalPaths: []*criticalPaths{{{"zone3", 0}, {"zone2", 2}}}, + TpValueToMatchNum: []map[string]int{{ + "zone1": 3, + "zone2": 2, + "zone3": 0, + }}, }, }, { @@ -264,14 +252,11 @@ func TestPreFilterState(t *testing.T) { NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, }, }, - TpKeyToDomainsNum: map[string]int{"zone": 2}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone2", 1}, {"zone1", 2}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone1"}: 2, - {key: "zone", value: "zone2"}: 1, - }, + CriticalPaths: []*criticalPaths{{{"zone2", 1}, {"zone1", 2}}}, + TpValueToMatchNum: []map[string]int{{ + "zone1": 2, + "zone2": 1, + }}, }, }, { @@ -314,18 +299,18 @@ func TestPreFilterState(t *testing.T) { NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, }, }, - TpKeyToDomainsNum: map[string]int{"zone": 2, "node": 4}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone1", 3}, {"zone2", 4}}, - "node": {{"node-x", 0}, {"node-b", 1}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone1"}: 3, - {key: "zone", value: "zone2"}: 4, - {key: "node", value: "node-a"}: 2, - {key: "node", value: "node-b"}: 1, - {key: "node", value: "node-x"}: 0, - {key: "node", value: "node-y"}: 4, + CriticalPaths: []*criticalPaths{{{"zone1", 3}, {"zone2", 4}}, {{"node-x", 0}, {"node-b", 1}}}, + TpValueToMatchNum: []map[string]int{ + { + "zone1": 3, + "zone2": 4, + }, + { + "node-a": 2, + "node-b": 1, + "node-x": 0, + "node-y": 4, + }, }, }, }, @@ -370,17 +355,17 @@ func TestPreFilterState(t *testing.T) { NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, }, }, - TpKeyToDomainsNum: map[string]int{"zone": 2, "node": 3}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone1", 3}, {"zone2", 4}}, - "node": {{"node-b", 1}, {"node-a", 2}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone1"}: 3, - {key: "zone", value: "zone2"}: 4, - {key: "node", value: "node-a"}: 2, - {key: "node", value: "node-b"}: 1, - {key: "node", value: "node-y"}: 4, + CriticalPaths: []*criticalPaths{{{"zone1", 3}, {"zone2", 4}}, {{"node-b", 1}, {"node-a", 2}}}, + TpValueToMatchNum: []map[string]int{ + { + "zone1": 3, + "zone2": 4, + }, + { + "node-a": 2, + "node-b": 1, + "node-y": 4, + }, }, }, }, @@ -418,17 +403,17 @@ func TestPreFilterState(t *testing.T) { NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, }, }, - TpKeyToDomainsNum: map[string]int{"zone": 2, "node": 3}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone2", 0}, {"zone1", 1}}, - "node": {{"node-a", 0}, {"node-y", 0}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone1"}: 1, - {key: "zone", value: "zone2"}: 0, - {key: "node", value: "node-a"}: 0, - {key: "node", value: "node-b"}: 1, - {key: "node", value: "node-y"}: 0, + CriticalPaths: []*criticalPaths{{{"zone2", 0}, {"zone1", 1}}, {{"node-a", 0}, {"node-y", 0}}}, + TpValueToMatchNum: []map[string]int{ + { + "zone1": 1, + "zone2": 0, + }, + { + "node-a": 0, + "node-b": 1, + "node-y": 0, + }, }, }, }, @@ -471,17 +456,17 @@ func TestPreFilterState(t *testing.T) { NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, }, }, - TpKeyToDomainsNum: map[string]int{"zone": 2, "node": 3}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone1", 3}, {"zone2", 4}}, - "node": {{"node-b", 0}, {"node-a", 1}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone1"}: 3, - {key: "zone", value: "zone2"}: 4, - {key: "node", value: "node-a"}: 1, - {key: "node", value: "node-b"}: 0, - {key: "node", value: "node-y"}: 2, + CriticalPaths: []*criticalPaths{{{"zone1", 3}, {"zone2", 4}}, {{"node-b", 0}, {"node-a", 1}}}, + TpValueToMatchNum: []map[string]int{ + { + "zone1": 3, + "zone2": 4, + }, + { + "node-a": 1, + "node-b": 0, + "node-y": 2, + }, }, }, }, @@ -526,17 +511,17 @@ func TestPreFilterState(t *testing.T) { NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, }, }, - TpKeyToDomainsNum: map[string]int{"zone": 2, "node": 3}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone1", 3}, {"zone2", 4}}, - "node": {{"node-b", 1}, {"node-a", 2}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone1"}: 3, - {key: "zone", value: "zone2"}: 4, - {key: "node", value: "node-a"}: 2, - {key: "node", value: "node-b"}: 1, - {key: "node", value: "node-y"}: 4, + CriticalPaths: []*criticalPaths{{{"zone1", 3}, {"zone2", 4}}, {{"node-b", 1}, {"node-a", 2}}}, + TpValueToMatchNum: []map[string]int{ + { + "zone1": 3, + "zone2": 4, + }, + { + "node-a": 2, + "node-b": 1, + "node-y": 4, + }, }, }, }, @@ -570,12 +555,8 @@ func TestPreFilterState(t *testing.T) { NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, }, }, - TpKeyToDomainsNum: map[string]int{}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "node": newCriticalPaths(), - "rack": newCriticalPaths(), - }, - TpPairToMatchNum: make(map[topologyPair]int), + CriticalPaths: []*criticalPaths{newCriticalPaths(), newCriticalPaths()}, + TpValueToMatchNum: []map[string]int{{}, {}}, }, }, { @@ -612,11 +593,8 @@ func TestPreFilterState(t *testing.T) { NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, }, }, - TpKeyToDomainsNum: map[string]int{}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": newCriticalPaths(), - }, - TpPairToMatchNum: make(map[topologyPair]int), + CriticalPaths: []*criticalPaths{newCriticalPaths()}, + TpValueToMatchNum: []map[string]int{{}}, }, }, { @@ -670,21 +648,18 @@ func TestPreFilterState(t *testing.T) { NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, }, }, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone1", 3}, {"zone2", 4}}, - "node": {{"node-x", 0}, {"node-b", 1}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone1"}: 3, - {key: "zone", value: "zone2"}: 4, - {key: "node", value: "node-a"}: 2, - {key: "node", value: "node-b"}: 1, - {key: "node", value: "node-x"}: 0, - {key: "node", value: "node-y"}: 4, - }, - TpKeyToDomainsNum: map[string]int{ - "zone": 2, - "node": 4, + CriticalPaths: []*criticalPaths{{{"zone1", 3}, {"zone2", 4}}, {{"node-x", 0}, {"node-b", 1}}}, + TpValueToMatchNum: []map[string]int{ + { + "zone1": 3, + "zone2": 4, + }, + { + "node-a": 2, + "node-b": 1, + "node-x": 0, + "node-y": 4, + }, }, }, }, @@ -716,14 +691,11 @@ func TestPreFilterState(t *testing.T) { NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, }, }, - TpKeyToDomainsNum: map[string]int{"node": 2}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "node": {{"node-a", 1}, {"node-b", 2}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "node", value: "node-a"}: 1, - {key: "node", value: "node-b"}: 2, - }, + CriticalPaths: []*criticalPaths{{{"node-a", 1}, {"node-b", 2}}}, + TpValueToMatchNum: []map[string]int{{ + "node-a": 1, + "node-b": 2, + }}, }, enableNodeInclusionPolicy: false, }, @@ -755,14 +727,11 @@ func TestPreFilterState(t *testing.T) { NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, }, }, - TpKeyToDomainsNum: map[string]int{"node": 2}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "node": {{"node-a", 1}, {"node-b", 2}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "node", value: "node-a"}: 1, - {key: "node", value: "node-b"}: 2, - }, + CriticalPaths: []*criticalPaths{{{"node-a", 1}, {"node-b", 2}}}, + TpValueToMatchNum: []map[string]int{{ + "node-a": 1, + "node-b": 2, + }}, }, enableNodeInclusionPolicy: true, }, @@ -794,15 +763,12 @@ func TestPreFilterState(t *testing.T) { NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, }, }, - TpKeyToDomainsNum: map[string]int{"node": 3}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "node": {{"node-c", 0}, {"node-a", 1}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "node", value: "node-a"}: 1, - {key: "node", value: "node-b"}: 2, - {key: "node", value: "node-c"}: 0, - }, + CriticalPaths: []*criticalPaths{{{"node-c", 0}, {"node-a", 1}}}, + TpValueToMatchNum: []map[string]int{{ + "node-a": 1, + "node-b": 2, + "node-c": 0, + }}, }, enableNodeInclusionPolicy: true, }, @@ -834,14 +800,11 @@ func TestPreFilterState(t *testing.T) { NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, }, }, - TpKeyToDomainsNum: map[string]int{"node": 2}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "node": {{"node-a", 1}, {"node-b", 2}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "node", value: "node-a"}: 1, - {key: "node", value: "node-b"}: 2, - }, + CriticalPaths: []*criticalPaths{{{"node-a", 1}, {"node-b", 2}}}, + TpValueToMatchNum: []map[string]int{{ + "node-a": 1, + "node-b": 2, + }}, }, enableNodeInclusionPolicy: true, }, @@ -873,15 +836,12 @@ func TestPreFilterState(t *testing.T) { NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, }, }, - TpKeyToDomainsNum: map[string]int{"node": 3}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "node": {{"node-c", 0}, {"node-a", 1}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "node", value: "node-a"}: 1, - {key: "node", value: "node-b"}: 2, - {key: "node", value: "node-c"}: 0, - }, + CriticalPaths: []*criticalPaths{{{"node-c", 0}, {"node-a", 1}}}, + TpValueToMatchNum: []map[string]int{{ + "node-a": 1, + "node-b": 2, + "node-c": 0, + }}, }, enableNodeInclusionPolicy: true, }, @@ -912,15 +872,12 @@ func TestPreFilterState(t *testing.T) { NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, }, }, - TpKeyToDomainsNum: map[string]int{"node": 3}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "node": {{"node-c", 0}, {"node-a", 1}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "node", value: "node-a"}: 1, - {key: "node", value: "node-b"}: 2, - {key: "node", value: "node-c"}: 0, - }, + CriticalPaths: []*criticalPaths{{{"node-c", 0}, {"node-a", 1}}}, + TpValueToMatchNum: []map[string]int{{ + "node-a": 1, + "node-b": 2, + "node-c": 0, + }}, }, enableNodeInclusionPolicy: false, }, @@ -951,15 +908,12 @@ func TestPreFilterState(t *testing.T) { NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, }, }, - TpKeyToDomainsNum: map[string]int{"node": 3}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "node": {{"node-c", 0}, {"node-a", 1}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "node", value: "node-a"}: 1, - {key: "node", value: "node-b"}: 2, - {key: "node", value: "node-c"}: 0, - }, + CriticalPaths: []*criticalPaths{{{"node-c", 0}, {"node-a", 1}}}, + TpValueToMatchNum: []map[string]int{{ + "node-a": 1, + "node-b": 2, + "node-c": 0, + }}, }, enableNodeInclusionPolicy: true, }, @@ -990,14 +944,11 @@ func TestPreFilterState(t *testing.T) { NodeTaintsPolicy: v1.NodeInclusionPolicyHonor, }, }, - TpKeyToDomainsNum: map[string]int{"node": 2}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "node": {{"node-a", 1}, {"node-b", 2}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "node", value: "node-a"}: 1, - {key: "node", value: "node-b"}: 2, - }, + CriticalPaths: []*criticalPaths{{{"node-a", 1}, {"node-b", 2}}}, + TpValueToMatchNum: []map[string]int{{ + "node-a": 1, + "node-b": 2, + }}, }, enableNodeInclusionPolicy: true, }, @@ -1029,15 +980,12 @@ func TestPreFilterState(t *testing.T) { NodeTaintsPolicy: v1.NodeInclusionPolicyHonor, }, }, - TpKeyToDomainsNum: map[string]int{"node": 3}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "node": {{"node-c", 0}, {"node-a", 1}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "node", value: "node-a"}: 1, - {key: "node", value: "node-b"}: 2, - {key: "node", value: "node-c"}: 0, - }, + CriticalPaths: []*criticalPaths{{{"node-c", 0}, {"node-a", 1}}}, + TpValueToMatchNum: []map[string]int{{ + "node-a": 1, + "node-b": 2, + "node-c": 0, + }}, }, enableNodeInclusionPolicy: true, }, @@ -1077,17 +1025,17 @@ func TestPreFilterState(t *testing.T) { NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, }, }, - TpKeyToDomainsNum: map[string]int{"zone": 2, "node": 3}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone1", 0}, {"zone2", 1}}, - "node": {{"node-a", 0}, {"node-x", 1}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone1"}: 0, - {key: "zone", value: "zone2"}: 1, - {key: "node", value: "node-a"}: 0, - {key: "node", value: "node-b"}: 2, - {key: "node", value: "node-x"}: 1, + CriticalPaths: []*criticalPaths{{{"zone1", 0}, {"zone2", 1}}, {{"node-a", 0}, {"node-x", 1}}}, + TpValueToMatchNum: []map[string]int{ + { + "zone1": 0, + "zone2": 1, + }, + { + "node-a": 0, + "node-b": 2, + "node-x": 1, + }, }, }, enableNodeInclusionPolicy: true, @@ -1127,17 +1075,17 @@ func TestPreFilterState(t *testing.T) { NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, }, }, - TpKeyToDomainsNum: map[string]int{"zone": 2, "node": 3}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone1", 0}, {"zone2", 1}}, - "node": {{"node-a", 0}, {"node-x", 1}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone1"}: 0, - {key: "zone", value: "zone2"}: 1, - {key: "node", value: "node-a"}: 0, - {key: "node", value: "node-b"}: 2, - {key: "node", value: "node-x"}: 1, + CriticalPaths: []*criticalPaths{{{"zone1", 0}, {"zone2", 1}}, {{"node-a", 0}, {"node-x", 1}}}, + TpValueToMatchNum: []map[string]int{ + { + "zone1": 0, + "zone2": 1, + }, + { + "node-a": 0, + "node-b": 2, + "node-x": 1, + }, }, }, enableNodeInclusionPolicy: true, @@ -1180,16 +1128,16 @@ func TestPreFilterState(t *testing.T) { NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, }, }, - TpKeyToDomainsNum: map[string]int{"zone": 2, "node": 2}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone1", 0}, {"zone2", 1}}, - "node": {{"node-b", 0}, {"node-x", 1}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone1"}: 0, - {key: "zone", value: "zone2"}: 1, - {key: "node", value: "node-b"}: 0, - {key: "node", value: "node-x"}: 1, + CriticalPaths: []*criticalPaths{{{"zone1", 0}, {"zone2", 1}}, {{"node-b", 0}, {"node-x", 1}}}, + TpValueToMatchNum: []map[string]int{ + { + "zone1": 0, + "zone2": 1, + }, + { + "node-b": 0, + "node-x": 1, + }, }, }, enableNodeInclusionPolicy: true, @@ -1233,16 +1181,16 @@ func TestPreFilterState(t *testing.T) { NodeTaintsPolicy: v1.NodeInclusionPolicyHonor, }, }, - TpKeyToDomainsNum: map[string]int{"zone": 2, "node": 2}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone1", 2}, {"zone2", 3}}, - "node": {{"node-y", 1}, {"node-x", 2}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone1"}: 2, - {key: "zone", value: "zone2"}: 3, - {key: "node", value: "node-x"}: 2, - {key: "node", value: "node-y"}: 1, + CriticalPaths: []*criticalPaths{{{"zone1", 2}, {"zone2", 3}}, {{"node-y", 1}, {"node-x", 2}}}, + TpValueToMatchNum: []map[string]int{ + { + "zone1": 2, + "zone2": 3, + }, + { + "node-x": 2, + "node-y": 1, + }, }, }, enableNodeInclusionPolicy: true, @@ -1276,14 +1224,11 @@ func TestPreFilterState(t *testing.T) { NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, }, }, - TpKeyToDomainsNum: map[string]int{"zone": 2}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone2", 2}, {"zone1", 3}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone1"}: 3, - {key: "zone", value: "zone2"}: 2, - }, + CriticalPaths: []*criticalPaths{{{"zone2", 2}, {"zone1", 3}}}, + TpValueToMatchNum: []map[string]int{{ + "zone1": 3, + "zone2": 2, + }}, }, enableMatchLabelKeys: false, }, @@ -1316,14 +1261,11 @@ func TestPreFilterState(t *testing.T) { NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, }, }, - TpKeyToDomainsNum: map[string]int{"zone": 2}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone2", 1}, {"zone1", 1}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone1"}: 1, - {key: "zone", value: "zone2"}: 1, - }, + CriticalPaths: []*criticalPaths{{{"zone2", 1}, {"zone1", 1}}}, + TpValueToMatchNum: []map[string]int{{ + "zone1": 1, + "zone2": 1, + }}, }, enableMatchLabelKeys: true, }, @@ -1356,14 +1298,11 @@ func TestPreFilterState(t *testing.T) { NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, }, }, - TpKeyToDomainsNum: map[string]int{"zone": 2}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone2", 2}, {"zone1", 3}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone1"}: 3, - {key: "zone", value: "zone2"}: 2, - }, + CriticalPaths: []*criticalPaths{{{"zone2", 2}, {"zone1", 3}}}, + TpValueToMatchNum: []map[string]int{{ + "zone1": 3, + "zone2": 2, + }}, }, enableMatchLabelKeys: true, }, @@ -1396,14 +1335,11 @@ func TestPreFilterState(t *testing.T) { NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, }, }, - TpKeyToDomainsNum: map[string]int{"zone": 2}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone2", 0}, {"zone1", 0}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone1"}: 0, - {key: "zone", value: "zone2"}: 0, - }, + CriticalPaths: []*criticalPaths{{{"zone2", 0}, {"zone1", 0}}}, + TpValueToMatchNum: []map[string]int{{ + "zone1": 0, + "zone2": 0, + }}, }, enableMatchLabelKeys: true, }, @@ -1436,14 +1372,11 @@ func TestPreFilterState(t *testing.T) { NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, }, }, - TpKeyToDomainsNum: map[string]int{"zone": 2}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone2", 0}, {"zone1", 0}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone1"}: 0, - {key: "zone", value: "zone2"}: 0, - }, + CriticalPaths: []*criticalPaths{{{"zone2", 0}, {"zone1", 0}}}, + TpValueToMatchNum: []map[string]int{{ + "zone1": 0, + "zone2": 0, + }}, }, }, { @@ -1475,17 +1408,65 @@ func TestPreFilterState(t *testing.T) { NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, }, }, - TpKeyToDomainsNum: map[string]int{"zone": 2}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone2", 2}, {"zone1", 3}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone1"}: 3, - {key: "zone", value: "zone2"}: 2, - }, + CriticalPaths: []*criticalPaths{{{"zone2", 2}, {"zone1", 3}}}, + TpValueToMatchNum: []map[string]int{{ + "zone1": 3, + "zone2": 2, + }}, }, enableMatchLabelKeys: true, }, + { + name: "two spreadConstraints with the same topologyKey", + pod: st.MakePod().Name("p").Label("foo", "").Label("bar", ""). + SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). + SpreadConstraint(1, "zone", v1.DoNotSchedule, barSelector, nil, nil, nil, nil). + Obj(), + nodes: []*v1.Node{ + st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), + st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").Obj(), + st.MakeNode().Name("node-x").Label("zone", "zone2").Label("node", "node-x").Obj(), + st.MakeNode().Name("node-y").Label("zone", "zone2").Label("node", "node-y").Obj(), + }, + existingPods: []*v1.Pod{ + st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Label("bar", "").Obj(), + st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Obj(), + st.MakePod().Name("p-x1").Node("node-x").Label("bar", "").Obj(), + st.MakePod().Name("p-y1").Node("node-y").Label("foo", "").Label("bar", "").Obj(), + st.MakePod().Name("p-y2").Node("node-y").Label("bar", "").Obj(), + }, + want: &preFilterState{ + Constraints: []topologySpreadConstraint{ + { + MaxSkew: 1, + TopologyKey: "zone", + Selector: mustConvertLabelSelectorAsSelector(t, fooSelector), + MinDomains: 1, + NodeAffinityPolicy: v1.NodeInclusionPolicyHonor, + NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, + }, + { + MaxSkew: 1, + TopologyKey: "zone", + Selector: mustConvertLabelSelectorAsSelector(t, barSelector), + MinDomains: 1, + NodeAffinityPolicy: v1.NodeInclusionPolicyHonor, + NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, + }, + }, + CriticalPaths: []*criticalPaths{{{"zone2", 1}, {"zone1", 2}}, {{"zone1", 1}, {"zone2", 3}}}, + TpValueToMatchNum: []map[string]int{ + { + "zone1": 2, + "zone2": 1, + }, + { + "zone1": 1, + "zone2": 3, + }, + }, + }, + }, { name: "skip if not specified", pod: st.MakePod().Name("p").Label("foo", "").Obj(), @@ -1564,15 +1545,12 @@ func TestPreFilterStateAddPod(t *testing.T) { st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").Obj(), }, want: &preFilterState{ - Constraints: []topologySpreadConstraint{nodeConstraint}, - TpKeyToDomainsNum: map[string]int{"node": 2}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "node": {{"node-b", 0}, {"node-a", 1}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "node", value: "node-a"}: 1, - {key: "node", value: "node-b"}: 0, - }, + Constraints: []topologySpreadConstraint{nodeConstraint}, + CriticalPaths: []*criticalPaths{{{"node-b", 0}, {"node-a", 1}}}, + TpValueToMatchNum: []map[string]int{{ + "node-a": 1, + "node-b": 0, + }}, }, }, { @@ -1590,15 +1568,12 @@ func TestPreFilterStateAddPod(t *testing.T) { st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").Obj(), }, want: &preFilterState{ - Constraints: []topologySpreadConstraint{nodeConstraint}, - TpKeyToDomainsNum: map[string]int{"node": 2}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "node": {{"node-a", 1}, {"node-b", 1}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "node", value: "node-a"}: 1, - {key: "node", value: "node-b"}: 1, - }, + Constraints: []topologySpreadConstraint{nodeConstraint}, + CriticalPaths: []*criticalPaths{{{"node-a", 1}, {"node-b", 1}}}, + TpValueToMatchNum: []map[string]int{{ + "node-a": 1, + "node-b": 1, + }}, }, }, { @@ -1616,15 +1591,12 @@ func TestPreFilterStateAddPod(t *testing.T) { st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").Obj(), }, want: &preFilterState{ - Constraints: []topologySpreadConstraint{nodeConstraint}, - TpKeyToDomainsNum: map[string]int{"node": 2}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "node": {{"node-a", 0}, {"node-b", 1}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "node", value: "node-a"}: 0, - {key: "node", value: "node-b"}: 1, - }, + Constraints: []topologySpreadConstraint{nodeConstraint}, + CriticalPaths: []*criticalPaths{{{"node-a", 0}, {"node-b", 1}}}, + TpValueToMatchNum: []map[string]int{{ + "node-a": 0, + "node-b": 1, + }}, }, }, { @@ -1642,15 +1614,12 @@ func TestPreFilterStateAddPod(t *testing.T) { st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").Obj(), }, want: &preFilterState{ - Constraints: []topologySpreadConstraint{nodeConstraint}, - TpKeyToDomainsNum: map[string]int{"node": 2}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "node": {{"node-a", 0}, {"node-b", 2}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "node", value: "node-a"}: 0, - {key: "node", value: "node-b"}: 2, - }, + Constraints: []topologySpreadConstraint{nodeConstraint}, + CriticalPaths: []*criticalPaths{{{"node-a", 0}, {"node-b", 2}}}, + TpValueToMatchNum: []map[string]int{{ + "node-a": 0, + "node-b": 2, + }}, }, }, { @@ -1667,17 +1636,17 @@ func TestPreFilterStateAddPod(t *testing.T) { st.MakeNode().Name("node-x").Label("zone", "zone2").Label("node", "node-x").Obj(), }, want: &preFilterState{ - Constraints: []topologySpreadConstraint{zoneConstraint, nodeConstraint}, - TpKeyToDomainsNum: map[string]int{"zone": 2, "node": 2}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone2", 0}, {"zone1", 1}}, - "node": {{"node-x", 0}, {"node-a", 1}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone1"}: 1, - {key: "zone", value: "zone2"}: 0, - {key: "node", value: "node-a"}: 1, - {key: "node", value: "node-x"}: 0, + Constraints: []topologySpreadConstraint{zoneConstraint, nodeConstraint}, + CriticalPaths: []*criticalPaths{{{"zone2", 0}, {"zone1", 1}}, {{"node-x", 0}, {"node-a", 1}}}, + TpValueToMatchNum: []map[string]int{ + { + "zone1": 1, + "zone2": 0, + }, + { + "node-a": 1, + "node-x": 0, + }, }, }, }, @@ -1697,17 +1666,17 @@ func TestPreFilterStateAddPod(t *testing.T) { st.MakeNode().Name("node-x").Label("zone", "zone2").Label("node", "node-x").Obj(), }, want: &preFilterState{ - Constraints: []topologySpreadConstraint{zoneConstraint, nodeConstraint}, - TpKeyToDomainsNum: map[string]int{"zone": 2, "node": 2}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone1", 1}, {"zone2", 1}}, - "node": {{"node-a", 1}, {"node-x", 1}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone1"}: 1, - {key: "zone", value: "zone2"}: 1, - {key: "node", value: "node-a"}: 1, - {key: "node", value: "node-x"}: 1, + Constraints: []topologySpreadConstraint{zoneConstraint, nodeConstraint}, + CriticalPaths: []*criticalPaths{{{"zone1", 1}, {"zone2", 1}}, {{"node-a", 1}, {"node-x", 1}}}, + TpValueToMatchNum: []map[string]int{ + { + "zone1": 1, + "zone2": 1, + }, + { + "node-a": 1, + "node-x": 1, + }, }, }, }, @@ -1730,18 +1699,18 @@ func TestPreFilterStateAddPod(t *testing.T) { st.MakeNode().Name("node-x").Label("zone", "zone2").Label("node", "node-x").Obj(), }, want: &preFilterState{ - Constraints: []topologySpreadConstraint{zoneConstraint, nodeConstraint}, - TpKeyToDomainsNum: map[string]int{"zone": 2, "node": 3}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone2", 1}, {"zone1", 3}}, - "node": {{"node-a", 1}, {"node-x", 1}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone1"}: 3, - {key: "zone", value: "zone2"}: 1, - {key: "node", value: "node-a"}: 1, - {key: "node", value: "node-b"}: 2, - {key: "node", value: "node-x"}: 1, + Constraints: []topologySpreadConstraint{zoneConstraint, nodeConstraint}, + CriticalPaths: []*criticalPaths{{{"zone2", 1}, {"zone1", 3}}, {{"node-a", 1}, {"node-x", 1}}}, + TpValueToMatchNum: []map[string]int{ + { + "zone1": 3, + "zone2": 1, + }, + { + "node-a": 1, + "node-b": 2, + "node-x": 1, + }, }, }, }, @@ -1775,17 +1744,17 @@ func TestPreFilterStateAddPod(t *testing.T) { NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, }, }, - TpKeyToDomainsNum: map[string]int{"zone": 2, "node": 3}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone2", 1}, {"zone1", 2}}, - "node": {{"node-a", 0}, {"node-b", 1}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone1"}: 2, - {key: "zone", value: "zone2"}: 1, - {key: "node", value: "node-a"}: 0, - {key: "node", value: "node-b"}: 1, - {key: "node", value: "node-x"}: 2, + CriticalPaths: []*criticalPaths{{{"zone2", 1}, {"zone1", 2}}, {{"node-a", 0}, {"node-b", 1}}}, + TpValueToMatchNum: []map[string]int{ + { + "zone1": 2, + "zone2": 1, + }, + { + "node-a": 0, + "node-b": 1, + "node-x": 2, + }, }, }, }, @@ -1819,17 +1788,17 @@ func TestPreFilterStateAddPod(t *testing.T) { NodeTaintsPolicy: v1.NodeInclusionPolicyIgnore, }, }, - TpKeyToDomainsNum: map[string]int{"zone": 2, "node": 3}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone1", 1}, {"zone2", 1}}, - "node": {{"node-a", 1}, {"node-b", 1}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone1"}: 1, - {key: "zone", value: "zone2"}: 1, - {key: "node", value: "node-a"}: 1, - {key: "node", value: "node-b"}: 1, - {key: "node", value: "node-x"}: 2, + CriticalPaths: []*criticalPaths{{{"zone1", 1}, {"zone2", 1}}, {{"node-a", 1}, {"node-b", 1}}}, + TpValueToMatchNum: []map[string]int{ + { + "zone1": 1, + "zone2": 1, + }, + { + "node-a": 1, + "node-b": 1, + "node-x": 2, + }, }, }, }, @@ -1849,14 +1818,11 @@ func TestPreFilterStateAddPod(t *testing.T) { st.MakeNode().Name("node-b").Label("zone", "zone2").Label("node", "node-b").Label("foo", "").Obj(), }, want: &preFilterState{ - Constraints: []topologySpreadConstraint{zoneConstraint}, - TpKeyToDomainsNum: map[string]int{"zone": 1}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone2", 1}, {MatchNum: math.MaxInt32}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone2"}: 1, - }, + Constraints: []topologySpreadConstraint{zoneConstraint}, + CriticalPaths: []*criticalPaths{{{"zone2", 1}, {MatchNum: math.MaxInt32}}}, + TpValueToMatchNum: []map[string]int{{ + "zone2": 1, + }}, }, enableNodeInclusionPolicy: false, }, @@ -1876,14 +1842,11 @@ func TestPreFilterStateAddPod(t *testing.T) { st.MakeNode().Name("node-b").Label("zone", "zone2").Label("node", "node-b").Label("foo", "").Obj(), }, want: &preFilterState{ - Constraints: []topologySpreadConstraint{zoneConstraint}, - TpKeyToDomainsNum: map[string]int{"zone": 1}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone2", 1}, {MatchNum: math.MaxInt32}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone2"}: 1, - }, + Constraints: []topologySpreadConstraint{zoneConstraint}, + CriticalPaths: []*criticalPaths{{{"zone2", 1}, {MatchNum: math.MaxInt32}}}, + TpValueToMatchNum: []map[string]int{{ + "zone2": 1, + }}, }, enableNodeInclusionPolicy: true, }, @@ -1903,15 +1866,12 @@ func TestPreFilterStateAddPod(t *testing.T) { st.MakeNode().Name("node-b").Label("zone", "zone2").Label("node", "node-b").Label("foo", "").Obj(), }, want: &preFilterState{ - Constraints: []topologySpreadConstraint{zoneConstraint}, - TpKeyToDomainsNum: map[string]int{"zone": 2}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone1", 1}, {"zone2", 2}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone1"}: 1, - {key: "zone", value: "zone2"}: 2, - }, + Constraints: []topologySpreadConstraint{zoneConstraint}, + CriticalPaths: []*criticalPaths{{{"zone1", 1}, {"zone2", 2}}}, + TpValueToMatchNum: []map[string]int{{ + "zone1": 1, + "zone2": 2, + }}, }, enableNodeInclusionPolicy: false, }, @@ -1931,15 +1891,12 @@ func TestPreFilterStateAddPod(t *testing.T) { st.MakeNode().Name("node-b").Label("zone", "zone2").Label("node", "node-b").Label("foo", "").Obj(), }, want: &preFilterState{ - Constraints: []topologySpreadConstraint{zoneConstraint}, - TpKeyToDomainsNum: map[string]int{"zone": 2}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone1", 1}, {"zone2", 2}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone1"}: 1, - {key: "zone", value: "zone2"}: 2, - }, + Constraints: []topologySpreadConstraint{zoneConstraint}, + CriticalPaths: []*criticalPaths{{{"zone1", 1}, {"zone2", 2}}}, + TpValueToMatchNum: []map[string]int{{ + "zone1": 1, + "zone2": 2, + }}, }, enableNodeInclusionPolicy: true, }, @@ -1959,15 +1916,12 @@ func TestPreFilterStateAddPod(t *testing.T) { st.MakeNode().Name("node-b").Label("zone", "zone2").Label("node", "node-b").Label("foo", "").Obj(), }, want: &preFilterState{ - Constraints: []topologySpreadConstraint{zoneConstraint}, - TpKeyToDomainsNum: map[string]int{"zone": 2}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone1", 1}, {"zone2", 1}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone1"}: 1, - {key: "zone", value: "zone2"}: 1, - }, + Constraints: []topologySpreadConstraint{zoneConstraint}, + CriticalPaths: []*criticalPaths{{{"zone1", 1}, {"zone2", 1}}}, + TpValueToMatchNum: []map[string]int{{ + "zone1": 1, + "zone2": 1, + }}, }, enableNodeInclusionPolicy: true, }, @@ -1987,15 +1941,12 @@ func TestPreFilterStateAddPod(t *testing.T) { st.MakeNode().Name("node-b").Label("zone", "zone2").Label("node", "node-b").Obj(), }, want: &preFilterState{ - Constraints: []topologySpreadConstraint{zoneConstraint}, - TpKeyToDomainsNum: map[string]int{"zone": 2}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone1", 1}, {"zone2", 2}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone2"}: 2, - {key: "zone", value: "zone1"}: 1, - }, + Constraints: []topologySpreadConstraint{zoneConstraint}, + CriticalPaths: []*criticalPaths{{{"zone1", 1}, {"zone2", 2}}}, + TpValueToMatchNum: []map[string]int{{ + "zone2": 2, + "zone1": 1, + }}, }, enableNodeInclusionPolicy: false, }, @@ -2015,15 +1966,12 @@ func TestPreFilterStateAddPod(t *testing.T) { st.MakeNode().Name("node-b").Label("zone", "zone2").Label("node", "node-b").Obj(), }, want: &preFilterState{ - Constraints: []topologySpreadConstraint{zoneConstraint}, - TpKeyToDomainsNum: map[string]int{"zone": 2}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone1", 1}, {"zone2", 2}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone2"}: 2, - {key: "zone", value: "zone1"}: 1, - }, + Constraints: []topologySpreadConstraint{zoneConstraint}, + CriticalPaths: []*criticalPaths{{{"zone1", 1}, {"zone2", 2}}}, + TpValueToMatchNum: []map[string]int{{ + "zone2": 2, + "zone1": 1, + }}, }, enableNodeInclusionPolicy: true, }, @@ -2100,15 +2048,12 @@ func TestPreFilterStateRemovePod(t *testing.T) { deletedPodIdx: 0, // remove pod "p-a1" nodeIdx: 0, // node-a want: &preFilterState{ - Constraints: []topologySpreadConstraint{zoneConstraint}, - TpKeyToDomainsNum: map[string]int{"zone": 2}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone1", 1}, {"zone2", 1}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone1"}: 1, - {key: "zone", value: "zone2"}: 1, - }, + Constraints: []topologySpreadConstraint{zoneConstraint}, + CriticalPaths: []*criticalPaths{{{"zone1", 1}, {"zone2", 1}}}, + TpValueToMatchNum: []map[string]int{{ + "zone1": 1, + "zone2": 1, + }}, }, }, { @@ -2131,15 +2076,12 @@ func TestPreFilterStateRemovePod(t *testing.T) { deletedPodIdx: 0, // remove pod "p-a1" nodeIdx: 0, // node-a want: &preFilterState{ - Constraints: []topologySpreadConstraint{zoneConstraint}, - TpKeyToDomainsNum: map[string]int{"zone": 2}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone1", 1}, {"zone2", 2}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone1"}: 1, - {key: "zone", value: "zone2"}: 2, - }, + Constraints: []topologySpreadConstraint{zoneConstraint}, + CriticalPaths: []*criticalPaths{{{"zone1", 1}, {"zone2", 2}}}, + TpValueToMatchNum: []map[string]int{{ + "zone1": 1, + "zone2": 2, + }}, }, }, { @@ -2163,15 +2105,12 @@ func TestPreFilterStateRemovePod(t *testing.T) { deletedPodIdx: 0, // remove pod "p-a0" nodeIdx: 0, // node-a want: &preFilterState{ - Constraints: []topologySpreadConstraint{zoneConstraint}, - TpKeyToDomainsNum: map[string]int{"zone": 2}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone1", 2}, {"zone2", 2}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone1"}: 2, - {key: "zone", value: "zone2"}: 2, - }, + Constraints: []topologySpreadConstraint{zoneConstraint}, + CriticalPaths: []*criticalPaths{{{"zone1", 2}, {"zone2", 2}}}, + TpValueToMatchNum: []map[string]int{{ + "zone1": 2, + "zone2": 2, + }}, }, }, { @@ -2195,15 +2134,12 @@ func TestPreFilterStateRemovePod(t *testing.T) { deletedPod: st.MakePod().Name("p-a0").Node("node-a").Label("bar", "").Obj(), nodeIdx: 0, // node-a want: &preFilterState{ - Constraints: []topologySpreadConstraint{zoneConstraint}, - TpKeyToDomainsNum: map[string]int{"zone": 2}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone1", 2}, {"zone2", 2}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone1"}: 2, - {key: "zone", value: "zone2"}: 2, - }, + Constraints: []topologySpreadConstraint{zoneConstraint}, + CriticalPaths: []*criticalPaths{{{"zone1", 2}, {"zone2", 2}}}, + TpValueToMatchNum: []map[string]int{{ + "zone1": 2, + "zone2": 2, + }}, }, }, { @@ -2227,18 +2163,18 @@ func TestPreFilterStateRemovePod(t *testing.T) { deletedPodIdx: 3, // remove pod "p-x1" nodeIdx: 2, // node-x want: &preFilterState{ - Constraints: []topologySpreadConstraint{zoneConstraint, nodeConstraint}, - TpKeyToDomainsNum: map[string]int{"zone": 2, "node": 3}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone2", 1}, {"zone1", 3}}, - "node": {{"node-b", 1}, {"node-x", 1}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone1"}: 3, - {key: "zone", value: "zone2"}: 1, - {key: "node", value: "node-a"}: 2, - {key: "node", value: "node-b"}: 1, - {key: "node", value: "node-x"}: 1, + Constraints: []topologySpreadConstraint{zoneConstraint, nodeConstraint}, + CriticalPaths: []*criticalPaths{{{"zone2", 1}, {"zone1", 3}}, {{"node-b", 1}, {"node-x", 1}}}, + TpValueToMatchNum: []map[string]int{ + { + "zone1": 3, + "zone2": 1, + }, + { + "node-a": 2, + "node-b": 1, + "node-x": 1, + }, }, }, }, @@ -2258,14 +2194,11 @@ func TestPreFilterStateRemovePod(t *testing.T) { st.MakeNode().Name("node-b").Label("zone", "zone2").Label("node", "node-b").Label("foo", "").Obj(), }, want: &preFilterState{ - Constraints: []topologySpreadConstraint{zoneConstraint}, - TpKeyToDomainsNum: map[string]int{"zone": 1}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone2", 1}, {MatchNum: math.MaxInt32}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone2"}: 1, - }, + Constraints: []topologySpreadConstraint{zoneConstraint}, + CriticalPaths: []*criticalPaths{{{"zone2", 1}, {MatchNum: math.MaxInt32}}}, + TpValueToMatchNum: []map[string]int{{ + "zone2": 1, + }}, }, enableNodeInclusionPolicy: false, }, @@ -2285,14 +2218,11 @@ func TestPreFilterStateRemovePod(t *testing.T) { st.MakeNode().Name("node-b").Label("zone", "zone2").Label("node", "node-b").Label("foo", "").Obj(), }, want: &preFilterState{ - Constraints: []topologySpreadConstraint{zoneConstraint}, - TpKeyToDomainsNum: map[string]int{"zone": 1}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone2", 1}, {MatchNum: math.MaxInt32}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone2"}: 1, - }, + Constraints: []topologySpreadConstraint{zoneConstraint}, + CriticalPaths: []*criticalPaths{{{"zone2", 1}, {MatchNum: math.MaxInt32}}}, + TpValueToMatchNum: []map[string]int{{ + "zone2": 1, + }}, }, enableNodeInclusionPolicy: true, }, @@ -2312,15 +2242,12 @@ func TestPreFilterStateRemovePod(t *testing.T) { st.MakeNode().Name("node-b").Label("zone", "zone2").Label("node", "node-b").Label("foo", "").Obj(), }, want: &preFilterState{ - Constraints: []topologySpreadConstraint{zoneConstraint}, - TpKeyToDomainsNum: map[string]int{"zone": 2}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone2", 0}, {"zone1", 1}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone2"}: 0, - {key: "zone", value: "zone1"}: 1, - }, + Constraints: []topologySpreadConstraint{zoneConstraint}, + CriticalPaths: []*criticalPaths{{{"zone2", 0}, {"zone1", 1}}}, + TpValueToMatchNum: []map[string]int{{ + "zone2": 0, + "zone1": 1, + }}, }, enableNodeInclusionPolicy: false, }, @@ -2340,15 +2267,12 @@ func TestPreFilterStateRemovePod(t *testing.T) { st.MakeNode().Name("node-b").Label("zone", "zone2").Label("node", "node-b").Label("foo", "").Obj(), }, want: &preFilterState{ - Constraints: []topologySpreadConstraint{zoneConstraint}, - TpKeyToDomainsNum: map[string]int{"zone": 2}, - TpKeyToCriticalPaths: map[string]*criticalPaths{ - "zone": {{"zone2", 0}, {"zone1", 1}}, - }, - TpPairToMatchNum: map[topologyPair]int{ - {key: "zone", value: "zone2"}: 0, - {key: "zone", value: "zone1"}: 1, - }, + Constraints: []topologySpreadConstraint{zoneConstraint}, + CriticalPaths: []*criticalPaths{{{"zone2", 0}, {"zone1", 1}}}, + TpValueToMatchNum: []map[string]int{{ + "zone2": 0, + "zone1": 1, + }}, }, enableNodeInclusionPolicy: true, }, @@ -3397,6 +3321,67 @@ func TestMultipleConstraints(t *testing.T) { }, enableNodeInclusionPolicy: true, }, + { + // 1. to fulfil first "zone" constraint, incoming pod can be placed on zone2 (node-x or node-y) + // 2. to fulfil second "zone" constraint, incoming pod can be placed on any zone + // intersection of (1) and (2) returns node-x and node-y + name: "two Constraints on zone, spreads = [3/3, 2/1]", + pod: st.MakePod().Name("p").Label("foo", "").Label("bar", ""). + SpreadConstraint(1, "zone", v1.DoNotSchedule, barSelector, nil, nil, nil, nil). + SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). + Obj(), + nodes: []*v1.Node{ + st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), + st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").Obj(), + st.MakeNode().Name("node-x").Label("zone", "zone2").Label("node", "node-x").Obj(), + st.MakeNode().Name("node-y").Label("zone", "zone2").Label("node", "node-y").Obj(), + }, + existingPods: []*v1.Pod{ + st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Label("bar", "").Obj(), + st.MakePod().Name("p-a2").Node("node-a").Label("foo", "").Obj(), + st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Label("bar", "").Obj(), + st.MakePod().Name("p-y1").Node("node-y").Label("foo", "").Obj(), + st.MakePod().Name("p-y2").Node("node-y").Label("foo", "").Label("bar", "").Obj(), + st.MakePod().Name("p-y3").Node("node-y").Label("foo", "").Obj(), + }, + wantStatusCode: map[string]framework.Code{ + "node-a": framework.Unschedulable, + "node-b": framework.Unschedulable, + "node-x": framework.Success, + "node-y": framework.Success, + }, + }, + { + // 1. to fulfil first "zone" constraint, incoming pod can be placed on zone2 (node-x or node-y) + // 2. to fulfil second "zone" constraint, incoming pod can be placed on zone1 (node-a or node-b) + // intersection of (1) and (2) returns no node + name: "two Constraints on zone, spreads = [3/4, 2/1]", + pod: st.MakePod().Name("p").Label("foo", "").Label("bar", ""). + SpreadConstraint(1, "zone", v1.DoNotSchedule, barSelector, nil, nil, nil, nil). + SpreadConstraint(1, "zone", v1.DoNotSchedule, fooSelector, nil, nil, nil, nil). + Obj(), + nodes: []*v1.Node{ + st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), + st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").Obj(), + st.MakeNode().Name("node-x").Label("zone", "zone2").Label("node", "node-x").Obj(), + st.MakeNode().Name("node-y").Label("zone", "zone2").Label("node", "node-y").Obj(), + }, + existingPods: []*v1.Pod{ + st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Label("bar", "").Obj(), + st.MakePod().Name("p-a2").Node("node-a").Label("foo", "").Obj(), + st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Label("bar", "").Obj(), + st.MakePod().Name("p-y1").Node("node-y").Label("foo", "").Obj(), + st.MakePod().Name("p-y2").Node("node-y").Label("foo", "").Label("bar", "").Obj(), + st.MakePod().Name("p-y3").Node("node-y").Label("foo", "").Obj(), + st.MakePod().Name("p-y4").Node("node-y").Label("foo", "").Obj(), + }, + wantStatusCode: map[string]framework.Code{ + "node-a": framework.Unschedulable, + "node-b": framework.Unschedulable, + "node-x": framework.Unschedulable, + "node-y": framework.Unschedulable, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/scheduler/framework/plugins/podtopologyspread/scoring.go b/pkg/scheduler/framework/plugins/podtopologyspread/scoring.go index b377ce6a517..d18dd3b6a9b 100644 --- a/pkg/scheduler/framework/plugins/podtopologyspread/scoring.go +++ b/pkg/scheduler/framework/plugins/podtopologyspread/scoring.go @@ -37,8 +37,9 @@ type preScoreState struct { Constraints []topologySpreadConstraint // IgnoredNodes is a set of node names which miss some Constraints[*].topologyKey. IgnoredNodes sets.Set[string] - // TopologyPairToPodCounts is keyed with topologyPair, and valued with the number of matching pods. - TopologyPairToPodCounts map[topologyPair]*int64 + // TopologyValueToPodCounts is a slice indexed by constraint index. + // Each entry is keyed with topology value, and valued with the number of matching pods. + TopologyValueToPodCounts []map[string]*int64 // TopologyNormalizingWeight is the weight we give to the counts per topology. // This allows the pod counts of smaller topologies to not be watered down by // bigger ones. @@ -76,6 +77,10 @@ func (pl *PodTopologySpread) initPreScoreState(s *preScoreState, pod *v1.Pod, fi if len(s.Constraints) == 0 { return nil } + s.TopologyValueToPodCounts = make([]map[string]*int64, len(s.Constraints)) + for i := 0; i < len(s.Constraints); i++ { + s.TopologyValueToPodCounts[i] = make(map[string]*int64) + } topoSize := make([]int, len(s.Constraints)) for _, node := range filteredNodes { if requireAllTopologies && !nodeLabelsMatchSpreadConstraints(node.Node().Labels, s.Constraints) { @@ -89,9 +94,9 @@ func (pl *PodTopologySpread) initPreScoreState(s *preScoreState, pod *v1.Pod, fi if constraint.TopologyKey == v1.LabelHostname { continue } - pair := topologyPair{key: constraint.TopologyKey, value: node.Node().Labels[constraint.TopologyKey]} - if s.TopologyPairToPodCounts[pair] == nil { - s.TopologyPairToPodCounts[pair] = new(int64) + value := node.Node().Labels[constraint.TopologyKey] + if s.TopologyValueToPodCounts[i][value] == nil { + s.TopologyValueToPodCounts[i][value] = new(int64) topoSize[i]++ } } @@ -126,8 +131,7 @@ func (pl *PodTopologySpread) PreScore( } state := &preScoreState{ - IgnoredNodes: sets.New[string](), - TopologyPairToPodCounts: make(map[topologyPair]*int64), + IgnoredNodes: sets.New[string](), } // Only require that nodes have all the topology labels if using // non-system-default spreading rules. This allows nodes that don't have a @@ -145,8 +149,8 @@ func (pl *PodTopologySpread) PreScore( // Ignore parsing errors for backwards compatibility. requiredNodeAffinity := nodeaffinity.GetRequiredNodeAffinity(pod) - processAllNode := func(i int) { - nodeInfo := allNodes[i] + processAllNode := func(n int) { + nodeInfo := allNodes[n] node := nodeInfo.Node() if !pl.enableNodeInclusionPolicyInPodTopologySpread { @@ -161,17 +165,17 @@ func (pl *PodTopologySpread) PreScore( return } - for _, c := range state.Constraints { + for i, c := range state.Constraints { if pl.enableNodeInclusionPolicyInPodTopologySpread && !c.matchNodeInclusionPolicies(pod, node, requiredNodeAffinity) { continue } - pair := topologyPair{key: c.TopologyKey, value: node.Labels[c.TopologyKey]} + value := node.Labels[c.TopologyKey] // If current topology pair is not associated with any candidate node, // continue to avoid unnecessary calculation. // Per-node counts are also skipped, as they are done during Score. - tpCount := state.TopologyPairToPodCounts[pair] + tpCount := state.TopologyValueToPodCounts[i][value] if tpCount == nil { continue } @@ -214,8 +218,7 @@ func (pl *PodTopologySpread) Score(ctx context.Context, cycleState *framework.Cy if c.TopologyKey == v1.LabelHostname { cnt = int64(countPodsMatchSelector(nodeInfo.Pods, c.Selector, pod.Namespace)) } else { - pair := topologyPair{key: c.TopologyKey, value: tpVal} - cnt = *s.TopologyPairToPodCounts[pair] + cnt = *s.TopologyValueToPodCounts[i][tpVal] } score += scoreForCount(cnt, c.MaxSkew, s.TopologyNormalizingWeight[i]) } diff --git a/pkg/scheduler/framework/plugins/podtopologyspread/scoring_test.go b/pkg/scheduler/framework/plugins/podtopologyspread/scoring_test.go index c38e0ed14c5..56a1625c613 100644 --- a/pkg/scheduler/framework/plugins/podtopologyspread/scoring_test.go +++ b/pkg/scheduler/framework/plugins/podtopologyspread/scoring_test.go @@ -156,9 +156,12 @@ func TestPreScoreStateEmptyNodes(t *testing.T) { }, }, IgnoredNodes: sets.New[string](), - TopologyPairToPodCounts: map[topologyPair]*int64{ - {key: "zone", value: "zone1"}: ptr.To[int64](0), - {key: "zone", value: "zone2"}: ptr.To[int64](0), + TopologyValueToPodCounts: []map[string]*int64{ + { + "zone1": ptr.To[int64](0), + "zone2": ptr.To[int64](0), + }, + {}, }, TopologyNormalizingWeight: []float64{topologyNormalizingWeight(2), topologyNormalizingWeight(3)}, }, @@ -188,10 +191,10 @@ func TestPreScoreStateEmptyNodes(t *testing.T) { }, }, IgnoredNodes: sets.New[string](), - TopologyPairToPodCounts: map[topologyPair]*int64{ - {key: "zone", value: "zone1"}: ptr.To[int64](0), - {key: "zone", value: "zone2"}: ptr.To[int64](0), - }, + TopologyValueToPodCounts: []map[string]*int64{{ + "zone1": ptr.To[int64](0), + "zone2": ptr.To[int64](0), + }}, TopologyNormalizingWeight: []float64{topologyNormalizingWeight(2)}, }, }, @@ -229,8 +232,11 @@ func TestPreScoreStateEmptyNodes(t *testing.T) { }, }, IgnoredNodes: sets.New("node-x"), - TopologyPairToPodCounts: map[topologyPair]*int64{ - {key: "zone", value: "zone1"}: ptr.To[int64](0), + TopologyValueToPodCounts: []map[string]*int64{ + { + "zone1": ptr.To[int64](0), + }, + {}, }, TopologyNormalizingWeight: []float64{topologyNormalizingWeight(1), topologyNormalizingWeight(2)}, }, @@ -271,9 +277,12 @@ func TestPreScoreStateEmptyNodes(t *testing.T) { }, }, IgnoredNodes: sets.New[string](), - TopologyPairToPodCounts: map[topologyPair]*int64{ - {key: v1.LabelTopologyZone, value: "mars"}: ptr.To[int64](0), - {key: v1.LabelTopologyZone, value: ""}: ptr.To[int64](0), + TopologyValueToPodCounts: []map[string]*int64{ + {}, + { + "mars": ptr.To[int64](0), + "": ptr.To[int64](0), + }, }, TopologyNormalizingWeight: []float64{topologyNormalizingWeight(4), topologyNormalizingWeight(2)}, }, @@ -322,8 +331,11 @@ func TestPreScoreStateEmptyNodes(t *testing.T) { }, }, IgnoredNodes: sets.New[string](), - TopologyPairToPodCounts: map[topologyPair]*int64{ - {key: "planet", value: "mars"}: ptr.To[int64](0), + TopologyValueToPodCounts: []map[string]*int64{ + {}, + { + "mars": ptr.To[int64](0), + }, }, TopologyNormalizingWeight: []float64{topologyNormalizingWeight(1), topologyNormalizingWeight(1)}, }, @@ -363,9 +375,9 @@ func TestPreScoreStateEmptyNodes(t *testing.T) { }, }, IgnoredNodes: sets.New[string](), - TopologyPairToPodCounts: map[topologyPair]*int64{ - {"planet", "mars"}: ptr.To[int64](0), - }, + TopologyValueToPodCounts: []map[string]*int64{{ + "mars": ptr.To[int64](0), + }}, TopologyNormalizingWeight: []float64{topologyNormalizingWeight(1)}, }, }, @@ -395,10 +407,10 @@ func TestPreScoreStateEmptyNodes(t *testing.T) { }, }, IgnoredNodes: sets.New[string](), - TopologyPairToPodCounts: map[topologyPair]*int64{ - {key: "zone", value: "zone1"}: ptr.To[int64](0), - {key: "zone", value: "zone2"}: ptr.To[int64](0), - }, + TopologyValueToPodCounts: []map[string]*int64{{ + "zone1": ptr.To[int64](0), + "zone2": ptr.To[int64](0), + }}, TopologyNormalizingWeight: []float64{topologyNormalizingWeight(2)}, }, enableNodeInclusionPolicy: true, @@ -429,10 +441,10 @@ func TestPreScoreStateEmptyNodes(t *testing.T) { }, }, IgnoredNodes: sets.New[string](), - TopologyPairToPodCounts: map[topologyPair]*int64{ - {key: "zone", value: "zone1"}: ptr.To[int64](0), - {key: "zone", value: "zone2"}: ptr.To[int64](0), - }, + TopologyValueToPodCounts: []map[string]*int64{{ + "zone1": ptr.To[int64](0), + "zone2": ptr.To[int64](0), + }}, TopologyNormalizingWeight: []float64{topologyNormalizingWeight(2)}, }, enableNodeInclusionPolicy: true, @@ -463,10 +475,10 @@ func TestPreScoreStateEmptyNodes(t *testing.T) { }, }, IgnoredNodes: sets.New[string](), - TopologyPairToPodCounts: map[topologyPair]*int64{ - {key: "zone", value: "zone1"}: ptr.To[int64](0), - {key: "zone", value: "zone2"}: ptr.To[int64](0), - }, + TopologyValueToPodCounts: []map[string]*int64{{ + "zone1": ptr.To[int64](0), + "zone2": ptr.To[int64](0), + }}, TopologyNormalizingWeight: []float64{topologyNormalizingWeight(2)}, }, enableNodeInclusionPolicy: true, @@ -497,10 +509,10 @@ func TestPreScoreStateEmptyNodes(t *testing.T) { }, }, IgnoredNodes: sets.New[string](), - TopologyPairToPodCounts: map[topologyPair]*int64{ - {key: "zone", value: "zone1"}: ptr.To[int64](0), - {key: "zone", value: "zone2"}: ptr.To[int64](0), - }, + TopologyValueToPodCounts: []map[string]*int64{{ + "zone1": ptr.To[int64](0), + "zone2": ptr.To[int64](0), + }}, TopologyNormalizingWeight: []float64{topologyNormalizingWeight(2)}, }, enableNodeInclusionPolicy: true, @@ -530,10 +542,10 @@ func TestPreScoreStateEmptyNodes(t *testing.T) { }, }, IgnoredNodes: sets.New[string](), - TopologyPairToPodCounts: map[topologyPair]*int64{ - {key: "zone", value: "zone1"}: ptr.To[int64](0), - {key: "zone", value: "zone2"}: ptr.To[int64](0), - }, + TopologyValueToPodCounts: []map[string]*int64{{ + "zone1": ptr.To[int64](0), + "zone2": ptr.To[int64](0), + }}, TopologyNormalizingWeight: []float64{topologyNormalizingWeight(2)}, }, enableNodeInclusionPolicy: true, @@ -563,10 +575,10 @@ func TestPreScoreStateEmptyNodes(t *testing.T) { }, }, IgnoredNodes: sets.New[string](), - TopologyPairToPodCounts: map[topologyPair]*int64{ - {key: "zone", value: "zone1"}: ptr.To[int64](0), - {key: "zone", value: "zone2"}: ptr.To[int64](0), - }, + TopologyValueToPodCounts: []map[string]*int64{{ + "zone1": ptr.To[int64](0), + "zone2": ptr.To[int64](0), + }}, TopologyNormalizingWeight: []float64{topologyNormalizingWeight(2)}, }, enableNodeInclusionPolicy: true, @@ -953,6 +965,33 @@ func TestPodTopologySpreadScore(t *testing.T) { {Name: "node-x", Score: 63}, }, }, + { + name: "two Constraints on zone, 2 out of 4 nodes are candidates", + pod: st.MakePod().Name("p").Label("foo", "").Label("bar", ""). + SpreadConstraint(1, "zone", v1.ScheduleAnyway, fooSelector, nil, nil, nil, nil). + SpreadConstraint(1, "zone", v1.ScheduleAnyway, barSelector, nil, nil, nil, nil). + Obj(), + existingPods: []*v1.Pod{ + st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Label("bar", "").Obj(), + st.MakePod().Name("p-a2").Node("node-a").Label("foo", "").Obj(), + st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Label("bar", "").Obj(), + st.MakePod().Name("p-y1").Node("node-y").Label("foo", "").Obj(), + st.MakePod().Name("p-y2").Node("node-y").Label("foo", "").Label("bar", "").Obj(), + st.MakePod().Name("p-y3").Node("node-y").Label("foo", "").Obj(), + }, + nodes: []*v1.Node{ + st.MakeNode().Name("node-a").Label("zone", "zone1").Obj(), + st.MakeNode().Name("node-x").Label("zone", "zone2").Obj(), + }, + failedNodes: []*v1.Node{ + st.MakeNode().Name("node-b").Label("zone", "zone1").Obj(), + st.MakeNode().Name("node-y").Label("zone", "zone2").Obj(), + }, + want: []framework.NodeScore{ + {Name: "node-a", Score: 85}, + {Name: "node-x", Score: 100}, + }, + }, { // If Constraints hold different labelSelectors, it's a little complex. // +----------------------+------------------------+