Skip to content

Commit 28ad17c

Browse files
authored
Restore API: Fix file settings handling (elastic#137585) (elastic#137875)
* 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 elastic#122429
1 parent e8280ea commit 28ad17c

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
@@ -130,19 +130,18 @@ public Path watchedFile() {
130130
public void handleSnapshotRestore(ClusterState clusterState, Metadata.Builder mdBuilder) {
131131
assert clusterState.nodes().isLocalNodeElectedMaster();
132132

133-
ReservedStateMetadata fileSettingsMetadata = clusterState.metadata().reservedStateMetadata().get(NAMESPACE);
134-
135133
// When we restore from a snapshot we remove the reserved cluster state for file settings,
136134
// since we don't know the current operator configuration, e.g. file settings could be disabled
137135
// on the target cluster. If file settings exist and the cluster state has lost it's reserved
138136
// state for the "file_settings" namespace, we touch our file settings file to cause it to re-process the file.
139137
if (watching() && filesExists(watchedFile)) {
138+
ReservedStateMetadata fileSettingsMetadata = clusterState.metadata().reservedStateMetadata().get(NAMESPACE);
140139
if (fileSettingsMetadata != null) {
141140
ReservedStateMetadata withResetVersion = new ReservedStateMetadata.Builder(fileSettingsMetadata).version(0L).build();
142141
mdBuilder.put(withResetVersion);
143142
}
144-
} else if (fileSettingsMetadata != null) {
145-
mdBuilder.removeReservedState(fileSettingsMetadata);
143+
} else {
144+
stateService.initEmpty(NAMESPACE, ActionListener.noop());
146145
}
147146
}
148147

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

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

445-
public void testHandleSnapshotRestoreClearsMetadata() throws Exception {
444+
public void testHandleSnapshotRestoreClearsMetadata() {
446445
ClusterState state = ClusterState.builder(clusterService.state())
447446
.metadata(
448447
Metadata.builder(clusterService.state().metadata())
@@ -454,7 +453,7 @@ public void testHandleSnapshotRestoreClearsMetadata() throws Exception {
454453
Metadata.Builder metadata = Metadata.builder(state.metadata());
455454
fileSettingsService.handleSnapshotRestore(state, metadata);
456455

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

460459
public void testHandleSnapshotRestoreResetsMetadata() throws Exception {

0 commit comments

Comments
 (0)