mrok 0.4.2__tar.gz → 0.4.4__tar.gz

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 (159) hide show
  1. {mrok-0.4.2 → mrok-0.4.4}/PKG-INFO +1 -1
  2. {mrok-0.4.2 → mrok-0.4.4}/mrok/proxy/app.py +7 -12
  3. mrok-0.4.4/mrok/proxy/ziti.py +102 -0
  4. {mrok-0.4.2 → mrok-0.4.4}/pyproject.toml +1 -1
  5. mrok-0.4.2/mrok/proxy/dataclasses.py +0 -12
  6. mrok-0.4.2/mrok/proxy/streams.py +0 -124
  7. mrok-0.4.2/mrok/proxy/types.py +0 -11
  8. mrok-0.4.2/mrok/proxy/ziti.py +0 -117
  9. {mrok-0.4.2 → mrok-0.4.4}/.github/actions/setup-python-env/action.yml +0 -0
  10. {mrok-0.4.2 → mrok-0.4.4}/.github/workflows/assets/turing_team_pr_bot.png +0 -0
  11. {mrok-0.4.2 → mrok-0.4.4}/.github/workflows/notify-pr-closed.yaml +0 -0
  12. {mrok-0.4.2 → mrok-0.4.4}/.github/workflows/notify-pr-reviewed.yml +0 -0
  13. {mrok-0.4.2 → mrok-0.4.4}/.github/workflows/pr-build-merge.yaml +0 -0
  14. {mrok-0.4.2 → mrok-0.4.4}/.github/workflows/release.yml +0 -0
  15. {mrok-0.4.2 → mrok-0.4.4}/.gitignore +0 -0
  16. {mrok-0.4.2 → mrok-0.4.4}/.pre-commit-config.yaml +0 -0
  17. {mrok-0.4.2 → mrok-0.4.4}/.python-version +0 -0
  18. {mrok-0.4.2 → mrok-0.4.4}/LICENSE.txt +0 -0
  19. {mrok-0.4.2 → mrok-0.4.4}/README.md +0 -0
  20. {mrok-0.4.2 → mrok-0.4.4}/dev.Dockerfile +0 -0
  21. {mrok-0.4.2 → mrok-0.4.4}/docker-compose.yaml +0 -0
  22. {mrok-0.4.2 → mrok-0.4.4}/entrypoint.sh +0 -0
  23. {mrok-0.4.2 → mrok-0.4.4}/mrok/__init__.py +0 -0
  24. {mrok-0.4.2 → mrok-0.4.4}/mrok/agent/__init__.py +0 -0
  25. {mrok-0.4.2 → mrok-0.4.4}/mrok/agent/devtools/__init__.py +0 -0
  26. {mrok-0.4.2 → mrok-0.4.4}/mrok/agent/devtools/__main__.py +0 -0
  27. {mrok-0.4.2 → mrok-0.4.4}/mrok/agent/devtools/inspector/__init__.py +0 -0
  28. {mrok-0.4.2 → mrok-0.4.4}/mrok/agent/devtools/inspector/__main__.py +0 -0
  29. {mrok-0.4.2 → mrok-0.4.4}/mrok/agent/devtools/inspector/app.py +0 -0
  30. {mrok-0.4.2 → mrok-0.4.4}/mrok/agent/devtools/inspector/server.py +0 -0
  31. {mrok-0.4.2 → mrok-0.4.4}/mrok/agent/sidecar/__init__.py +0 -0
  32. {mrok-0.4.2 → mrok-0.4.4}/mrok/agent/sidecar/app.py +0 -0
  33. {mrok-0.4.2 → mrok-0.4.4}/mrok/agent/sidecar/main.py +0 -0
  34. {mrok-0.4.2 → mrok-0.4.4}/mrok/agent/ziticorn.py +0 -0
  35. {mrok-0.4.2 → mrok-0.4.4}/mrok/cli/__init__.py +0 -0
  36. {mrok-0.4.2 → mrok-0.4.4}/mrok/cli/commands/__init__.py +0 -0
  37. {mrok-0.4.2 → mrok-0.4.4}/mrok/cli/commands/admin/__init__.py +0 -0
  38. {mrok-0.4.2 → mrok-0.4.4}/mrok/cli/commands/admin/bootstrap.py +0 -0
  39. {mrok-0.4.2 → mrok-0.4.4}/mrok/cli/commands/admin/list/__init__.py +0 -0
  40. {mrok-0.4.2 → mrok-0.4.4}/mrok/cli/commands/admin/list/extensions.py +0 -0
  41. {mrok-0.4.2 → mrok-0.4.4}/mrok/cli/commands/admin/list/instances.py +0 -0
  42. {mrok-0.4.2 → mrok-0.4.4}/mrok/cli/commands/admin/register/__init__.py +0 -0
  43. {mrok-0.4.2 → mrok-0.4.4}/mrok/cli/commands/admin/register/extensions.py +0 -0
  44. {mrok-0.4.2 → mrok-0.4.4}/mrok/cli/commands/admin/register/instances.py +0 -0
  45. {mrok-0.4.2 → mrok-0.4.4}/mrok/cli/commands/admin/unregister/__init__.py +0 -0
  46. {mrok-0.4.2 → mrok-0.4.4}/mrok/cli/commands/admin/unregister/extensions.py +0 -0
  47. {mrok-0.4.2 → mrok-0.4.4}/mrok/cli/commands/admin/unregister/instances.py +0 -0
  48. {mrok-0.4.2 → mrok-0.4.4}/mrok/cli/commands/admin/utils.py +0 -0
  49. {mrok-0.4.2 → mrok-0.4.4}/mrok/cli/commands/agent/__init__.py +0 -0
  50. {mrok-0.4.2 → mrok-0.4.4}/mrok/cli/commands/agent/dev/__init__.py +0 -0
  51. {mrok-0.4.2 → mrok-0.4.4}/mrok/cli/commands/agent/dev/console.py +0 -0
  52. {mrok-0.4.2 → mrok-0.4.4}/mrok/cli/commands/agent/dev/web.py +0 -0
  53. {mrok-0.4.2 → mrok-0.4.4}/mrok/cli/commands/agent/run/__init__.py +0 -0
  54. {mrok-0.4.2 → mrok-0.4.4}/mrok/cli/commands/agent/run/asgi.py +0 -0
  55. {mrok-0.4.2 → mrok-0.4.4}/mrok/cli/commands/agent/run/sidecar.py +0 -0
  56. {mrok-0.4.2 → mrok-0.4.4}/mrok/cli/commands/agent/utils.py +0 -0
  57. {mrok-0.4.2 → mrok-0.4.4}/mrok/cli/commands/controller/__init__.py +0 -0
  58. {mrok-0.4.2 → mrok-0.4.4}/mrok/cli/commands/controller/openapi.py +0 -0
  59. {mrok-0.4.2 → mrok-0.4.4}/mrok/cli/commands/controller/run.py +0 -0
  60. {mrok-0.4.2 → mrok-0.4.4}/mrok/cli/commands/proxy/__init__.py +0 -0
  61. {mrok-0.4.2 → mrok-0.4.4}/mrok/cli/commands/proxy/run.py +0 -0
  62. {mrok-0.4.2 → mrok-0.4.4}/mrok/cli/main.py +0 -0
  63. {mrok-0.4.2 → mrok-0.4.4}/mrok/cli/rich.py +0 -0
  64. {mrok-0.4.2 → mrok-0.4.4}/mrok/cli/utils.py +0 -0
  65. {mrok-0.4.2 → mrok-0.4.4}/mrok/conf.py +0 -0
  66. {mrok-0.4.2 → mrok-0.4.4}/mrok/controller/__init__.py +0 -0
  67. {mrok-0.4.2 → mrok-0.4.4}/mrok/controller/app.py +0 -0
  68. {mrok-0.4.2 → mrok-0.4.4}/mrok/controller/auth.py +0 -0
  69. {mrok-0.4.2 → mrok-0.4.4}/mrok/controller/dependencies/__init__.py +0 -0
  70. {mrok-0.4.2 → mrok-0.4.4}/mrok/controller/dependencies/conf.py +0 -0
  71. {mrok-0.4.2 → mrok-0.4.4}/mrok/controller/dependencies/ziti.py +0 -0
  72. {mrok-0.4.2 → mrok-0.4.4}/mrok/controller/openapi/__init__.py +0 -0
  73. {mrok-0.4.2 → mrok-0.4.4}/mrok/controller/openapi/examples.py +0 -0
  74. {mrok-0.4.2 → mrok-0.4.4}/mrok/controller/openapi/utils.py +0 -0
  75. {mrok-0.4.2 → mrok-0.4.4}/mrok/controller/pagination.py +0 -0
  76. {mrok-0.4.2 → mrok-0.4.4}/mrok/controller/routes/__init__.py +0 -0
  77. {mrok-0.4.2 → mrok-0.4.4}/mrok/controller/routes/extensions.py +0 -0
  78. {mrok-0.4.2 → mrok-0.4.4}/mrok/controller/routes/instances.py +0 -0
  79. {mrok-0.4.2 → mrok-0.4.4}/mrok/controller/schemas.py +0 -0
  80. {mrok-0.4.2 → mrok-0.4.4}/mrok/datastructures.py +0 -0
  81. {mrok-0.4.2 → mrok-0.4.4}/mrok/errors.py +0 -0
  82. {mrok-0.4.2 → mrok-0.4.4}/mrok/http/__init__.py +0 -0
  83. {mrok-0.4.2 → mrok-0.4.4}/mrok/http/config.py +0 -0
  84. {mrok-0.4.2 → mrok-0.4.4}/mrok/http/constants.py +0 -0
  85. {mrok-0.4.2 → mrok-0.4.4}/mrok/http/forwarder.py +0 -0
  86. {mrok-0.4.2 → mrok-0.4.4}/mrok/http/lifespan.py +0 -0
  87. {mrok-0.4.2 → mrok-0.4.4}/mrok/http/middlewares.py +0 -0
  88. {mrok-0.4.2 → mrok-0.4.4}/mrok/http/protocol.py +0 -0
  89. {mrok-0.4.2 → mrok-0.4.4}/mrok/http/server.py +0 -0
  90. {mrok-0.4.2 → mrok-0.4.4}/mrok/http/types.py +0 -0
  91. {mrok-0.4.2 → mrok-0.4.4}/mrok/http/utils.py +0 -0
  92. {mrok-0.4.2 → mrok-0.4.4}/mrok/logging.py +0 -0
  93. {mrok-0.4.2 → mrok-0.4.4}/mrok/master.py +0 -0
  94. {mrok-0.4.2 → mrok-0.4.4}/mrok/metrics.py +0 -0
  95. {mrok-0.4.2 → mrok-0.4.4}/mrok/proxy/__init__.py +0 -0
  96. {mrok-0.4.2 → mrok-0.4.4}/mrok/proxy/main.py +0 -0
  97. {mrok-0.4.2 → mrok-0.4.4}/mrok/ziti/__init__.py +0 -0
  98. {mrok-0.4.2 → mrok-0.4.4}/mrok/ziti/api.py +0 -0
  99. {mrok-0.4.2 → mrok-0.4.4}/mrok/ziti/bootstrap.py +0 -0
  100. {mrok-0.4.2 → mrok-0.4.4}/mrok/ziti/constants.py +0 -0
  101. {mrok-0.4.2 → mrok-0.4.4}/mrok/ziti/errors.py +0 -0
  102. {mrok-0.4.2 → mrok-0.4.4}/mrok/ziti/identities.py +0 -0
  103. {mrok-0.4.2 → mrok-0.4.4}/mrok/ziti/pki.py +0 -0
  104. {mrok-0.4.2 → mrok-0.4.4}/mrok/ziti/services.py +0 -0
  105. {mrok-0.4.2 → mrok-0.4.4}/prod.Dockerfile +0 -0
  106. {mrok-0.4.2 → mrok-0.4.4}/scripts/ziti.sh +0 -0
  107. {mrok-0.4.2 → mrok-0.4.4}/settings.yaml +0 -0
  108. {mrok-0.4.2 → mrok-0.4.4}/snapshot_report.html +0 -0
  109. {mrok-0.4.2 → mrok-0.4.4}/sonar-project.properties +0 -0
  110. {mrok-0.4.2 → mrok-0.4.4}/tests/__init__.py +0 -0
  111. {mrok-0.4.2 → mrok-0.4.4}/tests/agent/__init__.py +0 -0
  112. {mrok-0.4.2 → mrok-0.4.4}/tests/agent/sidecar/__init__.py +0 -0
  113. {mrok-0.4.2 → mrok-0.4.4}/tests/agent/sidecar/__snapshots__/test_inspector/test_inspector_app.svg +0 -0
  114. {mrok-0.4.2 → mrok-0.4.4}/tests/agent/sidecar/__snapshots__/test_inspector/test_inspector_app_empty_card.svg +0 -0
  115. {mrok-0.4.2 → mrok-0.4.4}/tests/agent/sidecar/__snapshots__/test_inspector/test_inspector_app_filed_store_connection.svg +0 -0
  116. {mrok-0.4.2 → mrok-0.4.4}/tests/agent/sidecar/__snapshots__/test_inspector/test_inspector_app_open_card.svg +0 -0
  117. {mrok-0.4.2 → mrok-0.4.4}/tests/agent/sidecar/test_app.py +0 -0
  118. {mrok-0.4.2 → mrok-0.4.4}/tests/agent/sidecar/test_main.py +0 -0
  119. {mrok-0.4.2 → mrok-0.4.4}/tests/agent/test_ziticorn.py +0 -0
  120. {mrok-0.4.2 → mrok-0.4.4}/tests/cli/__init__.py +0 -0
  121. {mrok-0.4.2 → mrok-0.4.4}/tests/cli/admin/__init__.py +0 -0
  122. {mrok-0.4.2 → mrok-0.4.4}/tests/cli/admin/test_bootstrap.py +0 -0
  123. {mrok-0.4.2 → mrok-0.4.4}/tests/cli/admin/test_list.py +0 -0
  124. {mrok-0.4.2 → mrok-0.4.4}/tests/cli/admin/test_register.py +0 -0
  125. {mrok-0.4.2 → mrok-0.4.4}/tests/cli/admin/test_unregister.py +0 -0
  126. {mrok-0.4.2 → mrok-0.4.4}/tests/cli/admin/test_utils.py +0 -0
  127. {mrok-0.4.2 → mrok-0.4.4}/tests/cli/agent/__init__.py +0 -0
  128. {mrok-0.4.2 → mrok-0.4.4}/tests/cli/agent/test_run.py +0 -0
  129. {mrok-0.4.2 → mrok-0.4.4}/tests/cli/controller/__init__.py +0 -0
  130. {mrok-0.4.2 → mrok-0.4.4}/tests/cli/controller/test_openapi.py +0 -0
  131. {mrok-0.4.2 → mrok-0.4.4}/tests/cli/controller/test_run.py +0 -0
  132. {mrok-0.4.2 → mrok-0.4.4}/tests/cli/proxy/__init__.py +0 -0
  133. {mrok-0.4.2 → mrok-0.4.4}/tests/cli/proxy/test_run.py +0 -0
  134. {mrok-0.4.2 → mrok-0.4.4}/tests/cli/test_main.py +0 -0
  135. {mrok-0.4.2 → mrok-0.4.4}/tests/conftest.py +0 -0
  136. {mrok-0.4.2 → mrok-0.4.4}/tests/controller/__init__.py +0 -0
  137. {mrok-0.4.2 → mrok-0.4.4}/tests/controller/test_auth.py +0 -0
  138. {mrok-0.4.2 → mrok-0.4.4}/tests/controller/test_extensions.py +0 -0
  139. {mrok-0.4.2 → mrok-0.4.4}/tests/controller/test_instances.py +0 -0
  140. {mrok-0.4.2 → mrok-0.4.4}/tests/controller/test_openapi.py +0 -0
  141. {mrok-0.4.2 → mrok-0.4.4}/tests/http/__init__.py +0 -0
  142. {mrok-0.4.2 → mrok-0.4.4}/tests/http/test_config.py +0 -0
  143. {mrok-0.4.2 → mrok-0.4.4}/tests/http/test_forwarder.py +0 -0
  144. {mrok-0.4.2 → mrok-0.4.4}/tests/http/test_lifespan.py +0 -0
  145. {mrok-0.4.2 → mrok-0.4.4}/tests/http/test_master.py +0 -0
  146. {mrok-0.4.2 → mrok-0.4.4}/tests/http/test_protocol.py +0 -0
  147. {mrok-0.4.2 → mrok-0.4.4}/tests/http/test_server.py +0 -0
  148. {mrok-0.4.2 → mrok-0.4.4}/tests/proxy/__init__.py +0 -0
  149. {mrok-0.4.2 → mrok-0.4.4}/tests/proxy/test_app.py +0 -0
  150. {mrok-0.4.2 → mrok-0.4.4}/tests/proxy/test_ziti.py +0 -0
  151. {mrok-0.4.2 → mrok-0.4.4}/tests/proxy/test_ziti_branches.py +0 -0
  152. {mrok-0.4.2 → mrok-0.4.4}/tests/types.py +0 -0
  153. {mrok-0.4.2 → mrok-0.4.4}/tests/ziti/__init__.py +0 -0
  154. {mrok-0.4.2 → mrok-0.4.4}/tests/ziti/test_api.py +0 -0
  155. {mrok-0.4.2 → mrok-0.4.4}/tests/ziti/test_bootstrap.py +0 -0
  156. {mrok-0.4.2 → mrok-0.4.4}/tests/ziti/test_identities.py +0 -0
  157. {mrok-0.4.2 → mrok-0.4.4}/tests/ziti/test_pki.py +0 -0
  158. {mrok-0.4.2 → mrok-0.4.4}/tests/ziti/test_services.py +0 -0
  159. {mrok-0.4.2 → mrok-0.4.4}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mrok
