Coverage for src / lstautorta / Auto_RTA.py: 23%

178 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-08-10 11:56 +0000

1# Copyright 2026 CNRS 

2# This software is distributed under the terms of the CeCILL-C free software license. 

3 

4"""Automatically start/stop RTA reconstruction pipeline for new runs during a observation night. 

5 

6New runs are found querying the TCU pymongo database regularly. 

7This script: 

8- copies the conda environment and reconstruction model to the RAM of the slurm nodes 

9- queries the TCU pymongo database for new runs regularly and for each new run: 

10 - stops the r0->dl1 daemons for the previous runs 

11 - starts the r0->dl1 daemons for the new run, using a static configuration (CDB configuration) 

12 from disk, and writing the dynamic configuration to disk as well. 

13 - starts the "engineering gui" scripts allowing to monitor data processing for the run. 

14- stops at a fixed hour according to its configuration file 

15- cleans the RAM of slurm worker nodes 

16""" 

17 

18import argparse 

19import datetime 

20import json 

21import logging 

22import shlex 

23import signal 

24import subprocess as sp 

25import time 

26from pathlib import Path 

27from subprocess import CalledProcessError, CompletedProcess 

28from threading import Thread 

29from typing import Annotated, NamedTuple 

30 

31from annotated_types import Gt, Le 

32from pymongo.errors import PyMongoError 

33 

34from lstautorta.config.configuration import ( 

35 AutoRTAConfiguration, 

36 DataStreamConnectionConfiguration, 

37 ObservationParameters, 

38) 

39from lstautorta.observation_data import ObsInfo, get_current_run_info 

40from lstautorta.paths import RecoPathStructure 

41from lstautorta.shared_observation import DEFAULT_SHARED_OBS_PATH, write_shared_obs 

42from lstautorta.utils.logging import LOGGING_LEVELS_DICT, init_logging 

43from lstautorta.utils.slurm import ( 

44 job_statistics_from_squeue_output, 

45 parse_slurm_job_ID, 

46 subprocess_run_and_raise_exception_on_error, 

47) 

48 

49 

50class ConnectionJobInfo(NamedTuple): 

51 """Store a slurm computing daemon's information.""" 

52 

53 tel_id: Annotated[int, Gt(0)] 

54 hostname: str 

55 port: Annotated[int, Gt(0), Le(65535)] 

56 slurm_reservation: str 

57 slurm_node: str 

58 

59 

60def assign_worker_to_data_connection( 

61 slurm_nodes: dict[str, list[str]], 

62 tel_ids_to_data_servers: dict[Annotated[int, Gt(0)], list[DataStreamConnectionConfiguration]], 

63) -> list[ConnectionJobInfo]: 

64 """Assign a worker node from `slurm_nodes` to each data server connection in `tel_ids_to_data_servers`. 

65 

66 Parameters 

67 ---------- 

68 slurm_nodes : Dict[str, List[str]] 

69 Mapping from slurm reservation to slurm nodes, see AutoRTAConfiguration.slurm_nodes field. 

70 tel_ids_to_data_servers : Dict[Annotated[int, Gt(0)], List[DataStreamConnectionConfiguration]] 

71 Mapping from telescope ID to data servers connections, see AutoRTAConfiguration.tel_ids_to_data_servers 

72 

73 Returns 

74 ------- 

75 Dict[Tuple[int, str, str], Tuple[str, str]] 

76 Map from (tel_id, hostname, port) to (slurm_reservation, slurm_nodename) 

77 """ 

78 # "Flatten" the slurm nodes information into a list of tuple (reservation, node) 

79 node_list = [(reservation, node) for reservation, nodes in slurm_nodes.items() for node in nodes] 

80 tel_to_node_map = [] 

81 node_idx = 0 

82 # Now make the tuples from telescope connections and available nodes. 

83 for tel_id, tel_data_server_connections in tel_ids_to_data_servers.items(): 

