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
@@ -1,96 +1,140 @@
1
1
  import io
2
+ import ipaddress
2
3
  import tarfile
3
- import subprocess
4
4
  import time
5
+ from urllib.parse import urlparse
5
6
 
6
- from docker.models.containers import Container # type: ignore
7
- from docker.errors import ImageNotFound # type: ignore
8
- from docker.models.images import Image # type: ignore
9
- from docker.models.networks import Network # type: ignore
7
+ from docker.models.containers import Container
8
+ from docker.models.networks import Network
9
+ from docker.types import EndpointConfig
10
10
 
11
11
  from biolib import utils
12
- from biolib.compute_node import enclave
13
- from biolib.compute_node.cloud_utils.enclave_parent_types import VsockProxyResponse
14
- from biolib.compute_node.cloud_utils import CloudUtils
15
- from biolib.typing_utils import Optional
16
- from biolib.biolib_api_client import RemoteHost
12
+ from biolib._internal.utils import base64_encode_string
13
+ from biolib.biolib_api_client import BiolibApiClient, RemoteHost
14
+ from biolib.biolib_api_client.job_types import CreatedJobDict
17
15
  from biolib.biolib_docker_client import BiolibDockerClient
18
- from biolib.biolib_logging import logger
16
+ from biolib.biolib_errors import BioLibError
17
+ from biolib.biolib_logging import logger_no_user_data
18
+ from biolib.compute_node.cloud_utils import CloudUtils
19
+ from biolib.compute_node.utils import BIOLIB_PROXY_NETWORK_NAME
20
+ from biolib.compute_node.webserver.proxy_utils import get_biolib_nginx_proxy_image
21
+ from biolib.typing_utils import Dict, List, Optional
22
+
23
+
24
+ def get_static_ip_from_network(network: Network, offset: int = 2) -> str:
25
+ ipam_config = network.attrs['IPAM']['Config']
26
+ if not ipam_config:
27
+ raise BioLibError(f'Network {network.name} has no IPAM configuration')
28
+
29
+ subnet_str = ipam_config[0]['Subnet']
30
+ subnet = ipaddress.ip_network(subnet_str, strict=False)
31
+
32
+ static_ip = str(subnet.network_address + offset)
33
+
34
+ return static_ip
19
35
 
20
36
 
21
37
  # Prepare for remote hosts with specified port
22
38
  class RemoteHostExtended(RemoteHost):
23
- port: int
39
+ ports: List[int]
24
40
 
25
41
 
26
- class RemoteHostProxy:
27
- _DOCKER_IMAGE_URI = 'nginx:1.21.1-alpine'
28
- _TRAFFIC_FORWARDER_PORT_OFFSET = 10000 # Port offset relative to port of a VSOCK proxy
42
+ class RemoteHostMapping:
43
+ def __init__(self, hostname: str, ports: List[int], network: Network, static_ip: str):
44
+ self.hostname = hostname
45
+ self.ports = ports
46
+ self.network = network
47
+ self.static_ip = static_ip
48
+
29
49
 
50
+ class RemoteHostProxy:
30
51
  def __init__(
31
- self,
32
- remote_host: RemoteHost,
33
- public_network: Network,
34
- internal_network: Optional[Network],
35
- job_id: Optional[str]
52
+ self,
53
+ remote_host_mappings: List[RemoteHostMapping],
54
+ job: CreatedJobDict,
55
+ app_caller_network: Optional[Network] = None,
36
56
  ):
37
- # Default to port 443 for now until backend serves remote_hosts with port specified
38
- self._remote_host: RemoteHostExtended = RemoteHostExtended(hostname=remote_host['hostname'], port=443)
39
- self._public_network: Network = public_network
40
- self._internal_network: Optional[Network] = internal_network
57
+ self._remote_host_mappings = remote_host_mappings
58
+ self._app_caller_network = app_caller_network
59
+ self.is_app_caller_proxy = app_caller_network is not None
41
60
 
