Unverified Commit 65f87b5a authored by Kubernetes Prow Robot's avatar Kubernetes Prow Robot Committed by GitHub

Merge pull request #72259 from bsalamat/fix_nominated_node

Fix a race in setting nominated node and the scheduling cycle after it.
parents 57c80240 b75672c4
...@@ -362,7 +362,7 @@ func (g *genericScheduler) processPreemptionWithExtenders( ...@@ -362,7 +362,7 @@ func (g *genericScheduler) processPreemptionWithExtenders(
// worth the complexity, especially because we generally expect to have a very // worth the complexity, especially because we generally expect to have a very
// small number of nominated pods per node. // small number of nominated pods per node.
func (g *genericScheduler) getLowerPriorityNominatedPods(pod *v1.Pod, nodeName string) []*v1.Pod { func (g *genericScheduler) getLowerPriorityNominatedPods(pod *v1.Pod, nodeName string) []*v1.Pod {
pods := g.schedulingQueue.WaitingPodsForNode(nodeName) pods := g.schedulingQueue.NominatedPodsForNode(nodeName)
if len(pods) == 0 { if len(pods) == 0 {
return nil return nil
...@@ -509,7 +509,7 @@ func addNominatedPods(pod *v1.Pod, meta predicates.PredicateMetadata, ...@@ -509,7 +509,7 @@ func addNominatedPods(pod *v1.Pod, meta predicates.PredicateMetadata,
// This may happen only in tests. // This may happen only in tests.
return false, meta, nodeInfo return false, meta, nodeInfo
} }
nominatedPods := queue.WaitingPodsForNode(nodeInfo.Node().Name) nominatedPods := queue.NominatedPodsForNode(nodeInfo.Node().Name)
if nominatedPods == nil || len(nominatedPods) == 0 { if nominatedPods == nil || len(nominatedPods) == 0 {
return false, meta, nodeInfo return false, meta, nodeInfo
} }
......
...@@ -105,8 +105,14 @@ func TestPriorityQueue_Add(t *testing.T) { ...@@ -105,8 +105,14 @@ func TestPriorityQueue_Add(t *testing.T) {
if err := q.Add(&highPriorityPod); err != nil { if err := q.Add(&highPriorityPod); err != nil {
t.Errorf("add failed: %v", err) t.Errorf("add failed: %v", err)
} }
expectedNominatedPods := map[string][]*v1.Pod{ expectedNominatedPods := &nominatedPodMap{
nominatedPodToNode: map[types.UID]string{
medPriorityPod.UID: "node1",
unschedulablePod.UID: "node1",
},
nominatedPods: map[string][]*v1.Pod{
"node1": {&medPriorityPod, &unschedulablePod}, "node1": {&medPriorityPod, &unschedulablePod},
},
} }
if !reflect.DeepEqual(q.nominatedPods, expectedNominatedPods) { if !reflect.DeepEqual(q.nominatedPods, expectedNominatedPods) {
t.Errorf("Unexpected nominated map after adding pods. Expected: %v, got: %v", expectedNominatedPods, q.nominatedPods) t.Errorf("Unexpected nominated map after adding pods. Expected: %v, got: %v", expectedNominatedPods, q.nominatedPods)
...@@ -120,8 +126,8 @@ func TestPriorityQueue_Add(t *testing.T) { ...@@ -120,8 +126,8 @@ func TestPriorityQueue_Add(t *testing.T) {
if p, err := q.Pop(); err != nil || p != &unschedulablePod { if p, err := q.Pop(); err != nil || p != &unschedulablePod {
t.Errorf("Expected: %v after Pop, but got: %v", unschedulablePod.Name, p.Name) t.Errorf("Expected: %v after Pop, but got: %v", unschedulablePod.Name, p.Name)
} }
if len(q.nominatedPods["node1"]) != 2 { if len(q.nominatedPods.nominatedPods["node1"]) != 2 {
t.Errorf("Expected medPriorityPod and unschedulablePod to be still present in nomindatePods: %v", q.nominatedPods["node1"]) t.Errorf("Expected medPriorityPod and unschedulablePod to be still present in nomindatePods: %v", q.nominatedPods.nominatedPods["node1"])
} }
} }
...@@ -131,8 +137,14 @@ func TestPriorityQueue_AddIfNotPresent(t *testing.T) { ...@@ -131,8 +137,14 @@ func TestPriorityQueue_AddIfNotPresent(t *testing.T) {
q.AddIfNotPresent(&highPriNominatedPod) // Must not add anything. q.AddIfNotPresent(&highPriNominatedPod) // Must not add anything.
q.AddIfNotPresent(&medPriorityPod) q.AddIfNotPresent(&medPriorityPod)
q.AddIfNotPresent(&unschedulablePod) q.AddIfNotPresent(&unschedulablePod)
expectedNominatedPods := map[string][]*v1.Pod{ expectedNominatedPods := &nominatedPodMap{
nominatedPodToNode: map[types.UID]string{
medPriorityPod.UID: "node1",
unschedulablePod.UID: "node1",
},
nominatedPods: map[string][]*v1.Pod{
"node1": {&medPriorityPod, &unschedulablePod}, "node1": {&medPriorityPod, &unschedulablePod},
},
} }
if !reflect.DeepEqual(q.nominatedPods, expectedNominatedPods) { if !reflect.DeepEqual(q.nominatedPods, expectedNominatedPods) {
t.Errorf("Unexpected nominated map after adding pods. Expected: %v, got: %v", expectedNominatedPods, q.nominatedPods) t.Errorf("Unexpected nominated map after adding pods. Expected: %v, got: %v", expectedNominatedPods, q.nominatedPods)
...@@ -143,8 +155,8 @@ func TestPriorityQueue_AddIfNotPresent(t *testing.T) { ...@@ -143,8 +155,8 @@ func TestPriorityQueue_AddIfNotPresent(t *testing.T) {
if p, err := q.Pop(); err != nil || p != &unschedulablePod { if p, err := q.Pop(); err != nil || p != &unschedulablePod {
t.Errorf("Expected: %v after Pop, but got: %v", unschedulablePod.Name, p.Name) t.Errorf("Expected: %v after Pop, but got: %v", unschedulablePod.Name, p.Name)
} }
if len(q.nominatedPods["node1"]) != 2 { if len(q.nominatedPods.nominatedPods["node1"]) != 2 {
t.Errorf("Expected medPriorityPod and unschedulablePod to be still present in nomindatePods: %v", q.nominatedPods["node1"]) t.Errorf("Expected medPriorityPod and unschedulablePod to be still present in nomindatePods: %v", q.nominatedPods.nominatedPods["node1"])
} }
if q.unschedulableQ.get(&highPriNominatedPod) != &highPriNominatedPod { if q.unschedulableQ.get(&highPriNominatedPod) != &highPriNominatedPod {
t.Errorf("Pod %v was not found in the unschedulableQ.", highPriNominatedPod.Name) t.Errorf("Pod %v was not found in the unschedulableQ.", highPriNominatedPod.Name)
...@@ -157,8 +169,15 @@ func TestPriorityQueue_AddUnschedulableIfNotPresent(t *testing.T) { ...@@ -157,8 +169,15 @@ func TestPriorityQueue_AddUnschedulableIfNotPresent(t *testing.T) {
q.AddUnschedulableIfNotPresent(&highPriNominatedPod) // Must not add anything. q.AddUnschedulableIfNotPresent(&highPriNominatedPod) // Must not add anything.
q.AddUnschedulableIfNotPresent(&medPriorityPod) // This should go to activeQ. q.AddUnschedulableIfNotPresent(&medPriorityPod) // This should go to activeQ.
q.AddUnschedulableIfNotPresent(&unschedulablePod) q.AddUnschedulableIfNotPresent(&unschedulablePod)
expectedNominatedPods := map[string][]*v1.Pod{ expectedNominatedPods := &nominatedPodMap{
nominatedPodToNode: map[types.UID]string{
medPriorityPod.UID: "node1",
unschedulablePod.UID: "node1",
highPriNominatedPod.UID: "node1",
},
nominatedPods: map[string][]*v1.Pod{
"node1": {&highPriNominatedPod, &medPriorityPod, &unschedulablePod}, "node1": {&highPriNominatedPod, &medPriorityPod, &unschedulablePod},
},
} }
if !reflect.DeepEqual(q.nominatedPods, expectedNominatedPods) { if !reflect.DeepEqual(q.nominatedPods, expectedNominatedPods) {
t.Errorf("Unexpected nominated map after adding pods. Expected: %v, got: %v", expectedNominatedPods, q.nominatedPods) t.Errorf("Unexpected nominated map after adding pods. Expected: %v, got: %v", expectedNominatedPods, q.nominatedPods)
...@@ -169,7 +188,7 @@ func TestPriorityQueue_AddUnschedulableIfNotPresent(t *testing.T) { ...@@ -169,7 +188,7 @@ func TestPriorityQueue_AddUnschedulableIfNotPresent(t *testing.T) {
if p, err := q.Pop(); err != nil || p != &medPriorityPod { if p, err := q.Pop(); err != nil || p != &medPriorityPod {
t.Errorf("Expected: %v after Pop, but got: %v", medPriorityPod.Name, p.Name) t.Errorf("Expected: %v after Pop, but got: %v", medPriorityPod.Name, p.Name)
} }
if len(q.nominatedPods) != 1 { if len(q.nominatedPods.nominatedPods) != 1 {
t.Errorf("Expected nomindatePods to have one element: %v", q.nominatedPods) t.Errorf("Expected nomindatePods to have one element: %v", q.nominatedPods)
} }
if q.unschedulableQ.get(&unschedulablePod) != &unschedulablePod { if q.unschedulableQ.get(&unschedulablePod) != &unschedulablePod {
...@@ -186,8 +205,8 @@ func TestPriorityQueue_Pop(t *testing.T) { ...@@ -186,8 +205,8 @@ func TestPriorityQueue_Pop(t *testing.T) {
if p, err := q.Pop(); err != nil || p != &medPriorityPod { if p, err := q.Pop(); err != nil || p != &medPriorityPod {
t.Errorf("Expected: %v after Pop, but got: %v", medPriorityPod.Name, p.Name) t.Errorf("Expected: %v after Pop, but got: %v", medPriorityPod.Name, p.Name)
} }
if len(q.nominatedPods["node1"]) != 1 { if len(q.nominatedPods.nominatedPods["node1"]) != 1 {
t.Errorf("Expected medPriorityPod to be present in nomindatePods: %v", q.nominatedPods["node1"]) t.Errorf("Expected medPriorityPod to be present in nomindatePods: %v", q.nominatedPods.nominatedPods["node1"])
} }
}() }()
q.Add(&medPriorityPod) q.Add(&medPriorityPod)
...@@ -200,7 +219,7 @@ func TestPriorityQueue_Update(t *testing.T) { ...@@ -200,7 +219,7 @@ func TestPriorityQueue_Update(t *testing.T) {
if _, exists, _ := q.activeQ.Get(&highPriorityPod); !exists { if _, exists, _ := q.activeQ.Get(&highPriorityPod); !exists {
t.Errorf("Expected %v to be added to activeQ.", highPriorityPod.Name) t.Errorf("Expected %v to be added to activeQ.", highPriorityPod.Name)
} }
if len(q.nominatedPods) != 0 { if len(q.nominatedPods.nominatedPods) != 0 {
t.Errorf("Expected nomindatePods to be empty: %v", q.nominatedPods) t.Errorf("Expected nomindatePods to be empty: %v", q.nominatedPods)
} }
// Update highPriorityPod and add a nominatedNodeName to it. // Update highPriorityPod and add a nominatedNodeName to it.
...@@ -208,7 +227,7 @@ func TestPriorityQueue_Update(t *testing.T) { ...@@ -208,7 +227,7 @@ func TestPriorityQueue_Update(t *testing.T) {
if q.activeQ.Len() != 1 { if q.activeQ.Len() != 1 {
t.Error("Expected only one item in activeQ.") t.Error("Expected only one item in activeQ.")
} }
if len(q.nominatedPods) != 1 { if len(q.nominatedPods.nominatedPods) != 1 {
t.Errorf("Expected one item in nomindatePods map: %v", q.nominatedPods) t.Errorf("Expected one item in nomindatePods map: %v", q.nominatedPods)
} }
// Updating an unschedulable pod which is not in any of the two queues, should // Updating an unschedulable pod which is not in any of the two queues, should
...@@ -243,13 +262,13 @@ func TestPriorityQueue_Delete(t *testing.T) { ...@@ -243,13 +262,13 @@ func TestPriorityQueue_Delete(t *testing.T) {
if _, exists, _ := q.activeQ.Get(&highPriNominatedPod); exists { if _, exists, _ := q.activeQ.Get(&highPriNominatedPod); exists {
t.Errorf("Didn't expect %v to be in activeQ.", highPriorityPod.Name) t.Errorf("Didn't expect %v to be in activeQ.", highPriorityPod.Name)
} }
if len(q.nominatedPods) != 1 { if len(q.nominatedPods.nominatedPods) != 1 {
t.Errorf("Expected nomindatePods to have only 'unschedulablePod': %v", q.nominatedPods) t.Errorf("Expected nomindatePods to have only 'unschedulablePod': %v", q.nominatedPods.nominatedPods)
} }
if err := q.Delete(&unschedulablePod); err != nil { if err := q.Delete(&unschedulablePod); err != nil {
t.Errorf("delete failed: %v", err) t.Errorf("delete failed: %v", err)
} }
if len(q.nominatedPods) != 0 { if len(q.nominatedPods.nominatedPods) != 0 {
t.Errorf("Expected nomindatePods to be empty: %v", q.nominatedPods) t.Errorf("Expected nomindatePods to be empty: %v", q.nominatedPods)
} }
} }
...@@ -321,7 +340,7 @@ func TestPriorityQueue_AssignedPodAdded(t *testing.T) { ...@@ -321,7 +340,7 @@ func TestPriorityQueue_AssignedPodAdded(t *testing.T) {
} }
} }
func TestPriorityQueue_WaitingPodsForNode(t *testing.T) { func TestPriorityQueue_NominatedPodsForNode(t *testing.T) {
q := NewPriorityQueue(nil) q := NewPriorityQueue(nil)
q.Add(&medPriorityPod) q.Add(&medPriorityPod)
q.Add(&unschedulablePod) q.Add(&unschedulablePod)
...@@ -330,10 +349,10 @@ func TestPriorityQueue_WaitingPodsForNode(t *testing.T) { ...@@ -330,10 +349,10 @@ func TestPriorityQueue_WaitingPodsForNode(t *testing.T) {
t.Errorf("Expected: %v after Pop, but got: %v", highPriorityPod.Name, p.Name) t.Errorf("Expected: %v after Pop, but got: %v", highPriorityPod.Name, p.Name)
} }
expectedList := []*v1.Pod{&medPriorityPod, &unschedulablePod} expectedList := []*v1.Pod{&medPriorityPod, &unschedulablePod}
if !reflect.DeepEqual(expectedList, q.WaitingPodsForNode("node1")) { if !reflect.DeepEqual(expectedList, q.NominatedPodsForNode("node1")) {
t.Error("Unexpected list of nominated Pods for node.") t.Error("Unexpected list of nominated Pods for node.")
} }
if q.WaitingPodsForNode("node2") != nil { if q.NominatedPodsForNode("node2") != nil {
t.Error("Expected list of nominated Pods for node2 to be empty.") t.Error("Expected list of nominated Pods for node2 to be empty.")
} }
} }
...@@ -354,6 +373,75 @@ func TestPriorityQueue_PendingPods(t *testing.T) { ...@@ -354,6 +373,75 @@ func TestPriorityQueue_PendingPods(t *testing.T) {
} }
} }
func TestPriorityQueue_UpdateNominatedPodForNode(t *testing.T) {
q := NewPriorityQueue(nil)
if err := q.Add(&medPriorityPod); err != nil {
t.Errorf("add failed: %v", err)
}
// Update unschedulablePod on a different node than specified in the pod.
q.UpdateNominatedPodForNode(&unschedulablePod, "node5")
// Update nominated node name of a pod on a node that is not specified in the pod object.
q.UpdateNominatedPodForNode(&highPriorityPod, "node2")
expectedNominatedPods := &nominatedPodMap{
nominatedPodToNode: map[types.UID]string{
medPriorityPod.UID: "node1",
highPriorityPod.UID: "node2",
unschedulablePod.UID: "node5",
},
nominatedPods: map[string][]*v1.Pod{
"node1": {&medPriorityPod},
"node2": {&highPriorityPod},
"node5": {&unschedulablePod},
},
}
if !reflect.DeepEqual(q.nominatedPods, expectedNominatedPods) {
t.Errorf("Unexpected nominated map after adding pods. Expected: %v, got: %v", expectedNominatedPods, q.nominatedPods)
}
if p, err := q.Pop(); err != nil || p != &medPriorityPod {
t.Errorf("Expected: %v after Pop, but got: %v", medPriorityPod.Name, p.Name)
}
// List of nominated pods shouldn't change after popping them from the queue.
if !reflect.DeepEqual(q.nominatedPods, expectedNominatedPods) {
t.Errorf("Unexpected nominated map after popping pods. Expected: %v, got: %v", expectedNominatedPods, q.nominatedPods)
}
// Update one of the nominated pods that doesn't have nominatedNodeName in the
// pod object. It should be updated correctly.
q.UpdateNominatedPodForNode(&highPriorityPod, "node4")
expectedNominatedPods = &nominatedPodMap{
nominatedPodToNode: map[types.UID]string{
medPriorityPod.UID: "node1",
highPriorityPod.UID: "node4",
unschedulablePod.UID: "node5",
},
nominatedPods: map[string][]*v1.Pod{
"node1": {&medPriorityPod},
"node4": {&highPriorityPod},
"node5": {&unschedulablePod},
},
}
if !reflect.DeepEqual(q.nominatedPods, expectedNominatedPods) {
t.Errorf("Unexpected nominated map after updating pods. Expected: %v, got: %v", expectedNominatedPods, q.nominatedPods)
}
// Delete a nominated pod that doesn't have nominatedNodeName in the pod
// object. It should be deleted.
q.DeleteNominatedPodIfExists(&highPriorityPod)
expectedNominatedPods = &nominatedPodMap{
nominatedPodToNode: map[types.UID]string{
medPriorityPod.UID: "node1",
unschedulablePod.UID: "node5",
},
nominatedPods: map[string][]*v1.Pod{
"node1": {&medPriorityPod},
"node5": {&unschedulablePod},
},
}
if !reflect.DeepEqual(q.nominatedPods, expectedNominatedPods) {
t.Errorf("Unexpected nominated map after deleting pods. Expected: %v, got: %v", expectedNominatedPods, q.nominatedPods)
}
}
func TestUnschedulablePodsMap(t *testing.T) { func TestUnschedulablePodsMap(t *testing.T) {
var pods = []*v1.Pod{ var pods = []*v1.Pod{
{ {
......
...@@ -318,11 +318,19 @@ func (sched *Scheduler) preempt(preemptor *v1.Pod, scheduleErr error) (string, e ...@@ -318,11 +318,19 @@ func (sched *Scheduler) preempt(preemptor *v1.Pod, scheduleErr error) (string, e
var nodeName = "" var nodeName = ""
if node != nil { if node != nil {
nodeName = node.Name nodeName = node.Name
// Update the scheduling queue with the nominated pod information. Without
// this, there would be a race condition between the next scheduling cycle
// and the time the scheduler receives a Pod Update for the nominated pod.
sched.config.SchedulingQueue.UpdateNominatedPodForNode(preemptor, nodeName)
// Make a call to update nominated node name of the pod on the API server.
err = sched.config.PodPreemptor.SetNominatedNodeName(preemptor, nodeName) err = sched.config.PodPreemptor.SetNominatedNodeName(preemptor, nodeName)
if err != nil { if err != nil {
klog.Errorf("Error in preemption process. Cannot update pod %v/%v annotations: %v", preemptor.Namespace, preemptor.Name, err) klog.Errorf("Error in preemption process. Cannot update pod %v/%v annotations: %v", preemptor.Namespace, preemptor.Name, err)
sched.config.SchedulingQueue.DeleteNominatedPodIfExists(preemptor)
return "", err return "", err
} }
for _, victim := range victims { for _, victim := range victims {
if err := sched.config.PodPreemptor.DeletePod(victim); err != nil { if err := sched.config.PodPreemptor.DeletePod(victim); err != nil {
klog.Errorf("Error preempting pod %v/%v: %v", victim.Namespace, victim.Name, err) klog.Errorf("Error preempting pod %v/%v: %v", victim.Namespace, victim.Name, err)
......
...@@ -26,6 +26,7 @@ go_test( ...@@ -26,6 +26,7 @@ go_test(
"//cmd/kube-scheduler/app:go_default_library", "//cmd/kube-scheduler/app:go_default_library",
"//cmd/kube-scheduler/app/config:go_default_library", "//cmd/kube-scheduler/app/config:go_default_library",
"//pkg/api/legacyscheme:go_default_library", "//pkg/api/legacyscheme:go_default_library",
"//pkg/api/v1/pod:go_default_library",
"//pkg/controller/nodelifecycle:go_default_library", "//pkg/controller/nodelifecycle:go_default_library",
"//pkg/controller/volume/persistentvolume:go_default_library", "//pkg/controller/volume/persistentvolume:go_default_library",
"//pkg/controller/volume/persistentvolume/options:go_default_library", "//pkg/controller/volume/persistentvolume/options:go_default_library",
......
...@@ -584,6 +584,17 @@ func podScheduled(c clientset.Interface, podNamespace, podName string) wait.Cond ...@@ -584,6 +584,17 @@ func podScheduled(c clientset.Interface, podNamespace, podName string) wait.Cond
// podUnschedulable returns a condition function that returns true if the given pod // podUnschedulable returns a condition function that returns true if the given pod
// gets unschedulable status. // gets unschedulable status.
func podSchedulableCondition(c clientset.Interface, podNamespace, podName string) (*v1.PodCondition, error) {
pod, err := c.CoreV1().Pods(podNamespace).Get(podName, metav1.GetOptions{})
if err != nil {
return nil, err
}
_, cond := podutil.GetPodCondition(&pod.Status, v1.PodScheduled)
return cond, nil
}
// podUnschedulable returns a condition function that returns true if the given pod
// gets unschedulable status.
func podUnschedulable(c clientset.Interface, podNamespace, podName string) wait.ConditionFunc { func podUnschedulable(c clientset.Interface, podNamespace, podName string) wait.ConditionFunc {
return func() (bool, error) { return func() (bool, error) {
pod, err := c.CoreV1().Pods(podNamespace).Get(podName, metav1.GetOptions{}) pod, err := c.CoreV1().Pods(podNamespace).Get(podName, metav1.GetOptions{})
...@@ -710,7 +721,7 @@ func cleanupPods(cs clientset.Interface, t *testing.T, pods []*v1.Pod) { ...@@ -710,7 +721,7 @@ func cleanupPods(cs clientset.Interface, t *testing.T, pods []*v1.Pod) {
} }
} }
for _, p := range pods { for _, p := range pods {
if err := wait.Poll(time.Second, wait.ForeverTestTimeout, if err := wait.Poll(time.Millisecond, wait.ForeverTestTimeout,
podDeleted(cs, p.Namespace, p.Name)); err != nil { podDeleted(cs, p.Namespace, p.Name)); err != nil {
t.Errorf("error while waiting for pod %v/%v to get deleted: %v", p.Namespace, p.Name, err) t.Errorf("error while waiting for pod %v/%v to get deleted: %v", p.Namespace, p.Name, err)
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment