Repository: code-ready/crc
Pull request #1676 (2020-11-16 07:56:35)

Connect ssh only once, and reuse the same connection
It removes plenty of ssh handshakes and should improve the startup time.
    
When crc change the private key, the connection with the previous key is
kept opened and still works.


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

This pull-request has been approved by: *<a href="https://github.com/code-ready/crc/pull/1676#" title="Author self-approved">guillaumerose</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:

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

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":[]} -->
cfergeau: Can the ssh connection time out if it's idle for too long? Does the code deal with this?

Patch:
diff --git a/.travis.yml b/.travis.yml
index 9e13186c3..61ee2a35f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -9,7 +9,7 @@ before_script:
   - echo "deb https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_18.04 /" | sudo tee /etc/apt/sources.list.d/devel:kubic:libcontainers:stable.list
   - curl -L https://download.opensuse.org/repositories/devel:/kubic:/libcontainers:/stable/xUbuntu_18.04/Release.key | sudo apt-key add -
   - sudo apt-get update -qq
-  - sudo apt-get -qq -y install podman slirp4netns
+  - sudo apt-get -qq -y install podman
 
 script:
 - make
diff --git a/Makefile b/Makefile
index 3f3fd5c76..0b55ad10a 100644
--- a/Makefile
+++ b/Makefile
@@ -1,8 +1,8 @@
 SHELL := /bin/bash
 
-BUNDLE_VERSION = 4.6.1
+BUNDLE_VERSION = 4.6.3
 BUNDLE_EXTENSION = crcbundle
-CRC_VERSION = 1.18.0
+CRC_VERSION = 1.19.0
 COMMIT_SHA=$(shell git rev-parse --short HEAD)
 CONTAINER_RUNTIME ?= podman
 
diff --git a/cmd/crc/cmd/start.go b/cmd/crc/cmd/start.go
index 6d0faebd6..bf6518cf4 100644
--- a/cmd/crc/cmd/start.go
+++ b/cmd/crc/cmd/start.go
@@ -145,7 +145,6 @@ func (s *startResult) prettyPrintTo(writer io.Writer) error {
 		"To access the cluster, first set up your environment by following 'crc oc-env' instructions.",
 		fmt.Sprintf("Then you can access it by running 'oc login -u %s -p %s %s'.", s.ClusterConfig.DeveloperCredentials.Username, s.ClusterConfig.DeveloperCredentials.Password, s.ClusterConfig.URL),
 		fmt.Sprintf("To login as an admin, run 'oc login -u %s -p %s %s'.", s.ClusterConfig.AdminCredentials.Username, s.ClusterConfig.AdminCredentials.Password, s.ClusterConfig.URL),
-		"To access the cluster, first set up your environment by following 'crc oc-env' instructions.",
 		"",
 		"You can now run 'crc console' and use these credentials to access the OpenShift web console.",
 	}, "\n"))
diff --git a/cmd/crc/main.go b/cmd/crc/main.go
index c9d924530..f231bde0a 100644
--- a/cmd/crc/main.go
+++ b/cmd/crc/main.go
@@ -2,10 +2,8 @@ package main
 
 import (
 	"github.com/code-ready/crc/cmd/crc/cmd"
-	"github.com/code-ready/crc/pkg/crc/machine/client"
 )
 
 func main() {
-	client.StartDriver()
 	cmd.Execute()
 }
diff --git a/docs/source/topics/proc_deploying-sample-application-with-odo.adoc b/docs/source/topics/proc_deploying-sample-application-with-odo.adoc
index 4b1f0ac3b..bcff017fa 100644
--- a/docs/source/topics/proc_deploying-sample-application-with-odo.adoc
+++ b/docs/source/topics/proc_deploying-sample-application-with-odo.adoc
@@ -41,7 +41,7 @@ $ cd sample-app
 +
 [subs="+quotes,attributes"]
 ----
-$ odo create nodejs --git https://github.com/openshift/nodejs-ex
+$ odo create nodejs --s2i --git https://github.com/openshift/nodejs-ex
 ----
 +
 [NOTE]
