Repository navigation
Conversation
The secretEnv check is the only error branch in Reconcile that leaves status.state alone, so the CR keeps whatever it last reported while the Error condition says the reconcile failed. nvDriverUpdater copies the old state through and its empty-state fallback cannot catch it, so a driver that was ready goes on printing ready. It also returned no error and no requeue, and nothing watches Secrets, so creating the Secret afterwards raised no event and the driver stayed frozen until an unrelated one arrived. Signed-off-by: Dmitry Voropaev <dy.voropaev@gmail.com>
📝 Walkthrough
Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to A driver whose Secret is missing is now marked NotReady and retried every five seconds. No merge-blocking risk was found; the test could assert more precisely.
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
- Configuration used: Repository: NVIDIA/gpu-operator/.coderabbit.yaml
- Review profile: QUIET
- Plan: Enterprise
- Run ID:
840804d8-a9da-446f-8db7-6d197f12adbb
📒 Files selected for processing (2)
controllers/nvidiadriver_controller.gocontrollers/nvidiadriver_controller_test.go
Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 10 remain after this review.
| require.Equal(t, nvidiav1alpha1.NotReady, updater.LastErrorState) | ||
| require.Equal(t, conditions.ReconcileFailed, updater.LastErrorReason) | ||
| require.Contains(t, updater.LastErrorMessage, "driver-env-secret") | ||
| // Nothing watches Secrets, so without a requeue the driver stays stuck | ||
| // after the secret is created. | ||
| require.NotZero(t, res.RequeueAfter) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Assert the observable status and the five-second retry.
LastErrorState only records the in-memory value passed to FakeConditionUpdater. The test can pass even if the driver remains ready in persisted status. A nonzero RequeueAfter can also pass with the wrong retry delay. Add a check that reads the updated driver status through the client, using an updater that persists conditions, and assert RequeueAfter == 5*time.Second.
As per path instructions, “Flag assertions that would still pass if the behavior under test were broken.”
Source: Path instructions
|
/ok to test 10c8db4 |
kvalliyurnatt
left a comment
There was a problem hiding this comment.
Thanks for your contribution @v0ropaev ! LGTM.
| updater := &FakeConditionUpdater{} | ||
| reconciler := &NVIDIADriverReconciler{ | ||
| Client: client, | ||
| Scheme: scheme, | ||
| Namespace: "gpu-operator", | ||
| conditionUpdater: updater, | ||
| nodeSelectorValidator: &FakeNodeSelectorValidator{}, | ||
| } | ||
|
|
||
| res, err := reconciler.Reconcile(context.Background(), ctrl.Request{ | ||
| NamespacedName: types.NamespacedName{Name: driver.Name}, | ||
| }) | ||
|
|
||
| require.NoError(t, err) | ||
| require.Equal(t, nvidiav1alpha1.NotReady, updater.LastErrorState) | ||
| require.Equal(t, conditions.ReconcileFailed, updater.LastErrorReason) | ||
| require.Contains(t, updater.LastErrorMessage, "driver-env-secret") |
Nothing watched Secrets, so deleting the secret of a Ready driver left it Ready until some other event came. Watch Secrets in the operator namespace and enqueue the drivers whose spec.secretEnv names them. Signed-off-by: Dmitry Voropaev <dy.voropaev@gmail.com>
| } | ||
| return reconcile.Result{}, nil | ||
| // The Secret watch in SetupWithManager brings us back once the secret | ||
| // is created; the requeue is a fallback in case that event is missed. |
There was a problem hiding this comment.
@v0ropaev with the watch added do we really need this fallback ?
There was a problem hiding this comment.
Agreed, dropped it in 66cc126. The informer relists after watch errors, so a create can't really get lost, and polling every 5s for a secret that may never show up isn't worth it. It's back to returning an empty result like before.
| return r.enqueueNVIDIADriversForSecret(ctx, secret) | ||
| } | ||
|
|
||
| err = c.Watch( |
There was a problem hiding this comment.
I think this will not work and will break the operator
-
The manager cache uses
DefaultNamespaceswith two namespaces: the operator namespace andopenshift(seecmd/gpu-operator/main.golines 130-136). For a namespaced type,source.KindcallsmultiNamespaceCache.GetInformer, which starts one informer for each cache namespace. This watch therefore also lists and watches Secrets in theopenshiftnamespace. -
The operator has no RBAC for Secrets in that namespace:
- Helm grants
secretsonly in the namespacedRolein the release namespace. TheClusterRoledoes not includesecrets. - The OLM CSV lists
secretsonly underpermissions, not underclusterPermissions.
The controller does not need Secrets from openshift. Reconcile reads only r.Namespace, and enqueueNVIDIADriversForSecret drops other namespaces. To limit Secrets to the operator namespace in main.go:
cacheOptions := cache.Options{
DefaultNamespaces: map[string]cache.Config{
operatorNamespace: {},
openshiftNamespace: {},
},
ByObject: map[client.Object]cache.ByObject{
&corev1.Secret{}: {
Namespaces: map[string]cache.Config{operatorNamespace: {}},
},
},
}
There was a problem hiding this comment.
Right, I missed that the cache also covers the openshift namespace. I reproduced it in envtest with a user that only has the namespaced Role for secrets, and the Secret informer never syncs, so the controller wouldn't start. 66cc126 adds a ByObject entry for Secret in main.go limited to the operator namespace, and with that it syncs fine. RBAC needs no change since the Role and the CSV permissions already grant list/watch on secrets there.
|
|
||
| err = c.Watch( | ||
| source.Kind(mgr.GetCache(), | ||
| &corev1.Secret{}, |
There was a problem hiding this comment.
I wonder if we should just cache metav1.PartialObjectMetadata here, we don't need anything beyond the name and namespace of the secret right ?
There was a problem hiding this comment.
Yes, name and namespace are all we need. The watch and the existence check in Reconcile both use PartialObjectMetadata with the Secret GVK now, so this controller no longer puts secret data in the cache. The ByObject entry still applies because the cache picks the delegate by GVK. The ClusterPolicy path (createSecretEnvReference) still does a full Get, it only needs the name too, so I can switch it the same way in a follow-up if you want.
The manager cache spans the operator and openshift namespaces, so the Secret watch started an informer in openshift as well. The operator has no access to Secrets there, that informer never syncs and the controller fails to start. Limit the Secret cache to the operator namespace with ByObject, which covers metadata-only informers too since the cache is picked by GVK. Reconcile only checks that the Secret exists, so watch and get it as PartialObjectMetadata and keep Secret data out of the cache. With the watch covering create and delete, the 5s requeue is not needed anymore. Signed-off-by: Dmitry Voropaev <dy.voropaev@gmail.com>
A missing Secret brings the driver back through the Secret watch, but a failed lookup of an existing one produces no Secret event, so the driver would stay NotReady. Return the error so the request is retried with backoff. Signed-off-by: Dmitry Voropaev <dy.voropaev@gmail.com>
kubectl get nvidiadriverkeeps printingreadyafter the Secret named byspec.secretEnvgoes missing, and the driver stops being reconciled until something unrelated wakes the controller.The branch at
controllers/nvidiadriver_controller.go:183-194:Two things are missing, and they compound.
The state is left stale. Every other error branch in
Reconcileassigns it first, at lines 98, 113, 126 (Disabled), 152, 166 and 176. This one does not, andnvDriverUpdater.SetConditionsErrorcannot make up for it:internal/conditions/nvidiadriver.go:104-107copiescr.Status.Stateacross and only falls back toNotReadywhen it is empty, under a comment that says whose job it is.So on a fresh install with the Secret absent from the start the fallback does the right thing, and the case that breaks is the one where the CR has already reconciled once:
helm upgrade --set driver.secretEnv=<name>before the Secret exists or into the wrong namespace, a rotation done as delete-then-recreate, a rename..status.stateis a printcolumn (api/nvidia/v1alpha1/nvidiadriver_types.go:522), so that is what the operator,kubectl waitand any CI gate reading it see.And nothing comes back for it.
reconcile.Result{}, nilasks for no requeue, andSetupWithManagerwatches NVIDIADriver, ClusterPolicy, GPUCluster and Node but not Secrets, so creating the Secret raises no event of its own. The driver stays frozen until a Node label changes, a ClusterPolicy or GPUCluster generation bumps, or the operator restarts.The ClusterPolicy controller gets the same user error right.
createSecretEnvReferenceatcontrollers/object_controls.go:3827returns an error, which reachesclusterpolicy_controller.go:182-189and both setsNotReadyand returns the error, so the request is requeued with backoff.The change
Set
instance.Status.State = nvidiav1alpha1.NotReadythe way the siblings do, and requeue after 5 seconds, the interval already used at line 222.timeis already imported.I put both halves in one commit because the stale status on its own is the less interesting symptom; what a user notices is a driver that never picks the Secret up. If you would rather have them separately, the state assignment is the uncontroversial one and I will split it out.
The requeue alone does not cover a Secret deleted after a successful reconcile (pointed out in review), since nothing schedules a run then. So
SetupWithManagernow also watches Secrets in the operator namespace and enqueues the NVIDIADrivers whosespec.secretEnvnames the Secret, on create, update and delete. The manager cache is already limited to the operator namespace and the reconciler already reads that Secret through it, and both the kustomize and the Helm roles grantwatchon secrets, so this adds no new informer or RBAC. The 5 second requeue stays as a fallback.Test
TestEnqueueNVIDIADriversForSecretchecks that a Secret event (with the Secret absent from the client, as on delete) enqueues only the driver that references it, and nothing for a Secret of the same name in another namespace.TestReconcileMissingSecretEnvSetsNotReadyState, a fourth sibling to the three status tests already incontrollers/nvidiadriver_controller_test.go, seeded withStatus.State: Readythe wayTestReconcileConflictSetsNotReadyStateis. Onmainit fails on the state:go build ./...,go test ./controllers/(all pass) andgolangci-lint run ./controllers/...(0 issues).go test ./...fails only intests/e2eTestE2E, which needs a live cluster and fails the same way on a cleanmain.I have no GPU cluster here, so everything above is from reading the code plus the fake-client test.