Skip to content

Commit

Permalink
Fix downsample persistent task params serialization bwc (#106878) (#1…
Browse files Browse the repository at this point in the history
…06896)

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]>
  • Loading branch information
dnhatn and salvatore-campagna authored Mar 29, 2024
1 parent a5a80c4 commit 9287f29
Show file tree
Hide file tree
Showing 7 changed files with 412 additions and 4 deletions.
5 changes: 5 additions & 0 deletions docs/changelog/106878.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 106878
summary: Gate reading of optional string array for bwc
area: Downsampling
type: bug
issues: []
49 changes: 49 additions & 0 deletions x-pack/plugin/downsample/qa/mixed-cluster/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import org.elasticsearch.gradle.Version
import org.elasticsearch.gradle.internal.info.BuildParams
import org.elasticsearch.gradle.testclusters.StandaloneRestIntegTestTask

apply plugin: 'elasticsearch.internal-yaml-rest-test'
apply plugin: 'elasticsearch.internal-test-artifact'
apply plugin: 'elasticsearch.bwc-test'


dependencies {
testImplementation project(path: ':test:test-clusters')
yamlRestTestImplementation project(path: xpackModule('rollup'))
}

restResources {
restApi {
include '_common', 'bulk', 'cluster', 'indices', 'search', 'ingest.put_pipeline', 'ingest.delete_pipeline'
}
}

def supportedVersion = bwcVersion -> {
return bwcVersion.onOrAfter("8.8.0");
}

BuildParams.bwcVersions.withWireCompatible(supportedVersion) { bwcVersion, baseName ->

def yamlRestTest = tasks.register("v${bwcVersion}#yamlRestTest", StandaloneRestIntegTestTask) {
usesDefaultDistribution()
usesBwcDistribution(bwcVersion)
systemProperty("tests.old_cluster_version", bwcVersion)
testClassesDirs = sourceSets.yamlRestTest.output.classesDirs
classpath = sourceSets.yamlRestTest.runtimeClasspath
}

tasks.register(bwcTaskName(bwcVersion)) {
dependsOn yamlRestTest
}
}

