-
Notifications
You must be signed in to change notification settings - Fork 9
CMP-3831: Add test for namespace exemption test for resource limit checks #71
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -497,3 +497,119 @@ func TestProfileRemediations(t *testing.T) { | |
| t.Logf("Warning: Failed to wait for scan cleanup for binding %s: %s", bindingName, err) | ||
| } | ||
| } | ||
|
|
||
| // TestNamespaceExemptionVariables tests the namespace exemption logic for | ||
| // resource limit checks. This test validates that: | ||
| // 1. Workloads without resource limits in exempted namespaces pass the check | ||
| // 2. The exemption variables work correctly for DaemonSet, Deployment, and StatefulSet | ||
| func TestNamespaceExemptionVariables(t *testing.T) { | ||
| // Skip if test type doesn't include platform tests | ||
| if tc.TestType != "platform" && tc.TestType != "all" { | ||
| t.Skipf("Skipping namespace exemption test: -test-type is %s", tc.TestType) | ||
| } | ||
|
|
||
| c, err := helpers.GenerateKubeConfig() | ||
| if err != nil { | ||
| t.Fatalf("Failed to generate kube config: %s", err) | ||
| } | ||
|
|
||
| // Test namespace names | ||
| testNamespaces := []string{ | ||
| "ns-76797-test-1", | ||
| "ns-76797-test-2", | ||
| } | ||
|
|
||
| // Create test namespaces | ||
| for _, ns := range testNamespaces { | ||
| err = createNamespace(c, ns) | ||
| if err != nil { | ||
| t.Fatalf("Failed to create test namespace %s: %s", ns, err) | ||
| } | ||
| t.Logf("Created test namespace: %s", ns) | ||
| } | ||
|
|
||
| // Cleanup namespaces at the end | ||
| defer func() { | ||
| for _, ns := range testNamespaces { | ||
| deleteNamespace(c, ns) | ||
| } | ||
| }() | ||
|
|
||
| // Create workloads without resource limits in test namespaces | ||
| err = createTestWorkloadsWithoutLimits(c, testNamespaces[0]) | ||
| if err != nil { | ||
| t.Fatalf("Failed to create test workloads: %s", err) | ||
| } | ||
| t.Logf("Created test workloads without resource limits in %s", testNamespaces[0]) | ||
|
|
||
| // Wait for workloads to be created | ||
| time.Sleep(5 * time.Second) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It'll be safer to add polling here instead of a blanket 5 second wait. |
||
|
|
||
| // Build regex pattern to exempt test namespaces | ||
| // Pattern matches both test namespaces | ||
| exemptionPattern := "^ns-76797-test-.*$" | ||
|
|
||
| // Create TailoredProfile with namespace exemption variables | ||
| tailoredProfileName := "ns-exemption-test-profile" | ||
| err = createTailoredProfileWithExemptions(tc, c, tailoredProfileName, exemptionPattern) | ||
| if err != nil { | ||
| t.Fatalf("Failed to create tailored profile with exemptions: %s", err) | ||
| } | ||
| t.Logf("Created TailoredProfile: %s with exemption pattern: %s", tailoredProfileName, exemptionPattern) | ||
|
|
||
| // Create scan binding for the tailored profile | ||
| bindingName := "ns-exemption-scan-binding" | ||
| err = helpers.CreateScanBinding(c, tc, bindingName, tailoredProfileName, "TailoredProfile", "default") | ||
| if err != nil { | ||
| t.Fatalf("Failed to create scan binding: %s", err) | ||
| } | ||
| t.Logf("Created ScanSettingBinding: %s", bindingName) | ||
|
|
||
| // Wait for compliance suite to complete | ||
| err = helpers.WaitForComplianceSuite(tc, c, bindingName) | ||
| if err != nil { | ||
| t.Fatalf("Failed to wait for compliance suite: %s", err) | ||
| } | ||
|
|
||
| // Get scan results | ||
| results, err := helpers.CreateResultMap(tc, c, bindingName) | ||
| if err != nil { | ||
| t.Fatalf("Failed to create result map: %s", err) | ||
| } | ||
|
|
||
| // Verify that resource limit rules PASS because namespaces are exempted | ||
| expectedRules := map[string]string{ | ||
| "resource-requests-limits-in-daemonset": "PASS", | ||
| "resource-requests-limits-in-deployment": "PASS", | ||
| "resource-requests-limits-in-statefulset": "PASS", | ||
| } | ||
|
|
||
| var failures []string | ||
| for ruleName, expectedResult := range expectedRules { | ||
| // Find the actual result - the result name might include scan name prefix | ||
| actualResult := findRuleResult(results, ruleName) | ||
| if actualResult == "" { | ||
| failures = append(failures, fmt.Sprintf("Rule %s not found in scan results", ruleName)) | ||
| continue | ||
| } | ||
|
|
||
| if actualResult != expectedResult { | ||
| failures = append(failures, | ||
| fmt.Sprintf("Rule %s: expected %s, got %s", ruleName, expectedResult, actualResult)) | ||
| } else { | ||
| t.Logf("Rule %s: %s (namespace exemption working correctly)", ruleName, actualResult) | ||
| } | ||
| } | ||
|
|
||
| // Save results for debugging | ||
| err = helpers.SaveResultAsYAML(tc, results, "namespace-exemption-test-results.yaml") | ||
| if err != nil { | ||
| t.Logf("Warning: Failed to save test results: %s", err) | ||
| } | ||
|
|
||
| if len(failures) > 0 { | ||
| t.Fatalf("Namespace exemption test failed:\n%v", failures) | ||
| } | ||
|
|
||
| t.Log("Namespace exemption test passed successfully - all exempted workloads passed the resource limit checks") | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,17 @@ | ||
| package ocp4e2e | ||
|
|
||
| import ( | ||
| "context" | ||
| "strings" | ||
|
|
||
| cmpv1alpha1 "github.com/ComplianceAsCode/compliance-operator/pkg/apis/compliance/v1alpha1" | ||
| "github.com/ComplianceAsCode/ocp4e2e/config" | ||
| appsv1 "k8s.io/api/apps/v1" | ||
| corev1 "k8s.io/api/core/v1" | ||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
| dynclient "sigs.k8s.io/controller-runtime/pkg/client" | ||
| ) | ||
|
|
||
| // RuleTest is the definition of the structure rule-specific e2e tests should have. | ||
| type RuleTest struct { | ||
| DefaultResult interface{} `yaml:"default_result"` | ||
|
|
@@ -13,3 +25,114 @@ type RuleTestResults struct { | |
|
|
||
| func init() { | ||
| } | ||
|
|
||
| // createTailoredProfileWithExemptions creates a TailoredProfile for namespace exemption testing. | ||
| func createTailoredProfileWithExemptions(tc *config.TestConfig, c dynclient.Client, name, exemptionPattern string) error { | ||
| tp := &cmpv1alpha1.TailoredProfile{ | ||
| ObjectMeta: metav1.ObjectMeta{ | ||
| Name: name, Namespace: tc.OperatorNamespace.Namespace, | ||
| Annotations: map[string]string{"compliance.openshift.io/product-type": "Platform"}, | ||
| }, | ||
| Spec: cmpv1alpha1.TailoredProfileSpec{ | ||
| Title: "Namespace Exemption Test Profile", | ||
| Description: "Test profile for validating namespace exemption variables", | ||
| EnableRules: []cmpv1alpha1.RuleReferenceSpec{ | ||
| {Name: "ocp4-resource-requests-limits-in-daemonset"}, | ||
| {Name: "ocp4-resource-requests-limits-in-deployment"}, | ||
| {Name: "ocp4-resource-requests-limits-in-statefulset"}, | ||
| }, | ||
| SetValues: []cmpv1alpha1.VariableValueSpec{ | ||
| {Name: "ocp4-var-daemonset-limit-namespaces-exempt-regex", Value: exemptionPattern}, | ||
| {Name: "ocp4-var-deployment-limit-namespaces-exempt-regex", Value: exemptionPattern}, | ||
| {Name: "ocp4-var-statefulset-limit-namespaces-exempt-regex", Value: exemptionPattern}, | ||
| }, | ||
| }, | ||
| } | ||
| return c.Create(context.TODO(), tp) | ||
| } | ||
|
|
||
| // createTestWorkloadsWithoutLimits creates test workloads without resource limits. | ||
| func createTestWorkloadsWithoutLimits(c dynclient.Client, namespace string) error { | ||
| ctx := context.TODO() | ||
|
|
||
| workloads := []dynclient.Object{ | ||
| &appsv1.Deployment{ | ||
| ObjectMeta: metav1.ObjectMeta{Name: "test-deployment-no-limits", Namespace: namespace}, | ||
| Spec: appsv1.DeploymentSpec{ | ||
| Replicas: int32Ptr(1), | ||
| Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "test"}}, | ||
| Template: corev1.PodTemplateSpec{ | ||
| ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "test"}}, | ||
| Spec: corev1.PodSpec{Containers: []corev1.Container{{ | ||
| Name: "nginx", Image: "registry.access.redhat.com/ubi8/ubi-minimal:latest", | ||
| Command: []string{"/bin/sh", "-c", "sleep infinity"}, | ||
| }}}, | ||
| }, | ||
| }, | ||
| }, | ||
| &appsv1.DaemonSet{ | ||
| ObjectMeta: metav1.ObjectMeta{Name: "test-daemonset-no-limits", Namespace: namespace}, | ||
| Spec: appsv1.DaemonSetSpec{ | ||
| Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "test"}}, | ||
| Template: corev1.PodTemplateSpec{ | ||
| ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "test"}}, | ||
| Spec: corev1.PodSpec{ | ||
| Containers: []corev1.Container{{ | ||
| Name: "nginx", Image: "registry.access.redhat.com/ubi8/ubi-minimal:latest", | ||
| Command: []string{"/bin/sh", "-c", "sleep infinity"}, | ||
| }}, | ||
| Tolerations: []corev1.Toleration{{Operator: corev1.TolerationOpExists}}, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| &appsv1.StatefulSet{ | ||
| ObjectMeta: metav1.ObjectMeta{Name: "test-statefulset-no-limits", Namespace: namespace}, | ||
| Spec: appsv1.StatefulSetSpec{ | ||
| Replicas: int32Ptr(1), ServiceName: "test", | ||
| Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "test"}}, | ||
| Template: corev1.PodTemplateSpec{ | ||
| ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "test"}}, | ||
| Spec: corev1.PodSpec{Containers: []corev1.Container{{ | ||
| Name: "nginx", Image: "registry.access.redhat.com/ubi8/ubi-minimal:latest", | ||
| Command: []string{"/bin/sh", "-c", "sleep infinity"}, | ||
| }}}, | ||
| }, | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| for _, w := range workloads { | ||
| if err := c.Create(ctx, w); err != nil { | ||
| return err | ||
| } | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| // createNamespace creates a namespace. | ||
| func createNamespace(c dynclient.Client, name string) error { | ||
| return c.Create(context.TODO(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: name}}) | ||
| } | ||
|
|
||
| // deleteNamespace deletes a namespace. | ||
| func deleteNamespace(c dynclient.Client, name string) error { | ||
| c.Delete(context.TODO(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: name}}) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should return what |
||
| return nil | ||
| } | ||
|
|
||
| // findRuleResult searches for a rule result by partial name match. | ||
| func findRuleResult(results map[string]string, ruleName string) string { | ||
| if result, exists := results[ruleName]; exists { | ||
| return result | ||
| } | ||
| for resultName, resultValue := range results { | ||
| if strings.Contains(resultName, ruleName) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rule names would always be a suffix of the result name? But what happens if we have the same rule running in two different profiles? This would return the first found, right? |
||
| return resultValue | ||
| } | ||
| } | ||
| return "" | ||
| } | ||
|
|
||
| // int32Ptr returns a pointer to an int32 value. | ||
| func int32Ptr(i int32) *int32 { return &i } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does 76797 carry significance?