Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add FTPS server to system test and test successful upload #268

Merged
merged 66 commits into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
c7fb3d1
Update `pixl_ehr` README, not actually using cogstack Docker service
milanmlft Jan 30, 2024
8faea3c
Move cogstack dummy service to subdirectory
milanmlft Jan 30, 2024
c936aac
Move ftp-server to `test/dummy-services`
milanmlft Jan 30, 2024
afa866d
Add ftp-server to system-test docker compose
milanmlft Jan 30, 2024
ca639b4
Add `SendViaFTPS` to `OnChange` callback for `orthanc-anon`
milanmlft Jan 30, 2024
69eeee1
Check FTPS upload in system test
milanmlft Jan 30, 2024
cafff1a
Fix `check_ftps_upload` and log expected export files
milanmlft Jan 30, 2024
6da3296
Trigger system test on CI
milanmlft Jan 30, 2024
75d6c6f
Merge branch 'main' into milanmlft/system-test-ftps
milanmlft Jan 30, 2024
843c04b
Typo fix
milanmlft Jan 30, 2024
3d50586
Merge branch 'main' into milanmlft/system-test-ftps
milanmlft Jan 30, 2024
a9be50b
Rename endpoint uploading env variable and set default to `false`
milanmlft Jan 30, 2024
0a94fdb
Remove type annotations for `OnChange` again
milanmlft Jan 30, 2024
139c0b9
Fix `glob_list` creation
milanmlft Jan 30, 2024
eaea84a
Make container naming consistent
milanmlft Jan 30, 2024
769c0ac
Rename mount data env variable and expand it
milanmlft Jan 30, 2024
6fe45a2
Merge branch 'main' into milanmlft/system-test-ftps
milanmlft Jan 31, 2024
99a209f
Print uploaded files instead of logging
milanmlft Jan 31, 2024
a217b3c
Merge branch 'main' into milanmlft/system-test-ftps
milanmlft Jan 31, 2024
9c29421
Hardcode project name slug
milanmlft Jan 31, 2024
7744f0d
Disable Azure related code in `orthanc-anon` plugin if no Azure avail…
milanmlft Jan 31, 2024
c906fcb
fix: get the correct pseudo-anon ID before sending via FTPS
milanmlft Jan 31, 2024
226ad0c
Merge branch 'main' into milanmlft/system-test-ftps
milanmlft Jan 31, 2024
5d53ea0
Add missing env variables
milanmlft Jan 31, 2024
edc1789
fix: check for env var correctly
milanmlft Jan 31, 2024
f217a0d
More bug fixes
milanmlft Jan 31, 2024
52e5ce1
More consistent naming of env variable
milanmlft Feb 1, 2024
104d50c
Rename query helper function
milanmlft Feb 1, 2024
9136b4f
Make helper functions snakecase
milanmlft Feb 1, 2024
dd1dd7a
Also update the `.env` files
milanmlft Feb 1, 2024
fce8fab
Make orthanc plugin code more type safe and system exit when query fails
milanmlft Feb 1, 2024
5484e06
More type safety
milanmlft Feb 1, 2024
a7f292e
Define the `FTP_PORT` environment variable
milanmlft Feb 1, 2024
7f314cd
Document the FTPS server requirements
milanmlft Feb 1, 2024
c25ac15
Markdown formatting
milanmlft Feb 1, 2024
a5b1123
Fix FTP config
milanmlft Feb 1, 2024
f71958f
Merge branch 'main' into milanmlft/system-test-ftps
milanmlft Feb 1, 2024
65f7d0b
Add more FTP logging
milanmlft Feb 1, 2024
b487e4b
Allow python 11 for CLI
stefpiatek Feb 2, 2024
d1f0a6d
Ignore new data mount location
stefpiatek Feb 2, 2024
0ad83b1
Allow system tests to pass
stefpiatek Feb 2, 2024
c9d7083
DEBUG: check ftp logs
stefpiatek Feb 2, 2024
de73920
Poll for two minutes instead of hardcoded wait
stefpiatek Feb 2, 2024
4bff8b9
Poll for two minutes instead of hardcoded wait
stefpiatek Feb 2, 2024
a84cde7
More debugging
stefpiatek Feb 2, 2024
324d17c
Reorder system test scripts
stefpiatek Feb 2, 2024
cd838e4
For arguments sake, try polling for images for 4 minutes
stefpiatek Feb 2, 2024
dcffd58
Increase timeout
stefpiatek Feb 2, 2024
7c4ab34
Use logger rather than logging directly
stefpiatek Feb 2, 2024
6ba1926
Increase timeout
stefpiatek Feb 2, 2024
89874f9
Add comment about number of expected studies
milanmlft Feb 2, 2024
4103790
Clean up ftp output directory after check
milanmlft Feb 2, 2024
e71b42e
Actually wait for the desired number of seconds
milanmlft Feb 2, 2024
75cc1d9
Empty-Commit to test building
stefpiatek Feb 2, 2024
d8daa93
Fix CI for ftps upload
stefpiatek Feb 2, 2024
06db377
Remove logs check
stefpiatek Feb 2, 2024
71db543
Reorder system tests to make parquet upload export testing easier
stefpiatek Feb 2, 2024
ffd53af
Empty-Commit to test building
stefpiatek Feb 2, 2024
6e399d4
Wait until there are two exported DICOM messages
stefpiatek Feb 2, 2024
bce13f8
Document FTPS server specifics
milanmlft Feb 2, 2024
327275f
Try to make ftp server more robust in CI
stefpiatek Feb 2, 2024
ffa298f
Copy certs into dockerfile instead of mounting
stefpiatek Feb 2, 2024
cd3706a
Copy certs into dockerfile instead of mounting
stefpiatek Feb 2, 2024
7bdedef
Update core test compose file too
stefpiatek Feb 2, 2024
122c253
Put "when" and "then" statements on their own lines
milanmlft Feb 2, 2024
83bc46e
docs: SSL certificates are now copied instead of mounted
milanmlft Feb 2, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,16 @@ ORTHANC_RAW_MAXIMUM_STORAGE_SIZE= // MB

