Files
serverAppConfigs/tests/frank_primary_unit.bats
T
Frank d7bf9f119a retire frank-medium: tests, planning and observability ground on frank-primary
The frank-medium retirement tail the 2026-09-26 lucebox cutover left
uncommitted: the radiance quadlet tree, its swap/fetch tooling and its
seam test are deleted (archive copies live in dankserver/.AGENTTRASH/
20260926-frank-medium-radiance/ and scripts/.AGENTTRASH/20260926-
frank-medium-radiance/), the planner/allowlist/test-changed mappings
drop the service, and frank-large's chat template moves to scripts/
(its old home was the retired frank-medium dir).

Finished here:
- tests/frank_primary_unit.bats: the lucebox quadlet seam (boot
  persistence, host-network bind, HIP_VISIBLE_DEVICES=0,1, HIP-reserved
  env-name guard, checkout-mounted entrypoint/healthcheck, first-start
  patience, deployer payload/port gates).
- test-changed.sh: explicit .AGENTTRASH/ case — retirement commits move
  files to trash, and the wrapper refused those additions with "no
  focused-test mapping".
- observability: the vllm: namespace left with the radiance runtime.
  Rules keep only llamacpp:* series (prefix-cache reuse, spec-decode,
  queueing, endpoint-down); the dashboard drops the vLLM KV/latency
  panels; prometheus.yml documents that frank-primary (lucebox) has no
  /metrics and therefore no scrape job. llm-fleet.json was left
  corrupted mid-edit (missing opening brace) and is rewritten from HEAD.

Suite: 339 green (was 7 red).
2026-09-26 19:25:46 -07:00

95 lines
4.6 KiB
Bash
Executable File

#!/usr/bin/env bats
# Seam: frank-primary — the Dankserver LLM endpoint on :7770.
#
# This file asserts the LUCEBOX shape (the 2026-09-26 cutover that replaced
# frank-medium): a rootless Quadlet running the lucebox DS4.1 engine on the
# two R9700s, with the model mmapped out of /home/dank/models and the
# entrypoint/healthcheck mounted straight out of this checkout.
load helpers
UNIT="$REPO_ROOT/dankserver/frank-primary/quadlet/frank-primary.container"
EP="$REPO_ROOT/dankserver/frank-primary/frank-primary-entrypoint.sh"
HC="$REPO_ROOT/dankserver/frank-primary/frank-primary-healthcheck.py"
@test "the frank-primary quadlet file and its mounted scripts exist" {
[ -f "$UNIT" ]
[ -x "$EP" ]
[ -f "$HC" ]
# The healthcheck is python; a syntax error would leave the container
# permanently unhealthy with the failure hidden inside HealthCmd.
python3 -m py_compile "$HC"
}
@test "frank-primary is boot-persistent and self-healing" {
# Rootless Podman does not auto-start bare containers and restart:
# unless-stopped does not survive shutdown's explicit stop. The unit must
# carry both the boot symlink and a restart policy (2026-08-26 incident:
# a compose-only Caddy never came back after a reboot).
grep -q '^WantedBy=default.target' "$UNIT"
grep -qE '^Restart=(always|on-failure)' "$UNIT"
}
@test "frank-primary binds the host network and needs no published port" {
# luce_server binds 0.0.0.0:7770 directly, so the container takes the
# host netns. A PublishPort would be dead config.
grep -q -- '--network=host' "$UNIT"
! grep -q '^PublishPort=' "$UNIT"
}
@test "frank-primary reaches both R9700s and hides the Raphael iGPU" {
# /dev/kfd + /dev/dri via AddDevice; HIP_VISIBLE_DEVICES=0,1 keeps the
# host's Raphael iGPU (index 2) out of the enumeration so the engine's
# device picks cannot land on it.
grep -q '^AddDevice=/dev/kfd' "$UNIT"
grep -q '^AddDevice=/dev/dri' "$UNIT"
grep -q '^Environment=HIP_VISIBLE_DEVICES=0,1$' "$UNIT"
}
@test "frank-primary never names a bare HIP-reserved env var" {
# The HIP runtime reads bare TEMP for its JIT cache path (the 2026-08-13
# incident: TEMP=1.0 sent the JIT cache to path "1.0" and segfaulted every
# load). HOST and GPU_MAX_HEAP_SIZE are the same class. The unit's
# Environment= lines must not define them; the entrypoint exports
# GPU_MAX_HW_QUEUES (explicitly not GPU_MAX_HEAP_SIZE).
! grep -qE '^Environment=(TEMP|HOST|GPU_MAX_HEAP_SIZE)=' "$UNIT"
! grep -qE '^(export )?(TEMP|HOST|GPU_MAX_HEAP_SIZE)=' "$EP"
}
@test "the unit mounts the entrypoint and healthcheck from the checkout" {
# deploy.sh git-pulls this checkout before dispatching, so a Volume
# reference is how an edit here reaches the container without an image
# rebuild. Absolute host paths, read-only.
grep -q 'Volume=/home/dank/serverAppConfigs/dankserver/frank-primary/frank-primary-entrypoint.sh:/usr/local/bin/frank-primary-entrypoint.sh:ro' "$UNIT"
grep -q 'Volume=/home/dank/serverAppConfigs/dankserver/frank-primary/frank-primary-healthcheck.py:/usr/local/bin/frank-primary-healthcheck.py:ro' "$UNIT"
}
@test "first-start patience is configured on both sides of the health check" {
# Cold start page-ins the dense weights and builds the graphs; the
# container must not be killed as unhealthy while that happens, and the
# deployer must wait the same order of magnitude.
grep -q '^HealthStartPeriod=1800s' "$UNIT"
grep -q '^TimeoutStartSec=1800$' "$UNIT"
grep -q 'wait_container_healthy frank-primary 1800' \
"$REPO_ROOT/scripts/deploy-remote.sh"
}
@test "the deployer refuses to start without image and complete payload" {
# A 377 GiB partial download must never be served: the handler gates on
# the image being present and the model file existing at its exact
# declared byte count (stat -c %s, never wc -c — wc read the whole stream
# for minutes on 2026-09-26).
grep -q 'deploy_llm_primary() {' "$REPO_ROOT/scripts/deploy-remote.sh"
grep -q 'podman image exists localhost/lucebox-rocm:gfx1201' \
"$REPO_ROOT/scripts/deploy-remote.sh"
grep -q 'want_size=405349859328' "$REPO_ROOT/scripts/deploy-remote.sh"
grep -q 'stat -c %s' "$REPO_ROOT/scripts/deploy-remote.sh"
}
@test "the deployer gates on the port actually being free" {
# 7770 is a host-network bind: the previous holder must release it before
# the new container can be created, or pasta's bind fails at create time
# and the failure hides behind Restart=.
grep -q 'wait_port_free 7770' "$REPO_ROOT/scripts/deploy-remote.sh"
}