Skip to content

Commit bab3e89

Browse files
authored
Merge pull request #65 from germag/macos-switch-to-krunkit
Macos switch to krunkit
2 parents 576583b + 5236935 commit bab3e89

File tree

11 files changed

+482
-117
lines changed

11 files changed

+482
-117
lines changed

cmd/vmmon.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//go:build darwin
2+
3+
package cmd
4+
5+
import (
6+
"context"
7+
"fmt"
8+
"os"
9+
"path/filepath"
10+
"strconv"
11+
12+
"github.com/containers/podman-bootc/pkg/user"
13+
"github.com/containers/podman-bootc/pkg/vm"
14+
15+
"github.com/spf13/cobra"
16+
)
17+
18+
// We treat this command as internal, we don't expect the user to call
19+
// this directly. In the future this will be replaced by an external binary
20+
var (
21+
monCmd = &cobra.Command{
22+
Use: "vmmon <ID> <username> <ssh identity> <ssh port>",
23+
Hidden: true,
24+
Args: cobra.ExactArgs(4),
25+
RunE: doMon,
26+
}
27+
console bool
28+
)
29+
30+
func init() {
31+
RootCmd.AddCommand(monCmd)
32+
runCmd.Flags().BoolVar(&console, "console", false, "Show boot console")
33+
}
34+
35+
func doMon(_ *cobra.Command, args []string) error {
36+
usr, err := user.NewUser()
37+
if err != nil {
38+
return err
39+
}
40+
41+
ctx := context.Background()
42+
fullImageId := args[0]
43+
username := args[1]
44+
sshIdentity := args[2]
45+
46+
sshPort, err := strconv.Atoi(args[3])
47+
if err != nil {
48+
return fmt.Errorf("invalid ssh port: %w", err)
49+
}
50+
51+
cacheDir := filepath.Join(usr.CacheDir(), fullImageId)
52+
53+
// MacOS has a 104 bytes limit for a unix socket path
54+
runDir := filepath.Join(usr.RunDir(), fullImageId[0:12])
55+
if err := os.MkdirAll(runDir, os.ModePerm); err != nil {
56+
return err
57+
}
58+
59+
params := vm.MonitorParmeters{
60+
CacheDir: cacheDir,
61+
RunDir: runDir,
62+
Username: username,
63+
SshIdentity: sshIdentity,
64+
SshPort: sshPort,
65+
}
66+
67+
return vm.StartMonitor(ctx, params)
68+
}

go.mod

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,17 @@ go 1.20
55
require (
66
github.com/adrg/xdg v0.4.0
77
github.com/containers/common v0.58.1
8+
github.com/containers/gvisor-tap-vsock v0.7.3
89
github.com/containers/podman/v5 v5.0.1
910
github.com/docker/go-units v0.5.0
1011
github.com/gofrs/flock v0.8.1
1112
github.com/onsi/ginkgo/v2 v2.17.1
1213
github.com/onsi/gomega v1.32.0
13-
github.com/opencontainers/runtime-spec v1.2.0
1414
github.com/sirupsen/logrus v1.9.3
1515
github.com/spf13/cobra v1.8.0
1616
golang.org/x/crypto v0.22.0
1717
golang.org/x/sys v0.19.0
18+
golang.org/x/term v0.19.0
1819
libvirt.org/go/libvirt v1.10002.0
1920
)
2021