# PIXL Orthanc anon instance
ORTHANC_ANON_USERNAME=
ORTHANC_ANON_PASSWORD=
ORTHANC_ANON_PASSWORD=
ORTHANC_ANON_AE_TITLE=
ORTHANC_ANON_HTTP_TIMEOUT=60
ORTHANC_AUTOROUTE_ANON_TO_AZURE=false
ORTHANC_AUTOROUTE_ANON_TO_FTPS=true
milanmlft marked this conversation as resolved.
Show resolved Hide resolved
ENABLE_DICOM_WEB=true
STUDY_TIME_OFFSET=
SALT_VALUE=PIXL


# UCVNAQR DICOM node information
# UCVNAQR DICOM node information
VNAQR_AE_TITLE=
VNAQR_DICOM_PORT=
VNAQR_IP_ADDR=
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ services:
ORTHANC_USERNAME: ${ORTHANC_ANON_USERNAME}
ORTHANC_PASSWORD: ${ORTHANC_ANON_PASSWORD}
ORTHANC_ANON_AE_TITLE: ${ORTHANC_ANON_AE_TITLE}
ORTHANC_AUTOROUTE_ANON_TO_AZURE: ${ORTHANC_AUTOROUTE_ANON_TO_AZURE}
ORTHANC_AUTOROUTE_ANON_TO_FTPS: ${ORTHANC_AUTOROUTE_ANON_TO_FTPS}
ORTHANC_RAW_AE_TITLE: ${ORTHANC_RAW_AE_TITLE}
ORTHANC_RAW_DICOM_PORT: "4242"
ORTHANC_RAW_HOSTNAME: "orthanc-raw"
Expand Down
18 changes: 11 additions & 7 deletions orthanc/orthanc-anon/plugin/pixl.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from io import BytesIO
from pathlib import Path
from time import sleep
from typing import TYPE_CHECKING

import requests
import yaml
Expand All @@ -38,6 +39,9 @@
import orthanc
import pixl_dcmd

if TYPE_CHECKING:
from typing import Any

ORTHANC_USERNAME = config("ORTHANC_USERNAME")
ORTHANC_PASSWORD = config("ORTHANC_PASSWORD")
ORTHANC_URL = "http://localhost:8042"
Expand Down Expand Up @@ -180,17 +184,17 @@ def SendViaFTPS(resourceId: str) -> None:

