Skip to content

Commit

Permalink
wasmplugin controller: integration tests: New free route on a Gateway…
Browse files Browse the repository at this point in the history
… with RLP
  • Loading branch information
eguzki committed Dec 18, 2023
1 parent ac352dd commit a95be73
Showing 1 changed file with 304 additions and 1 deletion.
305 changes: 304 additions & 1 deletion controllers/rate_limiting_wasmplugin_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1411,7 +1411,7 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() {
})

Context("Free Route gets dedicated RLP", func() {
It("wasmplugin config should update config", func() {
It("wasmplugin should update config", func() {
// Initial state
// Gw A
// Route A -> Gw A (free route, i.e. no rlp targeting it)
Expand Down Expand Up @@ -1551,7 +1551,9 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() {
// Proceed with the update:
// New RLP 2 -> Route A

//
// create RLP 2 -> Route A
//
rlp2 := &kuadrantv1beta2.RateLimitPolicy{
TypeMeta: metav1.TypeMeta{
Kind: "RateLimitPolicy", APIVersion: kuadrantv1beta2.GroupVersion.String(),
Expand Down Expand Up @@ -1649,6 +1651,307 @@ var _ = Describe("Rate Limiting WasmPlugin controller", func() {
}, time.Minute, 5*time.Second).Should(BeTrue())
})
})

Context("New free route on a Gateway with RLP", func() {
It("wasmplugin should update config", func() {
// Initial state
// Gw A
// Route A -> Gw A
// RLP 1 -> Gw A
// RLP 2 -> Route A
//
// Add new Route B (free route, i.e. no rlp targeting it)
// Gw A
// Route A -> Gw A
// Route B -> Gw A
// RLP 1 -> Gw A
// RLP 2 -> Route A

var (
routeAName = "route-a"
routeBName = "route-b"
rlp1Name = "rlp-1"
rlp2Name = "rlp-2"
)

//
// create Route A -> Gw A on *.a.example.com
//
httpRouteA := testBuildBasicHttpRoute(routeAName, gwName, testNamespace, []string{"*.a.example.com"})
// GET /routeA
httpRouteA.Spec.Rules = []gatewayapiv1.HTTPRouteRule{
{
Matches: []gatewayapiv1.HTTPRouteMatch{
{
Path: &gatewayapiv1.HTTPPathMatch{
Type: ptr.To(gatewayapiv1.PathMatchPathPrefix),
Value: ptr.To("/routeA"),
},
Method: ptr.To(gatewayapiv1.HTTPMethod("GET")),
},
},
},
}
err := k8sClient.Create(context.Background(), httpRouteA)
Expect(err).ToNot(HaveOccurred())
Eventually(testRouteIsAccepted(client.ObjectKeyFromObject(httpRouteA)), time.Minute, 5*time.Second).Should(BeTrue())

// create RLP 1 -> Gw A
rlp1 := &kuadrantv1beta2.RateLimitPolicy{
TypeMeta: metav1.TypeMeta{
Kind: "RateLimitPolicy", APIVersion: kuadrantv1beta2.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{Name: rlp1Name, Namespace: testNamespace},
Spec: kuadrantv1beta2.RateLimitPolicySpec{
TargetRef: gatewayapiv1alpha2.PolicyTargetReference{
Group: gatewayapiv1.Group("gateway.networking.k8s.io"),
Kind: "Gateway",
Name: gatewayapiv1.ObjectName(gwName),
},
Limits: map[string]kuadrantv1beta2.Limit{
"gatewaylimit": {
Rates: []kuadrantv1beta2.Rate{
{
Limit: 1, Duration: 3, Unit: kuadrantv1beta2.TimeUnit("minute"),
},
},
},
},
},
}
err = k8sClient.Create(context.Background(), rlp1)
Expect(err).ToNot(HaveOccurred())
// Check RLP status is available
rlp1Key := client.ObjectKey{Name: rlp1Name, Namespace: testNamespace}
Eventually(testRLPIsAvailable(rlp1Key), time.Minute, 5*time.Second).Should(BeTrue())

// create RLP 2 -> Route A
rlp2 := &kuadrantv1beta2.RateLimitPolicy{
TypeMeta: metav1.TypeMeta{
Kind: "RateLimitPolicy", APIVersion: kuadrantv1beta2.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{Name: rlp2Name, Namespace: testNamespace},
Spec: kuadrantv1beta2.RateLimitPolicySpec{
TargetRef: gatewayapiv1alpha2.PolicyTargetReference{
Group: gatewayapiv1.Group("gateway.networking.k8s.io"),
Kind: "HTTPRoute",
Name: gatewayapiv1.ObjectName(routeAName),
},
Limits: map[string]kuadrantv1beta2.Limit{
"routelimit": {
Rates: []kuadrantv1beta2.Rate{
{
Limit: 4, Duration: 3, Unit: kuadrantv1beta2.TimeUnit("minute"),
},
},
},
},
},
}
err = k8sClient.Create(context.Background(), rlp2)
Expect(err).ToNot(HaveOccurred())
// Check RLP status is available
rlp2Key := client.ObjectKey{Name: rlp2Name, Namespace: testNamespace}
Eventually(testRLPIsAvailable(rlp2Key), time.Minute, 5*time.Second).Should(BeTrue())

// 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
Eventually(func() bool {
wasmPluginKey := client.ObjectKey{
Name: rlptools.WASMPluginName(gateway), Namespace: testNamespace,
}
existingWasmPlugin := &istioclientgoextensionv1alpha1.WasmPlugin{}
err := k8sClient.Get(context.Background(), wasmPluginKey, existingWasmPlugin)
if err != nil {
logf.Log.V(1).Info("wasmplugin not read", "key", wasmPluginKey, "error", err)
return false
}
existingWASMConfig, err := rlptools.WASMPluginFromStruct(existingWasmPlugin.Spec.PluginConfig)
if err != nil {
logf.Log.V(1).Info("wasmplugin could not be deserialized", "key", wasmPluginKey, "error", err)
return false
}

expectedPlugin := &wasm.Plugin{
FailureMode: wasm.FailureModeDeny,
RateLimitPolicies: []wasm.RateLimitPolicy{
{
Name: rlp2Key.String(),
Domain: rlptools.LimitsNamespaceFromRLP(rlp2),
Rules: []wasm.Rule{
{
Conditions: []wasm.Condition{
{
AllOf: []wasm.PatternExpression{
{
Selector: "request.url_path",
Operator: wasm.PatternOperator(kuadrantv1beta2.StartsWithOperator),
Value: "/routeA",
},
{
Selector: "request.method",
Operator: wasm.PatternOperator(kuadrantv1beta2.EqualOperator),
Value: "GET",
},
},
},
},
Data: []wasm.DataItem{
{
Static: &wasm.StaticSpec{
Key: `limit.routelimit__efc5113c`,
Value: "1",
},
},
},
},
},
Hostnames: []string{"*.a.example.com"},
Service: common.KuadrantRateLimitClusterName,
},
},
}

if !reflect.DeepEqual(existingWASMConfig, expectedPlugin) {
diff := cmp.Diff(existingWASMConfig, expectedPlugin)
logf.Log.V(1).Info("wasmplugin does not match", "key", wasmPluginKey, "diff", diff)
return false
}

return true
}, time.Minute, 5*time.Second).Should(BeTrue())

// Proceed with the update:
// New Route B -> Gw A (free route, i.e. no rlp targeting it)

//
// create Route B -> Gw A on *.b.example.com
//
httpRouteB := testBuildBasicHttpRoute(routeBName, gwName, testNamespace, []string{"*.b.example.com"})
// GET /routeB
httpRouteB.Spec.Rules = []gatewayapiv1.HTTPRouteRule{
{
Matches: []gatewayapiv1.HTTPRouteMatch{
{
Path: &gatewayapiv1.HTTPPathMatch{
Type: ptr.To(gatewayapiv1.PathMatchPathPrefix),
Value: ptr.To("/routeB"),
},
Method: ptr.To(gatewayapiv1.HTTPMethod("GET")),
},
},
},
}
err = k8sClient.Create(context.Background(), httpRouteB)
Expect(err).ToNot(HaveOccurred())
Eventually(testRouteIsAccepted(client.ObjectKeyFromObject(httpRouteB)), time.Minute, 5*time.Second).Should(BeTrue())

// Check wasm plugin has configuration from:
// - the route A with route level RLP 2
// - the route B with gateway level RLP 1
// it may take some reconciliation loops to get to that, so checking it with eventually
Eventually(func() bool {
wasmPluginKey := client.ObjectKey{
Name: rlptools.WASMPluginName(gateway), Namespace: testNamespace,
}
existingWasmPlugin := &istioclientgoextensionv1alpha1.WasmPlugin{}
err := k8sClient.Get(context.Background(), wasmPluginKey, existingWasmPlugin)
if err != nil {
logf.Log.V(1).Info("wasmplugin not read", "key", wasmPluginKey, "error", err)
return false
}
existingWASMConfig, err := rlptools.WASMPluginFromStruct(existingWasmPlugin.Spec.PluginConfig)
if err != nil {
logf.Log.V(1).Info("wasmplugin could not be deserialized", "key", wasmPluginKey, "error", err)
return false
}

expectedPlugin := &wasm.Plugin{
FailureMode: wasm.FailureModeDeny,
RateLimitPolicies: []wasm.RateLimitPolicy{
{ // First RLP 1 as the controller will sort based on RLP name
Name: rlp1Key.String(), // Route B affected by RLP 1 -> Gateway
Domain: rlptools.LimitsNamespaceFromRLP(rlp1),
Rules: []wasm.Rule{
{
Conditions: []wasm.Condition{
{
AllOf: []wasm.PatternExpression{
{
Selector: "request.url_path",
Operator: wasm.PatternOperator(kuadrantv1beta2.StartsWithOperator),
Value: "/routeB",
},
{
Selector: "request.method",
Operator: wasm.PatternOperator(kuadrantv1beta2.EqualOperator),
Value: "GET",
},
},
},
},
Data: []wasm.DataItem{
{
Static: &wasm.StaticSpec{
Key: `limit.gatewaylimit__b95fa83b`,
Value: "1",
},
},
},
},
},
Hostnames: []string{"*"},
Service: common.KuadrantRateLimitClusterName,
},
{
Name: rlp2Key.String(), // Route A affected by RLP 1 -> Route A
Domain: rlptools.LimitsNamespaceFromRLP(rlp2),
Rules: []wasm.Rule{
{
Conditions: []wasm.Condition{
{
AllOf: []wasm.PatternExpression{
{
Selector: "request.url_path",
Operator: wasm.PatternOperator(kuadrantv1beta2.StartsWithOperator),
Value: "/routeA",
},
{
Selector: "request.method",
Operator: wasm.PatternOperator(kuadrantv1beta2.EqualOperator),
Value: "GET",
},
},
},
},
Data: []wasm.DataItem{
{
Static: &wasm.StaticSpec{
Key: `limit.routelimit__efc5113c`,
Value: "1",
},
},
},
},
},
Hostnames: []string{"*.a.example.com"},
Service: common.KuadrantRateLimitClusterName,
},
},
}

if !reflect.DeepEqual(existingWASMConfig, expectedPlugin) {
diff := cmp.Diff(existingWASMConfig, expectedPlugin)
logf.Log.V(1).Info("wasmplugin does not match", "key", wasmPluginKey, "diff", diff)
return false
}

return true
}, time.Minute, 5*time.Second).Should(BeTrue())

})
})
})

func testWasmPluginIsAvailable(key client.ObjectKey) func() bool {
Expand Down

0 comments on commit a95be73

Please sign in to comment.