diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index bbad389..00b8cd3 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -105,3 +105,27 @@ jobs: - name: Execute tests in the running services run: | make -f ./e2e/parallel-requests/Makefile test + unreachable-service: + name: Unreachable service integration test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + target: wasm32-unknown-unknown + - uses: arduino/setup-protoc@v1 + with: + version: '3.x' + - uses: actions-rs/cargo@v1 + with: + command: build + args: --target wasm32-unknown-unknown + - name: Run docker compose + run: | + docker compose -f ./e2e/unreachable-service/docker-compose.yaml run start_services + - name: Execute tests in the running services + run: | + make -f ./e2e/unreachable-service/Makefile test diff --git a/e2e/missing-cluster/README.md b/e2e/missing-cluster/README.md index 0555ba8..39d74e3 100644 --- a/e2e/missing-cluster/README.md +++ b/e2e/missing-cluster/README.md @@ -89,7 +89,7 @@ Check Envoy logs: docker compose logs -f envoy ``` -The test will run one request and expect it to fail because `failureMode` is set to `deny`. +The test will run two requests and expect them to fail because `failureMode` is set to `deny`. ### Run Manually diff --git a/e2e/unreachable-service/Makefile b/e2e/unreachable-service/Makefile new file mode 100644 index 0000000..8f8c354 --- /dev/null +++ b/e2e/unreachable-service/Makefile @@ -0,0 +1,27 @@ +SHELL = /usr/bin/env bash -o pipefail +.SHELLFLAGS = -ec +.DEFAULT_GOAL := gateway +MKFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST))) +WORKDIR := $(patsubst %/,%,$(dir $(MKFILE_PATH))) +DOCKER ?= $(shell which docker 2> /dev/null || echo "docker") + +run: + $(DOCKER) compose -f docker-compose.yaml run start_services + +test: + @{ \ + set -e ;\ + STATUSCODE=$(shell curl --silent --output /dev/null --write-out "%{http_code}" --max-time 5 --resolve fail-on-first-action.example.com:18000:127.0.0.1 http://fail-on-first-action.example.com:18000) && \ + echo "received status code $${STATUSCODE}" && \ + test $${STATUSCODE} -ne 200 ;\ + } + @{ \ + set -e ;\ + STATUSCODE=$(shell curl --silent --output /dev/null --write-out "%{http_code}" --max-time 5 --resolve fail-on-second-action.example.com:18000:127.0.0.1 http://fail-on-second-action.example.com:18000) && \ + echo "received status code $${STATUSCODE}" && \ + test $${STATUSCODE} -ne 200 ;\ + } + +clean: + $(DOCKER) compose down --volumes --remove-orphans + $(DOCKER) compose -f docker-compose.yaml down --volumes --remove-orphans diff --git a/e2e/unreachable-service/README.md b/e2e/unreachable-service/README.md new file mode 100644 index 0000000..5456825 --- /dev/null +++ b/e2e/unreachable-service/README.md @@ -0,0 +1,118 @@ +## Basic integration test + +This is a integration test to validate when envoy cluster exists but it is not reachable. + +The test configures unreachable envoy cluster on the fist action `fail-on-first-action.example.com`, +as well as on the second action `fail-on-second-action.example.com`. Reason being to validate +error handling on the `on_grpc_call_response` event. + +This test is being added to the CI test suite + +### Description + +```yaml +"services": { + "limitadorA": { + "type": "ratelimit", + "endpoint": "limitador", + "failureMode": "deny" + }, + "limitador-unreachable": { + "type": "ratelimit", + "endpoint": "unreachable-cluster", + "failureMode": "deny" + } +}, +"actionSets": [ +{ + "name": "envoy-cluster-unreachable-on-first-action", + "routeRuleConditions": { + "hostnames": [ + "fail-on-first-action.example.com" + ] + }, + "actions": [ + { + "service": "limitador-unreachable", + "scope": "a", + "data": [ + { + "expression": { + "key": "a", + "value": "1" + } + } + ] + } + ] +}, +{ + "name": "envoy-cluster-unreachable-on-second-action", + "routeRuleConditions": { + "hostnames": [ + "fail-on-second-action.example.com" + ] + }, + "actions": [ + { + "service": "limitadorA", + "scope": "b", + "data": [ + { + "expression": { + "key": "limit_to_be_activated", + "value": "1" + } + } + ] + }, + { + "service": "limitador-unreachable", + "scope": "b", + "data": [ + { + "expression": { + "key": "limit_to_be_activated", + "value": "1" + } + } + ] + } + ] +} +] +``` + +And a new limit configuration + +```yaml +- namespace: basic + max_value: 30 + seconds: 60 + conditions: + - "a == '1'" + variables: [] +``` + +The test will run two requests and expect them to fail because `failureMode` is set to `deny`. + +### Run Manually + +It requires Wasm module being built at `target/wasm32-unknown-unknown/debug/wasm_shim.wasm`. +Check *Makefile* at the root of the project to build the module. + +``` +make run +``` + +Run the test + +``` +make test +``` + +### Clean up + +``` +make clean +``` diff --git a/e2e/unreachable-service/docker-compose.yaml b/e2e/unreachable-service/docker-compose.yaml new file mode 100644 index 0000000..bcecea7 --- /dev/null +++ b/e2e/unreachable-service/docker-compose.yaml @@ -0,0 +1,56 @@ +--- +services: + envoy: + image: envoyproxy/envoy:v1.31-latest + depends_on: + - limitador + - upstream + command: + - /usr/local/bin/envoy + - --config-path + - /etc/envoy.yaml + - --log-level + - info + - --component-log-level + - wasm:debug,http:debug,router:debug + - --service-cluster + - proxy + expose: + - "80" + - "8001" + ports: + - "18000:80" + - "18001:8001" + volumes: + - ./envoy.yaml:/etc/envoy.yaml + - ../../target/wasm32-unknown-unknown/debug/wasm_shim.wasm:/opt/kuadrant/wasm/wasm_shim.wasm + limitador: + image: quay.io/kuadrant/limitador:latest + command: ["limitador-server", "-vvv", "/opt/kuadrant/limits/limits.yaml"] + ports: + - "18080:8080" + - "18081:8081" + expose: + - "8080" + - "8081" + volumes: + - ./limits.yaml:/opt/kuadrant/limits/limits.yaml + upstream: + image: quay.io/kuadrant/authorino-examples:talker-api + environment: + PORT: 3000 + expose: + - "3000" + start_services: + image: alpine + depends_on: + - envoy + command: > + /bin/sh -c " + while ! nc -z envoy 80; + do + echo sleeping; + sleep 1; + done; + echo Connected! + " diff --git a/e2e/unreachable-service/envoy.yaml b/e2e/unreachable-service/envoy.yaml new file mode 100644 index 0000000..9b8b026 --- /dev/null +++ b/e2e/unreachable-service/envoy.yaml @@ -0,0 +1,156 @@ +--- +static_resources: + listeners: + - name: main + address: + socket_address: + address: 0.0.0.0 + port_value: 80 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + stat_prefix: ingress_http + use_remote_address: true + xff_num_trusted_hops: 1 + route_config: + name: local_route + virtual_hosts: + - name: local_service + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: upstream + http_filters: + - name: envoy.filters.http.wasm + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm + config: + name: kuadrant_wasm + root_id: kuadrant_wasm + vm_config: + vm_id: vm.sentinel.kuadrant_wasm + runtime: envoy.wasm.runtime.v8 + code: + local: + filename: /opt/kuadrant/wasm/wasm_shim.wasm + allow_precompiled: true + configuration: + "@type": "type.googleapis.com/google.protobuf.StringValue" + value: > + { + "services": { + "limitador": { + "type": "ratelimit", + "endpoint": "limitador", + "failureMode": "deny" + }, + "limitador-unreachable": { + "type": "ratelimit", + "endpoint": "unreachable-cluster", + "failureMode": "deny" + } + }, + "actionSets": [ + { + "name": "envoy-cluster-unreachable-on-first-action", + "routeRuleConditions": { + "hostnames": [ + "fail-on-first-action.example.com" + ] + }, + "actions": [ + { + "service": "limitador-unreachable", + "scope": "a", + "data": [ + { + "expression": { + "key": "a", + "value": "1" + } + } + ] + } + ] + }, + { + "name": "envoy-cluster-unreachable-on-second-action", + "routeRuleConditions": { + "hostnames": [ + "fail-on-second-action.example.com" + ] + }, + "actions": [ + { + "service": "limitador", + "scope": "a", + "data": [ + { + "expression": { + "key": "a", + "value": "1" + } + } + ] + }, + { + "service": "limitador-unreachable", + "scope": "a", + "data": [ + { + "expression": { + "key": "a", + "value": "1" + } + } + ] + } + ] + } + ] + } + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + clusters: + - name: upstream + connect_timeout: 0.25s + type: STRICT_DNS + lb_policy: round_robin + load_assignment: + cluster_name: upstream + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: upstream + port_value: 3000 + - name: limitador + connect_timeout: 0.25s + type: STRICT_DNS + lb_policy: round_robin + typed_extension_protocol_options: + envoy.extensions.upstreams.http.v3.HttpProtocolOptions: + "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions + explicit_http_config: + http2_protocol_options: {} + load_assignment: + cluster_name: limitador + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: limitador + port_value: 8081 +admin: + address: + socket_address: + address: 0.0.0.0 + port_value: 8001 diff --git a/e2e/unreachable-service/limits.yaml b/e2e/unreachable-service/limits.yaml new file mode 100644 index 0000000..3d75521 --- /dev/null +++ b/e2e/unreachable-service/limits.yaml @@ -0,0 +1,7 @@ +--- +- namespace: a + max_value: 30 + seconds: 60 + conditions: + - "a == '1'" + variables: []