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.
Files changed (262) hide show
  1. biolib/__init__.py +357 -11
  2. biolib/_data_record/data_record.py +380 -0
  3. biolib/_index/__init__.py +0 -0
  4. biolib/_index/index.py +55 -0
  5. biolib/_index/query_result.py +103 -0
  6. biolib/_internal/__init__.py +0 -0
  7. biolib/_internal/add_copilot_prompts.py +58 -0
  8. biolib/_internal/add_gui_files.py +81 -0
  9. biolib/_internal/data_record/__init__.py +1 -0
  10. biolib/_internal/data_record/data_record.py +85 -0
  11. biolib/_internal/data_record/push_data.py +116 -0
  12. biolib/_internal/data_record/remote_storage_endpoint.py +43 -0
  13. biolib/_internal/errors.py +5 -0
  14. biolib/_internal/file_utils.py +125 -0
  15. biolib/_internal/fuse_mount/__init__.py +1 -0
  16. biolib/_internal/fuse_mount/experiment_fuse_mount.py +209 -0
  17. biolib/_internal/http_client.py +159 -0
  18. biolib/_internal/lfs/__init__.py +1 -0
  19. biolib/_internal/lfs/cache.py +51 -0
  20. biolib/_internal/libs/__init__.py +1 -0
  21. biolib/_internal/libs/fusepy/__init__.py +1257 -0
  22. biolib/_internal/push_application.py +488 -0
  23. biolib/_internal/runtime.py +22 -0
  24. biolib/_internal/string_utils.py +13 -0
  25. biolib/_internal/templates/__init__.py +1 -0
  26. biolib/_internal/templates/copilot_template/.github/instructions/general-app-knowledge.instructions.md +10 -0
  27. biolib/_internal/templates/copilot_template/.github/instructions/style-general.instructions.md +20 -0
  28. biolib/_internal/templates/copilot_template/.github/instructions/style-python.instructions.md +16 -0
  29. biolib/_internal/templates/copilot_template/.github/instructions/style-react-ts.instructions.md +47 -0
  30. biolib/_internal/templates/copilot_template/.github/prompts/biolib_app_inputs.prompt.md +11 -0
  31. biolib/_internal/templates/copilot_template/.github/prompts/biolib_onboard_repo.prompt.md +19 -0
  32. biolib/_internal/templates/copilot_template/.github/prompts/biolib_run_apps.prompt.md +12 -0
  33. biolib/_internal/templates/dashboard_template/.biolib/config.yml +5 -0
  34. biolib/_internal/templates/github_workflow_template/.github/workflows/biolib.yml +21 -0
  35. biolib/_internal/templates/gitignore_template/.gitignore +10 -0
  36. biolib/_internal/templates/gui_template/.yarnrc.yml +1 -0
  37. biolib/_internal/templates/gui_template/App.tsx +53 -0
  38. biolib/_internal/templates/gui_template/Dockerfile +27 -0
  39. biolib/_internal/templates/gui_template/biolib-sdk.ts +82 -0
  40. biolib/_internal/templates/gui_template/dev-data/output.json +7 -0
  41. biolib/_internal/templates/gui_template/index.css +5 -0
  42. biolib/_internal/templates/gui_template/index.html +13 -0
  43. biolib/_internal/templates/gui_template/index.tsx +10 -0
  44. biolib/_internal/templates/gui_template/package.json +27 -0
  45. biolib/_internal/templates/gui_template/tsconfig.json +24 -0
  46. biolib/_internal/templates/gui_template/vite-plugin-dev-data.ts +50 -0
  47. biolib/_internal/templates/gui_template/vite.config.mts +10 -0
  48. biolib/_internal/templates/init_template/.biolib/config.yml +19 -0
  49. biolib/_internal/templates/init_template/Dockerfile +14 -0
  50. biolib/_internal/templates/init_template/requirements.txt +1 -0
  51. biolib/_internal/templates/init_template/run.py +12 -0
  52. biolib/_internal/templates/init_template/run.sh +4 -0
  53. biolib/_internal/templates/templates.py +25 -0
  54. biolib/_internal/tree_utils.py +106 -0
  55. biolib/_internal/utils/__init__.py +65 -0
  56. biolib/_internal/utils/auth.py +46 -0
  57. biolib/_internal/utils/job_url.py +33 -0
  58. biolib/_internal/utils/multinode.py +263 -0
  59. biolib/_runtime/runtime.py +157 -0
  60. biolib/_session/session.py +44 -0
  61. biolib/_shared/__init__.py +0 -0
  62. biolib/_shared/types/__init__.py +74 -0
  63. biolib/_shared/types/account.py +12 -0
  64. biolib/_shared/types/account_member.py +8 -0
  65. biolib/_shared/types/app.py +9 -0
  66. biolib/_shared/types/data_record.py +40 -0
  67. biolib/_shared/types/experiment.py +32 -0
  68. biolib/_shared/types/file_node.py +17 -0
  69. biolib/_shared/types/push.py +6 -0
  70. biolib/_shared/types/resource.py +37 -0
  71. biolib/_shared/types/resource_deploy_key.py +11 -0
  72. biolib/_shared/types/resource_permission.py +14 -0
  73. biolib/_shared/types/resource_version.py +19 -0
  74. biolib/_shared/types/result.py +14 -0
  75. biolib/_shared/types/typing.py +10 -0
  76. biolib/_shared/types/user.py +19 -0
  77. biolib/_shared/utils/__init__.py +7 -0
  78. biolib/_shared/utils/resource_uri.py +75 -0
  79. biolib/api/__init__.py +6 -0
  80. biolib/api/client.py +168 -0
  81. biolib/app/app.py +252 -49
  82. biolib/app/search_apps.py +45 -0
  83. biolib/biolib_api_client/api_client.py +126 -31
  84. biolib/biolib_api_client/app_types.py +24 -4
  85. biolib/biolib_api_client/auth.py +31 -8
  86. biolib/biolib_api_client/biolib_app_api.py +147 -52
  87. biolib/biolib_api_client/biolib_job_api.py +161 -141
  88. biolib/biolib_api_client/job_types.py +21 -5
  89. biolib/biolib_api_client/lfs_types.py +7 -23
  90. biolib/biolib_api_client/user_state.py +56 -0
  91. biolib/biolib_binary_format/__init__.py +1 -4
  92. biolib/biolib_binary_format/file_in_container.py +105 -0
  93. biolib/biolib_binary_format/module_input.py +24 -7
  94. biolib/biolib_binary_format/module_output_v2.py +149 -0
  95. biolib/biolib_binary_format/remote_endpoints.py +34 -0
  96. biolib/biolib_binary_format/remote_stream_seeker.py +59 -0
  97. biolib/biolib_binary_format/saved_job.py +3 -2
  98. biolib/biolib_binary_format/{attestation_document.py → stdout_and_stderr.py} +8 -8
  99. biolib/biolib_binary_format/system_status_update.py +3 -2
  100. biolib/biolib_binary_format/utils.py +175 -0
  101. biolib/biolib_docker_client/__init__.py +11 -2
  102. biolib/biolib_errors.py +36 -0
  103. biolib/biolib_logging.py +27 -10
  104. biolib/cli/__init__.py +38 -0
  105. biolib/cli/auth.py +46 -0
  106. biolib/cli/data_record.py +164 -0
  107. biolib/cli/index.py +32 -0
  108. biolib/cli/init.py +421 -0
  109. biolib/cli/lfs.py +101 -0
  110. biolib/cli/push.py +50 -0
  111. biolib/cli/run.py +63 -0
  112. biolib/cli/runtime.py +14 -0
  113. biolib/cli/sdk.py +16 -0
  114. biolib/cli/start.py +56 -0
  115. biolib/compute_node/cloud_utils/cloud_utils.py +110 -161
  116. biolib/compute_node/job_worker/cache_state.py +66 -88
  117. biolib/compute_node/job_worker/cache_types.py +1 -6
  118. biolib/compute_node/job_worker/docker_image_cache.py +112 -37
  119. biolib/compute_node/job_worker/executors/__init__.py +0 -3
  120. biolib/compute_node/job_worker/executors/docker_executor.py +532 -199
  121. biolib/compute_node/job_worker/executors/docker_types.py +9 -1
  122. biolib/compute_node/job_worker/executors/types.py +19 -9
  123. biolib/compute_node/job_worker/job_legacy_input_wait_timeout_thread.py +30 -0
  124. biolib/compute_node/job_worker/job_max_runtime_timer_thread.py +3 -5
  125. biolib/compute_node/job_worker/job_storage.py +108 -0
  126. biolib/compute_node/job_worker/job_worker.py +397 -212
  127. biolib/compute_node/job_worker/large_file_system.py +87 -38
  128. biolib/compute_node/job_worker/network_alloc.py +99 -0
  129. biolib/compute_node/job_worker/network_buffer.py +240 -0
  130. biolib/compute_node/job_worker/utilization_reporter_thread.py +197 -0
  131. biolib/compute_node/job_worker/utils.py +9 -24
  132. biolib/compute_node/remote_host_proxy.py +400 -98
  133. biolib/compute_node/utils.py +31 -9
  134. biolib/compute_node/webserver/compute_node_results_proxy.py +189 -0
  135. biolib/compute_node/webserver/proxy_utils.py +28 -0
  136. biolib/compute_node/webserver/webserver.py +130 -44
  137. biolib/compute_node/webserver/webserver_types.py +2 -6
  138. biolib/compute_node/webserver/webserver_utils.py +77 -12
  139. biolib/compute_node/webserver/worker_thread.py +183 -42
  140. biolib/experiments/__init__.py +0 -0
  141. biolib/experiments/experiment.py +356 -0
  142. biolib/jobs/__init__.py +1 -0
  143. biolib/jobs/job.py +741 -0
  144. biolib/jobs/job_result.py +185 -0
  145. biolib/jobs/types.py +50 -0
  146. biolib/py.typed +0 -0
  147. biolib/runtime/__init__.py +14 -0
  148. biolib/sdk/__init__.py +91 -0
  149. biolib/tables.py +34 -0
  150. biolib/typing_utils.py +2 -7
  151. biolib/user/__init__.py +1 -0
  152. biolib/user/sign_in.py +54 -0
  153. biolib/utils/__init__.py +162 -0
  154. biolib/utils/cache_state.py +94 -0
  155. biolib/utils/multipart_uploader.py +194 -0
  156. biolib/utils/seq_util.py +150 -0
  157. biolib/utils/zip/remote_zip.py +640 -0
  158. pybiolib-1.2.1890.dist-info/METADATA +41 -0
  159. pybiolib-1.2.1890.dist-info/RECORD +177 -0
  160. {pybiolib-0.2.951.dist-info → pybiolib-1.2.1890.dist-info}/WHEEL +1 -1
  161. pybiolib-1.2.1890.dist-info/entry_points.txt +2 -0
  162. README.md +0 -17
  163. biolib/app/app_result.py +0 -68
  164. biolib/app/utils.py +0 -62
  165. biolib/biolib-js/0-biolib.worker.js +0 -1
  166. biolib/biolib-js/1-biolib.worker.js +0 -1
  167. biolib/biolib-js/2-biolib.worker.js +0 -1
  168. biolib/biolib-js/3-biolib.worker.js +0 -1
  169. biolib/biolib-js/4-biolib.worker.js +0 -1
  170. biolib/biolib-js/5-biolib.worker.js +0 -1
  171. biolib/biolib-js/6-biolib.worker.js +0 -1
  172. biolib/biolib-js/index.html +0 -10
  173. biolib/biolib-js/main-biolib.js +0 -1
  174. biolib/biolib_api_client/biolib_account_api.py +0 -21
  175. biolib/biolib_api_client/biolib_large_file_system_api.py +0 -108
  176. biolib/biolib_binary_format/aes_encrypted_package.py +0 -42
  177. biolib/biolib_binary_format/module_output.py +0 -58
  178. biolib/biolib_binary_format/rsa_encrypted_aes_package.py +0 -57
  179. biolib/biolib_push.py +0 -114
  180. biolib/cli.py +0 -203
  181. biolib/cli_utils.py +0 -273
  182. biolib/compute_node/cloud_utils/enclave_parent_types.py +0 -7
  183. biolib/compute_node/enclave/__init__.py +0 -2
  184. biolib/compute_node/enclave/enclave_remote_hosts.py +0 -53
  185. biolib/compute_node/enclave/nitro_secure_module_utils.py +0 -64
  186. biolib/compute_node/job_worker/executors/base_executor.py +0 -18
  187. biolib/compute_node/job_worker/executors/pyppeteer_executor.py +0 -173
  188. biolib/compute_node/job_worker/executors/remote/__init__.py +0 -1
  189. biolib/compute_node/job_worker/executors/remote/nitro_enclave_utils.py +0 -81
  190. biolib/compute_node/job_worker/executors/remote/remote_executor.py +0 -51
  191. biolib/lfs.py +0 -196
  192. biolib/pyppeteer/.circleci/config.yml +0 -100
  193. biolib/pyppeteer/.coveragerc +0 -3
  194. biolib/pyppeteer/.gitignore +0 -89
  195. biolib/pyppeteer/.pre-commit-config.yaml +0 -28
  196. biolib/pyppeteer/CHANGES.md +0 -253
  197. biolib/pyppeteer/CONTRIBUTING.md +0 -26
  198. biolib/pyppeteer/LICENSE +0 -12
  199. biolib/pyppeteer/README.md +0 -137
  200. biolib/pyppeteer/docs/Makefile +0 -177
  201. biolib/pyppeteer/docs/_static/custom.css +0 -28
  202. biolib/pyppeteer/docs/_templates/layout.html +0 -10
  203. biolib/pyppeteer/docs/changes.md +0 -1
  204. biolib/pyppeteer/docs/conf.py +0 -299
  205. biolib/pyppeteer/docs/index.md +0 -21
  206. biolib/pyppeteer/docs/make.bat +0 -242
  207. biolib/pyppeteer/docs/reference.md +0 -211
  208. biolib/pyppeteer/docs/server.py +0 -60
  209. biolib/pyppeteer/poetry.lock +0 -1699
  210. biolib/pyppeteer/pyppeteer/__init__.py +0 -135
  211. biolib/pyppeteer/pyppeteer/accessibility.py +0 -286
  212. biolib/pyppeteer/pyppeteer/browser.py +0 -401
  213. biolib/pyppeteer/pyppeteer/browser_fetcher.py +0 -194
  214. biolib/pyppeteer/pyppeteer/command.py +0 -22
  215. biolib/pyppeteer/pyppeteer/connection/__init__.py +0 -242
  216. biolib/pyppeteer/pyppeteer/connection/cdpsession.py +0 -101
  217. biolib/pyppeteer/pyppeteer/coverage.py +0 -346
  218. biolib/pyppeteer/pyppeteer/device_descriptors.py +0 -787
  219. biolib/pyppeteer/pyppeteer/dialog.py +0 -79
  220. biolib/pyppeteer/pyppeteer/domworld.py +0 -597
  221. biolib/pyppeteer/pyppeteer/emulation_manager.py +0 -53
  222. biolib/pyppeteer/pyppeteer/errors.py +0 -48
  223. biolib/pyppeteer/pyppeteer/events.py +0 -63
  224. biolib/pyppeteer/pyppeteer/execution_context.py +0 -156
  225. biolib/pyppeteer/pyppeteer/frame/__init__.py +0 -299
  226. biolib/pyppeteer/pyppeteer/frame/frame_manager.py +0 -306
  227. biolib/pyppeteer/pyppeteer/helpers.py +0 -245
  228. biolib/pyppeteer/pyppeteer/input.py +0 -371
  229. biolib/pyppeteer/pyppeteer/jshandle.py +0 -598
  230. biolib/pyppeteer/pyppeteer/launcher.py +0 -683
  231. biolib/pyppeteer/pyppeteer/lifecycle_watcher.py +0 -169
  232. biolib/pyppeteer/pyppeteer/models/__init__.py +0 -103
  233. biolib/pyppeteer/pyppeteer/models/_protocol.py +0 -12460
  234. biolib/pyppeteer/pyppeteer/multimap.py +0 -82
  235. biolib/pyppeteer/pyppeteer/network_manager.py +0 -678
  236. biolib/pyppeteer/pyppeteer/options.py +0 -8
  237. biolib/pyppeteer/pyppeteer/page.py +0 -1728
  238. biolib/pyppeteer/pyppeteer/pipe_transport.py +0 -59
  239. biolib/pyppeteer/pyppeteer/target.py +0 -147
  240. biolib/pyppeteer/pyppeteer/task_queue.py +0 -24
  241. biolib/pyppeteer/pyppeteer/timeout_settings.py +0 -36
  242. biolib/pyppeteer/pyppeteer/tracing.py +0 -93
  243. biolib/pyppeteer/pyppeteer/us_keyboard_layout.py +0 -305
  244. biolib/pyppeteer/pyppeteer/util.py +0 -18
  245. biolib/pyppeteer/pyppeteer/websocket_transport.py +0 -47
  246. biolib/pyppeteer/pyppeteer/worker.py +0 -101
  247. biolib/pyppeteer/pyproject.toml +0 -97
  248. biolib/pyppeteer/spell.txt +0 -137
  249. biolib/pyppeteer/tox.ini +0 -72
  250. biolib/pyppeteer/utils/generate_protocol_types.py +0 -603
  251. biolib/start_cli.py +0 -7
  252. biolib/utils.py +0 -47
  253. biolib/validators/validate_app_version.py +0 -183
  254. biolib/validators/validate_argument.py +0 -134
  255. biolib/validators/validate_module.py +0 -323
  256. biolib/validators/validate_zip_file.py +0 -40
  257. biolib/validators/validator_utils.py +0 -103
  258. pybiolib-0.2.951.dist-info/LICENSE +0 -21
  259. pybiolib-0.2.951.dist-info/METADATA +0 -61
  260. pybiolib-0.2.951.dist-info/RECORD +0 -153
  261. pybiolib-0.2.951.dist-info/entry_points.txt +0 -3
  262. /LICENSE → /pybiolib-1.2.1890.dist-info/licenses/LICENSE +0 -0