3
- Version: 0.4.2
3
+ Version: 0.4.4
4
4
  Summary: MPT Extensions OpenZiti Orchestrator
5
5
  Author: SoftwareOne AG
6
6
  License: Apache License
@@ -1,3 +1,4 @@
1
+ import asyncio
1
2
  import logging
2
3
  from pathlib import Path
3
4
 
@@ -5,7 +6,7 @@ from mrok.conf import get_settings
5
6
  from mrok.http.forwarder import ForwardAppBase
6
7
  from mrok.http.types import Scope, StreamReader, StreamWriter
7
8
  from mrok.logging import setup_logging
8
- from mrok.proxy.ziti import ZitiConnectionManager
9
+ from mrok.proxy.ziti import ZitiSocketCache
9
10
 
10
11
  logger = logging.getLogger("mrok.proxy")
11
12
 
@@ -20,8 +21,6 @@ class ProxyApp(ForwardAppBase):
20
21
  identity_file: str | Path,
21
22
  *,
22
23
  read_chunk_size: int = 65536,
23
- ziti_connection_ttl_seconds: float = 60,
24
- ziti_conn_cache_purge_interval_seconds: float = 10,
25
24
  ) -> None:
26
25
  super().__init__(read_chunk_size=read_chunk_size)
27
26
  self._identity_file = identity_file
