mrok 0.3.0__py3-none-any.whl → 0.4.1__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 (47) hide show
  1. mrok/agent/devtools/__init__.py +0 -0
  2. mrok/agent/devtools/__main__.py +34 -0
  3. mrok/agent/devtools/inspector/__init__.py +0 -0
  4. mrok/agent/devtools/inspector/__main__.py +25 -0
  5. mrok/agent/devtools/inspector/app.py +556 -0
  6. mrok/agent/devtools/inspector/server.py +18 -0
  7. mrok/agent/sidecar/app.py +9 -10
  8. mrok/agent/sidecar/main.py +35 -16
  9. mrok/agent/ziticorn.py +27 -18
  10. mrok/cli/commands/__init__.py +2 -1
  11. mrok/cli/commands/agent/__init__.py +2 -0
  12. mrok/cli/commands/agent/dev/__init__.py +7 -0
  13. mrok/cli/commands/agent/dev/console.py +25 -0
  14. mrok/cli/commands/agent/dev/web.py +37 -0
  15. mrok/cli/commands/agent/run/asgi.py +35 -16
  16. mrok/cli/commands/agent/run/sidecar.py +29 -13
  17. mrok/cli/commands/agent/utils.py +5 -0
  18. mrok/cli/commands/controller/run.py +1 -5
  19. mrok/cli/commands/proxy/__init__.py +6 -0
  20. mrok/cli/commands/proxy/run.py +49 -0
  21. mrok/cli/utils.py +5 -0
  22. mrok/conf.py +6 -0
  23. mrok/controller/auth.py +2 -2
  24. mrok/datastructures.py +159 -0
  25. mrok/http/config.py +3 -6
  26. mrok/http/constants.py +22 -0
  27. mrok/http/forwarder.py +62 -23
  28. mrok/http/lifespan.py +29 -0
  29. mrok/http/middlewares.py +143 -0
  30. mrok/http/types.py +43 -0
  31. mrok/http/utils.py +90 -0
  32. mrok/logging.py +22 -0
  33. mrok/master.py +272 -0
  34. mrok/metrics.py +139 -0
  35. mrok/proxy/__init__.py +3 -0
  36. mrok/proxy/app.py +77 -0
  37. mrok/proxy/dataclasses.py +12 -0
  38. mrok/proxy/main.py +58 -0
  39. mrok/proxy/streams.py +124 -0
  40. mrok/proxy/types.py +12 -0
  41. mrok/proxy/ziti.py +173 -0
  42. {mrok-0.3.0.dist-info → mrok-0.4.1.dist-info}/METADATA +7 -1
  43. {mrok-0.3.0.dist-info → mrok-0.4.1.dist-info}/RECORD +46 -20
  44. mrok/http/master.py +0 -132
  45. {mrok-0.3.0.dist-info → mrok-0.4.1.dist-info}/WHEEL +0 -0
  46. {mrok-0.3.0.dist-info → mrok-0.4.1.dist-info}/entry_points.txt +0 -0
  47. {mrok-0.3.0.dist-info → mrok-0.4.1.dist-info}/licenses/LICENSE.txt +0 -0
