Skip to content

Commit

Permalink
vm: add experimental long-polling based filesystem inotify events (ab…
Browse files Browse the repository at this point in the history
…iosoft#655)

* vm: mounts: begin inotify implementation

* chore: update deps

* ci: upgrade to Go 1.20

* vm: mounts: rename to inotify

* vm: mounts: it works but erratic

* vm: mounts: improved performance

* vm: add polling based file system inotify event

* vm: fix containerd for inotify

* ci: fix Go version

* ci: fix linter
  • Loading branch information
abiosoft authored Mar 19, 2023
1 parent 92157a8 commit 30efd6c
Show file tree
Hide file tree
Showing 17 changed files with 521 additions and 117 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
go-version: "1.20"

- name: Build
run: go build -v ./...
Expand All @@ -41,7 +41,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
go-version: "1.20"

- name: generate macOS binaries
run: |
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/golang-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
go-version: "1.20"
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: v1.48.0
version: v1.52.0
args: --timeout 3m0s
12 changes: 6 additions & 6 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
go-version: "1.20"

- name: Install CLI deps
run: |
Expand Down Expand Up @@ -66,7 +66,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
go-version: "1.20"

- name: Install CLI deps
run: |
Expand Down Expand Up @@ -102,7 +102,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
go-version: "1.20"

- name: Install CLI deps
run: |
Expand Down Expand Up @@ -159,7 +159,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
go-version: "1.20"

- name: Install CLI deps
run: |
Expand Down Expand Up @@ -216,7 +216,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
go-version: "1.20"

- name: Install CLI deps
run: |
Expand Down Expand Up @@ -270,7 +270,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
go-version: "1.20"

- name: Install CLI deps
run: |
Expand Down
12 changes: 10 additions & 2 deletions cmd/daemon/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import (
"github.com/abiosoft/colima/config"
"github.com/abiosoft/colima/daemon/process"
"github.com/abiosoft/colima/daemon/process/gvproxy"
"github.com/abiosoft/colima/daemon/process/inotify"
"github.com/abiosoft/colima/daemon/process/vmnet"
"github.com/abiosoft/colima/environment/host"
"github.com/abiosoft/colima/environment/vm/lima"
"github.com/spf13/cobra"
)

Expand All @@ -35,6 +38,11 @@ var startCmd = &cobra.Command{
if daemonArgs.gvproxy.enabled {
processes = append(processes, gvproxy.New(daemonArgs.gvproxy.dnsHosts))
}
if daemonArgs.inotify {
processes = append(processes, inotify.New())
guest := lima.New(host.New())
ctx = context.WithValue(ctx, inotify.CtxKeyGuest, guest)
}

return start(ctx, processes)
},
Expand Down Expand Up @@ -75,7 +83,7 @@ var daemonArgs struct {
enabled bool
dnsHosts map[string]string
}
fsnotify bool
inotify bool

verbose bool
}
Expand All @@ -90,5 +98,5 @@ func init() {
startCmd.Flags().BoolVar(&daemonArgs.vmnet, "vmnet", false, "start vmnet")
startCmd.Flags().BoolVar(&daemonArgs.gvproxy.enabled, "gvproxy", false, "start gvproxy")
startCmd.Flags().StringToStringVar(&daemonArgs.gvproxy.dnsHosts, "gvproxy-hosts", nil, "start gvproxy")
startCmd.Flags().BoolVar(&daemonArgs.fsnotify, "fsnotify", false, "start fsnotify")
startCmd.Flags().BoolVar(&daemonArgs.inotify, "inotify", false, "start inotify")
}
4 changes: 4 additions & 0 deletions cmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ func init() {
// mounts
startCmd.Flags().StringSliceVarP(&startCmdArgs.Flags.Mounts, "mount", "V", nil, "directories to mount, suffix ':w' for writable")
startCmd.Flags().StringVar(&startCmdArgs.MountType, "mount-type", defaultMountTypeQEMU, "volume driver for the mount ("+mounts+")")
startCmd.Flags().BoolVar(&startCmdArgs.MountINotify, "mount-inotify", false, "propagate inotify file events to the VM")

// ssh agent
startCmd.Flags().BoolVarP(&startCmdArgs.ForwardAgent, "ssh-agent", "s", false, "forward SSH agent to the VM")
Expand Down Expand Up @@ -359,6 +360,9 @@ func prepareConfig(cmd *cobra.Command) {
if !cmd.Flag("mount-type").Changed {
startCmdArgs.MountType = current.MountType
}
if !cmd.Flag("mount-inotify").Changed {
startCmdArgs.MountINotify = current.MountINotify
}
if !cmd.Flag("ssh-agent").Changed {
startCmdArgs.ForwardAgent = current.ForwardAgent
}
Expand Down
5 changes: 3 additions & 2 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,9 @@ type Config struct {
VZRosetta bool `yaml:"rosetta,omitempty"`

// volume mounts
Mounts []Mount `yaml:"mounts,omitempty"`
MountType string `yaml:"mountType,omitempty"`
Mounts []Mount `yaml:"mounts,omitempty"`
MountType string `yaml:"mountType,omitempty"`
MountINotify bool `yaml:"mountInotify,omitempty"`

// Runtime is one of docker, containerd.
Runtime string `yaml:"runtime,omitempty"`
Expand Down
19 changes: 14 additions & 5 deletions daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import (
"github.com/abiosoft/colima/config"
"github.com/abiosoft/colima/daemon/process"
"github.com/abiosoft/colima/daemon/process/gvproxy"
"github.com/abiosoft/colima/daemon/process/inotify"
"github.com/abiosoft/colima/daemon/process/vmnet"
"github.com/abiosoft/colima/environment"
"github.com/abiosoft/colima/util"
"github.com/abiosoft/colima/util/fsutil"
"github.com/abiosoft/colima/util/osutil"
)
Expand Down Expand Up @@ -92,6 +94,9 @@ func (l processManager) Start(ctx context.Context) error {
if opts.Vmnet {
args = append(args, "--vmnet")
}
if opts.INotify {
args = append(args, "--inotify")
}
if opts.GVProxy.Enabled {
args = append(args, "--gvproxy")
if len(opts.GVProxy.Hosts) > 0 {
Expand All @@ -102,10 +107,11 @@ func (l processManager) Start(ctx context.Context) error {
}

if cli.Settings.Verbose {
args = append(args, "--verbose")
args = append(args, "--very-verbose")
}

return l.host.RunQuiet(args...)
host := l.host.WithDir(util.HomeDir())
return host.RunQuiet(args...)
}
func (l processManager) Stop(ctx context.Context) error {
if s, err := l.Running(ctx); err != nil || !s.Running {
Expand All @@ -120,20 +126,20 @@ func optsFromCtx(ctx context.Context) struct {
Enabled bool
Hosts map[string]string
}
FSNotify bool
INotify bool
} {
var opts = struct {
Vmnet bool
GVProxy struct {
Enabled bool
Hosts map[string]string
}

FSNotify bool
INotify bool
}{}
opts.Vmnet, _ = ctx.Value(CtxKey(vmnet.Name)).(bool)
opts.GVProxy.Enabled, _ = ctx.Value(CtxKey(gvproxy.Name)).(bool)
opts.GVProxy.Hosts, _ = ctx.Value(CtxKey(gvproxy.CtxKeyHosts)).(map[string]string)
opts.INotify, _ = ctx.Value(CtxKey(inotify.Name)).(bool)

return opts
}
Expand All @@ -148,6 +154,9 @@ func processesFromCtx(ctx context.Context) []process.Process {
if opts.GVProxy.Enabled {
processes = append(processes, gvproxy.New(opts.GVProxy.Hosts))
}
if opts.INotify {
processes = append(processes, inotify.New())
}

return processes
}
130 changes: 130 additions & 0 deletions daemon/process/inotify/inotify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package inotify

import (
"context"
"fmt"
"sync"
"time"

"github.com/abiosoft/colima/daemon/process"
"github.com/abiosoft/colima/environment"
"github.com/abiosoft/colima/environment/vm/lima/limautil"
"github.com/abiosoft/colima/util"
"github.com/sirupsen/logrus"
)

const Name = "inotify"

const watchInterval = time.Second * 1
const volumesInterval = time.Second * 5

var CtxKeyGuest = struct{ name string }{name: "inotify_guest"}

// New returns inotify process.
func New() process.Process {
return &inotifyProcess{
interval: watchInterval,
log: logrus.WithField("context", "inotify"),
}
}

var _ process.Process = (*inotifyProcess)(nil)

type inotifyProcess struct {
alive bool
containerVols []string
// will only be used for alive and containerVols
sync.RWMutex

interval time.Duration
vmVols []string
guest environment.GuestActions
runtime string

log *logrus.Entry
}

// Alive implements process.Process
func (f *inotifyProcess) Alive(ctx context.Context) error {
f.Lock()
defer f.RUnlock()

if f.alive {
return nil
}
return fmt.Errorf("inotify not running")
}

// Dependencies implements process.Process
func (*inotifyProcess) Dependencies() (deps []process.Dependency, root bool) {
return nil, false
}

// Name implements process.Process
func (*inotifyProcess) Name() string {
return Name
}

// Start implements process.Process
func (f *inotifyProcess) Start(ctx context.Context) error {
guest, ok := ctx.Value(CtxKeyGuest).(environment.GuestActions)
if !ok {
return fmt.Errorf("environment.GuestActions missing in context")
}
f.guest = guest

log := f.log

log.Info("waiting for VM to start")
f.waitForLima(ctx)
log.Info("VM started")

c, err := limautil.InstanceConfig()
if err != nil {
return fmt.Errorf("error retrieving config")
}
f.runtime = c.Runtime

for _, mount := range c.MountsOrDefault() {
p, err := util.CleanPath(mount.Location)
if err != nil {
return fmt.Errorf("error retrieving mount path: %w", err)
}
f.vmVols = append(f.vmVols, p)
}

return f.watch(ctx)
}

// waitForLima waits until lima starts and sets the directory to watch.
func (f *inotifyProcess) waitForLima(ctx context.Context) {
log := f.log

// wait for Lima to finish starting
for {
log.Info("attempting to fetch config from Lima")

// 5 second interval
after := time.After(time.Second * 5)

select {
case <-ctx.Done():
return
case <-after:
i, err := limautil.Instance()
if err == nil && i.Running() {
if _, err := limautil.InstanceConfig(); err == nil {
return
}
}
}
}
}

func (f *inotifyProcess) watch(ctx context.Context) error {
if err := f.fetchContainerVolumes(ctx); err != nil {
return fmt.Errorf("error fetching container volumes: %w", err)
}

return f.watchFiles(ctx)
}
Loading

0 comments on commit 30efd6c

Please sign in to comment.