From c7b522a971a9edfef6b9f4cb412ad1dbc23a037d Mon Sep 17 00:00:00 2001 From: Guilherme Cassolato Date: Tue, 15 Oct 2024 20:53:43 +0200 Subject: [PATCH] new structure of the wasm config based on https://github.com/Kuadrant/wasm-shim/pull/110 Signed-off-by: Guilherme Cassolato --- .../envoy_gateway_extenion_reconciler.go | 14 +- controllers/istio_extension_reconciler.go | 14 +- controllers/ratelimit_workflow.go | 38 +- controllers/ratelimit_workflow_test.go | 349 +--- pkg/wasm/types.go | 328 ++-- pkg/wasm/types_test.go | 124 +- pkg/wasm/utils.go | 122 +- tests/commons.go | 19 + ...r_test.go => extension_reconciler_test.go} | 160 +- ...r_test.go => extension_reconciler_test.go} | 1448 ++++++++++------- 10 files changed, 1304 insertions(+), 1312 deletions(-) rename tests/envoygateway/{wasm_controller_test.go => extension_reconciler_test.go} (76%) rename tests/istio/{rate_limiting_istio_wasmplugin_controller_test.go => extension_reconciler_test.go} (64%) diff --git a/controllers/envoy_gateway_extenion_reconciler.go b/controllers/envoy_gateway_extenion_reconciler.go index b16d568e5..960b97317 100644 --- a/controllers/envoy_gateway_extenion_reconciler.go +++ b/controllers/envoy_gateway_extenion_reconciler.go @@ -139,7 +139,7 @@ func (r *envoyGatewayExtensionReconciler) buildWasmConfigs(ctx context.Context, logger.V(1).Info("building wasm configs for envoy gateway extension", "effectivePolicies", len(effectivePolicies.(EffectiveRateLimitPolicies))) - wasmPolicies := kuadrantgatewayapi.GrouppedHTTPRouteMatchConfigs{} + wasmActionSets := kuadrantgatewayapi.GrouppedHTTPRouteMatchConfigs{} // build the wasm policies for each topological path that contains an effective rate limit policy affecting an envoy gateway gateway for pathID, effectivePolicy := range effectivePolicies.(EffectiveRateLimitPolicies) { @@ -152,17 +152,17 @@ func (r *envoyGatewayExtensionReconciler) buildWasmConfigs(ctx context.Context, continue } - wasmPoliciesForPath, err := wasm.BuildWasmPoliciesForPath(pathID, effectivePolicy.Path, effectivePolicy.Spec.Rules(), rateLimitWasmRuleBuilder(pathID, effectivePolicy, state)) + wasmActionSetsForPath, err := wasm.BuildActionSetsForPath(pathID, effectivePolicy.Path, effectivePolicy.Spec.Rules(), rateLimitWasmActionBuilder(pathID, effectivePolicy, state)) if err != nil { logger.Error(err, "failed to build wasm policies for path", "pathID", pathID) continue } - wasmPolicies.Add(gateway.GetLocator(), wasmPoliciesForPath...) + wasmActionSets.Add(gateway.GetLocator(), wasmActionSetsForPath...) } - wasmConfigs := lo.MapValues(wasmPolicies.Sorted(), func(configs kuadrantgatewayapi.SortableHTTPRouteMatchConfigs, _ string) wasm.Config { - return wasm.BuildWasmConfigForPolicies(lo.Map(configs, func(c kuadrantgatewayapi.HTTPRouteMatchConfig, _ int) wasm.Policy { - return c.Config.(wasm.Policy) + wasmConfigs := lo.MapValues(wasmActionSets.Sorted(), func(configs kuadrantgatewayapi.SortableHTTPRouteMatchConfigs, _ string) wasm.Config { + return wasm.BuildConfigForActionSet(lo.Map(configs, func(c kuadrantgatewayapi.HTTPRouteMatchConfig, _ int) wasm.ActionSet { + return c.Config.(wasm.ActionSet) })) }) @@ -212,7 +212,7 @@ func buildEnvoyExtensionPolicyForGateway(gateway machinery.Targetable, wasmConfi }, } - if len(wasmConfig.Policies) == 0 { + if len(wasmConfig.ActionSets) == 0 { utils.TagObjectToDelete(envoyPolicy) } else { pluginConfigJSON, err := wasmConfig.ToJSON() diff --git a/controllers/istio_extension_reconciler.go b/controllers/istio_extension_reconciler.go index aff90dbf1..17eeda94e 100644 --- a/controllers/istio_extension_reconciler.go +++ b/controllers/istio_extension_reconciler.go @@ -140,7 +140,7 @@ func (r *istioExtensionReconciler) buildWasmConfigs(ctx context.Context, state * logger.V(1).Info("building wasm configs for istio extension", "effectivePolicies", len(effectivePolicies.(EffectiveRateLimitPolicies))) - wasmPolicies := kuadrantgatewayapi.GrouppedHTTPRouteMatchConfigs{} + wasmActionSets := kuadrantgatewayapi.GrouppedHTTPRouteMatchConfigs{} // build the wasm policies for each topological path that contains an effective rate limit policy affecting an istio gateway for pathID, effectivePolicy := range effectivePolicies.(EffectiveRateLimitPolicies) { @@ -153,17 +153,17 @@ func (r *istioExtensionReconciler) buildWasmConfigs(ctx context.Context, state * continue } - wasmPoliciesForPath, err := wasm.BuildWasmPoliciesForPath(pathID, effectivePolicy.Path, effectivePolicy.Spec.Rules(), rateLimitWasmRuleBuilder(pathID, effectivePolicy, state)) + wasmActionSetsForPath, err := wasm.BuildActionSetsForPath(pathID, effectivePolicy.Path, effectivePolicy.Spec.Rules(), rateLimitWasmActionBuilder(pathID, effectivePolicy, state)) if err != nil { logger.Error(err, "failed to build wasm policies for path", "pathID", pathID) continue } - wasmPolicies.Add(gateway.GetLocator(), wasmPoliciesForPath...) + wasmActionSets.Add(gateway.GetLocator(), wasmActionSetsForPath...) } - wasmConfigs := lo.MapValues(wasmPolicies.Sorted(), func(configs kuadrantgatewayapi.SortableHTTPRouteMatchConfigs, _ string) wasm.Config { - return wasm.BuildWasmConfigForPolicies(lo.Map(configs, func(c kuadrantgatewayapi.HTTPRouteMatchConfig, _ int) wasm.Policy { - return c.Config.(wasm.Policy) + wasmConfigs := lo.MapValues(wasmActionSets.Sorted(), func(configs kuadrantgatewayapi.SortableHTTPRouteMatchConfigs, _ string) wasm.Config { + return wasm.BuildConfigForActionSet(lo.Map(configs, func(c kuadrantgatewayapi.HTTPRouteMatchConfig, _ int) wasm.ActionSet { + return c.Config.(wasm.ActionSet) })) }) @@ -195,7 +195,7 @@ func buildIstioWasmPluginForGateway(gateway machinery.Targetable, wasmConfig was }, } - if len(wasmConfig.Policies) == 0 { + if len(wasmConfig.ActionSets) == 0 { utils.TagObjectToDelete(wasmPlugin) } else { pluginConfigStruct, err := wasmConfig.ToStruct() diff --git a/controllers/ratelimit_workflow.go b/controllers/ratelimit_workflow.go index 3f1a406e0..b02f9b58b 100644 --- a/controllers/ratelimit_workflow.go +++ b/controllers/ratelimit_workflow.go @@ -151,7 +151,7 @@ func rateLimitClusterPatch(host string, port int) map[string]any { } } -func rateLimitWasmRuleBuilder(pathID string, effectivePolicy EffectiveRateLimitPolicy, state *sync.Map) wasm.RuleBuilderFunc { +func rateLimitWasmActionBuilder(pathID string, effectivePolicy EffectiveRateLimitPolicy, state *sync.Map) wasm.ActionBuilderFunc { policiesInPath := kuadrantv1.PoliciesInPath(effectivePolicy.Path, isRateLimitPolicyAcceptedAndNotDeletedFunc(state)) // assumes the path is always [gatewayclass, gateway, listener, httproute, httprouterule] @@ -159,43 +159,31 @@ func rateLimitWasmRuleBuilder(pathID string, effectivePolicy EffectiveRateLimitP limitsNamespace := LimitsNamespaceFromRoute(httpRoute.HTTPRoute) - return func(httpRouteMatch gatewayapiv1.HTTPRouteMatch, uniquePolicyRuleKey string, policyRule kuadrantv1.MergeableRule) (wasm.Rule, error) { + return func(uniquePolicyRuleKey string, policyRule kuadrantv1.MergeableRule) (wasm.Action, error) { source, found := lo.Find(policiesInPath, func(p machinery.Policy) bool { return p.GetLocator() == policyRule.Source }) if !found { // should never happen - return wasm.Rule{}, fmt.Errorf("could not find source policy %s in path %s", policyRule.Source, pathID) + return wasm.Action{}, fmt.Errorf("could not find source policy %s in path %s", policyRule.Source, pathID) } limitIdentifier := LimitNameToLimitadorIdentifier(k8stypes.NamespacedName{Name: source.GetName(), Namespace: source.GetNamespace()}, uniquePolicyRuleKey) limit := policyRule.Spec.(kuadrantv1beta3.Limit) - return wasmRuleFromLimit(limit, limitIdentifier, limitsNamespace, httpRouteMatch), nil + return wasmActionFromLimit(limit, limitIdentifier, limitsNamespace), nil } } -// wasmRuleFromLimit builds a wasm rate-limit rule for a given limit. -// Conditions are built from the limit top-level conditions and a HTTPRouteMatch. -// The order of the conditions is as follows: -// 1. Route-level conditions: HTTP method, path, headers -// 2. Top-level conditions: 'when' conditions (blended into each block of route-level conditions) +// wasmActionFromLimit builds a wasm rate-limit action for a given limit. +// Conditions are built from the limit top-level conditions. // -// The only action of the rule is the rate-limit policy extension, whose data includes the activation of the limit +// The only action of the rule is the ratelimit service, whose data includes the activation of the limit // and any counter qualifier of the limit. -func wasmRuleFromLimit(limit kuadrantv1beta3.Limit, limitIdentifier, scope string, routeMatch gatewayapiv1.HTTPRouteMatch) wasm.Rule { - rule := wasm.Rule{ - Conditions: wasm.ConditionsFromHTTPRouteMatch(routeMatch, limit.When...), +func wasmActionFromLimit(limit kuadrantv1beta3.Limit, limitIdentifier, scope string) wasm.Action { + return wasm.Action{ + ServiceName: wasm.RateLimitServiceName, + Scope: scope, + Conditions: wasm.PredicatesFromWhenConditions(limit.When...), + Data: wasmDataFromLimit(limitIdentifier, limit), } - - if data := wasmDataFromLimit(limitIdentifier, limit); data != nil { - rule.Actions = []wasm.Action{ - { - Scope: scope, - ExtensionName: wasm.RateLimitExtensionName, - Data: data, - }, - } - } - - return rule } func wasmDataFromLimit(limitIdentifier string, limit kuadrantv1beta3.Limit) (data []wasm.DataType) { diff --git a/controllers/ratelimit_workflow_test.go b/controllers/ratelimit_workflow_test.go index 0b68b7a7d..701f69464 100644 --- a/controllers/ratelimit_workflow_test.go +++ b/controllers/ratelimit_workflow_test.go @@ -8,8 +8,6 @@ import ( "github.com/google/go-cmp/cmp" k8stypes "k8s.io/apimachinery/pkg/types" - "k8s.io/utils/ptr" - gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" kuadrantv1beta3 "github.com/kuadrant/kuadrant-operator/api/v1beta3" "github.com/kuadrant/kuadrant-operator/pkg/wasm" @@ -69,161 +67,29 @@ func TestLimitNameToLimitadorIdentifier(t *testing.T) { } } -func TestWasmRuleFromLimit(t *testing.T) { +func TestWasmActionFromLimit(t *testing.T) { testCases := []struct { name string limit kuadrantv1beta3.Limit limitIdentifier string scope string - routeMatch gatewayapiv1.HTTPRouteMatch - expectedRule wasm.Rule + expectedAction wasm.Action }{ { name: "limit without conditions nor counters", limit: kuadrantv1beta3.Limit{}, limitIdentifier: "limit.myLimit__d681f6c3", scope: "my-ns/my-route", - routeMatch: gatewayapiv1.HTTPRouteMatch{}, - expectedRule: wasm.Rule{ - Actions: []wasm.Action{ + expectedAction: wasm.Action{ + ServiceName: wasm.RateLimitServiceName, + Scope: "my-ns/my-route", + Conditions: []wasm.Predicate{}, + Data: []wasm.DataType{ { - Scope: "my-ns/my-route", - ExtensionName: wasm.RateLimitExtensionName, - Data: []wasm.DataType{ - { - Value: &wasm.Static{ - Static: wasm.StaticSpec{ - Key: "limit.myLimit__d681f6c3", - Value: "1", - }, - }, - }, - }, - }, - }, - }, - }, - { - name: "limit with httproutematch", - limit: kuadrantv1beta3.Limit{}, - limitIdentifier: "limit.myLimit__d681f6c3", - scope: "my-ns/my-route", - routeMatch: gatewayapiv1.HTTPRouteMatch{ - Path: &gatewayapiv1.HTTPPathMatch{ - Type: ptr.To(gatewayapiv1.PathMatchPathPrefix), - Value: ptr.To("/v1"), - }, - Method: ptr.To(gatewayapiv1.HTTPMethodGet), - Headers: []gatewayapiv1.HTTPHeaderMatch{ - { - Name: gatewayapiv1.HTTPHeaderName("X-kuadrant-a"), - Value: "1", - }, - { - Name: gatewayapiv1.HTTPHeaderName("X-kuadrant-b"), - Value: "1", - }, - }, - }, - expectedRule: wasm.Rule{ - Conditions: []wasm.Condition{ - { - AllOf: []wasm.PatternExpression{ - { - Selector: "request.method", - Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), - Value: "GET", - }, - { - Selector: "request.url_path", - Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), - Value: "/v1", - }, - { - Selector: "request.headers.X-kuadrant-a", - Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), - Value: "1", - }, - { - Selector: "request.headers.X-kuadrant-b", - Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), - Value: "1", - }, - }, - }, - }, - Actions: []wasm.Action{ - { - Scope: "my-ns/my-route", - ExtensionName: wasm.RateLimitExtensionName, - Data: []wasm.DataType{ - { - Value: &wasm.Static{ - Static: wasm.StaticSpec{ - Key: "limit.myLimit__d681f6c3", - Value: "1", - }, - }, - }, - }, - }, - }, - }, - }, - { - name: "limit with httproutematch and when conditions", - limit: kuadrantv1beta3.Limit{ - When: []kuadrantv1beta3.WhenCondition{ - { - Selector: kuadrantv1beta3.ContextSelector("auth.identity.group"), - Operator: kuadrantv1beta3.NotEqualOperator, - Value: "admin", - }, - }, - }, - limitIdentifier: "limit.myLimit__d681f6c3", - scope: "my-ns/my-route", - routeMatch: gatewayapiv1.HTTPRouteMatch{ - Method: ptr.To(gatewayapiv1.HTTPMethodGet), - Path: &gatewayapiv1.HTTPPathMatch{ - Type: ptr.To(gatewayapiv1.PathMatchPathPrefix), - Value: ptr.To("/toys"), - }, - }, - expectedRule: wasm.Rule{ - Conditions: []wasm.Condition{ - { - AllOf: []wasm.PatternExpression{ - { - Selector: "request.method", - Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), - Value: "GET", - }, - { - Selector: "request.url_path", - Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), - Value: "/toys", - }, - { - Selector: "auth.identity.group", - Operator: wasm.PatternOperator(kuadrantv1beta3.NotEqualOperator), - Value: "admin", - }, - }, - }, - }, - Actions: []wasm.Action{ - { - Scope: "my-ns/my-route", - ExtensionName: wasm.RateLimitExtensionName, - Data: []wasm.DataType{ - { - Value: &wasm.Static{ - Static: wasm.StaticSpec{ - Key: "limit.myLimit__d681f6c3", - Value: "1", - }, - }, + Value: &wasm.Static{ + Static: wasm.StaticSpec{ + Key: "limit.myLimit__d681f6c3", + Value: "1", }, }, }, @@ -237,83 +103,23 @@ func TestWasmRuleFromLimit(t *testing.T) { }, limitIdentifier: "limit.myLimit__d681f6c3", scope: "my-ns/my-route", - routeMatch: gatewayapiv1.HTTPRouteMatch{}, - expectedRule: wasm.Rule{ - Actions: []wasm.Action{ + expectedAction: wasm.Action{ + ServiceName: wasm.RateLimitServiceName, + Scope: "my-ns/my-route", + Conditions: []wasm.Predicate{}, + Data: []wasm.DataType{ { - Scope: "my-ns/my-route", - ExtensionName: wasm.RateLimitExtensionName, - Data: []wasm.DataType{ - { - Value: &wasm.Static{ - Static: wasm.StaticSpec{ - Key: "limit.myLimit__d681f6c3", - Value: "1", - }, - }, - }, - { - Value: &wasm.Selector{ - Selector: wasm.SelectorSpec{ - Selector: "auth.identity.username", - }, - }, + Value: &wasm.Static{ + Static: wasm.StaticSpec{ + Key: "limit.myLimit__d681f6c3", + Value: "1", }, }, }, - }, - }, - }, - { - name: "limit with counter qualifiers and httproutematch", - limit: kuadrantv1beta3.Limit{ - Counters: []kuadrantv1beta3.ContextSelector{"auth.identity.username"}, - }, - limitIdentifier: "limit.myLimit__d681f6c3", - scope: "my-ns/my-route", - routeMatch: gatewayapiv1.HTTPRouteMatch{ - Method: ptr.To(gatewayapiv1.HTTPMethodGet), - Path: &gatewayapiv1.HTTPPathMatch{ - Type: ptr.To(gatewayapiv1.PathMatchPathPrefix), - Value: ptr.To("/toys"), - }, - }, - expectedRule: wasm.Rule{ - Conditions: []wasm.Condition{ - { - AllOf: []wasm.PatternExpression{ - { - Selector: "request.method", - Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), - Value: "GET", - }, - { - Selector: "request.url_path", - Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), - Value: "/toys", - }, - }, - }, - }, - Actions: []wasm.Action{ { - Scope: "my-ns/my-route", - ExtensionName: wasm.RateLimitExtensionName, - Data: []wasm.DataType{ - { - Value: &wasm.Static{ - Static: wasm.StaticSpec{ - Key: "limit.myLimit__d681f6c3", - Value: "1", - }, - }, - }, - { - Value: &wasm.Selector{ - Selector: wasm.SelectorSpec{ - Selector: "auth.identity.username", - }, - }, + Value: &wasm.Selector{ + Selector: wasm.SelectorSpec{ + Selector: "auth.identity.username", }, }, }, @@ -334,106 +140,29 @@ func TestWasmRuleFromLimit(t *testing.T) { }, limitIdentifier: "limit.myLimit__d681f6c3", scope: "my-ns/my-route", - routeMatch: gatewayapiv1.HTTPRouteMatch{}, - expectedRule: wasm.Rule{ - Conditions: []wasm.Condition{ - { - AllOf: []wasm.PatternExpression{ - { - Selector: "auth.identity.group", - Operator: wasm.PatternOperator(kuadrantv1beta3.NotEqualOperator), - Value: "admin", - }, - }, - }, - }, - Actions: []wasm.Action{ + expectedAction: wasm.Action{ + ServiceName: wasm.RateLimitServiceName, + Scope: "my-ns/my-route", + Conditions: []wasm.Predicate{ { - Scope: "my-ns/my-route", - ExtensionName: wasm.RateLimitExtensionName, - Data: []wasm.DataType{ - { - Value: &wasm.Static{ - Static: wasm.StaticSpec{ - Key: "limit.myLimit__d681f6c3", - Value: "1", - }, - }, - }, - { - Value: &wasm.Selector{ - Selector: wasm.SelectorSpec{ - Selector: "auth.identity.username", - }, - }, - }, - }, - }, - }, - }, - }, - { - name: "limit with counter qualifiers, httproutematch and when conditions", - limit: kuadrantv1beta3.Limit{ - Counters: []kuadrantv1beta3.ContextSelector{"auth.identity.username"}, - When: []kuadrantv1beta3.WhenCondition{ - { - Selector: kuadrantv1beta3.ContextSelector("auth.identity.group"), - Operator: kuadrantv1beta3.NotEqualOperator, + Selector: "auth.identity.group", + Operator: wasm.PatternOperator(kuadrantv1beta3.NotEqualOperator), Value: "admin", }, }, - }, - limitIdentifier: "limit.myLimit__d681f6c3", - scope: "my-ns/my-route", - routeMatch: gatewayapiv1.HTTPRouteMatch{ - Method: ptr.To(gatewayapiv1.HTTPMethodGet), - Path: &gatewayapiv1.HTTPPathMatch{ - Type: ptr.To(gatewayapiv1.PathMatchPathPrefix), - Value: ptr.To("/toys"), - }, - }, - expectedRule: wasm.Rule{ - Conditions: []wasm.Condition{ + Data: []wasm.DataType{ { - AllOf: []wasm.PatternExpression{ - { - Selector: "request.method", - Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), - Value: "GET", - }, - { - Selector: "request.url_path", - Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), - Value: "/toys", - }, - { - Selector: "auth.identity.group", - Operator: wasm.PatternOperator(kuadrantv1beta3.NotEqualOperator), - Value: "admin", + Value: &wasm.Static{ + Static: wasm.StaticSpec{ + Key: "limit.myLimit__d681f6c3", + Value: "1", }, }, }, - }, - Actions: []wasm.Action{ { - Scope: "my-ns/my-route", - ExtensionName: wasm.RateLimitExtensionName, - Data: []wasm.DataType{ - { - Value: &wasm.Static{ - Static: wasm.StaticSpec{ - Key: "limit.myLimit__d681f6c3", - Value: "1", - }, - }, - }, - { - Value: &wasm.Selector{ - Selector: wasm.SelectorSpec{ - Selector: "auth.identity.username", - }, - }, + Value: &wasm.Selector{ + Selector: wasm.SelectorSpec{ + Selector: "auth.identity.username", }, }, }, @@ -444,8 +173,8 @@ func TestWasmRuleFromLimit(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - computedRule := wasmRuleFromLimit(tc.limit, tc.limitIdentifier, tc.scope, tc.routeMatch) - if diff := cmp.Diff(tc.expectedRule, computedRule); diff != "" { + computedRule := wasmActionFromLimit(tc.limit, tc.limitIdentifier, tc.scope) + if diff := cmp.Diff(tc.expectedAction, computedRule); diff != "" { t.Errorf("unexpected wasm rule (-want +got):\n%s", diff) } }) diff --git a/pkg/wasm/types.go b/pkg/wasm/types.go index 44c31c735..aba1dcf42 100644 --- a/pkg/wasm/types.go +++ b/pkg/wasm/types.go @@ -20,118 +20,94 @@ var ( } ) -type SelectorSpec struct { - // Selector of an attribute from the contextual properties provided by kuadrant - // during request and connection processing - Selector kuadrantv1beta3.ContextSelector `json:"selector"` - - // If not set it defaults to `selector` field value as the descriptor key. - // +optional - Key *string `json:"key,omitempty"` - - // An optional value to use if the selector is not found in the context. - // If not set and the selector is not found in the context, then no descriptor is generated. - // +optional - Default *string `json:"default,omitempty"` +type Config struct { + Services map[string]Service `json:"services"` + ActionSets []ActionSet `json:"actionSets"` } -type StaticSpec struct { - Value string `json:"value"` - Key string `json:"key"` -} +func (c *Config) ToStruct() (*_struct.Struct, error) { + configJSON, err := json.Marshal(c) + if err != nil { + return nil, err + } -type Static struct { - Static StaticSpec `json:"static"` + configStruct := &_struct.Struct{} + if err := configStruct.UnmarshalJSON(configJSON); err != nil { + return nil, err + } + return configStruct, nil } -type Selector struct { - Selector SelectorSpec `json:"selector"` -} +func (c *Config) ToJSON() (*apiextensionsv1.JSON, error) { + configJSON, err := json.Marshal(c) + if err != nil { + return nil, err + } -type DataType struct { - Value interface{} + return &apiextensionsv1.JSON{Raw: configJSON}, nil } -func (d *DataType) UnmarshalJSON(data []byte) error { - // Precisely one of "static", "selector" must be set. - types := []interface{}{ - &Static{}, - &Selector{}, +func (c *Config) EqualTo(other *Config) bool { + if len(c.Services) != len(other.Services) || len(c.ActionSets) != len(other.ActionSets) { + return false } - var err error + for key, service := range c.Services { + if otherService, ok := other.Services[key]; !ok || service != otherService { + return false + } + } - for idx := range types { - dec := json.NewDecoder(bytes.NewReader(data)) - dec.DisallowUnknownFields() // Force errors - err = dec.Decode(types[idx]) - if err == nil { - d.Value = types[idx] - return nil + for i := range c.ActionSets { + if !c.ActionSets[i].EqualTo(other.ActionSets[i]) { + return false } } - return err + return true } -func (d *DataType) MarshalJSON() ([]byte, error) { - switch val := d.Value.(type) { - case *Static: - return json.Marshal(val) - case *Selector: - return json.Marshal(val) - default: - return nil, errors.New("DataType.Value has unknown type") - } +type Service struct { + Endpoint string `json:"endpoint"` + Type ServiceType `json:"type"` + FailureMode FailureModeType `json:"failureMode"` + Timeout *string `json:"timeout,omitempty"` } -func (d *DataType) EqualTo(other DataType) bool { - dt, err := d.MarshalJSON() - if err != nil { - return false - } - odt, err := other.MarshalJSON() - if err != nil { - return false - } - return bytes.Equal(dt, odt) -} +// +kubebuilder:validation:Enum:=ratelimit;auth +type ServiceType string -type PatternOperator kuadrantv1beta3.WhenConditionOperator +const ( + RateLimitServiceType ServiceType = "ratelimit" + AuthServiceType ServiceType = "auth" +) -type PatternExpression struct { - // Selector of an attribute from the contextual properties provided by kuadrant - // during request and connection processing - Selector kuadrantv1beta3.ContextSelector `json:"selector"` +// +kubebuilder:validation:Enum:=deny;allow +type FailureModeType string - // The binary operator to be applied to the content fetched from context, for comparison with "value". - // Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) - // TODO build comprehensive list of operators - Operator PatternOperator `json:"operator"` +const ( + FailureModeDeny FailureModeType = "deny" + FailureModeAllow FailureModeType = "allow" +) - // The value of reference for the comparison with the content fetched from the context. - Value string `json:"value"` -} +type ActionSet struct { + Name string `json:"name"` -func (p *PatternExpression) EqualTo(other PatternExpression) bool { - return p.Selector == other.Selector && - p.Operator == other.Operator && - p.Value == other.Value -} + // Conditions that activate the action set + RouteRuleConditions RouteRuleConditions `json:"routeRuleConditions,omitempty"` -type Condition struct { - // All the expressions defined must match to match this rule + // Actions that will be invoked when the conditions are met // +optional - AllOf []PatternExpression `json:"allOf,omitempty"` + Actions []Action `json:"actions,omitempty"` } -func (c *Condition) EqualTo(other Condition) bool { - if len(c.AllOf) != len(other.AllOf) { +func (s *ActionSet) EqualTo(other ActionSet) bool { + if s.Name != other.Name || !s.RouteRuleConditions.EqualTo(other.RouteRuleConditions) || len(s.Actions) != len(other.Actions) { return false } - for i := range c.AllOf { - if !c.AllOf[i].EqualTo(other.AllOf[i]) { + for i := range s.Actions { + if !s.Actions[i].EqualTo(other.Actions[i]) { return false } } @@ -139,29 +115,24 @@ func (c *Condition) EqualTo(other Condition) bool { return true } -type Rule struct { - // Top level conditions for the rule. At least one of the conditions must be met. - // Empty conditions evaluate to true, so actions will be invoked. - // +optional - Conditions []Condition `json:"conditions,omitempty"` - - // Actions defines which extensions will be invoked when any of the top level conditions match. - Actions []Action `json:"actions"` +type RouteRuleConditions struct { + Hostnames []string `json:"hostnames"` + Matches []Predicate `json:"matches"` } -func (r *Rule) EqualTo(other Rule) bool { - if len(r.Conditions) != len(other.Conditions) || len(r.Actions) != len(other.Actions) { +func (r *RouteRuleConditions) EqualTo(other RouteRuleConditions) bool { + if len(r.Hostnames) != len(other.Hostnames) || len(r.Matches) != len(other.Matches) { return false } - for i := range r.Conditions { - if !r.Conditions[i].EqualTo(other.Conditions[i]) { + for i := range r.Hostnames { + if r.Hostnames[i] != other.Hostnames[i] { return false } } - for i := range r.Actions { - if !r.Actions[i].EqualTo(other.Actions[i]) { + for i := range r.Matches { + if !r.Matches[i].EqualTo(other.Matches[i]) { return false } } @@ -169,48 +140,51 @@ func (r *Rule) EqualTo(other Rule) bool { return true } -type Policy struct { - Name string `json:"name"` - Hostnames []string `json:"hostnames"` - - // Rules includes top level conditions and actions to be invoked - // +optional - Rules []Rule `json:"rules,omitempty"` -} - -func (p *Policy) EqualTo(other Policy) bool { - if p.Name != other.Name || len(p.Hostnames) != len(other.Hostnames) || len(p.Rules) != len(other.Rules) { - return false - } +type Predicate struct { + // Selector of an attribute from the contextual properties provided by kuadrant + // during request and connection processing + Selector kuadrantv1beta3.ContextSelector `json:"selector"` - for i := range p.Hostnames { - if p.Hostnames[i] != other.Hostnames[i] { - return false - } - } + // The binary operator to be applied to the content fetched from context, for comparison with "value". + // Possible values are: "eq" (equal to), "neq" (not equal to), "incl" (includes; for arrays), "excl" (excludes; for arrays), "matches" (regex) + // TODO build comprehensive list of operators + Operator PatternOperator `json:"operator"` - for i := range p.Rules { - if !p.Rules[i].EqualTo(other.Rules[i]) { - return false - } - } + // The value of reference for the comparison with the content fetched from the context. + Value string `json:"value"` +} - return true +func (p *Predicate) EqualTo(other Predicate) bool { + return p.Selector == other.Selector && + p.Operator == other.Operator && + p.Value == other.Value } +type PatternOperator kuadrantv1beta3.WhenConditionOperator + type Action struct { - Scope string `json:"scope"` - ExtensionName string `json:"extension"` + ServiceName string `json:"service"` + Scope string `json:"scope"` + // Conditions that activate the action + Conditions []Predicate `json:"conditions"` + + // Data to be sent to the service // +optional Data []DataType `json:"data,omitempty"` } func (a *Action) EqualTo(other Action) bool { - if a.Scope != other.Scope || a.ExtensionName != other.ExtensionName || len(a.Data) != len(other.Data) { + if a.Scope != other.Scope || a.ServiceName != other.ServiceName || len(a.Conditions) != len(other.Conditions) || len(a.Data) != len(other.Data) { return false } + for i := range a.Conditions { + if !a.Conditions[i].EqualTo(other.Conditions[i]) { + return false + } + } + for i := range a.Data { if !a.Data[i].EqualTo(other.Data[i]) { return false @@ -220,75 +194,79 @@ func (a *Action) EqualTo(other Action) bool { return true } -// +kubebuilder:validation:Enum:=ratelimit;auth -type ExtensionType string - -const ( - RateLimitExtensionType ExtensionType = "ratelimit" - AuthExtensionType ExtensionType = "auth" -) +type DataType struct { + Value interface{} +} -// +kubebuilder:validation:Enum:=deny;allow -type FailureModeType string +func (d *DataType) UnmarshalJSON(data []byte) error { + // Precisely one of "static", "selector" must be set. + types := []interface{}{ + &Static{}, + &Selector{}, + } -const ( - FailureModeDeny FailureModeType = "deny" - FailureModeAllow FailureModeType = "allow" -) + var err error -type Extension struct { - Endpoint string `json:"endpoint"` - FailureMode FailureModeType `json:"failureMode"` - Type ExtensionType `json:"type"` -} + for idx := range types { + dec := json.NewDecoder(bytes.NewReader(data)) + dec.DisallowUnknownFields() // Force errors + err = dec.Decode(types[idx]) + if err == nil { + d.Value = types[idx] + return nil + } + } -type LimitadorExtension struct { - Endpoint string `json:"endpoint"` + return err } -type Config struct { - Extensions map[string]Extension `json:"extensions"` - Policies []Policy `json:"policies"` +func (d *DataType) MarshalJSON() ([]byte, error) { + switch val := d.Value.(type) { + case *Static: + return json.Marshal(val) + case *Selector: + return json.Marshal(val) + default: + return nil, errors.New("DataType.Value has unknown type") + } } -func (c *Config) ToStruct() (*_struct.Struct, error) { - configJSON, err := json.Marshal(c) +func (d *DataType) EqualTo(other DataType) bool { + dt, err := d.MarshalJSON() if err != nil { - return nil, err + return false } - - configStruct := &_struct.Struct{} - if err := configStruct.UnmarshalJSON(configJSON); err != nil { - return nil, err + odt, err := other.MarshalJSON() + if err != nil { + return false } - return configStruct, nil + return bytes.Equal(dt, odt) } -func (c *Config) ToJSON() (*apiextensionsv1.JSON, error) { - configJSON, err := json.Marshal(c) - if err != nil { - return nil, err - } +type Static struct { + Static StaticSpec `json:"static"` +} - return &apiextensionsv1.JSON{Raw: configJSON}, nil +type Selector struct { + Selector SelectorSpec `json:"selector"` } -func (c *Config) EqualTo(other *Config) bool { - if len(c.Extensions) != len(other.Extensions) || len(c.Policies) != len(other.Policies) { - return false - } +type StaticSpec struct { + Value string `json:"value"` + Key string `json:"key"` +} - for key, extension := range c.Extensions { - if otherExtension, ok := other.Extensions[key]; !ok || extension != otherExtension { - return false - } - } +type SelectorSpec struct { + // Selector of an attribute from the contextual properties provided by kuadrant + // during request and connection processing + Selector kuadrantv1beta3.ContextSelector `json:"selector"` - for i := range c.Policies { - if !c.Policies[i].EqualTo(other.Policies[i]) { - return false - } - } + // If not set it defaults to `selector` field value as the descriptor key. + // +optional + Key *string `json:"key,omitempty"` - return true + // An optional value to use if the selector is not found in the context. + // If not set and the selector is not found in the context, then no descriptor is generated. + // +optional + Default *string `json:"default,omitempty"` } diff --git a/pkg/wasm/types_test.go b/pkg/wasm/types_test.go index 52b02ed76..5a796e876 100644 --- a/pkg/wasm/types_test.go +++ b/pkg/wasm/types_test.go @@ -24,31 +24,34 @@ func TestConfig(t *testing.T) { name: "basic example", expectedConfig: testBasicConfigExample(), yaml: ` -extensions: - limitador: +services: + ratelimit-service: type: ratelimit endpoint: kuadrant-rate-limiting-service failureMode: allow -policies: +actionSets: - name: rlp-ns-A/rlp-name-A - hostnames: - - '*.toystore.com' - - example.com - rules: - - conditions: - - allOf: - - selector: request.host - operator: eq - value: cars.toystore.com - actions: - - extension: limitador - scope: rlp-ns-A/rlp-name-A - data: - - static: - key: rlp-ns-A/rlp-name-A - value: "1" - - selector: - selector: auth.metadata.username + routeRuleConditions: + hostnames: + - '*.toystore.com' + - example.com + matches: + - selector: request.path + operator: startswith + value: /cars + actions: + - service: ratelimit-service + scope: rlp-ns-A/rlp-name-A + conditions: + - selector: source.ip + operator: neq + value: 127.0.0.1 + data: + - static: + key: rlp-ns-A/rlp-name-A + value: "1" + - selector: + selector: auth.metadata.username `, }, } @@ -97,13 +100,13 @@ func TestValidActionConfig(t *testing.T) { { name: "valid empty data", expectedAction: &Action{ - Scope: "some-scope", - ExtensionName: "limitador", - Data: nil, + ServiceName: "ratelimit-service", + Scope: "some-scope", + Data: nil, }, yaml: ` +service: ratelimit-service scope: some-scope -extension: limitador `, }, } @@ -130,8 +133,8 @@ func TestInValidActionConfig(t *testing.T) { { name: "unknown data type", yaml: ` +service: ratelimit-service scope: some-scope -extension: limitador data: - other: key: keyA @@ -140,8 +143,8 @@ data: { name: "both data types at the same time", yaml: ` +service: ratelimit-service scope: some-scope -extension: limitador data: - static: key: keyA @@ -163,52 +166,53 @@ data: func testBasicConfigExample() *Config { return &Config{ - Extensions: map[string]Extension{ - RateLimitExtensionName: { + Services: map[string]Service{ + RateLimitServiceName: { + Type: RateLimitServiceType, Endpoint: common.KuadrantRateLimitClusterName, FailureMode: FailureModeAllow, - Type: RateLimitExtensionType, }, }, - Policies: []Policy{ + ActionSets: []ActionSet{ { Name: "rlp-ns-A/rlp-name-A", - Hostnames: []string{ - "*.toystore.com", - "example.com", + RouteRuleConditions: RouteRuleConditions{ + Hostnames: []string{ + "*.toystore.com", + "example.com", + }, + Matches: []Predicate{ + { + Selector: "request.path", + Operator: PatternOperator(kuadrantv1beta3.StartsWithOperator), + Value: "/cars", + }, + }, }, - Rules: []Rule{ + Actions: []Action{ { - Conditions: []Condition{ + ServiceName: RateLimitServiceName, + Scope: "rlp-ns-A/rlp-name-A", + Conditions: []Predicate{ { - AllOf: []PatternExpression{ - { - Selector: "request.host", - Operator: PatternOperator(kuadrantv1beta3.EqualOperator), - Value: "cars.toystore.com", - }, - }, + Selector: "source.ip", + Operator: PatternOperator(kuadrantv1beta3.NotEqualOperator), + Value: "127.0.0.1", }, }, - Actions: []Action{ + Data: []DataType{ { - Scope: "rlp-ns-A/rlp-name-A", - ExtensionName: RateLimitExtensionName, - Data: []DataType{ - { - Value: &Static{ - Static: StaticSpec{ - Key: "rlp-ns-A/rlp-name-A", - Value: "1", - }, - }, + Value: &Static{ + Static: StaticSpec{ + Key: "rlp-ns-A/rlp-name-A", + Value: "1", }, - { - Value: &Selector{ - Selector: SelectorSpec{ - Selector: "auth.metadata.username", - }, - }, + }, + }, + { + Value: &Selector{ + Selector: SelectorSpec{ + Selector: "auth.metadata.username", }, }, }, diff --git a/pkg/wasm/utils.go b/pkg/wasm/utils.go index 7cd7b649b..11494da3c 100644 --- a/pkg/wasm/utils.go +++ b/pkg/wasm/utils.go @@ -18,32 +18,31 @@ import ( ) const ( - // TODO(guicassolato): move these to github.com/kuadrant/kuadrant-operator/pkg/common (?) - RateLimitExtensionName = "limitador" - AuthExtensionName = "authorino" + RateLimitServiceName = "ratelimit-service" + AuthServiceName = "auth-service" ) func ExtensionName(gatewayName string) string { return fmt.Sprintf("kuadrant-%s", gatewayName) } -func BuildWasmConfigForPolicies(policies []Policy) Config { +func BuildConfigForActionSet(actionSets []ActionSet) Config { return Config{ - Extensions: map[string]Extension{ - RateLimitExtensionName: { + Services: map[string]Service{ + RateLimitServiceName: { + Type: RateLimitServiceType, Endpoint: common.KuadrantRateLimitClusterName, FailureMode: FailureModeAllow, - Type: RateLimitExtensionType, }, // TODO: add auth extension }, - Policies: policies, + ActionSets: actionSets, } } -type RuleBuilderFunc func(httpRouteMatch gatewayapiv1.HTTPRouteMatch, uniquePolicyRuleKey string, policyRule kuadrantv1.MergeableRule) (Rule, error) +type ActionBuilderFunc func(uniquePolicyRuleKey string, policyRule kuadrantv1.MergeableRule) (Action, error) -func BuildWasmPoliciesForPath(pathID string, path []machinery.Targetable, policyRules map[string]kuadrantv1.MergeableRule, wasmRuleBuilder RuleBuilderFunc) ([]kuadrantgatewayapi.HTTPRouteMatchConfig, error) { +func BuildActionSetsForPath(pathID string, path []machinery.Targetable, policyRules map[string]kuadrantv1.MergeableRule, actionBuilder ActionBuilderFunc) ([]kuadrantgatewayapi.HTTPRouteMatchConfig, error) { // assumes the path is always [gatewayclass, gateway, listener, httproute, httprouterule] listener, _ := path[2].(*machinery.Listener) httpRoute, _ := path[3].(*machinery.HTTPRoute) @@ -51,28 +50,30 @@ func BuildWasmPoliciesForPath(pathID string, path []machinery.Targetable, policy var err error + actions := lo.FilterMap(lo.Entries(policyRules), func(r lo.Entry[string, kuadrantv1.MergeableRule] , _ int) (Action, bool) { + action, err := actionBuilder(r.Key, r.Value) + if err != nil { + errors.Join(err) + return Action{}, false + } + return action, true + }) + return lo.FlatMap(kuadrantgatewayapi.HostnamesFromListenerAndHTTPRoute(listener.Listener, httpRoute.HTTPRoute), func(hostname gatewayapiv1.Hostname, i int) []kuadrantgatewayapi.HTTPRouteMatchConfig { return lo.Map(httpRouteRule.Matches, func(httpRouteMatch gatewayapiv1.HTTPRouteMatch, j int) kuadrantgatewayapi.HTTPRouteMatchConfig { - var wasmRules []Rule - for uniquePolicyRuleKey, mergeablePolicyRule := range policyRules { - wasmRule, err := wasmRuleBuilder(httpRouteMatch, uniquePolicyRuleKey, mergeablePolicyRule) - if err != nil { - errors.Join(err) - continue - } - wasmRules = append(wasmRules, wasmRule) - } - return kuadrantgatewayapi.HTTPRouteMatchConfig{ Hostname: string(hostname), HTTPRouteMatch: httpRouteMatch, CreationTimestamp: httpRoute.GetCreationTimestamp(), Namespace: httpRoute.GetNamespace(), Name: httpRoute.GetName(), - Config: Policy{ - Name: fmt.Sprintf("%d-%s-%d", i, pathID, j), - Hostnames: []string{string(hostname)}, - Rules: wasmRules, + Config: ActionSet{ + Name: fmt.Sprintf("%d-%s-%d", i, pathID, j), + RouteRuleConditions: RouteRuleConditions{ + Hostnames: []string{string(hostname)}, + Matches: PredicatesFromHTTPRouteMatch(httpRouteMatch), + }, + Actions: actions, }, } }) @@ -110,68 +111,45 @@ func ConfigFromJSON(configJSON *apiextensionsv1.JSON) (*Config, error) { return config, nil } -func ConditionsFromHTTPRouteMatch(routeMatch gatewayapiv1.HTTPRouteMatch, otherConditions ...kuadrantv1beta3.WhenCondition) []Condition { - var ruleConditions []Condition - if routeMatch.Path != nil || routeMatch.Method != nil || len(routeMatch.Headers) > 0 || len(routeMatch.QueryParams) > 0 { - ruleConditions = append(ruleConditions, conditionsFromHTTPRouteMatch(routeMatch)) - } - - // only rule conditions (or no condition at all) - if len(otherConditions) == 0 { - if len(ruleConditions) == 0 { - return nil +// PredicatesFromWhenConditions builds a list of predicates from a list of (selector, operator, value) when conditions +func PredicatesFromWhenConditions(when ...kuadrantv1beta3.WhenCondition) []Predicate { + return lo.Map(when, func(when kuadrantv1beta3.WhenCondition, _ int) Predicate { + return Predicate{ + Selector: when.Selector, + Operator: PatternOperator(when.Operator), + Value: when.Value, } - return ruleConditions - } - - whenConditionToWasmPatternExpressionFunc := func(when kuadrantv1beta3.WhenCondition, _ int) PatternExpression { - return patternExpresionFromWhenCondition(when) - } - - // top-level conditions merged into the rule conditions - if len(ruleConditions) > 0 { - return lo.Map(ruleConditions, func(condition Condition, _ int) Condition { - condition.AllOf = append(condition.AllOf, lo.Map(otherConditions, whenConditionToWasmPatternExpressionFunc)...) - return condition - }) - } - - // only top-level conditions - return []Condition{{AllOf: lo.Map(otherConditions, whenConditionToWasmPatternExpressionFunc)}} + }) } -// conditionsFromHTTPRouteMatch builds a list of conditions from a rule match -func conditionsFromHTTPRouteMatch(match gatewayapiv1.HTTPRouteMatch) Condition { - return Condition{AllOf: patternExpresionsFromMatch(match)} -} - -func patternExpresionsFromMatch(match gatewayapiv1.HTTPRouteMatch) []PatternExpression { - expressions := make([]PatternExpression, 0) +// PredicatesFromHTTPRouteMatch builds a list of conditions from a rule match +func PredicatesFromHTTPRouteMatch(match gatewayapiv1.HTTPRouteMatch) []Predicate { + predicates := make([]Predicate, 0) // method if match.Method != nil { - expressions = append(expressions, patternExpresionFromMethod(*match.Method)) + predicates = append(predicates, predicateFromMethod(*match.Method)) } // path if match.Path != nil { - expressions = append(expressions, patternExpresionFromPathMatch(*match.Path)) + predicates = append(predicates, predicateFromPathMatch(*match.Path)) } // headers for _, headerMatch := range match.Headers { // Multiple match values are ANDed together - expressions = append(expressions, patternExpresionFromHeader(headerMatch)) + predicates = append(predicates, predicateFromHeader(headerMatch)) } // TODO(eguzki): query params. Investigate integration with wasm regarding Envoy params // from https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/attributes // request.query -> string : The query portion of the URL in the format of “name1=value1&name2=value2”. - return expressions + return predicates } -func patternExpresionFromPathMatch(pathMatch gatewayapiv1.HTTPPathMatch) PatternExpression { +func predicateFromPathMatch(pathMatch gatewayapiv1.HTTPPathMatch) Predicate { var ( operator = PatternOperator(kuadrantv1beta3.StartsWithOperator) // default value value = "/" // default value @@ -187,36 +165,28 @@ func patternExpresionFromPathMatch(pathMatch gatewayapiv1.HTTPPathMatch) Pattern } } - return PatternExpression{ + return Predicate{ Selector: "request.url_path", Operator: operator, Value: value, } } -func patternExpresionFromMethod(method gatewayapiv1.HTTPMethod) PatternExpression { - return PatternExpression{ +func predicateFromMethod(method gatewayapiv1.HTTPMethod) Predicate { + return Predicate{ Selector: "request.method", Operator: PatternOperator(kuadrantv1beta3.EqualOperator), Value: string(method), } } -func patternExpresionFromHeader(headerMatch gatewayapiv1.HTTPHeaderMatch) PatternExpression { +func predicateFromHeader(headerMatch gatewayapiv1.HTTPHeaderMatch) Predicate { // As for gateway api v1, the only operation type with core support is Exact match. // https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io/v1.HTTPHeaderMatch - return PatternExpression{ + return Predicate{ Selector: kuadrantv1beta3.ContextSelector(fmt.Sprintf("request.headers.%s", headerMatch.Name)), Operator: PatternOperator(kuadrantv1beta3.EqualOperator), Value: headerMatch.Value, } } - -func patternExpresionFromWhenCondition(when kuadrantv1beta3.WhenCondition) PatternExpression { - return PatternExpression{ - Selector: when.Selector, - Operator: PatternOperator(when.Operator), - Value: when.Value, - } -} diff --git a/tests/commons.go b/tests/commons.go index a23e760d8..ba320e12a 100644 --- a/tests/commons.go +++ b/tests/commons.go @@ -68,6 +68,25 @@ func HostTwo(domain string) string { return fmt.Sprintf("%s.%s", "other.test", domain) } +func BuildBasicGatewayClass(gcName string, mutateFns ...func(*gatewayapiv1.GatewayClass)) *gatewayapiv1.GatewayClass { + gatewayClass := &gatewayapiv1.GatewayClass{ + TypeMeta: metav1.TypeMeta{ + Kind: "GatewayClass", + APIVersion: gatewayapiv1.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: gcName, + }, + Spec: gatewayapiv1.GatewayClassSpec{ + ControllerName: gatewayapiv1.GatewayController("test-controller"), + }, + } + for _, mutateFn := range mutateFns { + mutateFn(gatewayClass) + } + return gatewayClass +} + func BuildBasicGateway(gwName, ns string, mutateFns ...func(*gatewayapiv1.Gateway)) *gatewayapiv1.Gateway { gateway := &gatewayapiv1.Gateway{ TypeMeta: metav1.TypeMeta{ diff --git a/tests/envoygateway/wasm_controller_test.go b/tests/envoygateway/extension_reconciler_test.go similarity index 76% rename from tests/envoygateway/wasm_controller_test.go rename to tests/envoygateway/extension_reconciler_test.go index 261cee35c..9f0f3a8ec 100644 --- a/tests/envoygateway/wasm_controller_test.go +++ b/tests/envoygateway/extension_reconciler_test.go @@ -8,6 +8,7 @@ import ( "time" egv1alpha1 "github.com/envoyproxy/gateway/api/v1alpha1" + "github.com/kuadrant/policy-machinery/machinery" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -17,6 +18,7 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" + kuadrantv1 "github.com/kuadrant/kuadrant-operator/api/v1" kuadrantv1beta3 "github.com/kuadrant/kuadrant-operator/api/v1beta3" "github.com/kuadrant/kuadrant-operator/controllers" "github.com/kuadrant/kuadrant-operator/pkg/common" @@ -32,11 +34,13 @@ var _ = Describe("wasm controller", func() { var ( testNamespace string gwHost = fmt.Sprintf("*.toystore-%s.com", rand.String(4)) + gatewayClass *gatewayapiv1.GatewayClass gateway *gatewayapiv1.Gateway ) BeforeEach(func(ctx SpecContext) { testNamespace = tests.CreateNamespace(ctx, testClient()) + gatewayClass = tests.BuildBasicGatewayClass(tests.GatewayClassName) gateway = tests.NewGatewayBuilder(TestGatewayName, tests.GatewayClassName, testNamespace). WithHTTPListener("test-listener", gwHost). Gateway @@ -77,8 +81,9 @@ var _ = Describe("wasm controller", func() { Context("RateLimitPolicy attached to the gateway", func() { var ( - gwPolicy *kuadrantv1beta3.RateLimitPolicy - gwRoute *gatewayapiv1.HTTPRoute + gwPolicy *kuadrantv1beta3.RateLimitPolicy + gwRoute *gatewayapiv1.HTTPRoute + actionSetName string ) BeforeEach(func(ctx SpecContext) { @@ -87,6 +92,17 @@ var _ = Describe("wasm controller", func() { Expect(err).ToNot(HaveOccurred()) Eventually(tests.RouteIsAccepted(ctx, testClient(), client.ObjectKeyFromObject(gwRoute))).WithContext(ctx).Should(BeTrue()) + mGateway := &machinery.Gateway{Gateway: gateway} + mHTTPRoute := &machinery.HTTPRoute{HTTPRoute: gwRoute} + pathID := kuadrantv1.PathID([]machinery.Targetable{ + &machinery.GatewayClass{GatewayClass: gatewayClass}, + mGateway, + &machinery.Listener{Listener: &gateway.Spec.Listeners[0], Gateway: mGateway}, + mHTTPRoute, + &machinery.HTTPRouteRule{HTTPRouteRule: &gwRoute.Spec.Rules[0], HTTPRoute: mHTTPRoute}, + }) + actionSetName = fmt.Sprintf("%d-%s-%d", 0, pathID, 0) // Hostname: 0, HTTPRouteMatch: 0 + gwPolicy = policyFactory(func(policy *kuadrantv1beta3.RateLimitPolicy) { policy.Name = "gw" policy.Spec.TargetRef.Group = gatewayapiv1.GroupName @@ -148,47 +164,41 @@ var _ = Describe("wasm controller", func() { existingWASMConfig, err := wasm.ConfigFromJSON(ext.Spec.Wasm[0].Config) Expect(err).ToNot(HaveOccurred()) Expect(existingWASMConfig).To(Equal(&wasm.Config{ - Extensions: map[string]wasm.Extension{ - wasm.RateLimitExtensionName: { + Services: map[string]wasm.Service{ + wasm.RateLimitServiceName: { + Type: wasm.RateLimitServiceType, Endpoint: common.KuadrantRateLimitClusterName, FailureMode: wasm.FailureModeAllow, - Type: wasm.RateLimitExtensionType, }, }, - Policies: []wasm.Policy{ + ActionSets: []wasm.ActionSet{ { - Name: gwPolicyKey.String(), - Hostnames: []string{gwHost}, - Rules: []wasm.Rule{ - { - Conditions: []wasm.Condition{ - { - AllOf: []wasm.PatternExpression{ - { - Selector: "request.url_path", - Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), - Value: "/toy", - }, - { - Selector: "request.method", - Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), - Value: "GET", - }, - }, - }, + Name: actionSetName, + RouteRuleConditions: wasm.RouteRuleConditions{ + Hostnames: []string{gwHost}, + Matches: []wasm.Predicate{ + { + Selector: "request.url_path", + Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), + Value: "/toy", + }, + { + Selector: "request.method", + Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), + Value: "GET", }, - Actions: []wasm.Action{ + }, + }, + Actions: []wasm.Action{ + { + ServiceName: wasm.RateLimitServiceName, + Scope: controllers.LimitsNamespaceFromRoute(gwRoute), + Data: []wasm.DataType{ { - Scope: controllers.LimitsNamespaceFromRoute(gwRoute), - ExtensionName: wasm.RateLimitExtensionName, - Data: []wasm.DataType{ - { - Value: &wasm.Static{ - Static: wasm.StaticSpec{ - Key: controllers.LimitNameToLimitadorIdentifier(gwPolicyKey, "l1"), - Value: "1", - }, - }, + Value: &wasm.Static{ + Static: wasm.StaticSpec{ + Key: controllers.LimitNameToLimitadorIdentifier(gwPolicyKey, "l1"), + Value: "1", }, }, }, @@ -239,8 +249,9 @@ var _ = Describe("wasm controller", func() { Context("RateLimitPolicy attached to the route", func() { var ( - routePolicy *kuadrantv1beta3.RateLimitPolicy - gwRoute *gatewayapiv1.HTTPRoute + routePolicy *kuadrantv1beta3.RateLimitPolicy + gwRoute *gatewayapiv1.HTTPRoute + actionSetName string ) BeforeEach(func(ctx SpecContext) { @@ -249,6 +260,17 @@ var _ = Describe("wasm controller", func() { Expect(err).ToNot(HaveOccurred()) Eventually(tests.RouteIsAccepted(ctx, testClient(), client.ObjectKeyFromObject(gwRoute))).WithContext(ctx).Should(BeTrue()) + mGateway := &machinery.Gateway{Gateway: gateway} + mHTTPRoute := &machinery.HTTPRoute{HTTPRoute: gwRoute} + pathID := kuadrantv1.PathID([]machinery.Targetable{ + &machinery.GatewayClass{GatewayClass: gatewayClass}, + mGateway, + &machinery.Listener{Listener: &gateway.Spec.Listeners[0], Gateway: mGateway}, + mHTTPRoute, + &machinery.HTTPRouteRule{HTTPRouteRule: &gwRoute.Spec.Rules[0], HTTPRoute: mHTTPRoute}, + }) + actionSetName = fmt.Sprintf("%d-%s-%d", 0, pathID, 0) // Hostname: 0, HTTPRouteMatch: 0 + routePolicy = policyFactory(func(policy *kuadrantv1beta3.RateLimitPolicy) { policy.Name = "route" policy.Spec.TargetRef.Group = gatewayapiv1.GroupName @@ -309,47 +331,41 @@ var _ = Describe("wasm controller", func() { existingWASMConfig, err := wasm.ConfigFromJSON(ext.Spec.Wasm[0].Config) Expect(err).ToNot(HaveOccurred()) Expect(existingWASMConfig).To(Equal(&wasm.Config{ - Extensions: map[string]wasm.Extension{ - wasm.RateLimitExtensionName: { + Services: map[string]wasm.Service{ + wasm.RateLimitServiceName: { + Type: wasm.RateLimitServiceType, Endpoint: common.KuadrantRateLimitClusterName, FailureMode: wasm.FailureModeAllow, - Type: wasm.RateLimitExtensionType, }, }, - Policies: []wasm.Policy{ + ActionSets: []wasm.ActionSet{ { - Name: routePolicyKey.String(), - Hostnames: []string{string(gwRoute.Spec.Hostnames[0])}, - Rules: []wasm.Rule{ - { - Conditions: []wasm.Condition{ - { - AllOf: []wasm.PatternExpression{ - { - Selector: "request.url_path", - Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), - Value: "/toy", - }, - { - Selector: "request.method", - Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), - Value: "GET", - }, - }, - }, + Name: actionSetName, + RouteRuleConditions: wasm.RouteRuleConditions{ + Hostnames: []string{string(gwRoute.Spec.Hostnames[0])}, + Matches: []wasm.Predicate{ + { + Selector: "request.url_path", + Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), + Value: "/toy", + }, + { + Selector: "request.method", + Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), + Value: "GET", }, - Actions: []wasm.Action{ + }, + }, + Actions: []wasm.Action{ + { + ServiceName: wasm.RateLimitServiceName, + Scope: controllers.LimitsNamespaceFromRoute(gwRoute), + Data: []wasm.DataType{ { - Scope: controllers.LimitsNamespaceFromRoute(gwRoute), - ExtensionName: wasm.RateLimitExtensionName, - Data: []wasm.DataType{ - { - Value: &wasm.Static{ - Static: wasm.StaticSpec{ - Key: controllers.LimitNameToLimitadorIdentifier(routePolicyKey, "l1"), - Value: "1", - }, - }, + Value: &wasm.Static{ + Static: wasm.StaticSpec{ + Key: controllers.LimitNameToLimitadorIdentifier(routePolicyKey, "l1"), + Value: "1", }, }, }, diff --git a/tests/istio/rate_limiting_istio_wasmplugin_controller_test.go b/tests/istio/extension_reconciler_test.go similarity index 64% rename from tests/istio/rate_limiting_istio_wasmplugin_controller_test.go rename to tests/istio/extension_reconciler_test.go index c5fcadddf..86970b983 100644 --- a/tests/istio/rate_limiting_istio_wasmplugin_controller_test.go +++ b/tests/istio/extension_reconciler_test.go @@ -4,10 +4,12 @@ package istio_test import ( "context" + "fmt" "reflect" "time" "github.com/google/go-cmp/cmp" + "github.com/kuadrant/policy-machinery/machinery" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" istioclientgoextensionv1alpha1 "istio.io/client-go/pkg/apis/extensions/v1alpha1" @@ -19,6 +21,7 @@ import ( gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1" gatewayapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + kuadrantv1 "github.com/kuadrant/kuadrant-operator/api/v1" kuadrantv1beta3 "github.com/kuadrant/kuadrant-operator/api/v1beta3" "github.com/kuadrant/kuadrant-operator/controllers" "github.com/kuadrant/kuadrant-operator/pkg/common" @@ -59,12 +62,14 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { Context("Basic tests", func() { var ( - routeName = "toystore-route" - rlpName = "toystore-rlp" - gateway *gatewayapiv1.Gateway + routeName = "toystore-route" + rlpName = "toystore-rlp" + gatewayClass *gatewayapiv1.GatewayClass + gateway *gatewayapiv1.Gateway ) beforeEachCallback := func(ctx SpecContext) { + gatewayClass = tests.BuildBasicGatewayClass(tests.GatewayClassName) gateway = tests.BuildBasicGateway(TestGatewayName, testNamespace) err := testClient().Create(ctx, gateway) Expect(err).ToNot(HaveOccurred()) @@ -80,6 +85,16 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { Expect(err).ToNot(HaveOccurred()) Eventually(tests.RouteIsAccepted(ctx, testClient(), client.ObjectKeyFromObject(httpRoute))).WithContext(ctx).Should(BeTrue()) + mGateway := &machinery.Gateway{Gateway: gateway} + mHTTPRoute := &machinery.HTTPRoute{HTTPRoute: httpRoute} + pathID := kuadrantv1.PathID([]machinery.Targetable{ + &machinery.GatewayClass{GatewayClass: gatewayClass}, + mGateway, + &machinery.Listener{Listener: &gateway.Spec.Listeners[0], Gateway: mGateway}, + mHTTPRoute, + &machinery.HTTPRouteRule{HTTPRouteRule: &httpRoute.Spec.Rules[0], HTTPRoute: mHTTPRoute}, + }) + // create ratelimitpolicy rlp := &kuadrantv1beta3.RateLimitPolicy{ TypeMeta: metav1.TypeMeta{ @@ -129,47 +144,41 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { existingWASMConfig, err := wasm.ConfigFromStruct(existingWasmPlugin.Spec.PluginConfig) Expect(err).ToNot(HaveOccurred()) Expect(existingWASMConfig).To(Equal(&wasm.Config{ - Extensions: map[string]wasm.Extension{ - wasm.RateLimitExtensionName: { + Services: map[string]wasm.Service{ + wasm.RateLimitServiceName: { + Type: wasm.RateLimitServiceType, Endpoint: common.KuadrantRateLimitClusterName, FailureMode: wasm.FailureModeAllow, - Type: wasm.RateLimitExtensionType, }, }, - Policies: []wasm.Policy{ + ActionSets: []wasm.ActionSet{ { - Name: rlpKey.String(), - Hostnames: []string{"*.example.com"}, - Rules: []wasm.Rule{ - { - Conditions: []wasm.Condition{ - { - AllOf: []wasm.PatternExpression{ - { - Selector: "request.url_path", - Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), - Value: "/toy", - }, - { - Selector: "request.method", - Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), - Value: "GET", - }, - }, - }, + Name: fmt.Sprintf("%d-%s-%d", 0, pathID, 0), + RouteRuleConditions: wasm.RouteRuleConditions{ + Hostnames: []string{"*.example.com"}, + Matches: []wasm.Predicate{ + { + Selector: "request.url_path", + Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), + Value: "/toy", }, - Actions: []wasm.Action{ + { + Selector: "request.method", + Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), + Value: "GET", + }, + }, + }, + Actions: []wasm.Action{ + { + ServiceName: wasm.RateLimitServiceName, + Scope: controllers.LimitsNamespaceFromRoute(httpRoute), + Data: []wasm.DataType{ { - Scope: controllers.LimitsNamespaceFromRoute(httpRoute), - ExtensionName: wasm.RateLimitExtensionName, - Data: []wasm.DataType{ - { - Value: &wasm.Static{ - Static: wasm.StaticSpec{ - Key: controllers.LimitNameToLimitadorIdentifier(rlpKey, "l1"), - Value: "1", - }, - }, + Value: &wasm.Static{ + Static: wasm.StaticSpec{ + Key: controllers.LimitNameToLimitadorIdentifier(rlpKey, "l1"), + Value: "1", }, }, }, @@ -277,151 +286,389 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { Expect(err).ToNot(HaveOccurred()) existingWASMConfig, err := wasm.ConfigFromStruct(existingWasmPlugin.Spec.PluginConfig) Expect(err).ToNot(HaveOccurred()) - Expect(existingWASMConfig.Extensions).To(HaveKeyWithValue(wasm.RateLimitExtensionName, wasm.Extension{ + Expect(existingWASMConfig.Services).To(HaveKeyWithValue(wasm.RateLimitServiceName, wasm.Service{ Endpoint: common.KuadrantRateLimitClusterName, FailureMode: wasm.FailureModeAllow, - Type: wasm.RateLimitExtensionType, + Type: wasm.RateLimitServiceType, })) - Expect(existingWASMConfig.Policies).To(HaveLen(1)) - policy := existingWASMConfig.Policies[0] - Expect(policy.Name).To(Equal(rlpKey.String())) - Expect(policy.Hostnames).To(Equal([]string{"*.toystore.acme.com", "api.toystore.io"})) - Expect(policy.Rules).To(ContainElement(wasm.Rule{ // rule to activate the 'users' limit definition - Conditions: []wasm.Condition{ - { - AllOf: []wasm.PatternExpression{ - { - Selector: "request.url_path", - Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), - Value: "/toys", + Expect(existingWASMConfig.ActionSets).To(HaveLen(6)) + + basePath := []machinery.Targetable{ + &machinery.GatewayClass{GatewayClass: gatewayClass}, + &machinery.Gateway{Gateway: gateway}, + &machinery.Listener{Listener: &gateway.Spec.Listeners[0], Gateway: &machinery.Gateway{Gateway: gateway}}, + &machinery.HTTPRoute{HTTPRoute: httpRoute}, + } + httpRouteRuleToys := &machinery.HTTPRouteRule{HTTPRouteRule: &httpRoute.Spec.Rules[0]} + httpRouteRuleAssets := &machinery.HTTPRouteRule{HTTPRouteRule: &httpRoute.Spec.Rules[1]} + + // GET api.toystore.io/toys* + actionSet := existingWASMConfig.ActionSets[0] + pathID := kuadrantv1.PathID(append(basePath, httpRouteRuleToys)) + Expect(actionSet.Name).To(Equal(fmt.Sprintf("%d-%s-%d", 1, pathID, 0))) // Hostname: 1, HTTPRouteMatch: 0 + Expect(actionSet.RouteRuleConditions.Hostnames).To(Equal([]string{"api.toystore.io"})) + Expect(actionSet.RouteRuleConditions.Matches).To(ContainElements( + wasm.Predicate{ + Selector: "request.url_path", + Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), + Value: "/toys", + }, + wasm.Predicate{ + Selector: "request.method", + Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), + Value: "GET", + }, + )) + Expect(actionSet.Actions).To(HaveLen(2)) + Expect(actionSet.Actions).To(ContainElements( + wasm.Action{ // action to activate the 'users' limit definition + ServiceName: wasm.RateLimitServiceName, + Scope: controllers.LimitsNamespaceFromRoute(httpRoute), + Conditions: []wasm.Predicate{ + { + Selector: "auth.identity.group", + Operator: wasm.PatternOperator(kuadrantv1beta3.NotEqualOperator), + Value: "admin", + }, + }, + Data: []wasm.DataType{ + { + Value: &wasm.Static{ + Static: wasm.StaticSpec{ + Key: controllers.LimitNameToLimitadorIdentifier(rlpKey, "users"), + Value: "1", + }, }, - { - Selector: "request.method", - Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), - Value: "GET", + }, + { + Value: &wasm.Selector{ + Selector: wasm.SelectorSpec{ + Selector: kuadrantv1beta3.ContextSelector("auth.identity.username"), + }, }, - { - Selector: "auth.identity.group", - Operator: wasm.PatternOperator(kuadrantv1beta3.NotEqualOperator), - Value: "admin", + }, + }, + }, + wasm.Action{ // action to activate the 'all' limit definition + ServiceName: wasm.RateLimitServiceName, + Scope: controllers.LimitsNamespaceFromRoute(httpRoute), + Data: []wasm.DataType{ + { + Value: &wasm.Static{ + Static: wasm.StaticSpec{ + Key: controllers.LimitNameToLimitadorIdentifier(rlpKey, "all"), + Value: "1", + }, }, }, }, - { - AllOf: []wasm.PatternExpression{ - { - Selector: "request.url_path", - Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), - Value: "/toys", + }, + )) + + // POST api.toystore.io/toys* + actionSet = existingWASMConfig.ActionSets[1] + pathID = kuadrantv1.PathID(append(basePath, httpRouteRuleToys)) + Expect(actionSet.Name).To(Equal(fmt.Sprintf("%d-%s-%d", 1, pathID, 1))) // Hostname: 1, HTTPRouteMatch: 1 + Expect(actionSet.RouteRuleConditions.Hostnames).To(Equal([]string{"api.toystore.io"})) + Expect(actionSet.RouteRuleConditions.Matches).To(ContainElements( + wasm.Predicate{ + Selector: "request.url_path", + Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), + Value: "/toys", + }, + wasm.Predicate{ + Selector: "request.method", + Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), + Value: "POST", + }, + )) + Expect(actionSet.Actions).To(HaveLen(2)) + Expect(actionSet.Actions).To(ContainElements( + wasm.Action{ // action to activate the 'users' limit definition + ServiceName: wasm.RateLimitServiceName, + Scope: controllers.LimitsNamespaceFromRoute(httpRoute), + Conditions: []wasm.Predicate{ + { + Selector: "auth.identity.group", + Operator: wasm.PatternOperator(kuadrantv1beta3.NotEqualOperator), + Value: "admin", + }, + }, + Data: []wasm.DataType{ + { + Value: &wasm.Static{ + Static: wasm.StaticSpec{ + Key: controllers.LimitNameToLimitadorIdentifier(rlpKey, "users"), + Value: "1", + }, }, - { - Selector: "request.method", - Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), - Value: "POST", + }, + { + Value: &wasm.Selector{ + Selector: wasm.SelectorSpec{ + Selector: kuadrantv1beta3.ContextSelector("auth.identity.username"), + }, }, - { - Selector: "auth.identity.group", - Operator: wasm.PatternOperator(kuadrantv1beta3.NotEqualOperator), - Value: "admin", + }, + }, + }, + wasm.Action{ // action to activate the 'all' limit definition + ServiceName: wasm.RateLimitServiceName, + Scope: controllers.LimitsNamespaceFromRoute(httpRoute), + Data: []wasm.DataType{ + { + Value: &wasm.Static{ + Static: wasm.StaticSpec{ + Key: controllers.LimitNameToLimitadorIdentifier(rlpKey, "all"), + Value: "1", + }, }, }, }, - { - AllOf: []wasm.PatternExpression{ - { - Selector: "request.url_path", - Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), - Value: "/assets", + }, + )) + + // api.toystore.io/assets* + actionSet = existingWASMConfig.ActionSets[2] + pathID = kuadrantv1.PathID(append(basePath, httpRouteRuleAssets)) + Expect(actionSet.Name).To(Equal(fmt.Sprintf("%d-%s-%d", 1, pathID, 2))) // Hostname: 1, HTTPRouteMatch: 2 + Expect(actionSet.RouteRuleConditions.Hostnames).To(Equal([]string{"api.toystore.io"})) + Expect(actionSet.RouteRuleConditions.Matches).To(ContainElements( + wasm.Predicate{ + Selector: "request.url_path", + Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), + Value: "/assets", + }, + )) + Expect(actionSet.Actions).To(HaveLen(2)) + Expect(actionSet.Actions).To(ContainElements( + wasm.Action{ // action to activate the 'users' limit definition + ServiceName: wasm.RateLimitServiceName, + Scope: controllers.LimitsNamespaceFromRoute(httpRoute), + Conditions: []wasm.Predicate{ + { + Selector: "auth.identity.group", + Operator: wasm.PatternOperator(kuadrantv1beta3.NotEqualOperator), + Value: "admin", + }, + }, + Data: []wasm.DataType{ + { + Value: &wasm.Static{ + Static: wasm.StaticSpec{ + Key: controllers.LimitNameToLimitadorIdentifier(rlpKey, "users"), + Value: "1", + }, }, - { - Selector: "auth.identity.group", - Operator: wasm.PatternOperator(kuadrantv1beta3.NotEqualOperator), - Value: "admin", + }, + { + Value: &wasm.Selector{ + Selector: wasm.SelectorSpec{ + Selector: kuadrantv1beta3.ContextSelector("auth.identity.username"), + }, }, }, }, }, - Actions: []wasm.Action{ - { - Scope: controllers.LimitsNamespaceFromRoute(httpRoute), - ExtensionName: wasm.RateLimitExtensionName, - Data: []wasm.DataType{ - { - Value: &wasm.Static{ - Static: wasm.StaticSpec{ - Key: controllers.LimitNameToLimitadorIdentifier(rlpKey, "users"), - Value: "1", - }, + wasm.Action{ // action to activate the 'all' limit definition + ServiceName: wasm.RateLimitServiceName, + Scope: controllers.LimitsNamespaceFromRoute(httpRoute), + Data: []wasm.DataType{ + { + Value: &wasm.Static{ + Static: wasm.StaticSpec{ + Key: controllers.LimitNameToLimitadorIdentifier(rlpKey, "all"), + Value: "1", }, }, - { - Value: &wasm.Selector{ - Selector: wasm.SelectorSpec{ - Selector: kuadrantv1beta3.ContextSelector("auth.identity.username"), - }, + }, + }, + }, + )) + + // GET *.toystore.acme.com/toys* + actionSet = existingWASMConfig.ActionSets[3] + pathID = kuadrantv1.PathID(append(basePath, httpRouteRuleToys)) + Expect(actionSet.Name).To(Equal(fmt.Sprintf("%d-%s-%d", 0, pathID, 0))) // Hostname: 0, HTTPRouteMatch: 0 + Expect(actionSet.RouteRuleConditions.Hostnames).To(Equal([]string{"*.toystore.acme.com"})) + Expect(actionSet.RouteRuleConditions.Matches).To(ContainElements( + wasm.Predicate{ + Selector: "request.url_path", + Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), + Value: "/toys", + }, + wasm.Predicate{ + Selector: "request.method", + Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), + Value: "GET", + }, + )) + Expect(actionSet.Actions).To(HaveLen(2)) + Expect(actionSet.Actions).To(ContainElements( + wasm.Action{ // action to activate the 'users' limit definition + ServiceName: wasm.RateLimitServiceName, + Scope: controllers.LimitsNamespaceFromRoute(httpRoute), + Conditions: []wasm.Predicate{ + { + Selector: "auth.identity.group", + Operator: wasm.PatternOperator(kuadrantv1beta3.NotEqualOperator), + Value: "admin", + }, + }, + Data: []wasm.DataType{ + { + Value: &wasm.Static{ + Static: wasm.StaticSpec{ + Key: controllers.LimitNameToLimitadorIdentifier(rlpKey, "users"), + Value: "1", + }, + }, + }, + { + Value: &wasm.Selector{ + Selector: wasm.SelectorSpec{ + Selector: kuadrantv1beta3.ContextSelector("auth.identity.username"), }, }, }, }, }, - })) - Expect(policy.Rules).To(ContainElement(wasm.Rule{ // rule to activate the 'all' limit definition - Conditions: []wasm.Condition{ - { - AllOf: []wasm.PatternExpression{ - { - Selector: "request.url_path", - Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), - Value: "/toys", - }, - { - Selector: "request.method", - Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), - Value: "GET", + wasm.Action{ // action to activate the 'all' limit definition + ServiceName: wasm.RateLimitServiceName, + Scope: controllers.LimitsNamespaceFromRoute(httpRoute), + Data: []wasm.DataType{ + { + Value: &wasm.Static{ + Static: wasm.StaticSpec{ + Key: controllers.LimitNameToLimitadorIdentifier(rlpKey, "all"), + Value: "1", + }, }, }, }, - { - AllOf: []wasm.PatternExpression{ - { - Selector: "request.url_path", - Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), - Value: "/toys", + }, + )) + + // POST *.toystore.acme.com/toys* + actionSet = existingWASMConfig.ActionSets[4] + pathID = kuadrantv1.PathID(append(basePath, httpRouteRuleToys)) + Expect(actionSet.Name).To(Equal(fmt.Sprintf("%d-%s-%d", 0, pathID, 1))) // Hostname: 0, HTTPRouteMatch: 1 + Expect(actionSet.RouteRuleConditions.Hostnames).To(Equal([]string{"*.toystore.acme.com"})) + Expect(actionSet.RouteRuleConditions.Matches).To(ContainElements( + wasm.Predicate{ + Selector: "request.url_path", + Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), + Value: "/toys", + }, + wasm.Predicate{ + Selector: "request.method", + Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), + Value: "POST", + }, + )) + Expect(actionSet.Actions).To(HaveLen(2)) + Expect(actionSet.Actions).To(ContainElements( + wasm.Action{ // action to activate the 'users' limit definition + ServiceName: wasm.RateLimitServiceName, + Scope: controllers.LimitsNamespaceFromRoute(httpRoute), + Conditions: []wasm.Predicate{ + { + Selector: "auth.identity.group", + Operator: wasm.PatternOperator(kuadrantv1beta3.NotEqualOperator), + Value: "admin", + }, + }, + Data: []wasm.DataType{ + { + Value: &wasm.Static{ + Static: wasm.StaticSpec{ + Key: controllers.LimitNameToLimitadorIdentifier(rlpKey, "users"), + Value: "1", + }, }, - { - Selector: "request.method", - Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), - Value: "POST", + }, + { + Value: &wasm.Selector{ + Selector: wasm.SelectorSpec{ + Selector: kuadrantv1beta3.ContextSelector("auth.identity.username"), + }, }, }, }, - { - AllOf: []wasm.PatternExpression{ - { - Selector: "request.url_path", - Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), - Value: "/assets", + }, + wasm.Action{ // action to activate the 'all' limit definition + ServiceName: wasm.RateLimitServiceName, + Scope: controllers.LimitsNamespaceFromRoute(httpRoute), + Data: []wasm.DataType{ + { + Value: &wasm.Static{ + Static: wasm.StaticSpec{ + Key: controllers.LimitNameToLimitadorIdentifier(rlpKey, "all"), + Value: "1", + }, }, }, }, }, - Actions: []wasm.Action{ - { - Scope: controllers.LimitsNamespaceFromRoute(httpRoute), - ExtensionName: wasm.RateLimitExtensionName, - Data: []wasm.DataType{ - { - Value: &wasm.Static{ - Static: wasm.StaticSpec{ - Key: controllers.LimitNameToLimitadorIdentifier(rlpKey, "all"), - Value: "1", - }, + )) + + // *.toystore.acme.com/assets* + actionSet = existingWASMConfig.ActionSets[5] + pathID = kuadrantv1.PathID(append(basePath, httpRouteRuleAssets)) + Expect(actionSet.Name).To(Equal(fmt.Sprintf("%d-%s-%d", 0, pathID, 2))) // Hostname: 0, HTTPRouteMatch: 2 + Expect(actionSet.RouteRuleConditions.Hostnames).To(Equal([]string{"*.toystore.acme.com"})) + Expect(actionSet.RouteRuleConditions.Matches).To(ContainElements( + wasm.Predicate{ + Selector: "request.url_path", + Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), + Value: "/assets", + }, + )) + Expect(actionSet.Actions).To(HaveLen(2)) + Expect(actionSet.Actions).To(ContainElements( + wasm.Action{ // action to activate the 'users' limit definition + ServiceName: wasm.RateLimitServiceName, + Scope: controllers.LimitsNamespaceFromRoute(httpRoute), + Conditions: []wasm.Predicate{ + { + Selector: "auth.identity.group", + Operator: wasm.PatternOperator(kuadrantv1beta3.NotEqualOperator), + Value: "admin", + }, + }, + Data: []wasm.DataType{ + { + Value: &wasm.Static{ + Static: wasm.StaticSpec{ + Key: controllers.LimitNameToLimitadorIdentifier(rlpKey, "users"), + Value: "1", + }, + }, + }, + { + Value: &wasm.Selector{ + Selector: wasm.SelectorSpec{ + Selector: kuadrantv1beta3.ContextSelector("auth.identity.username"), }, }, }, }, }, - })) + wasm.Action{ // action to activate the 'all' limit definition + ServiceName: wasm.RateLimitServiceName, + Scope: controllers.LimitsNamespaceFromRoute(httpRoute), + Data: []wasm.DataType{ + { + Value: &wasm.Static{ + Static: wasm.StaticSpec{ + Key: controllers.LimitNameToLimitadorIdentifier(rlpKey, "all"), + Value: "1", + }, + }, + }, + }, + }, + )) }, testTimeOut) It("Simple RLP targeting Gateway parented by one HTTPRoute creates wasmplugin", func(ctx SpecContext) { @@ -431,6 +678,16 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { Expect(err).ToNot(HaveOccurred()) Eventually(tests.RouteIsAccepted(ctx, testClient(), client.ObjectKeyFromObject(httpRoute))).WithContext(ctx).Should(BeTrue()) + mGateway := &machinery.Gateway{Gateway: gateway} + mHTTPRoute := &machinery.HTTPRoute{HTTPRoute: httpRoute} + pathID := kuadrantv1.PathID([]machinery.Targetable{ + &machinery.GatewayClass{GatewayClass: gatewayClass}, + mGateway, + &machinery.Listener{Listener: &gateway.Spec.Listeners[0], Gateway: mGateway}, + mHTTPRoute, + &machinery.HTTPRouteRule{HTTPRouteRule: &httpRoute.Spec.Rules[0], HTTPRoute: mHTTPRoute}, + }) + // create ratelimitpolicy rlp := &kuadrantv1beta3.RateLimitPolicy{ TypeMeta: metav1.TypeMeta{ @@ -475,47 +732,41 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { existingWASMConfig, err := wasm.ConfigFromStruct(existingWasmPlugin.Spec.PluginConfig) Expect(err).ToNot(HaveOccurred()) Expect(existingWASMConfig).To(Equal(&wasm.Config{ - Extensions: map[string]wasm.Extension{ - wasm.RateLimitExtensionName: { + Services: map[string]wasm.Service{ + wasm.RateLimitServiceName: { Endpoint: common.KuadrantRateLimitClusterName, FailureMode: wasm.FailureModeAllow, - Type: wasm.RateLimitExtensionType, + Type: wasm.RateLimitServiceType, }, }, - Policies: []wasm.Policy{ + ActionSets: []wasm.ActionSet{ { - Name: rlpKey.String(), - Hostnames: []string{"*"}, - Rules: []wasm.Rule{ - { - Conditions: []wasm.Condition{ - { - AllOf: []wasm.PatternExpression{ - { - Selector: "request.url_path", - Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), - Value: "/toy", - }, - { - Selector: "request.method", - Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), - Value: "GET", - }, - }, - }, + Name: fmt.Sprintf("%d-%s-%d", 0, pathID, 0), + RouteRuleConditions: wasm.RouteRuleConditions{ + Hostnames: []string{"*"}, + Matches: []wasm.Predicate{ + { + Selector: "request.url_path", + Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), + Value: "/toy", + }, + { + Selector: "request.method", + Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), + Value: "GET", }, - Actions: []wasm.Action{ + }, + }, + Actions: []wasm.Action{ + { + ServiceName: wasm.RateLimitServiceName, + Scope: controllers.LimitsNamespaceFromRoute(httpRoute), + Data: []wasm.DataType{ { - Scope: controllers.LimitsNamespaceFromRoute(httpRoute), - ExtensionName: wasm.RateLimitExtensionName, - Data: []wasm.DataType{ - { - Value: &wasm.Static{ - Static: wasm.StaticSpec{ - Key: controllers.LimitNameToLimitadorIdentifier(rlpKey, "l1"), - Value: "1", - }, - }, + Value: &wasm.Static{ + Static: wasm.StaticSpec{ + Key: controllers.LimitNameToLimitadorIdentifier(rlpKey, "l1"), + Value: "1", }, }, }, @@ -596,11 +847,13 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { routeName = "route-a" rlpName = "rlp-a" TestGatewayName = "toystore-gw" + gatewayClass *gatewayapiv1.GatewayClass gateway *gatewayapiv1.Gateway gwBName = "gw-b" ) beforeEachCallback := func(ctx SpecContext) { + gatewayClass = tests.BuildBasicGatewayClass(tests.GatewayClassName) gateway = tests.BuildBasicGateway(TestGatewayName, testNamespace) err := testClient().Create(ctx, gateway) Expect(err).ToNot(HaveOccurred()) @@ -671,6 +924,16 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { Expect(err).ToNot(HaveOccurred()) Eventually(tests.GatewayIsReady(ctx, testClient(), gwB)).WithContext(ctx).Should(BeTrue()) + mGateway := &machinery.Gateway{Gateway: gateway} + mHTTPRoute := &machinery.HTTPRoute{HTTPRoute: httpRoute} + pathID := kuadrantv1.PathID([]machinery.Targetable{ + &machinery.GatewayClass{GatewayClass: gatewayClass}, + mGateway, + &machinery.Listener{Listener: &gateway.Spec.Listeners[0], Gateway: mGateway}, + mHTTPRoute, + &machinery.HTTPRouteRule{HTTPRouteRule: &httpRoute.Spec.Rules[0], HTTPRoute: mHTTPRoute}, + }) + // Initial state set. // Check wasm plugin for gateway A has configuration from the route // it may take some reconciliation loops to get to that, so checking it with eventually @@ -691,47 +954,41 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { } expectedPlugin := &wasm.Config{ - Extensions: map[string]wasm.Extension{ - wasm.RateLimitExtensionName: { + Services: map[string]wasm.Service{ + wasm.RateLimitServiceName: { Endpoint: common.KuadrantRateLimitClusterName, FailureMode: wasm.FailureModeAllow, - Type: wasm.RateLimitExtensionType, + Type: wasm.RateLimitServiceType, }, }, - Policies: []wasm.Policy{ + ActionSets: []wasm.ActionSet{ { - Name: rlpKey.String(), - Hostnames: []string{"*"}, - Rules: []wasm.Rule{ - { - Conditions: []wasm.Condition{ - { - AllOf: []wasm.PatternExpression{ - { - Selector: "request.url_path", - Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), - Value: "/toy", - }, - { - Selector: "request.method", - Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), - Value: "GET", - }, - }, - }, + Name: fmt.Sprintf("%d-%s-%d", 0, pathID, 0), + RouteRuleConditions: wasm.RouteRuleConditions{ + Hostnames: []string{"*"}, + Matches: []wasm.Predicate{ + { + Selector: "request.url_path", + Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), + Value: "/toy", }, - Actions: []wasm.Action{ + { + Selector: "request.method", + Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), + Value: "GET", + }, + }, + }, + Actions: []wasm.Action{ + { + ServiceName: wasm.RateLimitServiceName, + Scope: controllers.LimitsNamespaceFromRoute(httpRoute), + Data: []wasm.DataType{ { - Scope: controllers.LimitsNamespaceFromRoute(httpRoute), - ExtensionName: wasm.RateLimitExtensionName, - Data: []wasm.DataType{ - { - Value: &wasm.Static{ - Static: wasm.StaticSpec{ - Key: controllers.LimitNameToLimitadorIdentifier(rlpKey, "l1"), - Value: "1", - }, - }, + Value: &wasm.Static{ + Static: wasm.StaticSpec{ + Key: controllers.LimitNameToLimitadorIdentifier(rlpKey, "l1"), + Value: "1", }, }, }, @@ -847,6 +1104,16 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { Expect(err).ToNot(HaveOccurred()) Eventually(tests.RouteIsAccepted(ctx, testClient(), client.ObjectKeyFromObject(httpRoute))).WithContext(ctx).Should(BeTrue()) + mGateway := &machinery.Gateway{Gateway: gateway} + mHTTPRoute := &machinery.HTTPRoute{HTTPRoute: httpRoute} + pathID := kuadrantv1.PathID([]machinery.Targetable{ + &machinery.GatewayClass{GatewayClass: gatewayClass}, + mGateway, + &machinery.Listener{Listener: &gateway.Spec.Listeners[0], Gateway: mGateway}, + mHTTPRoute, + &machinery.HTTPRouteRule{HTTPRouteRule: &httpRoute.Spec.Rules[0], HTTPRoute: mHTTPRoute}, + }) + // create RLP A -> Route A rlpA := &kuadrantv1beta3.RateLimitPolicy{ TypeMeta: metav1.TypeMeta{ @@ -900,47 +1167,41 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { } expectedPlugin := &wasm.Config{ - Extensions: map[string]wasm.Extension{ - wasm.RateLimitExtensionName: { + Services: map[string]wasm.Service{ + wasm.RateLimitServiceName: { Endpoint: common.KuadrantRateLimitClusterName, FailureMode: wasm.FailureModeAllow, - Type: wasm.RateLimitExtensionType, + Type: wasm.RateLimitServiceType, }, }, - Policies: []wasm.Policy{ + ActionSets: []wasm.ActionSet{ { - Name: rlpKey.String(), - Hostnames: []string{"*.example.com"}, - Rules: []wasm.Rule{ - { - Conditions: []wasm.Condition{ - { - AllOf: []wasm.PatternExpression{ - { - Selector: "request.url_path", - Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), - Value: "/toy", - }, - { - Selector: "request.method", - Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), - Value: "GET", - }, - }, - }, + Name: fmt.Sprintf("%d-%s-%d", 0, pathID, 0), + RouteRuleConditions: wasm.RouteRuleConditions{ + Hostnames: []string{"*.example.com"}, + Matches: []wasm.Predicate{ + { + Selector: "request.url_path", + Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), + Value: "/toy", + }, + { + Selector: "request.method", + Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), + Value: "GET", }, - Actions: []wasm.Action{ + }, + }, + Actions: []wasm.Action{ + { + ServiceName: wasm.RateLimitServiceName, + Scope: controllers.LimitsNamespaceFromRoute(httpRoute), + Data: []wasm.DataType{ { - Scope: controllers.LimitsNamespaceFromRoute(httpRoute), - ExtensionName: wasm.RateLimitExtensionName, - Data: []wasm.DataType{ - { - Value: &wasm.Static{ - Static: wasm.StaticSpec{ - Key: controllers.LimitNameToLimitadorIdentifier(rlpKey, "l1"), - Value: "1", - }, - }, + Value: &wasm.Static{ + Static: wasm.StaticSpec{ + Key: controllers.LimitNameToLimitadorIdentifier(rlpKey, "l1"), + Value: "1", }, }, }, @@ -1009,6 +1270,15 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { return true }) + mGateway = &machinery.Gateway{Gateway: gwB} + pathID = kuadrantv1.PathID([]machinery.Targetable{ + &machinery.GatewayClass{GatewayClass: gatewayClass}, + mGateway, + &machinery.Listener{Listener: &gateway.Spec.Listeners[0], Gateway: mGateway}, + mHTTPRoute, + &machinery.HTTPRouteRule{HTTPRouteRule: &httpRoute.Spec.Rules[0], HTTPRoute: mHTTPRoute}, + }) + // Check wasm plugin for gateway B has configuration from the route // it may take some reconciliation loops to get to that, so checking it with eventually Eventually(func() bool { @@ -1028,47 +1298,41 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { } expectedPlugin := &wasm.Config{ - Extensions: map[string]wasm.Extension{ - wasm.RateLimitExtensionName: { + Services: map[string]wasm.Service{ + wasm.RateLimitServiceName: { Endpoint: common.KuadrantRateLimitClusterName, FailureMode: wasm.FailureModeAllow, - Type: wasm.RateLimitExtensionType, + Type: wasm.RateLimitServiceType, }, }, - Policies: []wasm.Policy{ + ActionSets: []wasm.ActionSet{ { - Name: rlpKey.String(), - Hostnames: []string{"*.example.com"}, - Rules: []wasm.Rule{ - { - Conditions: []wasm.Condition{ - { - AllOf: []wasm.PatternExpression{ - { - Selector: "request.url_path", - Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), - Value: "/toy", - }, - { - Selector: "request.method", - Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), - Value: "GET", - }, - }, - }, + Name: fmt.Sprintf("%d-%s-%d", 0, pathID, 0), + RouteRuleConditions: wasm.RouteRuleConditions{ + Hostnames: []string{"*.example.com"}, + Matches: []wasm.Predicate{ + { + Selector: "request.url_path", + Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), + Value: "/toy", + }, + { + Selector: "request.method", + Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), + Value: "GET", }, - Actions: []wasm.Action{ + }, + }, + Actions: []wasm.Action{ + { + ServiceName: wasm.RateLimitServiceName, + Scope: controllers.LimitsNamespaceFromRoute(httpRoute), + Data: []wasm.DataType{ { - Scope: controllers.LimitsNamespaceFromRoute(httpRoute), - ExtensionName: wasm.RateLimitExtensionName, - Data: []wasm.DataType{ - { - Value: &wasm.Static{ - Static: wasm.StaticSpec{ - Key: controllers.LimitNameToLimitadorIdentifier(rlpKey, "l1"), - Value: "1", - }, - }, + Value: &wasm.Static{ + Static: wasm.StaticSpec{ + Key: controllers.LimitNameToLimitadorIdentifier(rlpKey, "l1"), + Value: "1", }, }, }, @@ -1093,10 +1357,12 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { Context("RLP switches targetRef from one route A to another route B", func() { var ( TestGatewayName = "toystore-gw" + gatewayClass *gatewayapiv1.GatewayClass gateway *gatewayapiv1.Gateway ) beforeEachCallback := func(ctx SpecContext) { + gatewayClass = tests.BuildBasicGatewayClass(tests.GatewayClassName) gateway = tests.BuildBasicGateway(TestGatewayName, testNamespace) err := testClient().Create(ctx, gateway) Expect(err).ToNot(HaveOccurred()) @@ -1203,6 +1469,16 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { rlpKey := client.ObjectKey{Name: rlpName, Namespace: testNamespace} Eventually(assertPolicyIsAcceptedAndEnforced(ctx, rlpKey)).WithContext(ctx).Should(BeTrue()) + mGateway := &machinery.Gateway{Gateway: gateway} + mHTTPRoute := &machinery.HTTPRoute{HTTPRoute: httpRouteA} + pathID := kuadrantv1.PathID([]machinery.Targetable{ + &machinery.GatewayClass{GatewayClass: gatewayClass}, + mGateway, + &machinery.Listener{Listener: &gateway.Spec.Listeners[0], Gateway: mGateway}, + mHTTPRoute, + &machinery.HTTPRouteRule{HTTPRouteRule: &httpRouteA.Spec.Rules[0], HTTPRoute: mHTTPRoute}, + }) + // Initial state set. // Check wasm plugin has configuration from the route A // it may take some reconciliation loops to get to that, so checking it with eventually @@ -1223,47 +1499,41 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { } expectedPlugin := &wasm.Config{ - Extensions: map[string]wasm.Extension{ - wasm.RateLimitExtensionName: { + Services: map[string]wasm.Service{ + wasm.RateLimitServiceName: { Endpoint: common.KuadrantRateLimitClusterName, FailureMode: wasm.FailureModeAllow, - Type: wasm.RateLimitExtensionType, + Type: wasm.RateLimitServiceType, }, }, - Policies: []wasm.Policy{ + ActionSets: []wasm.ActionSet{ { - Name: rlpKey.String(), - Hostnames: []string{"*.a.example.com"}, - Rules: []wasm.Rule{ - { - Conditions: []wasm.Condition{ - { - AllOf: []wasm.PatternExpression{ - { - Selector: "request.url_path", - Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), - Value: "/routeA", - }, - { - Selector: "request.method", - Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), - Value: "GET", - }, - }, - }, + Name: fmt.Sprintf("%d-%s-%d", 0, pathID, 0), + RouteRuleConditions: wasm.RouteRuleConditions{ + Hostnames: []string{"*.a.example.com"}, + Matches: []wasm.Predicate{ + { + Selector: "request.url_path", + Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), + Value: "/routeA", + }, + { + Selector: "request.method", + Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), + Value: "GET", }, - Actions: []wasm.Action{ + }, + }, + Actions: []wasm.Action{ + { + ServiceName: wasm.RateLimitServiceName, + Scope: controllers.LimitsNamespaceFromRoute(httpRouteA), + Data: []wasm.DataType{ { - Scope: controllers.LimitsNamespaceFromRoute(httpRouteA), - ExtensionName: wasm.RateLimitExtensionName, - Data: []wasm.DataType{ - { - Value: &wasm.Static{ - Static: wasm.StaticSpec{ - Key: controllers.LimitNameToLimitadorIdentifier(rlpKey, "l1"), - Value: "1", - }, - }, + Value: &wasm.Static{ + Static: wasm.StaticSpec{ + Key: controllers.LimitNameToLimitadorIdentifier(rlpKey, "l1"), + Value: "1", }, }, }, @@ -1295,6 +1565,15 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { g.Expect(err).ToNot(HaveOccurred()) }).WithContext(ctx).Should(Succeed()) + mHTTPRoute = &machinery.HTTPRoute{HTTPRoute: httpRouteB} + pathID = kuadrantv1.PathID([]machinery.Targetable{ + &machinery.GatewayClass{GatewayClass: gatewayClass}, + mGateway, + &machinery.Listener{Listener: &gateway.Spec.Listeners[0], Gateway: mGateway}, + mHTTPRoute, + &machinery.HTTPRouteRule{HTTPRouteRule: &httpRouteB.Spec.Rules[0], HTTPRoute: mHTTPRoute}, + }) + // Check wasm plugin has configuration from the route B // it may take some reconciliation loops to get to that, so checking it with eventually Eventually(func() bool { @@ -1314,47 +1593,41 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { } expectedPlugin := &wasm.Config{ - Extensions: map[string]wasm.Extension{ - wasm.RateLimitExtensionName: { + Services: map[string]wasm.Service{ + wasm.RateLimitServiceName: { Endpoint: common.KuadrantRateLimitClusterName, FailureMode: wasm.FailureModeAllow, - Type: wasm.RateLimitExtensionType, + Type: wasm.RateLimitServiceType, }, }, - Policies: []wasm.Policy{ + ActionSets: []wasm.ActionSet{ { - Name: rlpKey.String(), - Hostnames: []string{"*.b.example.com"}, - Rules: []wasm.Rule{ - { - Conditions: []wasm.Condition{ - { - AllOf: []wasm.PatternExpression{ - { - Selector: "request.url_path", - Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), - Value: "/routeB", - }, - { - Selector: "request.method", - Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), - Value: "GET", - }, - }, - }, + Name: fmt.Sprintf("%d-%s-%d", 0, pathID, 0), + RouteRuleConditions: wasm.RouteRuleConditions{ + Hostnames: []string{"*.b.example.com"}, + Matches: []wasm.Predicate{ + { + Selector: "request.url_path", + Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), + Value: "/routeB", }, - Actions: []wasm.Action{ + { + Selector: "request.method", + Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), + Value: "GET", + }, + }, + }, + Actions: []wasm.Action{ + { + ServiceName: wasm.RateLimitServiceName, + Scope: controllers.LimitsNamespaceFromRoute(httpRouteA), + Data: []wasm.DataType{ { - Scope: controllers.LimitsNamespaceFromRoute(httpRouteA), - ExtensionName: wasm.RateLimitExtensionName, - Data: []wasm.DataType{ - { - Value: &wasm.Static{ - Static: wasm.StaticSpec{ - Key: controllers.LimitNameToLimitadorIdentifier(rlpKey, "l1"), - Value: "1", - }, - }, + Value: &wasm.Static{ + Static: wasm.StaticSpec{ + Key: controllers.LimitNameToLimitadorIdentifier(rlpKey, "l1"), + Value: "1", }, }, }, @@ -1379,10 +1652,12 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { Context("Free Route gets dedicated RLP", func() { var ( TestGatewayName = "toystore-gw" + gatewayClass *gatewayapiv1.GatewayClass gateway *gatewayapiv1.Gateway ) beforeEachCallback := func(ctx SpecContext) { + gatewayClass = tests.BuildBasicGatewayClass(tests.GatewayClassName) gateway = tests.BuildBasicGateway(TestGatewayName, testNamespace) err := testClient().Create(ctx, gateway) Expect(err).ToNot(HaveOccurred()) @@ -1464,6 +1739,16 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { rlp1Key := client.ObjectKey{Name: rlp1Name, Namespace: testNamespace} Eventually(assertPolicyIsAcceptedAndEnforced(ctx, rlp1Key)).WithContext(ctx).Should(BeTrue()) + mGateway := &machinery.Gateway{Gateway: gateway} + mHTTPRoute := &machinery.HTTPRoute{HTTPRoute: httpRouteA} + pathID := kuadrantv1.PathID([]machinery.Targetable{ + &machinery.GatewayClass{GatewayClass: gatewayClass}, + mGateway, + &machinery.Listener{Listener: &gateway.Spec.Listeners[0], Gateway: mGateway}, + mHTTPRoute, + &machinery.HTTPRouteRule{HTTPRouteRule: &httpRouteA.Spec.Rules[0], HTTPRoute: mHTTPRoute}, + }) + // Initial state set. // Check wasm plugin for gateway A has configuration from the route 1 // it may take some reconciliation loops to get to that, so checking it with eventually @@ -1484,47 +1769,41 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { } expectedPlugin := &wasm.Config{ - Extensions: map[string]wasm.Extension{ - wasm.RateLimitExtensionName: { + Services: map[string]wasm.Service{ + wasm.RateLimitServiceName: { Endpoint: common.KuadrantRateLimitClusterName, FailureMode: wasm.FailureModeAllow, - Type: wasm.RateLimitExtensionType, + Type: wasm.RateLimitServiceType, }, }, - Policies: []wasm.Policy{ + ActionSets: []wasm.ActionSet{ { - Name: rlp1Key.String(), - Hostnames: []string{"*"}, - Rules: []wasm.Rule{ - { - Conditions: []wasm.Condition{ - { - AllOf: []wasm.PatternExpression{ - { - Selector: "request.url_path", - Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), - Value: "/routeA", - }, - { - Selector: "request.method", - Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), - Value: "GET", - }, - }, - }, + Name: fmt.Sprintf("%d-%s-%d", 0, pathID, 0), + RouteRuleConditions: wasm.RouteRuleConditions{ + Hostnames: []string{"*"}, + Matches: []wasm.Predicate{ + { + Selector: "request.url_path", + Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), + Value: "/routeA", }, - Actions: []wasm.Action{ + { + Selector: "request.method", + Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), + Value: "GET", + }, + }, + }, + Actions: []wasm.Action{ + { + ServiceName: wasm.RateLimitServiceName, + Scope: controllers.LimitsNamespaceFromRoute(httpRouteA), + Data: []wasm.DataType{ { - Scope: controllers.LimitsNamespaceFromRoute(httpRouteA), - ExtensionName: wasm.RateLimitExtensionName, - Data: []wasm.DataType{ - { - Value: &wasm.Static{ - Static: wasm.StaticSpec{ - Key: controllers.LimitNameToLimitadorIdentifier(rlp1Key, "gatewaylimit"), - Value: "1", - }, - }, + Value: &wasm.Static{ + Static: wasm.StaticSpec{ + Key: controllers.LimitNameToLimitadorIdentifier(rlp1Key, "gatewaylimit"), + Value: "1", }, }, }, @@ -1602,47 +1881,41 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { } expectedPlugin := &wasm.Config{ - Extensions: map[string]wasm.Extension{ - wasm.RateLimitExtensionName: { + Services: map[string]wasm.Service{ + wasm.RateLimitServiceName: { Endpoint: common.KuadrantRateLimitClusterName, FailureMode: wasm.FailureModeAllow, - Type: wasm.RateLimitExtensionType, + Type: wasm.RateLimitServiceType, }, }, - Policies: []wasm.Policy{ + ActionSets: []wasm.ActionSet{ { - Name: rlp2Key.String(), - Hostnames: []string{"*.a.example.com"}, - Rules: []wasm.Rule{ - { - Conditions: []wasm.Condition{ - { - AllOf: []wasm.PatternExpression{ - { - Selector: "request.url_path", - Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), - Value: "/routeA", - }, - { - Selector: "request.method", - Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), - Value: "GET", - }, - }, - }, + Name: fmt.Sprintf("%d-%s-%d", 0, pathID, 0), + RouteRuleConditions: wasm.RouteRuleConditions{ + Hostnames: []string{"*.a.example.com"}, + Matches: []wasm.Predicate{ + { + Selector: "request.url_path", + Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), + Value: "/routeA", + }, + { + Selector: "request.method", + Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), + Value: "GET", }, - Actions: []wasm.Action{ + }, + }, + Actions: []wasm.Action{ + { + ServiceName: wasm.RateLimitServiceName, + Scope: controllers.LimitsNamespaceFromRoute(httpRouteA), + Data: []wasm.DataType{ { - Scope: controllers.LimitsNamespaceFromRoute(httpRouteA), - ExtensionName: wasm.RateLimitExtensionName, - Data: []wasm.DataType{ - { - Value: &wasm.Static{ - Static: wasm.StaticSpec{ - Key: controllers.LimitNameToLimitadorIdentifier(rlp2Key, "routelimit"), - Value: "1", - }, - }, + Value: &wasm.Static{ + Static: wasm.StaticSpec{ + Key: controllers.LimitNameToLimitadorIdentifier(rlp2Key, "routelimit"), + Value: "1", }, }, }, @@ -1667,10 +1940,12 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { Context("New free route on a Gateway with RLP", func() { var ( TestGatewayName = "toystore-gw" + gatewayClass *gatewayapiv1.GatewayClass gateway *gatewayapiv1.Gateway ) beforeEachCallback := func(ctx SpecContext) { + gatewayClass = tests.BuildBasicGatewayClass(tests.GatewayClassName) gateway = tests.BuildBasicGateway(TestGatewayName, testNamespace) err := testClient().Create(ctx, gateway) Expect(err).ToNot(HaveOccurred()) @@ -1788,6 +2063,16 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { rlp2Key := client.ObjectKey{Name: rlp2Name, Namespace: testNamespace} Eventually(assertPolicyIsAcceptedAndEnforced(ctx, rlp2Key)).WithContext(ctx).Should(BeTrue()) + mGateway := &machinery.Gateway{Gateway: gateway} + mHTTPRoute := &machinery.HTTPRoute{HTTPRoute: httpRouteA} + pathID := kuadrantv1.PathID([]machinery.Targetable{ + &machinery.GatewayClass{GatewayClass: gatewayClass}, + mGateway, + &machinery.Listener{Listener: &gateway.Spec.Listeners[0], Gateway: mGateway}, + mHTTPRoute, + &machinery.HTTPRouteRule{HTTPRouteRule: &httpRouteA.Spec.Rules[0], HTTPRoute: mHTTPRoute}, + }) + // Initial state set. // Check wasm plugin for gateway A has configuration from the route A only affected by RLP 2 // it may take some reconciliation loops to get to that, so checking it with eventually @@ -1808,47 +2093,41 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { } expectedPlugin := &wasm.Config{ - Extensions: map[string]wasm.Extension{ - wasm.RateLimitExtensionName: { + Services: map[string]wasm.Service{ + wasm.RateLimitServiceName: { Endpoint: common.KuadrantRateLimitClusterName, FailureMode: wasm.FailureModeAllow, - Type: wasm.RateLimitExtensionType, + Type: wasm.RateLimitServiceType, }, }, - Policies: []wasm.Policy{ + ActionSets: []wasm.ActionSet{ { - Name: rlp2Key.String(), - Hostnames: []string{"*.a.example.com"}, - Rules: []wasm.Rule{ - { - Conditions: []wasm.Condition{ - { - AllOf: []wasm.PatternExpression{ - { - Selector: "request.url_path", - Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), - Value: "/routeA", - }, - { - Selector: "request.method", - Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), - Value: "GET", - }, - }, - }, + Name: fmt.Sprintf("%d-%s-%d", 0, pathID, 0), + RouteRuleConditions: wasm.RouteRuleConditions{ + Hostnames: []string{"*.a.example.com"}, + Matches: []wasm.Predicate{ + { + Selector: "request.url_path", + Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), + Value: "/routeA", + }, + { + Selector: "request.method", + Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), + Value: "GET", }, - Actions: []wasm.Action{ + }, + }, + Actions: []wasm.Action{ + { + ServiceName: wasm.RateLimitServiceName, + Scope: controllers.LimitsNamespaceFromRoute(httpRouteA), + Data: []wasm.DataType{ { - Scope: controllers.LimitsNamespaceFromRoute(httpRouteA), - ExtensionName: wasm.RateLimitExtensionName, - Data: []wasm.DataType{ - { - Value: &wasm.Static{ - Static: wasm.StaticSpec{ - Key: controllers.LimitNameToLimitadorIdentifier(rlp2Key, "routelimit"), - Value: "1", - }, - }, + Value: &wasm.Static{ + Static: wasm.StaticSpec{ + Key: controllers.LimitNameToLimitadorIdentifier(rlp2Key, "routelimit"), + Value: "1", }, }, }, @@ -1913,48 +2192,51 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { return false } + mHTTPRoute := &machinery.HTTPRoute{HTTPRoute: httpRouteB} + pathID_B := kuadrantv1.PathID([]machinery.Targetable{ + &machinery.GatewayClass{GatewayClass: gatewayClass}, + mGateway, + &machinery.Listener{Listener: &gateway.Spec.Listeners[0], Gateway: mGateway}, + mHTTPRoute, + &machinery.HTTPRouteRule{HTTPRouteRule: &httpRouteB.Spec.Rules[0], HTTPRoute: mHTTPRoute}, + }) + expectedPlugin := &wasm.Config{ - Extensions: map[string]wasm.Extension{ - wasm.RateLimitExtensionName: { + Services: map[string]wasm.Service{ + wasm.RateLimitServiceName: { Endpoint: common.KuadrantRateLimitClusterName, FailureMode: wasm.FailureModeAllow, - Type: wasm.RateLimitExtensionType, + Type: wasm.RateLimitServiceType, }, }, - Policies: []wasm.Policy{ - { // First RLP 1 as the controller will sort based on RLP name - Name: rlp1Key.String(), // Route B affected by RLP 1 -> Gateway - Hostnames: []string{"*"}, - Rules: []wasm.Rule{ - { - Conditions: []wasm.Condition{ - { - AllOf: []wasm.PatternExpression{ - { - Selector: "request.url_path", - Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), - Value: "/routeB", - }, - { - Selector: "request.method", - Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), - Value: "GET", - }, - }, - }, + ActionSets: []wasm.ActionSet{ + { + Name: fmt.Sprintf("%d-%s-%d", 0, pathID, 0), // Route A affected by RLP 1 -> Route A + RouteRuleConditions: wasm.RouteRuleConditions{ + Hostnames: []string{"*.a.example.com"}, + Matches: []wasm.Predicate{ + { + Selector: "request.url_path", + Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), + Value: "/routeA", + }, + { + Selector: "request.method", + Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), + Value: "GET", }, - Actions: []wasm.Action{ + }, + }, + Actions: []wasm.Action{ + { + ServiceName: wasm.RateLimitServiceName, + Scope: controllers.LimitsNamespaceFromRoute(httpRouteA), + Data: []wasm.DataType{ { - Scope: controllers.LimitsNamespaceFromRoute(httpRouteB), - ExtensionName: wasm.RateLimitExtensionName, - Data: []wasm.DataType{ - { - Value: &wasm.Static{ - Static: wasm.StaticSpec{ - Key: controllers.LimitNameToLimitadorIdentifier(rlp1Key, "gatewaylimit"), - Value: "1", - }, - }, + Value: &wasm.Static{ + Static: wasm.StaticSpec{ + Key: controllers.LimitNameToLimitadorIdentifier(rlp2Key, "routelimit"), + Value: "1", }, }, }, @@ -1963,38 +2245,32 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { }, }, { - Name: rlp2Key.String(), // Route A affected by RLP 1 -> Route A - Hostnames: []string{"*.a.example.com"}, - Rules: []wasm.Rule{ - { - Conditions: []wasm.Condition{ - { - AllOf: []wasm.PatternExpression{ - { - Selector: "request.url_path", - Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), - Value: "/routeA", - }, - { - Selector: "request.method", - Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), - Value: "GET", - }, - }, - }, + Name: fmt.Sprintf("%d-%s-%d", 0, pathID_B, 0), // Route B affected by RLP 1 -> Gateway + RouteRuleConditions: wasm.RouteRuleConditions{ + Hostnames: []string{"*"}, + Matches: []wasm.Predicate{ + { + Selector: "request.url_path", + Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), + Value: "/routeB", }, - Actions: []wasm.Action{ + { + Selector: "request.method", + Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), + Value: "GET", + }, + }, + }, + Actions: []wasm.Action{ + { + ServiceName: wasm.RateLimitServiceName, + Scope: controllers.LimitsNamespaceFromRoute(httpRouteB), + Data: []wasm.DataType{ { - Scope: controllers.LimitsNamespaceFromRoute(httpRouteA), - ExtensionName: wasm.RateLimitExtensionName, - Data: []wasm.DataType{ - { - Value: &wasm.Static{ - Static: wasm.StaticSpec{ - Key: controllers.LimitNameToLimitadorIdentifier(rlp2Key, "routelimit"), - Value: "1", - }, - }, + Value: &wasm.Static{ + Static: wasm.StaticSpec{ + Key: controllers.LimitNameToLimitadorIdentifier(rlp1Key, "gatewaylimit"), + Value: "1", }, }, }, @@ -2022,11 +2298,13 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { TestGatewayName = "toystore-gw" routeName = "toystore-route" rlpName = "rlp-a" + gatewayClass *gatewayapiv1.GatewayClass gateway *gatewayapiv1.Gateway gwHostname = "*.gw.example.com" ) beforeEachCallback := func(ctx SpecContext) { + gatewayClass = tests.BuildBasicGatewayClass(tests.GatewayClassName) gateway = tests.BuildBasicGateway(TestGatewayName, testNamespace) gateway.Spec.Listeners[0].Hostname = ptr.To(gatewayapiv1.Hostname(gwHostname)) err := testClient().Create(ctx, gateway) @@ -2078,6 +2356,16 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { rlpKey := client.ObjectKeyFromObject(rlp) Eventually(assertPolicyIsAcceptedAndEnforced(ctx, rlpKey)).WithContext(ctx).Should(BeTrue()) + mGateway := &machinery.Gateway{Gateway: gateway} + mHTTPRoute := &machinery.HTTPRoute{HTTPRoute: httpRoute} + pathID := kuadrantv1.PathID([]machinery.Targetable{ + &machinery.GatewayClass{GatewayClass: gatewayClass}, + mGateway, + &machinery.Listener{Listener: &gateway.Spec.Listeners[0], Gateway: mGateway}, + mHTTPRoute, + &machinery.HTTPRouteRule{HTTPRouteRule: &httpRoute.Spec.Rules[0], HTTPRoute: mHTTPRoute}, + }) + // Check wasm plugin wasmPluginKey := client.ObjectKey{Name: wasm.ExtensionName(gateway.GetName()), Namespace: testNamespace} Eventually(tests.WasmPluginIsAvailable(ctx, testClient(), wasmPluginKey)).WithContext(ctx).Should(BeTrue()) @@ -2088,47 +2376,41 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { existingWASMConfig, err := wasm.ConfigFromStruct(existingWasmPlugin.Spec.PluginConfig) Expect(err).ToNot(HaveOccurred()) Expect(existingWASMConfig).To(Equal(&wasm.Config{ - Extensions: map[string]wasm.Extension{ - wasm.RateLimitExtensionName: { + Services: map[string]wasm.Service{ + wasm.RateLimitServiceName: { Endpoint: common.KuadrantRateLimitClusterName, FailureMode: wasm.FailureModeAllow, - Type: wasm.RateLimitExtensionType, + Type: wasm.RateLimitServiceType, }, }, - Policies: []wasm.Policy{ + ActionSets: []wasm.ActionSet{ { - Name: rlpKey.String(), - Hostnames: []string{gwHostname}, - Rules: []wasm.Rule{ - { - Conditions: []wasm.Condition{ - { - AllOf: []wasm.PatternExpression{ - { - Selector: "request.url_path", - Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), - Value: "/toy", - }, - { - Selector: "request.method", - Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), - Value: "GET", - }, - }, - }, + Name: fmt.Sprintf("%d-%s-%d", 0, pathID, 0), + RouteRuleConditions: wasm.RouteRuleConditions{ + Hostnames: []string{gwHostname}, + Matches: []wasm.Predicate{ + { + Selector: "request.url_path", + Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), + Value: "/toy", + }, + { + Selector: "request.method", + Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), + Value: "GET", }, - Actions: []wasm.Action{ + }, + }, + Actions: []wasm.Action{ + { + ServiceName: wasm.RateLimitServiceName, + Scope: controllers.LimitsNamespaceFromRoute(httpRoute), + Data: []wasm.DataType{ { - Scope: controllers.LimitsNamespaceFromRoute(httpRoute), - ExtensionName: wasm.RateLimitExtensionName, - Data: []wasm.DataType{ - { - Value: &wasm.Static{ - Static: wasm.StaticSpec{ - Key: controllers.LimitNameToLimitadorIdentifier(rlpKey, "l1"), - Value: "1", - }, - }, + Value: &wasm.Static{ + Static: wasm.StaticSpec{ + Key: controllers.LimitNameToLimitadorIdentifier(rlpKey, "l1"), + Value: "1", }, }, }, @@ -2147,10 +2429,12 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { gwRLPName = "gw-rlp" routeRLPName = "route-rlp" TestGatewayName = "toystore-gw" + gatewayClass *gatewayapiv1.GatewayClass gateway *gatewayapiv1.Gateway ) beforeEachCallback := func(ctx SpecContext) { + gatewayClass = tests.BuildBasicGatewayClass(tests.GatewayClassName) gateway = tests.BuildBasicGateway(TestGatewayName, testNamespace) err := testClient().Create(ctx, gateway) Expect(err).ToNot(HaveOccurred()) @@ -2158,48 +2442,52 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() { } expectedWasmPluginConfig := func(rlpKey client.ObjectKey, httpRoute *gatewayapiv1.HTTPRoute, key, hostname string) *wasm.Config { + mGateway := &machinery.Gateway{Gateway: gateway} + mHTTPRoute := &machinery.HTTPRoute{HTTPRoute: httpRoute} + pathID := kuadrantv1.PathID([]machinery.Targetable{ + &machinery.GatewayClass{GatewayClass: gatewayClass}, + mGateway, + &machinery.Listener{Listener: &gateway.Spec.Listeners[0], Gateway: mGateway}, + mHTTPRoute, + &machinery.HTTPRouteRule{HTTPRouteRule: &httpRoute.Spec.Rules[0], HTTPRoute: mHTTPRoute}, + }) + return &wasm.Config{ - Extensions: map[string]wasm.Extension{ - wasm.RateLimitExtensionName: { + Services: map[string]wasm.Service{ + wasm.RateLimitServiceName: { Endpoint: common.KuadrantRateLimitClusterName, FailureMode: wasm.FailureModeAllow, - Type: wasm.RateLimitExtensionType, + Type: wasm.RateLimitServiceType, }, }, - Policies: []wasm.Policy{ + ActionSets: []wasm.ActionSet{ { - Name: rlpKey.String(), - Hostnames: []string{hostname}, - Rules: []wasm.Rule{ - { - Conditions: []wasm.Condition{ - { - AllOf: []wasm.PatternExpression{ - { - Selector: "request.url_path", - Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), - Value: "/toy", - }, - { - Selector: "request.method", - Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), - Value: "GET", - }, - }, - }, + Name: fmt.Sprintf("%d-%s-%d", 0, pathID, 0), + RouteRuleConditions: wasm.RouteRuleConditions{ + Hostnames: []string{hostname}, + Matches: []wasm.Predicate{ + { + Selector: "request.url_path", + Operator: wasm.PatternOperator(kuadrantv1beta3.StartsWithOperator), + Value: "/toy", + }, + { + Selector: "request.method", + Operator: wasm.PatternOperator(kuadrantv1beta3.EqualOperator), + Value: "GET", }, - Actions: []wasm.Action{ + }, + }, + Actions: []wasm.Action{ + { + ServiceName: wasm.RateLimitServiceName, + Scope: controllers.LimitsNamespaceFromRoute(httpRoute), + Data: []wasm.DataType{ { - Scope: controllers.LimitsNamespaceFromRoute(httpRoute), - ExtensionName: wasm.RateLimitExtensionName, - Data: []wasm.DataType{ - { - Value: &wasm.Static{ - Static: wasm.StaticSpec{ - Key: key, - Value: "1", - }, - }, + Value: &wasm.Static{ + Static: wasm.StaticSpec{ + Key: key, + Value: "1", }, }, },