a73x

internal/agent/bootstrap/bootstrap_test.go

Ref:   Size: 7.2 KiB   History

package bootstrap

import (
	"bytes"
	"context"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"log/slog"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"testing"

	"github.com/a73x/eitri/internal/relmanifest"
)

var platform = runtime.GOOS + "/" + runtime.GOARCH

// fixture describes a manifest server's content: which artifact keys are
// present for the test's platform, their bodies, and (optionally) a SHA256
// override to force a mismatch.
type fixture struct {
	chBody, fwBody       []byte
	chSHA, fwSHA         string // "" ⇒ compute the correct sha of the body
	includeCH, includeFW bool
}

// newServer stands up an httptest server exposing /manifest.json, /ch, and
// /fw, and returns the manifest URL. Also returns the running request
// counter so callers can assert on how many requests were made.
func newServer(t *testing.T, f fixture) (manifestURL string, requests *int) {
	t.Helper()
	mux := http.NewServeMux()
	srv := httptest.NewServer(mux)
	t.Cleanup(srv.Close)

	count := 0
	requests = &count

	mux.HandleFunc("/ch", func(w http.ResponseWriter, r *http.Request) {
		*requests++
		w.Write(f.chBody)
	})
	mux.HandleFunc("/fw", func(w http.ResponseWriter, r *http.Request) {
		*requests++
		w.Write(f.fwBody)
	})

	artifacts := map[string]map[string]relmanifest.Artifact{}
	if f.includeCH {
		sha := f.chSHA
		if sha == "" {
			sum := sha256.Sum256(f.chBody)
			sha = hex.EncodeToString(sum[:])
		}
		artifacts["cloud-hypervisor"] = map[string]relmanifest.Artifact{platform: {URL: srv.URL + "/ch", SHA256: sha}}
	}
	if f.includeFW {
		sha := f.fwSHA
		if sha == "" {
			sum := sha256.Sum256(f.fwBody)
			sha = hex.EncodeToString(sum[:])
		}
		artifacts["firmware"] = map[string]relmanifest.Artifact{platform: {URL: srv.URL + "/fw", SHA256: sha}}
	}
	manifest := relmanifest.Manifest{Version: "v1", Artifacts: artifacts}

	mux.HandleFunc("/manifest.json", func(w http.ResponseWriter, r *http.Request) {
		*requests++
		_ = json.NewEncoder(w).Encode(manifest)
	})
	return srv.URL + "/manifest.json", requests
}

func TestEnsureNoopWhenBothFilesExist(t *testing.T) {
	dir := t.TempDir()
	chPath := filepath.Join(dir, "cloud-hypervisor")
	fwPath := filepath.Join(dir, "CLOUDHV.fd")
	if err := os.WriteFile(chPath, []byte("ch"), 0o755); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(fwPath, []byte("fw"), 0o644); err != nil {
		t.Fatal(err)
	}

	requests := 0
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		requests++
		w.WriteHeader(http.StatusInternalServerError)
	}))
	t.Cleanup(srv.Close)

	b := &Bootstrapper{CHPath: chPath, FirmwarePath: fwPath, ManifestURL: srv.URL}
	if err := b.Ensure(context.Background()); err != nil {
		t.Fatalf("Ensure: %v", err)
	}
	if requests != 0 {
		t.Fatalf("want zero HTTP requests when both files exist, got %d", requests)
	}
}

func TestEnsureInstallsBothWhenMissing(t *testing.T) {
	dir := t.TempDir()
	// Nested, not-yet-created parent dirs: Ensure must MkdirAll them.
	chPath := filepath.Join(dir, "bin", "cloud-hypervisor")
	fwPath := filepath.Join(dir, "share", "CLOUDHV.fd")

	chBody := []byte("ch-binary-contents")
	fwBody := []byte("firmware-contents")
	manifestURL, requests := newServer(t, fixture{chBody: chBody, fwBody: fwBody, includeCH: true, includeFW: true})

	b := &Bootstrapper{CHPath: chPath, FirmwarePath: fwPath, ManifestURL: manifestURL}
	if err := b.Ensure(context.Background()); err != nil {
		t.Fatalf("Ensure: %v", err)
	}
	if *requests == 0 {
		t.Fatal("want at least one HTTP request")
	}

	gotCH, err := os.ReadFile(chPath)
	if err != nil || string(gotCH) != string(chBody) {
		t.Fatalf("cloud-hypervisor contents = %q, %v; want %q", gotCH, err, chBody)
	}
	if fi, _ := os.Stat(chPath); fi.Mode().Perm() != 0o755 {
		t.Fatalf("cloud-hypervisor mode = %v, want 0755", fi.Mode().Perm())
	}

	gotFW, err := os.ReadFile(fwPath)
	if err != nil || string(gotFW) != string(fwBody) {
		t.Fatalf("firmware contents = %q, %v; want %q", gotFW, err, fwBody)
	}
	if fi, _ := os.Stat(fwPath); fi.Mode().Perm() != 0o644 {
		t.Fatalf("firmware mode = %v, want 0644", fi.Mode().Perm())
	}
}