biolib/cli/start.py ADDED
@@ -0,0 +1,56 @@
1
+ import logging
2
+ import platform
3
+ import sys
4
+
5
+ import click
6
+
7
+ from biolib.biolib_logging import logger, logger_no_user_data
8
+ from biolib.typing_utils import Optional
9
+
10
+
11
+ @click.command(help='Start a local compute node', hidden=True)
12
+ @click.option('--host', default='127.0.0.1', required=False) # TODO: Validate host
13
+ @click.option('--port', default=5000, type=click.IntRange(1, 65_535), required=False)
14
+ @click.option('--tls-certificate', type=click.Path(exists=True), required=False, hidden=True)
15
+ @click.option('--tls-key', type=click.Path(exists=True), required=False, hidden=True)
16
+ @click.option('--initialize-network-buffer', is_flag=True, help='Initialize the remote host network buffer and exit')
17
+ def start(
18
+ host: str, port: int, tls_certificate: Optional[str], tls_key: Optional[str], initialize_network_buffer: bool
19
+ ) -> None:
20
+ logger.configure(default_log_level=logging.INFO)
21
+ logger_no_user_data.configure(default_log_level=logging.INFO)
22
+ if platform.system() == 'Windows':
23
+ raise Exception('Starting a compute node is currently not supported on Windows')
24
+
25
+ if tls_certificate and not tls_key or tls_key and not tls_certificate:
26
+ raise Exception('Options --tls-certificate and --tls-key must be specified together')
27
+
28
+ if initialize_network_buffer:
29
+ from biolib.compute_node.job_worker.network_buffer import ( # pylint: disable=import-outside-toplevel
30
+ NetworkBuffer,
31
+ )
32
+
33
+ network_buffer = NetworkBuffer.get_instance()
34
+ created = network_buffer.fill_buffer()
35
+ logger_no_user_data.info(f'Initialized network buffer (created {created} networks)')
36
+ return
37
+
38
+ try:
39
+ from biolib.compute_node.webserver import webserver # pylint: disable=import-outside-toplevel
40
+
41
+ webserver.start_webserver(
42
+ host=host,
43
+ port=port,
44
+ tls_pem_key_path=tls_key,
45
+ tls_pem_certificate_path=tls_certificate,
46
+ )
47
+ except ModuleNotFoundError as error:
48
+ if error.name in ('flask', 'gunicorn'):
49
+ print(
50
+ 'To use this command, please install the compute-node extras with '
51
+ '"pip3 install --upgrade pybiolib[compute-node]"',
52
+ file=sys.stderr,
53
+ )
54
+ sys.exit(1)
55
+
56
+ raise error
@@ -1,187 +1,138 @@
1
1
  # pylint: disable=unsubscriptable-object
