Add Rosetta support for Apple Silicon mac

Signed-off-by: Shion Tanaka <shtanaka@redhat.com>
This commit is contained in:
Shion Tanaka
2024-02-15 23:04:08 +09:00
parent 87bd77538e
commit fe7cc67ef4
15 changed files with 198 additions and 3 deletions

View File

@ -74,6 +74,11 @@ func inspect(cmd *cobra.Command, args []string) error {
return err return err
} }
rosetta, err := provider.GetRosetta(mc)
if err != nil {
return err
}
ii := machine.InspectInfo{ ii := machine.InspectInfo{
ConfigDir: *dirs.ConfigDir, ConfigDir: *dirs.ConfigDir,
ConnectionInfo: machine.ConnectionConfig{ ConnectionInfo: machine.ConnectionConfig{
@ -88,6 +93,7 @@ func inspect(cmd *cobra.Command, args []string) error {
State: state, State: state,
UserModeNetworking: provider.UserModeNetworkEnabled(mc), UserModeNetworking: provider.UserModeNetworkEnabled(mc),
Rootful: mc.HostUser.Rootful, Rootful: mc.HostUser.Rootful,
Rosetta: rosetta,
} }
vms = append(vms, ii) vms = append(vms, ii)

View File

@ -32,6 +32,7 @@ Print results with a Go template.
| .Name | Name of the machine | | .Name | Name of the machine |
| .Resources ... | Resources used by the machine | | .Resources ... | Resources used by the machine |
| .Rootful | Whether the machine prefers rootful or rootless container execution | | .Rootful | Whether the machine prefers rootful or rootless container execution |
| .Rosetta | Whether this machine uses Rosetta |
| .SSHConfig ... | SSH configuration info for communicating with machine | | .SSHConfig ... | SSH configuration info for communicating with machine |
| .State | Machine state | | .State | Machine state |
| .UserModeNetworking | Whether this machine uses user-mode networking | | .UserModeNetworking | Whether this machine uses user-mode networking |

View File

@ -42,6 +42,18 @@ func GetDefaultDevices(mc *vmconfigs.MachineConfig) ([]vfConfig.VirtioDevice, *d
return nil, nil, err return nil, nil, err
} }
devices = append(devices, disk, rng, serial, readyDevice) devices = append(devices, disk, rng, serial, readyDevice)
rosettaCfg := mc.AppleHypervisor.Vfkit.Rosetta
if rosettaCfg {
rosetta := &vfConfig.RosettaShare{
DirectorySharingConfig: vfConfig.DirectorySharingConfig{
MountTag: define.MountTag,
},
InstallRosetta: true,
}
devices = append(devices, rosetta)
}
return devices, readySocket, nil return devices, readySocket, nil
} }

View File

@ -124,4 +124,5 @@ type Helper struct {
Endpoint string Endpoint string
BinaryPath *define.VMFile BinaryPath *define.VMFile
VirtualMachine *config.VirtualMachine VirtualMachine *config.VirtualMachine
Rosetta bool
} }

View File

