Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 42 additions & 13 deletions test/extended/imagepolicy/imagepolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,7 @@ func updateImageConfig(oc *exutil.CLI, allowedRegistries []string) {
return err
})
o.Expect(err).NotTo(o.HaveOccurred(), "error updating image config")
WaitForMCPConfigSpecChangeAndUpdated(oc, workerPool, initialWorkerSpec)
WaitForMCPConfigSpecChangeAndUpdated(oc, masterPool, initialMasterSpec)
WaitForMCPsConfigSpecChangeAndUpdated(oc, initialWorkerSpec, initialMasterSpec)
}

func cleanupImageConfig(oc *exutil.CLI) error {
Expand All @@ -238,8 +237,7 @@ func cleanupImageConfig(oc *exutil.CLI) error {
return err
})
o.Expect(err).NotTo(o.HaveOccurred(), "error cleaning up image config")
WaitForMCPConfigSpecChangeAndUpdated(oc, workerPool, initialWorkerSpec)
WaitForMCPConfigSpecChangeAndUpdated(oc, masterPool, initialMasterSpec)
WaitForMCPsConfigSpecChangeAndUpdated(oc, initialWorkerSpec, initialMasterSpec)
return nil
}

Expand Down Expand Up @@ -285,8 +283,7 @@ func createClusterImagePolicy(oc *exutil.CLI, policy configv1.ClusterImagePolicy
_, err := oc.AdminConfigClient().ConfigV1().ClusterImagePolicies().Create(context.TODO(), &policy, metav1.CreateOptions{})
o.Expect(err).NotTo(o.HaveOccurred())

WaitForMCPConfigSpecChangeAndUpdated(oc, workerPool, initialWorkerSpec)
WaitForMCPConfigSpecChangeAndUpdated(oc, masterPool, initialMasterSpec)
WaitForMCPsConfigSpecChangeAndUpdated(oc, initialWorkerSpec, initialMasterSpec)
}

func deleteClusterImagePolicy(oc *exutil.CLI, policyName string) error {
Expand All @@ -296,8 +293,7 @@ func deleteClusterImagePolicy(oc *exutil.CLI, policyName string) error {
if err := oc.AdminConfigClient().ConfigV1().ClusterImagePolicies().Delete(context.TODO(), policyName, metav1.DeleteOptions{}); err != nil && !errors.IsNotFound(err) {
return fmt.Errorf("failed to delete cluster image policy %s: %v", policyName, err)
}
WaitForMCPConfigSpecChangeAndUpdated(oc, workerPool, initialWorkerSpec)
WaitForMCPConfigSpecChangeAndUpdated(oc, masterPool, initialMasterSpec)
WaitForMCPsConfigSpecChangeAndUpdated(oc, initialWorkerSpec, initialMasterSpec)
return nil
}

Expand All @@ -312,8 +308,7 @@ func createImagePolicy(oc *exutil.CLI, policy configv1.ImagePolicy, namespace st

// Wait until each pool's Spec.Configuration.Name changes from the initial value
// and the pool reports Updated=true
WaitForMCPConfigSpecChangeAndUpdated(oc, workerPool, initialWorkerSpec)
WaitForMCPConfigSpecChangeAndUpdated(oc, masterPool, initialMasterSpec)
WaitForMCPsConfigSpecChangeAndUpdated(oc, initialWorkerSpec, initialMasterSpec)
}

func deleteImagePolicy(oc *exutil.CLI, policyName string, namespace string) error {
Expand All @@ -323,8 +318,7 @@ func deleteImagePolicy(oc *exutil.CLI, policyName string, namespace string) erro
if err := oc.AdminConfigClient().ConfigV1().ImagePolicies(namespace).Delete(context.TODO(), policyName, metav1.DeleteOptions{}); err != nil && !errors.IsNotFound(err) {
return fmt.Errorf("failed to delete image policy %s in namespace %s: %v", policyName, namespace, err)
}
WaitForMCPConfigSpecChangeAndUpdated(oc, workerPool, initialWorkerSpec)
WaitForMCPConfigSpecChangeAndUpdated(oc, masterPool, initialMasterSpec)
WaitForMCPsConfigSpecChangeAndUpdated(oc, initialWorkerSpec, initialMasterSpec)
return nil
}

Expand Down Expand Up @@ -707,7 +701,42 @@ func WaitForMCPConfigSpecChangeAndUpdated(oc *exutil.CLI, pool string, initialSp
return false
}
return machineconfighelper.IsMachineConfigPoolConditionTrue(mcp.Status.Conditions, mcfgv1.MachineConfigPoolUpdated)
}, 20*time.Minute, 10*time.Second).Should(o.BeTrue())
}, 15*time.Minute, 10*time.Second).Should(o.BeTrue())
}

