diff --git a/pkg/scheduler/algorithm/predicates/BUILD b/pkg/scheduler/algorithm/predicates/BUILD index 5e6c799e5c4..dd31d198949 100644 --- a/pkg/scheduler/algorithm/predicates/BUILD +++ b/pkg/scheduler/algorithm/predicates/BUILD @@ -64,6 +64,7 @@ go_test( "//pkg/scheduler/api:go_default_library", "//pkg/scheduler/nodeinfo:go_default_library", "//pkg/scheduler/testing:go_default_library", + "//pkg/scheduler/util:go_default_library", "//pkg/volume/util:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/api/storage/v1:go_default_library", diff --git a/pkg/scheduler/algorithm/predicates/error.go b/pkg/scheduler/algorithm/predicates/error.go index 372cfc8ddf9..d5325af3f2e 100644 --- a/pkg/scheduler/algorithm/predicates/error.go +++ b/pkg/scheduler/algorithm/predicates/error.go @@ -75,8 +75,8 @@ var ( ErrVolumeNodeConflict = newPredicateFailureError("VolumeNodeAffinityConflict", "node(s) had volume node affinity conflict") // ErrVolumeBindConflict is used for VolumeBindingNoMatch predicate error. ErrVolumeBindConflict = newPredicateFailureError("VolumeBindingNoMatch", "node(s) didn't find available persistent volumes to bind") - // ErrSpreadConstraintsNotMatch is used for EvenPodsSpread predicate error. - ErrSpreadConstraintsNotMatch = newPredicateFailureError("EvenPodsSpread", "node(s) didn't match pod topology spread constraints") + // ErrTopologySpreadConstraintsNotMatch is used for EvenPodsSpread predicate error. + ErrTopologySpreadConstraintsNotMatch = newPredicateFailureError("EvenPodsSpreadNotMatch", "node(s) didn't match pod topology spread constraints") // ErrFakePredicate is used for test only. The fake predicates returning false also returns error // as ErrFakePredicate. ErrFakePredicate = newPredicateFailureError("FakePredicateError", "Nodes failed the fake predicate") diff --git a/pkg/scheduler/algorithm/predicates/metadata.go b/pkg/scheduler/algorithm/predicates/metadata.go index cd5d0367d1a..147f2ef5d8a 100644 --- a/pkg/scheduler/algorithm/predicates/metadata.go +++ b/pkg/scheduler/algorithm/predicates/metadata.go @@ -35,6 +35,9 @@ import ( schedutil "k8s.io/kubernetes/pkg/scheduler/util" ) +// MaxInt32 is the maximum value of int32 +const MaxInt32 = math.MaxInt32 + // PredicateMetadata interface represents anything that can access a predicate metadata. type PredicateMetadata interface { ShallowCopy() PredicateMetadata diff --git a/pkg/scheduler/algorithm/predicates/predicates.go b/pkg/scheduler/algorithm/predicates/predicates.go index 6487a6bcdec..4fefdbbe577 100644 --- a/pkg/scheduler/algorithm/predicates/predicates.go +++ b/pkg/scheduler/algorithm/predicates/predicates.go @@ -1722,9 +1722,60 @@ func EvenPodsSpreadPredicate(pod *v1.Pod, meta PredicateMetadata, nodeInfo *sche if node == nil { return false, nil, fmt.Errorf("node not found") } + constraints := getHardTopologySpreadConstraints(pod) if len(constraints) == 0 { return true, nil, nil } + + var topologyPairsPodSpreadMap *topologyPairsPodSpreadMap + if predicateMeta, ok := meta.(*predicateMetadata); ok { + topologyPairsPodSpreadMap = predicateMeta.topologyPairsPodSpreadMap + } else { // We don't have precomputed metadata. We have to follow a slow path to check spread constraints. + // TODO(Huang-Wei): get it implemented + return false, nil, errors.New("metadata not pre-computed for EvenPodsSpreadPredicate") + } + + if topologyPairsPodSpreadMap == nil || len(topologyPairsPodSpreadMap.topologyKeyToMinPodsMap) == 0 { + return true, nil, nil + } + + selfMatch, err := podLabelsMatchesSpreadConstraints(pod.Labels, constraints) + if err != nil { + return false, nil, err + } + selfMatchNum := 0 + if selfMatch { + selfMatchNum = 1 + } + for _, constraint := range constraints { + tpKey := constraint.TopologyKey + tpVal, ok := node.Labels[constraint.TopologyKey] + if !ok { + klog.V(5).Infof("node '%s' doesn't have required label '%s'", node.Name, tpKey) + return false, []PredicateFailureReason{ErrTopologySpreadConstraintsNotMatch}, nil + } + + pair := topologyPair{key: tpKey, value: tpVal} + minMatchNum, ok := topologyPairsPodSpreadMap.topologyKeyToMinPodsMap[tpKey] + if !ok { + // error which should not happen + klog.Errorf("internal error: get minMatchNum from key %q of %#v", tpKey, topologyPairsPodSpreadMap.topologyKeyToMinPodsMap) + continue + } + // judging criteria: + // 'existing matching num' + 'if self-match (1 or 0)' - 'global min matching num' <= 'maxSkew' + matchNum := len(topologyPairsPodSpreadMap.topologyPairToPods[pair]) + + // TODO(Huang-Wei): remove this after thorough testing + // fmt.Printf("node '%s' => spreadConstraint[%s]: matchNum(%d) + selfMatchNum(%d) - minMatchNum(%d) ?<= maxSkew(%d)\n", node.Name, tpKey, matchNum, selfMatchNum, minMatchNum, constraint.MaxSkew) + + // TODO(Huang-Wei): check if it can overflow? + if int32(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 true, nil, nil } diff --git a/pkg/scheduler/algorithm/predicates/predicates_test.go b/pkg/scheduler/algorithm/predicates/predicates_test.go index f45f30d5c7f..b091c02dac6 100644 --- a/pkg/scheduler/algorithm/predicates/predicates_test.go +++ b/pkg/scheduler/algorithm/predicates/predicates_test.go @@ -35,7 +35,7 @@ import ( "k8s.io/kubernetes/pkg/features" schedulerapi "k8s.io/kubernetes/pkg/scheduler/api" schedulernodeinfo "k8s.io/kubernetes/pkg/scheduler/nodeinfo" - schedulertesting "k8s.io/kubernetes/pkg/scheduler/testing" + st "k8s.io/kubernetes/pkg/scheduler/testing" ) var ( @@ -95,7 +95,7 @@ func newResourceOverheadPod(pod *v1.Pod, overhead v1.ResourceList) *v1.Pod { } func GetPredicateMetadata(p *v1.Pod, nodeInfo map[string]*schedulernodeinfo.NodeInfo) PredicateMetadata { - pm := PredicateMetadataFactory{schedulertesting.FakePodLister{p}} + pm := PredicateMetadataFactory{st.FakePodLister{p}} return pm.GetMetadata(p, nodeInfo) } @@ -1863,7 +1863,7 @@ func TestServiceAffinity(t *testing.T) { nodeInfo.SetNode(test.node) nodeInfoMap := map[string]*schedulernodeinfo.NodeInfo{test.node.Name: nodeInfo} // Reimplementing the logic that the scheduler implements: Any time it makes a predicate, it registers any precomputations. - predicate, precompute := NewServiceAffinityPredicate(schedulertesting.FakePodLister(test.pods), schedulertesting.FakeServiceLister(test.services), FakeNodeListInfo(nodes), test.labels) + predicate, precompute := NewServiceAffinityPredicate(st.FakePodLister(test.pods), st.FakeServiceLister(test.services), FakeNodeListInfo(nodes), test.labels) // Register a precomputation or Rewrite the precomputation to a no-op, depending on the state we want to test. RegisterPredicateMetadataProducer("ServiceAffinityMetaProducer", func(pm *predicateMetadata) { if !skipPrecompute { @@ -2937,7 +2937,7 @@ func TestInterPodAffinity(t *testing.T) { fit := PodAffinityChecker{ info: FakeNodeInfo(*node), - podLister: schedulertesting.FakePodLister(test.pods), + podLister: st.FakePodLister(test.pods), } nodeInfo := schedulernodeinfo.NewNodeInfo(podsOnNode...) nodeInfo.SetNode(test.node) @@ -4051,7 +4051,7 @@ func TestInterPodAffinityWithMultipleNodes(t *testing.T) { for indexNode, node := range test.nodes { testFit := PodAffinityChecker{ info: nodeListInfo, - podLister: schedulertesting.FakePodLister(test.pods), + podLister: st.FakePodLister(test.pods), } var meta PredicateMetadata @@ -5039,3 +5039,363 @@ func TestCheckNodeUnschedulablePredicate(t *testing.T) { } } } + +func TestEvenPodsSpreadPredicate_SingleConstraint(t *testing.T) { + tests := []struct { + name string + pod *v1.Pod + nodes []*v1.Node + existingPods []*v1.Pod + fits map[string]bool + }{ + { + name: "no existing pods", + pod: st.MakePod().Name("p").Label("foo", "").SpreadConstraint( + 1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), + ).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(), + }, + fits: map[string]bool{ + "node-a": true, + "node-b": true, + "node-x": true, + "node-y": true, + }, + }, + { + name: "no existing pods, incoming pod doesn't match itself", + pod: st.MakePod().Name("p").Label("foo", "").SpreadConstraint( + 1, "zone", hardSpread, st.MakeLabelSelector().Exists("bar").Obj(), + ).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(), + }, + fits: map[string]bool{ + "node-a": true, + "node-b": true, + "node-x": true, + "node-y": true, + }, + }, + { + name: "existing pods with mis-matched namespace doens't count", + pod: st.MakePod().Name("p").Label("foo", "").SpreadConstraint( + 1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), + ).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").Namespace("ns1").Node("node-a").Label("foo", "").Obj(), + st.MakePod().Name("p-b1").Namespace("ns2").Node("node-a").Label("foo", "").Obj(), + st.MakePod().Name("p-x1").Node("node-x").Label("foo", "").Obj(), + st.MakePod().Name("p-y1").Node("node-y").Label("foo", "").Obj(), + }, + fits: map[string]bool{ + "node-a": true, + "node-b": true, + "node-x": false, + "node-y": false, + }, + }, + { + name: "pods spread across zones as 3/3, all nodes fit", + pod: st.MakePod().Name("p").Label("foo", "").SpreadConstraint( + 1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), + ).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", "").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(), + st.MakePod().Name("p-y3").Node("node-y").Label("foo", "").Obj(), + }, + fits: map[string]bool{ + "node-a": true, + "node-b": true, + "node-x": true, + "node-y": true, + }, + }, + { + // TODO(Huang-Wei): maybe document this to remind users that typos on node labels + // can cause unexpected behavior + name: "pods spread across zones as 1/2 due to absence of label 'zone' on node-b", + pod: st.MakePod().Name("p").Label("foo", "").SpreadConstraint( + 1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), + ).Obj(), + nodes: []*v1.Node{ + st.MakeNode().Name("node-a").Label("zone", "zone1").Label("node", "node-a").Obj(), + st.MakeNode().Name("node-b").Label("zon", "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", "").Obj(), + st.MakePod().Name("p-b1").Node("node-b").Label("foo", "").Obj(), + st.MakePod().Name("p-x1").Node("node-x").Label("foo", "").Obj(), + st.MakePod().Name("p-y1").Node("node-y").Label("foo", "").Obj(), + }, + fits: map[string]bool{ + "node-a": true, + "node-b": false, + "node-x": false, + "node-y": false, + }, + }, + { + name: "pods spread across nodes as 2/1/0/3, only node-x fits", + pod: st.MakePod().Name("p").Label("foo", "").SpreadConstraint( + 1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), + ).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", "").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(), + st.MakePod().Name("p-y3").Node("node-y").Label("foo", "").Obj(), + }, + fits: map[string]bool{ + "node-a": false, + "node-b": false, + "node-x": true, + "node-y": false, + }, + }, + { + name: "pods spread across nodes as 2/1/0/3, maxSkew is 2, node-b and node-x fit", + pod: st.MakePod().Name("p").Label("foo", "").SpreadConstraint( + 2, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), + ).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", "").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(), + st.MakePod().Name("p-y3").Node("node-y").Label("foo", "").Obj(), + }, + fits: map[string]bool{ + "node-a": false, + "node-b": true, + "node-x": true, + "node-y": false, + }, + }, + { + // not a desired case, but it can happen + // TODO(Huang-Wei): document this "pod-not-match-itself" case + // in this case, placement of the new pod doesn't change pod distribution of the cluster + // as the incoming pod doesn't have label "foo" + name: "pods spread across nodes as 2/1/0/3, but pod doesn't match itself", + pod: st.MakePod().Name("p").Label("bar", "").SpreadConstraint( + 1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj(), + ).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", "").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(), + st.MakePod().Name("p-y3").Node("node-y").Label("foo", "").Obj(), + }, + fits: map[string]bool{ + "node-a": false, + "node-b": true, + "node-x": true, + "node-y": false, + }, + }, + { + // only node-a and node-y are considered, so pods spread as 2/~1~/~0~/3 + // ps: '~num~' is a markdown symbol to denote a crossline through 'num' + // but in this unit test, we don't run NodeAffinityPredicate, so node-b and node-x are + // still expected to be fits; + // the fact that node-a fits can prove the underlying logic works + name: "incoming pod has nodeAffinity, pods spread as 2/~1~/~0~/3, hence node-a fits", + pod: st.MakePod().Name("p").Label("foo", ""). + NodeAffinityIn("node", []string{"node-a", "node-y"}). + SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()). + 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", "").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(), + st.MakePod().Name("p-y3").Node("node-y").Label("foo", "").Obj(), + }, + fits: map[string]bool{ + "node-a": true, + "node-b": true, // in real case, it's false + "node-x": true, // in real case, it's false + "node-y": false, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + nodeInfoMap := schedulernodeinfo.CreateNodeNameToInfoMap(tt.existingPods, tt.nodes) + meta := GetPredicateMetadata(tt.pod, nodeInfoMap) + for _, node := range tt.nodes { + fits, _, _ := EvenPodsSpreadPredicate(tt.pod, meta, nodeInfoMap[node.Name]) + if fits != tt.fits[node.Name] { + t.Errorf("[%s]: expected %v got %v", node.Name, tt.fits[node.Name], fits) + } + } + }) + } +} + +func TestEvenPodsSpreadPredicate_MultipleConstraints(t *testing.T) { + tests := []struct { + name string + pod *v1.Pod + nodes []*v1.Node + existingPods []*v1.Pod + fits map[string]bool + }{ + { + // 1. to fulfil "zone" constraint, incoming pod can be placed on any zone (hence any node) + // 2. to fulfil "node" constraint, incoming pod can be placed on node-x + // intersaction of (1) and (2) returns node-x + name: "two constraints on zone and node, spreads = [3/3, 2/1/0/3]", + pod: st.MakePod().Name("p").Label("foo", ""). + SpreadConstraint(1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()). + SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()). + 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", "").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(), + st.MakePod().Name("p-y3").Node("node-y").Label("foo", "").Obj(), + }, + fits: map[string]bool{ + "node-a": false, + "node-b": false, + "node-x": true, + "node-y": false, + }, + }, + { + // 1. to fulfil "zone" constraint, incoming pod can be placed on zone1 (node-a or node-b) + // 2. to fulfil "node" constraint, incoming pod can be placed on node-x + // intersaction of (1) and (2) returns no node + name: "two constraints on zone and node, spreads = [3/4, 2/1/0/4]", + pod: st.MakePod().Name("p").Label("foo", ""). + SpreadConstraint(1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()). + SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()). + 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", "").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(), + st.MakePod().Name("p-y3").Node("node-y").Label("foo", "").Obj(), + st.MakePod().Name("p-y4").Node("node-y").Label("foo", "").Obj(), + }, + fits: map[string]bool{ + "node-a": false, + "node-b": false, + "node-x": false, + "node-y": false, + }, + }, + { + name: "constraints hold different labelSelectors, spreads = [1/1, 1/0/0/1]", + pod: st.MakePod().Name("p").Label("foo", "").Label("bar", ""). + SpreadConstraint(1, "zone", hardSpread, st.MakeLabelSelector().Exists("foo").Obj()). + SpreadConstraint(1, "node", hardSpread, st.MakeLabelSelector().Exists("bar").Obj()). + 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", "").Obj(), + st.MakePod().Name("p-a2").Node("node-a").Label("foo", "").Label("bar", "").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", "").Label("bar", "").Obj(), + st.MakePod().Name("p-y3").Node("node-y").Label("foo", "").Obj(), + }, + fits: map[string]bool{ + "node-a": false, + "node-b": true, + "node-x": true, + "node-y": false, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + nodeInfoMap := schedulernodeinfo.CreateNodeNameToInfoMap(tt.existingPods, tt.nodes) + meta := GetPredicateMetadata(tt.pod, nodeInfoMap) + for _, node := range tt.nodes { + fits, _, _ := EvenPodsSpreadPredicate(tt.pod, meta, nodeInfoMap[node.Name]) + if fits != tt.fits[node.Name] { + t.Errorf("[%s]: expected %v got %v", node.Name, tt.fits, fits) + } + } + }) + } +}