Skip to content

Commit 7dff45b

Browse files
Randomized BWC test (elastic#139078)
This PR adds a rolling upgrade test that uses the data generation framework to generate a random mapping and index random documents.
1 parent a9315e4 commit 7dff45b

File tree

8 files changed

+399
-82
lines changed

8 files changed

+399
-82
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.datageneration.datasource;
11+
12+
import org.elasticsearch.test.ESTestCase;
13+
14+
/**
15+
* A {@link DataSourceHandler} that causes generated strings to be ASCII-only instead of full Unicode. Useful for debugging.
16+
*/
17+
public class ASCIIStringsHandler implements DataSourceHandler {
18+
@Override
19+
public DataSourceResponse.ChildFieldGenerator handle(DataSourceRequest.ChildFieldGenerator request) {
20+
return new DefaultObjectGenerationHandler.DefaultChildFieldGenerator(request) {
21+
@Override
22+
public String generateFieldName() {
23+
return ESTestCase.randomAlphaOfLengthBetween(1, 10);
24+
}
25+
};
26+
}
27+
28+
@Override
29+
public DataSourceResponse.StringGenerator handle(DataSourceRequest.StringGenerator request) {
30+
return new DataSourceResponse.StringGenerator(() -> ESTestCase.randomAlphaOfLengthBetween(0, 50));
31+
}
32+
}

test/framework/src/main/java/org/elasticsearch/datageneration/datasource/DefaultObjectGenerationHandler.java

Lines changed: 50 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -30,51 +30,59 @@ public class DefaultObjectGenerationHandler implements DataSourceHandler {
3030
*/
3131
public static final String RESERVED_FIELD_NAME_PREFIX = "_reserved_";
3232

33-
@Override
34-
public DataSourceResponse.ChildFieldGenerator handle(DataSourceRequest.ChildFieldGenerator request) {
35-
return new DataSourceResponse.ChildFieldGenerator() {
36-
@Override
37-
public int generateChildFieldCount() {
38-
// no child fields is legal
39-
return ESTestCase.randomIntBetween(0, request.specification().maxFieldCountPerLevel());
40-
}
41-
42-
@Override
43-
public boolean generateDynamicSubObject() {
44-
// Using a static 5% chance, this is just a chosen value that can be tweaked.
45-
return randomDouble() <= 0.05;
46-
}
47-
48-
@Override
49-
public boolean generateNestedSubObject() {
50-
// Using a static 5% chance, this is just a chosen value that can be tweaked.
51-
return randomDouble() <= 0.05;
52-
}
33+
public static class DefaultChildFieldGenerator implements DataSourceResponse.ChildFieldGenerator {
34+
private final DataSourceRequest.ChildFieldGenerator request;
35+
36+
public DefaultChildFieldGenerator(DataSourceRequest.ChildFieldGenerator request) {
37+
this.request = request;
38+
}
39+
40+
@Override
41+
public int generateChildFieldCount() {
42+
// no child fields is legal
43+
return ESTestCase.randomIntBetween(0, request.specification().maxFieldCountPerLevel());
44+
}
45+
46+
@Override
47+
public boolean generateDynamicSubObject() {
48+
// Using a static 5% chance, this is just a chosen value that can be tweaked.
49+
return randomDouble() <= 0.05;
50+
}
51+
52+
@Override
53+
public boolean generateNestedSubObject() {
54+
// Using a static 5% chance, this is just a chosen value that can be tweaked.
55+
return randomDouble() <= 0.05;
56+
}
57+
58+
@Override
59+
public boolean generateRegularSubObject() {
60+
// Using a static 5% chance, this is just a chosen value that can be tweaked.
61+
return randomDouble() <= 0.05;
62+
}
63+
64+
@Override
65+
public String generateFieldName() {
66+
while (true) {
67+
String fieldName = randomRealisticUnicodeOfCodepointLengthBetween(1, 10);
68+
if (fieldName.isBlank()) {
69+
continue;
70+
}
71+
if (fieldName.indexOf('.') != -1) {
72+
continue;
73+
}
74+
if (fieldName.startsWith(RESERVED_FIELD_NAME_PREFIX)) {
75+
continue;
76+
}
5377

54-
@Override
55-
public boolean generateRegularSubObject() {
56-
// Using a static 5% chance, this is just a chosen value that can be tweaked.
57-
return randomDouble() <= 0.05;
78+
return fieldName;
5879
}
80+
}
81+
}
5982

60-
@Override
61-
public String generateFieldName() {
62-
while (true) {
63-
String fieldName = randomRealisticUnicodeOfCodepointLengthBetween(1, 10);
64-
if (fieldName.isBlank()) {
65-
continue;
66-
}
67-
if (fieldName.indexOf('.') != -1) {
68-
continue;
69-
}
70-
if (fieldName.startsWith(RESERVED_FIELD_NAME_PREFIX)) {
71-
continue;
72-
}
73-
74-
return fieldName;
75-
}
76-
}
77-
};
83+
@Override
84+
public DataSourceResponse.ChildFieldGenerator handle(DataSourceRequest.ChildFieldGenerator request) {
85+
return new DefaultChildFieldGenerator(request);
7886
}
7987

8088
// UNSIGNED_LONG is excluded because it is mapped as long

test/framework/src/main/java/org/elasticsearch/datageneration/matchers/source/DynamicFieldMatcher.java

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,20 @@ class DynamicFieldMatcher {
2929
private final Settings.Builder actualSettings;
3030
private final XContentBuilder expectedMappings;
3131
private final Settings.Builder expectedSettings;
32+
private final boolean ignoringSort;
3233

3334
DynamicFieldMatcher(
3435
XContentBuilder actualMappings,
3536
Settings.Builder actualSettings,
3637
XContentBuilder expectedMappings,
37-
Settings.Builder expectedSettings
38+
Settings.Builder expectedSettings,
39+
boolean ignoringSort
3840
) {
3941
this.actualMappings = actualMappings;
4042
this.actualSettings = actualSettings;
4143
this.expectedMappings = expectedMappings;
4244
this.expectedSettings = expectedSettings;
45+
this.ignoringSort = ignoringSort;
4346
}
4447

4548
/**
@@ -60,8 +63,8 @@ public MatchResult match(List<Object> actual, List<Object> expected) {
6063
if (isDouble) {
6164
assert expected.stream().allMatch(o -> o == null || o instanceof Double);
6265

63-
var normalizedActual = normalizeDoubles(actual);
64-
var normalizedExpected = normalizeDoubles(expected);
66+
var normalizedActual = normalizeDoubles(actual, ignoringSort);
67+
var normalizedExpected = normalizeDoubles(expected, ignoringSort);
6568
Supplier<MatchResult> noMatchSupplier = () -> MatchResult.noMatch(
6669
formatErrorMessage(
6770
actualMappings,
@@ -94,15 +97,21 @@ public MatchResult match(List<Object> actual, List<Object> expected) {
9497
* values within a margin of error. Synthetic source does support duplicate values and preserves the order, but it loses some accuracy,
9598
* this is why the margin of error is very important. In the future, we can make {@link SourceTransforms#normalizeValues} also stricter.
9699
*/
97-
private static List<Float> normalizeDoubles(List<Object> values) {
100+
private static List<Float> normalizeDoubles(List<Object> values, boolean ignoringSort) {
98101
if (values == null) {
99102
return List.of();
100103
}
101104

102105
Function<Object, Float> toFloat = (o) -> o instanceof Number n ? n.floatValue() : Float.parseFloat((String) o);
103106

104107
// We skip nulls because they trip the pretty print collections.
105-
return values.stream().filter(Objects::nonNull).map(toFloat).toList();
108+
var stream = values.stream().filter(Objects::nonNull).map(toFloat);
109+
110+
if (ignoringSort) {
111+
stream = stream.sorted();
112+
}
113+
114+
return stream.toList();
106115
}
107116

108117
private static boolean floatEquals(Float actual, Float expected) {
@@ -117,7 +126,7 @@ private MatchResult matchWithGenericMatcher(List<Object> actualValues, List<Obje
117126
expectedSettings,
118127
SourceTransforms.normalizeValues(actualValues),
119128
SourceTransforms.normalizeValues(expectedValues),
120-
true
129+
ignoringSort
121130
);
122131

123132
return genericListMatcher.match();

test/framework/src/main/java/org/elasticsearch/datageneration/matchers/source/FieldSpecificMatcher.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,9 @@ public MatchResult match(
140140
}
141141

142142
private static List<String> normalize(List<Object> values) {
143+
if (values == null) {
144+
return List.of();
145+
}
143146
return values.stream().filter(Objects::nonNull).map(it -> (String) it).toList();
144147
}
145148
}

test/framework/src/main/java/org/elasticsearch/datageneration/matchers/source/SourceMatcher.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,13 @@ public SourceMatcher(
5555
this.expectedNormalizedMapping = MappingTransforms.normalizeMapping(expectedMappingAsMap);
5656

5757
this.fieldSpecificMatchers = FieldSpecificMatcher.matchers(actualMappings, actualSettings, expectedMappings, expectedSettings);
58-
this.dynamicFieldMatcher = new DynamicFieldMatcher(actualMappings, actualSettings, expectedMappings, expectedSettings);
58+
this.dynamicFieldMatcher = new DynamicFieldMatcher(
59+
actualMappings,
60+
actualSettings,
61+
expectedMappings,
62+
expectedSettings,
63+
ignoringSort
64+
);
5965
}
6066

6167
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.logsdb;
9+
10+
import org.elasticsearch.common.settings.SecureString;
11+
import org.elasticsearch.common.settings.Settings;
12+
import org.elasticsearch.common.util.concurrent.ThreadContext;
13+
import org.elasticsearch.test.cluster.ElasticsearchCluster;
14+
import org.elasticsearch.test.cluster.util.Version;
15+
import org.elasticsearch.test.rest.ESRestTestCase;
16+
import org.junit.ClassRule;
17+
18+
import java.io.IOException;
19+
20+
public abstract class AbstractLogsdbRollingUpgradeTestCase extends ESRestTestCase {
21+
private static final String USER = "admin-user";
22+
private static final String PASS = "x-pack-test-password";
23+
24+
@ClassRule
25+
public static final ElasticsearchCluster cluster = Clusters.oldVersionCluster(USER, PASS);
26+
27+
@Override
28+
protected String getTestRestCluster() {
29+
return cluster.getHttpAddresses();
30+
}
31+
32+
protected Settings restClientSettings() {
33+
String token = basicAuthHeaderValue(USER, new SecureString(PASS.toCharArray()));
34+
return Settings.builder().put(super.restClientSettings()).put(ThreadContext.PREFIX + ".Authorization", token).build();
35+
}
36+
37+
protected void upgradeNode(int n) throws IOException {
38+
closeClients();
39+
var upgradeVersion = System.getProperty("tests.new_cluster_version") != null
40+
? Version.fromString(System.getProperty("tests.new_cluster_version"))
41+
: Version.CURRENT;
42+
logger.info("Upgrading node {} to version {}", n, upgradeVersion);
43+
cluster.upgradeNodeToVersion(n, upgradeVersion);
44+
initClient();
45+
}
46+
}

x-pack/plugin/logsdb/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/xpack/logsdb/LogsdbIndexingRollingUpgradeIT.java

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,12 @@
1010
import org.elasticsearch.client.Response;
1111
import org.elasticsearch.client.RestClient;
1212
import org.elasticsearch.common.network.NetworkAddress;
13-
import org.elasticsearch.common.settings.SecureString;
14-
import org.elasticsearch.common.settings.Settings;
1513
import org.elasticsearch.common.time.DateFormatter;
1614
import org.elasticsearch.common.time.FormatNames;
17-
import org.elasticsearch.common.util.concurrent.ThreadContext;
1815
import org.elasticsearch.common.xcontent.XContentHelper;
19-
import org.elasticsearch.test.cluster.ElasticsearchCluster;
2016
import org.elasticsearch.test.cluster.util.Version;
21-
import org.elasticsearch.test.rest.ESRestTestCase;
2217
import org.elasticsearch.test.rest.ObjectPath;
2318
import org.elasticsearch.xcontent.XContentType;
24-
import org.junit.ClassRule;
2519

2620
import java.io.IOException;
2721
import java.io.InputStream;
@@ -36,7 +30,7 @@
3630
import static org.hamcrest.Matchers.hasSize;
3731
import static org.hamcrest.Matchers.notNullValue;
3832

39-
public class LogsdbIndexingRollingUpgradeIT extends ESRestTestCase {
33+
public class LogsdbIndexingRollingUpgradeIT extends AbstractLogsdbRollingUpgradeTestCase {
4034

4135
static String BULK_ITEM_TEMPLATE =
4236
"""
@@ -69,22 +63,6 @@ public class LogsdbIndexingRollingUpgradeIT extends ESRestTestCase {
6963
}
7064
}""";
7165

72-
private static final String USER = "admin-user";
73-
private static final String PASS = "x-pack-test-password";
74-
75-
@ClassRule
76-
public static final ElasticsearchCluster cluster = Clusters.oldVersionCluster(USER, PASS);
77-
78-
@Override
79-
protected String getTestRestCluster() {
80-
return cluster.getHttpAddresses();
81-
}
82-
83-
protected Settings restClientSettings() {
84-
String token = basicAuthHeaderValue(USER, new SecureString(PASS.toCharArray()));
85-
return Settings.builder().put(super.restClientSettings()).put(ThreadContext.PREFIX + ".Authorization", token).build();
86-
}
87-
8866
public void testIndexing() throws Exception {
8967
for (Map.Entry<Object, Object> entry : System.getProperties().entrySet()) {
9068
logger.info("system_property: {} / {}", entry.getKey(), entry.getValue());
@@ -131,16 +109,6 @@ public void testIndexing() throws Exception {
131109
}
132110
}
133111

134-
private void upgradeNode(int n) throws IOException {
135-
closeClients();
136-
var upgradeVersion = System.getProperty("tests.new_cluster_version") != null
137-
? Version.fromString(System.getProperty("tests.new_cluster_version"))
138-
: Version.CURRENT;
139-
logger.info("Upgrading node {} to version {}", n, upgradeVersion);
140-
cluster.upgradeNodeToVersion(n, upgradeVersion);
141-
initClient();
142-
}
143-
144112
static void assertDataStream(String dataStreamName, String templateId) throws IOException {
145113
var getDataStreamsRequest = new Request("GET", "/_data_stream/" + dataStreamName);
146114
var getDataStreamResponse = client().performRequest(getDataStreamsRequest);

0 commit comments

Comments
 (0)