Skip to content

Commit 8198706

Browse files
pintaoz-awspintaoz
andauthored
Throw an error when listing training jobs for a nonexistent namespace (#79)
* Throw an error when listing training jobs for a nonexistent namespace * Add unit test * Add test_job test * Add test_job test --------- Co-authored-by: pintaoz <[email protected]>
1 parent f0cf31b commit 8198706

File tree

5 files changed

+75
-0
lines changed

5 files changed

+75
-0
lines changed

src/hyperpod_cli/clients/kubernetes_client.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,17 @@ def list_training_jobs(
302302
plural=PYTORCH_CUSTOM_OBJECT_PLURAL,
303303
label_selector=label_selector,
304304
)
305+
306+
def check_if_namespace_exists(self, namespace: str):
307+
try:
308+
client.CoreV1Api().read_namespace(name=namespace)
309+
return True
310+
except client.rest.ApiException as e:
311+
if e.status == 404:
312+
return False
313+
else:
314+
print(f"Exception when calling read_namespace: {e}")
315+
raise e
305316

306317
def exec_command_on_pod(
307318
self,

src/hyperpod_cli/service/list_training_jobs.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from hyperpod_cli.clients.kubernetes_client import (
1919
KubernetesClient,
2020
)
21+
from hyperpod_cli.utils import setup_logger
2122
from kubernetes.client.rest import ApiException
2223
from kubernetes.client import (
2324
V1ResourceAttributes
@@ -49,6 +50,8 @@ def list_training_jobs(
4950
k8s_client = KubernetesClient()
5051

5152
jobs: List = []
53+
logger = setup_logger(__name__)
54+
logger.debug(namespace)
5255
try:
5356
if all_namespaces:
5457
namespaces: List[str] = k8s_client.list_namespaces()
@@ -70,6 +73,9 @@ def list_training_jobs(
7073
namespace = DiscoverNamespaces().discover_accessible_namespace(
7174
resource_attributes_template
7275
)
76+
else:
77+
if not k8s_client.check_if_namespace_exists(namespace):
78+
raise ValueError(f"Namespace {namespace} does not exist!")
7379

7480
namespace_jobs = k8s_client.list_training_jobs(
7581
namespace=namespace,

test/unit_tests/clients/test_kubernetes_client.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,3 +661,39 @@ def test_get_cluster_queue(
661661
plural="clusterqueues",
662662
name="test-cluster-queue",
663663
)
664+
665+
@patch("kubernetes.config.load_kube_config")
666+
@patch(
667+
"kubernetes.client.CoreV1Api.read_namespace",
668+
return_value=Mock(
669+
read_namespace=Mock(
670+
return_value=V1Namespace(metadata=V1ObjectMeta(name="kubeflow"))
671+
)
672+
),
673+
)
674+
def test_check_if_namespace_exists_true(
675+
self,
676+
mock_core_client: Mock,
677+
mock_kube_config: Mock,
678+
):
679+
mock_kube_config.return_value = None
680+
test_client = KubernetesClient()
681+
result = test_client.check_if_namespace_exists("kubeflow")
682+
self.assertTrue(result)
683+
684+
@patch("kubernetes.config.load_kube_config")
685+
@patch(
686+
"kubernetes.client.CoreV1Api.read_namespace",
687+
side_effect=client.rest.ApiException(
688+
status=404,
689+
),
690+
)
691+
def test_check_if_namespace_exists_false(
692+
self,
693+
mock_core_client: Mock,
694+
mock_kube_config: Mock,
695+
):
696+
mock_kube_config.return_value = None
697+
test_client = KubernetesClient()
698+
result = test_client.check_if_namespace_exists("abcdef")
699+
self.assertFalse(result)

test/unit_tests/service/test_list_training_jobs_service.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,16 @@ def test_list_training_jobs_all_namespace_missing_status(
273273
result = self.mock_list_training_jobs.list_training_jobs(None, True, None, None)
274274
self.assertNotIn("State: null", result)
275275

276+
@mock.patch("hyperpod_cli.clients.kubernetes_client.KubernetesClient.__new__")
277+
def test_list_training_jobs_namespace_not_exist(
278+
self,
279+
mock_kubernetes_client: mock.Mock,
280+
):
281+
mock_kubernetes_client.return_value = self.mock_k8s_client
282+
self.mock_k8s_client.check_if_namespace_exists.return_value = False
283+
with self.assertRaises(ValueError):
284+
self.mock_list_training_jobs.list_training_jobs("abcdef", False, None, None)
285+
276286
def test_generate_table_with_no_priority_header_and_values(self):
277287
list_training_jobs = ListTrainingJobs()
278288
output_jobs = {

test/unit_tests/test_job.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,18 @@ def test_list_job_when_subprocess_command_gives_exception(
258258
"Unexpected error happens when trying to list training job",
259259
result.output,
260260
)
261+
262+
@mock.patch("hyperpod_cli.clients.kubernetes_client.KubernetesClient.__new__")
263+
def test_list_job_when_namespace_not_exist(
264+
self,
265+
mock_kubernetes_client: mock.Mock,
266+
):
267+
mock_client_instance = mock_kubernetes_client.return_value
268+
mock_client_instance.check_if_namespace_exists.return_value = False
269+
result = self.runner.invoke(list_jobs, ["--namespace", "abcdef"])
270+
mock_client_instance.check_if_namespace_exists.assert_any_call("abcdef")
271+
self.assertEqual(result.exit_code, 1)
272+
self.assertIn("Namespace abcdef does not exist!", result.output)
261273

262274
@mock.patch("hyperpod_cli.service.list_pods.ListPods")
263275
@mock.patch("hyperpod_cli.service.list_pods.ListPods.list_pods_for_training_job")

0 commit comments

Comments
 (0)