Skip to content

Commit 926f698

Browse files
authored
[9.0] Adjust unpromotable shard refresh request validation to allow RefreshResult.NO_REFRESH (#129334)
* 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 Backport of #129176 for 9.0.3 * fix import
1 parent 1f4e305 commit 926f698

File tree

8 files changed

+87
-15
lines changed

8 files changed

+87
-15
lines changed

build-tools-internal/src/main/resources/changelog-schema.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
"Rollup",
8787
"SQL",
8888
"Search",
89+
"Searchable Snapshots",
8990
"Security",
9091
"Snapshot/Restore",
9192
"Stats",

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: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -125,17 +125,13 @@ public void onPrimaryOperationComplete(
125125
IndexShardRoutingTable indexShardRoutingTable,
126126
ActionListener<Void> listener
127127
) {
128-
assert replicaRequest.primaryRefreshResult.refreshed() : "primary has not refreshed";
129-
UnpromotableShardRefreshRequest unpromotableReplicaRequest = new UnpromotableShardRefreshRequest(
130-
indexShardRoutingTable,
131-
replicaRequest.primaryRefreshResult.primaryTerm(),
132-
replicaRequest.primaryRefreshResult.generation(),
133-
false
134-
);
128+
var primaryTerm = replicaRequest.primaryRefreshResult.primaryTerm();
129+
var generation = replicaRequest.primaryRefreshResult.generation();
130+
135131
transportService.sendRequest(
136132
transportService.getLocalNode(),
137133
TransportUnpromotableShardRefreshAction.NAME,
138-
unpromotableReplicaRequest,
134+
new UnpromotableShardRefreshRequest(indexShardRoutingTable, primaryTerm, generation, false),
139135
new ActionListenerResponseHandler<>(listener.safeMap(r -> null), in -> ActionResponse.Empty.INSTANCE, refreshExecutor)
140136
);
141137
}

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
@@ -70,6 +70,7 @@
7070
import static org.elasticsearch.search.aggregations.AggregationBuilders.dateHistogram;
7171
import static org.elasticsearch.snapshots.SearchableSnapshotsSettings.SEARCHABLE_SNAPSHOT_STORE_TYPE;
7272
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
73+
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures;
7374
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailuresAndResponse;
7475
import static org.elasticsearch.xpack.searchablesnapshots.SearchableSnapshots.SNAPSHOT_RECOVERY_STATE_FACTORY_KEY;
7576
import static org.hamcrest.Matchers.arrayWithSize;
@@ -536,6 +537,37 @@ public void testRequestCacheOnFrozen() throws Exception {
536537
}
537538
}
538539

540+
public void testRefreshPartiallyMountedIndex() throws Exception {
541+
internalCluster().ensureAtLeastNumDataNodes(2);
542+
543+
final var index = "index";
544+
createIndex(index, 1, 0);
545+
populateIndex(index, 1_000);
546+
547+
final var repository = "repository";
548+
createRepository(repository, FsRepository.TYPE, Settings.builder().put("location", randomRepoPath()));
549+
550+
final var snapshot = "repository";
551+
createFullSnapshot(repository, snapshot);
552+
553+
assertAcked(indicesAdmin().prepareDelete(index));
554+
555+
final var partialIndex = "partial-" + index;
556+
mountSnapshot(
557+
repository,
558+
snapshot,
559+
index,
560+
partialIndex,
561+
Settings.builder().put(IndexMetadata.SETTING_NUMBER_OF_REPLICAS, randomInt(1)).build(),
562+
MountSearchableSnapshotRequest.Storage.SHARED_CACHE
563+
);
564+
ensureGreen(partialIndex);
565+
566+
// before the fix this would have failed
567+
var refreshResult = indicesAdmin().prepareRefresh(partialIndex).execute().actionGet();
568+
assertNoFailures(refreshResult);
569+
}
570+
539571
public void testTierPreferenceCannotBeRemovedForFrozenIndex() throws Exception {
540572
final String fsRepoName = randomAlphaOfLength(10);
541573
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;
@@ -1274,6 +1276,37 @@ public void onFailure(Exception e) {
12741276
}
12751277
}
12761278

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

0 commit comments

Comments
 (0)