gpustack-runtime 0.2.3.post1__tar.gz → 0.2.4__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.
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/PKG-INFO +1 -1
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/_version.py +2 -2
- gpustack_runtime-0.2.4/gpustack_runtime/_version_appendix.py +1 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/cmds/deployer.py +21 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/deployer/__init__.py +11 -1
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/deployer/__types__.py +40 -1
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/deployer/cdi/__utils__.py +81 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/deployer/cdi/ascend.py +64 -7
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/deployer/docker.py +114 -8
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/deployer/kuberentes.py +143 -9
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/deployer/podman.py +117 -8
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/detector/ascend.py +389 -3
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/detector/pydcmi/__init__.py +180 -1
- gpustack_runtime-0.2.4/tests/gpustack_runtime/deployer/test_termination_grace_period.py +728 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/test_ascend.py +711 -0
- gpustack_runtime-0.2.3.post1/gpustack_runtime/_version_appendix.py +0 -1
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/.codespelldict +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/.codespellrc +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/.dockerignore +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/.gitattributes +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/.gitignore +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/.pre-commit-config.yaml +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/.python-version +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/LICENSE +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/Makefile +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/README.md +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/deploy/manifests/docker-compose.yaml +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/deploy/manifests/kubernetes.yaml +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/docs/index.md +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/docs/modules/gpustack_runtime.deployer.md +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/docs/modules/gpustack_runtime.detector.md +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/docs/modules/gpustack_runtime.md +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/__init__.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/__main__.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/_version.pyi +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/cmds/__init__.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/cmds/__types__.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/cmds/detector.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/cmds/images.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/deployer/__patches__.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/deployer/__utils__.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/deployer/cdi/__init__.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/deployer/cdi/__types__.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/deployer/cdi/amd.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/deployer/cdi/hygon.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/deployer/cdi/iluvatar.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/deployer/cdi/metax.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/deployer/cdi/thead.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/deployer/k8s/devicemanager/__init__.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/detector/__init__.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/detector/__types__.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/detector/__utils__.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/detector/amd.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/detector/cambricon.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/detector/hygon.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/detector/iluvatar.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/detector/metax.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/detector/mthreads.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/detector/nvidia.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/detector/pyamdgpu/__init__.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/detector/pyamdsmi/__init__.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/detector/pycndev/__init__.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/detector/pyhgml/__init__.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/detector/pyhgml/libhgml.so +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/detector/pyhgml/libuki.so +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/detector/pyhsa/__init__.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/detector/pyixml/__init__.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/detector/pymtml/__init__.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/detector/pymxsml/__init__.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/detector/pynvml/__init__.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/detector/pyrocmsmi/__init__.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/detector/thead.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/envs.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/logging.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/hatch.toml +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/mkdocs.yml +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/pack/Dockerfile +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/pack/Dockerfile.dummy +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/pyproject.toml +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/pytest.ini +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/ruff.toml +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/specs/2026-08-14-detector-alignment-and-workload-exit-status.md +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/deployer/fixtures/__init__.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/deployer/fixtures/test_compare_versions.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/deployer/fixtures/test_correct_runner_image.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/deployer/fixtures/test_load_yaml_or_json.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/deployer/fixtures/test_load_yaml_or_json_multiple_jsons.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/deployer/fixtures/test_load_yaml_or_json_multiple_yamls.yaml +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/deployer/fixtures/test_load_yaml_or_json_single_json.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/deployer/fixtures/test_load_yaml_or_json_single_yaml.yaml +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/deployer/fixtures/test_nginx_entrypoint.sh +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/deployer/test_docker_status.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/deployer/test_kubernetes_status.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/deployer/test_podman_status.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/deployer/test_privileged.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/deployer/test_resource_injection_policy.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/deployer/test_runtime_class.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/deployer/test_utils.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/deployer/test_visible_devices_ordering.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/deployer/test_workload_status.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/fixtures/__init__.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/samples/README.md +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/samples/detect_output_amd_mi300x.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/samples/detect_output_amd_mi308x.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/samples/detect_output_amd_rx7800xt.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/samples/detect_output_ascend_310p3.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/samples/detect_output_ascend_910b2.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/samples/detect_output_hygon_k100ai.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/samples/detect_output_metax_c500.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_gb10.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_h100.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_h100_mig.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_h200.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_rtx4080super.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_rtx4090_48g.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_rtx4090d.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/samples/detect_output_nvidia_rtx5090d.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/samples/detect_output_thead_ppu.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/samples/topology_output_amd_mi300x.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/samples/topology_output_amd_mi308x.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/samples/topology_output_amd_rx7800xt.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/samples/topology_output_ascend_310p3.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/samples/topology_output_ascend_910b2.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/samples/topology_output_hygon_k100ai.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/samples/topology_output_metax_c500.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/samples/topology_output_mthreads_s5000.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_h100.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_h100_mig.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_h200.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_rtx4080super.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_rtx4090_48g.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_rtx4090d.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/samples/topology_output_nvidia_rtx5090d.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/samples/topology_output_thead_ppu.json +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/test_amd.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/test_cambricon.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/test_detector_cli.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/test_detector_types.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/test_detector_utils.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/test_hygon.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/test_iluvatar.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/test_metax.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/test_mig_devices.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/test_mthreads.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/test_nvidia.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/test_pycndev.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/test_samples.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/tests/gpustack_runtime/detector/test_thead.py +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/uv.lock +0 -0
- {gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/uv.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: gpustack-runtime
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: GPUStack Runtime is library for detecting GPU resources and launching GPU workloads.
|
|
5
5
|
Project-URL: Homepage, https://github.com/gpustack/runtime
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/gpustack/gpustack/issues
|
|
@@ -27,8 +27,8 @@ version_tuple: VERSION_TUPLE
|
|
|
27
27
|
__commit_id__: COMMIT_ID
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
|
|
30
|
-
__version__ = version = '0.2.
|
|
31
|
-
__version_tuple__ = version_tuple = (0, 2,
|
|
30
|
+
__version__ = version = '0.2.4'
|
|
31
|
+
__version_tuple__ = version_tuple = (0, 2, 4)
|
|
32
32
|
try:
|
|
33
33
|
from ._version_appendix import git_commit
|
|
34
34
|
__commit_id__ = commit_id = git_commit
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
git_commit = "59ea8c8"
|
|
@@ -362,6 +362,7 @@ class DeleteWorkloadSubCommand(SubCommand):
|
|
|
362
362
|
|
|
363
363
|
namespace: str
|
|
364
364
|
name: str
|
|
365
|
+
grace_period_seconds: int | None
|
|
365
366
|
|
|
366
367
|
@staticmethod
|
|
367
368
|
def register(parser: _SubParsersAction):
|
|
@@ -376,6 +377,13 @@ class DeleteWorkloadSubCommand(SubCommand):
|
|
|
376
377
|
help="Namespace of the workload",
|
|
377
378
|
)
|
|
378
379
|
|
|
380
|
+
delete_parser.add_argument(
|
|
381
|
+
"--grace-period-seconds",
|
|
382
|
+
type=int,
|
|
383
|
+
help="Duration in seconds the workload needs to terminate gracefully, "
|
|
384
|
+
"which overrides the one declared by the workload plan",
|
|
385
|
+
)
|
|
386
|
+
|
|
379
387
|
delete_parser.add_argument(
|
|
380
388
|
"name",
|
|
381
389
|
type=str,
|
|
@@ -387,6 +395,7 @@ class DeleteWorkloadSubCommand(SubCommand):
|
|
|
387
395
|
def __init__(self, args: Namespace):
|
|
388
396
|
self.namespace = args.namespace
|
|
389
397
|
self.name = args.name
|
|
398
|
+
self.grace_period_seconds = args.grace_period_seconds
|
|
390
399
|
|
|
391
400
|
if not self.name:
|
|
392
401
|
msg = "The name argument is required."
|
|
@@ -396,6 +405,7 @@ class DeleteWorkloadSubCommand(SubCommand):
|
|
|
396
405
|
st = delete_workload(
|
|
397
406
|
name=self.name,
|
|
398
407
|
namespace=self.namespace,
|
|
408
|
+
grace_period_seconds=self.grace_period_seconds,
|
|
399
409
|
)
|
|
400
410
|
if st:
|
|
401
411
|
print(f"Deleted workload '{self.name}'.")
|
|
@@ -428,11 +438,21 @@ class DeleteWorkloadsSubCommand(SubCommand):
|
|
|
428
438
|
help="Filter workloads by labels (key=value pairs separated by commas)",
|
|
429
439
|
)
|
|
430
440
|
|
|
441
|
+
delete_parser.add_argument(
|
|
442
|
+
"--grace-period-seconds",
|
|
443
|
+
type=int,
|
|
444
|
+
help="Duration in seconds the workloads need to terminate gracefully, "
|
|
445
|
+
"which overrides the one declared by the workload plans. "
|
|
446
|
+
"The workloads are deleted one after another, "
|
|
447
|
+
"so the whole deletion may take this duration per workload",
|
|
448
|
+
)
|
|
449
|
+
|
|
431
450
|
delete_parser.set_defaults(func=DeleteWorkloadsSubCommand)
|
|
432
451
|
|
|
433
452
|
def __init__(self, args: Namespace):
|
|
434
453
|
self.namespace = args.namespace
|
|
435
454
|
self.labels = args.labels
|
|
455
|
+
self.grace_period_seconds = args.grace_period_seconds
|
|
436
456
|
|
|
437
457
|
def run(self):
|
|
438
458
|
sts: list[WorkloadStatus] = list_workloads(
|
|
@@ -443,6 +463,7 @@ class DeleteWorkloadsSubCommand(SubCommand):
|
|
|
443
463
|
delete_workload(
|
|
444
464
|
name=st.name,
|
|
445
465
|
namespace=st.namespace,
|
|
466
|
+
grace_period_seconds=self.grace_period_seconds,
|
|
446
467
|
)
|
|
447
468
|
print(f"Deleted workload '{st.name}'.")
|
|
448
469
|
if not sts:
|
{gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/deployer/__init__.py
RENAMED
|
@@ -150,6 +150,7 @@ def get_workload(
|
|
|
150
150
|
def delete_workload(
|
|
151
151
|
name: WorkloadName,
|
|
152
152
|
namespace: WorkloadNamespace | None = None,
|
|
153
|
+
grace_period_seconds: int | None = None,
|
|
153
154
|
) -> WorkloadStatus | None:
|
|
154
155
|
"""
|
|
155
156
|
Delete the given workload.
|
|
@@ -159,11 +160,16 @@ def delete_workload(
|
|
|
159
160
|
The name of the workload to delete.
|
|
160
161
|
namespace:
|
|
161
162
|
The namespace of the workload.
|
|
163
|
+
grace_period_seconds:
|
|
164
|
+
Duration in seconds the workload needs to terminate gracefully,
|
|
165
|
+
which overrides the one declared by the workload plan.
|
|
162
166
|
|
|
163
167
|
Return:
|
|
164
168
|
The status if found, None otherwise.
|
|
165
169
|
|
|
166
170
|
Raises:
|
|
171
|
+
ValueError:
|
|
172
|
+
If the grace period is negative.
|
|
167
173
|
UnsupportedError:
|
|
168
174
|
If no deployer supports the given workload.
|
|
169
175
|
OperationError:
|
|
@@ -174,7 +180,11 @@ def delete_workload(
|
|
|
174
180
|
if not dep.is_supported():
|
|
175
181
|
continue
|
|
176
182
|
|
|
177
|
-
return dep.delete(
|
|
183
|
+
return dep.delete(
|
|
184
|
+
name=name,
|
|
185
|
+
namespace=namespace,
|
|
186
|
+
grace_period_seconds=grace_period_seconds,
|
|
187
|
+
)
|
|
178
188
|
|
|
179
189
|
raise UnsupportedError(_NO_AVAILABLE_DEPLOYER_MSG)
|
|
180
190
|
|
{gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/deployer/__types__.py
RENAMED
|
@@ -832,6 +832,12 @@ WorkloadName = str
|
|
|
832
832
|
Name for a workload.
|
|
833
833
|
"""
|
|
834
834
|
|
|
835
|
+
DEFAULT_TERMINATION_GRACE_PERIOD_SECONDS = 30
|
|
836
|
+
"""
|
|
837
|
+
Default duration in seconds a workload needs to terminate gracefully,
|
|
838
|
+
which aligns with the Kubernetes API server default.
|
|
839
|
+
"""
|
|
840
|
+
|
|
835
841
|
|
|
836
842
|
@dataclass_json
|
|
837
843
|
@dataclass
|
|
@@ -862,6 +868,8 @@ class WorkloadPlan(WorkloadSecurity):
|
|
|
862
868
|
The group ID to own the filesystem of the workload.
|
|
863
869
|
sysctls (dict[str, str] | None):
|
|
864
870
|
Sysctls to set for the workload.
|
|
871
|
+
termination_grace_period_seconds (int):
|
|
872
|
+
Duration in seconds the containers of the workload need to terminate gracefully.
|
|
865
873
|
containers (list[Container] | None):
|
|
866
874
|
Containers in the workload.
|
|
867
875
|
It must contain at least one "RUN" profile container.
|
|
@@ -909,6 +917,12 @@ class WorkloadPlan(WorkloadSecurity):
|
|
|
909
917
|
Containers in the workload.
|
|
910
918
|
It must contain at least one "RUN" profile container.
|
|
911
919
|
"""
|
|
920
|
+
termination_grace_period_seconds: int = DEFAULT_TERMINATION_GRACE_PERIOD_SECONDS
|
|
921
|
+
"""
|
|
922
|
+
Duration in seconds the containers of the workload need to terminate gracefully.
|
|
923
|
+
Containers are signaled to stop, and killed once the duration elapses.
|
|
924
|
+
Zero means killing immediately.
|
|
925
|
+
"""
|
|
912
926
|
|
|
913
927
|
##
|
|
914
928
|
## The below are internal use only fields.
|
|
@@ -957,6 +971,15 @@ class WorkloadPlan(WorkloadSecurity):
|
|
|
957
971
|
msg = 'Workload must contain at least one "RUN" profile container.'
|
|
958
972
|
raise ValueError(msg)
|
|
959
973
|
|
|
974
|
+
# Default and validate termination grace period.
|
|
975
|
+
if self.termination_grace_period_seconds is None:
|
|
976
|
+
self.termination_grace_period_seconds = (
|
|
977
|
+
DEFAULT_TERMINATION_GRACE_PERIOD_SECONDS
|
|
978
|
+
)
|
|
979
|
+
elif self.termination_grace_period_seconds < 0:
|
|
980
|
+
msg = "Workload termination grace period must not be negative."
|
|
981
|
+
raise ValueError(msg)
|
|
982
|
+
|
|
960
983
|
# Validate workload labels, including label names and values.
|
|
961
984
|
for ln, lv in self.labels.items():
|
|
962
985
|
for s in ln.split("/"):
|
|
@@ -2109,6 +2132,8 @@ class Deployer(ABC):
|
|
|
2109
2132
|
self,
|
|
2110
2133
|
name: WorkloadName,
|
|
2111
2134
|
namespace: WorkloadNamespace | None = None,
|
|
2135
|
+
*,
|
|
2136
|
+
grace_period_seconds: int | None = None,
|
|
2112
2137
|
async_mode: bool | None = None,
|
|
2113
2138
|
) -> WorkloadStatus | None:
|
|
2114
2139
|
"""
|
|
@@ -2119,6 +2144,9 @@ class Deployer(ABC):
|
|
|
2119
2144
|
The name of the workload.
|
|
2120
2145
|
namespace:
|
|
2121
2146
|
The namespace of the workload.
|
|
2147
|
+
grace_period_seconds:
|
|
2148
|
+
Duration in seconds the workload needs to terminate gracefully,
|
|
2149
|
+
which overrides the one declared by the workload plan.
|
|
2122
2150
|
async_mode:
|
|
2123
2151
|
Whether to execute in a separate thread.
|
|
2124
2152
|
|
|
@@ -2128,16 +2156,23 @@ class Deployer(ABC):
|
|
|
2128
2156
|
Raises:
|
|
2129
2157
|
UnsupportedError:
|
|
2130
2158
|
If the deployer is not supported in the current environment.
|
|
2159
|
+
ValueError:
|
|
2160
|
+
If the grace period is negative.
|
|
2131
2161
|
OperationError:
|
|
2132
2162
|
If the workload fails to delete.
|
|
2133
2163
|
|
|
2134
2164
|
"""
|
|
2165
|
+
if grace_period_seconds is not None and grace_period_seconds < 0:
|
|
2166
|
+
msg = "Workload termination grace period must not be negative."
|
|
2167
|
+
raise ValueError(msg)
|
|
2168
|
+
|
|
2135
2169
|
if async_mode:
|
|
2136
2170
|
try:
|
|
2137
2171
|
future = self.pool.submit(
|
|
2138
2172
|
self._delete,
|
|
2139
2173
|
name,
|
|
2140
2174
|
namespace,
|
|
2175
|
+
grace_period_seconds,
|
|
2141
2176
|
)
|
|
2142
2177
|
return future.result()
|
|
2143
2178
|
except OperationError:
|
|
@@ -2146,13 +2181,14 @@ class Deployer(ABC):
|
|
|
2146
2181
|
msg = "Asynchronous workload delete failed."
|
|
2147
2182
|
raise OperationError(msg) from e
|
|
2148
2183
|
else:
|
|
2149
|
-
return self._delete(name, namespace)
|
|
2184
|
+
return self._delete(name, namespace, grace_period_seconds)
|
|
2150
2185
|
|
|
2151
2186
|
@abstractmethod
|
|
2152
2187
|
def _delete(
|
|
2153
2188
|
self,
|
|
2154
2189
|
name: WorkloadName,
|
|
2155
2190
|
namespace: WorkloadNamespace | None = None,
|
|
2191
|
+
grace_period_seconds: int | None = None,
|
|
2156
2192
|
) -> WorkloadStatus | None:
|
|
2157
2193
|
"""
|
|
2158
2194
|
Delete a workload.
|
|
@@ -2162,6 +2198,9 @@ class Deployer(ABC):
|
|
|
2162
2198
|
The name of the workload.
|
|
2163
2199
|
namespace:
|
|
2164
2200
|
The namespace of the workload.
|
|
2201
|
+
grace_period_seconds:
|
|
2202
|
+
Duration in seconds the workload needs to terminate gracefully,
|
|
2203
|
+
which overrides the one declared by the workload plan.
|
|
2165
2204
|
|
|
2166
2205
|
Return:
|
|
2167
2206
|
The status if found, None otherwise.
|
{gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/deployer/cdi/__utils__.py
RENAMED
|
@@ -143,6 +143,56 @@ def device_to_cdi_device_node(
|
|
|
143
143
|
)
|
|
144
144
|
|
|
145
145
|
|
|
146
|
+
def path_to_cdi_device_nodes(
|
|
147
|
+
path: str,
|
|
148
|
+
permission: str = "rw",
|
|
149
|
+
no_user: bool = False,
|
|
150
|
+
) -> list[ConfigDeviceNode]:
|
|
151
|
+
"""
|
|
152
|
+
Convert a device path, or a directory holding device paths, to ConfigDeviceNodes.
|
|
153
|
+
|
|
154
|
+
A generation may expose a bus as a directory of device nodes rather than as
|
|
155
|
+
a single node -- Ascend's UB does, which is why the operator enumerates it,
|
|
156
|
+
see addUBDevicesFromDir in
|
|
157
|
+
https://gitcode.com/Ascend/mind-cluster/blob/master/component/ascend-common/cdi/devnode.go.
|
|
158
|
+
Both shapes are accepted so that a path which is a plain device node on one
|
|
159
|
+
driver and a directory on another needs no caller-side branch.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
path:
|
|
163
|
+
Path to the device, or to a directory of devices, on the host.
|
|
164
|
+
permission:
|
|
165
|
+
Permissions for the devices.
|
|
166
|
+
no_user:
|
|
167
|
+
Whether to omit user and group information.
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
The ConfigDeviceNode objects, empty if the path holds no device.
|
|
171
|
+
|
|
172
|
+
"""
|
|
173
|
+
p = Path(path)
|
|
174
|
+
if not p.is_dir():
|
|
175
|
+
cdn = device_to_cdi_device_node(
|
|
176
|
+
path=path,
|
|
177
|
+
permission=permission,
|
|
178
|
+
no_user=no_user,
|
|
179
|
+
)
|
|
180
|
+
return [cdn] if cdn else []
|
|
181
|
+
|
|
182
|
+
return [
|
|
183
|
+
cdn
|
|
184
|
+
for entry in sorted(p.iterdir())
|
|
185
|
+
if not entry.is_dir()
|
|
186
|
+
and (
|
|
187
|
+
cdn := device_to_cdi_device_node(
|
|
188
|
+
path=str(entry),
|
|
189
|
+
permission=permission,
|
|
190
|
+
no_user=no_user,
|
|
191
|
+
)
|
|
192
|
+
)
|
|
193
|
+
]
|
|
194
|
+
|
|
195
|
+
|
|
146
196
|
def path_to_cdi_mount(
|
|
147
197
|
path: str,
|
|
148
198
|
container_path: str | None = None,
|
|
@@ -181,3 +231,34 @@ def path_to_cdi_mount(
|
|
|
181
231
|
container_path=container_path,
|
|
182
232
|
options=options,
|
|
183
233
|
)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def glob_to_cdi_mounts(
|
|
237
|
+
pattern: str,
|
|
238
|
+
options: list[str] | None = None,
|
|
239
|
+
) -> list[ConfigMount]:
|
|
240
|
+
"""
|
|
241
|
+
Convert every path matching a glob pattern to ConfigMounts.
|
|
242
|
+
|
|
243
|
+
A user-space library is versioned in its file name, so the set of files to
|
|
244
|
+
mount cannot be spelled out ahead of time -- the operator's mount profile
|
|
245
|
+
lists them as patterns for the same reason.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
pattern:
|
|
249
|
+
Path on the host whose last segment may carry a wildcard; the
|
|
250
|
+
directory part is taken literally, so a wildcard there matches
|
|
251
|
+
nothing. A pattern without a wildcard resolves to that path alone.
|
|
252
|
+
options:
|
|
253
|
+
Mount options.
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
The ConfigMount objects, empty if nothing matches.
|
|
257
|
+
|
|
258
|
+
"""
|
|
259
|
+
p = Path(pattern)
|
|
260
|
+
return [
|
|
261
|
+
cm
|
|
262
|
+
for path in sorted(p.parent.glob(p.name))
|
|
263
|
+
if (cm := path_to_cdi_mount(path=str(path), options=options))
|
|
264
|
+
]
|
{gpustack_runtime-0.2.3.post1 → gpustack_runtime-0.2.4}/gpustack_runtime/deployer/cdi/ascend.py
RENAMED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
from __future__ import annotations as __future_annotations__
|
|
2
2
|
|
|
3
|
+
import logging
|
|
4
|
+
|
|
3
5
|
from ...detector import (
|
|
4
6
|
Devices,
|
|
5
7
|
ManufacturerEnum,
|
|
6
8
|
detect_devices,
|
|
7
9
|
filter_devices_by_manufacturer,
|
|
8
10
|
)
|
|
11
|
+
from ...detector.ascend import get_ascend_cann_variant
|
|
9
12
|
from .__types__ import (
|
|
10
13
|
Config,
|
|
11
14
|
ConfigContainerEdits,
|
|
@@ -14,7 +17,44 @@ from .__types__ import (
|
|
|
14
17
|
manufacturer_to_cdi_kind,
|
|
15
18
|
manufacturer_to_runtime_env,
|
|
16
19
|
)
|
|
17
|
-
from .__utils__ import
|
|
20
|
+
from .__utils__ import (
|
|
21
|
+
device_to_cdi_device_node,
|
|
22
|
+
glob_to_cdi_mounts,
|
|
23
|
+
path_to_cdi_device_nodes,
|
|
24
|
+
path_to_cdi_mount,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
_A5_CANN_VARIANT = "950"
|
|
30
|
+
"""
|
|
31
|
+
The CANN variant of the A5 generation, whose UB fabric replaces what the
|
|
32
|
+
earlier generations reach through the shared memory device.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
_A5_UB_MOUNT_PATTERNS = [
|
|
36
|
+
"/usr/lib64/libummu*",
|
|
37
|
+
"/usr/lib64/liburma*",
|
|
38
|
+
"/usr/lib64/urma",
|
|
39
|
+
"/usr/lib64/libnl*",
|
|
40
|
+
"/usr/bin/urma_admin",
|
|
41
|
+
"/usr/bin/urma_perftest",
|
|
42
|
+
"/usr/bin/urma_ping",
|
|
43
|
+
]
|
|
44
|
+
"""
|
|
45
|
+
The UB user-space libraries an A5 container needs, mirroring the operator's
|
|
46
|
+
mount profile for the Ascend950 generation, see
|
|
47
|
+
https://gitcode.com/Ascend/mind-cluster/blob/master/component/ascend-common/cdi/mount/profile.go.
|
|
48
|
+
|
|
49
|
+
These are mounted for the A5 generation only. Some of them -- libnl above all
|
|
50
|
+
-- are ordinary system libraries present on any host, so mounting them
|
|
51
|
+
unconditionally would shadow what an earlier generation's container ships with
|
|
52
|
+
its own image.
|
|
53
|
+
|
|
54
|
+
The /usr/lib64 prefix is the operator's, and holds on the openEuler and CentOS
|
|
55
|
+
hosts an A5 ships on; a Debian-derived host uses /usr/lib/<triplet>, where none
|
|
56
|
+
of these match -- hence the log line when a pattern finds nothing.
|
|
57
|
+
"""
|
|
18
58
|
|
|
19
59
|
|
|
20
60
|
class AscendGenerator(Generator):
|
|
@@ -73,16 +113,14 @@ class AscendGenerator(Generator):
|
|
|
73
113
|
break
|
|
74
114
|
for p in [
|
|
75
115
|
"/dev/dvpp_cmdlist",
|
|
116
|
+
# UB exposes a directory of device nodes rather than a single one,
|
|
117
|
+
# so every entry below it has to be injected.
|
|
76
118
|
"/dev/uburma",
|
|
77
119
|
"/dev/ummu",
|
|
78
120
|
"/dev/devmm_svm",
|
|
79
121
|
"/dev/hisi_hdc",
|
|
80
122
|
]:
|
|
81
|
-
|
|
82
|
-
path=p,
|
|
83
|
-
)
|
|
84
|
-
if cdn:
|
|
85
|
-
common_device_nodes.append(cdn)
|
|
123
|
+
common_device_nodes.extend(path_to_cdi_device_nodes(path=p))
|
|
86
124
|
if not common_device_nodes:
|
|
87
125
|
return None
|
|
88
126
|
|
|
@@ -102,6 +140,25 @@ class AscendGenerator(Generator):
|
|
|
102
140
|
if cm:
|
|
103
141
|
common_mounts.append(cm)
|
|
104
142
|
|
|
143
|
+
# Device.appendix defaults to None and callers pass their own devices
|
|
144
|
+
# in, so every read goes through `or {}`.
|
|
145
|
+
if any(
|
|
146
|
+
get_ascend_cann_variant((dev.appendix or {}).get("arch_family"))
|
|
147
|
+
== _A5_CANN_VARIANT
|
|
148
|
+
for dev in devices
|
|
149
|
+
if dev
|
|
150
|
+
):
|
|
151
|
+
for pattern in _A5_UB_MOUNT_PATTERNS:
|
|
152
|
+
ub_mounts = glob_to_cdi_mounts(pattern=pattern)
|
|
153
|
+
if not ub_mounts:
|
|
154
|
+
# Otherwise this surfaces later as a container that cannot
|
|
155
|
+
# reach the UB fabric.
|
|
156
|
+
logger.debug(
|
|
157
|
+
"No UB library matched %s, an A5 container will lack it",
|
|
158
|
+
pattern,
|
|
159
|
+
)
|
|
160
|
+
common_mounts.extend(ub_mounts)
|
|
161
|
+
|
|
105
162
|
cdi_devices: list[ConfigDevice] = []
|
|
106
163
|
|
|
107
164
|
all_device_nodes = []
|
|
@@ -119,7 +176,7 @@ class AscendGenerator(Generator):
|
|
|
119
176
|
# addressed by the index, which would resolve to another NPU's
|
|
120
177
|
# node. The detector already drops such a device; this guards the
|
|
121
178
|
# devices a caller passes in.
|
|
122
|
-
cdn_number = dev.appendix.get("physical_id")
|
|
179
|
+
cdn_number = (dev.appendix or {}).get("physical_id")
|
|
123
180
|
if cdn_number is None:
|
|
124
181
|
continue
|
|
125
182
|
cdn_path = f"/dev/davinci{cdn_number}"
|
|
@@ -8,6 +8,7 @@ import os
|
|
|
8
8
|
import socket
|
|
9
9
|
import sys
|
|
10
10
|
import tarfile
|
|
11
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
11
12
|
from dataclasses import dataclass, field
|
|
12
13
|
from math import ceil
|
|
13
14
|
from pathlib import Path
|
|
@@ -27,6 +28,7 @@ from tqdm import tqdm
|
|
|
27
28
|
from .. import envs
|
|
28
29
|
from ..logging import debug_log_exception, debug_log_warning
|
|
29
30
|
from .__types__ import (
|
|
31
|
+
DEFAULT_TERMINATION_GRACE_PERIOD_SECONDS,
|
|
30
32
|
Container,
|
|
31
33
|
ContainerCheck,
|
|
32
34
|
ContainerEnv,
|
|
@@ -68,6 +70,9 @@ _LABEL_COMPONENT = f"{envs.GPUSTACK_RUNTIME_DEPLOY_LABEL_PREFIX}/component"
|
|
|
68
70
|
_LABEL_COMPONENT_NAME = f"{_LABEL_COMPONENT}-name"
|
|
69
71
|
_LABEL_COMPONENT_INDEX = f"{_LABEL_COMPONENT}-index"
|
|
70
72
|
_LABEL_COMPONENT_HEAL_PREFIX = f"{_LABEL_COMPONENT}-heal"
|
|
73
|
+
_LABEL_TERMINATION_GRACE_PERIOD_SECONDS = (
|
|
74
|
+
f"{envs.GPUSTACK_RUNTIME_DEPLOY_LABEL_PREFIX}/termination-grace-period-seconds"
|
|
75
|
+
)
|
|
71
76
|
|
|
72
77
|
|
|
73
78
|
@dataclass_json
|
|
@@ -104,6 +109,8 @@ class DockerWorkloadPlan(WorkloadPlan):
|
|
|
104
109
|
The group ID to own the filesystem of the workload.
|
|
105
110
|
sysctls (dict[str, str] | None):
|
|
106
111
|
Sysctls to set for the workload.
|
|
112
|
+
termination_grace_period_seconds (int):
|
|
113
|
+
Duration in seconds the containers of the workload need to terminate gracefully.
|
|
107
114
|
containers (list[tuple[int, Container]] | None):
|
|
108
115
|
List of containers in the workload.
|
|
109
116
|
It must contain at least one "RUN" profile container.
|
|
@@ -138,6 +145,12 @@ class DockerWorkloadPlan(WorkloadPlan):
|
|
|
138
145
|
# Default and validate in the base class.
|
|
139
146
|
super().validate_and_default()
|
|
140
147
|
|
|
148
|
+
# Carry the termination grace period on the containers,
|
|
149
|
+
# as the Docker models layer cannot express a create time stop timeout.
|
|
150
|
+
self.labels[_LABEL_TERMINATION_GRACE_PERIOD_SECONDS] = str(
|
|
151
|
+
self.termination_grace_period_seconds,
|
|
152
|
+
)
|
|
153
|
+
|
|
141
154
|
# Adjust images.
|
|
142
155
|
self.pause_image = adjust_image_with_envs(self.pause_image)
|
|
143
156
|
self.unhealthy_restart_image = adjust_image_with_envs(
|
|
@@ -1650,6 +1663,7 @@ class DockerDeployer(EndoscopicDeployer):
|
|
|
1650
1663
|
self,
|
|
1651
1664
|
name: WorkloadName,
|
|
1652
1665
|
namespace: WorkloadNamespace | None = None,
|
|
1666
|
+
grace_period_seconds: int | None = None,
|
|
1653
1667
|
) -> WorkloadStatus | None:
|
|
1654
1668
|
"""
|
|
1655
1669
|
Delete a Docker workload.
|
|
@@ -1659,6 +1673,9 @@ class DockerDeployer(EndoscopicDeployer):
|
|
|
1659
1673
|
The name of the workload.
|
|
1660
1674
|
namespace:
|
|
1661
1675
|
The namespace of the workload.
|
|
1676
|
+
grace_period_seconds:
|
|
1677
|
+
Duration in seconds the workload needs to terminate gracefully,
|
|
1678
|
+
which overrides the one declared by the workload plan.
|
|
1662
1679
|
|
|
1663
1680
|
Return:
|
|
1664
1681
|
The status if found, None otherwise.
|
|
@@ -1678,19 +1695,40 @@ class DockerDeployer(EndoscopicDeployer):
|
|
|
1678
1695
|
# Remove all containers with the workload label.
|
|
1679
1696
|
try:
|
|
1680
1697
|
d_containers = getattr(workload, "_d_containers", [])
|
|
1681
|
-
# Remove
|
|
1698
|
+
# Remove the unhealthy restart container first,
|
|
1699
|
+
# otherwise it restarts the containers draining below.
|
|
1682
1700
|
for c in d_containers:
|
|
1683
|
-
if "-
|
|
1701
|
+
if c.labels.get(_LABEL_COMPONENT) == "unhealthy-restart":
|
|
1684
1702
|
c.remove(
|
|
1685
1703
|
force=True,
|
|
1686
1704
|
)
|
|
1687
|
-
# Then
|
|
1705
|
+
# Then drain the non-pause containers concurrently,
|
|
1706
|
+
# so each of them gets the whole grace period,
|
|
1707
|
+
# and remove them.
|
|
1708
|
+
d_drain_containers = [
|
|
1709
|
+
c
|
|
1710
|
+
for c in d_containers
|
|
1711
|
+
if c.labels.get(_LABEL_COMPONENT) not in ("pause", "unhealthy-restart")
|
|
1712
|
+
]
|
|
1713
|
+
_stop_containers(
|
|
1714
|
+
d_drain_containers,
|
|
1715
|
+
_termination_grace_period_seconds(d_containers, grace_period_seconds),
|
|
1716
|
+
)
|
|
1717
|
+
for c in d_drain_containers:
|
|
1718
|
+
c.remove(
|
|
1719
|
+
force=True,
|
|
1720
|
+
)
|
|
1721
|
+
# Finally remove the pause containers,
|
|
1722
|
+
# which hold the namespaces shared by the above containers.
|
|
1688
1723
|
for c in d_containers:
|
|
1689
|
-
if "
|
|
1724
|
+
if c.labels.get(_LABEL_COMPONENT) == "pause":
|
|
1690
1725
|
c.remove(
|
|
1691
1726
|
force=True,
|
|
1692
1727
|
)
|
|
1693
|
-
|
|
1728
|
+
# NB(thxCode): Deleting a workload always fails with an OperationError,
|
|
1729
|
+
# as neither the Docker SDK errors nor the transport ones underneath them
|
|
1730
|
+
# share a base class narrow enough to catch on its own.
|
|
1731
|
+
except Exception as e:
|
|
1694
1732
|
msg = f"Failed to delete containers for workload {name}{_detail_api_call_error(e)}"
|
|
1695
1733
|
raise OperationError(msg) from e
|
|
1696
1734
|
|
|
@@ -1711,7 +1749,7 @@ class DockerDeployer(EndoscopicDeployer):
|
|
|
1711
1749
|
v.remove(
|
|
1712
1750
|
force=True,
|
|
1713
1751
|
)
|
|
1714
|
-
except
|
|
1752
|
+
except Exception as e:
|
|
1715
1753
|
msg = f"Failed to delete volumes for workload {name}{_detail_api_call_error(e)}"
|
|
1716
1754
|
raise OperationError(msg) from e
|
|
1717
1755
|
|
|
@@ -2168,6 +2206,71 @@ def _has_restart_policy(
|
|
|
2168
2206
|
)
|
|
2169
2207
|
|
|
2170
2208
|
|
|
2209
|
+
def _stop_containers(
|
|
2210
|
+
containers: list[docker.models.containers.Container],
|
|
2211
|
+
grace_period_seconds: int,
|
|
2212
|
+
):
|
|
2213
|
+
"""
|
|
2214
|
+
Stop the given containers concurrently,
|
|
2215
|
+
so each of them gets the whole grace period,
|
|
2216
|
+
which mirrors how Kubernetes terminates the containers of a Pod.
|
|
2217
|
+
|
|
2218
|
+
A container failing to stop is left to the forceful removal following this,
|
|
2219
|
+
as the removal is the authoritative teardown.
|
|
2220
|
+
|
|
2221
|
+
Args:
|
|
2222
|
+
containers:
|
|
2223
|
+
List of Docker containers to stop.
|
|
2224
|
+
grace_period_seconds:
|
|
2225
|
+
Duration in seconds the containers need to terminate gracefully.
|
|
2226
|
+
|
|
2227
|
+
"""
|
|
2228
|
+
if not containers:
|
|
2229
|
+
return
|
|
2230
|
+
|
|
2231
|
+
def stop(container: docker.models.containers.Container):
|
|
2232
|
+
try:
|
|
2233
|
+
container.stop(
|
|
2234
|
+
timeout=grace_period_seconds,
|
|
2235
|
+
)
|
|
2236
|
+
except Exception:
|
|
2237
|
+
debug_log_exception(logger, f"Failed to stop container {container.name}")
|
|
2238
|
+
|
|
2239
|
+
with ThreadPoolExecutor(max_workers=min(len(containers), 16)) as pool:
|
|
2240
|
+
list(pool.map(stop, containers))
|
|
2241
|
+
|
|
2242
|
+
|
|
2243
|
+
def _termination_grace_period_seconds(
|
|
2244
|
+
containers: list[docker.models.containers.Container],
|
|
2245
|
+
override: int | None = None,
|
|
2246
|
+
) -> int:
|
|
2247
|
+
"""
|
|
2248
|
+
Resolve how long the given containers may take to terminate gracefully,
|
|
2249
|
+
which prefers the given override, then the one declared by the workload plan,
|
|
2250
|
+
and finally the default.
|
|
2251
|
+
|
|
2252
|
+
Args:
|
|
2253
|
+
containers:
|
|
2254
|
+
List of Docker containers in the workload.
|
|
2255
|
+
override:
|
|
2256
|
+
Duration in seconds overriding the one declared by the workload plan.
|
|
2257
|
+
|
|
2258
|
+
Returns:
|
|
2259
|
+
The duration in seconds.
|
|
2260
|
+
|
|
2261
|
+
"""
|
|
2262
|
+
if override is not None:
|
|
2263
|
+
return override
|
|
2264
|
+
|
|
2265
|
+
for c in containers:
|
|
2266
|
+
declared = c.labels.get(_LABEL_TERMINATION_GRACE_PERIOD_SECONDS)
|
|
2267
|
+
if declared:
|
|
2268
|
+
with contextlib.suppress(ValueError):
|
|
2269
|
+
return max(0, int(declared))
|
|
2270
|
+
|
|
2271
|
+
return DEFAULT_TERMINATION_GRACE_PERIOD_SECONDS
|
|
2272
|
+
|
|
2273
|
+
|
|
2171
2274
|
class DockerWorkloadExecStream(WorkloadExecStream):
|
|
2172
2275
|
"""
|
|
2173
2276
|
A WorkloadExecStream implementation for Docker exec socket streams.
|
|
@@ -2202,14 +2305,14 @@ class DockerWorkloadExecStream(WorkloadExecStream):
|
|
|
2202
2305
|
self._sock.close()
|
|
2203
2306
|
|
|
2204
2307
|
|
|
2205
|
-
def _detail_api_call_error(err:
|
|
2308
|
+
def _detail_api_call_error(err: Exception) -> str:
|
|
2206
2309
|
"""
|
|
2207
2310
|
Explain a Docker API error in a concise way,
|
|
2208
2311
|
if the envs.GPUSTACK_RUNTIME_DEPLOY_API_CALL_ERROR_DETAIL is enabled.
|
|
2209
2312
|
|
|
2210
2313
|
Args:
|
|
2211
2314
|
err:
|
|
2212
|
-
The Docker API error.
|
|
2315
|
+
The Docker API error, or the transport error underneath it.
|
|
2213
2316
|
|
|
2214
2317
|
Returns:
|
|
2215
2318
|
A concise explanation of the error.
|
|
@@ -2218,6 +2321,9 @@ def _detail_api_call_error(err: docker.errors.APIError) -> str:
|
|
|
2218
2321
|
if not envs.GPUSTACK_RUNTIME_DEPLOY_API_CALL_ERROR_DETAIL:
|
|
2219
2322
|
return ""
|
|
2220
2323
|
|
|
2324
|
+
if not isinstance(err, docker.errors.APIError):
|
|
2325
|
+
return f": {err}"
|
|
2326
|
+
|
|
2221
2327
|
msg = f": Docker {'Client' if err.is_client_error() else 'Server'} Error"
|
|
2222
2328
|
if err.explanation:
|
|
2223
2329
|
msg += f": {err.explanation}"
|