42
- if job_id:
43
- self._name = f"biolib-remote-host-proxy-{job_id}-{self.hostname}"
44
- else:
45
- if not utils.BIOLIB_IS_RUNNING_IN_ENCLAVE:
46
- raise Exception('RemoteHostProxy missing argument "job_id"')
47
- self._name = f"biolib-enclave-remote-host-proxy-{self.hostname}"
61
+ if not job:
62
+ raise Exception('RemoteHostProxy missing argument "job"')
48
63
 
64
+ self._job = job
65
+ suffix = '-AppCallerProxy' if app_caller_network else ''
66
+ self._name = f'biolib-remote-host-proxy-{self._job_uuid}{suffix}'
49
67
  self._container: Optional[Container] = None
50
- self._enclave_traffic_forwarder_process: Optional[subprocess.Popen] = None
51
- self._enclave_vsock_proxy: Optional[VsockProxyResponse] = None
68
+ self._docker = BiolibDockerClient().get_docker_client()
52
69
 
53
70
  @property
54
- def hostname(self) -> str:
55
- return self._remote_host['hostname']
71
+ def _job_uuid(self) -> str:
72
+ return self._job['uuid']
73
+
74
+ def get_hostname_to_ip_mapping(self) -> Dict[str, str]:
75
+ return {mapping.hostname: mapping.static_ip for mapping in self._remote_host_mappings}
76
+
77
+ def get_remote_host_networks(self) -> List[Network]:
78
+ networks = [mapping.network for mapping in self._remote_host_mappings]
79
+ return networks
56
80
 
57
81
  def get_ip_address_on_network(self, network: Network) -> str:
58
82
  if not self._container:
59
- raise Exception('RemoteHostProxy not yet started')
83
+ raise BioLibError('RemoteHostProxy not yet started')
60
84
 
61
85
  container_networks = self._container.attrs['NetworkSettings']['Networks']
62
86
  if network.name in container_networks:
63
87
  ip_address: str = container_networks[network.name]['IPAddress']
88
+ if not ip_address:
89
+ raise BioLibError(f'No IP address found for network {network.name}')
64
90
  return ip_address
65
91
 
66
- raise Exception(f'RemoteHostProxy not connected to network {network.name}')
92
+ raise BioLibError(f'RemoteHostProxy not connected to network {network.name}')
67
93
 
68
94
  def start(self) -> None:
69
- # TODO: Implement nice error handling in this method
95
+ docker = BiolibDockerClient.get_docker_client()
70
96
 
71
- upstream_server_name = self._remote_host['hostname']
72
- upstream_server_port = self._remote_host['port']
97
+ networking_config: Optional[Dict[str, EndpointConfig]] = (
98
+ None
99
+ if not self.is_app_caller_proxy
100
+ else {
101
+ BIOLIB_PROXY_NETWORK_NAME: docker.api.create_endpoint_config(
102
+ aliases=[f'biolib-app-caller-proxy-{self._job_uuid}']
103
+ )
104
+ }
105
+ )
73
106
 
74
- if utils.BIOLIB_IS_RUNNING_IN_ENCLAVE:
75
- # In an enclave the flow is: application -> remote host proxy -> traffic forwarder -> VSOCK proxy -> remote
76
- upstream_server_name = self._public_network.attrs['IPAM']['Config'][0]['Gateway']
77
- upstream_server_port = self._start_vsock_proxy_and_traffic_forwarder_and_return_local_port()
107
+ for index in range(3):
108
+ logger_no_user_data.debug(f'Attempt {index} at creating RemoteHostProxy container "{self._name}"...')
109
+ try:
110
+ self._container = docker.containers.create(
111
+ detach=True,
112
+ image=get_biolib_nginx_proxy_image(),
113
+ name=self._name,
114
+ network=BIOLIB_PROXY_NETWORK_NAME,
115
+ networking_config=networking_config,
116
+ )
117
+ break
118
+ except Exception as error:
119
+ logger_no_user_data.exception(f'Failed to create container "{self._name}" hit error: {error}')
78
120
 
