From 6b82f1629102756f11078a6ec28d6bd29f045a5d Mon Sep 17 00:00:00 2001 From: saquino0827 Date: Tue, 4 Jun 2024 16:44:12 -0500 Subject: [PATCH 01/14] Dockerize load tests Co-authored-by: James Herr --- docker-compose.postgres-test.yml | 4 +- .../DatabasePartnerMetadataStorage.java | 3 +- load-execute.sh | 37 +++++++++++++------ operations/locustfile.py | 9 +++-- 4 files changed, 35 insertions(+), 18 deletions(-) diff --git a/docker-compose.postgres-test.yml b/docker-compose.postgres-test.yml index 23c3b1804..85a5c0004 100644 --- a/docker-compose.postgres-test.yml +++ b/docker-compose.postgres-test.yml @@ -1,7 +1,7 @@ version: "3.7" services: - postgresql-test: + postgresql: image: postgres:16 restart: unless-stopped environment: @@ -9,7 +9,7 @@ services: POSTGRES_PASSWORD: "changeIT!" # pragma: allowlist secret POSTGRES_USER: "intermediary" ports: - - 5434:5432 + - 5433:5432 volumes: - ti_postgres_test_data:/var/lib/postgresql/data diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/database/DatabasePartnerMetadataStorage.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/database/DatabasePartnerMetadataStorage.java index 08bfc8814..50d57d706 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/database/DatabasePartnerMetadataStorage.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/database/DatabasePartnerMetadataStorage.java @@ -129,7 +129,8 @@ public Set readMetadataForMessageLinking(String submissionId) AND (m1.sending_facility_details = m2.sending_facility_details OR m1.sending_facility_details = m2.receiving_facility_details) AND m1.received_message_id <> m2.received_message_id - WHERE m1.received_message_id = ?; + WHERE m1.received_message_id = ? + LIMIT 50; """); // -- LIMIT 50 This is a potential fix for load test failures // since they link all the ids together; diff --git a/load-execute.sh b/load-execute.sh index 812e701d5..355ba10bd 100755 --- a/load-execute.sh +++ b/load-execute.sh @@ -3,13 +3,14 @@ set -e start_api() { echo 'Starting API' - export DB_URL=localhost - export DB_PORT=5434 + export DB_URL=postgresql + export DB_PORT=5432 export DB_NAME=intermediary-test export DB_USER=intermediary export DB_PASS=changeIT! export DB_SSL=require - ./gradlew --no-daemon app:clean app:run > /dev/null 2>&1 & + ./gradlew shadowJar + docker compose up --build -d export API_PID="${!}" echo "API starting at PID ${API_PID}" } @@ -20,10 +21,9 @@ start_database() { sleep 2 echo "Database started" } - migrate_database() { echo 'Migrating database' - liquibase update --changelog-file ./etor/databaseMigrations/root.yml --url jdbc:postgresql://localhost:5434/intermediary-test --username intermediary --password 'changeIT!' --label-filter '!azure' + liquibase update --changelog-file ./etor/databaseMigrations/root.yml --url jdbc:postgresql://localhost:5433/intermediary-test --username intermediary --password 'changeIT!' --label-filter '!azure' echo "Database migrated" } @@ -44,18 +44,32 @@ wait_for_api() { echo 'API is responding' } +warm_up_api() { + echo 'Warming up API...' + curl --output /dev/null --silent --request POST http://localhost:8080/v1/etor/token + curl --output /dev/null --silent --request POST http://localhost:8080/v1/etor/token + curl --output /dev/null --silent --request POST http://localhost:8080/v1/etor/orders + curl --output /dev/null --silent --request POST http://localhost:8080/v1/etor/orders + curl --output /dev/null --silent --request POST http://localhost:8080/v1/etor/results + curl --output /dev/null --silent --request POST http://localhost:8080/v1/etor/results + curl --output /dev/null --silent --request POST http://localhost:8080/v1/etor/metadata/1 + curl --output /dev/null --silent --request POST http://localhost:8080/v1/etor/results/1 + sleep 15 + echo 'API is cozy' +} + run_tests() { echo 'Running the load test' - locust --headless -f ./operations/locustfile.py -H http://localhost:8080 -u 1000 -r 17 -t 5m + locust --headless -f ./operations/locustfile.py -H http://localhost:8080 -u 1000 -r 15 -t 5m } cleanup() { - echo "Killing API at PID ${API_PID}" - kill "${API_PID}" - echo "PID ${API_PID} killed" + echo "Stopping API docker container" + docker compose down + echo "API Docker container stopped" echo "Stopping and deleting database" - docker stop trusted-intermediary-postgresql-test-1 - docker rm -f trusted-intermediary-postgresql-test-1 + docker stop trusted-intermediary-postgresql-1 + docker rm -f trusted-intermediary-postgresql-1 docker volume rm trusted-intermediary_ti_postgres_test_data echo "Database stopped and deleted" } @@ -65,4 +79,5 @@ start_database migrate_database start_api wait_for_api +warm_up_api run_tests diff --git a/operations/locustfile.py b/operations/locustfile.py index f9cc75a45..71ac9c8ba 100644 --- a/operations/locustfile.py +++ b/operations/locustfile.py @@ -5,7 +5,7 @@ import urllib.request import uuid -from locust import FastHttpUser, events, task +from locust import FastHttpUser, events, task, between from locust.runners import MasterRunner HEALTH_ENDPOINT = "/health" @@ -27,6 +27,7 @@ class SampleUser(FastHttpUser): token_refresh_interval = 280 access_token = None + wait_time = between(1, 5) def on_start(self): self.authenticate() @@ -52,11 +53,11 @@ def authenticate(self): data = response.json() self.access_token = data["access_token"] - @task + @task(1) def get_health(self): self.client.get(HEALTH_ENDPOINT) - @task(5) + @task(1) def post_v1_etor_orders(self): response = self.client.post( ORDERS_ENDPOINT, @@ -69,7 +70,7 @@ def post_v1_etor_orders(self): if response.status_code == 200: self.orders_api_called = True - @task(5) + @task(1) def post_v1_etor_results(self): response = self.client.post( RESULTS_ENDPOINT, From 53c2e7d57fbb042500d140e646dd2d2a6a0baf6c Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Tue, 4 Jun 2024 14:57:53 -0700 Subject: [PATCH 02/14] Modified to regenerate submission id for every request, and handle generating and linking placer order id, replaced in message --- operations/locustfile.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/operations/locustfile.py b/operations/locustfile.py index f9cc75a45..e1e10cef4 100644 --- a/operations/locustfile.py +++ b/operations/locustfile.py @@ -31,7 +31,8 @@ class SampleUser(FastHttpUser): def on_start(self): self.authenticate() - self.submission_id = str(uuid.uuid4()) + self.submission_id = None + self.placer_order_id = None self.orders_api_called = False self.results_api_called = False self.sender = "flexion.simulated-hospital" @@ -58,33 +59,39 @@ def get_health(self): @task(5) def post_v1_etor_orders(self): + self.submission_id = str(uuid.uuid4()) + poi = self.placer_order_id or str(uuid.uuid4()) + self.placer_order_id = None if self.placer_order_id else poi response = self.client.post( ORDERS_ENDPOINT, headers={ "Authorization": self.access_token, "RecordId": self.submission_id, }, - data=order_request_body, + data=order_request_body.replace("{{placer_order_id}}", poi), ) if response.status_code == 200: self.orders_api_called = True @task(5) def post_v1_etor_results(self): + self.submission_id = str(uuid.uuid4()) + poi = self.placer_order_id or str(uuid.uuid4()) + self.placer_order_id = None response = self.client.post( RESULTS_ENDPOINT, headers={ "Authorization": self.access_token, "RecordId": self.submission_id, }, - data=result_request_body, + data=result_request_body.replace("{{placer_order_id}}", poi), ) if response.status_code == 200: self.results_api_called = True @task(1) def get_v1_etor_metadata(self): - if self.orders_api_called: + if self.orders_api_called or self.results_api_called: self.client.get( f"{METADATA_ENDPOINT}/{self.submission_id}", headers={"Authorization": self.access_token}, @@ -93,7 +100,7 @@ def get_v1_etor_metadata(self): @task(1) def get_v1_metadata_consolidated(self): - if self.orders_api_called: + if self.orders_api_called or self.results_api_called: self.client.get( f"{CONSOLIDATED_ENDPOINT}/{self.sender}", headers={"Authorization": self.access_token}, @@ -111,8 +118,8 @@ def test_start(environment): return auth_request_body = get_auth_request_body() - order_request_body = get_orders_request_body() - result_request_body = get_results_request_body() + order_request_body = get_order_fhir_message() + result_request_body = get_result_fhir_message() @events.quitting.add_listener @@ -139,13 +146,13 @@ def get_auth_request_body(): return params.encode("utf-8") -def get_orders_request_body(): +def get_order_fhir_message(): # read the sample request body for the orders endpoint with open("examples/Test/e2e/orders/001_OML_O21_short.fhir", "r") as f: return f.read() -def get_results_request_body(): +def get_result_fhir_message(): # read the sample request body for the results endpoint with open("examples/Test/e2e/results/001_ORU_R01_short.fhir", "r") as f: return f.read() From 3c5606aee8dde6ba0da010247489c0a719963c5d Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Tue, 4 Jun 2024 15:01:52 -0700 Subject: [PATCH 03/14] Added example ORM with required values for message linking --- .../Test/e2e/orders/002_ORM_O01_short.fhir | 1017 +++++++++++++++++ .../Test/e2e/results/001_ORU_R01_short.fhir | 2 +- operations/locustfile.py | 2 +- 3 files changed, 1019 insertions(+), 2 deletions(-) create mode 100644 examples/Test/e2e/orders/002_ORM_O01_short.fhir diff --git a/examples/Test/e2e/orders/002_ORM_O01_short.fhir b/examples/Test/e2e/orders/002_ORM_O01_short.fhir new file mode 100644 index 000000000..43a682d86 --- /dev/null +++ b/examples/Test/e2e/orders/002_ORM_O01_short.fhir @@ -0,0 +1,1017 @@ +{ + "resourceType": "Bundle", + "id": "1713991685806650392.f865cc8e-d438-4d5f-9147-05930f25a997", + "meta": { + "lastUpdated": "2024-04-24T20:48:05.813+00:00" + }, + "identifier": { + "system": "https://reportstream.cdc.gov/prime-router", + "value": "31808297" + }, + "type": "message", + "timestamp": "2023-05-06T10:29:16.000+00:00", + "entry": [ + { + "fullUrl": "MessageHeader/87c2d0db-6f31-3666-b9e2-7152e039c11f", + "resource": { + "resourceType": "MessageHeader", + "id": "87c2d0db-6f31-3666-b9e2-7152e039c11f", + "meta": { + "tag": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0103", + "code": "P" + } + ] + }, + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/msh-message-header", + "extension": [ + { + "url": "MSH.7", + "valueString": "20230506052916-0500" + }, + { + "url": "MSH.15", + "valueString": "AL" + }, + { + "url": "MSH.16", + "valueString": "AL" + }, + { + "url": "MSH.21", + "valueIdentifier": { + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/assigning-authority", + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id", + "valueString": "2.16.840.1.113883.9.82" + }, + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id-type", + "valueCode": "ISO" + } + ] + } + ], + "value": "LAB_PRU_COMPONENT" + } + }, + { + "url": "MSH.21", + "valueIdentifier": { + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/assigning-authority", + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id", + "valueString": "2.16.840.1.113883.9.22" + }, + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id-type", + "valueCode": "ISO" + } + ] + } + ], + "value": "LAB_TO_COMPONENT" + } + } + ] + } + ], + "eventCoding": { + "system": "http://terminology.hl7.org/CodeSystem/v2-0003", + "code": "O01", + "display": "ORM^O01^ORM_O01" + }, + "destination": [ + { + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id", + "valueString": "natus.health.state.mn.us" + }, + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id-type", + "valueString": "DNS" + }, + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2Field", + "valueString": "MSH.5" + } + ], + "name": "NATUS", + "receiver": { + "reference": "Organization/1713991685926824266.a1dc31ff-2719-470f-9a9d-2bfd3d6353e3" + } + } + ], + "sender": { + "reference": "Organization/1713991685881552998.10ccacc7-1879-4a11-b1cc-c1f87830d494" + }, + "source": { + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/namespace-id", + "valueString": "Epic" + }, + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id", + "valueString": "1.2.840.114350.1.13.145.2.7.2.695071" + }, + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id-type", + "valueString": "ISO" + }, + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2Field", + "valueString": "MSH.3" + } + ], + "endpoint": "urn:oid:1.2.840.114350.1.13.145.2.7.2.695071" + } + } + }, + { + "fullUrl": "Organization/1713991685881552998.10ccacc7-1879-4a11-b1cc-c1f87830d494", + "resource": { + "resourceType": "Organization", + "id": "1713991685881552998.10ccacc7-1879-4a11-b1cc-c1f87830d494", + "identifier": [ + { + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2Field", + "valueString": "HD.1" + } + ], + "value": "Centracare" + }, + { + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2Field", + "valueString": "HD.2,HD.3" + } + ], + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0301", + "code": "DNS" + } + ] + }, + "value": "centracare.com" + } + ] + } + }, + { + "fullUrl": "Organization/1713991685926824266.a1dc31ff-2719-470f-9a9d-2bfd3d6353e3", + "resource": { + "resourceType": "Organization", + "id": "1713991685926824266.a1dc31ff-2719-470f-9a9d-2bfd3d6353e3", + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2Field", + "valueString": "MSH.6" + } + ], + "identifier": [ + { + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2Field", + "valueString": "HD.1" + } + ], + "value": "MN Public Health Lab" + }, + { + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2Field", + "valueString": "HD.2,HD.3" + } + ], + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0301", + "code": "ISO" + } + ] + }, + "system": "urn:ietf:rfc:3986", + "value": "2.16.840.1.114222.4.1.10080" + } + ] + } + }, + { + "fullUrl": "Patient/1713991686420540992.4160b099-3871-449c-9e90-dadeb21100e7", + "resource": { + "resourceType": "Patient", + "id": "1713991686420540992.4160b099-3871-449c-9e90-dadeb21100e7", + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/pid-patient", + "extension": [ + { + "url": "PID.8" + }, + { + "url": "PID.24", + "valueString": "N" + }, + { + "url": "PID.30", + "valueString": "N" + } + ] + }, + { + "url": "http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName", + "valueHumanName": { + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/xpn-human-name", + "extension": [ + { + "url": "XPN.2", + "valueString": "SADIE" + }, + { + "url": "XPN.3", + "valueString": "S" + } + ] + } + ], + "family": "SMITH", + "given": [ + "SADIE", + "S" + ] + } + } + ], + "identifier": [ + { + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/cx-identifier", + "extension": [ + { + "url": "CX.5", + "valueString": "MR" + } + ] + }, + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2Field", + "valueString": "PID.3" + } + ], + "type": { + "coding": [ + { + "code": "MR" + } + ] + }, + "value": "11102779", + "assigner": { + "reference": "Organization/1713991686376793169.d63d39d1-bd81-4180-a007-1a963547ff8d" + } + } + ], + "name": [ + { + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/xpn-human-name", + "extension": [ + { + "url": "XPN.2", + "valueString": "BB SARAH" + }, + { + "url": "XPN.7", + "valueString": "L" + } + ] + } + ], + "use": "official", + "text": "TestValue", + "family": "SMITH", + "given": [ + "BB SARAH" + ] + } + ], + "telecom": [ + { + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/contactpoint-area", + "valueString": "763" + }, + { + "url": "http://hl7.org/fhir/StructureDefinition/contactpoint-local", + "valueString": "5555555" + }, + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/xtn-contact-point", + "extension": [ + { + "url": "XTN.3", + "valueString": "PH" + }, + { + "url": "XTN.7", + "valueString": "5555555" + }, + { + "url": "XTN.9", + "valueString": "(763)555-5555" + } + ] + } + ], + "system": "phone", + "use": "home" + } + ], + "gender": "male", + "birthDate": "2023-05-04", + "_birthDate": { + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2-date-time", + "valueString": "20230504131023-0500" + }, + { + "url": "http://hl7.org/fhir/StructureDefinition/patient-birthTime", + "valueDateTime": "2023-05-04T13:10:23-05:00" + } + ] + }, + "deceasedBoolean": false, + "address": [ + { + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/xad-address", + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/sad-address-line", + "extension": [ + { + "url": "SAD.1", + "valueString": "555 STATE HIGHWAY 13" + } + ] + }, + { + "url": "XAD.7", + "valueCode": "H" + } + ] + } + ], + "use": "home", + "line": [ + "555 STATE HIGHWAY 13" + ], + "city": "DEER CREEK", + "district": "OTTER TAIL", + "state": "MN", + "postalCode": "56527-9657", + "country": "USA" + } + ], + "multipleBirthInteger": 1 + } + }, + { + "fullUrl": "Organization/1713991686376793169.d63d39d1-bd81-4180-a007-1a963547ff8d", + "resource": { + "resourceType": "Organization", + "id": "1713991686376793169.d63d39d1-bd81-4180-a007-1a963547ff8d", + "identifier": [ + { + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2Field", + "valueString": "HD.1" + } + ], + "value": "CRPMRN" + } + ] + } + }, + { + "fullUrl": "ServiceRequest/1713991686444544794.4cd34f9e-a7da-489c-8c3a-bd8c3edfaf67", + "resource": { + "resourceType": "ServiceRequest", + "id": "1713991686444544794.4cd34f9e-a7da-489c-8c3a-bd8c3edfaf67", + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/business-event", + "valueCode": "NW" + }, + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/business-event", + "valueString": "20230506052913-0500" + }, + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/orc-common-order", + "extension": [ + { + "url": "orc-21-ordering-facility-name", + "valueReference": { + "reference": "Organization/1713991686439304395.dadd5b53-5f0e-4ca5-8e58-d6109d26f5ee" + } + }, + { + "url": "orc-21-ordering-facility-name", + "valueReference": { + "reference": "Organization/1713991686440767640.8871e10e-767a-46a3-81d3-150c106697f7" + } + }, + { + "url": "orc-12-ordering-provider", + "valueReference": { + "reference": "Practitioner/1713991686442730297.157adf6e-ef27-4065-a895-18dfd561d19e" + } + } + ] + }, + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/obr-observation-request", + "extension": [ + { + "url": "OBR.2", + "valueIdentifier": { + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/assigning-authority", + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/namespace-id", + "valueString": "EPIC" + }, + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id", + "valueString": "1.2.840.114350.1.13.145.2.7.2.695071" + }, + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id-type", + "valueCode": "ISO" + } + ] + } + ], + "value": "421832901" + } + } + ] + } + ], + "identifier": [ + { + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2Field", + "valueString": "ORC.2" + }, + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/assigning-authority", + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/namespace-id", + "valueString": "EPIC" + }, + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id", + "valueString": "1.2.840.114350.1.13.145.2.7.2.695071" + }, + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id-type", + "valueCode": "ISO" + } + ] + } + ], + "type": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "code": "PLAC" + } + ] + }, + "value": "{{placer_order_id}}" + } + ], + "status": "unknown", + "subject": { + "reference": "Patient/1713991686420540992.4160b099-3871-449c-9e90-dadeb21100e7" + }, + "authoredOn": "2023-05-06T05:29:13-05:00", + "_authoredOn": { + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2-date-time", + "valueString": "20230506052913-0500" + } + ] + }, + "requester": { + "reference": "PractitionerRole/1713991686430248172.048e0c37-4095-440f-994b-5cd80bf34c69" + } + } + }, + { + "fullUrl": "Organization/1713991686430978000.29e1ded0-6428-44af-8e58-0eeb2bea06af", + "resource": { + "resourceType": "Organization", + "id": "1713991686430978000.29e1ded0-6428-44af-8e58-0eeb2bea06af", + "identifier": [ + { + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2Field", + "valueString": "HD.1" + } + ], + "value": "NPI" + } + ] + } + }, + { + "fullUrl": "Practitioner/1713991686433173618.77eeecb0-0bad-4feb-8c05-d06d06735c90", + "resource": { + "resourceType": "Practitioner", + "id": "1713991686433173618.77eeecb0-0bad-4feb-8c05-d06d06735c90", + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/assigning-authority", + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/namespace-id", + "valueString": "NPI" + }, + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id-unknown-type" + }, + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id-type" + } + ] + }, + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/xcn-practitioner", + "extension": [ + { + "url": "XCN.3", + "valueString": "JANE" + }, + { + "url": "XCN.10", + "valueString": "L" + } + ] + }, + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2Field", + "valueString": "ORC.12" + } + ], + "identifier": [ + { + "type": { + "coding": [ + { + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/codeable-concept-id", + "valueBoolean": true + } + ], + "code": "NPI" + } + ] + }, + "value": "1265136360", + "assigner": { + "reference": "Organization/1713991686430978000.29e1ded0-6428-44af-8e58-0eeb2bea06af" + } + } + ], + "name": [ + { + "use": "official", + "family": "JONES", + "given": [ + "JANE" + ] + } + ] + } + }, + { + "fullUrl": "Organization/1713991686435100196.d7b35c8a-94d2-492f-9010-728a69b55a92", + "resource": { + "resourceType": "Organization", + "id": "1713991686435100196.d7b35c8a-94d2-492f-9010-728a69b55a92", + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/organization-name-type", + "valueCoding": { + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/cwe-coding", + "valueCodeableConcept": { + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2Field", + "valueString": "XON.2" + } + ] + } + } + ] + } + }, + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/xon-organization", + "extension": [ + { + "url": "XON.10", + "valueString": "1043269798" + } + ] + } + ], + "identifier": [ + { + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/assigning-authority", + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/namespace-id", + "valueString": "CMS" + }, + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id-unknown-type" + }, + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id-type" + } + ] + } + ], + "type": { + "coding": [ + { + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/code-index-name", + "valueString": "identifier" + } + ], + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "code": "NPI" + } + ] + }, + "value": "1043269798" + } + ], + "name": "ST. CLOUD HOSPITAL" + } + }, + { + "fullUrl": "PractitionerRole/1713991686430248172.048e0c37-4095-440f-994b-5cd80bf34c69", + "resource": { + "resourceType": "PractitionerRole", + "id": "1713991686430248172.048e0c37-4095-440f-994b-5cd80bf34c69", + "practitioner": { + "reference": "Practitioner/1713991686433173618.77eeecb0-0bad-4feb-8c05-d06d06735c90" + }, + "organization": { + "reference": "Organization/1713991686435100196.d7b35c8a-94d2-492f-9010-728a69b55a92" + } + } + }, + { + "fullUrl": "Organization/1713991686439304395.dadd5b53-5f0e-4ca5-8e58-d6109d26f5ee", + "resource": { + "resourceType": "Organization", + "id": "1713991686439304395.dadd5b53-5f0e-4ca5-8e58-d6109d26f5ee", + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/organization-name-type", + "valueCoding": { + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/cwe-coding", + "valueCodeableConcept": { + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2Field", + "valueString": "XON.2" + } + ] + } + } + ] + } + }, + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/xon-organization", + "extension": [ + { + "url": "XON.10", + "valueString": "1043269798" + } + ] + } + ], + "identifier": [ + { + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/assigning-authority", + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/namespace-id", + "valueString": "CMS" + }, + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id-unknown-type" + }, + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id-type" + } + ] + } + ], + "type": { + "coding": [ + { + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/code-index-name", + "valueString": "identifier" + } + ], + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "code": "NPI" + } + ] + }, + "value": "1043269798" + } + ], + "name": "ST. CLOUD HOSPITAL" + } + }, + { + "fullUrl": "Organization/1713991686440767640.8871e10e-767a-46a3-81d3-150c106697f7", + "resource": { + "resourceType": "Organization", + "id": "1713991686440767640.8871e10e-767a-46a3-81d3-150c106697f7", + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/organization-name-type", + "valueCoding": { + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/cwe-coding", + "valueCodeableConcept": { + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2Field", + "valueString": "XON.2" + } + ] + } + } + ] + } + }, + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/xon-organization", + "extension": [ + { + "url": "XON.10", + "valueString": "739" + } + ] + } + ], + "identifier": [ + { + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/assigning-authority", + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/namespace-id", + "valueString": "MN Public Health Lab" + }, + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id-unknown-type" + }, + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id-type" + } + ] + } + ], + "type": { + "coding": [ + { + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/code-index-name", + "valueString": "identifier" + } + ], + "system": "http://terminology.hl7.org/CodeSystem/v2-0203", + "code": "Submitter ID" + } + ] + }, + "value": "739" + } + ], + "name": "ST. CLOUD HOSPITAL" + } + }, + { + "fullUrl": "Organization/1713991686441504208.f7b9b8ec-da49-4154-9290-0a8df47fbc4d", + "resource": { + "resourceType": "Organization", + "id": "1713991686441504208.f7b9b8ec-da49-4154-9290-0a8df47fbc4d", + "identifier": [ + { + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2Field", + "valueString": "HD.1" + } + ], + "value": "NPI" + } + ] + } + }, + { + "fullUrl": "Practitioner/1713991686442730297.157adf6e-ef27-4065-a895-18dfd561d19e", + "resource": { + "resourceType": "Practitioner", + "id": "1713991686442730297.157adf6e-ef27-4065-a895-18dfd561d19e", + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/assigning-authority", + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/namespace-id", + "valueString": "NPI" + }, + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id-unknown-type" + }, + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/universal-id-type" + } + ] + }, + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/xcn-practitioner", + "extension": [ + { + "url": "XCN.3", + "valueString": "JANE" + }, + { + "url": "XCN.10", + "valueString": "L" + } + ] + } + ], + "identifier": [ + { + "type": { + "coding": [ + { + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/codeable-concept-id", + "valueBoolean": true + } + ], + "code": "NPI" + } + ] + }, + "value": "1265136360", + "assigner": { + "reference": "Organization/1713991686441504208.f7b9b8ec-da49-4154-9290-0a8df47fbc4d" + } + } + ], + "name": [ + { + "use": "official", + "family": "JONES", + "given": [ + "JANE" + ] + } + ] + } + }, + { + "fullUrl": "Observation/1713991686770356478.1910e6f0-c091-4478-a7f6-ede1f3711b9f", + "resource": { + "resourceType": "Observation", + "id": "1713991686770356478.1910e6f0-c091-4478-a7f6-ede1f3711b9f", + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/sub-id", + "valueString": "1" + }, + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/observation-sub-type", + "valueCode": "AOE" + }, + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/obx-observation", + "extension": [ + { + "url": "OBX.2", + "valueId": "NM" + }, + { + "url": "OBX.6" + }, + { + "url": "OBX.11", + "valueString": "O" + } + ] + } + ], + "status": "unknown", + "subject": { + "reference": "Patient/1713991686420540992.4160b099-3871-449c-9e90-dadeb21100e7" + }, + "effectiveDateTime": "2023-05-06T05:00:00-05:00", + "_effectiveDateTime": { + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2-date-time", + "valueString": "20230506050000-0500" + } + ] + }, + "valueQuantity": { + "value": 1769.859285, + "unit": "gram", + "system": "UCUM", + "code": "g" + } + } + }, + { + "fullUrl": "Specimen/1713991686845404556.ccf23f10-1abe-4090-aa7c-c91dd0988c22", + "resource": { + "resourceType": "Specimen", + "id": "1713991686845404556.ccf23f10-1abe-4090-aa7c-c91dd0988c22", + "extension": [ + { + "url": "https://reportstream.cdc.gov/fhir/StructureDefinition/hl7v2Segment", + "valueString": "SPM" + } + ] + } + } + ] +} diff --git a/examples/Test/e2e/results/001_ORU_R01_short.fhir b/examples/Test/e2e/results/001_ORU_R01_short.fhir index d5a33880f..66f74bbd7 100644 --- a/examples/Test/e2e/results/001_ORU_R01_short.fhir +++ b/examples/Test/e2e/results/001_ORU_R01_short.fhir @@ -966,7 +966,7 @@ } ] }, - "value": "423787478" + "value": "{{placer_order_id}}" }, { "extension": [ diff --git a/operations/locustfile.py b/operations/locustfile.py index e1e10cef4..e51424b1d 100644 --- a/operations/locustfile.py +++ b/operations/locustfile.py @@ -148,7 +148,7 @@ def get_auth_request_body(): def get_order_fhir_message(): # read the sample request body for the orders endpoint - with open("examples/Test/e2e/orders/001_OML_O21_short.fhir", "r") as f: + with open("examples/Test/e2e/orders/002_ORM_O01_short.fhir", "r") as f: return f.read() From 77b6f76bdaab913daf0a889ad156005cdfafa385 Mon Sep 17 00:00:00 2001 From: jorge Lopez Date: Wed, 5 Jun 2024 06:19:04 -0700 Subject: [PATCH 04/14] refactoring of endpoints and teardown of docker containers --- load-execute.sh | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/load-execute.sh b/load-execute.sh index 355ba10bd..12e37bcea 100755 --- a/load-execute.sh +++ b/load-execute.sh @@ -46,14 +46,20 @@ wait_for_api() { warm_up_api() { echo 'Warming up API...' - curl --output /dev/null --silent --request POST http://localhost:8080/v1/etor/token - curl --output /dev/null --silent --request POST http://localhost:8080/v1/etor/token - curl --output /dev/null --silent --request POST http://localhost:8080/v1/etor/orders - curl --output /dev/null --silent --request POST http://localhost:8080/v1/etor/orders - curl --output /dev/null --silent --request POST http://localhost:8080/v1/etor/results - curl --output /dev/null --silent --request POST http://localhost:8080/v1/etor/results - curl --output /dev/null --silent --request POST http://localhost:8080/v1/etor/metadata/1 - curl --output /dev/null --silent --request POST http://localhost:8080/v1/etor/results/1 + local endpoint=( + "http://localhost:8080/v1/etor/token" + "http://localhost:8080/v1/etor/orders" + "http://localhost:8080/v1/etor/results" + "http://localhost:8080/v1/etor/metadata/1" + "http://localhost:8080/v1/etor/results/1" + ) + + for endpoint in "${endpoints[@]}"; do + for i in {1..2}; do + curl --output /dev/null --silent --request POST "$endpoint" + done + done + sleep 15 echo 'API is cozy' } @@ -68,9 +74,7 @@ cleanup() { docker compose down echo "API Docker container stopped" echo "Stopping and deleting database" - docker stop trusted-intermediary-postgresql-1 - docker rm -f trusted-intermediary-postgresql-1 - docker volume rm trusted-intermediary_ti_postgres_test_data + docker compose -f docker-compose.postgres-test.yml down -v echo "Database stopped and deleted" } From 9f51f92366f4492400301a4a63fc5fc04de9f0ff Mon Sep 17 00:00:00 2001 From: Basilio Bogado <541149+basiliskus@users.noreply.github.com> Date: Wed, 5 Jun 2024 07:18:05 -0700 Subject: [PATCH 05/14] Added error handling for authentication --- operations/locustfile.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/operations/locustfile.py b/operations/locustfile.py index e51424b1d..c5cbb695e 100644 --- a/operations/locustfile.py +++ b/operations/locustfile.py @@ -50,8 +50,11 @@ def authenticate_periodically(self): def authenticate(self): logging.debug("Authenticating...") response = self.client.post(AUTH_ENDPOINT, data=auth_request_body) - data = response.json() - self.access_token = data["access_token"] + if response.status_code == 200: + data = response.json() + self.access_token = data["access_token"] + else: + logging.error(f"Authentication failed: {response.error}") @task def get_health(self): From 8c1075f7142aa3e284756654fcc8b6d2d5e98f48 Mon Sep 17 00:00:00 2001 From: saquino0827 Date: Wed, 5 Jun 2024 15:57:10 -0500 Subject: [PATCH 06/14] update load tests dockerization to properly warm up orders & results endpoints @Co-authored-by: Luis Pabon --- docker-compose.postgres-test.yml | 2 +- .../DatabasePartnerMetadataStorage.java | 3 +- load-execute.sh | 45 ++++++++++++------- 3 files changed, 32 insertions(+), 18 deletions(-) diff --git a/docker-compose.postgres-test.yml b/docker-compose.postgres-test.yml index 85a5c0004..1cf387c5e 100644 --- a/docker-compose.postgres-test.yml +++ b/docker-compose.postgres-test.yml @@ -9,7 +9,7 @@ services: POSTGRES_PASSWORD: "changeIT!" # pragma: allowlist secret POSTGRES_USER: "intermediary" ports: - - 5433:5432 + - 5434:5432 volumes: - ti_postgres_test_data:/var/lib/postgresql/data diff --git a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/database/DatabasePartnerMetadataStorage.java b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/database/DatabasePartnerMetadataStorage.java index 50d57d706..08bfc8814 100644 --- a/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/database/DatabasePartnerMetadataStorage.java +++ b/etor/src/main/java/gov/hhs/cdc/trustedintermediary/external/database/DatabasePartnerMetadataStorage.java @@ -129,8 +129,7 @@ public Set readMetadataForMessageLinking(String submissionId) AND (m1.sending_facility_details = m2.sending_facility_details OR m1.sending_facility_details = m2.receiving_facility_details) AND m1.received_message_id <> m2.received_message_id - WHERE m1.received_message_id = ? - LIMIT 50; + WHERE m1.received_message_id = ?; """); // -- LIMIT 50 This is a potential fix for load test failures // since they link all the ids together; diff --git a/load-execute.sh b/load-execute.sh index 12e37bcea..3795e6b19 100755 --- a/load-execute.sh +++ b/load-execute.sh @@ -23,7 +23,7 @@ start_database() { } migrate_database() { echo 'Migrating database' - liquibase update --changelog-file ./etor/databaseMigrations/root.yml --url jdbc:postgresql://localhost:5433/intermediary-test --username intermediary --password 'changeIT!' --label-filter '!azure' + liquibase update --changelog-file ./etor/databaseMigrations/root.yml --url jdbc:postgresql://localhost:5434/intermediary-test --username intermediary --password 'changeIT!' --label-filter '!azure' echo "Database migrated" } @@ -46,21 +46,36 @@ wait_for_api() { warm_up_api() { echo 'Warming up API...' - local endpoint=( - "http://localhost:8080/v1/etor/token" - "http://localhost:8080/v1/etor/orders" - "http://localhost:8080/v1/etor/results" - "http://localhost:8080/v1/etor/metadata/1" - "http://localhost:8080/v1/etor/results/1" - ) - - for endpoint in "${endpoints[@]}"; do - for i in {1..2}; do - curl --output /dev/null --silent --request POST "$endpoint" - done - done + pem=$(pwd)/mock_credentials/organization-trusted-intermediary-private-key-local.pem + token=$(jwt encode --exp='+30min' --jti $(uuidgen) --alg RS256 --no-iat -S@${pem}) + + echo 'Warming up auth...' + tiAuthResponse=$(curl --silent --request POST "http://localhost:8080/v1/auth/token" \ + --header "Content-Type: application/x-www-form-urlencoded" \ + --data-urlencode "scope=trusted-intermediary" \ + --data-urlencode "client_assertion=${token}") + + tiToken=$(echo "${tiAuthResponse}" | awk -F '"' '{print $8}') + + echo 'Warming up results...' + resultFile=$(pwd)/examples/Test/e2e/results/001_ORU_R01_short.fhir + + curl --output /dev/null --silent --request POST "http://localhost:8080/v1/etor/results" \ + --header "recordId: 9999" \ + --header "Authorization: Bearer ${tiToken}" \ + --data-binary "@${resultFile}" + + echo 'Warming up orders...' + orderFile=$(pwd)/examples/Test/e2e/orders/002_ORM_O01_short.fhir + + curl --output /dev/null --silent --request POST "http://localhost:8080/v1/etor/results" \ + --header "recordId: 9999" \ + --header "Authorization: Bearer ${tiToken}" \ + --data-binary "@${orderFile}" + + echo 'Warm up nap time...' + sleep 10 - sleep 15 echo 'API is cozy' } From cc82f0f6010ecddd22144fb358d9cec1194a08d9 Mon Sep 17 00:00:00 2001 From: saquino0827 Date: Thu, 6 Jun 2024 11:07:08 -0500 Subject: [PATCH 07/14] Update load-test to warm up metadata --- load-execute.sh | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/load-execute.sh b/load-execute.sh index 3795e6b19..23ef8cd08 100755 --- a/load-execute.sh +++ b/load-execute.sh @@ -59,20 +59,22 @@ warm_up_api() { echo 'Warming up results...' resultFile=$(pwd)/examples/Test/e2e/results/001_ORU_R01_short.fhir - curl --output /dev/null --silent --request POST "http://localhost:8080/v1/etor/results" \ - --header "recordId: 9999" \ + --header "recordId: 6789" \ --header "Authorization: Bearer ${tiToken}" \ --data-binary "@${resultFile}" echo 'Warming up orders...' orderFile=$(pwd)/examples/Test/e2e/orders/002_ORM_O01_short.fhir - - curl --output /dev/null --silent --request POST "http://localhost:8080/v1/etor/results" \ - --header "recordId: 9999" \ + curl --output /dev/null --silent --request POST "http://localhost:8080/v1/etor/orders" \ + --header "recordId: 1234" \ --header "Authorization: Bearer ${tiToken}" \ --data-binary "@${orderFile}" + echo 'Warming up metadata...' + curl --output /dev/null --silent --request GET "http://localhost:8080/v1/etor/metadata/1234" \ + --header "Authorization: Bearer ${tiToken}" \ + echo 'Warm up nap time...' sleep 10 From 3993fb0c8e3c8f4ad49e3efb1d77f22748de094c Mon Sep 17 00:00:00 2001 From: saquino0827 Date: Thu, 6 Jun 2024 11:17:43 -0500 Subject: [PATCH 08/14] use jq instead of awk for parsing jwt token --- load-execute.sh | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/load-execute.sh b/load-execute.sh index 23ef8cd08..1448cf925 100755 --- a/load-execute.sh +++ b/load-execute.sh @@ -50,29 +50,31 @@ warm_up_api() { token=$(jwt encode --exp='+30min' --jti $(uuidgen) --alg RS256 --no-iat -S@${pem}) echo 'Warming up auth...' - tiAuthResponse=$(curl --silent --request POST "http://localhost:8080/v1/auth/token" \ + tiAuthResponse=$(curl --request POST "http://localhost:8080/v1/auth/token" \ --header "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "scope=trusted-intermediary" \ --data-urlencode "client_assertion=${token}") - tiToken=$(echo "${tiAuthResponse}" | awk -F '"' '{print $8}') + echo ${tiAuthResponse} + tiToken=$(echo "${tiAuthResponse}" | jq -r '.access_token') + echo ${tiToken} echo 'Warming up results...' resultFile=$(pwd)/examples/Test/e2e/results/001_ORU_R01_short.fhir - curl --output /dev/null --silent --request POST "http://localhost:8080/v1/etor/results" \ + curl --request POST "http://localhost:8080/v1/etor/results" \ --header "recordId: 6789" \ --header "Authorization: Bearer ${tiToken}" \ --data-binary "@${resultFile}" echo 'Warming up orders...' orderFile=$(pwd)/examples/Test/e2e/orders/002_ORM_O01_short.fhir - curl --output /dev/null --silent --request POST "http://localhost:8080/v1/etor/orders" \ + curl --request POST "http://localhost:8080/v1/etor/orders" \ --header "recordId: 1234" \ --header "Authorization: Bearer ${tiToken}" \ --data-binary "@${orderFile}" echo 'Warming up metadata...' - curl --output /dev/null --silent --request GET "http://localhost:8080/v1/etor/metadata/1234" \ + curl --request GET "http://localhost:8080/v1/etor/metadata/1234" \ --header "Authorization: Bearer ${tiToken}" \ echo 'Warm up nap time...' From 501c1ae5645969f7123a11f9cf11d9a77fca26e8 Mon Sep 17 00:00:00 2001 From: Luis Pabon Date: Thu, 6 Jun 2024 12:20:33 -0400 Subject: [PATCH 09/14] Update load-execute.sh Bring back silence --- load-execute.sh | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/load-execute.sh b/load-execute.sh index 1448cf925..a6f417996 100755 --- a/load-execute.sh +++ b/load-execute.sh @@ -50,31 +50,30 @@ warm_up_api() { token=$(jwt encode --exp='+30min' --jti $(uuidgen) --alg RS256 --no-iat -S@${pem}) echo 'Warming up auth...' - tiAuthResponse=$(curl --request POST "http://localhost:8080/v1/auth/token" \ + tiAuthResponse=$(curl --silent --request POST "http://localhost:8080/v1/auth/token" \ --header "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "scope=trusted-intermediary" \ --data-urlencode "client_assertion=${token}") echo ${tiAuthResponse} tiToken=$(echo "${tiAuthResponse}" | jq -r '.access_token') - echo ${tiToken} echo 'Warming up results...' resultFile=$(pwd)/examples/Test/e2e/results/001_ORU_R01_short.fhir - curl --request POST "http://localhost:8080/v1/etor/results" \ + curl --silent --request POST "http://localhost:8080/v1/etor/results" \ --header "recordId: 6789" \ --header "Authorization: Bearer ${tiToken}" \ --data-binary "@${resultFile}" echo 'Warming up orders...' orderFile=$(pwd)/examples/Test/e2e/orders/002_ORM_O01_short.fhir - curl --request POST "http://localhost:8080/v1/etor/orders" \ + curl --silent --request POST "http://localhost:8080/v1/etor/orders" \ --header "recordId: 1234" \ --header "Authorization: Bearer ${tiToken}" \ --data-binary "@${orderFile}" echo 'Warming up metadata...' - curl --request GET "http://localhost:8080/v1/etor/metadata/1234" \ + curl --silent --request GET "http://localhost:8080/v1/etor/metadata/1234" \ --header "Authorization: Bearer ${tiToken}" \ echo 'Warm up nap time...' From 1a9b3ac0e5c8670ad50a7e46d30a956bc5e2d618 Mon Sep 17 00:00:00 2001 From: saquino0827 Date: Thu, 6 Jun 2024 11:59:05 -0500 Subject: [PATCH 10/14] Load test scripts available in docker and gradle with readme explanation Co-Authored-by: Luis Pabon --- README.md | 6 +- load-execute.sh => docker-load-execute.sh | 9 ++- gradle-load-execute.sh | 68 +++++++++++++++++++++++ 3 files changed, 76 insertions(+), 7 deletions(-) rename load-execute.sh => docker-load-execute.sh (93%) create mode 100755 gradle-load-execute.sh diff --git a/README.md b/README.md index fa14542bd..d49301060 100644 --- a/README.md +++ b/README.md @@ -105,10 +105,12 @@ The `test` directory contains the tests. The `main` directory contains our cust #### Load Testing Load tests are completed with [Locust.io](https://docs.locust.io/en/stable/installation.html). Run the load tests by -running... +running... Currently, we are migrating to using docker (incomplete). The load tests running using gradle is also provided until the migration to docker is complete. ```shell -./load-execute.sh +./gradle-load-execute.sh + +./docker-load-execute.sh ``` This will run the API for you, so no need to run it manually. diff --git a/load-execute.sh b/docker-load-execute.sh similarity index 93% rename from load-execute.sh rename to docker-load-execute.sh index 1448cf925..eb48e4ba2 100755 --- a/load-execute.sh +++ b/docker-load-execute.sh @@ -55,9 +55,7 @@ warm_up_api() { --data-urlencode "scope=trusted-intermediary" \ --data-urlencode "client_assertion=${token}") - echo ${tiAuthResponse} tiToken=$(echo "${tiAuthResponse}" | jq -r '.access_token') - echo ${tiToken} echo 'Warming up results...' resultFile=$(pwd)/examples/Test/e2e/results/001_ORU_R01_short.fhir @@ -73,9 +71,9 @@ warm_up_api() { --header "Authorization: Bearer ${tiToken}" \ --data-binary "@${orderFile}" - echo 'Warming up metadata...' - curl --request GET "http://localhost:8080/v1/etor/metadata/1234" \ - --header "Authorization: Bearer ${tiToken}" \ +# echo 'Warming up metadata...' +# curl --request GET "http://localhost:8080/v1/etor/metadata/1234" \ +# --header "Authorization: Bearer ${tiToken}" \ echo 'Warm up nap time...' sleep 10 @@ -103,4 +101,5 @@ migrate_database start_api wait_for_api warm_up_api +warm_up_api run_tests diff --git a/gradle-load-execute.sh b/gradle-load-execute.sh new file mode 100755 index 000000000..812e701d5 --- /dev/null +++ b/gradle-load-execute.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash +set -e + +start_api() { + echo 'Starting API' + export DB_URL=localhost + export DB_PORT=5434 + export DB_NAME=intermediary-test + export DB_USER=intermediary + export DB_PASS=changeIT! + export DB_SSL=require + ./gradlew --no-daemon app:clean app:run > /dev/null 2>&1 & + export API_PID="${!}" + echo "API starting at PID ${API_PID}" +} + +start_database() { + echo 'Starting database' + docker compose -f docker-compose.postgres-test.yml up -d + sleep 2 + echo "Database started" +} + +migrate_database() { + echo 'Migrating database' + liquibase update --changelog-file ./etor/databaseMigrations/root.yml --url jdbc:postgresql://localhost:5434/intermediary-test --username intermediary --password 'changeIT!' --label-filter '!azure' + echo "Database migrated" +} + +wait_for_api() { + attempt_counter=0 + max_attempts=36 + + until curl --output /dev/null --silent --head --fail http://localhost:8080/health; do + if [ "${attempt_counter}" -eq "${max_attempts}" ];then + echo 'Done waiting for API to respond' + exit 1 + fi + attempt_counter=$(($attempt_counter+1)) + echo 'Waiting for API to respond' + sleep 5 + done + + echo 'API is responding' +} + +run_tests() { + echo 'Running the load test' + locust --headless -f ./operations/locustfile.py -H http://localhost:8080 -u 1000 -r 17 -t 5m +} + +cleanup() { + echo "Killing API at PID ${API_PID}" + kill "${API_PID}" + echo "PID ${API_PID} killed" + echo "Stopping and deleting database" + docker stop trusted-intermediary-postgresql-test-1 + docker rm -f trusted-intermediary-postgresql-test-1 + docker volume rm trusted-intermediary_ti_postgres_test_data + echo "Database stopped and deleted" +} + +trap cleanup EXIT # Run the cleanup function on exit +start_database +migrate_database +start_api +wait_for_api +run_tests From 27dea3454244b71deb0f4ac386b8c1094cf5db81 Mon Sep 17 00:00:00 2001 From: saquino0827 Date: Mon, 10 Jun 2024 09:54:24 -0500 Subject: [PATCH 11/14] Finish warm-up for dockerized load testing --- docker-load-execute.sh | 7 +++++-- gradle-load-execute.sh | 42 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/docker-load-execute.sh b/docker-load-execute.sh index c77bdf77c..b65340855 100755 --- a/docker-load-execute.sh +++ b/docker-load-execute.sh @@ -11,6 +11,7 @@ start_api() { export DB_SSL=require ./gradlew shadowJar docker compose up --build -d + docker compose logs router --no-color > docker-load-test-logs.txt export API_PID="${!}" echo "API starting at PID ${API_PID}" } @@ -47,10 +48,12 @@ wait_for_api() { warm_up_api() { echo 'Warming up API...' pem=$(pwd)/mock_credentials/organization-trusted-intermediary-private-key-local.pem - token=$(jwt encode --exp='+30min' --jti $(uuidgen) --alg RS256 --no-iat -S@${pem}) + token=$(jwt encode --exp='+5min' --jti $(uuidgen) --alg RS256 --no-iat -S@${pem}) + + echo ${token} echo 'Warming up auth...' - tiAuthResponse=$(curl --silent --request POST "http://localhost:8080/v1/auth/token" \ + tiAuthResponse=$(curl --request POST "http://localhost:8080/v1/auth/token" \ --header "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "scope=trusted-intermediary" \ --data-urlencode "client_assertion=${token}") diff --git a/gradle-load-execute.sh b/gradle-load-execute.sh index 812e701d5..757ea0344 100755 --- a/gradle-load-execute.sh +++ b/gradle-load-execute.sh @@ -9,7 +9,7 @@ start_api() { export DB_USER=intermediary export DB_PASS=changeIT! export DB_SSL=require - ./gradlew --no-daemon app:clean app:run > /dev/null 2>&1 & + ./gradlew --no-daemon app:clean app:run > ./gradlew-load-tests.log 2>&1 & export API_PID="${!}" echo "API starting at PID ${API_PID}" } @@ -44,6 +44,41 @@ wait_for_api() { echo 'API is responding' } +warm_up_api() { + echo 'Warming up API...' + pem=$(pwd)/mock_credentials/organization-trusted-intermediary-private-key-local.pem + token=$(jwt encode --exp='+5min' --jti $(uuidgen) --alg RS256 --no-iat -S@${pem}) + + echo ${token} + + echo 'Warming up auth...' + tiAuthResponse=$(curl --request POST "http://localhost:8080/v1/auth/token" \ + --header "Content-Type: application/x-www-form-urlencoded" \ + --data-urlencode "scope=trusted-intermediary" \ + --data-urlencode "client_assertion=${token}") + + tiToken=$(echo "${tiAuthResponse}" | jq -r '.access_token') + + echo 'Warming up results...' + resultFile=$(pwd)/examples/Test/e2e/results/001_ORU_R01_short.fhir + curl --silent --request POST "http://localhost:8080/v1/etor/results" \ + --header "recordId: 6789" \ + --header "Authorization: Bearer ${tiToken}" \ + --data-binary "@${resultFile}" + + echo 'Warming up orders...' + orderFile=$(pwd)/examples/Test/e2e/orders/002_ORM_O01_short.fhir + curl --silent --request POST "http://localhost:8080/v1/etor/orders" \ + --header "recordId: 1234" \ + --header "Authorization: Bearer ${tiToken}" \ + --data-binary "@${orderFile}" + + echo 'Warm up nap time...' + sleep 10 + + echo 'API is cozy' +} + run_tests() { echo 'Running the load test' locust --headless -f ./operations/locustfile.py -H http://localhost:8080 -u 1000 -r 17 -t 5m @@ -54,9 +89,7 @@ cleanup() { kill "${API_PID}" echo "PID ${API_PID} killed" echo "Stopping and deleting database" - docker stop trusted-intermediary-postgresql-test-1 - docker rm -f trusted-intermediary-postgresql-test-1 - docker volume rm trusted-intermediary_ti_postgres_test_data + docker compose -f docker-compose.postgres-test.yml down -v echo "Database stopped and deleted" } @@ -65,4 +98,5 @@ start_database migrate_database start_api wait_for_api +#warm_up_api run_tests From 63947bf149b9ec77af72fdc6cca5064e718c32b8 Mon Sep 17 00:00:00 2001 From: saquino0827 Date: Wed, 12 Jun 2024 10:09:44 -0500 Subject: [PATCH 12/14] Load test auth endpoint checks and README.md updates for load test info --- README.md | 7 +++++-- gradle-load-execute.sh | 3 +++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 677fa748d..d2e833cd8 100644 --- a/README.md +++ b/README.md @@ -104,14 +104,17 @@ The `test` directory contains the tests. The `main` directory contains our cust #### Load Testing -Load tests are completed with [Locust.io](https://docs.locust.io/en/stable/installation.html). Run the load tests by -running... Currently, we are migrating to using docker (incomplete). The load tests running using gradle is also provided until the migration to docker is complete. +Load tests are completed with [Locust.io](https://docs.locust.io/en/stable/installation.html). +Please make sure your `/shared/src/main/resoirces/.env` file does not contain the variable `REPORT_STREAM_URL_PREFIX=http://localhost:7071`. +This causes the environment to use a Mock setup of ReportStream which is how load testing is configured. +Run the load tests by running... ```shell ./gradle-load-execute.sh ./docker-load-execute.sh ``` +Currently, we are migrating to using Azure. Local load testing is using gradle, however a docker load test is available to mimic the Azure environment settings until the azure migration is complete. This will run the API for you, so no need to run it manually. **If you are already running the API, stop it before running the load tests or the cleanup steps won't work.** diff --git a/gradle-load-execute.sh b/gradle-load-execute.sh index 757ea0344..998ff3055 100755 --- a/gradle-load-execute.sh +++ b/gradle-load-execute.sh @@ -57,6 +57,9 @@ warm_up_api() { --data-urlencode "scope=trusted-intermediary" \ --data-urlencode "client_assertion=${token}") + echo 'Auth successful!' + echo ${tiAuthResponse} + tiToken=$(echo "${tiAuthResponse}" | jq -r '.access_token') echo 'Warming up results...' From cc14305d1864b6056fcba82f469a78d5f965cc91 Mon Sep 17 00:00:00 2001 From: saquino0827 Date: Thu, 13 Jun 2024 11:32:44 -0500 Subject: [PATCH 13/14] PR updates --- README.md | 2 -- docker-load-execute.sh | 7 ++++++- gradle-load-execute.sh | 10 ++++++---- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index d2e833cd8..18608974d 100644 --- a/README.md +++ b/README.md @@ -105,8 +105,6 @@ The `test` directory contains the tests. The `main` directory contains our cust #### Load Testing Load tests are completed with [Locust.io](https://docs.locust.io/en/stable/installation.html). -Please make sure your `/shared/src/main/resoirces/.env` file does not contain the variable `REPORT_STREAM_URL_PREFIX=http://localhost:7071`. -This causes the environment to use a Mock setup of ReportStream which is how load testing is configured. Run the load tests by running... ```shell diff --git a/docker-load-execute.sh b/docker-load-execute.sh index b65340855..f0fcb7b38 100755 --- a/docker-load-execute.sh +++ b/docker-load-execute.sh @@ -9,6 +9,7 @@ start_api() { export DB_USER=intermediary export DB_PASS=changeIT! export DB_SSL=require + export REPORT_STREAM_URL_PREFIX= ./gradlew shadowJar docker compose up --build -d docker compose logs router --no-color > docker-load-test-logs.txt @@ -58,7 +59,11 @@ warm_up_api() { --data-urlencode "scope=trusted-intermediary" \ --data-urlencode "client_assertion=${token}") - tiToken=$(echo "${tiAuthResponse}" | jq -r '.access_token') + echo 'Retrieving access token...' + + tiToken=$(echo "${tiAuthResponse}" | grep -o '"access_token": "[^"]*' test-response.json | grep -o '[^"]*$') + + echo ${token} echo 'Warming up results...' resultFile=$(pwd)/examples/Test/e2e/results/001_ORU_R01_short.fhir diff --git a/gradle-load-execute.sh b/gradle-load-execute.sh index 998ff3055..517e6566d 100755 --- a/gradle-load-execute.sh +++ b/gradle-load-execute.sh @@ -9,6 +9,7 @@ start_api() { export DB_USER=intermediary export DB_PASS=changeIT! export DB_SSL=require + export REPORT_STREAM_URL_PREFIX= ./gradlew --no-daemon app:clean app:run > ./gradlew-load-tests.log 2>&1 & export API_PID="${!}" echo "API starting at PID ${API_PID}" @@ -57,10 +58,11 @@ warm_up_api() { --data-urlencode "scope=trusted-intermediary" \ --data-urlencode "client_assertion=${token}") - echo 'Auth successful!' - echo ${tiAuthResponse} + echo 'Retrieving access token...' - tiToken=$(echo "${tiAuthResponse}" | jq -r '.access_token') + tiToken=$(echo "${tiAuthResponse}" | grep -o '"access_token": "[^"]*' test-response.json | grep -o '[^"]*$') + + echo ${token} echo 'Warming up results...' resultFile=$(pwd)/examples/Test/e2e/results/001_ORU_R01_short.fhir @@ -101,5 +103,5 @@ start_database migrate_database start_api wait_for_api -#warm_up_api +warm_up_api run_tests From 093963d747f4376558b9ebc035d5a4f5a01acf33 Mon Sep 17 00:00:00 2001 From: saquino0827 Date: Fri, 14 Jun 2024 11:29:38 -0500 Subject: [PATCH 14/14] Update load test scripts with PR review fixes --- docker-load-execute.sh | 23 +++++++++++++---------- gradle-load-execute.sh | 24 ++++++++++++++---------- 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/docker-load-execute.sh b/docker-load-execute.sh index f0fcb7b38..4efc2b7e3 100755 --- a/docker-load-execute.sh +++ b/docker-load-execute.sh @@ -1,10 +1,12 @@ #!/usr/bin/env bash set -e +local_port=5434 + start_api() { echo 'Starting API' export DB_URL=postgresql - export DB_PORT=5432 + export DB_PORT=${local_port} export DB_NAME=intermediary-test export DB_USER=intermediary export DB_PASS=changeIT! @@ -12,7 +14,6 @@ start_api() { export REPORT_STREAM_URL_PREFIX= ./gradlew shadowJar docker compose up --build -d - docker compose logs router --no-color > docker-load-test-logs.txt export API_PID="${!}" echo "API starting at PID ${API_PID}" } @@ -25,7 +26,7 @@ start_database() { } migrate_database() { echo 'Migrating database' - liquibase update --changelog-file ./etor/databaseMigrations/root.yml --url jdbc:postgresql://localhost:5434/intermediary-test --username intermediary --password 'changeIT!' --label-filter '!azure' + liquibase update --changelog-file ./etor/databaseMigrations/root.yml --url jdbc:postgresql://localhost:${local_port}/intermediary-test --username intermediary --password 'changeIT!' --label-filter '!azure' echo "Database migrated" } @@ -48,8 +49,9 @@ wait_for_api() { warm_up_api() { echo 'Warming up API...' - pem=$(pwd)/mock_credentials/organization-trusted-intermediary-private-key-local.pem - token=$(jwt encode --exp='+5min' --jti $(uuidgen) --alg RS256 --no-iat -S@${pem}) + sleep 5 + tokenPath=$(pwd)/mock_credentials/trusted-intermediary-valid-token.jwt + token=$(cat "${tokenPath}") echo ${token} @@ -59,28 +61,29 @@ warm_up_api() { --data-urlencode "scope=trusted-intermediary" \ --data-urlencode "client_assertion=${token}") + echo "$tiAuthResponse" echo 'Retrieving access token...' - tiToken=$(echo "${tiAuthResponse}" | grep -o '"access_token": "[^"]*' test-response.json | grep -o '[^"]*$') + accessToken=$(echo "$tiAuthResponse" | grep -o '"access_token":"[^"]*' | grep -o '[^"]*$') - echo ${token} + echo "$accessToken" echo 'Warming up results...' resultFile=$(pwd)/examples/Test/e2e/results/001_ORU_R01_short.fhir curl --silent --request POST "http://localhost:8080/v1/etor/results" \ --header "recordId: 6789" \ - --header "Authorization: Bearer ${tiToken}" \ + --header "Authorization: Bearer ${accessToken}" \ --data-binary "@${resultFile}" echo 'Warming up orders...' orderFile=$(pwd)/examples/Test/e2e/orders/002_ORM_O01_short.fhir curl --silent --request POST "http://localhost:8080/v1/etor/orders" \ --header "recordId: 1234" \ - --header "Authorization: Bearer ${tiToken}" \ + --header "Authorization: Bearer ${accessToken}" \ --data-binary "@${orderFile}" echo 'Warm up nap time...' - sleep 10 + sleep 5 echo 'API is cozy' } diff --git a/gradle-load-execute.sh b/gradle-load-execute.sh index 517e6566d..6a72f9e21 100755 --- a/gradle-load-execute.sh +++ b/gradle-load-execute.sh @@ -1,16 +1,18 @@ #!/usr/bin/env bash set -e +local_port=5434 + start_api() { echo 'Starting API' export DB_URL=localhost - export DB_PORT=5434 + export DB_PORT=${local_port} export DB_NAME=intermediary-test export DB_USER=intermediary export DB_PASS=changeIT! export DB_SSL=require export REPORT_STREAM_URL_PREFIX= - ./gradlew --no-daemon app:clean app:run > ./gradlew-load-tests.log 2>&1 & + ./gradlew --no-daemon app:clean app:run > /dev/null 2>&1 & export API_PID="${!}" echo "API starting at PID ${API_PID}" } @@ -24,7 +26,7 @@ start_database() { migrate_database() { echo 'Migrating database' - liquibase update --changelog-file ./etor/databaseMigrations/root.yml --url jdbc:postgresql://localhost:5434/intermediary-test --username intermediary --password 'changeIT!' --label-filter '!azure' + liquibase update --changelog-file ./etor/databaseMigrations/root.yml --url jdbc:postgresql://localhost:${local_port}/intermediary-test --username intermediary --password 'changeIT!' --label-filter '!azure' echo "Database migrated" } @@ -47,8 +49,9 @@ wait_for_api() { warm_up_api() { echo 'Warming up API...' - pem=$(pwd)/mock_credentials/organization-trusted-intermediary-private-key-local.pem - token=$(jwt encode --exp='+5min' --jti $(uuidgen) --alg RS256 --no-iat -S@${pem}) + sleep 5 + tokenPath=$(pwd)/mock_credentials/trusted-intermediary-valid-token.jwt + token=$(cat "${tokenPath}") echo ${token} @@ -58,28 +61,29 @@ warm_up_api() { --data-urlencode "scope=trusted-intermediary" \ --data-urlencode "client_assertion=${token}") + echo "$tiAuthResponse" echo 'Retrieving access token...' - tiToken=$(echo "${tiAuthResponse}" | grep -o '"access_token": "[^"]*' test-response.json | grep -o '[^"]*$') + accessToken=$(echo "$tiAuthResponse" | grep -o '"access_token":"[^"]*' | grep -o '[^"]*$') - echo ${token} + echo "$accessToken" echo 'Warming up results...' resultFile=$(pwd)/examples/Test/e2e/results/001_ORU_R01_short.fhir curl --silent --request POST "http://localhost:8080/v1/etor/results" \ --header "recordId: 6789" \ - --header "Authorization: Bearer ${tiToken}" \ + --header "Authorization: Bearer ${accessToken}" \ --data-binary "@${resultFile}" echo 'Warming up orders...' orderFile=$(pwd)/examples/Test/e2e/orders/002_ORM_O01_short.fhir curl --silent --request POST "http://localhost:8080/v1/etor/orders" \ --header "recordId: 1234" \ - --header "Authorization: Bearer ${tiToken}" \ + --header "Authorization: Bearer ${accessToken}" \ --data-binary "@${orderFile}" echo 'Warm up nap time...' - sleep 10 + sleep 5 echo 'API is cozy' }