Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

exp(feat): enable Virtualization.framework and Rosetta #282

Merged
merged 20 commits into from
Mar 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ jobs:
if pgrep '^socket_vmnet'; then
sudo pkill '^socket_vmnet'
fi
- name: Install Rosetta 2
run: echo "A" | softwareupdate --install-rosetta || true
- run: brew install go lz4 automake autoconf libtool
- name: Build project
run: |
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@ ifneq (,$(findstring arm64,$(ARCH)))
LIMA_ARCH = aarch64
# From https://dl.fedoraproject.org/pub/fedora/linux/releases/37/Cloud/aarch64/images/
FINCH_OS_BASENAME ?= Fedora-Cloud-Base-37-1.7.aarch64-20230321224649.qcow2
LIMA_URL ?= https://deps.runfinch.com/aarch64/lima-and-qemu.macos-aarch64.1678826933.tar.gz
LIMA_URL ?= https://deps.runfinch.com/aarch64/lima-and-qemu.macos-aarch64.1679936560.tar.gz
else ifneq (,$(findstring x86_64,$(ARCH)))
SUPPORTED_ARCH = true
LIMA_ARCH = x86_64
# From https://dl.fedoraproject.org/pub/fedora/linux/releases/37/Cloud/x86_64/images/
FINCH_OS_BASENAME ?= Fedora-Cloud-Base-37-1.7.x86_64-20230321224635.qcow2
LIMA_URL ?= https://deps.runfinch.com/x86-64/lima-and-qemu.macos-x86_64.1678817277.tar.gz
LIMA_URL ?= https://deps.runfinch.com/x86-64/lima-and-qemu.macos-x86_64.1679936560.tar.gz
endif

