From 39cc8c3acdbb15a25fd91a2b6989634dbb940f26 Mon Sep 17 00:00:00 2001 From: Luca Stocchi Date: Fri, 4 Oct 2024 15:39:36 +0200 Subject: [PATCH] rosetta: add option to ignore rosetta failure when installation cancelled this adds a ignore-if-missing option that allows vfkit to not fail if the rosetta installation is cancelled by the user. So far, if the rosetta installation was cancelled vfkit exited with an error. This behavior still exists if ignore-if-missing option is not set. This will be used by podman bc there could be a fallback and rosetta could also be not mandatory https://github.com/containers/podman/issues/23153 --- pkg/config/virtio.go | 8 ++- pkg/vf/rosetta_arm64.go | 20 +++++-- pkg/vf/rosetta_arm64_test.go | 111 +++++++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+), 6 deletions(-) create mode 100644 pkg/vf/rosetta_arm64_test.go diff --git a/pkg/config/virtio.go b/pkg/config/virtio.go index 40c5121e..8fa85599 100644 --- a/pkg/config/virtio.go +++ b/pkg/config/virtio.go @@ -75,7 +75,8 @@ type VirtioFs struct { // RosettaShare configures rosetta support in the guest to run Intel binaries on Apple CPUs type RosettaShare struct { DirectorySharingConfig - InstallRosetta bool `json:"installRosetta"` + InstallRosetta bool `json:"installRosetta"` + IgnoreIfMissing bool `json:"ignoreIfMissing"` } // NVMExpressController configures a NVMe controller in the guest @@ -639,6 +640,9 @@ func (dev *RosettaShare) ToCmdLine() ([]string, error) { if dev.InstallRosetta { builder.WriteString(",install") } + if dev.IgnoreIfMissing { + builder.WriteString(",ignore-if-missing") + } return []string{"--device", builder.String()}, nil } @@ -650,6 +654,8 @@ func (dev *RosettaShare) FromOptions(options []option) error { dev.MountTag = option.value case "install": dev.InstallRosetta = true + case "ignore-if-missing": + dev.IgnoreIfMissing = true default: return fmt.Errorf("unknown option for rosetta share: %s", option.key) } diff --git a/pkg/vf/rosetta_arm64.go b/pkg/vf/rosetta_arm64.go index a21c6868..bc324fc3 100644 --- a/pkg/vf/rosetta_arm64.go +++ b/pkg/vf/rosetta_arm64.go @@ -2,22 +2,32 @@ package vf import ( "fmt" + "strings" "github.com/Code-Hex/vz/v3" log "github.com/sirupsen/logrus" ) -func checkRosettaAvailability(install bool) error { - availability := vz.LinuxRosettaDirectoryShareAvailability() +var ( + checkRosettaDirectoryShareAvailability = vz.LinuxRosettaDirectoryShareAvailability + doInstallRosetta = vz.LinuxRosettaDirectoryShareInstallRosetta +) + +func (dev *RosettaShare) checkRosettaAvailability() error { + availability := checkRosettaDirectoryShareAvailability() switch availability { case vz.LinuxRosettaAvailabilityNotSupported: return fmt.Errorf("rosetta is not supported") case vz.LinuxRosettaAvailabilityNotInstalled: - if !install { + if !dev.InstallRosetta { return fmt.Errorf("rosetta is not installed") } log.Debugf("installing rosetta") - if err := vz.LinuxRosettaDirectoryShareInstallRosetta(); err != nil { + if err := doInstallRosetta(); err != nil { + if dev.IgnoreIfMissing && strings.Contains(err.Error(), fmt.Sprintf("VZErrorDomain Code=%d", vz.ErrorOperationCancelled)) { + log.Debugf("user cancelled rosetta installation but ignoreIfMissing option is true") + return nil + } return fmt.Errorf("failed to install rosetta: %w", err) } log.Debugf("rosetta installed") @@ -32,7 +42,7 @@ func (dev *RosettaShare) toVz() (vz.DirectorySharingDeviceConfiguration, error) if dev.MountTag == "" { return nil, fmt.Errorf("missing mandatory 'mountTage' option for rosetta share") } - if err := checkRosettaAvailability(dev.InstallRosetta); err != nil { + if err := dev.checkRosettaAvailability(); err != nil { return nil, err } diff --git a/pkg/vf/rosetta_arm64_test.go b/pkg/vf/rosetta_arm64_test.go new file mode 100644 index 00000000..193c919a --- /dev/null +++ b/pkg/vf/rosetta_arm64_test.go @@ -0,0 +1,111 @@ +package vf + +import ( + "fmt" + "testing" + + "github.com/Code-Hex/vz/v3" + "github.com/crc-org/vfkit/pkg/config" + "github.com/stretchr/testify/require" +) + +type checkRosettaAvailabilityTest struct { + installRosetta bool + ignoreIfMissing bool + checkRosettaDirectoryShareAvailability func() vz.LinuxRosettaAvailability + doInstallRosetta func() error + errorValue string +} + +var checkRosettaAvailabilityTests = map[string]checkRosettaAvailabilityTest{ + "TestRosettaIsNotSupported": { + checkRosettaDirectoryShareAvailability: func() vz.LinuxRosettaAvailability { + return vz.LinuxRosettaAvailabilityNotSupported + }, + errorValue: "rosetta is not supported", + }, + "TestRosettaInstalled": { + checkRosettaDirectoryShareAvailability: func() vz.LinuxRosettaAvailability { + return vz.LinuxRosettaAvailabilityInstalled + }, + }, + "TestRosettaNotInstalled-NotToBeInstalled": { + installRosetta: false, + checkRosettaDirectoryShareAvailability: func() vz.LinuxRosettaAvailability { + return vz.LinuxRosettaAvailabilityNotInstalled + }, + errorValue: "rosetta is not installed", + }, + "TestRosettaNotInstalled-InstallationCancelledButIgnoreIfMissingFalse": { + installRosetta: true, + ignoreIfMissing: false, + checkRosettaDirectoryShareAvailability: func() vz.LinuxRosettaAvailability { + return vz.LinuxRosettaAvailabilityNotInstalled + }, + doInstallRosetta: func() error { + return fmt.Errorf("VZErrorDomain Code=%d", vz.ErrorOperationCancelled) + }, + errorValue: fmt.Sprintf("failed to install rosetta: VZErrorDomain Code=%d", vz.ErrorOperationCancelled), + }, + "TestRosettaNotInstalled-InstallationCancelledButIgnoreIfMissingTrue": { + installRosetta: true, + ignoreIfMissing: true, + checkRosettaDirectoryShareAvailability: func() vz.LinuxRosettaAvailability { + return vz.LinuxRosettaAvailabilityNotInstalled + }, + doInstallRosetta: func() error { + return fmt.Errorf("VZErrorDomain Code=%d", vz.ErrorOperationCancelled) + }, + }, + "TestRosettaNotInstalled-InstallationFailed": { + installRosetta: true, + ignoreIfMissing: true, + checkRosettaDirectoryShareAvailability: func() vz.LinuxRosettaAvailability { + return vz.LinuxRosettaAvailabilityNotInstalled + }, + doInstallRosetta: func() error { + return fmt.Errorf("VZErrorDomain Code=%d", vz.ErrorInstallationFailed) + }, + errorValue: fmt.Sprintf("failed to install rosetta: VZErrorDomain Code=%d", vz.ErrorInstallationFailed), + }, +} + +func TestCheckRosettaAvailability(t *testing.T) { + t.Run("name", func(t *testing.T) { + for name := range checkRosettaAvailabilityTests { + t.Run(name, func(t *testing.T) { + test := checkRosettaAvailabilityTests[name] + testCheckRosettaAvailability(t, &test) + }) + } + }) +} + +func testCheckRosettaAvailability(t *testing.T, test *checkRosettaAvailabilityTest) { + rosetta := + RosettaShare{ + InstallRosetta: test.installRosetta, + IgnoreIfMissing: test.ignoreIfMissing, + DirectorySharingConfig: config.DirectorySharingConfig{ + MountTag: "mount", + }, + } + + origCheckRosettaDirectoryShareAvailability := checkRosettaDirectoryShareAvailability + checkRosettaDirectoryShareAvailability = test.checkRosettaDirectoryShareAvailability + origDoInstallRosetta := doInstallRosetta + doInstallRosetta = test.doInstallRosetta + defer func() { + checkRosettaDirectoryShareAvailability = origCheckRosettaDirectoryShareAvailability + doInstallRosetta = origDoInstallRosetta + }() + + err := rosetta.checkRosettaAvailability() + + if test.errorValue != "" { + require.Error(t, err) + require.ErrorContains(t, err, test.errorValue) + } else { + require.NoError(t, err) + } +}