tasks.named("yamlRestTest") {
enabled = false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.downsample;

import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;

import org.elasticsearch.test.cluster.ElasticsearchCluster;
import org.elasticsearch.test.cluster.local.distribution.DistributionType;
import org.elasticsearch.test.cluster.util.Version;
import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate;
import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase;
import org.junit.ClassRule;

public class MixedClusterDownsampleRestIT extends ESClientYamlSuiteTestCase {

@ClassRule
public static ElasticsearchCluster cluster = ElasticsearchCluster.local()
.distribution(DistributionType.DEFAULT)
.withNode(node -> node.version(getOldVersion()))
.withNode(node -> node.version(Version.CURRENT))
.setting("xpack.security.enabled", "false")
.setting("xpack.license.self_generated.type", "trial")
.build();

static Version getOldVersion() {
return Version.fromString(System.getProperty("tests.old_cluster_version"));
}

@Override
protected String getTestRestCluster() {
return cluster.getHttpAddresses();
}

public MixedClusterDownsampleRestIT(final ClientYamlTestCandidate testCandidate) {
super(testCandidate);
}

@ParametersFactory
public static Iterable<Object[]> parameters() throws Exception {
return ESClientYamlSuiteTestCase.createParameters();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
setup:
- skip:
version: " - 8.4.99"
reason: "rollup renamed to downsample in 8.5.0"

- do:
indices.create:
index: test
body:
settings:
number_of_shards: 1
index:
mode: time_series
routing_path: [metricset, k8s.pod.uid]
time_series:
start_time: 2021-04-28T00:00:00Z
end_time: 2021-04-29T00:00:00Z
mappings:
properties:
"@timestamp":
type: date
metricset:
type: keyword
time_series_dimension: true
k8s:
properties:
pod:
properties:
uid:
type: keyword
time_series_dimension: true
name:
type: keyword
created_at:
type: date_nanos
running:
type: boolean
number_of_containers:
type: integer
ip:
type: ip
tags:
type: keyword
values:
type: integer
multi-counter:
type: long
time_series_metric: counter
scaled-counter:
type: scaled_float
scaling_factor: 100
time_series_metric: counter
multi-gauge:
type: integer
time_series_metric: gauge
scaled-gauge:
type: scaled_float
scaling_factor: 100
time_series_metric: gauge
network:
properties:
tx:
type: long
time_series_metric: gauge
rx:
type: long
time_series_metric: gauge
- do:
bulk:
refresh: true
index: test
body:
- '{"index": {}}'
- '{"@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]}}}'
- '{"index": {}}'
- '{"@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]}}}'
- '{"index": {}}'
- '{"@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]}}}'
- '{"index": {}}'
- '{"@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]}}}'
- '{"index": {}}'
- '{"@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]}}}'
- '{"index": {}}'
- '{"@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]}}}'
- '{"index": {}}'
- '{"@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]}}}'
- '{"index": {}}'
- '{"@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]}}}'

- do:
indices.put_settings:
index: test
body:
index.blocks.write: true

---
"Downsample index":

- do:
indices.downsample:
index: test
target_index: test-downsample
body: >
{
"fixed_interval": "1h"
}
- is_true: acknowledged

- do:
search:
index: test-downsample
body:
sort: [ "@timestamp" ]

- length: { hits.hits: 4 }
- match: { hits.hits.0._source._doc_count: 2 }
- match: { hits.hits.0._source.metricset: pod }

# Assert rollup index settings
- do:
indices.get_settings:
index: test-downsample

- match: { test-downsample.settings.index.mode: time_series }
- match: { test-downsample.settings.index.time_series.end_time: 2021-04-29T00:00:00Z }
- match: { test-downsample.settings.index.time_series.start_time: 2021-04-28T00:00:00Z }
- match: { test-downsample.settings.index.routing_path: [ "metricset", "k8s.pod.uid"] }
- match: { test-downsample.settings.index.downsample.source.name: test }

# Assert rollup index mapping
- do:
indices.get_mapping:
index: test-downsample

- match: { [email protected]: date }
- match: { [email protected]_interval: 1h }
- match: { [email protected]_zone: UTC }
- match: { test-downsample.mappings.properties.k8s.properties.pod.properties.multi-gauge.type: aggregate_metric_double }
- match: { test-downsample.mappings.properties.k8s.properties.pod.properties.multi-gauge.metrics: [ "min", "max", "sum", "value_count" ] }
- match: { test-downsample.mappings.properties.k8s.properties.pod.properties.multi-gauge.default_metric: max }
- match: { test-downsample.mappings.properties.k8s.properties.pod.properties.multi-gauge.time_series_metric: gauge }
- match: { test-downsample.mappings.properties.k8s.properties.pod.properties.multi-counter.type: long }
- match: { test-downsample.mappings.properties.k8s.properties.pod.properties.multi-counter.time_series_metric: counter }
- match: { test-downsample.mappings.properties.k8s.properties.pod.properties.scaled-counter.type: scaled_float }
- match: { test-downsample.mappings.properties.k8s.properties.pod.properties.scaled-counter.scaling_factor: 100 }
- match: { test-downsample.mappings.properties.k8s.properties.pod.properties.scaled-counter.time_series_metric: counter }
- match: { test-downsample.mappings.properties.k8s.properties.pod.properties.scaled-gauge.type: aggregate_metric_double }
- match: { test-downsample.mappings.properties.k8s.properties.pod.properties.scaled-gauge.metrics: [ "min", "max", "sum", "value_count" ] }
- match: { test-downsample.mappings.properties.k8s.properties.pod.properties.scaled-gauge.default_metric: max }
- match: { test-downsample.mappings.properties.k8s.properties.pod.properties.scaled-gauge.time_series_metric: gauge }
- match: { test-downsample.mappings.properties.k8s.properties.pod.properties.uid.type: keyword }
- match: { test-downsample.mappings.properties.k8s.properties.pod.properties.uid.time_series_dimension: true }
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,15 @@ public XContentBuilder buildDownsampleDocument() throws IOException {
fieldProducer.write(builder);
}

if (dimensions.length == 0) {
logger.debug("extracting dimensions from legacy tsid");
Map<?, ?> dimensions = (Map<?, ?>) DocValueFormat.TIME_SERIES_ID.format(tsid);
for (Map.Entry<?, ?> e : dimensions.entrySet()) {
assert e.getValue() != null;
builder.field((String) e.getKey(), e.getValue());
}
}

builder.endObject();
return builder;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.elasticsearch.TransportVersion;
import org.elasticsearch.TransportVersions;
import org.elasticsearch.action.downsample.DownsampleConfig;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.index.shard.ShardId;
Expand All @@ -36,6 +37,7 @@ public record DownsampleShardTaskParams(
String[] dimensions
) implements PersistentTaskParams {

private static final TransportVersion V_8_13_0 = TransportVersions.ML_MODEL_IN_SERVICE_SETTINGS;
public static final String NAME = DownsampleShardTask.TASK_NAME;
private static final ParseField DOWNSAMPLE_CONFIG = new ParseField("downsample_config");
private static final ParseField DOWNSAMPLE_INDEX = new ParseField("rollup_index");
Expand Down Expand Up @@ -71,7 +73,7 @@ public record DownsampleShardTaskParams(
new ShardId(in),
in.readStringArray(),
in.readStringArray(),
in.readOptionalStringArray()
in.getTransportVersion().onOrAfter(V_8_13_0) ? in.readOptionalStringArray() : new String[] {}
);
}

Expand All @@ -85,7 +87,9 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
builder.field(SHARD_ID.getPreferredName(), shardId);
builder.array(METRICS.getPreferredName(), metrics);
builder.array(LABELS.getPreferredName(), labels);
builder.array(DIMENSIONS.getPreferredName(), dimensions);
if (dimensions.length > 0) {
builder.array(DIMENSIONS.getPreferredName(), dimensions);
}
return builder.endObject();
}

Expand All @@ -108,7 +112,9 @@ public void writeTo(StreamOutput out) throws IOException {
shardId.writeTo(out);
out.writeStringArray(metrics);
out.writeStringArray(labels);
out.writeOptionalStringArray(dimensions);
if (out.getTransportVersion().onOrAfter(V_8_13_0)) {
out.writeOptionalStringArray(dimensions);
}
}

public static DownsampleShardTaskParams fromXContent(XContentParser parser) throws IOException {
Expand Down Expand Up @@ -157,7 +163,7 @@ public static class Builder {
ShardId shardId;
String[] metrics;
String[] labels;
String[] dimensions;
String[] dimensions = Strings.EMPTY_ARRAY;

public Builder downsampleConfig(final DownsampleConfig downsampleConfig) {
this.downsampleConfig = downsampleConfig;
Expand Down Expand Up @@ -212,4 +218,9 @@ public DownsampleShardTaskParams build() {
);
}
}

@Override
public String toString() {
return Strings.toString(this, true, true);
}
}
Loading

0 comments on commit 9287f29

Please sign in to comment.