idmtools-platform-container 0.0.0.dev0__py3-none-any.whl → 0.0.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. docker_image/BASE_VERSION +1 -0
  2. docker_image/Dockerfile +48 -0
  3. docker_image/Dockerfile_buildenv +46 -0
  4. docker_image/ImageName +1 -0
  5. docker_image/README.md +78 -0
  6. docker_image/__init__.py +6 -0
  7. docker_image/build_docker_image.py +145 -0
  8. docker_image/debian/BASE_VERSION +1 -0
  9. docker_image/debian/Dockerfile +40 -0
  10. docker_image/debian/ImageName +1 -0
  11. docker_image/debian/README.md +48 -0
  12. docker_image/debian/pip.conf +3 -0
  13. docker_image/debian/requirements.txt +1 -0
  14. docker_image/docker_image_history.py +101 -0
  15. docker_image/pip.conf +3 -0
  16. docker_image/push_docker_image.py +62 -0
  17. docker_image/requirements.txt +1 -0
  18. docker_image/rocky_meta_runtime.txt +37 -0
  19. idmtools_platform_container/__init__.py +18 -8
  20. idmtools_platform_container/cli/__init__.py +5 -0
  21. idmtools_platform_container/cli/container.py +682 -0
  22. idmtools_platform_container/container_operations/__init__.py +5 -0
  23. idmtools_platform_container/container_operations/docker_operations.py +593 -0
  24. idmtools_platform_container/container_platform.py +375 -0
  25. idmtools_platform_container/platform_operations/__init__.py +5 -0
  26. idmtools_platform_container/platform_operations/experiment_operations.py +112 -0
  27. idmtools_platform_container/platform_operations/simulation_operations.py +58 -0
  28. idmtools_platform_container/plugin_info.py +79 -0
  29. idmtools_platform_container/utils/__init__.py +5 -0
  30. idmtools_platform_container/utils/general.py +136 -0
  31. idmtools_platform_container/utils/status.py +130 -0
  32. idmtools_platform_container-0.0.3.dist-info/METADATA +212 -0
  33. idmtools_platform_container-0.0.3.dist-info/RECORD +69 -0
  34. idmtools_platform_container-0.0.3.dist-info/entry_points.txt +5 -0
  35. idmtools_platform_container-0.0.3.dist-info/licenses/LICENSE.TXT +3 -0
  36. {idmtools_platform_container-0.0.0.dev0.dist-info → idmtools_platform_container-0.0.3.dist-info}/top_level.txt +2 -0
  37. tests/inputs/Assets/MyLib/functions.py +2 -0
  38. tests/inputs/__init__.py +0 -0
  39. tests/inputs/model.py +28 -0
  40. tests/inputs/model1.py +31 -0
  41. tests/inputs/model3.py +21 -0
  42. tests/inputs/model_file.py +18 -0
  43. tests/inputs/run.sh +1 -0
  44. tests/inputs/sleep.py +9 -0
  45. tests/test_container_cli/__init__.py +0 -0
  46. tests/test_container_cli/helper.py +57 -0
  47. tests/test_container_cli/test_base.py +14 -0
  48. tests/test_container_cli/test_cancel.py +96 -0
  49. tests/test_container_cli/test_clear_results.py +54 -0
  50. tests/test_container_cli/test_container.py +72 -0
  51. tests/test_container_cli/test_file_container_cli.py +121 -0
  52. tests/test_container_cli/test_get_detail.py +60 -0
  53. tests/test_container_cli/test_history.py +136 -0
  54. tests/test_container_cli/test_history_count.py +53 -0
  55. tests/test_container_cli/test_inspect.py +53 -0
  56. tests/test_container_cli/test_install.py +48 -0
  57. tests/test_container_cli/test_is_running.py +69 -0
  58. tests/test_container_cli/test_jobs.py +138 -0
  59. tests/test_container_cli/test_list_containers.py +99 -0
  60. tests/test_container_cli/test_packages.py +41 -0
  61. tests/test_container_cli/test_path.py +96 -0
  62. tests/test_container_cli/test_ps.py +47 -0
  63. tests/test_container_cli/test_remove_container.py +78 -0
  64. tests/test_container_cli/test_status.py +149 -0
  65. tests/test_container_cli/test_stop_container.py +71 -0
  66. tests/test_container_cli/test_sync_history.py +98 -0
  67. tests/test_container_cli/test_verify_docker.py +28 -0
  68. tests/test_container_cli/test_volume.py +28 -0
  69. idmtools_platform_container-0.0.0.dev0.dist-info/METADATA +0 -41
  70. idmtools_platform_container-0.0.0.dev0.dist-info/RECORD +0 -5
  71. {idmtools_platform_container-0.0.0.dev0.dist-info → idmtools_platform_container-0.0.3.dist-info}/WHEEL +0 -0
