فهرست منبع

Fix downsample persistent task params serialization bwc (#106878)

Missing a check on the transport version results in unreadable cluster state
if it includes a serialized instance of DownsampleShardTaskParams.
#98023 introduced an optional string array including dimensions used by time
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
Salvatore Campagna 1 سال پیش
والد
کامیت
f7fedb4d0a

+ 5 - 0
docs/changelog/106878.yaml

@@ -0,0 +1,5 @@
+pr: 106878
+summary: Gate reading of optional string array for bwc
+area: Downsampling
+type: bug
+issues: []

+ 1 - 0
server/src/main/java/org/elasticsearch/TransportVersions.java

@@ -133,6 +133,7 @@ public class TransportVersions {
     public static final TransportVersion INDEX_REQUEST_NORMALIZED_BYTES_PARSED = def(8_593_00_0);
     public static final TransportVersion INGEST_GRAPH_STRUCTURE_EXCEPTION = def(8_594_00_0);
     public static final TransportVersion ML_MODEL_IN_SERVICE_SETTINGS = def(8_595_00_0);
+    // 8.14.0+
     public static final TransportVersion RANDOM_AGG_SHARD_SEED = def(8_596_00_0);
     public static final TransportVersion ESQL_TIMINGS = def(8_597_00_0);
     public static final TransportVersion DATA_STREAM_AUTO_SHARDING_EVENT = def(8_598_00_0);

+ 49 - 0
x-pack/plugin/downsample/qa/mixed-cluster/build.gradle

@@ -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
+}

+ 48 - 0
x-pack/plugin/downsample/qa/mixed-cluster/src/yamlRestTest/java/org/elasticsearch/xpack/downsample/MixedClusterDownsampleRestIT.java

@@ -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();
+    }
+
+}

+ 152 - 0
x-pack/plugin/downsample/qa/mixed-cluster/src/yamlRestTest/resources/rest-api-spec/test/downsample/10_basic.yml

@@ -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: { test-downsample.mappings.properties.@timestamp.type: date }
+  - match: { test-downsample.mappings.properties.@timestamp.meta.fixed_interval: 1h }
+  - match: { test-downsample.mappings.properties.@timestamp.meta.time_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 }

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

@@ -564,6 +564,15 @@ class DownsampleShardIndexer {
                 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;
         }

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

@@ -10,6 +10,7 @@ package org.elasticsearch.xpack.downsample;
 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;
@@ -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");
@@ -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[] {}
         );
     }
 
@@ -85,7 +87,9 @@ public record DownsampleShardTaskParams(
         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();
     }
 