diff --git a/pkg/crc/machine/client/client_darwin.go b/pkg/crc/machine/client/client_darwin.go
deleted file mode 100644
index 89babc6e2..000000000
--- a/pkg/crc/machine/client/client_darwin.go
+++ /dev/null
@@ -1,17 +0,0 @@
-package client
-
-import (
-	"os"
-
-	"github.com/code-ready/crc/pkg/crc/exit"
-	"github.com/code-ready/machine/libmachine/drivers/plugin/localbinary"
-)
-
-// StartDriver starts the desired machine driver if necessary.
-func StartDriver() {
-	if os.Getenv(localbinary.PluginEnvKey) == localbinary.PluginEnvVal {
-		exit.WithMessage(1, "Unregistered driver: %s\n", os.Getenv(localbinary.PluginEnvDriverName))
-		return
-	}
-	localbinary.CurrentBinaryIsCRCMachine = true
-}
diff --git a/pkg/crc/machine/client/client_linux.go b/pkg/crc/machine/client/client_linux.go
deleted file mode 100644
index 89babc6e2..000000000
--- a/pkg/crc/machine/client/client_linux.go
+++ /dev/null
@@ -1,17 +0,0 @@
-package client
-
-import (
-	"os"
-
-	"github.com/code-ready/crc/pkg/crc/exit"
-	"github.com/code-ready/machine/libmachine/drivers/plugin/localbinary"
-)
-
-// StartDriver starts the desired machine driver if necessary.
-func StartDriver() {
-	if os.Getenv(localbinary.PluginEnvKey) == localbinary.PluginEnvVal {
-		exit.WithMessage(1, "Unregistered driver: %s\n", os.Getenv(localbinary.PluginEnvDriverName))
-		return
-	}
-	localbinary.CurrentBinaryIsCRCMachine = true
-}
diff --git a/pkg/crc/machine/client/client_windows.go b/pkg/crc/machine/client/client_windows.go
deleted file mode 100644
index 2fde7119a..000000000
--- a/pkg/crc/machine/client/client_windows.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package client
-
-import (
-	"os"
-
-	"github.com/code-ready/crc/pkg/crc/exit"
-	"github.com/code-ready/machine/drivers/hyperv"
-	"github.com/code-ready/machine/libmachine/drivers/plugin"
-	"github.com/code-ready/machine/libmachine/drivers/plugin/localbinary"
-)
-
-// StartDriver starts the desired machine driver if necessary.
-func StartDriver() {
-	if os.Getenv(localbinary.PluginEnvKey) == localbinary.PluginEnvVal {
-		driverName := os.Getenv(localbinary.PluginEnvDriverName)
-		switch driverName {
-		case "hyperv":
-			plugin.RegisterDriver(hyperv.NewDriver("", ""))
-		default:
-			exit.WithMessage(1, "Unregistered driver: %s\n", driverName)
-		}
-		return
-	}
-	localbinary.CurrentBinaryIsCRCMachine = true
-}
diff --git a/pkg/crc/machine/driver.go b/pkg/crc/machine/driver.go
index 4193c7e24..12aa40b06 100644
--- a/pkg/crc/machine/driver.go
+++ b/pkg/crc/machine/driver.go
@@ -6,37 +6,53 @@ import (
 	"github.com/code-ready/machine/libmachine/host"
 )
 
