Repository: code-ready/crc
Pull request #2523 (2021-07-05 10:36:32)

Issue 2468 Add forceStop flag for bundle generate command
Recently with 4.8 testing one of the blocking issue we found is vm taking longer time to shutdown gracefully
(~7-8 mins) and a bug is filled against it https://bugzilla.redhat.com/show_bug.cgi?id=1965992.
As part of bundle creation we first stop the cluster and that run client.Stop functionality
which waits for 2 min before error out and currently if there is any error we just error out
from the bundle generate command. This patch adds a `forceStop` flag for `bundle generate` command
to make sure if user uses it then force stop the VM otherwise error out.

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

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

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":[]} -->

Patch:
diff --git a/Makefile b/Makefile
index 5b9b4e01f..5b13a99da 100644
--- a/Makefile
+++ b/Makefile
@@ -2,7 +2,7 @@ SHELL := /bin/bash
 
 BUNDLE_VERSION ?= 4.7.18
 BUNDLE_EXTENSION = crcbundle
-CRC_VERSION = 1.29.0
+CRC_VERSION = 1.29.1
 COMMIT_SHA=$(shell git rev-parse --short HEAD)
 MACOS_INSTALL_PATH = /Applications/CodeReady Containers.app/Contents/Resources/
 CONTAINER_RUNTIME ?= podman
