Skip to content

Commit

Permalink
Add ability to expose resource reconciliation progress
Browse files Browse the repository at this point in the history
The following optional fields has been added to claim/composite/composed status
* `observedGeneration` when available shows what is the last observed resource generation
* `observedLabels` when available shows what are the last observed resource labels
* `observedAnnotations` when available shows what are the last observed resource annotations

Signed-off-by: Predrag Knezevic <[email protected]>
  • Loading branch information
pedjak committed Dec 27, 2023
1 parent d23a82b commit 8a33531
Show file tree
Hide file tree
Showing 10 changed files with 436 additions and 1 deletion.
68 changes: 68 additions & 0 deletions apis/common/v1/condition.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,21 @@ type ConditionedStatus struct {
// +listMapKey=type
// +optional
Conditions []Condition `json:"conditions,omitempty"`

// ObservedGeneration is the most recent resource metadata.generation
// observed by the reconciler
// +optional
ObservedGeneration *int64 `json:"observedGeneration,omitempty"`

// 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"`
}

// NewConditionedStatus returns a stat with the supplied conditions set.
Expand Down Expand Up @@ -153,6 +168,59 @@ func (s *ConditionedStatus) SetConditions(c ...Condition) {
}
}

// SetObservedGeneration sets the generation of the main resource
// during the last reconciliation.
func (s *ConditionedStatus) SetObservedGeneration(generation int64) {
s.ObservedGeneration = &generation
}

// GetObservedGeneration returns the last observed generation of the main resource.
func (s *ConditionedStatus) GetObservedGeneration() *int64 {
return s.ObservedGeneration
}

// SetObservedLabels set the labels observed on the main resource
// during the last reconciliation.
func (s *ConditionedStatus) SetObservedLabels(labels map[string]string) {
s.ObserverLabels = make(map[string]string)
for k, v := range labels {
s.ObserverLabels[k] = v
}
}

// GetObservedLabels returns the last observed labels of the main resource.
func (s *ConditionedStatus) GetObservedLabels() map[string]string {
if s.ObserverLabels == nil {
return nil
}
r := make(map[string]string)
for k, v := range s.ObserverLabels {
r[k] = v
}
return r
}

// SetObservedAnnotations set the annotations observed on the main resource
// during the last reconciliation.
func (s *ConditionedStatus) SetObservedAnnotations(annotations map[string]string) {
s.ObserverAnnotations = make(map[string]string)
for k, v := range annotations {
s.ObserverAnnotations[k] = v
}
}

// GetObservedAnnotations returns the last observed annotations of the main resource.
func (s *ConditionedStatus) GetObservedAnnotations() map[string]string {
if s.ObserverAnnotations == nil {
return nil
}
r := make(map[string]string)
for k, v := range s.ObserverAnnotations {
r[k] = v
}
return r
}

// Equal returns true if the status is identical to the supplied status,
// ignoring the LastTransitionTimes and order of statuses.
func (s *ConditionedStatus) Equal(other *ConditionedStatus) bool {
Expand Down
19 changes: 19 additions & 0 deletions apis/common/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions pkg/resource/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,18 @@ type ConnectionDetailsPublishedTimer interface {
GetConnectionDetailsLastPublishedTime() *metav1.Time
}

// ReconciliationObserver can track data observed by resource reconciler.
type ReconciliationObserver interface {
SetObservedGeneration(generation int64)
GetObservedGeneration() *int64

SetObservedLabels(labels map[string]string)
GetObservedLabels() map[string]string

SetObservedAnnotations(annotations map[string]string)
GetObservedAnnotations() map[string]string
}

// An Object is a Kubernetes object.
type Object interface {
metav1.Object
Expand Down Expand Up @@ -245,6 +257,7 @@ type Composite interface {

Conditioned
ConnectionDetailsPublishedTimer
ReconciliationObserver
}

// Composed resources can be a composed into a Composite resource.
Expand All @@ -254,6 +267,7 @@ type Composed interface {
Conditioned
ConnectionSecretWriterTo
ConnectionDetailsPublisherTo
ReconciliationObserver
}

// A CompositeClaim for a Composite resource.
Expand All @@ -272,4 +286,5 @@ type CompositeClaim interface {

Conditioned
ConnectionDetailsPublishedTimer
ReconciliationObserver
}
11 changes: 10 additions & 1 deletion pkg/resource/interfaces_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ limitations under the License.

package resource

import "github.com/crossplane/crossplane-runtime/pkg/resource/fake"
import (
"github.com/crossplane/crossplane-runtime/pkg/resource/fake"
"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/claim"
"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/composed"
"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/composite"
)

// We test that our fakes satisfy our interfaces here rather than in the fake
// package to avoid a cyclic dependency.
Expand All @@ -29,4 +34,8 @@ var (
_ CompositeClaim = &fake.CompositeClaim{}
_ Composite = &fake.Composite{}
_ Composed = &fake.Composed{}

_ CompositeClaim = &claim.Unstructured{}
_ Composite = &composite.Unstructured{}
_ Composed = &composed.Unstructured{}
)
45 changes: 45 additions & 0 deletions pkg/resource/unstructured/claim/claim.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,3 +253,48 @@ func (c *Unstructured) GetConnectionDetailsLastPublishedTime() *metav1.Time {
func (c *Unstructured) SetConnectionDetailsLastPublishedTime(t *metav1.Time) {
_ = fieldpath.Pave(c.Object).SetValue("status.connectionDetails.lastPublishedTime", t)
}

// SetObservedGeneration of this composite resource claim.
func (c *Unstructured) SetObservedGeneration(generation int64) {
status := &xpv1.ConditionedStatus{}
_ = fieldpath.Pave(c.Object).GetValueInto("status", status)
status.SetObservedGeneration(generation)
_ = fieldpath.Pave(c.Object).SetValue("status", status)
}

// GetObservedGeneration of this composite resource claim.
func (c *Unstructured) GetObservedGeneration() *int64 {
status := &xpv1.ConditionedStatus{}
_ = fieldpath.Pave(c.Object).GetValueInto("status", status)
return status.GetObservedGeneration()
}

// SetObservedLabels of this composite resource claim.
func (c *Unstructured) SetObservedLabels(labels map[string]string) {
status := &xpv1.ConditionedStatus{}
_ = fieldpath.Pave(c.Object).GetValueInto("status", status)
status.SetObservedLabels(labels)
_ = fieldpath.Pave(c.Object).SetValue("status", status)
}

// GetObservedLabels of this composite resource claim.
func (c *Unstructured) GetObservedLabels() map[string]string {
status := &xpv1.ConditionedStatus{}
_ = fieldpath.Pave(c.Object).GetValueInto("status", status)
return status.GetObservedLabels()
}

// SetObservedAnnotations of this composite resource claim.
func (c *Unstructured) SetObservedAnnotations(annotations map[string]string) {
status := &xpv1.ConditionedStatus{}
_ = fieldpath.Pave(c.Object).GetValueInto("status", status)
status.SetObservedAnnotations(annotations)
_ = fieldpath.Pave(c.Object).SetValue("status", status)
}

// GetObservedAnnotations of this composite resource claim.
func (c *Unstructured) GetObservedAnnotations() map[string]string {
status := &xpv1.ConditionedStatus{}
_ = fieldpath.Pave(c.Object).GetValueInto("status", status)
return status.GetObservedAnnotations()
}
63 changes: 63 additions & 0 deletions pkg/resource/unstructured/claim/claim_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -368,3 +368,66 @@ func TestConnectionDetailsLastPublishedTime(t *testing.T) {
})
}
}

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 != nil {
t.Errorf("u.GetObservedGeneration(): expected nil, but got %v", g)
}
}

