Skip to content

Commit 747ec94

Browse files
authored
Implement MlConfigIndexMappingsFullClusterRestartIT test which verifies that .ml-config index mappings are properly updated during cluster upgrade (#44341)
1 parent 8020871 commit 747ec94

File tree

3 files changed

+160
-12
lines changed

3 files changed

+160
-12
lines changed

server/src/main/java/org/elasticsearch/common/xcontent/support/XContentMapValues.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,10 @@ private static void extractRawValues(List values, List<Object> part, String[] pa
9898
}
9999

100100
public static Object extractValue(String path, Map<?, ?> map) {
101-
String[] pathElements = path.split("\\.");
101+
return extractValue(map, path.split("\\."));
102+
}
103+
104+
public static Object extractValue(Map<?, ?> map, String... pathElements) {
102105
if (pathElements.length == 0) {
103106
return null;
104107
}

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/config/AnalysisConfig.java

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -398,10 +398,10 @@ public Builder(AnalysisConfig analysisConfig) {
398398
this.multivariateByFields = analysisConfig.multivariateByFields;
399399
}
400400

401-
public void setDetectors(List<Detector> detectors) {
401+
public Builder setDetectors(List<Detector> detectors) {
402402
if (detectors == null) {
403403
this.detectors = null;
404-
return;
404+
return this;
405405
}
406406
// We always assign sequential IDs to the detectors that are correct for this analysis config
407407
int detectorIndex = 0;
@@ -412,42 +412,52 @@ public void setDetectors(List<Detector> detectors) {
412412
sequentialIndexDetectors.add(builder.build());
413413
}
414414
this.detectors = sequentialIndexDetectors;
415+
return this;
415416
}
416417

417-
public void setDetector(int detectorIndex, Detector detector) {
418+
public Builder setDetector(int detectorIndex, Detector detector) {
418419
detectors.set(detectorIndex, detector);
420+
return this;
419421
}
420422

421-
public void setBucketSpan(TimeValue bucketSpan) {
423+
public Builder setBucketSpan(TimeValue bucketSpan) {
422424
this.bucketSpan = bucketSpan;
425+
return this;
423426
}
424427

425-
public void setLatency(TimeValue latency) {
428+
public Builder setLatency(TimeValue latency) {
426429
this.latency = latency;
430+
return this;
427431
}
428432

429-
public void setCategorizationFieldName(String categorizationFieldName) {
433+
public Builder setCategorizationFieldName(String categorizationFieldName) {
430434
this.categorizationFieldName = categorizationFieldName;
435+
return this;
431436
}
432437

433-
public void setCategorizationFilters(List<String> categorizationFilters) {
438+
public Builder setCategorizationFilters(List<String> categorizationFilters) {
434439
this.categorizationFilters = categorizationFilters;
440+
return this;
435441
}
436442

437-
public void setCategorizationAnalyzerConfig(CategorizationAnalyzerConfig categorizationAnalyzerConfig) {
443+
public Builder setCategorizationAnalyzerConfig(CategorizationAnalyzerConfig categorizationAnalyzerConfig) {
438444
this.categorizationAnalyzerConfig = categorizationAnalyzerConfig;
445+
return this;
439446
}
440447

441-
public void setSummaryCountFieldName(String summaryCountFieldName) {
448+
public Builder setSummaryCountFieldName(String summaryCountFieldName) {
442449
this.summaryCountFieldName = summaryCountFieldName;
450+
return this;
443451
}
444452

445-
public void setInfluencers(List<String> influencers) {
453+
public Builder setInfluencers(List<String> influencers) {
446454
this.influencers = ExceptionsHelper.requireNonNull(influencers, INFLUENCERS.getPreferredName());
455+
return this;
447456
}
448457

449-
public void setMultivariateByFields(Boolean multivariateByFields) {
458+
public Builder setMultivariateByFields(Boolean multivariateByFields) {
450459
this.multivariateByFields = multivariateByFields;
460+
return this;
451461
}
452462

453463
/**
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
package org.elasticsearch.xpack.restart;
7+
8+
import org.elasticsearch.Version;
9+
import org.elasticsearch.client.Request;
10+
import org.elasticsearch.client.Response;
11+
import org.elasticsearch.client.ResponseException;
12+
import org.elasticsearch.common.Strings;
13+
import org.elasticsearch.common.settings.Settings;
14+
import org.elasticsearch.common.unit.TimeValue;
15+
import org.elasticsearch.common.util.concurrent.ThreadContext;
16+
import org.elasticsearch.common.xcontent.support.XContentMapValues;
17+
import org.elasticsearch.upgrades.AbstractFullClusterRestartTestCase;
18+
import org.elasticsearch.xpack.core.ml.job.config.AnalysisConfig;
19+
import org.elasticsearch.xpack.core.ml.job.config.DataDescription;
20+
import org.elasticsearch.xpack.core.ml.job.config.Detector;
21+
import org.elasticsearch.xpack.core.ml.job.config.Job;
22+
import org.elasticsearch.xpack.test.rest.XPackRestTestConstants;
23+
import org.elasticsearch.xpack.test.rest.XPackRestTestHelper;
24+
import org.junit.Before;
25+
26+
import java.io.IOException;
27+
import java.nio.charset.StandardCharsets;
28+
import java.util.Base64;
29+
import java.util.Collections;
30+
import java.util.HashMap;
31+
import java.util.List;
32+
import java.util.Map;
33+
34+
import static org.hamcrest.Matchers.equalTo;
35+
import static org.hamcrest.Matchers.is;
36+
import static org.hamcrest.Matchers.nullValue;
37+
38+
public class MlConfigIndexMappingsFullClusterRestartIT extends AbstractFullClusterRestartTestCase {
39+
40+
private static final String OLD_CLUSTER_JOB_ID = "ml-config-mappings-old-cluster-job";
41+
private static final String NEW_CLUSTER_JOB_ID = "ml-config-mappings-new-cluster-job";
42+
43+
private static final Map<String, Object> EXPECTED_DATA_FRAME_ANALYSIS_MAPPINGS =
44+
mapOf(
45+
"properties", mapOf(
46+
"outlier_detection", mapOf(
47+
"properties", mapOf(
48+
"method", mapOf("type", "keyword"),
49+
"n_neighbors", mapOf("type", "integer"),
50+
"feature_influence_threshold", mapOf("type", "double")))));
51+
52+
@Override
53+
protected Settings restClientSettings() {
54+
String token = "Basic " + Base64.getEncoder().encodeToString("test_user:x-pack-test-password".getBytes(StandardCharsets.UTF_8));
55+
return Settings.builder()
56+
.put(ThreadContext.PREFIX + ".Authorization", token)
57+
.build();
58+
}
59+
60+
@Before
61+
public void waitForMlTemplates() throws Exception {
62+
List<String> templatesToWaitFor = XPackRestTestConstants.ML_POST_V660_TEMPLATES;
63+
XPackRestTestHelper.waitForTemplates(client(), templatesToWaitFor);
64+
}
65+
66+
public void testMlConfigIndexMappingsAfterMigration() throws Exception {
67+
if (isRunningAgainstOldCluster()) {
68+
assertThatMlConfigIndexDoesNotExist();
69+
// trigger .ml-config index creation
70+
createAnomalyDetectorJob(OLD_CLUSTER_JOB_ID);
71+
if (getOldClusterVersion().onOrAfter(Version.V_7_3_0)) {
72+
// .ml-config has correct mappings from the start
73+
assertThat(mappingsForDataFrameAnalysis(), is(equalTo(EXPECTED_DATA_FRAME_ANALYSIS_MAPPINGS)));
74+
} else {
75+
// .ml-config does not yet have correct mappings, it will need an update after cluster is upgraded
76+
assertThat(mappingsForDataFrameAnalysis(), is(nullValue()));
77+
}
78+
} else {
79+
// trigger .ml-config index mappings update
80+
createAnomalyDetectorJob(NEW_CLUSTER_JOB_ID);
81+
// assert that the mappings are updated
82+
assertThat(mappingsForDataFrameAnalysis(), is(equalTo(EXPECTED_DATA_FRAME_ANALYSIS_MAPPINGS)));
83+
}
84+
}
85+
86+
private void assertThatMlConfigIndexDoesNotExist() {
87+
Request getIndexRequest = new Request("GET", ".ml-config");
88+
ResponseException e = expectThrows(ResponseException.class, () -> client().performRequest(getIndexRequest));
89+
assertThat(e.getResponse().getStatusLine().getStatusCode(), equalTo(404));
90+
}
91+
92+
private void createAnomalyDetectorJob(String jobId) throws IOException {
93+
Detector.Builder detector = new Detector.Builder("metric", "responsetime")
94+
.setByFieldName("airline");
95+
AnalysisConfig.Builder analysisConfig = new AnalysisConfig.Builder(Collections.singletonList(detector.build()))
96+
.setBucketSpan(TimeValue.timeValueMinutes(10));
97+
Job.Builder job = new Job.Builder(jobId)
98+
.setAnalysisConfig(analysisConfig)
99+
.setDataDescription(new DataDescription.Builder());
100+
101+
Request putJobRequest = new Request("PUT", "/_ml/anomaly_detectors/" + jobId);
102+
putJobRequest.setJsonEntity(Strings.toString(job));
103+
Response putJobResponse = client().performRequest(putJobRequest);
104+
assertThat(putJobResponse.getStatusLine().getStatusCode(), equalTo(200));
105+
}
106+
107+
@SuppressWarnings("unchecked")
108+
private Map<String, Object> mappingsForDataFrameAnalysis() throws Exception {
109+
Request getIndexMappingsRequest = new Request("GET", ".ml-config/_mappings");
110+
Response getIndexMappingsResponse = client().performRequest(getIndexMappingsRequest);
111+
assertThat(getIndexMappingsResponse.getStatusLine().getStatusCode(), equalTo(200));
112+
113+
Map<String, Object> mappings = entityAsMap(getIndexMappingsResponse);
114+
mappings = (Map<String, Object>) XContentMapValues.extractValue(mappings, ".ml-config", "mappings");
115+
if (mappings.containsKey("doc")) {
116+
mappings = (Map<String, Object>) XContentMapValues.extractValue(mappings, "doc");
117+
}
118+
mappings = (Map<String, Object>) XContentMapValues.extractValue(mappings, "properties", "analysis");
119+
return mappings;
120+
}
121+
122+
private static <K, V> Map<K, V> mapOf(K k1, V v1) {
123+
Map<K, V> map = new HashMap<>();
124+
map.put(k1, v1);
125+
return map;
126+
}
127+
128+
private static <K, V> Map<K, V> mapOf(K k1, V v1, K k2, V v2, K k3, V v3) {
129+
Map<K, V> map = new HashMap<>();
130+
map.put(k1, v1);
131+
map.put(k2, v2);
132+
map.put(k3, v3);
133+
return map;
134+
}
135+
}

0 commit comments

Comments
 (0)