Skip to content

Commit 1694009

Browse files
authored
Restore API: Fix file settings handling (#137585) (#137878)
* Restore API: Fix file settings handling When restoring a snapshot including global state, do not remove the reserved state, but initialise an empty one. This prevents the readiness service from crashing. Also, initialising an empty state now correctly removes pre-existing reserved state settings and file settings. Fixes #122429
1 parent 2f636f3 commit 1694009

File tree

6 files changed

+89
-12
lines changed

6 files changed

+89
-12
lines changed

docs/changelog/137585.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 137585
2+
summary: "Restore API: Fix file settings handling"
3+
area: Infra/Settings
4+
type: bug
5+
issues:
6+
- 122429

server/src/internalClusterTest/java/org/elasticsearch/reservedstate/service/FileSettingsServiceIT.java

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.elasticsearch.core.Tuple;
2828
import org.elasticsearch.reservedstate.action.ReservedClusterSettingsAction;
2929
import org.elasticsearch.test.ESIntegTestCase;
30+
import org.elasticsearch.test.InternalTestCluster;
3031
import org.junit.Before;
3132

3233
import java.io.IOException;
@@ -38,6 +39,7 @@
3839
import java.util.concurrent.TimeUnit;
3940
import java.util.concurrent.atomic.AtomicLong;
4041

42+
import static org.elasticsearch.cluster.metadata.ReservedStateMetadata.EMPTY_VERSION;
4143
import static org.elasticsearch.indices.recovery.RecoverySettings.INDICES_RECOVERY_MAX_BYTES_PER_SEC_SETTING;
4244
import static org.elasticsearch.node.Node.INITIAL_STATE_TIMEOUT_SETTING;
4345
import static org.elasticsearch.test.NodeRoles.dataOnlyNode;
@@ -318,6 +320,68 @@ public void testReservedStatePersistsOnRestart() throws Exception {
318320
);
319321
}
320322