79
- docker = BiolibDockerClient.get_docker_client()
80
- self._container = docker.containers.create(
81
- detach=True,
82
- image=self._get_nginx_docker_image(),
83
- name=self._name,
84
- network=self._public_network.name,
85
- )
121
+ logger_no_user_data.debug('Sleeping before re-trying container creation...')
122
+ time.sleep(3)
86
123
 
87
- self._write_nginx_config_to_container(
88
- upstream_server_name,
89
- upstream_server_port,
90
- )
124
+ if not self._container or not self._container.id:
125
+ raise BioLibError(f'Exceeded re-try limit for creating container {self._name}')
126
+
127
+ for mapping in self._remote_host_mappings:
128
+ mapping.network.connect(self._container.id, ipv4_address=mapping.static_ip)
129
+ logger_no_user_data.debug(
130
+ f'Connected proxy to network {mapping.network.name} with static IP {mapping.static_ip}'
131
+ )
91
132
 
92
- if self._internal_network:
93
- self._internal_network.connect(self._container.id)
133
+ if self._app_caller_network:
134
+ self._app_caller_network.connect(self._container.id)
135
+ logger_no_user_data.debug(f'Connected app caller proxy to network {self._app_caller_network.name}')
136
+
137
+ self._write_nginx_config_to_container()
94
138
 
95
139
  self._container.start()
96
140
 
@@ -99,7 +143,8 @@ class RemoteHostProxy:
99
143
  time.sleep(0.5 * retry_count)
100
144
  # Use the container logs as a health check.
101
145
  # By using logs instead of a http endpoint on the NGINX we avoid publishing a port of container to the host
102
- if b'start worker process ' in self._container.logs():
146
+ container_logs = self._container.logs()
147
+ if b'ready for start up\n' in container_logs or b'start worker process ' in container_logs:
103
148
  proxy_is_ready = True
104
149
  break
105
150
 
@@ -115,58 +160,315 @@ class RemoteHostProxy:
115
160
  if self._container:
116
161
  self._container.remove(force=True)
117
162
 
118
- if self._enclave_traffic_forwarder_process:
119
- self._enclave_traffic_forwarder_process.terminate()
120
-
121
- if self._enclave_vsock_proxy:
122
- CloudUtils.enclave.stop_vsock_proxy(self._enclave_vsock_proxy['id'])
163
+ def _write_nginx_config_to_container(self) -> None:
164
+ if not self._container:
165
+ raise Exception('RemoteHostProxy container not defined when attempting to write NGINX config')
123
166
 
124
- def _get_nginx_docker_image(self) -> Image:
125
167
  docker = BiolibDockerClient.get_docker_client()
