Skip to content

Commit 0deae2a

Browse files
YunchuWangCopilot
andcommitted
Validate on-demand sandbox substrate
Require DTS_SUBSTRATE to be injected as Sandbox or AcaSessionPool when constructing an on-demand sandbox worker. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent d3f1f99 commit 0deae2a

2 files changed

Lines changed: 51 additions & 1 deletion

File tree

durabletask-azuremanaged/durabletask/azuremanaged/preview/on_demand_sandbox/worker.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ def __init__(self) -> None:
4545
resolved_secure_channel = _resolve_secure_channel(resolved_host_address)
4646
resolved_token_credential = _resolve_token_credential()
4747
resolved_max_concurrent_activities = _resolve_max_concurrent_activities()
48+
resolved_substrate = _resolve_substrate()
4849
concurrency_options = ConcurrencyOptions(
4950
maximum_concurrent_activity_work_items=resolved_max_concurrent_activities)
5051

@@ -64,7 +65,7 @@ def __init__(self) -> None:
6465
self._on_demand_sandbox_worker_profile_id = _resolve_worker_profile_id()
6566
self._on_demand_sandbox_activity_names: list[str] = []
6667
self._on_demand_sandbox_max_activities = resolved_max_concurrent_activities
67-
self._on_demand_sandbox_substrate = os.getenv("DTS_SUBSTRATE")
68+
self._on_demand_sandbox_substrate = resolved_substrate
6869
self._on_demand_sandbox_dts_sandbox_identifier = os.getenv("DTS_SANDBOX_ID")
6970
self._on_demand_sandbox_heartbeat_interval_seconds = 2.0
7071
self._on_demand_sandbox_registration_stop = threading.Event()
@@ -211,6 +212,21 @@ def _resolve_token_credential() -> TokenCredential | None:
211212
return ManagedIdentityCredential(client_id=client_id.strip())
212213

213214

