Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test(load-test): Performance tests for create dialog and search #1331

Merged
merged 23 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c4e3f27
test(load-tests): add load test for create dialog (serviceowner)
dagfinno Oct 21, 2024
6c859ea
test(load-tests): add load test for simple search (enduser)
dagfinno Oct 21, 2024
25556e8
test(load-tests): add option to send end user ssn as parameter to cre…
dagfinno Oct 21, 2024
2601211
test(load-tests): add testdata for staging(tt02), only one serviceown…
dagfinno Oct 21, 2024
e10b035
test(load-tests): add script to generate tokens
dagfinno Oct 21, 2024
3e1a4d5
test(load-tests): add environment part of enduser data file
dagfinno Oct 21, 2024
f1a5dce
test(load-tests): do not create token if AUthorization given in param…
dagfinno Oct 21, 2024
f96e5aa
test(load-tests): ignore .secrets and files with tokens
dagfinno Oct 21, 2024
345a738
test(load-tests): add github actions workflow
dagfinno Oct 21, 2024
d8e238b
test(load-tests): fix imports; add missing one and remove unused
dagfinno Oct 22, 2024
bb982c2
Update .github/workflows/dispatch-k6-performance.yml
dagfinno Oct 22, 2024
9f55f7f
made changes proposed by coderabbitai
dagfinno Oct 23, 2024
a09f2fa
changed default testSuitePath
dagfinno Oct 23, 2024
6c0f44a
Merge branch 'main' into performance/create_and_search_dialog
dagfinno Oct 23, 2024
e782a2d
changed Headers to headers, as suggested by coderabbitai
dagfinno Oct 23, 2024
108091c
changed Headers to headers, as suggested by coderabbitai
dagfinno Oct 23, 2024
22d55b8
Removed while(true) in default function, as suggested by coderabbitai
dagfinno Oct 23, 2024
a62d4aa
removed an eval and store command status before checking, as suggeste…
dagfinno Oct 23, 2024
335e164
Merge branch 'main' into performance/create_and_search_dialog
dagfinno Oct 23, 2024
7604ae1
Update tests/k6/tests/enduser/performance/simple-search.js
dagfinno Oct 23, 2024
ff4b5e8
Merge branch 'main' into performance/create_and_search_dialog
dagfinno Oct 23, 2024
afc33c4
Merge branch 'main' into performance/create_and_search_dialog
dagfinno Oct 24, 2024
091ec36
Merge branch 'main' into performance/create_and_search_dialog
dagfinno Oct 28, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions .github/workflows/dispatch-k6-performance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: Run K6 performance test

on:
workflow_dispatch:
inputs:
apiVersion:
description: 'API Version'
required: true
default: 'v1'
environment:
description: 'Environment'
required: true
default: 'staging'
type: choice
options:
- test
- staging
- performance
tokens:
description: 'Tokens to generate; for create dialog, search, none, or both'
required: true
default: 'both'
type: choice
options:
- both
- enterprise
- personal
- none
vus:
description: 'Number of VUS'
required: true
type: number
default: 10
duration:
description: 'Duration of test, ie 30s, 1m, 10m'
required: true
default: 1m
type: string
testSuitePath:
description: 'Path to test suite to run'
required: true
default: 'tests/k6/tests/serviceowner/performance/create-dialog.js'

jobs:
k6-performance:
name: "Run K6 performance test"
uses: ./.github/workflows/performance-workflows/workflow-run-k6-performance.yml
secrets:
TOKEN_GENERATOR_USERNAME: ${{ secrets.TOKEN_GENERATOR_USERNAME }}
TOKEN_GENERATOR_PASSWORD: ${{ secrets.TOKEN_GENERATOR_PASSWORD }}
K6_CLOUD_TOKEN: ${{ secrets.K6_CLOUD_TOKEN }}
K6_CLOUD_PROJECT_ID: ${{ secrets.K6_CLOUD_PROJECT_ID }}
with:
environment: ${{ inputs.environment }}
apiVersion: ${{ inputs.apiVersion }}
testSuitePath: ${{ inputs.testSuitePath }}
vus: ${{ inputs.vus }}
duration: ${{ inputs.duration }}
tokens: ${{ inputs.tokens }}

dagfinno marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: Run K6 performance tests