84 for connection_idx, connection in enumerate(tel_data_server_connections): 

85 tel_to_node_map.append( 

86 ConnectionJobInfo( 

87 tel_id=tel_id, 

88 hostname=connection.hostname, 

89 port=connection.port, 

90 slurm_reservation=node_list[node_idx + connection_idx][0], 

91 slurm_node=node_list[node_idx + connection_idx][1], 

92 ) 

93 ) 

94 

95 node_idx += len(tel_data_server_connections) 

96 return tel_to_node_map 

97 

98 

99def srun_cmd_worker_nodes( 

100 connection_jobs_info: list[ConnectionJobInfo], 

101 cmd: str, 

102 additional_slurm_params: list[str] | None = None, 

103 error_level=LOGGING_LEVELS_DICT["CRITICAL"], 

104) -> list[CompletedProcess]: 

105 """Submit a slurm command with srun on all worker nodes in `connection_jobs_info`. 

106 

107 Parameters 

108 ---------- 

109 connection_jobs_info : List[ConnectionJobInfo] 

110 List of connection job information: `cmd` will be executed for each node entry in this list. 

111 cmd : str 

112 Command to run with srun. 

113 additional_slurm_params : List[str] or None 

114 List of additional slurm jobs parameters, separated between args and values, for instance ["--mem", "20G"] 

115 Optionnal, default is None. 

116 

117 Returns 

118 ------- 

119 List[subprocess.CompletedProcess] 

120 List of completed processes. 

121 """ 

122 completed_processes = [] 

123 for job_info in connection_jobs_info: 

124 srun_cmd = " ".join( 

125 [ 

126 "srun", 

127 *(additional_slurm_params if additional_slurm_params is not None else ""), 

128 f"--reservation={job_info.slurm_reservation}", 

129 f"--nodelist={job_info.slurm_node}", 

130 cmd, 

131 ] 

132 ) 

133 logging.info(f"Running {srun_cmd}") 

134 completed_processes.append( 

135 subprocess_run_and_raise_exception_on_error( 

136 shlex.split(srun_cmd), 

137 success_log_string=f"Success on node {job_info.slurm_node}", 

138 failure_log_string=f"Failure on node {job_info.slurm_node} with {srun_cmd}", 

139 error_level=error_level, 

140 log_level=LOGGING_LEVELS_DICT["DEBUG"], 

141 ) 

142 ) 

143 return completed_processes 

144 

145 

146def scancel_jobs( 

147 job_ids: list[int], signal: signal.Signals, delay_s: float | None = None, ignore_error: bool = False 

148) -> None: 

149 """Run `scancel on `job_ids`, sending `signal` after `delay_s` seconds. 

150 

151 Use the --quiet argument of scancel to not raise error if the jobs are already stopped. 

152 

153 Parameters 

154 ---------- 

155 job_ids : List[str] 

156 List of job ids to scancel 

157 signal : signal.Signals 

158 Signal to send with scancel 

159 delay_s : float | None 

160 Amount of time in second to wait before performing the scancel 

161 ignore_error : bool, optional 

162 If True, the scancel is run directly with subprocess.srun, and any error happening in the subprocess 

163 is simply ignored. This is usefull when running scancel -s KILL after a scancel -s INT: if the SIGINT 

164 stopped the job already, the SIGKILL would have exit code 1 even with --quiet, but we want to ignore 

165 the error in this case. 

166 """ 

167 # note: --full or -f is required for r0_dl1 daemons to receive signal 

168 scancel_cmd = " ".join(["scancel", "--full", "-s", str(signal), *[str(job_id) for job_id in job_ids]]) 

169 if delay_s is not None: 

170 time.sleep(delay_s) 

171 if ignore_error: 

172 try: 

173 sp.run(shlex.split(scancel_cmd), capture_output=True, text=True, check=True) 

174 logging.debug(f"Stopping jobs with {scancel_cmd}") 

175 except CalledProcessError as error: 

