diff --git a/tests/mdr-operator/README.md b/tests/mdr-operator/README.md index 8ea6fc6278..0714842994 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 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 non-existent namespace rejected with NotFound error; MDRT with invalid name rejected with Invalid error (k8serrors.IsInvalid) + ## 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..e12bd14828 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 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 new file mode 100644 index 0000000000..25d60d7c8e --- /dev/null +++ b/tests/mdr-operator/tests/mdr_negative.go @@ -0,0 +1,132 @@ +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.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.Background(), mdrtInvalidNs) + if err == nil { + DeferCleanup(func() { + 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, + 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.Background(), 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 !k8serrors.IsInvalid(err) { + validationErrors = append(validationErrors, + fmt.Sprintf("MDRT with name %q: expected Invalid 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 +}