|  | 
| 8 | 8 | package org.elasticsearch.datastreams; | 
| 9 | 9 | 
 | 
| 10 | 10 | import org.elasticsearch.client.Request; | 
|  | 11 | +import org.elasticsearch.client.ResponseException; | 
| 11 | 12 | import org.elasticsearch.common.time.DateFormatter; | 
| 12 | 13 | import org.elasticsearch.common.time.FormatNames; | 
| 13 | 14 | import org.elasticsearch.test.rest.ESRestTestCase; | 
| 14 | 15 | import org.elasticsearch.test.rest.yaml.ObjectPath; | 
| 15 | 16 | 
 | 
| 16 | 17 | import java.io.IOException; | 
| 17 | 18 | import java.time.Instant; | 
|  | 19 | +import java.time.temporal.ChronoUnit; | 
| 18 | 20 | import java.util.Map; | 
| 19 | 21 | 
 | 
| 20 | 22 | import static org.elasticsearch.cluster.metadata.DataStreamTestHelper.backingIndexEqualTo; | 
| 21 | 23 | import static org.hamcrest.Matchers.aMapWithSize; | 
| 22 | 24 | import static org.hamcrest.Matchers.contains; | 
|  | 25 | +import static org.hamcrest.Matchers.containsString; | 
| 23 | 26 | import static org.hamcrest.Matchers.empty; | 
| 24 | 27 | import static org.hamcrest.Matchers.equalTo; | 
| 25 | 28 | import static org.hamcrest.Matchers.hasSize; | 
| 26 | 29 | import static org.hamcrest.Matchers.is; | 
| 27 | 30 | import static org.hamcrest.Matchers.notNullValue; | 
|  | 31 | +import static org.hamcrest.Matchers.nullValue; | 
| 28 | 32 | 
 | 
| 29 | 33 | public class TsdbDataStreamRestIT extends ESRestTestCase { | 
| 30 | 34 | 
 | 
| @@ -84,6 +88,57 @@ public class TsdbDataStreamRestIT extends ESRestTestCase { | 
| 84 | 88 |             } | 
| 85 | 89 |         }"""; | 
| 86 | 90 | 
 | 
|  | 91 | +    private static final String NON_TSDB_TEMPLATE = """ | 
|  | 92 | +        { | 
|  | 93 | +            "index_patterns": ["k8s*"], | 
|  | 94 | +            "template": { | 
|  | 95 | +                "settings":{ | 
|  | 96 | +                    "index": { | 
|  | 97 | +                        "number_of_replicas": 0, | 
|  | 98 | +                        "number_of_shards": 2 | 
|  | 99 | +                    } | 
|  | 100 | +                }, | 
|  | 101 | +                "mappings":{ | 
|  | 102 | +                    "properties": { | 
|  | 103 | +                        "@timestamp" : { | 
|  | 104 | +                            "type": "date" | 
|  | 105 | +                        }, | 
|  | 106 | +                        "metricset": { | 
|  | 107 | +                            "type": "keyword" | 
|  | 108 | +                        }, | 
|  | 109 | +                        "k8s": { | 
|  | 110 | +                            "properties": { | 
|  | 111 | +                                "pod": { | 
|  | 112 | +                                    "properties": { | 
|  | 113 | +                                        "uid": { | 
|  | 114 | +                                            "type": "keyword" | 
|  | 115 | +                                        }, | 
|  | 116 | +                                        "name": { | 
|  | 117 | +                                            "type": "keyword" | 
|  | 118 | +                                        }, | 
|  | 119 | +                                        "ip": { | 
|  | 120 | +                                            "type": "ip" | 
|  | 121 | +                                        }, | 
|  | 122 | +                                        "network": { | 
|  | 123 | +                                            "properties": { | 
|  | 124 | +                                                "tx": { | 
|  | 125 | +                                                    "type": "long" | 
|  | 126 | +                                                }, | 
|  | 127 | +                                                "rx": { | 
|  | 128 | +                                                    "type": "long" | 
|  | 129 | +                                                } | 
|  | 130 | +                                            } | 
|  | 131 | +                                        } | 
|  | 132 | +                                    } | 
|  | 133 | +                                } | 
|  | 134 | +                            } | 
|  | 135 | +                        } | 
|  | 136 | +                    } | 
|  | 137 | +                } | 
|  | 138 | +            }, | 
|  | 139 | +            "data_stream": {} | 
|  | 140 | +        }"""; | 
|  | 141 | + | 
| 87 | 142 |     private static final String DOC = """ | 
| 88 | 143 |         { | 
| 89 | 144 |             "@timestamp": "$time", | 
| @@ -235,6 +290,82 @@ public void testSubsequentRollovers() throws Exception { | 
| 235 | 290 |         } | 
| 236 | 291 |     } | 
| 237 | 292 | 
 | 