@@ -40,7 +41,6 @@ require (
4041
github.com/containerd/stargz-snapshotter/estargz v0.15.1 // indirect
4142
github.com/containerd/typeurl/v2 v2.1.1 // indirect
4243
github.com/containers/buildah v1.35.3 // indirect
43-
github.com/containers/gvisor-tap-vsock v0.7.3 // indirect
4444
github.com/containers/image/v5 v5.30.0 // indirect
4545
github.com/containers/libhvee v0.7.0 // indirect
4646
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect
@@ -134,6 +134,7 @@ require (
134134
github.com/opencontainers/go-digest v1.0.0 // indirect
135135
github.com/opencontainers/image-spec v1.1.0 // indirect
136136
github.com/opencontainers/runc v1.1.12 // indirect
137+
github.com/opencontainers/runtime-spec v1.2.0 // indirect
137138
github.com/opencontainers/runtime-tools v0.9.1-0.20230914150019-408c51e934dc // indirect
138139
github.com/opencontainers/selinux v1.11.0 // indirect
139140
github.com/openshift/imagebuilder v1.2.6 // indirect
@@ -176,7 +177,6 @@ require (
176177
golang.org/x/mod v0.15.0 // indirect
177178
golang.org/x/net v0.22.0 // indirect
178179
golang.org/x/sync v0.6.0 // indirect
179-
golang.org/x/term v0.19.0 // indirect
180180
golang.org/x/text v0.14.0 // indirect
181181
golang.org/x/time v0.3.0 // indirect
182182
golang.org/x/tools v0.18.0 // indirect

pkg/user/user.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"os"
66
"os/user"
77
"path/filepath"
8+
"runtime"
89
"strconv"
910

1011
"github.com/containers/podman-bootc/pkg/config"
@@ -57,6 +58,15 @@ func (u *User) DefaultIdentity() string {
5758
}
5859

5960
func (u *User) RunDir() string {
61+
if runtime.GOOS == "darwin" {
62+
tmpDir, ok := os.LookupEnv("TMPDIR")
63+
if !ok {
64+
tmpDir = "/tmp"
65+
}
66+
67+
return filepath.Join(tmpDir, config.ProjectName, "run")
68+
}
69+
6070
return filepath.Join(xdg.RuntimeDir, config.ProjectName, "run")
6171
}
6272

pkg/utils/file.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ package utils
33
import (
44
"bytes"
55
"errors"
6+
"fmt"
67
"os"
78
"strconv"
9+
"time"
810
)
911

1012
func ReadPidFile(pidFile string) (int, error) {
@@ -24,6 +26,15 @@ func ReadPidFile(pidFile string) (int, error) {
2426
return int(pid), nil
2527
}
2628

29+
func WritePidFile(pidFile string, pid int) error {
30+
if pid < 1 {
31+
// We might be running as PID 1 when running docker-in-docker,
32+
// but 0 or negative PIDs are not acceptable.
33+
return fmt.Errorf("invalid negative PID %d", pid)
34+
}
35+
return os.WriteFile(pidFile, []byte(strconv.Itoa(pid)), 0o644)
36+
}
37+
2738
func FileExists(path string) (bool, error) {
2839
_, err := os.Stat(path)
2940
exists := false
@@ -35,3 +46,17 @@ func FileExists(path string) (bool, error) {
3546
}
3647
return exists, err
3748
}
49+
50+
// WaitForFileWithBackoffs attempts to discover a file in maxBackoffs attempts
51+
func WaitForFileWithBackoffs(maxBackoffs int, backoff time.Duration, path string) error {
52+
backoffWait := backoff
53+
for i := 0; i < maxBackoffs; i++ {
54+
e, _ := FileExists(path)
55+
if e {
56+
return nil
57+
}
58+
time.Sleep(backoffWait)
59+
backoffWait *= 2
60+
}
61+
return fmt.Errorf("unable to find file at %q", path)
62+
}

pkg/utils/process.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,13 @@ func IsProcessAlive(pid int) bool {
1414
err = process.Signal(syscall.Signal(0))
1515
return err == nil
1616
}
17+
18+
// SendInterrupt sends SIGINT to pid
19+
func SendInterrupt(pid int) error {
20+
process, err := os.FindProcess(pid)
21+
if err != nil {
22+
return err
23+
}
24+
25+
return process.Signal(os.Interrupt)
26+
}

pkg/vm/gvproxy.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package vm
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"os/exec"
8+
"path/filepath"
9+
"strings"
10+
"time"
11+
12+
"github.com/containers/podman-bootc/pkg/utils"
13+
14+
gvproxy "github.com/containers/gvisor-tap-vsock/pkg/types"
15+
"github.com/sirupsen/logrus"
16+
)
17+
18+
const gvproxyBinaryName = "gvproxy"
19+
20+
type Vmm int
21+
22+
const (
23+
pidFileName = "gvproxy.pid"
24+
socketFile = "net.sock"
25+
// How log we should wait for gvproxy to be ready
26+
maxBackoffs = 5
27+
backoff = time.Millisecond * 200
28+
)
29+
30+
type gvproxyParams struct {
31+
SshPort int
32+
}
33+
34+
type gvproxyDaemon struct {
35+
socketPath string
36+
pidFile string
37+
cmd *exec.Cmd
38+
}
39+
40+
func newGvproxy(ctx context.Context, binaryPath, rundir string, param gvproxyParams) *gvproxyDaemon {
41+
socketPath := filepath.Join(rundir, socketFile)
42+
pidFile := filepath.Join(rundir, pidFileName)
43+
44+
gvpCmd := gvproxy.NewGvproxyCommand()
45+
gvpCmd.SSHPort = param.SshPort
46+
gvpCmd.PidFile = pidFile
47+
gvpCmd.AddVfkitSocket(fmt.Sprintf("unixgram://%s", socketPath))
48+
49+
cmdLine := gvpCmd.ToCmdline()
50+
cmd := exec.CommandContext(ctx, binaryPath, cmdLine...)
51+
logrus.Debugf("gvproxy command-line: %s %s", binaryPath, strings.Join(cmdLine, " "))
52+
53+
return &gvproxyDaemon{socketPath: socketPath, pidFile: pidFile, cmd: cmd}
54+
}
55+
56+
// Start spawn the gvproxy daemon, killing any running daemon using the same unix socket file.
57+
// It blocks until the unix socket file exists, this does not guarantee that the socket will be
58+
// on listen state
59+
func (d *gvproxyDaemon) start() error {
60+
cleanup(d.pidFile, d.socketPath)
61+
62+
if err := d.cmd.Start(); err != nil {
63+
return fmt.Errorf("unable to start gvproxy: %w", err)
64+
}
65+
66+
// this is racy, the socket file could exist but not be in the listen state yet
67+
if err := utils.WaitForFileWithBackoffs(maxBackoffs, backoff, d.socketPath); err != nil {
68+
return fmt.Errorf("waiting for gvproxy socket: %w", err)
69+
}
70+
return nil
71+
}
72+
73+
func (d *gvproxyDaemon) stop() error {
74+
return d.cmd.Cancel()
75+
}
76+
77+
func (d *gvproxyDaemon) wait() error {
78+
return d.cmd.Wait()
79+
}
80+
81+
func cleanup(pidFile, socketPath string) {
82+
// Let's kill any possible running daemon
83+
pid, err := utils.ReadPidFile(pidFile)
84+
if err == nil {
85+
_ = utils.SendInterrupt(pid)
86+
}
87+
88+
_ = os.Remove(pidFile)
89+
_ = os.Remove(socketPath)
90+
}

pkg/vm/krunkit.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package vm
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os/exec"
7+
"strconv"
8+
"strings"
9+
10+
"github.com/containers/podman-bootc/pkg/utils"
11+
12+
"github.com/sirupsen/logrus"
13+
)
14+
15+
const krunkitBinaryName = "krunkit"
16+
17+
const defaultCpus = 4
18+
const defaultMemory = 2048
19+
20+
type krunkitParams struct {
21+
disk string
22+
netSocket string
23+
oemString string
24+
pidFile string
25+
}
26+
27+
type krunkit struct {
28+
pidFile string
29+
cmd *exec.Cmd
30+
}
31+
32+
func newKrunkit(ctx context.Context, binaryPath string, params krunkitParams) *krunkit {
33+
cmdLine := newKrunkitCmdLine(defaultCpus, defaultMemory)
34+
cmdLine.addRngDevice()
35+
cmdLine.addBlockDevice(params.disk)
36+
cmdLine.addNetworkDevice(params.netSocket)
37+
cmdLine.addOemString(params.oemString)
38+
39+
cmdLineSlice := cmdLine.asSlice()
40+
cmd := exec.CommandContext(ctx, binaryPath, cmdLineSlice...)
41+
logrus.Debugf("krunkit command-line: %s %s", binaryPath, strings.Join(cmdLineSlice, " "))
42+
43+
return &krunkit{cmd: cmd, pidFile: params.pidFile}
44+
}
45+
46+
func (k *krunkit) start() error {
47+
if err := k.cmd.Start(); err != nil {
48+
return fmt.Errorf("unable to start krunkit: %w", err)
49+
}
50+
51+
if err := utils.WritePidFile(k.pidFile, k.cmd.Process.Pid); err != nil {
52+
if err := k.cmd.Cancel(); err != nil {
53+
logrus.Debugf("stopping krunkit: %v", err)
54+
}
55+
return fmt.Errorf("writing pid file %s: %w", k.pidFile, err)
56+
}
57+
58+
return nil
59+
}
60+
61+
func (k *krunkit) wait() error {
62+
return k.cmd.Wait()
63+
}
64+
65+
type krunkitCmdLine struct {
66+
cpus int
67+
memory int
68+
devices []string
69+
oemString []string
70+
}
71+
72+
func newKrunkitCmdLine(cpus int, memory int) *krunkitCmdLine {
73+
return &krunkitCmdLine{cpus: cpus, memory: memory}
74+
}
75+
76+
func (kc *krunkitCmdLine) addDevice(device string) {
77+
kc.devices = append(kc.devices, device)
78+
}
79+
80+
func (kc *krunkitCmdLine) addRngDevice() {
81+
kc.addDevice("virtio-rng")
82+
}
83+
84+
func (kc *krunkitCmdLine) addBlockDevice(diskAbsPath string) {
85+
kc.addDevice(fmt.Sprintf("virtio-blk,path=%s", diskAbsPath))
86+
}
87+
88+
func (kc *krunkitCmdLine) addNetworkDevice(socketAbsPath string) {
89+
kc.addDevice(fmt.Sprintf("virtio-net,unixSocketPath=%s,mac=5a:94:ef:e4:0c:ee", socketAbsPath))
90+
}
91+
92+
func (kc *krunkitCmdLine) addOemString(oemStr string) {
93+
kc.oemString = append(kc.oemString, oemStr)
94+
}
95+
96+
func (kc *krunkitCmdLine) asSlice() []string {
97+
args := []string{}
98+
99+
args = append(args, "--cpus", strconv.Itoa(kc.cpus))
100+
args = append(args, "--memory", strconv.Itoa(kc.memory))
101+
102+
for _, device := range kc.devices {
103+
args = append(args, "--device", device)
104+
}
105+
106+
for _, oemStr := range kc.oemString {
107+
args = append(args, "--oem-string", oemStr)
108+
}
109+
return args
110+
}

0 commit comments

Comments
 (0)