FINCH_OS_HASH := `shasum -a 256 $(OUTDIR)/os/$(FINCH_OS_BASENAME) | cut -d ' ' -f 1`
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,17 @@ memory: 4GiB
additional_directories:
# the path of each additional directory.
- path: /Volumes
# vmType (Experimental): sets which Hypervisor to use to launch the VM. (optional)
# Only takes effect when a new VM is launched (only on vm init).
# One of: "qemu", "vz".
# - "qemu" (default): Uses QEMU as the Hypervisor.
# - "vz": Uses Virtualization.framework as the Hypervisor.
vmType: "qemu"
# rosetta (Experimental): sets whether to enable Rosetta as the binfmt_misc handler inside the VM. (optional)
# Only takes effect when a new VM is launched (only on vm init).
# Only available when using vmType "vz" on Apple Silicon running macOS 13+.
# If true, also sets vmType to "vz".
rosetta: false
```

### FAQ
Expand Down
4 changes: 2 additions & 2 deletions cmd/finch/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,11 @@ func virtualMachineCommands(
lcc,
logger,
optionalDepGroups,
config.NewLimaApplier(fc, fs, fp.LimaOverrideConfigPath()),
config.NewLimaApplier(fc, ecc, fs, fp.LimaOverrideConfigPath(), system.NewStdLib()),
config.NewNerdctlApplier(fssh.NewDialer(), fs, fp.LimaSSHPrivateKeyPath()),
fp,
fs,
disk.NewUserDataDiskManager(lcc, &afero.OsFs{}, fp, system.NewStdLib().Env("HOME")),
disk.NewUserDataDiskManager(lcc, ecc, &afero.OsFs{}, fp, system.NewStdLib().Env("HOME"), fc),
)
}

Expand Down
2 changes: 1 addition & 1 deletion cmd/finch/virtual_machine_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func (iva *initVMAction) run() error {
iva.logger.Errorf("Dependency error: %v", err)
}

err = iva.limaConfigApplier.Apply()
err = iva.limaConfigApplier.Apply(true)
if err != nil {
return err
}
Expand Down
8 changes: 4 additions & 4 deletions cmd/finch/virtual_machine_init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func TestInitVMAction_runAdapter(t *testing.T) {
logger.EXPECT().Debugf("Status of virtual machine: %s", "")

command := mocks.NewCommand(ctrl)
lca.EXPECT().Apply().Return(nil)
lca.EXPECT().Apply(true).Return(nil)
dm.EXPECT().EnsureUserDataDisk().Return(nil)
lcc.EXPECT().CreateWithoutStdio("start", fmt.Sprintf("--name=%s", limaInstanceName),
mockBaseYamlFilePath, "--tty=false").Return(command)
Expand Down Expand Up @@ -135,7 +135,7 @@ func TestInitVMAction_run(t *testing.T) {
getVMStatusC.EXPECT().Output().Return([]byte(""), nil)
logger.EXPECT().Debugf("Status of virtual machine: %s", "")

lca.EXPECT().Apply().Return(nil)
lca.EXPECT().Apply(true).Return(nil)
dm.EXPECT().EnsureUserDataDisk().Return(nil)

command := mocks.NewCommand(ctrl)
Expand Down Expand Up @@ -253,7 +253,7 @@ func TestInitVMAction_run(t *testing.T) {
getVMStatusC.EXPECT().Output().Return([]byte(""), nil)
logger.EXPECT().Debugf("Status of virtual machine: %s", "")

lca.EXPECT().Apply().Return(errors.New("load config fails"))
lca.EXPECT().Apply(true).Return(errors.New("load config fails"))
logger.EXPECT().Errorf("Dependency error: %v",
fmt.Errorf("failed to install dependencies: %w",
errors.Join(fmt.Errorf("%s: %w", "mock_error_msg", errors.Join(errors.New("dependency error occurs")))),
Expand All @@ -279,7 +279,7 @@ func TestInitVMAction_run(t *testing.T) {
getVMStatusC.EXPECT().Output().Return([]byte(""), nil)
logger.EXPECT().Debugf("Status of virtual machine: %s", "")

lca.EXPECT().Apply().Return(nil)
lca.EXPECT().Apply(true).Return(nil)
dm.EXPECT().EnsureUserDataDisk().Return(nil)

logs := []byte("stdout + stderr")
Expand Down
2 changes: 1 addition & 1 deletion cmd/finch/virtual_machine_start.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func (sva *startVMAction) run() error {
sva.logger.Errorf("Dependency error: %v", err)
}

err = sva.limaConfigApplier.Apply()
err = sva.limaConfigApplier.Apply(false)
if err != nil {
return err
}
Expand Down
8 changes: 4 additions & 4 deletions cmd/finch/virtual_machine_start_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func TestStartVMAction_runAdapter(t *testing.T) {
getVMStatusC.EXPECT().Output().Return([]byte("Stopped"), nil)
logger.EXPECT().Debugf("Status of virtual machine: %s", "Stopped")

lca.EXPECT().Apply().Return(nil)
lca.EXPECT().Apply(false).Return(nil)

dm.EXPECT().EnsureUserDataDisk().Return(nil)

Expand Down Expand Up @@ -145,7 +145,7 @@ func TestStartVMAction_run(t *testing.T) {
getVMStatusC.EXPECT().Output().Return([]byte("Stopped"), nil)
logger.EXPECT().Debugf("Status of virtual machine: %s", "Stopped")

lca.EXPECT().Apply().Return(nil)
lca.EXPECT().Apply(false).Return(nil)

dm.EXPECT().EnsureUserDataDisk().Return(nil)

Expand Down Expand Up @@ -262,7 +262,7 @@ func TestStartVMAction_run(t *testing.T) {
getVMStatusC.EXPECT().Output().Return([]byte("Stopped"), nil)
logger.EXPECT().Debugf("Status of virtual machine: %s", "Stopped")

lca.EXPECT().Apply().Return(errors.New("load config fails"))
lca.EXPECT().Apply(false).Return(errors.New("load config fails"))

logger.EXPECT().Errorf("Dependency error: %v",
fmt.Errorf("failed to install dependencies: %w",
Expand Down Expand Up @@ -295,7 +295,7 @@ func TestStartVMAction_run(t *testing.T) {
getVMStatusC.EXPECT().Output().Return([]byte("Stopped"), nil)
logger.EXPECT().Debugf("Status of virtual machine: %s", "Stopped")

lca.EXPECT().Apply().Return(nil)
lca.EXPECT().Apply(false).Return(nil)

dm.EXPECT().EnsureUserDataDisk().Return(nil)

Expand Down
51 changes: 50 additions & 1 deletion e2e/vm/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"

"github.com/lima-vm/lima/pkg/limayaml"
"github.com/onsi/ginkgo/v2"
Expand All @@ -20,6 +21,8 @@ import (
"gopkg.in/yaml.v3"

"github.com/runfinch/finch/e2e"
finch_cmd "github.com/runfinch/finch/pkg/command"
"github.com/runfinch/finch/pkg/config"
)

var finchConfigFilePath = os.Getenv("HOME") + "/.finch/finch.yaml"
Expand Down Expand Up @@ -80,7 +83,7 @@ var testConfig = func(o *option.Option, installed bool) {
writeFile(limaConfigFilePath, origLimaCfg)

command.New(o, virtualMachineRootCmd, "stop").WithoutCheckingExitCode().WithTimeoutInSeconds(90).Run()
command.New(o, virtualMachineRootCmd, "start").WithTimeoutInSeconds(120).Run()
command.New(o, virtualMachineRootCmd, "start").WithTimeoutInSeconds(600).Run()
})
})

Expand Down Expand Up @@ -166,5 +169,51 @@ additional_directories:
gomega.Expect(limaCfg.Mounts[1].Location).Should(gomega.Equal("/tmp/workspace"))
gomega.Expect(limaCfg.Mounts[1].Writable).Should(gomega.Equal(pointer.Bool(true)))
})

ginkgo.It("does not update init-only config values when values are changed between start/stop", func() {
startCmdSession := updateAndApplyConfig(o, []byte("memory: 4GiB\ncpus: 6\nvmType: vz\nrosetta: true"))
gomega.Expect(startCmdSession).Should(gexec.Exit(0))

gomega.Expect(limaConfigFilePath).Should(gomega.BeARegularFile())
cfgBuf, err := os.ReadFile(filepath.Clean(limaConfigFilePath))
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())

var limaCfg limayaml.LimaYAML
err = yaml.Unmarshal(cfgBuf, &limaCfg)
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
gomega.Expect(*limaCfg.CPUs).Should(gomega.Equal(6))
gomega.Expect(*limaCfg.Memory).Should(gomega.Equal("4GiB"))
gomega.Expect(*limaCfg.VMType).Should(gomega.Equal("qemu"))
gomega.Expect(limaCfg.Rosetta.Enabled).Should(gomega.Equal(false))
gomega.Expect(limaCfg.Rosetta.BinFmt).Should(gomega.Equal(false))
})
})

ginkgo.Describe("Config (after init)", ginkgo.Serial, func() {
ginkgo.It("updates init-only config values when values are changed after init", func() {
supportsVz, supportsVzErr := config.SupportsVirtualizationFramework(finch_cmd.NewExecCmdCreator())
gomega.Expect(supportsVzErr).ShouldNot(gomega.HaveOccurred())
if !supportsVz || runtime.GOOS != "darwin" {
ginkgo.Skip("Skipping because existing init only configuration options require Virtualization.framework support to test")
}

limaConfigFilePath := resetVM(o, installed)
writeFile(finchConfigFilePath, []byte("memory: 4GiB\ncpus: 6\nvmType: vz\nrosetta: false"))
initCmdSession := command.New(o, virtualMachineRootCmd, "init").WithTimeoutInSeconds(120).Run()
gomega.Expect(initCmdSession).Should(gexec.Exit(0))

gomega.Expect(limaConfigFilePath).Should(gomega.BeARegularFile())
cfgBuf, err := os.ReadFile(filepath.Clean(limaConfigFilePath))
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())

var limaCfg limayaml.LimaYAML
err = yaml.Unmarshal(cfgBuf, &limaCfg)
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
gomega.Expect(*limaCfg.CPUs).Should(gomega.Equal(6))
gomega.Expect(*limaCfg.Memory).Should(gomega.Equal("4GiB"))
gomega.Expect(*limaCfg.VMType).Should(gomega.Equal("vz"))
gomega.Expect(limaCfg.Rosetta.Enabled).Should(gomega.Equal(false))
gomega.Expect(limaCfg.Rosetta.BinFmt).Should(gomega.Equal(false))
})
})
}
61 changes: 61 additions & 0 deletions e2e/vm/virtualization_framework_rosetta_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package vm

import (
"runtime"

"github.com/onsi/ginkgo/v2"
"github.com/onsi/gomega"
"github.com/onsi/gomega/gexec"
"github.com/runfinch/common-tests/command"
"github.com/runfinch/common-tests/option"
"github.com/runfinch/common-tests/tests"

finch_cmd "github.com/runfinch/finch/pkg/command"
"github.com/runfinch/finch/pkg/config"
)

var testVirtualizationFrameworkAndRosetta = func(o *option.Option, installed bool) {
ginkgo.Describe("Virtualization framework", ginkgo.Ordered, func() {
supportsVz, supportsVzErr := config.SupportsVirtualizationFramework(finch_cmd.NewExecCmdCreator())
gomega.Expect(supportsVzErr).ShouldNot(gomega.HaveOccurred())

ginkgo.Describe("Virtualization framework", ginkgo.Ordered, func() {
ginkgo.BeforeAll(func() {
if !supportsVz {
ginkgo.Skip("Skipping because system does not support Virtualization.framework")
}

resetVM(o, installed)
writeFile(finchConfigFilePath, []byte("memory: 4GiB\ncpus: 6\nvmType: vz\nrosetta: false"))
initCmdSession := command.New(o, virtualMachineRootCmd, "init").WithTimeoutInSeconds(180).Run()
gomega.Expect(initCmdSession).Should(gexec.Exit(0))
})

// Run sanity check tests
tests.Build(o)
tests.Run(&tests.RunOption{BaseOpt: o, CGMode: tests.Unified, DefaultHostGatewayIP: "192.168.5.2"})
tests.Port(o)
})

ginkgo.Describe("Virtualization framework and Rosetta", ginkgo.Ordered, func() {
ginkgo.BeforeAll(func() {
if !supportsVz || runtime.GOOS != "darwin" || runtime.GOARCH != "arm64" {
ginkgo.Skip("Skipping because system does not support Rosetta")
}

resetVM(o, installed)
writeFile(finchConfigFilePath, []byte("memory: 4GiB\ncpus: 6\nvmType: vz\nrosetta: true"))
initCmdSession := command.New(o, virtualMachineRootCmd, "init").WithTimeoutInSeconds(180).Run()
gomega.Expect(initCmdSession).Should(gexec.Exit(0))
})

// Run sanity check tests
tests.Build(o)
tests.Run(&tests.RunOption{BaseOpt: o, CGMode: tests.Unified, DefaultHostGatewayIP: "192.168.5.2"})
ningziwen marked this conversation as resolved.
Show resolved Hide resolved
tests.Port(o)
})
})
}
37 changes: 35 additions & 2 deletions e2e/vm/vm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@
package vm

import (
"os/exec"
"path/filepath"
"testing"

"github.com/onsi/ginkgo/v2"
"github.com/onsi/gomega"

"github.com/runfinch/common-tests/command"
"github.com/runfinch/common-tests/option"

"github.com/runfinch/finch/e2e"
)
Expand All @@ -33,17 +37,46 @@ func TestVM(t *testing.T) {
}, func(bytes []byte) {})

ginkgo.SynchronizedAfterSuite(func() {
command.New(o, "vm", "stop").WithTimeoutInSeconds(90).Run()
command.New(o, "vm", "remove").WithTimeoutInSeconds(60).Run()
command.New(o, "vm", "stop", "-f").WithTimeoutInSeconds(90).Run()
command.New(o, "vm", "remove", "-f").WithTimeoutInSeconds(60).Run()
}, func() {})

ginkgo.Describe("", func() {
testVMLifecycle(o)
testAdditionalDisk(o)
testConfig(o, *e2e.Installed)
testVersion(o)
testVirtualizationFrameworkAndRosetta(o, *e2e.Installed)
})

gomega.RegisterFailHandler(ginkgo.Fail)
ginkgo.RunSpecs(t, description)
}

var resetVM = func(o *option.Option, installed bool) string {
var limaConfigFilePath string

origFinchCfg := readFile(finchConfigFilePath)
limaConfigFilePath = defaultLimaConfigFilePath
if installed {
path, err := exec.LookPath(e2e.InstalledTestSubject)
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
realFinchPath, err := filepath.EvalSymlinks(path)
gomega.Expect(err).ShouldNot(gomega.HaveOccurred())
limaConfigFilePath = filepath.Join(realFinchPath, "../../lima/data/_config/override.yaml")
}
origLimaCfg := readFile(limaConfigFilePath)

command.New(o, virtualMachineRootCmd, "stop", "-f").WithoutCheckingExitCode().WithTimeoutInSeconds(90).Run()
command.New(o, virtualMachineRootCmd, "remove", "-f").WithoutCheckingExitCode().WithTimeoutInSeconds(90).Run()

ginkgo.DeferCleanup(func() {
writeFile(finchConfigFilePath, origFinchCfg)
writeFile(limaConfigFilePath, origLimaCfg)
command.New(o, virtualMachineRootCmd, "stop", "-f").WithoutCheckingExitCode().WithTimeoutInSeconds(90).Run()
command.New(o, virtualMachineRootCmd, "remove", "-f").WithoutCheckingExitCode().WithTimeoutInSeconds(90).Run()
command.New(o, virtualMachineRootCmd, "init").WithoutCheckingExitCode().WithTimeoutInSeconds(600).Run()
})

return limaConfigFilePath
}
14 changes: 4 additions & 10 deletions finch.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,6 @@ mounts:
# 🔵 This file: true (only for "/tmp/lima")
writable: true

# Mount type for above mounts, such as "reverse-sshfs" (from sshocker) or "9p" (EXPERIMENTAL, from QEMU’s virtio-9p-pci, aka virtfs)
ningziwen marked this conversation as resolved.
Show resolved Hide resolved
# 🟢 Builtin default: "reverse-sshfs"
mountType: reverse-sshfs

# Lima disks to attach to the instance. The disks will be accessible from inside the
# instance, labeled by name. (e.g. if the disk is named "data", it will be labeled
# "lima-data" inside the instance). The disk will be mounted inside the instance at
Expand Down Expand Up @@ -134,16 +130,14 @@ containerd:
# multiple times, e.g. when the host VM is being restarted.
# 🟢 Builtin default: null
provision:
# Install packages needed for QEMU user-mode emulation
ningziwen marked this conversation as resolved.
Show resolved Hide resolved
- mode: system
script: |
#!/bin/bash
dnf install -y --setopt=install_weak_deps=False qemu-user-static-aarch64 qemu-user-static-arm qemu-user-static-x86
- mode: boot
script: |
systemctl stop NetworkManager-wait-online.service
systemctl reset-failed NetworkManager-wait-online.service
systemctl mask NetworkManager-wait-online.service
- mode: boot
script: |
modprobe virtiofs
# # `user` is executed without the root privilege
- mode: user
script: |
Expand Down Expand Up @@ -236,4 +230,4 @@ env:
# Containerd namespace is used by the lima cidata script
# 40-install-containerd.sh. Specifically this variable is defining the
# Buildkit Workers Containerd namespace.
CONTAINERD_NAMESPACE: finch
CONTAINERD_NAMESPACE: finch
Loading