From 585a11181c086f208b06f563b47cf5436fd80309 Mon Sep 17 00:00:00 2001 From: Gal Amado Date: Thu, 13 Aug 2026 14:33:09 +0300 Subject: [PATCH 1/4] mdr-operator: add MDRT negative validation test (OCP-60889) Validates that the API server rejects MachineDeletionRemediationTemplate CRs with invalid metadata: non-existent namespace (-2) returns NotFound, invalid name (-1-invalid-value) returns RFC 1123 validation error. Ported from ocp-edge-auto test_mdr_cli.py TestMDRNegativeScenarios. RHWA-1249 Co-Authored-By: Claude Opus 4.6 --- tests/mdr-operator/README.md | 17 ++- .../mdr-operator/internal/mdrparams/const.go | 9 ++ tests/mdr-operator/tests/mdr_negative.go | 126 ++++++++++++++++++ 3 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 tests/mdr-operator/tests/mdr_negative.go diff --git a/tests/mdr-operator/README.md b/tests/mdr-operator/README.md index 8ea6fc6278..096d817baf 100644 --- a/tests/mdr-operator/README.md +++ b/tests/mdr-operator/README.md @@ -75,6 +75,21 @@ container or pod level). Only checks the `manager` container. - **Standalone**: `ginkgo --label-filter="mdr" --focus="runs as non-root" ./tests/mdr-operator/...` - **Pass criteria**: Pod runAsNonRoot=true; expected manager container exists; manager container runAsUser != 0; allowPrivilegeEscalation=false; readOnlyRootFilesystem=true; capabilities.drop=[ALL]; seccomp profile RuntimeDefault +## Negative Validation Tests + +### 5. Verify MDRT With Invalid Values Is Rejected ([OCP-60889](https://polarion.engineering.redhat.com/polarion/#/project/OSE/workitem?id=OCP-60889)) + +Validates that the API server rejects MachineDeletionRemediationTemplate CRs +with invalid metadata. First attempts creation with an invalid namespace (`-2`), +then with an invalid name (`-1-invalid-value`) that violates RFC 1123 subdomain +rules. + +- **Operators**: MDR v0.7.0+ +- **Cluster**: Any topology (MNO or SNO) +- **Environment**: Connected or disconnected +- **Standalone**: `ginkgo --label-filter="mdr" --focus="invalid values" ./tests/mdr-operator/...` +- **Pass criteria**: MDRT with namespace `-2` rejected with NotFound error; MDRT with name `-1-invalid-value` rejected with RFC 1123 validation error; MDR controller pod running with expected replicas after test + ## Destructive Tests -- NHC-Triggered Remediation Tests that stop kubelet on a worker node, let NHC detect the unhealthy node @@ -88,7 +103,7 @@ provider provisions a new VM. The node is re-created (new creation timestamp). - At least 2 Ready worker nodes (target + spare for cluster schedulability) - `KUBECONFIG` set with cluster-admin access -### 5. MDR Remediation with Condition Transitions ([OCP-66138](https://polarion.engineering.redhat.com/polarion/#/project/OSE/workitem?id=OCP-66138)) +### 6. MDR Remediation with Condition Transitions ([OCP-66138](https://polarion.engineering.redhat.com/polarion/#/project/OSE/workitem?id=OCP-66138)) Stops kubelet on a worker node. NHC detects the unhealthy node and creates an MDR CR via the MDR template. Verifies the MDR CR status conditions diff --git a/tests/mdr-operator/internal/mdrparams/const.go b/tests/mdr-operator/internal/mdrparams/const.go index ecba6bbebf..13941ee60f 100644 --- a/tests/mdr-operator/internal/mdrparams/const.go +++ b/tests/mdr-operator/internal/mdrparams/const.go @@ -67,4 +67,13 @@ const ( // ConditionReasonRemediationStarted is the reason set on Processing and Succeeded // conditions when remediation begins. ConditionReasonRemediationStarted = "RemediationStarted" + + // MDRTNegativeTestName is the MDRT name used in negative validation tests. + MDRTNegativeTestName = "mdr-negative-test-template" + + // MDRTInvalidTestName is a name that violates RFC 1123 subdomain rules. + MDRTInvalidTestName = "-1-invalid-value" + + // MDRTInvalidTestNamespace is a namespace value that does not exist. + MDRTInvalidTestNamespace = "-2" ) diff --git a/tests/mdr-operator/tests/mdr_negative.go b/tests/mdr-operator/tests/mdr_negative.go new file mode 100644 index 0000000000..846063daee --- /dev/null +++ b/tests/mdr-operator/tests/mdr_negative.go @@ -0,0 +1,126 @@ +package tests + +import ( + "context" + "fmt" + "strings" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/rh-ecosystem-edge/eco-goinfra/pkg/deployment" + "github.com/rh-ecosystem-edge/eco-goinfra/pkg/pod" + "github.com/rh-ecosystem-edge/eco-goinfra/pkg/reportxml" + + "github.com/medik8s/system-tests/tests/internal/helpers" + "github.com/medik8s/system-tests/tests/internal/labels" + . "github.com/medik8s/system-tests/tests/internal/medik8sinittools" + "github.com/medik8s/system-tests/tests/internal/medik8sparams" + "github.com/medik8s/system-tests/tests/mdr-operator/internal/mdrparams" + + k8serrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var _ = Describe( + "MDR Negative Validation tests", + Ordered, + ContinueOnFailure, + Label(labels.OperatorMDR, mdrparams.Label), func() { + BeforeAll(func() { + By("Verify MDR deployment is ready") + + mdrDeployment, err := deployment.Pull( + APIClient, mdrparams.OperatorDeploymentName, medik8sparams.OperatorNs) + Expect(err).ToNot(HaveOccurred(), "Failed to get MDR deployment") + Expect(mdrDeployment.IsReady(medik8sparams.DefaultTimeout)).To(BeTrue(), + "MDR deployment is not Ready") + + By("Pre-cleaning stale test resources from previous runs") + + cleanupMDRT(mdrparams.MDRTNegativeTestName) + cleanupMDRT(mdrparams.MDRTInvalidTestName) + }) + + AfterAll(func() { + By("Cleaning up test MDRTs if unexpectedly created") + + cleanupMDRT(mdrparams.MDRTNegativeTestName) + cleanupMDRT(mdrparams.MDRTInvalidTestName) + + By("Verifying MDR controller pod is running") + + Eventually(verifyMDRControllerRunning, + medik8sparams.DefaultTimeout, mdrparams.DefaultPollInterval).Should(Succeed(), + "MDR controller pod should be running after negative tests") + }) + + It("Verify MDRT with invalid values is rejected by API server", + reportxml.ID("60889"), + Label(labels.TierAcceptance, labels.ComponentController, + labels.DisruptionNonDestructive, labels.PlatformAny, + labels.FrequencyPresubmit), func() { + var validationErrors []string + + By("Creating MDRT with non-existent namespace") + + mdrtInvalidNs := buildMDRT(mdrparams.MDRTNegativeTestName) + mdrtInvalidNs.SetNamespace(mdrparams.MDRTInvalidTestNamespace) + + err := APIClient.Create(context.TODO(), mdrtInvalidNs) + if err == nil { + DeferCleanup(func() { cleanupMDRT(mdrtInvalidNs.GetName()) }) + + validationErrors = append(validationErrors, + fmt.Sprintf("MDRT with namespace %q was unexpectedly created", + mdrparams.MDRTInvalidTestNamespace)) + } else if !k8serrors.IsNotFound(err) { + validationErrors = append(validationErrors, + fmt.Sprintf("MDRT with namespace %q: expected NotFound error, got: %v", + mdrparams.MDRTInvalidTestNamespace, err)) + } + + By("Creating MDRT with name violating RFC 1123") + + mdrtInvalidName := buildMDRT(mdrparams.MDRTInvalidTestName) + + err = APIClient.Create(context.TODO(), mdrtInvalidName) + if err == nil { + DeferCleanup(func() { cleanupMDRT(mdrtInvalidName.GetName()) }) + + validationErrors = append(validationErrors, + fmt.Sprintf("MDRT with name %q was unexpectedly created", + mdrparams.MDRTInvalidTestName)) + } else if !strings.Contains(err.Error(), "a lowercase RFC 1123 subdomain must consist of") { + validationErrors = append(validationErrors, + fmt.Sprintf("MDRT with name %q: expected RFC 1123 validation error, got: %v", + mdrparams.MDRTInvalidTestName, err)) + } + + if len(validationErrors) > 0 { + Fail("MDRT negative validation failures:\n- " + + strings.Join(validationErrors, "\n- ")) + } + }) + }) + +func verifyMDRControllerRunning() error { + listOptions := metav1.ListOptions{ + LabelSelector: mdrparams.OperatorControllerPodLabelSelector, + } + + allPods, listErr := pod.List(APIClient, medik8sparams.OperatorNs, listOptions) + if listErr != nil { + return fmt.Errorf("failed to list MDR pods: %w", listErr) + } + + mdrPods := helpers.FilterPodsByDeployment(allPods, mdrparams.OperatorDeploymentName) + runningCount := int32(len(helpers.FilterRunningPods(mdrPods))) + + if runningCount != mdrparams.ExpectedReplicas { + return fmt.Errorf("expected %d running MDR pod(s), found %d", + mdrparams.ExpectedReplicas, runningCount) + } + + return nil +} From d2f6041f5ffb4b55b9aaf298a118fe7f132e3136 Mon Sep 17 00:00:00 2001 From: Gal Amado Date: Sun, 16 Aug 2026 23:41:05 +0300 Subject: [PATCH 2/4] mdr-operator: address razo7 review comments on PR #88 - Fix DeferCleanup for invalid-namespace case to delete via the original object reference (correct namespace), not cleanupMDRT - Change test namespace from "-2" (syntactically invalid) to "mdr-test-nonexistent-ns" (valid but non-existent) to guarantee NotFound response across K8s versions - Replace string matching for RFC 1123 error with k8serrors.IsInvalid() - Remove ComponentController label (test validates K8s API server admission, not MDR controller behavior) Co-Authored-By: Claude Opus 4.6 --- tests/mdr-operator/README.md | 8 ++++---- tests/mdr-operator/internal/mdrparams/const.go | 4 ++-- tests/mdr-operator/tests/mdr_negative.go | 10 ++++++---- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/tests/mdr-operator/README.md b/tests/mdr-operator/README.md index 096d817baf..dd0ad9239f 100644 --- a/tests/mdr-operator/README.md +++ b/tests/mdr-operator/README.md @@ -80,15 +80,15 @@ container or pod level). Only checks the `manager` container. ### 5. Verify MDRT With Invalid Values Is Rejected ([OCP-60889](https://polarion.engineering.redhat.com/polarion/#/project/OSE/workitem?id=OCP-60889)) Validates that the API server rejects MachineDeletionRemediationTemplate CRs -with invalid metadata. First attempts creation with an invalid namespace (`-2`), -then with an invalid name (`-1-invalid-value`) that violates RFC 1123 subdomain -rules. +with invalid metadata. First attempts creation with a non-existent namespace +(`mdr-test-nonexistent-ns`), then with an invalid name (`-1-invalid-value`) +that violates RFC 1123 subdomain rules. - **Operators**: MDR v0.7.0+ - **Cluster**: Any topology (MNO or SNO) - **Environment**: Connected or disconnected - **Standalone**: `ginkgo --label-filter="mdr" --focus="invalid values" ./tests/mdr-operator/...` -- **Pass criteria**: MDRT with namespace `-2` rejected with NotFound error; MDRT with name `-1-invalid-value` rejected with RFC 1123 validation error; MDR controller pod running with expected replicas after test +- **Pass criteria**: MDRT with non-existent namespace rejected with NotFound error; MDRT with invalid name rejected with Invalid error (k8serrors.IsInvalid); MDR controller pod running with expected replicas after test ## Destructive Tests -- NHC-Triggered Remediation diff --git a/tests/mdr-operator/internal/mdrparams/const.go b/tests/mdr-operator/internal/mdrparams/const.go index 13941ee60f..e12bd14828 100644 --- a/tests/mdr-operator/internal/mdrparams/const.go +++ b/tests/mdr-operator/internal/mdrparams/const.go @@ -74,6 +74,6 @@ const ( // MDRTInvalidTestName is a name that violates RFC 1123 subdomain rules. MDRTInvalidTestName = "-1-invalid-value" - // MDRTInvalidTestNamespace is a namespace value that does not exist. - MDRTInvalidTestNamespace = "-2" + // MDRTInvalidTestNamespace is a syntactically valid namespace that does not exist. + MDRTInvalidTestNamespace = "mdr-test-nonexistent-ns" ) diff --git a/tests/mdr-operator/tests/mdr_negative.go b/tests/mdr-operator/tests/mdr_negative.go index 846063daee..8fe64716b2 100644 --- a/tests/mdr-operator/tests/mdr_negative.go +++ b/tests/mdr-operator/tests/mdr_negative.go @@ -57,7 +57,7 @@ var _ = Describe( It("Verify MDRT with invalid values is rejected by API server", reportxml.ID("60889"), - Label(labels.TierAcceptance, labels.ComponentController, + Label(labels.TierAcceptance, labels.DisruptionNonDestructive, labels.PlatformAny, labels.FrequencyPresubmit), func() { var validationErrors []string @@ -69,7 +69,9 @@ var _ = Describe( err := APIClient.Create(context.TODO(), mdrtInvalidNs) if err == nil { - DeferCleanup(func() { cleanupMDRT(mdrtInvalidNs.GetName()) }) + DeferCleanup(func() { + _ = APIClient.Delete(context.Background(), mdrtInvalidNs) + }) validationErrors = append(validationErrors, fmt.Sprintf("MDRT with namespace %q was unexpectedly created", @@ -91,9 +93,9 @@ var _ = Describe( validationErrors = append(validationErrors, fmt.Sprintf("MDRT with name %q was unexpectedly created", mdrparams.MDRTInvalidTestName)) - } else if !strings.Contains(err.Error(), "a lowercase RFC 1123 subdomain must consist of") { + } else if !k8serrors.IsInvalid(err) { validationErrors = append(validationErrors, - fmt.Sprintf("MDRT with name %q: expected RFC 1123 validation error, got: %v", + fmt.Sprintf("MDRT with name %q: expected Invalid error, got: %v", mdrparams.MDRTInvalidTestName, err)) } From 8630dac65c76f021995caca5c852e9e1b6aa8b1e Mon Sep 17 00:00:00 2001 From: Gal Amado Date: Mon, 17 Aug 2026 15:43:43 +0300 Subject: [PATCH 3/4] mdr-operator: address ugreener review comments on PR #88 - Add By() and error logging to DeferCleanup for invalid-namespace MDRT (can't use cleanupMDRT here -- it hardcodes OperatorNs, but this MDRT was created in a different namespace) - Remove AfterAll pod-running assertion from README pass criteria (AfterAll failures don't attach to OCP-60889's Polarion JUnit result) Co-Authored-By: Claude Opus 4.6 --- tests/mdr-operator/README.md | 2 +- tests/mdr-operator/tests/mdr_negative.go | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/mdr-operator/README.md b/tests/mdr-operator/README.md index dd0ad9239f..0714842994 100644 --- a/tests/mdr-operator/README.md +++ b/tests/mdr-operator/README.md @@ -88,7 +88,7 @@ that violates RFC 1123 subdomain rules. - **Cluster**: Any topology (MNO or SNO) - **Environment**: Connected or disconnected - **Standalone**: `ginkgo --label-filter="mdr" --focus="invalid values" ./tests/mdr-operator/...` -- **Pass criteria**: MDRT with non-existent namespace rejected with NotFound error; MDRT with invalid name rejected with Invalid error (k8serrors.IsInvalid); MDR controller pod running with expected replicas after test +- **Pass criteria**: MDRT with non-existent namespace rejected with NotFound error; MDRT with invalid name rejected with Invalid error (k8serrors.IsInvalid) ## Destructive Tests -- NHC-Triggered Remediation diff --git a/tests/mdr-operator/tests/mdr_negative.go b/tests/mdr-operator/tests/mdr_negative.go index 8fe64716b2..50cc339429 100644 --- a/tests/mdr-operator/tests/mdr_negative.go +++ b/tests/mdr-operator/tests/mdr_negative.go @@ -70,7 +70,11 @@ var _ = Describe( err := APIClient.Create(context.TODO(), mdrtInvalidNs) if err == nil { DeferCleanup(func() { - _ = APIClient.Delete(context.Background(), mdrtInvalidNs) + By("Cleaning up unexpectedly created MDRT in non-existent namespace") + if delErr := APIClient.Delete(context.Background(), mdrtInvalidNs); delErr != nil && !k8serrors.IsNotFound(delErr) { + GinkgoWriter.Printf("Warning: failed to delete MDRT %s/%s: %v\n", + mdrtInvalidNs.GetNamespace(), mdrtInvalidNs.GetName(), delErr) + } }) validationErrors = append(validationErrors, From db5759e1df9bb665449b845d2633d868fbaf5aec Mon Sep 17 00:00:00 2001 From: Gal Amado Date: Wed, 19 Aug 2026 15:00:38 +0300 Subject: [PATCH 4/4] mdr-operator: replace remaining context.TODO() in negative test Fix the two APIClient.Create calls (lines 70, 93) that still used context.TODO(); the rest of the MDR suite uses context.Background(). Addresses razo7 follow-up on PR #88. Co-Authored-By: Claude Opus 4.6 --- tests/mdr-operator/tests/mdr_negative.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/mdr-operator/tests/mdr_negative.go b/tests/mdr-operator/tests/mdr_negative.go index 50cc339429..25d60d7c8e 100644 --- a/tests/mdr-operator/tests/mdr_negative.go +++ b/tests/mdr-operator/tests/mdr_negative.go @@ -67,7 +67,7 @@ var _ = Describe( mdrtInvalidNs := buildMDRT(mdrparams.MDRTNegativeTestName) mdrtInvalidNs.SetNamespace(mdrparams.MDRTInvalidTestNamespace) - err := APIClient.Create(context.TODO(), mdrtInvalidNs) + err := APIClient.Create(context.Background(), mdrtInvalidNs) if err == nil { DeferCleanup(func() { By("Cleaning up unexpectedly created MDRT in non-existent namespace") @@ -90,7 +90,7 @@ var _ = Describe( mdrtInvalidName := buildMDRT(mdrparams.MDRTInvalidTestName) - err = APIClient.Create(context.TODO(), mdrtInvalidName) + err = APIClient.Create(context.Background(), mdrtInvalidName) if err == nil { DeferCleanup(func() { cleanupMDRT(mdrtInvalidName.GetName()) })