Skip to content

Commit 815b021

Browse files
authored
Composing data lifecycle in index templates (#96027)
In this PR we introduce the composing of lifecycle templates. It follows the pattern of the other templates, for example, in the following case we have two lifecycle both of which have the data_retention defined. ``` [ { "lifecycle": { "data_retention" : "10d" } }, { "lifecycle": { "data_retention" : "20d" } } ] ``` The result will be { "lifecycle": { "data_retention" : "20d"}}. However, if we have the following two lifecycles: ``` [ { "lifecycle": { "data_retention" : "10d" } }, { "lifecycle": { } } ] The result will be { "lifecycle": { "data_retention" : "10d"} } because the latest lifecycle does not the data_retention defined. ```
1 parent 45443a6 commit 815b021

File tree

4 files changed

+137
-17
lines changed

4 files changed

+137
-17
lines changed

server/src/main/java/org/elasticsearch/cluster/metadata/DataLifecycle.java

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.elasticsearch.xcontent.XContentParser;
2727

2828
import java.io.IOException;
29+
import java.util.List;
2930
import java.util.Objects;
3031

3132
/**
@@ -83,6 +84,54 @@ public DataLifecycle(long timeInMills) {
8384
this(TimeValue.timeValueMillis(timeInMills));
8485
}
8586

87+
/**
88+
* This method composes a series of lifecycles to a final one. The lifecycles are getting composed one level deep,
89+
* meaning that the keys present on the latest lifecycle will override the ones of the others. If a key is missing
90+
* then it keeps the value of the previous lifecycles. For example, if we have the following two lifecycles:
91+
* [
92+
* {
93+
* "lifecycle": {
94+
* "data_retention" : "10d"
95+
* }
96+
* },
97+
* {
98+
* "lifecycle": {
99+
* "data_retention" : "20d"
100+
* }
101+
* }
102+
* ]
103+
* The result will be { "lifecycle": { "data_retention" : "20d"}} because the second data retention overrides the first.
104+
* However, if we have the following two lifecycles:
105+
* [
106+
* {
107+
* "lifecycle": {
108+
* "data_retention" : "10d"
109+
* }
110+
* },
111+
* {
112+
* "lifecycle": { }
113+
* }
114+
* ]
115+
* The result will be { "lifecycle": { "data_retention" : "10d"} } because the latest lifecycle does not have any
116+
* information on retention.
117+
* @param lifecycles a sorted list of lifecycles in the order that they will be composed
118+
* @return the final lifecycle
119+
*/
120+
@Nullable
121+
public static DataLifecycle compose(List<DataLifecycle> lifecycles) {
122+
DataLifecycle.Builder builder = null;
123+
for (DataLifecycle current : lifecycles) {
124+
if (builder == null) {
125+
builder = Builder.newBuilder(current);
126+
} else {
127+
if (current.dataRetention != null) {
128+
builder.dataRetention(current.getDataRetention());
129+
}
130+
}
131+
}
132+
return builder == null ? null : builder.build();
133+
}
134+
86135
@Nullable
87136
public TimeValue getDataRetention() {
88137
return dataRetention;
@@ -145,4 +194,25 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params, @Nulla
145194
public static DataLifecycle fromXContent(XContentParser parser) throws IOException {
146195
return PARSER.parse(parser, null);
147196
}
197+
198+
/**
199+
* This builder helps during the composition of the data lifecycle templates.
200+
*/
201+
static class Builder {
202+
@Nullable
203+
private TimeValue dataRetention = null;
204+
205+
Builder dataRetention(@Nullable TimeValue value) {
206+
dataRetention = value;
207+
return this;
208+
}
209+
210+
DataLifecycle build() {
211+
return new DataLifecycle(dataRetention);
212+
}
213+
214+
static Builder newBuilder(DataLifecycle dataLifecycle) {
215+
return new Builder().dataRetention(dataLifecycle.getDataRetention());
216+
}
217+
}
148218
}

server/src/main/java/org/elasticsearch/cluster/metadata/MetadataIndexTemplateService.java

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1423,19 +1423,22 @@ public static DataLifecycle resolveLifecycle(final Metadata metadata, final Stri
14231423
public static DataLifecycle resolveLifecycle(ComposableIndexTemplate template, Map<String, ComponentTemplate> componentTemplates) {
14241424
Objects.requireNonNull(template, "attempted to resolve lifecycle for a null template");
14251425
Objects.requireNonNull(componentTemplates, "attempted to resolve lifecycle with null component templates");
1426-
// The actual index template's lifecycle takes the highest precedence.
1426+
1427+
List<DataLifecycle> lifecycles = new ArrayList<>();
1428+
for (String componentTemplateName : template.composedOf()) {
1429+
if (componentTemplates.containsKey(componentTemplateName) == false) {
1430+
continue;
1431+
}
1432+
DataLifecycle dataLifecycle = componentTemplates.get(componentTemplateName).template().lifecycle();
1433+
if (dataLifecycle != null) {
1434+
lifecycles.add(dataLifecycle);
1435+
}
1436+
}
1437+
// The actual index template's lifecycle has the highest precedence.
14271438
if (template.template() != null && template.template().lifecycle() != null) {
1428-
return template.template().lifecycle();
1439+
lifecycles.add(template.template().lifecycle());
14291440
}
1430-
List<DataLifecycle> componentLifecycle = template.composedOf()
1431-
.stream()
1432-
.map(componentTemplates::get)
1433-
.filter(Objects::nonNull)
1434-
.map(ComponentTemplate::template)
1435-
.map(Template::lifecycle)
1436-
.filter(Objects::nonNull)
1437-
.toList();
1438-
return componentLifecycle.isEmpty() ? null : componentLifecycle.get(componentLifecycle.size() - 1);
1441+
return DataLifecycle.compose(lifecycles);
14391442
}
14401443

14411444
/**

server/src/test/java/org/elasticsearch/cluster/metadata/DataLifecycleTests.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.elasticsearch.xcontent.XContentType;
2424

2525
import java.io.IOException;
26+
import java.util.List;
2627
import java.util.Set;
2728

2829
import static org.hamcrest.Matchers.containsString;
@@ -78,6 +79,34 @@ public void testXContentSerializationWithRollover() throws IOException {
7879
}
7980
}
8081

82+
public void testLifecycleComposition() {
83+
// No lifecycles result to null
84+
{
85+
List<DataLifecycle> lifecycles = List.of();
86+
assertThat(DataLifecycle.compose(lifecycles), nullValue());
87+
}
88+
// One lifecycle results to this lifecycle as the final
89+
{
90+
DataLifecycle lifecycle = createTestInstance();
91+
List<DataLifecycle> lifecycles = List.of(lifecycle);
92+
assertThat(DataLifecycle.compose(lifecycles), equalTo(lifecycle));
93+
}
94+
// If the last lifecycle is missing a property we keep the latest from the previous ones
95+
{
96+
DataLifecycle lifecycleWithRetention = new DataLifecycle(randomMillisUpToYear9999());
97+
List<DataLifecycle> lifecycles = List.of(lifecycleWithRetention, new DataLifecycle());
98+
assertThat(DataLifecycle.compose(lifecycles).getDataRetention(), equalTo(lifecycleWithRetention.getDataRetention()));
99+
}
100+
// If both lifecycle have all properties, then the latest one overwrites all the others
101+
{
102+
DataLifecycle lifecycle1 = new DataLifecycle(randomMillisUpToYear9999());
103+
DataLifecycle lifecycle2 = new DataLifecycle(randomMillisUpToYear9999());
104+
List<DataLifecycle> lifecycles = List.of(lifecycle1, lifecycle2);
105+
assertThat(DataLifecycle.compose(lifecycles), equalTo(lifecycle2));
106+
}
107+
108+
}
109+
81110
public void testDefaultClusterSetting() {
82111
ClusterSettings clusterSettings = new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS);
83112
RolloverConfiguration rolloverConfiguration = clusterSettings.get(DataLifecycle.CLUSTER_DLM_DEFAULT_ROLLOVER_SETTING);

server/src/test/java/org/elasticsearch/cluster/metadata/MetadataIndexTemplateServiceTests.java

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1505,15 +1505,15 @@ public void testResolveLifecycle() throws Exception {
15051505
ComponentTemplate ct2 = new ComponentTemplate(new Template(null, null, null, dataLifecycle2), null, null);
15061506
ComponentTemplate ctNoLifecycle = new ComponentTemplate(new Template(null, null, null, null), null, null);
15071507

1508-
state = service.addComponentTemplate(state, true, "ct_high", ct1);
1509-
state = service.addComponentTemplate(state, true, "ct_low", ct2);
1508+
state = service.addComponentTemplate(state, true, "ct_1", ct1);
1509+
state = service.addComponentTemplate(state, true, "ct_2", ct2);
15101510
state = service.addComponentTemplate(state, true, "ct_no_lifecycle", ctNoLifecycle);
15111511
{
15121512
// Respect the order the templates are defined
15131513
ComposableIndexTemplate it = new ComposableIndexTemplate(
15141514
List.of("i1*"),
15151515
new Template(null, null, null),
1516-
List.of("ct_low", "ct_high"),
1516+
List.of("ct_2", "ct_1"),
15171517
0L,
15181518
1L,
15191519
null,
@@ -1530,7 +1530,7 @@ public void testResolveLifecycle() throws Exception {
15301530
ComposableIndexTemplate it = new ComposableIndexTemplate(
15311531
List.of("i2*"),
15321532
new Template(null, null, null),
1533-
List.of("ct_high", "ct_no_lifecycle"),
1533+
List.of("ct_1", "ct_no_lifecycle"),
15341534
0L,
15351535
1L,
15361536
null,
@@ -1548,7 +1548,7 @@ public void testResolveLifecycle() throws Exception {
15481548
ComposableIndexTemplate it = new ComposableIndexTemplate(
15491549
List.of("i3*"),
15501550
new Template(null, null, null, dataLifecycle2),
1551-
List.of("ct_no_lifecycle", "ct_high"),
1551+
List.of("ct_no_lifecycle", "ct_1"),
15521552
0L,
15531553
1L,
15541554
null,
@@ -1559,9 +1559,27 @@ public void testResolveLifecycle() throws Exception {
15591559

15601560
DataLifecycle resolvedLifecycle = MetadataIndexTemplateService.resolveLifecycle(state.metadata(), "my-template-3");
15611561

1562-
// Based on precedence only the latest
1562+
// The index template has higher precedence and overwrites the retention of the others.
15631563
assertThat(resolvedLifecycle, equalTo(dataLifecycle2));
15641564
}
1565+
{
1566+
ComposableIndexTemplate it = new ComposableIndexTemplate(
1567+
List.of("i4*"),
1568+
new Template(null, null, null, new DataLifecycle()),
1569+
List.of("ct_no_lifecycle", "ct_1"),
1570+
0L,
1571+
1L,
1572+
null,
1573+
new ComposableIndexTemplate.DataStreamTemplate(),
1574+
null
1575+
);
1576+
state = service.addIndexTemplateV2(state, true, "my-template-4", it);
1577+
1578+
DataLifecycle resolvedLifecycle = MetadataIndexTemplateService.resolveLifecycle(state.metadata(), "my-template-4");
1579+
1580+
// The index template lifecycle does not have a retention so the one from ct_1 remains unchanged.
1581+
assertThat(resolvedLifecycle, equalTo(dataLifecycle1));
1582+
}
15651583
}
15661584

15671585
public void testAddInvalidTemplate() throws Exception {

0 commit comments

Comments
 (0)