2
-
2
+ import base64
3
+ import json
3
4
  import os
4
5
  import subprocess
5
6
  import time
6
- import signal
7
- from multiprocessing import Process
8
7
  from datetime import datetime
9
8
  from socket import gethostbyname, gethostname
10
- import requests
11
9
 
12
- from biolib import utils
13
- from biolib.biolib_errors import BioLibError
14
- from biolib.biolib_logging import logger
15
- from biolib.typing_utils import Optional
10
+ from biolib import api, utils
16
11
  from biolib.biolib_api_client import BiolibApiClient
17
- from biolib.compute_node.cloud_utils.enclave_parent_types import VsockProxyResponse
18
- from biolib.compute_node.webserver.webserver_types import WebserverConfig, ComputeNodeInfo, ShutdownTimes
19
- from biolib.biolib_api_client import RemoteHost
20
-
12
+ from biolib.biolib_logging import logger_no_user_data
13
+ from biolib.compute_node.webserver.webserver_types import ComputeNodeInfo, ShutdownTimes, WebserverConfig
14
+ from biolib.typing_utils import Dict, List, Optional, cast
21
15
 
22
- class CloudAutoShutdownTimer(Process):
23
- def __init__(self, seconds_to_wait: int) -> None:
24
- super().__init__()
25
- self._seconds_to_wait = seconds_to_wait
26
16
 
27
- def run(self) -> None:
28
- time.sleep(self._seconds_to_wait)
29
- CloudUtils.deregister_and_shutdown()
17
+ def trust_ceritificates(certs_data: List[str]) -> None:
18
+ ca_directory_amazon_linux_2 = '/etc/pki/ca-trust/source/anchors/'
30
19
 
20
+ if not os.path.exists(ca_directory_amazon_linux_2):
21
+ logger_no_user_data.error(f'Certificate path not found at {ca_directory_amazon_linux_2}')
22
+ return
31
23
 
32
- class _EnclaveUtils:
33
- _BASE_URL = 'http://127.0.0.1:5005'
34
-
35
- @staticmethod
36
- def get_webserver_config():
37
- response = requests.get(f'{_EnclaveUtils._BASE_URL}/config/', timeout=5)
38
- return response.json()
39
-
40
- @staticmethod
41
- def deregister_and_shutdown() -> None:
42
- requests.post(url=f'{_EnclaveUtils._BASE_URL}/deregister_and_shutdown/', timeout=5)
24
+ for idx, cert_data in enumerate(certs_data):
25
+ with open(f'{ca_directory_amazon_linux_2}bl-cert-{idx}.crt', mode='w') as cert_file:
26
+ cert_file.write(cert_data)
43
27
 
