Take machine offline and notify when dpkg is corrupted during prereq install - #5267
Take machine offline and notify when dpkg is corrupted during prereq install#5267DrewScoggins wants to merge 3 commits into
Conversation
…install
Some prerequisite-install failures are not transient but indicate a broken
host. The canonical case is the apt/dpkg database getting stuck in an
interrupted state ("dpkg was interrupted, you must manually run
'sudo dpkg --configure -a'"), which currently just fails the run repeatedly.
When this specific state is detected during prerequisite installation:
- The machine is taken out of Helix rotation by creating the `offline`
sentinel file in $HELIX_CONFIG_ROOT.
- A semaphore blob named after the machine is written to the new
`offline-machines` container in pvscmdupload so the perf team is notified.
The semaphore blob is cleared only when the machine next uploads results
successfully, guaranteeing a machine must actually be working to have its
semaphore removed.
The notification blob write is best effort: it uses the already-available
azure libraries, falls back to bootstrapping a throwaway venv with the
dependencies, and if both fail the machine is still taken offline and a
message is printed to the console.
- scripts/machine_health.py: detection, offline sentinel, semaphore
write (with venv fallback) and clear.
- scripts/performance/constants.py: add OFFLINE_MACHINES_CONTAINER.
- scripts/run_performance_job.py: run the check on prereq failure.
- scripts/benchmarks_ci.py: clear the semaphore after a successful upload.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 418ccfe4-f031-42ad-92d8-38130011c784
There was a problem hiding this comment.
Pull request overview
This PR adds a machine-health hook to detect a specific non-transient prereq install failure mode (corrupted/interrupted apt/dpkg state), then automatically removes the affected Helix machine from rotation and emits an external “offline machine” signal via Azure Storage until the machine proves healthy again by successfully uploading results.
Changes:
- Add
scripts/machine_health.pyto detect interrupted dpkg state, create Helixofflinesentinel, and write/clear an “offline semaphore” blob. - Add
OFFLINE_MACHINES_CONTAINERconstant for the new Azure Storage container. - Invoke the health check on prereq-install failure and clear the semaphore after successful results upload.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| scripts/run_performance_job.py | Runs machine health check after prereq install failure (without affecting exit code). |
| scripts/performance/constants.py | Adds OFFLINE_MACHINES_CONTAINER constant for offline-machine semaphores. |
| scripts/machine_health.py | Implements dpkg corruption detection, Helix offline sentinel creation, and Azure blob semaphore write/clear. |
| scripts/benchmarks_ci.py | Clears the offline semaphore after a successful results upload. |
Comments suppressed due to low confidence (1)
scripts/machine_health.py:132
- The fallback sentinel creation path uses
sudo teewithout-n, which can block on an interactive sudo password prompt (up to the 30s timeout) and delay/offline-marking in the exact failure scenario this script is meant to handle. Adding-nmakes the operation non-interactive and fail fast.
try:
subprocess.run(
["sudo", "tee", offline_path],
input=content,
text=True,
stdout=subprocess.DEVNULL,
check=True,
timeout=30,
)
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -331,7 +331,12 @@ def get_pre_commands( | |||
| helix_pre_commands += [ | |||
| 'echo "** Installing prerequistes **"', | |||
| f"{combined_prequisites} || export PERF_PREREQS_INSTALL_FAILED=1", | |||
| 'test "x$PERF_PREREQS_INSTALL_FAILED" = "x1" && echo "** Error: Failed to install prerequites **"' | |||
| 'test "x$PERF_PREREQS_INSTALL_FAILED" = "x1" && echo "** Error: Failed to install prerequites **"', | |||
|
We should fix the typos in the names, and to double check, deleting the helix offline file will be a manual thing we do? |
Yes, deleting the file on the machine itself will be something we do manually. Deleting the file that is in the storage account is automatic. |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (2)
scripts/run_performance_job.py:334
- This prereq-install block has multiple typos ("prerequistes" / "prerequites") and the variable
combined_prequisitesis misspelled. These reduce log searchability and make this branch inconsistent with the Windows block’scombined_prerequisites.
combined_prequisites = " && ".join(install_prerequisites)
helix_pre_commands += [
'echo "** Installing prerequistes **"',
f"{combined_prequisites} || export PERF_PREREQS_INSTALL_FAILED=1",
'test "x$PERF_PREREQS_INSTALL_FAILED" = "x1" && echo "** Error: Failed to install prerequites **"',
scripts/machine_health.py:125
- The sudo fallback for writing the Helix
offlinesentinel runssudo teewithout-n. If sudo ever requires a password, this will block until the timeout and still fail to create the sentinel. Usingsudo -nmatches the non-interactive pattern used elsewhere in this script and fails fast.
["sudo", "tee", offline_path],
| if not sys.platform.startswith("linux"): | ||
| return False | ||
| result = subprocess.run( | ||
| ["sudo", "-n", "apt-get", "check"], | ||
| stdout=subprocess.PIPE, | ||
| stderr=subprocess.STDOUT, | ||
| text=True, | ||
| timeout=120, | ||
| ) | ||
| except FileNotFoundError: | ||
| # apt-get not present (e.g. Azure Linux) - not an apt/dpkg machine. | ||
| return False | ||
| except Exception as ex: # pragma: no cover - defensive | ||
| getLogger().warning("Unable to run 'apt-get check' to probe dpkg state: %s", ex) | ||
| return False |
- machine_health.py: use 'sudo -n' for the 'apt-get check' probe and the offline-sentinel 'tee' fallback so they are non-interactive and fail fast instead of blocking on a sudo password prompt until the timeout. - run_performance_job.py: fix the long-standing "prerequistes"/"prerequites" log typos and rename combined_prequisites -> combined_prerequisites so the non-Windows branch matches the Windows branch and logs are searchable. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 418ccfe4-f031-42ad-92d8-38130011c784
|
Addressed the review feedback in the latest push:
Re: the offline sentinel — confirmed the local |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.
Suppressed comments (2)
scripts/machine_health.py:260
- The
reasonstring and the subsequent semaphore-failure message claim the machine "has been taken out of Helix rotation" / "has still been taken offline", butcreate_offline_sentinel(reason)can return False (e.g., HELIX_CONFIG_ROOT unset, permission/sudo failure). This can produce misleading sentinel/blob content and console output when the offline sentinel was not actually created. Capture and check the return value and only claim the machine was taken offline when the sentinel creation succeeds; otherwise log an explicit error.
reason = (
f"apt/dpkg is in a corrupted (interrupted) state on {machine_name}. Prerequisite "
f"installation cannot proceed. The machine has been taken out of Helix rotation. Recover it "
f"by running 'sudo dpkg --configure -a' and removing $HELIX_CONFIG_ROOT/offline."
)
scripts/machine_health.py:76
- This PR introduces a new critical operational safety path (dpkg corruption detection + taking the machine offline + Azure semaphore writes), but there are no unit tests covering the decision logic (e.g., signature detection in
is_dpkg_broken, handling of apt-get missing, and thecheck_and_mark_offlineflow). The repo already has pytest-based tests underscripts/tests/, so adding targeted tests with mocks forsubprocess.run,os.getenv, and the Azure client helpers would help prevent regressions.
def is_dpkg_broken() -> bool:
'''Return True if apt/dpkg is stuck in the interrupted state that requires 'dpkg --configure -a'.
Detection mirrors exactly what triggers the failure: any apt-get invocation on such a machine
prints the "dpkg was interrupted" error. ``apt-get check`` is a read-only diagnostic, so it is a
Generated by Copilot
Summary
Some prerequisite-install failures are not transient but indicate a broken host. The canonical case is the apt/dpkg database getting stuck in an interrupted state:
Today this just fails the run repeatedly on the same bad machine. This PR detects that specific state during prerequisite installation and:
offlinesentinel file in$HELIX_CONFIG_ROOT(Helix stops sending work to a machine with that file).offline-machinescontainer inpvscmdupload.The semaphore blob is cleared only when the machine next uploads results successfully, so a machine has to actually be working to have its semaphore removed.
The notification write is best effort: it first uses the already-available azure libraries, then falls back to bootstrapping a throwaway venv with the dependencies, and if both fail the machine is still taken offline and a message is printed to the console.
Changes
scripts/machine_health.py(new): dpkg-state detection, offline sentinel creation, semaphore write (with venv fallback), and semaphore clear.scripts/performance/constants.py: addOFFLINE_MACHINES_CONTAINER.scripts/run_performance_job.py: run the health check on prerequisite failure.scripts/benchmarks_ci.py: clear the semaphore after a successful results upload.Detection
Detection mirrors exactly what triggers the failure: a read-only
sudo apt-get checkis probed for thedpkg was interrupted/dpkg --configure -asignatures, so only this non-transient condition takes a machine offline (flaky network failures do not).Notes
dotnet-performance-infra(PerfMonitor) consumes theoffline-machinescontainer and alerts the team.offline-machinescontainer; PerfMonitor's identity needs read.