e7fb10200e
Layer pve: never auto-approved; the candidate is the Proxmox userspace set every ring-0 box reports (kernel / boot / firmware names left out); the operator's "Approve Proxmox set" button appears only after 2 healthy night pve steps on every ring-0 box; an approval nudges no box (ring 1 by a signed os_pve_step). 11 §5.10 written (BUILT, unreleased, not yet proven live); §8 step 6 split (userspace §5.10, kernel R-836). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
98 lines
3.4 KiB
Go
98 lines
3.4 KiB
Go
package osupdates
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// R-812 option A (`09` §3 decision 163): the Proxmox package set — approved only by the operator's button, after every
|
|
// ring-0 box ran it in healthy night steps (the host health rule, judged on the box), never a kernel, never pushed to
|
|
// ring 1 by the desired state (a signed os_pve_step only).
|
|
|
|
var pveSet = []Package{{Name: "pve-manager", Version: "9.2.21", Origin: "Proxmox"},
|
|
{Name: "qemu-server", Version: "9.0.9", Origin: "Proxmox"}}
|
|
|
|
func (f *fix) pveNight(t *testing.T, host string, healthy bool) {
|
|
t.Helper()
|
|
out := "nothing"
|
|
if !healthy {
|
|
out = "health_failed"
|
|
}
|
|
f.ingest(t, host, Report{Layer: LayerPVE, Trigger: "night", Mode: "apply", Outcome: out, Healthy: healthy,
|
|
Installed: append([]Package{pk("libc6", "x"), {Name: "proxmox-kernel-helper", Version: "9.0.6", Origin: "Proxmox"}}, pveSet...)})
|
|
}
|
|
|
|
// COMPANION RED-PROOF (observed): add LayerPVE to Layers (the auto-approved list) → this fails.
|
|
func TestPVE_NeverAutoApproved(t *testing.T) {
|
|
f := newFix(t)
|
|
for i := 0; i < 3; i++ {
|
|
f.pveNight(t, "hp", true)
|
|
f.pveNight(t, "n100", true)
|
|
f.now = f.now.Add(25 * time.Hour)
|
|
f.s.Evaluate()
|
|
}
|
|
if rel, _ := f.s.Store.LatestOSRelease(LayerPVE); rel != nil {
|
|
t.Fatalf("a Proxmox set was auto-approved: %+v", rel)
|
|
}
|
|
}
|
|
|
|
// The button works only after two healthy nights on every ring-0 box; the release holds Proxmox userspace only (no
|
|
// kernel name, no Debian package) and nudges no box.
|
|
//
|
|
// COMPANION RED-PROOF (observed): drop the hostSlowRE check for the pve layer in candidate → "proxmox-kernel-helper".
|
|
func TestPVE_ApproveNeedsTwoHealthyNightsOnEveryRing0Box(t *testing.T) {
|
|
f := newFix(t)
|
|
f.pveNight(t, "hp", true)
|
|
f.pveNight(t, "n100", true)
|
|
if _, err := f.s.ApprovePVE(); err == nil || !strings.Contains(err.Error(), "1 of 2") {
|
|
t.Fatalf("approved after one night: %v", err)
|
|
}
|
|
f.now = f.now.Add(24 * time.Hour)
|
|
f.pveNight(t, "hp", true)
|
|
f.pveNight(t, "n100", true)
|
|
id, err := f.s.ApprovePVE()
|
|
if err != nil || !strings.HasPrefix(id, "os-pve-") {
|
|
t.Fatalf("%q %v", id, err)
|
|
}
|
|
rel, _ := f.s.Store.LatestOSRelease(LayerPVE)
|
|
if rel.ApprovedBy != "operator" || !strings.Contains(rel.PackagesJSON, "pve-manager") ||
|
|
strings.Contains(rel.PackagesJSON, "libc6") || strings.Contains(rel.PackagesJSON, "proxmox-kernel") {
|
|
t.Fatalf("release %+v", rel)
|
|
}
|
|
if len(f.bumps) != 0 {
|
|
t.Fatalf("a Proxmox approval must nudge no box (ring 1 takes it by a signed job): %v", f.bumps)
|
|
}
|
|
if b := f.s.DesiredBlock("cust1"); b.Release != nil || b.HostRelease != nil {
|
|
t.Fatalf("the Proxmox set leaked into the desired block: %+v", b)
|
|
}
|
|
}
|
|
|
|
func TestPVE_UnhealthyStepBlocksTheButton(t *testing.T) {
|
|
f := newFix(t)
|
|
f.pveNight(t, "hp", true)
|
|
f.pveNight(t, "n100", true)
|
|
f.now = f.now.Add(24 * time.Hour)
|
|
f.pveNight(t, "hp", false)
|
|
f.pveNight(t, "n100", true)
|
|
if _, err := f.s.ApprovePVE(); err == nil || !strings.Contains(err.Error(), "health_failed") {
|
|
t.Fatalf("an unhealthy Proxmox step did not block: %v", err)
|
|
}
|
|
}
|
|
|
|
// The System page lists the Proxmox candidate beside the Docker one.
|
|
func TestPVE_InTheCandidates(t *testing.T) {
|
|
f := newFix(t)
|
|
f.pveNight(t, "hp", true)
|
|
f.pveNight(t, "n100", true)
|
|
found := false
|
|
for _, c := range f.s.Candidates() {
|
|
if c.Layer == LayerPVE && c.Packages == 2 {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatalf("no pve candidate with 2 packages: %+v", f.s.Candidates())
|
|
}
|
|
}
|