func WaitForMCPsConfigSpecChangeAndUpdated(oc *exutil.CLI, workerInitialSpec, masterInitialSpec string) {
e2e.Logf("Waiting for worker and master pools to complete")
clientSet, err := machineconfigclient.NewForConfig(oc.KubeFramework().ClientConfig())
o.Expect(err).NotTo(o.HaveOccurred())

o.Eventually(func() bool {
workerMCP, err := clientSet.MachineconfigurationV1().MachineConfigPools().Get(context.TODO(), "worker", metav1.GetOptions{})
if err != nil {
return false
}
masterMCP, err := clientSet.MachineconfigurationV1().MachineConfigPools().Get(context.TODO(), "master", metav1.GetOptions{})
if err != nil {
return false
}

workerReady := workerMCP.Status.Configuration.Name != workerInitialSpec &&
workerMCP.Spec.Configuration.Name == workerMCP.Status.Configuration.Name &&
machineconfighelper.IsMachineConfigPoolConditionTrue(workerMCP.Status.Conditions, mcfgv1.MachineConfigPoolUpdated)

masterReady := masterMCP.Status.Configuration.Name != masterInitialSpec &&
masterMCP.Spec.Configuration.Name == masterMCP.Status.Configuration.Name &&
machineconfighelper.IsMachineConfigPoolConditionTrue(masterMCP.Status.Conditions, mcfgv1.MachineConfigPoolUpdated)

if !workerReady {
e2e.Logf("Worker MCP not ready yet")
}
if !masterReady {
e2e.Logf("Master MCP not ready yet")
}

return workerReady && masterReady
}, 15*time.Minute, 10*time.Second).Should(o.BeTrue())
e2e.Logf("Both worker and master pools completed successfully")
}

func isDisconnectedCluster(oc *exutil.CLI) bool {
Expand Down
9 changes: 3 additions & 6 deletions test/extended/node/criocredentialprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,7 @@ func updateCRIOCredentialProviderConfig(oc *exutil.CLI, matchImages []string, ex
return
}

imagepolicy.WaitForMCPConfigSpecChangeAndUpdated(oc, workerPool, initialWorkerSpec)
imagepolicy.WaitForMCPConfigSpecChangeAndUpdated(oc, masterPool, initialMasterSpec)
imagepolicy.WaitForMCPsConfigSpecChangeAndUpdated(oc, initialWorkerSpec, initialMasterSpec)
}

