@@ -41,8 +41,8 @@ import (
4141 logf "sigs.k8s.io/controller-runtime/pkg/log"
4242 "sigs.k8s.io/controller-runtime/pkg/log/zap"
4343 "sigs.k8s.io/controller-runtime/pkg/manager"
44- crewv1 "sigs.k8s.io/controller-runtime/pkg/manager/integration/v1"
45- crewv2 "sigs.k8s.io/controller-runtime/pkg/manager/integration/v2"
44+ crewv1 "sigs.k8s.io/controller-runtime/pkg/manager/internal/ integration/api /v1"
45+ crewv2 "sigs.k8s.io/controller-runtime/pkg/manager/internal/ integration/api /v2"
4646 "sigs.k8s.io/controller-runtime/pkg/reconcile"
4747 "sigs.k8s.io/controller-runtime/pkg/webhook"
4848 "sigs.k8s.io/controller-runtime/pkg/webhook/conversion"
6767 {
6868 Name : crewv1 .GroupVersion .Version ,
6969 Served : true ,
70- // At creation v1 will be the storage version.
71- // During the test v2 will become the storage version .
70+ // v1 will be the storage version.
71+ // Reconciler and index will use v2 so we can validate the conversion webhook works .
7272 Storage : true ,
7373 Schema : & apiextensionsv1.CustomResourceValidation {
7474 OpenAPIV3Schema : & apiextensionsv1.JSONSchemaProps {
@@ -91,23 +91,22 @@ var (
9191 }
9292)
9393
94- var _ = Describe ("manger.Manager" , func () {
94+ var _ = Describe ("manger.Manager Start " , func () {
9595 // This test ensure the Manager starts without running into any deadlocks as it can be very tricky
9696 // to start health probes, webhooks, caches (including informers) and reconcilers in the right order.
9797 //
9898 // To verify this we set up a test environment in the following state:
9999 // * Ensure Informer sync requires a functioning conversion webhook (and thus readiness probe)
100100 // * Driver CRD is deployed with v1 as storage version
101101 // * A Driver CR is created and stored in the v1 version
102- // * The CRD is updated to make v2 the storage version
103- // * This ensures every Driver list call goes through conversion.
104102 // * Setup manager:
105103 // * Set up health probes
106- // * Set up a Driver reconciler to verify reconciliation works
107- // * Set up a conversion webhook which only works if readiness probe succeeds (just like a Kubernetes service)
104+ // * Set up a Driver v2 reconciler to verify reconciliation works
105+ // * Set up a conversion webhook which only works if readiness probe succeeds (just like via a Kubernetes service)
108106 // * Add an index on v2 Driver to ensure we start and wait for an informer during cache.Start (as part of manager.Start)
109107 // * Note: cache.Start would fail if the conversion webhook doesn't work (which in turn depends on the readiness probe)
110- Describe ("Start should start all components without deadlock" , func () {
108+ // * Note: Adding the index for v2 ensures the Driver list call during Informer sync goes through conversion.
109+ It ("should start all components without deadlock" , func () {
111110 ctx := ctrl .SetupSignalHandler ()
112111
113112 // Set up schema.
@@ -123,6 +122,7 @@ var _ = Describe("manger.Manager", func() {
123122 CRDs : []* apiextensionsv1.CustomResourceDefinition {driverCRD },
124123 },
125124 }
125+ // Note: The test env configures a conversion webhook on driverCRD during Start.
126126 cfg , err := env .Start ()
127127 Expect (err ).NotTo (HaveOccurred ())
128128 Expect (cfg ).NotTo (BeNil ())
@@ -139,12 +139,6 @@ var _ = Describe("manger.Manager", func() {
139139 driverV1 .SetNamespace (metav1 .NamespaceDefault )
140140 Expect (c .Create (ctx , driverV1 )).To (Succeed ())
141141
142- // Update driver CRD to make v2 the storage version.
143- driverCRDV2Storage := driverCRD .DeepCopy ()
144- driverCRDV2Storage .Spec .Versions [0 ].Storage = false
145- driverCRDV2Storage .Spec .Versions [0 ].Storage = true
146- Expect (c .Patch (ctx , driverCRDV2Storage , client .MergeFrom (driverCRD ))).To (Succeed ())
147-
148142 // Set up Manager.
149143 ctrl .SetLogger (zap .New ())
150144 mgr , err := manager .New (env .Config , manager.Options {
@@ -164,17 +158,18 @@ var _ = Describe("manger.Manager", func() {
164158 Expect (mgr .AddReadyzCheck ("webhook" , mgr .GetWebhookServer ().StartedChecker ())).To (Succeed ())
165159 Expect (mgr .AddHealthzCheck ("webhook" , mgr .GetWebhookServer ().StartedChecker ())).To (Succeed ())
166160
167- // Set up Driver reconciler.
161+ // Set up Driver reconciler (using v2) .
168162 driverReconciler := & DriverReconciler {
169163 Client : mgr .GetClient (),
170164 }
171- Expect (ctrl .NewControllerManagedBy (mgr ).For (& crewv1 .Driver {}).Complete (driverReconciler )).To (Succeed ())
165+ Expect (ctrl .NewControllerManagedBy (mgr ).For (& crewv2 .Driver {}).Complete (driverReconciler )).To (Succeed ())
172166
173167 // Set up a conversion webhook.
174168 conversionWebhook := createConversionWebhook (mgr )
175169 mgr .GetWebhookServer ().Register ("/convert" , conversionWebhook )
176170
177- // Add an index on v2 Driver.
171+ // Add an index on Driver (using v2).
172+ // Note: This triggers the creation of an Informer for Driver v2.
178173 Expect (mgr .GetCache ().IndexField (ctx , & crewv2.Driver {}, "name" , func (object client.Object ) []string {
179174 return []string {object .GetName ()}
180175 })).To (Succeed ())
@@ -183,11 +178,19 @@ var _ = Describe("manger.Manager", func() {
183178 ctx , cancel := context .WithCancel (ctx )
184179 go func () {
185180 defer GinkgoRecover ()
186- Expect (mgr .Start (ctx )).NotTo ( HaveOccurred ())
181+ Expect (mgr .Start (ctx )).To ( Succeed ())
187182 }()
188183
189184 // Verify manager.Start successfully started health probes, webhooks, caches (including informers) and reconcilers.
190- <- mgr .Elected ()
185+ // Notes:
186+ // * The cache will only start successfully if the informer for v2 Driver is synced.
187+ // * The informer for v2 Driver will only sync if a list on v2 Driver succeeds (which requires a working conversion webhook)
188+ select {
189+ case <- time .After (30 * time .Second ):
190+ // Don't wait forever if the manager doesn't come up.
191+ Fail ("Manager didn't start in time" )
192+ case <- mgr .Elected ():
193+ }
191194
192195 // Verify the reconciler reconciles.
193196 Eventually (func (g Gomega ) {
@@ -197,7 +200,8 @@ var _ = Describe("manger.Manager", func() {
197200 // Verify conversion webhook was called.
198201 Expect (atomic .LoadUint64 (& conversionWebhook .ConversionCount )).Should (BeNumerically (">" , 0 ))
199202
200- // Verify the conversion webhook works.
203+ // Verify the conversion webhook works by getting the Driver as v1 and v2.
204+ Expect (c .Get (ctx , client .ObjectKeyFromObject (driverV1 ), driverV1 )).To (Succeed ())
201205 driverV2 := & unstructured.Unstructured {}
202206 driverV2 .SetGroupVersionKind (crewv2 .GroupVersion .WithKind ("Driver" ))
203207 driverV2 .SetName ("driver1" )
@@ -234,6 +238,10 @@ func (r *DriverReconciler) Reconcile(ctx context.Context, req reconcile.Request)
234238 return reconcile.Result {}, nil
235239}
236240
241+ // ConversionWebhook is just a shim around the conversion handler from
242+ // the webhook package. We use it to simulate the behavior of a conversion
243+ // webhook in a real cluster, i.e. the conversion webhook only works after the
244+ // controller Pod is ready (the readiness probe is up).
237245type ConversionWebhook struct {
238246 httpClient http.Client
239247 conversionHandler http.Handler
0 commit comments