mrok/proxy/ziti.py ADDED
@@ -0,0 +1,173 @@
1
+ """Ziti-backed connection manager for the proxy.
2
+
3
+ This manager owns creation of connections via an OpenZiti context, wraps
4
+ streams to observe IO errors, evicts idle entries, and serializes creation
5
+ per-key.
6
+ """
7
+
8
+ import asyncio
9
+ import logging
10
+ import time
11
+ from pathlib import Path
12
+
13
+ # typing imports intentionally minimized
14
+ import openziti
15
+
16
+ from mrok.http.types import StreamReader, StreamWriter
17
+ from mrok.proxy.dataclasses import CachedStreamEntry
18
+ from mrok.proxy.streams import CachedStreamReader, CachedStreamWriter
19
+ from mrok.proxy.types import CachedStream, ConnectionKey
20
+
21
+ logger = logging.getLogger("mrok.proxy")
22
+
23
+
24
+ class ZitiConnectionManager:
25
+ def __init__(
26
+ self,
27
+ identity_file: str | Path,
28
+ ziti_timeout_ms: int = 10000,
29
+ ttl_seconds: float = 60.0,
30
+ purge_interval: float = 10.0,
31
+ ):
32
+ self._identity_file = identity_file
33
+ self._ziti_ctx = None
34
+ self._ziti_timeout_ms = ziti_timeout_ms
35
+ self._ttl = float(ttl_seconds)
36
+ self._purge_interval = float(purge_interval)
37
+ self._cache: dict[ConnectionKey, CachedStreamEntry] = {}
38
+ self._lock = asyncio.Lock()
39
+ self._in_progress: dict[ConnectionKey, asyncio.Lock] = {}
40
+ self._purge_task: asyncio.Task | None = None
41
+
42
+ async def get(self, target: str) -> tuple[StreamReader, StreamWriter] | tuple[None, None]:
43
+ head, _, tail = target.partition(".")
44
+ terminator = target if head and tail else ""
45
+ service = tail if tail else head
46
+ r, w = await self._get_or_create_key((service, terminator))
47
+ return r, w
48
+
49
+ async def invalidate(self, key: ConnectionKey) -> None:
50
+ async with self._lock:
51
+ item = self._cache.pop(key, None)
52
+ if item is None:
53
+ return
54
+ await self._close_writer(item.writer)
55
+
56
+ async def start(self) -> None:
57
+ if self._ziti_ctx is None:
58
+ ctx, err = openziti.load(str(self._identity_file), timeout=self._ziti_timeout_ms)
59
+ if err != 0:
60
+ raise Exception(f"Cannot create a Ziti context from the identity file: {err}")
61
+ self._ziti_ctx = ctx
62
+ if self._purge_task is None:
63
+ self._purge_task = asyncio.create_task(self._purge_loop())
64
+ logger.info("Ziti connection manager started")
65
+
66
+ async def stop(self) -> None:
67
+ if self._purge_task is not None:
68
+ self._purge_task.cancel()
69
+ try:
70
+ await self._purge_task
71
+ except asyncio.CancelledError:
72
+ logger.debug("Purge task was cancelled")
73
+ except Exception as e:
74
+ logger.warning(f"An error occurred stopping the purge task: {e}")
75
+ self._purge_task = None
76
+ logger.info("Ziti connection manager stopped")
77
+
78
+ async with self._lock:
79
+ items = list(self._cache.items())
80
+ self._cache.clear()
81
+
82
+ for _, item in items:
83
+ await self._close_writer(item.writer)
84
+
85
+ async def _purge_loop(self) -> None:
86
+ try:
87
+ while True:
88
+ await asyncio.sleep(self._purge_interval)
89
+ await self._purge_once()
90
+ except asyncio.CancelledError:
91
+ return
92
+
93
+ async def _purge_once(self) -> None:
94
+ to_close: list[tuple[StreamReader, StreamWriter]] = []
95
+ async with self._lock:
96
+ now = time.time()
97
+ for key, item in list(self._cache.items()):
98
+ if now - item.last_access > self._ttl:
99
+ to_close.append((item.reader, item.writer))
100
+ del self._cache[key]
101
+
102
+ for _, writer in to_close:
103
+ writer.close()
104
+ await self._close_writer(writer)
105
+
106
+ def _is_writer_closed(self, writer: StreamWriter) -> bool:
107
+ return writer.transport.is_closing()
108
+
109
+ async def _close_writer(self, writer: StreamWriter) -> None:
110
+ writer.close()
111
+ try:
112
+ await writer.wait_closed()
113
+ except Exception as e:
114
+ logger.debug(f"Error closing writer: {e}")
115
+
116
+ async def _get_or_create_key(self, key: ConnectionKey) -> CachedStream:
117
+ """Internal: create or return a cached wrapped pair for the concrete key."""
118
+ await self._purge_once()
119
+ to_close = None
120
+ async with self._lock:
121
+ if key in self._cache:
122
+ now = time.time()
123
+ item = self._cache[key]
124
+ reader, writer = item.reader, item.writer
125
+ if not self._is_writer_closed(writer) and not reader.at_eof():
126
+ self._cache[key] = CachedStreamEntry(reader, writer, now)
127
+ return reader, writer
128
+ to_close = writer
129
+ del self._cache[key]
130
+
131
+ lock = self._in_progress.get(key)
132
+ if lock is None:
133
+ lock = asyncio.Lock()
134
+ self._in_progress[key] = lock
135
+
136
+ if to_close:
137
+ await self._close_writer(to_close)
138
+
139
+ async with lock:
140
+ try:
141
+ # # double-check cache after acquiring the per-key lock
142
+ # async with self._lock:
143
+ # now = time.time()
144
+ # if key in self._cache:
145
+ # r, w, _ = self._cache[key]
146
+ # if not self._is_writer_closed(w) and not r.at_eof():
147
+ # self._cache[key] = (r, w, now)
148
+ # return r, w
149
+
150
+ # perform creation via ziti context
151
+ extension, instance = key
152
+ logger.info(f"Create connection to {extension}: {instance}")
153
+ # loop = asyncio.get_running_loop()
154
+ # sock = await loop.run_in_executor(None, self._ziti_ctx.connect,
155
+ # extension, instance)
156
+ if instance:
157
+ sock = self._ziti_ctx.connect(
158
+ extension, terminator=instance
159
+ ) # , terminator=instance)
160
+ else:
161
+ sock = self._ziti_ctx.connect(extension)
162
+ orig_reader, orig_writer = await asyncio.open_connection(sock=sock)
163
+
164
+ reader = CachedStreamReader(orig_reader, key, self)
165
+ writer = CachedStreamWriter(orig_writer, key, self)
166
+
167
+ async with self._lock:
168
+ self._cache[key] = CachedStreamEntry(reader, writer, time.time())
169
+
170
+ return reader, writer
171
+ finally:
172
+ async with self._lock:
173
+ self._in_progress.pop(key, None)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mrok
3
- Version: 0.3.0
3
+ Version: 0.4.1
4
4
  Summary: MPT Extensions OpenZiti Orchestrator
