mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-07-01 01:19:38 +02:00
feat: make bootstrapper initialiser timeouts configurable (#999)
* feat: make bootstrapper initialiser timeouts configurable DefaultFlowStart and WorkspaceInit hardcoded the request timeouts for their flow-svc and IAM calls, leaving operators no way to tune them for high-latency environments (#874). Expose them as constructor parameters threaded through the existing initialiser `params:` mechanism, defaulting to the current values so behaviour is unchanged unless explicitly overridden: - DefaultFlowStart: list_timeout=10 (list-flows), start_timeout=30 (start-flow) - WorkspaceInit: iam_timeout=10 (create-workspace) Add unit tests for the defaults, override storage, and that configured values reach the underlying request calls. * test: mark async bootstrap test with @pytest.mark.asyncio Addresses review feedback on PR #999: add the explicit @pytest.mark.asyncio decorator to test_run_forwards_configured_timeouts so it does not rely on asyncio_mode=auto and stays consistent with the rest of the suite.
This commit is contained in:
parent
5cb4f83afa
commit
1aa9549912
4 changed files with 85 additions and 5 deletions
54
tests/unit/test_bootstrap/test_default_flow_start.py
Normal file
54
tests/unit/test_bootstrap/test_default_flow_start.py
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
"""
|
||||||
|
Unit tests for trustgraph.bootstrap.initialisers.DefaultFlowStart
|
||||||
|
|
||||||
|
Verifies the list/start timeouts are configurable and that the
|
||||||
|
configured values actually reach the flow-client request calls.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from unittest.mock import AsyncMock, MagicMock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from trustgraph.bootstrap.initialisers.default_flow_start import (
|
||||||
|
DefaultFlowStart,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_default_timeouts():
|
||||||
|
init = DefaultFlowStart(blueprint="bp")
|
||||||
|
assert init.list_timeout == 10
|
||||||
|
assert init.start_timeout == 30
|
||||||
|
|
||||||
|
|
||||||
|
def test_timeout_overrides_are_stored():
|
||||||
|
init = DefaultFlowStart(blueprint="bp", list_timeout=5, start_timeout=99)
|
||||||
|
assert init.list_timeout == 5
|
||||||
|
assert init.start_timeout == 99
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_run_forwards_configured_timeouts():
|
||||||
|
init = DefaultFlowStart(blueprint="bp", list_timeout=5, start_timeout=99)
|
||||||
|
|
||||||
|
# Flow client: list-flows returns no error + empty flow list,
|
||||||
|
# start-flow returns no error.
|
||||||
|
flow = MagicMock()
|
||||||
|
flow.start = AsyncMock()
|
||||||
|
flow.stop = AsyncMock()
|
||||||
|
flow.request = AsyncMock(side_effect=[
|
||||||
|
MagicMock(error=None, flow_ids=[]), # list-flows response
|
||||||
|
MagicMock(error=None), # start-flow response
|
||||||
|
])
|
||||||
|
|
||||||
|
# Context: workspace "default" exists, hands back our mock flow client.
|
||||||
|
ctx = MagicMock()
|
||||||
|
ctx.logger = MagicMock()
|
||||||
|
ctx.config.keys = AsyncMock(return_value=["default"])
|
||||||
|
ctx.make_flow_client = MagicMock(return_value=flow)
|
||||||
|
|
||||||
|
await init.run(ctx, None, "v1")
|
||||||
|
|
||||||
|
calls = flow.request.call_args_list
|
||||||
|
assert len(calls) == 2
|
||||||
|
assert calls[0].kwargs["timeout"] == 5
|
||||||
|
assert calls[1].kwargs["timeout"] == 99
|
||||||
13
tests/unit/test_bootstrap/test_workspace_init.py
Normal file
13
tests/unit/test_bootstrap/test_workspace_init.py
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
"""Unit tests for trustgraph.bootstrap.initialisers.WorkspaceInit."""
|
||||||
|
|
||||||
|
from trustgraph.bootstrap.initialisers.workspace_init import WorkspaceInit
|
||||||
|
|
||||||
|
|
||||||
|
def test_default_iam_timeout():
|
||||||
|
init = WorkspaceInit()
|
||||||
|
assert init.iam_timeout == 10
|
||||||
|
|
||||||
|
|
||||||
|
def test_iam_timeout_override_is_stored():
|
||||||
|
init = WorkspaceInit(iam_timeout=42)
|
||||||
|
assert init.iam_timeout == 42
|
||||||
|
|
@ -18,6 +18,10 @@ description : str (default "Default")
|
||||||
Human-readable description passed to flow-svc.
|
Human-readable description passed to flow-svc.
|
||||||
parameters : dict (optional)
|
parameters : dict (optional)
|
||||||
Optional parameter overrides passed to start-flow.
|
Optional parameter overrides passed to start-flow.
|
||||||
|
list_timeout : int (default 10)
|
||||||
|
Timeout in seconds for the list-flows request.
|
||||||
|
start_timeout : int (default 30)
|
||||||
|
Timeout in seconds for the start-flow request.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from trustgraph.schema import FlowRequest
|
from trustgraph.schema import FlowRequest
|
||||||
|
|
@ -34,6 +38,8 @@ class DefaultFlowStart(Initialiser):
|
||||||
blueprint=None,
|
blueprint=None,
|
||||||
description="Default",
|
description="Default",
|
||||||
parameters=None,
|
parameters=None,
|
||||||
|
list_timeout=10,
|
||||||
|
start_timeout=30,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
@ -46,6 +52,8 @@ class DefaultFlowStart(Initialiser):
|
||||||
self.blueprint = blueprint
|
self.blueprint = blueprint
|
||||||
self.description = description
|
self.description = description
|
||||||
self.parameters = dict(parameters) if parameters else {}
|
self.parameters = dict(parameters) if parameters else {}
|
||||||
|
self.list_timeout = list_timeout
|
||||||
|
self.start_timeout = start_timeout
|
||||||
|
|
||||||
async def run(self, ctx, old_flag, new_flag):
|
async def run(self, ctx, old_flag, new_flag):
|
||||||
|
|
||||||
|
|
@ -70,7 +78,7 @@ class DefaultFlowStart(Initialiser):
|
||||||
FlowRequest(
|
FlowRequest(
|
||||||
operation="list-flows",
|
operation="list-flows",
|
||||||
),
|
),
|
||||||
timeout=10,
|
timeout=self.list_timeout,
|
||||||
)
|
)
|
||||||
if list_resp.error:
|
if list_resp.error:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
|
|
@ -99,7 +107,7 @@ class DefaultFlowStart(Initialiser):
|
||||||
description=self.description,
|
description=self.description,
|
||||||
parameters=self.parameters,
|
parameters=self.parameters,
|
||||||
),
|
),
|
||||||
timeout=30,
|
timeout=self.start_timeout,
|
||||||
)
|
)
|
||||||
if resp.error:
|
if resp.error:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,9 @@ seed_file : str (required when source=="seed-file")
|
||||||
Path to a JSON seed file with the same shape TemplateSeed consumes.
|
Path to a JSON seed file with the same shape TemplateSeed consumes.
|
||||||
overwrite : bool (default False)
|
overwrite : bool (default False)
|
||||||
On re-run (flag change), if True overwrite all keys; if False,
|
On re-run (flag change), if True overwrite all keys; if False,
|
||||||
upsert-missing-only (preserves in-workspace customisations).
|
upsert-missing-only (preserves in-workspace customisations)
|
||||||
|
iam_timeout : int (default 10)
|
||||||
|
Timeout in seconds for the IAM create-workspace request.
|
||||||
|
|
||||||
Raises (in ``run``)
|
Raises (in ``run``)
|
||||||
-------------------
|
-------------------
|
||||||
|
|
@ -41,7 +43,9 @@ class WorkspaceInit(Initialiser):
|
||||||
source="template",
|
source="template",
|
||||||
seed_file=None,
|
seed_file=None,
|
||||||
overwrite=False,
|
overwrite=False,
|
||||||
|
iam_timeout=10,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
|
|
||||||
):
|
):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
|
@ -59,6 +63,7 @@ class WorkspaceInit(Initialiser):
|
||||||
self.source = source
|
self.source = source
|
||||||
self.seed_file = seed_file
|
self.seed_file = seed_file
|
||||||
self.overwrite = overwrite
|
self.overwrite = overwrite
|
||||||
|
self.iam_timeout = iam_timeout
|
||||||
|
|
||||||
async def run(self, ctx, old_flag, new_flag):
|
async def run(self, ctx, old_flag, new_flag):
|
||||||
await self._create_workspace(ctx)
|
await self._create_workspace(ctx)
|
||||||
|
|
@ -120,10 +125,10 @@ class WorkspaceInit(Initialiser):
|
||||||
workspace_record=WorkspaceInput(
|
workspace_record=WorkspaceInput(
|
||||||
id=self.workspace,
|
id=self.workspace,
|
||||||
name=self.workspace.title(),
|
name=self.workspace.title(),
|
||||||
enabled=True,
|
enabled=True,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
timeout=10,
|
timeout=self.iam_timeout,
|
||||||
)
|
)
|
||||||
if resp.error:
|
if resp.error:
|
||||||
if resp.error.type == "duplicate":
|
if resp.error.type == "duplicate":
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue