Fixed a bug that prevents pipelines to load that use stored scripts after a restart.
The bug was caused because the ScriptService had no reference to a ClusterState instance, because it received the ClusterState after the PipelineStore. This only is the case after a restart. A bad side effect is that during a restart, any pipeline to be loaded after the pipeline that uses a stored script, was never loaded, which caused many pipeline to be missing in bulk / index request api calls.
This commit is contained in:
parent
5735e088f9
commit
766b9d600e
|
@ -0,0 +1,151 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch under one or more contributor
|
||||||
|
* license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright
|
||||||
|
* ownership. Elasticsearch licenses this file to you under
|
||||||
|
* the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
* not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License 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.
|
||||||
|
*/
|
||||||
|
package org.elasticsearch.ingest.common;
|
||||||
|
|
||||||
|
import org.elasticsearch.action.support.WriteRequest;
|
||||||
|
import org.elasticsearch.common.bytes.BytesArray;
|
||||||
|
import org.elasticsearch.common.bytes.BytesReference;
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.common.xcontent.XContentType;
|
||||||
|
import org.elasticsearch.plugins.Plugin;
|
||||||
|
import org.elasticsearch.script.MockScriptEngine;
|
||||||
|
import org.elasticsearch.script.MockScriptPlugin;
|
||||||
|
import org.elasticsearch.test.ESIntegTestCase;
|
||||||
|
import org.elasticsearch.test.InternalTestCluster;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
|
||||||
|
// Ideally I like this test to live in the server module, but otherwise a large part of the ScriptProcessor
|
||||||
|
// ends up being copied into this test.
|
||||||
|
@ESIntegTestCase.ClusterScope(numDataNodes = 0, numClientNodes = 0, scope = ESIntegTestCase.Scope.TEST)
|
||||||
|
public class IngestRestartIT extends ESIntegTestCase {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Collection<Class<? extends Plugin>> nodePlugins() {
|
||||||
|
return Arrays.asList(IngestCommonPlugin.class, CustomScriptPlugin.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean ignoreExternalCluster() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class CustomScriptPlugin extends MockScriptPlugin {
|
||||||
|
@Override
|
||||||
|
protected Map<String, Function<Map<String, Object>, Object>> pluginScripts() {
|
||||||
|
return Collections.singletonMap("my_script", script -> {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> ctx = (Map) script.get("ctx");
|
||||||
|
ctx.put("z", 0);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testPipelineWithScriptProcessorThatHasStoredScript() throws Exception {
|
||||||
|
internalCluster().startNode();
|
||||||
|
|
||||||
|
client().admin().cluster().preparePutStoredScript()
|
||||||
|
.setId("1")
|
||||||
|
.setContent(new BytesArray("{\"script\": {\"lang\": \"" + MockScriptEngine.NAME +
|
||||||
|
"\", \"source\": \"my_script\"} }"), XContentType.JSON)
|
||||||
|
.get();
|
||||||
|
BytesReference pipeline = new BytesArray("{\n" +
|
||||||
|
" \"processors\" : [\n" +
|
||||||
|
" {\"set\" : {\"field\": \"y\", \"value\": 0}},\n" +
|
||||||
|
" {\"script\" : {\"id\": \"1\"}}\n" +
|
||||||
|
" ]\n" +
|
||||||
|
"}");
|
||||||
|
client().admin().cluster().preparePutPipeline("_id", pipeline, XContentType.JSON).get();
|
||||||
|
|
||||||
|
client().prepareIndex("index", "doc", "1")
|
||||||
|
.setSource("x", 0)
|
||||||
|
.setPipeline("_id")
|
||||||
|
.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE)
|
||||||
|
.get();
|
||||||
|
|
||||||
|
Map<String, Object> source = client().prepareGet("index", "doc", "1").get().getSource();
|
||||||
|
assertThat(source.get("x"), equalTo(0));
|
||||||
|
assertThat(source.get("y"), equalTo(0));
|
||||||
|
assertThat(source.get("z"), equalTo(0));
|
||||||
|
|
||||||
|
// Prior to making this ScriptService implement ClusterStateApplier instead of ClusterStateListener,
|
||||||
|
// pipelines with a script processor failed to load causing these pipelines and pipelines that were
|
||||||
|
// supposed to load after these pipelines to not be available during ingestion, which then causes
|
||||||
|
// the next index request in this test to fail.
|
||||||
|
internalCluster().fullRestart();
|
||||||
|
ensureYellow("index");
|
||||||
|
|
||||||
|
client().prepareIndex("index", "doc", "2")
|
||||||
|
.setSource("x", 0)
|
||||||
|
.setPipeline("_id")
|
||||||
|
.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE)
|
||||||
|
.get();
|
||||||
|
|
||||||
|
source = client().prepareGet("index", "doc", "2").get().getSource();
|
||||||
|
assertThat(source.get("x"), equalTo(0));
|
||||||
|
assertThat(source.get("y"), equalTo(0));
|
||||||
|
assertThat(source.get("z"), equalTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testWithDedicatedIngestNode() throws Exception {
|
||||||
|
String node = internalCluster().startNode();
|
||||||
|
String ingestNode = internalCluster().startNode(Settings.builder()
|
||||||
|
.put("node.master", false)
|
||||||
|
.put("node.data", false)
|
||||||
|
);
|
||||||
|
|
||||||
|
BytesReference pipeline = new BytesArray("{\n" +
|
||||||
|
" \"processors\" : [\n" +
|
||||||
|
" {\"set\" : {\"field\": \"y\", \"value\": 0}}\n" +
|
||||||
|
" ]\n" +
|
||||||
|
"}");
|
||||||
|
client().admin().cluster().preparePutPipeline("_id", pipeline, XContentType.JSON).get();
|
||||||
|
|
||||||
|
client().prepareIndex("index", "doc", "1")
|
||||||
|
.setSource("x", 0)
|
||||||
|
.setPipeline("_id")
|
||||||
|
.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE)
|
||||||
|
.get();
|
||||||
|
|
||||||
|
Map<String, Object> source = client().prepareGet("index", "doc", "1").get().getSource();
|
||||||
|
assertThat(source.get("x"), equalTo(0));
|
||||||
|
assertThat(source.get("y"), equalTo(0));
|
||||||
|
|
||||||
|
logger.info("Stopping");
|
||||||
|
internalCluster().restartNode(node, new InternalTestCluster.RestartCallback());
|
||||||
|
|
||||||
|
client(ingestNode).prepareIndex("index", "doc", "2")
|
||||||
|
.setSource("x", 0)
|
||||||
|
.setPipeline("_id")
|
||||||
|
.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE)
|
||||||
|
.get();
|
||||||
|
|
||||||
|
source = client(ingestNode).prepareGet("index", "doc", "2").get().getSource();
|
||||||
|
assertThat(source.get("x"), equalTo(0));
|
||||||
|
assertThat(source.get("y"), equalTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -35,9 +35,9 @@ import org.elasticsearch.cluster.node.DiscoveryNode;
|
||||||
import org.elasticsearch.cluster.service.ClusterService;
|
import org.elasticsearch.cluster.service.ClusterService;
|
||||||
import org.elasticsearch.common.component.AbstractComponent;
|
import org.elasticsearch.common.component.AbstractComponent;
|
||||||
import org.elasticsearch.common.regex.Regex;
|
import org.elasticsearch.common.regex.Regex;
|
||||||
import org.elasticsearch.common.settings.ClusterSettings;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.xcontent.XContentHelper;
|
import org.elasticsearch.common.xcontent.XContentHelper;
|
||||||
|
import org.elasticsearch.gateway.GatewayService;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -70,6 +70,10 @@ public class PipelineStore extends AbstractComponent implements ClusterStateAppl
|
||||||
}
|
}
|
||||||
|
|
||||||
void innerUpdatePipelines(ClusterState previousState, ClusterState state) {
|
void innerUpdatePipelines(ClusterState previousState, ClusterState state) {
|
||||||
|
if (state.blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
IngestMetadata ingestMetadata = state.getMetaData().custom(IngestMetadata.TYPE);
|
IngestMetadata ingestMetadata = state.getMetaData().custom(IngestMetadata.TYPE);
|
||||||
IngestMetadata previousIngestMetadata = previousState.getMetaData().custom(IngestMetadata.TYPE);
|
IngestMetadata previousIngestMetadata = previousState.getMetaData().custom(IngestMetadata.TYPE);
|
||||||
if (Objects.equals(ingestMetadata, previousIngestMetadata)) {
|
if (Objects.equals(ingestMetadata, previousIngestMetadata)) {
|
||||||
|
|
|
@ -342,7 +342,7 @@ public class Node implements Closeable {
|
||||||
List<ClusterPlugin> clusterPlugins = pluginsService.filterPlugins(ClusterPlugin.class);
|
List<ClusterPlugin> clusterPlugins = pluginsService.filterPlugins(ClusterPlugin.class);
|
||||||
final ClusterService clusterService = new ClusterService(settings, settingsModule.getClusterSettings(), threadPool,
|
final ClusterService clusterService = new ClusterService(settings, settingsModule.getClusterSettings(), threadPool,
|
||||||
ClusterModule.getClusterStateCustomSuppliers(clusterPlugins));
|
ClusterModule.getClusterStateCustomSuppliers(clusterPlugins));
|
||||||
clusterService.addListener(scriptModule.getScriptService());
|
clusterService.addStateApplier(scriptModule.getScriptService());
|
||||||
resourcesToClose.add(clusterService);
|
resourcesToClose.add(clusterService);
|
||||||
final IngestService ingestService = new IngestService(settings, threadPool, this.environment,
|
final IngestService ingestService = new IngestService(settings, threadPool, this.environment,
|
||||||
scriptModule.getScriptService(), analysisModule.getAnalysisRegistry(), pluginsService.filterPlugins(IngestPlugin.class));
|
scriptModule.getScriptService(), analysisModule.getAnalysisRegistry(), pluginsService.filterPlugins(IngestPlugin.class));
|
||||||
|
|
|
@ -30,7 +30,7 @@ import org.elasticsearch.action.admin.cluster.storedscripts.PutStoredScriptRespo
|
||||||
import org.elasticsearch.cluster.AckedClusterStateUpdateTask;
|
import org.elasticsearch.cluster.AckedClusterStateUpdateTask;
|
||||||
import org.elasticsearch.cluster.ClusterChangedEvent;
|
import org.elasticsearch.cluster.ClusterChangedEvent;
|
||||||
import org.elasticsearch.cluster.ClusterState;
|
import org.elasticsearch.cluster.ClusterState;
|
||||||
import org.elasticsearch.cluster.ClusterStateListener;
|
import org.elasticsearch.cluster.ClusterStateApplier;
|
||||||
import org.elasticsearch.cluster.metadata.MetaData;
|
import org.elasticsearch.cluster.metadata.MetaData;
|
||||||
import org.elasticsearch.cluster.service.ClusterService;
|
import org.elasticsearch.cluster.service.ClusterService;
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
|
@ -57,7 +57,7 @@ import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
public class ScriptService extends AbstractComponent implements Closeable, ClusterStateListener {
|
public class ScriptService extends AbstractComponent implements Closeable, ClusterStateApplier {
|
||||||
|
|
||||||
static final String DISABLE_DYNAMIC_SCRIPTING_SETTING = "script.disable_dynamic";
|
static final String DISABLE_DYNAMIC_SCRIPTING_SETTING = "script.disable_dynamic";
|
||||||
|
|
||||||
|
@ -508,7 +508,7 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clusterChanged(ClusterChangedEvent event) {
|
public void applyClusterState(ClusterChangedEvent event) {
|
||||||
clusterState = event.state();
|
clusterState = event.state();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue