From d255c33f5dea62290e1d7a9cbbc3aae5bce45553 Mon Sep 17 00:00:00 2001 From: Sander Tervoert <32864332+sandert-k8s@users.noreply.github.com> Date: Mon, 1 Jun 2026 06:51:46 +0200 Subject: [PATCH] feat: add observedgeneration to status object of all crds (#1930) * feat: add observedgeneration to status object of all crds Signed-off-by: sandert-k8s * chore(api): rename rulestatusspec to rulestatusstatus Signed-off-by: sandert-k8s --------- Signed-off-by: sandert-k8s --- api/v1beta2/capsuleconfiguration_status.go | 3 + api/v1beta2/customquota_status.go | 3 + api/v1beta2/resourcepool_status.go | 3 + api/v1beta2/resourcepoolclaim_types.go | 3 + api/v1beta2/rule_status_type.go | 7 +- api/v1beta2/tenant_status.go | 4 + api/v1beta2/tenantowner_types.go | 6 +- api/v1beta2/tenantresource_types.go | 3 + api/v1beta2/zz_generated.deepcopy.go | 8 +- ...sule.clastix.io_capsuleconfigurations.yaml | 5 + .../crds/capsule.clastix.io_customquotas.yaml | 5 + ...capsule.clastix.io_globalcustomquotas.yaml | 5 + ...sule.clastix.io_globaltenantresources.yaml | 5 + ...capsule.clastix.io_resourcepoolclaims.yaml | 5 + .../capsule.clastix.io_resourcepools.yaml | 5 + .../crds/capsule.clastix.io_rulestatuses.yaml | 5 + .../crds/capsule.clastix.io_tenantowners.yaml | 6 + .../capsule.clastix.io_tenantresources.yaml | 5 + .../crds/capsule.clastix.io_tenants.yaml | 5 + e2e/observed_generation_test.go | 249 ++++++++++++++++++ e2e/utils_test.go | 8 + internal/controllers/cfg/status/manager.go | 1 + .../customquotas/custom_quota_controller.go | 1 + .../global_custom_quota_controller.go | 1 + .../resourcepools/claim_controller.go | 1 + .../resourcepools/pool_controller.go | 1 + internal/controllers/resources/global.go | 1 + internal/controllers/resources/namespaced.go | 1 + internal/controllers/rulestatus/manager.go | 1 + internal/controllers/tenant/status.go | 1 + 30 files changed, 350 insertions(+), 7 deletions(-) create mode 100644 e2e/observed_generation_test.go diff --git a/api/v1beta2/capsuleconfiguration_status.go b/api/v1beta2/capsuleconfiguration_status.go index d2c3ffd1..716b98d9 100644 --- a/api/v1beta2/capsuleconfiguration_status.go +++ b/api/v1beta2/capsuleconfiguration_status.go @@ -9,6 +9,9 @@ import ( // CapsuleConfigurationStatus defines the Capsule configuration status. type CapsuleConfigurationStatus struct { + // ObservedGeneration is the most recent generation the controller has observed. + // +optional + ObservedGeneration int64 `json:"observedGeneration,omitempty"` // Users which are considered Capsule Users and are bound to the Capsule Tenant construct. Users rbac.UserListSpec `json:"users,omitempty"` } diff --git a/api/v1beta2/customquota_status.go b/api/v1beta2/customquota_status.go index 09b6d4c2..d68f8a15 100644 --- a/api/v1beta2/customquota_status.go +++ b/api/v1beta2/customquota_status.go @@ -14,6 +14,9 @@ import ( // CustomQuotaStatus defines the observed state of GlobalResourceQuota. type CustomQuotaStatus struct { + // ObservedGeneration is the most recent generation the controller has observed. + // +optional + ObservedGeneration int64 `json:"observedGeneration,omitempty"` // Usage measurements // +optional Usage CustomQuotaStatusUsage `json:"usage,omitempty"` diff --git a/api/v1beta2/resourcepool_status.go b/api/v1beta2/resourcepool_status.go index 1e859f3f..fb17bf77 100644 --- a/api/v1beta2/resourcepool_status.go +++ b/api/v1beta2/resourcepool_status.go @@ -13,6 +13,9 @@ import ( // GlobalResourceQuotaStatus defines the observed state of GlobalResourceQuota. type ResourcePoolStatus struct { + // ObservedGeneration is the most recent generation the controller has observed. + // +optional + ObservedGeneration int64 `json:"observedGeneration,omitempty"` // How many namespaces are considered // +kubebuilder:default=0 NamespaceSize uint `json:"namespaceCount,omitempty"` diff --git a/api/v1beta2/resourcepoolclaim_types.go b/api/v1beta2/resourcepoolclaim_types.go index 558908a4..e46a7147 100644 --- a/api/v1beta2/resourcepoolclaim_types.go +++ b/api/v1beta2/resourcepoolclaim_types.go @@ -21,6 +21,9 @@ type ResourcePoolClaimSpec struct { // ResourceQuotaClaimStatus defines the observed state of ResourceQuotaClaim. type ResourcePoolClaimStatus struct { + // ObservedGeneration is the most recent generation the controller has observed. + // +optional + ObservedGeneration int64 `json:"observedGeneration,omitempty"` // Reference to the GlobalQuota being claimed from // +optional Pool meta.LocalRFC1123ObjectReferenceWithUID `json:"pool,omitzero"` diff --git a/api/v1beta2/rule_status_type.go b/api/v1beta2/rule_status_type.go index 94f3a106..6802f8c5 100644 --- a/api/v1beta2/rule_status_type.go +++ b/api/v1beta2/rule_status_type.go @@ -12,7 +12,10 @@ import ( // RuleStatus contains the accumulated rules applying to namespace it's deployed in. // +kubebuilder:object:generate=true -type RuleStatusSpec struct { +type RuleStatusStatus struct { + // ObservedGeneration is the most recent generation the controller has observed. + // +optional + ObservedGeneration int64 `json:"observedGeneration,omitempty"` // Managed Enforcement properties per Namespace (aggregated from rules) //+optional Rule api.NamespaceRuleBodyNamespace `json:"rule,omitzero"` @@ -34,7 +37,7 @@ type RuleStatus struct { Spec []*api.NamespaceRuleBodyNamespace `json:"spec,omitzero"` // +optional - Status RuleStatusSpec `json:"status,omitzero"` + Status RuleStatusStatus `json:"status,omitzero"` } // +kubebuilder:object:root=true diff --git a/api/v1beta2/tenant_status.go b/api/v1beta2/tenant_status.go index 7e05f877..2a229c83 100644 --- a/api/v1beta2/tenant_status.go +++ b/api/v1beta2/tenant_status.go @@ -25,6 +25,10 @@ type TenantStatus struct { // Allowed Cluster Objects within Tenant TenantAvailableStatus `json:",inline"` + // ObservedGeneration is the most recent generation the controller has observed. + // +optional + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + // Collected owners for this tenant Owners rbac.OwnerStatusListSpec `json:"owners,omitempty"` // Promoted ServiceAccounts across the Tenant diff --git a/api/v1beta2/tenantowner_types.go b/api/v1beta2/tenantowner_types.go index 55b3d0da..ffd1dfab 100644 --- a/api/v1beta2/tenantowner_types.go +++ b/api/v1beta2/tenantowner_types.go @@ -22,7 +22,11 @@ type TenantOwnerSpec struct { } // TenantOwnerStatus defines the observed state of TenantOwner. -type TenantOwnerStatus struct{} +type TenantOwnerStatus struct { + // ObservedGeneration is the most recent generation the controller has observed. + // +optional + ObservedGeneration int64 `json:"observedGeneration,omitempty"` +} // +kubebuilder:object:root=true // +kubebuilder:subresource:status diff --git a/api/v1beta2/tenantresource_types.go b/api/v1beta2/tenantresource_types.go index 401857ad..3b9b6119 100644 --- a/api/v1beta2/tenantresource_types.go +++ b/api/v1beta2/tenantresource_types.go @@ -13,6 +13,9 @@ import ( ) type TenantResourceCommonStatus struct { + // ObservedGeneration is the most recent generation the controller has observed. + // +optional + ObservedGeneration int64 `json:"observedGeneration,omitempty"` // Condition of the GlobalTenantResource. Conditions meta.ConditionList `json:"conditions,omitempty"` diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index 34279fb6..6653aa02 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -1626,7 +1626,7 @@ func (in *RuleStatusList) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RuleStatusSpec) DeepCopyInto(out *RuleStatusSpec) { +func (in *RuleStatusStatus) DeepCopyInto(out *RuleStatusStatus) { *out = *in in.Rule.DeepCopyInto(&out.Rule) if in.Conditions != nil { @@ -1638,12 +1638,12 @@ func (in *RuleStatusSpec) DeepCopyInto(out *RuleStatusSpec) { } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RuleStatusSpec. -func (in *RuleStatusSpec) DeepCopy() *RuleStatusSpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RuleStatusStatus. +func (in *RuleStatusStatus) DeepCopy() *RuleStatusStatus { if in == nil { return nil } - out := new(RuleStatusSpec) + out := new(RuleStatusStatus) in.DeepCopyInto(out) return out } diff --git a/charts/capsule/crds/capsule.clastix.io_capsuleconfigurations.yaml b/charts/capsule/crds/capsule.clastix.io_capsuleconfigurations.yaml index 8d1f7817..82234d9a 100644 --- a/charts/capsule/crds/capsule.clastix.io_capsuleconfigurations.yaml +++ b/charts/capsule/crds/capsule.clastix.io_capsuleconfigurations.yaml @@ -1250,6 +1250,11 @@ spec: description: CapsuleConfigurationStatus defines the Capsule configuration status. properties: + observedGeneration: + description: ObservedGeneration is the most recent generation the + controller has observed. + format: int64 + type: integer users: description: Users which are considered Capsule Users and are bound to the Capsule Tenant construct. diff --git a/charts/capsule/crds/capsule.clastix.io_customquotas.yaml b/charts/capsule/crds/capsule.clastix.io_customquotas.yaml index 39c49003..3d8b7146 100644 --- a/charts/capsule/crds/capsule.clastix.io_customquotas.yaml +++ b/charts/capsule/crds/capsule.clastix.io_customquotas.yaml @@ -329,6 +329,11 @@ spec: - type type: object type: array + observedGeneration: + description: ObservedGeneration is the most recent generation the + controller has observed. + format: int64 + type: integer targets: description: Targeting GVK items: diff --git a/charts/capsule/crds/capsule.clastix.io_globalcustomquotas.yaml b/charts/capsule/crds/capsule.clastix.io_globalcustomquotas.yaml index cfc7d264..8ee42687 100644 --- a/charts/capsule/crds/capsule.clastix.io_globalcustomquotas.yaml +++ b/charts/capsule/crds/capsule.clastix.io_globalcustomquotas.yaml @@ -384,6 +384,11 @@ spec: items: type: string type: array + observedGeneration: + description: ObservedGeneration is the most recent generation the + controller has observed. + format: int64 + type: integer targets: description: Targeting GVK items: diff --git a/charts/capsule/crds/capsule.clastix.io_globaltenantresources.yaml b/charts/capsule/crds/capsule.clastix.io_globaltenantresources.yaml index 56ff764f..4ed7fb4f 100644 --- a/charts/capsule/crds/capsule.clastix.io_globaltenantresources.yaml +++ b/charts/capsule/crds/capsule.clastix.io_globaltenantresources.yaml @@ -521,6 +521,11 @@ spec: - type type: object type: array + observedGeneration: + description: ObservedGeneration is the most recent generation the + controller has observed. + format: int64 + type: integer processedItems: description: List of the replicated resources for the given TenantResource. items: diff --git a/charts/capsule/crds/capsule.clastix.io_resourcepoolclaims.yaml b/charts/capsule/crds/capsule.clastix.io_resourcepoolclaims.yaml index 5228fa96..e16097f8 100644 --- a/charts/capsule/crds/capsule.clastix.io_resourcepoolclaims.yaml +++ b/charts/capsule/crds/capsule.clastix.io_resourcepoolclaims.yaml @@ -230,6 +230,11 @@ spec: - type type: object type: array + observedGeneration: + description: ObservedGeneration is the most recent generation the + controller has observed. + format: int64 + type: integer pool: description: Reference to the GlobalQuota being claimed from properties: diff --git a/charts/capsule/crds/capsule.clastix.io_resourcepools.yaml b/charts/capsule/crds/capsule.clastix.io_resourcepools.yaml index 5b8b83c5..84f1bbd3 100644 --- a/charts/capsule/crds/capsule.clastix.io_resourcepools.yaml +++ b/charts/capsule/crds/capsule.clastix.io_resourcepools.yaml @@ -389,6 +389,11 @@ spec: items: type: string type: array + observedGeneration: + description: ObservedGeneration is the most recent generation the + controller has observed. + format: int64 + type: integer required: - conditions type: object diff --git a/charts/capsule/crds/capsule.clastix.io_rulestatuses.yaml b/charts/capsule/crds/capsule.clastix.io_rulestatuses.yaml index 24fb073a..6254fdd5 100644 --- a/charts/capsule/crds/capsule.clastix.io_rulestatuses.yaml +++ b/charts/capsule/crds/capsule.clastix.io_rulestatuses.yaml @@ -145,6 +145,11 @@ spec: - type type: object type: array + observedGeneration: + description: ObservedGeneration is the most recent generation the + controller has observed. + format: int64 + type: integer rule: description: Managed Enforcement properties per Namespace (aggregated from rules) diff --git a/charts/capsule/crds/capsule.clastix.io_tenantowners.yaml b/charts/capsule/crds/capsule.clastix.io_tenantowners.yaml index 97447ec6..a4fb5e20 100644 --- a/charts/capsule/crds/capsule.clastix.io_tenantowners.yaml +++ b/charts/capsule/crds/capsule.clastix.io_tenantowners.yaml @@ -74,6 +74,12 @@ spec: type: object status: description: status defines the observed state of TenantOwner. + properties: + observedGeneration: + description: ObservedGeneration is the most recent generation the + controller has observed. + format: int64 + type: integer type: object required: - spec diff --git a/charts/capsule/crds/capsule.clastix.io_tenantresources.yaml b/charts/capsule/crds/capsule.clastix.io_tenantresources.yaml index 188f9551..65d7a745 100644 --- a/charts/capsule/crds/capsule.clastix.io_tenantresources.yaml +++ b/charts/capsule/crds/capsule.clastix.io_tenantresources.yaml @@ -456,6 +456,11 @@ spec: - type type: object type: array + observedGeneration: + description: ObservedGeneration is the most recent generation the + controller has observed. + format: int64 + type: integer processedItems: description: List of the replicated resources for the given TenantResource. items: diff --git a/charts/capsule/crds/capsule.clastix.io_tenants.yaml b/charts/capsule/crds/capsule.clastix.io_tenants.yaml index 62fc6038..791bb5ad 100644 --- a/charts/capsule/crds/capsule.clastix.io_tenants.yaml +++ b/charts/capsule/crds/capsule.clastix.io_tenants.yaml @@ -2949,6 +2949,11 @@ spec: items: type: string type: array + observedGeneration: + description: ObservedGeneration is the most recent generation the + controller has observed. + format: int64 + type: integer owners: description: Collected owners for this tenant items: diff --git a/e2e/observed_generation_test.go b/e2e/observed_generation_test.go new file mode 100644 index 00000000..5bb296f4 --- /dev/null +++ b/e2e/observed_generation_test.go @@ -0,0 +1,249 @@ +// Copyright 2020-2026 Project Capsule Authors +// SPDX-License-Identifier: Apache-2.0 + +package e2e + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + capmeta "github.com/projectcapsule/capsule/pkg/api/meta" + "github.com/projectcapsule/capsule/pkg/api/rbac" +) + +var _ = Describe("observedGeneration is tracked in status", Ordered, Label("observedGeneration"), func() { + var tnt *capsulev1beta2.Tenant + + JustBeforeEach(func() { + tnt = &capsulev1beta2.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "e2e-observed-generation", + }, + Spec: capsulev1beta2.TenantSpec{ + Owners: rbac.OwnerListSpec{ + { + CoreOwnerSpec: rbac.CoreOwnerSpec{ + UserSpec: rbac.UserSpec{ + Name: "e2e-observed-generation-owner", + Kind: "User", + }, + }, + }, + }, + }, + } + + EventuallyCreation(func() error { + return k8sClient.Create(context.TODO(), tnt) + }).Should(Succeed()) + }) + + JustAfterEach(func() { + EventuallyDeletion(tnt) + }) + + It("sets observedGeneration after initial reconciliation", func() { + By("waiting for the tenant to be ready", func() { + TenantReadyTrue(tnt) + }) + + By("verifying observedGeneration equals metadata.generation", func() { + Eventually(func(g Gomega) { + current := &capsulev1beta2.Tenant{} + g.Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt.GetName()}, current)).To(Succeed()) + + g.Expect(current.Status.ObservedGeneration).To( + Equal(current.GetGeneration()), + "expected status.observedGeneration (%d) to equal metadata.generation (%d) after initial reconciliation", + current.Status.ObservedGeneration, + current.GetGeneration(), + ) + }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) + }) + }) + + It("updates observedGeneration after a spec change", func() { + By("waiting for the tenant to be ready at generation 1", func() { + TenantReadyTrue(tnt) + }) + + var genBeforeUpdate int64 + + By("recording the current generation", func() { + current := &capsulev1beta2.Tenant{} + Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt.GetName()}, current)).To(Succeed()) + genBeforeUpdate = current.GetGeneration() + Expect(genBeforeUpdate).To(BeNumerically(">=", int64(1))) + }) + + By("mutating the spec to increment metadata.generation", func() { + UpdateTenantEventually(tnt, func(t *capsulev1beta2.Tenant) { + t.Spec.Cordoned = true + }) + }) + + By("verifying observedGeneration advances to the new generation", func() { + Eventually(func(g Gomega) { + current := &capsulev1beta2.Tenant{} + g.Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: tnt.GetName()}, current)).To(Succeed()) + + g.Expect(current.GetGeneration()).To( + BeNumerically(">", genBeforeUpdate), + "expected metadata.generation to have incremented after spec update", + ) + + g.Expect(current.Status.ObservedGeneration).To( + Equal(current.GetGeneration()), + "expected status.observedGeneration (%d) to match updated metadata.generation (%d)", + current.Status.ObservedGeneration, + current.GetGeneration(), + ) + }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) + }) + }) +}) + +var _ = Describe("CapsuleConfiguration observedGeneration is tracked in status", Serial, Ordered, Label("observedGeneration"), func() { + It("observedGeneration matches metadata.generation for the default CapsuleConfiguration", func() { + Eventually(func(g Gomega) { + cfg := &capsulev1beta2.CapsuleConfiguration{} + g.Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: defaultConfigurationName}, cfg)).To(Succeed()) + + g.Expect(cfg.Status.ObservedGeneration).To( + Equal(cfg.GetGeneration()), + "expected status.observedGeneration (%d) to equal metadata.generation (%d)", + cfg.Status.ObservedGeneration, + cfg.GetGeneration(), + ) + }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) + }) + + It("advances observedGeneration after a CapsuleConfiguration spec change", func() { + var genBefore int64 + + By("recording the current generation", func() { + cfg := &capsulev1beta2.CapsuleConfiguration{} + Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: defaultConfigurationName}, cfg)).To(Succeed()) + genBefore = cfg.GetGeneration() + }) + + By("mutating CapsuleConfiguration to increment metadata.generation", func() { + ModifyCapsuleConfigurationOpts(func(cfg *capsulev1beta2.CapsuleConfiguration) { + cfg.Spec.IgnoreUserWithGroups = append(cfg.Spec.IgnoreUserWithGroups, "e2e-observed-gen-dummy-group") + }) + }) + + DeferCleanup(func() { + ModifyCapsuleConfigurationOpts(func(cfg *capsulev1beta2.CapsuleConfiguration) { + groups := cfg.Spec.IgnoreUserWithGroups + filtered := groups[:0] + for _, g := range groups { + if g != "e2e-observed-gen-dummy-group" { + filtered = append(filtered, g) + } + } + cfg.Spec.IgnoreUserWithGroups = filtered + }) + }) + + By("verifying observedGeneration advances to the new generation", func() { + Eventually(func(g Gomega) { + cfg := &capsulev1beta2.CapsuleConfiguration{} + g.Expect(k8sClient.Get(context.TODO(), types.NamespacedName{Name: defaultConfigurationName}, cfg)).To(Succeed()) + + g.Expect(cfg.GetGeneration()).To( + BeNumerically(">", genBefore), + "expected metadata.generation to increment after spec update", + ) + g.Expect(cfg.Status.ObservedGeneration).To( + Equal(cfg.GetGeneration()), + "expected status.observedGeneration (%d) to match updated metadata.generation (%d)", + cfg.Status.ObservedGeneration, + cfg.GetGeneration(), + ) + }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) + }) + }) +}) + +var _ = Describe("RuleStatus observedGeneration is tracked in status", Ordered, Label("observedGeneration"), func() { + ctx := context.TODO() + + var ( + tnt *capsulev1beta2.Tenant + ns *corev1.Namespace + ) + + JustBeforeEach(func() { + tnt = &capsulev1beta2.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "e2e-rulestatus-observed-gen", + }, + Spec: capsulev1beta2.TenantSpec{ + Owners: rbac.OwnerListSpec{ + { + CoreOwnerSpec: rbac.CoreOwnerSpec{ + UserSpec: rbac.UserSpec{ + Name: "e2e-rulestatus-observed-gen-owner", + Kind: "User", + }, + }, + }, + }, + }, + } + + EventuallyCreation(func() error { + return k8sClient.Create(ctx, tnt) + }).Should(Succeed()) + + TenantReadyTrue(tnt) + + ns = NewNamespace("e2e-rulestatus-observed-gen-ns", map[string]string{ + capmeta.TenantLabel: tnt.GetName(), + }) + + NamespaceCreation(ns, tnt.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) + + NamespaceIsPartOfTenant(tnt, ns).Should(Succeed()) + }) + + JustAfterEach(func() { + if ns != nil { + _ = k8sClient.Delete(ctx, ns) + } + + EventuallyDeletion(tnt) + }) + + It("sets observedGeneration on RuleStatus after reconciliation", func() { + By("waiting for RuleStatus to exist and have observedGeneration set", func() { + Eventually(func(g Gomega) { + ruleStatus := &capsulev1beta2.RuleStatus{} + g.Expect(k8sClient.Get(ctx, client.ObjectKey{ + Name: capmeta.NameForManagedRuleStatus(), + Namespace: ns.Name, + }, ruleStatus)).To(Succeed()) + + g.Expect(ruleStatus.Status.ObservedGeneration).To( + BeNumerically(">", int64(0)), + "expected status.observedGeneration to be non-zero", + ) + + g.Expect(ruleStatus.Status.ObservedGeneration).To( + Equal(ruleStatus.GetGeneration()), + "expected status.observedGeneration (%d) to equal metadata.generation (%d)", + ruleStatus.Status.ObservedGeneration, + ruleStatus.GetGeneration(), + ) + }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) + }) + }) +}) diff --git a/e2e/utils_test.go b/e2e/utils_test.go index e7d04d15..9463967e 100644 --- a/e2e/utils_test.go +++ b/e2e/utils_test.go @@ -1086,6 +1086,14 @@ func TenantReady( current.Status.Owners, ) } + + g.Expect(current.Status.ObservedGeneration).To( + Equal(current.GetGeneration()), + "expected Tenant %q status.observedGeneration (%d) to equal metadata.generation (%d)", + tnt.GetName(), + current.Status.ObservedGeneration, + current.GetGeneration(), + ) }, timeoutInterval, defaultPollInterval).Should(Succeed()) } diff --git a/internal/controllers/cfg/status/manager.go b/internal/controllers/cfg/status/manager.go index 5bd1a289..f556a76b 100644 --- a/internal/controllers/cfg/status/manager.go +++ b/internal/controllers/cfg/status/manager.go @@ -194,6 +194,7 @@ func (r *Manager) updateConfigStatus( } latest.Status = instance.Status + latest.Status.ObservedGeneration = instance.GetGeneration() return r.Client.Status().Update(ctx, latest) }) diff --git a/internal/controllers/customquotas/custom_quota_controller.go b/internal/controllers/customquotas/custom_quota_controller.go index 4363135c..328e0724 100644 --- a/internal/controllers/customquotas/custom_quota_controller.go +++ b/internal/controllers/customquotas/custom_quota_controller.go @@ -298,6 +298,7 @@ func (r *customQuotaClaimController) updateStatus( } latest.Status = instance.Status + latest.Status.ObservedGeneration = instance.GetGeneration() // Set Ready Condition readyCondition := meta.NewReadyCondition(instance) diff --git a/internal/controllers/customquotas/global_custom_quota_controller.go b/internal/controllers/customquotas/global_custom_quota_controller.go index d3bf46b5..8bba8928 100644 --- a/internal/controllers/customquotas/global_custom_quota_controller.go +++ b/internal/controllers/customquotas/global_custom_quota_controller.go @@ -390,6 +390,7 @@ func (r *clusterCustomQuotaClaimController) updateStatus( } latest.Status = instance.Status + latest.Status.ObservedGeneration = instance.GetGeneration() // Set Ready Condition readyCondition := meta.NewReadyCondition(instance) diff --git a/internal/controllers/resourcepools/claim_controller.go b/internal/controllers/resourcepools/claim_controller.go index e7134b9e..7f0cb451 100644 --- a/internal/controllers/resourcepools/claim_controller.go +++ b/internal/controllers/resourcepools/claim_controller.go @@ -281,6 +281,7 @@ func (r *resourceClaimController) updateStatus( } latest.Status = instance.Status + latest.Status.ObservedGeneration = instance.GetGeneration() readyCondition := meta.NewReadyCondition(latest) if reconcileError != nil { diff --git a/internal/controllers/resourcepools/pool_controller.go b/internal/controllers/resourcepools/pool_controller.go index 1d52d04d..56209476 100644 --- a/internal/controllers/resourcepools/pool_controller.go +++ b/internal/controllers/resourcepools/pool_controller.go @@ -912,6 +912,7 @@ func (r *resourcePoolController) updateStatus(ctx context.Context, instance *cap } latest.Status = instance.Status + latest.Status.ObservedGeneration = instance.GetGeneration() // Set Ready Condition readyCondition := meta.NewReadyCondition(instance) diff --git a/internal/controllers/resources/global.go b/internal/controllers/resources/global.go index 0b046211..dd7958bb 100644 --- a/internal/controllers/resources/global.go +++ b/internal/controllers/resources/global.go @@ -644,6 +644,7 @@ func (r *globalResourceController) updateStatus(ctx context.Context, instance *c } latest.Status = instance.Status + latest.Status.ObservedGeneration = instance.GetGeneration() // Set Ready Condition readyCondition := meta.NewReadyCondition(instance) diff --git a/internal/controllers/resources/namespaced.go b/internal/controllers/resources/namespaced.go index 30bd4dda..8d3e3e9d 100644 --- a/internal/controllers/resources/namespaced.go +++ b/internal/controllers/resources/namespaced.go @@ -648,6 +648,7 @@ func (r *namespacedResourceController) updateStatus(ctx context.Context, instanc } latest.Status = instance.Status + latest.Status.ObservedGeneration = instance.GetGeneration() // Set Ready Condition readyCondition := meta.NewReadyCondition(instance) diff --git a/internal/controllers/rulestatus/manager.go b/internal/controllers/rulestatus/manager.go index 0a13962a..9aa58a63 100644 --- a/internal/controllers/rulestatus/manager.go +++ b/internal/controllers/rulestatus/manager.go @@ -162,6 +162,7 @@ func (r *Manager) updateStatus(ctx context.Context, instance *capsulev1beta2.Rul } latest.Status = instance.Status + latest.Status.ObservedGeneration = instance.GetGeneration() // Set Ready Condition readyCondition := meta.NewReadyCondition(instance) diff --git a/internal/controllers/tenant/status.go b/internal/controllers/tenant/status.go index 967d4c08..51500b0a 100644 --- a/internal/controllers/tenant/status.go +++ b/internal/controllers/tenant/status.go @@ -56,6 +56,7 @@ func (r *Manager) updateTenantStatus(ctx context.Context, instance *capsulev1bet } latest.Status = instance.Status + latest.Status.ObservedGeneration = instance.GetGeneration() setTenantStatusState(latest) readyCondition := capmeta.NewReadyCondition(instance)