Skip to content

Commit

Permalink
Mac: Use launchd to start the daemon as part of setup
Browse files Browse the repository at this point in the history
This PR adds a launchd plist file for daemon in `~/Library/LaunchAgents/com.redhat.crc.daemon.plist` and
starts it as part of setup.

As of now this plist file doesn't use socket activation due to
initial socket file descriptor hold by the launchd process and not by the
`crc daemon` process, first query to daemon api blocks and one way to overcome
to this problem is use something we are doing in the linux to check the file
descriptor and use for golang net listener on that file. In the mac it is tricky
because then we need cgo which will break the cross compilation by using something
like https://github.com/sstephenson/launch_socket_server/blob/master/src/launch/socket.go

```
✗ ./crc setup
[...]
Your system is correctly setup for using CodeReady Containers. Use 'crc start' to start the instance

✗ launchctl list | grep crc
97867	0	com.redhat.crc.daemon

✗ ps aux | grep crc
prkumar          97734   0.0  0.2 34905808  28068   ??  S     8:58PM   0:00.10 /Users/prkumar/.crc/bin/crc daemon --log-level=debug

✗ curl --unix-socket ~/.crc/crc-http.sock http://foo/api/version
{"CrcVersion":"2.0.1","CommitSha":"b9541d33","OpenshiftVersion":"4.10.3","PodmanVersion":"3.4.4"}

✗ ./crc cleanup
[...]
Cleanup finished

✗ launchctl list | grep crc
<-no result->

✗ curl --unix-socket ~/.crc/crc-http.sock http://foo/api/version
curl: (7) Couldn't connect to server
```
  • Loading branch information
praveenkumar committed Mar 31, 2022
1 parent 197df63 commit 2ca23cf
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 31 deletions.
4 changes: 0 additions & 4 deletions cmd/crc/cmd/daemon_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (

"github.com/code-ready/crc/pkg/crc/constants"
"github.com/code-ready/crc/pkg/crc/logging"
crcversion "github.com/code-ready/crc/pkg/crc/version"
)

func vsockListener() (net.Listener, error) {
Expand Down Expand Up @@ -34,9 +33,6 @@ func checkIfDaemonIsRunning() (bool, error) {
}

func daemonNotRunningMessage() string {
if crcversion.IsInstaller() {
return "Is '/Applications/CodeReady Containers.app' running? Cannot reach daemon API"
}
return genericDaemonNotRunningMessage
}

Expand Down
1 change: 1 addition & 0 deletions pkg/crc/constants/constants_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const (
OcExecutableName = "oc"
PodmanRemoteExecutableName = "podman"
TrayExecutableName = "CodeReady Containers.app"
DaemonAgentLabel = "com.redhat.crc.daemon"
)

var (
Expand Down
63 changes: 63 additions & 0 deletions pkg/crc/preflight/preflight_checks_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/code-ready/crc/pkg/crc/network"
"github.com/code-ready/crc/pkg/crc/version"
crcos "github.com/code-ready/crc/pkg/os"
"github.com/code-ready/crc/pkg/os/launchd"
"github.com/klauspost/cpuid/v2"
"golang.org/x/sys/unix"
)
Expand Down Expand Up @@ -242,3 +243,65 @@ func stopCRCHyperkitProcess() error {
}
return nil
}

func getDaemonConfig() (*launchd.AgentConfig, error) {
logFilePath := filepath.Join(constants.CrcBaseDir, ".launchd-crcd.log")

env := map[string]string{"Version": version.GetCRCVersion()}
daemonConfig := launchd.AgentConfig{
Label: constants.DaemonAgentLabel,
ExecutablePath: constants.CrcSymlinkPath,
StdOutFilePath: logFilePath,
StdErrFilePath: logFilePath,
Args: []string{"daemon", "--log-level=debug"},
Env: env,
}

return &daemonConfig, nil
}

func checkIfDaemonPlistFileExists() error {
daemonConfig, err := getDaemonConfig()
if err != nil {
return err
}
if err := launchd.CheckPlist(*daemonConfig); err != nil {
return err
}
if !launchd.AgentRunning(daemonConfig.Label) {
return fmt.Errorf("launchd agent '%s' is not running", daemonConfig.Label)
}
return nil
}

func fixDaemonPlistFileExists() error {
daemonConfig, err := getDaemonConfig()
if err != nil {
return err
}
return fixPlistFileExists(*daemonConfig)
}

func removeDaemonPlistFile() error {
if err := launchd.UnloadPlist(constants.DaemonAgentLabel); err != nil {
return err
}
return launchd.RemovePlist(constants.DaemonAgentLabel)
}

