From 5e75fff63817651650e9857e28aa8c02df4c09f7 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Thu, 3 Nov 2022 11:46:41 -0700 Subject: [PATCH] Error prevention stage 1 (#579) (#591) * Error prevention stage 1 * Removed recursion from Explain Action to avoid stackoverflow in some situations (#419) * enabled by default integrated * Fix Test cases * Fix comments; set validation disabled by default * Rename validation_service to action_validation; Fix some detekt issues Signed-off-by: Joanne Wang Signed-off-by: Petar Dzepina Signed-off-by: Angie Zhang Co-authored-by: Joanne Wang Co-authored-by: Petar Co-authored-by: Angie Zhang --- .../indexstatemanagement/Validate.kt | 46 ++++ .../model/ManagedIndexMetaData.kt | 1 - .../model/ValidationResult.kt | 94 ++++++++ .../indexmanagement/IndexManagementPlugin.kt | 6 + .../ManagedIndexRunner.kt | 34 ++- .../resthandler/RestExplainAction.kt | 3 + .../settings/ManagedIndexSettings.kt | 8 + .../action/explain/ExplainRequest.kt | 6 + .../action/explain/ExplainResponse.kt | 15 +- .../action/explain/TransportExplainAction.kt | 47 +++- .../util/RestHandlerUtils.kt | 3 + .../validation/ActionValidation.kt | 38 +++ .../validation/ValidateDelete.kt | 108 +++++++++ .../validation/ValidateForceMerge.kt | 52 +++++ .../validation/ValidateNothing.kt | 25 ++ .../validation/ValidateOpen.kt | 72 ++++++ .../validation/ValidateReadOnly.kt | 71 ++++++ .../validation/ValidateReadWrite.kt | 57 +++++ .../validation/ValidateReplicaCount.kt | 74 ++++++ .../validation/ValidateRollover.kt | 128 ++++++++++ .../IndexManagementIndicesIT.kt | 2 +- .../IndexStateManagementRestTestCase.kt | 51 +++- .../MetadataRegressionIT.kt | 2 +- .../action/ActionRetryIT.kt | 6 +- .../action/ActionTimeoutIT.kt | 2 +- .../action/AllocationActionIT.kt | 2 +- .../action/CloseActionIT.kt | 2 +- .../action/DeleteActionIT.kt | 2 +- .../action/ForceMergeActionIT.kt | 2 +- .../action/IndexPolicyActionIT.kt | 2 +- .../action/IndexPriorityActionIT.kt | 2 +- .../action/IndexStateManagementHistoryIT.kt | 4 +- .../action/NotificationActionIT.kt | 2 +- .../action/OpenActionIT.kt | 2 +- .../action/ReadOnlyActionIT.kt | 2 +- .../action/ReadWriteActionIT.kt | 2 +- .../action/ReplicaCountActionIT.kt | 2 +- .../action/RolloverActionIT.kt | 3 +- .../action/RollupActionIT.kt | 2 +- .../action/SnapshotActionIT.kt | 2 +- .../action/TransitionActionIT.kt | 2 +- .../coordinator/ManagedIndexCoordinatorIT.kt | 1 + .../resthandler/ISMTemplateRestAPIIT.kt | 3 +- .../resthandler/RestChangePolicyActionIT.kt | 2 +- .../resthandler/RestExplainActionIT.kt | 13 +- .../RestRetryFailedManagedIndexActionIT.kt | 2 +- .../action/explain/ExplainRequestTests.kt | 6 +- .../action/explain/ExplainResponseTests.kt | 7 +- .../validation/ValidateDeleteIT.kt | 74 ++++++ .../validation/ValidateForceMergeIT.kt | 96 ++++++++ .../validation/ValidateOpenIT.kt | 73 ++++++ .../validation/ValidateReadOnlyIT.kt | 71 ++++++ .../validation/ValidateReadWriteIT.kt | 80 +++++++ .../validation/ValidateReplicaCountIT.kt | 70 ++++++ .../validation/ValidateRolloverIT.kt | 218 ++++++++++++++++++ .../validation/ValidateRolloverTests.kt | 72 ++++++ .../indexmanagement/rollup/TestHelpers.kt | 6 +- .../indexmanagement/transform/TestHelpers.kt | 8 +- worksheets/ism/delete.http | 33 +++ worksheets/ism/rollover.http | 133 +++++++++++ 60 files changed, 1904 insertions(+), 50 deletions(-) create mode 100644 spi/src/main/kotlin/org.opensearch.indexmanagement.spi/indexstatemanagement/Validate.kt create mode 100644 spi/src/main/kotlin/org.opensearch.indexmanagement.spi/indexstatemanagement/model/ValidationResult.kt create mode 100644 src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ActionValidation.kt create mode 100644 src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateDelete.kt create mode 100644 src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateForceMerge.kt create mode 100644 src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateNothing.kt create mode 100644 src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateOpen.kt create mode 100644 src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateReadOnly.kt create mode 100644 src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateReadWrite.kt create mode 100644 src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateReplicaCount.kt create mode 100644 src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateRollover.kt create mode 100644 src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateDeleteIT.kt create mode 100644 src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateForceMergeIT.kt create mode 100644 src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateOpenIT.kt create mode 100644 src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateReadOnlyIT.kt create mode 100644 src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateReadWriteIT.kt create mode 100644 src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateReplicaCountIT.kt create mode 100644 src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateRolloverIT.kt create mode 100644 src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateRolloverTests.kt create mode 100644 worksheets/ism/delete.http create mode 100644 worksheets/ism/rollover.http diff --git a/spi/src/main/kotlin/org.opensearch.indexmanagement.spi/indexstatemanagement/Validate.kt b/spi/src/main/kotlin/org.opensearch.indexmanagement.spi/indexstatemanagement/Validate.kt new file mode 100644 index 000000000..a747824f5 --- /dev/null +++ b/spi/src/main/kotlin/org.opensearch.indexmanagement.spi/indexstatemanagement/Validate.kt @@ -0,0 +1,46 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.indexmanagement.spi.indexstatemanagement + +import org.opensearch.cluster.service.ClusterService +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import org.opensearch.common.settings.Settings +import org.opensearch.monitor.jvm.JvmService +import java.util.Locale + +abstract class Validate( + val settings: Settings, + val clusterService: ClusterService, + val jvmService: JvmService +) { + + var validationStatus = ValidationStatus.PASSED + var validationMessage: String? = "Starting Validation" + + abstract fun execute(indexName: String): Validate + + enum class ValidationStatus(val status: String) : Writeable { + PASSED("passed"), + RE_VALIDATING("re_validating"), + FAILED("failed"); + + override fun toString(): String { + return status + } + + override fun writeTo(out: StreamOutput) { + out.writeString(status) + } + + companion object { + fun read(streamInput: StreamInput): ValidationStatus { + return valueOf(streamInput.readString().uppercase(Locale.ROOT)) + } + } + } +} diff --git a/spi/src/main/kotlin/org.opensearch.indexmanagement.spi/indexstatemanagement/model/ManagedIndexMetaData.kt b/spi/src/main/kotlin/org.opensearch.indexmanagement.spi/indexstatemanagement/model/ManagedIndexMetaData.kt index 28e6c4051..fb1019f2e 100644 --- a/spi/src/main/kotlin/org.opensearch.indexmanagement.spi/indexstatemanagement/model/ManagedIndexMetaData.kt +++ b/spi/src/main/kotlin/org.opensearch.indexmanagement.spi/indexstatemanagement/model/ManagedIndexMetaData.kt @@ -241,7 +241,6 @@ data class ManagedIndexMetaData( var action: ActionMetaData? = null var step: StepMetaData? = null var retryInfo: PolicyRetryInfoMetaData? = null - var info: Map? = null XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) diff --git a/spi/src/main/kotlin/org.opensearch.indexmanagement.spi/indexstatemanagement/model/ValidationResult.kt b/spi/src/main/kotlin/org.opensearch.indexmanagement.spi/indexstatemanagement/model/ValidationResult.kt new file mode 100644 index 000000000..b8ab85608 --- /dev/null +++ b/spi/src/main/kotlin/org.opensearch.indexmanagement.spi/indexstatemanagement/model/ValidationResult.kt @@ -0,0 +1,94 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.indexmanagement.spi.indexstatemanagement.model + +import org.opensearch.common.Strings +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import org.opensearch.common.xcontent.LoggingDeprecationHandler +import org.opensearch.common.xcontent.NamedXContentRegistry +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.ToXContentFragment +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.common.xcontent.XContentType +import org.opensearch.indexmanagement.spi.indexstatemanagement.Validate +import java.io.ByteArrayInputStream +import java.nio.charset.StandardCharsets +import java.util.Locale + +data class ValidationResult( + val validationMessage: String, + val validationStatus: Validate.ValidationStatus +) : Writeable, ToXContentFragment { + + override fun writeTo(out: StreamOutput) { + out.writeString(validationMessage) + validationStatus.writeTo(out) + } + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + builder + .field(VALIDATION_MESSAGE, validationMessage) + .field(VALIDATION_STATUS, validationStatus.toString()) + return builder + } + + fun getMapValueString(): String { + return Strings.toString(this, false, false) + } + + companion object { + const val VALIDATE = "validate" + const val VALIDATION_MESSAGE = "validation_message" + const val VALIDATION_STATUS = "validation_status" + + fun fromStreamInput(si: StreamInput): ValidationResult { + val validationMessage: String? = si.readString() + val validationStatus: Validate.ValidationStatus? = Validate.ValidationStatus.read(si) + + return ValidationResult( + requireNotNull(validationMessage) { "$VALIDATION_MESSAGE is null" }, + requireNotNull(validationStatus) { "$VALIDATION_STATUS is null" } + ) + } + + fun fromManagedIndexMetaDataMap(map: Map): ValidationResult? { + val stepJsonString = map[VALIDATE] + return if (stepJsonString != null) { + val inputStream = ByteArrayInputStream(stepJsonString.toByteArray(StandardCharsets.UTF_8)) + val parser = XContentType.JSON.xContent().createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, inputStream) + parser.nextToken() + parse(parser) + } else { + null + } + } + + fun parse(xcp: XContentParser): ValidationResult { + var validationMessage: String? = null + var validationStatus: Validate.ValidationStatus? = null + + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = xcp.currentName() + xcp.nextToken() + + when (fieldName) { + VALIDATION_MESSAGE -> validationMessage = xcp.text() + VALIDATION_STATUS -> validationStatus = Validate.ValidationStatus.valueOf(xcp.text().uppercase(Locale.ROOT)) + } + } + + return ValidationResult( + requireNotNull(validationMessage) { "$VALIDATION_MESSAGE is null" }, + requireNotNull(validationStatus) { "$VALIDATION_STATUS is null" } + ) + } + } +} diff --git a/src/main/kotlin/org/opensearch/indexmanagement/IndexManagementPlugin.kt b/src/main/kotlin/org/opensearch/indexmanagement/IndexManagementPlugin.kt index 7ed68c9bd..cc9ba8c38 100644 --- a/src/main/kotlin/org/opensearch/indexmanagement/IndexManagementPlugin.kt +++ b/src/main/kotlin/org/opensearch/indexmanagement/IndexManagementPlugin.kt @@ -74,6 +74,7 @@ import org.opensearch.indexmanagement.indexstatemanagement.transport.action.retr import org.opensearch.indexmanagement.indexstatemanagement.transport.action.updateindexmetadata.TransportUpdateManagedIndexMetaDataAction import org.opensearch.indexmanagement.indexstatemanagement.transport.action.updateindexmetadata.UpdateManagedIndexMetaDataAction import org.opensearch.indexmanagement.indexstatemanagement.util.DEFAULT_INDEX_TYPE +import org.opensearch.indexmanagement.indexstatemanagement.validation.ActionValidation import org.opensearch.indexmanagement.indexstatemanagement.migration.ISMTemplateService import org.opensearch.indexmanagement.refreshanalyzer.RefreshSearchAnalyzerAction import org.opensearch.indexmanagement.refreshanalyzer.RestRefreshSearchAnalyzerAction @@ -187,6 +188,7 @@ class IndexManagementPlugin : JobSchedulerExtension, NetworkPlugin, ActionPlugin private val logger = LogManager.getLogger(javaClass) lateinit var indexManagementIndices: IndexManagementIndices + lateinit var actionValidation: ActionValidation lateinit var clusterService: ClusterService lateinit var indexNameExpressionResolver: IndexNameExpressionResolver lateinit var rollupInterceptor: RollupInterceptor @@ -387,6 +389,7 @@ class IndexManagementPlugin : JobSchedulerExtension, NetworkPlugin, ActionPlugin .registerConsumers() .registerClusterConfigurationProvider(skipFlag) indexManagementIndices = IndexManagementIndices(settings, client.admin().indices(), clusterService) + actionValidation = ActionValidation(settings, clusterService, jvmService) val indexStateManagementHistory = IndexStateManagementHistory( settings, @@ -408,6 +411,7 @@ class IndexManagementPlugin : JobSchedulerExtension, NetworkPlugin, ActionPlugin val managedIndexRunner = ManagedIndexRunner .registerClient(client) .registerClusterService(clusterService) + .registerValidationService(actionValidation) .registerNamedXContentRegistry(xContentRegistry) .registerScriptService(scriptService) .registerSettings(settings) @@ -436,6 +440,7 @@ class IndexManagementPlugin : JobSchedulerExtension, NetworkPlugin, ActionPlugin rollupRunner, transformRunner, indexManagementIndices, + actionValidation, managedIndexCoordinator, indexStateManagementHistory, indexMetadataProvider, @@ -458,6 +463,7 @@ class IndexManagementPlugin : JobSchedulerExtension, NetworkPlugin, ActionPlugin ManagedIndexSettings.ROLLOVER_ALIAS, ManagedIndexSettings.ROLLOVER_SKIP, ManagedIndexSettings.INDEX_STATE_MANAGEMENT_ENABLED, + ManagedIndexSettings.ACTION_VALIDATION_ENABLED, ManagedIndexSettings.METADATA_SERVICE_ENABLED, ManagedIndexSettings.AUTO_MANAGE, ManagedIndexSettings.METADATA_SERVICE_STATUS, diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/ManagedIndexRunner.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/ManagedIndexRunner.kt index d4c1c9e3f..0b0f58d64 100644 --- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/ManagedIndexRunner.kt +++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/ManagedIndexRunner.kt @@ -51,8 +51,10 @@ import org.opensearch.indexmanagement.indexstatemanagement.settings.ManagedIndex import org.opensearch.indexmanagement.indexstatemanagement.settings.ManagedIndexSettings.Companion.ALLOW_LIST_NONE import org.opensearch.indexmanagement.indexstatemanagement.settings.ManagedIndexSettings.Companion.DEFAULT_ISM_ENABLED import org.opensearch.indexmanagement.indexstatemanagement.settings.ManagedIndexSettings.Companion.DEFAULT_JOB_INTERVAL +import org.opensearch.indexmanagement.indexstatemanagement.settings.ManagedIndexSettings.Companion.DEFAULT_ACTION_VALIDATION_ENABLED import org.opensearch.indexmanagement.indexstatemanagement.settings.ManagedIndexSettings.Companion.INDEX_STATE_MANAGEMENT_ENABLED import org.opensearch.indexmanagement.indexstatemanagement.settings.ManagedIndexSettings.Companion.JOB_INTERVAL +import org.opensearch.indexmanagement.indexstatemanagement.settings.ManagedIndexSettings.Companion.ACTION_VALIDATION_ENABLED import org.opensearch.indexmanagement.indexstatemanagement.util.DEFAULT_INDEX_TYPE import org.opensearch.indexmanagement.indexstatemanagement.util.MetadataCheck import org.opensearch.indexmanagement.indexstatemanagement.util.checkMetadata @@ -74,6 +76,7 @@ import org.opensearch.indexmanagement.indexstatemanagement.util.sendNotification import org.opensearch.indexmanagement.indexstatemanagement.util.shouldBackoff import org.opensearch.indexmanagement.indexstatemanagement.util.shouldChangePolicy import org.opensearch.indexmanagement.indexstatemanagement.util.updateDisableManagedIndexRequest +import org.opensearch.indexmanagement.indexstatemanagement.validation.ActionValidation import org.opensearch.indexmanagement.opensearchapi.IndexManagementSecurityContext import org.opensearch.indexmanagement.opensearchapi.convertToMap import org.opensearch.indexmanagement.opensearchapi.parseWithType @@ -83,6 +86,7 @@ import org.opensearch.indexmanagement.opensearchapi.suspendUntil import org.opensearch.indexmanagement.opensearchapi.withClosableContext import org.opensearch.indexmanagement.spi.indexstatemanagement.Action import org.opensearch.indexmanagement.spi.indexstatemanagement.Step +import org.opensearch.indexmanagement.spi.indexstatemanagement.Validate import org.opensearch.indexmanagement.spi.indexstatemanagement.model.ActionMetaData import org.opensearch.indexmanagement.spi.indexstatemanagement.model.ManagedIndexMetaData import org.opensearch.indexmanagement.spi.indexstatemanagement.model.PolicyRetryInfoMetaData @@ -114,12 +118,14 @@ object ManagedIndexRunner : private lateinit var scriptService: ScriptService private lateinit var settings: Settings private lateinit var imIndices: IndexManagementIndices + lateinit var actionValidation: ActionValidation private lateinit var ismHistory: IndexStateManagementHistory private lateinit var skipExecFlag: SkipExecution private lateinit var threadPool: ThreadPool private lateinit var extensionStatusChecker: ExtensionStatusChecker private lateinit var indexMetadataProvider: IndexMetadataProvider private var indexStateManagementEnabled: Boolean = DEFAULT_ISM_ENABLED + private var validationServiceEnabled: Boolean = DEFAULT_ACTION_VALIDATION_ENABLED @Suppress("MagicNumber") private val savePolicyRetryPolicy = BackoffPolicy.exponentialBackoff(TimeValue.timeValueMillis(250), 3) @Suppress("MagicNumber") @@ -166,6 +172,11 @@ object ManagedIndexRunner : indexStateManagementEnabled = it } + validationServiceEnabled = ACTION_VALIDATION_ENABLED.get(settings) + clusterService.clusterSettings.addSettingsUpdateConsumer(ACTION_VALIDATION_ENABLED) { + validationServiceEnabled = it + } + allowList = ALLOW_LIST.get(settings) clusterService.clusterSettings.addSettingsUpdateConsumer(ALLOW_LIST) { allowList = it @@ -179,6 +190,11 @@ object ManagedIndexRunner : return this } + fun registerValidationService(actionValidation: ActionValidation): ManagedIndexRunner { + this.actionValidation = actionValidation + return this + } + fun registerHistoryIndex(ismHistory: IndexStateManagementHistory): ManagedIndexRunner { this.ismHistory = ismHistory return this @@ -385,8 +401,23 @@ object ManagedIndexRunner : val startingManagedIndexMetaData = managedIndexMetaData.getStartingManagedIndexMetaData(state, action, step) val updateResult = updateManagedIndexMetaData(startingManagedIndexMetaData) - @Suppress("ComplexCondition") + @Suppress("ComplexCondition", "MaxLineLength") if (updateResult.metadataSaved && state != null && action != null && step != null && currentActionMetaData != null) { + if (validationServiceEnabled) { + val validationResult = actionValidation.validate(action.type, stepContext.metadata.index) + if (validationResult.validationStatus == Validate.ValidationStatus.RE_VALIDATING) { + logger.warn("Validation Status is: RE_VALIDATING. The action is {}, state is {}, step is {}.\", action.type, state.name, step.name") + publishErrorNotification(policy, managedIndexMetaData) + return + } + if (validationResult.validationStatus == Validate.ValidationStatus.FAILED) { + logger.warn("Validation Status is: FAILED. The action is {}, state is {}, step is {}.", action.type, state.name, step.name) + publishErrorNotification(policy, managedIndexMetaData) + disableManagedIndexConfig(managedIndexConfig) + return + } + } + // Step null check is done in getStartingManagedIndexMetaData withClosableContext( IndexManagementSecurityContext( @@ -671,7 +702,6 @@ object ManagedIndexRunner : } catch (e: Exception) { logger.error("Failed to save ManagedIndexMetaData for [index=${managedIndexMetaData.index}]", e) } - return result } diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/resthandler/RestExplainAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/resthandler/RestExplainAction.kt index 65e3287b5..5852e3302 100644 --- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/resthandler/RestExplainAction.kt +++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/resthandler/RestExplainAction.kt @@ -13,7 +13,9 @@ import org.opensearch.indexmanagement.IndexManagementPlugin.Companion.ISM_BASE_U import org.opensearch.indexmanagement.IndexManagementPlugin.Companion.LEGACY_ISM_BASE_URI import org.opensearch.indexmanagement.indexstatemanagement.transport.action.explain.ExplainAction import org.opensearch.indexmanagement.indexstatemanagement.transport.action.explain.ExplainRequest +import org.opensearch.indexmanagement.indexstatemanagement.util.DEFAULT_EXPLAIN_VALIDATE_ACTION import org.opensearch.indexmanagement.indexstatemanagement.util.DEFAULT_EXPLAIN_SHOW_POLICY +import org.opensearch.indexmanagement.indexstatemanagement.util.SHOW_VALIDATE_ACTION import org.opensearch.indexmanagement.indexstatemanagement.util.DEFAULT_INDEX_TYPE import org.opensearch.indexmanagement.indexstatemanagement.util.DEFAULT_JOB_SORT_FIELD import org.opensearch.indexmanagement.indexstatemanagement.util.SHOW_POLICY_QUERY_PARAM @@ -77,6 +79,7 @@ class RestExplainAction : BaseRestHandler() { clusterManagerTimeout, searchParams, request.paramAsBoolean(SHOW_POLICY_QUERY_PARAM, DEFAULT_EXPLAIN_SHOW_POLICY), + request.paramAsBoolean(SHOW_VALIDATE_ACTION, DEFAULT_EXPLAIN_VALIDATE_ACTION), indexType ) diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/settings/ManagedIndexSettings.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/settings/ManagedIndexSettings.kt index ed60fc83b..56bda817e 100644 --- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/settings/ManagedIndexSettings.kt +++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/settings/ManagedIndexSettings.kt @@ -14,6 +14,7 @@ import java.util.function.Function class ManagedIndexSettings { companion object { const val DEFAULT_ISM_ENABLED = true + const val DEFAULT_ACTION_VALIDATION_ENABLED = false const val DEFAULT_TEMPLATE_MIGRATION_TIMESTAMP = 0L const val DEFAULT_JOB_INTERVAL = 5 const val DEFAULT_JITTER = 0.6 @@ -28,6 +29,13 @@ class ManagedIndexSettings { Setting.Property.Dynamic ) + val ACTION_VALIDATION_ENABLED: Setting = Setting.boolSetting( + "plugins.index_state_management.action_validation.enabled", + DEFAULT_ACTION_VALIDATION_ENABLED, + Setting.Property.NodeScope, + Setting.Property.Dynamic + ) + // 0: migration is going on // 1: migration succeed // -1: migration failed diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/explain/ExplainRequest.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/explain/ExplainRequest.kt index f0969643c..cebb73a0a 100644 --- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/explain/ExplainRequest.kt +++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/explain/ExplainRequest.kt @@ -22,14 +22,17 @@ class ExplainRequest : ActionRequest { val clusterManagerTimeout: TimeValue val searchParams: SearchParams val showPolicy: Boolean + val validateAction: Boolean val indexType: String + @Suppress("LongParameterList") constructor( indices: List, local: Boolean, clusterManagerTimeout: TimeValue, searchParams: SearchParams, showPolicy: Boolean, + validateAction: Boolean, indexType: String ) : super() { this.indices = indices @@ -37,6 +40,7 @@ class ExplainRequest : ActionRequest { this.clusterManagerTimeout = clusterManagerTimeout this.searchParams = searchParams this.showPolicy = showPolicy + this.validateAction = validateAction this.indexType = indexType } @@ -47,6 +51,7 @@ class ExplainRequest : ActionRequest { clusterManagerTimeout = sin.readTimeValue(), searchParams = SearchParams(sin), showPolicy = sin.readBoolean(), + validateAction = sin.readBoolean(), indexType = sin.readString() ) @@ -68,6 +73,7 @@ class ExplainRequest : ActionRequest { out.writeTimeValue(clusterManagerTimeout) searchParams.writeTo(out) out.writeBoolean(showPolicy) + out.writeBoolean(validateAction) out.writeString(indexType) } diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/explain/ExplainResponse.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/explain/ExplainResponse.kt index cda65f548..11a1585b3 100644 --- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/explain/ExplainResponse.kt +++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/explain/ExplainResponse.kt @@ -12,11 +12,13 @@ import org.opensearch.common.xcontent.ToXContent import org.opensearch.common.xcontent.ToXContentObject import org.opensearch.common.xcontent.XContentBuilder import org.opensearch.indexmanagement.indexstatemanagement.model.Policy +import org.opensearch.indexmanagement.indexstatemanagement.opensearchapi.addObject import org.opensearch.indexmanagement.indexstatemanagement.settings.LegacyOpenDistroManagedIndexSettings import org.opensearch.indexmanagement.indexstatemanagement.settings.ManagedIndexSettings import org.opensearch.indexmanagement.indexstatemanagement.util.TOTAL_MANAGED_INDICES import org.opensearch.indexmanagement.indexstatemanagement.util.XCONTENT_WITHOUT_TYPE_AND_USER import org.opensearch.indexmanagement.spi.indexstatemanagement.model.ManagedIndexMetaData +import org.opensearch.indexmanagement.spi.indexstatemanagement.model.ValidationResult import java.io.IOException open class ExplainResponse : ActionResponse, ToXContentObject { @@ -28,14 +30,17 @@ open class ExplainResponse : ActionResponse, ToXContentObject { val totalManagedIndices: Int val enabledState: Map val policies: Map + val validationResults: List + @Suppress("LongParameterList") constructor( indexNames: List, indexPolicyIDs: List, indexMetadatas: List, totalManagedIndices: Int, enabledState: Map, - policies: Map + policies: Map, + validationResults: List ) : super() { this.indexNames = indexNames this.indexPolicyIDs = indexPolicyIDs @@ -43,6 +48,7 @@ open class ExplainResponse : ActionResponse, ToXContentObject { this.totalManagedIndices = totalManagedIndices this.enabledState = enabledState this.policies = policies + this.validationResults = validationResults } @Throws(IOException::class) @@ -52,7 +58,8 @@ open class ExplainResponse : ActionResponse, ToXContentObject { indexMetadatas = sin.readList { ManagedIndexMetaData.fromStreamInput(it) }, totalManagedIndices = sin.readInt(), enabledState = sin.readMap() as Map, - policies = sin.readMap(StreamInput::readString, ::Policy) + policies = sin.readMap(StreamInput::readString, ::Policy), + validationResults = sin.readList { ValidationResult.fromStreamInput(it) } ) @Throws(IOException::class) @@ -67,6 +74,7 @@ open class ExplainResponse : ActionResponse, ToXContentObject { { _out, key -> _out.writeString(key) }, { _out, policy -> policy.writeTo(_out) } ) + out.writeCollection(validationResults) } override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { @@ -78,6 +86,9 @@ open class ExplainResponse : ActionResponse, ToXContentObject { indexMetadatas[ind]?.toXContent(builder, ToXContent.EMPTY_PARAMS) builder.field("enabled", enabledState[name]) policies[name]?.let { builder.field(Policy.POLICY_TYPE, it, XCONTENT_WITHOUT_TYPE_AND_USER) } + if (validationResults[ind] != null) { + builder.addObject(ValidationResult.VALIDATE, validationResults[ind], params, true) + } builder.endObject() } builder.field(TOTAL_MANAGED_INDICES, totalManagedIndices) diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/explain/TransportExplainAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/explain/TransportExplainAction.kt index 0948be960..ac7caab1f 100644 --- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/explain/TransportExplainAction.kt +++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/explain/TransportExplainAction.kt @@ -42,6 +42,7 @@ import org.opensearch.indexmanagement.indexstatemanagement.ManagedIndexCoordinat import org.opensearch.indexmanagement.indexstatemanagement.model.ManagedIndexConfig import org.opensearch.indexmanagement.indexstatemanagement.model.Policy import org.opensearch.indexmanagement.common.model.rest.SearchParams +import org.opensearch.indexmanagement.indexstatemanagement.ManagedIndexRunner.actionValidation import org.opensearch.indexmanagement.indexstatemanagement.opensearchapi.getManagedIndexMetadata import org.opensearch.indexmanagement.indexstatemanagement.transport.action.managedIndex.ManagedIndexAction import org.opensearch.indexmanagement.indexstatemanagement.transport.action.managedIndex.ManagedIndexRequest @@ -55,6 +56,7 @@ import org.opensearch.indexmanagement.opensearchapi.parseWithType import org.opensearch.indexmanagement.opensearchapi.suspendUntil import org.opensearch.indexmanagement.spi.indexstatemanagement.model.ISMIndexMetadata import org.opensearch.indexmanagement.spi.indexstatemanagement.model.ManagedIndexMetaData +import org.opensearch.indexmanagement.spi.indexstatemanagement.model.ValidationResult import org.opensearch.indexmanagement.util.SecurityUtils.Companion.buildUser import org.opensearch.search.SearchHit import org.opensearch.search.builder.SearchSourceBuilder @@ -94,6 +96,7 @@ class TransportExplainAction @Inject constructor( * special case: when user explicitly query for an un-managed index * return this index with its policy id shown 'null' meaning it's not managed */ + @Suppress("LongMethod") inner class ExplainHandler( private val client: NodeClient, private val actionListener: ActionListener, @@ -103,6 +106,7 @@ class TransportExplainAction @Inject constructor( private val indices: List = request.indices private val explainAll: Boolean = indices.isEmpty() private val showPolicy: Boolean = request.showPolicy + private val validateAction: Boolean = request.validateAction // Map of indexName to index metadata got from config index job which is fake/not a real full metadata document private val managedIndicesMetaDataMap: MutableMap = mutableMapOf() @@ -115,8 +119,10 @@ class TransportExplainAction @Inject constructor( private val enabledState: MutableMap = mutableMapOf() private val indexPolicyIDs = mutableListOf() private val indexMetadatas = mutableListOf() + private val validationResults = mutableListOf() private var totalManagedIndices = 0 private val appliedPolicies: MutableMap = mutableMapOf() + private val policiesforValidation: MutableMap = mutableMapOf() @Suppress("SpreadOperator", "NestedBlockDepth") fun start() { @@ -196,6 +202,9 @@ class TransportExplainAction @Inject constructor( if (showPolicy) { managedIndex.policy?.let { appliedPolicies[managedIndex.index] = it } } + if (validateAction) { + managedIndex.policy?.let { policiesforValidation[managedIndex.index] = it } + } } // explain all only return managed indices @@ -204,7 +213,10 @@ class TransportExplainAction @Inject constructor( // edge case: if specify query param pagination size to be 0 // we still show total managed indices indexNames.clear() - sendResponse(indexNames, indexMetadatas, indexPolicyIDs, enabledState, totalManagedIndices, appliedPolicies) + sendResponse( + indexNames, indexMetadatas, indexPolicyIDs, enabledState, + totalManagedIndices, appliedPolicies, validationResults + ) return } else { // Clear and add the managedIndices from the response to preserve the sort order and size @@ -230,7 +242,10 @@ class TransportExplainAction @Inject constructor( return } indexNames.clear() - sendResponse(indexNames, indexMetadatas, indexPolicyIDs, enabledState, totalManagedIndices, appliedPolicies) + sendResponse( + indexNames, indexMetadatas, indexPolicyIDs, + enabledState, totalManagedIndices, appliedPolicies, validationResults + ) return } actionListener.onFailure(ExceptionsHelper.unwrapCause(t) as Exception) @@ -322,12 +337,29 @@ class TransportExplainAction @Inject constructor( info?.let { managedIndexMetadata = clusterStateMetadata?.copy(info = it) } } } + if (validateAction) { + var validationResult = actionValidation.validate("nothing", indexName) + val policy = policiesforValidation[indexName] + if (policy != null && managedIndexMetadata != null) { + val state = policy.getStateToExecute(managedIndexMetadata!!) + val action = state?.getActionToExecute(managedIndexMetadata!!, indexMetadataProvider) + var actionName = action?.type + if (actionName == null) { + actionName = "nothing" + } + validationResult = actionValidation.validate(actionName, indexName) + } + validationResults.add(validationResult) + } else { + validationResults.add(null) + } + indexMetadatas.add(managedIndexMetadata) } managedIndicesMetaDataMap.clear() if (user == null || indexNames.isEmpty()) { - sendResponse(indexNames, indexMetadatas, indexPolicyIDs, enabledState, totalManagedIndices, appliedPolicies) + sendResponse(indexNames, indexMetadatas, indexPolicyIDs, enabledState, totalManagedIndices, appliedPolicies, validationResults) } else { filterAndSendResponse(threadContext) } @@ -337,6 +369,7 @@ class TransportExplainAction @Inject constructor( threadContext.restore() val filteredIndices = mutableListOf() val filteredMetadata = mutableListOf() + val filteredValidationResult = mutableListOf() val filteredPolicies = mutableListOf() val enabledStatus = mutableMapOf() val filteredAppliedPolicies = mutableMapOf() @@ -350,6 +383,7 @@ class TransportExplainAction @Inject constructor( filteredIndices.add(indexNames[i]) filteredMetadata.add(indexMetadatas[i]) filteredPolicies.add(indexPolicyIDs[i]) + validationResults[i]?.let { filteredValidationResult.add(it) } enabledState[indexNames[i]]?.let { enabledStatus[indexNames[i]] = it } appliedPolicies[indexNames[i]]?.let { filteredAppliedPolicies[indexNames[i]] = it } } catch (e: OpenSearchSecurityException) { @@ -360,7 +394,7 @@ class TransportExplainAction @Inject constructor( } sendResponse( filteredIndices, filteredMetadata, filteredPolicies, enabledStatus, - totalManagedIndices, filteredAppliedPolicies + totalManagedIndices, filteredAppliedPolicies, filteredValidationResult ) } } @@ -372,9 +406,10 @@ class TransportExplainAction @Inject constructor( policyIDs: List, enabledStatus: Map, totalIndices: Int, - policies: Map + policies: Map, + validationResult: List, ) { - actionListener.onResponse(ExplainResponse(indices, policyIDs, metadata, totalIndices, enabledStatus, policies)) + actionListener.onResponse(ExplainResponse(indices, policyIDs, metadata, totalIndices, enabledStatus, policies, validationResult)) } @Suppress("ReturnCount") diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/util/RestHandlerUtils.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/util/RestHandlerUtils.kt index 4709461ec..fe48a7a70 100644 --- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/util/RestHandlerUtils.kt +++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/util/RestHandlerUtils.kt @@ -54,6 +54,9 @@ const val DEFAULT_POLICY_SORT_FIELD = "policy.policy_id.keyword" const val SHOW_POLICY_QUERY_PARAM = "show_policy" const val DEFAULT_EXPLAIN_SHOW_POLICY = false +const val SHOW_VALIDATE_ACTION = "validate_action" +const val DEFAULT_EXPLAIN_VALIDATE_ACTION = false + const val INDEX_HIDDEN = "index.hidden" const val INDEX_NUMBER_OF_SHARDS = "index.number_of_shards" const val INDEX_NUMBER_OF_REPLICAS = "index.number_of_replicas" diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ActionValidation.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ActionValidation.kt new file mode 100644 index 000000000..dcd0410ab --- /dev/null +++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ActionValidation.kt @@ -0,0 +1,38 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.indexmanagement.indexstatemanagement.validation + +import org.opensearch.cluster.service.ClusterService +import org.opensearch.common.settings.Settings +import org.opensearch.indexmanagement.util.OpenForTesting +import org.opensearch.indexmanagement.spi.indexstatemanagement.model.ValidationResult +import org.opensearch.monitor.jvm.JvmService + +@OpenForTesting +class ActionValidation( + val settings: Settings, + val clusterService: ClusterService, + val jvmService: JvmService +) { + + fun validate(actionName: String, indexName: String): ValidationResult { + // map action to validation class + val validation = when (actionName) { + "rollover" -> ValidateRollover(settings, clusterService, jvmService).execute(indexName) + "delete" -> ValidateDelete(settings, clusterService, jvmService).execute(indexName) + "force_merge" -> ValidateForceMerge(settings, clusterService, jvmService).execute(indexName) + "open" -> ValidateOpen(settings, clusterService, jvmService).execute(indexName) + "read_only" -> ValidateReadOnly(settings, clusterService, jvmService).execute(indexName) + "read_write" -> ValidateReadWrite(settings, clusterService, jvmService).execute(indexName) + "replica_count" -> ValidateReplicaCount(settings, clusterService, jvmService).execute(indexName) + else -> { + // temporary call until all actions are mapped + ValidateNothing(settings, clusterService, jvmService).execute(indexName) + } + } + return ValidationResult(validation.validationMessage.toString(), validation.validationStatus) + } +} diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateDelete.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateDelete.kt new file mode 100644 index 000000000..34d8d7c3f --- /dev/null +++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateDelete.kt @@ -0,0 +1,108 @@ +/* + * 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.validateIndexOrAliasName +import org.opensearch.cluster.service.ClusterService +import org.opensearch.common.settings.Settings +import org.opensearch.indexmanagement.indexstatemanagement.opensearchapi.getRolloverAlias +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 ValidateDelete( + 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 delete action + if (!deleteIndexExists(indexName) || !validIndex(indexName)) { + return this + } + val (rolloverTarget, isDataStream) = getRolloverTargetOrUpdateInfo(indexName) + if (rolloverTarget != null && !notWriteIndexForDataStream(rolloverTarget, indexName)) { + return this // can't be deleted if it's write index + } + validationMessage = getValidationPassedMessage(indexName) + return this + } + + private fun getRolloverTargetOrUpdateInfo(indexName: String): Pair { + val metadata = clusterService.state().metadata() + val indexAbstraction = metadata.indicesLookup[indexName] + val isDataStreamIndex = indexAbstraction?.parentDataStream != null + val rolloverTarget = when { + isDataStreamIndex -> indexAbstraction?.parentDataStream?.name + else -> metadata.index(indexName).getRolloverAlias() + } + return rolloverTarget to isDataStreamIndex + } + + private fun notWriteIndexForDataStream(alias: String?, indexName: String): Boolean { + val metadata = clusterService.state().metadata + val indexAlias = metadata.index(indexName)?.aliases?.get(alias) + + val isWriteIndex = indexAlias?.writeIndex() // this could be null + if (isWriteIndex == true) { + val aliasIndices = metadata.indicesLookup[alias]?.indices?.map { it.index } + logger.debug("Alias $alias contains indices $aliasIndices") + if (aliasIndices != null) { + val message = getFailedIsWriteIndexMessage(indexName) + logger.warn(message) + validationStatus = ValidationStatus.RE_VALIDATING + validationMessage = message + return false + } + } + return true + } + + // checks if index exists + private fun deleteIndexExists(indexName: String): Boolean { + val indexExists = clusterService.state().metadata.indices.containsKey(indexName) + if (!indexExists) { + val message = getNoIndexMessage(indexName) + logger.warn(message) + validationStatus = ValidationStatus.RE_VALIDATING + validationMessage = message + return false + } + return true + } + + // checks if index is valid + 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 { + validateIndexOrAliasName(indexName, exceptionGenerator) + } catch (e: Exception) { + val message = getIndexNotValidMessage(indexName) + logger.warn(message) + validationStatus = ValidationStatus.RE_VALIDATING + validationMessage = message + } + return true + } + + @Suppress("TooManyFunctions") + companion object { + const val name = "validate_delete" + fun getNoIndexMessage(index: String) = "no such index [index=$index]" + fun getIndexNotValidMessage(index: String) = "delete index [index=$index] not valid" + fun getFailedIsWriteIndexMessage(index: String) = "Index [index=$index] is the write index for data stream and cannot be deleted" + fun getValidationPassedMessage(index: String) = "Delete validation passed for [index=$index]" + } +} diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateForceMerge.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateForceMerge.kt new file mode 100644 index 000000000..14a8328c7 --- /dev/null +++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateForceMerge.kt @@ -0,0 +1,52 @@ +/* + * 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.service.ClusterService +import org.opensearch.common.settings.Settings +import org.opensearch.indexmanagement.util.OpenForTesting +import org.opensearch.indexmanagement.spi.indexstatemanagement.Validate +import org.opensearch.indexmanagement.transform.settings.TransformSettings +import org.opensearch.monitor.jvm.JvmService + +@OpenForTesting +class ValidateForceMerge( + 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 (!dataSizeNotLarge(indexName)) { + return this + } + validationMessage = getValidationPassedMessage(indexName) + return this + } + + fun dataSizeNotLarge(indexName: String): Boolean { + val circuitBreakerEnabled = TransformSettings.TRANSFORM_CIRCUIT_BREAKER_ENABLED.get(settings) + val circuitBreakerJvmThreshold = TransformSettings.TRANSFORM_CIRCUIT_BREAKER_JVM_THRESHOLD.get(settings) + if (circuitBreakerEnabled && jvmService.stats().mem.heapUsedPercent > circuitBreakerJvmThreshold) { + val message = getFailedDataTooLargeMessage(indexName) + logger.warn(message) + validationStatus = ValidationStatus.RE_VALIDATING + return false + } + return true + } + + @Suppress("TooManyFunctions") + companion object { + const val name = "validate_force_merge" + fun getFailedDataTooLargeMessage(index: String) = "Data too large and is over the allowed limit for index [index=$index]" + fun getValidationPassedMessage(index: String) = "Force merge validation passed for [index=$index]" + } +} diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateNothing.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateNothing.kt new file mode 100644 index 000000000..596c46edd --- /dev/null +++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateNothing.kt @@ -0,0 +1,25 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.indexmanagement.indexstatemanagement.validation + +import org.opensearch.cluster.service.ClusterService +import org.opensearch.common.settings.Settings +import org.opensearch.indexmanagement.util.OpenForTesting +import org.opensearch.indexmanagement.spi.indexstatemanagement.Validate +import org.opensearch.monitor.jvm.JvmService + +@OpenForTesting +class ValidateNothing( + settings: Settings, + clusterService: ClusterService, + jvmService: JvmService +) : Validate(settings, clusterService, jvmService) { + + // skips validation + override fun execute(indexName: String): Validate { + return this + } +} diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateOpen.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateOpen.kt new file mode 100644 index 000000000..3c9668fec --- /dev/null +++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateOpen.kt @@ -0,0 +1,72 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * 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.service.ClusterService +import org.opensearch.common.settings.Settings +import org.opensearch.indexmanagement.spi.indexstatemanagement.Validate +import org.opensearch.indexmanagement.util.OpenForTesting +import org.opensearch.monitor.jvm.JvmService + +@OpenForTesting +class ValidateOpen( + 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 open action + if (hasReadOnlyAllowDeleteBlock(indexName)) { + return this + } + validationMessage = getValidationPassedMessage(indexName) + return this + } + + fun hasReadOnlyAllowDeleteBlock(indexName: String): Boolean { + val readOnlyAllowDeleteBlock = settings.get(ValidateReadOnly.settingKey) + if (!readOnlyAllowDeleteBlock.isNullOrEmpty()) { + val message = getReadOnlyAllowDeleteBlockMessage(indexName) + logger.warn(message) + validationStatus = ValidationStatus.RE_VALIDATING + return true + } + return false + } + + // TODO: evaluate incoming shard number changes dynamically + fun maxNumberOfShardsExceeded(indexName: String): Boolean { + val totalShards = clusterService.state().metadata.totalNumberOfShards + val openShards = clusterService.state().metadata.totalOpenIndexShards + val numberOfShards = clusterService.state().metadata.index(indexName).numberOfShards + val replicaCount = clusterService.state().metadata.index(indexName).numberOfReplicas + if (replicaCount * numberOfShards > (totalShards - openShards)) { + val message = getMaxNumberOfShardsExceededMessage(indexName) + logger.warn(message) + validationStatus = ValidationStatus.RE_VALIDATING + return true + } + return false + } + + @Suppress("TooManyFunctions") + companion object { + const val name = "validate_open" + fun getReadOnlyAllowDeleteBlockMessage(index: String) = "read_only_allow_delete block is not null for index [index=$index]" + fun getMaxNumberOfShardsExceededMessage(index: String) = "Maximum number of shards exceeded for index [index=$index]" + fun getValidationPassedMessage(index: String) = "Open action validation passed for [index=$index]" + } +} diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateReadOnly.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateReadOnly.kt new file mode 100644 index 000000000..ab2d02abd --- /dev/null +++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateReadOnly.kt @@ -0,0 +1,71 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * 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.service.ClusterService +import org.opensearch.common.settings.Settings +import org.opensearch.indexmanagement.spi.indexstatemanagement.Validate +import org.opensearch.indexmanagement.transform.settings.TransformSettings +import org.opensearch.indexmanagement.util.OpenForTesting +import org.opensearch.monitor.jvm.JvmService + +@OpenForTesting +class ValidateReadOnly( + 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 read_only action + if (dataSizeTooLarge(indexName) || hasReadOnlyAllowDeleteBlock(indexName)) { + return this + } + validationMessage = getValidationPassedMessage(indexName) + return this + } + + fun hasReadOnlyAllowDeleteBlock(indexName: String): Boolean { + val readOnlyAllowDeleteBlock = settings.get(settingKey) + if (!readOnlyAllowDeleteBlock.isNullOrEmpty()) { + val message = getReadOnlyAllowDeleteBlockMessage(indexName) + logger.warn(message) + validationStatus = ValidationStatus.RE_VALIDATING + return true + } + return false + } + + fun dataSizeTooLarge(indexName: String): Boolean { + val circuitBreakerEnabled = TransformSettings.TRANSFORM_CIRCUIT_BREAKER_ENABLED.get(settings) + val circuitBreakerJvmThreshold = TransformSettings.TRANSFORM_CIRCUIT_BREAKER_JVM_THRESHOLD.get(settings) + if (circuitBreakerEnabled && jvmService.stats().mem.heapUsedPercent > circuitBreakerJvmThreshold) { + val message = getFailedDataTooLargeMessage(indexName) + logger.warn(message) + validationStatus = ValidationStatus.RE_VALIDATING + return true + } + return false + } + + @Suppress("TooManyFunctions") + companion object { + const val name = "validate_read_only" + const val settingKey = "read_only_allow_delete" + fun getReadOnlyAllowDeleteBlockMessage(index: String) = "read_only_allow_delete block is not null for index [index=$index]" + fun getFailedDataTooLargeMessage(index: String) = "Data too large and is over the allowed limit for index [index=$index]" + fun getValidationPassedMessage(index: String) = "validate_read_only action validation passed for [index=$index]" + } +} diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateReadWrite.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateReadWrite.kt new file mode 100644 index 000000000..78521bb56 --- /dev/null +++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateReadWrite.kt @@ -0,0 +1,57 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * 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.service.ClusterService +import org.opensearch.common.settings.Settings +import org.opensearch.indexmanagement.spi.indexstatemanagement.Validate +import org.opensearch.indexmanagement.util.OpenForTesting +import org.opensearch.monitor.jvm.JvmService + +@OpenForTesting +class ValidateReadWrite( + 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 read_write action + if (hasReadOnlyAllowDeleteBlock(indexName)) { + return this + } + validationMessage = getValidationPassedMessage(indexName) + return this + } + + fun hasReadOnlyAllowDeleteBlock(indexName: String): Boolean { + val readOnlyAllowDeleteBlock = settings.get(settingKey) + if (!readOnlyAllowDeleteBlock.isNullOrEmpty()) { + val message = getReadOnlyAllowDeleteBlockMessage(indexName) + logger.warn(message) + validationStatus = ValidationStatus.RE_VALIDATING + return true + } + return false + } + + @Suppress("TooManyFunctions") + companion object { + const val name = "validate_read_write" + const val settingKey = "read_only_allow_delete" + fun getReadOnlyAllowDeleteBlockMessage(index: String) = "read_only_allow_delete block is not null for index [index=$index]" + fun getValidationPassedMessage(index: String) = "read_write validation passed for [index=$index]" + } +} diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateReplicaCount.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateReplicaCount.kt new file mode 100644 index 000000000..0e9410248 --- /dev/null +++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateReplicaCount.kt @@ -0,0 +1,74 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * 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.service.ClusterService +import org.opensearch.common.settings.Settings +import org.opensearch.indexmanagement.spi.indexstatemanagement.Validate +import org.opensearch.indexmanagement.transform.settings.TransformSettings +import org.opensearch.indexmanagement.util.OpenForTesting +import org.opensearch.monitor.jvm.JvmService + +@OpenForTesting +class ValidateReplicaCount( + 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 set replica count to the index + if (dataSizeTooLarge(indexName)) { + return this + } + validationMessage = getValidationPassedMessage(indexName) + return this + } + + // TODO: evaluate incoming shard number changes dynamically + fun dataSizeTooLarge(indexName: String): Boolean { + val circuitBreakerEnabled = TransformSettings.TRANSFORM_CIRCUIT_BREAKER_ENABLED.get(settings) + val circuitBreakerJvmThreshold = TransformSettings.TRANSFORM_CIRCUIT_BREAKER_JVM_THRESHOLD.get(settings) + if (circuitBreakerEnabled && jvmService.stats().mem.heapUsedPercent > circuitBreakerJvmThreshold) { + val message = getFailedDataTooLargeMessage(indexName) + logger.warn(message) + validationStatus = ValidationStatus.RE_VALIDATING + return true + } + return false + } + + fun maxNumberOfShardsExceeded(indexName: String): Boolean { + val totalShards = clusterService.state().metadata.totalNumberOfShards + val openShards = clusterService.state().metadata.totalOpenIndexShards + val numberOfShards = clusterService.state().metadata.index(indexName).numberOfShards + val replicaCount = clusterService.state().metadata.index(indexName).numberOfReplicas + if (replicaCount * numberOfShards > (totalShards - openShards)) { + val message = getMaxNumberOfShardsExceededMessage(indexName) + logger.warn(message) + validationStatus = ValidationStatus.RE_VALIDATING + return true + } + return false + } + + @Suppress("TooManyFunctions") + companion object { + const val name = "validate_replica_count" + fun getFailedDataTooLargeMessage(index: String) = "Data too large and is over the allowed limit for index [index=$index]" + fun getMaxNumberOfShardsExceededMessage(index: String) = "Maximum number of shards exceeded for index [index=$index]" + fun getValidationPassedMessage(index: String) = "Replica Count validation passed for [index=$index]" + } +} diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateRollover.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateRollover.kt new file mode 100644 index 000000000..aaa0ea4cb --- /dev/null +++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateRollover.kt @@ -0,0 +1,128 @@ +/* + * 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.service.ClusterService +import org.opensearch.common.settings.Settings +import org.opensearch.indexmanagement.indexstatemanagement.opensearchapi.getRolloverAlias +import org.opensearch.indexmanagement.indexstatemanagement.opensearchapi.getRolloverSkip +import org.opensearch.indexmanagement.util.OpenForTesting +import org.opensearch.indexmanagement.spi.indexstatemanagement.Validate +import org.opensearch.monitor.jvm.JvmService + +@OpenForTesting +class ValidateRollover( + settings: Settings, + clusterService: ClusterService, + jvmService: JvmService +) : Validate(settings, clusterService, jvmService) { + + private val logger = LogManager.getLogger(javaClass) + + // returns a Validate object with updated validation and step status + @Suppress("ReturnSuppressCount", "ReturnCount") + override fun execute(indexName: String): Validate { + val (rolloverTarget, isDataStream) = getRolloverTargetOrUpdateInfo(indexName) + rolloverTarget ?: return this + + if (skipRollover(indexName) || alreadyRolledOver(rolloverTarget, indexName)) return this + + if (!isDataStream) { + if (!hasAlias(rolloverTarget, indexName) || !isWriteIndex(rolloverTarget, indexName) + ) { + return this + } + } + validationMessage = getValidationPassedMessage(indexName) + return this + } + + private fun skipRollover(indexName: String): Boolean { + val skipRollover = clusterService.state().metadata.index(indexName).getRolloverSkip() + if (skipRollover) { + validationStatus = ValidationStatus.PASSED + validationMessage = getSkipRolloverMessage(indexName) + return true + } + return false + } + + private fun alreadyRolledOver(alias: String?, indexName: String): Boolean { + if (clusterService.state().metadata.index(indexName).rolloverInfos.containsKey(alias)) { + validationStatus = ValidationStatus.PASSED + validationMessage = getAlreadyRolledOverMessage(indexName, alias) + return true + } + return false + } + + private fun hasAlias(alias: String?, indexName: String): Boolean { + val metadata = clusterService.state().metadata + val indexAlias = metadata.index(indexName)?.aliases?.get(alias) + + logger.debug("Index $indexName has aliases $indexAlias") + if (indexAlias == null) { + val message = getMissingAliasMessage(indexName) + logger.warn(message) + validationStatus = ValidationStatus.RE_VALIDATING + validationMessage = message + return false + } + return true + } + + private fun isWriteIndex(alias: String?, indexName: String): Boolean { + val metadata = clusterService.state().metadata + val indexAlias = metadata.index(indexName)?.aliases?.get(alias) + + val isWriteIndex = indexAlias?.writeIndex() + if (isWriteIndex != true) { + val aliasIndices = metadata.indicesLookup[alias]?.indices?.map { it.index } + logger.debug("Alias $alias contains indices $aliasIndices") + if (aliasIndices != null && aliasIndices.size > 1) { + val message = getFailedWriteIndexMessage(indexName) + logger.warn(message) + validationStatus = ValidationStatus.RE_VALIDATING + validationMessage = message + return false + } + } + return true + } + + private fun getRolloverTargetOrUpdateInfo(indexName: String): Pair { + val metadata = clusterService.state().metadata() + val indexAbstraction = metadata.indicesLookup[indexName] + val isDataStreamIndex = indexAbstraction?.parentDataStream != null + + val rolloverTarget = when { + isDataStreamIndex -> indexAbstraction?.parentDataStream?.name + else -> metadata.index(indexName).getRolloverAlias() + } + + if (rolloverTarget == null) { + val message = getFailedNoValidAliasMessage(indexName) + logger.warn(message) + validationStatus = ValidationStatus.RE_VALIDATING + validationMessage = message + } + + return rolloverTarget to isDataStreamIndex + } + + @Suppress("TooManyFunctions") + companion object { + const val name = "validate_rollover" + fun getFailedWriteIndexMessage(index: String) = "Not the write index when rollover [index=$index]" + fun getMissingAliasMessage(index: String) = "Missing alias when rollover [index=$index]" + fun getFailedNoValidAliasMessage(index: String) = "Missing rollover_alias index setting [index=$index]" + fun getAlreadyRolledOverMessage(index: String, alias: String?) = + "This index has already been rolled over using this alias, treating as a success [index=$index, alias=$alias]" + fun getSkipRolloverMessage(index: String) = "Skipped rollover action for [index=$index]" + fun getValidationPassedMessage(index: String) = "Rollover validation passed for [index=$index]" + } +} diff --git a/src/test/kotlin/org/opensearch/indexmanagement/IndexManagementIndicesIT.kt b/src/test/kotlin/org/opensearch/indexmanagement/IndexManagementIndicesIT.kt index 22e9f9d03..34e68cfa9 100644 --- a/src/test/kotlin/org/opensearch/indexmanagement/IndexManagementIndicesIT.kt +++ b/src/test/kotlin/org/opensearch/indexmanagement/IndexManagementIndicesIT.kt @@ -38,7 +38,7 @@ import java.util.Locale class IndexManagementIndicesIT : IndexStateManagementRestTestCase() { - private val testIndexName = javaClass.simpleName.toLowerCase(Locale.ROOT) + private val testIndexName = javaClass.simpleName.lowercase(Locale.ROOT) /* * If this test fails it means you changed the config mappings diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/IndexStateManagementRestTestCase.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/IndexStateManagementRestTestCase.kt index a345653f0..4a582f130 100644 --- a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/IndexStateManagementRestTestCase.kt +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/IndexStateManagementRestTestCase.kt @@ -58,6 +58,7 @@ import org.opensearch.indexmanagement.spi.indexstatemanagement.model.ManagedInde import org.opensearch.indexmanagement.spi.indexstatemanagement.model.PolicyRetryInfoMetaData import org.opensearch.indexmanagement.spi.indexstatemanagement.model.StateMetaData import org.opensearch.indexmanagement.spi.indexstatemanagement.model.StepMetaData +import org.opensearch.indexmanagement.spi.indexstatemanagement.model.ValidationResult import org.opensearch.indexmanagement.util._ID import org.opensearch.indexmanagement.util._PRIMARY_TERM import org.opensearch.indexmanagement.util._SEQ_NO @@ -82,6 +83,16 @@ abstract class IndexStateManagementRestTestCase : IndexManagementRestTestCase() updateIndexStateManagementJitterSetting(0.0) } + @Before + protected fun disableValidationService() { + updateValidationServiceSetting(false) + } + + @Before + protected fun enableValidationService() { + updateValidationServiceSetting(true) + } + protected fun createPolicy( policy: Policy, policyId: String = OpenSearchTestCase.randomAlphaOfLength(10), @@ -165,7 +176,7 @@ abstract class IndexStateManagementRestTestCase : IndexManagementRestTestCase() } protected fun createIndex( - index: String = randomAlphaOfLength(10).toLowerCase(Locale.ROOT), + index: String = randomAlphaOfLength(10).lowercase(Locale.ROOT), policyID: String? = randomAlphaOfLength(10), alias: String? = null, replicas: String? = null, @@ -288,6 +299,10 @@ abstract class IndexStateManagementRestTestCase : IndexManagementRestTestCase() updateClusterSetting(ManagedIndexSettings.JITTER.key, value.toString(), false) } + protected fun updateValidationServiceSetting(value: Boolean) { + updateClusterSetting(ManagedIndexSettings.ACTION_VALIDATION_ENABLED.key, value.toString(), false) + } + protected fun updateIndexSetting( index: String, key: String, @@ -623,6 +638,40 @@ abstract class IndexStateManagementRestTestCase : IndexManagementRestTestCase() return metadata } + // Calls explain API for a single concrete index and converts the response into a ValidationResponse + // This only works for indices with a ManagedIndexMetaData that has been initialized + @Suppress("LoopWithTooManyJumpStatements") + protected fun getExplainValidationResult(indexName: String): ValidationResult { + if (indexName.contains("*") || indexName.contains(",")) { + throw IllegalArgumentException("This method is only for a single concrete index") + } + + val response = client().makeRequest(RestRequest.Method.GET.toString(), "${RestExplainAction.EXPLAIN_BASE_URI}/$indexName?validate_action=true") + + assertEquals("Unexpected RestStatus", RestStatus.OK, response.restStatus()) + lateinit var validationResult: ValidationResult + val xcp = createParser(XContentType.JSON.xContent(), response.entity.content) + ensureExpectedToken(Token.START_OBJECT, xcp.nextToken(), xcp) + while (xcp.nextToken() != Token.END_OBJECT) { + val cn = xcp.currentName() + if (cn == "total_managed_indices") continue + + xcp.nextToken() // going into start object + // loop next token until you find currentName == validate + while (true) { + val cn2 = xcp.currentName() + if (cn2 == "validate") { + ensureExpectedToken(Token.START_OBJECT, xcp.nextToken(), xcp) + validationResult = ValidationResult.parse(xcp) + break + } + xcp.nextToken() + } + break // bypass roles field + } + return validationResult + } + protected fun rolloverIndex(alias: String) { val response = client().performRequest( Request( diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/MetadataRegressionIT.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/MetadataRegressionIT.kt index 3874f79b3..c7e83c874 100644 --- a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/MetadataRegressionIT.kt +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/MetadataRegressionIT.kt @@ -30,7 +30,7 @@ import kotlin.collections.HashMap class MetadataRegressionIT : IndexStateManagementIntegTestCase() { - private val testIndexName = javaClass.simpleName.toLowerCase(Locale.ROOT) + private val testIndexName = javaClass.simpleName.lowercase(Locale.ROOT) @Before fun startMetadataService() { diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/ActionRetryIT.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/ActionRetryIT.kt index cf05dd34d..2618dc549 100644 --- a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/ActionRetryIT.kt +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/ActionRetryIT.kt @@ -18,12 +18,13 @@ import java.time.Instant import java.util.Locale class ActionRetryIT : IndexStateManagementRestTestCase() { - private val testIndexName = javaClass.simpleName.toLowerCase(Locale.ROOT) + private val testIndexName = javaClass.simpleName.lowercase(Locale.ROOT) /** * We are forcing RollOver to fail in this Integ test. */ fun `test failed action`() { + disableValidationService() val testPolicy = """ {"policy":{"description":"Default policy","default_state":"Ingest","states":[ {"name":"Ingest","actions":[{"retry":{"count":2,"backoff":"constant","delay":"1s"},"rollover":{"min_doc_count":100}}],"transitions":[{"state_name":"Search"}]}, @@ -95,6 +96,7 @@ class ActionRetryIT : IndexStateManagementRestTestCase() { } fun `test exponential backoff`() { + disableValidationService() val testPolicy = """ {"policy":{"description":"Default policy","default_state":"Ingest","states":[ {"name":"Ingest","actions":[{"retry":{"count":2,"backoff":"exponential","delay":"1m"},"rollover":{"min_doc_count":100}}],"transitions":[{"state_name":"Search"}]}, @@ -118,7 +120,6 @@ class ActionRetryIT : IndexStateManagementRestTestCase() { // First execution. We need to initialize the policy. waitFor { assertEquals(policyID, getExplainManagedIndexMetaData(indexName).policyID) } - // Second execution is to fail the step once. updateManagedIndexConfigStartTime(managedIndexConfig) @@ -127,7 +128,6 @@ class ActionRetryIT : IndexStateManagementRestTestCase() { // Third execution should not run job since we have the retry backoff. updateManagedIndexConfigStartTime(managedIndexConfig) Thread.sleep(5000) // currently there is nothing to compare when backing off so we have to sleep - // Fourth execution should not run job since we have the retry backoff. updateManagedIndexConfigStartTime(managedIndexConfig) diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/ActionTimeoutIT.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/ActionTimeoutIT.kt index b3d792705..a924dc637 100644 --- a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/ActionTimeoutIT.kt +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/ActionTimeoutIT.kt @@ -16,7 +16,7 @@ import java.time.Instant import java.util.Locale class ActionTimeoutIT : IndexStateManagementRestTestCase() { - private val testIndexName = javaClass.simpleName.toLowerCase(Locale.ROOT) + private val testIndexName = javaClass.simpleName.lowercase(Locale.ROOT) fun `test failed action`() { val indexName = "${testIndexName}_index_1" diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/AllocationActionIT.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/AllocationActionIT.kt index 295208fdc..82b8daddf 100644 --- a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/AllocationActionIT.kt +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/AllocationActionIT.kt @@ -17,7 +17,7 @@ import java.time.temporal.ChronoUnit import java.util.Locale class AllocationActionIT : IndexStateManagementRestTestCase() { - private val testIndexName = javaClass.simpleName.toLowerCase(Locale.ROOT) + private val testIndexName = javaClass.simpleName.lowercase(Locale.ROOT) fun `test basic`() { val indexName = "${testIndexName}_index_1" diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/CloseActionIT.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/CloseActionIT.kt index aa8ab9e09..1a2760cce 100644 --- a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/CloseActionIT.kt +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/CloseActionIT.kt @@ -16,7 +16,7 @@ import java.time.temporal.ChronoUnit import java.util.Locale class CloseActionIT : IndexStateManagementRestTestCase() { - private val testIndexName = javaClass.simpleName.toLowerCase(Locale.ROOT) + private val testIndexName = javaClass.simpleName.lowercase(Locale.ROOT) fun `test basic`() { val indexName = "${testIndexName}_index_1" diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/DeleteActionIT.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/DeleteActionIT.kt index ff7265df9..163e862e2 100644 --- a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/DeleteActionIT.kt +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/DeleteActionIT.kt @@ -15,7 +15,7 @@ import java.time.temporal.ChronoUnit import java.util.Locale class DeleteActionIT : IndexStateManagementRestTestCase() { - private val testIndexName = javaClass.simpleName.toLowerCase(Locale.ROOT) + private val testIndexName = javaClass.simpleName.lowercase(Locale.ROOT) fun `test basic`() { val indexName = "${testIndexName}_index_1" diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/ForceMergeActionIT.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/ForceMergeActionIT.kt index 9866f175a..fca73466a 100644 --- a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/ForceMergeActionIT.kt +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/ForceMergeActionIT.kt @@ -20,7 +20,7 @@ import java.time.temporal.ChronoUnit import java.util.Locale class ForceMergeActionIT : IndexStateManagementRestTestCase() { - private val testIndexName = javaClass.simpleName.toLowerCase(Locale.ROOT) + private val testIndexName = javaClass.simpleName.lowercase(Locale.ROOT) fun `test basic workflow`() { val indexName = "${testIndexName}_index_1" diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/IndexPolicyActionIT.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/IndexPolicyActionIT.kt index b730100af..c42fa88fe 100644 --- a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/IndexPolicyActionIT.kt +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/IndexPolicyActionIT.kt @@ -23,7 +23,7 @@ import java.time.temporal.ChronoUnit import java.util.Locale class IndexPolicyActionIT : IndexStateManagementRestTestCase() { - private val testIndexName = javaClass.simpleName.toLowerCase(Locale.ROOT) + private val testIndexName = javaClass.simpleName.lowercase(Locale.ROOT) fun `test allocation aware replica count`() { val policyID = "${testIndexName}_testPolicyName_replica" diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/IndexPriorityActionIT.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/IndexPriorityActionIT.kt index b13bfbab2..c77b5e8ad 100644 --- a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/IndexPriorityActionIT.kt +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/IndexPriorityActionIT.kt @@ -15,7 +15,7 @@ import java.time.temporal.ChronoUnit import java.util.Locale class IndexPriorityActionIT : IndexStateManagementRestTestCase() { - private val testIndexName = javaClass.simpleName.toLowerCase(Locale.ROOT) + private val testIndexName = javaClass.simpleName.lowercase(Locale.ROOT) fun `test basic index priority`() { val indexName = "${testIndexName}_index_1" diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/IndexStateManagementHistoryIT.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/IndexStateManagementHistoryIT.kt index 1f80bb245..b94eedeae 100644 --- a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/IndexStateManagementHistoryIT.kt +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/IndexStateManagementHistoryIT.kt @@ -14,10 +14,10 @@ import org.opensearch.indexmanagement.indexstatemanagement.randomErrorNotificati import org.opensearch.indexmanagement.indexstatemanagement.settings.ManagedIndexSettings import org.opensearch.indexmanagement.indexstatemanagement.step.readonly.SetReadOnlyStep import org.opensearch.indexmanagement.spi.indexstatemanagement.Step +import org.opensearch.indexmanagement.spi.indexstatemanagement.model.StateMetaData import org.opensearch.indexmanagement.spi.indexstatemanagement.model.ActionMetaData import org.opensearch.indexmanagement.spi.indexstatemanagement.model.ManagedIndexMetaData import org.opensearch.indexmanagement.spi.indexstatemanagement.model.PolicyRetryInfoMetaData -import org.opensearch.indexmanagement.spi.indexstatemanagement.model.StateMetaData import org.opensearch.indexmanagement.spi.indexstatemanagement.model.StepMetaData import org.opensearch.indexmanagement.waitFor import java.time.Instant @@ -25,7 +25,7 @@ import java.time.temporal.ChronoUnit import java.util.Locale class IndexStateManagementHistoryIT : IndexStateManagementRestTestCase() { - private val testIndexName = javaClass.simpleName.toLowerCase(Locale.ROOT) + private val testIndexName = javaClass.simpleName.lowercase(Locale.ROOT) fun `test basic workflow`() { val indexName = "${testIndexName}_index_1" diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/NotificationActionIT.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/NotificationActionIT.kt index 8b7aa558b..6790d2ed6 100644 --- a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/NotificationActionIT.kt +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/NotificationActionIT.kt @@ -21,7 +21,7 @@ import java.time.temporal.ChronoUnit import java.util.Locale class NotificationActionIT : IndexStateManagementRestTestCase() { - private val testIndexName = javaClass.simpleName.toLowerCase(Locale.ROOT) + private val testIndexName = javaClass.simpleName.lowercase(Locale.ROOT) // TODO: this seems to have broken with the notification plugin // cannot test chime/slack in integ tests, but can test a custom webhook by diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/OpenActionIT.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/OpenActionIT.kt index 4db5127c5..d253806ba 100644 --- a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/OpenActionIT.kt +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/OpenActionIT.kt @@ -15,7 +15,7 @@ import java.time.temporal.ChronoUnit import java.util.Locale class OpenActionIT : IndexStateManagementRestTestCase() { - private val testIndexName = javaClass.simpleName.toLowerCase(Locale.ROOT) + private val testIndexName = javaClass.simpleName.lowercase(Locale.ROOT) fun `test basic`() { val indexName = "${testIndexName}_index_1" diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/ReadOnlyActionIT.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/ReadOnlyActionIT.kt index 71d329520..8ecf52aeb 100644 --- a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/ReadOnlyActionIT.kt +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/ReadOnlyActionIT.kt @@ -15,7 +15,7 @@ import java.time.temporal.ChronoUnit import java.util.Locale class ReadOnlyActionIT : IndexStateManagementRestTestCase() { - private val testIndexName = javaClass.simpleName.toLowerCase(Locale.ROOT) + private val testIndexName = javaClass.simpleName.lowercase(Locale.ROOT) fun `test basic workflow`() { val indexName = "${testIndexName}_index_1" diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/ReadWriteActionIT.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/ReadWriteActionIT.kt index 92a971513..e52786314 100644 --- a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/ReadWriteActionIT.kt +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/ReadWriteActionIT.kt @@ -17,7 +17,7 @@ import java.util.Locale class ReadWriteActionIT : IndexStateManagementRestTestCase() { - private val testIndexName = javaClass.simpleName.toLowerCase(Locale.ROOT) + private val testIndexName = javaClass.simpleName.lowercase(Locale.ROOT) fun `test basic workflow`() { val indexName = "${testIndexName}_index_1" diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/ReplicaCountActionIT.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/ReplicaCountActionIT.kt index 21b8e67ce..f88a094b8 100644 --- a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/ReplicaCountActionIT.kt +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/ReplicaCountActionIT.kt @@ -16,7 +16,7 @@ import java.util.Locale class ReplicaCountActionIT : IndexStateManagementRestTestCase() { - private val testIndexName = javaClass.simpleName.toLowerCase(Locale.ROOT) + private val testIndexName = javaClass.simpleName.lowercase(Locale.ROOT) fun `test basic replica count`() { val indexName = "${testIndexName}_index_1" diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/RolloverActionIT.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/RolloverActionIT.kt index 7d6d77935..cc3cc057d 100644 --- a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/RolloverActionIT.kt +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/RolloverActionIT.kt @@ -35,7 +35,7 @@ import java.util.Locale class RolloverActionIT : IndexStateManagementRestTestCase() { - private val testIndexName = javaClass.simpleName.toLowerCase(Locale.ROOT) + private val testIndexName = javaClass.simpleName.lowercase(Locale.ROOT) @Suppress("UNCHECKED_CAST") fun `test rollover no condition`() { @@ -377,6 +377,7 @@ class RolloverActionIT : IndexStateManagementRestTestCase() { } fun `test rollover pre check`() { + disableValidationService() // index-1 alias x // index-2 alias x is_write_index // manage index-1, expect it fail to rollover diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/RollupActionIT.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/RollupActionIT.kt index 19ae0239b..fa9402a3b 100644 --- a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/RollupActionIT.kt +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/RollupActionIT.kt @@ -35,7 +35,7 @@ import java.util.Locale class RollupActionIT : IndexStateManagementRestTestCase() { - private val testIndexName = javaClass.simpleName.toLowerCase(Locale.ROOT) + private val testIndexName = javaClass.simpleName.lowercase(Locale.ROOT) fun `test rollup action`() { val indexName = "${testIndexName}_index_basic" diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/SnapshotActionIT.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/SnapshotActionIT.kt index e1bd328d7..555f8610e 100644 --- a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/SnapshotActionIT.kt +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/SnapshotActionIT.kt @@ -19,7 +19,7 @@ import java.util.Locale class SnapshotActionIT : IndexStateManagementRestTestCase() { - private val testIndexName = javaClass.simpleName.toLowerCase(Locale.ROOT) + private val testIndexName = javaClass.simpleName.lowercase(Locale.ROOT) fun `test basic`() { val indexName = "${testIndexName}_index_basic" diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/TransitionActionIT.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/TransitionActionIT.kt index 80f153738..05fb85954 100644 --- a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/TransitionActionIT.kt +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/TransitionActionIT.kt @@ -20,7 +20,7 @@ import java.util.Locale class TransitionActionIT : IndexStateManagementRestTestCase() { - private val testIndexName = javaClass.simpleName.toLowerCase(Locale.ROOT) + private val testIndexName = javaClass.simpleName.lowercase(Locale.ROOT) fun `test doc count condition`() { val indexName = "${testIndexName}_index_1" diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/coordinator/ManagedIndexCoordinatorIT.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/coordinator/ManagedIndexCoordinatorIT.kt index 605e0a1c4..b78a827f9 100644 --- a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/coordinator/ManagedIndexCoordinatorIT.kt +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/coordinator/ManagedIndexCoordinatorIT.kt @@ -61,6 +61,7 @@ class ManagedIndexCoordinatorIT : IndexStateManagementRestTestCase() { } fun `test managed index metadata is cleaned up after removing policy`() { + disableValidationService() val policy = createRandomPolicy() val (index) = createIndex(policyID = policy.id) diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/resthandler/ISMTemplateRestAPIIT.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/resthandler/ISMTemplateRestAPIIT.kt index 563c3de50..e7e804944 100644 --- a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/resthandler/ISMTemplateRestAPIIT.kt +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/resthandler/ISMTemplateRestAPIIT.kt @@ -25,7 +25,7 @@ import java.util.Locale class ISMTemplateRestAPIIT : IndexStateManagementRestTestCase() { - private val testIndexName = javaClass.simpleName.toLowerCase(Locale.ROOT) + private val testIndexName = javaClass.simpleName.lowercase(Locale.ROOT) private val policyID1 = "t1" private val policyID2 = "t2" @@ -79,6 +79,7 @@ class ISMTemplateRestAPIIT : IndexStateManagementRestTestCase() { } fun `test ism template managing index`() { + disableValidationService() val indexName1 = "log-000001" val indexName2 = "log-000002" val indexName3 = "log-000003" diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/resthandler/RestChangePolicyActionIT.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/resthandler/RestChangePolicyActionIT.kt index 22c2f4586..e225d559b 100644 --- a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/resthandler/RestChangePolicyActionIT.kt +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/resthandler/RestChangePolicyActionIT.kt @@ -39,7 +39,7 @@ import java.util.Locale class RestChangePolicyActionIT : IndexStateManagementRestTestCase() { - private val testIndexName = javaClass.simpleName.toLowerCase(Locale.ROOT) + private val testIndexName = javaClass.simpleName.lowercase(Locale.ROOT) @Before fun setup() { diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/resthandler/RestExplainActionIT.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/resthandler/RestExplainActionIT.kt index c7a980932..586a86c60 100644 --- a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/resthandler/RestExplainActionIT.kt +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/resthandler/RestExplainActionIT.kt @@ -25,9 +25,10 @@ import java.util.Locale class RestExplainActionIT : IndexStateManagementRestTestCase() { - private val testIndexName = javaClass.simpleName.toLowerCase(Locale.ROOT) + private val testIndexName = javaClass.simpleName.lowercase(Locale.ROOT) fun `test single index`() { + disableValidationService() val indexName = "${testIndexName}_movies" createIndex(indexName, null) val expected = mapOf( @@ -51,6 +52,7 @@ class RestExplainActionIT : IndexStateManagementRestTestCase() { } fun `test two indices, one managed one not managed`() { + disableValidationService() // explicitly asks for un-managed index, will return policy_id as null val indexName1 = "${testIndexName}_managed" val indexName2 = "${testIndexName}_not_managed" @@ -80,6 +82,7 @@ class RestExplainActionIT : IndexStateManagementRestTestCase() { } fun `test two indices, one managed one not managed explain all`() { + disableValidationService() // explain all returns only managed indices val indexName1 = "${testIndexName}_managed" val indexName2 = "${testIndexName}_not_managed" @@ -104,6 +107,7 @@ class RestExplainActionIT : IndexStateManagementRestTestCase() { } fun `test index pattern`() { + disableValidationService() val indexName1 = "${testIndexName}_pattern" val indexName2 = "${indexName1}_2" val indexName3 = "${indexName1}_3" @@ -141,6 +145,7 @@ class RestExplainActionIT : IndexStateManagementRestTestCase() { } fun `test search query string`() { + disableValidationService() val indexName1 = "$testIndexName-search-query-string" val indexName2 = "$indexName1-testing-2" val indexName3 = "$indexName1-testing-3" @@ -249,6 +254,7 @@ class RestExplainActionIT : IndexStateManagementRestTestCase() { } fun `test attached policy`() { + disableValidationService() val indexName = "${testIndexName}_watermelon" val policy = createRandomPolicy() createIndex(indexName, policy.id) @@ -288,6 +294,7 @@ class RestExplainActionIT : IndexStateManagementRestTestCase() { } fun `test failed policy`() { + disableValidationService() val indexName = "${testIndexName}_melon" val policy = createRandomPolicy() createIndex(indexName, policy.id) @@ -330,6 +337,7 @@ class RestExplainActionIT : IndexStateManagementRestTestCase() { } fun `test show_applied_policy query parameter`() { + disableValidationService() val indexName = "${testIndexName}_show_applied_policy" val policy = createRandomPolicy() createIndex(indexName, policy.id) @@ -343,11 +351,12 @@ class RestExplainActionIT : IndexStateManagementRestTestCase() { "index_uuid" to getUuid(indexName), "policy_id" to policy.id, ManagedIndexMetaData.ENABLED to true, - "policy" to expectedPolicy + "policy" to expectedPolicy, ), TOTAL_MANAGED_INDICES to 1, ) waitFor { + logger.info(getExplainMap(indexName, queryParams = SHOW_POLICY_QUERY_PARAM)) assertResponseMap(expected, getExplainMap(indexName, queryParams = SHOW_POLICY_QUERY_PARAM)) } } diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/resthandler/RestRetryFailedManagedIndexActionIT.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/resthandler/RestRetryFailedManagedIndexActionIT.kt index 07c7fd244..c1b385030 100644 --- a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/resthandler/RestRetryFailedManagedIndexActionIT.kt +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/resthandler/RestRetryFailedManagedIndexActionIT.kt @@ -27,7 +27,7 @@ import java.util.Locale class RestRetryFailedManagedIndexActionIT : IndexStateManagementRestTestCase() { - private val testIndexName = javaClass.simpleName.toLowerCase(Locale.ROOT) + private val testIndexName = javaClass.simpleName.lowercase(Locale.ROOT) fun `test missing indices`() { try { diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/explain/ExplainRequestTests.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/explain/ExplainRequestTests.kt index b63aa1a75..34c694eeb 100644 --- a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/explain/ExplainRequestTests.kt +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/explain/ExplainRequestTests.kt @@ -20,7 +20,8 @@ class ExplainRequestTests : OpenSearchTestCase() { val clusterManagerTimeout = TimeValue.timeValueSeconds(30) val params = SearchParams(0, 20, "sort-field", "asc", "*") val showPolicy = false - val req = ExplainRequest(indices, local, clusterManagerTimeout, params, showPolicy, DEFAULT_INDEX_TYPE) + val showValidationResult = false + val req = ExplainRequest(indices, local, clusterManagerTimeout, params, showPolicy, showValidationResult, DEFAULT_INDEX_TYPE) val out = BytesStreamOutput() req.writeTo(out) @@ -36,7 +37,8 @@ class ExplainRequestTests : OpenSearchTestCase() { val clusterManagerTimeout = TimeValue.timeValueSeconds(30) val params = SearchParams(0, 20, "sort-field", "asc", "*") val showPolicy = false - val req = ExplainRequest(indices, local, clusterManagerTimeout, params, showPolicy, "non-existent-index-type") + val showValidationResult = false + val req = ExplainRequest(indices, local, clusterManagerTimeout, params, showPolicy, showValidationResult, "non-existent-index-type") val actualException: String? = req.validate()?.validationErrors()?.firstOrNull() val expectedException: String = ExplainRequest.MULTIPLE_INDICES_CUSTOM_INDEX_TYPE_ERROR diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/explain/ExplainResponseTests.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/explain/ExplainResponseTests.kt index 647ffd99f..680b04009 100644 --- a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/explain/ExplainResponseTests.kt +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/explain/ExplainResponseTests.kt @@ -8,7 +8,9 @@ package org.opensearch.indexmanagement.indexstatemanagement.transport.action.exp import org.opensearch.common.io.stream.BytesStreamOutput import org.opensearch.common.io.stream.StreamInput import org.opensearch.indexmanagement.indexstatemanagement.randomPolicy +import org.opensearch.indexmanagement.spi.indexstatemanagement.Validate import org.opensearch.indexmanagement.spi.indexstatemanagement.model.ManagedIndexMetaData +import org.opensearch.indexmanagement.spi.indexstatemanagement.model.ValidationResult import org.opensearch.test.OpenSearchTestCase class ExplainResponseTests : OpenSearchTestCase() { @@ -32,11 +34,13 @@ class ExplainResponseTests : OpenSearchTestCase() { policyRetryInfo = null, info = null ) + val validationResult = ValidationResult("test", Validate.ValidationStatus.FAILED) + val validationResults = listOf(validationResult) val indexMetadatas = listOf(metadata) val totalManagedIndices = 1 val enabledState = mapOf("index1" to true) val appliedPolicies = mapOf("policy" to randomPolicy()) - val res = ExplainResponse(indexNames, indexPolicyIDs, indexMetadatas, totalManagedIndices, enabledState, appliedPolicies) + val res = ExplainResponse(indexNames, indexPolicyIDs, indexMetadatas, totalManagedIndices, enabledState, appliedPolicies, validationResults) val out = BytesStreamOutput() res.writeTo(out) @@ -48,5 +52,6 @@ class ExplainResponseTests : OpenSearchTestCase() { assertEquals(totalManagedIndices, newRes.totalManagedIndices) assertEquals(enabledState, newRes.enabledState) assertEquals(appliedPolicies, newRes.policies) + assertEquals(validationResults, newRes.validationResults) } } diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateDeleteIT.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateDeleteIT.kt new file mode 100644 index 000000000..696989d18 --- /dev/null +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateDeleteIT.kt @@ -0,0 +1,74 @@ +/* + * 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.action.DeleteAction +import org.opensearch.indexmanagement.indexstatemanagement.model.Policy +import org.opensearch.indexmanagement.indexstatemanagement.model.State +import org.opensearch.indexmanagement.indexstatemanagement.randomErrorNotification +import org.opensearch.indexmanagement.indexstatemanagement.settings.ManagedIndexSettings +import org.opensearch.indexmanagement.spi.indexstatemanagement.Validate +import org.opensearch.indexmanagement.spi.indexstatemanagement.model.ActionRetry +import org.opensearch.indexmanagement.waitFor +import java.time.Instant +import java.time.temporal.ChronoUnit +import java.util.Locale + +class ValidateDeleteIT : IndexStateManagementRestTestCase() { + private val testIndexName = javaClass.simpleName.lowercase(Locale.ROOT) + + fun `test delete index is write index`() { + enableValidationService() + val index1 = "index-1" + val alias1 = "x" + val policyID = "${testIndexName}_precheck" + val actionConfig = DeleteAction(0) + actionConfig.configRetry = ActionRetry(0) + val states = listOf(State(name = "DeleteAction", actions = listOf(actionConfig), transitions = 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(index1, policyID) + changeAlias(index1, alias1, "add", true) + updateIndexSetting(index1, ManagedIndexSettings.ROLLOVER_ALIAS.key, alias1) + + val managedIndexConfig = getExistingManagedIndexConfig(index1) + + // Change the start time so the job will trigger in 2 seconds, this will trigger the first initialization of the policy + updateManagedIndexConfigStartTime(managedIndexConfig) + waitFor { assertEquals(policyID, getExplainManagedIndexMetaData(index1).policyID) } + // waitFor { assertIndexExists(index1) } + + // Need to speed up to second execution where it will trigger the first execution of the action + updateManagedIndexConfigStartTime(managedIndexConfig) + waitFor { + val data = getExplainValidationResult(index1) + assertEquals( + "Index delete action validation status is RE_VALIDATING.", + Validate.ValidationStatus.RE_VALIDATING, + data?.validationStatus + ) + } + waitFor { + val data = getExplainValidationResult(index1) + assertEquals( + "Index delete action validation message is index is write index.", + ValidateDelete.getFailedIsWriteIndexMessage(index1), + data?.validationMessage + ) + } + } +} diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateForceMergeIT.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateForceMergeIT.kt new file mode 100644 index 000000000..a9cfd2940 --- /dev/null +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateForceMergeIT.kt @@ -0,0 +1,96 @@ +/* + * 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.Policy +import org.opensearch.indexmanagement.indexstatemanagement.model.State +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 ValidateForceMergeIT : IndexStateManagementRestTestCase() { + + private val testIndexName = javaClass.simpleName.lowercase(Locale.ROOT) + + fun `test basic workflow`() { + enableValidationService() + val indexName = "${testIndexName}_index_1" + val policyID = "${testIndexName}_testPolicyName_1" + + // Create a Policy with one State that only preforms a force_merge Action + val forceMergeActionConfig = org.opensearch.indexmanagement.indexstatemanagement.action.ForceMergeAction(maxNumSegments = 1, index = 0) + val states = listOf(State("ForceMergeState", listOf(forceMergeActionConfig), 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) + + // Add sample data to increase segment count, passing in a delay to ensure multiple segments get created + insertSampleData(indexName, 3, 1000) + + waitFor { assertTrue("Segment count for [$indexName] was less than expected", validateSegmentCount(indexName, min = 2)) } + + val managedIndexConfig = getExistingManagedIndexConfig(indexName) + + // Will change the startTime each execution so that it triggers in 2 seconds + // First execution: Policy is initialized + updateManagedIndexConfigStartTime(managedIndexConfig) + + waitFor { assertEquals(policyID, getExplainManagedIndexMetaData(indexName).policyID) } + + // Second execution: Index is set to read-only for force_merge + updateManagedIndexConfigStartTime(managedIndexConfig) + + waitFor { assertEquals("true", getIndexBlocksWriteSetting(indexName)) } + + // Third execution: Force merge operation is kicked off + updateManagedIndexConfigStartTime(managedIndexConfig) + + // verify we set maxNumSegments in action properties when kicking off force merge + waitFor { + val data = getExplainValidationResult(indexName) + assertEquals( + "Index force_merge action validation status is RE_VALIDATING.", + Validate.ValidationStatus.PASSED, + data?.validationStatus + ) + assertEquals( + "Index force_merge action validation status is RE_VALIDATING.", + ValidateForceMerge.getValidationPassedMessage(indexName), + data?.validationMessage + ) + } + waitFor { + assertEquals( + "maxNumSegments not set in ActionProperties", + forceMergeActionConfig.maxNumSegments, + getExplainManagedIndexMetaData(indexName).actionMetaData?.actionProperties?.maxNumSegments + ) + } + + // Fourth execution: Waits for force merge to complete, which will happen in this execution since index is small + updateManagedIndexConfigStartTime(managedIndexConfig) + + waitFor { assertTrue("Segment count for [$indexName] after force merge is incorrect", validateSegmentCount(indexName, min = 1, max = 1)) } + // verify we reset actionproperties at end of forcemerge + waitFor { assertNull("maxNumSegments was not reset", getExplainManagedIndexMetaData(indexName).actionMetaData?.actionProperties) } + // index should still be readonly after force merge finishes + waitFor { assertEquals("true", getIndexBlocksWriteSetting(indexName)) } + } +} diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateOpenIT.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateOpenIT.kt new file mode 100644 index 000000000..f52cbb2ac --- /dev/null +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateOpenIT.kt @@ -0,0 +1,73 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * 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.action.OpenAction +import org.opensearch.indexmanagement.indexstatemanagement.model.Policy +import org.opensearch.indexmanagement.indexstatemanagement.model.State +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 ValidateOpenIT : IndexStateManagementRestTestCase() { + private val testIndexName = javaClass.simpleName.lowercase(Locale.ROOT) + + fun `test validate open basic`() { + enableValidationService() + val indexName = "index_1" + val policyID = "${testIndexName}_precheck" + val actionConfig = OpenAction(0) + val states = listOf( + State("OpenState", listOf(actionConfig), 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) + closeIndex(indexName) + + assertEquals("close", getIndexState(indexName)) + + val managedIndexConfig = getExistingManagedIndexConfig(indexName) + + // Change the start time so the job will trigger in 2 seconds. + updateManagedIndexConfigStartTime(managedIndexConfig) + + waitFor { assertEquals(policyID, getExplainManagedIndexMetaData(indexName).policyID) } + + // Need to wait two cycles. + // Change the start time so the job will trigger in 2 seconds. + updateManagedIndexConfigStartTime(managedIndexConfig) + + waitFor { assertEquals("open", getIndexState(indexName)) } + + waitFor { + val data = getExplainValidationResult(indexName) + assertEquals( + "Index open action validation status is PASSED.", + Validate.ValidationStatus.PASSED, + data?.validationStatus + ) + } + } +} diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateReadOnlyIT.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateReadOnlyIT.kt new file mode 100644 index 000000000..4d9033609 --- /dev/null +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateReadOnlyIT.kt @@ -0,0 +1,71 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * 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.action.ReadOnlyAction +import org.opensearch.indexmanagement.indexstatemanagement.model.Policy +import org.opensearch.indexmanagement.indexstatemanagement.model.State +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 ValidateReadOnlyIT : IndexStateManagementRestTestCase() { + private val testIndexName = javaClass.simpleName.lowercase(Locale.ROOT) + + fun `test read_only validation`() { + enableValidationService() + val indexName = "${testIndexName}_index_1" + val policyID = "${testIndexName}_testPolicyName_1" + val actionConfig = ReadOnlyAction(0) + val states = listOf( + State("ReadOnlyState", listOf(actionConfig), 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) + + // Change the start time so the job will trigger in 2 seconds. + updateManagedIndexConfigStartTime(managedIndexConfig) + + waitFor { assertEquals(policyID, getExplainManagedIndexMetaData(indexName).policyID) } + + // Need to wait two cycles. + // Change the start time so the job will trigger in 2 seconds. + updateManagedIndexConfigStartTime(managedIndexConfig) + + waitFor { assertEquals("true", getIndexBlocksWriteSetting(indexName)) } + + waitFor { + val data = getExplainValidationResult(indexName) + assertEquals( + "Index read cation validation status is PASSED.", + Validate.ValidationStatus.PASSED, + data?.validationStatus + ) + } + } +} diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateReadWriteIT.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateReadWriteIT.kt new file mode 100644 index 000000000..25b6aee1c --- /dev/null +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateReadWriteIT.kt @@ -0,0 +1,80 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.indexmanagement.indexstatemanagement.validation + +import org.opensearch.common.settings.Settings +import org.opensearch.indexmanagement.indexstatemanagement.IndexStateManagementRestTestCase +import org.opensearch.indexmanagement.indexstatemanagement.action.ReadWriteAction +import org.opensearch.indexmanagement.indexstatemanagement.model.Policy +import org.opensearch.indexmanagement.indexstatemanagement.model.State +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 ValidateReadWriteIT : IndexStateManagementRestTestCase() { + private val testIndexName = javaClass.simpleName.lowercase(Locale.ROOT) + + fun `test read_write validation`() { + enableValidationService() + val indexName = "${testIndexName}_index_1" + val policyID = "${testIndexName}_testPolicyName_1" + val actionConfig = ReadWriteAction(0) + val states = listOf( + State("ReadWriteState", listOf(actionConfig), 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, null) + // Set index to read-only + updateIndexSettings( + indexName, + Settings.builder().put("index.blocks.write", true) + ) + + assertEquals("true", getIndexBlocksWriteSetting(indexName)) + addPolicyToIndex(indexName, policyID) + + val managedIndexConfig = getExistingManagedIndexConfig(indexName) + + // Change the start time so the job will trigger in 2 seconds. + updateManagedIndexConfigStartTime(managedIndexConfig) + + waitFor { assertEquals(policyID, getExplainManagedIndexMetaData(indexName).policyID) } + + // Need to wait two cycles. + // Change the start time so the job will trigger in 2 seconds. + updateManagedIndexConfigStartTime(managedIndexConfig) + + waitFor { assertEquals("false", getIndexBlocksWriteSetting(indexName)) } + + waitFor { + val data = getExplainValidationResult(indexName) + assertEquals( + "Index read_write action validation status is PASSED.", + Validate.ValidationStatus.PASSED, + data?.validationStatus + ) + } + } +} diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateReplicaCountIT.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateReplicaCountIT.kt new file mode 100644 index 000000000..90c838f80 --- /dev/null +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateReplicaCountIT.kt @@ -0,0 +1,70 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * 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.action.ReplicaCountAction +import org.opensearch.indexmanagement.indexstatemanagement.model.Policy +import org.opensearch.indexmanagement.indexstatemanagement.model.State +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 ValidateReplicaCountIT : IndexStateManagementRestTestCase() { + private val testIndexName = javaClass.simpleName.lowercase(Locale.ROOT) + + fun `test replica count validation`() { + val indexName = "${testIndexName}_index_1" + val policyID = "${testIndexName}_testPolicyName_1" + val actionConfig = ReplicaCountAction(10, 0) + val states = listOf(State(name = "ReplicaCountState", actions = listOf(actionConfig), transitions = 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) + // create index defaults to 1 replica + createIndex(indexName, policyID) + + assertEquals("Index did not default to 1 replica", 1, getNumberOfReplicasSetting(indexName)) + + val managedIndexConfig = getExistingManagedIndexConfig(indexName) + + // Change the start time so the job will trigger in 2 seconds, this will trigger the first initialization of the policy + updateManagedIndexConfigStartTime(managedIndexConfig) + + waitFor { assertEquals(policyID, getExplainManagedIndexMetaData(indexName).policyID) } + + // Need to speed up to second execution where it will trigger the first execution of the action which + // should set the replica count to the desired number + updateManagedIndexConfigStartTime(managedIndexConfig) + + waitFor { assertEquals("Index did not set number_of_replicas to ${actionConfig.numOfReplicas}", actionConfig.numOfReplicas, getNumberOfReplicasSetting(indexName)) } + + waitFor { + val data = getExplainValidationResult(indexName) + assertEquals( + "Index replica_count action validation status is PASSED.", + Validate.ValidationStatus.PASSED, + data?.validationStatus + ) + } + } +} diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateRolloverIT.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateRolloverIT.kt new file mode 100644 index 000000000..199a9fc15 --- /dev/null +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateRolloverIT.kt @@ -0,0 +1,218 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.indexmanagement.indexstatemanagement.validation + +import org.opensearch.common.unit.TimeValue +import org.opensearch.indexmanagement.indexstatemanagement.IndexStateManagementRestTestCase +import org.opensearch.indexmanagement.indexstatemanagement.action.RolloverAction +import org.opensearch.indexmanagement.indexstatemanagement.model.Policy +import org.opensearch.indexmanagement.indexstatemanagement.model.State +import org.opensearch.indexmanagement.indexstatemanagement.randomErrorNotification +import org.opensearch.indexmanagement.indexstatemanagement.resthandler.RestRetryFailedManagedIndexAction +import org.opensearch.indexmanagement.indexstatemanagement.settings.ManagedIndexSettings +import org.opensearch.indexmanagement.makeRequest +import org.opensearch.indexmanagement.spi.indexstatemanagement.Validate +import org.opensearch.indexmanagement.spi.indexstatemanagement.model.ActionRetry +import org.opensearch.indexmanagement.waitFor +import org.opensearch.rest.RestRequest +import org.opensearch.rest.RestStatus +import java.time.Instant +import java.time.temporal.ChronoUnit +import java.util.Locale + +class ValidateRolloverIT : IndexStateManagementRestTestCase() { + private val testIndexName = javaClass.simpleName.lowercase(Locale.ROOT) + + // status: PASSED + fun `test skip rollover`() { + enableValidationService() + val index1 = "index-1" + val alias1 = "x" + val policyID = "${testIndexName}_precheck" + val actionConfig = RolloverAction(null, 3, TimeValue.timeValueDays(2), null, 0) + actionConfig.configRetry = ActionRetry(0) + val states = listOf(State(name = "RolloverAction", actions = listOf(actionConfig), transitions = 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(index1, policyID) + changeAlias(index1, alias1, "add", true) + updateIndexSetting(index1, ManagedIndexSettings.ROLLOVER_ALIAS.key, alias1) + + val managedIndexConfig = getExistingManagedIndexConfig(index1) + + // Change the start time so the job will trigger in 2 seconds, this will trigger the first initialization of the policy + updateManagedIndexConfigStartTime(managedIndexConfig) + waitFor { assertEquals(policyID, getExplainManagedIndexMetaData(index1).policyID) } + + updateIndexSetting(index1, ManagedIndexSettings.ROLLOVER_SKIP.key, "true") + + val response = client().makeRequest( + RestRequest.Method.POST.toString(), + "${RestRetryFailedManagedIndexAction.RETRY_BASE_URI}/$index1" + ) + assertEquals("Unexpected RestStatus", RestStatus.OK, response.restStatus()) + + updateManagedIndexConfigStartTime(managedIndexConfig) + waitFor { + val data = getExplainValidationResult(index1) + assertEquals( + "Index rollover validation status is pass.", + Validate.ValidationStatus.PASSED, data?.validationStatus + ) + assertEquals( + "Index rollover validation message is skipped rollover", + ValidateRollover.getSkipRolloverMessage(index1), data?.validationMessage + ) + } + } + + // status: PASSED + fun `test rollover has already been rolled over`() { + enableValidationService() + val aliasName = "${testIndexName}_alias" + val indexNameBase = "${testIndexName}_index" + val index1 = "$indexNameBase-1" + val policyID = "${testIndexName}_testPolicyName_1" + val actionConfig = RolloverAction(null, null, null, null, 0) + val states = listOf(State(name = "RolloverAction", actions = listOf(actionConfig), transitions = 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) + // create index defaults + createIndex(index1, policyID, aliasName) + + val managedIndexConfig = getExistingManagedIndexConfig(index1) + + // Change the start time so the job will trigger in 2 seconds, this will trigger the first initialization of the policy + updateManagedIndexConfigStartTime(managedIndexConfig) + waitFor { assertEquals(policyID, getExplainManagedIndexMetaData(index1).policyID) } + + // Rollover the alias manually before ISM tries to roll it over + rolloverIndex(aliasName) + + // Need to speed up to second execution where it will trigger the first execution of the action + updateManagedIndexConfigStartTime(managedIndexConfig) + waitFor { + val data = getExplainValidationResult(index1) + assertEquals( + "Index rollover validation status is PASSED.", + Validate.ValidationStatus.PASSED, data?.validationStatus + ) + assertEquals( + "Index rollover validation message is already rolled over", + ValidateRollover.getAlreadyRolledOverMessage(index1, aliasName), data?.validationMessage + ) + } + assertTrue("New rollover index does not exist.", indexExists("$indexNameBase-000002")) + } + + // status: RE_VALIDATING + fun `test rollover does not have rollover alias index setting`() { + enableValidationService() + val index1 = "index-1" + val index2 = "index-2" + val policyID = "${testIndexName}_precheck" + val actionConfig = RolloverAction(null, 3, TimeValue.timeValueDays(2), null, 0) + actionConfig.configRetry = ActionRetry(0) + val states = listOf(State(name = "RolloverAction", actions = listOf(actionConfig), transitions = 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(index1, policyID) + createIndex(index2, policyID) + + val managedIndexConfig = getExistingManagedIndexConfig(index1) + + // Change the start time so the job will trigger in 2 seconds, this will trigger the first initialization of the policy + updateManagedIndexConfigStartTime(managedIndexConfig) + waitFor { assertEquals(policyID, getExplainManagedIndexMetaData(index1).policyID) } + + // Need to speed up to second execution where it will trigger the first execution of the action + updateManagedIndexConfigStartTime(managedIndexConfig) + waitFor { + val data = getExplainValidationResult(index1) + assertEquals( + "Index rollover validation status is RE_VALIDATING", + Validate.ValidationStatus.RE_VALIDATING, data?.validationStatus + ) + assertEquals( + "Index rollover validation message is no alias index setting", + ValidateRollover.getFailedNoValidAliasMessage(index1), data?.validationMessage + ) + } + } + + // status: RE_VALIDATING + fun `test rollover not write index`() { + enableValidationService() + val index1 = "index-1" + val index2 = "index-2" + val alias1 = "x" + val policyID = "${testIndexName}_precheck" + val actionConfig = RolloverAction(null, 3, TimeValue.timeValueDays(2), null, 0) + actionConfig.configRetry = ActionRetry(0) + val states = listOf(State(name = "RolloverAction", actions = listOf(actionConfig), transitions = 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(index1, policyID) + changeAlias(index1, alias1, "add") + updateIndexSetting(index1, ManagedIndexSettings.ROLLOVER_ALIAS.key, alias1) + createIndex(index2, policyID) + changeAlias(index2, alias1, "add", true) + updateIndexSetting(index2, ManagedIndexSettings.ROLLOVER_ALIAS.key, alias1) + + val managedIndexConfig = getExistingManagedIndexConfig(index1) + + // Change the start time so the job will trigger in 2 seconds, this will trigger the first initialization of the policy + updateManagedIndexConfigStartTime(managedIndexConfig) + waitFor { assertEquals(policyID, getExplainManagedIndexMetaData(index1).policyID) } + + // Need to speed up to second execution where it will trigger the first execution of the action + updateManagedIndexConfigStartTime(managedIndexConfig) + waitFor { + val data = getExplainValidationResult(index1) + assertEquals( + "Index rollover validation status is RE_VALIDATING.", + Validate.ValidationStatus.RE_VALIDATING, data?.validationStatus + ) + assertEquals( + "Index rollover validation message is not write index", + ValidateRollover.getFailedWriteIndexMessage(index1), data?.validationMessage + ) + } + } +} diff --git a/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateRolloverTests.kt b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateRolloverTests.kt new file mode 100644 index 000000000..805d3b045 --- /dev/null +++ b/src/test/kotlin/org/opensearch/indexmanagement/indexstatemanagement/validation/ValidateRolloverTests.kt @@ -0,0 +1,72 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.indexmanagement.indexstatemanagement.validation + +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.whenever +import kotlinx.coroutines.runBlocking +import org.opensearch.client.Client +import org.opensearch.cluster.ClusterState +import org.opensearch.cluster.metadata.IndexAbstraction +import org.opensearch.cluster.metadata.IndexMetadata +import org.opensearch.cluster.metadata.Metadata +import org.opensearch.cluster.service.ClusterService +import org.opensearch.common.settings.Settings +import org.opensearch.common.unit.TimeValue +import org.opensearch.indexmanagement.indexstatemanagement.action.RolloverAction +import org.opensearch.indexmanagement.indexstatemanagement.validation.ValidateRollover.Companion.getFailedNoValidAliasMessage +import org.opensearch.indexmanagement.spi.indexstatemanagement.model.ActionMetaData +import org.opensearch.indexmanagement.spi.indexstatemanagement.model.ManagedIndexMetaData +import org.opensearch.indexmanagement.spi.indexstatemanagement.model.StepContext +import org.opensearch.indexmanagement.spi.indexstatemanagement.Validate +import org.opensearch.jobscheduler.spi.utils.LockService +import org.opensearch.monitor.jvm.JvmService +import org.opensearch.script.ScriptService +import org.opensearch.test.OpenSearchTestCase +import java.util.* + +class ValidateRolloverTests : OpenSearchTestCase() { + private val scriptService: ScriptService = mock() + private val settings: Settings = Settings.EMPTY + private val clusterService: ClusterService = mock() + private val jvmService: JvmService = mock() + private val indexName: String = "test" + private val metadata = ManagedIndexMetaData( + indexName, "indexUuid", "policy_id", null, null, null, null, null, null, null, + ActionMetaData + ("rollover", 1, 0, false, 0, null, null), + null, null, null + ) + val actionConfig = RolloverAction(null, 3, TimeValue.timeValueDays(2), null, 0) + private val client: Client = mock() + private val lockService: LockService = LockService(mock(), clusterService) + private val validate = ValidateRollover(settings, clusterService, jvmService) + private val clusterState: ClusterState = mock() + private val clusterServiceMetadata: Metadata = mock() + private val indexAbstraction: IndexAbstraction = mock() + private val indicesLookup: SortedMap = mock() + private val listOfMetadata: MutableList = mock() + private val indexMetadata: IndexMetadata = mock() + + fun `test rollover when missing rollover alias`() { + val metadata = metadata.copy() + val context = StepContext(metadata, clusterService, client, null, null, scriptService, settings, lockService) + whenever(context.clusterService.state()).thenReturn(clusterState) + whenever(clusterState.metadata()).thenReturn(clusterServiceMetadata) + whenever(clusterServiceMetadata.indicesLookup).thenReturn(indicesLookup) + whenever(indicesLookup[indexName]).thenReturn(indexAbstraction) + whenever(indexAbstraction.indices).thenReturn(listOfMetadata) + whenever(clusterServiceMetadata.index(indexName)).thenReturn(indexMetadata) + whenever(indexMetadata.settings).thenReturn(settings) + + // null pointer exception + runBlocking { + validate.execute(indexName) + } + assertEquals("Validation status is RE_VALIDATING", Validate.ValidationStatus.RE_VALIDATING, validate.validationStatus) + assertEquals("Info message is NO VALID ALIAS", getFailedNoValidAliasMessage(indexName), validate.validationMessage) + } +} diff --git a/src/test/kotlin/org/opensearch/indexmanagement/rollup/TestHelpers.kt b/src/test/kotlin/org/opensearch/indexmanagement/rollup/TestHelpers.kt index beef89ca0..303336922 100644 --- a/src/test/kotlin/org/opensearch/indexmanagement/rollup/TestHelpers.kt +++ b/src/test/kotlin/org/opensearch/indexmanagement/rollup/TestHelpers.kt @@ -105,8 +105,8 @@ fun randomRollup(): Rollup { jobLastUpdatedTime = randomInstant(), jobEnabledTime = if (enabled) randomInstant() else null, description = OpenSearchRestTestCase.randomAlphaOfLength(10), - sourceIndex = OpenSearchRestTestCase.randomAlphaOfLength(10).toLowerCase(Locale.ROOT), - targetIndex = OpenSearchRestTestCase.randomAlphaOfLength(10).toLowerCase(Locale.ROOT), + sourceIndex = OpenSearchRestTestCase.randomAlphaOfLength(10).lowercase(Locale.ROOT), + targetIndex = OpenSearchRestTestCase.randomAlphaOfLength(10).lowercase(Locale.ROOT), metadataID = if (OpenSearchRestTestCase.randomBoolean()) null else OpenSearchRestTestCase.randomAlphaOfLength(10), roles = OpenSearchRestTestCase.randomList(10) { OpenSearchRestTestCase.randomAlphaOfLength(10) }, pageSize = OpenSearchRestTestCase.randomIntBetween(1, 10000), @@ -173,7 +173,7 @@ fun randomExplainRollup(): ExplainRollup { fun randomISMRollup(): ISMRollup { return ISMRollup( description = OpenSearchRestTestCase.randomAlphaOfLength(10), - targetIndex = OpenSearchRestTestCase.randomAlphaOfLength(10).toLowerCase(Locale.ROOT), + targetIndex = OpenSearchRestTestCase.randomAlphaOfLength(10).lowercase(Locale.ROOT), pageSize = OpenSearchRestTestCase.randomIntBetween(1, 10000), dimensions = randomRollupDimensions(), metrics = OpenSearchRestTestCase.randomList(20, ::randomRollupMetrics).distinctBy { it.targetField } diff --git a/src/test/kotlin/org/opensearch/indexmanagement/transform/TestHelpers.kt b/src/test/kotlin/org/opensearch/indexmanagement/transform/TestHelpers.kt index a08700907..baa8b982e 100644 --- a/src/test/kotlin/org/opensearch/indexmanagement/transform/TestHelpers.kt +++ b/src/test/kotlin/org/opensearch/indexmanagement/transform/TestHelpers.kt @@ -84,8 +84,8 @@ fun randomTransform(): Transform { enabled = enabled, enabledAt = if (enabled) randomInstant() else null, description = OpenSearchRestTestCase.randomAlphaOfLength(10), - sourceIndex = OpenSearchRestTestCase.randomAlphaOfLength(10).toLowerCase(Locale.ROOT), - targetIndex = OpenSearchRestTestCase.randomAlphaOfLength(10).toLowerCase(Locale.ROOT), + sourceIndex = OpenSearchRestTestCase.randomAlphaOfLength(10).lowercase(Locale.ROOT), + targetIndex = OpenSearchRestTestCase.randomAlphaOfLength(10).lowercase(Locale.ROOT), roles = OpenSearchRestTestCase.randomList(10) { OpenSearchRestTestCase.randomAlphaOfLength(10) }, pageSize = if (isContinuous) OpenSearchRestTestCase.randomIntBetween(1, 1000) else OpenSearchRestTestCase.randomIntBetween(1, 10000), groups = randomGroups(), @@ -130,7 +130,7 @@ fun randomShardIDToGlobalCheckpoint(): Map { } fun randomShardID(): ShardId { - val indexName: String = OpenSearchRestTestCase.randomAlphaOfLength(10).toLowerCase(Locale.ROOT) + val indexName: String = OpenSearchRestTestCase.randomAlphaOfLength(10).lowercase(Locale.ROOT) // We lose the index uuid in an XContent round trip, but we don't use it anyways val testIndex = Index(indexName, IndexMetadata.INDEX_UUID_NA_VALUE) val shardNumber: Int = OpenSearchRestTestCase.randomIntBetween(0, 100) @@ -146,7 +146,7 @@ fun randomContinuousStats(): ContinuousTransformStats { fun randomDocumentsBehind(): Map { val numIndices = OpenSearchRestTestCase.randomIntBetween(1, 10) - val randomIndices = (1..numIndices).map { OpenSearchRestTestCase.randomAlphaOfLength(10).toLowerCase(Locale.ROOT) } + val randomIndices = (1..numIndices).map { OpenSearchRestTestCase.randomAlphaOfLength(10).lowercase(Locale.ROOT) } return randomIndices.associateWith { OpenSearchRestTestCase.randomNonNegativeLong() } } diff --git a/worksheets/ism/delete.http b/worksheets/ism/delete.http new file mode 100644 index 000000000..b2f3290ed --- /dev/null +++ b/worksheets/ism/delete.http @@ -0,0 +1,33 @@ +### delete policy +PUT localhost:9200/_opendistro/_ism/policies/exampledelete +Content-Type: application/json + +{ + "policy": { + "description": "Example delete policy.", + "default_state": "delete", + "states": [ + { + "name": "delete", + "actions": [ + { + "delete": {} + } + ], + "transitions": [] + } + ], + "ism_template": { + "index_patterns": ["testdelete"], + "priority": 100 + } + } +} + +### delete index +PUT http://localhost:9200/testdelete +Content-Type: application/json + +### explain api call +GET localhost:9200/_plugins/_ism/explain/testdelete?validate_action=true +Accept: application/json diff --git a/worksheets/ism/rollover.http b/worksheets/ism/rollover.http new file mode 100644 index 000000000..d181f387c --- /dev/null +++ b/worksheets/ism/rollover.http @@ -0,0 +1,133 @@ + + +### policy with notification +PUT localhost:9200/_opendistro/_ism/policies/example +Content-Type: application/json + +{ + "policy": { + "description": "Example rollover policy.", + "default_state": "rollover", + "states": [ + { + "name": "rollover", + "actions": [ + { + "rollover": { + "min_doc_count": 1 + } + } + ], + "transitions": [] + } + ], + "ism_template": { + "index_patterns": ["log*", "testnoalias"], + "priority": 100 + }, + "error_notification": { + "destination": { + "chime": { + "url": "https://hooks.chime.aws/incomingwebhooks/90edf596-a8f0-4451-9003-97a44e6c6105?token=WXRzWm1VVWt8MXxwMzJjUENNWVpJTXZXTXg5NUdDYWdtaUY4ZHlvTzR6bW5rS0ZJLWxQX2dN" + } + }, + "message_template": { + "source": "The index {{ctx.index}} failed during validation." + } + } + } +} + +### policy without notification +PUT localhost:9200/_opendistro/_ism/policies/example +Content-Type: application/json + +{ + "policy": { + "description": "Example rollover policy.", + "default_state": "rollover", + "states": [ + { + "name": "rollover", + "actions": [ + { + "rollover": { + "min_doc_count": 1 + } + } + ], + "transitions": [] + } + ], + "ism_template": { + "index_patterns": ["log*", "testnoalias"], + "priority": 100 + } + } +} + +### delete policy +DELETE localhost:9200/_opendistro/_ism/policies/example + + +### index missing alias +PUT http://localhost:9200/testnoalias +Content-Type: application/json + +### explain API +GET localhost:9200/_plugins/_ism/explain/testnoalias?validate_action=true +Accept: application/json + +### delete index +DELETE http://localhost:9200/testnoalias + +### index not write index +PUT localhost:9200/_index_template/ism_rollover +Content-Type: application/json + +{ + "index_patterns": ["log*"], + "template": { + "settings": { + "plugins.index_state_management.rollover_alias": "log" + } + } +} + +### set is write index to false +PUT localhost:9200/log-000002 +Content-Type: application/json + +{ + "aliases": { + "log": { + "is_write_index": false + } + } +} + +### set is write index to true +PUT localhost:9200/log-000003 +Content-Type: application/json + +{ + "aliases": { + "log": { + "is_write_index": true + } + } +} + +### call explain API +GET localhost:9200/_plugins/_ism/explain/log-000002?validate_action=true +Accept: application/json + +### call explain API +GET localhost:9200/_plugins/_ism/explain/log-000003?validate_action=true +Accept: application/json + +### delete index +DELETE http://localhost:9200/log-000002 + +### delete index +DELETE http://localhost:9200/log-000003