11"""The Job Tester 'compose' module.
22
3- This module is responsible for injecting a docker-compose file into the
3+ This module is responsible for injecting a ' docker-compose.yml' file into the
44repository of the Data Manager Job repository under test. It also
5- executes docker-compose and can remove the test directory.
5+ created project and instance directories, and executes 'docker-compose up'
6+ to run the Job, and can remove the test directory.
7+
8+ This module is designed to simulate the actions of the Data Manager
9+ and Job Operator that are running in the DM kubernetes deployment.
610"""
711import copy
812import os
913import shutil
1014import subprocess
1115from typing import Any , Dict , Optional , Tuple
1216
17+ # The 'simulated' instance directory,
18+ # created by the Data Manager prior to launching the corresponding Job.
19+ # Jobs know this directory because their container has this set via
20+ # the environment variable 'DM_INSTANCE_DIRECTORY'.
1321INSTANCE_DIRECTORY : str = ".instance-88888888-8888-8888-8888-888888888888"
1422
15- # A default test execution timeout
23+ # A default test execution timeout (minutes)
1624DEFAULT_TEST_TIMEOUT_M : int = 10
1725
18- # The user id containers will be started as
19- _USER_ID : int = 8888
20-
26+ # The docker-compose file template.
27+ # A multi-line string with variable mapping,
28+ # expanded and written to the test directory in 'create()'.
2129_COMPOSE_CONTENT : str = """---
30+ # We use compose v2
31+ # because we're relying on 'mem_limit' and 'cpus',
32+ # which are ignored (moved to swarm) in v3.
2233version: '2.4'
2334services:
2435 job:
2536 image: {image}
2637 container_name: {job}-{test}-jote
27- user: '{uid}'
28- entrypoint: {working_directory}/{instance_directory}/ command
38+ user: '{uid}:{gid} '
39+ entrypoint: {command}
2940 command: []
3041 working_dir: {working_directory}
3142 volumes:
3546 cpus: {cpus}.0
3647 environment:
3748 - DM_INSTANCE_DIRECTORY={instance_directory}
38- {additional_environment}
39- """
49+ {additional_environment}"""
4050
4151_NF_CONFIG_CONTENT : str = """
4252docker.enabled = true
@@ -84,6 +94,7 @@ def __init__(
8494 command : str ,
8595 test_environment : Dict [str , str ],
8696 user_id : Optional [int ] = None ,
97+ group_id : Optional [int ] = None ,
8798 ):
8899
89100 # Memory must have a Mi or Gi suffix.
@@ -105,6 +116,7 @@ def __init__(
105116 self ._command : str = command
106117 self ._test_environment = copy .deepcopy (test_environment )
107118 self ._user_id : Optional [int ] = user_id
119+ self ._group_id : Optional [int ] = group_id
108120
109121 def get_test_path (self ) -> str :
110122 """Returns the path to the root directory for a given test."""
@@ -122,7 +134,7 @@ def create(self) -> str:
122134 full path to the test (project) directory.
123135 """
124136
125- print ("# Creating test environment..." )
137+ print ("# Compose: Creating test environment..." )
126138
127139 # First, delete
128140 test_path : str = self .get_test_path ()
@@ -132,7 +144,7 @@ def create(self) -> str:
132144 # Do we have the docker-compose version the user's installed?
133145 if not Compose ._COMPOSE_VERSION :
134146 Compose ._COMPOSE_VERSION = _get_docker_compose_version ()
135- print (f"# docker-compose ({ Compose ._COMPOSE_VERSION } )" )
147+ print (f"# Compose: docker-compose ({ Compose ._COMPOSE_VERSION } )" )
136148
137149 # Make the test directory
138150 # (where the test is launched from)
@@ -143,25 +155,31 @@ def create(self) -> str:
143155 if not os .path .exists (inst_path ):
144156 os .makedirs (inst_path )
145157
146- # Run as a specific user ID?
158+ # Run as a specific user/group ID?
147159 if self ._user_id is not None :
148160 user_id = self ._user_id
149161 else :
150162 user_id = os .getuid ()
163+ if self ._group_id is not None :
164+ group_id = self ._group_id
165+ else :
166+ group_id = os .getgid ()
151167
152168 # Write the Docker compose content to a file in the test directory
153169 additional_environment : str = ""
154170 if self ._test_environment :
155171 for e_name , e_value in self ._test_environment .items ():
156172 additional_environment += f" - { e_name } ='{ e_value } '\n "
157173 variables : Dict [str , Any ] = {
174+ "command" : self ._command ,
158175 "test_path" : project_path ,
159176 "job" : self ._job ,
160177 "test" : self ._test ,
161178 "image" : self ._image ,
162179 "memory_limit" : self ._memory ,
163180 "cpus" : self ._cores ,
164181 "uid" : user_id ,
182+ "gid" : group_id ,
165183 "project_directory" : self ._project_directory ,
166184 "working_directory" : self ._working_directory ,
167185 "instance_directory" : INSTANCE_DIRECTORY ,
@@ -172,14 +190,6 @@ def create(self) -> str:
172190 with open (compose_path , "wt" , encoding = "UTF-8" ) as compose_file :
173191 compose_file .write (compose_content )
174192
175- # Now write the command to the instance directory, as `command`.
176- command_path : str = f"{ inst_path } /command"
177- with open (command_path , "wt" , encoding = "UTF-8" ) as command_file :
178- command_file .write ("#!/bin/sh\n umask 0002\n " )
179- command_file .write (self ._command + "\n " )
180- # And set read/write/execute permission...
181- os .chmod (command_path , 0o775 )
182-
183193 # nextflow config?
184194 if self ._image_type == "nextflow" :
185195 # Write a nextflow config to the project path
@@ -189,7 +199,7 @@ def create(self) -> str:
189199 with open (nf_cfg_path , "wt" , encoding = "UTF-8" ) as nf_cfg_file :
190200 nf_cfg_file .write (_NF_CONFIG_CONTENT )
191201
192- print ("# Created" )
202+ print ("# Compose: Created" )
193203
194204 return project_path
195205
@@ -204,8 +214,8 @@ def run(
204214
205215 execution_directory : str = self .get_test_path ()
206216
207- print ('# Executing the test ("docker-compose up")...' )
208- print (f'# Execution directory is "{ execution_directory } "' )
217+ print ('# Compose: Executing the test ("docker-compose up")...' )
218+ print (f'# Compose: Execution directory is "{ execution_directory } "' )
209219
210220 cwd = os .getcwd ()
211221 os .chdir (execution_directory )
@@ -234,16 +244,16 @@ def run(
234244 finally :
235245 os .chdir (cwd )
236246
237- print (f"# Executed (exit code { test .returncode } )" )
247+ print (f"# Compose: Executed (exit code { test .returncode } )" )
238248
239249 return test .returncode , test .stdout .decode ("utf-8" ), test .stderr .decode ("utf-8" )
240250
241251 def delete (self ) -> None :
242252 """Deletes a test directory created by 'create()'."""
243- print ("# Deleting the test..." )
253+ print ("# Compose: Deleting the test..." )
244254
245255 test_path : str = self .get_test_path ()
246256 if os .path .exists (test_path ):
247257 shutil .rmtree (test_path )
248258
249- print ("# Deleted" )
259+ print ("# Compose: Deleted" )
0 commit comments