diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/Configuration.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/Configuration.java index 42aa8f6b31e..ce712a80d7a 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/Configuration.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/Configuration.java @@ -299,18 +299,25 @@ public class Configuration implements Iterable>, this.customMessage = customMessage; } + private final String getWarningMessage(String key) { + return getWarningMessage(key, null); + } + /** * Method to provide the warning message. It gives the custom message if * non-null, and default message otherwise. * @param key the associated deprecated key. + * @param source the property source. * @return message that is to be logged when a deprecated key is used. */ - private final String getWarningMessage(String key) { + private String getWarningMessage(String key, String source) { String warningMessage; if(customMessage == null) { StringBuilder message = new StringBuilder(key); - String deprecatedKeySuffix = " is deprecated. Instead, use "; - message.append(deprecatedKeySuffix); + if (source != null) { + message.append(" in " + source); + } + message.append(" is deprecated. Instead, use "); for (int i = 0; i < newKeys.length; i++) { message.append(newKeys[i]); if(i != newKeys.length-1) { @@ -586,6 +593,14 @@ public class Configuration implements Iterable>, return deprecationContext.get().getDeprecatedKeyMap().containsKey(key); } + private static String getDeprecatedKey(String key) { + return deprecationContext.get().getReverseDeprecatedKeyMap().get(key); + } + + private static DeprecatedKeyInfo getDeprecatedKeyInfo(String key) { + return deprecationContext.get().getDeprecatedKeyMap().get(key); + } + /** * Sets all deprecated properties that are not currently set but have a * corresponding new property that is set. Useful for iterating the @@ -1212,6 +1227,13 @@ public class Configuration implements Iterable>, LOG_DEPRECATION.info(message); } + void logDeprecationOnce(String name, String source) { + DeprecatedKeyInfo keyInfo = getDeprecatedKeyInfo(name); + if (keyInfo != null && !keyInfo.getAndSetAccessed()) { + LOG_DEPRECATION.info(keyInfo.getWarningMessage(name, source)); + } + } + /** * Unset a previously set property. */ @@ -1995,6 +2017,47 @@ public class Configuration implements Iterable>, return pass; } + /** + * Get the credential entry by name from a credential provider. + * + * Handle key deprecation. + * + * @param provider a credential provider + * @param name alias of the credential + * @return the credential entry or null if not found + */ + private CredentialEntry getCredentialEntry(CredentialProvider provider, + String name) throws IOException { + CredentialEntry entry = provider.getCredentialEntry(name); + if (entry != null) { + return entry; + } + + // The old name is stored in the credential provider. + String oldName = getDeprecatedKey(name); + if (oldName != null) { + entry = provider.getCredentialEntry(oldName); + if (entry != null) { + logDeprecationOnce(oldName, provider.toString()); + return entry; + } + } + + // The name is deprecated. + DeprecatedKeyInfo keyInfo = getDeprecatedKeyInfo(name); + if (keyInfo != null && keyInfo.newKeys != null) { + for (String newName : keyInfo.newKeys) { + entry = provider.getCredentialEntry(newName); + if (entry != null) { + logDeprecationOnce(name, null); + return entry; + } + } + } + + return null; + } + /** * Try and resolve the provided element name as a credential provider * alias. @@ -2012,7 +2075,7 @@ public class Configuration implements Iterable>, if (providers != null) { for (CredentialProvider provider : providers) { try { - CredentialEntry entry = provider.getCredentialEntry(name); + CredentialEntry entry = getCredentialEntry(provider, name); if (entry != null) { pass = entry.getCredential(); break; diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/conf/TestConfiguration.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/conf/TestConfiguration.java index 66f717d957f..1e0f78326e8 100644 --- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/conf/TestConfiguration.java +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/conf/TestConfiguration.java @@ -49,13 +49,19 @@ import static org.junit.Assert.assertArrayEquals; import org.apache.commons.lang.StringUtils; import org.apache.hadoop.conf.Configuration.IntegerRanges; import org.apache.hadoop.fs.CommonConfigurationKeysPublic; +import org.apache.hadoop.fs.FileUtil; import org.apache.hadoop.fs.Path; import org.apache.hadoop.io.IOUtils; import org.apache.hadoop.net.NetUtils; +import org.apache.hadoop.security.alias.CredentialProvider; +import org.apache.hadoop.security.alias.CredentialProviderFactory; +import org.apache.hadoop.security.alias.LocalJavaKeyStoreProvider; import org.apache.hadoop.test.GenericTestUtils; import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.Logger; import org.apache.log4j.spi.LoggingEvent; +import org.hamcrest.CoreMatchers; +import org.junit.Assert; import org.mockito.Mockito; import static org.apache.hadoop.util.PlatformName.IBM_JAVA; @@ -1903,6 +1909,60 @@ public class TestConfiguration extends TestCase { + classes.length, 0, classes.length); } + public void testGetPasswordDeprecatedKeyStored() throws Exception { + final String oldKey = "test.password.old.key"; + final String newKey = "test.password.new.key"; + final String password = "MyPasswordForDeprecatedKey"; + + final File tmpDir = GenericTestUtils.getRandomizedTestDir(); + tmpDir.mkdirs(); + final String ourUrl = new URI(LocalJavaKeyStoreProvider.SCHEME_NAME, + "file", new File(tmpDir, "test.jks").toString(), null).toString(); + + conf = new Configuration(false); + conf.set(CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH, ourUrl); + CredentialProvider provider = + CredentialProviderFactory.getProviders(conf).get(0); + provider.createCredentialEntry(oldKey, password.toCharArray()); + provider.flush(); + + Configuration.addDeprecation(oldKey, newKey); + + Assert.assertThat(conf.getPassword(newKey), + CoreMatchers.is(password.toCharArray())); + Assert.assertThat(conf.getPassword(oldKey), + CoreMatchers.is(password.toCharArray())); + + FileUtil.fullyDelete(tmpDir); + } + + public void testGetPasswordByDeprecatedKey() throws Exception { + final String oldKey = "test.password.old.key"; + final String newKey = "test.password.new.key"; + final String password = "MyPasswordForDeprecatedKey"; + + final File tmpDir = GenericTestUtils.getRandomizedTestDir(); + tmpDir.mkdirs(); + final String ourUrl = new URI(LocalJavaKeyStoreProvider.SCHEME_NAME, + "file", new File(tmpDir, "test.jks").toString(), null).toString(); + + conf = new Configuration(false); + conf.set(CredentialProviderFactory.CREDENTIAL_PROVIDER_PATH, ourUrl); + CredentialProvider provider = + CredentialProviderFactory.getProviders(conf).get(0); + provider.createCredentialEntry(newKey, password.toCharArray()); + provider.flush(); + + Configuration.addDeprecation(oldKey, newKey); + + Assert.assertThat(conf.getPassword(newKey), + CoreMatchers.is(password.toCharArray())); + Assert.assertThat(conf.getPassword(oldKey), + CoreMatchers.is(password.toCharArray())); + + FileUtil.fullyDelete(tmpDir); + } + public static void main(String[] argv) throws Exception { junit.textui.TestRunner.main(new String[]{ TestConfiguration.class.getName()