5
5
  Author: SoftwareOne AG
6
6
  License: Apache License
@@ -212,14 +212,20 @@ Requires-Dist: dynaconf<4.0.0,>=3.2.11
212
212
  Requires-Dist: fastapi-pagination<0.15.0,>=0.14.1
213
213
  Requires-Dist: fastapi[standard]<0.120.0,>=0.119.0
214
214
  Requires-Dist: gunicorn<24.0.0,>=23.0.0
215
+ Requires-Dist: hdrhistogram<0.11.0,>=0.10.3
215
216
  Requires-Dist: httptools<0.8.0,>=0.7.1
216
217
  Requires-Dist: httpx<0.29.0,>=0.28.1
217
218
  Requires-Dist: openziti<2.0.0,>=1.3.1
219
+ Requires-Dist: psutil<8.0.0,>=7.1.3
218
220
  Requires-Dist: pydantic<3.0.0,>=2.11.7
219
221
  Requires-Dist: pyfiglet<2.0.0,>=1.0.4
220
222
  Requires-Dist: pyjwt<3.0.0,>=2.10.1
223
+ Requires-Dist: pytest-textual-snapshot<2.0.0,>=1.1.0
221
224
  Requires-Dist: pyyaml<7.0.0,>=6.0.2
225
+ Requires-Dist: pyzmq<28.0.0,>=27.1.0
222
226
  Requires-Dist: rich<15.0.0,>=14.1.0
227
+ Requires-Dist: textual-serve<2.0.0,>=1.1.3
228
+ Requires-Dist: textual<7.0.0,>=6.5.0
223
229
  Requires-Dist: typer<0.20.0,>=0.19.2
224
230
  Requires-Dist: uvicorn-worker<0.5.0,>=0.4.0
225
231
  Description-Content-Type: text/markdown
@@ -1,16 +1,26 @@
1
1
  mrok/__init__.py,sha256=D1PUs3KtMCqG4bFLceVNG62L3RN53NS95uSCNXpgvzs,181