func fixPlistFileExists(agentConfig launchd.AgentConfig) error {
logging.Debugf("Creating plist for %s", agentConfig.Label)
err := launchd.CreatePlist(agentConfig)
if err != nil {
return err
}
if err := launchd.LoadPlist(agentConfig.Label); err != nil {
logging.Debugf("failed to load launchd agent '%s': %v", agentConfig.Label, err.Error())
return err
}
if err := launchd.RestartAgent(agentConfig.Label); err != nil {
logging.Debugf("failed to restart launchd agent '%s': %v", agentConfig.Label, err.Error())
return err
}
return nil
}
13 changes: 13 additions & 0 deletions pkg/crc/preflight/preflight_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,18 @@ var resolverPreflightChecks = []Check{
},
}

var daemonLaunchdChecks = []Check{
{
configKeySuffix: "check-daemon-launchd-plist",
checkDescription: "Checking if crc daemon plist file is present and loaded",
check: checkIfDaemonPlistFileExists,
fixDescription: "Adding crc daemon plist file and loading it",
fix: fixDaemonPlistFileExists,
cleanupDescription: "Unloading and removing the daemon plist file",
cleanup: removeDaemonPlistFile,
},
}

// We want all preflight checks including
// - experimental checks
// - tray checks when using an installer, regardless of tray enabled or not
Expand All @@ -120,6 +132,7 @@ func getChecks(mode network.Mode, bundlePath string, preset crcpreset.Preset) []
checks = append(checks, resolverPreflightChecks...)
checks = append(checks, bundleCheck(bundlePath, preset))
checks = append(checks, trayLaunchdCleanupChecks...)
checks = append(checks, daemonLaunchdChecks...)

return checks
}
Expand Down
10 changes: 5 additions & 5 deletions pkg/crc/preflight/preflight_darwin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ import (
func TestCountConfigurationOptions(t *testing.T) {
cfg := config.New(config.NewEmptyInMemoryStorage())
RegisterSettings(cfg)
assert.Len(t, cfg.AllConfigs(), 13)
assert.Len(t, cfg.AllConfigs(), 14)
}

func TestCountPreflights(t *testing.T) {
assert.Len(t, getPreflightChecks(true, network.SystemNetworkingMode, constants.GetDefaultBundlePath(preset.OpenShift), preset.OpenShift), 18)
assert.Len(t, getPreflightChecks(true, network.SystemNetworkingMode, constants.GetDefaultBundlePath(preset.OpenShift), preset.OpenShift), 18)
assert.Len(t, getPreflightChecks(true, network.SystemNetworkingMode, constants.GetDefaultBundlePath(preset.OpenShift), preset.OpenShift), 19)
assert.Len(t, getPreflightChecks(true, network.SystemNetworkingMode, constants.GetDefaultBundlePath(preset.OpenShift), preset.OpenShift), 19)

assert.Len(t, getPreflightChecks(true, network.UserNetworkingMode, constants.GetDefaultBundlePath(preset.OpenShift), preset.OpenShift), 17)
assert.Len(t, getPreflightChecks(true, network.UserNetworkingMode, constants.GetDefaultBundlePath(preset.OpenShift), preset.OpenShift), 17)
assert.Len(t, getPreflightChecks(true, network.UserNetworkingMode, constants.GetDefaultBundlePath(preset.OpenShift), preset.OpenShift), 18)
assert.Len(t, getPreflightChecks(true, network.UserNetworkingMode, constants.GetDefaultBundlePath(preset.OpenShift), preset.OpenShift), 18)
}
50 changes: 28 additions & 22 deletions pkg/os/launchd/launchd_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,36 +18,42 @@ import (

const (
plistTemplate = `<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version='1.0'>
<dict>
<key>Label</key>
<string>{{ .Label }}</string>
<key>ProgramArguments</key>
<array>
<string>{{ .ExecutablePath }}</string>
{{ range .Args }}
<string>{{ . }}</string>
{{ end }}
</array>
<key>StandardOutPath</key>
<string>{{ .StdOutFilePath }}</string>
<key>Disabled</key>
<false/>
<key>RunAtLoad</key>
<true/>
<key>ProcessType</key>
<string>Interactive</string>
</dict>
</plist>`
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version='1.0'>
<dict>
<key>Label</key>
<string>{{ .Label }}</string>
<key>ProgramArguments</key>
<array>
<string>{{ .ExecutablePath }}</string>
{{ range .Args }}
<string>{{ . }}</string>
{{ end }}
</array>
<key>StandardOutPath</key>
<string>{{ .StdOutFilePath }}</string>
<key>StandardErrorPath</key>
<string>{{ .StdErrFilePath }}</string>
<key>EnvironmentVariables</key>
<dict>
{{ range $key, $value := .Env }}
<key>{{ $key }}</key>
<string>{{ $value }}</string>
{{ end }}
</dict>
</dict>
</plist>
`
)

// AgentConfig is struct to contain configuration for agent plist file
type AgentConfig struct {
Label string
ExecutablePath string
StdOutFilePath string
StdErrFilePath string
Args []string
Env map[string]string
}

var (
Expand Down

0 comments on commit 2ca23cf

Please sign in to comment.