|
|
@@ -21,6 +21,7 @@ import org.elasticsearch.action.support.IndicesOptions;
|
|
|
import org.elasticsearch.action.support.PlainActionFuture;
|
|
|
import org.elasticsearch.cluster.metadata.IndexMetaData;
|
|
|
import org.elasticsearch.cluster.node.DiscoveryNode;
|
|
|
+import org.elasticsearch.cluster.routing.ShardRouting;
|
|
|
import org.elasticsearch.cluster.service.ClusterService;
|
|
|
import org.elasticsearch.common.settings.Setting;
|
|
|
import org.elasticsearch.common.settings.Settings;
|
|
|
@@ -33,6 +34,7 @@ import org.elasticsearch.index.IndexService;
|
|
|
import org.elasticsearch.index.IndexSettings;
|
|
|
import org.elasticsearch.index.seqno.RetentionLease;
|
|
|
import org.elasticsearch.index.seqno.RetentionLeaseActions;
|
|
|
+import org.elasticsearch.index.seqno.RetentionLeaseNotFoundException;
|
|
|
import org.elasticsearch.index.seqno.RetentionLeases;
|
|
|
import org.elasticsearch.index.shard.IndexShard;
|
|
|
import org.elasticsearch.index.shard.IndexShardClosedException;
|
|
|
@@ -40,14 +42,19 @@ import org.elasticsearch.indices.IndicesService;
|
|
|
import org.elasticsearch.plugins.Plugin;
|
|
|
import org.elasticsearch.snapshots.RestoreInfo;
|
|
|
import org.elasticsearch.snapshots.RestoreService;
|
|
|
+import org.elasticsearch.test.junit.annotations.TestLogging;
|
|
|
import org.elasticsearch.test.transport.MockTransportService;
|
|
|
import org.elasticsearch.transport.ConnectTransportException;
|
|
|
+import org.elasticsearch.transport.RemoteTransportException;
|
|
|
+import org.elasticsearch.transport.Transport;
|
|
|
import org.elasticsearch.transport.TransportActionProxy;
|
|
|
+import org.elasticsearch.transport.TransportMessageListener;
|
|
|
import org.elasticsearch.transport.TransportService;
|
|
|
import org.elasticsearch.xpack.CcrIntegTestCase;
|
|
|
import org.elasticsearch.xpack.ccr.action.repositories.ClearCcrRestoreSessionAction;
|
|
|
import org.elasticsearch.xpack.ccr.repository.CcrRepository;
|
|
|
import org.elasticsearch.xpack.core.ccr.action.PutFollowAction;
|
|
|
+import org.elasticsearch.xpack.core.ccr.action.ResumeFollowAction;
|
|
|
import org.elasticsearch.xpack.core.ccr.action.UnfollowAction;
|
|
|
|
|
|
import java.io.IOException;
|
|
|
@@ -83,7 +90,7 @@ public class CcrRetentionLeaseIT extends CcrIntegTestCase {
|
|
|
|
|
|
@Override
|
|
|
public List<Setting<?>> getSettings() {
|
|
|
- return Collections.singletonList(CcrRepository.RETENTION_LEASE_RENEW_INTERVAL_SETTING);
|
|
|
+ return Collections.singletonList(CcrRetentionLeases.RETENTION_LEASE_RENEW_INTERVAL_SETTING);
|
|
|
}
|
|
|
|
|
|
}
|
|
|
@@ -105,6 +112,13 @@ public class CcrRetentionLeaseIT extends CcrIntegTestCase {
|
|
|
.collect(Collectors.toList());
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ protected Settings followerClusterSettings() {
|
|
|
+ return Settings.builder()
|
|
|
+ .put(CcrRetentionLeases.RETENTION_LEASE_RENEW_INTERVAL_SETTING.getKey(), TimeValue.timeValueMillis(200))
|
|
|
+ .build();
|
|
|
+ }
|
|
|
+
|
|
|
private final IndicesOptions indicesOptions = IndicesOptions.strictSingleIndexNoExpandForbidClosed();
|
|
|
|
|
|
private RestoreSnapshotRequest setUpRestoreSnapshotRequest(
|
|
|
@@ -140,7 +154,6 @@ public class CcrRetentionLeaseIT extends CcrIntegTestCase {
|
|
|
|
|
|
final Settings.Builder settingsBuilder = Settings.builder()
|
|
|
.put(IndexMetaData.SETTING_INDEX_PROVIDED_NAME, followerIndex)
|
|
|
- .put(CcrRepository.RETENTION_LEASE_RENEW_INTERVAL_SETTING.getKey(), TimeValue.timeValueMillis(200))
|
|
|
.put(CcrSettings.CCR_FOLLOWING_INDEX_SETTING.getKey(), true);
|
|
|
return new RestoreSnapshotRequest(leaderClusterRepoName, CcrRepository.LATEST)
|
|
|
.indexSettings(settingsBuilder)
|
|
|
@@ -227,42 +240,7 @@ public class CcrRetentionLeaseIT extends CcrIntegTestCase {
|
|
|
restoreService.restoreSnapshot(restoreRequest, waitForRestore(clusterService, future));
|
|
|
|
|
|
try {
|
|
|
- // ensure that a retention lease has been put in place on each shard, and grab a copy of them
|
|
|
- final List<RetentionLeases> retentionLeases = new ArrayList<>();
|
|
|
- assertBusy(() -> {
|
|
|
- retentionLeases.clear();
|
|
|
- final IndicesStatsResponse stats =
|
|
|
- leaderClient().admin().indices().stats(new IndicesStatsRequest().clear().indices(leaderIndex)).actionGet();
|
|
|
- assertNotNull(stats.getShards());
|
|
|
- assertThat(stats.getShards(), arrayWithSize(numberOfShards * (1 + numberOfReplicas)));
|
|
|
- final List<ShardStats> shardsStats = getShardsStats(stats);
|
|
|
- for (int i = 0; i < numberOfShards * (1 + numberOfReplicas); i++) {
|
|
|
- final RetentionLeases currentRetentionLeases = shardsStats.get(i).getRetentionLeaseStats().retentionLeases();
|
|
|
- assertThat(currentRetentionLeases.leases(), hasSize(1));
|
|
|
- final RetentionLease retentionLease =
|
|
|
- currentRetentionLeases.leases().iterator().next();
|
|
|
- assertThat(retentionLease.id(), equalTo(getRetentionLeaseId(followerIndex, leaderIndex)));
|
|
|
- retentionLeases.add(currentRetentionLeases);
|
|
|
- }
|
|
|
- });
|
|
|
-
|
|
|
- // now ensure that the retention leases are being renewed
|
|
|
- assertBusy(() -> {
|
|
|
- final IndicesStatsResponse stats =
|
|
|
- leaderClient().admin().indices().stats(new IndicesStatsRequest().clear().indices(leaderIndex)).actionGet();
|
|
|
- assertNotNull(stats.getShards());
|
|
|
- assertThat(stats.getShards(), arrayWithSize(numberOfShards * (1 + numberOfReplicas)));
|
|
|
- final List<ShardStats> shardsStats = getShardsStats(stats);
|
|
|
- for (int i = 0; i < numberOfShards * (1 + numberOfReplicas); i++) {
|
|
|
- final RetentionLeases currentRetentionLeases = shardsStats.get(i).getRetentionLeaseStats().retentionLeases();
|
|
|
- assertThat(currentRetentionLeases.leases(), hasSize(1));
|
|
|
- final RetentionLease retentionLease =
|
|
|
- currentRetentionLeases.leases().iterator().next();
|
|
|
- assertThat(retentionLease.id(), equalTo(getRetentionLeaseId(followerIndex, leaderIndex)));
|
|
|
- // we assert that retention leases are being renewed by an increase in the timestamp
|
|
|
- assertThat(retentionLease.timestamp(), greaterThan(retentionLeases.get(i).leases().iterator().next().timestamp()));
|
|
|
- }
|
|
|
- });
|
|
|
+ assertRetentionLeaseRenewal(numberOfShards, numberOfReplicas, followerIndex, leaderIndex);
|
|
|
latch.countDown();
|
|
|
} finally {
|
|
|
for (final ObjectCursor<DiscoveryNode> senderNode : followerClusterState.getState().nodes().getDataNodes().values()) {
|
|
|
@@ -354,15 +332,7 @@ public class CcrRetentionLeaseIT extends CcrIntegTestCase {
|
|
|
* After we wake up, it should be the case that the retention leases are the same (same timestamp) as that indicates that they were
|
|
|
* not renewed while we were sleeping.
|
|
|
*/
|
|
|
- final TimeValue renewIntervalSetting = CcrRepository.RETENTION_LEASE_RENEW_INTERVAL_SETTING.get(
|
|
|
- followerClient()
|
|
|
- .admin()
|
|
|
- .indices()
|
|
|
- .prepareGetSettings(followerIndex)
|
|
|
- .get()
|
|
|
- .getIndexToSettings()
|
|
|
- .get(followerIndex));
|
|
|
-
|
|
|
+ final TimeValue renewIntervalSetting = CcrRetentionLeases.RETENTION_LEASE_RENEW_INTERVAL_SETTING.get(followerClusterSettings());
|
|
|
final long renewEnd = System.nanoTime();
|
|
|
Thread.sleep(Math.max(0, randomIntBetween(2, 4) * renewIntervalSetting.millis() - TimeUnit.NANOSECONDS.toMillis(renewEnd - start)));
|
|
|
|
|
|
@@ -404,6 +374,7 @@ public class CcrRetentionLeaseIT extends CcrIntegTestCase {
|
|
|
final String leaderIndexSettings =
|
|
|
getIndexSettings(numberOfShards, 0, singletonMap(IndexSettings.INDEX_SOFT_DELETES_SETTING.getKey(), "true"));
|
|
|
assertAcked(leaderClient().admin().indices().prepareCreate(leaderIndex).setSource(leaderIndexSettings, XContentType.JSON).get());
|
|
|
+ ensureLeaderYellow(leaderIndex);
|
|
|
final PutFollowAction.Request followRequest = putFollow(leaderIndex, followerIndex);
|
|
|
followerClient().execute(PutFollowAction.INSTANCE, followRequest).get();
|
|
|
|
|
|
@@ -469,11 +440,8 @@ public class CcrRetentionLeaseIT extends CcrIntegTestCase {
|
|
|
});
|
|
|
}
|
|
|
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
pauseFollow(followerIndex);
|
|
|
- followerClient().admin().indices().close(new CloseIndexRequest(followerIndex)).actionGet();
|
|
|
+ assertAcked(followerClient().admin().indices().close(new CloseIndexRequest(followerIndex)).actionGet());
|
|
|
assertAcked(followerClient().execute(UnfollowAction.INSTANCE, new UnfollowAction.Request(followerIndex)).actionGet());
|
|
|
|
|
|
final IndicesStatsResponse afterUnfollowStats =
|
|
|
@@ -498,6 +466,7 @@ public class CcrRetentionLeaseIT extends CcrIntegTestCase {
|
|
|
final String leaderIndexSettings =
|
|
|
getIndexSettings(numberOfShards, 0, singletonMap(IndexSettings.INDEX_SOFT_DELETES_SETTING.getKey(), "true"));
|
|
|
assertAcked(leaderClient().admin().indices().prepareCreate(leaderIndex).setSource(leaderIndexSettings, XContentType.JSON).get());
|
|
|
+ ensureLeaderYellow(leaderIndex);
|
|
|
final PutFollowAction.Request followRequest = putFollow(leaderIndex, followerIndex);
|
|
|
followerClient().execute(PutFollowAction.INSTANCE, followRequest).get();
|
|
|
|
|
|
@@ -560,6 +529,438 @@ public class CcrRetentionLeaseIT extends CcrIntegTestCase {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ public void testRetentionLeaseRenewedWhileFollowing() throws Exception {
|
|
|
+ final String leaderIndex = "leader";
|
|
|
+ final String followerIndex = "follower";
|
|
|
+ final int numberOfShards = randomIntBetween(1, 4);
|
|
|
+ final int numberOfReplicas = randomIntBetween(0, 1);
|
|
|
+ final Map<String, String> additionalIndexSettings = new HashMap<>();
|
|
|
+ additionalIndexSettings.put(IndexSettings.INDEX_SOFT_DELETES_SETTING.getKey(), Boolean.toString(true));
|
|
|
+ additionalIndexSettings.put(
|
|
|
+ IndexService.RETENTION_LEASE_SYNC_INTERVAL_SETTING.getKey(),
|
|
|
+ TimeValue.timeValueMillis(200).getStringRep());
|
|
|
+ final String leaderIndexSettings = getIndexSettings(numberOfShards, numberOfReplicas, additionalIndexSettings);
|
|
|
+ assertAcked(leaderClient().admin().indices().prepareCreate(leaderIndex).setSource(leaderIndexSettings, XContentType.JSON).get());
|
|
|
+ ensureLeaderYellow(leaderIndex);
|
|
|
+ final PutFollowAction.Request followRequest = putFollow(leaderIndex, followerIndex);
|
|
|
+ followerClient().execute(PutFollowAction.INSTANCE, followRequest).get();
|
|
|
+
|
|
|
+ ensureFollowerGreen(true, followerIndex);
|
|
|
+ assertRetentionLeaseRenewal(numberOfShards, numberOfReplicas, followerIndex, leaderIndex);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testRetentionLeaseAdvancesWhileFollowing() throws Exception {
|
|
|
+ final String leaderIndex = "leader";
|
|
|
+ final String followerIndex = "follower";
|
|
|
+ final int numberOfShards = randomIntBetween(1, 4);
|
|
|
+ final int numberOfReplicas = randomIntBetween(0, 1);
|
|
|
+ final Map<String, String> additionalIndexSettings = new HashMap<>();
|
|
|
+ additionalIndexSettings.put(IndexSettings.INDEX_SOFT_DELETES_SETTING.getKey(), Boolean.toString(true));
|
|
|
+ additionalIndexSettings.put(
|
|
|
+ IndexService.RETENTION_LEASE_SYNC_INTERVAL_SETTING.getKey(),
|
|
|
+ TimeValue.timeValueMillis(200).getStringRep());
|
|
|
+ final String leaderIndexSettings = getIndexSettings(numberOfShards, numberOfReplicas, additionalIndexSettings);
|
|
|
+ assertAcked(leaderClient().admin().indices().prepareCreate(leaderIndex).setSource(leaderIndexSettings, XContentType.JSON).get());
|
|
|
+ ensureLeaderYellow(leaderIndex);
|
|
|
+ final PutFollowAction.Request followRequest = putFollow(leaderIndex, followerIndex);
|
|
|
+ followerClient().execute(PutFollowAction.INSTANCE, followRequest).get();
|
|
|
+
|
|
|
+ ensureFollowerGreen(true, followerIndex);
|
|
|
+
|
|
|
+ final int numberOfDocuments = randomIntBetween(128, 2048);
|
|
|
+ logger.debug("indexing [{}] docs", numberOfDocuments);
|
|
|
+ for (int i = 0; i < numberOfDocuments; i++) {
|
|
|
+ final String source = String.format(Locale.ROOT, "{\"f\":%d}", i);
|
|
|
+ leaderClient().prepareIndex(leaderIndex, "doc", Integer.toString(i)).setSource(source, XContentType.JSON).get();
|
|
|
+ if (rarely()) {
|
|
|
+ leaderClient().admin().indices().prepareFlush(leaderIndex).setForce(true).setWaitIfOngoing(true).get();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // wait until the follower global checkpoints have caught up to the leader
|
|
|
+ assertIndexFullyReplicatedToFollower(leaderIndex, followerIndex);
|
|
|
+
|
|
|
+ final List<ShardStats> leaderShardsStats = getShardsStats(leaderClient().admin().indices().prepareStats(leaderIndex).get());
|
|
|
+ final Map<Integer, Long> leaderGlobalCheckpoints = new HashMap<>();
|
|
|
+ for (final ShardStats leaderShardStats : leaderShardsStats) {
|
|
|
+ final ShardRouting routing = leaderShardStats.getShardRouting();
|
|
|
+ if (routing.primary() == false) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ leaderGlobalCheckpoints.put(routing.id(), leaderShardStats.getSeqNoStats().getGlobalCheckpoint());
|
|
|
+ }
|
|
|
+
|
|
|
+ // now assert that the retention leases have advanced to the global checkpoints
|
|
|
+ assertBusy(() -> {
|
|
|
+ final IndicesStatsResponse stats =
|
|
|
+ leaderClient().admin().indices().stats(new IndicesStatsRequest().clear().indices(leaderIndex)).actionGet();
|
|
|
+ assertNotNull(stats.getShards());
|
|
|
+ assertThat(stats.getShards(), arrayWithSize(numberOfShards * (1 + numberOfReplicas)));
|
|
|
+ final List<ShardStats> shardsStats = getShardsStats(stats);
|
|
|
+ for (int i = 0; i < numberOfShards * (1 + numberOfReplicas); i++) {
|
|
|
+ final RetentionLeases currentRetentionLeases = shardsStats.get(i).getRetentionLeaseStats().retentionLeases();
|
|
|
+ assertThat(currentRetentionLeases.leases(), hasSize(1));
|
|
|
+ final RetentionLease retentionLease =
|
|
|
+ currentRetentionLeases.leases().iterator().next();
|
|
|
+ assertThat(retentionLease.id(), equalTo(getRetentionLeaseId(followerIndex, leaderIndex)));
|
|
|
+ // we assert that retention leases are being advanced
|
|
|
+ assertThat(
|
|
|
+ retentionLease.retainingSequenceNumber(),
|
|
|
+ equalTo(leaderGlobalCheckpoints.get(shardsStats.get(i).getShardRouting().id())));
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ @TestLogging(value = "org.elasticsearch.xpack.ccr:trace")
|
|
|
+ public void testRetentionLeaseRenewalIsCancelledWhenFollowingIsPaused() throws Exception {
|
|
|
+ final String leaderIndex = "leader";
|
|
|
+ final String followerIndex = "follower";
|
|
|
+ final int numberOfShards = randomIntBetween(1, 4);
|
|
|
+ final int numberOfReplicas = randomIntBetween(0, 1);
|
|
|
+ final Map<String, String> additionalIndexSettings = new HashMap<>();
|
|
|
+ additionalIndexSettings.put(IndexSettings.INDEX_SOFT_DELETES_SETTING.getKey(), Boolean.toString(true));
|
|
|
+ additionalIndexSettings.put(
|
|
|
+ IndexService.RETENTION_LEASE_SYNC_INTERVAL_SETTING.getKey(),
|
|
|
+ TimeValue.timeValueMillis(200).getStringRep());
|
|
|
+ final String leaderIndexSettings = getIndexSettings(numberOfShards, numberOfReplicas, additionalIndexSettings);
|
|
|
+ assertAcked(leaderClient().admin().indices().prepareCreate(leaderIndex).setSource(leaderIndexSettings, XContentType.JSON).get());
|
|
|
+ ensureLeaderYellow(leaderIndex);
|
|
|
+ final PutFollowAction.Request followRequest = putFollow(leaderIndex, followerIndex);
|
|
|
+ followerClient().execute(PutFollowAction.INSTANCE, followRequest).get();
|
|
|
+
|
|
|
+ ensureFollowerGreen(true, followerIndex);
|
|
|
+
|
|
|
+ final long start = System.nanoTime();
|
|
|
+ pauseFollow(followerIndex);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * We want to ensure that the retention leases have been synced to all shard copies, as otherwise they might sync between the two
|
|
|
+ * times that we sample the retention leases, which would cause our check to fail.
|
|
|
+ */
|
|
|
+ final TimeValue syncIntervalSetting = IndexService.RETENTION_LEASE_SYNC_INTERVAL_SETTING.get(
|
|
|
+ leaderClient()
|
|
|
+ .admin()
|
|
|
+ .indices()
|
|
|
+ .prepareGetSettings(leaderIndex)
|
|
|
+ .get()
|
|
|
+ .getIndexToSettings()
|
|
|
+ .get(leaderIndex));
|
|
|
+ final long syncEnd = System.nanoTime();
|
|
|
+ Thread.sleep(Math.max(0, randomIntBetween(2, 4) * syncIntervalSetting.millis() - TimeUnit.NANOSECONDS.toMillis(syncEnd - start)));
|
|
|
+
|
|
|
+ final ClusterStateResponse leaderIndexClusterState =
|
|
|
+ leaderClient().admin().cluster().prepareState().clear().setMetaData(true).setIndices(leaderIndex).get();
|
|
|
+ final String leaderUUID = leaderIndexClusterState.getState().metaData().index(leaderIndex).getIndexUUID();
|
|
|
+
|
|
|
+ // sample the leases after pausing
|
|
|
+ final List<RetentionLeases> retentionLeases = new ArrayList<>();
|
|
|
+ assertBusy(() -> {
|
|
|
+ retentionLeases.clear();
|
|
|
+ final IndicesStatsResponse stats =
|
|
|
+ leaderClient().admin().indices().stats(new IndicesStatsRequest().clear().indices(leaderIndex)).actionGet();
|
|
|
+ assertNotNull(stats.getShards());
|
|
|
+ assertThat(stats.getShards(), arrayWithSize(numberOfShards * (1 + numberOfReplicas)));
|
|
|
+ final List<ShardStats> shardsStats = getShardsStats(stats);
|
|
|
+ for (int i = 0; i < numberOfShards * (1 + numberOfReplicas); i++) {
|
|
|
+ final RetentionLeases currentRetentionLeases = shardsStats.get(i).getRetentionLeaseStats().retentionLeases();
|
|
|
+ assertThat(currentRetentionLeases.leases(), hasSize(1));
|
|
|
+ final ClusterStateResponse followerIndexClusterState =
|
|
|
+ followerClient().admin().cluster().prepareState().clear().setMetaData(true).setIndices(followerIndex).get();
|
|
|
+ final String followerUUID = followerIndexClusterState.getState().metaData().index(followerIndex).getIndexUUID();
|
|
|
+ final RetentionLease retentionLease =
|
|
|
+ currentRetentionLeases.leases().iterator().next();
|
|
|
+ final String expectedRetentionLeaseId = retentionLeaseId(
|
|
|
+ getFollowerCluster().getClusterName(),
|
|
|
+ new Index(followerIndex, followerUUID),
|
|
|
+ getLeaderCluster().getClusterName(),
|
|
|
+ new Index(leaderIndex, leaderUUID));
|
|
|
+ assertThat(retentionLease.id(), equalTo(expectedRetentionLeaseId));
|
|
|
+ retentionLeases.add(currentRetentionLeases);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ /*
|
|
|
+ * We want to ensure that the background renewal is cancelled after pausing. To do this, we will sleep a small multiple of the renew
|
|
|
+ * interval. If the renews are not cancelled, we expect that a renewal would have been sent while we were sleeping. After we wake
|
|
|
+ * up, it should be the case that the retention leases are the same (same timestamp) as that indicates that they were not renewed
|
|
|
+ * while we were sleeping.
|
|
|
+ */
|
|
|
+ final TimeValue renewIntervalSetting = CcrRetentionLeases.RETENTION_LEASE_RENEW_INTERVAL_SETTING.get(followerClusterSettings());
|
|
|
+ final long renewEnd = System.nanoTime();
|
|
|
+ Thread.sleep(Math.max(0, randomIntBetween(2, 4) * renewIntervalSetting.millis() - TimeUnit.NANOSECONDS.toMillis(renewEnd - start)));
|
|
|
+
|
|
|
+ // now ensure that the retention leases are the same
|
|
|
+ assertBusy(() -> {
|
|
|
+ final IndicesStatsResponse stats =
|
|
|
+ leaderClient().admin().indices().stats(new IndicesStatsRequest().clear().indices(leaderIndex)).actionGet();
|
|
|
+ assertNotNull(stats.getShards());
|
|
|
+ assertThat(stats.getShards(), arrayWithSize(numberOfShards * (1 + numberOfReplicas)));
|
|
|
+ final List<ShardStats> shardsStats = getShardsStats(stats);
|
|
|
+ for (int i = 0; i < numberOfShards * (1 + numberOfReplicas); i++) {
|
|
|
+ if (shardsStats.get(i).getShardRouting().primary() == false) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ final RetentionLeases currentRetentionLeases = shardsStats.get(i).getRetentionLeaseStats().retentionLeases();
|
|
|
+ assertThat(currentRetentionLeases.leases(), hasSize(1));
|
|
|
+ final ClusterStateResponse followerIndexClusterState =
|
|
|
+ followerClient().admin().cluster().prepareState().clear().setMetaData(true).setIndices(followerIndex).get();
|
|
|
+ final String followerUUID = followerIndexClusterState.getState().metaData().index(followerIndex).getIndexUUID();
|
|
|
+ final RetentionLease retentionLease =
|
|
|
+ currentRetentionLeases.leases().iterator().next();
|
|
|
+ assertThat(retentionLease.id(), equalTo(getRetentionLeaseId(followerIndex, followerUUID, leaderIndex, leaderUUID)));
|
|
|
+ // we assert that retention leases are not being renewed by an unchanged timestamp
|
|
|
+ assertThat(retentionLease.timestamp(), equalTo(retentionLeases.get(i).leases().iterator().next().timestamp()));
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testRetentionLeaseRenewalIsResumedWhenFollowingIsResumed() throws Exception {
|
|
|
+ final String leaderIndex = "leader";
|
|
|
+ final String followerIndex = "follower";
|
|
|
+ final int numberOfShards = randomIntBetween(1, 4);
|
|
|
+ final int numberOfReplicas = randomIntBetween(0, 1);
|
|
|
+ final Map<String, String> additionalIndexSettings = new HashMap<>();
|
|
|
+ additionalIndexSettings.put(IndexSettings.INDEX_SOFT_DELETES_SETTING.getKey(), Boolean.toString(true));
|
|
|
+ additionalIndexSettings.put(
|
|
|
+ IndexService.RETENTION_LEASE_SYNC_INTERVAL_SETTING.getKey(),
|
|
|
+ TimeValue.timeValueMillis(200).getStringRep());
|
|
|
+ final String leaderIndexSettings = getIndexSettings(numberOfShards, numberOfReplicas, additionalIndexSettings);
|
|
|
+ assertAcked(leaderClient().admin().indices().prepareCreate(leaderIndex).setSource(leaderIndexSettings, XContentType.JSON).get());
|
|
|
+ ensureLeaderYellow(leaderIndex);
|
|
|
+ final PutFollowAction.Request followRequest = putFollow(leaderIndex, followerIndex);
|
|
|
+ followerClient().execute(PutFollowAction.INSTANCE, followRequest).get();
|
|
|
+
|
|
|
+ ensureFollowerGreen(true, followerIndex);
|
|
|
+
|
|
|
+ pauseFollow(followerIndex);
|
|
|
+
|
|
|
+ followerClient().execute(ResumeFollowAction.INSTANCE, resumeFollow(followerIndex)).actionGet();
|
|
|
+
|
|
|
+ ensureFollowerGreen(true, followerIndex);
|
|
|
+
|
|
|
+ assertRetentionLeaseRenewal(numberOfShards, numberOfReplicas, followerIndex, leaderIndex);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testRetentionLeaseIsAddedIfItDisappearsWhileFollowing() throws Exception {
|
|
|
+ final String leaderIndex = "leader";
|
|
|
+ final String followerIndex = "follower";
|
|
|
+ final int numberOfShards = 1;
|
|
|
+ final int numberOfReplicas = 1;
|
|
|
+ final Map<String, String> additionalIndexSettings = new HashMap<>();
|
|
|
+ additionalIndexSettings.put(IndexSettings.INDEX_SOFT_DELETES_SETTING.getKey(), Boolean.toString(true));
|
|
|
+ additionalIndexSettings.put(
|
|
|
+ IndexService.RETENTION_LEASE_SYNC_INTERVAL_SETTING.getKey(),
|
|
|
+ TimeValue.timeValueMillis(200).getStringRep());
|
|
|
+ final String leaderIndexSettings = getIndexSettings(numberOfShards, numberOfReplicas, additionalIndexSettings);
|
|
|
+ assertAcked(leaderClient().admin().indices().prepareCreate(leaderIndex).setSource(leaderIndexSettings, XContentType.JSON).get());
|
|
|
+ ensureLeaderYellow(leaderIndex);
|
|
|
+ final PutFollowAction.Request followRequest = putFollow(leaderIndex, followerIndex);
|
|
|
+ followerClient().execute(PutFollowAction.INSTANCE, followRequest).get();
|
|
|
+
|
|
|
+ ensureFollowerGreen(true, followerIndex);
|
|
|
+
|
|
|
+ final CountDownLatch latch = new CountDownLatch(1);
|
|
|
+
|
|
|
+ final ClusterStateResponse followerClusterState = followerClient().admin().cluster().prepareState().clear().setNodes(true).get();
|
|
|
+ for (final ObjectCursor<DiscoveryNode> senderNode : followerClusterState.getState().nodes().getNodes().values()) {
|
|
|
+ final MockTransportService senderTransportService =
|
|
|
+ (MockTransportService) getFollowerCluster().getInstance(TransportService.class, senderNode.value.getName());
|
|
|
+ senderTransportService.addSendBehavior(
|
|
|
+ (connection, requestId, action, request, options) -> {
|
|
|
+ if (RetentionLeaseActions.Renew.ACTION_NAME.equals(action)
|
|
|
+ || TransportActionProxy.getProxyAction(RetentionLeaseActions.Renew.ACTION_NAME).equals(action)) {
|
|
|
+ senderTransportService.clearAllRules();
|
|
|
+ final RetentionLeaseActions.RenewRequest renewRequest = (RetentionLeaseActions.RenewRequest) request;
|
|
|
+ final String primaryShardNodeId =
|
|
|
+ getLeaderCluster()
|
|
|
+ .clusterService()
|
|
|
+ .state()
|
|
|
+ .routingTable()
|
|
|
+ .index(leaderIndex)
|
|
|
+ .shard(renewRequest.getShardId().id())
|
|
|
+ .primaryShard()
|
|
|
+ .currentNodeId();
|
|
|
+ final String primaryShardNodeName =
|
|
|
+ getLeaderCluster().clusterService().state().nodes().get(primaryShardNodeId).getName();
|
|
|
+ final IndexShard primary =
|
|
|
+ getLeaderCluster()
|
|
|
+ .getInstance(IndicesService.class, primaryShardNodeName)
|
|
|
+ .getShardOrNull(renewRequest.getShardId());
|
|
|
+ final CountDownLatch innerLatch = new CountDownLatch(1);
|
|
|
+ // this forces the background renewal from following to face a retention lease not found exception
|
|
|
+ primary.removeRetentionLease(
|
|
|
+ getRetentionLeaseId(followerIndex, leaderIndex),
|
|
|
+ ActionListener.wrap(r -> innerLatch.countDown(), e -> fail(e.toString())));
|
|
|
+
|
|
|
+ try {
|
|
|
+ innerLatch.await();
|
|
|
+ } catch (final InterruptedException e) {
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ fail(e.toString());
|
|
|
+ }
|
|
|
+
|
|
|
+ latch.countDown();
|
|
|
+ }
|
|
|
+ connection.sendRequest(requestId, action, request, options);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ latch.await();
|
|
|
+
|
|
|
+ assertRetentionLeaseRenewal(numberOfShards, numberOfReplicas, followerIndex, leaderIndex);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * This test is fairly evil. This test is to ensure that we are protected against a race condition when unfollowing and a background
|
|
|
+ * renewal fires. The action of unfollowing will remove retention leases from the leader. If a background renewal is firing at that
|
|
|
+ * time, it means that we will be met with a retention lease not found exception. That will in turn trigger behavior to attempt to
|
|
|
+ * re-add the retention lease, which means we are left in a situation where we have unfollowed, but the retention lease still remains
|
|
|
+ * on the leader. However, we have a guard against this in the callback after the retention lease not found exception is thrown, which
|
|
|
+ * checks if the shard follow node task is cancelled or completed.
|
|
|
+ *
|
|
|
+ * To test this this behavior is correct, we capture the call to renew the retention lease. Then, we will step in between and execute
|
|
|
+ * an unfollow request. This will remove the retention lease on the leader. At this point, we can unlatch the renew call, which will
|
|
|
+ * now be met with a retention lease not found exception. We will cheat and wait for that response to come back, and then synchronously
|
|
|
+ * trigger the listener which will check to see if the shard follow node task is cancelled or completed, and if not, add the retention
|
|
|
+ * lease back. After that listener returns, we can check to see if a retention lease exists on the leader.
|
|
|
+ *
|
|
|
+ * Note, this done mean that listener will fire twice, once in our onResponseReceived hook, and once after our onResponseReceived
|
|
|
+ * callback returns. 🤷♀️
|
|
|
+ *
|
|
|
+ * @throws Exception if an exception occurs in the main test thread
|
|
|
+ */
|
|
|
+ public void testPeriodicRenewalDoesNotAddRetentionLeaseAfterUnfollow() throws Exception {
|
|
|
+ final String leaderIndex = "leader";
|
|
|
+ final String followerIndex = "follower";
|
|
|
+ final int numberOfShards = 1;
|
|
|
+ final int numberOfReplicas = 1;
|
|
|
+ final Map<String, String> additionalIndexSettings = new HashMap<>();
|
|
|
+ additionalIndexSettings.put(IndexSettings.INDEX_SOFT_DELETES_SETTING.getKey(), Boolean.toString(true));
|
|
|
+ additionalIndexSettings.put(
|
|
|
+ IndexService.RETENTION_LEASE_SYNC_INTERVAL_SETTING.getKey(),
|
|
|
+ TimeValue.timeValueMillis(200).getStringRep());
|
|
|
+ final String leaderIndexSettings = getIndexSettings(numberOfShards, numberOfReplicas, additionalIndexSettings);
|
|
|
+ assertAcked(leaderClient().admin().indices().prepareCreate(leaderIndex).setSource(leaderIndexSettings, XContentType.JSON).get());
|
|
|
+ ensureLeaderYellow(leaderIndex);
|
|
|
+ final PutFollowAction.Request followRequest = putFollow(leaderIndex, followerIndex);
|
|
|
+ followerClient().execute(PutFollowAction.INSTANCE, followRequest).get();
|
|
|
+
|
|
|
+ ensureFollowerGreen(true, followerIndex);
|
|
|
+
|
|
|
+ final CountDownLatch removeLeaseLatch = new CountDownLatch(1);
|
|
|
+ final CountDownLatch unfollowLatch = new CountDownLatch(1);
|
|
|
+ final CountDownLatch responseLatch = new CountDownLatch(1);
|
|
|
+
|
|
|
+ final ClusterStateResponse followerClusterState = followerClient().admin().cluster().prepareState().clear().setNodes(true).get();
|
|
|
+
|
|
|
+ try {
|
|
|
+ for (final ObjectCursor<DiscoveryNode> senderNode : followerClusterState.getState().nodes().getNodes().values()) {
|
|
|
+ final MockTransportService senderTransportService =
|
|
|
+ (MockTransportService) getFollowerCluster().getInstance(TransportService.class, senderNode.value.getName());
|
|
|
+ senderTransportService.addSendBehavior(
|
|
|
+ (connection, requestId, action, request, options) -> {
|
|
|
+ if (RetentionLeaseActions.Renew.ACTION_NAME.equals(action)
|
|
|
+ || TransportActionProxy.getProxyAction(RetentionLeaseActions.Renew.ACTION_NAME).equals(action)) {
|
|
|
+ final String retentionLeaseId = getRetentionLeaseId(followerIndex, leaderIndex);
|
|
|
+ try {
|
|
|
+ removeLeaseLatch.countDown();
|
|
|
+ unfollowLatch.await();
|
|
|
+
|
|
|
+ senderTransportService.transport().addMessageListener(new TransportMessageListener() {
|
|
|
+
|
|
|
+ @SuppressWarnings("rawtypes")
|
|
|
+ @Override
|
|
|
+ public void onResponseReceived(
|
|
|
+ final long responseRequestId,
|
|
|
+ final Transport.ResponseContext context) {
|
|
|
+ if (requestId == responseRequestId) {
|
|
|
+ final RetentionLeaseNotFoundException e =
|
|
|
+ new RetentionLeaseNotFoundException(retentionLeaseId);
|
|
|
+ context.handler().handleException(new RemoteTransportException(e.getMessage(), e));
|
|
|
+ responseLatch.countDown();
|
|
|
+ senderTransportService.transport().removeMessageListener(this);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ });
|
|
|
+
|
|
|
+ } catch (final InterruptedException e) {
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
+ fail(e.toString());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ connection.sendRequest(requestId, action, request, options);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ removeLeaseLatch.await();
|
|
|
+
|
|
|
+ pauseFollow(followerIndex);
|
|
|
+ assertAcked(followerClient().admin().indices().close(new CloseIndexRequest(followerIndex)).actionGet());
|
|
|
+ assertAcked(followerClient().execute(UnfollowAction.INSTANCE, new UnfollowAction.Request(followerIndex)).actionGet());
|
|
|
+
|
|
|
+ unfollowLatch.countDown();
|
|
|
+
|
|
|
+ responseLatch.await();
|
|
|
+
|
|
|
+ final IndicesStatsResponse afterUnfollowStats =
|
|
|
+ leaderClient().admin().indices().stats(new IndicesStatsRequest().clear().indices(leaderIndex)).actionGet();
|
|
|
+ final List<ShardStats> afterUnfollowShardsStats = getShardsStats(afterUnfollowStats);
|
|
|
+ for (final ShardStats shardStats : afterUnfollowShardsStats) {
|
|
|
+ assertThat(shardStats.getRetentionLeaseStats().retentionLeases().leases(), empty());
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ for (final ObjectCursor<DiscoveryNode> senderNode : followerClusterState.getState().nodes().getDataNodes().values()) {
|
|
|
+ final MockTransportService senderTransportService =
|
|
|
+ (MockTransportService) getFollowerCluster().getInstance(TransportService.class, senderNode.value.getName());
|
|
|
+ senderTransportService.clearAllRules();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void assertRetentionLeaseRenewal(
|
|
|
+ final int numberOfShards,
|
|
|
+ final int numberOfReplicas,
|
|
|
+ final String followerIndex,
|
|
|
+ final String leaderIndex) throws Exception {
|
|
|
+ // ensure that a retention lease has been put in place on each shard, and grab a copy of them
|
|
|
+ final List<RetentionLeases> retentionLeases = new ArrayList<>();
|
|
|
+ assertBusy(() -> {
|
|
|
+ retentionLeases.clear();
|
|
|
+ final IndicesStatsResponse stats =
|
|
|
+ leaderClient().admin().indices().stats(new IndicesStatsRequest().clear().indices(leaderIndex)).actionGet();
|
|
|
+ assertNotNull(stats.getShards());
|
|
|
+ assertThat(stats.getShards(), arrayWithSize(numberOfShards * (1 + numberOfReplicas)));
|
|
|
+ final List<ShardStats> shardsStats = getShardsStats(stats);
|
|
|
+ for (int i = 0; i < numberOfShards * (1 + numberOfReplicas); i++) {
|
|
|
+ final RetentionLeases currentRetentionLeases = shardsStats.get(i).getRetentionLeaseStats().retentionLeases();
|
|
|
+ assertThat(currentRetentionLeases.leases(), hasSize(1));
|
|
|
+ final RetentionLease retentionLease =
|
|
|
+ currentRetentionLeases.leases().iterator().next();
|
|
|
+ assertThat(retentionLease.id(), equalTo(getRetentionLeaseId(followerIndex, leaderIndex)));
|
|
|
+ retentionLeases.add(currentRetentionLeases);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // now ensure that the retention leases are being renewed
|
|
|
+ assertBusy(() -> {
|
|
|
+ final IndicesStatsResponse stats =
|
|
|
+ leaderClient().admin().indices().stats(new IndicesStatsRequest().clear().indices(leaderIndex)).actionGet();
|
|
|
+ assertNotNull(stats.getShards());
|
|
|
+ assertThat(stats.getShards(), arrayWithSize(numberOfShards * (1 + numberOfReplicas)));
|
|
|
+ final List<ShardStats> shardsStats = getShardsStats(stats);
|
|
|
+ for (int i = 0; i < numberOfShards * (1 + numberOfReplicas); i++) {
|
|
|
+ final RetentionLeases currentRetentionLeases = shardsStats.get(i).getRetentionLeaseStats().retentionLeases();
|
|
|
+ assertThat(currentRetentionLeases.leases(), hasSize(1));
|
|
|
+ final RetentionLease retentionLease =
|
|
|
+ currentRetentionLeases.leases().iterator().next();
|
|
|
+ assertThat(retentionLease.id(), equalTo(getRetentionLeaseId(followerIndex, leaderIndex)));
|
|
|
+ // we assert that retention leases are being renewed by an increase in the timestamp
|
|
|
+ assertThat(retentionLease.timestamp(), greaterThan(retentionLeases.get(i).leases().iterator().next().timestamp()));
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Extract the shard stats from an indices stats response, with the stats ordered by shard ID with primaries first. This is to have a
|
|
|
* consistent ordering when comparing two responses.
|