Skip to content
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

base reconciler reconciles status #752

Merged
merged 2 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 29 additions & 18 deletions api/v1beta2/ratelimitpolicy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@

"github.com/go-logr/logr"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1"
gatewayapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"

kuadrantgatewayapi "github.com/kuadrant/kuadrant-operator/pkg/library/gatewayapi"
"github.com/kuadrant/kuadrant-operator/pkg/library/kuadrant"
"github.com/kuadrant/kuadrant-operator/pkg/library/reconcilers"
"github.com/kuadrant/kuadrant-operator/pkg/library/utils"
)

Expand Down Expand Up @@ -157,9 +159,7 @@

// RateLimitPolicyStatus defines the observed state of RateLimitPolicy
type RateLimitPolicyStatus struct {
// ObservedGeneration reflects the generation of the most recently observed spec.
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
reconcilers.StatusMeta `json:",inline"`

// Represents the observations of a foo's current state.
// Known .status.conditions.type are: "Available"
Expand All @@ -170,25 +170,33 @@
Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"`
}

func (s *RateLimitPolicyStatus) Equals(other *RateLimitPolicyStatus, logger logr.Logger) bool {
if s.ObservedGeneration != other.ObservedGeneration {
diff := cmp.Diff(s.ObservedGeneration, other.ObservedGeneration)
logger.V(1).Info("ObservedGeneration not equal", "difference", diff)
return false
}
func RateLimitPolicyStatusMutator(desiredStatus *RateLimitPolicyStatus, logger logr.Logger) reconcilers.StatusMutatorFunc {
return func(obj client.Object) (bool, error) {
existingRLP, ok := obj.(*RateLimitPolicy)
if !ok {
return false, fmt.Errorf("unsupported object type %T", obj)

Check warning on line 177 in api/v1beta2/ratelimitpolicy_types.go

View check run for this annotation

Codecov / codecov/patch

api/v1beta2/ratelimitpolicy_types.go#L177

Added line #L177 was not covered by tests
}

opts := cmp.Options{
cmpopts.IgnoreFields(metav1.Condition{}, "LastTransitionTime"),
cmpopts.IgnoreMapEntries(func(k string, _ any) bool {
return k == "lastTransitionTime"
}),

Check warning on line 184 in api/v1beta2/ratelimitpolicy_types.go

View check run for this annotation

Codecov / codecov/patch

api/v1beta2/ratelimitpolicy_types.go#L183-L184

Added lines #L183 - L184 were not covered by tests
}

if cmp.Equal(*desiredStatus, existingRLP.Status, opts) {
return false, nil
}

// Marshalling sorts by condition type
currentMarshaledJSON, _ := kuadrant.ConditionMarshal(s.Conditions)
otherMarshaledJSON, _ := kuadrant.ConditionMarshal(other.Conditions)
if string(currentMarshaledJSON) != string(otherMarshaledJSON) {
if logger.V(1).Enabled() {
diff := cmp.Diff(string(currentMarshaledJSON), string(otherMarshaledJSON))
logger.V(1).Info("Conditions not equal", "difference", diff)
diff := cmp.Diff(*desiredStatus, existingRLP.Status, opts)
logger.V(1).Info("status not equal", "difference", diff)
}
return false
}

return true
existingRLP.Status = *desiredStatus

return true, nil
}
}

func (s *RateLimitPolicyStatus) GetConditions() []metav1.Condition {
Expand Down Expand Up @@ -226,6 +234,9 @@
return nil
}

func (r *RateLimitPolicy) GetObservedGeneration() int64 { return r.Status.GetObservedGeneration() }
func (r *RateLimitPolicy) SetObservedGeneration(o int64) { r.Status.SetObservedGeneration(o) }

Check warning on line 238 in api/v1beta2/ratelimitpolicy_types.go

View check run for this annotation

Codecov / codecov/patch

api/v1beta2/ratelimitpolicy_types.go#L237-L238

Added lines #L237 - L238 were not covered by tests

//+kubebuilder:object:root=true

// RateLimitPolicyList contains a list of RateLimitPolicy
Expand Down
1 change: 1 addition & 0 deletions api/v1beta2/zz_generated.deepcopy.go

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

42 changes: 14 additions & 28 deletions controllers/ratelimitpolicy_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

import (
"context"
"fmt"
"slices"

"github.com/go-logr/logr"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
gatewayapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"

Expand All @@ -19,46 +19,32 @@
func (r *RateLimitPolicyReconciler) reconcileStatus(ctx context.Context, rlp *kuadrantv1beta2.RateLimitPolicy, specErr error) (ctrl.Result, error) {
logger, _ := logr.FromContext(ctx)
newStatus := r.calculateStatus(rlp, specErr)

equalStatus := rlp.Status.Equals(newStatus, logger)
logger.V(1).Info("Status", "status is different", !equalStatus)
logger.V(1).Info("Status", "generation is different", rlp.Generation != rlp.Status.ObservedGeneration)
if equalStatus && rlp.Generation == rlp.Status.ObservedGeneration {
// Steady state
logger.V(1).Info("Status was not updated")
return reconcile.Result{}, nil
}

// Save the generation number we acted on, otherwise we might wrongfully indicate
// that we've seen a spec update when we retry.
// TODO: This can clobber an update if we allow multiple agents to write to the
// same status.
newStatus.ObservedGeneration = rlp.Generation

logger.V(1).Info("Updating Status", "sequence no:", fmt.Sprintf("sequence No: %v->%v", rlp.Status.ObservedGeneration, newStatus.ObservedGeneration))

rlp.Status = *newStatus
updateErr := r.Client().Status().Update(ctx, rlp)
logger.V(1).Info("Updating Status", "err", updateErr)
if updateErr != nil {
if err := r.ReconcileResourceStatus(
ctx,
client.ObjectKeyFromObject(rlp),
&kuadrantv1beta2.RateLimitPolicy{},
kuadrantv1beta2.RateLimitPolicyStatusMutator(newStatus, logger),
); err != nil {
// Ignore conflicts, resource might just be outdated.
if apierrors.IsConflict(updateErr) {
logger.Info("Failed to update status: resource might just be outdated")
if apierrors.IsConflict(err) {
logger.V(1).Info("Failed to update status: resource might just be outdated")
return reconcile.Result{Requeue: true}, nil
}

return reconcile.Result{}, fmt.Errorf("failed to update status: %w", updateErr)
return ctrl.Result{}, err

Check warning on line 34 in controllers/ratelimitpolicy_status.go

View check run for this annotation

Codecov / codecov/patch

controllers/ratelimitpolicy_status.go#L34

Added line #L34 was not covered by tests
}

return ctrl.Result{}, nil
}

func (r *RateLimitPolicyReconciler) calculateStatus(rlp *kuadrantv1beta2.RateLimitPolicy, specErr error) *kuadrantv1beta2.RateLimitPolicyStatus {
newStatus := &kuadrantv1beta2.RateLimitPolicyStatus{
// Copy initial conditions. Otherwise, status will always be updated
Conditions: slices.Clone(rlp.Status.Conditions),
ObservedGeneration: rlp.Status.ObservedGeneration,
Conditions: slices.Clone(rlp.Status.Conditions),
}

newStatus.SetObservedGeneration(rlp.GetGeneration())

acceptedCond := kuadrant.AcceptedCondition(rlp, specErr)

meta.SetStatusCondition(&newStatus.Conditions, *acceptedCond)
Expand Down
56 changes: 56 additions & 0 deletions pkg/library/reconcilers/base_reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,32 @@
"github.com/kuadrant/kuadrant-operator/pkg/library/utils"
)

type StatusMeta struct {
// ObservedGeneration reflects the generation of the most recently observed spec.
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
}

func (meta *StatusMeta) GetObservedGeneration() int64 { return meta.ObservedGeneration }

Check warning on line 43 in pkg/library/reconcilers/base_reconciler.go

View check run for this annotation

Codecov / codecov/patch

pkg/library/reconcilers/base_reconciler.go#L43

Added line #L43 was not covered by tests
func (meta *StatusMeta) SetObservedGeneration(o int64) { meta.ObservedGeneration = o }

// StatusMutator is an interface to hold mutator functions for status updates.
type StatusMutator interface {
Mutate(obj client.Object) (bool, error)
}

// StatusMutatorFunc is a function adaptor for StatusMutators.
type StatusMutatorFunc func(client.Object) (bool, error)

// Mutate adapts the MutatorFunc to fit through the StatusMutator interface.
func (s StatusMutatorFunc) Mutate(o client.Object) (bool, error) {
if s == nil {
return false, nil

Check warning on line 57 in pkg/library/reconcilers/base_reconciler.go

View check run for this annotation

Codecov / codecov/patch

pkg/library/reconcilers/base_reconciler.go#L57

Added line #L57 was not covered by tests
}

return s(o)
}

// MutateFn is a function which mutates the existing object into it's desired state.
type MutateFn func(existing, desired client.Object) (bool, error)

Expand Down Expand Up @@ -143,6 +169,36 @@
return nil
}

func (b *BaseReconciler) ReconcileResourceStatus(ctx context.Context, objKey client.ObjectKey, obj client.Object, mutator StatusMutator) error {
logger, err := logr.FromContext(ctx)
if err != nil {
return err

Check warning on line 175 in pkg/library/reconcilers/base_reconciler.go

View check run for this annotation

Codecov / codecov/patch

pkg/library/reconcilers/base_reconciler.go#L175

Added line #L175 was not covered by tests
}

if err := b.Client().Get(ctx, objKey, obj); err != nil {
return err

Check warning on line 179 in pkg/library/reconcilers/base_reconciler.go

View check run for this annotation

Codecov / codecov/patch

pkg/library/reconcilers/base_reconciler.go#L179

Added line #L179 was not covered by tests
}

update, err := mutator.Mutate(obj)
if err != nil {
return err

Check warning on line 184 in pkg/library/reconcilers/base_reconciler.go

View check run for this annotation

Codecov / codecov/patch

pkg/library/reconcilers/base_reconciler.go#L184

Added line #L184 was not covered by tests
}

if !update {
// Steady state, early return 🎉
logger.V(1).Info("status was not updated")
return nil
}

updateErr := b.Client().Status().Update(ctx, obj)
logger.V(1).Info("updating status", "err", updateErr)
if updateErr != nil {
return updateErr
}

return nil
}

func (b *BaseReconciler) GetResource(ctx context.Context, objKey types.NamespacedName, obj client.Object) error {
logger, _ := logr.FromContext(ctx)
logger.Info("get object", "kind", strings.Replace(fmt.Sprintf("%T", obj), "*", "", 1), "name", objKey.Name, "namespace", objKey.Namespace)
Expand Down
Loading