Skip to content

Commit d7d002b

Browse files
authored
Restore API: Fix file settings handling (#137585)
* 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 397def3 commit d7d002b

File tree

6 files changed

+88
-12
lines changed

6 files changed

+88
-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
@@ -28,6 +28,7 @@
2828
import org.elasticsearch.health.node.FetchHealthInfoCacheAction;
2929
import org.elasticsearch.reservedstate.action.ReservedClusterSettingsAction;
3030
import org.elasticsearch.test.ESIntegTestCase;
31+
import org.elasticsearch.test.InternalTestCluster;
3132
import org.junit.Before;
3233

3334
import java.io.IOException;
@@ -40,6 +41,7 @@
4041
import java.util.concurrent.atomic.AtomicLong;
4142
import java.util.stream.Stream;
4243

44+
import static org.elasticsearch.cluster.metadata.ReservedStateMetadata.EMPTY_VERSION;
4345
import static org.elasticsearch.health.HealthStatus.YELLOW;
4446
import static org.elasticsearch.indices.recovery.RecoverySettings.INDICES_RECOVERY_MAX_BYTES_PER_SEC_SETTING;
4547
import static org.elasticsearch.node.Node.INITIAL_STATE_TIMEOUT_SETTING;
@@ -322,6 +324,68 @@ public void testReservedStatePersistsOnRestart() throws Exception {
322324
);
323325
}
324326

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

144-
ReservedStateMetadata fileSettingsMetadata = clusterState.metadata().reservedStateMetadata().get(NAMESPACE);
145-
146144
// When we restore from a snapshot we remove the reserved cluster state for file settings,
147145
// since we don't know the current operator configuration, e.g. file settings could be disabled
148146
// on the target cluster. If file settings exist and the cluster state has lost it's reserved
149147
// state for the "file_settings" namespace, we touch our file settings file to cause it to re-process the file.
150148
if (watching() && filesExists(watchedFile)) {
149+
ReservedStateMetadata fileSettingsMetadata = clusterState.metadata().reservedStateMetadata().get(NAMESPACE);
151150
if (fileSettingsMetadata != null) {
152151
ReservedStateMetadata withResetVersion = new ReservedStateMetadata.Builder(fileSettingsMetadata).version(0L).build();
153152
mdBuilder.put(withResetVersion);
154153
}
155-
} else if (fileSettingsMetadata != null) {
156-
mdBuilder.removeReservedState(fileSettingsMetadata);
154+
} else {
155+
stateService.initEmpty(NAMESPACE, ActionListener.noop());
157156
}
158157
}
159158

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ public void initEmpty(String namespace, ActionListener<ActionResponse.Empty> lis
278278
namespace,
279279
emptyState,
280280
ReservedStateVersionCheck.HIGHER_VERSION_ONLY,
281-
Map.of(),
281+
clusterHandlers,
282282
List.of(),
283283
// error state should not be possible since there is no metadata being parsed or processed
284284
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
@@ -74,7 +74,6 @@
7474
import static org.elasticsearch.health.HealthStatus.GREEN;
7575
import static org.elasticsearch.health.HealthStatus.YELLOW;
7676
import static org.elasticsearch.node.Node.NODE_NAME_SETTING;
77-
import static org.hamcrest.Matchers.anEmptyMap;
7877
import static org.hamcrest.Matchers.hasEntry;
7978
import static org.mockito.ArgumentMatchers.any;
8079
import static org.mockito.ArgumentMatchers.argThat;
@@ -443,7 +442,7 @@ public void testStopWorksInMiddleOfProcessing() throws Exception {
443442
deadThreadLatch.countDown();
444443
}
445444

446-
public void testHandleSnapshotRestoreClearsMetadata() throws Exception {
445+
public void testHandleSnapshotRestoreClearsMetadata() {
447446
ClusterState state = ClusterState.builder(clusterService.state())
448447
.metadata(
449448
Metadata.builder(clusterService.state().metadata())
@@ -455,7 +454,7 @@ public void testHandleSnapshotRestoreClearsMetadata() throws Exception {
455454
Metadata.Builder metadata = Metadata.builder(state.metadata());
456455
fileSettingsService.handleSnapshotRestore(state, ClusterState.builder(state), metadata, ProjectId.DEFAULT);
457456

458-
assertThat(metadata.build().reservedStateMetadata(), anEmptyMap());
457+
verify(controller).initEmpty(FileSettingsService.NAMESPACE, ActionListener.noop());
459458
}
460459

461460
public void testHandleSnapshotRestoreResetsMetadata() throws Exception {

0 commit comments

Comments
 (0)