176 logging.info( 

177 f"Ignoring error of {scancel_cmd} caused by jobs already been stopped. Error info:\nstdout: {error.stdout}\nstderr: {error.stderr}" 

178 ) 

179 else: 

180 subprocess_run_and_raise_exception_on_error( 

181 shlex.split(scancel_cmd), 

182 success_log_string=f"Stopping job with {scancel_cmd}", 

183 failure_log_string=f"FAILURE to stop job with {scancel_cmd}", 

184 error_level=LOGGING_LEVELS_DICT["ERROR"], 

185 log_level=LOGGING_LEVELS_DICT["DEBUG"], 

186 ) 

187 

188 

189def stop_rta(slurm_reservations: list[str], slurm_account: str, r0dl1_job_name: str) -> Thread | None: 

190 """Stop the r0dl1 daemons. 

191 

192 The r0dl1 jobs are immediately send a SIGINT signal, which should tell them to 

193 gracefully shut down. A SIGKILL is also scheduled to run 10 seconds later to 

194 ensure the jobs are indeed stopped. 

195 

196 Parameters 

197 ---------- 

198 slurm_reservations : List[str] 

199 List of slurm reservation to search for r0dl1 daemons. 

200 slurm_account : str 

201 Slurm account to use when searching for the r0dl1 daemons. 

202 r0dl1_job_name : str 

203 Name of the r0dl1 jobs in the CDB configuration 

204 

205 Returns 

206 ------- 

207 stop_thread : Thread | None 

208 Started thread that will SIGKILL the r0dl1 jobs after 10 secs. 

209 None if there were no jobs to stop. 

210 """ 

211 job_ids = parse_slurm_job_ID(slurm_reservations, slurm_account, r0dl1_job_name) 

212 logging.info(f"Found r0dl1 job ids {job_ids} to stop.") 

213 if job_ids: 

214 # immediately send the SIGINT 

215 scancel_jobs(job_ids, signal.SIGINT, None) 

216 # start a detached thread to send the SIGKILL in 10 seconds. 

217 # this allows autorta to continue and start new run immediately, while jobs are shuting down. 

218 stop_thread = Thread(target=scancel_jobs, args=(job_ids, signal.SIGKILL, 10.0, True)) 

219 stop_thread.start() 

220 return stop_thread 

221 return None 

222 

223 

224def nuke_rta(slurm_account: str): 

225 """Hard stop of All RTA jobs: scancels all jobs of `slurm_account`. 

226 

227 Parameters 

228 ---------- 

229 slurm_account : str 

230 slurm account 

231 """ 

232 nuke_rta_cmd = " ".join(["scancel", "-u", slurm_account]) 

233 subprocess_run_and_raise_exception_on_error( 

234 shlex.split(nuke_rta_cmd), 

235 f"Stopped RTA with {nuke_rta_cmd}", 

236 f"Could not stop RTA with {nuke_rta_cmd}", 

237 error_level=LOGGING_LEVELS_DICT["CRITICAL"], 

238 log_level=LOGGING_LEVELS_DICT["WARNING"], 

239 ) 

240 

241 

242def write_reco_manager_observation_config( 

243 obs_info: ObsInfo, 

244 obs_dir: Path, 

245 night_path_structure: RecoPathStructure, 

246 auto_rta_config: AutoRTAConfiguration, 

247 output_path: Path, 

248) -> None: 

249 """Write the observation configuration for the reco-manager. 

250 

251 Parameters 

252 ---------- 

253 obs_info : ObsInfo 

254 Observation parameters from observation DB 

255 obs_dir : Path 

256 Path to the observation data directory 

257 night_path_structure : RecoPathStructure 

258 Path structure of the night 

259 auto_rta_config : AutoRTAConfiguration 

260 Configuration of Auto RTA 

261 output_path: Path 

262 Path where to write the hiperta_stream_start configuration 

263 """ 