@@ -31,11 +30,7 @@ class ProxyApp(ForwardAppBase):
31
30
  if settings.proxy.domain[0] == "."
32
31
  else f".{settings.proxy.domain}"
33
32
  )
34
- self._conn_manager = ZitiConnectionManager(
35
- identity_file,
36
- ttl_seconds=ziti_connection_ttl_seconds,
37
- cleanup_interval=ziti_conn_cache_purge_interval_seconds,
38
- )
33
+ self._ziti_socket_cache = ZitiSocketCache(self._identity_file)
39
34
 
40
35
  def get_target_from_header(self, headers: dict[str, str], name: str) -> str | None:
41
36
  header_value = headers.get(name, "")
@@ -54,10 +49,9 @@ class ProxyApp(ForwardAppBase):
54
49
 
55
50
  async def startup(self):
56
51
  setup_logging(get_settings())
57
- await self._conn_manager.start()
58
52
 
59
53
  async def shutdown(self):
60
- await self._conn_manager.stop()
54
+ await self._ziti_socket_cache.stop()
61
55
 
62
56
  async def select_backend(
63
57
  self,
@@ -65,5 +59,6 @@ class ProxyApp(ForwardAppBase):
65
59
  headers: dict[str, str],
66
60
  ) -> tuple[StreamReader, StreamWriter] | tuple[None, None]:
