Files
orchard/internal/worker/vmmanager/tart/tart_test.go
T

249 lines
7.2 KiB
Go

//nolint:exhaustruct_v5,goconst // Fixtures omit unrelated fields and keep expected command strings explicit.
package tart //nolint:testpackage // Exercise clone and configuration through the real command runner.
import (
"context"
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/cirruslabs/orchard/internal/worker/ondiskname"
"github.com/cirruslabs/orchard/internal/worker/vmmanager/base"
v1 "github.com/cirruslabs/orchard/pkg/resource/v1"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
)
func TestCloneAndConfigurePreservesSuspendedVM(t *testing.T) {
for _, failedCommand := range []string{"", "fqn"} {
t.Run("failed command="+failedCommand, func(t *testing.T) {
commandLog := installCloneFakeTart(t,
`{"OS":"darwin","CPU":4,"Memory":8192,"Disk":50,"Running":false,"State":"suspended"}`,
failedCommand)
vm := newCloneTestVM(v1.VM{
Name: "test-vm",
UID: "00112233-4455-6677-8899-aabbccddeeff",
Image: "source-image",
CPU: 2,
AssignedCPU: 6,
Memory: 4096,
AssignedMemory: 12288,
DiskSize: 100,
RandomSerial: true,
})
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
defer cancel()
require.NoError(t, vm.cloneAndConfigure(ctx))
requireCloneCommands(t, commandLog, []string{
"clone source-image " + vm.id(),
"fqn source-image",
"get " + vm.id() + " --format json",
})
require.False(t, vm.ConditionsSet().ContainsOne(v1.ConditionTypeCloning))
if failedCommand == "fqn" {
require.Nil(t, vm.ImageFQN())
} else {
require.NotNil(t, vm.ImageFQN())
require.Equal(t, "registry.example/source@sha256:abc", *vm.ImageFQN())
}
})
}
}
func TestRunUSBAccessoriesConfiguration(t *testing.T) {
for _, test := range []struct {
name string
usbAccessories bool
suspendable bool
wantFlags string
}{
{name: "default", wantFlags: "--no-usb-accessories "},
{name: "USB accessories enabled", usbAccessories: true},
{name: "suspendable", suspendable: true, wantFlags: "--no-usb-accessories --suspendable "},
{name: "both", usbAccessories: true, suspendable: true, wantFlags: "--suspendable "},
} {
t.Run(test.name, func(t *testing.T) {
commandLog := installCloneFakeTart(t, "", "")
vm := newCloneTestVM(v1.VM{
Name: "test-vm",
UID: "00112233-4455-6677-8899-aabbccddeeff",
Audio: true,
Clipboard: true,
USBAccessories: test.usbAccessories,
Suspendable: test.suspendable,
})
vm.ctx = t.Context()
vm.ConditionsSet().Add(v1.ConditionTypeStopping)
vm.run(t.Context(), nil)
require.NoError(t, vm.Err())
requireCloneCommands(t, commandLog, []string{"run " + test.wantFlags + vm.id()})
})
}
}
func TestCloneAndConfigureConfiguresStoppedVM(t *testing.T) {
tests := []struct {
name string
resource v1.VM
setArgs []string
}{
{
name: "requested resources",
resource: v1.VM{
CPU: 2,
Memory: 4096,
DiskSize: 100,
RandomSerial: true,
},
setArgs: []string{"--memory 4096", "--cpu 2", "--disk-size 100", "--random-mac", "--random-serial"},
},
{
name: "assigned resources override requested resources",
resource: v1.VM{
CPU: 2,
AssignedCPU: 6,
Memory: 4096,
AssignedMemory: 12288,
},
setArgs: []string{"--memory 12288", "--cpu 6", "--random-mac"},
},
{
name: "image resource defaults",
setArgs: []string{"--random-mac"},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
commandLog := installCloneFakeTart(t, `{"Running":false,"State":"stopped"}`, "")
test.resource.Name = "test-vm"
test.resource.UID = "00112233-4455-6677-8899-aabbccddeeff"
test.resource.Image = "source-image"
vm := newCloneTestVM(test.resource)
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
defer cancel()
require.NoError(t, vm.cloneAndConfigure(ctx))
commands := []string{
"clone source-image " + vm.id(),
"fqn source-image",
"get " + vm.id() + " --format json",
}
for _, args := range test.setArgs {
commands = append(commands, "set "+args+" "+vm.id())
}
requireCloneCommands(t, commandLog, commands)
})
}
}
func TestCloneAndConfigureStopsOnMetadataError(t *testing.T) {
tests := []struct {
name string
output string
failedCommand string
wantError string
}{
{
name: "get command fails",
failedCommand: "get",
wantError: "injected get failure",
},
{
name: "invalid JSON",
output: `{"State":`,
wantError: "unexpected end of JSON input",
},
{
name: "invalid state type",
output: `{"State":123}`,
wantError: "cannot unmarshal number",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
commandLog := installCloneFakeTart(t, test.output, test.failedCommand)
vm := newCloneTestVM(v1.VM{
Name: "test-vm",
UID: "00112233-4455-6677-8899-aabbccddeeff",
Image: "source-image",
Memory: 4096,
})
ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second)
defer cancel()
err := vm.cloneAndConfigure(ctx)
require.ErrorContains(t, err, test.wantError)
if test.name == "invalid JSON" {
var syntaxError *json.SyntaxError
require.ErrorAs(t, err, &syntaxError)
}
if test.name == "invalid state type" {
var typeError *json.UnmarshalTypeError
require.ErrorAs(t, err, &typeError)
}
requireCloneCommands(t, commandLog, []string{
"clone source-image " + vm.id(),
"fqn source-image",
"get " + vm.id() + " --format json",
})
})
}
}
func newCloneTestVM(resource v1.VM) *VM {
logger := zap.NewNop().Sugar()
return &VM{
onDiskName: ondiskname.NewFromResource(resource),
resource: resource,
logger: logger,
VM: base.NewVM(resource, ondiskname.NewFromResource(resource), logger),
}
}
func requireCloneCommands(t *testing.T, commandLog string, commands []string) {
t.Helper()
logged, err := os.ReadFile(commandLog) //nolint:gosec // The command log is created in t.TempDir.
require.NoError(t, err)
require.Equal(t, commands, strings.Split(strings.TrimSpace(string(logged)), "\n"))
}
func installCloneFakeTart(t *testing.T, infoOutput string, failedCommand string) string {
t.Helper()
dir := t.TempDir()
commandLog := filepath.Join(dir, "commands.log")
script := `#!/bin/sh
set -eu
printf '%s\n' "$*" >> "$ORCHARD_TEST_TART_COMMAND_LOG"
if [ "$1" = "$ORCHARD_TEST_TART_FAILED_COMMAND" ]; then
printf 'injected %s failure\n' "$1" >&2
exit 1
fi
case "$1" in
clone|set|run) ;;
fqn) printf 'registry.example/source@sha256:abc\n' ;;
get) printf '%s\n' "$ORCHARD_TEST_TART_INFO" ;;
*) printf 'unexpected command: %s\n' "$*" >&2; exit 1 ;;
esac
`
commandPath := filepath.Join(dir, tartCommandName)
require.NoError(t, os.WriteFile(commandPath, []byte(script), 0o600))
require.NoError(t, os.Chmod(commandPath, 0o700)) //nolint:gosec // The fake Tart command must be executable.
t.Setenv("PATH", dir+string(os.PathListSeparator)+os.Getenv("PATH"))
t.Setenv("ORCHARD_TEST_TART_COMMAND_LOG", commandLog)
t.Setenv("ORCHARD_TEST_TART_INFO", infoOutput)
t.Setenv("ORCHARD_TEST_TART_FAILED_COMMAND", failedCommand)
return commandLog
}