mrok 0.4.6__py3-none-any.whl → 0.6.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.
Files changed (50) hide show
  1. mrok/agent/devtools/inspector/app.py +2 -2
  2. mrok/agent/sidecar/app.py +61 -35
  3. mrok/agent/sidecar/main.py +35 -9
  4. mrok/agent/ziticorn.py +9 -3
  5. mrok/cli/commands/__init__.py +2 -2
  6. mrok/cli/commands/admin/bootstrap.py +3 -2
  7. mrok/cli/commands/admin/utils.py +2 -2
  8. mrok/cli/commands/agent/run/sidecar.py +59 -1
  9. mrok/cli/commands/{proxy → frontend}/__init__.py +1 -1
  10. mrok/cli/commands/frontend/run.py +91 -0
  11. mrok/constants.py +0 -2
  12. mrok/controller/openapi/examples.py +13 -0
  13. mrok/controller/schemas.py +2 -2
  14. mrok/frontend/__init__.py +3 -0
  15. mrok/frontend/app.py +75 -0
  16. mrok/{proxy → frontend}/main.py +12 -10
  17. mrok/proxy/__init__.py +0 -3
  18. mrok/proxy/app.py +158 -83
  19. mrok/proxy/asgi.py +96 -0
  20. mrok/proxy/backend.py +45 -0
  21. mrok/proxy/event_publisher.py +66 -0
  22. mrok/proxy/exceptions.py +22 -0
  23. mrok/{master.py → proxy/master.py} +36 -81
  24. mrok/{metrics.py → proxy/metrics.py} +38 -50
  25. mrok/{http/middlewares.py → proxy/middleware.py} +17 -26
  26. mrok/{datastructures.py → proxy/models.py} +43 -10
  27. mrok/proxy/stream.py +68 -0
  28. mrok/{http → proxy}/utils.py +1 -1
  29. mrok/proxy/worker.py +64 -0
  30. mrok/{http/config.py → proxy/ziticorn.py} +29 -6
  31. mrok/types/proxy.py +20 -0
  32. mrok/types/ziti.py +1 -0
  33. mrok/ziti/api.py +15 -18
  34. mrok/ziti/bootstrap.py +3 -2
  35. mrok/ziti/identities.py +5 -4
  36. mrok/ziti/services.py +3 -2
  37. {mrok-0.4.6.dist-info → mrok-0.6.0.dist-info}/METADATA +2 -5
  38. {mrok-0.4.6.dist-info → mrok-0.6.0.dist-info}/RECORD +43 -39
  39. mrok/cli/commands/proxy/run.py +0 -49
  40. mrok/http/forwarder.py +0 -354
  41. mrok/http/lifespan.py +0 -39
  42. mrok/http/pool.py +0 -239
  43. mrok/http/protocol.py +0 -11
  44. mrok/http/server.py +0 -14
  45. mrok/http/types.py +0 -18
  46. /mrok/{http → proxy}/constants.py +0 -0
  47. /mrok/{http → types}/__init__.py +0 -0
  48. {mrok-0.4.6.dist-info → mrok-0.6.0.dist-info}/WHEEL +0 -0
  49. {mrok-0.4.6.dist-info → mrok-0.6.0.dist-info}/entry_points.txt +0 -0
  50. {mrok-0.4.6.dist-info → mrok-0.6.0.dist-info}/licenses/LICENSE.txt +0 -0