2
- mrok/conf.py,sha256=iDMxJtorJWxK0mI-pGEi1TBd_064R-VLcWzHB4esUlE,661
2
+ mrok/conf.py,sha256=_5Z-A5LyojQeY8J7W8C0QidsmrPl99r9qKYEoMf4kcI,840
3
+ mrok/datastructures.py,sha256=gp8KF2JoNOxIRzYStVZLKL_XVDbcIVSIDnmpQo4FNt0,4067
3
4
  mrok/errors.py,sha256=ruNMDFr2_0ezCGXuCG1OswCEv-bHOIzMMd02J_0ABcs,37
4
- mrok/logging.py,sha256=4F5rviPK1-MWWMZuHfzNNQmGxg-emAPRdKz0PsWDSww,2261
5
+ mrok/logging.py,sha256=ZMWn0w4fJ-F_g-L37H_GM14BSXAIF2mFF_ougX5S7mg,2856
6
+ mrok/master.py,sha256=XuketJZuB1YWdbTs819pjLum7Qfv232F9ZCxdwRztCQ,8340
7
+ mrok/metrics.py,sha256=asweK_7xiV5MtkDkvbEm9Tktqrl2KHM8VflF0AkNGI0,4036
5
8
  mrok/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- mrok/agent/ziticorn.py,sha256=sWtX7cyaIks8_Re3RmLlcgFPGOZ7-xBqdv6TZ50I1ac,765
9
+ mrok/agent/ziticorn.py,sha256=marXGcr6CbDdiNi8V3CZJhg8YCUbKw6ySuO3-0-zf8g,900
10
+ mrok/agent/devtools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ mrok/agent/devtools/__main__.py,sha256=R8ezbW7hCik5r45U3w2TgiTubg9SlbVsWA-bapILJXU,781
12
+ mrok/agent/devtools/inspector/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ mrok/agent/devtools/inspector/__main__.py,sha256=HeYcRf1bjXPji2LKMPCcTU61afrRH2P1RqnFmHClRTc,524
14
+ mrok/agent/devtools/inspector/app.py,sha256=_pzxemMqIunE5EdMq5amjqpOGsMWIOw17GgiCtRAi6Q,16464
15
+ mrok/agent/devtools/inspector/server.py,sha256=C4uD6_1psSHMjJLUDCMPGvKdQYKaEwYTw27NAbwuuA0,636
7
16
  mrok/agent/sidecar/__init__.py,sha256=DrjJGhqFyxsVODW06KI20Wpr6HsD2lD6qFCKUXc7GIE,59
8
- mrok/agent/sidecar/app.py,sha256=QEfPdeFwlerxDbjAqUNtMsnslVUu2h4nCoOVCBFVsy0,992
9
- mrok/agent/sidecar/main.py,sha256=6HVJRqtxNOKrCQ0XQhQdfWCfjzHJMgBn_zPqtLsIxWw,798
17
+ mrok/agent/sidecar/app.py,sha256=1p6qWkXVq78zcJ2dhCYlw8CqfwPsgEtu07Lp5csK3Iw,874
18
+ mrok/agent/sidecar/main.py,sha256=h31wynUCcFmRckvqLHtH97w1QgMv4fzcmYjhRPUobxY,1076
10
19
  mrok/cli/__init__.py,sha256=mtFEa8IeS1x6Gm4dUYoSnAxyEzNqbUVSmWxtuZUMR84,61
11
20
  mrok/cli/main.py,sha256=DFcYPwDskXi8SKAgEsuP4GMFzaniIf_6bZaSDWvYKDk,2724
12
21
  mrok/cli/rich.py,sha256=P3Dyu8EArUR9_0j7DPK7LRx85TWdYdZ1SaJzD_S1ZCE,511
13
- mrok/cli/commands/__init__.py,sha256=M6Sypb2vAh6qxQQJNIO9xop2DrD6zt6TEp8rbbD7LS0,114
22
+ mrok/cli/utils.py,sha256=m_olScdIUGks5IoC6p2F9D6CQIucWZ7LHyrvwm2bkJw,106
23
+ mrok/cli/commands/__init__.py,sha256=jihISOj3ZZQ_dn0rogXrJOx6b283KLRUfTw9USQgAhI,134
14
24
  mrok/cli/commands/admin/__init__.py,sha256=WU49jpMF9p18UONjYywWEFzjF57zLpLKJ0qAZvrzcR4,414
