Repository: code-ready/crc
Pull request #2071 (2021-03-06 00:38:05)

Add support for dynamically checking network stack
This is a naive attempt to alter the network stack selection on Linux from hard-coded by distribution to seeing what the host is presently running with.

I don't think any changes to the test suite are needed as I didn't add/alter any checks, but I'm not sure if there needs to be a new check for the dnsmasq list to remove any systemd-resolved related dispatch files.

None of this code has been tested, so unsure if the `systemd-resolve` command will actually work.

This looks to address notes in #1991 #2056 and #1572

(I am presently fighting with VSCode on some linting that made it into the commit)

centos-ci: Can one of the admins verify this patch?
openshift-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/code-ready/crc/pull/2071#" title="Author self-approved">omenos</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **gbraad** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @gbraad` in a comment when ready.

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

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

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

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":["gbraad"]} -->
openshift-ci-robot: Hi @omenos. Thanks for your PR.

I'm waiting for a [code-ready](https://github.com/orgs/code-ready/people) member to verify that this patch is reasonable to test. If it is, they should reply with `/ok-to-test` on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should [join the org](https://github.com/orgs/code-ready/people) to skip this step.

Once the patch is verified, the new status will be reflected by the `ok-to-test` label.

I understand the commands that are listed [here](https://go.k8s.io/bot-commands?repo=code-ready%2Fcrc).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>

omenos: Some of the tests for `preflight_linux_test.go` need to be redesigned, as they are writen with distro specific choices hardcoded.
praveenkumar: /ok-to-test
guillaumerose: Thanks for your PR! Let's focus on the feature now and fix the tests after. I am sure we can mock `os.Stat` somehow.

@cfergeau WDYT of this new way of checking network manager ?
cfergeau: > @cfergeau WDYT of this new way of checking network manager ?

Slightly worried we're going to hit corner cases of `checkSystemdResolvedIsRunning`. This is not going to do the right thing on a distro where `DNSStubListener` is set to `no` in `/etc/systemd/resolved.conf`. Before, this code was mostly used as a failsafe on distros where we were sure we want to use systemd-resolved. Now, it's going to be this method which determines if we use systemd-resolved or dnsmasq, so it becomes more important to get it right.

I'll need to take a look at a few non-systemd-resolved distros to make sure they don't use this setup. We can move forward with this in the mean time.

cfergeau: > > @cfergeau WDYT of this new way of checking network manager ?
> 
> Slightly worried we're going to hit corner cases of `checkSystemdResolvedIsRunning`. This is not going to do the right thing on a distro where `DNSStubListener` is set to `no` in `/etc/systemd/resolved.conf`. Before, this code was mostly used as a failsafe on distros where we were sure we want to use systemd-resolved. Now, it's going to be this method which determines if we use systemd-resolved or dnsmasq, so it becomes more important to get it right.
> 
> I'll need to take a look at a few non-systemd-resolved distros to make sure they don't use this setup. We can move forward with this in the mean time.

I think what I had in mind was systemd-resolved on RHEL8 which has a default configuration where it does not bind on 127.0.0.53:53, but can still get UDP DNS queries. The RHEL doc linked in https://github.com/code-ready/crc/pull/2056 explains how to configure this properly (NM configuration change is needed). Since systemd-resolved is not running by default on RHEL (tried a fresh 8.3 install), I don't think this is an issue. So it should be fine to detect systemd-resolved at runtime instead of the distro matching we currently are doing.

omenos: If approved and merged, #2056 will likely need some changes.
praveenkumar: /retest
openshift-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/code-ready/crc/pull/2071#pullrequestreview-615240145" title="Approved">gbraad</a>*, *<a href="https://github.com/code-ready/crc/pull/2071#" title="Author self-approved">omenos</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)~~ [gbraad]

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":[]} -->
gbraad: have not been able to verify this... but the impact seems minimal

gbraad: > If approved and merged, #2056 will likely need some changes.

can you detail this?
omenos: > can you detail this?

The doc change details how to configure a RHEL 8.3 system using systemd-resolved for CRC usage. Those changes include disabling preflight checks, a step which wouldn’t be needed with this patch.

However, until this patch or a similar one is really approved/merged, I wouldn’t touch those docs. And the mock tests still need to be fixed in this PR.
cfergeau: I've tested the current version of this PR on freshly installed ubuntu 20.04/Fedora 32/Fedora 33/RHEL8.3 and they all worked fine.
openshift-ci-robot: New changes are detected. LGTM label has been removed.
openshift-ci[bot]: @omenos: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
ci/prow/images | 0d5bd6542adf8b0b39ff317eb8fca60f17a4df77 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/code-ready_crc/2071/pull-ci-code-ready-crc-master-images/1374414955121479680) | `/test images`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=code-ready&repo=crc&pr=2071). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:omenos).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
openshift-ci[bot]: @omenos: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
ci/prow/images | 0d5bd6542adf8b0b39ff317eb8fca60f17a4df77 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/code-ready_crc/2071/pull-ci-code-ready-crc-master-images/1374414955121479680) | `/test images`
ci/prow/e2e-crc | 0d5bd6542adf8b0b39ff317eb8fca60f17a4df77 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/code-ready_crc/2071/pull-ci-code-ready-crc-master-e2e-crc/1374414955083730944) | `/test e2e-crc`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=code-ready&repo=crc&pr=2071). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:omenos).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
omenos: @guillaumerose I took a shot at the tests, but they don't seem to have worked out. I may have been overthinking what you were suggesting. I'm a little confused why `checkSystemdResolvedIsRunning()` function isn't being recognized within the `preflight` package according to some of the tests run above. If you would like, I can revert this commit, add the small doc change that should accompany it, and let you handle the tests the way you see fit. 
cfergeau: > @guillaumerose I took a shot at the tests, but they don't seem to have worked out. I may have been overthinking what you were suggesting. I'm a little confused why `checkSystemdResolvedIsRunning()` function isn't being recognized within the `preflight` package according to some of the tests run above. If you would like, I can revert this commit, add the small doc change that should accompany it, and let you handle the tests the way you see fit.

The code in `*_linux.go` files is only built on linux. `checkSystemdResolvedIsRunning` is defined in such a file, so it can't be used in `preflight.go`: `preflight.go` is built on all 3 platforms (macOS, Windows, Linux) while `checkSystemdResolvedIsRunning` is linux-only. If you move the logic to `getPreflightChecks` in `preflight_linux.go` (which is also only built on linux), then it builds fine. I did not try to fix the test cases.

```diff
diff --git a/pkg/crc/preflight/preflight.go b/pkg/crc/preflight/preflight.go
index 5bc8414c..05686689 100644
--- a/pkg/crc/preflight/preflight.go
+++ b/pkg/crc/preflight/preflight.go
@@ -155,12 +155,7 @@ func StartPreflightChecks(config config.Storage) error {
        experimentalFeatures := config.Get(cmdConfig.ExperimentalFeatures).AsBool()
        mode := network.ParseMode(config.Get(cmdConfig.NetworkMode).AsString())
        trayAutostart := config.Get(cmdConfig.AutostartTray).AsBool()
-       usingSystemdResolved := checkSystemdResolvedIsRunning()
-       useSystemdResolved := false
-       if usingSystemdResolved == nil {
-               useSystemdResolved = true
-       }
-       if err := doPreflightChecks(config, getPreflightChecks(experimentalFeatures, trayAutostart, mode, useSystemdResolved)); err != nil {
+       if err := doPreflightChecks(config, getPreflightChecks(experimentalFeatures, trayAutostart, mode)); err != nil {
                return &errors.PreflightError{Err: err}
        }
 
@@ -172,12 +167,7 @@ func SetupHost(config config.Storage) error {
        experimentalFeatures := config.Get(cmdConfig.ExperimentalFeatures).AsBool()
        mode := network.ParseMode(config.Get(cmdConfig.NetworkMode).AsString())
        trayAutostart := config.Get(cmdConfig.AutostartTray).AsBool()
-       usingSystemdResolved := checkSystemdResolvedIsRunning()
-       useSystemdResolved := false
-       if usingSystemdResolved == nil {
-               useSystemdResolved = true
-       }
-       return doFixPreflightChecks(config, getPreflightChecks(experimentalFeatures, trayAutostart, mode, useSystemdResolved))
+       return doFixPreflightChecks(config, getPreflightChecks(experimentalFeatures, trayAutostart, mode))
 }
 
 func RegisterSettings(config config.Schema) {
diff --git a/pkg/crc/preflight/preflight_linux.go b/pkg/crc/preflight/preflight_linux.go
index c73b34d1..25c73680 100644
--- a/pkg/crc/preflight/preflight_linux.go
+++ b/pkg/crc/preflight/preflight_linux.go
@@ -215,8 +215,10 @@ func getAllPreflightChecks() []Check {
        return checks
 }
 
-func getPreflightChecks(_ bool, _ bool, networkMode network.Mode, systemdResolved bool) []Check {
-       return getPreflightChecksForDistro(distro(), networkMode, systemdResolved)
+func getPreflightChecks(_ bool, _ bool, networkMode network.Mode) []Check {
+       usingSystemdResolved := checkSystemdResolvedIsRunning()
+
+       return getPreflightChecksForDistro(distro(), networkMode, usingSystemdResolved == nil)
 }
 
 func getNetworkChecksForDistro(networkMode network.Mode, systemdResolved bool) []Check {
```
openshift-ci[bot]: @omenos: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
ci/prow/e2e-crc | 017af93c91bd7765b4238b7ee52dffa6fba1850a | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/code-ready_crc/2071/pull-ci-code-ready-crc-master-e2e-crc/1374788030635708416) | `/test e2e-crc`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=code-ready&repo=crc&pr=2071). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:omenos).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
omenos: /retest
openshift-ci[bot]: @omenos: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
ci/prow/e2e-crc | 017af93c91bd7765b4238b7ee52dffa6fba1850a | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/code-ready_crc/2071/pull-ci-code-ready-crc-master-e2e-crc/1374825007846264832) | `/test e2e-crc`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=code-ready&repo=crc&pr=2071). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:omenos).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
openshift-ci[bot]: @omenos: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
ci/prow/e2e-crc | a25dc26dcdef01d5a229128fabb4199c55b01d6b | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/code-ready_crc/2071/pull-ci-code-ready-crc-master-e2e-crc/1374867795132551168) | `/test e2e-crc`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=code-ready&repo=crc&pr=2071). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:omenos).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
omenos: Made the requested changes and fixed issues with the tests (forgot to create a third `unexpected` test case).

Something that did seem a bit strange is that the `TestApi` and `TestHTTPApi` tests will fail if the `systemdResolved` component in the `getAllPreflightChecks` function is set to false, which is what I had it as originally. The API tests will then complain that they couldn't find the dispatcher script and bomb out.

However, with those problems out of the way, I don't know if I can solve the current e2e-crc and PR issues, as they seem (to me) completely unrelated to my code changes.
praveenkumar: /retest
cfergeau: > Something that did seem a bit strange is that the `TestApi` and `TestHTTPApi` tests will fail if the `systemdResolved` component in the `getAllPreflightChecks` function is set to false, which is what I had it as originally. The API tests will then complain that they couldn't find the dispatcher script and bomb out.

