This is an automated email from the ASF dual-hosted git repository.
ndimiduk pushed a commit to branch branch-3
in repository https://gitbox.apache.org/repos/asf/hbase.git
The following commit(s) were added to refs/heads/branch-3 by this push:
new 6c55c3f6be4 HBASE-28931: Poll SSL cert file for changes (#6381)
6c55c3f6be4 is described below
commit 6c55c3f6be4babcc89f7818c3f1d587412f1f41e
Author: Charles Connell <[email protected]>
AuthorDate: Tue Dec 10 11:33:42 2024 -0500
HBASE-28931: Poll SSL cert file for changes (#6381)
Signed-off-by: Nick Dimiduk <[email protected]>
---
.../apache/hadoop/hbase/io/FileChangeWatcher.java | 112 +++++++-------
.../hadoop/hbase/io/crypto/tls/X509Util.java | 49 ++----
.../hadoop/hbase/io/TestFileChangeWatcher.java | 172 +++++++--------------
3 files changed, 115 insertions(+), 218 deletions(-)
diff --git
a/hbase-common/src/main/java/org/apache/hadoop/hbase/io/FileChangeWatcher.java
b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/FileChangeWatcher.java
index 70300da45d9..3ac4076766b 100644
---
a/hbase-common/src/main/java/org/apache/hadoop/hbase/io/FileChangeWatcher.java
+++
b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/FileChangeWatcher.java
@@ -18,30 +18,27 @@
package org.apache.hadoop.hbase.io;
import java.io.IOException;
-import java.nio.file.ClosedWatchServiceException;
-import java.nio.file.FileSystem;
+import java.nio.file.Files;
import java.nio.file.Path;
-import java.nio.file.StandardWatchEventKinds;
-import java.nio.file.WatchEvent;
-import java.nio.file.WatchKey;
-import java.nio.file.WatchService;
-import java.util.function.Consumer;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileTime;
+import java.time.Duration;
import org.apache.yetus.audience.InterfaceAudience;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
- * Instances of this class can be used to watch a directory for file changes.
When a file is added
- * to, deleted from, or is modified in the given directory, the callback
provided by the user will
- * be called from a background thread. Some things to keep in mind:
+ * Instances of this class can be used to watch a file for changes. When a
file's modification time
+ * changes, the callback provided by the user will be called from a background
thread. Modification
+ * are detected by checking the file's attributes every polling interval. Some
things to keep in
+ * mind:
* <ul>
* <li>The callback should be thread-safe.</li>
* <li>Changes that happen around the time the thread is started may be
missed.</li>
* <li>There is a delay between a file changing and the callback firing.</li>
- * <li>The watch is not recursive - changes to subdirectories will not trigger
a callback.</li>
* </ul>
* <p/>
- * This file has been copied from the Apache ZooKeeper project.
+ * This file was originally copied from the Apache ZooKeeper project, and then
modified.
* @see <a href=
*
"https://github.com/apache/zookeeper/blob/8148f966947d3ecf3db0b756d93c9ffa88174af9/zookeeper-server/src/main/java/org/apache/zookeeper/common/FileChangeWatcher.java">Base
* revision</a>
@@ -49,9 +46,13 @@ import org.slf4j.LoggerFactory;
@InterfaceAudience.Private
public final class FileChangeWatcher {
+ public interface FileChangeWatcherCallback {
+ void callback(Path path);
+ }
+
private static final Logger LOG =
LoggerFactory.getLogger(FileChangeWatcher.class);
- public enum State {
+ enum State {
NEW, // object created but start() not called yet
STARTING, // start() called but background thread has not entered main loop
RUNNING, // background thread is running
@@ -61,29 +62,29 @@ public final class FileChangeWatcher {
private final WatcherThread watcherThread;
private State state; // protected by synchronized(this)
+ private FileTime lastModifiedTime;
+ private final Object lastModifiedTimeLock;
+ private final Path filePath;
+ private final Duration pollInterval;
/**
- * Creates a watcher that watches <code>dirPath</code> and invokes
<code>callback</code> on
+ * Creates a watcher that watches <code>filePath</code> and invokes
<code>callback</code> on
* changes.
- * @param dirPath the directory to watch.
+ * @param filePath the file to watch.
* @param callback the callback to invoke with events.
<code>event.kind()</code> will return the
* type of event, and <code>event.context()</code> will
return the filename
* relative to <code>dirPath</code>.
* @throws IOException if there is an error creating the WatchService.
*/
- public FileChangeWatcher(Path dirPath, String threadNameSuffix,
Consumer<WatchEvent<?>> callback)
- throws IOException {
- FileSystem fs = dirPath.getFileSystem();
- WatchService watchService = fs.newWatchService();
-
- LOG.debug("Registering with watch service: {}", dirPath);
+ public FileChangeWatcher(Path filePath, String threadNameSuffix, Duration
pollInterval,
+ FileChangeWatcherCallback callback) throws IOException {
+ this.filePath = filePath;
+ this.pollInterval = pollInterval;
- dirPath.register(watchService,
- new WatchEvent.Kind<?>[] { StandardWatchEventKinds.ENTRY_CREATE,
- StandardWatchEventKinds.ENTRY_DELETE,
StandardWatchEventKinds.ENTRY_MODIFY,
- StandardWatchEventKinds.OVERFLOW });
state = State.NEW;
- this.watcherThread = new WatcherThread(threadNameSuffix, watchService,
callback);
+ lastModifiedTimeLock = new Object();
+ lastModifiedTime = Files.readAttributes(filePath,
BasicFileAttributes.class).lastModifiedTime();
+ this.watcherThread = new WatcherThread(threadNameSuffix, callback);
this.watcherThread.setDaemon(true);
}
@@ -91,7 +92,7 @@ public final class FileChangeWatcher {
* Returns the current {@link FileChangeWatcher.State}.
* @return the current state.
*/
- public synchronized State getState() {
+ private synchronized State getState() {
return state;
}
@@ -187,13 +188,10 @@ public final class FileChangeWatcher {
private static final String THREAD_NAME_PREFIX = "FileChangeWatcher-";
- final WatchService watchService;
- final Consumer<WatchEvent<?>> callback;
+ final FileChangeWatcherCallback callback;
- WatcherThread(String threadNameSuffix, WatchService watchService,
- Consumer<WatchEvent<?>> callback) {
+ WatcherThread(String threadNameSuffix, FileChangeWatcherCallback callback)
{
super(THREAD_NAME_PREFIX + threadNameSuffix);
- this.watchService = watchService;
this.callback = callback;
setUncaughtExceptionHandler(FileChangeWatcher::handleException);
}
@@ -201,7 +199,7 @@ public final class FileChangeWatcher {
@Override
public void run() {
try {
- LOG.info("{} thread started", getName());
+ LOG.debug("{} thread started", getName());
if (
!compareAndSetState(FileChangeWatcher.State.STARTING,
FileChangeWatcher.State.RUNNING)
) {
@@ -216,44 +214,40 @@ public final class FileChangeWatcher {
runLoop();
} catch (Exception e) {
LOG.warn("Error in runLoop()", e);
- throw e;
+ throw new RuntimeException(e);
} finally {
- try {
- watchService.close();
- } catch (IOException e) {
- LOG.warn("Error closing watch service", e);
- }
- LOG.info("{} thread finished", getName());
+ LOG.debug("{} thread finished", getName());
FileChangeWatcher.this.setState(FileChangeWatcher.State.STOPPED);
}
}
- private void runLoop() {
+ private void runLoop() throws IOException {
while (FileChangeWatcher.this.getState() ==
FileChangeWatcher.State.RUNNING) {
- WatchKey key;
- try {
- key = watchService.take();
- } catch (InterruptedException | ClosedWatchServiceException e) {
- LOG.debug("{} was interrupted and is shutting down...", getName());
- break;
+ BasicFileAttributes attributes = Files.readAttributes(filePath,
BasicFileAttributes.class);
+ boolean modified = false;
+ synchronized (lastModifiedTimeLock) {
+ FileTime maybeNewLastModifiedTime = attributes.lastModifiedTime();
+ if (!lastModifiedTime.equals(maybeNewLastModifiedTime)) {
+ modified = true;
+ lastModifiedTime = maybeNewLastModifiedTime;
+ }
}
- for (WatchEvent<?> event : key.pollEvents()) {
- LOG.debug("Got file changed event: {} with context: {}",
event.kind(), event.context());
+
+ // avoid calling callback while holding lock
+ if (modified) {
try {
- callback.accept(event);
+ callback.callback(filePath);
} catch (Throwable e) {
LOG.error("Error from callback", e);
}
}
- boolean isKeyValid = key.reset();
- if (!isKeyValid) {
- // This is likely a problem, it means that file reloading is broken,
probably because the
- // directory we are watching was deleted or otherwise became
inaccessible (unmounted,
- // permissions
- // changed, ???).
- // For now, we log an error and exit the watcher thread.
- LOG.error("Watch key no longer valid, maybe the directory is
inaccessible?");
- break;
+
+ try {
+ Thread.sleep(pollInterval.toMillis());
+ } catch (InterruptedException e) {
+ LOG.debug("Interrupted", e);
+ Thread.currentThread().interrupt();
+ return;
}
}
}
diff --git
a/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/X509Util.java
b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/X509Util.java
index 46809050b5a..4f9d2f5611b 100644
---
a/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/X509Util.java
+++
b/hbase-common/src/main/java/org/apache/hadoop/hbase/io/crypto/tls/X509Util.java
@@ -20,13 +20,12 @@ package org.apache.hadoop.hbase.io.crypto.tls;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
-import java.nio.file.StandardWatchEventKinds;
-import java.nio.file.WatchEvent;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.Security;
import java.security.cert.PKIXBuilderParameters;
import java.security.cert.X509CertSelector;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -155,6 +154,8 @@ public final class X509Util {
private static final String[] DEFAULT_CIPHERS_OPENSSL =
getOpenSslFilteredDefaultCiphers();
+ private static final Duration FILE_POLL_INTERVAL = Duration.ofMinutes(1);
+
/**
* Not all of our default ciphers are available in OpenSSL. Takes our
default cipher lists and
* filters them to only those available in OpenSsl. Prefers TLS 1.3, then
GCM, then CBC because
@@ -510,51 +511,19 @@ public final class X509Util {
return null;
}
final Path filePath = Paths.get(fileLocation).toAbsolutePath();
- Path parentPath = filePath.getParent();
- if (parentPath == null) {
- throw new IOException("Key/trust store path does not have a parent: " +
filePath);
- }
FileChangeWatcher fileChangeWatcher =
- new FileChangeWatcher(parentPath,
Objects.toString(filePath.getFileName()), watchEvent -> {
- handleWatchEvent(filePath, watchEvent, resetContext);
- });
+ new FileChangeWatcher(filePath,
Objects.toString(filePath.getFileName()), FILE_POLL_INTERVAL,
+ watchEventFilePath -> handleWatchEvent(watchEventFilePath,
resetContext));
fileChangeWatcher.start();
return fileChangeWatcher;
}
/**
* Handler for watch events that let us know a file we may care about has
changed on disk.
- * @param filePath the path to the file we are watching for changes.
- * @param event the WatchEvent.
*/
- private static void handleWatchEvent(Path filePath, WatchEvent<?> event,
Runnable resetContext) {
- boolean shouldResetContext = false;
- Path dirPath = filePath.getParent();
- if (event.kind().equals(StandardWatchEventKinds.OVERFLOW)) {
- // If we get notified about possibly missed events, reload the key store
/ trust store just to
- // be sure.
- shouldResetContext = true;
- } else if (
- event.kind().equals(StandardWatchEventKinds.ENTRY_MODIFY)
- || event.kind().equals(StandardWatchEventKinds.ENTRY_CREATE)
- ) {
- Path eventFilePath = dirPath.resolve((Path) event.context());
- if (filePath.equals(eventFilePath)) {
- shouldResetContext = true;
- }
- }
- // Note: we don't care about delete events
- if (shouldResetContext) {
- LOG.info(
- "Attempting to reset default SSL context after receiving watch event:
{} with context: {}",
- event.kind(), event.context());
- resetContext.run();
- } else {
- if (LOG.isDebugEnabled()) {
- LOG.debug(
- "Ignoring watch event and keeping previous default SSL context.
Event kind: {} with context: {}",
- event.kind(), event.context());
- }
- }
+ private static void handleWatchEvent(Path filePath, Runnable resetContext) {
+ LOG.info("Attempting to reset default SSL context after receiving watch
event on file {}",
+ filePath);
+ resetContext.run();
}
}
diff --git
a/hbase-common/src/test/java/org/apache/hadoop/hbase/io/TestFileChangeWatcher.java
b/hbase-common/src/test/java/org/apache/hadoop/hbase/io/TestFileChangeWatcher.java
index ea00110d816..106c077e22d 100644
---
a/hbase-common/src/test/java/org/apache/hadoop/hbase/io/TestFileChangeWatcher.java
+++
b/hbase-common/src/test/java/org/apache/hadoop/hbase/io/TestFileChangeWatcher.java
@@ -27,8 +27,8 @@ import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
-import java.nio.file.StandardWatchEventKinds;
-import java.nio.file.WatchEvent;
+import java.nio.file.Path;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
@@ -50,7 +50,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
- * This file has been copied from the Apache ZooKeeper project.
+ * This file was originally copied from the Apache ZooKeeper project, but has
been modified
* @see <a href=
*
"https://github.com/apache/zookeeper/blob/391cb4aa6b54e19a028215e1340232a114c23ed3/zookeeper-server/src/test/java/org/apache/zookeeper/common/FileChangeWatcherTest.java">Base
* revision</a>
@@ -62,20 +62,17 @@ public class TestFileChangeWatcher {
public static final HBaseClassTestRule CLASS_RULE =
HBaseClassTestRule.forClass(TestFileChangeWatcher.class);
- private static File tempDir;
private static File tempFile;
private static final Logger LOG =
LoggerFactory.getLogger(TestFileChangeWatcher.class);
private static final HBaseCommonTestingUtil UTIL = new
HBaseCommonTestingUtil();
private static final long FS_TIMEOUT = 30000L;
+ private static final Duration POLL_INTERVAL = Duration.ofMillis(100);
@BeforeClass
public static void createTempFile() throws IOException {
- tempDir = new
File(UTIL.getDataTestDir(TestFileChangeWatcher.class.getSimpleName()).toString())
- .getCanonicalFile();
- FileUtils.forceMkdir(tempDir);
- tempFile = File.createTempFile("zk_test_", "", tempDir);
+ tempFile = File.createTempFile("zk_test_", "");
}
@AfterClass
@@ -86,7 +83,7 @@ public class TestFileChangeWatcher {
@Test
public void testEnableCertFileReloading() throws IOException {
Configuration myConf = new Configuration();
- String sharedPath = "/tmp/foo.jks";
+ String sharedPath = File.createTempFile("foo",
"foo.jks").getAbsolutePath();
myConf.set(X509Util.TLS_CONFIG_KEYSTORE_LOCATION, sharedPath);
myConf.set(X509Util.TLS_CONFIG_TRUSTSTORE_LOCATION, sharedPath);
AtomicReference<FileChangeWatcher> keystoreWatcher = new
AtomicReference<>();
@@ -94,61 +91,42 @@ public class TestFileChangeWatcher {
X509Util.enableCertFileReloading(myConf, keystoreWatcher,
truststoreWatcher, () -> {
});
assertNotNull(keystoreWatcher.get());
- assertThat(keystoreWatcher.get().getWatcherThreadName(),
Matchers.endsWith("-foo.jks"));
+ assertThat(keystoreWatcher.get().getWatcherThreadName(),
Matchers.endsWith("foo.jks"));
assertNull(truststoreWatcher.get());
keystoreWatcher.getAndSet(null).stop();
truststoreWatcher.set(null);
- String truststorePath = "/tmp/bar.jks";
+ String truststorePath = File.createTempFile("bar",
"bar.jks").getAbsolutePath();
myConf.set(X509Util.TLS_CONFIG_TRUSTSTORE_LOCATION, truststorePath);
X509Util.enableCertFileReloading(myConf, keystoreWatcher,
truststoreWatcher, () -> {
});
assertNotNull(keystoreWatcher.get());
- assertThat(keystoreWatcher.get().getWatcherThreadName(),
Matchers.endsWith("-foo.jks"));
+ assertThat(keystoreWatcher.get().getWatcherThreadName(),
Matchers.endsWith("foo.jks"));
assertNotNull(truststoreWatcher.get());
- assertThat(truststoreWatcher.get().getWatcherThreadName(),
Matchers.endsWith("-bar.jks"));
+ assertThat(truststoreWatcher.get().getWatcherThreadName(),
Matchers.endsWith("bar.jks"));
keystoreWatcher.getAndSet(null).stop();
truststoreWatcher.getAndSet(null).stop();
}
@Test
- public void testCallbackWorksOnFileChanges() throws IOException,
InterruptedException {
+ public void testNoFalseNotifications() throws IOException,
InterruptedException {
FileChangeWatcher watcher = null;
try {
- final List<WatchEvent<?>> events = new ArrayList<>();
- watcher = new FileChangeWatcher(tempDir.toPath(), "test", event -> {
- LOG.info("Got an update: {} {}", event.kind(), event.context());
- // Filter out the extra ENTRY_CREATE events that are
- // sometimes seen at the start. Even though we create the watcher
- // after the file exists, sometimes we still get a create event.
- if (StandardWatchEventKinds.ENTRY_CREATE.equals(event.kind())) {
- return;
- }
- synchronized (events) {
- events.add(event);
- events.notifyAll();
+ final List<Path> notifiedPaths = new ArrayList<>();
+ watcher = new FileChangeWatcher(tempFile.toPath(), "test",
POLL_INTERVAL, path -> {
+ LOG.info("Got an update on path {}", path);
+ synchronized (notifiedPaths) {
+ notifiedPaths.add(path);
+ notifiedPaths.notifyAll();
}
});
watcher.start();
watcher.waitForState(FileChangeWatcher.State.RUNNING);
Thread.sleep(1000L); // TODO hack
- for (int i = 0; i < 3; i++) {
- LOG.info("Modifying file, attempt {}", (i + 1));
- FileUtils.writeStringToFile(tempFile, "Hello world " + i + "\n",
StandardCharsets.UTF_8,
- true);
- synchronized (events) {
- if (events.size() < i + 1) {
- events.wait(FS_TIMEOUT);
- }
- assertEquals("Wrong number of events", i + 1, events.size());
- WatchEvent<?> event = events.get(i);
- assertEquals(StandardWatchEventKinds.ENTRY_MODIFY, event.kind());
- assertEquals(tempFile.getName(), event.context().toString());
- }
- }
+ assertEquals("Should not have been notified", 0, notifiedPaths.size());
} finally {
if (watcher != null) {
watcher.stop();
@@ -158,70 +136,32 @@ public class TestFileChangeWatcher {
}
@Test
- public void testCallbackWorksOnFileTouched() throws IOException,
InterruptedException {
- FileChangeWatcher watcher = null;
- try {
- final List<WatchEvent<?>> events = new ArrayList<>();
- watcher = new FileChangeWatcher(tempDir.toPath(), "test", event -> {
- LOG.info("Got an update: {} {}", event.kind(), event.context());
- // Filter out the extra ENTRY_CREATE events that are
- // sometimes seen at the start. Even though we create the watcher
- // after the file exists, sometimes we still get a create event.
- if (StandardWatchEventKinds.ENTRY_CREATE.equals(event.kind())) {
- return;
- }
- synchronized (events) {
- events.add(event);
- events.notifyAll();
- }
- });
- watcher.start();
- watcher.waitForState(FileChangeWatcher.State.RUNNING);
- Thread.sleep(1000L); // TODO hack
- LOG.info("Touching file");
- FileUtils.touch(tempFile);
- synchronized (events) {
- if (events.isEmpty()) {
- events.wait(FS_TIMEOUT);
- }
- assertFalse(events.isEmpty());
- WatchEvent<?> event = events.get(0);
- assertEquals(StandardWatchEventKinds.ENTRY_MODIFY, event.kind());
- assertEquals(tempFile.getName(), event.context().toString());
- }
- } finally {
- if (watcher != null) {
- watcher.stop();
- watcher.waitForState(FileChangeWatcher.State.STOPPED);
- }
- }
- }
-
- @Test
- public void testCallbackWorksOnFileAdded() throws IOException,
InterruptedException {
+ public void testCallbackWorksOnFileChanges() throws IOException,
InterruptedException {
FileChangeWatcher watcher = null;
try {
- final List<WatchEvent<?>> events = new ArrayList<>();
- watcher = new FileChangeWatcher(tempDir.toPath(), "test", event -> {
- LOG.info("Got an update: {} {}", event.kind(), event.context());
- synchronized (events) {
- events.add(event);
- events.notifyAll();
+ final List<Path> notifiedPaths = new ArrayList<>();
+ watcher = new FileChangeWatcher(tempFile.toPath(), "test",
POLL_INTERVAL, path -> {
+ LOG.info("Got an update on path {}", path);
+ synchronized (notifiedPaths) {
+ notifiedPaths.add(path);
+ notifiedPaths.notifyAll();
}
});
watcher.start();
watcher.waitForState(FileChangeWatcher.State.RUNNING);
Thread.sleep(1000L); // TODO hack
- File tempFile2 = File.createTempFile("zk_test_", "", tempDir);
- tempFile2.deleteOnExit();
- synchronized (events) {
- if (events.isEmpty()) {
- events.wait(FS_TIMEOUT);
+ for (int i = 0; i < 3; i++) {
+ LOG.info("Modifying file, attempt {}", (i + 1));
+ FileUtils.writeStringToFile(tempFile, "Hello world " + i + "\n",
StandardCharsets.UTF_8,
+ true);
+ synchronized (notifiedPaths) {
+ if (notifiedPaths.size() < i + 1) {
+ notifiedPaths.wait(FS_TIMEOUT);
+ }
+ assertEquals("Wrong number of notifications", i + 1,
notifiedPaths.size());
+ Path path = notifiedPaths.get(i);
+ assertEquals(tempFile.getPath(), path.toString());
}
- assertFalse(events.isEmpty());
- WatchEvent<?> event = events.get(0);
- assertEquals(StandardWatchEventKinds.ENTRY_CREATE, event.kind());
- assertEquals(tempFile2.getName(), event.context().toString());
}
} finally {
if (watcher != null) {
@@ -232,35 +172,29 @@ public class TestFileChangeWatcher {
}
@Test
- public void testCallbackWorksOnFileDeleted() throws IOException,
InterruptedException {
+ public void testCallbackWorksOnFileTouched() throws IOException,
InterruptedException {
FileChangeWatcher watcher = null;
try {
- final List<WatchEvent<?>> events = new ArrayList<>();
- watcher = new FileChangeWatcher(tempDir.toPath(), "test", event -> {
- LOG.info("Got an update: {} {}", event.kind(), event.context());
- // Filter out the extra ENTRY_CREATE events that are
- // sometimes seen at the start. Even though we create the watcher
- // after the file exists, sometimes we still get a create event.
- if (StandardWatchEventKinds.ENTRY_CREATE.equals(event.kind())) {
- return;
- }
- synchronized (events) {
- events.add(event);
- events.notifyAll();
+ final List<Path> notifiedPaths = new ArrayList<>();
+ watcher = new FileChangeWatcher(tempFile.toPath(), "test",
POLL_INTERVAL, path -> {
+ LOG.info("Got an update on path {}", path);
+ synchronized (notifiedPaths) {
+ notifiedPaths.add(path);
+ notifiedPaths.notifyAll();
}
});
watcher.start();
watcher.waitForState(FileChangeWatcher.State.RUNNING);
Thread.sleep(1000L); // TODO hack
- tempFile.delete();
- synchronized (events) {
- if (events.isEmpty()) {
- events.wait(FS_TIMEOUT);
+ LOG.info("Touching file");
+ FileUtils.touch(tempFile);
+ synchronized (notifiedPaths) {
+ if (notifiedPaths.isEmpty()) {
+ notifiedPaths.wait(FS_TIMEOUT);
}
- assertFalse(events.isEmpty());
- WatchEvent<?> event = events.get(0);
- assertEquals(StandardWatchEventKinds.ENTRY_DELETE, event.kind());
- assertEquals(tempFile.getName(), event.context().toString());
+ assertFalse(notifiedPaths.isEmpty());
+ Path path = notifiedPaths.get(0);
+ assertEquals(tempFile.getPath(), path.toString());
}
} finally {
if (watcher != null) {
@@ -276,8 +210,8 @@ public class TestFileChangeWatcher {
FileChangeWatcher watcher = null;
try {
final AtomicInteger callCount = new AtomicInteger(0);
- watcher = new FileChangeWatcher(tempDir.toPath(), "test", event -> {
- LOG.info("Got an update: {} {}", event.kind(), event.context());
+ watcher = new FileChangeWatcher(tempFile.toPath(), "test",
POLL_INTERVAL, path -> {
+ LOG.info("Got an update for path {}", path);
int oldValue;
synchronized (callCount) {
oldValue = callCount.getAndIncrement();