126
- try:
127
- return docker.images.get(self._DOCKER_IMAGE_URI)
128
- except ImageNotFound:
129
- logger.debug('Pulling remote host docker image...')
130
- return docker.images.pull(self._DOCKER_IMAGE_URI)
168
+ upstream_hostname = urlparse(BiolibApiClient.get().base_url).hostname
169
+ if self.is_app_caller_proxy:
170
+ if not utils.IS_RUNNING_IN_CLOUD:
171
+ raise BioLibError('Calling apps inside apps is not supported in local compute environment')
172
+
173
+ logger_no_user_data.debug(f'Job "{self._job_uuid}" writing config for and starting App Caller Proxy...')
174
+ config = CloudUtils.get_webserver_config()
175
+ compute_node_uuid = config['compute_node_info']['public_id']
176
+ compute_node_auth_token = config['compute_node_info']['auth_token']
177
+
178
+ # TODO: Get access_token from new API class instead
179
+ access_token = BiolibApiClient.get().access_token
180
+ bearer_token = f'Bearer {access_token}' if access_token else ''
181
+
182
+ user_uuid = self._job.get('user_id')
183
+ if user_uuid:
184
+ biolib_index_basic_auth = (
185
+ f'biolib_user|{user_uuid.replace("-", "_")}:cloud-{compute_node_auth_token},{self._job_uuid}'
186
+ )
187
+ biolib_index_auth_header_value = f'Basic {base64_encode_string(biolib_index_basic_auth)}'
188
+ logger_no_user_data.debug(f'Job "{self._job_uuid}" using biolib_user auth for biolib-index')
189
+ else:
190
+ biolib_index_auth_header_value = ''
191
+ logger_no_user_data.debug(f'Job "{self._job_uuid}" has no biolib-index auth configured')
192
+
193
+ nginx_config = f"""
194
+ events {{
195
+ worker_connections 1024;
196
+ }}
131
197
 
132
- def _start_vsock_proxy_and_traffic_forwarder_and_return_local_port(self) -> int:
133
- # TODO: Implement nice error handling in this method
198
+ http {{
199
+ client_max_body_size 0;
200
+ map $request_method $bearer_token_on_post {{
201
+ POST "{bearer_token}";
202
+ default "";
203
+ }}
134
204
 
135
- if not utils.BIOLIB_IS_RUNNING_IN_ENCLAVE:
136
- raise Exception('VSOCK proxy and traffic forwarder should only be started in enclave')
205
+ map $request_method $bearer_token_on_get {{
206
+ GET "{bearer_token}";
207
+ default "";
208
+ }}
137
209
 
138
- logger.debug(f"Requesting vsock proxy for hostname {self.hostname}")
139
- self._enclave_vsock_proxy = CloudUtils.enclave.start_vsock_proxy(self._remote_host)
210
+ map $request_method $bearer_token_on_patch_and_get {{
211
+ PATCH "{bearer_token}";
212
+ GET "{bearer_token}";
213
+ default "";
214
+ }}
140
215
 
141
- traffic_forwarder_local_port = self._enclave_vsock_proxy['port'] + self._TRAFFIC_FORWARDER_PORT_OFFSET
142
- logger.debug(
143
- f"Starting traffic forwarder on local port {traffic_forwarder_local_port} to "
144
- f"VSOCK port {self._enclave_vsock_proxy['port']} for hostname {self.hostname}"
145
- )
146
- self._enclave_traffic_forwarder_process = subprocess.Popen([
147
- 'biolib_traffic_forwarder',
148
- str(traffic_forwarder_local_port),
149
- str(enclave.PARENT_CID),
150
- str(self._enclave_vsock_proxy['port']),
151
- ])
152
- return traffic_forwarder_local_port
153
-
154
- def _write_nginx_config_to_container(self, upstream_server_name: str, upstream_server_port: int) -> None:
155
- if not self._container:
156
- raise Exception('RemoteHostProxy container not defined when attempting to write NGINX config')
216
+ server {{
217
+ listen 80;
218
+ resolver 127.0.0.11 ipv6=off valid=30s;
219
+ set $upstream_hostname {upstream_hostname};
220
+
221
+ location ~* "^/api/jobs/cloud/(?<job_id>[a-z0-9-]{{36}})/status/$" {{
222
+ proxy_pass https://$upstream_hostname/api/jobs/cloud/$job_id/status/;
223
+ proxy_set_header authorization $bearer_token_on_get;
224
+ proxy_set_header cookie "";
225
+ proxy_ssl_server_name on;
226
+ }}
227
+
228
+ location ~* "^/api/jobs/cloud/$" {{
229
+ # Note: Using $1 here as URI part from regex must be used for proxy_pass
230
+ proxy_pass https://$upstream_hostname/api/jobs/cloud/$1;
231
+ proxy_set_header authorization $bearer_token_on_post;
232
+ proxy_set_header cookie "";
233
+ proxy_ssl_server_name on;
234
+ }}
235
+
236
+ location ~* "^/api/jobs/(?<job_id>[a-z0-9-]{{36}})/storage/input/start_upload/$" {{
237
+ proxy_pass https://$upstream_hostname/api/jobs/$job_id/storage/input/start_upload/;
238
+ proxy_set_header authorization "";
239
+ proxy_set_header cookie "";
240
+ proxy_ssl_server_name on;
241
+ }}
242
+
243
+ location ~* "^/api/jobs/(?<job_id>[a-z0-9-]{{36}})/storage/input/presigned_upload_url/$" {{
244
+ proxy_pass https://$upstream_hostname/api/jobs/$job_id/storage/input/presigned_upload_url/$is_args$args;
245
+ proxy_set_header authorization "";
246
+ proxy_set_header cookie "";
247
+ proxy_ssl_server_name on;
248
+ }}
249
+
250
+ location ~* "^/api/jobs/(?<job_id>[a-z0-9-]{{36}})/storage/input/complete_upload/$" {{
251
+ proxy_pass https://$upstream_hostname/api/jobs/$job_id/storage/input/complete_upload/;
252
+ proxy_set_header authorization "";
253
+ proxy_set_header cookie "";
254
+ proxy_ssl_server_name on;
255
+ }}
256
+
257
+ location ~* "^/api/jobs/(?<job_id>[a-z0-9-]{{36}})/main_result/$" {{
258
+ proxy_pass https://$upstream_hostname/api/jobs/$job_id/main_result/;
259
+ proxy_set_header authorization "";
260
+ proxy_set_header cookie "";
261
+ proxy_pass_request_headers on;
262
+ proxy_ssl_server_name on;
263
+ }}
264
+
265
+ location ~* "^/api/jobs/(?<job_id>[a-z0-9-]{{36}})/$" {{
266
+ proxy_pass https://$upstream_hostname/api/jobs/$job_id/;
267
+ proxy_set_header authorization $bearer_token_on_patch_and_get;
268
+ proxy_set_header caller-job-uuid "{self._job_uuid}";
269
+ proxy_set_header cookie "";
270
+ proxy_ssl_server_name on;
271
+ }}
272
+
273
+ location ~* "^/api/jobs/create_job_with_data/$" {{
274
+ # Note: Using $1 here as URI part from regex must be used for proxy_pass
275
+ proxy_pass https://$upstream_hostname/api/jobs/create_job_with_data/$1;
276
+ proxy_set_header authorization $bearer_token_on_post;
277
+ proxy_set_header caller-job-uuid "{self._job_uuid}";
278
+ proxy_set_header cookie "";
279
+ proxy_ssl_server_name on;
280
+ }}
281
+
282
+ location ~* "^/api/jobs/$" {{
283
+ # Note: Using $1 here as URI part from regex must be used for proxy_pass
284
+ proxy_pass https://$upstream_hostname/api/jobs/$1;
285
+ proxy_set_header authorization $bearer_token_on_post;
286
+ proxy_set_header caller-job-uuid "{self._job_uuid}";
287
+ proxy_set_header cookie "";
288
+ proxy_ssl_server_name on;
289
+ }}
290
+
291
+ location ~ "^/api/jobs/{self._job_uuid}/notes/$" {{
292
+ # Note: Using $1 here as URI part from regex must be used for proxy_pass
293
+ proxy_pass https://$upstream_hostname/api/jobs/{self._job_uuid}/notes/$1;
294
+ proxy_set_header authorization "";
295
+ proxy_set_header job-auth-token "";
296
+ proxy_set_header compute-node-auth-token "{compute_node_auth_token}";
297
+ proxy_set_header compute-node-uuid "{compute_node_uuid}";
298
+ proxy_set_header cookie "";
299
+ proxy_ssl_server_name on;
300
+ }}
301
+
302
+ location ~ "^/api/auth/oauth-token-exchange/$" {{
303
+ # Note: Using $1 here as URI part from regex must be used for proxy_pass
304
+ proxy_pass https://$upstream_hostname/api/auth/oauth-token-exchange/$1;
305
+ proxy_set_header authorization "";
306
+ proxy_set_header compute-node-auth-token "{compute_node_auth_token}";
307
+ proxy_set_header job-uuid "{self._job_uuid}";
308
+ proxy_set_header cookie "";
309
+ proxy_ssl_server_name on;
310
+ }}
311
+
312
+ location /api/lfs/ {{
313
+ proxy_pass https://$upstream_hostname$request_uri;
314
+ proxy_set_header authorization "";
315
+ proxy_set_header compute-node-auth-token "{compute_node_auth_token}";
316
+ proxy_set_header job-uuid "{self._job_uuid}";
317
+ proxy_set_header cookie "";
318
+ proxy_ssl_server_name on;
319
+ }}
320
+
321
+ location /api/app/ {{
322
+ proxy_pass https://$upstream_hostname$request_uri;
323
+ proxy_set_header authorization "";
324
+ proxy_set_header compute-node-auth-token "{compute_node_auth_token}";
325
+ proxy_set_header job-uuid "{self._job_uuid}";
326
+ proxy_set_header cookie "";
327
+ proxy_ssl_server_name on;
328
+ }}
329
+
330
+ location /api/resource/ {{
331
+ proxy_pass https://$upstream_hostname$request_uri;
332
+ proxy_set_header authorization "";
333
+ proxy_set_header compute-node-auth-token "{compute_node_auth_token}";
334
+ proxy_set_header job-uuid "{self._job_uuid}";
335
+ proxy_set_header cookie "";
336
+ proxy_ssl_server_name on;
337
+ }}
338
+
339
+ location /api/resources/data-records/ {{
340
+ proxy_pass https://$upstream_hostname$request_uri;
341
+ proxy_set_header authorization "";
342
+ proxy_set_header compute-node-auth-token "{compute_node_auth_token}";
343
+ proxy_set_header job-uuid "{self._job_uuid}";
344
+ proxy_set_header cookie "";
345
+ proxy_ssl_server_name on;
346
+ }}
347
+
348
+ location /api/resources/versions/ {{
349
+ proxy_pass https://$upstream_hostname$request_uri;
350
+ proxy_set_header authorization "";
351
+ proxy_set_header compute-node-auth-token "{compute_node_auth_token}";
352
+ proxy_set_header job-uuid "{self._job_uuid}";
353
+ proxy_set_header cookie "";
354
+ proxy_ssl_server_name on;
355
+ }}
356
+
357
+ location /api/proxy/files/data-record-versions/ {{
358
+ proxy_pass https://$upstream_hostname$request_uri;
359
+ proxy_set_header authorization "";
360
+ proxy_set_header compute-node-auth-token "{compute_node_auth_token}";
361
+ proxy_set_header job-uuid "{self._job_uuid}";
362
+ proxy_set_header cookie "";
363
+ proxy_ssl_server_name on;
364
+ }}
365
+
366
+ location /api/proxy/index/ {{
367
+ proxy_pass https://$upstream_hostname$request_uri;
368
+ proxy_set_header authorization "{biolib_index_auth_header_value}";
369
+ proxy_set_header cookie "";
370
+ proxy_ssl_server_name on;
371
+ }}
372
+
373
+ location ~* "^/api/accounts/(?<account_id>[a-z0-9-]{{36}})/metrics/jobs/$" {{
374
+ proxy_pass https://$upstream_hostname/api/accounts/$account_id/metrics/jobs/$is_args$args;
375
+ proxy_set_header authorization "";
376
+ proxy_set_header compute-node-auth-token "{compute_node_auth_token}";
377
+ proxy_set_header job-uuid "{self._job_uuid}";
378
+ proxy_set_header cookie "";
379
+ proxy_ssl_server_name on;
380
+ }}
381
+
382
+ location /api/ {{
383
+ proxy_pass https://$upstream_hostname$request_uri;
384
+ proxy_set_header authorization "";
385
+ proxy_set_header cookie "";
386
+ proxy_ssl_server_name on;
387
+ }}
388
+
389
+ location /proxy/storage/job-storage/ {{
390
+ proxy_pass https://$upstream_hostname$request_uri;
391
+ proxy_set_header authorization "";
392
+ proxy_set_header cookie "";
393
+ proxy_ssl_server_name on;
394
+ }}
395
+
396
+ location /proxy/storage/lfs/versions/ {{
397
+ proxy_pass https://$upstream_hostname$request_uri;
398
+ proxy_set_header authorization "";
399
+ proxy_set_header cookie "";
400
+ proxy_ssl_server_name on;
401
+ }}
402
+
403
+ location /proxy/cloud/ {{
404
+ proxy_pass https://$upstream_hostname$request_uri;
405
+ proxy_set_header authorization "";
406
+ proxy_set_header cookie "";
407
+ proxy_ssl_server_name on;
408
+ }}
409
+
410
+ location / {{
411
+ return 404 "Not found";
412
+ }}
413
+ }}
157
414
 
158
- docker = BiolibDockerClient.get_docker_client()
159
- nginx_config = f'''
160
- events {{}}
161
- error_log /dev/stdout info;
162
- stream {{
163
415
  server {{
164
- listen {self._remote_host['port']};
165
- proxy_pass {upstream_server_name}:{upstream_server_port};
416
+ listen 1080;
417
+ resolver 127.0.0.11 ipv6=off valid=30s;
418
+
419
+ if ($http_biolib_result_uuid != "{self._job_uuid}") {{
420
+ return 403 "Invalid or missing biolib-result-uuid header";
421
+ }}
422
+
423
+ if ($http_biolib_result_port = "") {{
424
+ return 400 "Missing biolib-result-port header";
425
+ }}
426
+
427
+ location / {{
428
+ proxy_pass http://main:$http_biolib_result_port$request_uri;
429
+ proxy_set_header Host $http_host;
430
+ proxy_set_header biolib-result-uuid "";
431
+ proxy_set_header biolib-result-port "";
432
+ proxy_pass_request_headers on;
433
+ }}
166
434
  }}
167
435
  }}
436
+ """
437
+ else:
438
+ port_to_mappings: Dict[int, List[RemoteHostMapping]] = {}
439
+ for mapping in self._remote_host_mappings:
440
+ for port in mapping.ports:
441
+ if port not in port_to_mappings:
442
+ port_to_mappings[port] = []
443
+ port_to_mappings[port].append(mapping)
444
+
445
+ nginx_config = """
446
+ events {}
447
+ error_log /dev/stdout info;
448
+ stream {
449
+ resolver 127.0.0.11 valid=30s;"""
450
+
451
+ for port, mappings in port_to_mappings.items():
452
+ nginx_config += f"""
453
+ map $server_addr $backend_{port} {{"""
454
+ for mapping in mappings:
455
+ nginx_config += f'\n {mapping.static_ip} {mapping.hostname}:{port};'
456
+
457
+ nginx_config += f"""
458
+ }}
459
+ server {{
460
+ listen 0.0.0.0:{port};
461
+ proxy_pass $backend_{port};
462
+ }}
463
+ server {{
464
+ listen 0.0.0.0:{port} udp;
465
+ proxy_pass $backend_{port};
466
+ }}"""
467
+
468
+ nginx_config += """
469
+ }
470
+ """
168
471
 
