Fix SSHRemoteJobOperator custom remote_base_dir cleanup validation#69834
Fix SSHRemoteJobOperator custom remote_base_dir cleanup validation#69834axelray-dev wants to merge 3 commits into
Conversation
|
Congratulations on your first Pull Request and welcome to the Apache Airflow community! If you have any issues or are unsure about any anything please check our Contributors' Guide
|
|
Suggestion: Remove the |
| leave the directory rather than failing the (already finished) task. | ||
| """ | ||
| self.log.info("Cleaning up remote job directory: %s", job_dir) | ||
| expected_base = self._paths.base_dir if self._paths else self.remote_base_dir |
There was a problem hiding this comment.
The new tests pin the builders, but this line is the wiring #69813 actually hit: if the operator change were reverted and only the utils change kept, every test in the PR would still pass. test_execute_complete_with_cleanup in operators/test_ssh_remote_job.py is easy to clone into a variant with remote_base_dir="/tmp-data/airflow-ssh-jobs" and a matching event job_dir, which would cover the reported scenario end to end.
| expected_base = self._paths.base_dir if self._paths else self.remote_base_dir | ||
| if remote_os == "posix": | ||
| cleanup_cmd = build_posix_cleanup_command(job_dir) | ||
| cleanup_cmd = build_posix_cleanup_command(job_dir, expected_base=expected_base) |
There was a problem hiding this comment.
If validation fails here, the ValueError escapes _cleanup_remote_job before the retry loop and fails a task whose remote job already finished, which is the failure mode from #69813 and contradicts the log-and-leave contract in the docstring above. That's still reachable after this fix: remote_base_dir is templated, and templates are re-rendered when the operator resumes after deferral, so a value like {{ var.value.ssh_jobs_base }} re-renders against resume-time state while event["job_dir"] keeps the submission-time base the trigger was constructed with. If the Variable changes while the job runs, cleanup rejects a valid path and the task fails.
The cheap guard is to catch the ValueError and route it through the same warn-and-leave path used when retries are exhausted. A fuller fix would thread the submission-time base (self._paths.base_dir) through the trigger event alongside job_dir and validate against that, which also makes the self._paths fallback on the line above unnecessary, since cleanup always runs on a fresh instance where _paths is None. Happy for either to land as a follow-up if you'd rather keep this PR minimal.
| :raises ValueError: If job_dir is not under the expected base directory | ||
| """ | ||
| _validate_job_dir(job_dir, "posix") | ||
| _validate_job_dir(job_dir, "posix", expected_base) |
There was a problem hiding this comment.
On the suggestion to drop this validation from the cleanup builders instead: I'd keep it. By the time cleanup runs, job_dir is whatever came back in the trigger event (execute_complete passes event["job_dir"] straight through), and it ends up in rm -rf. This prefix check is the only thing pinning an event-supplied path under the configured base.
There was a problem hiding this comment.
Hmm, interesting. I thought I could trust the return values of the trigger as it comes from the validated operator argument. I probably haven't conquered the full complexity of the deferral mechanism.
The eternal question of "how paranoid should I be" was (rightfully) preventing me from diving into a fix/PR straight away.
| """ | ||
| if remote_os == "posix": | ||
| expected_prefix = POSIX_DEFAULT_BASE_DIR + "/" | ||
| expected_base = expected_base or POSIX_DEFAULT_BASE_DIR |
There was a problem hiding this comment.
expected_base or POSIX_DEFAULT_BASE_DIR treats a falsy value as unset, but RemoteJobPaths.__post_init__ only substitutes the default when base_dir is None. Since remote_base_dir is templated and only validated pre-render, a value that renders empty builds job_dir directly under / at submission, then cleanup validates it against the hardcoded default here and raises. Checking expected_base is None keeps the two consistent.
|
Thanks for the detailed notes. I agree we should keep the cleanup validation. Once job_dir comes back from the trigger event, that prefix check is basically the only guard before rm -rf. I'll update this PR to:
|
Summary
Fixes inconsistent validation of custom
remote_base_dironSSHRemoteJobOperatorcleanup (#69813).The operator accepts a custom base directory when constructing job paths, but cleanup validation only allowed the hardcoded defaults (
/tmp/airflow-ssh-jobsand the Windows temp default). Jobs with a custom base then failed at the end withValueError: Invalid job directory ... Expected path under '/tmp/airflow-ssh-jobs'.Changes
_validate_job_dirand the cleanup command builders, defaulting to the existing platform defaults.RemoteJobPaths.base_dir/remote_base_dir) into cleanup so custom bases validate the same way as defaults.Verification
python -m pytest providers/ssh/tests/unit/ssh/utils/test_remote_job.py -qBackwards compatibility
remote_base_diris unset.Fixes #69813