Skip to content

Commit ef13d88

Browse files
authored
Merge pull request #910 from qingyuppp/fix/pool-platform-extract
fix(server): handle null spec.template in pool-mode BatchSandbox
2 parents a25dcb3 + e462eb1 commit ef13d88

2 files changed

Lines changed: 110 additions & 3 deletions

File tree

server/opensandbox_server/services/k8s/workload_mapper.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,12 @@ def _build_sandbox_from_workload(workload: Any, workload_provider: Any) -> Sandb
8484

8585
def _extract_platform_from_workload(workload: Any) -> Optional[PlatformSpec]:
8686
if isinstance(workload, dict):
87-
spec = workload.get("spec", {})
87+
spec = workload.get("spec") or {}
88+
template = spec.get("template") or {}
89+
pod_template = spec.get("podTemplate") or {}
8890
pod_spec = (
89-
spec.get("template", {}).get("spec")
90-
or spec.get("podTemplate", {}).get("spec")
91+
(template.get("spec") if isinstance(template, dict) else None)
92+
or (pod_template.get("spec") if isinstance(pod_template, dict) else None)
9193
or {}
9294
)
9395
else:
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
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+
from opensandbox_server.services.k8s.workload_mapper import (
16+
_extract_platform_from_workload,
17+
)
18+
19+
20+
class TestExtractPlatformFromWorkload:
21+
"""Regression tests for _extract_platform_from_workload.
22+
23+
The BatchSandbox CRD declares spec.template as an optional preserve-unknown-fields
24+
object. In pool mode, the BatchSandbox CR is created with only ``poolRef`` and
25+
``taskTemplate`` under spec; the Kubernetes API server may then return the object
26+
with ``spec.template`` explicitly set to ``None`` (because the field is part of the
27+
schema but unset). Earlier code did ``spec.get("template", {}).get("spec")`` which
28+
crashed in that case because the default ``{}`` is only returned when the key is
29+
absent, not when its value is ``None``.
30+
"""
31+
32+
def test_pool_mode_workload_with_null_template_returns_none(self):
33+
"""Pool-mode BatchSandbox CR has spec.template == None; must not crash."""
34+
workload = {
35+
"metadata": {"name": "sb-1", "namespace": "opensandbox-system"},
36+
"spec": {
37+
"replicas": 1,
38+
"poolRef": "pool-runc",
39+
"template": None, # <-- this used to crash
40+
"taskTemplate": {},
41+
},
42+
"status": {"replicas": 1, "ready": 1, "allocated": 1},
43+
}
44+
# Should return None (no platform info), not raise.
45+
assert _extract_platform_from_workload(workload) is None
46+
47+
def test_pool_mode_workload_without_template_key_returns_none(self):
48+
"""Pool-mode BatchSandbox CR may also omit spec.template entirely."""
49+
workload = {
50+
"metadata": {"name": "sb-1"},
51+
"spec": {
52+
"replicas": 1,
53+
"poolRef": "pool-runc",
54+
},
55+
}
56+
assert _extract_platform_from_workload(workload) is None
57+
58+
def test_template_mode_with_full_platform_returns_platform(self):
59+
"""Template-mode workload with nodeSelector returns the declared platform."""
60+
workload = {
61+
"metadata": {"name": "sb-1"},
62+
"spec": {
63+
"replicas": 1,
64+
"template": {
65+
"spec": {
66+
"nodeSelector": {
67+
"kubernetes.io/os": "linux",
68+
"kubernetes.io/arch": "amd64",
69+
},
70+
},
71+
},
72+
},
73+
}
74+
platform = _extract_platform_from_workload(workload)
75+
assert platform is not None
76+
assert platform.os == "linux"
77+
assert platform.arch == "amd64"
78+
79+
def test_pod_template_alias_still_works(self):
80+
"""Some workload types use ``podTemplate`` instead of ``template``."""
81+
workload = {
82+
"spec": {
83+
"podTemplate": {
84+
"spec": {
85+
"nodeSelector": {
86+
"kubernetes.io/os": "linux",
87+
"kubernetes.io/arch": "arm64",
88+
},
89+
},
90+
},
91+
},
92+
}
93+
platform = _extract_platform_from_workload(workload)
94+
assert platform is not None
95+
assert platform.os == "linux"
96+
assert platform.arch == "arm64"
97+
98+
def test_null_spec_returns_none(self):
99+
"""spec itself being None must not crash."""
100+
workload = {"metadata": {"name": "sb-1"}, "spec": None}
101+
assert _extract_platform_from_workload(workload) is None
102+
103+
def test_empty_workload_returns_none(self):
104+
workload = {}
105+
assert _extract_platform_from_workload(workload) is None

0 commit comments

Comments
 (0)