From 2c9afbfc7edf8df3d5fe31ebc4d74db5c45de8f2 Mon Sep 17 00:00:00 2001 From: Aman Yadav Date: Tue, 8 Sep 2026 11:06:00 +0530 Subject: [PATCH] fix(trainer): handle missing Podman container filters Co-Authored-By: Claude Sonnet 5 --- .../backends/container/adapters/podman.py | 1 + .../container/adapters/podman_test.py | 73 +++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 kubeflow/trainer/backends/container/adapters/podman_test.py diff --git a/kubeflow/trainer/backends/container/adapters/podman.py b/kubeflow/trainer/backends/container/adapters/podman.py index ee1376fae..6ced993f3 100644 --- a/kubeflow/trainer/backends/container/adapters/podman.py +++ b/kubeflow/trainer/backends/container/adapters/podman.py @@ -209,6 +209,7 @@ def get_container_ip(self, container_id: str, network_id: str) -> str | None: def list_containers(self, filters: dict[str, list[str]] | None = None) -> list[dict]: """List Podman containers with optional filters.""" + filters = filters or {} # Work-around for https://github.com/containers/podman-py/issues/542 for k, v in filters.items(): if len(v) == 1: diff --git a/kubeflow/trainer/backends/container/adapters/podman_test.py b/kubeflow/trainer/backends/container/adapters/podman_test.py new file mode 100644 index 000000000..1b14cb58a --- /dev/null +++ b/kubeflow/trainer/backends/container/adapters/podman_test.py @@ -0,0 +1,73 @@ +# Copyright 2025 The Kubeflow Authors. +# +# 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. + +"""Tests for PodmanClientAdapter.""" + +from unittest.mock import Mock + +from kubeflow.trainer.backends.container.adapters.podman import PodmanClientAdapter + + +def test_list_containers_without_filters(): + """Test that list_containers() with no filters lists all containers.""" + adapter = PodmanClientAdapter.__new__(PodmanClientAdapter) + container = Mock() + container.id = "abc123" + container.name = "job-0" + container.labels = {"app": "trainjob"} + container.status = "running" + container.attrs = {"Created": "2025-01-01T00:00:00Z"} + adapter.client = Mock() + adapter.client.containers.list.return_value = [container] + + result = adapter.list_containers() + + adapter.client.containers.list.assert_called_once_with(all=True, filters={}) + assert result == [ + { + "id": "abc123", + "name": "job-0", + "labels": {"app": "trainjob"}, + "status": "running", + "created": "2025-01-01T00:00:00Z", + } + ] + + +def test_list_containers_with_filters(): + """Test that a single-value label filter is collapsed and forwarded to Podman.""" + adapter = PodmanClientAdapter.__new__(PodmanClientAdapter) + container = Mock() + container.id = "abc123" + container.name = "job-0" + container.labels = {"app": "trainjob"} + container.status = "running" + container.attrs = {"Created": "2025-01-01T00:00:00Z"} + adapter.client = Mock() + adapter.client.containers.list.return_value = [container] + + result = adapter.list_containers(filters={"label": ["app=trainjob"]}) + + adapter.client.containers.list.assert_called_once_with( + all=True, filters={"label": "app=trainjob"} + ) + assert result == [ + { + "id": "abc123", + "name": "job-0", + "labels": {"app": "trainjob"}, + "status": "running", + "created": "2025-01-01T00:00:00Z", + } + ]