opensandbox-server 0.2.1__tar.gz → 0.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/.gitignore +14 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/PKG-INFO +9 -4
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/api/devops.py +41 -27
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/api/lifecycle.py +32 -4
- opensandbox_server-0.2.3/opensandbox_server/api/metrics.py +83 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/api/proxy.py +249 -52
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/api/schema.py +147 -5
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/cli.py +2 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/config.py +214 -14
- opensandbox_server-0.2.3/opensandbox_server/constants.py +18 -0
- opensandbox_server-0.2.3/opensandbox_server/examples/e2e.batchsandbox-template.yaml +33 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/examples/example.config.k8s.toml +4 -4
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/examples/example.config.k8s.zh.toml +4 -4
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/examples/example.config.toml +16 -4
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/examples/example.config.zh.toml +15 -4
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/extensions/__init__.py +4 -2
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/extensions/codec.py +23 -3
- opensandbox_server-0.2.3/opensandbox_server/integrations/otel/__init__.py +29 -0
- opensandbox_server-0.2.3/opensandbox_server/integrations/otel/metrics.py +235 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/integrations/renew_intent/consumer.py +2 -2
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/main.py +115 -12
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/middleware/auth.py +59 -40
- opensandbox_server-0.2.3/opensandbox_server/middleware/date_header.py +43 -0
- opensandbox_server-0.2.3/opensandbox_server/middleware/http_metrics.py +86 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/repositories/snapshots/sqlite.py +80 -1
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/constants.py +17 -1
- opensandbox_server-0.2.3/opensandbox_server/services/diagnostics.py +85 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/docker/__init__.py +1 -2
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/docker/container_ops.py +7 -1
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/docker/docker_diagnostics.py +103 -3
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/docker/docker_service.py +112 -180
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/docker/metadata.py +30 -8
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/docker/networking.py +76 -23
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/docker/port_allocator.py +3 -1
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/docker/runtime.py +116 -1
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/docker/snapshot_runtime.py +12 -3
- opensandbox_server-0.2.3/opensandbox_server/services/fleets/__init__.py +13 -0
- opensandbox_server-0.2.3/opensandbox_server/services/fleets/create_mapping.py +280 -0
- opensandbox_server-0.2.3/opensandbox_server/services/fleets/fastpath_client.py +310 -0
- opensandbox_server-0.2.3/opensandbox_server/services/fleets/status_mapping.py +106 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/helpers.py +13 -1
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/k8s/agent_sandbox_provider.py +26 -3
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/k8s/batchsandbox_provider.py +159 -26
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/k8s/client.py +26 -6
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/k8s/create_helpers.py +6 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/k8s/egress_helper.py +6 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/k8s/informer.py +60 -6
- opensandbox_server-0.2.3/opensandbox_server/services/k8s/k8s_diagnostics.py +455 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/k8s/kubernetes_service.py +265 -52
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/k8s/pool_service.py +24 -22
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/k8s/provider_common.py +9 -1
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/k8s/snapshot_runtime.py +34 -15
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/k8s/status_helpers.py +8 -1
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/k8s/volume_helper.py +20 -1
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/k8s/workload_mapper.py +126 -1
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/k8s/workload_provider.py +9 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/sandbox_service.py +47 -1
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/snapshot_models.py +1 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/snapshot_repository.py +2 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/snapshot_restore.py +11 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/snapshot_runtime.py +8 -4
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/snapshot_service.py +38 -4
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/validators.py +39 -2
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/startup_guard.py +4 -4
- opensandbox_server-0.2.3/opensandbox_server/tenants/__init__.py +156 -0
- opensandbox_server-0.2.3/opensandbox_server/tenants/context.py +30 -0
- opensandbox_server-0.2.3/opensandbox_server/tenants/file_provider.py +193 -0
- opensandbox_server-0.2.3/opensandbox_server/tenants/http_provider.py +240 -0
- opensandbox_server-0.2.3/opensandbox_server/tenants/models.py +25 -0
- opensandbox_server-0.2.3/opensandbox_server/tenants/provider.py +74 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/pyproject.toml +9 -2
- opensandbox_server-0.2.1/opensandbox_server/examples/e2e.batchsandbox-template.yaml +0 -17
- opensandbox_server-0.2.1/opensandbox_server/services/k8s/k8s_diagnostics.py +0 -198
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/LICENSE +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/README.md +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/__init__.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/api/__init__.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/api/pool.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/examples/.gitkeep +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/examples/__init__.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/examples/example.batchsandbox-template.yaml +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/extensions/keys.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/extensions/validation.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/integrations/__init__.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/integrations/renew_intent/__init__.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/integrations/renew_intent/constants.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/integrations/renew_intent/controller.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/integrations/renew_intent/intent.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/integrations/renew_intent/logutil.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/integrations/renew_intent/proxy_renew.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/integrations/renew_intent/redis_client.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/integrations/renew_intent/runner.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/logging_config.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/middleware/__init__.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/middleware/request_id.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/py.typed +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/repositories/__init__.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/repositories/snapshots/__init__.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/repositories/snapshots/factory.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/__init__.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/docker/ossfs_mixin.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/docker/volumes.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/docker/windows_profile.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/endpoint_auth.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/extension_service.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/factory.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/k8s/__init__.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/k8s/agent_sandbox_template.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/k8s/batchsandbox_template.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/k8s/endpoint_resolver.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/k8s/error_helpers.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/k8s/image_pull_secret_helper.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/k8s/label_selector.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/k8s/list_helpers.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/k8s/provider_factory.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/k8s/rate_limiter.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/k8s/security_context.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/k8s/template_manager.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/k8s/windows_profile.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/k8s/workload_access.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/runtime_resolver.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/signing.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/snapshot_runtime_factory.py +0 -0
- {opensandbox_server-0.2.1 → opensandbox_server-0.2.3}/opensandbox_server/services/windows_common.py +0 -0
|
@@ -269,6 +269,19 @@ nbdist/
|
|
|
269
269
|
generated/
|
|
270
270
|
**/generated/**
|
|
271
271
|
|
|
272
|
+
# fleets FastPath gRPC stubs are committed intentionally (deterministic protoc
|
|
273
|
+
# output; the server package needs them at install time, so they are checked
|
|
274
|
+
# in rather than regenerated).
|
|
275
|
+
!server/opensandbox_server/services/fleets/generated/
|
|
276
|
+
!server/opensandbox_server/services/fleets/generated/**
|
|
277
|
+
server/opensandbox_server/services/fleets/generated/__pycache__/
|
|
278
|
+
|
|
279
|
+
# execd eBPF kernel BTF dumps (bpf2go build-time only, never committed —
|
|
280
|
+
# see components/execd/pkg/ebpf/prog/audit.bpf.c; regenerate with:
|
|
281
|
+
# bpftool btf dump file /sys/kernel/btf/vmlinux format c > .../vmlinux.h)
|
|
282
|
+
components/execd/pkg/ebpf/prog/vmlinux.h
|
|
283
|
+
components/execd/pkg/ebpf/prog/vmlinux_*.h
|
|
284
|
+
|
|
272
285
|
# gVisor runtime binaries (downloaded dynamically)
|
|
273
286
|
kubernetes/test/kind/gvisor/runsc
|
|
274
287
|
kubernetes/test/kind/gvisor/containerd-shim-runsc-v1
|
|
@@ -276,3 +289,4 @@ bin/
|
|
|
276
289
|
obj/
|
|
277
290
|
|
|
278
291
|
.qoder/
|
|
292
|
+
/.vs
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: opensandbox-server
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: FastAPI control plane for OpenSandbox that manages sandbox lifecycle on Docker (ready) and Kubernetes (planned) runtimes.
|
|
5
5
|
Project-URL: Homepage, https://github.com/opensandbox-group/OpenSandbox
|
|
6
6
|
Project-URL: Repository, https://github.com/opensandbox-group/OpenSandbox
|
|
@@ -24,11 +24,16 @@ Classifier: Typing :: Typed
|
|
|
24
24
|
Requires-Python: >=3.10
|
|
25
25
|
Requires-Dist: docker
|
|
26
26
|
Requires-Dist: fastapi>=0.137.0
|
|
27
|
+
Requires-Dist: grpcio>=1.83.0
|
|
27
28
|
Requires-Dist: httpx[socks]
|
|
28
29
|
Requires-Dist: kubernetes
|
|
30
|
+
Requires-Dist: opentelemetry-api
|
|
31
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http
|
|
32
|
+
Requires-Dist: opentelemetry-sdk
|
|
33
|
+
Requires-Dist: protobuf>=7.35.1
|
|
29
34
|
Requires-Dist: pydantic
|
|
30
|
-
Requires-Dist: pydantic-settings
|
|
31
|
-
Requires-Dist: python-multipart>=0.0.
|
|
35
|
+
Requires-Dist: pydantic-settings>=2.14.2
|
|
36
|
+
Requires-Dist: python-multipart>=0.0.31
|
|
32
37
|
Requires-Dist: pyyaml
|
|
33
38
|
Requires-Dist: redis>=5
|
|
34
39
|
Requires-Dist: starlette>=1.3.1
|
|
@@ -15,10 +15,9 @@
|
|
|
15
15
|
"""
|
|
16
16
|
API routes for OpenSandbox DevOps diagnostics.
|
|
17
17
|
|
|
18
|
-
Requests that include
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
plain-text behavior for legacy humans, agents, and CLI clients.
|
|
18
|
+
Requests that include ``scope`` target the stable Diagnostics API and return an
|
|
19
|
+
inline JSON descriptor. Requests without ``scope`` preserve the deprecated
|
|
20
|
+
DevOps plain-text behavior for legacy humans, agents, and CLI clients.
|
|
22
21
|
"""
|
|
23
22
|
|
|
24
23
|
import logging
|
|
@@ -28,19 +27,29 @@ from fastapi import APIRouter, HTTPException, Query, status
|
|
|
28
27
|
from fastapi.responses import JSONResponse, PlainTextResponse
|
|
29
28
|
|
|
30
29
|
from opensandbox_server.api.lifecycle import sandbox_service
|
|
30
|
+
from opensandbox_server.services.diagnostics import DiagnosticResult
|
|
31
31
|
|
|
32
32
|
logger = logging.getLogger(__name__)
|
|
33
33
|
router = APIRouter(tags=["DevOps"])
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
def
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
36
|
+
def _diagnostic_inline_response(
|
|
37
|
+
result: DiagnosticResult,
|
|
38
|
+
) -> JSONResponse:
|
|
39
|
+
"""Build a Diagnostics API descriptor for inline plain-text content."""
|
|
40
|
+
payload: dict[str, object] = {
|
|
41
|
+
"sandboxId": result.sandbox_id,
|
|
42
|
+
"kind": result.kind,
|
|
43
|
+
"scope": result.scope,
|
|
44
|
+
"delivery": "inline",
|
|
45
|
+
"contentType": "text/plain; charset=utf-8",
|
|
46
|
+
"content": result.content,
|
|
47
|
+
"contentLength": len(result.content.encode("utf-8")),
|
|
48
|
+
"truncated": result.truncated,
|
|
49
|
+
}
|
|
50
|
+
if result.warnings:
|
|
51
|
+
payload["warnings"] = list(result.warnings)
|
|
52
|
+
return JSONResponse(status_code=status.HTTP_200_OK, content=payload)
|
|
44
53
|
|
|
45
54
|
|
|
46
55
|
def _deprecated_plain_text_response(content: str) -> PlainTextResponse:
|
|
@@ -56,13 +65,10 @@ def _deprecated_plain_text_response(content: str) -> PlainTextResponse:
|
|
|
56
65
|
status_code=status.HTTP_200_OK,
|
|
57
66
|
responses={
|
|
58
67
|
200: {
|
|
59
|
-
"description": "
|
|
60
|
-
"content": {"text/plain": {}},
|
|
61
|
-
},
|
|
62
|
-
501: {
|
|
63
|
-
"description": "Stable Diagnostics API is not implemented by this server",
|
|
64
|
-
"content": {"application/json": {}},
|
|
68
|
+
"description": "Stable JSON descriptor, or deprecated text when scope is omitted",
|
|
69
|
+
"content": {"application/json": {}, "text/plain": {}},
|
|
65
70
|
},
|
|
71
|
+
400: {"description": "Unsupported diagnostics scope"},
|
|
66
72
|
404: {"description": "Sandbox not found"},
|
|
67
73
|
},
|
|
68
74
|
)
|
|
@@ -84,11 +90,21 @@ def get_sandbox_logs(
|
|
|
84
90
|
deprecated=True,
|
|
85
91
|
description="Deprecated plain-text logs only. Only return logs newer than this duration (e.g. 10m, 1h).",
|
|
86
92
|
),
|
|
93
|
+
container: Optional[str] = Query(
|
|
94
|
+
None,
|
|
95
|
+
deprecated=True,
|
|
96
|
+
description=(
|
|
97
|
+
"Deprecated plain-text logs only. Container name to read logs from. "
|
|
98
|
+
"Defaults to the canonical user container (typically 'sandbox') when "
|
|
99
|
+
"the runtime supports multi-container pods."
|
|
100
|
+
),
|
|
101
|
+
),
|
|
87
102
|
) -> JSONResponse | PlainTextResponse:
|
|
88
103
|
"""Retrieve diagnostic logs for a sandbox."""
|
|
89
104
|
if scope is not None:
|
|
90
|
-
|
|
91
|
-
|
|
105
|
+
result = sandbox_service.get_sandbox_log_diagnostics(sandbox_id, scope)
|
|
106
|
+
return _diagnostic_inline_response(result)
|
|
107
|
+
text = sandbox_service.get_sandbox_logs(sandbox_id, tail=tail, since=since, container=container)
|
|
92
108
|
return _deprecated_plain_text_response(text)
|
|
93
109
|
|
|
94
110
|
|
|
@@ -114,13 +130,10 @@ def get_sandbox_inspect(sandbox_id: str) -> PlainTextResponse:
|
|
|
114
130
|
status_code=status.HTTP_200_OK,
|
|
115
131
|
responses={
|
|
116
132
|
200: {
|
|
117
|
-
"description": "
|
|
118
|
-
"content": {"text/plain": {}},
|
|
119
|
-
},
|
|
120
|
-
501: {
|
|
121
|
-
"description": "Stable Diagnostics API is not implemented by this server",
|
|
122
|
-
"content": {"application/json": {}},
|
|
133
|
+
"description": "Stable JSON descriptor, or deprecated text when scope is omitted",
|
|
134
|
+
"content": {"application/json": {}, "text/plain": {}},
|
|
123
135
|
},
|
|
136
|
+
400: {"description": "Unsupported diagnostics scope"},
|
|
124
137
|
404: {"description": "Sandbox not found"},
|
|
125
138
|
},
|
|
126
139
|
)
|
|
@@ -140,7 +153,8 @@ def get_sandbox_events(
|
|
|
140
153
|
) -> JSONResponse | PlainTextResponse:
|
|
141
154
|
"""Retrieve diagnostic events for a sandbox."""
|
|
142
155
|
if scope is not None:
|
|
143
|
-
|
|
156
|
+
result = sandbox_service.get_sandbox_event_diagnostics(sandbox_id, scope)
|
|
157
|
+
return _diagnostic_inline_response(result)
|
|
144
158
|
text = sandbox_service.get_sandbox_events(sandbox_id, limit=limit)
|
|
145
159
|
return _deprecated_plain_text_response(text)
|
|
146
160
|
|
|
@@ -45,7 +45,10 @@ from opensandbox_server.api.schema import (
|
|
|
45
45
|
Snapshot,
|
|
46
46
|
SnapshotFilter,
|
|
47
47
|
)
|
|
48
|
-
from opensandbox_server.services.constants import
|
|
48
|
+
from opensandbox_server.services.constants import (
|
|
49
|
+
OPEN_SANDBOX_INGRESS_HEADER,
|
|
50
|
+
SandboxErrorCodes,
|
|
51
|
+
)
|
|
49
52
|
from opensandbox_server.services.factory import create_sandbox_service
|
|
50
53
|
from opensandbox_server.services.snapshot_service import create_snapshot_service
|
|
51
54
|
|
|
@@ -71,6 +74,16 @@ snapshot_service = create_snapshot_service(sandbox_service)
|
|
|
71
74
|
400: {"model": ErrorResponse, "description": "The request was invalid or malformed"},
|
|
72
75
|
401: {"model": ErrorResponse, "description": "Authentication credentials are missing or invalid"},
|
|
73
76
|
409: {"model": ErrorResponse, "description": "The operation conflicts with the current state"},
|
|
77
|
+
429: {
|
|
78
|
+
"model": ErrorResponse,
|
|
79
|
+
"description": "Pool capacity remained unavailable before the acquisition timeout",
|
|
80
|
+
"headers": {
|
|
81
|
+
"Retry-After": {
|
|
82
|
+
"description": "Suggested delay in seconds before retrying",
|
|
83
|
+
"schema": {"type": "integer", "minimum": 1},
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
},
|
|
74
87
|
500: {"model": ErrorResponse, "description": "An unexpected server error occurred"},
|
|
75
88
|
},
|
|
76
89
|
)
|
|
@@ -431,6 +444,7 @@ def create_snapshot(
|
|
|
431
444
|
)
|
|
432
445
|
def list_snapshots(
|
|
433
446
|
sandbox_id: Optional[str] = Query(None, alias="sandboxId", description="Filter snapshots by source sandbox identifier"),
|
|
447
|
+
name: Optional[str] = Query(None, description="Filter snapshots by exact snapshot name"),
|
|
434
448
|
state: Optional[List[str]] = Query(None, description="Filter by snapshot lifecycle state. Pass multiple times for OR logic."),
|
|
435
449
|
page: int = Query(1, ge=1, description="Page number for pagination"),
|
|
436
450
|
page_size: int = Query(20, ge=1, le=200, alias="pageSize", description="Number of items per page"),
|
|
@@ -440,7 +454,7 @@ def list_snapshots(
|
|
|
440
454
|
List snapshots with optional filtering and pagination.
|
|
441
455
|
"""
|
|
442
456
|
request = ListSnapshotsRequest(
|
|
443
|
-
filter=SnapshotFilter(sandboxId=sandbox_id, state=state),
|
|
457
|
+
filter=SnapshotFilter(sandboxId=sandbox_id, name=name, state=state),
|
|
444
458
|
pagination=PaginationRequest(page=page, pageSize=page_size),
|
|
445
459
|
)
|
|
446
460
|
return snapshot_service.list_snapshots(request)
|
|
@@ -564,12 +578,26 @@ def get_sandbox_endpoint(
|
|
|
564
578
|
endpoint = sandbox_service.get_endpoint(sandbox_id, port, expires=expires)
|
|
565
579
|
|
|
566
580
|
if use_server_proxy:
|
|
567
|
-
# Prefer configured external address when available
|
|
568
|
-
|
|
581
|
+
# Prefer configured external address when available (it is the full
|
|
582
|
+
# external base and may already carry a path prefix).
|
|
569
583
|
eip = (get_config().server.eip or "").strip().rstrip("/")
|
|
570
584
|
if eip:
|
|
571
585
|
base_url = eip
|
|
586
|
+
else:
|
|
587
|
+
# Reflect how the client reached this API: same host AND same mount
|
|
588
|
+
# prefix (the API is served both bare and under /v1). Dropping the
|
|
589
|
+
# prefix pointed proxied traffic at whatever backend owns the root
|
|
590
|
+
# path on shared hosts (breaking HTTP with redirects and WebSocket
|
|
591
|
+
# handshakes outright).
|
|
592
|
+
mount_prefix = request.url.path.split("/sandboxes/", 1)[0]
|
|
593
|
+
base_url = str(request.base_url).rstrip("/") + mount_prefix
|
|
572
594
|
base_url = base_url.replace("https://", "").replace("http://", "")
|
|
573
595
|
endpoint.endpoint = f"{base_url}/sandboxes/{sandbox_id}/proxy/{port}"
|
|
596
|
+
if endpoint.headers:
|
|
597
|
+
endpoint.headers = {
|
|
598
|
+
key: value
|
|
599
|
+
for key, value in endpoint.headers.items()
|
|
600
|
+
if key.lower() != OPEN_SANDBOX_INGRESS_HEADER.lower()
|
|
601
|
+
} or None
|
|
574
602
|
|
|
575
603
|
return endpoint
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Copyright 2026 Alibaba Group Holding Ltd.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""SDK metrics ingestion endpoints."""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import logging
|
|
20
|
+
import re
|
|
21
|
+
from typing import Optional
|
|
22
|
+
|
|
23
|
+
from fastapi import APIRouter, Header, Response, status
|
|
24
|
+
|
|
25
|
+
from opensandbox_server.api.schema import MetricsEvent
|
|
26
|
+
from opensandbox_server.integrations.otel import record_sandbox_create_duration
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
router = APIRouter(tags=["Metrics"])
|
|
31
|
+
|
|
32
|
+
# OpenSandbox-Python-SDK/0.1.14, OpenSandbox-Go-SDK/0.1.0, OpenSandbox-JS-SDK/0.1.10
|
|
33
|
+
_SDK_USER_AGENT_RE = re.compile(
|
|
34
|
+
r"OpenSandbox-([A-Za-z0-9]+)-SDK/([^\s]+)",
|
|
35
|
+
re.IGNORECASE,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def parse_sdk_user_agent(user_agent: Optional[str]) -> tuple[str, str]:
|
|
40
|
+
"""Extract (language, version) from SDK User-Agent; fallback to unknown."""
|
|
41
|
+
if not user_agent:
|
|
42
|
+
return "unknown", "unknown"
|
|
43
|
+
match = _SDK_USER_AGENT_RE.search(user_agent)
|
|
44
|
+
if not match:
|
|
45
|
+
return "unknown", "unknown"
|
|
46
|
+
return match.group(1).lower(), match.group(2)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@router.post(
|
|
50
|
+
"/metrics/events",
|
|
51
|
+
status_code=status.HTTP_204_NO_CONTENT,
|
|
52
|
+
responses={
|
|
53
|
+
400: {"description": "Bad request"},
|
|
54
|
+
401: {"description": "Unauthorized"},
|
|
55
|
+
500: {"description": "Internal server error"},
|
|
56
|
+
},
|
|
57
|
+
)
|
|
58
|
+
def report_metrics_event(
|
|
59
|
+
event: MetricsEvent,
|
|
60
|
+
x_request_id: Optional[str] = Header(None, alias="X-Request-ID"),
|
|
61
|
+
user_agent: Optional[str] = Header(None, alias="User-Agent"),
|
|
62
|
+
) -> Response:
|
|
63
|
+
"""Accept best-effort SDK metrics and record to OTEL (noop when disabled)."""
|
|
64
|
+
_ = x_request_id
|
|
65
|
+
if event.event_type == "sandbox.create":
|
|
66
|
+
sdk_language, sdk_version = parse_sdk_user_agent(user_agent)
|
|
67
|
+
record_sandbox_create_duration(
|
|
68
|
+
create_duration_ms=event.create_duration_ms,
|
|
69
|
+
sdk_language=sdk_language,
|
|
70
|
+
sdk_version=sdk_version,
|
|
71
|
+
success=event.success,
|
|
72
|
+
)
|
|
73
|
+
logger.debug(
|
|
74
|
+
"Accepted sandbox.create metrics event sandbox_id=%s image=%s "
|
|
75
|
+
"duration_ms=%s sdk=%s/%s success=%s",
|
|
76
|
+
event.sandbox_id,
|
|
77
|
+
event.image,
|
|
78
|
+
event.create_duration_ms,
|
|
79
|
+
sdk_language,
|
|
80
|
+
sdk_version,
|
|
81
|
+
event.success,
|
|
82
|
+
)
|
|
83
|
+
return Response(status_code=status.HTTP_204_NO_CONTENT)
|