def ShouldAutoRoute():
"""
Checks whether ORTHANC_AUTOROUTE_ANON_TO_AZURE environment variable is
Checks whether ORTHANC_AUTOROUTE_ANON_TO_FTPS environment variable is
set to true or false
"""
return os.environ.get("ORTHANC_AUTOROUTE_ANON_TO_AZURE", "false").lower() == "true"
return os.environ.get("ORTHANC_AUTOROUTE_ANON_TO_FTPS", "false").lower() == "true"


def OnChange(changeType, level, resource) -> None: # noqa: ARG001
def OnChange(changeType: str, _level: None, resource: str) -> Any:
milanmlft marked this conversation as resolved.
Show resolved Hide resolved
"""
Three ChangeTypes included in this function:
- If a study if stable and if ShouldAutoRoute returns true
milanmlft marked this conversation as resolved.
Show resolved Hide resolved
then SendViaStow is called
then SendViaFTPS is called
- If orthanc has started then message added to Orthanc LogWarning
and AzureDICOMTokenRefresh called
- If orthanc has stopped and TIMER is not none then message added
Expand All @@ -202,7 +206,7 @@ def OnChange(changeType, level, resource) -> None: # noqa: ARG001

if changeType == orthanc.ChangeType.STABLE_STUDY and ShouldAutoRoute():
print("Stable study: %s" % resource) # noqa: T201
SendViaStow(resource)
SendViaFTPS(resource)
stefpiatek marked this conversation as resolved.
Show resolved Hide resolved

if changeType == orthanc.ChangeType.ORTHANC_STARTED:
orthanc.LogWarning("Starting the scheduler")
Expand All @@ -213,13 +217,13 @@ def OnChange(changeType, level, resource) -> None: # noqa: ARG001
TIMER.cancel()


def OnHeartBeat(output, uri, **request): # noqa: ARG001
def OnHeartBeat(output, uri, **request) -> Any: # noqa: ARG001
"""Extends the REST API by registering a new route in the REST API"""
orthanc.LogWarning("OK")
output.AnswerBuffer("OK\n", "text/plain")


def ReceivedInstanceCallback(receivedDicom, origin):
def ReceivedInstanceCallback(receivedDicom: BytesIO, origin: str) -> Any:
"""Modifies a DICOM instance received by Orthanc and applies anonymisation."""
if origin == orthanc.InstanceOrigin.REST_API:
orthanc.LogWarning("DICOM instance received from the REST API")
Expand Down
7 changes: 5 additions & 2 deletions pixl_core/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
os.environ["FTP_PORT"] = "20021"

TEST_DIR = Path(__file__).parent
DUMMY_SERVICES_DIR = Path(__file__).parents[2] / "test" / "dummy-services"
stefpiatek marked this conversation as resolved.
Show resolved Hide resolved
STUDY_DATE = datetime.date.fromisoformat("2023-01-01")


Expand Down Expand Up @@ -71,9 +72,11 @@ def mounted_data() -> Path:
The mounted data directory for the ftp server.
This will contain the data after successful upload.
"""
yield TEST_DIR / "ftp-server" / "mounts" / "data"
yield DUMMY_SERVICES_DIR / "ftp-server" / "mounts" / "data"
sub_dirs = [
f.path for f in os.scandir(TEST_DIR / "ftp-server" / "mounts" / "data") if f.is_dir()
f.path
for f in os.scandir(DUMMY_SERVICES_DIR / "ftp-server" / "mounts" / "data")
milanmlft marked this conversation as resolved.
Show resolved Hide resolved
if f.is_dir()
milanmlft marked this conversation as resolved.
Show resolved Hide resolved
]
# Tear down the directory after tests
for sub_dir in sub_dirs:
Expand Down
8 changes: 4 additions & 4 deletions pixl_core/tests/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,16 @@ services:
retries: 5
ftp-server:
container_name: test-ftp-server
build:
context: ftp-server
build:
context: ../../test/dummy-services/ftp-server
ports:
- "20021:21"
- "21000-21010:21000-21010"
volumes:
# Mount for uploaded data
- "./ftp-server/mounts/data/:/home/pixl/"
- "../../test/dummy-services/ftp-server/mounts/data/:/home/pixl/"
# Mount SSL keys for TLS
- "./ftp-server/mounts/ssl/:/etc/ssl/private/"
- "../../test/dummy-services/ftp-server/mounts/ssl/:/etc/ssl/private/"
environment:
ADDRESS: "localhost"
USERS: pixl|longpassword|/home/pixl
Expand Down
3 changes: 2 additions & 1 deletion pixl_ehr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ and the processing tests with
```

To test the availability of a CogStack instance, we mock up a *FastAPI* server which simply takes in
some input text and returns the same text. The configuration of this mock instance is defined in [`test/dummy-services`](/test/dummy-services/).
some input text and returns the same text. The mocking is handled by a *pytest* fixture in
`test_processing.py()` (`_mock_requests`).

## Usage

Expand Down
2 changes: 1 addition & 1 deletion test/.env.test
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ ORTHANC_ANON_PASSWORD=orthanc_anon_password
ORTHANC_ANON_AE_TITLE=ORTHANCANON
ORTHANC_ANON_HTTP_TIMEOUT=60
ENABLE_DICOM_WEB=true
ORTHANC_AUTOROUTE_ANON_TO_AZURE=false
ORTHANC_AUTOROUTE_ANON_TO_FTPS=true
PIXL_DICOM_TRANSFER_TIMEOUT=240

# UCVNAQR DICOM node information
Expand Down
25 changes: 23 additions & 2 deletions test/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ services:
cogstack-api:
container_name: system-test-cogstack
build:
context: ./dummy-services/
context: ./dummy-services/cogstack/
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost:8000/heart-beat" ]
interval: 10s
Expand All @@ -76,4 +76,25 @@ services:
networks:
pixl-net:


ftp-server:
container_name: test-ftp-server
milanmlft marked this conversation as resolved.
Show resolved Hide resolved
build:
context: ./dummy-services/ftp-server
ports:
- "20021:21"
- "21000-21010:21000-21010"
volumes:
# Mount for uploaded data
- "./dummy-services/ftp-server/mounts/data/:/home/pixl/"
# Mount SSL keys for TLS
- "./dummy-services/ftp-server/mounts/ssl/:/etc/ssl/private/"
environment:
ADDRESS: "localhost"
USERS: pixl|longpassword|/home/pixl
TLS_KEY: /etc/ssl/private/localhost.key
TLS_CERT: /etc/ssl/private/localhost.crt
healthcheck:
test: ping localhost:21 -w 2
interval: 10s
timeout: 5s
retries: 5
File renamed without changes.
2 changes: 1 addition & 1 deletion test/run-system-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ sleep 65 # need to wait until the DICOM image is "stable" = 60s
./scripts/check_entry_in_pixl_anon.sh
./scripts/check_entry_in_orthanc_anon.sh
./scripts/check_max_storage_in_orthanc_raw.sh
./scripts/check_ftps_upload.py

pixl extract-radiology-reports "${PACKAGE_DIR}/test/resources/omop"

Expand All @@ -45,4 +46,3 @@ docker exec system-test-ehr-api-1 rm -r /run/exports/test-extract-uclh-omop-cdm/

cd "${PACKAGE_DIR}"
docker compose -f docker-compose.yml -f test/docker-compose.yml -p system-test down --volumes

33 changes: 33 additions & 0 deletions test/scripts/check_ftps_upload.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env python
# Copyright (c) University College London Hospitals NHS Foundation Trust
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
from pathlib import Path
from pixl_cli._io import config_from_log_file

logger = logging.getLogger(__name__)

PARQUET_PATH = Path(__file__).parents[1] / "resources" / "omop"
MOUNTED_DATA_DIR = Path(__file__).parents[1] / "dummy-services" / "ftp-server" / "mounts" / "data"

project_name, omop_es_timestamp = config_from_log_file(PARQUET_PATH)
expected_output_dir = MOUNTED_DATA_DIR / project_name

# Test whether DICOM images have been uploaded
glob_list = [expected_output_dir.glob("*.zip")]
milanmlft marked this conversation as resolved.
Show resolved Hide resolved
logger.info(f"glob_list: {glob_list}")

assert len(glob_list) == 1

# TODO: check parquet files upload
milanmlft marked this conversation as resolved.
Show resolved Hide resolved
Loading