Merge pull request #119208 from atosatto/separate-taint-manager

Decouple TaintManager from NodeLifeCycleController (KEP-3902)
This commit is contained in:
Kubernetes Prow Robot 2023-10-30 21:11:33 +01:00 committed by GitHub
commit e4212878dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 556 additions and 224 deletions

View File

@ -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 {

View File

@ -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")
}
}

View File

@ -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,

View File

@ -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"

View File

@ -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

View 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

View 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

View 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)
})
}

View File

@ -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
} }

View File

@ -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 {

View File

@ -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 {

View File

@ -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

View File

@ -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},

View File

@ -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"),