fix(sandbox): scope provisioner PVC data by user (#2973)
* fix(sandbox): scope provisioner PVC data by user * Address provisioner PVC review feedback
This commit is contained in:
@@ -21,6 +21,8 @@ import logging
|
||||
|
||||
import requests
|
||||
|
||||
from deerflow.runtime.user_context import get_effective_user_id
|
||||
|
||||
from .backend import SandboxBackend
|
||||
from .sandbox_info import SandboxInfo
|
||||
|
||||
@@ -138,6 +140,7 @@ class RemoteSandboxBackend(SandboxBackend):
|
||||
json={
|
||||
"sandbox_id": sandbox_id,
|
||||
"thread_id": thread_id,
|
||||
"user_id": get_effective_user_id(),
|
||||
},
|
||||
timeout=30,
|
||||
)
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
"""Tests for AioSandboxProvider mount helpers."""
|
||||
|
||||
import importlib
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from deerflow.config.paths import Paths, join_host_path
|
||||
from deerflow.runtime.user_context import reset_current_user, set_current_user
|
||||
|
||||
# ── ensure_thread_dirs ───────────────────────────────────────────────────────
|
||||
|
||||
@@ -136,3 +138,36 @@ def test_discover_or_create_only_unlocks_when_lock_succeeds(tmp_path, monkeypatc
|
||||
provider._discover_or_create_with_lock("thread-5", "sandbox-5")
|
||||
|
||||
assert unlock_calls == []
|
||||
|
||||
|
||||
def test_remote_backend_create_forwards_effective_user_id(monkeypatch):
|
||||
"""Provisioner mode must receive user_id so PVC subPath matches user isolation."""
|
||||
remote_mod = importlib.import_module("deerflow.community.aio_sandbox.remote_backend")
|
||||
backend = remote_mod.RemoteSandboxBackend("http://provisioner:8002")
|
||||
token = set_current_user(SimpleNamespace(id="user-7"))
|
||||
posted: dict = {}
|
||||
|
||||
class _Response:
|
||||
def raise_for_status(self):
|
||||
return None
|
||||
|
||||
def json(self):
|
||||
return {"sandbox_url": "http://sandbox.local"}
|
||||
|
||||
def _post(url, json, timeout): # noqa: A002 - mirrors requests.post kwarg
|
||||
posted.update({"url": url, "json": json, "timeout": timeout})
|
||||
return _Response()
|
||||
|
||||
monkeypatch.setattr(remote_mod.requests, "post", _post)
|
||||
|
||||
try:
|
||||
backend.create("thread-42", "sandbox-42")
|
||||
finally:
|
||||
reset_current_user(token)
|
||||
|
||||
assert posted["url"] == "http://provisioner:8002/api/sandboxes"
|
||||
assert posted["json"] == {
|
||||
"sandbox_id": "sandbox-42",
|
||||
"thread_id": "thread-42",
|
||||
"user_id": "user-7",
|
||||
}
|
||||
|
||||
@@ -92,12 +92,19 @@ class TestBuildVolumeMounts:
|
||||
userdata_mount = mounts[1]
|
||||
assert userdata_mount.sub_path is None
|
||||
|
||||
def test_pvc_sets_subpath(self, provisioner_module):
|
||||
"""PVC mode should set sub_path to threads/{thread_id}/user-data."""
|
||||
def test_pvc_sets_user_scoped_subpath(self, provisioner_module):
|
||||
"""PVC mode should include user_id in the user-data subPath."""
|
||||
provisioner_module.USERDATA_PVC_NAME = "my-pvc"
|
||||
mounts = provisioner_module._build_volume_mounts("thread-42", user_id="user-7")
|
||||
userdata_mount = mounts[1]
|
||||
assert userdata_mount.sub_path == "deer-flow/users/user-7/threads/thread-42/user-data"
|
||||
|
||||
def test_pvc_defaults_to_default_user_subpath(self, provisioner_module):
|
||||
"""Older callers should still land under a stable default user namespace."""
|
||||
provisioner_module.USERDATA_PVC_NAME = "my-pvc"
|
||||
mounts = provisioner_module._build_volume_mounts("thread-42")
|
||||
userdata_mount = mounts[1]
|
||||
assert userdata_mount.sub_path == "threads/thread-42/user-data"
|
||||
assert userdata_mount.sub_path == "deer-flow/users/default/threads/thread-42/user-data"
|
||||
|
||||
def test_skills_mount_read_only(self, provisioner_module):
|
||||
"""Skills mount should always be read-only."""
|
||||
@@ -146,13 +153,12 @@ class TestBuildPodVolumes:
|
||||
pod = provisioner_module._build_pod("sandbox-1", "thread-1")
|
||||
assert len(pod.spec.containers[0].volume_mounts) == 2
|
||||
|
||||
def test_pod_pvc_mode(self, provisioner_module):
|
||||
"""Pod should use PVC volumes when PVC names are configured."""
|
||||
def test_pod_pvc_mode_uses_user_scoped_subpath(self, provisioner_module):
|
||||
"""Pod should use a user-scoped subPath for PVC user-data."""
|
||||
provisioner_module.SKILLS_PVC_NAME = "skills-pvc"
|
||||
provisioner_module.USERDATA_PVC_NAME = "userdata-pvc"
|
||||
pod = provisioner_module._build_pod("sandbox-1", "thread-1")
|
||||
pod = provisioner_module._build_pod("sandbox-1", "thread-1", user_id="user-7")
|
||||
assert pod.spec.volumes[0].persistent_volume_claim is not None
|
||||
assert pod.spec.volumes[1].persistent_volume_claim is not None
|
||||
# subPath should be set on user-data mount
|
||||
userdata_mount = pod.spec.containers[0].volume_mounts[1]
|
||||
assert userdata_mount.sub_path == "threads/thread-1/user-data"
|
||||
assert userdata_mount.sub_path == "deer-flow/users/user-7/threads/thread-1/user-data"
|
||||
|
||||
@@ -144,7 +144,11 @@ def test_provisioner_create_returns_sandbox_info(monkeypatch):
|
||||
|
||||
def mock_post(url: str, json: dict, timeout: int):
|
||||
assert url == "http://provisioner:8002/api/sandboxes"
|
||||
assert json == {"sandbox_id": "abc123", "thread_id": "thread-1"}
|
||||
assert json == {
|
||||
"sandbox_id": "abc123",
|
||||
"thread_id": "thread-1",
|
||||
"user_id": "test-user-autouse",
|
||||
}
|
||||
assert timeout == 30
|
||||
return _StubResponse(payload={"sandbox_id": "abc123", "sandbox_url": "http://k3s:31001"})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user