44
- @staticmethod
45
- def start_vsock_proxy(remote_host: RemoteHost) -> VsockProxyResponse:
46
- response = requests.post(url=f'{_EnclaveUtils._BASE_URL}/vsock_proxy/', json=remote_host, timeout=5)
47
- vsock_proxy: VsockProxyResponse = response.json()
48
- return vsock_proxy
49
-
50
- @staticmethod
51
- def stop_vsock_proxy(vsock_proxy_id: str) -> None:
52
- requests.delete(url=f'{_EnclaveUtils._BASE_URL}/vsock_proxy/{vsock_proxy_id}/', timeout=5)
53
-
54
- @staticmethod
55
- def log_message_to_log_file(log_message: str, level: int) -> None:
56
- requests.post(
57
- url=f'{_EnclaveUtils._BASE_URL}/log/',
58
- json={
59
- 'log_message': log_message,
60
- 'level': level
61
- },
62
- timeout=5,
63
- )
28
+ result = subprocess.run(['update-ca-trust'], capture_output=True, check=False)
29
+ logger_no_user_data.debug(result.stdout.decode())
30
+ if result.returncode == 0:
31
+ logger_no_user_data.info('Certificates added successfully!')
32
+ else:
33
+ logger_no_user_data.error(f'Failed to update certificates: {result.stderr.decode()}')
64
34
 
65
35
 
66
36
  class CloudUtils:
67
37
  _webserver_config: Optional[WebserverConfig] = None
68
- enclave = _EnclaveUtils
69
- _auto_shutdown_timer_pid_file_name: str = '/tmp/biolib_auto_shutdown_timer.pid'
70
38
 
71
39
  @staticmethod
72
40
  def initialize() -> None:
73
- logger.debug('Reporting availability...')
41
+ logger_no_user_data.info('Reporting availability...')
74
42
  CloudUtils._report_availability()
75
- CloudUtils.start_auto_shutdown_timer()
76
43
 
77
44
  @staticmethod
78
45
  def get_webserver_config() -> WebserverConfig:
79
46
  if CloudUtils._webserver_config:
80
47
  return CloudUtils._webserver_config
81
48
 
