TT Lab
Get started
Learn Learning paths Courses

FDE Capstone: The Warehouse Got the Same Order Three Times

Install reported done, but the agent died on startup

Continue in TT Lab

Goal

You build a preflight script that checks the customer host's installation requirements and outputs pass, warn, and fail as a JSON report and an exit code. The verdicts must be right across the various environments the grader sets up, and the check must fix nothing.

Why it matters

The installation window at a customer site is often a one-shot. The last installation ended with "done", but the port was being used by an internal proxy and the certificate had already expired. A preflight check is a tool that reveals such facts with evidence before the installation. Only if you leave the observed values as well as the verdict can the customer's owner check again, and only if the exit code is consistent can the install automation tell proceeding from stopping. The grader does not trust your wording. It makes an occupied port, a TIME_WAIT port, an expiring or expired certificate, and a nonexistent directory in a temporary directory by itself, and runs your script while changing the port numbers, threshold values, and paths on each run.

Steps

  1. Read the customer memo and write the seven kinds of requirements into /root/preflight/spec.json with the contract's key names. Distinguish the values in the old document from the support policy.
  2. In /root/preflight/preflight.py, implement the python_min and ports checks, the report (summary, exit_code, checks), and exit codes 0, 1, and 2. Do not perform a check for a key that is not in the specification.
  3. Fix the port check of preflight.py so that it does not falsely flag a port with only TIME_WAIT left as in use. A listening port must still be fail.
  4. Add the disk check to preflight.py. Write the free MiB in observed and the measured path in measured_path, and for a path that does not exist yet, do not create it and measure the nearest parent.
  5. Add the certs check to preflight.py. Write notAfter as epoch seconds in observed and the number of remaining days in days_left; expired or unreadable is fail, and less than warn_days is warn.
  6. Add the files, hosts, and writable_dirs checks to preflight.py. observed uses the contract's state names, the write test file is deleted, and a directory that does not exist is not created.
  7. Check that it distinguishes a normal, a warning, and a failing host across the whole specification, and a broken specification. The exit codes are 0, 1, and 2, and if the specification cannot be read, it is 3 with no report.
  8. Check this host with spec.json to leave /root/preflight/report.json, and in /root/preflight/decision.json, write the report hash, the verdict (go, go-with-warnings, no-go), blockers, and warnings.

Notes

Turn the customer memo into a specification

Write the seven kinds of requirements in the customer memo into /root/preflight/spec.json with the contract's key names.

The memo mixes values from the old document with anecdotes that "it did run". The check criteria are this version and the vendor's support policy. Ports are an integer array, and disk and certs are object arrays.

Judge the Python version and the ports first

Make /root/preflight/preflight.py check python_min and ports and produce a report and exit codes 0, 1, and 2.

You can tell whether a port is free by actually binding to 0.0.0.0. Compare versions with sys.version_info and integer tuples. summary is fail if there is even one fail. The grader runs it each time with a different port and version criteria.

Do not falsely flag a finished connection as occupied

Make the port check of /root/preflight/preflight.py tell a port with only TIME_WAIT left as free and a listening port as in_use.

A connection that the server side closed first leaves TIME_WAIT on that port. A bind with no options is blocked by this too. With a single socket option, TIME_WAIT passes and a real listener is still blocked.

Split free space into a warning and a failure

Add the disk check to /root/preflight/preflight.py so that it produces observed (free MiB), measured_path, and pass, warn, or fail.

It is the quotient of the free of shutil.disk_usage divided by 1024*1024. If the path does not exist, go up with os.path.dirname to find a place that exists, but do not create it. The grader changes the threshold values against the current free space to imitate a short disk.

Measure the remaining days of a certificate with notAfter

Add the certs check to /root/preflight/preflight.py so that it produces observed (notAfter epoch seconds), days_left, and the verdict.

From the output of openssl x509 -enddate -noout -in file (the placeholder is the file), convert what follows notAfter= with ssl.cert_time_to_seconds. A file that could not be read is fail with observed null. The grader makes and mixes certificates that are comfortable, expiring, expired, and broken.

Check settings, hostnames, and write permission

Add the files, hosts, and writable_dirs checks to /root/preflight/preflight.py. Do not create a directory that does not exist, and delete the write test file.

For files, distinguish missing, not a file, and empty. For name resolution, socket.getaddrinfo raises socket.gaierror if it fails. For writing, instead of os.access, actually create with tempfile.mkstemp and delete.

Distinguish three hosts and a broken specification

Check that /root/preflight/preflight.py, across the whole specification, ends normal, warning, and failure with exit codes 0, 1, and 2, and an unreadable specification with 3 and no report.

Even if the order of the specification keys changes, the check ids must be exactly the same as what is written in the specification. If the specification JSON is broken, there is no basis to judge, so you do not write a report.

Give the customer host a go / no-go

Check with spec.json to leave /root/preflight/report.json, and in /root/preflight/decision.json write report_sha256, decision, blockers, and warnings.

decision comes from summary (pass→go, warn→go-with-warnings, fail→no-go). blockers are the fail check ids and warnings are the warn check ids. The grader checks again now to see that the report is not stale and verifies the hash. This is not a step where you fix the blocking items to make them pass.