@ -4,8 +4,10 @@ package applehv
import ( import (
"fmt" "fmt"
"runtime"
"strconv" "strconv"
"github.com/containers/common/pkg/config"
gvproxy "github.com/containers/gvisor-tap-vsock/pkg/types" gvproxy "github.com/containers/gvisor-tap-vsock/pkg/types"
"github.com/containers/podman/v5/pkg/machine" "github.com/containers/podman/v5/pkg/machine"
"github.com/containers/podman/v5/pkg/machine/apple" "github.com/containers/podman/v5/pkg/machine/apple"
@ -65,6 +67,16 @@ func (a AppleHVStubber) CreateVM(opts define.CreateVMOpts, mc *vmconfigs.Machine
} }
ignBuilder.WithUnit(virtIOIgnitionMounts...) ignBuilder.WithUnit(virtIOIgnitionMounts...)
cfg, err := config.Default()
if err != nil {
return err
}
rosetta := cfg.Machine.Rosetta
if runtime.GOARCH != "arm64" {
rosetta = false
}
mc.AppleHypervisor.Vfkit.Rosetta = rosetta
return apple.ResizeDisk(mc, mc.Resources.DiskSize) return apple.ResizeDisk(mc, mc.Resources.DiskSize)
} }
@ -104,6 +116,18 @@ func (a AppleHVStubber) StartVM(mc *vmconfigs.MachineConfig) (func() error, func
return nil, nil, fmt.Errorf("unable to determine boot loader for this machine") return nil, nil, fmt.Errorf("unable to determine boot loader for this machine")
} }
cfg, err := config.Default()
if err != nil {
return nil, nil, err
}
rosetta := cfg.Machine.Rosetta
rosettaNew := rosetta
if runtime.GOARCH == "arm64" {
rosettaMC := mc.AppleHypervisor.Vfkit.Rosetta
if rosettaMC != rosettaNew {
mc.AppleHypervisor.Vfkit.Rosetta = rosettaNew
}
}
return apple.StartGenericAppleVM(mc, vfkitCommand, bl, mc.AppleHypervisor.Vfkit.Endpoint) return apple.StartGenericAppleVM(mc, vfkitCommand, bl, mc.AppleHypervisor.Vfkit.Endpoint)
} }
@ -131,3 +155,8 @@ func (a AppleHVStubber) PostStartNetworking(mc *vmconfigs.MachineConfig, noInfo
func (a AppleHVStubber) GetDisk(userInputPath string, dirs *define.MachineDirs, mc *vmconfigs.MachineConfig) error { func (a AppleHVStubber) GetDisk(userInputPath string, dirs *define.MachineDirs, mc *vmconfigs.MachineConfig) error {
return diskpull.GetDisk(userInputPath, dirs, mc.ImagePath, a.VMType(), mc.Name) return diskpull.GetDisk(userInputPath, dirs, mc.ImagePath, a.VMType(), mc.Name)
} }
func (a *AppleHVStubber) GetRosetta(mc *vmconfigs.MachineConfig) (bool, error) {
rosetta := mc.AppleHypervisor.Vfkit.Rosetta
return rosetta, nil
}

View File

@ -71,8 +71,9 @@ type SSHOptions struct {
} }
type StartOptions struct { type StartOptions struct {
NoInfo bool NoInfo bool
Quiet bool Quiet bool
Rosetta bool
} }
type StopOptions struct{} type StopOptions struct{}
@ -117,6 +118,7 @@ type InspectInfo struct {
State define.Status State define.Status
UserModeNetworking bool UserModeNetworking bool
Rootful bool Rootful bool
Rosetta bool
} }
// ImageConfig describes the bootable image for the VM // ImageConfig describes the bootable image for the VM

View File

@ -5,6 +5,10 @@ import "os"
const UserCertsTargetPath = "/etc/containers/certs.d" const UserCertsTargetPath = "/etc/containers/certs.d"
const DefaultIdentityName = "machine" const DefaultIdentityName = "machine"
// MountTag is an identifier to mount a VirtioFS file system tag on a mount point in the VM.
// Ref: https://developer.apple.com/documentation/virtualization/running_intel_binaries_in_linux_vms_with_rosetta
const MountTag = "rosetta"
var ( var (
DefaultFilePerm os.FileMode = 0644 DefaultFilePerm os.FileMode = 0644
) )

View File