diff --git a/cmd/crc/cmd/bundle/generate.go b/cmd/crc/cmd/bundle/generate.go
index c1cd18c81..519101184 100644
--- a/cmd/crc/cmd/bundle/generate.go
+++ b/cmd/crc/cmd/bundle/generate.go
@@ -9,20 +9,23 @@ import (
 )
 
 func getGenerateCmd(config *config.Config) *cobra.Command {
-	return &cobra.Command{
+	var forceStop bool
+	generateCmd := &cobra.Command{
 		Use:   "generate",
 		Short: "Generate a custom bundle from the running OpenShift cluster",
 		Long:  "Generate a custom bundle from the running OpenShift cluster",
 		RunE: func(cmd *cobra.Command, args []string) error {
-			return runGenerate(config)
+			return runGenerate(config, forceStop)
 		},
 	}
+	generateCmd.PersistentFlags().BoolVarP(&forceStop, "forceStop", "f", false, "Forcefully stop the instance")
+	return generateCmd
 }
 
-func runGenerate(config *config.Config) error {
+func runGenerate(config *config.Config, forceStop bool) error {
 	client := machine.NewClient(constants.DefaultName, isDebugLog(), config)
 
-	return client.GenerateBundle()
+	return client.GenerateBundle(forceStop)
 }
 
 func isDebugLog() bool {
diff --git a/go.mod b/go.mod
index 5152a74ce..69b436191 100644
--- a/go.mod
+++ b/go.mod
@@ -48,7 +48,7 @@ require (
 	github.com/pelletier/go-toml v1.9.1 // indirect
 	github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4
 	github.com/pkg/errors v0.9.1
-	github.com/segmentio/analytics-go v3.2.0+incompatible
+	github.com/segmentio/analytics-go v1.2.1-0.20201110202747-0566e489c7b9
 	github.com/sirupsen/logrus v1.8.1
 	github.com/spf13/afero v1.5.1 // indirect
 	github.com/spf13/cast v1.3.1
diff --git a/go.sum b/go.sum
index ece6adff9..5a88e5b71 100644
--- a/go.sum
+++ b/go.sum
@@ -813,8 +813,8 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb
 github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
 github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
-github.com/segmentio/analytics-go v3.2.0+incompatible h1:OgwqVXxWzan4yR/3vZHBajaHB/W1rpHqF1IYe2YAGk8=
-github.com/segmentio/analytics-go v3.2.0+incompatible/go.mod h1:hce3YxWWeelowcvE47nVz5ECMFf/ADjtbFzGi0zsgug=
+github.com/segmentio/analytics-go v1.2.1-0.20201110202747-0566e489c7b9 h1:UjvQddFFXEVncfsF8qzBmCyPDHKlGDw3UW1SnlSJXkQ=
+github.com/segmentio/analytics-go v1.2.1-0.20201110202747-0566e489c7b9/go.mod h1:hce3YxWWeelowcvE47nVz5ECMFf/ADjtbFzGi0zsgug=
 github.com/segmentio/backo-go v0.0.0-20200129164019-23eae7c10bd3 h1:ZuhckGJ10ulaKkdvJtiAqsLTiPrLaXSdnVgXJKJkTxE=
 github.com/segmentio/backo-go v0.0.0-20200129164019-23eae7c10bd3/go.mod h1:9/Rh6yILuLysoQnZ2oNooD2g7aBnvM7r/fNVxRNWfBc=
 github.com/segmentio/conf v1.2.0/go.mod h1:Y3B9O/PqqWqjyxyWWseyj/quPEtMu1zDp/kVbSWWaB0=
diff --git a/packaging/windows/product.wxs.in b/packaging/windows/product.wxs.in
index 286abb745..fab1a86c7 100755
--- a/packaging/windows/product.wxs.in
+++ b/packaging/windows/product.wxs.in
@@ -116,7 +116,7 @@
         <CustomAction Id="RemoveCrcGroupRollback" BinaryKey="WixCA" DllEntry="WixQuietExec" Execute="rollback" Impersonate="no" Return="ignore" />
 
         <SetProperty Action="CAInstallHyperv" Id="InstallHyperv" Value='"[System64Folder]dism.exe" /online /enable-feature /featureName:microsoft-hyper-v-all /NoRestart /quiet' Before="InstallHyperv" Sequence="execute"/>
-        <CustomAction Id="InstallHyperv" BinaryKey="WixCA" DllEntry="WixQuietExec64" Execute="deferred" Impersonate="no" />
+        <CustomAction Id="InstallHyperv" BinaryKey="WixCA" DllEntry="WixQuietExec64" Execute="deferred" Impersonate="no" Return="ignore" />
 
         <util:CloseApplication Id = "TrayRunning" Description="Please exit CodeReady Containers from tray and run the installation again."
             Target="crc-tray.exe" RebootPrompt="no"
diff --git a/pkg/crc/cluster/cluster.go b/pkg/crc/cluster/cluster.go
index b0aa553b3..f7483cd59 100644
--- a/pkg/crc/cluster/cluster.go
+++ b/pkg/crc/cluster/cluster.go
@@ -163,7 +163,7 @@ func EnsureGeneratedClientCAPresentInTheCluster(ocConfig oc.Config, sshRunner *s
 		return nil
 	}
 
-	logging.Info("Updating root CA cert to admine-kubeconfig-client-ca configmap...")
+	logging.Info("Updating root CA cert to admin-kubeconfig-client-ca configmap...")
 	jsonPath := fmt.Sprintf(`'{"data": {"ca-bundle.crt": %q}}'`, selfSignedCAPem)
 	cmdArgs := []string{"patch", "configmap", "admin-kubeconfig-client-ca",
 		"-n", "openshift-config", "--patch", jsonPath}
diff --git a/pkg/crc/machine/client.go b/pkg/crc/machine/client.go
index a3983a8d4..8a3b2fdc5 100644
--- a/pkg/crc/machine/client.go
+++ b/pkg/crc/machine/client.go
@@ -23,7 +23,7 @@ type Client interface {
 	Status() (*types.ClusterStatusResult, error)
 	Stop() (state.State, error)
 	IsRunning() (bool, error)
-	GenerateBundle() error
+	GenerateBundle(forceStop bool) error
 }
 
 type client struct {
diff --git a/pkg/crc/machine/fakemachine/client.go b/pkg/crc/machine/fakemachine/client.go
index d74323b60..ac0fc2a67 100644
--- a/pkg/crc/machine/fakemachine/client.go
+++ b/pkg/crc/machine/fakemachine/client.go
@@ -68,7 +68,7 @@ func (c *Client) PowerOff() error {
 	return nil
 }
 
-func (c *Client) GenerateBundle() error {
+func (c *Client) GenerateBundle(forceStop bool) error {
 	return nil
 }
 
diff --git a/pkg/crc/machine/generate_bundle.go b/pkg/crc/machine/generate_bundle.go
index ecb554b21..2c17946fe 100644
--- a/pkg/crc/machine/generate_bundle.go
+++ b/pkg/crc/machine/generate_bundle.go
@@ -16,7 +16,7 @@ import (
 	"github.com/pkg/errors"
 )
 
-func (client *client) GenerateBundle() error {
+func (client *client) GenerateBundle(forceStop bool) error {
 	bundleMetadata, sshRunner, err := loadVM(client)
 	if err != nil {
 		return err
@@ -31,6 +31,11 @@ func (client *client) GenerateBundle() error {
 	// Stop the cluster
 	currentState, err := client.Stop()
 	if err != nil {
+		if forceStop {
+			if err := client.PowerOff(); err != nil {
+				return err
+			}
+		}
 		return err
 	}
 	if currentState != state.Stopped {
diff --git a/pkg/crc/machine/sync.go b/pkg/crc/machine/sync.go
index e55ebbbeb..ddbf15994 100644
--- a/pkg/crc/machine/sync.go
+++ b/pkg/crc/machine/sync.go
@@ -183,6 +183,6 @@ func (s *Synchronized) IsRunning() (bool, error) {
 	return s.underlying.IsRunning()
 }
 
-func (s *Synchronized) GenerateBundle() error {
-	return s.underlying.GenerateBundle()
+func (s *Synchronized) GenerateBundle(forceStop bool) error {
+	return s.underlying.GenerateBundle(forceStop)
 }
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 41307f865..6cc09fa9d 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -298,7 +298,7 @@ github.com/pkg/errors
 github.com/pmezard/go-difflib/difflib
 # github.com/rivo/uniseg v0.2.0
 github.com/rivo/uniseg
-# github.com/segmentio/analytics-go v3.2.0+incompatible
+# github.com/segmentio/analytics-go v1.2.1-0.20201110202747-0566e489c7b9
 ## explicit
 github.com/segmentio/analytics-go
 # github.com/segmentio/backo-go v0.0.0-20200129164019-23eae7c10bd3


Test patch:
diff --git a/pkg/crc/machine/sync_test.go b/pkg/crc/machine/sync_test.go
index 461afeadd..b973f50bd 100644
--- a/pkg/crc/machine/sync_test.go
+++ b/pkg/crc/machine/sync_test.go
@@ -166,6 +166,6 @@ func (m *waitingMachine) Stop() (state.State, error) {
 	return state.Stopped, nil
 }
 
-func (m *waitingMachine) GenerateBundle() error {
+func (m *waitingMachine) GenerateBundle(forceStop bool) error {
 	return errors.New("not implemented")
 }
diff --git a/test/e2e/crcsuite/crcsuite.go b/test/e2e/crcsuite/crcsuite.go
index 27342ff86..72cd703d1 100644
--- a/test/e2e/crcsuite/crcsuite.go
+++ b/test/e2e/crcsuite/crcsuite.go
@@ -18,7 +18,7 @@ import (
 
 	clicumber "github.com/code-ready/clicumber/testsuite"
 	"github.com/code-ready/crc/test/e2e/crcsuite/ux"
-	"github.com/code-ready/crc/test/extended/crc/cmd"
+	crcCmd "github.com/code-ready/crc/test/extended/crc/cmd"
 	"github.com/code-ready/crc/test/extended/util"
 )
 
@@ -49,7 +49,7 @@ func FeatureContext(s *godog.Suite) {
 	s.Step(`^setting config property "(.*)" to value "(.*)" (succeeds|fails)$`,
 		SetConfigPropertyToValueSucceedsOrFails)
 	s.Step(`^unsetting config property "(.*)" (succeeds|fails)$`,
-		cmd.UnsetConfigPropertySucceedsOrFails)
+		crcCmd.UnsetConfigPropertySucceedsOrFails)
 	s.Step(`^login to the oc cluster (succeeds|fails)$`,
 		LoginToOcClusterSucceedsOrFails)
 	s.Step(`^setting kubeconfig context to "(.*)" (succeeds|fails)$`,
@@ -62,6 +62,10 @@ func FeatureContext(s *godog.Suite) {
 		CheckCRCStatus)
 	s.Step(`^(stdout|stderr) (?:should contain|contains) "(.*)" if bundle (is|is not) embedded$`,
 		CommandReturnShouldContainIfBundleEmbeddedOrNot)
+	s.Step(`^execute crc (.*) command$`,
+		ExecuteCommand)
+	s.Step(`^execute crc (.*) command (.*)$`,
+		ExecuteCommandWithExpectedExitStatus)
 
 	// CRC file operations
 	s.Step(`^file "([^"]*)" exists in CRC home folder$`,
@@ -159,7 +163,7 @@ func FeatureContext(s *godog.Suite) {
 	})
 
 	s.AfterSuite(func() {
-		err := cmd.DeleteCRC()
+		err := crcCmd.DeleteCRC()
 		if err != nil {
 			fmt.Printf("Could not delete CRC VM: %s.", err)
 		}
@@ -180,7 +184,7 @@ func ParseFlags() {
 }
 
 func WaitForClusterInState(state string) error {
-	return cmd.WaitForClusterInState(state)
+	return crcCmd.WaitForClusterInState(state)
 }
 
 func RemoveCRCHome() error {
@@ -246,7 +250,7 @@ func CheckOutputMatchWithRetry(retryCount int, retryTime string, command string,
 // CheckCRCStatus checks that output of status command
 // matches given regex
 func CheckCRCStatus(state string) error {
-	return cmd.CheckCRCStatus(state)
+	return crcCmd.CheckCRCStatus(state)
 }
 
 func DeleteFileFromCRCHome(fileName string) error {
@@ -345,7 +349,8 @@ func StartCRCWithDefaultBundleSucceedsOrFails(expected string) error {
 	if !bundleEmbedded {
 		extraBundleArgs = fmt.Sprintf("-b %s", bundleLocation)
 	}
-	cmd = fmt.Sprintf("CRC_DISABLE_UPDATE_CHECK=true crc start -p '%s' %s --log-level debug", pullSecretFile, extraBundleArgs)
+	crcStart := crcCmd.CRC("start").WithDisableUpdateCheck().ToString()
+	cmd = fmt.Sprintf("%s -p '%s' %s --log-level debug", crcStart, pullSecretFile, extraBundleArgs)
 	err := clicumber.ExecuteCommandSucceedsOrFails(cmd, expected)
 
 	return err
@@ -359,14 +364,16 @@ func StartCRCWithDefaultBundleWithStopNetworkTimeSynchronizationSucceedsOrFails(
 	if !bundleEmbedded {
 		extraBundleArgs = fmt.Sprintf("-b %s", bundleLocation)
 	}
-	cmd = fmt.Sprintf("CRC_DISABLE_UPDATE_CHECK=true CRC_DEBUG_ENABLE_STOP_NTP=true crc start -p '%s' %s --log-level debug", pullSecretFile, extraBundleArgs)
+	crcStart := crcCmd.CRC("start").WithDisableUpdateCheck().WithdisableNTP().ToString()
+	cmd = fmt.Sprintf("%s -p '%s' %s --log-level debug", crcStart, pullSecretFile, extraBundleArgs)
 	err := clicumber.ExecuteCommandSucceedsOrFails(cmd, expected)
 
 	return err
 }
 
 func StartCRCWithCustomBundleSucceedsOrFails(expected string) error {
-	cmd := fmt.Sprintf("CRC_DISABLE_UPDATE_CHECK=true crc start -p '%s' -b *.crcbundle --log-level debug", pullSecretFile)
+	crcStart := crcCmd.CRC("start").WithDisableUpdateCheck().ToString()
+	cmd := fmt.Sprintf("%s -p '%s' -b *.crcbundle --log-level debug", crcStart, pullSecretFile)
 	return clicumber.ExecuteCommandSucceedsOrFails(cmd, expected)
 }
 
@@ -377,7 +384,8 @@ func StartCRCWithDefaultBundleAndNameServerSucceedsOrFails(nameserver string, ex
 		extraBundleArgs = fmt.Sprintf("-b %s", bundleLocation)
 	}
 
-	cmd := fmt.Sprintf("CRC_DISABLE_UPDATE_CHECK=true crc start -n %s -p '%s' %s --log-level debug", nameserver, pullSecretFile, extraBundleArgs)
+	crcStart := crcCmd.CRC("start").WithDisableUpdateCheck().ToString()
+	cmd := fmt.Sprintf("%s -n %s -p '%s' %s --log-level debug", crcStart, nameserver, pullSecretFile, extraBundleArgs)
 	return clicumber.ExecuteCommandSucceedsOrFails(cmd, expected)
 }
 
@@ -403,5 +411,13 @@ func SetConfigPropertyToValueSucceedsOrFails(property string, value string, expe
 			value = bundleLocation
 		}
 	}
-	return cmd.SetConfigPropertyToValueSucceedsOrFails(property, value, expected)
+	return crcCmd.SetConfigPropertyToValueSucceedsOrFails(property, value, expected)
+}
+
+func ExecuteCommand(command string) error {
+	return crcCmd.CRC(command).WithDisableUpdateCheck().Execute()
+}
+
+func ExecuteCommandWithExpectedExitStatus(command string, expectedExitStatus string) error {
+	return crcCmd.CRC(command).WithDisableUpdateCheck().ExecuteWithExpectedExit(expectedExitStatus)
 }
diff --git a/test/e2e/features/basic.feature b/test/e2e/features/basic.feature
index 803b4abe6..20e8f6272 100644
--- a/test/e2e/features/basic.feature
+++ b/test/e2e/features/basic.feature
@@ -6,13 +6,12 @@ Feature: Basic test
 
     @darwin @linux @windows
     Scenario: CRC version
-        When executing "CRC_DISABLE_UPDATE_CHECK=true crc version" succeeds
-        Then stderr should be empty
-        And stdout should contain "version:"
+        When execute crc version command
+        Then stdout should contain "version:"
 
     @darwin @linux @windows
     Scenario: CRC help
-        When executing "crc --help" succeeds
+        When execute crc help command succeeds
         Then stdout should contain "Usage:"
         And stdout should contain "Available Commands:"
         And stdout should contain "help"
@@ -30,7 +29,7 @@ Feature: Basic test
 
     @darwin @linux @windows
     Scenario: CRC status
-        When executing "crc status" fails
+        When execute crc status command fails
         Then stderr should contain
             """
             Machine does not exist. Use 'crc start' to create it
@@ -39,8 +38,8 @@ Feature: Basic test
     @linux
     Scenario: CRC setup on Linux
         When executing "crc setup --check-only" fails
-        Then executing "crc start" fails
-        And executing "crc setup" succeeds
+        Then execute crc start command fails
+        And execute crc setup command succeeds
         And stderr should contain "Checking if CRC bundle is extracted in '$HOME/.crc'"
         And stderr should contain "Checking if running as non-root"
         And stderr should contain "Checking if Virtualization is enabled"
@@ -69,7 +68,7 @@ Feature: Basic test
     @darwin
     Scenario: CRC setup on Mac
         When executing "crc setup --check-only" fails
-        And executing "crc setup" succeeds
+        And execute crc setup command succeeds
         And stderr should contain "Checking if running as non-root"
         And stderr should contain "Checking if HyperKit is installed"
         And stderr should contain "Checking if crc-driver-hyperkit is installed"
@@ -80,7 +79,7 @@ Feature: Basic test
 
     @windows
     Scenario: CRC setup on Windows
-        When executing "crc setup" succeeds
+        When execute crc setup command succeeds
         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"
@@ -110,7 +109,7 @@ Feature: Basic test
 
     @darwin @linux @windows
     Scenario: CRC IP check
-        When executing "crc ip" succeeds
+        When execute crc ip command succeeds
         Then stdout should match "\d+\.\d+\.\d+\.\d+"
 
     @darwin @linux @windows
@@ -164,7 +163,7 @@ Feature: Basic test
 
     @darwin @linux @windows
     Scenario: CRC console check
-        When executing "crc console"
+        When execute crc console command
         Then stderr should contain "The OpenShift cluster is not running, cannot open the OpenShift Web Console"
 
     @darwin @linux @windows
@@ -179,7 +178,7 @@ Feature: Basic test
 
     @darwin
     Scenario Outline: CRC clean-up
-        When executing "crc cleanup" succeeds
+        When execute crc cleanup command succeeds
         Then stderr should contain "Removing /etc/resolver/testing file"
         And stderr should contain "Unload CodeReady Containers daemon"
         And stderr should contain "Removing pull secret from the keyring"
@@ -188,7 +187,7 @@ Feature: Basic test
 
     @linux
     Scenario Outline: CRC clean-up
-        When executing "crc cleanup" succeeds
+        When execute crc cleanup command succeeds
         Then stderr should contain "Removing the crc VM if exists"
         And stderr should contain "Removing 'crc' network from libvirt"
         And stderr should contain "Using root access: Executing systemctl daemon-reload command"
@@ -200,7 +199,7 @@ Feature: Basic test
 
     @windows
     Scenario Outline: CRC clean-up
-        When executing "crc cleanup" succeeds
+        When execute crc cleanup command succeeds
         Then stderr should contain "Uninstalling tray if installed"
         Then stderr should contain "Uninstalling daemon if installed"
         And stderr should contain "Removing the crc VM if exists"
diff --git a/test/e2e/features/cert_rotation.feature b/test/e2e/features/cert_rotation.feature
index 9ea651c40..f12d8e808 100644
--- a/test/e2e/features/cert_rotation.feature
+++ b/test/e2e/features/cert_rotation.feature
@@ -6,7 +6,7 @@ Feature: Certificate rotation test
     an app and check its accessibility.
 
     Scenario: Setup CRC
-        Given executing "crc setup" succeeds
+        Given execute crc setup command succeeds
 
     Scenario: Set clock to 3 months ahead on the host
         Given executing "sudo timedatectl set-ntp off" succeeds
@@ -27,4 +27,4 @@ Feature: Certificate rotation test
     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 execute crc cleanup command succeeds
diff --git a/test/e2e/features/config.feature b/test/e2e/features/config.feature
index 42354a988..63885e5de 100644
--- a/test/e2e/features/config.feature
+++ b/test/e2e/features/config.feature
@@ -41,7 +41,7 @@ Feature: Test configuration settings
 
     @linux @darwin @windows
     Scenario: CRC config checks (bundle version)
-        Given executing "crc setup" succeeds
+        Given execute crc setup command succeeds
         When setting config property "bundle" to value "current bundle" succeeds
         And "JSON" config file "crc.json" in CRC home folder contains key "bundle" with value matching "current bundle"
         And setting config property "bundle" to value "/path/to/nonexistent/bundle/crc_hypervisor_version.tar.xz" fails
@@ -112,7 +112,7 @@ Feature: Test configuration settings
     @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 execute crc setup command 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
@@ -123,7 +123,7 @@ Feature: Test configuration settings
     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
+        Then execute crc setup command succeeds
         And executing "sudo virsh net-list --name" succeeds
         And stdout contains "crc"
 
@@ -144,6 +144,6 @@ Feature: Test configuration settings
     Scenario: Clean-up
         # Remove the config file
         When removing file "crc.json" from CRC home folder succeeds
-        And executing "crc setup" succeeds
+        And execute crc setup command succeeds
         Then stderr should not contain "Skipping above check"
        
diff --git a/test/e2e/features/proxy.feature b/test/e2e/features/proxy.feature
index 0e7c36c7f..b37a417ed 100644
--- a/test/e2e/features/proxy.feature
+++ b/test/e2e/features/proxy.feature
@@ -8,7 +8,7 @@ Feature: Behind proxy test
         Given executing "podman run --name squid -d -p 3128:3128 quay.io/crcont/squid" succeeds
 
     Scenario: Start CRC
-        Given executing "crc setup" succeeds
+        Given execute crc setup command 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
@@ -27,5 +27,6 @@ Feature: Behind proxy test
         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
+        And execute crc cleanup command succeeds
+        
 
diff --git a/test/e2e/features/story_health.feature b/test/e2e/features/story_health.feature
index a0a259eff..b670fce1c 100644
--- a/test/e2e/features/story_health.feature
+++ b/test/e2e/features/story_health.feature
@@ -8,7 +8,7 @@ Feature: End-to-end health check
 
     @linux @darwin
     Scenario: Start CRC
-        Given executing "crc setup" succeeds
+        Given execute crc setup command succeeds
         When setting config property "memory" to value "12000" succeeds
         When starting CRC with default bundle succeeds
         Then stdout should contain "Started the OpenShift cluster"
@@ -18,7 +18,7 @@ Feature: End-to-end health check
 
     @windows
     Scenario: Start CRC on Windows
-        Given executing "crc setup" succeeds
+        Given execute crc setup command succeeds
         When setting config property "memory" to value "12000" succeeds
         When starting CRC with default bundle and nameserver "10.75.5.25" succeeds
         Then stdout should contain "Started the OpenShift cluster"
@@ -59,4 +59,4 @@ Feature: End-to-end health check
     Scenario: Switch off CRC
         When executing "oc delete project testproj" succeeds
         And executing "crc delete -f" succeeds
-        And executing "crc cleanup" succeeds
+        And execute crc cleanup command succeeds
diff --git a/test/e2e/features/story_marketplace.feature b/test/e2e/features/story_marketplace.feature
index b79416283..b91e57c46 100644
--- a/test/e2e/features/story_marketplace.feature
+++ b/test/e2e/features/story_marketplace.feature
@@ -6,7 +6,7 @@ Feature: Operator from marketplace
 
     @linux @darwin
     Scenario: Start CRC and login to cluster
-        Given executing "crc setup" succeeds
+        Given execute crc setup command succeeds
         When starting CRC with default bundle succeeds
         Then stdout should contain "Started the OpenShift cluster"
         When checking that CRC is running
@@ -15,7 +15,7 @@ Feature: Operator from marketplace
 
     @windows
     Scenario: Start CRC on Windows
-        Given executing "crc setup" succeeds
+        Given execute crc setup command succeeds
         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
@@ -24,18 +24,14 @@ Feature: Operator from marketplace
 
     @darwin @linux @windows @testdata
     Scenario: Install new operator
-        When executing "oc apply -f etcdop-sub.yaml" succeeds
+        When executing "oc apply -f redis-sub.yaml" succeeds
         # check if cluster operator is running
-        Then with up to "20" retries with wait period of "30s" command "oc get csv" output matches ".*etcdoperator\.(.*)Succeeded$"
+        Then with up to "20" retries with wait period of "30s" command "oc get csv" output matches ".*redis-operator\.(.*)Succeeded$"
         
     @darwin @linux @windows @testdata
-    Scenario: Scale up
-        # start cluster with 3 pods
-        When executing "oc apply -f etcd-cluster3.yaml" succeeds
-        Then with up to "10" retries with wait period of "30s" command "oc get pods" output matches "(?s)(.*example-[a-z0-9]* *1/1 *Running.*){3}"
-        # reconfigure cluster to 5 pods
-        When executing "oc apply -f etcd-cluster5.yaml" succeeds
-        Then with up to "10" retries with wait period of "30s" command "oc get pods" output matches "(?s)(.*example-[a-z0-9]* *1/1 *Running.*){5}"
+    Scenario: Install the redis instance
+        When executing "oc apply -f redis-cluster.yaml" succeeds
+        Then with up to "10" retries with wait period of "30s" command "oc get pods" output matches "redis-standalone-[a-z0-9]* .*Running.*"
     
     @darwin @linux
     Scenario: Failover
@@ -44,11 +40,8 @@ Feature: Operator from marketplace
         And executing "echo $POD" succeeds
         And executing "oc delete pod $POD --now" succeeds
         Then stdout should match "^pod(.*)deleted$"
-        # after a while 5 pods should be up & running again
-        And with up to "10" retries with wait period of "30s" command "oc get pods" output matches "(?s)(.*example-[a-z0-9]* *1/1 *Running.*){5}"
-        # but the deleted pod should not be up, it was replaced
-        But executing "oc get pods $POD" fails
-        And stderr matches "(.*)pods (.*) not found$"
+        # after a while 1 pods should be up & running again
+        And with up to "10" retries with wait period of "30s" command "oc get pods" output matches "redis-standalone-[a-z0-9]* .*Running.*"
 
     @windows
     Scenario: Failover
@@ -57,22 +50,12 @@ Feature: Operator from marketplace
         And executing "echo $Env:POD" succeeds
         And executing "oc delete pod $Env:POD --now" succeeds
         Then stdout should match "^pod(.*)deleted$"
-        # after a while 5 pods should be up & running again
-        And with up to "10" retries with wait period of "30s" command "oc get pods" output matches "(?s)(.*example-[a-z0-9]* *1/1 *Running.*){5}"
-        # but the deleted pod should not be up, it was replaced
-        But executing "oc get pods $Env:POD" fails
-        And stderr matches "(.*)pods (.*) not found$"
-
-    @darwin @linux @windows @testdata
-    Scenario: Scale down
-        # scale back down to 3 pods
-        When executing "oc apply -f etcd-cluster3.yaml" succeeds
-        Then with up to "10" retries with wait period of "30s" command "oc get pods" output matches "(?s)(.*example-[a-z0-9]* *1/1 *Running.*){3}"
-        But with up to "10" retries with wait period of "30s" command "oc get pods" output does not match "(?s)(.*example-[a-z0-9]* *1/1 *Running.*){4}"
+        # after a while 1 pods should be up & running again
+        And with up to "10" retries with wait period of "30s" command "oc get pods" output matches "redis-standalone-[a-z0-9]* .*Running.*"
         
     @darwin @linux @windows
     Scenario: Clean up
         When executing "crc delete -f" succeeds
         Then stdout should contain "Deleted the OpenShift cluster"
-        When executing "crc cleanup" succeeds
+        When execute crc cleanup command succeeds
         Then stdout should contain "Cleanup finished"
diff --git a/test/e2e/features/story_registry.feature b/test/e2e/features/story_registry.feature
index 421569363..b67954578 100644
--- a/test/e2e/features/story_registry.feature
+++ b/test/e2e/features/story_registry.feature
@@ -6,7 +6,7 @@ Feature: Local image to image-registry
     They deploy and expose the app and check its accessibility.
 
     Scenario: Start CRC and login to cluster
-        Given executing "crc setup" succeeds
+        Given execute crc setup command succeeds
         When starting CRC with default bundle succeeds
         Then stdout should contain "Started the OpenShift cluster"
         When checking that CRC is running
@@ -41,5 +41,5 @@ Feature: Local image to image-registry
         And executing "oc delete project testproj-img" succeeds
         And executing "crc delete -f" succeeds
         Then stdout should contain "Deleted the OpenShift cluster"
-        When executing "crc cleanup" succeeds
+        When execute crc cleanup command succeeds
         Then stdout should contain "Cleanup finished"
diff --git a/test/extended/crc/cmd/cmd.go b/test/extended/crc/cmd/cmd.go
index f87b6a2e5..e52d6c388 100644
--- a/test/extended/crc/cmd/cmd.go
+++ b/test/extended/crc/cmd/cmd.go
@@ -2,6 +2,8 @@ package cmd
 
 import (
 	"fmt"
+	"runtime"
+	"strings"
 	"time"
 
 	clicumber "github.com/code-ready/clicumber/testsuite"
@@ -15,6 +17,88 @@ const (
 	CRCExecutableNotInstalled = "notInstalled"
 )
 
+var (
+	commands = map[string]struct{}{
+		"help":    {},
+		"version": {},
+		"setup":   {},
+		"start":   {},
+		"stop":    {},
+		"delete":  {},
+		"status":  {},
+		"config":  {},
+		"ip":      {},
+		"console": {},
+		"cleanup": {}}
+)
+
+type Command struct {
+	command            string
+	disableUpdateCheck bool
+	disableNTP         bool
+}
+
+func CRC(command string) Command {
+	return Command{command: command}
+}
+
+func (c Command) WithDisableUpdateCheck() Command {
+	c.disableUpdateCheck = true
+	return c
+}
+
+func (c Command) WithdisableNTP() Command {
+	c.disableNTP = true
+	return c
+}
+
+func (c Command) ToString() string {
+	cmd := append(c.env(), "crc", c.command)
+	return strings.Join(cmd, " ")
+}
+
+func (c Command) ExecuteWithExpectedExit(expectedExit string) error {
+	if err := c.validate(); err != nil {
+		return err
+	}
+	if expectedExit == "succeeds" || expectedExit == "fails" {
+		return clicumber.ExecuteCommandSucceedsOrFails(c.ToString(), expectedExit)
+	}
+	return fmt.Errorf("%s is a valid expected exit status", expectedExit)
+}
+
+func (c Command) Execute() error {
+	if err := c.validate(); err != nil {
+		return err
+	}
+	return clicumber.ExecuteCommand(c.ToString())
+}
+
+func (c Command) env() []string {
+	var env []string
+	if c.disableUpdateCheck {
+		env = append(env, envVariable("CRC_DISABLE_UPDATE_CHECK", "true"))
+	}
+	if c.disableNTP {
+		env = append(env, envVariable("CRC_DEBUG_ENABLE_STOP_NTP", "true"))
+	}
+	return env
+}
+
+func envVariable(key, value string) string {
+	if runtime.GOOS == "windows" {
+		return fmt.Sprintf("$env:%s=%s", key, value)
+	}
+	return fmt.Sprintf("%s=%s", key, value)
+}
+
+func (c Command) validate() error {
+	if _, ok := commands[c.command]; !ok {
+		return fmt.Errorf("%s is not a supported command", c.command)
+	}
+	return nil
+}
+
 func SetConfigPropertyToValueSucceedsOrFails(property string, value string, expected string) error {
 	cmd := "crc config set " + property + " " + value
 	return clicumber.ExecuteCommandSucceedsOrFails(cmd, expected)
@@ -51,7 +135,7 @@ func CheckCRCStatus(state string) error {
 		expression = ".*OpenShift: .*Stopped.*"
 	}
 
-	err := clicumber.ExecuteCommand("crc status")
+	err := clicumber.ExecuteCommand(CRC("status").ToString())
 	if err != nil {
 		return err
 	}
@@ -72,7 +156,7 @@ func CheckCRCExecutableState(state string) error {
 
 func CheckMachineNotExists() error {
 	expression := `.*Machine does not exist.*`
-	err := clicumber.ExecuteCommand("crc status")
+	err := clicumber.ExecuteCommand(CRC("status").ToString())
 	if err != nil {
 		return err
 	}
@@ -81,8 +165,7 @@ func CheckMachineNotExists() error {
 
 func DeleteCRC() error {
 
-	command := "crc delete"
-	_ = clicumber.ExecuteCommand(command)
+	_ = clicumber.ExecuteCommand(CRC("delete").ToString())
 
 	fmt.Printf("Deleted CRC instance (if one existed).\n")
 	return nil
diff --git a/test/extended/crc/cmd/cmd_test.go b/test/extended/crc/cmd/cmd_test.go
new file mode 100644
index 000000000..3b5578db9
--- /dev/null
+++ b/test/extended/crc/cmd/cmd_test.go
@@ -0,0 +1,22 @@
+package cmd
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCommand(t *testing.T) {
+	assert.Equal(t, "crc setup", Command{
+		command: "setup",
+	}.ToString())
+	assert.Equal(t, "CRC_DEBUG_ENABLE_STOP_NTP=true crc start -b bundle", Command{
+		command:    "start -b bundle",
+		disableNTP: true,
+	}.ToString())
+	assert.Equal(t, "CRC_DISABLE_UPDATE_CHECK=true CRC_DEBUG_ENABLE_STOP_NTP=true crc start -b bundle", Command{
+		command:            "start -b bundle",
+		disableNTP:         true,
+		disableUpdateCheck: true,
+	}.ToString())
+}
diff --git a/test/testdata/etcd-cluster3.yaml b/test/testdata/etcd-cluster3.yaml
deleted file mode 100644
index 28ebb25a4..000000000
--- a/test/testdata/etcd-cluster3.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-apiVersion: etcd.database.coreos.com/v1beta2
-kind: EtcdCluster
-metadata:
-  name: example
-  annotations:
-    etcd.database.coreos.com/scope: clusterwide
-  namespace: default
-spec:
-  size: 3
-  version: 3.2.13
-  pod:
-    busyboxImage: quay.io/libpod/alpine:3.10.2
diff --git a/test/testdata/etcd-cluster5.yaml b/test/testdata/etcd-cluster5.yaml
deleted file mode 100644
index a40f69291..000000000
--- a/test/testdata/etcd-cluster5.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-apiVersion: etcd.database.coreos.com/v1beta2
-kind: EtcdCluster
-metadata:
-  name: example
-  annotations:
-    etcd.database.coreos.com/scope: clusterwide
-  namespace: default
-spec:
-  size: 5
-  version: 3.2.13
-  pod:
-    busyboxImage: quay.io/libpod/alpine:3.10.2
diff --git a/test/testdata/redis-cluster.yaml b/test/testdata/redis-cluster.yaml
new file mode 100644
index 000000000..fbb5e41e7
--- /dev/null
+++ b/test/testdata/redis-cluster.yaml
@@ -0,0 +1,49 @@
+---
+apiVersion: v1
+data:
+  password: c3VwZXJzZWNyZXQ=
+kind: Secret
+metadata:
+  name: redis-secret
+  namespace: default
+---
+apiVersion: redis.redis.opstreelabs.in/v1beta1
+kind: Redis
+metadata:
+  name: redis-standalone
+  namespace: default
+spec:
+  kubernetesConfig:
+    image: 'quay.io/opstree/redis:v6.2'
+    imagePullPolicy: IfNotPresent
+    redisSecret:
+      key: password
+      name: redis-secret
+    resources:
+      limits:
+        cpu: 101m
+        memory: 128Mi
+      requests:
+        cpu: 101m
+        memory: 128Mi
+    serviceType: LoadBalancer
+  storage:
+    volumeClaimTemplate:
+      spec:
+        accessModes:
+          - ReadWriteOnce
+        resources:
+          requests:
+            storage: 1Gi
+  redisExporter:
+    enabled: true
+    image: 'quay.io/opstree/redis-exporter:1.0'
+    imagePullPolicy: Always
+    resources:
+      limits:
+        cpu: 100m
+        memory: 128Mi
+      requests:
+        cpu: 100m
+        memory: 128Mi
+  redisConfig: {}
diff --git a/test/testdata/etcdop-sub.yaml b/test/testdata/redis-sub.yaml
similarity index 79%
rename from test/testdata/etcdop-sub.yaml
rename to test/testdata/redis-sub.yaml
index 8bdfd9845..dd82f6b88 100644
--- a/test/testdata/etcdop-sub.yaml
+++ b/test/testdata/redis-sub.yaml
@@ -1,11 +1,11 @@
 apiVersion: operators.coreos.com/v1alpha1
 kind: Subscription
 metadata:
-  name: etcd
+  name: redis
   namespace: openshift-operators
 spec:
-  channel: clusterwide-alpha
+  channel: stable
   installPlanApproval: Automatic
-  name: etcd
+  name: redis-operator
   source: community-operators
   sourceNamespace: openshift-marketplace