mirror of
https://github.com/cirruslabs/orchard.git
synced 2026-09-30 03:51:43 +02:00
* Regenerate .pb with protoc-gen-go v1.36.11 * Support running host processes alongside VMs * Replace existing host processes when starting a set
149 lines
4.2 KiB
Go
149 lines
4.2 KiB
Go
//nolint:modernize,noctx,testpackage // Preserve the original integration-test setup and process helper.
|
|
package tests
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"net"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/cirruslabs/orchard/internal/controller"
|
|
"github.com/cirruslabs/orchard/internal/tests/devcontroller"
|
|
"github.com/cirruslabs/orchard/internal/tests/wait"
|
|
"github.com/cirruslabs/orchard/internal/worker"
|
|
v1 "github.com/cirruslabs/orchard/pkg/resource/v1"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
const integrationHelperArg = "--orchard-host-process-integration-test-helper"
|
|
|
|
func TestMain(m *testing.M) {
|
|
if len(os.Args) == 2 && os.Args[1] == integrationHelperArg {
|
|
os.Exit(runIntegrationEchoHelper())
|
|
}
|
|
|
|
os.Exit(m.Run())
|
|
}
|
|
|
|
func TestHostProcessIntegration(t *testing.T) {
|
|
// Use this test binary as an echo host process
|
|
executable, err := os.Executable()
|
|
require.NoError(t, err)
|
|
|
|
const startupMarker = "synthetic VM started canary (from startup script)"
|
|
|
|
// Create a development environment with a synthetic Controller and Worker
|
|
devClient, _, _ := devcontroller.StartIntegrationTestEnvironmentWithAdditionalOpts(
|
|
t,
|
|
false,
|
|
[]controller.Option{controller.WithSynthetic()},
|
|
false,
|
|
[]worker.Option{worker.WithSynthetic()},
|
|
)
|
|
|
|
// Advertise the original test's Tart platform even on a Linux synthetic worker.
|
|
workers, err := devClient.Workers().List(t.Context())
|
|
require.NoError(t, err)
|
|
require.Len(t, workers, 1)
|
|
workers[0].Arch = v1.ArchitectureARM64
|
|
workers[0].Runtime = v1.RuntimeTart
|
|
workers[0].Resources[v1.ResourceTartVMs] = 1
|
|
_, err = devClient.Workers().Create(t.Context(), workers[0])
|
|
require.NoError(t, err)
|
|
|
|
// Create a VM without any host processes
|
|
const vmName = "test-vm"
|
|
|
|
err = devClient.VMs().Create(t.Context(), &v1.VM{
|
|
Meta: v1.Meta{Name: vmName},
|
|
Image: "synthetic",
|
|
StartupScript: &v1.VMScript{ScriptContent: startupMarker},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Wait for the VM to start
|
|
var vm *v1.VM
|
|
|
|
require.True(t, wait.Wait(time.Minute, func() bool {
|
|
vm, err = devClient.VMs().Get(t.Context(), vmName)
|
|
require.NoError(t, err)
|
|
|
|
t.Logf("Waiting for the VM to start. Current status: %s", vm.Status)
|
|
|
|
return vm.Status == v1.VMStatusRunning
|
|
}), "failed to wait for the VM to start")
|
|
|
|
// Add an echo host process to the running VM
|
|
const hostProcessName = "echo"
|
|
|
|
vm.HostProcesses = []v1.HostProcess{{
|
|
Name: hostProcessName,
|
|
Program: executable,
|
|
Args: []string{integrationHelperArg},
|
|
}}
|
|
|
|
vm, err = devClient.VMs().Update(t.Context(), *vm)
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, 1, vm.Generation)
|
|
require.EqualValues(t, 0, vm.ObservedGeneration)
|
|
|
|
// Wait for the Worker to start the host process without restarting the VM
|
|
require.True(t, wait.Wait(time.Minute, func() bool {
|
|
vm, err = devClient.VMs().Get(t.Context(), vmName)
|
|
require.NoError(t, err)
|
|
|
|
t.Logf("Waiting for the host process to start. Current observed generation: %d", vm.ObservedGeneration)
|
|
|
|
return vm.Status == v1.VMStatusRunning &&
|
|
vm.ObservedGeneration == 1 &&
|
|
v1.ConditionIsTrue(vm.Conditions, v1.ConditionTypeHostProcessesReady)
|
|
}), "failed to wait for the host process to start")
|
|
|
|
// Connect to the host process through the Controller
|
|
ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
connection, err := devClient.VMs().PortForwardHostProcess(ctx, vmName, hostProcessName, 30)
|
|
require.NoError(t, err)
|
|
defer connection.Close()
|
|
|
|
require.NoError(t, connection.SetDeadline(time.Now().Add(10*time.Second)))
|
|
|
|
message := []byte("Hello, World!")
|
|
|
|
_, err = connection.Write(message)
|
|
require.NoError(t, err)
|
|
|
|
response := make([]byte, len(message))
|
|
|
|
_, err = io.ReadFull(connection, response)
|
|
require.NoError(t, err)
|
|
require.Equal(t, message, response)
|
|
|
|
// The startup script would run again if applying the update restarted the VM
|
|
logLines, err := devClient.VMs().Logs(t.Context(), vmName)
|
|
require.NoError(t, err)
|
|
require.Equal(t, []string{startupMarker}, logLines)
|
|
}
|
|
|
|
func runIntegrationEchoHelper() int {
|
|
listener, err := net.Listen("unix", os.Getenv("ORCHARD_PROCESS_SOCKET"))
|
|
if err != nil {
|
|
return 2
|
|
}
|
|
defer listener.Close()
|
|
|
|
for {
|
|
connection, err := listener.Accept()
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
go func() {
|
|
defer connection.Close()
|
|
_, _ = io.Copy(connection, connection)
|
|
}()
|
|
}
|
|
}
|