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

Support for Virtualization.Framework for macOS 13 #1147

Merged
merged 1 commit into from
Nov 16, 2022

Conversation

balajiv113
Copy link
Member

@balajiv113 balajiv113 commented Nov 7, 2022

This PR provides support for using Virtualization.Framework as a optional driver.

The following are the changes done related to using drivers,

  • Driver interface
  • Migrate current QEMU implementation to driver
  • New driver vz for Virtualization.Framework

The following are the features of lima, the vz driver should provide support for below,

  • Running VM (Using both disk img and iso)*
  • Slirp network for guest to host communication (uses gvisor-tap-vsock)
  • Host to guest network (uses vz NAT gateway)*
  • Directory sharing, supports reverse-sshfs and virtiofs (newly added)
  • Port forwarding
  • Host DNS resolver
  • Display (Deferring it for now as it requires runtime.LockOsThread() to be called in the beginning of hostagent start cmd)

Notes

  • Vz driver internally converts qcow to raw image using (qemu-img convert). This is because vz only supports raw disk
  • Need to provide yaml configuration for NAT

Know Issues

  • Serial log doesn't contain system boot logs
  • Gvisor-tap-vsock will throw some errors like [e.connection](tcpproxy:) during start-up (This is because of port 22 Forwards being tried before VM is ready) [Not a failure/blocker just info message thrown, but we can look into fixing in a follow-up to call ssh forward manually]
  • When vz vm stops, it doesn't trigger hostagent stop [Fixed]

Testing
Tested the following templates with driver: vz. All test are done on macOS 13 intel as of now.

  • almalinux.yaml
  • alpine.yaml
  • apptainer.yaml
  • archlinux.yaml
  • buildkit.yaml
  • centos-stream.yaml
  • debian.yaml
  • docker.yaml
  • faasd.yaml
  • fedora.yaml
  • k3s.yaml
  • k8s.yaml
  • nomad.yaml
  • opensuse.yaml
  • oraclelinux.yaml
  • podman.yaml
  • rocky.yaml
  • ubuntu.yaml
  • vmnet.yaml

@@ -7,6 +7,7 @@
# $ docker ...

