server/v0.1.14

opensandbox-group/OpenSandboxserver/v0.1.14May 18, 2026by Pangjiping

AI Summary

A major performance update that unblocks the event loop by switching route handlers to sync execution, improving concurrency. It also introduces a new PATCH endpoint for metadata updates and fixes critical bugs in pool-mode sandbox creation and metadata deletion.

Key Highlights

  • Performance boost: 17 route handlers switched to sync to offload to anyio threadpool, improving `list_sandboxes` concurrency.
  • New PATCH endpoint for incremental metadata updates on sandboxes.
  • Fixed pool-mode sandbox creation returning HTTP 500 due to null template handling.
  • Fixed K8s metadata patch to properly delete keys using JSON merge patch semantics.

New Features

  • PATCH /sandboxes/{sandboxId}/metadata endpoint
  • Stable Diagnostics API stub
  • Concurrency configuration knobs (workers, limit_concurrency, etc.)

Full Release Notes

## What's New

### ✨ Features

- **Sandbox metadata PATCH endpoint** — new `PATCH /sandboxes/{sandboxId}/metadata` for incremental label updates: pass a value to set/update, pass `null` to delete a key. Smoke tests included. (#861)

- **Stable Diagnostics API (server-side stub)** — SDKs and CLI gain `osb diagnostics logs|events` with required `scope`. The server returns `501 DIAGNOSTICS_NOT_IMPLEMENTED` for `scope`-bearing requests until the stable backend lands; requests without `scope` keep the legacy DevOps `text/plain` behavior, so existing clients are unaffected. (#869)

### ⚡ Performance

- **Unblock the event loop, expose concurrency knobs** — 17 lifecycle/pool route handlers that wrap synchronous Kubernetes/Docker SDK calls switched from `async def` to sync `def`, letting FastAPI auto-offload them to the anyio threadpool. The four sync K8s calls inside `create_sandbox` / `_wait_for_sandbox_ready` are now wrapped in `asyncio.to_thread`. `list_custom_objects` serves from the informer cache when synced, falling back to the API for unsynced caches or unsupported label-selector grammars. New `[server]` keys: `workers`, `limit_concurrency`, `backlog`, `thread_pool_size`, `loop`, `http` — all additive, defaults preserve current behavior. `uvicorn` upgraded to `uvicorn[standard]` (uvloop / httptools / watchfiles). 8 × 200 ms concurrent `list_sandboxes` drops from a 1.6 s serial floor to ~250 ms. Closes #887. (#903)

### 🐛 Bug Fixes

- **Pool-mode sandbox creation returned HTTP 500** — pool-mode `BatchSandbox` CRs come back from the K8s API server with `spec.template: null` (key present, value None) because the CRD declares `template` as an optional `preserve-unknown-fields` object. The previous `spec.get("template", {}).get("spec")` in `_extract_platform_from_workload` crashed because `dict.get` only returns the default when the key is absent, not when its value is `None`. The underlying sandbox actually reached `Running`, but clients saw a 500 with no `sandbox_id`, leaking resources. Switched to the `or {}` pattern that treats null and missing alike; six regression tests cover null template, missing template, template-mode happy path, `podTemplate` alias, null spec, and empty workload. (#910)

- **`image` / `entrypoint` / `resourceLimits` no longer required when `poolRef` is set** — these fields are defined on the Pool CRD template, but the schema previously rejected pool-mode requests without dummy values (`422`). Validation in `schema.py`, `kubernetes_service.py`, `docker_service.py`, and `container_ops.py` now skips these fields when `poolRef` is present, explicitly rejects the `poolRef` + `snapshotId` combination, and rejects `poolRef` on the Docker provider (unsupported). `specs/sandbox-lifecycle.yml` updated accordingly. Fixes #885. (#883)

- **K8s `patch_sandbox_metadata` failed to delete keys and returned stale state** — two bugs combined to break the nightly e2e `test_02_metadata_filter_and_logic`: (1) JSON merge patch (RFC 7396) merges labels recursively, so keys absent from the patch body are kept; the old code computed the final desired dict (already without the deleted keys) and sent that, so the deletes never reached the API server. (2) After PATCH the code re-read via `_get_workload_or_404`, which prefers the informer cache and could land before the watch event arrived, returning pre-patch labels. Fix: build the merge-patch body with explicit `None` for deleted keys, and use the API server's PATCH response directly instead of re-reading the cache. `WorkloadProvider.patch_labels` now takes `Dict[str, Optional[str]]` and returns the patched workload dict. (#899)

### 📦 Misc

- **Dependency**: urllib3 `2.6.3 → 2.7.0`, includes two high-severity security fixes — `GHSA-mf9v-mfxr-j63j` (decompression-bomb safeguard bypass on `drain_conn` / second `read(amt=N)` with Brotli) and `GHSA-qccp-gfcp-xxvc` (`ProxyManager.connection_from_url` did not strip `Retry.remove_headers_on_redirect` headers on cross-host redirect). (#872)
- **Bundled runtime image bumps** — egress → v1.0.11 / v1.0.12, execd → v1.0.15 / v1.0.16, synced across example configs and docker-compose. (#847, #876, #889, #907)
- **Docs**: fixed dead links to `example.config*.toml` in `server/README.md` and `server/configuration.md` (files actually live under `server/opensandbox_server/examples/`). (#904)
- **Example**: added dockur windows pool example. (#878)
- **Agent guidance**: updated `server/AGENTS.md`. (#839)

## 👥 Contributors

Thanks to these contributors ❤️

- @qingyuppp
- @Pangjiping
- @longsuizhi
- @ninan-nn
- @Generalwin
- @immanuwell
- @jwx0925

---

- PyPI: opensandbox-server==0.1.14
- Docker Hub: opensandbox/server:v0.1.14
- Aliyun Registry: sandbox-registry.cn-zhangjiakou.cr.aliyuncs.com/opensandbox/server:v0.1.14