// Filters the minions to find the ones that fit based on the given predicate functions // Each minion is passed through the predicate functions to determine if it is a fit func findNodesThatFit(pod *api.Pod, podLister algorithm.PodLister, predicateFuncs map[string]algorithm.FitPredicate, nodes api.NodeList) (api.NodeList, FailedPredicateMap, error) { filtered := []api.Node{} machineToPods, err := predicates.MapPodsToMachines(podLister) failedPredicateMap := FailedPredicateMap{} if err != nil { return api.NodeList{}, FailedPredicateMap{}, err } for _, node := range nodes.Items { fits := true for name, predicate := range predicateFuncs { fit, err := predicate(pod, machineToPods[node.Name], node.Name) if err != nil { return api.NodeList{}, FailedPredicateMap{}, err } if !fit { fits = false if _, found := failedPredicateMap[node.Name]; !found { failedPredicateMap[node.Name] = util.StringSet{} } failedPredicateMap[node.Name].Insert(name) break } } if fits { filtered = append(filtered, node) } } return api.NodeList{Items: filtered}, failedPredicateMap, nil }
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) }
// BalancedResourceAllocation favors nodes with balanced resource usage rate. // BalancedResourceAllocation should **NOT** be used alone, and **MUST** be used together with LeastRequestedPriority. // It calculates the difference between the cpu and memory fracion of capacity, and prioritizes the host based on how // close the two metrics are to each other. // Detail: score = 10 - abs(cpuFraction-memoryFraction)*10. The algorithm is partly inspired by: // "Wei Huang et al. An Energy Efficient Virtual Machine Placement Algorithm with Balanced Resource Utilization" func BalancedResourceAllocation(pod *api.Pod, podLister algorithm.PodLister, minionLister algorithm.MinionLister) (algorithm.HostPriorityList, error) { nodes, err := minionLister.List() if err != nil { return algorithm.HostPriorityList{}, err } podsToMachines, err := predicates.MapPodsToMachines(podLister) list := algorithm.HostPriorityList{} for _, node := range nodes.Items { list = append(list, calculateBalancedResourceAllocation(pod, node, podsToMachines[node.Name])) } return list, nil }
// LeastRequestedPriority is a priority function that favors nodes with fewer requested resources. // It calculates the percentage of memory and CPU requested by pods scheduled on the node, and prioritizes // based on the minimum of the average of the fraction of requested to capacity. // Details: cpu((capacity - sum(requested)) * 10 / capacity) + memory((capacity - sum(requested)) * 10 / capacity) / 2 func LeastRequestedPriority(pod *api.Pod, podLister algorithm.PodLister, nodeLister algorithm.NodeLister) (algorithm.HostPriorityList, error) { nodes, err := nodeLister.List() if err != nil { return algorithm.HostPriorityList{}, err } podsToMachines, err := predicates.MapPodsToMachines(podLister) list := algorithm.HostPriorityList{} for _, node := range nodes.Items { list = append(list, calculateResourceOccupancy(pod, node, podsToMachines[node.Name])) } return list, nil }
// Filters the nodes to find the ones that fit based on the given predicate functions // Each node is passed through the predicate functions to determine if it is a fit func findNodesThatFit(pod *api.Pod, podLister algorithm.PodLister, predicateFuncs map[string]algorithm.FitPredicate, nodes api.NodeList, extenders []algorithm.SchedulerExtender) (api.NodeList, FailedPredicateMap, error) { filtered := []api.Node{} machineToPods, err := predicates.MapPodsToMachines(podLister) failedPredicateMap := FailedPredicateMap{} if err != nil { return api.NodeList{}, FailedPredicateMap{}, err } for _, node := range nodes.Items { fits := true for name, predicate := range predicateFuncs { predicates.FailedResourceType = "" fit, err := predicate(pod, machineToPods[node.Name], node.Name) if err != nil { return api.NodeList{}, FailedPredicateMap{}, err } if !fit { fits = false if _, found := failedPredicateMap[node.Name]; !found { failedPredicateMap[node.Name] = sets.String{} } if predicates.FailedResourceType != "" { failedPredicateMap[node.Name].Insert(predicates.FailedResourceType) break } failedPredicateMap[node.Name].Insert(name) break } } if fits { filtered = append(filtered, node) } } if len(filtered) > 0 && len(extenders) != 0 { for _, extender := range extenders { filteredList, err := extender.Filter(pod, &api.NodeList{Items: filtered}) if err != nil { return api.NodeList{}, FailedPredicateMap{}, err } filtered = filteredList.Items if len(filtered) == 0 { break } } } return api.NodeList{Items: filtered}, failedPredicateMap, nil }
func CollateTotals(nodes api.NodeList, listner algorithm.PodLister) { // for _,e := range nodes.Items { // fmt.Println("Node Name :: ",e.Name) // fmt.Println("Node Status :: ",e.Status.Capacity) // fmt.Println("Number of Pods",e.Status.Capacity.Pods()) // } mapping, _ := predicates.MapPodsToMachines(listner) for k, v := range mapping { glog.Infof("Node Name :: %s", k) totalCpu := int64(0) totalMem := int64(0) for _, pod := range v { for _, container := range pod.Spec.Containers { requests := container.Resources.Requests totalMem += requests.Memory().Value() totalCpu += requests.Cpu().MilliValue() } } glog.Infof("Node CPU:: %s", totalCpu) glog.Infof("Node Mem:: %s", totalMem) } }
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 { m2p, err := predicates.MapPodsToMachines(algorithm.FakePodLister(test.pods)) if err != nil { t.Errorf("unexpected error: %v", err) } list, err := BalancedResourceAllocation(test.pod, m2p, algorithm.FakePodLister(test.pods), 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 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(defaultMilliCpuRequest, 10) + "m"), "memory": resource.MustParse( strconv.FormatInt(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(defaultMilliCpuRequest*3, 10) + "m"), "memory": resource.MustParse( strconv.FormatInt(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, defaultMemoryRequest*10), makeNode("machine2", 1000, 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, defaultMemoryRequest*10), makeNode("machine2", 1000, 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, defaultMemoryRequest*10), makeNode("machine2", 1000, 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 { m2p, err := predicates.MapPodsToMachines(algorithm.FakePodLister(test.pods)) if err != nil { t.Errorf("unexpected error: %v", err) } list, err := scheduler.PrioritizeNodes( test.pod, m2p, algorithm.FakePodLister(test.pods), // 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. []algorithm.PriorityConfig{{Function: LeastRequestedPriority, Weight: 1}, {Function: BalancedResourceAllocation, Weight: 1}, {Function: NewSelectorSpreadPriority(algorithm.FakeServiceLister([]api.Service{}), algorithm.FakeControllerLister([]api.ReplicationController{})), Weight: 1}}, algorithm.FakeNodeLister(api.NodeList{Items: 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 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 { m2p, err := predicates.MapPodsToMachines(algorithm.FakePodLister(test.pods)) if err != nil { t.Errorf("unexpected error: %v", err) } list, err := LeastRequestedPriority(test.pod, m2p, algorithm.FakePodLister(test.pods), 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 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{ { RepoTags: []string{ "gcr.io/40", "gcr.io/40:v1", "gcr.io/40:v1", }, Size: int64(40 * mb), }, { RepoTags: []string{ "gcr.io/140", "gcr.io/140:v1", }, Size: int64(140 * mb), }, { RepoTags: []string{ "gcr.io/2000", }, Size: int64(2000 * mb), }, }, } node_250_10 := api.NodeStatus{ Images: []api.ContainerImage{ { RepoTags: []string{ "gcr.io/250", }, Size: int64(250 * mb), }, { RepoTags: []string{ "gcr.io/10", "gcr.io/10:v1", }, Size: 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 { m2p, err := predicates.MapPodsToMachines(algorithm.FakePodLister(test.pods)) if err != nil { t.Errorf("unexpected error: %v", err) } list, err := ImageLocalityPriority(test.pod, m2p, algorithm.FakePodLister(test.pods), 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 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 { m2p, err := predicates.MapPodsToMachines(algorithm.FakePodLister(test.pods)) if err != nil { t.Errorf("unexpected error: %v", err) } zoneSpread := ServiceAntiAffinity{serviceLister: algorithm.FakeServiceLister(test.services), label: "zone"} list, err := zoneSpread.CalculateAntiAffinityPriority(test.pod, m2p, algorithm.FakePodLister(test.pods), 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 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: 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: 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"}, 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", }, } for _, test := range tests { m2p, err := predicates.MapPodsToMachines(algorithm.FakePodLister(test.pods)) if err != nil { t.Errorf("unexpected error: %v", err) } selectorSpread := SelectorSpread{serviceLister: algorithm.FakeServiceLister(test.services), controllerLister: algorithm.FakeControllerLister(test.rcs)} list, err := selectorSpread.CalculateSpreadPriority(test.pod, m2p, algorithm.FakePodLister(test.pods), 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 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 { selectorSpread := SelectorSpread{serviceLister: algorithm.FakeServiceLister(test.services), controllerLister: algorithm.FakeControllerLister(test.rcs)} m2p, err := predicates.MapPodsToMachines(algorithm.FakePodLister(test.pods)) if err != nil { t.Errorf("unexpected error: %v", err) } list, err := selectorSpread.CalculateSpreadPriority(test.pod, m2p, algorithm.FakePodLister(test.pods), 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) } } }