Skip to content

Commit b868677

Browse files
authored
Fix file settings service tests (#115770)
This PR addresses some of the failure causes tracked under #115280 and #115725: the latch-await setup was rather convoluted and the move command not always correctly invoked in the right order. This PR cleans up latching by separating awaiting the first processing call (on start) from waiting on the subsequent call. Also, it makes writing the file more robust w.r.t. OS'es where `atomic_move` may not be available. This should address failures around the timeout await, and the assertion failures around invoked methods tracked here: #115725 (comment) But will likely require another round of changes to address the failures to delete files. Relates: #115280 Relates: #115725
1 parent 04682dc commit b868677

File tree

2 files changed

+52
-36
lines changed

2 files changed

+52
-36
lines changed

muted-tests.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -236,9 +236,6 @@ tests:
236236
- class: org.elasticsearch.xpack.inference.DefaultEndPointsIT
237237
method: testInferDeploysDefaultE5
238238
issue: https://github.com/elastic/elasticsearch/issues/115361
239-
- class: org.elasticsearch.reservedstate.service.FileSettingsServiceTests
240-
method: testProcessFileChanges
241-
issue: https://github.com/elastic/elasticsearch/issues/115280
242239
- class: org.elasticsearch.xpack.security.FileSettingsRoleMappingsRestartIT
243240
method: testFileSettingsReprocessedOnRestartWithoutVersionChange
244241
issue: https://github.com/elastic/elasticsearch/issues/115450

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

Lines changed: 52 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import org.elasticsearch.common.component.Lifecycle;
2525
import org.elasticsearch.common.settings.ClusterSettings;
2626
import org.elasticsearch.common.settings.Settings;
27+
import org.elasticsearch.core.IOUtils;
28+
import org.elasticsearch.core.Strings;
2729
import org.elasticsearch.core.TimeValue;
2830
import org.elasticsearch.env.BuildVersion;
2931
import org.elasticsearch.env.Environment;
@@ -39,9 +41,10 @@
3941
import org.mockito.stubbing.Answer;
4042

4143
import java.io.IOException;
44+
import java.io.UncheckedIOException;
45+
import java.nio.file.AtomicMoveNotSupportedException;
4246
import java.nio.file.Files;
4347
import java.nio.file.Path;
44-
import java.nio.file.StandardCopyOption;
4548
import java.util.List;
4649
import java.util.Map;
4750
import java.util.Set;
@@ -50,6 +53,8 @@
5053
import java.util.concurrent.atomic.AtomicBoolean;
5154
import java.util.function.Consumer;
5255

56+
import static java.nio.file.StandardCopyOption.ATOMIC_MOVE;
57+
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
5358
import static org.elasticsearch.node.Node.NODE_NAME_SETTING;
5459
import static org.hamcrest.Matchers.anEmptyMap;
5560
import static org.hamcrest.Matchers.hasEntry;
@@ -190,9 +195,7 @@ public void testInitialFileWorks() throws Exception {
190195
return null;
191196
}).when(controller).process(any(), any(XContentParser.class), any(), any());
192197

193-
CountDownLatch latch = new CountDownLatch(1);
194-
195-
fileSettingsService.addFileChangedListener(latch::countDown);
198+
CountDownLatch fileProcessingLatch = new CountDownLatch(1);
196199

197200
Files.createDirectories(fileSettingsService.watchedFileDir());
198201
// contents of the JSON don't matter, we just need a file to exist
@@ -202,15 +205,14 @@ public void testInitialFileWorks() throws Exception {
202205
try {
203206
return invocation.callRealMethod();
204207
} finally {
205-
latch.countDown();
208+
fileProcessingLatch.countDown();
206209
}
207210
}).when(fileSettingsService).processFileOnServiceStart();
208211

209212
fileSettingsService.start();
210213
fileSettingsService.clusterChanged(new ClusterChangedEvent("test", clusterService.state(), ClusterState.EMPTY_STATE));
211214

212-
// wait for listener to be called
213-
assertTrue(latch.await(20, TimeUnit.SECONDS));
215+
longAwait(fileProcessingLatch);
214216

215217
verify(fileSettingsService, times(1)).processFileOnServiceStart();
216218
verify(controller, times(1)).process(any(), any(XContentParser.class), eq(ReservedStateVersionCheck.HIGHER_OR_SAME_VERSION), any());
@@ -223,40 +225,40 @@ public void testProcessFileChanges() throws Exception {
223225
return null;
224226
}).when(controller).process(any(), any(XContentParser.class), any(), any());
225227

226-
// we get three events: initial clusterChanged event, first write, second write
227-
CountDownLatch latch = new CountDownLatch(3);
228-
229-
fileSettingsService.addFileChangedListener(latch::countDown);
230-
231-
Files.createDirectories(fileSettingsService.watchedFileDir());
232-
// contents of the JSON don't matter, we just need a file to exist
233-
writeTestFile(fileSettingsService.watchedFile(), "{}");
234-
228+
CountDownLatch changesOnStartLatch = new CountDownLatch(1);
235229
doAnswer((Answer<?>) invocation -> {
236230
try {
237231
return invocation.callRealMethod();
238232
} finally {
239-
latch.countDown();
233+
changesOnStartLatch.countDown();
240234
}
241235
}).when(fileSettingsService).processFileOnServiceStart();
236+
237+
CountDownLatch changesLatch = new CountDownLatch(1);
242238
doAnswer((Answer<?>) invocation -> {
243239
try {
244240
return invocation.callRealMethod();
245241
} finally {
246-
latch.countDown();
242+
changesLatch.countDown();
247243
}
248244
}).when(fileSettingsService).processFileChanges();
249245

246+
Files.createDirectories(fileSettingsService.watchedFileDir());
247+
// contents of the JSON don't matter, we just need a file to exist
248+
writeTestFile(fileSettingsService.watchedFile(), "{}");
249+
250250
fileSettingsService.start();
251251
fileSettingsService.clusterChanged(new ClusterChangedEvent("test", clusterService.state(), ClusterState.EMPTY_STATE));
252-
// second file change; contents still don't matter
253-
overwriteTestFile(fileSettingsService.watchedFile(), "{}");
254252

255-
// wait for listener to be called (once for initial processing, once for subsequent update)
256-
assertTrue(latch.await(20, TimeUnit.SECONDS));
253+
longAwait(changesOnStartLatch);
257254

258255
verify(fileSettingsService, times(1)).processFileOnServiceStart();
259256
verify(controller, times(1)).process(any(), any(XContentParser.class), eq(ReservedStateVersionCheck.HIGHER_OR_SAME_VERSION), any());
257+
258+
// second file change; contents still don't matter
259+
writeTestFile(fileSettingsService.watchedFile(), "[]");
260+
longAwait(changesLatch);
261+
260262
verify(fileSettingsService, times(1)).processFileChanges();
261263
verify(controller, times(1)).process(any(), any(XContentParser.class), eq(ReservedStateVersionCheck.HIGHER_VERSION_ONLY), any());
262264
}
@@ -295,9 +297,7 @@ public void testStopWorksInMiddleOfProcessing() throws Exception {
295297
// Make some fake settings file to cause the file settings service to process it
296298
writeTestFile(fileSettingsService.watchedFile(), "{}");
297299

298-
// we need to wait a bit, on MacOS it may take up to 10 seconds for the Java watcher service to notice the file,
299-
// on Linux is instantaneous. Windows is instantaneous too.
300-
assertTrue(processFileLatch.await(30, TimeUnit.SECONDS));
300+
longAwait(processFileLatch);
301301

302302
// Stopping the service should interrupt the watcher thread, we should be able to stop
303303
fileSettingsService.stop();
@@ -352,15 +352,34 @@ public void testHandleSnapshotRestoreResetsMetadata() throws Exception {
352352
}
353353

354354
// helpers
355-
private void writeTestFile(Path path, String contents) throws IOException {
356-
Path tempFilePath = createTempFile();
357-
Files.writeString(tempFilePath, contents);
358-
Files.move(tempFilePath, path, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
355+
private static void writeTestFile(Path path, String contents) {
356+
Path tempFile = null;
357+
try {
358+
tempFile = Files.createTempFile(path.getParent(), path.getFileName().toString(), "tmp");
359+
Files.writeString(tempFile, contents);
360+
361+
try {
362+
Files.move(tempFile, path, REPLACE_EXISTING, ATOMIC_MOVE);
363+
} catch (AtomicMoveNotSupportedException e) {
364+
Files.move(tempFile, path, REPLACE_EXISTING);
365+
}
366+
} catch (final IOException e) {
367+
throw new UncheckedIOException(Strings.format("could not write file [%s]", path.toAbsolutePath()), e);
368+
} finally {
369+
// we are ignoring exceptions here, so we do not need handle whether or not tempFile was initialized nor if the file exists
370+
IOUtils.deleteFilesIgnoringExceptions(tempFile);
371+
}
359372
}
360373

361-
private void overwriteTestFile(Path path, String contents) throws IOException {
362-
Path tempFilePath = createTempFile();
363-
Files.writeString(tempFilePath, contents);
364-
Files.move(tempFilePath, path, StandardCopyOption.REPLACE_EXISTING);
374+
// this waits for up to 20 seconds to account for watcher service differences between OSes
375+
// on MacOS it may take up to 10 seconds for the Java watcher service to notice the file,
376+
// on Linux is instantaneous. Windows is instantaneous too.
377+
private static void longAwait(CountDownLatch latch) {
378+
try {
379+
assertTrue("longAwait: CountDownLatch did not reach zero within the timeout", latch.await(20, TimeUnit.SECONDS));
380+
} catch (InterruptedException e) {
381+
Thread.currentThread().interrupt();
382+
fail(e, "longAwait: interrupted waiting for CountDownLatch to reach zero");
383+
}
365384
}
366385
}

0 commit comments

Comments
 (0)