diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index f497235fa..fbc76ee75 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -3,7 +3,7 @@
*Description of changes:*
*CheckList:*
-[ ] Commits are signed per the DCO using --signoff
+- [ ] Commits are signed per the DCO using --signoff
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check [here](https://github.com/opensearch-project/index-management/blob/main/CONTRIBUTING.md#developer-certificate-of-origin).
diff --git a/.github/draft-release-notes-config.yml b/.github/draft-release-notes-config.yml
index 8f4636f0c..bd620b93d 100644
--- a/.github/draft-release-notes-config.yml
+++ b/.github/draft-release-notes-config.yml
@@ -14,7 +14,7 @@ replacers:
- search: '##'
replace: '###'
-# Organizing the tagged PRs into unified ODFE categories
+# Organizing the tagged PRs into unified OpenSearch categories
categories:
- title: 'Breaking changes'
labels:
@@ -45,4 +45,4 @@ categories:
- 'backwards-compatibility'
- title: 'Refactoring'
labels:
- - 'Refactor'
+ - 'refactor'
diff --git a/.github/workflows/multi-node-test-workflow.yml b/.github/workflows/multi-node-test-workflow.yml
index d1fc68499..98ad25fbf 100644
--- a/.github/workflows/multi-node-test-workflow.yml
+++ b/.github/workflows/multi-node-test-workflow.yml
@@ -20,51 +20,11 @@ jobs:
uses: actions/setup-java@v1
with:
java-version: 14
- # dependencies: OpenSearch
- - name: Checkout OpenSearch
- uses: actions/checkout@v2
- with:
- repository: 'opensearch-project/OpenSearch'
- path: OpenSearch
- ref: '1.0'
- - name: Build OpenSearch
- working-directory: ./OpenSearch
- run: ./gradlew publishToMavenLocal -Dbuild.snapshot=false
- # dependencies: common-utils
- - name: Checkout common-utils
- uses: actions/checkout@v2
- with:
- repository: 'opensearch-project/common-utils'
- path: common-utils
- ref: '1.0'
- - name: Build common-utils
- working-directory: ./common-utils
- run: ./gradlew publishToMavenLocal -Dopensearch.version=1.0.0
- # dependencies: job-scheduler
- - name: Checkout job-scheduler
- uses: actions/checkout@v2
- with:
- repository: 'opensearch-project/job-scheduler'
- path: job-scheduler
- ref: '1.0'
- - name: Build job-scheduler
- working-directory: ./job-scheduler
- run: ./gradlew publishToMavenLocal -Dopensearch.version=1.0.0 -Dbuild.snapshot=false
- # dependencies: alerting-notification
- - name: Checkout alerting
- uses: actions/checkout@v2
- with:
- repository: 'opensearch-project/alerting'
- path: alerting
- ref: '1.0'
- - name: Build alerting
- working-directory: ./alerting
- run: ./gradlew :alerting-notification:publishToMavenLocal -Dopensearch.version=1.0.0 -Dbuild.snapshot=false
# index-management
- name: Checkout Branch
uses: actions/checkout@v2
- name: Run integration tests with multi node config
- run: ./gradlew integTest -PnumNodes=3
+ run: ./gradlew integTest -PnumNodes=3 -Dopensearch.version=1.2.0-SNAPSHOT
- name: Upload failed logs
uses: actions/upload-artifact@v2
if: failure()
diff --git a/.github/workflows/test-and-build-workflow.yml b/.github/workflows/test-and-build-workflow.yml
index 2f2d55c70..011d3ee9f 100644
--- a/.github/workflows/test-and-build-workflow.yml
+++ b/.github/workflows/test-and-build-workflow.yml
@@ -20,51 +20,11 @@ jobs:
uses: actions/setup-java@v1
with:
java-version: 14
- # dependencies: OpenSearch
- - name: Checkout OpenSearch
- uses: actions/checkout@v2
- with:
- repository: 'opensearch-project/OpenSearch'
- path: OpenSearch
- ref: '1.0'
- - name: Build OpenSearch
- working-directory: ./OpenSearch
- run: ./gradlew publishToMavenLocal -Dbuild.snapshot=false
- # dependencies: common-utils
- - name: Checkout common-utils
- uses: actions/checkout@v2
- with:
- repository: 'opensearch-project/common-utils'
- path: common-utils
- ref: '1.0'
- - name: Build common-utils
- working-directory: ./common-utils
- run: ./gradlew publishToMavenLocal -Dopensearch.version=1.0.0
- # dependencies: job-scheduler
- - name: Checkout job-scheduler
- uses: actions/checkout@v2
- with:
- repository: 'opensearch-project/job-scheduler'
- path: job-scheduler
- ref: '1.0'
- - name: Build job-scheduler
- working-directory: ./job-scheduler
- run: ./gradlew publishToMavenLocal -Dopensearch.version=1.0.0 -Dbuild.snapshot=false
- # dependencies: alerting-notification
- - name: Checkout alerting
- uses: actions/checkout@v2
- with:
- repository: 'opensearch-project/alerting'
- path: alerting
- ref: '1.0'
- - name: Build alerting
- working-directory: ./alerting
- run: ./gradlew :alerting-notification:publishToMavenLocal -Dopensearch.version=1.0.0 -Dbuild.snapshot=false
- # index-management
+ # build index management
- name: Checkout Branch
uses: actions/checkout@v2
- name: Build with Gradle
- run: ./gradlew build
+ run: ./gradlew build -Dopensearch.version=1.2.0-SNAPSHOT
- name: Upload failed logs
uses: actions/upload-artifact@v2
if: failure()
diff --git a/.gitignore b/.gitignore
index 4f755f873..037f7a3e3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,12 @@
.gradle/
build/
out/
-.idea/
+.idea/*
+!.idea/copyright
*.ipr
*.iws
.DS_Store
*.log
-http
\ No newline at end of file
+http
+.project
+.settings
\ No newline at end of file
diff --git a/.idea/copyright/OpenSearch.xml b/.idea/copyright/OpenSearch.xml
new file mode 100644
index 000000000..dadc9ed84
--- /dev/null
+++ b/.idea/copyright/OpenSearch.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml
new file mode 100644
index 000000000..5f45523cc
--- /dev/null
+++ b/.idea/copyright/profiles_settings.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/NOTICE b/NOTICE
index be83767d4..731cb6006 100644
--- a/NOTICE
+++ b/NOTICE
@@ -1,12 +1,2 @@
-OpenSearch
-Copyright 2021 OpenSearch Contributors
-
-This product includes software developed by
-Elasticsearch (http://www.elastic.co).
-Copyright 2009-2018 Elasticsearch
-
-This product includes software developed by The Apache Software
-Foundation (http://www.apache.org/).
-
-This product includes software developed by
-Joda.org (http://www.joda.org/).
+OpenSearch (https://opensearch.org/)
+Copyright OpenSearch Contributors
diff --git a/README.md b/README.md
index 848c0de4d..a98c73bfa 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
[![Test and Build Workflow](https://github.com/opensearch-project/index-management/workflows/Test%20and%20Build%20Workflow/badge.svg)](https://github.com/opensearch-project/index-management/actions)
[![codecov](https://codecov.io/gh/opensearch-project/index-management/branch/main/graph/badge.svg)](https://codecov.io/gh/opensearch-project/index-management)
-[![Documentation](https://img.shields.io/badge/api-reference-blue.svg)](https://docs-beta.opensearch.org/im-plugin/index/)
+[![Documentation](https://img.shields.io/badge/api-reference-blue.svg)](https://opensearch.org/docs/im-plugin/index/)
[![Chat](https://img.shields.io/badge/chat-on%20forums-blue)](https://discuss.opendistrocommunity.dev/c/index-management/)
![PRs welcome!](https://img.shields.io/badge/PRs-welcome!-success)
@@ -58,7 +58,7 @@ See [developer guide](DEVELOPER_GUIDE.md) and [how to contribute to this project
If you find a bug, or have a feature request, please don't hesitate to open an issue in this repository.
-For more information, see [project website](https://opensearch.org/) and [documentation](https://docs-beta.opensearch.org/). If you need help and are unsure where to open an issue, try [forums](https://discuss.opendistrocommunity.dev/).
+For more information, see [project website](https://opensearch.org/) and [documentation](https://opensearch.org/docs/). If you need help and are unsure where to open an issue, try [forums](https://discuss.opendistrocommunity.dev/).
## Code of Conduct
@@ -74,4 +74,4 @@ This project is licensed under the [Apache v2.0 License](./LICENSE)
## Copyright
-Copyright 2020-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+Copyright OpenSearch Contributors. See [NOTICE](NOTICE) for details.
diff --git a/build-tools/coverage.gradle b/build-tools/coverage.gradle
index d24749dc8..33a12c8c7 100644
--- a/build-tools/coverage.gradle
+++ b/build-tools/coverage.gradle
@@ -1,3 +1,8 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
/*
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
diff --git a/build-tools/pkgbuild.gradle b/build-tools/pkgbuild.gradle
index 7aaa7fe43..ee9c92b4e 100644
--- a/build-tools/pkgbuild.gradle
+++ b/build-tools/pkgbuild.gradle
@@ -1,3 +1,8 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
/*
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
diff --git a/build.gradle b/build.gradle
index 474c9c2da..06e1046cd 100644
--- a/build.gradle
+++ b/build.gradle
@@ -31,14 +31,20 @@ import java.util.function.Predicate
buildscript {
ext {
- opensearch_version = System.getProperty("opensearch.version", "1.0.0")
- kotlin_version = System.getProperty("kotlin.version", "1.3.72")
+ opensearch_version = System.getProperty("opensearch.version", "1.2.0-SNAPSHOT")
+ // 1.1.0 -> 1.1.0.0, and 1.1.0-SNAPSHOT -> 1.1.0.0-SNAPSHOT
+ opensearch_build = opensearch_version.replaceAll(/(\.\d)([^\d]*)$/, '$1.0$2')
+ notification_version = System.getProperty("notification.version", opensearch_build)
+ common_utils_version = System.getProperty("common_utils.version", opensearch_build)
+ job_scheduler_version = System.getProperty("job_scheduler_version.version", opensearch_build)
+ kotlin_version = System.getProperty("kotlin.version", "1.4.0")
}
repositories {
mavenLocal()
mavenCentral()
maven { url "https://plugins.gradle.org/m2/" }
+ maven { url "https://aws.oss.sonatype.org/content/repositories/snapshots" }
}
dependencies {
@@ -139,30 +145,35 @@ configurations.testCompile {
ext {
projectSubstitutions = [:]
- opensearchVersion = "${version}"
isSnapshot = "true" == System.getProperty("build.snapshot", "true")
licenseFile = rootProject.file('LICENSE')
noticeFile = rootProject.file('NOTICE')
}
-group = "org.opensearch"
-version = "${opensearchVersion}.0"
+allprojects {
+ group = "org.opensearch"
+ version = "${opensearch_version}" - "-SNAPSHOT" + ".0"
+ if (isSnapshot) {
+ version += "-SNAPSHOT"
+ }
+}
dependencies {
compileOnly "org.opensearch:opensearch:${opensearch_version}"
- compileOnly "org.opensearch:opensearch-job-scheduler-spi:1.0.0.0"
+ compileOnly "org.opensearch:opensearch-job-scheduler-spi:${job_scheduler_version}"
compile "org.jetbrains.kotlin:kotlin-stdlib:${kotlin_version}"
compile "org.jetbrains.kotlin:kotlin-stdlib-common:${kotlin_version}"
- compile 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7'
+ compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlin_version}"
+ compile 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9'
compile "org.jetbrains:annotations:13.0"
- compile "org.opensearch:notification:1.0.0.0"
- compile "org.opensearch:common-utils:1.0.0.0"
+ compile "org.opensearch:notification:${notification_version}"
+ compile "org.opensearch:common-utils:${common_utils_version}"
compile "com.github.seancfoley:ipaddress:5.3.3"
testCompile "org.opensearch.test:framework:${opensearch_version}"
testCompile "org.jetbrains.kotlin:kotlin-test:${kotlin_version}"
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
- testCompile "org.mockito:mockito-core:2.23.0"
+ testCompile "org.mockito:mockito-core:3.12.4"
add("ktlint", "com.pinterest:ktlint:0.41.0") {
attributes {
@@ -173,10 +184,7 @@ dependencies {
repositories {
mavenLocal()
-}
-
-if (isSnapshot) {
- version += "-SNAPSHOT"
+ maven { url "https://aws.oss.sonatype.org/content/repositories/snapshots" }
}
plugins.withId('java') {
@@ -186,7 +194,6 @@ plugins.withId('java') {
plugins.withId('org.jetbrains.kotlin.jvm') {
compileKotlin.kotlinOptions.jvmTarget = compileTestKotlin.kotlinOptions.jvmTarget = "1.8"
compileKotlin.dependsOn ktlint
-
}
javadoc.enabled = false // turn off javadoc as it barfs on Kotlin code
@@ -261,6 +268,7 @@ testClusters.integTest {
File getAsFile() { fileTree("src/test/resources/job-scheduler").getSingleFile() }
}
}))
+
if (securityEnabled) {
plugin(provider({
new RegularFile() {
diff --git a/docs/rfc.md b/docs/rfc.md
index d0fafc584..f860bc4e7 100644
--- a/docs/rfc.md
+++ b/docs/rfc.md
@@ -113,4 +113,4 @@ After building Index State Management API, we will build an administrative panel
## Providing Feedback
-If you have comments or feedback on our plans for Index Management, please comment on [the RFC Github issue](../../issues/1) in this project to discuss.
+If you have comments or feedback on our plans for Index Management, please comment on [the RFC Github issue](https://github.com/opendistro-for-elasticsearch/index-management/issues/1) in this project to discuss.
diff --git a/gradle.properties b/gradle.properties
deleted file mode 100644
index 43ca52b80..000000000
--- a/gradle.properties
+++ /dev/null
@@ -1,26 +0,0 @@
-# SPDX-License-Identifier: Apache-2.0
-#
-# The OpenSearch Contributors require contributions made to
-# this file be licensed under the Apache-2.0 license or a
-# compatible open source license.
-#
-# Modifications Copyright OpenSearch Contributors. See
-# GitHub history for details.
-#
-
-#
-# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License").
-# You may not use this file except in compliance with the License.
-# A copy of the License is located at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# or in the "license" file accompanying this file. This file is distributed
-# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
-# express or implied. See the License for the specific language governing
-# permissions and limitations under the License.
-#
-
-version = 1.0.0
diff --git a/release-notes/opensearch-index-management.release-notes-1.1.0.0.md b/release-notes/opensearch-index-management.release-notes-1.1.0.0.md
new file mode 100644
index 000000000..27b5552c9
--- /dev/null
+++ b/release-notes/opensearch-index-management.release-notes-1.1.0.0.md
@@ -0,0 +1,23 @@
+## Version 1.1.0.0 2021-09-03
+
+Compatible with OpenSearch 1.1.0
+
+### Infrastructure
+
+* Upgrade dependencies to 1.1 and build snapshot by default. ([#121](https://github.com/opensearch-project/index-management/pull/121))
+
+### Features
+
+* Storing user information as part of the job when security plugin is installed ([#113](https://github.com/opensearch-project/index-management/pull/113))
+* Storing user object in all APIs and enabling filter of response based on user ([#115](https://github.com/opensearch-project/index-management/pull/115))
+* Security improvements ([#126](https://github.com/opensearch-project/index-management/pull/126))
+* Updating security filtering logic ([#137](https://github.com/opensearch-project/index-management/pull/137))
+
+### Enhancements
+
+* Enhance ISM template ([#105](https://github.com/opensearch-project/index-management/pull/105))
+
+### Bug Fixes
+
+* Removing Usages of Action Get Call and using listeners ([#100](https://github.com/opensearch-project/index-management/pull/100))
+* Explain response still use old opendistro policy id ([#109](https://github.com/opensearch-project/index-management/pull/109))
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/IndexManagementPlugin.kt b/src/main/kotlin/org/opensearch/indexmanagement/IndexManagementPlugin.kt
index d704f73d4..f5130a257 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/IndexManagementPlugin.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/IndexManagementPlugin.kt
@@ -82,6 +82,8 @@ import org.opensearch.indexmanagement.indexstatemanagement.transport.action.getp
import org.opensearch.indexmanagement.indexstatemanagement.transport.action.getpolicy.TransportGetPolicyAction
import org.opensearch.indexmanagement.indexstatemanagement.transport.action.indexpolicy.IndexPolicyAction
import org.opensearch.indexmanagement.indexstatemanagement.transport.action.indexpolicy.TransportIndexPolicyAction
+import org.opensearch.indexmanagement.indexstatemanagement.transport.action.managedIndex.ManagedIndexAction
+import org.opensearch.indexmanagement.indexstatemanagement.transport.action.managedIndex.TransportManagedIndexAction
import org.opensearch.indexmanagement.indexstatemanagement.transport.action.removepolicy.RemovePolicyAction
import org.opensearch.indexmanagement.indexstatemanagement.transport.action.removepolicy.TransportRemovePolicyAction
import org.opensearch.indexmanagement.indexstatemanagement.transport.action.retryfailedmanagedindex.RetryFailedManagedIndexAction
@@ -124,6 +126,7 @@ import org.opensearch.indexmanagement.rollup.resthandler.RestStartRollupAction
import org.opensearch.indexmanagement.rollup.resthandler.RestStopRollupAction
import org.opensearch.indexmanagement.rollup.settings.LegacyOpenDistroRollupSettings
import org.opensearch.indexmanagement.rollup.settings.RollupSettings
+import org.opensearch.indexmanagement.settings.IndexManagementSettings
import org.opensearch.indexmanagement.transform.TransformRunner
import org.opensearch.indexmanagement.transform.action.delete.DeleteTransformsAction
import org.opensearch.indexmanagement.transform.action.delete.TransportDeleteTransformsAction
@@ -301,7 +304,15 @@ class IndexManagementPlugin : JobSchedulerExtension, NetworkPlugin, ActionPlugin
this.clusterService = clusterService
rollupInterceptor = RollupInterceptor(clusterService, settings, indexNameExpressionResolver)
val jvmService = JvmService(environment.settings())
- val transformRunner = TransformRunner.initialize(client, clusterService, xContentRegistry, settings, indexNameExpressionResolver, jvmService)
+ val transformRunner = TransformRunner.initialize(
+ client,
+ clusterService,
+ xContentRegistry,
+ settings,
+ indexNameExpressionResolver,
+ jvmService,
+ threadPool
+ )
fieldCapsFilter = FieldCapsFilter(clusterService, settings, indexNameExpressionResolver)
this.indexNameExpressionResolver = indexNameExpressionResolver
@@ -339,6 +350,7 @@ class IndexManagementPlugin : JobSchedulerExtension, NetworkPlugin, ActionPlugin
.registerIMIndex(indexManagementIndices)
.registerHistoryIndex(indexStateManagementHistory)
.registerSkipFlag(skipFlag)
+ .registerThreadPool(threadPool)
val metadataService = MetadataService(client, clusterService, skipFlag, indexManagementIndices)
@@ -367,6 +379,8 @@ class IndexManagementPlugin : JobSchedulerExtension, NetworkPlugin, ActionPlugin
ManagedIndexSettings.ROLLOVER_SKIP,
ManagedIndexSettings.INDEX_STATE_MANAGEMENT_ENABLED,
ManagedIndexSettings.METADATA_SERVICE_ENABLED,
+ ManagedIndexSettings.AUTO_MANAGE,
+ ManagedIndexSettings.JITTER,
ManagedIndexSettings.JOB_INTERVAL,
ManagedIndexSettings.SWEEP_PERIOD,
ManagedIndexSettings.COORDINATOR_BACKOFF_COUNT,
@@ -381,12 +395,14 @@ class IndexManagementPlugin : JobSchedulerExtension, NetworkPlugin, ActionPlugin
RollupSettings.ROLLUP_ENABLED,
RollupSettings.ROLLUP_SEARCH_ENABLED,
RollupSettings.ROLLUP_DASHBOARDS,
+ RollupSettings.ROLLUP_SEARCH_ALL_JOBS,
TransformSettings.TRANSFORM_JOB_INDEX_BACKOFF_COUNT,
TransformSettings.TRANSFORM_JOB_INDEX_BACKOFF_MILLIS,
TransformSettings.TRANSFORM_JOB_SEARCH_BACKOFF_COUNT,
TransformSettings.TRANSFORM_JOB_SEARCH_BACKOFF_MILLIS,
TransformSettings.TRANSFORM_CIRCUIT_BREAKER_ENABLED,
TransformSettings.TRANSFORM_CIRCUIT_BREAKER_JVM_THRESHOLD,
+ IndexManagementSettings.FILTER_BY_BACKEND_ROLES,
LegacyOpenDistroManagedIndexSettings.HISTORY_ENABLED,
LegacyOpenDistroManagedIndexSettings.HISTORY_INDEX_MAX_AGE,
LegacyOpenDistroManagedIndexSettings.HISTORY_MAX_DOCS,
@@ -443,7 +459,8 @@ class IndexManagementPlugin : JobSchedulerExtension, NetworkPlugin, ActionPlugin
ActionPlugin.ActionHandler(DeleteTransformsAction.INSTANCE, TransportDeleteTransformsAction::class.java),
ActionPlugin.ActionHandler(ExplainTransformAction.INSTANCE, TransportExplainTransformAction::class.java),
ActionPlugin.ActionHandler(StartTransformAction.INSTANCE, TransportStartTransformAction::class.java),
- ActionPlugin.ActionHandler(StopTransformAction.INSTANCE, TransportStopTransformAction::class.java)
+ ActionPlugin.ActionHandler(StopTransformAction.INSTANCE, TransportStopTransformAction::class.java),
+ ActionPlugin.ActionHandler(ManagedIndexAction.INSTANCE, TransportManagedIndexAction::class.java)
)
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/ISMTemplateService.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/ISMTemplateService.kt
index 8d896ce45..fdd71543f 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/ISMTemplateService.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/ISMTemplateService.kt
@@ -26,65 +26,14 @@
package org.opensearch.indexmanagement.indexstatemanagement
-import org.apache.logging.log4j.LogManager
import org.apache.lucene.util.automaton.Operations
import org.opensearch.OpenSearchException
-import org.opensearch.cluster.ClusterState
-import org.opensearch.cluster.metadata.IndexMetadata
import org.opensearch.common.Strings
import org.opensearch.common.ValidationException
import org.opensearch.common.regex.Regex
import org.opensearch.indexmanagement.indexstatemanagement.model.ISMTemplate
import org.opensearch.indexmanagement.util.IndexManagementException
-private val log = LogManager.getLogger("ISMTemplateService")
-
-/**
- * find the matching policy based on ISM template field for the given index
- *
- * filter out hidden index
- * filter out older index than template lastUpdateTime
- *
- * @param ismTemplates current ISM templates saved in metadata
- * @param indexMetadata cluster state index metadata
- * @return policyID
- */
-@Suppress("ReturnCount")
-fun Map.findMatchingPolicy(clusterState: ClusterState, indexName: String): String? {
- if (this.isEmpty()) return null
-
- val indexMetadata = clusterState.metadata.index(indexName)
- val indexAbstraction = clusterState.metadata.indicesLookup[indexName]
- val isDataStreamIndex = indexAbstraction?.parentDataStream != null
-
- // Don't include hidden index unless it belongs to a data stream.
- val isHidden = IndexMetadata.INDEX_HIDDEN_SETTING.get(indexMetadata.settings)
- if (!isDataStreamIndex && isHidden) return null
-
- // If the index belongs to a data stream, then find the matching policy using the data stream name.
- val lookupName = when {
- isDataStreamIndex -> indexAbstraction?.parentDataStream?.name
- else -> indexName
- }
-
- // only process indices created after template
- // traverse all ism templates for matching ones
- val patternMatchPredicate = { pattern: String -> Regex.simpleMatch(pattern, lookupName) }
- var matchedPolicy: String? = null
- var highestPriority: Int = -1
- this.filter { (_, template) ->
- template.lastUpdatedTime.toEpochMilli() < indexMetadata.creationDate
- }.forEach { (policyID, template) ->
- val matched = template.indexPatterns.stream().anyMatch(patternMatchPredicate)
- if (matched && highestPriority < template.priority) {
- highestPriority = template.priority
- matchedPolicy = policyID
- }
- }
-
- return matchedPolicy
-}
-
/**
* validate the template Name and indexPattern provided in the template
*
@@ -120,30 +69,61 @@ fun validateFormat(indexPatterns: List): OpenSearchException? {
return null
}
+fun List.findSelfConflictingTemplates(): Pair, List>? {
+ val priorityToTemplates = mutableMapOf>()
+ this.forEach {
+ val templateList = priorityToTemplates[it.priority]
+ if (templateList != null) {
+ priorityToTemplates[it.priority] = templateList.plus(it)
+ } else {
+ priorityToTemplates[it.priority] = mutableListOf(it)
+ }
+ }
+ priorityToTemplates.forEach { (_, templateList) ->
+ // same priority
+ val indexPatternsList = templateList.map { it.indexPatterns }
+ if (indexPatternsList.size > 1) {
+ indexPatternsList.forEachIndexed { ind, indexPatterns ->
+ val comparePatterns = indexPatternsList.subList(ind + 1, indexPatternsList.size).flatten()
+ if (overlapping(indexPatterns, comparePatterns)) {
+ return indexPatterns to comparePatterns
+ }
+ }
+ }
+ }
+
+ return null
+}
+
+@Suppress("SpreadOperator")
+fun overlapping(p1: List, p2: List): Boolean {
+ if (p1.isEmpty() || p2.isEmpty()) return false
+ val a1 = Regex.simpleMatchToAutomaton(*p1.toTypedArray())
+ val a2 = Regex.simpleMatchToAutomaton(*p2.toTypedArray())
+ return !Operations.isEmpty(Operations.intersection(a1, a2))
+}
+
/**
* find policy templates whose index patterns overlap with given template
*
* @return map of overlapping template name to its index patterns
*/
-@Suppress("SpreadOperator")
-fun Map.findConflictingPolicyTemplates(
+fun Map>.findConflictingPolicyTemplates(
candidate: String,
indexPatterns: List,
priority: Int
): Map> {
- val automaton1 = Regex.simpleMatchToAutomaton(*indexPatterns.toTypedArray())
val overlappingTemplates = mutableMapOf>()
- // focus on template with same priority
- this.filter { it.value.priority == priority }
- .forEach { (policyID, template) ->
- val automaton2 = Regex.simpleMatchToAutomaton(*template.indexPatterns.toTypedArray())
- if (!Operations.isEmpty(Operations.intersection(automaton1, automaton2))) {
- log.info("Existing ism_template for $policyID overlaps candidate $candidate")
- overlappingTemplates[policyID] = template.indexPatterns
+ this.forEach { (policyID, templateList) ->
+ templateList.filter { it.priority == priority }
+ .map { it.indexPatterns }
+ .forEach {
+ if (overlapping(indexPatterns, it)) {
+ overlappingTemplates[policyID] = it
+ }
}
- }
+ }
overlappingTemplates.remove(candidate)
-
return overlappingTemplates
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/IndexStateManagementHistory.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/IndexStateManagementHistory.kt
index 69ff4ffa3..fd51d9a09 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/IndexStateManagementHistory.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/IndexStateManagementHistory.kt
@@ -30,6 +30,7 @@ import org.apache.logging.log4j.LogManager
import org.opensearch.action.ActionListener
import org.opensearch.action.DocWriteRequest
import org.opensearch.action.admin.cluster.state.ClusterStateRequest
+import org.opensearch.action.admin.cluster.state.ClusterStateResponse
import org.opensearch.action.admin.indices.delete.DeleteIndexRequest
import org.opensearch.action.admin.indices.rollover.RolloverRequest
import org.opensearch.action.admin.indices.rollover.RolloverResponse
@@ -37,6 +38,7 @@ import org.opensearch.action.bulk.BulkRequest
import org.opensearch.action.bulk.BulkResponse
import org.opensearch.action.index.IndexRequest
import org.opensearch.action.support.IndicesOptions
+import org.opensearch.action.support.master.AcknowledgedResponse
import org.opensearch.client.Client
import org.opensearch.cluster.LocalNodeMasterListener
import org.opensearch.cluster.service.ClusterService
@@ -44,7 +46,6 @@ import org.opensearch.common.settings.Settings
import org.opensearch.common.xcontent.ToXContent
import org.opensearch.common.xcontent.XContentFactory
import org.opensearch.common.xcontent.XContentType
-import org.opensearch.index.IndexNotFoundException
import org.opensearch.indexmanagement.IndexManagementIndices
import org.opensearch.indexmanagement.IndexManagementPlugin
import org.opensearch.indexmanagement.indexstatemanagement.model.ManagedIndexMetaData
@@ -61,6 +62,7 @@ import org.opensearch.threadpool.ThreadPool
import java.time.Instant
@OpenForTesting
+@Suppress("TooManyFunctions")
class IndexStateManagementHistory(
settings: Settings,
private val client: Client,
@@ -105,7 +107,7 @@ class IndexStateManagementHistory(
override fun onMaster() {
try {
// try to rollover immediately as we might be restarting the cluster
- rolloverHistoryIndex()
+ if (historyEnabled) rolloverHistoryIndex()
// schedule the next rollover for approx MAX_AGE later
scheduledRollover = threadPool.scheduleWithFixedDelay(
{ rolloverAndDeleteHistoryIndex() },
@@ -176,7 +178,6 @@ class IndexStateManagementHistory(
@Suppress("SpreadOperator", "NestedBlockDepth", "ComplexMethod")
private fun deleteOldHistoryIndex() {
- val indexToDelete = mutableListOf()
val clusterStateRequest = ClusterStateRequest()
.clear()
@@ -185,8 +186,28 @@ class IndexStateManagementHistory(
.local(true)
.indicesOptions(IndicesOptions.strictExpand())
- val clusterStateResponse = client.admin().cluster().state(clusterStateRequest).actionGet()
+ client.admin().cluster().state(
+ clusterStateRequest,
+ object : ActionListener {
+ override fun onResponse(clusterStateResponse: ClusterStateResponse) {
+ if (!clusterStateResponse.state.metadata.indices.isEmpty) {
+ val indicesToDelete = getIndicesToDelete(clusterStateResponse)
+ logger.info("Deleting old history indices viz $indicesToDelete")
+ deleteAllOldHistoryIndices(indicesToDelete)
+ } else {
+ logger.info("No Old History Indices to delete")
+ }
+ }
+
+ override fun onFailure(exception: Exception) {
+ logger.error("Error fetching cluster state ${exception.message}")
+ }
+ }
+ )
+ }
+ private fun getIndicesToDelete(clusterStateResponse: ClusterStateResponse): List {
+ var indicesToDelete = mutableListOf()
for (entry in clusterStateResponse.state.metadata.indices()) {
val indexMetaData = entry.value
val creationTime = indexMetaData.creationDate
@@ -198,27 +219,51 @@ class IndexStateManagementHistory(
continue
}
- indexToDelete.add(indexMetaData.index.name)
+ indicesToDelete.add(indexMetaData.index.name)
}
}
+ return indicesToDelete
+ }
+
+ @Suppress("SpreadOperator")
+ private fun deleteAllOldHistoryIndices(indicesToDelete: List) {
+ if (indicesToDelete.isNotEmpty()) {
+ val deleteRequest = DeleteIndexRequest(*indicesToDelete.toTypedArray())
+ client.admin().indices().delete(
+ deleteRequest,
+ object : ActionListener {
+ override fun onResponse(deleteIndicesResponse: AcknowledgedResponse) {
+ if (!deleteIndicesResponse.isAcknowledged) {
+ logger.error("could not delete one or more ISM history index. $indicesToDelete. Retrying one by one.")
+ deleteOldHistoryIndex(indicesToDelete)
+ }
+ }
+ override fun onFailure(exception: Exception) {
+ logger.error("Error deleting old history indices ${exception.message}")
+ deleteOldHistoryIndex(indicesToDelete)
+ }
+ }
+ )
+ }
+ }
- if (indexToDelete.isNotEmpty()) {
- val deleteRequest = DeleteIndexRequest(*indexToDelete.toTypedArray())
- val deleteResponse = client.admin().indices().delete(deleteRequest).actionGet()
- if (!deleteResponse.isAcknowledged) {
- logger.error("could not delete one or more ISM history index. $indexToDelete. Retrying one by one.")
- for (index in indexToDelete) {
- try {
- val singleDeleteRequest = DeleteIndexRequest(*indexToDelete.toTypedArray())
- val singleDeleteResponse = client.admin().indices().delete(singleDeleteRequest).actionGet()
+ @Suppress("SpreadOperator")
+ private fun deleteOldHistoryIndex(indicesToDelete: List) {
+ for (index in indicesToDelete) {
+ val singleDeleteRequest = DeleteIndexRequest(*indicesToDelete.toTypedArray())
+ client.admin().indices().delete(
+ singleDeleteRequest,
+ object : ActionListener {
+ override fun onResponse(singleDeleteResponse: AcknowledgedResponse) {
if (!singleDeleteResponse.isAcknowledged) {
logger.error("could not delete one or more ISM history index. $index.")
}
- } catch (e: IndexNotFoundException) {
- logger.debug("$index was already deleted. ${e.message}")
+ }
+ override fun onFailure(exception: Exception) {
+ logger.debug("Exception ${exception.message} while deleting the index $index")
}
}
- }
+ )
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/ManagedIndexCoordinator.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/ManagedIndexCoordinator.kt
index 4441d475d..4018bb703 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/ManagedIndexCoordinator.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/ManagedIndexCoordinator.kt
@@ -34,45 +34,53 @@ import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import org.apache.logging.log4j.LogManager
import org.opensearch.ExceptionsHelper
+import org.opensearch.OpenSearchSecurityException
import org.opensearch.action.DocWriteRequest
import org.opensearch.action.bulk.BackoffPolicy
import org.opensearch.action.bulk.BulkRequest
import org.opensearch.action.bulk.BulkResponse
import org.opensearch.action.get.MultiGetRequest
import org.opensearch.action.get.MultiGetResponse
+import org.opensearch.action.index.IndexRequest
import org.opensearch.action.search.SearchPhaseExecutionException
import org.opensearch.action.search.SearchRequest
import org.opensearch.action.search.SearchResponse
+import org.opensearch.action.support.master.AcknowledgedResponse
import org.opensearch.action.update.UpdateRequest
import org.opensearch.client.Client
import org.opensearch.cluster.ClusterChangedEvent
import org.opensearch.cluster.ClusterState
import org.opensearch.cluster.ClusterStateListener
import org.opensearch.cluster.block.ClusterBlockException
+import org.opensearch.cluster.metadata.IndexMetadata
import org.opensearch.cluster.service.ClusterService
import org.opensearch.common.component.LifecycleListener
+import org.opensearch.common.regex.Regex
import org.opensearch.common.settings.Settings
import org.opensearch.common.unit.TimeValue
+import org.opensearch.commons.authuser.User
import org.opensearch.index.Index
import org.opensearch.index.IndexNotFoundException
import org.opensearch.index.query.QueryBuilders
import org.opensearch.indexmanagement.IndexManagementIndices
import org.opensearch.indexmanagement.IndexManagementPlugin
import org.opensearch.indexmanagement.IndexManagementPlugin.Companion.INDEX_MANAGEMENT_INDEX
-import org.opensearch.indexmanagement.indexstatemanagement.model.ISMTemplate
import org.opensearch.indexmanagement.indexstatemanagement.model.ManagedIndexConfig
import org.opensearch.indexmanagement.indexstatemanagement.model.ManagedIndexMetaData
+import org.opensearch.indexmanagement.indexstatemanagement.model.Policy
import org.opensearch.indexmanagement.indexstatemanagement.model.coordinator.ClusterStateManagedIndexConfig
import org.opensearch.indexmanagement.indexstatemanagement.model.coordinator.SweptManagedIndexConfig
-import org.opensearch.indexmanagement.indexstatemanagement.opensearchapi.filterNotNullValues
-import org.opensearch.indexmanagement.indexstatemanagement.opensearchapi.getPolicyToTemplateMap
import org.opensearch.indexmanagement.indexstatemanagement.opensearchapi.mgetManagedIndexMetadata
+import org.opensearch.indexmanagement.indexstatemanagement.settings.ManagedIndexSettings.Companion.AUTO_MANAGE
import org.opensearch.indexmanagement.indexstatemanagement.settings.ManagedIndexSettings.Companion.COORDINATOR_BACKOFF_COUNT
import org.opensearch.indexmanagement.indexstatemanagement.settings.ManagedIndexSettings.Companion.COORDINATOR_BACKOFF_MILLIS
import org.opensearch.indexmanagement.indexstatemanagement.settings.ManagedIndexSettings.Companion.INDEX_STATE_MANAGEMENT_ENABLED
+import org.opensearch.indexmanagement.indexstatemanagement.settings.ManagedIndexSettings.Companion.JITTER
import org.opensearch.indexmanagement.indexstatemanagement.settings.ManagedIndexSettings.Companion.JOB_INTERVAL
import org.opensearch.indexmanagement.indexstatemanagement.settings.ManagedIndexSettings.Companion.METADATA_SERVICE_ENABLED
import org.opensearch.indexmanagement.indexstatemanagement.settings.ManagedIndexSettings.Companion.SWEEP_PERIOD
+import org.opensearch.indexmanagement.indexstatemanagement.transport.action.managedIndex.ManagedIndexAction
+import org.opensearch.indexmanagement.indexstatemanagement.transport.action.managedIndex.ManagedIndexRequest
import org.opensearch.indexmanagement.indexstatemanagement.util.ISM_TEMPLATE_FIELD
import org.opensearch.indexmanagement.indexstatemanagement.util.deleteManagedIndexMetadataRequest
import org.opensearch.indexmanagement.indexstatemanagement.util.deleteManagedIndexRequest
@@ -83,10 +91,13 @@ import org.opensearch.indexmanagement.indexstatemanagement.util.isFailed
import org.opensearch.indexmanagement.indexstatemanagement.util.isPolicyCompleted
import org.opensearch.indexmanagement.indexstatemanagement.util.managedIndexConfigIndexRequest
import org.opensearch.indexmanagement.indexstatemanagement.util.updateEnableManagedIndexRequest
+import org.opensearch.indexmanagement.opensearchapi.IndexManagementSecurityContext
import org.opensearch.indexmanagement.opensearchapi.contentParser
+import org.opensearch.indexmanagement.opensearchapi.parseFromSearchResponse
import org.opensearch.indexmanagement.opensearchapi.parseWithType
import org.opensearch.indexmanagement.opensearchapi.retry
import org.opensearch.indexmanagement.opensearchapi.suspendUntil
+import org.opensearch.indexmanagement.opensearchapi.withClosableContext
import org.opensearch.indexmanagement.util.NO_ID
import org.opensearch.indexmanagement.util.OpenForTesting
import org.opensearch.rest.RestStatus
@@ -112,7 +123,7 @@ import org.opensearch.threadpool.ThreadPool
@Suppress("TooManyFunctions")
@OpenForTesting
class ManagedIndexCoordinator(
- settings: Settings,
+ private val settings: Settings,
private val client: Client,
private val clusterService: ClusterService,
private val threadPool: ThreadPool,
@@ -135,6 +146,7 @@ class ManagedIndexCoordinator(
@Volatile private var retryPolicy =
BackoffPolicy.constantBackoff(COORDINATOR_BACKOFF_MILLIS.get(settings), COORDINATOR_BACKOFF_COUNT.get(settings))
@Volatile private var jobInterval = JOB_INTERVAL.get(settings)
+ @Volatile private var jobJitter = JITTER.get(settings)
@Volatile private var isMaster = false
@@ -148,6 +160,9 @@ class ManagedIndexCoordinator(
clusterService.clusterSettings.addSettingsUpdateConsumer(JOB_INTERVAL) {
jobInterval = it
}
+ clusterService.clusterSettings.addSettingsUpdateConsumer(JITTER) {
+ jobJitter = it
+ }
clusterService.clusterSettings.addSettingsUpdateConsumer(INDEX_STATE_MANAGEMENT_ENABLED) {
indexStateManagementEnabled = it
if (!indexStateManagementEnabled) disable() else enable()
@@ -292,62 +307,146 @@ class ManagedIndexCoordinator(
/**
* build requests to create jobs for indices matching ISM templates
*/
+ @Suppress("NestedBlockDepth")
suspend fun getMatchingIndicesUpdateReq(
clusterState: ClusterState,
indexNames: List
): List> {
- val updateManagedIndexReqs = mutableListOf>()
- if (indexNames.isEmpty()) return updateManagedIndexReqs
+ val updateManagedIndexReqs = mutableListOf>()
+ if (indexNames.isEmpty()) return updateManagedIndexReqs.toList()
+
+ val policiesWithTemplates = getPoliciesWithISMTemplates()
+
+ // Iterate over each unmanaged hot/warm index and if it matches an ISM template add a managed index config index request
+ indexNames.forEach { indexName ->
+ val lookupName = findIndexLookupName(indexName, clusterState)
+ if (lookupName != null) {
+ val indexMetadata = clusterState.metadata.index(indexName)
+ val creationDate = indexMetadata.creationDate
+ val indexUuid = indexMetadata.indexUUID
+ findMatchingPolicy(lookupName, creationDate, policiesWithTemplates)
+ ?.let { policy ->
+ logger.info("Index [$indexName] matched ISM policy template and will be managed by ${policy.id}")
+ updateManagedIndexReqs.add(
+ managedIndexConfigIndexRequest(
+ indexName,
+ indexUuid,
+ policy.id,
+ jobInterval,
+ policy,
+ jobJitter
+ )
+ )
+ }
+ }
+ }
- val indexMetadatas = clusterState.metadata.indices
- val templates = getISMTemplates()
+ return updateManagedIndexReqs.toList()
+ }
- val indexToMatchedPolicy = indexNames.map { indexName ->
- indexName to templates.findMatchingPolicy(clusterState, indexName)
- }.toMap()
+ private fun findIndexLookupName(indexName: String, clusterState: ClusterState): String? {
+ if (clusterState.metadata.hasIndex(indexName)) {
+ val indexMetadata = clusterState.metadata.index(indexName)
+ val autoManage = indexMetadata.settings.getAsBoolean(AUTO_MANAGE.key, true)
+ if (autoManage) {
+ val isHiddenIndex =
+ IndexMetadata.INDEX_HIDDEN_SETTING.get(indexMetadata.settings) || indexName.startsWith(".")
+ val indexAbstraction = clusterState.metadata.indicesLookup[indexName]
+ val isDataStreamIndex = indexAbstraction?.parentDataStream != null
+ if (!isDataStreamIndex && isHiddenIndex) {
+ return null
+ }
- indexToMatchedPolicy.filterNotNullValues()
- .forEach { (index, policyID) ->
- val indexUuid = indexMetadatas[index].indexUUID
- val ismTemplate = templates[policyID]
- if (indexUuid != null && ismTemplate != null) {
- logger.info("Index [$index] will be managed by policy [$policyID]")
- updateManagedIndexReqs.add(
- managedIndexConfigIndexRequest(index, indexUuid, policyID, jobInterval)
- )
- } else {
- logger.warn(
- "Index [$index] has index uuid [$indexUuid] and/or " +
- "a matching template [$ismTemplate] that is null."
- )
+ return when {
+ isDataStreamIndex -> indexAbstraction?.parentDataStream?.name
+ else -> indexName
+ }
+ }
+ }
+
+ return null
+ }
+
+ /**
+ * Find a policy that has highest priority ism template with matching index pattern to the index and is created before index creation date. If
+ * the policy has user, ensure that the user can manage the index if not find the one that can.
+ * */
+ private suspend fun findMatchingPolicy(indexName: String, creationDate: Long, policies: List): Policy? {
+ val patternMatchPredicate = { pattern: String -> Regex.simpleMatch(pattern, indexName) }
+ val priorityPolicyMap = mutableMapOf()
+ policies.forEach { policy ->
+ var highestPriorityForPolicy = -1
+ policy.ismTemplate?.filter { template ->
+ template.lastUpdatedTime.toEpochMilli() < creationDate
+ }?.forEach { template ->
+ if (template.indexPatterns.stream().anyMatch(patternMatchPredicate)) {
+ if (highestPriorityForPolicy < template.priority) {
+ highestPriorityForPolicy = template.priority
+ }
+ }
+ }
+ if (highestPriorityForPolicy > -1) {
+ priorityPolicyMap[highestPriorityForPolicy] = policy
+ }
+ }
+
+ val previouslyCheckedUsers = mutableSetOf()
+ // sorting the applicable policies based on the priority highest to lowest
+ val sortedPriorityPolicyMap = priorityPolicyMap.toSortedMap(reverseOrder())
+ sortedPriorityPolicyMap.forEach { (_, policy) ->
+ if (!previouslyCheckedUsers.contains(policy.user) && canPolicyManagedIndex(policy, indexName)) {
+ return policy
+ }
+
+ policy.user?.let { previouslyCheckedUsers.add(it) }
+ }
+
+ logger.debug("Couldn't find any matching policy with appropriate permissions that can manage index $indexName")
+ return null
+ }
+
+ suspend fun canPolicyManagedIndex(policy: Policy, indexName: String): Boolean {
+ if (policy.user != null) {
+ try {
+ val request = ManagedIndexRequest().indices(indexName)
+ withClosableContext(IndexManagementSecurityContext("ApplyPolicyOnIndexCreation", settings, threadPool.threadContext, policy.user)) {
+ val response: AcknowledgedResponse = client.suspendUntil { execute(ManagedIndexAction.INSTANCE, request, it) }
}
+ } catch (e: OpenSearchSecurityException) {
+ logger.debug("Skipping applying policy ${policy.id} on $indexName as the policy user is missing perimissions", e)
+ return false
+ } catch (e: Exception) {
+ // Ignore other exceptions
}
+ }
- return updateManagedIndexReqs
+ return true
}
- suspend fun getISMTemplates(): Map {
+ suspend fun getPoliciesWithISMTemplates(): List {
+ val errorMessage = "Failed to get ISM policies with templates"
val searchRequest = SearchRequest()
.source(
SearchSourceBuilder().query(
QueryBuilders.existsQuery(ISM_TEMPLATE_FIELD)
- )
+ ).size(MAX_HITS)
)
.indices(INDEX_MANAGEMENT_INDEX)
return try {
val response: SearchResponse = client.suspendUntil { search(searchRequest, it) }
- getPolicyToTemplateMap(response).filterNotNullValues()
+ parseFromSearchResponse(response = response, parse = Policy.Companion::parse)
} catch (ex: IndexNotFoundException) {
- emptyMap()
+ emptyList()
} catch (ex: ClusterBlockException) {
- emptyMap()
+ logger.error(errorMessage)
+ emptyList()
} catch (e: SearchPhaseExecutionException) {
- logger.error("Failed to get ISM templates: $e")
- emptyMap()
+ logger.error("$errorMessage: $e")
+ emptyList()
} catch (e: Exception) {
- logger.error("Failed to get ISM templates", e)
- emptyMap()
+ logger.error(errorMessage, e)
+ emptyList()
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/ManagedIndexRunner.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/ManagedIndexRunner.kt
index 865be64d8..562b20d28 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/ManagedIndexRunner.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/ManagedIndexRunner.kt
@@ -36,16 +36,13 @@ import kotlinx.coroutines.withContext
import org.apache.logging.log4j.LogManager
import org.opensearch.action.admin.cluster.state.ClusterStateRequest
import org.opensearch.action.admin.cluster.state.ClusterStateResponse
-import org.opensearch.action.admin.indices.settings.put.UpdateSettingsRequest
import org.opensearch.action.bulk.BackoffPolicy
import org.opensearch.action.get.GetRequest
import org.opensearch.action.get.GetResponse
import org.opensearch.action.index.IndexResponse
import org.opensearch.action.support.IndicesOptions
-import org.opensearch.action.support.master.AcknowledgedResponse
import org.opensearch.action.update.UpdateResponse
import org.opensearch.client.Client
-import org.opensearch.cluster.block.ClusterBlockException
import org.opensearch.cluster.health.ClusterHealthStatus
import org.opensearch.cluster.health.ClusterStateHealth
import org.opensearch.cluster.metadata.IndexMetadata
@@ -60,7 +57,6 @@ import org.opensearch.common.xcontent.XContentHelper
import org.opensearch.common.xcontent.XContentParser.Token
import org.opensearch.common.xcontent.XContentParserUtils.ensureExpectedToken
import org.opensearch.common.xcontent.XContentType
-import org.opensearch.index.Index
import org.opensearch.index.engine.VersionConflictEngineException
import org.opensearch.index.seqno.SequenceNumbers
import org.opensearch.indexmanagement.IndexManagementIndices
@@ -82,8 +78,6 @@ import org.opensearch.indexmanagement.indexstatemanagement.settings.ManagedIndex
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.step.Step
-import org.opensearch.indexmanagement.indexstatemanagement.transport.action.updateindexmetadata.UpdateManagedIndexMetaDataAction
-import org.opensearch.indexmanagement.indexstatemanagement.transport.action.updateindexmetadata.UpdateManagedIndexMetaDataRequest
import org.opensearch.indexmanagement.indexstatemanagement.util.getActionToExecute
import org.opensearch.indexmanagement.indexstatemanagement.util.getCompletedManagedIndexMetaData
import org.opensearch.indexmanagement.indexstatemanagement.util.getStartingManagedIndexMetaData
@@ -102,11 +96,13 @@ import org.opensearch.indexmanagement.indexstatemanagement.util.managedIndexMeta
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.opensearchapi.IndexManagementSecurityContext
import org.opensearch.indexmanagement.opensearchapi.convertToMap
import org.opensearch.indexmanagement.opensearchapi.parseWithType
import org.opensearch.indexmanagement.opensearchapi.retry
import org.opensearch.indexmanagement.opensearchapi.string
import org.opensearch.indexmanagement.opensearchapi.suspendUntil
+import org.opensearch.indexmanagement.opensearchapi.withClosableContext
import org.opensearch.jobscheduler.spi.JobExecutionContext
import org.opensearch.jobscheduler.spi.LockModel
import org.opensearch.jobscheduler.spi.ScheduledJobParameter
@@ -116,6 +112,7 @@ import org.opensearch.rest.RestStatus
import org.opensearch.script.Script
import org.opensearch.script.ScriptService
import org.opensearch.script.TemplateScript
+import org.opensearch.threadpool.ThreadPool
import java.time.Instant
import java.time.temporal.ChronoUnit
@@ -134,6 +131,7 @@ object ManagedIndexRunner :
private lateinit var imIndices: IndexManagementIndices
private lateinit var ismHistory: IndexStateManagementHistory
private lateinit var skipExecFlag: SkipExecution
+ private lateinit var threadPool: ThreadPool
private var indexStateManagementEnabled: Boolean = DEFAULT_ISM_ENABLED
@Suppress("MagicNumber")
private val savePolicyRetryPolicy = BackoffPolicy.exponentialBackoff(TimeValue.timeValueMillis(250), 3)
@@ -206,6 +204,11 @@ object ManagedIndexRunner :
return this
}
+ fun registerThreadPool(threadPool: ThreadPool): ManagedIndexRunner {
+ this.threadPool = threadPool
+ return this
+ }
+
override fun runJob(job: ScheduledJobParameter, context: JobExecutionContext) {
if (job !is ManagedIndexConfig) {
throw IllegalArgumentException("Invalid job type, found ${job.javaClass.simpleName} with id: ${context.jobId}")
@@ -284,7 +287,9 @@ object ManagedIndexRunner :
}
val state = policy.getStateToExecute(managedIndexMetaData)
- val action: Action? = state?.getActionToExecute(clusterService, scriptService, client, settings, managedIndexMetaData)
+ val action: Action? = state?.getActionToExecute(
+ clusterService, scriptService, client, settings, managedIndexMetaData.copy(user = policy.user, threadContext = threadPool.threadContext)
+ )
val step: Step? = action?.getStepToExecute()
val currentActionMetaData = action?.getUpdatedActionMetaData(managedIndexMetaData, state)
@@ -353,7 +358,13 @@ object ManagedIndexRunner :
@Suppress("ComplexCondition")
if (updateResult.metadataSaved && state != null && action != null && step != null && currentActionMetaData != null) {
// Step null check is done in getStartingManagedIndexMetaData
- step.preExecute(logger).execute().postExecute(logger)
+ withClosableContext(
+ IndexManagementSecurityContext(
+ managedIndexConfig.id, settings, threadPool.threadContext, managedIndexConfig.policy.user
+ )
+ ) {
+ step.preExecute(logger).execute().postExecute(logger)
+ }
var executedManagedIndexMetaData = startingManagedIndexMetaData.getCompletedManagedIndexMetaData(action, step)
if (executedManagedIndexMetaData.isFailed) {
@@ -573,29 +584,6 @@ object ManagedIndexRunner :
}
}
- // delete metadata in cluster state
- private suspend fun deleteManagedIndexMetaData(managedIndexMetaData: ManagedIndexMetaData): Boolean {
- var result = false
- try {
- val request = UpdateManagedIndexMetaDataRequest(
- indicesToRemoveManagedIndexMetaDataFrom = listOf(Index(managedIndexMetaData.index, managedIndexMetaData.indexUuid))
- )
- updateMetaDataRetryPolicy.retry(logger) {
- val response: AcknowledgedResponse = client.suspendUntil { execute(UpdateManagedIndexMetaDataAction.INSTANCE, request, it) }
- if (response.isAcknowledged) {
- result = true
- } else {
- logger.error("Failed to delete ManagedIndexMetaData for [index=${managedIndexMetaData.index}]")
- }
- }
- } catch (e: ClusterBlockException) {
- logger.error("There was ClusterBlockException trying to delete the metadata for ${managedIndexMetaData.index}. Message: ${e.message}", e)
- } catch (e: Exception) {
- logger.error("Failed to delete ManagedIndexMetaData for [index=${managedIndexMetaData.index}]", e)
- }
- return result
- }
-
/**
* update metadata in config index, and save metadata in history after update
* this can be called 2 times in one job run, so need to save seqNo & primeTerm
@@ -721,18 +709,8 @@ object ManagedIndexRunner :
if (!updated.metadataSaved || policy == null) return
- // this will save the new policy on the job and reset the change policy back to null
- val saved = savePolicyToManagedIndexConfig(managedIndexConfig, policy)
-
- if (saved) {
- /*
- * If we successfully saved the the new policy then the last thing we need to do is update the
- * opendistro.indexstatemanagement.policy_id setting to the new policy id we don't care that much if this fails, because we'll
- * have a check in the beginning of the runner to read in the setting and compare it with the policy_id on the job and update
- * the setting if they ever differ, as we do not allow someone to change an existing policy using _settings API
- * */
- updateIndexPolicyIDSetting(managedIndexConfig.index, changePolicy.policyID)
- }
+ // Change the policy and user stored on the job from changePolicy, this will also set the changePolicy to null on the job
+ savePolicyToManagedIndexConfig(managedIndexConfig, policy.copy(user = changePolicy.user))
}
@Suppress("TooGenericExceptionCaught")
@@ -750,29 +728,6 @@ object ManagedIndexRunner :
}
}
- /**
- * Once we successfully swap over a ChangePolicy then we need to update the [ManagedIndexSettings.POLICY_ID] setting.
- *
- * We will constantly check the [ManagedIndexSettings.POLICY_ID] against the [ManagedIndexConfig] policyID and if
- * there is ever a mismatch we will overwrite the [ManagedIndexSettings.POLICY_ID] with the [ManagedIndexConfig] policyID.
- *
- * We do this because if this fails we want to ensure we try again on the next execution of the job. At the same time, this
- * will disallow the user from directly using the _settings API to change the policy_id. We do not want to allow this,
- * they must use the ChangePolicy API as the [ManagedIndexSettings.POLICY_ID] is referring to the currently running policy.
- */
- private suspend fun updateIndexPolicyIDSetting(index: String, policyID: String) {
- try {
- val settings = Settings.builder().put(ManagedIndexSettings.POLICY_ID.key, policyID).build()
- val updateSettingsRequest = UpdateSettingsRequest(index).settings(settings)
- val response: AcknowledgedResponse = client.admin().indices().suspendUntil { updateSettings(updateSettingsRequest, it) }
- if (!response.isAcknowledged) {
- logger.warn("Updating policy_id ($policyID) for $index was not acknowledged")
- }
- } catch (e: Exception) {
- logger.error("There was an error while trying to update the policy_id ($policyID) setting for $index", e)
- }
- }
-
private suspend fun publishErrorNotification(policy: Policy, managedIndexMetaData: ManagedIndexMetaData) {
policy.errorNotification?.run {
errorNotificationRetryPolicy.retry(logger) {
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/SnapshotAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/SnapshotAction.kt
index f06e1e870..00fea6a2d 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/SnapshotAction.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/action/SnapshotAction.kt
@@ -34,14 +34,16 @@ import org.opensearch.indexmanagement.indexstatemanagement.model.action.Snapshot
import org.opensearch.indexmanagement.indexstatemanagement.step.Step
import org.opensearch.indexmanagement.indexstatemanagement.step.snapshot.AttemptSnapshotStep
import org.opensearch.indexmanagement.indexstatemanagement.step.snapshot.WaitForSnapshotStep
+import org.opensearch.script.ScriptService
class SnapshotAction(
clusterService: ClusterService,
+ scriptService: ScriptService,
client: Client,
managedIndexMetaData: ManagedIndexMetaData,
config: SnapshotActionConfig
) : Action(ActionType.SNAPSHOT, config, managedIndexMetaData) {
- private val attemptSnapshotStep = AttemptSnapshotStep(clusterService, client, config, managedIndexMetaData)
+ private val attemptSnapshotStep = AttemptSnapshotStep(clusterService, scriptService, client, config, managedIndexMetaData)
private val waitForSnapshotStep = WaitForSnapshotStep(clusterService, client, config, managedIndexMetaData)
override fun getSteps(): List = listOf(attemptSnapshotStep, waitForSnapshotStep)
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/model/ChangePolicy.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/model/ChangePolicy.kt
index 33f897341..cce5ca2a5 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/model/ChangePolicy.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/model/ChangePolicy.kt
@@ -35,7 +35,10 @@ import org.opensearch.common.xcontent.XContentBuilder
import org.opensearch.common.xcontent.XContentParser
import org.opensearch.common.xcontent.XContentParser.Token
import org.opensearch.common.xcontent.XContentParserUtils.ensureExpectedToken
+import org.opensearch.commons.authuser.User
import org.opensearch.indexmanagement.indexstatemanagement.model.managedindexmetadata.StateMetaData
+import org.opensearch.indexmanagement.indexstatemanagement.util.WITH_USER
+import org.opensearch.indexmanagement.opensearchapi.optionalUserField
import java.io.IOException
/**
@@ -52,7 +55,8 @@ data class ChangePolicy(
val policyID: String,
val state: String?,
val include: List,
- val isSafe: Boolean
+ val isSafe: Boolean,
+ val user: User? = null
) : Writeable, ToXContentObject {
@Throws(IOException::class)
@@ -60,7 +64,10 @@ data class ChangePolicy(
policyID = sin.readString(),
state = sin.readOptionalString(),
include = sin.readList(::StateFilter),
- isSafe = sin.readBoolean()
+ isSafe = sin.readBoolean(),
+ user = if (sin.readBoolean()) {
+ User(sin)
+ } else null
)
override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder {
@@ -69,8 +76,8 @@ data class ChangePolicy(
.field(ManagedIndexConfig.POLICY_ID_FIELD, policyID)
.field(StateMetaData.STATE, state)
.field(IS_SAFE_FIELD, isSafe)
- .endObject()
- return builder
+ if (params.paramAsBoolean(WITH_USER, true)) builder.optionalUserField(USER_FIELD, user)
+ return builder.endObject()
}
@Throws(IOException::class)
@@ -79,6 +86,8 @@ data class ChangePolicy(
out.writeOptionalString(state)
out.writeList(include)
out.writeBoolean(isSafe)
+ out.writeBoolean(user != null)
+ user?.writeTo(out)
}
companion object {
@@ -86,6 +95,7 @@ data class ChangePolicy(
const val STATE_FIELD = "state"
const val INCLUDE_FIELD = "include"
const val IS_SAFE_FIELD = "is_safe"
+ const val USER_FIELD = "user"
@JvmStatic
@Throws(IOException::class)
@@ -93,6 +103,7 @@ data class ChangePolicy(
var policyID: String? = null
var state: String? = null
var isSafe: Boolean = false
+ var user: User? = null
val include = mutableListOf()
ensureExpectedToken(Token.START_OBJECT, xcp.currentToken(), xcp)
@@ -110,6 +121,9 @@ data class ChangePolicy(
}
}
IS_SAFE_FIELD -> isSafe = xcp.booleanValue()
+ USER_FIELD -> {
+ user = if (xcp.currentToken() == Token.VALUE_NULL) null else User.parse(xcp)
+ }
else -> throw IllegalArgumentException("Invalid field: [$fieldName] found in ChangePolicy.")
}
}
@@ -118,7 +132,8 @@ data class ChangePolicy(
requireNotNull(policyID) { "ChangePolicy policy id is null" },
state,
include.toList(),
- isSafe
+ isSafe,
+ user
)
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/model/ManagedIndexConfig.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/model/ManagedIndexConfig.kt
index 0645c0b2f..69cb6e6af 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/model/ManagedIndexConfig.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/model/ManagedIndexConfig.kt
@@ -56,7 +56,8 @@ data class ManagedIndexConfig(
val policySeqNo: Long?,
val policyPrimaryTerm: Long?,
val policy: Policy?,
- val changePolicy: ChangePolicy?
+ val changePolicy: ChangePolicy?,
+ val jobJitter: Double?
) : ScheduledJobParameter {
init {
@@ -79,6 +80,10 @@ data class ManagedIndexConfig(
override fun getLockDurationSeconds(): Long = 3600L // 1 hour
+ override fun getJitter(): Double? {
+ return jobJitter
+ }
+
override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder {
builder
.startObject()
@@ -95,9 +100,9 @@ data class ManagedIndexConfig(
.field(POLICY_PRIMARY_TERM_FIELD, policyPrimaryTerm)
.field(POLICY_FIELD, policy, XCONTENT_WITHOUT_TYPE)
.field(CHANGE_POLICY_FIELD, changePolicy)
- .endObject()
- .endObject()
- return builder
+ .field(JITTER, jobJitter)
+ builder.endObject()
+ return builder.endObject()
}
companion object {
@@ -115,6 +120,7 @@ data class ManagedIndexConfig(
const val POLICY_SEQ_NO_FIELD = "policy_seq_no"
const val POLICY_PRIMARY_TERM_FIELD = "policy_primary_term"
const val CHANGE_POLICY_FIELD = "change_policy"
+ const val JITTER = "jitter"
@Suppress("ComplexMethod", "LongMethod")
@JvmStatic
@@ -138,6 +144,7 @@ data class ManagedIndexConfig(
var enabled = true
var policyPrimaryTerm: Long? = SequenceNumbers.UNASSIGNED_PRIMARY_TERM
var policySeqNo: Long? = SequenceNumbers.UNASSIGNED_SEQ_NO
+ var jitter: Double? = null
ensureExpectedToken(Token.START_OBJECT, xcp.currentToken(), xcp)
while (xcp.nextToken() != Token.END_OBJECT) {
@@ -165,6 +172,9 @@ data class ManagedIndexConfig(
CHANGE_POLICY_FIELD -> {
changePolicy = if (xcp.currentToken() == Token.VALUE_NULL) null else ChangePolicy.parse(xcp)
}
+ JITTER -> {
+ jitter = if (xcp.currentToken() == Token.VALUE_NULL) null else xcp.doubleValue()
+ }
else -> throw IllegalArgumentException("Invalid field: [$fieldName] found in ManagedIndexConfig.")
}
}
@@ -193,7 +203,8 @@ data class ManagedIndexConfig(
seqNo = policySeqNo ?: SequenceNumbers.UNASSIGNED_SEQ_NO,
primaryTerm = policyPrimaryTerm ?: SequenceNumbers.UNASSIGNED_PRIMARY_TERM
),
- changePolicy = changePolicy
+ changePolicy = changePolicy,
+ jobJitter = jitter
)
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/model/ManagedIndexMetaData.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/model/ManagedIndexMetaData.kt
index 0b89901f9..79c04291a 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/model/ManagedIndexMetaData.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/model/ManagedIndexMetaData.kt
@@ -30,6 +30,7 @@ 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.util.concurrent.ThreadContext
import org.opensearch.common.xcontent.ToXContent
import org.opensearch.common.xcontent.ToXContentFragment
import org.opensearch.common.xcontent.XContentBuilder
@@ -39,6 +40,7 @@ import org.opensearch.common.xcontent.XContentParser
import org.opensearch.common.xcontent.XContentParser.Token
import org.opensearch.common.xcontent.XContentParserUtils.ensureExpectedToken
import org.opensearch.common.xcontent.json.JsonXContent
+import org.opensearch.commons.authuser.User
import org.opensearch.index.seqno.SequenceNumbers
import org.opensearch.indexmanagement.indexstatemanagement.model.action.ActionConfig
import org.opensearch.indexmanagement.indexstatemanagement.model.managedindexmetadata.ActionMetaData
@@ -64,7 +66,13 @@ data class ManagedIndexMetaData(
val info: Map?,
val id: String = NO_ID,
val seqNo: Long = SequenceNumbers.UNASSIGNED_SEQ_NO,
- val primaryTerm: Long = SequenceNumbers.UNASSIGNED_PRIMARY_TERM
+ val primaryTerm: Long = SequenceNumbers.UNASSIGNED_PRIMARY_TERM,
+ // TODO: Remove this once the step interface is updated to pass in user information.
+ // The user information is not being stored/written anywhere, this is only intended to be used during the step execution.
+ val user: User? = null,
+ // TODO: Remove this once the step interface is updated to pass in thread context information.
+ // This information is not being stored/written anywhere, this is only intended to be used during the step execution.
+ val threadContext: ThreadContext? = null
) : Writeable, ToXContentFragment {
@Suppress("ComplexMethod")
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/model/Policy.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/model/Policy.kt
index f60cda4d9..bed51d43c 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/model/Policy.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/model/Policy.kt
@@ -35,11 +35,14 @@ import org.opensearch.common.xcontent.XContentBuilder
import org.opensearch.common.xcontent.XContentParser
import org.opensearch.common.xcontent.XContentParser.Token
import org.opensearch.common.xcontent.XContentParserUtils.ensureExpectedToken
+import org.opensearch.commons.authuser.User
import org.opensearch.index.seqno.SequenceNumbers
import org.opensearch.indexmanagement.indexstatemanagement.util.WITH_TYPE
+import org.opensearch.indexmanagement.indexstatemanagement.util.WITH_USER
import org.opensearch.indexmanagement.opensearchapi.instant
import org.opensearch.indexmanagement.opensearchapi.optionalISMTemplateField
import org.opensearch.indexmanagement.opensearchapi.optionalTimeField
+import org.opensearch.indexmanagement.opensearchapi.optionalUserField
import org.opensearch.indexmanagement.util.IndexUtils
import java.io.IOException
import java.time.Instant
@@ -54,7 +57,8 @@ data class Policy(
val errorNotification: ErrorNotification?,
val defaultState: String,
val states: List,
- val ismTemplate: ISMTemplate? = null
+ val ismTemplate: List? = null,
+ val user: User? = null
) : ToXContentObject, Writeable {
init {
@@ -86,6 +90,7 @@ data class Policy(
.field(DEFAULT_STATE_FIELD, defaultState)
.field(STATES_FIELD, states.toTypedArray())
.optionalISMTemplateField(ISM_TEMPLATE, ismTemplate)
+ if (params.paramAsBoolean(WITH_USER, true)) builder.optionalUserField(USER_FIELD, user)
if (params.paramAsBoolean(WITH_TYPE, true)) builder.endObject()
return builder.endObject()
}
@@ -101,7 +106,12 @@ data class Policy(
errorNotification = sin.readOptionalWriteable(::ErrorNotification),
defaultState = sin.readString(),
states = sin.readList(::State),
- ismTemplate = sin.readOptionalWriteable(::ISMTemplate)
+ ismTemplate = if (sin.readBoolean()) {
+ sin.readList(::ISMTemplate)
+ } else null,
+ user = if (sin.readBoolean()) {
+ User(sin)
+ } else null
)
@Throws(IOException::class)
@@ -115,7 +125,14 @@ data class Policy(
out.writeOptionalWriteable(errorNotification)
out.writeString(defaultState)
out.writeList(states)
- out.writeOptionalWriteable(ismTemplate)
+ if (ismTemplate != null) {
+ out.writeBoolean(true)
+ out.writeList(ismTemplate)
+ } else {
+ out.writeBoolean(false)
+ }
+ out.writeBoolean(user != null)
+ user?.writeTo(out)
}
companion object {
@@ -129,8 +146,9 @@ data class Policy(
const val DEFAULT_STATE_FIELD = "default_state"
const val STATES_FIELD = "states"
const val ISM_TEMPLATE = "ism_template"
+ const val USER_FIELD = "user"
- @Suppress("ComplexMethod")
+ @Suppress("ComplexMethod", "LongMethod", "NestedBlockDepth")
@JvmStatic
@JvmOverloads
@Throws(IOException::class)
@@ -146,7 +164,8 @@ data class Policy(
var lastUpdatedTime: Instant? = null
var schemaVersion: Long = IndexUtils.DEFAULT_SCHEMA_VERSION
val states: MutableList = mutableListOf()
- var ismTemplate: ISMTemplate? = null
+ var ismTemplates: List? = null
+ var user: User? = null
ensureExpectedToken(Token.START_OBJECT, xcp.currentToken(), xcp)
while (xcp.nextToken() != Token.END_OBJECT) {
@@ -166,7 +185,23 @@ data class Policy(
states.add(State.parse(xcp))
}
}
- ISM_TEMPLATE -> ismTemplate = if (xcp.currentToken() == Token.VALUE_NULL) null else ISMTemplate.parse(xcp)
+ ISM_TEMPLATE -> {
+ if (xcp.currentToken() != Token.VALUE_NULL) {
+ ismTemplates = mutableListOf()
+ when (xcp.currentToken()) {
+ Token.START_ARRAY -> {
+ while (xcp.nextToken() != Token.END_ARRAY) {
+ ismTemplates.add(ISMTemplate.parse(xcp))
+ }
+ }
+ Token.START_OBJECT -> {
+ ismTemplates.add(ISMTemplate.parse(xcp))
+ }
+ else -> ensureExpectedToken(Token.START_ARRAY, xcp.currentToken(), xcp)
+ }
+ }
+ }
+ USER_FIELD -> user = if (xcp.currentToken() == Token.VALUE_NULL) null else User.parse(xcp)
else -> throw IllegalArgumentException("Invalid field: [$fieldName] found in Policy.")
}
}
@@ -181,7 +216,8 @@ data class Policy(
errorNotification = errorNotification,
defaultState = requireNotNull(defaultState) { "$DEFAULT_STATE_FIELD is null" },
states = states.toList(),
- ismTemplate = ismTemplate
+ ismTemplate = ismTemplates,
+ user = user
)
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/model/action/SnapshotActionConfig.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/model/action/SnapshotActionConfig.kt
index a09f7c475..7243497cb 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/model/action/SnapshotActionConfig.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/model/action/SnapshotActionConfig.kt
@@ -66,7 +66,7 @@ data class SnapshotActionConfig(
client: Client,
settings: Settings,
managedIndexMetaData: ManagedIndexMetaData
- ): Action = SnapshotAction(clusterService, client, managedIndexMetaData, this)
+ ): Action = SnapshotAction(clusterService, scriptService, client, managedIndexMetaData, this)
@Throws(IOException::class)
constructor(sin: StreamInput) : this(
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/opensearchapi/OpenSearchExtensions.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/opensearchapi/OpenSearchExtensions.kt
index 361e0de6d..b3cf68ea8 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/opensearchapi/OpenSearchExtensions.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/opensearchapi/OpenSearchExtensions.kt
@@ -37,7 +37,6 @@ import org.opensearch.action.get.GetRequest
import org.opensearch.action.get.GetResponse
import org.opensearch.action.get.MultiGetRequest
import org.opensearch.action.get.MultiGetResponse
-import org.opensearch.action.search.SearchResponse
import org.opensearch.client.Client
import org.opensearch.cluster.ClusterState
import org.opensearch.cluster.metadata.IndexMetadata
@@ -46,20 +45,16 @@ 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.XContentFactory
import org.opensearch.common.xcontent.XContentHelper
import org.opensearch.common.xcontent.XContentType
import org.opensearch.index.Index
import org.opensearch.index.IndexNotFoundException
import org.opensearch.indexmanagement.IndexManagementPlugin.Companion.INDEX_MANAGEMENT_INDEX
-import org.opensearch.indexmanagement.indexstatemanagement.model.ISMTemplate
import org.opensearch.indexmanagement.indexstatemanagement.model.ManagedIndexMetaData
-import org.opensearch.indexmanagement.indexstatemanagement.model.Policy
import org.opensearch.indexmanagement.indexstatemanagement.settings.LegacyOpenDistroManagedIndexSettings
import org.opensearch.indexmanagement.indexstatemanagement.settings.ManagedIndexSettings
import org.opensearch.indexmanagement.indexstatemanagement.util.managedIndexMetadataID
import org.opensearch.indexmanagement.opensearchapi.contentParser
-import org.opensearch.indexmanagement.opensearchapi.parseWithType
import org.opensearch.indexmanagement.opensearchapi.suspendUntil
private val log = LogManager.getLogger("Index Management Helper")
@@ -104,27 +99,6 @@ fun getUuidsForClosedIndices(state: ClusterState): MutableList {
return closeList
}
-/**
- * Do a exists search query to retrieve all policy with ism_template field
- * parse search response with this function
- *
- * @return map of policyID to ISMTemplate in this policy
- * @throws [IllegalArgumentException]
- */
-@Throws(Exception::class)
-fun getPolicyToTemplateMap(response: SearchResponse, xContentRegistry: NamedXContentRegistry = NamedXContentRegistry.EMPTY):
- Map {
- return response.hits.hits.map {
- val id = it.id
- val seqNo = it.seqNo
- val primaryTerm = it.primaryTerm
- val xcp = XContentFactory.xContent(XContentType.JSON)
- .createParser(xContentRegistry, LoggingDeprecationHandler.INSTANCE, it.sourceAsString)
- xcp.parseWithType(id, seqNo, primaryTerm, Policy.Companion::parse)
- .copy(id = id, seqNo = seqNo, primaryTerm = primaryTerm)
- }.map { it.id to it.ismTemplate }.toMap()
-}
-
@Suppress("UNCHECKED_CAST")
fun Map.filterNotNullValues(): Map =
filterValues { it != null } as Map
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 d83cb5304..dd26a25f8 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/settings/ManagedIndexSettings.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/settings/ManagedIndexSettings.kt
@@ -36,6 +36,7 @@ class ManagedIndexSettings {
const val DEFAULT_ISM_ENABLED = true
const val DEFAULT_METADATA_SERVICE_ENABLED = true
const val DEFAULT_JOB_INTERVAL = 5
+ const val DEFAULT_JITTER = 0.6
private val ALLOW_LIST_ALL = ActionConfig.ActionType.values().toList().map { it.type }
val ALLOW_LIST_NONE = emptyList()
val SNAPSHOT_DENY_LIST_NONE = emptyList()
@@ -76,6 +77,13 @@ class ManagedIndexSettings {
Setting.Property.Dynamic
)
+ val AUTO_MANAGE: Setting = Setting.boolSetting(
+ "index.plugins.index_state_management.auto_manage",
+ true,
+ Setting.Property.IndexScope,
+ Setting.Property.Dynamic
+ )
+
val JOB_INTERVAL: Setting = Setting.intSetting(
"plugins.index_state_management.job_interval",
LegacyOpenDistroManagedIndexSettings.JOB_INTERVAL,
@@ -172,5 +180,14 @@ class ManagedIndexSettings {
Setting.Property.NodeScope,
Setting.Property.Dynamic
)
+
+ val JITTER: Setting = Setting.doubleSetting(
+ "plugins.index_state_management.jitter",
+ DEFAULT_JITTER,
+ 0.0,
+ 1.0,
+ Setting.Property.NodeScope,
+ Setting.Property.Dynamic
+ )
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/step/notification/AttemptNotificationStep.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/step/notification/AttemptNotificationStep.kt
index 26cea8962..b53cdaa48 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/step/notification/AttemptNotificationStep.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/step/notification/AttemptNotificationStep.kt
@@ -66,6 +66,7 @@ class AttemptNotificationStep(
}
// publish internally throws an error for any invalid responses so its safe to assume if we reach this point it was successful
+ // publish and send throws an error for any invalid responses so its safe to assume if we reach this point it was successful
stepStatus = StepStatus.COMPLETED
info = mapOf("message" to getSuccessMessage(indexName))
} catch (e: Exception) {
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/step/rollover/AttemptRolloverStep.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/step/rollover/AttemptRolloverStep.kt
index cd9bbb1ca..3f2137891 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/step/rollover/AttemptRolloverStep.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/step/rollover/AttemptRolloverStep.kt
@@ -63,7 +63,7 @@ class AttemptRolloverStep(
override fun isIdempotent() = false
- @Suppress("TooGenericExceptionCaught")
+ @Suppress("ComplexMethod", "LongMethod", "TooGenericExceptionCaught")
override suspend fun execute(): AttemptRolloverStep {
val skipRollover = clusterService.state().metadata.index(indexName).getRolloverSkip()
if (skipRollover) {
@@ -72,18 +72,16 @@ class AttemptRolloverStep(
return this
}
- // If we have already rolled over this index then fail as we only allow an index to be rolled over once
- if (managedIndexMetaData.rolledOver == true) {
- logger.warn("$indexName was already rolled over, cannot execute rollover step")
- stepStatus = StepStatus.FAILED
- info = mapOf("message" to getFailedDuplicateRolloverMessage(indexName))
- return this
- }
-
val (rolloverTarget, isDataStream) = getRolloverTargetOrUpdateInfo()
// If the rolloverTarget is null, we would've already updated the failed info from getRolloverTargetOrUpdateInfo and can return early
rolloverTarget ?: return this
+ if (clusterService.state().metadata.index(indexName).rolloverInfos.containsKey(rolloverTarget)) {
+ stepStatus = StepStatus.COMPLETED
+ info = mapOf("message" to getAlreadyRolledOverMessage(indexName, rolloverTarget))
+ return this
+ }
+
if (!isDataStream && !preCheckIndexAlias(rolloverTarget)) {
stepStatus = StepStatus.FAILED
info = mapOf("message" to getFailedPreCheckMessage(indexName))
@@ -278,13 +276,13 @@ class AttemptRolloverStep(
)
}
+ @Suppress("TooManyFunctions")
companion object {
fun getFailedMessage(index: String) = "Failed to rollover index [index=$index]"
fun getFailedAliasUpdateMessage(index: String, newIndex: String) =
"New index created, but failed to update alias [index=$index, newIndex=$newIndex]"
fun getFailedDataStreamRolloverMessage(dataStream: String) = "Failed to rollover data stream [data_stream=$dataStream]"
fun getFailedNoValidAliasMessage(index: String) = "Missing rollover_alias index setting [index=$index]"
- fun getFailedDuplicateRolloverMessage(index: String) = "Index has already been rolled over [index=$index]"
fun getFailedEvaluateMessage(index: String) = "Failed to evaluate conditions for rollover [index=$index]"
fun getPendingMessage(index: String) = "Pending rollover of index [index=$index]"
fun getSuccessMessage(index: String) = "Successfully rolled over index [index=$index]"
@@ -292,5 +290,7 @@ class AttemptRolloverStep(
"Successfully rolled over data stream [data_stream=$dataStream index=$index]"
fun getFailedPreCheckMessage(index: String) = "Missing alias or not the write index when rollover [index=$index]"
fun getSkipRolloverMessage(index: String) = "Skipped rollover action for [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]"
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/step/rollup/AttemptCreateRollupJobStep.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/step/rollup/AttemptCreateRollupJobStep.kt
index bc4635c12..edc0a0418 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/step/rollup/AttemptCreateRollupJobStep.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/step/rollup/AttemptCreateRollupJobStep.kt
@@ -68,7 +68,7 @@ class AttemptCreateRollupJobStep(
hasPreviousRollupAttemptFailed = managedIndexMetaData.actionMetaData?.actionProperties?.hasRollupFailed
// Creating a rollup job
- val rollup = ismRollup.toRollup(indexName)
+ val rollup = ismRollup.toRollup(indexName, managedIndexMetaData.user)
rollupId = rollup.id
logger.info("Attempting to create a rollup job $rollupId for index $indexName")
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/step/snapshot/AttemptSnapshotStep.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/step/snapshot/AttemptSnapshotStep.kt
index 34b5bc9f5..dcc3ead71 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/step/snapshot/AttemptSnapshotStep.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/step/snapshot/AttemptSnapshotStep.kt
@@ -39,8 +39,13 @@ import org.opensearch.indexmanagement.indexstatemanagement.model.managedindexmet
import org.opensearch.indexmanagement.indexstatemanagement.model.managedindexmetadata.StepMetaData
import org.opensearch.indexmanagement.indexstatemanagement.settings.ManagedIndexSettings.Companion.SNAPSHOT_DENY_LIST
import org.opensearch.indexmanagement.indexstatemanagement.step.Step
+import org.opensearch.indexmanagement.opensearchapi.convertToMap
import org.opensearch.indexmanagement.opensearchapi.suspendUntil
import org.opensearch.rest.RestStatus
+import org.opensearch.script.Script
+import org.opensearch.script.ScriptService
+import org.opensearch.script.ScriptType
+import org.opensearch.script.TemplateScript
import org.opensearch.snapshots.ConcurrentSnapshotExecutionException
import org.opensearch.transport.RemoteTransportException
import java.time.LocalDateTime
@@ -50,6 +55,7 @@ import java.util.Locale
class AttemptSnapshotStep(
val clusterService: ClusterService,
+ val scriptService: ScriptService,
val client: Client,
val config: SnapshotActionConfig,
managedIndexMetaData: ManagedIndexMetaData
@@ -74,15 +80,15 @@ class AttemptSnapshotStep(
info = mutableInfo.toMap()
return this
}
+ val snapshotNameSuffix = "-".plus(
+ LocalDateTime.now(ZoneId.of("UTC"))
+ .format(DateTimeFormatter.ofPattern("uuuu.MM.dd-HH:mm:ss.SSS", Locale.ROOT))
+ )
- snapshotName = config
- .snapshot
- .plus("-")
- .plus(
- LocalDateTime
- .now(ZoneId.of("UTC"))
- .format(DateTimeFormatter.ofPattern("uuuu.MM.dd-HH:mm:ss.SSS", Locale.ROOT))
- )
+ val snapshotScript = Script(ScriptType.INLINE, Script.DEFAULT_TEMPLATE_LANG, config.snapshot, mapOf())
+ // If user intentionally set the snapshot name empty then we are going to honor it
+ val defaultSnapshotName = if (config.snapshot.isBlank()) config.snapshot else indexName
+ snapshotName = compileTemplate(snapshotScript, managedIndexMetaData, defaultSnapshotName).plus(snapshotNameSuffix)
val createSnapshotRequest = CreateSnapshotRequest()
.userMetadata(mapOf("snapshot_created" to "Open Distro for Elasticsearch Index Management"))
@@ -148,6 +154,16 @@ class AttemptSnapshotStep(
info = mutableInfo.toMap()
}
+ private fun compileTemplate(template: Script, managedIndexMetaData: ManagedIndexMetaData, defaultValue: String): String {
+ val contextMap = managedIndexMetaData.convertToMap().filterKeys { key ->
+ key in validTopContextFields
+ }
+ val compiledValue = scriptService.compile(template, TemplateScript.CONTEXT)
+ .newInstance(template.params + mapOf("ctx" to contextMap))
+ .execute()
+ return if (compiledValue.isBlank()) defaultValue else compiledValue
+ }
+
override fun getUpdatedManagedIndexMetaData(currentMetaData: ManagedIndexMetaData): ManagedIndexMetaData {
val currentActionMetaData = currentMetaData.actionMetaData
return currentMetaData.copy(
@@ -159,6 +175,7 @@ class AttemptSnapshotStep(
}
companion object {
+ val validTopContextFields = setOf("index", "indexUuid")
const val name = "attempt_snapshot"
fun getBlockedMessage(denyList: List, repoName: String, index: String) =
"Snapshot repository [$repoName] is blocked in $denyList [index=$index]"
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/addpolicy/AddPolicyAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/addpolicy/AddPolicyAction.kt
index dcb24cb8b..9cdf8f3d8 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/addpolicy/AddPolicyAction.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/addpolicy/AddPolicyAction.kt
@@ -32,6 +32,6 @@ import org.opensearch.indexmanagement.indexstatemanagement.transport.action.ISMS
class AddPolicyAction private constructor() : ActionType(NAME, ::ISMStatusResponse) {
companion object {
val INSTANCE = AddPolicyAction()
- val NAME = "cluster:admin/opendistro/ism/managedindex/add"
+ const val NAME = "cluster:admin/opendistro/ism/managedindex/add"
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/addpolicy/AddPolicyRequest.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/addpolicy/AddPolicyRequest.kt
index 58da2ed21..8d2a493bb 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/addpolicy/AddPolicyRequest.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/addpolicy/AddPolicyRequest.kt
@@ -33,18 +33,10 @@ import org.opensearch.common.io.stream.StreamInput
import org.opensearch.common.io.stream.StreamOutput
import java.io.IOException
-class AddPolicyRequest : ActionRequest {
-
- val indices: List
+class AddPolicyRequest(
+ val indices: List,
val policyID: String
-
- constructor(
- indices: List,
- policyID: String
- ) : super() {
- this.indices = indices
- this.policyID = policyID
- }
+) : ActionRequest() {
@Throws(IOException::class)
constructor(sin: StreamInput) : this(
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/addpolicy/TransportAddPolicyAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/addpolicy/TransportAddPolicyAction.kt
index bf5d898a3..64ec1a869 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/addpolicy/TransportAddPolicyAction.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/addpolicy/TransportAddPolicyAction.kt
@@ -28,6 +28,7 @@ package org.opensearch.indexmanagement.indexstatemanagement.transport.action.add
import org.apache.logging.log4j.LogManager
import org.opensearch.ExceptionsHelper
+import org.opensearch.OpenSearchSecurityException
import org.opensearch.OpenSearchStatusException
import org.opensearch.OpenSearchTimeoutException
import org.opensearch.action.ActionListener
@@ -35,6 +36,8 @@ import org.opensearch.action.admin.cluster.state.ClusterStateRequest
import org.opensearch.action.admin.cluster.state.ClusterStateResponse
import org.opensearch.action.bulk.BulkRequest
import org.opensearch.action.bulk.BulkResponse
+import org.opensearch.action.get.GetRequest
+import org.opensearch.action.get.GetResponse
import org.opensearch.action.get.MultiGetRequest
import org.opensearch.action.get.MultiGetResponse
import org.opensearch.action.support.ActionFilters
@@ -44,43 +47,65 @@ import org.opensearch.action.support.master.AcknowledgedResponse
import org.opensearch.client.node.NodeClient
import org.opensearch.cluster.ClusterState
import org.opensearch.cluster.block.ClusterBlockException
+import org.opensearch.cluster.metadata.IndexNameExpressionResolver
import org.opensearch.cluster.service.ClusterService
import org.opensearch.common.inject.Inject
import org.opensearch.common.settings.Settings
import org.opensearch.common.unit.TimeValue
-import org.opensearch.indexmanagement.IndexManagementIndices
+import org.opensearch.common.xcontent.NamedXContentRegistry
+import org.opensearch.commons.authuser.User
import org.opensearch.indexmanagement.IndexManagementPlugin.Companion.INDEX_MANAGEMENT_INDEX
+import org.opensearch.indexmanagement.indexstatemanagement.model.Policy
import org.opensearch.indexmanagement.indexstatemanagement.opensearchapi.getUuidsForClosedIndices
import org.opensearch.indexmanagement.indexstatemanagement.settings.ManagedIndexSettings
import org.opensearch.indexmanagement.indexstatemanagement.transport.action.ISMStatusResponse
+import org.opensearch.indexmanagement.indexstatemanagement.transport.action.managedIndex.ManagedIndexAction
+import org.opensearch.indexmanagement.indexstatemanagement.transport.action.managedIndex.ManagedIndexRequest
import org.opensearch.indexmanagement.indexstatemanagement.util.FailedIndex
import org.opensearch.indexmanagement.indexstatemanagement.util.managedIndexConfigIndexRequest
+import org.opensearch.indexmanagement.opensearchapi.parseFromGetResponse
+import org.opensearch.indexmanagement.settings.IndexManagementSettings
+import org.opensearch.indexmanagement.util.IndexUtils
+import org.opensearch.indexmanagement.util.SecurityUtils.Companion.buildUser
+import org.opensearch.indexmanagement.util.SecurityUtils.Companion.userHasPermissionForResource
+import org.opensearch.indexmanagement.util.SecurityUtils.Companion.validateUserConfiguration
import org.opensearch.rest.RestStatus
import org.opensearch.tasks.Task
import org.opensearch.transport.TransportService
import java.lang.Exception
+import java.lang.IllegalArgumentException
import java.time.Duration
import java.time.Instant
private val log = LogManager.getLogger(TransportAddPolicyAction::class.java)
+@Suppress("SpreadOperator", "ReturnCount")
class TransportAddPolicyAction @Inject constructor(
val client: NodeClient,
transportService: TransportService,
actionFilters: ActionFilters,
val settings: Settings,
val clusterService: ClusterService,
- val ismIndices: IndexManagementIndices
+ val xContentRegistry: NamedXContentRegistry,
+ val indexNameExpressionResolver: IndexNameExpressionResolver
) : HandledTransportAction(
AddPolicyAction.NAME, transportService, actionFilters, ::AddPolicyRequest
) {
@Volatile private var jobInterval = ManagedIndexSettings.JOB_INTERVAL.get(settings)
+ @Volatile private var jobJitter = ManagedIndexSettings.JITTER.get(settings)
+ @Volatile private var filterByEnabled = IndexManagementSettings.FILTER_BY_BACKEND_ROLES.get(settings)
init {
clusterService.clusterSettings.addSettingsUpdateConsumer(ManagedIndexSettings.JOB_INTERVAL) {
jobInterval = it
}
+ clusterService.clusterSettings.addSettingsUpdateConsumer(ManagedIndexSettings.JITTER) {
+ jobJitter = it
+ }
+ clusterService.clusterSettings.addSettingsUpdateConsumer(IndexManagementSettings.FILTER_BY_BACKEND_ROLES) {
+ filterByEnabled = it
+ }
}
override fun doExecute(task: Task, request: AddPolicyRequest, listener: ActionListener) {
@@ -90,25 +115,119 @@ class TransportAddPolicyAction @Inject constructor(
inner class AddPolicyHandler(
private val client: NodeClient,
private val actionListener: ActionListener,
- private val request: AddPolicyRequest
+ private val request: AddPolicyRequest,
+ private val user: User? = buildUser(client.threadPool().threadContext)
) {
private lateinit var startTime: Instant
+ private lateinit var policy: Policy
+ private val resolvedIndices = mutableListOf()
private val indicesToAdd = mutableMapOf() // uuid: name
private val failedIndices: MutableList = mutableListOf()
fun start() {
- ismIndices.checkAndUpdateIMConfigIndex(object : ActionListener {
- override fun onResponse(response: AcknowledgedResponse) {
- onCreateMappingsResponse(response)
+ if (!validateUserConfiguration(user, filterByEnabled, actionListener)) {
+ return
+ }
+ val requestedIndices = mutableListOf()
+ request.indices.forEach { index ->
+ requestedIndices.addAll(
+ indexNameExpressionResolver.concreteIndexNames(
+ clusterService.state(),
+ IndicesOptions.lenientExpand(),
+ true,
+ index
+ )
+ )
+ }
+ if (requestedIndices.isEmpty()) {
+ // Nothing to do will ignore since found no matching indices
+ actionListener.onResponse(ISMStatusResponse(0, failedIndices))
+ return
+ }
+ if (user == null) {
+ resolvedIndices.addAll(requestedIndices)
+ getPolicy()
+ } else {
+ validateAndGetPolicy(0, requestedIndices)
+ }
+ }
+
+ /**
+ * We filter the requested indices to the indices user has permission to manage and apply policies only on top of those
+ */
+ private fun validateAndGetPolicy(current: Int, indices: List) {
+ val request = ManagedIndexRequest().indices(indices[current])
+ client.execute(
+ ManagedIndexAction.INSTANCE,
+ request,
+ object : ActionListener {
+ override fun onResponse(response: AcknowledgedResponse) {
+ resolvedIndices.add(indices[current])
+ proceed(current, indices)
+ }
+
+ override fun onFailure(e: Exception) {
+ when (e is OpenSearchSecurityException) {
+ true -> {
+ proceed(current, indices)
+ }
+ false -> {
+ // failing the request for any other exception
+ actionListener.onFailure(e)
+ }
+ }
+ }
+ }
+ )
+ }
+
+ private fun proceed(current: Int, indices: List) {
+ if (current < indices.count() - 1) {
+ validateAndGetPolicy(current + 1, indices)
+ } else {
+ // sanity check that there are indices - if none then return
+ if (resolvedIndices.isEmpty()) {
+ actionListener.onResponse(ISMStatusResponse(0, failedIndices))
+ return
}
+ getPolicy()
+ }
+ }
- override fun onFailure(t: Exception) {
- actionListener.onFailure(ExceptionsHelper.unwrapCause(t) as Exception)
+ private fun getPolicy() {
+ val getRequest = GetRequest(INDEX_MANAGEMENT_INDEX, request.policyID)
+
+ client.threadPool().threadContext.stashContext().use {
+ if (!validateUserConfiguration(user, filterByEnabled, actionListener)) {
+ return
}
- })
+ client.get(getRequest, ActionListener.wrap(::onGetPolicyResponse, ::onFailure))
+ }
}
- private fun onCreateMappingsResponse(response: AcknowledgedResponse) {
+ private fun onGetPolicyResponse(response: GetResponse) {
+ if (!response.isExists || response.isSourceEmpty) {
+ actionListener.onFailure(OpenSearchStatusException("Could not find policy=${request.policyID}", RestStatus.NOT_FOUND))
+ return
+ }
+ try {
+ this.policy = parseFromGetResponse(response, xContentRegistry, Policy.Companion::parse)
+ } catch (e: IllegalArgumentException) {
+ actionListener.onFailure(OpenSearchStatusException("Could not find policy=${request.policyID}", RestStatus.NOT_FOUND))
+ return
+ }
+ if (!userHasPermissionForResource(user, policy.user, filterByEnabled, "policy", request.policyID, actionListener)) {
+ return
+ }
+
+ IndexUtils.checkAndUpdateConfigIndexMapping(
+ clusterService.state(),
+ client.admin().indices(),
+ ActionListener.wrap(::onUpdateMapping, ::onFailure)
+ )
+ }
+
+ private fun onUpdateMapping(response: AcknowledgedResponse) {
if (response.isAcknowledged) {
log.info("Successfully created or updated $INDEX_MANAGEMENT_INDEX with newest mappings.")
getClusterState()
@@ -130,7 +249,7 @@ class TransportAddPolicyAction @Inject constructor(
val clusterStateRequest = ClusterStateRequest()
.clear()
- .indices(*request.indices.toTypedArray())
+ .indices(*resolvedIndices.toTypedArray())
.metadata(true)
.local(false)
.waitForTimeout(TimeValue.timeValueMillis(ADD_POLICY_TIMEOUT_IN_MILLIS))
@@ -213,7 +332,9 @@ class TransportAddPolicyAction @Inject constructor(
val bulkReq = BulkRequest().timeout(TimeValue.timeValueMillis(bulkReqTimeout))
indicesToAdd.forEach { (uuid, name) ->
- bulkReq.add(managedIndexConfigIndexRequest(name, uuid, request.policyID, jobInterval))
+ bulkReq.add(
+ managedIndexConfigIndexRequest(name, uuid, request.policyID, jobInterval, policy = policy.copy(user = this.user), jobJitter)
+ )
}
client.bulk(
@@ -251,6 +372,10 @@ class TransportAddPolicyAction @Inject constructor(
actionListener.onResponse(ISMStatusResponse(0, failedIndices))
}
}
+
+ private fun onFailure(t: Exception) {
+ actionListener.onFailure(ExceptionsHelper.unwrapCause(t) as Exception)
+ }
}
companion object {
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/changepolicy/ChangePolicyAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/changepolicy/ChangePolicyAction.kt
index 37cdfbff2..94fdfd732 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/changepolicy/ChangePolicyAction.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/changepolicy/ChangePolicyAction.kt
@@ -32,6 +32,6 @@ import org.opensearch.indexmanagement.indexstatemanagement.transport.action.ISMS
class ChangePolicyAction private constructor() : ActionType(NAME, ::ISMStatusResponse) {
companion object {
val INSTANCE = ChangePolicyAction()
- val NAME = "cluster:admin/opendistro/ism/managedindex/change"
+ const val NAME = "cluster:admin/opendistro/ism/managedindex/change"
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/changepolicy/ChangePolicyRequest.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/changepolicy/ChangePolicyRequest.kt
index 2df9a4f8e..28e3851e2 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/changepolicy/ChangePolicyRequest.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/changepolicy/ChangePolicyRequest.kt
@@ -34,18 +34,10 @@ import org.opensearch.common.io.stream.StreamOutput
import org.opensearch.indexmanagement.indexstatemanagement.model.ChangePolicy
import java.io.IOException
-class ChangePolicyRequest : ActionRequest {
-
- val indices: List
+class ChangePolicyRequest(
+ val indices: List,
val changePolicy: ChangePolicy
-
- constructor(
- indices: List,
- changePolicy: ChangePolicy
- ) : super() {
- this.indices = indices
- this.changePolicy = changePolicy
- }
+) : ActionRequest() {
@Throws(IOException::class)
constructor(sin: StreamInput) : this(
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/changepolicy/TransportChangePolicyAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/changepolicy/TransportChangePolicyAction.kt
index 06e98196c..6981ea46f 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/changepolicy/TransportChangePolicyAction.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/changepolicy/TransportChangePolicyAction.kt
@@ -28,6 +28,7 @@ package org.opensearch.indexmanagement.indexstatemanagement.transport.action.cha
import org.apache.logging.log4j.LogManager
import org.opensearch.ExceptionsHelper
+import org.opensearch.OpenSearchSecurityException
import org.opensearch.OpenSearchStatusException
import org.opensearch.action.ActionListener
import org.opensearch.action.admin.cluster.state.ClusterStateRequest
@@ -46,10 +47,9 @@ import org.opensearch.client.node.NodeClient
import org.opensearch.cluster.ClusterState
import org.opensearch.cluster.service.ClusterService
import org.opensearch.common.inject.Inject
-import org.opensearch.common.xcontent.LoggingDeprecationHandler
+import org.opensearch.common.settings.Settings
import org.opensearch.common.xcontent.NamedXContentRegistry
-import org.opensearch.common.xcontent.XContentHelper
-import org.opensearch.common.xcontent.XContentType
+import org.opensearch.commons.authuser.User
import org.opensearch.index.Index
import org.opensearch.indexmanagement.IndexManagementPlugin
import org.opensearch.indexmanagement.indexstatemanagement.model.ManagedIndexConfig
@@ -61,38 +61,58 @@ import org.opensearch.indexmanagement.indexstatemanagement.opensearchapi.getMana
import org.opensearch.indexmanagement.indexstatemanagement.opensearchapi.mgetResponseToList
import org.opensearch.indexmanagement.indexstatemanagement.resthandler.RestChangePolicyAction
import org.opensearch.indexmanagement.indexstatemanagement.transport.action.ISMStatusResponse
+import org.opensearch.indexmanagement.indexstatemanagement.transport.action.managedIndex.ManagedIndexAction
+import org.opensearch.indexmanagement.indexstatemanagement.transport.action.managedIndex.ManagedIndexRequest
import org.opensearch.indexmanagement.indexstatemanagement.util.FailedIndex
import org.opensearch.indexmanagement.indexstatemanagement.util.isSafeToChange
import org.opensearch.indexmanagement.indexstatemanagement.util.updateManagedIndexRequest
import org.opensearch.indexmanagement.opensearchapi.contentParser
+import org.opensearch.indexmanagement.opensearchapi.parseFromGetResponse
import org.opensearch.indexmanagement.opensearchapi.parseWithType
+import org.opensearch.indexmanagement.settings.IndexManagementSettings
+import org.opensearch.indexmanagement.util.IndexManagementException
import org.opensearch.indexmanagement.util.IndexUtils
import org.opensearch.indexmanagement.util.NO_ID
+import org.opensearch.indexmanagement.util.SecurityUtils.Companion.buildUser
+import org.opensearch.indexmanagement.util.SecurityUtils.Companion.userHasPermissionForResource
+import org.opensearch.indexmanagement.util.SecurityUtils.Companion.validateUserConfiguration
import org.opensearch.rest.RestStatus
import org.opensearch.search.fetch.subphase.FetchSourceContext
import org.opensearch.tasks.Task
import org.opensearch.transport.TransportService
+import java.lang.IllegalArgumentException
private val log = LogManager.getLogger(TransportChangePolicyAction::class.java)
+@Suppress("SpreadOperator", "TooManyFunctions")
class TransportChangePolicyAction @Inject constructor(
val client: NodeClient,
transportService: TransportService,
actionFilters: ActionFilters,
val clusterService: ClusterService,
+ val settings: Settings,
val xContentRegistry: NamedXContentRegistry
) : HandledTransportAction(
ChangePolicyAction.NAME, transportService, actionFilters, ::ChangePolicyRequest
) {
+
+ @Volatile private var filterByEnabled = IndexManagementSettings.FILTER_BY_BACKEND_ROLES.get(settings)
+
+ init {
+ clusterService.clusterSettings.addSettingsUpdateConsumer(IndexManagementSettings.FILTER_BY_BACKEND_ROLES) {
+ filterByEnabled = it
+ }
+ }
+
override fun doExecute(task: Task, request: ChangePolicyRequest, listener: ActionListener) {
ChangePolicyHandler(client, listener, request).start()
}
- @Suppress("TooManyFunctions")
inner class ChangePolicyHandler(
private val client: NodeClient,
private val actionListener: ActionListener,
- private val request: ChangePolicyRequest
+ private val request: ChangePolicyRequest,
+ private val user: User? = buildUser(client.threadPool().threadContext)
) {
private val failedIndices = mutableListOf()
@@ -100,14 +120,53 @@ class TransportChangePolicyAction @Inject constructor(
private val indexUuidToCurrentState = mutableMapOf()
private val changePolicy = request.changePolicy
private lateinit var policy: Policy
- private lateinit var getPolicyResponse: GetResponse
private lateinit var clusterState: ClusterState
private var updated: Int = 0
fun start() {
+ if (user == null) {
+ getPolicy()
+ } else {
+ validateAndGetPolicy()
+ }
+ }
+
+ private fun validateAndGetPolicy() {
+ val request = ManagedIndexRequest().indices(*request.indices.toTypedArray())
+ client.execute(
+ ManagedIndexAction.INSTANCE,
+ request,
+ object : ActionListener {
+ override fun onResponse(response: AcknowledgedResponse) {
+ getPolicy()
+ }
+
+ override fun onFailure(e: java.lang.Exception) {
+ actionListener.onFailure(
+ IndexManagementException.wrap(
+ when (e is OpenSearchSecurityException) {
+ true -> OpenSearchStatusException(
+ "User doesn't have required index permissions on one or more requested indices: ${e.localizedMessage}",
+ RestStatus.FORBIDDEN
+ )
+ false -> e
+ }
+ )
+ )
+ }
+ }
+ )
+ }
+
+ private fun getPolicy() {
val getRequest = GetRequest(IndexManagementPlugin.INDEX_MANAGEMENT_INDEX, changePolicy.policyID)
- client.get(getRequest, ActionListener.wrap(::onGetPolicyResponse, ::onFailure))
+ client.threadPool().threadContext.stashContext().use {
+ if (!validateUserConfiguration(user, filterByEnabled, actionListener)) {
+ return
+ }
+ client.get(getRequest, ActionListener.wrap(::onGetPolicyResponse, ::onFailure))
+ }
}
private fun onGetPolicyResponse(response: GetResponse) {
@@ -115,7 +174,15 @@ class TransportChangePolicyAction @Inject constructor(
actionListener.onFailure(OpenSearchStatusException("Could not find policy=${request.changePolicy.policyID}", RestStatus.NOT_FOUND))
return
}
- this.getPolicyResponse = response
+ try {
+ policy = parseFromGetResponse(response, xContentRegistry, Policy.Companion::parse)
+ } catch (e: IllegalArgumentException) {
+ actionListener.onFailure(OpenSearchStatusException("Could not find policy=${request.changePolicy.policyID}", RestStatus.NOT_FOUND))
+ return
+ }
+ if (!userHasPermissionForResource(user, policy.user, filterByEnabled, "policy", request.changePolicy.policyID, actionListener)) {
+ return
+ }
IndexUtils.checkAndUpdateConfigIndexMapping(
clusterService.state(),
@@ -135,13 +202,6 @@ class TransportChangePolicyAction @Inject constructor(
return
}
- policy = XContentHelper.createParser(
- xContentRegistry,
- LoggingDeprecationHandler.INSTANCE,
- getPolicyResponse.sourceAsBytesRef,
- XContentType.JSON
- ).use { it.parseWithType(getPolicyResponse.id, getPolicyResponse.seqNo, getPolicyResponse.primaryTerm, Policy.Companion::parse) }
-
getClusterState()
}
@@ -271,7 +331,7 @@ class TransportChangePolicyAction @Inject constructor(
// compare the sweptConfig policy to the get policy here and update changePolicy
val currentStateName = indexUuidToCurrentState[sweptConfig.uuid]
val updatedChangePolicy = changePolicy
- .copy(isSafe = sweptConfig.policy?.isSafeToChange(currentStateName, policy, changePolicy) == true)
+ .copy(isSafe = sweptConfig.policy?.isSafeToChange(currentStateName, policy, changePolicy) == true, user = this.user)
bulkUpdateManagedIndexRequest.add(updateManagedIndexRequest(sweptConfig.copy(changePolicy = updatedChangePolicy)))
mapOfItemIdToIndex[id] = Index(sweptConfig.index, sweptConfig.uuid)
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/deletepolicy/DeletePolicyAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/deletepolicy/DeletePolicyAction.kt
index 87f6b7548..04a93d257 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/deletepolicy/DeletePolicyAction.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/deletepolicy/DeletePolicyAction.kt
@@ -32,6 +32,6 @@ import org.opensearch.action.delete.DeleteResponse
class DeletePolicyAction private constructor() : ActionType(NAME, ::DeleteResponse) {
companion object {
val INSTANCE = DeletePolicyAction()
- val NAME = "cluster:admin/opendistro/ism/policy/delete"
+ const val NAME = "cluster:admin/opendistro/ism/policy/delete"
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/deletepolicy/DeletePolicyRequest.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/deletepolicy/DeletePolicyRequest.kt
index 500aa9366..ff8d98a28 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/deletepolicy/DeletePolicyRequest.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/deletepolicy/DeletePolicyRequest.kt
@@ -34,18 +34,7 @@ import org.opensearch.common.io.stream.StreamInput
import org.opensearch.common.io.stream.StreamOutput
import java.io.IOException
-class DeletePolicyRequest : ActionRequest {
-
- val policyID: String
- val refreshPolicy: WriteRequest.RefreshPolicy
-
- constructor(
- policyID: String,
- refreshPolicy: WriteRequest.RefreshPolicy
- ) : super() {
- this.policyID = policyID
- this.refreshPolicy = refreshPolicy
- }
+class DeletePolicyRequest(val policyID: String, val refreshPolicy: WriteRequest.RefreshPolicy) : ActionRequest() {
@Throws(IOException::class)
constructor(sin: StreamInput) : this(
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/deletepolicy/TransportDeletePolicyAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/deletepolicy/TransportDeletePolicyAction.kt
index 4b24089bd..400c4bf30 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/deletepolicy/TransportDeletePolicyAction.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/deletepolicy/TransportDeletePolicyAction.kt
@@ -26,28 +26,109 @@
package org.opensearch.indexmanagement.indexstatemanagement.transport.action.deletepolicy
+import org.opensearch.ExceptionsHelper
+import org.opensearch.OpenSearchStatusException
import org.opensearch.action.ActionListener
import org.opensearch.action.delete.DeleteRequest
import org.opensearch.action.delete.DeleteResponse
+import org.opensearch.action.get.GetRequest
+import org.opensearch.action.get.GetResponse
import org.opensearch.action.support.ActionFilters
import org.opensearch.action.support.HandledTransportAction
+import org.opensearch.client.Client
import org.opensearch.client.node.NodeClient
+import org.opensearch.cluster.service.ClusterService
import org.opensearch.common.inject.Inject
+import org.opensearch.common.settings.Settings
+import org.opensearch.common.xcontent.NamedXContentRegistry
+import org.opensearch.commons.authuser.User
import org.opensearch.indexmanagement.IndexManagementPlugin
+import org.opensearch.indexmanagement.indexstatemanagement.model.Policy
+import org.opensearch.indexmanagement.opensearchapi.parseFromGetResponse
+import org.opensearch.indexmanagement.settings.IndexManagementSettings
+import org.opensearch.indexmanagement.util.SecurityUtils.Companion.buildUser
+import org.opensearch.indexmanagement.util.SecurityUtils.Companion.userHasPermissionForResource
+import org.opensearch.rest.RestStatus
import org.opensearch.tasks.Task
import org.opensearch.transport.TransportService
+import java.lang.IllegalArgumentException
+@Suppress("ReturnCount")
class TransportDeletePolicyAction @Inject constructor(
val client: NodeClient,
transportService: TransportService,
- actionFilters: ActionFilters
+ actionFilters: ActionFilters,
+ val clusterService: ClusterService,
+ val settings: Settings,
+ val xContentRegistry: NamedXContentRegistry
) : HandledTransportAction(
DeletePolicyAction.NAME, transportService, actionFilters, ::DeletePolicyRequest
) {
+
+ @Volatile private var filterByEnabled = IndexManagementSettings.FILTER_BY_BACKEND_ROLES.get(settings)
+
+ init {
+ clusterService.clusterSettings.addSettingsUpdateConsumer(IndexManagementSettings.FILTER_BY_BACKEND_ROLES) {
+ filterByEnabled = it
+ }
+ }
+
override fun doExecute(task: Task, request: DeletePolicyRequest, listener: ActionListener) {
- val deleteRequest = DeleteRequest(IndexManagementPlugin.INDEX_MANAGEMENT_INDEX, request.policyID)
- .setRefreshPolicy(request.refreshPolicy)
+ DeletePolicyHandler(client, listener, request).start()
+ }
+
+ inner class DeletePolicyHandler(
+ private val client: Client,
+ private val actionListener: ActionListener,
+ private val request: DeletePolicyRequest,
+ private val user: User? = buildUser(client.threadPool().threadContext)
+ ) {
+
+ fun start() {
+ client.threadPool().threadContext.stashContext().use {
+ getPolicy()
+ }
+ }
+
+ private fun getPolicy() {
+ val getRequest = GetRequest(IndexManagementPlugin.INDEX_MANAGEMENT_INDEX, request.policyID)
+ client.get(
+ getRequest,
+ object : ActionListener {
+ override fun onResponse(response: GetResponse) {
+ if (!response.isExists) {
+ actionListener.onFailure(OpenSearchStatusException("Policy ${request.policyID} is not found", RestStatus.NOT_FOUND))
+ return
+ }
+
+ val policy: Policy?
+ try {
+ policy = parseFromGetResponse(response, xContentRegistry, Policy.Companion::parse)
+ } catch (e: IllegalArgumentException) {
+ actionListener.onFailure(OpenSearchStatusException("Policy ${request.policyID} is not found", RestStatus.NOT_FOUND))
+ return
+ }
+ if (!userHasPermissionForResource(user, policy.user, filterByEnabled, "policy", request.policyID, actionListener)) {
+ return
+ } else {
+ delete()
+ }
+ }
+
+ override fun onFailure(t: Exception) {
+ actionListener.onFailure(ExceptionsHelper.unwrapCause(t) as Exception)
+ }
+ }
+ )
+ }
+
+ private fun delete() {
+ val deleteRequest = DeleteRequest(IndexManagementPlugin.INDEX_MANAGEMENT_INDEX, request.policyID)
+ .setRefreshPolicy(request.refreshPolicy)
- client.delete(deleteRequest, listener)
+ client.threadPool().threadContext.stashContext().use {
+ client.delete(deleteRequest, actionListener)
+ }
+ }
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/explain/ExplainAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/explain/ExplainAction.kt
index bdf5c7388..807eb32c5 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/explain/ExplainAction.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/explain/ExplainAction.kt
@@ -31,6 +31,6 @@ import org.opensearch.action.ActionType
class ExplainAction private constructor() : ActionType(NAME, ::ExplainResponse) {
companion object {
val INSTANCE = ExplainAction()
- val NAME = "cluster:admin/opendistro/ism/managedindex/explain"
+ const val NAME = "cluster:admin/opendistro/ism/managedindex/explain"
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/explain/ExplainAllResponse.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/explain/ExplainAllResponse.kt
index 66484e4b7..5f5e21cff 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/explain/ExplainAllResponse.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/explain/ExplainAllResponse.kt
@@ -32,6 +32,7 @@ import org.opensearch.common.xcontent.ToXContent
import org.opensearch.common.xcontent.ToXContentObject
import org.opensearch.common.xcontent.XContentBuilder
import org.opensearch.indexmanagement.indexstatemanagement.model.ManagedIndexMetaData
+import org.opensearch.indexmanagement.indexstatemanagement.settings.LegacyOpenDistroManagedIndexSettings
import org.opensearch.indexmanagement.indexstatemanagement.settings.ManagedIndexSettings
import java.io.IOException
@@ -71,6 +72,7 @@ class ExplainAllResponse : ExplainResponse, ToXContentObject {
builder.startObject()
indexNames.forEachIndexed { ind, name ->
builder.startObject(name)
+ builder.field(LegacyOpenDistroManagedIndexSettings.POLICY_ID.key, indexPolicyIDs[ind])
builder.field(ManagedIndexSettings.POLICY_ID.key, indexPolicyIDs[ind])
indexMetadatas[ind]?.toXContent(builder, ToXContent.EMPTY_PARAMS)
builder.field("enabled", enabledState[name])
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 cdb7c2f02..fd57065cd 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
@@ -33,6 +33,7 @@ import org.opensearch.common.xcontent.ToXContent
import org.opensearch.common.xcontent.ToXContentObject
import org.opensearch.common.xcontent.XContentBuilder
import org.opensearch.indexmanagement.indexstatemanagement.model.ManagedIndexMetaData
+import org.opensearch.indexmanagement.indexstatemanagement.settings.LegacyOpenDistroManagedIndexSettings
import org.opensearch.indexmanagement.indexstatemanagement.settings.ManagedIndexSettings
import java.io.IOException
@@ -71,6 +72,7 @@ open class ExplainResponse : ActionResponse, ToXContentObject {
builder.startObject()
indexNames.forEachIndexed { ind, name ->
builder.startObject(name)
+ builder.field(LegacyOpenDistroManagedIndexSettings.POLICY_ID.key, indexPolicyIDs[ind])
builder.field(ManagedIndexSettings.POLICY_ID.key, indexPolicyIDs[ind])
indexMetadatas[ind]?.toXContent(builder, ToXContent.EMPTY_PARAMS)
builder.endObject()
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 425332e46..115026100 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
@@ -28,6 +28,7 @@ package org.opensearch.indexmanagement.indexstatemanagement.transport.action.exp
import org.apache.logging.log4j.LogManager
import org.opensearch.ExceptionsHelper
+import org.opensearch.OpenSearchSecurityException
import org.opensearch.action.ActionListener
import org.opensearch.action.admin.cluster.state.ClusterStateRequest
import org.opensearch.action.admin.cluster.state.ClusterStateResponse
@@ -39,13 +40,17 @@ import org.opensearch.action.search.SearchResponse
import org.opensearch.action.support.ActionFilters
import org.opensearch.action.support.HandledTransportAction
import org.opensearch.action.support.IndicesOptions
+import org.opensearch.action.support.master.AcknowledgedResponse
import org.opensearch.client.node.NodeClient
import org.opensearch.cluster.metadata.IndexMetadata
+import org.opensearch.cluster.service.ClusterService
import org.opensearch.common.inject.Inject
+import org.opensearch.common.util.concurrent.ThreadContext
import org.opensearch.common.xcontent.LoggingDeprecationHandler
import org.opensearch.common.xcontent.NamedXContentRegistry
import org.opensearch.common.xcontent.XContentHelper
import org.opensearch.common.xcontent.XContentType
+import org.opensearch.commons.authuser.User
import org.opensearch.index.IndexNotFoundException
import org.opensearch.index.query.Operator
import org.opensearch.index.query.QueryBuilders
@@ -53,8 +58,11 @@ import org.opensearch.indexmanagement.IndexManagementPlugin.Companion.INDEX_MANA
import org.opensearch.indexmanagement.indexstatemanagement.ManagedIndexCoordinator.Companion.MAX_HITS
import org.opensearch.indexmanagement.indexstatemanagement.model.ManagedIndexMetaData
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
import org.opensearch.indexmanagement.indexstatemanagement.util.isMetadataMoved
import org.opensearch.indexmanagement.indexstatemanagement.util.managedIndexMetadataID
+import org.opensearch.indexmanagement.util.SecurityUtils.Companion.buildUser
import org.opensearch.search.builder.SearchSourceBuilder
import org.opensearch.search.fetch.subphase.FetchSourceContext.FETCH_SOURCE
import org.opensearch.search.sort.SortBuilders
@@ -64,14 +72,17 @@ import org.opensearch.transport.TransportService
private val log = LogManager.getLogger(TransportExplainAction::class.java)
+@Suppress("SpreadOperator")
class TransportExplainAction @Inject constructor(
val client: NodeClient,
transportService: TransportService,
actionFilters: ActionFilters,
+ val clusterService: ClusterService,
val xContentRegistry: NamedXContentRegistry
) : HandledTransportAction(
ExplainAction.NAME, transportService, actionFilters, ::ExplainRequest
) {
+
override fun doExecute(task: Task, request: ExplainRequest, listener: ActionListener) {
ExplainHandler(client, listener, request).start()
}
@@ -85,7 +96,8 @@ class TransportExplainAction @Inject constructor(
inner class ExplainHandler(
private val client: NodeClient,
private val actionListener: ActionListener,
- private val request: ExplainRequest
+ private val request: ExplainRequest,
+ private val user: User? = buildUser(client.threadPool().threadContext)
) {
private val indices: List = request.indices
private val explainAll: Boolean = indices.isEmpty()
@@ -97,7 +109,8 @@ class TransportExplainAction @Inject constructor(
private val indexNames: MutableList = mutableListOf()
private val enabledState: MutableMap = mutableMapOf()
- private val rolesMap: MutableMap?> = mutableMapOf()
+ private val indexPolicyIDs = mutableListOf()
+ private val indexMetadatas = mutableListOf()
private var totalManagedIndices = 0
@Suppress("SpreadOperator", "NestedBlockDepth")
@@ -147,69 +160,69 @@ class TransportExplainAction @Inject constructor(
.indices(INDEX_MANAGEMENT_INDEX)
.source(searchSourceBuilder)
- client.search(
- searchRequest,
- object : ActionListener {
- override fun onResponse(response: SearchResponse) {
- val totalHits = response.hits.totalHits
- if (totalHits != null) {
- totalManagedIndices = totalHits.value.toInt()
- }
+ client.threadPool().threadContext.stashContext().use { threadContext ->
+ client.search(
+ searchRequest,
+ object : ActionListener {
+ override fun onResponse(response: SearchResponse) {
+ val totalHits = response.hits.totalHits
+ if (totalHits != null) {
+ totalManagedIndices = totalHits.value.toInt()
+ }
- response.hits.hits.map {
- val hitMap = it.sourceAsMap["managed_index"] as Map
- val managedIndex = hitMap["index"] as String
- managedIndices.add(managedIndex)
- enabledState[managedIndex] = hitMap["enabled"] as Boolean
- val user = hitMap["user"] as Map?
- rolesMap[managedIndex] = user?.get("roles") as List?
- managedIndicesMetaDataMap[managedIndex] = mapOf(
- "index" to hitMap["index"] as String?,
- "index_uuid" to hitMap["index_uuid"] as String?,
- "policy_id" to hitMap["policy_id"] as String?,
- "enabled" to hitMap["enabled"]?.toString()
- )
- }
+ response.hits.hits.map {
+ val hitMap = it.sourceAsMap["managed_index"] as Map
+ val managedIndex = hitMap["index"] as String
+ managedIndices.add(managedIndex)
+ enabledState[managedIndex] = hitMap["enabled"] as Boolean
+ managedIndicesMetaDataMap[managedIndex] = mapOf(
+ "index" to hitMap["index"] as String?,
+ "index_uuid" to hitMap["index_uuid"] as String?,
+ "policy_id" to hitMap["policy_id"] as String?,
+ "enabled" to hitMap["enabled"]?.toString()
+ )
+ }
- // explain all only return managed indices
- if (explainAll) {
- if (managedIndices.size == 0) {
- // edge case: if specify query param pagination size to be 0
- // we still show total managed indices
- emptyResponse(totalManagedIndices)
- return
- } else {
- indexNames.addAll(managedIndices)
- getMetadata(managedIndices)
- return
+ // explain all only return managed indices
+ if (explainAll) {
+ if (managedIndices.size == 0) {
+ // edge case: if specify query param pagination size to be 0
+ // we still show total managed indices
+ sendResponse()
+ return
+ } else {
+ indexNames.addAll(managedIndices)
+ getMetadata(managedIndices, threadContext)
+ return
+ }
}
- }
- // explain/{index} return results for all indices
- indexNames.addAll(indices)
- getMetadata(indices)
- }
+ // explain/{index} return results for all indices
+ indexNames.addAll(indices)
+ getMetadata(indices, threadContext)
+ }
- override fun onFailure(t: Exception) {
- if (t is IndexNotFoundException) {
- // config index hasn't been initialized
- // show all requested indices not managed
- if (indices.isNotEmpty()) {
- indexNames.addAll(indices)
- getMetadata(indices)
+ override fun onFailure(t: Exception) {
+ if (t is IndexNotFoundException) {
+ // config index hasn't been initialized
+ // show all requested indices not managed
+ if (indices.isNotEmpty()) {
+ indexNames.addAll(indices)
+ getMetadata(indices, threadContext)
+ return
+ }
+ sendResponse()
return
}
- emptyResponse()
- return
+ actionListener.onFailure(ExceptionsHelper.unwrapCause(t) as Exception)
}
- actionListener.onFailure(ExceptionsHelper.unwrapCause(t) as Exception)
}
- }
- )
+ )
+ }
}
@Suppress("SpreadOperator")
- fun getMetadata(indices: List) {
+ fun getMetadata(indices: List, threadContext: ThreadContext.StoredContext) {
val clusterStateRequest = ClusterStateRequest()
val strictExpandIndicesOptions = IndicesOptions.strictExpand()
@@ -224,7 +237,7 @@ class TransportExplainAction @Inject constructor(
clusterStateRequest,
object : ActionListener {
override fun onResponse(response: ClusterStateResponse) {
- onClusterStateResponse(response)
+ onClusterStateResponse(response, threadContext)
}
override fun onFailure(t: Exception) {
@@ -234,7 +247,7 @@ class TransportExplainAction @Inject constructor(
)
}
- fun onClusterStateResponse(clusterStateResponse: ClusterStateResponse) {
+ fun onClusterStateResponse(clusterStateResponse: ClusterStateResponse, threadContext: ThreadContext.StoredContext) {
val clusterStateIndexMetadatas = clusterStateResponse.state.metadata.indices.map { it.key to it.value }.toMap()
if (wildcard) {
@@ -252,7 +265,7 @@ class TransportExplainAction @Inject constructor(
object : ActionListener {
override fun onResponse(response: MultiGetResponse) {
val metadataMap = response.responses.map { it.id to getMetadata(it.response)?.toMap() }.toMap()
- buildResponse(indices, metadataMap, clusterStateIndexMetadatas)
+ buildResponse(indices, metadataMap, clusterStateIndexMetadatas, threadContext)
}
override fun onFailure(t: Exception) {
@@ -266,10 +279,9 @@ class TransportExplainAction @Inject constructor(
fun buildResponse(
indices: Map,
metadataMap: Map?>,
- clusterStateIndexMetadatas: Map
+ clusterStateIndexMetadatas: Map,
+ threadContext: ThreadContext.StoredContext
) {
- val indexPolicyIDs = mutableListOf()
- val indexMetadatas = mutableListOf()
// cluster state response will not resisting the sort order
// so use the order from previous search result saved in indexNames
@@ -297,11 +309,79 @@ class TransportExplainAction @Inject constructor(
}
managedIndicesMetaDataMap.clear()
+ if (user == null || indexNames.isEmpty()) {
+ sendResponse()
+ } else {
+ filterAndSendResponse(threadContext)
+ }
+ }
+
+ private fun filterAndSendResponse(threadContext: ThreadContext.StoredContext) {
+ threadContext.restore()
+ val filteredIndices = mutableListOf()
+ val filteredMetadata = mutableListOf()
+ val filteredPolicies = mutableListOf()
+ val enabledStatus = mutableMapOf()
+ filter(0, filteredIndices, filteredMetadata, filteredPolicies, enabledStatus)
+ }
+
+ private fun filter(
+ current: Int,
+ filteredIndices: MutableList,
+ filteredMetadata: MutableList,
+ filteredPolicies: MutableList,
+ enabledStatus: MutableMap
+ ) {
+ val request = ManagedIndexRequest().indices(indexNames[current])
+ client.execute(
+ ManagedIndexAction.INSTANCE,
+ request,
+ object : ActionListener {
+ override fun onResponse(response: AcknowledgedResponse) {
+ filteredIndices.add(indexNames[current])
+ filteredMetadata.add(indexMetadatas[current])
+ filteredPolicies.add(indexPolicyIDs[current])
+ enabledStatus[indexNames[current]] = enabledState.getOrDefault(indexNames[current], false)
+ if (current < indexNames.count() - 1) {
+ // do nothing - skip the index and go to next one
+ filter(current + 1, filteredIndices, filteredMetadata, filteredPolicies, enabledStatus)
+ } else {
+ sendResponse(filteredIndices, filteredMetadata, filteredPolicies, enabledStatus)
+ }
+ }
+
+ override fun onFailure(e: Exception) {
+ when (e is OpenSearchSecurityException) {
+ true -> {
+ totalManagedIndices -= 1
+ if (current < indexNames.count() - 1) {
+ // do nothing - skip the index and go to next one
+ filter(current + 1, filteredIndices, filteredMetadata, filteredPolicies, enabledStatus)
+ } else {
+ sendResponse(filteredIndices, filteredMetadata, filteredPolicies, enabledStatus)
+ }
+ }
+ false -> {
+ actionListener.onFailure(e)
+ }
+ }
+ }
+ }
+ )
+ }
+
+ private fun sendResponse(
+ indices: List = indexNames,
+ metadata: List = indexMetadatas,
+ policies: List = indexPolicyIDs,
+ enabledStatus: Map = enabledState,
+ totalIndices: Int = totalManagedIndices
+ ) {
if (explainAll) {
- actionListener.onResponse(ExplainAllResponse(indexNames, indexPolicyIDs, indexMetadatas, totalManagedIndices, enabledState))
+ actionListener.onResponse(ExplainAllResponse(indices, policies, metadata, totalIndices, enabledStatus))
return
}
- actionListener.onResponse(ExplainResponse(indexNames, indexPolicyIDs, indexMetadatas))
+ actionListener.onResponse(ExplainResponse(indices, policies, metadata))
}
private fun getMetadata(response: GetResponse?): ManagedIndexMetaData? {
@@ -319,13 +399,5 @@ class TransportExplainAction @Inject constructor(
response.id, response.seqNo, response.primaryTerm
)
}
-
- private fun emptyResponse(size: Int = 0) {
- if (explainAll) {
- actionListener.onResponse(ExplainAllResponse(emptyList(), emptyList(), emptyList(), size, emptyMap()))
- return
- }
- actionListener.onResponse(ExplainResponse(emptyList(), emptyList(), emptyList()))
- }
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/getpolicy/GetPoliciesAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/getpolicy/GetPoliciesAction.kt
index a999d265d..a0dc572d7 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/getpolicy/GetPoliciesAction.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/getpolicy/GetPoliciesAction.kt
@@ -31,6 +31,6 @@ import org.opensearch.action.ActionType
class GetPoliciesAction private constructor() : ActionType(NAME, ::GetPoliciesResponse) {
companion object {
val INSTANCE = GetPoliciesAction()
- val NAME = "cluster:admin/opendistro/ism/policy/search"
+ const val NAME = "cluster:admin/opendistro/ism/policy/search"
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/getpolicy/GetPoliciesResponse.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/getpolicy/GetPoliciesResponse.kt
index 2852fafde..a82367de0 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/getpolicy/GetPoliciesResponse.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/getpolicy/GetPoliciesResponse.kt
@@ -33,7 +33,7 @@ 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.util.XCONTENT_WITHOUT_TYPE
+import org.opensearch.indexmanagement.indexstatemanagement.util.XCONTENT_WITHOUT_TYPE_AND_USER
import org.opensearch.indexmanagement.util._ID
import org.opensearch.indexmanagement.util._PRIMARY_TERM
import org.opensearch.indexmanagement.util._SEQ_NO
@@ -72,7 +72,7 @@ class GetPoliciesResponse : ActionResponse, ToXContentObject {
.field(_ID, policy.id)
.field(_SEQ_NO, policy.seqNo)
.field(_PRIMARY_TERM, policy.primaryTerm)
- .field(Policy.POLICY_TYPE, policy, XCONTENT_WITHOUT_TYPE)
+ .field(Policy.POLICY_TYPE, policy, XCONTENT_WITHOUT_TYPE_AND_USER)
.endObject()
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/getpolicy/GetPolicyAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/getpolicy/GetPolicyAction.kt
index 4d0d16c5b..ef6089353 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/getpolicy/GetPolicyAction.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/getpolicy/GetPolicyAction.kt
@@ -31,6 +31,6 @@ import org.opensearch.action.ActionType
class GetPolicyAction private constructor() : ActionType(NAME, ::GetPolicyResponse) {
companion object {
val INSTANCE = GetPolicyAction()
- val NAME = "cluster:admin/opendistro/ism/policy/get"
+ const val NAME = "cluster:admin/opendistro/ism/policy/get"
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/getpolicy/GetPolicyResponse.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/getpolicy/GetPolicyResponse.kt
index 0dc31f899..4ab208eef 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/getpolicy/GetPolicyResponse.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/getpolicy/GetPolicyResponse.kt
@@ -33,7 +33,7 @@ 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.util.XCONTENT_WITHOUT_TYPE
+import org.opensearch.indexmanagement.indexstatemanagement.util.XCONTENT_WITHOUT_TYPE_AND_USER
import org.opensearch.indexmanagement.util._ID
import org.opensearch.indexmanagement.util._PRIMARY_TERM
import org.opensearch.indexmanagement.util._SEQ_NO
@@ -86,7 +86,7 @@ class GetPolicyResponse : ActionResponse, ToXContentObject {
.field(_SEQ_NO, seqNo)
.field(_PRIMARY_TERM, primaryTerm)
if (policy != null) {
- builder.field(Policy.POLICY_TYPE, policy, XCONTENT_WITHOUT_TYPE)
+ builder.field(Policy.POLICY_TYPE, policy, XCONTENT_WITHOUT_TYPE_AND_USER)
}
return builder.endObject()
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/getpolicy/TransportGetPoliciesAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/getpolicy/TransportGetPoliciesAction.kt
index ac3eaf3b4..5b1716f61 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/getpolicy/TransportGetPoliciesAction.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/getpolicy/TransportGetPoliciesAction.kt
@@ -34,17 +34,19 @@ import org.opensearch.action.search.SearchResponse
import org.opensearch.action.support.ActionFilters
import org.opensearch.action.support.HandledTransportAction
import org.opensearch.client.Client
+import org.opensearch.cluster.service.ClusterService
import org.opensearch.common.inject.Inject
-import org.opensearch.common.xcontent.LoggingDeprecationHandler
+import org.opensearch.common.settings.Settings
import org.opensearch.common.xcontent.NamedXContentRegistry
-import org.opensearch.common.xcontent.XContentFactory
-import org.opensearch.common.xcontent.XContentType
import org.opensearch.index.IndexNotFoundException
import org.opensearch.index.query.Operator
import org.opensearch.index.query.QueryBuilders
import org.opensearch.indexmanagement.IndexManagementPlugin.Companion.INDEX_MANAGEMENT_INDEX
import org.opensearch.indexmanagement.indexstatemanagement.model.Policy
-import org.opensearch.indexmanagement.opensearchapi.parseWithType
+import org.opensearch.indexmanagement.opensearchapi.parseFromSearchResponse
+import org.opensearch.indexmanagement.settings.IndexManagementSettings
+import org.opensearch.indexmanagement.util.SecurityUtils.Companion.addUserFilter
+import org.opensearch.indexmanagement.util.SecurityUtils.Companion.buildUser
import org.opensearch.search.builder.SearchSourceBuilder
import org.opensearch.search.sort.SortBuilders
import org.opensearch.search.sort.SortOrder
@@ -57,17 +59,28 @@ class TransportGetPoliciesAction @Inject constructor(
transportService: TransportService,
val client: Client,
actionFilters: ActionFilters,
+ val clusterService: ClusterService,
+ val settings: Settings,
val xContentRegistry: NamedXContentRegistry
) : HandledTransportAction(
GetPoliciesAction.NAME, transportService, actionFilters, ::GetPoliciesRequest
) {
+ @Volatile private var filterByEnabled = IndexManagementSettings.FILTER_BY_BACKEND_ROLES.get(settings)
+
+ init {
+ clusterService.clusterSettings.addSettingsUpdateConsumer(IndexManagementSettings.FILTER_BY_BACKEND_ROLES) {
+ filterByEnabled = it
+ }
+ }
+
override fun doExecute(
task: Task,
getPoliciesRequest: GetPoliciesRequest,
actionListener: ActionListener
) {
val params = getPoliciesRequest.searchParams
+ val user = buildUser(client.threadPool().threadContext)
val sortBuilder = SortBuilders
.fieldSort(params.sortField)
@@ -76,6 +89,9 @@ class TransportGetPoliciesAction @Inject constructor(
val queryBuilder = QueryBuilders.boolQuery()
.must(QueryBuilders.existsQuery("policy"))
+ // Add user filter if enabled
+ addUserFilter(user, queryBuilder, filterByEnabled, "policy.user")
+
queryBuilder.must(
QueryBuilders
.queryStringQuery(params.queryString)
@@ -94,33 +110,26 @@ class TransportGetPoliciesAction @Inject constructor(
.source(searchSourceBuilder)
.indices(INDEX_MANAGEMENT_INDEX)
- client.search(
- searchRequest,
- object : ActionListener {
- override fun onResponse(response: SearchResponse) {
- val totalPolicies = response.hits.totalHits?.value ?: 0
- val policies = response.hits.hits.map {
- val id = it.id
- val seqNo = it.seqNo
- val primaryTerm = it.primaryTerm
- val xcp = XContentFactory.xContent(XContentType.JSON)
- .createParser(xContentRegistry, LoggingDeprecationHandler.INSTANCE, it.sourceAsString)
- xcp.parseWithType(id, seqNo, primaryTerm, Policy.Companion::parse)
- .copy(id = id, seqNo = seqNo, primaryTerm = primaryTerm)
+ client.threadPool().threadContext.stashContext().use {
+ client.search(
+ searchRequest,
+ object : ActionListener {
+ override fun onResponse(response: SearchResponse) {
+ val totalPolicies = response.hits.totalHits?.value ?: 0
+ val policies = parseFromSearchResponse(response, xContentRegistry, Policy.Companion::parse)
+ actionListener.onResponse(GetPoliciesResponse(policies, totalPolicies.toInt()))
}
- actionListener.onResponse(GetPoliciesResponse(policies, totalPolicies.toInt()))
- }
-
- override fun onFailure(t: Exception) {
- if (t is IndexNotFoundException) {
- // config index hasn't been initialized, catch this here and show empty result on Kibana
- actionListener.onResponse(GetPoliciesResponse(emptyList(), 0))
- return
+ override fun onFailure(t: Exception) {
+ if (t is IndexNotFoundException) {
+ // config index hasn't been initialized, catch this here and show empty result on Kibana
+ actionListener.onResponse(GetPoliciesResponse(emptyList(), 0))
+ return
+ }
+ actionListener.onFailure(ExceptionsHelper.unwrapCause(t) as Exception)
}
- actionListener.onFailure(ExceptionsHelper.unwrapCause(t) as Exception)
}
- }
- )
+ )
+ }
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/getpolicy/TransportGetPolicyAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/getpolicy/TransportGetPolicyAction.kt
index 38df8497a..266df16fe 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/getpolicy/TransportGetPolicyAction.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/getpolicy/TransportGetPolicyAction.kt
@@ -34,26 +34,42 @@ import org.opensearch.action.get.GetResponse
import org.opensearch.action.support.ActionFilters
import org.opensearch.action.support.HandledTransportAction
import org.opensearch.client.node.NodeClient
+import org.opensearch.cluster.service.ClusterService
import org.opensearch.common.inject.Inject
-import org.opensearch.common.xcontent.LoggingDeprecationHandler
+import org.opensearch.common.settings.Settings
import org.opensearch.common.xcontent.NamedXContentRegistry
-import org.opensearch.common.xcontent.XContentHelper
-import org.opensearch.common.xcontent.XContentType
+import org.opensearch.commons.authuser.User
import org.opensearch.indexmanagement.IndexManagementPlugin
import org.opensearch.indexmanagement.indexstatemanagement.model.Policy
-import org.opensearch.indexmanagement.opensearchapi.parseWithType
+import org.opensearch.indexmanagement.opensearchapi.parseFromGetResponse
+import org.opensearch.indexmanagement.settings.IndexManagementSettings.Companion.FILTER_BY_BACKEND_ROLES
+import org.opensearch.indexmanagement.util.SecurityUtils.Companion.buildUser
+import org.opensearch.indexmanagement.util.SecurityUtils.Companion.userHasPermissionForResource
import org.opensearch.rest.RestStatus
import org.opensearch.tasks.Task
import org.opensearch.transport.TransportService
+import java.lang.IllegalArgumentException
+@Suppress("ReturnCount")
class TransportGetPolicyAction @Inject constructor(
val client: NodeClient,
transportService: TransportService,
actionFilters: ActionFilters,
+ val clusterService: ClusterService,
+ val settings: Settings,
val xContentRegistry: NamedXContentRegistry
) : HandledTransportAction(
GetPolicyAction.NAME, transportService, actionFilters, ::GetPolicyRequest
) {
+
+ @Volatile private var filterByEnabled = FILTER_BY_BACKEND_ROLES.get(settings)
+
+ init {
+ clusterService.clusterSettings.addSettingsUpdateConsumer(FILTER_BY_BACKEND_ROLES) {
+ filterByEnabled = it
+ }
+ }
+
override fun doExecute(task: Task, request: GetPolicyRequest, listener: ActionListener) {
GetPolicyHandler(client, listener, request).start()
}
@@ -61,25 +77,27 @@ class TransportGetPolicyAction @Inject constructor(
inner class GetPolicyHandler(
private val client: NodeClient,
private val actionListener: ActionListener,
- private val request: GetPolicyRequest
+ private val request: GetPolicyRequest,
+ private val user: User? = buildUser(client.threadPool().threadContext)
) {
fun start() {
val getRequest = GetRequest(IndexManagementPlugin.INDEX_MANAGEMENT_INDEX, request.policyID)
.version(request.version)
- .fetchSourceContext(request.fetchSrcContext)
- client.get(
- getRequest,
- object : ActionListener {
- override fun onResponse(response: GetResponse) {
- onGetResponse(response)
- }
+ client.threadPool().threadContext.stashContext().use {
+ client.get(
+ getRequest,
+ object : ActionListener {
+ override fun onResponse(response: GetResponse) {
+ onGetResponse(response)
+ }
- override fun onFailure(t: Exception) {
- actionListener.onFailure(ExceptionsHelper.unwrapCause(t) as Exception)
+ override fun onFailure(t: Exception) {
+ actionListener.onFailure(ExceptionsHelper.unwrapCause(t) as Exception)
+ }
}
- }
- )
+ )
+ }
}
fun onGetResponse(response: GetResponse) {
@@ -88,21 +106,24 @@ class TransportGetPolicyAction @Inject constructor(
return
}
- var policy: Policy? = null
- if (!response.isSourceEmpty) {
- XContentHelper.createParser(
- xContentRegistry,
- LoggingDeprecationHandler.INSTANCE,
- response.sourceAsBytesRef,
- XContentType.JSON
- ).use { xcp ->
- policy = xcp.parseWithType(response.id, response.seqNo, response.primaryTerm, Policy.Companion::parse)
+ val policy: Policy?
+ try {
+ policy = parseFromGetResponse(response, xContentRegistry, Policy.Companion::parse)
+ } catch (e: IllegalArgumentException) {
+ actionListener.onFailure(OpenSearchStatusException("Policy not found", RestStatus.NOT_FOUND))
+ return
+ }
+ if (!userHasPermissionForResource(user, policy.user, filterByEnabled, "policy", request.policyID, actionListener)) {
+ return
+ } else {
+ // if HEAD request don't return the policy
+ val policyResponse = if (!request.fetchSrcContext.fetchSource()) {
+ GetPolicyResponse(response.id, response.version, response.seqNo, response.primaryTerm, null)
+ } else {
+ GetPolicyResponse(response.id, response.version, response.seqNo, response.primaryTerm, policy)
}
+ actionListener.onResponse(policyResponse)
}
-
- actionListener.onResponse(
- GetPolicyResponse(response.id, response.version, response.seqNo, response.primaryTerm, policy)
- )
}
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/indexpolicy/IndexPolicyAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/indexpolicy/IndexPolicyAction.kt
index a57878b17..1ef16d001 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/indexpolicy/IndexPolicyAction.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/indexpolicy/IndexPolicyAction.kt
@@ -31,6 +31,6 @@ import org.opensearch.action.ActionType
class IndexPolicyAction private constructor() : ActionType(NAME, ::IndexPolicyResponse) {
companion object {
val INSTANCE = IndexPolicyAction()
- val NAME = "cluster:admin/opendistro/ism/policy/write"
+ const val NAME = "cluster:admin/opendistro/ism/policy/write"
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/indexpolicy/IndexPolicyResponse.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/indexpolicy/IndexPolicyResponse.kt
index 48f8f5331..1a263101f 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/indexpolicy/IndexPolicyResponse.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/indexpolicy/IndexPolicyResponse.kt
@@ -33,6 +33,7 @@ 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.util.XCONTENT_WITHOUT_USER
import org.opensearch.indexmanagement.util._ID
import org.opensearch.indexmanagement.util._PRIMARY_TERM
import org.opensearch.indexmanagement.util._SEQ_NO
@@ -91,7 +92,7 @@ class IndexPolicyResponse : ActionResponse, ToXContentObject {
.field(_VERSION, version)
.field(_PRIMARY_TERM, primaryTerm)
.field(_SEQ_NO, seqNo)
- .field(Policy.POLICY_TYPE, policy)
+ .field(Policy.POLICY_TYPE, policy, XCONTENT_WITHOUT_USER)
.endObject()
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/indexpolicy/TransportIndexPolicyAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/indexpolicy/TransportIndexPolicyAction.kt
index d53dcbe3b..77393e757 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/indexpolicy/TransportIndexPolicyAction.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/indexpolicy/TransportIndexPolicyAction.kt
@@ -39,20 +39,30 @@ import org.opensearch.action.support.ActionFilters
import org.opensearch.action.support.HandledTransportAction
import org.opensearch.action.support.master.AcknowledgedResponse
import org.opensearch.client.node.NodeClient
+import org.opensearch.cluster.service.ClusterService
import org.opensearch.common.inject.Inject
+import org.opensearch.common.settings.Settings
import org.opensearch.common.xcontent.NamedXContentRegistry
import org.opensearch.common.xcontent.XContentFactory
+import org.opensearch.commons.authuser.User
import org.opensearch.index.query.QueryBuilders
import org.opensearch.index.seqno.SequenceNumbers
import org.opensearch.indexmanagement.IndexManagementIndices
import org.opensearch.indexmanagement.IndexManagementPlugin
+import org.opensearch.indexmanagement.indexstatemanagement.ManagedIndexCoordinator.Companion.MAX_HITS
import org.opensearch.indexmanagement.indexstatemanagement.findConflictingPolicyTemplates
+import org.opensearch.indexmanagement.indexstatemanagement.findSelfConflictingTemplates
+import org.opensearch.indexmanagement.indexstatemanagement.model.ISMTemplate
+import org.opensearch.indexmanagement.indexstatemanagement.model.Policy
import org.opensearch.indexmanagement.indexstatemanagement.opensearchapi.filterNotNullValues
-import org.opensearch.indexmanagement.indexstatemanagement.opensearchapi.getPolicyToTemplateMap
import org.opensearch.indexmanagement.indexstatemanagement.util.ISM_TEMPLATE_FIELD
import org.opensearch.indexmanagement.indexstatemanagement.validateFormat
+import org.opensearch.indexmanagement.opensearchapi.parseFromSearchResponse
+import org.opensearch.indexmanagement.settings.IndexManagementSettings
import org.opensearch.indexmanagement.util.IndexManagementException
import org.opensearch.indexmanagement.util.IndexUtils
+import org.opensearch.indexmanagement.util.SecurityUtils.Companion.buildUser
+import org.opensearch.indexmanagement.util.SecurityUtils.Companion.validateUserConfiguration
import org.opensearch.rest.RestStatus
import org.opensearch.search.builder.SearchSourceBuilder
import org.opensearch.tasks.Task
@@ -65,10 +75,21 @@ class TransportIndexPolicyAction @Inject constructor(
transportService: TransportService,
actionFilters: ActionFilters,
val ismIndices: IndexManagementIndices,
+ val clusterService: ClusterService,
+ val settings: Settings,
val xContentRegistry: NamedXContentRegistry
) : HandledTransportAction(
IndexPolicyAction.NAME, transportService, actionFilters, ::IndexPolicyRequest
) {
+
+ @Volatile private var filterByEnabled = IndexManagementSettings.FILTER_BY_BACKEND_ROLES.get(settings)
+
+ init {
+ clusterService.clusterSettings.addSettingsUpdateConsumer(IndexManagementSettings.FILTER_BY_BACKEND_ROLES) {
+ filterByEnabled = it
+ }
+ }
+
override fun doExecute(task: Task, request: IndexPolicyRequest, listener: ActionListener) {
IndexPolicyHandler(client, listener, request).start()
}
@@ -76,18 +97,24 @@ class TransportIndexPolicyAction @Inject constructor(
inner class IndexPolicyHandler(
private val client: NodeClient,
private val actionListener: ActionListener,
- private val request: IndexPolicyRequest
+ private val request: IndexPolicyRequest,
+ private val user: User? = buildUser(client.threadPool().threadContext)
) {
fun start() {
- ismIndices.checkAndUpdateIMConfigIndex(object : ActionListener {
- override fun onResponse(response: AcknowledgedResponse) {
- onCreateMappingsResponse(response)
+ client.threadPool().threadContext.stashContext().use {
+ if (!validateUserConfiguration(user, filterByEnabled, actionListener)) {
+ return
}
+ ismIndices.checkAndUpdateIMConfigIndex(object : ActionListener {
+ override fun onResponse(response: AcknowledgedResponse) {
+ onCreateMappingsResponse(response)
+ }
- override fun onFailure(t: Exception) {
- actionListener.onFailure(ExceptionsHelper.unwrapCause(t) as Exception)
- }
- })
+ override fun onFailure(t: Exception) {
+ actionListener.onFailure(ExceptionsHelper.unwrapCause(t) as Exception)
+ }
+ })
+ }
}
private fun onCreateMappingsResponse(response: AcknowledgedResponse) {
@@ -95,9 +122,9 @@ class TransportIndexPolicyAction @Inject constructor(
log.info("Successfully created or updated ${IndexManagementPlugin.INDEX_MANAGEMENT_INDEX} with newest mappings.")
// if there is template field, we will check
- val reqTemplate = request.policy.ismTemplate
- if (reqTemplate != null) {
- checkTemplate(reqTemplate.indexPatterns, reqTemplate.priority)
+ val reqTemplates = request.policy.ismTemplate
+ if (reqTemplates != null) {
+ validateISMTemplates(reqTemplates)
} else putPolicy()
} else {
log.error("Unable to create or update ${IndexManagementPlugin.INDEX_MANAGEMENT_INDEX} with newest mapping.")
@@ -111,18 +138,28 @@ class TransportIndexPolicyAction @Inject constructor(
}
}
- private fun checkTemplate(indexPatterns: List, priority: Int) {
- val possibleEx = validateFormat(indexPatterns)
+ private fun validateISMTemplates(ismTemplateList: List) {
+ val possibleEx = validateFormat(ismTemplateList.map { it.indexPatterns }.flatten())
if (possibleEx != null) {
actionListener.onFailure(possibleEx)
return
}
+ // check self overlapping
+ val selfOverlap = ismTemplateList.findSelfConflictingTemplates()
+ if (selfOverlap != null) {
+ val errorMessage = "New policy ${request.policyID} has an ISM template with index pattern ${selfOverlap.first} " +
+ "matching this policy's other ISM templates with index patterns ${selfOverlap.second}," +
+ " please use different priority"
+ actionListener.onFailure(IndexManagementException.wrap(IllegalArgumentException(errorMessage)))
+ return
+ }
+
val searchRequest = SearchRequest()
.source(
SearchSourceBuilder().query(
QueryBuilders.existsQuery(ISM_TEMPLATE_FIELD)
- )
+ ).size(MAX_HITS)
)
.indices(IndexManagementPlugin.INDEX_MANAGEMENT_INDEX)
@@ -130,14 +167,26 @@ class TransportIndexPolicyAction @Inject constructor(
searchRequest,
object : ActionListener {
override fun onResponse(response: SearchResponse) {
- val policyToTemplateMap = getPolicyToTemplateMap(response, xContentRegistry).filterNotNullValues()
- val conflictingPolicyTemplates = policyToTemplateMap.findConflictingPolicyTemplates(request.policyID, indexPatterns, priority)
- if (conflictingPolicyTemplates.isNotEmpty()) {
- val errorMessage = "New policy ${request.policyID} has an ISM template with index pattern $indexPatterns " +
- "matching existing policy templates," +
- " please use a different priority than $priority"
- actionListener.onFailure(IndexManagementException.wrap(IllegalArgumentException(errorMessage)))
- return
+ val policies = parseFromSearchResponse(response, xContentRegistry, Policy.Companion::parse)
+ val policyToTemplateMap: Map> =
+ policies.map { it.id to it.ismTemplate }.toMap().filterNotNullValues()
+ ismTemplateList.forEach {
+ val conflictingPolicyTemplates = policyToTemplateMap
+ .findConflictingPolicyTemplates(request.policyID, it.indexPatterns, it.priority)
+ if (conflictingPolicyTemplates.isNotEmpty()) {
+ val errorMessage =
+ "New policy ${request.policyID} has an ISM template with index pattern ${it.indexPatterns} " +
+ "matching existing policy templates," +
+ " please use a different priority than ${it.priority}"
+ actionListener.onFailure(
+ IndexManagementException.wrap(
+ IllegalArgumentException(
+ errorMessage
+ )
+ )
+ )
+ return
+ }
}
putPolicy()
@@ -151,11 +200,13 @@ class TransportIndexPolicyAction @Inject constructor(
}
private fun putPolicy() {
- request.policy.copy(schemaVersion = IndexUtils.indexManagementConfigSchemaVersion)
+ val policy = request.policy.copy(
+ schemaVersion = IndexUtils.indexManagementConfigSchemaVersion, user = this.user
+ )
val indexRequest = IndexRequest(IndexManagementPlugin.INDEX_MANAGEMENT_INDEX)
.setRefreshPolicy(request.refreshPolicy)
- .source(request.policy.toXContent(XContentFactory.jsonBuilder()))
+ .source(policy.toXContent(XContentFactory.jsonBuilder()))
.id(request.policyID)
.timeout(IndexRequest.DEFAULT_TIMEOUT)
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/managedIndex/ManagedIndexAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/managedIndex/ManagedIndexAction.kt
new file mode 100644
index 000000000..257704e6e
--- /dev/null
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/managedIndex/ManagedIndexAction.kt
@@ -0,0 +1,22 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * The OpenSearch Contributors require contributions made to
+ * this file be licensed under the Apache-2.0 license or a
+ * compatible open source license.
+ *
+ * Modifications Copyright OpenSearch Contributors. See
+ * GitHub history for details.
+ */
+
+package org.opensearch.indexmanagement.indexstatemanagement.transport.action.managedIndex
+
+import org.opensearch.action.ActionType
+import org.opensearch.action.support.master.AcknowledgedResponse
+
+class ManagedIndexAction : ActionType(NAME, ::AcknowledgedResponse) {
+ companion object {
+ const val NAME = "indices:admin/opensearch/ism/managedindex"
+ val INSTANCE = ManagedIndexAction()
+ }
+}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/managedIndex/ManagedIndexRequest.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/managedIndex/ManagedIndexRequest.kt
new file mode 100644
index 000000000..c3a094c29
--- /dev/null
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/managedIndex/ManagedIndexRequest.kt
@@ -0,0 +1,25 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * The OpenSearch Contributors require contributions made to
+ * this file be licensed under the Apache-2.0 license or a
+ * compatible open source license.
+ *
+ * Modifications Copyright OpenSearch Contributors. See
+ * GitHub history for details.
+ */
+
+package org.opensearch.indexmanagement.indexstatemanagement.transport.action.managedIndex
+
+import org.opensearch.action.support.broadcast.BroadcastRequest
+import org.opensearch.common.io.stream.StreamInput
+import java.io.IOException
+
+@Suppress("SpreadOperator")
+class ManagedIndexRequest : BroadcastRequest {
+
+ constructor(vararg indices: String) : super(*indices)
+
+ @Throws(IOException::class)
+ constructor(sin: StreamInput) : super(sin)
+}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/managedIndex/TransportManagedIndexAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/managedIndex/TransportManagedIndexAction.kt
new file mode 100644
index 000000000..bf89c2718
--- /dev/null
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/managedIndex/TransportManagedIndexAction.kt
@@ -0,0 +1,38 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * The OpenSearch Contributors require contributions made to
+ * this file be licensed under the Apache-2.0 license or a
+ * compatible open source license.
+ *
+ * Modifications Copyright OpenSearch Contributors. See
+ * GitHub history for details.
+ */
+
+package org.opensearch.indexmanagement.indexstatemanagement.transport.action.managedIndex
+
+import org.opensearch.action.ActionListener
+import org.opensearch.action.support.ActionFilters
+import org.opensearch.action.support.HandledTransportAction
+import org.opensearch.action.support.master.AcknowledgedResponse
+import org.opensearch.cluster.service.ClusterService
+import org.opensearch.common.inject.Inject
+import org.opensearch.tasks.Task
+import org.opensearch.transport.TransportService
+
+/**
+ * This is a non operational transport action that is used by ISM to check if the user has required index permissions to manage index
+ */
+class TransportManagedIndexAction @Inject constructor(
+ transportService: TransportService,
+ actionFilters: ActionFilters,
+ val clusterService: ClusterService,
+) : HandledTransportAction(
+ ManagedIndexAction.NAME, transportService, actionFilters, ::ManagedIndexRequest
+) {
+
+ override fun doExecute(task: Task, request: ManagedIndexRequest, listener: ActionListener) {
+ // Do nothing
+ return listener.onResponse(AcknowledgedResponse(true))
+ }
+}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/removepolicy/RemovePolicyAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/removepolicy/RemovePolicyAction.kt
index 281e1fe72..63e90447e 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/removepolicy/RemovePolicyAction.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/removepolicy/RemovePolicyAction.kt
@@ -32,6 +32,6 @@ import org.opensearch.indexmanagement.indexstatemanagement.transport.action.ISMS
class RemovePolicyAction private constructor() : ActionType(NAME, ::ISMStatusResponse) {
companion object {
val INSTANCE = RemovePolicyAction()
- val NAME = "cluster:admin/opendistro/ism/managedindex/remove"
+ const val NAME = "cluster:admin/opendistro/ism/managedindex/remove"
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/removepolicy/TransportRemovePolicyAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/removepolicy/TransportRemovePolicyAction.kt
index f570188ff..684a710a6 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/removepolicy/TransportRemovePolicyAction.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/removepolicy/TransportRemovePolicyAction.kt
@@ -26,11 +26,13 @@
package org.opensearch.indexmanagement.indexstatemanagement.transport.action.removepolicy
-import org.apache.logging.log4j.LogManager
import org.opensearch.ExceptionsHelper
+import org.opensearch.OpenSearchSecurityException
+import org.opensearch.OpenSearchStatusException
import org.opensearch.action.ActionListener
import org.opensearch.action.admin.cluster.state.ClusterStateRequest
import org.opensearch.action.admin.cluster.state.ClusterStateResponse
+import org.opensearch.action.admin.indices.settings.put.UpdateSettingsRequest
import org.opensearch.action.bulk.BulkRequest
import org.opensearch.action.bulk.BulkResponse
import org.opensearch.action.get.MultiGetRequest
@@ -38,31 +40,45 @@ import org.opensearch.action.get.MultiGetResponse
import org.opensearch.action.support.ActionFilters
import org.opensearch.action.support.HandledTransportAction
import org.opensearch.action.support.IndicesOptions
+import org.opensearch.action.support.master.AcknowledgedResponse
import org.opensearch.client.node.NodeClient
import org.opensearch.cluster.ClusterState
import org.opensearch.cluster.block.ClusterBlockException
+import org.opensearch.cluster.metadata.IndexMetadata.INDEX_BLOCKS_READ_ONLY_ALLOW_DELETE_SETTING
+import org.opensearch.cluster.metadata.IndexMetadata.INDEX_READ_ONLY_SETTING
+import org.opensearch.cluster.metadata.IndexMetadata.SETTING_READ_ONLY
+import org.opensearch.cluster.metadata.IndexMetadata.SETTING_READ_ONLY_ALLOW_DELETE
+import org.opensearch.cluster.service.ClusterService
import org.opensearch.common.inject.Inject
+import org.opensearch.common.settings.Settings
+import org.opensearch.commons.authuser.User
import org.opensearch.index.Index
import org.opensearch.index.IndexNotFoundException
import org.opensearch.indexmanagement.IndexManagementPlugin.Companion.INDEX_MANAGEMENT_INDEX
import org.opensearch.indexmanagement.indexstatemanagement.opensearchapi.getUuidsForClosedIndices
+import org.opensearch.indexmanagement.indexstatemanagement.settings.ManagedIndexSettings
import org.opensearch.indexmanagement.indexstatemanagement.transport.action.ISMStatusResponse
+import org.opensearch.indexmanagement.indexstatemanagement.transport.action.managedIndex.ManagedIndexAction
+import org.opensearch.indexmanagement.indexstatemanagement.transport.action.managedIndex.ManagedIndexRequest
import org.opensearch.indexmanagement.indexstatemanagement.util.FailedIndex
import org.opensearch.indexmanagement.indexstatemanagement.util.deleteManagedIndexMetadataRequest
import org.opensearch.indexmanagement.indexstatemanagement.util.deleteManagedIndexRequest
import org.opensearch.indexmanagement.util.IndexManagementException
+import org.opensearch.indexmanagement.util.SecurityUtils.Companion.buildUser
+import org.opensearch.rest.RestStatus
import org.opensearch.tasks.Task
import org.opensearch.transport.TransportService
-private val log = LogManager.getLogger(TransportRemovePolicyAction::class.java)
-
+@Suppress("SpreadOperator")
class TransportRemovePolicyAction @Inject constructor(
val client: NodeClient,
transportService: TransportService,
- actionFilters: ActionFilters
+ actionFilters: ActionFilters,
+ val clusterService: ClusterService
) : HandledTransportAction(
RemovePolicyAction.NAME, transportService, actionFilters, ::RemovePolicyRequest
) {
+
override fun doExecute(task: Task, request: RemovePolicyRequest, listener: ActionListener) {
RemovePolicyHandler(client, listener, request).start()
}
@@ -70,14 +86,52 @@ class TransportRemovePolicyAction @Inject constructor(
inner class RemovePolicyHandler(
private val client: NodeClient,
private val actionListener: ActionListener,
- private val request: RemovePolicyRequest
+ private val request: RemovePolicyRequest,
+ private val user: User? = buildUser(client.threadPool().threadContext)
) {
private val failedIndices: MutableList = mutableListOf()
private val indicesToRemove = mutableMapOf() // uuid: name
+ private val indicesWithAutoManageBlock = mutableSetOf()
+ private val indicesWithReadOnlyBlock = mutableSetOf()
+ private val indicesWithReadOnlyAllowDeleteBlock = mutableSetOf()
- @Suppress("SpreadOperator")
fun start() {
+ if (user == null) {
+ getClusterState()
+ } else {
+ validateAndGetClusterState()
+ }
+ }
+
+ private fun validateAndGetClusterState() {
+ val request = ManagedIndexRequest().indices(*request.indices.toTypedArray())
+ client.execute(
+ ManagedIndexAction.INSTANCE,
+ request,
+ object : ActionListener {
+ override fun onResponse(response: AcknowledgedResponse) {
+ getClusterState()
+ }
+
+ override fun onFailure(e: java.lang.Exception) {
+ actionListener.onFailure(
+ IndexManagementException.wrap(
+ when (e is OpenSearchSecurityException) {
+ true -> OpenSearchStatusException(
+ "User doesn't have required index permissions on one or more requested indices: ${e.localizedMessage}",
+ RestStatus.FORBIDDEN
+ )
+ false -> e
+ }
+ )
+ )
+ }
+ }
+ )
+ }
+
+ private fun getClusterState() {
val strictExpandOptions = IndicesOptions.strictExpand()
val clusterStateRequest = ClusterStateRequest()
@@ -87,24 +141,35 @@ class TransportRemovePolicyAction @Inject constructor(
.local(false)
.indicesOptions(strictExpandOptions)
- client.admin()
- .cluster()
- .state(
- clusterStateRequest,
- object : ActionListener {
- override fun onResponse(response: ClusterStateResponse) {
- val indexMetadatas = response.state.metadata.indices
- indexMetadatas.forEach {
- indicesToRemove.putIfAbsent(it.value.indexUUID, it.key)
+ client.threadPool().threadContext.stashContext().use {
+ client.admin()
+ .cluster()
+ .state(
+ clusterStateRequest,
+ object : ActionListener {
+ override fun onResponse(response: ClusterStateResponse) {
+ val indexMetadatas = response.state.metadata.indices
+ indexMetadatas.forEach {
+ indicesToRemove.putIfAbsent(it.value.indexUUID, it.key)
+ if (it.value.settings.get(ManagedIndexSettings.AUTO_MANAGE.key) == "false") {
+ indicesWithAutoManageBlock.add(it.value.indexUUID)
+ }
+ if (it.value.settings.get(SETTING_READ_ONLY) == "true") {
+ indicesWithReadOnlyBlock.add(it.value.indexUUID)
+ }
+ if (it.value.settings.get(SETTING_READ_ONLY_ALLOW_DELETE) == "true") {
+ indicesWithReadOnlyAllowDeleteBlock.add(it.value.indexUUID)
+ }
+ }
+ populateLists(response.state)
}
- populateLists(response.state)
- }
- override fun onFailure(t: Exception) {
- actionListener.onFailure(ExceptionsHelper.unwrapCause(t) as Exception)
+ override fun onFailure(t: Exception) {
+ actionListener.onFailure(ExceptionsHelper.unwrapCause(t) as Exception)
+ }
}
- }
- )
+ )
+ }
}
private fun populateLists(state: ClusterState) {
@@ -128,7 +193,13 @@ class TransportRemovePolicyAction @Inject constructor(
val f = response.responses.first()
if (f.isFailed && f.failure.failure is IndexNotFoundException) {
indicesToRemove.forEach { (uuid, name) ->
- failedIndices.add(FailedIndex(name, uuid, "This index does not have a policy to remove"))
+ failedIndices.add(
+ FailedIndex(
+ name,
+ uuid,
+ "This index does not have a policy to remove"
+ )
+ )
}
actionListener.onResponse(ISMStatusResponse(0, failedIndices))
return
@@ -147,7 +218,7 @@ class TransportRemovePolicyAction @Inject constructor(
}
}
- removeManagedIndices()
+ updateSettings(indicesToRemove)
}
override fun onFailure(t: Exception) {
@@ -157,6 +228,85 @@ class TransportRemovePolicyAction @Inject constructor(
)
}
+ /**
+ * try to update auto_manage setting to false before delete managed-index
+ * so that index will not be picked up by Coordinator background sweep process
+ * this wont happen for cold indices
+ * if update setting failed, remove managed-index and metadata will not happen
+ */
+ @Suppress("SpreadOperator")
+ fun updateSettings(indices: Map) {
+ // indices divide to read_only, read_only_allow_delete, normal
+ val indicesUuidsSet = indices.keys.toSet() - indicesWithAutoManageBlock
+ val readOnlyIndices = indicesUuidsSet.filter { it in indicesWithReadOnlyBlock }
+ val readOnlyAllowDeleteIndices = (indicesUuidsSet - readOnlyIndices).filter { it in indicesWithReadOnlyAllowDeleteBlock }
+ val normalIndices = indicesUuidsSet - readOnlyIndices - readOnlyAllowDeleteIndices
+
+ val updateSettingReqsList = mutableListOf()
+ if (readOnlyIndices.isNotEmpty()) {
+ updateSettingReqsList.add(
+ UpdateSettingsRequest().indices(*readOnlyIndices.map { indices[it] }.toTypedArray())
+ .settings(
+ Settings.builder().put(ManagedIndexSettings.AUTO_MANAGE.key, false)
+ .put(INDEX_READ_ONLY_SETTING.key, true)
+ )
+ )
+ }
+ if (readOnlyAllowDeleteIndices.isNotEmpty()) {
+ updateSettingReqsList.add(
+ UpdateSettingsRequest().indices(*readOnlyAllowDeleteIndices.map { indices[it] }.toTypedArray())
+ .settings(
+ Settings.builder().put(ManagedIndexSettings.AUTO_MANAGE.key, false)
+ .put(INDEX_BLOCKS_READ_ONLY_ALLOW_DELETE_SETTING.key, true)
+ )
+ )
+ }
+ if (normalIndices.isNotEmpty()) {
+ updateSettingReqsList.add(
+ UpdateSettingsRequest().indices(*normalIndices.map { indices[it] }.toTypedArray())
+ .settings(Settings.builder().put(ManagedIndexSettings.AUTO_MANAGE.key, false))
+ )
+ }
+
+ updateSettingCallChain(0, updateSettingReqsList)
+ }
+
+ fun updateSettingCallChain(current: Int, updateSettingReqsList: List) {
+ if (updateSettingReqsList.isEmpty()) {
+ removeManagedIndices()
+ return
+ }
+ client.admin().indices().updateSettings(
+ updateSettingReqsList[current],
+ object : ActionListener {
+ override fun onResponse(response: AcknowledgedResponse) {
+ if (!response.isAcknowledged) {
+ actionListener.onFailure(
+ IndexManagementException.wrap(
+ Exception("Failed to remove policy because ISM auto_manage setting update requests are not fully acknowledged.")
+ )
+ )
+ return
+ }
+ if (current < updateSettingReqsList.size - 1) {
+ updateSettingCallChain(current + 1, updateSettingReqsList)
+ } else {
+ removeManagedIndices()
+ }
+ }
+
+ override fun onFailure(t: Exception) {
+ val ex = ExceptionsHelper.unwrapCause(t) as Exception
+ actionListener.onFailure(
+ IndexManagementException.wrap(
+ Exception("Failed to remove policy because ISM auto_manage setting update requests failed with exception:", ex)
+ )
+ )
+ }
+ }
+ )
+ }
+
@Suppress("SpreadOperator") // There is no way around dealing with java vararg without spread operator.
fun removeManagedIndices() {
if (indicesToRemove.isNotEmpty()) {
@@ -169,7 +319,13 @@ class TransportRemovePolicyAction @Inject constructor(
response.forEach {
val docId = it.id // docId is indexUuid of the managed index
if (it.isFailed) {
- failedIndices.add(FailedIndex(indicesToRemove[docId] as String, docId, "Failed to remove policy"))
+ failedIndices.add(
+ FailedIndex(
+ indicesToRemove[docId] as String,
+ docId,
+ "Failed to remove policy"
+ )
+ )
indicesToRemove.remove(docId)
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/retryfailedmanagedindex/RetryFailedManagedIndexAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/retryfailedmanagedindex/RetryFailedManagedIndexAction.kt
index 7bdd15f6d..f201cc9d0 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/retryfailedmanagedindex/RetryFailedManagedIndexAction.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/retryfailedmanagedindex/RetryFailedManagedIndexAction.kt
@@ -32,6 +32,6 @@ import org.opensearch.indexmanagement.indexstatemanagement.transport.action.ISMS
class RetryFailedManagedIndexAction private constructor() : ActionType(NAME, ::ISMStatusResponse) {
companion object {
val INSTANCE = RetryFailedManagedIndexAction()
- val NAME = "cluster:admin/opendistro/ism/managedindex/retry"
+ const val NAME = "cluster:admin/opendistro/ism/managedindex/retry"
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/retryfailedmanagedindex/TransportRetryFailedManagedIndexAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/retryfailedmanagedindex/TransportRetryFailedManagedIndexAction.kt
index 27e3780b9..af872da56 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/retryfailedmanagedindex/TransportRetryFailedManagedIndexAction.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/retryfailedmanagedindex/TransportRetryFailedManagedIndexAction.kt
@@ -28,6 +28,8 @@ package org.opensearch.indexmanagement.indexstatemanagement.transport.action.ret
import org.apache.logging.log4j.LogManager
import org.opensearch.ExceptionsHelper
+import org.opensearch.OpenSearchSecurityException
+import org.opensearch.OpenSearchStatusException
import org.opensearch.action.ActionListener
import org.opensearch.action.admin.cluster.state.ClusterStateRequest
import org.opensearch.action.admin.cluster.state.ClusterStateResponse
@@ -38,6 +40,7 @@ import org.opensearch.action.get.MultiGetResponse
import org.opensearch.action.support.ActionFilters
import org.opensearch.action.support.HandledTransportAction
import org.opensearch.action.support.IndicesOptions
+import org.opensearch.action.support.master.AcknowledgedResponse
import org.opensearch.action.update.UpdateRequest
import org.opensearch.client.node.NodeClient
import org.opensearch.cluster.ClusterState
@@ -45,6 +48,7 @@ import org.opensearch.cluster.block.ClusterBlockException
import org.opensearch.common.inject.Inject
import org.opensearch.common.xcontent.ToXContent
import org.opensearch.common.xcontent.XContentFactory
+import org.opensearch.commons.authuser.User
import org.opensearch.index.Index
import org.opensearch.index.IndexNotFoundException
import org.opensearch.indexmanagement.IndexManagementPlugin.Companion.INDEX_MANAGEMENT_INDEX
@@ -54,15 +58,21 @@ import org.opensearch.indexmanagement.indexstatemanagement.opensearchapi.buildMg
import org.opensearch.indexmanagement.indexstatemanagement.opensearchapi.getManagedIndexMetadata
import org.opensearch.indexmanagement.indexstatemanagement.opensearchapi.mgetResponseToList
import org.opensearch.indexmanagement.indexstatemanagement.transport.action.ISMStatusResponse
+import org.opensearch.indexmanagement.indexstatemanagement.transport.action.managedIndex.ManagedIndexAction
+import org.opensearch.indexmanagement.indexstatemanagement.transport.action.managedIndex.ManagedIndexRequest
import org.opensearch.indexmanagement.indexstatemanagement.util.FailedIndex
import org.opensearch.indexmanagement.indexstatemanagement.util.isFailed
import org.opensearch.indexmanagement.indexstatemanagement.util.managedIndexMetadataID
import org.opensearch.indexmanagement.indexstatemanagement.util.updateEnableManagedIndexRequest
+import org.opensearch.indexmanagement.util.IndexManagementException
+import org.opensearch.indexmanagement.util.SecurityUtils.Companion.buildUser
+import org.opensearch.rest.RestStatus
import org.opensearch.tasks.Task
import org.opensearch.transport.TransportService
private val log = LogManager.getLogger(TransportRetryFailedManagedIndexAction::class.java)
+@Suppress("SpreadOperator")
class TransportRetryFailedManagedIndexAction @Inject constructor(
val client: NodeClient,
transportService: TransportService,
@@ -77,7 +87,8 @@ class TransportRetryFailedManagedIndexAction @Inject constructor(
inner class RetryFailedManagedIndexHandler(
private val client: NodeClient,
private val actionListener: ActionListener,
- private val request: RetryFailedManagedIndexRequest
+ private val request: RetryFailedManagedIndexRequest,
+ private val user: User? = buildUser(client.threadPool().threadContext)
) {
private val failedIndices: MutableList = mutableListOf()
private val listOfMetadata: MutableList = mutableListOf()
@@ -89,6 +100,42 @@ class TransportRetryFailedManagedIndexAction @Inject constructor(
@Suppress("SpreadOperator")
fun start() {
+ if (user == null) {
+ // Security plugin is not enabled
+ getClusterState()
+ } else {
+ validateAndGetClusterState()
+ }
+ }
+
+ fun validateAndGetClusterState() {
+ val request = ManagedIndexRequest().indices(*request.indices.toTypedArray())
+ client.execute(
+ ManagedIndexAction.INSTANCE,
+ request,
+ object : ActionListener {
+ override fun onResponse(response: AcknowledgedResponse) {
+ getClusterState()
+ }
+
+ override fun onFailure(e: java.lang.Exception) {
+ actionListener.onFailure(
+ IndexManagementException.wrap(
+ when (e is OpenSearchSecurityException) {
+ true -> OpenSearchStatusException(
+ "User doesn't have required index permissions on one or more requested indices: ${e.localizedMessage}",
+ RestStatus.FORBIDDEN
+ )
+ false -> e
+ }
+ )
+ )
+ }
+ }
+ )
+ }
+
+ fun getClusterState() {
val strictExpandIndicesOptions = IndicesOptions.strictExpand()
val clusterStateRequest = ClusterStateRequest()
@@ -99,25 +146,27 @@ class TransportRetryFailedManagedIndexAction @Inject constructor(
.masterNodeTimeout(request.masterTimeout)
.indicesOptions(strictExpandIndicesOptions)
- client.admin()
- .cluster()
- .state(
- clusterStateRequest,
- object : ActionListener {
- override fun onResponse(response: ClusterStateResponse) {
- clusterState = response.state
- val indexMetadatas = response.state.metadata.indices
- indexMetadatas.forEach {
- indicesToRetry.putIfAbsent(it.value.indexUUID, it.key)
+ client.threadPool().threadContext.stashContext().use {
+ client.admin()
+ .cluster()
+ .state(
+ clusterStateRequest,
+ object : ActionListener {
+ override fun onResponse(response: ClusterStateResponse) {
+ clusterState = response.state
+ val indexMetadatas = response.state.metadata.indices
+ indexMetadatas.forEach {
+ indicesToRetry.putIfAbsent(it.value.indexUUID, it.key)
+ }
+ processResponse(response)
}
- processResponse(response)
- }
- override fun onFailure(t: Exception) {
- actionListener.onFailure(ExceptionsHelper.unwrapCause(t) as Exception)
+ override fun onFailure(t: Exception) {
+ actionListener.onFailure(ExceptionsHelper.unwrapCause(t) as Exception)
+ }
}
- }
- )
+ )
+ }
}
fun processResponse(clusterStateResponse: ClusterStateResponse) {
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/updateindexmetadata/TransportUpdateManagedIndexMetaDataAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/updateindexmetadata/TransportUpdateManagedIndexMetaDataAction.kt
index 63588cada..98ae2bf73 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/updateindexmetadata/TransportUpdateManagedIndexMetaDataAction.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/transport/action/updateindexmetadata/TransportUpdateManagedIndexMetaDataAction.kt
@@ -31,7 +31,6 @@ import org.opensearch.action.ActionListener
import org.opensearch.action.support.ActionFilters
import org.opensearch.action.support.master.AcknowledgedResponse
import org.opensearch.action.support.master.TransportMasterNodeAction
-import org.opensearch.client.Client
import org.opensearch.cluster.ClusterState
import org.opensearch.cluster.ClusterStateTaskConfig
import org.opensearch.cluster.ClusterStateTaskExecutor
@@ -53,30 +52,23 @@ import org.opensearch.indexmanagement.indexstatemanagement.model.ManagedIndexMet
import org.opensearch.threadpool.ThreadPool
import org.opensearch.transport.TransportService
-class TransportUpdateManagedIndexMetaDataAction : TransportMasterNodeAction {
-
- @Inject
- constructor(
- client: Client,
- threadPool: ThreadPool,
- clusterService: ClusterService,
- transportService: TransportService,
- actionFilters: ActionFilters,
- indexNameExpressionResolver: IndexNameExpressionResolver
- ) : super(
- UpdateManagedIndexMetaDataAction.INSTANCE.name(),
- transportService,
- clusterService,
- threadPool,
- actionFilters,
- Writeable.Reader { UpdateManagedIndexMetaDataRequest(it) },
- indexNameExpressionResolver
- ) {
- this.client = client
- }
+class TransportUpdateManagedIndexMetaDataAction @Inject constructor(
+ threadPool: ThreadPool,
+ clusterService: ClusterService,
+ transportService: TransportService,
+ actionFilters: ActionFilters,
+ indexNameExpressionResolver: IndexNameExpressionResolver
+) : TransportMasterNodeAction(
+ UpdateManagedIndexMetaDataAction.INSTANCE.name(),
+ transportService,
+ clusterService,
+ threadPool,
+ actionFilters,
+ Writeable.Reader { UpdateManagedIndexMetaDataRequest(it) },
+ indexNameExpressionResolver
+) {
private val log = LogManager.getLogger(javaClass)
- private val client: Client
private val executor = ManagedIndexMetaDataExecutor()
override fun checkBlock(request: UpdateManagedIndexMetaDataRequest, state: ClusterState): ClusterBlockException? {
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/util/ManagedIndexUtils.kt b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/util/ManagedIndexUtils.kt
index 8f8578ee2..46e0d55a1 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/util/ManagedIndexUtils.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/util/ManagedIndexUtils.kt
@@ -76,7 +76,15 @@ import java.net.InetAddress
import java.time.Instant
import java.time.temporal.ChronoUnit
-fun managedIndexConfigIndexRequest(index: String, uuid: String, policyID: String, jobInterval: Int): IndexRequest {
+@Suppress("LongParameterList")
+fun managedIndexConfigIndexRequest(
+ index: String,
+ uuid: String,
+ policyID: String,
+ jobInterval: Int,
+ policy: Policy? = null,
+ jobJitter: Double?
+): IndexRequest {
val managedIndexConfig = ManagedIndexConfig(
jobName = index,
index = index,
@@ -86,10 +94,11 @@ fun managedIndexConfigIndexRequest(index: String, uuid: String, policyID: String
jobLastUpdatedTime = Instant.now(),
jobEnabledTime = Instant.now(),
policyID = policyID,
- policy = null,
- policySeqNo = null,
- policyPrimaryTerm = null,
- changePolicy = null
+ policy = policy,
+ policySeqNo = policy?.seqNo,
+ policyPrimaryTerm = policy?.primaryTerm,
+ changePolicy = null,
+ jobJitter = jobJitter
)
return IndexRequest(INDEX_MANAGEMENT_INDEX)
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 49581d430..4fd95b2b6 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/util/RestHandlerUtils.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/indexstatemanagement/util/RestHandlerUtils.kt
@@ -40,7 +40,10 @@ import org.opensearch.indexmanagement.opensearchapi.optionalTimeField
import java.time.Instant
const val WITH_TYPE = "with_type"
+const val WITH_USER = "with_user"
val XCONTENT_WITHOUT_TYPE = ToXContent.MapParams(mapOf(WITH_TYPE to "false"))
+val XCONTENT_WITHOUT_USER = ToXContent.MapParams(mapOf(WITH_USER to "false"))
+val XCONTENT_WITHOUT_TYPE_AND_USER = ToXContent.MapParams(mapOf(WITH_TYPE to "false", WITH_USER to "false"))
const val FAILURES = "failures"
const val FAILED_INDICES = "failed_indices"
@@ -108,11 +111,10 @@ data class FailedIndex(val name: String, val uuid: String, val reason: String) :
fun getPartialChangePolicyBuilder(
changePolicy: ChangePolicy?
): XContentBuilder {
- return XContentFactory.jsonBuilder()
+ val builder = XContentFactory.jsonBuilder()
.startObject()
.startObject(ManagedIndexConfig.MANAGED_INDEX_TYPE)
.optionalTimeField(ManagedIndexConfig.LAST_UPDATED_TIME_FIELD, Instant.now())
.field(ManagedIndexConfig.CHANGE_POLICY_FIELD, changePolicy)
- .endObject()
- .endObject()
+ return builder.endObject().endObject()
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/opensearchapi/OpenSearchExtensions.kt b/src/main/kotlin/org/opensearch/indexmanagement/opensearchapi/OpenSearchExtensions.kt
index 1a8859862..fb12e83f8 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/opensearchapi/OpenSearchExtensions.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/opensearchapi/OpenSearchExtensions.kt
@@ -28,35 +28,49 @@
package org.opensearch.indexmanagement.opensearchapi
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ThreadContextElement
import kotlinx.coroutines.delay
+import kotlinx.coroutines.withContext
+import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.Logger
import org.opensearch.ExceptionsHelper
import org.opensearch.OpenSearchException
import org.opensearch.action.ActionListener
import org.opensearch.action.bulk.BackoffPolicy
+import org.opensearch.action.get.GetResponse
+import org.opensearch.action.search.SearchResponse
import org.opensearch.action.support.DefaultShardOperationFailedException
import org.opensearch.client.OpenSearchClient
import org.opensearch.common.bytes.BytesReference
+import org.opensearch.common.settings.Settings
import org.opensearch.common.unit.TimeValue
+import org.opensearch.common.util.concurrent.ThreadContext
import org.opensearch.common.xcontent.LoggingDeprecationHandler
import org.opensearch.common.xcontent.NamedXContentRegistry
import org.opensearch.common.xcontent.ToXContent
import org.opensearch.common.xcontent.XContentBuilder
+import org.opensearch.common.xcontent.XContentFactory
import org.opensearch.common.xcontent.XContentHelper
import org.opensearch.common.xcontent.XContentParser
import org.opensearch.common.xcontent.XContentParser.Token
import org.opensearch.common.xcontent.XContentParserUtils
import org.opensearch.common.xcontent.XContentParserUtils.ensureExpectedToken
import org.opensearch.common.xcontent.XContentType
+import org.opensearch.commons.InjectSecurity
+import org.opensearch.commons.authuser.User
import org.opensearch.index.seqno.SequenceNumbers
import org.opensearch.indexmanagement.indexstatemanagement.model.ISMTemplate
import org.opensearch.indexmanagement.indexstatemanagement.model.Policy
import org.opensearch.indexmanagement.util.NO_ID
+import org.opensearch.indexmanagement.util.SecurityUtils.Companion.DEFAULT_INJECT_ROLES
+import org.opensearch.indexmanagement.util.SecurityUtils.Companion.INTERNAL_REQUEST
import org.opensearch.jobscheduler.spi.utils.LockService
import org.opensearch.rest.RestStatus
import org.opensearch.transport.RemoteTransportException
import java.io.IOException
import java.time.Instant
+import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
@@ -92,11 +106,50 @@ fun XContentBuilder.optionalTimeField(name: String, instant: Instant?): XContent
return this.timeField(name, "${name}_in_millis", instant.toEpochMilli())
}
-fun XContentBuilder.optionalISMTemplateField(name: String, ismTemplate: ISMTemplate?): XContentBuilder {
- if (ismTemplate == null) {
+fun XContentBuilder.optionalISMTemplateField(name: String, ismTemplates: List?): XContentBuilder {
+ if (ismTemplates == null) {
return nullField(name)
}
- return this.field(Policy.ISM_TEMPLATE, ismTemplate)
+ return this.field(Policy.ISM_TEMPLATE, ismTemplates.toTypedArray())
+}
+
+fun XContentBuilder.optionalUserField(name: String, user: User?): XContentBuilder {
+ return if (user == null) nullField(name) else this.field(name, user)
+}
+
+/**
+ * Parse data from SearchResponse using the defined parser and xContentRegistry
+ */
+fun parseFromSearchResponse(
+ response: SearchResponse,
+ xContentRegistry: NamedXContentRegistry = NamedXContentRegistry.EMPTY,
+ parse: (xcp: XContentParser, id: String, seqNo: Long, primaryTerm: Long) -> T
+): List {
+ return response.hits.hits.map {
+ val id = it.id
+ val seqNo = it.seqNo
+ val primaryTerm = it.primaryTerm
+ val xcp = XContentFactory.xContent(XContentType.JSON)
+ .createParser(xContentRegistry, LoggingDeprecationHandler.INSTANCE, it.sourceAsString)
+ xcp.parseWithType(id, seqNo, primaryTerm, parse)
+ }
+}
+
+/**
+ * Parse data from GetResponse using the defined parser and xContentRegistry
+ */
+fun parseFromGetResponse(
+ response: GetResponse,
+ xContentRegistry: NamedXContentRegistry = NamedXContentRegistry.EMPTY,
+ parse: (xcp: XContentParser, id: String, seqNo: Long, primaryTerm: Long) -> T
+): T {
+ val xcp = XContentHelper.createParser(
+ xContentRegistry,
+ LoggingDeprecationHandler.INSTANCE,
+ response.sourceAsBytesRef,
+ XContentType.JSON
+ )
+ return xcp.parseWithType(response.id, response.seqNo, response.primaryTerm, parse)
}
/**
@@ -199,3 +252,47 @@ fun XContentParser.parseWithType(
ensureExpectedToken(Token.END_OBJECT, this.nextToken(), this)
return parsed
}
+
+class IndexManagementSecurityContext(
+ private val id: String,
+ settings: Settings,
+ private val threadContext: ThreadContext,
+ private val user: User?
+) : ThreadContextElement {
+
+ companion object Key : CoroutineContext.Key
+
+ private val logger: Logger = LogManager.getLogger(javaClass)
+ override val key: CoroutineContext.Key<*>
+ get() = Key
+ val injector = InjectSecurity(id, settings, threadContext)
+
+ /**
+ * Before the thread executes the coroutine we want the thread context to contain user roles so they are used when executing the code inside
+ * the coroutine
+ */
+ override fun updateThreadContext(context: CoroutineContext) {
+ logger.debug("Setting security context in thread ${Thread.currentThread().name} for job $id")
+ injector.injectRoles(if (user == null) DEFAULT_INJECT_ROLES else user.roles)
+ injector.injectProperty(INTERNAL_REQUEST, true)
+ }
+
+ /**
+ * Clean up the thread context before the coroutine executed by thread is suspended
+ */
+ override fun restoreThreadContext(context: CoroutineContext, oldState: Unit) {
+ logger.debug("Cleaning up security context in thread ${Thread.currentThread().name} for job $id")
+ injector.close()
+ }
+}
+
+suspend fun withClosableContext(
+ context: IndexManagementSecurityContext,
+ block: suspend CoroutineScope.() -> T
+): T {
+ try {
+ return withContext(context) { block() }
+ } finally {
+ context.injector.close()
+ }
+}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/rollup/RollupIndexer.kt b/src/main/kotlin/org/opensearch/indexmanagement/rollup/RollupIndexer.kt
index 80c21ebb7..a10457840 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/rollup/RollupIndexer.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/rollup/RollupIndexer.kt
@@ -30,6 +30,7 @@ import org.apache.logging.log4j.LogManager
import org.opensearch.ExceptionsHelper
import org.opensearch.action.DocWriteRequest
import org.opensearch.action.bulk.BackoffPolicy
+import org.opensearch.action.bulk.BulkItemResponse
import org.opensearch.action.bulk.BulkRequest
import org.opensearch.action.bulk.BulkResponse
import org.opensearch.action.index.IndexRequest
@@ -55,6 +56,7 @@ import org.opensearch.search.aggregations.metrics.InternalSum
import org.opensearch.search.aggregations.metrics.InternalValueCount
import org.opensearch.transport.RemoteTransportException
+@Suppress("ThrowsCount", "ComplexMethod")
class RollupIndexer(
settings: Settings,
clusterService: ClusterService,
@@ -77,6 +79,7 @@ class RollupIndexer(
try {
var requestsToRetry = convertResponseToRequests(rollup, internalComposite)
var stats = RollupStats(0, 0, requestsToRetry.size.toLong(), 0, 0)
+ var nonRetryableFailures = mutableListOf()
if (requestsToRetry.isNotEmpty()) {
retryIngestPolicy.retry(logger, listOf(RestStatus.TOO_MANY_REQUESTS)) {
if (it.seconds >= (Rollup.ROLLUP_LOCK_DURATION_SECONDS / 2)) {
@@ -87,16 +90,26 @@ class RollupIndexer(
val bulkRequest = BulkRequest().add(requestsToRetry)
val bulkResponse: BulkResponse = client.suspendUntil { bulk(bulkRequest, it) }
stats = stats.copy(indexTimeInMillis = stats.indexTimeInMillis + bulkResponse.took.millis)
- val failedResponses = (bulkResponse.items ?: arrayOf()).filter { it.isFailed }
- requestsToRetry = failedResponses.filter { it.status() == RestStatus.TOO_MANY_REQUESTS }
- .map { bulkRequest.requests()[it.itemId] as IndexRequest }
+ val retryableFailures = mutableListOf()
+ (bulkResponse.items ?: arrayOf()).filter { it.isFailed }.forEach { failedResponse ->
+ if (failedResponse.status() == RestStatus.TOO_MANY_REQUESTS) {
+ retryableFailures.add(failedResponse)
+ } else {
+ nonRetryableFailures.add(failedResponse)
+ }
+ }
+ requestsToRetry = retryableFailures.map { retryableFailure -> bulkRequest.requests()[retryableFailure.itemId] as IndexRequest }
if (requestsToRetry.isNotEmpty()) {
- val retryCause = failedResponses.first { it.status() == RestStatus.TOO_MANY_REQUESTS }.failure.cause
+ val retryCause = retryableFailures.first().failure.cause
throw ExceptionsHelper.convertToOpenSearchException(retryCause)
}
}
}
+ if (nonRetryableFailures.isNotEmpty()) {
+ logger.error("Failed to index ${nonRetryableFailures.size} documents")
+ throw ExceptionsHelper.convertToOpenSearchException(nonRetryableFailures.first().failure.cause)
+ }
return RollupIndexResult.Success(stats)
} catch (e: RemoteTransportException) {
logger.error(e.message, e.cause)
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/rollup/RollupMapperService.kt b/src/main/kotlin/org/opensearch/indexmanagement/rollup/RollupMapperService.kt
index bc3ac8d96..c0c0bb7b0 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/rollup/RollupMapperService.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/rollup/RollupMapperService.kt
@@ -30,6 +30,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.apache.logging.log4j.LogManager
import org.opensearch.ExceptionsHelper
+import org.opensearch.OpenSearchSecurityException
import org.opensearch.action.admin.indices.create.CreateIndexRequest
import org.opensearch.action.admin.indices.create.CreateIndexResponse
import org.opensearch.action.admin.indices.mapping.get.GetMappingsRequest
@@ -106,6 +107,9 @@ class RollupMapperService(
val unwrappedException = ExceptionsHelper.unwrapCause(e) as Exception
logger.error(errorMessage, unwrappedException)
RollupJobValidationResult.Failure(errorMessage, unwrappedException)
+ } catch (e: OpenSearchSecurityException) {
+ logger.error("$errorMessage because ", e)
+ RollupJobValidationResult.Failure("$errorMessage - missing required cluster permissions: ${e.localizedMessage}", e)
} catch (e: Exception) {
logger.error("$errorMessage because ", e)
RollupJobValidationResult.Failure(errorMessage, e)
@@ -256,13 +260,16 @@ class RollupMapperService(
val unwrappedException = ExceptionsHelper.unwrapCause(e) as Exception
logger.error(errorMessage, unwrappedException)
return GetMappingsResult.Failure(errorMessage, unwrappedException)
+ } catch (e: OpenSearchSecurityException) {
+ logger.error(errorMessage, e)
+ return GetMappingsResult.Failure("$errorMessage - missing required index permissions: ${e.localizedMessage}", e)
} catch (e: Exception) {
logger.error(errorMessage, e)
return GetMappingsResult.Failure(errorMessage, e)
}
}
- fun indexExists(index: String): Boolean = clusterService.state().routingTable.hasIndex(index)
+ private fun indexExists(index: String): Boolean = clusterService.state().routingTable.hasIndex(index)
// TODO: error handling - can RemoteTransportException happen here?
// TODO: The use of the master transport action UpdateRollupMappingAction will prevent
@@ -288,6 +295,9 @@ class RollupMapperService(
return RollupJobValidationResult.Failure(errorMessage)
}
return RollupJobValidationResult.Valid
+ } catch (e: OpenSearchSecurityException) {
+ logger.error("$errorMessage because ", e)
+ return RollupJobValidationResult.Failure("$errorMessage - missing required index permissions: ${e.localizedMessage}", e)
} catch (e: Exception) {
logger.error("$errorMessage because ", e)
return RollupJobValidationResult.Failure(errorMessage, e)
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/rollup/RollupRunner.kt b/src/main/kotlin/org/opensearch/indexmanagement/rollup/RollupRunner.kt
index b9c276e1d..2399614bf 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/rollup/RollupRunner.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/rollup/RollupRunner.kt
@@ -41,7 +41,9 @@ import org.opensearch.common.settings.Settings
import org.opensearch.common.unit.TimeValue
import org.opensearch.common.xcontent.NamedXContentRegistry
import org.opensearch.indexmanagement.indexstatemanagement.SkipExecution
+import org.opensearch.indexmanagement.opensearchapi.IndexManagementSecurityContext
import org.opensearch.indexmanagement.opensearchapi.suspendUntil
+import org.opensearch.indexmanagement.opensearchapi.withClosableContext
import org.opensearch.indexmanagement.rollup.action.get.GetRollupAction
import org.opensearch.indexmanagement.rollup.action.get.GetRollupRequest
import org.opensearch.indexmanagement.rollup.action.get.GetRollupResponse
@@ -257,7 +259,12 @@ object RollupRunner :
}
}
- when (val result = rollupMapperService.attemptCreateRollupTargetIndex(updatableJob, clusterConfigurationProvider.hasLegacyPlugin)) {
+ val result = withClosableContext(
+ IndexManagementSecurityContext(job.id, settings, threadPool.threadContext, job.user)
+ ) {
+ rollupMapperService.attemptCreateRollupTargetIndex(updatableJob, clusterConfigurationProvider.hasLegacyPlugin)
+ }
+ when (result) {
is RollupJobValidationResult.Failure -> {
setFailedMetadataAndDisableJob(updatableJob, result.message, metadata)
return
@@ -272,11 +279,21 @@ object RollupRunner :
while (rollupSearchService.shouldProcessRollup(updatableJob, metadata)) {
do {
try {
- val rollupResult = when (val rollupSearchResult = rollupSearchService.executeCompositeSearch(updatableJob, metadata)) {
+ val rollupSearchResult = withClosableContext(
+ IndexManagementSecurityContext(job.id, settings, threadPool.threadContext, job.user)
+ ) {
+ rollupSearchService.executeCompositeSearch(updatableJob, metadata)
+ }
+ val rollupResult = when (rollupSearchResult) {
is RollupSearchResult.Success -> {
val compositeRes: InternalComposite = rollupSearchResult.searchResponse.aggregations.get(updatableJob.id)
metadata = metadata.incrementStats(rollupSearchResult.searchResponse, compositeRes)
- when (val rollupIndexResult = rollupIndexer.indexRollups(updatableJob, compositeRes)) {
+ val rollupIndexResult = withClosableContext(
+ IndexManagementSecurityContext(job.id, settings, threadPool.threadContext, job.user)
+ ) {
+ rollupIndexer.indexRollups(updatableJob, compositeRes)
+ }
+ when (rollupIndexResult) {
is RollupIndexResult.Success -> RollupResult.Success(compositeRes, rollupIndexResult.stats)
is RollupIndexResult.Failure -> RollupResult.Failure(rollupIndexResult.message, rollupIndexResult.cause)
}
@@ -291,9 +308,13 @@ object RollupRunner :
updatableJob,
metadata.mergeStats(rollupResult.stats), rollupResult.internalComposite
)
- updatableJob = client.suspendUntil { listener: ActionListener ->
- execute(GetRollupAction.INSTANCE, GetRollupRequest(updatableJob.id, null, "_local"), listener)
- }.rollup ?: throw IllegalStateException("Unable to get rollup job")
+ updatableJob = withClosableContext(
+ IndexManagementSecurityContext(job.id, settings, threadPool.threadContext, null)
+ ) {
+ client.suspendUntil { listener: ActionListener ->
+ execute(GetRollupAction.INSTANCE, GetRollupRequest(updatableJob.id, null, "_local"), listener)
+ }.rollup ?: throw IllegalStateException("Unable to get rollup job")
+ }
}
is RollupResult.Failure -> {
rollupMetadataService.updateMetadata(
@@ -353,10 +374,14 @@ object RollupRunner :
*/
private suspend fun updateRollupJob(job: Rollup, metadata: RollupMetadata): RollupJobResult {
try {
- val req = IndexRollupRequest(rollup = job, refreshPolicy = WriteRequest.RefreshPolicy.IMMEDIATE)
- val res: IndexRollupResponse = client.suspendUntil { execute(IndexRollupAction.INSTANCE, req, it) }
- // TODO: Verify the seqNo/primterm got updated
- return RollupJobResult.Success(res.rollup)
+ return withClosableContext(
+ IndexManagementSecurityContext(job.id, settings, threadPool.threadContext, null)
+ ) {
+ val req = IndexRollupRequest(rollup = job, refreshPolicy = WriteRequest.RefreshPolicy.IMMEDIATE)
+ val res: IndexRollupResponse = client.suspendUntil { execute(IndexRollupAction.INSTANCE, req, it) }
+ // TODO: Verify the seqNo/primterm got updated
+ return@withClosableContext RollupJobResult.Success(res.rollup)
+ }
} catch (e: Exception) {
// TODO: Catching general exceptions for now, can make more granular
// Set metadata to failed since update to rollup job failed
@@ -377,30 +402,35 @@ object RollupRunner :
// which means we always need to validate the source index on every execution?
@Suppress("ReturnCount", "ComplexMethod")
private suspend fun isJobValid(job: Rollup): RollupJobValidationResult {
- var metadata: RollupMetadata? = null
- if (job.metadataID != null) {
- logger.debug("Fetching associated metadata for rollup job [${job.id}]")
- metadata = when (val getMetadataResult = rollupMetadataService.getExistingMetadata(job)) {
- is MetadataResult.Success -> getMetadataResult.metadata
- is MetadataResult.NoMetadata -> null
- is MetadataResult.Failure ->
- throw RollupMetadataException("Failed to get existing rollup metadata [${job.metadataID}]", getMetadataResult.cause)
+ return withClosableContext(
+ IndexManagementSecurityContext(job.id, settings, threadPool.threadContext, job.user)
+ ) {
+ var metadata: RollupMetadata? = null
+ if (job.metadataID != null) {
+ logger.debug("Fetching associated metadata for rollup job [${job.id}]")
+ metadata = when (val getMetadataResult = rollupMetadataService.getExistingMetadata(job)) {
+ is MetadataResult.Success -> getMetadataResult.metadata
+ is MetadataResult.NoMetadata -> null
+ is MetadataResult.Failure ->
+ throw RollupMetadataException("Failed to get existing rollup metadata [${job.metadataID}]", getMetadataResult.cause)
+ }
}
- }
- logger.debug("Validating source index [${job.sourceIndex}] for rollup job [${job.id}]")
- when (val sourceIndexValidationResult = rollupMapperService.isSourceIndexValid(job)) {
- is RollupJobValidationResult.Valid -> {} // No action taken when valid
- else -> return sourceIndexValidationResult
- }
+ logger.debug("Validating source index [${job.sourceIndex}] for rollup job [${job.id}]")
+ when (val sourceIndexValidationResult = rollupMapperService.isSourceIndexValid(job)) {
+ is RollupJobValidationResult.Valid -> {
+ } // No action taken when valid
+ else -> return@withClosableContext sourceIndexValidationResult
+ }
- // we validate target index only if there is metadata document in the rollup
- if (metadata != null) {
- logger.debug("Attempting to create/validate target index [${job.targetIndex}] for rollup job [${job.id}]")
- return rollupMapperService.attemptCreateRollupTargetIndex(job, clusterConfigurationProvider.hasLegacyPlugin)
- }
+ // we validate target index only if there is metadata document in the rollup
+ if (metadata != null) {
+ logger.debug("Attempting to create/validate target index [${job.targetIndex}] for rollup job [${job.id}]")
+ return@withClosableContext rollupMapperService.attemptCreateRollupTargetIndex(job, clusterConfigurationProvider.hasLegacyPlugin)
+ }
- return RollupJobValidationResult.Valid
+ return@withClosableContext RollupJobValidationResult.Valid
+ }
}
/**
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/rollup/RollupSearchService.kt b/src/main/kotlin/org/opensearch/indexmanagement/rollup/RollupSearchService.kt
index 4f4a4e095..b953387ee 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/rollup/RollupSearchService.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/rollup/RollupSearchService.kt
@@ -28,6 +28,7 @@ package org.opensearch.indexmanagement.rollup
import org.apache.logging.log4j.LogManager
import org.opensearch.ExceptionsHelper
+import org.opensearch.OpenSearchSecurityException
import org.opensearch.action.ActionListener
import org.opensearch.action.bulk.BackoffPolicy
import org.opensearch.action.search.SearchPhaseExecutionException
@@ -103,12 +104,12 @@ class RollupSearchService(
logger.debug("Non-continuous job [${rollup.id}] is not processing next window [$metadata]")
return false
} else {
- return hasNextFullWindow(metadata) // TODO: Behavior when next full window but 0 docs/afterkey is null
+ return hasNextFullWindow(rollup, metadata) // TODO: Behavior when next full window but 0 docs/afterkey is null
}
}
- private fun hasNextFullWindow(metadata: RollupMetadata): Boolean {
- return Instant.now().isAfter(metadata.continuous!!.nextWindowEndTime) // TODO: !!
+ private fun hasNextFullWindow(rollup: Rollup, metadata: RollupMetadata): Boolean {
+ return Instant.now().isAfter(metadata.continuous!!.nextWindowEndTime.plusMillis(rollup.delay ?: 0)) // TODO: !!
}
@Suppress("ComplexMethod")
@@ -145,6 +146,9 @@ class RollupSearchService(
} catch (e: MultiBucketConsumerService.TooManyBucketsException) {
logger.error(e.message, e.cause)
RollupSearchResult.Failure(cause = e)
+ } catch (e: OpenSearchSecurityException) {
+ logger.error(e.message, e.cause)
+ RollupSearchResult.Failure("Cannot search data in source index/s - missing required index permissions: ${e.localizedMessage}", e)
} catch (e: Exception) {
logger.error(e.message, e.cause)
RollupSearchResult.Failure(cause = e)
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/delete/DeleteRollupAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/delete/DeleteRollupAction.kt
index 26bb7b1e2..971958ce1 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/delete/DeleteRollupAction.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/delete/DeleteRollupAction.kt
@@ -32,6 +32,6 @@ import org.opensearch.action.delete.DeleteResponse
class DeleteRollupAction private constructor() : ActionType(NAME, ::DeleteResponse) {
companion object {
val INSTANCE = DeleteRollupAction()
- val NAME = "cluster:admin/opendistro/rollup/delete"
+ const val NAME = "cluster:admin/opendistro/rollup/delete"
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/delete/TransportDeleteRollupAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/delete/TransportDeleteRollupAction.kt
index 7f61fc64d..7644a6c4d 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/delete/TransportDeleteRollupAction.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/delete/TransportDeleteRollupAction.kt
@@ -26,28 +26,107 @@
package org.opensearch.indexmanagement.rollup.action.delete
+import org.opensearch.ExceptionsHelper
+import org.opensearch.OpenSearchStatusException
import org.opensearch.action.ActionListener
import org.opensearch.action.delete.DeleteRequest
import org.opensearch.action.delete.DeleteResponse
+import org.opensearch.action.get.GetRequest
+import org.opensearch.action.get.GetResponse
import org.opensearch.action.support.ActionFilters
import org.opensearch.action.support.HandledTransportAction
import org.opensearch.client.Client
+import org.opensearch.cluster.service.ClusterService
import org.opensearch.common.inject.Inject
+import org.opensearch.common.settings.Settings
+import org.opensearch.common.xcontent.NamedXContentRegistry
+import org.opensearch.commons.authuser.User
import org.opensearch.indexmanagement.IndexManagementPlugin.Companion.INDEX_MANAGEMENT_INDEX
+import org.opensearch.indexmanagement.rollup.model.Rollup
+import org.opensearch.indexmanagement.rollup.util.parseRollup
+import org.opensearch.indexmanagement.settings.IndexManagementSettings
+import org.opensearch.indexmanagement.util.SecurityUtils
+import org.opensearch.indexmanagement.util.SecurityUtils.Companion.userHasPermissionForResource
+import org.opensearch.rest.RestStatus
import org.opensearch.tasks.Task
import org.opensearch.transport.TransportService
+import java.lang.Exception
+@Suppress("ReturnCount")
class TransportDeleteRollupAction @Inject constructor(
transportService: TransportService,
val client: Client,
- actionFilters: ActionFilters
+ val clusterService: ClusterService,
+ val settings: Settings,
+ actionFilters: ActionFilters,
+ val xContentRegistry: NamedXContentRegistry
) : HandledTransportAction(
DeleteRollupAction.NAME, transportService, actionFilters, ::DeleteRollupRequest
) {
+ @Volatile private var filterByEnabled = IndexManagementSettings.FILTER_BY_BACKEND_ROLES.get(settings)
+
+ init {
+ clusterService.clusterSettings.addSettingsUpdateConsumer(IndexManagementSettings.FILTER_BY_BACKEND_ROLES) {
+ filterByEnabled = it
+ }
+ }
+
override fun doExecute(task: Task, request: DeleteRollupRequest, actionListener: ActionListener) {
- val deleteRequest = DeleteRequest(INDEX_MANAGEMENT_INDEX, request.id())
- .setRefreshPolicy(request.refreshPolicy)
- client.delete(deleteRequest, actionListener)
+ DeleteRollupHandler(client, actionListener, request).start()
+ }
+
+ inner class DeleteRollupHandler(
+ private val client: Client,
+ private val actionListener: ActionListener,
+ private val request: DeleteRollupRequest,
+ private val user: User? = SecurityUtils.buildUser(client.threadPool().threadContext)
+ ) {
+
+ fun start() {
+ client.threadPool().threadContext.stashContext().use {
+ getRollup()
+ }
+ }
+
+ private fun getRollup() {
+ val getRequest = GetRequest(INDEX_MANAGEMENT_INDEX, request.id())
+ client.get(
+ getRequest,
+ object : ActionListener {
+ override fun onResponse(response: GetResponse) {
+ if (!response.isExists) {
+ actionListener.onFailure(OpenSearchStatusException("Rollup ${request.id()} is not found", RestStatus.NOT_FOUND))
+ return
+ }
+
+ val rollup: Rollup?
+ try {
+ rollup = parseRollup(response, xContentRegistry)
+ } catch (e: IllegalArgumentException) {
+ actionListener.onFailure(OpenSearchStatusException("Rollup ${request.id()} is not found", RestStatus.NOT_FOUND))
+ return
+ }
+ if (!userHasPermissionForResource(user, rollup.user, filterByEnabled, "rollup", rollup.id, actionListener)) {
+ return
+ } else {
+ delete()
+ }
+ }
+
+ override fun onFailure(e: Exception) {
+ actionListener.onFailure(ExceptionsHelper.unwrapCause(e) as Exception)
+ }
+ }
+ )
+ }
+
+ private fun delete() {
+ val deleteRequest = DeleteRequest(INDEX_MANAGEMENT_INDEX, request.id())
+ .setRefreshPolicy(request.refreshPolicy)
+ client.threadPool().threadContext.stashContext().use {
+ client.delete(deleteRequest, actionListener)
+ }
+ }
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/explain/ExplainRollupAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/explain/ExplainRollupAction.kt
index 71c398353..fbd8019a0 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/explain/ExplainRollupAction.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/explain/ExplainRollupAction.kt
@@ -31,6 +31,6 @@ import org.opensearch.action.ActionType
class ExplainRollupAction private constructor() : ActionType(NAME, ::ExplainRollupResponse) {
companion object {
val INSTANCE = ExplainRollupAction()
- val NAME = "cluster:admin/opendistro/rollup/explain"
+ const val NAME = "cluster:admin/opendistro/rollup/explain"
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/explain/TransportExplainRollupAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/explain/TransportExplainRollupAction.kt
index 75c3029f1..cf0b204e5 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/explain/TransportExplainRollupAction.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/explain/TransportExplainRollupAction.kt
@@ -35,7 +35,9 @@ import org.opensearch.action.search.SearchResponse
import org.opensearch.action.support.ActionFilters
import org.opensearch.action.support.HandledTransportAction
import org.opensearch.client.Client
+import org.opensearch.cluster.service.ClusterService
import org.opensearch.common.inject.Inject
+import org.opensearch.common.settings.Settings
import org.opensearch.index.query.BoolQueryBuilder
import org.opensearch.index.query.IdsQueryBuilder
import org.opensearch.index.query.WildcardQueryBuilder
@@ -45,6 +47,9 @@ import org.opensearch.indexmanagement.opensearchapi.parseWithType
import org.opensearch.indexmanagement.rollup.model.ExplainRollup
import org.opensearch.indexmanagement.rollup.model.Rollup
import org.opensearch.indexmanagement.rollup.model.RollupMetadata
+import org.opensearch.indexmanagement.settings.IndexManagementSettings
+import org.opensearch.indexmanagement.util.SecurityUtils.Companion.addUserFilter
+import org.opensearch.indexmanagement.util.SecurityUtils.Companion.buildUser
import org.opensearch.search.builder.SearchSourceBuilder
import org.opensearch.tasks.Task
import org.opensearch.transport.RemoteTransportException
@@ -54,11 +59,21 @@ import kotlin.Exception
class TransportExplainRollupAction @Inject constructor(
transportService: TransportService,
val client: Client,
+ val settings: Settings,
+ val clusterService: ClusterService,
actionFilters: ActionFilters
) : HandledTransportAction(
ExplainRollupAction.NAME, transportService, actionFilters, ::ExplainRollupRequest
) {
+ @Volatile private var filterByEnabled = IndexManagementSettings.FILTER_BY_BACKEND_ROLES.get(settings)
+
+ init {
+ clusterService.clusterSettings.addSettingsUpdateConsumer(IndexManagementSettings.FILTER_BY_BACKEND_ROLES) {
+ filterByEnabled = it
+ }
+ }
+
private val log = LogManager.getLogger(javaClass)
@Suppress("SpreadOperator")
@@ -67,78 +82,80 @@ class TransportExplainRollupAction @Inject constructor(
// Instantiate concrete ids to metadata map by removing wildcard matches
val idsToExplain: MutableMap = ids.filter { !it.contains("*") }.map { it to null }.toMap(mutableMapOf())
// First search is for all rollup documents that match at least one of the given rollupIDs
- val searchRequest = SearchRequest(INDEX_MANAGEMENT_INDEX)
- .source(
- SearchSourceBuilder().query(
- BoolQueryBuilder().minimumShouldMatch(1).apply {
- ids.forEach {
- this.should(WildcardQueryBuilder("${Rollup.ROLLUP_TYPE}.${Rollup.ROLLUP_ID_FIELD}.keyword", "*$it*"))
- }
- }
- )
- )
- client.search(
- searchRequest,
- object : ActionListener {
- override fun onResponse(response: SearchResponse) {
- try {
- response.hits.hits.forEach {
- val rollup = contentParser(it.sourceRef).parseWithType(it.id, it.seqNo, it.primaryTerm, Rollup.Companion::parse)
- idsToExplain[rollup.id] = ExplainRollup(metadataID = rollup.metadataID)
+ val queryBuilder = BoolQueryBuilder().minimumShouldMatch(1).apply {
+ ids.forEach {
+ this.should(WildcardQueryBuilder("${Rollup.ROLLUP_TYPE}.${Rollup.ROLLUP_ID_FIELD}.keyword", "*$it*"))
+ }
+ }
+ val user = buildUser(client.threadPool().threadContext)
+ addUserFilter(user, queryBuilder, filterByEnabled, "rollup.user")
+
+ val searchRequest = SearchRequest(INDEX_MANAGEMENT_INDEX).source(SearchSourceBuilder().query(queryBuilder))
+
+ client.threadPool().threadContext.stashContext().use {
+ client.search(
+ searchRequest,
+ object : ActionListener {
+ override fun onResponse(response: SearchResponse) {
+ try {
+ response.hits.hits.forEach {
+ val rollup = contentParser(it.sourceRef).parseWithType(it.id, it.seqNo, it.primaryTerm, Rollup.Companion::parse)
+ idsToExplain[rollup.id] = ExplainRollup(metadataID = rollup.metadataID)
+ }
+ } catch (e: Exception) {
+ log.error("Failed to parse explain response", e)
+ actionListener.onFailure(e)
+ return
}
- } catch (e: Exception) {
- log.error("Failed to parse explain response", e)
- actionListener.onFailure(e)
- return
- }
- val metadataIds = idsToExplain.values.mapNotNull { it?.metadataID }
- val metadataSearchRequest = SearchRequest(INDEX_MANAGEMENT_INDEX)
- .source(SearchSourceBuilder().query(IdsQueryBuilder().addIds(*metadataIds.toTypedArray())))
- client.search(
- metadataSearchRequest,
- object : ActionListener {
- override fun onResponse(response: SearchResponse) {
- try {
- response.hits.hits.forEach {
- val metadata = contentParser(it.sourceRef)
- .parseWithType(it.id, it.seqNo, it.primaryTerm, RollupMetadata.Companion::parse)
- idsToExplain.computeIfPresent(metadata.rollupID) { _,
- explainRollup ->
- explainRollup.copy(metadata = metadata)
+ val metadataIds = idsToExplain.values.mapNotNull { it?.metadataID }
+ val metadataSearchRequest = SearchRequest(INDEX_MANAGEMENT_INDEX)
+ .source(SearchSourceBuilder().query(IdsQueryBuilder().addIds(*metadataIds.toTypedArray())))
+ client.search(
+ metadataSearchRequest,
+ object : ActionListener {
+ override fun onResponse(response: SearchResponse) {
+ try {
+ response.hits.hits.forEach {
+ val metadata = contentParser(it.sourceRef)
+ .parseWithType(it.id, it.seqNo, it.primaryTerm, RollupMetadata.Companion::parse)
+ idsToExplain.computeIfPresent(metadata.rollupID) { _,
+ explainRollup ->
+ explainRollup.copy(metadata = metadata)
+ }
}
+ actionListener.onResponse(ExplainRollupResponse(idsToExplain.toMap()))
+ } catch (e: Exception) {
+ log.error("Failed to parse rollup metadata", e)
+ actionListener.onFailure(e)
+ return
}
- actionListener.onResponse(ExplainRollupResponse(idsToExplain.toMap()))
- } catch (e: Exception) {
- log.error("Failed to parse rollup metadata", e)
- actionListener.onFailure(e)
- return
}
- }
- override fun onFailure(e: Exception) {
- log.error("Failed to search rollup metadata", e)
- when (e) {
- is RemoteTransportException -> actionListener.onFailure(ExceptionsHelper.unwrapCause(e) as Exception)
- else -> actionListener.onFailure(e)
+ override fun onFailure(e: Exception) {
+ log.error("Failed to search rollup metadata", e)
+ when (e) {
+ is RemoteTransportException -> actionListener.onFailure(ExceptionsHelper.unwrapCause(e) as Exception)
+ else -> actionListener.onFailure(e)
+ }
}
}
- }
- )
- }
+ )
+ }
- override fun onFailure(e: Exception) {
- log.error("Failed to search for rollups", e)
- when (e) {
- is ResourceNotFoundException -> {
- val nonWildcardIds = ids.filter { !it.contains("*") }.map { it to null }.toMap(mutableMapOf())
- actionListener.onResponse(ExplainRollupResponse(nonWildcardIds))
+ override fun onFailure(e: Exception) {
+ log.error("Failed to search for rollups", e)
+ when (e) {
+ is ResourceNotFoundException -> {
+ val nonWildcardIds = ids.filter { !it.contains("*") }.map { it to null }.toMap(mutableMapOf())
+ actionListener.onResponse(ExplainRollupResponse(nonWildcardIds))
+ }
+ is RemoteTransportException -> actionListener.onFailure(ExceptionsHelper.unwrapCause(e) as Exception)
+ else -> actionListener.onFailure(e)
}
- is RemoteTransportException -> actionListener.onFailure(ExceptionsHelper.unwrapCause(e) as Exception)
- else -> actionListener.onFailure(e)
}
}
- }
- )
+ )
+ }
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/get/GetRollupAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/get/GetRollupAction.kt
index 4f25a2796..627c6b9fa 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/get/GetRollupAction.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/get/GetRollupAction.kt
@@ -31,6 +31,6 @@ import org.opensearch.action.ActionType
class GetRollupAction private constructor() : ActionType(NAME, ::GetRollupResponse) {
companion object {
val INSTANCE = GetRollupAction()
- val NAME = "cluster:admin/opendistro/rollup/get"
+ const val NAME = "cluster:admin/opendistro/rollup/get"
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/get/GetRollupResponse.kt b/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/get/GetRollupResponse.kt
index f4896b99a..7c5e209e4 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/get/GetRollupResponse.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/get/GetRollupResponse.kt
@@ -32,7 +32,7 @@ import org.opensearch.common.io.stream.StreamOutput
import org.opensearch.common.xcontent.ToXContent
import org.opensearch.common.xcontent.ToXContentObject
import org.opensearch.common.xcontent.XContentBuilder
-import org.opensearch.indexmanagement.indexstatemanagement.util.XCONTENT_WITHOUT_TYPE
+import org.opensearch.indexmanagement.indexstatemanagement.util.XCONTENT_WITHOUT_TYPE_AND_USER
import org.opensearch.indexmanagement.rollup.model.Rollup
import org.opensearch.indexmanagement.rollup.model.Rollup.Companion.ROLLUP_TYPE
import org.opensearch.indexmanagement.util._ID
@@ -98,7 +98,7 @@ class GetRollupResponse : ActionResponse, ToXContentObject {
.field(_VERSION, version)
.field(_SEQ_NO, seqNo)
.field(_PRIMARY_TERM, primaryTerm)
- if (rollup != null) builder.field(ROLLUP_TYPE, rollup, XCONTENT_WITHOUT_TYPE)
+ if (rollup != null) builder.field(ROLLUP_TYPE, rollup, XCONTENT_WITHOUT_TYPE_AND_USER)
return builder.endObject()
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/get/GetRollupsAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/get/GetRollupsAction.kt
index d3033dcf1..fce50bc87 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/get/GetRollupsAction.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/get/GetRollupsAction.kt
@@ -31,6 +31,6 @@ import org.opensearch.action.ActionType
class GetRollupsAction private constructor() : ActionType(NAME, ::GetRollupsResponse) {
companion object {
val INSTANCE = GetRollupsAction()
- val NAME = "cluster:admin/opendistro/rollup/search"
+ const val NAME = "cluster:admin/opendistro/rollup/search"
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/get/GetRollupsResponse.kt b/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/get/GetRollupsResponse.kt
index 88778f7f4..fd09eea7f 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/get/GetRollupsResponse.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/get/GetRollupsResponse.kt
@@ -32,7 +32,7 @@ import org.opensearch.common.io.stream.StreamOutput
import org.opensearch.common.xcontent.ToXContent
import org.opensearch.common.xcontent.ToXContentObject
import org.opensearch.common.xcontent.XContentBuilder
-import org.opensearch.indexmanagement.indexstatemanagement.util.XCONTENT_WITHOUT_TYPE
+import org.opensearch.indexmanagement.indexstatemanagement.util.XCONTENT_WITHOUT_TYPE_AND_USER
import org.opensearch.indexmanagement.rollup.model.Rollup
import org.opensearch.indexmanagement.rollup.model.Rollup.Companion.ROLLUP_TYPE
import org.opensearch.indexmanagement.util._ID
@@ -81,7 +81,7 @@ class GetRollupsResponse : ActionResponse, ToXContentObject {
.field(_ID, rollup.id)
.field(_SEQ_NO, rollup.seqNo)
.field(_PRIMARY_TERM, rollup.primaryTerm)
- .field(ROLLUP_TYPE, rollup, XCONTENT_WITHOUT_TYPE)
+ .field(ROLLUP_TYPE, rollup, XCONTENT_WITHOUT_TYPE_AND_USER)
.endObject()
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/get/TransportGetRollupAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/get/TransportGetRollupAction.kt
index 1cc88b5cb..3c4f11a75 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/get/TransportGetRollupAction.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/get/TransportGetRollupAction.kt
@@ -33,14 +33,16 @@ import org.opensearch.action.get.GetResponse
import org.opensearch.action.support.ActionFilters
import org.opensearch.action.support.HandledTransportAction
import org.opensearch.client.Client
+import org.opensearch.cluster.service.ClusterService
import org.opensearch.common.inject.Inject
-import org.opensearch.common.xcontent.LoggingDeprecationHandler
+import org.opensearch.common.settings.Settings
import org.opensearch.common.xcontent.NamedXContentRegistry
-import org.opensearch.common.xcontent.XContentHelper
-import org.opensearch.common.xcontent.XContentType
import org.opensearch.indexmanagement.IndexManagementPlugin.Companion.INDEX_MANAGEMENT_INDEX
-import org.opensearch.indexmanagement.opensearchapi.parseWithType
import org.opensearch.indexmanagement.rollup.model.Rollup
+import org.opensearch.indexmanagement.rollup.util.parseRollup
+import org.opensearch.indexmanagement.settings.IndexManagementSettings
+import org.opensearch.indexmanagement.util.SecurityUtils
+import org.opensearch.indexmanagement.util.SecurityUtils.Companion.buildUser
import org.opensearch.rest.RestStatus
import org.opensearch.tasks.Task
import org.opensearch.transport.TransportService
@@ -50,39 +52,59 @@ class TransportGetRollupAction @Inject constructor(
transportService: TransportService,
val client: Client,
actionFilters: ActionFilters,
+ val settings: Settings,
+ val clusterService: ClusterService,
val xContentRegistry: NamedXContentRegistry
) : HandledTransportAction (
GetRollupAction.NAME, transportService, actionFilters, ::GetRollupRequest
) {
+ @Volatile private var filterByEnabled = IndexManagementSettings.FILTER_BY_BACKEND_ROLES.get(settings)
+
+ init {
+ clusterService.clusterSettings.addSettingsUpdateConsumer(IndexManagementSettings.FILTER_BY_BACKEND_ROLES) {
+ filterByEnabled = it
+ }
+ }
+
+ @Suppress("ReturnCount")
override fun doExecute(task: Task, request: GetRollupRequest, listener: ActionListener) {
- val getRequest = GetRequest(INDEX_MANAGEMENT_INDEX, request.id)
- .fetchSourceContext(request.srcContext).preference(request.preference)
- client.get(
- getRequest,
- object : ActionListener {
- override fun onResponse(response: GetResponse) {
- if (!response.isExists) {
- return listener.onFailure(OpenSearchStatusException("Rollup not found", RestStatus.NOT_FOUND))
- }
+ val getRequest = GetRequest(INDEX_MANAGEMENT_INDEX, request.id).preference(request.preference)
+ val user = buildUser(client.threadPool().threadContext)
+ client.threadPool().threadContext.stashContext().use {
+ client.get(
+ getRequest,
+ object : ActionListener {
+ override fun onResponse(response: GetResponse) {
+ if (!response.isExists) {
+ return listener.onFailure(OpenSearchStatusException("Rollup not found", RestStatus.NOT_FOUND))
+ }
- var rollup: Rollup? = null
- if (!response.isSourceEmpty) {
- XContentHelper.createParser(
- xContentRegistry, LoggingDeprecationHandler.INSTANCE,
- response.sourceAsBytesRef, XContentType.JSON
- ).use { xcp ->
- rollup = xcp.parseWithType(response.id, response.seqNo, response.primaryTerm, Rollup.Companion::parse)
+ val rollup: Rollup?
+ try {
+ rollup = parseRollup(response, xContentRegistry)
+ } catch (e: IllegalArgumentException) {
+ listener.onFailure(OpenSearchStatusException("Rollup not found", RestStatus.NOT_FOUND))
+ return
+ }
+ if (!SecurityUtils.userHasPermissionForResource(user, rollup.user, filterByEnabled, "rollup", request.id, listener)) {
+ return
+ } else {
+ // if HEAD request don't return the rollup
+ val rollupResponse = if (request.srcContext != null && !request.srcContext.fetchSource()) {
+ GetRollupResponse(response.id, response.version, response.seqNo, response.primaryTerm, RestStatus.OK, null)
+ } else {
+ GetRollupResponse(response.id, response.version, response.seqNo, response.primaryTerm, RestStatus.OK, rollup)
+ }
+ listener.onResponse(rollupResponse)
}
}
- listener.onResponse(GetRollupResponse(response.id, response.version, response.seqNo, response.primaryTerm, RestStatus.OK, rollup))
- }
-
- override fun onFailure(e: Exception) {
- listener.onFailure(e)
+ override fun onFailure(e: Exception) {
+ listener.onFailure(e)
+ }
}
- }
- )
+ )
+ }
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/get/TransportGetRollupsAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/get/TransportGetRollupsAction.kt
index c5cc95b48..e60cab2b3 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/get/TransportGetRollupsAction.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/get/TransportGetRollupsAction.kt
@@ -34,7 +34,9 @@ import org.opensearch.action.search.SearchResponse
import org.opensearch.action.support.ActionFilters
import org.opensearch.action.support.HandledTransportAction
import org.opensearch.client.Client
+import org.opensearch.cluster.service.ClusterService
import org.opensearch.common.inject.Inject
+import org.opensearch.common.settings.Settings
import org.opensearch.common.xcontent.NamedXContentRegistry
import org.opensearch.index.query.BoolQueryBuilder
import org.opensearch.index.query.ExistsQueryBuilder
@@ -43,6 +45,9 @@ import org.opensearch.indexmanagement.IndexManagementPlugin.Companion.INDEX_MANA
import org.opensearch.indexmanagement.opensearchapi.contentParser
import org.opensearch.indexmanagement.opensearchapi.parseWithType
import org.opensearch.indexmanagement.rollup.model.Rollup
+import org.opensearch.indexmanagement.settings.IndexManagementSettings
+import org.opensearch.indexmanagement.util.SecurityUtils.Companion.addUserFilter
+import org.opensearch.indexmanagement.util.SecurityUtils.Companion.buildUser
import org.opensearch.rest.RestStatus
import org.opensearch.search.builder.SearchSourceBuilder
import org.opensearch.search.sort.SortOrder
@@ -54,11 +59,21 @@ class TransportGetRollupsAction @Inject constructor(
transportService: TransportService,
val client: Client,
actionFilters: ActionFilters,
+ val clusterService: ClusterService,
+ val settings: Settings,
val xContentRegistry: NamedXContentRegistry
) : HandledTransportAction (
GetRollupsAction.NAME, transportService, actionFilters, ::GetRollupsRequest
) {
+ @Volatile private var filterByEnabled = IndexManagementSettings.FILTER_BY_BACKEND_ROLES.get(settings)
+
+ init {
+ clusterService.clusterSettings.addSettingsUpdateConsumer(IndexManagementSettings.FILTER_BY_BACKEND_ROLES) {
+ filterByEnabled = it
+ }
+ }
+
override fun doExecute(task: Task, request: GetRollupsRequest, listener: ActionListener) {
val searchString = request.searchString.trim()
val from = request.from
@@ -70,37 +85,41 @@ class TransportGetRollupsAction @Inject constructor(
if (searchString.isNotEmpty()) {
boolQueryBuilder.filter(WildcardQueryBuilder("${Rollup.ROLLUP_TYPE}.${Rollup.ROLLUP_ID_FIELD}.keyword", "*$searchString*"))
}
+ val user = buildUser(client.threadPool().threadContext)
+ addUserFilter(user, boolQueryBuilder, filterByEnabled, "rollup.user")
val searchSourceBuilder = SearchSourceBuilder().query(boolQueryBuilder).from(from).size(size).seqNoAndPrimaryTerm(true)
.sort(sortField, SortOrder.fromString(sortDirection))
val searchRequest = SearchRequest(INDEX_MANAGEMENT_INDEX).source(searchSourceBuilder)
- client.search(
- searchRequest,
- object : ActionListener {
- override fun onResponse(response: SearchResponse) {
- val totalRollups = response.hits.totalHits?.value ?: 0
+ client.threadPool().threadContext.stashContext().use {
+ client.search(
+ searchRequest,
+ object : ActionListener {
+ override fun onResponse(response: SearchResponse) {
+ val totalRollups = response.hits.totalHits?.value ?: 0
- if (response.shardFailures.isNotEmpty()) {
- val failure = response.shardFailures.reduce { s1, s2 -> if (s1.status().status > s2.status().status) s1 else s2 }
- listener.onFailure(OpenSearchStatusException("Get rollups failed on some shards", failure.status(), failure.cause))
- } else {
- try {
- val rollups = response.hits.hits.map {
- contentParser(it.sourceRef).parseWithType(it.id, it.seqNo, it.primaryTerm, Rollup.Companion::parse)
- }
- listener.onResponse(GetRollupsResponse(rollups, totalRollups.toInt(), RestStatus.OK))
- } catch (e: Exception) {
- listener.onFailure(
- OpenSearchStatusException(
- "Failed to parse rollups",
- RestStatus.INTERNAL_SERVER_ERROR, ExceptionsHelper.unwrapCause(e)
+ if (response.shardFailures.isNotEmpty()) {
+ val failure = response.shardFailures.reduce { s1, s2 -> if (s1.status().status > s2.status().status) s1 else s2 }
+ listener.onFailure(OpenSearchStatusException("Get rollups failed on some shards", failure.status(), failure.cause))
+ } else {
+ try {
+ val rollups = response.hits.hits.map {
+ contentParser(it.sourceRef).parseWithType(it.id, it.seqNo, it.primaryTerm, Rollup.Companion::parse)
+ }
+ listener.onResponse(GetRollupsResponse(rollups, totalRollups.toInt(), RestStatus.OK))
+ } catch (e: Exception) {
+ listener.onFailure(
+ OpenSearchStatusException(
+ "Failed to parse rollups",
+ RestStatus.INTERNAL_SERVER_ERROR, ExceptionsHelper.unwrapCause(e)
+ )
)
- )
+ }
}
}
- }
- override fun onFailure(e: Exception) = listener.onFailure(e)
- }
- )
+ override fun onFailure(e: Exception) = listener.onFailure(e)
+ }
+ )
+ }
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/index/IndexRollupAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/index/IndexRollupAction.kt
index be2e48af1..25d0dcafe 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/index/IndexRollupAction.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/index/IndexRollupAction.kt
@@ -31,6 +31,6 @@ import org.opensearch.action.ActionType
class IndexRollupAction private constructor() : ActionType(NAME, ::IndexRollupResponse) {
companion object {
val INSTANCE = IndexRollupAction()
- val NAME = "cluster:admin/opendistro/rollup/index"
+ const val NAME = "cluster:admin/opendistro/rollup/index"
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/index/IndexRollupResponse.kt b/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/index/IndexRollupResponse.kt
index d7de18d3c..3de0a03b7 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/index/IndexRollupResponse.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/index/IndexRollupResponse.kt
@@ -32,7 +32,7 @@ import org.opensearch.common.io.stream.StreamOutput
import org.opensearch.common.xcontent.ToXContent
import org.opensearch.common.xcontent.ToXContentObject
import org.opensearch.common.xcontent.XContentBuilder
-import org.opensearch.indexmanagement.indexstatemanagement.util.XCONTENT_WITHOUT_TYPE
+import org.opensearch.indexmanagement.indexstatemanagement.util.XCONTENT_WITHOUT_TYPE_AND_USER
import org.opensearch.indexmanagement.rollup.model.Rollup
import org.opensearch.indexmanagement.rollup.model.Rollup.Companion.ROLLUP_TYPE
import org.opensearch.indexmanagement.util._ID
@@ -93,7 +93,7 @@ class IndexRollupResponse : ActionResponse, ToXContentObject {
.field(_VERSION, version)
.field(_SEQ_NO, seqNo)
.field(_PRIMARY_TERM, primaryTerm)
- .field(ROLLUP_TYPE, rollup, XCONTENT_WITHOUT_TYPE)
+ .field(ROLLUP_TYPE, rollup, XCONTENT_WITHOUT_TYPE_AND_USER)
.endObject()
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/index/TransportIndexRollupAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/index/TransportIndexRollupAction.kt
index 5a5961fb7..1a5b5126f 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/index/TransportIndexRollupAction.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/index/TransportIndexRollupAction.kt
@@ -30,6 +30,8 @@ import org.apache.logging.log4j.LogManager
import org.opensearch.OpenSearchStatusException
import org.opensearch.action.ActionListener
import org.opensearch.action.DocWriteRequest
+import org.opensearch.action.get.GetRequest
+import org.opensearch.action.get.GetResponse
import org.opensearch.action.index.IndexRequest
import org.opensearch.action.index.IndexResponse
import org.opensearch.action.support.ActionFilters
@@ -38,15 +40,20 @@ import org.opensearch.action.support.master.AcknowledgedResponse
import org.opensearch.client.Client
import org.opensearch.cluster.service.ClusterService
import org.opensearch.common.inject.Inject
+import org.opensearch.common.settings.Settings
+import org.opensearch.common.xcontent.NamedXContentRegistry
import org.opensearch.common.xcontent.ToXContent
import org.opensearch.common.xcontent.XContentFactory.jsonBuilder
+import org.opensearch.commons.authuser.User
import org.opensearch.indexmanagement.IndexManagementIndices
import org.opensearch.indexmanagement.IndexManagementPlugin.Companion.INDEX_MANAGEMENT_INDEX
-import org.opensearch.indexmanagement.rollup.action.get.GetRollupAction
-import org.opensearch.indexmanagement.rollup.action.get.GetRollupRequest
-import org.opensearch.indexmanagement.rollup.action.get.GetRollupResponse
import org.opensearch.indexmanagement.rollup.model.Rollup
+import org.opensearch.indexmanagement.rollup.util.parseRollup
+import org.opensearch.indexmanagement.settings.IndexManagementSettings
import org.opensearch.indexmanagement.util.IndexUtils
+import org.opensearch.indexmanagement.util.SecurityUtils
+import org.opensearch.indexmanagement.util.SecurityUtils.Companion.buildUser
+import org.opensearch.indexmanagement.util.SecurityUtils.Companion.validateUserConfiguration
import org.opensearch.rest.RestStatus
import org.opensearch.tasks.Task
import org.opensearch.transport.TransportService
@@ -57,11 +64,21 @@ class TransportIndexRollupAction @Inject constructor(
val client: Client,
actionFilters: ActionFilters,
val indexManagementIndices: IndexManagementIndices,
- val clusterService: ClusterService
+ val clusterService: ClusterService,
+ val settings: Settings,
+ val xContentRegistry: NamedXContentRegistry
) : HandledTransportAction(
IndexRollupAction.NAME, transportService, actionFilters, ::IndexRollupRequest
) {
+ @Volatile private var filterByEnabled = IndexManagementSettings.FILTER_BY_BACKEND_ROLES.get(settings)
+
+ init {
+ clusterService.clusterSettings.addSettingsUpdateConsumer(IndexManagementSettings.FILTER_BY_BACKEND_ROLES) {
+ filterByEnabled = it
+ }
+ }
+
private val log = LogManager.getLogger(javaClass)
override fun doExecute(task: Task, request: IndexRollupRequest, listener: ActionListener) {
@@ -71,11 +88,17 @@ class TransportIndexRollupAction @Inject constructor(
inner class IndexRollupHandler(
private val client: Client,
private val actionListener: ActionListener,
- private val request: IndexRollupRequest
+ private val request: IndexRollupRequest,
+ private val user: User? = buildUser(client.threadPool().threadContext, request.rollup.user)
) {
fun start() {
- indexManagementIndices.checkAndUpdateIMConfigIndex(ActionListener.wrap(::onCreateMappingsResponse, actionListener::onFailure))
+ client.threadPool().threadContext.stashContext().use {
+ if (!validateUserConfiguration(user, filterByEnabled, actionListener)) {
+ return
+ }
+ indexManagementIndices.checkAndUpdateIMConfigIndex(ActionListener.wrap(::onCreateMappingsResponse, actionListener::onFailure))
+ }
}
private fun onCreateMappingsResponse(response: AcknowledgedResponse) {
@@ -94,17 +117,27 @@ class TransportIndexRollupAction @Inject constructor(
}
private fun getRollup() {
- val getReq = GetRollupRequest(request.rollup.id, null)
- client.execute(GetRollupAction.INSTANCE, getReq, ActionListener.wrap(::onGetRollup, actionListener::onFailure))
+ val getRequest = GetRequest(INDEX_MANAGEMENT_INDEX, request.rollup.id)
+ client.get(getRequest, ActionListener.wrap(::onGetRollup, actionListener::onFailure))
}
@Suppress("ReturnCount")
- private fun onGetRollup(response: GetRollupResponse) {
- if (response.status != RestStatus.OK) {
- return actionListener.onFailure(OpenSearchStatusException("Unable to get existing rollup", response.status))
+ private fun onGetRollup(response: GetResponse) {
+ if (!response.isExists) {
+ actionListener.onFailure(OpenSearchStatusException("Rollup not found", RestStatus.NOT_FOUND))
+ return
+ }
+
+ val rollup: Rollup?
+ try {
+ rollup = parseRollup(response, xContentRegistry)
+ } catch (e: IllegalArgumentException) {
+ actionListener.onFailure(OpenSearchStatusException("Rollup not found", RestStatus.NOT_FOUND))
+ return
+ }
+ if (!SecurityUtils.userHasPermissionForResource(user, rollup.user, filterByEnabled, "rollup", rollup.id, actionListener)) {
+ return
}
- val rollup = response.rollup
- ?: return actionListener.onFailure(OpenSearchStatusException("The current rollup is null", RestStatus.INTERNAL_SERVER_ERROR))
val modified = modifiedImmutableProperties(rollup, request.rollup)
if (modified.isNotEmpty()) {
return actionListener.onFailure(OpenSearchStatusException("Not allowed to modify $modified", RestStatus.BAD_REQUEST))
@@ -124,7 +157,7 @@ class TransportIndexRollupAction @Inject constructor(
}
private fun putRollup() {
- val rollup = request.rollup.copy(schemaVersion = IndexUtils.indexManagementConfigSchemaVersion)
+ val rollup = request.rollup.copy(schemaVersion = IndexUtils.indexManagementConfigSchemaVersion, user = this.user)
request.index(INDEX_MANAGEMENT_INDEX)
.id(request.rollup.id)
.source(rollup.toXContent(jsonBuilder(), ToXContent.EMPTY_PARAMS))
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/start/StartRollupAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/start/StartRollupAction.kt
index 0bc17871c..318df65e7 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/start/StartRollupAction.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/start/StartRollupAction.kt
@@ -32,6 +32,6 @@ import org.opensearch.action.support.master.AcknowledgedResponse
class StartRollupAction private constructor() : ActionType(NAME, ::AcknowledgedResponse) {
companion object {
val INSTANCE = StartRollupAction()
- val NAME = "cluster:admin/opendistro/rollup/start"
+ const val NAME = "cluster:admin/opendistro/rollup/start"
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/start/TransportStartRollupAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/start/TransportStartRollupAction.kt
index e023f239e..2b0f85275 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/start/TransportStartRollupAction.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/start/TransportStartRollupAction.kt
@@ -39,63 +39,91 @@ import org.opensearch.action.support.master.AcknowledgedResponse
import org.opensearch.action.update.UpdateRequest
import org.opensearch.action.update.UpdateResponse
import org.opensearch.client.Client
+import org.opensearch.cluster.service.ClusterService
import org.opensearch.common.inject.Inject
+import org.opensearch.common.settings.Settings
import org.opensearch.common.xcontent.LoggingDeprecationHandler
import org.opensearch.common.xcontent.NamedXContentRegistry
import org.opensearch.common.xcontent.XContentHelper
import org.opensearch.common.xcontent.XContentType
+import org.opensearch.commons.authuser.User
import org.opensearch.indexmanagement.IndexManagementPlugin.Companion.INDEX_MANAGEMENT_INDEX
import org.opensearch.indexmanagement.opensearchapi.parseWithType
-import org.opensearch.indexmanagement.rollup.action.get.GetRollupAction
-import org.opensearch.indexmanagement.rollup.action.get.GetRollupRequest
-import org.opensearch.indexmanagement.rollup.action.get.GetRollupResponse
import org.opensearch.indexmanagement.rollup.model.Rollup
import org.opensearch.indexmanagement.rollup.model.RollupMetadata
+import org.opensearch.indexmanagement.rollup.util.parseRollup
+import org.opensearch.indexmanagement.settings.IndexManagementSettings
+import org.opensearch.indexmanagement.util.SecurityUtils.Companion.buildUser
+import org.opensearch.indexmanagement.util.SecurityUtils.Companion.userHasPermissionForResource
import org.opensearch.rest.RestStatus
import org.opensearch.tasks.Task
import org.opensearch.transport.TransportService
+import java.lang.IllegalArgumentException
import java.time.Instant
+@Suppress("ReturnCount")
class TransportStartRollupAction @Inject constructor(
transportService: TransportService,
val client: Client,
- actionFilters: ActionFilters
+ val clusterService: ClusterService,
+ val settings: Settings,
+ actionFilters: ActionFilters,
+ val xContentRegistry: NamedXContentRegistry
) : HandledTransportAction(
StartRollupAction.NAME, transportService, actionFilters, ::StartRollupRequest
) {
+ @Volatile private var filterByEnabled = IndexManagementSettings.FILTER_BY_BACKEND_ROLES.get(settings)
+
+ init {
+ clusterService.clusterSettings.addSettingsUpdateConsumer(IndexManagementSettings.FILTER_BY_BACKEND_ROLES) {
+ filterByEnabled = it
+ }
+ }
+
private val log = LogManager.getLogger(javaClass)
override fun doExecute(task: Task, request: StartRollupRequest, actionListener: ActionListener) {
- val getReq = GetRollupRequest(request.id(), null)
- client.execute(
- GetRollupAction.INSTANCE, getReq,
- object : ActionListener {
- override fun onResponse(response: GetRollupResponse) {
- val rollup = response.rollup
- if (rollup == null) {
- return actionListener.onFailure(
- OpenSearchStatusException("Could not find rollup [${request.id()}]", RestStatus.NOT_FOUND)
- )
- }
+ val getReq = GetRequest(INDEX_MANAGEMENT_INDEX, request.id())
+ val user: User? = buildUser(client.threadPool().threadContext)
+ client.threadPool().threadContext.stashContext().use {
+ client.get(
+ getReq,
+ object : ActionListener {
+ override fun onResponse(response: GetResponse) {
+ if (!response.isExists) {
+ actionListener.onFailure(OpenSearchStatusException("Rollup not found", RestStatus.NOT_FOUND))
+ return
+ }
- if (rollup.enabled) {
- log.debug("Rollup job is already enabled, checking if metadata needs to be updated")
- return if (rollup.metadataID == null) {
- actionListener.onResponse(AcknowledgedResponse(true))
- } else {
- getRollupMetadata(rollup, actionListener)
+ val rollup: Rollup?
+ try {
+ rollup = parseRollup(response, xContentRegistry)
+ } catch (e: IllegalArgumentException) {
+ actionListener.onFailure(OpenSearchStatusException("Rollup not found", RestStatus.NOT_FOUND))
+ return
+ }
+ if (!userHasPermissionForResource(user, rollup.user, filterByEnabled, "rollup", rollup.id, actionListener)) {
+ return
+ }
+ if (rollup.enabled) {
+ log.debug("Rollup job is already enabled, checking if metadata needs to be updated")
+ return if (rollup.metadataID == null) {
+ actionListener.onResponse(AcknowledgedResponse(true))
+ } else {
+ getRollupMetadata(rollup, actionListener)
+ }
}
- }
- updateRollupJob(rollup, request, actionListener)
- }
+ updateRollupJob(rollup, request, actionListener)
+ }
- override fun onFailure(e: Exception) {
- actionListener.onFailure(ExceptionsHelper.unwrapCause(e) as Exception)
+ override fun onFailure(e: Exception) {
+ actionListener.onFailure(ExceptionsHelper.unwrapCause(e) as Exception)
+ }
}
- }
- )
+ )
+ }
}
// TODO: Should create a transport action to update metadata
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/stop/StopRollupAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/stop/StopRollupAction.kt
index c08d38fd1..506b697bd 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/stop/StopRollupAction.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/stop/StopRollupAction.kt
@@ -32,6 +32,6 @@ import org.opensearch.action.support.master.AcknowledgedResponse
class StopRollupAction private constructor() : ActionType(NAME, ::AcknowledgedResponse) {
companion object {
val INSTANCE = StopRollupAction()
- val NAME = "cluster:admin/opendistro/rollup/stop"
+ const val NAME = "cluster:admin/opendistro/rollup/stop"
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/stop/TransportStopRollupAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/stop/TransportStopRollupAction.kt
index 0f4ec1891..04e6a8fa3 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/stop/TransportStopRollupAction.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/rollup/action/stop/TransportStopRollupAction.kt
@@ -39,21 +39,25 @@ import org.opensearch.action.support.master.AcknowledgedResponse
import org.opensearch.action.update.UpdateRequest
import org.opensearch.action.update.UpdateResponse
import org.opensearch.client.Client
+import org.opensearch.cluster.service.ClusterService
import org.opensearch.common.inject.Inject
+import org.opensearch.common.settings.Settings
import org.opensearch.common.xcontent.LoggingDeprecationHandler
import org.opensearch.common.xcontent.NamedXContentRegistry
import org.opensearch.common.xcontent.XContentHelper
import org.opensearch.common.xcontent.XContentType
import org.opensearch.indexmanagement.IndexManagementPlugin
import org.opensearch.indexmanagement.opensearchapi.parseWithType
-import org.opensearch.indexmanagement.rollup.action.get.GetRollupAction
-import org.opensearch.indexmanagement.rollup.action.get.GetRollupRequest
-import org.opensearch.indexmanagement.rollup.action.get.GetRollupResponse
import org.opensearch.indexmanagement.rollup.model.Rollup
import org.opensearch.indexmanagement.rollup.model.RollupMetadata
+import org.opensearch.indexmanagement.rollup.util.parseRollup
+import org.opensearch.indexmanagement.settings.IndexManagementSettings
+import org.opensearch.indexmanagement.util.SecurityUtils.Companion.buildUser
+import org.opensearch.indexmanagement.util.SecurityUtils.Companion.userHasPermissionForResource
import org.opensearch.rest.RestStatus
import org.opensearch.tasks.Task
import org.opensearch.transport.TransportService
+import java.lang.IllegalArgumentException
import java.time.Instant
/**
@@ -72,39 +76,62 @@ import java.time.Instant
class TransportStopRollupAction @Inject constructor(
transportService: TransportService,
val client: Client,
- actionFilters: ActionFilters
+ val clusterService: ClusterService,
+ val settings: Settings,
+ actionFilters: ActionFilters,
+ val xContentRegistry: NamedXContentRegistry
) : HandledTransportAction(
StopRollupAction.NAME, transportService, actionFilters, ::StopRollupRequest
) {
+ @Volatile private var filterByEnabled = IndexManagementSettings.FILTER_BY_BACKEND_ROLES.get(settings)
+
+ init {
+ clusterService.clusterSettings.addSettingsUpdateConsumer(IndexManagementSettings.FILTER_BY_BACKEND_ROLES) {
+ filterByEnabled = it
+ }
+ }
+
private val log = LogManager.getLogger(javaClass)
+ @Suppress("ReturnCount")
override fun doExecute(task: Task, request: StopRollupRequest, actionListener: ActionListener) {
log.debug("Executing StopRollupAction on ${request.id()}")
- val getReq = GetRollupRequest(request.id(), null)
- client.execute(
- GetRollupAction.INSTANCE, getReq,
- object : ActionListener {
- override fun onResponse(response: GetRollupResponse) {
- val rollup = response.rollup
- if (rollup == null) {
- return actionListener.onFailure(
- OpenSearchStatusException("Could not find rollup [${request.id()}]", RestStatus.NOT_FOUND)
- )
- }
+ val getRequest = GetRequest(IndexManagementPlugin.INDEX_MANAGEMENT_INDEX, request.id())
+ val user = buildUser(client.threadPool().threadContext)
+ client.threadPool().threadContext.stashContext().use {
+ client.get(
+ getRequest,
+ object : ActionListener {
+ override fun onResponse(response: GetResponse) {
+ if (!response.isExists) {
+ actionListener.onFailure(OpenSearchStatusException("Rollup not found", RestStatus.NOT_FOUND))
+ return
+ }
- if (rollup.metadataID != null) {
- getRollupMetadata(rollup, request, actionListener)
- } else {
- updateRollupJob(rollup, request, actionListener)
+ val rollup: Rollup?
+ try {
+ rollup = parseRollup(response, xContentRegistry)
+ } catch (e: IllegalArgumentException) {
+ actionListener.onFailure(OpenSearchStatusException("Rollup not found", RestStatus.NOT_FOUND))
+ return
+ }
+ if (!userHasPermissionForResource(user, rollup.user, filterByEnabled, "rollup", rollup.id, actionListener)) {
+ return
+ }
+ if (rollup.metadataID != null) {
+ getRollupMetadata(rollup, request, actionListener)
+ } else {
+ updateRollupJob(rollup, request, actionListener)
+ }
}
- }
- override fun onFailure(e: Exception) {
- actionListener.onFailure(ExceptionsHelper.unwrapCause(e) as Exception)
+ override fun onFailure(e: Exception) {
+ actionListener.onFailure(ExceptionsHelper.unwrapCause(e) as Exception)
+ }
}
- }
- )
+ )
+ }
}
private fun getRollupMetadata(rollup: Rollup, request: StopRollupRequest, actionListener: ActionListener) {
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/rollup/interceptor/RollupInterceptor.kt b/src/main/kotlin/org/opensearch/indexmanagement/rollup/interceptor/RollupInterceptor.kt
index 9d8f8585a..4cd68c1b4 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/rollup/interceptor/RollupInterceptor.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/rollup/interceptor/RollupInterceptor.kt
@@ -77,11 +77,15 @@ class RollupInterceptor(
private val logger = LogManager.getLogger(javaClass)
@Volatile private var searchEnabled = RollupSettings.ROLLUP_SEARCH_ENABLED.get(settings)
+ @Volatile private var searchAllJobs = RollupSettings.ROLLUP_SEARCH_ALL_JOBS.get(settings)
init {
clusterService.clusterSettings.addSettingsUpdateConsumer(RollupSettings.ROLLUP_SEARCH_ENABLED) {
searchEnabled = it
}
+ clusterService.clusterSettings.addSettingsUpdateConsumer(RollupSettings.ROLLUP_SEARCH_ALL_JOBS) {
+ searchAllJobs = it
+ }
}
@Suppress("SpreadOperator")
@@ -126,12 +130,9 @@ class RollupInterceptor(
throw IllegalArgumentException("Could not find a rollup job that can answer this query because $issues")
}
- val matchedRollup = pickRollupJob(matchingRollupJobs.keys)
- val fieldNameMappingTypeMap = matchingRollupJobs.getValue(matchedRollup).associateBy({ it.fieldName }, { it.mappingType })
-
// only rebuild if there is necessity to rebuild
if (fieldMappings.isNotEmpty()) {
- request.source(request.source().rewriteSearchSourceBuilder(matchedRollup, fieldNameMappingTypeMap))
+ rewriteShardSearchForRollupJobs(request, matchingRollupJobs)
}
}
}
@@ -298,4 +299,14 @@ class RollupInterceptor(
DateHistogramInterval(rollup.getDateHistogram().fixedInterval).estimateMillis()
}
}
+
+ private fun rewriteShardSearchForRollupJobs(request: ShardSearchRequest, matchingRollupJobs: Map>) {
+ val matchedRollup = pickRollupJob(matchingRollupJobs.keys)
+ val fieldNameMappingTypeMap = matchingRollupJobs.getValue(matchedRollup).associateBy({ it.fieldName }, { it.mappingType })
+ if (searchAllJobs) {
+ request.source(request.source().rewriteSearchSourceBuilder(matchingRollupJobs.keys, fieldNameMappingTypeMap))
+ } else {
+ request.source(request.source().rewriteSearchSourceBuilder(matchedRollup, fieldNameMappingTypeMap))
+ }
+ }
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/rollup/model/ISMRollup.kt b/src/main/kotlin/org/opensearch/indexmanagement/rollup/model/ISMRollup.kt
index 2203bba54..f9e7d1539 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/rollup/model/ISMRollup.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/rollup/model/ISMRollup.kt
@@ -34,6 +34,7 @@ import org.opensearch.common.xcontent.ToXContentObject
import org.opensearch.common.xcontent.XContentBuilder
import org.opensearch.common.xcontent.XContentParser
import org.opensearch.common.xcontent.XContentParserUtils
+import org.opensearch.commons.authuser.User
import org.opensearch.index.seqno.SequenceNumbers
import org.opensearch.indexmanagement.common.model.dimension.DateHistogram
import org.opensearch.indexmanagement.common.model.dimension.Dimension
@@ -79,7 +80,7 @@ data class ISMRollup(
return builder
}
- fun toRollup(sourceIndex: String, roles: List = listOf()): Rollup {
+ fun toRollup(sourceIndex: String, user: User? = null): Rollup {
val id = sourceIndex + toString()
val currentTime = Instant.now()
return Rollup(
@@ -95,12 +96,12 @@ data class ISMRollup(
sourceIndex = sourceIndex,
targetIndex = this.targetIndex,
metadataID = null,
- roles = roles,
pageSize = pageSize,
delay = null,
continuous = false,
dimensions = dimensions,
- metrics = metrics
+ metrics = metrics,
+ user = user
)
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/rollup/model/Rollup.kt b/src/main/kotlin/org/opensearch/indexmanagement/rollup/model/Rollup.kt
index 09e114365..00ed128f8 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/rollup/model/Rollup.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/rollup/model/Rollup.kt
@@ -34,14 +34,17 @@ import org.opensearch.common.xcontent.XContentBuilder
import org.opensearch.common.xcontent.XContentParser
import org.opensearch.common.xcontent.XContentParser.Token
import org.opensearch.common.xcontent.XContentParserUtils.ensureExpectedToken
+import org.opensearch.commons.authuser.User
import org.opensearch.index.seqno.SequenceNumbers
import org.opensearch.indexmanagement.common.model.dimension.DateHistogram
import org.opensearch.indexmanagement.common.model.dimension.Dimension
import org.opensearch.indexmanagement.common.model.dimension.Histogram
import org.opensearch.indexmanagement.common.model.dimension.Terms
import org.opensearch.indexmanagement.indexstatemanagement.util.WITH_TYPE
+import org.opensearch.indexmanagement.indexstatemanagement.util.WITH_USER
import org.opensearch.indexmanagement.opensearchapi.instant
import org.opensearch.indexmanagement.opensearchapi.optionalTimeField
+import org.opensearch.indexmanagement.opensearchapi.optionalUserField
import org.opensearch.indexmanagement.util.IndexUtils
import org.opensearch.indexmanagement.util._ID
import org.opensearch.jobscheduler.spi.ScheduledJobParameter
@@ -58,19 +61,20 @@ data class Rollup(
val primaryTerm: Long = SequenceNumbers.UNASSIGNED_PRIMARY_TERM,
val enabled: Boolean,
val schemaVersion: Long,
- val jobSchedule: Schedule,
+ var jobSchedule: Schedule,
val jobLastUpdatedTime: Instant,
val jobEnabledTime: Instant?,
val description: String,
val sourceIndex: String,
val targetIndex: String,
val metadataID: String?,
- val roles: List,
+ @Deprecated("Will be ignored, to check the roles use user field") val roles: List = listOf(),
val pageSize: Int,
val delay: Long?,
val continuous: Boolean,
val dimensions: List,
- val metrics: List
+ val metrics: List,
+ val user: User? = null
) : ScheduledJobParameter, Writeable {
init {
@@ -79,12 +83,26 @@ data class Rollup(
} else {
require(jobEnabledTime == null) { "Job enabled time must not be present if the job is disabled" }
}
+ // Copy the delay parameter of the job into the job scheduler for continuous jobs only
+ if (jobSchedule.delay != delay && continuous) {
+ jobSchedule = when (jobSchedule) {
+ is CronSchedule -> {
+ val cronSchedule = jobSchedule as CronSchedule
+ CronSchedule(cronSchedule.cronExpression, cronSchedule.timeZone, delay ?: 0)
+ }
+ is IntervalSchedule -> {
+ val intervalSchedule = jobSchedule as IntervalSchedule
+ IntervalSchedule(intervalSchedule.startTime, intervalSchedule.interval, intervalSchedule.unit, delay ?: 0)
+ }
+ else -> jobSchedule
+ }
+ }
when (jobSchedule) {
is CronSchedule -> {
// Job scheduler already correctly throws errors for this
}
is IntervalSchedule -> {
- require(jobSchedule.interval >= MINIMUM_JOB_INTERVAL) { "Rollup job schedule interval must be greater than 0" }
+ require((jobSchedule as IntervalSchedule).interval >= MINIMUM_JOB_INTERVAL) { "Rollup job schedule interval must be greater than 0" }
}
}
require(sourceIndex != targetIndex) { "Your source and target index cannot be the same" }
@@ -93,7 +111,10 @@ data class Rollup(
}
require(dimensions.first().type == Dimension.Type.DATE_HISTOGRAM) { "The first dimension must be a date histogram" }
require(pageSize in MINIMUM_PAGE_SIZE..MAXIMUM_PAGE_SIZE) { "Page size must be between 1 and 10,000" }
- if (delay != null) require(delay >= MINIMUM_DELAY) { "Delay must be non-negative if set" }
+ if (delay != null) {
+ require(delay >= MINIMUM_DELAY) { "Delay must be non-negative if set" }
+ require(delay <= Instant.now().toEpochMilli()) { "Delay must be less than the current unix time" }
+ }
}
override fun isEnabled() = enabled
@@ -146,7 +167,10 @@ data class Rollup(
}
dimensionsList.toList()
},
- metrics = sin.readList(::RollupMetrics)
+ metrics = sin.readList(::RollupMetrics),
+ user = if (sin.readBoolean()) {
+ User(sin)
+ } else null
)
override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder {
@@ -162,12 +186,12 @@ data class Rollup(
.field(SOURCE_INDEX_FIELD, sourceIndex)
.field(TARGET_INDEX_FIELD, targetIndex)
.field(METADATA_ID_FIELD, metadataID)
- .field(ROLES_FIELD, roles.toTypedArray())
.field(PAGE_SIZE_FIELD, pageSize)
.field(DELAY_FIELD, delay)
.field(CONTINUOUS_FIELD, continuous)
.field(DIMENSIONS_FIELD, dimensions.toTypedArray())
.field(RollupMetrics.METRICS_FIELD, metrics.toTypedArray())
+ if (params.paramAsBoolean(WITH_USER, true)) builder.optionalUserField(USER_FIELD, user)
if (params.paramAsBoolean(WITH_TYPE, true)) builder.endObject()
builder.endObject()
return builder
@@ -205,6 +229,8 @@ data class Rollup(
}
}
out.writeCollection(metrics)
+ out.writeBoolean(user != null)
+ user?.writeTo(out)
}
companion object {
@@ -238,6 +264,7 @@ data class Rollup(
const val ROLLUP_DOC_ID_FIELD = "$ROLLUP_TYPE.$_ID"
const val ROLLUP_DOC_COUNT_FIELD = "$ROLLUP_TYPE._doc_count"
const val ROLLUP_DOC_SCHEMA_VERSION_FIELD = "$ROLLUP_TYPE._$SCHEMA_VERSION_FIELD"
+ const val USER_FIELD = "user"
@Suppress("ComplexMethod", "LongMethod", "NestedBlockDepth")
@JvmStatic
@@ -258,12 +285,12 @@ data class Rollup(
var sourceIndex: String? = null
var targetIndex: String? = null
var metadataID: String? = null
- val roles = mutableListOf()
var pageSize: Int? = null
var delay: Long? = null
var continuous = false
val dimensions = mutableListOf()
val metrics = mutableListOf()
+ var user: User? = null
ensureExpectedToken(Token.START_OBJECT, xcp.currentToken(), xcp)
@@ -283,9 +310,10 @@ data class Rollup(
TARGET_INDEX_FIELD -> targetIndex = xcp.text()
METADATA_ID_FIELD -> metadataID = xcp.textOrNull()
ROLES_FIELD -> {
+ // Parsing but not storing the field, deprecated
ensureExpectedToken(Token.START_ARRAY, xcp.currentToken(), xcp)
while (xcp.nextToken() != Token.END_ARRAY) {
- roles.add(xcp.text())
+ xcp.text()
}
}
PAGE_SIZE_FIELD -> pageSize = xcp.intValue()
@@ -303,6 +331,9 @@ data class Rollup(
metrics.add(RollupMetrics.parse(xcp))
}
}
+ USER_FIELD -> {
+ user = if (xcp.currentToken() == Token.VALUE_NULL) null else User.parse(xcp)
+ }
else -> throw IllegalArgumentException("Invalid field [$fieldName] found in Rollup.")
}
}
@@ -317,7 +348,7 @@ data class Rollup(
// TODO: Make startTime public in Job Scheduler so we can just directly check the value
if (seqNo == SequenceNumbers.UNASSIGNED_SEQ_NO || primaryTerm == SequenceNumbers.UNASSIGNED_PRIMARY_TERM) {
if (schedule is IntervalSchedule) {
- schedule = IntervalSchedule(Instant.now(), schedule.interval, schedule.unit)
+ schedule = IntervalSchedule(Instant.now(), schedule.interval, schedule.unit, schedule.delay ?: 0)
}
}
return Rollup(
@@ -333,12 +364,12 @@ data class Rollup(
sourceIndex = requireNotNull(sourceIndex) { "Rollup source index is null" },
targetIndex = requireNotNull(targetIndex) { "Rollup target index is null" },
metadataID = metadataID,
- roles = roles.toList(),
pageSize = requireNotNull(pageSize) { "Rollup page size is null" },
delay = delay,
continuous = continuous,
dimensions = dimensions,
- metrics = metrics
+ metrics = metrics,
+ user = user
)
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/rollup/settings/RollupSettings.kt b/src/main/kotlin/org/opensearch/indexmanagement/rollup/settings/RollupSettings.kt
index c6bd99968..063724df8 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/rollup/settings/RollupSettings.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/rollup/settings/RollupSettings.kt
@@ -33,6 +33,7 @@ class RollupSettings {
companion object {
const val DEFAULT_ROLLUP_ENABLED = true
+ const val DEFAULT_SEARCH_ALL_JOBS = false
const val DEFAULT_ACQUIRE_LOCK_RETRY_COUNT = 3
const val DEFAULT_ACQUIRE_LOCK_RETRY_DELAY = 1000L
const val DEFAULT_RENEW_LOCK_RETRY_COUNT = 3
@@ -89,6 +90,13 @@ class RollupSettings {
Setting.Property.Dynamic
)
+ val ROLLUP_SEARCH_ALL_JOBS: Setting = Setting.boolSetting(
+ "plugins.rollup.search.search_all_jobs",
+ DEFAULT_SEARCH_ALL_JOBS,
+ Setting.Property.NodeScope,
+ Setting.Property.Dynamic
+ )
+
val ROLLUP_DASHBOARDS: Setting = Setting.boolSetting(
"plugins.rollup.dashboards.enabled",
LegacyOpenDistroRollupSettings.ROLLUP_DASHBOARDS,
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/rollup/util/RollupUtils.kt b/src/main/kotlin/org/opensearch/indexmanagement/rollup/util/RollupUtils.kt
index ab799c90e..a1bcf2f4e 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/rollup/util/RollupUtils.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/rollup/util/RollupUtils.kt
@@ -28,6 +28,7 @@
package org.opensearch.indexmanagement.rollup.util
+import org.opensearch.action.get.GetResponse
import org.opensearch.action.search.SearchRequest
import org.opensearch.cluster.ClusterState
import org.opensearch.cluster.metadata.IndexMetadata
@@ -51,6 +52,7 @@ import org.opensearch.indexmanagement.common.model.dimension.DateHistogram
import org.opensearch.indexmanagement.common.model.dimension.Dimension
import org.opensearch.indexmanagement.common.model.dimension.Histogram
import org.opensearch.indexmanagement.common.model.dimension.Terms
+import org.opensearch.indexmanagement.opensearchapi.parseWithType
import org.opensearch.indexmanagement.rollup.RollupMapperService
import org.opensearch.indexmanagement.rollup.model.Rollup
import org.opensearch.indexmanagement.rollup.model.RollupFieldMapping
@@ -382,10 +384,11 @@ fun Rollup.rewriteQueryBuilder(queryBuilder: QueryBuilder, fieldNameMappingTypeM
}
}
-fun Rollup.buildRollupQuery(fieldNameMappingTypeMap: Map, oldQuery: QueryBuilder): QueryBuilder {
+fun Set.buildRollupQuery(fieldNameMappingTypeMap: Map, oldQuery: QueryBuilder): QueryBuilder {
val wrappedQueryBuilder = BoolQueryBuilder()
- wrappedQueryBuilder.must(this.rewriteQueryBuilder(oldQuery, fieldNameMappingTypeMap))
- wrappedQueryBuilder.filter(TermQueryBuilder("rollup._id", this.id))
+ wrappedQueryBuilder.must(this.first().rewriteQueryBuilder(oldQuery, fieldNameMappingTypeMap))
+ wrappedQueryBuilder.should(TermsQueryBuilder("rollup._id", this.map { it.id }))
+ wrappedQueryBuilder.minimumShouldMatch(1)
return wrappedQueryBuilder
}
@@ -405,9 +408,10 @@ fun Rollup.populateFieldMappings(): Set {
// TODO: Not a fan of this.. but I can't find a way to overwrite the aggregations on the shallow copy or original
// so we need to instantiate a new one so we can add the rewritten aggregation builders
@Suppress("ComplexMethod")
-fun SearchSourceBuilder.rewriteSearchSourceBuilder(job: Rollup, fieldNameMappingTypeMap: Map): SearchSourceBuilder {
+fun SearchSourceBuilder.rewriteSearchSourceBuilder(jobs: Set, fieldNameMappingTypeMap: Map): SearchSourceBuilder {
val ssb = SearchSourceBuilder()
- this.aggregations()?.aggregatorFactories?.forEach { ssb.aggregation(job.rewriteAggregationBuilder(it)) }
+ // can use first() here as all jobs in the set will have a superset of the query's terms
+ this.aggregations()?.aggregatorFactories?.forEach { ssb.aggregation(jobs.first().rewriteAggregationBuilder(it)) }
if (this.explain() != null) ssb.explain(this.explain())
if (this.ext() != null) ssb.ext(this.ext())
ssb.fetchSource(this.fetchSource())
@@ -419,7 +423,7 @@ fun SearchSourceBuilder.rewriteSearchSourceBuilder(job: Rollup, fieldNameMapping
if (this.minScore() != null) ssb.minScore(this.minScore())
if (this.postFilter() != null) ssb.postFilter(this.postFilter())
ssb.profile(this.profile())
- if (this.query() != null) ssb.query(job.buildRollupQuery(fieldNameMappingTypeMap, this.query()))
+ if (this.query() != null) ssb.query(jobs.buildRollupQuery(fieldNameMappingTypeMap, this.query()))
this.rescores()?.forEach { ssb.addRescorer(it) }
this.scriptFields()?.forEach { ssb.scriptField(it.fieldName(), it.script(), it.ignoreFailure()) }
if (this.searchAfter() != null) ssb.searchAfter(this.searchAfter())
@@ -438,9 +442,22 @@ fun SearchSourceBuilder.rewriteSearchSourceBuilder(job: Rollup, fieldNameMapping
return ssb
}
+fun SearchSourceBuilder.rewriteSearchSourceBuilder(job: Rollup, fieldNameMappingTypeMap: Map): SearchSourceBuilder {
+ return this.rewriteSearchSourceBuilder(setOf(job), fieldNameMappingTypeMap)
+}
+
fun Rollup.getInitialDocValues(docCount: Long): MutableMap =
mutableMapOf(
Rollup.ROLLUP_DOC_ID_FIELD to this.id,
Rollup.ROLLUP_DOC_COUNT_FIELD to docCount,
Rollup.ROLLUP_DOC_SCHEMA_VERSION_FIELD to this.schemaVersion
)
+
+fun parseRollup(response: GetResponse, xContentRegistry: NamedXContentRegistry = NamedXContentRegistry.EMPTY): Rollup {
+ val xcp = XContentHelper.createParser(
+ xContentRegistry, LoggingDeprecationHandler.INSTANCE,
+ response.sourceAsBytesRef, XContentType.JSON
+ )
+
+ return xcp.parseWithType(response.id, response.seqNo, response.primaryTerm, Rollup.Companion::parse)
+}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/settings/IndexManagementSettings.kt b/src/main/kotlin/org/opensearch/indexmanagement/settings/IndexManagementSettings.kt
new file mode 100644
index 000000000..8d5775582
--- /dev/null
+++ b/src/main/kotlin/org/opensearch/indexmanagement/settings/IndexManagementSettings.kt
@@ -0,0 +1,26 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * The OpenSearch Contributors require contributions made to
+ * this file be licensed under the Apache-2.0 license or a
+ * compatible open source license.
+ *
+ * Modifications Copyright OpenSearch Contributors. See
+ * GitHub history for details.
+ */
+package org.opensearch.indexmanagement.settings
+
+import org.opensearch.common.settings.Setting
+
+class IndexManagementSettings {
+
+ companion object {
+
+ val FILTER_BY_BACKEND_ROLES: Setting = Setting.boolSetting(
+ "plugins.index_management.filter_by_backend_roles",
+ false,
+ Setting.Property.NodeScope,
+ Setting.Property.Dynamic
+ )
+ }
+}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/transform/TransformIndexer.kt b/src/main/kotlin/org/opensearch/indexmanagement/transform/TransformIndexer.kt
index 239af8a94..0b702865f 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/transform/TransformIndexer.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/transform/TransformIndexer.kt
@@ -13,10 +13,12 @@ package org.opensearch.indexmanagement.transform
import org.apache.logging.log4j.LogManager
import org.opensearch.ExceptionsHelper
+import org.opensearch.OpenSearchSecurityException
import org.opensearch.action.DocWriteRequest
import org.opensearch.action.admin.indices.create.CreateIndexRequest
import org.opensearch.action.admin.indices.create.CreateIndexResponse
import org.opensearch.action.bulk.BackoffPolicy
+import org.opensearch.action.bulk.BulkItemResponse
import org.opensearch.action.bulk.BulkRequest
import org.opensearch.action.bulk.BulkResponse
import org.opensearch.action.index.IndexRequest
@@ -33,6 +35,7 @@ import org.opensearch.indexmanagement.util._DOC
import org.opensearch.rest.RestStatus
import org.opensearch.transport.RemoteTransportException
+@Suppress("ComplexMethod")
class TransformIndexer(
settings: Settings,
private val clusterService: ClusterService,
@@ -65,7 +68,7 @@ class TransformIndexer(
val response: CreateIndexResponse = client.admin().indices().suspendUntil { create(request, it) }
if (!response.isAcknowledged) {
logger.error("Failed to create the target index $index")
- throw Exception()
+ throw TransformIndexException("Failed to create the target index")
}
}
}
@@ -74,6 +77,7 @@ class TransformIndexer(
suspend fun index(docsToIndex: List>): Long {
var updatableDocsToIndex = docsToIndex
var indexTimeInMillis = 0L
+ var nonRetryableFailures = mutableListOf()
try {
if (updatableDocsToIndex.isNotEmpty()) {
val targetIndex = updatableDocsToIndex.first().index()
@@ -83,22 +87,34 @@ class TransformIndexer(
val bulkRequest = BulkRequest().add(updatableDocsToIndex)
val bulkResponse: BulkResponse = client.suspendUntil { bulk(bulkRequest, it) }
indexTimeInMillis += bulkResponse.took.millis
-
- val failed = (bulkResponse.items ?: arrayOf()).filter { item -> item.isFailed }
-
- updatableDocsToIndex = failed.map { itemResponse ->
- updatableDocsToIndex[itemResponse.itemId] as IndexRequest
+ val retryableFailures = mutableListOf()
+ (bulkResponse.items ?: arrayOf()).filter { it.isFailed }.forEach { failedResponse ->
+ if (failedResponse.status() == RestStatus.TOO_MANY_REQUESTS) {
+ retryableFailures.add(failedResponse)
+ } else {
+ nonRetryableFailures.add(failedResponse)
+ }
+ }
+ updatableDocsToIndex = retryableFailures.map { failure ->
+ updatableDocsToIndex[failure.itemId] as IndexRequest
}
if (updatableDocsToIndex.isNotEmpty()) {
- val retryCause = failed.first().failure.cause
- throw ExceptionsHelper.convertToOpenSearchException(retryCause)
+ throw ExceptionsHelper.convertToOpenSearchException(retryableFailures.first().failure.cause)
}
}
}
+ if (nonRetryableFailures.isNotEmpty()) {
+ logger.error("Failed to index ${nonRetryableFailures.size} documents")
+ throw ExceptionsHelper.convertToOpenSearchException(nonRetryableFailures.first().failure.cause)
+ }
return indexTimeInMillis
+ } catch (e: TransformIndexException) {
+ throw e
} catch (e: RemoteTransportException) {
val unwrappedException = ExceptionsHelper.unwrapCause(e) as Exception
throw TransformIndexException("Failed to index the documents", unwrappedException)
+ } catch (e: OpenSearchSecurityException) {
+ throw TransformIndexException("Failed to index the documents - missing required index permissions: ${e.localizedMessage}", e)
} catch (e: Exception) {
throw TransformIndexException("Failed to index the documents", e)
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/transform/TransformRunner.kt b/src/main/kotlin/org/opensearch/indexmanagement/transform/TransformRunner.kt
index f54fce14f..f4ba7f400 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/transform/TransformRunner.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/transform/TransformRunner.kt
@@ -25,7 +25,9 @@ import org.opensearch.cluster.service.ClusterService
import org.opensearch.common.settings.Settings
import org.opensearch.common.unit.TimeValue
import org.opensearch.common.xcontent.NamedXContentRegistry
+import org.opensearch.indexmanagement.opensearchapi.IndexManagementSecurityContext
import org.opensearch.indexmanagement.opensearchapi.suspendUntil
+import org.opensearch.indexmanagement.opensearchapi.withClosableContext
import org.opensearch.indexmanagement.transform.action.index.IndexTransformAction
import org.opensearch.indexmanagement.transform.action.index.IndexTransformRequest
import org.opensearch.indexmanagement.transform.action.index.IndexTransformResponse
@@ -39,6 +41,7 @@ import org.opensearch.jobscheduler.spi.JobExecutionContext
import org.opensearch.jobscheduler.spi.ScheduledJobParameter
import org.opensearch.jobscheduler.spi.ScheduledJobRunner
import org.opensearch.monitor.jvm.JvmService
+import org.opensearch.threadpool.ThreadPool
import java.time.Instant
@Suppress("LongParameterList")
@@ -56,6 +59,7 @@ object TransformRunner :
private lateinit var transformSearchService: TransformSearchService
private lateinit var transformIndexer: TransformIndexer
private lateinit var transformValidator: TransformValidator
+ private lateinit var threadPool: ThreadPool
fun initialize(
client: Client,
@@ -63,7 +67,8 @@ object TransformRunner :
xContentRegistry: NamedXContentRegistry,
settings: Settings,
indexNameExpressionResolver: IndexNameExpressionResolver,
- jvmService: JvmService
+ jvmService: JvmService,
+ threadPool: ThreadPool
): TransformRunner {
this.clusterService = clusterService
this.client = client
@@ -73,6 +78,7 @@ object TransformRunner :
this.transformMetadataService = TransformMetadataService(client, xContentRegistry)
this.transformIndexer = TransformIndexer(settings, clusterService, client)
this.transformValidator = TransformValidator(indexNameExpressionResolver, clusterService, client, settings, jvmService)
+ this.threadPool = threadPool
return this
}
@@ -131,7 +137,7 @@ object TransformRunner :
}
} while (currentMetadata.afterKey != null)
} catch (e: Exception) {
- logger.error("Failed to execute the transform job because of exception [${e.localizedMessage}]", e)
+ logger.error("Failed to execute the transform job [${transform.id}] because of exception [${e.localizedMessage}]", e)
currentMetadata = currentMetadata.copy(
lastUpdatedAt = Instant.now(),
status = TransformMetadata.Status.FAILED,
@@ -148,10 +154,22 @@ object TransformRunner :
}
private suspend fun executeJobIteration(transform: Transform, metadata: TransformMetadata): TransformMetadata {
- val validationResult = transformValidator.validate(transform)
+ val validationResult = withClosableContext(
+ IndexManagementSecurityContext(transform.id, settings, threadPool.threadContext, transform.user)
+ ) {
+ transformValidator.validate(transform)
+ }
if (validationResult.isValid) {
- val transformSearchResult = transformSearchService.executeCompositeSearch(transform, metadata.afterKey)
- val indexTimeInMillis = transformIndexer.index(transformSearchResult.docsToIndex)
+ val transformSearchResult = withClosableContext(
+ IndexManagementSecurityContext(transform.id, settings, threadPool.threadContext, transform.user)
+ ) {
+ transformSearchService.executeCompositeSearch(transform, metadata.afterKey)
+ }
+ val indexTimeInMillis = withClosableContext(
+ IndexManagementSecurityContext(transform.id, settings, threadPool.threadContext, transform.user)
+ ) {
+ transformIndexer.index(transformSearchResult.docsToIndex)
+ }
val afterKey = transformSearchResult.afterKey
val stats = transformSearchResult.stats
val updatedStats = stats.copy(
@@ -175,10 +193,16 @@ object TransformRunner :
transform = transform.copy(updatedAt = Instant.now()),
refreshPolicy = WriteRequest.RefreshPolicy.IMMEDIATE
)
- val response: IndexTransformResponse = client.suspendUntil { execute(IndexTransformAction.INSTANCE, request, it) }
- return transform.copy(
- seqNo = response.seqNo,
- primaryTerm = response.primaryTerm
- )
+ return withClosableContext(
+ IndexManagementSecurityContext(transform.id, settings, threadPool.threadContext, null)
+ ) {
+ val response: IndexTransformResponse = client.suspendUntil {
+ execute(IndexTransformAction.INSTANCE, request, it)
+ }
+ return@withClosableContext transform.copy(
+ seqNo = response.seqNo,
+ primaryTerm = response.primaryTerm
+ )
+ }
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/transform/TransformSearchService.kt b/src/main/kotlin/org/opensearch/indexmanagement/transform/TransformSearchService.kt
index 8b054845a..7145e3f0d 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/transform/TransformSearchService.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/transform/TransformSearchService.kt
@@ -13,6 +13,7 @@ package org.opensearch.indexmanagement.transform
import org.apache.logging.log4j.LogManager
import org.opensearch.ExceptionsHelper
+import org.opensearch.OpenSearchSecurityException
import org.opensearch.action.ActionListener
import org.opensearch.action.bulk.BackoffPolicy
import org.opensearch.action.index.IndexRequest
@@ -70,7 +71,7 @@ class TransformSearchService(
}
suspend fun executeCompositeSearch(transform: Transform, afterKey: Map? = null): TransformSearchResult {
- val errorMessage = "Failed to search data in source indices in transform job ${transform.id}"
+ val errorMessage = "Failed to search data in source indices"
try {
var retryAttempt = 0
val searchResponse = backoffPolicy.retry(logger) {
@@ -90,14 +91,13 @@ class TransformSearchService(
}
return convertResponse(transform, searchResponse)
} catch (e: TransformSearchServiceException) {
- logger.error(errorMessage)
throw e
} catch (e: RemoteTransportException) {
val unwrappedException = ExceptionsHelper.unwrapCause(e) as Exception
- logger.error(errorMessage, unwrappedException)
throw TransformSearchServiceException(errorMessage, unwrappedException)
+ } catch (e: OpenSearchSecurityException) {
+ throw TransformSearchServiceException("$errorMessage - missing required index permissions: ${e.localizedMessage}", e)
} catch (e: Exception) {
- logger.error(errorMessage, e)
throw TransformSearchServiceException(errorMessage, e)
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/transform/TransformValidator.kt b/src/main/kotlin/org/opensearch/indexmanagement/transform/TransformValidator.kt
index 99da72619..24b9bc728 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/transform/TransformValidator.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/transform/TransformValidator.kt
@@ -11,8 +11,8 @@
package org.opensearch.indexmanagement.transform
-import org.apache.logging.log4j.LogManager
import org.opensearch.ExceptionsHelper
+import org.opensearch.OpenSearchSecurityException
import org.opensearch.action.admin.cluster.health.ClusterHealthAction
import org.opensearch.action.admin.cluster.health.ClusterHealthRequest
import org.opensearch.action.admin.cluster.health.ClusterHealthResponse
@@ -41,8 +41,6 @@ class TransformValidator(
private val jvmService: JvmService
) {
- private val logger = LogManager.getLogger(javaClass)
-
@Volatile private var circuitBreakerEnabled = TransformSettings.TRANSFORM_CIRCUIT_BREAKER_ENABLED.get(settings)
@Volatile private var circuitBreakerJvmThreshold = TransformSettings.TRANSFORM_CIRCUIT_BREAKER_JVM_THRESHOLD.get(settings)
@@ -86,8 +84,9 @@ class TransformValidator(
} catch (e: RemoteTransportException) {
val unwrappedException = ExceptionsHelper.unwrapCause(e) as Exception
throw TransformValidationException(errorMessage, unwrappedException)
+ } catch (e: OpenSearchSecurityException) {
+ throw TransformValidationException("$errorMessage - missing required index permissions: ${e.localizedMessage}")
} catch (e: Exception) {
- logger.error("Failed to validate transform [${transform.id}]", e)
throw TransformValidationException(errorMessage, e)
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/transform/action/delete/DeleteTransformsAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/transform/action/delete/DeleteTransformsAction.kt
index 8671c3e39..3d2f4a50b 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/transform/action/delete/DeleteTransformsAction.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/transform/action/delete/DeleteTransformsAction.kt
@@ -17,6 +17,6 @@ import org.opensearch.action.bulk.BulkResponse
class DeleteTransformsAction private constructor() : ActionType(NAME, ::BulkResponse) {
companion object {
val INSTANCE = DeleteTransformsAction()
- val NAME = "cluster:admin/opendistro/transform/delete"
+ const val NAME = "cluster:admin/opendistro/transform/delete"
}
}
diff --git a/src/main/kotlin/org/opensearch/indexmanagement/transform/action/delete/TransportDeleteTransformsAction.kt b/src/main/kotlin/org/opensearch/indexmanagement/transform/action/delete/TransportDeleteTransformsAction.kt
index 4d6fa1904..73a6dd326 100644
--- a/src/main/kotlin/org/opensearch/indexmanagement/transform/action/delete/TransportDeleteTransformsAction.kt
+++ b/src/main/kotlin/org/opensearch/indexmanagement/transform/action/delete/TransportDeleteTransformsAction.kt
@@ -11,6 +11,7 @@
package org.opensearch.indexmanagement.transform.action.delete
+import org.apache.logging.log4j.LogManager
import org.opensearch.OpenSearchStatusException
import org.opensearch.action.ActionListener
import org.opensearch.action.bulk.BulkRequest
@@ -22,110 +23,157 @@ import org.opensearch.action.support.ActionFilters
import org.opensearch.action.support.HandledTransportAction
import org.opensearch.action.support.WriteRequest
import org.opensearch.client.Client
+import org.opensearch.cluster.service.ClusterService
import org.opensearch.common.inject.Inject
+import org.opensearch.common.settings.Settings
+import org.opensearch.common.xcontent.NamedXContentRegistry
+import org.opensearch.commons.authuser.User
import org.opensearch.indexmanagement.IndexManagementPlugin.Companion.INDEX_MANAGEMENT_INDEX
+import org.opensearch.indexmanagement.opensearchapi.parseFromGetResponse
+import org.opensearch.indexmanagement.settings.IndexManagementSettings
import org.opensearch.indexmanagement.transform.model.Transform
+import org.opensearch.indexmanagement.util.SecurityUtils.Companion.buildUser
+import org.opensearch.indexmanagement.util.SecurityUtils.Companion.userHasPermissionForResource
import org.opensearch.rest.RestStatus
import org.opensearch.search.fetch.subphase.FetchSourceContext
import org.opensearch.tasks.Task
import org.opensearch.transport.TransportService
+@Suppress("ReturnCount")
class TransportDeleteTransformsAction @Inject constructor(
transportService: TransportService,
val client: Client,
+ val settings: Settings,
+ val clusterService: ClusterService,
+ val xContentRegistry: NamedXContentRegistry,
actionFilters: ActionFilters
) : HandledTransportAction(
DeleteTransformsAction.NAME, transportService, actionFilters, ::DeleteTransformsRequest
) {
+ private val log = LogManager.getLogger(javaClass)
+ @Volatile private var filterByEnabled = IndexManagementSettings.FILTER_BY_BACKEND_ROLES.get(settings)
+
+ init {
+ clusterService.clusterSettings.addSettingsUpdateConsumer(IndexManagementSettings.FILTER_BY_BACKEND_ROLES) {
+ filterByEnabled = it
+ }
+ }
+
override fun doExecute(task: Task, request: DeleteTransformsRequest, actionListener: ActionListener) {
// TODO: if metadata id exists delete the metadata doc else just delete transform
- // Use Multi-Get Request
- val getRequest = MultiGetRequest()
- val includes = arrayOf(
- "${Transform.TRANSFORM_TYPE}.${Transform.ENABLED_FIELD}"
- )
- val fetchSourceContext = FetchSourceContext(true, includes, emptyArray())
- request.ids.forEach { id ->
- getRequest.add(MultiGetRequest.Item(INDEX_MANAGEMENT_INDEX, id).fetchSourceContext(fetchSourceContext))
+ DeleteTransformHandler(client, request, actionListener).start()
+ }
+
+ inner class DeleteTransformHandler(
+ val client: Client,
+ val request: DeleteTransformsRequest,
+ val actionListener: ActionListener,
+ val user: User? = buildUser(client.threadPool().threadContext)
+ ) {
+
+ fun start() {
+ // Use Multi-Get Request
+ val getRequest = MultiGetRequest()
+ val fetchSourceContext = FetchSourceContext(true)
+ request.ids.forEach { id ->
+ getRequest.add(MultiGetRequest.Item(INDEX_MANAGEMENT_INDEX, id).fetchSourceContext(fetchSourceContext))
+ }
+
+ client.threadPool().threadContext.stashContext().use {
+ client.multiGet(
+ getRequest,
+ object : ActionListener {
+ override fun onResponse(response: MultiGetResponse) {
+ try {
+ // response is failed only if managed index is not present
+ if (response.responses.first().isFailed) {
+ actionListener.onFailure(
+ OpenSearchStatusException(
+ "Cluster missing system index $INDEX_MANAGEMENT_INDEX, cannot execute the request", RestStatus.BAD_REQUEST
+ )
+ )
+ return
+ }
+
+ bulkDelete(response, request.ids, request.force, actionListener)
+ } catch (e: Exception) {
+ actionListener.onFailure(e)
+ }
+ }
+
+ override fun onFailure(e: Exception) = actionListener.onFailure(e)
+ }
+ )
+ }
}
- client.multiGet(
- getRequest,
- object : ActionListener {
- override fun onResponse(response: MultiGetResponse) {
+ private fun bulkDelete(response: MultiGetResponse, ids: List, forceDelete: Boolean, actionListener: ActionListener) {
+ val enabledIDs = mutableListOf()
+ val notTransform = mutableListOf()
+ val noPermission = mutableListOf