264 hiperta_obs_config = ObservationParameters.model_validate( 

265 { 

266 "sb_id": 1, # no scheduling block in LST 

267 "obs_id": obs_info.obs_id, 

268 "tel_id": 1, # only 1 tel 

269 "RA_pointing": obs_info.RA, 

270 "DEC_pointing": obs_info.DEC, 

271 "dl1_dir": str(night_path_structure.dl1_dir(obs_dir)), 

272 "dl2_dir": str(night_path_structure.dl2_dir(obs_dir)), 

273 "dl3_dir": str(night_path_structure.dl3_dir(obs_dir)), 

274 "log_dir": str(night_path_structure.log_dir(obs_dir)), 

275 "reco_manager_log_file": str(night_path_structure.log_dir(obs_dir) / "hiperta_stream_start.log"), 

276 "data_stream_connections": auto_rta_config.tel_ids_to_data_servers[1], # only do tel ID 1 for LST 1 

277 "slurm_nodelists": auto_rta_config.slurm_nodes, 

278 } 

279 ) 

280 with output_path.open("w") as obs_config_f: 

281 obs_config_f.write(hiperta_obs_config.model_dump_json(indent=4)) 

282 

283 

284def main() -> None: 

285 """Entrypoint of Auto_RTA. 

286 

287 - parse the available slurm nodes 

288 - check that the slurm nodes are in "connected" network mode 

289 - copy conda environment to slurm nodes (usually in /dev/shm/ (RAM)) 

290 - query database for current RUN and for each run: 

291 - stops previous RTA reconstruction slurm jobs 

292 - starts new R0-DL1 jobs for the new RUN with static configuration (CDB config) and 

293 a newly written dynamic config 

294 - starts engineering GUI plotting scripts 

295 - stops at a fixed hour set in configuration, after cleaning slurm nodes memory. 

296 """ 

297 start_time = datetime.datetime.now(datetime.UTC) 

298 

299 # initially write log where the script is called (home if cron job) 

300 # so that errors can be logged if we can't parse the config 

301 init_logging(log_level="DEBUG", log_filename="lstautorta.log") 

302 

303 # Load configuration 

304 parser = argparse.ArgumentParser( 

305 description="Automatic Starting of the RTA Reconstruction for an observation night", 

306 formatter_class=argparse.ArgumentDefaultsHelpFormatter, 

307 ) 

308 parser.add_argument( 

309 "-c", "--config", dest="config", type=str, required=True, help="LST auto RTA configuration file." 

310 ) 

311 args = parser.parse_args() 

312 with open(args.config) as config_file: 

313 config = AutoRTAConfiguration.model_validate_json(config_file.read()) 

314 

315 # Create night data directory 

316 path_structure = RecoPathStructure(config.data_dir, start_time) 

317 path_structure.create_night_data_dir() 

318 # now update logging to write log in night's directory 

319 init_logging( 

320 log_level=config.log_level, 

321 log_filename=path_structure.night_data_dir / "lstautorta.log", 

322 ) 

323 

324 # get the stop time during today 

325 stop_time = start_time.replace( 

326 hour=config.stop_time_UTC_hours, minute=config.stop_time_UTC_minutes, second=0, microsecond=0 

327 ) 

328 # If we are passed it, it is actually tomorrow 

329 if stop_time < start_time: 

330 stop_time += datetime.timedelta(days=1) 

331 

332 logging.info("Start RTA at " + str(start_time)) 

333 logging.info(f"Found worker nodes : {config.slurm_nodes}") 

334 

335 connection_jobs_info = assign_worker_to_data_connection(config.slurm_nodes, config.tel_ids_to_data_servers) 

336 for job_info in connection_jobs_info: 

337 logging.info( 

338 f'Will run tel {job_info.tel_id} connection {{"hostname": {job_info.hostname}, "port": {job_info.port}}} r0->dl1 job on {job_info.slurm_node} (reservation {job_info.slurm_reservation})' 

339 ) 

