From 21ad4350e1d9d8b2aea3eb9c65e11b79c711c307 Mon Sep 17 00:00:00 2001 From: AgnesToulet <35176601+AgnesToulet@users.noreply.github.com> Date: Tue, 29 Apr 2025 14:39:24 +0200 Subject: [PATCH 1/3] Load tests: Upgrade code and add SA token support --- devenv/loadtest/README.md | 10 ++++- devenv/loadtest/modules/client.js | 66 +++++++++++++++---------------- devenv/loadtest/render_test.js | 41 +++++++++---------- devenv/loadtest/run.sh | 9 ++++- 4 files changed, 70 insertions(+), 56 deletions(-) diff --git a/devenv/loadtest/README.md b/devenv/loadtest/README.md index 6aa1ea00..e540f1f9 100644 --- a/devenv/loadtest/README.md +++ b/devenv/loadtest/README.md @@ -4,7 +4,8 @@ Runs load tests and checks using [k6](https://k6.io/). ## Prerequisites -Docker +- Docker +- Grafana instance running (you can use the environment in `devenv/docker/simple`) ## Run @@ -24,6 +25,7 @@ Run only 1 iteration of the load test (useful for testing): ```bash $ ./run.sh -i 1 +``` Run load test for custom target url: @@ -37,6 +39,12 @@ Run load test for 10 virtual users: $ ./run.sh -v 10 ``` +Run load test using a service account token instead of default username/password: + +```bash +$ ./run.sh -a glsa_xxxx +``` + Example output: ```bash diff --git a/devenv/loadtest/modules/client.js b/devenv/loadtest/modules/client.js index 123d0a3d..abae15ab 100644 --- a/devenv/loadtest/modules/client.js +++ b/devenv/loadtest/modules/client.js @@ -1,4 +1,4 @@ -import http from "k6/http"; +import http from 'k6/http'; export const DatasourcesEndpoint = class DatasourcesEndpoint { constructor(httpClient) { @@ -68,31 +68,34 @@ export const UIEndpoint = class UIEndpoint { login(username, pwd) { const payload = { user: username, password: pwd }; - return this.httpClient.formPost('/login', payload); + return this.httpClient.post('/login', JSON.stringify(payload)); } renderPanel(orgId, dashboardUid, panelId) { - return this.httpClient.get( - `/render/d-solo/${dashboardUid}/graph-panel`, - { - orgId, - panelId, - width: 1000, - height: 500, - tz: 'Europe/Stockholm', - } - ); + return this.httpClient.get(`/render/d-solo/${dashboardUid}/graph-panel`, { + orgId, + panelId: `panel-${panelId}`, + width: 1000, + height: 500, + tz: 'Europe/Stockholm', + timeout: '10', + }); } -} +}; export const GrafanaClient = class GrafanaClient { constructor(httpClient) { httpClient.onBeforeRequest = (params) => { + params.headers = params.headers || {}; + if (this.orgId && this.orgId > 0) { - params.headers = params.headers || {}; - params.headers["X-Grafana-Org-Id"] = this.orgId; + params.headers['X-Grafana-Org-Id'] = this.orgId; } - } + + if (this.authToken) { + params.headers['Authorization'] = `Bearer ${this.authToken}`; + } + }; this.raw = httpClient; this.dashboards = new DashboardsEndpoint(httpClient.withUrl('/api')); @@ -118,7 +121,11 @@ export const GrafanaClient = class GrafanaClient { withOrgId(orgId) { this.orgId = orgId; } -} + + withAuthToken(authToken) { + this.authToken = authToken; + } +}; export const BaseClient = class BaseClient { constructor(url, subUrl) { @@ -135,35 +142,28 @@ export const BaseClient = class BaseClient { } withUrl(subUrl) { - let c = new BaseClient(this.url, subUrl); + let c = new BaseClient(this.url, subUrl); c.onBeforeRequest = this.onBeforeRequest; return c; } - beforeRequest(params) { - - } + beforeRequest(params) {} get(url, queryParams, params) { params = params || {}; this.onBeforeRequest(params); if (queryParams) { - url += '?' + Array.from(Object.entries(queryParams)).map(([key, value]) => - `${key}=${encodeURIComponent(value)}` - ).join('&'); + url += + '?' + + Array.from(Object.entries(queryParams)) + .map(([key, value]) => `${key}=${encodeURIComponent(value)}`) + .join('&'); } return http.get(this.url + url, params); } - formPost(url, body, params) { - params = params || {}; - this.beforeRequest(params); - this.onBeforeRequest(params); - return http.post(this.url + url, body, params); - } - post(url, body, params) { params = params || {}; params.headers = params.headers || {}; @@ -206,8 +206,8 @@ export const BaseClient = class BaseClient { return http.batch(requests); } -} +}; export const createClient = (url) => { return new GrafanaClient(new BaseClient(url, '')); -} +}; diff --git a/devenv/loadtest/render_test.js b/devenv/loadtest/render_test.js index c690c8b3..3b5b4a36 100644 --- a/devenv/loadtest/render_test.js +++ b/devenv/loadtest/render_test.js @@ -1,28 +1,29 @@ import { check, group } from 'k6'; import { createClient } from './modules/client.js'; -import { - createTestOrgIfNotExists, - upsertTestdataDatasource, - upsertDashboard, -} from './modules/util.js'; +import { createTestOrgIfNotExists, upsertTestdataDatasource, upsertDashboard } from './modules/util.js'; export let options = { noCookiesReset: true, - thresholds: { checks: [ { threshold: 'rate=1', abortOnFail: true } ] }, }; let endpoint = __ENV.URL || 'http://localhost:3000'; +let authToken = __ENV.AUTH_TOKEN || ''; + const client = createClient(endpoint); const dashboard = JSON.parse(open('fixtures/graph_panel.json')); export const setup = () => { - group("user authenticates thru ui with username and password", () => { - let res = client.ui.login('admin', 'admin'); + if (!authToken) { + group('user authenticates thru ui with username and password', () => { + let res = client.ui.login('admin', 'admin'); - check(res, { - 'response status is 200': (r) => r.status === 200, + check(res, { + 'response status is 200': (r) => r.status === 200, + }); }); - }); + } else { + client.withAuthToken(authToken); + } const orgId = createTestOrgIfNotExists(client); client.withOrgId(orgId); @@ -39,19 +40,19 @@ export default (data) => { client.loadCookies(data.cookies); client.withOrgId(data.orgId); - group("render test", () => { - group("render graph panel", () => { - const response = client.ui.renderPanel( - data.orgId, - dashboard.uid, - dashboard.panels[0].id, - ); + if (authToken) { + client.withAuthToken(authToken); + } + + group('render test', () => { + group('render graph panel', () => { + const response = client.ui.renderPanel(data.orgId, dashboard.uid, dashboard.panels[0].id); check(response, { 'response status is 200': (r) => r.status === 200, 'response is a PNG': (r) => r.headers['Content-Type'] == 'image/png', }); }); }); -} +}; -export const teardown = () => {} +export const teardown = () => {}; diff --git a/devenv/loadtest/run.sh b/devenv/loadtest/run.sh index 892f170b..6adb1fe0 100755 --- a/devenv/loadtest/run.sh +++ b/devenv/loadtest/run.sh @@ -5,10 +5,11 @@ cd "$(dirname $0)" run() { local duration='15m' local url='http://localhost:3000' + local authToken='' local vus='2' local iterationsOption='' - while getopts ":d:i:u:v:" o; do + while getopts ":d:i:u:v:a:" o; do case "${o}" in d) duration=${OPTARG} @@ -22,6 +23,9 @@ run() { v) vus=${OPTARG} ;; + a) + authToken=${OPTARG} + ;; esac done shift $((OPTIND-1)) @@ -31,8 +35,9 @@ run() { --network=host \ --mount type=bind,source=$PWD,destination=/src \ -e URL=$url \ + -e AUTH_TOKEN=$authToken \ --rm \ - loadimpact/k6:master run \ + grafana/k6:master run \ --vus $vus \ --duration $duration \ $iterationsOption \ From 29426ae8f4ca780439b2da880153b1b55b80cf5a Mon Sep 17 00:00:00 2001 From: AgnesToulet <35176601+AgnesToulet@users.noreply.github.com> Date: Tue, 29 Apr 2025 14:39:53 +0200 Subject: [PATCH 2/3] fix simple docker env --- devenv/docker/simple/docker-compose.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/devenv/docker/simple/docker-compose.yaml b/devenv/docker/simple/docker-compose.yaml index b7c4fe1e..67233092 100644 --- a/devenv/docker/simple/docker-compose.yaml +++ b/devenv/docker/simple/docker-compose.yaml @@ -1,10 +1,8 @@ -version: '2' - services: grafana: image: grafana/grafana:latest ports: - - 3000 + - 3000:3000 environment: GF_RENDERING_SERVER_URL: http://renderer:8081/render GF_RENDERING_CALLBACK_URL: http://grafana:3000/ From 54814535ad42f962411840679c8ef3e127e89fd4 Mon Sep 17 00:00:00 2001 From: AgnesToulet <35176601+AgnesToulet@users.noreply.github.com> Date: Tue, 29 Apr 2025 14:40:23 +0200 Subject: [PATCH 3/3] fix custom config docker env --- devenv/docker/custom-config/docker-compose.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/devenv/docker/custom-config/docker-compose.yaml b/devenv/docker/custom-config/docker-compose.yaml index c71e9b2d..ad20911f 100644 --- a/devenv/docker/custom-config/docker-compose.yaml +++ b/devenv/docker/custom-config/docker-compose.yaml @@ -1,10 +1,8 @@ -version: '2' - services: grafana: image: grafana/grafana:latest ports: - - 3000 + - 3000:3000 environment: GF_RENDERING_SERVER_URL: http://renderer:8081/render GF_RENDERING_CALLBACK_URL: http://grafana:3000/