Skip to content

Commit 0aa1ae5

Browse files
Merge branch 'develop' into NRL-1841-refresh-account-wide-infra
2 parents f3184a0 + 37c8996 commit 0aa1ae5

11 files changed

Lines changed: 500 additions & 217 deletions

File tree

.github/workflows/pr-env-deploy.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,10 +300,10 @@ jobs:
300300
run: make test-performance-prepare TF_WORKSPACE_NAME=${{ needs.set-environment-id.outputs.environment_id }}
301301

302302
- name: Run Performance Test - Baseline
303-
run: make test-performance-baseline HOST=${{ needs.set-environment-id.outputs.environment_id }}.api.record-locator.dev.national.nhs.uk ENV_TYPE=dev
303+
run: make test-performance-baseline-internal HOST=${{ needs.set-environment-id.outputs.environment_id }}.api.record-locator.dev.national.nhs.uk ENV_TYPE=dev
304304

305305
- name: Run Performance Test - Stress
306-
run: make test-performance-stress HOST=${{ needs.set-environment-id.outputs.environment_id }}.api.record-locator.dev.national.nhs.uk ENV_TYPE=dev
306+
run: make test-performance-stress-internal HOST=${{ needs.set-environment-id.outputs.environment_id }}.api.record-locator.dev.national.nhs.uk ENV_TYPE=dev
307307

308308
- name: Process Performance Test Outputs
309309
run: make test-performance-output

Makefile

Lines changed: 92 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ PERFTEST_PATIENTS_WITH_POINTERS ?= 0
2121
PERFTEST_POINTERS_PER_PATIENT ?= 0
2222
PERFTEST_TYPE_DIST_PROFILE ?= default
2323
PERFTEST_CUSTODIAN_DIST_PROFILE ?= default
24+
PERFTEST_TOKEN_REFRESH_PORT ?= 8765
2425

2526
export PATH := $(PATH):$(PWD)/.venv/bin
2627
export USE_SHARED_RESOURCES := $(shell poetry run python scripts/are_resources_shared_for_stack.py $(TF_WORKSPACE_NAME))
@@ -156,20 +157,58 @@ test-performance-prepare:
156157
mkdir -p $(DIST_PATH)
157158
PYTHONPATH=. poetry run python tests/performance/environment.py setup $(TF_WORKSPACE_NAME)
158159

159-
test-performance: check-warn test-performance-baseline test-performance-stress ## Run the performance tests
160+
test-performance-internal: check-warn test-performance-baseline-internal test-performance-stress-internal ## Run the performance tests against the internal access points
160161

161-
test-performance-baseline:
162-
@echo "Running consumer performance baseline test"
163-
k6 run --out csv=$(DIST_PATH)/consumer-baseline.csv tests/performance/consumer/baseline.js -e HOST=$(HOST) -e ENV_TYPE=$(ENV_TYPE)
164-
165-
test-performance-stress:
166-
@echo "Running consumer performance stress test"
162+
test-performance-baseline-internal: check-warn ## Run the performance baseline tests for the internal access points
163+
@echo "Running internal consumer performance baseline test"
164+
TEST_CONNECT_MODE=internal \
165+
TEST_STACK_DOMAIN=$(shell terraform -chdir=terraform/infrastructure output -raw domain 2>/dev/null) \
166+
k6 run --out csv=$(DIST_PATH)/consumer-baseline.csv tests/performance/consumer/baseline.js -e HOST=$(HOST) -e ENV_TYPE=$(ENV_TYPE)
167+
168+
test-performance-baseline-public: check-warn ## Run the baseline performance tests for the external access points
169+
@echo "Fetching public mode configuration and bearer token..."
170+
@CONFIG_FILE=$$(mktemp /tmp/perf_config_XXXXXX); \
171+
trap "rm -f $$CONFIG_FILE" EXIT; \
172+
PYTHONPATH=. python3 tests/performance/get_test_config.py $(ENV_TYPE) 2>&1 | tail -n 1 > $$CONFIG_FILE; \
173+
PUBLIC_BASE_URL=$$(jq -r '.public_base_url' $$CONFIG_FILE); \
174+
echo "Running consumer performance baseline test against the external access points"; \
175+
TEST_CONNECT_MODE=public \
176+
TEST_PUBLIC_BASE_URL=$$PUBLIC_BASE_URL \
177+
TEST_CONFIG_FILE=$$CONFIG_FILE \
178+
k6 run --out csv=$(DIST_PATH)/consumer-baseline-public.csv tests/performance/consumer/baseline.js -e ENV_TYPE=$(ENV_TYPE)
179+
180+
test-performance-stress-internal: ## Run the performance stress tests for the internal access points
181+
@echo "Running internal consumer performance stress test"
167182
k6 run --out csv=$(DIST_PATH)/consumer-stress.csv tests/performance/consumer/stress.js -e HOST=$(HOST) -e ENV_TYPE=$(ENV_TYPE)
168183

