// FindNodeToRemove finds a node that can be removed. func FindNodeToRemove(nodes []*kube_api.Node, pods []*kube_api.Pod, client *kube_client.Client) (*kube_api.Node, error) { nodeNameToNodeInfo := schedulercache.CreateNodeNameToInfoMap(pods) //TODO: Interate over underutulized nodes first. for _, node := range nodes { nodeInfo, found := nodeNameToNodeInfo[node.Name] if !found { glog.Errorf("Node info for %s not found", node.Name) continue } reservation, err := calculateReservation(node, nodeInfo) if err != nil { glog.Warningf("Failed to calculate reservation for %s: %v", node.Name, err) } glog.V(4).Infof("Node %s - reservation %f", node.Name, reservation) if reservation > unusedThreshold { glog.Infof("Node %s is not suitable for removal - reservation to big (%f)", node.Name, reservation) continue } // Let's try to remove this one. glog.V(2).Infof("Considering %s for removal", node.Name) podsToRemoveList, _, _, err := cmd.GetPodsForDeletionOnNodeDrain(client, node.Name, kube_api.Codecs.UniversalDecoder(), false, true) if err != nil { glog.V(1).Infof("Node %s cannot be removed: %v", node.Name, err) continue } tempNodeNameToNodeInfo := schedulercache.CreateNodeNameToInfoMap(pods) delete(tempNodeNameToNodeInfo, node.Name) for _, node := range nodes { if nodeInfo, found := tempNodeNameToNodeInfo[node.Name]; found { nodeInfo.SetNode(node) } } ptrPodsToRemove := make([]*kube_api.Pod, 0, len(podsToRemoveList)) for i := range podsToRemoveList { ptrPodsToRemove = append(ptrPodsToRemove, &podsToRemoveList[i]) } findProblems := findPlaceFor(ptrPodsToRemove, nodes, tempNodeNameToNodeInfo) if findProblems == nil { return node, nil } glog.Infof("Node %s is not suitable for removal %v", node.Name, err) } return nil, nil }
// 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 } // 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 pods, err := g.pods.List(labels.Everything()) if err != nil { return "", err } nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap(pods) 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.pods, g.prioritizers, algorithm.FakeNodeLister(filteredNodes), g.extenders) if err != nil { return "", err } return g.selectHost(priorityList) }
// FindNodesToRemove finds nodes that can be removed. Returns also an information about good // rescheduling location for each of the pods. func FindNodesToRemove(candidates []*kube_api.Node, allNodes []*kube_api.Node, pods []*kube_api.Pod, client *kube_client.Client, predicateChecker *PredicateChecker, maxCount int, fastCheck bool, oldHints map[string]string, usageTracker *UsageTracker, timestamp time.Time) (nodesToRemove []NodeToBeRemoved, podReschedulingHints map[string]string, finalError error) { nodeNameToNodeInfo := schedulercache.CreateNodeNameToInfoMap(pods) for _, node := range allNodes { if nodeInfo, found := nodeNameToNodeInfo[node.Name]; found { nodeInfo.SetNode(node) } } result := make([]NodeToBeRemoved, 0) evaluationType := "Detailed evaluation" if fastCheck { evaluationType = "Fast evaluation" } newHints := make(map[string]string, len(oldHints)) candidateloop: for _, node := range candidates { glog.V(2).Infof("%s: %s for removal", evaluationType, node.Name) var podsToRemove []*kube_api.Pod var err error if nodeInfo, found := nodeNameToNodeInfo[node.Name]; found { if fastCheck { podsToRemove, err = FastGetPodsToMove(nodeInfo, *skipNodesWithSystemPods, *skipNodesWithLocalStorage) } else { podsToRemove, err = DetailedGetPodsForMove(nodeInfo, *skipNodesWithSystemPods, *skipNodesWithLocalStorage, client, int32(*minReplicaCount)) } if err != nil { glog.V(2).Infof("%s: node %s cannot be removed: %v", evaluationType, node.Name, err) continue candidateloop } } else { glog.V(2).Infof("%s: nodeInfo for %s not found", evaluationType, node.Name) continue candidateloop } findProblems := findPlaceFor(node.Name, podsToRemove, allNodes, nodeNameToNodeInfo, predicateChecker, oldHints, newHints, usageTracker, timestamp) if findProblems == nil { result = append(result, NodeToBeRemoved{ Node: node, PodsToReschedule: podsToRemove, }) glog.V(2).Infof("%s: node %s may be removed", evaluationType, node.Name) if len(result) >= maxCount { break candidateloop } } else { glog.V(2).Infof("%s: node %s is not suitable for removal %v", evaluationType, node.Name, err) } } return result, newHints, nil }
// FindUnneededNodes calculates which nodes are not needed, i.e. all pods can be scheduled somewhere else, // and updates unneededNodes map accordingly. It also returns information where pods can be rescheduld and // node utilization level. func FindUnneededNodes( context AutoscalingContext, nodes []*apiv1.Node, unneededNodes map[string]time.Time, pods []*apiv1.Pod, oldHints map[string]string, tracker *simulator.UsageTracker, timestamp time.Time) (unnededTimeMap map[string]time.Time, podReschedulingHints map[string]string, utilizationMap map[string]float64) { currentlyUnneededNodes := make([]*apiv1.Node, 0) nodeNameToNodeInfo := schedulercache.CreateNodeNameToInfoMap(pods, nodes) utilizationMap = make(map[string]float64) // Phase1 - look at the nodes utilization. for _, node := range nodes { nodeInfo, found := nodeNameToNodeInfo[node.Name] if !found { glog.Errorf("Node info for %s not found", node.Name) continue } utilization, err := simulator.CalculateUtilization(node, nodeInfo) if err != nil { glog.Warningf("Failed to calculate utilization for %s: %v", node.Name, err) } glog.V(4).Infof("Node %s - utilization %f", node.Name, utilization) utilizationMap[node.Name] = utilization if utilization >= context.ScaleDownUtilizationThreshold { glog.V(4).Infof("Node %s is not suitable for removal - utilization too big (%f)", node.Name, utilization) continue } currentlyUnneededNodes = append(currentlyUnneededNodes, node) } // Phase2 - check which nodes can be probably removed using fast drain. nodesToRemove, newHints, err := simulator.FindNodesToRemove(currentlyUnneededNodes, nodes, pods, nil, context.PredicateChecker, len(currentlyUnneededNodes), true, oldHints, tracker, timestamp) if err != nil { glog.Errorf("Error while simulating node drains: %v", err) return map[string]time.Time{}, oldHints, map[string]float64{} } // Update the timestamp map. now := time.Now() result := make(map[string]time.Time) for _, node := range nodesToRemove { name := node.Node.Name if val, found := unneededNodes[name]; !found { result[name] = now } else { result[name] = val } } return result, newHints, utilizationMap }
// TODO: move this function to scheduler utils. func createNodeNameToInfoMap(pods []*kube_api.Pod, nodes []*kube_api.Node) map[string]*schedulercache.NodeInfo { nodeNameToNodeInfo := schedulercache.CreateNodeNameToInfoMap(pods) for _, node := range nodes { if nodeInfo, found := nodeNameToNodeInfo[node.Name]; found { nodeInfo.SetNode(node) } } return nodeNameToNodeInfo }
// CalculateUnderutilizedNodes calculates which nodes are underutilized. func CalculateUnderutilizedNodes(nodes []*kube_api.Node, underutilizedNodes map[string]time.Time, utilizationThreshold float64, pods []*kube_api.Pod, client *kube_client.Client, predicateChecker *simulator.PredicateChecker) map[string]time.Time { currentlyUnderutilizedNodes := make([]*kube_api.Node, 0) nodeNameToNodeInfo := schedulercache.CreateNodeNameToInfoMap(pods) // Phase1 - look at the nodes reservation. for _, node := range nodes { nodeInfo, found := nodeNameToNodeInfo[node.Name] if !found { glog.Errorf("Node info for %s not found", node.Name) continue } reservation, err := simulator.CalculateReservation(node, nodeInfo) if err != nil { glog.Warningf("Failed to calculate reservation for %s: %v", node.Name, err) } glog.V(4).Infof("Node %s - reservation %f", node.Name, reservation) if reservation >= utilizationThreshold { glog.V(4).Infof("Node %s is not suitable for removal - reservation to big (%f)", node.Name, reservation) continue } currentlyUnderutilizedNodes = append(currentlyUnderutilizedNodes, node) } // Phase2 - check which nodes can be probably removed using fast drain. nodesToRemove, err := simulator.FindNodesToRemove(currentlyUnderutilizedNodes, nodes, pods, client, predicateChecker, len(currentlyUnderutilizedNodes), true) if err != nil { glog.Errorf("Error while evaluating node utilization: %v", err) return map[string]time.Time{} } // Update the timestamp map. now := time.Now() result := make(map[string]time.Time) for _, node := range nodesToRemove { name := node.Name if val, found := underutilizedNodes[name]; !found { result[name] = now } else { result[name] = val } } return result }
// FindNodesToRemove finds nodes that can be removed. func FindNodesToRemove(candidates []*kube_api.Node, allNodes []*kube_api.Node, pods []*kube_api.Pod, client *kube_client.Client, predicateChecker *PredicateChecker, maxCount int, fastCheck bool) ([]*kube_api.Node, error) { nodeNameToNodeInfo := schedulercache.CreateNodeNameToInfoMap(pods) for _, node := range allNodes { if nodeInfo, found := nodeNameToNodeInfo[node.Name]; found { nodeInfo.SetNode(node) } } result := make([]*kube_api.Node, 0) candidateloop: for _, node := range candidates { glog.V(2).Infof("Considering %s for removal", node.Name) var podsToRemove []*kube_api.Pod var err error if fastCheck { if nodeInfo, found := nodeNameToNodeInfo[node.Name]; found { podsToRemove, err = FastGetPodsToMove(nodeInfo, false, true, kube_api.Codecs.UniversalDecoder()) } } else { drainResult, _, _, err := cmd.GetPodsForDeletionOnNodeDrain(client, node.Name, kube_api.Codecs.UniversalDecoder(), false, true) if err != nil { glog.V(2).Infof("Node %s cannot be removed: %v", node.Name, err) continue } podsToRemove = make([]*kube_api.Pod, 0, len(drainResult)) for i := range drainResult { podsToRemove = append(podsToRemove, &drainResult[i]) } } findProblems := findPlaceFor(node.Name, podsToRemove, allNodes, nodeNameToNodeInfo, predicateChecker) if findProblems == nil { result = append(result, node) if len(result) >= maxCount { break candidateloop } } else { glog.V(2).Infof("Node %s is not suitable for removal %v", node.Name, err) } } return result, nil }
// TODO: move this function to scheduler utils. func createNodeNameToInfoMap(pods []*kube_api.Pod, nodes []*kube_api.Node) map[string]*schedulercache.NodeInfo { nodeNameToNodeInfo := schedulercache.CreateNodeNameToInfoMap(pods) for _, node := range nodes { if nodeInfo, found := nodeNameToNodeInfo[node.Name]; found { nodeInfo.SetNode(node) } } // Some pods may be out of sync with node lists. Removing incomplete node infos. keysToRemove := make([]string, 0) for key, nodeInfo := range nodeNameToNodeInfo { if nodeInfo.Node() == nil { keysToRemove = append(keysToRemove, key) } } for _, key := range keysToRemove { delete(nodeNameToNodeInfo, key) } return nodeNameToNodeInfo }
// FindEmptyNodesToRemove finds empty nodes that can be removed. func FindEmptyNodesToRemove(candidates []*kube_api.Node, pods []*kube_api.Pod) []*kube_api.Node { nodeNameToNodeInfo := schedulercache.CreateNodeNameToInfoMap(pods) for _, node := range candidates { if nodeInfo, found := nodeNameToNodeInfo[node.Name]; found { nodeInfo.SetNode(node) } } result := make([]*kube_api.Node, 0) for _, node := range candidates { if nodeInfo, found := nodeNameToNodeInfo[node.Name]; found { // Should block on all pods. podsToRemove, err := FastGetPodsToMove(nodeInfo, true, true) if err == nil && len(podsToRemove) == 0 { result = append(result, node) } } else { // Node without pods. result = append(result, node) } } return result }
func (p PodsToCache) UpdateNodeNameToInfoMap(infoMap map[string]*schedulercache.NodeInfo) error { infoMap = schedulercache.CreateNodeNameToInfoMap(p) return nil }
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 := &v1.Affinity{ NodeAffinity: &v1.NodeAffinity{ PreferredDuringSchedulingIgnoredDuringExecution: []v1.PreferredSchedulingTerm{{ Weight: 2, Preference: v1.NodeSelectorTerm{ MatchExpressions: []v1.NodeSelectorRequirement{{ Key: "foo", Operator: v1.NodeSelectorOpIn, Values: []string{"bar"}, }}, }, }}, }, } affinity2 := &v1.Affinity{ NodeAffinity: &v1.NodeAffinity{ PreferredDuringSchedulingIgnoredDuringExecution: []v1.PreferredSchedulingTerm{ { Weight: 2, Preference: v1.NodeSelectorTerm{ MatchExpressions: []v1.NodeSelectorRequirement{ { Key: "foo", Operator: v1.NodeSelectorOpIn, Values: []string{"bar"}, }, }, }, }, { Weight: 4, Preference: v1.NodeSelectorTerm{ MatchExpressions: []v1.NodeSelectorRequirement{ { Key: "key", Operator: v1.NodeSelectorOpIn, Values: []string{"value"}, }, }, }, }, { Weight: 5, Preference: v1.NodeSelectorTerm{ MatchExpressions: []v1.NodeSelectorRequirement{ { Key: "foo", Operator: v1.NodeSelectorOpIn, Values: []string{"bar"}, }, { Key: "key", Operator: v1.NodeSelectorOpIn, Values: []string{"value"}, }, { Key: "az", Operator: v1.NodeSelectorOpIn, Values: []string{"az1"}, }, }, }, }, }, }, } tests := []struct { pod *v1.Pod nodes []*v1.Node expectedList schedulerapi.HostPriorityList test string }{ { pod: &v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{}, }, }, nodes: []*v1.Node{ {ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: label1}}, {ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: label2}}, {ObjectMeta: metav1.ObjectMeta{Name: "machine3", Labels: label3}}, }, expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 0}, {Host: "machine2", Score: 0}, {Host: "machine3", Score: 0}}, test: "all machines are same priority as NodeAffinity is nil", }, { pod: &v1.Pod{ Spec: v1.PodSpec{ Affinity: affinity1, }, }, nodes: []*v1.Node{ {ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: label4}}, {ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: label2}}, {ObjectMeta: metav1.ObjectMeta{Name: "machine3", Labels: label3}}, }, expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 0}, {Host: "machine2", Score: 0}, {Host: "machine3", Score: 0}}, test: "no machine macthes preferred scheduling requirements in NodeAffinity of pod so all machines' priority is zero", }, { pod: &v1.Pod{ Spec: v1.PodSpec{ Affinity: affinity1, }, }, nodes: []*v1.Node{ {ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: label1}}, {ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: label2}}, {ObjectMeta: metav1.ObjectMeta{Name: "machine3", Labels: label3}}, }, expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 10}, {Host: "machine2", Score: 0}, {Host: "machine3", Score: 0}}, test: "only machine1 matches the preferred scheduling requirements of pod", }, { pod: &v1.Pod{ Spec: v1.PodSpec{ Affinity: affinity2, }, }, nodes: []*v1.Node{ {ObjectMeta: metav1.ObjectMeta{Name: "machine1", Labels: label1}}, {ObjectMeta: metav1.ObjectMeta{Name: "machine5", Labels: label5}}, {ObjectMeta: metav1.ObjectMeta{Name: "machine2", Labels: label2}}, }, expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 1}, {Host: "machine5", Score: 10}, {Host: "machine2", Score: 3}}, test: "all machines matches the preferred scheduling requirements of pod but with different priorities ", }, } for _, test := range tests { nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap(nil, test.nodes) nap := priorityFunction(CalculateNodeAffinityPriorityMap, CalculateNodeAffinityPriorityReduce) list, err := nap(test.pod, nodeNameToInfo, 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 TestMostRequested(t *testing.T) { labels1 := map[string]string{ "foo": "bar", "baz": "blah", } labels2 := map[string]string{ "bar": "foo", "baz": "blah", } 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 (used resources) on 0-10 scale CPU Score: (0 * 10 / 4000 = 0 Memory Score: (0 * 10) / 10000 = 0 Node1 Score: (0 + 0) / 2 = 0 Node2 scores (used resources) on 0-10 scale CPU Score: (0 * 10 / 4000 = 0 Memory Score: (0 * 10 / 10000 = 0 Node2 Score: (0 + 0) / 2 = 0 */ pod: &api.Pod{Spec: noResources}, nodes: []*api.Node{makeNode("machine1", 4000, 10000), makeNode("machine2", 4000, 10000)}, expectedList: []schedulerapi.HostPriority{{"machine1", 0}, {"machine2", 0}}, test: "nothing scheduled, nothing requested", }, { /* Node1 scores on 0-10 scale CPU Score: (3000 * 10 / 4000 = 7.5 Memory Score: (5000 * 10) / 10000 = 5 Node1 Score: (7.5 + 5) / 2 = 6 Node2 scores on 0-10 scale CPU Score: (3000 * 10 / 6000 = 5 Memory Score: (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", 6}, {"machine2", 5}}, test: "nothing scheduled, resources requested, differently sized machines", }, { /* Node1 scores on 0-10 scale CPU Score: (6000 * 10) / 10000 = 6 Memory Score: (0 * 10) / 20000 = 10 Node1 Score: (6 + 0) / 2 = 3 Node2 scores on 0-10 scale CPU Score: (6000 * 10) / 10000 = 6 Memory Score: (5000 * 10) / 20000 = 2.5 Node2 Score: (6 + 2.5) / 2 = 4 */ pod: &api.Pod{Spec: noResources}, nodes: []*api.Node{makeNode("machine1", 10000, 20000), makeNode("machine2", 10000, 20000)}, expectedList: []schedulerapi.HostPriority{{"machine1", 3}, {"machine2", 4}}, 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: (6000 * 10) / 10000 = 6 Memory Score: (5000 * 10) / 20000 = 2.5 Node1 Score: (6 + 2.5) / 2 = 4 Node2 scores on 0-10 scale CPU Score: (6000 * 10) / 10000 = 6 Memory Score: (10000 * 10) / 20000 = 5 Node2 Score: (6 + 5) / 2 = 5 */ pod: &api.Pod{Spec: cpuAndMemory}, nodes: []*api.Node{makeNode("machine1", 10000, 20000), makeNode("machine2", 10000, 20000)}, expectedList: []schedulerapi.HostPriority{{"machine1", 4}, {"machine2", 5}}, test: "resources requested, pods scheduled with resources", pods: []*api.Pod{ {Spec: cpuOnly}, {Spec: cpuAndMemory}, }, }, } for _, test := range tests { nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap(test.pods, test.nodes) list, err := priorityFunction(MostRequestedPriorityMap, nil)(test.pod, nodeNameToInfo, 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{ { 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 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 (p PodsToCache) GetNodeNameToInfoMap() (map[string]*schedulercache.NodeInfo, error) { return schedulercache.CreateNodeNameToInfoMap(p), nil }
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 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 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 { list, err := CalculateNodeAffinityPriority(test.pod, schedulercache.CreateNodeNameToInfoMap(nil, test.nodes), 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 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 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 []*v1.Node label string presence bool expectedList schedulerapi.HostPriorityList test string }{ { nodes: []*v1.Node{ {ObjectMeta: v1.ObjectMeta{Name: "machine1", Labels: label1}}, {ObjectMeta: v1.ObjectMeta{Name: "machine2", Labels: label2}}, {ObjectMeta: v1.ObjectMeta{Name: "machine3", Labels: label3}}, }, expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 0}, {Host: "machine2", Score: 0}, {Host: "machine3", Score: 0}}, label: "baz", presence: true, test: "no match found, presence true", }, { nodes: []*v1.Node{ {ObjectMeta: v1.ObjectMeta{Name: "machine1", Labels: label1}}, {ObjectMeta: v1.ObjectMeta{Name: "machine2", Labels: label2}}, {ObjectMeta: v1.ObjectMeta{Name: "machine3", Labels: label3}}, }, expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 10}, {Host: "machine2", Score: 10}, {Host: "machine3", Score: 10}}, label: "baz", presence: false, test: "no match found, presence false", }, { nodes: []*v1.Node{ {ObjectMeta: v1.ObjectMeta{Name: "machine1", Labels: label1}}, {ObjectMeta: v1.ObjectMeta{Name: "machine2", Labels: label2}}, {ObjectMeta: v1.ObjectMeta{Name: "machine3", Labels: label3}}, }, expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 10}, {Host: "machine2", Score: 0}, {Host: "machine3", Score: 0}}, label: "foo", presence: true, test: "one match found, presence true", }, { nodes: []*v1.Node{ {ObjectMeta: v1.ObjectMeta{Name: "machine1", Labels: label1}}, {ObjectMeta: v1.ObjectMeta{Name: "machine2", Labels: label2}}, {ObjectMeta: v1.ObjectMeta{Name: "machine3", Labels: label3}}, }, expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 0}, {Host: "machine2", Score: 10}, {Host: "machine3", Score: 10}}, label: "foo", presence: false, test: "one match found, presence false", }, { nodes: []*v1.Node{ {ObjectMeta: v1.ObjectMeta{Name: "machine1", Labels: label1}}, {ObjectMeta: v1.ObjectMeta{Name: "machine2", Labels: label2}}, {ObjectMeta: v1.ObjectMeta{Name: "machine3", Labels: label3}}, }, expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 0}, {Host: "machine2", Score: 10}, {Host: "machine3", Score: 10}}, label: "bar", presence: true, test: "two matches found, presence true", }, { nodes: []*v1.Node{ {ObjectMeta: v1.ObjectMeta{Name: "machine1", Labels: label1}}, {ObjectMeta: v1.ObjectMeta{Name: "machine2", Labels: label2}}, {ObjectMeta: v1.ObjectMeta{Name: "machine3", Labels: label3}}, }, expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 10}, {Host: "machine2", Score: 0}, {Host: "machine3", Score: 0}}, label: "bar", presence: false, test: "two matches found, presence false", }, } for _, test := range tests { nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap(nil, test.nodes) list, err := priorityFunction(NewNodeLabelPriority(test.label, test.presence))(nil, nodeNameToInfo, 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 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 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(nil, nil) list, err := ComputeTaintTolerationPriority(test.pod, nodeNameToInfo, 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) } } }
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 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 TestNodePreferAvoidPriority(t *testing.T) { annotations1 := map[string]string{ api.PreferAvoidPodsAnnotationKey: ` { "preferAvoidPods": [ { "podSignature": { "podController": { "apiVersion": "v1", "kind": "ReplicationController", "name": "foo", "uid": "abcdef123456", "controller": true } }, "reason": "some reason", "message": "some message" } ] }`, } annotations2 := map[string]string{ api.PreferAvoidPodsAnnotationKey: ` { "preferAvoidPods": [ { "podSignature": { "podController": { "apiVersion": "v1", "kind": "ReplicaSet", "name": "foo", "uid": "qwert12345", "controller": true } }, "reason": "some reason", "message": "some message" } ] }`, } testNodes := []*api.Node{ { ObjectMeta: api.ObjectMeta{Name: "machine1", Annotations: annotations1}, }, { ObjectMeta: api.ObjectMeta{Name: "machine2", Annotations: annotations2}, }, { ObjectMeta: api.ObjectMeta{Name: "machine3"}, }, } trueVar := true tests := []struct { pod *api.Pod nodes []*api.Node expectedList schedulerapi.HostPriorityList test string }{ { pod: &api.Pod{ ObjectMeta: api.ObjectMeta{ Namespace: "default", OwnerReferences: []api.OwnerReference{ {Kind: "ReplicationController", Name: "foo", UID: "abcdef123456", Controller: &trueVar}, }, }, }, nodes: testNodes, expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 0}, {Host: "machine2", Score: 10}, {Host: "machine3", Score: 10}}, test: "pod managed by ReplicationController should avoid a node, this node get lowest priority score", }, { pod: &api.Pod{ ObjectMeta: api.ObjectMeta{ Namespace: "default", OwnerReferences: []api.OwnerReference{ {Kind: "RandomController", Name: "foo", UID: "abcdef123456", Controller: &trueVar}, }, }, }, nodes: testNodes, expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 10}, {Host: "machine2", Score: 10}, {Host: "machine3", Score: 10}}, test: "ownership by random controller should be ignored", }, { pod: &api.Pod{ ObjectMeta: api.ObjectMeta{ Namespace: "default", OwnerReferences: []api.OwnerReference{ {Kind: "ReplicationController", Name: "foo", UID: "abcdef123456"}, }, }, }, nodes: testNodes, expectedList: []schedulerapi.HostPriority{{Host: "machine1", Score: 10}, {Host: "machine2", Score: 10}, {Host: "machine3", Score: 10}}, test: "owner without Controller field set should be ignored", }, { pod: &api.Pod{ ObjectMeta: api.ObjectMeta{ Namespace: "default", OwnerReferences: []api.OwnerReference{ {Kind: "ReplicaSet", Name: "foo", UID: "qwert12345", Controller: &trueVar}, }, }, }, nodes: testNodes, expectedList: []schedulerapi.HostPriority{{"machine1", 10}, {"machine2", 0}, {"machine3", 10}}, test: "pod managed by ReplicaSet should avoid a node, this node get lowest priority score", }, } for _, test := range tests { nodeNameToInfo := schedulercache.CreateNodeNameToInfoMap(nil, test.nodes) list, err := priorityFunction(CalculateNodePreferAvoidPodsPriorityMap, nil)(test.pod, nodeNameToInfo, 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) } } }
// 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) } } } } }
// FindNodesToRemove finds nodes that can be removed. Returns also an information about good // rescheduling location for each of the pods. func FindNodesToRemove(candidates []*kube_api.Node, allNodes []*kube_api.Node, pods []*kube_api.Pod, client *kube_client.Client, predicateChecker *PredicateChecker, maxCount int, fastCheck bool, oldHints map[string]string, usageTracker *UsageTracker, timestamp time.Time) (nodesToRemove []*kube_api.Node, podReschedulingHints map[string]string, finalError error) { nodeNameToNodeInfo := schedulercache.CreateNodeNameToInfoMap(pods) for _, node := range allNodes { if nodeInfo, found := nodeNameToNodeInfo[node.Name]; found { nodeInfo.SetNode(node) } } result := make([]*kube_api.Node, 0) evaluationType := "Detailed evaluation" if fastCheck { evaluationType = "Fast evaluation" } newHints := make(map[string]string, len(oldHints)) candidateloop: for _, node := range candidates { glog.V(2).Infof("%s: %s for removal", evaluationType, node.Name) var podsToRemove []*kube_api.Pod var err error if fastCheck { if nodeInfo, found := nodeNameToNodeInfo[node.Name]; found { podsToRemove, err = FastGetPodsToMove(nodeInfo, false, *skipNodesWithSystemPods, *skipNodesWithLocalStorage, kube_api.Codecs.UniversalDecoder()) if err != nil { glog.V(2).Infof("%s: node %s cannot be removed: %v", evaluationType, node.Name, err) continue candidateloop } } else { glog.V(2).Infof("%s: nodeInfo for %s not found", evaluationType, node.Name) continue candidateloop } } else { drainResult, _, _, err := cmd.GetPodsForDeletionOnNodeDrain(client, node.Name, kube_api.Codecs.UniversalDecoder(), false, true) if err != nil { glog.V(2).Infof("%s: node %s cannot be removed: %v", evaluationType, node.Name, err) continue candidateloop } podsToRemove = make([]*kube_api.Pod, 0, len(drainResult)) for i := range drainResult { podsToRemove = append(podsToRemove, &drainResult[i]) } } findProblems := findPlaceFor(node.Name, podsToRemove, allNodes, nodeNameToNodeInfo, predicateChecker, oldHints, newHints, usageTracker, timestamp) if findProblems == nil { result = append(result, node) glog.V(2).Infof("%s: node %s may be removed", evaluationType, node.Name) if len(result) >= maxCount { break candidateloop } } else { glog.V(2).Infof("%s: node %s is not suitable for removal %v", evaluationType, node.Name, err) } } return result, newHints, nil }