@ -370,6 +370,100 @@ var _ = Describe("podman machine init", func() {
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Expect(inspectShouldPass).To(Exit(0)) Expect(inspectShouldPass).To(Exit(0))
}) })
It("machine init with rosetta=true", func() {
skipIfVmtype(define.QemuVirt, "Test is only for AppleHv")
skipIfVmtype(define.WSLVirt, "Test is only for AppleHv")
skipIfVmtype(define.HyperVVirt, "Test is only for AppleHv")
skipIfVmtype(define.LibKrun, "Test is only for AppleHv")
if runtime.GOARCH != "arm64" {
Skip("Test is only for AppleHv with arm64 architecture")
}
i := initMachine{}
name := randomString()
session, err := mb.setName(name).setCmd(i.withImage(mb.imagePath)).run()
Expect(err).ToNot(HaveOccurred())
Expect(session).To(Exit(0))
s := startMachine{}
ssession, err := mb.setCmd(s).setTimeout(time.Minute * 10).run()
Expect(err).ToNot(HaveOccurred())
Expect(ssession).Should(Exit(0))
inspect := new(inspectMachine)
inspect = inspect.withFormat("{{.Rosetta}}")
inspectSession, err := mb.setName(name).setCmd(inspect).run()
Expect(err).ToNot(HaveOccurred())
Expect(inspectSession).To(Exit(0))
Expect(inspectSession.outputToString()).To(Equal("true"))
mnt := sshMachine{}
mntSession, err := mb.setName(name).setCmd(mnt.withSSHCommand([]string{"ls -d /mnt/rosetta"})).run()
Expect(err).ToNot(HaveOccurred())
Expect(mntSession).To(Exit(0))
Expect(mntSession.outputToString()).To(ContainSubstring("/mnt/rosetta"))
proc := sshMachine{}
procSession, err := mb.setName(name).setCmd(proc.withSSHCommand([]string{"ls -d /proc/sys/fs/binfmt_misc/rosetta"})).run()
Expect(err).ToNot(HaveOccurred())
Expect(procSession).To(Exit(0))
Expect(procSession.outputToString()).To(ContainSubstring("/proc/sys/fs/binfmt_misc/rosetta"))
proc2 := sshMachine{}
proc2Session, err := mb.setName(name).setCmd(proc2.withSSHCommand([]string{"ls -d /proc/sys/fs/binfmt_misc/qemu-x86_64"})).run()
Expect(err).ToNot(HaveOccurred())
Expect(proc2Session.ExitCode()).To(Equal(2))
})
It("machine init with rosetta=false", func() {
skipIfVmtype(define.QemuVirt, "Test is only for AppleHv")
skipIfVmtype(define.WSLVirt, "Test is only for AppleHv")
skipIfVmtype(define.HyperVVirt, "Test is only for AppleHv")
skipIfVmtype(define.LibKrun, "Test is only for AppleHv")
if runtime.GOARCH != "arm64" {
Skip("Test is only for AppleHv with arm64 architecture")
}
configDir := filepath.Join(testDir, ".config", "containers")
err := os.MkdirAll(configDir, 0755)
Expect(err).ToNot(HaveOccurred())
err = os.WriteFile(filepath.Join(configDir, "containers.conf"), rosettaConfig, 0644)
Expect(err).ToNot(HaveOccurred())
i := initMachine{}
name := randomString()
session, err := mb.setName(name).setCmd(i.withImage(mb.imagePath)).run()
Expect(err).ToNot(HaveOccurred())
Expect(session).To(Exit(0))
s := startMachine{}
ssession, err := mb.setCmd(s).setTimeout(time.Minute * 10).run()
Expect(err).ToNot(HaveOccurred())
Expect(ssession).Should(Exit(0))
inspect := new(inspectMachine)
inspect = inspect.withFormat("{{.Rosetta}}")
inspectSession, err := mb.setName(name).setCmd(inspect).run()
Expect(err).ToNot(HaveOccurred())
Expect(inspectSession).To(Exit(0))
Expect(inspectSession.outputToString()).To(Equal("false"))
mnt := sshMachine{}
mntSession, err := mb.setName(name).setCmd(mnt.withSSHCommand([]string{"ls -d /mnt/rosetta"})).run()
Expect(err).ToNot(HaveOccurred())
Expect(mntSession.ExitCode()).To(Equal(2))
proc := sshMachine{}
procSession, err := mb.setName(name).setCmd(proc.withSSHCommand([]string{"ls -d /proc/sys/fs/binfmt_misc/rosetta"})).run()
Expect(err).ToNot(HaveOccurred())
Expect(procSession.ExitCode()).To(Equal(2))
proc2 := sshMachine{}
proc2Session, err := mb.setName(name).setCmd(proc2.withSSHCommand([]string{"ls -d /proc/sys/fs/binfmt_misc/qemu-x86_64"})).run()
Expect(err).ToNot(HaveOccurred())
Expect(proc2Session.outputToString()).To(ContainSubstring("/proc/sys/fs/binfmt_misc/qemu-x86_64"))
})
}) })
var p4Config = []byte(`{ var p4Config = []byte(`{
@ -455,3 +549,8 @@ var p4Config = []byte(`{
"LastUp": "0001-01-01T00:00:00Z" "LastUp": "0001-01-01T00:00:00Z"
} }
`) `)
var rosettaConfig = []byte(`
[machine]
rosetta=false
`)

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"runtime" "runtime"
"testing" "testing"
@ -62,7 +63,13 @@ var _ = BeforeSuite(func() {
if pullError != nil { if pullError != nil {
Fail(fmt.Sprintf("failed to pull wsl disk: %q", pullError)) Fail(fmt.Sprintf("failed to pull wsl disk: %q", pullError))
} }
if testProvider.VMType() == define.AppleHvVirt {
cmd := exec.Command("softwareupdate", "--install-rosetta", "--agree-to-license")
err := cmd.Run()
if err != nil {
Fail(fmt.Sprintf("Command failed with error: %q", err))
}
}
}) })
var _ = SynchronizedAfterSuite(func() {}, func() {}) var _ = SynchronizedAfterSuite(func() {}, func() {})

View File

@ -557,3 +557,7 @@ func createNetworkUnit(netPort uint64) (string, error) {
netUnit.Add("Install", "WantedBy", "multi-user.target") netUnit.Add("Install", "WantedBy", "multi-user.target")
return netUnit.ToString() return netUnit.ToString()
} }
func (h HyperVStubber) GetRosetta(mc *vmconfigs.MachineConfig) (bool, error) {
return false, nil
}

View File

@ -10,6 +10,7 @@ import (
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
"runtime"
"github.com/containers/podman/v5/pkg/machine/define" "github.com/containers/podman/v5/pkg/machine/define"
"github.com/containers/podman/v5/pkg/systemd/parser" "github.com/containers/podman/v5/pkg/systemd/parser"
@ -65,6 +66,7 @@ type DynamicIgnition struct {
Cfg Config Cfg Config
Rootful bool Rootful bool
NetRecover bool NetRecover bool
Rosetta bool
} }
func (ign *DynamicIgnition) Write() error { func (ign *DynamicIgnition) Write() error {
@ -239,6 +241,19 @@ func (ign *DynamicIgnition) GenerateIgnitionConfig() error {
ignSystemd.Units = append(ignSystemd.Units, qemuUnit) ignSystemd.Units = append(ignSystemd.Units, qemuUnit)
} }
// Only AppleHv with Apple Silicon can use Rosetta
if ign.VMType == define.AppleHvVirt && runtime.GOARCH == "arm64" {
rosettaUnit := Systemd{
Units: []Unit{
{
Enabled: BoolToPtr(true),
Name: "rosetta-activation.service",
},
},
}
ignSystemd.Units = append(ignSystemd.Units, rosettaUnit.Units...)
}
// Only after all checks are done // Only after all checks are done
// it's ready create the ingConfig // it's ready create the ingConfig
ign.Cfg = Config{ ign.Cfg = Config{

View File

@ -139,3 +139,7 @@ func (l LibKrunStubber) RequireExclusiveActive() bool {
func (l LibKrunStubber) UpdateSSHPort(mc *vmconfigs.MachineConfig, port int) error { func (l LibKrunStubber) UpdateSSHPort(mc *vmconfigs.MachineConfig, port int) error {
return nil return nil
} }
func (l LibKrunStubber) GetRosetta(mc *vmconfigs.MachineConfig) (bool, error) {
return false, nil
}

View File

@ -360,3 +360,7 @@ func (q *QEMUStubber) UpdateSSHPort(mc *vmconfigs.MachineConfig, port int) error
func (q *QEMUStubber) GetDisk(userInputPath string, dirs *define.MachineDirs, mc *vmconfigs.MachineConfig) error { func (q *QEMUStubber) GetDisk(userInputPath string, dirs *define.MachineDirs, mc *vmconfigs.MachineConfig) error {
return diskpull.GetDisk(userInputPath, dirs, mc.ImagePath, q.VMType(), mc.Name) return diskpull.GetDisk(userInputPath, dirs, mc.ImagePath, q.VMType(), mc.Name)
} }
func (q *QEMUStubber) GetRosetta(mc *vmconfigs.MachineConfig) (bool, error) {
return false, nil
}

View File

@ -51,6 +51,8 @@ type MachineConfig struct {
// Starting is defined as "on" but not fully booted // Starting is defined as "on" but not fully booted
Starting bool Starting bool
Rosetta bool
} }
type machineImage interface { //nolint:unused type machineImage interface { //nolint:unused
@ -99,6 +101,7 @@ type VMProvider interface { //nolint:interfacebloat
UseProviderNetworkSetup() bool UseProviderNetworkSetup() bool
RequireExclusiveActive() bool RequireExclusiveActive() bool
UpdateSSHPort(mc *MachineConfig, port int) error UpdateSSHPort(mc *MachineConfig, port int) error
GetRosetta(mc *MachineConfig) (bool, error)
} }
// HostUser describes the host user // HostUser describes the host user

View File

@ -342,3 +342,7 @@ func (w WSLStubber) GetDisk(userInputPath string, dirs *define.MachineDirs, mc *
// pull if needed and decompress to image location // pull if needed and decompress to image location
return myDisk.Get() return myDisk.Get()
} }
func (w WSLStubber) GetRosetta(mc *vmconfigs.MachineConfig) (bool, error) {
return false, nil
}