245 lines
6.4 KiB
Go
245 lines
6.4 KiB
Go
package tests_test
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"github.com/cirruslabs/orchard/internal/command/dev"
|
|
"github.com/cirruslabs/orchard/pkg/client"
|
|
v1 "github.com/cirruslabs/orchard/pkg/resource/v1"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"golang.org/x/crypto/ssh"
|
|
"net"
|
|
"net/http"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestSingleVM(t *testing.T) {
|
|
devClient := StartIntegrationTestEnvironment(t)
|
|
|
|
workers, err := devClient.Workers().List(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.Equal(t, 1, len(workers))
|
|
err = devClient.VMs().Create(context.Background(), &v1.VM{
|
|
Meta: v1.Meta{
|
|
Name: "test-vm",
|
|
},
|
|
Image: "ghcr.io/cirruslabs/macos-ventura-base:latest",
|
|
CPU: 4,
|
|
Memory: 8 * 1024,
|
|
Softnet: false,
|
|
Headless: true,
|
|
Status: v1.VMStatusPending,
|
|
StartupScript: &v1.VMScript{
|
|
ScriptContent: "echo \"Hello, $FOO!\"",
|
|
Env: map[string]string{"FOO": "Bar"},
|
|
},
|
|
ShutdownScript: &v1.VMScript{
|
|
ScriptContent: "echo \"Buy!\"",
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.True(t, Wait(2*time.Minute, func() bool {
|
|
vm, err := devClient.VMs().Get(context.Background(), "test-vm")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Logf("Waiting for the VM to start. Current status: %s", vm.Status)
|
|
return vm.Status == v1.VMStatusRunning || vm.Status == v1.VMStatusFailed
|
|
}), "failed to start a VM")
|
|
runningVM, err := devClient.VMs().Get(context.Background(), "test-vm")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.Empty(t, runningVM.StatusMessage)
|
|
assert.Equal(t, v1.VMStatusRunning, runningVM.Status)
|
|
assert.True(t, Wait(2*time.Minute, func() bool {
|
|
logLines, err := devClient.VMs().Logs(context.Background(), "test-vm")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return len(logLines) > 0
|
|
}), "failed to wait for logs to become available")
|
|
logLines, err := devClient.VMs().Logs(context.Background(), "test-vm")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.Equal(t, []string{"Hello, Bar!"}, logLines)
|
|
|
|
stoppingVM, err := devClient.VMs().Stop(context.Background(), "test-vm")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.Equal(t, v1.VMStatusStopping, stoppingVM.Status)
|
|
assert.True(t, Wait(2*time.Minute, func() bool {
|
|
vm, err := devClient.VMs().Get(context.Background(), "test-vm")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Logf("Waiting for the VM to stop. Current status: %s", vm.Status)
|
|
return vm.Status == v1.VMStatusStopped
|
|
}), "failed to stop a VM")
|
|
logLines, err = devClient.VMs().Logs(context.Background(), "test-vm")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.Equal(t, []string{"Hello, Bar!", "Buy!"}, logLines)
|
|
}
|
|
|
|
func TestFailedStartupScript(t *testing.T) {
|
|
devClient := StartIntegrationTestEnvironment(t)
|
|
|
|
workers, err := devClient.Workers().List(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.Equal(t, 1, len(workers))
|
|
err = devClient.VMs().Create(context.Background(), &v1.VM{
|
|
Meta: v1.Meta{
|
|
Name: "test-vm",
|
|
},
|
|
Image: "ghcr.io/cirruslabs/macos-ventura-base:latest",
|
|
CPU: 4,
|
|
Memory: 8 * 1024,
|
|
Softnet: false,
|
|
Headless: true,
|
|
Status: v1.VMStatusPending,
|
|
StartupScript: &v1.VMScript{
|
|
ScriptContent: "exit 123",
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.True(t, Wait(2*time.Minute, func() bool {
|
|
vm, err := devClient.VMs().Get(context.Background(), "test-vm")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Logf("Waiting for the VM to start. Current status: %s", vm.Status)
|
|
return vm.Status == v1.VMStatusFailed
|
|
}), "failed to start a VM")
|
|
runningVM, err := devClient.VMs().Get(context.Background(), "test-vm")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assert.Equal(t, "failed to run script: Process exited with status 123", runningVM.StatusMessage)
|
|
}
|
|
|
|
func Wait(duration time.Duration, condition func() bool) bool {
|
|
ctx, cancel := context.WithTimeout(context.Background(), duration)
|
|
defer cancel()
|
|
for {
|
|
if condition() {
|
|
// all good
|
|
return true
|
|
}
|
|
select {
|
|
case <-ctx.Done():
|
|
return false
|
|
case <-time.After(5 * time.Second):
|
|
// try again
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
func StartIntegrationTestEnvironment(t *testing.T) *client.Client {
|
|
t.Setenv("ORCHARD_HOME", t.TempDir())
|
|
devController, devWorker, err := dev.CreateDevControllerAndWorker(t.TempDir())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() {
|
|
_ = devWorker.DeleteAllVMs()
|
|
})
|
|
devContext, cancelDevFunc := context.WithCancel(context.Background())
|
|
t.Cleanup(cancelDevFunc)
|
|
go func() {
|
|
err := devController.Run(devContext)
|
|
if err != nil && !errors.Is(err, context.Canceled) && !errors.Is(err, http.ErrServerClosed) {
|
|
t.Errorf("dev controller failed: %v", err)
|
|
}
|
|
}()
|
|
go func() {
|
|
err := devWorker.Run(devContext)
|
|
if err != nil && !errors.Is(err, context.Canceled) {
|
|
t.Errorf("dev worker failed: %v", err)
|
|
}
|
|
}()
|
|
|
|
time.Sleep(5 * time.Second)
|
|
|
|
devClient, err := client.New(client.WithAddress(devController.Address()))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return devClient
|
|
}
|
|
|
|
func TestPortForwarding(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
devClient := StartIntegrationTestEnvironment(t)
|
|
|
|
// Create a generic macOS VM
|
|
err := devClient.VMs().Create(ctx, &v1.VM{
|
|
Meta: v1.Meta{
|
|
Name: "test-vm",
|
|
},
|
|
Image: "ghcr.io/cirruslabs/macos-ventura-base:latest",
|
|
CPU: 4,
|
|
Memory: 8 * 1024,
|
|
Softnet: false,
|
|
Headless: true,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Wait for the VM to start
|
|
var vm *v1.VM
|
|
|
|
require.True(t, Wait(2*time.Minute, func() bool {
|
|
vm, err = devClient.VMs().Get(ctx, "test-vm")
|
|
require.NoError(t, err)
|
|
|
|
t.Logf("Waiting for the VM to start, current status: %s", vm.Status)
|
|
|
|
return vm.Status == v1.VMStatusRunning || vm.Status == v1.VMStatusFailed
|
|
}), "failed to start a VM")
|
|
|
|
require.Equal(t, v1.VMStatusRunning, vm.Status)
|
|
require.Empty(t, vm.StatusMessage)
|
|
|
|
// Establish port forwarding to VMs SSH port
|
|
wsConn, err := devClient.VMs().PortForward(ctx, "test-vm", 22)
|
|
require.NoError(t, err)
|
|
|
|
// Make sure we can connect to the VM over SSH via the forwarded port
|
|
sshConfig := &ssh.ClientConfig{
|
|
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
|
return nil
|
|
},
|
|
User: "admin",
|
|
Auth: []ssh.AuthMethod{
|
|
ssh.Password("admin"),
|
|
},
|
|
}
|
|
|
|
sshConn, chans, reqs, err := ssh.NewClientConn(wsConn, "", sshConfig)
|
|
require.NoError(t, err)
|
|
|
|
sshClient := ssh.NewClient(sshConn, chans, reqs)
|
|
|
|
sshSession, err := sshClient.NewSession()
|
|
require.NoError(t, err)
|
|
|
|
unameOutput, err := sshSession.Output("uname -mo")
|
|
require.NoError(t, err)
|
|
require.Contains(t, string(unameOutput), "Darwin arm64")
|
|
}
|