-type valueSetter func(driver *libmachine.VMDriver)
+type valueSetter func(driver *libmachine.VMDriver) bool
 
 func updateDriverValue(host *host.Host, setDriverValue valueSetter) error {
 	driver, err := loadDriverConfig(host)
 	if err != nil {
 		return err
 	}
-	setDriverValue(driver.VMDriver)
+	valueChanged := setDriverValue(driver.VMDriver)
+	if !valueChanged {
+		return nil
+	}
 
 	return updateDriverConfig(host, driver)
 }
 
 func setMemory(host *host.Host, memorySize int) error {
-	memorySetter := func(driver *libmachine.VMDriver) {
+	memorySetter := func(driver *libmachine.VMDriver) bool {
+		if driver.Memory == memorySize {
+			return false
+		}
 		driver.Memory = memorySize
+		return true
 	}
 
 	return updateDriverValue(host, memorySetter)
 }
 
 func setVcpus(host *host.Host, vcpus int) error {
-	vcpuSetter := func(driver *libmachine.VMDriver) {
+	vcpuSetter := func(driver *libmachine.VMDriver) bool {
+		if driver.CPU == vcpus {
+			return false
+		}
 		driver.CPU = vcpus
+		return true
 	}
 
 	return updateDriverValue(host, vcpuSetter)
 }
 
 func setDiskSize(host *host.Host, diskSizeGiB int) error {
-	diskSizeSetter := func(driver *libmachine.VMDriver) {
-		driver.DiskCapacity = config.ConvertGiBToBytes(diskSizeGiB)
+	diskSizeSetter := func(driver *libmachine.VMDriver) bool {
+		capacity := config.ConvertGiBToBytes(diskSizeGiB)
+		if driver.DiskCapacity == capacity {
+			return false
+		}
+		driver.DiskCapacity = capacity
+		return true
 	}
 
 	return updateDriverValue(host, diskSizeSetter)
diff --git a/pkg/crc/machine/hyperkit/constants.go b/pkg/crc/machine/hyperkit/constants.go
index 87083a935..9b2b3a2d8 100644
--- a/pkg/crc/machine/hyperkit/constants.go
+++ b/pkg/crc/machine/hyperkit/constants.go
@@ -6,7 +6,7 @@ import "fmt"
 
 const (
 	MachineDriverCommand = "crc-driver-hyperkit"
-	MachineDriverVersion = "0.12.10-beta1"
+	MachineDriverVersion = "0.12.10"
 	HyperKitCommand      = "hyperkit"
 	HyperKitVersion      = "v0.20200224-44-gb54460"
 )
diff --git a/pkg/crc/machine/libvirt/constants.go b/pkg/crc/machine/libvirt/constants.go
index 346f385b1..ec1720ada 100644
--- a/pkg/crc/machine/libvirt/constants.go
+++ b/pkg/crc/machine/libvirt/constants.go
@@ -16,7 +16,7 @@ const (
 
 const (
 	MachineDriverCommand = "crc-driver-libvirt"
-	MachineDriverVersion = "0.12.11"
+	MachineDriverVersion = "0.12.12"
 )
 
 var (
diff --git a/pkg/crc/machine/machine.go b/pkg/crc/machine/machine.go
index 07a878ea2..ab7e51455 100644
--- a/pkg/crc/machine/machine.go
+++ b/pkg/crc/machine/machine.go
@@ -269,7 +269,11 @@ func (client *client) Start(startConfig StartConfig) (*StartResult, error) {
 	if err != nil {
 		return nil, errors.Wrap(err, "Error getting the IP")
 	}
-	sshRunner := crcssh.CreateRunner(instanceIP, getSSHPort(client.useVSock()), crcBundleMetadata.GetSSHKeyPath(), constants.GetPrivateKeyPath())
+	sshRunner, err := crcssh.CreateRunner(instanceIP, getSSHPort(client.useVSock()), crcBundleMetadata.GetSSHKeyPath(), constants.GetPrivateKeyPath())
+	if err != nil {
+		return nil, errors.Wrap(err, "Error creating the ssh client")
+	}
+	defer sshRunner.Close()
 
 	logging.Debug("Waiting until ssh is available")
 	if err := cluster.WaitForSSH(sshRunner); err != nil {
@@ -602,7 +606,11 @@ func (client *client) Status() (*ClusterStatusResult, error) {
 	if err != nil {
 		return nil, errors.Wrap(err, "Error getting ip")
 	}
-	sshRunner := crcssh.CreateRunner(ip, getSSHPort(client.useVSock()), constants.GetPrivateKeyPath())
+	sshRunner, err := crcssh.CreateRunner(ip, getSSHPort(client.useVSock()), constants.GetPrivateKeyPath())
+	if err != nil {
+		return nil, errors.Wrap(err, "Error creating the ssh client")
+	}
+	defer sshRunner.Close()
 	// check if all the clusteroperators are running
 	diskSize, diskUse, err := cluster.GetRootPartitionUsage(sshRunner)
 	if err != nil {
diff --git a/pkg/crc/preflight/preflight_checks_common.go b/pkg/crc/preflight/preflight_checks_common.go
index a66cddcee..3a21230be 100644
--- a/pkg/crc/preflight/preflight_checks_common.go
+++ b/pkg/crc/preflight/preflight_checks_common.go
@@ -9,6 +9,7 @@ import (
 	"github.com/code-ready/crc/pkg/crc/cache"
 	"github.com/code-ready/crc/pkg/crc/constants"
 	"github.com/code-ready/crc/pkg/crc/logging"
+	"github.com/code-ready/crc/pkg/crc/machine/bundle"
 	"github.com/code-ready/crc/pkg/crc/validation"
 	"github.com/code-ready/crc/pkg/embed"
 	"github.com/docker/go-units"
@@ -30,11 +31,11 @@ var genericPreflightChecks = [...]Check{
 		fix:              fixGoodhostsExecutableCached,
 	},
 	{
-		configKeySuffix:  "check-bundle-cached",
-		checkDescription: "Checking if CRC bundle is cached in '$HOME/.crc'",
-		check:            checkBundleCached,
-		fixDescription:   "Unpacking bundle from the CRC executable",
-		fix:              fixBundleCached,
+		configKeySuffix:  "check-bundle-extracted",
+		checkDescription: "Checking if CRC bundle is extracted in '$HOME/.crc'",
+		check:            checkBundleExtracted,
+		fixDescription:   "Extracting bundle from the CRC executable",
+		fix:              fixBundleExtracted,
 		flags:            SetupOnly,
 	},
 	{
@@ -48,7 +49,7 @@ var genericPreflightChecks = [...]Check{
 	},
 }
 
-func checkBundleCached() error {
+func checkBundleExtracted() error {
 	if !constants.BundleEmbedded() {
 		return nil
 	}
@@ -58,7 +59,7 @@ func checkBundleCached() error {
 	return nil
 }
 
-func fixBundleCached() error {
+func fixBundleExtracted() error {
 	// Should be removed after 1.19 release
 	// This check will ensure correct mode for `~/.crc/cache` directory
 	// in case it exists.
@@ -67,12 +68,15 @@ func fixBundleCached() error {
 	}
 	if constants.BundleEmbedded() {
 		bundleDir := filepath.Dir(constants.DefaultBundlePath)
-		err := os.MkdirAll(bundleDir, 0775)
-		if err != nil && !os.IsExist(err) {
-			return fmt.Errorf("Cannot create directory %s", bundleDir)
+		if err := os.MkdirAll(bundleDir, 0775); err != nil {
+			return fmt.Errorf("Cannot create directory %s: %v", bundleDir, err)
 		}
 
-		return embed.Extract(filepath.Base(constants.DefaultBundlePath), constants.DefaultBundlePath)
+		if err := embed.Extract(filepath.Base(constants.DefaultBundlePath), constants.DefaultBundlePath); err != nil {
+			return err
+		}
+		_, err := bundle.Extract(constants.DefaultBundlePath)
+		return err
 	}
 	return fmt.Errorf("CRC bundle is not embedded in the executable")
 }
diff --git a/pkg/crc/ssh/client.go b/pkg/crc/ssh/client.go
index d75154bda..1cd5c6023 100644
--- a/pkg/crc/ssh/client.go
+++ b/pkg/crc/ssh/client.go
@@ -2,7 +2,6 @@ package ssh
 
 import (
 	"fmt"
-	"io"
 	"io/ioutil"
 	"net"
 	"strconv"
@@ -14,46 +13,43 @@ import (
 
 type Client interface {
 	Output(command string) (string, error)
+	Close()
 }
 
 type NativeClient struct {
-	Config   ssh.ClientConfig
+	User     string
 	Hostname string
 	Port     int
-}
+	Keys     []string
 
-type Auth struct {
-	Keys []string
+	conn *ssh.Client
 }
 
-func NewClient(user string, host string, port int, auth *Auth) (Client, error) {
-	config, err := NewNativeConfig(user, auth)
-	if err != nil {
-		return nil, fmt.Errorf("Error getting config for native Go SSH: %s", err)
-	}
-
+func NewClient(user string, host string, port int, keys ...string) (Client, error) {
 	return &NativeClient{
-		Config:   config,
+		User:     user,
 		Hostname: host,
 		Port:     port,
+		Keys:     keys,
 	}, nil
 }
 
-func NewNativeConfig(user string, auth *Auth) (ssh.ClientConfig, error) {
+func clientConfig(user string, keys []string) (*ssh.ClientConfig, error) {
 	var (
 		privateKeys []ssh.Signer
 		authMethods []ssh.AuthMethod
 	)
 
-	for _, k := range auth.Keys {
+	for _, k := range keys {
 		key, err := ioutil.ReadFile(k)
 		if err != nil {
-			return ssh.ClientConfig{}, err
+			log.Debugf("Cannot read private ssh key %s", k)
+			continue
 		}
 
 		privateKey, err := ssh.ParsePrivateKey(key)
 		if err != nil {
-			return ssh.ClientConfig{}, err
+			return nil, err
 		}
 
 		privateKeys = append(privateKeys, privateKey)
@@ -63,7 +59,7 @@ func NewNativeConfig(user string, auth *Auth) (ssh.ClientConfig, error) {
 		authMethods = append(authMethods, ssh.PublicKeys(privateKeys...))
 	}
 
-	return ssh.ClientConfig{
+	return &ssh.ClientConfig{
 		User: user,
 		Auth: authMethods,
 		// #nosec G106
@@ -72,25 +68,30 @@ func NewNativeConfig(user string, auth *Auth) (ssh.ClientConfig, error) {
 	}, nil
 }
 
-func (client *NativeClient) session() (*ssh.Client, *ssh.Session, error) {
-	conn, err := ssh.Dial("tcp", net.JoinHostPort(client.Hostname, strconv.Itoa(client.Port)), &client.Config)
-	if err != nil {
-		return nil, nil, err
+func (client *NativeClient) session() (*ssh.Session, error) {
+	if client.conn == nil {
+		var err error
+		config, err := clientConfig(client.User, client.Keys)
+		if err != nil {
+			return nil, fmt.Errorf("Error getting config for native Go SSH: %s", err)
+		}
+		client.conn, err = ssh.Dial("tcp", net.JoinHostPort(client.Hostname, strconv.Itoa(client.Port)), config)
+		if err != nil {
+			return nil, err
+		}
 	}
-	session, err := conn.NewSession()
+	session, err := client.conn.NewSession()
 	if err != nil {
-		_ = conn.Close()
-		return nil, nil, err
+		return nil, err
 	}
-	return conn, session, err
+	return session, err
 }
 
 func (client *NativeClient) Output(command string) (string, error) {
-	conn, session, err := client.session()
+	session, err := client.session()
 	if err != nil {
 		return "", err
 	}
-	defer closeConn(conn)
 	defer session.Close()
 
 	output, err := session.CombinedOutput(command)
@@ -100,8 +101,11 @@ func (client *NativeClient) Output(command string) (string, error) {
 	return string(output), nil
 }
 
-func closeConn(c io.Closer) {
-	err := c.Close()
+func (client *NativeClient) Close() {
+	if client.conn == nil {
+		return
+	}
+	err := client.conn.Close()
 	if err != nil {
 		log.Debugf("Error closing SSH Client: %s", err)
 	}
diff --git a/pkg/crc/ssh/ssh.go b/pkg/crc/ssh/ssh.go
index 13fc20e2c..170ae4400 100644
--- a/pkg/crc/ssh/ssh.go
+++ b/pkg/crc/ssh/ssh.go
@@ -13,17 +13,21 @@ import (
 )
 
 type Runner struct {
-	ip             string
-	port           int
-	privateSSHKeys []string
+	client Client
 }
 
-func CreateRunner(ip string, port int, privateKeys ...string) *Runner {
-	return &Runner{
-		ip:             ip,
-		port:           port,
-		privateSSHKeys: privateKeys,
+func CreateRunner(ip string, port int, privateKeys ...string) (*Runner, error) {
+	client, err := NewClient(constants.DefaultSSHUser, ip, port, privateKeys...)
+	if err != nil {
+		return nil, err
 	}
+	return &Runner{
+		client: client,
+	}, nil
+}
+
+func (runner *Runner) Close() {
+	runner.client.Close()
 }
 
 // Create a host using the driver's config
@@ -53,27 +57,13 @@ func (runner *Runner) CopyFile(srcFilename string, destFilename string, mode os.
 }
 
 func (runner *Runner) runSSHCommandFromDriver(command string, runPrivate bool) (string, error) {
-	var availableKeys []string
-	for _, privateKey := range runner.privateSSHKeys {
-		if _, err := os.Stat(privateKey); err == nil {
-			availableKeys = append(availableKeys, privateKey)
-		}
-	}
-
-	client, err := NewClient(constants.DefaultSSHUser, runner.ip, runner.port, &Auth{
-		Keys: availableKeys,
-	})
-	if err != nil {
-		return "", err
-	}
-
 	if runPrivate {
 		logging.Debugf("About to run SSH command with hidden output")
 	} else {
 		logging.Debugf("About to run SSH command:\n%s", command)
 	}
 
-	output, err := client.Output(command)
+	output, err := runner.client.Output(command)
 	if runPrivate {
 		if err != nil {
 			logging.Debugf("SSH command failed")
diff --git a/pkg/crc/validation/validation.go b/pkg/crc/validation/validation.go
index cc46401a3..653f11643 100644
--- a/pkg/crc/validation/validation.go
+++ b/pkg/crc/validation/validation.go
@@ -6,6 +6,7 @@ import (
 	"net"
 	"os"
 	"path/filepath"
+	"runtime"
 	"strings"
 
 	"github.com/code-ready/crc/pkg/crc/constants"
@@ -35,6 +36,11 @@ func ValidateDiskSize(value int) error {
 	if value < constants.DefaultDiskSize {
 		return fmt.Errorf("requires disk size in GiB >= %d", constants.DefaultDiskSize)
 	}
+	// https://github.com/code-ready/machine-driver-hyperkit/issues/18
+	if runtime.GOOS == "darwin" && value > constants.DefaultDiskSize {
+		return fmt.Errorf("Disk resizing is not supported on macOS")
+	}
+
 	return nil
 }
 
diff --git a/vendor/github.com/code-ready/machine/libmachine/drivers/plugin/register_driver.go b/vendor/github.com/code-ready/machine/libmachine/drivers/plugin/register_driver.go
deleted file mode 100644
index 303e19a54..000000000
--- a/vendor/github.com/code-ready/machine/libmachine/drivers/plugin/register_driver.go
+++ /dev/null
@@ -1,69 +0,0 @@
-package plugin
-
-import (
-	"fmt"
-	"net"
-	"net/http"
-	"net/rpc"
-	"os"
-	"time"
-
-	"github.com/code-ready/machine/libmachine/drivers"
-	"github.com/code-ready/machine/libmachine/drivers/plugin/localbinary"
-	rpcdriver "github.com/code-ready/machine/libmachine/drivers/rpc"
-	"github.com/code-ready/machine/libmachine/log"
-	"github.com/code-ready/machine/libmachine/version"
-)
-
-var (
-	heartbeatTimeout = 10 * time.Second
-)
-
-func RegisterDriver(d drivers.Driver) {
-	if os.Getenv(localbinary.PluginEnvKey) != localbinary.PluginEnvVal {
-		fmt.Fprintf(os.Stderr, `This is a hypervisor plugin binary for CodeReady Containers.
-Please use this plugin through the main 'crc' binary.
-(Driver version: %s, API version: %d)
-`, d.DriverVersion(),
-			version.APIVersion)
-		os.Exit(1)
-	}
-
-	log.SetDebug(true)
-	os.Setenv("MACHINE_DEBUG", "1")
-
-	rpcd := rpcdriver.NewRPCServerDriver(d)
-	if err := rpc.RegisterName(rpcdriver.RPCServiceNameV0, rpcd); err != nil {
-		log.Error(err)
-	}
-	if err := rpc.RegisterName(rpcdriver.RPCServiceNameV1, rpcd); err != nil {
-		log.Error(err)
-	}
-	rpc.HandleHTTP()
-
-	listener, err := net.Listen("tcp", "127.0.0.1:0")
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "Error loading RPC server: %s\n", err)
-		os.Exit(1)
-	}
-	defer listener.Close()
-
-	fmt.Println(listener.Addr())
-
-	go func() {
-		_ = http.Serve(listener, nil)
-	}()
-
-	for {
-		select {
-		case <-rpcd.CloseCh:
-			log.Debug("Closing plugin on server side")
-			os.Exit(0)
-		case <-rpcd.HeartbeatCh:
-			continue
-		case <-time.After(heartbeatTimeout):
-			// TODO: Add heartbeat retry logic
-			os.Exit(1)
-		}
-	}
-}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 0a1f12b06..414e3755d 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -42,7 +42,6 @@ github.com/code-ready/machine/drivers/none
 github.com/code-ready/machine/libmachine
 github.com/code-ready/machine/libmachine/auth
 github.com/code-ready/machine/libmachine/drivers
-github.com/code-ready/machine/libmachine/drivers/plugin
 github.com/code-ready/machine/libmachine/drivers/plugin/localbinary
 github.com/code-ready/machine/libmachine/drivers/rpc
 github.com/code-ready/machine/libmachine/engine


Test patch:
diff --git a/cmd/crc/cmd/start_test.go b/cmd/crc/cmd/start_test.go
index f22dac596..a8b318448 100644
--- a/cmd/crc/cmd/start_test.go
+++ b/cmd/crc/cmd/start_test.go
@@ -29,7 +29,6 @@ func TestRenderActionPlainSuccess(t *testing.T) {
 To access the cluster, first set up your environment by following 'crc oc-env' instructions.
 Then you can access it by running 'oc login -u developer -p developer https://api.crc.testing:6443'.
 To login as an admin, run 'oc login -u kubeadmin -p secret https://api.crc.testing:6443'.
-To access the cluster, first set up your environment by following 'crc oc-env' instructions.
 
 You can now run 'crc console' and use these credentials to access the OpenShift web console.
 `, out.String())
diff --git a/pkg/crc/ssh/ssh_test.go b/pkg/crc/ssh/ssh_test.go
index 9ef829b56..96bf698cc 100644
--- a/pkg/crc/ssh/ssh_test.go
+++ b/pkg/crc/ssh/ssh_test.go
@@ -36,7 +36,7 @@ func TestRunner(t *testing.T) {
 	require.NoError(t, err)
 	defer listener.Close()
 
-	createSSHServer(t, listener, clientKey, func(input string) (byte, string) {
+	totalConn := createSSHServer(t, listener, clientKey, func(input string) (byte, string) {
 		escaped := fmt.Sprintf("%q", input)
 		if escaped == `"echo hello"` {
 			return 0, "hello"
@@ -48,7 +48,9 @@ func TestRunner(t *testing.T) {
 	})
 
 	addr := listener.Addr().String()
-	runner := CreateRunner(ipFor(addr), portFor(addr), clientKeyFile)
+	runner, err := CreateRunner(ipFor(addr), portFor(addr), clientKeyFile)
+	assert.NoError(t, err)
+	defer runner.Close()
 
 	bin, err := runner.Run("echo hello")
 	assert.NoError(t, err)
@@ -60,9 +62,12 @@ func TestRunner(t *testing.T) {
 	assert.NoError(t, err)
 	assert.Equal(t, "hello", stdout)
 	assert.Empty(t, stderr)
+
+	assert.Equal(t, 1, *totalConn)
 }
 
-func createSSHServer(t *testing.T, listener net.Listener, clientKey *rsa.PrivateKey, fun func(string) (byte, string)) {
+func createSSHServer(t *testing.T, listener net.Listener, clientKey *rsa.PrivateKey, fun func(string) (byte, string)) *int {
+	totalConn := 0
 	config := &ssh.ServerConfig{
 		PublicKeyCallback: func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
 			pub, err := ssh.NewPublicKey(&clientKey.PublicKey)
@@ -93,6 +98,7 @@ func createSSHServer(t *testing.T, listener net.Listener, clientKey *rsa.Private
 				logrus.Debugf("cannot accept connection: %v", err)
 				return
 			}
+			totalConn++
 
 			conn, chans, reqs, err := ssh.NewServerConn(nConn, config)
 			require.NoError(t, err)
@@ -126,6 +132,7 @@ func createSSHServer(t *testing.T, listener net.Listener, clientKey *rsa.Private
 			}
 		}
 	}()
+	return &totalConn
 }
 
 func writePrivateKey(t *testing.T, clientKeyFile string, clientKey *rsa.PrivateKey) {
diff --git a/test/integration/crcsuite/collect.go b/test/integration/crcsuite/collect.go
index 9d1ace498..38e7050cb 100644
--- a/test/integration/crcsuite/collect.go
+++ b/test/integration/crcsuite/collect.go
@@ -182,9 +182,7 @@ func (collector *VMCommandCollector) Collect(w Writer) error {
 	if err != nil {
 		return err
 	}
-	ssh, err := ssh.NewClient(constants.DefaultSSHUser, ip, 22, &ssh.Auth{
-		Keys: []string{constants.GetPrivateKeyPath()},
-	})
+	ssh, err := ssh.NewClient(constants.DefaultSSHUser, ip, 22, constants.GetPrivateKeyPath())
 	if err != nil {
 		return err
 	}
@@ -205,9 +203,7 @@ func (collector *ContainerLogCollector) Collect(w Writer) error {
 	if err != nil {
 		return err
 	}
-	ssh, err := ssh.NewClient(constants.DefaultSSHUser, ip, 22, &ssh.Auth{
-		Keys: []string{constants.GetPrivateKeyPath()},
-	})
+	ssh, err := ssh.NewClient(constants.DefaultSSHUser, ip, 22, constants.GetPrivateKeyPath())
 	if err != nil {
 		return err
 	}
diff --git a/test/integration/features/basic.feature b/test/integration/features/basic.feature
index 07185795d..049413e7b 100644
--- a/test/integration/features/basic.feature
+++ b/test/integration/features/basic.feature
@@ -37,7 +37,7 @@ Feature: Basic test
     @linux
     Scenario: CRC setup on Linux
         When executing "crc setup" succeeds
-        And stderr should contain "Checking if CRC bundle is cached in '$HOME/.crc'"
+        And stderr should contain "Checking if CRC bundle is extracted in '$HOME/.crc'"
         And stderr should contain "Checking if running as non-root"
         And stderr should contain "Checking if Virtualization is enabled"
         And stderr should contain "Checking if KVM is enabled"
diff --git a/test/integration/features/cert_rotation.feature b/test/integration/features/cert_rotation.feature
index e1ca76cae..0cacde1d1 100644
--- a/test/integration/features/cert_rotation.feature
+++ b/test/integration/features/cert_rotation.feature
@@ -18,7 +18,8 @@ Feature: Check the cert is rotation happen after it expire
       Then login to the oc cluster succeeds
 
     Scenario: Set clock back to original time
-      Given executing "sudo timedatectl set-ntp on" succeeds
+      When executing "sudo date -s '-3 month'" succeeds
+      And executing "sudo timedatectl set-ntp on" succeeds
 
     Scenario: CRC delete and cleanup
       When executing "crc delete -f" succeeds
diff --git a/test/integration/features/config.feature b/test/integration/features/config.feature
index 9599931ad..155d353c8 100644
--- a/test/integration/features/config.feature
+++ b/test/integration/features/config.feature
@@ -67,7 +67,7 @@ Checks whether CRC `config set` command works as expected in conjunction with `c
         @darwin
         Examples: Config warnings on Mac
             | property                             | value1 | value2 |
-            | warn-check-bundle-cached             | true   | false  |
+            | warn-check-bundle-extracted          | true   | false  |
             | warn-check-hosts-file-permissions    | true   | false  |
             | warn-check-hyperkit-driver           | true   | false  |
             | warn-check-hyperkit-installed        | true   | false  |
@@ -77,7 +77,7 @@ Checks whether CRC `config set` command works as expected in conjunction with `c
         @linux
         Examples: Config warnings on Linux
             | property                             | value1 | value2 |
-            | warn-check-bundle-cached             | true   | false  |
+            | warn-check-bundle-extracted          | true   | false  |
             | warn-check-crc-dnsmasq-file          | true   | false  |
             | warn-check-crc-network               | true   | false  |
             | warn-check-crc-network-active        | true   | false  |
@@ -97,7 +97,7 @@ Checks whether CRC `config set` command works as expected in conjunction with `c
         Examples: Config warnings on Windows
             | property                        | value1 | value2 |
             | warn-check-administrator-user   | true   | false  |
-            | warn-check-bundle-cached        | true   | false  |
+            | warn-check-bundle-extracted     | true   | false  |
             | warn-check-hyperv-installed     | true   | false  |
             | warn-check-hyperv-switch        | true   | false  |
             | warn-check-user-in-hyperv-group | true   | false  |
@@ -120,7 +120,7 @@ Checks whether CRC `config set` command works as expected in conjunction with `c
         @darwin
         Examples:
             | property                             | value1 | value2 |
-            | skip-check-bundle-cached             | true   | false  |
+            | skip-check-bundle-extracted          | true   | false  |
             | skip-check-hosts-file-permissions    | true   | false  |
             | skip-check-hyperkit-driver           | true   | false  |
             | skip-check-hyperkit-installed        | true   | false  |
@@ -130,7 +130,7 @@ Checks whether CRC `config set` command works as expected in conjunction with `c
         @linux
         Examples:
             | property                             | value1 | value2 |
-            | skip-check-bundle-cached             | true   | false  |
+            | skip-check-bundle-extracted          | true   | false  |
             | skip-check-crc-dnsmasq-file          | true   | false  |
             | skip-check-crc-network               | true   | false  |
             | skip-check-crc-network-active        | true   | false  |
@@ -150,7 +150,7 @@ Checks whether CRC `config set` command works as expected in conjunction with `c
         Examples:
             | property                        | value1 | value2 |
             | skip-check-administrator-user   | true   | false  |
-            | skip-check-bundle-cached        | true   | false  |
+            | skip-check-bundle-extracted     | true   | false  |
             | skip-check-hyperv-installed     | true   | false  |
             | skip-check-hyperv-switch        | true   | false  |
             | skip-check-user-in-hyperv-group | true   | false  |