323+
public void testReservedStateClearedWhenFileAbsentAtStartup() throws Exception {
324+
internalCluster().setBootstrapMasterNodeIndex(0);
325+
logger.info("--> start master node");
326+
final String masterNode = internalCluster().startMasterOnlyNode(
327+
Settings.builder().put(INITIAL_STATE_TIMEOUT_SETTING.getKey(), "0s").build()
328+
);
329+
assertMasterNode(internalCluster().masterClient(), masterNode);
330+
var savedClusterState = setupClusterStateListener(masterNode);
331+
332+
FileSettingsService masterFileSettingsService = internalCluster().getInstance(FileSettingsService.class, masterNode);
333+
334+
assertBusy(() -> assertTrue(masterFileSettingsService.watching()));
335+
336+
logger.info("--> write some settings");
337+
writeJSONFile(masterNode, testJSON, logger, versionCounter.get());
338+
assertClusterStateSaveOK(savedClusterState.v1(), savedClusterState.v2(), "50mb");
339+
340+
logger.info("--> get file path before restarting node");
341+
Path watchedFile = masterFileSettingsService.watchedFile();
342+
assertTrue("File should exist before deletion", Files.exists(watchedFile));
343+
344+
logger.info("--> restart master and delete file while node is stopped");
345+
internalCluster().restartNode(masterNode, new InternalTestCluster.RestartCallback() {
346+
@Override
347+
public Settings onNodeStopped(String nodeName) throws Exception {
348+
logger.info("--> delete the file settings file while node is stopped");
349+
Files.deleteIfExists(watchedFile);
350+
assertFalse("File should not exist after deletion", Files.exists(watchedFile));
351+
return super.onNodeStopped(nodeName);
352+
}
353+
});
354+
355+
logger.info("--> verify reserved state is cleared when missing file is processed at startup");
356+
assertBusy(() -> {
357+
final ClusterStateResponse clusterStateResponse = clusterAdmin().state(new ClusterStateRequest(TEST_REQUEST_TIMEOUT))
358+
.actionGet();
359+
ReservedStateMetadata reservedState = clusterStateResponse.getState()
360+
.metadata()
361+
.reservedStateMetadata()
362+
.get(FileSettingsService.NAMESPACE);
363+
assertThat(reservedState, notNullValue());
364+
assertThat(reservedState.version(), equalTo(EMPTY_VERSION));
365+
assertTrue(reservedState.handlers().isEmpty());
366+
}, 20, TimeUnit.SECONDS);
367+
368+
logger.info("--> verify settings are no longer reserved and can be modified");
369+
ClusterUpdateSettingsRequest req = new ClusterUpdateSettingsRequest(TEST_REQUEST_TIMEOUT, TEST_REQUEST_TIMEOUT).persistentSettings(
370+
Settings.builder().put(INDICES_RECOVERY_MAX_BYTES_PER_SEC_SETTING.getKey(), "1234kb")
371+
);
372+
clusterAdmin().updateSettings(req).get();
373+
374+
assertThat(
375+
clusterAdmin().state(new ClusterStateRequest(TEST_REQUEST_TIMEOUT))
376+
.actionGet()
377+
.getState()
378+
.metadata()
379+
.persistentSettings()
380+
.get(INDICES_RECOVERY_MAX_BYTES_PER_SEC_SETTING.getKey()),
381+
equalTo("1234kb")
382+
);
383+
}
384+
321385
private Tuple<CountDownLatch, AtomicLong> setupClusterStateListenerForError(String node) {
322386
ClusterService clusterService = internalCluster().clusterService(node);
323387
CountDownLatch savedClusterState = new CountDownLatch(1);

server/src/internalClusterTest/java/org/elasticsearch/reservedstate/service/SnapshotsAndFileSettingsIT.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,10 @@
3232
import java.util.concurrent.TimeUnit;
3333
import java.util.concurrent.atomic.AtomicLong;
3434

35+
import static org.elasticsearch.cluster.metadata.ReservedStateMetadata.EMPTY_VERSION;
3536
import static org.elasticsearch.indices.recovery.RecoverySettings.INDICES_RECOVERY_MAX_BYTES_PER_SEC_SETTING;
3637
import static org.hamcrest.Matchers.equalTo;
38+
import static org.hamcrest.Matchers.notNullValue;
3739

3840
/**
3941
* Tests that snapshot restore behaves correctly when we have file based settings that reserve part of the
@@ -152,8 +154,14 @@ public void testRestoreWithRemovedFileSettings() throws Exception {
152154
final ClusterStateResponse clusterStateResponse = clusterAdmin().state(new ClusterStateRequest(TEST_REQUEST_TIMEOUT).metadata(true))
153155
.actionGet();
154156

155-
// We expect no reserved metadata state for file based settings, the operator file was deleted.
156-
assertNull(clusterStateResponse.getState().metadata().reservedStateMetadata().get(FileSettingsService.NAMESPACE));
157+
// We expect empty reserved metadata state for file based settings, the operator file was deleted.
158+
ReservedStateMetadata reservedState = clusterStateResponse.getState()
159+
.metadata()
160+
.reservedStateMetadata()
161+
.get(FileSettingsService.NAMESPACE);
162+
assertThat(reservedState, notNullValue());
163+
assertThat(reservedState.version(), equalTo(EMPTY_VERSION));
164+
assertTrue(reservedState.handlers().isEmpty());
157165

158166
final ClusterGetSettingsAction.Response getSettingsResponse = clusterAdmin().execute(
159167
ClusterGetSettingsAction.INSTANCE,
@@ -164,8 +172,8 @@ public void testRestoreWithRemovedFileSettings() throws Exception {
164172
getSettingsResponse.persistentSettings().get(InternalClusterInfoService.INTERNAL_CLUSTER_INFO_TIMEOUT_SETTING.getKey()),
165173
equalTo("25s")
166174
);
167-
// We didn't remove the setting set by file settings, we simply removed the reserved (operator) section.
168-
assertThat(getSettingsResponse.persistentSettings().get("indices.recovery.max_bytes_per_sec"), equalTo("50mb"));
175+
// File setting were re-set as the file was absent, this trumps the snapshot restore.
176+
assertNull(getSettingsResponse.persistentSettings().get("indices.recovery.max_bytes_per_sec"));
169177
// cleanup
170178
updateClusterSettings(
171179
Settings.builder()

server/src/main/java/org/elasticsearch/reservedstate/service/FileSettingsService.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import org.apache.logging.log4j.LogManager;
1313
import org.apache.logging.log4j.Logger;
14+
import org.elasticsearch.action.ActionListener;
1415
import org.elasticsearch.action.ActionResponse;
1516
import org.elasticsearch.action.support.PlainActionFuture;
1617
import org.elasticsearch.cluster.ClusterState;
@@ -84,19 +85,18 @@ public FileSettingsService(ClusterService clusterService, ReservedClusterStateSe
8485
public void handleSnapshotRestore(ClusterState clusterState, Metadata.Builder mdBuilder) {
8586
assert clusterState.nodes().isLocalNodeElectedMaster();
8687

87-
ReservedStateMetadata fileSettingsMetadata = clusterState.metadata().reservedStateMetadata().get(NAMESPACE);
88-
8988
// When we restore from a snapshot we remove the reserved cluster state for file settings,
9089
// since we don't know the current operator configuration, e.g. file settings could be disabled
9190
// on the target cluster. If file settings exist and the cluster state has lost it's reserved
9291
// state for the "file_settings" namespace, we touch our file settings file to cause it to re-process the file.
9392
if (watching() && filesExists(watchedFile())) {
93+
ReservedStateMetadata fileSettingsMetadata = clusterState.metadata().reservedStateMetadata().get(NAMESPACE);
9494
if (fileSettingsMetadata != null) {
9595
ReservedStateMetadata withResetVersion = new ReservedStateMetadata.Builder(fileSettingsMetadata).version(0L).build();
9696
mdBuilder.put(withResetVersion);
9797
}
98-
} else if (fileSettingsMetadata != null) {
99-
mdBuilder.removeReservedState(fileSettingsMetadata);
98+
} else {
99+
stateService.initEmpty(NAMESPACE, ActionListener.noop());
100100
}
101101
}
102102

server/src/main/java/org/elasticsearch/reservedstate/service/ReservedClusterStateService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ public void initEmpty(String namespace, ActionListener<ActionResponse.Empty> lis
166166
namespace,
167167
emptyState,
168168
ReservedStateVersionCheck.HIGHER_VERSION_ONLY,
169-
Map.of(),
169+
handlers,
170170
List.of(),
171171
// error state should not be possible since there is no metadata being parsed or processed
172172
errorState -> { throw new AssertionError(); },

server/src/test/java/org/elasticsearch/reservedstate/service/FileSettingsServiceTests.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@
6262

6363
import static java.nio.file.StandardCopyOption.ATOMIC_MOVE;
6464
import static org.elasticsearch.node.Node.NODE_NAME_SETTING;
65-
import static org.hamcrest.Matchers.anEmptyMap;
6665
import static org.hamcrest.Matchers.hasEntry;
6766
import static org.mockito.ArgumentMatchers.any;
6867
import static org.mockito.ArgumentMatchers.eq;
@@ -306,7 +305,7 @@ public void testStopWorksInMiddleOfProcessing() throws Exception {
306305
deadThreadLatch.countDown();
307306
}
308307

309-
public void testHandleSnapshotRestoreClearsMetadata() throws Exception {
308+
public void testHandleSnapshotRestoreClearsMetadata() {
310309
ClusterState state = ClusterState.builder(clusterService.state())
311310
.metadata(
312311
Metadata.builder(clusterService.state().metadata())
@@ -318,7 +317,7 @@ public void testHandleSnapshotRestoreClearsMetadata() throws Exception {
318317
Metadata.Builder metadata = Metadata.builder(state.metadata());
319318
fileSettingsService.handleSnapshotRestore(state, metadata);
320319

321-
assertThat(metadata.build().reservedStateMetadata(), anEmptyMap());
320+
verify(controller).initEmpty(FileSettingsService.NAMESPACE, ActionListener.noop());
322321
}
323322

324323
public void testHandleSnapshotRestoreResetsMetadata() throws Exception {

0 commit comments

Comments
 (0)