R-898: ring 0 stages exactly the told kernel (select listed, KernelSet(kver))
gates / gates (push) Successful in 1m6s
gates / gates (push) Successful in 1m6s
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -1,3 +1,16 @@
|
||||
## Unreleased — part of v0.153.0: ring 0 stages exactly the told kernel (R-898; `09` §3 decision 176) (2026-10-07)
|
||||
|
||||
**Delivery: the agent binary only** — no root file changed (the wrapper is unchanged; its tests gained two cases).
|
||||
|
||||
- `internal/osupdate/kernel.go`: ring 0's night kernel step stages EXACTLY the kernel the household was told about
|
||||
(select `listed`, `KernelSet(kver)` = the series meta-package and the signed image at the kernel's own version) instead
|
||||
of "whatever is pending tonight". Seen 2026-10-07: demo-felhom was told about 7.0.14-20 while its sources offered
|
||||
7.0.14-22 by night — the old code staged `pending-kernel` and the wrapper refused it (R23), losing the night. A told
|
||||
version that is no longer installable is refused by the wrapper before any change (R7) and the hub tells the household
|
||||
again for the newer kernel (hub v0.143.1). Tests `TestKernel_Ring0ToldNightStagesThenReboots` (red-proved against the
|
||||
old select), `TestKernelSet`; wrapper `test_ring0_listed_installs_the_told_kernel_not_the_newest`,
|
||||
`test_ring0_told_kernel_gone_is_refused_before_any_change`.
|
||||
|
||||
## v0.152.0 — the kernel lane (R-836; `09` §3 decisions 164, 172; `11` §5.11) (2026-10-07)
|
||||
|
||||
Released by `scripts/release-agent.sh`: binary sha256 `95ff42208e36ba49b6e2b09a97e81a6fa11562ecc8042f1ed18d378b8b1f88b1`,
|
||||
|
||||
@@ -1838,6 +1838,33 @@ class KernelLane(unittest.TestCase):
|
||||
m.origins = {"proxmox-kernel-7.0": DEB}
|
||||
self.refused(m, "R2")
|
||||
|
||||
# R-898: ring 0 stages EXACTLY the told kernel (select listed, the set derived from it) — even when the sources
|
||||
# offer a newer one by night; a told version that can no longer be installed is refused BEFORE any change (R7).
|
||||
def test_ring0_listed_installs_the_told_kernel_not_the_newest(self):
|
||||
f = kfake()
|
||||
f.live["proxmox-kernel-7.0"] = {"7.0.14-20", "7.0.14-22", "7.0.2-6"}
|
||||
f.live["proxmox-kernel-7.0.14-20-pve-signed"] = {"7.0.14-20"}
|
||||
told = [{"name": "proxmox-kernel-7.0", "version": "7.0.14-20", "origin": "Proxmox Debian Repository"},
|
||||
{"name": "proxmox-kernel-7.0.14-20-pve-signed", "version": "7.0.14-20", "origin": "Proxmox Debian Repository"}]
|
||||
f.plan.update(select="listed", packages=told, expect_kver="7.0.14-20-pve")
|
||||
rc, rep = run(f)
|
||||
self.assertEqual(rc, 0, rep)
|
||||
self.assertEqual(rep["authority"], "ring0")
|
||||
self.assertEqual(f.env, {"felhom_next": "7.0.14-20-pve"})
|
||||
self.assertEqual(f.installed["proxmox-kernel-7.0"], "7.0.14-20")
|
||||
self.assertNotIn("7.0.14-22-pve", f.boot)
|
||||
|
||||
def test_ring0_told_kernel_gone_is_refused_before_any_change(self):
|
||||
f = kfake()
|
||||
f.live["proxmox-kernel-7.0"] = {"7.0.14-22"} # 7.0.14-20 is no longer in the archive
|
||||
told = [{"name": "proxmox-kernel-7.0", "version": "7.0.14-20", "origin": "Proxmox Debian Repository"},
|
||||
{"name": "proxmox-kernel-7.0.14-20-pve-signed", "version": "7.0.14-20", "origin": "Proxmox Debian Repository"}]
|
||||
f.plan.update(select="listed", packages=told, expect_kver="7.0.14-20-pve")
|
||||
rep = self.refused(f, "R7")
|
||||
self.assertIsNone(f.env)
|
||||
self.assertNotIn(osapply.KERNEL_DEFAULT_CFG, f.tree, "refused before the default was even pinned")
|
||||
self.assertEqual(f.installed["proxmox-kernel-7.0"], "7.0.2-6")
|
||||
|
||||
def test_nothing_pending_changes_nothing(self):
|
||||
f = kfake(kernel_pending=[])
|
||||
rc, rep = run(f)
|
||||
|
||||
@@ -119,8 +119,12 @@ func (l *Leg) runKernel(ctx context.Context, runID string, vmid int, trigger str
|
||||
lg.Info("osupdate: kernel step skipped — ring 1 boots only a kernel a signed os_kernel_step staged", "phase", st.Phase, "staged", st.To, "want", want)
|
||||
return Report{}
|
||||
default:
|
||||
// R-898: EXACTLY the kernel the household was told about — never "whatever is pending tonight" (the sources can
|
||||
// offer a newer one by night; the step then refused, R23, and the night was lost). A version no longer
|
||||
// installable is refused by the wrapper before any change (R7) and the hub tells the household again.
|
||||
wr, cerr := l.call(ctx, runID, kernelPlan("apply", vmid, map[string]any{"release_id": "ring0-" + runID,
|
||||
"select": "pending-kernel", "expect_kver": want, "run_id": runID, "trigger": trigger, "ring": blk.Ring}))
|
||||
"select": "listed", "packages": KernelSet(want), "expect_kver": want, "run_id": runID, "trigger": trigger,
|
||||
"ring": blk.Ring}))
|
||||
rep.unsent = reportFile(l.planDir(), runID, LayerKernel, "apply")
|
||||
rep.Kernel = rawOrNil(wr.Kernel)
|
||||
switch {
|
||||
@@ -322,6 +326,21 @@ func truncate(s string, n int) string {
|
||||
return s[:n]
|
||||
}
|
||||
|
||||
// KernelSet is the package set that installs exactly kver (R-898; the hub's kernelSet, field-exact): the series
|
||||
// meta-package and the signed image, both at the kernel's own version. Proxmox keeps old kernel versions in its archive.
|
||||
// nil for a string that is not a kernel version.
|
||||
func KernelSet(kver string) []Package {
|
||||
m := kverSeriesRE.FindStringSubmatch(kver)
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
v := kver[:len(kver)-len("-pve")]
|
||||
return []Package{{Name: "proxmox-kernel-" + m[1], Version: v, Origin: PVEOrigin},
|
||||
{Name: "proxmox-kernel-" + kver + "-signed", Version: v, Origin: PVEOrigin}}
|
||||
}
|
||||
|
||||
var kverSeriesRE = regexp.MustCompile(`^([0-9]+\.[0-9]+)\.[0-9]+-[0-9]+-pve$`)
|
||||
|
||||
// KernelStepParams are a signed os_kernel_step's params: the exact kernel set (the wrapper compares it with the plan).
|
||||
type KernelStepParams struct {
|
||||
ReleaseID string `json:"release_id"`
|
||||
|
||||
@@ -54,8 +54,12 @@ func TestKernel_Ring0ToldNightStagesThenReboots(t *testing.T) {
|
||||
ap = x
|
||||
}
|
||||
}
|
||||
if ap["select"] != "pending-kernel" || ap["expect_kver"] != kNew || ap["lane"] != "slow" {
|
||||
t.Fatalf("stage plan = %v", ap)
|
||||
// R-898: EXACTLY the told kernel — the listed set derived from it, never "pending" (red before the fix: select was
|
||||
// pending-kernel, so a newer kernel in the sources by night was refused R23 and the night was lost)
|
||||
pk, _ := json.Marshal(ap["packages"])
|
||||
if ap["select"] != "listed" || ap["expect_kver"] != kNew || ap["lane"] != "slow" ||
|
||||
string(pk) != `[{"name":"proxmox-kernel-7.0","origin":"Proxmox Debian Repository","version":"7.0.14-22"},{"name":"proxmox-kernel-7.0.14-22-pve-signed","origin":"Proxmox Debian Repository","version":"7.0.14-22"}]` {
|
||||
t.Fatalf("stage plan = %v (packages %s)", ap, pk)
|
||||
}
|
||||
if p.Kernel.Outcome != "staged" || !p.Kernel.Healthy {
|
||||
t.Fatalf("kernel report = %+v", p.Kernel)
|
||||
@@ -312,3 +316,13 @@ func TestKernel_KeptStageReportIsStaged(t *testing.T) {
|
||||
t.Fatalf("kept = %+v", rep)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKernelSet(t *testing.T) {
|
||||
if got := KernelSet("7.0.14-20-pve"); len(got) != 2 || got[0].Name != "proxmox-kernel-7.0" || got[0].Version != "7.0.14-20" ||
|
||||
got[1].Name != "proxmox-kernel-7.0.14-20-pve-signed" {
|
||||
t.Fatalf("%+v", got)
|
||||
}
|
||||
if KernelSet("7.0; reboot") != nil || KernelSet("") != nil {
|
||||
t.Fatal("a non-kernel string must give no set")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user