82
- if utils.BIOLIB_IS_RUNNING_IN_ENCLAVE:
83
- CloudUtils._webserver_config = CloudUtils.enclave.get_webserver_config()
84
-
85
- else:
86
- CloudUtils._webserver_config = WebserverConfig(
87
- compute_node_info=ComputeNodeInfo(
88
- auth_token=CloudUtils._get_environment_variable('BIOLIB_COMPUTE_NODE_AUTH_TOKEN'),
89
- public_id=CloudUtils._get_environment_variable('BIOLIB_COMPUTE_NODE_PUBLIC_ID'),
90
- ip_address=gethostbyname(gethostname())
91
- ),
92
- base_url=CloudUtils._get_environment_variable('BIOLIB_BASE_URL'),
93
- ecr_region_name=CloudUtils._get_environment_variable('BIOLIB_ECR_REGION_NAME'),
94
- s3_general_storage_bucket_name=CloudUtils._get_environment_variable(
95
- 'BIOLIB_S3_GENERAL_STORAGE_BUCKET_NAME'
49
+ CloudUtils._webserver_config = WebserverConfig(
50
+ compute_node_info=ComputeNodeInfo(
51
+ auth_token=CloudUtils._get_environment_variable_or_fail('BIOLIB_COMPUTE_NODE_AUTH_TOKEN'),
52
+ ip_address=os.environ.get('BIOLIB_COMPUTE_NODE_CUSTOM_IP', default=gethostbyname(gethostname())),
53
+ public_id=CloudUtils._get_environment_variable_or_fail('BIOLIB_COMPUTE_NODE_PUBLIC_ID'),
54
+ pybiolib_version=utils.BIOLIB_PACKAGE_VERSION,
55
+ ),
56
+ base_url=CloudUtils._get_environment_variable_or_fail('BIOLIB_BASE_URL'),
57
+ is_dev=os.environ.get('BIOLIB_DEV') == 'TRUE',
58
+ shutdown_times=ShutdownTimes(
59
+ auto_shutdown_time_in_seconds=CloudUtils._get_environment_variable_as_int(
60
+ 'BIOLIB_CLOUD_AUTO_SHUTDOWN_TIME_IN_SECONDS'
96
61
  ),
97
- s3_lfs_bucket_name=CloudUtils._get_environment_variable('BIOLIB_S3_LFS_BUCKET_NAME'),
98
- max_docker_image_cache_size_bytes=CloudUtils._get_environment_variable_as_int(
99
- 'BIOLIB_MAX_DOCKER_IMAGE_CACHE_SIZE_BYTES'
100
- ),
101
- is_dev=CloudUtils._get_environment_variable('BIOLIB_DEV').upper() == 'TRUE',
102
- shutdown_times=ShutdownTimes(
103
- job_max_runtime_shutdown_time_in_seconds=CloudUtils._get_environment_variable_as_int(
104
- 'BIOLIB_CLOUD_JOB_MAX_RUNTIME_IN_SECONDS'
105
- ),
106
- auto_shutdown_time_in_seconds=CloudUtils._get_environment_variable_as_int(
107
- 'BIOLIB_CLOUD_AUTO_SHUTDOWN_TIME_IN_SECONDS'
108
- ),
109
- )
110
- )
62
+ ),
63
+ )
111
64
 
112
65
  return CloudUtils._webserver_config
113
66
 
114
- # Currently only used for enclaves
115
- @staticmethod
116
- def log(log_message: str, level: int) -> None:
117
- CloudUtils.enclave.log_message_to_log_file(log_message, level)
118
-
119
67
  @staticmethod
120
- def deregister_and_shutdown() -> None:
121
- logger.debug('Waiting 10 seconds, deregistering and shutting down...')
122
-
123
- # Sleep for 10 seconds to ensure logs are written
124
- time.sleep(10)
125
-
126
- if utils.BIOLIB_IS_RUNNING_IN_ENCLAVE:
127
- CloudUtils.enclave.deregister_and_shutdown()
128
- else:
68
+ def deregister(error: Optional[str] = None) -> None:
69
+ if utils.IS_RUNNING_IN_CLOUD:
129
70
  config = CloudUtils.get_webserver_config()
130
71
  try:
131
- requests.post(url=f'{config["base_url"]}/api/jobs/deregister/', json={
132
- 'auth_token': config["compute_node_info"]["auth_token"],
133
- 'public_id': config["compute_node_info"]["public_id"],
134
- })
135
- except Exception as error: # pylint: disable=broad-except
136
- logger.error(error)
72
+ api.client.post(
73
+ authenticate=False,
74
+ path='/jobs/deregister/',
75
+ data={
76
+ 'auth_token': config['compute_node_info']['auth_token'],
77
+ 'public_id': config['compute_node_info']['public_id'],
78
+ 'error': error,
79
+ },
80
+ )
81
+ except BaseException as error_object:
82
+ logger_no_user_data.error(f'Failed to deregister got error: {error_object}')
83
+ else:
84
+ logger_no_user_data.error('Not deregistering as environment is not cloud')
137
85
 
138
- logger.debug('Shutting down...')
86
+ @staticmethod
87
+ def shutdown() -> None:
88
+ if utils.IS_RUNNING_IN_CLOUD:
89
+ logger_no_user_data.debug('Waiting 10 seconds and shutting down...')
90
+ # Sleep for 10 seconds to ensure logs are written
91
+ time.sleep(10)
92
+ logger_no_user_data.debug('Shutting down...')
139
93
  try:
140
94
  subprocess.run(['sudo', 'shutdown', 'now'], check=True)
141
95
  except Exception as error: # pylint: disable=broad-except
142
- logger.error(error)
96
+ logger_no_user_data.error(f'Failed to shutdown got error: {error}')
97
+ else:
98
+ logger_no_user_data.error('Not running shutdown as environment is not cloud')
143
99
 
144
100
  @staticmethod
145
- def finish_job(job_id) -> None:
146
- config = CloudUtils.get_webserver_config()
147
- try:
148
- requests.post(
149
- url=f'{config["base_url"]}/api/jobs/cloud/finish/',
150
- json={
151
- 'auth_token': config["compute_node_info"]["auth_token"],
152
- 'job_id': job_id,
153
- },
154
- timeout=5
155
- )
101
+ def deregister_and_shutdown() -> None:
102
+ logger_no_user_data.debug('Deregistering and shutting down...')
156
103
 
157
- except Exception as error: # pylint: disable=broad-except
158
- logger.error(f"Could not finish job with id: {job_id}")
159
- logger.error(error)
104
+ CloudUtils.deregister()
105
+ CloudUtils.shutdown()
160
106
 
161
107
  @staticmethod
162
- def start_auto_shutdown_timer() -> None:
163
- if not utils.IS_RUNNING_IN_CLOUD:
164
- raise BioLibError('Can not start shutdown timer when not running in cloud.')
165
-
166
- if os.path.exists(CloudUtils._auto_shutdown_timer_pid_file_name):
167
- logger.debug("Re-starting the auto shutdown timer")
168
- try:
169
- pid = int(open(CloudUtils._auto_shutdown_timer_pid_file_name, 'r').read())
170
- os.kill(pid, signal.SIGTERM)
171
-
172
- except Exception as error: # pylint: disable=broad-except
173
- logger.warning(error)
174
- logger.warning('Could not kill old auto shutdown timer')
108
+ def finish_cloud_job(cloud_job_id: str, system_exception_code: Optional[int], exit_code: Optional[int]) -> None:
109
+ if not cloud_job_id:
110
+ logger_no_user_data.error(
111
+ 'Finish cloud job was called but no cloud job was supplied. '
112
+ f'System exception code: {system_exception_code}'
113
+ )
114
+ return
175
115
 
176
- config = CloudUtils.get_webserver_config()
177
- timer_process = CloudAutoShutdownTimer(
178
- seconds_to_wait=config['shutdown_times']['auto_shutdown_time_in_seconds']
116
+ logger_no_user_data.debug(
117
+ f'Reporting CloudJob "{cloud_job_id}" as finished with exit code: {exit_code} '
118
+ f'and system exception code: {system_exception_code}'
179
119
  )
180
120
 
181
- timer_process.start()
182
-
183
- with open(CloudUtils._auto_shutdown_timer_pid_file_name, 'w') as pid_file:
184
- pid_file.write(str(timer_process.pid))
121
+ config = CloudUtils.get_webserver_config()
122
+ try:
123
+ api.client.post(
124
+ authenticate=False,
125
+ path='/jobs/cloud/finish/',
126
+ retries=100,
127
+ data={
128
+ 'auth_token': config['compute_node_info']['auth_token'],
129
+ 'cloud_job_id': cloud_job_id,
130
+ 'system_exception_code': system_exception_code,
131
+ 'exit_code': exit_code,
132
+ },
133
+ )
134
+ except BaseException as error:
135
+ logger_no_user_data.debug(f'Failed to finish CloudJob "{cloud_job_id}" due to: {error}')
185
136
 
186
137
  @staticmethod
187
138
  def _report_availability() -> None:
@@ -189,35 +140,33 @@ class CloudUtils:
189
140
  config = CloudUtils.get_webserver_config()
190
141
  compute_node_info = config['compute_node_info']
191
142
  api_client = BiolibApiClient.get()
192
- logger.debug(f'Registering with {compute_node_info} to host {api_client.base_url} at {datetime.now()}')
193
-
194
- response: Optional[requests.Response] = None
195
- max_retries = 5
196
- for retry_count in range(max_retries):
197
- try:
198
- response = requests.post(f'{api_client.base_url}/api/jobs/report_available/',
199
- json=compute_node_info)
200
- break
201
- except Exception as error: # pylint: disable=broad-except
202
- logger.error(f'Self-registering failed with error: {error}')
203
- if retry_count < max_retries - 1:
204
- seconds_to_sleep = 1
205
- logger.info(f'Retrying self-registering in {seconds_to_sleep} seconds')
206
- time.sleep(seconds_to_sleep)
207
-
208
- if not response:
209
- raise BioLibError('Failed to register. Max retry limit reached')
210
-
143
+ logger_no_user_data.debug(
144
+ f'Registering with {compute_node_info} to host {api_client.base_url} at {datetime.now()}'
145
+ )
146
+ response = api.client.post(
147
+ authenticate=False,
148
+ path='/jobs/report_available/',
149
+ data=cast(Dict[str, str], compute_node_info),
150
+ )
211
151
  if response.status_code != 201:
212
- raise Exception("Non 201 error code")
152
+ raise Exception('Non 201 error code')
153
+ else:
154
+ logger_no_user_data.info('Compute node registered!')
155
+ response_data = response.json()
156
+ logger_no_user_data.info(f'Got data on register: {json.dumps(response_data)}')
157
+ certs = []
158
+ for federation in response_data['federation']:
159
+ for cert_b64 in federation['certs_b64']:
160
+ certs.append(base64.b64decode(cert_b64).decode())
161
+ trust_ceritificates(certs)
213
162
 
214
163
  except Exception as exception: # pylint: disable=broad-except
215
- logger.error(f'Shutting down as self register failed due to: {exception}')
164
+ logger_no_user_data.error(f'Shutting down as self register failed due to: {exception}')
216
165
  if not utils.IS_DEV:
217
166
  CloudUtils.deregister_and_shutdown()
218
167
 
219
168
  @staticmethod
220
- def _get_environment_variable(key: str) -> str:
169
+ def _get_environment_variable_or_fail(key: str) -> str:
221
170
  value = os.environ.get(key)
222
171
  # Purposely loose falsy check (instead of `is not None`) as empty string should fail
223
172
  if not value:
@@ -227,4 +176,4 @@ class CloudUtils:
227
176
 
228
177
  @staticmethod
229
178
  def _get_environment_variable_as_int(key: str) -> int:
230
- return int(CloudUtils._get_environment_variable(key))
179
+ return int(CloudUtils._get_environment_variable_or_fail(key))
@@ -1,104 +1,60 @@
1
- import abc
2
1
  import json
3
2
  import os
4
3
  import shutil
5
- import time
6
4
  import uuid
7
- from datetime import datetime
8
5
 
9
- import appdirs # type: ignore
10
-
11
- from biolib.biolib_errors import BioLibError
6
+ from biolib.biolib_logging import logger_no_user_data
12
7
  from biolib.compute_node.job_worker.cache_types import LfsCacheStateDict, UuidStr, StoragePartition, \
13
8
  DockerImageCacheStateDict
14
- from biolib.typing_utils import Dict, List, Optional, Generic, TypeVar
15
-
16
-
17
- class CacheStateError(BioLibError):
18
- pass
9
+ from biolib.typing_utils import Dict, List, cast, Optional
10
+ from biolib.utils.cache_state import CacheState, CacheStateError
19
11
 
20
12
 
21
13
  class DockerCacheStateError(CacheStateError):
22
14
  pass
23
15
 
24
16
 
25
- StateType = TypeVar('StateType')
17
+ class LfsCacheState(CacheState):
26
18
 
19
+ def __init__(self) -> None:
20
+ super().__init__()
27
21
 
28
- class CacheState(abc.ABC, Generic[StateType]):
29
- _cache_dir: str = appdirs.user_cache_dir(appname='pybiolib', appauthor='biolib')
22
+ self._storage_path_for_write: str = self._get_storage_path_for_write()
23
+ self._storage_paths: List[str] = self._get_storage_paths()
24
+ self._tmp_storage_paths: List[str] = self._get_tmp_storage_paths()
30
25
 
31
26
  @property
32
- @abc.abstractmethod
33
- def _state_path(self) -> str:
34
- raise NotImplementedError
27
+ def storage_paths(self) -> List[str]:
28
+ return self._storage_paths
35
29
 
36
30
  @property
37
- @abc.abstractmethod
38
- def _state_lock_path(self) -> str:
39
- raise NotImplementedError
40
-
41
- @abc.abstractmethod
42
- def _get_default_state(self) -> StateType:
43
- raise NotImplementedError
44
-
45
- def __init__(self):
46
- self._state: Optional[StateType] = None
31
+ def storage_path_for_write(self) -> str:
32
+ return self._storage_path_for_write
47
33
 
48
- def __enter__(self) -> StateType:
49
- self._acquire_state_lock()
50
- if os.path.exists(self._state_path):
51
- with open(self._state_path, mode='r') as file:
52
- self._state = json.loads(file.read())
53
- else:
54
- self._state = self._get_default_state()
55
- with open(self._state_path, mode='w') as file:
56
- file.write(json.dumps(self._state))
57
-
58
- # Check for type checking
59
- if self._state is None:
60
- raise CacheStateError('Internal state is not defined')
61
-
62
- return self._state
63
-
64
- def __exit__(self, exc_type, exc_val, exc_tb) -> None:
65
- with open(self._state_path, mode='w') as file:
66
- file.write(json.dumps(self._state))
67
-
68
- self._release_state_lock()
69
-
70
- def _acquire_state_lock(self) -> None:
71
- timeout_seconds = 5.0
72
- seconds_to_sleep = 0.5
73
- while os.path.exists(self._state_lock_path):
74
- time.sleep(seconds_to_sleep)
75
- timeout_seconds -= seconds_to_sleep
76
- if timeout_seconds < 0:
77
- raise CacheStateError('Cache state timed out waiting for lock file')
78
-
79
- os.makedirs(self._cache_dir, exist_ok=True)
80
- lock_file = open(self._state_lock_path, mode='x')
81
- lock_file.close()
82
-
83
- def _release_state_lock(self) -> None:
84
- if os.path.exists(self._state_lock_path):
85
- os.remove(self._state_lock_path)
86
- else:
87
- raise CacheStateError('Cache state was not locked.')
88
-
89
- @staticmethod
90
- def get_timestamp_now() -> str:
91
- return datetime.now().isoformat()
34
+ @property
35
+ def tmp_storage_paths(self) -> List[str]:
36
+ return self._tmp_storage_paths
92
37
 
38
+ @property
39
+ def main_lfs_storage_path(self) -> str:
40
+ return self._storage_paths[0]
93
41
 
94
- class LfsCacheState(CacheState):
95
42
  @property
96
43
  def _state_path(self) -> str:
97
- return f'{super()._cache_dir}/lfs-cache-state.json'
44
+ state_path = os.environ.get('BIOLIB_LFS_STATE_PATH')
45
+ if not state_path:
46
+ raise CacheStateError('Environment variable "BIOLIB_LFS_STATE_PATH" not set')
98
47
 
99
- @property
100
- def _state_lock_path(self) -> str:
101
- return f'{self._state_path}.lock'
48
+ return state_path
49
+
50
+ def get_read_only_dict_without_lock(self) -> Optional[LfsCacheStateDict]:
51
+ try:
52
+ if not os.path.isfile(self._state_path):
53
+ return None
54
+ with open(self._state_path, mode='r') as file:
55
+ return cast(LfsCacheStateDict, json.load(file))
56
+ except BaseException:
57
+ return None
102
58
 
103
59
  def _get_default_state(self) -> LfsCacheStateDict:
104
60
  return LfsCacheStateDict(
@@ -107,26 +63,52 @@ class LfsCacheState(CacheState):
107
63
  )
108
64
 
109
65
  @staticmethod
110
- def get_tmp_storage_paths() -> List[str]:
66
+ def _get_tmp_storage_paths() -> List[str]:
111
67
  lfs_tmp_storage_path_env_key = 'BIOLIB_LFS_TMP_STORAGE_PATHS'
112
68
  lfs_tmp_storage_paths = os.environ.get(lfs_tmp_storage_path_env_key)
113
- if lfs_tmp_storage_paths is None:
69
+ if not lfs_tmp_storage_paths:
114
70
  raise CacheStateError(f'Environment variable "{lfs_tmp_storage_path_env_key}" not set')
115
71
 
116
- return lfs_tmp_storage_paths.split(',')
72
+ lfs_tmp_storage_paths_list = lfs_tmp_storage_paths.split(',')
73
+ for lfs_tmp_storage_path in lfs_tmp_storage_paths_list:
74
+ if not os.path.isdir(lfs_tmp_storage_path):
75
+ raise CacheStateError(f'LFS temporary storage path {lfs_tmp_storage_path} is not a directory')
76
+
77
+ return lfs_tmp_storage_paths_list
117
78
 
118
79
  @staticmethod
119
- def _get_storage_partitions_from_env() -> Dict[UuidStr, StoragePartition]:
80
+ def _get_storage_paths() -> List[str]:
120
81
  lfs_storage_path_env_key = 'BIOLIB_LFS_STORAGE_PATHS'
121
82
  lfs_storage_paths = os.environ.get(lfs_storage_path_env_key)
122
- if lfs_storage_paths is None:
83
+ logger_no_user_data.debug(f'LFS storage paths: {lfs_storage_paths}')
84
+
85
+ # It is essential to check like this so we catch if it is None and if it is empty string
86
+ if not lfs_storage_paths:
123
87
  raise CacheStateError(f'Environment variable "{lfs_storage_path_env_key}" not set')
124
88
 
125
- storage_states: Dict[UuidStr, StoragePartition] = {}
126
- for lfs_storage_path in lfs_storage_paths.split(','):
89
+ lfs_storage_paths_list = lfs_storage_paths.split(',')
90
+ for lfs_storage_path in lfs_storage_paths_list:
127
91
  if not os.path.isdir(lfs_storage_path):
128
92
  raise CacheStateError(f'LFS storage path {lfs_storage_path} is not a directory')
129
93
 
94
+ return lfs_storage_paths_list
95
+
96
+ @staticmethod
97
+ def _get_storage_path_for_write() -> str:
98
+ lfs_storage_path_for_write = os.environ.get('BIOLIB_LFS_WRITE_STORAGE_PATH')
99
+ logger_no_user_data.debug(f'lfs_storage_path_for_write={lfs_storage_path_for_write}')
100
+
101
+ if not lfs_storage_path_for_write:
102
+ raise CacheStateError('Environment variable "BIOLIB_LFS_WRITE_STORAGE_PATH" not set')
103
+
104
+ if not os.path.isdir(lfs_storage_path_for_write):
105
+ raise CacheStateError(f'LFS storage path {lfs_storage_path_for_write} is not a directory')
106
+
107
+ return lfs_storage_path_for_write
108
+
109
+ def _get_storage_partitions_from_env(self) -> Dict[UuidStr, StoragePartition]:
110
+ storage_states: Dict[UuidStr, StoragePartition] = {}
111
+ for lfs_storage_path in self._storage_paths:
130
112
  uuid_str = str(uuid.uuid4())
131
113
  disk_usage = shutil.disk_usage(lfs_storage_path)
132
114
  storage_states[uuid_str] = StoragePartition(
@@ -142,11 +124,7 @@ class LfsCacheState(CacheState):
142
124
  class DockerImageCacheState(CacheState):
143
125
  @property
144
126
  def _state_path(self) -> str:
145
- return f'{super()._cache_dir}/docker-cache-state.json'
146
-
147
- @property
148
- def _state_lock_path(self) -> str:
149
- return f'{self._state_path}.lock'
127
+ return f'{self._user_cache_dir}/docker-cache-state.json'
150
128
 
151
129
  def _get_default_state(self) -> DockerImageCacheStateDict:
152
130
  return {}
@@ -6,7 +6,6 @@ DockerImageUri = str
6
6
 
7
7
 
8
8
  class LargeFileSystemCache(TypedDict):
9
- active_jobs: List[UuidStr]
10
9
  last_used_at: str
11
10
  size_bytes: int
12
11
  state: Literal['downloading', 'ready']
@@ -26,15 +25,11 @@ class LfsCacheStateDict(TypedDict):
26
25
  large_file_systems: Dict[UuidStr, LargeFileSystemCache]
27
26
 
28
27
 
29
- class DockerAuthConfig(TypedDict):
30
- username: str
31
- password: str
32
-
33
-
34
28
  class DockerImageInfo(TypedDict):
35
29
  last_used_at: str
36
30
  estimated_image_size_bytes: int
37
31
  state: Literal['pulling', 'ready']
32
+ active_jobs: List[UuidStr]
38
33
  uri: str
39
34
 
40
35