From 3cf8668ccf0b939c8f3ddee8b854a88bfc7fac31 Mon Sep 17 00:00:00 2001 From: Angie Zhang Date: Fri, 14 Apr 2023 13:19:54 -0700 Subject: [PATCH] Error Prevention: Add transition action (#744) * Error Prevention: Add transition action Signed-off-by: Angie Zhang * Fixed detekt Signed-off-by: Angie Zhang * Added return false Signed-off-by: Angie Zhang * Fixed detekt Signed-off-by: Angie Zhang --------- Signed-off-by: Angie Zhang --- .../validation/ActionValidation.kt | 2 + .../validation/ValidateTransition.kt | 71 +++++++++++++++++++ .../validation/ValidateTransitionIT.kt | 69 ++++++++++++++++++ 3 files changed, 142 insertions(+) create mode 100644 src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateTransition.kt create mode 100644 src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateTransitionIT.kt diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ActionValidation.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ActionValidation.kt index c34234bd4..b45e2b18d 100644 --- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ActionValidation.kt +++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ActionValidation.kt @@ -18,6 +18,7 @@ class ActionValidation( val jvmService: JvmService ) { + @Suppress("ComplexMethod") fun validate(actionName: String, indexName: String): ValidationResult { // map action to validation class val validation = when (actionName) { @@ -28,6 +29,7 @@ class ActionValidation( "read_only" -> ValidateReadOnly(settings, clusterService, jvmService).execute(indexName) "read_write" -> ValidateReadWrite(settings, clusterService, jvmService).execute(indexName) "replica_count" -> ValidateReplicaCount(settings, clusterService, jvmService).execute(indexName) + "transition" -> ValidateTransition(settings, clusterService, jvmService).execute(indexName) "close" -> ValidateClose(settings, clusterService, jvmService).execute(indexName) "index_priority" -> ValidateIndexPriority(settings, clusterService, jvmService).execute(indexName) // No validations for these actions at current stage. diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateTransition.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateTransition.kt new file mode 100644 index 000000000..9faeff20f --- /dev/null +++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateTransition.kt @@ -0,0 +1,71 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.indexmanagement.indexstatemanagement.validation + +import org.apache.logging.log4j.LogManager +import org.opensearch.cluster.metadata.MetadataCreateIndexService +import org.opensearch.cluster.service.ClusterService +import org.opensearch.common.settings.Settings +import org.opensearch.indexmanagement.spi.indexstatemanagement.Validate +import org.opensearch.indexmanagement.util.OpenForTesting +import org.opensearch.indices.InvalidIndexNameException +import org.opensearch.monitor.jvm.JvmService + +@OpenForTesting +class ValidateTransition( + settings: Settings, + clusterService: ClusterService, + jvmService: JvmService +) : Validate(settings, clusterService, jvmService) { + + private val logger = LogManager.getLogger(javaClass) + + @Suppress("ReturnSuppressCount", "ReturnCount") + override fun execute(indexName: String): Validate { + // if these conditions are false, fail validation and do not execute transition action + if (!indexExists(indexName) || !validIndex(indexName)) { + return this + } + validationMessage = getValidationPassedMessage(indexName) + return this + } + + private fun indexExists(indexName: String): Boolean { + val isIndexExists = clusterService.state().metadata.indices.containsKey(indexName) + if (!isIndexExists) { + val message = getNoIndexMessage(indexName) + logger.warn(message) + validationStatus = ValidationStatus.RE_VALIDATING + validationMessage = message + return false + } + return true + } + + private fun validIndex(indexName: String): Boolean { + val exceptionGenerator: (String, String) -> RuntimeException = { index_name, reason -> InvalidIndexNameException(index_name, reason) } + // If the index name is invalid for any reason, this will throw an exception giving the reason why in the message. + // That will be displayed to the user as the cause. + try { + MetadataCreateIndexService.validateIndexOrAliasName(indexName, exceptionGenerator) + } catch (e: Exception) { + val message = getIndexNotValidMessage(indexName) + logger.warn(message) + validationStatus = ValidationStatus.RE_VALIDATING + validationMessage = message + return false + } + return true + } + + @Suppress("TooManyFunctions") + companion object { + const val name = "validate_transition" + fun getNoIndexMessage(index: String) = "Index [index=$index] does not exist for transition" + fun getIndexNotValidMessage(index: String) = "Index [index=$index] is not valid for transition" + fun getValidationPassedMessage(index: String) = "Transition action validation passed for [index=$index]" + } +} diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateTransitionIT.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateTransitionIT.kt new file mode 100644 index 000000000..a2383b7b6 --- /dev/null +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateTransitionIT.kt @@ -0,0 +1,69 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.indexmanagement.indexstatemanagement.validation + +import org.opensearch.indexmanagement.indexstatemanagement.IndexStateManagementRestTestCase +import org.opensearch.indexmanagement.indexstatemanagement.model.Conditions +import org.opensearch.indexmanagement.indexstatemanagement.model.Policy +import org.opensearch.indexmanagement.indexstatemanagement.model.State +import org.opensearch.indexmanagement.indexstatemanagement.model.Transition +import org.opensearch.indexmanagement.indexstatemanagement.randomErrorNotification +import org.opensearch.indexmanagement.spi.indexstatemanagement.Validate +import org.opensearch.indexmanagement.waitFor +import java.time.Instant +import java.time.temporal.ChronoUnit +import java.util.Locale + +class ValidateTransitionIT : IndexStateManagementRestTestCase() { + + private val testIndexName = javaClass.simpleName.lowercase(Locale.ROOT) + + fun `test transition validation with doc count condition`() { + enableValidationService() + val indexName = "${testIndexName}_index_1" + val policyID = "${testIndexName}_testPolicyName_1" + val secondStateName = "second" + val states = listOf( + State("first", listOf(), listOf(Transition(secondStateName, Conditions(docCount = 5L)))), + State(secondStateName, listOf(), listOf()) + ) + + val policy = Policy( + id = policyID, + description = "$testIndexName description", + schemaVersion = 1L, + lastUpdatedTime = Instant.now().truncatedTo(ChronoUnit.MILLIS), + errorNotification = randomErrorNotification(), + defaultState = states[0].name, + states = states + ) + + createPolicy(policy, policyID) + createIndex(indexName, policyID) + + val managedIndexConfig = getExistingManagedIndexConfig(indexName) + + // Initializing the policy/metadata + updateManagedIndexConfigStartTime(managedIndexConfig) + + waitFor { assertEquals(policyID, getExplainManagedIndexMetaData(indexName).policyID) } + + // Add 6 documents (>5) + insertSampleData(indexName, 6) + + // Evaluating transition conditions for second time + updateManagedIndexConfigStartTime(managedIndexConfig) + + waitFor { + val data = getExplainValidationResult(indexName) + assertEquals( + "Index transition validation status is PASSED.", + Validate.ValidationStatus.PASSED, + data.validationStatus + ) + } + } +}