e0e362d700
SDK Tests / changes (push) Successful in 2m29s
Real E2E Tests / changes (push) Successful in 2m29s
Deploy Docs Pages / build (push) Has been cancelled
Deploy Docs Pages / deploy (push) Has been cancelled
Real E2E Tests / JavaScript E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Python E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Java E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / C# E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Go E2E (docker bridge) (push) Has been cancelled
Real E2E Tests / Real E2E CI (push) Has been cancelled
SDK Tests / SDK CI (push) Has been cancelled
SDK Tests / CLI Tests (push) Has been cancelled
SDK Tests / Python SDK Quality (code-interpreter) (push) Has been cancelled
SDK Tests / Python SDK Quality (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / JavaScript SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Python SDK Tests (sandbox) (push) Has been cancelled
SDK Tests / CLI Quality (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Kotlin SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (code-interpreter) (push) Has been cancelled
SDK Tests / C# SDK Quality And Tests (sandbox) (push) Has been cancelled
SDK Tests / Go SDK Quality And Tests (push) Has been cancelled
561 lines
18 KiB
Python
561 lines
18 KiB
Python
#
|
|
# Copyright 2025 Alibaba Group Holding Ltd.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
#
|
|
from __future__ import annotations
|
|
|
|
from datetime import timedelta
|
|
from uuid import uuid4
|
|
|
|
import pytest
|
|
|
|
from opensandbox.config.connection_sync import ConnectionConfigSync
|
|
from opensandbox.constants import DEFAULT_EGRESS_PORT, DEFAULT_EXECD_PORT
|
|
from opensandbox.exceptions import SandboxReadyTimeoutException
|
|
from opensandbox.models.diagnostics import DiagnosticContent
|
|
from opensandbox.models.sandboxes import NetworkPolicy, NetworkRule, SandboxEndpoint
|
|
from opensandbox.sync.sandbox import SandboxSync
|
|
|
|
|
|
class _Noop:
|
|
pass
|
|
|
|
|
|
class _SandboxServiceStub:
|
|
def __init__(self) -> None:
|
|
self.endpoint_calls: list[tuple[object, int, bool]] = []
|
|
|
|
def get_sandbox_endpoint(self, sandbox_id, port: int, use_server_proxy: bool = False) -> SandboxEndpoint:
|
|
self.endpoint_calls.append((sandbox_id, port, use_server_proxy))
|
|
return SandboxEndpoint(endpoint=f"sync-egress:{port}", headers={"X-Egress": "1"})
|
|
|
|
|
|
class _EgressServiceStub:
|
|
def __init__(self) -> None:
|
|
self.patch_calls: list[list[NetworkRule]] = []
|
|
|
|
def get_policy(self) -> NetworkPolicy:
|
|
return NetworkPolicy(
|
|
defaultAction="deny",
|
|
egress=[NetworkRule(action="allow", target="pypi.org")],
|
|
)
|
|
|
|
def patch_rules(self, rules: list[NetworkRule]) -> None:
|
|
self.patch_calls.append(rules)
|
|
|
|
|
|
class _DiagnosticsServiceStub:
|
|
def __init__(self) -> None:
|
|
self.calls: list[tuple[str, object, str | None]] = []
|
|
|
|
def get_logs(self, sandbox_id: str, scope: str) -> DiagnosticContent:
|
|
self.calls.append(("logs", sandbox_id, scope))
|
|
return DiagnosticContent(
|
|
sandboxId=sandbox_id,
|
|
kind="logs",
|
|
scope=scope or "container",
|
|
delivery="inline",
|
|
contentType="text/plain; charset=utf-8",
|
|
content="log line",
|
|
truncated=False,
|
|
)
|
|
|
|
def get_events(self, sandbox_id: str, scope: str) -> DiagnosticContent:
|
|
self.calls.append(("events", sandbox_id, scope))
|
|
return DiagnosticContent(
|
|
sandboxId=sandbox_id,
|
|
kind="events",
|
|
scope=scope or "runtime",
|
|
delivery="inline",
|
|
contentType="text/plain; charset=utf-8",
|
|
content="event line",
|
|
truncated=False,
|
|
)
|
|
|
|
|
|
def test_sync_check_ready_timeout_message_includes_troubleshooting_hints() -> None:
|
|
def _always_false(_: SandboxSync) -> bool:
|
|
return False
|
|
|
|
sbx = SandboxSync(
|
|
sandbox_id=str(uuid4()),
|
|
sandbox_service=_Noop(),
|
|
filesystem_service=_Noop(),
|
|
command_service=_Noop(),
|
|
health_service=_Noop(),
|
|
metrics_service=_Noop(),
|
|
egress_service=_EgressServiceStub(),
|
|
diagnostics_service=_DiagnosticsServiceStub(),
|
|
connection_config=ConnectionConfigSync(
|
|
domain="10.0.0.2:8080",
|
|
use_server_proxy=False,
|
|
),
|
|
custom_health_check=_always_false,
|
|
)
|
|
|
|
with pytest.raises(SandboxReadyTimeoutException) as exc_info:
|
|
sbx.check_ready(timeout=timedelta(seconds=0.01), polling_interval=timedelta(seconds=0))
|
|
|
|
message = str(exc_info.value)
|
|
assert "ConnectionConfig(domain=10.0.0.2:8080, use_server_proxy=False)" in message
|
|
assert "ConnectionConfigSync(use_server_proxy=True)" in message
|
|
|
|
|
|
def test_sync_get_egress_policy_uses_injected_egress_service() -> None:
|
|
sbx = SandboxSync(
|
|
sandbox_id=str(uuid4()),
|
|
sandbox_service=_SandboxServiceStub(),
|
|
filesystem_service=_Noop(),
|
|
command_service=_Noop(),
|
|
health_service=_Noop(),
|
|
metrics_service=_Noop(),
|
|
egress_service=_EgressServiceStub(),
|
|
diagnostics_service=_DiagnosticsServiceStub(),
|
|
connection_config=ConnectionConfigSync(use_server_proxy=True),
|
|
)
|
|
|
|
policy = sbx.get_egress_policy()
|
|
|
|
assert policy.default_action == "deny"
|
|
assert policy.egress is not None
|
|
assert policy.egress[0].target == "pypi.org"
|
|
|
|
|
|
def test_sync_patch_egress_rules_uses_injected_egress_service() -> None:
|
|
svc = _SandboxServiceStub()
|
|
egress_service = _EgressServiceStub()
|
|
|
|
sbx = SandboxSync(
|
|
sandbox_id=str(uuid4()),
|
|
sandbox_service=svc,
|
|
filesystem_service=_Noop(),
|
|
command_service=_Noop(),
|
|
health_service=_Noop(),
|
|
metrics_service=_Noop(),
|
|
egress_service=egress_service,
|
|
diagnostics_service=_DiagnosticsServiceStub(),
|
|
connection_config=ConnectionConfigSync(use_server_proxy=False),
|
|
)
|
|
rules = [NetworkRule(action="allow", target="www.github.com")]
|
|
|
|
sbx.patch_egress_rules(rules)
|
|
|
|
assert svc.endpoint_calls == []
|
|
assert egress_service.patch_calls == [rules]
|
|
|
|
|
|
def test_sync_get_diagnostics_uses_injected_diagnostics_service() -> None:
|
|
diagnostics_service = _DiagnosticsServiceStub()
|
|
sbx = SandboxSync(
|
|
sandbox_id=str(uuid4()),
|
|
sandbox_service=_SandboxServiceStub(),
|
|
filesystem_service=_Noop(),
|
|
command_service=_Noop(),
|
|
health_service=_Noop(),
|
|
metrics_service=_Noop(),
|
|
egress_service=_EgressServiceStub(),
|
|
diagnostics_service=diagnostics_service,
|
|
connection_config=ConnectionConfigSync(use_server_proxy=True),
|
|
)
|
|
|
|
logs = sbx.get_diagnostic_logs(scope="container")
|
|
events = sbx.diagnostics.get_events(sbx.id, scope="runtime")
|
|
|
|
assert logs.kind == "logs"
|
|
assert events.kind == "events"
|
|
assert diagnostics_service.calls == [
|
|
("logs", sbx.id, "container"),
|
|
("events", sbx.id, "runtime"),
|
|
]
|
|
|
|
|
|
def test_sync_create_resolves_egress_endpoint_and_builds_service(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
egress_service = _EgressServiceStub()
|
|
factory_calls: list[SandboxEndpoint] = []
|
|
|
|
class _CreateResponse:
|
|
id = "sync-created"
|
|
|
|
class _SandboxServiceCreateStub:
|
|
def __init__(self) -> None:
|
|
self.endpoint_calls: list[tuple[str, int, bool]] = []
|
|
|
|
def create_sandbox(self, *_args, **_kwargs):
|
|
return _CreateResponse()
|
|
|
|
def get_sandbox_endpoint(self, sandbox_id, port: int, use_server_proxy: bool = False) -> SandboxEndpoint:
|
|
self.endpoint_calls.append((sandbox_id, port, use_server_proxy))
|
|
return SandboxEndpoint(endpoint=f"sync-egress:{port}", headers={"X-Port": str(port)})
|
|
|
|
def kill_sandbox(self, _sandbox_id: str) -> None:
|
|
return None
|
|
|
|
class _FactoryStub:
|
|
def __init__(self, connection_config: ConnectionConfigSync) -> None:
|
|
self.connection_config = connection_config
|
|
|
|
def create_sandbox_service(self):
|
|
return sandbox_service
|
|
|
|
def create_filesystem_service(self, endpoint: SandboxEndpoint):
|
|
return _Noop()
|
|
|
|
def create_command_service(self, endpoint: SandboxEndpoint):
|
|
return _Noop()
|
|
|
|
def create_health_service(self, endpoint: SandboxEndpoint):
|
|
return _Noop()
|
|
|
|
def create_metrics_service(self, endpoint: SandboxEndpoint):
|
|
return _Noop()
|
|
|
|
def create_egress_service(self, endpoint: SandboxEndpoint) -> _EgressServiceStub:
|
|
factory_calls.append(endpoint)
|
|
return egress_service
|
|
|
|
def create_diagnostics_service(self):
|
|
return _DiagnosticsServiceStub()
|
|
|
|
def create_isolated_session_service(self, endpoint):
|
|
return _Noop()
|
|
|
|
sandbox_service = _SandboxServiceCreateStub()
|
|
monkeypatch.setattr("opensandbox.sync.sandbox.AdapterFactorySync", _FactoryStub)
|
|
|
|
SandboxSync.create(
|
|
"python:3.11",
|
|
connection_config=ConnectionConfigSync(use_server_proxy=False),
|
|
health_check=lambda _sbx: True,
|
|
)
|
|
|
|
assert sandbox_service.endpoint_calls == [
|
|
("sync-created", DEFAULT_EXECD_PORT, False),
|
|
("sync-created", DEFAULT_EGRESS_PORT, False),
|
|
]
|
|
assert len(factory_calls) == 1
|
|
assert factory_calls == [
|
|
SandboxEndpoint(
|
|
endpoint=f"sync-egress:{DEFAULT_EGRESS_PORT}",
|
|
headers={"X-Port": str(DEFAULT_EGRESS_PORT)},
|
|
)
|
|
]
|
|
|
|
|
|
def test_sync_create_passes_new_signature_keywords_even_when_unused(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
class _CreateResponse:
|
|
id = "sync-created"
|
|
|
|
class _SandboxServiceCreateStub:
|
|
def create_sandbox(
|
|
self,
|
|
spec,
|
|
entrypoint,
|
|
env,
|
|
metadata,
|
|
timeout,
|
|
resource,
|
|
network_policy,
|
|
extensions,
|
|
volumes,
|
|
platform=None,
|
|
secure_access=False,
|
|
snapshot_id=None,
|
|
credential_proxy=None,
|
|
resource_requests=None,
|
|
):
|
|
assert spec is not None
|
|
assert entrypoint is not None
|
|
assert isinstance(env, dict)
|
|
assert isinstance(metadata, dict)
|
|
assert timeout is not None
|
|
assert isinstance(resource, dict)
|
|
assert isinstance(network_policy, NetworkPolicy)
|
|
assert isinstance(extensions, dict)
|
|
assert volumes is None
|
|
assert platform is None
|
|
assert secure_access is False
|
|
assert snapshot_id is None
|
|
return _CreateResponse()
|
|
|
|
def get_sandbox_endpoint(self, _sandbox_id, port: int, _use_server_proxy: bool = False):
|
|
return SandboxEndpoint(endpoint=f"sync-egress:{port}")
|
|
|
|
def kill_sandbox(self, _sandbox_id: str) -> None:
|
|
return None
|
|
|
|
class _FactoryStub:
|
|
def __init__(self, _connection_config: ConnectionConfigSync) -> None:
|
|
pass
|
|
|
|
def create_sandbox_service(self):
|
|
return _SandboxServiceCreateStub()
|
|
|
|
def create_filesystem_service(self, _endpoint):
|
|
return _Noop()
|
|
|
|
def create_command_service(self, _endpoint):
|
|
return _Noop()
|
|
|
|
def create_health_service(self, _endpoint):
|
|
return _Noop()
|
|
|
|
def create_metrics_service(self, _endpoint):
|
|
return _Noop()
|
|
|
|
def create_egress_service(self, _endpoint):
|
|
return _EgressServiceStub()
|
|
|
|
def create_diagnostics_service(self):
|
|
return _DiagnosticsServiceStub()
|
|
|
|
def create_isolated_session_service(self, endpoint):
|
|
return _Noop()
|
|
|
|
monkeypatch.setattr("opensandbox.sync.sandbox.AdapterFactorySync", _FactoryStub)
|
|
SandboxSync.create(
|
|
"python:3.11",
|
|
network_policy=NetworkPolicy(
|
|
defaultAction="deny",
|
|
egress=[NetworkRule(action="allow", target="pypi.org")],
|
|
),
|
|
skip_health_check=True,
|
|
)
|
|
|
|
|
|
def test_sync_create_preserves_manual_cleanup_timeout(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
class _CreateResponse:
|
|
id = "sync-created"
|
|
|
|
class _SandboxServiceCreateStub:
|
|
def __init__(self) -> None:
|
|
self.create_calls: list[tuple[tuple[object, ...], dict[str, object]]] = []
|
|
|
|
def create_sandbox(self, *args, **kwargs):
|
|
self.create_calls.append((args, kwargs))
|
|
return _CreateResponse()
|
|
|
|
def get_sandbox_endpoint(
|
|
self, _sandbox_id, port: int, _use_server_proxy: bool = False
|
|
) -> SandboxEndpoint:
|
|
return SandboxEndpoint(endpoint=f"sync-egress:{port}")
|
|
|
|
def kill_sandbox(self, _sandbox_id: str) -> None:
|
|
return None
|
|
|
|
class _FactoryStub:
|
|
def __init__(self, _connection_config: ConnectionConfigSync) -> None:
|
|
pass
|
|
|
|
def create_sandbox_service(self):
|
|
return sandbox_service
|
|
|
|
def create_filesystem_service(self, _endpoint: SandboxEndpoint):
|
|
return _Noop()
|
|
|
|
def create_command_service(self, _endpoint: SandboxEndpoint):
|
|
return _Noop()
|
|
|
|
def create_health_service(self, _endpoint: SandboxEndpoint):
|
|
return _Noop()
|
|
|
|
def create_metrics_service(self, _endpoint: SandboxEndpoint):
|
|
return _Noop()
|
|
|
|
def create_egress_service(self, _endpoint: SandboxEndpoint):
|
|
return _EgressServiceStub()
|
|
|
|
def create_diagnostics_service(self):
|
|
return _DiagnosticsServiceStub()
|
|
|
|
def create_isolated_session_service(self, endpoint):
|
|
return _Noop()
|
|
|
|
sandbox_service = _SandboxServiceCreateStub()
|
|
monkeypatch.setattr("opensandbox.sync.sandbox.AdapterFactorySync", _FactoryStub)
|
|
|
|
sandbox = SandboxSync.create(
|
|
"python:3.11",
|
|
timeout=None,
|
|
skip_health_check=True,
|
|
connection_config=ConnectionConfigSync(),
|
|
)
|
|
|
|
assert sandbox.id == "sync-created"
|
|
assert len(sandbox_service.create_calls) == 1
|
|
args, kwargs = sandbox_service.create_calls[0]
|
|
assert args == ()
|
|
assert kwargs["timeout"] is None
|
|
|
|
|
|
def test_sync_create_restore_from_snapshot_passes_snapshot_id(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
class _CreateResponse:
|
|
id = "sync-created"
|
|
|
|
class _SandboxServiceCreateStub:
|
|
def create_sandbox(
|
|
self,
|
|
spec,
|
|
entrypoint,
|
|
env,
|
|
metadata,
|
|
timeout,
|
|
resource,
|
|
network_policy,
|
|
extensions,
|
|
volumes,
|
|
platform=None,
|
|
secure_access=False,
|
|
snapshot_id=None,
|
|
credential_proxy=None,
|
|
resource_requests=None,
|
|
):
|
|
assert isinstance(env, dict)
|
|
assert isinstance(metadata, dict)
|
|
assert timeout is not None
|
|
assert isinstance(resource, dict)
|
|
assert network_policy is None
|
|
assert isinstance(extensions, dict)
|
|
assert volumes is None
|
|
assert platform is None
|
|
assert secure_access is False
|
|
assert snapshot_id == "snap-123"
|
|
assert spec is None
|
|
assert entrypoint == ["tail", "-f", "/dev/null"]
|
|
return _CreateResponse()
|
|
|
|
def get_sandbox_endpoint(self, _sandbox_id, port: int, _use_server_proxy: bool = False):
|
|
return SandboxEndpoint(endpoint=f"sync-egress:{port}")
|
|
|
|
def kill_sandbox(self, _sandbox_id: str) -> None:
|
|
return None
|
|
|
|
class _FactoryStub:
|
|
def __init__(self, _connection_config: ConnectionConfigSync) -> None:
|
|
pass
|
|
|
|
def create_sandbox_service(self):
|
|
return _SandboxServiceCreateStub()
|
|
|
|
def create_filesystem_service(self, _endpoint):
|
|
return _Noop()
|
|
|
|
def create_command_service(self, _endpoint):
|
|
return _Noop()
|
|
|
|
def create_health_service(self, _endpoint):
|
|
return _Noop()
|
|
|
|
def create_metrics_service(self, _endpoint):
|
|
return _Noop()
|
|
|
|
def create_egress_service(self, _endpoint):
|
|
return _EgressServiceStub()
|
|
|
|
def create_diagnostics_service(self):
|
|
return _DiagnosticsServiceStub()
|
|
|
|
def create_isolated_session_service(self, endpoint):
|
|
return _Noop()
|
|
|
|
monkeypatch.setattr("opensandbox.sync.sandbox.AdapterFactorySync", _FactoryStub)
|
|
SandboxSync.create(snapshot_id="snap-123", skip_health_check=True)
|
|
|
|
|
|
def test_sync_create_restore_from_snapshot_preserves_custom_entrypoint(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
class _CreateResponse:
|
|
id = "sync-created"
|
|
|
|
class _SandboxServiceCreateStub:
|
|
def create_sandbox(
|
|
self,
|
|
spec,
|
|
entrypoint,
|
|
env,
|
|
metadata,
|
|
timeout,
|
|
resource,
|
|
network_policy,
|
|
extensions,
|
|
volumes,
|
|
platform=None,
|
|
secure_access=False,
|
|
snapshot_id=None,
|
|
credential_proxy=None,
|
|
resource_requests=None,
|
|
):
|
|
assert isinstance(env, dict)
|
|
assert isinstance(metadata, dict)
|
|
assert timeout is not None
|
|
assert isinstance(resource, dict)
|
|
assert network_policy is None
|
|
assert isinstance(extensions, dict)
|
|
assert volumes is None
|
|
assert platform is None
|
|
assert secure_access is False
|
|
assert snapshot_id == "snap-123"
|
|
assert spec is None
|
|
assert entrypoint == ["python", "app.py"]
|
|
return _CreateResponse()
|
|
|
|
def get_sandbox_endpoint(self, _sandbox_id, port: int, _use_server_proxy: bool = False):
|
|
return SandboxEndpoint(endpoint=f"sync-egress:{port}")
|
|
|
|
def kill_sandbox(self, _sandbox_id: str) -> None:
|
|
return None
|
|
|
|
class _FactoryStub:
|
|
def __init__(self, _connection_config: ConnectionConfigSync) -> None:
|
|
pass
|
|
|
|
def create_sandbox_service(self):
|
|
return _SandboxServiceCreateStub()
|
|
|
|
def create_filesystem_service(self, _endpoint):
|
|
return _Noop()
|
|
|
|
def create_command_service(self, _endpoint):
|
|
return _Noop()
|
|
|
|
def create_health_service(self, _endpoint):
|
|
return _Noop()
|
|
|
|
def create_metrics_service(self, _endpoint):
|
|
return _Noop()
|
|
|
|
def create_egress_service(self, _endpoint):
|
|
return _EgressServiceStub()
|
|
|
|
def create_diagnostics_service(self):
|
|
return _DiagnosticsServiceStub()
|
|
|
|
def create_isolated_session_service(self, endpoint):
|
|
return _Noop()
|
|
|
|
monkeypatch.setattr("opensandbox.sync.sandbox.AdapterFactorySync", _FactoryStub)
|
|
SandboxSync.create(
|
|
snapshot_id="snap-123",
|
|
entrypoint=["python", "app.py"],
|
|
skip_health_check=True,
|
|
)
|