diff --git a/examples/tracetest-cloud-k6/.env.template b/examples/tracetest-cloud-k6/.env.template new file mode 100644 index 0000000000..3fb2140af6 --- /dev/null +++ b/examples/tracetest-cloud-k6/.env.template @@ -0,0 +1 @@ +TRACETEST_API_KEY= diff --git a/examples/tracetest-cloud-k6/.gitignore b/examples/tracetest-cloud-k6/.gitignore new file mode 100644 index 0000000000..1dbdb9b1d4 --- /dev/null +++ b/examples/tracetest-cloud-k6/.gitignore @@ -0,0 +1,2 @@ +.env +k6 diff --git a/examples/tracetest-cloud-k6/README.md b/examples/tracetest-cloud-k6/README.md new file mode 100644 index 0000000000..0d55abb95d --- /dev/null +++ b/examples/tracetest-cloud-k6/README.md @@ -0,0 +1,61 @@ +# Tracetest Cloud + K6 + +This example's objective is to show how you can run load tests enhanced with trace-based testing using Tracetest Cloud and k6 against an instrumented service (Pokeshop API). + +For more detailed information about the K6 Tracetest Binary take a look a the [docs](https://docs.tracetest.io/tools-and-integrations/integrations/k6). + +## Prerequisites + +1. Signing up to [app.tracetest.io](https://app.tracetest.io). +2. Creating an [environment](https://docs.tracetest.io/concepts/environments). +3. Having access to the environment's [agent token](https://docs.tracetest.io/configuration/agent). + +## Steps + +1. [Install the Tracetest CLI](https://docs.tracetest.io/installing/). +2. Copy the `.env.template` file into `.env` and add the `TRACETEST_API_KEY`. This is the Agent API token from your environment. +3. Create a [token from your environment](https://docs.tracetest.io/concepts/environment-tokens). +4. Run `tracetest configure` on a terminal and select the environment in use. +5. Run the project by using docker-compose: `docker-compose up -d` (Linux) or `docker compose up -d` (Mac). +6. Test if it works by running: `tracetest run test -f tests/test.yaml`. This will create and run a test with trace id as the trigger. +7. Build the k6 binary with the extension by using `xk6 build v0.42.0 --with github.com/kubeshop/xk6-tracetest`. +8. Now you are ready to run your load test; you can achieve this by running the following command: `XK6_TRACETEST_API_TOKEN= ./k6 run ./import-pokemon.js -o xk6-tracetest`. +9. After the load test finishes you should be able to see an output like the following: + +```bash +./k6 run ./import-pokemon.js -o xk6-tracetest +context menu + + + /\ |‾‾| /‾‾/ /‾‾/ + /\ / \ | |/ / / / + / \/ \ | ( / ‾‾\ + / \ | |\ \ | (‾) | + / __________ \ |__| \__\ \_____/ .io + + execution: local + script: ./import-pokemon.js + output: xk6-tracetest-output (TestRunID: 38055) + + scenarios: (100.00%) 1 scenario, 1 max VUs, 35s max duration (incl. graceful stop): + * default: 1 looping VUs for 5s (gracefulStop: 30s) + +[TotalRuns=6, SuccessfulRus=1, FailedRuns=5] +[FAILED] +[Request=GET - http://localhost:8081/pokemon/import, TraceID=dc0718bcecceeec731b343235eb9c15a, RunState=FINISHED FailingSpecs=true, TracetestURL= https://app.tracetest.io/organizations/ttorg_ced62e34638d965e/environments/ttenv_807d0129a10be776/test/kc_MgKoVR/run/11] +[Request=POST - http://localhost:8081/pokemon/import, TraceID=dc0718fe83cfeec7315daf10d212d351, RunState=FINISHED FailingSpecs=true, TracetestURL= https://app.tracetest.io/organizations/ttorg_ced62e34638d965e/environments/ttenv_807d0129a10be776/test/kc_MgKoVR/run/4] +[Request=POST - http://localhost:8081/pokemon/import, TraceID=dc0718a8f4ceeec731e47f13762e61b8, RunState=FINISHED FailingSpecs=true, TracetestURL= https://app.tracetest.io/organizations/ttorg_ced62e34638d965e/environments/ttenv_807d0129a10be776/test/kc_MgKoVR/run/8] +[Request=POST - http://localhost:8081/pokemon/import, TraceID=dc0718bcecceeec731b343235eb9c15a, RunState=FINISHED FailingSpecs=true, TracetestURL= https://app.tracetest.io/organizations/ttorg_ced62e34638d965e/environments/ttenv_807d0129a10be776/test/kc_MgKoVR/run/9] +[Request=POST - http://localhost:8081/pokemon/import, TraceID=dc071893fcceeec731148270c6671a1e, RunState=FINISHED FailingSpecs=true, TracetestURL= https://app.tracetest.io/organizations/ttorg_ced62e34638d965e/environments/ttenv_807d0129a10be776/test/kc_MgKoVR/run/6] +[SUCCESSFUL] +[Request=POST - http://localhost:8081/pokemon/import, TraceID=dc0718cee4ceeec731f3f414bf3a2a16, RunState=FINISHED FailingSpecs=false, TracetestURL= https://app.tracetest.io/organizations/ttorg_ced62e34638d965e/environments/ttenv_807d0129a10be776/test/kc_MgKoVR/run/3] + +running (05.0s), 0/1 VUs, 5 complete and 0 interrupted iterations +default ✓ [======================================] 1 VUs 5s +``` + +## What's Next? + +After running the initial set of tests, you can click the run link for any of them, update the assertions and run the scripts once more. This flow enables complete a trace-based TDD flow. + +![assertions](assets/assertions.gif) diff --git a/examples/tracetest-cloud-k6/assets/assertions.gif b/examples/tracetest-cloud-k6/assets/assertions.gif new file mode 100644 index 0000000000..bee4d94487 Binary files /dev/null and b/examples/tracetest-cloud-k6/assets/assertions.gif differ diff --git a/examples/tracetest-cloud-k6/collector.config.yaml b/examples/tracetest-cloud-k6/collector.config.yaml new file mode 100644 index 0000000000..a1ab056cc6 --- /dev/null +++ b/examples/tracetest-cloud-k6/collector.config.yaml @@ -0,0 +1,22 @@ +receivers: + otlp: + protocols: + grpc: + http: + +processors: + batch: + timeout: 100ms + +exporters: + jaeger: + endpoint: jaeger:14250 + tls: + insecure: true + +service: + pipelines: + traces: + receivers: [otlp] + processors: [batch] + exporters: [jaeger] diff --git a/examples/tracetest-cloud-k6/docker-compose.yml b/examples/tracetest-cloud-k6/docker-compose.yml new file mode 100644 index 0000000000..bd6416ef93 --- /dev/null +++ b/examples/tracetest-cloud-k6/docker-compose.yml @@ -0,0 +1,125 @@ +version: "3" + +services: + tracetest-agent: + image: kubeshop/tracetest-agent:latest + environment: + TRACETEST_DEV: ${TRACETEST_DEV} + TRACETEST_API_KEY: ${TRACETEST_API_KEY} + + postgres: + image: postgres:14 + environment: + POSTGRES_PASSWORD: postgres + POSTGRES_USER: postgres + healthcheck: + test: pg_isready -U "$$POSTGRES_USER" -d "$$POSTGRES_DB" + interval: 1s + timeout: 5s + retries: 60 + + otel-collector: + image: otel/opentelemetry-collector:0.54.0 + command: + - "--config" + - "/otel-local-config.yaml" + volumes: + - ./collector.config.yaml:/otel-local-config.yaml + depends_on: + - jaeger + + jaeger: + image: jaegertracing/all-in-one:latest + restart: unless-stopped + healthcheck: + test: ["CMD", "wget", "--spider", "localhost:16686"] + interval: 1s + timeout: 3s + retries: 60 + + cache: + image: redis:6 + restart: unless-stopped + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 1s + timeout: 3s + retries: 60 + + queue: + image: rabbitmq:3.8-management + restart: unless-stopped + healthcheck: + test: rabbitmq-diagnostics -q check_running + interval: 1s + timeout: 5s + retries: 60 + + demo-api: + image: kubeshop/demo-pokemon-api:latest + restart: unless-stopped + pull_policy: always + environment: + REDIS_URL: cache + DATABASE_URL: postgresql://postgres:postgres@postgres:5432/postgres?schema=public + RABBITMQ_HOST: queue + POKE_API_BASE_URL: https://pokeapi.co/api/v2 + COLLECTOR_ENDPOINT: http://otel-collector:4317 + NPM_RUN_COMMAND: api + healthcheck: + test: ["CMD", "wget", "--spider", "localhost:8081"] + interval: 1s + timeout: 3s + retries: 60 + ports: + - 8081:8081 + depends_on: + postgres: + condition: service_healthy + cache: + condition: service_healthy + queue: + condition: service_healthy + + demo-worker: + image: kubeshop/demo-pokemon-api:latest + restart: unless-stopped + pull_policy: always + environment: + REDIS_URL: cache + DATABASE_URL: postgresql://postgres:postgres@postgres:5432/postgres?schema=public + RABBITMQ_HOST: queue + POKE_API_BASE_URL: https://pokeapi.co/api/v2 + COLLECTOR_ENDPOINT: http://otel-collector:4317 + NPM_RUN_COMMAND: worker + depends_on: + postgres: + condition: service_healthy + cache: + condition: service_healthy + queue: + condition: service_healthy + + demo-rpc: + image: kubeshop/demo-pokemon-api:latest + restart: unless-stopped + pull_policy: always + environment: + REDIS_URL: cache + DATABASE_URL: postgresql://postgres:postgres@postgres:5432/postgres?schema=public + RABBITMQ_HOST: queue + POKE_API_BASE_URL: https://pokeapi.co/api/v2 + COLLECTOR_ENDPOINT: http://otel-collector:4317 + NPM_RUN_COMMAND: rpc + healthcheck: + test: ["CMD", "lsof", "-i", "8082"] + interval: 1s + timeout: 3s + retries: 60 + depends_on: + postgres: + condition: service_healthy + cache: + condition: service_healthy + queue: + condition: service_healthy diff --git a/examples/tracetest-cloud-k6/import-pokemon.js b/examples/tracetest-cloud-k6/import-pokemon.js new file mode 100644 index 0000000000..fb757b1a45 --- /dev/null +++ b/examples/tracetest-cloud-k6/import-pokemon.js @@ -0,0 +1,56 @@ +import { Http, Tracetest } from "k6/x/tracetest"; +import { sleep } from "k6"; + +export const options = { + vus: 1, + duration: "5s", +}; + +const http = new Http(); +const testId = "kc_MgKoVR"; +const tracetest = Tracetest(); + +let pokemonId = 6; + +export default function () { + const url = "http://localhost:8081/pokemon/import"; + const payload = JSON.stringify({ + id: pokemonId++, + }); + const params = { + headers: { + "Content-Type": "application/json", + }, + tracetest: { + testId, + }, + }; + + const response = http.post(url, payload, params); + + tracetest.runTest( + response.trace_id, + { + test_id: testId, + variable_name: "TRACE_ID", + should_wait: true, + }, + { + id: "123", + url, + method: "GET", + } + ); + + sleep(1); +} + +export function handleSummary() { + return { + stdout: tracetest.summary(), + }; +} + +export function teardown() { + tracetest.validateResult(); +} \ No newline at end of file diff --git a/examples/tracetest-cloud-k6/tests/test.yaml b/examples/tracetest-cloud-k6/tests/test.yaml new file mode 100644 index 0000000000..38ccd4dc7b --- /dev/null +++ b/examples/tracetest-cloud-k6/tests/test.yaml @@ -0,0 +1,23 @@ +type: Test +spec: + id: kc_MgKoVR + name: K6 + description: K6 + trigger: + type: traceid + traceid: + id: ${env:TRACE_ID} + specs: + - selector: span[tracetest.span.type="general" name="import pokemon"] + name: Should have imported the pokemon + assertions: + - attr:tracetest.selected_spans.count = 1 + - selector: |- + span[tracetest.span.type="http" net.peer.name="pokeapi.co" http.method="GET"] + name: Should trigger a request to the POKEAPI + assertions: + - attr:http.url = "https://pokeapi.co/api/v2/pokemon/6" + - selector: span[tracetest.span.type="database" name="create postgres.pokemon"] + name: Should insert the pokemon to the DB + assertions: + - attr:db.result | json_path '.name' = "charizard" diff --git a/examples/tracetest-cloud-k6/tracetest-config.yaml b/examples/tracetest-cloud-k6/tracetest-config.yaml new file mode 100644 index 0000000000..5e732f6d38 --- /dev/null +++ b/examples/tracetest-cloud-k6/tracetest-config.yaml @@ -0,0 +1,7 @@ +postgres: + host: postgres + user: postgres + password: postgres + port: 5432 + dbname: postgres + params: sslmode=disable diff --git a/examples/tracetest-cloud-k6/tracetest-provision.yaml b/examples/tracetest-cloud-k6/tracetest-provision.yaml new file mode 100644 index 0000000000..ec50a15e45 --- /dev/null +++ b/examples/tracetest-cloud-k6/tracetest-provision.yaml @@ -0,0 +1,19 @@ +--- +type: PollingProfile +spec: + name: Default + strategy: periodic + default: true + periodic: + retryDelay: 5s + timeout: 10m + +--- +type: DataStore +spec: + name: jaeger + type: jaeger + jaeger: + endpoint: jaeger:16685 + tls: + insecure: true