Skip to content

Commit ab4cc0c

Browse files
authored
Adjust unpromotable shard refresh request validation to allow RefreshResult.NO_REFRESH (#129176)
When a primary shard uses the read-only engine, it always returns a RefreshResult.NO_REFRESH for refreshes. Since #93600 we added an extra roundtrip to hook unpromotable shard refresh logic. This hook is always executed, even if there are no unpromotable shards, but the UnpromotableShardRefreshRequest would fail if the primary shard returns a RefreshResult.NO_REFRESH result. Fix to be backported to several versions as it's annoying. Closes #129036
1 parent 936f338 commit ab4cc0c

File tree

7 files changed

+82
-11
lines changed

7 files changed

+82
-11
lines changed

docs/changelog/129176.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 129176
2+
summary: Adjust unpromotable shard refresh request validation to allow `RefreshResult.NO_REFRESH`
3+
area: Searchable Snapshots
4+
type: bug
5+
issues:
6+
- 129036

server/src/main/java/org/elasticsearch/action/admin/indices/refresh/TransportShardRefreshAction.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import org.elasticsearch.cluster.service.ClusterService;
2424
import org.elasticsearch.common.io.stream.StreamInput;
2525
import org.elasticsearch.common.settings.Settings;
26-
import org.elasticsearch.index.engine.Engine;
2726
import org.elasticsearch.index.shard.IndexShard;
2827
import org.elasticsearch.indices.IndicesService;
2928
import org.elasticsearch.injection.guice.Inject;
@@ -127,10 +126,7 @@ public void onPrimaryOperationComplete(
127126
ActionListener<Void> listener
128127
) {
129128
var primaryTerm = replicaRequest.primaryRefreshResult.primaryTerm();
130-
assert Engine.UNKNOWN_PRIMARY_TERM < primaryTerm : primaryTerm;
131-
132129
var generation = replicaRequest.primaryRefreshResult.generation();
133-
assert Engine.RefreshResult.UNKNOWN_GENERATION < generation : generation;
134130

135131
transportService.sendRequest(
136132
transportService.getLocalNode(),

server/src/main/java/org/elasticsearch/action/admin/indices/refresh/TransportUnpromotableShardRefreshAction.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.elasticsearch.cluster.service.ClusterService;
2323
import org.elasticsearch.common.io.stream.StreamInput;
2424
import org.elasticsearch.core.TimeValue;
25+
import org.elasticsearch.index.engine.Engine;
2526
import org.elasticsearch.indices.IndicesService;
2627
import org.elasticsearch.injection.guice.Inject;
2728
import org.elasticsearch.node.NodeClosedException;
@@ -154,12 +155,13 @@ protected void unpromotableShardOperation(
154155
return;
155156
}
156157

158+
var primaryTerm = request.getPrimaryTerm();
159+
assert Engine.UNKNOWN_PRIMARY_TERM < primaryTerm : primaryTerm;
160+
var segmentGeneration = request.getSegmentGeneration();
161+
assert Engine.RefreshResult.UNKNOWN_GENERATION < segmentGeneration : segmentGeneration;
162+
157163
ActionListener.run(responseListener, listener -> {
158-
shard.waitForPrimaryTermAndGeneration(
159-
request.getPrimaryTerm(),
160-
request.getSegmentGeneration(),
161-
listener.map(l -> ActionResponse.Empty.INSTANCE)
162-
);
164+
shard.waitForPrimaryTermAndGeneration(primaryTerm, segmentGeneration, listener.map(l -> ActionResponse.Empty.INSTANCE));
163165
});
164166
}
165167

server/src/main/java/org/elasticsearch/action/admin/indices/refresh/UnpromotableShardRefreshRequest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ public UnpromotableShardRefreshRequest(StreamInput in) throws IOException {
6363
@Override
6464
public ActionRequestValidationException validate() {
6565
ActionRequestValidationException validationException = super.validate();
66-
if (segmentGeneration == Engine.RefreshResult.UNKNOWN_GENERATION) {
66+
if (segmentGeneration == Engine.RefreshResult.UNKNOWN_GENERATION && primaryTerm == Engine.UNKNOWN_PRIMARY_TERM) {
67+
// read-only primary shards (like searchable snapshot shard) return Engine.RefreshResult.NO_REFRESH during refresh
68+
} else if (segmentGeneration == Engine.RefreshResult.UNKNOWN_GENERATION) {
6769
validationException = addValidationError("segment generation is unknown", validationException);
6870
}
6971
return validationException;

server/src/main/java/org/elasticsearch/index/engine/ReadOnlyEngine.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,7 @@ public RefreshResult refresh(String source) {
446446

447447
@Override
448448
public void maybeRefresh(String source, ActionListener<RefreshResult> listener) throws EngineException {
449-
listener.onResponse(RefreshResult.NO_REFRESH);
449+
ActionListener.completeWith(listener, () -> refresh(source));
450450
}
451451

452452
@Override

x-pack/plugin/searchable-snapshots/src/internalClusterTest/java/org/elasticsearch/xpack/searchablesnapshots/FrozenSearchableSnapshotsIntegTests.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
import static org.elasticsearch.search.aggregations.AggregationBuilders.dateHistogram;
6868
import static org.elasticsearch.snapshots.SearchableSnapshotsSettings.SEARCHABLE_SNAPSHOT_STORE_TYPE;
6969
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
70+
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures;
7071
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailuresAndResponse;
7172
import static org.elasticsearch.xpack.searchablesnapshots.SearchableSnapshots.SNAPSHOT_RECOVERY_STATE_FACTORY_KEY;
7273
import static org.hamcrest.Matchers.containsString;
@@ -521,6 +522,37 @@ public void testRequestCacheOnFrozen() throws Exception {
521522
}
522523
}
523524

525+
public void testRefreshPartiallyMountedIndex() throws Exception {
526+
internalCluster().ensureAtLeastNumDataNodes(2);
527+
528+
final var index = "index";
529+
createIndex(index, 1, 0);
530+
populateIndex(index, 1_000);
531+
532+
final var repository = "repository";
533+
createRepository(repository, FsRepository.TYPE, Settings.builder().put("location", randomRepoPath()));
534+
535+
final var snapshot = "repository";
536+
createFullSnapshot(repository, snapshot);
537+
538+
assertAcked(indicesAdmin().prepareDelete(index));
539+
540+
final var partialIndex = "partial-" + index;
541+
mountSnapshot(
542+
repository,
543+
snapshot,
544+
index,
545+
partialIndex,
546+
Settings.builder().put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, randomInt(1)).build(),
547+
MountSearchableSnapshotRequest.Storage.SHARED_CACHE
548+
);
549+
ensureGreen(partialIndex);
550+
551+
// before the fix this would have failed
552+
var refreshResult = indicesAdmin().prepareRefresh(partialIndex).execute().actionGet();
553+
assertNoFailures(refreshResult);
554+
}
555+
524556
public void testTierPreferenceCannotBeRemovedForFrozenIndex() throws Exception {
525557
final String fsRepoName = randomAlphaOfLength(10);
526558
final String indexName = randomAlphaOfLength(10).toLowerCase(Locale.ROOT);

x-pack/plugin/searchable-snapshots/src/internalClusterTest/java/org/elasticsearch/xpack/searchablesnapshots/SearchableSnapshotsIntegTests.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import org.elasticsearch.index.shard.IndexLongFieldRange;
5252
import org.elasticsearch.indices.IndicesService;
5353
import org.elasticsearch.repositories.RepositoryData;
54+
import org.elasticsearch.repositories.fs.FsRepository;
5455
import org.elasticsearch.rest.RestStatus;
5556
import org.elasticsearch.search.SearchResponseUtils;
5657
import org.elasticsearch.snapshots.SnapshotId;
@@ -89,6 +90,7 @@
8990
import static org.elasticsearch.snapshots.SearchableSnapshotsSettings.SEARCHABLE_SNAPSHOT_STORE_TYPE;
9091
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
9192
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
93+
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures;
9294
import static org.elasticsearch.xpack.searchablesnapshots.SearchableSnapshots.SNAPSHOT_RECOVERY_STATE_FACTORY_KEY;
9395
import static org.hamcrest.Matchers.allOf;
9496
import static org.hamcrest.Matchers.containsString;
@@ -1280,6 +1282,37 @@ public void onFailure(Exception e) {
12801282
}
12811283
}
12821284

1285+
public void testRefreshFullyMountedIndex() throws Exception {
1286+
internalCluster().ensureAtLeastNumDataNodes(2);
1287+
1288+
final var index = "index";
1289+
createIndex(index, 1, 0);
1290+
populateIndex(index, 1_000);
1291+
1292+
final var repository = "repository";
1293+
createRepository(repository, FsRepository.TYPE, Settings.builder().put("location", randomRepoPath()));
1294+
1295+
final var snapshot = "repository";
1296+
createFullSnapshot(repository, snapshot);
1297+
1298+
assertAcked(indicesAdmin().prepareDelete(index));
1299+
1300+
final var fullIndex = "full-" + index;
1301+
mountSnapshot(
1302+
repository,
1303+
snapshot,
1304+
index,
1305+
fullIndex,
1306+
Settings.builder().put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, randomInt(1)).build(),
1307+
MountSearchableSnapshotRequest.Storage.FULL_COPY
1308+
);
1309+
ensureGreen(fullIndex);
1310+
1311+
// before the fix this would have failed
1312+
var refreshResult = indicesAdmin().prepareRefresh(fullIndex).execute().actionGet();
1313+
assertNoFailures(refreshResult);
1314+
}
1315+
12831316
private TaskInfo getTaskForActionFromMaster(String action) {
12841317
ListTasksResponse response = client().execute(
12851318
TransportListTasksAction.TYPE,

0 commit comments

Comments
 (0)