1515import org .elasticsearch .action .admin .cluster .state .ClusterStateResponse ;
1616import org .elasticsearch .action .admin .indices .create .CreateIndexRequest ;
1717import org .elasticsearch .action .admin .indices .refresh .RefreshRequest ;
18+ import org .elasticsearch .action .admin .indices .template .put .PutComponentTemplateAction ;
1819import org .elasticsearch .action .admin .indices .template .put .PutIndexTemplateRequest ;
1920import org .elasticsearch .action .admin .indices .template .put .TransportPutComposableIndexTemplateAction ;
2021import org .elasticsearch .action .index .IndexRequest ;
2122import org .elasticsearch .action .ingest .SimulateIndexResponse ;
2223import org .elasticsearch .action .search .SearchRequest ;
2324import org .elasticsearch .action .search .SearchResponse ;
25+ import org .elasticsearch .cluster .metadata .ComponentTemplate ;
2426import org .elasticsearch .cluster .metadata .ComposableIndexTemplate ;
2527import org .elasticsearch .cluster .metadata .Template ;
2628import org .elasticsearch .common .compress .CompressedXContent ;
@@ -57,7 +59,7 @@ public void testMappingValidationIndexExists() {
5759 }
5860 """ ;
5961 indicesAdmin ().create (new CreateIndexRequest (indexName ).mapping (mapping )).actionGet ();
60- BulkRequest bulkRequest = new BulkRequest ( );
62+ BulkRequest bulkRequest = new SimulateBulkRequest ( Map . of (), Map . of () );
6163 bulkRequest .add (new IndexRequest (indexName ).source ("""
6264 {
6365 "foo1": "baz"
@@ -87,13 +89,125 @@ public void testMappingValidationIndexExists() {
8789 assertThat (fields .size (), equalTo (1 ));
8890 }
8991
92+ @ SuppressWarnings ("unchecked" )
93+ public void testMappingValidationIndexExistsWithComponentTemplate () throws IOException {
94+ /*
95+ * This test simulates a BulkRequest of two documents into an existing index. Then we make sure the index contains no documents, and
96+ * that the index's mapping in the cluster state has not been updated with the two new field. With the mapping from the template
97+ * that was used to create the index, we would expect the second document to throw an exception because it uses a field that does
98+ * not exist. But we substitute a new version of the component template named "test-component-template" that allows for the new
99+ * field.
100+ */
101+ String originalComponentTemplateMappingString = """
102+ {
103+ "_doc":{
104+ "dynamic":"strict",
105+ "properties":{
106+ "foo1":{
107+ "type":"text"
108+ }
109+ }
110+ }
111+ }
112+ """ ;
113+ CompressedXContent mapping = CompressedXContent .fromJSON (originalComponentTemplateMappingString );
114+ Template template = new Template (Settings .EMPTY , mapping , null );
115+ PutComponentTemplateAction .Request componentTemplateActionRequest = new PutComponentTemplateAction .Request (
116+ "test-component-template"
117+ );
118+ ComponentTemplate componentTemplate = new ComponentTemplate (template , null , null );
119+ componentTemplateActionRequest .componentTemplate (componentTemplate );
120+ client ().execute (PutComponentTemplateAction .INSTANCE , componentTemplateActionRequest ).actionGet ();
121+ ComposableIndexTemplate composableIndexTemplate = ComposableIndexTemplate .builder ()
122+ .indexPatterns (List .of ("my-index-*" ))
123+ .componentTemplates (List .of ("test-component-template" ))
124+ .build ();
125+ TransportPutComposableIndexTemplateAction .Request request = new TransportPutComposableIndexTemplateAction .Request ("test" );
126+ request .indexTemplate (composableIndexTemplate );
127+ client ().execute (TransportPutComposableIndexTemplateAction .TYPE , request ).actionGet ();
128+
129+ String indexName = "my-index-1" ;
130+ // First, run before the index is created:
131+ assertMappingsUpdatedFromComponentTemplateSubstitutions (indexName );
132+ // Now, create the index and make sure the component template substitutions work the same:
133+ indicesAdmin ().create (new CreateIndexRequest (indexName )).actionGet ();
134+ assertMappingsUpdatedFromComponentTemplateSubstitutions (indexName );
135+ // Now make sure nothing was actually changed:
136+ indicesAdmin ().refresh (new RefreshRequest (indexName )).actionGet ();
137+ SearchResponse searchResponse = client ().search (new SearchRequest (indexName )).actionGet ();
138+ assertThat (searchResponse .getHits ().getTotalHits ().value , equalTo (0L ));
139+ searchResponse .decRef ();
140+ ClusterStateResponse clusterStateResponse = admin ().cluster ().state (new ClusterStateRequest (TEST_REQUEST_TIMEOUT )).actionGet ();
141+ Map <String , Object > indexMapping = clusterStateResponse .getState ().metadata ().index (indexName ).mapping ().sourceAsMap ();
142+ Map <String , Object > fields = (Map <String , Object >) indexMapping .get ("properties" );
143+ assertThat (fields .size (), equalTo (1 ));
144+ }
145+
146+ private void assertMappingsUpdatedFromComponentTemplateSubstitutions (String indexName ) {
147+ IndexRequest indexRequest1 = new IndexRequest (indexName ).source ("""
148+ {
149+ "foo1": "baz"
150+ }
151+ """ , XContentType .JSON ).id (randomUUID ());
152+ IndexRequest indexRequest2 = new IndexRequest (indexName ).source ("""
153+ {
154+ "foo3": "baz"
155+ }
156+ """ , XContentType .JSON ).id (randomUUID ());
157+ {
158+ // First we use the original component template, and expect a failure in the second document:
159+ BulkRequest bulkRequest = new SimulateBulkRequest (Map .of (), Map .of ());
160+ bulkRequest .add (indexRequest1 );
161+ bulkRequest .add (indexRequest2 );
162+ BulkResponse response = client ().execute (new ActionType <BulkResponse >(SimulateBulkAction .NAME ), bulkRequest ).actionGet ();
163+ assertThat (response .getItems ().length , equalTo (2 ));
164+ assertThat (response .getItems ()[0 ].getResponse ().getResult (), equalTo (DocWriteResponse .Result .CREATED ));
165+ assertNull (((SimulateIndexResponse ) response .getItems ()[0 ].getResponse ()).getException ());
166+ assertThat (response .getItems ()[1 ].getResponse ().getResult (), equalTo (DocWriteResponse .Result .CREATED ));
167+ assertThat (
168+ ((SimulateIndexResponse ) response .getItems ()[1 ].getResponse ()).getException ().getMessage (),
169+ containsString ("mapping set to strict, dynamic introduction of" )
170+ );
171+ }
172+
173+ {
174+ // Now we substitute a "test-component-template" that defines both fields, so we expect no exception:
175+ BulkRequest bulkRequest = new SimulateBulkRequest (
176+ Map .of (),
177+ Map .of (
178+ "test-component-template" ,
179+ Map .of (
180+ "template" ,
181+ Map .of (
182+ "mappings" ,
183+ Map .of (
184+ "dynamic" ,
185+ "strict" ,
186+ "properties" ,
187+ Map .of ("foo1" , Map .of ("type" , "text" ), "foo3" , Map .of ("type" , "text" ))
188+ )
189+ )
190+ )
191+ )
192+ );
193+ bulkRequest .add (indexRequest1 );
194+ bulkRequest .add (indexRequest2 );
195+ BulkResponse response = client ().execute (new ActionType <BulkResponse >(SimulateBulkAction .NAME ), bulkRequest ).actionGet ();
196+ assertThat (response .getItems ().length , equalTo (2 ));
197+ assertThat (response .getItems ()[0 ].getResponse ().getResult (), equalTo (DocWriteResponse .Result .CREATED ));
198+ assertNull (((SimulateIndexResponse ) response .getItems ()[0 ].getResponse ()).getException ());
199+ assertThat (response .getItems ()[1 ].getResponse ().getResult (), equalTo (DocWriteResponse .Result .CREATED ));
200+ assertNull (((SimulateIndexResponse ) response .getItems ()[1 ].getResponse ()).getException ());
201+ }
202+ }
203+
90204 public void testMappingValidationIndexDoesNotExistsNoTemplate () {
91205 /*
92206 * This test simulates a BulkRequest of two documents into an index that does not exist. There is no template (other than the
93207 * mapping-less "random-index-template" created by the parent class), so we expect no mapping validation failure.
94208 */
95209 String indexName = randomAlphaOfLength (20 ).toLowerCase (Locale .ROOT );
96- BulkRequest bulkRequest = new BulkRequest ( );
210+ BulkRequest bulkRequest = new SimulateBulkRequest ( Map . of (), Map . of () );
97211 bulkRequest .add (new IndexRequest (indexName ).source ("""
98212 {
99213 "foo1": "baz"
@@ -140,7 +254,7 @@ public void testMappingValidationIndexDoesNotExistsV2Template() throws IOExcepti
140254 request .indexTemplate (composableIndexTemplate );
141255
142256 client ().execute (TransportPutComposableIndexTemplateAction .TYPE , request ).actionGet ();
143- BulkRequest bulkRequest = new BulkRequest ( );
257+ BulkRequest bulkRequest = new SimulateBulkRequest ( Map . of (), Map . of () );
144258 bulkRequest .add (new IndexRequest (indexName ).source ("""
145259 {
146260 "foo1": "baz"
@@ -172,7 +286,7 @@ public void testMappingValidationIndexDoesNotExistsV1Template() {
172286 indicesAdmin ().putTemplate (
173287 new PutIndexTemplateRequest ("test-template" ).patterns (List .of ("my-index-*" )).mapping ("foo1" , "type=integer" )
174288 ).actionGet ();
175- BulkRequest bulkRequest = new BulkRequest ( );
289+ BulkRequest bulkRequest = new SimulateBulkRequest ( Map . of (), Map . of () );
176290 bulkRequest .add (new IndexRequest (indexName ).source ("""
177291 {
178292 "foo1": "baz"
@@ -226,7 +340,7 @@ public void testMappingValidationIndexDoesNotExistsDataStream() throws IOExcepti
226340 client ().execute (TransportPutComposableIndexTemplateAction .TYPE , request ).actionGet ();
227341 {
228342 // First, try with no @timestamp to make sure we're picking up data-stream-specific templates
229- BulkRequest bulkRequest = new BulkRequest ( );
343+ BulkRequest bulkRequest = new SimulateBulkRequest ( Map . of (), Map . of () );
230344 bulkRequest .add (new IndexRequest (indexName ).source ("""
231345 {
232346 "foo1": "baz"
@@ -252,7 +366,7 @@ public void testMappingValidationIndexDoesNotExistsDataStream() throws IOExcepti
252366 }
253367 {
254368 // Now with @timestamp
255- BulkRequest bulkRequest = new BulkRequest ( );
369+ BulkRequest bulkRequest = new SimulateBulkRequest ( Map . of (), Map . of () );
256370 bulkRequest .add (new IndexRequest (indexName ).source ("""
257371 {
258372 "@timestamp": "2024-08-27",
0 commit comments