This happens if `getPreflightChecks` and `getAllPreflightChecks` are out of sync, the api tests call `doRegisterSettings` which uses `getAllPreflightChecks`. Then it disables all the preflight checks which were registered through `doRegisterSettings`. The failure you saw happened because this specific tests was not disabled correctly because the 2 lists were different. If you change both `getPreflightChecks` and `getAllPreflightChecks` to use false then the error no longer occurs.
openshift-ci[bot]: @omenos: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
ci/prow/e2e-crc | 171ef9de809b991d3be3aa9a90f666a402b40fc5 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/code-ready_crc/2071/pull-ci-code-ready-crc-master-e2e-crc/1375153179448053760) | `/test e2e-crc`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=code-ready&repo=crc&pr=2071). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:omenos).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
omenos: /retest
openshift-ci[bot]: @omenos: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
ci/prow/e2e-crc | 171ef9de809b991d3be3aa9a90f666a402b40fc5 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/code-ready_crc/2071/pull-ci-code-ready-crc-master-e2e-crc/1375220477596798976) | `/test e2e-crc`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=code-ready&repo=crc&pr=2071). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:omenos).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
praveenkumar: /retest
openshift-ci[bot]: @omenos: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
ci/prow/e2e-crc | 171ef9de809b991d3be3aa9a90f666a402b40fc5 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/code-ready_crc/2071/pull-ci-code-ready-crc-master-e2e-crc/1375365900521181184) | `/test e2e-crc`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=code-ready&repo=crc&pr=2071). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:omenos).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
omenos: @guillaumerose Those changes lgtm. There should also be some changes to the DNS topic in the docs to correct the language and the script provided.
omenos: @cfergeau I replaced my branch with yours and added the documentation changes.

Patch:
diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md
deleted file mode 100644
index afc77aab3..000000000
--- a/.github/ISSUE_TEMPLATE/custom.md
+++ /dev/null
@@ -1,54 +0,0 @@
----
-name: Issue template
-about: Any generic issue
-title: ''
-labels: ''
-assignees: ''
-
----
-
-### General information
-
-  * OS: Linux / macOS / Windows
-  * Hypervisor: KVM / Hyper-V / hyperkit
-  * Did you run `crc setup` before starting it (Yes/No)?
-
-## CRC version
-```bash
-# Put the output of `crc version`
-```
-  
-## CRC status
-```bash
-# Put the output of `crc status`
-```
-
-## CRC config
-```bash
-# Put the output of `crc config view`
-```
-
-## Host Operating System
-```bash
-# Put the output of `cat /etc/os-release` in case of Linux
-# put the output of `sw_vers` in case of Mac
-# Put the output of `systeminfo` in case of Windows
-```
-
-### Steps to reproduce
-
-  1. 
-  2. 
-  3. 
-  4. 
-
-### Expected
-
-
-### Actual
-
-
-### Logs
-
-You can start crc with `crc start --log-level debug` to collect logs.
-Please consider posting this on http://gist.github.com/ and post the link in the issue.
diff --git a/Makefile b/Makefile
index a778fb9ec..f1ce20708 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
 SHELL := /bin/bash
 
-BUNDLE_VERSION = 4.7.2
+BUNDLE_VERSION = 4.7.4
 BUNDLE_EXTENSION = crcbundle
 CRC_VERSION = 1.24.0
 COMMIT_SHA=$(shell git rev-parse --short HEAD)
diff --git a/centos_ci.sh b/centos_ci.sh
index e5682be2f..0efe0d42d 100644
--- a/centos_ci.sh
+++ b/centos_ci.sh
@@ -1,7 +1,7 @@
 #!/bin/bash
 
 # bundle location
-BUNDLE_VERSION=4.7.2
+BUNDLE_VERSION=4.7.4
 BUNDLE=crc_libvirt_$BUNDLE_VERSION.crcbundle
 GO_VERSION=1.14.13
 
diff --git a/cmd/crc/cmd/console.go b/cmd/crc/cmd/console.go
index 717a811f3..83e6f5c31 100644
--- a/cmd/crc/cmd/console.go
+++ b/cmd/crc/cmd/console.go
@@ -8,6 +8,7 @@ import (
 
 	crcErrors "github.com/code-ready/crc/pkg/crc/errors"
 	"github.com/code-ready/crc/pkg/crc/machine"
+	"github.com/code-ready/crc/pkg/crc/machine/types"
 	"github.com/code-ready/machine/libmachine/state"
 	"github.com/pkg/browser"
 	"github.com/spf13/cobra"
@@ -36,7 +37,7 @@ var consoleCmd = &cobra.Command{
 	},
 }
 
-func showConsole(client machine.Client) (*machine.ConsoleResult, error) {
+func showConsole(client machine.Client) (*types.ConsoleResult, error) {
 	if err := checkIfMachineMissing(client); err != nil {
 		// In case of machine doesn't exist then consoleResult error
 		// should be updated so that when rendering the result it have
@@ -105,14 +106,14 @@ func (s *consoleResult) prettyPrintTo(writer io.Writer) error {
 	return nil
 }
 
-func toState(result *machine.ConsoleResult) state.State {
+func toState(result *types.ConsoleResult) state.State {
 	if result == nil {
 		return state.Error
 	}
 	return result.State
 }
 
-func toConsoleClusterConfig(result *machine.ConsoleResult) *clusterConfig {
+func toConsoleClusterConfig(result *types.ConsoleResult) *clusterConfig {
 	if result == nil {
 		return nil
 	}
diff --git a/cmd/crc/cmd/daemon.go b/cmd/crc/cmd/daemon.go
index fec7cd2a3..148fd7f2f 100644
--- a/cmd/crc/cmd/daemon.go
+++ b/cmd/crc/cmd/daemon.go
@@ -1,7 +1,9 @@
 package cmd
 
 import (
+	"errors"
 	"fmt"
+	"io/ioutil"
 	"net"
 	"net/http"
 	"os"
@@ -14,6 +16,7 @@ import (
 	cmdConfig "github.com/code-ready/crc/cmd/crc/cmd/config"
 	"github.com/code-ready/crc/pkg/crc/api"
 	"github.com/code-ready/crc/pkg/crc/constants"
+	"github.com/code-ready/crc/pkg/crc/daemonclient"
 	"github.com/code-ready/crc/pkg/crc/logging"
 	"github.com/code-ready/gvisor-tap-vsock/pkg/types"
 	"github.com/code-ready/gvisor-tap-vsock/pkg/virtualnetwork"
@@ -22,7 +25,10 @@ import (
 	"github.com/spf13/cobra"
 )
 
+var watchdog bool
+
 func init() {
+	daemonCmd.Flags().BoolVar(&watchdog, "watchdog", false, "Monitor stdin and shutdown the daemon if stdin is closed")
 	rootCmd.AddCommand(daemonCmd)
 }
 
@@ -34,6 +40,10 @@ var daemonCmd = &cobra.Command{
 	Long:   "Run the crc daemon",
 	Hidden: true,
 	RunE: func(cmd *cobra.Command, args []string) error {
+		if _, err := daemonclient.New().APIClient.Version(); err == nil {
+			return errors.New("daemon is already running")
+		}
+
 		virtualNetworkConfig := types.Configuration{
 			Debug:             false, // never log packets
 			CaptureFile:       captureFile(),
@@ -152,6 +162,17 @@ func run(configuration *types.Configuration) error {
 	}()
 
 	c := make(chan os.Signal, 1)
+
+	if watchdog {
+		go func() {
+			if _, err := ioutil.ReadAll(os.Stdin); err != nil {
+				logging.Errorf("unexpected error while reading stdin: %v", err)
+			}
+			logging.Error("stdin is closed, shutdown...")
+			c <- syscall.SIGTERM
+		}()
+	}
+
 	signal.Notify(c, os.Interrupt, syscall.SIGTERM)
 	select {
 	case <-c:
diff --git a/cmd/crc/cmd/root.go b/cmd/crc/cmd/root.go
index 9bb02180f..f18991ea4 100644
--- a/cmd/crc/cmd/root.go
+++ b/cmd/crc/cmd/root.go
@@ -164,7 +164,7 @@ func newViperConfig() (*crcConfig.Config, *crcConfig.ViperStorage, error) {
 }
 
 func newMachine() machine.Client {
-	return machine.NewClient(constants.DefaultName, isDebugLog(), config)
+	return machine.NewSynchronizedMachine(machine.NewClient(constants.DefaultName, isDebugLog(), config))
 }
 
 func addForceFlag(cmd *cobra.Command) {
diff --git a/cmd/crc/cmd/start.go b/cmd/crc/cmd/start.go
index 67dcbadb1..97aadf5ad 100644
--- a/cmd/crc/cmd/start.go
+++ b/cmd/crc/cmd/start.go
@@ -15,7 +15,7 @@ import (
 	"github.com/code-ready/crc/pkg/crc/constants"
 	crcErrors "github.com/code-ready/crc/pkg/crc/errors"
 	"github.com/code-ready/crc/pkg/crc/logging"
-	"github.com/code-ready/crc/pkg/crc/machine"
+	"github.com/code-ready/crc/pkg/crc/machine/types"
 	"github.com/code-ready/crc/pkg/crc/preflight"
 	"github.com/code-ready/crc/pkg/crc/validation"
 	crcversion "github.com/code-ready/crc/pkg/crc/version"
@@ -55,14 +55,14 @@ var startCmd = &cobra.Command{
 	},
 }
 
-func runStart(ctx context.Context) (*machine.StartResult, error) {
+func runStart(ctx context.Context) (*types.StartResult, error) {
 	if err := validateStartFlags(); err != nil {
 		return nil, err
 	}
 
 	checkIfNewVersionAvailable(config.Get(cmdConfig.DisableUpdateCheck).AsBool())
 
-	startConfig := machine.StartConfig{
+	startConfig := types.StartConfig{
 		BundlePath: config.Get(cmdConfig.Bundle).AsString(),
 		Memory:     config.Get(cmdConfig.Memory).AsInt(),
 		DiskSize:   config.Get(cmdConfig.DiskSize).AsInt(),
@@ -83,7 +83,7 @@ func runStart(ctx context.Context) (*machine.StartResult, error) {
 	return client.Start(ctx, startConfig)
 }
 
-func renderStartResult(result *machine.StartResult, err error) error {
+func renderStartResult(result *types.StartResult, err error) error {
 	return render(&startResult{
 		Success:       err == nil,
 		Error:         crcErrors.ToSerializableError(err),
@@ -91,7 +91,7 @@ func renderStartResult(result *machine.StartResult, err error) error {
 	}, os.Stdout, outputFormat)
 }
 
-func toClusterConfig(result *machine.StartResult) *clusterConfig {
+func toClusterConfig(result *types.StartResult) *clusterConfig {
 	if result == nil {
 		return nil
 	}
diff --git a/docs/source/topics/proc_setting-up-remote-server.adoc b/docs/source/topics/proc_setting-up-remote-server.adoc
index d9b6449ab..45b56e2b6 100644
--- a/docs/source/topics/proc_setting-up-remote-server.adoc
+++ b/docs/source/topics/proc_setting-up-remote-server.adoc
@@ -31,7 +31,7 @@ Ensure that the cluster remains running throughout this procedure.
 . Install the [package]`haproxy` package and other utilities:
 +
 ----
-$ sudo dnf install haproxy policycoreutils-python-utils jq
+$ sudo dnf install haproxy policycoreutils-python-utils
 ----
 
 . Modify the firewall to allow communication with the cluster:
@@ -63,49 +63,28 @@ $ sudo cp /etc/haproxy/haproxy.cfg{,.bak}
 $ export CRC_IP=$({bin} ip)
 $ sudo tee /etc/haproxy/haproxy.cfg &>/dev/null <<EOF
 global
-    debug
+    log /dev/log local0
 
 defaults
+    balance roundrobin
     log global
-    mode http
-    timeout connect 5000
-    timeout client 500000
-    timeout server 500000
+    maxconn 100
+    mode tcp
+    timeout connect 5s
+    timeout client 500s
+    timeout server 500s
 
-frontend apps
+listen apps
     bind 0.0.0.0:80
-    option tcplog
-    mode tcp
-    default_backend apps
+    server crcvm $CRC_IP:80 check
 
-frontend apps_ssl
+listen apps_ssl
     bind 0.0.0.0:443
-    option tcplog
-    mode tcp
-    default_backend apps_ssl
-
-backend apps
-    mode tcp
-    balance roundrobin
-    server webserver1 $CRC_IP:80 check
+    server crcvm $CRC_IP:443 check
 
-backend apps_ssl
-    mode tcp
-    balance roundrobin
-    option ssl-hello-chk
-    server webserver1 $CRC_IP:443 check
-
-frontend api
+listen api
     bind 0.0.0.0:6443
-    option tcplog
-    mode tcp
-    default_backend api
-
-backend api
-    mode tcp
-    balance roundrobin
-    option ssl-hello-chk
-    server webserver1 $CRC_IP:6443 check
+    server crcvm $CRC_IP:6443 check
 EOF
 ----
 
diff --git a/docs/source/topics/ref_dns-configuration.adoc b/docs/source/topics/ref_dns-configuration.adoc
index 1ff4d1c5e..6f8a6f2cc 100644
--- a/docs/source/topics/ref_dns-configuration.adoc
+++ b/docs/source/topics/ref_dns-configuration.adoc
@@ -11,7 +11,7 @@ The `apps-crc.testing` domain is for accessing OpenShift applications deployed o
 For example, the OpenShift API server will be exposed as `api.crc.testing` while the OpenShift console is accessed through `console-openshift-console.apps-crc.testing`.
 These DNS domains are served by a `dnsmasq` DNS container running inside the {prod} virtual machine.
 
-Running [command]`{bin} setup` will adjust your system DNS configuration so that it can resolve these domains.
+Running [command]`{bin} setup` will detect and adjust your system DNS configuration so that it can resolve these domains.
 Additional checks are done to verify DNS is properly configured when running [command]`{bin} start`.
 
 [id="dns-configuration-linux_{context}"]
@@ -21,7 +21,7 @@ On Linux, depending on your distribution, {prod} expects the following DNS confi
 
 === NetworkManager + systemd-resolved
 
-This configuration is used on Fedora 33 or newer, and on Ubuntu Desktop editions.
+This configuration is used by default 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.
@@ -30,29 +30,23 @@ This configuration is used on Fedora 33 or newer, and on Ubuntu Desktop editions
 +
 ----
 #!/bin/sh
-resolvectl domain crc ~testing
-resolvectl dns crc 192.168.130.11
-resolvectl default-route crc false
+
+export LC_ALL=C
+
+systemd-resolve --interface crc --set-dns 192.168.130.11 --set-domain ~testing
 
 exit 0
 ----
 
 [NOTE]
 ====
-`systemd-resolved` is also available as an unsupported Technology Preview on {rhel} and {centos} 8.3. After {rhel-resolved-docs}[configuring the host] to use `systemd-resolved`, any {prod} `dnsmasq` configurations need to be removed and the above script needs to be created with executable permissions.
-
-Configure the {prod} binary to skip startup checks for `dnsmasq` and NetworkManager configuration:
-
-[subs="+quotes,attributes"]
-----
-$ {bin} config set skip-check-crc-dnsmasq-file true
-$ {bin} config set skip-check-network-manager-config true
-----
+`systemd-resolved` is also available as an unsupported Technology Preview on {rhel} and {centos} 8.3.
+After {rhel-resolved-docs}[configuring the host] to use `systemd-resolved`, stop any running clusters and rerun [command]`{bin} setup`.
 ====
 
 === NetworkManager + dnsmasq
 
-This configuration is used on Fedora 32 or older, on {rhel}, and on {centos}.
+This configuration is used by default 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/api/adapter.go b/pkg/crc/api/adapter.go
index 4abcb41e8..21bb94bb6 100644
--- a/pkg/crc/api/adapter.go
+++ b/pkg/crc/api/adapter.go
@@ -6,6 +6,7 @@ import (
 	"github.com/code-ready/crc/pkg/crc/api/client"
 	"github.com/code-ready/crc/pkg/crc/logging"
 	"github.com/code-ready/crc/pkg/crc/machine"
+	"github.com/code-ready/crc/pkg/crc/machine/types"
 	"github.com/code-ready/machine/libmachine/state"
 )
 
@@ -13,7 +14,7 @@ type AdaptedClient interface {
 	GetName() string
 	Delete() client.Result
 	GetConsoleURL() client.ConsoleResult
-	Start(ctx context.Context, startConfig machine.StartConfig) client.StartResult
+	Start(ctx context.Context, startConfig types.StartConfig) client.StartResult
 	Status() client.ClusterStatusResult
 	Stop() client.Result
 }
@@ -57,7 +58,7 @@ func (a *Adapter) GetConsoleURL() client.ConsoleResult {
 	}
 }
 
-func (a *Adapter) Start(ctx context.Context, startConfig machine.StartConfig) client.StartResult {
+func (a *Adapter) Start(ctx context.Context, startConfig types.StartConfig) client.StartResult {
 	res, err := a.Underlying.Start(ctx, startConfig)
 	if err != nil {
 		logging.Error(err)
diff --git a/pkg/crc/api/api.go b/pkg/crc/api/api.go
index 7b7953b37..d55e0ff34 100644
--- a/pkg/crc/api/api.go
+++ b/pkg/crc/api/api.go
@@ -22,8 +22,7 @@ func CreateServer(socketPath string, config crcConfig.Storage, machine machine.C
 
 func createServerWithListener(listener net.Listener, config crcConfig.Storage, machine machine.Client, logger Logger) (Server, error) {
 	apiServer := Server{
-		listener:               listener,
-		clusterOpsRequestsChan: make(chan clusterOpsRequest, 10),
+		listener: listener,
 		handler: &Handler{
 			Config:        config,
 			MachineClient: &Adapter{Underlying: machine},
@@ -34,7 +33,6 @@ func createServerWithListener(listener net.Listener, config crcConfig.Storage, m
 }
 
 func (api Server) Serve() error {
-	go api.handleClusterOperations() // go routine that handles start, stop and delete calls
 	for {
 		conn, err := api.listener.Accept()
 		if err != nil {
@@ -48,12 +46,6 @@ func (api Server) Serve() error {
 	}
 }
 
-func (api Server) handleClusterOperations() {
-	for req := range api.clusterOpsRequestsChan {
-		api.handleRequest(req.command, req.socket)
-	}
-}
-
 func (api Server) handleRequest(req commandRequest, conn net.Conn) {
 	defer conn.Close()
 	var result string
@@ -99,32 +91,7 @@ func (api Server) handleConnections(conn net.Conn) {
 		logging.Error("Error decoding request: ", err.Error())
 		return
 	}
-	// start, stop and delete are slow operations, and change the VM state so they have to run sequentially.
-	// We don't want other operations querying the status of the VM to be blocked by these,
-	// so they are treated by a dedicated go routine
-
-	switch req.Command {
-	case "start", "stop", "delete":
-		// queue new request to channel
-		r := clusterOpsRequest{
-			command: req,
-			socket:  conn,
-		}
-		if !addRequestToChannel(r, api.clusterOpsRequestsChan) {
-			logging.Error("Channel capacity reached, unable to add new request")
-			errMsg := encodeErrorToJSON("Sockets channel capacity reached, unable to add new request")
-			writeStringToSocket(conn, errMsg)
-			conn.Close()
-		}
-
-	case "status", "version", "setconfig", "getconfig", "unsetconfig", "webconsoleurl", "logs":
-		go api.handleRequest(req, conn)
-
-	default:
-		err := encodeErrorToJSON(fmt.Sprintf("Unknown command supplied: %s", req.Command))
-		writeStringToSocket(conn, err)
-		conn.Close()
-	}
+	go api.handleRequest(req, conn)
 }
 
 func writeStringToSocket(socket net.Conn, msg string) {
@@ -142,12 +109,3 @@ func writeStringToSocket(socket net.Conn, msg string) {
 		return
 	}
 }
-
-func addRequestToChannel(req clusterOpsRequest, requestsChan chan clusterOpsRequest) bool {
-	select {
-	case requestsChan <- req:
-		return true
-	default:
-		return false
-	}
-}
diff --git a/pkg/crc/api/client/client.go b/pkg/crc/api/client/client.go
index ed34ef7de..a6c22bc42 100644
--- a/pkg/crc/api/client/client.go
+++ b/pkg/crc/api/client/client.go
@@ -7,8 +7,6 @@ import (
 	"io"
 	"io/ioutil"
 	"net/http"
-
-	"github.com/code-ready/crc/pkg/crc/logging"
 )
 
 type Client struct {
@@ -27,12 +25,10 @@ func (c *Client) Version() (VersionResult, error) {
 	var vr = VersionResult{}
 	body, err := c.sendGetRequest("/version")
 	if err != nil {
-		logging.Error(err)
 		return vr, err
 	}
 	err = json.Unmarshal(body, &vr)
 	if err != nil {
-		logging.Error(err)
 		return vr, err
 	}
 	return vr, nil
@@ -42,12 +38,10 @@ func (c *Client) Status() (ClusterStatusResult, error) {
 	var sr = ClusterStatusResult{}
 	body, err := c.sendGetRequest("/status")
 	if err != nil {
-		logging.Error(err)
 		return sr, err
 	}
 	err = json.Unmarshal(body, &sr)
 	if err != nil {
-		logging.Error(err)
 		return sr, err
 	}
 	return sr, nil
@@ -64,12 +58,10 @@ func (c *Client) Start(config StartConfig) (StartResult, error) {
 	}
 	body, err := c.sendPostRequest("/start", data)
 	if err != nil {
-		logging.Error(err)
 		return sr, err
 	}
 	err = json.Unmarshal(body, &sr)
 	if err != nil {
-		logging.Error(err)
 		return sr, err
 	}
 	return sr, nil
@@ -79,12 +71,10 @@ func (c *Client) Stop() (Result, error) {
 	var sr = Result{}
 	body, err := c.sendGetRequest("/stop")
 	if err != nil {
-		logging.Error(err)
 		return sr, err
 	}
 	err = json.Unmarshal(body, &sr)
 	if err != nil {
-		logging.Error(err)
 		return sr, err
 	}
 	return sr, nil
@@ -94,12 +84,10 @@ func (c *Client) Delete() (Result, error) {
 	var dr = Result{}
 	body, err := c.sendGetRequest("/delete")
 	if err != nil {
-		logging.Error(err)
 		return dr, err
 	}
 	err = json.Unmarshal(body, &dr)
 	if err != nil {
-		logging.Error(err)
 		return dr, err
 	}
 	return dr, nil
@@ -109,12 +97,10 @@ func (c *Client) WebconsoleURL() (ConsoleResult, error) {
 	var cr = ConsoleResult{}
 	body, err := c.sendGetRequest("/webconsoleurl")
 	if err != nil {
-		logging.Error(err)
 		return cr, err
 	}
 	err = json.Unmarshal(body, &cr)
 	if err != nil {
-		logging.Error(err)
 		return cr, err
 	}
 	return cr, nil
@@ -134,12 +120,10 @@ func (c *Client) GetConfig(configs []string) (GetConfigResult, error) {
 	}
 	body, err := c.sendPostRequest("/config/get", data)
 	if err != nil {
-		logging.Error(err)
 		return gcr, err
 	}
 	err = json.Unmarshal(body, &gcr)
 	if err != nil {
-		logging.Error(err)
 		return gcr, err
 	}
 	return gcr, nil
@@ -159,7 +143,6 @@ func (c *Client) SetConfig(configs SetConfigRequest) (SetOrUnsetConfigResult, er
 
 	body, err := c.sendPostRequest("/config/set", data)
 	if err != nil {
-		logging.Error(err)
 		return scr, err
 	}
 
@@ -182,12 +165,10 @@ func (c *Client) UnsetConfig(configs []string) (SetOrUnsetConfigResult, error) {
 	}
 	body, err := c.sendPostRequest("/config/unset", data)
 	if err != nil {
-		logging.Error(err)
 		return ucr, err
 	}
 	err = json.Unmarshal(body, &ucr)
 	if err != nil {
-		logging.Error(err)
 		return ucr, err
 	}
 	return ucr, nil
@@ -196,7 +177,6 @@ func (c *Client) UnsetConfig(configs []string) (SetOrUnsetConfigResult, error) {
 func (c *Client) sendGetRequest(url string) ([]byte, error) {
 	res, err := c.client.Get(fmt.Sprintf("%s%s", c.base, url))
 	if err != nil {
-		logging.Error(err)
 		return nil, err
 	}
 	if res.StatusCode != http.StatusOK {
@@ -219,7 +199,6 @@ func (c *Client) sendPostRequest(url string, data io.Reader) ([]byte, error) {
 
 	res, err := c.client.Do(req)
 	if err != nil {
-		logging.Error(err)
 		return nil, err
 	}
 	if res.StatusCode != http.StatusOK {
diff --git a/pkg/crc/api/client/types.go b/pkg/crc/api/client/types.go
index 5767ae969..9acd8922b 100644
--- a/pkg/crc/api/client/types.go
+++ b/pkg/crc/api/client/types.go
@@ -1,7 +1,7 @@
 package client
 
 import (
-	"github.com/code-ready/crc/pkg/crc/machine"
+	"github.com/code-ready/crc/pkg/crc/machine/types"
 )
 
 type VersionResult struct {
@@ -21,7 +21,7 @@ type StartResult struct {
 	Name           string
 	Status         string
 	Error          string
-	ClusterConfig  machine.ClusterConfig
+	ClusterConfig  types.ClusterConfig
 	KubeletStarted bool
 }
 
@@ -37,7 +37,7 @@ type ClusterStatusResult struct {
 }
 
 type ConsoleResult struct {
-	ClusterConfig machine.ClusterConfig
+	ClusterConfig types.ClusterConfig
 	Success       bool
 	Error         string
 }
diff --git a/pkg/crc/api/handlers.go b/pkg/crc/api/handlers.go
index d862cb54b..729f08f5e 100644
--- a/pkg/crc/api/handlers.go
+++ b/pkg/crc/api/handlers.go
@@ -13,6 +13,7 @@ import (
 	"github.com/code-ready/crc/pkg/crc/errors"
 	"github.com/code-ready/crc/pkg/crc/logging"
 	"github.com/code-ready/crc/pkg/crc/machine"
+	"github.com/code-ready/crc/pkg/crc/machine/types"
 	"github.com/code-ready/crc/pkg/crc/preflight"
 	"github.com/code-ready/crc/pkg/crc/version"
 )
@@ -89,8 +90,8 @@ func parseStartArgs(args json.RawMessage) (startArgs, error) {
 	return parsedArgs, nil
 }
 
-func getStartConfig(cfg crcConfig.Storage, args startArgs) machine.StartConfig {
-	return machine.StartConfig{
+func getStartConfig(cfg crcConfig.Storage, args startArgs) types.StartConfig {
+	return types.StartConfig{
 		BundlePath: cfg.Get(config.Bundle).AsString(),
 		Memory:     cfg.Get(config.Memory).AsInt(),
 		CPUs:       cfg.Get(config.CPUs).AsInt(),
diff --git a/pkg/crc/api/types.go b/pkg/crc/api/types.go
index c4aa6cac2..6bcee06d7 100644
--- a/pkg/crc/api/types.go
+++ b/pkg/crc/api/types.go
@@ -6,9 +6,8 @@ import (
 )
 
 type Server struct {
-	handler                RequestHandler
-	listener               net.Listener
-	clusterOpsRequestsChan chan clusterOpsRequest
+	handler  RequestHandler
+	listener net.Listener
 }
 
 type RequestHandler interface {
@@ -24,12 +23,6 @@ type RequestHandler interface {
 	Logs() string
 }
 
-// clusterOpsRequest struct is used to store the command request and associated socket
-type clusterOpsRequest struct {
-	command commandRequest
-	socket  net.Conn
-}
-
 // commandRequest struct is used to decode the json request from tray
 type commandRequest struct {
 	Command string          `json:"command"`
diff --git a/pkg/crc/daemonclient/client.go b/pkg/crc/daemonclient/client.go
index aa866634a..7df516385 100644
--- a/pkg/crc/daemonclient/client.go
+++ b/pkg/crc/daemonclient/client.go
@@ -3,17 +3,22 @@ package daemonclient
 import (
 	"net/http"
 
+	"github.com/code-ready/crc/pkg/crc/api/client"
 	networkclient "github.com/code-ready/gvisor-tap-vsock/pkg/client"
 )
 
 type Client struct {
-	*networkclient.Client
+	NetworkClient *networkclient.Client
+	APIClient     *client.Client
 }
 
 func New() *Client {
 	return &Client{
-		Client: networkclient.New(&http.Client{
+		NetworkClient: networkclient.New(&http.Client{
 			Transport: transport(),
 		}, "http://unix/network"),
+		APIClient: client.New(&http.Client{
+			Transport: transport(),
+		}, "http://unix/api"),
 	}
 }
diff --git a/pkg/crc/machine/client.go b/pkg/crc/machine/client.go
index b3770047f..58101cd2e 100644
--- a/pkg/crc/machine/client.go
+++ b/pkg/crc/machine/client.go
@@ -5,6 +5,7 @@ import (
 
 	cmdConfig "github.com/code-ready/crc/cmd/crc/cmd/config"
 	crcConfig "github.com/code-ready/crc/pkg/crc/config"
+	"github.com/code-ready/crc/pkg/crc/machine/types"
 	"github.com/code-ready/crc/pkg/crc/network"
 	"github.com/code-ready/machine/libmachine/state"
 )
@@ -14,11 +15,11 @@ type Client interface {
 
 	Delete() error
 	Exists() (bool, error)
-	GetConsoleURL() (*ConsoleResult, error)
+	GetConsoleURL() (*types.ConsoleResult, error)
 	IP() (string, error)
 	PowerOff() error
-	Start(ctx context.Context, startConfig StartConfig) (*StartResult, error)
-	Status() (*ClusterStatusResult, error)
+	Start(ctx context.Context, startConfig types.StartConfig) (*types.StartResult, error)
+	Status() (*types.ClusterStatusResult, error)
 	Stop() (state.State, error)
 	IsRunning() (bool, error)
 }
diff --git a/pkg/crc/machine/console.go b/pkg/crc/machine/console.go
index c5c1a5ff0..54b216938 100644
--- a/pkg/crc/machine/console.go
+++ b/pkg/crc/machine/console.go
@@ -1,9 +1,12 @@
 package machine
 
-import "github.com/pkg/errors"
+import (
+	"github.com/code-ready/crc/pkg/crc/machine/types"
+	"github.com/pkg/errors"
+)
 
 // Return console URL if the VM is present.
-func (client *client) GetConsoleURL() (*ConsoleResult, error) {
+func (client *client) GetConsoleURL() (*types.ConsoleResult, error) {
 	// Here we are only checking if the VM exist and not the status of the VM.
 	// We might need to improve and use crc status logic, only
 	// return if the Openshift is running as part of status.
@@ -29,7 +32,7 @@ func (client *client) GetConsoleURL() (*ConsoleResult, error) {
 		return nil, errors.Wrap(err, "Error loading cluster configuration")
 	}
 
-	return &ConsoleResult{
+	return &types.ConsoleResult{
 		ClusterConfig: *clusterConfig,
 		State:         vmState,
 	}, nil
diff --git a/pkg/crc/machine/fakemachine/client.go b/pkg/crc/machine/fakemachine/client.go
index 64387aece..0e9ecb9ec 100644
--- a/pkg/crc/machine/fakemachine/client.go
+++ b/pkg/crc/machine/fakemachine/client.go
@@ -4,7 +4,7 @@ import (
 	"context"
 	"errors"
 
-	"github.com/code-ready/crc/pkg/crc/machine"
+	"github.com/code-ready/crc/pkg/crc/machine/types"
 	"github.com/code-ready/crc/pkg/crc/network"
 	"github.com/code-ready/machine/libmachine/state"
 )
@@ -23,7 +23,7 @@ type Client struct {
 	Failing bool
 }
 
-var DummyClusterConfig = machine.ClusterConfig{
+var DummyClusterConfig = types.ClusterConfig{
 	ClusterCACert: "MIIDODCCAiCgAwIBAgIIRVfCKNUa1wIwDQYJ",
 	KubeConfig:    "/tmp/kubeconfig",
 	KubeAdminPass: "foobar",
@@ -43,11 +43,11 @@ func (c *Client) Delete() error {
 	return nil
 }
 
-func (c *Client) GetConsoleURL() (*machine.ConsoleResult, error) {
+func (c *Client) GetConsoleURL() (*types.ConsoleResult, error) {
 	if c.Failing {
 		return nil, errors.New("console failed")
 	}
-	return &machine.ConsoleResult{
+	return &types.ConsoleResult{
 		ClusterConfig: DummyClusterConfig,
 		State:         state.Running,
 	}, nil
@@ -68,11 +68,11 @@ func (c *Client) PowerOff() error {
 	return nil
 }
 
-func (c *Client) Start(ctx context.Context, startConfig machine.StartConfig) (*machine.StartResult, error) {
+func (c *Client) Start(ctx context.Context, startConfig types.StartConfig) (*types.StartResult, error) {
 	if c.Failing {
 		return nil, errors.New("Failed to start")
 	}
-	return &machine.StartResult{
+	return &types.StartResult{
 		ClusterConfig:  DummyClusterConfig,
 		KubeletStarted: true,
 	}, nil
@@ -85,11 +85,11 @@ func (c *Client) Stop() (state.State, error) {
 	return state.Stopped, nil
 }
 
-func (c *Client) Status() (*machine.ClusterStatusResult, error) {
+func (c *Client) Status() (*types.ClusterStatusResult, error) {
 	if c.Failing {
 		return nil, errors.New("broken")
 	}
-	return &machine.ClusterStatusResult{
+	return &types.ClusterStatusResult{
 		CrcStatus:        state.Running,
 		OpenshiftStatus:  "Running",
 		OpenshiftVersion: "4.5.1",
diff --git a/pkg/crc/machine/kubeconfig.go b/pkg/crc/machine/kubeconfig.go
index a398f03b6..fd6b950bd 100644
--- a/pkg/crc/machine/kubeconfig.go
+++ b/pkg/crc/machine/kubeconfig.go
@@ -14,6 +14,7 @@ import (
 	"time"
 
 	"github.com/code-ready/crc/pkg/crc/constants"
+	"github.com/code-ready/crc/pkg/crc/machine/types"
 	"github.com/openshift/oc/pkg/helpers/tokencmd"
 	restclient "k8s.io/client-go/rest"
 	"k8s.io/client-go/tools/clientcmd"
@@ -25,7 +26,7 @@ const (
 	developerContext = "crc-developer"
 )
 
-func writeKubeconfig(ip string, clusterConfig *ClusterConfig) error {
+func writeKubeconfig(ip string, clusterConfig *types.ClusterConfig) error {
 	kubeconfig := getGlobalKubeConfigPath()
 	dir := filepath.Dir(kubeconfig)
 	if err := os.MkdirAll(dir, 0755); err != nil {
@@ -86,7 +87,7 @@ func hostname(clusterAPI string) (string, error) {
 	return p.Host, nil
 }
 
-func addContext(cfg *api.Config, ip string, clusterConfig *ClusterConfig, ca []byte, context, username, password string) error {
+func addContext(cfg *api.Config, ip string, clusterConfig *types.ClusterConfig, ca []byte, context, username, password string) error {
 	host, err := hostname(clusterConfig.ClusterAPI)
 	if err != nil {
 		return err
diff --git a/pkg/crc/machine/machine.go b/pkg/crc/machine/machine.go
index ae7fdd3df..01dfbd189 100644
--- a/pkg/crc/machine/machine.go
+++ b/pkg/crc/machine/machine.go
@@ -6,13 +6,14 @@ import (
 
 	"github.com/code-ready/crc/pkg/crc/constants"
 	"github.com/code-ready/crc/pkg/crc/machine/bundle"
+	"github.com/code-ready/crc/pkg/crc/machine/types"
 	"github.com/code-ready/crc/pkg/crc/network"
 	"github.com/code-ready/crc/pkg/libmachine"
 	"github.com/code-ready/crc/pkg/libmachine/host"
 	"github.com/code-ready/machine/libmachine/drivers"
 )
 
-func getClusterConfig(bundleInfo *bundle.CrcBundleInfo, generatedKubeadminPassword string) (*ClusterConfig, error) {
+func getClusterConfig(bundleInfo *bundle.CrcBundleInfo, generatedKubeadminPassword string) (*types.ClusterConfig, error) {
 	kubeadminPassword, err := bundleInfo.GetKubeadminPassword()
 	if err != nil {
 		return nil, fmt.Errorf("Error reading kubeadmin password from bundle %v", err)
@@ -28,7 +29,7 @@ func getClusterConfig(bundleInfo *bundle.CrcBundleInfo, generatedKubeadminPasswo
 	if err != nil {
 		return nil, err
 	}
-	return &ClusterConfig{
+	return &types.ClusterConfig{
 		ClusterCACert: base64.StdEncoding.EncodeToString(clusterCACert),
 		KubeConfig:    bundleInfo.GetKubeConfigPath(),
 		KubeAdminPass: kubeadminPassword,
diff --git a/pkg/crc/machine/start.go b/pkg/crc/machine/start.go
index 1716f5ea0..0096c1169 100644
--- a/pkg/crc/machine/start.go
+++ b/pkg/crc/machine/start.go
@@ -17,6 +17,7 @@ import (
 	"github.com/code-ready/crc/pkg/crc/logging"
 	"github.com/code-ready/crc/pkg/crc/machine/bundle"
 	"github.com/code-ready/crc/pkg/crc/machine/config"
+	"github.com/code-ready/crc/pkg/crc/machine/types"
 	"github.com/code-ready/crc/pkg/crc/network"
 	"github.com/code-ready/crc/pkg/crc/oc"
 	"github.com/code-ready/crc/pkg/crc/services"
@@ -52,7 +53,7 @@ func getCrcBundleInfo(bundlePath string) (*bundle.CrcBundleInfo, error) {
 	return bundle.Use(bundleName)
 }
 
-func (client *client) updateVMConfig(startConfig StartConfig, api libmachine.API, host *host.Host) error {
+func (client *client) updateVMConfig(startConfig types.StartConfig, api libmachine.API, host *host.Host) error {
 	/* Memory */
 	logging.Debugf("Updating CRC VM configuration")
 	if err := setMemory(host, startConfig.Memory); err != nil {
@@ -128,7 +129,7 @@ func growRootFileSystem(sshRunner *crcssh.Runner) error {
 
 	return nil
 }
-func (client *client) Start(ctx context.Context, startConfig StartConfig) (*StartResult, error) {
+func (client *client) Start(ctx context.Context, startConfig types.StartConfig) (*types.StartResult, error) {
 	telemetry.SetCPUs(ctx, startConfig.CPUs)
 	telemetry.SetMemory(ctx, uint64(startConfig.Memory)*1024*1024)
 	telemetry.SetDiskSize(ctx, uint64(startConfig.DiskSize)*1024*1024*1024)
@@ -228,7 +229,7 @@ func (client *client) Start(ctx context.Context, startConfig StartConfig) (*Star
 			}
 
 			telemetry.SetStartType(ctx, telemetry.AlreadyRunningStartType)
-			return &StartResult{
+			return &types.StartResult{
 				Status:         vmState,
 				ClusterConfig:  *clusterConfig,
 				KubeletStarted: true,
@@ -459,7 +460,7 @@ func (client *client) Start(ctx context.Context, startConfig StartConfig) (*Star
 		logging.Errorf("Cannot update kubeconfig: %v", err)
 	}
 
-	return &StartResult{
+	return &types.StartResult{
 		KubeletStarted: true,
 		ClusterConfig:  *clusterConfig,
 		Status:         vmState,
@@ -487,7 +488,7 @@ func (client *client) IsRunning() (bool, error) {
 	return true, nil
 }
 
-func (client *client) validateStartConfig(startConfig StartConfig) error {
+func (client *client) validateStartConfig(startConfig types.StartConfig) error {
 	if client.monitoringEnabled() && startConfig.Memory < minimumMemoryForMonitoring {
 		return fmt.Errorf("Too little memory (%s) allocated to the virtual machine to start the monitoring stack, %s is the minimum",
 			units.BytesSize(float64(startConfig.Memory)*1024*1024),
diff --git a/pkg/crc/machine/status.go b/pkg/crc/machine/status.go
index 43630ae04..d1fae2a77 100644
--- a/pkg/crc/machine/status.go
+++ b/pkg/crc/machine/status.go
@@ -4,13 +4,14 @@ import (
 	"github.com/code-ready/crc/pkg/crc/cluster"
 	"github.com/code-ready/crc/pkg/crc/constants"
 	"github.com/code-ready/crc/pkg/crc/logging"
+	"github.com/code-ready/crc/pkg/crc/machine/types"
 	"github.com/code-ready/crc/pkg/crc/oc"
 	crcssh "github.com/code-ready/crc/pkg/crc/ssh"
 	"github.com/code-ready/machine/libmachine/state"
 	"github.com/pkg/errors"
 )
 
-func (client *client) Status() (*ClusterStatusResult, error) {
+func (client *client) Status() (*types.ClusterStatusResult, error) {
 	libMachineAPIClient, cleanup := createLibMachineClient()
 	defer cleanup()
 
@@ -34,7 +35,7 @@ func (client *client) Status() (*ClusterStatusResult, error) {
 	}
 
 	if vmStatus != state.Running {
-		return &ClusterStatusResult{
+		return &types.ClusterStatusResult{
 			CrcStatus:        vmStatus,
 			OpenshiftStatus:  "Stopped",
 			OpenshiftVersion: crcBundleMetadata.GetOpenshiftVersion(),
@@ -55,7 +56,7 @@ func (client *client) Status() (*ClusterStatusResult, error) {
 	if err != nil {
 		logging.Debugf("Cannot get root partition usage: %v", err)
 	}
-	return &ClusterStatusResult{
+	return &types.ClusterStatusResult{
 		CrcStatus:        state.Running,
 		OpenshiftStatus:  getOpenShiftStatus(sshRunner, client.monitoringEnabled()),
 		OpenshiftVersion: crcBundleMetadata.GetOpenshiftVersion(),
diff --git a/pkg/crc/machine/sync.go b/pkg/crc/machine/sync.go
new file mode 100644
index 000000000..8c9ebb0ca
--- /dev/null
+++ b/pkg/crc/machine/sync.go
@@ -0,0 +1,170 @@
+package machine
+
+import (
+	"context"
+	"errors"
+	"sync"
+	"time"
+
+	"github.com/code-ready/crc/pkg/crc/logging"
+	"github.com/code-ready/machine/libmachine/state"
+)
+
+const startCancelTimeout = 15 * time.Second
+
+type State string
+
+const (
+	Idle     State = "Idle"
+	Deleting State = "Deleting"
+	Stopping State = "Stopping"
+	Starting State = "Starting"
+)
+
+type Synchronized struct {
+	underlying Client
+
+	stateLock    sync.Mutex
+	currentState State
+	startCancel  context.CancelFunc
+
+	syncOperationDone chan State
+}
+
+func NewSynchronizedMachine(machine Client) *Synchronized {
+	return &Synchronized{
+		underlying:        machine,
+		currentState:      Idle,
+		syncOperationDone: make(chan State, 1),
+	}
+}
+
+func (s *Synchronized) CurrentState() State {
+	s.stateLock.Lock()
+	defer s.stateLock.Unlock()
+
+	return s.currentStateUnlocked()
+}
+
+func (s *Synchronized) currentStateUnlocked() State {
+	select {
+	case st := <-s.syncOperationDone:
+		if s.currentState == st {
+			s.currentState = Idle
+		}
+		if st == Starting {
+			s.startCancel = nil
+		}
+	default:
+	}
+	return s.currentState
+}
+
+func (s *Synchronized) Delete() error {
+	if err := s.prepareStopDelete(Deleting); err != nil {
+		return err
+	}
+
+	err := s.underlying.Delete()
+	s.syncOperationDone <- Deleting
+	return err
+}
+
+func (s *Synchronized) prepareStart(startCancel context.CancelFunc) error {
+	s.stateLock.Lock()
+	defer s.stateLock.Unlock()
+	if s.currentStateUnlocked() != Idle {
+		return errors.New("cluster is busy")
+	}
+	s.startCancel = startCancel
+	s.currentState = Starting
+
+	return nil
+}
+
+func (s *Synchronized) Start(ctx context.Context, startConfig StartConfig) (*StartResult, error) {
+	ctx, startCancel := context.WithCancel(ctx)
+	if err := s.prepareStart(startCancel); err != nil {
+		return nil, err
+	}
+
+	startResult, err := s.underlying.Start(ctx, startConfig)
+	s.syncOperationDone <- Starting
+	return startResult, err
+}
+
+/* cancel ongoing start, and wait until the start is fully cancelled. Time out if cancellation takes more than 'timeout'
+ * s.stateLock must be locked before calling this function
+ */
+func (s *Synchronized) cancelUnlocked(timeout time.Duration) error {
+	if s.startCancel != nil {
+		logging.Infof("Cancelling virtual machine start...")
+		s.startCancel()
+	}
+	select {
+	case <-s.syncOperationDone:
+		return nil
+	case <-time.After(timeout):
+		return errors.New("cannot abort startup sequence quickly enough")
+	}
+}
+
+func (s *Synchronized) prepareStopDelete(state State) error {
+	s.stateLock.Lock()
+	defer s.stateLock.Unlock()
+
+	switch s.currentStateUnlocked() {
+	case Starting:
+		if err := s.cancelUnlocked(startCancelTimeout); err != nil {
+			return err
+		}
+	case Idle:
+		break
+	case Deleting, Stopping:
+		return errors.New("cluster is stopping or deleting")
+	default:
+		return errors.New("invalid condition")
+	}
+
+	s.currentState = state
+	return nil
+}
+
+func (s *Synchronized) Stop() (state.State, error) {
+	if err := s.prepareStopDelete(Stopping); err != nil {
+		return state.Error, err
+	}
+
+	st, err := s.underlying.Stop()
+	s.syncOperationDone <- Stopping
+
+	return st, err
+}
+
+func (s *Synchronized) GetName() string {
+	return s.underlying.GetName()
+}
+
+func (s *Synchronized) Exists() (bool, error) {
+	return s.underlying.Exists()
+}
+
+func (s *Synchronized) GetConsoleURL() (*ConsoleResult, error) {
+	return s.underlying.GetConsoleURL()
+}
+
+func (s *Synchronized) IP() (string, error) {
+	return s.underlying.IP()
+}
+
+func (s *Synchronized) PowerOff() error {
+	return s.underlying.PowerOff()
+}
+
+func (s *Synchronized) Status() (*ClusterStatusResult, error) {
+	return s.underlying.Status()
+}
+
+func (s *Synchronized) IsRunning() (bool, error) {
+	return s.underlying.IsRunning()
+}
diff --git a/pkg/crc/machine/types.go b/pkg/crc/machine/types/types.go
similarity index 98%
rename from pkg/crc/machine/types.go
rename to pkg/crc/machine/types/types.go
index 196bd07fa..6bb212e75 100644
--- a/pkg/crc/machine/types.go
+++ b/pkg/crc/machine/types/types.go
@@ -1,4 +1,4 @@
-package machine
+package types
 
 import (
 	"github.com/code-ready/crc/pkg/crc/cluster"
diff --git a/pkg/crc/machine/vsock.go b/pkg/crc/machine/vsock.go
index b45b030c2..b09601ec5 100644
--- a/pkg/crc/machine/vsock.go
+++ b/pkg/crc/machine/vsock.go
@@ -12,7 +12,7 @@ import (
 func exposePorts() error {
 	daemonClient := daemonclient.New()
 	portsToExpose := vsockPorts()
-	alreadyOpenedPorts, err := daemonClient.List()
+	alreadyOpenedPorts, err := daemonClient.NetworkClient.List()
 	if err != nil {
 		return err
 	}
@@ -24,7 +24,7 @@ func exposePorts() error {
 	}
 	for i := range missingPorts {
 		port := &missingPorts[i]
-		if err := daemonClient.Expose(port); err != nil {
+		if err := daemonClient.NetworkClient.Expose(port); err != nil {
 			return errors.Wrapf(err, "failed to expose port %s -> %s", port.Local, port.Remote)
 		}
 	}
diff --git a/pkg/crc/preflight/preflight_checks_network_linux.go b/pkg/crc/preflight/preflight_checks_network_linux.go
index 385c98227..1171154e6 100644
--- a/pkg/crc/preflight/preflight_checks_network_linux.go
+++ b/pkg/crc/preflight/preflight_checks_network_linux.go
@@ -80,9 +80,13 @@ dns=dnsmasq
 #
 # 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.
+# change directly using systemd-resolve instead.
 #
-# NetworkManager will overwrite this resolvectl configuration every time a
+# systemd-resolve is used instead of resolvectl due to distributions shipping
+# systemd releases older than 239 not having the newer renamed tool. resolvectl
+# supports being called as systemd-resolve, correctly handling the old CLI.
+#
+# NetworkManager will overwrite this systemd-resolve 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:
@@ -90,9 +94,7 @@ dns=dnsmasq
 
 export LC_ALL=C
 
-resolvectl domain crc ~testing
-resolvectl dns crc 192.168.130.11
-resolvectl default-route crc false
+systemd-resolve --interface crc --set-dns 192.168.130.11 --set-domain ~testing
 
 exit 0
 `
diff --git a/pkg/crc/preflight/preflight_linux.go b/pkg/crc/preflight/preflight_linux.go
index b589ea2d9..c06c7d27a 100644
--- a/pkg/crc/preflight/preflight_linux.go
+++ b/pkg/crc/preflight/preflight_linux.go
@@ -217,16 +217,18 @@ func removeVsockCrcSettings() error {
 }
 
 func getAllPreflightChecks() []Check {
-	checks := getPreflightChecksForDistro(distro(), network.DefaultMode)
+	usingSystemdResolved := checkSystemdResolvedIsRunning()
+	checks := getPreflightChecksForDistro(distro(), network.DefaultMode, usingSystemdResolved == nil)
 	checks = append(checks, vsockPreflightChecks)
 	return checks
 }
 
 func getPreflightChecks(_ bool, _ bool, networkMode network.Mode) []Check {
-	return getPreflightChecksForDistro(distro(), networkMode)
+	usingSystemdResolved := checkSystemdResolvedIsRunning()
+	return getPreflightChecksForDistro(distro(), networkMode, usingSystemdResolved == nil)
 }
 
-func getNetworkChecksForDistro(distro *linux.OsRelease, networkMode network.Mode) []Check {
+func getNetworkChecks(networkMode network.Mode, systemdResolved bool) []Check {
 	var checks []Check
 
 	if networkMode == network.VSockMode {
@@ -234,28 +236,22 @@ func getNetworkChecksForDistro(distro *linux.OsRelease, networkMode network.Mode
 		return append(checks, daemonRunningChecks)
 	}
 
-	switch {
-	default:
-		logging.Warnf("distribution-specific preflight checks are not implemented for '%s'", distro.ID)
-		fallthrough
-	case distroIsLike(distro, linux.Ubuntu), distroIsLike(distro, linux.Fedora):
-		checks = append(checks, nmPreflightChecks[:]...)
-		if usesSystemdResolved(distro) {
-			checks = append(checks, systemdResolvedPreflightChecks[:]...)
-		} else {
-			checks = append(checks, dnsmasqPreflightChecks[:]...)
-		}
+	checks = append(checks, nmPreflightChecks[:]...)
+	if systemdResolved {
+		checks = append(checks, systemdResolvedPreflightChecks[:]...)
+	} else {
+		checks = append(checks, dnsmasqPreflightChecks[:]...)
 	}
 
 	return checks
 }
 
-func getPreflightChecksForDistro(distro *linux.OsRelease, networkMode network.Mode) []Check {
+func getPreflightChecksForDistro(distro *linux.OsRelease, networkMode network.Mode, systemdResolved bool) []Check {
 	var checks []Check
 	checks = append(checks, nonWinPreflightChecks[:]...)
 	checks = append(checks, genericPreflightChecks[:]...)
 	checks = append(checks, libvirtPreflightChecks(distro)...)
-	networkChecks := getNetworkChecksForDistro(distro, networkMode)
+	networkChecks := getNetworkChecks(networkMode, systemdResolved)
 	checks = append(checks, networkChecks...)
 	if networkMode == network.DefaultMode {
 		checks = append(checks, libvirtNetworkPreflightChecks[:]...)
@@ -264,17 +260,6 @@ func getPreflightChecksForDistro(distro *linux.OsRelease, networkMode network.Mo
 	return checks
 }
 
-func usesSystemdResolved(distro *linux.OsRelease) bool {
-	switch {
-	case distroIsLike(distro, linux.Ubuntu):
-		return true
-	case distro.ID == linux.Fedora:
-		return distro.VersionID >= "33"
-	default:
-		return false
-	}
-}
-
 func distroIsLike(osRelease *linux.OsRelease, osType linux.OsType) bool {
 	if osRelease == nil {
 		return false


Test patch:
diff --git a/pkg/crc/api/api_http_test.go b/pkg/crc/api/api_http_test.go
index 9be5e88f4..8b1025dec 100644
--- a/pkg/crc/api/api_http_test.go
+++ b/pkg/crc/api/api_http_test.go
@@ -6,8 +6,8 @@ import (
 	"testing"
 
 	apiClient "github.com/code-ready/crc/pkg/crc/api/client"
-	"github.com/code-ready/crc/pkg/crc/machine"
 	"github.com/code-ready/crc/pkg/crc/machine/fakemachine"
+	"github.com/code-ready/crc/pkg/crc/machine/types"
 	"github.com/code-ready/crc/pkg/crc/version"
 	"github.com/stretchr/testify/assert"
 )
@@ -59,7 +59,7 @@ func TestHTTPApi(t *testing.T) {
 			Status:         "",
 			Error:          "",
 			KubeletStarted: true,
-			ClusterConfig: machine.ClusterConfig{
+			ClusterConfig: types.ClusterConfig{
 				ClusterCACert: "MIIDODCCAiCgAwIBAgIIRVfCKNUa1wIwDQYJ",
 				KubeConfig:    "/tmp/kubeconfig",
 				KubeAdminPass: "foobar",
diff --git a/pkg/crc/machine/sync_test.go b/pkg/crc/machine/sync_test.go
new file mode 100644
index 000000000..765fed386
--- /dev/null
+++ b/pkg/crc/machine/sync_test.go
@@ -0,0 +1,166 @@
+package machine
+
+import (
+	"context"
+	"errors"
+	"sync"
+	"testing"
+
+	"github.com/code-ready/machine/libmachine/state"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestOneStartAtTheSameTime(t *testing.T) {
+	isRunning := make(chan struct{}, 1)
+	startCh := make(chan struct{}, 1)
+	waitingMachine := &waitingMachine{
+		isRunning:       isRunning,
+		startCompleteCh: startCh,
+	}
+	syncMachine := NewSynchronizedMachine(waitingMachine)
+	assert.Equal(t, Idle, syncMachine.CurrentState())
+
+	lock := &sync.WaitGroup{}
+	lock.Add(1)
+	go func() {
+		defer lock.Done()
+		_, err := syncMachine.Start(context.Background(), StartConfig{})
+		assert.NoError(t, err)
+	}()
+
+	<-isRunning
+	assert.Equal(t, Starting, syncMachine.CurrentState())
+	assert.Equal(t, waitingMachine.GetName(), syncMachine.GetName())
+	_, err := syncMachine.Start(context.Background(), StartConfig{})
+	assert.EqualError(t, err, "cluster is busy")
+
+	startCh <- struct{}{}
+	lock.Wait()
+
+	assert.Equal(t, Idle, syncMachine.CurrentState())
+}
+
+func TestDeleteStop(t *testing.T) {
+	isRunning := make(chan struct{}, 1)
+	deleteCh := make(chan struct{}, 1)
+	waitingMachine := &waitingMachine{
+		isRunning:        isRunning,
+		deleteCompleteCh: deleteCh,
+	}
+	syncMachine := NewSynchronizedMachine(waitingMachine)
+	assert.Equal(t, Idle, syncMachine.CurrentState())
+
+	lock := &sync.WaitGroup{}
+	lock.Add(1)
+	go func() {
+		defer lock.Done()
+		assert.NoError(t, syncMachine.Delete())
+	}()
+
+	<-isRunning
+	assert.Equal(t, Deleting, syncMachine.CurrentState())
+	assert.EqualError(t, syncMachine.Delete(), "cluster is stopping or deleting")
+	_, err := syncMachine.Stop()
+	assert.EqualError(t, err, "cluster is stopping or deleting")
+	_, err = syncMachine.Start(context.Background(), StartConfig{})
+	assert.EqualError(t, err, "cluster is busy")
+
+	deleteCh <- struct{}{}
+	lock.Wait()
+
+	assert.Equal(t, Idle, syncMachine.CurrentState())
+}
+
+func TestCancelStart(t *testing.T) {
+	isRunning := make(chan struct{}, 1)
+	deleteCh := make(chan struct{}, 1)
+	waitingMachine := &waitingMachine{
+		isRunning:        isRunning,
+		startCompleteCh:  make(chan struct{}, 1),
+		deleteCompleteCh: deleteCh,
+	}
+	syncMachine := NewSynchronizedMachine(waitingMachine)
+	assert.Equal(t, Idle, syncMachine.CurrentState())
+
+	lock := &sync.WaitGroup{}
+	lock.Add(1)
+	go func() {
+		defer lock.Done()
+		_, err := syncMachine.Start(context.Background(), StartConfig{})
+		assert.EqualError(t, err, "context canceled")
+	}()
+
+	<-isRunning
+	assert.Equal(t, Starting, syncMachine.CurrentState())
+
+	lock.Add(1)
+	go func() {
+		defer lock.Done()
+		assert.NoError(t, syncMachine.Delete())
+	}()
+
+	deleteCh <- struct{}{}
+	lock.Wait()
+
+	assert.Equal(t, Idle, syncMachine.CurrentState())
+}
+
+type waitingMachine struct {
+	isRunning        chan struct{}
+	startCompleteCh  chan struct{}
+	stopCompleteCh   chan struct{}
+	deleteCompleteCh chan struct{}
+}
+
+func (m *waitingMachine) IsRunning() (bool, error) {
+	return false, errors.New("not implemented")
+}
+
+func (m *waitingMachine) GetName() string {
+	return "waiting machine"
+}
+
+func (m *waitingMachine) Delete() error {
+	m.isRunning <- struct{}{}
+	<-m.deleteCompleteCh
+	return nil
+}
+
+func (m *waitingMachine) Exists() (bool, error) {
+	return false, errors.New("not implemented")
+}
+
+func (m *waitingMachine) GetConsoleURL() (*ConsoleResult, error) {
+	return nil, errors.New("not implemented")
+}
+
+func (m *waitingMachine) IP() (string, error) {
+	return "", errors.New("not implemented")
+}
+
+func (m *waitingMachine) PowerOff() error {
+	return nil
+}
+
+func (m *waitingMachine) Start(context context.Context, _ StartConfig) (*StartResult, error) {
+	m.isRunning <- struct{}{}
+	select {
+	case <-context.Done():
+		return nil, context.Err()
+	case <-m.startCompleteCh:
+		return &StartResult{
+			Status:         state.Running,
+			KubeletStarted: true,
+		}, nil
+	}
+}
+
+func (m *waitingMachine) Status() (*ClusterStatusResult, error) {
+	return nil, errors.New("not implemented")
+}
+
+func (m *waitingMachine) Stop() (state.State, error) {
+	m.isRunning <- struct{}{}
+	<-m.stopCompleteCh
+	return state.Stopped, nil
+}
diff --git a/pkg/crc/preflight/preflight_linux_test.go b/pkg/crc/preflight/preflight_linux_test.go
index 1cc33c3b0..f8f7d0dd3 100644
--- a/pkg/crc/preflight/preflight_linux_test.go
+++ b/pkg/crc/preflight/preflight_linux_test.go
@@ -35,7 +35,7 @@ var (
 
 	rhel = crcos.OsRelease{
 		ID:        crcos.RHEL,
-		VersionID: "8.2",
+		VersionID: "8.3",
 		IDLike:    string(crcos.Fedora),
 	}
 
@@ -51,15 +51,17 @@ var (
 )
 
 type checkListForDistro struct {
-	distro      *crcos.OsRelease
-	networkMode network.Mode
-	checks      []Check
+	distro          *crcos.OsRelease
+	networkMode     network.Mode
+	systemdResolved bool
+	checks          []Check
 }
 
 var checkListForDistros = []checkListForDistro{
 	{
-		distro:      &fedora,
-		networkMode: network.DefaultMode,
+		distro:          &fedora,
+		networkMode:     network.DefaultMode,
+		systemdResolved: true,
 		checks: []Check{
 			{check: checkIfRunningAsNormalUser},
 			{cleanup: adminhelper.CleanHostsFile},
@@ -90,8 +92,41 @@ var checkListForDistros = []checkListForDistro{
 		},
 	},
 	{
-		distro:      &fedora,
-		networkMode: network.VSockMode,
+		distro:          &fedora,
+		networkMode:     network.DefaultMode,
+		systemdResolved: false,
+		checks: []Check{
+			{check: checkIfRunningAsNormalUser},
+			{cleanup: adminhelper.CleanHostsFile},
+			{check: checkPodmanExecutableCached},
+			{check: checkAdminHelperExecutableCached},
+			{configKeySuffix: "check-ram"},
+			{cleanup: removeCRCMachinesDir},
+			{cleanup: removeOldLogs},
+			{cleanup: cluster.ForgetPullSecret},
+			{check: checkVirtualizationEnabled},
+			{check: checkKvmEnabled},
+			{check: checkLibvirtInstalled},
+			{check: checkUserPartOfLibvirtGroup},
+			{configKeySuffix: "check-libvirt-group-active"},
+			{check: checkLibvirtServiceRunning},
+			{check: checkLibvirtVersion},
+			{check: checkMachineDriverLibvirtInstalled},
+			{cleanup: removeCrcVM},
+			{check: checkSystemdNetworkdIsNotRunning},
+			{check: checkNetworkManagerInstalled},
+			{check: checkNetworkManagerIsRunning},
+			{check: checkCrcNetworkManagerConfig},
+			{check: checkCrcDnsmasqConfigFile},
+			{check: checkLibvirtCrcNetworkAvailable},
+			{check: checkLibvirtCrcNetworkActive},
+			{check: checkBundleExtracted},
+		},
+	},
+	{
+		distro:          &fedora,
+		networkMode:     network.VSockMode,
+		systemdResolved: false,
 		checks: []Check{
 			{check: checkIfRunningAsNormalUser},
 			{cleanup: adminhelper.CleanHostsFile},
@@ -116,8 +151,42 @@ var checkListForDistros = []checkListForDistro{
 		},
 	},
 	{
-		distro:      &rhel,
-		networkMode: network.DefaultMode,
+		distro:          &rhel,
+		networkMode:     network.DefaultMode,
+		systemdResolved: true,
+		checks: []Check{
+			{check: checkIfRunningAsNormalUser},
+			{cleanup: adminhelper.CleanHostsFile},
+			{check: checkPodmanExecutableCached},
+			{check: checkAdminHelperExecutableCached},
+			{configKeySuffix: "check-ram"},
+			{cleanup: removeCRCMachinesDir},
+			{cleanup: removeOldLogs},
+			{cleanup: cluster.ForgetPullSecret},
+			{check: checkVirtualizationEnabled},
+			{check: checkKvmEnabled},
+			{check: checkLibvirtInstalled},
+			{check: checkUserPartOfLibvirtGroup},
+			{configKeySuffix: "check-libvirt-group-active"},
+			{check: checkLibvirtServiceRunning},
+			{check: checkLibvirtVersion},
+			{check: checkMachineDriverLibvirtInstalled},
+			{cleanup: removeCrcVM},
+			{check: checkSystemdNetworkdIsNotRunning},
+			{check: checkNetworkManagerInstalled},
+			{check: checkNetworkManagerIsRunning},
+			{check: checkCrcDnsmasqAndNetworkManagerConfigFile},
+			{check: checkSystemdResolvedIsRunning},
+			{check: checkCrcNetworkManagerDispatcherFile},
+			{check: checkLibvirtCrcNetworkAvailable},
+			{check: checkLibvirtCrcNetworkActive},
+			{check: checkBundleExtracted},
+		},
+	},
+	{
+		distro:          &rhel,
+		networkMode:     network.DefaultMode,
+		systemdResolved: false,
 		checks: []Check{
 			{check: checkIfRunningAsNormalUser},
 			{cleanup: adminhelper.CleanHostsFile},
@@ -147,8 +216,9 @@ var checkListForDistros = []checkListForDistro{
 		},
 	},
 	{
-		distro:      &rhel,
-		networkMode: network.VSockMode,
+		distro:          &rhel,
+		networkMode:     network.VSockMode,
+		systemdResolved: false,
 		checks: []Check{
 			{check: checkIfRunningAsNormalUser},
 			{cleanup: adminhelper.CleanHostsFile},
@@ -173,8 +243,42 @@ var checkListForDistros = []checkListForDistro{
 		},
 	},
 	{
-		distro:      &unexpected,
-		networkMode: network.DefaultMode,
+		distro:          &unexpected,
+		networkMode:     network.DefaultMode,
+		systemdResolved: true,
+		checks: []Check{
+			{check: checkIfRunningAsNormalUser},
+			{cleanup: adminhelper.CleanHostsFile},
+			{check: checkPodmanExecutableCached},
+			{check: checkAdminHelperExecutableCached},
+			{configKeySuffix: "check-ram"},
+			{cleanup: removeCRCMachinesDir},
+			{cleanup: removeOldLogs},
+			{cleanup: cluster.ForgetPullSecret},
+			{check: checkVirtualizationEnabled},
+			{check: checkKvmEnabled},
+			{check: checkLibvirtInstalled},
+			{check: checkUserPartOfLibvirtGroup},
+			{configKeySuffix: "check-libvirt-group-active"},
+			{check: checkLibvirtServiceRunning},
+			{check: checkLibvirtVersion},
+			{check: checkMachineDriverLibvirtInstalled},
+			{cleanup: removeCrcVM},
+			{check: checkSystemdNetworkdIsNotRunning},
+			{check: checkNetworkManagerInstalled},
+			{check: checkNetworkManagerIsRunning},
+			{check: checkCrcDnsmasqAndNetworkManagerConfigFile},
+			{check: checkSystemdResolvedIsRunning},
+			{check: checkCrcNetworkManagerDispatcherFile},
+			{check: checkLibvirtCrcNetworkAvailable},
+			{check: checkLibvirtCrcNetworkActive},
+			{check: checkBundleExtracted},
+		},
+	},
+	{
+		distro:          &unexpected,
+		networkMode:     network.DefaultMode,
+		systemdResolved: false,
 		checks: []Check{
 			{check: checkIfRunningAsNormalUser},
 			{cleanup: adminhelper.CleanHostsFile},
@@ -204,8 +308,9 @@ var checkListForDistros = []checkListForDistro{
 		},
 	},
 	{
-		distro:      &unexpected,
-		networkMode: network.VSockMode,
+		distro:          &unexpected,
+		networkMode:     network.VSockMode,
+		systemdResolved: false,
 		checks: []Check{
 			{check: checkIfRunningAsNormalUser},
 			{cleanup: adminhelper.CleanHostsFile},
@@ -230,8 +335,9 @@ var checkListForDistros = []checkListForDistro{
 		},
 	},
 	{
-		distro:      &ubuntu,
-		networkMode: network.DefaultMode,
+		distro:          &ubuntu,
+		networkMode:     network.DefaultMode,
+		systemdResolved: true,
 		checks: []Check{
 			{check: checkIfRunningAsNormalUser},
 			{cleanup: adminhelper.CleanHostsFile},
@@ -263,8 +369,42 @@ var checkListForDistros = []checkListForDistro{
 		},
 	},
 	{
-		distro:      &ubuntu,
-		networkMode: network.VSockMode,
+		distro:          &ubuntu,
+		networkMode:     network.DefaultMode,
+		systemdResolved: false,
+		checks: []Check{
+			{check: checkIfRunningAsNormalUser},
+			{cleanup: adminhelper.CleanHostsFile},
+			{check: checkPodmanExecutableCached},
+			{check: checkAdminHelperExecutableCached},
+			{configKeySuffix: "check-ram"},
+			{cleanup: removeCRCMachinesDir},
+			{cleanup: removeOldLogs},
+			{cleanup: cluster.ForgetPullSecret},
+			{check: checkVirtualizationEnabled},
+			{check: checkKvmEnabled},
+			{check: checkLibvirtInstalled},
+			{check: checkUserPartOfLibvirtGroup},
+			{configKeySuffix: "check-libvirt-group-active"},
+			{check: checkLibvirtServiceRunning},
+			{check: checkLibvirtVersion},
+			{check: checkMachineDriverLibvirtInstalled},
+			{cleanup: removeCrcVM},
+			{configKeySuffix: "check-apparmor-profile-setup"},
+			{check: checkSystemdNetworkdIsNotRunning},
+			{check: checkNetworkManagerInstalled},
+			{check: checkNetworkManagerIsRunning},
+			{check: checkCrcNetworkManagerConfig},
+			{check: checkCrcDnsmasqConfigFile},
+			{check: checkLibvirtCrcNetworkAvailable},
+			{check: checkLibvirtCrcNetworkActive},
+			{check: checkBundleExtracted},
+		},
+	},
+	{
+		distro:          &ubuntu,
+		networkMode:     network.VSockMode,
+		systemdResolved: false,
 		checks: []Check{
 			{check: checkIfRunningAsNormalUser},
 			{cleanup: adminhelper.CleanHostsFile},
@@ -299,11 +439,11 @@ func assertFuncEqual(t *testing.T, func1 interface{}, func2 interface{}) {
 	assert.Equal(t, reflect.ValueOf(func1).Pointer(), reflect.ValueOf(func2).Pointer(), "%s != %s", funcToString(func1), funcToString(func2))
 }
 
-func assertExpectedPreflights(t *testing.T, distro *crcos.OsRelease, networkMode network.Mode) {
-	preflights := getPreflightChecksForDistro(distro, networkMode)
+func assertExpectedPreflights(t *testing.T, distro *crcos.OsRelease, networkMode network.Mode, systemdResolved bool) {
+	preflights := getPreflightChecksForDistro(distro, networkMode, systemdResolved)
 	var expected checkListForDistro
 	for _, expected = range checkListForDistros {
-		if expected.distro == distro && expected.networkMode == networkMode {
+		if expected.distro == distro && expected.networkMode == networkMode && expected.systemdResolved == systemdResolved {
 			break
 		}
 	}
@@ -328,15 +468,19 @@ func assertExpectedPreflights(t *testing.T, distro *crcos.OsRelease, networkMode
 }
 
 func TestCountPreflights(t *testing.T) {
-	assertExpectedPreflights(t, &fedora, network.DefaultMode)
-	assertExpectedPreflights(t, &fedora, network.VSockMode)
+	assertExpectedPreflights(t, &fedora, network.DefaultMode, true)
+	assertExpectedPreflights(t, &fedora, network.DefaultMode, false)
+	assertExpectedPreflights(t, &fedora, network.VSockMode, false)
 
-	assertExpectedPreflights(t, &rhel, network.DefaultMode)
-	assertExpectedPreflights(t, &rhel, network.VSockMode)
+	assertExpectedPreflights(t, &rhel, network.DefaultMode, true)
+	assertExpectedPreflights(t, &rhel, network.DefaultMode, false)
+	assertExpectedPreflights(t, &rhel, network.VSockMode, false)
 
-	assertExpectedPreflights(t, &unexpected, network.DefaultMode)
-	assertExpectedPreflights(t, &unexpected, network.VSockMode)
+	assertExpectedPreflights(t, &unexpected, network.DefaultMode, true)
+	assertExpectedPreflights(t, &unexpected, network.DefaultMode, false)
+	assertExpectedPreflights(t, &unexpected, network.VSockMode, false)
 
-	assertExpectedPreflights(t, &ubuntu, network.DefaultMode)
-	assertExpectedPreflights(t, &ubuntu, network.VSockMode)
+	assertExpectedPreflights(t, &ubuntu, network.DefaultMode, true)
+	assertExpectedPreflights(t, &ubuntu, network.DefaultMode, false)
+	assertExpectedPreflights(t, &ubuntu, network.VSockMode, false)
 }
diff --git a/test/e2e/features/basic.feature b/test/e2e/features/basic.feature
index ad9ab1f87..a4ccc58b7 100644
--- a/test/e2e/features/basic.feature
+++ b/test/e2e/features/basic.feature
@@ -70,7 +70,7 @@ Feature: Basic test
     @windows
     Scenario: CRC setup on Windows
         When executing "crc setup" succeeds
-        Then stderr should contain "Unpacking bundle from the CRC executable" if bundle is embedded
+        Then stderr should contain "Extracting bundle from the CRC executable" if bundle is embedded
         Then stderr should contain "Checking Windows 10 release"
         Then stderr should contain "Checking if Hyper-V is installed"
         Then stderr should contain "Checking if user is a member of the Hyper-V Administrators group"
diff --git a/test/integration/resize_test.go b/test/integration/resize_test.go
index 9356bc035..4fd8c0f52 100644
--- a/test/integration/resize_test.go
+++ b/test/integration/resize_test.go
@@ -16,7 +16,12 @@ var _ = Describe("vary VM parameters: memory cpus, disk", func() {
 		})
 
 		It("start CRC", func() {
-			Expect(RunCRCExpectSuccess("start", "-b", bundlePath, "-p", pullSecretPath)).To(ContainSubstring("Started the OpenShift cluster")) // default values: "--memory", "9216", "--cpus", "4", "disk-size", "31"
+			// default values: "--memory", "9216", "--cpus", "4", "disk-size", "31"
+			if bundlePath == "embedded" {
+				Expect(RunCRCExpectSuccess("start", "-p", pullSecretPath)).To(ContainSubstring("Started the OpenShift cluster"))
+			} else {
+				Expect(RunCRCExpectSuccess("start", "-b", bundlePath, "-p", pullSecretPath)).To(ContainSubstring("Started the OpenShift cluster"))
+			}
 		})
 
 		It("check VM's memory size", func() {
@@ -48,10 +53,10 @@ var _ = Describe("vary VM parameters: memory cpus, disk", func() {
 
 		It("start CRC", func() {
 			if runtime.GOOS == "darwin" {
-				Expect(RunCRCExpectFail("start", "-b", bundlePath, "--memory", "12000", "--cpus", "6", "--disk-size", "40")).To(ContainSubstring("Disk resizing is not supported on macOS"))
-				Expect(RunCRCExpectSuccess("start", "-b", bundlePath, "--memory", "12000", "--cpus", "6")).To(ContainSubstring("Started the OpenShift cluster"))
+				Expect(RunCRCExpectFail("start", "--memory", "12000", "--cpus", "6", "--disk-size", "40")).To(ContainSubstring("Disk resizing is not supported on macOS"))
+				Expect(RunCRCExpectSuccess("start", "--memory", "12000", "--cpus", "6")).To(ContainSubstring("Started the OpenShift cluster"))
 			} else {
-				Expect(RunCRCExpectSuccess("start", "-b", bundlePath, "--memory", "12000", "--cpus", "6", "--disk-size", "40")).To(ContainSubstring("Started the OpenShift cluster"))
+				Expect(RunCRCExpectSuccess("start", "--memory", "12000", "--cpus", "6", "--disk-size", "40")).To(ContainSubstring("Started the OpenShift cluster"))
 			}
 		})
 
@@ -71,7 +76,11 @@ var _ = Describe("vary VM parameters: memory cpus, disk", func() {
 		It("check VM's disk size", func() {
 			out, err := SendCommandToVM("df -h | grep sysroot")
 			Expect(err).NotTo(HaveOccurred())
-			Expect(out).Should(MatchRegexp(`.*40G[\s].*[\s]/sysroot`))
+			if runtime.GOOS == "darwin" { // darwin does not support resize
+				Expect(out).Should(MatchRegexp(`.*31G[\s].*[\s]/sysroot`))
+			} else {
+				Expect(out).Should(MatchRegexp(`.*40G[\s].*[\s]/sysroot`))
+			}
 		})
 
 		It("stop CRC", func() {
@@ -105,7 +114,7 @@ var _ = Describe("vary VM parameters: memory cpus, disk", func() {
 	Describe("use default values again", func() {
 
 		It("start CRC", func() {
-			Expect(RunCRCExpectSuccess("start", "-b", bundlePath)).To(ContainSubstring("Started the OpenShift cluster")) // default values: "--memory", "9216", "--cpus", "4", "disk-size", "31"
+			Expect(RunCRCExpectSuccess("start")).To(ContainSubstring("Started the OpenShift cluster")) // default values: "--memory", "9216", "--cpus", "4", "disk-size", "31"
 		})
 
 		It("check VM's memory size", func() {
diff --git a/test/integration/testsuite_test.go b/test/integration/testsuite_test.go
index 935f8aaad..a425889d4 100644
--- a/test/integration/testsuite_test.go
+++ b/test/integration/testsuite_test.go
@@ -55,16 +55,12 @@ var _ = BeforeSuite(func() {
 	Expect(err).NotTo(HaveOccurred())
 
 	// bundle location
+	bundlePath = "embedded"
 	if !versionInfo.Embedded {
-		bundlePath = os.Getenv("BUNDLE_PATH") // this env var should contain location of bundle
-		if bundlePath == "" {
-			logrus.Infof("Error: You need to set BUNDLE_PATH because your binary does not contain a bundle.")
-			logrus.Infof("%v", err)
-			Expect(err).NotTo(HaveOccurred())
+		bundlePath = os.Getenv("BUNDLE_PATH") // this env var should contain location of bundle or string "embedded"
+		if bundlePath != "embedded" {         // if real bundle
+			Expect(bundlePath).To(BeAnExistingFile())
 		}
-		Expect(bundlePath).To(BeAnExistingFile()) // not checking if it's an actual bundle
-	} else {
-		bundlePath = "embedded"
 	}
 
 	// pull-secret location