internal/smoke/cloudinit_test.go
Ref: Size: 5.5 KiB History
package smoke
import (
"strconv"
"strings"
"testing"
"github.com/a73x/eitri/internal/cloudinit"
"github.com/a73x/eitri/internal/server/api/client"
)
// grownRootBytes is what a guest reports once the seed's runcmd has grown its
// root to the disk the scenario asked for: the whole default disk, less the
// filesystem's own overhead, which is nowhere near half of it.
const grownRootBytes = client.DefaultDiskGB * (1 << 30)
// imageRootBytes is the root the default Ubuntu image ships — about 3.5G, the
// size an ungrown guest reports. Written as a literal because it is a fact
// about somebody else's image, not a number eitri gets to choose.
const imageRootBytes int64 = 3650722816
func TestGrownRootFloorSitsBetweenTheImageAndTheDefaultDisk(t *testing.T) {
// The floor is derived from the default disk, so it moves when that moves.
// What must stay true either way is that it separates the two roots a guest
// can actually have: a floor under the image's root passes an ungrown guest,
// and one above the disk fails every guest.
if minGrownRootBytes <= imageRootBytes {
t.Errorf("floor %d does not clear the image's own root %d — an ungrown guest would pass", minGrownRootBytes, imageRootBytes)
}
if minGrownRootBytes >= grownRootBytes {
t.Errorf("floor %d is at or above the whole default disk %d — no guest could clear it", minGrownRootBytes, grownRootBytes)
}
}
// probeOutput renders what seedProbeCmd prints in a guest, so each test states
// the three facts it is about and nothing else.
func probeOutput(rootBytes, writeFiles, runcmd string) string {
return "root_bytes=" + rootBytes + "\n" +
"byo_write_files=" + writeFiles + "\n" +
"byo_runcmd=" + runcmd + "\n"
}
func TestSeedProbeAcceptsAGuestWhereBothSidesOfTheMergeLanded(t *testing.T) {
out := probeOutput(strconv.FormatInt(grownRootBytes, 10), "tenant write_files ran", "tenant runcmd ran")
if err := proveSeedSurvivedBYO(out); err != nil {
t.Fatalf("proveSeedSurvivedBYO: %v", err)
}
}
func TestSeedProbeRejectsARootTheGrowNeverReached(t *testing.T) {
// The image's own ~3.5G root on a 10G disk: the seed's runcmd was replaced
// by the tenant's, which is the half of the defect that costs disk.
out := probeOutput(strconv.FormatInt(imageRootBytes, 10), "tenant write_files ran", "tenant runcmd ran")
err := proveSeedSurvivedBYO(out)
if err == nil {
t.Fatal("proveSeedSurvivedBYO: want an error for an ungrown root, got nil")
}
if !strings.Contains(err.Error(), "runcmd") {
t.Errorf("error = %q, want it to name the runcmd that was dropped", err)
}
}
func TestSeedProbeRejectsAGuestThatLostTheTenantsWriteFiles(t *testing.T) {
// The mirror image: eitri's lists won and the tenant's were discarded. A
// merge that only ever keeps eitri's side is not a merge.
out := probeOutput(strconv.FormatInt(grownRootBytes, 10), "", "tenant runcmd ran")
err := proveSeedSurvivedBYO(out)
if err == nil {
t.Fatal("proveSeedSurvivedBYO: want an error when the tenant's write_files is gone, got nil")
}
if !strings.Contains(err.Error(), "write_files") {
t.Errorf("error = %q, want it to name the tenant's write_files", err)
}
}
func TestSeedProbeRejectsAGuestThatLostTheTenantsRuncmd(t *testing.T) {
out := probeOutput(strconv.FormatInt(grownRootBytes, 10), "tenant write_files ran", "")
err := proveSeedSurvivedBYO(out)
if err == nil {
t.Fatal("proveSeedSurvivedBYO: want an error when the tenant's runcmd is gone, got nil")
}
if !strings.Contains(err.Error(), "runcmd") {
t.Errorf("error = %q, want it to name the tenant's runcmd", err)
}
}
func TestSeedProbeRejectsOutputItCannotRead(t *testing.T) {
// A probe whose root_bytes is missing or not a number proves nothing, and
// must never be mistaken for a pass.
for _, out := range []string{
"",
probeOutput("", "w", "r"),
probeOutput("not-a-number", "w", "r"),
} {
if err := proveSeedSurvivedBYO(out); err == nil {
t.Errorf("proveSeedSurvivedBYO(%q): want an error, got nil", out)
}
}
}
func TestBYOCloudInitCarriesTheTwoKeysThatUsedToClobberTheSeed(t *testing.T) {
// The document is the whole point of the leg: without both keys the guest
// never exercises the merge the seed's merge_how exists for.
if !strings.Contains(byoCloudInit, "\nwrite_files:") {
t.Error("byoCloudInit must carry a write_files key")
}
if !strings.Contains(byoCloudInit, "\nruncmd:") {
t.Error("byoCloudInit must carry a runcmd key")
}
if !strings.HasPrefix(byoCloudInit, "#cloud-config\n") {
t.Error("byoCloudInit must be a #cloud-config document")
}
for _, path := range []string{byoWriteFilesMarker, byoRuncmdMarker} {
if !strings.Contains(byoCloudInit, path) {
t.Errorf("byoCloudInit does not write %s, so the probe reads a file nothing creates", path)
}
if !strings.Contains(seedProbeCmd, path) {
t.Errorf("seedProbeCmd does not read %s", path)
}
}
}
func TestBYOCloudInitSurvivesTheControlPlanesOwnSSHKeyMerge(t *testing.T) {
// The create request carries an SSH key alongside the document, and the
// control plane merges the one into the other before the seed ever sees it.
// A document that merge refuses is a 400 at create, so the leg would fail
// before it proved anything about cloud-init.
merged, err := cloudinit.AddSSHKey(byoCloudInit, "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAISMOKE smoke@gate")
if err != nil {
t.Fatalf("AddSSHKey(byoCloudInit): %v", err)
}
for _, want := range []string{"write_files", "runcmd", byoWriteFilesMarker, byoRuncmdMarker, "ssh_authorized_keys"} {
if !strings.Contains(merged, want) {
t.Errorf("merged document lost %q:\n%s", want, merged)
}
}
}