1818"""
1919
2020import datetime
21- import tempfile
2221import os
22+ import subprocess
23+ import tempfile
24+ from typing import List
2325
2426from airflow import models
2527from airflow .decorators import task
3436from dags .tpu_observability .configs .common import MachineConfigMap , GCS_CONFIG_PATH
3537
3638
37- def _get_tpu_info_help_output (info : node_pool .Info , pod_name : str ) -> str :
38- """
39- Retrieves the raw output of `tpu-info -help` from a specific pod.
40- """
39+ def run_command (info , pod_name : str , tpu_args : str ) -> str :
40+ """Helper to handle KUBECONFIG and execute kubectl."""
4141 with tempfile .NamedTemporaryFile () as temp_config_file :
4242 env = os .environ .copy ()
4343 env ["KUBECONFIG" ] = temp_config_file .name
4444
4545 cmd = " && " .join ([
4646 jobset .Command .get_credentials_command (info ),
47- f"kubectl exec { pod_name } -n default -- tpu-info -help " ,
47+ f"kubectl exec { pod_name } -n default -- { tpu_args } " ,
4848 ])
49-
5049 return subprocess .run_exec (cmd , env = env )
5150
5251
52+ def check_output_contains (output : str , patterns : List [str ], context : str ):
53+ """Helper to verify expected strings in output."""
54+ for pattern in patterns :
55+ if pattern not in output :
56+ raise AssertionError (
57+ f"Validation failed for '{ context } ': Missing '{ pattern } '."
58+ )
59+
60+
5361@task
54- def validate_tpu_info_help (info : node_pool .Info , pod_name : str ) -> str :
55- """
56- Validates that the `tpu-info -help` output contains all required fields.
57- """
58- output = _get_tpu_info_help_output (info , pod_name )
59- required_patterns = [
62+ def validate_help (info , pod_name : str ) -> str :
63+ output = run_command (info , pod_name , "tpu-info -help" )
64+ patterns = [
6065 "Display TPU info and metrics." ,
6166 "options:" ,
6267 "-h, --help" ,
@@ -66,99 +71,37 @@ def validate_tpu_info_help(info: node_pool.Info, pod_name: str) -> str:
6671 "--rate RATE" ,
6772 "--list_metrics" ,
6873 ]
69-
70- for pattern in required_patterns :
71- if pattern not in output :
72- raise AssertionError (
73- "Validation failed: Missing expected string "
74- f"'{ pattern } ' in tpu-info help.\n "
75- f"Output received:\n { output } "
76- )
77-
74+ check_output_contains (output , patterns , "tpu-info -help" )
7875 return output
7976
8077
81- def _get_tpu_version_output (info : node_pool .Info , pod_name : str ) -> str :
82- """
83- Executes the version command in the pod to get libtpu version and accelerator type.
84- """
85- with tempfile .NamedTemporaryFile () as temp_config_file :
86- env = os .environ .copy ()
87- env ["KUBECONFIG" ] = temp_config_file .name
88-
89- cmd = " && " .join ([
90- jobset .Command .get_credentials_command (info ),
91- f"kubectl exec { pod_name } -n default -- tpu-info --version" ,
92- ])
93- return subprocess .run_exec (cmd , env = env )
94-
95-
9678@task
97- def validate_tpu_info_version (info : node_pool .Info , pod_name : str ) -> str :
98- """
99- Validates that `tpu-info --version` returns version and accelerator info.
100- """
101- output = _get_tpu_version_output (info , pod_name )
102- required_patterns = [
103- "tpu-info version:" ,
104- "libtpu version:" ,
105- "accelerator type:" ,
106- ]
107-
108- for pattern in required_patterns :
109- if pattern not in output :
110- raise AssertionError (
111- f"Validation failed: Missing expected string '{ pattern } ' "
112- f"in tpu-info --version output.\n Output:\n { output } "
113- )
114-
79+ def validate_version (info , pod_name : str ) -> str :
80+ output = run_command (info , pod_name , "tpu-info --version" )
81+ patterns = ["tpu-info version:" , "libtpu version:" , "accelerator type:" ]
82+ check_output_contains (output , patterns , "tpu-info --version" )
11583 return output
11684
11785
118- def _get_tpu_process_output (info : node_pool .Info , pod_name : str ) -> str :
119- """
120- Executes the process command in the pod to get the TPU process table.
121- """
122- with tempfile .NamedTemporaryFile () as temp_config_file :
123- env = os .environ .copy ()
124- env ["KUBECONFIG" ] = temp_config_file .name
125-
126- cmd = " && " .join ([
127- jobset .Command .get_credentials_command (info ),
128- f"kubectl exec { pod_name } -n default -- tpu-info --process" ,
129- ])
130- return subprocess .run_exec (cmd , env = env )
131-
132-
13386@task
134- def validate_tpu_info_process (info : node_pool .Info , pod_name : str ) -> str :
135- """
136- Validates that `tpu-info --process` displays the TPU process table.
137- """
138- output = _get_tpu_process_output (info , pod_name )
139- required_patterns = [
87+ def validate_process (info , pod_name : str ) -> str :
88+ output = run_command (info , pod_name , "tpu-info --process" )
89+ patterns = [
14090 "TPU Process Info" ,
14191 "Chip" ,
14292 "PID" ,
14393 "Process Name" ,
14494 "/dev/vfio/" ,
14595 "python" ,
14696 ]
147-
148- for pattern in required_patterns :
149- if pattern not in output :
150- raise AssertionError (
151- f"Validation failed: Missing expected string '{ pattern } ' "
152- f"in tpu-info --process output.\n Output:\n { output } "
153- )
154-
97+ check_output_contains (output , patterns , "tpu-info --process" )
15598 return output
15699
157100
158101# Keyword arguments are generated dynamically at runtime (pylint does not
159102# know this signature).
160103with models .DAG ( # pylint: disable=unexpected-keyword-arg
161- dag_id = "tpu_info_help_validation_dags " ,
104+ dag_id = "tpu_info_cli_validation_dags " ,
162105 start_date = datetime .datetime (2025 , 8 , 10 ),
163106 schedule = "0 18 * * *" if composer_env .is_prod_env () else None ,
164107 catchup = False ,
@@ -193,7 +136,7 @@ def validate_tpu_info_process(info: node_pool.Info, pod_name: str) -> str:
193136 config = machine .value
194137
195138 jobset_config = JobSet (
196- jobset_name = "tpu-info-help -validation-jobset" ,
139+ jobset_name = "tpu-info-cli -validation-jobset" ,
197140 namespace = "default" ,
198141 max_restarts = 5 ,
199142 replicated_job_name = "tpu-job-slice" ,
@@ -218,7 +161,7 @@ def validate_tpu_info_process(info: node_pool.Info, pod_name: str) -> str:
218161 task_id = "build_node_pool_info_from_gcs_yaml"
219162 )(
220163 gcs_path = GCS_CONFIG_PATH ,
221- dag_name = "tpu_info_help_validation_dags " ,
164+ dag_name = "tpu_info_cli_validation_dags " ,
222165 is_prod = composer_env .is_prod_env (),
223166 machine_type = config .machine_version .value ,
224167 tpu_topology = config .tpu_topology ,
@@ -250,17 +193,17 @@ def validate_tpu_info_process(info: node_pool.Info, pod_name: str) -> str:
250193 with TaskGroup ( # pylint: disable=unexpected-keyword-arg
251194 group_id = "verification_group"
252195 ) as verification_group :
253- validate_help = validate_tpu_info_help .partial (
254- info = cluster_info
255- ). expand ( pod_name = pod_names )
196+ help_validation = validate_help .partial ( info = cluster_info ). expand (
197+ pod_name = pod_names
198+ )
256199
257- validate_version = validate_tpu_info_version .partial (
258- info = cluster_info
259- ). expand ( pod_name = pod_names )
200+ version_validation = validate_version .partial ( info = cluster_info ). expand (
201+ pod_name = pod_names
202+ )
260203
261- validate_process = validate_tpu_info_process .partial (
262- info = cluster_info
263- ). expand ( pod_name = pod_names )
204+ process_validation = validate_process .partial ( info = cluster_info ). expand (
205+ pod_name = pod_names
206+ )
264207
265208 cleanup_workload = jobset .end_workload .override (
266209 task_id = "cleanup_workload" , trigger_rule = TriggerRule .ALL_DONE
0 commit comments