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

Envoygateway wasm controller #848

Merged
merged 3 commits into from
Sep 12, 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
14 changes: 13 additions & 1 deletion bundle/manifests/kuadrant-operator.clusterserviceversion.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ metadata:
capabilities: Basic Install
categories: Integration & Delivery
containerImage: quay.io/kuadrant/kuadrant-operator:latest
createdAt: "2024-08-20T09:51:49Z"
createdAt: "2024-09-05T18:00:43Z"
operators.operatorframework.io/builder: operator-sdk-v1.32.0
operators.operatorframework.io/project_layout: go.kubebuilder.io/v3
repository: https://github.com/Kuadrant/kuadrant-operator
Expand Down Expand Up @@ -294,6 +294,18 @@ spec:
- patch
- update
- watch
- apiGroups:
- gateway.envoyproxy.io
resources:
- envoypatchpolicies
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- gateway.envoyproxy.io
resources:
Expand Down
12 changes: 12 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,18 @@ rules:
- patch
- update
- watch
- apiGroups:
- gateway.envoyproxy.io
resources:
- envoypatchpolicies
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- gateway.envoyproxy.io
resources:
Expand Down
224 changes: 224 additions & 0 deletions controllers/envoygateway_limitador_cluster_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
package controllers

import (
"context"
"encoding/json"
"fmt"

egv1alpha1 "github.com/envoyproxy/gateway/api/v1alpha1"
"github.com/go-logr/logr"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"

kuadrantv1beta1 "github.com/kuadrant/kuadrant-operator/api/v1beta1"
"github.com/kuadrant/kuadrant-operator/pkg/common"
kuadrantenvoygateway "github.com/kuadrant/kuadrant-operator/pkg/envoygateway"
kuadrantgatewayapi "github.com/kuadrant/kuadrant-operator/pkg/library/gatewayapi"
"github.com/kuadrant/kuadrant-operator/pkg/library/reconcilers"
limitadorv1alpha1 "github.com/kuadrant/limitador-operator/api/v1alpha1"
)

// EnvoyGatewayLimitadorClusterReconciler reconciles an EnvoyGateway EnvoyPatchPolicy object
// to setup limitador's cluster on the gateway. It is a requirement for the wasm module to work.
// https://gateway.envoyproxy.io/latest/api/extension_types/#envoypatchpolicy
type EnvoyGatewayLimitadorClusterReconciler struct {
*reconcilers.BaseReconciler
}

//+kubebuilder:rbac:groups=gateway.envoyproxy.io,resources=envoypatchpolicies,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=gateway.envoyproxy.io,resources=envoyextensionpolicies,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=gateways,verbs=get;list;watch;update;patch

// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.10.0/pkg/reconcile
func (r *EnvoyGatewayLimitadorClusterReconciler) Reconcile(eventCtx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := r.Logger().WithValues("envoyExtensionPolicy", req.NamespacedName)
logger.V(1).Info("Reconciling limitador cluster")
ctx := logr.NewContext(eventCtx, logger)

extPolicy := &egv1alpha1.EnvoyExtensionPolicy{}
if err := r.Client().Get(ctx, req.NamespacedName, extPolicy); err != nil {
if apierrors.IsNotFound(err) {
logger.Info("no envoygateway extension policy object found")
return ctrl.Result{}, nil
}
logger.Error(err, "failed to get envoygateway extension policy object")
return ctrl.Result{}, err

Check warning on line 50 in controllers/envoygateway_limitador_cluster_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoygateway_limitador_cluster_controller.go#L49-L50

Added lines #L49 - L50 were not covered by tests
}

if logger.V(1).Enabled() {
jsonData, err := json.MarshalIndent(extPolicy.Spec.PolicyTargetReferences, "", " ")
if err != nil {
return ctrl.Result{}, err

Check warning on line 56 in controllers/envoygateway_limitador_cluster_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoygateway_limitador_cluster_controller.go#L56

Added line #L56 was not covered by tests
}
logger.V(1).Info(string(jsonData))
}

if extPolicy.DeletionTimestamp != nil {
// no need to handle deletion
// ownerrefs will do the job
return ctrl.Result{}, nil

Check warning on line 64 in controllers/envoygateway_limitador_cluster_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoygateway_limitador_cluster_controller.go#L64

Added line #L64 was not covered by tests
}

//
// Get kuadrant
//
kuadrantList := &kuadrantv1beta1.KuadrantList{}
err := r.Client().List(ctx, kuadrantList)
if err != nil {
return ctrl.Result{}, err

Check warning on line 73 in controllers/envoygateway_limitador_cluster_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoygateway_limitador_cluster_controller.go#L73

Added line #L73 was not covered by tests
}
if len(kuadrantList.Items) == 0 {
logger.Info("kuadrant object not found. Nothing to do")
return ctrl.Result{}, nil

Check warning on line 77 in controllers/envoygateway_limitador_cluster_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoygateway_limitador_cluster_controller.go#L76-L77

Added lines #L76 - L77 were not covered by tests
}

kObj := kuadrantList.Items[0]

//
// Get limitador
//
limitadorKey := client.ObjectKey{Name: common.LimitadorName, Namespace: kObj.Namespace}
limitador := &limitadorv1alpha1.Limitador{}
err = r.Client().Get(ctx, limitadorKey, limitador)
logger.V(1).Info("read limitador", "key", limitadorKey, "err", err)
if err != nil {
if apierrors.IsNotFound(err) {
logger.Info("limitador object not found. Nothing to do")
return ctrl.Result{}, nil

Check warning on line 92 in controllers/envoygateway_limitador_cluster_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoygateway_limitador_cluster_controller.go#L90-L92

Added lines #L90 - L92 were not covered by tests
}
return ctrl.Result{}, err

Check warning on line 94 in controllers/envoygateway_limitador_cluster_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoygateway_limitador_cluster_controller.go#L94

Added line #L94 was not covered by tests
}

if !meta.IsStatusConditionTrue(limitador.Status.Conditions, "Ready") {
logger.Info("limitador status reports not ready. Retrying")
return ctrl.Result{Requeue: true}, nil
}

limitadorClusterPatchPolicy, err := r.desiredLimitadorClusterPatchPolicy(ctx, extPolicy, limitador)
if err != nil {
return ctrl.Result{}, err

Check warning on line 104 in controllers/envoygateway_limitador_cluster_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoygateway_limitador_cluster_controller.go#L104

Added line #L104 was not covered by tests
}
err = r.ReconcileResource(ctx, &egv1alpha1.EnvoyPatchPolicy{}, limitadorClusterPatchPolicy, reconcilers.CreateOnlyMutator)
if err != nil {
return ctrl.Result{}, err
}

logger.V(1).Info("Envoygateway limitador cluster reconciled successfully")

return ctrl.Result{}, nil
}

func (r *EnvoyGatewayLimitadorClusterReconciler) desiredLimitadorClusterPatchPolicy(
ctx context.Context, extPolicy *egv1alpha1.EnvoyExtensionPolicy,
limitador *limitadorv1alpha1.Limitador) (*egv1alpha1.EnvoyPatchPolicy, error) {

patchPolicy := &egv1alpha1.EnvoyPatchPolicy{
TypeMeta: metav1.TypeMeta{
Kind: egv1alpha1.KindEnvoyPatchPolicy,
APIVersion: egv1alpha1.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: LimitadorClusterEnvoyPatchPolicyName(extPolicy.GetName()),
Namespace: extPolicy.Namespace,
},
Spec: egv1alpha1.EnvoyPatchPolicySpec{
// Same target ref as the associated extension policy
TargetRef: extPolicy.Spec.PolicyTargetReferences.TargetRefs[0].LocalPolicyTargetReference,
Type: egv1alpha1.JSONPatchEnvoyPatchType,
JSONPatches: []egv1alpha1.EnvoyJSONPatchConfig{
limitadorClusterPatch(
limitador.Status.Service.Host,
int(limitador.Status.Service.Ports.GRPC),
),
},
},
}

// controller reference
// patchPolicy has ownerref to extension policy
if err := r.SetOwnerReference(extPolicy, patchPolicy); err != nil {
return nil, err

Check warning on line 145 in controllers/envoygateway_limitador_cluster_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoygateway_limitador_cluster_controller.go#L145

Added line #L145 was not covered by tests
}

return patchPolicy, nil
}