169
- '''
170
472
  nginx_config_bytes = nginx_config.encode()
171
473
  tarfile_in_memory = io.BytesIO()
172
474
  with tarfile.open(fileobj=tarfile_in_memory, mode='w:gz') as tar:
@@ -1,36 +1,41 @@
1
- from enum import Enum
2
1
  import random
3
2
  import string
3
+ from enum import Enum
4
4
 
5
5
  from biolib.biolib_logging import logger
6
6
 
7
+ BIOLIB_PROXY_NETWORK_NAME = 'biolib-proxy-network'
8
+
7
9
 
8
10
  def get_package_type(package):
9
11
  package_type = int.from_bytes(package[1:2], 'big')
10
12
  if package_type == 1:
11
13
  return 'ModuleInput'
12
14
  elif package_type == 2:
13
- return 'ModuleOutput'
14
- elif package_type == 3: # Not used in the compute node
15
- return 'ModuleSource'
15
+ return 'ModuleOutput' # Note: This package is deprecated
16
+ elif package_type == 3:
17
+ return 'ModuleSource' # Note: This package is deprecated
16
18
  elif package_type == 4:
17
- return 'AttestationDocument'
19
+ return 'AttestationDocument' # Note: This package is deprecated
18
20
  elif package_type == 5:
19
21
  return 'SavedJob'
20
22
  elif package_type == 6:
21
- return 'RsaEncryptedAesPackage'
23
+ return 'RsaEncryptedAesPackage' # Note: This package is deprecated
22
24
  elif package_type == 7:
23
- return 'AesEncryptedPackage'
25
+ return 'AesEncryptedPackage' # Note: This package is deprecated
24
26
  elif package_type == 8:
25
27
  return 'SystemStatusUpdate'
26
28
  elif package_type == 9:
27
29
  return 'SystemException'
30
+ elif package_type == 10:
31
+ return 'StdoutAndStderr'
28
32
 
29
33
  else:
30
- raise Exception(f"Unexpected package type {package_type}")
34
+ raise Exception(f'Unexpected package type {package_type}')
31
35
 
32
36
 
33
37
  class SystemExceptionCodes(Enum):
38
+ COMPLETED_SUCCESSFULLY = 0
34
39
  FAILED_TO_INIT_COMPUTE_PROCESS_VARIABLES = 1
35
40
  FAILED_TO_CONNECT_TO_WORKER_THREAD_SOCKET = 2
36
41
  FAILED_TO_START_SENDER_THREAD_OR_RECEIVER_THREAD = 3
@@ -55,13 +60,22 @@ class SystemExceptionCodes(Enum):
55
60
  FAILED_TO_RETRIEVE_AND_MAP_OUTPUT_FILES = 22
56
61
  FAILED_TO_SERIALIZE_AND_SEND_MODULE_OUTPUT = 23
57
62
  FAILED_TO_DESERIALIZE_SAVED_JOB = 24
58
- UNKOWN_COMPUTE_PROCESS_ERROR = 25
63
+ UNKNOWN_COMPUTE_PROCESS_ERROR = 25
59
64
  FAILED_TO_INITIALIZE_WORKER_THREAD = 26
60
65
  FAILED_TO_HANDLE_PACKAGE_IN_WORKER_THREAD = 27
61
66
  EXCEEDED_MAX_JOB_RUNTIME = 28
67
+ OUT_OF_MEMORY = 29
68
+ CANCELLED_BY_USER = 30
69
+ COMMAND_OVERRIDE_NOT_ALLOWED = 31
70
+ FAILED_TO_ALLOCATE_JOB_TO_COMPUTE_NODE = 32
71
+ NO_MODULES_FOUND_ON_JOB = 33
72
+ FAILED_TO_INITIALIZE_DOCKER_EXECUTOR = 34
73
+ COMPUTE_NODE_SHUTDOWN = 35
74
+ COMPUTE_NODE_SHUTDOWN_DUE_TO_HEALTH_CHECK_FAILURE = 36
62
75
 
63
76
 
64
77
  SystemExceptionCodeMap = {
78
+ 0: 'Job completed successfully',
65
79
  1: 'Failed to init compute process variables',
66
80
  2: 'Failed to connect to worker thread socket',
67
81
  3: 'Failed to start sender or receiver thread',
@@ -90,6 +104,14 @@ SystemExceptionCodeMap = {
90
104
  26: 'Failed to initialize worker thread',
91
105
  27: 'Failed to handle package in worker thread',
92
106
  28: 'Job exceeded max run time',
107
+ 29: 'Container ran out of memory',
108
+ 30: 'Job was cancelled by the user',
109
+ 31: 'The application does not allow the arguments to override the command',
110
+ 32: 'Failed to allocate job to compute node',
111
+ 33: 'No modules found on job',
112
+ 34: 'Failed to initialize docker executor',
113
+ 35: 'Compute node shutdown',
114
+ 36: 'Compute node shutdown due to health check failure',
93
115
  }
94
116
 
95
117