169-
test-performance-soak:
170-
@echo "Running consumer performance soak test"
184+
test-performance-stress-public: check-warn ## Run the stress performance tests for the external access points
185+
@echo "Fetching public mode configuration and bearer token..."
186+
@CONFIG_FILE=$$(mktemp /tmp/perf_config_XXXXXX); \
187+
trap "rm -f $$CONFIG_FILE" EXIT; \
188+
PYTHONPATH=. python3 tests/performance/get_test_config.py $(ENV_TYPE) 2>&1 | tail -n 1 > $$CONFIG_FILE; \
189+
PUBLIC_BASE_URL=$$(jq -r '.public_base_url' $$CONFIG_FILE); \
190+
echo "Running consumer performance stress test against the external access points"; \
191+
TEST_CONNECT_MODE=public \
192+
TEST_PUBLIC_BASE_URL=$$PUBLIC_BASE_URL \
193+
TEST_CONFIG_FILE=$$CONFIG_FILE \
194+
k6 run --out csv=$(DIST_PATH)/consumer-stress-public.csv tests/performance/consumer/stress.js -e ENV_TYPE=$(ENV_TYPE)
195+
196+
test-performance-soak-internal:
197+
@echo "Running internal consumer performance soak test"
171198
k6 run --out csv=$(DIST_PATH)/consumer-soak.csv tests/performance/consumer/soak.js -e HOST=$(HOST) -e ENV_TYPE=$(ENV_TYPE)
172199

200+
test-performance-soak-public: check-warn ## Run the soak performance tests for the external access points
201+
@echo "Fetching public mode configuration and bearer token..."
202+
@CONFIG_FILE=$$(mktemp /tmp/perf_config_XXXXXX); \
203+
trap "rm -f $$CONFIG_FILE" EXIT; \
204+
PYTHONPATH=. python3 tests/performance/get_test_config.py $(ENV_TYPE) 2>&1 | tail -n 1 > $$CONFIG_FILE; \
205+
PUBLIC_BASE_URL=$$(jq -r '.public_base_url' $$CONFIG_FILE); \
206+
echo "Running consumer performance soak test against the external access points"; \
207+
TEST_CONNECT_MODE=public \
208+
TEST_PUBLIC_BASE_URL=$$PUBLIC_BASE_URL \
209+
TEST_CONFIG_FILE=$$CONFIG_FILE \
210+
k6 run --out csv=$(DIST_PATH)/consumer-soak-public.csv tests/performance/consumer/soak.js -e ENV_TYPE=$(ENV_TYPE)
211+
173212
test-performance-output: ## Process outputs from the performance tests
174213
@echo "Processing performance test outputs"
175214
poetry run python tests/performance/process_results.py baseline $(DIST_PATH)/consumer-baseline.csv
@@ -254,6 +293,7 @@ generate-models: check-warn ## Generate Pydantic Models
254293

255294

256295
perftest-generate-permissions: ## Generate perftest permissions and add to nrlf_permissions
296+
@echo "Generating permissions for performance tests with DIST_PATH=$(DIST_PATH)"
257297
PYTHONPATH=. poetry run python tests/performance/producer/generate_permissions.py --output_dir="$(DIST_PATH)/nrlf_permissions/K6PerformanceTest"
258298

