-
Notifications
You must be signed in to change notification settings - Fork 108
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
Add ability to expose resource reconciliation progress #633
Conversation
apis/common/v1/condition.go
Outdated
// ObservedLabels are the most recent resource metadata.labels | ||
// observed by the reconciler | ||
// +optional | ||
ObserverLabels map[string]string `json:"observedLabels,omitempty"` | ||
|
||
// ObservedLabels are the most recent resource metadata.annotations | ||
// observed by the reconciler | ||
// +optional | ||
ObserverAnnotations map[string]string `json:"observedAnnotations,omitempty"` |
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.
// ObservedLabels are the most recent resource metadata.labels | |
// observed by the reconciler | |
// +optional | |
ObserverLabels map[string]string `json:"observedLabels,omitempty"` | |
// ObservedLabels are the most recent resource metadata.annotations | |
// observed by the reconciler | |
// +optional | |
ObserverAnnotations map[string]string `json:"observedAnnotations,omitempty"` | |
// ObservedLabels are the most recent resource metadata.labels | |
// observed by the reconciler | |
// +optional | |
ObservedLabels map[string]string `json:"observedLabels,omitempty"` | |
// ObservedLabels are the most recent resource metadata.annotations | |
// observed by the reconciler | |
// +optional | |
ObservedAnnotations map[string]string `json:"observedAnnotations,omitempty"` |
Can you please create a new struct (something like ObservedStatus
) for these fields, instead of adding them to ConditionedStatus
. The idea of ConditionedStatus
is that you can embed it in a status struct to support status conditions. The new struct should be in a different file (not condition.go).
Is there precedent for other tools having observedLabels and observedAnnotations as well as observedGeneration? I'm supportive of observedGeneration but not convinced that it's worth two additional fields for labels and annotations.
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.
@negz great that you spot the typo!
The new struct should be in a different file (not condition.go).
Done, the struct lives now in apis/common/v1/observation.go
Is there precedent for other tools having observedLabels and observedAnnotations as well as observedGeneration? I'm supportive of observedGeneration but not convinced that it's worth two additional fields for labels and annotations.
Value in metadata.generation
get increased only when resource spec
changes. Given that our controllers react also on label/annotation changes and often use/propagate them during the reconciliation, writing down just the observed generation might not be enough, i.e. an observer would not be able to detect if/when a label/annotation change got processed.
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 sold on observedGeneration
because:
- It's succinct, just a generation number not a copy of the whole spec object.
- It's an established Kubernetes pattern - it's common (and recommended iirc) to use
observedGeneration
. - Reacting to spec (i.e. desired state) changes is pretty much always critical.
However I'm not sold on observedLabels
and observedAnnotations
. Compared to observedGeneration
:
- It's more verbose - we have to duplicate all labels and annotations under status.
- It's not a pattern I've ever seen before. (I didn't search though - let me know if I'm mistaken.)
- Reacting to label/annotation changes seems less clearly critical. I can see that it could be in some cases, but it doesn't seem as clear cut at spec.
Do we know of concrete situations (ideally issues) that recording observed labels and annotations would have helped with? If not, I would suggest we omit them until there's evidence they're 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.
Do we know of concrete situations (ideally issues) that recording observed labels and annotations would have helped with? If not, I would suggest we omit them until there's evidence they're needed.
The PR adds an ability to record the observed labels/annotations and the both fields are optional. IMHO a controller should set them if they are used anyhow to influence the reconcilation logic.
In a Crossplane claim, its annotations and labels are copied down to its counterpart composite as a part of reconciliation.
If we do not record the observed claim annotations/lables, then an observer is not able to detect if a reconciliation has happened:
- A claim got created and became ready
- The claim annotation got added/removed/updated
- Given that
metadata.generation
did not change,status.observedGeneration
does not change either after the reconcile
I have found an old issue about asking to increase metadata.generation
on annotation/label changes:
kubernetes/kubernetes#67428 and very interesting followup discussion.
@sttts what are your thought on this? Should we stop propagating annotations/labels from claim to composite? Should it be acceptable if claim reconciliation on pure annotation/label changes is not detectable by an observer?
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.
If we do not record the observed claim annotations/lables, then an observer is not able to detect if a reconciliation has happened.
I get that, but I'm challenging whether this has ever been a real problem for someone. Should we wait to see if this is hurting people before we fix it?
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.
Should we stop propagating annotations/labels from claim to composite?
I don't think we can - this would be a breaking behaviour change.
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.
The PR adds an ability to record the observed labels/annotations and the both fields are optional. IMHO a controller should set them if they are used anyhow to influence the reconcilation logic.
In a Crossplane claim, its annotations and labels are copied down to its counterpart composite as a part of reconciliation.
If we do not record the observed claim annotations/lables, then an observer is not able to detect if a reconciliation has happened
I tend to agree with these statements.
That having said, IMO passing down labels/annotations from claim to xrd and similar is wrong, especially for annotations. But as they are passed down today, it sounds logical that the controller records them as part of the observed state.
|
||
SetObservedAnnotations(annotations map[string]string) | ||
GetObservedAnnotations() map[string]string | ||
} |
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.
Do you expect that managed resources would also satisfy this interface, eventually?
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.
Do you expect that managed resources would also satisfy this interface, eventually?
Yes, I was just thinking that adding ReconciliationObserver
to Managed
would be too much for this PR, because then we would need to add the necessary logic to pkg/reconciler/managed/reconciler.go
.
8a33531
to
8fd92c1
Compare
Which labels and annotations do we have in mind here? In the Kubernetes object model neither should really matter, the spec does. How can we move this forward? If the observedGeneration is a no-brainer, can we move that forward first? |
Yes, I think we should do that. The labels/annotations seems trickier, so I want to hold on that for now:
|
@@ -226,6 +226,7 @@ type ResourceSpec struct { | |||
// ResourceStatus represents the observed state of a managed resource. | |||
type ResourceStatus struct { | |||
ConditionedStatus `json:",inline"` | |||
ObservedStatus `json:",inline"` |
This comment was marked as duplicate.
This comment was marked as duplicate.
Sorry, something went wrong.
This comment was marked as duplicate.
This comment was marked as duplicate.
Sorry, something went wrong.
* `status.observedGeneration` fields has been added to claim/composite/composed status, showing the latest metadata.generation which resulted in either a ready state, or stalled due to error it can not recover from without human intervention. * `status.conditions[x].observedGeneration represents the .metadata.generation that the condition was set based upon Signed-off-by: Predrag Knezevic <[email protected]>
8fd92c1
to
b5462b5
Compare
@negz @sttts I have updated PR, aligned also with SIG Architecture API convention. This opens up the possibility to achieve compatibility with kstatus specification at some point. |
@negz can you take another look? |
func TestObservedGeneration(t *testing.T) { | ||
u := &Unstructured{unstructured.Unstructured{Object: map[string]any{}}} | ||
want := int64(123) | ||
u.SetObservedGeneration(want) | ||
|
||
if got := u.GetObservedGeneration(); got != want { | ||
t.Errorf("u.GetObservedGeneration() got: %v, want %v", got, want) | ||
} | ||
if g := u.GetUnstructured().Object["status"].(map[string]any)["observedGeneration"]; g != want { | ||
t.Errorf("Generations do not match! got: %v (%T)", g, g) | ||
} | ||
} | ||
|
||
func TestObservedGenerationNotFound(t *testing.T) { | ||
u := &Unstructured{unstructured.Unstructured{Object: map[string]any{}}} | ||
|
||
if g := u.GetObservedGeneration(); g != 0 { | ||
t.Errorf("u.GetObservedGeneration(): expected nil, but got %v", g) | ||
} | ||
} |
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.
These tests are written in a different style from every other test in the file. Despite the fact that they're mostly tables with a single case, I'd prefer to match the existing practices.
@@ -141,6 +157,9 @@ func (s *ConditionedStatus) SetConditions(c ...Condition) { | |||
|
|||
if existing.Equal(new) { |
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.
GitHub won't let me add a comment to the existing.Equal
docstring, but it should explicitly call out that it doesn't consider ObservedGeneration
when assessing equality. It already calls out that it ignores LastTransitionTime.
@@ -223,7 +223,7 @@ func (c *Unstructured) SetPublishConnectionDetailsTo(ref *xpv1.PublishConnection | |||
|
|||
// GetCondition of this composite resource claim. | |||
func (c *Unstructured) GetCondition(ct xpv1.ConditionType) xpv1.Condition { | |||
conditioned := xpv1.ConditionedStatus{} | |||
conditioned := xpv1.ResourceStatus{} |
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.
Why change this to ResourceStatus?
@@ -233,7 +233,7 @@ func (c *Unstructured) GetCondition(ct xpv1.ConditionType) xpv1.Condition { | |||
|
|||
// SetConditions of this composite resource claim. | |||
func (c *Unstructured) SetConditions(conditions ...xpv1.Condition) { | |||
conditioned := xpv1.ConditionedStatus{} | |||
conditioned := xpv1.ResourceStatus{} |
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.
Why change this to ResourceStatus?
@@ -72,7 +72,7 @@ func (cr *Unstructured) GetUnstructured() *unstructured.Unstructured { | |||
|
|||
// GetCondition of this Composed resource. | |||
func (cr *Unstructured) GetCondition(ct xpv1.ConditionType) xpv1.Condition { | |||
conditioned := xpv1.ConditionedStatus{} | |||
conditioned := xpv1.ResourceStatus{} |
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.
Why change this to ResourceStatus?
@@ -82,7 +82,7 @@ func (cr *Unstructured) GetCondition(ct xpv1.ConditionType) xpv1.Condition { | |||
|
|||
// SetConditions of this Composed resource. | |||
func (cr *Unstructured) SetConditions(c ...xpv1.Condition) { | |||
conditioned := xpv1.ConditionedStatus{} | |||
conditioned := xpv1.ResourceStatus{} |
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.
Why change this to ResourceStatus?
|
||
// SetObservedGeneration of this composite resource claim. | ||
func (c *Unstructured) SetObservedGeneration(generation int64) { | ||
status := &xpv1.ResourceStatus{} |
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.
Why not use ObservedStatus? (Here and elsewhere)
Signed-off-by: Predrag Knezevic <[email protected]>
I have addressed your comments @negz ptal. |
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.
Thank you!
Description of your changes
status.observedGeneration
fields has been added to claim/composite/composed status, showing the latest metadata.generation which resulted in either a ready state, or stalled due to error it can not recover from without human intervention.status.conditions[x].observedGeneration
represents the .metadata.generation that the condition was set based upon.Conditions are now aligned with SIG Architecture API convention.
Fixes crossplane/crossplane#4695
Part of crossplane/crossplane#4655
I have:
make reviewable test
to ensure this PR is ready for review.