340 

341 logging.info("Reading R0-DL1 jobnames from CDB configuration") 

342 # load CDB configuration, checking it exists, and parsing r0_dl1_job_name 

343 with open(config.hiperta_CDB_config_file) as CDB_f: 

344 CDB_config = json.load(CDB_f) 

345 r0_dl1_job_name = CDB_config["r0_dl1_params"]["r0_dl1_job_name"] 

346 logging.info(f"Found job name: {r0_dl1_job_name}") 

347 

348 if config.copy_env: 

349 # For all srun commands here: error level is critical because RTA can not run if environment can not be copied 

350 logging.info("Loading the environment on worker nodes for the night") 

351 logging.info(f"Cleaning previous content in {config.env_archive_extraction_path}") 

352 srun_cmd_worker_nodes( 

353 connection_jobs_info, 

354 f"rm -rf {config.env_archive_extraction_path}", 

355 error_level=LOGGING_LEVELS_DICT["CRITICAL"], 

356 ) 

357 logging.info(f"Cleaning previous content in {config.models_archive_copy_path}") 

358 srun_cmd_worker_nodes( 

359 connection_jobs_info, 

360 f"rm -rf {config.models_archive_copy_path}", 

361 error_level=LOGGING_LEVELS_DICT["CRITICAL"], 

362 ) 

363 logging.info(f"Extracting environment from {config.env_archive} to {config.env_archive_extraction_path}") 

364 srun_cmd_worker_nodes( 

365 connection_jobs_info, 

366 f"bash -c 'mkdir {config.env_archive_extraction_path} && tar -xzf {config.env_archive} -C {config.env_archive_extraction_path}'", 

367 ["--mem", "20G"], 

368 error_level=LOGGING_LEVELS_DICT["CRITICAL"], 

369 ) 

370 logging.info( 

371 f"Copying reconstruction models from {config.models_archive_path} to {config.models_archive_copy_path}" 

372 ) 

373 srun_cmd_worker_nodes( 

374 connection_jobs_info, 

375 f"cp -rf {config.models_archive_path} {config.models_archive_copy_path}", 

376 ["--mem", "20G"], 

377 error_level=LOGGING_LEVELS_DICT["CRITICAL"], 

378 ) 

379 

380 if config.check_node_connection: 

381 comp_proc_ib0_cat = srun_cmd_worker_nodes(connection_jobs_info, "cat /sys/class/net/ib0/mode") 

382 # If a node is not connected, raise error 

383 if not all("connected" in process.stdout.strip() for process in comp_proc_ib0_cat): 

384 raise RuntimeError( 

385 "Not all RTA slurm nodes are connected to ib0! Found {}".format( 

386 ", ".join( 

387 [ 

388 f"{job_info.slurm_node}: {process.stdout.strip()}" 

389 for job_info, process in zip(connection_jobs_info, comp_proc_ib0_cat) 

390 ] 

391 ) 

392 ) 

393 ) 

394 

395 logging.info("RTA ready for the night !") 

396 

397 current_obs_info = ObsInfo(None, None, None, None, None, None) 

398 while not (datetime.datetime.now(datetime.UTC) > stop_time): 

399 # query TCU DB for observation 

400 try: 

401 obs_info = get_current_run_info(config.db_hostname, 10) 

402 except PyMongoError: 

403 logging.error("Error retrieving observation information from DB, ignoring...", exc_info=True) 

404 obs_info = ObsInfo(None, None, None, None, None, None) 

405 

406 # Check observation data 

407 obs_info_is_none = obs_info.RA is None or obs_info.DEC is None 

408 obs_recent_enough = True # default value if we couldn't get an obs_info 

409 if obs_info_is_none: 

410 logging.warning("Queried observation information had no RA DEC.") 

411 elif config.ignore_old_observation: 

412 obs_info_tstart_datetime = datetime.datetime.fromtimestamp(obs_info.time_start_camera, datetime.UTC) 