func TestEnsureShaMismatchOnCloudHypervisor(t *testing.T) {
	dir := t.TempDir()
	chPath := filepath.Join(dir, "cloud-hypervisor")
	fwPath := filepath.Join(dir, "CLOUDHV.fd")
	// Firmware already present so only the cloud-hypervisor path is exercised.
	if err := os.WriteFile(fwPath, []byte("fw"), 0o644); err != nil {
		t.Fatal(err)
	}

	manifestURL, _ := newServer(t, fixture{chBody: []byte("ch-binary"), chSHA: "deadbeef", includeCH: true})

	b := &Bootstrapper{CHPath: chPath, FirmwarePath: fwPath, ManifestURL: manifestURL}
	err := b.Ensure(context.Background())
	if err == nil {
		t.Fatal("want sha mismatch error")
	}
	if _, statErr := os.Stat(chPath); !os.IsNotExist(statErr) {
		t.Fatal("cloud-hypervisor destination must be absent after sha mismatch")
	}
	entries, _ := os.ReadDir(dir)
	for _, e := range entries {
		if e.Name() != "CLOUDHV.fd" {
			t.Fatalf("temp file leaked in %s: %v", dir, e.Name())
		}
	}
}

func TestEnsureSkipsWhenManifestURLEmpty(t *testing.T) {
	dir := t.TempDir()
	chPath := filepath.Join(dir, "cloud-hypervisor")
	fwPath := filepath.Join(dir, "CLOUDHV.fd")

	b := &Bootstrapper{CHPath: chPath, FirmwarePath: fwPath, ManifestURL: ""}
	if err := b.Ensure(context.Background()); err != nil {
		t.Fatalf("Ensure: %v", err)
	}
	if _, err := os.Stat(chPath); !os.IsNotExist(err) {
		t.Fatal("cloud-hypervisor must not be installed when ManifestURL is empty")
	}
	if _, err := os.Stat(fwPath); !os.IsNotExist(err) {
		t.Fatal("firmware must not be installed when ManifestURL is empty")
	}
}

func TestEnsureWarnsWhenFirmwareEntryAbsent(t *testing.T) {
	dir := t.TempDir()
	chPath := filepath.Join(dir, "cloud-hypervisor")
	fwPath := filepath.Join(dir, "CLOUDHV.fd") // missing; manifest has no entry (arm64-style)

	chBody := []byte("ch-binary")
	manifestURL, _ := newServer(t, fixture{chBody: chBody, includeCH: true, includeFW: false})

	var logs bytes.Buffer
	logger := slog.New(slog.NewTextHandler(&logs, nil))

	b := &Bootstrapper{CHPath: chPath, FirmwarePath: fwPath, ManifestURL: manifestURL, Log: logger}
	if err := b.Ensure(context.Background()); err != nil {
		t.Fatalf("Ensure: %v", err)
	}
	if _, err := os.Stat(chPath); err != nil {
		t.Fatalf("cloud-hypervisor must be installed: %v", err)
	}
	if _, err := os.Stat(fwPath); !os.IsNotExist(err) {
		t.Fatal("firmware must remain absent when the manifest has no entry for this platform")
	}
	if !strings.Contains(logs.String(), "level=WARN") {
		t.Fatalf("want a warning logged for the missing firmware entry, got: %s", logs.String())
	}
}

func TestEnsureErrorsWhenCloudHypervisorEntryAbsent(t *testing.T) {
	dir := t.TempDir()
	chPath := filepath.Join(dir, "cloud-hypervisor") // missing; manifest has no entry
	fwPath := filepath.Join(dir, "CLOUDHV.fd")
	if err := os.WriteFile(fwPath, []byte("fw"), 0o644); err != nil {
		t.Fatal(err)
	}

	manifestURL, _ := newServer(t, fixture{includeCH: false, includeFW: false})

	b := &Bootstrapper{CHPath: chPath, FirmwarePath: fwPath, ManifestURL: manifestURL}
	err := b.Ensure(context.Background())
	if err == nil {
		t.Fatal("want an error when cloud-hypervisor is missing and has no manifest entry")
	}
	if !strings.Contains(err.Error(), "cloud-hypervisor") {
		t.Fatalf("error should name cloud-hypervisor, got: %v", err)
	}
}