|
11 | 11 | import sys |
12 | 12 | import unittest |
13 | 13 | from datetime import datetime |
14 | | -from typing import Any, Dict |
| 14 | +from typing import Any, cast, Dict |
15 | 15 | from unittest.mock import MagicMock, patch |
16 | 16 |
|
17 | 17 | import torchx |
@@ -726,6 +726,7 @@ def test_runopts(self) -> None: |
726 | 726 | "image_repo", |
727 | 727 | "service_account", |
728 | 728 | "priority_class", |
| 729 | + "validate_spec", |
729 | 730 | }, |
730 | 731 | ) |
731 | 732 |
|
@@ -929,6 +930,97 @@ def test_min_replicas(self) -> None: |
929 | 930 | ] |
930 | 931 | self.assertEqual(min_available, [1, 1, 0]) |
931 | 932 |
|
| 933 | + @patch( |
| 934 | + "torchx.schedulers.kubernetes_scheduler.KubernetesScheduler._custom_objects_api" |
| 935 | + ) |
| 936 | + def test_validate_spec_invalid_name(self, mock_api: MagicMock) -> None: |
| 937 | + from kubernetes.client.rest import ApiException |
| 938 | + |
| 939 | + scheduler = create_scheduler("test") |
| 940 | + app = _test_app() |
| 941 | + app.name = "Invalid_Name" |
| 942 | + |
| 943 | + mock_api_instance = MagicMock() |
| 944 | + mock_api_instance.create_namespaced_custom_object.side_effect = ApiException( |
| 945 | + status=422, |
| 946 | + reason="Invalid", |
| 947 | + ) |
| 948 | + mock_api.return_value = mock_api_instance |
| 949 | + |
| 950 | + cfg = cast(KubernetesOpts, {"queue": "testqueue", "validate_spec": True}) |
| 951 | + |
| 952 | + with self.assertRaises(ValueError) as ctx: |
| 953 | + scheduler.submit_dryrun(app, cfg) |
| 954 | + |
| 955 | + self.assertIn("Invalid job spec", str(ctx.exception)) |
| 956 | + mock_api_instance.create_namespaced_custom_object.assert_called_once() |
| 957 | + call_kwargs = mock_api_instance.create_namespaced_custom_object.call_args[1] |
| 958 | + self.assertEqual(call_kwargs["dry_run"], "All") |
| 959 | + |
| 960 | + def test_validate_spec_disabled_by_default(self) -> None: |
| 961 | + scheduler = create_scheduler("test") |
| 962 | + app = _test_app() |
| 963 | + app.name = "x" * 60 |
| 964 | + |
| 965 | + cfg = KubernetesOpts({"queue": "testqueue"}) |
| 966 | + |
| 967 | + with patch( |
| 968 | + "torchx.schedulers.kubernetes_scheduler.make_unique" |
| 969 | + ) as make_unique_ctx: |
| 970 | + make_unique_ctx.return_value = "x" * 70 |
| 971 | + info = scheduler.submit_dryrun(app, cfg) |
| 972 | + |
| 973 | + self.assertIsNotNone(info) |
| 974 | + |
| 975 | + @patch( |
| 976 | + "torchx.schedulers.kubernetes_scheduler.KubernetesScheduler._custom_objects_api" |
| 977 | + ) |
| 978 | + def test_validate_spec_invalid_task_name(self, mock_api: MagicMock) -> None: |
| 979 | + from kubernetes.client.rest import ApiException |
| 980 | + |
| 981 | + scheduler = create_scheduler("test") |
| 982 | + app = _test_app() |
| 983 | + app.roles[0].name = "Invalid-Task-Name" |
| 984 | + |
| 985 | + mock_api_instance = MagicMock() |
| 986 | + mock_api_instance.create_namespaced_custom_object.side_effect = ApiException( |
| 987 | + status=422, |
| 988 | + reason="Invalid", |
| 989 | + ) |
| 990 | + mock_api.return_value = mock_api_instance |
| 991 | + |
| 992 | + cfg = cast(KubernetesOpts, {"queue": "testqueue", "validate_spec": True}) |
| 993 | + |
| 994 | + with self.assertRaises(ValueError) as ctx: |
| 995 | + scheduler.submit_dryrun(app, cfg) |
| 996 | + |
| 997 | + self.assertIn("Invalid job spec", str(ctx.exception)) |
| 998 | + |
| 999 | + @patch( |
| 1000 | + "torchx.schedulers.kubernetes_scheduler.KubernetesScheduler._custom_objects_api" |
| 1001 | + ) |
| 1002 | + def test_validate_spec_long_pod_name(self, mock_api: MagicMock) -> None: |
| 1003 | + scheduler = create_scheduler("test") |
| 1004 | + app = _test_app() |
| 1005 | + app.name = "x" * 50 |
| 1006 | + app.roles[0].name = "y" * 20 |
| 1007 | + |
| 1008 | + mock_api_instance = MagicMock() |
| 1009 | + mock_api_instance.create_namespaced_custom_object.return_value = {} |
| 1010 | + mock_api.return_value = mock_api_instance |
| 1011 | + |
| 1012 | + cfg = cast(KubernetesOpts, {"queue": "testqueue", "validate_spec": True}) |
| 1013 | + |
| 1014 | + with patch( |
| 1015 | + "torchx.schedulers.kubernetes_scheduler.make_unique" |
| 1016 | + ) as make_unique_ctx: |
| 1017 | + make_unique_ctx.return_value = "x" * 50 |
| 1018 | + with self.assertRaises(ValueError) as ctx: |
| 1019 | + scheduler.submit_dryrun(app, cfg) |
| 1020 | + |
| 1021 | + self.assertIn("Pod name", str(ctx.exception)) |
| 1022 | + self.assertIn("exceeds 63 character limit", str(ctx.exception)) |
| 1023 | + |
932 | 1024 |
|
933 | 1025 | class KubernetesSchedulerNoImportTest(unittest.TestCase): |
934 | 1026 | """ |
|
0 commit comments