|
11 | 11 | import org.apache.logging.log4j.Logger; |
12 | 12 | import org.elasticsearch.ElasticsearchException; |
13 | 13 | import org.elasticsearch.action.DocWriteRequest; |
| 14 | +import org.elasticsearch.action.admin.cluster.stats.MappingVisitor; |
| 15 | +import org.elasticsearch.action.admin.indices.get.GetIndexResponse; |
14 | 16 | import org.elasticsearch.action.admin.indices.template.put.TransportPutComposableIndexTemplateAction; |
15 | 17 | import org.elasticsearch.action.bulk.BulkItemResponse; |
16 | 18 | import org.elasticsearch.action.bulk.BulkRequestBuilder; |
17 | 19 | import org.elasticsearch.action.bulk.BulkResponse; |
18 | 20 | import org.elasticsearch.action.datastreams.lifecycle.PutDataStreamLifecycleAction; |
| 21 | +import org.elasticsearch.action.downsample.DownsampleConfig; |
19 | 22 | import org.elasticsearch.action.index.IndexRequest; |
20 | 23 | import org.elasticsearch.action.support.WriteRequest; |
21 | 24 | import org.elasticsearch.cluster.metadata.ComposableIndexTemplate; |
22 | 25 | import org.elasticsearch.cluster.metadata.DataStreamLifecycle; |
23 | 26 | import org.elasticsearch.cluster.metadata.IndexMetadata; |
| 27 | +import org.elasticsearch.cluster.metadata.Metadata; |
24 | 28 | import org.elasticsearch.cluster.metadata.Template; |
25 | 29 | import org.elasticsearch.common.Strings; |
26 | 30 | import org.elasticsearch.common.compress.CompressedXContent; |
|
31 | 35 | import org.elasticsearch.index.IndexMode; |
32 | 36 | import org.elasticsearch.index.IndexSettings; |
33 | 37 | import org.elasticsearch.index.engine.VersionConflictEngineException; |
| 38 | +import org.elasticsearch.index.mapper.DateFieldMapper; |
| 39 | +import org.elasticsearch.index.mapper.MapperService; |
| 40 | +import org.elasticsearch.index.mapper.TimeSeriesParams; |
| 41 | +import org.elasticsearch.indices.IndicesService; |
34 | 42 | import org.elasticsearch.plugins.Plugin; |
35 | 43 | import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramInterval; |
36 | 44 | import org.elasticsearch.test.ESIntegTestCase; |
|
44 | 52 | import java.time.ZoneId; |
45 | 53 | import java.util.ArrayList; |
46 | 54 | import java.util.Collection; |
| 55 | +import java.util.HashMap; |
| 56 | +import java.util.HashSet; |
47 | 57 | import java.util.List; |
48 | 58 | import java.util.Locale; |
49 | 59 | import java.util.Map; |
| 60 | +import java.util.Set; |
| 61 | +import java.util.concurrent.atomic.AtomicBoolean; |
50 | 62 | import java.util.function.Supplier; |
51 | 63 |
|
| 64 | +import static org.elasticsearch.index.mapper.TimeSeriesParams.TIME_SERIES_DIMENSION_PARAM; |
| 65 | +import static org.elasticsearch.index.mapper.TimeSeriesParams.TIME_SERIES_METRIC_PARAM; |
52 | 66 | import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; |
| 67 | +import static org.hamcrest.Matchers.arrayContaining; |
| 68 | +import static org.hamcrest.Matchers.equalTo; |
53 | 69 |
|
54 | 70 | /** |
55 | 71 | * Base test case for downsampling integration tests. It provides helper methods to: |
@@ -237,4 +253,85 @@ String randomDateForInterval(final DateHistogramInterval interval, final long st |
237 | 253 | String randomDateForRange(long start, long end) { |
238 | 254 | return DATE_FORMATTER.formatMillis(randomLongBetween(start, end)); |
239 | 255 | } |
| 256 | + |
| 257 | + /** |
| 258 | + * Currently we assert the correctness of metrics and dimensions. The assertions can be extended when needed. |
| 259 | + */ |
| 260 | + @SuppressWarnings("unchecked") |
| 261 | + void assertDownsampleIndexFieldsAndDimensions(String sourceIndex, String downsampleIndex, DownsampleConfig config) throws Exception { |
| 262 | + GetIndexResponse getIndexResponse = indicesAdmin().prepareGetIndex(TEST_REQUEST_TIMEOUT) |
| 263 | + .setIndices(sourceIndex, downsampleIndex) |
| 264 | + .get(); |
| 265 | + assertThat(getIndexResponse.indices(), arrayContaining(sourceIndex, downsampleIndex)); |
| 266 | + |
| 267 | + // Retrieve field information for the metric fields |
| 268 | + final Map<String, Object> sourceIndexMappings = getIndexResponse.mappings().get(sourceIndex).getSourceAsMap(); |
| 269 | + final Map<String, Object> downsampleIndexMappings = getIndexResponse.mappings().get(downsampleIndex).getSourceAsMap(); |
| 270 | + |
| 271 | + final MapperService mapperService = getMapperServiceForIndex(sourceIndex); |
| 272 | + final CompressedXContent sourceIndexCompressedXContent = new CompressedXContent(sourceIndexMappings); |
| 273 | + mapperService.merge(MapperService.SINGLE_MAPPING_NAME, sourceIndexCompressedXContent, MapperService.MergeReason.INDEX_TEMPLATE); |
| 274 | + |
| 275 | + // Collect expected mappings for fields and dimensions |
| 276 | + Map<String, TimeSeriesParams.MetricType> metricFields = new HashMap<>(); |
| 277 | + Map<String, String> dimensionFields = new HashMap<>(); |
| 278 | + MappingVisitor.visitMapping(sourceIndexMappings, (field, fieldMapping) -> { |
| 279 | + if (isTimeSeriesMetric(fieldMapping)) { |
| 280 | + metricFields.put(field, TimeSeriesParams.MetricType.fromString(fieldMapping.get(TIME_SERIES_METRIC_PARAM).toString())); |
| 281 | + } else if (hasTimeSeriesDimensionTrue(fieldMapping)) { |
| 282 | + // This includes passthrough objects |
| 283 | + dimensionFields.put(field, fieldMapping.get("type").toString()); |
| 284 | + } |
| 285 | + }); |
| 286 | + |
| 287 | + AtomicBoolean encounteredTimestamp = new AtomicBoolean(false); |
| 288 | + Set<String> encounteredMetrics = new HashSet<>(); |
| 289 | + Set<String> encounteredDimensions = new HashSet<>(); |
| 290 | + MappingVisitor.visitMapping(downsampleIndexMappings, (field, fieldMapping) -> { |
| 291 | + if (field.equals(config.getTimestampField())) { |
| 292 | + encounteredTimestamp.set(true); |
| 293 | + assertThat(fieldMapping.get("type"), equalTo(DateFieldMapper.CONTENT_TYPE)); |
| 294 | + Map<String, Object> dateTimeMeta = (Map<String, Object>) fieldMapping.get("meta"); |
| 295 | + assertThat(dateTimeMeta.get("time_zone"), equalTo(config.getTimeZone())); |
| 296 | + assertThat(dateTimeMeta.get(config.getIntervalType()), equalTo(config.getInterval().toString())); |
| 297 | + } else if (metricFields.containsKey(field)) { |
| 298 | + encounteredMetrics.add(field); |
| 299 | + TimeSeriesParams.MetricType metricType = metricFields.get(field); |
| 300 | + switch (metricType) { |
| 301 | + case COUNTER -> assertThat(fieldMapping.get("type"), equalTo("double")); |
| 302 | + case GAUGE -> assertThat(fieldMapping.get("type"), equalTo("aggregate_metric_double")); |
| 303 | + default -> fail("Unsupported field type"); |
| 304 | + } |
| 305 | + assertThat(fieldMapping.get("time_series_metric"), equalTo(metricType.toString())); |
| 306 | + } else if (dimensionFields.containsKey(field)) { |
| 307 | + encounteredDimensions.add(field); |
| 308 | + assertThat(fieldMapping.get("type"), equalTo(dimensionFields.get(field))); |
| 309 | + assertThat(fieldMapping.get("time_series_dimension"), equalTo(true)); |
| 310 | + } |
| 311 | + }); |
| 312 | + assertThat(encounteredTimestamp.get(), equalTo(true)); |
| 313 | + assertThat(encounteredMetrics, equalTo(metricFields.keySet())); |
| 314 | + assertThat(encounteredDimensions, equalTo(dimensionFields.keySet())); |
| 315 | + } |
| 316 | + |
| 317 | + private static MapperService getMapperServiceForIndex(String sourceIndex) throws IOException { |
| 318 | + final IndexMetadata indexMetadata = clusterAdmin().prepareState(TEST_REQUEST_TIMEOUT) |
| 319 | + .get() |
| 320 | + .getState() |
| 321 | + .getMetadata() |
| 322 | + .getProject(Metadata.DEFAULT_PROJECT_ID) |
| 323 | + .index(sourceIndex); |
| 324 | + final IndicesService indicesService = internalCluster().getAnyMasterNodeInstance(IndicesService.class); |
| 325 | + return indicesService.createIndexMapperServiceForValidation(indexMetadata); |
| 326 | + } |
| 327 | + |
| 328 | + boolean isTimeSeriesMetric(final Map<String, ?> fieldMapping) { |
| 329 | + final String metricType = (String) fieldMapping.get(TIME_SERIES_METRIC_PARAM); |
| 330 | + return metricType != null |
| 331 | + && List.of(TimeSeriesParams.MetricType.values()).contains(TimeSeriesParams.MetricType.fromString(metricType)); |
| 332 | + } |
| 333 | + |
| 334 | + private static boolean hasTimeSeriesDimensionTrue(Map<String, ?> fieldMapping) { |
| 335 | + return Boolean.TRUE.equals(fieldMapping.get(TIME_SERIES_DIMENSION_PARAM)); |
| 336 | + } |
240 | 337 | } |
0 commit comments