15
25
  mrok/cli/commands/admin/bootstrap.py,sha256=iOnHctYajgcHrG_Idjn5Y7VVSaWYRIhdgqKSw9TWq9I,1680
16
26
  mrok/cli/commands/admin/utils.py,sha256=wQ-qQJGFyhikMJY_CWT-G6sTEIZb-LUdj1AUZisLPBw,1363
@@ -23,16 +33,22 @@ mrok/cli/commands/admin/register/instances.py,sha256=XB6uAchc7Rm8uAu7o3-oHaN_rS8
23
33
  mrok/cli/commands/admin/unregister/__init__.py,sha256=-GjjCPX1pISbWmJK6GpKO3ijGsDQb21URjU1hNu99O4,215
24
34
  mrok/cli/commands/admin/unregister/extensions.py,sha256=GR3Iwzeksk_R0GkgmCSG7iHRcUrI7ABqDi25Gbes64Y,1016
25
35
  mrok/cli/commands/admin/unregister/instances.py,sha256=-28wL8pTXTWHVHtw93y8-dqi-Dlf0OZOnlBCKOyGo80,1138
26
- mrok/cli/commands/agent/__init__.py,sha256=Jr9RDSDdRPjbVJ7NhzgjRD-jtr5hD2vvKzDe7XsLnVo,140
36
+ mrok/cli/commands/agent/__init__.py,sha256=ZAi7eTkKQtfwwV1c1mv3uvEEsyMMrhCQ_-id_0wksAQ,218
37
+ mrok/cli/commands/agent/utils.py,sha256=m_olScdIUGks5IoC6p2F9D6CQIucWZ7LHyrvwm2bkJw,106
38
+ mrok/cli/commands/agent/dev/__init__.py,sha256=ZfreyRuaLqO0AwPS8Ll1DIpsKacsu7_dTmbxV5QecOM,172
39
+ mrok/cli/commands/agent/dev/console.py,sha256=rrKAGoKXVQQBOC75H0JSuX1sYyvc2QSrV-dfMPK49p4,673
40
+ mrok/cli/commands/agent/dev/web.py,sha256=O9dYk-o1FV2E_sKLOezdEmLsnexwbJNDdsYL5pATZRQ,1028
27
41
  mrok/cli/commands/agent/run/__init__.py,sha256=E_IJCl3BfMffqFASe8gzJwhhQgt5bQfjhuyekVwdEBA,164
28
- mrok/cli/commands/agent/run/asgi.py,sha256=aqwu_h9WyCDI2Ts8D4zTvawCETNmcke7cX3zIUyRww4,1265
29
- mrok/cli/commands/agent/run/sidecar.py,sha256=VC6o1Xw6XXB25xh5F2PyPsDe5Sii0cWLVLDrpNIC5hU,1490
42
+ mrok/cli/commands/agent/run/asgi.py,sha256=dCgzwJtTLv2eyEIP7v1tDfe_PrFBS02SfN5dSDw1Jzg,2054
43
+ mrok/cli/commands/agent/run/sidecar.py,sha256=Tj5inAeSX1E3yCVs2q4P3sP3trvvwk2lYMSUtyFfxo8,2098
30
44
  mrok/cli/commands/controller/__init__.py,sha256=2xw-YVN0akiLiuGUU3XbYyZZ0ugOjQ6XhtTkzEKSmMA,161
31
45
  mrok/cli/commands/controller/openapi.py,sha256=QLjVao9UkB2vBaGkFi_q_jrlg4Np4ldMRwDIJsrJ7A8,1175
