Skip to content

Commit 9287f29

Browse files
Fix downsample persistent task params serialization bwc (#106878) (#106896)
Missing a check on the transport version results in unreadable cluster state if it includes a serialized instance of DownsampleShardTaskParams. serie indices. Reading an optional array requires reading a boolean first which is required to know if an array of values exists in serialized form. From 8.13 on we try to read such a boolean which is not there because older versions don't write any boolean nor any string array. Here we include the check on versions for backward compatibility skipping reading any boolean or array whatsoever whenever not possible. Customers using downsampling might have cluster states including such serielized objects and would be unable to upgrade to version 8.13. They will be able to upgrade to any version including this fix. This fix has a side effect #106880 Co-authored-by: Salvatore Campagna <[email protected]>
1 parent a5a80c4 commit 9287f29

File tree

7 files changed

+412
-4
lines changed

7 files changed

+412
-4
lines changed

docs/changelog/106878.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 106878
2+
summary: Gate reading of optional string array for bwc
3+
area: Downsampling
4+
type: bug
5+
issues: []
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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+
import org.elasticsearch.gradle.Version
9+
import org.elasticsearch.gradle.internal.info.BuildParams
10+
import org.elasticsearch.gradle.testclusters.StandaloneRestIntegTestTask
11+
12+
apply plugin: 'elasticsearch.internal-yaml-rest-test'
13+
apply plugin: 'elasticsearch.internal-test-artifact'
14+
apply plugin: 'elasticsearch.bwc-test'
15+
16+
17+
dependencies {
18+
testImplementation project(path: ':test:test-clusters')
19+
yamlRestTestImplementation project(path: xpackModule('rollup'))
20+
}
21+
22+
restResources {
23+
restApi {
24+
include '_common', 'bulk', 'cluster', 'indices', 'search', 'ingest.put_pipeline', 'ingest.delete_pipeline'
25+
}
26+
}
27+
28+
def supportedVersion = bwcVersion -> {
29+
return bwcVersion.onOrAfter("8.8.0");
30+
}
31+
32+
BuildParams.bwcVersions.withWireCompatible(supportedVersion) { bwcVersion, baseName ->
33+
34+
def yamlRestTest = tasks.register("v${bwcVersion}#yamlRestTest", StandaloneRestIntegTestTask) {
35+
usesDefaultDistribution()
36+
usesBwcDistribution(bwcVersion)
37+
systemProperty("tests.old_cluster_version", bwcVersion)
38+
testClassesDirs = sourceSets.yamlRestTest.output.classesDirs
39+
classpath = sourceSets.yamlRestTest.runtimeClasspath
40+
}
41+
42+
tasks.register(bwcTaskName(bwcVersion)) {
43+
dependsOn yamlRestTest
44+
}
45+
}
46+
47+
tasks.named("yamlRestTest") {
48+
enabled = false
49+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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.downsample;
9+
10+
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
11+
12+
import org.elasticsearch.test.cluster.ElasticsearchCluster;
13+
import org.elasticsearch.test.cluster.local.distribution.DistributionType;
14+
import org.elasticsearch.test.cluster.util.Version;
15+
import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate;
16+
import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase;
17+
import org.junit.ClassRule;
18+
19+
public class MixedClusterDownsampleRestIT extends ESClientYamlSuiteTestCase {
20+
21+
@ClassRule
22+
public static ElasticsearchCluster cluster = ElasticsearchCluster.local()
23+
.distribution(DistributionType.DEFAULT)
24+
.withNode(node -> node.version(getOldVersion()))
25+
.withNode(node -> node.version(Version.CURRENT))
26+
.setting("xpack.security.enabled", "false")
27+
.setting("xpack.license.self_generated.type", "trial")
28+
.build();
29+
30+
static Version getOldVersion() {
31+
return Version.fromString(System.getProperty("tests.old_cluster_version"));
32+
}
33+
34+
@Override
35+
protected String getTestRestCluster() {
36+
return cluster.getHttpAddresses();
37+
}
38+
39+
public MixedClusterDownsampleRestIT(final ClientYamlTestCandidate testCandidate) {
40+
super(testCandidate);
41+
}
42+
43+
@ParametersFactory
44+
public static Iterable<Object[]> parameters() throws Exception {
45+
return ESClientYamlSuiteTestCase.createParameters();
46+
}
47+
48+
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
setup:
2+
- skip:
3+
version: " - 8.4.99"
4+
reason: "rollup renamed to downsample in 8.5.0"
5+
6+
- do:
7+
indices.create:
8+
index: test
9+
body:
10+
settings:
11+
number_of_shards: 1
12+
index:
13+
mode: time_series
14+
routing_path: [metricset, k8s.pod.uid]
15+
time_series:
16+
start_time: 2021-04-28T00:00:00Z
17+
end_time: 2021-04-29T00:00:00Z
18+
mappings:
19+
properties:
20+
"@timestamp":
21+
type: date
22+
metricset:
23+
type: keyword
24+
time_series_dimension: true
25+
k8s:
26+
properties:
27+
pod:
28+
properties:
29+
uid:
30+
type: keyword
31+
time_series_dimension: true
32+
name:
33+
type: keyword
34+
created_at:
35+
type: date_nanos
36+
running:
37+
type: boolean
38+
number_of_containers:
39+
type: integer
40+
ip:
41+
type: ip
42+
tags:
43+
type: keyword
44+
values:
45+
type: integer
46+
multi-counter:
47+
type: long
48+
time_series_metric: counter
49+
scaled-counter:
50+
type: scaled_float
51+
scaling_factor: 100
52+
time_series_metric: counter
53+
multi-gauge:
54+
type: integer
55+
time_series_metric: gauge
56+
scaled-gauge:
57+
type: scaled_float
58+
scaling_factor: 100
59+
time_series_metric: gauge
60+
network:
61+
properties:
62+
tx:
63+
type: long
64+
time_series_metric: gauge
65+
rx:
66+
type: long
67+
time_series_metric: gauge
68+
- do:
69+
bulk:
70+
refresh: true
71+
index: test
72+
body:
73+
- '{"index": {}}'
74+
- '{"@timestamp": "2021-04-28T18:50:04.467Z", "metricset": "pod", "k8s": {"pod": {"name": "cat", "uid":"947e4ced-1786-4e53-9e0c-5c447e959507", "ip": "10.10.55.1", "multi-counter" : [10, 11, 12], "scaled-counter": 10.0, "multi-gauge": [100, 200, 150], "scaled-gauge": 100.0, "network": {"tx": 2001818691, "rx": 802133794}, "created_at": "2021-04-28T19:34:00.000Z", "running": false, "number_of_containers": 2, "tags": ["backend", "prod"], "values": [2, 3, 6]}}}'
75+
- '{"index": {}}'
76+
- '{"@timestamp": "2021-04-28T18:50:24.467Z", "metricset": "pod", "k8s": {"pod": {"name": "cat", "uid":"947e4ced-1786-4e53-9e0c-5c447e959507", "ip": "10.10.55.26", "multi-counter" : [21, 22, 23], "scaled-counter": 20.0, "multi-gauge": [90, 91, 95], "scaled-gauge": 90.0, "network": {"tx": 2005177954, "rx": 801479970}, "created_at": "2021-04-28T19:35:00.000Z", "running": true, "number_of_containers": 2, "tags": ["backend", "prod", "us-west1"], "values": [1, 1, 3]}}}'
77+
- '{"index": {}}'
78+
- '{"@timestamp": "2021-04-28T20:50:44.467Z", "metricset": "pod", "k8s": {"pod": {"name": "cat", "uid":"947e4ced-1786-4e53-9e0c-5c447e959507", "ip": "10.10.55.41", "multi-counter" : [1, 5, 10], "scaled-counter": 1.0, "multi-gauge": [103, 110, 109], "scaled-gauge": 104.0, "network": {"tx": 2006223737, "rx": 802337279}, "created_at": "2021-04-28T19:36:00.000Z", "running": true, "number_of_containers": 2, "tags": ["backend", "prod", "us-west2"], "values": [4, 1, 2]}}}'
79+
- '{"index": {}}'
80+
- '{"@timestamp": "2021-04-28T20:51:04.467Z", "metricset": "pod", "k8s": {"pod": {"name": "cat", "uid":"947e4ced-1786-4e53-9e0c-5c447e959507", "ip": "10.10.55.22", "multi-counter" : [101, 102, 105], "scaled-counter": 100.0, "multi-gauge": [100, 100, 100], "scaled-gauge": 102.0, "network": {"tx": 2012916202, "rx": 803685721}, "created_at": "2021-04-28T19:37:00.000Z", "running": true, "number_of_containers": 2, "tags": ["backend", "prod"], "values": [2, 3, 1]}}}'
81+
- '{"index": {}}'
82+
- '{"@timestamp": "2021-04-28T18:50:03.142Z", "metricset": "pod", "k8s": {"pod": {"name": "dog", "uid":"df3145b3-0563-4d3b-a0f7-897eb2876ea9", "ip": "10.10.55.33", "multi-counter" : [7, 11, 44], "scaled-counter": 7.0, "multi-gauge": [100, 100, 102], "scaled-gauge": 100.0, "network": {"tx": 1434521831, "rx": 530575198}, "created_at": "2021-04-28T19:42:00.000Z", "running": false, "number_of_containers": 1, "tags": ["backend", "test"], "values": [2, 3, 4]}}}'
83+
- '{"index": {}}'
84+
- '{"@timestamp": "2021-04-28T18:50:23.142Z", "metricset": "pod", "k8s": {"pod": {"name": "dog", "uid":"df3145b3-0563-4d3b-a0f7-897eb2876ea9", "ip": "10.10.55.56", "multi-counter" : [0, 0, 1], "scaled-counter": 0.0, "multi-gauge": [101, 102, 102], "scaled-gauge": 101.0, "network": {"tx": 1434577921, "rx": 530600088}, "created_at": "2021-04-28T19:43:00.000Z", "running": false, "number_of_containers": 1, "tags": ["backend", "test", "us-west2"], "values": [2, 1, 1]}}}'
85+
- '{"index": {}}'
86+
- '{"@timestamp": "2021-04-28T19:50:53.142Z", "metricset": "pod", "k8s": {"pod": {"name": "dog", "uid":"df3145b3-0563-4d3b-a0f7-897eb2876ea9", "ip": "10.10.55.37", "multi-counter" : [1000, 1001, 1002], "scaled-counter": 1000.0, "multi-gauge": [99, 100, 110], "scaled-gauge": 99.0, "network": {"tx": 1434587694, "rx": 530604797}, "created_at": "2021-04-28T19:44:00.000Z", "running": true, "number_of_containers": 1, "tags": ["backend", "test", "us-west1"], "values": [4, 5, 2]}}}'
87+
- '{"index": {}}'
88+
- '{"@timestamp": "2021-04-28T19:51:03.142Z", "metricset": "pod", "k8s": {"pod": {"name": "dog", "uid":"df3145b3-0563-4d3b-a0f7-897eb2876ea9", "ip": "10.10.55.120", "multi-counter" : [76, 77, 78], "scaled-counter": 70.0, "multi-gauge": [95, 98, 100], "scaled-gauge": 95.0, "network": {"tx": 1434595272, "rx": 530605511}, "created_at": "2021-04-28T19:45:00.000Z", "running": true, "number_of_containers": 1, "tags": ["backend", "test", "us-west1"], "values": [3, 2, 1]}}}'
89+
90+
- do:
91+
indices.put_settings:
92+
index: test
93+
body:
94+
index.blocks.write: true
95+
96+
---
97+
"Downsample index":
98+
99+
- do:
100+
indices.downsample:
101+
index: test
102+
target_index: test-downsample
103+
body: >
104+
{
105+
"fixed_interval": "1h"
106+
}
107+
- is_true: acknowledged
108+
109+
- do:
110+
search:
111+
index: test-downsample
112+
body:
113+
sort: [ "@timestamp" ]
114+
115+
- length: { hits.hits: 4 }
116+
- match: { hits.hits.0._source._doc_count: 2 }
117+
- match: { hits.hits.0._source.metricset: pod }
118+
119+
# Assert rollup index settings
120+
- do:
121+
indices.get_settings:
122+
index: test-downsample
123+
124+
- match: { test-downsample.settings.index.mode: time_series }
125+
- match: { test-downsample.settings.index.time_series.end_time: 2021-04-29T00:00:00Z }
126+
- match: { test-downsample.settings.index.time_series.start_time: 2021-04-28T00:00:00Z }
127+
- match: { test-downsample.settings.index.routing_path: [ "metricset", "k8s.pod.uid"] }
128+
- match: { test-downsample.settings.index.downsample.source.name: test }
129+
130+
# Assert rollup index mapping
131+
- do:
132+
indices.get_mapping:
133+
index: test-downsample
134+
135+
- match: { [email protected]: date }
136+
- match: { [email protected]_interval: 1h }
137+
- match: { [email protected]_zone: UTC }
138+
- match: { test-downsample.mappings.properties.k8s.properties.pod.properties.multi-gauge.type: aggregate_metric_double }
139+
- match: { test-downsample.mappings.properties.k8s.properties.pod.properties.multi-gauge.metrics: [ "min", "max", "sum", "value_count" ] }
140+
- match: { test-downsample.mappings.properties.k8s.properties.pod.properties.multi-gauge.default_metric: max }
141+
- match: { test-downsample.mappings.properties.k8s.properties.pod.properties.multi-gauge.time_series_metric: gauge }
142+
- match: { test-downsample.mappings.properties.k8s.properties.pod.properties.multi-counter.type: long }
143+
- match: { test-downsample.mappings.properties.k8s.properties.pod.properties.multi-counter.time_series_metric: counter }
144+
- match: { test-downsample.mappings.properties.k8s.properties.pod.properties.scaled-counter.type: scaled_float }
145+
- match: { test-downsample.mappings.properties.k8s.properties.pod.properties.scaled-counter.scaling_factor: 100 }
146+
- match: { test-downsample.mappings.properties.k8s.properties.pod.properties.scaled-counter.time_series_metric: counter }
147+
- match: { test-downsample.mappings.properties.k8s.properties.pod.properties.scaled-gauge.type: aggregate_metric_double }
148+
- match: { test-downsample.mappings.properties.k8s.properties.pod.properties.scaled-gauge.metrics: [ "min", "max", "sum", "value_count" ] }
149+
- match: { test-downsample.mappings.properties.k8s.properties.pod.properties.scaled-gauge.default_metric: max }
150+
- match: { test-downsample.mappings.properties.k8s.properties.pod.properties.scaled-gauge.time_series_metric: gauge }
151+
- match: { test-downsample.mappings.properties.k8s.properties.pod.properties.uid.type: keyword }
152+
- match: { test-downsample.mappings.properties.k8s.properties.pod.properties.uid.time_series_dimension: true }

x-pack/plugin/downsample/src/main/java/org/elasticsearch/xpack/downsample/DownsampleShardIndexer.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,15 @@ public XContentBuilder buildDownsampleDocument() throws IOException {
557557
fieldProducer.write(builder);
558558
}
559559

560+
if (dimensions.length == 0) {
561+
logger.debug("extracting dimensions from legacy tsid");
562+
Map<?, ?> dimensions = (Map<?, ?>) DocValueFormat.TIME_SERIES_ID.format(tsid);
563+
for (Map.Entry<?, ?> e : dimensions.entrySet()) {
564+
assert e.getValue() != null;
565+
builder.field((String) e.getKey(), e.getValue());
566+
}
567+
}
568+
560569
builder.endObject();
561570
return builder;
562571
}

x-pack/plugin/downsample/src/main/java/org/elasticsearch/xpack/downsample/DownsampleShardTaskParams.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import org.elasticsearch.TransportVersion;
1111
import org.elasticsearch.TransportVersions;
1212
import org.elasticsearch.action.downsample.DownsampleConfig;
13+
import org.elasticsearch.common.Strings;
1314
import org.elasticsearch.common.io.stream.StreamInput;
1415
import org.elasticsearch.common.io.stream.StreamOutput;
1516
import org.elasticsearch.index.shard.ShardId;
@@ -36,6 +37,7 @@ public record DownsampleShardTaskParams(
3637
String[] dimensions
3738
) implements PersistentTaskParams {
3839

40+
private static final TransportVersion V_8_13_0 = TransportVersions.ML_MODEL_IN_SERVICE_SETTINGS;
3941
public static final String NAME = DownsampleShardTask.TASK_NAME;
4042
private static final ParseField DOWNSAMPLE_CONFIG = new ParseField("downsample_config");
4143
private static final ParseField DOWNSAMPLE_INDEX = new ParseField("rollup_index");
@@ -71,7 +73,7 @@ public record DownsampleShardTaskParams(
7173
new ShardId(in),
7274
in.readStringArray(),
7375
in.readStringArray(),
74-
in.readOptionalStringArray()
76+
in.getTransportVersion().onOrAfter(V_8_13_0) ? in.readOptionalStringArray() : new String[] {}
7577
);
7678
}
7779

@@ -85,7 +87,9 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
8587
builder.field(SHARD_ID.getPreferredName(), shardId);
8688
builder.array(METRICS.getPreferredName(), metrics);
8789
builder.array(LABELS.getPreferredName(), labels);
88-
builder.array(DIMENSIONS.getPreferredName(), dimensions);
90+
if (dimensions.length > 0) {
91+
builder.array(DIMENSIONS.getPreferredName(), dimensions);
92+
}
8993
return builder.endObject();
9094
}
9195

@@ -108,7 +112,9 @@ public void writeTo(StreamOutput out) throws IOException {
108112
shardId.writeTo(out);
109113
out.writeStringArray(metrics);
110114
out.writeStringArray(labels);
111-
out.writeOptionalStringArray(dimensions);
115+
if (out.getTransportVersion().onOrAfter(V_8_13_0)) {
116+
out.writeOptionalStringArray(dimensions);
117+
}
112118
}
113119

114120
public static DownsampleShardTaskParams fromXContent(XContentParser parser) throws IOException {
@@ -157,7 +163,7 @@ public static class Builder {
157163
ShardId shardId;
158164
String[] metrics;
159165
String[] labels;
160-
String[] dimensions;
166+
String[] dimensions = Strings.EMPTY_ARRAY;
161167

162168
public Builder downsampleConfig(final DownsampleConfig downsampleConfig) {
163169
this.downsampleConfig = downsampleConfig;
@@ -212,4 +218,9 @@ public DownsampleShardTaskParams build() {
212218
);
213219
}
214220
}
221+
222+
@Override
223+
public String toString() {
224+
return Strings.toString(this, true, true);
225+
}
215226
}

0 commit comments

Comments
 (0)