From 4f4d8e8f01531df363416f24cd477dcde640101f Mon Sep 17 00:00:00 2001 From: Jan Dubois Date: Thu, 2 Mar 2023 13:36:32 -0800 Subject: [PATCH] wip Signed-off-by: Jan Dubois --- e2e/rdctl.e2e.spec.ts | 7 ++- .../assets/specs/command-api.yaml | 9 +++- pkg/rancher-desktop/backend/lima.ts | 27 ++++++++-- .../config/__tests__/settings.spec.ts | 8 ++- pkg/rancher-desktop/config/settings.ts | 8 +++ .../__tests__/settingsValidator.spec.ts | 2 + .../main/commandServer/settingsValidator.ts | 49 ++++++++++++++++++- 7 files changed, 101 insertions(+), 9 deletions(-) diff --git a/e2e/rdctl.e2e.spec.ts b/e2e/rdctl.e2e.spec.ts index 62139fb84f2..d7342ad9578 100644 --- a/e2e/rdctl.e2e.spec.ts +++ b/e2e/rdctl.e2e.spec.ts @@ -40,6 +40,7 @@ import { CacheMode, ProtocolVersion, SecurityModel, + VMType, } from '@pkg/config/settings'; import { PathManagementStrategy } from '@pkg/integrations/pathManager'; import { ServerState } from '@pkg/main/commandServer/httpCommandServer'; @@ -708,6 +709,8 @@ test.describe('Command server', () => { ['experimental.virtual-machine.mount.9p.security-model', SecurityModel.NONE], ['experimental.virtual-machine.mount.type', MountType.NINEP], ['experimental.virtual-machine.socket-vmnet', true], + ['experimental.virtual-machine.use-rosetta', true], + ['experimental.virtual-machine.type', VMType.VZ], ['virtual-machine.memory-in-gb', 10], ['virtual-machine.number-cpus', 10], ], @@ -716,6 +719,8 @@ test.describe('Command server', () => { ], linux: [ ['experimental.virtual-machine.socket-vmnet', true], + ['experimental.virtual-machine.use-rosetta', true], + ['experimental.virtual-machine.type', VMType.VZ], ['virtual-machine.host-resolver', true], ], }; @@ -732,7 +737,7 @@ test.describe('Command server', () => { stdout: '', }); expect(stderr).toContain('Usage:'); - expect(stderr.split(/\n/).filter(line => /^\s+--/.test(line)).length).toBe(35 - unsupportedOptions.length); + expect(stderr.split(/\n/).filter(line => /^\s+--/.test(line)).length).toBe(37 - unsupportedOptions.length); }); test('complains when option value missing', async() => { diff --git a/pkg/rancher-desktop/assets/specs/command-api.yaml b/pkg/rancher-desktop/assets/specs/command-api.yaml index 368482182d7..c9c03b43c27 100644 --- a/pkg/rancher-desktop/assets/specs/command-api.yaml +++ b/pkg/rancher-desktop/assets/specs/command-api.yaml @@ -342,7 +342,7 @@ components: properties: type: type: string - enum: ['reverse-sshfs', '9p'] + enum: ['reverse-sshfs', '9p', 'virtiofs'] 9p: type: object properties: @@ -361,6 +361,13 @@ components: networkingTunnel: type: boolean x-rd-platforms: ['win32'] + type: + type: string + enum: ['qemu', 'vz'] + x-rd-platforms: ['darwin'] + useRosetta: + type: boolean + x-rd-platforms: ['darwin'] WSL: type: object x-rd-platforms: ['win32'] diff --git a/pkg/rancher-desktop/backend/lima.ts b/pkg/rancher-desktop/backend/lima.ts index 1938664132f..b55122664cc 100644 --- a/pkg/rancher-desktop/backend/lima.ts +++ b/pkg/rancher-desktop/backend/lima.ts @@ -86,6 +86,11 @@ export type LimaMount = { * Lima configuration */ export type LimaConfiguration = { + vmType?: 'qemu' | 'vz'; + rosetta?: { + enabled?: boolean; + binfmt?: boolean; + }, arch?: 'x86_64' | 'aarch64'; images: { location: string; @@ -96,7 +101,7 @@ export type LimaConfiguration = { memory?: number; disk?: number; mounts?: LimaMount[]; - mountType: string; + mountType: 'reverse-sshfs' | '9p' | 'virtiofs'; ssh: { localPort: number; loadDotSSHPubKeys?: boolean; @@ -160,6 +165,7 @@ interface LimaListResult { status: 'Broken' | 'Stopped' | 'Running'; dir: string; arch: 'x86_64' | 'aarch64'; + vmType?: 'qemu' | 'vz'; sshLocalPort?: number; hostAgentPID?: number; qemuPID?: number; @@ -617,6 +623,11 @@ export default class LimaBackend extends events.EventEmitter implements VMBacken // We use {} as the first argument because merge() modifies // it, and it would be less safe to modify baseConfig. const config: LimaConfiguration = merge({}, baseConfig, DEFAULT_CONFIG as LimaConfiguration, { + vmType: this.cfg?.experimental.virtualMachine.type, + rosetta: { + enabled: this.cfg?.experimental.virtualMachine.useRosetta, + binfmt: this.cfg?.experimental.virtualMachine.useRosetta, + }, images: [{ location: this.baseDiskImage, arch: this.arch, @@ -1705,9 +1716,17 @@ export default class LimaBackend extends events.EventEmitter implements VMBacken return; } - // Start the VM; if it's already running, this does nothing. - const isVMAlreadyRunning = (await this.status)?.status === 'Running'; + const vmStatus = await this.status; + const isVMAlreadyRunning = vmStatus?.status === 'Running'; + // Delete existing VM if the user modified the vmType. + if (vmStatus && vmStatus.vmType !== config.experimental.virtualMachine.type) { + await this.del(true); + // Restore this.cfg because `del` sets it to undefined. + this.cfg = config; + } + + // Start the VM; if it's already running, this does nothing. await this.startVM(); if (config.kubernetes.enabled) { @@ -2019,6 +2038,8 @@ CREDFWD_URL='http://${ hostIPAddr }:${ stateInfo.port }' 'experimental.virtualMachine.mount.9p.protocolVersion': undefined, 'experimental.virtualMachine.mount.9p.securityModel': undefined, 'experimental.virtualMachine.mount.type': undefined, + 'experimental.virtualMachine.useRosetta': undefined, + 'experimental.virtualMachine.type': undefined, })); if (process.platform === 'darwin') { Object.assign(reasons, this.kubeBackend.k3sHelper.requiresRestartReasons(this.cfg, cfg, { 'experimental.virtualMachine.socketVMNet': undefined })); diff --git a/pkg/rancher-desktop/config/__tests__/settings.spec.ts b/pkg/rancher-desktop/config/__tests__/settings.spec.ts index 03c174a8f3c..e99e1cfbf81 100644 --- a/pkg/rancher-desktop/config/__tests__/settings.spec.ts +++ b/pkg/rancher-desktop/config/__tests__/settings.spec.ts @@ -1,7 +1,9 @@ import fs from 'fs'; import * as settings from '../settings'; -import { CacheMode, MountType, ProtocolVersion, SecurityModel } from '../settings'; +import { + CacheMode, MountType, ProtocolVersion, SecurityModel, VMType, +} from '../settings'; import { TransientSettings } from '@pkg/config/transientSettings'; import { PathManagementStrategy } from '@pkg/integrations/pathManager'; @@ -51,8 +53,10 @@ describe('updateFromCommandLine', () => { cacheMode: CacheMode.MMAP, }, }, - socketVMNet: true, networkingTunnel: false, + socketVMNet: true, + useRosetta: false, + type: VMType.QEMU, }, }, WSL: { integrations: {} }, diff --git a/pkg/rancher-desktop/config/settings.ts b/pkg/rancher-desktop/config/settings.ts index d06fa76236c..fdb195814c1 100644 --- a/pkg/rancher-desktop/config/settings.ts +++ b/pkg/rancher-desktop/config/settings.ts @@ -25,6 +25,10 @@ const console = Logging.settings; export const CURRENT_SETTINGS_VERSION = 6 as const; +export enum VMType { + QEMU = 'qemu', + VZ = 'vz', +} export enum ContainerEngine { NONE = '', CONTAINERD = 'containerd', @@ -119,6 +123,10 @@ export const defaultSettings = { */ experimental: { virtualMachine: { + /** can only be set to VMType.VZ on macOS Ventura and later */ + type: VMType.QEMU, + /** can only be used when type is VMType.VZ, and only on aarch64 */ + useRosetta: false, /** macOS only: if set, use socket_vmnet instead of vde_vmnet. */ socketVMNet: false, mount: { diff --git a/pkg/rancher-desktop/main/commandServer/__tests__/settingsValidator.spec.ts b/pkg/rancher-desktop/main/commandServer/__tests__/settingsValidator.spec.ts index 610e2cc2c67..e628a24e136 100644 --- a/pkg/rancher-desktop/main/commandServer/__tests__/settingsValidator.spec.ts +++ b/pkg/rancher-desktop/main/commandServer/__tests__/settingsValidator.spec.ts @@ -71,6 +71,8 @@ describe(SettingsValidator, () => { ['experimental', 'virtualMachine', 'mount', '9p', 'protocolVersion'], ['experimental', 'virtualMachine', 'mount', '9p', 'securityModel'], ['experimental', 'virtualMachine', 'mount', 'type'], + ['experimental', 'virtualMachine', 'useRosetta'], + ['experimental', 'virtualMachine', 'type'], ['kubernetes', 'version'], ['version'], ['WSL', 'integrations'], diff --git a/pkg/rancher-desktop/main/commandServer/settingsValidator.ts b/pkg/rancher-desktop/main/commandServer/settingsValidator.ts index cbf507d1d2d..459d09ebfbb 100644 --- a/pkg/rancher-desktop/main/commandServer/settingsValidator.ts +++ b/pkg/rancher-desktop/main/commandServer/settingsValidator.ts @@ -3,7 +3,7 @@ import os from 'os'; import _ from 'lodash'; import { - CacheMode, defaultSettings, MountType, ProtocolVersion, SecurityModel, Settings, + CacheMode, defaultSettings, MountType, ProtocolVersion, SecurityModel, Settings, VMType, } from '@pkg/config/settings'; import { NavItemName, navItemNames, TransientSettings } from '@pkg/config/transientSettings'; import { PathManagementStrategy } from '@pkg/integrations/pathManager'; @@ -97,6 +97,11 @@ export default class SettingsValidator { }, socketVMNet: this.checkPlatform('darwin', this.checkBoolean), networkingTunnel: this.checkPlatform('win32', this.checkBoolean), + useRosetta: this.checkPlatform('darwin', this.checkRosetta), + type: this.checkPlatform('darwin', this.checkMulti( + this.checkEnum(...Object.values(VMType)), + this.checkVMType), + ), }, }, WSL: { integrations: this.checkPlatform('win32', this.checkBooleanMapping) }, @@ -188,7 +193,8 @@ export default class SettingsValidator { if (!(k in allowedSettings)) { continue; - } else if (typeof (allowedSettings[k]) === 'object') { + } + if (typeof (allowedSettings[k]) === 'object') { if (typeof (newSettings[k]) === 'object') { changeNeeded = this.checkProposedSettings(mergedSettings, allowedSettings[k], currentSettings[k], newSettings[k], errors, fqname) || changeNeeded; } else { @@ -239,6 +245,33 @@ export default class SettingsValidator { }; } + protected checkRosetta(mergedSettings: Settings, currentValue: boolean, desiredValue: boolean, errors: string[], fqname: string): boolean { + if (desiredValue) { + if (mergedSettings.experimental.virtualMachine.type !== VMType.VZ) { + errors.push(`Setting ${ fqname } can only be enabled when experimental.virtual-machine.vm-type is "${ VMType.VZ }".`); + + return false; + } + if (!Electron.app.runningUnderARM64Translation && os.arch() !== 'arm64') { + errors.push(`Setting ${ fqname } can only be enabled on aarch64 systems.`); + + return false; + } + } + + return currentValue !== desiredValue; + } + + protected checkVMType(mergedSettings: Settings, currentValue: string, desiredValue: string, errors: string[], fqname: string): boolean { + if (desiredValue === VMType.VZ && parseInt(os.release()) < 22) { + errors.push(`Setting ${ fqname } to "${ VMType.VZ }" requires macOS 13.0 (Ventura) or later.`); + + return false; + } + + return currentValue !== desiredValue; + } + protected checkPlatform(platform: NodeJS.Platform, validator: ValidatorFunc) { return (mergedSettings: Settings, currentValue: C, desiredValue: D, errors: string[], fqname: string) => { if (!_.isEqual(currentValue, desiredValue)) { @@ -267,6 +300,18 @@ export default class SettingsValidator { }; } + protected checkMulti(...validators: ValidatorFunc[]) { + return (mergedSettings: S, currentValue: C, desiredValue: D, errors: string[], fqname: string) => { + let retval = false; + + for (const validator of validators) { + retval ||= validator.call(this, mergedSettings, currentValue, desiredValue, errors, fqname); + } + + return retval; + }; + } + /** * checkBoolean is a generic checker for simple boolean values. */