diff --git a/integration/fuse/src/main/java/alluxio/fuse/AlluxioFuseFileSystem.java b/integration/fuse/src/main/java/alluxio/fuse/AlluxioFuseFileSystem.java index 3d0b005677dc..6e1cb6e0edd9 100644 --- a/integration/fuse/src/main/java/alluxio/fuse/AlluxioFuseFileSystem.java +++ b/integration/fuse/src/main/java/alluxio/fuse/AlluxioFuseFileSystem.java @@ -26,6 +26,7 @@ import alluxio.util.CommonUtils; import alluxio.util.WaitForOptions; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; @@ -61,16 +62,25 @@ * Implements the FUSE callbacks defined by jnr-fuse. */ @ThreadSafe -final class AlluxioFuseFileSystem extends FuseStubFS { +public final class AlluxioFuseFileSystem extends FuseStubFS { private static final Logger LOG = LoggerFactory.getLogger(AlluxioFuseFileSystem.class); - private static final int MAX_OPEN_FILES = Integer.MAX_VALUE; private static final int MAX_OPEN_WAITTIME_MS = 5000; + /** + * 4294967295 is unsigned long -1, -1 means that uid or gid is not set. + * 4294967295 or -1 occurs when chown without user name or group name. + * Please view https://github.com/SerCeMan/jnr-fuse/issues/67 for more details. + */ + @VisibleForTesting + public static final long ID_NOT_SET_VALUE = -1; + @VisibleForTesting + public static final long ID_NOT_SET_VALUE_UNSIGNED = 4294967295L; + private static final long UID = AlluxioFuseUtils.getUid(System.getProperty("user.name")); private static final long GID = AlluxioFuseUtils.getGid(System.getProperty("user.name")); - private final boolean mIsUserGroupTranslation; + private final boolean mIsUserGroupTranslation; private final FileSystem mFileSystem; // base path within Alluxio namespace that is used for FUSE operations // For example, if alluxio-fuse is mounted in /mnt/alluxio and mAlluxioRootPath @@ -79,9 +89,9 @@ final class AlluxioFuseFileSystem extends FuseStubFS { private final Path mAlluxioRootPath; // Keeps a cache of the most recently translated paths from String to Alluxio URI private final LoadingCache mPathResolverCache; - // Table of open files with corresponding InputStreams and OutputStreams private final Map mOpenFiles; + private long mNextOpenFileId; /** @@ -90,7 +100,7 @@ final class AlluxioFuseFileSystem extends FuseStubFS { * @param fs Alluxio file system * @param opts options */ - AlluxioFuseFileSystem(FileSystem fs, AlluxioFuseOptions opts) { + public AlluxioFuseFileSystem(FileSystem fs, AlluxioFuseOptions opts) { super(); mFileSystem = fs; mAlluxioRootPath = Paths.get(opts.getAlluxioRoot()); @@ -149,34 +159,45 @@ public int chown(String path, @uid_t long uid, @gid_t long gid) { } try { - String groupName = AlluxioFuseUtils.getGroupName(gid); - if (groupName.isEmpty()) { - // This should never be reached since input gid is always valid - // If user chown without group name, the primary group gid of the user name will be provided - LOG.error("Failed to get group name from gid {}.", gid); - return -ErrorCodes.EFAULT(); - } - - SetAttributeOptions options = SetAttributeOptions.defaults().setGroup(groupName); + SetAttributeOptions options = SetAttributeOptions.defaults(); final AlluxioURI uri = mPathResolverCache.getUnchecked(path); - if (uid != -1 && uid != 4294967295L) { - // 4294967295 is just unsigned long -1, -1 means that uid is not set - // 4294967295 or -1 occurs when chown without user name or chgrp - // Please view https://github.com/SerCeMan/jnr-fuse/issues/67 for more details - String userName = AlluxioFuseUtils.getUserName(uid); + String userName = ""; + if (uid != ID_NOT_SET_VALUE && uid != ID_NOT_SET_VALUE_UNSIGNED) { + userName = AlluxioFuseUtils.getUserName(uid); if (userName.isEmpty()) { // This should never be reached LOG.error("Failed to get user name from uid {}", uid); return -ErrorCodes.EFAULT(); } options.setOwner(userName); - LOG.info("Change owner and group of file {} to {}:{}", path, userName, groupName); - } else { - LOG.info("Change group of file {} to {}", path, groupName); } - mFileSystem.setAttribute(uri, options); + String groupName = ""; + if (gid != ID_NOT_SET_VALUE && gid != ID_NOT_SET_VALUE_UNSIGNED) { + groupName = AlluxioFuseUtils.getGroupName(gid); + if (groupName.isEmpty()) { + // This should never be reached + LOG.error("Failed to get group name from gid {}", gid); + return -ErrorCodes.EFAULT(); + } + options.setGroup(groupName); + } else if (!userName.isEmpty()) { + groupName = AlluxioFuseUtils.getGroupName(userName); + options.setGroup(groupName); + } + + if (userName.isEmpty() && groupName.isEmpty()) { + // This should never be reached + LOG.info("Unable to change owner and group of file {} when uid is {} and gid is {}", + path, userName, groupName); + } else if (userName.isEmpty()) { + LOG.info("Change group of file {} to {}", path, groupName); + mFileSystem.setAttribute(uri, options); + } else { + LOG.info("Change owner of file {} to {}", path, groupName); + mFileSystem.setAttribute(uri, options); + } } catch (IOException | AlluxioException e) { LOG.error("Exception on {}", path, e); return -ErrorCodes.EIO(); diff --git a/integration/fuse/src/main/java/alluxio/fuse/AlluxioFuseOptions.java b/integration/fuse/src/main/java/alluxio/fuse/AlluxioFuseOptions.java index 4a15b6a320e9..59f22bf7b797 100644 --- a/integration/fuse/src/main/java/alluxio/fuse/AlluxioFuseOptions.java +++ b/integration/fuse/src/main/java/alluxio/fuse/AlluxioFuseOptions.java @@ -19,13 +19,20 @@ * Convenience class to pass around Alluxio-FUSE options. */ @ThreadSafe -final class AlluxioFuseOptions { +public final class AlluxioFuseOptions { private final String mMountPoint; private final String mAlluxioRoot; private final boolean mDebug; private final List mFuseOpts; - AlluxioFuseOptions(String mountPoint, String alluxioRoot, boolean debug, List fuseOpts) { + /** + * @param mountPoint the path to where the FS should be mounted + * @param alluxioRoot the path within alluxio that will be used as the mounted FS root + * @param debug whether the file system should be mounted in debug mode + * @param fuseOpts extra options to pass to the FUSE mount command + */ + public AlluxioFuseOptions(String mountPoint, String alluxioRoot, + boolean debug, List fuseOpts) { mMountPoint = mountPoint; mAlluxioRoot = alluxioRoot; mDebug = debug; diff --git a/integration/fuse/src/main/java/alluxio/fuse/AlluxioFuseUtils.java b/integration/fuse/src/main/java/alluxio/fuse/AlluxioFuseUtils.java index c69b6604f834..5879ef6e6dad 100644 --- a/integration/fuse/src/main/java/alluxio/fuse/AlluxioFuseUtils.java +++ b/integration/fuse/src/main/java/alluxio/fuse/AlluxioFuseUtils.java @@ -112,6 +112,27 @@ public static String getGroupName(long gid) throws IOException { return ""; } + /** + * Checks whether fuse is installed in local file system. + * Alluxio-Fuse only support mac and linux. + * + * @return true if fuse is installed, false otherwise + */ + public static boolean isFuseInstalled() { + try { + if (OSUtils.isLinux()) { + String result = ShellUtils.execCommand("fusermount", "-V"); + return !result.isEmpty(); + } else if (OSUtils.isMacOS()) { + String result = ShellUtils.execCommand("bash", "-c", "mount | grep FUSE"); + return !result.isEmpty(); + } + } catch (Exception e) { + return false; + } + return false; + } + /** * Runs the "id" command with the given options on the passed username. * diff --git a/integration/fuse/src/test/java/alluxio/fuse/AlluxioFuseFileSystemTest.java b/integration/fuse/src/test/java/alluxio/fuse/AlluxioFuseFileSystemTest.java index 38300ed9853e..1f5fad454381 100644 --- a/integration/fuse/src/test/java/alluxio/fuse/AlluxioFuseFileSystemTest.java +++ b/integration/fuse/src/test/java/alluxio/fuse/AlluxioFuseFileSystemTest.java @@ -18,6 +18,7 @@ import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -106,6 +107,54 @@ public void chown() throws Exception { verify(mFileSystem).setAttribute(expectedPath, options); } + @Test + public void chownWithoutValidGid() throws Exception { + long uid = AlluxioFuseUtils.getUid(System.getProperty("user.name")); + long gid = AlluxioFuseFileSystem.ID_NOT_SET_VALUE; + mFuseFs.chown("/foo/bar", uid, gid); + String userName = System.getProperty("user.name"); + String groupName = AlluxioFuseUtils.getGroupName(userName); + AlluxioURI expectedPath = BASE_EXPECTED_URI.join("/foo/bar"); + SetAttributeOptions options = + SetAttributeOptions.defaults().setGroup(groupName).setOwner(userName); + verify(mFileSystem).setAttribute(expectedPath, options); + + gid = AlluxioFuseFileSystem.ID_NOT_SET_VALUE_UNSIGNED; + mFuseFs.chown("/foo/bar", uid, gid); + verify(mFileSystem, times(2)).setAttribute(expectedPath, options); + } + + @Test + public void chownWithoutValidUid() throws Exception { + String userName = System.getProperty("user.name"); + long uid = AlluxioFuseFileSystem.ID_NOT_SET_VALUE; + long gid = AlluxioFuseUtils.getGid(userName); + mFuseFs.chown("/foo/bar", uid, gid); + + String groupName = AlluxioFuseUtils.getGroupName(userName); + AlluxioURI expectedPath = BASE_EXPECTED_URI.join("/foo/bar"); + SetAttributeOptions options = + SetAttributeOptions.defaults().setGroup(groupName); + verify(mFileSystem).setAttribute(expectedPath, options); + + uid = AlluxioFuseFileSystem.ID_NOT_SET_VALUE_UNSIGNED; + mFuseFs.chown("/foo/bar", uid, gid); + verify(mFileSystem, times(2)).setAttribute(expectedPath, options); + } + + @Test + public void chownWithoutValidUidAndGid() throws Exception { + long uid = AlluxioFuseFileSystem.ID_NOT_SET_VALUE; + long gid = AlluxioFuseFileSystem.ID_NOT_SET_VALUE; + mFuseFs.chown("/foo/bar", uid, gid); + verify(mFileSystem, never()).setAttribute(any()); + + uid = AlluxioFuseFileSystem.ID_NOT_SET_VALUE_UNSIGNED; + gid = AlluxioFuseFileSystem.ID_NOT_SET_VALUE_UNSIGNED; + mFuseFs.chown("/foo/bar", uid, gid); + verify(mFileSystem, never()).setAttribute(any()); + } + @Test public void create() throws Exception { mFileInfo.flags.set(O_WRONLY.intValue()); @@ -271,6 +320,19 @@ public void renameNewExist() throws Exception { assertEquals(-ErrorCodes.EEXIST(), mFuseFs.rename("/old", "/new")); } + @Test + public void rmdir() throws Exception { + AlluxioURI expectedPath = BASE_EXPECTED_URI.join("/foo/bar"); + FileInfo info = new FileInfo(); + info.setFolder(true); + URIStatus status = new URIStatus(info); + when(mFileSystem.getStatus(expectedPath)).thenReturn(status); + when(mFileSystem.exists(expectedPath)).thenReturn(true); + doNothing().when(mFileSystem).delete(expectedPath); + mFuseFs.rmdir("/foo/bar"); + verify(mFileSystem).delete(expectedPath); + } + @Test public void write() throws Exception { FileOutStream fos = mock(FileOutStream.class); @@ -295,6 +357,19 @@ public void write() throws Exception { verify(fos, times(1)).write(expected); } + @Test + public void unlink() throws Exception { + AlluxioURI expectedPath = BASE_EXPECTED_URI.join("/foo/bar"); + FileInfo info = new FileInfo(); + info.setFolder(false); + URIStatus status = new URIStatus(info); + when(mFileSystem.getStatus(expectedPath)).thenReturn(status); + when(mFileSystem.exists(expectedPath)).thenReturn(true); + doNothing().when(mFileSystem).delete(expectedPath); + mFuseFs.unlink("/foo/bar"); + verify(mFileSystem).delete(expectedPath); + } + @Test public void pathTranslation() throws Exception { final LoadingCache resolver = mFuseFs.getPathResolverCache(); diff --git a/tests/pom.xml b/tests/pom.xml index 25700676689b..ec8e630d011b 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -215,6 +215,11 @@ alluxio-underfs-local ${project.version} + + org.alluxio + alluxio-integration-fuse + ${project.version} + diff --git a/tests/src/test/java/alluxio/client/fuse/FuseFileSystemIntegrationTest.java b/tests/src/test/java/alluxio/client/fuse/FuseFileSystemIntegrationTest.java new file mode 100644 index 000000000000..199452e62de6 --- /dev/null +++ b/tests/src/test/java/alluxio/client/fuse/FuseFileSystemIntegrationTest.java @@ -0,0 +1,307 @@ +/* + * The Alluxio Open Foundation licenses this work under the Apache License, version 2.0 + * (the "License"). You may not use this work except in compliance with the License, which is + * available at www.apache.org/licenses/LICENSE-2.0 + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied, as more fully set forth in the License. + * + * See the NOTICE file distributed with this work for information regarding copyright ownership. + */ + +package alluxio.client.fuse; + +import alluxio.AlluxioTestDirectory; +import alluxio.AlluxioURI; +import alluxio.Constants; +import alluxio.PropertyKey; +import alluxio.client.ReadType; +import alluxio.client.WriteType; +import alluxio.client.file.FileInStream; +import alluxio.client.file.FileOutStream; +import alluxio.client.file.FileSystem; +import alluxio.client.file.FileSystemTestUtils; +import alluxio.client.file.URIStatus; +import alluxio.client.file.options.DeleteOptions; +import alluxio.client.file.options.OpenFileOptions; +import alluxio.fuse.AlluxioFuseFileSystem; +import alluxio.fuse.AlluxioFuseOptions; +import alluxio.fuse.AlluxioFuseUtils; +import alluxio.testutils.LocalAlluxioClusterResource; +import alluxio.util.CommonUtils; +import alluxio.util.OSUtils; +import alluxio.util.ShellUtils; +import alluxio.util.WaitForOptions; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Assume; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.concurrent.TimeoutException; + +/** + * Integration tests for {@link AlluxioFuseFileSystem}. + */ +public class FuseFileSystemIntegrationTest { + private static final int WAIT_TIMEOUT_MS = 60 * Constants.SECOND_MS; + + // Fuse user group translation needs to be enabled to support chown/chgrp/ls commands + // to show accurate information + @ClassRule + public static LocalAlluxioClusterResource sLocalAlluxioClusterResource = + new LocalAlluxioClusterResource.Builder() + .setProperty(PropertyKey.FUSE_USER_GROUP_TRANSLATION_ENABLED, true) + .setProperty(PropertyKey.USER_BLOCK_SIZE_BYTES_DEFAULT, 4 * Constants.MB) + .build(); + + private static String sAlluxioRoot; + private static boolean sFuseInstalled; + private static FileSystem sFileSystem; + private static AlluxioFuseFileSystem sFuseFileSystem; + private static Thread sFuseThread; + private static String sMountPoint; + + @BeforeClass + public static void beforeClass() throws Exception { + // This test only runs when fuse is installed + sFuseInstalled = AlluxioFuseUtils.isFuseInstalled(); + Assume.assumeTrue(sFuseInstalled); + + sFileSystem = sLocalAlluxioClusterResource.get().getClient(); + + sMountPoint = AlluxioTestDirectory + .createTemporaryDirectory("FuseMountPoint").getAbsolutePath(); + sAlluxioRoot = "/"; + + AlluxioFuseOptions options = new AlluxioFuseOptions(sMountPoint, + sAlluxioRoot, false, new ArrayList<>()); + sFuseFileSystem = new AlluxioFuseFileSystem(sFileSystem, options); + sFuseThread = new Thread(() -> sFuseFileSystem.mount(Paths.get(sMountPoint), + true, false, new String[]{"-odirect_io"})); + sFuseThread.start(); + + if (!waitForFuseMounted()) { + // Fuse may not be mounted within timeout and we need to umount it + umountFuse(); + Assume.assumeTrue(false); + } + } + + @AfterClass + public static void afterClass() throws Exception { + if (sFuseInstalled && fuseMounted()) { + umountFuse(); + } + } + + @After + public void after() throws Exception { + for (URIStatus status : sFileSystem.listStatus(new AlluxioURI(sAlluxioRoot))) { + DeleteOptions options = DeleteOptions.defaults().setRecursive(true); + sFileSystem.delete(new AlluxioURI(status.getPath()), options); + } + } + + @Test + public void cat() throws Exception { + String testFile = "/catTestFile"; + String content = "Alluxio Cat Test File Content"; + + try (FileOutStream os = sFileSystem.createFile(new AlluxioURI(testFile))) { + os.write(content.getBytes()); + } + + String result = ShellUtils.execCommand("cat", sMountPoint + testFile); + Assert.assertEquals(content + "\n", result); + } + + @Test + public void chgrp() throws Exception { + String testFile = "/chgrpTestFile"; + String userName = System.getProperty("user.name"); + String groupName = AlluxioFuseUtils.getGroupName(userName); + FileSystemTestUtils.createByteFile(sFileSystem, testFile, WriteType.MUST_CACHE, 10); + ShellUtils.execCommand("chgrp", groupName, sMountPoint + testFile); + Assert.assertEquals(groupName, sFileSystem.getStatus(new AlluxioURI(testFile)).getGroup()); + } + + @Test + public void chmod() throws Exception { + String testFile = "/chmodTestFile"; + FileSystemTestUtils.createByteFile(sFileSystem, testFile, WriteType.MUST_CACHE, 10); + ShellUtils.execCommand("chmod", "777", sMountPoint + testFile); + Assert.assertEquals((short) 0777, sFileSystem.getStatus(new AlluxioURI(testFile)).getMode()); + } + + @Test + public void chown() throws Exception { + String testFile = "/chownTestFile"; + FileSystemTestUtils.createByteFile(sFileSystem, testFile, WriteType.MUST_CACHE, 10); + + String userName = System.getProperty("user.name"); + String groupName = AlluxioFuseUtils.getGroupName(userName); + ShellUtils.execCommand("chown", userName + ":" + groupName, sMountPoint + testFile); + Assert.assertEquals(userName, sFileSystem.getStatus(new AlluxioURI(testFile)).getOwner()); + Assert.assertEquals(groupName, sFileSystem.getStatus(new AlluxioURI(testFile)).getGroup()); + } + + @Test + public void cp() throws Exception { + String testFile = "/cpTestFile"; + String content = "Alluxio Cp Test File Content"; + File localFile = generateFileContent("/TestFileOnLocalPath", content.getBytes()); + + ShellUtils.execCommand("cp", localFile.getPath(), sMountPoint + testFile); + Assert.assertTrue(sFileSystem.exists(new AlluxioURI(testFile))); + + // Fuse release() is async + // Cp again to make sure the first cp is completed + String testFolder = "/cpTestFolder"; + ShellUtils.execCommand("mkdir", sMountPoint + testFolder); + ShellUtils.execCommand("cp", sMountPoint + testFile, sMountPoint + testFolder + testFile); + Assert.assertTrue(sFileSystem.exists(new AlluxioURI(testFolder + testFile))); + + byte[] read = new byte[content.length()]; + try (FileInStream is = sFileSystem.openFile(new AlluxioURI(testFile), + OpenFileOptions.defaults().setReadType(ReadType.NO_CACHE))) { + is.read(read); + } + Assert.assertEquals(content, new String(read, "UTF8")); + } + + @Test + public void ddAndRm() throws Exception { + String testFile = "/ddTestFile"; + ShellUtils.execCommand("dd", "if=/dev/zero", + "of=" + sMountPoint + testFile, "count=10", "bs=" + 4 * Constants.MB); + + // Fuse release() is async + // Open the file to make sure dd is completed + ShellUtils.execCommand("head", "-c", "10", sMountPoint + testFile); + + Assert.assertTrue(sFileSystem.exists(new AlluxioURI(testFile))); + Assert.assertEquals(40 * Constants.MB, + sFileSystem.getStatus(new AlluxioURI(testFile)).getLength()); + + ShellUtils.execCommand("rm", sMountPoint + testFile); + Assert.assertFalse(sFileSystem.exists(new AlluxioURI(testFile))); + } + + @Test + public void head() throws Exception { + String testFile = "/headTestFile"; + String content = "Alluxio Head Test File Content"; + try (FileOutStream os = sFileSystem.createFile(new AlluxioURI(testFile))) { + os.write(content.getBytes()); + } + String result = ShellUtils.execCommand("head", "-c", "17", sMountPoint + testFile); + Assert.assertEquals("Alluxio Head Test\n", result); + } + + @Test + public void mkdirAndMv() throws Exception { + String testFile = "/mvTestFile"; + String testFolder = "/mkdirTestFolder"; + FileSystemTestUtils.createByteFile(sFileSystem, testFile, WriteType.MUST_CACHE, 10); + ShellUtils.execCommand("mkdir", sMountPoint + testFolder); + ShellUtils.execCommand("mv", sMountPoint + testFile, + sMountPoint + testFolder + testFile); + Assert.assertFalse(sFileSystem.exists(new AlluxioURI(testFile))); + Assert.assertTrue(sFileSystem.exists(new AlluxioURI(testFolder + testFile))); + } + + @Test + public void tail() throws Exception { + String testFile = "/tailTestFile"; + String content = "Alluxio Tail Test File Content"; + try (FileOutStream os = sFileSystem.createFile(new AlluxioURI(testFile))) { + os.write(content.getBytes()); + } + String result = ShellUtils.execCommand("tail", "-c", "17", sMountPoint + testFile); + Assert.assertEquals("Test File Content\n", result); + } + + @Test + public void touchAndLs() throws Exception { + FileSystemTestUtils.createByteFile(sFileSystem, "/lsTestFile", WriteType.MUST_CACHE, 10); + String touchTestFile = "/touchTestFile"; + ShellUtils.execCommand("touch", sMountPoint + touchTestFile); + + String lsResult = ShellUtils.execCommand("ls", sMountPoint); + Assert.assertTrue(lsResult.contains("lsTestFile")); + Assert.assertTrue(lsResult.contains("touchTestFile")); + Assert.assertTrue(sFileSystem.exists(new AlluxioURI(touchTestFile))); + } + + /** + * Umounts the Alluxio-Fuse. + */ + private static void umountFuse() throws InterruptedException { + sFuseFileSystem.umount(); + sFuseThread.interrupt(); + sFuseThread.join(); + } + + /** + * Creates file by given path and writes content to file. + * + * @param path the file path + * @param toWrite the file content + * @return the created file instance + * @throws FileNotFoundException if file not found + */ + private File generateFileContent(String path, byte[] toWrite) throws IOException { + File testFile = new File(sLocalAlluxioClusterResource.get().getAlluxioHome() + path); + testFile.createNewFile(); + FileOutputStream fos = new FileOutputStream(testFile); + fos.write(toWrite); + fos.close(); + return testFile; + } + + /** + * Checks whether Alluxio-Fuse is mounted. + * + * @return true if fuse is mounted, false otherwise + */ + private static boolean fuseMounted() throws IOException { + String result = ShellUtils.execCommand("mount"); + return result.contains(sMountPoint); + } + + /** + * Waits for the Alluxio-Fuse to be mounted. + * + * @return true if Alluxio-Fuse mounted successfully in the given timeout, false otherwise + */ + private static boolean waitForFuseMounted() throws IOException { + if (OSUtils.isLinux() || OSUtils.isMacOS()) { + try { + CommonUtils.waitFor("Alluxio-Fuse mounted on local filesystem", () -> { + try { + return fuseMounted(); + } catch (IOException e) { + return false; + } + }, WaitForOptions.defaults().setTimeoutMs(WAIT_TIMEOUT_MS)); + return true; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return false; + } catch (TimeoutException te) { + return false; + } + } + return false; + } +}