e0e362d700
Deploy Docs Pages / build (push) Waiting to run
Deploy Docs Pages / deploy (push) Blocked by required conditions
Real E2E Tests / Real E2E CI (push) Blocked by required conditions
SDK Tests / SDK CI (push) Blocked by required conditions
SDK Tests / changes (push) Successful in 2m29s
Real E2E Tests / changes (push) Successful in 2m29s
Real E2E Tests / Python E2E (docker bridge) (push) Waiting to run
Real E2E Tests / C# E2E (docker bridge) (push) Waiting to run
Real E2E Tests / Go E2E (docker bridge) (push) Waiting to run
Real E2E Tests / Java E2E (docker bridge) (push) Waiting to run
Real E2E Tests / JavaScript E2E (docker bridge) (push) Waiting to run
SDK Tests / Python SDK Tests (sandbox) (push) Waiting to run
SDK Tests / CLI Quality (push) Waiting to run
SDK Tests / CLI Tests (push) Waiting to run
SDK Tests / Python SDK Quality (code-interpreter) (push) Waiting to run
SDK Tests / Python SDK Quality (sandbox) (push) Waiting to run
SDK Tests / Python SDK Tests (code-interpreter) (push) Waiting to run
SDK Tests / JavaScript SDK Quality And Tests (code-interpreter) (push) Waiting to run
SDK Tests / JavaScript SDK Quality And Tests (sandbox) (push) Waiting to run
SDK Tests / Kotlin SDK Quality And Tests (sandbox) (push) Waiting to run
SDK Tests / Kotlin SDK Quality And Tests (code-interpreter) (push) Waiting to run
SDK Tests / C# SDK Quality And Tests (code-interpreter) (push) Waiting to run
SDK Tests / C# SDK Quality And Tests (sandbox) (push) Waiting to run
SDK Tests / Go SDK Quality And Tests (push) Waiting to run
227 lines
7.7 KiB
Python
227 lines
7.7 KiB
Python
# Copyright 2026 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.
|
|
|
|
import unittest.mock
|
|
from types import SimpleNamespace
|
|
from unittest.mock import MagicMock
|
|
|
|
import pytest
|
|
from kubernetes.client.exceptions import ApiException
|
|
|
|
from opensandbox_server.config import AppConfig, EgressConfig, RuntimeConfig, SecureRuntimeConfig
|
|
from opensandbox_server.services.runtime_resolver import (
|
|
SecureRuntimeResolver,
|
|
validate_secure_runtime_on_startup,
|
|
)
|
|
|
|
|
|
def _config(runtime_type: str = "docker", secure_runtime=None, egress=None):
|
|
return AppConfig(
|
|
runtime=RuntimeConfig(type=runtime_type, execd_image="opensandbox/execd:test"),
|
|
secure_runtime=secure_runtime,
|
|
egress=egress,
|
|
)
|
|
|
|
|
|
def test_secure_runtime_resolver_disabled_without_runtime() -> None:
|
|
resolver = SecureRuntimeResolver(_config())
|
|
|
|
assert resolver.is_enabled() is False
|
|
assert resolver.get_docker_runtime() is None
|
|
assert resolver.get_k8s_runtime_class() is None
|
|
|
|
|
|
def test_secure_runtime_resolver_prefers_explicit_values_and_defaults() -> None:
|
|
docker_resolver = SecureRuntimeResolver(
|
|
_config(secure_runtime=SecureRuntimeConfig(type="gvisor", docker_runtime="custom-runsc"))
|
|
)
|
|
k8s_resolver = SecureRuntimeResolver(
|
|
_config(
|
|
runtime_type="kubernetes",
|
|
secure_runtime=SecureRuntimeConfig(type="kata", k8s_runtime_class="kata-custom"),
|
|
)
|
|
)
|
|
default_docker = SecureRuntimeResolver(
|
|
_config(secure_runtime=SecureRuntimeConfig(type="gvisor", k8s_runtime_class="gvisor"))
|
|
)
|
|
default_k8s = SecureRuntimeResolver(
|
|
_config(
|
|
runtime_type="kubernetes",
|
|
secure_runtime=SecureRuntimeConfig(type="kata", docker_runtime="kata-runtime"),
|
|
)
|
|
)
|
|
|
|
assert docker_resolver.is_enabled() is True
|
|
assert docker_resolver.get_docker_runtime() == "custom-runsc"
|
|
assert k8s_resolver.get_k8s_runtime_class() == "kata-custom"
|
|
assert default_docker.get_docker_runtime() == "runsc"
|
|
assert default_k8s.get_k8s_runtime_class() == "kata-qemu"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_validate_secure_runtime_skips_when_disabled() -> None:
|
|
docker_client = MagicMock()
|
|
|
|
await validate_secure_runtime_on_startup(_config(), docker_client=docker_client)
|
|
|
|
docker_client.info.assert_not_called()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_validate_secure_runtime_checks_docker_runtime() -> None:
|
|
docker_client = MagicMock()
|
|
docker_client.info.return_value = {"Runtimes": {"runsc": {"path": "/usr/bin/runsc"}}}
|
|
config = _config(
|
|
secure_runtime=SecureRuntimeConfig(type="gvisor", docker_runtime="runsc")
|
|
)
|
|
|
|
await validate_secure_runtime_on_startup(config, docker_client=docker_client)
|
|
|
|
docker_client.info.assert_called_once()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_validate_secure_runtime_rejects_missing_docker_runtime() -> None:
|
|
docker_client = MagicMock()
|
|
docker_client.info.return_value = {"Runtimes": {"runc": {}}}
|
|
config = _config(
|
|
secure_runtime=SecureRuntimeConfig(type="gvisor", docker_runtime="runsc")
|
|
)
|
|
|
|
with pytest.raises(ValueError, match="runsc"):
|
|
await validate_secure_runtime_on_startup(config, docker_client=docker_client)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_validate_secure_runtime_allows_missing_docker_client() -> None:
|
|
config = _config(
|
|
secure_runtime=SecureRuntimeConfig(type="gvisor", docker_runtime="runsc")
|
|
)
|
|
|
|
await validate_secure_runtime_on_startup(config, docker_client=None)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_validate_secure_runtime_checks_k8s_runtime_class() -> None:
|
|
k8s_client = MagicMock()
|
|
config = _config(
|
|
runtime_type="kubernetes",
|
|
secure_runtime=SecureRuntimeConfig(type="gvisor", k8s_runtime_class="gvisor"),
|
|
)
|
|
|
|
await validate_secure_runtime_on_startup(config, k8s_client=k8s_client)
|
|
|
|
k8s_client.read_runtime_class.assert_called_once_with("gvisor")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_validate_secure_runtime_rejects_missing_k8s_runtime_class() -> None:
|
|
k8s_client = MagicMock()
|
|
k8s_client.read_runtime_class.side_effect = ApiException(status=404)
|
|
config = _config(
|
|
runtime_type="kubernetes",
|
|
secure_runtime=SecureRuntimeConfig(type="gvisor", k8s_runtime_class="gvisor"),
|
|
)
|
|
|
|
with pytest.raises(ValueError, match="RuntimeClass 'gvisor'"):
|
|
await validate_secure_runtime_on_startup(config, k8s_client=k8s_client)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_validate_secure_runtime_reraises_k8s_api_errors() -> None:
|
|
k8s_client = MagicMock()
|
|
k8s_client.read_runtime_class.side_effect = ApiException(status=500)
|
|
config = _config(
|
|
runtime_type="kubernetes",
|
|
secure_runtime=SecureRuntimeConfig(type="gvisor", k8s_runtime_class="gvisor"),
|
|
)
|
|
|
|
with pytest.raises(ApiException):
|
|
await validate_secure_runtime_on_startup(config, k8s_client=k8s_client)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_validate_secure_runtime_allows_missing_k8s_client() -> None:
|
|
config = _config(
|
|
runtime_type="kubernetes",
|
|
secure_runtime=SecureRuntimeConfig(type="gvisor", k8s_runtime_class="gvisor"),
|
|
)
|
|
|
|
await validate_secure_runtime_on_startup(config, k8s_client=None)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_validate_secure_runtime_skips_unknown_runtime_type() -> None:
|
|
config = SimpleNamespace(
|
|
runtime=SimpleNamespace(type="custom"),
|
|
secure_runtime=SimpleNamespace(
|
|
type="gvisor",
|
|
docker_runtime="runsc",
|
|
k8s_runtime_class=None,
|
|
),
|
|
)
|
|
|
|
await validate_secure_runtime_on_startup(config)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_validate_startup_warns_gvisor_with_egress() -> None:
|
|
k8s_client = MagicMock()
|
|
config = _config(
|
|
runtime_type="kubernetes",
|
|
secure_runtime=SecureRuntimeConfig(type="gvisor", k8s_runtime_class="gvisor"),
|
|
egress=EgressConfig(image="opensandbox/egress:latest"),
|
|
)
|
|
|
|
with unittest.mock.patch(
|
|
"opensandbox_server.services.runtime_resolver.logger"
|
|
) as mock_logger:
|
|
await validate_secure_runtime_on_startup(config, k8s_client=k8s_client)
|
|
|
|
mock_logger.warning.assert_called_once()
|
|
assert "iptables nat" in mock_logger.warning.call_args[0][0]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_validate_startup_no_warn_gvisor_without_egress() -> None:
|
|
k8s_client = MagicMock()
|
|
config = _config(
|
|
runtime_type="kubernetes",
|
|
secure_runtime=SecureRuntimeConfig(type="gvisor", k8s_runtime_class="gvisor"),
|
|
)
|
|
|
|
with unittest.mock.patch(
|
|
"opensandbox_server.services.runtime_resolver.logger"
|
|
) as mock_logger:
|
|
await validate_secure_runtime_on_startup(config, k8s_client=k8s_client)
|
|
|
|
mock_logger.warning.assert_not_called()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_validate_startup_no_warn_kata_with_egress() -> None:
|
|
k8s_client = MagicMock()
|
|
config = _config(
|
|
runtime_type="kubernetes",
|
|
secure_runtime=SecureRuntimeConfig(type="kata", k8s_runtime_class="kata-qemu"),
|
|
egress=EgressConfig(image="opensandbox/egress:latest"),
|
|
)
|
|
|
|
with unittest.mock.patch(
|
|
"opensandbox_server.services.runtime_resolver.logger"
|
|
) as mock_logger:
|
|
await validate_secure_runtime_on_startup(config, k8s_client=k8s_client)
|
|
|
|
mock_logger.warning.assert_not_called()
|