32
- mrok/cli/commands/controller/run.py,sha256=osyjssb81xNMYZLPb6dfPR4W_BQlCxKDfvl-BIhG_1A,2460
46
+ mrok/cli/commands/controller/run.py,sha256=yl1p7oRHhQINWWjUKlRHtMIWUCV0KsxYdyVyazhX834,2406
47
+ mrok/cli/commands/proxy/__init__.py,sha256=Y9oluemsuWSaykDniLVsI2cyTurcEO3_GJDHgf-7BdU,120
48
+ mrok/cli/commands/proxy/run.py,sha256=QzKAjNCib-SS8IrGGHOxDjTtgTQxfeeeqmI3LaIkiLo,1293
33
49
  mrok/controller/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
50
  mrok/controller/app.py,sha256=XxCIB7N1YE52vSYfvGW2UPgEEOZ9jxDMe2l9D2SfXi8,1866
35
- mrok/controller/auth.py,sha256=Kg94W8yNMs6TvUmLRYv1QeUjDy4qlGZ-_6OHa4KH1zg,2648
51
+ mrok/controller/auth.py,sha256=hYa0OPJ5X0beGxRP6qbxwJOVXj5TmzHjmam2OjTBKn4,2704
36
52
  mrok/controller/pagination.py,sha256=raYpYa34q8Ckl4BXBOEdpWlKkFj6z7e6QLWr2HT7dzI,2187
37
53
  mrok/controller/schemas.py,sha256=AaF8_bEwZTHM02apVEBAzlUb2t71zoxYaG-VHtPNeMk,1705
38
54
  mrok/controller/dependencies/__init__.py,sha256=voewk6gjkA0OarL6HFmfT_RLqBns0Fpl-VIqK5xVAEI,202
@@ -45,12 +61,22 @@ mrok/controller/routes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3
45
61
  mrok/controller/routes/extensions.py,sha256=zoY4sNz_BIZcbly6WXM7Rbpn2jmB89njS_0xdJkoKfs,9192
46
62
  mrok/controller/routes/instances.py,sha256=v-fn_F6JHbDZ4YUNCIZzClgHp6aC1Eu5HB7k7qBG5pk,2202
47
63
  mrok/http/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
- mrok/http/config.py,sha256=k8mjvD3ninJn-v1t-co-GSa3upm4b70bWyk3fwdcOh8,2161
49
- mrok/http/forwarder.py,sha256=mo-Z8B8Zg6kdDX-lWEiptRv-9kJU9cEdmg6gt6eF0cc,11374
50
- mrok/http/lifespan.py,sha256=9qevhD_5Y0f8fGTh2axdfWx7v1K4vnWtiUNyJLesOHE,262
51
- mrok/http/master.py,sha256=TwU78yE_GQecogBs_PDpl3gY7_jWYNIRNaAXRzi0rvY,4152
64
+ mrok/http/config.py,sha256=k73-4hBo6jag1RpyZagJLtTCL6EQoebZaX8Vv-CMN_k,2050
65
+ mrok/http/constants.py,sha256=ao5gI2HFBWmrdd2Yc6XFK_RGaHk-omxI4AqvfIiGes8,409
66
+ mrok/http/forwarder.py,sha256=DakD9hrrCWAzB1B_4SgQaQaEHcDYLLI9WaYs5F0O36I,12977
67
+ mrok/http/lifespan.py,sha256=UdbOqjWZsHzJJjX0CTd2hY96Jpk5QWtdHJEzPG6Z4hQ,1288
68
+ mrok/http/middlewares.py,sha256=SGo4EwhTId2uJx1aMuqGbNy7MXgZlDEdZI0buzBYVv0,5011
52
69
  mrok/http/protocol.py,sha256=ap8jbLUvgbAH81ZJZCBkQiYR7mkV_eL3rpfwEkoE8sU,392
53
70
  mrok/http/server.py,sha256=Mj7C85fc-DXp-WTBWaOd7ag808oliLmFBH5bf-G2FHg,370
