diff --git a/.github/workflows/compile.yml b/.github/workflows/compile.yml index badf1d0a..05245006 100644 --- a/.github/workflows/compile.yml +++ b/.github/workflows/compile.yml @@ -73,4 +73,4 @@ jobs: run: make download_kernel - name: Unit Test run: make test - timeout-minutes: 10 + timeout-minutes: 10 \ No newline at end of file diff --git a/Makefile b/Makefile index 4197b2eb..1fc5cfd8 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -PUIPUI_LINUX_VERSION := 0.0.1 +PUIPUI_LINUX_VERSION := 1.0.3 ARCH := $(shell uname -m) KERNEL_ARCH := $(shell echo $(ARCH) | sed -e s/arm64/aarch64/) KERNEL_TAR := puipui_linux_v$(PUIPUI_LINUX_VERSION)_$(KERNEL_ARCH).tar.gz @@ -10,7 +10,7 @@ fmt: .PHONY: test test: - go test -p 1 -exec "go run $(PWD)/cmd/codesign" ./... -timeout 60s -v + go test -p 1 -exec "go run $(PWD)/cmd/codesign" ./... -timeout 2m -v .PHONY: test/run test/run: @@ -36,4 +36,4 @@ install/stringer: .PHONY: clean clean: - @rm testdata/{Image,initramfs.cpio.gz,*.tar.gz} \ No newline at end of file + @rm testdata/{Image,initramfs.cpio.gz,*.tar.gz} diff --git a/internal/testhelper/ssh.go b/internal/testhelper/ssh.go index 0ba5bf93..990329f9 100644 --- a/internal/testhelper/ssh.go +++ b/internal/testhelper/ssh.go @@ -14,6 +14,7 @@ func NewSshConfig(username, password string) *ssh.ClientConfig { User: username, Auth: []ssh.AuthMethod{ssh.Password(password)}, HostKeyCallback: ssh.InsecureIgnoreHostKey(), + Timeout: 5 * time.Second, } } @@ -28,12 +29,18 @@ func NewSshClient(conn net.Conn, addr string, config *ssh.ClientConfig) (*ssh.Cl func SetKeepAlive(t *testing.T, session *ssh.Session) { t.Helper() go func() { - for range time.Tick(5 * time.Second) { + ticker := time.NewTicker(5 * time.Second) + defer ticker.Stop() + + for range ticker.C { _, err := session.SendRequest("keepalive@codehex.vz", true, nil) if err != nil && err != io.EOF { t.Logf("failed to send keep-alive request: %v", err) return } + if err == io.EOF { + return + } } }() } diff --git a/virtualization_test.go b/virtualization_test.go index f42d8dd1..2af5a19a 100644 --- a/virtualization_test.go +++ b/virtualization_test.go @@ -3,6 +3,8 @@ package vz_test import ( "errors" "fmt" + "math" + "net" "os" "runtime" "syscall" @@ -111,15 +113,30 @@ func (c *Container) NewSession(t *testing.T) *ssh.Session { return sshSession } +func getFreePort() (int, error) { + l, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + return 0, err + } + defer l.Close() + return l.Addr().(*net.TCPAddr).Port, nil +} + func newVirtualizationMachine( t *testing.T, configs ...func(*vz.VirtualMachineConfiguration) error, ) *Container { + port, err := getFreePort() + if err != nil { + t.Fatalf("failed to resolve free tcp addr: %v", err) + } + vmlinuz := "./testdata/Image" initramfs := "./testdata/initramfs.cpio.gz" + cmdline := fmt.Sprintf("console=hvc0 vsock_port=%d", port) bootLoader, err := vz.NewLinuxBootLoader( vmlinuz, - vz.WithCommandLine("console=hvc0"), + vz.WithCommandLine(cmdline), vz.WithInitrd(initramfs), ) if err != nil { @@ -172,28 +189,52 @@ func newVirtualizationMachine( time.Sleep(5 * time.Second) } + max := 8 RETRY: for i := 1; ; i++ { - conn, err := socketDevice.Connect(2222) + conn, err := socketDevice.Connect(uint32(port)) if err != nil { var nserr *vz.NSError - if !errors.As(err, &nserr) || i > 5 { + if !errors.As(err, &nserr) || i > max { t.Fatal(err) } if nserr.Code == int(syscall.ECONNRESET) { t.Logf("retry vsock connect: %d", i) - time.Sleep(time.Second) + time.Sleep(backOffDelay(i)) continue RETRY } t.Fatalf("failed to connect vsock: %v", err) } + t.Log("setup ssh client in container") + + initialized := make(chan struct{}) + retry := make(chan struct{}) + go func() { + select { + case <-initialized: + case <-time.After(5 * time.Second): + close(retry) + t.Log("closed", conn.Close()) + } + }() + sshClient, err := testhelper.NewSshClient(conn, ":22", sshConfig) if err != nil { + select { + case <-retry: + t.Log("retry because ssh handshake has been failed") + continue RETRY + default: + } conn.Close() t.Fatalf("failed to create a new ssh client: %v", err) } + close(initialized) + + t.Logf("container setup done") + return &Container{ VirtualMachine: vm, Client: sshClient, @@ -201,6 +242,12 @@ RETRY: } } +func backOffDelay(retryAttempts int) time.Duration { + factor := 0.5 + delay := math.Exp2(float64(retryAttempts)) * factor + return time.Duration(math.Min(delay, 10)) * time.Second +} + func waitState(t *testing.T, wait time.Duration, vm *vz.VirtualMachine, want vz.VirtualMachineState) { t.Helper() select {