|  | 293 | +    public void testMigrateRegularDataStreamToTsdbDataStream() throws Exception { | 
|  | 294 | +        // Create a non tsdb template | 
|  | 295 | +        var putComposableIndexTemplateRequest = new Request("POST", "/_index_template/1"); | 
|  | 296 | +        putComposableIndexTemplateRequest.setJsonEntity(NON_TSDB_TEMPLATE); | 
|  | 297 | +        assertOK(client().performRequest(putComposableIndexTemplateRequest)); | 
|  | 298 | + | 
|  | 299 | +        // Index a few docs and sometimes rollover | 
|  | 300 | +        int numRollovers = 4; | 
|  | 301 | +        int numDocs = 32; | 
|  | 302 | +        var currentTime = Instant.now(); | 
|  | 303 | +        var currentMinus30Days = currentTime.minus(30, ChronoUnit.DAYS); | 
|  | 304 | +        for (int i = 0; i < numRollovers; i++) { | 
|  | 305 | +            for (int j = 0; j < numDocs; j++) { | 
|  | 306 | +                var indexRequest = new Request("POST", "/k8s/_doc"); | 
|  | 307 | +                var time = Instant.ofEpochMilli(randomLongBetween(currentMinus30Days.toEpochMilli(), currentTime.toEpochMilli())); | 
|  | 308 | +                indexRequest.setJsonEntity(DOC.replace("$time", formatInstant(time))); | 
|  | 309 | +                var response = client().performRequest(indexRequest); | 
|  | 310 | +                assertOK(response); | 
|  | 311 | +                var responseBody = entityAsMap(response); | 
|  | 312 | +                // i rollovers and +1 offset: | 
|  | 313 | +                assertThat((String) responseBody.get("_index"), backingIndexEqualTo("k8s", i + 1)); | 
|  | 314 | +            } | 
|  | 315 | +            var rolloverRequest = new Request("POST", "/k8s/_rollover"); | 
|  | 316 | +            var rolloverResponse = client().performRequest(rolloverRequest); | 
|  | 317 | +            assertOK(rolloverResponse); | 
|  | 318 | +            var rolloverResponseBody = entityAsMap(rolloverResponse); | 
|  | 319 | +            assertThat(rolloverResponseBody.get("rolled_over"), is(true)); | 
|  | 320 | +        } | 
|  | 321 | + | 
|  | 322 | +        var getDataStreamsRequest = new Request("GET", "/_data_stream"); | 
|  | 323 | +        var getDataStreamResponse = client().performRequest(getDataStreamsRequest); | 
|  | 324 | +        assertOK(getDataStreamResponse); | 
|  | 325 | +        var dataStreams = entityAsMap(getDataStreamResponse); | 
|  | 326 | +        assertThat(ObjectPath.evaluate(dataStreams, "data_streams.0.name"), equalTo("k8s")); | 
|  | 327 | +        assertThat(ObjectPath.evaluate(dataStreams, "data_streams.0.generation"), equalTo(5)); | 
|  | 328 | +        for (int i = 0; i < 5; i++) { | 
|  | 329 | +            String backingIndex = ObjectPath.evaluate(dataStreams, "data_streams.0.indices." + i + ".index_name"); | 
|  | 330 | +            assertThat(backingIndex, backingIndexEqualTo("k8s", i + 1)); | 
|  | 331 | +            var indices = getIndex(backingIndex); | 
|  | 332 | +            var escapedBackingIndex = backingIndex.replace(".", "\\."); | 
|  | 333 | +            assertThat(ObjectPath.evaluate(indices, escapedBackingIndex + ".data_stream"), equalTo("k8s")); | 
|  | 334 | +            assertThat(ObjectPath.evaluate(indices, escapedBackingIndex + ".settings.index.mode"), nullValue()); | 
|  | 335 | +            assertThat(ObjectPath.evaluate(indices, escapedBackingIndex + ".settings.index.time_series.start_time"), nullValue()); | 
|  | 336 | +            assertThat(ObjectPath.evaluate(indices, escapedBackingIndex + ".settings.index.time_series.end_time"), nullValue()); | 
|  | 337 | +        } | 
|  | 338 | + | 
|  | 339 | +        // Update template | 
|  | 340 | +        putComposableIndexTemplateRequest = new Request("POST", "/_index_template/1"); | 
|  | 341 | +        putComposableIndexTemplateRequest.setJsonEntity(TEMPLATE); | 
|  | 342 | +        assertOK(client().performRequest(putComposableIndexTemplateRequest)); | 
|  | 343 | + | 
|  | 344 | +        var rolloverRequest = new Request("POST", "/k8s/_rollover"); | 
|  | 345 | +        var rolloverResponse = client().performRequest(rolloverRequest); | 
|  | 346 | +        assertOK(rolloverResponse); | 
|  | 347 | +        var rolloverResponseBody = entityAsMap(rolloverResponse); | 
|  | 348 | +        assertThat(rolloverResponseBody.get("rolled_over"), is(true)); | 
|  | 349 | +        var newIndex = (String) rolloverResponseBody.get("new_index"); | 
|  | 350 | +        assertThat(newIndex, backingIndexEqualTo("k8s", 6)); | 
|  | 351 | + | 
|  | 352 | +        // Ingest documents that will land in the new tsdb backing index: | 
|  | 353 | +        for (int i = 0; i < numDocs; i++) { | 
|  | 354 | +            var indexRequest = new Request("POST", "/k8s/_doc"); | 
|  | 355 | +            indexRequest.setJsonEntity(DOC.replace("$time", formatInstant(currentTime))); | 
|  | 356 | +            var response = client().performRequest(indexRequest); | 
|  | 357 | +            assertOK(response); | 
|  | 358 | +            var responseBody = entityAsMap(response); | 
|  | 359 | +            assertThat((String) responseBody.get("_index"), backingIndexEqualTo("k8s", 6)); | 
|  | 360 | +        } | 
|  | 361 | + | 
|  | 362 | +        // Fail if documents target older non tsdb backing index: | 
|  | 363 | +        var indexRequest = new Request("POST", "/k8s/_doc"); | 
|  | 364 | +        indexRequest.setJsonEntity(DOC.replace("$time", formatInstant(currentMinus30Days))); | 
|  | 365 | +        var e = expectThrows(ResponseException.class, () -> client().performRequest(indexRequest)); | 
|  | 366 | +        assertThat(e.getMessage(), containsString("is outside of ranges of currently writable indices")); | 
|  | 367 | +    } | 
|  | 368 | + | 
| 238 | 369 |     private static Map<?, ?> getIndex(String indexName) throws IOException { | 
| 239 | 370 |         var getIndexRequest = new Request("GET", "/" + indexName + "?human"); | 
| 240 | 371 |         var response = client().performRequest(getIndexRequest); | 
|  | 
0 commit comments