diff --git a/grpc4bmi/bmi_client_docker.py b/grpc4bmi/bmi_client_docker.py index b774be1..47c58e2 100644 --- a/grpc4bmi/bmi_client_docker.py +++ b/grpc4bmi/bmi_client_docker.py @@ -23,6 +23,17 @@ class BmiClientDocker(BmiClient): output_dir (str): Directory for input files of model user (str): Username or UID of Docker container remove (bool): Automatically remove the container when it exits + extra_volumes (Dict[str,Dict]): Extra volumes to attach to Docker container. + The key is either the hosts path or a volume name and the value is a dictionary with the keys: + + - ``bind`` The path to mount the volume inside the container + - ``mode`` Either ``rw`` to mount the volume read/write, or ``ro`` to mount it read-only. + + For example: + + .. code-block:: python + + {'/data/shared/forcings/': {'bind': '/forcings', 'mode': 'ro'}} """ @@ -31,10 +42,13 @@ class BmiClientDocker(BmiClient): def __init__(self, image, image_port=50051, host=None, input_dir=None, output_dir=None, - user=os.getuid(), remove=True): + user=os.getuid(), remove=True, + extra_volumes=None): port = BmiClient.get_unique_port() client = docker.from_env() volumes = {} + if extra_volumes is not None: + volumes.update(extra_volumes) self.input_dir = None if input_dir is not None: self.input_dir = os.path.abspath(input_dir) diff --git a/test/conftest.py b/test/conftest.py index 09a0b11..e966f30 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1,7 +1,8 @@ import pytest -def write_config(p): - p.write_text("""data: /data/input/PEQ_Hupsel.dat + +def write_config(p, data_fn='/data/input/PEQ_Hupsel.dat'): + p.write_text(f"""data: {data_fn} parameters: cW: 200 cV: 4 @@ -59,3 +60,17 @@ def walrus_input(tmp_path): write_config(cfg) write_datafile(tmp_path / 'PEQ_Hupsel.dat') return cfg + + +@pytest.fixture() +def walrus_input_on_extra_volume(tmp_path): + # Have config in input dir and forcings data file on extra volume + input_dir = tmp_path / 'input' + input_dir.mkdir() + cfg = input_dir / 'config.yml' + write_config(cfg, '/forcings/PEQ_Hupsel.dat') + extra_dir = tmp_path / 'forcings' + extra_dir.mkdir() + write_datafile(extra_dir / 'PEQ_Hupsel.dat') + extra_volumes = {extra_dir: {'bind': '/forcings', 'mode': 'ro'}} + return input_dir, extra_volumes diff --git a/test/test_docker.py b/test/test_docker.py index d776516..b3ed700 100644 --- a/test/test_docker.py +++ b/test/test_docker.py @@ -10,6 +10,17 @@ def walrus_model(tmp_path, walrus_input): del model +@pytest.fixture() +def walrus_model_with_extra_volume(tmp_path, walrus_input_on_extra_volume): + (input_dir, extra_volumes) = walrus_input_on_extra_volume + model = BmiClientDocker(image="ewatercycle/walrus-grpc4bmi:v0.2.0", + image_port=55555, + input_dir=str(input_dir), + extra_volumes=extra_volumes) + yield model + del model + + class TestBmiClientDocker: def test_component_name(self, walrus_model): assert walrus_model.get_component_name() == 'WALRUS' @@ -26,3 +37,9 @@ def test_initialize(self, walrus_input, walrus_model): def test_get_value_ref(self, walrus_model): with pytest.raises(NotImplementedError): walrus_model.get_value_ref('Q') + + def test_extra_volume(self, walrus_model_with_extra_volume): + walrus_model_with_extra_volume.initialize('/data/input/config.yml') + walrus_model_with_extra_volume.update() + # After initialization and update the forcings have been read from the extra volume + assert len(walrus_model_with_extra_volume.get_value('Q')) == 1