Skip to content

Commit c5f3af2

Browse files
committed
Squash all changes to overwrite mistakenly committed secret value
1 parent ac8a4fb commit c5f3af2

File tree

13 files changed

+318
-20
lines changed

13 files changed

+318
-20
lines changed

.devcontainer/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ RUN apt-get update \
99
jq apt-transport-https ca-certificates gnupg-agent \
1010
software-properties-common bash-completion python3-pip make libbz2-dev \
1111
libreadline-dev libsqlite3-dev wget llvm libncurses5-dev libncursesw5-dev \
12-
xz-utils tk-dev liblzma-dev netcat libyaml-dev
12+
xz-utils tk-dev liblzma-dev netcat-traditional libyaml-dev
1313

1414
# install aws stuff
1515
RUN wget -O /tmp/awscliv2.zip "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" && \

.github/workflows/run_notify_load.yml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,10 @@ on:
1515
description: 'The duration of the main test'
1616
required: true
1717
default: '900'
18-
rampUpDuration:
19-
description: 'The duration to ramp up to the arrival rate'
20-
required: true
21-
default: '900'
2218
maxVusers:
2319
description: 'Maximum number of vusers to create'
2420
required: true
25-
default: '10000'
21+
default: '1000'
2622

2723
jobs:
2824
run_artillery:
@@ -38,7 +34,6 @@ jobs:
3834
echo "## environment : ${{ github.event.inputs.environment }}" >> "$GITHUB_STEP_SUMMARY"
3935
echo "## arrivalRate : ${{ github.event.inputs.arrivalRate }}" >> "$GITHUB_STEP_SUMMARY"
4036
echo "## duration : ${{ github.event.inputs.duration }}" >> "$GITHUB_STEP_SUMMARY"
41-
echo "## rampUpDuration : ${{ github.event.inputs.rampUpDuration }}" >> "$GITHUB_STEP_SUMMARY"
4237
echo "## maxVusers : ${{ github.event.inputs.maxVusers }}" >> "$GITHUB_STEP_SUMMARY"
4338
4439
- name: Checkout repo

Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,7 @@ local-cpsu:
3636
./scripts/test_cpsu_load_test.sh
3737

3838
local-psu:
39-
./scripts/test_psu_load_test.sh
39+
./scripts/test_psu_load_test.sh
40+
41+
local-notify:
42+
./scripts/test_notify_load_test.sh

artillery/helper/odscodes.mjs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// These ODS codes should match the values in AWS parameter store. They're assumed to be enabled in the whitelist
2+
export const allowedOdsCodes = [
3+
"FA565"
4+
]
5+
6+
export const blockedOdsCodes = [
7+
// "B3J1Z"
8+
]

artillery/helper/psu.mjs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,19 @@ const logger = pino()
66
let oauthToken
77
let tokenExpiryTime
88

9-
export function getBody(isValid = true) {
9+
export function getBody(
10+
isValid = true,
11+
status = "in-progress",
12+
odsCode = "C9Z10",
13+
nhsNumber = "9449304130",
14+
businessStatus = "With Pharmacy",
15+
) {
1016
// If this is intended to be a failed request, mangle the prescription ID.
1117
const prescriptionID = isValid ? shortPrescId() : invalidShortPrescId();
1218

1319
const task_identifier = uuidv4()
1420
const prescriptionOrderItemNumber = uuidv4()
15-
const nhsNumber = "9449304130"
1621
const currentTimestamp = new Date().toISOString()
17-
const odsCode = "C9Z1O"
18-
const status = "in-progress"
19-
const businessStatus = "With Pharmacy"
2022
const body = {
2123
resourceType: "Bundle",
2224
type: "transaction",
@@ -130,7 +132,6 @@ export async function getPSUParams(requestParams, vuContext) {
130132
const isValid = vuContext.scenario.tags.isValid
131133
const body = getBody(isValid)
132134

133-
requestParams.json = body
134135
// This sets the body of the request and some variables so headers are unique
135136
requestParams.json = body
136137
vuContext.vars.x_request_id = uuidv4()

artillery/notify_entrypoint.mjs

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import {v4 as uuidv4} from "uuid"
2+
import pino from "pino"
3+
import {getSharedAuthToken, getBody} from "./helper/psu.mjs"
4+
import {allowedOdsCodes, blockedOdsCodes} from "./helper/odscodes.mjs"
5+
6+
export { getSharedAuthToken }
7+
8+
const logger = pino()
9+
10+
const LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
11+
const DIGITS = "0123456789";
12+
13+
const randomChar = (chars) => chars[Math.floor(Math.random() * chars.length)];
14+
15+
/** Generate one two-letter, three-digit ODS code, e.g. "AB123" */
16+
const generateOdsCode = () =>
17+
`${randomChar(LETTERS)}${randomChar(LETTERS)}${randomChar(DIGITS)}${randomChar(DIGITS)}${randomChar(DIGITS)}`;
18+
19+
function buildFullOdsCodes(targetCount, seedCodes) {
20+
const codes = new Set(seedCodes);
21+
22+
while (codes.size < targetCount) {
23+
codes.add(generateOdsCode());
24+
}
25+
26+
return Array.from(codes);
27+
}
28+
29+
// The complete list of ODS codes
30+
const fullOdsCodes = allowedOdsCodes.concat(blockedOdsCodes)
31+
32+
function computeCheckDigit(nhsNumber) {
33+
const factors = [10,9,8,7,6,5,4,3,2]
34+
let total = 0
35+
36+
for (let i = 0; i < 9; i++) {
37+
total += parseInt(nhsNumber.charAt(i),10) * factors[i]
38+
}
39+
40+
const rem = total % 11
41+
let d = 11 - rem
42+
if (d === 11) d = 0
43+
44+
return d
45+
}
46+
47+
function generateValidNhsNumber() {
48+
while (true) {
49+
const partial = Array.from({length:9},() => Math.floor(Math.random()*10)).join("")
50+
const cd = computeCheckDigit(partial)
51+
if (cd < 10) return partial + cd
52+
}
53+
}
54+
55+
// Apparently Math.sampleNormal isn't a function? Do a quick Box-Muller transform instead
56+
function sampleNormal(mean = 0, sd = 1) {
57+
let u = 0, v = 0;
58+
// avoid zeros because of log(0)
59+
while (u === 0) u = Math.random();
60+
while (v === 0) v = Math.random();
61+
const z = Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
62+
return z * sd + mean;
63+
}
64+
65+
export function initUser(context, events, done) {
66+
// Generate data for a patient
67+
context.vars.odsCode = fullOdsCodes[Math.floor(Math.random()*fullOdsCodes.length)]
68+
context.vars.nhsNumber = generateValidNhsNumber()
69+
70+
let prescriptionCount = Math.round(sampleNormal(3,1))
71+
if (prescriptionCount < 1) prescriptionCount = 1 // just truncate at 1.
72+
context.vars.prescriptionCount = prescriptionCount
73+
context.vars.loopcount = 0
74+
75+
logger.info(`Patient ${context.vars.nhsNumber}, ODS ${context.vars.odsCode} has ${context.vars.prescriptionCount} prescriptions`)
76+
77+
done()
78+
}
79+
80+
export function generatePrescData(requestParams, context, ee, next) {
81+
logger.debug(`Generating a prescription for patient ${context.vars.nhsNumber}`)
82+
const body = getBody(
83+
true, /* isValid */
84+
"completed", /* status */
85+
context.vars.odsCode, /* odsCode */
86+
context.vars.nhsNumber, /* nhsNumber */
87+
"ready to collect" /* Item status */
88+
)
89+
// The body is fine - it works when I put it in postman
90+
91+
requestParams.json = body
92+
context.vars.x_request_id = uuidv4()
93+
context.vars.x_correlation_id = uuidv4()
94+
95+
context.vars.loopcount += 1
96+
97+
// Wait this long between requests
98+
let meanDelay = 10 // seconds
99+
let stdDevDelay = 10 // seconds
100+
let delay = 0
101+
if (context.vars.loopcount < context.vars.prescriptionCount) {
102+
delay = sampleNormal(meanDelay, stdDevDelay)
103+
while (delay < 0) delay = sampleNormal(meanDelay, stdDevDelay)
104+
}
105+
106+
context.vars.nextDelay = delay
107+
logger.debug(`Patient ${context.vars.nhsNumber} (on prescription update ${context.vars.loopcount}/${context.vars.prescriptionCount}) will think for ${context.vars.nextDelay} seconds`)
108+
109+
next()
110+
}
111+

artillery/notify_load_test.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
config:
2+
processor: "./notify_entrypoint.mjs"
3+
plugins:
4+
expect: {}
5+
apdex: {}
6+
phases:
7+
- name: run phase
8+
duration: "{{ $env.duration }}"
9+
arrivalRate: "{{ $env.arrivalRate }}"
10+
maxVusers: "{{ $env.maxVusers }}"
11+
environments:
12+
dev:
13+
target: https://internal-dev.api.service.nhs.uk/
14+
ref:
15+
target: https://ref.api.service.nhs.uk/
16+
int:
17+
target: https://int.api.service.nhs.uk/
18+
pr:
19+
target: https://psu-pr-{{ prNumber }}.dev.eps.national.nhs.uk/
20+
21+
before:
22+
flow:
23+
- function: getSharedAuthToken
24+
25+
scenarios:
26+
- name: dynamic PSU calls for each patient
27+
28+
flow:
29+
- function: initUser
30+
31+
# for each prescription (number of prescriptions is variable)
32+
- loop:
33+
- function: getSharedAuthToken
34+
- post:
35+
url: "/prescription-status-update/"
36+
beforeRequest: "generatePrescData"
37+
headers:
38+
Authorization: "Bearer {{ authToken }}"
39+
x-request-id: "{{ x_request_id }}"
40+
x-correlation-id: "{{ x_correlation_id }}"
41+
expect:
42+
- statusCode: 201
43+
- think: "{{ nextDelay }}seconds"
44+
count: "{{ prescriptionCount }}"

scripts/run_cpsu_load_test.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ npx artillery run-fargate \
5757
--security-group-ids "${security_group}" \
5858
--subnet-ids "${vpc_subnets}" \
5959
--task-role-name "${artillery_worker_role_name}" \
60-
--dotenv runtimeenv.env \
60+
--env-file runtimeenv.env \
6161
--output cpsu_load_test.json \
6262
artillery/cpsu_load_test.yml
6363

scripts/run_notify_load_test.sh

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#!/usr/bin/env bash
2+
3+
4+
if [ -z "${artillery_key}" ]; then
5+
echo "artillery_key is unset or set to the empty string"
6+
exit 1
7+
fi
8+
9+
if [ -z "${environment}" ]; then
10+
echo "environment is unset or set to the empty string"
11+
exit 1
12+
fi
13+
14+
if [ -z "${maxVusers}" ]; then
15+
echo "maxVusers is unset or set to the empty string"
16+
exit 1
17+
fi
18+
19+
if [ -z "${duration}" ]; then
20+
echo "duration is unset or set to the empty string"
21+
exit 1
22+
fi
23+
24+
if [ -z "${arrivalRate}" ]; then
25+
echo "arrivalRate is unset or set to the empty string"
26+
exit 1
27+
fi
28+
29+
if ! [[ "${environment}" =~ ^(dev|ref)$ ]]
30+
then
31+
echo "environment must be dev or ref"
32+
exit 1
33+
fi
34+
35+
security_group=$(aws cloudformation list-exports --output json | jq -r '.Exports[] | select(.Name == "artillery-resources:ArtillerySecurityGroupId") | .Value' | grep -o '[^:]*$')
36+
export security_group
37+
vpc_subnets=$(aws cloudformation list-exports --output json | jq -r '.Exports[] | select(.Name == "vpc-resources:PrivateSubnets") | .Value' | grep -o '[^:]*$')
38+
export vpc_subnets
39+
40+
artillery_worker_role_name=$(aws cloudformation list-exports --output json | jq -r '.Exports[] | select(.Name == "artillery-resources:ArtilleryWorkerRoleName") | .Value' | grep -o '[^:]*$')
41+
export artillery_worker_role_name
42+
43+
cat <<EOF > runtimeenv.env
44+
maxVusers=$maxVusers
45+
duration=$duration
46+
arrivalRate=$arrivalRate
47+
EOF
48+
49+
echo ${launch_config}
50+
51+
# shellcheck disable=SC2090,SC2086
52+
npx artillery run-fargate \
53+
--environment "${environment}" \
54+
--secret psu_api_key psu_private_key psu_kid \
55+
--region eu-west-2 \
56+
--cluster artilleryio-cluster \
57+
--security-group-ids "${security_group}" \
58+
--subnet-ids "${vpc_subnets}" \
59+
--task-role-name "${artillery_worker_role_name}" \
60+
--env-file runtimeenv.env \
61+
--output notify_load_test.json \
62+
artillery/notify_load_test.yml
63+
64+
npx artillery report notify_load_test.json

scripts/run_psu_load_test.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ npx artillery run-fargate \
5959
--security-group-ids "${security_group}" \
6060
--subnet-ids "${vpc_subnets}" \
6161
--task-role-name "${artillery_worker_role_name}" \
62-
--dotenv runtimeenv.env \
62+
--env-file runtimeenv.env \
6363
--output psu_load_test.json \
6464
artillery/psu_load_test.yml
6565

0 commit comments

Comments
 (0)