@@ -0,0 +1,375 @@
1
+ """
2
+ Here we implement the ContainerPlatform object.
3
+
4
+ Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
5
+ """
6
+ import os
7
+ import docker
8
+ import platform
9
+ import subprocess
10
+ from uuid import uuid4
11
+ from docker.models.containers import Container
12
+ from typing import Union, NoReturn, List, Dict
13
+ from dataclasses import dataclass, field
14
+ from idmtools.core.interfaces.ientity import IEntity
15
+ from idmtools.entities import Suite
16
+ from idmtools.entities.experiment import Experiment
17
+ from idmtools.entities.simulation import Simulation
18
+ from idmtools_platform_container.container_operations.docker_operations import validate_container_running, \
19
+ find_container_by_image, compare_mounts, find_running_job, get_container, CONTAINER_STATUS, restart_container, \
20
+ is_docker_installed, is_docker_daemon_running
21
+ from idmtools_platform_container.platform_operations.simulation_operations import ContainerPlatformSimulationOperations
22
+ from idmtools_platform_container.utils.general import map_container_path
23
+ from idmtools_platform_file.tools.job_history import JobHistory
24
+ from idmtools_platform_file.file_platform import FilePlatform
25
+ from idmtools_platform_container.platform_operations.experiment_operations import ContainerPlatformExperimentOperations
26
+ from logging import getLogger, DEBUG
27
+
28
+
29
+ logger = getLogger(__name__)
30
+ user_logger = getLogger('user')
31
+
32
+
33
+ @dataclass(repr=False)
34
+ class ContainerPlatform(FilePlatform):
35
+ """
36
+ Container Platform definition.
37
+ """
38
+ __CONTAINER_IMAGE = 'docker-production-public.packages.idmod.org/idmtools/container-rocky-runtime:0.0.4'
39
+ __CONTAINER_MOUNT = "/home/container_data"
40
+ docker_image: str = field(default=None, metadata=dict(help="Docker image to run the container"))
41
+ data_mount: str = field(default=None, metadata=dict(help="Data mount point in the container"))
42
+ user_mounts: dict = field(default=None, metadata=dict(help="User-defined mounts"))
43
+ container_prefix: str = field(default=None, metadata=dict(help="Container name prefix"))
44
+ force_start: bool = field(default=False, metadata=dict(help="Force start a new container"))
45
+ new_container: bool = field(default=False, metadata=dict(help="Start a new container"))
46
+ include_stopped: bool = field(default=False, metadata=dict(help="Include stopped containers"))
47
+ debug: bool = field(default=False, metadata=dict(help="Debug mode"))
48
+ container_id: str = field(default=None, metadata=dict(help="Container Id"))
49
+
50
+ def __post_init__(self):
51
+ super().__post_init__()
52
+ self._experiments = ContainerPlatformExperimentOperations(platform=self)
53
+ self._simulations = ContainerPlatformSimulationOperations(platform=self)
54
+ self.job_directory = os.path.abspath(self.job_directory)
55
+ self.run_sequence = False
56
+ if self.docker_image is None:
57
+ self.docker_image = self.__CONTAINER_IMAGE
58
+ if self.data_mount is None:
59
+ self.data_mount = self.__CONTAINER_MOUNT
60
+
61
+ if self.debug:
62
+ root_logger = getLogger()
63
+ root_logger.setLevel(DEBUG)
64
+
65
+ # Check if Docker is installed and running
66
+ if not is_docker_installed():
67
+ user_logger.error("Docker is not installed.")
68
+ exit(-1)
69
+ if not is_docker_daemon_running():
70
+ user_logger.error("Docker daemon is not running.")
71
+ exit(-1)
72
+
73
+ def validate_container(self, container_id: str) -> str:
74
+ """
75
+ Validate the container.
76
+ Args:
77
+ container_id: container id
78
+ Returns:
79
+ Container short id
80
+ """
81
+ # Check if the container exists
82
+ container = get_container(container_id)
83
+ if not container:
84
+ user_logger.warning(f"Container {container_id} is not found.")
85
+ exit(-1)
86
+
87
+ # Check if the container is in the right status
88
+ if container.status not in CONTAINER_STATUS:
89
+ user_logger.warning(
90
+ f"Container {container_id} is in {container.status} status, but we only support status: {CONTAINER_STATUS}.")
91
+ exit(-1)
92
+
93
+ # Check if the container is running if we do not include stopped containers
94
+ if not self.include_stopped and container.status != 'running':
95
+ user_logger.warning(f"Container {container_id} is not running.")
96
+ exit(-1)
97
+
98
+ # Check if the container matches the platform mounts
99
+ if not self.validate_mount(container):
100
+ user_logger.warning(f"Container {container_id} does not match the platform mounts.")
101
+ exit(-1)
102
+
103
+ # Restart the container if it is not running
104
+ if container.status != 'running':
105
+ restart_container(container)
106
+
107
+ return container.short_id
108
+
109
+ def run_items(self, items: Union[IEntity, List[IEntity]], **kwargs):
110
+ """
111
+ Run items on the platform.
112
+ Args:
113
+ items: Runnable items
114
+ kwargs: additional arguments
115
+ Returns:
116
+ None
117
+ """
118
+ if self.container_id is not None:
119
+ self.container_id = self.validate_container(self.container_id)
120
+ super().run_items(items, **kwargs)
121
+
122
+ def submit_job(self, item: Union[Experiment, Simulation], dry_run: bool = False, **kwargs) -> NoReturn:
123
+ """
124
+ Submit a Process job in a docker container.
125
+ Args:
126
+ item: Experiment or Simulation
127
+ dry_run: True/False
128
+ kwargs: keyword arguments used to expand functionality
129
+ Returns:
130
+ Any
131
+ """
132
+ if dry_run:
133
+ user_logger.info(f'\nDry run: {dry_run}')
134
+ return
135
+
136
+ if isinstance(item, Experiment):
137
+ if logger.isEnabledFor(DEBUG):
138
+ logger.debug("Run experiment on container!")
139
+
140
+ # Check if the experiment is already running
141
+ his_job = JobHistory.get_job(item.id)
142
+ if his_job:
143
+ job = find_running_job(item.id, his_job['CONTAINER'])
144
+ if job:
145
+ user_logger.warning(f"Experiment {item.id} is already running on Container {job.container_id}.")
146
+ exit(-1)
147
+
148
+ # Start the container
149
+ if self.container_id is None:
150
+ if logger.isEnabledFor(DEBUG):
151
+ logger.debug("Check provided container!")
152
+ self.container_id = self.check_container(**kwargs)
153
+
154
+ # If the platform is Windows, convert the scripts to Linux format
155
+ if platform.system() in ["Windows"]:
156
+ if logger.isEnabledFor(DEBUG):
157
+ logger.debug("Script runs on Windows!")
158
+ self.convert_scripts_to_linux(item, **kwargs)
159
+
160
+ # Submit the experiment/simulations
161
+ if logger.isEnabledFor(DEBUG):
162
+ logger.debug(f"Submit experiment/simulations to container: {self.container_id}")
163
+ self.submit_experiment(item, **kwargs)
164
+
165
+ # Save the job to history
166
+ JobHistory.save_job(self.job_directory, self.container_id, item, self)
167
+ elif isinstance(item, Simulation):
168
+ raise NotImplementedError("submit_job directly for simulation is not implemented on ContainerPlatform.")
169
+ else:
170
+ raise NotImplementedError(
171
+ f"Submit job is not implemented for {item.__class__.__name__} on ContainerPlatform.")
172
+
173
+ def check_container(self, **kwargs) -> str:
174
+ """
175
+ Check the container status.
176
+ Args:
177
+ kwargs: keyword arguments used to expand functionality
178
+ Returns:
179
+ container id
180
+ """
181
+ container_id = validate_container_running(self, **kwargs)
182
+ return container_id
183
+
184
+ def start_container(self, **kwargs) -> str:
185
+ """
186
+ Execute a command in a container.
187
+ Args:
188
+ kwargs: keyword arguments used to expand functionality
189
+ Returns:
190
+ container id
191
+ """
192
+ # Create a Docker client
193
+ client = docker.from_env()
194
+ env_vars = {"HOME": self.__CONTAINER_MOUNT, "PIP_USER": "yes"}
195
+ volumes = self.build_binding_volumes()
196
+ docker_user = None
197
+ if hasattr(os, 'getuid'):
198
+ uid = os.getuid()
199
+ gid = os.getgid()
200
+ docker_user = f"{uid}:{gid}"
201
+ # Run the container
202
+ container = client.containers.run(
203
+ self.docker_image,
204
+ command="bash",
205
+ volumes=volumes,
206
+ environment=env_vars,
207
+ stdin_open=True,
208
+ tty=True,
209
+ detach=True,
210
+ name=f"{self.container_prefix}_{str(uuid4())}" if self.container_prefix else None,
211
+ user=docker_user
212
+ )
213
+
214
+ return container.short_id
215
+
216
+ def convert_scripts_to_linux(self, experiment: Experiment, **kwargs) -> NoReturn:
217
+ """
218
+ Convert the scripts to Linux format.
219
+ Args:
220
+ experiment: Experiment
221
+ kwargs: keyword arguments used to expand functionality
222
+ Returns:
223
+ No return
224
+ """
225
+ directory = self.get_container_directory(experiment)
226
+
227
+ try:
228
+ commands = [
229
+ f"cd {directory}",
230
+ r"sed -i 's/\r//g' batch.sh;sed -i 's/\r//g' run_simulation.sh"
231
+ ]
232
+
233
+ # Constructing the overall command
234
+ full_command = ["docker", "exec", self.container_id, "bash", "-c", ";".join(commands)]
235
+ # Execute the command
236
+ subprocess.run(full_command, stdout=subprocess.PIPE)
237
+ except subprocess.CalledProcessError as e:
238
+ user_logger.warning(f"Failed to convert script: {e}")
239
+ except Exception as ex:
240
+ user_logger.warning(f"Failed to convert script to Linux: {ex}")
241
+
242
+ def submit_experiment(self, experiment: Experiment, **kwargs) -> NoReturn:
243
+ """
244
+ Submit an experiment to the container.
245
+ Args:
246
+ experiment: Experiment
247
+ kwargs: keyword arguments used to expand functionality
248
+ Returns:
249
+ No return
250
+ """
251
+ directory = self.get_container_directory(experiment)
252
+ if logger.isEnabledFor(DEBUG):
253
+ logger.debug(f"Directory: {directory}")
254
+ logger.debug(f"container_id: {self.container_id}")
255
+
256
+ try:
257
+ # Commands to change directory and run the script
258
+ command = f'exec -a "EXPERIMENT:{experiment.id}" bash batch.sh &'
259
+ # Constructing the overall command
260
+ full_command = ["docker", "exec", "--workdir", directory, self.container_id, "bash", "-c", command]
261
+
262
+ # Execute the command using Popen for handling background processes
263
+ subprocess.Popen(full_command)
264
+
265
+ # Optionally, you can wait for a short period to ensure the command starts
266
+ # process = subprocess.Popen(full_command)
267
+ # process.wait(timeout=5)
268
+
269
+ logger.debug(f"Submit experiment {experiment.id} successfully")
270
+ except subprocess.TimeoutExpired:
271
+ user_logger.error(f"Submit experiment {experiment.id} timed out")
272
+ exit(-1)
273
+ except Exception as ex:
274
+ user_logger.error(f"Submit experiment {experiment.id} encounter Error: {ex}")
275
+ exit(-1)
276
+
277
+ def build_binding_volumes(self) -> Dict:
278
+ """
279
+ Build the binding volumes for the container.
280
+ Returns:
281
+ bindings in dict format
282
+ """
283
+ volumes = {
284
+ self.job_directory: {"bind": self.data_mount, "mode": "rw"}
285
+ }
286
+
287
+ # Add user-defined volume mappings
288
+ if self.user_mounts is not None:
289
+ for key, value in self.user_mounts.items():
290
+ volumes[key] = {"bind": value, "mode": "rw"}
291
+ return volumes
292
+
293
+ def get_mounts(self) -> List:
294
+ """
295
+ Build the mounts of the container.
296
+ Returns:
297
+ List of mounts (Dict)
298
+ """
299
+ mounts = []
300
+ mount = {'Type': 'bind',
301
+ 'Source': self.job_directory,
302
+ 'Destination': self.data_mount,
303
+ 'Mode': 'rw'}
304
+ mounts.append(mount)
305
+
306
+ # Add user-defined volume mappings
307
+ if self.user_mounts is not None:
308
+ for key, value in self.user_mounts.items():
309
+ mount = {'Type': 'bind',
310
+ 'Source': key,
311
+ 'Destination': value,
312
+ 'Mode': 'rw'}
313
+ mounts.append(mount)
314
+
315
+ return mounts
316
+
317
+ def validate_mount(self, container: Union[str, Container]) -> bool:
318
+ """
319
+ Compare the mounts of the container with the platform.
320
+ Args:
321
+ container: a container object or id.
322
+ Returns:
323
+ True/False
324
+ """
325
+ if isinstance(container, str):
326
+ ct = get_container(container)
327
+ else:
328
+ ct = container
329
+
330
+ if ct is None:
331
+ logger.warning(f"Container {container} is not found.")
332
+ return False
333
+ mounts1 = self.get_mounts()
334
+ mounts2 = ct.attrs['Mounts']
335
+ return compare_mounts(mounts1, mounts2)
336
+
337
+ def get_container_directory(self, item: Union[Suite, Experiment, Simulation]) -> str:
338
+ """
339
+ Get the container corresponding directory of an item.
340
+ Args:
341
+ item: Suite, Experiment or Simulation
342
+ Returns:
343
+ string Path
344
+ """
345
+ item_dir = self.get_directory(item)
346
+ item_container_dir = map_container_path(self.job_directory, self.data_mount, str(item_dir))
347
+
348
+ return item_container_dir
349
+
350
+ def retrieve_match_containers(self, image: str = None) -> List:
351
+ """
352
+ Find the containers that match math the image.
353
+ Args:
354
+ image: docker image
355
+ Returns:
356
+ list of containers
357
+ """
358
+ if image is None:
359
+ image = self.docker_image
360
+ container_found = find_container_by_image(image, self.include_stopped)
361
+ container_match = []
362
+ if len(container_found) > 0:
363
+ for status, containers in container_found.items():
364
+ for container in containers:
365
+ if self.validate_mount(container):
366
+ container_match.append((status, container))
367
+
368
+ if len(container_match) == 0:
369
+ if logger.isEnabledFor(DEBUG):
370
+ logger.debug(f"Found container with image {image}, but no one match platform mounts.")
371
+ else:
372
+ if logger.isEnabledFor(DEBUG):
373
+ logger.debug(f"Not found container matching image {image}.")
374
+
375
+ return container_match
@@ -0,0 +1,5 @@
1
+ """
2
+ idmtools ContainerPlatform platform operations module.
3
+
4
+ Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
5
+ """
@@ -0,0 +1,112 @@
1
+ """
2
+ Here we implement the ContainerPlatform experiment operations.
3
+
4
+ Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
5
+ """
6
+ import shutil
7
+ import subprocess
8
+ from dataclasses import dataclass
9
+ from typing import NoReturn, Dict, TYPE_CHECKING
10
+ from idmtools.core import ItemType
11
+ from idmtools.entities.experiment import Experiment
12
+ from idmtools_platform_file.platform_operations.experiment_operations import FilePlatformExperimentOperations
13
+ from idmtools_platform_container.container_operations.docker_operations import find_running_job
14
+ from logging import getLogger
15
+
16
+ logger = getLogger(__name__)
17
+ user_logger = getLogger('user')
18
+
19
+ if TYPE_CHECKING:
20
+ from idmtools_platform_container.container_platform import ContainerPlatform
21
+
22
+
23
+ @dataclass
24
+ class ContainerPlatformExperimentOperations(FilePlatformExperimentOperations):
25
+ """
26
+ Experiment Operations for Process Platform.
27
+ """
28
+ platform: 'ContainerPlatform'
29
+
30
+ def platform_run_item(self, experiment: Experiment, **kwargs):
31
+ """
32
+ Run experiment.
33
+ Args:
34
+ experiment: idmtools Experiment
35
+ kwargs: keyword arguments used to expand functionality
36
+ Returns:
37
+ None
38
+ """
39
+ super().platform_run_item(experiment, **kwargs)
40
+ # Commission
41
+ self.platform.submit_job(experiment, **kwargs)
42
+
43
+ def post_run_item(self, experiment: Experiment, **kwargs):
44
+ """
45
+ Trigger right after commissioning experiment on platform.
46
+ Args:
47
+ experiment: Experiment just commissioned
48
+ kwargs: keyword arguments used to expand functionality
49
+ Returns:
50
+ None
51
+ """
52
+ super().post_run_item(experiment, **kwargs)
53
+ dry_run = kwargs.get('dry_run', False)
54
+ if not dry_run:
55
+ user_logger.info(f"\nContainer ID: {self.platform.container_id}")
56
+ user_logger.info(
57
+ f'\nYou may try the following command to check simulations running status: \n idmtools container status {experiment.id}')
58
+
59
+ def platform_cancel(self, experiment_id: str, force: bool = True) -> NoReturn:
60
+ """
61
+ Cancel platform experiment's container job.
62
+ Args:
63
+ experiment_id: Experiment ID
64
+ force: bool, True/False
65
+ Returns:
66
+ No Return
67
+ """
68
+ job = find_running_job(experiment_id)
69
+ if job:
70
+ logger.debug(
71
+ f"{job.item_type.name} {experiment_id} is running on Container {job.container_id}.")
72
+ kill_cmd = f"docker exec {job.container_id} pkill -TERM -g {job.job_id}"
73
+ result = subprocess.run(kill_cmd, shell=True, stderr=subprocess.PIPE, text=True)
74
+ if result.returncode == 0:
75
+ logger.debug(f"Successfully killed {job.item_type.name} {experiment_id}")
76
+ else:
77
+ logger.debug(f"Error killing {job.item_type.name} {experiment_id}: {result.stderr}")
78
+ else:
79
+ logger.debug(f"Experiment {experiment_id} is not running, no cancel needed...")
80
+
81
+ def platform_delete(self, experiment_id: str) -> NoReturn:
82
+ """
83
+ Delete platform experiment.
84
+ Args:
85
+ experiment_id: Experiment ID
86
+ Returns:
87
+ No Return
88
+ """
89
+ from idmtools_platform_file.tools.job_history import JobHistory
90
+ job = JobHistory.get_job(experiment_id)
91
+ exp_dir = job['EXPERIMENT_DIR']
92
+ try:
93
+ logger.debug(f"Deleting experiment {experiment_id}")
94
+ shutil.rmtree(exp_dir)
95
+ # Delete the job history
96
+ logger.debug(f"Deleting job history {experiment_id}")
97
+ JobHistory.delete(experiment_id)
98
+ except RuntimeError:
99
+ logger.debug(f"Could not delete the associated experiment {experiment_id}")
100
+
101
+ def create_sim_directory_map(self, experiment_id: str) -> Dict:
102
+ """
103
+ Build simulation working directory mapping.
104
+ Args:
105
+ experiment_id: experiment id
106
+
107
+ Returns:
108
+ Dict of simulation id as key and working dir as value
109
+ """
110
+ exp = self.platform.get_item(experiment_id, ItemType.EXPERIMENT, raw=False)
111
+ sims = exp.simulations
112
+ return {sim.id: str(self.platform.get_container_directory(sim)) for sim in sims}
@@ -0,0 +1,58 @@
1
+ """
2
+ Here we implement the ContainerPlatform simulation operations.
3
+
4
+ Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
5
+ """
6
+ import subprocess
7
+ from dataclasses import dataclass
8
+ from typing import NoReturn, Dict
9
+ from idmtools.core import ItemType
10
+ from idmtools_platform_file.platform_operations.simulation_operations import FilePlatformSimulationOperations
11
+ from idmtools_platform_container.container_operations.docker_operations import find_running_job
12
+ from logging import getLogger
13
+
14
+ logger = getLogger(__name__)
15
+ user_logger = getLogger('user')
16
+
17
+
18
+ @dataclass
19
+ class ContainerPlatformSimulationOperations(FilePlatformSimulationOperations):
20
+ """
21
+ Simulation Operation for Container Platform.
22
+ """
23
+
24
+ def platform_cancel(self, sim_id: str, force: bool = False) -> NoReturn:
25
+ """
26
+ Cancel platform simulation's container job.
27
+ Args:
28
+ sim_id: simulation id
29
+ force: bool, True/False
30
+ Returns:
31
+ NoReturn
32
+ """
33
+ job = find_running_job(sim_id)
34
+ if job:
35
+ if job.item_type != ItemType.SIMULATION:
36
+ pass
37
+ user_logger.debug(
38
+ f"{job.item_type.name} {sim_id} is running on Container {job.container_id}.")
39
+ kill_cmd = f"docker exec {job.container_id} kill -9 {job.job_id}"
40
+ result = subprocess.run(kill_cmd, shell=True, stderr=subprocess.PIPE, text=True)
41
+ if result.returncode == 0:
42
+ print(f"Successfully killed {job.item_type.name} {sim_id}")
43
+ else:
44
+ print(f"Error killing {job.item_type.name} {sim_id}: {result.stderr}")
45
+ else:
46
+ user_logger.info(f"Simulation {sim_id} is not running, no cancel needed...")
47
+
48
+ def create_sim_directory_map(self, simulation_id: str) -> Dict:
49
+ """
50
+ Build simulation working directory mapping.
51
+ Args:
52
+ simulation_id: simulation id
53
+
54
+ Returns:
55
+ Dict of simulation id as key and working dir as value
56
+ """
57
+ sim = self.platform.get_item(simulation_id, ItemType.SIMULATION, raw=False)
58
+ return {sim.id: str(self.platform.get_container_directory(sim))}
@@ -0,0 +1,79 @@
1
+ """
2
+ idmtools process platform plugin definition.
3
+
4
+ Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
5
+ """
6
+ from pathlib import Path
7
+ from typing import Type, Dict
8
+ from idmtools.entities.iplatform import IPlatform
9
+ from idmtools.registry.platform_specification import example_configuration_impl, get_platform_impl, \
10
+ get_platform_type_impl, PlatformSpecification
11
+ from idmtools.registry.plugin_specification import get_description_impl
12
+ from idmtools_platform_container.container_platform import ContainerPlatform
13
+
14
+ CONTAINER_EXAMPLE_CONFIG = """
15
+ [Process]
16
+ job_directory = /data
17
+ """
18
+
19
+
20
+ class ContainerPlatformSpecification(PlatformSpecification):
21
+ """
22
+ Process Platform Specification definition.
23
+ """
24
+
25
+ @get_description_impl
26
+ def get_description(self) -> str:
27
+ """
28
+ Retrieve description.
29
+ """
30
+ return "Provides access to the Container Platform to IDM Tools"
31
+
32
+ @get_platform_impl
33
+ def get(self, **configuration) -> IPlatform:
34
+ """
35
+ Build our process platform from the passed in configuration object.
36
+
37
+ We do our import of platform here to avoid any weirdness
38
+ Args:
39
+ configuration:
40
+ Returns:
41
+ IPlatform
42
+ """
43
+ return ContainerPlatform(**configuration)
44
+
45
+ @example_configuration_impl
46
+ def example_configuration(self):
47
+ """
48
+ Retrieve example configuration.
49
+ """
50
+ return CONTAINER_EXAMPLE_CONFIG
51
+
52
+ @get_platform_type_impl
53
+ def get_type(self) -> Type[ContainerPlatform]: # noqa: F821
54
+ """
55
+ Get type.
56
+ Returns:
57
+ Type
58
+ """
59
+ return ContainerPlatform
60
+
61
+ def get_version(self) -> str:
62
+ """
63
+ Returns the version of the plugin.
64
+ Returns:
65
+ Plugin Version
66
+ """
67
+ from idmtools_platform_container import __version__
68
+ return __version__
69
+
70
+ def get_configuration_aliases(self) -> Dict[str, Dict]:
71
+ """
72
+ Provides configuration aliases that exist in CONTAINER.
73
+ """
74
+ config_aliases = dict(
75
+ CONTAINER=dict(
76
+ job_directory=str(Path.home())
77
+ )
78
+ )
79
+ return config_aliases
@@ -0,0 +1,5 @@
1
+ """
2
+ idmtools ContainerPlatform platform operations module.
3
+
4
+ Copyright 2021, Bill & Melinda Gates Foundation. All rights reserved.
5
+ """