Repository: code-ready/crc
Pull request #1772 (2020-12-10 14:05:29)

doc: Add systemd-resolved description to our DNS reference
This will need to be iterated on after getting some Kevin's feedback, something similar would be needed for the remote configuration, ...

openshift-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/code-ready/crc/pull/1772#" title="Author self-approved">cfergeau</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=code-ready%2Fcrc).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[docs/OWNERS](https://github.com/code-ready/crc/blob/master/docs/OWNERS)~~ [cfergeau]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
praveenkumar: /assign @robin-owen 
cfergeau: I've made a few more changes than what was requested to align this with https://github.com/code-ready/crc/pull/1819

Patch:
diff --git a/cmd/crc-embedder/cmd/extract.go b/cmd/crc-embedder/cmd/extract.go
index e99ce853f..f0cab79ed 100644
--- a/cmd/crc-embedder/cmd/extract.go
+++ b/cmd/crc-embedder/cmd/extract.go
@@ -1,10 +1,10 @@
 package cmd
 
 import (
+	"fmt"
+
 	"github.com/code-ready/crc/pkg/crc/logging"
-	"github.com/code-ready/crc/pkg/crc/output"
 	"github.com/code-ready/crc/pkg/embed"
-
 	"github.com/spf13/cobra"
 )
 
@@ -32,5 +32,5 @@ func runExtract(args []string) {
 	if err != nil {
 		logging.Fatalf("Could not extract data embedded in %s: %v", executablePath, err)
 	}
-	output.Outf("Successfully copied embedded '%s' from %s to %s: %v", embedName, executablePath, destFile, err)
+	fmt.Printf("Successfully copied embedded '%s' from %s to %s: %v", embedName, executablePath, destFile, err)
 }
diff --git a/cmd/crc-embedder/cmd/list.go b/cmd/crc-embedder/cmd/list.go
index 3fe8d6d1a..9ab0c52bb 100644
--- a/cmd/crc-embedder/cmd/list.go
+++ b/cmd/crc-embedder/cmd/list.go
@@ -1,10 +1,10 @@
 package cmd
 
 import (
-	"github.com/code-ready/crc/pkg/crc/logging"
-	"github.com/code-ready/crc/pkg/crc/output"
+	"fmt"
 
 	"github.com/YourFin/binappend"
+	"github.com/code-ready/crc/pkg/crc/logging"
 	"github.com/spf13/cobra"
 )
 
@@ -30,8 +30,8 @@ func runList(args []string) {
 	if err != nil {
 		logging.Fatalf("Could not access data embedded in %s: %v", executablePath, err)
 	}
-	output.Outf("Data files embedded in %s:\n", executablePath)
+	fmt.Printf("Data files embedded in %s:\n", executablePath)
 	for _, name := range extractor.AvalibleData() {
-		output.Outln("\t", name)
+		fmt.Println("\t", name)
 	}
 }
diff --git a/cmd/crc-embedder/cmd/root.go b/cmd/crc-embedder/cmd/root.go
index a062cd816..eb00a0634 100644
--- a/cmd/crc-embedder/cmd/root.go
+++ b/cmd/crc-embedder/cmd/root.go
@@ -1,10 +1,10 @@
 package cmd
 
 import (
+	"fmt"
+
 	"github.com/code-ready/crc/pkg/crc/constants"
 	"github.com/code-ready/crc/pkg/crc/logging"
-	"github.com/code-ready/crc/pkg/crc/output"
-
 	"github.com/spf13/cobra"
 )
 
@@ -41,8 +41,8 @@ func runPrerun() {
 }
 
 func runRoot() {
-	output.Outln("No command given")
-	output.Outln("")
+	fmt.Println("No command given")
+	fmt.Println("")
 }
 
 func runPostrun() {
diff --git a/cmd/crc/cmd/config/config.go b/cmd/crc/cmd/config/config.go
index 4aa2e4cdb..d3ba19a85 100644
--- a/cmd/crc/cmd/config/config.go
+++ b/cmd/crc/cmd/config/config.go
@@ -48,7 +48,7 @@ func RegisterSettings(cfg *config.Config) {
 	cfg.AddSetting(EnableClusterMonitoring, false, config.ValidateBool, config.SuccessfullyApplied)
 
 	// Telemeter Configuration
-	cfg.AddSetting(ConsentTelemetry, false, config.ValidateBool, config.SuccessfullyApplied)
+	cfg.AddSetting(ConsentTelemetry, "", config.ValidateYesNo, config.SuccessfullyApplied)
 }
 
 func isPreflightKey(key string) bool {
diff --git a/cmd/crc/cmd/config/get.go b/cmd/crc/cmd/config/get.go
index 2a6cb92f7..9f694eb79 100644
--- a/cmd/crc/cmd/config/get.go
+++ b/cmd/crc/cmd/config/get.go
@@ -5,7 +5,6 @@ import (
 	"fmt"
 
 	"github.com/code-ready/crc/pkg/crc/config"
-	"github.com/code-ready/crc/pkg/crc/output"
 	"github.com/spf13/cobra"
 )
 
@@ -26,7 +25,7 @@ func configGetCmd(config config.Storage) *cobra.Command {
 			case v.IsDefault:
 				return fmt.Errorf("Configuration property '%s' is not set. Default value is '%s'", key, v.AsString())
 			default:
-				output.Outln(key, ":", v.AsString())
+				fmt.Println(key, ":", v.AsString())
 			}
 			return nil
 		},
diff --git a/cmd/crc/cmd/config/set.go b/cmd/crc/cmd/config/set.go
index f6b356b20..0afc89d54 100644
--- a/cmd/crc/cmd/config/set.go
+++ b/cmd/crc/cmd/config/set.go
@@ -2,9 +2,9 @@ package config
 
 import (
 	"errors"
+	"fmt"
 
 	"github.com/code-ready/crc/pkg/crc/config"
-	"github.com/code-ready/crc/pkg/crc/output"
 	"github.com/spf13/cobra"
 )
 
@@ -23,7 +23,7 @@ func configSetCmd(config config.Storage) *cobra.Command {
 			}
 
 			if setMessage != "" {
-				output.Outln(setMessage)
+				fmt.Println(setMessage)
 			}
 			return nil
 		},
