· about 30 minutes · runs on ubuntu-26.04-devops · unrated
An unrated lab. It runs on your machine with no account and no network, and everything about it — the faults, the checks, the hints and the reference solution — is in the repository. An attempt on it is recorded on your profile and never moves a rating: only rated labs do. Rated and unrated labs.
/opt/sync/sync.py copies the inventory records from the internal inventory API into
/var/lib/sync/records.json, which three other services read. sync.timer runs it shortly after boot
and every five minutes after that.
On Monday the dashboards showed zero machines for an hour. The sync's journal said
done: 0 records every five minutes, and systemd listed every run as successful. On Wednesday the
API was slow to answer and the job was still "running" at lunchtime, so no later run could start.
The API itself is records-api.service. It should answer on http://127.0.0.1:8901/records.
What is expected, and graded — the grader runs sync.py itself, with its own test servers, through
the SYNC_API and SYNC_OUTPUT variables the script already reads:
sync.timer succeeds and writes the API's records, and still does after a
reboot.You have root through sudo.
The machine is checked, rebooted, and checked again. A check passes only if it passes both times.
| Check | Objective |
|---|---|
| 01_api_error_is_a_failure | Turn errors into a non-zero exit status instead of a false success |
| 02_stalled_api_times_out | Put a timeout on every network call |
| 03_good_run_replaces_the_file | Replace a data file atomically, and never overwrite good data with nothing |
| 04_timer_run_succeeds | Run a job from a systemd timer with the right configuration |
Where the lab's hints send you, level by level, as you ask for them (h, then l opens a journal section in the TUI).
man 5 systemd.exec4 questions on the same topic, in the lab's Theory tab. They never affect the lab's grade. Three of them, to answer here:
What does this print?
import subprocess, sys
code = "def main():\n return 3\nmain()"
print(subprocess.run([sys.executable, "-c", code]).returncode,
subprocess.run([sys.executable, "-c", "import sys\nsys.exit(3)"]).returncode)
0 3
A return value from main() is just a value; a Python process that reaches the end of its script exits 0 unless sys.exit (or an uncaught exception, status 1) says otherwise. That is why jobs end with sys.exit(main()).
https://docs.python.org/3/library/sys.html#sys.exit · executed in a sandbox
What does this print?
import json, socket, urllib.error
print(issubclass(urllib.error.HTTPError, OSError),
issubclass(TimeoutError, OSError),
issubclass(json.JSONDecodeError, ValueError))
True True True
HTTPError derives from URLError, which derives from OSError, and TimeoutError is an OSError too; JSONDecodeError derives from ValueError. So catching (OSError, ValueError) around a fetch covers network failures, HTTP errors, timeouts and bad JSON without hiding programming errors.
https://docs.python.org/3/library/exceptions.html#exception-hierarchy · https://docs.python.org/3/library/urllib.error.html · executed in a sandbox
What does this print?
import os, tempfile
d = tempfile.mkdtemp()
path = os.path.join(d, "data.json")
open(path, "w").write("old")
before = os.stat(path).st_ino
fd, tmp = tempfile.mkstemp(dir=d)
os.write(fd, b"new"); os.close(fd)
os.replace(tmp, path)
print(os.stat(path).st_ino == before, oct(os.stat(path).st_mode & 0o777))
False 0o600
os.replace renames the temporary file over the path, so the path now names a different inode. mkstemp creates files with mode 0600, and the rename keeps that mode — a file other services must read needs a chmod before the rename.
https://docs.python.org/3/library/tempfile.html#tempfile.mkstemp · https://docs.python.org/3/library/os.html#os.replace · executed in a sandbox