71
+ mrok/http/types.py,sha256=XpNrvbfpANKvmjOBYtLF1FmDHoJF3z_MIMQHXoJlvmE,1302
72
+ mrok/http/utils.py,sha256=sOixYu3R9-nNoMFYdifrreYvcFRIHYVtb6AAmtVzaLE,2125
73
+ mrok/proxy/__init__.py,sha256=vWXyImroqM1Eq8e_oFPBup8VJ3reyp8SVjFTbLzRkI8,51
74
+ mrok/proxy/app.py,sha256=YP4hM3BIRf_MFw9YNEnsFrrBz76zVegrNFcvcsKTFvI,2579
75
+ mrok/proxy/dataclasses.py,sha256=DtX-Yuma-uOECOPefJnoQJhZMEtT6Za_27cd-lJE9Iw,237
76
+ mrok/proxy/main.py,sha256=ZXpticE6J4FABaslDB_8J5qklPsf3e7xIFSZmcPAAjQ,1588
77
+ mrok/proxy/streams.py,sha256=6TMZwrQPbSyQqpqavsoTeyUmS2O026pJfiCnxLopPqg,3425
78
+ mrok/proxy/types.py,sha256=dgWqAj6dFGVH_Q8-k8sU5h18yoUF_fTn-SRPIfEs_gA,308
79
+ mrok/proxy/ziti.py,sha256=dKd6UzmEAFu9-gey871sPEDUZTkt4YVPyCYRzeA5mlA,6539
54
80
  mrok/ziti/__init__.py,sha256=20OWMiexRhOovZOX19zlX87-V78QyWnEnSZfyAftUdE,263
55
81
  mrok/ziti/api.py,sha256=KvGiT9d4oSgC3JbFWLDQyuHcLX2HuZJoJ8nHmWtCDkY,16154
56
82
  mrok/ziti/bootstrap.py,sha256=QIDhlkIxPW2QRuumFq2D1WDbD003P5f3z24pAUsyeBI,2696
@@ -59,8 +85,8 @@ mrok/ziti/errors.py,sha256=yYCbVDwktnR0AYduqtynIjo73K3HOhIrwA_vQimvEd4,368
59
85
  mrok/ziti/identities.py,sha256=1BcwfqAJHMBhc3vRaf0aLaIkoHskj5Xe2Lsq2lO9Vs8,6735
60
86
  mrok/ziti/pki.py,sha256=o2tySqHC8-7bvFuI2Tqxg9vX6H6ZSxWxfP_9x29e19M,1954
61
87
  mrok/ziti/services.py,sha256=zR1PEBYwXVou20iJK4euh0ZZFAo9UB8PZk8f6SDmiUE,3194
