Skip to content

Commit

Permalink
refactor: target status -> discoverability reconciler (#958)
Browse files Browse the repository at this point in the history
* refactor: initial target status -> gateway policy discoverability reconciler

Signed-off-by: KevFan <[email protected]>

* refactor: initial target status -> http route policy discoverability reconciler

Signed-off-by: KevFan <[email protected]>

* feat: policyAffectedBy condition is ListenerStatus

Signed-off-by: KevFan <[email protected]>

* refactor: list all policies in httproute affected by condition

Signed-off-by: KevFan <[email protected]>

* refactor: smaller function for gateway discoverability

Signed-off-by: KevFan <[email protected]>

* refactor: breakdown to methods/functions + integration tests

Signed-off-by: KevFan <[email protected]>

* refactor: methods/functions

Signed-off-by: KevFan <[email protected]>

* tests: additional tests for auth, dns and tls

Signed-off-by: KevFan <[email protected]>

* gomod: tidy

Signed-off-by: KevFan <[email protected]>

---------

Signed-off-by: KevFan <[email protected]>
  • Loading branch information
KevFan authored Nov 5, 2024
1 parent a3579b1 commit 87a5613
Show file tree
Hide file tree
Showing 9 changed files with 762 additions and 491 deletions.
62 changes: 61 additions & 1 deletion controllers/common.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@
package controllers

import (
"context"
"fmt"
"sync"

"github.com/kuadrant/policy-machinery/machinery"
"github.com/samber/lo"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/client"
gatewayapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"

kuadrantv1alpha1 "github.com/kuadrant/kuadrant-operator/api/v1alpha1"
kuadrantv1beta3 "github.com/kuadrant/kuadrant-operator/api/v1beta3"
)

const (
KuadrantAppName = "kuadrant"
KuadrantAppName = "kuadrant"
PolicyAffectedConditionPattern = "kuadrant.io/%sAffected" // Policy kinds are expected to be named XPolicy
)

var (
Expand All @@ -19,3 +36,46 @@ func CommonLabels() map[string]string {
"app.kubernetes.io/part-of": KuadrantAppName,
}
}

func PolicyAffectedCondition(policyKind string, policies []machinery.Policy) metav1.Condition {
condition := metav1.Condition{
Type: PolicyAffectedConditionType(policyKind),
Status: metav1.ConditionTrue,
Reason: string(gatewayapiv1alpha2.PolicyReasonAccepted),
Message: fmt.Sprintf("Object affected by %s %s", policyKind, lo.Map(policies, func(item machinery.Policy, index int) client.ObjectKey {
return client.ObjectKey{Name: item.GetName(), Namespace: item.GetNamespace()}
})),
}

return condition
}

func PolicyAffectedConditionType(policyKind string) string {
return fmt.Sprintf(PolicyAffectedConditionPattern, policyKind)
}

func IsPolicyAccepted(ctx context.Context, p machinery.Policy, s *sync.Map) bool {
switch t := p.(type) {
case *kuadrantv1beta3.AuthPolicy:
return isAuthPolicyAcceptedFunc(s)(p)
case *kuadrantv1beta3.RateLimitPolicy:
return isRateLimitPolicyAcceptedFunc(s)(p)
case *kuadrantv1alpha1.TLSPolicy:
isValid, _ := IsTLSPolicyValid(ctx, s, t)
return isValid
case *kuadrantv1alpha1.DNSPolicy:
isValid, _ := dnsPolicyAcceptedStatusFunc(s)(p)
return isValid
default:
return false
}
}

func policyGroupKinds() []*schema.GroupKind {
return []*schema.GroupKind{
&kuadrantv1beta3.AuthPolicyGroupKind,
&kuadrantv1beta3.RateLimitPolicyGroupKind,
&kuadrantv1alpha1.TLSPolicyGroupKind,
&kuadrantv1alpha1.DNSPolicyGroupKind,
}
}
153 changes: 152 additions & 1 deletion controllers/gateway_policy_discoverability_reconciler.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
package controllers

import (
"context"
"sync"

"github.com/go-logr/logr"
"github.com/kuadrant/policy-machinery/controller"
"github.com/kuadrant/policy-machinery/machinery"
"github.com/samber/lo"
"k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1"

kuadrantv1 "github.com/kuadrant/kuadrant-operator/api/v1"
kuadrantv1alpha1 "github.com/kuadrant/kuadrant-operator/api/v1alpha1"
kuadrantv1beta3 "github.com/kuadrant/kuadrant-operator/api/v1beta3"
)

type GatewayPolicyDiscoverabilityReconciler struct {
Expand All @@ -14,5 +29,141 @@ func NewGatewayPolicyDiscoverabilityReconciler(client *dynamic.DynamicClient) *G
}

func (r *GatewayPolicyDiscoverabilityReconciler) Subscription() *controller.Subscription {
return &controller.Subscription{}
return &controller.Subscription{
Events: []controller.ResourceEventMatcher{
{Kind: &machinery.GatewayGroupKind},
{Kind: &kuadrantv1beta3.AuthPolicyGroupKind},
{Kind: &kuadrantv1beta3.RateLimitPolicyGroupKind},
{Kind: &kuadrantv1alpha1.TLSPolicyGroupKind},
{Kind: &kuadrantv1alpha1.DNSPolicyGroupKind},
},
ReconcileFunc: r.reconcile,
}
}

func (r *GatewayPolicyDiscoverabilityReconciler) reconcile(ctx context.Context, _ []controller.ResourceEvent, topology *machinery.Topology, _ error, syncMap *sync.Map) error {
logger := controller.LoggerFromContext(ctx).WithName("GatewayPolicyDiscoverabilityReconciler").WithName("reconcile")

gateways := lo.FilterMap(topology.Targetables().Items(), func(item machinery.Targetable, _ int) (*machinery.Gateway, bool) {
gw, ok := item.(*machinery.Gateway)
return gw, ok
})
policyKinds := policyGroupKinds()

for _, gw := range gateways {
updatedStatus := buildGatewayStatus(ctx, syncMap, gw, topology, logger, policyKinds)
if !equality.Semantic.DeepEqual(updatedStatus, gw.Status) {
gw.Status = *updatedStatus
if err := r.updateGatewayStatus(ctx, gw); err != nil {
logger.Error(err, "failed to update gateway status", "gateway", gw.GetName())
}
}
}

return nil
}

func (r *GatewayPolicyDiscoverabilityReconciler) updateGatewayStatus(ctx context.Context, gw *machinery.Gateway) error {
obj, err := controller.Destruct(gw.Gateway)
if err != nil {
return err
}
_, err = r.Client.Resource(controller.GatewaysResource).Namespace(gw.GetNamespace()).UpdateStatus(ctx, obj, metav1.UpdateOptions{})
return err
}

func buildGatewayStatus(ctx context.Context, syncMap *sync.Map, gw *machinery.Gateway, topology *machinery.Topology, logger logr.Logger, policyKinds []*schema.GroupKind) *gatewayapiv1.GatewayStatus {
status := gw.Status.DeepCopy()

listeners := lo.Map(topology.Targetables().Children(gw), func(item machinery.Targetable, _ int) *machinery.Listener {
listener, _ := item.(*machinery.Listener)
return listener
})

for _, listener := range listeners {
updatedListenerStatus := updateListenerStatus(ctx, syncMap, gw, listener, logger, policyKinds)
status.Listeners = updateListenerList(status.Listeners, updatedListenerStatus)
}

for _, policyKind := range policyKinds {
updatePolicyConditions(ctx, syncMap, gw, policyKind, status, logger)
}

return status
}

func updateListenerStatus(ctx context.Context, syncMap *sync.Map, gw *machinery.Gateway, listener *machinery.Listener, logger logr.Logger, policyKinds []*schema.GroupKind) gatewayapiv1.ListenerStatus {
status, _, exists := findListenerStatus(gw.Status.Listeners, listener.Name)
if !exists {
status = gatewayapiv1.ListenerStatus{Name: listener.Name, Conditions: []metav1.Condition{}}
}

for _, kind := range policyKinds {
conditionType := PolicyAffectedConditionType(kind.Kind)
policies := extractAcceptedPolicies(ctx, syncMap, kind, gw, listener)

if len(policies) == 0 {
removeConditionIfExists(&status.Conditions, conditionType, logger, listener.GetName())
} else {
addOrUpdateCondition(&status.Conditions, PolicyAffectedCondition(kind.Kind, policies), gw.GetGeneration(), logger)
}
}

return status
}

func updatePolicyConditions(ctx context.Context, syncMap *sync.Map, gw *machinery.Gateway, policyKind *schema.GroupKind, status *gatewayapiv1.GatewayStatus, logger logr.Logger) {
conditionType := PolicyAffectedConditionType(policyKind.Kind)
policies := extractAcceptedPolicies(ctx, syncMap, policyKind, gw)

if len(policies) == 0 {
removeConditionIfExists(&status.Conditions, conditionType, logger, gw.GetName())
} else {
addOrUpdateCondition(&status.Conditions, PolicyAffectedCondition(policyKind.Kind, policies), gw.GetGeneration(), logger)
}
}

func addOrUpdateCondition(conditions *[]metav1.Condition, condition metav1.Condition, generation int64, logger logr.Logger) {
existingCondition := meta.FindStatusCondition(*conditions, condition.Type)
if existingCondition != nil && equality.Semantic.DeepEqual(*existingCondition, condition) {
logger.V(1).Info("condition unchanged", "condition", condition.Type)
return
}

condition.ObservedGeneration = generation
meta.SetStatusCondition(conditions, condition)
logger.V(1).Info("updated condition", "condition", condition.Type)
}

func removeConditionIfExists(conditions *[]metav1.Condition, conditionType string, logger logr.Logger, name string) {
if meta.RemoveStatusCondition(conditions, conditionType) {
logger.V(1).Info("removed condition", "condition", conditionType, "name", name)
} else {
logger.V(1).Info("condition absent, skipping removal", "condition", conditionType, "name", name)
}
}

func updateListenerList(listeners []gatewayapiv1.ListenerStatus, updatedStatus gatewayapiv1.ListenerStatus) []gatewayapiv1.ListenerStatus {
_, index, exists := findListenerStatus(listeners, updatedStatus.Name)
if exists {
listeners[index] = updatedStatus
} else {
listeners = append(listeners, updatedStatus)
}
return listeners
}

func findListenerStatus(listeners []gatewayapiv1.ListenerStatus, name gatewayapiv1.SectionName) (gatewayapiv1.ListenerStatus, int, bool) {
for i, status := range listeners {
if status.Name == name {
return status, i, true
}
}
return gatewayapiv1.ListenerStatus{}, -1, false
}

func extractAcceptedPolicies(ctx context.Context, syncMap *sync.Map, policyKind *schema.GroupKind, targets ...machinery.Targetable) []machinery.Policy {
return kuadrantv1.PoliciesInPath(targets, func(policy machinery.Policy) bool {
return policy.GroupVersionKind().GroupKind() == *policyKind && IsPolicyAccepted(ctx, policy, syncMap) // Use enforced policies instead?
})
}
Loading

0 comments on commit 87a5613

Please sign in to comment.