Unverified Commit 2fdd328d authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #67556 from msau42/fix-assume

Automatic merge from submit-queue (batch tested with PRs 67709, 67556). If you want to cherry-pick this change to another branch, please follow the instructions here: https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md. Fix volume scheduling issue with pod affinity and anti-affinity **What this PR does / why we need it**: The previous design of the volume scheduler had volume assume + bind done before pod assume + bind. This causes issues when trying to evaluate future pods with pod affinity/anti-affinity because the pod has not been assumed while the volumes have been decided. This PR changes the design so that volume and pod are assumed first, followed by volume and pod binding. Volume binding waits (asynchronously) for the operations to complete or error. This eliminates the subsequent passes through the scheduler to wait for volume binding to complete (although pod events or resyncs may still cause the pod to run through scheduling while binding is still in progress). This design also aligns better with the scheduler framework design, so will make it easier to migrate in the future. Many changes had to be made in the volume scheduler to handle this new design, mostly around: * How we cache pending binding operations. Now, any delayed binding PVC that is not fully bound must have a cached binding operation. This also means bind API updates may be repeated. * Waiting for the bind operation to fully complete, and detecting failure conditions to abort the bind and retry scheduling. **Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*: Fixes #65131 **Special notes for your reviewer**: **Release note**: ```release-note Fixes issue where pod scheduling may fail when using local PVs and pod affinity and anti-affinity without the default StatefulSet OrderedReady pod management policy ```
parents 743e4fba e1241599
...@@ -144,6 +144,7 @@ users: ...@@ -144,6 +144,7 @@ users:
} }
defaultSource := "DefaultProvider" defaultSource := "DefaultProvider"
defaultBindTimeoutSeconds := int64(600)
testcases := []struct { testcases := []struct {
name string name string
...@@ -157,7 +158,10 @@ users: ...@@ -157,7 +158,10 @@ users:
options: &Options{ options: &Options{
ConfigFile: configFile, ConfigFile: configFile,
ComponentConfig: func() kubeschedulerconfig.KubeSchedulerConfiguration { ComponentConfig: func() kubeschedulerconfig.KubeSchedulerConfiguration {
cfg, _ := newDefaultComponentConfig() cfg, err := newDefaultComponentConfig()
if err != nil {
t.Fatal(err)
}
return *cfg return *cfg
}(), }(),
}, },
...@@ -187,6 +191,7 @@ users: ...@@ -187,6 +191,7 @@ users:
ContentType: "application/vnd.kubernetes.protobuf", ContentType: "application/vnd.kubernetes.protobuf",
}, },
PercentageOfNodesToScore: 50, PercentageOfNodesToScore: 50,
BindTimeoutSeconds: &defaultBindTimeoutSeconds,
}, },
}, },
{ {
...@@ -229,6 +234,7 @@ users: ...@@ -229,6 +234,7 @@ users:
ContentType: "application/vnd.kubernetes.protobuf", ContentType: "application/vnd.kubernetes.protobuf",
}, },
PercentageOfNodesToScore: 50, PercentageOfNodesToScore: 50,
BindTimeoutSeconds: &defaultBindTimeoutSeconds,
}, },
}, },
{ {
......
...@@ -305,6 +305,7 @@ func NewSchedulerConfig(s schedulerserverconfig.CompletedConfig) (*scheduler.Con ...@@ -305,6 +305,7 @@ func NewSchedulerConfig(s schedulerserverconfig.CompletedConfig) (*scheduler.Con
EnableEquivalenceClassCache: utilfeature.DefaultFeatureGate.Enabled(features.EnableEquivalenceClassCache), EnableEquivalenceClassCache: utilfeature.DefaultFeatureGate.Enabled(features.EnableEquivalenceClassCache),
DisablePreemption: s.ComponentConfig.DisablePreemption, DisablePreemption: s.ComponentConfig.DisablePreemption,
PercentageOfNodesToScore: s.ComponentConfig.PercentageOfNodesToScore, PercentageOfNodesToScore: s.ComponentConfig.PercentageOfNodesToScore,
BindTimeoutSeconds: *s.ComponentConfig.BindTimeoutSeconds,
}) })
source := s.ComponentConfig.AlgorithmSource source := s.ComponentConfig.AlgorithmSource
......
...@@ -83,7 +83,8 @@ func (e *errObjectName) Error() string { ...@@ -83,7 +83,8 @@ func (e *errObjectName) Error() string {
// Restore() sets the latest object pointer back to the informer object. // Restore() sets the latest object pointer back to the informer object.
// Get/List() always returns the latest object pointer. // Get/List() always returns the latest object pointer.
type assumeCache struct { type assumeCache struct {
mutex sync.Mutex // Synchronizes updates to store
rwMutex sync.RWMutex
// describes the object stored // describes the object stored
description string description string
...@@ -155,8 +156,8 @@ func (c *assumeCache) add(obj interface{}) { ...@@ -155,8 +156,8 @@ func (c *assumeCache) add(obj interface{}) {
return return
} }
c.mutex.Lock() c.rwMutex.Lock()
defer c.mutex.Unlock() defer c.rwMutex.Unlock()
if objInfo, _ := c.getObjInfo(name); objInfo != nil { if objInfo, _ := c.getObjInfo(name); objInfo != nil {
newVersion, err := c.getObjVersion(name, obj) newVersion, err := c.getObjVersion(name, obj)
...@@ -199,8 +200,8 @@ func (c *assumeCache) delete(obj interface{}) { ...@@ -199,8 +200,8 @@ func (c *assumeCache) delete(obj interface{}) {
return return
} }
c.mutex.Lock() c.rwMutex.Lock()
defer c.mutex.Unlock() defer c.rwMutex.Unlock()
objInfo := &objInfo{name: name} objInfo := &objInfo{name: name}
err = c.store.Delete(objInfo) err = c.store.Delete(objInfo)
...@@ -239,8 +240,8 @@ func (c *assumeCache) getObjInfo(name string) (*objInfo, error) { ...@@ -239,8 +240,8 @@ func (c *assumeCache) getObjInfo(name string) (*objInfo, error) {
} }
func (c *assumeCache) Get(objName string) (interface{}, error) { func (c *assumeCache) Get(objName string) (interface{}, error) {
c.mutex.Lock() c.rwMutex.RLock()
defer c.mutex.Unlock() defer c.rwMutex.RUnlock()
objInfo, err := c.getObjInfo(objName) objInfo, err := c.getObjInfo(objName)
if err != nil { if err != nil {
...@@ -250,8 +251,8 @@ func (c *assumeCache) Get(objName string) (interface{}, error) { ...@@ -250,8 +251,8 @@ func (c *assumeCache) Get(objName string) (interface{}, error) {
} }
func (c *assumeCache) List(indexObj interface{}) []interface{} { func (c *assumeCache) List(indexObj interface{}) []interface{} {
c.mutex.Lock() c.rwMutex.RLock()
defer c.mutex.Unlock() defer c.rwMutex.RUnlock()
allObjs := []interface{}{} allObjs := []interface{}{}
objs, err := c.store.Index(c.indexName, &objInfo{latestObj: indexObj}) objs, err := c.store.Index(c.indexName, &objInfo{latestObj: indexObj})
...@@ -277,8 +278,8 @@ func (c *assumeCache) Assume(obj interface{}) error { ...@@ -277,8 +278,8 @@ func (c *assumeCache) Assume(obj interface{}) error {
return &errObjectName{err} return &errObjectName{err}
} }
c.mutex.Lock() c.rwMutex.Lock()
defer c.mutex.Unlock() defer c.rwMutex.Unlock()
objInfo, err := c.getObjInfo(name) objInfo, err := c.getObjInfo(name)
if err != nil { if err != nil {
...@@ -306,8 +307,8 @@ func (c *assumeCache) Assume(obj interface{}) error { ...@@ -306,8 +307,8 @@ func (c *assumeCache) Assume(obj interface{}) error {
} }
func (c *assumeCache) Restore(objName string) { func (c *assumeCache) Restore(objName string) {
c.mutex.Lock() c.rwMutex.Lock()
defer c.mutex.Unlock() defer c.rwMutex.Unlock()
objInfo, err := c.getObjInfo(objName) objInfo, err := c.getObjInfo(objName)
if err != nil { if err != nil {
......
...@@ -31,6 +31,8 @@ type PodBindingCache interface { ...@@ -31,6 +31,8 @@ type PodBindingCache interface {
UpdateBindings(pod *v1.Pod, node string, bindings []*bindingInfo) UpdateBindings(pod *v1.Pod, node string, bindings []*bindingInfo)
// GetBindings will return the cached bindings for the given pod and node. // GetBindings will return the cached bindings for the given pod and node.
// A nil return value means that the entry was not found. An empty slice
// means that no binding operations are needed.
GetBindings(pod *v1.Pod, node string) []*bindingInfo GetBindings(pod *v1.Pod, node string) []*bindingInfo
// UpdateProvisionedPVCs will update the cache with the given provisioning decisions // UpdateProvisionedPVCs will update the cache with the given provisioning decisions
...@@ -38,6 +40,8 @@ type PodBindingCache interface { ...@@ -38,6 +40,8 @@ type PodBindingCache interface {
UpdateProvisionedPVCs(pod *v1.Pod, node string, provisionings []*v1.PersistentVolumeClaim) UpdateProvisionedPVCs(pod *v1.Pod, node string, provisionings []*v1.PersistentVolumeClaim)
// GetProvisionedPVCs will return the cached provisioning decisions for the given pod and node. // GetProvisionedPVCs will return the cached provisioning decisions for the given pod and node.
// A nil return value means that the entry was not found. An empty slice
// means that no provisioning operations are needed.
GetProvisionedPVCs(pod *v1.Pod, node string) []*v1.PersistentVolumeClaim GetProvisionedPVCs(pod *v1.Pod, node string) []*v1.PersistentVolumeClaim
// DeleteBindings will remove all cached bindings and provisionings for the given pod. // DeleteBindings will remove all cached bindings and provisionings for the given pod.
...@@ -46,7 +50,8 @@ type PodBindingCache interface { ...@@ -46,7 +50,8 @@ type PodBindingCache interface {
} }
type podBindingCache struct { type podBindingCache struct {
mutex sync.Mutex // synchronizes bindingDecisions
rwMutex sync.RWMutex
// Key = pod name // Key = pod name
// Value = nodeDecisions // Value = nodeDecisions
...@@ -68,16 +73,16 @@ func NewPodBindingCache() PodBindingCache { ...@@ -68,16 +73,16 @@ func NewPodBindingCache() PodBindingCache {
} }
func (c *podBindingCache) DeleteBindings(pod *v1.Pod) { func (c *podBindingCache) DeleteBindings(pod *v1.Pod) {
c.mutex.Lock() c.rwMutex.Lock()
defer c.mutex.Unlock() defer c.rwMutex.Unlock()
podName := getPodName(pod) podName := getPodName(pod)
delete(c.bindingDecisions, podName) delete(c.bindingDecisions, podName)
} }
func (c *podBindingCache) UpdateBindings(pod *v1.Pod, node string, bindings []*bindingInfo) { func (c *podBindingCache) UpdateBindings(pod *v1.Pod, node string, bindings []*bindingInfo) {
c.mutex.Lock() c.rwMutex.Lock()
defer c.mutex.Unlock() defer c.rwMutex.Unlock()
podName := getPodName(pod) podName := getPodName(pod)
decisions, ok := c.bindingDecisions[podName] decisions, ok := c.bindingDecisions[podName]
...@@ -97,8 +102,8 @@ func (c *podBindingCache) UpdateBindings(pod *v1.Pod, node string, bindings []*b ...@@ -97,8 +102,8 @@ func (c *podBindingCache) UpdateBindings(pod *v1.Pod, node string, bindings []*b
} }
func (c *podBindingCache) GetBindings(pod *v1.Pod, node string) []*bindingInfo { func (c *podBindingCache) GetBindings(pod *v1.Pod, node string) []*bindingInfo {
c.mutex.Lock() c.rwMutex.RLock()
defer c.mutex.Unlock() defer c.rwMutex.RUnlock()
podName := getPodName(pod) podName := getPodName(pod)
decisions, ok := c.bindingDecisions[podName] decisions, ok := c.bindingDecisions[podName]
...@@ -113,8 +118,8 @@ func (c *podBindingCache) GetBindings(pod *v1.Pod, node string) []*bindingInfo { ...@@ -113,8 +118,8 @@ func (c *podBindingCache) GetBindings(pod *v1.Pod, node string) []*bindingInfo {
} }
func (c *podBindingCache) UpdateProvisionedPVCs(pod *v1.Pod, node string, pvcs []*v1.PersistentVolumeClaim) { func (c *podBindingCache) UpdateProvisionedPVCs(pod *v1.Pod, node string, pvcs []*v1.PersistentVolumeClaim) {
c.mutex.Lock() c.rwMutex.Lock()
defer c.mutex.Unlock() defer c.rwMutex.Unlock()
podName := getPodName(pod) podName := getPodName(pod)
decisions, ok := c.bindingDecisions[podName] decisions, ok := c.bindingDecisions[podName]
...@@ -134,8 +139,8 @@ func (c *podBindingCache) UpdateProvisionedPVCs(pod *v1.Pod, node string, pvcs [ ...@@ -134,8 +139,8 @@ func (c *podBindingCache) UpdateProvisionedPVCs(pod *v1.Pod, node string, pvcs [
} }
func (c *podBindingCache) GetProvisionedPVCs(pod *v1.Pod, node string) []*v1.PersistentVolumeClaim { func (c *podBindingCache) GetProvisionedPVCs(pod *v1.Pod, node string) []*v1.PersistentVolumeClaim {
c.mutex.Lock() c.rwMutex.RLock()
defer c.mutex.Unlock() defer c.rwMutex.RUnlock()
podName := getPodName(pod) podName := getPodName(pod)
decisions, ok := c.bindingDecisions[podName] decisions, ok := c.bindingDecisions[podName]
......
...@@ -16,18 +16,15 @@ limitations under the License. ...@@ -16,18 +16,15 @@ limitations under the License.
package persistentvolume package persistentvolume
import ( import "k8s.io/api/core/v1"
"k8s.io/api/core/v1"
)
type FakeVolumeBinderConfig struct { type FakeVolumeBinderConfig struct {
AllBound bool AllBound bool
FindUnboundSatsified bool FindUnboundSatsified bool
FindBoundSatsified bool FindBoundSatsified bool
FindErr error FindErr error
AssumeBindingRequired bool AssumeErr error
AssumeErr error BindErr error
BindErr error
} }
// NewVolumeBinder sets up all the caches needed for the scheduler to make // NewVolumeBinder sets up all the caches needed for the scheduler to make
...@@ -48,9 +45,9 @@ func (b *FakeVolumeBinder) FindPodVolumes(pod *v1.Pod, node *v1.Node) (unboundVo ...@@ -48,9 +45,9 @@ func (b *FakeVolumeBinder) FindPodVolumes(pod *v1.Pod, node *v1.Node) (unboundVo
return b.config.FindUnboundSatsified, b.config.FindBoundSatsified, b.config.FindErr return b.config.FindUnboundSatsified, b.config.FindBoundSatsified, b.config.FindErr
} }
func (b *FakeVolumeBinder) AssumePodVolumes(assumedPod *v1.Pod, nodeName string) (bool, bool, error) { func (b *FakeVolumeBinder) AssumePodVolumes(assumedPod *v1.Pod, nodeName string) (bool, error) {
b.AssumeCalled = true b.AssumeCalled = true
return b.config.AllBound, b.config.AssumeBindingRequired, b.config.AssumeErr return b.config.AllBound, b.config.AssumeErr
} }
func (b *FakeVolumeBinder) BindPodVolumes(assumedPod *v1.Pod) error { func (b *FakeVolumeBinder) BindPodVolumes(assumedPod *v1.Pod) error {
......
...@@ -85,6 +85,11 @@ type KubeSchedulerConfiguration struct { ...@@ -85,6 +85,11 @@ type KubeSchedulerConfiguration struct {
// DEPRECATED. // DEPRECATED.
// Indicate the "all topologies" set for empty topologyKey when it's used for PreferredDuringScheduling pod anti-affinity. // Indicate the "all topologies" set for empty topologyKey when it's used for PreferredDuringScheduling pod anti-affinity.
FailureDomains string FailureDomains string
// Duration to wait for a binding operation to complete before timing out
// Value must be non-negative integer. The value zero indicates no waiting.
// If this value is nil, the default value will be used.
BindTimeoutSeconds *int64
} }
// SchedulerAlgorithmSource is the source of a scheduler algorithm. One source // SchedulerAlgorithmSource is the source of a scheduler algorithm. One source
......
...@@ -23,6 +23,7 @@ import ( ...@@ -23,6 +23,7 @@ import (
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
apiserverconfigv1alpha1 "k8s.io/apiserver/pkg/apis/config/v1alpha1" apiserverconfigv1alpha1 "k8s.io/apiserver/pkg/apis/config/v1alpha1"
kubescedulerconfigv1alpha1 "k8s.io/kube-scheduler/config/v1alpha1" kubescedulerconfigv1alpha1 "k8s.io/kube-scheduler/config/v1alpha1"
// this package shouldn't really depend on other k8s.io/kubernetes code // this package shouldn't really depend on other k8s.io/kubernetes code
api "k8s.io/kubernetes/pkg/apis/core" api "k8s.io/kubernetes/pkg/apis/core"
kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis" kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis"
...@@ -102,4 +103,9 @@ func SetDefaults_KubeSchedulerConfiguration(obj *kubescedulerconfigv1alpha1.Kube ...@@ -102,4 +103,9 @@ func SetDefaults_KubeSchedulerConfiguration(obj *kubescedulerconfigv1alpha1.Kube
// Use the default LeaderElectionConfiguration options // Use the default LeaderElectionConfiguration options
apiserverconfigv1alpha1.RecommendedDefaultLeaderElectionConfiguration(&obj.LeaderElection.LeaderElectionConfiguration) apiserverconfigv1alpha1.RecommendedDefaultLeaderElectionConfiguration(&obj.LeaderElection.LeaderElectionConfiguration)
if obj.BindTimeoutSeconds == nil {
defaultBindTimeoutSeconds := int64(600)
obj.BindTimeoutSeconds = &defaultBindTimeoutSeconds
}
} }
...@@ -121,6 +121,7 @@ func autoConvert_v1alpha1_KubeSchedulerConfiguration_To_config_KubeSchedulerConf ...@@ -121,6 +121,7 @@ func autoConvert_v1alpha1_KubeSchedulerConfiguration_To_config_KubeSchedulerConf
out.DisablePreemption = in.DisablePreemption out.DisablePreemption = in.DisablePreemption
out.PercentageOfNodesToScore = in.PercentageOfNodesToScore out.PercentageOfNodesToScore = in.PercentageOfNodesToScore
out.FailureDomains = in.FailureDomains out.FailureDomains = in.FailureDomains
out.BindTimeoutSeconds = (*int64)(unsafe.Pointer(in.BindTimeoutSeconds))
return nil return nil
} }
...@@ -149,6 +150,7 @@ func autoConvert_config_KubeSchedulerConfiguration_To_v1alpha1_KubeSchedulerConf ...@@ -149,6 +150,7 @@ func autoConvert_config_KubeSchedulerConfiguration_To_v1alpha1_KubeSchedulerConf
out.DisablePreemption = in.DisablePreemption out.DisablePreemption = in.DisablePreemption
out.PercentageOfNodesToScore = in.PercentageOfNodesToScore out.PercentageOfNodesToScore = in.PercentageOfNodesToScore
out.FailureDomains = in.FailureDomains out.FailureDomains = in.FailureDomains
out.BindTimeoutSeconds = (*int64)(unsafe.Pointer(in.BindTimeoutSeconds))
return nil return nil
} }
......
...@@ -41,6 +41,9 @@ func ValidateKubeSchedulerConfiguration(cc *config.KubeSchedulerConfiguration) f ...@@ -41,6 +41,9 @@ func ValidateKubeSchedulerConfiguration(cc *config.KubeSchedulerConfiguration) f
if cc.HardPodAffinitySymmetricWeight < 0 || cc.HardPodAffinitySymmetricWeight > 100 { if cc.HardPodAffinitySymmetricWeight < 0 || cc.HardPodAffinitySymmetricWeight > 100 {
allErrs = append(allErrs, field.Invalid(field.NewPath("hardPodAffinitySymmetricWeight"), cc.HardPodAffinitySymmetricWeight, "not in valid range 0-100")) allErrs = append(allErrs, field.Invalid(field.NewPath("hardPodAffinitySymmetricWeight"), cc.HardPodAffinitySymmetricWeight, "not in valid range 0-100"))
} }
if cc.BindTimeoutSeconds == nil {
allErrs = append(allErrs, field.Required(field.NewPath("bindTimeoutSeconds"), ""))
}
return allErrs return allErrs
} }
......
...@@ -17,15 +17,17 @@ limitations under the License. ...@@ -17,15 +17,17 @@ limitations under the License.
package validation package validation
import ( import (
"testing"
"time"
apimachinery "k8s.io/apimachinery/pkg/apis/config" apimachinery "k8s.io/apimachinery/pkg/apis/config"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
apiserver "k8s.io/apiserver/pkg/apis/config" apiserver "k8s.io/apiserver/pkg/apis/config"
"k8s.io/kubernetes/pkg/scheduler/apis/config" "k8s.io/kubernetes/pkg/scheduler/apis/config"
"testing"
"time"
) )
func TestValidateKubeSchedulerConfiguration(t *testing.T) { func TestValidateKubeSchedulerConfiguration(t *testing.T) {
testTimeout := int64(0)
validConfig := &config.KubeSchedulerConfiguration{ validConfig := &config.KubeSchedulerConfiguration{
SchedulerName: "me", SchedulerName: "me",
HealthzBindAddress: "0.0.0.0:10254", HealthzBindAddress: "0.0.0.0:10254",
...@@ -56,6 +58,7 @@ func TestValidateKubeSchedulerConfiguration(t *testing.T) { ...@@ -56,6 +58,7 @@ func TestValidateKubeSchedulerConfiguration(t *testing.T) {
RetryPeriod: metav1.Duration{Duration: 5 * time.Second}, RetryPeriod: metav1.Duration{Duration: 5 * time.Second},
}, },
}, },
BindTimeoutSeconds: &testTimeout,
} }
HardPodAffinitySymmetricWeightGt100 := validConfig.DeepCopy() HardPodAffinitySymmetricWeightGt100 := validConfig.DeepCopy()
...@@ -86,6 +89,9 @@ func TestValidateKubeSchedulerConfiguration(t *testing.T) { ...@@ -86,6 +89,9 @@ func TestValidateKubeSchedulerConfiguration(t *testing.T) {
enableContentProfilingSetWithoutEnableProfiling.EnableProfiling = false enableContentProfilingSetWithoutEnableProfiling.EnableProfiling = false
enableContentProfilingSetWithoutEnableProfiling.EnableContentionProfiling = true enableContentProfilingSetWithoutEnableProfiling.EnableContentionProfiling = true
bindTimeoutUnset := validConfig.DeepCopy()
bindTimeoutUnset.BindTimeoutSeconds = nil
scenarios := map[string]struct { scenarios := map[string]struct {
expectedToFail bool expectedToFail bool
config *config.KubeSchedulerConfiguration config *config.KubeSchedulerConfiguration
...@@ -126,6 +132,10 @@ func TestValidateKubeSchedulerConfiguration(t *testing.T) { ...@@ -126,6 +132,10 @@ func TestValidateKubeSchedulerConfiguration(t *testing.T) {
expectedToFail: true, expectedToFail: true,
config: HardPodAffinitySymmetricWeightLt0, config: HardPodAffinitySymmetricWeightLt0,
}, },
"bind-timeout-unset": {
expectedToFail: true,
config: bindTimeoutUnset,
},
} }
for name, scenario := range scenarios { for name, scenario := range scenarios {
......
...@@ -32,6 +32,11 @@ func (in *KubeSchedulerConfiguration) DeepCopyInto(out *KubeSchedulerConfigurati ...@@ -32,6 +32,11 @@ func (in *KubeSchedulerConfiguration) DeepCopyInto(out *KubeSchedulerConfigurati
out.LeaderElection = in.LeaderElection out.LeaderElection = in.LeaderElection
out.ClientConnection = in.ClientConnection out.ClientConnection = in.ClientConnection
out.DebuggingConfiguration = in.DebuggingConfiguration out.DebuggingConfiguration = in.DebuggingConfiguration
if in.BindTimeoutSeconds != nil {
in, out := &in.BindTimeoutSeconds, &out.BindTimeoutSeconds
*out = new(int64)
**out = **in
}
return return
} }
......
...@@ -159,6 +159,7 @@ type ConfigFactoryArgs struct { ...@@ -159,6 +159,7 @@ type ConfigFactoryArgs struct {
EnableEquivalenceClassCache bool EnableEquivalenceClassCache bool
DisablePreemption bool DisablePreemption bool
PercentageOfNodesToScore int32 PercentageOfNodesToScore int32
BindTimeoutSeconds int64
} }
// NewConfigFactory initializes the default implementation of a Configurator To encourage eventual privatization of the struct type, we only // NewConfigFactory initializes the default implementation of a Configurator To encourage eventual privatization of the struct type, we only
...@@ -305,7 +306,7 @@ func NewConfigFactory(args *ConfigFactoryArgs) scheduler.Configurator { ...@@ -305,7 +306,7 @@ func NewConfigFactory(args *ConfigFactoryArgs) scheduler.Configurator {
if utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) { if utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) {
// Setup volume binder // Setup volume binder
c.volumeBinder = volumebinder.NewVolumeBinder(args.Client, args.PvcInformer, args.PvInformer, args.StorageClassInformer) c.volumeBinder = volumebinder.NewVolumeBinder(args.Client, args.PvcInformer, args.PvInformer, args.StorageClassInformer, time.Duration(args.BindTimeoutSeconds)*time.Second)
args.StorageClassInformer.Informer().AddEventHandler( args.StorageClassInformer.Informer().AddEventHandler(
cache.ResourceEventHandlerFuncs{ cache.ResourceEventHandlerFuncs{
......
...@@ -49,6 +49,7 @@ import ( ...@@ -49,6 +49,7 @@ import (
const ( const (
enableEquivalenceCache = true enableEquivalenceCache = true
disablePodPreemption = false disablePodPreemption = false
bindTimeoutSeconds = 600
) )
func TestCreate(t *testing.T) { func TestCreate(t *testing.T) {
...@@ -557,6 +558,7 @@ func newConfigFactory(client *clientset.Clientset, hardPodAffinitySymmetricWeigh ...@@ -557,6 +558,7 @@ func newConfigFactory(client *clientset.Clientset, hardPodAffinitySymmetricWeigh
enableEquivalenceCache, enableEquivalenceCache,
disablePodPreemption, disablePodPreemption,
schedulerapi.DefaultPercentageOfNodesToScore, schedulerapi.DefaultPercentageOfNodesToScore,
bindTimeoutSeconds,
}) })
} }
......
...@@ -17,7 +17,6 @@ limitations under the License. ...@@ -17,7 +17,6 @@ limitations under the License.
package scheduler package scheduler
import ( import (
"fmt"
"time" "time"
"k8s.io/api/core/v1" "k8s.io/api/core/v1"
...@@ -184,10 +183,6 @@ func (sched *Scheduler) Run() { ...@@ -184,10 +183,6 @@ func (sched *Scheduler) Run() {
return return
} }
if utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) {
go sched.config.VolumeBinder.Run(sched.bindVolumesWorker, sched.config.StopEverything)
}
go wait.Until(sched.scheduleOne, 0, sched.config.StopEverything) go wait.Until(sched.scheduleOne, 0, sched.config.StopEverything)
} }
...@@ -265,17 +260,12 @@ func (sched *Scheduler) preempt(preemptor *v1.Pod, scheduleErr error) (string, e ...@@ -265,17 +260,12 @@ func (sched *Scheduler) preempt(preemptor *v1.Pod, scheduleErr error) (string, e
return nodeName, err return nodeName, err
} }
// assumeAndBindVolumes will update the volume cache and then asynchronously bind volumes if required. // assumeVolumes will update the volume cache with the chosen bindings
//
// If volume binding is required, then the bind volumes routine will update the pod to send it back through
// the scheduler.
//
// Otherwise, return nil error and continue to assume the pod.
// //
// This function modifies assumed if volume binding is required. // This function modifies assumed if volume binding is required.
func (sched *Scheduler) assumeAndBindVolumes(assumed *v1.Pod, host string) error { func (sched *Scheduler) assumeVolumes(assumed *v1.Pod, host string) (allBound bool, err error) {
if utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) { if utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) {
allBound, bindingRequired, err := sched.config.VolumeBinder.Binder.AssumePodVolumes(assumed, host) allBound, err = sched.config.VolumeBinder.Binder.AssumePodVolumes(assumed, host)
if err != nil { if err != nil {
sched.config.Error(assumed, err) sched.config.Error(assumed, err)
sched.config.Recorder.Eventf(assumed, v1.EventTypeWarning, "FailedScheduling", "AssumePodVolumes failed: %v", err) sched.config.Recorder.Eventf(assumed, v1.EventTypeWarning, "FailedScheduling", "AssumePodVolumes failed: %v", err)
...@@ -285,76 +275,38 @@ func (sched *Scheduler) assumeAndBindVolumes(assumed *v1.Pod, host string) error ...@@ -285,76 +275,38 @@ func (sched *Scheduler) assumeAndBindVolumes(assumed *v1.Pod, host string) error
Reason: "SchedulerError", Reason: "SchedulerError",
Message: err.Error(), Message: err.Error(),
}) })
return err
} }
if !allBound { // Invalidate ecache because assumed volumes could have affected the cached
err = fmt.Errorf("Volume binding started, waiting for completion") // pvs for other pods
if bindingRequired { if sched.config.Ecache != nil {
if sched.config.Ecache != nil { invalidPredicates := sets.NewString(predicates.CheckVolumeBindingPred)
invalidPredicates := sets.NewString(predicates.CheckVolumeBindingPred) sched.config.Ecache.InvalidatePredicates(invalidPredicates)
sched.config.Ecache.InvalidatePredicates(invalidPredicates)
}
// bindVolumesWorker() will update the Pod object to put it back in the scheduler queue
sched.config.VolumeBinder.BindQueue.Add(assumed)
} else {
// We are just waiting for PV controller to finish binding, put it back in the
// scheduler queue
sched.config.Error(assumed, err)
sched.config.Recorder.Eventf(assumed, v1.EventTypeNormal, "FailedScheduling", "%v", err)
sched.config.PodConditionUpdater.Update(assumed, &v1.PodCondition{
Type: v1.PodScheduled,
Status: v1.ConditionFalse,
Reason: "VolumeBindingWaiting",
})
}
return err
} }
} }
return nil return
} }
// bindVolumesWorker() processes pods queued in assumeAndBindVolumes() and tries to // bindVolumes will make the API update with the assumed bindings and wait until
// make the API update for volume binding. // the PV controller has completely finished the binding operation.
// This function runs forever until the volume BindQueue is closed. //
func (sched *Scheduler) bindVolumesWorker() { // If binding errors, times out or gets undone, then an error will be returned to
workFunc := func() bool { // retry scheduling.
keyObj, quit := sched.config.VolumeBinder.BindQueue.Get() func (sched *Scheduler) bindVolumes(assumed *v1.Pod) error {
if quit { var reason string
return true var eventType string
}
defer sched.config.VolumeBinder.BindQueue.Done(keyObj) glog.V(5).Infof("Trying to bind volumes for pod \"%v/%v\"", assumed.Namespace, assumed.Name)
err := sched.config.VolumeBinder.Binder.BindPodVolumes(assumed)
assumed, ok := keyObj.(*v1.Pod) if err != nil {
if !ok { glog.V(1).Infof("Failed to bind volumes for pod \"%v/%v\": %v", assumed.Namespace, assumed.Name, err)
glog.V(4).Infof("Object is not a *v1.Pod")
return false
}
// TODO: add metrics
var reason string
var eventType string
glog.V(5).Infof("Trying to bind volumes for pod \"%v/%v\"", assumed.Namespace, assumed.Name)
// The Pod is always sent back to the scheduler afterwards. // Unassume the Pod and retry scheduling
err := sched.config.VolumeBinder.Binder.BindPodVolumes(assumed) if forgetErr := sched.config.SchedulerCache.ForgetPod(assumed); forgetErr != nil {
if err != nil { glog.Errorf("scheduler cache ForgetPod failed: %v", forgetErr)
glog.V(1).Infof("Failed to bind volumes for pod \"%v/%v\": %v", assumed.Namespace, assumed.Name, err)
reason = "VolumeBindingFailed"
eventType = v1.EventTypeWarning
} else {
glog.V(4).Infof("Successfully bound volumes for pod \"%v/%v\"", assumed.Namespace, assumed.Name)
reason = "VolumeBindingWaiting"
eventType = v1.EventTypeNormal
err = fmt.Errorf("Volume binding started, waiting for completion")
} }
// Always fail scheduling regardless of binding success. reason = "VolumeBindingFailed"
// The Pod needs to be sent back through the scheduler to: eventType = v1.EventTypeWarning
// * Retry volume binding if it fails.
// * Retry volume binding if dynamic provisioning fails.
// * Bind the Pod to the Node once all volumes are bound.
sched.config.Error(assumed, err) sched.config.Error(assumed, err)
sched.config.Recorder.Eventf(assumed, eventType, "FailedScheduling", "%v", err) sched.config.Recorder.Eventf(assumed, eventType, "FailedScheduling", "%v", err)
sched.config.PodConditionUpdater.Update(assumed, &v1.PodCondition{ sched.config.PodConditionUpdater.Update(assumed, &v1.PodCondition{
...@@ -362,15 +314,11 @@ func (sched *Scheduler) bindVolumesWorker() { ...@@ -362,15 +314,11 @@ func (sched *Scheduler) bindVolumesWorker() {
Status: v1.ConditionFalse, Status: v1.ConditionFalse,
Reason: reason, Reason: reason,
}) })
return false return err
} }
for { glog.V(5).Infof("Success binding volumes for pod \"%v/%v\"", assumed.Namespace, assumed.Name)
if quit := workFunc(); quit { return nil
glog.V(4).Infof("bindVolumesWorker shutting down")
break
}
}
} }
// assume signals to the cache that a pod is already in the cache, so that binding can be asynchronous. // assume signals to the cache that a pod is already in the cache, so that binding can be asynchronous.
...@@ -478,16 +426,12 @@ func (sched *Scheduler) scheduleOne() { ...@@ -478,16 +426,12 @@ func (sched *Scheduler) scheduleOne() {
// Assume volumes first before assuming the pod. // Assume volumes first before assuming the pod.
// //
// If no volumes need binding, then nil is returned, and continue to assume the pod. // If all volumes are completely bound, then allBound is true and binding will be skipped.
//
// Otherwise, error is returned and volume binding is started asynchronously for all of the pod's volumes.
// scheduleOne() returns immediately on error, so that it doesn't continue to assume the pod.
// //
// After the asynchronous volume binding updates are made, it will send the pod back through the scheduler for // Otherwise, binding of volumes is started after the pod is assumed, but before pod binding.
// subsequent passes until all volumes are fully bound.
// //
// This function modifies 'assumedPod' if volume binding is required. // This function modifies 'assumedPod' if volume binding is required.
err = sched.assumeAndBindVolumes(assumedPod, suggestedHost) allBound, err := sched.assumeVolumes(assumedPod, suggestedHost)
if err != nil { if err != nil {
return return
} }
...@@ -499,6 +443,14 @@ func (sched *Scheduler) scheduleOne() { ...@@ -499,6 +443,14 @@ func (sched *Scheduler) scheduleOne() {
} }
// bind the pod to its host asynchronously (we can do this b/c of the assumption step above). // bind the pod to its host asynchronously (we can do this b/c of the assumption step above).
go func() { go func() {
// Bind volumes first before Pod
if !allBound {
err = sched.bindVolumes(assumedPod)
if err != nil {
return
}
}
err := sched.bind(assumedPod, &v1.Binding{ err := sched.bind(assumedPod, &v1.Binding{
ObjectMeta: metav1.ObjectMeta{Namespace: assumedPod.Namespace, Name: assumedPod.Name, UID: assumedPod.UID}, ObjectMeta: metav1.ObjectMeta{Namespace: assumedPod.Namespace, Name: assumedPod.Name, UID: assumedPod.UID},
Target: v1.ObjectReference{ Target: v1.ObjectReference{
......
...@@ -707,8 +707,7 @@ func TestSchedulerWithVolumeBinding(t *testing.T) { ...@@ -707,8 +707,7 @@ func TestSchedulerWithVolumeBinding(t *testing.T) {
}, },
expectAssumeCalled: true, expectAssumeCalled: true,
expectPodBind: &v1.Binding{ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: types.UID("foo")}, Target: v1.ObjectReference{Kind: "Node", Name: "machine1"}}, expectPodBind: &v1.Binding{ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: types.UID("foo")}, Target: v1.ObjectReference{Kind: "Node", Name: "machine1"}},
eventReason: "Scheduled",
eventReason: "Scheduled",
}, },
{ {
name: "bound/invalid pv affinity", name: "bound/invalid pv affinity",
...@@ -739,28 +738,15 @@ func TestSchedulerWithVolumeBinding(t *testing.T) { ...@@ -739,28 +738,15 @@ func TestSchedulerWithVolumeBinding(t *testing.T) {
expectError: makePredicateError("1 node(s) didn't find available persistent volumes to bind, 1 node(s) had volume node affinity conflict"), expectError: makePredicateError("1 node(s) didn't find available persistent volumes to bind, 1 node(s) had volume node affinity conflict"),
}, },
{ {
name: "unbound/found matches", name: "unbound/found matches/bind succeeds",
volumeBinderConfig: &persistentvolume.FakeVolumeBinderConfig{ volumeBinderConfig: &persistentvolume.FakeVolumeBinderConfig{
FindUnboundSatsified: true, FindUnboundSatsified: true,
FindBoundSatsified: true, FindBoundSatsified: true,
AssumeBindingRequired: true,
}, },
expectAssumeCalled: true, expectAssumeCalled: true,
expectBindCalled: true, expectBindCalled: true,
eventReason: "FailedScheduling", expectPodBind: &v1.Binding{ObjectMeta: metav1.ObjectMeta{Name: "foo", UID: types.UID("foo")}, Target: v1.ObjectReference{Kind: "Node", Name: "machine1"}},
expectError: fmt.Errorf("Volume binding started, waiting for completion"), eventReason: "Scheduled",
},
{
name: "unbound/found matches/already-bound",
volumeBinderConfig: &persistentvolume.FakeVolumeBinderConfig{
FindUnboundSatsified: true,
FindBoundSatsified: true,
AssumeBindingRequired: false,
},
expectAssumeCalled: true,
expectBindCalled: false,
eventReason: "FailedScheduling",
expectError: fmt.Errorf("Volume binding started, waiting for completion"),
}, },
{ {
name: "predicate error", name: "predicate error",
...@@ -784,10 +770,9 @@ func TestSchedulerWithVolumeBinding(t *testing.T) { ...@@ -784,10 +770,9 @@ func TestSchedulerWithVolumeBinding(t *testing.T) {
{ {
name: "bind error", name: "bind error",
volumeBinderConfig: &persistentvolume.FakeVolumeBinderConfig{ volumeBinderConfig: &persistentvolume.FakeVolumeBinderConfig{
FindUnboundSatsified: true, FindUnboundSatsified: true,
FindBoundSatsified: true, FindBoundSatsified: true,
AssumeBindingRequired: true, BindErr: bindErr,
BindErr: bindErr,
}, },
expectAssumeCalled: true, expectAssumeCalled: true,
expectBindCalled: true, expectBindCalled: true,
...@@ -814,8 +799,6 @@ func TestSchedulerWithVolumeBinding(t *testing.T) { ...@@ -814,8 +799,6 @@ func TestSchedulerWithVolumeBinding(t *testing.T) {
close(eventChan) close(eventChan)
}) })
go fakeVolumeBinder.Run(s.bindVolumesWorker, stop)
s.scheduleOne() s.scheduleOne()
// Wait for pod to succeed or fail scheduling // Wait for pod to succeed or fail scheduling
......
...@@ -8,11 +8,9 @@ go_library( ...@@ -8,11 +8,9 @@ go_library(
deps = [ deps = [
"//pkg/controller/volume/persistentvolume:go_default_library", "//pkg/controller/volume/persistentvolume:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//staging/src/k8s.io/client-go/informers/core/v1:go_default_library", "//staging/src/k8s.io/client-go/informers/core/v1:go_default_library",
"//staging/src/k8s.io/client-go/informers/storage/v1:go_default_library", "//staging/src/k8s.io/client-go/informers/storage/v1:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library", "//staging/src/k8s.io/client-go/kubernetes:go_default_library",
"//staging/src/k8s.io/client-go/util/workqueue:go_default_library",
], ],
) )
......
...@@ -20,19 +20,15 @@ import ( ...@@ -20,19 +20,15 @@ import (
"time" "time"
"k8s.io/api/core/v1" "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/wait"
coreinformers "k8s.io/client-go/informers/core/v1" coreinformers "k8s.io/client-go/informers/core/v1"
storageinformers "k8s.io/client-go/informers/storage/v1" storageinformers "k8s.io/client-go/informers/storage/v1"
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/util/workqueue"
"k8s.io/kubernetes/pkg/controller/volume/persistentvolume" "k8s.io/kubernetes/pkg/controller/volume/persistentvolume"
) )
// VolumeBinder sets up the volume binding library and manages // VolumeBinder sets up the volume binding library
// the volume binding operations with a queue.
type VolumeBinder struct { type VolumeBinder struct {
Binder persistentvolume.SchedulerVolumeBinder Binder persistentvolume.SchedulerVolumeBinder
BindQueue *workqueue.Type
} }
// NewVolumeBinder sets up the volume binding library and binding queue // NewVolumeBinder sets up the volume binding library and binding queue
...@@ -40,30 +36,21 @@ func NewVolumeBinder( ...@@ -40,30 +36,21 @@ func NewVolumeBinder(
client clientset.Interface, client clientset.Interface,
pvcInformer coreinformers.PersistentVolumeClaimInformer, pvcInformer coreinformers.PersistentVolumeClaimInformer,
pvInformer coreinformers.PersistentVolumeInformer, pvInformer coreinformers.PersistentVolumeInformer,
storageClassInformer storageinformers.StorageClassInformer) *VolumeBinder { storageClassInformer storageinformers.StorageClassInformer,
bindTimeout time.Duration) *VolumeBinder {
return &VolumeBinder{ return &VolumeBinder{
Binder: persistentvolume.NewVolumeBinder(client, pvcInformer, pvInformer, storageClassInformer), Binder: persistentvolume.NewVolumeBinder(client, pvcInformer, pvInformer, storageClassInformer, bindTimeout),
BindQueue: workqueue.NewNamed("podsToBind"),
} }
} }
// NewFakeVolumeBinder sets up a fake volume binder and binding queue // NewFakeVolumeBinder sets up a fake volume binder and binding queue
func NewFakeVolumeBinder(config *persistentvolume.FakeVolumeBinderConfig) *VolumeBinder { func NewFakeVolumeBinder(config *persistentvolume.FakeVolumeBinderConfig) *VolumeBinder {
return &VolumeBinder{ return &VolumeBinder{
Binder: persistentvolume.NewFakeVolumeBinder(config), Binder: persistentvolume.NewFakeVolumeBinder(config),
BindQueue: workqueue.NewNamed("podsToBind"),
} }
} }
// Run starts a goroutine to handle the binding queue with the given function.
func (b *VolumeBinder) Run(bindWorkFunc func(), stopCh <-chan struct{}) {
go wait.Until(bindWorkFunc, time.Second, stopCh)
<-stopCh
b.BindQueue.ShutDown()
}
// DeletePodBindings will delete the cached volume bindings for the given pod. // DeletePodBindings will delete the cached volume bindings for the given pod.
func (b *VolumeBinder) DeletePodBindings(pod *v1.Pod) { func (b *VolumeBinder) DeletePodBindings(pod *v1.Pod) {
cache := b.Binder.GetBindingsCache() cache := b.Binder.GetBindingsCache()
......
...@@ -239,6 +239,7 @@ type FakeVolumePlugin struct { ...@@ -239,6 +239,7 @@ type FakeVolumePlugin struct {
VolumeLimits map[string]int64 VolumeLimits map[string]int64
VolumeLimitsError error VolumeLimitsError error
LimitKey string LimitKey string
ProvisionDelaySeconds int
Mounters []*FakeVolume Mounters []*FakeVolume
Unmounters []*FakeVolume Unmounters []*FakeVolume
...@@ -437,7 +438,7 @@ func (plugin *FakeVolumePlugin) NewProvisioner(options VolumeOptions) (Provision ...@@ -437,7 +438,7 @@ func (plugin *FakeVolumePlugin) NewProvisioner(options VolumeOptions) (Provision
plugin.Lock() plugin.Lock()
defer plugin.Unlock() defer plugin.Unlock()
plugin.LastProvisionerOptions = options plugin.LastProvisionerOptions = options
return &FakeProvisioner{options, plugin.Host}, nil return &FakeProvisioner{options, plugin.Host, plugin.ProvisionDelaySeconds}, nil
} }
func (plugin *FakeVolumePlugin) GetAccessModes() []v1.PersistentVolumeAccessMode { func (plugin *FakeVolumePlugin) GetAccessModes() []v1.PersistentVolumeAccessMode {
...@@ -779,8 +780,9 @@ func (fd *FakeDeleter) GetPath() string { ...@@ -779,8 +780,9 @@ func (fd *FakeDeleter) GetPath() string {
} }
type FakeProvisioner struct { type FakeProvisioner struct {
Options VolumeOptions Options VolumeOptions
Host VolumeHost Host VolumeHost
ProvisionDelaySeconds int
} }
func (fc *FakeProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) { func (fc *FakeProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
...@@ -807,6 +809,10 @@ func (fc *FakeProvisioner) Provision(selectedNode *v1.Node, allowedTopologies [] ...@@ -807,6 +809,10 @@ func (fc *FakeProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []
}, },
} }
if fc.ProvisionDelaySeconds > 0 {
time.Sleep(time.Duration(fc.ProvisionDelaySeconds) * time.Second)
}
return pv, nil return pv, nil
} }
......
...@@ -81,6 +81,11 @@ type KubeSchedulerConfiguration struct { ...@@ -81,6 +81,11 @@ type KubeSchedulerConfiguration struct {
// DEPRECATED. // DEPRECATED.
// Indicate the "all topologies" set for empty topologyKey when it's used for PreferredDuringScheduling pod anti-affinity. // Indicate the "all topologies" set for empty topologyKey when it's used for PreferredDuringScheduling pod anti-affinity.
FailureDomains string `json:"failureDomains"` FailureDomains string `json:"failureDomains"`
// Duration to wait for a binding operation to complete before timing out
// Value must be non-negative integer. The value zero indicates no waiting.
// If this value is nil, the default value will be used.
BindTimeoutSeconds *int64 `json:"bindTimeoutSeconds"`
} }
// SchedulerAlgorithmSource is the source of a scheduler algorithm. One source // SchedulerAlgorithmSource is the source of a scheduler algorithm. One source
......
...@@ -32,6 +32,11 @@ func (in *KubeSchedulerConfiguration) DeepCopyInto(out *KubeSchedulerConfigurati ...@@ -32,6 +32,11 @@ func (in *KubeSchedulerConfiguration) DeepCopyInto(out *KubeSchedulerConfigurati
in.LeaderElection.DeepCopyInto(&out.LeaderElection) in.LeaderElection.DeepCopyInto(&out.LeaderElection)
out.ClientConnection = in.ClientConnection out.ClientConnection = in.ClientConnection
out.DebuggingConfiguration = in.DebuggingConfiguration out.DebuggingConfiguration = in.DebuggingConfiguration
if in.BindTimeoutSeconds != nil {
in, out := &in.BindTimeoutSeconds, &out.BindTimeoutSeconds
*out = new(int64)
**out = **in
}
return return
} }
......
...@@ -566,13 +566,28 @@ var _ = utils.SIGDescribe("PersistentVolumes-local ", func() { ...@@ -566,13 +566,28 @@ var _ = utils.SIGDescribe("PersistentVolumes-local ", func() {
framework.Skipf("Runs only when number of nodes >= %v", ssReplicas) framework.Skipf("Runs only when number of nodes >= %v", ssReplicas)
} }
By("Creating a StatefulSet with pod anti-affinity on nodes") By("Creating a StatefulSet with pod anti-affinity on nodes")
ss := createStatefulSet(config, ssReplicas, volsPerNode, true) ss := createStatefulSet(config, ssReplicas, volsPerNode, true, false)
validateStatefulSet(config, ss, true) validateStatefulSet(config, ss, true)
}) })
It("should use volumes on one node when pod has affinity", func() { It("should use volumes on one node when pod has affinity", func() {
By("Creating a StatefulSet with pod affinity on nodes") By("Creating a StatefulSet with pod affinity on nodes")
ss := createStatefulSet(config, ssReplicas, volsPerNode/ssReplicas, false) ss := createStatefulSet(config, ssReplicas, volsPerNode/ssReplicas, false, false)
validateStatefulSet(config, ss, false)
})
It("should use volumes spread across nodes when pod management is parallel and pod has anti-affinity", func() {
if len(config.nodes) < ssReplicas {
framework.Skipf("Runs only when number of nodes >= %v", ssReplicas)
}
By("Creating a StatefulSet with pod anti-affinity on nodes")
ss := createStatefulSet(config, ssReplicas, 1, true, true)
validateStatefulSet(config, ss, true)
})
It("should use volumes on one node when pod management is parallel and pod has affinity", func() {
By("Creating a StatefulSet with pod affinity on nodes")
ss := createStatefulSet(config, ssReplicas, 1, false, true)
validateStatefulSet(config, ss, false) validateStatefulSet(config, ss, false)
}) })
}) })
...@@ -1830,7 +1845,7 @@ func findLocalPersistentVolume(c clientset.Interface, volumePath string) (*v1.Pe ...@@ -1830,7 +1845,7 @@ func findLocalPersistentVolume(c clientset.Interface, volumePath string) (*v1.Pe
return nil, nil return nil, nil
} }
func createStatefulSet(config *localTestConfig, ssReplicas int32, volumeCount int, anti bool) *appsv1.StatefulSet { func createStatefulSet(config *localTestConfig, ssReplicas int32, volumeCount int, anti, parallel bool) *appsv1.StatefulSet {
mounts := []v1.VolumeMount{} mounts := []v1.VolumeMount{}
claims := []v1.PersistentVolumeClaim{} claims := []v1.PersistentVolumeClaim{}
for i := 0; i < volumeCount; i++ { for i := 0; i < volumeCount; i++ {
...@@ -1897,6 +1912,10 @@ func createStatefulSet(config *localTestConfig, ssReplicas int32, volumeCount in ...@@ -1897,6 +1912,10 @@ func createStatefulSet(config *localTestConfig, ssReplicas int32, volumeCount in
}, },
} }
if parallel {
spec.Spec.PodManagementPolicy = appsv1.ParallelPodManagement
}
ss, err := config.client.AppsV1().StatefulSets(config.ns).Create(spec) ss, err := config.client.AppsV1().StatefulSets(config.ns).Create(spec)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
......
...@@ -34,6 +34,7 @@ go_test( ...@@ -34,6 +34,7 @@ go_test(
"//pkg/kubeapiserver/admission:go_default_library", "//pkg/kubeapiserver/admission:go_default_library",
"//pkg/scheduler:go_default_library", "//pkg/scheduler:go_default_library",
"//pkg/scheduler/algorithm:go_default_library", "//pkg/scheduler/algorithm:go_default_library",
"//pkg/scheduler/algorithm/predicates:go_default_library",
"//pkg/scheduler/algorithmprovider:go_default_library", "//pkg/scheduler/algorithmprovider:go_default_library",
"//pkg/scheduler/api:go_default_library", "//pkg/scheduler/api:go_default_library",
"//pkg/scheduler/apis/config:go_default_library", "//pkg/scheduler/apis/config:go_default_library",
......
...@@ -183,6 +183,7 @@ func TestSchedulerCreationFromConfigMap(t *testing.T) { ...@@ -183,6 +183,7 @@ func TestSchedulerCreationFromConfigMap(t *testing.T) {
eventBroadcaster := record.NewBroadcaster() eventBroadcaster := record.NewBroadcaster()
eventBroadcaster.StartRecordingToSink(&clientv1core.EventSinkImpl{Interface: clientSet.CoreV1().Events("")}) eventBroadcaster.StartRecordingToSink(&clientv1core.EventSinkImpl{Interface: clientSet.CoreV1().Events("")})
defaultBindTimeout := int64(30)
ss := &schedulerappconfig.Config{ ss := &schedulerappconfig.Config{
ComponentConfig: kubeschedulerconfig.KubeSchedulerConfiguration{ ComponentConfig: kubeschedulerconfig.KubeSchedulerConfiguration{
HardPodAffinitySymmetricWeight: v1.DefaultHardPodAffinitySymmetricWeight, HardPodAffinitySymmetricWeight: v1.DefaultHardPodAffinitySymmetricWeight,
...@@ -195,6 +196,7 @@ func TestSchedulerCreationFromConfigMap(t *testing.T) { ...@@ -195,6 +196,7 @@ func TestSchedulerCreationFromConfigMap(t *testing.T) {
}, },
}, },
}, },
BindTimeoutSeconds: &defaultBindTimeout,
}, },
Client: clientSet, Client: clientSet,
InformerFactory: informerFactory, InformerFactory: informerFactory,
...@@ -244,6 +246,7 @@ func TestSchedulerCreationFromNonExistentConfigMap(t *testing.T) { ...@@ -244,6 +246,7 @@ func TestSchedulerCreationFromNonExistentConfigMap(t *testing.T) {
eventBroadcaster := record.NewBroadcaster() eventBroadcaster := record.NewBroadcaster()
eventBroadcaster.StartRecordingToSink(&clientv1core.EventSinkImpl{Interface: clientSet.CoreV1().Events("")}) eventBroadcaster.StartRecordingToSink(&clientv1core.EventSinkImpl{Interface: clientSet.CoreV1().Events("")})
defaultBindTimeout := int64(30)
ss := &schedulerappconfig.Config{ ss := &schedulerappconfig.Config{
ComponentConfig: kubeschedulerconfig.KubeSchedulerConfiguration{ ComponentConfig: kubeschedulerconfig.KubeSchedulerConfiguration{
SchedulerName: v1.DefaultSchedulerName, SchedulerName: v1.DefaultSchedulerName,
...@@ -256,6 +259,7 @@ func TestSchedulerCreationFromNonExistentConfigMap(t *testing.T) { ...@@ -256,6 +259,7 @@ func TestSchedulerCreationFromNonExistentConfigMap(t *testing.T) {
}, },
}, },
HardPodAffinitySymmetricWeight: v1.DefaultHardPodAffinitySymmetricWeight, HardPodAffinitySymmetricWeight: v1.DefaultHardPodAffinitySymmetricWeight,
BindTimeoutSeconds: &defaultBindTimeout,
}, },
Client: clientSet, Client: clientSet,
InformerFactory: informerFactory, InformerFactory: informerFactory,
......
...@@ -91,6 +91,7 @@ func createConfiguratorWithPodInformer( ...@@ -91,6 +91,7 @@ func createConfiguratorWithPodInformer(
EnableEquivalenceClassCache: utilfeature.DefaultFeatureGate.Enabled(features.EnableEquivalenceClassCache), EnableEquivalenceClassCache: utilfeature.DefaultFeatureGate.Enabled(features.EnableEquivalenceClassCache),
DisablePreemption: false, DisablePreemption: false,
PercentageOfNodesToScore: schedulerapi.DefaultPercentageOfNodesToScore, PercentageOfNodesToScore: schedulerapi.DefaultPercentageOfNodesToScore,
BindTimeoutSeconds: 600,
}) })
} }
...@@ -143,7 +144,7 @@ func initTestScheduler( ...@@ -143,7 +144,7 @@ func initTestScheduler(
) *TestContext { ) *TestContext {
// Pod preemption is enabled by default scheduler configuration, but preemption only happens when PodPriority // Pod preemption is enabled by default scheduler configuration, but preemption only happens when PodPriority
// feature gate is enabled at the same time. // feature gate is enabled at the same time.
return initTestSchedulerWithOptions(t, context, controllerCh, setPodInformer, policy, false, time.Second) return initTestSchedulerWithOptions(t, context, controllerCh, setPodInformer, policy, false, false, time.Second)
} }
// initTestSchedulerWithOptions initializes a test environment and creates a scheduler with default // initTestSchedulerWithOptions initializes a test environment and creates a scheduler with default
...@@ -155,13 +156,15 @@ func initTestSchedulerWithOptions( ...@@ -155,13 +156,15 @@ func initTestSchedulerWithOptions(
setPodInformer bool, setPodInformer bool,
policy *schedulerapi.Policy, policy *schedulerapi.Policy,
disablePreemption bool, disablePreemption bool,
disableEquivalenceCache bool,
resyncPeriod time.Duration, resyncPeriod time.Duration,
) *TestContext { ) *TestContext {
// Enable EnableEquivalenceClassCache for all integration tests. if !disableEquivalenceCache {
defer utilfeaturetesting.SetFeatureGateDuringTest( defer utilfeaturetesting.SetFeatureGateDuringTest(
t, t,
utilfeature.DefaultFeatureGate, utilfeature.DefaultFeatureGate,
features.EnableEquivalenceClassCache, true)() features.EnableEquivalenceClassCache, true)()
}
// 1. Create scheduler // 1. Create scheduler
context.informerFactory = informers.NewSharedInformerFactory(context.clientSet, resyncPeriod) context.informerFactory = informers.NewSharedInformerFactory(context.clientSet, resyncPeriod)
...@@ -256,7 +259,7 @@ func initTest(t *testing.T, nsPrefix string) *TestContext { ...@@ -256,7 +259,7 @@ func initTest(t *testing.T, nsPrefix string) *TestContext {
// configuration but with pod preemption disabled. // configuration but with pod preemption disabled.
func initTestDisablePreemption(t *testing.T, nsPrefix string) *TestContext { func initTestDisablePreemption(t *testing.T, nsPrefix string) *TestContext {
return initTestSchedulerWithOptions( return initTestSchedulerWithOptions(
t, initTestMaster(t, nsPrefix, nil), nil, true, nil, true, time.Second) t, initTestMaster(t, nsPrefix, nil), nil, true, nil, true, false, time.Second)
} }
// cleanupTest deletes the scheduler and the test namespace. It should be called // cleanupTest deletes the scheduler and the test namespace. It should be called
......
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