@@ -108,7 +112,9 @@ public record DownsampleShardTaskParams(
         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 {
@@ -157,7 +163,7 @@ public record DownsampleShardTaskParams(
         ShardId shardId;
         String[] metrics;
         String[] labels;
-        String[] dimensions;
+        String[] dimensions = Strings.EMPTY_ARRAY;
 
         public Builder downsampleConfig(final DownsampleConfig downsampleConfig) {
             this.downsampleConfig = downsampleConfig;
@@ -212,4 +218,9 @@ public record DownsampleShardTaskParams(
             );
         }
     }
+
+    @Override
+    public String toString() {
+        return Strings.toString(this, true, true);
+    }
 }

+ 134 - 0
x-pack/plugin/downsample/src/test/java/org/elasticsearch/xpack/downsample/DownsampleShardTaskParamsTests.java

@@ -0,0 +1,134 @@
+/*
+ * 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 org.elasticsearch.action.downsample.DownsampleConfig;
+import org.elasticsearch.common.io.stream.Writeable;
+import org.elasticsearch.index.Index;
+import org.elasticsearch.index.shard.ShardId;
+import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval;
+import org.elasticsearch.test.AbstractXContentSerializingTestCase;
+import org.elasticsearch.xcontent.XContentParser;
+
+import java.io.IOException;
+
+public class DownsampleShardTaskParamsTests extends AbstractXContentSerializingTestCase<DownsampleShardTaskParams> {
+    @Override
+    protected Writeable.Reader<DownsampleShardTaskParams> instanceReader() {
+        return DownsampleShardTaskParams::new;
+    }
+
+    @Override
+    protected DownsampleShardTaskParams createTestInstance() {
+        long startTime = randomLongBetween(100000, 200000);
+        long endTime = startTime + randomLongBetween(1000, 10_000);
+        String[] dimensions = randomBoolean() ? generateRandomStringArray(5, 5, false, true) : new String[] {};
+        return new DownsampleShardTaskParams(
+            new DownsampleConfig(randomFrom(DateHistogramInterval.HOUR, DateHistogramInterval.DAY)),
+            randomAlphaOfLength(5),
+            startTime,
+            endTime,
+            new ShardId(new Index(randomAlphaOfLength(5), "n/a"), between(0, 5)),
+            generateRandomStringArray(5, 5, false, false),
+            generateRandomStringArray(5, 5, false, false),
+            dimensions
+        );
+    }
+
+    @Override
+    protected DownsampleShardTaskParams mutateInstance(DownsampleShardTaskParams in) throws IOException {
+        return switch (between(0, 7)) {
+            case 0 -> new DownsampleShardTaskParams(
+                new DownsampleConfig(randomFrom(DateHistogramInterval.WEEK, DateHistogramInterval.MONTH)),
+                in.downsampleIndex(),
+                in.indexStartTimeMillis(),
+                in.indexEndTimeMillis(),
+                in.shardId(),
+                in.metrics(),
+                in.labels(),
+                in.dimensions()
+            );
+            case 1 -> new DownsampleShardTaskParams(
+                in.downsampleConfig(),
+                randomAlphaOfLength(6),
+                in.indexStartTimeMillis(),
+                in.indexEndTimeMillis(),
+                in.shardId(),
+                in.metrics(),
+                in.labels(),
+                in.dimensions()
+            );
+            case 2 -> new DownsampleShardTaskParams(
+                in.downsampleConfig(),
+                in.downsampleIndex(),
+                in.indexStartTimeMillis() + between(1, 100),
+                in.indexEndTimeMillis() + between(1, 100),
+                in.shardId(),
+                in.metrics(),
+                in.labels(),
+                in.dimensions()
+            );
+            case 3 -> new DownsampleShardTaskParams(
+                in.downsampleConfig(),
+                in.downsampleIndex(),
+                in.indexStartTimeMillis(),
+                in.indexEndTimeMillis() + between(10, 100),
+                new ShardId(new Index(randomAlphaOfLength(6), "n/a"), between(0, 5)),
+                in.metrics(),
+                in.labels(),
+                in.dimensions()
+            );
+            case 4 -> new DownsampleShardTaskParams(
+                in.downsampleConfig(),
+                in.downsampleIndex(),
+                in.indexStartTimeMillis(),
+                in.indexEndTimeMillis() + between(10, 100),
+                in.shardId(),
+                in.metrics(),
+                in.labels(),
+                in.dimensions()
+            );
+            case 5 -> new DownsampleShardTaskParams(
+                in.downsampleConfig(),
+                in.downsampleIndex(),
+                in.indexStartTimeMillis(),
+                in.indexEndTimeMillis(),
+                in.shardId(),
+                generateRandomStringArray(6, 6, false, false),
+                in.labels(),
+                in.dimensions()
+            );
+            case 6 -> new DownsampleShardTaskParams(
+                in.downsampleConfig(),
+                in.downsampleIndex(),
+                in.indexStartTimeMillis(),
+                in.indexEndTimeMillis(),
+                in.shardId(),
+                in.metrics(),
+                generateRandomStringArray(6, 6, false, false),
+                in.dimensions()
+            );
+            case 7 -> new DownsampleShardTaskParams(
+                in.downsampleConfig(),
+                in.downsampleIndex(),
+                in.indexStartTimeMillis(),
+                in.indexEndTimeMillis(),
+                in.shardId(),
+                in.metrics(),
+                in.labels(),
+                generateRandomStringArray(6, 6, false, false)
+            );
+            default -> throw new AssertionError("unknown option");
+        };
+    }
+
+    @Override
+    protected DownsampleShardTaskParams doParseInstance(XContentParser parser) throws IOException {
+        return DownsampleShardTaskParams.fromXContent(parser);
+    }
+}