func LimitadorClusterEnvoyPatchPolicyName(targetName string) string {
return fmt.Sprintf("patch-for-%s", targetName)
}

func limitadorClusterPatch(limitadorSvcHost string, limitadorGRPCPort int) egv1alpha1.EnvoyJSONPatchConfig {
// The patch defines the rate_limit_cluster, which provides the endpoint location of the external rate limit service.
// TODO(eguzki): Istio EnvoyFilter uses almost the same structure. DRY
patchUnstructured := map[string]any{
"name": common.KuadrantRateLimitClusterName,
"type": "STRICT_DNS",
"connect_timeout": "1s",
"lb_policy": "ROUND_ROBIN",
"http2_protocol_options": map[string]any{},
"load_assignment": map[string]any{
"cluster_name": common.KuadrantRateLimitClusterName,
"endpoints": []map[string]any{
{
"lb_endpoints": []map[string]any{
{
"endpoint": map[string]any{
"address": map[string]any{
"socket_address": map[string]any{
"address": limitadorSvcHost,
"port_value": limitadorGRPCPort,
},
},
},
},
},
},
},
},
}

patchRaw, _ := json.Marshal(patchUnstructured)
value := &apiextensionsv1.JSON{}
value.UnmarshalJSON(patchRaw)

return egv1alpha1.EnvoyJSONPatchConfig{
Type: egv1alpha1.ClusterEnvoyResourceType,
Name: common.KuadrantRateLimitClusterName,
Operation: egv1alpha1.JSONPatchOperation{
Op: egv1alpha1.JSONPatchOperationType("add"),
Path: "",
Value: value,
},
}
}

// SetupWithManager sets up the controller with the Manager.
func (r *EnvoyGatewayLimitadorClusterReconciler) SetupWithManager(mgr ctrl.Manager) error {
ok, err := kuadrantenvoygateway.IsEnvoyGatewayInstalled(mgr.GetRESTMapper())
if err != nil {
return err

Check warning on line 204 in controllers/envoygateway_limitador_cluster_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoygateway_limitador_cluster_controller.go#L204

Added line #L204 was not covered by tests
}
if !ok {
r.Logger().Info("EnvoyGateway limitador cluster controller disabled. EnvoyGateway API was not found")
return nil
}

ok, err = kuadrantgatewayapi.IsGatewayAPIInstalled(mgr.GetRESTMapper())
if err != nil {
return err

Check warning on line 213 in controllers/envoygateway_limitador_cluster_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoygateway_limitador_cluster_controller.go#L213

Added line #L213 was not covered by tests
}
if !ok {
r.Logger().Info("EnvoyGateway limitador cluster disabled. GatewayAPI was not found")
return nil

Check warning on line 217 in controllers/envoygateway_limitador_cluster_controller.go

View check run for this annotation

Codecov / codecov/patch

controllers/envoygateway_limitador_cluster_controller.go#L216-L217

Added lines #L216 - L217 were not covered by tests
}

return ctrl.NewControllerManagedBy(mgr).
For(&egv1alpha1.EnvoyExtensionPolicy{}).
Owns(&egv1alpha1.EnvoyPatchPolicy{}).
Complete(r)
}
Loading
Loading