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
140 lines
4.5 KiB
Python
140 lines
4.5 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.
|
|
|
|
"""
|
|
Volume helper utilities for Kubernetes pod specs.
|
|
"""
|
|
|
|
import logging
|
|
from typing import Any, Dict, List
|
|
|
|
from opensandbox_server.api.schema import Volume
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def _raise_mixed_pvc_read_only_policy(pvc_claim_name: str) -> None:
|
|
raise ValueError(
|
|
f"PVC claim '{pvc_claim_name}' is mounted with mixed read_only values. "
|
|
"All mounts sharing the same PVC must use the same read_only policy."
|
|
)
|
|
|
|
|
|
def ensure_shared_pvc_read_only_policy(volumes: List[Volume]) -> None:
|
|
"""Ensure every mount that references the same PVC uses the same read_only policy."""
|
|
pvc_to_read_only: Dict[str, bool] = {}
|
|
|
|
for vol in volumes:
|
|
if vol.pvc is None:
|
|
continue
|
|
|
|
pvc_claim_name = vol.pvc.claim_name
|
|
if pvc_claim_name in pvc_to_read_only:
|
|
if pvc_to_read_only[pvc_claim_name] != vol.read_only:
|
|
_raise_mixed_pvc_read_only_policy(pvc_claim_name)
|
|
continue
|
|
|
|
pvc_to_read_only[pvc_claim_name] = vol.read_only
|
|
|
|
|
|
def apply_volumes_to_pod_spec(
|
|
pod_spec: Dict[str, Any],
|
|
volumes: List[Volume],
|
|
) -> None:
|
|
"""Apply user-specified volumes to a pod spec in-place."""
|
|
containers = pod_spec.get("containers", [])
|
|
if not containers:
|
|
logger.warning("No containers in pod spec, skipping volume mounts")
|
|
return
|
|
|
|
main_container = containers[0]
|
|
mounts = main_container.get("volumeMounts", [])
|
|
pod_volumes = pod_spec.get("volumes", [])
|
|
|
|
ensure_shared_pvc_read_only_policy(volumes)
|
|
|
|
existing_volume_names = {v.get("name") for v in pod_volumes if isinstance(v, dict)}
|
|
pvc_to_volume_name: Dict[str, str] = {}
|
|
|
|
for vol in volumes:
|
|
vol_name = vol.name
|
|
|
|
if vol_name in existing_volume_names:
|
|
raise ValueError(
|
|
f"Volume name '{vol_name}' conflicts with an internal volume. "
|
|
"Please use a different volume name."
|
|
)
|
|
|
|
if vol.pvc is not None:
|
|
pvc_claim_name = vol.pvc.claim_name
|
|
|
|
if pvc_claim_name not in pvc_to_volume_name:
|
|
pod_volumes.append({
|
|
"name": vol_name,
|
|
"persistentVolumeClaim": {
|
|
"claimName": pvc_claim_name,
|
|
"readOnly": vol.read_only,
|
|
},
|
|
})
|
|
pvc_to_volume_name[pvc_claim_name] = vol_name
|
|
existing_volume_names.add(vol_name)
|
|
|
|
mount = {
|
|
"name": pvc_to_volume_name[pvc_claim_name],
|
|
"mountPath": vol.mount_path,
|
|
"readOnly": vol.read_only,
|
|
}
|
|
if vol.sub_path:
|
|
mount["subPath"] = vol.sub_path
|
|
mounts.append(mount)
|
|
|
|
logger.info(
|
|
"Added PVC volume '%s' (claim: %s, read_only=%s) mounted at '%s' for sandbox",
|
|
pvc_to_volume_name[pvc_claim_name],
|
|
pvc_claim_name,
|
|
vol.read_only,
|
|
vol.mount_path,
|
|
)
|
|
elif vol.host is not None:
|
|
host_path = vol.host.path
|
|
|
|
pod_volumes.append({
|
|
"name": vol_name,
|
|
"hostPath": {
|
|
"path": host_path,
|
|
"type": "DirectoryOrCreate",
|
|
},
|
|
})
|
|
|
|
mount = {
|
|
"name": vol_name,
|
|
"mountPath": vol.mount_path,
|
|
"readOnly": vol.read_only,
|
|
}
|
|
if vol.sub_path:
|
|
mount["subPath"] = vol.sub_path
|
|
mounts.append(mount)
|
|
|
|
logger.info(
|
|
f"Added hostPath volume '{vol_name}' (path: {host_path}) mounted at '{vol.mount_path}' for sandbox"
|
|
)
|
|
else:
|
|
raise ValueError(
|
|
f"Volume '{vol_name}' has no supported backend specified. "
|
|
"Supported backends: pvc, host"
|
|
)
|
|
|
|
pod_spec["volumes"] = pod_volumes
|
|
main_container["volumeMounts"] = mounts
|