# This example requires Lima v0.8.0 or later
driver: "vz"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default must remain qemu

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can create examples/vz.yaml for the VZ example. (Maybe under experimental dir?)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes yes that make sense. Will put it under experimental directory.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -42,6 +42,8 @@ const (
HostAgentSock = "ha.sock"
HostAgentStdoutLog = "ha.stdout.log"
HostAgentStderrLog = "ha.stderr.log"
Identifier = "identifier"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please describe in https://github.com/lima-vm/lima/blob/master/docs/internal.md

Also, perhaps the filename should be something like vz-identifier?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -42,6 +42,8 @@ const (
HostAgentSock = "ha.sock"
HostAgentStdoutLog = "ha.stdout.log"
HostAgentStderrLog = "ha.stderr.log"
Identifier = "identifier"
EFI = "efi"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


err = machine.Start()

networks.StartGVisor(ctx, &networks.GVisorOpts{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks a misnomer.
This starts the network stack that originates from gvisor, but does not start gvisor (sandboxed kernel) actually

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe StartGVisorNetstack? StartNetstack? StartGvisorNet?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -7,6 +7,7 @@ import (
)

type LimaYAML struct {
Driver *Driver `yaml:"driver,omitempty" json:"driver,omitempty"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be called like VMType for consistency with MountType?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also please create a document like docs/vmtype.md or docs/vz.md to explain the usage, requirements, and limitations of vz

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't VMType kind of gives a wrong meaning (Type of VM like linux etc).

How about DriverType ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type of VM like linux etc

We only support Linux.
Eventually we may support FreeBSD (if somebody contributes), but that will be probably called "type of guest", not "type of VM"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done the VMType change only for yaml config related places.
Do let me know if you want to change the word driver everywhere (like driver interface, implementations etc).

Will add doc related things as next step.

Copy link
Member

@AkihiroSuda AkihiroSuda Nov 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, "driver" in the interface and impl is ok

}

func searchDomains() []string {
if runtime.GOOS == "darwin" || runtime.GOOS == "linux" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps just runtime.GOOS != "windows"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

logrus.Errorf("Error while stopping the VM %q", err)
}
case newState := <-machine.StateChangedNotify():
if newState == vz.VirtualMachineStateRunning {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use switch{} for newState comparisons?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}

func attachNetwork(driver *driver.BaseDriver, vmConfig *vz.VirtualMachineConfiguration, networkConn *os.File) error {
//Slirp network
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean gvisor network?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, updated the comments

return vz.NewEFIVariableStore(efi)
}

func createFileAndWriteTo(data []byte, path string) error {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

os.WriteFile may suffice

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

set -eux -o pipefail
command -v docker >/dev/null 2>&1 && exit 0
export DEBIAN_FRONTEND=noninteractive
curl -fsSL https://get.docker.com | sh
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vz.yaml should not have Docker.
You may create docker-vz.yaml separately though

Copy link
Member

@AkihiroSuda AkihiroSuda Nov 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But anyway no need to keep docker-vz.yaml separately when we have limactl start --set K=V or maybe limactl start --vm-type vz

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. Then i will keep a default example alone in vz.yaml

//Other networks
for _, network := range driver.Yaml.Networks {
//TODO - Handle NAT network type
if network.Socket != "" {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AkihiroSuda
For NAT network type, shall i go ahead with network.NAT: true/false or network.socket: NAT ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean by "NAT"?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anyway probably it should be a separate PR

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For host to guest network, the below code uses https://developer.apple.com/documentation/virtualization/vznatnetworkdeviceattachment provided from virtualization.framework itself.

This doesn't require sudo access.

Copy link
Member

@AkihiroSuda AkihiroSuda Nov 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to just use vznat by default and drop gvisor netstack ?

If vznat has to be optional, probably it should be like network.VZNAT: true/false(EDIT: see #1161 (comment)) (separate PR would be preferrable)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that should work but with vzNAT there will be other issues like VPN won't be working.

Also i faced issues with calls to host dns resolver via UDP didn't work

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, let's keep using gvisor netstack by default, and revisit VZNAT in a separate PR

//Other networks
for _, network := range driver.Yaml.Networks {
//TODO - Handle NAT network type
if network.Socket != "" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The network.Socket value does not seem consumed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I expected it could be supported via VZFileHandleNetworkDeviceAttachment , but seems incompatible with QEMU:
https://developer.apple.com/documentation/virtualization/vzfilehandlenetworkdeviceattachment?language=objc

So we have to error out when network.Socket is non-empty

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can support it, maybe some extra support might be needed from socker_vmnet as well. I will try to support this in a different PR.

case vz.VirtualMachineStateStopped:
logrus.Println("[VZ] - VM state change: stopped")
cancelHa()
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Every switch{} should have default:

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. By default doing a debug log.

if *l.Yaml.MountType == limayaml.NINEP {
return fmt.Errorf("field `mountType` must be %q or %q for VZ driver , got %q", limayaml.REVSSHFS, limayaml.VIRTIOFS, *l.Yaml.MountType)
}
return nil
Copy link
Member

@AkihiroSuda AkihiroSuda Nov 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. Will take care of this in follow-up PR

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added errors for not supported one like legacyBios. As vm will not even start with this as true.
For other things will take it up in a follow-up PR like networks, mount as 9p etc.


func getEFI(driver *driver.BaseDriver) (*vz.EFIVariableStore, error) {
efi := filepath.Join(driver.Instance.Dir, filenames.VzEfi)
if _, err := os.Open(efi); os.IsNotExist(err) {
Copy link

@Code-Hex Code-Hex Nov 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nits: I think Stat is better because if the file exists, it will not be closed until it is GC'd.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 45 to 56
vm, err := startVM(ctx, l.BaseDriver)
if err != nil {
return err
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think error handling would be required to check version compatibility. Given that lima-vm will be built with the latest OS, handling of vz.ErrBuildTargetOSVersion is unnecessary. (maybe)
https://github.com/Code-Hex/vz#version-compatibility-check

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Code-Hex
Lima will only support macOS 13 based EFILoader. Not the old ones. With that, i believe we don't need to do any check right ?
If either the driver is disabled / os version is incompatible, vz already throws a appropriate error. Am i missing something ?

Copy link

@Code-Hex Code-Hex Nov 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@balajiv113 Right. However, will be returned error might be not helpful for user side. So I meant to return an error with message which is helpful that this driver cannot use below macOS 13. (Or better logging?)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure will add that as well

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

return nil
}

func attachOtherDevices(_ *driver.BaseDriver, vmConfig *vz.VirtualMachineConfiguration) error {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it make sense to register there also NewLinuxRosettaDirectoryShare

So we have Rosetta inside VZ? https://github.com/Code-Hex/vz/blob/c436fc367107393c6eda419ff0f69903f674c118/shared_directory_arm64.go#L63

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be another PR

@balajiv113
Copy link
Member Author

Done testing with all the templates,
There are 2 open issues that we might need to address before merging,

  1. templates with legacyBoot: true is not working with vz driver. If any pointer please do suggest.
  2. kernel boot logs are not coming via serial attachment with UEFI. @Code-Hex, please do suggest if am missing something.

@AkihiroSuda
Copy link
Member

No need to support legacy BIOS (in this PR, at least).
Could you document that restriction in a documentation like docs/vmtype.md ?

Also please consider squashing commits

@Code-Hex
Copy link

@balajiv113 I believe it's correct behavior that It's started up and it does not appear kernel early messages on the serial attachment because the console is provided via virtio-pci (not UART). This is undocumented officially.

Note that Virtualization.framework provides all IO as virtio-pci, including the console (i.e. not a UART).

https://github.com/evansm7/vftool#kernelsnotes

However, as noted on the wiki, some messages are displayed.

It's not possible to capture early boot messages with it. It starts working around the time /dev is populated. (by using mdev, sdev, etc.)

https://github.com/Code-Hex/vz/wiki#linux-kernel-requirements

@AkihiroSuda
Copy link
Member

@chancez Thanks, could you open your rosetta PR after merging this base PR?

One consideration, if we're going add VM-type specific configuration (options specific to VZ or Qemu), perhaps vmType should be made into a object with values.

We already have VM-specific configs in the top-level struct, so I guess we are too late to make that change 😞

lima/examples/default.yaml

Lines 212 to 219 in 06a4b33

# Specify desired QEMU CPU type for each arch.
# You can see what options are available for host emulation with: `qemu-system-$(arch) -cpu help`.
# Setting of instructions is supported like this: "qemu64,+ssse3".
cpuType:
# 🟢 Builtin default: "cortex-a72" (or "host" when running on aarch64 host)
aarch64: null
# 🟢 Builtin default: "qemu64" (or "host" when running on x86_64 host)
x86_64: null

lima/examples/default.yaml

Lines 226 to 233 in 06a4b33

video:
# QEMU display, e.g., "none", "cocoa", "sdl", "gtk", "default".
# Choosing "none" will hide the video output, and not show any window.
# Choosing "default" will pick the first available of: gtk, sdl, cocoa.
# As of QEMU v6.2, enabling this is known to have negative impact
# on performance on macOS hosts: https://gitlab.com/qemu-project/qemu/-/issues/334
# 🟢 Builtin default: "none"
display: null

@chancez
Copy link
Contributor

chancez commented Nov 15, 2022

@chancez Thanks, could you open your rosetta PR after merging this base PR?

Yeah I'll make a PR with some adjustments to make it closer to "ready" and we can discuss the rest a bit further in that PR.

@AkihiroSuda
Copy link
Member

Sorry, needs another rebase

@balajiv113
Copy link
Member Author

Done the rebase.
Also addressed @chancez comments about QEMU log during delete instance and also updated printf with infof

AkihiroSuda
AkihiroSuda previously approved these changes Nov 15, 2022
pkg/limayaml/defaults.go Outdated Show resolved Hide resolved
docs/mount.md Outdated Show resolved Hide resolved
added support for drivers to lima, migrated exiting qemu to drivers modal and support for apple virtualization.framework as new driver

Signed-off-by: Balaji Vijayakumar <kuttibalaji.v6@gmail.com>
### Known Issues
- "vz" doesn't support `legacyBoot: true` option, so guest machine like centos-stream, archlinux, oraclelinux will not work
- Host to guest networking (`networks` section in lima yaml) is not supported
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this outrightly not supported by the driver, and with no workarounds?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expected to be added as "vznat" network in a separate PR
#1147 (comment)

@jandubois
Copy link
Member

I've been testing this PR today on an M1 machine running Ventura, and I saw this error when starting template://experimental/vz:

ERRO[0296] [hostagent] r.CreateEndpoint() = connection was refused

Which seems to originate from https://github.com/containers/gvisor-tap-vsock/blob/main/pkg/services/forwarder/tcp.go#L45.

Is this expected? There was no additional information in ha.stderr.log.

@jandubois
Copy link
Member

This is being added as an experimental feature, so the implementation can be revised later at any time, but I'd like to make sure that we are fine with the YAML property (Just vmType: "vz")

I'm fine with the vmType property, and fine with merging it now, and refining it later.

I've not had time to review the code in detail, but so far it all looks good, and most importantly: works. I've only tested on M1 so far, but should be able to get remote access to an Intel Ventura machine for testing as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Optional support for Virtualization.framework (on macOS 13)
8 participants