mrok/http/pool.py DELETED
@@ -1,239 +0,0 @@
1
- import asyncio
2
- import contextlib
3
- import logging
4
- import time
5
- from collections.abc import AsyncGenerator, Awaitable, Callable, Coroutine
6
- from contextlib import asynccontextmanager
7
- from typing import Any
8
-
9
- from cachetools import TTLCache
10
-
11
- from mrok.http.types import StreamPair
12
-
13
- PoolItem = tuple[asyncio.StreamReader, asyncio.StreamWriter, float]
14
-
15
- logger = logging.getLogger("mrok.proxy")
16
-
17
-
18
- class ConnectionPool:
19
- def __init__(
20
- self,
21
- pool_name: str,
22
- factory: Callable[[], Awaitable[StreamPair]],
23
- *,
24
- initial_connections: int = 0,
25
- max_size: int = 10,
26
- idle_timeout: float = 30.0,
27
- reaper_interval: float = 5.0,
28
- ) -> None:
29
- if initial_connections < 0:
30
- raise ValueError("initial_connections must be >= 0")
31
- if max_size < 1:
32
- raise ValueError("max_size must be >= 1")
33
- if initial_connections > max_size:
34
- raise ValueError("initial_connections cannot exceed max_size")
35
- self.pool_name = pool_name
36
- self.factory = factory
37
- self.initial_connections = initial_connections
38
- self.max_size = max_size
39
- self.idle_timeout = idle_timeout
40
- self.reaper_interval = reaper_interval
41
-
42
- self._pool: list[PoolItem] = []
43
- self._in_use = 0
44
- self._lock = asyncio.Lock()
45
- self._cond = asyncio.Condition()
46
- self._stop_event = asyncio.Event()
47
-
48
- self._started = False
49
- self._reaper_task: asyncio.Task | None = None
50
-
51
- async def start(self) -> None:
52
- if self._started:
53
- return
54
- self._reaper_task = asyncio.create_task(self._reaper())
55
- await self._prewarm()
56
- self._started = True
57
-
58
- async def stop(self) -> None:
59
- self._stop_event.set()
60
- if self._reaper_task is not None:
61
- self._reaper_task.cancel()
62
- with contextlib.suppress(Exception):
63
- await self._reaper_task
64
-
65
- to_close: list[asyncio.StreamWriter] = []
66
- async with self._lock:
67
- to_close = [writer for _, writer, _ in self._pool]
68
- self._pool.clear()
69
- for w in to_close:
70
- with contextlib.suppress(Exception):
71
- w.close()
72
- await w.wait_closed()
73
-
74
- async with self._cond:
75
- self._cond.notify_all()
76
-
77
- @asynccontextmanager
78
- async def acquire(self) -> AsyncGenerator[StreamPair]:
79
- if not self._started:
80
- await self.start()
81
- reader, writer = await self._acquire()
82
- logger.info(
83
- f"Acquire stats for pool {self.pool_name}: "
84
- f"in_use={self._in_use}, size={len(self._pool)}"
85
- )
86
- try:
87
- yield (reader, writer)
88
- finally:
89
- await self._release(reader, writer)
90
-
91
- async def _prewarm(self) -> None:
92
- conns: list[PoolItem] = []
93
- needed = max(0, self.initial_connections - (self._in_use + len(self._pool)))
94
- for _ in range(needed):
95
- reader, writer = await self.factory()
96
- conns.append((reader, writer, time.time()))
97
- if conns:
98
- async with self._lock:
99
- self._pool.extend(conns)
100
- # notify any waiters
101
- async with self._cond:
102
- self._cond.notify_all()
103
-
104
- async def _acquire(self) -> StreamPair: # type: ignore
105
- to_close: list[asyncio.StreamWriter] = []
106
- create_new = False
107
- while True:
108
- need_prewarm = False
109
- async with self._cond:
110
- now = time.time()
111
- if not self._pool:
112
- need_prewarm = True
113
- while self._pool:
114
- reader, writer, ts = self._pool.pop()
115
- if now - ts <= self.idle_timeout and not writer.is_closing():
116
- self._in_use += 1
117
- return reader, writer
118
- to_close.append(writer)
119
-
120
- total = self._in_use + len(self._pool)
121
- if total < self.max_size:
122
- self._in_use += 1
123
- create_new = True
124
- break
125
- await self._cond.wait()
126
-
127
- if need_prewarm:
128
- await self._prewarm()
129
- continue
130
-
131
- for w in to_close:
132
- with contextlib.suppress(Exception):
133
- w.close()
134
- await w.wait_closed()
135
-
136
- if create_new:
137
- try:
138
- reader, writer = await self.factory()
139
- except Exception:
140
- async with self._cond:
141
- if self._in_use > 0:
142
- self._in_use -= 1
143
- self._cond.notify()
144
- raise
145
- return reader, writer
146
-
147
- async def _release(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None:
148
- async with self._cond:
149
- if self._in_use > 0:
150
- self._in_use -= 1
151
-
152
- if not writer.is_closing():
153
- self._pool.append((reader, writer, time.time()))
154
-
155
- self._cond.notify()
156
- logger.info(
157
- f"Release stats for pool {self.pool_name}: "
158
- f"in_use={self._in_use}, size={len(self._pool)}"
159
- )
160
-
161
- async def _reaper(self) -> None:
162
- try:
163
- while not self._stop_event.is_set():
164
- await asyncio.sleep(self.reaper_interval)
165
- to_close: list[asyncio.StreamWriter] = []
166
- now = time.time()
167
- async with self._lock:
168
- new_pool: list[PoolItem] = []
169
- for reader, writer, ts in self._pool:
170
- if now - ts > self.idle_timeout or writer.is_closing():
171
- to_close.append(writer)
172
- else:
173
- new_pool.append((reader, writer, ts))
174
- self._pool = new_pool
175
-
176
- for w in to_close:
177
- with contextlib.suppress(Exception):
178
- w.close()
179
- await w.wait_closed()
180
-
181
- async with self._cond:
182
- self._cond.notify_all()
183
- except asyncio.CancelledError:
184
- pass
185
-
186
-
187
- class SlidingTTLCache(TTLCache):
188
- def __init__(
189
- self,
190
- *,
191
- maxsize: float,
192
- ttl: float,
193
- on_evict: Callable[[Any], Coroutine[Any, Any, None]] | None,
194
- ) -> None:
195
- super().__init__(maxsize=maxsize, ttl=ttl)
196
- self.on_evict = on_evict
197
-
198
- def __getitem__(self, key: Any) -> Any:
199
- value = super().__getitem__(key)
200
- super().__setitem__(key, value)
201
- return value
202
-
203
- def popitem(self) -> Any:
204
- key, value = super().popitem()
205
- if self.on_evict:
206
- asyncio.create_task(self.on_evict(value))
207
- return key, value
208
-
209
-
210
- class PoolManager:
211
- def __init__(
212
- self,
213
- pool_factory: Callable[[Any], Awaitable[ConnectionPool]],
214
- idle_timeout: int = 300,
215
- ):
216
- self.factory = pool_factory
217
- self.cache = SlidingTTLCache(
218
- maxsize=float("inf"),
219
- ttl=idle_timeout,
220
- on_evict=self._close_pool,
221
- )
222
-
223
- async def _close_pool(self, pool: ConnectionPool):
224
- with contextlib.suppress(Exception):
225
- await pool.stop()
226
-
227
- async def get_pool(self, key) -> ConnectionPool:
228
- try:
229
- return self.cache[key]
230
- except KeyError:
231
- pool = await self.factory(key)
232
- await pool.start()
233
- self.cache[key] = pool
234
- return pool
235
-
236
- async def shutdown(self) -> None:
237
- pools = list(self.cache.values())
238
- for pool in pools:
239
- await self._close_pool(pool)
mrok/http/protocol.py DELETED
@@ -1,11 +0,0 @@
1
- import logging
2
-
3
- from uvicorn.protocols.http.httptools_impl import HttpToolsProtocol
4
-
5
-
6
- class MrokHttpToolsProtocol(HttpToolsProtocol):
7
- def __init__(self, *args, **kwargs):
8
- super().__init__(*args, **kwargs)
9
- self.logger = logging.getLogger("mrok.proxy")
10
- self.access_logger = logging.getLogger("mrok.access")
11
- self.access_log = self.access_logger.hasHandlers()
mrok/http/server.py DELETED
@@ -1,14 +0,0 @@
1
- import logging
2
- import socket
3
-
4
- from uvicorn import server
5
-
6
- server.logger = logging.getLogger("mrok.proxy")
7
-
8
-
9
- class MrokServer(server.Server):
10
- async def serve(self, sockets: list[socket.socket] | None = None) -> None:
11
- if not sockets:
12
- sockets = [self.config.bind_socket()]
13
- with self.capture_signals():
14
- await self._serve(sockets)
mrok/http/types.py DELETED
@@ -1,18 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import asyncio
4
- from collections.abc import Awaitable, Callable, MutableMapping
5
- from typing import Any
6
-
7
- from mrok.datastructures import HTTPRequest, HTTPResponse
8
-
9
- Scope = MutableMapping[str, Any]
10
- Message = MutableMapping[str, Any]
11
-
12
- ASGIReceive = Callable[[], Awaitable[Message]]
13
- ASGISend = Callable[[Message], Awaitable[None]]
14
- ASGIApp = Callable[[Scope, ASGIReceive, ASGISend], Awaitable[None]]
15
- RequestCompleteCallback = Callable[[HTTPRequest], Awaitable | None]
16
- ResponseCompleteCallback = Callable[[HTTPResponse], Awaitable | None]
17
-
18
- StreamPair = tuple[asyncio.StreamReader, asyncio.StreamWriter]
File without changes
File without changes
File without changes