diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/HConstants.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/HConstants.java index b5a5d62b244..fa1a772c0f4 100644 --- a/hbase-common/src/main/java/org/apache/hadoop/hbase/HConstants.java +++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/HConstants.java @@ -1077,7 +1077,13 @@ public final class HConstants { * Valid values are: HOT, COLD, WARM, ALL_SSD, ONE_SSD, LAZY_PERSIST * See http://hadoop.apache.org/docs/r2.7.3/hadoop-project-dist/hadoop-hdfs/ArchivalStorage.html*/ public static final String WAL_STORAGE_POLICY = "hbase.wal.storage.policy"; - public static final String DEFAULT_WAL_STORAGE_POLICY = "HOT"; + /** + * "NONE" is not a valid storage policy and means we defer the policy to HDFS. @see + * HBASE-20691 + */ + public static final String DEFER_TO_HDFS_STORAGE_POLICY = "NONE"; + /** By default we defer the WAL storage policy to HDFS */ + public static final String DEFAULT_WAL_STORAGE_POLICY = DEFER_TO_HDFS_STORAGE_POLICY; /** Region in Transition metrics threshold time */ public static final String METRICS_RIT_STUCK_WARNING_THRESHOLD = diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/util/CommonFSUtils.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/util/CommonFSUtils.java index 5b46de9733d..76a3601e505 100644 --- a/hbase-common/src/main/java/org/apache/hadoop/hbase/util/CommonFSUtils.java +++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/util/CommonFSUtils.java @@ -454,36 +454,6 @@ public abstract class CommonFSUtils { new Path(namespace))); } - /** - * Sets storage policy for given path according to config setting. - * If the passed path is a directory, we'll set the storage policy for all files - * created in the future in said directory. Note that this change in storage - * policy takes place at the FileSystem level; it will persist beyond this RS's lifecycle. - * If we're running on a FileSystem implementation that doesn't support the given storage policy - * (or storage policies at all), then we'll issue a log message and continue. - * - * See http://hadoop.apache.org/docs/r2.6.0/hadoop-project-dist/hadoop-hdfs/ArchivalStorage.html - * - * @param fs We only do anything it implements a setStoragePolicy method - * @param conf used to look up storage policy with given key; not modified. - * @param path the Path whose storage policy is to be set - * @param policyKey Key to use pulling a policy from Configuration: - * e.g. HConstants.WAL_STORAGE_POLICY (hbase.wal.storage.policy). - * @param defaultPolicy if the configured policy is equal to this policy name, we will skip - * telling the FileSystem to set a storage policy. - */ - public static void setStoragePolicy(final FileSystem fs, final Configuration conf, - final Path path, final String policyKey, final String defaultPolicy) { - String storagePolicy = conf.get(policyKey, defaultPolicy).toUpperCase(Locale.ROOT); - if (storagePolicy.equals(defaultPolicy)) { - if (LOG.isTraceEnabled()) { - LOG.trace("default policy of " + defaultPolicy + " requested, exiting early."); - } - return; - } - setStoragePolicy(fs, path, storagePolicy); - } - // this mapping means that under a federated FileSystem implementation, we'll // only log the first failure from any of the underlying FileSystems at WARN and all others // will be at DEBUG. @@ -508,33 +478,63 @@ public abstract class CommonFSUtils { */ public static void setStoragePolicy(final FileSystem fs, final Path path, final String storagePolicy) { + try { + setStoragePolicy(fs, path, storagePolicy, false); + } catch (IOException e) { + // should never arrive here + LOG.warn("We have chosen not to throw exception but some unexpectedly thrown out", e); + } + } + + static void setStoragePolicy(final FileSystem fs, final Path path, final String storagePolicy, + boolean throwException) throws IOException { if (storagePolicy == null) { if (LOG.isTraceEnabled()) { LOG.trace("We were passed a null storagePolicy, exiting early."); } return; } - final String trimmedStoragePolicy = storagePolicy.trim(); + String trimmedStoragePolicy = storagePolicy.trim(); if (trimmedStoragePolicy.isEmpty()) { if (LOG.isTraceEnabled()) { LOG.trace("We were passed an empty storagePolicy, exiting early."); } return; + } else { + trimmedStoragePolicy = trimmedStoragePolicy.toUpperCase(Locale.ROOT); + } + if (trimmedStoragePolicy.equals(HConstants.DEFER_TO_HDFS_STORAGE_POLICY)) { + if (LOG.isTraceEnabled()) { + LOG.trace("We were passed the defer-to-hdfs policy {}, exiting early.", + trimmedStoragePolicy); + } + return; + } + try { + invokeSetStoragePolicy(fs, path, trimmedStoragePolicy); + } catch (IOException e) { + if (LOG.isTraceEnabled()) { + LOG.trace("Failed to invoke set storage policy API on FS", e); + } + if (throwException) { + throw e; + } } - invokeSetStoragePolicy(fs, path, trimmedStoragePolicy); } /* * All args have been checked and are good. Run the setStoragePolicy invocation. */ private static void invokeSetStoragePolicy(final FileSystem fs, final Path path, - final String storagePolicy) { + final String storagePolicy) throws IOException { Method m = null; + Exception toThrow = null; try { m = fs.getClass().getDeclaredMethod("setStoragePolicy", new Class[] { Path.class, String.class }); m.setAccessible(true); } catch (NoSuchMethodException e) { + toThrow = e; final String msg = "FileSystem doesn't support setStoragePolicy; HDFS-6584, HDFS-9345 " + "not available. This is normal and expected on earlier Hadoop versions."; if (!warningMap.containsKey(fs)) { @@ -545,6 +545,7 @@ public abstract class CommonFSUtils { } m = null; } catch (SecurityException e) { + toThrow = e; final String msg = "No access to setStoragePolicy on FileSystem from the SecurityManager; " + "HDFS-6584, HDFS-9345 not available. This is unusual and probably warrants an email " + "to the user@hbase mailing list. Please be sure to include a link to your configs, and " + @@ -565,6 +566,7 @@ public abstract class CommonFSUtils { LOG.debug("Set storagePolicy=" + storagePolicy + " for path=" + path); } } catch (Exception e) { + toThrow = e; // This swallows FNFE, should we be throwing it? seems more likely to indicate dev // misuse than a runtime problem with HDFS. if (!warningMap.containsKey(fs)) { @@ -603,6 +605,9 @@ public abstract class CommonFSUtils { } } } + if (toThrow != null) { + throw new IOException(toThrow); + } } /** diff --git a/hbase-procedure/src/main/java/org/apache/hadoop/hbase/procedure2/store/wal/WALProcedureStore.java b/hbase-procedure/src/main/java/org/apache/hadoop/hbase/procedure2/store/wal/WALProcedureStore.java index 977921f2866..64bc7125704 100644 --- a/hbase-procedure/src/main/java/org/apache/hadoop/hbase/procedure2/store/wal/WALProcedureStore.java +++ b/hbase-procedure/src/main/java/org/apache/hadoop/hbase/procedure2/store/wal/WALProcedureStore.java @@ -216,8 +216,9 @@ public class WALProcedureStore extends ProcedureStoreBase { } } // Now that it exists, set the log policy - CommonFSUtils.setStoragePolicy(fs, conf, walDir, HConstants.WAL_STORAGE_POLICY, - HConstants.DEFAULT_WAL_STORAGE_POLICY); + String storagePolicy = + conf.get(HConstants.WAL_STORAGE_POLICY, HConstants.DEFAULT_WAL_STORAGE_POLICY); + CommonFSUtils.setStoragePolicy(fs, walDir, storagePolicy); // Create archive dir up front. Rename won't work w/o it up on HDFS. if (this.walArchiveDir != null && !this.fs.exists(this.walArchiveDir)) { diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/wal/AbstractFSWAL.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/wal/AbstractFSWAL.java index 72ad8b8b53e..2b45a04341f 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/wal/AbstractFSWAL.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/wal/AbstractFSWAL.java @@ -363,8 +363,9 @@ public abstract class AbstractFSWAL implements WAL { } // Now that it exists, set the storage policy for the entire directory of wal files related to // this FSHLog instance - CommonFSUtils.setStoragePolicy(fs, conf, this.walDir, HConstants.WAL_STORAGE_POLICY, - HConstants.DEFAULT_WAL_STORAGE_POLICY); + String storagePolicy = + conf.get(HConstants.WAL_STORAGE_POLICY, HConstants.DEFAULT_WAL_STORAGE_POLICY); + CommonFSUtils.setStoragePolicy(fs, this.walDir, storagePolicy); this.walFileSuffix = (suffix == null) ? "" : URLEncoder.encode(suffix, "UTF8"); this.prefixPathStr = new Path(walDir, walFilePrefix + WAL_FILE_NAME_DELIMITER).toString(); diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestFSUtils.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestFSUtils.java index d5c920d50dc..c5863f7033a 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestFSUtils.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestFSUtils.java @@ -40,12 +40,15 @@ import org.apache.hadoop.hbase.HBaseTestingUtility; import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.HDFSBlocksDistribution; import org.apache.hadoop.hbase.exceptions.DeserializationException; +import org.apache.hadoop.hbase.fs.HFileSystem; import org.apache.hadoop.hbase.testclassification.MediumTests; import org.apache.hadoop.hbase.testclassification.MiscTests; import org.apache.hadoop.hdfs.DFSConfigKeys; import org.apache.hadoop.hdfs.DFSHedgedReadMetrics; import org.apache.hadoop.hdfs.DFSTestUtil; +import org.apache.hadoop.hdfs.DistributedFileSystem; import org.apache.hadoop.hdfs.MiniDFSCluster; +import org.junit.Assert; import org.junit.Before; import org.junit.ClassRule; import org.junit.Test; @@ -338,19 +341,54 @@ public class TestFSUtils { @Test public void testSetStoragePolicyDefault() throws Exception { + verifyNoHDFSApiInvocationForDefaultPolicy(); verifyFileInDirWithStoragePolicy(HConstants.DEFAULT_WAL_STORAGE_POLICY); } + /** + * Note: currently the default policy is set to defer to HDFS and this case is to verify the + * logic, will need to remove the check if the default policy is changed + */ + private void verifyNoHDFSApiInvocationForDefaultPolicy() { + FileSystem testFs = new AlwaysFailSetStoragePolicyFileSystem(); + // There should be no exception thrown when setting to default storage policy, which indicates + // the HDFS API hasn't been called + try { + FSUtils.setStoragePolicy(testFs, new Path("non-exist"), HConstants.DEFAULT_WAL_STORAGE_POLICY, + true); + } catch (IOException e) { + Assert.fail("Should have bypassed the FS API when setting default storage policy"); + } + // There should be exception thrown when given non-default storage policy, which indicates the + // HDFS API has been called + try { + FSUtils.setStoragePolicy(testFs, new Path("non-exist"), "HOT", true); + Assert.fail("Should have invoked the FS API but haven't"); + } catch (IOException e) { + // expected given an invalid path + } + } + + class AlwaysFailSetStoragePolicyFileSystem extends DistributedFileSystem { + @Override + public void setStoragePolicy(final Path src, final String policyName) + throws IOException { + throw new IOException("The setStoragePolicy method is invoked"); + } + } + /* might log a warning, but still work. (always warning on Hadoop < 2.6.0) */ @Test public void testSetStoragePolicyValidButMaybeNotPresent() throws Exception { verifyFileInDirWithStoragePolicy("ALL_SSD"); } + final String INVALID_STORAGE_POLICY = "1772"; + /* should log a warning, but still work. (different warning on Hadoop < 2.6.0) */ @Test public void testSetStoragePolicyInvalid() throws Exception { - verifyFileInDirWithStoragePolicy("1772"); + verifyFileInDirWithStoragePolicy(INVALID_STORAGE_POLICY); } // Here instead of TestCommonFSUtils because we need a minicluster @@ -365,12 +403,25 @@ public class TestFSUtils { Path testDir = htu.getDataTestDirOnTestFS("testArchiveFile"); fs.mkdirs(testDir); - FSUtils.setStoragePolicy(fs, conf, testDir, HConstants.WAL_STORAGE_POLICY, - HConstants.DEFAULT_WAL_STORAGE_POLICY); + String storagePolicy = + conf.get(HConstants.WAL_STORAGE_POLICY, HConstants.DEFAULT_WAL_STORAGE_POLICY); + FSUtils.setStoragePolicy(fs, testDir, storagePolicy); String file =htu.getRandomUUID().toString(); Path p = new Path(testDir, file); WriteDataToHDFS(fs, p, 4096); + HFileSystem hfs = new HFileSystem(fs); + String policySet = hfs.getStoragePolicyName(p); + LOG.debug("The storage policy of path " + p + " is " + policySet); + if (policy.equals(HConstants.DEFER_TO_HDFS_STORAGE_POLICY) + || policy.equals(INVALID_STORAGE_POLICY)) { + String hdfsDefaultPolicy = hfs.getStoragePolicyName(hfs.getHomeDirectory()); + LOG.debug("The default hdfs storage policy (indicated by home path: " + + hfs.getHomeDirectory() + ") is " + hdfsDefaultPolicy); + Assert.assertEquals(hdfsDefaultPolicy, policySet); + } else { + Assert.assertEquals(policy, policySet); + } // will assert existance before deleting. cleanupFile(fs, testDir); } finally {