413 obs_info_time_delta = datetime.datetime.now(datetime.UTC) - obs_info_tstart_datetime 

414 obs_recent_enough = obs_info_time_delta < datetime.timedelta(hours=4) 

415 if not obs_recent_enough: 

416 logging.info( 

417 f"Queried observation {obs_info.obs_id} has start time {obs_info_tstart_datetime}, timedelta wrt now: {obs_info_time_delta}. Too old to start RTA" 

418 ) 

419 

420 if (not obs_info_is_none) and obs_recent_enough: 

421 try: 

422 write_shared_obs(DEFAULT_SHARED_OBS_PATH, obs_info) 

423 except Exception: 

424 logging.error("Failed to write shared observation file at %s", DEFAULT_SHARED_OBS_PATH, exc_info=True) 

425 

426 # Start RTA 

427 if current_obs_info.obs_id != obs_info.obs_id and (not obs_info_is_none) and obs_recent_enough: 

428 # We got a new observation! 

429 logging.info( 

430 f"Got new observation! ID: {obs_info.obs_id} - RA: {obs_info.RA} - DEC: {obs_info.DEC} - SOURCE.RA: {obs_info.source_RA} - SOURCE.DEC: {obs_info.source_DEC}" 

431 ) 

432 

433 try: 

434 logging.info("Stopping RTA") 

435 stop_rta(config.slurm_reservations, config.slurm_account, r0_dl1_job_name) 

436 except Exception: 

437 logging.exception("Could not stop RTA. NEXT RUN DATA MAY BE ACQUIRED BY PREVIOUS RUN DAEMONS !") 

438 # Note: we could nuke_rta, but it would also kill DQ, SCI jobs, etc... 

439 

440 obs_dir = path_structure.create_observation_data_dirs(str(obs_info.obs_id), True) 

441 logging.info(f"Created directories for obs {obs_info.obs_id} at {obs_dir}") 

442 

443 reco_manager_obs_config_path = ( 

444 path_structure.log_dir(obs_dir) / "hiperta_stream_start_observation_config.json" 

445 ) 

446 logging.info(f"Writing reco-manager observation configuration at {reco_manager_obs_config_path}") 

447 write_reco_manager_observation_config( 

448 obs_info, 

449 obs_dir, 

450 path_structure, 

451 config, 

452 reco_manager_obs_config_path, 

453 ) 

454 

455 # Note: hiperta_stream_start has to be started on a worker node as well, because it 

456 # will read the training r0dl1 configuration, which is in the model's archive copied to /dev/shm 

457 # (it reads the r0dl1 configuration from the path set in the CDB config, that must point to /dev/shm) 

458 # Note2: to run several commands with srun, need to wrap with bash -c '...' 

459 hiperta_stream_start_cmd = " ".join( 

460 [ 

461 f"bash -c 'export PATH={config.env_archive_extraction_path}/bin/:$PATH ; {config.env_archive_extraction_path}/bin/hiperta_stream_start", 

462 "-c", 

463 config.hiperta_CDB_config_file, 

464 "-d", 

465 str(reco_manager_obs_config_path), 

466 "'", 

467 ] 

468 ) 

469 try: 

470 # use 1st node to start hiperta_stream 

471 # Note: hiperta_stream must run on a worker node as well, because it needs access to 

472 # the r0dl1 training configuration, which is found form the path in the CDB configuration, 

473 # which points to /dev/shm/model_archive/... 

474 hiperta_stream_job_info = connection_jobs_info[0] 

475 hiperta_stream_srun_cmd = " ".join( 

476 [ 

477 "srun", 

478 f"--reservation={hiperta_stream_job_info.slurm_reservation}", 

479 f"--nodelist={hiperta_stream_job_info.slurm_node}", 

480 hiperta_stream_start_cmd, 

481 ] 

482 ) 

483 logging.info(f"Starting hiperta_stream_start with {hiperta_stream_srun_cmd}") 

