pybiolib 0.2.951__py3-none-any.whl → 1.2.1890__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.
- biolib/__init__.py +357 -11
- biolib/_data_record/data_record.py +380 -0
- biolib/_index/__init__.py +0 -0
- biolib/_index/index.py +55 -0
- biolib/_index/query_result.py +103 -0
- biolib/_internal/__init__.py +0 -0
- biolib/_internal/add_copilot_prompts.py +58 -0
- biolib/_internal/add_gui_files.py +81 -0
- biolib/_internal/data_record/__init__.py +1 -0
- biolib/_internal/data_record/data_record.py +85 -0
- biolib/_internal/data_record/push_data.py +116 -0
- biolib/_internal/data_record/remote_storage_endpoint.py +43 -0
- biolib/_internal/errors.py +5 -0
- biolib/_internal/file_utils.py +125 -0
- biolib/_internal/fuse_mount/__init__.py +1 -0
- biolib/_internal/fuse_mount/experiment_fuse_mount.py +209 -0
- biolib/_internal/http_client.py +159 -0
- biolib/_internal/lfs/__init__.py +1 -0
- biolib/_internal/lfs/cache.py +51 -0
- biolib/_internal/libs/__init__.py +1 -0
- biolib/_internal/libs/fusepy/__init__.py +1257 -0
- biolib/_internal/push_application.py +488 -0
- biolib/_internal/runtime.py +22 -0
- biolib/_internal/string_utils.py +13 -0
- biolib/_internal/templates/__init__.py +1 -0
- biolib/_internal/templates/copilot_template/.github/instructions/general-app-knowledge.instructions.md +10 -0
- biolib/_internal/templates/copilot_template/.github/instructions/style-general.instructions.md +20 -0
- biolib/_internal/templates/copilot_template/.github/instructions/style-python.instructions.md +16 -0
- biolib/_internal/templates/copilot_template/.github/instructions/style-react-ts.instructions.md +47 -0
- biolib/_internal/templates/copilot_template/.github/prompts/biolib_app_inputs.prompt.md +11 -0
- biolib/_internal/templates/copilot_template/.github/prompts/biolib_onboard_repo.prompt.md +19 -0
- biolib/_internal/templates/copilot_template/.github/prompts/biolib_run_apps.prompt.md +12 -0
- biolib/_internal/templates/dashboard_template/.biolib/config.yml +5 -0
- biolib/_internal/templates/github_workflow_template/.github/workflows/biolib.yml +21 -0
- biolib/_internal/templates/gitignore_template/.gitignore +10 -0
- biolib/_internal/templates/gui_template/.yarnrc.yml +1 -0
- biolib/_internal/templates/gui_template/App.tsx +53 -0
- biolib/_internal/templates/gui_template/Dockerfile +27 -0
- biolib/_internal/templates/gui_template/biolib-sdk.ts +82 -0
- biolib/_internal/templates/gui_template/dev-data/output.json +7 -0
- biolib/_internal/templates/gui_template/index.css +5 -0
- biolib/_internal/templates/gui_template/index.html +13 -0
- biolib/_internal/templates/gui_template/index.tsx +10 -0
- biolib/_internal/templates/gui_template/package.json +27 -0
- biolib/_internal/templates/gui_template/tsconfig.json +24 -0
- biolib/_internal/templates/gui_template/vite-plugin-dev-data.ts +50 -0
- biolib/_internal/templates/gui_template/vite.config.mts +10 -0
- biolib/_internal/templates/init_template/.biolib/config.yml +19 -0
- biolib/_internal/templates/init_template/Dockerfile +14 -0
- biolib/_internal/templates/init_template/requirements.txt +1 -0
- biolib/_internal/templates/init_template/run.py +12 -0
- biolib/_internal/templates/init_template/run.sh +4 -0
- biolib/_internal/templates/templates.py +25 -0
- biolib/_internal/tree_utils.py +106 -0
- biolib/_internal/utils/__init__.py +65 -0
- biolib/_internal/utils/auth.py +46 -0
- biolib/_internal/utils/job_url.py +33 -0
- biolib/_internal/utils/multinode.py +263 -0
- biolib/_runtime/runtime.py +157 -0
- biolib/_session/session.py +44 -0
- biolib/_shared/__init__.py +0 -0
- biolib/_shared/types/__init__.py +74 -0
- biolib/_shared/types/account.py +12 -0
- biolib/_shared/types/account_member.py +8 -0
- biolib/_shared/types/app.py +9 -0
- biolib/_shared/types/data_record.py +40 -0
- biolib/_shared/types/experiment.py +32 -0
- biolib/_shared/types/file_node.py +17 -0
- biolib/_shared/types/push.py +6 -0
- biolib/_shared/types/resource.py +37 -0
- biolib/_shared/types/resource_deploy_key.py +11 -0
- biolib/_shared/types/resource_permission.py +14 -0
- biolib/_shared/types/resource_version.py +19 -0
- biolib/_shared/types/result.py +14 -0
- biolib/_shared/types/typing.py +10 -0
- biolib/_shared/types/user.py +19 -0
- biolib/_shared/utils/__init__.py +7 -0
- biolib/_shared/utils/resource_uri.py +75 -0
- biolib/api/__init__.py +6 -0
- biolib/api/client.py +168 -0
- biolib/app/app.py +252 -49
- biolib/app/search_apps.py +45 -0
- biolib/biolib_api_client/api_client.py +126 -31
- biolib/biolib_api_client/app_types.py +24 -4
- biolib/biolib_api_client/auth.py +31 -8
- biolib/biolib_api_client/biolib_app_api.py +147 -52
- biolib/biolib_api_client/biolib_job_api.py +161 -141
- biolib/biolib_api_client/job_types.py +21 -5
- biolib/biolib_api_client/lfs_types.py +7 -23
- biolib/biolib_api_client/user_state.py +56 -0
- biolib/biolib_binary_format/__init__.py +1 -4
- biolib/biolib_binary_format/file_in_container.py +105 -0
- biolib/biolib_binary_format/module_input.py +24 -7
- biolib/biolib_binary_format/module_output_v2.py +149 -0
- biolib/biolib_binary_format/remote_endpoints.py +34 -0
- biolib/biolib_binary_format/remote_stream_seeker.py +59 -0
- biolib/biolib_binary_format/saved_job.py +3 -2
- biolib/biolib_binary_format/{attestation_document.py → stdout_and_stderr.py} +8 -8
- biolib/biolib_binary_format/system_status_update.py +3 -2
- biolib/biolib_binary_format/utils.py +175 -0
- biolib/biolib_docker_client/__init__.py +11 -2
- biolib/biolib_errors.py +36 -0
- biolib/biolib_logging.py +27 -10
- biolib/cli/__init__.py +38 -0
- biolib/cli/auth.py +46 -0
- biolib/cli/data_record.py +164 -0
- biolib/cli/index.py +32 -0
- biolib/cli/init.py +421 -0
- biolib/cli/lfs.py +101 -0
- biolib/cli/push.py +50 -0
- biolib/cli/run.py +63 -0
- biolib/cli/runtime.py +14 -0
- biolib/cli/sdk.py +16 -0
- biolib/cli/start.py +56 -0
- biolib/compute_node/cloud_utils/cloud_utils.py +110 -161
- biolib/compute_node/job_worker/cache_state.py +66 -88
- biolib/compute_node/job_worker/cache_types.py +1 -6
- biolib/compute_node/job_worker/docker_image_cache.py +112 -37
- biolib/compute_node/job_worker/executors/__init__.py +0 -3
- biolib/compute_node/job_worker/executors/docker_executor.py +532 -199
- biolib/compute_node/job_worker/executors/docker_types.py +9 -1
- biolib/compute_node/job_worker/executors/types.py +19 -9
- biolib/compute_node/job_worker/job_legacy_input_wait_timeout_thread.py +30 -0
- biolib/compute_node/job_worker/job_max_runtime_timer_thread.py +3 -5
- biolib/compute_node/job_worker/job_storage.py +108 -0
- biolib/compute_node/job_worker/job_worker.py +397 -212
- biolib/compute_node/job_worker/large_file_system.py +87 -38
- biolib/compute_node/job_worker/network_alloc.py +99 -0
- biolib/compute_node/job_worker/network_buffer.py +240 -0
- biolib/compute_node/job_worker/utilization_reporter_thread.py +197 -0
- biolib/compute_node/job_worker/utils.py +9 -24
- biolib/compute_node/remote_host_proxy.py +400 -98
- biolib/compute_node/utils.py +31 -9
- biolib/compute_node/webserver/compute_node_results_proxy.py +189 -0
- biolib/compute_node/webserver/proxy_utils.py +28 -0
- biolib/compute_node/webserver/webserver.py +130 -44
- biolib/compute_node/webserver/webserver_types.py +2 -6
- biolib/compute_node/webserver/webserver_utils.py +77 -12
- biolib/compute_node/webserver/worker_thread.py +183 -42
- biolib/experiments/__init__.py +0 -0
- biolib/experiments/experiment.py +356 -0
- biolib/jobs/__init__.py +1 -0
- biolib/jobs/job.py +741 -0
- biolib/jobs/job_result.py +185 -0
- biolib/jobs/types.py +50 -0
- biolib/py.typed +0 -0
- biolib/runtime/__init__.py +14 -0
- biolib/sdk/__init__.py +91 -0
- biolib/tables.py +34 -0
- biolib/typing_utils.py +2 -7
- biolib/user/__init__.py +1 -0
- biolib/user/sign_in.py +54 -0
- biolib/utils/__init__.py +162 -0
- biolib/utils/cache_state.py +94 -0
- biolib/utils/multipart_uploader.py +194 -0
- biolib/utils/seq_util.py +150 -0
- biolib/utils/zip/remote_zip.py +640 -0
- pybiolib-1.2.1890.dist-info/METADATA +41 -0
- pybiolib-1.2.1890.dist-info/RECORD +177 -0
- {pybiolib-0.2.951.dist-info → pybiolib-1.2.1890.dist-info}/WHEEL +1 -1
- pybiolib-1.2.1890.dist-info/entry_points.txt +2 -0
- README.md +0 -17
- biolib/app/app_result.py +0 -68
- biolib/app/utils.py +0 -62
- biolib/biolib-js/0-biolib.worker.js +0 -1
- biolib/biolib-js/1-biolib.worker.js +0 -1
- biolib/biolib-js/2-biolib.worker.js +0 -1
- biolib/biolib-js/3-biolib.worker.js +0 -1
- biolib/biolib-js/4-biolib.worker.js +0 -1
- biolib/biolib-js/5-biolib.worker.js +0 -1
- biolib/biolib-js/6-biolib.worker.js +0 -1
- biolib/biolib-js/index.html +0 -10
- biolib/biolib-js/main-biolib.js +0 -1
- biolib/biolib_api_client/biolib_account_api.py +0 -21
- biolib/biolib_api_client/biolib_large_file_system_api.py +0 -108
- biolib/biolib_binary_format/aes_encrypted_package.py +0 -42
- biolib/biolib_binary_format/module_output.py +0 -58
- biolib/biolib_binary_format/rsa_encrypted_aes_package.py +0 -57
- biolib/biolib_push.py +0 -114
- biolib/cli.py +0 -203
- biolib/cli_utils.py +0 -273
- biolib/compute_node/cloud_utils/enclave_parent_types.py +0 -7
- biolib/compute_node/enclave/__init__.py +0 -2
- biolib/compute_node/enclave/enclave_remote_hosts.py +0 -53
- biolib/compute_node/enclave/nitro_secure_module_utils.py +0 -64
- biolib/compute_node/job_worker/executors/base_executor.py +0 -18
- biolib/compute_node/job_worker/executors/pyppeteer_executor.py +0 -173
- biolib/compute_node/job_worker/executors/remote/__init__.py +0 -1
- biolib/compute_node/job_worker/executors/remote/nitro_enclave_utils.py +0 -81
- biolib/compute_node/job_worker/executors/remote/remote_executor.py +0 -51
- biolib/lfs.py +0 -196
- biolib/pyppeteer/.circleci/config.yml +0 -100
- biolib/pyppeteer/.coveragerc +0 -3
- biolib/pyppeteer/.gitignore +0 -89
- biolib/pyppeteer/.pre-commit-config.yaml +0 -28
- biolib/pyppeteer/CHANGES.md +0 -253
- biolib/pyppeteer/CONTRIBUTING.md +0 -26
- biolib/pyppeteer/LICENSE +0 -12
- biolib/pyppeteer/README.md +0 -137
- biolib/pyppeteer/docs/Makefile +0 -177
- biolib/pyppeteer/docs/_static/custom.css +0 -28
- biolib/pyppeteer/docs/_templates/layout.html +0 -10
- biolib/pyppeteer/docs/changes.md +0 -1
- biolib/pyppeteer/docs/conf.py +0 -299
- biolib/pyppeteer/docs/index.md +0 -21
- biolib/pyppeteer/docs/make.bat +0 -242
- biolib/pyppeteer/docs/reference.md +0 -211
- biolib/pyppeteer/docs/server.py +0 -60
- biolib/pyppeteer/poetry.lock +0 -1699
- biolib/pyppeteer/pyppeteer/__init__.py +0 -135
- biolib/pyppeteer/pyppeteer/accessibility.py +0 -286
- biolib/pyppeteer/pyppeteer/browser.py +0 -401
- biolib/pyppeteer/pyppeteer/browser_fetcher.py +0 -194
- biolib/pyppeteer/pyppeteer/command.py +0 -22
- biolib/pyppeteer/pyppeteer/connection/__init__.py +0 -242
- biolib/pyppeteer/pyppeteer/connection/cdpsession.py +0 -101
- biolib/pyppeteer/pyppeteer/coverage.py +0 -346
- biolib/pyppeteer/pyppeteer/device_descriptors.py +0 -787
- biolib/pyppeteer/pyppeteer/dialog.py +0 -79
- biolib/pyppeteer/pyppeteer/domworld.py +0 -597
- biolib/pyppeteer/pyppeteer/emulation_manager.py +0 -53
- biolib/pyppeteer/pyppeteer/errors.py +0 -48
- biolib/pyppeteer/pyppeteer/events.py +0 -63
- biolib/pyppeteer/pyppeteer/execution_context.py +0 -156
- biolib/pyppeteer/pyppeteer/frame/__init__.py +0 -299
- biolib/pyppeteer/pyppeteer/frame/frame_manager.py +0 -306
- biolib/pyppeteer/pyppeteer/helpers.py +0 -245
- biolib/pyppeteer/pyppeteer/input.py +0 -371
- biolib/pyppeteer/pyppeteer/jshandle.py +0 -598
- biolib/pyppeteer/pyppeteer/launcher.py +0 -683
- biolib/pyppeteer/pyppeteer/lifecycle_watcher.py +0 -169
- biolib/pyppeteer/pyppeteer/models/__init__.py +0 -103
- biolib/pyppeteer/pyppeteer/models/_protocol.py +0 -12460
- biolib/pyppeteer/pyppeteer/multimap.py +0 -82
- biolib/pyppeteer/pyppeteer/network_manager.py +0 -678
- biolib/pyppeteer/pyppeteer/options.py +0 -8
- biolib/pyppeteer/pyppeteer/page.py +0 -1728
- biolib/pyppeteer/pyppeteer/pipe_transport.py +0 -59
- biolib/pyppeteer/pyppeteer/target.py +0 -147
- biolib/pyppeteer/pyppeteer/task_queue.py +0 -24
- biolib/pyppeteer/pyppeteer/timeout_settings.py +0 -36
- biolib/pyppeteer/pyppeteer/tracing.py +0 -93
- biolib/pyppeteer/pyppeteer/us_keyboard_layout.py +0 -305
- biolib/pyppeteer/pyppeteer/util.py +0 -18
- biolib/pyppeteer/pyppeteer/websocket_transport.py +0 -47
- biolib/pyppeteer/pyppeteer/worker.py +0 -101
- biolib/pyppeteer/pyproject.toml +0 -97
- biolib/pyppeteer/spell.txt +0 -137
- biolib/pyppeteer/tox.ini +0 -72
- biolib/pyppeteer/utils/generate_protocol_types.py +0 -603
- biolib/start_cli.py +0 -7
- biolib/utils.py +0 -47
- biolib/validators/validate_app_version.py +0 -183
- biolib/validators/validate_argument.py +0 -134
- biolib/validators/validate_module.py +0 -323
- biolib/validators/validate_zip_file.py +0 -40
- biolib/validators/validator_utils.py +0 -103
- pybiolib-0.2.951.dist-info/LICENSE +0 -21
- pybiolib-0.2.951.dist-info/METADATA +0 -61
- pybiolib-0.2.951.dist-info/RECORD +0 -153
- pybiolib-0.2.951.dist-info/entry_points.txt +0 -3
- /LICENSE → /pybiolib-1.2.1890.dist-info/licenses/LICENSE +0 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import os
|
|
3
|
+
import tarfile
|
|
4
|
+
import threading
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
from docker.models.containers import Container
|
|
8
|
+
|
|
9
|
+
from biolib.biolib_docker_client import BiolibDockerClient
|
|
10
|
+
from biolib.biolib_errors import BioLibError
|
|
11
|
+
from biolib.biolib_logging import logger, logger_no_user_data
|
|
12
|
+
from biolib.compute_node.utils import BIOLIB_PROXY_NETWORK_NAME
|
|
13
|
+
from biolib.compute_node.webserver.proxy_utils import get_biolib_nginx_proxy_image
|
|
14
|
+
from biolib.typing_utils import Optional
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class LogStreamingThread(threading.Thread):
|
|
18
|
+
def __init__(self, container, container_name: str):
|
|
19
|
+
super().__init__(daemon=True)
|
|
20
|
+
self._container = container
|
|
21
|
+
self._container_name = container_name
|
|
22
|
+
self._stop_event = threading.Event()
|
|
23
|
+
|
|
24
|
+
def run(self) -> None:
|
|
25
|
+
try:
|
|
26
|
+
logger_no_user_data.debug(f'Starting log streaming for container "{self._container_name}"')
|
|
27
|
+
log_stream = self._container.logs(follow=True, stream=True)
|
|
28
|
+
for log_line in log_stream:
|
|
29
|
+
if self._stop_event.is_set():
|
|
30
|
+
break
|
|
31
|
+
if log_line:
|
|
32
|
+
logger.debug(f'ComputeNodeResultsProxy | {log_line.decode("utf-8", errors="replace").rstrip()}')
|
|
33
|
+
except Exception as error:
|
|
34
|
+
logger_no_user_data.debug(f'Log streaming for container "{self._container_name}" ended: {error}')
|
|
35
|
+
|
|
36
|
+
def stop(self) -> None:
|
|
37
|
+
self._stop_event.set()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class ComputeNodeResultsProxy:
|
|
41
|
+
_instance: Optional['ComputeNodeResultsProxy'] = None
|
|
42
|
+
|
|
43
|
+
def __init__(self, tls_pem_certificate_path: str, tls_pem_key_path: str):
|
|
44
|
+
assert tls_pem_certificate_path, 'tls_pem_certificate_path is required'
|
|
45
|
+
assert tls_pem_key_path, 'tls_pem_key_path is required'
|
|
46
|
+
self._name = 'biolib-compute-node-results-proxy'
|
|
47
|
+
self._container: Optional[Container] = None
|
|
48
|
+
self._docker = BiolibDockerClient().get_docker_client()
|
|
49
|
+
self._tls_pem_certificate_path = tls_pem_certificate_path
|
|
50
|
+
self._tls_pem_key_path = tls_pem_key_path
|
|
51
|
+
self._log_streaming_thread: Optional[LogStreamingThread] = None
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def start_proxy(tls_pem_certificate_path: str, tls_pem_key_path: str) -> None:
|
|
55
|
+
abs_cert_path = os.path.abspath(tls_pem_certificate_path)
|
|
56
|
+
abs_key_path = os.path.abspath(tls_pem_key_path)
|
|
57
|
+
assert os.path.exists(abs_cert_path), f'TLS certificate file does not exist: {abs_cert_path}'
|
|
58
|
+
assert os.path.exists(abs_key_path), f'TLS key file does not exist: {abs_key_path}'
|
|
59
|
+
|
|
60
|
+
if ComputeNodeResultsProxy._instance is None:
|
|
61
|
+
logger_no_user_data.debug(
|
|
62
|
+
f'Creating ComputeNodeResultsProxy instance with cert: {abs_cert_path}, key: {abs_key_path}'
|
|
63
|
+
)
|
|
64
|
+
ComputeNodeResultsProxy._instance = ComputeNodeResultsProxy(abs_cert_path, abs_key_path)
|
|
65
|
+
ComputeNodeResultsProxy._instance._start() # pylint: disable=protected-access
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def stop_proxy() -> None:
|
|
69
|
+
if ComputeNodeResultsProxy._instance is not None:
|
|
70
|
+
ComputeNodeResultsProxy._instance._terminate() # pylint: disable=protected-access
|
|
71
|
+
ComputeNodeResultsProxy._instance = None
|
|
72
|
+
|
|
73
|
+
def _start(self) -> None:
|
|
74
|
+
docker = BiolibDockerClient.get_docker_client()
|
|
75
|
+
|
|
76
|
+
for index in range(3):
|
|
77
|
+
logger_no_user_data.debug(
|
|
78
|
+
f'Attempt {index} at creating ComputeNodeResultsProxy container "{self._name}"...'
|
|
79
|
+
)
|
|
80
|
+
try:
|
|
81
|
+
self._container = docker.containers.create(
|
|
82
|
+
detach=True,
|
|
83
|
+
image=get_biolib_nginx_proxy_image(),
|
|
84
|
+
name=self._name,
|
|
85
|
+
network=BIOLIB_PROXY_NETWORK_NAME,
|
|
86
|
+
ports={'443/tcp': 20443},
|
|
87
|
+
volumes={
|
|
88
|
+
self._tls_pem_certificate_path: {'bind': '/etc/ssl/certs/certificate.pem', 'mode': 'ro'},
|
|
89
|
+
self._tls_pem_key_path: {'bind': '/etc/ssl/private/private_key.pem', 'mode': 'ro'},
|
|
90
|
+
},
|
|
91
|
+
)
|
|
92
|
+
break
|
|
93
|
+
except Exception as error:
|
|
94
|
+
logger_no_user_data.exception(f'Failed to create container "{self._name}" hit error: {error}')
|
|
95
|
+
|
|
96
|
+
logger_no_user_data.debug('Sleeping before re-trying container creation...')
|
|
97
|
+
time.sleep(3)
|
|
98
|
+
|
|
99
|
+
if not self._container or not self._container.id:
|
|
100
|
+
raise BioLibError(f'Exceeded re-try limit for creating container {self._name}')
|
|
101
|
+
|
|
102
|
+
self._write_nginx_config_to_container()
|
|
103
|
+
self._container.start()
|
|
104
|
+
|
|
105
|
+
logger_no_user_data.debug(f'Waiting for container "{self._name}" to be ready...')
|
|
106
|
+
proxy_is_ready = False
|
|
107
|
+
for retry_count in range(1, 5):
|
|
108
|
+
time.sleep(0.5 * retry_count)
|
|
109
|
+
container_logs = self._container.logs()
|
|
110
|
+
if b'ready for start up\n' in container_logs or b'start worker process ' in container_logs:
|
|
111
|
+
proxy_is_ready = True
|
|
112
|
+
break
|
|
113
|
+
|
|
114
|
+
if not proxy_is_ready:
|
|
115
|
+
logger_no_user_data.error('ComputeNodeResultsProxy did not start properly.')
|
|
116
|
+
self._terminate()
|
|
117
|
+
raise Exception('ComputeNodeResultsProxy did not start properly')
|
|
118
|
+
|
|
119
|
+
self._container.reload()
|
|
120
|
+
logger.debug(f'ComputeNodeResultsProxy container "{self._name}" started with ID: {self._container.id}')
|
|
121
|
+
|
|
122
|
+
self._log_streaming_thread = LogStreamingThread(self._container, self._name)
|
|
123
|
+
self._log_streaming_thread.start()
|
|
124
|
+
logger_no_user_data.debug(f'Started log streaming for container "{self._name}"')
|
|
125
|
+
|
|
126
|
+
def _terminate(self):
|
|
127
|
+
logger_no_user_data.debug(f'Terminating ComputeNodeResultsProxy container "{self._name}"')
|
|
128
|
+
if self._log_streaming_thread:
|
|
129
|
+
self._log_streaming_thread.stop()
|
|
130
|
+
self._log_streaming_thread = None
|
|
131
|
+
|
|
132
|
+
logger.debug(f'Docker container removal temporarily disabled for debugging purposes (container "{self._name}")')
|
|
133
|
+
# TODO: Figure if we need to remove the container or keep it for debugging purposes
|
|
134
|
+
# if self._container:
|
|
135
|
+
# self._container.remove(force=True)
|
|
136
|
+
|
|
137
|
+
def _write_nginx_config_to_container(self) -> None:
|
|
138
|
+
if not self._container:
|
|
139
|
+
raise Exception('ComputeNodeResultsProxy container not defined when attempting to write NGINX config')
|
|
140
|
+
|
|
141
|
+
docker = BiolibDockerClient.get_docker_client()
|
|
142
|
+
|
|
143
|
+
nginx_config = """
|
|
144
|
+
events {
|
|
145
|
+
worker_connections 1024;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
http {
|
|
149
|
+
client_max_body_size 0;
|
|
150
|
+
resolver 127.0.0.11 ipv6=off valid=30s;
|
|
151
|
+
|
|
152
|
+
server {
|
|
153
|
+
listen 443 ssl http2 default_server;
|
|
154
|
+
|
|
155
|
+
ssl_certificate /etc/ssl/certs/certificate.pem;
|
|
156
|
+
ssl_certificate_key /etc/ssl/private/private_key.pem;
|
|
157
|
+
ssl_protocols TLSv1.2 TLSv1.3;
|
|
158
|
+
|
|
159
|
+
location / {
|
|
160
|
+
if ($http_biolib_result_uuid = "") {
|
|
161
|
+
return 400 "Missing biolib-result-uuid header";
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if ($http_biolib_result_port = "") {
|
|
165
|
+
return 400 "Missing biolib-result-port header";
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
set $target_host "biolib-app-caller-proxy-$http_biolib_result_uuid";
|
|
169
|
+
proxy_pass http://$target_host:1080$request_uri;
|
|
170
|
+
proxy_set_header Host $http_host;
|
|
171
|
+
proxy_set_header biolib-result-uuid $http_biolib_result_uuid;
|
|
172
|
+
proxy_set_header biolib-result-port $http_biolib_result_port;
|
|
173
|
+
proxy_pass_request_headers on;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
nginx_config_bytes = nginx_config.encode()
|
|
180
|
+
tarfile_in_memory = io.BytesIO()
|
|
181
|
+
with tarfile.open(fileobj=tarfile_in_memory, mode='w:gz') as tar:
|
|
182
|
+
info = tarfile.TarInfo('/nginx.conf')
|
|
183
|
+
info.size = len(nginx_config_bytes)
|
|
184
|
+
tar.addfile(info, io.BytesIO(nginx_config_bytes))
|
|
185
|
+
|
|
186
|
+
tarfile_bytes = tarfile_in_memory.getvalue()
|
|
187
|
+
tarfile_in_memory.close()
|
|
188
|
+
logger_no_user_data.debug('Writing NGINX configuration to ComputeNodeResultsProxy container')
|
|
189
|
+
docker.api.put_archive(self._container.id, '/etc/nginx', tarfile_bytes)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from docker.errors import ImageNotFound
|
|
2
|
+
from docker.models.images import Image
|
|
3
|
+
|
|
4
|
+
from biolib import utils
|
|
5
|
+
from biolib.biolib_docker_client import BiolibDockerClient
|
|
6
|
+
from biolib.biolib_logging import logger_no_user_data
|
|
7
|
+
from biolib.typing_utils import cast
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_biolib_nginx_proxy_image() -> Image:
|
|
11
|
+
docker = BiolibDockerClient().get_docker_client()
|
|
12
|
+
|
|
13
|
+
if utils.IS_RUNNING_IN_CLOUD:
|
|
14
|
+
try:
|
|
15
|
+
logger_no_user_data.debug('Getting local Docker image for nginx proxy')
|
|
16
|
+
return cast(Image, docker.images.get('biolib-remote-host-proxy:latest'))
|
|
17
|
+
except ImageNotFound:
|
|
18
|
+
logger_no_user_data.debug(
|
|
19
|
+
'Local Docker image for nginx proxy not available. Falling back to public image...'
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
public_image_uri = 'public.ecr.aws/h5y4b3l1/biolib-remote-host-proxy:latest'
|
|
23
|
+
try:
|
|
24
|
+
logger_no_user_data.debug('Getting public Docker image for nginx proxy')
|
|
25
|
+
return cast(Image, docker.images.get(public_image_uri))
|
|
26
|
+
except ImageNotFound:
|
|
27
|
+
logger_no_user_data.debug('Pulling public Docker image for nginx proxy')
|
|
28
|
+
return cast(Image, docker.images.pull(public_image_uri))
|
|
@@ -1,23 +1,34 @@
|
|
|
1
1
|
# pylint: disable=unsubscriptable-object
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
-
import time
|
|
5
|
-
import base64
|
|
6
4
|
import logging
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
import os
|
|
6
|
+
import tempfile
|
|
7
|
+
import time
|
|
8
|
+
|
|
9
|
+
from docker.models.networks import Network # type: ignore
|
|
10
|
+
from flask import Flask, Response, jsonify, request
|
|
9
11
|
|
|
10
12
|
from biolib import utils
|
|
11
13
|
from biolib.biolib_api_client import BiolibApiClient
|
|
12
14
|
from biolib.biolib_binary_format import SavedJob
|
|
13
|
-
from biolib.
|
|
14
|
-
from biolib.
|
|
15
|
+
from biolib.biolib_docker_client import BiolibDockerClient
|
|
16
|
+
from biolib.biolib_logging import TRACE, logger, logger_no_user_data
|
|
15
17
|
from biolib.compute_node.cloud_utils.cloud_utils import CloudUtils
|
|
18
|
+
from biolib.compute_node.utils import BIOLIB_PROXY_NETWORK_NAME
|
|
19
|
+
from biolib.compute_node.webserver import webserver_utils
|
|
20
|
+
from biolib.compute_node.webserver.compute_node_results_proxy import ComputeNodeResultsProxy
|
|
16
21
|
from biolib.compute_node.webserver.gunicorn_flask_application import GunicornFlaskApplication
|
|
17
|
-
from biolib.
|
|
22
|
+
from biolib.compute_node.webserver.webserver_utils import get_job_compute_state_or_404
|
|
23
|
+
from biolib.typing_utils import Optional
|
|
18
24
|
|
|
19
25
|
app = Flask(__name__)
|
|
20
|
-
|
|
26
|
+
|
|
27
|
+
if utils.IS_RUNNING_IN_CLOUD:
|
|
28
|
+
_BIOLIB_TMP_DIR = '/biolib/tmp'
|
|
29
|
+
os.makedirs(_BIOLIB_TMP_DIR, exist_ok=True)
|
|
30
|
+
else:
|
|
31
|
+
_BIOLIB_TMP_DIR = tempfile.TemporaryDirectory().name
|
|
21
32
|
|
|
22
33
|
|
|
23
34
|
@app.route('/hello/')
|
|
@@ -25,6 +36,11 @@ def hello():
|
|
|
25
36
|
return 'Hello'
|
|
26
37
|
|
|
27
38
|
|
|
39
|
+
@app.route('/health/')
|
|
40
|
+
def health():
|
|
41
|
+
return 'biolib-compute-node is running', 200
|
|
42
|
+
|
|
43
|
+
|
|
28
44
|
@app.route('/v1/job/', methods=['POST'])
|
|
29
45
|
def save_job():
|
|
30
46
|
saved_job = json.loads(request.data.decode())
|
|
@@ -34,79 +50,123 @@ def save_job():
|
|
|
34
50
|
return jsonify({'job': 'Invalid job'}), 400
|
|
35
51
|
|
|
36
52
|
job_id = saved_job['job']['public_id']
|
|
53
|
+
job_temporary_dir = os.path.join(_BIOLIB_TMP_DIR, job_id)
|
|
54
|
+
os.makedirs(job_temporary_dir)
|
|
37
55
|
saved_job['BASE_URL'] = BiolibApiClient.get().base_url
|
|
56
|
+
saved_job['job_temporary_dir'] = job_temporary_dir
|
|
38
57
|
|
|
39
58
|
compute_state = webserver_utils.get_compute_state(webserver_utils.UNASSIGNED_COMPUTE_PROCESSES)
|
|
40
59
|
compute_state['job_id'] = job_id
|
|
60
|
+
compute_state['job'] = saved_job['job']
|
|
61
|
+
compute_state['job_temporary_dir'] = job_temporary_dir
|
|
62
|
+
|
|
41
63
|
webserver_utils.JOB_ID_TO_COMPUTE_STATE_DICT[job_id] = compute_state
|
|
42
64
|
|
|
43
65
|
if utils.IS_RUNNING_IN_CLOUD:
|
|
44
66
|
config = CloudUtils.get_webserver_config()
|
|
45
67
|
saved_job['compute_node_info'] = config['compute_node_info']
|
|
68
|
+
compute_state['cloud_job_id'] = saved_job['cloud_job']['public_id']
|
|
69
|
+
compute_state['cloud_job'] = saved_job['cloud_job']
|
|
70
|
+
|
|
71
|
+
webserver_utils.update_auto_shutdown_time()
|
|
46
72
|
|
|
47
73
|
saved_job_bbf_package = SavedJob().serialize(json.dumps(saved_job))
|
|
48
74
|
send_package_to_compute_process(job_id, saved_job_bbf_package)
|
|
49
75
|
|
|
50
|
-
|
|
51
|
-
return Response(base64.b64encode(compute_state['attestation_document']), status=201)
|
|
52
|
-
else:
|
|
53
|
-
return '', 201
|
|
76
|
+
return '', 201
|
|
54
77
|
|
|
55
78
|
|
|
56
79
|
@app.route('/v1/job/<job_id>/start/', methods=['POST'])
|
|
57
80
|
def start_compute(job_id):
|
|
58
81
|
module_input_package = request.data
|
|
82
|
+
|
|
83
|
+
if 'AES-Key-String' in request.headers:
|
|
84
|
+
compute_state = webserver_utils.JOB_ID_TO_COMPUTE_STATE_DICT[job_id]
|
|
85
|
+
compute_state['aes_key_string_b64'] = request.headers['AES-Key-String']
|
|
86
|
+
|
|
59
87
|
send_package_to_compute_process(job_id, module_input_package)
|
|
60
88
|
return '', 201
|
|
61
89
|
|
|
62
90
|
|
|
91
|
+
@app.route('/v1/job/<job_id>/', methods=['DELETE'])
|
|
92
|
+
def terminate_job(job_id: str) -> Response:
|
|
93
|
+
compute_state = get_job_compute_state_or_404(job_id)
|
|
94
|
+
# TODO: Consider BBF package
|
|
95
|
+
compute_state['received_messages_queue'].put(b'CANCEL_JOB')
|
|
96
|
+
return Response()
|
|
97
|
+
|
|
98
|
+
|
|
63
99
|
@app.route('/v1/job/<job_id>/status/')
|
|
64
100
|
def status(job_id):
|
|
65
101
|
# TODO Implement auth token
|
|
66
|
-
|
|
67
|
-
response = jsonify(current_status)
|
|
102
|
+
return_full_logs = request.args.get('logs') == 'full'
|
|
68
103
|
|
|
69
|
-
|
|
70
|
-
|
|
104
|
+
compute_state = get_job_compute_state_or_404(job_id)
|
|
105
|
+
current_status = compute_state['status'].copy()
|
|
106
|
+
response_data = current_status
|
|
107
|
+
response_data['is_completed'] = compute_state['is_completed']
|
|
71
108
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
109
|
+
if current_status['stdout_and_stderr_packages_b64']:
|
|
110
|
+
compute_state['streamed_logs_packages_b64'] = (
|
|
111
|
+
compute_state['streamed_logs_packages_b64'] + current_status['stdout_and_stderr_packages_b64']
|
|
112
|
+
)
|
|
75
113
|
|
|
76
|
-
|
|
114
|
+
compute_state['status']['stdout_and_stderr_packages_b64'] = []
|
|
77
115
|
|
|
116
|
+
if current_status['status_updates']:
|
|
117
|
+
compute_state['previous_status_updates'].extend(current_status['status_updates'])
|
|
118
|
+
compute_state['status']['status_updates'] = []
|
|
78
119
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
result_data = webserver_utils.JOB_ID_TO_COMPUTE_STATE_DICT[job_id]['result']
|
|
83
|
-
# remove result from state dict, so we know the user has started the download
|
|
84
|
-
webserver_utils.JOB_ID_TO_COMPUTE_STATE_DICT[job_id]['result'] = None
|
|
85
|
-
response = Response(result_data)
|
|
86
|
-
response.call_on_close(lambda: webserver_utils.finalize_and_clean_up_compute_job(job_id))
|
|
120
|
+
if return_full_logs:
|
|
121
|
+
response_data['streamed_logs_packages_b64'] = compute_state['streamed_logs_packages_b64']
|
|
122
|
+
response_data['previous_status_updates'] = compute_state['previous_status_updates']
|
|
87
123
|
|
|
88
|
-
|
|
124
|
+
return jsonify(response_data)
|
|
89
125
|
|
|
90
|
-
|
|
91
|
-
|
|
126
|
+
|
|
127
|
+
@app.route('/v1/job/<job_id>/result/')
|
|
128
|
+
def result():
|
|
129
|
+
return 410, 'This endpoint is no longer available. Please update pybiolib by running `pip3 install -U pybiolib`'
|
|
92
130
|
|
|
93
131
|
|
|
94
132
|
def send_package_to_compute_process(job_id, package_bytes):
|
|
95
|
-
|
|
133
|
+
compute_state = get_job_compute_state_or_404(job_id)
|
|
134
|
+
message_queue = compute_state['messages_to_send_queue']
|
|
96
135
|
message_queue.put(package_bytes)
|
|
97
136
|
|
|
98
137
|
|
|
99
|
-
def start_webserver(
|
|
138
|
+
def start_webserver(
|
|
139
|
+
host: str,
|
|
140
|
+
port: int,
|
|
141
|
+
tls_pem_certificate_path: Optional[str] = None,
|
|
142
|
+
tls_pem_key_path: Optional[str] = None,
|
|
143
|
+
) -> None:
|
|
100
144
|
def worker_exit(server, worker): # pylint: disable=unused-argument
|
|
101
|
-
active_compute_states =
|
|
102
|
-
webserver_utils.JOB_ID_TO_COMPUTE_STATE_DICT.values()) + webserver_utils.UNASSIGNED_COMPUTE_PROCESSES
|
|
145
|
+
active_compute_states = (
|
|
146
|
+
list(webserver_utils.JOB_ID_TO_COMPUTE_STATE_DICT.values()) + webserver_utils.UNASSIGNED_COMPUTE_PROCESSES
|
|
147
|
+
)
|
|
103
148
|
logger.debug(f'Sending terminate signal to {len(active_compute_states)} compute processes')
|
|
104
149
|
if active_compute_states:
|
|
105
150
|
for compute_state in active_compute_states:
|
|
106
151
|
if compute_state['worker_thread']:
|
|
107
152
|
compute_state['worker_thread'].terminate()
|
|
108
153
|
time.sleep(2)
|
|
109
|
-
|
|
154
|
+
|
|
155
|
+
if utils.IS_RUNNING_IN_CLOUD:
|
|
156
|
+
try:
|
|
157
|
+
logger_no_user_data.debug('Stopping ComputeNodeResultsProxy...')
|
|
158
|
+
ComputeNodeResultsProxy.stop_proxy()
|
|
159
|
+
except BaseException:
|
|
160
|
+
logger_no_user_data.exception('Failed to stop ComputeNodeResultsProxy')
|
|
161
|
+
|
|
162
|
+
try:
|
|
163
|
+
logger_no_user_data.debug(f'Removing Docker network {BIOLIB_PROXY_NETWORK_NAME}')
|
|
164
|
+
docker_client = BiolibDockerClient.get_docker_client()
|
|
165
|
+
biolib_proxy_network: Network = docker_client.networks.get(BIOLIB_PROXY_NETWORK_NAME)
|
|
166
|
+
biolib_proxy_network.remove()
|
|
167
|
+
logger_no_user_data.debug(f'Successfully removed Docker network {BIOLIB_PROXY_NETWORK_NAME}')
|
|
168
|
+
except BaseException:
|
|
169
|
+
logger_no_user_data.exception(f'Failed to clean up network {BIOLIB_PROXY_NETWORK_NAME}')
|
|
110
170
|
|
|
111
171
|
def post_fork(server, worker): # pylint: disable=unused-argument
|
|
112
172
|
logger.info('Started compute node')
|
|
@@ -117,10 +177,33 @@ def start_webserver(port, host):
|
|
|
117
177
|
utils.IS_DEV = config['is_dev']
|
|
118
178
|
BiolibApiClient.initialize(config['base_url'])
|
|
119
179
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
180
|
+
biolib_proxy_network: Optional[Network] = None
|
|
181
|
+
try:
|
|
182
|
+
logger_no_user_data.debug(f'Creating Docker network {BIOLIB_PROXY_NETWORK_NAME}')
|
|
183
|
+
docker_client = BiolibDockerClient.get_docker_client()
|
|
184
|
+
biolib_proxy_network = docker_client.networks.create(
|
|
185
|
+
name=BIOLIB_PROXY_NETWORK_NAME,
|
|
186
|
+
internal=False,
|
|
187
|
+
driver='bridge',
|
|
188
|
+
)
|
|
189
|
+
logger_no_user_data.debug(f'Successfully created Docker network {BIOLIB_PROXY_NETWORK_NAME}')
|
|
190
|
+
except BaseException:
|
|
191
|
+
logger_no_user_data.exception(f'Failed to create Docker network {BIOLIB_PROXY_NETWORK_NAME}')
|
|
192
|
+
|
|
193
|
+
if biolib_proxy_network:
|
|
194
|
+
try:
|
|
195
|
+
logger_no_user_data.debug('Starting ComputeNodeResultsProxy...')
|
|
196
|
+
ComputeNodeResultsProxy.start_proxy(tls_pem_certificate_path, tls_pem_key_path)
|
|
197
|
+
except BaseException:
|
|
198
|
+
logger_no_user_data.exception('Failed to start ComputeNodeResultsProxy')
|
|
199
|
+
|
|
200
|
+
CloudUtils.initialize()
|
|
201
|
+
webserver_utils.start_auto_shutdown_timer()
|
|
202
|
+
else:
|
|
203
|
+
logger_no_user_data.error(
|
|
204
|
+
f'Docker network {BIOLIB_PROXY_NETWORK_NAME} was not created, shutting down...'
|
|
205
|
+
)
|
|
206
|
+
CloudUtils.deregister_and_shutdown()
|
|
124
207
|
|
|
125
208
|
if logger.level == TRACE:
|
|
126
209
|
gunicorn_log_level_name = 'DEBUG'
|
|
@@ -133,12 +216,15 @@ def start_webserver(port, host):
|
|
|
133
216
|
|
|
134
217
|
options = {
|
|
135
218
|
'bind': f'{host}:{port}',
|
|
136
|
-
'
|
|
137
|
-
'post_fork': post_fork,
|
|
138
|
-
'worker_exit': worker_exit,
|
|
139
|
-
'timeout': '300',
|
|
219
|
+
'certfile': tls_pem_certificate_path,
|
|
140
220
|
'graceful_timeout': 4,
|
|
221
|
+
'keyfile': tls_pem_key_path,
|
|
141
222
|
'loglevel': gunicorn_log_level_name,
|
|
223
|
+
'post_fork': post_fork,
|
|
224
|
+
'ssl_version': 'TLSv1_2',
|
|
225
|
+
'timeout': '7200', # Reduce to 300 when frontend no longer downloads from webserver
|
|
226
|
+
'worker_exit': worker_exit,
|
|
227
|
+
'workers': 1,
|
|
142
228
|
}
|
|
143
229
|
|
|
144
230
|
GunicornFlaskApplication(app, options).run()
|
|
@@ -3,21 +3,17 @@ from biolib.typing_utils import TypedDict
|
|
|
3
3
|
|
|
4
4
|
class ComputeNodeInfo(TypedDict):
|
|
5
5
|
auth_token: str
|
|
6
|
-
public_id: str
|
|
7
6
|
ip_address: str
|
|
7
|
+
public_id: str
|
|
8
|
+
pybiolib_version: str
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class ShutdownTimes(TypedDict):
|
|
11
12
|
auto_shutdown_time_in_seconds: int
|
|
12
|
-
job_max_runtime_shutdown_time_in_seconds: int
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class WebserverConfig(TypedDict):
|
|
16
16
|
base_url: str
|
|
17
|
-
ecr_region_name: str
|
|
18
|
-
max_docker_image_cache_size_bytes: int
|
|
19
|
-
s3_general_storage_bucket_name: str
|
|
20
|
-
s3_lfs_bucket_name: str
|
|
21
17
|
compute_node_info: ComputeNodeInfo
|
|
22
18
|
is_dev: bool
|
|
23
19
|
shutdown_times: ShutdownTimes
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import time
|
|
2
|
+
from datetime import datetime, timedelta
|
|
3
|
+
from multiprocessing import Process
|
|
4
|
+
|
|
5
|
+
from werkzeug.exceptions import NotFound
|
|
2
6
|
|
|
3
7
|
from biolib import utils
|
|
8
|
+
from biolib.biolib_logging import logger_no_user_data
|
|
4
9
|
from biolib.compute_node.cloud_utils import CloudUtils
|
|
5
10
|
from biolib.typing_utils import Dict, List
|
|
6
11
|
from biolib.compute_node.webserver.worker_thread import WorkerThread
|
|
@@ -10,10 +15,12 @@ JOB_ID_TO_COMPUTE_STATE_DICT: Dict = {}
|
|
|
10
15
|
UNASSIGNED_COMPUTE_PROCESSES: List = []
|
|
11
16
|
|
|
12
17
|
|
|
13
|
-
def
|
|
14
|
-
JOB_ID_TO_COMPUTE_STATE_DICT.
|
|
15
|
-
if
|
|
16
|
-
|
|
18
|
+
def get_job_compute_state_or_404(job_id: str):
|
|
19
|
+
compute_state = JOB_ID_TO_COMPUTE_STATE_DICT.get(job_id)
|
|
20
|
+
if compute_state:
|
|
21
|
+
return compute_state
|
|
22
|
+
|
|
23
|
+
raise NotFound('Job not found')
|
|
17
24
|
|
|
18
25
|
|
|
19
26
|
def get_compute_state(unassigned_compute_processes):
|
|
@@ -26,6 +33,12 @@ def get_compute_state(unassigned_compute_processes):
|
|
|
26
33
|
def start_compute_process(unassigned_compute_processes):
|
|
27
34
|
compute_state = {
|
|
28
35
|
'job_id': None,
|
|
36
|
+
'job': None,
|
|
37
|
+
'cloud_job_id': None,
|
|
38
|
+
'cloud_job': None,
|
|
39
|
+
'streamed_logs_packages_b64': [],
|
|
40
|
+
'previous_status_updates': [],
|
|
41
|
+
'is_completed': False,
|
|
29
42
|
'status': {
|
|
30
43
|
'status_updates': [
|
|
31
44
|
{
|
|
@@ -33,21 +46,16 @@ def start_compute_process(unassigned_compute_processes):
|
|
|
33
46
|
'log_message': 'Initializing'
|
|
34
47
|
}
|
|
35
48
|
],
|
|
49
|
+
'stdout_and_stderr_packages_b64': []
|
|
36
50
|
},
|
|
37
|
-
'
|
|
38
|
-
'
|
|
51
|
+
'progress': 0,
|
|
52
|
+
'job_temporary_dir': '',
|
|
39
53
|
'received_messages_queue': None,
|
|
40
54
|
'messages_to_send_queue': None,
|
|
41
55
|
'worker_process': None
|
|
42
56
|
}
|
|
43
57
|
|
|
44
58
|
WorkerThread(compute_state).start()
|
|
45
|
-
|
|
46
|
-
while True:
|
|
47
|
-
if compute_state['attestation_document']:
|
|
48
|
-
break
|
|
49
|
-
time.sleep(0.25)
|
|
50
|
-
|
|
51
59
|
unassigned_compute_processes.append(compute_state)
|
|
52
60
|
|
|
53
61
|
|
|
@@ -62,3 +70,60 @@ def validate_saved_job(saved_job):
|
|
|
62
70
|
return False
|
|
63
71
|
|
|
64
72
|
return True
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def start_auto_shutdown_timer() -> None:
|
|
76
|
+
if not utils.IS_RUNNING_IN_CLOUD:
|
|
77
|
+
logger_no_user_data.error('Not running in cloud so skipping auto shutdown time start')
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
update_auto_shutdown_time()
|
|
81
|
+
|
|
82
|
+
timer = CloudAutoShutdownTimer()
|
|
83
|
+
timer.start()
|
|
84
|
+
logger_no_user_data.debug(f"Started auto shutdown timer on pid: {timer.pid}")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def update_auto_shutdown_time() -> None:
|
|
88
|
+
if not utils.IS_RUNNING_IN_CLOUD:
|
|
89
|
+
logger_no_user_data.error('Not running in cloud so skipping auto shutdown time update')
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
webserver_config = CloudUtils.get_webserver_config()
|
|
93
|
+
auto_shutdown_buffer = webserver_config['shutdown_times']['auto_shutdown_time_in_seconds'] # pylint: disable=unsubscriptable-object
|
|
94
|
+
|
|
95
|
+
if JOB_ID_TO_COMPUTE_STATE_DICT:
|
|
96
|
+
highest_max_job_compute_state = max(
|
|
97
|
+
JOB_ID_TO_COMPUTE_STATE_DICT.values(),
|
|
98
|
+
key=lambda compute_state: compute_state['cloud_job']['max_runtime_in_seconds'] # type: ignore
|
|
99
|
+
)
|
|
100
|
+
highest_max_job_runtime = highest_max_job_compute_state['cloud_job']['max_runtime_in_seconds']
|
|
101
|
+
else:
|
|
102
|
+
highest_max_job_runtime = 0
|
|
103
|
+
|
|
104
|
+
auto_shutdown_time = datetime.now() + timedelta(seconds=highest_max_job_runtime) + timedelta(
|
|
105
|
+
seconds=auto_shutdown_buffer
|
|
106
|
+
)
|
|
107
|
+
auto_shutdown_time_isoformat = datetime.isoformat(auto_shutdown_time)
|
|
108
|
+
|
|
109
|
+
with open(CloudAutoShutdownTimer.SHUTDOWN_TIMESTAMP_FILE_PATH, 'w') as auto_shutdown_time_file:
|
|
110
|
+
auto_shutdown_time_file.write(auto_shutdown_time_isoformat)
|
|
111
|
+
|
|
112
|
+
logger_no_user_data.debug(f'Extending auto shutdown timer to: {auto_shutdown_time_isoformat}')
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class CloudAutoShutdownTimer(Process):
|
|
116
|
+
SHUTDOWN_TIMESTAMP_FILE_PATH = '/tmp/auto_shutdown_time.timestamp'
|
|
117
|
+
|
|
118
|
+
def _get_auto_shutdown_time(self) -> datetime:
|
|
119
|
+
with open(self.SHUTDOWN_TIMESTAMP_FILE_PATH, mode='r') as file:
|
|
120
|
+
return datetime.fromisoformat(file.read())
|
|
121
|
+
|
|
122
|
+
def run(self) -> None:
|
|
123
|
+
auto_shutdown_time = self._get_auto_shutdown_time()
|
|
124
|
+
while datetime.now() < auto_shutdown_time:
|
|
125
|
+
time.sleep(60)
|
|
126
|
+
auto_shutdown_time = self._get_auto_shutdown_time()
|
|
127
|
+
|
|
128
|
+
logger_no_user_data.debug(f'Hit auto shutdown timer since {datetime.now()} > {auto_shutdown_time}')
|
|
129
|
+
CloudUtils.deregister_and_shutdown()
|