ILM: parse origination date from index name (#46755) (#47124)

* ILM: parse origination date from index name (#46755)

Introduce the `index.lifecycle.parse_origination_date` setting that
indicates if the origination date should be parsed from the index name.
If set to true an index which doesn't match the expected format (namely
`indexName-{dateFormat}-optional_digits` will fail before being created.
The origination date will be parsed when initialising a lifecycle for an
index and it will be set as the `index.lifecycle.origination_date` for
that index.

A user set value for `index.lifecycle.origination_date` will always
override a possible parsable date from the index name.

(cherry picked from commit c363d27f0210733dad0c307d54fa224a92ddb569)
Signed-off-by: Andrei Dan <andrei.dan@elastic.co>

* Drop usage of Map.of to be java 8 compliant
This commit is contained in:
Andrei Dan 2019-09-25 21:44:16 +01:00 committed by GitHub
parent a267df30fa
commit 27520cac3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 315 additions and 10 deletions

View File

@ -27,6 +27,14 @@ information about rollover, see <<using-policies-rollover>>.
(<<time-units, time units>>) How often {ilm} checks for indices that meet policy (<<time-units, time units>>) How often {ilm} checks for indices that meet policy
criteria. Defaults to `10m`. criteria. Defaults to `10m`.
`index.lifecycle.parse_origination_date`::
When configured to `true` the origination date will be parsed from the index
name. The index format must match the pattern `^.*-{date_format}-\\d+`, where
the `date_format` is `yyyy.MM.dd` and the trailing digits are optional (an
index that was rolled over would normally match the full format eg.
`logs-2016.10.31-000002`). If the index name doesn't match the pattern
the index creation will fail.
`index.lifecycle.origination_date`:: `index.lifecycle.origination_date`::
The timestamp that will be used to calculate the index age for its phase The timestamp that will be used to calculate the index age for its phase
transitions. This allows the users to create an index containing old data and transitions. This allows the users to create an index containing old data and

View File

@ -0,0 +1,50 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.core.ilm;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.time.DateFormatter;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.elasticsearch.xpack.core.ilm.LifecycleSettings.LIFECYCLE_ORIGINATION_DATE;
import static org.elasticsearch.xpack.core.ilm.LifecycleSettings.LIFECYCLE_PARSE_ORIGINATION_DATE;
public class IndexLifecycleOriginationDateParser {
private static final DateFormatter DATE_FORMATTER = DateFormatter.forPattern("yyyy.MM.dd");
private static final String INDEX_NAME_REGEX = "^.*-(\\d{4}.\\d{2}.\\d{2})(-[\\d]+)?$";
private static final Pattern INDEX_NAME_PATTERN = Pattern.compile(INDEX_NAME_REGEX);
/**
* Determines if the origination date needs to be parsed from the index name.
*/
public static boolean shouldParseIndexName(Settings indexSettings) {
return indexSettings.getAsLong(LIFECYCLE_ORIGINATION_DATE, -1L) == -1L &&
indexSettings.getAsBoolean(LIFECYCLE_PARSE_ORIGINATION_DATE, false);
}
/**
* Parses the index according to the supported format and extracts the origination date. If the index does not match the expected
* format or the date in the index name doesn't match the `yyyy.MM.dd` format it throws an {@link IllegalArgumentException}
*/
public static long parseIndexNameAndExtractDate(String indexName) {
Matcher matcher = INDEX_NAME_PATTERN.matcher(indexName);
if (matcher.matches()) {
String dateAsString = matcher.group(1);
try {
return DATE_FORMATTER.parseMillis(dateAsString);
} catch (ElasticsearchParseException | IllegalArgumentException e) {
throw new IllegalArgumentException("index name [" + indexName + "] contains date [" + dateAsString + "] which " +
"couldn't be parsed using the 'yyyy.MM.dd' format", e);
}
}
throw new IllegalArgumentException("index name [" + indexName + "] does not match pattern '" + INDEX_NAME_REGEX + "'");
}
}

View File

@ -10,8 +10,11 @@ import org.apache.logging.log4j.Logger;
import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.Index; import org.elasticsearch.index.Index;
import static org.elasticsearch.xpack.core.ilm.IndexLifecycleOriginationDateParser.parseIndexNameAndExtractDate;
import static org.elasticsearch.xpack.core.ilm.IndexLifecycleOriginationDateParser.shouldParseIndexName;
import static org.elasticsearch.xpack.core.ilm.LifecycleExecutionState.ILM_CUSTOM_METADATA_KEY; import static org.elasticsearch.xpack.core.ilm.LifecycleExecutionState.ILM_CUSTOM_METADATA_KEY;
/** /**
@ -34,19 +37,34 @@ public final class InitializePolicyContextStep extends ClusterStateActionStep {
// Index must have been since deleted, ignore it // Index must have been since deleted, ignore it
return clusterState; return clusterState;
} }
LifecycleExecutionState lifecycleState = LifecycleExecutionState LifecycleExecutionState lifecycleState = LifecycleExecutionState
.fromIndexMetadata(indexMetaData); .fromIndexMetadata(indexMetaData);
if (lifecycleState.getLifecycleDate() != null) { if (lifecycleState.getLifecycleDate() != null) {
return clusterState; return clusterState;
} }
IndexMetaData.Builder indexMetadataBuilder = IndexMetaData.builder(indexMetaData);
if (shouldParseIndexName(indexMetaData.getSettings())) {
long parsedOriginationDate = parseIndexNameAndExtractDate(index.getName());
indexMetadataBuilder.settingsVersion(indexMetaData.getSettingsVersion() + 1)
.settings(Settings.builder()
.put(indexMetaData.getSettings())
.put(LifecycleSettings.LIFECYCLE_ORIGINATION_DATE, parsedOriginationDate)
.build()
);
}
ClusterState.Builder newClusterStateBuilder = ClusterState.builder(clusterState); ClusterState.Builder newClusterStateBuilder = ClusterState.builder(clusterState);
LifecycleExecutionState.Builder newCustomData = LifecycleExecutionState.builder(lifecycleState); LifecycleExecutionState.Builder newCustomData = LifecycleExecutionState.builder(lifecycleState);
newCustomData.setIndexCreationDate(indexMetaData.getCreationDate()); newCustomData.setIndexCreationDate(indexMetaData.getCreationDate());
newClusterStateBuilder.metaData(MetaData.builder(clusterState.getMetaData()).put(IndexMetaData indexMetadataBuilder.putCustom(ILM_CUSTOM_METADATA_KEY, newCustomData.build().asMap());
.builder(indexMetaData)
.putCustom(ILM_CUSTOM_METADATA_KEY, newCustomData.build().asMap()))); newClusterStateBuilder.metaData(
MetaData.builder(clusterState.getMetaData()).put(indexMetadataBuilder)
);
return newClusterStateBuilder.build(); return newClusterStateBuilder.build();
} }
} }

View File

@ -18,6 +18,7 @@ public class LifecycleSettings {
public static final String LIFECYCLE_NAME = "index.lifecycle.name"; public static final String LIFECYCLE_NAME = "index.lifecycle.name";
public static final String LIFECYCLE_INDEXING_COMPLETE = "index.lifecycle.indexing_complete"; public static final String LIFECYCLE_INDEXING_COMPLETE = "index.lifecycle.indexing_complete";
public static final String LIFECYCLE_ORIGINATION_DATE = "index.lifecycle.origination_date"; public static final String LIFECYCLE_ORIGINATION_DATE = "index.lifecycle.origination_date";
public static final String LIFECYCLE_PARSE_ORIGINATION_DATE = "index.lifecycle.parse_origination_date";
public static final String SLM_HISTORY_INDEX_ENABLED = "slm.history_index_enabled"; public static final String SLM_HISTORY_INDEX_ENABLED = "slm.history_index_enabled";
public static final String SLM_RETENTION_SCHEDULE = "slm.retention_schedule"; public static final String SLM_RETENTION_SCHEDULE = "slm.retention_schedule";
@ -32,6 +33,8 @@ public class LifecycleSettings {
Setting.Property.Dynamic, Setting.Property.IndexScope); Setting.Property.Dynamic, Setting.Property.IndexScope);
public static final Setting<Long> LIFECYCLE_ORIGINATION_DATE_SETTING = public static final Setting<Long> LIFECYCLE_ORIGINATION_DATE_SETTING =
Setting.longSetting(LIFECYCLE_ORIGINATION_DATE, -1, -1, Setting.Property.Dynamic, Setting.Property.IndexScope); Setting.longSetting(LIFECYCLE_ORIGINATION_DATE, -1, -1, Setting.Property.Dynamic, Setting.Property.IndexScope);
public static final Setting<Boolean> LIFECYCLE_PARSE_ORIGINATION_DATE_SETTING = Setting.boolSetting(LIFECYCLE_PARSE_ORIGINATION_DATE,
false, Setting.Property.Dynamic, Setting.Property.IndexScope);
public static final Setting<Boolean> SLM_HISTORY_INDEX_ENABLED_SETTING = Setting.boolSetting(SLM_HISTORY_INDEX_ENABLED, true, public static final Setting<Boolean> SLM_HISTORY_INDEX_ENABLED_SETTING = Setting.boolSetting(SLM_HISTORY_INDEX_ENABLED, true,
Setting.Property.NodeScope); Setting.Property.NodeScope);

View File

@ -0,0 +1,112 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.core.ilm;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.test.ESTestCase;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Locale;
import java.util.TimeZone;
import static org.elasticsearch.xpack.core.ilm.IndexLifecycleOriginationDateParser.parseIndexNameAndExtractDate;
import static org.elasticsearch.xpack.core.ilm.IndexLifecycleOriginationDateParser.shouldParseIndexName;
import static org.hamcrest.Matchers.is;
public class IndexLifecycleOriginationDateParserTests extends ESTestCase {
public void testShouldParseIndexNameReturnsFalseWhenOriginationDateIsSet() {
Settings settings = Settings.builder()
.put(LifecycleSettings.LIFECYCLE_ORIGINATION_DATE, 1L)
.build();
assertThat(shouldParseIndexName(settings), is(false));
}
public void testShouldParseIndexNameReturnsFalseIfParseOriginationDateIsDisabled() {
Settings settings = Settings.builder()
.put(LifecycleSettings.LIFECYCLE_PARSE_ORIGINATION_DATE, false)
.build();
assertThat(shouldParseIndexName(settings), is(false));
}
public void testShouldParseIndexNameReturnsTrueIfParseOriginationDateIsTrueAndOriginationDateIsNotSet() {
Settings settings = Settings.builder()
.put(LifecycleSettings.LIFECYCLE_PARSE_ORIGINATION_DATE, true)
.build();
assertThat(shouldParseIndexName(settings), is(true));
}
public void testParseIndexNameThatMatchesExpectedFormat() throws ParseException {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd", Locale.getDefault());
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
long expectedDate = dateFormat.parse("2019.09.04").getTime();
{
long parsedDate = parseIndexNameAndExtractDate("indexName-2019.09.04");
assertThat("indexName-yyyy.MM.dd is a valid index format", parsedDate, is(expectedDate));
}
{
long parsedDate = parseIndexNameAndExtractDate("indexName-2019.09.04-0000001");
assertThat("indexName-yyyy.MM.dd-\\d+$ is a valid index format", parsedDate, is(expectedDate));
}
{
long parsedDate = parseIndexNameAndExtractDate("indexName-2019.09.04-2019.09.24");
long secondDateInIndexName = dateFormat.parse("2019.09.24").getTime();
assertThat("indexName-yyyy.MM.dd-yyyy.MM.dd is a valid index format and the second date should be parsed",
parsedDate, is(secondDateInIndexName));
}
{
long parsedDate = parseIndexNameAndExtractDate("index-2019.09.04-2019.09.24-00002");
long secondDateInIndexName = dateFormat.parse("2019.09.24").getTime();
assertThat("indexName-yyyy.MM.dd-yyyy.MM.dd-digits is a valid index format and the second date should be parsed",
parsedDate, is(secondDateInIndexName));
}
}
public void testParseIndexNameThrowsIllegalArgumentExceptionForInvalidIndexFormat() {
expectThrows(
IllegalArgumentException.class,
"plainIndexName does not match the expected pattern",
() -> parseIndexNameAndExtractDate("plainIndexName")
);
expectThrows(
IllegalArgumentException.class,
"indexName--00001 does not match the expected pattern as the origination date is missing",
() -> parseIndexNameAndExtractDate("indexName--00001")
);
expectThrows(
IllegalArgumentException.class,
"indexName-00001 does not match the expected pattern as the origination date is missing",
() -> parseIndexNameAndExtractDate("indexName-00001")
);
expectThrows(
IllegalArgumentException.class,
"indexName_2019.09.04_00001 does not match the expected pattern as _ is not the expected delimiter",
() -> parseIndexNameAndExtractDate("indexName_2019.09.04_00001")
);
}
public void testParseIndexNameThrowsIllegalArgumentExceptionForInvalidDateFormat() {
expectThrows(
IllegalArgumentException.class,
"indexName-2019.04-00001 does not match the expected pattern as the date does not conform with the yyyy.MM.dd pattern",
() -> parseIndexNameAndExtractDate("indexName-2019.04-00001")
);
expectThrows(
IllegalArgumentException.class,
"java.lang.IllegalArgumentException: failed to parse date field [2019.09.44] with format [yyyy.MM.dd]",
() -> parseIndexNameAndExtractDate("index-2019.09.44")
);
}
}

View File

@ -28,6 +28,7 @@ import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.core.internal.io.IOUtils; import org.elasticsearch.core.internal.io.IOUtils;
import org.elasticsearch.env.Environment; import org.elasticsearch.env.Environment;
import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.env.NodeEnvironment;
import org.elasticsearch.index.IndexModule;
import org.elasticsearch.plugins.ActionPlugin; import org.elasticsearch.plugins.ActionPlugin;
import org.elasticsearch.plugins.Plugin; import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.rest.RestController; import org.elasticsearch.rest.RestController;
@ -155,6 +156,7 @@ public class IndexLifecycle extends Plugin implements ActionPlugin {
LifecycleSettings.LIFECYCLE_POLL_INTERVAL_SETTING, LifecycleSettings.LIFECYCLE_POLL_INTERVAL_SETTING,
LifecycleSettings.LIFECYCLE_NAME_SETTING, LifecycleSettings.LIFECYCLE_NAME_SETTING,
LifecycleSettings.LIFECYCLE_ORIGINATION_DATE_SETTING, LifecycleSettings.LIFECYCLE_ORIGINATION_DATE_SETTING,
LifecycleSettings.LIFECYCLE_PARSE_ORIGINATION_DATE_SETTING,
LifecycleSettings.LIFECYCLE_INDEXING_COMPLETE_SETTING, LifecycleSettings.LIFECYCLE_INDEXING_COMPLETE_SETTING,
RolloverAction.LIFECYCLE_ROLLOVER_ALIAS_SETTING, RolloverAction.LIFECYCLE_ROLLOVER_ALIAS_SETTING,
LifecycleSettings.SLM_HISTORY_INDEX_ENABLED_SETTING, LifecycleSettings.SLM_HISTORY_INDEX_ENABLED_SETTING,
@ -280,6 +282,14 @@ public class IndexLifecycle extends Plugin implements ActionPlugin {
return actions; return actions;
} }
@Override
public void onIndexModule(IndexModule indexModule) {
if (ilmEnabled) {
assert indexLifecycleInitialisationService.get() != null;
indexModule.addIndexEventListener(indexLifecycleInitialisationService.get());
}
}
@Override @Override
public void close() { public void close() {
try { try {

View File

@ -22,6 +22,8 @@ import org.elasticsearch.common.component.Lifecycle.State;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.shard.IndexEventListener;
import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.xpack.core.XPackField; import org.elasticsearch.xpack.core.XPackField;
import org.elasticsearch.xpack.core.ilm.IndexLifecycleMetadata; import org.elasticsearch.xpack.core.ilm.IndexLifecycleMetadata;
@ -39,11 +41,14 @@ import java.util.Collections;
import java.util.Set; import java.util.Set;
import java.util.function.LongSupplier; import java.util.function.LongSupplier;
import static org.elasticsearch.xpack.core.ilm.IndexLifecycleOriginationDateParser.parseIndexNameAndExtractDate;
import static org.elasticsearch.xpack.core.ilm.IndexLifecycleOriginationDateParser.shouldParseIndexName;
/** /**
* A service which runs the {@link LifecyclePolicy}s associated with indexes. * A service which runs the {@link LifecyclePolicy}s associated with indexes.
*/ */
public class IndexLifecycleService public class IndexLifecycleService
implements ClusterStateListener, ClusterStateApplier, SchedulerEngine.Listener, Closeable, LocalNodeMasterListener { implements ClusterStateListener, ClusterStateApplier, SchedulerEngine.Listener, Closeable, LocalNodeMasterListener, IndexEventListener {
private static final Logger logger = LogManager.getLogger(IndexLifecycleService.class); private static final Logger logger = LogManager.getLogger(IndexLifecycleService.class);
private static final Set<String> IGNORE_STEPS_MAINTENANCE_REQUESTED = Collections.singleton(ShrinkStep.NAME); private static final Set<String> IGNORE_STEPS_MAINTENANCE_REQUESTED = Collections.singleton(ShrinkStep.NAME);
private volatile boolean isMaster = false; private volatile boolean isMaster = false;
@ -148,6 +153,13 @@ public class IndexLifecycleService
return ThreadPool.Names.MANAGEMENT; return ThreadPool.Names.MANAGEMENT;
} }
@Override
public void beforeIndexAddedToCluster(Index index, Settings indexSettings) {
if (shouldParseIndexName(indexSettings)) {
parseIndexNameAndExtractDate(index.getName());
}
}
private void updatePollInterval(TimeValue newInterval) { private void updatePollInterval(TimeValue newInterval) {
this.pollInterval = newInterval; this.pollInterval = newInterval;
maybeScheduleJob(); maybeScheduleJob();

View File

@ -51,13 +51,17 @@ import org.elasticsearch.xpack.core.ilm.action.StopILMAction;
import org.junit.Before; import org.junit.Before;
import java.io.IOException; import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.Instant; import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -72,6 +76,7 @@ import static org.elasticsearch.xpack.core.ilm.LifecyclePolicyTestsUtils.newLock
import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.lessThanOrEqualTo; import static org.hamcrest.Matchers.lessThanOrEqualTo;
import static org.hamcrest.core.CombinableMatcher.both; import static org.hamcrest.core.CombinableMatcher.both;
@ -229,7 +234,7 @@ public class IndexLifecycleInitialisationTests extends ESIntegTestCase {
{ {
PhaseExecutionInfo expectedExecutionInfo = new PhaseExecutionInfo(lifecyclePolicy.getName(), mockPhase, 1L, actualModifiedDate); PhaseExecutionInfo expectedExecutionInfo = new PhaseExecutionInfo(lifecyclePolicy.getName(), mockPhase, 1L, actualModifiedDate);
assertBusy(() -> { assertBusy(() -> {
IndexLifecycleExplainResponse indexResponse = executeExplainRequestAndGetTestIndexResponse(); IndexLifecycleExplainResponse indexResponse = executeExplainRequestAndGetTestIndexResponse("test");
assertThat(indexResponse.getStep(), equalTo("observable_cluster_state_action")); assertThat(indexResponse.getStep(), equalTo("observable_cluster_state_action"));
assertThat(indexResponse.getPhaseExecutionInfo(), equalTo(expectedExecutionInfo)); assertThat(indexResponse.getPhaseExecutionInfo(), equalTo(expectedExecutionInfo));
originalLifecycleDate.set(indexResponse.getLifecycleDate()); originalLifecycleDate.set(indexResponse.getLifecycleDate());
@ -242,7 +247,7 @@ public class IndexLifecycleInitialisationTests extends ESIntegTestCase {
{ {
assertBusy(() -> { assertBusy(() -> {
IndexLifecycleExplainResponse indexResponse = executeExplainRequestAndGetTestIndexResponse(); IndexLifecycleExplainResponse indexResponse = executeExplainRequestAndGetTestIndexResponse("test");
assertThat("The configured origination date dictates the lifecycle date", assertThat("The configured origination date dictates the lifecycle date",
indexResponse.getLifecycleDate(), equalTo(1000L)); indexResponse.getLifecycleDate(), equalTo(1000L));
}); });
@ -254,7 +259,7 @@ public class IndexLifecycleInitialisationTests extends ESIntegTestCase {
{ {
assertBusy(() -> { assertBusy(() -> {
IndexLifecycleExplainResponse indexResponse = executeExplainRequestAndGetTestIndexResponse(); IndexLifecycleExplainResponse indexResponse = executeExplainRequestAndGetTestIndexResponse("test");
assertThat("Without the origination date, the index create date should dictate the lifecycle date", assertThat("Without the origination date, the index create date should dictate the lifecycle date",
indexResponse.getLifecycleDate(), equalTo(originalLifecycleDate.get())); indexResponse.getLifecycleDate(), equalTo(originalLifecycleDate.get()));
}); });
@ -267,7 +272,7 @@ public class IndexLifecycleInitialisationTests extends ESIntegTestCase {
{ {
PhaseExecutionInfo expectedExecutionInfo = new PhaseExecutionInfo(lifecyclePolicy.getName(), null, 1L, actualModifiedDate); PhaseExecutionInfo expectedExecutionInfo = new PhaseExecutionInfo(lifecyclePolicy.getName(), null, 1L, actualModifiedDate);
assertBusy(() -> { assertBusy(() -> {
IndexLifecycleExplainResponse indexResponse = executeExplainRequestAndGetTestIndexResponse(); IndexLifecycleExplainResponse indexResponse = executeExplainRequestAndGetTestIndexResponse("test");
assertThat(indexResponse.getPhase(), equalTo(TerminalPolicyStep.COMPLETED_PHASE)); assertThat(indexResponse.getPhase(), equalTo(TerminalPolicyStep.COMPLETED_PHASE));
assertThat(indexResponse.getStep(), equalTo(TerminalPolicyStep.KEY.getName())); assertThat(indexResponse.getStep(), equalTo(TerminalPolicyStep.KEY.getName()));
assertThat(indexResponse.getPhaseExecutionInfo(), equalTo(expectedExecutionInfo)); assertThat(indexResponse.getPhaseExecutionInfo(), equalTo(expectedExecutionInfo));
@ -275,11 +280,80 @@ public class IndexLifecycleInitialisationTests extends ESIntegTestCase {
} }
} }
private IndexLifecycleExplainResponse executeExplainRequestAndGetTestIndexResponse() throws ExecutionException, InterruptedException { public void testExplainParseOriginationDate() throws Exception {
// start node
logger.info("Starting server1");
internalCluster().startNode();
logger.info("Starting server2");
internalCluster().startNode();
logger.info("Creating lifecycle [test_lifecycle]");
PutLifecycleAction.Request putLifecycleRequest = new PutLifecycleAction.Request(lifecyclePolicy);
PutLifecycleAction.Response putLifecycleResponse = client().execute(PutLifecycleAction.INSTANCE, putLifecycleRequest).get();
assertAcked(putLifecycleResponse);
GetLifecycleAction.Response getLifecycleResponse = client().execute(GetLifecycleAction.INSTANCE,
new GetLifecycleAction.Request()).get();
assertThat(getLifecycleResponse.getPolicies().size(), equalTo(1));
GetLifecycleAction.LifecyclePolicyResponseItem responseItem = getLifecycleResponse.getPolicies().get(0);
assertThat(responseItem.getLifecyclePolicy(), equalTo(lifecyclePolicy));
assertThat(responseItem.getVersion(), equalTo(1L));
long actualModifiedDate = Instant.parse(responseItem.getModifiedDate()).toEpochMilli();
String indexName = "test-2019.09.14";
logger.info("Creating index [{}]", indexName);
CreateIndexResponse createIndexResponse =
client().admin().indices().create(createIndexRequest(indexName)
.settings(Settings.builder().put(settings).put(LifecycleSettings.LIFECYCLE_PARSE_ORIGINATION_DATE, true))
).actionGet();
assertAcked(createIndexResponse);
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd", Locale.getDefault());
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
long parsedLifecycleDate = dateFormat.parse("2019.09.14").getTime();
assertBusy(() -> {
IndexLifecycleExplainResponse indexResponse = executeExplainRequestAndGetTestIndexResponse(indexName);
assertThat(indexResponse.getLifecycleDate(), is(parsedLifecycleDate));
});
// disabling the lifecycle parsing would maintain the parsed value as that was set as the origination date
client().admin().indices().prepareUpdateSettings(indexName)
.setSettings(Collections.singletonMap(LifecycleSettings.LIFECYCLE_PARSE_ORIGINATION_DATE, false)).get();
assertBusy(() -> {
IndexLifecycleExplainResponse indexResponse = executeExplainRequestAndGetTestIndexResponse(indexName);
assertThat(indexResponse.getLifecycleDate(), is(parsedLifecycleDate));
});
// setting the lifecycle origination date setting to null should make the lifecyle date fallback on the index creation date
client().admin().indices().prepareUpdateSettings(indexName)
.setSettings(Collections.singletonMap(LifecycleSettings.LIFECYCLE_ORIGINATION_DATE, null)).get();
assertBusy(() -> {
IndexLifecycleExplainResponse indexResponse = executeExplainRequestAndGetTestIndexResponse(indexName);
assertThat(indexResponse.getLifecycleDate(), is(greaterThan(parsedLifecycleDate)));
});
// setting the lifecycle origination date to an explicit value overrides the date parsing
long originationDate = 42L;
Map<String, Object> settings = new HashMap<>();
settings.put(LifecycleSettings.LIFECYCLE_PARSE_ORIGINATION_DATE, true);
settings.put(LifecycleSettings.LIFECYCLE_ORIGINATION_DATE, originationDate);
client().admin().indices().prepareUpdateSettings(indexName)
.setSettings(settings)
.get();
assertBusy(() -> {
IndexLifecycleExplainResponse indexResponse = executeExplainRequestAndGetTestIndexResponse(indexName);
assertThat(indexResponse.getLifecycleDate(), is(originationDate));
});
}
private IndexLifecycleExplainResponse executeExplainRequestAndGetTestIndexResponse(String indexName) throws ExecutionException,
InterruptedException {
ExplainLifecycleRequest explainRequest = new ExplainLifecycleRequest(); ExplainLifecycleRequest explainRequest = new ExplainLifecycleRequest();
ExplainLifecycleResponse explainResponse = client().execute(ExplainLifecycleAction.INSTANCE, explainRequest).get(); ExplainLifecycleResponse explainResponse = client().execute(ExplainLifecycleAction.INSTANCE, explainRequest).get();
assertThat(explainResponse.getIndexResponses().size(), equalTo(1)); assertThat(explainResponse.getIndexResponses().size(), equalTo(1));
return explainResponse.getIndexResponses().get("test"); return explainResponse.getIndexResponses().get(indexName);
} }
public void testMasterDedicatedDataDedicated() throws Exception { public void testMasterDedicatedDataDedicated() throws Exception {

View File

@ -50,6 +50,7 @@ import java.time.ZoneId;
import java.util.Collections; import java.util.Collections;
import java.util.SortedMap; import java.util.SortedMap;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.UUID;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import static org.elasticsearch.node.Node.NODE_MASTER_SETTING; import static org.elasticsearch.node.Node.NODE_MASTER_SETTING;
@ -305,4 +306,21 @@ public class IndexLifecycleServiceTests extends ESTestCase {
indexLifecycleService.triggered(schedulerEvent); indexLifecycleService.triggered(schedulerEvent);
Mockito.verifyZeroInteractions(indicesClient, clusterService); Mockito.verifyZeroInteractions(indicesClient, clusterService);
} }
public void testParsingOriginationDateBeforeIndexCreation() {
Settings indexSettings = Settings.builder().put(LifecycleSettings.LIFECYCLE_PARSE_ORIGINATION_DATE, true).build();
Index index = new Index("invalid_index_name", UUID.randomUUID().toString());
expectThrows(IllegalArgumentException.class,
"The parse origination date setting was configured for index " + index.getName() +
" but the index name did not match the expected format",
() -> indexLifecycleService.beforeIndexAddedToCluster(index, indexSettings)
);
// disabling the parsing origination date setting should prevent the validation from throwing exception
try {
indexLifecycleService.beforeIndexAddedToCluster(index, Settings.EMPTY);
} catch (Exception e) {
fail("Did not expect the before index validation to throw an exception as the parse origination date setting was not set");
}
}
} }