mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-01 15:58:37 +00:00
Merge pull request #119208 from atosatto/separate-taint-manager
Decouple TaintManager from NodeLifeCycleController (KEP-3902)
This commit is contained in:
commit
e4212878dd
@ -57,7 +57,7 @@ import (
|
|||||||
"k8s.io/component-base/featuregate"
|
"k8s.io/component-base/featuregate"
|
||||||
"k8s.io/component-base/logs"
|
"k8s.io/component-base/logs"
|
||||||
logsapi "k8s.io/component-base/logs/api/v1"
|
logsapi "k8s.io/component-base/logs/api/v1"
|
||||||
"k8s.io/component-base/metrics/features"
|
metricsfeatures "k8s.io/component-base/metrics/features"
|
||||||
controllersmetrics "k8s.io/component-base/metrics/prometheus/controllers"
|
controllersmetrics "k8s.io/component-base/metrics/prometheus/controllers"
|
||||||
"k8s.io/component-base/metrics/prometheus/slis"
|
"k8s.io/component-base/metrics/prometheus/slis"
|
||||||
"k8s.io/component-base/term"
|
"k8s.io/component-base/term"
|
||||||
@ -75,12 +75,13 @@ import (
|
|||||||
"k8s.io/kubernetes/cmd/kube-controller-manager/names"
|
"k8s.io/kubernetes/cmd/kube-controller-manager/names"
|
||||||
kubectrlmgrconfig "k8s.io/kubernetes/pkg/controller/apis/config"
|
kubectrlmgrconfig "k8s.io/kubernetes/pkg/controller/apis/config"
|
||||||
serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount"
|
serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
"k8s.io/kubernetes/pkg/serviceaccount"
|
"k8s.io/kubernetes/pkg/serviceaccount"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
utilruntime.Must(logsapi.AddFeatureGates(utilfeature.DefaultMutableFeatureGate))
|
utilruntime.Must(logsapi.AddFeatureGates(utilfeature.DefaultMutableFeatureGate))
|
||||||
utilruntime.Must(features.AddFeatureGates(utilfeature.DefaultMutableFeatureGate))
|
utilruntime.Must(metricsfeatures.AddFeatureGates(utilfeature.DefaultMutableFeatureGate))
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -556,6 +557,10 @@ func NewControllerDescriptors() map[string]*ControllerDescriptor {
|
|||||||
register(newResourceClaimControllerDescriptor())
|
register(newResourceClaimControllerDescriptor())
|
||||||
register(newLegacyServiceAccountTokenCleanerControllerDescriptor())
|
register(newLegacyServiceAccountTokenCleanerControllerDescriptor())
|
||||||
register(newValidatingAdmissionPolicyStatusControllerDescriptor())
|
register(newValidatingAdmissionPolicyStatusControllerDescriptor())
|
||||||
|
if utilfeature.DefaultFeatureGate.Enabled(features.SeparateTaintEvictionController) {
|
||||||
|
// register the flag only if the SeparateTaintEvictionController flag is enabled
|
||||||
|
register(newTaintEvictionControllerDescriptor())
|
||||||
|
}
|
||||||
|
|
||||||
for _, alias := range aliases.UnsortedList() {
|
for _, alias := range aliases.UnsortedList() {
|
||||||
if _, ok := controllers[alias]; ok {
|
if _, ok := controllers[alias]; ok {
|
||||||
|
@ -28,8 +28,9 @@ import (
|
|||||||
cpnames "k8s.io/cloud-provider/names"
|
cpnames "k8s.io/cloud-provider/names"
|
||||||
"k8s.io/component-base/featuregate"
|
"k8s.io/component-base/featuregate"
|
||||||
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||||
|
|
||||||
"k8s.io/kubernetes/cmd/kube-controller-manager/names"
|
"k8s.io/kubernetes/cmd/kube-controller-manager/names"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
|
"k8s.io/utils/strings/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestControllerNamesConsistency(t *testing.T) {
|
func TestControllerNamesConsistency(t *testing.T) {
|
||||||
@ -73,6 +74,7 @@ func TestControllerNamesDeclaration(t *testing.T) {
|
|||||||
names.TokenCleanerController,
|
names.TokenCleanerController,
|
||||||
names.NodeIpamController,
|
names.NodeIpamController,
|
||||||
names.NodeLifecycleController,
|
names.NodeLifecycleController,
|
||||||
|
names.TaintEvictionController,
|
||||||
cpnames.ServiceLBController,
|
cpnames.ServiceLBController,
|
||||||
cpnames.NodeRouteController,
|
cpnames.NodeRouteController,
|
||||||
cpnames.CloudNodeLifecycleController,
|
cpnames.CloudNodeLifecycleController,
|
||||||
@ -156,3 +158,17 @@ func TestFeatureGatedControllersShouldNotDefineAliases(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestTaintEvictionControllerDeclaration ensures that it is possible to run taint-manager as a separated controller
|
||||||
|
// only when the SeparateTaintEvictionController feature is enabled
|
||||||
|
func TestTaintEvictionControllerDeclaration(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SeparateTaintEvictionController, true)()
|
||||||
|
if !slices.Contains(KnownControllers(), names.TaintEvictionController) {
|
||||||
|
t.Errorf("TaintEvictionController should be a registered controller when the SeparateTaintEvictionController feature is enabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SeparateTaintEvictionController, false)()
|
||||||
|
if slices.Contains(KnownControllers(), names.TaintEvictionController) {
|
||||||
|
t.Errorf("TaintEvictionController should not be a registered controller when the SeparateTaintEvictionController feature is disabled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -59,6 +59,7 @@ import (
|
|||||||
resourcequotacontroller "k8s.io/kubernetes/pkg/controller/resourcequota"
|
resourcequotacontroller "k8s.io/kubernetes/pkg/controller/resourcequota"
|
||||||
serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount"
|
serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount"
|
||||||
"k8s.io/kubernetes/pkg/controller/storageversiongc"
|
"k8s.io/kubernetes/pkg/controller/storageversiongc"
|
||||||
|
"k8s.io/kubernetes/pkg/controller/tainteviction"
|
||||||
ttlcontroller "k8s.io/kubernetes/pkg/controller/ttl"
|
ttlcontroller "k8s.io/kubernetes/pkg/controller/ttl"
|
||||||
"k8s.io/kubernetes/pkg/controller/ttlafterfinished"
|
"k8s.io/kubernetes/pkg/controller/ttlafterfinished"
|
||||||
"k8s.io/kubernetes/pkg/controller/volume/attachdetach"
|
"k8s.io/kubernetes/pkg/controller/volume/attachdetach"
|
||||||
@ -219,6 +220,32 @@ func startNodeLifecycleController(ctx context.Context, controllerContext Control
|
|||||||
return nil, true, nil
|
return nil, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newTaintEvictionControllerDescriptor() *ControllerDescriptor {
|
||||||
|
return &ControllerDescriptor{
|
||||||
|
name: names.TaintEvictionController,
|
||||||
|
initFunc: startTaintEvictionController,
|
||||||
|
requiredFeatureGates: []featuregate.Feature{
|
||||||
|
features.SeparateTaintEvictionController,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func startTaintEvictionController(ctx context.Context, controllerContext ControllerContext, controllerName string) (controller.Interface, bool, error) {
|
||||||
|
taintEvictionController, err := tainteviction.New(
|
||||||
|
ctx,
|
||||||
|
// taint-manager uses existing cluster role from node-controller
|
||||||
|
controllerContext.ClientBuilder.ClientOrDie("node-controller"),
|
||||||
|
controllerContext.InformerFactory.Core().V1().Pods(),
|
||||||
|
controllerContext.InformerFactory.Core().V1().Nodes(),
|
||||||
|
controllerName,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
go taintEvictionController.Run(ctx)
|
||||||
|
return nil, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
func newCloudNodeLifecycleControllerDescriptor() *ControllerDescriptor {
|
func newCloudNodeLifecycleControllerDescriptor() *ControllerDescriptor {
|
||||||
return &ControllerDescriptor{
|
return &ControllerDescriptor{
|
||||||
name: cpnames.CloudNodeLifecycleController,
|
name: cpnames.CloudNodeLifecycleController,
|
||||||
|
@ -68,6 +68,7 @@ const (
|
|||||||
TokenCleanerController = "token-cleaner-controller"
|
TokenCleanerController = "token-cleaner-controller"
|
||||||
NodeIpamController = "node-ipam-controller"
|
NodeIpamController = "node-ipam-controller"
|
||||||
NodeLifecycleController = "node-lifecycle-controller"
|
NodeLifecycleController = "node-lifecycle-controller"
|
||||||
|
TaintEvictionController = "taint-eviction-controller"
|
||||||
PersistentVolumeBinderController = "persistentvolume-binder-controller"
|
PersistentVolumeBinderController = "persistentvolume-binder-controller"
|
||||||
PersistentVolumeAttachDetachController = "persistentvolume-attach-detach-controller"
|
PersistentVolumeAttachDetachController = "persistentvolume-attach-detach-controller"
|
||||||
PersistentVolumeExpanderController = "persistentvolume-expander-controller"
|
PersistentVolumeExpanderController = "persistentvolume-expander-controller"
|
||||||
|
@ -37,6 +37,7 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
appsv1informers "k8s.io/client-go/informers/apps/v1"
|
appsv1informers "k8s.io/client-go/informers/apps/v1"
|
||||||
coordinformers "k8s.io/client-go/informers/coordination/v1"
|
coordinformers "k8s.io/client-go/informers/coordination/v1"
|
||||||
coreinformers "k8s.io/client-go/informers/core/v1"
|
coreinformers "k8s.io/client-go/informers/core/v1"
|
||||||
@ -54,7 +55,9 @@ import (
|
|||||||
kubeletapis "k8s.io/kubelet/pkg/apis"
|
kubeletapis "k8s.io/kubelet/pkg/apis"
|
||||||
"k8s.io/kubernetes/pkg/controller"
|
"k8s.io/kubernetes/pkg/controller"
|
||||||
"k8s.io/kubernetes/pkg/controller/nodelifecycle/scheduler"
|
"k8s.io/kubernetes/pkg/controller/nodelifecycle/scheduler"
|
||||||
|
"k8s.io/kubernetes/pkg/controller/tainteviction"
|
||||||
controllerutil "k8s.io/kubernetes/pkg/controller/util/node"
|
controllerutil "k8s.io/kubernetes/pkg/controller/util/node"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
taintutils "k8s.io/kubernetes/pkg/util/taints"
|
taintutils "k8s.io/kubernetes/pkg/util/taints"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -127,6 +130,13 @@ const (
|
|||||||
// podUpdateWorkerSizes assumes that in most cases pod will be handled by monitorNodeHealth pass.
|
// podUpdateWorkerSizes assumes that in most cases pod will be handled by monitorNodeHealth pass.
|
||||||
// Pod update workers will only handle lagging cache pods. 4 workers should be enough.
|
// Pod update workers will only handle lagging cache pods. 4 workers should be enough.
|
||||||
podUpdateWorkerSize = 4
|
podUpdateWorkerSize = 4
|
||||||
|
// nodeUpdateWorkerSize defines the size of workers for node update or/and pod update.
|
||||||
|
nodeUpdateWorkerSize = 8
|
||||||
|
|
||||||
|
// taintEvictionController is defined here in order to prevent imports of
|
||||||
|
// k8s.io/kubernetes/cmd/kube-controller-manager/names which would result in validation errors.
|
||||||
|
// This constant will be removed upon graduation of the SeparateTaintEvictionController feature.
|
||||||
|
taintEvictionController = "taint-eviction-controller"
|
||||||
)
|
)
|
||||||
|
|
||||||
// labelReconcileInfo lists Node labels to reconcile, and how to reconcile them.
|
// labelReconcileInfo lists Node labels to reconcile, and how to reconcile them.
|
||||||
@ -207,7 +217,7 @@ type podUpdateItem struct {
|
|||||||
|
|
||||||
// Controller is the controller that manages node's life cycle.
|
// Controller is the controller that manages node's life cycle.
|
||||||
type Controller struct {
|
type Controller struct {
|
||||||
taintManager *scheduler.NoExecuteTaintManager
|
taintManager *tainteviction.Controller
|
||||||
|
|
||||||
podLister corelisters.PodLister
|
podLister corelisters.PodLister
|
||||||
podInformerSynced cache.InformerSynced
|
podInformerSynced cache.InformerSynced
|
||||||
@ -326,7 +336,7 @@ func NewNodeLifecycleController(
|
|||||||
nodeMonitorPeriod: nodeMonitorPeriod,
|
nodeMonitorPeriod: nodeMonitorPeriod,
|
||||||
nodeStartupGracePeriod: nodeStartupGracePeriod,
|
nodeStartupGracePeriod: nodeStartupGracePeriod,
|
||||||
nodeMonitorGracePeriod: nodeMonitorGracePeriod,
|
nodeMonitorGracePeriod: nodeMonitorGracePeriod,
|
||||||
nodeUpdateWorkerSize: scheduler.UpdateWorkerSize,
|
nodeUpdateWorkerSize: nodeUpdateWorkerSize,
|
||||||
zoneNoExecuteTainter: make(map[string]*scheduler.RateLimitedTimedQueue),
|
zoneNoExecuteTainter: make(map[string]*scheduler.RateLimitedTimedQueue),
|
||||||
nodesToRetry: sync.Map{},
|
nodesToRetry: sync.Map{},
|
||||||
zoneStates: make(map[string]ZoneState),
|
zoneStates: make(map[string]ZoneState),
|
||||||
@ -346,17 +356,11 @@ func NewNodeLifecycleController(
|
|||||||
AddFunc: func(obj interface{}) {
|
AddFunc: func(obj interface{}) {
|
||||||
pod := obj.(*v1.Pod)
|
pod := obj.(*v1.Pod)
|
||||||
nc.podUpdated(nil, pod)
|
nc.podUpdated(nil, pod)
|
||||||
if nc.taintManager != nil {
|
|
||||||
nc.taintManager.PodUpdated(nil, pod)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
UpdateFunc: func(prev, obj interface{}) {
|
UpdateFunc: func(prev, obj interface{}) {
|
||||||
prevPod := prev.(*v1.Pod)
|
prevPod := prev.(*v1.Pod)
|
||||||
newPod := obj.(*v1.Pod)
|
newPod := obj.(*v1.Pod)
|
||||||
nc.podUpdated(prevPod, newPod)
|
nc.podUpdated(prevPod, newPod)
|
||||||
if nc.taintManager != nil {
|
|
||||||
nc.taintManager.PodUpdated(prevPod, newPod)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
DeleteFunc: func(obj interface{}) {
|
DeleteFunc: func(obj interface{}) {
|
||||||
pod, isPod := obj.(*v1.Pod)
|
pod, isPod := obj.(*v1.Pod)
|
||||||
@ -374,9 +378,6 @@ func NewNodeLifecycleController(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
nc.podUpdated(pod, nil)
|
nc.podUpdated(pod, nil)
|
||||||
if nc.taintManager != nil {
|
|
||||||
nc.taintManager.PodUpdated(pod, nil)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
nc.podInformerSynced = podInformer.Informer().HasSynced
|
nc.podInformerSynced = podInformer.Informer().HasSynced
|
||||||
@ -412,21 +413,14 @@ func NewNodeLifecycleController(
|
|||||||
nc.podLister = podInformer.Lister()
|
nc.podLister = podInformer.Lister()
|
||||||
nc.nodeLister = nodeInformer.Lister()
|
nc.nodeLister = nodeInformer.Lister()
|
||||||
|
|
||||||
nc.taintManager = scheduler.NewNoExecuteTaintManager(ctx, kubeClient, nc.podLister, nc.nodeLister, nc.getPodsAssignedToNode)
|
if !utilfeature.DefaultFeatureGate.Enabled(features.SeparateTaintEvictionController) {
|
||||||
nodeInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
logger.Info("Running TaintEvictionController as part of NodeLifecyleController")
|
||||||
AddFunc: controllerutil.CreateAddNodeHandler(func(node *v1.Node) error {
|
tm, err := tainteviction.New(ctx, kubeClient, podInformer, nodeInformer, taintEvictionController)
|
||||||
nc.taintManager.NodeUpdated(nil, node)
|
if err != nil {
|
||||||
return nil
|
return nil, err
|
||||||
}),
|
}
|
||||||
UpdateFunc: controllerutil.CreateUpdateNodeHandler(func(oldNode, newNode *v1.Node) error {
|
nc.taintManager = tm
|
||||||
nc.taintManager.NodeUpdated(oldNode, newNode)
|
}
|
||||||
return nil
|
|
||||||
}),
|
|
||||||
DeleteFunc: controllerutil.CreateDeleteNodeHandler(logger, func(node *v1.Node) error {
|
|
||||||
nc.taintManager.NodeUpdated(node, nil)
|
|
||||||
return nil
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
|
|
||||||
logger.Info("Controller will reconcile labels")
|
logger.Info("Controller will reconcile labels")
|
||||||
nodeInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
nodeInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||||
@ -480,10 +474,13 @@ func (nc *Controller) Run(ctx context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
go nc.taintManager.Run(ctx)
|
if !utilfeature.DefaultFeatureGate.Enabled(features.SeparateTaintEvictionController) {
|
||||||
|
logger.Info("Starting", "controller", taintEvictionController)
|
||||||
|
go nc.taintManager.Run(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
// Start workers to reconcile labels and/or update NoSchedule taint for nodes.
|
// Start workers to reconcile labels and/or update NoSchedule taint for nodes.
|
||||||
for i := 0; i < scheduler.UpdateWorkerSize; i++ {
|
for i := 0; i < nodeUpdateWorkerSize; i++ {
|
||||||
// Thanks to "workqueue", each worker just need to get item from queue, because
|
// Thanks to "workqueue", each worker just need to get item from queue, because
|
||||||
// the item is flagged when got from queue: if new event come, the new item will
|
// the item is flagged when got from queue: if new event come, the new item will
|
||||||
// be re-queued until "Done", so no more than one worker handle the same item and
|
// be re-queued until "Done", so no more than one worker handle the same item and
|
||||||
|
8
pkg/controller/tainteviction/OWNERS
Normal file
8
pkg/controller/tainteviction/OWNERS
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# See the OWNERS docs at https://go.k8s.io/owners
|
||||||
|
|
||||||
|
approvers:
|
||||||
|
- sig-scheduling-maintainers
|
||||||
|
reviewers:
|
||||||
|
- sig-scheduling
|
||||||
|
labels:
|
||||||
|
- sig/scheduling
|
19
pkg/controller/tainteviction/doc.go
Normal file
19
pkg/controller/tainteviction/doc.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 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 tainteviction contains the logic implementing taint-based eviction
|
||||||
|
// for Pods running on Nodes with NoExecute taints.
|
||||||
|
package tainteviction
|
60
pkg/controller/tainteviction/metrics/metrics.go
Normal file
60
pkg/controller/tainteviction/metrics/metrics.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 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 metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"k8s.io/component-base/metrics"
|
||||||
|
"k8s.io/component-base/metrics/legacyregistry"
|
||||||
|
)
|
||||||
|
|
||||||
|
const taintEvictionControllerSubsystem = "taint_eviction_controller"
|
||||||
|
|
||||||
|
var (
|
||||||
|
// PodDeletionsTotal counts the number of Pods deleted by TaintEvictionController since its start.
|
||||||
|
PodDeletionsTotal = metrics.NewCounter(
|
||||||
|
&metrics.CounterOpts{
|
||||||
|
Subsystem: taintEvictionControllerSubsystem,
|
||||||
|
Name: "pod_deletions_total",
|
||||||
|
Help: "Total number of Pods deleted by TaintEvictionController since its start.",
|
||||||
|
StabilityLevel: metrics.ALPHA,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
// PodDeletionsLatency tracks the latency, in seconds, between the time when a taint effect has been activated
|
||||||
|
// for the Pod and its deletion.
|
||||||
|
PodDeletionsLatency = metrics.NewHistogram(
|
||||||
|
&metrics.HistogramOpts{
|
||||||
|
Subsystem: taintEvictionControllerSubsystem,
|
||||||
|
Name: "pod_deletion_duration_seconds",
|
||||||
|
Help: "Latency, in seconds, between the time when a taint effect has been activated for the Pod and its deletion via TaintEvictionController.",
|
||||||
|
Buckets: []float64{0.005, 0.025, 0.1, 0.5, 1, 2.5, 10, 30, 60, 120, 180, 240}, // 5ms to 4m
|
||||||
|
StabilityLevel: metrics.ALPHA,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
var registerMetrics sync.Once
|
||||||
|
|
||||||
|
// Register registers TaintEvictionController metrics.
|
||||||
|
func Register() {
|
||||||
|
registerMetrics.Do(func() {
|
||||||
|
legacyregistry.MustRegister(PodDeletionsTotal)
|
||||||
|
legacyregistry.MustRegister(PodDeletionsLatency)
|
||||||
|
})
|
||||||
|
}
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package scheduler
|
package tainteviction
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -31,19 +31,22 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
"k8s.io/apiserver/pkg/util/feature"
|
"k8s.io/apiserver/pkg/util/feature"
|
||||||
|
corev1informers "k8s.io/client-go/informers/core/v1"
|
||||||
clientset "k8s.io/client-go/kubernetes"
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/client-go/kubernetes/scheme"
|
"k8s.io/client-go/kubernetes/scheme"
|
||||||
v1core "k8s.io/client-go/kubernetes/typed/core/v1"
|
v1core "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||||
corelisters "k8s.io/client-go/listers/core/v1"
|
corelisters "k8s.io/client-go/listers/core/v1"
|
||||||
|
"k8s.io/client-go/tools/cache"
|
||||||
"k8s.io/client-go/tools/record"
|
"k8s.io/client-go/tools/record"
|
||||||
"k8s.io/client-go/util/workqueue"
|
"k8s.io/client-go/util/workqueue"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
apipod "k8s.io/kubernetes/pkg/api/v1/pod"
|
apipod "k8s.io/kubernetes/pkg/api/v1/pod"
|
||||||
"k8s.io/kubernetes/pkg/apis/core/helper"
|
"k8s.io/kubernetes/pkg/apis/core/helper"
|
||||||
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
|
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
|
||||||
|
"k8s.io/kubernetes/pkg/controller/tainteviction/metrics"
|
||||||
|
controllerutil "k8s.io/kubernetes/pkg/controller/util/node"
|
||||||
"k8s.io/kubernetes/pkg/features"
|
"k8s.io/kubernetes/pkg/features"
|
||||||
utilpod "k8s.io/kubernetes/pkg/util/pod"
|
utilpod "k8s.io/kubernetes/pkg/util/pod"
|
||||||
|
|
||||||
"k8s.io/klog/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -77,14 +80,18 @@ func hash(val string, max int) int {
|
|||||||
// GetPodsByNodeNameFunc returns the list of pods assigned to the specified node.
|
// GetPodsByNodeNameFunc returns the list of pods assigned to the specified node.
|
||||||
type GetPodsByNodeNameFunc func(nodeName string) ([]*v1.Pod, error)
|
type GetPodsByNodeNameFunc func(nodeName string) ([]*v1.Pod, error)
|
||||||
|
|
||||||
// NoExecuteTaintManager listens to Taint/Toleration changes and is responsible for removing Pods
|
// Controller listens to Taint/Toleration changes and is responsible for removing Pods
|
||||||
// from Nodes tainted with NoExecute Taints.
|
// from Nodes tainted with NoExecute Taints.
|
||||||
type NoExecuteTaintManager struct {
|
type Controller struct {
|
||||||
|
name string
|
||||||
|
|
||||||
client clientset.Interface
|
client clientset.Interface
|
||||||
broadcaster record.EventBroadcaster
|
broadcaster record.EventBroadcaster
|
||||||
recorder record.EventRecorder
|
recorder record.EventRecorder
|
||||||
podLister corelisters.PodLister
|
podLister corelisters.PodLister
|
||||||
|
podListerSynced cache.InformerSynced
|
||||||
nodeLister corelisters.NodeLister
|
nodeLister corelisters.NodeLister
|
||||||
|
nodeListerSynced cache.InformerSynced
|
||||||
getPodsAssignedToNode GetPodsByNodeNameFunc
|
getPodsAssignedToNode GetPodsByNodeNameFunc
|
||||||
|
|
||||||
taintEvictionQueue *TimedWorkerQueue
|
taintEvictionQueue *TimedWorkerQueue
|
||||||
@ -99,11 +106,11 @@ type NoExecuteTaintManager struct {
|
|||||||
podUpdateQueue workqueue.Interface
|
podUpdateQueue workqueue.Interface
|
||||||
}
|
}
|
||||||
|
|
||||||
func deletePodHandler(c clientset.Interface, emitEventFunc func(types.NamespacedName)) func(ctx context.Context, args *WorkArgs) error {
|
func deletePodHandler(c clientset.Interface, emitEventFunc func(types.NamespacedName), controllerName string) func(ctx context.Context, fireAt time.Time, args *WorkArgs) error {
|
||||||
return func(ctx context.Context, args *WorkArgs) error {
|
return func(ctx context.Context, fireAt time.Time, args *WorkArgs) error {
|
||||||
ns := args.NamespacedName.Namespace
|
ns := args.NamespacedName.Namespace
|
||||||
name := args.NamespacedName.Name
|
name := args.NamespacedName.Name
|
||||||
klog.FromContext(ctx).Info("NoExecuteTaintManager is deleting pod", "pod", args.NamespacedName.String())
|
klog.FromContext(ctx).Info("Deleting pod", "controller", controllerName, "pod", args.NamespacedName)
|
||||||
if emitEventFunc != nil {
|
if emitEventFunc != nil {
|
||||||
emitEventFunc(args.NamespacedName)
|
emitEventFunc(args.NamespacedName)
|
||||||
}
|
}
|
||||||
@ -111,6 +118,8 @@ func deletePodHandler(c clientset.Interface, emitEventFunc func(types.Namespaced
|
|||||||
for i := 0; i < retries; i++ {
|
for i := 0; i < retries; i++ {
|
||||||
err = addConditionAndDeletePod(ctx, c, name, ns)
|
err = addConditionAndDeletePod(ctx, c, name, ns)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
metrics.PodDeletionsTotal.Inc()
|
||||||
|
metrics.PodDeletionsLatency.Observe(float64(time.Since(fireAt) * time.Second))
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
time.Sleep(10 * time.Millisecond)
|
time.Sleep(10 * time.Millisecond)
|
||||||
@ -175,34 +184,106 @@ func getMinTolerationTime(tolerations []v1.Toleration) time.Duration {
|
|||||||
return time.Duration(minTolerationTime) * time.Second
|
return time.Duration(minTolerationTime) * time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewNoExecuteTaintManager creates a new NoExecuteTaintManager that will use passed clientset to
|
// New creates a new Controller that will use passed clientset to communicate with the API server.
|
||||||
// communicate with the API server.
|
func New(ctx context.Context, c clientset.Interface, podInformer corev1informers.PodInformer, nodeInformer corev1informers.NodeInformer, controllerName string) (*Controller, error) {
|
||||||
func NewNoExecuteTaintManager(ctx context.Context, c clientset.Interface, podLister corelisters.PodLister, nodeLister corelisters.NodeLister, getPodsAssignedToNode GetPodsByNodeNameFunc) *NoExecuteTaintManager {
|
logger := klog.FromContext(ctx)
|
||||||
|
metrics.Register()
|
||||||
eventBroadcaster := record.NewBroadcaster()
|
eventBroadcaster := record.NewBroadcaster()
|
||||||
recorder := eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "taint-controller"})
|
recorder := eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: controllerName})
|
||||||
|
|
||||||
tm := &NoExecuteTaintManager{
|
podIndexer := podInformer.Informer().GetIndexer()
|
||||||
client: c,
|
|
||||||
broadcaster: eventBroadcaster,
|
|
||||||
recorder: recorder,
|
|
||||||
podLister: podLister,
|
|
||||||
nodeLister: nodeLister,
|
|
||||||
getPodsAssignedToNode: getPodsAssignedToNode,
|
|
||||||
taintedNodes: make(map[string][]v1.Taint),
|
|
||||||
|
|
||||||
nodeUpdateQueue: workqueue.NewNamed("noexec_taint_node"),
|
tm := &Controller{
|
||||||
podUpdateQueue: workqueue.NewNamed("noexec_taint_pod"),
|
name: controllerName,
|
||||||
|
|
||||||
|
client: c,
|
||||||
|
broadcaster: eventBroadcaster,
|
||||||
|
recorder: recorder,
|
||||||
|
podLister: podInformer.Lister(),
|
||||||
|
podListerSynced: podInformer.Informer().HasSynced,
|
||||||
|
nodeLister: nodeInformer.Lister(),
|
||||||
|
nodeListerSynced: nodeInformer.Informer().HasSynced,
|
||||||
|
getPodsAssignedToNode: func(nodeName string) ([]*v1.Pod, error) {
|
||||||
|
objs, err := podIndexer.ByIndex("spec.nodeName", nodeName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pods := make([]*v1.Pod, 0, len(objs))
|
||||||
|
for _, obj := range objs {
|
||||||
|
pod, ok := obj.(*v1.Pod)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pods = append(pods, pod)
|
||||||
|
}
|
||||||
|
return pods, nil
|
||||||
|
},
|
||||||
|
taintedNodes: make(map[string][]v1.Taint),
|
||||||
|
|
||||||
|
nodeUpdateQueue: workqueue.NewWithConfig(workqueue.QueueConfig{Name: "noexec_taint_node"}),
|
||||||
|
podUpdateQueue: workqueue.NewWithConfig(workqueue.QueueConfig{Name: "noexec_taint_pod"}),
|
||||||
}
|
}
|
||||||
tm.taintEvictionQueue = CreateWorkerQueue(deletePodHandler(c, tm.emitPodDeletionEvent))
|
tm.taintEvictionQueue = CreateWorkerQueue(deletePodHandler(c, tm.emitPodDeletionEvent, tm.name))
|
||||||
|
|
||||||
return tm
|
_, err := podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||||
|
AddFunc: func(obj interface{}) {
|
||||||
|
pod := obj.(*v1.Pod)
|
||||||
|
tm.PodUpdated(nil, pod)
|
||||||
|
},
|
||||||
|
UpdateFunc: func(prev, obj interface{}) {
|
||||||
|
prevPod := prev.(*v1.Pod)
|
||||||
|
newPod := obj.(*v1.Pod)
|
||||||
|
tm.PodUpdated(prevPod, newPod)
|
||||||
|
},
|
||||||
|
DeleteFunc: func(obj interface{}) {
|
||||||
|
pod, isPod := obj.(*v1.Pod)
|
||||||
|
// We can get DeletedFinalStateUnknown instead of *v1.Pod here and we need to handle that correctly.
|
||||||
|
if !isPod {
|
||||||
|
deletedState, ok := obj.(cache.DeletedFinalStateUnknown)
|
||||||
|
if !ok {
|
||||||
|
logger.Error(nil, "Received unexpected object", "object", obj)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pod, ok = deletedState.Obj.(*v1.Pod)
|
||||||
|
if !ok {
|
||||||
|
logger.Error(nil, "DeletedFinalStateUnknown contained non-Pod object", "object", deletedState.Obj)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tm.PodUpdated(pod, nil)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to add pod event handler: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = nodeInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||||
|
AddFunc: controllerutil.CreateAddNodeHandler(func(node *v1.Node) error {
|
||||||
|
tm.NodeUpdated(nil, node)
|
||||||
|
return nil
|
||||||
|
}),
|
||||||
|
UpdateFunc: controllerutil.CreateUpdateNodeHandler(func(oldNode, newNode *v1.Node) error {
|
||||||
|
tm.NodeUpdated(oldNode, newNode)
|
||||||
|
return nil
|
||||||
|
}),
|
||||||
|
DeleteFunc: controllerutil.CreateDeleteNodeHandler(logger, func(node *v1.Node) error {
|
||||||
|
tm.NodeUpdated(node, nil)
|
||||||
|
return nil
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to add node event handler: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tm, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run starts NoExecuteTaintManager which will run in loop until `stopCh` is closed.
|
// Run starts the controller which will run in loop until `stopCh` is closed.
|
||||||
func (tc *NoExecuteTaintManager) Run(ctx context.Context) {
|
func (tc *Controller) Run(ctx context.Context) {
|
||||||
defer utilruntime.HandleCrash()
|
defer utilruntime.HandleCrash()
|
||||||
logger := klog.FromContext(ctx)
|
logger := klog.FromContext(ctx)
|
||||||
logger.Info("Starting NoExecuteTaintManager")
|
logger.Info("Starting", "controller", tc.name)
|
||||||
|
defer logger.Info("Shutting down controller", "controller", tc.name)
|
||||||
|
|
||||||
// Start events processing pipeline.
|
// Start events processing pipeline.
|
||||||
tc.broadcaster.StartStructuredLogging(0)
|
tc.broadcaster.StartStructuredLogging(0)
|
||||||
@ -210,14 +291,18 @@ func (tc *NoExecuteTaintManager) Run(ctx context.Context) {
|
|||||||
logger.Info("Sending events to api server")
|
logger.Info("Sending events to api server")
|
||||||
tc.broadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: tc.client.CoreV1().Events("")})
|
tc.broadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: tc.client.CoreV1().Events("")})
|
||||||
} else {
|
} else {
|
||||||
logger.Error(nil, "kubeClient is nil when starting NodeController")
|
logger.Error(nil, "kubeClient is nil", "controller", tc.name)
|
||||||
klog.FlushAndExit(klog.ExitFlushTimeout, 1)
|
klog.FlushAndExit(klog.ExitFlushTimeout, 1)
|
||||||
}
|
}
|
||||||
defer tc.broadcaster.Shutdown()
|
defer tc.broadcaster.Shutdown()
|
||||||
|
|
||||||
defer tc.nodeUpdateQueue.ShutDown()
|
defer tc.nodeUpdateQueue.ShutDown()
|
||||||
defer tc.podUpdateQueue.ShutDown()
|
defer tc.podUpdateQueue.ShutDown()
|
||||||
|
|
||||||
|
// wait for the cache to be synced
|
||||||
|
if !cache.WaitForNamedCacheSync(tc.name, ctx.Done(), tc.podListerSynced, tc.nodeListerSynced) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
for i := 0; i < UpdateWorkerSize; i++ {
|
for i := 0; i < UpdateWorkerSize; i++ {
|
||||||
tc.nodeUpdateChannels = append(tc.nodeUpdateChannels, make(chan nodeUpdateItem, NodeUpdateChannelSize))
|
tc.nodeUpdateChannels = append(tc.nodeUpdateChannels, make(chan nodeUpdateItem, NodeUpdateChannelSize))
|
||||||
tc.podUpdateChannels = append(tc.podUpdateChannels, make(chan podUpdateItem, podUpdateChannelSize))
|
tc.podUpdateChannels = append(tc.podUpdateChannels, make(chan podUpdateItem, podUpdateChannelSize))
|
||||||
@ -273,11 +358,11 @@ func (tc *NoExecuteTaintManager) Run(ctx context.Context) {
|
|||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tc *NoExecuteTaintManager) worker(ctx context.Context, worker int, done func(), stopCh <-chan struct{}) {
|
func (tc *Controller) worker(ctx context.Context, worker int, done func(), stopCh <-chan struct{}) {
|
||||||
defer done()
|
defer done()
|
||||||
|
|
||||||
// When processing events we want to prioritize Node updates over Pod updates,
|
// When processing events we want to prioritize Node updates over Pod updates,
|
||||||
// as NodeUpdates that interest NoExecuteTaintManager should be handled as soon as possible -
|
// as NodeUpdates that interest the controller should be handled as soon as possible -
|
||||||
// we don't want user (or system) to wait until PodUpdate queue is drained before it can
|
// we don't want user (or system) to wait until PodUpdate queue is drained before it can
|
||||||
// start evicting Pods from tainted Nodes.
|
// start evicting Pods from tainted Nodes.
|
||||||
for {
|
for {
|
||||||
@ -307,7 +392,7 @@ func (tc *NoExecuteTaintManager) worker(ctx context.Context, worker int, done fu
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PodUpdated is used to notify NoExecuteTaintManager about Pod changes.
|
// PodUpdated is used to notify NoExecuteTaintManager about Pod changes.
|
||||||
func (tc *NoExecuteTaintManager) PodUpdated(oldPod *v1.Pod, newPod *v1.Pod) {
|
func (tc *Controller) PodUpdated(oldPod *v1.Pod, newPod *v1.Pod) {
|
||||||
podName := ""
|
podName := ""
|
||||||
podNamespace := ""
|
podNamespace := ""
|
||||||
nodeName := ""
|
nodeName := ""
|
||||||
@ -339,7 +424,7 @@ func (tc *NoExecuteTaintManager) PodUpdated(oldPod *v1.Pod, newPod *v1.Pod) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NodeUpdated is used to notify NoExecuteTaintManager about Node changes.
|
// NodeUpdated is used to notify NoExecuteTaintManager about Node changes.
|
||||||
func (tc *NoExecuteTaintManager) NodeUpdated(oldNode *v1.Node, newNode *v1.Node) {
|
func (tc *Controller) NodeUpdated(oldNode *v1.Node, newNode *v1.Node) {
|
||||||
nodeName := ""
|
nodeName := ""
|
||||||
oldTaints := []v1.Taint{}
|
oldTaints := []v1.Taint{}
|
||||||
if oldNode != nil {
|
if oldNode != nil {
|
||||||
@ -363,13 +448,13 @@ func (tc *NoExecuteTaintManager) NodeUpdated(oldNode *v1.Node, newNode *v1.Node)
|
|||||||
tc.nodeUpdateQueue.Add(updateItem)
|
tc.nodeUpdateQueue.Add(updateItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tc *NoExecuteTaintManager) cancelWorkWithEvent(logger klog.Logger, nsName types.NamespacedName) {
|
func (tc *Controller) cancelWorkWithEvent(logger klog.Logger, nsName types.NamespacedName) {
|
||||||
if tc.taintEvictionQueue.CancelWork(logger, nsName.String()) {
|
if tc.taintEvictionQueue.CancelWork(logger, nsName.String()) {
|
||||||
tc.emitCancelPodDeletionEvent(nsName)
|
tc.emitCancelPodDeletionEvent(nsName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tc *NoExecuteTaintManager) processPodOnNode(
|
func (tc *Controller) processPodOnNode(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
podNamespacedName types.NamespacedName,
|
podNamespacedName types.NamespacedName,
|
||||||
nodeName string,
|
nodeName string,
|
||||||
@ -410,7 +495,7 @@ func (tc *NoExecuteTaintManager) processPodOnNode(
|
|||||||
tc.taintEvictionQueue.AddWork(ctx, NewWorkArgs(podNamespacedName.Name, podNamespacedName.Namespace), startTime, triggerTime)
|
tc.taintEvictionQueue.AddWork(ctx, NewWorkArgs(podNamespacedName.Name, podNamespacedName.Namespace), startTime, triggerTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tc *NoExecuteTaintManager) handlePodUpdate(ctx context.Context, podUpdate podUpdateItem) {
|
func (tc *Controller) handlePodUpdate(ctx context.Context, podUpdate podUpdateItem) {
|
||||||
pod, err := tc.podLister.Pods(podUpdate.podNamespace).Get(podUpdate.podName)
|
pod, err := tc.podLister.Pods(podUpdate.podNamespace).Get(podUpdate.podName)
|
||||||
logger := klog.FromContext(ctx)
|
logger := klog.FromContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -451,7 +536,7 @@ func (tc *NoExecuteTaintManager) handlePodUpdate(ctx context.Context, podUpdate
|
|||||||
tc.processPodOnNode(ctx, podNamespacedName, nodeName, pod.Spec.Tolerations, taints, time.Now())
|
tc.processPodOnNode(ctx, podNamespacedName, nodeName, pod.Spec.Tolerations, taints, time.Now())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tc *NoExecuteTaintManager) handleNodeUpdate(ctx context.Context, nodeUpdate nodeUpdateItem) {
|
func (tc *Controller) handleNodeUpdate(ctx context.Context, nodeUpdate nodeUpdateItem) {
|
||||||
node, err := tc.nodeLister.Get(nodeUpdate.nodeName)
|
node, err := tc.nodeLister.Get(nodeUpdate.nodeName)
|
||||||
logger := klog.FromContext(ctx)
|
logger := klog.FromContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -508,7 +593,7 @@ func (tc *NoExecuteTaintManager) handleNodeUpdate(ctx context.Context, nodeUpdat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tc *NoExecuteTaintManager) emitPodDeletionEvent(nsName types.NamespacedName) {
|
func (tc *Controller) emitPodDeletionEvent(nsName types.NamespacedName) {
|
||||||
if tc.recorder == nil {
|
if tc.recorder == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -521,7 +606,7 @@ func (tc *NoExecuteTaintManager) emitPodDeletionEvent(nsName types.NamespacedNam
|
|||||||
tc.recorder.Eventf(ref, v1.EventTypeNormal, "TaintManagerEviction", "Marking for deletion Pod %s", nsName.String())
|
tc.recorder.Eventf(ref, v1.EventTypeNormal, "TaintManagerEviction", "Marking for deletion Pod %s", nsName.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tc *NoExecuteTaintManager) emitCancelPodDeletionEvent(nsName types.NamespacedName) {
|
func (tc *Controller) emitCancelPodDeletionEvent(nsName types.NamespacedName) {
|
||||||
if tc.recorder == nil {
|
if tc.recorder == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package scheduler
|
package tainteviction
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -25,7 +25,7 @@ import (
|
|||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
|
|
||||||
v1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/fields"
|
"k8s.io/apimachinery/pkg/fields"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
@ -44,16 +44,16 @@ import (
|
|||||||
var timeForControllerToProgressForSanityCheck = 20 * time.Millisecond
|
var timeForControllerToProgressForSanityCheck = 20 * time.Millisecond
|
||||||
|
|
||||||
func getPodsAssignedToNode(ctx context.Context, c *fake.Clientset) GetPodsByNodeNameFunc {
|
func getPodsAssignedToNode(ctx context.Context, c *fake.Clientset) GetPodsByNodeNameFunc {
|
||||||
return func(nodeName string) ([]*v1.Pod, error) {
|
return func(nodeName string) ([]*corev1.Pod, error) {
|
||||||
selector := fields.SelectorFromSet(fields.Set{"spec.nodeName": nodeName})
|
selector := fields.SelectorFromSet(fields.Set{"spec.nodeName": nodeName})
|
||||||
pods, err := c.CoreV1().Pods(v1.NamespaceAll).List(ctx, metav1.ListOptions{
|
pods, err := c.CoreV1().Pods(corev1.NamespaceAll).List(ctx, metav1.ListOptions{
|
||||||
FieldSelector: selector.String(),
|
FieldSelector: selector.String(),
|
||||||
LabelSelector: labels.Everything().String(),
|
LabelSelector: labels.Everything().String(),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []*v1.Pod{}, fmt.Errorf("failed to get Pods assigned to node %v", nodeName)
|
return []*corev1.Pod{}, fmt.Errorf("failed to get Pods assigned to node %v", nodeName)
|
||||||
}
|
}
|
||||||
rPods := make([]*v1.Pod, len(pods.Items))
|
rPods := make([]*corev1.Pod, len(pods.Items))
|
||||||
for i := range pods.Items {
|
for i := range pods.Items {
|
||||||
rPods[i] = &pods.Items[i]
|
rPods[i] = &pods.Items[i]
|
||||||
}
|
}
|
||||||
@ -61,31 +61,31 @@ func getPodsAssignedToNode(ctx context.Context, c *fake.Clientset) GetPodsByNode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createNoExecuteTaint(index int) v1.Taint {
|
func createNoExecuteTaint(index int) corev1.Taint {
|
||||||
now := metav1.Now()
|
now := metav1.Now()
|
||||||
return v1.Taint{
|
return corev1.Taint{
|
||||||
Key: "testTaint" + fmt.Sprintf("%v", index),
|
Key: "testTaint" + fmt.Sprintf("%v", index),
|
||||||
Value: "test" + fmt.Sprintf("%v", index),
|
Value: "test" + fmt.Sprintf("%v", index),
|
||||||
Effect: v1.TaintEffectNoExecute,
|
Effect: corev1.TaintEffectNoExecute,
|
||||||
TimeAdded: &now,
|
TimeAdded: &now,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func addToleration(pod *v1.Pod, index int, duration int64) *v1.Pod {
|
func addToleration(pod *corev1.Pod, index int, duration int64) *corev1.Pod {
|
||||||
if pod.Annotations == nil {
|
if pod.Annotations == nil {
|
||||||
pod.Annotations = map[string]string{}
|
pod.Annotations = map[string]string{}
|
||||||
}
|
}
|
||||||
if duration < 0 {
|
if duration < 0 {
|
||||||
pod.Spec.Tolerations = []v1.Toleration{{Key: "testTaint" + fmt.Sprintf("%v", index), Value: "test" + fmt.Sprintf("%v", index), Effect: v1.TaintEffectNoExecute}}
|
pod.Spec.Tolerations = []corev1.Toleration{{Key: "testTaint" + fmt.Sprintf("%v", index), Value: "test" + fmt.Sprintf("%v", index), Effect: corev1.TaintEffectNoExecute}}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
pod.Spec.Tolerations = []v1.Toleration{{Key: "testTaint" + fmt.Sprintf("%v", index), Value: "test" + fmt.Sprintf("%v", index), Effect: v1.TaintEffectNoExecute, TolerationSeconds: &duration}}
|
pod.Spec.Tolerations = []corev1.Toleration{{Key: "testTaint" + fmt.Sprintf("%v", index), Value: "test" + fmt.Sprintf("%v", index), Effect: corev1.TaintEffectNoExecute, TolerationSeconds: &duration}}
|
||||||
}
|
}
|
||||||
return pod
|
return pod
|
||||||
}
|
}
|
||||||
|
|
||||||
func addTaintsToNode(node *v1.Node, key, value string, indices []int) *v1.Node {
|
func addTaintsToNode(node *corev1.Node, key, value string, indices []int) *corev1.Node {
|
||||||
taints := []v1.Taint{}
|
taints := []corev1.Taint{}
|
||||||
for _, index := range indices {
|
for _, index := range indices {
|
||||||
taints = append(taints, createNoExecuteTaint(index))
|
taints = append(taints, createNoExecuteTaint(index))
|
||||||
}
|
}
|
||||||
@ -93,11 +93,16 @@ func addTaintsToNode(node *v1.Node, key, value string, indices []int) *v1.Node {
|
|||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupNewNoExecuteTaintManager(ctx context.Context, fakeClientSet *fake.Clientset) (*NoExecuteTaintManager, cache.Indexer, cache.Indexer) {
|
var alwaysReady = func() bool { return true }
|
||||||
|
|
||||||
|
func setupNewController(ctx context.Context, fakeClientSet *fake.Clientset) (*Controller, cache.Indexer, cache.Indexer) {
|
||||||
informerFactory := informers.NewSharedInformerFactory(fakeClientSet, 0)
|
informerFactory := informers.NewSharedInformerFactory(fakeClientSet, 0)
|
||||||
podIndexer := informerFactory.Core().V1().Pods().Informer().GetIndexer()
|
podIndexer := informerFactory.Core().V1().Pods().Informer().GetIndexer()
|
||||||
nodeIndexer := informerFactory.Core().V1().Nodes().Informer().GetIndexer()
|
nodeIndexer := informerFactory.Core().V1().Nodes().Informer().GetIndexer()
|
||||||
mgr := NewNoExecuteTaintManager(ctx, fakeClientSet, informerFactory.Core().V1().Pods().Lister(), informerFactory.Core().V1().Nodes().Lister(), getPodsAssignedToNode(ctx, fakeClientSet))
|
mgr, _ := New(ctx, fakeClientSet, informerFactory.Core().V1().Pods(), informerFactory.Core().V1().Nodes(), "taint-eviction-controller")
|
||||||
|
mgr.podListerSynced = alwaysReady
|
||||||
|
mgr.nodeListerSynced = alwaysReady
|
||||||
|
mgr.getPodsAssignedToNode = getPodsAssignedToNode(ctx, fakeClientSet)
|
||||||
return mgr, podIndexer, nodeIndexer
|
return mgr, podIndexer, nodeIndexer
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,16 +118,16 @@ func (a durationSlice) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|||||||
func (a durationSlice) Less(i, j int) bool { return a[i].timestamp < a[j].timestamp }
|
func (a durationSlice) Less(i, j int) bool { return a[i].timestamp < a[j].timestamp }
|
||||||
|
|
||||||
func TestFilterNoExecuteTaints(t *testing.T) {
|
func TestFilterNoExecuteTaints(t *testing.T) {
|
||||||
taints := []v1.Taint{
|
taints := []corev1.Taint{
|
||||||
{
|
{
|
||||||
Key: "one",
|
Key: "one",
|
||||||
Value: "one",
|
Value: "one",
|
||||||
Effect: v1.TaintEffectNoExecute,
|
Effect: corev1.TaintEffectNoExecute,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Key: "two",
|
Key: "two",
|
||||||
Value: "two",
|
Value: "two",
|
||||||
Effect: v1.TaintEffectNoSchedule,
|
Effect: corev1.TaintEffectNoSchedule,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
taints = getNoExecuteTaints(taints)
|
taints = getNoExecuteTaints(taints)
|
||||||
@ -134,8 +139,8 @@ func TestFilterNoExecuteTaints(t *testing.T) {
|
|||||||
func TestCreatePod(t *testing.T) {
|
func TestCreatePod(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
description string
|
description string
|
||||||
pod *v1.Pod
|
pod *corev1.Pod
|
||||||
taintedNodes map[string][]v1.Taint
|
taintedNodes map[string][]corev1.Taint
|
||||||
expectPatch bool
|
expectPatch bool
|
||||||
expectDelete bool
|
expectDelete bool
|
||||||
enablePodDisruptionConditions bool
|
enablePodDisruptionConditions bool
|
||||||
@ -143,19 +148,19 @@ func TestCreatePod(t *testing.T) {
|
|||||||
{
|
{
|
||||||
description: "not scheduled - ignore",
|
description: "not scheduled - ignore",
|
||||||
pod: testutil.NewPod("pod1", ""),
|
pod: testutil.NewPod("pod1", ""),
|
||||||
taintedNodes: map[string][]v1.Taint{},
|
taintedNodes: map[string][]corev1.Taint{},
|
||||||
expectDelete: false,
|
expectDelete: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "scheduled on untainted Node",
|
description: "scheduled on untainted Node",
|
||||||
pod: testutil.NewPod("pod1", "node1"),
|
pod: testutil.NewPod("pod1", "node1"),
|
||||||
taintedNodes: map[string][]v1.Taint{},
|
taintedNodes: map[string][]corev1.Taint{},
|
||||||
expectDelete: false,
|
expectDelete: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "schedule on tainted Node",
|
description: "schedule on tainted Node",
|
||||||
pod: testutil.NewPod("pod1", "node1"),
|
pod: testutil.NewPod("pod1", "node1"),
|
||||||
taintedNodes: map[string][]v1.Taint{
|
taintedNodes: map[string][]corev1.Taint{
|
||||||
"node1": {createNoExecuteTaint(1)},
|
"node1": {createNoExecuteTaint(1)},
|
||||||
},
|
},
|
||||||
expectDelete: true,
|
expectDelete: true,
|
||||||
@ -163,7 +168,7 @@ func TestCreatePod(t *testing.T) {
|
|||||||
{
|
{
|
||||||
description: "schedule on tainted Node; PodDisruptionConditions enabled",
|
description: "schedule on tainted Node; PodDisruptionConditions enabled",
|
||||||
pod: testutil.NewPod("pod1", "node1"),
|
pod: testutil.NewPod("pod1", "node1"),
|
||||||
taintedNodes: map[string][]v1.Taint{
|
taintedNodes: map[string][]corev1.Taint{
|
||||||
"node1": {createNoExecuteTaint(1)},
|
"node1": {createNoExecuteTaint(1)},
|
||||||
},
|
},
|
||||||
expectPatch: true,
|
expectPatch: true,
|
||||||
@ -173,7 +178,7 @@ func TestCreatePod(t *testing.T) {
|
|||||||
{
|
{
|
||||||
description: "schedule on tainted Node with finite toleration",
|
description: "schedule on tainted Node with finite toleration",
|
||||||
pod: addToleration(testutil.NewPod("pod1", "node1"), 1, 100),
|
pod: addToleration(testutil.NewPod("pod1", "node1"), 1, 100),
|
||||||
taintedNodes: map[string][]v1.Taint{
|
taintedNodes: map[string][]corev1.Taint{
|
||||||
"node1": {createNoExecuteTaint(1)},
|
"node1": {createNoExecuteTaint(1)},
|
||||||
},
|
},
|
||||||
expectDelete: false,
|
expectDelete: false,
|
||||||
@ -181,7 +186,7 @@ func TestCreatePod(t *testing.T) {
|
|||||||
{
|
{
|
||||||
description: "schedule on tainted Node with infinite toleration",
|
description: "schedule on tainted Node with infinite toleration",
|
||||||
pod: addToleration(testutil.NewPod("pod1", "node1"), 1, -1),
|
pod: addToleration(testutil.NewPod("pod1", "node1"), 1, -1),
|
||||||
taintedNodes: map[string][]v1.Taint{
|
taintedNodes: map[string][]corev1.Taint{
|
||||||
"node1": {createNoExecuteTaint(1)},
|
"node1": {createNoExecuteTaint(1)},
|
||||||
},
|
},
|
||||||
expectDelete: false,
|
expectDelete: false,
|
||||||
@ -189,7 +194,7 @@ func TestCreatePod(t *testing.T) {
|
|||||||
{
|
{
|
||||||
description: "schedule on tainted Node with infinite ivalid toleration",
|
description: "schedule on tainted Node with infinite ivalid toleration",
|
||||||
pod: addToleration(testutil.NewPod("pod1", "node1"), 2, -1),
|
pod: addToleration(testutil.NewPod("pod1", "node1"), 2, -1),
|
||||||
taintedNodes: map[string][]v1.Taint{
|
taintedNodes: map[string][]corev1.Taint{
|
||||||
"node1": {createNoExecuteTaint(1)},
|
"node1": {createNoExecuteTaint(1)},
|
||||||
},
|
},
|
||||||
expectDelete: true,
|
expectDelete: true,
|
||||||
@ -200,8 +205,8 @@ func TestCreatePod(t *testing.T) {
|
|||||||
t.Run(item.description, func(t *testing.T) {
|
t.Run(item.description, func(t *testing.T) {
|
||||||
defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.PodDisruptionConditions, item.enablePodDisruptionConditions)()
|
defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.PodDisruptionConditions, item.enablePodDisruptionConditions)()
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
fakeClientset := fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*item.pod}})
|
fakeClientset := fake.NewSimpleClientset(&corev1.PodList{Items: []corev1.Pod{*item.pod}})
|
||||||
controller, podIndexer, _ := setupNewNoExecuteTaintManager(ctx, fakeClientset)
|
controller, podIndexer, _ := setupNewController(ctx, fakeClientset)
|
||||||
controller.recorder = testutil.NewFakeRecorder()
|
controller.recorder = testutil.NewFakeRecorder()
|
||||||
go controller.Run(ctx)
|
go controller.Run(ctx)
|
||||||
controller.taintedNodes = item.taintedNodes
|
controller.taintedNodes = item.taintedNodes
|
||||||
@ -221,10 +226,10 @@ func TestDeletePod(t *testing.T) {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
fakeClientset := fake.NewSimpleClientset()
|
fakeClientset := fake.NewSimpleClientset()
|
||||||
controller, _, _ := setupNewNoExecuteTaintManager(ctx, fakeClientset)
|
controller, _, _ := setupNewController(ctx, fakeClientset)
|
||||||
controller.recorder = testutil.NewFakeRecorder()
|
controller.recorder = testutil.NewFakeRecorder()
|
||||||
go controller.Run(ctx)
|
go controller.Run(ctx)
|
||||||
controller.taintedNodes = map[string][]v1.Taint{
|
controller.taintedNodes = map[string][]corev1.Taint{
|
||||||
"node1": {createNoExecuteTaint(1)},
|
"node1": {createNoExecuteTaint(1)},
|
||||||
}
|
}
|
||||||
controller.PodUpdated(testutil.NewPod("pod1", "node1"), nil)
|
controller.PodUpdated(testutil.NewPod("pod1", "node1"), nil)
|
||||||
@ -235,10 +240,10 @@ func TestDeletePod(t *testing.T) {
|
|||||||
func TestUpdatePod(t *testing.T) {
|
func TestUpdatePod(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
description string
|
description string
|
||||||
prevPod *v1.Pod
|
prevPod *corev1.Pod
|
||||||
awaitForScheduledEviction bool
|
awaitForScheduledEviction bool
|
||||||
newPod *v1.Pod
|
newPod *corev1.Pod
|
||||||
taintedNodes map[string][]v1.Taint
|
taintedNodes map[string][]corev1.Taint
|
||||||
expectPatch bool
|
expectPatch bool
|
||||||
expectDelete bool
|
expectDelete bool
|
||||||
enablePodDisruptionConditions bool
|
enablePodDisruptionConditions bool
|
||||||
@ -247,7 +252,7 @@ func TestUpdatePod(t *testing.T) {
|
|||||||
description: "scheduling onto tainted Node results in patch and delete when PodDisruptionConditions enabled",
|
description: "scheduling onto tainted Node results in patch and delete when PodDisruptionConditions enabled",
|
||||||
prevPod: testutil.NewPod("pod1", ""),
|
prevPod: testutil.NewPod("pod1", ""),
|
||||||
newPod: testutil.NewPod("pod1", "node1"),
|
newPod: testutil.NewPod("pod1", "node1"),
|
||||||
taintedNodes: map[string][]v1.Taint{
|
taintedNodes: map[string][]corev1.Taint{
|
||||||
"node1": {createNoExecuteTaint(1)},
|
"node1": {createNoExecuteTaint(1)},
|
||||||
},
|
},
|
||||||
expectPatch: true,
|
expectPatch: true,
|
||||||
@ -258,7 +263,7 @@ func TestUpdatePod(t *testing.T) {
|
|||||||
description: "scheduling onto tainted Node",
|
description: "scheduling onto tainted Node",
|
||||||
prevPod: testutil.NewPod("pod1", ""),
|
prevPod: testutil.NewPod("pod1", ""),
|
||||||
newPod: testutil.NewPod("pod1", "node1"),
|
newPod: testutil.NewPod("pod1", "node1"),
|
||||||
taintedNodes: map[string][]v1.Taint{
|
taintedNodes: map[string][]corev1.Taint{
|
||||||
"node1": {createNoExecuteTaint(1)},
|
"node1": {createNoExecuteTaint(1)},
|
||||||
},
|
},
|
||||||
expectDelete: true,
|
expectDelete: true,
|
||||||
@ -267,7 +272,7 @@ func TestUpdatePod(t *testing.T) {
|
|||||||
description: "scheduling onto tainted Node with toleration",
|
description: "scheduling onto tainted Node with toleration",
|
||||||
prevPod: addToleration(testutil.NewPod("pod1", ""), 1, -1),
|
prevPod: addToleration(testutil.NewPod("pod1", ""), 1, -1),
|
||||||
newPod: addToleration(testutil.NewPod("pod1", "node1"), 1, -1),
|
newPod: addToleration(testutil.NewPod("pod1", "node1"), 1, -1),
|
||||||
taintedNodes: map[string][]v1.Taint{
|
taintedNodes: map[string][]corev1.Taint{
|
||||||
"node1": {createNoExecuteTaint(1)},
|
"node1": {createNoExecuteTaint(1)},
|
||||||
},
|
},
|
||||||
expectDelete: false,
|
expectDelete: false,
|
||||||
@ -277,7 +282,7 @@ func TestUpdatePod(t *testing.T) {
|
|||||||
prevPod: addToleration(testutil.NewPod("pod1", "node1"), 1, 100),
|
prevPod: addToleration(testutil.NewPod("pod1", "node1"), 1, 100),
|
||||||
newPod: testutil.NewPod("pod1", "node1"),
|
newPod: testutil.NewPod("pod1", "node1"),
|
||||||
awaitForScheduledEviction: true,
|
awaitForScheduledEviction: true,
|
||||||
taintedNodes: map[string][]v1.Taint{
|
taintedNodes: map[string][]corev1.Taint{
|
||||||
"node1": {createNoExecuteTaint(1)},
|
"node1": {createNoExecuteTaint(1)},
|
||||||
},
|
},
|
||||||
expectDelete: true,
|
expectDelete: true,
|
||||||
@ -287,7 +292,7 @@ func TestUpdatePod(t *testing.T) {
|
|||||||
prevPod: addToleration(testutil.NewPod("pod1", "node1"), 1, 1),
|
prevPod: addToleration(testutil.NewPod("pod1", "node1"), 1, 1),
|
||||||
newPod: addToleration(testutil.NewPod("pod1", "node1"), 1, 100),
|
newPod: addToleration(testutil.NewPod("pod1", "node1"), 1, 100),
|
||||||
awaitForScheduledEviction: true,
|
awaitForScheduledEviction: true,
|
||||||
taintedNodes: map[string][]v1.Taint{
|
taintedNodes: map[string][]corev1.Taint{
|
||||||
"node1": {createNoExecuteTaint(1)},
|
"node1": {createNoExecuteTaint(1)},
|
||||||
},
|
},
|
||||||
expectDelete: true,
|
expectDelete: true,
|
||||||
@ -298,8 +303,8 @@ func TestUpdatePod(t *testing.T) {
|
|||||||
t.Run(item.description, func(t *testing.T) {
|
t.Run(item.description, func(t *testing.T) {
|
||||||
defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.PodDisruptionConditions, item.enablePodDisruptionConditions)()
|
defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.PodDisruptionConditions, item.enablePodDisruptionConditions)()
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
fakeClientset := fake.NewSimpleClientset(&v1.PodList{Items: []v1.Pod{*item.prevPod}})
|
fakeClientset := fake.NewSimpleClientset(&corev1.PodList{Items: []corev1.Pod{*item.prevPod}})
|
||||||
controller, podIndexer, _ := setupNewNoExecuteTaintManager(context.TODO(), fakeClientset)
|
controller, podIndexer, _ := setupNewController(context.TODO(), fakeClientset)
|
||||||
controller.recorder = testutil.NewFakeRecorder()
|
controller.recorder = testutil.NewFakeRecorder()
|
||||||
controller.taintedNodes = item.taintedNodes
|
controller.taintedNodes = item.taintedNodes
|
||||||
go controller.Run(ctx)
|
go controller.Run(ctx)
|
||||||
@ -330,14 +335,14 @@ func TestUpdatePod(t *testing.T) {
|
|||||||
func TestCreateNode(t *testing.T) {
|
func TestCreateNode(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
description string
|
description string
|
||||||
pods []v1.Pod
|
pods []corev1.Pod
|
||||||
node *v1.Node
|
node *corev1.Node
|
||||||
expectPatch bool
|
expectPatch bool
|
||||||
expectDelete bool
|
expectDelete bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
description: "Creating Node matching already assigned Pod",
|
description: "Creating Node matching already assigned Pod",
|
||||||
pods: []v1.Pod{
|
pods: []corev1.Pod{
|
||||||
*testutil.NewPod("pod1", "node1"),
|
*testutil.NewPod("pod1", "node1"),
|
||||||
},
|
},
|
||||||
node: testutil.NewNode("node1"),
|
node: testutil.NewNode("node1"),
|
||||||
@ -346,7 +351,7 @@ func TestCreateNode(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Creating tainted Node matching already assigned Pod",
|
description: "Creating tainted Node matching already assigned Pod",
|
||||||
pods: []v1.Pod{
|
pods: []corev1.Pod{
|
||||||
*testutil.NewPod("pod1", "node1"),
|
*testutil.NewPod("pod1", "node1"),
|
||||||
},
|
},
|
||||||
node: addTaintsToNode(testutil.NewNode("node1"), "testTaint1", "taint1", []int{1}),
|
node: addTaintsToNode(testutil.NewNode("node1"), "testTaint1", "taint1", []int{1}),
|
||||||
@ -355,7 +360,7 @@ func TestCreateNode(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Creating tainted Node matching already assigned tolerating Pod",
|
description: "Creating tainted Node matching already assigned tolerating Pod",
|
||||||
pods: []v1.Pod{
|
pods: []corev1.Pod{
|
||||||
*addToleration(testutil.NewPod("pod1", "node1"), 1, -1),
|
*addToleration(testutil.NewPod("pod1", "node1"), 1, -1),
|
||||||
},
|
},
|
||||||
node: addTaintsToNode(testutil.NewNode("node1"), "testTaint1", "taint1", []int{1}),
|
node: addTaintsToNode(testutil.NewNode("node1"), "testTaint1", "taint1", []int{1}),
|
||||||
@ -366,8 +371,8 @@ func TestCreateNode(t *testing.T) {
|
|||||||
|
|
||||||
for _, item := range testCases {
|
for _, item := range testCases {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
fakeClientset := fake.NewSimpleClientset(&v1.PodList{Items: item.pods})
|
fakeClientset := fake.NewSimpleClientset(&corev1.PodList{Items: item.pods})
|
||||||
controller, _, nodeIndexer := setupNewNoExecuteTaintManager(ctx, fakeClientset)
|
controller, _, nodeIndexer := setupNewController(ctx, fakeClientset)
|
||||||
nodeIndexer.Add(item.node)
|
nodeIndexer.Add(item.node)
|
||||||
controller.recorder = testutil.NewFakeRecorder()
|
controller.recorder = testutil.NewFakeRecorder()
|
||||||
go controller.Run(ctx)
|
go controller.Run(ctx)
|
||||||
@ -382,9 +387,9 @@ func TestCreateNode(t *testing.T) {
|
|||||||
func TestDeleteNode(t *testing.T) {
|
func TestDeleteNode(t *testing.T) {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
fakeClientset := fake.NewSimpleClientset()
|
fakeClientset := fake.NewSimpleClientset()
|
||||||
controller, _, _ := setupNewNoExecuteTaintManager(ctx, fakeClientset)
|
controller, _, _ := setupNewController(ctx, fakeClientset)
|
||||||
controller.recorder = testutil.NewFakeRecorder()
|
controller.recorder = testutil.NewFakeRecorder()
|
||||||
controller.taintedNodes = map[string][]v1.Taint{
|
controller.taintedNodes = map[string][]corev1.Taint{
|
||||||
"node1": {createNoExecuteTaint(1)},
|
"node1": {createNoExecuteTaint(1)},
|
||||||
}
|
}
|
||||||
go controller.Run(ctx)
|
go controller.Run(ctx)
|
||||||
@ -406,9 +411,9 @@ func TestDeleteNode(t *testing.T) {
|
|||||||
func TestUpdateNode(t *testing.T) {
|
func TestUpdateNode(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
description string
|
description string
|
||||||
pods []v1.Pod
|
pods []corev1.Pod
|
||||||
oldNode *v1.Node
|
oldNode *corev1.Node
|
||||||
newNode *v1.Node
|
newNode *corev1.Node
|
||||||
expectPatch bool
|
expectPatch bool
|
||||||
expectDelete bool
|
expectDelete bool
|
||||||
additionalSleep time.Duration
|
additionalSleep time.Duration
|
||||||
@ -416,7 +421,7 @@ func TestUpdateNode(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
description: "Added taint, expect node patched and deleted when PodDisruptionConditions is enabled",
|
description: "Added taint, expect node patched and deleted when PodDisruptionConditions is enabled",
|
||||||
pods: []v1.Pod{
|
pods: []corev1.Pod{
|
||||||
*testutil.NewPod("pod1", "node1"),
|
*testutil.NewPod("pod1", "node1"),
|
||||||
},
|
},
|
||||||
oldNode: testutil.NewNode("node1"),
|
oldNode: testutil.NewNode("node1"),
|
||||||
@ -427,7 +432,7 @@ func TestUpdateNode(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Added taint",
|
description: "Added taint",
|
||||||
pods: []v1.Pod{
|
pods: []corev1.Pod{
|
||||||
*testutil.NewPod("pod1", "node1"),
|
*testutil.NewPod("pod1", "node1"),
|
||||||
},
|
},
|
||||||
oldNode: testutil.NewNode("node1"),
|
oldNode: testutil.NewNode("node1"),
|
||||||
@ -436,7 +441,7 @@ func TestUpdateNode(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Added tolerated taint",
|
description: "Added tolerated taint",
|
||||||
pods: []v1.Pod{
|
pods: []corev1.Pod{
|
||||||
*addToleration(testutil.NewPod("pod1", "node1"), 1, 100),
|
*addToleration(testutil.NewPod("pod1", "node1"), 1, 100),
|
||||||
},
|
},
|
||||||
oldNode: testutil.NewNode("node1"),
|
oldNode: testutil.NewNode("node1"),
|
||||||
@ -445,7 +450,7 @@ func TestUpdateNode(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Only one added taint tolerated",
|
description: "Only one added taint tolerated",
|
||||||
pods: []v1.Pod{
|
pods: []corev1.Pod{
|
||||||
*addToleration(testutil.NewPod("pod1", "node1"), 1, 100),
|
*addToleration(testutil.NewPod("pod1", "node1"), 1, 100),
|
||||||
},
|
},
|
||||||
oldNode: testutil.NewNode("node1"),
|
oldNode: testutil.NewNode("node1"),
|
||||||
@ -454,7 +459,7 @@ func TestUpdateNode(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Taint removed",
|
description: "Taint removed",
|
||||||
pods: []v1.Pod{
|
pods: []corev1.Pod{
|
||||||
*addToleration(testutil.NewPod("pod1", "node1"), 1, 1),
|
*addToleration(testutil.NewPod("pod1", "node1"), 1, 1),
|
||||||
},
|
},
|
||||||
oldNode: addTaintsToNode(testutil.NewNode("node1"), "testTaint1", "taint1", []int{1}),
|
oldNode: addTaintsToNode(testutil.NewNode("node1"), "testTaint1", "taint1", []int{1}),
|
||||||
@ -464,24 +469,24 @@ func TestUpdateNode(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Pod with multiple tolerations are evicted when first one runs out",
|
description: "Pod with multiple tolerations are evicted when first one runs out",
|
||||||
pods: []v1.Pod{
|
pods: []corev1.Pod{
|
||||||
{
|
{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Namespace: "default",
|
Namespace: "default",
|
||||||
Name: "pod1",
|
Name: "pod1",
|
||||||
},
|
},
|
||||||
Spec: v1.PodSpec{
|
Spec: corev1.PodSpec{
|
||||||
NodeName: "node1",
|
NodeName: "node1",
|
||||||
Tolerations: []v1.Toleration{
|
Tolerations: []corev1.Toleration{
|
||||||
{Key: "testTaint1", Value: "test1", Effect: v1.TaintEffectNoExecute, TolerationSeconds: &[]int64{1}[0]},
|
{Key: "testTaint1", Value: "test1", Effect: corev1.TaintEffectNoExecute, TolerationSeconds: &[]int64{1}[0]},
|
||||||
{Key: "testTaint2", Value: "test2", Effect: v1.TaintEffectNoExecute, TolerationSeconds: &[]int64{100}[0]},
|
{Key: "testTaint2", Value: "test2", Effect: corev1.TaintEffectNoExecute, TolerationSeconds: &[]int64{100}[0]},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Status: v1.PodStatus{
|
Status: corev1.PodStatus{
|
||||||
Conditions: []v1.PodCondition{
|
Conditions: []corev1.PodCondition{
|
||||||
{
|
{
|
||||||
Type: v1.PodReady,
|
Type: corev1.PodReady,
|
||||||
Status: v1.ConditionTrue,
|
Status: corev1.ConditionTrue,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -499,8 +504,8 @@ func TestUpdateNode(t *testing.T) {
|
|||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
fakeClientset := fake.NewSimpleClientset(&v1.PodList{Items: item.pods})
|
fakeClientset := fake.NewSimpleClientset(&corev1.PodList{Items: item.pods})
|
||||||
controller, _, nodeIndexer := setupNewNoExecuteTaintManager(ctx, fakeClientset)
|
controller, _, nodeIndexer := setupNewController(ctx, fakeClientset)
|
||||||
nodeIndexer.Add(item.newNode)
|
nodeIndexer.Add(item.newNode)
|
||||||
controller.recorder = testutil.NewFakeRecorder()
|
controller.recorder = testutil.NewFakeRecorder()
|
||||||
go controller.Run(ctx)
|
go controller.Run(ctx)
|
||||||
@ -521,23 +526,23 @@ func TestUpdateNodeWithMultipleTaints(t *testing.T) {
|
|||||||
|
|
||||||
minute := int64(60)
|
minute := int64(60)
|
||||||
pod := testutil.NewPod("pod1", "node1")
|
pod := testutil.NewPod("pod1", "node1")
|
||||||
pod.Spec.Tolerations = []v1.Toleration{
|
pod.Spec.Tolerations = []corev1.Toleration{
|
||||||
{Key: taint1.Key, Operator: v1.TolerationOpExists, Effect: v1.TaintEffectNoExecute},
|
{Key: taint1.Key, Operator: corev1.TolerationOpExists, Effect: corev1.TaintEffectNoExecute},
|
||||||
{Key: taint2.Key, Operator: v1.TolerationOpExists, Effect: v1.TaintEffectNoExecute, TolerationSeconds: &minute},
|
{Key: taint2.Key, Operator: corev1.TolerationOpExists, Effect: corev1.TaintEffectNoExecute, TolerationSeconds: &minute},
|
||||||
}
|
}
|
||||||
podNamespacedName := types.NamespacedName{Namespace: pod.Namespace, Name: pod.Name}
|
podNamespacedName := types.NamespacedName{Namespace: pod.Namespace, Name: pod.Name}
|
||||||
|
|
||||||
untaintedNode := testutil.NewNode("node1")
|
untaintedNode := testutil.NewNode("node1")
|
||||||
|
|
||||||
doubleTaintedNode := testutil.NewNode("node1")
|
doubleTaintedNode := testutil.NewNode("node1")
|
||||||
doubleTaintedNode.Spec.Taints = []v1.Taint{taint1, taint2}
|
doubleTaintedNode.Spec.Taints = []corev1.Taint{taint1, taint2}
|
||||||
|
|
||||||
singleTaintedNode := testutil.NewNode("node1")
|
singleTaintedNode := testutil.NewNode("node1")
|
||||||
singleTaintedNode.Spec.Taints = []v1.Taint{taint1}
|
singleTaintedNode.Spec.Taints = []corev1.Taint{taint1}
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.TODO())
|
ctx, cancel := context.WithCancel(context.TODO())
|
||||||
fakeClientset := fake.NewSimpleClientset(pod)
|
fakeClientset := fake.NewSimpleClientset(pod)
|
||||||
controller, _, nodeIndexer := setupNewNoExecuteTaintManager(ctx, fakeClientset)
|
controller, _, nodeIndexer := setupNewController(ctx, fakeClientset)
|
||||||
controller.recorder = testutil.NewFakeRecorder()
|
controller.recorder = testutil.NewFakeRecorder()
|
||||||
go controller.Run(ctx)
|
go controller.Run(ctx)
|
||||||
|
|
||||||
@ -585,14 +590,14 @@ func TestUpdateNodeWithMultipleTaints(t *testing.T) {
|
|||||||
func TestUpdateNodeWithMultiplePods(t *testing.T) {
|
func TestUpdateNodeWithMultiplePods(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
description string
|
description string
|
||||||
pods []v1.Pod
|
pods []corev1.Pod
|
||||||
oldNode *v1.Node
|
oldNode *corev1.Node
|
||||||
newNode *v1.Node
|
newNode *corev1.Node
|
||||||
expectedDeleteTimes durationSlice
|
expectedDeleteTimes durationSlice
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
description: "Pods with different toleration times are evicted appropriately",
|
description: "Pods with different toleration times are evicted appropriately",
|
||||||
pods: []v1.Pod{
|
pods: []corev1.Pod{
|
||||||
*testutil.NewPod("pod1", "node1"),
|
*testutil.NewPod("pod1", "node1"),
|
||||||
*addToleration(testutil.NewPod("pod2", "node1"), 1, 1),
|
*addToleration(testutil.NewPod("pod2", "node1"), 1, 1),
|
||||||
*addToleration(testutil.NewPod("pod3", "node1"), 1, -1),
|
*addToleration(testutil.NewPod("pod3", "node1"), 1, -1),
|
||||||
@ -606,7 +611,7 @@ func TestUpdateNodeWithMultiplePods(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "Evict all pods not matching all taints instantly",
|
description: "Evict all pods not matching all taints instantly",
|
||||||
pods: []v1.Pod{
|
pods: []corev1.Pod{
|
||||||
*testutil.NewPod("pod1", "node1"),
|
*testutil.NewPod("pod1", "node1"),
|
||||||
*addToleration(testutil.NewPod("pod2", "node1"), 1, 1),
|
*addToleration(testutil.NewPod("pod2", "node1"), 1, 1),
|
||||||
*addToleration(testutil.NewPod("pod3", "node1"), 1, -1),
|
*addToleration(testutil.NewPod("pod3", "node1"), 1, -1),
|
||||||
@ -625,9 +630,9 @@ func TestUpdateNodeWithMultiplePods(t *testing.T) {
|
|||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
fakeClientset := fake.NewSimpleClientset(&v1.PodList{Items: item.pods})
|
fakeClientset := fake.NewSimpleClientset(&corev1.PodList{Items: item.pods})
|
||||||
sort.Sort(item.expectedDeleteTimes)
|
sort.Sort(item.expectedDeleteTimes)
|
||||||
controller, _, nodeIndexer := setupNewNoExecuteTaintManager(ctx, fakeClientset)
|
controller, _, nodeIndexer := setupNewController(ctx, fakeClientset)
|
||||||
nodeIndexer.Add(item.newNode)
|
nodeIndexer.Add(item.newNode)
|
||||||
controller.recorder = testutil.NewFakeRecorder()
|
controller.recorder = testutil.NewFakeRecorder()
|
||||||
go controller.Run(ctx)
|
go controller.Run(ctx)
|
||||||
@ -704,15 +709,15 @@ func TestGetMinTolerationTime(t *testing.T) {
|
|||||||
oneSec := 1 * time.Second
|
oneSec := 1 * time.Second
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
tolerations []v1.Toleration
|
tolerations []corev1.Toleration
|
||||||
expected time.Duration
|
expected time.Duration
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
tolerations: []v1.Toleration{},
|
tolerations: []corev1.Toleration{},
|
||||||
expected: 0,
|
expected: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
tolerations: []v1.Toleration{
|
tolerations: []corev1.Toleration{
|
||||||
{
|
{
|
||||||
TolerationSeconds: nil,
|
TolerationSeconds: nil,
|
||||||
},
|
},
|
||||||
@ -720,7 +725,7 @@ func TestGetMinTolerationTime(t *testing.T) {
|
|||||||
expected: -1,
|
expected: -1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
tolerations: []v1.Toleration{
|
tolerations: []corev1.Toleration{
|
||||||
{
|
{
|
||||||
TolerationSeconds: &one,
|
TolerationSeconds: &one,
|
||||||
},
|
},
|
||||||
@ -732,7 +737,7 @@ func TestGetMinTolerationTime(t *testing.T) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
tolerations: []v1.Toleration{
|
tolerations: []corev1.Toleration{
|
||||||
{
|
{
|
||||||
TolerationSeconds: &one,
|
TolerationSeconds: &one,
|
||||||
},
|
},
|
||||||
@ -743,7 +748,7 @@ func TestGetMinTolerationTime(t *testing.T) {
|
|||||||
expected: oneSec,
|
expected: oneSec,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
tolerations: []v1.Toleration{
|
tolerations: []corev1.Toleration{
|
||||||
{
|
{
|
||||||
TolerationSeconds: nil,
|
TolerationSeconds: nil,
|
||||||
},
|
},
|
||||||
@ -770,17 +775,17 @@ func TestGetMinTolerationTime(t *testing.T) {
|
|||||||
func TestEventualConsistency(t *testing.T) {
|
func TestEventualConsistency(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
description string
|
description string
|
||||||
pods []v1.Pod
|
pods []corev1.Pod
|
||||||
prevPod *v1.Pod
|
prevPod *corev1.Pod
|
||||||
newPod *v1.Pod
|
newPod *corev1.Pod
|
||||||
oldNode *v1.Node
|
oldNode *corev1.Node
|
||||||
newNode *v1.Node
|
newNode *corev1.Node
|
||||||
expectPatch bool
|
expectPatch bool
|
||||||
expectDelete bool
|
expectDelete bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
description: "existing pod2 scheduled onto tainted Node",
|
description: "existing pod2 scheduled onto tainted Node",
|
||||||
pods: []v1.Pod{
|
pods: []corev1.Pod{
|
||||||
*testutil.NewPod("pod1", "node1"),
|
*testutil.NewPod("pod1", "node1"),
|
||||||
},
|
},
|
||||||
prevPod: testutil.NewPod("pod2", ""),
|
prevPod: testutil.NewPod("pod2", ""),
|
||||||
@ -792,7 +797,7 @@ func TestEventualConsistency(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "existing pod2 with taint toleration scheduled onto tainted Node",
|
description: "existing pod2 with taint toleration scheduled onto tainted Node",
|
||||||
pods: []v1.Pod{
|
pods: []corev1.Pod{
|
||||||
*testutil.NewPod("pod1", "node1"),
|
*testutil.NewPod("pod1", "node1"),
|
||||||
},
|
},
|
||||||
prevPod: addToleration(testutil.NewPod("pod2", ""), 1, 100),
|
prevPod: addToleration(testutil.NewPod("pod2", ""), 1, 100),
|
||||||
@ -804,7 +809,7 @@ func TestEventualConsistency(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "new pod2 created on tainted Node",
|
description: "new pod2 created on tainted Node",
|
||||||
pods: []v1.Pod{
|
pods: []corev1.Pod{
|
||||||
*testutil.NewPod("pod1", "node1"),
|
*testutil.NewPod("pod1", "node1"),
|
||||||
},
|
},
|
||||||
prevPod: nil,
|
prevPod: nil,
|
||||||
@ -816,7 +821,7 @@ func TestEventualConsistency(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: "new pod2 with tait toleration created on tainted Node",
|
description: "new pod2 with tait toleration created on tainted Node",
|
||||||
pods: []v1.Pod{
|
pods: []corev1.Pod{
|
||||||
*testutil.NewPod("pod1", "node1"),
|
*testutil.NewPod("pod1", "node1"),
|
||||||
},
|
},
|
||||||
prevPod: nil,
|
prevPod: nil,
|
||||||
@ -833,8 +838,8 @@ func TestEventualConsistency(t *testing.T) {
|
|||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
fakeClientset := fake.NewSimpleClientset(&v1.PodList{Items: item.pods})
|
fakeClientset := fake.NewSimpleClientset(&corev1.PodList{Items: item.pods})
|
||||||
controller, podIndexer, nodeIndexer := setupNewNoExecuteTaintManager(ctx, fakeClientset)
|
controller, podIndexer, nodeIndexer := setupNewController(ctx, fakeClientset)
|
||||||
nodeIndexer.Add(item.newNode)
|
nodeIndexer.Add(item.newNode)
|
||||||
controller.recorder = testutil.NewFakeRecorder()
|
controller.recorder = testutil.NewFakeRecorder()
|
||||||
go controller.Run(ctx)
|
go controller.Run(ctx)
|
||||||
@ -899,19 +904,19 @@ func TestPodDeletionEvent(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.Run("emitPodDeletionEvent", func(t *testing.T) {
|
t.Run("emitPodDeletionEvent", func(t *testing.T) {
|
||||||
controller := &NoExecuteTaintManager{}
|
controller := &Controller{}
|
||||||
recorder := testutil.NewFakeRecorder()
|
recorder := testutil.NewFakeRecorder()
|
||||||
controller.recorder = recorder
|
controller.recorder = recorder
|
||||||
controller.emitPodDeletionEvent(types.NamespacedName{
|
controller.emitPodDeletionEvent(types.NamespacedName{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Namespace: "test",
|
Namespace: "test",
|
||||||
})
|
})
|
||||||
want := []*v1.Event{
|
want := []*corev1.Event{
|
||||||
{
|
{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Namespace: "test",
|
Namespace: "test",
|
||||||
},
|
},
|
||||||
InvolvedObject: v1.ObjectReference{
|
InvolvedObject: corev1.ObjectReference{
|
||||||
Kind: "Pod",
|
Kind: "Pod",
|
||||||
APIVersion: "v1",
|
APIVersion: "v1",
|
||||||
Namespace: "test",
|
Namespace: "test",
|
||||||
@ -921,7 +926,7 @@ func TestPodDeletionEvent(t *testing.T) {
|
|||||||
Type: "Normal",
|
Type: "Normal",
|
||||||
Count: 1,
|
Count: 1,
|
||||||
Message: "Marking for deletion Pod test/test",
|
Message: "Marking for deletion Pod test/test",
|
||||||
Source: v1.EventSource{Component: "nodeControllerTest"},
|
Source: corev1.EventSource{Component: "nodeControllerTest"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if diff := cmp.Diff(want, recorder.Events, cmp.FilterPath(f, cmp.Ignore())); len(diff) > 0 {
|
if diff := cmp.Diff(want, recorder.Events, cmp.FilterPath(f, cmp.Ignore())); len(diff) > 0 {
|
||||||
@ -930,19 +935,19 @@ func TestPodDeletionEvent(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("emitCancelPodDeletionEvent", func(t *testing.T) {
|
t.Run("emitCancelPodDeletionEvent", func(t *testing.T) {
|
||||||
controller := &NoExecuteTaintManager{}
|
controller := &Controller{}
|
||||||
recorder := testutil.NewFakeRecorder()
|
recorder := testutil.NewFakeRecorder()
|
||||||
controller.recorder = recorder
|
controller.recorder = recorder
|
||||||
controller.emitCancelPodDeletionEvent(types.NamespacedName{
|
controller.emitCancelPodDeletionEvent(types.NamespacedName{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Namespace: "test",
|
Namespace: "test",
|
||||||
})
|
})
|
||||||
want := []*v1.Event{
|
want := []*corev1.Event{
|
||||||
{
|
{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
Namespace: "test",
|
Namespace: "test",
|
||||||
},
|
},
|
||||||
InvolvedObject: v1.ObjectReference{
|
InvolvedObject: corev1.ObjectReference{
|
||||||
Kind: "Pod",
|
Kind: "Pod",
|
||||||
APIVersion: "v1",
|
APIVersion: "v1",
|
||||||
Namespace: "test",
|
Namespace: "test",
|
||||||
@ -952,7 +957,7 @@ func TestPodDeletionEvent(t *testing.T) {
|
|||||||
Type: "Normal",
|
Type: "Normal",
|
||||||
Count: 1,
|
Count: 1,
|
||||||
Message: "Cancelling deletion of Pod test/test",
|
Message: "Cancelling deletion of Pod test/test",
|
||||||
Source: v1.EventSource{Component: "nodeControllerTest"},
|
Source: corev1.EventSource{Component: "nodeControllerTest"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if diff := cmp.Diff(want, recorder.Events, cmp.FilterPath(f, cmp.Ignore())); len(diff) > 0 {
|
if diff := cmp.Diff(want, recorder.Events, cmp.FilterPath(f, cmp.Ignore())); len(diff) > 0 {
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package scheduler
|
package tainteviction
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -38,7 +38,9 @@ func (w *WorkArgs) KeyFromWorkArgs() string {
|
|||||||
|
|
||||||
// NewWorkArgs is a helper function to create new `WorkArgs`
|
// NewWorkArgs is a helper function to create new `WorkArgs`
|
||||||
func NewWorkArgs(name, namespace string) *WorkArgs {
|
func NewWorkArgs(name, namespace string) *WorkArgs {
|
||||||
return &WorkArgs{types.NamespacedName{Namespace: namespace, Name: name}}
|
return &WorkArgs{
|
||||||
|
NamespacedName: types.NamespacedName{Namespace: namespace, Name: name},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TimedWorker is a responsible for executing a function no earlier than at FireAt time.
|
// TimedWorker is a responsible for executing a function no earlier than at FireAt time.
|
||||||
@ -50,13 +52,13 @@ type TimedWorker struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// createWorker creates a TimedWorker that will execute `f` not earlier than `fireAt`.
|
// createWorker creates a TimedWorker that will execute `f` not earlier than `fireAt`.
|
||||||
func createWorker(ctx context.Context, args *WorkArgs, createdAt time.Time, fireAt time.Time, f func(ctx context.Context, args *WorkArgs) error, clock clock.WithDelayedExecution) *TimedWorker {
|
func createWorker(ctx context.Context, args *WorkArgs, createdAt time.Time, fireAt time.Time, f func(ctx context.Context, fireAt time.Time, args *WorkArgs) error, clock clock.WithDelayedExecution) *TimedWorker {
|
||||||
delay := fireAt.Sub(createdAt)
|
delay := fireAt.Sub(createdAt)
|
||||||
logger := klog.FromContext(ctx)
|
logger := klog.FromContext(ctx)
|
||||||
fWithErrorLogging := func() {
|
fWithErrorLogging := func() {
|
||||||
err := f(ctx, args)
|
err := f(ctx, fireAt, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(err, "NodeLifecycle: timed worker failed")
|
logger.Error(err, "TaintEvictionController: timed worker failed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if delay <= 0 {
|
if delay <= 0 {
|
||||||
@ -84,13 +86,13 @@ type TimedWorkerQueue struct {
|
|||||||
sync.Mutex
|
sync.Mutex
|
||||||
// map of workers keyed by string returned by 'KeyFromWorkArgs' from the given worker.
|
// map of workers keyed by string returned by 'KeyFromWorkArgs' from the given worker.
|
||||||
workers map[string]*TimedWorker
|
workers map[string]*TimedWorker
|
||||||
workFunc func(ctx context.Context, args *WorkArgs) error
|
workFunc func(ctx context.Context, fireAt time.Time, args *WorkArgs) error
|
||||||
clock clock.WithDelayedExecution
|
clock clock.WithDelayedExecution
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateWorkerQueue creates a new TimedWorkerQueue for workers that will execute
|
// CreateWorkerQueue creates a new TimedWorkerQueue for workers that will execute
|
||||||
// given function `f`.
|
// given function `f`.
|
||||||
func CreateWorkerQueue(f func(ctx context.Context, args *WorkArgs) error) *TimedWorkerQueue {
|
func CreateWorkerQueue(f func(ctx context.Context, fireAt time.Time, args *WorkArgs) error) *TimedWorkerQueue {
|
||||||
return &TimedWorkerQueue{
|
return &TimedWorkerQueue{
|
||||||
workers: make(map[string]*TimedWorker),
|
workers: make(map[string]*TimedWorker),
|
||||||
workFunc: f,
|
workFunc: f,
|
||||||
@ -98,9 +100,9 @@ func CreateWorkerQueue(f func(ctx context.Context, args *WorkArgs) error) *Timed
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *TimedWorkerQueue) getWrappedWorkerFunc(key string) func(ctx context.Context, args *WorkArgs) error {
|
func (q *TimedWorkerQueue) getWrappedWorkerFunc(key string) func(ctx context.Context, fireAt time.Time, args *WorkArgs) error {
|
||||||
return func(ctx context.Context, args *WorkArgs) error {
|
return func(ctx context.Context, fireAt time.Time, args *WorkArgs) error {
|
||||||
err := q.workFunc(ctx, args)
|
err := q.workFunc(ctx, fireAt, args)
|
||||||
q.Lock()
|
q.Lock()
|
||||||
defer q.Unlock()
|
defer q.Unlock()
|
||||||
if err == nil {
|
if err == nil {
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package scheduler
|
package tainteviction
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -31,7 +31,7 @@ func TestExecute(t *testing.T) {
|
|||||||
testVal := int32(0)
|
testVal := int32(0)
|
||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
wg.Add(5)
|
wg.Add(5)
|
||||||
queue := CreateWorkerQueue(func(ctx context.Context, args *WorkArgs) error {
|
queue := CreateWorkerQueue(func(ctx context.Context, fireAt time.Time, args *WorkArgs) error {
|
||||||
atomic.AddInt32(&testVal, 1)
|
atomic.AddInt32(&testVal, 1)
|
||||||
wg.Done()
|
wg.Done()
|
||||||
return nil
|
return nil
|
||||||
@ -59,7 +59,7 @@ func TestExecuteDelayed(t *testing.T) {
|
|||||||
testVal := int32(0)
|
testVal := int32(0)
|
||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
wg.Add(5)
|
wg.Add(5)
|
||||||
queue := CreateWorkerQueue(func(ctx context.Context, args *WorkArgs) error {
|
queue := CreateWorkerQueue(func(ctx context.Context, fireAt time.Time, args *WorkArgs) error {
|
||||||
atomic.AddInt32(&testVal, 1)
|
atomic.AddInt32(&testVal, 1)
|
||||||
wg.Done()
|
wg.Done()
|
||||||
return nil
|
return nil
|
||||||
@ -90,7 +90,7 @@ func TestCancel(t *testing.T) {
|
|||||||
testVal := int32(0)
|
testVal := int32(0)
|
||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
wg.Add(3)
|
wg.Add(3)
|
||||||
queue := CreateWorkerQueue(func(ctx context.Context, args *WorkArgs) error {
|
queue := CreateWorkerQueue(func(ctx context.Context, fireAt time.Time, args *WorkArgs) error {
|
||||||
atomic.AddInt32(&testVal, 1)
|
atomic.AddInt32(&testVal, 1)
|
||||||
wg.Done()
|
wg.Done()
|
||||||
return nil
|
return nil
|
||||||
@ -124,7 +124,7 @@ func TestCancelAndReadd(t *testing.T) {
|
|||||||
testVal := int32(0)
|
testVal := int32(0)
|
||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
wg.Add(4)
|
wg.Add(4)
|
||||||
queue := CreateWorkerQueue(func(ctx context.Context, args *WorkArgs) error {
|
queue := CreateWorkerQueue(func(ctx context.Context, fireAt time.Time, args *WorkArgs) error {
|
||||||
atomic.AddInt32(&testVal, 1)
|
atomic.AddInt32(&testVal, 1)
|
||||||
wg.Done()
|
wg.Done()
|
||||||
return nil
|
return nil
|
@ -729,6 +729,13 @@ const (
|
|||||||
// https://github.com/kubernetes/kubernetes/issues/111516
|
// https://github.com/kubernetes/kubernetes/issues/111516
|
||||||
SecurityContextDeny featuregate.Feature = "SecurityContextDeny"
|
SecurityContextDeny featuregate.Feature = "SecurityContextDeny"
|
||||||
|
|
||||||
|
// owner: @atosatto @yuanchen8911
|
||||||
|
// kep: http://kep.k8s.io/3902
|
||||||
|
// beta: v1.29
|
||||||
|
//
|
||||||
|
// Decouples Taint Eviction Controller, performing taint-based Pod eviction, from Node Lifecycle Controller.
|
||||||
|
SeparateTaintEvictionController featuregate.Feature = "SeparateTaintEvictionController"
|
||||||
|
|
||||||
// owner: @xuzhenglun
|
// owner: @xuzhenglun
|
||||||
// kep: http://kep.k8s.io/3682
|
// kep: http://kep.k8s.io/3682
|
||||||
// alpha: v1.27
|
// alpha: v1.27
|
||||||
@ -1093,6 +1100,8 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
|
|||||||
|
|
||||||
SecurityContextDeny: {Default: false, PreRelease: featuregate.Alpha},
|
SecurityContextDeny: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
|
||||||
|
SeparateTaintEvictionController: {Default: true, PreRelease: featuregate.Beta},
|
||||||
|
|
||||||
ServiceNodePortStaticSubrange: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // GA in 1.29; remove in 1.31
|
ServiceNodePortStaticSubrange: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // GA in 1.29; remove in 1.31
|
||||||
|
|
||||||
SidecarContainers: {Default: false, PreRelease: featuregate.Alpha},
|
SidecarContainers: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
@ -32,8 +32,10 @@ import (
|
|||||||
restclient "k8s.io/client-go/rest"
|
restclient "k8s.io/client-go/rest"
|
||||||
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
|
"k8s.io/kubernetes/cmd/kube-controller-manager/names"
|
||||||
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
|
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
|
||||||
"k8s.io/kubernetes/pkg/controller/nodelifecycle"
|
"k8s.io/kubernetes/pkg/controller/nodelifecycle"
|
||||||
|
"k8s.io/kubernetes/pkg/controller/tainteviction"
|
||||||
"k8s.io/kubernetes/pkg/features"
|
"k8s.io/kubernetes/pkg/features"
|
||||||
"k8s.io/kubernetes/plugin/pkg/admission/defaulttolerationseconds"
|
"k8s.io/kubernetes/plugin/pkg/admission/defaulttolerationseconds"
|
||||||
"k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction"
|
"k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction"
|
||||||
@ -49,13 +51,34 @@ func TestEvictionForNoExecuteTaintAddedByUser(t *testing.T) {
|
|||||||
nodeIndex := 1 // the exact node doesn't matter, pick one
|
nodeIndex := 1 // the exact node doesn't matter, pick one
|
||||||
|
|
||||||
tests := map[string]struct {
|
tests := map[string]struct {
|
||||||
enablePodDisruptionConditions bool
|
enablePodDisruptionConditions bool
|
||||||
|
enableSeparateTaintEvictionController bool
|
||||||
|
startStandaloneTaintEvictionController bool
|
||||||
|
wantPodEvicted bool
|
||||||
}{
|
}{
|
||||||
"Test eviciton for NoExecute taint added by user; pod condition added when PodDisruptionConditions enabled": {
|
"Test eviction for NoExecute taint added by user; pod condition added when PodDisruptionConditions enabled; separate taint eviction controller disabled": {
|
||||||
enablePodDisruptionConditions: true,
|
enablePodDisruptionConditions: true,
|
||||||
|
enableSeparateTaintEvictionController: false,
|
||||||
|
startStandaloneTaintEvictionController: false,
|
||||||
|
wantPodEvicted: true,
|
||||||
},
|
},
|
||||||
"Test eviciton for NoExecute taint added by user; no pod condition added when PodDisruptionConditions disabled": {
|
"Test eviction for NoExecute taint added by user; no pod condition added when PodDisruptionConditions disabled; separate taint eviction controller disabled": {
|
||||||
enablePodDisruptionConditions: false,
|
enablePodDisruptionConditions: false,
|
||||||
|
enableSeparateTaintEvictionController: false,
|
||||||
|
startStandaloneTaintEvictionController: false,
|
||||||
|
wantPodEvicted: true,
|
||||||
|
},
|
||||||
|
"Test eviction for NoExecute taint added by user; separate taint eviction controller enabled but not started": {
|
||||||
|
enablePodDisruptionConditions: false,
|
||||||
|
enableSeparateTaintEvictionController: true,
|
||||||
|
startStandaloneTaintEvictionController: false,
|
||||||
|
wantPodEvicted: false,
|
||||||
|
},
|
||||||
|
"Test eviction for NoExecute taint added by user; separate taint eviction controller enabled and started": {
|
||||||
|
enablePodDisruptionConditions: false,
|
||||||
|
enableSeparateTaintEvictionController: true,
|
||||||
|
startStandaloneTaintEvictionController: true,
|
||||||
|
wantPodEvicted: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,6 +125,7 @@ func TestEvictionForNoExecuteTaintAddedByUser(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.PodDisruptionConditions, test.enablePodDisruptionConditions)()
|
defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.PodDisruptionConditions, test.enablePodDisruptionConditions)()
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.SeparateTaintEvictionController, test.enableSeparateTaintEvictionController)()
|
||||||
testCtx := testutils.InitTestAPIServer(t, "taint-no-execute", nil)
|
testCtx := testutils.InitTestAPIServer(t, "taint-no-execute", nil)
|
||||||
cs := testCtx.ClientSet
|
cs := testCtx.ClientSet
|
||||||
|
|
||||||
@ -138,6 +162,18 @@ func TestEvictionForNoExecuteTaintAddedByUser(t *testing.T) {
|
|||||||
// Run all controllers
|
// Run all controllers
|
||||||
go nc.Run(testCtx.Ctx)
|
go nc.Run(testCtx.Ctx)
|
||||||
|
|
||||||
|
// Start TaintManager
|
||||||
|
if test.startStandaloneTaintEvictionController {
|
||||||
|
tm, _ := tainteviction.New(
|
||||||
|
testCtx.Ctx,
|
||||||
|
testCtx.ClientSet,
|
||||||
|
externalInformers.Core().V1().Pods(),
|
||||||
|
externalInformers.Core().V1().Nodes(),
|
||||||
|
names.TaintEvictionController,
|
||||||
|
)
|
||||||
|
go tm.Run(testCtx.Ctx)
|
||||||
|
}
|
||||||
|
|
||||||
for index := range nodes {
|
for index := range nodes {
|
||||||
nodes[index], err = cs.CoreV1().Nodes().Create(testCtx.Ctx, nodes[index], metav1.CreateOptions{})
|
nodes[index], err = cs.CoreV1().Nodes().Create(testCtx.Ctx, nodes[index], metav1.CreateOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -155,9 +191,12 @@ func TestEvictionForNoExecuteTaintAddedByUser(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = wait.PollUntilContextTimeout(testCtx.Ctx, time.Second, time.Second*20, true, testutils.PodIsGettingEvicted(cs, testPod.Namespace, testPod.Name))
|
err = wait.PollUntilContextTimeout(testCtx.Ctx, time.Second, time.Second*20, true, testutils.PodIsGettingEvicted(cs, testPod.Namespace, testPod.Name))
|
||||||
if err != nil {
|
if err != nil && test.wantPodEvicted {
|
||||||
t.Fatalf("Error %q in test %q when waiting for terminating pod: %q", err, name, klog.KObj(testPod))
|
t.Fatalf("Test Failed: error %v while waiting for pod %q to be evicted", err, klog.KObj(testPod))
|
||||||
|
} else if !wait.Interrupted(err) && !test.wantPodEvicted {
|
||||||
|
t.Fatalf("Test Failed: unexpected eviction of pod %q", klog.KObj(testPod))
|
||||||
}
|
}
|
||||||
|
|
||||||
testPod, err = cs.CoreV1().Pods(testCtx.NS.Name).Get(testCtx.Ctx, testPod.Name, metav1.GetOptions{})
|
testPod, err = cs.CoreV1().Pods(testCtx.NS.Name).Get(testCtx.Ctx, testPod.Name, metav1.GetOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Test Failed: error: %q, while getting updated pod", err)
|
t.Fatalf("Test Failed: error: %q, while getting updated pod", err)
|
||||||
@ -196,23 +235,34 @@ func TestTaintBasedEvictions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
nodeTaints []v1.Taint
|
nodeTaints []v1.Taint
|
||||||
nodeConditions []v1.NodeCondition
|
nodeConditions []v1.NodeCondition
|
||||||
pod *v1.Pod
|
pod *v1.Pod
|
||||||
tolerationSeconds int64
|
tolerationSeconds int64
|
||||||
expectedWaitForPodCondition string
|
expectedWaitForPodCondition string
|
||||||
|
enableSeparateTaintEvictionController bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Taint based evictions for NodeNotReady and 200 tolerationseconds",
|
name: "Taint based evictions for NodeNotReady and 200 tolerationseconds; separate taint eviction controller disabled",
|
||||||
nodeTaints: []v1.Taint{{Key: v1.TaintNodeNotReady, Effect: v1.TaintEffectNoExecute}},
|
nodeTaints: []v1.Taint{{Key: v1.TaintNodeNotReady, Effect: v1.TaintEffectNoExecute}},
|
||||||
nodeConditions: []v1.NodeCondition{{Type: v1.NodeReady, Status: v1.ConditionFalse}},
|
nodeConditions: []v1.NodeCondition{{Type: v1.NodeReady, Status: v1.ConditionFalse}},
|
||||||
pod: testPod.DeepCopy(),
|
pod: testPod.DeepCopy(),
|
||||||
tolerationSeconds: 200,
|
tolerationSeconds: 200,
|
||||||
expectedWaitForPodCondition: "updated with tolerationSeconds of 200",
|
expectedWaitForPodCondition: "updated with tolerationSeconds of 200",
|
||||||
|
enableSeparateTaintEvictionController: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Taint based evictions for NodeNotReady with no pod tolerations",
|
name: "Taint based evictions for NodeNotReady and 200 tolerationseconds; separate taint eviction controller enabled",
|
||||||
|
nodeTaints: []v1.Taint{{Key: v1.TaintNodeNotReady, Effect: v1.TaintEffectNoExecute}},
|
||||||
|
nodeConditions: []v1.NodeCondition{{Type: v1.NodeReady, Status: v1.ConditionFalse}},
|
||||||
|
pod: testPod.DeepCopy(),
|
||||||
|
tolerationSeconds: 200,
|
||||||
|
expectedWaitForPodCondition: "updated with tolerationSeconds of 200",
|
||||||
|
enableSeparateTaintEvictionController: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Taint based evictions for NodeNotReady with no pod tolerations; separate taint eviction controller disabled",
|
||||||
nodeTaints: []v1.Taint{{Key: v1.TaintNodeNotReady, Effect: v1.TaintEffectNoExecute}},
|
nodeTaints: []v1.Taint{{Key: v1.TaintNodeNotReady, Effect: v1.TaintEffectNoExecute}},
|
||||||
nodeConditions: []v1.NodeCondition{{Type: v1.NodeReady, Status: v1.ConditionFalse}},
|
nodeConditions: []v1.NodeCondition{{Type: v1.NodeReady, Status: v1.ConditionFalse}},
|
||||||
pod: &v1.Pod{
|
pod: &v1.Pod{
|
||||||
@ -223,21 +273,55 @@ func TestTaintBasedEvictions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
tolerationSeconds: 300,
|
tolerationSeconds: 300,
|
||||||
expectedWaitForPodCondition: "updated with tolerationSeconds=300",
|
expectedWaitForPodCondition: "updated with tolerationSeconds=300",
|
||||||
|
enableSeparateTaintEvictionController: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Taint based evictions for NodeNotReady and 0 tolerationseconds",
|
name: "Taint based evictions for NodeNotReady with no pod tolerations; separate taint eviction controller enabled",
|
||||||
nodeTaints: []v1.Taint{{Key: v1.TaintNodeNotReady, Effect: v1.TaintEffectNoExecute}},
|
nodeTaints: []v1.Taint{{Key: v1.TaintNodeNotReady, Effect: v1.TaintEffectNoExecute}},
|
||||||
nodeConditions: []v1.NodeCondition{{Type: v1.NodeReady, Status: v1.ConditionFalse}},
|
nodeConditions: []v1.NodeCondition{{Type: v1.NodeReady, Status: v1.ConditionFalse}},
|
||||||
pod: testPod.DeepCopy(),
|
pod: &v1.Pod{
|
||||||
tolerationSeconds: 0,
|
ObjectMeta: metav1.ObjectMeta{Name: "testpod1"},
|
||||||
expectedWaitForPodCondition: "terminating",
|
Spec: v1.PodSpec{
|
||||||
|
Containers: []v1.Container{
|
||||||
|
{Name: "container", Image: imageutils.GetPauseImageName()},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tolerationSeconds: 300,
|
||||||
|
expectedWaitForPodCondition: "updated with tolerationSeconds=300",
|
||||||
|
enableSeparateTaintEvictionController: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Taint based evictions for NodeUnreachable",
|
name: "Taint based evictions for NodeNotReady and 0 tolerationseconds; separate taint eviction controller disabled",
|
||||||
nodeTaints: []v1.Taint{{Key: v1.TaintNodeUnreachable, Effect: v1.TaintEffectNoExecute}},
|
nodeTaints: []v1.Taint{{Key: v1.TaintNodeNotReady, Effect: v1.TaintEffectNoExecute}},
|
||||||
nodeConditions: []v1.NodeCondition{{Type: v1.NodeReady, Status: v1.ConditionUnknown}},
|
nodeConditions: []v1.NodeCondition{{Type: v1.NodeReady, Status: v1.ConditionFalse}},
|
||||||
|
pod: testPod.DeepCopy(),
|
||||||
|
tolerationSeconds: 0,
|
||||||
|
expectedWaitForPodCondition: "terminating",
|
||||||
|
enableSeparateTaintEvictionController: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Taint based evictions for NodeNotReady and 0 tolerationseconds; separate taint eviction controller enabled",
|
||||||
|
nodeTaints: []v1.Taint{{Key: v1.TaintNodeNotReady, Effect: v1.TaintEffectNoExecute}},
|
||||||
|
nodeConditions: []v1.NodeCondition{{Type: v1.NodeReady, Status: v1.ConditionFalse}},
|
||||||
|
pod: testPod.DeepCopy(),
|
||||||
|
tolerationSeconds: 0,
|
||||||
|
expectedWaitForPodCondition: "terminating",
|
||||||
|
enableSeparateTaintEvictionController: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Taint based evictions for NodeUnreachable; separate taint eviction controller disabled",
|
||||||
|
nodeTaints: []v1.Taint{{Key: v1.TaintNodeUnreachable, Effect: v1.TaintEffectNoExecute}},
|
||||||
|
nodeConditions: []v1.NodeCondition{{Type: v1.NodeReady, Status: v1.ConditionUnknown}},
|
||||||
|
enableSeparateTaintEvictionController: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Taint based evictions for NodeUnreachable; separate taint eviction controller enabled",
|
||||||
|
nodeTaints: []v1.Taint{{Key: v1.TaintNodeUnreachable, Effect: v1.TaintEffectNoExecute}},
|
||||||
|
nodeConditions: []v1.NodeCondition{{Type: v1.NodeReady, Status: v1.ConditionUnknown}},
|
||||||
|
enableSeparateTaintEvictionController: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,6 +333,8 @@ func TestTaintBasedEvictions(t *testing.T) {
|
|||||||
)
|
)
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.name, func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.SeparateTaintEvictionController, test.enableSeparateTaintEvictionController)()
|
||||||
|
|
||||||
testCtx := testutils.InitTestAPIServer(t, "taint-based-evictions", admission)
|
testCtx := testutils.InitTestAPIServer(t, "taint-based-evictions", admission)
|
||||||
|
|
||||||
// Build clientset and informers for controllers.
|
// Build clientset and informers for controllers.
|
||||||
@ -288,6 +374,18 @@ func TestTaintBasedEvictions(t *testing.T) {
|
|||||||
// Run the controller
|
// Run the controller
|
||||||
go nc.Run(testCtx.Ctx)
|
go nc.Run(testCtx.Ctx)
|
||||||
|
|
||||||
|
// Start TaintEvictionController
|
||||||
|
if test.enableSeparateTaintEvictionController {
|
||||||
|
tm, _ := tainteviction.New(
|
||||||
|
testCtx.Ctx,
|
||||||
|
testCtx.ClientSet,
|
||||||
|
externalInformers.Core().V1().Pods(),
|
||||||
|
externalInformers.Core().V1().Nodes(),
|
||||||
|
names.TaintEvictionController,
|
||||||
|
)
|
||||||
|
go tm.Run(testCtx.Ctx)
|
||||||
|
}
|
||||||
|
|
||||||
nodeRes := v1.ResourceList{
|
nodeRes := v1.ResourceList{
|
||||||
v1.ResourceCPU: resource.MustParse("4000m"),
|
v1.ResourceCPU: resource.MustParse("4000m"),
|
||||||
v1.ResourceMemory: resource.MustParse("16Gi"),
|
v1.ResourceMemory: resource.MustParse("16Gi"),
|
||||||
|
Loading…
Reference in New Issue
Block a user