1
16
17 package app
18
19 import (
20 "context"
21 "regexp"
22 "strings"
23 "testing"
24
25 "github.com/google/go-cmp/cmp"
26
27 "k8s.io/apimachinery/pkg/util/sets"
28 utilfeature "k8s.io/apiserver/pkg/util/feature"
29 cpnames "k8s.io/cloud-provider/names"
30 "k8s.io/component-base/featuregate"
31 featuregatetesting "k8s.io/component-base/featuregate/testing"
32 controllermanagercontroller "k8s.io/controller-manager/controller"
33 "k8s.io/klog/v2/ktesting"
34 "k8s.io/kubernetes/cmd/kube-controller-manager/names"
35 "k8s.io/kubernetes/pkg/features"
36 )
37
38 func TestControllerNamesConsistency(t *testing.T) {
39 controllerNameRegexp := regexp.MustCompile("^[a-z]([-a-z]*[a-z])?$")
40
41 for _, name := range KnownControllers() {
42 if !controllerNameRegexp.MatchString(name) {
43 t.Errorf("name consistency check failed: controller %q must consist of lower case alphabetic characters or '-', and must start and end with an alphabetic character", name)
44 }
45 if !strings.HasSuffix(name, "-controller") {
46 t.Errorf("name consistency check failed: controller %q must have \"-controller\" suffix", name)
47 }
48 }
49 }
50
51 func TestControllerNamesDeclaration(t *testing.T) {
52 declaredControllers := sets.NewString(
53 names.ServiceAccountTokenController,
54 names.EndpointsController,
55 names.EndpointSliceController,
56 names.EndpointSliceMirroringController,
57 names.ReplicationControllerController,
58 names.PodGarbageCollectorController,
59 names.ResourceQuotaController,
60 names.NamespaceController,
61 names.ServiceAccountController,
62 names.GarbageCollectorController,
63 names.DaemonSetController,
64 names.JobController,
65 names.DeploymentController,
66 names.ReplicaSetController,
67 names.HorizontalPodAutoscalerController,
68 names.DisruptionController,
69 names.StatefulSetController,
70 names.CronJobController,
71 names.CertificateSigningRequestSigningController,
72 names.CertificateSigningRequestApprovingController,
73 names.CertificateSigningRequestCleanerController,
74 names.TTLController,
75 names.BootstrapSignerController,
76 names.TokenCleanerController,
77 names.NodeIpamController,
78 names.NodeLifecycleController,
79 names.TaintEvictionController,
80 cpnames.ServiceLBController,
81 cpnames.NodeRouteController,
82 cpnames.CloudNodeLifecycleController,
83 names.PersistentVolumeBinderController,
84 names.PersistentVolumeAttachDetachController,
85 names.PersistentVolumeExpanderController,
86 names.ClusterRoleAggregationController,
87 names.PersistentVolumeClaimProtectionController,
88 names.PersistentVolumeProtectionController,
89 names.TTLAfterFinishedController,
90 names.RootCACertificatePublisherController,
91 names.EphemeralVolumeController,
92 names.StorageVersionGarbageCollectorController,
93 names.ResourceClaimController,
94 names.LegacyServiceAccountTokenCleanerController,
95 names.ValidatingAdmissionPolicyStatusController,
96 names.ServiceCIDRController,
97 names.StorageVersionMigratorController,
98 )
99
100 for _, name := range KnownControllers() {
101 if !declaredControllers.Has(name) {
102 t.Errorf("name declaration check failed: controller name %q should be declared in \"controller_names.go\" and added to this test", name)
103 }
104 }
105 }
106
107 func TestNewControllerDescriptorsShouldNotPanic(t *testing.T) {
108 NewControllerDescriptors()
109 }
110
111 func TestNewControllerDescriptorsAlwaysReturnsDescriptorsForAllControllers(t *testing.T) {
112 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, "AllAlpha", false)()
113 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, "AllBeta", false)()
114
115 controllersWithoutFeatureGates := KnownControllers()
116
117 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, "AllAlpha", true)()
118 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, "AllBeta", true)()
119
120 controllersWithFeatureGates := KnownControllers()
121
122 if diff := cmp.Diff(controllersWithoutFeatureGates, controllersWithFeatureGates); diff != "" {
123 t.Errorf("unexpected controllers after enabling feature gates, NewControllerDescriptors should always return all controller descriptors. Controllers should define required feature gates in ControllerDescriptor.requiredFeatureGates. Diff of returned controllers:\n%s", diff)
124 }
125 }
126
127 func TestFeatureGatedControllersShouldNotDefineAliases(t *testing.T) {
128 featureGateRegex := regexp.MustCompile("^([a-zA-Z0-9]+)")
129
130 alphaFeatures := sets.NewString()
131 for _, featureText := range utilfeature.DefaultFeatureGate.KnownFeatures() {
132
133 feature := featureGateRegex.FindString(featureText)
134 if strings.Contains(featureText, string(featuregate.Alpha)) && feature != "AllAlpha" {
135 alphaFeatures.Insert(feature)
136 }
137
138 }
139
140 for name, controller := range NewControllerDescriptors() {
141 if len(controller.GetAliases()) == 0 {
142 continue
143 }
144
145 requiredFeatureGates := controller.GetRequiredFeatureGates()
146 if len(requiredFeatureGates) == 0 {
147 continue
148 }
149
150
151 if name == names.ResourceClaimController {
152 continue
153 }
154
155 areAllRequiredFeaturesAlpha := true
156 for _, feature := range requiredFeatureGates {
157 if !alphaFeatures.Has(string(feature)) {
158 areAllRequiredFeaturesAlpha = false
159 break
160 }
161 }
162
163 if areAllRequiredFeaturesAlpha {
164 t.Errorf("alias check failed: controller name %q should not be aliased as it is still guarded by alpha feature gates (%v) and thus should have only a canonical name", name, requiredFeatureGates)
165 }
166 }
167 }
168
169
170
171 func TestTaintEvictionControllerGating(t *testing.T) {
172 tests := []struct {
173 name string
174 enableFeatureGate bool
175 expectInitFuncCall bool
176 }{
177 {
178 name: "standalone taint-eviction-controller should run when SeparateTaintEvictionController feature gate is enabled",
179 enableFeatureGate: true,
180 expectInitFuncCall: true,
181 },
182 {
183 name: "standalone taint-eviction-controller should not run when SeparateTaintEvictionController feature gate is not enabled",
184 enableFeatureGate: false,
185 expectInitFuncCall: false,
186 },
187 }
188
189 for _, test := range tests {
190 t.Run(test.name, func(t *testing.T) {
191 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.SeparateTaintEvictionController, test.enableFeatureGate)()
192 _, ctx := ktesting.NewTestContext(t)
193 ctx, cancel := context.WithCancel(ctx)
194 defer cancel()
195
196 controllerCtx := ControllerContext{}
197 controllerCtx.ComponentConfig.Generic.Controllers = []string{names.TaintEvictionController}
198
199 initFuncCalled := false
200
201 taintEvictionControllerDescriptor := NewControllerDescriptors()[names.TaintEvictionController]
202 taintEvictionControllerDescriptor.initFunc = func(ctx context.Context, controllerContext ControllerContext, controllerName string) (controller controllermanagercontroller.Interface, enabled bool, err error) {
203 initFuncCalled = true
204 return nil, true, nil
205 }
206
207 healthCheck, err := StartController(ctx, controllerCtx, taintEvictionControllerDescriptor, nil)
208 if err != nil {
209 t.Errorf("starting a TaintEvictionController controller should not return an error")
210 }
211 if test.expectInitFuncCall != initFuncCalled {
212 t.Errorf("TaintEvictionController init call check failed: expected=%v, got=%v", test.expectInitFuncCall, initFuncCalled)
213 }
214 hasHealthCheck := healthCheck != nil
215 expectHealthCheck := test.expectInitFuncCall
216 if expectHealthCheck != hasHealthCheck {
217 t.Errorf("TaintEvictionController healthCheck check failed: expected=%v, got=%v", expectHealthCheck, hasHealthCheck)
218 }
219 })
220 }
221 }
222
View as plain text