diff --git a/cmd/crc/cmd/config/unset.go b/cmd/crc/cmd/config/unset.go
index cfbe6484b..222c20595 100644
--- a/cmd/crc/cmd/config/unset.go
+++ b/cmd/crc/cmd/config/unset.go
@@ -2,9 +2,9 @@ package config
 
 import (
 	"errors"
+	"fmt"
 
 	"github.com/code-ready/crc/pkg/crc/config"
-	"github.com/code-ready/crc/pkg/crc/output"
 	"github.com/spf13/cobra"
 )
 
@@ -22,7 +22,7 @@ func configUnsetCmd(config config.Storage) *cobra.Command {
 				return err
 			}
 			if unsetMessage != "" {
-				output.Outln(unsetMessage)
+				fmt.Println(unsetMessage)
 			}
 			return nil
 		},
diff --git a/cmd/crc/cmd/ip.go b/cmd/crc/cmd/ip.go
index 576178ac0..477040f60 100644
--- a/cmd/crc/cmd/ip.go
+++ b/cmd/crc/cmd/ip.go
@@ -1,7 +1,8 @@
 package cmd
 
 import (
-	"github.com/code-ready/crc/pkg/crc/output"
+	"fmt"
+
 	"github.com/spf13/cobra"
 )
 
@@ -28,6 +29,6 @@ func runIP(arguments []string) error {
 	if err != nil {
 		return err
 	}
-	output.Outln(ip)
+	fmt.Println(ip)
 	return nil
 }
diff --git a/cmd/crc/cmd/oc_env.go b/cmd/crc/cmd/oc_env.go
index 09495bf92..b9ae212df 100644
--- a/cmd/crc/cmd/oc_env.go
+++ b/cmd/crc/cmd/oc_env.go
@@ -4,7 +4,6 @@ import (
 	"fmt"
 
 	"github.com/code-ready/crc/pkg/crc/constants"
-	"github.com/code-ready/crc/pkg/crc/output"
 	"github.com/code-ready/crc/pkg/os/shell"
 	"github.com/spf13/cobra"
 )
@@ -34,13 +33,13 @@ func runOcEnv(args []string) error {
 		return err
 	}
 	proxyConfig := consoleResult.ClusterConfig.ProxyConfig
-	output.Outln(shell.GetPathEnvString(userShell, constants.CrcOcBinDir))
+	fmt.Println(shell.GetPathEnvString(userShell, constants.CrcOcBinDir))
 	if proxyConfig.IsEnabled() {
-		output.Outln(shell.GetEnvString(userShell, "HTTP_PROXY", proxyConfig.HTTPProxy))
-		output.Outln(shell.GetEnvString(userShell, "HTTPS_PROXY", proxyConfig.HTTPSProxy))
-		output.Outln(shell.GetEnvString(userShell, "NO_PROXY", proxyConfig.GetNoProxyString()))
+		fmt.Println(shell.GetEnvString(userShell, "HTTP_PROXY", proxyConfig.HTTPProxy))
+		fmt.Println(shell.GetEnvString(userShell, "HTTPS_PROXY", proxyConfig.HTTPSProxy))
+		fmt.Println(shell.GetEnvString(userShell, "NO_PROXY", proxyConfig.GetNoProxyString()))
 	}
-	output.Outln(shell.GenerateUsageHint(userShell, "crc oc-env"))
+	fmt.Println(shell.GenerateUsageHint(userShell, "crc oc-env"))
 	return nil
 }
 