259299
perftest-seed-tables: ## Seed tables and upload generated perftest input files to s3
@@ -270,22 +310,61 @@ perftest-prepare: ## Prepare input files for producer & consumer perf tests
270310
mkdir -p "${DIST_PATH}/nft"
271311
aws s3 cp "s3://nhsd-nrlf--${ENV}-metadata/performance/seed-pointers-extract-${PERFTEST_TABLE_NAME}.zip" "${DIST_PATH}/pointer_extract-${PERFTEST_TABLE_NAME}.zip"
272312
unzip "${DIST_PATH}/pointer_extract-${PERFTEST_TABLE_NAME}.zip"
273-
# cp "${DIST_PATH}/nft/seed-pointers-extract-${PERFTEST_TABLE_NAME}.csv" "${DIST_PATH}/seed-pointers-extract.csv"
274313
PYTHONPATH=. poetry run python ./tests/performance/generate_producer_distributions.py
275314

276-
perftest-producer: ## Run producer perf tests
315+
perftest-producer-internal: ## Run producer perf tests
277316
@echo "Running producer performance tests with HOST=$(PERFTEST_HOST) and ENV_TYPE=$(ENV_TYPE) and DIST_PATH=$(DIST_PATH)"
278317
k6 run tests/performance/producer/perftest.js -e HOST=$(PERFTEST_HOST) -e ENV_TYPE=$(ENV_TYPE) -e DIST_PATH=$(DIST_PATH)
279318

280-
perftest-consumer: ## Run consumer perf tests
319+
perftest-producer-public: check-warn ## Run the producer perftests for the external access points
320+
@echo "Starting token refresher in background with ENV=$(ENV) PERFTEST_TOKEN_REFRESH_PORT=$(PERFTEST_TOKEN_REFRESH_PORT)"
321+
ENV=$(ENV) TOKEN_REFRESH_PORT=$(PERFTEST_TOKEN_REFRESH_PORT) PYTHONPATH=. poetry run python ./tests/performance/token_refresher.py &
322+
trap "kill $$(lsof -t -i :$(PERFTEST_TOKEN_REFRESH_PORT)) 2>/dev/null" EXIT
323+
@echo "Fetching public mode configuration..."
324+
@CONFIG_FILE=$$(mktemp /tmp/perf_config_XXXXXX); \
325+
trap "rm -f $$CONFIG_FILE" EXIT; \
326+
PYTHONPATH=. poetry run python tests/performance/get_test_config.py $(ENV_TYPE) 2>&1 | tail -n 1 > $$CONFIG_FILE; \
327+
PUBLIC_BASE_URL=$$(jq -r '.public_base_url' $$CONFIG_FILE); \
328+
echo "Running public producer perftests with ENV_TYPE=$(ENV_TYPE) and DIST_PATH=$(DIST_PATH)"; \
329+
TEST_CONNECT_MODE=public \
330+
TEST_PUBLIC_BASE_URL=$$PUBLIC_BASE_URL \
331+
TEST_CONFIG_FILE=$$CONFIG_FILE \
332+
k6 run tests/performance/producer/perftest.js -e ENV_TYPE=$(ENV_TYPE) -e DIST_PATH=$(DIST_PATH)
333+
kill $$(lsof -t -i :$(PERFTEST_TOKEN_REFRESH_PORT))
334+
335+
perftest-consumer-internal:
281336
@echo "Running consumer performance tests with HOST=$(PERFTEST_HOST) and ENV_TYPE=$(ENV_TYPE) and DIST_PATH=$(DIST_PATH)"
282337
k6 run tests/performance/consumer/perftest.js -e HOST=$(PERFTEST_HOST) -e ENV_TYPE=$(ENV_TYPE) -e DIST_PATH=$(DIST_PATH)
283338

