Repository: aws/sagemaker-hyperpod-cli

Issue:
Listing jobs with invalid namespace returns empty list
```
(.venv) gianpo@80a997400474 hyperpod-cli-test % hyperpod list-jobs --namespace abcdef
{
    "jobs": []
}
```

As per subject. I would expect an error to be raised if the namespace does not exist.

Discussion:
@pintaoz-aws: Fixed in https://github.com/aws/sagemaker-hyperpod-cli/pull/79. Now if the namespace does not exist it will output an error like this:
```
Unexpected error happens when trying to list training job : Namespace abcdef does not exist!
```

Solution patch:
diff --git a/src/hyperpod_cli/clients/kubernetes_client.py b/src/hyperpod_cli/clients/kubernetes_client.py
index 17cd162..ddbee18 100644
--- a/src/hyperpod_cli/clients/kubernetes_client.py
+++ b/src/hyperpod_cli/clients/kubernetes_client.py
@@ -302,6 +302,17 @@ class KubernetesClient:
             plural=PYTORCH_CUSTOM_OBJECT_PLURAL,
             label_selector=label_selector,
         )
+    
+    def check_if_namespace_exists(self, namespace: str):
+        try:
+            client.CoreV1Api().read_namespace(name=namespace)
+            return True
+        except client.rest.ApiException as e:
+            if e.status == 404:
+                return False
+            else:
+                print(f"Exception when calling read_namespace: {e}")
+                raise e
 
     def exec_command_on_pod(
         self,
diff --git a/src/hyperpod_cli/service/list_training_jobs.py b/src/hyperpod_cli/service/list_training_jobs.py
index 7b94738..37dfe3f 100644
--- a/src/hyperpod_cli/service/list_training_jobs.py
+++ b/src/hyperpod_cli/service/list_training_jobs.py
@@ -18,6 +18,7 @@ from datetime import datetime
 from hyperpod_cli.clients.kubernetes_client import (
     KubernetesClient,
 )
+from hyperpod_cli.utils import setup_logger
 from kubernetes.client.rest import ApiException
 from kubernetes.client import (
     V1ResourceAttributes
@@ -49,6 +50,8 @@ class ListTrainingJobs:
         k8s_client = KubernetesClient()
 
         jobs: List = []
+        logger = setup_logger(__name__)
+        logger.debug(namespace)
         try:
             if all_namespaces:
                 namespaces: List[str] = k8s_client.list_namespaces()
@@ -70,6 +73,9 @@ class ListTrainingJobs:
                     namespace = DiscoverNamespaces().discover_accessible_namespace(
                         resource_attributes_template
                     )
+                else:
+                    if not k8s_client.check_if_namespace_exists(namespace):
+                        raise ValueError(f"Namespace {namespace} does not exist!")
 
                 namespace_jobs = k8s_client.list_training_jobs(
                     namespace=namespace,


Test patch:
diff --git a/test/unit_tests/clients/test_kubernetes_client.py b/test/unit_tests/clients/test_kubernetes_client.py
index 4998674..cb269d6 100644
--- a/test/unit_tests/clients/test_kubernetes_client.py
+++ b/test/unit_tests/clients/test_kubernetes_client.py
@@ -661,3 +661,39 @@ class TestKubernetesClient(unittest.TestCase):
             plural="clusterqueues",
             name="test-cluster-queue",
         )
+
+    @patch("kubernetes.config.load_kube_config")
+    @patch(
+        "kubernetes.client.CoreV1Api.read_namespace",
+        return_value=Mock(
+            read_namespace=Mock(
+                return_value=V1Namespace(metadata=V1ObjectMeta(name="kubeflow"))
+            )
+        ),
+    )
+    def test_check_if_namespace_exists_true(
+        self,
+        mock_core_client: Mock,
+        mock_kube_config: Mock,
+    ):
+        mock_kube_config.return_value = None
+        test_client = KubernetesClient()
+        result = test_client.check_if_namespace_exists("kubeflow")
+        self.assertTrue(result)
+
+    @patch("kubernetes.config.load_kube_config")
+    @patch(
+        "kubernetes.client.CoreV1Api.read_namespace",
+        side_effect=client.rest.ApiException(
+            status=404,
+        ),
+    )
+    def test_check_if_namespace_exists_false(
+        self, 
+        mock_core_client: Mock,
+        mock_kube_config: Mock,
+    ):
+        mock_kube_config.return_value = None
+        test_client = KubernetesClient()
+        result = test_client.check_if_namespace_exists("abcdef")
+        self.assertFalse(result)
diff --git a/test/unit_tests/service/test_list_training_jobs_service.py b/test/unit_tests/service/test_list_training_jobs_service.py
index 4113cbc..d359333 100644
--- a/test/unit_tests/service/test_list_training_jobs_service.py
+++ b/test/unit_tests/service/test_list_training_jobs_service.py
@@ -273,6 +273,16 @@ class ListTrainingJobsTest(unittest.TestCase):
         result = self.mock_list_training_jobs.list_training_jobs(None, True, None, None)
         self.assertNotIn("State: null", result)
 
+    @mock.patch("hyperpod_cli.clients.kubernetes_client.KubernetesClient.__new__")
+    def test_list_training_jobs_namespace_not_exist(
+        self,
+        mock_kubernetes_client: mock.Mock,
+    ):
+        mock_kubernetes_client.return_value = self.mock_k8s_client
+        self.mock_k8s_client.check_if_namespace_exists.return_value = False
+        with self.assertRaises(ValueError):
+            self.mock_list_training_jobs.list_training_jobs("abcdef", False, None, None)
+
     def test_generate_table_with_no_priority_header_and_values(self):
         list_training_jobs = ListTrainingJobs()
         output_jobs = {
diff --git a/test/unit_tests/test_job.py b/test/unit_tests/test_job.py
index e4aca64..30a6e0d 100644
--- a/test/unit_tests/test_job.py
+++ b/test/unit_tests/test_job.py
@@ -258,6 +258,18 @@ class JobTest(unittest.TestCase):
             "Unexpected error happens when trying to list training job",
             result.output,
         )
+    
+    @mock.patch("hyperpod_cli.clients.kubernetes_client.KubernetesClient.__new__")
+    def test_list_job_when_namespace_not_exist(
+        self,
+        mock_kubernetes_client: mock.Mock,
+    ):
+        mock_client_instance = mock_kubernetes_client.return_value
+        mock_client_instance.check_if_namespace_exists.return_value = False
+        result = self.runner.invoke(list_jobs, ["--namespace", "abcdef"])
+        mock_client_instance.check_if_namespace_exists.assert_any_call("abcdef")
+        self.assertEqual(result.exit_code, 1)
+        self.assertIn("Namespace abcdef does not exist!", result.output)
 
     @mock.patch("hyperpod_cli.service.list_pods.ListPods")
     @mock.patch("hyperpod_cli.service.list_pods.ListPods.list_pods_for_training_job")