Skip to content

Add virtfs/9p mounts, instead of sshocker/sshfs #726

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

Merged
merged 2 commits into from
Apr 8, 2022
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
7 changes: 7 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,13 @@ jobs:
retry_on: error
max_attempts: 3
command: ./hack/test-example.sh examples/alpine.yaml
- name: "Test experimental-9p.yaml"
uses: nick-invision/retry@v2
with:
timeout_minutes: 30
retry_on: error
max_attempts: 3
command: ./hack/test-example.sh examples/experimental-9p.yaml
- name: "Test vmnet.yaml"
uses: nick-invision/retry@v2
with:
Expand Down
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,6 @@ The current default spec:
- Performance optimization
- More guest distros
- Windows hosts
- [VirtFS to replace the current reverse sshfs (work has to be done on QEMU repo)](https://github.com/NixOS/nixpkgs/pull/122420)
- [vsock](https://github.com/apple/darwin-xnu/blob/xnu-7195.81.3/bsd/man/man4/vsock.4) to replace SSH (work has to be done on QEMU repo)

## FAQs & Troubleshooting
Expand Down
1 change: 1 addition & 0 deletions docs/internal.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ The volume label is "cidata", as defined by [cloud-init NoCloud](https://cloudin
- `LIMA_CIDATA_UID`: the numeric UID
- `LIMA_CIDATA_MOUNTS`: the number of the Lima mounts
- `LIMA_CIDATA_MOUNTS_%d_MOUNTPOINT`: the N-th mount point of Lima mounts (N=0, 1, ...)
- `LIMA_CIDATA_MOUNTTYPE`: the type of the Lima mounts ("reverse-sshfs", "9p", ...)
- `LIMA_CIDATA_CONTAINERD_USER`: set to "1" if rootless containerd to be set up
- `LIMA_CIDATA_CONTAINERD_SYSTEM`: set to "1" if system-wide containerd to be set up
- `LIMA_CIDATA_SLIRP_GATEWAY`: set to the IP address of the host on the SLIRP network. `192.168.5.2`.
Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Container orchestration:
Others:
- [`vmnet.yaml`](./vmnet.yaml): ⭐enable [`vmnet.framework`](../docs/network.md)
- [`deprecated/centos-7.yaml`](./deprecated/centos-7.yaml): [deprecated] CentOS 7
- [`experimental-9p.yaml`](experimental-9p.yaml): use 9p mount type

## Tier 1

Expand Down
17 changes: 17 additions & 0 deletions examples/default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,28 @@ mounts:
# system will look and feel like regular files directories in the Guest OS.
# 🟢 Builtin default: false
followSymlinks: null
9p:
# Supported security models are "passthrough", "mapped-xattr", "mapped-file" and "none".
# 🟢 Builtin default: "mapped-xattr"
securityModel: null
# Select 9P protocol version. Valid options are: "9p2000" (legacy), "9p2000.u", "9p2000.L".
# 🟢 Builtin default: "9p2000.L"
protocolVersion: null
# The number of bytes to use for 9p packet payload, where 4KiB is the absolute minimum.
# 🟢 Builtin default: "128KiB"
msize: null
# Specifies a caching policy. Valid options are: "none", "loose", "fscache" and "mmap".
# 🟢 Builtin default: "mmap"
cache: null
- location: "/tmp/lima"
# 🟢 Builtin default: false
# 🔵 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)
# 🟢 Builtin default: "reverse-sshfs"
mountType: null

ssh:
# A localhost port of the host. Forwarded to port 22 of the guest.
# 🟢 Builtin default: 0 (automatically assigned to a free port)
Expand Down
23 changes: 23 additions & 0 deletions examples/experimental-9p.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# This example requires Lima v0.10.0 or later.
# On macOS hosts, QEMU needs to be v7.0.0 or later. Homebrew's QEMU v6.2.0_1 can be used too.
# This example is planned to be merged to default.yaml in Lima v1.0 (ETA: 2022 Q2).
images:
# Try to use release-yyyyMMdd image if available. Note that release-yyyyMMdd will be removed after several months.
- location: "https://cloud-images.ubuntu.com/releases/21.10/release-20220201/ubuntu-21.10-server-cloudimg-amd64.img"
arch: "x86_64"
digest: "sha256:73fe1785c60edeb506f191affff0440abcc2de02420bb70865d51d0ff9b28223"
- location: "https://cloud-images.ubuntu.com/releases/21.10/release-20220201/ubuntu-21.10-server-cloudimg-arm64.img"
arch: "aarch64"
digest: "sha256:1b5b3fe616e1eea4176049d434a360344a7d471f799e151190f21b0a27f0b424"
# Fallback to the latest release image.
# Hint: run `limactl prune` to invalidate the cache
- location: "https://cloud-images.ubuntu.com/releases/21.10/release/ubuntu-21.10-server-cloudimg-amd64.img"
arch: "x86_64"
- location: "https://cloud-images.ubuntu.com/releases/21.10/release/ubuntu-21.10-server-cloudimg-arm64.img"
arch: "aarch64"

mounts:
- location: "~"
- location: "/tmp/lima"
writable: true
mountType: "9p"
20 changes: 11 additions & 9 deletions pkg/cidata/cidata.TEMPLATE.d/boot/25-guestagent-base.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@

set -eux

# Create mount points
# NOTE: Busybox sh does not support `for ((i=0;i<$N;i++))` form
for f in $(seq 0 $((LIMA_CIDATA_MOUNTS - 1))); do
mountpointvar="LIMA_CIDATA_MOUNTS_${f}_MOUNTPOINT"
mountpoint="$(eval echo \$"$mountpointvar")"
mkdir -p "${mountpoint}"
gid=$(id -g "${LIMA_CIDATA_USER}")
chown "${LIMA_CIDATA_UID}:${gid}" "${mountpoint}"
done
if [ "${LIMA_CIDATA_MOUNTTYPE}" != "9p" ]; then
# Create mount points
# NOTE: Busybox sh does not support `for ((i=0;i<$N;i++))` form
for f in $(seq 0 $((LIMA_CIDATA_MOUNTS - 1))); do
mountpointvar="LIMA_CIDATA_MOUNTS_${f}_MOUNTPOINT"
mountpoint="$(eval echo \$"$mountpointvar")"
mkdir -p "${mountpoint}"
gid=$(id -g "${LIMA_CIDATA_USER}")
chown "${LIMA_CIDATA_UID}:${gid}" "${mountpoint}"
done
fi

# Install or update the guestagent binary
install -m 755 "${LIMA_CIDATA_MNT}"/lima-guestagent /usr/local/bin/lima-guestagent
Expand Down
34 changes: 23 additions & 11 deletions pkg/cidata/cidata.TEMPLATE.d/boot/30-install-packages.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ fi
# Install minimum dependencies
if command -v apt-get >/dev/null 2>&1; then
pkgs=""
if [ "${LIMA_CIDATA_MOUNTS}" -gt 0 ] && ! command -v sshfs >/dev/null 2>&1; then
pkgs="${pkgs} sshfs"
if [ "${LIMA_CIDATA_MOUNTTYPE}" = "reverse-sshfs" ]; then
if [ "${LIMA_CIDATA_MOUNTS}" -gt 0 ] && ! command -v sshfs >/dev/null 2>&1; then
pkgs="${pkgs} sshfs"
fi
fi
if [ "${INSTALL_IPTABLES}" = 1 ] && [ ! -e /usr/sbin/iptables ]; then
pkgs="${pkgs} iptables"
Expand All @@ -42,8 +44,10 @@ elif command -v dnf >/dev/null 2>&1; then
if ! command -v tar >/dev/null 2>&1; then
pkgs="${pkgs} tar"
fi
if [ "${LIMA_CIDATA_MOUNTS}" -gt 0 ] && ! command -v sshfs >/dev/null 2>&1; then
pkgs="${pkgs} fuse-sshfs"
if [ "${LIMA_CIDATA_MOUNTTYPE}" = "reverse-sshfs" ]; then
if [ "${LIMA_CIDATA_MOUNTS}" -gt 0 ] && ! command -v sshfs >/dev/null 2>&1; then
pkgs="${pkgs} fuse-sshfs"
fi
fi
if [ "${INSTALL_IPTABLES}" = 1 ] && [ ! -e /usr/sbin/iptables ]; then
pkgs="${pkgs} iptables"
Expand Down Expand Up @@ -102,8 +106,10 @@ elif command -v yum >/dev/null 2>&1; then
fi
elif command -v pacman >/dev/null 2>&1; then
pkgs=""
if [ "${LIMA_CIDATA_MOUNTS}" -gt 0 ] && ! command -v sshfs >/dev/null 2>&1; then
pkgs="${pkgs} sshfs"
if [ "${LIMA_CIDATA_MOUNTTYPE}" = "reverse-sshfs" ]; then
if [ "${LIMA_CIDATA_MOUNTS}" -gt 0 ] && ! command -v sshfs >/dev/null 2>&1; then
pkgs="${pkgs} sshfs"
fi
fi
# other dependencies are preinstalled on Arch Linux
if [ -n "${pkgs}" ]; then
Expand All @@ -112,8 +118,10 @@ elif command -v pacman >/dev/null 2>&1; then
fi
elif command -v zypper >/dev/null 2>&1; then
pkgs=""
if [ "${LIMA_CIDATA_MOUNTS}" -gt 0 ] && ! command -v sshfs >/dev/null 2>&1; then
pkgs="${pkgs} sshfs"
if [ "${LIMA_CIDATA_MOUNTTYPE}" = "reverse-sshfs" ]; then
if [ "${LIMA_CIDATA_MOUNTS}" -gt 0 ] && ! command -v sshfs >/dev/null 2>&1; then
pkgs="${pkgs} sshfs"
fi
fi
if [ "${INSTALL_IPTABLES}" = 1 ] && [ ! -e /usr/sbin/iptables ]; then
pkgs="${pkgs} iptables"
Expand All @@ -127,8 +135,10 @@ elif command -v zypper >/dev/null 2>&1; then
fi
elif command -v apk >/dev/null 2>&1; then
pkgs=""
if [ "${LIMA_CIDATA_MOUNTS}" -gt 0 ] && ! command -v sshfs >/dev/null 2>&1; then
pkgs="${pkgs} sshfs"
if [ "${LIMA_CIDATA_MOUNTTYPE}" = "reverse-sshfs" ]; then
if [ "${LIMA_CIDATA_MOUNTS}" -gt 0 ] && ! command -v sshfs >/dev/null 2>&1; then
pkgs="${pkgs} sshfs"
fi
fi
if [ "${INSTALL_IPTABLES}" = 1 ] && ! command -v iptables >/dev/null 2>&1; then
pkgs="${pkgs} iptables"
Expand All @@ -154,4 +164,6 @@ fi

# update_fuse_conf has to be called after installing all the packages,
# otherwise apt-get fails with conflict
update_fuse_conf
if [ "${LIMA_CIDATA_MOUNTTYPE}" = "reverse-sshfs" ]; then
update_fuse_conf
fi
3 changes: 2 additions & 1 deletion pkg/cidata/cidata.TEMPLATE.d/lima.env
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ LIMA_CIDATA_USER={{ .User }}
LIMA_CIDATA_UID={{ .UID }}
LIMA_CIDATA_MOUNTS={{ len .Mounts }}
{{- range $i, $val := .Mounts}}
LIMA_CIDATA_MOUNTS_{{$i}}_MOUNTPOINT={{$val}}
LIMA_CIDATA_MOUNTS_{{$i}}_MOUNTPOINT={{$val.Target}}
{{- end}}
LIMA_CIDATA_MOUNTTYPE={{ .MountType }}
{{- if .Containerd.User}}
LIMA_CIDATA_CONTAINERD_USER=1
{{- else}}
Expand Down
9 changes: 9 additions & 0 deletions pkg/cidata/cidata.TEMPLATE.d/user-data
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ growpart:
mode: auto
devices: ['/']

{{- if eq .MountType "9p" }}
{{- if .Mounts }}
mounts:
{{- range $m := $.Mounts}}
- [{{$m.Tag}}, {{$m.Target}}, {{$m.Type}}, "{{$m.Options}}", "0", "0"]
{{- end }}
{{- end }}
{{- end }}

users:
- name: "{{.User}}"
uid: "{{.UID}}"
Expand Down
37 changes: 35 additions & 2 deletions pkg/cidata/cidata.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"strings"
"time"

"github.com/docker/go-units"
"github.com/lima-vm/lima/pkg/iso9660util"
"github.com/lima-vm/lima/pkg/limayaml"
"github.com/lima-vm/lima/pkg/localpathutil"
Expand Down Expand Up @@ -120,12 +121,44 @@ func GenerateISO9660(instDir, name string, y *limayaml.LimaYAML, udpDNSLocalPort
args.SSHPubKeys = append(args.SSHPubKeys, f.Content)
}

for _, f := range y.Mounts {
var fstype string
switch *y.MountType {
case limayaml.REVSSHFS:
fstype = "sshfs"
case limayaml.NINEP:
fstype = "9p"
}
for i, f := range y.Mounts {
tag := fmt.Sprintf("mount%d", i)
expanded, err := localpathutil.Expand(f.Location)
if err != nil {
return err
}
args.Mounts = append(args.Mounts, expanded)
options := "defaults"
if fstype == "9p" {
options = "ro"
if *f.Writable {
options = "rw"
}
options += ",trans=virtio"
options += fmt.Sprintf(",version=%s", *f.NineP.ProtocolVersion)
msize, err := units.RAMInBytes(*f.NineP.Msize)
if err != nil {
return fmt.Errorf("failed to parse msize for %q: %w", expanded, err)
}
options += fmt.Sprintf(",msize=%d", msize)
options += fmt.Sprintf(",cache=%s", *f.NineP.Cache)
// don't fail the boot, if virtfs is not available
options += ",nofail"
}
args.Mounts = append(args.Mounts, Mount{Tag: tag, Target: expanded, Type: fstype, Options: options})
}

switch *y.MountType {
case limayaml.REVSSHFS:
args.MountType = "reverse-sshfs"
case limayaml.NINEP:
args.MountType = "9p"
}

slirpMACAddress := limayaml.MACAddress(instDir)
Expand Down
12 changes: 10 additions & 2 deletions pkg/cidata/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,20 @@ type Network struct {
MACAddress string
Interface string
}
type Mount struct {
Tag string
Target string // abs path, accessible by the User
Type string
Options string
}
type TemplateArgs struct {
Name string // instance name
IID string // instance id
User string // user name
UID int
SSHPubKeys []string
Mounts []string // abs path, accessible by the User
Mounts []Mount
MountType string
Containerd Containerd
Networks []Network
SlirpNICName string
Expand Down Expand Up @@ -62,7 +69,8 @@ func ValidateTemplateArgs(args TemplateArgs) error {
if len(args.SSHPubKeys) == 0 {
return errors.New("field SSHPubKeys must be set")
}
for i, f := range args.Mounts {
for i, m := range args.Mounts {
f := m.Target
if !filepath.IsAbs(f) {
return fmt.Errorf("field mounts[%d] must be absolute, got %q", i, f)
}
Expand Down
40 changes: 37 additions & 3 deletions pkg/cidata/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cidata

import (
"io"
"strings"
"testing"

"gotest.tools/v3/assert"
Expand All @@ -15,10 +16,11 @@ func TestTemplate(t *testing.T) {
SSHPubKeys: []string{
"ssh-rsa dummy foo@example.com",
},
Mounts: []string{
"/Users/dummy",
"/Users/dummy/lima",
Mounts: []Mount{
{Target: "/Users/dummy"},
{Target: "/Users/dummy/lima"},
},
MountType: "reverse-sshfs",
}
layout, err := ExecuteTemplate(args)
assert.NilError(t, err)
Expand All @@ -27,5 +29,37 @@ func TestTemplate(t *testing.T) {
b, err := io.ReadAll(f.Reader)
assert.NilError(t, err)
t.Log(string(b))
if f.Path == "user-data" {
// mounted later
assert.Assert(t, !strings.Contains(string(b), "mounts:"))
}
}
}

func TestTemplate9p(t *testing.T) {
args := TemplateArgs{
Name: "default",
User: "foo",
UID: 501,
SSHPubKeys: []string{
"ssh-rsa dummy foo@example.com",
},
Mounts: []Mount{
{Tag: "mount0", Target: "/Users/dummy", Type: "9p", Options: "ro,trans=virtio"},
{Tag: "mount1", Target: "/Users/dummy/lima", Type: "9p", Options: "rw,trans=virtio"},
},
MountType: "9p",
}
layout, err := ExecuteTemplate(args)
assert.NilError(t, err)
for _, f := range layout {
t.Logf("=== %q ===", f.Path)
b, err := io.ReadAll(f.Reader)
assert.NilError(t, err)
t.Log(string(b))
if f.Path == "user-data" {
// mounted at boot
assert.Assert(t, strings.Contains(string(b), "mounts:"))
}
}
}
26 changes: 14 additions & 12 deletions pkg/hostagent/hostagent.go
Original file line number Diff line number Diff line change
Expand Up @@ -378,19 +378,21 @@ func (a *HostAgent) startHostAgentRoutines(ctx context.Context) error {
if err := a.waitForRequirements(ctx, "essential", a.essentialRequirements()); err != nil {
mErr = multierror.Append(mErr, err)
}
mounts, err := a.setupMounts(ctx)
if err != nil {
mErr = multierror.Append(mErr, err)
}
a.onClose = append(a.onClose, func() error {
var unmountMErr error
for _, m := range mounts {
if unmountErr := m.close(); unmountErr != nil {
unmountMErr = multierror.Append(unmountMErr, unmountErr)
}
if *a.y.MountType == limayaml.REVSSHFS {
mounts, err := a.setupMounts(ctx)
if err != nil {
mErr = multierror.Append(mErr, err)
}
return unmountMErr
})
a.onClose = append(a.onClose, func() error {
var unmountMErr error
for _, m := range mounts {
if unmountErr := m.close(); unmountErr != nil {
unmountMErr = multierror.Append(unmountMErr, unmountErr)
}
}
return unmountMErr
})
}
go a.watchGuestAgentEvents(ctx)
if err := a.waitForRequirements(ctx, "optional", a.optionalRequirements()); err != nil {
mErr = multierror.Append(mErr, err)
Expand Down
Loading