284-
perftest-generate-pointer-table-extract: ## Refresh the perf test input files in s3. Can be expensive to run on large tables
339+
perftest-consumer-public: check-warn ## Run the consumer perftests for the external access points
340+
@echo "Starting token refresher in background with ENV=$(ENV) PERFTEST_TOKEN_REFRESH_PORT=$(PERFTEST_TOKEN_REFRESH_PORT)"
341+
ENV=$(ENV) TOKEN_REFRESH_PORT=$(PERFTEST_TOKEN_REFRESH_PORT) PYTHONPATH=. poetry run python ./tests/performance/token_refresher.py &
342+
trap "kill $$(lsof -t -i :$(PERFTEST_TOKEN_REFRESH_PORT)) 2>/dev/null" EXIT
343+
@echo "Fetching public mode configuration..."
344+
@CONFIG_FILE=$$(mktemp /tmp/perf_config_XXXXXX); \
345+
trap "rm -f $$CONFIG_FILE" EXIT; \
346+
PYTHONPATH=. poetry run python tests/performance/get_test_config.py $(ENV_TYPE) 2>&1 | tail -n 1 > $$CONFIG_FILE; \
347+
PUBLIC_BASE_URL=$$(jq -r '.public_base_url' $$CONFIG_FILE); \
348+
echo "Running public consumer perftests with ENV_TYPE=$(ENV_TYPE) and DIST_PATH=$(DIST_PATH)"; \
349+
TEST_CONNECT_MODE=public \
350+
TEST_PUBLIC_BASE_URL=$$PUBLIC_BASE_URL \
351+
TEST_CONFIG_FILE=$$CONFIG_FILE \
352+
k6 run tests/performance/consumer/perftest.js -e ENV_TYPE=$(ENV_TYPE) -e DIST_PATH=$(DIST_PATH)
353+
kill $$(lsof -t -i :$(PERFTEST_TOKEN_REFRESH_PORT))
354+
355+
perftest-generate-pointer-table-extract:
285356
@echo "Generating pointer table extract with PERFTEST_TABLE_NAME=$(PERFTEST_TABLE_NAME) and DIST_PATH=$(DIST_PATH)"
286357
rm -rf "${DIST_PATH}/nft"
287358
mkdir -p "${DIST_PATH}/nft"
288359
PYTHONPATH=. poetry run python tests/performance/perftest_environment.py generate_pointer_table_extract --output_dir="${DIST_PATH}/nft"
289360
./scripts/get-current-info.sh > "${DIST_PATH}/nft/info.json"
290361
zip -r "${DIST_PATH}/pointer_extract-${PERFTEST_TABLE_NAME}.zip" "${DIST_PATH}/nft"
291362
aws s3 cp "${DIST_PATH}/pointer_extract-${PERFTEST_TABLE_NAME}.zip" "s3://nhsd-nrlf--${ENV}-metadata/performance/seed-pointers-extract-${PERFTEST_TABLE_NAME}.zip"
363+
364+
perftest-run-token-refresher:
365+
@echo "Starting token refresher in background with ENV=$(ENV) PERFTEST_TOKEN_REFRESH_PORT=$(PERFTEST_TOKEN_REFRESH_PORT)"
366+
ENV=$(ENV) TOKEN_REFRESH_PORT=$(PERFTEST_TOKEN_REFRESH_PORT) PYTHONPATH=. poetry run python ./tests/performance/token_refresher.py &
367+
trap "kill $$(lsof -t -i :$(PERFTEST_TOKEN_REFRESH_PORT)) 2>/dev/null" EXIT
368+
369+
make perftest-consumer-public
370+
kill $$(lsof -t -i :$(PERFTEST_TOKEN_REFRESH_PORT))

