Skip to content

Commit

Permalink
Disallow changing template's index mode from time_series to something…
Browse files Browse the repository at this point in the history
… else if a tsdb data stream still matches with that template
  • Loading branch information
martijnvg committed Feb 17, 2022
1 parent ca7b0b9 commit 62cc71d
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,35 @@ public void testMigrateRegularDataStreamToTsdbDataStream() throws Exception {
assertThat(e.getMessage(), containsString("is outside of ranges of currently writable indices"));
}

public void testChangeTemplateIndexMode() throws Exception {
// Create a template
{
var putComposableIndexTemplateRequest = new Request("POST", "/_index_template/1");
putComposableIndexTemplateRequest.setJsonEntity(TEMPLATE);
assertOK(client().performRequest(putComposableIndexTemplateRequest));
}
{
var indexRequest = new Request("POST", "/k8s/_doc");
var time = Instant.now();
indexRequest.setJsonEntity(DOC.replace("$time", formatInstant(time)));
var response = client().performRequest(indexRequest);
assertOK(response);
}
{
var putComposableIndexTemplateRequest = new Request("POST", "/_index_template/1");
putComposableIndexTemplateRequest.setJsonEntity(NON_TSDB_TEMPLATE);
var e = expectThrows(ResponseException.class, () -> client().performRequest(putComposableIndexTemplateRequest));
assertThat(
e.getMessage(),
containsString(
"composable template [1] with index patterns [k8s*], priority [null],"
+ " index_mode [null] would cause tsdb data streams [k8s] to no longer match a data stream template"
+ " with a time_series index_mode"
)
);
}
}

private static Map<?, ?> getIndex(String indexName) throws IOException {
var getIndexRequest = new Request("GET", "/" + indexName + "?human");
var response = client().performRequest(getIndexRequest);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.elasticsearch.core.Nullable;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.IndexMode;
import org.elasticsearch.index.IndexService;
import org.elasticsearch.index.IndexSettingProvider;
import org.elasticsearch.index.IndexSettingProviders;
Expand Down Expand Up @@ -662,6 +663,7 @@ private void validateIndexTemplateV2(String name, ComposableIndexTemplate indexT

validate(name, templateToValidate);
validateDataStreamsStillReferenced(currentState, name, templateToValidate);
validateTsdbDataStreamsReferringTsdbTemplate(currentState, name, templateToValidate);

// Finally, right before adding the template, we need to ensure that the composite settings,
// mappings, and aliases are valid after it's been composed with the component templates
Expand Down Expand Up @@ -734,6 +736,53 @@ private static void validateDataStreamsStillReferenced(ClusterState state, Strin
}
}

// This method should be invoked after validateDataStreamsStillReferenced(...)
private static void validateTsdbDataStreamsReferringTsdbTemplate(
ClusterState state,
String templateName,
ComposableIndexTemplate newTemplate
) {
Metadata updatedMetadata = null;
Set<String> dataStreamsWithNonTsdbTemplate = null;

for (var dataStream : state.metadata().dataStreams().values()) {
if (dataStream.getIndexMode() != IndexMode.TIME_SERIES) {
continue;
}

if (updatedMetadata == null) {
updatedMetadata = Metadata.builder(state.metadata()).put(templateName, newTemplate).build();
}
var matchingTemplate = findV2Template(updatedMetadata, dataStream.getName(), false);
if (templateName.equals(matchingTemplate)) {
if (newTemplate.getDataStreamTemplate().getIndexMode() != IndexMode.TIME_SERIES) {
if (dataStreamsWithNonTsdbTemplate == null) {
dataStreamsWithNonTsdbTemplate = new HashSet<>();
}
dataStreamsWithNonTsdbTemplate.add(dataStream.getName());
}
}
}

if (dataStreamsWithNonTsdbTemplate != null) {
throw new IllegalArgumentException(
"composable template ["
+ templateName
+ "] with index patterns "
+ newTemplate.indexPatterns()
+ ", priority ["
+ newTemplate.priority()
+ "]"
+ ", index_mode ["
+ newTemplate.getDataStreamTemplate().getIndexMode()
+ "] "
+ "would cause tsdb data streams "
+ dataStreamsWithNonTsdbTemplate
+ " to no longer match a data stream template with a time_series index_mode"
);
}
}

/**
* Return a map of v1 template names to their index patterns for v1 templates that would overlap
* with the given v2 template's index patterns.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import com.fasterxml.jackson.core.JsonParseException;

import org.elasticsearch.Build;
import org.elasticsearch.ResourceNotFoundException;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionListener;
Expand All @@ -23,8 +24,10 @@
import org.elasticsearch.common.settings.IndexScopedSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.core.Tuple;
import org.elasticsearch.env.Environment;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.IndexMode;
import org.elasticsearch.index.IndexSettingProviders;
import org.elasticsearch.index.mapper.MapperParsingException;
import org.elasticsearch.index.mapper.MapperService;
Expand All @@ -38,6 +41,8 @@
import org.elasticsearch.xcontent.NamedXContentRegistry;

import java.io.IOException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
Expand Down Expand Up @@ -1915,6 +1920,57 @@ public void testUnreferencedDataStreamsWhenAddingTemplate() throws Exception {
service.addIndexTemplateV2(stateWithDSAndTemplate, false, "logs", nonDSTemplate);
}

public void testValidateTsdbDataStreamsReferringTsdbTemplate() throws Exception {
ClusterState state = ClusterState.EMPTY_STATE;
final MetadataIndexTemplateService service = getMetadataIndexTemplateService();
ComposableIndexTemplate template = new ComposableIndexTemplate(
Collections.singletonList("logs-*-*"),
null,
null,
100L,
null,
null,
new ComposableIndexTemplate.DataStreamTemplate(false, false, IndexMode.TIME_SERIES),
null
);
state = service.addIndexTemplateV2(state, false, "logs", template);

Instant now = Instant.now();
Metadata.Builder mBuilder = new Metadata.Builder(state.getMetadata());
DataStreamTestHelper.getClusterStateWithDataStream(
mBuilder,
"unreferenced",
List.of(Tuple.tuple(now.minus(2, ChronoUnit.HOURS), now))
);
DataStreamTestHelper.getClusterStateWithDataStream(
mBuilder,
"logs-mysql-default",
List.of(Tuple.tuple(now.minus(2, ChronoUnit.HOURS), now))
);
ClusterState stateWithDS = ClusterState.builder(state).metadata(mBuilder).build();

IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> {
ComposableIndexTemplate nonDSTemplate = new ComposableIndexTemplate(
Collections.singletonList("logs-*-*"),
null,
null,
100L,
null,
null,
new ComposableIndexTemplate.DataStreamTemplate(false, false, randomBoolean() ? null : IndexMode.STANDARD),
null
);
service.addIndexTemplateV2(stateWithDS, false, "logs", nonDSTemplate);
});

assertThat(
e.getMessage(),
containsString(
"would cause tsdb data streams [logs-mysql-default] to no longer match a data stream template with a time_series index_mode"
)
);
}

private static List<Throwable> putTemplate(NamedXContentRegistry xContentRegistry, PutRequest request) {
ThreadPool testThreadPool = mock(ThreadPool.class);
ClusterService clusterService = ClusterServiceUtils.createClusterService(testThreadPool);
Expand Down

0 comments on commit 62cc71d

Please sign in to comment.