From 5e5e35b8d806b1b39c84e4f447f2ada9f963a33e Mon Sep 17 00:00:00 2001 From: Oscar Reyes Date: Wed, 3 Apr 2024 15:57:22 -0600 Subject: [PATCH] Adding k6 to GHA --- .github/workflows/k6.yml | 24 +++++++++++ docker-compose.e2e.yml | 4 +- docker-compose.k6.workflows.yml | 15 +++++++ docker-compose.k6.yml | 22 ++++++++++ k6.Dockerfile | 17 ++++++++ test/k6/add-pokemon.js | 74 +++++++++++++++++++++++++++++++++ test/k6/import-pokemon.js | 41 ++++++++++++------ test/k6/import-pokemon.yaml | 23 ---------- 8 files changed, 181 insertions(+), 39 deletions(-) create mode 100644 .github/workflows/k6.yml create mode 100644 docker-compose.k6.workflows.yml create mode 100644 docker-compose.k6.yml create mode 100644 k6.Dockerfile create mode 100644 test/k6/add-pokemon.js delete mode 100644 test/k6/import-pokemon.yaml diff --git a/.github/workflows/k6.yml b/.github/workflows/k6.yml new file mode 100644 index 0000000..d9215c2 --- /dev/null +++ b/.github/workflows/k6.yml @@ -0,0 +1,24 @@ +name: K6 Tests +on: + workflow_dispatch: + schedule: + - cron: '0 */1 * * *' # every hour + +jobs: + docker: + timeout-minutes: 10 + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v1 + + - name: Start containers + run: docker compose -f docker-compose.yml -f docker-compose.k6.workflows.yml run k6-tracetest + env: + TRACETEST_API_TOKEN: ${{secrets.TRACETEST_TOKEN}} + POKESHOP_DEMO_URL: ${{secrets.POKESHOP_DEMO_URL}} + + - name: Stop containers + if: always() + run: docker compose -f docker-compose.yml -f docker-compose.k6.workflows.yml down diff --git a/docker-compose.e2e.yml b/docker-compose.e2e.yml index adb29d7..b1a1889 100644 --- a/docker-compose.e2e.yml +++ b/docker-compose.e2e.yml @@ -3,10 +3,8 @@ name: pokeshop services: tracetest-agent: + image: kubeshop/tracetest-agent:latest environment: TRACETEST_DEV: ${TRACETEST_DEV} TRACETEST_API_KEY: ${TRACETEST_AGENT_API_KEY} TRACETEST_SERVER_URL: ${TRACETEST_SERVER_URL} - image: kubeshop/tracetest-agent:latest - networks: - default: null diff --git a/docker-compose.k6.workflows.yml b/docker-compose.k6.workflows.yml new file mode 100644 index 0000000..7cfc796 --- /dev/null +++ b/docker-compose.k6.workflows.yml @@ -0,0 +1,15 @@ +version: '3' + +services: + k6-tracetest: + build: + context: . + dockerfile: k6.Dockerfile + environment: + XK6_TRACETEST_API_TOKEN: ${TRACETEST_API_TOKEN} + POKESHOP_DEMO_URL: ${POKESHOP_DEMO_URL} + depends_on: + - worker + - api + volumes: + - ./test/k6/add-pokemon.js:/import-pokemon.js diff --git a/docker-compose.k6.yml b/docker-compose.k6.yml new file mode 100644 index 0000000..b0bd13c --- /dev/null +++ b/docker-compose.k6.yml @@ -0,0 +1,22 @@ +version: '3' + +services: + k6-tracetest: + build: + context: . + dockerfile: k6.Dockerfile + environment: + XK6_TRACETEST_API_TOKEN: ${TRACETEST_API_TOKEN} + POKESHOP_DEMO_URL: ${POKESHOP_DEMO_URL} + depends_on: + - api + - tracetest-agent + - worker + volumes: + - ./test/k6/import-pokemon.js:/import-pokemon.js + tracetest-agent: + image: kubeshop/tracetest-agent:latest + environment: + TRACETEST_DEV: ${TRACETEST_DEV} + TRACETEST_API_KEY: ${TRACETEST_AGENT_API_KEY} + TRACETEST_SERVER_URL: ${TRACETEST_SERVER_URL} diff --git a/k6.Dockerfile b/k6.Dockerfile new file mode 100644 index 0000000..4e646c6 --- /dev/null +++ b/k6.Dockerfile @@ -0,0 +1,17 @@ +FROM golang:1.21-alpine as builder + +WORKDIR /app + +ENV CGO_ENABLED 0 +RUN go install go.k6.io/xk6/cmd/xk6@latest + +RUN xk6 build v0.50.0 --with github.com/kubeshop/xk6-tracetest + +FROM alpine + +COPY --from=builder /app/k6 /bin/ +COPY ./test/k6/import-pokemon.js . +ENV XK6_TRACETEST_API_TOKEN=your-api-token +ENV POKESHOP_DEMO_URL=http://localhost:8081 + +ENTRYPOINT k6 run /import-pokemon.js -o xk6-tracetest -e POKESHOP_DEMO_URL=$POKESHOP_DEMO_URL diff --git a/test/k6/add-pokemon.js b/test/k6/add-pokemon.js new file mode 100644 index 0000000..2faec3d --- /dev/null +++ b/test/k6/add-pokemon.js @@ -0,0 +1,74 @@ +import { Http, Tracetest } from 'k6/x/tracetest'; +import { sleep } from 'k6'; + +export const options = { + vus: 5, + duration: '5s', +}; + +const POKESHOP_DEMO_URL = __ENV.POKESHOP_DEMO_URL || 'http://localhost:8081'; + +const http = new Http(); +const tracetest = Tracetest(); + +export default function () { + const url = `${POKESHOP_DEMO_URL}/pokemon`; + const payload = JSON.stringify({ + name: 'charizard', + type: 'flying', + imageUrl: 'https://assets.pokemon.com/assets/cms2/img/pokedex/full/006.png', + isFeatured: true, + }); + const params = { + headers: { + 'Content-Type': 'application/json', + }, + }; + + const definition = `type: Test +spec: + id: k6-tracetest-pokeshop-add-pokemon + name: "K6 - Add a Pokemon" + trigger: + type: k6 + specs: + - selector: span[tracetest.span.type="http" name="POST /pokemon" http.method="POST"] + name: The POST /pokemon was called correctly + assertions: + - attr:http.status_code = 201 + - selector: span[tracetest.span.type="general" name="validate request"] + name: The request sent to API is valid + assertions: + - attr:validation.is_valid = "true" + - selector: span[tracetest.span.type="database" name="create pokeshop.pokemon" db.system="postgres" db.name="pokeshop" db.user="ashketchum" db.operation="create" db.sql.table="pokemon"] + name: A Pokemon was inserted on database + assertions: + - attr:db.result | json_path '$.imageUrl' = "https://assets.pokemon.com/assets/cms2/img/pokedex/full/052.png" +`; + + const response = http.post(url, payload, params); + + tracetest.runTest( + response.trace_id, + { + definition, + should_wait: true, + }, + { + url, + method: 'GET', + } + ); + + sleep(1); +} + +export function handleSummary() { + return { + stdout: tracetest.summary(), + }; +} + +export function teardown() { + tracetest.validateResult(); +} diff --git a/test/k6/import-pokemon.js b/test/k6/import-pokemon.js index 2bb927d..07e50ac 100644 --- a/test/k6/import-pokemon.js +++ b/test/k6/import-pokemon.js @@ -1,44 +1,59 @@ -import { Http, Tracetest } from "k6/x/tracetest"; -import { sleep } from "k6"; +import { Http, Tracetest } from 'k6/x/tracetest'; +import { sleep } from 'k6'; export const options = { vus: 1, - duration: "5s", + duration: '5s', }; +const POKESHOP_DEMO_URL = __ENV.POKESHOP_DEMO_URL || 'http://localhost:8081'; + const http = new Http(); -const testId = "kc_MgKoVR"; const tracetest = Tracetest(); let pokemonId = 6; // charizard export default function () { - const url = "http://localhost:8081/pokemon/import"; + const url = `${POKESHOP_DEMO_URL}/pokemon/import`; const payload = JSON.stringify({ id: pokemonId++, }); const params = { headers: { - "Content-Type": "application/json", - }, - tracetest: { - testId, + 'Content-Type': 'application/json', }, }; + const definition = `type: Test +spec: + id: k6-tracetest-pokeshop-import-pokemon + name: K6 + description: K6 + trigger: + type: k6 + 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/${pokemonId}" +`; + const response = http.post(url, payload, params); tracetest.runTest( response.trace_id, { - test_id: testId, - variable_name: "TRACE_ID", + definition, should_wait: true, }, { - id: "123", url, - method: "GET", + method: 'GET', } ); diff --git a/test/k6/import-pokemon.yaml b/test/k6/import-pokemon.yaml deleted file mode 100644 index dee21f1..0000000 --- a/test/k6/import-pokemon.yaml +++ /dev/null @@ -1,23 +0,0 @@ -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" \ No newline at end of file