215+
def _resolve_substrate() -> str:
216+
substrate = os.getenv("DTS_SUBSTRATE")
217+
if not substrate:
218+
raise ValueError(
219+
"On-demand sandbox worker requires DTS_SUBSTRATE to be injected in the "
220+
"sandbox environment.")
221+
222+
normalized = substrate.strip()
223+
if normalized.lower() not in ("sandbox", "acasessionpool"):
224+
raise ValueError(
225+
"On-demand sandbox worker requires DTS_SUBSTRATE to be Sandbox or AcaSessionPool.")
226+
227+
return normalized
228+
229+
214230
def _resolve_max_concurrent_activities() -> int:
215231
value = os.getenv("DTS_ON_DEMAND_SANDBOX_MAX_ACTIVITIES")
216232
max_concurrent_activities = (

tests/durabletask-azuremanaged/test_on_demand_sandbox_extension.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,7 @@ def test_on_demand_sandbox_activities_client_does_not_expose_worker_registration
309309
def test_on_demand_sandbox_worker_does_not_own_legacy_wakeup_server(monkeypatch) -> None:
310310
monkeypatch.setenv("DTS_ENDPOINT", "http://localhost:8080")
311311
monkeypatch.setenv("DTS_TASK_HUB", "env-hub")
312+
monkeypatch.setenv("DTS_SUBSTRATE", "Sandbox")
312313

313314
worker = OnDemandSandboxWorker()
314315

@@ -358,6 +359,7 @@ def OtherActivity(_ctx, value):
358359
def test_on_demand_sandbox_worker_stop_keeps_handle_for_still_running_registration_thread(monkeypatch) -> None:
359360
monkeypatch.setenv("DTS_ENDPOINT", "http://localhost:8080")
360361
monkeypatch.setenv("DTS_TASK_HUB", "env-hub")
362+
monkeypatch.setenv("DTS_SUBSTRATE", "Sandbox")
361363

362364
class StillRunningThread:
363365
def __init__(self):
@@ -383,6 +385,7 @@ def is_alive(self):
383385
def test_on_demand_sandbox_worker_uses_scheduler_channel_without_credential(monkeypatch) -> None:
384386
monkeypatch.setenv("DTS_ENDPOINT", "https://example.scheduler")
385387
monkeypatch.setenv("DTS_TASK_HUB", "env-hub")
388+
monkeypatch.setenv("DTS_SUBSTRATE", "Sandbox")
386389

387390
worker = OnDemandSandboxWorker()
388391

@@ -393,6 +396,7 @@ def test_on_demand_sandbox_worker_uses_scheduler_channel_without_credential(monk
393396
def test_on_demand_sandbox_worker_ignores_legacy_max_activities(monkeypatch) -> None:
394397
monkeypatch.setenv("DTS_ENDPOINT", "https://example.scheduler")
395398
monkeypatch.setenv("DTS_TASK_HUB", "env-hub")
399+
monkeypatch.setenv("DTS_SUBSTRATE", "Sandbox")
396400
monkeypatch.delenv("DTS_ON_DEMAND_SANDBOX_MAX_ACTIVITIES", raising=False)
397401
monkeypatch.setenv("DTS_" + "SERVER" + "LESS_MAX_ACTIVITIES", "7")
398402

@@ -404,6 +408,7 @@ def test_on_demand_sandbox_worker_ignores_legacy_max_activities(monkeypatch) ->
404408
def test_on_demand_sandbox_worker_tracks_active_activity_count_with_hooks(monkeypatch) -> None:
405409
monkeypatch.setenv("DTS_ENDPOINT", "https://example.scheduler")
406410
monkeypatch.setenv("DTS_TASK_HUB", "env-hub")
411+
monkeypatch.setenv("DTS_SUBSTRATE", "Sandbox")
407412

408413
worker = OnDemandSandboxWorker()
409414

@@ -420,6 +425,7 @@ def test_on_demand_sandbox_worker_tracks_active_activity_count_with_hooks(monkey
420425
def test_on_demand_sandbox_worker_uses_managed_identity_credential_when_injected(monkeypatch) -> None:
421426
monkeypatch.setenv("DTS_ENDPOINT", "https://example.scheduler")
422427
monkeypatch.setenv("DTS_TASK_HUB", "env-hub")
428+
monkeypatch.setenv("DTS_SUBSTRATE", "Sandbox")
423429
monkeypatch.setenv("DTS_AUTHENTICATION", "ManagedIdentity")
424430
monkeypatch.setenv("DTS_UMI_CLIENT_ID", "worker-client-id")
425431
monkeypatch.setattr(sandbox_worker, "ManagedIdentityCredential", _FakeManagedIdentityCredential)
@@ -434,6 +440,7 @@ def test_on_demand_sandbox_worker_uses_managed_identity_credential_when_injected
434440
def test_on_demand_sandbox_worker_requires_managed_identity_client_id_when_auth_enabled(monkeypatch) -> None:
435441
monkeypatch.setenv("DTS_ENDPOINT", "https://example.scheduler")
436442
monkeypatch.setenv("DTS_TASK_HUB", "env-hub")
443+
monkeypatch.setenv("DTS_SUBSTRATE", "Sandbox")
437444
monkeypatch.setenv("DTS_AUTHENTICATION", "ManagedIdentity")
438445
monkeypatch.delenv("DTS_UMI_CLIENT_ID", raising=False)
439446

@@ -448,6 +455,7 @@ def test_on_demand_sandbox_worker_requires_managed_identity_client_id_when_auth_
448455
def test_on_demand_sandbox_worker_requires_registered_activities(monkeypatch) -> None:
449456
monkeypatch.setenv("DTS_ENDPOINT", "http://localhost:8080")
450457
monkeypatch.setenv("DTS_TASK_HUB", "env-hub")
458+
monkeypatch.setenv("DTS_SUBSTRATE", "Sandbox")
451459

452460
worker = OnDemandSandboxWorker()
453461

@@ -459,6 +467,32 @@ def test_on_demand_sandbox_worker_requires_registered_activities(monkeypatch) ->
459467
raise AssertionError("Expected missing registered activity names to fail.")
460468

461469

470+
def test_on_demand_sandbox_worker_requires_injected_substrate(monkeypatch) -> None:
471+
monkeypatch.setenv("DTS_ENDPOINT", "https://example.scheduler")
472+
monkeypatch.setenv("DTS_TASK_HUB", "env-hub")
473+
monkeypatch.delenv("DTS_SUBSTRATE", raising=False)
474+
475+
try:
476+
OnDemandSandboxWorker()
477+
except ValueError as ex:
478+
assert "DTS_SUBSTRATE" in str(ex)
479+
else:
480+
raise AssertionError("Expected missing DTS_SUBSTRATE to fail.")
481+
482+
483+
def test_on_demand_sandbox_worker_rejects_invalid_substrate(monkeypatch) -> None:
484+
monkeypatch.setenv("DTS_ENDPOINT", "https://example.scheduler")
485+
monkeypatch.setenv("DTS_TASK_HUB", "env-hub")
486+
monkeypatch.setenv("DTS_SUBSTRATE", "ContainerApp")
487+
488+
try:
489+
OnDemandSandboxWorker()
490+
except ValueError as ex:
491+
assert "Sandbox or AcaSessionPool" in str(ex)
492+
else:
493+
raise AssertionError("Expected invalid DTS_SUBSTRATE to fail.")
494+
495+
462496
class _RecordingChannel:
463497
def __init__(self) -> None:
464498
self.methods: list[str] = []

0 commit comments

Comments
 (0)