Repository: code-ready/crc
Pull request #1738 (2020-12-02 11:02:56)

Status check
**Fixes:** Issue #1737 

## Solution

The entire `crc status` check is hidden in one function. If looking for `Stopped` status, then exit with success as soon as status returns `Stopped` for the first time. If looking for `Running` status, then exit with success only if 3 consecutive probes (at 1min intervals) return `Running` cluster status. The probing is set to 15x1min.

Second commit updates the language in Feature files.

## Testing

1. `make integration` succeeds


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

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

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

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":[]} -->
openshift-merge-robot: @jsliacan: The following test **failed**, say `/retest` to rerun all failed tests:

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

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

<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 -->
jsliacan: @praveenkumar @cfergeau , I addressed all comments (I hope!). Kept 1st commit as the main one. Second improves Feature descriptions and such, and I moved all formatting changes to a new (3rd) commit. 

Patch:
diff --git a/pkg/crc/machine/libvirt/constants.go b/pkg/crc/machine/libvirt/constants.go
index ec1720ada..27d970ea3 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.12"
+	MachineDriverVersion = "0.12.13"
 )
 
 var (
diff --git a/pkg/crc/preflight/preflight_checks_linux.go b/pkg/crc/preflight/preflight_checks_linux.go
index 9f057ec90..eda0353fb 100644
--- a/pkg/crc/preflight/preflight_checks_linux.go
+++ b/pkg/crc/preflight/preflight_checks_linux.go
@@ -20,6 +20,7 @@ import (
 	"github.com/code-ready/crc/pkg/crc/systemd"
 	"github.com/code-ready/crc/pkg/crc/systemd/states"
 	crcos "github.com/code-ready/crc/pkg/os"
+	"github.com/code-ready/crc/pkg/os/linux"
 	libvirtxml "github.com/libvirt/libvirt-go-xml"
 )
 
@@ -129,14 +130,29 @@ func checkLibvirtInstalled() error {
 	return nil
 }
 
-func fixLibvirtInstalled() error {
-	logging.Debug("Trying to install libvirt")
-	stdOut, stdErr, err := crcos.RunWithPrivilege("install virtualization related packages", "yum", "install", "-y", "libvirt", "libvirt-daemon-kvm", "qemu-kvm")
-	if err != nil {
-		return fmt.Errorf("Could not install required packages: %s %v: %s", stdOut, err, stdErr)
+func fixLibvirtInstalled(distro *linux.OsRelease) func() error {
+	return func() error {
+		logging.Debug("Trying to install libvirt")
+		stdOut, stdErr, err := crcos.RunWithPrivilege("install virtualization related packages", "/bin/sh", "-c", installLibvirtCommand(distro))
+		if err != nil {
+			return fmt.Errorf("Could not install required packages: %s %v: %s", stdOut, err, stdErr)
+		}
+		logging.Debug("libvirt was successfully installed")
+		return nil
+	}
+}
+
+func installLibvirtCommand(distro *linux.OsRelease) string {
+	yumCommand := "yum install -y libvirt libvirt-daemon-kvm qemu-kvm"
+	switch distroID(distro) {
+	case linux.Ubuntu:
+		return "apt-get update && apt-get install -y libvirt-daemon libvirt-daemon-system libvirt-clients"
+	case linux.RHEL, linux.CentOS, linux.Fedora:
+		return yumCommand
+	default:
+		logging.Warnf("unsupported distribution %s, trying to install libvirt with yum", distro)
+		return yumCommand
 	}
-	logging.Debug("libvirt was successfully installed")
-	return nil
 }
 
 func fixLibvirtVersion() error {
@@ -188,19 +204,25 @@ func checkUserPartOfLibvirtGroup() error {
 	return fmt.Errorf("%s not part of libvirtd group", currentUser.Username)
 }
 
-func fixUserPartOfLibvirtGroup() error {
-	logging.Debug("Adding current user to the libvirt group")
-	currentUser, err := user.Current()
-	if err != nil {
-		logging.Debugf("user.Current() failed: %v", err)
-		return fmt.Errorf("Failed to get current user id")
-	}
-	_, _, err = crcos.RunWithPrivilege("add user to libvirt group", "usermod", "-a", "-G", "libvirt", currentUser.Username)
-	if err != nil {
-		return fmt.Errorf("Failed to add user to libvirt group")
+func fixUserPartOfLibvirtGroup(distro *linux.OsRelease) func() error {
+	return func() error {
+		logging.Debug("Adding current user to the libvirt group")
+		currentUser, err := user.Current()
+		if err != nil {
+			logging.Debugf("user.Current() failed: %v", err)
+			return fmt.Errorf("Failed to get current user id")
+		}
+		_, _, err = crcos.RunWithPrivilege("add user to libvirt group", "usermod", "-a", "-G", "libvirt", currentUser.Username)
+		if err != nil {
+			return fmt.Errorf("Failed to add user to libvirt group")
+		}
+		logging.Debug("Current user is in the libvirt group")
+
+		if distroIsLike(distro, linux.Ubuntu) {
+			return fmt.Errorf("Current user added to libvirt group. Please logout and login again")
+		}
+		return err
 	}
-	logging.Debug("Current user is in the libvirt group")
-	return nil
 }
 
 func checkLibvirtServiceRunning() error {
diff --git a/pkg/crc/preflight/preflight_linux.go b/pkg/crc/preflight/preflight_linux.go
index 246465c9c..c62be4746 100644
--- a/pkg/crc/preflight/preflight_linux.go
+++ b/pkg/crc/preflight/preflight_linux.go
@@ -14,61 +14,67 @@ import (
 	"github.com/code-ready/crc/pkg/os/linux"
 )
 
-var libvirtPreflightChecks = [...]Check{
-	{
-		configKeySuffix:  "check-virt-enabled",
-		checkDescription: "Checking if Virtualization is enabled",
-		check:            checkVirtualizationEnabled,
-		fixDescription:   "Setting up virtualization",
-		fix:              fixVirtualizationEnabled,
-	},
-	{
-		configKeySuffix:  "check-kvm-enabled",
-		checkDescription: "Checking if KVM is enabled",
-		check:            checkKvmEnabled,
-		fixDescription:   "Setting up KVM",
-		fix:              fixKvmEnabled,
-	},
-	{
-		configKeySuffix:  "check-libvirt-installed",
-		checkDescription: "Checking if libvirt is installed",
-		check:            checkLibvirtInstalled,
-		fixDescription:   "Installing libvirt service and dependencies",
-		fix:              fixLibvirtInstalled,
-	},
-	{
-		configKeySuffix:  "check-user-in-libvirt-group",
-		checkDescription: "Checking if user is part of libvirt group",
-		check:            checkUserPartOfLibvirtGroup,
-		fixDescription:   "Adding user to libvirt group",
-		fix:              fixUserPartOfLibvirtGroup,
-	},
-	{
-		configKeySuffix:  "check-libvirt-running",
-		checkDescription: "Checking if libvirt daemon is running",
-		check:            checkLibvirtServiceRunning,
-		fixDescription:   "Starting libvirt service",
-		fix:              fixLibvirtServiceRunning,
-	},
-	{
-		configKeySuffix:  "check-libvirt-version",
-		checkDescription: "Checking if a supported libvirt version is installed",
-		check:            checkLibvirtVersion,
-		fixDescription:   "Installing a supported libvirt version",
-		fix:              fixLibvirtVersion,
-	},
-	{
-		configKeySuffix:  "check-libvirt-driver",
-		checkDescription: "Checking if crc-driver-libvirt is installed",
-		check:            checkMachineDriverLibvirtInstalled,
-		fixDescription:   "Installing crc-driver-libvirt",
-		fix:              fixMachineDriverLibvirtInstalled,
-	},
-	{
-		cleanupDescription: "Removing the crc VM if exists",
-		cleanup:            removeCrcVM,
-		flags:              CleanUpOnly,
-	},
+func libvirtPreflightChecks(distro *linux.OsRelease) []Check {
+	checks := []Check{
+		{
+			configKeySuffix:  "check-virt-enabled",
+			checkDescription: "Checking if Virtualization is enabled",
+			check:            checkVirtualizationEnabled,
+			fixDescription:   "Setting up virtualization",
+			fix:              fixVirtualizationEnabled,
+		},
+		{
+			configKeySuffix:  "check-kvm-enabled",
+			checkDescription: "Checking if KVM is enabled",
+			check:            checkKvmEnabled,
+			fixDescription:   "Setting up KVM",
+			fix:              fixKvmEnabled,
+		},
+		{
+			configKeySuffix:  "check-libvirt-installed",
+			checkDescription: "Checking if libvirt is installed",
+			check:            checkLibvirtInstalled,
+			fixDescription:   "Installing libvirt service and dependencies",
+			fix:              fixLibvirtInstalled(distro),
+		},
+		{
+			configKeySuffix:  "check-user-in-libvirt-group",
+			checkDescription: "Checking if user is part of libvirt group",
+			check:            checkUserPartOfLibvirtGroup,
+			fixDescription:   "Adding user to libvirt group",
+			fix:              fixUserPartOfLibvirtGroup(distro),
+		},
+		{
+			configKeySuffix:  "check-libvirt-running",
+			checkDescription: "Checking if libvirt daemon is running",
+			check:            checkLibvirtServiceRunning,
+			fixDescription:   "Starting libvirt service",
+			fix:              fixLibvirtServiceRunning,
+		},
+		{
+			configKeySuffix:  "check-libvirt-version",
+			checkDescription: "Checking if a supported libvirt version is installed",
+			check:            checkLibvirtVersion,
+			fixDescription:   "Installing a supported libvirt version",
+			fix:              fixLibvirtVersion,
+		},
+		{
+			configKeySuffix:  "check-libvirt-driver",
+			checkDescription: "Checking if crc-driver-libvirt is installed",
+			check:            checkMachineDriverLibvirtInstalled,
+			fixDescription:   "Installing crc-driver-libvirt",
+			fix:              fixMachineDriverLibvirtInstalled,
+		},
+		{
+			cleanupDescription: "Removing the crc VM if exists",
+			cleanup:            removeCrcVM,
+			flags:              CleanUpOnly,
+		},
+	}
+	if distroID(distro) == linux.Ubuntu {
+		checks = append(checks, ubuntuPreflightChecks...)
+	}
+	return checks
 }
 
 var libvirtNetworkPreflightChecks = [...]Check{
@@ -172,19 +178,17 @@ func getNetworkChecksForDistro(distro *linux.OsRelease, networkMode network.Mode
 		return append(checks, vsockPreflightChecks)
 	}
 
-	switch distroID(distro) {
+	switch {
 	default:
 		logging.Warnf("distribution-specific preflight checks are not implemented for '%s'", distroID(distro))
 		fallthrough
-	case linux.RHEL, linux.CentOS, linux.Fedora:
+	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[:]...)
 		}
-	case linux.Ubuntu:
-		break
 	}
 
 	return checks
@@ -194,15 +198,19 @@ func getPreflightChecksForDistro(distro *linux.OsRelease, networkMode network.Mo
 	var checks []Check
 	checks = append(checks, genericPreflightChecks[:]...)
 	checks = append(checks, nonWinPreflightChecks[:]...)
-	checks = append(checks, libvirtPreflightChecks[:]...)
+	checks = append(checks, libvirtPreflightChecks(distro)...)
 	networkChecks := getNetworkChecksForDistro(distro, networkMode)
 	checks = append(checks, networkChecks...)
-	checks = append(checks, libvirtNetworkPreflightChecks[:]...)
+	if networkMode == network.DefaultMode {
+		checks = append(checks, libvirtNetworkPreflightChecks[:]...)
+	}
 	return checks
 }
 
 func usesSystemdResolved(osRelease *linux.OsRelease) bool {
 	switch distroID(osRelease) {
+	case linux.Ubuntu:
+		return true
 	case linux.Fedora:
 		return osRelease.VersionID >= "33"
 	default:
@@ -218,6 +226,23 @@ func distroID(osRelease *linux.OsRelease) linux.OsType {
 	return osRelease.ID
 }
 
+func distroIsLike(osRelease *linux.OsRelease, osType linux.OsType) bool {
+	if osRelease == nil {
+		return false
+	}
+	if osRelease.ID == osType {
+		return true
+	}
+
+	for _, id := range osRelease.GetIDLike() {
+		if id == osType {
+			return true
+		}
+	}
+
+	return false
+}
+
 func distro() *linux.OsRelease {
 	distro, err := linux.GetOsRelease()
 	if err != nil {
diff --git a/pkg/crc/preflight/preflight_ubuntu_linux.go b/pkg/crc/preflight/preflight_ubuntu_linux.go
new file mode 100644
index 000000000..6f38f8c4c
--- /dev/null
+++ b/pkg/crc/preflight/preflight_ubuntu_linux.go
@@ -0,0 +1,79 @@
+// +build linux
+
+package preflight
+
+import (
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"github.com/code-ready/crc/pkg/crc/constants"
+	crcos "github.com/code-ready/crc/pkg/os"
+)
+
+var ubuntuPreflightChecks = []Check{
+	{
+		configKeySuffix:    "check-apparmor-profile-setup",
+		checkDescription:   "Checking if AppArmor is configured",
+		check:              checkAppArmorExceptionIsPresent(ioutil.ReadFile),
+		fixDescription:     "Updating AppArmor configuration",
+		fix:                addAppArmorExceptionForQcowDisks(ioutil.ReadFile, crcos.WriteToFileAsRoot),
+		cleanupDescription: "Cleaning up AppArmor configuration",
+		cleanup:            removeAppArmorExceptionForQcowDisks(ioutil.ReadFile, crcos.WriteToFileAsRoot),
+	},
+}
+
+const (
+	appArmorTemplate = "/etc/apparmor.d/libvirt/TEMPLATE.qemu"
+	appArmorHeader   = "profile LIBVIRT_TEMPLATE flags=(attach_disconnected) {"
+)
+
+type reader func(filename string) ([]byte, error)
+type writer func(reason, content, filepath string, mode os.FileMode) error
+
+// Verify the line is present in AppArmor template
+func checkAppArmorExceptionIsPresent(reader reader) func() error {
+	return func() error {
+		template, err := reader(appArmorTemplate)
+		if err != nil {
+			return err
+		}
+		if !strings.Contains(string(template), expectedLines()) {
+			return errors.New("AppArmor profile not configured")
+		}
+		return nil
+	}
+}
+
+// Add the exception `cacheDir/*/crc.qcow2 rk` in AppArmor template
+func addAppArmorExceptionForQcowDisks(reader reader, writer writer) func() error {
+	return replaceInAppArmorTemplate(reader, writer, appArmorHeader, expectedLines())
+}
+
+// Eventually remove the exception in AppArmor template
+func removeAppArmorExceptionForQcowDisks(reader reader, writer writer) func() error {
+	return replaceInAppArmorTemplate(reader, writer, expectedLines(), appArmorHeader)
+}
+
+// Search for the string `before` in the AppArmor template and replace it with `after` string.
+func replaceInAppArmorTemplate(reader reader, writer writer, before string, after string) func() error {
+	return func() error {
+		template, err := reader(appArmorTemplate)
+		if err != nil {
+			return err
+		}
+		if !strings.Contains(string(template), before) {
+			return fmt.Errorf("unexpected AppArmor template file %s, cannot configure it automatically", appArmorTemplate)
+		}
+		content := strings.Replace(string(template), before, after, 1)
+		return writer("updating AppArmor configuration", content, appArmorTemplate, 0644)
+	}
+}
+
+func expectedLines() string {
+	line := fmt.Sprintf("  %s rk,", filepath.Join(constants.MachineCacheDir, "*", "crc.qcow2"))
+	return fmt.Sprintf("%s\n%s\n", appArmorHeader, line)
+}
diff --git a/pkg/os/linux/release_info.go b/pkg/os/linux/release_info.go
index 614e6f21d..bd951be21 100644
--- a/pkg/os/linux/release_info.go
+++ b/pkg/os/linux/release_info.go
@@ -114,3 +114,17 @@ func GetOsRelease() (*OsRelease, error) {
 	}
 	return &osr, nil
 }
+
+func (osr *OsRelease) GetIDLike() []OsType {
+	var idLike []OsType
+
+	if osr == nil {
+		return idLike
+	}
+
+	for _, id := range strings.Split(osr.IDLike, " ") {
+		idLike = append(idLike, OsType(id))
+	}
+
+	return idLike
+}


Test patch:
diff --git a/pkg/crc/preflight/preflight_linux_test.go b/pkg/crc/preflight/preflight_linux_test.go
index f7171b440..ad6571aae 100644
--- a/pkg/crc/preflight/preflight_linux_test.go
+++ b/pkg/crc/preflight/preflight_linux_test.go
@@ -1,6 +1,8 @@
 package preflight
 
 import (
+	"reflect"
+	"runtime"
 	"testing"
 
 	"github.com/code-ready/crc/pkg/crc/config"
@@ -24,29 +26,279 @@ func TestCountConfigurationOptions(t *testing.T) {
 }
 
 var (
-	rhel crcos.OsRelease = crcos.OsRelease{
+	fedora = crcos.OsRelease{
+		ID:        crcos.Fedora,
+		VersionID: "35",
+	}
+
+	rhel = crcos.OsRelease{
 		ID:        crcos.RHEL,
 		VersionID: "8.2",
+		IDLike:    string(crcos.Fedora),
 	}
 
-	ubuntu crcos.OsRelease = crcos.OsRelease{
+	ubuntu = crcos.OsRelease{
 		ID:        crcos.Ubuntu,
 		VersionID: "20.04",
 	}
 
-	unexpected crcos.OsRelease = crcos.OsRelease{
+	unexpected = crcos.OsRelease{
 		ID:        "unexpected",
 		VersionID: "1234",
 	}
 )
 
+type checkListForDistro struct {
+	distro      *crcos.OsRelease
+	networkMode network.Mode
+	checks      []Check
+}
+
+var checkListForDistros = []checkListForDistro{
+	{
+		distro:      &fedora,
+		networkMode: network.DefaultMode,
+		checks: []Check{
+			{check: checkPodmanExecutableCached},
+			{check: checkGoodhostsExecutableCached},
+			{check: checkBundleExtracted},
+			{configKeySuffix: "check-ram"},
+			{cleanup: removeCRCMachinesDir},
+			{check: checkIfRunningAsNormalUser},
+			{check: checkVirtualizationEnabled},
+			{check: checkKvmEnabled},
+			{check: checkLibvirtInstalled},
+			{check: checkUserPartOfLibvirtGroup},
+			{check: checkLibvirtServiceRunning},
+			{check: checkLibvirtVersion},
+			{check: checkMachineDriverLibvirtInstalled},
+			{cleanup: removeCrcVM},
+			{check: checkSystemdNetworkdIsNotRunning},
+			{check: checkNetworkManagerInstalled},
+			{check: checkNetworkManagerIsRunning},
+			{check: checkCrcDnsmasqAndNetworkManagerConfigFile},
+			{check: checkSystemdResolvedIsRunning},
+			{check: checkCrcNetworkManagerDispatcherFile},
+			{check: checkLibvirtCrcNetworkAvailable},
+			{check: checkLibvirtCrcNetworkActive},
+		},
+	},
+	{
+		distro:      &fedora,
+		networkMode: network.VSockMode,
+		checks: []Check{
+			{check: checkPodmanExecutableCached},
+			{check: checkGoodhostsExecutableCached},
+			{check: checkBundleExtracted},
+			{configKeySuffix: "check-ram"},
+			{cleanup: removeCRCMachinesDir},
+			{check: checkIfRunningAsNormalUser},
+			{check: checkVirtualizationEnabled},
+			{check: checkKvmEnabled},
+			{check: checkLibvirtInstalled},
+			{check: checkUserPartOfLibvirtGroup},
+			{check: checkLibvirtServiceRunning},
+			{check: checkLibvirtVersion},
+			{check: checkMachineDriverLibvirtInstalled},
+			{cleanup: removeCrcVM},
+			{check: checkVsock},
+		},
+	},
+	{
+		distro:      &rhel,
+		networkMode: network.DefaultMode,
+		checks: []Check{
+			{check: checkPodmanExecutableCached},
+			{check: checkGoodhostsExecutableCached},
+			{check: checkBundleExtracted},
+			{configKeySuffix: "check-ram"},
+			{cleanup: removeCRCMachinesDir},
+			{check: checkIfRunningAsNormalUser},
+			{check: checkVirtualizationEnabled},
+			{check: checkKvmEnabled},
+			{check: checkLibvirtInstalled},
+			{check: checkUserPartOfLibvirtGroup},
+			{check: checkLibvirtServiceRunning},
+			{check: checkLibvirtVersion},
+			{check: checkMachineDriverLibvirtInstalled},
+			{cleanup: removeCrcVM},
+			{check: checkSystemdNetworkdIsNotRunning},
+			{check: checkNetworkManagerInstalled},
+			{check: checkNetworkManagerIsRunning},
+			{check: checkCrcNetworkManagerConfig},
+			{check: checkCrcDnsmasqConfigFile},
+			{check: checkLibvirtCrcNetworkAvailable},
+			{check: checkLibvirtCrcNetworkActive},
+		},
+	},
+	{
+		distro:      &rhel,
+		networkMode: network.VSockMode,
+		checks: []Check{
+			{check: checkPodmanExecutableCached},
+			{check: checkGoodhostsExecutableCached},
+			{check: checkBundleExtracted},
+			{configKeySuffix: "check-ram"},
+			{cleanup: removeCRCMachinesDir},
+			{check: checkIfRunningAsNormalUser},
+			{check: checkVirtualizationEnabled},
+			{check: checkKvmEnabled},
+			{check: checkLibvirtInstalled},
+			{check: checkUserPartOfLibvirtGroup},
+			{check: checkLibvirtServiceRunning},
+			{check: checkLibvirtVersion},
+			{check: checkMachineDriverLibvirtInstalled},
+			{cleanup: removeCrcVM},
+			{check: checkVsock},
+		},
+	},
+	{
+		distro:      &unexpected,
+		networkMode: network.DefaultMode,
+		checks: []Check{
+			{check: checkPodmanExecutableCached},
+			{check: checkGoodhostsExecutableCached},
+			{check: checkBundleExtracted},
+			{configKeySuffix: "check-ram"},
+			{cleanup: removeCRCMachinesDir},
+			{check: checkIfRunningAsNormalUser},
+			{check: checkVirtualizationEnabled},
+			{check: checkKvmEnabled},
+			{check: checkLibvirtInstalled},
+			{check: checkUserPartOfLibvirtGroup},
+			{check: checkLibvirtServiceRunning},
+			{check: checkLibvirtVersion},
+			{check: checkMachineDriverLibvirtInstalled},
+			{cleanup: removeCrcVM},
+			{check: checkSystemdNetworkdIsNotRunning},
+			{check: checkNetworkManagerInstalled},
+			{check: checkNetworkManagerIsRunning},
+			{check: checkCrcNetworkManagerConfig},
+			{check: checkCrcDnsmasqConfigFile},
+			{check: checkLibvirtCrcNetworkAvailable},
+			{check: checkLibvirtCrcNetworkActive},
+		},
+	},
+	{
+		distro:      &unexpected,
+		networkMode: network.VSockMode,
+		checks: []Check{
+			{check: checkPodmanExecutableCached},
+			{check: checkGoodhostsExecutableCached},
+			{check: checkBundleExtracted},
+			{configKeySuffix: "check-ram"},
+			{cleanup: removeCRCMachinesDir},
+			{check: checkIfRunningAsNormalUser},
+			{check: checkVirtualizationEnabled},
+			{check: checkKvmEnabled},
+			{check: checkLibvirtInstalled},
+			{check: checkUserPartOfLibvirtGroup},
+			{check: checkLibvirtServiceRunning},
+			{check: checkLibvirtVersion},
+			{check: checkMachineDriverLibvirtInstalled},
+			{cleanup: removeCrcVM},
+			{check: checkVsock},
+		},
+	},
+	{
+		distro:      &ubuntu,
+		networkMode: network.DefaultMode,
+		checks: []Check{
+			{check: checkPodmanExecutableCached},
+			{check: checkGoodhostsExecutableCached},
+			{check: checkBundleExtracted},
+			{configKeySuffix: "check-ram"},
+			{cleanup: removeCRCMachinesDir},
+			{check: checkIfRunningAsNormalUser},
+			{check: checkVirtualizationEnabled},
+			{check: checkKvmEnabled},
+			{check: checkLibvirtInstalled},
+			{check: checkUserPartOfLibvirtGroup},
+			{check: checkLibvirtServiceRunning},
+			{check: checkLibvirtVersion},
+			{check: checkMachineDriverLibvirtInstalled},
+			{cleanup: removeCrcVM},
+			{configKeySuffix: "check-apparmor-profile-setup"},
+			{check: checkSystemdNetworkdIsNotRunning},
+			{check: checkNetworkManagerInstalled},
+			{check: checkNetworkManagerIsRunning},
+			{check: checkCrcDnsmasqAndNetworkManagerConfigFile},
+			{check: checkSystemdResolvedIsRunning},
+			{check: checkCrcNetworkManagerDispatcherFile},
+			{check: checkLibvirtCrcNetworkAvailable},
+			{check: checkLibvirtCrcNetworkActive},
+		},
+	},
+	{
+		distro:      &ubuntu,
+		networkMode: network.VSockMode,
+		checks: []Check{
+			{check: checkPodmanExecutableCached},
+			{check: checkGoodhostsExecutableCached},
+			{check: checkBundleExtracted},
+			{configKeySuffix: "check-ram"},
+			{cleanup: removeCRCMachinesDir},
+			{check: checkIfRunningAsNormalUser},
+			{check: checkVirtualizationEnabled},
+			{check: checkKvmEnabled},
+			{check: checkLibvirtInstalled},
+			{check: checkUserPartOfLibvirtGroup},
+			{check: checkLibvirtServiceRunning},
+			{check: checkLibvirtVersion},
+			{check: checkMachineDriverLibvirtInstalled},
+			{cleanup: removeCrcVM},
+			{configKeySuffix: "check-apparmor-profile-setup"},
+			{check: checkVsock},
+		},
+	},
+}
+
+func funcToString(f interface{}) string {
+	return runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
+}
+
+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)
+	var expected checkListForDistro
+	for _, expected = range checkListForDistros {
+		if expected.distro == distro && expected.networkMode == networkMode {
+			break
+		}
+	}
+
+	assert.Equal(t, len(preflights), len(expected.checks), "%s %s - %s - expected: %d - got: %d", distro.ID, distro.VersionID, networkMode, len(expected.checks), len(preflights))
+
+	for i := range preflights {
+		expectedCheck := expected.checks[i]
+		if expectedCheck.configKeySuffix != "" {
+			assert.Equal(t, preflights[i].configKeySuffix, expectedCheck.configKeySuffix)
+		}
+		if expectedCheck.check != nil {
+			assertFuncEqual(t, preflights[i].check, expectedCheck.check)
+		}
+		if expectedCheck.fix != nil {
+			assertFuncEqual(t, preflights[i].fix, expectedCheck.fix)
+		}
+		if expectedCheck.cleanup != nil {
+			assertFuncEqual(t, preflights[i].cleanup, expectedCheck.cleanup)
+		}
+	}
+}
+
 func TestCountPreflights(t *testing.T) {
-	assert.Len(t, getPreflightChecksForDistro(&rhel, network.DefaultMode), 21)
-	assert.Len(t, getPreflightChecksForDistro(&rhel, network.VSockMode), 17)
+	assertExpectedPreflights(t, &fedora, network.DefaultMode)
+	assertExpectedPreflights(t, &fedora, network.VSockMode)
+
+	assertExpectedPreflights(t, &rhel, network.DefaultMode)
+	assertExpectedPreflights(t, &rhel, network.VSockMode)
 
-	assert.Len(t, getPreflightChecksForDistro(&unexpected, network.DefaultMode), 21)
-	assert.Len(t, getPreflightChecksForDistro(&unexpected, network.VSockMode), 17)
+	assertExpectedPreflights(t, &unexpected, network.DefaultMode)
+	assertExpectedPreflights(t, &unexpected, network.VSockMode)
 
-	assert.Len(t, getPreflightChecksForDistro(&ubuntu, network.DefaultMode), 16)
-	assert.Len(t, getPreflightChecksForDistro(&ubuntu, network.VSockMode), 17)
+	assertExpectedPreflights(t, &ubuntu, network.DefaultMode)
+	assertExpectedPreflights(t, &ubuntu, network.VSockMode)
 }
diff --git a/pkg/crc/preflight/preflight_ubuntu_linux_test.go b/pkg/crc/preflight/preflight_ubuntu_linux_test.go
new file mode 100644
index 000000000..b268443ab
--- /dev/null
+++ b/pkg/crc/preflight/preflight_ubuntu_linux_test.go
@@ -0,0 +1,56 @@
+// +build linux
+
+package preflight
+
+import (
+	"errors"
+	"os"
+	"testing"
+
+	"github.com/code-ready/crc/pkg/crc/constants"
+
+	"github.com/stretchr/testify/assert"
+)
+
+var (
+	source = `#include <tunables/global>
+profile LIBVIRT_TEMPLATE flags=(attach_disconnected) {
+  #include <abstractions/libvirt-qemu>
+}`
+	expected = `#include <tunables/global>
+profile LIBVIRT_TEMPLATE flags=(attach_disconnected) {
+  ` + constants.MachineCacheDir + `/*/crc.qcow2 rk,
+
+  #include <abstractions/libvirt-qemu>
+}`
+)
+
+func TestCheckAppArmor(t *testing.T) {
+	assert.EqualError(t, checkAppArmorExceptionIsPresent(mockReader(source))(), "AppArmor profile not configured")
+	assert.NoError(t, checkAppArmorExceptionIsPresent(mockReader(expected))())
+}
+
+func TestFixAppArmor(t *testing.T) {
+	assert.NoError(t, addAppArmorExceptionForQcowDisks(mockReader(source), writerVerifier(expected))())
+	assert.EqualError(t, addAppArmorExceptionForQcowDisks(mockReader("invalid"), writerVerifier(expected))(),
+		"unexpected AppArmor template file /etc/apparmor.d/libvirt/TEMPLATE.qemu, cannot configure it automatically")
+}
+
+func TestCleanupAppArmor(t *testing.T) {
+	assert.NoError(t, removeAppArmorExceptionForQcowDisks(mockReader(expected), writerVerifier(source))())
+}
+
+func writerVerifier(expected string) func(reason string, content string, filepath string, mode os.FileMode) error {
+	return func(reason, content, filepath string, mode os.FileMode) error {
+		if content == expected {
+			return nil
+		}
+		return errors.New("unexpected content")
+	}
+}
+
+func mockReader(input string) func(string) ([]byte, error) {
+	return func(string) ([]byte, error) {
+		return []byte(input), nil
+	}
+}
diff --git a/test/integration/crcsuite/crcsuite.go b/test/integration/crcsuite/crcsuite.go
index 0d4de421b..9e1ca7fca 100644
--- a/test/integration/crcsuite/crcsuite.go
+++ b/test/integration/crcsuite/crcsuite.go
@@ -54,6 +54,8 @@ func FeatureContext(s *godog.Suite) {
 		CheckHTTPResponseWithRetry)
 	s.Step(`^with up to "(\d+)" retries with wait period of "(\d*(?:ms|s|m))" command "(.*)" output (should match|matches|should not match|does not match) "(.*)"$`,
 		CheckOutputMatchWithRetry)
+	s.Step(`^checking that CRC is (running|stopped)$`,
+		CheckCRCStatus)
 	s.Step(`stdout (?:should contain|contains) "(.*)" if bundle (is|is not) embedded$`,
 		StdoutContainsIfBundleEmbeddedOrNot)
 
@@ -247,6 +249,47 @@ func CheckOutputMatchWithRetry(retryCount int, retryTime string, command string,
 	return matchErr
 }
 
+// CheckCRCStatus checks that output of status command
+// matches given regex number of consecutive times
+func CheckCRCStatus(state string) error {
+	retryDuration := 1 * time.Minute
+	retryCount := 15
+	cmd := "crc status --log-level debug"
+	expression := ""
+	var numConsecutive int
+
+	if state == "stopped" {
+		numConsecutive = 1
+		expression = ".*Stopped.*"
+	} else {
+		numConsecutive = 3
+		expression = `.*Running \(v\d+\.\d+\.\d+.*\).*`
+	}
+
+	var matchErr error
+	var count int // holds num of consecutive matches
+
+	for i := 0; i < retryCount; i++ {
+		execErr := clicumber.ExecuteCommand(cmd)
+		if execErr == nil {
+			matchErr = clicumber.CommandReturnShouldMatch("stdout", expression)
+			// update counter for consecutive matches
+			if matchErr == nil {
+				count++
+			} else {
+				count = 0
+			}
+			// break if done
+			if count == numConsecutive {
+				return nil
+			}
+		}
+		time.Sleep(retryDuration)
+	}
+
+	return fmt.Errorf("did not get enough consecutive matches: have %d but need %d", count, numConsecutive)
+}
+
 func DeleteFileFromCRCHome(fileName string) error {
 
 	theFile := filepath.Join(CRCHome, fileName)
diff --git a/test/integration/features/basic.feature b/test/integration/features/basic.feature
index f69f2e9d1..3867d75f5 100644
--- a/test/integration/features/basic.feature
+++ b/test/integration/features/basic.feature
@@ -1,6 +1,8 @@
 @basic
 Feature: Basic test
-    Checks whether CRC top-level commands behave correctly.
+
+    User explores some of the top-level CRC commands while going
+    through the lifecycle of CRC.
 
     @darwin @linux @windows
     Scenario: CRC version
@@ -21,7 +23,7 @@ Feature: Basic test
         And stdout should contain "delete"
         And stdout should contain "status"
         And stdout should contain "Flags:"
-        And stdout should contain 
+        And stdout should contain
             """
             Use "crc [command] --help" for more information about a command.
             """
@@ -29,10 +31,10 @@ Feature: Basic test
     @darwin @linux @windows
     Scenario: CRC status
         When executing "crc status" fails
-        Then stderr should contain 
-        """
-        Machine 'crc' does not exist. Use 'crc start' to create it
-        """
+        Then stderr should contain
+            """
+            Machine 'crc' does not exist. Use 'crc start' to create it
+            """
 
     @linux
     Scenario: CRC setup on Linux
@@ -55,8 +57,8 @@ Feature: Basic test
         And stderr should contain "Will use root access: executing systemctl daemon-reload command"
         And stderr should contain "Will use root access: executing systemctl reload NetworkManager"
         And stderr should contain "Checking if /etc/NetworkManager/dnsmasq.d/crc.conf exists"
-	And stderr should contain "Writing dnsmasq config for crc"
-	And stderr should contain "Will use root access: write NetworkManager configuration to /etc/NetworkManager/dnsmasq.d/crc.conf"
+        And stderr should contain "Writing dnsmasq config for crc"
+        And stderr should contain "Will use root access: write NetworkManager configuration to /etc/NetworkManager/dnsmasq.d/crc.conf"
         And stderr should contain "Will use root access: executing systemctl daemon-reload command"
         And stderr should contain "Will use root access: executing systemctl reload NetworkManager"
         And stdout should contain "Setup is complete, you can now run 'crc start -b $bundlename' to start the OpenShift cluster" if bundle is not embedded
@@ -92,8 +94,7 @@ Feature: Basic test
 
     @darwin @linux @windows
     Scenario: CRC status and disk space check
-        When with up to "15" retries with wait period of "1m" command "crc status --log-level debug" output should not contain "Stopped"
-        And stdout should contain "Running"
+        When checking that CRC is running
         And stdout should match ".*Disk Usage: *\d+\.\d+GB of 32.\d+GB.*"
 
     @darwin @linux @windows
@@ -119,13 +120,11 @@ Feature: Basic test
 
     @darwin @linux @windows
     Scenario: CRC status check
-        When with up to "2" retries with wait period of "1m" command "crc status --log-level debug" output should not contain "Running"
-        And stdout should contain "Stopped"
+        When checking that CRC is stopped
+        And stdout should not contain "Running"
 
     @darwin @linux @windows
     Scenario: CRC console check
-        Given executing "crc status" succeeds
-        And stdout contains "Stopped"
         When executing "crc console"
         Then stderr should contain "The OpenShift cluster is not running, cannot open the OpenShift Web Console"
 
@@ -150,7 +149,7 @@ Feature: Basic test
         When executing "crc cleanup" succeeds
         Then stderr should contain "Removing the crc VM if exists"
         And stderr should contain "Removing /etc/NetworkManager/dnsmasq.d/crc.conf file"
-	And stderr should contain "Will use root access: removing NetworkManager configuration file in /etc/NetworkManager/dnsmasq.d/crc.conf"
+        And stderr should contain "Will use root access: removing NetworkManager configuration file in /etc/NetworkManager/dnsmasq.d/crc.conf"
         And stderr should contain "Will use root access: executing systemctl daemon-reload command"
         And stderr should contain "Will use root access: executing systemctl reload NetworkManager"
         And stderr should contain "Removing /etc/NetworkManager/conf.d/crc-nm-dnsmasq.conf file"
diff --git a/test/integration/features/cert_rotation.feature b/test/integration/features/cert_rotation.feature
index 0cacde1d1..85e4919cb 100644
--- a/test/integration/features/cert_rotation.feature
+++ b/test/integration/features/cert_rotation.feature
@@ -1,27 +1,27 @@
 @cert_rotation @linux
-Feature: Check the cert is rotation happen after it expire
+Feature: Certificate rotation test
 
-    The user try to use crc after one month. he/she expect the
-    cert rotation happen successfully and able to deploy the app and check its
-    accessibility.
+    User starts CRC more than one month after the release. They expect
+    certificate rotation to happen successfully and to be able to deploy
+    an app and check its accessibility.
 
-    Scenario: Set clock to 3 month ahead on the host
-      Given executing "sudo timedatectl set-ntp off" succeeds
-      Then executing "sudo date -s '3 month'" succeeds
+    Scenario: Set clock to 3 months ahead on the host
+        Given executing "sudo timedatectl set-ntp off" succeeds
+        Then executing "sudo date -s '3 month'" succeeds
 
     Scenario: Start CRC
-      Given executing "crc setup" succeeds
-      When starting CRC with default bundle along with stopped network time synchronization succeeds
-      Then stdout should contain "Started the OpenShift cluster"
-      And executing "eval $(crc oc-env)" succeeds
-      When with up to "4" retries with wait period of "2m" command "crc status --log-level debug" output matches ".*Running \(v\d+\.\d+\.\d+.*\).*"
-      Then login to the oc cluster succeeds
+        Given executing "crc setup" succeeds
+        When starting CRC with default bundle along with stopped network time synchronization succeeds
+        Then stdout should contain "Started the OpenShift cluster"
+        And executing "eval $(crc oc-env)" succeeds
+        When checking that CRC is running
+        Then login to the oc cluster succeeds
 
-    Scenario: Set clock back to original time
-      When executing "sudo date -s '-3 month'" succeeds
-      And executing "sudo timedatectl set-ntp on" succeeds
+    Scenario: Set clock back to the original time
+        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
-      Then stdout should contain "Deleted the OpenShift cluster"
-      When executing "crc cleanup" succeeds
+        When executing "crc delete -f" succeeds
+        Then stdout should contain "Deleted the OpenShift cluster"
+        When executing "crc cleanup" succeeds
diff --git a/test/integration/features/config.feature b/test/integration/features/config.feature
index 155d353c8..dd323b76c 100644
--- a/test/integration/features/config.feature
+++ b/test/integration/features/config.feature
@@ -1,8 +1,10 @@
 @config
 Feature: Test configuration settings
-Checks whether CRC `config set` command works as expected in conjunction with `crc setup` and `crc start`.
 
-# SETTINGS
+    User checks whether CRC `config set` command works as expected 
+    in conjunction with `crc setup` and `crc start` commands.
+
+    # SETTINGS
 
     Scenario Outline: CRC config checks (settings)
         When setting config property "<property>" to value "<value1>" succeeds
@@ -54,7 +56,7 @@ Checks whether CRC `config set` command works as expected in conjunction with `c
         When unsetting config property "disable-update-check" succeeds
         Then "JSON" config file "crc.json" in CRC home folder does not contain key "disable-update-check"
 
-# WARNINGS
+    # WARNINGS
 
     Scenario Outline: CRC config checks (warnings)
         When setting config property "<property>" to value "<value1>" succeeds
@@ -103,7 +105,7 @@ Checks whether CRC `config set` command works as expected in conjunction with `c
             | warn-check-user-in-hyperv-group | true   | false  |
             | warn-check-windows-version      | true   | false  |
 
-# SKIP
+    # SKIP
 
     Scenario Outline: CRC config checks (skips)
         When setting config property "<property>" to value "<value1>" succeeds
@@ -156,44 +158,44 @@ Checks whether CRC `config set` command works as expected in conjunction with `c
             | skip-check-user-in-hyperv-group | true   | false  |
             | skip-check-windows-version      | true   | false  |
 
-# --------------------------------------
-# Linux-specific Scenarios
-
-   @linux
-   Scenario: Check network setup and destroy it, then check again
-       When removing file "crc.json" from CRC home folder succeeds
-       And executing "crc setup" succeeds
-       And executing "sudo virsh net-list --name" succeeds
-       Then stdout contains "crc"
-       When executing "sudo virsh net-undefine crc && sudo virsh net-destroy crc" succeeds
-       And executing "sudo virsh net-list --name" succeeds
-       Then stdout should not contain "crc"
-
-   @linux
-   Scenario: Running `crc setup` with checks enabled restores destroyed network
-       When executing "crc config set skip-check-crc-network false" succeeds
-       And executing "crc config set skip-check-crc-network-active false" succeeds
-       Then executing "crc setup" succeeds
-       And executing "sudo virsh net-list --name" succeeds
-       And stdout contains "crc"
-
-   @linux
-   Scenario: Running `crc start` without `crc setup` and with checks disabled fails when network destroyed
-       # Destroy network again
-       When executing "sudo virsh net-undefine crc && sudo virsh net-destroy crc" succeeds
-       And executing "sudo virsh net-list --name" succeeds
-       Then stdout should not contain "crc"
-       # Disable checks
-       When executing "crc config set skip-check-crc-network true" succeeds
-       And executing "crc config set skip-check-crc-network-active true" succeeds
-       # Start CRC
-       Then starting CRC with default bundle fails
-       And stderr contains "Network not found: no network with matching name 'crc'"
-
-   @linux
-   Scenario: Clean-up
-       # Remove the config file
-       When removing file "crc.json" from CRC home folder succeeds
-       And executing "crc setup" succeeds
-       Then stderr should not contain "Skipping above check"
+    # --------------------------------------
+    # Linux-specific Scenarios
+
+    @linux
+    Scenario: Check network setup and destroy it, then check again
+        When removing file "crc.json" from CRC home folder succeeds
+        And executing "crc setup" succeeds
+        And executing "sudo virsh net-list --name" succeeds
+        Then stdout contains "crc"
+        When executing "sudo virsh net-undefine crc && sudo virsh net-destroy crc" succeeds
+        And executing "sudo virsh net-list --name" succeeds
+        Then stdout should not contain "crc"
+
+    @linux
+    Scenario: Running `crc setup` with checks enabled restores destroyed network
+        When executing "crc config set skip-check-crc-network false" succeeds
+        And executing "crc config set skip-check-crc-network-active false" succeeds
+        Then executing "crc setup" succeeds
+        And executing "sudo virsh net-list --name" succeeds
+        And stdout contains "crc"
+
+    @linux
+    Scenario: Running `crc start` without `crc setup` and with checks disabled fails when network destroyed
+        # Destroy network again
+        When executing "sudo virsh net-undefine crc && sudo virsh net-destroy crc" succeeds
+        And executing "sudo virsh net-list --name" succeeds
+        Then stdout should not contain "crc"
+        # Disable checks
+        When executing "crc config set skip-check-crc-network true" succeeds
+        And executing "crc config set skip-check-crc-network-active true" succeeds
+        # Start CRC
+        Then starting CRC with default bundle fails
+        And stderr contains "Network not found: no network with matching name 'crc'"
+
+    @linux
+    Scenario: Clean-up
+        # Remove the config file
+        When removing file "crc.json" from CRC home folder succeeds
+        And executing "crc setup" succeeds
+        Then stderr should not contain "Skipping above check"
        
diff --git a/test/integration/features/proxy.feature b/test/integration/features/proxy.feature
index 12c8733f2..39c022fc8 100644
--- a/test/integration/features/proxy.feature
+++ b/test/integration/features/proxy.feature
@@ -1,33 +1,31 @@
 @proxy @linux
-Feature: Test the proxy
+Feature: Behind proxy test
 
-  The user tries to use crc behind proxy. he/she expects the
-  crc start happen successfully and able to deploy the app and check its
-  accessibility.
+    User starts CRC behind a proxy. They expect a successful start 
+    and to be able to deploy an app and check its accessibility.
 
-  Scenario: Setup the proxy container using podman
-    Given executing "sudo podman run --name squid -d -p 3128:3128 quay.io/crcont/squid" succeeds
+    Scenario: Setup the proxy container using podman
+        Given executing "sudo podman run --name squid -d -p 3128:3128 quay.io/crcont/squid" succeeds
 
-  Scenario: Start CRC
-    Given executing "crc setup" succeeds
-    And  executing "crc config set http-proxy http://192.168.130.1:3128" succeeds
-    Then executing "crc config set https-proxy http://192.168.130.1:3128" succeeds
-    When starting CRC with default bundle succeeds
-    Then stdout should contain "Started the OpenShift cluster"
-    And executing "eval $(crc oc-env)" succeeds
-    When with up to "4" retries with wait period of "2m" command "crc status --log-level debug" output matches ".*Running \(v\d+\.\d+\.\d+.*\).*"
-    Then login to the oc cluster succeeds
+    Scenario: Start CRC
+        Given executing "crc setup" succeeds
+        And  executing "crc config set http-proxy http://192.168.130.1:3128" succeeds
+        Then executing "crc config set https-proxy http://192.168.130.1:3128" succeeds
+        When starting CRC with default bundle succeeds
+        Then stdout should contain "Started the OpenShift cluster"
+        And executing "eval $(crc oc-env)" succeeds
+        When checking that CRC is running
+        Then login to the oc cluster succeeds
 
-  Scenario: Remove the proxy container and host proxy env (which set because of oc-env)
-    Given executing "sudo podman stop squid" succeeds
-    And executing "sudo podman rm squid" succeeds
-    And executing "unset HTTP_PROXY HTTPS_PROXY NO_PROXY" succeeds
+    Scenario: Remove the proxy container and host proxy env (which set because of oc-env)
+        Given executing "sudo podman stop squid" succeeds
+        And executing "sudo podman rm squid" succeeds
+        And executing "unset HTTP_PROXY HTTPS_PROXY NO_PROXY" succeeds
 
-
-  Scenario: CRC delete and remove proxy settings from config
-    When executing "crc delete -f" succeeds
-    Then stdout should contain "Deleted the OpenShift cluster"
-    And  executing "crc config unset http-proxy" succeeds
-    And executing "crc config unset https-proxy" succeeds
-    And executing "crc cleanup" succeeds
+    Scenario: CRC delete and remove proxy settings from config
+        When executing "crc delete -f" succeeds
+        Then stdout should contain "Deleted the OpenShift cluster"
+        And  executing "crc config unset http-proxy" succeeds
+        And executing "crc config unset https-proxy" succeeds
+        And executing "crc cleanup" succeeds
 
diff --git a/test/integration/features/story_health.feature b/test/integration/features/story_health.feature
index 8ebb22673..d4c1c21de 100644
--- a/test/integration/features/story_health.feature
+++ b/test/integration/features/story_health.feature
@@ -1,8 +1,10 @@
 @story_health
-Feature: 
-    End-to-end health check. Set-up and start CRC. Then create a
-    project and deploy an app. Check on the app and delete the
-    project. Stop and delete CRC.
+Feature: End-to-end health check
+
+    User goes through a "day in the life of" CRC. They set up
+    and start CRC. They then create a project and deploy an app.
+    They check on the app and delete the project. They stop CRC
+    and delete the CRC VM.
 
     @linux @darwin
     Scenario: Start CRC
@@ -11,7 +13,7 @@ Feature:
         When starting CRC with default bundle succeeds
         Then stdout should contain "Started the OpenShift cluster"
         And executing "eval $(crc oc-env)" succeeds
-        When with up to "4" retries with wait period of "2m" command "crc status --log-level debug" output matches ".*Running \(v\d+\.\d+\.\d+.*\).*"
+        When checking that CRC is running
         Then login to the oc cluster succeeds
 
     @windows
@@ -21,15 +23,13 @@ Feature:
         When starting CRC with default bundle and nameserver "10.75.5.25" succeeds
         Then stdout should contain "Started the OpenShift cluster"
         And executing "crc oc-env | Invoke-Expression" succeeds
-        When with up to "4" retries with wait period of "2m" command "crc status --log-level debug" output matches ".*Running \(v\d+\.\d+\.\d+.*\).*"
+        When checking that CRC is running
         Then login to the oc cluster succeeds
 
-    @linux @darwin @windows    
+    @linux @darwin @windows
     Scenario: Check cluster health
-        Given executing "crc status" succeeds
-        And stdout should match ".*Running \(v\d+\.\d+\.\d+.*\).*"
         When executing "oc get nodes"
-        Then stdout contains "Ready" 
+        Then stdout contains "Ready"
         And stdout does not contain "Not ready"
         # next line checks similar things as `crc status` except gives more informative output
         And with up to "5" retries with wait period of "1m" all cluster operators are running
@@ -58,11 +58,10 @@ Feature:
     Scenario: Stop and start CRC, then check app still runs
         Given with up to "2" retries with wait period of "60s" http response from "http://httpd-example-testproj.apps-crc.testing" has status code "200"
         When executing "crc stop -f" succeeds
-        Then with up to "4" retries with wait period of "2m" command "crc status" output should contain "Stopped"
+        Then checking that CRC is stopped
         When starting CRC with default bundle succeeds
-        Then with up to "4" retries with wait period of "2m" command "crc status" output should match ".*Running \(v\d+\.\d+\.\d+.*\).*"
-        And with up to "2" retries with wait period of "60s" http response from "http://httpd-example-testproj.apps-crc.testing" has status code "200"
-
+        Then checking that CRC is running
+        And with up to "2" retries with wait period of "1m" http response from "http://httpd-example-testproj.apps-crc.testing" has status code "200"
 
     @darwin @linux @windows
     Scenario: Switch off CRC
diff --git a/test/integration/features/story_marketplace.feature b/test/integration/features/story_marketplace.feature
index 2e757f2b4..c7eb28b96 100644
--- a/test/integration/features/story_marketplace.feature
+++ b/test/integration/features/story_marketplace.feature
@@ -1,14 +1,15 @@
 @story_marketplace
-Feature: 
-    Install OpenShift operator from OperatorHub and use it to manage
-    admin tasks.
+Feature: Operator from marketplace
+
+    User installs an OpenShift operator from OperatorHub and uses
+    it to manage admin tasks.
 
     @linux @darwin
     Scenario: Start CRC and login to cluster
         Given executing "crc setup" succeeds
         When starting CRC with default bundle succeeds
         Then stdout should contain "Started the OpenShift cluster"
-        When with up to "8" retries with wait period of "2m" command "crc status" output matches ".*Running \(v\d+\.\d+\.\d+.*\).*"
+        When checking that CRC is running
         Then executing "eval $(crc oc-env)" succeeds
         And login to the oc cluster succeeds
 
@@ -18,7 +19,7 @@ Feature:
         When starting CRC with default bundle and nameserver "10.75.5.25" succeeds
         Then stdout should contain "Started the OpenShift cluster"
         And executing "crc oc-env | Invoke-Expression" succeeds
-        When with up to "4" retries with wait period of "2m" command "crc status --log-level debug" output matches ".*Running \(v\d+\.\d+\.\d+.*\).*"
+        When checking that CRC is running
         Then login to the oc cluster succeeds
 
     @darwin @linux @windows
diff --git a/test/integration/features/story_registry.feature b/test/integration/features/story_registry.feature
index 1cee404af..83d589722 100644
--- a/test/integration/features/story_registry.feature
+++ b/test/integration/features/story_registry.feature
@@ -1,16 +1,15 @@
 @story_registry @linux
-Feature: Local image to image-registry to deployment
+Feature: Local image to image-registry
 
-    The user creates a local container image with an app. They then
-    push it to the openshift image-registry in their
-    project/namespace. They deploy and expose the app and check its
-    accessibility.
+    User creates a local container image with an app inside it. They then
+    push it to the OpenShift image-registry in their project/namespace.
+    They deploy and expose the app and check its accessibility.
 
     Scenario: Start CRC and login to cluster
         Given executing "crc setup" succeeds
         When starting CRC with default bundle succeeds
         Then stdout should contain "Started the OpenShift cluster"
-        When with up to "8" retries with wait period of "2m" command "crc status" output matches ".*Running \(v\d+\.\d+\.\d+.*\).*"
+        When checking that CRC is running
         Then executing "eval $(crc oc-env)" succeeds
         And login to the oc cluster succeeds