func (g *genericScheduler) Schedule(pod *api.Pod, nodeLister algorithm.NodeLister) (string, error) { nodes, err := nodeLister.List() if err != nil { return "", err } if len(nodes.Items) == 0 { return "", ErrNoNodesAvailable } filteredNodes, failedPredicateMap, err := findNodesThatFit(pod, g.pods, g.predicates, nodes) if err != nil { return "", err } priorityList, err := PrioritizeNodes(pod, g.pods, g.prioritizers, algorithm.FakeNodeLister(filteredNodes)) if err != nil { return "", err } if len(priorityList) == 0 { return "", &FitError{ Pod: pod, FailedPredicates: failedPredicateMap, } } return g.selectHost(priorityList) }
func (g *genericScheduler) Schedule(pod *api.Pod, nodeLister algorithm.NodeLister) (string, error) { nodes, err := nodeLister.List() if err != nil { return "", err } if len(nodes.Items) == 0 { return "", ErrNoNodesAvailable } // TODO: we should compute this once and dynamically update it using Watch, not constantly re-compute. // But at least we're now only doing it in one place machinesToPods, err := predicates.MapPodsToMachines(g.pods) if err != nil { return "", err } filteredNodes, failedPredicateMap, err := findNodesThatFit(pod, machinesToPods, g.predicates, nodes, g.extenders) if err != nil { return "", err } priorityList, err := PrioritizeNodes(pod, machinesToPods, g.pods, g.prioritizers, algorithm.FakeNodeLister(filteredNodes), g.extenders) if err != nil { return "", err } if len(priorityList) == 0 { return "", &FitError{ Pod: pod, FailedPredicates: failedPredicateMap, } } return g.selectHost(priorityList) }
// Schedule tries to schedule the given pod to one of node in the node list. // If it succeeds, it will return the name of the node. // If it fails, it will return a Fiterror error with reasons. func (g *genericScheduler) Schedule(pod *api.Pod, nodeLister algorithm.NodeLister) (string, error) { nodes, err := nodeLister.List() if err != nil { return "", err } if len(nodes.Items) == 0 { return "", ErrNoNodesAvailable } // Used for all fit and priority funcs. nodeNameToInfo, err := g.cache.GetNodeNameToInfoMap() if err != nil { return "", err } filteredNodes, failedPredicateMap, err := findNodesThatFit(pod, nodeNameToInfo, g.predicates, nodes, g.extenders) if err != nil { return "", err } if len(filteredNodes.Items) == 0 { return "", &FitError{ Pod: pod, FailedPredicates: failedPredicateMap, } } priorityList, err := PrioritizeNodes(pod, nodeNameToInfo, g.prioritizers, algorithm.FakeNodeLister(filteredNodes), g.extenders) if err != nil { return "", err } return g.selectHost(priorityList) }
func TestSchedulerNoPhantomPodAfterDelete(t *testing.T) { stop := make(chan struct{}) defer close(stop) queuedPodStore := clientcache.NewFIFO(clientcache.MetaNamespaceKeyFunc) scache := schedulercache.New(10*time.Minute, stop) firstPod := podWithPort("pod.Name", "", 8080) node := api.Node{ObjectMeta: api.ObjectMeta{Name: "machine1"}} scache.AddNode(&node) nodeLister := algorithm.FakeNodeLister([]*api.Node{&node}) predicateMap := map[string]algorithm.FitPredicate{"PodFitsHostPorts": predicates.PodFitsHostPorts} scheduler, bindingChan, errChan := setupTestSchedulerWithOnePodOnNode(t, queuedPodStore, scache, nodeLister, predicateMap, firstPod, &node) // We use conflicted pod ports to incur fit predicate failure. secondPod := podWithPort("bar", "", 8080) queuedPodStore.Add(secondPod) // queuedPodStore: [bar:8080] // cache: [(assumed)foo:8080] scheduler.scheduleOne() select { case err := <-errChan: expectErr := &FitError{ Pod: secondPod, FailedPredicates: FailedPredicateMap{node.Name: []algorithm.PredicateFailureReason{predicates.ErrPodNotFitsHostPorts}}, } if !reflect.DeepEqual(expectErr, err) { t.Errorf("err want=%v, get=%v", expectErr, err) } case <-time.After(wait.ForeverTestTimeout): t.Fatalf("timeout after %v", wait.ForeverTestTimeout) } // We mimic the workflow of cache behavior when a pod is removed by user. // Note: if the schedulercache timeout would be super short, the first pod would expire // and would be removed itself (without any explicit actions on schedulercache). Even in that case, // explicitly AddPod will as well correct the behavior. firstPod.Spec.NodeName = node.Name if err := scache.AddPod(firstPod); err != nil { t.Fatalf("err: %v", err) } if err := scache.RemovePod(firstPod); err != nil { t.Fatalf("err: %v", err) } queuedPodStore.Add(secondPod) scheduler.scheduleOne() select { case b := <-bindingChan: expectBinding := &api.Binding{ ObjectMeta: api.ObjectMeta{Name: "bar"}, Target: api.ObjectReference{Kind: "Node", Name: node.Name}, } if !reflect.DeepEqual(expectBinding, b) { t.Errorf("binding want=%v, get=%v", expectBinding, b) } case <-time.After(wait.ForeverTestTimeout): t.Fatalf("timeout after %v", wait.ForeverTestTimeout) } }
func TestSchedulerNoPhantomPodAfterExpire(t *testing.T) { stop := make(chan struct{}) defer close(stop) queuedPodStore := clientcache.NewFIFO(clientcache.MetaNamespaceKeyFunc) scache := schedulercache.New(100*time.Millisecond, stop) pod := podWithPort("pod.Name", "", 8080) node := api.Node{ObjectMeta: api.ObjectMeta{Name: "machine1"}} scache.AddNode(&node) nodeLister := algorithm.FakeNodeLister([]*api.Node{&node}) predicateMap := map[string]algorithm.FitPredicate{"PodFitsHostPorts": predicates.PodFitsHostPorts} scheduler, bindingChan, _ := setupTestSchedulerWithOnePodOnNode(t, queuedPodStore, scache, nodeLister, predicateMap, pod, &node) waitPodExpireChan := make(chan struct{}) timeout := make(chan struct{}) go func() { for { select { case <-timeout: return default: } pods, err := scache.List(labels.Everything()) if err != nil { t.Fatalf("cache.List failed: %v", err) } if len(pods) == 0 { close(waitPodExpireChan) return } time.Sleep(100 * time.Millisecond) } }() // waiting for the assumed pod to expire select { case <-waitPodExpireChan: case <-time.After(wait.ForeverTestTimeout): close(timeout) t.Fatalf("timeout after %v", wait.ForeverTestTimeout) } // We use conflicted pod ports to incur fit predicate failure if first pod not removed. secondPod := podWithPort("bar", "", 8080) queuedPodStore.Add(secondPod) scheduler.scheduleOne() select { case b := <-bindingChan: expectBinding := &api.Binding{ ObjectMeta: api.ObjectMeta{Name: "bar"}, Target: api.ObjectReference{Kind: "Node", Name: node.Name}, } if !reflect.DeepEqual(expectBinding, b) { t.Errorf("binding want=%v, get=%v", expectBinding, b) } case <-time.After(wait.ForeverTestTimeout): t.Fatalf("timeout after %v", wait.ForeverTestTimeout) } }
// Scheduler should preserve predicate constraint even if binding was longer // than cache ttl func TestSchedulerErrorWithLongBinding(t *testing.T) { stop := make(chan struct{}) defer close(stop) firstPod := podWithPort("foo", "", 8080) conflictPod := podWithPort("bar", "", 8080) pods := map[string]*v1.Pod{firstPod.Name: firstPod, conflictPod.Name: conflictPod} for _, test := range []struct { Expected map[string]bool CacheTTL time.Duration BindingDuration time.Duration }{ { Expected: map[string]bool{firstPod.Name: true}, CacheTTL: 100 * time.Millisecond, BindingDuration: 300 * time.Millisecond, }, { Expected: map[string]bool{firstPod.Name: true}, CacheTTL: 10 * time.Second, BindingDuration: 300 * time.Millisecond, }, } { queuedPodStore := clientcache.NewFIFO(clientcache.MetaNamespaceKeyFunc) scache := schedulercache.New(test.CacheTTL, stop) node := v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "machine1"}} scache.AddNode(&node) nodeLister := algorithm.FakeNodeLister([]*v1.Node{&node}) predicateMap := map[string]algorithm.FitPredicate{"PodFitsHostPorts": predicates.PodFitsHostPorts} scheduler, bindingChan := setupTestSchedulerLongBindingWithRetry( queuedPodStore, scache, nodeLister, predicateMap, stop, test.BindingDuration) scheduler.Run() queuedPodStore.Add(firstPod) queuedPodStore.Add(conflictPod) resultBindings := map[string]bool{} waitChan := time.After(5 * time.Second) for finished := false; !finished; { select { case b := <-bindingChan: resultBindings[b.Name] = true p := pods[b.Name] p.Spec.NodeName = b.Target.Name scache.AddPod(p) case <-waitChan: finished = true } } if !reflect.DeepEqual(resultBindings, test.Expected) { t.Errorf("Result binding are not equal to expected. %v != %v", resultBindings, test.Expected) } } }
func TestSchedulerFailedSchedulingReasons(t *testing.T) { stop := make(chan struct{}) defer close(stop) queuedPodStore := clientcache.NewFIFO(clientcache.MetaNamespaceKeyFunc) scache := schedulercache.New(10*time.Minute, stop) node := api.Node{ ObjectMeta: api.ObjectMeta{Name: "machine1"}, Status: api.NodeStatus{ Capacity: api.ResourceList{ api.ResourceCPU: *(resource.NewQuantity(2, resource.DecimalSI)), api.ResourceMemory: *(resource.NewQuantity(100, resource.DecimalSI)), api.ResourcePods: *(resource.NewQuantity(10, resource.DecimalSI)), }, Allocatable: api.ResourceList{ api.ResourceCPU: *(resource.NewQuantity(2, resource.DecimalSI)), api.ResourceMemory: *(resource.NewQuantity(100, resource.DecimalSI)), api.ResourcePods: *(resource.NewQuantity(10, resource.DecimalSI)), }}, } scache.AddNode(&node) nodeLister := algorithm.FakeNodeLister([]*api.Node{&node}) predicateMap := map[string]algorithm.FitPredicate{ "PodFitsResources": predicates.PodFitsResources, } scheduler, _, errChan := setupTestScheduler(queuedPodStore, scache, nodeLister, predicateMap) podWithTooBigResourceRequests := podWithResources("bar", "", api.ResourceList{ api.ResourceCPU: *(resource.NewQuantity(4, resource.DecimalSI)), api.ResourceMemory: *(resource.NewQuantity(500, resource.DecimalSI)), }, api.ResourceList{ api.ResourceCPU: *(resource.NewQuantity(4, resource.DecimalSI)), api.ResourceMemory: *(resource.NewQuantity(500, resource.DecimalSI)), }) queuedPodStore.Add(podWithTooBigResourceRequests) scheduler.scheduleOne() select { case err := <-errChan: expectErr := &FitError{ Pod: podWithTooBigResourceRequests, FailedPredicates: FailedPredicateMap{node.Name: []algorithm.PredicateFailureReason{ predicates.NewInsufficientResourceError(api.ResourceCPU, 4000, 0, 2000), predicates.NewInsufficientResourceError(api.ResourceMemory, 500, 0, 100), }}, } if !reflect.DeepEqual(expectErr, err) { t.Errorf("err want=%+v, get=%+v", expectErr, err) } case <-time.After(wait.ForeverTestTimeout): t.Fatalf("timeout after %v", wait.ForeverTestTimeout) } }
// queuedPodStore: pods queued before processing. // cache: scheduler cache that might contain assumed pods. func setupTestSchedulerWithOnePod(t *testing.T, queuedPodStore *clientcache.FIFO, scache schedulercache.Cache, pod *api.Pod) (*Scheduler, chan *api.Binding, chan error) { // Create the scheduler config algo := NewGenericScheduler( scache, map[string]algorithm.FitPredicate{"PodFitsHostPorts": predicates.PodFitsHostPorts}, []algorithm.PriorityConfig{}, []algorithm.SchedulerExtender{}) bindingChan := make(chan *api.Binding, 1) errChan := make(chan error, 1) cfg := &Config{ SchedulerCache: scache, NodeLister: algorithm.FakeNodeLister( []*api.Node{{ObjectMeta: api.ObjectMeta{Name: "machine1"}}}, ), Algorithm: algo, Binder: fakeBinder{func(b *api.Binding) error { bindingChan <- b return nil }}, NextPod: func() *api.Pod { return clientcache.Pop(queuedPodStore).(*api.Pod) }, Error: func(p *api.Pod, err error) { errChan <- err }, Recorder: &record.FakeRecorder{}, PodConditionUpdater: fakePodConditionUpdater{}, } scheduler := New(cfg) queuedPodStore.Add(pod) // queuedPodStore: [foo:8080] // cache: [] scheduler.scheduleOne() // queuedPodStore: [] // cache: [(assumed)foo:8080] select { case b := <-bindingChan: expectBinding := &api.Binding{ ObjectMeta: api.ObjectMeta{Name: "pod.Name"}, Target: api.ObjectReference{Kind: "Node", Name: "machine1"}, } if !reflect.DeepEqual(expectBinding, b) { t.Errorf("binding want=%v, get=%v", expectBinding, b) } case <-time.After(wait.ForeverTestTimeout): t.Fatalf("timeout after %v", wait.ForeverTestTimeout) } return scheduler, bindingChan, errChan }
func TestSchedulerRateLimitsBinding(t *testing.T) { scheduledPodStore := cache.NewStore(cache.MetaNamespaceKeyFunc) scheduledPodLister := &cache.StoreToPodLister{Store: scheduledPodStore} queuedPodStore := cache.NewFIFO(cache.MetaNamespaceKeyFunc) queuedPodLister := &cache.StoreToPodLister{Store: queuedPodStore} modeler := NewSimpleModeler(queuedPodLister, scheduledPodLister) algo := NewGenericScheduler( map[string]algorithm.FitPredicate{}, []algorithm.PriorityConfig{}, []algorithm.SchedulerExtender{}, modeler.PodLister(), rand.New(rand.NewSource(time.Now().UnixNano()))) // Rate limit to 1 pod fr := FakeRateLimiter{util.NewTokenBucketRateLimiter(0.02, 1), []bool{}} c := &Config{ Modeler: modeler, NodeLister: algorithm.FakeNodeLister( api.NodeList{Items: []api.Node{{ObjectMeta: api.ObjectMeta{Name: "machine1"}}}}, ), Algorithm: algo, Binder: fakeBinder{func(b *api.Binding) error { return nil }}, NextPod: func() *api.Pod { return queuedPodStore.Pop().(*api.Pod) }, Error: func(p *api.Pod, err error) { t.Errorf("Unexpected error when scheduling pod %+v: %v", p, err) }, Recorder: &record.FakeRecorder{}, BindPodsRateLimiter: &fr, } s := New(c) firstPod := podWithID("foo", "") secondPod := podWithID("boo", "") queuedPodStore.Add(firstPod) queuedPodStore.Add(secondPod) for i, hitRateLimit := range []bool{true, false} { s.scheduleOne() if fr.acceptValues[i] != hitRateLimit { t.Errorf("Unexpected rate limiting, expect rate limit to be: %v but found it was %v", hitRateLimit, fr.acceptValues[i]) } } }
// Schedule tries to schedule the given pod to one of node in the node list. // If it succeeds, it will return the name of the node. // If it fails, it will return a Fiterror error with reasons. func (g *genericScheduler) Schedule(pod *api.Pod, nodeLister algorithm.NodeLister) (string, error) { var trace *util.Trace if pod != nil { trace = util.NewTrace(fmt.Sprintf("Scheduling %s/%s", pod.Namespace, pod.Name)) } else { trace = util.NewTrace("Scheduling <nil> pod") } defer trace.LogIfLong(20 * time.Millisecond) nodes, err := nodeLister.List() if err != nil { return "", err } if len(nodes.Items) == 0 { return "", ErrNoNodesAvailable } // Used for all fit and priority funcs. nodeNameToInfo, err := g.cache.GetNodeNameToInfoMap() if err != nil { return "", err } trace.Step("Computing predicates") filteredNodes, failedPredicateMap, err := findNodesThatFit(pod, nodeNameToInfo, g.predicates, nodes, g.extenders) if err != nil { return "", err } if len(filteredNodes.Items) == 0 { return "", &FitError{ Pod: pod, FailedPredicates: failedPredicateMap, } } trace.Step("Prioritizing") priorityList, err := PrioritizeNodes(pod, nodeNameToInfo, g.prioritizers, algorithm.FakeNodeLister(filteredNodes), g.extenders) if err != nil { return "", err } trace.Step("Selecting host") return g.selectHost(priorityList) }
func TestScheduler(t *testing.T) { eventBroadcaster := record.NewBroadcaster() defer eventBroadcaster.StartLogging(t.Logf).Stop() errS := errors.New("scheduler") errB := errors.New("binder") table := []struct { injectBindError error sendPod *api.Pod algo algorithm.ScheduleAlgorithm expectErrorPod *api.Pod expectAssumedPod *api.Pod expectError error expectBind *api.Binding eventReason string }{ { sendPod: podWithID("foo", ""), algo: mockScheduler{"machine1", nil}, expectBind: &api.Binding{ObjectMeta: api.ObjectMeta{Name: "foo"}, Target: api.ObjectReference{Kind: "Node", Name: "machine1"}}, expectAssumedPod: podWithID("foo", "machine1"), eventReason: "Scheduled", }, { sendPod: podWithID("foo", ""), algo: mockScheduler{"machine1", errS}, expectError: errS, expectErrorPod: podWithID("foo", ""), eventReason: "FailedScheduling", }, { sendPod: podWithID("foo", ""), algo: mockScheduler{"machine1", nil}, expectBind: &api.Binding{ObjectMeta: api.ObjectMeta{Name: "foo"}, Target: api.ObjectReference{Kind: "Node", Name: "machine1"}}, injectBindError: errB, expectError: errB, expectErrorPod: podWithID("foo", ""), eventReason: "FailedScheduling", }, } for i, item := range table { var gotError error var gotPod *api.Pod var gotAssumedPod *api.Pod var gotBinding *api.Binding c := &Config{ Modeler: &FakeModeler{ AssumePodFunc: func(pod *api.Pod) { gotAssumedPod = pod }, }, NodeLister: algorithm.FakeNodeLister( api.NodeList{Items: []api.Node{{ObjectMeta: api.ObjectMeta{Name: "machine1"}}}}, ), Algorithm: item.algo, Binder: fakeBinder{func(b *api.Binding) error { gotBinding = b return item.injectBindError }}, Error: func(p *api.Pod, err error) { gotPod = p gotError = err }, NextPod: func() *api.Pod { return item.sendPod }, Recorder: eventBroadcaster.NewRecorder(api.EventSource{Component: "scheduler"}), } s := New(c) called := make(chan struct{}) events := eventBroadcaster.StartEventWatcher(func(e *api.Event) { if e, a := item.eventReason, e.Reason; e != a { t.Errorf("%v: expected %v, got %v", i, e, a) } close(called) }) s.scheduleOne() if e, a := item.expectAssumedPod, gotAssumedPod; !reflect.DeepEqual(e, a) { t.Errorf("%v: assumed pod: wanted %v, got %v", i, e, a) } if e, a := item.expectErrorPod, gotPod; !reflect.DeepEqual(e, a) { t.Errorf("%v: error pod: wanted %v, got %v", i, e, a) } if e, a := item.expectError, gotError; !reflect.DeepEqual(e, a) { t.Errorf("%v: error: wanted %v, got %v", i, e, a) } if e, a := item.expectBind, gotBinding; !reflect.DeepEqual(e, a) { t.Errorf("%v: error: %s", i, util.ObjectDiff(e, a)) } <-called events.Stop() } }
func TestSchedulerForgetAssumedPodAfterDelete(t *testing.T) { eventBroadcaster := record.NewBroadcaster() defer eventBroadcaster.StartLogging(t.Logf).Stop() // Setup modeler so we control the contents of all 3 stores: assumed, // scheduled and queued scheduledPodStore := cache.NewStore(cache.MetaNamespaceKeyFunc) scheduledPodLister := &cache.StoreToPodLister{Store: scheduledPodStore} queuedPodStore := cache.NewFIFO(cache.MetaNamespaceKeyFunc) queuedPodLister := &cache.StoreToPodLister{Store: queuedPodStore} modeler := NewSimpleModeler(queuedPodLister, scheduledPodLister) // Create a fake clock used to timestamp entries and calculate ttl. Nothing // will expire till we flip to something older than the ttl, at which point // all entries inserted with fakeTime will expire. ttl := 30 * time.Second fakeTime := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) fakeClock := &util.FakeClock{Time: fakeTime} ttlPolicy := &cache.TTLPolicy{Ttl: ttl, Clock: fakeClock} assumedPodsStore := cache.NewFakeExpirationStore( cache.MetaNamespaceKeyFunc, nil, ttlPolicy, fakeClock) modeler.assumedPods = &cache.StoreToPodLister{Store: assumedPodsStore} // Port is the easiest way to cause a fit predicate failure podPort := 8080 firstPod := podWithPort("foo", "", podPort) // Create the scheduler config algo := NewGenericScheduler( map[string]algorithm.FitPredicate{"PodFitsHostPorts": predicates.PodFitsHostPorts}, []algorithm.PriorityConfig{}, []algorithm.SchedulerExtender{}, modeler.PodLister(), rand.New(rand.NewSource(time.Now().UnixNano()))) var gotBinding *api.Binding c := &Config{ Modeler: modeler, NodeLister: algorithm.FakeNodeLister( api.NodeList{Items: []api.Node{{ObjectMeta: api.ObjectMeta{Name: "machine1"}}}}, ), Algorithm: algo, Binder: fakeBinder{func(b *api.Binding) error { scheduledPodStore.Add(podWithPort(b.Name, b.Target.Name, podPort)) gotBinding = b return nil }}, NextPod: func() *api.Pod { return queuedPodStore.Pop().(*api.Pod) }, Error: func(p *api.Pod, err error) { t.Errorf("Unexpected error when scheduling pod %+v: %v", p, err) }, Recorder: eventBroadcaster.NewRecorder(api.EventSource{Component: "scheduler"}), } // First scheduling pass should schedule the pod s := New(c) called := make(chan struct{}) events := eventBroadcaster.StartEventWatcher(func(e *api.Event) { if e, a := "Scheduled", e.Reason; e != a { t.Errorf("expected %v, got %v", e, a) } close(called) }) queuedPodStore.Add(firstPod) // queuedPodStore: [foo:8080] // scheduledPodStore: [] // assumedPods: [] s.scheduleOne() // queuedPodStore: [] // scheduledPodStore: [foo:8080] // assumedPods: [foo:8080] pod, exists, _ := scheduledPodStore.GetByKey("foo") if !exists { t.Errorf("Expected scheduled pod store to contain pod") } pod, exists, _ = queuedPodStore.GetByKey("foo") if exists { t.Errorf("Did not expect a queued pod, found %+v", pod) } pod, exists, _ = assumedPodsStore.GetByKey("foo") if !exists { t.Errorf("Assumed pod store should contain stale pod") } expectBind := &api.Binding{ ObjectMeta: api.ObjectMeta{Name: "foo"}, Target: api.ObjectReference{Kind: "Node", Name: "machine1"}, } if ex, ac := expectBind, gotBinding; !reflect.DeepEqual(ex, ac) { t.Errorf("Expected exact match on binding: %s", util.ObjectDiff(ex, ac)) } <-called events.Stop() scheduledPodStore.Delete(pod) _, exists, _ = assumedPodsStore.Get(pod) if !exists { t.Errorf("Expected pod %#v in assumed pod store", pod) } secondPod := podWithPort("bar", "", podPort) queuedPodStore.Add(secondPod) // queuedPodStore: [bar:8080] // scheduledPodStore: [] // assumedPods: [foo:8080] // Second scheduling pass will fail to schedule if the store hasn't expired // the deleted pod. This would normally happen with a timeout. //expirationPolicy.NeverExpire = util.NewStringSet() fakeClock.Time = fakeClock.Time.Add(ttl + 1) called = make(chan struct{}) events = eventBroadcaster.StartEventWatcher(func(e *api.Event) { if e, a := "Scheduled", e.Reason; e != a { t.Errorf("expected %v, got %v", e, a) } close(called) }) s.scheduleOne() expectBind = &api.Binding{ ObjectMeta: api.ObjectMeta{Name: "bar"}, Target: api.ObjectReference{Kind: "Node", Name: "machine1"}, } if ex, ac := expectBind, gotBinding; !reflect.DeepEqual(ex, ac) { t.Errorf("Expected exact match on binding: %s", util.ObjectDiff(ex, ac)) } <-called events.Stop() }
func TestZoneSpreadPriority(t *testing.T) { labels1 := map[string]string{ "foo": "bar", "baz": "blah", } labels2 := map[string]string{ "bar": "foo", "baz": "blah", } zone1 := map[string]string{ "zone": "zone1", } zone2 := map[string]string{ "zone": "zone2", } nozone := map[string]string{ "name": "value", } zone0Spec := api.PodSpec{ NodeName: "machine01", } zone1Spec := api.PodSpec{ NodeName: "machine11", } zone2Spec := api.PodSpec{ NodeName: "machine21", } labeledNodes := map[string]map[string]string{ "machine01": nozone, "machine02": nozone, "machine11": zone1, "machine12": zone1, "machine21": zone2, "machine22": zone2, } tests := []struct { pod *api.Pod pods []*api.Pod nodes map[string]map[string]string services []api.Service expectedList schedulerapi.HostPriorityList test string }{ { pod: new(api.Pod), nodes: labeledNodes, expectedList: []schedulerapi.HostPriority{{"machine11", 10}, {"machine12", 10}, {"machine21", 10}, {"machine22", 10}, {"machine01", 0}, {"machine02", 0}}, test: "nothing scheduled", }, { pod: &api.Pod{ObjectMeta: api.ObjectMeta{Labels: labels1}}, pods: []*api.Pod{{Spec: zone1Spec}}, nodes: labeledNodes, expectedList: []schedulerapi.HostPriority{{"machine11", 10}, {"machine12", 10}, {"machine21", 10}, {"machine22", 10}, {"machine01", 0}, {"machine02", 0}}, test: "no services", }, { pod: &api.Pod{ObjectMeta: api.ObjectMeta{Labels: labels1}}, pods: []*api.Pod{{Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels2}}}, nodes: labeledNodes, services: []api.Service{{Spec: api.ServiceSpec{Selector: map[string]string{"key": "value"}}}}, expectedList: []schedulerapi.HostPriority{{"machine11", 10}, {"machine12", 10}, {"machine21", 10}, {"machine22", 10}, {"machine01", 0}, {"machine02", 0}}, test: "different services", }, { pod: &api.Pod{ObjectMeta: api.ObjectMeta{Labels: labels1}}, pods: []*api.Pod{ {Spec: zone0Spec, ObjectMeta: api.ObjectMeta{Labels: labels2}}, {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels2}}, {Spec: zone2Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, }, nodes: labeledNodes, services: []api.Service{{Spec: api.ServiceSpec{Selector: labels1}}}, expectedList: []schedulerapi.HostPriority{{"machine11", 10}, {"machine12", 10}, {"machine21", 0}, {"machine22", 0}, {"machine01", 0}, {"machine02", 0}}, test: "three pods, one service pod", }, { pod: &api.Pod{ObjectMeta: api.ObjectMeta{Labels: labels1}}, pods: []*api.Pod{ {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels2}}, {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, {Spec: zone2Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, }, nodes: labeledNodes, services: []api.Service{{Spec: api.ServiceSpec{Selector: labels1}}}, expectedList: []schedulerapi.HostPriority{{"machine11", 5}, {"machine12", 5}, {"machine21", 5}, {"machine22", 5}, {"machine01", 0}, {"machine02", 0}}, test: "three pods, two service pods on different machines", }, { pod: &api.Pod{ObjectMeta: api.ObjectMeta{Labels: labels1, Namespace: api.NamespaceDefault}}, pods: []*api.Pod{ {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels1, Namespace: api.NamespaceDefault}}, {Spec: zone2Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, {Spec: zone2Spec, ObjectMeta: api.ObjectMeta{Labels: labels1, Namespace: "ns1"}}, }, nodes: labeledNodes, services: []api.Service{{Spec: api.ServiceSpec{Selector: labels1}, ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault}}}, expectedList: []schedulerapi.HostPriority{{"machine11", 0}, {"machine12", 0}, {"machine21", 10}, {"machine22", 10}, {"machine01", 0}, {"machine02", 0}}, test: "three service label match pods in different namespaces", }, { pod: &api.Pod{ObjectMeta: api.ObjectMeta{Labels: labels1}}, pods: []*api.Pod{ {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels2}}, {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, {Spec: zone2Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, {Spec: zone2Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, }, nodes: labeledNodes, services: []api.Service{{Spec: api.ServiceSpec{Selector: labels1}}}, expectedList: []schedulerapi.HostPriority{{"machine11", 6}, {"machine12", 6}, {"machine21", 3}, {"machine22", 3}, {"machine01", 0}, {"machine02", 0}}, test: "four pods, three service pods", }, { pod: &api.Pod{ObjectMeta: api.ObjectMeta{Labels: labels1}}, pods: []*api.Pod{ {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels2}}, {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, {Spec: zone2Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, }, nodes: labeledNodes, services: []api.Service{{Spec: api.ServiceSpec{Selector: map[string]string{"baz": "blah"}}}}, expectedList: []schedulerapi.HostPriority{{"machine11", 3}, {"machine12", 3}, {"machine21", 6}, {"machine22", 6}, {"machine01", 0}, {"machine02", 0}}, test: "service with partial pod label matches", }, { pod: &api.Pod{ObjectMeta: api.ObjectMeta{Labels: labels1}}, pods: []*api.Pod{ {Spec: zone0Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, {Spec: zone2Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, {Spec: zone2Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, }, nodes: labeledNodes, services: []api.Service{{Spec: api.ServiceSpec{Selector: labels1}}}, expectedList: []schedulerapi.HostPriority{{"machine11", 7}, {"machine12", 7}, {"machine21", 5}, {"machine22", 5}, {"machine01", 0}, {"machine02", 0}}, test: "service pod on non-zoned node", }, } for _, test := range tests { nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap(test.pods) zoneSpread := ServiceAntiAffinity{podLister: algorithm.FakePodLister(test.pods), serviceLister: algorithm.FakeServiceLister(test.services), label: "zone"} list, err := zoneSpread.CalculateAntiAffinityPriority(test.pod, nodeNameToInfo, algorithm.FakeNodeLister(makeLabeledNodeList(test.nodes))) if err != nil { t.Errorf("unexpected error: %v", err) } // sort the two lists to avoid failures on account of different ordering sort.Sort(test.expectedList) sort.Sort(list) if !reflect.DeepEqual(test.expectedList, list) { t.Errorf("%s: expected %#v, got %#v", test.test, test.expectedList, list) } } }
func TestSelectorSpreadPriority(t *testing.T) { labels1 := map[string]string{ "foo": "bar", "baz": "blah", } labels2 := map[string]string{ "bar": "foo", "baz": "blah", } zone1Spec := api.PodSpec{ NodeName: "machine1", } zone2Spec := api.PodSpec{ NodeName: "machine2", } tests := []struct { pod *api.Pod pods []*api.Pod nodes []string rcs []api.ReplicationController rss []extensions.ReplicaSet services []api.Service expectedList schedulerapi.HostPriorityList test string }{ { pod: new(api.Pod), nodes: []string{"machine1", "machine2"}, expectedList: []schedulerapi.HostPriority{{"machine1", 10}, {"machine2", 10}}, test: "nothing scheduled", }, { pod: &api.Pod{ObjectMeta: api.ObjectMeta{Labels: labels1}}, pods: []*api.Pod{{Spec: zone1Spec}}, nodes: []string{"machine1", "machine2"}, expectedList: []schedulerapi.HostPriority{{"machine1", 10}, {"machine2", 10}}, test: "no services", }, { pod: &api.Pod{ObjectMeta: api.ObjectMeta{Labels: labels1}}, pods: []*api.Pod{{Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels2}}}, nodes: []string{"machine1", "machine2"}, services: []api.Service{{Spec: api.ServiceSpec{Selector: map[string]string{"key": "value"}}}}, expectedList: []schedulerapi.HostPriority{{"machine1", 10}, {"machine2", 10}}, test: "different services", }, { pod: &api.Pod{ObjectMeta: api.ObjectMeta{Labels: labels1}}, pods: []*api.Pod{ {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels2}}, {Spec: zone2Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, }, nodes: []string{"machine1", "machine2"}, services: []api.Service{{Spec: api.ServiceSpec{Selector: labels1}}}, expectedList: []schedulerapi.HostPriority{{"machine1", 10}, {"machine2", 0}}, test: "two pods, one service pod", }, { pod: &api.Pod{ObjectMeta: api.ObjectMeta{Labels: labels1}}, pods: []*api.Pod{ {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels2}}, {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels1, Namespace: api.NamespaceDefault}}, {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels1, Namespace: "ns1"}}, {Spec: zone2Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, {Spec: zone2Spec, ObjectMeta: api.ObjectMeta{Labels: labels2}}, }, nodes: []string{"machine1", "machine2"}, services: []api.Service{{Spec: api.ServiceSpec{Selector: labels1}}}, expectedList: []schedulerapi.HostPriority{{"machine1", 10}, {"machine2", 0}}, test: "five pods, one service pod in no namespace", }, { pod: &api.Pod{ObjectMeta: api.ObjectMeta{Labels: labels1, Namespace: api.NamespaceDefault}}, pods: []*api.Pod{ {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels1, Namespace: "ns1"}}, {Spec: zone2Spec, ObjectMeta: api.ObjectMeta{Labels: labels1, Namespace: api.NamespaceDefault}}, {Spec: zone2Spec, ObjectMeta: api.ObjectMeta{Labels: labels2}}, }, nodes: []string{"machine1", "machine2"}, services: []api.Service{{Spec: api.ServiceSpec{Selector: labels1}, ObjectMeta: api.ObjectMeta{Namespace: api.NamespaceDefault}}}, expectedList: []schedulerapi.HostPriority{{"machine1", 10}, {"machine2", 0}}, test: "four pods, one service pod in default namespace", }, { pod: &api.Pod{ObjectMeta: api.ObjectMeta{Labels: labels1, Namespace: "ns1"}}, pods: []*api.Pod{ {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels1, Namespace: api.NamespaceDefault}}, {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels1, Namespace: "ns2"}}, {Spec: zone2Spec, ObjectMeta: api.ObjectMeta{Labels: labels1, Namespace: "ns1"}}, {Spec: zone2Spec, ObjectMeta: api.ObjectMeta{Labels: labels2}}, }, nodes: []string{"machine1", "machine2"}, services: []api.Service{{Spec: api.ServiceSpec{Selector: labels1}, ObjectMeta: api.ObjectMeta{Namespace: "ns1"}}}, expectedList: []schedulerapi.HostPriority{{"machine1", 10}, {"machine2", 0}}, test: "five pods, one service pod in specific namespace", }, { pod: &api.Pod{ObjectMeta: api.ObjectMeta{Labels: labels1}}, pods: []*api.Pod{ {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels2}}, {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, {Spec: zone2Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, }, nodes: []string{"machine1", "machine2"}, services: []api.Service{{Spec: api.ServiceSpec{Selector: labels1}}}, expectedList: []schedulerapi.HostPriority{{"machine1", 0}, {"machine2", 0}}, test: "three pods, two service pods on different machines", }, { pod: &api.Pod{ObjectMeta: api.ObjectMeta{Labels: labels1}}, pods: []*api.Pod{ {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels2}}, {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, {Spec: zone2Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, {Spec: zone2Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, }, nodes: []string{"machine1", "machine2"}, services: []api.Service{{Spec: api.ServiceSpec{Selector: labels1}}}, expectedList: []schedulerapi.HostPriority{{"machine1", 5}, {"machine2", 0}}, test: "four pods, three service pods", }, { pod: &api.Pod{ObjectMeta: api.ObjectMeta{Labels: labels1}}, pods: []*api.Pod{ {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels2}}, {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, {Spec: zone2Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, }, nodes: []string{"machine1", "machine2"}, services: []api.Service{{Spec: api.ServiceSpec{Selector: map[string]string{"baz": "blah"}}}}, expectedList: []schedulerapi.HostPriority{{"machine1", 0}, {"machine2", 5}}, test: "service with partial pod label matches", }, { pod: &api.Pod{ObjectMeta: api.ObjectMeta{Labels: labels1}}, pods: []*api.Pod{ {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels2}}, {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, {Spec: zone2Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, }, nodes: []string{"machine1", "machine2"}, services: []api.Service{{Spec: api.ServiceSpec{Selector: map[string]string{"baz": "blah"}}}}, rcs: []api.ReplicationController{{Spec: api.ReplicationControllerSpec{Selector: map[string]string{"foo": "bar"}}}}, // "baz=blah" matches both labels1 and labels2, and "foo=bar" matches only labels 1. This means that we assume that we want to // do spreading between all pods. The result should be exactly as above. expectedList: []schedulerapi.HostPriority{{"machine1", 0}, {"machine2", 5}}, test: "service with partial pod label matches with service and replication controller", }, { pod: &api.Pod{ObjectMeta: api.ObjectMeta{Labels: labels1}}, pods: []*api.Pod{ {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels2}}, {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, {Spec: zone2Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, }, nodes: []string{"machine1", "machine2"}, services: []api.Service{{Spec: api.ServiceSpec{Selector: map[string]string{"baz": "blah"}}}}, rss: []extensions.ReplicaSet{{Spec: extensions.ReplicaSetSpec{Selector: &unversioned.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}}}}, // We use ReplicaSet, instead of ReplicationController. The result should be exactly as above. expectedList: []schedulerapi.HostPriority{{"machine1", 0}, {"machine2", 5}}, test: "service with partial pod label matches with service and replication controller", }, { pod: &api.Pod{ObjectMeta: api.ObjectMeta{Labels: map[string]string{"foo": "bar", "bar": "foo"}}}, pods: []*api.Pod{ {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels2}}, {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, {Spec: zone2Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, }, nodes: []string{"machine1", "machine2"}, services: []api.Service{{Spec: api.ServiceSpec{Selector: map[string]string{"bar": "foo"}}}}, rcs: []api.ReplicationController{{Spec: api.ReplicationControllerSpec{Selector: map[string]string{"foo": "bar"}}}}, // Taken together Service and Replication Controller should match all Pods, hence result should be equal to one above. expectedList: []schedulerapi.HostPriority{{"machine1", 0}, {"machine2", 5}}, test: "disjoined service and replication controller should be treated equally", }, { pod: &api.Pod{ObjectMeta: api.ObjectMeta{Labels: map[string]string{"foo": "bar", "bar": "foo"}}}, pods: []*api.Pod{ {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels2}}, {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, {Spec: zone2Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, }, nodes: []string{"machine1", "machine2"}, services: []api.Service{{Spec: api.ServiceSpec{Selector: map[string]string{"bar": "foo"}}}}, rss: []extensions.ReplicaSet{{Spec: extensions.ReplicaSetSpec{Selector: &unversioned.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}}}}, // We use ReplicaSet, instead of ReplicationController. The result should be exactly as above. expectedList: []schedulerapi.HostPriority{{"machine1", 0}, {"machine2", 5}}, test: "disjoined service and replication controller should be treated equally", }, { pod: &api.Pod{ObjectMeta: api.ObjectMeta{Labels: labels1}}, pods: []*api.Pod{ {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels2}}, {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, {Spec: zone2Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, }, nodes: []string{"machine1", "machine2"}, rcs: []api.ReplicationController{{Spec: api.ReplicationControllerSpec{Selector: map[string]string{"foo": "bar"}}}}, // Both Nodes have one pod from the given RC, hence both get 0 score. expectedList: []schedulerapi.HostPriority{{"machine1", 0}, {"machine2", 0}}, test: "Replication controller with partial pod label matches", }, { pod: &api.Pod{ObjectMeta: api.ObjectMeta{Labels: labels1}}, pods: []*api.Pod{ {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels2}}, {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, {Spec: zone2Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, }, nodes: []string{"machine1", "machine2"}, rss: []extensions.ReplicaSet{{Spec: extensions.ReplicaSetSpec{Selector: &unversioned.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}}}}}, // We use ReplicaSet, instead of ReplicationController. The result should be exactly as above. expectedList: []schedulerapi.HostPriority{{"machine1", 0}, {"machine2", 0}}, test: "Replication controller with partial pod label matches", }, { pod: &api.Pod{ObjectMeta: api.ObjectMeta{Labels: labels1}}, pods: []*api.Pod{ {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels2}}, {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, {Spec: zone2Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, }, nodes: []string{"machine1", "machine2"}, rcs: []api.ReplicationController{{Spec: api.ReplicationControllerSpec{Selector: map[string]string{"baz": "blah"}}}}, expectedList: []schedulerapi.HostPriority{{"machine1", 0}, {"machine2", 5}}, test: "Replication controller with partial pod label matches", }, { pod: &api.Pod{ObjectMeta: api.ObjectMeta{Labels: labels1}}, pods: []*api.Pod{ {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels2}}, {Spec: zone1Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, {Spec: zone2Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, }, nodes: []string{"machine1", "machine2"}, rss: []extensions.ReplicaSet{{Spec: extensions.ReplicaSetSpec{Selector: &unversioned.LabelSelector{MatchLabels: map[string]string{"baz": "blah"}}}}}, // We use ReplicaSet, instead of ReplicationController. The result should be exactly as above. expectedList: []schedulerapi.HostPriority{{"machine1", 0}, {"machine2", 5}}, test: "Replication controller with partial pod label matches", }, } for _, test := range tests { nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap(test.pods) selectorSpread := SelectorSpread{podLister: algorithm.FakePodLister(test.pods), serviceLister: algorithm.FakeServiceLister(test.services), controllerLister: algorithm.FakeControllerLister(test.rcs), replicaSetLister: algorithm.FakeReplicaSetLister(test.rss)} list, err := selectorSpread.CalculateSpreadPriority(test.pod, nodeNameToInfo, algorithm.FakeNodeLister(makeNodeList(test.nodes))) if err != nil { t.Errorf("unexpected error: %v", err) } if !reflect.DeepEqual(test.expectedList, list) { t.Errorf("%s: expected %#v, got %#v", test.test, test.expectedList, list) } } }
func TestZoneSelectorSpreadPriority(t *testing.T) { labels1 := map[string]string{ "label1": "l1", "baz": "blah", } labels2 := map[string]string{ "label2": "l2", "baz": "blah", } const nodeMachine1Zone1 = "machine1.zone1" const nodeMachine1Zone2 = "machine1.zone2" const nodeMachine2Zone2 = "machine2.zone2" const nodeMachine1Zone3 = "machine1.zone3" const nodeMachine2Zone3 = "machine2.zone3" const nodeMachine3Zone3 = "machine3.zone3" buildNodeLabels := func(failureDomain string) map[string]string { labels := map[string]string{ wellknownlabels.LabelZoneFailureDomain: failureDomain, } return labels } labeledNodes := map[string]map[string]string{ nodeMachine1Zone1: buildNodeLabels("zone1"), nodeMachine1Zone2: buildNodeLabels("zone2"), nodeMachine2Zone2: buildNodeLabels("zone2"), nodeMachine1Zone3: buildNodeLabels("zone3"), nodeMachine2Zone3: buildNodeLabels("zone3"), nodeMachine3Zone3: buildNodeLabels("zone3"), } buildPod := func(nodeName string, labels map[string]string) *api.Pod { pod := &api.Pod{Spec: api.PodSpec{NodeName: nodeName}, ObjectMeta: api.ObjectMeta{Labels: labels}} return pod } tests := []struct { pod *api.Pod pods []*api.Pod nodes []string rcs []api.ReplicationController rss []extensions.ReplicaSet services []api.Service expectedList schedulerapi.HostPriorityList test string }{ { pod: new(api.Pod), expectedList: []schedulerapi.HostPriority{ {nodeMachine1Zone1, 10}, {nodeMachine1Zone2, 10}, {nodeMachine2Zone2, 10}, {nodeMachine1Zone3, 10}, {nodeMachine2Zone3, 10}, {nodeMachine3Zone3, 10}, }, test: "nothing scheduled", }, { pod: buildPod("", labels1), pods: []*api.Pod{buildPod(nodeMachine1Zone1, nil)}, expectedList: []schedulerapi.HostPriority{ {nodeMachine1Zone1, 10}, {nodeMachine1Zone2, 10}, {nodeMachine2Zone2, 10}, {nodeMachine1Zone3, 10}, {nodeMachine2Zone3, 10}, {nodeMachine3Zone3, 10}, }, test: "no services", }, { pod: buildPod("", labels1), pods: []*api.Pod{buildPod(nodeMachine1Zone1, labels2)}, services: []api.Service{{Spec: api.ServiceSpec{Selector: map[string]string{"key": "value"}}}}, expectedList: []schedulerapi.HostPriority{ {nodeMachine1Zone1, 10}, {nodeMachine1Zone2, 10}, {nodeMachine2Zone2, 10}, {nodeMachine1Zone3, 10}, {nodeMachine2Zone3, 10}, {nodeMachine3Zone3, 10}, }, test: "different services", }, { pod: buildPod("", labels1), pods: []*api.Pod{ buildPod(nodeMachine1Zone1, labels2), buildPod(nodeMachine1Zone2, labels1), }, services: []api.Service{{Spec: api.ServiceSpec{Selector: labels1}}}, expectedList: []schedulerapi.HostPriority{ {nodeMachine1Zone1, 10}, {nodeMachine1Zone2, 0}, // Already have pod on machine {nodeMachine2Zone2, 3}, // Already have pod in zone {nodeMachine1Zone3, 10}, {nodeMachine2Zone3, 10}, {nodeMachine3Zone3, 10}, }, test: "two pods, 1 matching (in z2)", }, { pod: buildPod("", labels1), pods: []*api.Pod{ buildPod(nodeMachine1Zone1, labels2), buildPod(nodeMachine1Zone2, labels1), buildPod(nodeMachine2Zone2, labels1), buildPod(nodeMachine1Zone3, labels2), buildPod(nodeMachine2Zone3, labels1), }, services: []api.Service{{Spec: api.ServiceSpec{Selector: labels1}}}, expectedList: []schedulerapi.HostPriority{ {nodeMachine1Zone1, 10}, {nodeMachine1Zone2, 0}, // Pod on node {nodeMachine2Zone2, 0}, // Pod on node {nodeMachine1Zone3, 6}, // Pod in zone {nodeMachine2Zone3, 3}, // Pod on node {nodeMachine3Zone3, 6}, // Pod in zone }, test: "five pods, 3 matching (z2=2, z3=1)", }, { pod: buildPod("", labels1), pods: []*api.Pod{ buildPod(nodeMachine1Zone1, labels1), buildPod(nodeMachine1Zone2, labels1), buildPod(nodeMachine2Zone2, labels2), buildPod(nodeMachine1Zone3, labels1), }, services: []api.Service{{Spec: api.ServiceSpec{Selector: labels1}}}, expectedList: []schedulerapi.HostPriority{ {nodeMachine1Zone1, 0}, // Pod on node {nodeMachine1Zone2, 0}, // Pod on node {nodeMachine2Zone2, 3}, // Pod in zone {nodeMachine1Zone3, 0}, // Pod on node {nodeMachine2Zone3, 3}, // Pod in zone {nodeMachine3Zone3, 3}, // Pod in zone }, test: "four pods, 3 matching (z1=1, z2=1, z3=1)", }, { pod: buildPod("", labels1), pods: []*api.Pod{ buildPod(nodeMachine1Zone1, labels1), buildPod(nodeMachine1Zone2, labels1), buildPod(nodeMachine1Zone3, labels1), buildPod(nodeMachine2Zone2, labels2), }, services: []api.Service{{Spec: api.ServiceSpec{Selector: labels1}}}, expectedList: []schedulerapi.HostPriority{ {nodeMachine1Zone1, 0}, // Pod on node {nodeMachine1Zone2, 0}, // Pod on node {nodeMachine2Zone2, 3}, // Pod in zone {nodeMachine1Zone3, 0}, // Pod on node {nodeMachine2Zone3, 3}, // Pod in zone {nodeMachine3Zone3, 3}, // Pod in zone }, test: "four pods, 3 matching (z1=1, z2=1, z3=1)", }, { pod: buildPod("", labels1), pods: []*api.Pod{ buildPod(nodeMachine1Zone3, labels1), buildPod(nodeMachine1Zone2, labels1), buildPod(nodeMachine1Zone3, labels1), }, rcs: []api.ReplicationController{{Spec: api.ReplicationControllerSpec{Selector: labels1}}}, expectedList: []schedulerapi.HostPriority{ // Note that because we put two pods on the same node (nodeMachine1Zone3), // the values here are questionable for zone2, in particular for nodeMachine1Zone2. // However they kind of make sense; zone1 is still most-highly favored. // zone3 is in general least favored, and m1.z3 particularly low priority. // We would probably prefer to see a bigger gap between putting a second // pod on m1.z2 and putting a pod on m2.z2, but the ordering is correct. // This is also consistent with what we have already. {nodeMachine1Zone1, 10}, // No pods in zone {nodeMachine1Zone2, 5}, // Pod on node {nodeMachine2Zone2, 6}, // Pod in zone {nodeMachine1Zone3, 0}, // Two pods on node {nodeMachine2Zone3, 3}, // Pod in zone {nodeMachine3Zone3, 3}, // Pod in zone }, test: "Replication controller spreading (z1=0, z2=1, z3=2)", }, } for _, test := range tests { nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap(test.pods) selectorSpread := SelectorSpread{podLister: algorithm.FakePodLister(test.pods), serviceLister: algorithm.FakeServiceLister(test.services), controllerLister: algorithm.FakeControllerLister(test.rcs), replicaSetLister: algorithm.FakeReplicaSetLister(test.rss)} list, err := selectorSpread.CalculateSpreadPriority(test.pod, nodeNameToInfo, algorithm.FakeNodeLister(makeLabeledNodeList(labeledNodes))) if err != nil { t.Errorf("unexpected error: %v", err) } // sort the two lists to avoid failures on account of different ordering sort.Sort(test.expectedList) sort.Sort(list) if !reflect.DeepEqual(test.expectedList, list) { t.Errorf("%s: expected %#v, got %#v", test.test, test.expectedList, list) } } }
func TestGenericSchedulerWithExtenders(t *testing.T) { tests := []struct { name string predicates map[string]algorithm.FitPredicate prioritizers []algorithm.PriorityConfig extenders []FakeExtender extenderPredicates []fitPredicate extenderPrioritizers []priorityConfig nodes []string expectedHost string expectsErr bool }{ { predicates: map[string]algorithm.FitPredicate{"true": truePredicate}, prioritizers: []algorithm.PriorityConfig{{Map: EqualPriorityMap, Weight: 1}}, extenders: []FakeExtender{ { predicates: []fitPredicate{truePredicateExtender}, }, { predicates: []fitPredicate{errorPredicateExtender}, }, }, nodes: []string{"machine1", "machine2"}, expectsErr: true, name: "test 1", }, { predicates: map[string]algorithm.FitPredicate{"true": truePredicate}, prioritizers: []algorithm.PriorityConfig{{Map: EqualPriorityMap, Weight: 1}}, extenders: []FakeExtender{ { predicates: []fitPredicate{truePredicateExtender}, }, { predicates: []fitPredicate{falsePredicateExtender}, }, }, nodes: []string{"machine1", "machine2"}, expectsErr: true, name: "test 2", }, { predicates: map[string]algorithm.FitPredicate{"true": truePredicate}, prioritizers: []algorithm.PriorityConfig{{Map: EqualPriorityMap, Weight: 1}}, extenders: []FakeExtender{ { predicates: []fitPredicate{truePredicateExtender}, }, { predicates: []fitPredicate{machine1PredicateExtender}, }, }, nodes: []string{"machine1", "machine2"}, expectedHost: "machine1", name: "test 3", }, { predicates: map[string]algorithm.FitPredicate{"true": truePredicate}, prioritizers: []algorithm.PriorityConfig{{Map: EqualPriorityMap, Weight: 1}}, extenders: []FakeExtender{ { predicates: []fitPredicate{machine2PredicateExtender}, }, { predicates: []fitPredicate{machine1PredicateExtender}, }, }, nodes: []string{"machine1", "machine2"}, expectsErr: true, name: "test 4", }, { predicates: map[string]algorithm.FitPredicate{"true": truePredicate}, prioritizers: []algorithm.PriorityConfig{{Map: EqualPriorityMap, Weight: 1}}, extenders: []FakeExtender{ { predicates: []fitPredicate{truePredicateExtender}, prioritizers: []priorityConfig{{errorPrioritizerExtender, 10}}, weight: 1, }, }, nodes: []string{"machine1"}, expectedHost: "machine1", name: "test 5", }, { predicates: map[string]algorithm.FitPredicate{"true": truePredicate}, prioritizers: []algorithm.PriorityConfig{{Map: EqualPriorityMap, Weight: 1}}, extenders: []FakeExtender{ { predicates: []fitPredicate{truePredicateExtender}, prioritizers: []priorityConfig{{machine1PrioritizerExtender, 10}}, weight: 1, }, { predicates: []fitPredicate{truePredicateExtender}, prioritizers: []priorityConfig{{machine2PrioritizerExtender, 10}}, weight: 5, }, }, nodes: []string{"machine1", "machine2"}, expectedHost: "machine2", name: "test 6", }, { predicates: map[string]algorithm.FitPredicate{"true": truePredicate}, prioritizers: []algorithm.PriorityConfig{{Function: machine2Prioritizer, Weight: 20}}, extenders: []FakeExtender{ { predicates: []fitPredicate{truePredicateExtender}, prioritizers: []priorityConfig{{machine1PrioritizerExtender, 10}}, weight: 1, }, }, nodes: []string{"machine1", "machine2"}, expectedHost: "machine2", // machine2 has higher score name: "test 7", }, } for _, test := range tests { extenders := []algorithm.SchedulerExtender{} for ii := range test.extenders { extenders = append(extenders, &test.extenders[ii]) } cache := schedulercache.New(time.Duration(0), wait.NeverStop) for _, name := range test.nodes { cache.AddNode(&v1.Node{ObjectMeta: metav1.ObjectMeta{Name: name}}) } scheduler := NewGenericScheduler( cache, test.predicates, algorithm.EmptyMetadataProducer, test.prioritizers, algorithm.EmptyMetadataProducer, extenders) podIgnored := &v1.Pod{} machine, err := scheduler.Schedule(podIgnored, algorithm.FakeNodeLister(makeNodeList(test.nodes))) if test.expectsErr { if err == nil { t.Errorf("Unexpected non-error for %s, machine %s", test.name, machine) } } else { if err != nil { t.Errorf("Unexpected error: %v", err) continue } if test.expectedHost != machine { t.Errorf("Failed : %s, Expected: %s, Saw: %s", test.name, test.expectedHost, machine) } } } }
func TestGenericScheduler(t *testing.T) { tests := []struct { name string predicates map[string]algorithm.FitPredicate prioritizers []algorithm.PriorityConfig nodes []string pod *api.Pod pods []*api.Pod expectedHosts sets.String expectsErr bool wErr error }{ { predicates: map[string]algorithm.FitPredicate{"false": falsePredicate}, prioritizers: []algorithm.PriorityConfig{{Map: EqualPriorityMap, Weight: 1}}, nodes: []string{"machine1", "machine2"}, expectsErr: true, pod: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "2"}}, name: "test 1", wErr: &FitError{ Pod: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "2"}}, FailedPredicates: FailedPredicateMap{ "machine1": []algorithm.PredicateFailureReason{algorithmpredicates.ErrFakePredicate}, "machine2": []algorithm.PredicateFailureReason{algorithmpredicates.ErrFakePredicate}, }}, }, { predicates: map[string]algorithm.FitPredicate{"true": truePredicate}, prioritizers: []algorithm.PriorityConfig{{Map: EqualPriorityMap, Weight: 1}}, nodes: []string{"machine1", "machine2"}, expectedHosts: sets.NewString("machine1", "machine2"), name: "test 2", wErr: nil, }, { // Fits on a machine where the pod ID matches the machine name predicates: map[string]algorithm.FitPredicate{"matches": matchesPredicate}, prioritizers: []algorithm.PriorityConfig{{Map: EqualPriorityMap, Weight: 1}}, nodes: []string{"machine1", "machine2"}, pod: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "machine2"}}, expectedHosts: sets.NewString("machine2"), name: "test 3", wErr: nil, }, { predicates: map[string]algorithm.FitPredicate{"true": truePredicate}, prioritizers: []algorithm.PriorityConfig{{Function: numericPriority, Weight: 1}}, nodes: []string{"3", "2", "1"}, expectedHosts: sets.NewString("3"), name: "test 4", wErr: nil, }, { predicates: map[string]algorithm.FitPredicate{"matches": matchesPredicate}, prioritizers: []algorithm.PriorityConfig{{Function: numericPriority, Weight: 1}}, nodes: []string{"3", "2", "1"}, pod: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "2"}}, expectedHosts: sets.NewString("2"), name: "test 5", wErr: nil, }, { predicates: map[string]algorithm.FitPredicate{"true": truePredicate}, prioritizers: []algorithm.PriorityConfig{{Function: numericPriority, Weight: 1}, {Function: reverseNumericPriority, Weight: 2}}, nodes: []string{"3", "2", "1"}, pod: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "2"}}, expectedHosts: sets.NewString("1"), name: "test 6", wErr: nil, }, { predicates: map[string]algorithm.FitPredicate{"true": truePredicate, "false": falsePredicate}, prioritizers: []algorithm.PriorityConfig{{Function: numericPriority, Weight: 1}}, nodes: []string{"3", "2", "1"}, pod: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "2"}}, expectsErr: true, name: "test 7", wErr: &FitError{ Pod: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "2"}}, FailedPredicates: FailedPredicateMap{ "3": []algorithm.PredicateFailureReason{algorithmpredicates.ErrFakePredicate}, "2": []algorithm.PredicateFailureReason{algorithmpredicates.ErrFakePredicate}, "1": []algorithm.PredicateFailureReason{algorithmpredicates.ErrFakePredicate}, }, }, }, { predicates: map[string]algorithm.FitPredicate{ "nopods": hasNoPodsPredicate, "matches": matchesPredicate, }, pods: []*api.Pod{ { ObjectMeta: api.ObjectMeta{Name: "2"}, Spec: api.PodSpec{ NodeName: "2", }, Status: api.PodStatus{ Phase: api.PodRunning, }, }, }, pod: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "2"}}, prioritizers: []algorithm.PriorityConfig{{Function: numericPriority, Weight: 1}}, nodes: []string{"1", "2"}, expectsErr: true, name: "test 8", wErr: &FitError{ Pod: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "2"}}, FailedPredicates: FailedPredicateMap{ "1": []algorithm.PredicateFailureReason{algorithmpredicates.ErrFakePredicate}, "2": []algorithm.PredicateFailureReason{algorithmpredicates.ErrFakePredicate}, }, }, }, } for _, test := range tests { cache := schedulercache.New(time.Duration(0), wait.NeverStop) for _, pod := range test.pods { cache.AddPod(pod) } for _, name := range test.nodes { cache.AddNode(&api.Node{ObjectMeta: api.ObjectMeta{Name: name}}) } scheduler := NewGenericScheduler( cache, test.predicates, algorithm.EmptyMetadataProducer, test.prioritizers, algorithm.EmptyMetadataProducer, []algorithm.SchedulerExtender{}) machine, err := scheduler.Schedule(test.pod, algorithm.FakeNodeLister(makeNodeList(test.nodes))) if !reflect.DeepEqual(err, test.wErr) { t.Errorf("Failed : %s, Unexpected error: %v, expected: %v", test.name, err, test.wErr) } if test.expectedHosts != nil && !test.expectedHosts.Has(machine) { t.Errorf("Failed : %s, Expected: %s, got: %s", test.name, test.expectedHosts, machine) } } }
func TestBalancedResourceAllocation(t *testing.T) { labels1 := map[string]string{ "foo": "bar", "baz": "blah", } labels2 := map[string]string{ "bar": "foo", "baz": "blah", } machine1Spec := api.PodSpec{ NodeName: "machine1", } machine2Spec := api.PodSpec{ NodeName: "machine2", } noResources := api.PodSpec{ Containers: []api.Container{}, } cpuOnly := api.PodSpec{ NodeName: "machine1", Containers: []api.Container{ { Resources: api.ResourceRequirements{ Requests: api.ResourceList{ "cpu": resource.MustParse("1000m"), "memory": resource.MustParse("0"), }, }, }, { Resources: api.ResourceRequirements{ Requests: api.ResourceList{ "cpu": resource.MustParse("2000m"), "memory": resource.MustParse("0"), }, }, }, }, } cpuOnly2 := cpuOnly cpuOnly2.NodeName = "machine2" cpuAndMemory := api.PodSpec{ NodeName: "machine2", Containers: []api.Container{ { Resources: api.ResourceRequirements{ Requests: api.ResourceList{ "cpu": resource.MustParse("1000m"), "memory": resource.MustParse("2000"), }, }, }, { Resources: api.ResourceRequirements{ Requests: api.ResourceList{ "cpu": resource.MustParse("2000m"), "memory": resource.MustParse("3000"), }, }, }, }, } tests := []struct { pod *api.Pod pods []*api.Pod nodes []api.Node expectedList schedulerapi.HostPriorityList test string }{ { /* Node1 scores (remaining resources) on 0-10 scale CPU Fraction: 0 / 4000 = 0% Memory Fraction: 0 / 10000 = 0% Node1 Score: 10 - (0-0)*10 = 10 Node2 scores (remaining resources) on 0-10 scale CPU Fraction: 0 / 4000 = 0 % Memory Fraction: 0 / 10000 = 0% Node2 Score: 10 - (0-0)*10 = 10 */ pod: &api.Pod{Spec: noResources}, nodes: []api.Node{makeNode("machine1", 4000, 10000), makeNode("machine2", 4000, 10000)}, expectedList: []schedulerapi.HostPriority{{"machine1", 10}, {"machine2", 10}}, test: "nothing scheduled, nothing requested", }, { /* Node1 scores on 0-10 scale CPU Fraction: 3000 / 4000= 75% Memory Fraction: 5000 / 10000 = 50% Node1 Score: 10 - (0.75-0.5)*10 = 7 Node2 scores on 0-10 scale CPU Fraction: 3000 / 6000= 50% Memory Fraction: 5000/10000 = 50% Node2 Score: 10 - (0.5-0.5)*10 = 10 */ pod: &api.Pod{Spec: cpuAndMemory}, nodes: []api.Node{makeNode("machine1", 4000, 10000), makeNode("machine2", 6000, 10000)}, expectedList: []schedulerapi.HostPriority{{"machine1", 7}, {"machine2", 10}}, test: "nothing scheduled, resources requested, differently sized machines", }, { /* Node1 scores on 0-10 scale CPU Fraction: 0 / 4000= 0% Memory Fraction: 0 / 10000 = 0% Node1 Score: 10 - (0-0)*10 = 10 Node2 scores on 0-10 scale CPU Fraction: 0 / 4000= 0% Memory Fraction: 0 / 10000 = 0% Node2 Score: 10 - (0-0)*10 = 10 */ pod: &api.Pod{Spec: noResources}, nodes: []api.Node{makeNode("machine1", 4000, 10000), makeNode("machine2", 4000, 10000)}, expectedList: []schedulerapi.HostPriority{{"machine1", 10}, {"machine2", 10}}, test: "no resources requested, pods scheduled", pods: []*api.Pod{ {Spec: machine1Spec, ObjectMeta: api.ObjectMeta{Labels: labels2}}, {Spec: machine1Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, {Spec: machine2Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, {Spec: machine2Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, }, }, { /* Node1 scores on 0-10 scale CPU Fraction: 6000 / 10000 = 60% Memory Fraction: 0 / 20000 = 0% Node1 Score: 10 - (0.6-0)*10 = 4 Node2 scores on 0-10 scale CPU Fraction: 6000 / 10000 = 60% Memory Fraction: 5000 / 20000 = 25% Node2 Score: 10 - (0.6-0.25)*10 = 6 */ pod: &api.Pod{Spec: noResources}, nodes: []api.Node{makeNode("machine1", 10000, 20000), makeNode("machine2", 10000, 20000)}, expectedList: []schedulerapi.HostPriority{{"machine1", 4}, {"machine2", 6}}, test: "no resources requested, pods scheduled with resources", pods: []*api.Pod{ {Spec: cpuOnly, ObjectMeta: api.ObjectMeta{Labels: labels2}}, {Spec: cpuOnly, ObjectMeta: api.ObjectMeta{Labels: labels1}}, {Spec: cpuOnly2, ObjectMeta: api.ObjectMeta{Labels: labels1}}, {Spec: cpuAndMemory, ObjectMeta: api.ObjectMeta{Labels: labels1}}, }, }, { /* Node1 scores on 0-10 scale CPU Fraction: 6000 / 10000 = 60% Memory Fraction: 5000 / 20000 = 25% Node1 Score: 10 - (0.6-0.25)*10 = 6 Node2 scores on 0-10 scale CPU Fraction: 6000 / 10000 = 60% Memory Fraction: 10000 / 20000 = 50% Node2 Score: 10 - (0.6-0.5)*10 = 9 */ pod: &api.Pod{Spec: cpuAndMemory}, nodes: []api.Node{makeNode("machine1", 10000, 20000), makeNode("machine2", 10000, 20000)}, expectedList: []schedulerapi.HostPriority{{"machine1", 6}, {"machine2", 9}}, test: "resources requested, pods scheduled with resources", pods: []*api.Pod{ {Spec: cpuOnly}, {Spec: cpuAndMemory}, }, }, { /* Node1 scores on 0-10 scale CPU Fraction: 6000 / 10000 = 60% Memory Fraction: 5000 / 20000 = 25% Node1 Score: 10 - (0.6-0.25)*10 = 6 Node2 scores on 0-10 scale CPU Fraction: 6000 / 10000 = 60% Memory Fraction: 10000 / 50000 = 20% Node2 Score: 10 - (0.6-0.2)*10 = 6 */ pod: &api.Pod{Spec: cpuAndMemory}, nodes: []api.Node{makeNode("machine1", 10000, 20000), makeNode("machine2", 10000, 50000)}, expectedList: []schedulerapi.HostPriority{{"machine1", 6}, {"machine2", 6}}, test: "resources requested, pods scheduled with resources, differently sized machines", pods: []*api.Pod{ {Spec: cpuOnly}, {Spec: cpuAndMemory}, }, }, { /* Node1 scores on 0-10 scale CPU Fraction: 6000 / 4000 > 100% ==> Score := 0 Memory Fraction: 0 / 10000 = 0 Node1 Score: 0 Node2 scores on 0-10 scale CPU Fraction: 6000 / 4000 > 100% ==> Score := 0 Memory Fraction 5000 / 10000 = 50% Node2 Score: 0 */ pod: &api.Pod{Spec: cpuOnly}, nodes: []api.Node{makeNode("machine1", 4000, 10000), makeNode("machine2", 4000, 10000)}, expectedList: []schedulerapi.HostPriority{{"machine1", 0}, {"machine2", 0}}, test: "requested resources exceed node capacity", pods: []*api.Pod{ {Spec: cpuOnly}, {Spec: cpuAndMemory}, }, }, { pod: &api.Pod{Spec: noResources}, nodes: []api.Node{makeNode("machine1", 0, 0), makeNode("machine2", 0, 0)}, expectedList: []schedulerapi.HostPriority{{"machine1", 0}, {"machine2", 0}}, test: "zero node resources, pods scheduled with resources", pods: []*api.Pod{ {Spec: cpuOnly}, {Spec: cpuAndMemory}, }, }, } for _, test := range tests { nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap(test.pods) list, err := BalancedResourceAllocation(test.pod, nodeNameToInfo, algorithm.FakeNodeLister(api.NodeList{Items: test.nodes})) if err != nil { t.Errorf("unexpected error: %v", err) } if !reflect.DeepEqual(test.expectedList, list) { t.Errorf("%s: expected %#v, got %#v", test.test, test.expectedList, list) } } }
func TestLeastRequested(t *testing.T) { labels1 := map[string]string{ "foo": "bar", "baz": "blah", } labels2 := map[string]string{ "bar": "foo", "baz": "blah", } machine1Spec := api.PodSpec{ NodeName: "machine1", } machine2Spec := api.PodSpec{ NodeName: "machine2", } noResources := api.PodSpec{ Containers: []api.Container{}, } cpuOnly := api.PodSpec{ NodeName: "machine1", Containers: []api.Container{ { Resources: api.ResourceRequirements{ Requests: api.ResourceList{ "cpu": resource.MustParse("1000m"), "memory": resource.MustParse("0"), }, }, }, { Resources: api.ResourceRequirements{ Requests: api.ResourceList{ "cpu": resource.MustParse("2000m"), "memory": resource.MustParse("0"), }, }, }, }, } cpuOnly2 := cpuOnly cpuOnly2.NodeName = "machine2" cpuAndMemory := api.PodSpec{ NodeName: "machine2", Containers: []api.Container{ { Resources: api.ResourceRequirements{ Requests: api.ResourceList{ "cpu": resource.MustParse("1000m"), "memory": resource.MustParse("2000"), }, }, }, { Resources: api.ResourceRequirements{ Requests: api.ResourceList{ "cpu": resource.MustParse("2000m"), "memory": resource.MustParse("3000"), }, }, }, }, } tests := []struct { pod *api.Pod pods []*api.Pod nodes []api.Node expectedList schedulerapi.HostPriorityList test string }{ { /* Node1 scores (remaining resources) on 0-10 scale CPU Score: ((4000 - 0) *10) / 4000 = 10 Memory Score: ((10000 - 0) *10) / 10000 = 10 Node1 Score: (10 + 10) / 2 = 10 Node2 scores (remaining resources) on 0-10 scale CPU Score: ((4000 - 0) *10) / 4000 = 10 Memory Score: ((10000 - 0) *10) / 10000 = 10 Node2 Score: (10 + 10) / 2 = 10 */ pod: &api.Pod{Spec: noResources}, nodes: []api.Node{makeNode("machine1", 4000, 10000), makeNode("machine2", 4000, 10000)}, expectedList: []schedulerapi.HostPriority{{"machine1", 10}, {"machine2", 10}}, test: "nothing scheduled, nothing requested", }, { /* Node1 scores on 0-10 scale CPU Score: ((4000 - 3000) *10) / 4000 = 2.5 Memory Score: ((10000 - 5000) *10) / 10000 = 5 Node1 Score: (2.5 + 5) / 2 = 3 Node2 scores on 0-10 scale CPU Score: ((6000 - 3000) *10) / 6000 = 5 Memory Score: ((10000 - 5000) *10) / 10000 = 5 Node2 Score: (5 + 5) / 2 = 5 */ pod: &api.Pod{Spec: cpuAndMemory}, nodes: []api.Node{makeNode("machine1", 4000, 10000), makeNode("machine2", 6000, 10000)}, expectedList: []schedulerapi.HostPriority{{"machine1", 3}, {"machine2", 5}}, test: "nothing scheduled, resources requested, differently sized machines", }, { /* Node1 scores on 0-10 scale CPU Score: ((4000 - 0) *10) / 4000 = 10 Memory Score: ((10000 - 0) *10) / 10000 = 10 Node1 Score: (10 + 10) / 2 = 10 Node2 scores on 0-10 scale CPU Score: ((4000 - 0) *10) / 4000 = 10 Memory Score: ((10000 - 0) *10) / 10000 = 10 Node2 Score: (10 + 10) / 2 = 10 */ pod: &api.Pod{Spec: noResources}, nodes: []api.Node{makeNode("machine1", 4000, 10000), makeNode("machine2", 4000, 10000)}, expectedList: []schedulerapi.HostPriority{{"machine1", 10}, {"machine2", 10}}, test: "no resources requested, pods scheduled", pods: []*api.Pod{ {Spec: machine1Spec, ObjectMeta: api.ObjectMeta{Labels: labels2}}, {Spec: machine1Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, {Spec: machine2Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, {Spec: machine2Spec, ObjectMeta: api.ObjectMeta{Labels: labels1}}, }, }, { /* Node1 scores on 0-10 scale CPU Score: ((10000 - 6000) *10) / 10000 = 4 Memory Score: ((20000 - 0) *10) / 20000 = 10 Node1 Score: (4 + 10) / 2 = 7 Node2 scores on 0-10 scale CPU Score: ((10000 - 6000) *10) / 10000 = 4 Memory Score: ((20000 - 5000) *10) / 20000 = 7.5 Node2 Score: (4 + 7.5) / 2 = 5 */ pod: &api.Pod{Spec: noResources}, nodes: []api.Node{makeNode("machine1", 10000, 20000), makeNode("machine2", 10000, 20000)}, expectedList: []schedulerapi.HostPriority{{"machine1", 7}, {"machine2", 5}}, test: "no resources requested, pods scheduled with resources", pods: []*api.Pod{ {Spec: cpuOnly, ObjectMeta: api.ObjectMeta{Labels: labels2}}, {Spec: cpuOnly, ObjectMeta: api.ObjectMeta{Labels: labels1}}, {Spec: cpuOnly2, ObjectMeta: api.ObjectMeta{Labels: labels1}}, {Spec: cpuAndMemory, ObjectMeta: api.ObjectMeta{Labels: labels1}}, }, }, { /* Node1 scores on 0-10 scale CPU Score: ((10000 - 6000) *10) / 10000 = 4 Memory Score: ((20000 - 5000) *10) / 20000 = 7.5 Node1 Score: (4 + 7.5) / 2 = 5 Node2 scores on 0-10 scale CPU Score: ((10000 - 6000) *10) / 10000 = 4 Memory Score: ((20000 - 10000) *10) / 20000 = 5 Node2 Score: (4 + 5) / 2 = 4 */ pod: &api.Pod{Spec: cpuAndMemory}, nodes: []api.Node{makeNode("machine1", 10000, 20000), makeNode("machine2", 10000, 20000)}, expectedList: []schedulerapi.HostPriority{{"machine1", 5}, {"machine2", 4}}, test: "resources requested, pods scheduled with resources", pods: []*api.Pod{ {Spec: cpuOnly}, {Spec: cpuAndMemory}, }, }, { /* Node1 scores on 0-10 scale CPU Score: ((10000 - 6000) *10) / 10000 = 4 Memory Score: ((20000 - 5000) *10) / 20000 = 7.5 Node1 Score: (4 + 7.5) / 2 = 5 Node2 scores on 0-10 scale CPU Score: ((10000 - 6000) *10) / 10000 = 4 Memory Score: ((50000 - 10000) *10) / 50000 = 8 Node2 Score: (4 + 8) / 2 = 6 */ pod: &api.Pod{Spec: cpuAndMemory}, nodes: []api.Node{makeNode("machine1", 10000, 20000), makeNode("machine2", 10000, 50000)}, expectedList: []schedulerapi.HostPriority{{"machine1", 5}, {"machine2", 6}}, test: "resources requested, pods scheduled with resources, differently sized machines", pods: []*api.Pod{ {Spec: cpuOnly}, {Spec: cpuAndMemory}, }, }, { /* Node1 scores on 0-10 scale CPU Score: ((4000 - 6000) *10) / 4000 = 0 Memory Score: ((10000 - 0) *10) / 10000 = 10 Node1 Score: (0 + 10) / 2 = 5 Node2 scores on 0-10 scale CPU Score: ((4000 - 6000) *10) / 4000 = 0 Memory Score: ((10000 - 5000) *10) / 10000 = 5 Node2 Score: (0 + 5) / 2 = 2 */ pod: &api.Pod{Spec: cpuOnly}, nodes: []api.Node{makeNode("machine1", 4000, 10000), makeNode("machine2", 4000, 10000)}, expectedList: []schedulerapi.HostPriority{{"machine1", 5}, {"machine2", 2}}, test: "requested resources exceed node capacity", pods: []*api.Pod{ {Spec: cpuOnly}, {Spec: cpuAndMemory}, }, }, { pod: &api.Pod{Spec: noResources}, nodes: []api.Node{makeNode("machine1", 0, 0), makeNode("machine2", 0, 0)}, expectedList: []schedulerapi.HostPriority{{"machine1", 0}, {"machine2", 0}}, test: "zero node resources, pods scheduled with resources", pods: []*api.Pod{ {Spec: cpuOnly}, {Spec: cpuAndMemory}, }, }, } for _, test := range tests { nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap(test.pods) list, err := LeastRequestedPriority(test.pod, nodeNameToInfo, algorithm.FakeNodeLister(api.NodeList{Items: test.nodes})) if err != nil { t.Errorf("unexpected error: %v", err) } if !reflect.DeepEqual(test.expectedList, list) { t.Errorf("%s: expected %#v, got %#v", test.test, test.expectedList, list) } } }
func TestGenericSchedulerWithExtenders(t *testing.T) { tests := []struct { name string predicates map[string]algorithm.FitPredicate prioritizers []algorithm.PriorityConfig extenders []FakeExtender extenderPredicates []fitPredicate extenderPrioritizers []priorityConfig nodes []string pod *api.Pod pods []*api.Pod expectedHost string expectsErr bool }{ { predicates: map[string]algorithm.FitPredicate{"true": truePredicate}, prioritizers: []algorithm.PriorityConfig{{EqualPriority, 1}}, extenders: []FakeExtender{ { predicates: []fitPredicate{truePredicateExtender}, }, { predicates: []fitPredicate{errorPredicateExtender}, }, }, nodes: []string{"machine1", "machine2"}, expectsErr: true, name: "test 1", }, { predicates: map[string]algorithm.FitPredicate{"true": truePredicate}, prioritizers: []algorithm.PriorityConfig{{EqualPriority, 1}}, extenders: []FakeExtender{ { predicates: []fitPredicate{truePredicateExtender}, }, { predicates: []fitPredicate{falsePredicateExtender}, }, }, nodes: []string{"machine1", "machine2"}, expectsErr: true, name: "test 2", }, { predicates: map[string]algorithm.FitPredicate{"true": truePredicate}, prioritizers: []algorithm.PriorityConfig{{EqualPriority, 1}}, extenders: []FakeExtender{ { predicates: []fitPredicate{truePredicateExtender}, }, { predicates: []fitPredicate{machine1PredicateExtender}, }, }, nodes: []string{"machine1", "machine2"}, expectedHost: "machine1", name: "test 3", }, { predicates: map[string]algorithm.FitPredicate{"true": truePredicate}, prioritizers: []algorithm.PriorityConfig{{EqualPriority, 1}}, extenders: []FakeExtender{ { predicates: []fitPredicate{machine2PredicateExtender}, }, { predicates: []fitPredicate{machine1PredicateExtender}, }, }, nodes: []string{"machine1", "machine2"}, expectsErr: true, name: "test 4", }, { predicates: map[string]algorithm.FitPredicate{"true": truePredicate}, prioritizers: []algorithm.PriorityConfig{{EqualPriority, 1}}, extenders: []FakeExtender{ { predicates: []fitPredicate{truePredicateExtender}, prioritizers: []priorityConfig{{errorPrioritizerExtender, 10}}, weight: 1, }, }, nodes: []string{"machine1"}, expectedHost: "machine1", name: "test 5", }, { predicates: map[string]algorithm.FitPredicate{"true": truePredicate}, prioritizers: []algorithm.PriorityConfig{{EqualPriority, 1}}, extenders: []FakeExtender{ { predicates: []fitPredicate{truePredicateExtender}, prioritizers: []priorityConfig{{machine1PrioritizerExtender, 10}}, weight: 1, }, { predicates: []fitPredicate{truePredicateExtender}, prioritizers: []priorityConfig{{machine2PrioritizerExtender, 10}}, weight: 5, }, }, nodes: []string{"machine1", "machine2"}, expectedHost: "machine2", name: "test 6", }, { predicates: map[string]algorithm.FitPredicate{"true": truePredicate}, prioritizers: []algorithm.PriorityConfig{{machine2Prioritizer, 20}}, extenders: []FakeExtender{ { predicates: []fitPredicate{truePredicateExtender}, prioritizers: []priorityConfig{{machine1PrioritizerExtender, 10}}, weight: 1, }, }, nodes: []string{"machine1", "machine2"}, expectedHost: "machine2", // machine2 has higher score name: "test 7", }, } for _, test := range tests { random := rand.New(rand.NewSource(0)) extenders := []algorithm.SchedulerExtender{} for ii := range test.extenders { extenders = append(extenders, &test.extenders[ii]) } scheduler := NewGenericScheduler(schedulertesting.PodsToCache(test.pods), test.predicates, test.prioritizers, extenders, random) machine, err := scheduler.Schedule(test.pod, algorithm.FakeNodeLister(makeNodeList(test.nodes))) if test.expectsErr { if err == nil { t.Errorf("Unexpected non-error for %s, machine %s", test.name, machine) } } else { if err != nil { t.Errorf("Unexpected error: %v", err) } if test.expectedHost != machine { t.Errorf("Failed : %s, Expected: %s, Saw: %s", test.name, test.expectedHost, machine) } } } }
func TestNodeAffinityPriority(t *testing.T) { label1 := map[string]string{"foo": "bar"} label2 := map[string]string{"key": "value"} label3 := map[string]string{"az": "az1"} label4 := map[string]string{"abc": "az11", "def": "az22"} label5 := map[string]string{"foo": "bar", "key": "value", "az": "az1"} affinity1 := map[string]string{ api.AffinityAnnotationKey: ` {"nodeAffinity": {"preferredDuringSchedulingIgnoredDuringExecution": [ { "weight": 2, "preference": { "matchExpressions": [ { "key": "foo", "operator": "In", "values": ["bar"] } ] } } ]}}`, } affinity2 := map[string]string{ api.AffinityAnnotationKey: ` {"nodeAffinity": {"preferredDuringSchedulingIgnoredDuringExecution": [ { "weight": 2, "preference": {"matchExpressions": [ { "key": "foo", "operator": "In", "values": ["bar"] } ]} }, { "weight": 4, "preference": {"matchExpressions": [ { "key": "key", "operator": "In", "values": ["value"] } ]} }, { "weight": 5, "preference": {"matchExpressions": [ { "key": "foo", "operator": "In", "values": ["bar"] }, { "key": "key", "operator": "In", "values": ["value"] }, { "key": "az", "operator": "In", "values": ["az1"] } ]} } ]}}`, } tests := []struct { pod *api.Pod nodes []api.Node expectedList schedulerapi.HostPriorityList test string }{ { pod: &api.Pod{ ObjectMeta: api.ObjectMeta{ Annotations: map[string]string{}, }, }, nodes: []api.Node{ {ObjectMeta: api.ObjectMeta{Name: "machine1", Labels: label1}}, {ObjectMeta: api.ObjectMeta{Name: "machine2", Labels: label2}}, {ObjectMeta: api.ObjectMeta{Name: "machine3", Labels: label3}}, }, expectedList: []schedulerapi.HostPriority{{"machine1", 0}, {"machine2", 0}, {"machine3", 0}}, test: "all machines are same priority as NodeAffinity is nil", }, { pod: &api.Pod{ ObjectMeta: api.ObjectMeta{ Annotations: affinity1, }, }, nodes: []api.Node{ {ObjectMeta: api.ObjectMeta{Name: "machine1", Labels: label4}}, {ObjectMeta: api.ObjectMeta{Name: "machine2", Labels: label2}}, {ObjectMeta: api.ObjectMeta{Name: "machine3", Labels: label3}}, }, expectedList: []schedulerapi.HostPriority{{"machine1", 0}, {"machine2", 0}, {"machine3", 0}}, test: "no machine macthes preferred scheduling requirements in NodeAffinity of pod so all machines' priority is zero", }, { pod: &api.Pod{ ObjectMeta: api.ObjectMeta{ Annotations: affinity1, }, }, nodes: []api.Node{ {ObjectMeta: api.ObjectMeta{Name: "machine1", Labels: label1}}, {ObjectMeta: api.ObjectMeta{Name: "machine2", Labels: label2}}, {ObjectMeta: api.ObjectMeta{Name: "machine3", Labels: label3}}, }, expectedList: []schedulerapi.HostPriority{{"machine1", 10}, {"machine2", 0}, {"machine3", 0}}, test: "only machine1 matches the preferred scheduling requirements of pod", }, { pod: &api.Pod{ ObjectMeta: api.ObjectMeta{ Annotations: affinity2, }, }, nodes: []api.Node{ {ObjectMeta: api.ObjectMeta{Name: "machine1", Labels: label1}}, {ObjectMeta: api.ObjectMeta{Name: "machine5", Labels: label5}}, {ObjectMeta: api.ObjectMeta{Name: "machine2", Labels: label2}}, }, expectedList: []schedulerapi.HostPriority{{"machine1", 1}, {"machine5", 10}, {"machine2", 3}}, test: "all machines matches the preferred scheduling requirements of pod but with different priorities ", }, } for _, test := range tests { nodeAffinity := NodeAffinity{nodeLister: algorithm.FakeNodeLister(api.NodeList{Items: test.nodes})} list, err := nodeAffinity.CalculateNodeAffinityPriority(test.pod, nil, nil, algorithm.FakeNodeLister(api.NodeList{Items: test.nodes})) if err != nil { t.Errorf("unexpected error: %v", err) } if !reflect.DeepEqual(test.expectedList, list) { t.Errorf("%s: \nexpected %#v, \ngot %#v", test.test, test.expectedList, list) } } }
func TestSoftPodAntiAffinityWithFailureDomains(t *testing.T) { labelAzAZ1 := map[string]string{ "az": "az1", } LabelZoneFailureDomainAZ1 := map[string]string{ unversioned.LabelZoneFailureDomain: "az1", } podLabel1 := map[string]string{ "security": "S1", } antiAffinity1 := map[string]string{ v1.AffinityAnnotationKey: ` {"podAntiAffinity": { "preferredDuringSchedulingIgnoredDuringExecution": [{ "weight": 5, "podAffinityTerm": { "labelSelector": { "matchExpressions": [{ "key": "security", "operator": "In", "values":["S1"] }] }, "namespaces": [], "topologyKey": "" } }] }}`, } tests := []struct { pod *v1.Pod pods []*v1.Pod nodes []*v1.Node failureDomains priorityutil.Topologies expectedList schedulerapi.HostPriorityList test string }{ { pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: v1.ObjectMeta{Labels: podLabel1, Annotations: antiAffinity1}}, pods: []*v1.Pod{ {Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: v1.ObjectMeta{Labels: podLabel1}}, {Spec: v1.PodSpec{NodeName: "machine2"}, ObjectMeta: v1.ObjectMeta{Labels: podLabel1}}, }, nodes: []*v1.Node{ {ObjectMeta: v1.ObjectMeta{Name: "machine1", Labels: LabelZoneFailureDomainAZ1}}, {ObjectMeta: v1.ObjectMeta{Name: "machine2", Labels: labelAzAZ1}}, }, failureDomains: priorityutil.Topologies{DefaultKeys: strings.Split(v1.DefaultFailureDomains, ",")}, expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 0}, {Host: "machine2", Score: 10}}, test: "Soft Pod Anti Affinity: when the topologyKey is emtpy, match among topologyKeys indicated by failure domains.", }, { pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: v1.ObjectMeta{Labels: podLabel1, Annotations: antiAffinity1}}, pods: []*v1.Pod{ {Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: v1.ObjectMeta{Labels: podLabel1}}, {Spec: v1.PodSpec{NodeName: "machine2"}, ObjectMeta: v1.ObjectMeta{Labels: podLabel1}}, }, nodes: []*v1.Node{ {ObjectMeta: v1.ObjectMeta{Name: "machine1", Labels: LabelZoneFailureDomainAZ1}}, {ObjectMeta: v1.ObjectMeta{Name: "machine2", Labels: labelAzAZ1}}, }, failureDomains: priorityutil.Topologies{}, expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 0}, {Host: "machine2", Score: 0}}, test: "Soft Pod Anti Affinity: when the topologyKey is emtpy, and no failure domains indicated, regard as topologyKey not match.", }, } for _, test := range tests { nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap(test.pods, test.nodes) ipa := InterPodAffinity{ info: FakeNodeListInfo(test.nodes), nodeLister: algorithm.FakeNodeLister(test.nodes), podLister: algorithm.FakePodLister(test.pods), hardPodAffinityWeight: v1.DefaultHardPodAffinitySymmetricWeight, failureDomains: test.failureDomains, } list, err := ipa.CalculateInterPodAffinityPriority(test.pod, nodeNameToInfo, test.nodes) if err != nil { t.Errorf("unexpected error: %v", err) } if !reflect.DeepEqual(test.expectedList, list) { t.Errorf("%s: \nexpected \n\t%#v, \ngot \n\t%#v\n", test.test, test.expectedList, list) } } }
func TestHardPodAffinitySymmetricWeight(t *testing.T) { podLabelServiceS1 := map[string]string{ "service": "S1", } labelRgChina := map[string]string{ "region": "China", } labelRgIndia := map[string]string{ "region": "India", } labelAzAz1 := map[string]string{ "az": "az1", } hardPodAffinity := map[string]string{ v1.AffinityAnnotationKey: ` {"podAffinity": { "requiredDuringSchedulingIgnoredDuringExecution": [ { "labelSelector":{ "matchExpressions": [{ "key": "service", "operator": "In", "values": ["S1"] }] }, "namespaces": [], "topologyKey": "region" } ] }}`, } tests := []struct { pod *v1.Pod pods []*v1.Pod nodes []*v1.Node hardPodAffinityWeight int expectedList schedulerapi.HostPriorityList test string }{ { pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: v1.ObjectMeta{Labels: podLabelServiceS1}}, pods: []*v1.Pod{ {Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: v1.ObjectMeta{Annotations: hardPodAffinity}}, {Spec: v1.PodSpec{NodeName: "machine2"}, ObjectMeta: v1.ObjectMeta{Annotations: hardPodAffinity}}, }, nodes: []*v1.Node{ {ObjectMeta: v1.ObjectMeta{Name: "machine1", Labels: labelRgChina}}, {ObjectMeta: v1.ObjectMeta{Name: "machine2", Labels: labelRgIndia}}, {ObjectMeta: v1.ObjectMeta{Name: "machine3", Labels: labelAzAz1}}, }, hardPodAffinityWeight: v1.DefaultHardPodAffinitySymmetricWeight, expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 10}, {Host: "machine2", Score: 10}, {Host: "machine3", Score: 0}}, test: "Hard Pod Affinity symmetry: hard pod affinity symmetry weights 1 by default, then nodes that match the hard pod affinity symmetry rules, get a high score", }, { pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: v1.ObjectMeta{Labels: podLabelServiceS1}}, pods: []*v1.Pod{ {Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: v1.ObjectMeta{Annotations: hardPodAffinity}}, {Spec: v1.PodSpec{NodeName: "machine2"}, ObjectMeta: v1.ObjectMeta{Annotations: hardPodAffinity}}, }, nodes: []*v1.Node{ {ObjectMeta: v1.ObjectMeta{Name: "machine1", Labels: labelRgChina}}, {ObjectMeta: v1.ObjectMeta{Name: "machine2", Labels: labelRgIndia}}, {ObjectMeta: v1.ObjectMeta{Name: "machine3", Labels: labelAzAz1}}, }, hardPodAffinityWeight: 0, expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 0}, {Host: "machine2", Score: 0}, {Host: "machine3", Score: 0}}, test: "Hard Pod Affinity symmetry: hard pod affinity symmetry is closed(weights 0), then nodes that match the hard pod affinity symmetry rules, get same score with those not match", }, } for _, test := range tests { nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap(test.pods, test.nodes) ipa := InterPodAffinity{ info: FakeNodeListInfo(test.nodes), nodeLister: algorithm.FakeNodeLister(test.nodes), podLister: algorithm.FakePodLister(test.pods), hardPodAffinityWeight: test.hardPodAffinityWeight, } list, err := ipa.CalculateInterPodAffinityPriority(test.pod, nodeNameToInfo, test.nodes) if err != nil { t.Errorf("unexpected error: %v", err) } if !reflect.DeepEqual(test.expectedList, list) { t.Errorf("%s: \nexpected \n\t%#v, \ngot \n\t%#v\n", test.test, test.expectedList, list) } } }
func TestInterPodAffinityPriority(t *testing.T) { labelRgChina := map[string]string{ "region": "China", } labelRgIndia := map[string]string{ "region": "India", } labelAzAz1 := map[string]string{ "az": "az1", } labelAzAz2 := map[string]string{ "az": "az2", } labelRgChinaAzAz1 := map[string]string{ "region": "China", "az": "az1", } podLabelSecurityS1 := map[string]string{ "security": "S1", } podLabelSecurityS2 := map[string]string{ "security": "S2", } // considered only preferredDuringSchedulingIgnoredDuringExecution in pod affinity stayWithS1InRegion := map[string]string{ v1.AffinityAnnotationKey: ` {"podAffinity": { "preferredDuringSchedulingIgnoredDuringExecution": [{ "weight": 5, "podAffinityTerm": { "labelSelector": { "matchExpressions": [{ "key": "security", "operator": "In", "values":["S1"] }] }, "namespaces": [], "topologyKey": "region" } }] }}`, } stayWithS2InRegion := map[string]string{ v1.AffinityAnnotationKey: ` {"podAffinity": { "preferredDuringSchedulingIgnoredDuringExecution": [{ "weight": 6, "podAffinityTerm": { "labelSelector": { "matchExpressions": [{ "key": "security", "operator": "In", "values":["S2"] }] }, "namespaces": [], "topologyKey": "region" } }] }}`, } affinity3 := map[string]string{ v1.AffinityAnnotationKey: ` {"podAffinity": { "preferredDuringSchedulingIgnoredDuringExecution": [ { "weight": 8, "podAffinityTerm": { "labelSelector": { "matchExpressions": [{ "key": "security", "operator": "NotIn", "values":["S1"] }, { "key": "security", "operator": "In", "values":["S2"] }] }, "namespaces": [], "topologyKey": "region" } }, { "weight": 2, "podAffinityTerm": { "labelSelector": { "matchExpressions": [{ "key": "security", "operator": "Exists" }, { "key": "wrongkey", "operator": "DoesNotExist" }] }, "namespaces": [], "topologyKey": "region" } } ] }}`, } hardAffinity := map[string]string{ v1.AffinityAnnotationKey: ` {"podAffinity": { "requiredDuringSchedulingIgnoredDuringExecution": [ { "labelSelector":{ "matchExpressions": [{ "key": "security", "operator": "In", "values": ["S1", "value2"] }] }, "namespaces": [], "topologyKey": "region" }, { "labelSelector": { "matchExpressions": [{ "key": "security", "operator": "Exists" }, { "key": "wrongkey", "operator": "DoesNotExist" }] }, "namespaces": [], "topologyKey": "region" } ] }}`, } awayFromS1InAz := map[string]string{ v1.AffinityAnnotationKey: ` {"podAntiAffinity": { "preferredDuringSchedulingIgnoredDuringExecution": [{ "weight": 5, "podAffinityTerm": { "labelSelector": { "matchExpressions": [{ "key": "security", "operator": "In", "values":["S1"] }] }, "namespaces": [], "topologyKey": "az" } }] }}`, } // to stay away from security S2 in any az. awayFromS2InAz := map[string]string{ v1.AffinityAnnotationKey: ` {"podAntiAffinity": { "preferredDuringSchedulingIgnoredDuringExecution": [{ "weight": 5, "podAffinityTerm": { "labelSelector": { "matchExpressions": [{ "key": "security", "operator": "In", "values":["S2"] }] }, "namespaces": [], "topologyKey": "az" } }] }}`, } // to stay with security S1 in same region, stay away from security S2 in any az. stayWithS1InRegionAwayFromS2InAz := map[string]string{ v1.AffinityAnnotationKey: ` {"podAffinity": { "preferredDuringSchedulingIgnoredDuringExecution": [{ "weight": 8, "podAffinityTerm": { "labelSelector": { "matchExpressions": [{ "key": "security", "operator": "In", "values":["S1"] }] }, "namespaces": [], "topologyKey": "region" } }] }, "podAntiAffinity": { "preferredDuringSchedulingIgnoredDuringExecution": [{ "weight": 5, "podAffinityTerm": { "labelSelector": { "matchExpressions": [{ "key": "security", "operator": "In", "values":["S2"] }] }, "namespaces": [], "topologyKey": "az" } }] }}`, } tests := []struct { pod *v1.Pod pods []*v1.Pod nodes []*v1.Node expectedList schedulerapi.HostPriorityList test string }{ { pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS1, Annotations: map[string]string{}}}, nodes: []*v1.Node{ {ObjectMeta: v1.ObjectMeta{Name: "machine1", Labels: labelRgChina}}, {ObjectMeta: v1.ObjectMeta{Name: "machine2", Labels: labelRgIndia}}, {ObjectMeta: v1.ObjectMeta{Name: "machine3", Labels: labelAzAz1}}, }, expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 0}, {Host: "machine2", Score: 0}, {Host: "machine3", Score: 0}}, test: "all machines are same priority as Affinity is nil", }, // the node(machine1) that have the label {"region": "China"} (match the topology key) and that have existing pods that match the labelSelector get high score // the node(machine3) that don't have the label {"region": "whatever the value is"} (mismatch the topology key) but that have existing pods that match the labelSelector get low score // the node(machine2) that have the label {"region": "China"} (match the topology key) but that have existing pods that mismatch the labelSelector get low score { pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS1, Annotations: stayWithS1InRegion}}, pods: []*v1.Pod{ {Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS1}}, {Spec: v1.PodSpec{NodeName: "machine2"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS2}}, {Spec: v1.PodSpec{NodeName: "machine3"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS1}}, }, nodes: []*v1.Node{ {ObjectMeta: v1.ObjectMeta{Name: "machine1", Labels: labelRgChina}}, {ObjectMeta: v1.ObjectMeta{Name: "machine2", Labels: labelRgIndia}}, {ObjectMeta: v1.ObjectMeta{Name: "machine3", Labels: labelAzAz1}}, }, expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 10}, {Host: "machine2", Score: 0}, {Host: "machine3", Score: 0}}, test: "Affinity: pod that matches topology key & pods in nodes will get high score comparing to others" + "which doesn't match either pods in nodes or in topology key", }, // the node1(machine1) that have the label {"region": "China"} (match the topology key) and that have existing pods that match the labelSelector get high score // the node2(machine2) that have the label {"region": "China"}, match the topology key and have the same label value with node1, get the same high score with node1 // the node3(machine3) that have the label {"region": "India"}, match the topology key but have a different label value, don't have existing pods that match the labelSelector, // get a low score. { pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: v1.ObjectMeta{Annotations: stayWithS1InRegion}}, pods: []*v1.Pod{ {Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS1}}, }, nodes: []*v1.Node{ {ObjectMeta: v1.ObjectMeta{Name: "machine1", Labels: labelRgChina}}, {ObjectMeta: v1.ObjectMeta{Name: "machine2", Labels: labelRgChinaAzAz1}}, {ObjectMeta: v1.ObjectMeta{Name: "machine3", Labels: labelRgIndia}}, }, expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 10}, {Host: "machine2", Score: 10}, {Host: "machine3", Score: 0}}, test: "All the nodes that have the same topology key & label value with one of them has an existing pod that match the affinity rules, have the same score", }, // there are 2 regions, say regionChina(machine1,machine3,machine4) and regionIndia(machine2,machine5), both regions have nodes that match the preference. // But there are more nodes(actually more existing pods) in regionChina that match the preference than regionIndia. // Then, nodes in regionChina get higher score than nodes in regionIndia, and all the nodes in regionChina should get a same score(high score), // while all the nodes in regionIndia should get another same score(low score). { pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS1, Annotations: stayWithS2InRegion}}, pods: []*v1.Pod{ {Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS2}}, {Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS2}}, {Spec: v1.PodSpec{NodeName: "machine2"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS2}}, {Spec: v1.PodSpec{NodeName: "machine3"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS2}}, {Spec: v1.PodSpec{NodeName: "machine4"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS2}}, {Spec: v1.PodSpec{NodeName: "machine5"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS2}}, }, nodes: []*v1.Node{ {ObjectMeta: v1.ObjectMeta{Name: "machine1", Labels: labelRgChina}}, {ObjectMeta: v1.ObjectMeta{Name: "machine2", Labels: labelRgIndia}}, {ObjectMeta: v1.ObjectMeta{Name: "machine3", Labels: labelRgChina}}, {ObjectMeta: v1.ObjectMeta{Name: "machine4", Labels: labelRgChina}}, {ObjectMeta: v1.ObjectMeta{Name: "machine5", Labels: labelRgIndia}}, }, expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 10}, {Host: "machine2", Score: 5}, {Host: "machine3", Score: 10}, {Host: "machine4", Score: 10}, {Host: "machine5", Score: 5}}, test: "Affinity: nodes in one region has more matching pods comparing to other reqion, so the region which has more macthes will get high score", }, // Test with the different operators and values for pod affinity scheduling preference, including some match failures. { pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS1, Annotations: affinity3}}, pods: []*v1.Pod{ {Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS1}}, {Spec: v1.PodSpec{NodeName: "machine2"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS2}}, {Spec: v1.PodSpec{NodeName: "machine3"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS1}}, }, nodes: []*v1.Node{ {ObjectMeta: v1.ObjectMeta{Name: "machine1", Labels: labelRgChina}}, {ObjectMeta: v1.ObjectMeta{Name: "machine2", Labels: labelRgIndia}}, {ObjectMeta: v1.ObjectMeta{Name: "machine3", Labels: labelAzAz1}}, }, expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 2}, {Host: "machine2", Score: 10}, {Host: "machine3", Score: 0}}, test: "Affinity: different Label operators and values for pod affinity scheduling preference, including some match failures ", }, // Test the symmetry cases for affinity, the difference between affinity and symmetry is not the pod wants to run together with some existing pods, // but the existing pods have the inter pod affinity preference while the pod to schedule satisfy the preference. { pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS2}}, pods: []*v1.Pod{ {Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS1, Annotations: stayWithS1InRegion}}, {Spec: v1.PodSpec{NodeName: "machine2"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS2, Annotations: stayWithS2InRegion}}, }, nodes: []*v1.Node{ {ObjectMeta: v1.ObjectMeta{Name: "machine1", Labels: labelRgChina}}, {ObjectMeta: v1.ObjectMeta{Name: "machine2", Labels: labelRgIndia}}, {ObjectMeta: v1.ObjectMeta{Name: "machine3", Labels: labelAzAz1}}, }, expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 0}, {Host: "machine2", Score: 10}, {Host: "machine3", Score: 0}}, test: "Affinity symmetry: considred only the preferredDuringSchedulingIgnoredDuringExecution in pod affinity symmetry", }, { pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS1}}, pods: []*v1.Pod{ {Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS1, Annotations: hardAffinity}}, {Spec: v1.PodSpec{NodeName: "machine2"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS2, Annotations: hardAffinity}}, }, nodes: []*v1.Node{ {ObjectMeta: v1.ObjectMeta{Name: "machine1", Labels: labelRgChina}}, {ObjectMeta: v1.ObjectMeta{Name: "machine2", Labels: labelRgIndia}}, {ObjectMeta: v1.ObjectMeta{Name: "machine3", Labels: labelAzAz1}}, }, expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 10}, {Host: "machine2", Score: 10}, {Host: "machine3", Score: 0}}, test: "Affinity symmetry: considred RequiredDuringSchedulingIgnoredDuringExecution in pod affinity symmetry", }, // The pod to schedule prefer to stay away from some existing pods at node level using the pod anti affinity. // the nodes that have the label {"node": "bar"} (match the topology key) and that have existing pods that match the labelSelector get low score // the nodes that don't have the label {"node": "whatever the value is"} (mismatch the topology key) but that have existing pods that match the labelSelector get high score // the nodes that have the label {"node": "bar"} (match the topology key) but that have existing pods that mismatch the labelSelector get high score // there are 2 nodes, say node1 and node2, both nodes have pods that match the labelSelector and have topology-key in node.Labels. // But there are more pods on node1 that match the preference than node2. Then, node1 get a lower score than node2. { pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS1, Annotations: awayFromS1InAz}}, pods: []*v1.Pod{ {Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS1}}, {Spec: v1.PodSpec{NodeName: "machine2"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS2}}, }, nodes: []*v1.Node{ {ObjectMeta: v1.ObjectMeta{Name: "machine1", Labels: labelAzAz1}}, {ObjectMeta: v1.ObjectMeta{Name: "machine2", Labels: labelRgChina}}, }, expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 0}, {Host: "machine2", Score: 10}}, test: "Anti Affinity: pod that doesnot match existing pods in node will get high score ", }, { pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS1, Annotations: awayFromS1InAz}}, pods: []*v1.Pod{ {Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS1}}, {Spec: v1.PodSpec{NodeName: "machine2"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS1}}, }, nodes: []*v1.Node{ {ObjectMeta: v1.ObjectMeta{Name: "machine1", Labels: labelAzAz1}}, {ObjectMeta: v1.ObjectMeta{Name: "machine2", Labels: labelRgChina}}, }, expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 0}, {Host: "machine2", Score: 10}}, test: "Anti Affinity: pod that does not matches topology key & matches the pods in nodes will get higher score comparing to others ", }, { pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS1, Annotations: awayFromS1InAz}}, pods: []*v1.Pod{ {Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS1}}, {Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS1}}, {Spec: v1.PodSpec{NodeName: "machine2"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS2}}, }, nodes: []*v1.Node{ {ObjectMeta: v1.ObjectMeta{Name: "machine1", Labels: labelAzAz1}}, {ObjectMeta: v1.ObjectMeta{Name: "machine2", Labels: labelRgIndia}}, }, expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 0}, {Host: "machine2", Score: 10}}, test: "Anti Affinity: one node has more matching pods comparing to other node, so the node which has more unmacthes will get high score", }, // Test the symmetry cases for anti affinity { pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS2}}, pods: []*v1.Pod{ {Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS1, Annotations: awayFromS2InAz}}, {Spec: v1.PodSpec{NodeName: "machine2"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS2, Annotations: awayFromS1InAz}}, }, nodes: []*v1.Node{ {ObjectMeta: v1.ObjectMeta{Name: "machine1", Labels: labelAzAz1}}, {ObjectMeta: v1.ObjectMeta{Name: "machine2", Labels: labelAzAz2}}, }, expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 0}, {Host: "machine2", Score: 10}}, test: "Anti Affinity symmetry: the existing pods in node which has anti affinity match will get high score", }, // Test both affinity and anti-affinity { pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS1, Annotations: stayWithS1InRegionAwayFromS2InAz}}, pods: []*v1.Pod{ {Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS1}}, {Spec: v1.PodSpec{NodeName: "machine2"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS1}}, }, nodes: []*v1.Node{ {ObjectMeta: v1.ObjectMeta{Name: "machine1", Labels: labelRgChina}}, {ObjectMeta: v1.ObjectMeta{Name: "machine2", Labels: labelAzAz1}}, }, expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 10}, {Host: "machine2", Score: 0}}, test: "Affinity and Anti Affinity: considered only preferredDuringSchedulingIgnoredDuringExecution in both pod affinity & anti affinity", }, // Combined cases considering both affinity and anti-affinity, the pod to schedule and existing pods have the same labels (they are in the same RC/service), // the pod prefer to run together with its brother pods in the same region, but wants to stay away from them at node level, // so that all the pods of a RC/service can stay in a same region but trying to separate with each other // machine-1,machine-3,machine-4 are in ChinaRegion others machin-2,machine-5 are in IndiaRegion { pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS1, Annotations: stayWithS1InRegionAwayFromS2InAz}}, pods: []*v1.Pod{ {Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS1}}, {Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS1}}, {Spec: v1.PodSpec{NodeName: "machine2"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS1}}, {Spec: v1.PodSpec{NodeName: "machine3"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS1}}, {Spec: v1.PodSpec{NodeName: "machine3"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS1}}, {Spec: v1.PodSpec{NodeName: "machine4"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS1}}, {Spec: v1.PodSpec{NodeName: "machine5"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS1}}, }, nodes: []*v1.Node{ {ObjectMeta: v1.ObjectMeta{Name: "machine1", Labels: labelRgChinaAzAz1}}, {ObjectMeta: v1.ObjectMeta{Name: "machine2", Labels: labelRgIndia}}, {ObjectMeta: v1.ObjectMeta{Name: "machine3", Labels: labelRgChina}}, {ObjectMeta: v1.ObjectMeta{Name: "machine4", Labels: labelRgChina}}, {ObjectMeta: v1.ObjectMeta{Name: "machine5", Labels: labelRgIndia}}, }, expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 10}, {Host: "machine2", Score: 4}, {Host: "machine3", Score: 10}, {Host: "machine4", Score: 10}, {Host: "machine5", Score: 4}}, test: "Affinity and Anti Affinity: considering both affinity and anti-affinity, the pod to schedule and existing pods have the same labels", }, // Consider Affinity, Anti Affinity and symmetry together. // for Affinity, the weights are: 8, 0, 0, 0 // for Anti Affinity, the weights are: 0, -5, 0, 0 // for Affinity symmetry, the weights are: 0, 0, 8, 0 // for Anti Affinity symmetry, the weights are: 0, 0, 0, -5 { pod: &v1.Pod{Spec: v1.PodSpec{NodeName: ""}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS1, Annotations: stayWithS1InRegionAwayFromS2InAz}}, pods: []*v1.Pod{ {Spec: v1.PodSpec{NodeName: "machine1"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS1}}, {Spec: v1.PodSpec{NodeName: "machine2"}, ObjectMeta: v1.ObjectMeta{Labels: podLabelSecurityS2}}, {Spec: v1.PodSpec{NodeName: "machine3"}, ObjectMeta: v1.ObjectMeta{Annotations: stayWithS1InRegionAwayFromS2InAz}}, {Spec: v1.PodSpec{NodeName: "machine4"}, ObjectMeta: v1.ObjectMeta{Annotations: awayFromS1InAz}}, }, nodes: []*v1.Node{ {ObjectMeta: v1.ObjectMeta{Name: "machine1", Labels: labelRgChina}}, {ObjectMeta: v1.ObjectMeta{Name: "machine2", Labels: labelAzAz1}}, {ObjectMeta: v1.ObjectMeta{Name: "machine3", Labels: labelRgIndia}}, {ObjectMeta: v1.ObjectMeta{Name: "machine4", Labels: labelAzAz2}}, }, expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 10}, {Host: "machine2", Score: 0}, {Host: "machine3", Score: 10}, {Host: "machine4", Score: 0}}, test: "Affinity and Anti Affinity and symmetry: considered only preferredDuringSchedulingIgnoredDuringExecution in both pod affinity & anti affinity & symmetry", }, } for _, test := range tests { nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap(test.pods, test.nodes) interPodAffinity := InterPodAffinity{ info: FakeNodeListInfo(test.nodes), nodeLister: algorithm.FakeNodeLister(test.nodes), podLister: algorithm.FakePodLister(test.pods), hardPodAffinityWeight: v1.DefaultHardPodAffinitySymmetricWeight, failureDomains: priorityutil.Topologies{DefaultKeys: strings.Split(v1.DefaultFailureDomains, ",")}, } list, err := interPodAffinity.CalculateInterPodAffinityPriority(test.pod, nodeNameToInfo, test.nodes) if err != nil { t.Errorf("unexpected error: %v", err) } if !reflect.DeepEqual(test.expectedList, list) { t.Errorf("%s: \nexpected \n\t%#v, \ngot \n\t%#v\n", test.test, test.expectedList, list) } } }
func TestNewNodeLabelPriority(t *testing.T) { label1 := map[string]string{"foo": "bar"} label2 := map[string]string{"bar": "foo"} label3 := map[string]string{"bar": "baz"} tests := []struct { nodes []api.Node label string presence bool expectedList schedulerapi.HostPriorityList test string }{ { nodes: []api.Node{ {ObjectMeta: api.ObjectMeta{Name: "machine1", Labels: label1}}, {ObjectMeta: api.ObjectMeta{Name: "machine2", Labels: label2}}, {ObjectMeta: api.ObjectMeta{Name: "machine3", Labels: label3}}, }, expectedList: []schedulerapi.HostPriority{{"machine1", 0}, {"machine2", 0}, {"machine3", 0}}, label: "baz", presence: true, test: "no match found, presence true", }, { nodes: []api.Node{ {ObjectMeta: api.ObjectMeta{Name: "machine1", Labels: label1}}, {ObjectMeta: api.ObjectMeta{Name: "machine2", Labels: label2}}, {ObjectMeta: api.ObjectMeta{Name: "machine3", Labels: label3}}, }, expectedList: []schedulerapi.HostPriority{{"machine1", 10}, {"machine2", 10}, {"machine3", 10}}, label: "baz", presence: false, test: "no match found, presence false", }, { nodes: []api.Node{ {ObjectMeta: api.ObjectMeta{Name: "machine1", Labels: label1}}, {ObjectMeta: api.ObjectMeta{Name: "machine2", Labels: label2}}, {ObjectMeta: api.ObjectMeta{Name: "machine3", Labels: label3}}, }, expectedList: []schedulerapi.HostPriority{{"machine1", 10}, {"machine2", 0}, {"machine3", 0}}, label: "foo", presence: true, test: "one match found, presence true", }, { nodes: []api.Node{ {ObjectMeta: api.ObjectMeta{Name: "machine1", Labels: label1}}, {ObjectMeta: api.ObjectMeta{Name: "machine2", Labels: label2}}, {ObjectMeta: api.ObjectMeta{Name: "machine3", Labels: label3}}, }, expectedList: []schedulerapi.HostPriority{{"machine1", 0}, {"machine2", 10}, {"machine3", 10}}, label: "foo", presence: false, test: "one match found, presence false", }, { nodes: []api.Node{ {ObjectMeta: api.ObjectMeta{Name: "machine1", Labels: label1}}, {ObjectMeta: api.ObjectMeta{Name: "machine2", Labels: label2}}, {ObjectMeta: api.ObjectMeta{Name: "machine3", Labels: label3}}, }, expectedList: []schedulerapi.HostPriority{{"machine1", 0}, {"machine2", 10}, {"machine3", 10}}, label: "bar", presence: true, test: "two matches found, presence true", }, { nodes: []api.Node{ {ObjectMeta: api.ObjectMeta{Name: "machine1", Labels: label1}}, {ObjectMeta: api.ObjectMeta{Name: "machine2", Labels: label2}}, {ObjectMeta: api.ObjectMeta{Name: "machine3", Labels: label3}}, }, expectedList: []schedulerapi.HostPriority{{"machine1", 10}, {"machine2", 0}, {"machine3", 0}}, label: "bar", presence: false, test: "two matches found, presence false", }, } for _, test := range tests { prioritizer := NodeLabelPrioritizer{ label: test.label, presence: test.presence, } list, err := prioritizer.CalculateNodeLabelPriority(nil, map[string]*schedulercache.NodeInfo{}, algorithm.FakeNodeLister(api.NodeList{Items: test.nodes})) if err != nil { t.Errorf("unexpected error: %v", err) } // sort the two lists to avoid failures on account of different ordering sort.Sort(test.expectedList) sort.Sort(list) if !reflect.DeepEqual(test.expectedList, list) { t.Errorf("%s: expected %#v, got %#v", test.test, test.expectedList, list) } } }
func TestSchedulerFailedSchedulingReasons(t *testing.T) { stop := make(chan struct{}) defer close(stop) queuedPodStore := clientcache.NewFIFO(clientcache.MetaNamespaceKeyFunc) scache := schedulercache.New(10*time.Minute, stop) // Design the baseline for the pods, and we will make nodes that dont fit it later. var cpu = int64(4) var mem = int64(500) podWithTooBigResourceRequests := podWithResources("bar", "", api.ResourceList{ api.ResourceCPU: *(resource.NewQuantity(cpu, resource.DecimalSI)), api.ResourceMemory: *(resource.NewQuantity(mem, resource.DecimalSI)), }, api.ResourceList{ api.ResourceCPU: *(resource.NewQuantity(cpu, resource.DecimalSI)), api.ResourceMemory: *(resource.NewQuantity(mem, resource.DecimalSI)), }) // create several nodes which cannot schedule the above pod nodes := []*api.Node{} for i := 0; i < 100; i++ { node := api.Node{ ObjectMeta: api.ObjectMeta{Name: fmt.Sprintf("machine%v", i)}, Status: api.NodeStatus{ Capacity: api.ResourceList{ api.ResourceCPU: *(resource.NewQuantity(cpu/2, resource.DecimalSI)), api.ResourceMemory: *(resource.NewQuantity(mem/5, resource.DecimalSI)), api.ResourcePods: *(resource.NewQuantity(10, resource.DecimalSI)), }, Allocatable: api.ResourceList{ api.ResourceCPU: *(resource.NewQuantity(cpu/2, resource.DecimalSI)), api.ResourceMemory: *(resource.NewQuantity(mem/5, resource.DecimalSI)), api.ResourcePods: *(resource.NewQuantity(10, resource.DecimalSI)), }}, } scache.AddNode(&node) nodes = append(nodes, &node) } nodeLister := algorithm.FakeNodeLister(nodes) predicateMap := map[string]algorithm.FitPredicate{ "PodFitsResources": predicates.PodFitsResources, } // Create expected failure reasons for all the nodes. Hopefully they will get rolled up into a non-spammy summary. failedPredicatesMap := FailedPredicateMap{} for _, node := range nodes { failedPredicatesMap[node.Name] = []algorithm.PredicateFailureReason{ predicates.NewInsufficientResourceError(api.ResourceCPU, 4000, 0, 2000), predicates.NewInsufficientResourceError(api.ResourceMemory, 500, 0, 100), } } scheduler, _, errChan := setupTestScheduler(queuedPodStore, scache, nodeLister, predicateMap) queuedPodStore.Add(podWithTooBigResourceRequests) scheduler.scheduleOne() select { case err := <-errChan: expectErr := &FitError{ Pod: podWithTooBigResourceRequests, FailedPredicates: failedPredicatesMap, } if len(fmt.Sprint(expectErr)) > 150 { t.Errorf("message is too spammy ! %v ", len(fmt.Sprint(expectErr))) } if !reflect.DeepEqual(expectErr, err) { t.Errorf("\n err \nWANT=%+v,\nGOT=%+v", expectErr, err) } case <-time.After(wait.ForeverTestTimeout): t.Fatalf("timeout after %v", wait.ForeverTestTimeout) } }
func TestImageLocalityPriority(t *testing.T) { test_40_250 := api.PodSpec{ Containers: []api.Container{ { Image: "gcr.io/40", }, { Image: "gcr.io/250", }, }, } test_40_140 := api.PodSpec{ Containers: []api.Container{ { Image: "gcr.io/40", }, { Image: "gcr.io/140", }, }, } test_min_max := api.PodSpec{ Containers: []api.Container{ { Image: "gcr.io/10", }, { Image: "gcr.io/2000", }, }, } node_40_140_2000 := api.NodeStatus{ Images: []api.ContainerImage{ { Names: []string{ "gcr.io/40", "gcr.io/40:v1", "gcr.io/40:v1", }, SizeBytes: int64(40 * mb), }, { Names: []string{ "gcr.io/140", "gcr.io/140:v1", }, SizeBytes: int64(140 * mb), }, { Names: []string{ "gcr.io/2000", }, SizeBytes: int64(2000 * mb), }, }, } node_250_10 := api.NodeStatus{ Images: []api.ContainerImage{ { Names: []string{ "gcr.io/250", }, SizeBytes: int64(250 * mb), }, { Names: []string{ "gcr.io/10", "gcr.io/10:v1", }, SizeBytes: int64(10 * mb), }, }, } tests := []struct { pod *api.Pod pods []*api.Pod nodes []api.Node expectedList schedulerapi.HostPriorityList test string }{ { // Pod: gcr.io/40 gcr.io/250 // Node1 // Image: gcr.io/40 40MB // Score: (40M-23M)/97.7M + 1 = 1 // Node2 // Image: gcr.io/250 250MB // Score: (250M-23M)/97.7M + 1 = 3 pod: &api.Pod{Spec: test_40_250}, nodes: []api.Node{makeImageNode("machine1", node_40_140_2000), makeImageNode("machine2", node_250_10)}, expectedList: []schedulerapi.HostPriority{{"machine1", 1}, {"machine2", 3}}, test: "two images spread on two nodes, prefer the larger image one", }, { // Pod: gcr.io/40 gcr.io/140 // Node1 // Image: gcr.io/40 40MB, gcr.io/140 140MB // Score: (40M+140M-23M)/97.7M + 1 = 2 // Node2 // Image: not present // Score: 0 pod: &api.Pod{Spec: test_40_140}, nodes: []api.Node{makeImageNode("machine1", node_40_140_2000), makeImageNode("machine2", node_250_10)}, expectedList: []schedulerapi.HostPriority{{"machine1", 2}, {"machine2", 0}}, test: "two images on one node, prefer this node", }, { // Pod: gcr.io/2000 gcr.io/10 // Node1 // Image: gcr.io/2000 2000MB // Score: 2000 > max score = 10 // Node2 // Image: gcr.io/10 10MB // Score: 10 < min score = 0 pod: &api.Pod{Spec: test_min_max}, nodes: []api.Node{makeImageNode("machine1", node_40_140_2000), makeImageNode("machine2", node_250_10)}, expectedList: []schedulerapi.HostPriority{{"machine1", 10}, {"machine2", 0}}, test: "if exceed limit, use limit", }, } for _, test := range tests { nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap(test.pods) list, err := ImageLocalityPriority(test.pod, nodeNameToInfo, algorithm.FakeNodeLister(api.NodeList{Items: test.nodes})) if err != nil { t.Errorf("unexpected error: %v", err) } sort.Sort(test.expectedList) sort.Sort(list) if !reflect.DeepEqual(test.expectedList, list) { t.Errorf("%s: expected %#v, got %#v", test.test, test.expectedList, list) } } }
func TestSchedulerForgetAssumedPodAfterDelete(t *testing.T) { // Set up a channel through which we'll funnel log messages from the watcher. // This way, we can guarantee that when the test ends no thread will still be // trying to write to t.Logf (which it would if we handed t.Logf directly to // StartLogging). ch := make(chan string) done := make(chan struct{}) var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() for { select { case msg := <-ch: t.Log(msg) case <-done: return } } }() eventBroadcaster := record.NewBroadcaster() watcher := eventBroadcaster.StartLogging(func(format string, args ...interface{}) { ch <- fmt.Sprintf(format, args...) }) defer func() { watcher.Stop() close(done) wg.Wait() }() // Setup stores to test pod's workflow: // - queuedPodStore: pods queued before processing // - scheduledPodStore: pods that has a scheduling decision scheduledPodStore := cache.NewStore(cache.MetaNamespaceKeyFunc) queuedPodStore := cache.NewFIFO(cache.MetaNamespaceKeyFunc) // Port is the easiest way to cause a fit predicate failure podPort := 8080 firstPod := podWithPort("foo", "", podPort) stop := make(chan struct{}) defer close(stop) cache := schedulercache.New(1*time.Second, stop) // Create the scheduler config algo := NewGenericScheduler( cache, map[string]algorithm.FitPredicate{"PodFitsHostPorts": predicates.PodFitsHostPorts}, []algorithm.PriorityConfig{}, []algorithm.SchedulerExtender{}, rand.New(rand.NewSource(time.Now().UnixNano()))) var gotBinding *api.Binding c := &Config{ SchedulerCache: cache, NodeLister: algorithm.FakeNodeLister( api.NodeList{Items: []api.Node{{ObjectMeta: api.ObjectMeta{Name: "machine1"}}}}, ), Algorithm: algo, Binder: fakeBinder{func(b *api.Binding) error { scheduledPodStore.Add(podWithPort(b.Name, b.Target.Name, podPort)) gotBinding = b return nil }}, NextPod: func() *api.Pod { return queuedPodStore.Pop().(*api.Pod) }, Error: func(p *api.Pod, err error) { t.Errorf("Unexpected error when scheduling pod %+v: %v", p, err) }, Recorder: eventBroadcaster.NewRecorder(api.EventSource{Component: "scheduler"}), } // First scheduling pass should schedule the pod s := New(c) called := make(chan struct{}) events := eventBroadcaster.StartEventWatcher(func(e *api.Event) { if e, a := "Scheduled", e.Reason; e != a { t.Errorf("expected %v, got %v", e, a) } close(called) }) queuedPodStore.Add(firstPod) // queuedPodStore: [foo:8080] // scheduledPodStore: [] // assumedPods: [] s.scheduleOne() <-called // queuedPodStore: [] // scheduledPodStore: [foo:8080] // assumedPods: [foo:8080] pod, exists, _ := scheduledPodStore.GetByKey("foo") if !exists { t.Errorf("Expected scheduled pod store to contain pod") } pod, exists, _ = queuedPodStore.GetByKey("foo") if exists { t.Errorf("Did not expect a queued pod, found %+v", pod) } expectBind := &api.Binding{ ObjectMeta: api.ObjectMeta{Name: "foo"}, Target: api.ObjectReference{Kind: "Node", Name: "machine1"}, } if ex, ac := expectBind, gotBinding; !reflect.DeepEqual(ex, ac) { t.Errorf("Expected exact match on binding: %s", diff.ObjectDiff(ex, ac)) } events.Stop() scheduledPodStore.Delete(pod) secondPod := podWithPort("bar", "", podPort) queuedPodStore.Add(secondPod) // queuedPodStore: [bar:8080] // scheduledPodStore: [] // assumedPods: [foo:8080] var waitUntilExpired sync.WaitGroup waitUntilExpired.Add(1) // waiting for the assumed pod to expire go func() { for { pods, err := cache.List(labels.Everything()) if err != nil { t.Fatalf("cache.List failed: %v", err) } if len(pods) == 0 { waitUntilExpired.Done() return } time.Sleep(1 * time.Second) } }() waitUntilExpired.Wait() // Second scheduling pass will fail to schedule if the store hasn't expired // the deleted pod. This would normally happen with a timeout. called = make(chan struct{}) events = eventBroadcaster.StartEventWatcher(func(e *api.Event) { if e, a := "Scheduled", e.Reason; e != a { t.Errorf("expected %v, got %v", e, a) } close(called) }) s.scheduleOne() <-called expectBind = &api.Binding{ ObjectMeta: api.ObjectMeta{Name: "bar"}, Target: api.ObjectReference{Kind: "Node", Name: "machine1"}, } if ex, ac := expectBind, gotBinding; !reflect.DeepEqual(ex, ac) { t.Errorf("Expected exact match on binding: %s", diff.ObjectDiff(ex, ac)) } events.Stop() }
// The point of this test is to show that you: // - get the same priority for a zero-request pod as for a pod with the defaults requests, // both when the zero-request pod is already on the machine and when the zero-request pod // is the one being scheduled. // - don't get the same score no matter what we schedule. func TestZeroRequest(t *testing.T) { // A pod with no resources. We expect spreading to count it as having the default resources. noResources := api.PodSpec{ Containers: []api.Container{ {}, }, } noResources1 := noResources noResources1.NodeName = "machine1" // A pod with the same resources as a 0-request pod gets by default as its resources (for spreading). small := api.PodSpec{ Containers: []api.Container{ { Resources: api.ResourceRequirements{ Requests: api.ResourceList{ "cpu": resource.MustParse( strconv.FormatInt(priorityutil.DefaultMilliCpuRequest, 10) + "m"), "memory": resource.MustParse( strconv.FormatInt(priorityutil.DefaultMemoryRequest, 10)), }, }, }, }, } small2 := small small2.NodeName = "machine2" // A larger pod. large := api.PodSpec{ Containers: []api.Container{ { Resources: api.ResourceRequirements{ Requests: api.ResourceList{ "cpu": resource.MustParse( strconv.FormatInt(priorityutil.DefaultMilliCpuRequest*3, 10) + "m"), "memory": resource.MustParse( strconv.FormatInt(priorityutil.DefaultMemoryRequest*3, 10)), }, }, }, }, } large1 := large large1.NodeName = "machine1" large2 := large large2.NodeName = "machine2" tests := []struct { pod *api.Pod pods []*api.Pod nodes []*api.Node test string }{ // The point of these next two tests is to show you get the same priority for a zero-request pod // as for a pod with the defaults requests, both when the zero-request pod is already on the machine // and when the zero-request pod is the one being scheduled. { pod: &api.Pod{Spec: noResources}, nodes: []*api.Node{makeNode("machine1", 1000, priorityutil.DefaultMemoryRequest*10), makeNode("machine2", 1000, priorityutil.DefaultMemoryRequest*10)}, test: "test priority of zero-request pod with machine with zero-request pod", pods: []*api.Pod{ {Spec: large1}, {Spec: noResources1}, {Spec: large2}, {Spec: small2}, }, }, { pod: &api.Pod{Spec: small}, nodes: []*api.Node{makeNode("machine1", 1000, priorityutil.DefaultMemoryRequest*10), makeNode("machine2", 1000, priorityutil.DefaultMemoryRequest*10)}, test: "test priority of nonzero-request pod with machine with zero-request pod", pods: []*api.Pod{ {Spec: large1}, {Spec: noResources1}, {Spec: large2}, {Spec: small2}, }, }, // The point of this test is to verify that we're not just getting the same score no matter what we schedule. { pod: &api.Pod{Spec: large}, nodes: []*api.Node{makeNode("machine1", 1000, priorityutil.DefaultMemoryRequest*10), makeNode("machine2", 1000, priorityutil.DefaultMemoryRequest*10)}, test: "test priority of larger pod with machine with zero-request pod", pods: []*api.Pod{ {Spec: large1}, {Spec: noResources1}, {Spec: large2}, {Spec: small2}, }, }, } const expectedPriority int = 25 for _, test := range tests { // This should match the configuration in defaultPriorities() in // plugin/pkg/scheduler/algorithmprovider/defaults/defaults.go if you want // to test what's actually in production. priorityConfigs := []algorithm.PriorityConfig{ {Map: algorithmpriorities.LeastRequestedPriorityMap, Weight: 1}, {Map: algorithmpriorities.BalancedResourceAllocationMap, Weight: 1}, { Function: algorithmpriorities.NewSelectorSpreadPriority( algorithm.FakeServiceLister([]*api.Service{}), algorithm.FakeControllerLister([]*api.ReplicationController{}), algorithm.FakeReplicaSetLister([]*extensions.ReplicaSet{})), Weight: 1, }, } nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap(test.pods, test.nodes) list, err := PrioritizeNodes( test.pod, nodeNameToInfo, algorithm.EmptyMetadataProducer, priorityConfigs, algorithm.FakeNodeLister(test.nodes), []algorithm.SchedulerExtender{}) if err != nil { t.Errorf("unexpected error: %v", err) } for _, hp := range list { if test.test == "test priority of larger pod with machine with zero-request pod" { if hp.Score == expectedPriority { t.Errorf("%s: expected non-%d for all priorities, got list %#v", test.test, expectedPriority, list) } } else { if hp.Score != expectedPriority { t.Errorf("%s: expected %d for all priorities, got list %#v", test.test, expectedPriority, list) } } } } }
func TestTaintAndToleration(t *testing.T) { tests := []struct { pod *api.Pod nodes []api.Node expectedList schedulerapi.HostPriorityList test string }{ // basic test case { test: "node with taints tolerated by the pod, gets a higher score than those node with intolerable taints", pod: podWithTolerations([]api.Toleration{{ Key: "foo", Operator: api.TolerationOpEqual, Value: "bar", Effect: api.TaintEffectPreferNoSchedule, }}), nodes: []api.Node{ nodeWithTaints("nodeA", []api.Taint{{ Key: "foo", Value: "bar", Effect: api.TaintEffectPreferNoSchedule, }}), nodeWithTaints("nodeB", []api.Taint{{ Key: "foo", Value: "blah", Effect: api.TaintEffectPreferNoSchedule, }}), }, expectedList: []schedulerapi.HostPriority{ {Host: "nodeA", Score: 10}, {Host: "nodeB", Score: 0}, }, }, // the count of taints that are tolerated by pod, does not matter. { test: "the nodes that all of their taints are tolerated by the pod, get the same score, no matter how many tolerable taints a node has", pod: podWithTolerations([]api.Toleration{ { Key: "cpu-type", Operator: api.TolerationOpEqual, Value: "arm64", Effect: api.TaintEffectPreferNoSchedule, }, { Key: "disk-type", Operator: api.TolerationOpEqual, Value: "ssd", Effect: api.TaintEffectPreferNoSchedule, }, }), nodes: []api.Node{ nodeWithTaints("nodeA", []api.Taint{}), nodeWithTaints("nodeB", []api.Taint{ { Key: "cpu-type", Value: "arm64", Effect: api.TaintEffectPreferNoSchedule, }, }), nodeWithTaints("nodeC", []api.Taint{ { Key: "cpu-type", Value: "arm64", Effect: api.TaintEffectPreferNoSchedule, }, { Key: "disk-type", Value: "ssd", Effect: api.TaintEffectPreferNoSchedule, }, }), }, expectedList: []schedulerapi.HostPriority{ {Host: "nodeA", Score: 10}, {Host: "nodeB", Score: 10}, {Host: "nodeC", Score: 10}, }, }, // the count of taints on a node that are not tolerated by pod, matters. { test: "the more intolerable taints a node has, the lower score it gets.", pod: podWithTolerations([]api.Toleration{{ Key: "foo", Operator: api.TolerationOpEqual, Value: "bar", Effect: api.TaintEffectPreferNoSchedule, }}), nodes: []api.Node{ nodeWithTaints("nodeA", []api.Taint{}), nodeWithTaints("nodeB", []api.Taint{ { Key: "cpu-type", Value: "arm64", Effect: api.TaintEffectPreferNoSchedule, }, }), nodeWithTaints("nodeC", []api.Taint{ { Key: "cpu-type", Value: "arm64", Effect: api.TaintEffectPreferNoSchedule, }, { Key: "disk-type", Value: "ssd", Effect: api.TaintEffectPreferNoSchedule, }, }), }, expectedList: []schedulerapi.HostPriority{ {Host: "nodeA", Score: 10}, {Host: "nodeB", Score: 5}, {Host: "nodeC", Score: 0}, }, }, // taints-tolerations priority only takes care about the taints and tolerations that have effect PreferNoSchedule { test: "only taints and tolerations that have effect PreferNoSchedule are checked by taints-tolerations priority function", pod: podWithTolerations([]api.Toleration{ { Key: "cpu-type", Operator: api.TolerationOpEqual, Value: "arm64", Effect: api.TaintEffectNoSchedule, }, { Key: "disk-type", Operator: api.TolerationOpEqual, Value: "ssd", Effect: api.TaintEffectNoSchedule, }, }), nodes: []api.Node{ nodeWithTaints("nodeA", []api.Taint{}), nodeWithTaints("nodeB", []api.Taint{ { Key: "cpu-type", Value: "arm64", Effect: api.TaintEffectNoSchedule, }, }), nodeWithTaints("nodeC", []api.Taint{ { Key: "cpu-type", Value: "arm64", Effect: api.TaintEffectPreferNoSchedule, }, { Key: "disk-type", Value: "ssd", Effect: api.TaintEffectPreferNoSchedule, }, }), }, expectedList: []schedulerapi.HostPriority{ {Host: "nodeA", Score: 10}, {Host: "nodeB", Score: 10}, {Host: "nodeC", Score: 0}, }, }, } for _, test := range tests { nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap([]*api.Pod{{}}) taintToleration := TaintToleration{nodeLister: algorithm.FakeNodeLister(api.NodeList{Items: test.nodes})} list, err := taintToleration.ComputeTaintTolerationPriority( test.pod, nodeNameToInfo, algorithm.FakeNodeLister(api.NodeList{Items: test.nodes})) if err != nil { t.Errorf("%s, unexpected error: %v", test.test, err) } if !reflect.DeepEqual(test.expectedList, list) { t.Errorf("%s,\nexpected:\n\t%+v,\ngot:\n\t%+v", test.test, test.expectedList, list) } } }