on:
workflow_call:
inputs:
apiVersion:
required: true
type: string
environment:
required: true
type: string
testSuitePath:
required: true
type: string
vus:
required: true
type: number
duration:
required: true
type: string
tokens:
required: true
type: string
secrets:
TOKEN_GENERATOR_USERNAME:
required: true
TOKEN_GENERATOR_PASSWORD:
required: true
K6_CLOUD_TOKEN:
required: true
K6_CLOUD_PROJECT_ID:
required: true

jobs:
k6-test:
runs-on: ubuntu-latest
permissions:
checks: write
pull-requests: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup k6
uses: grafana/setup-k6-action@v1
- name: Run K6 tests (${{ inputs.testSuitePath }})
run: |
./tests/k6/tests/scripts/generate_tokens.sh ./tests/k6/tests/performancetest_data ${{ inputs.tokens }}
k6 run ${{ inputs.testSuitePath }} --quiet --log-output=stdout --include-system-env-vars --vus=${{ inputs.vus }} --duration=${{ inputs.duration }}
env:
API_ENVIRONMENT: ${{ inputs.environment }}
API_VERSION: ${{ inputs.apiVersion }}
TOKEN_GENERATOR_USERNAME: ${{ secrets.TOKEN_GENERATOR_USERNAME }}
TOKEN_GENERATOR_PASSWORD: ${{ secrets.TOKEN_GENERATOR_PASSWORD }}
K6_CLOUD_TOKEN: ${{ secrets.K6_CLOUD_TOKEN }}
K6_CLOUD_PROJECT_ID: ${{ secrets.K6_CLOUD_PROJECT_ID }}
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -374,3 +374,10 @@ FodyWeavers.xsd

# MacOS
.DS_Store

# Secrets file used by act
.secrets

# Generated files with tokens
**/.endusers-with-tokens.csv
**/.serviceowners-with-tokens.csv
7 changes: 5 additions & 2 deletions tests/k6/common/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ function resolveParams(defaultParams, params) {
function getServiceOwnerRequestParams(params = null, tokenOptions = null) {

params = params || {};
const headers = params.Headers || {};
const headers = params.headers || {};
const hasOverridenAuthorizationHeader = headers.Authorization !== undefined;

const defaultParams = {
Expand All @@ -33,11 +33,14 @@ function getServiceOwnerRequestParams(params = null, tokenOptions = null) {
}

function getEnduserRequestParams(params = null, tokenOptions = null) {
params = params || {};
const headers = params.headers || {};
const hasOverridenAuthorizationHeader = headers.Authorization !== undefined;
let defaultParams = {
headers: {
'Accept': 'application/json',
'User-Agent': 'dialogporten-k6',
'Authorization': 'Bearer ' + getEnduserTokenFromGenerator(tokenOptions)
'Authorization': hasOverridenAuthorizationHeader ? headers.Authorization : 'Bearer ' + getEnduserTokenFromGenerator(tokenOptions)
}
}

Expand Down
57 changes: 57 additions & 0 deletions tests/k6/tests/enduser/performance/simple-search.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { getEU, expect, expectStatusFor, describe } from "../../../common/testimports.js";
import { SharedArray } from 'k6/data';
import papaparse from 'https://jslib.k6.io/papaparse/5.1.1/index.js';
import { randomItem } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js';

const filenameEndusers = '../../performancetest_data/.endusers-with-tokens.csv';

const endUsers = new SharedArray('endUsers', function () {
try {
const csvData = papaparse.parse(open(filenameEndusers), { header: true, skipEmptyLines: true }).data;
if (!csvData.length) {
throw new Error('No data found in CSV file');
}
csvData.forEach((user, index) => {
if (!user.token || !user.ssn) {
throw new Error(`Missing required fields at row ${index + 1}`);
}
});
return csvData;
} catch (error) {
throw new Error(`Failed to load end users: ${error.message}`);
}
});

export let options = {
summaryTrendStats: ['avg', 'min', 'med', 'max', 'p(95)', 'p(99)', 'p(99.5)', 'p(99.9)', 'count'],
thresholds: {
'http_req_duration{name:simple search}': [],
'http_reqs{name:simple search}': [],
},
};
dagfinno marked this conversation as resolved.
Show resolved Hide resolved

export default function() {
if ((options.vus === undefined || options.vus === 1) && (options.iterations === undefined || options.iterations === 1)) {
simpleSearch(endUsers[0]);
}
else {
simpleSearch(randomItem(endUsers));
}
}

export function simpleSearch(enduser) {
let paramsWithToken = {
headers: {
Authorization: "Bearer " + enduser.token
},
tags: { name: 'simple search' }
}
let defaultParty = "urn:altinn:person:identifier-no:" + enduser.ssn;
let defaultFilter = "?Party=" + defaultParty;
describe('Perform simple dialog list', () => {
let r = getEU('dialogs' + defaultFilter, paramsWithToken);
expectStatusFor(r).to.equal(200);
expect(r, 'response').to.have.validJsonBody();
});
}
dagfinno marked this conversation as resolved.
Show resolved Hide resolved

2 changes: 2 additions & 0 deletions tests/k6/tests/performancetest_data/endusers-staging.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ssn,resource,scopes
08895699684,super-simple-service,digdir:dialogporten
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
org,orgno,scopes,resource
digdir,991825827,digdir:dialogporten.serviceprovider,super-simple-service
94 changes: 94 additions & 0 deletions tests/k6/tests/scripts/generate_tokens.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#!/bin/bash

# Check if required environment variables are set
if [ -z "$TOKEN_GENERATOR_USERNAME" ] || [ -z "$TOKEN_GENERATOR_PASSWORD" ] || [ -z "$API_ENVIRONMENT" ]; then
echo "Error: TOKEN_GENERATOR_USERNAME, TOKEN_GENERATOR_PASSWORD, and API_ENVIRONMENT must be set"
exit 1
fi
dagfinno marked this conversation as resolved.
Show resolved Hide resolved

# Function to display usage information
usage() {
echo "Usage: $0 <testdatafilepath> <tokens>"
echo " <testdatafilepath>: Path to the test data files"
echo " <tokens>: Type of tokens to generate (both, enterprise, or personal)"
exit 1
}

# Validate arguments
if [ $# -ne 2 ]; then
usage
fi

tokengenuser=${TOKEN_GENERATOR_USERNAME}
tokengenpasswd=${TOKEN_GENERATOR_PASSWORD}

env=""
case $API_ENVIRONMENT in
"test")
env="at21" ;;
"staging")
env="tt02" ;;
"performance")
env="yt01" ;;
*)
echo "Error: Unknown api environment $API_ENVIRONMENT"
exit 1 ;;
esac

testdatafilepath=$1
tokens=$2

# Validate tokens argument
if [[ ! "$tokens" =~ ^(both|enterprise|personal)$ ]]; then
echo "Error: Invalid token type. Must be 'both', 'enterprise', or 'personal'."
usage
fi

serviceowner_datafile="$testdatafilepath/serviceowners-$API_ENVIRONMENT.csv"
serviceowner_tokenfile="$testdatafilepath/.serviceowners-with-tokens.csv"
enduser_datafile="$testdatafilepath/endusers-$API_ENVIRONMENT.csv"
enduser_tokenfile="$testdatafilepath/.endusers-with-tokens.csv"
dagfinno marked this conversation as resolved.
Show resolved Hide resolved

if [ "$tokens" = "both" ] || [ "$tokens" = "enterprise" ]; then
if [ ! -f "$serviceowner_datafile" ]; then
echo "Error: Input file not found: $serviceowner_datafile"
exit 1
fi
echo "org,orgno,scopes,resource,token" > $serviceowner_tokenfile
while IFS=, read -r org orgno scopes resource
do
url="https://altinn-testtools-token-generator.azurewebsites.net/api/GetEnterpriseToken?org=$org&env=$env&scopes=$scopes&orgno=$orgno&ttl=3600"
token=$(curl -s -f $url -u "$tokengenuser:$tokengenpasswd" )
if [ $? -ne 0 ]; then
echo "Error: Failed to generate enterprise token for: $env, $org, $orgno, $scopes "
continue
fi
echo "$org,$orgno,$scopes,$resource,$token" >> $serviceowner_tokenfile
status=$?
if [ $status -ne 0 ]; then
echo "Error: Failed to write enterprise token to file for: $env, $org, $orgno, $scopes"
fi
done < <(tail -n +2 $serviceowner_datafile)
fi
dagfinno marked this conversation as resolved.
Show resolved Hide resolved

if [ "$tokens" = "both" ] || [ "$tokens" = "personal" ]; then
if [ ! -f "$enduser_datafile" ]; then
echo "Error: Input file not found: $enduser_datafile"
exit 1
fi
echo "ssn,resource,scopes,token" > $enduser_tokenfile
while IFS=, read -r ssn resource scopes
do
url="https://altinn-testtools-token-generator.azurewebsites.net/api/GetPersonalToken?env=$env&scopes=$scopes&pid=$ssn&ttl=3600"
token=$(curl -s -f $url -u "$tokengenuser:$tokengenpasswd" )
if [ $? -ne 0 ]; then
echo "Error: Failed to generate personal token for: $ssn, $scopes "
continue
fi
echo "$ssn,$resource,$scopes,$token" >> $enduser_tokenfile
status=$?
if [ $status -ne 0 ]; then
echo "Error: Failed to write personal token to file for: $ssn, $scopes"
fi
done < <(tail -n +2 $enduser_datafile)
fi
dagfinno marked this conversation as resolved.
Show resolved Hide resolved
49 changes: 49 additions & 0 deletions tests/k6/tests/serviceowner/performance/create-dialog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { postSO, expect, describe } from "../../../common/testimports.js";
import { SharedArray } from 'k6/data';
import papaparse from 'https://jslib.k6.io/papaparse/5.1.1/index.js';
import { default as dialogToInsert } from '../testdata/01-create-dialog.js';
import { randomItem } from 'https://jslib.k6.io/k6-utils/1.2.0/index.js';

const filenameServiceowners = '../../performancetest_data/.serviceowners-with-tokens.csv';
const filenameEndusers = `../../performancetest_data/endusers-${__ENV.API_ENVIRONMENT}.csv`;

const serviceOwners = new SharedArray('serviceOwners', function () {
return papaparse.parse(open(filenameServiceowners), { header: true, skipEmptyLines: true }).data;
});
dagfinno marked this conversation as resolved.
Show resolved Hide resolved

const endUsers = new SharedArray('endUsers', function () {
return papaparse.parse(open(filenameEndusers), { header: true, skipEmptyLines: true }).data;
});

export let options = {
summaryTrendStats: ['avg', 'min', 'med', 'max', 'p(95)', 'p(99)', 'p(99.5)', 'p(99.9)', 'count'],
thresholds: {
'http_req_duration{scenario:default}': [`max>=0`],
'http_req_duration{name:create dialog}': [],
'http_reqs{name:create dialog}': [],
},
};

export default function() {
if ((options.vus === undefined || options.vus === 1) && (options.iterations === undefined || options.iterations === 1)) {
createDialog(serviceOwners[0], endUsers[0]);
}
else {
createDialog(randomItem(serviceOwners), randomItem(endUsers));
}
}

export function createDialog(serviceOwner, endUser) {
var paramsWithToken = {
headers: {
Authorization: "Bearer " + serviceOwner.token
},
tags: { name: 'create dialog' }
}

describe('create dialog', () => {
let r = postSO('dialogs', dialogToInsert(endUser.ssn), paramsWithToken);
expect(r.status, 'response status').to.equal(201);
});
oskogstad marked this conversation as resolved.
Show resolved Hide resolved

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { default as dialogToInsert } from '../testdata/01-create-dialog.js';
export function setup() {
// Get the token during setup stage so that it doesn't interfere with timings
return {
Headers: {
headers: {
Authorization: "Bearer " + getServiceOwnerTokenFromGenerator()
}
}
dagfinno marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
4 changes: 2 additions & 2 deletions tests/k6/tests/serviceowner/testdata/01-create-dialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { uuidv4 } from '../../../common/testimports.js'
import { getDefaultEnduserSsn } from "../../../common/token.js";
import { sentinelValue } from "../../../common/config.js";

export default function () {
export default function (endUser = getDefaultEnduserSsn()) {
return {
"serviceResource": "urn:altinn:resource:ttd-dialogporten-automated-tests", // urn starting with urn:altinn:resource:
"party": "urn:altinn:person:identifier-no:" + getDefaultEnduserSsn(), // or urn:altinn:organization:identifier-no:<9 digits>
"party": "urn:altinn:person:identifier-no:" + endUser, // or urn:altinn:organization:identifier-no:<9 digits>
"status": "new", // valid values: new, inprogress, waiting, signing, cancelled, completed
"extendedStatus": "urn:any/valid/uri",
"dueAt": "2033-11-25T06:37:54.2920190Z", // must be UTC
Expand Down
Loading