matlab-proxy 0.20.0__py3-none-any.whl → 0.21.0__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.
Potentially problematic release.
This version of matlab-proxy might be problematic. Click here for more details.
- matlab_proxy/gui/asset-manifest.json +6 -6
- matlab_proxy/gui/index.html +1 -1
- matlab_proxy/gui/static/css/main.6cd0caba.css +13 -0
- matlab_proxy/gui/static/css/main.6cd0caba.css.map +1 -0
- matlab_proxy/gui/static/js/main.61c661b8.js +3 -0
- matlab_proxy/gui/static/js/{main.9c68c75c.js.LICENSE.txt → main.61c661b8.js.LICENSE.txt} +0 -2
- matlab_proxy/gui/static/js/main.61c661b8.js.map +1 -0
- matlab_proxy/settings.py +1 -1
- {matlab_proxy-0.20.0.dist-info → matlab_proxy-0.21.0.dist-info}/METADATA +2 -1
- {matlab_proxy-0.20.0.dist-info → matlab_proxy-0.21.0.dist-info}/RECORD +32 -15
- {matlab_proxy-0.20.0.dist-info → matlab_proxy-0.21.0.dist-info}/entry_points.txt +1 -0
- matlab_proxy-0.21.0.dist-info/top_level.txt +3 -0
- matlab_proxy_manager/__init__.py +6 -0
- matlab_proxy_manager/lib/__init__.py +1 -0
- matlab_proxy_manager/lib/api.py +295 -0
- matlab_proxy_manager/storage/__init__.py +1 -0
- matlab_proxy_manager/storage/file_repository.py +144 -0
- matlab_proxy_manager/storage/interface.py +62 -0
- matlab_proxy_manager/storage/server.py +144 -0
- matlab_proxy_manager/utils/__init__.py +1 -0
- matlab_proxy_manager/utils/auth.py +77 -0
- matlab_proxy_manager/utils/constants.py +5 -0
- matlab_proxy_manager/utils/environment_variables.py +46 -0
- matlab_proxy_manager/utils/helpers.py +284 -0
- matlab_proxy_manager/utils/logger.py +73 -0
- matlab_proxy_manager/web/__init__.py +1 -0
- matlab_proxy_manager/web/app.py +447 -0
- matlab_proxy_manager/web/monitor.py +45 -0
- matlab_proxy_manager/web/watcher.py +54 -0
- tests/unit/test_app.py +1 -1
- matlab_proxy/gui/static/css/main.da9c4eb8.css +0 -13
- matlab_proxy/gui/static/css/main.da9c4eb8.css.map +0 -1
- matlab_proxy/gui/static/js/main.9c68c75c.js +0 -3
- matlab_proxy/gui/static/js/main.9c68c75c.js.map +0 -1
- matlab_proxy-0.20.0.dist-info/top_level.txt +0 -2
- {matlab_proxy-0.20.0.dist-info → matlab_proxy-0.21.0.dist-info}/LICENSE.md +0 -0
- {matlab_proxy-0.20.0.dist-info → matlab_proxy-0.21.0.dist-info}/WHEEL +0 -0
matlab_proxy/settings.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: matlab-proxy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.21.0
|
|
4
4
|
Summary: Python® package enables you to launch MATLAB® and access it from a web browser.
|
|
5
5
|
Home-page: https://github.com/mathworks/matlab-proxy/
|
|
6
6
|
Author: The MathWorks, Inc.
|
|
@@ -22,6 +22,7 @@ Requires-Dist: aiohttp-session[secure]
|
|
|
22
22
|
Requires-Dist: importlib-metadata
|
|
23
23
|
Requires-Dist: importlib-resources
|
|
24
24
|
Requires-Dist: psutil
|
|
25
|
+
Requires-Dist: watchdog
|
|
25
26
|
Provides-Extra: dev
|
|
26
27
|
Requires-Dist: aiohttp-devtools; extra == "dev"
|
|
27
28
|
Requires-Dist: black; extra == "dev"
|
|
@@ -4,21 +4,21 @@ matlab_proxy/app_state.py,sha256=pt6s3Yy6NV6LB-GxT1Y4cOpeDaTFMm_ItN_qPstDTwE,712
|
|
|
4
4
|
matlab_proxy/constants.py,sha256=-gEUNjBATe3_4Cd7CN-VhkEn-iMRfiZ-zdDDD6fM_II,1161
|
|
5
5
|
matlab_proxy/default_configuration.py,sha256=DxQaHzAivzstiPl_nDfxs8SOyP9oaK9v3RP4LtroJl4,843
|
|
6
6
|
matlab_proxy/devel.py,sha256=nR6XPVBUEdQ-RZGtYvX1YHTp8gj9cuw5Hp8ahasMBc8,14310
|
|
7
|
-
matlab_proxy/settings.py,sha256=
|
|
7
|
+
matlab_proxy/settings.py,sha256=63pyCUvT7WLBRUpUr3F4r4nL13pQ2Hn6-oZODAhuGLo,26722
|
|
8
8
|
matlab_proxy/gui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
matlab_proxy/gui/asset-manifest.json,sha256=
|
|
9
|
+
matlab_proxy/gui/asset-manifest.json,sha256=IarJ9XmKoHJovFfsx1gHk1frgKLGgyCzzYmZNz1DT3A,3516
|
|
10
10
|
matlab_proxy/gui/favicon.ico,sha256=7w7Ki1uQP2Rgwc64dOV4-NrTu97I3WsZw8OvRSoY1A0,130876
|
|
11
|
-
matlab_proxy/gui/index.html,sha256=
|
|
11
|
+
matlab_proxy/gui/index.html,sha256=BvGtBVGQxdCjSKGhUFEXZPK3SaFXB7TF3gbFxyx7miQ,637
|
|
12
12
|
matlab_proxy/gui/manifest.json,sha256=NwDbrALM5auYyj2bbEf4aGwAUDqNl1FzMFQpPiG2Ty4,286
|
|
13
13
|
matlab_proxy/gui/robots.txt,sha256=kNJLw79pisHhc3OVAimMzKcq3x9WT6sF9IS4xI0crdI,67
|
|
14
14
|
matlab_proxy/gui/static/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
15
|
matlab_proxy/gui/static/css/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
-
matlab_proxy/gui/static/css/main.
|
|
17
|
-
matlab_proxy/gui/static/css/main.
|
|
16
|
+
matlab_proxy/gui/static/css/main.6cd0caba.css,sha256=Ks90yx_8kQC9UQXlkvh5ecz2vk2jEN2FKkMCLn0hTb8,269168
|
|
17
|
+
matlab_proxy/gui/static/css/main.6cd0caba.css.map,sha256=-pczDWw0U0T9Yd7pVYDklDlsc6OJqxCYlGmS6qtAhqU,350517
|
|
18
18
|
matlab_proxy/gui/static/js/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
|
-
matlab_proxy/gui/static/js/main.
|
|
20
|
-
matlab_proxy/gui/static/js/main.
|
|
21
|
-
matlab_proxy/gui/static/js/main.
|
|
19
|
+
matlab_proxy/gui/static/js/main.61c661b8.js,sha256=w44nS4qbZOF7mS5ASEhmhDPLk1KPDP4f4qF3eNZRzBg,283166
|
|
20
|
+
matlab_proxy/gui/static/js/main.61c661b8.js.LICENSE.txt,sha256=uJRPpXtA1Wzfw2dMAUGOPgwqYk2GvDcIafoGMMNLECQ,1539
|
|
21
|
+
matlab_proxy/gui/static/js/main.61c661b8.js.map,sha256=02MznaDjhNTG_3yo4quVaeyxtnsJVacK_LbN4pTXJdA,911172
|
|
22
22
|
matlab_proxy/gui/static/media/MATLAB-env-blur.4fc94edbc82d3184e5cb.png,sha256=QpmQTLDvBu2-b7ev83Rvpt0Q72R6wdQGkuJMPPpjv7M,220290
|
|
23
23
|
matlab_proxy/gui/static/media/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
24
|
matlab_proxy/gui/static/media/arrow.0c2968b90bd9a64c8c3f.svg,sha256=XtmvDWzGZnwCZm08TKBnqt5hc1wphJnNupG0Fx_faAY,327
|
|
@@ -71,6 +71,23 @@ matlab_proxy/util/mwi/validators.py,sha256=br-22Gyef_vxVSSH2Qkb97Nfph9sfl7fyhQ8-
|
|
|
71
71
|
matlab_proxy/util/mwi/embedded_connector/__init__.py,sha256=Vfl2hNC7V1IwoK9_wrwfENs4BC8P-Mvvqh4BNGi2n48,119
|
|
72
72
|
matlab_proxy/util/mwi/embedded_connector/helpers.py,sha256=aOn-AvcDy6jBQJIffiv_agIa4UVldAIl3--QnDpXWDM,3656
|
|
73
73
|
matlab_proxy/util/mwi/embedded_connector/request.py,sha256=-IzTDjy3qViHfLJpK3OnFtEyV7dgwJKPQAfav9lqILc,4317
|
|
74
|
+
matlab_proxy_manager/__init__.py,sha256=CMqm2aSYUWo5sxV3vyqWudrQU31muouSqZRDesJNJSA,178
|
|
75
|
+
matlab_proxy_manager/lib/__init__.py,sha256=KfwQxxM5a1kMRtNbhz8tb7YfHp8e2d0tNLB55wYvDS8,37
|
|
76
|
+
matlab_proxy_manager/lib/api.py,sha256=X-VSbZ41TPosqGODve8PSG6G9dZfqi0kmY_iGWFW3EA,10615
|
|
77
|
+
matlab_proxy_manager/storage/__init__.py,sha256=KfwQxxM5a1kMRtNbhz8tb7YfHp8e2d0tNLB55wYvDS8,37
|
|
78
|
+
matlab_proxy_manager/storage/file_repository.py,sha256=U4FAw0zFN9z7YNlaMsYZXWm5ccs3rp3bzZL-W2BNhxA,5187
|
|
79
|
+
matlab_proxy_manager/storage/interface.py,sha256=pnRRD0Ku3gzbruAOM3J3NI2Kk8do3-_yRw9Pag1IqnE,1883
|
|
80
|
+
matlab_proxy_manager/storage/server.py,sha256=kQ4jtG2xqCa8c7zqDBFYxOhxMxwZwqTIITBxcJRDcvE,4848
|
|
81
|
+
matlab_proxy_manager/utils/__init__.py,sha256=KfwQxxM5a1kMRtNbhz8tb7YfHp8e2d0tNLB55wYvDS8,37
|
|
82
|
+
matlab_proxy_manager/utils/auth.py,sha256=60vi16eQ7LWp3I4CNv2easTjObw50irEm518fiMA5YI,2526
|
|
83
|
+
matlab_proxy_manager/utils/constants.py,sha256=pyg-bkk6wWfmy60nvhroZDMZt__FcbZbuvU-b9m2Fkg,163
|
|
84
|
+
matlab_proxy_manager/utils/environment_variables.py,sha256=rbDeWnyJp77Yr6btK3eXKZQ5thwiwhOGZcvDetGPOH8,1436
|
|
85
|
+
matlab_proxy_manager/utils/helpers.py,sha256=5yTRM0NjGUC6lcQ6O8ilDrG2AnICGfvJYdIPho41Egw,9193
|
|
86
|
+
matlab_proxy_manager/utils/logger.py,sha256=GSRGD-yf518o-2b1BxEeJYuNiEz2eEqpl0Solqbwpb4,1869
|
|
87
|
+
matlab_proxy_manager/web/__init__.py,sha256=KfwQxxM5a1kMRtNbhz8tb7YfHp8e2d0tNLB55wYvDS8,37
|
|
88
|
+
matlab_proxy_manager/web/app.py,sha256=Lu6sl3Mft98oL-Z1NwBdmmG1OvnwYEN31pU2bOdzFi0,16572
|
|
89
|
+
matlab_proxy_manager/web/monitor.py,sha256=Gj0DxwX0c1PEAly5jWmuIGqNJYGDDjTkQIzbVXu4zCQ,1589
|
|
90
|
+
matlab_proxy_manager/web/watcher.py,sha256=aNa_UDwvzaZrIdHyvWGX7dILH199uc6xVH4odrKU5-E,1817
|
|
74
91
|
tests/integration/__init__.py,sha256=ttzJ8xKWGxOJZz56qOiWOn6sp5LGomkZMn_w4KJLRMU,42
|
|
75
92
|
tests/integration/integration_tests_with_license/__init__.py,sha256=vVYZCur-QhmIGCxUmn-WZjIywtDQidaLDmlmrRHRlgY,37
|
|
76
93
|
tests/integration/integration_tests_with_license/conftest.py,sha256=sCaIXB8d4vf05C7JWSVA7g5gnPjbpRq3dftuBpWyp1s,1599
|
|
@@ -83,7 +100,7 @@ tests/integration/utils/integration_tests_utils.py,sha256=IbJ9CedFHiz3k85FBY-8Gw
|
|
|
83
100
|
tests/integration/utils/licensing.py,sha256=rEBjvMXO8R3mL6KnePu2lojmOsjD4GXl9frf9N0Wacs,4842
|
|
84
101
|
tests/unit/__init__.py,sha256=KfwQxxM5a1kMRtNbhz8tb7YfHp8e2d0tNLB55wYvDS8,37
|
|
85
102
|
tests/unit/conftest.py,sha256=Hfxq3h8IZuLJkRMh5jdEFiq78CIAdKvm-6KryRDZ0FY,1918
|
|
86
|
-
tests/unit/test_app.py,sha256=
|
|
103
|
+
tests/unit/test_app.py,sha256=15kSWsfIEQpGzr6QoDoFpV501SYA5XkszrwDp8pJi74,38442
|
|
87
104
|
tests/unit/test_app_state.py,sha256=ZtM79HAe5W6pVVWV-NICjDJBlmuxJ4NnZn-urGGtH-E,33429
|
|
88
105
|
tests/unit/test_constants.py,sha256=2nXxTmDP8utr8krsfZ4c_Bh4_mWPcDO5uI8MXeq4Usg,158
|
|
89
106
|
tests/unit/test_ddux.py,sha256=a2J2iM8j_nnfJVuMI38p5AjwrRdoMj3N88gFgS2I4hg,713
|
|
@@ -104,9 +121,9 @@ tests/unit/util/mwi/embedded_connector/test_helpers.py,sha256=vYTWNUTuDeaygo16JG
|
|
|
104
121
|
tests/unit/util/mwi/embedded_connector/test_request.py,sha256=PR-jddnXDEiip-lD7A_QSvRwEkwo3eQ8owZlk-r9vnk,1867
|
|
105
122
|
tests/utils/__init__.py,sha256=ttzJ8xKWGxOJZz56qOiWOn6sp5LGomkZMn_w4KJLRMU,42
|
|
106
123
|
tests/utils/logging_util.py,sha256=VBy_NRvwau3C_CVTBjK5RMROrQimnJYHO2U0aKSZiRw,2234
|
|
107
|
-
matlab_proxy-0.
|
|
108
|
-
matlab_proxy-0.
|
|
109
|
-
matlab_proxy-0.
|
|
110
|
-
matlab_proxy-0.
|
|
111
|
-
matlab_proxy-0.
|
|
112
|
-
matlab_proxy-0.
|
|
124
|
+
matlab_proxy-0.21.0.dist-info/LICENSE.md,sha256=oF0h3UdSF-rlUiMGYwi086ZHqelzz7yOOk9HFDv9ELo,2344
|
|
125
|
+
matlab_proxy-0.21.0.dist-info/METADATA,sha256=gnssXFWPF13KwXQjNiEuEaaE1IN-zOHHslCQ8TnnpGU,10190
|
|
126
|
+
matlab_proxy-0.21.0.dist-info/WHEEL,sha256=eOLhNAGa2EW3wWl_TU484h7q1UNgy0JXjjoqKoxAAQc,92
|
|
127
|
+
matlab_proxy-0.21.0.dist-info/entry_points.txt,sha256=ZAlCUsgKzGcAeQaMZOq31FrTB5tQ8Ypq8Op_8U600-A,305
|
|
128
|
+
matlab_proxy-0.21.0.dist-info/top_level.txt,sha256=KF-347aoRGsfHTpiSqfIPUZ95bzK5-oMIu8S_TUcu-w,40
|
|
129
|
+
matlab_proxy-0.21.0.dist-info/RECORD,,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
[console_scripts]
|
|
2
2
|
matlab-proxy-app = matlab_proxy.app:main
|
|
3
3
|
matlab-proxy-app-list-servers = matlab_proxy.util.list_servers:print_server_info
|
|
4
|
+
matlab-proxy-manager-app = matlab_proxy_manager.web.app:main
|
|
4
5
|
|
|
5
6
|
[matlab_proxy_configs]
|
|
6
7
|
default_configuration_matlab_proxy = matlab_proxy.default_configuration:config
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Copyright 2024 The MathWorks, Inc.
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# Copyright 2024 The MathWorks, Inc.
|
|
2
|
+
import asyncio
|
|
3
|
+
import os
|
|
4
|
+
import secrets
|
|
5
|
+
import subprocess
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
import matlab_proxy
|
|
9
|
+
import matlab_proxy.util.system as mwi_sys
|
|
10
|
+
from matlab_proxy_manager.storage.file_repository import FileRepository
|
|
11
|
+
from matlab_proxy_manager.storage.server import ServerProcess
|
|
12
|
+
from matlab_proxy_manager.utils import constants, helpers, logger
|
|
13
|
+
|
|
14
|
+
# Used to list all the public-facing APIs exported by this module.
|
|
15
|
+
__all__ = ["shutdown", "start_matlab_proxy_for_kernel", "start_matlab_proxy_for_jsp"]
|
|
16
|
+
|
|
17
|
+
log = logger.get()
|
|
18
|
+
shutdown_lock = asyncio.Lock()
|
|
19
|
+
log = logger.get(init=True)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
async def start_matlab_proxy_for_kernel(
|
|
23
|
+
caller_id: str, parent_id: str, is_isolated_matlab: bool
|
|
24
|
+
):
|
|
25
|
+
"""
|
|
26
|
+
Starts a MATLAB proxy server specifically for MATLAB Kernel.
|
|
27
|
+
|
|
28
|
+
This function is a wrapper around the `start_matlab_proxy` function, with mpm_auth_token
|
|
29
|
+
set to None, for starting the MATLAB proxy server via proxy manager.
|
|
30
|
+
"""
|
|
31
|
+
return await _start_matlab_proxy(
|
|
32
|
+
caller_id=caller_id, ctx=parent_id, is_isolated_matlab=is_isolated_matlab
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
async def start_matlab_proxy_for_jsp(
|
|
37
|
+
parent_id: str, is_isolated_matlab: bool, mpm_auth_token: str
|
|
38
|
+
):
|
|
39
|
+
"""
|
|
40
|
+
Starts a MATLAB proxy server specifically for Jupyter Server Proxy (JSP) - Open MATLAB launcher.
|
|
41
|
+
|
|
42
|
+
This function is a wrapper around the `start_matlab_proxy` function, providing
|
|
43
|
+
a more specific context (mpm_auth_token) for starting the MATLAB proxy server via proxy manager.
|
|
44
|
+
"""
|
|
45
|
+
return await _start_matlab_proxy(
|
|
46
|
+
caller_id="jsp",
|
|
47
|
+
ctx=parent_id,
|
|
48
|
+
is_isolated_matlab=is_isolated_matlab,
|
|
49
|
+
mpm_auth_token=mpm_auth_token,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
async def _start_matlab_proxy(**options) -> Optional[dict]:
|
|
54
|
+
"""
|
|
55
|
+
Start a MATLAB proxy server.
|
|
56
|
+
|
|
57
|
+
This function starts a MATLAB proxy server based on the provided context and caller ID.
|
|
58
|
+
It handles the creation of new servers and the reuse of existing ones.
|
|
59
|
+
|
|
60
|
+
Args (keyword arguments):
|
|
61
|
+
- caller_id (str): The identifier for the caller (kernel id for kernels, "jsp" for JSP).
|
|
62
|
+
- ctx (str): The context in which the server is being started (parent pid).
|
|
63
|
+
- is_isolated_matlab (bool, optional): Whether to start an isolated MATLAB proxy instance.
|
|
64
|
+
Defaults to False.
|
|
65
|
+
- mpm_auth_token (str, optional): The MATLAB proxy manager token. If not provided,
|
|
66
|
+
a new token is generated. Defaults to None.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
ServerProcess: The process representing the MATLAB proxy server.
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
ValueError: If `caller_id` is "default" and `is_isolated_matlab` is True.
|
|
73
|
+
"""
|
|
74
|
+
caller_id: str = options.get("caller_id")
|
|
75
|
+
ctx: str = options.get("ctx")
|
|
76
|
+
is_isolated_matlab: bool = options.get("is_isolated_matlab", False)
|
|
77
|
+
mpm_auth_token: Optional[str] = options.get("mpm_auth_token", None)
|
|
78
|
+
|
|
79
|
+
if is_isolated_matlab and caller_id == "default":
|
|
80
|
+
raise ValueError(
|
|
81
|
+
"Caller id cannot be default when isolated_matlab is set to true"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
mpm_auth_token = mpm_auth_token or secrets.token_hex(32)
|
|
85
|
+
|
|
86
|
+
# Cleanup stale entries before starting new instance of matlab proxy server
|
|
87
|
+
helpers._are_orphaned_servers_deleted(ctx)
|
|
88
|
+
|
|
89
|
+
ident = caller_id if is_isolated_matlab else "default"
|
|
90
|
+
key = f"{ctx}_{ident}"
|
|
91
|
+
log.debug("Starting matlab proxy using %s, %s, %s", ctx, ident, is_isolated_matlab)
|
|
92
|
+
|
|
93
|
+
data_dir = helpers.create_and_get_proxy_manager_data_dir()
|
|
94
|
+
server_process = ServerProcess.find_existing_server(data_dir, key)
|
|
95
|
+
|
|
96
|
+
if server_process:
|
|
97
|
+
log.debug("Found existing server for aliasing")
|
|
98
|
+
|
|
99
|
+
# Create a backend file for this caller for reference tracking
|
|
100
|
+
helpers.create_state_file(data_dir, server_process, f"{ctx}_{caller_id}")
|
|
101
|
+
|
|
102
|
+
# Create a new matlab proxy server
|
|
103
|
+
else:
|
|
104
|
+
server_process: ServerProcess | None = (
|
|
105
|
+
await _start_subprocess_and_check_for_readiness(
|
|
106
|
+
ident, ctx, key, is_isolated_matlab, mpm_auth_token
|
|
107
|
+
)
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Store the newly created server into filesystem
|
|
111
|
+
if server_process:
|
|
112
|
+
helpers.create_state_file(data_dir, server_process, f"{ctx}_{caller_id}")
|
|
113
|
+
|
|
114
|
+
return server_process.as_dict() if server_process else None
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
async def _start_subprocess_and_check_for_readiness(
|
|
118
|
+
server_id: str, ctx: str, key: str, isolated: bool, mpm_auth_token: str
|
|
119
|
+
) -> Optional[ServerProcess]:
|
|
120
|
+
"""
|
|
121
|
+
Starts a MATLAB proxy server.
|
|
122
|
+
|
|
123
|
+
This function performs the following steps:
|
|
124
|
+
1. Prepares the command and environment variables required to start the MATLAB proxy server.
|
|
125
|
+
2. Initializes the MATLAB proxy process.
|
|
126
|
+
3. Checks if the MATLAB proxy server is ready.
|
|
127
|
+
4. Creates and returns a ServerProcess instance if the server is ready.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Optional[ServerProcess]: An instance of ServerProcess if the server is successfully started,
|
|
131
|
+
otherwise None.
|
|
132
|
+
"""
|
|
133
|
+
log.debug("Starting new matlab proxy server")
|
|
134
|
+
|
|
135
|
+
# Prepare matlab proxy command and required environment variables
|
|
136
|
+
matlab_proxy_cmd, matlab_proxy_env = _prepare_cmd_and_env_for_matlab_proxy()
|
|
137
|
+
|
|
138
|
+
# Start the matlab proxy process
|
|
139
|
+
process_id, url, mwi_base_url = await _start_subprocess(
|
|
140
|
+
matlab_proxy_cmd, matlab_proxy_env, server_id
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
server_process = None
|
|
144
|
+
|
|
145
|
+
# Check for the matlab proxy server readiness
|
|
146
|
+
if helpers.is_server_ready(f"{url}{mwi_base_url}"):
|
|
147
|
+
log.debug("Matlab proxy process info: %s, %s", url, mwi_base_url)
|
|
148
|
+
server_process = ServerProcess(
|
|
149
|
+
server_url=url,
|
|
150
|
+
mwi_base_url=mwi_base_url,
|
|
151
|
+
headers=helpers.convert_mwi_env_vars_to_header_format(
|
|
152
|
+
matlab_proxy_env, "MWI"
|
|
153
|
+
),
|
|
154
|
+
pid=process_id,
|
|
155
|
+
parent_pid=ctx,
|
|
156
|
+
id=key,
|
|
157
|
+
type="named" if isolated else "shared",
|
|
158
|
+
mpm_auth_token=mpm_auth_token,
|
|
159
|
+
)
|
|
160
|
+
else:
|
|
161
|
+
log.error("Could not start matlab proxy")
|
|
162
|
+
|
|
163
|
+
return server_process
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _prepare_cmd_and_env_for_matlab_proxy():
|
|
167
|
+
"""
|
|
168
|
+
Prepare the command and environment variables for starting the MATLAB proxy.
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
Tuple: A tuple containing the MATLAB proxy command and environment variables.
|
|
172
|
+
"""
|
|
173
|
+
from jupyter_matlab_proxy import config
|
|
174
|
+
|
|
175
|
+
# Get the command to start matlab-proxy
|
|
176
|
+
matlab_proxy_cmd: list = [
|
|
177
|
+
matlab_proxy.get_executable_name(),
|
|
178
|
+
"--config",
|
|
179
|
+
config.get("extension_name"),
|
|
180
|
+
]
|
|
181
|
+
|
|
182
|
+
input_env: dict = {
|
|
183
|
+
"MWI_AUTH_TOKEN": secrets.token_urlsafe(32),
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
matlab_proxy_env: dict = os.environ.copy()
|
|
187
|
+
matlab_proxy_env.update(input_env)
|
|
188
|
+
|
|
189
|
+
return matlab_proxy_cmd, matlab_proxy_env
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
async def _start_subprocess(cmd, env, server_id) -> Optional[int]:
|
|
193
|
+
"""
|
|
194
|
+
Initializes and starts a subprocess using the specified command and provided environment.
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
Optional[int]: The process ID if the process is successfully created, otherwise None.
|
|
198
|
+
"""
|
|
199
|
+
process = None
|
|
200
|
+
mwi_base_url: str = f"{constants.MWI_BASE_URL_PREFIX}{server_id}"
|
|
201
|
+
|
|
202
|
+
# Get a free port, closer to starting the matlab proxy appx
|
|
203
|
+
port: str = helpers.find_free_port()
|
|
204
|
+
env.update(
|
|
205
|
+
{
|
|
206
|
+
"MWI_APP_PORT": port,
|
|
207
|
+
"MWI_BASE_URL": mwi_base_url,
|
|
208
|
+
}
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
# Using loopback address so that DNS resolution doesn't add latency in Windows
|
|
212
|
+
url: str = f"http://127.0.0.1:{port}"
|
|
213
|
+
|
|
214
|
+
if mwi_sys.is_posix():
|
|
215
|
+
process = await asyncio.create_subprocess_exec(
|
|
216
|
+
*cmd,
|
|
217
|
+
env=env,
|
|
218
|
+
)
|
|
219
|
+
log.debug("Started matlab proxy subprocess for posix")
|
|
220
|
+
else:
|
|
221
|
+
process = subprocess.Popen(
|
|
222
|
+
cmd,
|
|
223
|
+
env=env,
|
|
224
|
+
)
|
|
225
|
+
log.debug("Started matlab proxy subprocess for windows")
|
|
226
|
+
|
|
227
|
+
if not process:
|
|
228
|
+
log.error("Matlab proxy process not created %d", process.returncode)
|
|
229
|
+
return None
|
|
230
|
+
|
|
231
|
+
process_pid = process.pid
|
|
232
|
+
log.debug("MATLAB proxy info: pid = %s, rc = %s", process_pid, process.returncode)
|
|
233
|
+
return process_pid, url, mwi_base_url
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
async def shutdown(parent_pid: str, caller_id: str, mpm_auth_token: str):
|
|
237
|
+
"""
|
|
238
|
+
Shutdown the MATLAB proxy server if the provided authentication token is valid.
|
|
239
|
+
|
|
240
|
+
This function attempts to shut down the MATLAB proxy server identified by the
|
|
241
|
+
given context and ID, provided the correct authentication token is supplied.
|
|
242
|
+
It ensures that the shutdown process is thread-safe using an asyncio lock.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
parent_pid (str): The context identifier for the server.
|
|
246
|
+
caller_id (str): The unique identifier for the server.
|
|
247
|
+
mpm_auth_token (str): The authentication token for proxy manager and client communication.
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
Optional[None]: Returns None if the shutdown process is successful or if
|
|
251
|
+
required arguments are missing.
|
|
252
|
+
|
|
253
|
+
Raises:
|
|
254
|
+
FileNotFoundError: If the state file for the server does not exist.
|
|
255
|
+
ValueError: If the authentication token is invalid.
|
|
256
|
+
Exception: If any other error occurs during the shutdown process.
|
|
257
|
+
"""
|
|
258
|
+
if not parent_pid or not caller_id or not mpm_auth_token:
|
|
259
|
+
log.debug(
|
|
260
|
+
"Required arguments (parent_pid | caller_id | mpm_auth_token) for shutdown missing"
|
|
261
|
+
)
|
|
262
|
+
return
|
|
263
|
+
|
|
264
|
+
try:
|
|
265
|
+
data_dir = helpers.create_and_get_proxy_manager_data_dir()
|
|
266
|
+
storage = FileRepository(data_dir)
|
|
267
|
+
filename = f"{parent_pid}_{caller_id}"
|
|
268
|
+
full_file_path, server = storage.get(filename)
|
|
269
|
+
|
|
270
|
+
if not server:
|
|
271
|
+
log.debug("State file for this server not found, filename: %s", filename)
|
|
272
|
+
return
|
|
273
|
+
|
|
274
|
+
if mpm_auth_token != server.mpm_auth_token:
|
|
275
|
+
raise ValueError("Invalid authentication token")
|
|
276
|
+
|
|
277
|
+
# Using asyncio lock to ensure thread-safe shutdown: clicking shutdown
|
|
278
|
+
# all on Kernel UI sends shutdown request in parallel which could lead
|
|
279
|
+
# to a scenario where the kernels' shutdown just cleans the files from
|
|
280
|
+
# filesystem and doesn't shut down the backend matlab proxy server.
|
|
281
|
+
async with shutdown_lock:
|
|
282
|
+
if helpers.is_only_reference(full_file_path):
|
|
283
|
+
server.shutdown()
|
|
284
|
+
|
|
285
|
+
# Delete the file for this server
|
|
286
|
+
storage.delete(f"{filename}.info")
|
|
287
|
+
except FileNotFoundError as e:
|
|
288
|
+
log.error("State file for server %s not found: %s", filename, e)
|
|
289
|
+
return
|
|
290
|
+
except ValueError as e:
|
|
291
|
+
log.error("Authentication error for server %s: %s", filename, e)
|
|
292
|
+
return
|
|
293
|
+
except Exception as e:
|
|
294
|
+
log.error("Error during shutdown of server %s: %s", filename, e)
|
|
295
|
+
raise
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Copyright 2024 The MathWorks, Inc.
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# Copyright 2024 The MathWorks, Inc.
|
|
2
|
+
import glob
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from matlab_proxy_manager.utils import logger
|
|
9
|
+
|
|
10
|
+
from .interface import IRepository
|
|
11
|
+
|
|
12
|
+
log = logger.get()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class FileRepository(IRepository):
|
|
16
|
+
"""
|
|
17
|
+
A repository for managing MATLAB proxy server processes using the file system.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, data_dir) -> None:
|
|
21
|
+
super().__init__()
|
|
22
|
+
self.data_dir = data_dir
|
|
23
|
+
self.encoding = "utf-8"
|
|
24
|
+
|
|
25
|
+
def get_all(self):
|
|
26
|
+
"""Retrieves all server processes from the repository.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
A dictionary mapping file paths to server process instances.
|
|
30
|
+
"""
|
|
31
|
+
from matlab_proxy_manager.storage.server import ServerProcess
|
|
32
|
+
|
|
33
|
+
servers = {}
|
|
34
|
+
|
|
35
|
+
# Read all the files in data_dir
|
|
36
|
+
all_files = glob.glob(f"{self.data_dir}/**/*.info", recursive=True)
|
|
37
|
+
|
|
38
|
+
for file in all_files:
|
|
39
|
+
try:
|
|
40
|
+
with open(file, "r", encoding=self.encoding) as f:
|
|
41
|
+
data = f.read().strip()
|
|
42
|
+
|
|
43
|
+
# Convert the content of each file to ServerProcess
|
|
44
|
+
if data:
|
|
45
|
+
server_process = ServerProcess.instantiate_from_string(data)
|
|
46
|
+
servers[file] = server_process
|
|
47
|
+
except Exception as ex:
|
|
48
|
+
log.debug("ServerProcess instantiation failed for %s: %s", file, ex)
|
|
49
|
+
return servers
|
|
50
|
+
|
|
51
|
+
def get(self, name) -> tuple:
|
|
52
|
+
"""
|
|
53
|
+
Retrieves a server process from the repository by its filename.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
name (str): The name of the server process file.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Tuple[Optional[str], Optional[ServerProcess]]: A tuple containing the file path
|
|
60
|
+
and the server process instance.
|
|
61
|
+
"""
|
|
62
|
+
from matlab_proxy_manager.storage.server import ServerProcess
|
|
63
|
+
|
|
64
|
+
server_process = None
|
|
65
|
+
full_file_path: Optional[str] = None
|
|
66
|
+
current_files = glob.glob(f"{self.data_dir}/**/{name}.info", recursive=True)
|
|
67
|
+
if current_files:
|
|
68
|
+
full_file_path = current_files[0]
|
|
69
|
+
with open(full_file_path, "r", encoding=self.encoding) as f:
|
|
70
|
+
try:
|
|
71
|
+
data = f.read().strip()
|
|
72
|
+
if data:
|
|
73
|
+
server_process = ServerProcess.instantiate_from_string(data)
|
|
74
|
+
except Exception as ex:
|
|
75
|
+
log.debug(
|
|
76
|
+
"ServerProcess instantiation failed for %s: %s",
|
|
77
|
+
full_file_path,
|
|
78
|
+
ex,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
return full_file_path, server_process
|
|
82
|
+
|
|
83
|
+
def add(self, server, filename: str) -> None:
|
|
84
|
+
"""
|
|
85
|
+
Adds a server process to the repository.
|
|
86
|
+
Creates a directory like <ctx>_default|<kernel_id> and then creates a file as
|
|
87
|
+
default|<kernel_id>.info in that dir
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
server (ServerProcess): The server process instance to add.
|
|
91
|
+
filename (str): The filename to associate with the server process.
|
|
92
|
+
"""
|
|
93
|
+
# Creates a child dir under the data_dir
|
|
94
|
+
server_dir = Path(f"{self.data_dir}", f"{server.id}")
|
|
95
|
+
Path.mkdir(server_dir, parents=True, exist_ok=True)
|
|
96
|
+
server_dict = {}
|
|
97
|
+
|
|
98
|
+
server_file = Path(server_dir, f"{filename}.info")
|
|
99
|
+
with open(server_file, "w", encoding=self.encoding) as f:
|
|
100
|
+
server_dict[server.id] = server.as_dict()
|
|
101
|
+
file_content = json.dumps(server_dict)
|
|
102
|
+
f.write(file_content)
|
|
103
|
+
|
|
104
|
+
def delete(self, filename: str) -> None:
|
|
105
|
+
"""
|
|
106
|
+
Deletes a server process from the repository by its filename.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
filename (str): The filename associated with the server process to delete.
|
|
110
|
+
"""
|
|
111
|
+
# <path to proxy manager dir>/<parent_pid>_<name>/<name>.info
|
|
112
|
+
full_file_path, parent_dir = FileRepository._find_file_and_get_parent(
|
|
113
|
+
self.data_dir, filename
|
|
114
|
+
)
|
|
115
|
+
if full_file_path:
|
|
116
|
+
Path(full_file_path).unlink(missing_ok=True)
|
|
117
|
+
log.debug("Deleted file: %s", filename)
|
|
118
|
+
|
|
119
|
+
# delete the sub-directory (<parent_pid>_<id>) only if it is empty
|
|
120
|
+
if parent_dir and not len(os.listdir(parent_dir)):
|
|
121
|
+
os.rmdir(parent_dir)
|
|
122
|
+
log.debug("Deleted dir: %s", parent_dir)
|
|
123
|
+
|
|
124
|
+
@staticmethod
|
|
125
|
+
def _find_file_and_get_parent(data_dir: str, filename: str):
|
|
126
|
+
"""
|
|
127
|
+
Finds the file and its parent directory in the given data directory.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
data_dir (str): The base directory to search within.
|
|
131
|
+
filename (str): The filename to search for.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
Tuple[Optional[str], Optional[str]]: A tuple containing the full file path and the parent directory.
|
|
135
|
+
"""
|
|
136
|
+
for dirpath, _, filenames in os.walk(data_dir):
|
|
137
|
+
# Check if target file is in the current directory's files
|
|
138
|
+
if filename in filenames:
|
|
139
|
+
full_path = os.path.join(dirpath, filename)
|
|
140
|
+
parent_dir = os.path.dirname(full_path)
|
|
141
|
+
return full_path, parent_dir
|
|
142
|
+
|
|
143
|
+
# Return None if the file was not found
|
|
144
|
+
return None, None
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Copyright 2024 The MathWorks, Inc.
|
|
2
|
+
from typing import Protocol
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class IRepository(Protocol):
|
|
6
|
+
"""
|
|
7
|
+
Protocol for a repository that manages MATLAB proxy server processes.
|
|
8
|
+
This protocol defines the required methods for adding, retrieving, and deleting
|
|
9
|
+
server process instances to the storage system (files).
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
def add(self, server, filename: str):
|
|
13
|
+
"""
|
|
14
|
+
Adds a server process to the repository.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
server (server.ServerProcess): The server process instance to add.
|
|
18
|
+
filename (str): The filename to associate with the server process.
|
|
19
|
+
|
|
20
|
+
Raises:
|
|
21
|
+
NotImplementedError
|
|
22
|
+
"""
|
|
23
|
+
raise NotImplementedError("add not implemented")
|
|
24
|
+
|
|
25
|
+
def get(self, name: str) -> tuple:
|
|
26
|
+
"""
|
|
27
|
+
Retrieves a server process from the repository by its filename.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
filename (str): The filename associated with the server process.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
tuple (str, server.ServerProcess): Full file path and the retrieved server process instance.
|
|
34
|
+
|
|
35
|
+
Raises:
|
|
36
|
+
NotImplementedError
|
|
37
|
+
"""
|
|
38
|
+
raise NotImplementedError("get not implemented")
|
|
39
|
+
|
|
40
|
+
def get_all(self):
|
|
41
|
+
"""
|
|
42
|
+
Retrieves all server processes from the repository.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Dict[str, server.ServerProcess]: Dict with filename as key and corresponding server process as value.
|
|
46
|
+
|
|
47
|
+
Raises:
|
|
48
|
+
NotImplementedError
|
|
49
|
+
"""
|
|
50
|
+
raise NotImplementedError("get_all not implemented")
|
|
51
|
+
|
|
52
|
+
def delete(self, filename: str) -> None:
|
|
53
|
+
"""
|
|
54
|
+
Deletes a server process from the repository by its filename.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
filename (str): The filename associated with the server process to delete.
|
|
58
|
+
|
|
59
|
+
Raises:
|
|
60
|
+
NotImplementedError
|
|
61
|
+
"""
|
|
62
|
+
raise NotImplementedError("delete not implemented")
|