func getWorkerNodes(oc *exutil.CLI) ([]corev1.Node, error) {
Expand Down Expand Up @@ -289,8 +288,7 @@ func createIDMSResources(oc *exutil.CLI) {

e2e.Logf("Created ImageDigestMirrorSet %q", idms.Name)

imagepolicy.WaitForMCPConfigSpecChangeAndUpdated(oc, workerPool, initialWorkerSpec)
imagepolicy.WaitForMCPConfigSpecChangeAndUpdated(oc, masterPool, initialMasterSpec)
imagepolicy.WaitForMCPsConfigSpecChangeAndUpdated(oc, initialWorkerSpec, initialMasterSpec)
}

func cleanupIDMSResources(oc *exutil.CLI) {
Expand All @@ -302,8 +300,7 @@ func cleanupIDMSResources(oc *exutil.CLI) {

e2e.Logf("Deleted ImageDigestMirrorSet %q", "digest-mirror")

imagepolicy.WaitForMCPConfigSpecChangeAndUpdated(oc, workerPool, initialWorkerSpec)
imagepolicy.WaitForMCPConfigSpecChangeAndUpdated(oc, masterPool, initialMasterSpec)
imagepolicy.WaitForMCPsConfigSpecChangeAndUpdated(oc, initialWorkerSpec, initialMasterSpec)
}

func createNamespaceRBAC(f *e2e.Framework, namespace string) {
Expand Down
4 changes: 3 additions & 1 deletion test/extended/node/image_volume.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,15 @@ func describeImageVolumeTests(config imageVolumeTestConfig) bool {
podName = config.frameworkName + "-test"
)

g.BeforeEach(func() {
g.BeforeEach(func(ctx context.Context) {
// Microshift doesn't inherit OCP feature gates, and ImageVolume won't work either
isMicroshift, err := exutil.IsMicroShiftCluster(oc.AdminKubeClient())
o.Expect(err).NotTo(o.HaveOccurred())
if isMicroshift {
g.Skip("Not supported on Microshift")
}

EnsureNodesReady(ctx, oc)
})

g.It("should succeed with pod and pull policy of Always", func(ctx context.Context) {
Expand Down
10 changes: 5 additions & 5 deletions test/extended/node/kubelet_secret_pulled_images.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,8 @@ var _ = g.Describe("[sig-node][Suite:openshift/disruptive-longrunning][Disruptiv
credVerifyCreateSecret(ctx, oc, ns, "pull-secret", pullSecret)

g.DeferCleanup(func() {
_ = deleteKC(oc, kcName)
_ = waitForMCP(ctx, mcClient, "worker", 30*time.Minute)
cleanupCtx := context.Background()
_ = CleanupKubeletConfig(cleanupCtx, mcClient, kcName, "worker")
})
Comment on lines 207 to 210

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Don't swallow rollback failures in this ordered cleanup.

This DeferCleanup drops both the deleteKC and waitForMCP errors, so Case 4 can leave the worker pool mid-rollout and still pass cleanup. In an g.Ordered suite, that leaks mutated cluster state into later cases.

Suggested fix
 		g.DeferCleanup(func() {
-			_ = deleteKC(oc, kcName)
-			_ = waitForMCP(ctx, mcClient, "worker", 15*time.Minute)
+			if err := deleteKC(oc, kcName); err != nil {
+				framework.Failf("cleanup: failed to delete KubeletConfig %s: %v", kcName, err)
+			}
+			if err := waitForMCP(ctx, mcClient, "worker", 15*time.Minute); err != nil {
+				framework.Failf("cleanup: worker MCP did not recover after deleting %s: %v", kcName, err)
+			}
 		})

As per path instructions, **/*.go: Never ignore error returns.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
g.DeferCleanup(func() {
_ = deleteKC(oc, kcName)
_ = waitForMCP(ctx, mcClient, "worker", 30*time.Minute)
_ = waitForMCP(ctx, mcClient, "worker", 15*time.Minute)
})
g.DeferCleanup(func() {
if err := deleteKC(oc, kcName); err != nil {
framework.Failf("cleanup: failed to delete KubeletConfig %s: %v", kcName, err)
}
if err := waitForMCP(ctx, mcClient, "worker", 15*time.Minute); err != nil {
framework.Failf("cleanup: worker MCP did not recover after deleting %s: %v", kcName, err)
}
})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/extended/node/kubelet_secret_pulled_images.go` around lines 207 - 210,
The ordered cleanup in the kubelet secret pulled images test is swallowing
rollback failures because both deleteKC and waitForMCP errors are ignored.
Update the DeferCleanup registered in the Case 4 setup to handle and surface
errors from deleteKC and waitForMCP instead of discarding them, so the g.Ordered
suite does not continue with a partially rolled back worker pool. Use the
existing cleanup block near the Case 4 setup to keep the cluster state
consistent for later cases.

Source: Path instructions


g.By("Pre-caching private image on the node with a valid secret")
Expand All @@ -215,7 +215,7 @@ var _ = g.Describe("[sig-node][Suite:openshift/disruptive-longrunning][Disruptiv
g.By("Applying NeverVerify policy and waiting for MCO rollout")
credVerifyApplyPolicy(ctx, mcClient, kcName, `{"imagePullCredentialsVerificationPolicy":"NeverVerify"}`)
credVerifyWaitForMCPUpdating(ctx, mcClient, "worker")
err = waitForMCP(ctx, mcClient, "worker", 30*time.Minute)
err = WaitForMCP(ctx, mcClient, "worker", 15*time.Minute)
o.Expect(err).NotTo(o.HaveOccurred())

g.By("Verifying NeverVerify policy allows pod without secret to use cached image")
Expand All @@ -224,7 +224,7 @@ var _ = g.Describe("[sig-node][Suite:openshift/disruptive-longrunning][Disruptiv
g.By("Switching to AlwaysVerify policy and waiting for MCO rollout")
credVerifyApplyPolicy(ctx, mcClient, kcName, `{"imagePullCredentialsVerificationPolicy":"AlwaysVerify"}`)
credVerifyWaitForMCPUpdating(ctx, mcClient, "worker")
err = waitForMCP(ctx, mcClient, "worker", 30*time.Minute)
err = WaitForMCP(ctx, mcClient, "worker", 15*time.Minute)
o.Expect(err).NotTo(o.HaveOccurred())

// This pod also re-caches the image after MCO rollout since pull records are cleared
Expand Down Expand Up @@ -413,7 +413,7 @@ func credVerifyApplyPolicy(ctx context.Context, mcClient *mcclient.Clientset, na

existing, err := mcClient.MachineconfigurationV1().KubeletConfigs().Get(ctx, name, metav1.GetOptions{})
if apierrors.IsNotFound(err) {
_, err = mcClient.MachineconfigurationV1().KubeletConfigs().Create(ctx, kc, metav1.CreateOptions{})
_, err = CreateKubeletConfig(ctx, mcClient, kc)
o.Expect(err).NotTo(o.HaveOccurred())
return
}
Expand Down
110 changes: 41 additions & 69 deletions test/extended/node/kubeletconfig_features.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
o "github.com/onsi/gomega"

osconfigv1 "github.com/openshift/api/config/v1"
machineconfigclient "github.com/openshift/client-go/machineconfiguration/clientset/versioned"
exutil "github.com/openshift/origin/test/extended/util"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

Expand All @@ -32,96 +33,83 @@ var (
var _ = g.Describe("[Suite:openshift/disruptive-longrunning][sig-node][Disruptive]", func() {
defer g.GinkgoRecover()
var (
NodeMachineConfigPoolBaseDir = exutil.FixturePath("testdata", "node", "machineconfigpool")
NodeKubeletConfigBaseDir = exutil.FixturePath("testdata", "node", "kubeletconfig")

customMCPFixture = filepath.Join(NodeMachineConfigPoolBaseDir, "customMCP.yaml")
customLoggingKCFixture = filepath.Join(NodeKubeletConfigBaseDir, "loggingKC.yaml")
NodeKubeletConfigBaseDir = exutil.FixturePath("testdata", "node", "kubeletconfig")
customLoggingKCFixture = filepath.Join(NodeKubeletConfigBaseDir, "loggingKC.yaml")

oc = exutil.NewCLIWithoutNamespace("node-kubeletconfig")
)

// This test is also considered `Slow` because it takes longer than 5 minutes to run.
g.It("[Slow]should apply KubeletConfig with logging verbosity to custom pool [apigroup:machineconfiguration.openshift.io]", func() {
g.It("[Slow]should apply KubeletConfig with logging verbosity to custom pool [apigroup:machineconfiguration.openshift.io]", func(ctx context.Context) {
// Skip this test on single node and two-node platforms since custom MCPs are not supported
// for clusters with only a master MCP
skipOnSingleNodeTopology(oc)
skipOnTwoNodeTopology(oc)

// Get the MCP and KubeletConfig fixtures needed for this test
mcpFixture := customMCPFixture
// Get the KubeletConfig fixture needed for this test
kcFixture := customLoggingKCFixture

// Create kube client for test
kubeClient, err := kubernetes.NewForConfig(oc.KubeFramework().ClientConfig())
o.Expect(err).NotTo(o.HaveOccurred(), fmt.Sprintf("Error getting kube client: %v", err))

// Create custom MCP
defer deleteMCP(oc, "custom")
err = oc.Run("apply").Args("-f", mcpFixture).Execute()
o.Expect(err).NotTo(o.HaveOccurred(), fmt.Sprintf("Error creating MCP `custom`: %v", err))
// Get a worker node for testing
nodes, err := kubeClient.CoreV1().Nodes().List(ctx, metav1.ListOptions{LabelSelector: labels.SelectorFromSet(labels.Set{"node-role.kubernetes.io/worker": ""}).String()})
o.Expect(err).NotTo(o.HaveOccurred(), "Error getting worker nodes")
o.Expect(len(nodes.Items)).To(o.BeNumerically(">", 0), "No worker nodes found")
testNode := nodes.Items[0].Name

// Create machine config client
mcClient, err := machineconfigclient.NewForConfig(oc.KubeFramework().ClientConfig())
o.Expect(err).NotTo(o.HaveOccurred(), "Error creating machine config client")

// Create custom MCP for the node
mcpConfig, err := CreateCustomMCPForNode(ctx, oc, mcClient, "custom", testNode)
o.Expect(err).NotTo(o.HaveOccurred(), "Error creating custom MCP")

// Add node to custom MCP & wait for the node to be ready in the MCP
optedNodes, err := addWorkerNodesToCustomPool(oc, kubeClient, 1, "custom")
o.Expect(err).NotTo(o.HaveOccurred(), fmt.Sprintf("Error adding node to `custom` MCP: %v", err))
defer waitTillNodeReadyWithConfig(kubeClient, optedNodes[0], workerConfigPrefix)
defer unlabelNode(oc, optedNodes[0])
framework.Logf("Waiting for `%v` node to be ready in `custom` MCP.", optedNodes[0])
waitTillNodeReadyWithConfig(kubeClient, optedNodes[0], customConfigPrefix)
defer func() {
cleanupErr := CleanupCustomMCP(ctx, mcpConfig)
if cleanupErr != nil {
framework.Logf("Warning: cleanup had errors: %v", cleanupErr)
}
}()

// Wait for the node to be ready in the custom MCP
framework.Logf("Waiting for node %s to be ready in custom MCP", testNode)
waitTillNodeReadyWithConfig(kubeClient, testNode, customConfigPrefix)

// Get the current config before applying KubeletConfig
node, err := kubeClient.CoreV1().Nodes().Get(context.TODO(), optedNodes[0], metav1.GetOptions{})
node, err := kubeClient.CoreV1().Nodes().Get(ctx, testNode, metav1.GetOptions{})
o.Expect(err).NotTo(o.HaveOccurred(), fmt.Sprintf("Error getting node: %v", err))
originalConfig := node.Annotations["machineconfiguration.openshift.io/currentConfig"]
framework.Logf("Node '%v' has original config: %v", optedNodes[0], originalConfig)
framework.Logf("Node %s has original config: %s", testNode, originalConfig)

// Apply KubeletConfig with logging verbosity
defer deleteKC(oc, "custom-logging-config")
defer func() {
if err := CleanupKubeletConfig(ctx, mcClient, "custom-logging-config", ""); err != nil {
framework.Logf("Warning: KubeletConfig cleanup failed: %v", err)
}
}()
err = oc.Run("apply").Args("-f", kcFixture).Execute()
o.Expect(err).NotTo(o.HaveOccurred(), fmt.Sprintf("Error applying KubeletConfig: %v", err))

// Wait for the node to reboot after applying KubeletConfig
// KubeletConfig changes require a node reboot to take effect
framework.Logf("Waiting for node '%v' to reboot after applying KubeletConfig", optedNodes[0])
waitForReboot(kubeClient, optedNodes[0])
framework.Logf("Waiting for node %s to reboot after applying KubeletConfig", testNode)
waitForReboot(kubeClient, testNode)

// Verify the node has been updated with new config
framework.Logf("Verifying node '%v' has updated config after reboot", optedNodes[0])
node, err = kubeClient.CoreV1().Nodes().Get(context.TODO(), optedNodes[0], metav1.GetOptions{})
framework.Logf("Verifying node %s has updated config after reboot", testNode)
node, err = kubeClient.CoreV1().Nodes().Get(ctx, testNode, metav1.GetOptions{})
o.Expect(err).NotTo(o.HaveOccurred(), fmt.Sprintf("Error getting node after update: %v", err))
o.Expect(node.Annotations["machineconfiguration.openshift.io/state"]).To(o.Equal("Done"), "Node should be in Done state after reboot")
newConfig := node.Annotations["machineconfiguration.openshift.io/currentConfig"]
o.Expect(newConfig).NotTo(o.Equal(originalConfig), "Node config should have changed from %v to %v", originalConfig, newConfig)
o.Expect(newConfig).NotTo(o.Equal(originalConfig), "Node config should have changed from %s to %s", originalConfig, newConfig)

framework.Logf("Successfully applied KubeletConfig with logging verbosity to node '%v', config changed from '%v' to '%v'", optedNodes[0], originalConfig, newConfig)
framework.Logf("Successfully applied KubeletConfig with logging verbosity to node %s, config changed from %s to %s", testNode, originalConfig, newConfig)
})
})

// `addWorkerNodesToCustomPool` labels the desired number of worker nodes with the MCP role
// selector so that the nodes become part of the desired custom MCP
func addWorkerNodesToCustomPool(oc *exutil.CLI, kubeClient *kubernetes.Clientset, numberOfNodes int, customMCP string) ([]string, error) {
// Get the worker nodes
nodes, err := kubeClient.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{LabelSelector: labels.SelectorFromSet(labels.Set{"node-role.kubernetes.io/worker": ""}).String()})
if err != nil {
return nil, err
}
// Return an error if there are less worker nodes in the cluster than the desired number of nodes to add to the custom MCP
if len(nodes.Items) < numberOfNodes {
return nil, fmt.Errorf("Node in Worker MCP %d < Number of nodes needed in %d MCP", len(nodes.Items), numberOfNodes)
}

// Label the nodes with the custom MCP role selector
var optedNodes []string
for node_i := 0; node_i < numberOfNodes; node_i++ {
err = oc.AsAdmin().Run("label").Args("node", nodes.Items[node_i].Name, fmt.Sprintf("node-role.kubernetes.io/%s=", customMCP)).Execute()
if err != nil {
return nil, err
}
optedNodes = append(optedNodes, nodes.Items[node_i].Name)
}
return optedNodes, nil
}

// `waitForReboot` waits for up to 5 minutes for the input node to start a reboot and then up to 15
// minutes for the node to complete its reboot.
func waitForReboot(kubeClient *kubernetes.Clientset, nodeName string) {
Expand Down Expand Up @@ -172,22 +160,6 @@ func waitTillNodeReadyWithConfig(kubeClient *kubernetes.Clientset, nodeName, cur
}, 5*time.Minute, 10*time.Second).Should(o.BeTrue(), "Timed out waiting for Node '%s' to have rendered-worker config.", nodeName)
}

// `unlabelNode` removes the `node-role.kubernetes.io/custom` label from the node with the input
// name. This triggers the node's removal from the custom MCP named `custom`.
func unlabelNode(oc *exutil.CLI, name string) error {
return oc.AsAdmin().Run("label").Args("node", name, "node-role.kubernetes.io/custom-").Execute()
}

// `deleteKC` deletes the KubeletConfig with the input name
func deleteKC(oc *exutil.CLI, name string) error {
return oc.Run("delete").Args("kubeletconfig", name).Execute()
}

// `deleteMCP` deletes the MachineConfigPool with the input name
func deleteMCP(oc *exutil.CLI, name string) error {
return oc.Run("delete").Args("mcp", name).Execute()
}

// `skipOnSingleNodeTopology` skips the test if the cluster is using single-node topology
func skipOnSingleNodeTopology(oc *exutil.CLI) {
infra, err := oc.AdminConfigClient().ConfigV1().Infrastructures().Get(context.Background(), "cluster", metav1.GetOptions{})
Expand Down
Loading