67
61
  target_name = self.get_target_name(headers)
68
-
69
- return await self._conn_manager.get_or_create(target_name)
62
+ sock = await self._ziti_socket_cache.get_or_create(target_name)
63
+ reader, writer = await asyncio.open_connection(sock=sock)
64
+ return reader, writer
@@ -0,0 +1,102 @@
1
+ import asyncio
2
+ import contextlib
3
+ import logging
4
+ from asyncio import Task
5
+ from pathlib import Path
6
+
7
+ import openziti
8
+ from aiocache import Cache
9
+ from openziti.context import ZitiContext
10
+ from openziti.zitisock import ZitiSocket
11
+
12
+ logger = logging.getLogger("mrok.proxy")
13
+
14
+
15
+ class ZitiSocketCache:
16
+ def __init__(
17
+ self,
18
+ identity_file: str | Path,
19
+ ziti_ctx_timeout_ms: int = 10_000,
20
+ ttl_seconds: float = 60.0,
21
+ cleanup_interval: float = 10.0,
22
+ ) -> None:
23
+ self._identity_file = identity_file
24
+ self._ziti_ctx_timeout_ms = ziti_ctx_timeout_ms
25
+ self._ttl_seconds = ttl_seconds
26
+ self._cleanup_interval = cleanup_interval
27
+
28
+ self._ziti_ctx: ZitiContext | None = None
29
+ self._cache = Cache(Cache.MEMORY)
30
+ self._active_sockets: dict[str, ZitiSocket] = {}
31
+ self._cleanup_task: Task | None = None
32
+
33
+ def _get_ziti_ctx(self) -> ZitiContext:
34
+ if self._ziti_ctx is None:
35
+ ctx, err = openziti.load(str(self._identity_file), timeout=self._ziti_ctx_timeout_ms)
36
+ if err != 0:
37
+ raise Exception(f"Cannot create a Ziti context from the identity file: {err}")
38
+ self._ziti_ctx = ctx
39
+ return self._ziti_ctx
40
+
41
+ async def _create_socket(self, key: str):
42
+ return self._get_ziti_ctx().connect(key)
43
+
44
+ async def get_or_create(self, key: str):
45
+ sock = await self._cache.get(key)
46
+
47
+ if sock:
48
+ await self._cache.expire(key, self._ttl_seconds)
49
+ self._active_sockets[key] = sock
50
+ logger.debug(f"Ziti socket found for service {key}")
51
+ return sock
52
+
53
+ sock = await self._create_socket(key)
54
+ await self._cache.set(key, sock, self._ttl_seconds)
55
+ self._active_sockets[key] = sock
56
+ logger.info(f"New Ziti socket created for service {key}")
57
+ return sock
58
+
59
+ # async def invalidate(self, key: str):
60
+ # sock = await self._cache.get(key)
61
+ # if sock:
62
+ # await self._close_socket(sock)
63
+
64
+ # await self._cache.delete(key)
65
+ # self._active_sockets.pop(key, None)
66
+
67
+ async def start(self):
68
+ self._cleanup_task = asyncio.create_task(self._periodic_cleanup())
69
+ # Warmup ziti context
70
+ self._get_ziti_ctx()
71
+
72
+ async def stop(self):
73
+ self._cleanup_task.cancel()
74
+ with contextlib.suppress(Exception):
75
+ await self._cleanup_task
76
+
77
+ for sock in list(self._active_sockets.values()):
78
+ await self._close_socket(sock)
79
+
80
+ self._active_sockets.clear()
81
+ await self._cache.clear()
82
+
83
+ @staticmethod
84
+ async def _close_socket(sock: ZitiSocket):
85
+ with contextlib.suppress(Exception):
86
+ sock.close()
87
+
88
+ async def _periodic_cleanup(self):
89
+ try:
90
+ while True:
91
+ await asyncio.sleep(self._cleanup_interval)
92
+ await self._cleanup_once()
93
+ except asyncio.CancelledError:
94
+ return
95
+
96
+ async def _cleanup_once(self):
97
+ expired = {key for key in self._active_sockets.keys() if not self._cache.exists(key)}
98
+ for key in expired:
99
+ logger.debug(f"Cleaning up expired socket connection {key}")
100
+ sock = self._active_sockets.pop(key, None)
101
+ if sock:
102
+ await self._close_socket(sock)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mrok"
3
- version = "0.4.2"
3
+ version = "0.4.4"
4
4
  description = "MPT Extensions OpenZiti Orchestrator"
