-
Notifications
You must be signed in to change notification settings - Fork 103
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
Update status readiness #654
base: main
Are you sure you want to change the base?
Update status readiness #654
Conversation
Signed-off-by: Terje Lafton / Intility AS <[email protected]>
…uccessful update Signed-off-by: Terje Lafton / Intility AS <[email protected]>
Signed-off-by: Terje Lafton / Intility AS <[email protected]>
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.
We just did a quick self review to explain some of our decisions.
// accordingly. | ||
// https://github.com/crossplane/crossplane/issues/289 | ||
// skip the update if the management policy is set to ignore updates | ||
if !policy.ShouldUpdate() { |
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 was originally lower in the reconciler, but we moved it here to catch cases where the management policy does not allow updates early.
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 convinced this is an improvement on the previous behavior.
At this point in the reconcile loop we know no update is needed, regardless of whether the policy would or wouldn't allow an update. I feel it's more useful to log that no update is needed than to log that no update will be performed due to policy. Logging that no update will be performed due to policy leaves it ambiguous as to whether an update is needed.
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.
That is a good point! But I think I would rather rewrite the if
on line 1157:
From this: if !observation.ResourceUpToDate && policy.ShouldUpdate() {
To this: if !observation.ResourceUpToDate {
And then I can immediately on the line below check if !policy.ShouldUpdate()
and tell the user that the update will not be performed. This way it will only log this if an update is actually needed.
Would this maybe create spam for the users, though? I am not very familiar with how the ShouldUpdate
policy is set, but when it is set to false, would the user not be spammed with Skipping update...
on every reconcile then?
managed.SetConditions(xpv1.ReconcileSuccess()) | ||
return reconcile.Result{RequeueAfter: reconcileAfter}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus) | ||
} | ||
if !observation.ResourceUpToDate && policy.ShouldUpdate() { |
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 if condition is what allows us to treat the Update
case the same as the Create
case.
This is as close to the implementation for Create
as possible.
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 observation.ResourceUpToDate
is the correct signal from which to derive an Updating
condition.
To me:
ReasonCreating
means a resource is unavailable because it's still being created by the cloud provider.ReasonUpdating
would therefore mean a resource is currently being updated by the cloud provider.
It would potentially make sense to set ReasonUpdating
if the cloud provider API let us know that the resource was processing an update. For example I think a GKE cluster would go into state REPAIRING
at the API level and be partially degraded while updating. On the other hand, a lot of cloud APIs won't give any indication that an update is being processed.
I believe observation.ResourceUpToDate
indicates that there's a diff between the MR's desired state, and the observed state of the external resource. It's not indicating whether the external resource is updating but rather whether the external resource appears to need an update.
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.
Note that this reconciler sets the Creating
condition, but it never unsets it. It never sets the Available
condition.
The reconciler can know that it triggered the creation of of an external resource, but it has no way to know whether creation is done. The ExternalClient
is responsible for setting the resource to Available
when it notices that creation has completed. For some resources the ExternalClient
knows that creation has completed due to some API level state transition. Other resources are ready as soon as they're created.
Will ExternalClient
implementations need to factor in the existence of a new Updating
condition by handling the case where a resource has transitioned from Updating
back to Available
?
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
observation.ResourceUpToDate
is the correct signal from which to derive anUpdating
condition.To me:
* `ReasonCreating` means a resource is unavailable because it's still being created by the cloud provider. * `ReasonUpdating` would therefore mean a resource is currently being updated by the cloud provider.
It would potentially make sense to set
ReasonUpdating
if the cloud provider API let us know that the resource was processing an update. For example I think a GKE cluster would go into stateREPAIRING
at the API level and be partially degraded while updating. On the other hand, a lot of cloud APIs won't give any indication that an update is being processed.I believe
observation.ResourceUpToDate
indicates that there's a diff between the MR's desired state, and the observed state of the external resource. It's not indicating whether the external resource is updating but rather whether the external resource appears to need an update.
I don't really agree with this.
You seem to focus a lot on what the cloud provider reports for the resource, so if they say it is ready, we set it to ready, but is this really what we want?
When I see that a resource is ready
, I would think the cloud provider says it is good to go AND it matches my desired state.
If I update some firewall
resource to add a rule to block something happening, is this really ready until my observe
function has verified that the rule is actually there?
When I first read this in the reconcile
function, it read it as "The API returned a 2xx code indicating that we have successfully created the resource, now let's requeue immediately to make sure it is actually created".
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.
Note that this reconciler sets the
Creating
condition, but it never unsets it. It never sets theAvailable
condition.The reconciler can know that it triggered the creation of of an external resource, but it has no way to know whether creation is done. The
ExternalClient
is responsible for setting the resource toAvailable
when it notices that creation has completed. For some resources theExternalClient
knows that creation has completed due to some API level state transition. Other resources are ready as soon as they're created.Will
ExternalClient
implementations need to factor in the existence of a newUpdating
condition by handling the case where a resource has transitioned fromUpdating
back toAvailable
?
That is a good point.
I am not very familiar with how for example the Upjet providers set the ready
field, but I assume they do it at the end of the observe
function just before they return the response saying that the resource both exists and is up to date.
My desire was always to just make Update behave the same as Create, to always give the most up to date status on if the resource is as I desire.
So in the implementation I proposed, I set the status to not ready
and Updating
with an instant requeue to have the observe function have to verify that things are OK. This way it should be covered by the same logic that already sets the resource as Ready
by existing providers.
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 wasn't actually able to find these definitions in the docs, only this from the troubleshooting part:
Most Crossplane resources set the Ready condition. Ready represents the availability of the resource - whether it is creating, deleting, available, unavailable, binding, etc.
You acknowledge that there is a gap here, and I feel like the issues you linked confirm the gap and that is a big enough problem that workarounds have been attempted to fix it.
I understand that definitions and such can be scary to change, as they often introduce some level of a breaking change, but I don't really see the negative side of changing this. All providers already implement a way of checking if the resource is actually compliant with the desired state, I only suggest that we actually use it for Update
too.
What is the argument for not changing this?
The only negative I see is that the resource will be shown as Updating
for a couple of seconds before it shows Ready
.
Another issue with the current implementation:
If I were to create my own in-house provider, and I wanted the Ready
field to better indicate if the resource is updating, I could manually set the condition in my Update
function. The problem is that there is no reconcile requeue after successfully requesting the update, so if I have 10 minute poll interval, it means my resource is Updating
for 10 minutes, even though it only took a 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.
What is the argument for not changing this?
I'm not convinced the way you're addressing the problem in this PR is the best way to address the problem. If we're going to change the semantics of Ready
, we should take a step back and:
- Consider the impact.
- Consider whether changing the semantics of
Ready
is the right thing to do (vs alternatives like adoptingkstatus
.
I think a change like this warrants at least a one-pager to get us all aligned on what the problem is, and on which solution is best.
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.
Sounds good! Would you want me to write it, or is this something you would do internally?
I am not very familiar with the one-pager
concept, but it seems like most of the existing ones are written by maintainers.
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.
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.
Sure, I can give it a try 😄
I'll work on a first draft and open a PR in the Crossplane repo. I will do some research into Kstatus too.
managed.SetConditions(xpv1.Updating(), xpv1.ReconcileError(errors.Wrap(err, errReconcileUpdate))) | ||
return reconcile.Result{Requeue: true}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus) |
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 is mostly the point of the PR. This is what gives us more consistent behaviour for Update
.
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.
Also similar additions on line 1177, for example.
// We've successfully updated our external resource. In many cases the | ||
// updating process takes a little time to finish. We requeue explicitly | ||
// order to observe the external resource to determine whether it's | ||
// ready for use. |
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 am not sure about the "We requeue explicitly order to observe..." sentence. It seems a bit odd to me.
This comment was copied from the Create
case above and changed to fit with the Update
case.
// We did not need to create, update, or delete our external resource. | ||
// Per the below issue nothing will notify us if and when the external | ||
// resource we manage changes, so we requeue a speculative reconcile | ||
// after the specified poll interval in order to observe it and react | ||
// accordingly. |
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 is the comment that was used for the if observation.ResourceUpToDate
case.
We moved it to the bottom as it seems to work well for the last resort case, which is when there is nothing we need to do.
// https://github.com/crossplane/crossplane/issues/289 | ||
reconcileAfter := r.pollIntervalHook(managed, r.pollInterval) | ||
log.Debug("Successfully requested update of external resource", "requeue-after", time.Now().Add(reconcileAfter)) | ||
record.Event(managed, event.Normal(reasonUpdated, "Successfully requested update of external resource")) |
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.
We chose to not emit any events here, but we are not sure.
Emitting here would emit every reconcile that just observed and everything is okay, so it doesn't seem right.
Assigning myself - I'd like to make some time to review before we proceed here. This reconciler is very subtle and very widely used. |
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.
Apologies for the delayed review, this fell out of my GitHub notifications somehow.
// accordingly. | ||
// https://github.com/crossplane/crossplane/issues/289 | ||
// skip the update if the management policy is set to ignore updates | ||
if !policy.ShouldUpdate() { |
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 convinced this is an improvement on the previous behavior.
At this point in the reconcile loop we know no update is needed, regardless of whether the policy would or wouldn't allow an update. I feel it's more useful to log that no update is needed than to log that no update will be performed due to policy. Logging that no update will be performed due to policy leaves it ambiguous as to whether an update is needed.
func Updating() Condition { | ||
return Condition{ | ||
Type: TypeReady, | ||
Status: corev1.ConditionFalse, |
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.
Per #583 (comment), I don't think an updating resource is necessarily an unready resource.
managed.SetConditions(xpv1.ReconcileSuccess()) | ||
return reconcile.Result{RequeueAfter: reconcileAfter}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus) | ||
} | ||
if !observation.ResourceUpToDate && policy.ShouldUpdate() { |
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 observation.ResourceUpToDate
is the correct signal from which to derive an Updating
condition.
To me:
ReasonCreating
means a resource is unavailable because it's still being created by the cloud provider.ReasonUpdating
would therefore mean a resource is currently being updated by the cloud provider.
It would potentially make sense to set ReasonUpdating
if the cloud provider API let us know that the resource was processing an update. For example I think a GKE cluster would go into state REPAIRING
at the API level and be partially degraded while updating. On the other hand, a lot of cloud APIs won't give any indication that an update is being processed.
I believe observation.ResourceUpToDate
indicates that there's a diff between the MR's desired state, and the observed state of the external resource. It's not indicating whether the external resource is updating but rather whether the external resource appears to need an update.
managed.SetConditions(xpv1.ReconcileSuccess()) | ||
return reconcile.Result{RequeueAfter: reconcileAfter}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus) | ||
} | ||
if !observation.ResourceUpToDate && policy.ShouldUpdate() { |
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.
Note that this reconciler sets the Creating
condition, but it never unsets it. It never sets the Available
condition.
The reconciler can know that it triggered the creation of of an external resource, but it has no way to know whether creation is done. The ExternalClient
is responsible for setting the resource to Available
when it notices that creation has completed. For some resources the ExternalClient
knows that creation has completed due to some API level state transition. Other resources are ready as soon as they're created.
Will ExternalClient
implementations need to factor in the existence of a new Updating
condition by handling the case where a resource has transitioned from Updating
back to Available
?
No problem! Thanks for the review 😄 |
@TerjeLafton , when this is merged can you open a Docs Issue referencing this PR so we document your updates? Thanks! |
Crossplane does not currently have enough maintainers to address every issue and pull request. This pull request has been automatically marked as |
Description of your changes
This PR aims to align the behavior of the reconciler for
Update
operations with that ofCreate
operations.It achieves this by rewriting some of the
Reconcile
function to not use the call toexternal.Update
as a last resort, but instead evaluate if it is needed based on the returnedobservation.ResourceUpToDate
andpolicy.ShouldUpdate
.Fixes #583
I have:
make reviewable test
to ensure this PR is ready for review.How has this code been tested
We have updated all tests related to the
Update
functionality, so they now test if thexpv1.Updating()
condition is there and that it returns the updatedreconcile.Result{Requeue: true}
struct.No tests have been added, but we are not sure if it is needed.