From 94fbbeb85040f3442a57ce0bb38c6540bcb9f6b4 Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Tue, 25 Oct 2022 23:11:56 +0900 Subject: [PATCH 01/13] added integration test --- .gitignore | 4 +- go.mod | 5 +- go.sum | 33 ++++- testdata/.gitkeep | 0 virtualization_test.go | 267 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 305 insertions(+), 4 deletions(-) create mode 100644 testdata/.gitkeep create mode 100644 virtualization_test.go diff --git a/.gitignore b/.gitignore index be46f35..6deebbd 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ virtualization *.log .envrc .env -RestoreImage.ipsw \ No newline at end of file +RestoreImage.ipsw +testdata/* +!testdata/.gitkeep \ No newline at end of file diff --git a/go.mod b/go.mod index 5ea09b2..315951e 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module github.com/Code-Hex/vz/v2 go 1.17 -require golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664 +require ( + golang.org/x/crypto v0.1.0 + golang.org/x/sys v0.1.0 +) diff --git a/go.sum b/go.sum index 6b3634a..6b2e5c3 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,31 @@ -golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664 h1:v1W7bwXHsnLLloWYTVEdvGvA7BHMeBYsPcF0GLDxIRs= -golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/testdata/.gitkeep b/testdata/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/virtualization_test.go b/virtualization_test.go new file mode 100644 index 0000000..43b00ee --- /dev/null +++ b/virtualization_test.go @@ -0,0 +1,267 @@ +package vz_test + +import ( + "fmt" + "io" + "net" + "os" + "testing" + "time" + + "github.com/Code-Hex/vz/v2" + "golang.org/x/crypto/ssh" +) + +func setupConsoleConfig(config *vz.VirtualMachineConfiguration) error { + serialPortAttachment, err := vz.NewFileHandleSerialPortAttachment(os.Stdin, os.Stdout) + if err != nil { + return fmt.Errorf("failed to create file handle serial port attachment: %w", err) + } + consoleConfig, err := vz.NewVirtioConsoleDeviceSerialPortConfiguration(serialPortAttachment) + if err != nil { + return fmt.Errorf("failed to create a console device serial port config: %w", err) + } + config.SetSerialPortsVirtualMachineConfiguration([]*vz.VirtioConsoleDeviceSerialPortConfiguration{ + consoleConfig, + }) + return nil +} + +func setupNetworkConfig(config *vz.VirtualMachineConfiguration) error { + natAttachment, err := vz.NewNATNetworkDeviceAttachment() + if err != nil { + return fmt.Errorf("failed to create NAT network device attachment: %w", err) + } + networkConfig, err := vz.NewVirtioNetworkDeviceConfiguration(natAttachment) + if err != nil { + return fmt.Errorf("failed to create a network device config: %w", err) + } + config.SetNetworkDevicesVirtualMachineConfiguration([]*vz.VirtioNetworkDeviceConfiguration{ + networkConfig, + }) + mac, err := vz.NewRandomLocallyAdministeredMACAddress() + if err != nil { + return fmt.Errorf("failed to generate random MAC address: %w", err) + } + networkConfig.SetMACAddress(mac) + return nil +} + +func setupConfiguration(bootLoader vz.BootLoader) (*vz.VirtualMachineConfiguration, error) { + config, err := vz.NewVirtualMachineConfiguration( + bootLoader, + 1, + 512*1024*1024, + ) + if err != nil { + return nil, fmt.Errorf("failed to create a new virtual machine config: %w", err) + } + + // entropy device + entropyConfig, err := vz.NewVirtioEntropyDeviceConfiguration() + if err != nil { + return nil, fmt.Errorf("failed to create entropy device config: %w", err) + } + config.SetEntropyDevicesVirtualMachineConfiguration([]*vz.VirtioEntropyDeviceConfiguration{ + entropyConfig, + }) + + // memory balloon device + memoryBalloonDevice, err := vz.NewVirtioTraditionalMemoryBalloonDeviceConfiguration() + if err != nil { + return nil, fmt.Errorf("failed to create memory balloon device config: %w", err) + } + config.SetMemoryBalloonDevicesVirtualMachineConfiguration([]vz.MemoryBalloonDeviceConfiguration{ + memoryBalloonDevice, + }) + + // vsock device + vsockDevice, err := vz.NewVirtioSocketDeviceConfiguration() + if err != nil { + return nil, fmt.Errorf("failed to create virtio socket device config: %w", err) + } + config.SetSocketDevicesVirtualMachineConfiguration([]vz.SocketDeviceConfiguration{ + vsockDevice, + }) + + if err := setupNetworkConfig(config); err != nil { + return nil, err + } + + return config, nil +} + +type Container struct { + *vz.VirtualMachine + *ssh.Client +} + +func (c *Container) Close() error { + return c.Client.Close() +} + +func newVirtualizationMachine( + t *testing.T, + configs ...func(*vz.VirtualMachineConfiguration) error, +) *Container { + vmlinuz := "./testdata/Image" + initramfs := "./testdata/initramfs.cpio.gz" + bootLoader, err := vz.NewLinuxBootLoader( + vmlinuz, + vz.WithCommandLine("console=hvc0"), + vz.WithInitrd(initramfs), + ) + if err != nil { + t.Fatal(err) + } + + config, err := setupConfiguration(bootLoader) + if err != nil { + t.Fatal(err) + } + for _, setConfig := range configs { + if err := setConfig(config); err != nil { + t.Fatal(err) + } + } + + validated, err := config.Validate() + if !validated || err != nil { + t.Fatal(validated, err) + } + + vm, err := vz.NewVirtualMachine(config) + if err != nil { + t.Fatal(err) + } + socketDevices := vm.SocketDevices() + if len(socketDevices) != 1 { + t.Fatalf("want the number of socket devices is 1 but got %d", len(socketDevices)) + } + socketDevice := socketDevices[0] + + if canStart := vm.CanStart(); !canStart { + t.Fatal("want CanStart is true") + } + + if err := vm.Start(); err != nil { + t.Fatal(err) + } + + waitState(t, 3*time.Second, vm, vz.VirtualMachineStateStarting) + waitState(t, 3*time.Second, vm, vz.VirtualMachineStateRunning) + + sshConfig := &ssh.ClientConfig{ + User: "root", + Auth: []ssh.AuthMethod{ssh.Password("passwd")}, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + } + + time.Sleep(3 * time.Second) + + clientCh := make(chan *ssh.Client, 1) + socketDevice.ConnectToPort(2222, func(vsockConn *vz.VirtioSocketConnection, err error) { + if err != nil { + t.Errorf("failed to connect vsock: %v", err) + return + } + + sshClient, err := newSshClient(vsockConn, ":22", sshConfig) + if err != nil { + vsockConn.Close() + t.Errorf("failed to create a new ssh client: %v", err) + return + } + clientCh <- sshClient + close(clientCh) + }) + + sshClient := <-clientCh + + return &Container{ + VirtualMachine: vm, + Client: sshClient, + } +} + +func waitState(t *testing.T, wait time.Duration, vm *vz.VirtualMachine, want vz.VirtualMachineState) { + t.Helper() + select { + case got := <-vm.StateChangedNotify(): + if want != got { + t.Fatalf("unexpected state want %d but got %d", want, got) + } + case <-time.After(wait): + t.Fatal("failed to wait state changed notification") + } +} + +func newSshClient(conn net.Conn, addr string, config *ssh.ClientConfig) (*ssh.Client, error) { + c, chans, reqs, err := ssh.NewClientConn(conn, addr, config) + if err != nil { + return nil, err + } + return ssh.NewClient(c, chans, reqs), nil +} + +func setKeepAlive(t *testing.T, session *ssh.Session) { + go func() { + for range time.Tick(5 * time.Second) { + _, 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 + } + } + }() +} + +func TestRun(t *testing.T) { + container := newVirtualizationMachine(t) + sshSession, err := container.Client.NewSession() + if err != nil { + t.Error(err) + return + } + defer sshSession.Close() + setKeepAlive(t, sshSession) + + vm := container.VirtualMachine + + if got := vm.CanPause(); !got { + t.Fatal("want CanPause is true") + } + if err := vm.Pause(); err != nil { + t.Fatal(err) + } + + timeout := 5 * time.Second + waitState(t, timeout, vm, vz.VirtualMachineStatePausing) + waitState(t, timeout, vm, vz.VirtualMachineStatePaused) + + if got := vm.CanResume(); !got { + t.Fatal("want CanPause is true") + } + if err := vm.Resume(); err != nil { + t.Fatal(err) + } + + waitState(t, timeout, vm, vz.VirtualMachineStateResuming) + waitState(t, timeout, vm, vz.VirtualMachineStateRunning) + + // TODO(codehex): I need to support + // see: https://developer.apple.com/forums/thread/702160 + // + // t.Logf("CanRequestStop: %t", vm.CanRequestStop()) + // if success, err := vm.RequestStop(); !success || err != nil { + // t.Error(success, err) + // return + // } + + // waitState(t, 5*time.Second, vm, vz.VirtualMachineStateStopping) + // waitState(t, 5*time.Second, vm, vz.VirtualMachineStateStopped) + + sshSession.Run("poweroff") + + waitState(t, 5*time.Second, vm, vz.VirtualMachineStateStopped) +} From 2b8130364e8d6dace3a79f1eee12af4f508bbcc1 Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Tue, 25 Oct 2022 23:12:19 +0900 Subject: [PATCH 02/13] added download_kernel task in Makefile --- Makefile | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 2ac6892..63a3e24 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,24 @@ +PUIPUI_LINUX_VERSION := 0.0.1 +KERNEL_ARCH := $(shell uname -m | sed -e s/arm64/aarch64/) +KERNEL_TAR := puipui_linux_v$(PUIPUI_LINUX_VERSION)_$(KERNEL_ARCH).tar.gz +KERNEL_DOWNLOAD_URL := https://github.com/Code-Hex/puipui-linux/releases/download/v$(PUIPUI_LINUX_VERSION)/$(KERNEL_TAR) + .PHONY: fmt fmt: @ls | grep -E '\.(h|m)$$' | xargs clang-format -i .PHONY: test test: - go test -exec "go run $(PWD)/cmd/codesign" -count=1 ./... + go test -exec "go run $(PWD)/cmd/codesign" -count=1 ./... -timeout 60s + +.PHONY: download_kernel +download_kernel: +ifneq ("$(wildcard $(KERNEL_TAR))","") + curl --output-dir testdata -LO $(KERNEL_DOWNLOAD_URL) +endif + @tar xvf testdata/$(KERNEL_TAR) -C testdata +ifeq ($(shell uname -m), arm64) + @gunzip -f testdata/Image.gz +else + @mv testdata/bzImage testdata/Image +endif \ No newline at end of file From c7ba780ff8f4720ec3db995753902674c268363f Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Tue, 25 Oct 2022 23:13:49 +0900 Subject: [PATCH 03/13] added download_kernel task in actions --- .github/workflows/compile.yml | 4 ++-- Makefile | 13 ++++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.github/workflows/compile.yml b/.github/workflows/compile.yml index 24b6013..602c2ba 100644 --- a/.github/workflows/compile.yml +++ b/.github/workflows/compile.yml @@ -4,8 +4,6 @@ on: paths-ignore: - example/** - README.md - branches: - - "master" pull_request: paths-ignore: - example/** @@ -40,6 +38,8 @@ jobs: uses: actions/setup-go@v2 with: go-version: ${{ matrix.go }} + - name: Download Linux kernel + run: make download_kernel - name: Unit Test run: make test - name: Build diff --git a/Makefile b/Makefile index 63a3e24..1af0f81 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ PUIPUI_LINUX_VERSION := 0.0.1 -KERNEL_ARCH := $(shell uname -m | sed -e s/arm64/aarch64/) +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 KERNEL_DOWNLOAD_URL := https://github.com/Code-Hex/puipui-linux/releases/download/v$(PUIPUI_LINUX_VERSION)/$(KERNEL_TAR) @@ -13,12 +14,14 @@ test: .PHONY: download_kernel download_kernel: -ifneq ("$(wildcard $(KERNEL_TAR))","") curl --output-dir testdata -LO $(KERNEL_DOWNLOAD_URL) -endif @tar xvf testdata/$(KERNEL_TAR) -C testdata -ifeq ($(shell uname -m), arm64) +ifeq ($(ARCH),arm64) @gunzip -f testdata/Image.gz else @mv testdata/bzImage testdata/Image -endif \ No newline at end of file +endif + +.PHONY: clean +clean: + @rm testdata/{Image,initramfs.cpio.gz,*.tar.gz} \ No newline at end of file From 620f7551b43c7b1a2063e9fbef5cf10d9341bab6 Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Tue, 25 Oct 2022 23:53:25 +0900 Subject: [PATCH 04/13] added to setup console --- Makefile | 2 +- virtualization_test.go | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 1af0f81..6b0e63c 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ fmt: .PHONY: test test: - go test -exec "go run $(PWD)/cmd/codesign" -count=1 ./... -timeout 60s + go test -exec "go run $(PWD)/cmd/codesign" -count=1 ./... -timeout 60s -v .PHONY: download_kernel download_kernel: diff --git a/virtualization_test.go b/virtualization_test.go index 43b00ee..807332e 100644 --- a/virtualization_test.go +++ b/virtualization_test.go @@ -217,7 +217,9 @@ func setKeepAlive(t *testing.T, session *ssh.Session) { } func TestRun(t *testing.T) { - container := newVirtualizationMachine(t) + container := newVirtualizationMachine(t, func(vmc *vz.VirtualMachineConfiguration) error { + return setupConsoleConfig(vmc) + }) sshSession, err := container.Client.NewSession() if err != nil { t.Error(err) From 1da75fdd86be1b4dbdff52664a525fd674dc99d0 Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Wed, 26 Oct 2022 00:25:50 +0900 Subject: [PATCH 05/13] fixed sleep time --- virtualization_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/virtualization_test.go b/virtualization_test.go index 807332e..3117c50 100644 --- a/virtualization_test.go +++ b/virtualization_test.go @@ -157,7 +157,7 @@ func newVirtualizationMachine( HostKeyCallback: ssh.InsecureIgnoreHostKey(), } - time.Sleep(3 * time.Second) + time.Sleep(7 * time.Second) clientCh := make(chan *ssh.Client, 1) socketDevice.ConnectToPort(2222, func(vsockConn *vz.VirtioSocketConnection, err error) { From 86993e5b5887204468fde0190dfb1b87e41d51f2 Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Wed, 26 Oct 2022 22:35:27 +0900 Subject: [PATCH 06/13] updated gomod in example linux --- example/linux/go.mod | 2 +- example/linux/go.sum | 34 ++++++++++++++++++++++++++++++---- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/example/linux/go.mod b/example/linux/go.mod index 6d12d07..030ed4f 100644 --- a/example/linux/go.mod +++ b/example/linux/go.mod @@ -7,5 +7,5 @@ replace github.com/Code-Hex/vz/v2 => ../../ require ( github.com/Code-Hex/vz/v2 v2.0.0-00010101000000-000000000000 github.com/pkg/term v1.1.0 - golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664 + golang.org/x/sys v0.1.0 ) diff --git a/example/linux/go.sum b/example/linux/go.sum index 9e13d1d..03dedae 100644 --- a/example/linux/go.sum +++ b/example/linux/go.sum @@ -1,7 +1,33 @@ github.com/pkg/term v1.1.0 h1:xIAAdCMh3QIAy+5FrE8Ad8XoDhEU4ufwbaSozViP9kk= github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= -github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY= -github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664 h1:v1W7bwXHsnLLloWYTVEdvGvA7BHMeBYsPcF0GLDxIRs= -golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 991a83f3aa52431934ea0545b6ab90bb05ec55d6 Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Wed, 26 Oct 2022 22:35:51 +0900 Subject: [PATCH 07/13] updated gomod in example macOS --- example/macOS/go.mod | 6 +----- example/macOS/go.sum | 37 ++++++++++++++++++++++++++++++------- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/example/macOS/go.mod b/example/macOS/go.mod index a74da25..d0bbc9e 100644 --- a/example/macOS/go.mod +++ b/example/macOS/go.mod @@ -4,8 +4,4 @@ go 1.16 replace github.com/Code-Hex/vz/v2 => ../../ -require ( - github.com/Code-Hex/vz/v2 v2.0.0-00010101000000-000000000000 - github.com/pkg/term v1.1.0 - golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664 -) +require github.com/Code-Hex/vz/v2 v2.0.0-00010101000000-000000000000 diff --git a/example/macOS/go.sum b/example/macOS/go.sum index 9e13d1d..637640c 100644 --- a/example/macOS/go.sum +++ b/example/macOS/go.sum @@ -1,7 +1,30 @@ -github.com/pkg/term v1.1.0 h1:xIAAdCMh3QIAy+5FrE8Ad8XoDhEU4ufwbaSozViP9kk= -github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= -github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY= -github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664 h1:v1W7bwXHsnLLloWYTVEdvGvA7BHMeBYsPcF0GLDxIRs= -golang.org/x/sys v0.0.0-20220808155132-1c4a2a72c664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 651f5ea5aea931224055d7508a0831d68dd5c73c Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Wed, 26 Oct 2022 22:36:45 +0900 Subject: [PATCH 08/13] use stderr --- virtualization_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/virtualization_test.go b/virtualization_test.go index 3117c50..a1e9292 100644 --- a/virtualization_test.go +++ b/virtualization_test.go @@ -13,7 +13,7 @@ import ( ) func setupConsoleConfig(config *vz.VirtualMachineConfiguration) error { - serialPortAttachment, err := vz.NewFileHandleSerialPortAttachment(os.Stdin, os.Stdout) + serialPortAttachment, err := vz.NewFileHandleSerialPortAttachment(os.Stdin, os.Stderr) if err != nil { return fmt.Errorf("failed to create file handle serial port attachment: %w", err) } From dda3af335e1e5bc934c20e889377213800599423 Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Thu, 27 Oct 2022 00:04:44 +0900 Subject: [PATCH 09/13] added TestStop --- virtualization_export_test.go | 5 ++ virtualization_test.go | 129 +++++++++++++++++++++++++--------- 2 files changed, 102 insertions(+), 32 deletions(-) create mode 100644 virtualization_export_test.go diff --git a/virtualization_export_test.go b/virtualization_export_test.go new file mode 100644 index 0000000..1417e7e --- /dev/null +++ b/virtualization_export_test.go @@ -0,0 +1,5 @@ +package vz + +func MacosMajorVersionLessThan(version int) bool { + return macosMajorVersionLessThan(version) +} diff --git a/virtualization_test.go b/virtualization_test.go index a1e9292..f00b133 100644 --- a/virtualization_test.go +++ b/virtualization_test.go @@ -1,10 +1,12 @@ package vz_test import ( + "errors" "fmt" "io" "net" "os" + "syscall" "testing" "time" @@ -100,6 +102,15 @@ func (c *Container) Close() error { return c.Client.Close() } +func (c *Container) NewSession(t *testing.T) *ssh.Session { + sshSession, err := c.Client.NewSession() + if err != nil { + t.Fatal(err) + } + setKeepAlive(t, sshSession) + return sshSession +} + func newVirtualizationMachine( t *testing.T, configs ...func(*vz.VirtualMachineConfiguration) error, @@ -157,30 +168,44 @@ func newVirtualizationMachine( HostKeyCallback: ssh.InsecureIgnoreHostKey(), } - time.Sleep(7 * time.Second) - clientCh := make(chan *ssh.Client, 1) - socketDevice.ConnectToPort(2222, func(vsockConn *vz.VirtioSocketConnection, err error) { - if err != nil { - t.Errorf("failed to connect vsock: %v", err) - return - } - - sshClient, err := newSshClient(vsockConn, ":22", sshConfig) - if err != nil { - vsockConn.Close() - t.Errorf("failed to create a new ssh client: %v", err) - return - } - clientCh <- sshClient - close(clientCh) - }) + errCh := make(chan error, 1) - sshClient := <-clientCh +RETRY: + for i := 1; ; i++ { + socketDevice.ConnectToPort(2222, func(vsockConn *vz.VirtioSocketConnection, err error) { + if err != nil { + errCh <- fmt.Errorf("failed to connect vsock: %w", err) + return + } - return &Container{ - VirtualMachine: vm, - Client: sshClient, + sshClient, err := newSshClient(vsockConn, ":22", sshConfig) + if err != nil { + vsockConn.Close() + errCh <- fmt.Errorf("failed to create a new ssh client: %w", err) + return + } + clientCh <- sshClient + close(clientCh) + }) + + select { + case err := <-errCh: + var nserr *vz.NSError + if !errors.As(err, &nserr) || i > 5 { + t.Fatal(err) + } + if nserr.Code == int(syscall.ECONNRESET) { + t.Logf("retry vsock connect: %d", i) + time.Sleep(time.Second) + continue RETRY + } + case sshClient := <-clientCh: + return &Container{ + VirtualMachine: vm, + Client: sshClient, + } + } } } @@ -217,16 +242,15 @@ func setKeepAlive(t *testing.T, session *ssh.Session) { } func TestRun(t *testing.T) { - container := newVirtualizationMachine(t, func(vmc *vz.VirtualMachineConfiguration) error { - return setupConsoleConfig(vmc) - }) - sshSession, err := container.Client.NewSession() - if err != nil { - t.Error(err) - return - } + container := newVirtualizationMachine(t, + func(vmc *vz.VirtualMachineConfiguration) error { + return setupConsoleConfig(vmc) + }, + ) + defer container.Close() + + sshSession := container.NewSession(t) defer sshSession.Close() - setKeepAlive(t, sshSession) vm := container.VirtualMachine @@ -251,10 +275,12 @@ func TestRun(t *testing.T) { waitState(t, timeout, vm, vz.VirtualMachineStateResuming) waitState(t, timeout, vm, vz.VirtualMachineStateRunning) + if got := vm.CanRequestStop(); !got { + t.Fatal("want CanRequestStop is true") + } // TODO(codehex): I need to support // see: https://developer.apple.com/forums/thread/702160 // - // t.Logf("CanRequestStop: %t", vm.CanRequestStop()) // if success, err := vm.RequestStop(); !success || err != nil { // t.Error(success, err) // return @@ -265,5 +291,44 @@ func TestRun(t *testing.T) { sshSession.Run("poweroff") - waitState(t, 5*time.Second, vm, vz.VirtualMachineStateStopped) + waitState(t, timeout, vm, vz.VirtualMachineStateStopped) +} + +func TestStop(t *testing.T) { + container := newVirtualizationMachine(t) + defer container.Close() + + vm := container.VirtualMachine + + if vz.MacosMajorVersionLessThan(12) { + t.Run("check Stop API for macOS 11", func(t *testing.T) { + if got := vm.CanStop(); got { + t.Fatal("want CanStop is false") + } + if err := vm.Stop(); err != nil && !errors.Is(err, vz.ErrUnsupportedOSVersion) { + t.Fatalf("unexpected error want %v but got %v", + vz.ErrUnsupportedOSVersion, + err, + ) + } + + sshSession := container.NewSession(t) + defer sshSession.Close() + + sshSession.Run("poweroff") + waitState(t, 3*time.Second, vm, vz.VirtualMachineStateStopped) + }) + return + } + + if got := vm.CanStop(); !got { + t.Fatal("want CanRequestStop is true") + } + if err := vm.Stop(); err != nil { + t.Fatal(err) + } + + timeout := 3 * time.Second + waitState(t, timeout, vm, vz.VirtualMachineStateStopping) + waitState(t, timeout, vm, vz.VirtualMachineStateStopped) } From 15c8f4283f0f7ea7a09cfef4125631b3a46a4fba Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Thu, 27 Oct 2022 00:22:50 +0900 Subject: [PATCH 10/13] added unstable workaround for macOS 11 --- virtualization_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/virtualization_test.go b/virtualization_test.go index f00b133..7c95896 100644 --- a/virtualization_test.go +++ b/virtualization_test.go @@ -171,6 +171,14 @@ func newVirtualizationMachine( clientCh := make(chan *ssh.Client, 1) errCh := make(chan error, 1) + // Workaround for macOS 11 + // + // This is a workaround. This version of the API does not immediately return an error and + // does not seem to have a connection timeout set. + if vz.MacosMajorVersionLessThan(12) { + time.Sleep(5 * time.Second) + } + RETRY: for i := 1; ; i++ { socketDevice.ConnectToPort(2222, func(vsockConn *vz.VirtioSocketConnection, err error) { From f39cee70dba17873323888c8374b88edce2602ce Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Thu, 27 Oct 2022 00:34:52 +0900 Subject: [PATCH 11/13] added test to check current state --- virtualization_test.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/virtualization_test.go b/virtualization_test.go index 7c95896..706f1bd 100644 --- a/virtualization_test.go +++ b/virtualization_test.go @@ -262,6 +262,9 @@ func TestRun(t *testing.T) { vm := container.VirtualMachine + if got := vm.State(); vz.VirtualMachineStateRunning != got { + t.Fatalf("want state %v but got %v", vz.VirtualMachineStateRunning, got) + } if got := vm.CanPause(); !got { t.Fatal("want CanPause is true") } @@ -273,6 +276,9 @@ func TestRun(t *testing.T) { waitState(t, timeout, vm, vz.VirtualMachineStatePausing) waitState(t, timeout, vm, vz.VirtualMachineStatePaused) + if got := vm.State(); vz.VirtualMachineStatePaused != got { + t.Fatalf("want state %v but got %v", vz.VirtualMachineStatePaused, got) + } if got := vm.CanResume(); !got { t.Fatal("want CanPause is true") } @@ -300,6 +306,10 @@ func TestRun(t *testing.T) { sshSession.Run("poweroff") waitState(t, timeout, vm, vz.VirtualMachineStateStopped) + + if got := vm.State(); vz.VirtualMachineStateStopped != got { + t.Fatalf("want state %v but got %v", vz.VirtualMachineStateStopped, got) + } } func TestStop(t *testing.T) { From f9d1dbd1278252d61ebddef17bf71025526a5f35 Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Thu, 27 Oct 2022 02:11:06 +0900 Subject: [PATCH 12/13] added some tests for shared directory --- shared_folder.go => shared_directory.go | 11 +- shared_directory_test.go | 255 ++++++++++++++++++++++++ virtualization.h | 2 +- virtualization.m | 6 +- virtualization_test.go | 25 +-- 5 files changed, 274 insertions(+), 25 deletions(-) rename shared_folder.go => shared_directory.go (95%) create mode 100644 shared_directory_test.go diff --git a/shared_folder.go b/shared_directory.go similarity index 95% rename from shared_folder.go rename to shared_directory.go index a43a61a..d0edf6e 100644 --- a/shared_folder.go +++ b/shared_directory.go @@ -43,11 +43,18 @@ func NewVirtioFileSystemDeviceConfiguration(tag string) (*VirtioFileSystemDevice } tagChar := charWithGoString(tag) defer tagChar.Free() + + nserr := newNSErrorAsNil() + nserrPtr := nserr.Ptr() + fsdConfig := &VirtioFileSystemDeviceConfiguration{ pointer: pointer{ - ptr: C.newVZVirtioFileSystemDeviceConfiguration(tagChar.CString()), + ptr: C.newVZVirtioFileSystemDeviceConfiguration(tagChar.CString(), &nserrPtr), }, } + if err := newNSError(nserrPtr); err != nil { + return nil, err + } runtime.SetFinalizer(fsdConfig, func(self *VirtioFileSystemDeviceConfiguration) { self.Release() }) @@ -151,7 +158,7 @@ func NewMultipleDirectoryShare(shares map[string]*SharedDirectory) (*MultipleDir ptr: C.newVZMultipleDirectoryShare(dict.Ptr()), }, } - runtime.SetFinalizer(config, func(self *SingleDirectoryShare) { + runtime.SetFinalizer(config, func(self *MultipleDirectoryShare) { self.Release() }) return config, nil diff --git a/shared_directory_test.go b/shared_directory_test.go new file mode 100644 index 0000000..6fe9cde --- /dev/null +++ b/shared_directory_test.go @@ -0,0 +1,255 @@ +package vz_test + +import ( + "bytes" + "fmt" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/Code-Hex/vz/v2" +) + +func TestVirtioFileSystemDeviceConfigurationTag(t *testing.T) { + if vz.MacosMajorVersionLessThan(12) { + t.Skip("VirtioFileSystemDeviceConfiguration is supported from macOS 12") + } + + // The tag can’t be empty and must be fewer than 36 bytes when encoded in UTF-8. + // https://developer.apple.com/documentation/virtualization/vzvirtiofilesystemdeviceconfiguration/3816092-validatetag?language=objc + invalidTags := []string{ + "", + strings.Repeat("a", 37), + } + for _, invalidTag := range invalidTags { + _, err := vz.NewVirtioFileSystemDeviceConfiguration(invalidTag) + if err == nil { + t.Fatalf("want error for %q", invalidTag) + } + } +} + +func TestSingleDirectoryShare(t *testing.T) { + if vz.MacosMajorVersionLessThan(12) { + t.Skip("SingleDirectoryShare is supported from macOS 12") + } + + cases := []struct { + name string + readOnly bool + }{ + { + name: "readonly", + readOnly: true, + }, + { + name: "read-write", + readOnly: false, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + dir := t.TempDir() + sharedDirectory, err := vz.NewSharedDirectory(dir, tc.readOnly) + if err != nil { + t.Fatal(err) + } + single, err := vz.NewSingleDirectoryShare(sharedDirectory) + if err != nil { + t.Fatal(err) + } + + tag := tc.name + fileSystemDeviceConfig, err := vz.NewVirtioFileSystemDeviceConfiguration(tag) + if err != nil { + t.Fatal(err) + } + fileSystemDeviceConfig.SetDirectoryShare(single) + + container := newVirtualizationMachine(t, + func(vmc *vz.VirtualMachineConfiguration) error { + vmc.SetDirectorySharingDevicesVirtualMachineConfiguration( + []vz.DirectorySharingDeviceConfiguration{ + fileSystemDeviceConfig, + }, + ) + return nil + }, + ) + defer container.Close() + + vm := container.VirtualMachine + + file := "hello.txt" + for _, v := range []struct { + cmd string + wantErr bool + }{ + { + cmd: "mkdir -p /mnt/shared", + wantErr: false, + }, + { + cmd: fmt.Sprintf("mount -t virtiofs %s /mnt/shared", tag), + wantErr: false, + }, + { + cmd: fmt.Sprintf("touch /mnt/shared/%s", file), + wantErr: tc.readOnly, + }, + } { + session := container.NewSession(t) + var buf bytes.Buffer + session.Stderr = &buf + if err := session.Run(v.cmd); err != nil && !v.wantErr { + t.Fatalf("failed to run command %q: %v\nstderr: %q", v.cmd, err, buf) + } + session.Close() + } + + if !tc.readOnly { + _, err = os.Stat(filepath.Join(dir, file)) + if err != nil { + t.Fatalf("expected the file to exist: %v", err) + } + } + + tmpFile := "tmp.txt" + f, err := os.Create(filepath.Join(dir, tmpFile)) + if err != nil { + t.Fatal(err) + } + f.Close() + + session := container.NewSession(t) + defer session.Close() + + var buf bytes.Buffer + session.Stderr = &buf + check := "ls /mnt/shared/" + tmpFile + if err := session.Run(check); err != nil { + t.Fatalf("failed to run command %q: %v\nstderr: %q", check, err, buf) + } + session.Close() + + if err := vm.Stop(); err != nil { + t.Fatal(err) + } + + timeout := 3 * time.Second + waitState(t, timeout, vm, vz.VirtualMachineStateStopping) + waitState(t, timeout, vm, vz.VirtualMachineStateStopped) + }) + } +} + +func TestMultipleDirectoryShare(t *testing.T) { + if vz.MacosMajorVersionLessThan(12) { + t.Skip("MultipleDirectoryShare is supported from macOS 12") + } + + readOnlyDir := t.TempDir() + readOnlySharedDirectory, err := vz.NewSharedDirectory(readOnlyDir, true) + if err != nil { + t.Fatal(err) + } + + rwDir := t.TempDir() + rwSharedDirectory, err := vz.NewSharedDirectory(rwDir, false) + if err != nil { + t.Fatal(err) + } + + multiple, err := vz.NewMultipleDirectoryShare(map[string]*vz.SharedDirectory{ + "readonly": readOnlySharedDirectory, + "read_write": rwSharedDirectory, + }) + if err != nil { + t.Fatal(err) + } + + tag := "multiple" + fileSystemDeviceConfig, err := vz.NewVirtioFileSystemDeviceConfiguration(tag) + if err != nil { + t.Fatal(err) + } + fileSystemDeviceConfig.SetDirectoryShare(multiple) + + container := newVirtualizationMachine(t, + func(vmc *vz.VirtualMachineConfiguration) error { + vmc.SetDirectorySharingDevicesVirtualMachineConfiguration( + []vz.DirectorySharingDeviceConfiguration{ + fileSystemDeviceConfig, + }, + ) + return nil + }, + ) + defer container.Close() + + vm := container.VirtualMachine + + // Create a file in mount directories. + tmpFile := "tmp.txt" + for _, dir := range []string{readOnlyDir, rwDir} { + f, err := os.Create(filepath.Join(dir, tmpFile)) + if err != nil { + t.Fatal(err) + } + f.Close() + } + + helloTxt := "hello.txt" + for _, v := range []struct { + cmd string + wantErr bool + }{ + { + cmd: "mkdir -p /mnt/shared", + wantErr: false, + }, + { + cmd: fmt.Sprintf("mount -t virtiofs %s /mnt/shared", tag), + wantErr: false, + }, + { + cmd: fmt.Sprintf("ls /mnt/shared/readonly/%s", tmpFile), + wantErr: false, + }, + { + cmd: fmt.Sprintf("ls /mnt/shared/read_write/%s", tmpFile), + wantErr: false, + }, + { + cmd: fmt.Sprintf("touch /mnt/shared/readonly/%s", helloTxt), + wantErr: true, + }, + { + cmd: fmt.Sprintf("touch /mnt/shared/read_write/%s", helloTxt), + wantErr: false, + }, + } { + session := container.NewSession(t) + var buf bytes.Buffer + session.Stderr = &buf + if err := session.Run(v.cmd); err != nil && !v.wantErr { + t.Fatalf("failed to run command %q: %v\nstderr: %q", v.cmd, err, buf) + } + session.Close() + } + + _, err = os.Stat(filepath.Join(rwDir, helloTxt)) + if err != nil { + t.Fatalf("expected the file to exist in read/write directory: %v", err) + } + + if err := vm.Stop(); err != nil { + t.Fatal(err) + } + + timeout := 3 * time.Second + waitState(t, timeout, vm, vz.VirtualMachineStateStopping) + waitState(t, timeout, vm, vz.VirtualMachineStateStopped) +} diff --git a/virtualization.h b/virtualization.h index d44ce35..bd36d1e 100644 --- a/virtualization.h +++ b/virtualization.h @@ -83,7 +83,7 @@ void *newVZVirtioSocketListener(); void *newVZSharedDirectory(const char *dirPath, bool readOnly); void *newVZSingleDirectoryShare(void *sharedDirectory); void *newVZMultipleDirectoryShare(void *sharedDirectories); -void *newVZVirtioFileSystemDeviceConfiguration(const char *tag); +void *newVZVirtioFileSystemDeviceConfiguration(const char *tag, void **error); void setVZVirtioFileSystemDeviceConfigurationShare(void *config, void *share); void *VZVirtualMachine_socketDevices(void *machine); void VZVirtioSocketDevice_setSocketListenerForPort(void *socketDevice, void *vmQueue, void *listener, uint32_t port); diff --git a/virtualization.m b/virtualization.m index 5a782aa..516d4f9 100644 --- a/virtualization.m +++ b/virtualization.m @@ -962,10 +962,14 @@ void setNetworkDevicesVZMACAddress(void *config, void *macAddress) The tag to use for this device configuration. @return A VZVirtioFileSystemDeviceConfiguration */ -void *newVZVirtioFileSystemDeviceConfiguration(const char *tag) +void *newVZVirtioFileSystemDeviceConfiguration(const char *tag, void **error) { if (@available(macOS 12, *)) { NSString *tagNSString = [NSString stringWithUTF8String:tag]; + BOOL valid = [VZVirtioFileSystemDeviceConfiguration validateTag:tagNSString error:(NSError *_Nullable *_Nullable)error]; + if (!valid) { + return nil; + } return [[VZVirtioFileSystemDeviceConfiguration alloc] initWithTag:tagNSString]; } diff --git a/virtualization_test.go b/virtualization_test.go index 706f1bd..6ac19a0 100644 --- a/virtualization_test.go +++ b/virtualization_test.go @@ -313,32 +313,15 @@ func TestRun(t *testing.T) { } func TestStop(t *testing.T) { + if vz.MacosMajorVersionLessThan(12) { + t.Skip("Stop is supported from macOS 12") + } + container := newVirtualizationMachine(t) defer container.Close() vm := container.VirtualMachine - if vz.MacosMajorVersionLessThan(12) { - t.Run("check Stop API for macOS 11", func(t *testing.T) { - if got := vm.CanStop(); got { - t.Fatal("want CanStop is false") - } - if err := vm.Stop(); err != nil && !errors.Is(err, vz.ErrUnsupportedOSVersion) { - t.Fatalf("unexpected error want %v but got %v", - vz.ErrUnsupportedOSVersion, - err, - ) - } - - sshSession := container.NewSession(t) - defer sshSession.Close() - - sshSession.Run("poweroff") - waitState(t, 3*time.Second, vm, vz.VirtualMachineStateStopped) - }) - return - } - if got := vm.CanStop(); !got { t.Fatal("want CanRequestStop is true") } From 8dd7c5291e74bb7160634118201c3abcc276a4de Mon Sep 17 00:00:00 2001 From: Kei Kamikawa Date: Thu, 27 Oct 2022 02:18:30 +0900 Subject: [PATCH 13/13] readded branch rule --- .github/workflows/compile.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/compile.yml b/.github/workflows/compile.yml index 602c2ba..d2817b1 100644 --- a/.github/workflows/compile.yml +++ b/.github/workflows/compile.yml @@ -4,6 +4,8 @@ on: paths-ignore: - example/** - README.md + branches: + - "master" pull_request: paths-ignore: - example/**