Skip to content

Commit c1b9451

Browse files
committed
Simulate API: Return 400 on invalid processor(s)
e.g. missing processor or invalid grok pattern Closes elastic#120731
1 parent 6b0d534 commit c1b9451

File tree

5 files changed

+81
-3
lines changed

5 files changed

+81
-3
lines changed

modules/ingest-common/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ dependencies {
2828

2929
restResources {
3030
restApi {
31-
include '_common', 'ingest', 'cluster', 'indices', 'index', 'bulk', 'nodes', 'get', 'update', 'cat', 'mget', 'search'
31+
include '_common', 'ingest', 'cluster', 'indices', 'index', 'bulk', 'nodes', 'get', 'update', 'cat', 'mget', 'search', 'simulate'
3232
}
3333
}
3434

modules/ingest-common/src/yamlRestTest/resources/rest-api-spec/test/ingest/120_grok.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,3 +154,45 @@ teardown:
154154
ingest.processor_grok: {}
155155
- length: { patterns: 318 }
156156
- match: { patterns.PATH: "(?:%{UNIXPATH}|%{WINPATH})" }
157+
158+
159+
---
160+
"Test simulate with invalid GROK pattern":
161+
- skip:
162+
features: headers
163+
- do:
164+
catch: bad_request
165+
headers:
166+
Content-Type: application/json
167+
simulate.ingest:
168+
pipeline: "invalid-grok"
169+
body: >
170+
{
171+
"docs": [
172+
{
173+
"_index": "index-1",
174+
"_source": {
175+
"foo": "bar"
176+
}
177+
}
178+
],
179+
"pipeline_substitutions": {
180+
"invalid-grok": {
181+
"description": "invalid grok pattern",
182+
"processors": [
183+
{
184+
"grok": {
185+
"field": "field",
186+
"patterns": [
187+
"%{INVALID_PATTERN:field}"
188+
]
189+
}
190+
}
191+
]
192+
}
193+
}
194+
}
195+
- match: { status: 400 }
196+
- match: { error.reason: "[patterns] Invalid regex pattern found in: [%{INVALID_PATTERN:field}]. Unable to find pattern [INVALID_PATTERN] in Grok's pattern dictionary" }
197+
- match: { error.property_name: "patterns" }
198+
- match: { error.processor_type: "grok" }

rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/simulate.ingest/10_basic.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -628,7 +628,7 @@ setup:
628628
default_pipeline: "my-pipeline"
629629

630630
- do:
631-
catch: "request"
631+
catch: bad_request
632632
headers:
633633
Content-Type: application/json
634634
simulate.ingest:
@@ -661,7 +661,8 @@ setup:
661661
}
662662
}
663663
}
664-
- match: { status: 500 }
664+
- match: { status: 400 }
665+
- match: { error.reason: "No processor type exists with name [non-existent-processor]" }
665666

666667
---
667668
"Test index in path":

server/src/main/java/org/elasticsearch/ingest/SimulateIngestService.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
package org.elasticsearch.ingest;
1111

12+
import org.elasticsearch.ElasticsearchException;
1213
import org.elasticsearch.action.bulk.BulkRequest;
1314
import org.elasticsearch.action.bulk.SimulateBulkRequest;
1415
import org.elasticsearch.cluster.metadata.ProjectId;
@@ -28,6 +29,8 @@ public SimulateIngestService(IngestService ingestService, BulkRequest request) {
2829
if (request instanceof SimulateBulkRequest simulateBulkRequest) {
2930
try {
3031
pipelineSubstitutions = getPipelineSubstitutions(simulateBulkRequest.getPipelineSubstitutions(), ingestService);
32+
} catch (ElasticsearchException elasticEx) {
33+
throw elasticEx;
3134
} catch (Exception e) {
3235
throw new RuntimeException(e);
3336
}

server/src/test/java/org/elasticsearch/ingest/SimulateIngestServiceTests.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
package org.elasticsearch.ingest;
1111

12+
import org.elasticsearch.ElasticsearchParseException;
1213
import org.elasticsearch.action.bulk.FailureStoreMetrics;
1314
import org.elasticsearch.action.bulk.SimulateBulkRequest;
1415
import org.elasticsearch.client.internal.Client;
@@ -34,6 +35,8 @@
3435
import static org.elasticsearch.test.LambdaMatchers.transformedMatch;
3536
import static org.hamcrest.Matchers.contains;
3637
import static org.hamcrest.Matchers.equalTo;
38+
import static org.hamcrest.Matchers.is;
39+
import static org.junit.Assert.assertThrows;
3740
import static org.mockito.ArgumentMatchers.anyString;
3841
import static org.mockito.Mockito.mock;
3942
import static org.mockito.Mockito.when;
@@ -46,6 +49,10 @@ private static <K, V> Map<K, V> newHashMap(K key, V value) {
4649
return map;
4750
}
4851

52+
private static <K, V> Map<K, V> emptyMutableHashMap() {
53+
return new HashMap<>(0);
54+
}
55+
4956
public void testGetPipeline() {
5057
PipelineConfiguration pipelineConfiguration = new PipelineConfiguration("pipeline1", new BytesArray("""
5158
{"processors": [{"processor1" : {}}]}"""), XContentType.JSON);
@@ -118,6 +125,31 @@ public void testGetPipeline() {
118125
}
119126
}
120127

128+
public void testRethrowingOfElasticParseExceptionFromProcessors() {
129+
final PipelineConfiguration pipelineConfiguration = new PipelineConfiguration("pipeline1", new BytesArray("""
130+
{"processors": []}"""), XContentType.JSON);
131+
final IngestMetadata ingestMetadata = new IngestMetadata(Map.of("pipeline1", pipelineConfiguration));
132+
final Processor.Factory factoryThatThrowsElasticParseException = (factory, tag, description, config, projectId) -> {
133+
throw new ElasticsearchParseException("house: it's never lupus");
134+
};
135+
final Map<String, Processor.Factory> processors = Map.of("parse_exception_processor", factoryThatThrowsElasticParseException);
136+
final var projectId = randomProjectIdOrDefault();
137+
IngestService ingestService = createWithProcessors(projectId, processors);
138+
ingestService.innerUpdatePipelines(projectId, ingestMetadata);
139+
SimulateBulkRequest simulateBulkRequest = new SimulateBulkRequest(
140+
newHashMap("pipeline1", newHashMap("processors", List.of(Map.of("parse_exception_processor", emptyMutableHashMap())))),
141+
Map.of(),
142+
Map.of(),
143+
Map.of()
144+
);
145+
146+
final ElasticsearchParseException ex = assertThrows(
147+
ElasticsearchParseException.class,
148+
() -> new SimulateIngestService(ingestService, simulateBulkRequest)
149+
);
150+
assertThat(ex.getMessage(), is("house: it's never lupus"));
151+
}
152+
121153
private static IngestService createWithProcessors(ProjectId projectId, Map<String, Processor.Factory> processors) {
122154
Client client = mock(Client.class);
123155
ThreadPool threadPool = mock(ThreadPool.class);

0 commit comments

Comments
 (0)