484 subprocess_run_and_raise_exception_on_error( 

485 shlex.split(hiperta_stream_srun_cmd), 

486 success_log_string=f"hiperta_stream started with {hiperta_stream_job_info.slurm_node}", 

487 failure_log_string=f"Failed to start hiperta_stream on {hiperta_stream_job_info.slurm_node} with {hiperta_stream_start_cmd}", 

488 error_level=LOGGING_LEVELS_DICT[ 

489 "ERROR" 

490 ], # we might want to continue even if we can't start reco-manager 

491 log_level=LOGGING_LEVELS_DICT["DEBUG"], 

492 ) 

493 # Only if we could start RTA we update the current obs_info 

494 # Otherwise, we will loop, see a new observation again, stop RTA and re-start, etc ... 

495 current_obs_info = obs_info 

496 except sp.SubprocessError: 

497 logging.exception(f"Failed to start reco-manager with {hiperta_stream_start_cmd}") 

498 # continue anyway, obs_info is not updated so we will try again to start 

499 

500 # If not starting RTA: Query squeue for some statistics on the running jobs 

501 else: 

502 try: 

503 running_jobs_info = subprocess_run_and_raise_exception_on_error( 

504 shlex.split(f'squeue -u {config.slurm_account} --format="%T,%R"'), 

505 failure_log_string="Could not parse slurm info while waiting for next observation", 

506 error_level=LOGGING_LEVELS_DICT["ERROR"], 

507 log_level=LOGGING_LEVELS_DICT["DEBUG"], 

508 ).stdout 

509 n_jobs, n_running, n_pending, n_request_node_not_available = job_statistics_from_squeue_output( 

510 running_jobs_info 

511 ) 

512 logging.info( 

513 f"{config.slurm_account} job statistics: nb_jobs: {n_jobs} - nb_running: {n_running} - nb_pending: {n_pending} - nb_node_not_available: {n_request_node_not_available}" 

514 ) 

515 except sp.SubprocessError: 

516 pass # continue loop anyway 

517 

518 # sleep until we get an observation 

519 logging.info(f"RTA waiting for next observation, current observation is {obs_info.obs_id}") 

520 time.sleep(1.0) 

521 

522 logging.info("End of the night: Stop the RTA") 

523 try: 

524 logging.info("Stopping RTA") 

525 sigkill_thread = stop_rta(config.slurm_reservations, config.slurm_account, r0_dl1_job_name) 

526 if sigkill_thread is not None: # it is None if there are no jobs to kill 

527 sigkill_thread.join(timeout=180.0) # 3 minutes timeout but job should end right after delay of 10 sec 

528 else: 

529 logging.warning("Found no r0_dl1 jobs to stop !") 

530 except Exception: 

531 logging.exception(f"Could not stop RTA normally, nuking all {config.slurm_account} jobs") 

532 nuke_rta(config.slurm_account) 

533 

534 # Clean up node environment 

535 logging.info("Cleaning nodes copied files") 

536 logging.info(f"Cleaning content in {config.env_archive_extraction_path}") 

537 srun_cmd_worker_nodes( 

538 connection_jobs_info, 

539 f"rm -rf {config.env_archive_extraction_path}", 

540 error_level=LOGGING_LEVELS_DICT["CRITICAL"], 

541 ) 

542 logging.info(f"Cleaning content in {config.models_archive_copy_path}") 

543 srun_cmd_worker_nodes( 

544 connection_jobs_info, 

545 f"rm -rf {config.models_archive_copy_path}", 

546 error_level=LOGGING_LEVELS_DICT["CRITICAL"], 

547 ) 

548 logging.info("Done cleaning nodes.") 

549 

550 logging.info("RTA Done for the night. Good day !") 

551 

552 

553if __name__ == "__main__": 553 ↛ 554line 553 didn't jump to line 554 because the condition on line 553 was never true

554 main()