-
Notifications
You must be signed in to change notification settings - Fork 1.4k
🐛 ClusterResourceSet controller: fix reconciliation triggered by resource changes #4491
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
🐛 ClusterResourceSet controller: fix reconciliation triggered by resource changes #4491
Conversation
43e2eb0
to
2ff65de
Compare
Expect(testEnv.Create(ctx, newConfigmap)).To(Succeed()) | ||
defer func() { | ||
Expect(testEnv.Delete(ctx, newConfigmap)).To(Succeed()) | ||
}() | ||
|
||
cmKey := client.ObjectKey{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what the purpose of this Eventually Get was. It should just return instantly because the Create above is synchronous. Maybe it was a try to ensure that the ConfigMap exists in the local cache to fix some test flakiness?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it was a try to ensure that the ConfigMap exists in the local cache to fix some test flakiness?
This sounds about right 😅 All the controller runtime stuff uses cached clients normally so if testEnv
is also caching, this would be it waiting until the informer has received the create event and updated the cache. Looking at the next Eventually
, this basically allows us to know that the timeout is used solely for that reconcile.
Have you tried running this many times to see if you can get it to fail without this extra block?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not yet, I'll try. Although the 10s timeout is easily enough. So it's highly likely that there won't be any difference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ran it locally in a loop a few hundred times successfully. But I think we don't need the Get
. The Eventually
below tries for 20 seconds and locally it takes < 1s
. But I don't really care, if the consensus is that we should keep it, I'll just add it again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add it back, having a Get
to make sure the cache syncs is the most idiomatic way to deal with caches and controllers lagging behind a bit
// if the ConfigMap creation triggers a reconciliation. Otherwise a previous change | ||
// of another resource triggers the reconciliation and we cannot verify if we react | ||
// correctly to the ConfigMap creation | ||
time.Sleep(5 * time.Second) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has been added to ensure the test would consistently fail without the fix in the ClusterRersourceSet controller. If possible I would like to avoid adding a sleep. Are there any ways to detect the previous reconciliations are done? (I think the last one doesn't change anything anywhere, so I cannot really wait for something specific reconciliation)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Short of wrapping the reconciler to count reconciles (which I would recommend avoiding), I don't think there's really anything that can be done. You also need to account for other controllers and tests in the test suite that may also be creating events, so to get the queue to truly be empty will be very very difficult AFAIK.
If we wanted to be more certain that, we could potentially create a new test with a separate manager watching a different test namespace (I'm not sure this is actually possible here, this is more general advice), that would then be more isolated. In these scenarios it's easier to verify reaction to specific events occurring.
Out of interest, without this, were you seeing the test flaking at all?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not ideal but could think about something like adding an optional event recorder to the resourceToClusterResourceSet
signature that we only pass through in tests that would record the exact resource and operation triggering the reconciliation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about relying on Observed generation instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@JoelSpeed
The problem is that without the timeout the test was not reliably able to detect the broken code. So this means when I drop the timeout the test has a certain probability that it doesn't test the reconcile of the create ConfigMap but one of the previous one either Cluster or ClusterResourceSet creation. So this only makes the test "greener" but just because we're testing the wrong thing. So the test was flaky because it couldn't reliably detect the broken code, with the timeout it can. Usually tests are flaky because the bug in the code occur only under certain conditions, in this case the test code was only testing correctly under certain conditions (i.e. when all previous reconciles where already finished). In case the test code was working correctly the test did always fail.
@enxebre Would it be possible with this solution to detect which exact reconcile of which event lead to the condition we're checking in l.313? If it was already done by a previous reconcile and the "right" reconcile does a no-op, the test is already testing the wrong thing.
@fabriziopandini Do you mean instead of the sleep I should wait until the resources has a specific ObservedGeneration and only after that create the ConfigMap? I'm not entirely sure which reconcilation is currently the problem. I suspect there might be one which is actually a no-op (which then shouldn't change the ObservedGeneration?). I'm not sure if I can wait for that one when it usually does nothing :/.
I think the problem is that I don't have to only detect when the existing resources are steady. I also have to detect if there are some upcoming reconciliations, which are usually no-ops but could reconcile if they are triggered after the ConfigMap creation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@fabriziopandini I added
fmt.Printf("reconcile: %v %v\n", req.NamespacedName, clusterResourceSet.ObjectMeta.Generation)
After:
cluster-api/exp/addons/controllers/clusterresourceset_controller.go
Lines 106 to 117 in 2ff65de
clusterResourceSet := &addonsv1.ClusterResourceSet{} | |
if err := r.Client.Get(ctx, req.NamespacedName, clusterResourceSet); err != nil { | |
if apierrors.IsNotFound(err) { | |
// Object not found, return. Created objects are automatically garbage collected. | |
// For additional cleanup logic use finalizers. | |
return ctrl.Result{}, nil | |
} | |
// Error reading the object - requeue the request. | |
return ctrl.Result{}, err | |
} | |
// Initialize the patch helper. |
The output was:
reconcile: default/clusterresourceset-c5u43l 1
reconcile: default/clusterresourceset-c5u43l 1
reconcile: default/clusterresourceset-c5u43l 1
...
< create configmap >
...
after each
reconcile: default/clusterresourceset-c5u43l 2
So it looks like there are multiple reconciliations with the same observed generation. So I can't really wait until the last one by checking the generation
P.S. previous version of this comment showed a slightly different log, but that was because I tested it incorrectly and didn't realized that the cleanup lead to generation 2.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sbueringer
Catching up after lagging a little bit on this...
I think we should avoid to add a sleep here, because ensuring that watches with partial metadata works as expected is not really something a behavioural test for ClusterResourceSet should be concerned of.
If we want to have test coverage ensuring that watches with partial metadata works well, I think we should have a dedicated test, might be under util/predicates
so we can separate this issue from the CRS specific tests.
This will allow us to use a dedicated, super simple, test reconciler that e.g. increases an annotation value at every reconcile, so we can actually check when a reconcile happens.
Does this makes sense? This can be eventually addressed also in a separated PR...
/assign @vincepri |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sbueringer thanks for the huge work in investigating this issue.
Before green lighting the fix I would like to understand with @vincepri if there is chance/It should be better to fix this tricky error up in the stack (controller runtime or client-go)
Yeah of course, no problem. My take is that we cannot fix it upstream, because the But of course there might be entirely different ways to fix this. |
/hold I might have a solution for this in Controller Runtime, which is due to review kubernetes-sigs/controller-runtime#1484 |
Sounds reasonable, essentially looks like the same fix just one layer below. Didn't thought of that. If that one is merged, I would drop the changes to the controller and only fix the test. |
2ff65de
to
445a8f5
Compare
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here.
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
I rebased the PR and dropped the changes to the controller as the upgrade to the new controller-runtime version should fix the issue. I think it still makes sense to improve the test so it actually tests correct (i.e. it tests the reconciliation triggered by the ConfigMap creation). I'm not sure what we should do about: #4491 (comment) I still don't have a better solution than adding the sleep. |
@vincepri @fabriziopandini any ideas how/ if we should continue here? |
changes The ClusterResourceSet controller did not react to changes on ConfigMaps and Secrets (except when OwnerReferences were already set). The problem was that we use OnlyMetadata on the watches and we tried to get the kind of ConfigMap/Secret from the TypeMeta of an PartialObjectMetadata. The TypeMeta of a PartialObjectMetadata is empty so apiutil.GVKForObject(o, r.Client.Scheme()) always returned an error and the reconciliation was not triggered.
445a8f5
to
6dbf521
Compare
(Had to rebase because of the test refactoring, but essentially same situation as before) |
@sbueringer: PR needs rebase. Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
Should we close this? |
Created a new PR that solves this issue: #4723 |
/close |
@vincepri: Closed this PR. In response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
New PR looks good to me, thx for taking care of this. |
What this PR does / why we need it:
The ClusterResourceSet controller did not react to changes on
ConfigMaps and Secrets (except when OwnerReferences were already set).
The problem was that we use OnlyMetadata on the watches and we tried to
get the kind of ConfigMap/Secret from the TypeMeta of an PartialObjectMetadata.
The TypeMeta of a PartialObjectMetadata is empty so apiutil.GVKForObject(o,
r.Client.Scheme()) always returned an error and the reconciliation was
not triggered.
Which issue(s) this PR fixes (optional, in
fixes #<issue number>(, fixes #<issue_number>, ...)
format, will close the issue(s) when PR gets merged):Fixes #4486
Additional context: