Files
kubesolo-os/update/pkg/partition/partition_test.go
Adolfo Delorenzo 8d25e1890e feat: add A/B partition updates with GRUB and Go update agent (Phase 3)
Implement atomic OS updates via A/B partition scheme with automatic
rollback. GRUB bootloader manages slot selection with a 3-attempt
boot counter that auto-rolls back on repeated health check failures.

GRUB boot config:
- A/B slot selection with boot_counter/boot_success env vars
- Automatic rollback when counter reaches 0 (3 failed boots)
- Debug, emergency shell, and manual slot-switch menu entries

Disk image (refactored):
- 4-partition GPT layout: EFI + System A + System B + Data
- GRUB EFI/BIOS installation with graceful fallbacks
- Both system partitions populated during image creation

Update agent (Go, zero external deps):
- pkg/grubenv: read/write GRUB env vars (grub-editenv + manual fallback)
- pkg/partition: find/mount/write system partitions by label
- pkg/image: HTTP download with SHA256 verification
- pkg/health: post-boot checks (containerd, API server, node Ready)
- 6 CLI commands: check, apply, activate, rollback, healthcheck, status
- 37 unit tests across all 4 packages

Deployment:
- K8s CronJob for automatic update checks (every 6 hours)
- ConfigMap for update server URL
- Health check Job for post-boot verification

Build pipeline:
- build-update-agent.sh compiles static Linux binary (~5.9 MB)
- inject-kubesolo.sh includes update agent in initramfs
- Makefile: build-update-agent, test-update-agent, test-update targets

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 11:12:46 -06:00

130 lines
2.8 KiB
Go

package partition
import (
"os"
"path/filepath"
"testing"
)
func TestReadVersion(t *testing.T) {
dir := t.TempDir()
versionFile := filepath.Join(dir, "version")
if err := os.WriteFile(versionFile, []byte("1.2.3\n"), 0o644); err != nil {
t.Fatal(err)
}
version, err := ReadVersion(dir)
if err != nil {
t.Fatal(err)
}
if version != "1.2.3" {
t.Errorf("expected 1.2.3, got %s", version)
}
}
func TestReadVersionMissing(t *testing.T) {
dir := t.TempDir()
_, err := ReadVersion(dir)
if err == nil {
t.Fatal("expected error for missing version file")
}
}
func TestWriteSystemImage(t *testing.T) {
mountPoint := t.TempDir()
srcDir := t.TempDir()
// Create source files
vmlinuzPath := filepath.Join(srcDir, "vmlinuz")
initramfsPath := filepath.Join(srcDir, "kubesolo-os.gz")
if err := os.WriteFile(vmlinuzPath, []byte("kernel data"), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(initramfsPath, []byte("initramfs data"), 0o644); err != nil {
t.Fatal(err)
}
if err := WriteSystemImage(mountPoint, vmlinuzPath, initramfsPath, "2.0.0"); err != nil {
t.Fatal(err)
}
// Verify files were copied
data, err := os.ReadFile(filepath.Join(mountPoint, "vmlinuz"))
if err != nil {
t.Fatal(err)
}
if string(data) != "kernel data" {
t.Errorf("vmlinuz content mismatch")
}
data, err = os.ReadFile(filepath.Join(mountPoint, "kubesolo-os.gz"))
if err != nil {
t.Fatal(err)
}
if string(data) != "initramfs data" {
t.Errorf("initramfs content mismatch")
}
// Verify version file
version, err := ReadVersion(mountPoint)
if err != nil {
t.Fatal(err)
}
if version != "2.0.0" {
t.Errorf("expected version 2.0.0, got %s", version)
}
}
func TestCopyFile(t *testing.T) {
dir := t.TempDir()
src := filepath.Join(dir, "src")
dst := filepath.Join(dir, "dst")
if err := os.WriteFile(src, []byte("test content"), 0o644); err != nil {
t.Fatal(err)
}
if err := copyFile(src, dst); err != nil {
t.Fatal(err)
}
data, err := os.ReadFile(dst)
if err != nil {
t.Fatal(err)
}
if string(data) != "test content" {
t.Errorf("copy content mismatch")
}
}
func TestCopyFileNotFound(t *testing.T) {
dir := t.TempDir()
err := copyFile("/nonexistent", filepath.Join(dir, "dst"))
if err == nil {
t.Fatal("expected error for nonexistent source")
}
}
func TestGetSlotPartitionInvalid(t *testing.T) {
_, err := GetSlotPartition("C")
if err == nil {
t.Fatal("expected error for invalid slot")
}
}
func TestConstants(t *testing.T) {
if LabelSystemA != "KSOLOA" {
t.Errorf("unexpected LabelSystemA: %s", LabelSystemA)
}
if LabelSystemB != "KSOLOB" {
t.Errorf("unexpected LabelSystemB: %s", LabelSystemB)
}
if LabelData != "KSOLODATA" {
t.Errorf("unexpected LabelData: %s", LabelData)
}
if LabelEFI != "KSOLOEFI" {
t.Errorf("unexpected LabelEFI: %s", LabelEFI)
}
}