diff --git a/devenv/docker/loadtest/README.md b/devenv/docker/loadtest/README.md index 1c17f2f8ca9..34faf18d72f 100644 --- a/devenv/docker/loadtest/README.md +++ b/devenv/docker/loadtest/README.md @@ -53,6 +53,12 @@ Run auth proxy test: $ ./run.sh -c auth_proxy_test ``` +Run API key test (option `-k` must be a valid `admin` API key) + +```bash +$ ./run.sh -c auth_token_test -k "" +``` + Example output: diff --git a/devenv/docker/loadtest/annotations_by_tag_test.js b/devenv/docker/loadtest/annotations_by_tag_test.js index cbfa290a52a..578899700c1 100644 --- a/devenv/docker/loadtest/annotations_by_tag_test.js +++ b/devenv/docker/loadtest/annotations_by_tag_test.js @@ -12,15 +12,17 @@ const client = createClient(endpoint); export const setup = () => { const basicAuthClient = createBasicAuthClient(endpoint, 'admin', 'admin'); const orgId = createTestOrgIfNotExists(basicAuthClient); + basicAuthClient.withOrgId(orgId); const datasourceId = createTestdataDatasourceIfNotExists(basicAuthClient); - client.withOrgId(orgId); return { - orgId: orgId, - datasourceId: datasourceId, + orgId, + datasourceId, }; }; export default data => { + client.withOrgId(data.orgId); + group('annotation by tag test', () => { if (__ITER === 0) { group('user authenticates through ui with username and password', () => { diff --git a/devenv/docker/loadtest/auth_key_test.js b/devenv/docker/loadtest/auth_key_test.js new file mode 100644 index 00000000000..6d241a60b86 --- /dev/null +++ b/devenv/docker/loadtest/auth_key_test.js @@ -0,0 +1,82 @@ +import { sleep, check, group } from 'k6'; +import { createClient, createBasicAuthClient, createBearerAuthClient } from './modules/client.js'; +import { createTestOrgIfNotExists, createTestdataDatasourceIfNotExists } from './modules/util.js'; + + +export let options = { + noCookiesReset: true, +}; + +let endpoint = __ENV.URL || 'http://localhost:3000'; + +let apiKey = __ENV.API_KEY; +if (!apiKey) { + throw new Error('This script requires the API key argument -k to be defined.'); +} +const client = createBearerAuthClient(endpoint, apiKey); + +export const setup = () => { + const authClient = createBearerAuthClient(endpoint, apiKey); + + const orgId = createTestOrgIfNotExists(authClient); + authClient.withOrgId(orgId); + const datasourceId = createTestdataDatasourceIfNotExists(authClient); + + return { + orgId, + datasourceId, + }; +}; + +export default data => { + client.withOrgId(data.orgId); + + group('API key test', () => { + if (__ITER === 0) { + group('user can access grafana instance with APIKey', () => { + let res = client.datasources.getAll(); + + check(res, { + 'response status is 200': r => r.status === 200, + }); + }); + } + + if (__ITER !== 0) { + group('batch tsdb requests', () => { + const batchCount = 20; + const requests = []; + const payload = { + from: '1547765247624', + to: '1547768847624', + queries: [ + { + refId: 'A', + scenarioId: 'random_walk', + intervalMs: 10000, + maxDataPoints: 433, + datasourceId: data.datasourceId, + }, + ], + }; + + requests.push({ method: 'GET', url: '/api/annotations?dashboardId=2074&from=1548078832772&to=1548082432772' }); + + for (let n = 0; n < batchCount; n++) { + requests.push({ method: 'POST', url: '/api/tsdb/query', body: payload }); + } + + let responses = client.batch(requests); + for (let n = 0; n < batchCount; n++) { + check(responses[n], { + 'response status is 200': r => r.status === 200, + }); + } + }); + } + }); + + sleep(5); +}; + +export const teardown = data => {}; diff --git a/devenv/docker/loadtest/auth_token_slow_test.js b/devenv/docker/loadtest/auth_token_slow_test.js index ab7bc41314c..f2437ffa275 100644 --- a/devenv/docker/loadtest/auth_token_slow_test.js +++ b/devenv/docker/loadtest/auth_token_slow_test.js @@ -13,15 +13,17 @@ const client = createClient(endpoint); export const setup = () => { const basicAuthClient = createBasicAuthClient(endpoint, 'admin', 'admin'); const orgId = createTestOrgIfNotExists(basicAuthClient); + basicAuthClient.withOrgId(orgId); const datasourceId = createTestdataDatasourceIfNotExists(basicAuthClient); - client.withOrgId(orgId); return { - orgId: orgId, - datasourceId: datasourceId, + orgId, + datasourceId, }; }; export default data => { + client.withOrgId(data.orgId); + group(`user auth token slow test (queries between 1 and ${slowQuery} seconds)`, () => { if (__ITER === 0) { group('user authenticates through ui with username and password', () => { diff --git a/devenv/docker/loadtest/auth_token_test.js b/devenv/docker/loadtest/auth_token_test.js index ccf4b792689..82feecac493 100644 --- a/devenv/docker/loadtest/auth_token_test.js +++ b/devenv/docker/loadtest/auth_token_test.js @@ -12,15 +12,18 @@ const client = createClient(endpoint); export const setup = () => { const basicAuthClient = createBasicAuthClient(endpoint, 'admin', 'admin'); const orgId = createTestOrgIfNotExists(basicAuthClient); + basicAuthClient.withOrgId(orgId); const datasourceId = createTestdataDatasourceIfNotExists(basicAuthClient); - client.withOrgId(orgId); + return { - orgId: orgId, - datasourceId: datasourceId, + orgId, + datasourceId, }; }; export default data => { + client.withOrgId(data.orgId); + group('user auth token test', () => { if (__ITER === 0) { group('user authenticates through ui with username and password', () => { diff --git a/devenv/docker/loadtest/modules/client.js b/devenv/docker/loadtest/modules/client.js index 059d8e76f0a..d98d0c176cd 100644 --- a/devenv/docker/loadtest/modules/client.js +++ b/devenv/docker/loadtest/modules/client.js @@ -17,6 +17,10 @@ export const DatasourcesEndpoint = class DatasourcesEndpoint { this.httpClient = httpClient; } + getAll() { + return this.httpClient.get('/datasources'); + } + getById(id) { return this.httpClient.get(`/datasources/${id}`); } @@ -177,6 +181,25 @@ export class BasicAuthClient extends BaseClient { } } +export class BearerAuthClient extends BaseClient { + constructor(url, subUrl, token) { + super(url, subUrl); + this.token = token; + } + + withUrl(subUrl) { + let c = new BearerAuthClient(this.url, subUrl, this.token); + c.onBeforeRequest = this.onBeforeRequest; + return c; + } + + beforeRequest(params) { + params = params || {}; + params.headers = params.headers || {}; + params.headers['Authorization'] = `Bearer ${this.token}`; + } +} + export const createClient = url => { return new GrafanaClient(new BaseClient(url, '')); }; @@ -184,3 +207,7 @@ export const createClient = url => { export const createBasicAuthClient = (url, username, password) => { return new GrafanaClient(new BasicAuthClient(url, '', username, password)); }; + +export const createBearerAuthClient = (url, token) => { + return new GrafanaClient(new BearerAuthClient(url, '', token)); +} diff --git a/devenv/docker/loadtest/modules/util.js b/devenv/docker/loadtest/modules/util.js index 2d03a9136f8..90697c08e8b 100644 --- a/devenv/docker/loadtest/modules/util.js +++ b/devenv/docker/loadtest/modules/util.js @@ -1,20 +1,26 @@ export const createTestOrgIfNotExists = client => { let orgId = 0; + let res = client.orgs.getByName('k6'); if (res.status === 404) { res = client.orgs.create('k6'); if (res.status !== 200) { throw new Error('Expected 200 response status when creating org'); } - orgId = res.json().orgId; - } else { - orgId = res.json().id; + return res.json().orgId; } - client.withOrgId(orgId); - return orgId; + // This can happen e.g. in Hosted Grafana instances, where even admins + // cannot see organisations + if (res.status !== 200) { + console.info(`unable to get orgs from instance, continuing with default orgId ${orgId}`); + return orgId; + } + + return res.json().id; }; + export const createTestdataDatasourceIfNotExists = client => { const payload = { access: 'proxy', @@ -26,9 +32,10 @@ export const createTestdataDatasourceIfNotExists = client => { let res = client.datasources.getByName(payload.name); if (res.status === 404) { res = client.datasources.create(payload); - if (res.status !== 200) { - throw new Error('Expected 200 response status when creating datasource'); - } + } + + if (res.status !== 200) { + throw new Error(`expected 200 response status when creating datasource, got ${res.status}`); } return res.json().id; diff --git a/devenv/docker/loadtest/run.sh b/devenv/docker/loadtest/run.sh index aa2ff5bef37..e4458aeb0b3 100755 --- a/devenv/docker/loadtest/run.sh +++ b/devenv/docker/loadtest/run.sh @@ -1,4 +1,4 @@ -#/bin/bash +#!/usr/bin/env bash PWD=$(pwd) @@ -9,10 +9,11 @@ run() { testcase='auth_token_test' slowQuery='' out='' + apiKey='' - while getopts ":d:u:v:c:s:o:" o; do + while getopts ":d:u:v:c:s:o:k:" o; do case "${o}" in - d) + d) duration=${OPTARG} ;; u) @@ -27,14 +28,18 @@ run() { s) slowQuery=${OPTARG} ;; - o) out=${OPTARG} + o) + out=${OPTARG} + ;; + k) + apiKey=${OPTARG} ;; esac done shift $((OPTIND-1)) - docker run -t --network=host -v $PWD:/src -e URL=$url -e SLOW_QUERY=$slowQuery -e K6_OUT=$out --rm -i loadimpact/k6:master run --vus $vus --duration $duration src/$testcase.js + docker run -t --network=host -v $PWD:/src -e URL=$url -e SLOW_QUERY=$slowQuery -e K6_OUT=$out -e API_KEY=$apiKey --rm -i loadimpact/k6:master run --vus $vus --duration $duration src/$testcase.js } run "$@"