func TestObservedLabels(t *testing.T) {
u := &Unstructured{unstructured.Unstructured{Object: map[string]any{}}}
labels := map[string]string{
"foo": "1",
"bar": "2",
}
u.SetObservedLabels(labels)

if diff := cmp.Diff(labels, u.GetObservedLabels()); diff != "" {
t.Errorf("u.GetObservedLabels(): -want, +got:\n%s", diff)
}
}

func TestObservedLabelsNotFound(t *testing.T) {
u := &Unstructured{unstructured.Unstructured{Object: map[string]any{}}}

if g := u.GetObservedLabels(); g != nil {
t.Errorf("u.GetObservedLabels(): expected nil, but got %v", g)
}
}

func TestObservedAnnotations(t *testing.T) {
u := &Unstructured{unstructured.Unstructured{Object: map[string]any{}}}
annotations := map[string]string{
"foo": "1",
"bar": "2",
}
u.SetObservedAnnotations(annotations)

if diff := cmp.Diff(annotations, u.GetObservedAnnotations()); diff != "" {
t.Errorf("u.GetObservedAnnotations(): -want, +got:\n%s", diff)
}
}

func TestObservedAnnotationsNotFound(t *testing.T) {
u := &Unstructured{unstructured.Unstructured{Object: map[string]any{}}}

if g := u.GetObservedAnnotations(); g != nil {
t.Errorf("u.GetObservedAnnotations(): expected nil, but got %v", g)
}
}
45 changes: 45 additions & 0 deletions pkg/resource/unstructured/composed/composed.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,48 @@ type UnstructuredList struct {
func (cr *UnstructuredList) GetUnstructuredList() *unstructured.UnstructuredList {
return &cr.UnstructuredList
}

// SetObservedGeneration of this composite resource claim.
func (cr *Unstructured) SetObservedGeneration(generation int64) {
status := &xpv1.ConditionedStatus{}
_ = fieldpath.Pave(cr.Object).GetValueInto("status", status)
status.SetObservedGeneration(generation)
_ = fieldpath.Pave(cr.Object).SetValue("status", status)
}

// GetObservedGeneration of this composite resource claim.
func (cr *Unstructured) GetObservedGeneration() *int64 {
status := &xpv1.ConditionedStatus{}
_ = fieldpath.Pave(cr.Object).GetValueInto("status", status)
return status.GetObservedGeneration()
}

// SetObservedLabels of this composite resource claim.
func (cr *Unstructured) SetObservedLabels(labels map[string]string) {
status := &xpv1.ConditionedStatus{}
_ = fieldpath.Pave(cr.Object).GetValueInto("status", status)
status.SetObservedLabels(labels)
_ = fieldpath.Pave(cr.Object).SetValue("status", status)
}

// GetObservedLabels of this composite resource claim.
func (cr *Unstructured) GetObservedLabels() map[string]string {
status := &xpv1.ConditionedStatus{}
_ = fieldpath.Pave(cr.Object).GetValueInto("status", status)
return status.GetObservedLabels()
}

// SetObservedAnnotations of this composite resource claim.
func (cr *Unstructured) SetObservedAnnotations(annotations map[string]string) {
status := &xpv1.ConditionedStatus{}
_ = fieldpath.Pave(cr.Object).GetValueInto("status", status)
status.SetObservedAnnotations(annotations)
_ = fieldpath.Pave(cr.Object).SetValue("status", status)
}

// GetObservedAnnotations of this composite resource claim.
func (cr *Unstructured) GetObservedAnnotations() map[string]string {
status := &xpv1.ConditionedStatus{}
_ = fieldpath.Pave(cr.Object).GetValueInto("status", status)
return status.GetObservedAnnotations()
}
Loading

0 comments on commit 8a33531

Please sign in to comment.