Unverified Commit 0aa225f9 authored by Kubernetes Prow Robot's avatar Kubernetes Prow Robot Committed by GitHub

Merge pull request #73454 from denkensk/automated-cherry-pick-of-#72558-upstream-release-1.13

Automated cherry pick of #72558: add goroutine to move unschedulablepods to activeq regularly 1.13
parents 210452aa 06d31c3b
......@@ -507,7 +507,7 @@ func TestGenericSchedulerWithExtenders(t *testing.T) {
for _, name := range test.nodes {
cache.AddNode(createNode(name))
}
queue := internalqueue.NewSchedulingQueue()
queue := internalqueue.NewSchedulingQueue(nil)
scheduler := NewGenericScheduler(
cache,
nil,
......
......@@ -449,7 +449,7 @@ func TestGenericScheduler(t *testing.T) {
scheduler := NewGenericScheduler(
cache,
nil,
internalqueue.NewSchedulingQueue(),
internalqueue.NewSchedulingQueue(nil),
test.predicates,
algorithm.EmptyPredicateMetadataProducer,
test.prioritizers,
......@@ -485,7 +485,7 @@ func makeScheduler(predicates map[string]algorithm.FitPredicate, nodes []*v1.Nod
s := NewGenericScheduler(
cache,
nil,
internalqueue.NewSchedulingQueue(),
internalqueue.NewSchedulingQueue(nil),
predicates,
algorithm.EmptyPredicateMetadataProducer,
prioritizers,
......@@ -1412,7 +1412,7 @@ func TestPreempt(t *testing.T) {
scheduler := NewGenericScheduler(
cache,
nil,
internalqueue.NewSchedulingQueue(),
internalqueue.NewSchedulingQueue(nil),
map[string]algorithm.FitPredicate{"matches": algorithmpredicates.PodFitsResources},
algorithm.EmptyPredicateMetadataProducer,
[]algorithm.PriorityConfig{{Function: numericPriority, Weight: 1}},
......@@ -1543,7 +1543,7 @@ func TestCacheInvalidationRace(t *testing.T) {
scheduler := NewGenericScheduler(
mockCache,
eCache,
internalqueue.NewSchedulingQueue(),
internalqueue.NewSchedulingQueue(nil),
ps,
algorithm.EmptyPredicateMetadataProducer,
prioritizers,
......@@ -1626,7 +1626,7 @@ func TestCacheInvalidationRace2(t *testing.T) {
scheduler := NewGenericScheduler(
cache,
eCache,
internalqueue.NewSchedulingQueue(),
internalqueue.NewSchedulingQueue(nil),
ps,
algorithm.EmptyPredicateMetadataProducer,
prioritizers,
......
......@@ -277,7 +277,7 @@ func NewConfigFactory(args *ConfigFactoryArgs) Configurator {
c := &configFactory{
client: args.Client,
podLister: schedulerCache,
podQueue: internalqueue.NewSchedulingQueue(),
podQueue: internalqueue.NewSchedulingQueue(stopEverything),
nodeLister: args.NodeInformer.Lister(),
pVLister: args.PvInformer.Lister(),
pVCLister: args.PvcInformer.Lister(),
......
......@@ -13,6 +13,7 @@ go_library(
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//staging/src/k8s.io/client-go/tools/cache:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
],
......
......@@ -31,12 +31,14 @@ import (
"fmt"
"reflect"
"sync"
"time"
"k8s.io/klog"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
ktypes "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/tools/cache"
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
"k8s.io/kubernetes/pkg/scheduler/algorithm/predicates"
......@@ -48,6 +50,10 @@ var (
queueClosed = "scheduling queue is closed"
)
// If the pod stays in unschedulableQ longer than the unschedulableQTimeInterval,
// the pod will be moved from unschedulableQ to activeQ.
const unschedulableQTimeInterval = 60 * time.Second
// SchedulingQueue is an interface for a queue to store pods waiting to be scheduled.
// The interface follows a pattern similar to cache.FIFO and cache.Heap and
// makes it easy to use those data structures as a SchedulingQueue.
......@@ -79,9 +85,9 @@ type SchedulingQueue interface {
// NewSchedulingQueue initializes a new scheduling queue. If pod priority is
// enabled a priority queue is returned. If it is disabled, a FIFO is returned.
func NewSchedulingQueue() SchedulingQueue {
func NewSchedulingQueue(stop <-chan struct{}) SchedulingQueue {
if util.PodPriorityEnabled() {
return NewPriorityQueue()
return NewPriorityQueue(stop)
}
return NewFIFO()
}
......@@ -193,8 +199,10 @@ func NominatedNodeName(pod *v1.Pod) string {
// pods that are already tried and are determined to be unschedulable. The latter
// is called unschedulableQ.
type PriorityQueue struct {
lock sync.RWMutex
cond sync.Cond
stop <-chan struct{}
clock util.Clock
lock sync.RWMutex
cond sync.Cond
// activeQ is heap structure that scheduler actively looks at to find pods to
// schedule. Head of heap is the highest priority pod.
......@@ -244,16 +252,25 @@ func activeQComp(pod1, pod2 interface{}) bool {
}
// NewPriorityQueue creates a PriorityQueue object.
func NewPriorityQueue() *PriorityQueue {
func NewPriorityQueue(stop <-chan struct{}) *PriorityQueue {
pq := &PriorityQueue{
clock: util.RealClock{},
stop: stop,
activeQ: newHeap(cache.MetaNamespaceKeyFunc, activeQComp),
unschedulableQ: newUnschedulablePodsMap(),
nominatedPods: newNominatedPodMap(),
}
pq.cond.L = &pq.lock
pq.run()
return pq
}
// run starts the goroutine to pump from unschedulableQ to activeQ
func (p *PriorityQueue) run() {
go wait.Until(p.flushUnschedulableQLeftover, 30*time.Second, p.stop)
}
// 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.
func (p *PriorityQueue) Add(pod *v1.Pod) error {
......@@ -324,6 +341,26 @@ func (p *PriorityQueue) AddUnschedulableIfNotPresent(pod *v1.Pod) error {
return err
}
// flushUnschedulableQLeftover moves pod which stays in unschedulableQ longer than the durationStayUnschedulableQ
// to activeQ.
func (p *PriorityQueue) flushUnschedulableQLeftover() {
p.lock.Lock()
defer p.lock.Unlock()
var podsToMove []*v1.Pod
currentTime := p.clock.Now()
for _, pod := range p.unschedulableQ.pods {
lastScheduleTime := podTimestamp(pod)
if !lastScheduleTime.IsZero() && currentTime.Sub(lastScheduleTime.Time) > unschedulableQTimeInterval {
podsToMove = append(podsToMove, pod)
}
}
if len(podsToMove) > 0 {
p.movePodsToActiveQueue(podsToMove)
}
}
// Pop removes the head of the active queue and returns it. It blocks if the
// activeQ is empty and waits until a new item is added to the queue. It also
// clears receivedMoveRequest to mark the beginning of a new scheduling cycle.
......
......@@ -21,6 +21,7 @@ import (
"reflect"
"sync"
"testing"
"time"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
......@@ -94,8 +95,20 @@ var highPriorityPod, highPriNominatedPod, medPriorityPod, unschedulablePod = v1.
},
}
func addOrUpdateUnschedulablePod(p *PriorityQueue, pod *v1.Pod) {
p.lock.Lock()
defer p.lock.Unlock()
p.unschedulableQ.addOrUpdate(pod)
}
func getUnschedulablePod(p *PriorityQueue, pod *v1.Pod) *v1.Pod {
p.lock.Lock()
defer p.lock.Unlock()
return p.unschedulableQ.get(pod)
}
func TestPriorityQueue_Add(t *testing.T) {
q := NewPriorityQueue()
q := NewPriorityQueue(nil)
q.Add(&medPriorityPod)
q.Add(&unschedulablePod)
q.Add(&highPriorityPod)
......@@ -126,8 +139,8 @@ func TestPriorityQueue_Add(t *testing.T) {
}
func TestPriorityQueue_AddIfNotPresent(t *testing.T) {
q := NewPriorityQueue()
q.unschedulableQ.addOrUpdate(&highPriNominatedPod)
q := NewPriorityQueue(nil)
addOrUpdateUnschedulablePod(q, &highPriNominatedPod)
q.AddIfNotPresent(&highPriNominatedPod) // Must not add anything.
q.AddIfNotPresent(&medPriorityPod)
q.AddIfNotPresent(&unschedulablePod)
......@@ -152,13 +165,13 @@ func TestPriorityQueue_AddIfNotPresent(t *testing.T) {
if len(q.nominatedPods.nominatedPods["node1"]) != 2 {
t.Errorf("Expected medPriorityPod and unschedulablePod to be still present in nomindatePods: %v", q.nominatedPods.nominatedPods["node1"])
}
if q.unschedulableQ.get(&highPriNominatedPod) != &highPriNominatedPod {
if getUnschedulablePod(q, &highPriNominatedPod) != &highPriNominatedPod {
t.Errorf("Pod %v was not found in the unschedulableQ.", highPriNominatedPod.Name)
}
}
func TestPriorityQueue_AddUnschedulableIfNotPresent(t *testing.T) {
q := NewPriorityQueue()
q := NewPriorityQueue(nil)
q.Add(&highPriNominatedPod)
q.AddUnschedulableIfNotPresent(&highPriNominatedPod) // Must not add anything.
q.AddUnschedulableIfNotPresent(&medPriorityPod) // This should go to activeQ.
......@@ -185,13 +198,13 @@ func TestPriorityQueue_AddUnschedulableIfNotPresent(t *testing.T) {
if len(q.nominatedPods.nominatedPods) != 1 {
t.Errorf("Expected nomindatePods to have one element: %v", q.nominatedPods)
}
if q.unschedulableQ.get(&unschedulablePod) != &unschedulablePod {
if getUnschedulablePod(q, &unschedulablePod) != &unschedulablePod {
t.Errorf("Pod %v was not found in the unschedulableQ.", unschedulablePod.Name)
}
}
func TestPriorityQueue_Pop(t *testing.T) {
q := NewPriorityQueue()
q := NewPriorityQueue(nil)
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
......@@ -208,7 +221,7 @@ func TestPriorityQueue_Pop(t *testing.T) {
}
func TestPriorityQueue_Update(t *testing.T) {
q := NewPriorityQueue()
q := NewPriorityQueue(nil)
q.Update(nil, &highPriorityPod)
if _, exists, _ := q.activeQ.Get(&highPriorityPod); !exists {
t.Errorf("Expected %v to be added to activeQ.", highPriorityPod.Name)
......@@ -244,7 +257,7 @@ func TestPriorityQueue_Update(t *testing.T) {
}
func TestPriorityQueue_Delete(t *testing.T) {
q := NewPriorityQueue()
q := NewPriorityQueue(nil)
q.Update(&highPriorityPod, &highPriNominatedPod)
q.Add(&unschedulablePod)
q.Delete(&highPriNominatedPod)
......@@ -266,10 +279,10 @@ func TestPriorityQueue_Delete(t *testing.T) {
}
func TestPriorityQueue_MoveAllToActiveQueue(t *testing.T) {
q := NewPriorityQueue()
q := NewPriorityQueue(nil)
q.Add(&medPriorityPod)
q.unschedulableQ.addOrUpdate(&unschedulablePod)
q.unschedulableQ.addOrUpdate(&highPriorityPod)
addOrUpdateUnschedulablePod(q, &unschedulablePod)
addOrUpdateUnschedulablePod(q, &highPriorityPod)
q.MoveAllToActiveQueue()
if q.activeQ.data.Len() != 3 {
t.Error("Expected all items to be in activeQ.")
......@@ -312,28 +325,28 @@ func TestPriorityQueue_AssignedPodAdded(t *testing.T) {
Spec: v1.PodSpec{NodeName: "machine1"},
}
q := NewPriorityQueue()
q := NewPriorityQueue(nil)
q.Add(&medPriorityPod)
// Add a couple of pods to the unschedulableQ.
q.unschedulableQ.addOrUpdate(&unschedulablePod)
q.unschedulableQ.addOrUpdate(affinityPod)
addOrUpdateUnschedulablePod(q, &unschedulablePod)
addOrUpdateUnschedulablePod(q, affinityPod)
// Simulate addition of an assigned pod. The pod has matching labels for
// affinityPod. So, affinityPod should go to activeQ.
q.AssignedPodAdded(&labelPod)
if q.unschedulableQ.get(affinityPod) != nil {
if getUnschedulablePod(q, affinityPod) != nil {
t.Error("affinityPod is still in the unschedulableQ.")
}
if _, exists, _ := q.activeQ.Get(affinityPod); !exists {
t.Error("affinityPod is not moved to activeQ.")
}
// Check that the other pod is still in the unschedulableQ.
if q.unschedulableQ.get(&unschedulablePod) == nil {
if getUnschedulablePod(q, &unschedulablePod) == nil {
t.Error("unschedulablePod is not in the unschedulableQ.")
}
}
func TestPriorityQueue_NominatedPodsForNode(t *testing.T) {
q := NewPriorityQueue()
q := NewPriorityQueue(nil)
q.Add(&medPriorityPod)
q.Add(&unschedulablePod)
q.Add(&highPriorityPod)
......@@ -350,7 +363,7 @@ func TestPriorityQueue_NominatedPodsForNode(t *testing.T) {
}
func TestPriorityQueue_UpdateNominatedPodForNode(t *testing.T) {
q := NewPriorityQueue()
q := NewPriorityQueue(nil)
if err := q.Add(&medPriorityPod); err != nil {
t.Errorf("add failed: %v", err)
}
......@@ -581,7 +594,7 @@ func TestSchedulingQueue_Close(t *testing.T) {
},
{
name: "PriorityQueue close",
q: NewPriorityQueue(),
q: NewPriorityQueue(nil),
expectedErr: fmt.Errorf(queueClosed),
},
}
......@@ -610,7 +623,7 @@ func TestSchedulingQueue_Close(t *testing.T) {
// ensures that an unschedulable pod does not block head of the queue when there
// are frequent events that move pods to the active queue.
func TestRecentlyTriedPodsGoBack(t *testing.T) {
q := NewPriorityQueue()
q := NewPriorityQueue(nil)
// Add a few pods to priority queue.
for i := 0; i < 5; i++ {
p := v1.Pod{
......@@ -664,7 +677,7 @@ func TestRecentlyTriedPodsGoBack(t *testing.T) {
// This behavior ensures that an unschedulable pod does not block head of the queue when there
// are frequent events that move pods to the active queue.
func TestPodFailedSchedulingMultipleTimesDoesNotBlockNewerPod(t *testing.T) {
q := NewPriorityQueue()
q := NewPriorityQueue(nil)
// Add an unschedulable pod to a priority queue.
// This makes a situation that the pod was tried to schedule
......@@ -746,3 +759,63 @@ func TestPodFailedSchedulingMultipleTimesDoesNotBlockNewerPod(t *testing.T) {
t.Errorf("Expected that test-newer-pod was popped, got %v", p2.Name)
}
}
// TestHighProirotyFlushUnschedulableQLeftover tests that pods will be moved to
// activeQ after one minutes if it is in unschedulableQ
func TestHighProirotyFlushUnschedulableQLeftover(t *testing.T) {
q := NewPriorityQueue(nil)
midPod := v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-midpod",
Namespace: "ns1",
UID: types.UID("tp-mid"),
},
Spec: v1.PodSpec{
Priority: &midPriority,
},
Status: v1.PodStatus{
NominatedNodeName: "node1",
},
}
highPod := v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "test-highpod",
Namespace: "ns1",
UID: types.UID("tp-high"),
},
Spec: v1.PodSpec{
Priority: &highPriority,
},
Status: v1.PodStatus{
NominatedNodeName: "node1",
},
}
addOrUpdateUnschedulablePod(q, &highPod)
addOrUpdateUnschedulablePod(q, &midPod)
// Update pod condition to highPod.
podutil.UpdatePodCondition(&highPod.Status, &v1.PodCondition{
Type: v1.PodScheduled,
Status: v1.ConditionFalse,
Reason: v1.PodReasonUnschedulable,
Message: "fake scheduling failure",
LastProbeTime: metav1.Time{Time: time.Now().Add(-1 * unschedulableQTimeInterval)},
})
// Update pod condition to midPod.
podutil.UpdatePodCondition(&midPod.Status, &v1.PodCondition{
Type: v1.PodScheduled,
Status: v1.ConditionFalse,
Reason: v1.PodReasonUnschedulable,
Message: "fake scheduling failure",
LastProbeTime: metav1.Time{Time: time.Now().Add(-1 * unschedulableQTimeInterval)},
})
if p, err := q.Pop(); err != nil || p != &highPod {
t.Errorf("Expected: %v after Pop, but got: %v", highPriorityPod.Name, p.Name)
}
if p, err := q.Pop(); err != nil || p != &midPod {
t.Errorf("Expected: %v after Pop, but got: %v", medPriorityPod.Name, p.Name)
}
}
......@@ -25,6 +25,7 @@ go_library(
name = "go_default_library",
srcs = [
"backoff_utils.go",
"clock.go",
"utils.go",
],
importpath = "k8s.io/kubernetes/pkg/scheduler/util",
......
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"time"
)
// Clock provides an interface for getting the current time
type Clock interface {
Now() time.Time
}
// RealClock implements a clock using time
type RealClock struct{}
// Now returns the current time with time.Now
func (RealClock) Now() time.Time {
return time.Now()
}
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