Optimize internal data structure of EvenPodsSpread

- Rename 'topologyPairsPodSpreadMap' to 'podSpreadCache'
- New struct `criticalPaths criticalPaths`
- Add unified method `*criticalPaths.update()` for:
    - regular update
    - addPod in preemption case
    - remotePod in preemption case
This commit is contained in:
Wei Huang 2019-08-16 09:20:11 -07:00
parent 7f1a3965fd
commit 8f559ea53b
No known key found for this signature in database
GPG Key ID: BE5E9752F8B6E005
6 changed files with 462 additions and 613 deletions

View File

@ -39,7 +39,7 @@ import (
type PredicateMetadata interface { type PredicateMetadata interface {
ShallowCopy() PredicateMetadata ShallowCopy() PredicateMetadata
AddPod(addedPod *v1.Pod, nodeInfo *schedulernodeinfo.NodeInfo) error AddPod(addedPod *v1.Pod, nodeInfo *schedulernodeinfo.NodeInfo) error
RemovePod(deletedPod *v1.Pod) error RemovePod(deletedPod *v1.Pod, node *v1.Node) error
} }
// PredicateMetadataProducer is a function that computes predicate metadata for a given pod. // PredicateMetadataProducer is a function that computes predicate metadata for a given pod.
@ -67,17 +67,67 @@ type topologyPairsMaps struct {
podToTopologyPairs map[string]topologyPairSet podToTopologyPairs map[string]topologyPairSet
} }
// topologyPairsPodSpreadMap combines topologyKeyToMinPodsMap and topologyPairsMaps type criticalPath struct {
// topologyValue denotes the topology value mapping to topology key.
topologyValue string
// matchNum denotes the number of matching pods.
matchNum int32
}
// CAVEAT: the reason that `[2]criticalPath` can work is based on the implementation of current
// preemption algorithm, in particular the following 2 facts:
// Fact 1: we only preempt pods on the same node, instead of pods on multiple nodes.
// Fact 2: each node is evaluated on a separate copy of the metadata during its preemption cycle.
// If we plan to turn to a more complex algorithm like "arbitrary pods on multiple nodes", this
// structure needs to be revisited.
type criticalPaths [2]criticalPath
func newCriticalPaths() *criticalPaths {
return &criticalPaths{{matchNum: math.MaxInt32}, {matchNum: math.MaxInt32}}
}
func (paths *criticalPaths) update(tpVal string, num int32) {
// first verify if `tpVal` exists or not
i := -1
if tpVal == paths[0].topologyValue {
i = 0
} else if tpVal == paths[1].topologyValue {
i = 1
}
if i >= 0 {
// `tpVal` exists
paths[i].matchNum = num
if paths[0].matchNum > paths[1].matchNum {
// swap paths[0] and paths[1]
paths[0], paths[1] = paths[1], paths[0]
}
} else {
// `tpVal` doesn't exist
if num < paths[0].matchNum {
// update paths[1] with paths[0]
paths[1] = paths[0]
// update paths[0]
paths[0].topologyValue, paths[0].matchNum = tpVal, num
} else if num < paths[1].matchNum {
// update paths[1]
paths[1].topologyValue, paths[1].matchNum = tpVal, num
}
}
}
// podSpreadCache combines tpKeyToCriticalPaths and tpPairToMatchNum
// to represent: // to represent:
// (1) minimum number of pods matched on the spread constraints. // (1) critical paths where the least pods are matched on each spread constraint.
// (2) how existing pods match incoming pod on its spread constraints. // (2) number of pods matched on each spread constraint.
type topologyPairsPodSpreadMap struct { type podSpreadCache struct {
// This map is keyed with a topology key, and valued with minimum number // We record 2 critical paths instead of all critical paths here.
// of pods matched on that topology domain. // criticalPaths[0].matchNum always holds the minimum matching number.
// TODO(Huang-Wei): refactor to {tpKey->tpValSet(or tpValSlice)} // criticalPaths[1].matchNum is always greater or equal to criticalPaths[0].matchNum, but
topologyKeyToMinPodsMap map[string]int32 // it's not guaranteed to be the 2nd minimum match number.
// TODO(Huang-Wei): refactor to {tpPair->count, podName->tpPairSet(optional)} tpKeyToCriticalPaths map[string]*criticalPaths
*topologyPairsMaps // tpPairToMatchNum is keyed with topologyPair, and valued with the number of matching pods.
tpPairToMatchNum map[topologyPair]int32
} }
// NOTE: When new fields are added/removed or logic is changed, please make sure that // NOTE: When new fields are added/removed or logic is changed, please make sure that
@ -105,9 +155,9 @@ type predicateMetadata struct {
// which should be accounted only by the extenders. This set is synthesized // which should be accounted only by the extenders. This set is synthesized
// from scheduler extender configuration and does not change per pod. // from scheduler extender configuration and does not change per pod.
ignoredExtendedResources sets.String ignoredExtendedResources sets.String
// Similar to the map for pod (anti-)affinity, but imposes additional min matches info // podSpreadCache holds info of the minimum match number on each topology spread constraint,
// to describe minimum match number on each topology spread constraint. // and the match number of all valid topology pairs.
topologyPairsPodSpreadMap *topologyPairsPodSpreadMap podSpreadCache *podSpreadCache
} }
// Ensure that predicateMetadata implements algorithm.PredicateMetadata. // Ensure that predicateMetadata implements algorithm.PredicateMetadata.
@ -154,9 +204,9 @@ func (pfactory *PredicateMetadataFactory) GetMetadata(pod *v1.Pod, nodeNameToInf
if pod == nil { if pod == nil {
return nil return nil
} }
// existingPodSpreadConstraintsMap represents how existing pods match "pod" // existingPodSpreadCache represents how existing pods match "pod"
// on its spread constraints // on its spread constraints
existingPodSpreadConstraintsMap, err := getTPMapMatchingSpreadConstraints(pod, nodeNameToInfoMap) existingPodSpreadCache, err := getExistingPodSpreadCache(pod, nodeNameToInfoMap)
if err != nil { if err != nil {
klog.Errorf("Error calculating spreadConstraintsMap: %v", err) klog.Errorf("Error calculating spreadConstraintsMap: %v", err)
return nil return nil
@ -182,7 +232,7 @@ func (pfactory *PredicateMetadataFactory) GetMetadata(pod *v1.Pod, nodeNameToInf
topologyPairsPotentialAffinityPods: incomingPodAffinityMap, topologyPairsPotentialAffinityPods: incomingPodAffinityMap,
topologyPairsPotentialAntiAffinityPods: incomingPodAntiAffinityMap, topologyPairsPotentialAntiAffinityPods: incomingPodAntiAffinityMap,
topologyPairsAntiAffinityPodsMap: existingPodAntiAffinityMap, topologyPairsAntiAffinityPodsMap: existingPodAntiAffinityMap,
topologyPairsPodSpreadMap: existingPodSpreadConstraintsMap, podSpreadCache: existingPodSpreadCache,
} }
for predicateName, precomputeFunc := range predicateMetadataProducers { for predicateName, precomputeFunc := range predicateMetadataProducers {
klog.V(10).Infof("Precompute: %v", predicateName) klog.V(10).Infof("Precompute: %v", predicateName)
@ -191,7 +241,7 @@ func (pfactory *PredicateMetadataFactory) GetMetadata(pod *v1.Pod, nodeNameToInf
return predicateMetadata return predicateMetadata
} }
func getTPMapMatchingSpreadConstraints(pod *v1.Pod, nodeInfoMap map[string]*schedulernodeinfo.NodeInfo) (*topologyPairsPodSpreadMap, error) { func getExistingPodSpreadCache(pod *v1.Pod, nodeInfoMap map[string]*schedulernodeinfo.NodeInfo) (*podSpreadCache, error) {
// We have feature gating in APIServer to strip the spec // We have feature gating in APIServer to strip the spec
// so don't need to re-check feature gate, just check length of constraints. // so don't need to re-check feature gate, just check length of constraints.
constraints := getHardTopologySpreadConstraints(pod) constraints := getHardTopologySpreadConstraints(pod)
@ -207,14 +257,15 @@ func getTPMapMatchingSpreadConstraints(pod *v1.Pod, nodeInfoMap map[string]*sche
errCh := schedutil.NewErrorChannel() errCh := schedutil.NewErrorChannel()
var lock sync.Mutex var lock sync.Mutex
topologyPairsPodSpreadMap := &topologyPairsPodSpreadMap{ // TODO(Huang-Wei): It might be possible to use "make(map[topologyPair]*int32)".
// topologyKeyToMinPodsMap will be initialized with proper size later. // In that case, need to consider how to init each tpPairToCount[pair] in an atomic fashion.
topologyPairsMaps: newTopologyPairsMaps(), m := podSpreadCache{
tpKeyToCriticalPaths: make(map[string]*criticalPaths, len(constraints)),
tpPairToMatchNum: make(map[topologyPair]int32),
} }
addTopologyPairMatchNum := func(pair topologyPair, num int32) {
appendTopologyPairsMaps := func(toAppend *topologyPairsMaps) {
lock.Lock() lock.Lock()
topologyPairsPodSpreadMap.appendMaps(toAppend) m.tpPairToMatchNum[pair] += num
lock.Unlock() lock.Unlock()
} }
@ -237,9 +288,8 @@ func getTPMapMatchingSpreadConstraints(pod *v1.Pod, nodeInfoMap map[string]*sche
if !NodeLabelsMatchSpreadConstraints(node.Labels, constraints) { if !NodeLabelsMatchSpreadConstraints(node.Labels, constraints) {
return return
} }
nodeTopologyMaps := newTopologyPairsMaps()
for _, constraint := range constraints { for _, constraint := range constraints {
pairAdded := false matchTotal := int32(0)
// nodeInfo.Pods() can be empty; or all pods don't fit // nodeInfo.Pods() can be empty; or all pods don't fit
for _, existingPod := range nodeInfo.Pods() { for _, existingPod := range nodeInfo.Pods() {
if existingPod.Namespace != pod.Namespace { if existingPod.Namespace != pod.Namespace {
@ -251,26 +301,12 @@ func getTPMapMatchingSpreadConstraints(pod *v1.Pod, nodeInfoMap map[string]*sche
return return
} }
if ok { if ok {
// constraint.TopologyKey is already guaranteed to be present matchTotal++
pair := topologyPair{key: constraint.TopologyKey, value: node.Labels[constraint.TopologyKey]}
nodeTopologyMaps.addTopologyPair(pair, existingPod)
pairAdded = true
} }
} }
// If needed, append topology pair without entry of pods. pair := topologyPair{key: constraint.TopologyKey, value: node.Labels[constraint.TopologyKey]}
// For example, on node-x, there is no pod matching spread constraints, addTopologyPairMatchNum(pair, matchTotal)
// but node-x should be also considered as a match (with match number 0)
// i.e. <node: node-x>: {}
if !pairAdded {
pair := topologyPair{
key: constraint.TopologyKey,
value: node.Labels[constraint.TopologyKey],
}
nodeTopologyMaps.addTopologyPairWithoutPods(pair)
}
} }
appendTopologyPairsMaps(nodeTopologyMaps)
} }
workqueue.ParallelizeUntil(ctx, 16, len(allNodeNames), processNode) workqueue.ParallelizeUntil(ctx, 16, len(allNodeNames), processNode)
@ -279,18 +315,15 @@ func getTPMapMatchingSpreadConstraints(pod *v1.Pod, nodeInfoMap map[string]*sche
} }
// calculate min match for each topology pair // calculate min match for each topology pair
topologyPairsPodSpreadMap.topologyKeyToMinPodsMap = make(map[string]int32, len(constraints)) for i := 0; i < len(constraints); i++ {
for _, constraint := range constraints { key := constraints[i].TopologyKey
topologyPairsPodSpreadMap.topologyKeyToMinPodsMap[constraint.TopologyKey] = math.MaxInt32 m.tpKeyToCriticalPaths[key] = newCriticalPaths()
} }
for pair, podSet := range topologyPairsPodSpreadMap.topologyPairToPods { for pair, num := range m.tpPairToMatchNum {
// TODO(Huang-Wei): short circuit unvisited portions of <topologyKey: any value> m.tpKeyToCriticalPaths[pair.key].update(pair.value, num)
// if we already see 0 as min match of that topologyKey.
if l := int32(len(podSet)); l < topologyPairsPodSpreadMap.topologyKeyToMinPodsMap[pair.key] {
topologyPairsPodSpreadMap.topologyKeyToMinPodsMap[pair.key] = l
}
} }
return topologyPairsPodSpreadMap, nil
return &m, nil
} }
func getHardTopologySpreadConstraints(pod *v1.Pod) (constraints []v1.TopologySpreadConstraint) { func getHardTopologySpreadConstraints(pod *v1.Pod) (constraints []v1.TopologySpreadConstraint) {
@ -337,7 +370,9 @@ func newTopologyPairsMaps() *topologyPairsMaps {
func (m *topologyPairsMaps) addTopologyPair(pair topologyPair, pod *v1.Pod) { func (m *topologyPairsMaps) addTopologyPair(pair topologyPair, pod *v1.Pod) {
podFullName := schedutil.GetPodFullName(pod) podFullName := schedutil.GetPodFullName(pod)
m.addTopologyPairWithoutPods(pair) if m.topologyPairToPods[pair] == nil {
m.topologyPairToPods[pair] = make(map[*v1.Pod]struct{})
}
m.topologyPairToPods[pair][pod] = struct{}{} m.topologyPairToPods[pair][pod] = struct{}{}
if m.podToTopologyPairs[podFullName] == nil { if m.podToTopologyPairs[podFullName] == nil {
m.podToTopologyPairs[podFullName] = make(map[topologyPair]struct{}) m.podToTopologyPairs[podFullName] = make(map[topologyPair]struct{})
@ -345,13 +380,6 @@ func (m *topologyPairsMaps) addTopologyPair(pair topologyPair, pod *v1.Pod) {
m.podToTopologyPairs[podFullName][pair] = struct{}{} m.podToTopologyPairs[podFullName][pair] = struct{}{}
} }
// add a topology pair holder if needed
func (m *topologyPairsMaps) addTopologyPairWithoutPods(pair topologyPair) {
if m.topologyPairToPods[pair] == nil {
m.topologyPairToPods[pair] = make(map[*v1.Pod]struct{})
}
}
func (m *topologyPairsMaps) removePod(deletedPod *v1.Pod) { func (m *topologyPairsMaps) removePod(deletedPod *v1.Pod) {
deletedPodFullName := schedutil.GetPodFullName(deletedPod) deletedPodFullName := schedutil.GetPodFullName(deletedPod)
for pair := range m.podToTopologyPairs[deletedPodFullName] { for pair := range m.podToTopologyPairs[deletedPodFullName] {
@ -368,12 +396,8 @@ func (m *topologyPairsMaps) appendMaps(toAppend *topologyPairsMaps) {
return return
} }
for pair := range toAppend.topologyPairToPods { for pair := range toAppend.topologyPairToPods {
if podSet := toAppend.topologyPairToPods[pair]; len(podSet) == 0 { for pod := range toAppend.topologyPairToPods[pair] {
m.addTopologyPairWithoutPods(pair) m.addTopologyPair(pair, pod)
} else {
for pod := range podSet {
m.addTopologyPair(pair, pod)
}
} }
} }
} }
@ -384,8 +408,16 @@ func (m *topologyPairsMaps) clone() *topologyPairsMaps {
return copy return copy
} }
func (m *topologyPairsPodSpreadMap) addPod(addedPod, preemptorPod *v1.Pod, node *v1.Node) error { func (c *podSpreadCache) addPod(addedPod, preemptorPod *v1.Pod, node *v1.Node) error {
if addedPod.Namespace != preemptorPod.Namespace { return c.updatePod(addedPod, preemptorPod, node, 1)
}
func (c *podSpreadCache) removePod(deletedPod, preemptorPod *v1.Pod, node *v1.Node) {
c.updatePod(deletedPod, preemptorPod, node, -1)
}
func (c *podSpreadCache) updatePod(updatedPod, preemptorPod *v1.Pod, node *v1.Node, delta int32) error {
if updatedPod.Namespace != preemptorPod.Namespace || node == nil {
return nil return nil
} }
constraints := getHardTopologySpreadConstraints(preemptorPod) constraints := getHardTopologySpreadConstraints(preemptorPod)
@ -393,98 +425,45 @@ func (m *topologyPairsPodSpreadMap) addPod(addedPod, preemptorPod *v1.Pod, node
return nil return nil
} }
// records which topology key(s) needs to be updated podLabelSet := labels.Set(updatedPod.Labels)
minMatchNeedingUpdate := make(map[string]struct{})
podLabelSet := labels.Set(addedPod.Labels)
for _, constraint := range constraints { for _, constraint := range constraints {
if match, err := PodMatchesSpreadConstraint(podLabelSet, constraint); err != nil { if match, err := PodMatchesSpreadConstraint(podLabelSet, constraint); err != nil {
return err return err
} else if !match { } else if !match {
continue continue
} }
pair := topologyPair{
key: constraint.TopologyKey,
value: node.Labels[constraint.TopologyKey],
}
// it means current node is one of the critical paths of topologyKeyToMinPodsMap[TopologyKey]
if int32(len(m.topologyPairToPods[pair])) == m.topologyKeyToMinPodsMap[pair.key] {
minMatchNeedingUpdate[pair.key] = struct{}{}
}
m.addTopologyPair(pair, addedPod)
}
// no need to addTopologyPairWithoutPods b/c if a pair without pods must be present,
// it should have already been created earlier in removePod() phase
// In most cases, min match map doesn't need to be updated. k, v := constraint.TopologyKey, node.Labels[constraint.TopologyKey]
// But it's required to be updated when current node is the ONLY critical path which impacts pair := topologyPair{key: k, value: v}
// the min match. With that said, in this case min match needs to be updated to min match + 1 c.tpPairToMatchNum[pair] = c.tpPairToMatchNum[pair] + delta
if len(minMatchNeedingUpdate) != 0 {
// TODO(Huang-Wei): performance can be optimized. c.tpKeyToCriticalPaths[k].update(v, c.tpPairToMatchNum[pair])
// A possible solution is to record number of critical paths which co-impact the min match.
tempMinMatchMap := make(map[string]int32, len(minMatchNeedingUpdate))
for key := range minMatchNeedingUpdate {
tempMinMatchMap[key] = math.MaxInt32
}
for pair, podSet := range m.topologyPairToPods {
if _, ok := minMatchNeedingUpdate[pair.key]; !ok {
continue
}
if l := int32(len(podSet)); l < tempMinMatchMap[pair.key] {
tempMinMatchMap[pair.key] = l
}
}
for key, tempMin := range tempMinMatchMap {
if tempMin == m.topologyKeyToMinPodsMap[key]+1 {
m.topologyKeyToMinPodsMap[key] = tempMin
}
}
} }
return nil return nil
} }
func (m *topologyPairsPodSpreadMap) removePod(deletedPod *v1.Pod) { func (c *podSpreadCache) clone() *podSpreadCache {
if m == nil || deletedPod == nil { // c could be nil when EvenPodsSpread feature is disabled
return if c == nil {
}
deletedPodFullName := schedutil.GetPodFullName(deletedPod)
pairSet, ok := m.podToTopologyPairs[deletedPodFullName]
if !ok {
return
}
topologyPairToPods := m.topologyPairToPods
for pair := range pairSet {
delete(topologyPairToPods[pair], deletedPod)
// if topologyPairToPods[pair] is empty after deletion
// don't clean it up as that topology counts as a match now
// removal of the deletedPod would probably genereate a smaller matching number
// so re-calculate minMatch to a smaller value if possible
if l := int32(len(topologyPairToPods[pair])); l < m.topologyKeyToMinPodsMap[pair.key] {
m.topologyKeyToMinPodsMap[pair.key] = l
}
}
delete(m.podToTopologyPairs, deletedPodFullName)
}
func (m *topologyPairsPodSpreadMap) clone() *topologyPairsPodSpreadMap {
// m could be nil when EvenPodsSpread feature is disabled
if m == nil {
return nil return nil
} }
copy := &topologyPairsPodSpreadMap{ copy := podSpreadCache{
topologyKeyToMinPodsMap: make(map[string]int32), tpKeyToCriticalPaths: make(map[string]*criticalPaths),
topologyPairsMaps: m.topologyPairsMaps.clone(), tpPairToMatchNum: make(map[topologyPair]int32),
} }
for key, minMatched := range m.topologyKeyToMinPodsMap { for tpKey, paths := range c.tpKeyToCriticalPaths {
copy.topologyKeyToMinPodsMap[key] = minMatched copy.tpKeyToCriticalPaths[tpKey] = &criticalPaths{paths[0], paths[1]}
} }
return copy for tpPair, matchNum := range c.tpPairToMatchNum {
copyPair := topologyPair{key: tpPair.key, value: tpPair.value}
copy.tpPairToMatchNum[copyPair] = matchNum
}
return &copy
} }
// RemovePod changes predicateMetadata assuming that the given `deletedPod` is // RemovePod changes predicateMetadata assuming that the given `deletedPod` is
// deleted from the system. // deleted from the system.
func (meta *predicateMetadata) RemovePod(deletedPod *v1.Pod) error { func (meta *predicateMetadata) RemovePod(deletedPod *v1.Pod, node *v1.Node) error {
deletedPodFullName := schedutil.GetPodFullName(deletedPod) deletedPodFullName := schedutil.GetPodFullName(deletedPod)
if deletedPodFullName == schedutil.GetPodFullName(meta.pod) { if deletedPodFullName == schedutil.GetPodFullName(meta.pod) {
return fmt.Errorf("deletedPod and meta.pod must not be the same") return fmt.Errorf("deletedPod and meta.pod must not be the same")
@ -494,7 +473,7 @@ func (meta *predicateMetadata) RemovePod(deletedPod *v1.Pod) error {
meta.topologyPairsPotentialAffinityPods.removePod(deletedPod) meta.topologyPairsPotentialAffinityPods.removePod(deletedPod)
meta.topologyPairsPotentialAntiAffinityPods.removePod(deletedPod) meta.topologyPairsPotentialAntiAffinityPods.removePod(deletedPod)
// Delete pod from the pod spread topology maps. // Delete pod from the pod spread topology maps.
meta.topologyPairsPodSpreadMap.removePod(deletedPod) meta.podSpreadCache.removePod(deletedPod, meta.pod, node)
// All pods in the serviceAffinityMatchingPodList are in the same namespace. // All pods in the serviceAffinityMatchingPodList are in the same namespace.
// So, if the namespace of the first one is not the same as the namespace of the // So, if the namespace of the first one is not the same as the namespace of the
// deletedPod, we don't need to check the list, as deletedPod isn't in the list. // deletedPod, we don't need to check the list, as deletedPod isn't in the list.
@ -556,9 +535,9 @@ func (meta *predicateMetadata) AddPod(addedPod *v1.Pod, nodeInfo *schedulernodei
} }
} }
} }
// Update meta.topologyPairsPodSpreadMap if meta.pod has hard spread constraints // Update meta.podSpreadCache if meta.pod has hard spread constraints
// and addedPod matches that // and addedPod matches that
if err := meta.topologyPairsPodSpreadMap.addPod(addedPod, meta.pod, nodeInfo.Node()); err != nil { if err := meta.podSpreadCache.addPod(addedPod, meta.pod, nodeInfo.Node()); err != nil {
return err return err
} }
@ -588,7 +567,7 @@ func (meta *predicateMetadata) ShallowCopy() PredicateMetadata {
newPredMeta.topologyPairsPotentialAffinityPods = meta.topologyPairsPotentialAffinityPods.clone() newPredMeta.topologyPairsPotentialAffinityPods = meta.topologyPairsPotentialAffinityPods.clone()
newPredMeta.topologyPairsPotentialAntiAffinityPods = meta.topologyPairsPotentialAntiAffinityPods.clone() newPredMeta.topologyPairsPotentialAntiAffinityPods = meta.topologyPairsPotentialAntiAffinityPods.clone()
newPredMeta.topologyPairsAntiAffinityPodsMap = meta.topologyPairsAntiAffinityPodsMap.clone() newPredMeta.topologyPairsAntiAffinityPodsMap = meta.topologyPairsAntiAffinityPodsMap.clone()
newPredMeta.topologyPairsPodSpreadMap = meta.topologyPairsPodSpreadMap.clone() newPredMeta.podSpreadCache = meta.podSpreadCache.clone()
newPredMeta.serviceAffinityMatchingPodServices = append([]*v1.Service(nil), newPredMeta.serviceAffinityMatchingPodServices = append([]*v1.Service(nil),
meta.serviceAffinityMatchingPodServices...) meta.serviceAffinityMatchingPodServices...)
newPredMeta.serviceAffinityMatchingPodList = append([]*v1.Pod(nil), newPredMeta.serviceAffinityMatchingPodList = append([]*v1.Pod(nil),

View File

@ -385,7 +385,7 @@ func TestPredicateMetadata_AddRemovePod(t *testing.T) {
// Remove the added pod and from existingPodsMeta1 an make sure it is equal // Remove the added pod and from existingPodsMeta1 an make sure it is equal
// to meta generated for existing pods. // to meta generated for existing pods.
existingPodsMeta2, _ := getMeta(st.FakePodLister(test.existingPods)) existingPodsMeta2, _ := getMeta(st.FakePodLister(test.existingPods))
if err := existingPodsMeta1.RemovePod(test.addedPod); err != nil { if err := existingPodsMeta1.RemovePod(test.addedPod, nil); err != nil {
t.Errorf("error removing pod from meta: %v", err) t.Errorf("error removing pod from meta: %v", err)
} }
if err := predicateMetadataEquivalent(existingPodsMeta1, existingPodsMeta2); err != nil { if err := predicateMetadataEquivalent(existingPodsMeta1, existingPodsMeta2); err != nil {
@ -512,37 +512,13 @@ func TestPredicateMetadata_ShallowCopy(t *testing.T) {
}, },
}, },
}, },
topologyPairsPodSpreadMap: &topologyPairsPodSpreadMap{ podSpreadCache: &podSpreadCache{
topologyKeyToMinPodsMap: map[string]int32{"name": 1}, tpKeyToCriticalPaths: map[string]*criticalPaths{
topologyPairsMaps: &topologyPairsMaps{ "name": {{"nodeA", 1}, {"nodeC", 2}},
topologyPairToPods: map[topologyPair]podSet{ },
{key: "name", value: "nodeA"}: { tpPairToMatchNum: map[topologyPair]int32{
&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "p1", Labels: selector1}, {key: "name", value: "nodeA"}: 1,
Spec: v1.PodSpec{NodeName: "nodeA"}, {key: "name", value: "nodeC"}: 2,
}: struct{}{},
},
{key: "name", value: "nodeC"}: {
&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "p2"},
Spec: v1.PodSpec{
NodeName: "nodeC",
},
}: struct{}{},
&v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "p6", Labels: selector1},
Spec: v1.PodSpec{NodeName: "nodeC"},
}: struct{}{},
},
},
podToTopologyPairs: map[string]topologyPairSet{
"p1_": {
topologyPair{key: "name", value: "nodeA"}: struct{}{},
},
"p2_": {
topologyPair{key: "name", value: "nodeC"}: struct{}{},
},
"p6_": {
topologyPair{key: "name", value: "nodeC"}: struct{}{},
},
},
}, },
}, },
serviceAffinityInUse: true, serviceAffinityInUse: true,
@ -916,15 +892,12 @@ func TestPodMatchesSpreadConstraint(t *testing.T) {
} }
func TestGetTPMapMatchingSpreadConstraints(t *testing.T) { func TestGetTPMapMatchingSpreadConstraints(t *testing.T) {
// we need to inject the exact pod pointers to want.topologyPairsMaps.topologyPairToPods
// otherwise, *pod (as key of a map) will always fail in reflect.DeepEqual()
tests := []struct { tests := []struct {
name string name string
pod *v1.Pod pod *v1.Pod
nodes []*v1.Node nodes []*v1.Node
existingPods []*v1.Pod existingPods []*v1.Pod
injectPodPointers map[topologyPair][]int want *podSpreadCache
want *topologyPairsPodSpreadMap
}{ }{
{ {
name: "clean cluster with one spreadConstraint", name: "clean cluster with one spreadConstraint",
@ -937,16 +910,13 @@ func TestGetTPMapMatchingSpreadConstraints(t *testing.T) {
st.MakeNode().Name("node-x").Label("zone", "zone2").Label("node", "node-x").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(), st.MakeNode().Name("node-y").Label("zone", "zone2").Label("node", "node-y").Obj(),
}, },
injectPodPointers: map[topologyPair][]int{ want: &podSpreadCache{
// denotes no existing pod is matched on this zone pair, but still needed to be tpKeyToCriticalPaths: map[string]*criticalPaths{
// calculated if incoming pod matches its own spread constraints "zone": {{"zone1", 0}, {"zone2", 0}},
{key: "zone", value: "zone1"}: {}, },
{key: "zone", value: "zone2"}: {}, tpPairToMatchNum: map[topologyPair]int32{
}, {key: "zone", value: "zone1"}: 0,
want: &topologyPairsPodSpreadMap{ {key: "zone", value: "zone2"}: 0,
topologyKeyToMinPodsMap: map[string]int32{"zone": 0},
topologyPairsMaps: &topologyPairsMaps{
podToTopologyPairs: make(map[string]topologyPairSet),
}, },
}, },
}, },
@ -968,22 +938,44 @@ func TestGetTPMapMatchingSpreadConstraints(t *testing.T) {
st.MakePod().Name("p-y1").Node("node-y").Label("foo", "").Obj(), st.MakePod().Name("p-y1").Node("node-y").Label("foo", "").Obj(),
st.MakePod().Name("p-y2").Node("node-y").Label("foo", "").Obj(), st.MakePod().Name("p-y2").Node("node-y").Label("foo", "").Obj(),
}, },
injectPodPointers: map[topologyPair][]int{ want: &podSpreadCache{
// denotes existingPods[0,1,2] tpKeyToCriticalPaths: map[string]*criticalPaths{
{key: "zone", value: "zone1"}: {0, 1, 2}, "zone": {{"zone2", 2}, {"zone1", 3}},
// denotes existingPods[3,4] },
{key: "zone", value: "zone2"}: {3, 4}, tpPairToMatchNum: map[topologyPair]int32{
{key: "zone", value: "zone1"}: 3,
{key: "zone", value: "zone2"}: 2,
},
}, },
want: &topologyPairsPodSpreadMap{ },
topologyKeyToMinPodsMap: map[string]int32{"zone": 2}, {
topologyPairsMaps: &topologyPairsMaps{ name: "normal case with one spreadConstraint, on a 3-zone cluster",
podToTopologyPairs: map[string]topologyPairSet{ pod: st.MakePod().Name("p").Label("foo", "").SpreadConstraint(
"p-a1_": newPairSet("zone", "zone1"), 1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(),
"p-a2_": newPairSet("zone", "zone1"), ).Obj(),
"p-b1_": newPairSet("zone", "zone1"), nodes: []*v1.Node{
"p-y1_": newPairSet("zone", "zone2"), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
"p-y2_": newPairSet("zone", "zone2"), 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(),
st.MakeNode().Name("node-o").Label("zone", "zone3").Label("node", "node-o").Obj(),
st.MakeNode().Name("node-p").Label("zone", "zone3").Label("node", "node-p").Obj(),
},
existingPods: []*v1.Pod{
st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
st.MakePod().Name("p-a2").Node("node-a").Label("foo", "").Obj(),
st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Obj(),
st.MakePod().Name("p-y1").Node("node-y").Label("foo", "").Obj(),
st.MakePod().Name("p-y2").Node("node-y").Label("foo", "").Obj(),
},
want: &podSpreadCache{
tpKeyToCriticalPaths: map[string]*criticalPaths{
"zone": {{"zone3", 0}, {"zone2", 2}},
},
tpPairToMatchNum: map[topologyPair]int32{
{key: "zone", value: "zone1"}: 3,
{key: "zone", value: "zone2"}: 2,
{key: "zone", value: "zone3"}: 0,
}, },
}, },
}, },
@ -1005,18 +997,13 @@ func TestGetTPMapMatchingSpreadConstraints(t *testing.T) {
st.MakePod().Name("p-y1").Namespace("ns2").Node("node-y").Label("foo", "").Obj(), st.MakePod().Name("p-y1").Namespace("ns2").Node("node-y").Label("foo", "").Obj(),
st.MakePod().Name("p-y2").Node("node-y").Label("foo", "").Obj(), st.MakePod().Name("p-y2").Node("node-y").Label("foo", "").Obj(),
}, },
injectPodPointers: map[topologyPair][]int{ want: &podSpreadCache{
{key: "zone", value: "zone1"}: {0, 2}, tpKeyToCriticalPaths: map[string]*criticalPaths{
{key: "zone", value: "zone2"}: {4}, "zone": {{"zone2", 1}, {"zone1", 2}},
}, },
want: &topologyPairsPodSpreadMap{ tpPairToMatchNum: map[topologyPair]int32{
topologyKeyToMinPodsMap: map[string]int32{"zone": 1}, {key: "zone", value: "zone1"}: 2,
topologyPairsMaps: &topologyPairsMaps{ {key: "zone", value: "zone2"}: 1,
podToTopologyPairs: map[string]topologyPairSet{
"p-a1_": newPairSet("zone", "zone1"),
"p-b1_": newPairSet("zone", "zone1"),
"p-y2_": newPairSet("zone", "zone2"),
},
}, },
}, },
}, },
@ -1041,26 +1028,18 @@ func TestGetTPMapMatchingSpreadConstraints(t *testing.T) {
st.MakePod().Name("p-y3").Node("node-y").Label("foo", "").Obj(), st.MakePod().Name("p-y3").Node("node-y").Label("foo", "").Obj(),
st.MakePod().Name("p-y4").Node("node-y").Label("foo", "").Obj(), st.MakePod().Name("p-y4").Node("node-y").Label("foo", "").Obj(),
}, },
injectPodPointers: map[topologyPair][]int{ want: &podSpreadCache{
{key: "zone", value: "zone1"}: {0, 1, 2}, tpKeyToCriticalPaths: map[string]*criticalPaths{
{key: "zone", value: "zone2"}: {3, 4, 5, 6}, "zone": {{"zone1", 3}, {"zone2", 4}},
{key: "node", value: "node-a"}: {0, 1}, "node": {{"node-x", 0}, {"node-b", 1}},
{key: "node", value: "node-b"}: {2}, },
{key: "node", value: "node-x"}: {}, tpPairToMatchNum: map[topologyPair]int32{
{key: "node", value: "node-y"}: {3, 4, 5, 6}, {key: "zone", value: "zone1"}: 3,
}, {key: "zone", value: "zone2"}: 4,
want: &topologyPairsPodSpreadMap{ {key: "node", value: "node-a"}: 2,
topologyKeyToMinPodsMap: map[string]int32{"zone": 3, "node": 0}, {key: "node", value: "node-b"}: 1,
topologyPairsMaps: &topologyPairsMaps{ {key: "node", value: "node-x"}: 0,
podToTopologyPairs: map[string]topologyPairSet{ {key: "node", value: "node-y"}: 4,
"p-a1_": newPairSet("zone", "zone1", "node", "node-a"),
"p-a2_": newPairSet("zone", "zone1", "node", "node-a"),
"p-b1_": newPairSet("zone", "zone1", "node", "node-b"),
"p-y1_": newPairSet("zone", "zone2", "node", "node-y"),
"p-y2_": newPairSet("zone", "zone2", "node", "node-y"),
"p-y3_": newPairSet("zone", "zone2", "node", "node-y"),
"p-y4_": newPairSet("zone", "zone2", "node", "node-y"),
},
}, },
}, },
}, },
@ -1086,25 +1065,17 @@ func TestGetTPMapMatchingSpreadConstraints(t *testing.T) {
st.MakePod().Name("p-y3").Node("node-y").Label("foo", "").Obj(), st.MakePod().Name("p-y3").Node("node-y").Label("foo", "").Obj(),
st.MakePod().Name("p-y4").Node("node-y").Label("foo", "").Obj(), st.MakePod().Name("p-y4").Node("node-y").Label("foo", "").Obj(),
}, },
injectPodPointers: map[topologyPair][]int{ want: &podSpreadCache{
{key: "zone", value: "zone1"}: {0, 1, 2}, tpKeyToCriticalPaths: map[string]*criticalPaths{
{key: "zone", value: "zone2"}: {3, 4, 5, 6}, "zone": {{"zone1", 3}, {"zone2", 4}},
{key: "node", value: "node-a"}: {0, 1}, "node": {{"node-b", 1}, {"node-a", 2}},
{key: "node", value: "node-b"}: {2}, },
{key: "node", value: "node-y"}: {3, 4, 5, 6}, tpPairToMatchNum: map[topologyPair]int32{
}, {key: "zone", value: "zone1"}: 3,
want: &topologyPairsPodSpreadMap{ {key: "zone", value: "zone2"}: 4,
topologyKeyToMinPodsMap: map[string]int32{"zone": 3, "node": 1}, {key: "node", value: "node-a"}: 2,
topologyPairsMaps: &topologyPairsMaps{ {key: "node", value: "node-b"}: 1,
podToTopologyPairs: map[string]topologyPairSet{ {key: "node", value: "node-y"}: 4,
"p-a1_": newPairSet("zone", "zone1", "node", "node-a"),
"p-a2_": newPairSet("zone", "zone1", "node", "node-a"),
"p-b1_": newPairSet("zone", "zone1", "node", "node-b"),
"p-y1_": newPairSet("zone", "zone2", "node", "node-y"),
"p-y2_": newPairSet("zone", "zone2", "node", "node-y"),
"p-y3_": newPairSet("zone", "zone2", "node", "node-y"),
"p-y4_": newPairSet("zone", "zone2", "node", "node-y"),
},
}, },
}, },
}, },
@ -1121,20 +1092,19 @@ func TestGetTPMapMatchingSpreadConstraints(t *testing.T) {
}, },
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
st.MakePod().Name("p-a").Node("node-a").Label("foo", "").Obj(), st.MakePod().Name("p-a").Node("node-a").Label("foo", "").Obj(),
st.MakePod().Name("p-b").Node("node-b").Label("bar", "").Obj(),
}, },
injectPodPointers: map[topologyPair][]int{ want: &podSpreadCache{
{key: "zone", value: "zone1"}: {0}, tpKeyToCriticalPaths: map[string]*criticalPaths{
{key: "zone", value: "zone2"}: {}, "zone": {{"zone2", 0}, {"zone1", 1}},
{key: "node", value: "node-a"}: {}, "node": {{"node-a", 0}, {"node-y", 0}},
{key: "node", value: "node-b"}: {}, },
{key: "node", value: "node-y"}: {}, tpPairToMatchNum: map[topologyPair]int32{
}, {key: "zone", value: "zone1"}: 1,
want: &topologyPairsPodSpreadMap{ {key: "zone", value: "zone2"}: 0,
topologyKeyToMinPodsMap: map[string]int32{"zone": 0, "node": 0}, {key: "node", value: "node-a"}: 0,
topologyPairsMaps: &topologyPairsMaps{ {key: "node", value: "node-b"}: 1,
podToTopologyPairs: map[string]topologyPairSet{ {key: "node", value: "node-y"}: 0,
"p-a_": newPairSet("zone", "zone1"),
},
}, },
}, },
}, },
@ -1158,25 +1128,17 @@ func TestGetTPMapMatchingSpreadConstraints(t *testing.T) {
st.MakePod().Name("p-y3").Node("node-y").Label("foo", "").Obj(), st.MakePod().Name("p-y3").Node("node-y").Label("foo", "").Obj(),
st.MakePod().Name("p-y4").Node("node-y").Label("foo", "").Label("bar", "").Obj(), st.MakePod().Name("p-y4").Node("node-y").Label("foo", "").Label("bar", "").Obj(),
}, },
injectPodPointers: map[topologyPair][]int{ want: &podSpreadCache{
{key: "zone", value: "zone1"}: {0, 1, 2}, tpKeyToCriticalPaths: map[string]*criticalPaths{
{key: "zone", value: "zone2"}: {3, 4, 5, 6}, "zone": {{"zone1", 3}, {"zone2", 4}},
{key: "node", value: "node-a"}: {1}, "node": {{"node-b", 0}, {"node-a", 1}},
{key: "node", value: "node-b"}: {}, },
{key: "node", value: "node-y"}: {4, 6}, tpPairToMatchNum: map[topologyPair]int32{
}, {key: "zone", value: "zone1"}: 3,
want: &topologyPairsPodSpreadMap{ {key: "zone", value: "zone2"}: 4,
topologyKeyToMinPodsMap: map[string]int32{"zone": 3, "node": 0}, {key: "node", value: "node-a"}: 1,
topologyPairsMaps: &topologyPairsMaps{ {key: "node", value: "node-b"}: 0,
podToTopologyPairs: map[string]topologyPairSet{ {key: "node", value: "node-y"}: 2,
"p-a1_": newPairSet("zone", "zone1"),
"p-a2_": newPairSet("zone", "zone1", "node", "node-a"),
"p-b1_": newPairSet("zone", "zone1"),
"p-y1_": newPairSet("zone", "zone2"),
"p-y2_": newPairSet("zone", "zone2", "node", "node-y"),
"p-y3_": newPairSet("zone", "zone2"),
"p-y4_": newPairSet("zone", "zone2", "node", "node-y"),
},
}, },
}, },
}, },
@ -1202,61 +1164,46 @@ func TestGetTPMapMatchingSpreadConstraints(t *testing.T) {
st.MakePod().Name("p-y3").Node("node-y").Label("foo", "").Obj(), st.MakePod().Name("p-y3").Node("node-y").Label("foo", "").Obj(),
st.MakePod().Name("p-y4").Node("node-y").Label("foo", "").Obj(), st.MakePod().Name("p-y4").Node("node-y").Label("foo", "").Obj(),
}, },
injectPodPointers: map[topologyPair][]int{ want: &podSpreadCache{
{key: "zone", value: "zone1"}: {0, 1, 2}, tpKeyToCriticalPaths: map[string]*criticalPaths{
{key: "zone", value: "zone2"}: {3, 4, 5, 6}, "zone": {{"zone1", 3}, {"zone2", 4}},
{key: "node", value: "node-a"}: {0, 1}, "node": {{"node-b", 1}, {"node-a", 2}},
{key: "node", value: "node-b"}: {2}, },
{key: "node", value: "node-y"}: {3, 4, 5, 6}, tpPairToMatchNum: map[topologyPair]int32{
}, {key: "zone", value: "zone1"}: 3,
want: &topologyPairsPodSpreadMap{ {key: "zone", value: "zone2"}: 4,
topologyKeyToMinPodsMap: map[string]int32{"zone": 3, "node": 1}, {key: "node", value: "node-a"}: 2,
topologyPairsMaps: &topologyPairsMaps{ {key: "node", value: "node-b"}: 1,
podToTopologyPairs: map[string]topologyPairSet{ {key: "node", value: "node-y"}: 4,
"p-a1_": newPairSet("zone", "zone1", "node", "node-a"),
"p-a2_": newPairSet("zone", "zone1", "node", "node-a"),
"p-b1_": newPairSet("zone", "zone1", "node", "node-b"),
"p-y1_": newPairSet("zone", "zone2", "node", "node-y"),
"p-y2_": newPairSet("zone", "zone2", "node", "node-y"),
"p-y3_": newPairSet("zone", "zone2", "node", "node-y"),
"p-y4_": newPairSet("zone", "zone2", "node", "node-y"),
},
}, },
}, },
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
tt.want.topologyPairToPods = make(map[topologyPair]podSet)
for pair, indexes := range tt.injectPodPointers {
pSet := make(podSet)
for _, i := range indexes {
pSet[tt.existingPods[i]] = struct{}{}
}
tt.want.topologyPairToPods[pair] = pSet
}
nodeInfoMap := schedulernodeinfo.CreateNodeNameToInfoMap(tt.existingPods, tt.nodes) nodeInfoMap := schedulernodeinfo.CreateNodeNameToInfoMap(tt.existingPods, tt.nodes)
if got, _ := getTPMapMatchingSpreadConstraints(tt.pod, nodeInfoMap); !reflect.DeepEqual(got, tt.want) { got, _ := getExistingPodSpreadCache(tt.pod, nodeInfoMap)
t.Errorf("getTPMapMatchingSpreadConstraints() = %v, want %v", got, tt.want) got.sortCriticalPaths()
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("getExistingPodSpreadCache() = %v, want %v", *got, *tt.want)
} }
}) })
} }
} }
func TestPodSpreadMap_addPod(t *testing.T) { func TestPodSpreadCache_addPod(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
preemptorPod *v1.Pod preemptor *v1.Pod
addedPod *v1.Pod addedPod *v1.Pod
existingPods []*v1.Pod existingPods []*v1.Pod
nodeIdx int // denotes which node 'addedPod' belongs to nodeIdx int // denotes which node 'addedPod' belongs to
nodes []*v1.Node nodes []*v1.Node
injectPodPointers map[topologyPair][]int // non-negative index refers to existingPods[i], negative index refers to addedPod want *podSpreadCache
want *topologyPairsPodSpreadMap
}{ }{
{ {
name: "node a and b both impact current min match", name: "node a and b both impact current min match",
preemptorPod: st.MakePod().Name("p").Label("foo", ""). preemptor: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()). SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()).
Obj(), Obj(),
addedPod: st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(), addedPod: st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
@ -1266,24 +1213,19 @@ func TestPodSpreadMap_addPod(t *testing.T) {
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), 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-b").Label("zone", "zone1").Label("node", "node-b").Obj(),
}, },
injectPodPointers: map[topologyPair][]int{ want: &podSpreadCache{
{key: "node", value: "node-a"}: {-1}, tpKeyToCriticalPaths: map[string]*criticalPaths{
{key: "node", value: "node-b"}: {}, "node": {{"node-b", 0}, {"node-a", 1}},
}, },
want: &topologyPairsPodSpreadMap{ tpPairToMatchNum: map[topologyPair]int32{
// min match map shouldn't be changed b/c node-b is still on the critical path {key: "node", value: "node-a"}: 1,
// determining min match {key: "node", value: "node-b"}: 0,
topologyKeyToMinPodsMap: map[string]int32{"node": 0},
topologyPairsMaps: &topologyPairsMaps{
podToTopologyPairs: map[string]topologyPairSet{
"p-a1_": newPairSet("node", "node-a"),
},
}, },
}, },
}, },
{ {
name: "only node a impacts current min match", name: "only node a impacts current min match",
preemptorPod: st.MakePod().Name("p").Label("foo", ""). preemptor: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()). SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()).
Obj(), Obj(),
addedPod: st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(), addedPod: st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
@ -1295,24 +1237,19 @@ func TestPodSpreadMap_addPod(t *testing.T) {
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), 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-b").Label("zone", "zone1").Label("node", "node-b").Obj(),
}, },
injectPodPointers: map[topologyPair][]int{ want: &podSpreadCache{
{key: "node", value: "node-a"}: {-1}, tpKeyToCriticalPaths: map[string]*criticalPaths{
{key: "node", value: "node-b"}: {0}, "node": {{"node-a", 1}, {"node-b", 1}},
}, },
want: &topologyPairsPodSpreadMap{ tpPairToMatchNum: map[topologyPair]int32{
// min match should be changed from 0 to 1 {key: "node", value: "node-a"}: 1,
topologyKeyToMinPodsMap: map[string]int32{"node": 1}, {key: "node", value: "node-b"}: 1,
topologyPairsMaps: &topologyPairsMaps{
podToTopologyPairs: map[string]topologyPairSet{
"p-a1_": newPairSet("node", "node-a"),
"p-b1_": newPairSet("node", "node-b"),
},
}, },
}, },
}, },
{ {
name: "add a pod with mis-matched namespace doesn't change topologyKeyToMinPodsMap", name: "add a pod with mis-matched namespace doesn't change topologyKeyToMinPodsMap",
preemptorPod: st.MakePod().Name("p").Label("foo", ""). preemptor: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()). SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()).
Obj(), Obj(),
addedPod: st.MakePod().Name("p-a1").Namespace("ns1").Node("node-a").Label("foo", "").Obj(), addedPod: st.MakePod().Name("p-a1").Namespace("ns1").Node("node-a").Label("foo", "").Obj(),
@ -1324,24 +1261,19 @@ func TestPodSpreadMap_addPod(t *testing.T) {
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), 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-b").Label("zone", "zone1").Label("node", "node-b").Obj(),
}, },
injectPodPointers: map[topologyPair][]int{ want: &podSpreadCache{
{key: "node", value: "node-a"}: {}, tpKeyToCriticalPaths: map[string]*criticalPaths{
{key: "node", value: "node-b"}: {0}, "node": {{"node-a", 0}, {"node-b", 1}},
}, },
want: &topologyPairsPodSpreadMap{ tpPairToMatchNum: map[topologyPair]int32{
// min match remains the same {key: "node", value: "node-a"}: 0,
topologyKeyToMinPodsMap: map[string]int32{"node": 0}, {key: "node", value: "node-b"}: 1,
topologyPairsMaps: &topologyPairsMaps{
podToTopologyPairs: map[string]topologyPairSet{
// "p-a1_": newPairSet("node", "node-a") shouldn't exist
"p-b1_": newPairSet("node", "node-b"),
},
}, },
}, },
}, },
{ {
name: "add pod on non-critical node won't trigger re-calculation", name: "add pod on non-critical node won't trigger re-calculation",
preemptorPod: st.MakePod().Name("p").Label("foo", ""). preemptor: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()). SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()).
Obj(), Obj(),
addedPod: st.MakePod().Name("p-b2").Node("node-b").Label("foo", "").Obj(), addedPod: st.MakePod().Name("p-b2").Node("node-b").Label("foo", "").Obj(),
@ -1353,23 +1285,19 @@ func TestPodSpreadMap_addPod(t *testing.T) {
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), 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-b").Label("zone", "zone1").Label("node", "node-b").Obj(),
}, },
injectPodPointers: map[topologyPair][]int{ want: &podSpreadCache{
{key: "node", value: "node-a"}: {}, tpKeyToCriticalPaths: map[string]*criticalPaths{
{key: "node", value: "node-b"}: {-1, 0}, "node": {{"node-a", 0}, {"node-b", 2}},
}, },
want: &topologyPairsPodSpreadMap{ tpPairToMatchNum: map[topologyPair]int32{
topologyKeyToMinPodsMap: map[string]int32{"node": 0}, {key: "node", value: "node-a"}: 0,
topologyPairsMaps: &topologyPairsMaps{ {key: "node", value: "node-b"}: 2,
podToTopologyPairs: map[string]topologyPairSet{
"p-b1_": newPairSet("node", "node-b"),
"p-b2_": newPairSet("node", "node-b"),
},
}, },
}, },
}, },
{ {
name: "node a and x both impact topologyKeyToMinPodsMap on zone and node", name: "node a and x both impact topologyKeyToMinPodsMap on zone and node",
preemptorPod: st.MakePod().Name("p").Label("foo", ""). preemptor: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()). SpreadConstraint(1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()).
SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()). SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()).
Obj(), Obj(),
@ -1380,24 +1308,22 @@ func TestPodSpreadMap_addPod(t *testing.T) {
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
st.MakeNode().Name("node-x").Label("zone", "zone2").Label("node", "node-x").Obj(), st.MakeNode().Name("node-x").Label("zone", "zone2").Label("node", "node-x").Obj(),
}, },
injectPodPointers: map[topologyPair][]int{ want: &podSpreadCache{
{key: "zone", value: "zone1"}: {-1}, tpKeyToCriticalPaths: map[string]*criticalPaths{
{key: "zone", value: "zone2"}: {}, "zone": {{"zone2", 0}, {"zone1", 1}},
{key: "node", value: "node-a"}: {-1}, "node": {{"node-x", 0}, {"node-a", 1}},
{key: "node", value: "node-x"}: {}, },
}, tpPairToMatchNum: map[topologyPair]int32{
want: &topologyPairsPodSpreadMap{ {key: "zone", value: "zone1"}: 1,
topologyKeyToMinPodsMap: map[string]int32{"zone": 0, "node": 0}, {key: "zone", value: "zone2"}: 0,
topologyPairsMaps: &topologyPairsMaps{ {key: "node", value: "node-a"}: 1,
podToTopologyPairs: map[string]topologyPairSet{ {key: "node", value: "node-x"}: 0,
"p-a1_": newPairSet("zone", "zone1", "node", "node-a"),
},
}, },
}, },
}, },
{ {
name: "only node a impacts topologyKeyToMinPodsMap on zone and node", name: "only node a impacts topologyKeyToMinPodsMap on zone and node",
preemptorPod: st.MakePod().Name("p").Label("foo", ""). preemptor: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()). SpreadConstraint(1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()).
SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()). SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()).
Obj(), Obj(),
@ -1410,25 +1336,22 @@ func TestPodSpreadMap_addPod(t *testing.T) {
st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(),
st.MakeNode().Name("node-x").Label("zone", "zone2").Label("node", "node-x").Obj(), st.MakeNode().Name("node-x").Label("zone", "zone2").Label("node", "node-x").Obj(),
}, },
injectPodPointers: map[topologyPair][]int{ want: &podSpreadCache{
{key: "zone", value: "zone1"}: {-1}, tpKeyToCriticalPaths: map[string]*criticalPaths{
{key: "zone", value: "zone2"}: {0}, "zone": {{"zone1", 1}, {"zone2", 1}},
{key: "node", value: "node-a"}: {-1}, "node": {{"node-a", 1}, {"node-x", 1}},
{key: "node", value: "node-x"}: {0}, },
}, tpPairToMatchNum: map[topologyPair]int32{
want: &topologyPairsPodSpreadMap{ {key: "zone", value: "zone1"}: 1,
topologyKeyToMinPodsMap: map[string]int32{"zone": 1, "node": 1}, {key: "zone", value: "zone2"}: 1,
topologyPairsMaps: &topologyPairsMaps{ {key: "node", value: "node-a"}: 1,
podToTopologyPairs: map[string]topologyPairSet{ {key: "node", value: "node-x"}: 1,
"p-a1_": newPairSet("zone", "zone1", "node", "node-a"),
"p-x1_": newPairSet("zone", "zone2", "node", "node-x"),
},
}, },
}, },
}, },
{ {
name: "node a impacts topologyKeyToMinPodsMap on node, node x impacts topologyKeyToMinPodsMap on zone", name: "node a impacts topologyKeyToMinPodsMap on node, node x impacts topologyKeyToMinPodsMap on zone",
preemptorPod: st.MakePod().Name("p").Label("foo", ""). preemptor: st.MakePod().Name("p").Label("foo", "").
SpreadConstraint(1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()). SpreadConstraint(1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()).
SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()). SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()).
Obj(), Obj(),
@ -1444,35 +1367,30 @@ func TestPodSpreadMap_addPod(t *testing.T) {
st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").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-x").Label("zone", "zone2").Label("node", "node-x").Obj(),
}, },
injectPodPointers: map[topologyPair][]int{ want: &podSpreadCache{
{key: "zone", value: "zone1"}: {-1, 0, 1}, tpKeyToCriticalPaths: map[string]*criticalPaths{
{key: "zone", value: "zone2"}: {2}, "zone": {{"zone2", 1}, {"zone1", 3}},
{key: "node", value: "node-a"}: {-1}, "node": {{"node-a", 1}, {"node-x", 1}},
{key: "node", value: "node-b"}: {0, 1}, },
{key: "node", value: "node-x"}: {2}, tpPairToMatchNum: map[topologyPair]int32{
}, {key: "zone", value: "zone1"}: 3,
want: &topologyPairsPodSpreadMap{ {key: "zone", value: "zone2"}: 1,
topologyKeyToMinPodsMap: map[string]int32{"zone": 1, "node": 1}, {key: "node", value: "node-a"}: 1,
topologyPairsMaps: &topologyPairsMaps{ {key: "node", value: "node-b"}: 2,
podToTopologyPairs: map[string]topologyPairSet{ {key: "node", value: "node-x"}: 1,
"p-a1_": newPairSet("zone", "zone1", "node", "node-a"),
"p-b1_": newPairSet("zone", "zone1", "node", "node-b"),
"p-b2_": newPairSet("zone", "zone1", "node", "node-b"),
"p-x1_": newPairSet("zone", "zone2", "node", "node-x"),
},
}, },
}, },
}, },
{ {
name: "constraints hold different labelSelectors, node a impacts topologyKeyToMinPodsMap on node", name: "constraints hold different labelSelectors, node a impacts topologyKeyToMinPodsMap on zone",
preemptorPod: st.MakePod().Name("p").Label("foo", "").Label("bar", ""). preemptor: st.MakePod().Name("p").Label("foo", "").Label("bar", "").
SpreadConstraint(1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()). SpreadConstraint(1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()).
SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("bar").Obj()). SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("bar").Obj()).
Obj(), Obj(),
addedPod: st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(), addedPod: st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Label("bar", "").Obj(), st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Label("bar", "").Obj(),
st.MakePod().Name("p-x1").Node("node-x").Label("foo", "").Obj(), st.MakePod().Name("p-x1").Node("node-x").Label("foo", "").Label("bar", "").Obj(),
st.MakePod().Name("p-x2").Node("node-x").Label("bar", "").Obj(), st.MakePod().Name("p-x2").Node("node-x").Label("bar", "").Obj(),
}, },
nodeIdx: 0, nodeIdx: 0,
@ -1481,35 +1399,30 @@ func TestPodSpreadMap_addPod(t *testing.T) {
st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").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-x").Label("zone", "zone2").Label("node", "node-x").Obj(),
}, },
injectPodPointers: map[topologyPair][]int{ want: &podSpreadCache{
{key: "zone", value: "zone1"}: {-1, 0}, tpKeyToCriticalPaths: map[string]*criticalPaths{
{key: "zone", value: "zone2"}: {1}, "zone": {{"zone2", 1}, {"zone1", 2}},
{key: "node", value: "node-a"}: {}, "node": {{"node-a", 0}, {"node-b", 1}},
{key: "node", value: "node-b"}: {0}, },
{key: "node", value: "node-x"}: {2}, tpPairToMatchNum: map[topologyPair]int32{
}, {key: "zone", value: "zone1"}: 2,
want: &topologyPairsPodSpreadMap{ {key: "zone", value: "zone2"}: 1,
topologyKeyToMinPodsMap: map[string]int32{"zone": 1, "node": 0}, {key: "node", value: "node-a"}: 0,
topologyPairsMaps: &topologyPairsMaps{ {key: "node", value: "node-b"}: 1,
podToTopologyPairs: map[string]topologyPairSet{ {key: "node", value: "node-x"}: 2,
"p-a1_": newPairSet("zone", "zone1"),
"p-b1_": newPairSet("zone", "zone1", "node", "node-b"),
"p-x1_": newPairSet("zone", "zone2"),
"p-x2_": newPairSet("node", "node-x"),
},
}, },
}, },
}, },
{ {
name: "constraints hold different labelSelectors, node a impacts topologyKeyToMinPodsMap on both zone and node", name: "constraints hold different labelSelectors, node a impacts topologyKeyToMinPodsMap on both zone and node",
preemptorPod: st.MakePod().Name("p").Label("foo", "").Label("bar", ""). preemptor: st.MakePod().Name("p").Label("foo", "").Label("bar", "").
SpreadConstraint(1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()). SpreadConstraint(1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()).
SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("bar").Obj()). SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("bar").Obj()).
Obj(), Obj(),
addedPod: st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Label("bar", "").Obj(), addedPod: st.MakePod().Name("p-a1").Node("node-a").Label("foo", "").Label("bar", "").Obj(),
existingPods: []*v1.Pod{ existingPods: []*v1.Pod{
st.MakePod().Name("p-b1").Node("node-b").Label("bar", "").Obj(), st.MakePod().Name("p-b1").Node("node-b").Label("bar", "").Obj(),
st.MakePod().Name("p-x1").Node("node-x").Label("foo", "").Obj(), st.MakePod().Name("p-x1").Node("node-x").Label("foo", "").Label("bar", "").Obj(),
st.MakePod().Name("p-x2").Node("node-x").Label("bar", "").Obj(), st.MakePod().Name("p-x2").Node("node-x").Label("bar", "").Obj(),
}, },
nodeIdx: 0, nodeIdx: 0,
@ -1518,62 +1431,45 @@ func TestPodSpreadMap_addPod(t *testing.T) {
st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").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-x").Label("zone", "zone2").Label("node", "node-x").Obj(),
}, },
injectPodPointers: map[topologyPair][]int{ want: &podSpreadCache{
{key: "zone", value: "zone1"}: {-1}, tpKeyToCriticalPaths: map[string]*criticalPaths{
{key: "zone", value: "zone2"}: {1}, "zone": {{"zone1", 1}, {"zone2", 1}},
{key: "node", value: "node-a"}: {-1}, "node": {{"node-a", 1}, {"node-b", 1}},
{key: "node", value: "node-b"}: {0}, },
{key: "node", value: "node-x"}: {2}, tpPairToMatchNum: map[topologyPair]int32{
}, {key: "zone", value: "zone1"}: 1,
want: &topologyPairsPodSpreadMap{ {key: "zone", value: "zone2"}: 1,
topologyKeyToMinPodsMap: map[string]int32{"zone": 1, "node": 1}, {key: "node", value: "node-a"}: 1,
topologyPairsMaps: &topologyPairsMaps{ {key: "node", value: "node-b"}: 1,
podToTopologyPairs: map[string]topologyPairSet{ {key: "node", value: "node-x"}: 2,
"p-a1_": newPairSet("zone", "zone1", "node", "node-a"),
"p-b1_": newPairSet("node", "node-b"),
"p-x1_": newPairSet("zone", "zone2"),
"p-x2_": newPairSet("node", "node-x"),
},
}, },
}, },
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
tt.want.topologyPairToPods = make(map[topologyPair]podSet)
for pair, indexes := range tt.injectPodPointers {
pSet := make(podSet)
for _, i := range indexes {
if i >= 0 {
pSet[tt.existingPods[i]] = struct{}{}
} else {
pSet[tt.addedPod] = struct{}{}
}
}
tt.want.topologyPairToPods[pair] = pSet
}
nodeInfoMap := schedulernodeinfo.CreateNodeNameToInfoMap(tt.existingPods, tt.nodes) nodeInfoMap := schedulernodeinfo.CreateNodeNameToInfoMap(tt.existingPods, tt.nodes)
podSpreadMap, _ := getTPMapMatchingSpreadConstraints(tt.preemptorPod, nodeInfoMap) podSpreadCache, _ := getExistingPodSpreadCache(tt.preemptor, nodeInfoMap)
podSpreadMap.addPod(tt.addedPod, tt.preemptorPod, tt.nodes[tt.nodeIdx]) podSpreadCache.addPod(tt.addedPod, tt.preemptor, tt.nodes[tt.nodeIdx])
if !reflect.DeepEqual(podSpreadMap, tt.want) { podSpreadCache.sortCriticalPaths()
t.Errorf("podSpreadMap#addPod() = %v, want %v", podSpreadMap, tt.want) if !reflect.DeepEqual(podSpreadCache, tt.want) {
t.Errorf("podSpreadCache#addPod() = %v, want %v", podSpreadCache, tt.want)
} }
}) })
} }
} }
func TestPodSpreadMap_removePod(t *testing.T) { func TestPodSpreadCache_removePod(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
preemptor *v1.Pod // preemptor pod preemptor *v1.Pod // preemptor pod
nodes []*v1.Node nodes []*v1.Node
existingPods []*v1.Pod existingPods []*v1.Pod
deletedPodIdx int // need to reuse *Pod of existingPods[i] deletedPodIdx int // need to reuse *Pod of existingPods[i]
deletedPod *v1.Pod // if deletedPodIdx is invalid, this field is bypassed deletedPod *v1.Pod // this field is used only when deletedPodIdx is -1
injectPodPointers map[topologyPair][]int nodeIdx int // denotes which node "deletedPod" belongs to
want *topologyPairsPodSpreadMap want *podSpreadCache
}{ }{
{ {
// A high priority pod may not be scheduled due to node taints or resource shortage. // A high priority pod may not be scheduled due to node taints or resource shortage.
@ -1593,18 +1489,14 @@ func TestPodSpreadMap_removePod(t *testing.T) {
st.MakePod().Name("p-x1").Node("node-x").Label("foo", "").Obj(), st.MakePod().Name("p-x1").Node("node-x").Label("foo", "").Obj(),
}, },
deletedPodIdx: 0, // remove pod "p-a1" deletedPodIdx: 0, // remove pod "p-a1"
injectPodPointers: map[topologyPair][]int{ nodeIdx: 0, // node-a
{key: "zone", value: "zone1"}: {1}, want: &podSpreadCache{
{key: "zone", value: "zone2"}: {2}, tpKeyToCriticalPaths: map[string]*criticalPaths{
}, "zone": {{"zone1", 1}, {"zone2", 1}},
want: &topologyPairsPodSpreadMap{ },
// topologyKeyToMinPodsMap actually doesn't change tpPairToMatchNum: map[topologyPair]int32{
topologyKeyToMinPodsMap: map[string]int32{"zone": 1}, {key: "zone", value: "zone1"}: 1,
topologyPairsMaps: &topologyPairsMaps{ {key: "zone", value: "zone2"}: 1,
podToTopologyPairs: map[string]topologyPairSet{
"p-b1_": newPairSet("zone", "zone1"),
"p-x1_": newPairSet("zone", "zone2"),
},
}, },
}, },
}, },
@ -1626,20 +1518,14 @@ func TestPodSpreadMap_removePod(t *testing.T) {
st.MakePod().Name("p-y1").Node("node-y").Label("foo", "").Obj(), st.MakePod().Name("p-y1").Node("node-y").Label("foo", "").Obj(),
}, },
deletedPodIdx: 0, // remove pod "p-a1" deletedPodIdx: 0, // remove pod "p-a1"
injectPodPointers: map[topologyPair][]int{ nodeIdx: 0, // node-a
{key: "zone", value: "zone1"}: {1}, want: &podSpreadCache{
{key: "zone", value: "zone2"}: {2, 3}, tpKeyToCriticalPaths: map[string]*criticalPaths{
}, "zone": {{"zone1", 1}, {"zone2", 2}},
want: &topologyPairsPodSpreadMap{ },
// topologyKeyToMinPodsMap is expected to be re-calculated from {"zone": 2} tpPairToMatchNum: map[topologyPair]int32{
// to {"zone": 1} {key: "zone", value: "zone1"}: 1,
topologyKeyToMinPodsMap: map[string]int32{"zone": 1}, {key: "zone", value: "zone2"}: 2,
topologyPairsMaps: &topologyPairsMaps{
podToTopologyPairs: map[string]topologyPairSet{
"p-b1_": newPairSet("zone", "zone1"),
"p-x1_": newPairSet("zone", "zone2"),
"p-y1_": newPairSet("zone", "zone2"),
},
}, },
}, },
}, },
@ -1662,20 +1548,14 @@ func TestPodSpreadMap_removePod(t *testing.T) {
st.MakePod().Name("p-y1").Node("node-y").Label("foo", "").Obj(), st.MakePod().Name("p-y1").Node("node-y").Label("foo", "").Obj(),
}, },
deletedPodIdx: 0, // remove pod "p-a0" deletedPodIdx: 0, // remove pod "p-a0"
injectPodPointers: map[topologyPair][]int{ nodeIdx: 0, // node-a
{key: "zone", value: "zone1"}: {1, 2}, want: &podSpreadCache{
{key: "zone", value: "zone2"}: {3, 4}, tpKeyToCriticalPaths: map[string]*criticalPaths{
}, "zone": {{"zone1", 2}, {"zone2", 2}},
want: &topologyPairsPodSpreadMap{ },
// topologyKeyToMinPodsMap is unchanged tpPairToMatchNum: map[topologyPair]int32{
topologyKeyToMinPodsMap: map[string]int32{"zone": 2}, {key: "zone", value: "zone1"}: 2,
topologyPairsMaps: &topologyPairsMaps{ {key: "zone", value: "zone2"}: 2,
podToTopologyPairs: map[string]topologyPairSet{
"p-a1_": newPairSet("zone", "zone1"),
"p-b1_": newPairSet("zone", "zone1"),
"p-x1_": newPairSet("zone", "zone2"),
"p-y1_": newPairSet("zone", "zone2"),
},
}, },
}, },
}, },
@ -1698,20 +1578,14 @@ func TestPodSpreadMap_removePod(t *testing.T) {
}, },
deletedPodIdx: -1, deletedPodIdx: -1,
deletedPod: st.MakePod().Name("p-a0").Node("node-a").Label("bar", "").Obj(), deletedPod: st.MakePod().Name("p-a0").Node("node-a").Label("bar", "").Obj(),
injectPodPointers: map[topologyPair][]int{ nodeIdx: 0, // node-a
{key: "zone", value: "zone1"}: {0, 1}, want: &podSpreadCache{
{key: "zone", value: "zone2"}: {2, 3}, tpKeyToCriticalPaths: map[string]*criticalPaths{
}, "zone": {{"zone1", 2}, {"zone2", 2}},
want: &topologyPairsPodSpreadMap{ },
// topologyKeyToMinPodsMap is unchanged tpPairToMatchNum: map[topologyPair]int32{
topologyKeyToMinPodsMap: map[string]int32{"zone": 2}, {key: "zone", value: "zone1"}: 2,
topologyPairsMaps: &topologyPairsMaps{ {key: "zone", value: "zone2"}: 2,
podToTopologyPairs: map[string]topologyPairSet{
"p-a1_": newPairSet("zone", "zone1"),
"p-b1_": newPairSet("zone", "zone1"),
"p-x1_": newPairSet("zone", "zone2"),
"p-y1_": newPairSet("zone", "zone2"),
},
}, },
}, },
}, },
@ -1734,41 +1608,26 @@ func TestPodSpreadMap_removePod(t *testing.T) {
st.MakePod().Name("p-x2").Node("node-x").Label("foo", "").Obj(), st.MakePod().Name("p-x2").Node("node-x").Label("foo", "").Obj(),
}, },
deletedPodIdx: 3, // remove pod "p-x1" deletedPodIdx: 3, // remove pod "p-x1"
injectPodPointers: map[topologyPair][]int{ nodeIdx: 2, // node-x
{key: "zone", value: "zone1"}: {0, 1, 2}, want: &podSpreadCache{
{key: "zone", value: "zone2"}: {4}, tpKeyToCriticalPaths: map[string]*criticalPaths{
{key: "node", value: "node-a"}: {0, 1}, "zone": {{"zone2", 1}, {"zone1", 3}},
{key: "node", value: "node-b"}: {2}, "node": {{"node-b", 1}, {"node-x", 1}},
{key: "node", value: "node-x"}: {4}, },
}, tpPairToMatchNum: map[topologyPair]int32{
want: &topologyPairsPodSpreadMap{ {key: "zone", value: "zone1"}: 3,
// topologyKeyToMinPodsMap is expected to be re-calculated from {"zone": 2, "node": 1} {key: "zone", value: "zone2"}: 1,
// to {"zone": 1, "node": 1} {key: "node", value: "node-a"}: 2,
topologyKeyToMinPodsMap: map[string]int32{"zone": 1, "node": 1}, {key: "node", value: "node-b"}: 1,
topologyPairsMaps: &topologyPairsMaps{ {key: "node", value: "node-x"}: 1,
podToTopologyPairs: map[string]topologyPairSet{
"p-a1_": newPairSet("zone", "zone1", "node", "node-a"),
"p-a2_": newPairSet("zone", "zone1", "node", "node-a"),
"p-b1_": newPairSet("zone", "zone1", "node", "node-b"),
"p-x2_": newPairSet("zone", "zone2", "node", "node-x"),
},
}, },
}, },
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
tt.want.topologyPairToPods = make(map[topologyPair]podSet)
for pair, indexes := range tt.injectPodPointers {
pSet := make(podSet)
for _, i := range indexes {
pSet[tt.existingPods[i]] = struct{}{}
}
tt.want.topologyPairToPods[pair] = pSet
}
nodeInfoMap := schedulernodeinfo.CreateNodeNameToInfoMap(tt.existingPods, tt.nodes) nodeInfoMap := schedulernodeinfo.CreateNodeNameToInfoMap(tt.existingPods, tt.nodes)
podSpreadMap, _ := getTPMapMatchingSpreadConstraints(tt.preemptor, nodeInfoMap) podSpreadCache, _ := getExistingPodSpreadCache(tt.preemptor, nodeInfoMap)
var deletedPod *v1.Pod var deletedPod *v1.Pod
if tt.deletedPodIdx < len(tt.existingPods) && tt.deletedPodIdx >= 0 { if tt.deletedPodIdx < len(tt.existingPods) && tt.deletedPodIdx >= 0 {
@ -1776,9 +1635,10 @@ func TestPodSpreadMap_removePod(t *testing.T) {
} else { } else {
deletedPod = tt.deletedPod deletedPod = tt.deletedPod
} }
podSpreadMap.removePod(deletedPod) podSpreadCache.removePod(deletedPod, tt.preemptor, tt.nodes[tt.nodeIdx])
if !reflect.DeepEqual(podSpreadMap, tt.want) { podSpreadCache.sortCriticalPaths()
t.Errorf("podSpreadMap#removePod() = %v, want %v", podSpreadMap, tt.want) if !reflect.DeepEqual(podSpreadCache, tt.want) {
t.Errorf("podSpreadCache#removePod() = %v, want %v", podSpreadCache, tt.want)
} }
}) })
} }
@ -1827,7 +1687,7 @@ func BenchmarkTestGetTPMapMatchingSpreadConstraints(b *testing.B) {
nodeNameToInfo := schedulernodeinfo.CreateNodeNameToInfoMap(existingPods, allNodes) nodeNameToInfo := schedulernodeinfo.CreateNodeNameToInfoMap(existingPods, allNodes)
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
getTPMapMatchingSpreadConstraints(tt.pod, nodeNameToInfo) getExistingPodSpreadCache(tt.pod, nodeNameToInfo)
} }
}) })
} }
@ -1846,3 +1706,14 @@ func newPairSet(kv ...string) topologyPairSet {
} }
return result return result
} }
// sortCriticalPaths is only served for testing purpose.
func (c *podSpreadCache) sortCriticalPaths() {
for _, paths := range c.tpKeyToCriticalPaths {
// If two paths both hold minimum matching number, and topologyValue is unordered.
if paths[0].matchNum == paths[1].matchNum && paths[0].topologyValue > paths[1].topologyValue {
// Swap topologyValue to make them sorted alphabetically.
paths[0].topologyValue, paths[1].topologyValue = paths[1].topologyValue, paths[0].topologyValue
}
}
}

