diff --git a/CATALOG.md b/CATALOG.md index ad89b1daf..c28fda8d8 100644 --- a/CATALOG.md +++ b/CATALOG.md @@ -7,7 +7,7 @@ Depending on the workload type, not all tests are required to pass to satisfy be ## Test cases summary -### Total test cases: 114 +### Total test cases: 116 ### Total suites: 10 @@ -19,7 +19,7 @@ Depending on the workload type, not all tests are required to pass to satisfy be |manageability|2| |networking|12| |observability|5| -|operator|9| +|operator|10| |performance|6| |platform-alteration|13| |preflight|17| @@ -36,11 +36,11 @@ Depending on the workload type, not all tests are required to pass to satisfy be |---|---| |8|1| -### Non-Telco specific tests only: 67 +### Non-Telco specific tests only: 68 |Mandatory|Optional| |---|---| -|42|25| +|43|25| ### Telco specific tests only: 27 @@ -1266,15 +1266,31 @@ Tags|common,operator |Non-Telco|Mandatory| |Telco|Mandatory| +#### operator-multiple-same-operators + +Property|Description +---|--- +Unique ID|operator-multiple-same-operators +Description|Tests whether multiple instances of the same Operator CSV are installed. +Suggested Remediation|Ensure that only one Operator of the same type is installed in the cluster. +Best Practice Reference|https://redhat-best-practices-for-k8s.github.io/guide/#redhat-best-practices-for-k8s-cnf-operator-requirements +Exception Process|No exceptions +Tags|common,operator +|**Scenario**|**Optional/Mandatory**| +|Extended|Mandatory| +|Far-Edge|Mandatory| +|Non-Telco|Mandatory| +|Telco|Mandatory| + #### operator-olm-skip-range Property|Description ---|--- Unique ID|operator-olm-skip-range Description|Test that checks the operator has a valid olm skip range. -Suggested Remediation|Ensure that the Operator has a valid OLM skip range. +Suggested Remediation|Ensure that the Operator has a valid OLM skip range. If the operator does not have another version to "skip", then ignore the result of this test. Best Practice Reference|https://redhat-best-practices-for-k8s.github.io/guide/#redhat-best-practices-for-k8s-cnf-operator-requirements -Exception Process|No exceptions +Exception Process|If there is not a version of the operator that needs to be skipped, then an exception will be granted. Tags|common,operator |**Scenario**|**Optional/Mandatory**| |Extended|Optional| diff --git a/expected_results.yaml b/expected_results.yaml index 3789b3d29..9fa8b0f23 100644 --- a/expected_results.yaml +++ b/expected_results.yaml @@ -58,6 +58,7 @@ testCases: - operator-semantic-versioning - operator-single-crd-owner - operator-pods-no-hugepages + - operator-multiple-same-operators - performance-exclusive-cpu-pool - performance-max-resources-exec-probes - performance-shared-cpu-pool-non-rt-scheduling-policy # hazelcast pod meets requirements diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index d05ffbc01..7945c33fa 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -240,7 +240,7 @@ func buildTestEnvironment() { //nolint:funlen } env.AllSubscriptions = data.AllSubscriptions env.AllCatalogSources = data.AllCatalogSources - env.AllOperators = createOperators(data.AllCsvs, data.AllSubscriptions, data.AllInstallPlans, data.AllCatalogSources, false, false) + env.AllOperators = createOperators(data.AllCsvs, data.AllSubscriptions, data.AllInstallPlans, data.AllCatalogSources, false, true) env.AllOperatorsSummary = getSummaryAllOperators(env.AllOperators) env.AllCrds = data.AllCrds env.Namespaces = data.Namespaces diff --git a/tests/identifiers/doclinks.go b/tests/identifiers/doclinks.go index 0c530ad75..9fa0d3d17 100644 --- a/tests/identifiers/doclinks.go +++ b/tests/identifiers/doclinks.go @@ -117,6 +117,7 @@ const ( TestOperatorReadOnlyFilesystemDocLink = DocOperatorRequirement TestOperatorPodsNoHugepagesDocLink = DocOperatorRequirement TestOperatorOlmSkipRangeDocLink = DocOperatorRequirement + TestMultipleSameOperatorsIdentifierDocLink = DocOperatorRequirement // Observability Test Suite TestLoggingIdentifierDocLink = "https://redhat-best-practices-for-k8s.github.io/guide/#redhat-best-practices-for-k8s-logging" diff --git a/tests/identifiers/identifiers.go b/tests/identifiers/identifiers.go index 7a4efd14c..ec505d4e2 100644 --- a/tests/identifiers/identifiers.go +++ b/tests/identifiers/identifiers.go @@ -133,6 +133,7 @@ var ( TestOperatorCrdSchemaIdentifier claim.Identifier TestOperatorSingleCrdOwnerIdentifier claim.Identifier TestOperatorPodsNoHugepages claim.Identifier + TestMultipleSameOperatorsIdentifier claim.Identifier TestPodNodeSelectorAndAffinityBestPractices claim.Identifier TestPodHighAvailabilityBestPractices claim.Identifier TestPodClusterRoleBindingsBestPracticesIdentifier claim.Identifier @@ -1067,6 +1068,22 @@ that Node's kernel may not have the same hacks.'`, }, TagCommon) + TestMultipleSameOperatorsIdentifier = AddCatalogEntry( + "multiple-same-operators", + common.OperatorTestKey, + `Tests whether multiple instances of the same Operator CSV are installed.`, + MultipleSameOperatorsRemediation, + NoExceptions, + TestMultipleSameOperatorsIdentifierDocLink, + false, + map[string]string{ + FarEdge: Mandatory, + Telco: Mandatory, + NonTelco: Mandatory, + Extended: Mandatory, + }, + TagCommon) + TestPodNodeSelectorAndAffinityBestPractices = AddCatalogEntry( "pod-scheduling", common.LifecycleTestKey, diff --git a/tests/identifiers/remediation.go b/tests/identifiers/remediation.go index 46276748c..510594360 100644 --- a/tests/identifiers/remediation.go +++ b/tests/identifiers/remediation.go @@ -101,6 +101,8 @@ const ( OperatorPodsNoHugepagesRemediation = `Ensure that the pods are not using hugepages` + MultipleSameOperatorsRemediation = `Ensure that only one Operator of the same type is installed in the cluster.` + PodNodeSelectorAndAffinityBestPracticesRemediation = `In most cases, Pod's should not specify their host Nodes through nodeSelector or nodeAffinity. However, there are cases in which workloads require specialized hardware specific to a particular class of Node.` PodHighAvailabilityBestPracticesRemediation = `In high availability cases, Pod podAntiAffinity rule should be specified for pod scheduling and pod replica value is set to more than 1 .` diff --git a/tests/operator/suite.go b/tests/operator/suite.go index 334a40768..ed44397ce 100644 --- a/tests/operator/suite.go +++ b/tests/operator/suite.go @@ -108,6 +108,13 @@ func LoadChecks() { testOperatorOlmSkipRange(c, &env) return nil })) + + checksGroup.Add(checksdb.NewCheck(identifiers.GetTestIDAndLabels(identifiers.TestMultipleSameOperatorsIdentifier)). + WithSkipCheckFn(testhelper.GetNoOperatorsSkipFn(&env)). + WithCheckFn(func(c *checksdb.Check) error { + testMultipleSameOperators(c, &env) + return nil + })) } // This function check if the Operator CRD version follows K8s versioning @@ -373,3 +380,51 @@ func testOperatorOlmSkipRange(check *checksdb.Check, env *provider.TestEnvironme } check.SetResult(compliantObjects, nonCompliantObjects) } + +func testMultipleSameOperators(check *checksdb.Check, env *provider.TestEnvironment) { + var compliantObjects []*testhelper.ReportObject + var nonCompliantObjects []*testhelper.ReportObject + + // Ensure the CSV name is unique and not installed more than once. + // CSV Names are unique and OLM installs them with name.version format. + // So, we can check if the CSV name is installed more than once. + + check.LogInfo("Checking if the operator is installed more than once") + + for _, op := range env.AllOperators { + check.LogDebug("Checking operator %q", op.Name) + check.LogDebug("Number of operators to check %s against: %d", op.Name, len(env.AllOperators)) + for _, op2 := range env.AllOperators { + check.LogDebug("Comparing operator %q with operator %q", op.Name, op2.Name) + + // Retrieve the version from each CSV + csv1Version := op.Csv.Spec.Version.String() + csv2Version := op2.Csv.Spec.Version.String() + + log.Debug("CSV1 Version: %s", csv1Version) + log.Debug("CSV2 Version: %s", csv2Version) + + // Strip the version from the CSV name by removing the suffix (which should be the version) + csv1Name := strings.TrimSuffix(op.Csv.Name, ".v"+csv1Version) + csv2Name := strings.TrimSuffix(op2.Csv.Name, ".v"+csv2Version) + + check.LogDebug("Comparing CSV names %q and %q", csv1Name, csv2Name) + + // The CSV name should be the same, but the version should be different + // if the operator is installed more than once. + if op.Csv != nil && op2.Csv != nil && + csv1Name == csv2Name && + csv1Version != csv2Version { + check.LogError("Operator %q is installed more than once", op.Name) + nonCompliantObjects = append(nonCompliantObjects, testhelper.NewOperatorReportObject( + op.Namespace, op.Name, "Operator is installed more than once", false)) + break + } + } + + compliantObjects = append(compliantObjects, testhelper.NewOperatorReportObject( + op.Namespace, op.Name, "Operator is installed only once", true)) + } + + check.SetResult(compliantObjects, nonCompliantObjects) +}