5
5
  readme = { file = "README.md", content-type = "text/markdown" }
6
6
  authors = [
@@ -1,12 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass
4
-
5
- from mrok.http.types import StreamReader, StreamWriter
6
-
7
-
8
- @dataclass
9
- class CachedStreamEntry:
10
- reader: StreamReader
11
- writer: StreamWriter
12
- last_access: float
@@ -1,124 +0,0 @@
1
- import asyncio
2
-
3
- from mrok.proxy.types import ConnectionCache
4
-
5
-
6
- class CachedStreamReader:
7
- def __init__(
8
- self,
9
- reader: asyncio.StreamReader,
10
- key: str,
11
- manager: ConnectionCache,
12
- ):
13
- self._reader = reader
14
- self._key = key
15
- self._manager = manager
16
-
17
- async def read(self, n: int = -1) -> bytes:
18
- try:
19
- return await self._reader.read(n)
20
- except (
21
- asyncio.CancelledError,
22
- asyncio.IncompleteReadError,
23
- asyncio.LimitOverrunError,
24
- BrokenPipeError,
25
- ConnectionAbortedError,
26
- ConnectionResetError,
27
- RuntimeError,
28
- TimeoutError,
29
- UnicodeDecodeError,
30
- ):
31
- asyncio.create_task(self._manager.invalidate(self._key))
32
- raise
33
-
34
- async def readexactly(self, n: int) -> bytes:
35
- try:
36
- return await self._reader.readexactly(n)
37
- except (
38
- asyncio.CancelledError,
39
- asyncio.IncompleteReadError,
40
- asyncio.LimitOverrunError,
41
- BrokenPipeError,
42
- ConnectionAbortedError,
43
- ConnectionResetError,
44
- RuntimeError,
45
- TimeoutError,
46
- UnicodeDecodeError,
47
- ):
48
- asyncio.create_task(self._manager.invalidate(self._key))
49
- raise
50
-
51
- async def readline(self) -> bytes:
52
- try:
53
- return await self._reader.readline()
54
- except (
55
- asyncio.CancelledError,
56
- asyncio.IncompleteReadError,
57
- asyncio.LimitOverrunError,
58
- BrokenPipeError,
59
- ConnectionAbortedError,
60
- ConnectionResetError,
61
- RuntimeError,
62
- TimeoutError,
63
- UnicodeDecodeError,
64
- ):
65
- asyncio.create_task(self._manager.invalidate(self._key))
66
- raise
67
-
68
- def at_eof(self) -> bool:
69
- return self._reader.at_eof()
70
-
71
- @property
72
- def underlying(self) -> asyncio.StreamReader:
73
- return self._reader
74
-
75
-
76
- class CachedStreamWriter:
77
- def __init__(
78
- self,
79
- writer: asyncio.StreamWriter,
80
- key: str,
81
- manager: ConnectionCache,
82
- ):
83
- self._writer = writer
84
- self._key = key
85
- self._manager = manager
86
-
87
- def write(self, data: bytes) -> None:
88
- try:
89
- return self._writer.write(data)
90
- except (RuntimeError, TypeError):
91
- asyncio.create_task(self._manager.invalidate(self._key))
92
- raise
93
-
94
- async def drain(self) -> None:
95
- try:
96
- return await self._writer.drain()
97
- except (
98
- asyncio.CancelledError,
99
- BrokenPipeError,
100
- ConnectionAbortedError,
101
- ConnectionResetError,
102
- RuntimeError,
103
- TimeoutError,
104
- ):
105
- asyncio.create_task(self._manager.invalidate(self._key))
106
- raise
107
-
108
- def close(self) -> None:
109
- return self._writer.close()
110
-
111
- async def wait_closed(self) -> None:
112
- try:
113
- return await self._writer.wait_closed()
114
- except (ConnectionResetError, BrokenPipeError):
115
- asyncio.create_task(self._manager.invalidate(self._key))
116
- raise
117
-
118
- @property
119
- def transport(self):
120
- return self._writer.transport
121
-
122
- @property
123
- def underlying(self) -> asyncio.StreamWriter:
124
- return self._writer
@@ -1,11 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import Protocol
4
-
5
- from mrok.http.types import StreamReader, StreamWriter
6
-
7
- StreamPair = tuple[StreamReader, StreamWriter]
8
-
9
-
10
- class ConnectionCache(Protocol):
11
- async def invalidate(self, key: str) -> None: ...
@@ -1,117 +0,0 @@
1
- import asyncio
2
- import contextlib
3
- import logging
4
- from pathlib import Path
5
-
6
- import openziti
7
- from aiocache import Cache
8
-
9
- from mrok.proxy.streams import CachedStreamReader, CachedStreamWriter
10
- from mrok.proxy.types import StreamPair
11
-
12
- logger = logging.getLogger("mrok.proxy")
13
-
14
-
15
- class ZitiConnectionManager:
16
- def __init__(
17
- self,
18
- identity_file: str | Path,
19
- ziti_timeout_ms: int = 10000,
20
- ttl_seconds: float = 60.0,
21
- cleanup_interval: float = 10.0,
22
- ):
23
- self.identity_file = identity_file
24
- self.ziti_timeout_ms = ziti_timeout_ms
25
- self.ttl_seconds = ttl_seconds
26
- self.cleanup_interval = cleanup_interval
27
-
28
- self.cache = Cache(Cache.MEMORY)
29
-
30
- self._active_pairs: dict[str, StreamPair] = {}
31
-
32
- self._cleanup_task: asyncio.Task | None = None
33
- self._ziti_ctx: openziti.context.ZitiContext | None = None
34
-
35
- async def create_stream_pair(self, key: str) -> StreamPair:
36
- if not self._ziti_ctx:
37
- raise Exception("ZitiConnectionManager is not started")
38
- sock = self._ziti_ctx.connect(key)
39
- orig_reader, orig_writer = await asyncio.open_connection(sock=sock)
40
-
41
- reader = CachedStreamReader(orig_reader, key, self)
42
- writer = CachedStreamWriter(orig_writer, key, self)
43
- return (reader, writer)
44
-
45
- async def get_or_create(self, key: str) -> StreamPair:
46
- pair = await self.cache.get(key)
47
-
48
- if pair:
49
- logger.info(f"return cached connection for {key}")
50
- await self.cache.set(key, pair, ttl=self.ttl_seconds)
51
- self._active_pairs[key] = pair
52
- return pair
53
-
54
- pair = await self.create_stream_pair(key)
55
- await self.cache.set(key, pair, ttl=self.ttl_seconds)
56
- self._active_pairs[key] = pair
57
- logger.info(f"return new connection for {key}")
58
- return pair
59
-
60
- async def invalidate(self, key: str) -> None:
61
- logger.info(f"invalidating connection for {key}")
62
- pair = await self.cache.get(key)
63
- if pair:
64
- await self._close_pair(pair)
65
-
66
- await self.cache.delete(key)
67
- self._active_pairs.pop(key, None)
68
-
69
- async def start(self) -> None:
70
- if self._cleanup_task is None:
71
- self._cleanup_task = asyncio.create_task(self._periodic_cleanup())
72
- if self._ziti_ctx is None:
73
- ctx, err = openziti.load(str(self.identity_file), timeout=self.ziti_timeout_ms)
74
- if err != 0:
75
- raise Exception(f"Cannot create a Ziti context from the identity file: {err}")
76
- self._ziti_ctx = ctx
77
-
78
- async def stop(self) -> None:
79
- if self._cleanup_task:
80
- self._cleanup_task.cancel()
81
- with contextlib.suppress(Exception):
82
- await self._cleanup_task
83
-
84
- for pair in list(self._active_pairs.values()):
85
- await self._close_pair(pair)
86
-
87
- self._active_pairs.clear()
88
- await self.cache.clear()
89
- openziti.shutdown()
90
-
91
- @staticmethod
92
- async def _close_pair(pair: StreamPair) -> None:
93
- reader, writer = pair
94
- writer.close()
95
- with contextlib.suppress(Exception):
96
- await writer.wait_closed()
97
-
98
- async def _periodic_cleanup(self) -> None:
99
- try:
100
- while True:
101
- await asyncio.sleep(self.cleanup_interval)
102
- await self._cleanup_once()
103
- except asyncio.CancelledError:
104
- return
105
-
106
- async def _cleanup_once(self) -> None:
107
- # Keys currently stored in aiocache
108
- keys_in_cache = set(await self.cache.keys())
109
- # Keys we think are alive
110
- known_keys = set(self._active_pairs.keys())
111
-
112
- expired_keys = known_keys - keys_in_cache
113
-
114
- for key in expired_keys:
115
- pair = self._active_pairs.pop(key, None)
116
- if pair:
117
- await self._close_pair(pair)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes