55 */
66package org .elasticsearch .xpack .ml .integration ;
77
8+ import org .elasticsearch .action .ActionFuture ;
89import org .elasticsearch .action .ActionListener ;
10+ import org .elasticsearch .action .admin .indices .mapping .get .GetMappingsAction ;
11+ import org .elasticsearch .action .admin .indices .mapping .get .GetMappingsRequest ;
12+ import org .elasticsearch .action .admin .indices .mapping .get .GetMappingsResponse ;
913import org .elasticsearch .action .bulk .BulkRequestBuilder ;
1014import org .elasticsearch .action .bulk .BulkResponse ;
1115import org .elasticsearch .action .index .IndexRequest ;
1216import org .elasticsearch .action .support .WriteRequest ;
17+ import org .elasticsearch .cluster .metadata .MappingMetaData ;
1318import org .elasticsearch .cluster .routing .UnassignedInfo ;
1419import org .elasticsearch .common .Strings ;
20+ import org .elasticsearch .common .collect .ImmutableOpenMap ;
1521import org .elasticsearch .common .settings .Settings ;
1622import org .elasticsearch .common .unit .TimeValue ;
1723import org .elasticsearch .common .xcontent .ToXContent ;
3238import org .elasticsearch .xpack .core .ml .job .config .RuleAction ;
3339import org .elasticsearch .xpack .core .ml .job .config .RuleScope ;
3440import org .elasticsearch .xpack .core .ml .job .persistence .AnomalyDetectorsIndex ;
41+ import org .elasticsearch .xpack .core .ml .job .persistence .AnomalyDetectorsIndexFields ;
3542import org .elasticsearch .xpack .core .ml .job .process .autodetect .state .DataCounts ;
3643import org .elasticsearch .xpack .core .ml .job .process .autodetect .state .DataCountsTests ;
3744import org .elasticsearch .xpack .core .ml .job .process .autodetect .state .ModelSizeStats ;
5562import java .util .Date ;
5663import java .util .HashSet ;
5764import java .util .List ;
65+ import java .util .Map ;
5866import java .util .Set ;
5967import java .util .concurrent .CountDownLatch ;
6068import java .util .concurrent .atomic .AtomicReference ;
@@ -79,6 +87,54 @@ public void createComponents() throws Exception {
7987 waitForMlTemplates ();
8088 }
8189
90+ public void testMultipleSimultaneousJobCreations () {
91+
92+ int numJobs = randomIntBetween (4 , 7 );
93+
94+ // Each job should result in one extra field being added to the results index mappings: field1, field2, field3, etc.
95+ // Due to all being created simultaneously this test may reveal race conditions in the code that updates the mappings.
96+ List <PutJobAction .Request > requests = new ArrayList <>(numJobs );
97+ for (int i = 1 ; i <= numJobs ; ++i ) {
98+ Job .Builder builder = new Job .Builder ("job" + i );
99+ AnalysisConfig .Builder ac = createAnalysisConfig ("field" + i , Collections .emptyList ());
100+ DataDescription .Builder dc = new DataDescription .Builder ();
101+ builder .setAnalysisConfig (ac );
102+ builder .setDataDescription (dc );
103+
104+ requests .add (new PutJobAction .Request (builder ));
105+ }
106+
107+ // Start the requests as close together as possible, without waiting for each to complete before starting the next one.
108+ List <ActionFuture <PutJobAction .Response >> futures = new ArrayList <>(numJobs );
109+ for (PutJobAction .Request request : requests ) {
110+ futures .add (client ().execute (PutJobAction .INSTANCE , request ));
111+ }
112+
113+ // Only after all requests are in-flight, wait for all the requests to complete.
114+ for (ActionFuture <PutJobAction .Response > future : futures ) {
115+ future .actionGet ();
116+ }
117+
118+ // Assert that the mappings contain all the additional fields: field1, field2, field3, etc.
119+ String sharedResultsIndex = AnomalyDetectorsIndexFields .RESULTS_INDEX_PREFIX + AnomalyDetectorsIndexFields .RESULTS_INDEX_DEFAULT ;
120+ GetMappingsRequest request = new GetMappingsRequest ().indices (sharedResultsIndex );
121+ GetMappingsResponse response = client ().execute (GetMappingsAction .INSTANCE , request ).actionGet ();
122+ ImmutableOpenMap <String , ImmutableOpenMap <String , MappingMetaData >> indexMappings = response .getMappings ();
123+ assertNotNull (indexMappings );
124+ ImmutableOpenMap <String , MappingMetaData > typeMappings = indexMappings .get (sharedResultsIndex );
125+ assertNotNull ("expected " + sharedResultsIndex + " in " + indexMappings , typeMappings );
126+ assertEquals ("expected 1 type in " + typeMappings , 1 , typeMappings .size ());
127+ Map <String , Object > mappings = typeMappings .iterator ().next ().value .getSourceAsMap ();
128+ assertNotNull (mappings );
129+ @ SuppressWarnings ("unchecked" )
130+ Map <String , Object > properties = (Map <String , Object >) mappings .get ("properties" );
131+ assertNotNull ("expected 'properties' field in " + mappings , properties );
132+ for (int i = 1 ; i <= numJobs ; ++i ) {
133+ String fieldName = "field" + i ;
134+ assertNotNull ("expected '" + fieldName + "' field in " + properties , properties .get (fieldName ));
135+ }
136+ }
137+
82138 public void testGetCalandarByJobId () throws Exception {
83139 List <Calendar > calendars = new ArrayList <>();
84140 calendars .add (new Calendar ("empty calendar" , Collections .emptyList (), null ));
@@ -473,7 +529,7 @@ private Job.Builder createJob(String jobId, List<String> filterIds) {
473529 private Job .Builder createJob (String jobId , List <String > filterIds , List <String > jobGroups ) {
474530 Job .Builder builder = new Job .Builder (jobId );
475531 builder .setGroups (jobGroups );
476- AnalysisConfig .Builder ac = createAnalysisConfig (filterIds );
532+ AnalysisConfig .Builder ac = createAnalysisConfig ("by_field" , filterIds );
477533 DataDescription .Builder dc = new DataDescription .Builder ();
478534 builder .setAnalysisConfig (ac );
479535 builder .setDataDescription (dc );
@@ -483,14 +539,14 @@ private Job.Builder createJob(String jobId, List<String> filterIds, List<String>
483539 return builder ;
484540 }
485541
486- private AnalysisConfig .Builder createAnalysisConfig (List <String > filterIds ) {
542+ private AnalysisConfig .Builder createAnalysisConfig (String byFieldName , List <String > filterIds ) {
487543 Detector .Builder detector = new Detector .Builder ("mean" , "field" );
488- detector .setByFieldName ("by_field" );
544+ detector .setByFieldName (byFieldName );
489545 List <DetectionRule > rules = new ArrayList <>();
490546
491547 for (String filterId : filterIds ) {
492548 RuleScope .Builder ruleScope = RuleScope .builder ();
493- ruleScope .include ("by_field" , filterId );
549+ ruleScope .include (byFieldName , filterId );
494550
495551 rules .add (new DetectionRule .Builder (ruleScope ).setActions (RuleAction .SKIP_RESULT ).build ());
496552 }
0 commit comments