Commit 67f9528e authored by Bobby (Babak) Salamat's avatar Bobby (Babak) Salamat

Fix race in setting nominated node

parent c75356fd
...@@ -351,7 +351,7 @@ func (g *genericScheduler) processPreemptionWithExtenders( ...@@ -351,7 +351,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
...@@ -501,7 +501,7 @@ func addNominatedPods(pod *v1.Pod, meta algorithm.PredicateMetadata, ...@@ -501,7 +501,7 @@ func addNominatedPods(pod *v1.Pod, meta algorithm.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
} }
......
...@@ -36,6 +36,7 @@ import ( ...@@ -36,6 +36,7 @@ import (
"k8s.io/api/core/v1" "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
ktypes "k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/cache"
podutil "k8s.io/kubernetes/pkg/api/v1/pod" podutil "k8s.io/kubernetes/pkg/api/v1/pod"
"k8s.io/kubernetes/pkg/scheduler/algorithm/predicates" "k8s.io/kubernetes/pkg/scheduler/algorithm/predicates"
...@@ -62,11 +63,14 @@ type SchedulingQueue interface { ...@@ -62,11 +63,14 @@ type SchedulingQueue interface {
MoveAllToActiveQueue() MoveAllToActiveQueue()
AssignedPodAdded(pod *v1.Pod) AssignedPodAdded(pod *v1.Pod)
AssignedPodUpdated(pod *v1.Pod) AssignedPodUpdated(pod *v1.Pod)
WaitingPodsForNode(nodeName string) []*v1.Pod NominatedPodsForNode(nodeName string) []*v1.Pod
WaitingPods() []*v1.Pod WaitingPods() []*v1.Pod
// Close closes the SchedulingQueue so that the goroutine which is // Close closes the SchedulingQueue so that the goroutine which is
// waiting to pop items can exit gracefully. // waiting to pop items can exit gracefully.
Close() Close()
// UpdateNominatedPodForNode adds the given pod to the nominated pod map or
// updates it if it already exists.
UpdateNominatedPodForNode(pod *v1.Pod, nodeName string)
// DeleteNominatedPodIfExists deletes nominatedPod from internal cache // DeleteNominatedPodIfExists deletes nominatedPod from internal cache
DeleteNominatedPodIfExists(pod *v1.Pod) DeleteNominatedPodIfExists(pod *v1.Pod)
// NumUnschedulablePods returns the number of unschedulable pods exist in the SchedulingQueue. // NumUnschedulablePods returns the number of unschedulable pods exist in the SchedulingQueue.
...@@ -150,9 +154,9 @@ func (f *FIFO) AssignedPodUpdated(pod *v1.Pod) {} ...@@ -150,9 +154,9 @@ func (f *FIFO) AssignedPodUpdated(pod *v1.Pod) {}
// MoveAllToActiveQueue does nothing in FIFO as all pods are always in the active queue. // MoveAllToActiveQueue does nothing in FIFO as all pods are always in the active queue.
func (f *FIFO) MoveAllToActiveQueue() {} func (f *FIFO) MoveAllToActiveQueue() {}
// WaitingPodsForNode returns pods that are nominated to run on the given node, // NominatedPodsForNode returns pods that are nominated to run on the given node,
// but FIFO does not support it. // but FIFO does not support it.
func (f *FIFO) WaitingPodsForNode(nodeName string) []*v1.Pod { func (f *FIFO) NominatedPodsForNode(nodeName string) []*v1.Pod {
return nil return nil
} }
...@@ -164,6 +168,9 @@ func (f *FIFO) Close() { ...@@ -164,6 +168,9 @@ func (f *FIFO) Close() {
// DeleteNominatedPodIfExists does nothing in FIFO. // DeleteNominatedPodIfExists does nothing in FIFO.
func (f *FIFO) DeleteNominatedPodIfExists(pod *v1.Pod) {} func (f *FIFO) DeleteNominatedPodIfExists(pod *v1.Pod) {}
// UpdateNominatedPodForNode does nothing in FIFO.
func (f *FIFO) UpdateNominatedPodForNode(pod *v1.Pod, nodeName string) {}
// NumUnschedulablePods returns the number of unschedulable pods exist in the SchedulingQueue. // NumUnschedulablePods returns the number of unschedulable pods exist in the SchedulingQueue.
func (f *FIFO) NumUnschedulablePods() int { func (f *FIFO) NumUnschedulablePods() int {
return 0 return 0
...@@ -194,10 +201,9 @@ type PriorityQueue struct { ...@@ -194,10 +201,9 @@ type PriorityQueue struct {
activeQ *Heap activeQ *Heap
// unschedulableQ holds pods that have been tried and determined unschedulable. // unschedulableQ holds pods that have been tried and determined unschedulable.
unschedulableQ *UnschedulablePodsMap unschedulableQ *UnschedulablePodsMap
// nominatedPods is a map keyed by a node name and the value is a list of // nominatedPods is a structures that stores pods which are nominated to run
// pods which are nominated to run on the node. These are pods which can be in // on nodes.
// the activeQ or unschedulableQ. nominatedPods *nominatedPodMap
nominatedPods map[string][]*v1.Pod
// receivedMoveRequest is set to true whenever we receive a request to move a // receivedMoveRequest is set to true whenever we receive a request to move a
// pod from the unschedulableQ to the activeQ, and is set to false, when we pop // pod from the unschedulableQ to the activeQ, and is set to false, when we pop
// a pod from the activeQ. It indicates if we received a move request when a // a pod from the activeQ. It indicates if we received a move request when a
...@@ -239,52 +245,12 @@ func NewPriorityQueue() *PriorityQueue { ...@@ -239,52 +245,12 @@ func NewPriorityQueue() *PriorityQueue {
pq := &PriorityQueue{ pq := &PriorityQueue{
activeQ: newHeap(cache.MetaNamespaceKeyFunc, activeQComp), activeQ: newHeap(cache.MetaNamespaceKeyFunc, activeQComp),
unschedulableQ: newUnschedulablePodsMap(), unschedulableQ: newUnschedulablePodsMap(),
nominatedPods: map[string][]*v1.Pod{}, nominatedPods: newNominatedPodMap(),
} }
pq.cond.L = &pq.lock pq.cond.L = &pq.lock
return pq return pq
} }
// addNominatedPodIfNeeded adds a pod to nominatedPods if it has a NominatedNodeName and it does not
// already exist in the map. Adding an existing pod is not going to update the pod.
func (p *PriorityQueue) addNominatedPodIfNeeded(pod *v1.Pod) {
nnn := NominatedNodeName(pod)
if len(nnn) > 0 {
for _, np := range p.nominatedPods[nnn] {
if np.UID == pod.UID {
klog.V(4).Infof("Pod %v/%v already exists in the nominated map!", pod.Namespace, pod.Name)
return
}
}
p.nominatedPods[nnn] = append(p.nominatedPods[nnn], pod)
}
}
// deleteNominatedPodIfExists deletes a pod from the nominatedPods.
// NOTE: this function assumes lock has been acquired in caller.
func (p *PriorityQueue) deleteNominatedPodIfExists(pod *v1.Pod) {
nnn := NominatedNodeName(pod)
if len(nnn) > 0 {
for i, np := range p.nominatedPods[nnn] {
if np.UID == pod.UID {
p.nominatedPods[nnn] = append(p.nominatedPods[nnn][:i], p.nominatedPods[nnn][i+1:]...)
if len(p.nominatedPods[nnn]) == 0 {
delete(p.nominatedPods, nnn)
}
break
}
}
}
}
// updateNominatedPod updates a pod in the nominatedPods.
func (p *PriorityQueue) updateNominatedPod(oldPod, newPod *v1.Pod) {
// Even if the nominated node name of the Pod is not changed, we must delete and add it again
// to ensure that its pointer is updated.
p.deleteNominatedPodIfExists(oldPod)
p.addNominatedPodIfNeeded(newPod)
}
// Add adds a pod to the active queue. It should be called only when a new pod // Add adds a pod to the active queue. It should be called only when a new pod
// is added so there is no chance the pod is already in either queue. // is added so there is no chance the pod is already in either queue.
func (p *PriorityQueue) Add(pod *v1.Pod) error { func (p *PriorityQueue) Add(pod *v1.Pod) error {
...@@ -296,10 +262,9 @@ func (p *PriorityQueue) Add(pod *v1.Pod) error { ...@@ -296,10 +262,9 @@ func (p *PriorityQueue) Add(pod *v1.Pod) error {
} else { } else {
if p.unschedulableQ.get(pod) != nil { if p.unschedulableQ.get(pod) != nil {
klog.Errorf("Error: pod %v/%v is already in the unschedulable queue.", pod.Namespace, pod.Name) klog.Errorf("Error: pod %v/%v is already in the unschedulable queue.", pod.Namespace, pod.Name)
p.deleteNominatedPodIfExists(pod)
p.unschedulableQ.delete(pod) p.unschedulableQ.delete(pod)
} }
p.addNominatedPodIfNeeded(pod) p.nominatedPods.add(pod, "")
p.cond.Broadcast() p.cond.Broadcast()
} }
return err return err
...@@ -320,7 +285,7 @@ func (p *PriorityQueue) AddIfNotPresent(pod *v1.Pod) error { ...@@ -320,7 +285,7 @@ func (p *PriorityQueue) AddIfNotPresent(pod *v1.Pod) error {
if err != nil { if err != nil {
klog.Errorf("Error adding pod %v/%v to the scheduling queue: %v", pod.Namespace, pod.Name, err) klog.Errorf("Error adding pod %v/%v to the scheduling queue: %v", pod.Namespace, pod.Name, err)
} else { } else {
p.addNominatedPodIfNeeded(pod) p.nominatedPods.add(pod, "")
p.cond.Broadcast() p.cond.Broadcast()
} }
return err return err
...@@ -345,12 +310,12 @@ func (p *PriorityQueue) AddUnschedulableIfNotPresent(pod *v1.Pod) error { ...@@ -345,12 +310,12 @@ func (p *PriorityQueue) AddUnschedulableIfNotPresent(pod *v1.Pod) error {
} }
if !p.receivedMoveRequest && isPodUnschedulable(pod) { if !p.receivedMoveRequest && isPodUnschedulable(pod) {
p.unschedulableQ.addOrUpdate(pod) p.unschedulableQ.addOrUpdate(pod)
p.addNominatedPodIfNeeded(pod) p.nominatedPods.add(pod, "")
return nil return nil
} }
err := p.activeQ.Add(pod) err := p.activeQ.Add(pod)
if err == nil { if err == nil {
p.addNominatedPodIfNeeded(pod) p.nominatedPods.add(pod, "")
p.cond.Broadcast() p.cond.Broadcast()
} }
return err return err
...@@ -401,13 +366,13 @@ func (p *PriorityQueue) Update(oldPod, newPod *v1.Pod) error { ...@@ -401,13 +366,13 @@ func (p *PriorityQueue) Update(oldPod, newPod *v1.Pod) error {
defer p.lock.Unlock() defer p.lock.Unlock()
// If the pod is already in the active queue, just update it there. // If the pod is already in the active queue, just update it there.
if _, exists, _ := p.activeQ.Get(newPod); exists { if _, exists, _ := p.activeQ.Get(newPod); exists {
p.updateNominatedPod(oldPod, newPod) p.nominatedPods.update(oldPod, newPod)
err := p.activeQ.Update(newPod) err := p.activeQ.Update(newPod)
return err return err
} }
// If the pod is in the unschedulable queue, updating it may make it schedulable. // If the pod is in the unschedulable queue, updating it may make it schedulable.
if usPod := p.unschedulableQ.get(newPod); usPod != nil { if usPod := p.unschedulableQ.get(newPod); usPod != nil {
p.updateNominatedPod(oldPod, newPod) p.nominatedPods.update(oldPod, newPod)
if isPodUpdated(oldPod, newPod) { if isPodUpdated(oldPod, newPod) {
p.unschedulableQ.delete(usPod) p.unschedulableQ.delete(usPod)
err := p.activeQ.Add(newPod) err := p.activeQ.Add(newPod)
...@@ -422,7 +387,7 @@ func (p *PriorityQueue) Update(oldPod, newPod *v1.Pod) error { ...@@ -422,7 +387,7 @@ func (p *PriorityQueue) Update(oldPod, newPod *v1.Pod) error {
// If pod is not in any of the two queue, we put it in the active queue. // If pod is not in any of the two queue, we put it in the active queue.
err := p.activeQ.Add(newPod) err := p.activeQ.Add(newPod)
if err == nil { if err == nil {
p.addNominatedPodIfNeeded(newPod) p.nominatedPods.add(newPod, "")
p.cond.Broadcast() p.cond.Broadcast()
} }
return err return err
...@@ -433,7 +398,7 @@ func (p *PriorityQueue) Update(oldPod, newPod *v1.Pod) error { ...@@ -433,7 +398,7 @@ func (p *PriorityQueue) Update(oldPod, newPod *v1.Pod) error {
func (p *PriorityQueue) Delete(pod *v1.Pod) error { func (p *PriorityQueue) Delete(pod *v1.Pod) error {
p.lock.Lock() p.lock.Lock()
defer p.lock.Unlock() defer p.lock.Unlock()
p.deleteNominatedPodIfExists(pod) p.nominatedPods.delete(pod)
err := p.activeQ.Delete(pod) err := p.activeQ.Delete(pod)
if err != nil { // The item was probably not found in the activeQ. if err != nil { // The item was probably not found in the activeQ.
p.unschedulableQ.delete(pod) p.unschedulableQ.delete(pod)
...@@ -516,16 +481,13 @@ func (p *PriorityQueue) getUnschedulablePodsWithMatchingAffinityTerm(pod *v1.Pod ...@@ -516,16 +481,13 @@ func (p *PriorityQueue) getUnschedulablePodsWithMatchingAffinityTerm(pod *v1.Pod
return podsToMove return podsToMove
} }
// WaitingPodsForNode returns pods that are nominated to run on the given node, // NominatedPodsForNode returns pods that are nominated to run on the given node,
// but they are waiting for other pods to be removed from the node before they // but they are waiting for other pods to be removed from the node before they
// can be actually scheduled. // can be actually scheduled.
func (p *PriorityQueue) WaitingPodsForNode(nodeName string) []*v1.Pod { func (p *PriorityQueue) NominatedPodsForNode(nodeName string) []*v1.Pod {
p.lock.RLock() p.lock.RLock()
defer p.lock.RUnlock() defer p.lock.RUnlock()
if list, ok := p.nominatedPods[nodeName]; ok { return p.nominatedPods.podsForNode(nodeName)
return list
}
return nil
} }
// WaitingPods returns all the waiting pods in the queue. // WaitingPods returns all the waiting pods in the queue.
...@@ -551,10 +513,20 @@ func (p *PriorityQueue) Close() { ...@@ -551,10 +513,20 @@ func (p *PriorityQueue) Close() {
p.cond.Broadcast() p.cond.Broadcast()
} }
// DeleteNominatedPodIfExists deletes pod from internal cache if it's a nominatedPod // DeleteNominatedPodIfExists deletes pod nominatedPods.
func (p *PriorityQueue) DeleteNominatedPodIfExists(pod *v1.Pod) { func (p *PriorityQueue) DeleteNominatedPodIfExists(pod *v1.Pod) {
p.lock.Lock() p.lock.Lock()
p.deleteNominatedPodIfExists(pod) p.nominatedPods.delete(pod)
p.lock.Unlock()
}
// UpdateNominatedPodForNode adds a pod to the nominated pods of the given node.
// This is called during the preemption process after a node is nominated to run
// the pod. We update the structure before sending a request to update the pod
// object to avoid races with the following scheduling cycles.
func (p *PriorityQueue) UpdateNominatedPodForNode(pod *v1.Pod, nodeName string) {
p.lock.Lock()
p.nominatedPods.add(pod, nodeName)
p.lock.Unlock() p.lock.Unlock()
} }
...@@ -802,3 +774,77 @@ func newHeap(keyFn KeyFunc, lessFn LessFunc) *Heap { ...@@ -802,3 +774,77 @@ func newHeap(keyFn KeyFunc, lessFn LessFunc) *Heap {
}, },
} }
} }
// nominatedPodMap is a structure that stores pods nominated to run on nodes.
// It exists because nominatedNodeName of pod objects stored in the structure
// may be different than what scheduler has here. We should be able to find pods
// by their UID and update/delete them.
type nominatedPodMap struct {
// nominatedPods is a map keyed by a node name and the value is a list of
// pods which are nominated to run on the node. These are pods which can be in
// the activeQ or unschedulableQ.
nominatedPods map[string][]*v1.Pod
// nominatedPodToNode is map keyed by a Pod UID to the node name where it is
// nominated.
nominatedPodToNode map[ktypes.UID]string
}
func (npm *nominatedPodMap) add(p *v1.Pod, nodeName string) {
// always delete the pod if it already exist, to ensure we never store more than
// one instance of the pod.
npm.delete(p)
nnn := nodeName
if len(nnn) == 0 {
nnn = NominatedNodeName(p)
if len(nnn) == 0 {
return
}
}
npm.nominatedPodToNode[p.UID] = nnn
for _, np := range npm.nominatedPods[nnn] {
if np.UID == p.UID {
klog.V(4).Infof("Pod %v/%v already exists in the nominated map!", p.Namespace, p.Name)
return
}
}
npm.nominatedPods[nnn] = append(npm.nominatedPods[nnn], p)
}
func (npm *nominatedPodMap) delete(p *v1.Pod) {
nnn, ok := npm.nominatedPodToNode[p.UID]
if !ok {
return
}
for i, np := range npm.nominatedPods[nnn] {
if np.UID == p.UID {
npm.nominatedPods[nnn] = append(npm.nominatedPods[nnn][:i], npm.nominatedPods[nnn][i+1:]...)
if len(npm.nominatedPods[nnn]) == 0 {
delete(npm.nominatedPods, nnn)
}
break
}
}
delete(npm.nominatedPodToNode, p.UID)
}
func (npm *nominatedPodMap) update(oldPod, newPod *v1.Pod) {
// We update irrespective of the nominatedNodeName changed or not, to ensure
// that pod pointer is updated.
npm.delete(oldPod)
npm.add(newPod, "")
}
func (npm *nominatedPodMap) podsForNode(nodeName string) []*v1.Pod {
if list, ok := npm.nominatedPods[nodeName]; ok {
return list
}
return nil
}
func newNominatedPodMap() *nominatedPodMap {
return &nominatedPodMap{
nominatedPods: make(map[string][]*v1.Pod),
nominatedPodToNode: make(map[ktypes.UID]string),
}
}
...@@ -99,8 +99,14 @@ func TestPriorityQueue_Add(t *testing.T) { ...@@ -99,8 +99,14 @@ func TestPriorityQueue_Add(t *testing.T) {
q.Add(&medPriorityPod) q.Add(&medPriorityPod)
q.Add(&unschedulablePod) q.Add(&unschedulablePod)
q.Add(&highPriorityPod) q.Add(&highPriorityPod)
expectedNominatedPods := map[string][]*v1.Pod{ expectedNominatedPods := &nominatedPodMap{
"node1": {&medPriorityPod, &unschedulablePod}, nominatedPodToNode: map[types.UID]string{
medPriorityPod.UID: "node1",
unschedulablePod.UID: "node1",
},
nominatedPods: map[string][]*v1.Pod{
"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)
...@@ -114,8 +120,8 @@ func TestPriorityQueue_Add(t *testing.T) { ...@@ -114,8 +120,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"])
} }
} }
...@@ -125,8 +131,14 @@ func TestPriorityQueue_AddIfNotPresent(t *testing.T) { ...@@ -125,8 +131,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{
"node1": {&medPriorityPod, &unschedulablePod}, nominatedPodToNode: map[types.UID]string{
medPriorityPod.UID: "node1",
unschedulablePod.UID: "node1",
},
nominatedPods: map[string][]*v1.Pod{
"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)
...@@ -137,8 +149,8 @@ func TestPriorityQueue_AddIfNotPresent(t *testing.T) { ...@@ -137,8 +149,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)
...@@ -151,8 +163,15 @@ func TestPriorityQueue_AddUnschedulableIfNotPresent(t *testing.T) { ...@@ -151,8 +163,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{
"node1": {&highPriNominatedPod, &medPriorityPod, &unschedulablePod}, nominatedPodToNode: map[types.UID]string{
medPriorityPod.UID: "node1",
unschedulablePod.UID: "node1",
highPriNominatedPod.UID: "node1",
},
nominatedPods: map[string][]*v1.Pod{
"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)
...@@ -163,7 +182,7 @@ func TestPriorityQueue_AddUnschedulableIfNotPresent(t *testing.T) { ...@@ -163,7 +182,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 {
...@@ -180,8 +199,8 @@ func TestPriorityQueue_Pop(t *testing.T) { ...@@ -180,8 +199,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)
...@@ -194,7 +213,7 @@ func TestPriorityQueue_Update(t *testing.T) { ...@@ -194,7 +213,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.
...@@ -202,7 +221,7 @@ func TestPriorityQueue_Update(t *testing.T) { ...@@ -202,7 +221,7 @@ func TestPriorityQueue_Update(t *testing.T) {
if q.activeQ.data.Len() != 1 { if q.activeQ.data.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
...@@ -235,11 +254,13 @@ func TestPriorityQueue_Delete(t *testing.T) { ...@@ -235,11 +254,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 {
t.Errorf("delete failed: %v", err)
} }
q.Delete(&unschedulablePod) if len(q.nominatedPods.nominatedPods) != 0 {
if len(q.nominatedPods) != 0 {
t.Errorf("Expected nomindatePods to be empty: %v", q.nominatedPods) t.Errorf("Expected nomindatePods to be empty: %v", q.nominatedPods)
} }
} }
...@@ -311,7 +332,7 @@ func TestPriorityQueue_AssignedPodAdded(t *testing.T) { ...@@ -311,7 +332,7 @@ func TestPriorityQueue_AssignedPodAdded(t *testing.T) {
} }
} }
func TestPriorityQueue_WaitingPodsForNode(t *testing.T) { func TestPriorityQueue_NominatedPodsForNode(t *testing.T) {
q := NewPriorityQueue() q := NewPriorityQueue()
q.Add(&medPriorityPod) q.Add(&medPriorityPod)
q.Add(&unschedulablePod) q.Add(&unschedulablePod)
...@@ -320,14 +341,83 @@ func TestPriorityQueue_WaitingPodsForNode(t *testing.T) { ...@@ -320,14 +341,83 @@ 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.")
} }
} }
func TestPriorityQueue_UpdateNominatedPodForNode(t *testing.T) {
q := NewPriorityQueue()
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{
{ {
......
...@@ -328,11 +328,19 @@ func (sched *Scheduler) preempt(preemptor *v1.Pod, scheduleErr error) (string, e ...@@ -328,11 +328,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)
......
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