View File

@ -1796,15 +1796,15 @@ func EvenPodsSpreadPredicate(pod *v1.Pod, meta PredicateMetadata, nodeInfo *sche
return true, nil, nil return true, nil, nil
} }
var topologyPairsPodSpreadMap *topologyPairsPodSpreadMap var podSpreadCache *podSpreadCache
if predicateMeta, ok := meta.(*predicateMetadata); ok { if predicateMeta, ok := meta.(*predicateMetadata); ok {
topologyPairsPodSpreadMap = predicateMeta.topologyPairsPodSpreadMap podSpreadCache = predicateMeta.podSpreadCache
} else { // We don't have precomputed metadata. We have to follow a slow path to check spread constraints. } else { // We don't have precomputed metadata. We have to follow a slow path to check spread constraints.
// TODO(Huang-Wei): get it implemented // TODO(autoscaler): get it implemented
return false, nil, errors.New("metadata not pre-computed for EvenPodsSpreadPredicate") return false, nil, errors.New("metadata not pre-computed for EvenPodsSpreadPredicate")
} }
if topologyPairsPodSpreadMap == nil || len(topologyPairsPodSpreadMap.topologyKeyToMinPodsMap) == 0 { if podSpreadCache == nil || len(podSpreadCache.tpPairToMatchNum) == 0 {
return true, nil, nil return true, nil, nil
} }
@ -1821,25 +1821,24 @@ func EvenPodsSpreadPredicate(pod *v1.Pod, meta PredicateMetadata, nodeInfo *sche
if err != nil { if err != nil {
return false, nil, err return false, nil, err
} }
selfMatchNum := 0 selfMatchNum := int32(0)
if selfMatch { if selfMatch {
selfMatchNum = 1 selfMatchNum = 1
} }
pair := topologyPair{key: tpKey, value: tpVal} pair := topologyPair{key: tpKey, value: tpVal}
minMatchNum, ok := topologyPairsPodSpreadMap.topologyKeyToMinPodsMap[tpKey] paths, ok := podSpreadCache.tpKeyToCriticalPaths[tpKey]
if !ok { if !ok {
// error which should not happen // error which should not happen
klog.Errorf("internal error: get minMatchNum from key %q of %#v", tpKey, topologyPairsPodSpreadMap.topologyKeyToMinPodsMap) klog.Errorf("internal error: get paths from key %q of %#v", tpKey, podSpreadCache.tpKeyToCriticalPaths)
continue continue
} }
// judging criteria: // judging criteria:
// 'existing matching num' + 'if self-match (1 or 0)' - 'global min matching num' <= 'maxSkew' // 'existing matching num' + 'if self-match (1 or 0)' - 'global min matching num' <= 'maxSkew'
matchNum := len(topologyPairsPodSpreadMap.topologyPairToPods[pair]) minMatchNum := paths[0].matchNum
matchNum := podSpreadCache.tpPairToMatchNum[pair]
// cast to int to avoid potential overflow. skew := matchNum + selfMatchNum - minMatchNum
skew := matchNum + selfMatchNum - int(minMatchNum) if skew > constraint.MaxSkew {
if skew > int(constraint.MaxSkew) {
klog.V(5).Infof("node '%s' failed spreadConstraint[%s]: matchNum(%d) + selfMatchNum(%d) - minMatchNum(%d) > maxSkew(%d)", node.Name, tpKey, matchNum, selfMatchNum, minMatchNum, constraint.MaxSkew) klog.V(5).Infof("node '%s' failed spreadConstraint[%s]: matchNum(%d) + selfMatchNum(%d) - minMatchNum(%d) > maxSkew(%d)", node.Name, tpKey, matchNum, selfMatchNum, minMatchNum, constraint.MaxSkew)
return false, []PredicateFailureReason{ErrTopologySpreadConstraintsNotMatch}, nil return false, []PredicateFailureReason{ErrTopologySpreadConstraintsNotMatch}, nil
} }

View File

@ -38,15 +38,15 @@ type topologyPair struct {
type topologySpreadConstraintsMap struct { type topologySpreadConstraintsMap struct {
// nodeNameToPodCounts is keyed with node name, and valued with the number of matching pods. // nodeNameToPodCounts is keyed with node name, and valued with the number of matching pods.
nodeNameToPodCounts map[string]int64 nodeNameToPodCounts map[string]int32
// topologyPairToPodCounts is keyed with topologyPair, and valued with the number of matching pods. // topologyPairToPodCounts is keyed with topologyPair, and valued with the number of matching pods.
topologyPairToPodCounts map[topologyPair]*int64 topologyPairToPodCounts map[topologyPair]*int32
} }
func newTopologySpreadConstraintsMap() *topologySpreadConstraintsMap { func newTopologySpreadConstraintsMap() *topologySpreadConstraintsMap {
return &topologySpreadConstraintsMap{ return &topologySpreadConstraintsMap{
nodeNameToPodCounts: make(map[string]int64), nodeNameToPodCounts: make(map[string]int32),
topologyPairToPodCounts: make(map[topologyPair]*int64), topologyPairToPodCounts: make(map[topologyPair]*int32),
} }
} }
@ -54,7 +54,7 @@ func newTopologySpreadConstraintsMap() *topologySpreadConstraintsMap {
// This function iterates <nodes> to filter out the nodes which don't have required topologyKey(s), // This function iterates <nodes> to filter out the nodes which don't have required topologyKey(s),
// and initialize two maps: // and initialize two maps:
// 1) t.topologyPairToPodCounts: keyed with both eligible topology pair and node names. // 1) t.topologyPairToPodCounts: keyed with both eligible topology pair and node names.
// 2) t.nodeNameToPodCounts: keyed with node name, and valued with a *int64 pointer for eligible node only. // 2) t.nodeNameToPodCounts: keyed with node name, and valued with a *int32 pointer for eligible node only.
func (t *topologySpreadConstraintsMap) initialize(pod *v1.Pod, nodes []*v1.Node) { func (t *topologySpreadConstraintsMap) initialize(pod *v1.Pod, nodes []*v1.Node) {
constraints := getSoftTopologySpreadConstraints(pod) constraints := getSoftTopologySpreadConstraints(pod)
for _, node := range nodes { for _, node := range nodes {
@ -64,7 +64,7 @@ func (t *topologySpreadConstraintsMap) initialize(pod *v1.Pod, nodes []*v1.Node)
for _, constraint := range constraints { for _, constraint := range constraints {
pair := topologyPair{key: constraint.TopologyKey, value: node.Labels[constraint.TopologyKey]} pair := topologyPair{key: constraint.TopologyKey, value: node.Labels[constraint.TopologyKey]}
if t.topologyPairToPodCounts[pair] == nil { if t.topologyPairToPodCounts[pair] == nil {
t.topologyPairToPodCounts[pair] = new(int64) t.topologyPairToPodCounts[pair] = new(int32)
} }
} }
t.nodeNameToPodCounts[node.Name] = 0 t.nodeNameToPodCounts[node.Name] = 0
@ -122,7 +122,7 @@ func CalculateEvenPodsSpreadPriority(pod *v1.Pod, nodeNameToInfo map[string]*sch
} }
// <matchSum> indicates how many pods (on current node) match the <constraint>. // <matchSum> indicates how many pods (on current node) match the <constraint>.
matchSum := int64(0) matchSum := int32(0)
for _, existingPod := range nodeInfo.Pods() { for _, existingPod := range nodeInfo.Pods() {
match, err := predicates.PodMatchesSpreadConstraint(existingPod.Labels, constraint) match, err := predicates.PodMatchesSpreadConstraint(existingPod.Labels, constraint)
if err != nil { if err != nil {
@ -133,7 +133,7 @@ func CalculateEvenPodsSpreadPriority(pod *v1.Pod, nodeNameToInfo map[string]*sch
matchSum++ matchSum++
} }
} }
atomic.AddInt64(t.topologyPairToPodCounts[pair], matchSum) atomic.AddInt32(t.topologyPairToPodCounts[pair], matchSum)
} }
} }
workqueue.ParallelizeUntil(ctx, 16, len(allNodeNames), processAllNode) workqueue.ParallelizeUntil(ctx, 16, len(allNodeNames), processAllNode)
@ -141,9 +141,9 @@ func CalculateEvenPodsSpreadPriority(pod *v1.Pod, nodeNameToInfo map[string]*sch
return nil, err return nil, err
} }
var minCount int64 = math.MaxInt64 var minCount int32 = math.MaxInt32
// <total> sums up the number of matching pods on each qualified topology pair // <total> sums up the number of matching pods on each qualified topology pair
var total int64 var total int32
for _, node := range nodes { for _, node := range nodes {
if _, ok := t.nodeNameToPodCounts[node.Name]; !ok { if _, ok := t.nodeNameToPodCounts[node.Name]; !ok {
continue continue

View File

@ -31,8 +31,8 @@ func Test_topologySpreadConstraintsMap_initialize(t *testing.T) {
name string name string
pod *v1.Pod pod *v1.Pod
nodes []*v1.Node nodes []*v1.Node
wantNodeNameMap map[string]int64 wantNodeNameMap map[string]int32
wantTopologyPairMap map[topologyPair]*int64 wantTopologyPairMap map[topologyPair]*int32
}{ }{
{ {
name: "normal case", name: "normal case",
@ -45,17 +45,17 @@ func Test_topologySpreadConstraintsMap_initialize(t *testing.T) {
st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").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-x").Label("zone", "zone2").Label("node", "node-x").Obj(),
}, },
wantNodeNameMap: map[string]int64{ wantNodeNameMap: map[string]int32{
"node-a": 0, "node-a": 0,
"node-b": 0, "node-b": 0,
"node-x": 0, "node-x": 0,
}, },
wantTopologyPairMap: map[topologyPair]*int64{ wantTopologyPairMap: map[topologyPair]*int32{
{key: "zone", value: "zone1"}: new(int64), {key: "zone", value: "zone1"}: new(int32),
{key: "zone", value: "zone2"}: new(int64), {key: "zone", value: "zone2"}: new(int32),
{key: "node", value: "node-a"}: new(int64), {key: "node", value: "node-a"}: new(int32),
{key: "node", value: "node-b"}: new(int64), {key: "node", value: "node-b"}: new(int32),
{key: "node", value: "node-x"}: new(int64), {key: "node", value: "node-x"}: new(int32),
}, },
}, },
{ {
@ -69,14 +69,14 @@ func Test_topologySpreadConstraintsMap_initialize(t *testing.T) {
st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").Obj(), st.MakeNode().Name("node-b").Label("zone", "zone1").Label("node", "node-b").Obj(),
st.MakeNode().Name("node-x").Label("node", "node-x").Obj(), st.MakeNode().Name("node-x").Label("node", "node-x").Obj(),
}, },
wantNodeNameMap: map[string]int64{ wantNodeNameMap: map[string]int32{
"node-a": 0, "node-a": 0,
"node-b": 0, "node-b": 0,
}, },
wantTopologyPairMap: map[topologyPair]*int64{ wantTopologyPairMap: map[topologyPair]*int32{
{key: "zone", value: "zone1"}: new(int64), {key: "zone", value: "zone1"}: new(int32),
{key: "node", value: "node-a"}: new(int64), {key: "node", value: "node-a"}: new(int32),
{key: "node", value: "node-b"}: new(int64), {key: "node", value: "node-b"}: new(int32),
}, },
}, },
} }

View File

@ -1107,7 +1107,7 @@ func selectVictimsOnNode(
removePod := func(rp *v1.Pod) { removePod := func(rp *v1.Pod) {
nodeInfoCopy.RemovePod(rp) nodeInfoCopy.RemovePod(rp)
if meta != nil { if meta != nil {
meta.RemovePod(rp) meta.RemovePod(rp, nodeInfoCopy.Node())
} }
} }
addPod := func(ap *v1.Pod) { addPod := func(ap *v1.Pod) {