diff --git a/cmd/crc/cmd/podman_env.go b/cmd/crc/cmd/podman_env.go
index ad37ea0f4..2a2014b07 100644
--- a/cmd/crc/cmd/podman_env.go
+++ b/cmd/crc/cmd/podman_env.go
@@ -5,7 +5,6 @@ import (
 	"fmt"
 
 	"github.com/code-ready/crc/pkg/crc/constants"
-	"github.com/code-ready/crc/pkg/crc/output"
 	"github.com/code-ready/crc/pkg/os/shell"
 	"github.com/spf13/cobra"
 )
@@ -36,12 +35,12 @@ func RunPodmanEnv(args []string) error {
 		return err
 	}
 
-	output.Outln(shell.GetPathEnvString(userShell, constants.CrcBinDir))
-	output.Outln(shell.GetEnvString(userShell, "PODMAN_USER", constants.DefaultSSHUser))
-	output.Outln(shell.GetEnvString(userShell, "PODMAN_HOST", ip))
-	output.Outln(shell.GetEnvString(userShell, "PODMAN_IDENTITY_FILE", constants.GetPrivateKeyPath()))
-	output.Outln(shell.GetEnvString(userShell, "PODMAN_IGNORE_HOSTS", "1"))
-	output.Outln(shell.GenerateUsageHint(userShell, "crc podman-env"))
+	fmt.Println(shell.GetPathEnvString(userShell, constants.CrcBinDir))
+	fmt.Println(shell.GetEnvString(userShell, "PODMAN_USER", constants.DefaultSSHUser))
+	fmt.Println(shell.GetEnvString(userShell, "PODMAN_HOST", ip))
+	fmt.Println(shell.GetEnvString(userShell, "PODMAN_IDENTITY_FILE", constants.GetPrivateKeyPath()))
+	fmt.Println(shell.GetEnvString(userShell, "PODMAN_IGNORE_HOSTS", "1"))
+	fmt.Println(shell.GenerateUsageHint(userShell, "crc podman-env"))
 	return nil
 }
 