62
- mrok-0.3.0.dist-info/METADATA,sha256=5d5NEs5U_FXyVXXoZXhZgZUkdQl5N_EbFPjkDJ79whM,15546
63
- mrok-0.3.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
64
- mrok-0.3.0.dist-info/entry_points.txt,sha256=tloXwvU1uJicBJR2h-8HoVclPgwJWDwuREMHN8Zq-nU,38
65
- mrok-0.3.0.dist-info/licenses/LICENSE.txt,sha256=6PaICaoA3yNsZKLv5G6OKqSfLSoX7MakYqTDgJoTCBs,11346
66
- mrok-0.3.0.dist-info/RECORD,,
88
+ mrok-0.4.1.dist-info/METADATA,sha256=PuydR9Z4yuDSDMuJrn1FqWGb18fJTA6TFChAj-LwWZE,15796
89
+ mrok-0.4.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
90
+ mrok-0.4.1.dist-info/entry_points.txt,sha256=tloXwvU1uJicBJR2h-8HoVclPgwJWDwuREMHN8Zq-nU,38
91
+ mrok-0.4.1.dist-info/licenses/LICENSE.txt,sha256=6PaICaoA3yNsZKLv5G6OKqSfLSoX7MakYqTDgJoTCBs,11346
92
+ mrok-0.4.1.dist-info/RECORD,,
mrok/http/master.py DELETED
@@ -1,132 +0,0 @@
1
- import logging
2
- import os
3
- import signal
4
- import threading
5
- import time
6
- from collections.abc import Callable
7
- from pathlib import Path
8
-
9
- from watchfiles import watch
10
- from watchfiles.filters import PythonFilter
11
- from watchfiles.run import CombinedProcess, start_process
12
-
13
- logger = logging.getLogger("mrok.agent")
14
-
15
- MONITOR_THREAD_JOIN_TIMEOUT = 5
16
- MONITOR_THREAD_CHECK_DELAY = 1
17
- MONITOR_THREAD_ERROR_DELAY = 3
18
-
19
-
20
- def print_path(path):
21
- try:
22
- return f'"{path.relative_to(Path.cwd())}"'
23
- except ValueError:
24
- return f'"{path}"'
25
-
26
-
27
- class Master:
28
- def __init__(
29
- self,
30
- start_fn: Callable,
31
- workers: int,
32
- reload: bool,
33
- ):
34
- self.start_fn = start_fn
35
- self.workers = workers
36
- self.reload = reload
37
- self.worker_processes: dict[int, CombinedProcess] = {}
38
- self.stop_event = threading.Event()
39
- self.watch_filter = PythonFilter(ignore_paths=None)
40
- self.watcher = watch(
41
- Path.cwd(),
42
- watch_filter=self.watch_filter,
43
- stop_event=self.stop_event,
44
- yield_on_timeout=True,
45
- )
46
- self.setup_signals_handler()
47
- self.monitor_thread = None
48
-
49
- def setup_signals_handler(self):
50
- for sig in (signal.SIGINT, signal.SIGTERM):
51
- signal.signal(sig, self.handle_signal)
52
-
53
- def handle_signal(self, *args, **kwargs):
54
- self.stop_event.set()
55
-
56
- def start_worker(self, worker_id: int):
57
- """Start a single worker process"""
58
- p = start_process(
59
- self.start_fn,
60
- "function",
61
- (),
62
- None,
63
- )
64
- logger.info(f"Worker {worker_id} [{p.pid}] started")
65
- return p
66
-
67
- def start(self):
68
- for i in range(self.workers):
69
- p = self.start_worker(i)
70
- self.worker_processes[i] = p
71
-
72
- def stop(self):
73
- for process in self.worker_processes.values():
74
- process.stop(sigint_timeout=5, sigkill_timeout=1)
75
- self.worker_processes.clear()
76
-
77
- def restart(self):
78
- self.stop()
79
- self.start()
80
-
81
- def monitor_workers(self):
82
- while not self.stop_event.is_set():
83
- try:
84
- for worker_id, process in self.worker_processes.items():
85
- if not process.is_alive():
86
- logger.warning(f"Worker {worker_id} [{process.pid}] died unexpectedly")
87
- process.stop(sigint_timeout=1, sigkill_timeout=1)
88
- new_process = self.start_worker(worker_id)
89
- self.worker_processes[worker_id] = new_process
90
- logger.info(
91
- f"Restarted worker {worker_id} [{process.pid}] -> [{new_process.pid}]"
92
- )
93
-
94
- time.sleep(MONITOR_THREAD_CHECK_DELAY)
95
-
96
- except Exception as e:
97
- logger.error(f"Error in worker monitoring: {e}")
98
- time.sleep(MONITOR_THREAD_ERROR_DELAY)
99
-
100
- def __iter__(self):
101
- return self
102
-
103
- def __next__(self):
104
- changes = next(self.watcher)
105
- if changes:
106
- return list({Path(change[1]) for change in changes})
107
- return None
108
-
109
- def run(self):
110
- self.start()
111
- logger.info(f"Master process started: {os.getpid()}")
112
-
113
- # Start worker monitoring thread
114
- self.monitor_thread = threading.Thread(target=self.monitor_workers, daemon=True)
115
- self.monitor_thread.start()
116
- logger.debug("Worker monitoring thread started")
117
-
118
- try:
119
- if self.reload:
120
- for files_changed in self:
121
- if files_changed:
122
- logger.warning(
123
- f"{', '.join(map(print_path, files_changed))} changed, reloading...",
124
- )
125
- self.restart()
126
- else:
127
- self.stop_event.wait()
128
- finally:
129
- if self.monitor_thread and self.monitor_thread.is_alive(): # pragma: no cover
130
- logger.debug("Wait for monitor worker to exit")
131
- self.monitor_thread.join(timeout=MONITOR_THREAD_JOIN_TIMEOUT)
132
- self.stop()
File without changes