|
@@ -19,6 +19,9 @@
|
|
|
|
|
|
package org.elasticsearch.ingest.common;
|
|
|
|
|
|
+import org.elasticsearch.common.settings.Settings;
|
|
|
+import org.elasticsearch.common.util.concurrent.EsExecutors;
|
|
|
+import org.elasticsearch.common.util.concurrent.ThreadContext;
|
|
|
import org.elasticsearch.ingest.CompoundProcessor;
|
|
|
import org.elasticsearch.ingest.IngestDocument;
|
|
|
import org.elasticsearch.ingest.Processor;
|
|
@@ -26,6 +29,7 @@ import org.elasticsearch.ingest.TestProcessor;
|
|
|
import org.elasticsearch.ingest.TestTemplateService;
|
|
|
import org.elasticsearch.script.TemplateScript;
|
|
|
import org.elasticsearch.test.ESTestCase;
|
|
|
+import org.junit.Before;
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
import java.util.Arrays;
|
|
@@ -34,32 +38,98 @@ import java.util.HashMap;
|
|
|
import java.util.List;
|
|
|
import java.util.Locale;
|
|
|
import java.util.Map;
|
|
|
+import java.util.concurrent.ExecutorService;
|
|
|
+import java.util.concurrent.ThreadPoolExecutor;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+import java.util.function.BiConsumer;
|
|
|
+import java.util.function.Consumer;
|
|
|
+import java.util.stream.IntStream;
|
|
|
|
|
|
import static org.elasticsearch.ingest.IngestDocumentMatcher.assertIngestDocument;
|
|
|
import static org.hamcrest.Matchers.equalTo;
|
|
|
+import static org.mockito.Matchers.any;
|
|
|
+import static org.mockito.Mockito.doAnswer;
|
|
|
+import static org.mockito.Mockito.mock;
|
|
|
+import static org.mockito.Mockito.times;
|
|
|
+import static org.mockito.Mockito.verify;
|
|
|
+import static org.mockito.Mockito.verifyZeroInteractions;
|
|
|
|
|
|
public class ForEachProcessorTests extends ESTestCase {
|
|
|
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ private Consumer<Runnable> genericExecutor = (Consumer<Runnable>) mock(Consumer.class);
|
|
|
+ private final ExecutorService direct = EsExecutors.newDirectExecutorService();
|
|
|
+
|
|
|
+ @Before
|
|
|
+ public void setup() {
|
|
|
+ //execute runnable on same thread for simplicity. some tests will override this and actually run async
|
|
|
+ doAnswer(invocationOnMock -> {
|
|
|
+ direct.execute((Runnable) invocationOnMock.getArguments()[0]);
|
|
|
+ return null;
|
|
|
+ }).when(genericExecutor).accept(any(Runnable.class));
|
|
|
+ }
|
|
|
+
|
|
|
public void testExecute() throws Exception {
|
|
|
+ ThreadPoolExecutor asyncExecutor =
|
|
|
+ EsExecutors.newScaling(getClass().getName() + "/" + getTestName(), between(1, 2), between(3, 4), 10, TimeUnit.SECONDS,
|
|
|
+ EsExecutors.daemonThreadFactory("test"), new ThreadContext(Settings.EMPTY));
|
|
|
+ doAnswer(invocationOnMock -> {
|
|
|
+ asyncExecutor.execute((Runnable) invocationOnMock.getArguments()[0]);
|
|
|
+ return null;
|
|
|
+ }).when(genericExecutor).accept(any(Runnable.class));
|
|
|
+
|
|
|
List<String> values = new ArrayList<>();
|
|
|
values.add("foo");
|
|
|
values.add("bar");
|
|
|
values.add("baz");
|
|
|
+ IntStream.range(0, ForEachProcessor.MAX_RECURSE_PER_THREAD).forEach(value -> values.add("a"));
|
|
|
IngestDocument ingestDocument = new IngestDocument(
|
|
|
"_index", "_id", null, null, null, Collections.singletonMap("values", values)
|
|
|
);
|
|
|
|
|
|
ForEachProcessor processor = new ForEachProcessor(
|
|
|
"_tag", "values", new UppercaseProcessor("_tag", "_ingest._value", false, "_ingest._value"),
|
|
|
- false
|
|
|
+ false, genericExecutor
|
|
|
);
|
|
|
processor.execute(ingestDocument, (result, e) -> {});
|
|
|
|
|
|
+ assertBusy(() -> assertEquals(values.size() / ForEachProcessor.MAX_RECURSE_PER_THREAD, asyncExecutor.getCompletedTaskCount()));
|
|
|
+ asyncExecutor.shutdown();
|
|
|
+ asyncExecutor.awaitTermination(5, TimeUnit.SECONDS);
|
|
|
+
|
|
|
@SuppressWarnings("unchecked")
|
|
|
List<String> result = ingestDocument.getFieldValue("values", List.class);
|
|
|
assertThat(result.get(0), equalTo("FOO"));
|
|
|
assertThat(result.get(1), equalTo("BAR"));
|
|
|
assertThat(result.get(2), equalTo("BAZ"));
|
|
|
+ IntStream.range(3, ForEachProcessor.MAX_RECURSE_PER_THREAD + 3).forEach(i -> assertThat(result.get(i), equalTo("A")));
|
|
|
+ verify(genericExecutor, times(values.size() / ForEachProcessor.MAX_RECURSE_PER_THREAD)).accept(any(Runnable.class));
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testExecuteWithAsyncProcessor() throws Exception {
|
|
|
+ List<String> values = new ArrayList<>();
|
|
|
+ values.add("foo");
|
|
|
+ values.add("bar");
|
|
|
+ values.add("baz");
|
|
|
+ IngestDocument ingestDocument = new IngestDocument(
|
|
|
+ "_index", "_id", null, null, null, Collections.singletonMap("values", values)
|
|
|
+ );
|
|
|
+
|
|
|
+ ForEachProcessor processor = new ForEachProcessor("_tag", "values", new AsyncUpperCaseProcessor("_ingest._value"),
|
|
|
+ false, genericExecutor);
|
|
|
+ processor.execute(ingestDocument, (result, e) -> {
|
|
|
+ });
|
|
|
+
|
|
|
+ assertBusy(() -> {
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ List<String> result = ingestDocument.getFieldValue("values", List.class);
|
|
|
+ assertEquals(values.size(), result.size());
|
|
|
+ assertThat(result.get(0), equalTo("FOO"));
|
|
|
+ assertThat(result.get(1), equalTo("BAR"));
|
|
|
+ assertThat(result.get(2), equalTo("BAZ"));
|
|
|
+ });
|
|
|
+
|
|
|
+ verifyZeroInteractions(genericExecutor);
|
|
|
}
|
|
|
|
|
|
public void testExecuteWithFailure() throws Exception {
|
|
@@ -72,7 +142,7 @@ public class ForEachProcessorTests extends ESTestCase {
|
|
|
throw new RuntimeException("failure");
|
|
|
}
|
|
|
});
|
|
|
- ForEachProcessor processor = new ForEachProcessor("_tag", "values", testProcessor, false);
|
|
|
+ ForEachProcessor processor = new ForEachProcessor("_tag", "values", testProcessor, false, genericExecutor);
|
|
|
Exception[] exceptions = new Exception[1];
|
|
|
processor.execute(ingestDocument, (result, e) -> {exceptions[0] = e;});
|
|
|
assertThat(exceptions[0].getMessage(), equalTo("failure"));
|
|
@@ -90,7 +160,7 @@ public class ForEachProcessorTests extends ESTestCase {
|
|
|
Processor onFailureProcessor = new TestProcessor(ingestDocument1 -> {});
|
|
|
processor = new ForEachProcessor(
|
|
|
"_tag", "values", new CompoundProcessor(false, Arrays.asList(testProcessor), Arrays.asList(onFailureProcessor)),
|
|
|
- false
|
|
|
+ false, genericExecutor
|
|
|
);
|
|
|
processor.execute(ingestDocument, (result, e) -> {});
|
|
|
assertThat(testProcessor.getInvokedCounter(), equalTo(3));
|
|
@@ -109,7 +179,7 @@ public class ForEachProcessorTests extends ESTestCase {
|
|
|
id.setFieldValue("_ingest._value.index", id.getSourceAndMetadata().get("_index"));
|
|
|
id.setFieldValue("_ingest._value.id", id.getSourceAndMetadata().get("_id"));
|
|
|
});
|
|
|
- ForEachProcessor processor = new ForEachProcessor("_tag", "values", innerProcessor, false);
|
|
|
+ ForEachProcessor processor = new ForEachProcessor("_tag", "values", innerProcessor, false, genericExecutor);
|
|
|
processor.execute(ingestDocument, (result, e) -> {});
|
|
|
|
|
|
assertThat(innerProcessor.getInvokedCounter(), equalTo(2));
|
|
@@ -135,7 +205,7 @@ public class ForEachProcessorTests extends ESTestCase {
|
|
|
ForEachProcessor processor = new ForEachProcessor(
|
|
|
"_tag", "values", new SetProcessor("_tag",
|
|
|
new TestTemplateService.MockTemplateScript.Factory("_ingest._value.new_field"),
|
|
|
- (model) -> model.get("other")), false);
|
|
|
+ (model) -> model.get("other")), false, genericExecutor);
|
|
|
processor.execute(ingestDocument, (result, e) -> {});
|
|
|
|
|
|
assertThat(ingestDocument.getFieldValue("values.0.new_field", String.class), equalTo("value"));
|
|
@@ -146,6 +216,13 @@ public class ForEachProcessorTests extends ESTestCase {
|
|
|
}
|
|
|
|
|
|
public void testRandom() throws Exception {
|
|
|
+ ThreadPoolExecutor asyncExecutor =
|
|
|
+ EsExecutors.newScaling(getClass().getName() + "/" + getTestName(), between(1, 2), between(3, 4), 10, TimeUnit.SECONDS,
|
|
|
+ EsExecutors.daemonThreadFactory("test"), new ThreadContext(Settings.EMPTY));
|
|
|
+ doAnswer(invocationOnMock -> {
|
|
|
+ asyncExecutor.execute((Runnable) invocationOnMock.getArguments()[0]);
|
|
|
+ return null;
|
|
|
+ }).when(genericExecutor).accept(any(Runnable.class));
|
|
|
Processor innerProcessor = new Processor() {
|
|
|
@Override
|
|
|
public IngestDocument execute(IngestDocument ingestDocument) throws Exception {
|
|
@@ -164,7 +241,7 @@ public class ForEachProcessorTests extends ESTestCase {
|
|
|
return null;
|
|
|
}
|
|
|
};
|
|
|
- int numValues = randomIntBetween(1, 32);
|
|
|
+ int numValues = randomIntBetween(1, 10000);
|
|
|
List<String> values = new ArrayList<>(numValues);
|
|
|
for (int i = 0; i < numValues; i++) {
|
|
|
values.add("");
|
|
@@ -173,14 +250,20 @@ public class ForEachProcessorTests extends ESTestCase {
|
|
|
"_index", "_id", null, null, null, Collections.singletonMap("values", values)
|
|
|
);
|
|
|
|
|
|
- ForEachProcessor processor = new ForEachProcessor("_tag", "values", innerProcessor, false);
|
|
|
+ ForEachProcessor processor = new ForEachProcessor("_tag", "values", innerProcessor, false, genericExecutor);
|
|
|
processor.execute(ingestDocument, (result, e) -> {});
|
|
|
+
|
|
|
+ assertBusy(() -> assertEquals(values.size() / ForEachProcessor.MAX_RECURSE_PER_THREAD, asyncExecutor.getCompletedTaskCount()));
|
|
|
+ asyncExecutor.shutdown();
|
|
|
+ asyncExecutor.awaitTermination(5, TimeUnit.SECONDS);
|
|
|
+
|
|
|
@SuppressWarnings("unchecked")
|
|
|
List<String> result = ingestDocument.getFieldValue("values", List.class);
|
|
|
assertThat(result.size(), equalTo(numValues));
|
|
|
for (String r : result) {
|
|
|
assertThat(r, equalTo("."));
|
|
|
}
|
|
|
+ verify(genericExecutor, times(values.size() / ForEachProcessor.MAX_RECURSE_PER_THREAD)).accept(any(Runnable.class));
|
|
|
}
|
|
|
|
|
|
public void testModifyFieldsOutsideArray() throws Exception {
|
|
@@ -198,7 +281,7 @@ public class ForEachProcessorTests extends ESTestCase {
|
|
|
"_tag", "values", new CompoundProcessor(false,
|
|
|
Collections.singletonList(new UppercaseProcessor("_tag_upper", "_ingest._value", false, "_ingest._value")),
|
|
|
Collections.singletonList(new AppendProcessor("_tag", template, (model) -> (Collections.singletonList("added"))))
|
|
|
- ), false);
|
|
|
+ ), false, genericExecutor);
|
|
|
processor.execute(ingestDocument, (result, e) -> {});
|
|
|
|
|
|
List<?> result = ingestDocument.getFieldValue("values", List.class);
|
|
@@ -224,7 +307,7 @@ public class ForEachProcessorTests extends ESTestCase {
|
|
|
|
|
|
TestProcessor processor = new TestProcessor(doc -> doc.setFieldValue("_ingest._value",
|
|
|
doc.getFieldValue("_source._value", String.class)));
|
|
|
- ForEachProcessor forEachProcessor = new ForEachProcessor("_tag", "values", processor, false);
|
|
|
+ ForEachProcessor forEachProcessor = new ForEachProcessor("_tag", "values", processor, false, genericExecutor);
|
|
|
forEachProcessor.execute(ingestDocument, (result, e) -> {});
|
|
|
|
|
|
List<?> result = ingestDocument.getFieldValue("values", List.class);
|
|
@@ -257,7 +340,8 @@ public class ForEachProcessorTests extends ESTestCase {
|
|
|
doc -> doc.setFieldValue("_ingest._value", doc.getFieldValue("_ingest._value", String.class).toUpperCase(Locale.ENGLISH))
|
|
|
);
|
|
|
ForEachProcessor processor = new ForEachProcessor(
|
|
|
- "_tag", "values1", new ForEachProcessor("_tag", "_ingest._value.values2", testProcessor, false), false);
|
|
|
+ "_tag", "values1", new ForEachProcessor("_tag", "_ingest._value.values2", testProcessor, false, genericExecutor),
|
|
|
+ false, genericExecutor);
|
|
|
processor.execute(ingestDocument, (result, e) -> {});
|
|
|
|
|
|
List<?> result = ingestDocument.getFieldValue("values1.0.values2", List.class);
|
|
@@ -275,10 +359,47 @@ public class ForEachProcessorTests extends ESTestCase {
|
|
|
);
|
|
|
IngestDocument ingestDocument = new IngestDocument(originalIngestDocument);
|
|
|
TestProcessor testProcessor = new TestProcessor(doc -> {});
|
|
|
- ForEachProcessor processor = new ForEachProcessor("_tag", "_ingest._value", testProcessor, true);
|
|
|
+ ForEachProcessor processor = new ForEachProcessor("_tag", "_ingest._value", testProcessor, true, genericExecutor);
|
|
|
processor.execute(ingestDocument, (result, e) -> {});
|
|
|
assertIngestDocument(originalIngestDocument, ingestDocument);
|
|
|
assertThat(testProcessor.getInvokedCounter(), equalTo(0));
|
|
|
}
|
|
|
|
|
|
+ private class AsyncUpperCaseProcessor implements Processor {
|
|
|
+
|
|
|
+ private final String field;
|
|
|
+
|
|
|
+ private AsyncUpperCaseProcessor(String field) {
|
|
|
+ this.field = field;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void execute(IngestDocument document, BiConsumer<IngestDocument, Exception> handler) {
|
|
|
+ new Thread(() -> {
|
|
|
+ try {
|
|
|
+ String value = document.getFieldValue(field, String.class, false);
|
|
|
+ document.setFieldValue(field, value.toUpperCase(Locale.ROOT));
|
|
|
+ handler.accept(document, null);
|
|
|
+ } catch (Exception e) {
|
|
|
+ handler.accept(null, e);
|
|
|
+ }
|
|
|
+ }).start();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public IngestDocument execute(IngestDocument ingestDocument) throws Exception {
|
|
|
+ throw new UnsupportedOperationException("this is an async processor, don't call this");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String getType() {
|
|
|
+ return "uppercase-async";
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String getTag() {
|
|
|
+ return getType();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
}
|