diff --git a/cmd/crc/cmd/root.go b/cmd/crc/cmd/root.go
index 36e7a13b2..4c360a139 100644
--- a/cmd/crc/cmd/root.go
+++ b/cmd/crc/cmd/root.go
@@ -5,6 +5,7 @@ import (
 	"io/ioutil"
 	"os"
 	"strings"
+	"time"
 
 	cmdConfig "github.com/code-ready/crc/cmd/crc/cmd/config"
 	crcConfig "github.com/code-ready/crc/pkg/crc/config"
@@ -12,7 +13,6 @@ import (
 	"github.com/code-ready/crc/pkg/crc/logging"
 	"github.com/code-ready/crc/pkg/crc/machine"
 	"github.com/code-ready/crc/pkg/crc/network"
-	"github.com/code-ready/crc/pkg/crc/output"
 	"github.com/code-ready/crc/pkg/crc/preflight"
 	"github.com/code-ready/crc/pkg/crc/segment"
 	"github.com/spf13/cobra"
@@ -79,7 +79,7 @@ func runPostrun() {
 }
 
 func runRoot() {
-	output.Outln("No command given")
+	fmt.Println("No command given")
 }
 
 func Execute() {
@@ -175,13 +175,12 @@ func addForceFlag(cmd *cobra.Command) {
 func executeWithLogging(fullCmd string, input func(cmd *cobra.Command, args []string) error) func(cmd *cobra.Command, args []string) error {
 	return func(cmd *cobra.Command, args []string) error {
 		logging.Debugf("Running '%s'", fullCmd)
-		if err := input(cmd, args); err != nil {
-			if serr := segmentClient.Upload(fullCmd, err); serr != nil {
-				logging.Debugf("Cannot send data to telemetry: %v", serr)
-			}
-			return err
+		startTime := time.Now()
+		err := input(cmd, args)
+		if serr := segmentClient.Upload(fullCmd, time.Since(startTime), err); serr != nil {
+			logging.Debugf("Cannot send data to telemetry: %v", serr)
 		}
-		return nil
+		return err
 	}
 }
 
diff --git a/cmd/crc/cmd/setup.go b/cmd/crc/cmd/setup.go
index d8b5ace93..e0e3946d1 100644
--- a/cmd/crc/cmd/setup.go
+++ b/cmd/crc/cmd/setup.go
@@ -8,6 +8,7 @@ import (
 
 	cmdConfig "github.com/code-ready/crc/cmd/crc/cmd/config"
 	"github.com/code-ready/crc/pkg/crc/constants"
+	"github.com/code-ready/crc/pkg/crc/input"
 	"github.com/code-ready/crc/pkg/crc/preflight"
 	"github.com/spf13/cobra"
 )
@@ -31,6 +32,20 @@ var setupCmd = &cobra.Command{
 }
 
 func runSetup(arguments []string) error {
+	if config.Get(cmdConfig.ConsentTelemetry).AsString() == "" {
+		fmt.Println("CodeReady Containers is constantly improving and we would like to know more about usage!")
+		if input.PromptUserForYesOrNo("Would you like to contribute anonymous usage statistics (more details at https://developers.redhat.com/article/tool-data-collection)", false) {
+			if _, err := config.Set(cmdConfig.ConsentTelemetry, "yes"); err != nil {
+				return err
+			}
+			fmt.Printf("Thanks for helping us! You can disable telemetry with the command 'crc config set %s no'.\n", cmdConfig.ConsentTelemetry)
+		} else {
+			if _, err := config.Set(cmdConfig.ConsentTelemetry, "no"); err != nil {
+				return err
+			}
+			fmt.Printf("No worry, you can still enable telemetry manually with the command 'crc config set %s yes'.\n", cmdConfig.ConsentTelemetry)
+		}
+	}
 	err := preflight.SetupHost(config)
 	return render(&setupResult{
 		Success: err == nil,
diff --git a/docs/source/topics/ref_dns-configuration.adoc b/docs/source/topics/ref_dns-configuration.adoc
index 02caeb1cd..b80b654f8 100644
--- a/docs/source/topics/ref_dns-configuration.adoc
+++ b/docs/source/topics/ref_dns-configuration.adoc
@@ -17,7 +17,28 @@ Additional checks are done to verify DNS is properly configured when running [co
 [id="dns-configuration-linux_{context}"]
 == Linux
 
-On Linux, {prod} expects the following DNS configuration:
+On Linux, depending on your distribution, {prod} expects the following DNS configuration:
+
+=== NetworkManager + systemd-resolved
+
+This configuration is used on Fedora 33 or newer, and on Ubuntu Desktop editions.
+
+* {prod} expects NetworkManager to manage networking.
+* {prod} configures `systemd-resolved` to forward requests for the `testing` domain to the `192.168.130.11` DNS server.
+`192.168.130.11` is the IP of the {prod} virtual machine.
+* `systemd-resolved` configuration is done through a NetworkManager dispatcher script in `/etc/NetworkManager/dispatcher.d/99-crc.sh`:
++
+----
+resolvectl domain crc ~testing
+resolvectl dns crc 192.168.130.11
+resolvectl default-route crc false
+
+exit 0
+----
+
+=== NetworkManager + dnsmasq
+
+This configuration is used on Fedora 32 or older, on {rhel}, and on {centos}.
 
 * {prod} expects NetworkManager to manage networking.
 * NetworkManager uses `dnsmasq` through the [filename]`/etc/NetworkManager/conf.d/crc-nm-dnsmasq.conf` configuration file.
diff --git a/pkg/crc/config/config.go b/pkg/crc/config/config.go
index 7cd3cf121..0fe0f538d 100644
--- a/pkg/crc/config/config.go
+++ b/pkg/crc/config/config.go
@@ -9,7 +9,7 @@ import (
 
 const (
 	configPropDoesntExistMsg = "Configuration property '%s' does not exist"
-	invalidProp              = "Value '%s' for configuration property '%s' is invalid, reason: %s"
+	invalidProp              = "Value '%v' for configuration property '%s' is invalid, reason: %s"
 )
 
 type Config struct {
diff --git a/pkg/crc/config/validations.go b/pkg/crc/config/validations.go
index 634c21fe1..5ce5bd6bb 100644
--- a/pkg/crc/config/validations.go
+++ b/pkg/crc/config/validations.go
@@ -95,3 +95,10 @@ func ValidateNoProxy(value interface{}) (bool, string) {
 	}
 	return true, ""
 }
+
+func ValidateYesNo(value interface{}) (bool, string) {
+	if cast.ToString(value) == "yes" || cast.ToString(value) == "no" {
+		return true, ""
+	}
+	return false, "must be yes or no"
+}
diff --git a/pkg/crc/input/input.go b/pkg/crc/input/input.go
index be5086102..7f77c8729 100644
--- a/pkg/crc/input/input.go
+++ b/pkg/crc/input/input.go
@@ -6,7 +6,6 @@ import (
 	"os"
 	"strings"
 
-	"github.com/code-ready/crc/pkg/crc/output"
 	terminal "golang.org/x/term"
 	survey "gopkg.in/AlecAivazis/survey.v1"
 )
@@ -19,7 +18,7 @@ func PromptUserForYesOrNo(message string, force bool) bool {
 		return false
 	}
 	var response string
-	output.Outf(message + "? [y/N]: ")
+	fmt.Printf(message + "? [y/N]: ")
 	fmt.Scanf("%s", &response)
 
 	return strings.ToLower(response) == "y"
diff --git a/pkg/crc/machine/start.go b/pkg/crc/machine/start.go
index ad4b6b641..545c51c18 100644
--- a/pkg/crc/machine/start.go
+++ b/pkg/crc/machine/start.go
@@ -23,7 +23,6 @@ import (
 	"github.com/code-ready/crc/pkg/crc/ssh"
 	crcssh "github.com/code-ready/crc/pkg/crc/ssh"
 	"github.com/code-ready/crc/pkg/crc/systemd"
-	"github.com/code-ready/crc/pkg/crc/systemd/states"
 	crcos "github.com/code-ready/crc/pkg/os"
 	"github.com/code-ready/machine/libmachine"
 	"github.com/code-ready/machine/libmachine/drivers"
@@ -350,12 +349,6 @@ func (client *client) Start(startConfig StartConfig) (*StartResult, error) {
 		}
 	}
 
-	// Check if kubelet service is running inside the VM
-	kubeletStatus, err := sd.Status("kubelet")
-	if err != nil || kubeletStatus != states.Running {
-		return nil, errors.Wrap(err, "kubelet service is not running")
-	}
-
 	// In Openshift 4.3, when cluster comes up, the following happens
 	// 1. After the openshift-apiserver pod is started, its log contains multiple occurrences of `certificate has expired or is not yet valid`
 	// 2. Initially there is no request-header's client-ca crt available to `extension-apiserver-authentication` configmap
diff --git a/pkg/crc/output/output.go b/pkg/crc/output/output.go
deleted file mode 100644
index 80b50f461..000000000
--- a/pkg/crc/output/output.go
+++ /dev/null
@@ -1,18 +0,0 @@
-package output
-
-import (
-	"fmt"
-	"io"
-)
-
-func Outln(args ...interface{}) {
-	fmt.Println(args...)
-}
-
-func Outf(s string, args ...interface{}) {
-	fmt.Printf(s, args...)
-}
-
-func Fout(w io.Writer, args ...interface{}) (n int, err error) {
-	return fmt.Fprintln(w, args...)
-}
diff --git a/pkg/crc/preflight/preflight_checks_network_linux.go b/pkg/crc/preflight/preflight_checks_network_linux.go
index bc78d6802..ca22b3ea9 100644
--- a/pkg/crc/preflight/preflight_checks_network_linux.go
+++ b/pkg/crc/preflight/preflight_checks_network_linux.go
@@ -72,22 +72,27 @@ server=/crc.testing/192.168.130.11
 dns=dnsmasq
 `
 
-	crcNetworkManagerDispatcherPath   = filepath.Join(crcNetworkManagerRootPath, "dispatcher.d", "pre-up.d", "99-crc.sh")
-	crcNetworkManagerDispatcherConfig = `#!/bin/sh
+	crcNetworkManagerOldDispatcherPath = filepath.Join(crcNetworkManagerRootPath, "dispatcher.d", "pre-up.d", "99-crc.sh")
+	crcNetworkManagerDispatcherPath    = filepath.Join(crcNetworkManagerRootPath, "dispatcher.d", "99-crc.sh")
+	crcNetworkManagerDispatcherConfig  = `#!/bin/sh
 # This is a NetworkManager dispatcher script to configure split DNS for
 # the 'crc' libvirt network.
-# The corresponding crc bridge is recreated each time the system reboots, so
-# it cannot be configured permanently through NetworkManager.
-# Changing DNS settings with nmcli requires the connection to go down/up,
-# so we directly make the change using resolvectl
+#
+# The corresponding crc bridge is not created through NetworkManager, so
+# it cannot be configured permanently through NetworkManager. We make the
+# change directly using resolvectl instead.
+#
+# NetworkManager will overwrite this resolvectl configuration every time a
+# network connection goes up/down, so we run this script on each of these events
+# to restore our settings. This is a NetworkManager bug which is fixed in
+# version 1.26.6 by this commit:
+# https://cgit.freedesktop.org/NetworkManager/NetworkManager/commit/?id=ee4e679bc7479de42780ebd8e3a4d74afa2b2ebe
 
 export LC_ALL=C
 
-if [ "$1" = crc ]; then
-        resolvectl domain "$1" ~testing
-        resolvectl dns "$1" 192.168.130.11
-        resolvectl default-route "$1" false
-fi
+resolvectl domain crc ~testing
+resolvectl dns crc 192.168.130.11
+resolvectl default-route crc false
 
 exit 0
 `
@@ -266,6 +271,10 @@ func checkCrcNetworkManagerDispatcherFile() error {
 
 func fixCrcNetworkManagerDispatcherFile() error {
 	logging.Debug("Fixing NetworkManager dispatcher configuration")
+
+	// Remove dispatcher script which was used in crc 1.20 - it's been moved to a new location
+	_ = removeNetworkManagerConfigFile(crcNetworkManagerOldDispatcherPath)
+
 	err := fixNetworkManagerConfigFile(crcNetworkManagerDispatcherPath, crcNetworkManagerDispatcherConfig, 0755)
 	if err != nil {
 		return err
@@ -276,6 +285,9 @@ func fixCrcNetworkManagerDispatcherFile() error {
 }
 
 func removeCrcNetworkManagerDispatcherFile() error {
+	// Remove dispatcher script which was used in crc 1.20 - it's been moved to a new location
+	_ = removeNetworkManagerConfigFile(crcNetworkManagerOldDispatcherPath)
+
 	return removeNetworkManagerConfigFile(crcNetworkManagerDispatcherPath)
 }
 
diff --git a/pkg/crc/preflight/preflight_checks_tray_darwin.go b/pkg/crc/preflight/preflight_checks_tray_darwin.go
index 05961eee0..ee31ee2fd 100644
--- a/pkg/crc/preflight/preflight_checks_tray_darwin.go
+++ b/pkg/crc/preflight/preflight_checks_tray_darwin.go
@@ -195,7 +195,9 @@ func downloadOrExtractTrayApp() error {
 	}()
 
 	logging.Debug("Trying to extract tray from crc executable")
-	err = embed.Extract(filepath.Base(constants.GetCRCMacTrayDownloadURL()), tmpArchivePath)
+	trayFileName := filepath.Base(constants.GetCRCMacTrayDownloadURL())
+	trayDestFileName := filepath.Join(tmpArchivePath, trayFileName)
+	err = embed.Extract(trayFileName, trayDestFileName)
 	if err != nil {
 		logging.Debug("Could not extract tray from crc executable", err)
 		logging.Debug("Downloading crc tray")
@@ -204,15 +206,14 @@ func downloadOrExtractTrayApp() error {
 			return err
 		}
 	}
-	archivePath := filepath.Join(tmpArchivePath, filepath.Base(constants.GetCRCMacTrayDownloadURL()))
 	outputPath := constants.CrcBinDir
 	err = goos.MkdirAll(outputPath, 0750)
 	if err != nil {
 		return errors.Wrap(err, "Cannot create the target directory.")
 	}
-	_, err = extract.Uncompress(archivePath, outputPath, false)
+	_, err = extract.Uncompress(trayDestFileName, outputPath, false)
 	if err != nil {
-		return errors.Wrapf(err, "Cannot uncompress '%s'", archivePath)
+		return errors.Wrapf(err, "Cannot uncompress '%s'", trayDestFileName)
 	}
 	return nil
 }
diff --git a/pkg/crc/preflight/preflight_checks_tray_windows.go b/pkg/crc/preflight/preflight_checks_tray_windows.go
index 501984608..d2493018d 100644
--- a/pkg/crc/preflight/preflight_checks_tray_windows.go
+++ b/pkg/crc/preflight/preflight_checks_tray_windows.go
@@ -161,7 +161,9 @@ func fixTrayExecutableExists() error {
 	}()
 
 	logging.Debug("Trying to extract tray from crc executable")
-	err = embed.Extract(filepath.Base(constants.GetCRCWindowsTrayDownloadURL()), tmpArchivePath)
+	trayFileName := filepath.Base(constants.GetCRCWindowsTrayDownloadURL())
+	trayDestFileName := filepath.Join(tmpArchivePath, trayFileName)
+	err = embed.Extract(trayFileName, trayDestFileName)
 	if err != nil {
 		logging.Debug("Could not extract tray from crc executable", err)
 		logging.Debug("Downloading crc tray")
@@ -170,10 +172,9 @@ func fixTrayExecutableExists() error {
 			return err
 		}
 	}
-	archivePath := filepath.Join(tmpArchivePath, filepath.Base(constants.GetCRCWindowsTrayDownloadURL()))
-	_, err = extract.Uncompress(archivePath, constants.TrayExecutableDir, false)
+	_, err = extract.Uncompress(trayDestFileName, constants.TrayExecutableDir, false)
 	if err != nil {
-		return fmt.Errorf("Cannot uncompress '%s': %v", archivePath, err)
+		return fmt.Errorf("Cannot uncompress '%s': %v", trayDestFileName, err)
 	}
 
 	return nil
diff --git a/pkg/crc/preflight/preflight_linux.go b/pkg/crc/preflight/preflight_linux.go
index f2e7ff45f..e2587c8c1 100644
--- a/pkg/crc/preflight/preflight_linux.go
+++ b/pkg/crc/preflight/preflight_linux.go
@@ -8,6 +8,7 @@ import (
 	"strings"
 	"syscall"
 
+	crcErrors "github.com/code-ready/crc/pkg/crc/errors"
 	"github.com/code-ready/crc/pkg/crc/logging"
 	"github.com/code-ready/crc/pkg/crc/network"
 	crcos "github.com/code-ready/crc/pkg/os"
@@ -97,13 +98,20 @@ var libvirtNetworkPreflightChecks = [...]Check{
 }
 
 var vsockPreflightChecks = Check{
-	configKeySuffix:  "check-vsock",
-	checkDescription: "Checking if vsock is correctly configured",
-	check:            checkVsock,
-	fixDescription:   "Checking if vsock is correctly configured",
-	fix:              fixVsock,
+	configKeySuffix:    "check-vsock",
+	checkDescription:   "Checking if vsock is correctly configured",
+	check:              checkVsock,
+	fixDescription:     "Checking if vsock is correctly configured",
+	fix:                fixVsock,
+	cleanupDescription: "Removing vsock configuration",
+	cleanup:            removeVsockCrcSettings,
 }
 
+const (
+	vsockUdevRulesPath          = "/usr/lib/udev/rules.d/99-crc-vsock.rules"
+	vsockModuleAutoLoadConfPath = "/etc/modules-load.d/vhost_vsock.conf"
+)
+
 func checkVsock() error {
 	executable, err := os.Executable()
 	if err != nil {
@@ -126,7 +134,7 @@ func checkVsock() error {
 			return err
 		}
 		if group.Name != "libvirt" {
-			return errors.New("/dev/vsock is not is the right group")
+			return errors.New("/dev/vsock is not in the right group")
 		}
 	} else {
 		return errors.New("cannot cast info")
@@ -146,21 +154,39 @@ func fixVsock() error {
 	if err != nil {
 		return err
 	}
-	_, _, err = crcos.RunWithPrivilege("modprobe vhost_vsock", "modprobe", "vhost_vsock")
+
+	udevRule := `KERNEL=="vsock", MODE="0660", OWNER="root", GROUP="libvirt"`
+	err = crcos.WriteToFileAsRoot("Create udev rule for /dev/vsock", udevRule, vsockUdevRulesPath, 0644)
 	if err != nil {
 		return err
 	}
-	_, _, err = crcos.RunWithPrivilege("chown /dev/vsock", "chown", "root:libvirt", "/dev/vsock")
+	err = crcos.WriteToFileAsRoot(fmt.Sprintf("Create file %s", vsockModuleAutoLoadConfPath), "vhost_vsock", vsockModuleAutoLoadConfPath, 0644)
 	if err != nil {
 		return err
 	}
-	_, _, err = crcos.RunWithPrivilege("chmod /dev/vsock", "chmod", "g+rw", "/dev/vsock")
+	_, _, err = crcos.RunWithPrivilege("modprobe vhost_vsock", "modprobe", "vhost_vsock")
 	if err != nil {
 		return err
 	}
 	return nil
 }
 
+func removeVsockCrcSettings() error {
+	var mErr crcErrors.MultiError
+	_, _, err := crcos.RunWithPrivilege(fmt.Sprintf("rm %s", vsockUdevRulesPath), "rm", "-f", vsockUdevRulesPath)
+	if err != nil {
+		mErr.Collect(err)
+	}
+	_, _, err = crcos.RunWithPrivilege(fmt.Sprintf("rm %s", vsockModuleAutoLoadConfPath), "rm", "-f", vsockModuleAutoLoadConfPath)
+	if err != nil {
+		mErr.Collect(err)
+	}
+	if len(mErr.Errors) == 0 {
+		return nil
+	}
+	return mErr
+}
+
 func getAllPreflightChecks() []Check {
 	checks := getPreflightChecksForDistro(distro(), network.DefaultMode)
 	checks = append(checks, vsockPreflightChecks)
diff --git a/pkg/crc/segment/segment.go b/pkg/crc/segment/segment.go
index 254dfb386..f81d6131c 100644
--- a/pkg/crc/segment/segment.go
+++ b/pkg/crc/segment/segment.go
@@ -4,7 +4,9 @@ import (
 	"io/ioutil"
 	"os"
 	"path/filepath"
+	"runtime"
 	"strings"
+	"time"
 
 	"github.com/code-ready/crc/cmd/crc/cmd/config"
 	crcConfig "github.com/code-ready/crc/pkg/crc/config"
@@ -53,24 +55,35 @@ func (c *Client) Close() error {
 	return c.segmentClient.Close()
 }
 
-func (c *Client) Upload(action string, err error) error {
-	if !c.config.Get(config.ConsentTelemetry).AsBool() {
+func (c *Client) Upload(action string, duration time.Duration, err error) error {
+	if c.config.Get(config.ConsentTelemetry).AsString() != "yes" {
 		return nil
 	}
-	logging.Debug("Uploading the error to segment")
 
 	anonymousID, uerr := getUserIdentity(c.telemetryFilePath)
 	if uerr != nil {
 		return uerr
 	}
 
-	t := analytics.NewTraits().
-		Set("action", action).
-		Set("error", err.Error())
+	if err := c.segmentClient.Enqueue(analytics.Identify{
+		AnonymousId: anonymousID,
+		Traits: analytics.NewTraits().
+			Set("os", runtime.GOOS),
+	}); err != nil {
+		return err
+	}
+
+	properties := analytics.NewProperties().
+		Set("success", err == nil).
+		Set("duration", duration.Milliseconds())
+	if err != nil {
+		properties = properties.Set("error", err.Error())
+	}
 
-	return c.segmentClient.Enqueue(analytics.Identify{
+	return c.segmentClient.Enqueue(analytics.Track{
 		AnonymousId: anonymousID,
-		Traits:      t,
+		Event:       action,
+		Properties:  properties,
 	})
 }
 


Test patch:
diff --git a/pkg/crc/segment/segment_test.go b/pkg/crc/segment/segment_test.go
index cdf4faa22..0eabde50c 100644
--- a/pkg/crc/segment/segment_test.go
+++ b/pkg/crc/segment/segment_test.go
@@ -8,7 +8,9 @@ import (
 	"net/http/httptest"
 	"os"
 	"path/filepath"
+	"runtime"
 	"testing"
+	"time"
 
 	cmdConfig "github.com/code-ready/crc/cmd/crc/cmd/config"
 	crcConfig "github.com/code-ready/crc/pkg/crc/config"
@@ -22,8 +24,11 @@ type segmentResponse struct {
 		AnonymousID string `json:"anonymousId"`
 		MessageID   string `json:"messageId"`
 		Traits      struct {
-			Error string `json:"error"`
+			OS string `json:"os"`
 		} `json:"traits"`
+		Properties struct {
+			Error string `json:"error"`
+		} `json:"properties"`
 		Type string `json:"type"`
 	} `json:"batch"`
 	Context struct {
@@ -55,7 +60,7 @@ func mockServer() (chan []byte, *httptest.Server) {
 	return done, server
 }
 
-func newTestConfig(value bool) (*crcConfig.Config, error) {
+func newTestConfig(value string) (*crcConfig.Config, error) {
 	storage := crcConfig.NewEmptyInMemoryStorage()
 	config := crcConfig.New(storage)
 	cmdConfig.RegisterSettings(config)
@@ -75,20 +80,23 @@ func TestClientUploadWithConsent(t *testing.T) {
 	require.NoError(t, err)
 	defer os.RemoveAll(dir)
 
-	config, err := newTestConfig(true)
+	config, err := newTestConfig("yes")
 	require.NoError(t, err)
 
 	c, err := newCustomClient(config, filepath.Join(dir, "telemetry"), server.URL)
 	require.NoError(t, err)
 
-	require.NoError(t, c.Upload("start", errors.New("an error occurred")))
+	require.NoError(t, c.Upload("start", time.Minute, errors.New("an error occurred")))
 	require.NoError(t, c.Close())
 
 	select {
 	case x := <-body:
 		s := segmentResponse{}
 		require.NoError(t, json.Unmarshal(x, &s))
-		require.Equal(t, s.Batch[0].Traits.Error, "an error occurred")
+		require.Equal(t, s.Batch[0].Type, "identify")
+		require.Equal(t, s.Batch[0].Traits.OS, runtime.GOOS)
+		require.Equal(t, s.Batch[1].Type, "track")
+		require.Equal(t, s.Batch[1].Properties.Error, "an error occurred")
 		require.Equal(t, s.Context.App.Name, "crc")
 		require.Equal(t, s.Context.App.Version, version.GetCRCVersion())
 	default:
@@ -105,13 +113,13 @@ func TestClientUploadWithOutConsent(t *testing.T) {
 	require.NoError(t, err)
 	defer os.RemoveAll(dir)
 
-	config, err := newTestConfig(false)
+	config, err := newTestConfig("no")
 	require.NoError(t, err)
 
 	c, err := newCustomClient(config, filepath.Join(dir, "telemetry"), server.URL)
 	require.NoError(t, err)
 
-	require.NoError(t, c.Upload("start", errors.New("an error occurred")))
+	require.NoError(t, c.Upload("start", time.Second, errors.New("an error occurred")))
 	require.NoError(t, c.Close())
 
 	select {
diff --git a/test/integration/features/basic.feature b/test/integration/features/basic.feature
index 8c2c9626d..7a1b4da65 100644
--- a/test/integration/features/basic.feature
+++ b/test/integration/features/basic.feature
@@ -84,6 +84,14 @@ Feature: Basic test
         Then stderr should contain "Checking if user is a member of the Hyper-V Administrators group"
         Then stderr should contain "Checking if the Hyper-V virtual switch exist"
 
+    @darwin @linux @windows
+    Scenario: Request start with monitoring stack
+        When setting config property "enable-cluster-monitoring" to value "true" succeeds
+        And setting config property "memory" to value "14000" succeeds
+        Then starting CRC with default bundle fails
+        And stderr should contain "Too little memory"
+        And setting config property "memory" to value "16000" succeeds
+
     @darwin @linux @windows
     Scenario: CRC start
         When starting CRC with default bundle succeeds
@@ -113,6 +121,16 @@ Feature: Basic test
         Then stdout should contain "To login as a regular user, run 'oc login -u developer -p developer"
         And stdout should contain "To login as an admin, run 'oc login -u kubeadmin -p "
 
+    @darwin @linux @windows
+    Scenario: Monitoring stack check
+        Given checking that CRC is running
+        When executing "eval $(crc oc-env)" succeeds
+        And login to the oc cluster succeeds
+        And executing "oc get pods -n openshift-monitoring" succeeds
+        Then stdout matches ".*cluster-monitoring-operator-\w+-\w+\ *2/2\ *Running.*"
+        And unsetting config property "enable-cluster-monitoring" succeeds
+        And unsetting config property "memory" succeeds
+
     @darwin @linux @windows
     Scenario: CRC forcible stop
         When executing "crc stop -f"