HBASE-8372 Provide mutability to CompoundConfiguration

This patch also addresses:
HBASE-8347 TestSecureLoadInc* tests hang repeatedly getting UnsupportedOperationException in hadoop2 profile


git-svn-id: https://svn.apache.org/repos/asf/hbase/trunk@1471118 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Jonathan Hsieh 2013-04-23 19:52:09 +00:00
parent 93477aa6df
commit 136bc6995b
2 changed files with 182 additions and 252 deletions

View File

@ -19,12 +19,10 @@
*/ */
package org.apache.hadoop.hbase; package org.apache.hadoop.hbase;
import java.io.DataInput;
import java.io.DataOutput; import java.io.DataOutput;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -35,7 +33,6 @@ import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable; import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.util.StringUtils;
/** /**
* Do a shallow merge of multiple KV configuration pools. This is a very useful * Do a shallow merge of multiple KV configuration pools. This is a very useful
@ -51,9 +48,16 @@ import org.apache.hadoop.util.StringUtils;
* configuration objects and have changes reflected everywhere. In contrast to a * configuration objects and have changes reflected everywhere. In contrast to a
* deep merge, that requires you to explicitly know all applicable copies to * deep merge, that requires you to explicitly know all applicable copies to
* propagate changes. * propagate changes.
*
* WARNING: The values set in the CompoundConfiguration are do not handle Property variable
* substitution. However, if they are set in the underlying configuration substitutions are
* done.
*/ */
@InterfaceAudience.Private @InterfaceAudience.Private
public class CompoundConfiguration extends Configuration { public class CompoundConfiguration extends Configuration {
private Configuration mutableConf = null;
/** /**
* Default Constructor. Initializes empty configuration * Default Constructor. Initializes empty configuration
*/ */
@ -72,6 +76,60 @@ public class CompoundConfiguration extends Configuration {
protected List<ImmutableConfigMap> configs protected List<ImmutableConfigMap> configs
= new ArrayList<ImmutableConfigMap>(); = new ArrayList<ImmutableConfigMap>();
static class ImmutableConfWrapper implements ImmutableConfigMap {
Configuration c;
ImmutableConfWrapper(Configuration conf) {
c = conf;
}
@Override
public Iterator<Map.Entry<String,String>> iterator() {
return c.iterator();
}
@Override
public String get(String key) {
return c.get(key);
}
@Override
public String getRaw(String key) {
return c.getRaw(key);
}
@Override
public Class<?> getClassByName(String name)
throws ClassNotFoundException {
return c.getClassByName(name);
}
@Override
public int size() {
return c.size();
}
@Override
public String toString() {
return c.toString();
}
}
/**
* If set has been called, it will create a mutableConf. This converts the mutableConf to an
* immutable one and resets it to allow a new mutable conf. This is used when a new map or
* conf is added to the compound configuration to preserve proper override semantics.
*/
void freezeMutableConf() {
if (mutableConf == null) {
// do nothing if there is no current mutableConf
return;
}
this.configs.add(0, new ImmutableConfWrapper(mutableConf));
mutableConf = null;
}
/** /**
* Add Hadoop Configuration object to config list. * Add Hadoop Configuration object to config list.
* The added configuration overrides the previous ones if there are name collisions. * The added configuration overrides the previous ones if there are name collisions.
@ -79,45 +137,14 @@ public class CompoundConfiguration extends Configuration {
* @return this, for builder pattern * @return this, for builder pattern
*/ */
public CompoundConfiguration add(final Configuration conf) { public CompoundConfiguration add(final Configuration conf) {
freezeMutableConf();
if (conf instanceof CompoundConfiguration) { if (conf instanceof CompoundConfiguration) {
this.configs.addAll(0, ((CompoundConfiguration) conf).configs); this.configs.addAll(0, ((CompoundConfiguration) conf).configs);
return this; return this;
} }
// put new config at the front of the list (top priority) // put new config at the front of the list (top priority)
this.configs.add(0, new ImmutableConfigMap() { this.configs.add(0, new ImmutableConfWrapper(conf));
Configuration c = conf;
@Override
public Iterator<Map.Entry<String,String>> iterator() {
return c.iterator();
}
@Override
public String get(String key) {
return c.get(key);
}
@Override
public String getRaw(String key) {
return c.getRaw(key);
}
@Override
public Class<?> getClassByName(String name)
throws ClassNotFoundException {
return c.getClassByName(name);
}
@Override
public int size() {
return c.size();
}
@Override
public String toString() {
return c.toString();
}
});
return this; return this;
} }
@ -133,6 +160,8 @@ public class CompoundConfiguration extends Configuration {
*/ */
public CompoundConfiguration addWritableMap( public CompoundConfiguration addWritableMap(
final Map<ImmutableBytesWritable, ImmutableBytesWritable> map) { final Map<ImmutableBytesWritable, ImmutableBytesWritable> map) {
freezeMutableConf();
// put new map at the front of the list (top priority) // put new map at the front of the list (top priority)
this.configs.add(0, new ImmutableConfigMap() { this.configs.add(0, new ImmutableConfigMap() {
Map<ImmutableBytesWritable, ImmutableBytesWritable> m = map; Map<ImmutableBytesWritable, ImmutableBytesWritable> m = map;
@ -192,6 +221,8 @@ public class CompoundConfiguration extends Configuration {
* @return this, for builder pattern * @return this, for builder pattern
*/ */
public CompoundConfiguration addStringMap(final Map<String, String> map) { public CompoundConfiguration addStringMap(final Map<String, String> map) {
freezeMutableConf();
// put new map at the front of the list (top priority) // put new map at the front of the list (top priority)
this.configs.add(0, new ImmutableConfigMap() { this.configs.add(0, new ImmutableConfigMap() {
Map<String, String> m = map; Map<String, String> m = map;
@ -242,6 +273,13 @@ public class CompoundConfiguration extends Configuration {
@Override @Override
public String get(String key) { public String get(String key) {
if (mutableConf != null) {
String value = mutableConf.get(key);
if (value != null) {
return value;
}
}
for (ImmutableConfigMap m : this.configs) { for (ImmutableConfigMap m : this.configs) {
String value = m.get(key); String value = m.get(key);
if (value != null) { if (value != null) {
@ -253,6 +291,13 @@ public class CompoundConfiguration extends Configuration {
@Override @Override
public String getRaw(String key) { public String getRaw(String key) {
if (mutableConf != null) {
String value = mutableConf.getRaw(key);
if (value != null) {
return value;
}
}
for (ImmutableConfigMap m : this.configs) { for (ImmutableConfigMap m : this.configs) {
String value = m.getRaw(key); String value = m.getRaw(key);
if (value != null) { if (value != null) {
@ -264,6 +309,13 @@ public class CompoundConfiguration extends Configuration {
@Override @Override
public Class<?> getClassByName(String name) throws ClassNotFoundException { public Class<?> getClassByName(String name) throws ClassNotFoundException {
if (mutableConf != null) {
Class<?> value = mutableConf.getClassByName(name);
if (value != null) {
return value;
}
}
for (ImmutableConfigMap m : this.configs) { for (ImmutableConfigMap m : this.configs) {
Class<?> value = m.getClassByName(name); Class<?> value = m.getClassByName(name);
if (value != null) { if (value != null) {
@ -273,194 +325,51 @@ public class CompoundConfiguration extends Configuration {
throw new ClassNotFoundException(); throw new ClassNotFoundException();
} }
// TODO: This method overestimates the number of configuration settings -- if a value is masked
// by an overriding config or map, it will be counted multiple times.
@Override @Override
public int size() { public int size() {
int ret = 0; int ret = 0;
if (mutableConf != null) {
ret += mutableConf.size();
}
for (ImmutableConfigMap m : this.configs) { for (ImmutableConfigMap m : this.configs) {
ret += m.size(); ret += m.size();
} }
return ret; return ret;
} }
/*************************************************************************** /**
* You should just ignore everything below this line unless there's a bug in * Get the value of the <code>name</code>. If the key is deprecated,
* Configuration.java... * it returns the value of the first key which replaces the deprecated key
* * and is not null.
* Below get APIs are directly copied from Configuration.java Oh, how I wish * If no such property exists,
* this wasn't so! A tragically-sad example of why you use interfaces instead * then <code>defaultValue</code> is returned.
* of inheritance.
*
* Why the duplication? We basically need to override Configuration.getProps
* or we'd need protected access to Configuration.properties so we can modify
* that pointer. There are a bunch of functions in the base Configuration that
* call getProps() and we need to use our derived version instead of the base
* version. We need to make a generic implementation that works across all
* HDFS versions. We should modify Configuration.properties in HDFS 1.0 to be
* protected, but we still need to have this code until that patch makes it to
* all the HDFS versions we support.
***************************************************************************/
* The CompooundConfiguration does not do property substitution. To do so we need
* Configuration.getProps to be protected or package visible. Though in hadoop2 it is
* protected, in hadoop1 the method is private and not accessible.
*
* All of the get* methods call this overridden get method.
*
* @param name property name.
* @param defaultValue default value.
* @return property value, or <code>defaultValue</code> if the property
* doesn't exist.
**/
@Override @Override
public String get(String name, String defaultValue) { public String get(String name, String defaultValue) {
String ret = get(name); String ret = get(name);
return ret == null ? defaultValue : ret; return ret == null ? defaultValue : ret;
} }
@Override
public int getInt(String name, int defaultValue) {
String valueString = get(name);
if (valueString == null)
return defaultValue;
try {
String hexString = getHexDigits(valueString);
if (hexString != null) {
return Integer.parseInt(hexString, 16);
}
return Integer.parseInt(valueString);
} catch (NumberFormatException e) {
return defaultValue;
}
}
@Override
public long getLong(String name, long defaultValue) {
String valueString = get(name);
if (valueString == null)
return defaultValue;
try {
String hexString = getHexDigits(valueString);
if (hexString != null) {
return Long.parseLong(hexString, 16);
}
return Long.parseLong(valueString);
} catch (NumberFormatException e) {
return defaultValue;
}
}
protected String getHexDigits(String value) {
boolean negative = false;
String str = value;
String hexString = null;
if (value.startsWith("-")) {
negative = true;
str = value.substring(1);
}
if (str.startsWith("0x") || str.startsWith("0X")) {
hexString = str.substring(2);
if (negative) {
hexString = "-" + hexString;
}
return hexString;
}
return null;
}
@Override
public float getFloat(String name, float defaultValue) {
String valueString = get(name);
if (valueString == null)
return defaultValue;
try {
return Float.parseFloat(valueString);
} catch (NumberFormatException e) {
return defaultValue;
}
}
@Override
public boolean getBoolean(String name, boolean defaultValue) {
String valueString = get(name);
if ("true".equals(valueString))
return true;
else if ("false".equals(valueString))
return false;
else return defaultValue;
}
@Override
public IntegerRanges getRange(String name, String defaultValue) {
return new IntegerRanges(get(name, defaultValue));
}
@Override
public Collection<String> getStringCollection(String name) {
String valueString = get(name);
return StringUtils.getStringCollection(valueString);
}
@Override
public String[] getStrings(String name) {
String valueString = get(name);
return StringUtils.getStrings(valueString);
}
@Override
public String[] getStrings(String name, String... defaultValue) {
String valueString = get(name);
if (valueString == null) {
return defaultValue;
} else {
return StringUtils.getStrings(valueString);
}
}
@Override
public Class<?>[] getClasses(String name, Class<?>... defaultValue) {
String[] classnames = getStrings(name);
if (classnames == null)
return defaultValue;
try {
Class<?>[] classes = new Class<?>[classnames.length];
for (int i = 0; i < classnames.length; i++) {
classes[i] = getClassByName(classnames[i]);
}
return classes;
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
@Override
public Class<?> getClass(String name, Class<?> defaultValue) {
String valueString = get(name);
if (valueString == null)
return defaultValue;
try {
return getClassByName(valueString);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
@Override
public <U> Class<? extends U> getClass(String name,
Class<? extends U> defaultValue, Class<U> xface) {
try {
Class<?> theClass = getClass(name, defaultValue);
if (theClass != null && !xface.isAssignableFrom(theClass))
throw new RuntimeException(theClass + " not " + xface.getName());
else if (theClass != null)
return theClass.asSubclass(xface);
else
return null;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/*******************************************************************
* This class is immutable. Quickly abort any attempts to alter it *
*******************************************************************/
@Override
public void clear() {
throw new UnsupportedOperationException("Immutable Configuration");
}
@Override @Override
public Iterator<Map.Entry<String, String>> iterator() { public Iterator<Map.Entry<String, String>> iterator() {
Map<String, String> ret = new HashMap<String, String>(); Map<String, String> ret = new HashMap<String, String>();
// add in reverse order so that oldest get overridden.
if (!configs.isEmpty()) { if (!configs.isEmpty()) {
for (int i = configs.size() - 1; i >= 0; i--) { for (int i = configs.size() - 1; i >= 0; i--) {
ImmutableConfigMap map = configs.get(i); ImmutableConfigMap map = configs.get(i);
@ -471,52 +380,35 @@ public class CompoundConfiguration extends Configuration {
} }
} }
} }
// add mutations to this CompoundConfiguration last.
if (mutableConf != null) {
Iterator<Map.Entry<String, String>> miter = mutableConf.iterator();
while (miter.hasNext()) {
Map.Entry<String, String> entry = miter.next();
ret.put(entry.getKey(), entry.getValue());
}
}
return UnmodifiableIterator.decorate(ret.entrySet().iterator()); return UnmodifiableIterator.decorate(ret.entrySet().iterator());
} }
@Override @Override
public void set(String name, String value) { public void set(String name, String value) {
throw new UnsupportedOperationException("Immutable Configuration"); if (mutableConf == null) {
} // not thread safe
@Override mutableConf = new Configuration(false); // an empty configuration
public void setIfUnset(String name, String value) { }
throw new UnsupportedOperationException("Immutable Configuration"); mutableConf.set(name, value);
}
@Override
public void setInt(String name, int value) {
throw new UnsupportedOperationException("Immutable Configuration");
}
@Override
public void setLong(String name, long value) {
throw new UnsupportedOperationException("Immutable Configuration");
}
@Override
public void setFloat(String name, float value) {
throw new UnsupportedOperationException("Immutable Configuration");
}
@Override
public void setBoolean(String name, boolean value) {
throw new UnsupportedOperationException("Immutable Configuration");
}
@Override
public void setBooleanIfUnset(String name, boolean value) {
throw new UnsupportedOperationException("Immutable Configuration");
}
@Override
public void setStrings(String name, String... values) {
throw new UnsupportedOperationException("Immutable Configuration");
}
@Override
public void setClass(String name, Class<?> theClass, Class<?> xface) {
throw new UnsupportedOperationException("Immutable Configuration");
}
@Override
public void setClassLoader(ClassLoader classLoader) {
throw new UnsupportedOperationException("Immutable Configuration");
} }
/***********************************************************************************************
* These methods are unsupported, and no code using CompoundConfiguration depend upon them.
* Quickly abort upon any attempts to use them.
**********************************************************************************************/
@Override @Override
public void readFields(DataInput in) throws IOException { public void clear() {
throw new UnsupportedOperationException("Immutable Configuration"); throw new UnsupportedOperationException("Immutable Configuration");
} }

View File

@ -20,18 +20,15 @@
package org.apache.hadoop.hbase; package org.apache.hadoop.hbase;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.Map; import java.util.Map;
import junit.framework.TestCase;
import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable; import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.SmallTests;
import org.junit.experimental.categories.Category;
import org.junit.Test; import org.junit.Test;
import org.junit.experimental.categories.Category;
import junit.framework.TestCase;
@Category(SmallTests.class) @Category(SmallTests.class)
public class TestCompoundConfiguration extends TestCase { public class TestCompoundConfiguration extends TestCase {
@ -50,7 +47,7 @@ public class TestCompoundConfiguration extends TestCase {
@Test @Test
public void testBasicFunctionality() throws ClassNotFoundException { public void testBasicFunctionality() throws ClassNotFoundException {
CompoundConfiguration compoundConf = new CompoundConfiguration() CompoundConfiguration compoundConf = new CompoundConfiguration()
.add(baseConf); .add(baseConf);
assertEquals("1", compoundConf.get("A")); assertEquals("1", compoundConf.get("A"));
assertEquals(2, compoundConf.getInt("B", 0)); assertEquals(2, compoundConf.getInt("B", 0));
assertEquals(3, compoundConf.getInt("C", 0)); assertEquals(3, compoundConf.getInt("C", 0));
@ -66,6 +63,29 @@ public class TestCompoundConfiguration extends TestCase {
} }
} }
@Test
public void testPut() {
CompoundConfiguration compoundConf = new CompoundConfiguration()
.add(baseConf);
assertEquals("1", compoundConf.get("A"));
assertEquals(2, compoundConf.getInt("B", 0));
assertEquals(3, compoundConf.getInt("C", 0));
assertEquals(0, compoundConf.getInt("D", 0));
compoundConf.set("A", "1337");
compoundConf.set("string", "stringvalue");
assertEquals(1337, compoundConf.getInt("A", 0));
assertEquals("stringvalue", compoundConf.get("string"));
// we didn't modify the base conf
assertEquals("1", baseConf.get("A"));
assertNull(baseConf.get("string"));
// adding to the base shows up in the compound
baseConf.set("setInParent", "fromParent");
assertEquals("fromParent", compoundConf.get("setInParent"));
}
@Test @Test
public void testWithConfig() { public void testWithConfig() {
Configuration conf = new Configuration(); Configuration conf = new Configuration();
@ -128,6 +148,15 @@ public class TestCompoundConfiguration extends TestCase {
} }
// verify that entries from ImmutableConfigMap's are merged in the iterator's view // verify that entries from ImmutableConfigMap's are merged in the iterator's view
assertEquals(baseConfSize + 2, cnt); assertEquals(baseConfSize + 2, cnt);
// Verify that adding map after compound configuration is modified overrides properly
CompoundConfiguration conf2 = new CompoundConfiguration();
conf2.set("X", "modification");
conf2.set("D", "not4");
assertEquals("modification", conf2.get("X"));
assertEquals("not4", conf2.get("D"));
conf2.addWritableMap(map);
assertEquals("4", conf2.get("D")); // map overrides
} }
@Test @Test
@ -156,6 +185,15 @@ public class TestCompoundConfiguration extends TestCase {
} }
// verify that entries from ImmutableConfigMap's are merged in the iterator's view // verify that entries from ImmutableConfigMap's are merged in the iterator's view
assertEquals(4, cnt); assertEquals(4, cnt);
// Verify that adding map after compound configuration is modified overrides properly
CompoundConfiguration conf2 = new CompoundConfiguration();
conf2.set("X", "modification");
conf2.set("D", "not4");
assertEquals("modification", conf2.get("X"));
assertEquals("not4", conf2.get("D"));
conf2.addStringMap(map);
assertEquals("4", conf2.get("D")); // map overrides
} }
@Test @Test