This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / spindle / engines / microvm / image_test.go
5.3 kB 195 lines
1//go:build linux 2 3package microvm 4 5import ( 6 "encoding/json" 7 "os" 8 "path/filepath" 9 "strings" 10 "testing" 11) 12 13func writeSpecFile(t *testing.T, path string) { 14 t.Helper() 15 data, err := json.Marshal(validImageSpec()) 16 if err != nil { 17 t.Fatal(err) 18 } 19 if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { 20 t.Fatal(err) 21 } 22 if err := os.WriteFile(path, data, 0o644); err != nil { 23 t.Fatal(err) 24 } 25} 26 27func TestResolveImageConventionalLayouts(t *testing.T) { 28 cases := []struct { 29 name string 30 layout func(t *testing.T, dir string) 31 }{ 32 { 33 name: "directory with spec.json", 34 layout: func(t *testing.T, dir string) { 35 writeSpecFile(t, filepath.Join(dir, "nixos", "spec.json")) 36 }, 37 }, 38 { 39 name: "flat <name>.json", 40 layout: func(t *testing.T, dir string) { 41 writeSpecFile(t, filepath.Join(dir, "nixos.json")) 42 }, 43 }, 44 } 45 46 for _, tc := range cases { 47 t.Run(tc.name, func(t *testing.T) { 48 dir := t.TempDir() 49 tc.layout(t, dir) 50 51 e := testEngine(t, dir) 52 spec, path, name, err := e.resolveImage("nixos") 53 if err != nil { 54 t.Fatalf("resolveImage: %v", err) 55 } 56 if name != "nixos" { 57 t.Fatalf("name = %q, want nixos", name) 58 } 59 if !strings.HasPrefix(path, dir) { 60 t.Fatalf("resolved path %q not under image dir %q", path, dir) 61 } 62 if spec.Shell == "" { 63 t.Fatal("resolved spec not loaded") 64 } 65 }) 66 } 67} 68func TestImageSpecPathPinsSymlinkTarget(t *testing.T) { 69 dir := t.TempDir() 70 imageA := filepath.Join(dir, "image-a") 71 imageB := filepath.Join(dir, "image-b") 72 writeSpecFile(t, filepath.Join(imageA, imageSpecFileName)) 73 writeSpecFile(t, filepath.Join(imageB, imageSpecFileName)) 74 75 alias := filepath.Join(dir, "default") 76 if err := os.Symlink(imageA, alias); err != nil { 77 t.Fatal(err) 78 } 79 got, ok, err := imageSpecPath(alias) 80 if err != nil { 81 t.Fatal(err) 82 } 83 if !ok { 84 t.Fatal("imageSpecPath() did not resolve symlinked image") 85 } 86 87 if err := os.Remove(alias); err != nil { 88 t.Fatal(err) 89 } 90 if err := os.Symlink(imageB, alias); err != nil { 91 t.Fatal(err) 92 } 93 want := filepath.Join(imageA, imageSpecFileName) 94 if got != want { 95 t.Fatalf("imageSpecPath() = %q after alias resolution, want pinned target %q", got, want) 96 } 97} 98 99func TestResolveImageDirectoryMissingSpec(t *testing.T) { 100 dir := t.TempDir() 101 if err := os.MkdirAll(filepath.Join(dir, "nixos"), 0o755); err != nil { 102 t.Fatal(err) 103 } 104 105 e := testEngine(t, dir) 106 _, _, _, err := e.resolveImage("nixos") 107 if err == nil || !strings.Contains(err.Error(), imageSpecFileName) { 108 t.Fatalf("directory without %s should error, got: %v", imageSpecFileName, err) 109 } 110} 111 112func TestResolveImageRequiresImageDirOnlyWhenUsed(t *testing.T) { 113 e := testEngine(t, "") 114 _, _, _, err := e.resolveImage("nixos") 115 if err == nil || !strings.Contains(err.Error(), "SPINDLE_MICROVM_PIPELINES_IMAGE_DIR") { 116 t.Fatalf("missing image directory should error when resolving an image, got: %v", err) 117 } 118} 119 120func TestResolveImageRejectsPaths(t *testing.T) { 121 e := testEngine(t, t.TempDir()) 122 for _, name := range []string{"/etc/passwd", "../evil", "sub/evil", "..", "."} { 123 if _, _, _, err := e.resolveImage(name); err == nil || !strings.Contains(err.Error(), "must be a plain name") { 124 t.Fatalf("name %q should be rejected as a path, got: %v", name, err) 125 } 126 } 127} 128 129func validImageSpec() ImageSpec { 130 return ImageSpec{ 131 Arch: "x86_64", 132 BootArgs: "console=ttyS0", 133 Initrd: "initrd", 134 Kernel: "kernel", 135 RunnerConfig: RunnerConfig{ 136 Machine: "microvm", 137 }, 138 MemoryMiB: 2048, 139 Shell: "/bin/sh", 140 StoreDisk: "store-disk", 141 VCPUs: 2, 142 } 143} 144 145func TestImageSpecValidateWithoutBaseConfigHash(t *testing.T) { 146 spec := validImageSpec() 147 if err := spec.Validate(); err != nil { 148 t.Fatalf("non-NixOS image spec should validate: %v", err) 149 } 150 if spec.SupportsConfigActivation() { 151 t.Fatal("spec without baseConfigHash should not support config activation") 152 } 153 154 spec.BaseConfigHash = "abcdef" 155 if !spec.SupportsConfigActivation() { 156 t.Fatal("spec with baseConfigHash should support config activation") 157 } 158} 159 160func TestImageSpecRequiresShell(t *testing.T) { 161 spec := validImageSpec() 162 spec.Shell = "" 163 err := spec.Validate() 164 if err == nil || !strings.Contains(err.Error(), "shell") { 165 t.Fatalf("spec without shell should fail validation, got: %v", err) 166 } 167} 168func TestMkfsExt4ForVolumesSkipsLookupWithoutVolumes(t *testing.T) { 169 t.Setenv("PATH", "") 170 path, err := mkfsExt4ForVolumes(nil, "") 171 if err != nil { 172 t.Fatalf("mkfsExt4ForVolumes() with no volumes returned error: %v", err) 173 } 174 if path != "" { 175 t.Fatalf("mkfsExt4ForVolumes() = %q with no volumes, want empty path", path) 176 } 177 178 _, err = mkfsExt4ForVolumes([]Volume{{Image: "workspace"}}, "") 179 if err == nil || !strings.Contains(err.Error(), "mkfs.ext4") { 180 t.Fatalf("mkfsExt4ForVolumes() with a volume and no formatter returned %v", err) 181 } 182} 183 184func TestQEMURunnerValidateRejectsUnsupportedNetworkTypeBeforeHostChecks(t *testing.T) { 185 spec := validImageSpec() 186 spec.NetworkInterfaces = []NetworkInterface{{ 187 Type: "tap", 188 ID: "net0", 189 MAC: "02:00:00:00:00:01", 190 }} 191 err := (qemuRunner{}).Validate(spec, false) 192 if err == nil || !strings.Contains(err.Error(), `unsupported microvm network interface type "tap"`) { 193 t.Fatalf("Validate() error = %v, want unsupported network type", err) 194 } 195}