mrok 0.2.1__py3-none-any.whl → 0.2.3__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.
mrok/controller/app.py CHANGED
@@ -51,9 +51,15 @@ def setup_app():
51
51
 
52
52
  # TODO: Add healthcheck
53
53
  app.include_router(
54
- extensions_router, prefix="/extensions", dependencies=[Depends(authenticate)]
54
+ extensions_router,
55
+ prefix="/extensions",
56
+ dependencies=[Depends(authenticate)],
57
+ )
58
+ app.include_router(
59
+ instances_router,
60
+ prefix="/instances",
61
+ dependencies=[Depends(authenticate)],
55
62
  )
56
- app.include_router(instances_router, prefix="/instances", dependencies=[Depends(authenticate)])
57
63
 
58
64
  settings = get_settings()
59
65
 
@@ -1,5 +1,5 @@
1
1
  import logging
2
- from typing import Annotated
2
+ from typing import Annotated, Literal
3
3
 
4
4
  from fastapi import APIRouter, Body, HTTPException, status
5
5
 
@@ -116,8 +116,26 @@ async def create_extension(
116
116
  async def get_extension_by_id_or_extension_id(
117
117
  mgmt_api: ZitiManagementAPI,
118
118
  id_or_extension_id: str,
119
+ with_instances: Literal["none", "online", "offline"] = "none",
119
120
  ):
120
- return ExtensionRead(**(await fetch_extension_or_404(mgmt_api, id_or_extension_id)))
121
+ extension = await fetch_extension_or_404(mgmt_api, id_or_extension_id)
122
+
123
+ if with_instances == "none":
124
+ return ExtensionRead(**extension)
125
+
126
+ instances = list(
127
+ filter(
128
+ lambda ir: ir.status == with_instances,
129
+ [
130
+ InstanceRead(**identity)
131
+ async for identity in mgmt_api.identities(
132
+ {"filter": f'tags.{MROK_SERVICE_TAG_NAME} = "{extension["name"]}"'}
133
+ )
134
+ ],
135
+ )
136
+ )
137
+
138
+ return ExtensionRead(**extension, instances=instances)
121
139
 
122
140
 
123
141
  @router.delete(
@@ -272,11 +290,7 @@ async def get_instance_by_id_or_instance_id(
272
290
  id_or_instance_id: str,
273
291
  ):
274
292
  identity = await fetch_instance_or_404(mgmt_api, id_or_extension_id, id_or_instance_id)
275
- return InstanceRead(
276
- id=identity["id"],
277
- name=identity["name"],
278
- tags=identity["tags"],
279
- )
293
+ return InstanceRead(**identity)
280
294
 
281
295
 
282
296
  @router.delete(
@@ -6,6 +6,7 @@ from mrok.controller.dependencies import ZitiManagementAPI
6
6
  from mrok.controller.openapi import examples
7
7
  from mrok.controller.pagination import LimitOffsetPage, paginate
8
8
  from mrok.controller.schemas import InstanceRead
9
+ from mrok.ziti.constants import MROK_IDENTITY_TYPE_TAG_NAME, MROK_IDENTITY_TYPE_TAG_VALUE_INSTANCE
9
10
 
10
11
  logger = logging.getLogger("mrok.controller")
11
12
 
@@ -68,4 +69,7 @@ async def get_instance_by_id_or_instance_id(
68
69
  async def get_instances(
69
70
  mgmt_api: ZitiManagementAPI,
70
71
  ):
71
- return await paginate(mgmt_api, "/identities", InstanceRead)
72
+ params = {
73
+ "filter": f'tags.{MROK_IDENTITY_TYPE_TAG_NAME}="{MROK_IDENTITY_TYPE_TAG_VALUE_INSTANCE}"'
74
+ }
75
+ return await paginate(mgmt_api, "/identities", InstanceRead, extra_params=params)
@@ -1,4 +1,6 @@
1
- from typing import Annotated, Any
1
+ from __future__ import annotations
2
+
3
+ from typing import Annotated, Any, Literal
2
4
 
3
5
  from pydantic import (
4
6
  BaseModel,
@@ -34,6 +36,7 @@ class ExtensionBase(BaseSchema):
34
36
 
35
37
  class ExtensionRead(BaseSchema, IdSchema):
36
38
  name: str
39
+ instances: list[InstanceRead] | None = None
37
40
 
38
41
  @computed_field
39
42
  def extension(self) -> dict:
@@ -51,6 +54,11 @@ class InstanceBase(BaseSchema):
51
54
  class InstanceRead(BaseSchema, IdSchema):
52
55
  name: str
53
56
  identity: dict[str, Any] | None = None
57
+ has_edge_router_connection: bool | None = Field(
58
+ False,
59
+ alias="hasEdgeRouterConnection",
60
+ exclude=True,
61
+ )
54
62
 
55
63
  @computed_field
56
64
  def instance(self) -> dict:
@@ -62,6 +70,10 @@ class InstanceRead(BaseSchema, IdSchema):
62
70
  _, extension_id = self.name.split(".", 1)
63
71
  return {"id": extension_id.upper()}
64
72
 
73
+ @computed_field
74
+ def status(self) -> Literal["online", "offline"]:
75
+ return "online" if bool(self.has_edge_router_connection) else "offline"
76
+
65
77
 
66
78
  class InstanceCreate(InstanceBase):
67
79
  pass
mrok/http/master.py CHANGED
@@ -2,6 +2,7 @@ import logging
2
2
  import os
3
3
  import signal
4
4
  import threading
5
+ import time
5
6
  from collections.abc import Callable
6
7
  from pathlib import Path
7
8
 
@@ -11,6 +12,10 @@ from watchfiles.run import CombinedProcess, start_process
11
12
 
12
13
  logger = logging.getLogger("mrok.agent")
13
14
 
15
+ MONITOR_THREAD_JOIN_TIMEOUT = 5
16
+ MONITOR_THREAD_CHECK_DELAY = 1
17
+ MONITOR_THREAD_ERROR_DELAY = 3
18
+
14
19
 
15
20
  def print_path(path):
16
21
  try:
@@ -29,7 +34,7 @@ class Master:
29
34
  self.start_fn = start_fn
30
35
  self.workers = workers
31
36
  self.reload = reload
32
- self.worker_processes: list[CombinedProcess] = []
37
+ self.worker_processes: dict[int, CombinedProcess] = {}
33
38
  self.stop_event = threading.Event()
34
39
  self.watch_filter = PythonFilter(ignore_paths=None)
35
40
  self.watcher = watch(
@@ -39,6 +44,7 @@ class Master:
39
44
  yield_on_timeout=True,
40
45
  )
41
46
  self.setup_signals_handler()
47
+ self.monitor_thread = None
42
48
 
43
49
  def setup_signals_handler(self):
44
50
  for sig in (signal.SIGINT, signal.SIGTERM):
@@ -47,26 +53,50 @@ class Master:
47
53
  def handle_signal(self, *args, **kwargs):
48
54
  self.stop_event.set()
49
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
+
50
67
  def start(self):
51
- for _ in range(self.workers):
52
- p = start_process(
53
- self.start_fn,
54
- "function",
55
- (),
56
- None,
57
- )
58
- logger.info(f"Worker [{p.pid}] started")
59
- self.worker_processes.append(p)
68
+ for i in range(self.workers):
69
+ p = self.start_worker(i)
70
+ self.worker_processes[i] = p
60
71
 
61
72
  def stop(self):
62
- for process in self.worker_processes:
73
+ for process in self.worker_processes.values():
63
74
  process.stop(sigint_timeout=5, sigkill_timeout=1)
64
- self.worker_processes = []
75
+ self.worker_processes.clear()
65
76
 
66
77
  def restart(self):
67
78
  self.stop()
68
79
  self.start()
69
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
+
70
100
  def __iter__(self):
71
101
  return self
72
102
 
@@ -79,12 +109,24 @@ class Master:
79
109
  def run(self):
80
110
  self.start()
81
111
  logger.info(f"Master process started: {os.getpid()}")
82
- if self.reload:
83
- for files_changed in self:
84
- if files_changed:
85
- logger.warning(
86
- f"{', '.join(map(print_path, files_changed))} changed, reloading...",
87
- )
88
- self.restart()
89
- else:
90
- self.stop_event.wait()
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()
mrok/ziti/api.py CHANGED
@@ -397,7 +397,7 @@ class ZitiManagementAPI(BaseZitiAPI):
397
397
  async def search_config_type(self, id_or_name: str) -> dict[str, Any] | None:
398
398
  return await self.search_by_id_or_name("/config-types", id_or_name)
399
399
 
400
- async def delete_config_type(self, config_type_id: str) -> dict[str, Any] | None:
400
+ async def delete_config_type(self, config_type_id: str) -> None:
401
401
  return await self.delete("/config-types", config_type_id)
402
402
 
403
403
  async def get_identity(self, identity_id: str) -> dict[str, Any]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mrok
3
- Version: 0.2.1
3
+ Version: 0.2.3
4
4
  Summary: MPT Extensions OpenZiti Orchestrator
5
5
  Author: SoftwareOne AG
6
6
  License: Apache License
@@ -31,10 +31,10 @@ mrok/cli/commands/controller/__init__.py,sha256=2xw-YVN0akiLiuGUU3XbYyZZ0ugOjQ6X
31
31
  mrok/cli/commands/controller/openapi.py,sha256=QLjVao9UkB2vBaGkFi_q_jrlg4Np4ldMRwDIJsrJ7A8,1175
32
32
  mrok/cli/commands/controller/run.py,sha256=osyjssb81xNMYZLPb6dfPR4W_BQlCxKDfvl-BIhG_1A,2460
33
33
  mrok/controller/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
- mrok/controller/app.py,sha256=_MzTu_wFRbD4kGbw4PdP5OYpl3KDlOCJFeNWxt-WM-c,1818
34
+ mrok/controller/app.py,sha256=XxCIB7N1YE52vSYfvGW2UPgEEOZ9jxDMe2l9D2SfXi8,1866
35
35
  mrok/controller/auth.py,sha256=Kg94W8yNMs6TvUmLRYv1QeUjDy4qlGZ-_6OHa4KH1zg,2648
36
36
  mrok/controller/pagination.py,sha256=raYpYa34q8Ckl4BXBOEdpWlKkFj6z7e6QLWr2HT7dzI,2187
37
- mrok/controller/schemas.py,sha256=zk91PIJ0zncpBgs4bhU-n-76EWlRKo87r3VVx15JxPc,1320
37
+ mrok/controller/schemas.py,sha256=AaF8_bEwZTHM02apVEBAzlUb2t71zoxYaG-VHtPNeMk,1705
38
38
  mrok/controller/dependencies/__init__.py,sha256=voewk6gjkA0OarL6HFmfT_RLqBns0Fpl-VIqK5xVAEI,202
39
39
  mrok/controller/dependencies/conf.py,sha256=2Pa8fxJHkZ29q6UL-w6hUP_wr7WnNELfw5LlzWg1Tec,162
40
40
  mrok/controller/dependencies/ziti.py,sha256=fYoxeJb4s6p2_3gxbExbFSRabjpvp_gZMBb3ocXZV3Y,702
@@ -42,25 +42,25 @@ mrok/controller/openapi/__init__.py,sha256=U1dw45w76CcoQagyqg_FXdMuJF3qJZZM6wG8T
42
42
  mrok/controller/openapi/examples.py,sha256=ZI0BP7L6sI0z7Mq1I3uc2UrweGpzpPeGSIuf1bUKkgg,1419
43
43
  mrok/controller/openapi/utils.py,sha256=Kn55ISAWlMJNwrJTum7iFrBvJvr81To76pCK8W-s79Q,1114
44
44
  mrok/controller/routes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
45
- mrok/controller/routes/extensions.py,sha256=j-dLO4yLkUj-rdjnGhO2suvmR690-UdaNvokDZ7ZpF0,8670
46
- mrok/controller/routes/instances.py,sha256=rcpbKfLsYsokOo7aZamzQks-c9AYoU-8KKK9pDYpoOY,1963
45
+ mrok/controller/routes/extensions.py,sha256=jd3OGsJ_ESLQbOJ1mkzU8F44QgPe3R23dIapkNjmYDY,9126
46
+ mrok/controller/routes/instances.py,sha256=v-fn_F6JHbDZ4YUNCIZzClgHp6aC1Eu5HB7k7qBG5pk,2202
47
47
  mrok/http/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
48
  mrok/http/config.py,sha256=k8mjvD3ninJn-v1t-co-GSa3upm4b70bWyk3fwdcOh8,2161
49
49
  mrok/http/forwarder.py,sha256=mo-Z8B8Zg6kdDX-lWEiptRv-9kJU9cEdmg6gt6eF0cc,11374
50
50
  mrok/http/lifespan.py,sha256=9qevhD_5Y0f8fGTh2axdfWx7v1K4vnWtiUNyJLesOHE,262
51
- mrok/http/master.py,sha256=o_0Sxe2XuTgVAwvBbWkYcO3HkCcfvYP4rgxcuIDPwXo,2426
51
+ mrok/http/master.py,sha256=TwU78yE_GQecogBs_PDpl3gY7_jWYNIRNaAXRzi0rvY,4152
52
52
  mrok/http/protocol.py,sha256=ap8jbLUvgbAH81ZJZCBkQiYR7mkV_eL3rpfwEkoE8sU,392
53
53
  mrok/http/server.py,sha256=Mj7C85fc-DXp-WTBWaOd7ag808oliLmFBH5bf-G2FHg,370
54
54
  mrok/ziti/__init__.py,sha256=20OWMiexRhOovZOX19zlX87-V78QyWnEnSZfyAftUdE,263
55
- mrok/ziti/api.py,sha256=onVOSgcqALTeC3EBX-RPJvkqQ3OhhzvJ91HPsGaIJC4,16171
55
+ mrok/ziti/api.py,sha256=KvGiT9d4oSgC3JbFWLDQyuHcLX2HuZJoJ8nHmWtCDkY,16154
56
56
  mrok/ziti/bootstrap.py,sha256=QIDhlkIxPW2QRuumFq2D1WDbD003P5f3z24pAUsyeBI,2696
57
57
  mrok/ziti/constants.py,sha256=Urq1X3bCBQZfw8NbnEa1pqmY4oq1wmzkwPfzam3kbTw,339
58
58
  mrok/ziti/errors.py,sha256=yYCbVDwktnR0AYduqtynIjo73K3HOhIrwA_vQimvEd4,368
59
59
  mrok/ziti/identities.py,sha256=oE_3j6Y4xCr6uKNdprW55bxGsyKnmJt-MrxrylB2Ey4,5388
60
60
  mrok/ziti/pki.py,sha256=o2tySqHC8-7bvFuI2Tqxg9vX6H6ZSxWxfP_9x29e19M,1954
61
61
  mrok/ziti/services.py,sha256=JnznLTHNZjgbFwnBtv7y2XIp4NiQxLVawwP9EfWdVuM,3208
62
- mrok-0.2.1.dist-info/METADATA,sha256=6pEGhIuJ0KJNyG3QXfLouC71PaFpywYGg2AEWrrQFLg,15546
63
- mrok-0.2.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
64
- mrok-0.2.1.dist-info/entry_points.txt,sha256=tloXwvU1uJicBJR2h-8HoVclPgwJWDwuREMHN8Zq-nU,38
65
- mrok-0.2.1.dist-info/licenses/LICENSE.txt,sha256=6PaICaoA3yNsZKLv5G6OKqSfLSoX7MakYqTDgJoTCBs,11346
66
- mrok-0.2.1.dist-info/RECORD,,
62
+ mrok-0.2.3.dist-info/METADATA,sha256=HCW-FucwR1yddkxR12eUY63wEXxTiaeNlEIMKpORVUo,15546
63
+ mrok-0.2.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
64
+ mrok-0.2.3.dist-info/entry_points.txt,sha256=tloXwvU1uJicBJR2h-8HoVclPgwJWDwuREMHN8Zq-nU,38
65
+ mrok-0.2.3.dist-info/licenses/LICENSE.txt,sha256=6PaICaoA3yNsZKLv5G6OKqSfLSoX7MakYqTDgJoTCBs,11346
66
+ mrok-0.2.3.dist-info/RECORD,,
File without changes