tests/performance/README.md

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ You will need to generate pointer permissions the first time performance tests a
5050
```sh
5151
# In project root
5252
make perftest-generate-permissions # makes a bunch of json permission files for test organisations
53-
make build # will take all permissions & create nrlf_permissions.zip file
53+
make get-s3-perms ENV=perftest # will take all permissions & create nrlf_permissions.zip file
54+
make build
5455

5556
# apply this new permissions zip file to your environment
5657
cd ./terraform/infrastructure
@@ -71,11 +72,26 @@ make perftest-prepare PERFTEST_TABLE_NAME=nhsd-nrlf--perftest-baseline-pointers-
7172

7273
### Run tests
7374

75+
#### Internal mode
76+
7477
```sh
75-
make perftest-consumer ENV_TYPE=perftest PERFTEST_HOST=perftest-1.perftest.record-locator.national.nhs.uk
76-
make perftest-producer ENV_TYPE=perftest PERFTEST_HOST=perftest-1.perftest.record-locator.national.nhs.uk
78+
assume nhsd-nrlf-test
79+
make perftest-consumer-internal ENV_TYPE=perftest PERFTEST_HOST=perftest-1.perftest.record-locator.national.nhs.uk
80+
make perftest-producer-internal ENV_TYPE=perftest PERFTEST_HOST=perftest-1.perftest.record-locator.national.nhs.uk
7781
```
7882

83+
#### Public mode
84+
85+
Via apigee proxies - most similar to a supplier. Spins up a local http server in background responsible for refreshing bearer token (valid for 5 mins each).
86+
87+
```sh
88+
assume nhsd-nrlf-mgmt
89+
make perftest-consumer-public ENV=perftest
90+
make perftest-producer-public ENV=perftest
91+
```
92+
93+
> Troubleshooting: seeing an unprompted message like "Token refreshed at Mon Jan 19 16:43:35 2026" pop up in your terminal after the test run? The background token server is still going. To resolve, kill the server process with `kill $(lsof -t -i :8765)` (replacing 8765 with your custom port if you specified one).
94+
7995
## Seed data
8096

8197
Must be run on an empty table. Cannot top up an existing set of pointers.

tests/performance/constants.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export const NHS_NUMBERS = REFERENCE_DATA["nhs_numbers"];
1717

1818
// filter only 736253001, 736253002, 1363501000000100, 861421000000109, 749001000000101 for now
1919
export const FILTERED_POINTER_TYPES = [
20-
"736253001",
20+
// "736253001",
2121
"736253002",
2222
"1363501000000100",
2323
"861421000000109",
Lines changed: 33 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,14 @@
1+
import { getHeaders, getFullUrl } from "../test-config.js";
12
import {
3+
POINTER_TYPES,
4+
CATEGORIES,
25
NHS_NUMBERS,
36
POINTER_IDS,
4-
POINTER_TYPES,
57
ODS_CODE,
6-
CATEGORIES,
78
} from "../constants.js";
89
import http from "k6/http";
910
import { check } from "k6";
1011

11-
function getHeaders(odsCode = ODS_CODE) {
12-
return {
13-
"Content-Type": "application/fhir+json",
14-
"X-Request-Id": "K6PerformanceTest",
15-
"NHSD-Correlation-Id": "K6PerformanceTest",
16-
"NHSD-Connection-Metadata": JSON.stringify({
17-
"nrl.ods-code": odsCode,
18-
"nrl.pointer-types": POINTER_TYPES.map(
19-
(type) => `http://snomed.info/sct|${type}`
20-
),
21-
"nrl.app-id": "K6PerformanceTest",
22-
}),
23-
"NHSD-Client-RP-Details": JSON.stringify({
24-
"developer.app.name": "K6PerformanceTest",
25-
"developer.app.id": "K6PerformanceTest",
26-
}),
27-
};
28-
}
29-
3012
function checkResponse(res) {
3113
const is_success = check(res, { "status is 200": (r) => r.status === 200 });
3214
if (!is_success) {
@@ -41,25 +23,22 @@ export function countDocumentReference() {
4123
const identifier = encodeURIComponent(
4224
`https://fhir.nhs.uk/Id/nhs-number|${nhsNumber}`
4325
);
44-
const res = http.get(
45-
`https://${__ENV.HOST}/consumer/DocumentReference?_summary=count&subject:identifier=${identifier}`,
46-
{
47-
headers: getHeaders(),
48-
}
49-
);
26+
27+
const path = `/DocumentReference?_summary=count&subject:identifier=${identifier}`;
28+
const res = http.get(getFullUrl(path, "consumer"), {
29+
headers: getHeaders(ODS_CODE, "consumer"),
30+
});
5031
checkResponse(res);
5132
}
5233

5334
export function readDocumentReference() {
5435
const choice = Math.floor(Math.random() * POINTER_IDS.length);
5536
const id = POINTER_IDS[choice];
5637

57-
const res = http.get(
58-
`https://${__ENV.HOST}/consumer/DocumentReference/${id}`,
59-
{
60-
headers: getHeaders(),
61-
}
62-
);
38+
const path = `/DocumentReference/${id}`;
39+
const res = http.get(getFullUrl(path, "consumer"), {
40+
headers: getHeaders(ODS_CODE, "consumer"),
41+
});
6342

6443
checkResponse(res);
6544
}
@@ -74,12 +53,10 @@ export function searchDocumentReference() {
7453
);
7554
const type = encodeURIComponent(`http://snomed.info/sct|${pointer_type}`);
7655

77-
const res = http.get(
78-
`https://${__ENV.HOST}/consumer/DocumentReference?subject:identifier=${identifier}&type=${type}`,
79-
{
80-
headers: getHeaders(),
81-
}
82-
);
56+
const path = `/DocumentReference?subject:identifier=${identifier}&type=${type}`;
57+
const res = http.get(getFullUrl(path, "consumer"), {
58+
headers: getHeaders(ODS_CODE, "consumer"),
59+
});
8360
checkResponse(res);
8461
}
8562

@@ -95,12 +72,10 @@ export function searchDocumentReferenceByCategory() {
9572
`http://snomed.info/sct|${randomCategory}`
9673
);
9774

98-
const res = http.get(
99-
`https://${__ENV.HOST}/consumer/DocumentReference?subject:identifier=${identifier}&category=${category}`,
100-
{
101-
headers: getHeaders(),
102-
}
103-
);
75+
const path = `/DocumentReference?subject:identifier=${identifier}&category=${category}`;
76+
const res = http.get(getFullUrl(path, "consumer"), {
77+
headers: getHeaders(ODS_CODE, "consumer"),
78+
});
10479
checkResponse(res);
10580
}
10681

@@ -114,13 +89,10 @@ export function searchPostDocumentReference() {
11489
type: `http://snomed.info/sct|${pointer_type}`,
11590
});
11691

117-
const res = http.post(
118-
`https://${__ENV.HOST}/consumer/DocumentReference/_search`,
119-
body,
120-
{
121-
headers: getHeaders(),
122-
}
123-
);
92+
const path = `/DocumentReference/_search`;
93+
const res = http.post(getFullUrl(path, "consumer"), body, {
94+
headers: getHeaders(ODS_CODE, "consumer"),
95+
});
12496
checkResponse(res);
12597
}
12698

@@ -133,13 +105,10 @@ export function searchPostDocumentReferenceByCategory() {
133105
category: `http://snomed.info/sct|${category}`,
134106
});
135107

136-
const res = http.post(
137-
`https://${__ENV.HOST}/consumer/DocumentReference/_search`,
138-
body,
139-
{
140-
headers: getHeaders(),
141-
}
142-
);
108+
const path = `/DocumentReference/_search`;
109+
const res = http.post(getFullUrl(path, "consumer"), body, {
110+
headers: getHeaders(ODS_CODE, "consumer"),
111+
});
143112
checkResponse(res);
144113
}
145114

@@ -150,12 +119,10 @@ export function countPostDocumentReference() {
150119
const body = JSON.stringify({
151120
"subject:identifier": `https://fhir.nhs.uk/Id/nhs-number|${nhsNumber}`,
152121
});
153-
const res = http.post(
154-
`https://${__ENV.HOST}/consumer/DocumentReference/_search?_summary=count`,
155-
body,
156-
{
157-
headers: getHeaders(),
158-
}
159-
);
122+
123+
const path = `/DocumentReference/_search?_summary=count`;
124+
const res = http.post(getFullUrl(path, "consumer"), body, {
125+
headers: getHeaders(ODS_CODE, "consumer"),
126+
});
160127
checkResponse(res);
161128
}

0 commit comments

Comments
 (0)