HBASE-24770 Reimplement the Constraints API and revisit the IA annotations on related classes (#2140)

Signed-off-by: stack <stack@apache.org>
This commit is contained in:
Duo Zhang 2020-07-28 09:24:55 +08:00 committed by GitHub
parent 477debdc74
commit 7e6e7a7051
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 607 additions and 787 deletions

View File

@ -32,11 +32,11 @@ import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.apache.hadoop.hbase.Coprocessor;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.TableName;
@ -380,6 +380,10 @@ public class TableDescriptorBuilder {
return this;
}
public boolean hasCoprocessor(String classNameToMatch) {
return desc.hasCoprocessor(classNameToMatch);
}
public TableDescriptorBuilder setColumnFamily(final ColumnFamilyDescriptor family) {
desc.setColumnFamily(Objects.requireNonNull(family));
return this;
@ -411,6 +415,16 @@ public class TableDescriptorBuilder {
return this;
}
public TableDescriptorBuilder removeValue(BiPredicate<Bytes, Bytes> predicate) {
List<Bytes> toRemove =
desc.getValues().entrySet().stream().filter(e -> predicate.test(e.getKey(), e.getValue()))
.map(Map.Entry::getKey).collect(Collectors.toList());
for (Bytes key : toRemove) {
removeValue(key);
}
return this;
}
public TableDescriptorBuilder removeColumnFamily(final byte[] name) {
desc.removeColumnFamily(name);
return this;
@ -531,6 +545,10 @@ public class TableDescriptorBuilder {
return this;
}
public String getValue(String key) {
return desc.getValue(key);
}
/**
* Sets replication scope all & only the columns already in the builder. Columns added later won't
* be backfilled with replication scope.

View File

@ -22,45 +22,39 @@ import org.apache.hadoop.conf.Configurable;
import org.apache.hadoop.hbase.client.Put;
/**
* Apply a {@link Constraint} (in traditional database terminology) to a HTable.
* Any number of {@link Constraint Constraints} can be added to the table, in
* any order.
* <p>
* Apply a {@link Constraint} (in traditional database terminology) to a Table. Any number of
* {@link Constraint Constraints} can be added to the table, in any order.
* <p/>
* A {@link Constraint} must be added to a table before the table is loaded via
* {@link Constraints#add(org.apache.hadoop.hbase.HTableDescriptor, Class[])} or
* {@link Constraints#add(org.apache.hadoop.hbase.HTableDescriptor,
* org.apache.hadoop.hbase.util.Pair...)}
* (if you want to add a configuration with the {@link Constraint}). Constraints
* will be run in the order that they are added. Further, a Constraint will be
* configured before it is run (on load).
* <p>
* See {@link Constraints#enableConstraint(org.apache.hadoop.hbase.HTableDescriptor, Class)} and
* {@link Constraints#disableConstraint(org.apache.hadoop.hbase.HTableDescriptor, Class)} for
* enabling/disabling of a given {@link Constraint} after it has been added.
* <p>
* {@link Constraints#add(org.apache.hadoop.hbase.client.TableDescriptorBuilder, Class...)} or
* {@link Constraints#add(org.apache.hadoop.hbase.client.TableDescriptorBuilder, org.apache.hadoop.hbase.util.Pair...)}
* (if you want to add a configuration with the {@link Constraint}). Constraints will be run in the
* order that they are added. Further, a Constraint will be configured before it is run (on load).
* <p/>
* See
* {@link Constraints#enableConstraint(org.apache.hadoop.hbase.client.TableDescriptorBuilder, Class)}
* and
* {@link Constraints#disableConstraint(org.apache.hadoop.hbase.client.TableDescriptorBuilder, Class)}
* for enabling/disabling of a given {@link Constraint} after it has been added.
* <p/>
* If a {@link Put} is invalid, the Constraint should throw some sort of
* {@link org.apache.hadoop.hbase.constraint.ConstraintException}, indicating
* that the {@link Put} has failed. When
* this exception is thrown, not further retries of the {@link Put} are
* attempted nor are any other {@link Constraint Constraints} attempted (the
* {@link Put} is clearly not valid). Therefore, there are performance
* implications in the order in which {@link BaseConstraint Constraints} are
* specified.
* <p>
* {@link org.apache.hadoop.hbase.constraint.ConstraintException}, indicating that the {@link Put}
* has failed. When this exception is thrown, not further retries of the {@link Put} are attempted
* nor are any other {@link Constraint Constraints} attempted (the {@link Put} is clearly not
* valid). Therefore, there are performance implications in the order in which {@link BaseConstraint
* Constraints} are specified.
* <p/>
* If a {@link Constraint} fails to fail the {@link Put} via a
* {@link org.apache.hadoop.hbase.constraint.ConstraintException}, but instead
* throws a {@link RuntimeException},
* the entire constraint processing mechanism ({@link ConstraintProcessor}) will
* be unloaded from the table. This ensures that the region server is still
* functional, but no more {@link Put Puts} will be checked via
* {@link Constraint Constraints}.
* <p>
* Further, {@link Constraint Constraints} should probably not be used to
* enforce cross-table references as it will cause tremendous write slowdowns,
* but it is possible.
* <p>
* {@link org.apache.hadoop.hbase.constraint.ConstraintException}, but instead throws a
* {@link RuntimeException}, the entire constraint processing mechanism
* ({@link ConstraintProcessor}) will be unloaded from the table. This ensures that the region
* server is still functional, but no more {@link Put Puts} will be checked via {@link Constraint
* Constraints}.
* <p/>
* Further, {@link Constraint Constraints} should probably not be used to enforce cross-table
* references as it will cause tremendous write slowdowns, but it is possible.
* <p/>
* NOTE: Implementing classes must have a nullary (no-args) constructor
*
* @see BaseConstraint
* @see Constraints
*/
@ -68,15 +62,13 @@ import org.apache.hadoop.hbase.client.Put;
public interface Constraint extends Configurable {
/**
* Check a {@link Put} to ensure it is valid for the table. If the {@link Put}
* is valid, then just return from the method. Otherwise, throw an
* {@link Exception} specifying what happened. This {@link Exception} is
* propagated back to the client so you can see what caused the {@link Put} to
* fail.
* Check a {@link Put} to ensure it is valid for the table. If the {@link Put} is valid, then just
* return from the method. Otherwise, throw an {@link Exception} specifying what happened. This
* {@link Exception} is propagated back to the client so you can see what caused the {@link Put}
* to fail.
* @param p {@link Put} to check
* @throws org.apache.hadoop.hbase.constraint.ConstraintException when the
* {@link Put} does not match the
* constraint.
* @throws org.apache.hadoop.hbase.constraint.ConstraintException when the {@link Put} does not
* match the constraint.
*/
void check(Put p) throws ConstraintException;

View File

@ -29,26 +29,24 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Pattern;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.client.TableDescriptor;
import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.yetus.audience.InterfaceAudience;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.client.TableDescriptor;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.Pair;
/**
* Utilities for adding/removing constraints from a table.
* <p>
* Constraints can be added on table load time, via the {@link HTableDescriptor}.
* <p>
* NOTE: this class is NOT thread safe. Concurrent setting/enabling/disabling of
* constraints can cause constraints to be run at incorrect times or not at all.
* <p/>
* Since {@link TableDescriptor} is immutable now, you should use {@link TableDescriptorBuilder}.
* And when disabling or removing constraints, you could use
* {@link TableDescriptorBuilder#newBuilder(TableDescriptor)} to clone the old
* {@link TableDescriptor} and then pass it the below methods.
*/
@InterfaceAudience.Private
@InterfaceAudience.Public
public final class Constraints {
private static final int DEFAULT_PRIORITY = -1;
@ -57,8 +55,8 @@ public final class Constraints {
private static final Logger LOG = LoggerFactory.getLogger(Constraints.class);
private static final String CONSTRAINT_HTD_KEY_PREFIX = "constraint $";
private static final Pattern CONSTRAINT_HTD_ATTR_KEY_PATTERN = Pattern
.compile(CONSTRAINT_HTD_KEY_PREFIX, Pattern.LITERAL);
private static final Pattern CONSTRAINT_HTD_ATTR_KEY_PATTERN =
Pattern.compile(CONSTRAINT_HTD_KEY_PREFIX, Pattern.LITERAL);
// configuration key for if the constraint is enabled
private static final String ENABLED_KEY = "_ENABLED";
@ -74,112 +72,57 @@ public final class Constraints {
/**
* Enable constraints on a table.
* <p>
* Currently, if you attempt to add a constraint to the table, then
* Constraints will automatically be turned on.
*
* @param desc
* table description to add the processor
* @throws IOException
* If the {@link ConstraintProcessor} CP couldn't be added to the
* table.
* <p/>
* Currently, if you attempt to add a constraint to the table, then Constraints will automatically
* be turned on.
*/
public static void enable(HTableDescriptor desc) throws IOException {
// if the CP has already been loaded, do nothing
String clazz = ConstraintProcessor.class.getName();
if (desc.hasCoprocessor(clazz)) {
return;
public static TableDescriptorBuilder enable(TableDescriptorBuilder builder) throws IOException {
if (!builder.hasCoprocessor(ConstraintProcessor.class.getName())) {
builder.setCoprocessor(ConstraintProcessor.class.getName());
}
// add the constrain processor CP to the table
desc.addCoprocessor(clazz);
}
public static void enable(TableDescriptorBuilder.ModifyableTableDescriptor desc)
throws IOException {
// if the CP has already been loaded, do nothing
String clazz = ConstraintProcessor.class.getName();
if (desc.hasCoprocessor(clazz)) {
return;
}
// add the constrain processor CP to the table
desc.setCoprocessor(clazz);
return builder;
}
/**
* Turn off processing constraints for a given table, even if constraints have
* been turned on or added.
*
* @param desc
* {@link HTableDescriptor} where to disable {@link Constraint
* Constraints}.
* Turn off processing constraints for a given table, even if constraints have been turned on or
* added.
*/
public static void disable(HTableDescriptor desc) {
desc.removeCoprocessor(ConstraintProcessor.class.getName());
}
public static void disable(TableDescriptorBuilder.ModifyableTableDescriptor tableDescriptor) {
tableDescriptor.removeCoprocessor(ConstraintProcessor.class.getName());
public static TableDescriptorBuilder disable(TableDescriptorBuilder builder) throws IOException {
return builder.removeCoprocessor(ConstraintProcessor.class.getName());
}
/**
* Remove all {@link Constraint Constraints} that have been added to the table
* and turn off the constraint processing.
* <p>
* All {@link Configuration Configurations} and their associated
* {@link Constraint} are removed.
*
* @param desc
* {@link HTableDescriptor} to remove {@link Constraint Constraints}
* from.
* Remove all {@link Constraint Constraints} that have been added to the table and turn off the
* constraint processing.
* <p/>
* All {@link Configuration Configurations} and their associated {@link Constraint} are removed.
*/
public static void remove(HTableDescriptor desc) {
// disable constraints
disable(desc);
// remove all the constraint settings
List<Bytes> keys = new ArrayList<>();
// loop through all the key, values looking for constraints
for (Map.Entry<Bytes, Bytes> e : desc
.getValues().entrySet()) {
String key = Bytes.toString((e.getKey().get()));
String[] className = CONSTRAINT_HTD_ATTR_KEY_PATTERN.split(key);
if (className.length == 2) {
keys.add(e.getKey());
}
}
// now remove all the keys we found
for (Bytes key : keys) {
desc.remove(key);
}
public static TableDescriptorBuilder remove(TableDescriptorBuilder builder) throws IOException {
disable(builder);
return builder
.removeValue((k, v) -> CONSTRAINT_HTD_ATTR_KEY_PATTERN.split(k.toString()).length == 2);
}
/**
* Check to see if the Constraint is currently set.
*
* @param desc
* {@link HTableDescriptor} to check
* @param clazz
* {@link Constraint} class to check for.
* @return <tt>true</tt> if the {@link Constraint} is present, even if it is
* disabled. <tt>false</tt> otherwise.
* @param desc {@link TableDescriptor} to check
* @param clazz {@link Constraint} class to check for.
* @return <tt>true</tt> if the {@link Constraint} is present, even if it is disabled.
* <tt>false</tt> otherwise.
*/
public static boolean has(HTableDescriptor desc,
Class<? extends Constraint> clazz) {
public static boolean has(TableDescriptor desc, Class<? extends Constraint> clazz) {
return getKeyValueForClass(desc, clazz) != null;
}
/**
* Get the kv {@link Entry} in the descriptor for the specified class
*
* @param desc {@link HTableDescriptor} to read
* @param desc {@link TableDescriptor} to read
* @param clazz To search for
* @return The {@link Pair} of {@literal <key, value>} in the table, if that class is
* present. {@code NULL} otherwise.
* @return The {@link Pair} of {@literal <key, value>} in the table, if that class is present.
* {@code null} otherwise.
*/
private static Pair<String, String> getKeyValueForClass(
HTableDescriptor desc, Class<? extends Constraint> clazz) {
private static Pair<String, String> getKeyValueForClass(TableDescriptor desc,
Class<? extends Constraint> clazz) {
// get the serialized version of the constraint
String key = serializeConstraintClass(clazz);
String value = desc.getValue(key);
@ -187,179 +130,126 @@ public final class Constraints {
return value == null ? null : new Pair<>(key, value);
}
private static Pair<String, String> getKeyValueForClass(
TableDescriptorBuilder.ModifyableTableDescriptor tableDescriptor,
Class<? extends Constraint> clazz) {
/**
* Get the kv {@link Entry} in the descriptor builder for the specified class
* @param builder {@link TableDescriptorBuilder} to read
* @param clazz To search for
* @return The {@link Pair} of {@literal <key, value>} in the table, if that class is present.
* {@code null} otherwise.
*/
private static Pair<String, String> getKeyValueForClass(TableDescriptorBuilder builder,
Class<? extends Constraint> clazz) {
// get the serialized version of the constraint
String key = serializeConstraintClass(clazz);
String value = tableDescriptor.getValue(key);
String value = builder.getValue(key);
return value == null ? null : new Pair<>(key, value);
}
/**
* Add configuration-less constraints to the table.
* <p>
* This will overwrite any configuration associated with the previous
* constraint of the same class.
* <p>
* Each constraint, when added to the table, will have a specific priority,
* dictating the order in which the {@link Constraint} will be run. A
* {@link Constraint} earlier in the list will be run before those later in
* the list. The same logic applies between two Constraints over time (earlier
* added is run first on the regionserver).
*
* @param desc
* {@link HTableDescriptor} to add {@link Constraint Constraints}
* @param constraints
* {@link Constraint Constraints} to add. All constraints are
* considered automatically enabled on add
* @throws IOException
* If constraint could not be serialized/added to table
*/
public static void add(HTableDescriptor desc,
Class<? extends Constraint>... constraints) throws IOException {
// make sure constraints are enabled
enable(desc);
long priority = getNextPriority(desc);
// store each constraint
for (Class<? extends Constraint> clazz : constraints) {
addConstraint(desc, clazz, null, priority++);
}
updateLatestPriority(desc, priority);
}
/**
* Add configuration-less constraints to the table.
* <p>
* This will overwrite any configuration associated with the previous
* constraint of the same class.
* <p>
* Each constraint, when added to the table, will have a specific priority,
* dictating the order in which the {@link Constraint} will be run. A
* {@link Constraint} earlier in the list will be run before those later in
* the list. The same logic applies between two Constraints over time (earlier
* added is run first on the regionserver).
*
* @param tableDescriptor TableDescriptorBuilder.ModifyableTableDescriptor
* to add {@link Constraint}
* @param constraints {@link Constraint} to add. All constraints are
* considered automatically enabled on add
* <p/>
* This will overwrite any configuration associated with the previous constraint of the same
* class.
* <p/>
* Each constraint, when added to the table, will have a specific priority, dictating the order in
* which the {@link Constraint} will be run. A {@link Constraint} earlier in the list will be run
* before those later in the list. The same logic applies between two Constraints over time
* (earlier added is run first on the regionserver).
* @param builder {@link TableDescriptorBuilder} to add a {@link Constraint}
* @param constraints {@link Constraint Constraints} to add. All constraints are considered
* automatically enabled on add
* @throws IOException If constraint could not be serialized/added to table
*/
public static void add(TableDescriptorBuilder.ModifyableTableDescriptor tableDescriptor,
@SafeVarargs
public static TableDescriptorBuilder add(TableDescriptorBuilder builder,
Class<? extends Constraint>... constraints) throws IOException {
// make sure constraints are enabled
enable(tableDescriptor);
long priority = getNextPriority(tableDescriptor);
enable(builder);
long priority = getNextPriority(builder);
// store each constraint
for (Class<? extends Constraint> clazz : constraints) {
writeConstraint(tableDescriptor, serializeConstraintClass(clazz),
configure(null, true, priority));
addConstraint(builder, clazz, null, priority++);
}
updateLatestPriority(tableDescriptor, priority);
return updateLatestPriority(builder, priority);
}
/**
* Add constraints and their associated configurations to the table.
* <p>
* Adding the same constraint class twice will overwrite the first
* constraint's configuration
* Adding the same constraint class twice will overwrite the first constraint's configuration
* <p>
* Each constraint, when added to the table, will have a specific priority,
* dictating the order in which the {@link Constraint} will be run. A
* {@link Constraint} earlier in the list will be run before those later in
* the list. The same logic applies between two Constraints over time (earlier
* added is run first on the regionserver).
*
* @param desc
* {@link HTableDescriptor} to add a {@link Constraint}
* @param constraints
* {@link Pair} of a {@link Constraint} and its associated
* {@link Configuration}. The Constraint will be configured on load
* with the specified configuration.All constraints are considered
* automatically enabled on add
* @throws IOException
* if any constraint could not be deserialized. Assumes if 1
* constraint is not loaded properly, something has gone terribly
* wrong and that all constraints need to be enforced.
* Each constraint, when added to the table, will have a specific priority, dictating the order in
* which the {@link Constraint} will be run. A {@link Constraint} earlier in the list will be run
* before those later in the list. The same logic applies between two Constraints over time
* (earlier added is run first on the regionserver).
* @param builder {@link TableDescriptorBuilder} to add a {@link Constraint}
* @param constraints {@link Pair} of a {@link Constraint} and its associated
* {@link Configuration}. The Constraint will be configured on load with the specified
* configuration.All constraints are considered automatically enabled on add
* @throws IOException if any constraint could not be deserialized. Assumes if 1 constraint is not
* loaded properly, something has gone terribly wrong and that all constraints need to
* be enforced.
*/
public static void add(HTableDescriptor desc,
Pair<Class<? extends Constraint>, Configuration>... constraints)
throws IOException {
enable(desc);
long priority = getNextPriority(desc);
@SafeVarargs
public static TableDescriptorBuilder add(TableDescriptorBuilder builder,
Pair<Class<? extends Constraint>, Configuration>... constraints) throws IOException {
enable(builder);
long priority = getNextPriority(builder);
for (Pair<Class<? extends Constraint>, Configuration> pair : constraints) {
addConstraint(desc, pair.getFirst(), pair.getSecond(), priority++);
addConstraint(builder, pair.getFirst(), pair.getSecond(), priority++);
}
updateLatestPriority(desc, priority);
return updateLatestPriority(builder, priority);
}
/**
* Add a {@link Constraint} to the table with the given configuration
* <p>
* Each constraint, when added to the table, will have a specific priority,
* dictating the order in which the {@link Constraint} will be run. A
* {@link Constraint} added will run on the regionserver before those added to
* the {@link HTableDescriptor} later.
*
* @param desc
* table descriptor to the constraint to
* @param constraint
* to be added
* @param conf
* configuration associated with the constraint
* @throws IOException
* if any constraint could not be deserialized. Assumes if 1
* constraint is not loaded properly, something has gone terribly
* wrong and that all constraints need to be enforced.
* <p/>
* Each constraint, when added to the table, will have a specific priority, dictating the order in
* which the {@link Constraint} will be run. A {@link Constraint} added will run on the
* regionserver before those added to the {@link TableDescriptorBuilder} later.
* @param builder {@link TableDescriptorBuilder} to add a {@link Constraint}
* @param constraint to be added
* @param conf configuration associated with the constraint
* @throws IOException if any constraint could not be deserialized. Assumes if 1 constraint is not
* loaded properly, something has gone terribly wrong and that all constraints need to
* be enforced.
*/
public static void add(HTableDescriptor desc,
Class<? extends Constraint> constraint, Configuration conf)
throws IOException {
enable(desc);
long priority = getNextPriority(desc);
addConstraint(desc, constraint, conf, priority++);
public static TableDescriptorBuilder add(TableDescriptorBuilder builder,
Class<? extends Constraint> constraint, Configuration conf) throws IOException {
enable(builder);
long priority = getNextPriority(builder);
addConstraint(builder, constraint, conf, priority++);
updateLatestPriority(desc, priority);
return updateLatestPriority(builder, priority);
}
/**
* Write the raw constraint and configuration to the descriptor.
* <p>
* This method takes care of creating a new configuration based on the passed
* in configuration and then updating that with enabled and priority of the
* constraint.
* <p>
* <p/>
* This method takes care of creating a new configuration based on the passed in configuration and
* then updating that with enabled and priority of the constraint.
* <p/>
* When a constraint is added, it is automatically enabled.
*/
private static void addConstraint(HTableDescriptor desc,
Class<? extends Constraint> clazz, Configuration conf, long priority)
throws IOException {
writeConstraint(desc, serializeConstraintClass(clazz),
configure(conf, true, priority));
private static TableDescriptorBuilder addConstraint(TableDescriptorBuilder builder,
Class<? extends Constraint> clazz, Configuration conf, long priority) throws IOException {
return writeConstraint(builder, serializeConstraintClass(clazz),
configure(conf, true, priority));
}
/**
* Setup the configuration for a constraint as to whether it is enabled and
* its priority
*
* @param conf
* on which to base the new configuration
* @param enabled
* <tt>true</tt> if it should be run
* @param priority
* relative to other constraints
* @return a new configuration, storable in the {@link HTableDescriptor}
* Setup the configuration for a constraint as to whether it is enabled and its priority
* @param conf on which to base the new configuration
* @param enabled <tt>true</tt> if it should be run
* @param priority relative to other constraints
* @return a new configuration, storable in the {@link TableDescriptor}
*/
private static Configuration configure(Configuration conf, boolean enabled,
long priority) {
private static Configuration configure(Configuration conf, boolean enabled, long priority) {
// create the configuration to actually be stored
// clone if possible, but otherwise just create an empty configuration
Configuration toWrite = conf == null ? new Configuration()
: new Configuration(conf);
Configuration toWrite = conf == null ? new Configuration() : new Configuration(conf);
// update internal properties
toWrite.setBooleanIfUnset(ENABLED_KEY, enabled);
@ -373,46 +263,32 @@ public final class Constraints {
}
/**
* Just write the class to a String representation of the class as a key for
* the {@link HTableDescriptor}
*
* @param clazz
* Constraint class to convert to a {@link HTableDescriptor} key
* @return key to store in the {@link HTableDescriptor}
* Just write the class to a String representation of the class as a key for the
* {@link TableDescriptor}
* @param clazz Constraint class to convert to a {@link TableDescriptor} key
* @return key to store in the {@link TableDescriptor}
*/
private static String serializeConstraintClass(
Class<? extends Constraint> clazz) {
private static String serializeConstraintClass(Class<? extends Constraint> clazz) {
String constraintClazz = clazz.getName();
return CONSTRAINT_HTD_KEY_PREFIX + constraintClazz;
}
/**
* Write the given key and associated configuration to the
* {@link HTableDescriptor}
* Write the given key and associated configuration to the {@link TableDescriptorBuilder}.
*/
private static void writeConstraint(HTableDescriptor desc, String key,
Configuration conf) throws IOException {
private static TableDescriptorBuilder writeConstraint(TableDescriptorBuilder builder, String key,
Configuration conf) throws IOException {
// store the key and conf in the descriptor
desc.setValue(key, serializeConfiguration(conf));
}
private static void writeConstraint(
TableDescriptorBuilder.ModifyableTableDescriptor tableDescriptor, String key,
Configuration conf) throws IOException {
// store the key and conf in the descriptor
tableDescriptor.setValue(key, serializeConfiguration(conf));
return builder.setValue(key, serializeConfiguration(conf));
}
/**
* Write the configuration to a String
*
* @param conf
* to write
* @param conf to write
* @return String representation of that configuration
* @throws IOException
*/
private static String serializeConfiguration(Configuration conf)
throws IOException {
private static String serializeConfiguration(Configuration conf) throws IOException {
// write the configuration out to the data stream
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
@ -424,13 +300,10 @@ public final class Constraints {
/**
* Read the {@link Configuration} stored in the byte stream.
*
* @param bytes
* to read from
* @param bytes to read from
* @return A valid configuration
*/
private static Configuration readConfiguration(byte[] bytes)
throws IOException {
private static Configuration readConfiguration(byte[] bytes) throws IOException {
ByteArrayInputStream is = new ByteArrayInputStream(bytes);
Configuration conf = new Configuration(false);
conf.addResource(is);
@ -439,20 +312,16 @@ public final class Constraints {
/**
* Read in the configuration from the String encoded configuration
*
* @param bytes
* to read from
* @param bytes to read from
* @return A valid configuration
* @throws IOException
* if the configuration could not be read
* @throws IOException if the configuration could not be read
*/
private static Configuration readConfiguration(String bytes)
throws IOException {
private static Configuration readConfiguration(String bytes) throws IOException {
return readConfiguration(Bytes.toBytes(bytes));
}
private static long getNextPriority(TableDescriptor desc) {
String value = desc.getValue(COUNTER_KEY);
private static long getNextPriority(TableDescriptorBuilder builder) {
String value = builder.getValue(COUNTER_KEY);
long priority;
// get the current priority
@ -465,41 +334,30 @@ public final class Constraints {
return priority;
}
private static void updateLatestPriority(HTableDescriptor desc, long priority) {
private static TableDescriptorBuilder updateLatestPriority(TableDescriptorBuilder builder,
long priority) {
// update the max priority
desc.setValue(COUNTER_KEY, Long.toString(priority));
}
private static void updateLatestPriority(
TableDescriptorBuilder.ModifyableTableDescriptor tableDescriptor, long priority) {
// update the max priority
tableDescriptor.setValue(COUNTER_KEY, Long.toString(priority));
return builder.setValue(COUNTER_KEY, Long.toString(priority));
}
/**
* Update the configuration for the {@link Constraint}; does not change the
* order in which the constraint is run.
*
* @param desc
* {@link HTableDescriptor} to update
* @param clazz
* {@link Constraint} to update
* @param configuration
* to update the {@link Constraint} with.
* @throws IOException
* if the Constraint was not stored correctly
* @throws IllegalArgumentException
* if the Constraint was not present on this table.
* Update the configuration for the {@link Constraint}; does not change the order in which the
* constraint is run.
* @param builder {@link TableDescriptorBuilder} to update
* @param clazz {@link Constraint} to update
* @param configuration to update the {@link Constraint} with.
* @throws IOException if the Constraint was not stored correctly
* @throws IllegalArgumentException if the Constraint was not present on this table.
*/
public static void setConfiguration(HTableDescriptor desc,
Class<? extends Constraint> clazz, Configuration configuration)
throws IOException, IllegalArgumentException {
public static TableDescriptorBuilder setConfiguration(TableDescriptorBuilder builder,
Class<? extends Constraint> clazz, Configuration configuration)
throws IOException, IllegalArgumentException {
// get the entry for this class
Pair<String, String> e = getKeyValueForClass(desc, clazz);
Pair<String, String> e = getKeyValueForClass(builder, clazz);
if (e == null) {
throw new IllegalArgumentException("Constraint: " + clazz.getName()
+ " is not associated with this table.");
throw new IllegalArgumentException(
"Constraint: " + clazz.getName() + " is not associated with this table.");
}
// clone over the configuration elements
@ -513,95 +371,54 @@ public final class Constraints {
conf.setIfUnset(PRIORITY_KEY, internal.get(PRIORITY_KEY));
// update the current value
writeConstraint(desc, e.getFirst(), conf);
return writeConstraint(builder, e.getFirst(), conf);
}
/**
* Remove the constraint (and associated information) for the table
* descriptor.
*
* @param desc
* {@link HTableDescriptor} to modify
* @param clazz
* {@link Constraint} class to remove
* Remove the constraint (and associated information) for the table descriptor.
* @param builder {@link TableDescriptorBuilder} to modify
* @param clazz {@link Constraint} class to remove
*/
public static void remove(HTableDescriptor desc,
Class<? extends Constraint> clazz) {
public static TableDescriptorBuilder remove(TableDescriptorBuilder builder,
Class<? extends Constraint> clazz) {
String key = serializeConstraintClass(clazz);
desc.remove(key);
return builder.removeValue(key);
}
/**
* Enable the given {@link Constraint}. Retains all the information (e.g.
* Configuration) for the {@link Constraint}, but makes sure that it gets
* loaded on the table.
*
* @param desc
* {@link HTableDescriptor} to modify
* @param clazz
* {@link Constraint} to enable
* @throws IOException
* If the constraint cannot be properly deserialized
* Enable the given {@link Constraint}. Retains all the information (e.g. Configuration) for the
* {@link Constraint}, but makes sure that it gets loaded on the table.
* @param builder {@link TableDescriptorBuilder} to modify
* @param clazz {@link Constraint} to enable
* @throws IOException If the constraint cannot be properly deserialized
*/
public static void enableConstraint(HTableDescriptor desc,
Class<? extends Constraint> clazz) throws IOException {
changeConstraintEnabled(desc, clazz, true);
public static void enableConstraint(TableDescriptorBuilder builder,
Class<? extends Constraint> clazz) throws IOException {
changeConstraintEnabled(builder, clazz, true);
}
/**
* Disable the given {@link Constraint}. Retains all the information (e.g.
* Configuration) for the {@link Constraint}, but it just doesn't load the
* {@link Constraint} on the table.
*
* @param desc
* {@link HTableDescriptor} to modify
* @param clazz
* {@link Constraint} to disable.
* @throws IOException
* if the constraint cannot be found
* Disable the given {@link Constraint}. Retains all the information (e.g. Configuration) for the
* {@link Constraint}, but it just doesn't load the {@link Constraint} on the table.
* @param builder {@link TableDescriptorBuilder} to modify
* @param clazz {@link Constraint} to disable.
* @throws IOException if the constraint cannot be found
*/
public static void disableConstraint(HTableDescriptor desc,
Class<? extends Constraint> clazz) throws IOException {
changeConstraintEnabled(desc, clazz, false);
}
public static void disableConstraint(
TableDescriptorBuilder.ModifyableTableDescriptor tableDescriptor,
Class<? extends Constraint> clazz) throws IOException {
changeConstraintEnabled(tableDescriptor, clazz, false);
public static void disableConstraint(TableDescriptorBuilder builder,
Class<? extends Constraint> clazz) throws IOException {
changeConstraintEnabled(builder, clazz, false);
}
/**
* Change the whether the constraint (if it is already present) is enabled or
* disabled.
* Change the whether the constraint (if it is already present) is enabled or disabled.
*/
private static void changeConstraintEnabled(HTableDescriptor desc,
Class<? extends Constraint> clazz, boolean enabled) throws IOException {
// get the original constraint
Pair<String, String> entry = getKeyValueForClass(desc, clazz);
if (entry == null) {
throw new IllegalArgumentException("Constraint: " + clazz.getName()
+ " is not associated with this table. You can't enable it!");
}
// create a new configuration from that conf
Configuration conf = readConfiguration(entry.getSecond());
// set that it is enabled
conf.setBoolean(ENABLED_KEY, enabled);
// write it back out
writeConstraint(desc, entry.getFirst(), conf);
}
private static void changeConstraintEnabled(
TableDescriptorBuilder.ModifyableTableDescriptor tableDescriptor,
private static TableDescriptorBuilder changeConstraintEnabled(TableDescriptorBuilder builder,
Class<? extends Constraint> clazz, boolean enabled) throws IOException {
// get the original constraint
Pair<String, String> entry = getKeyValueForClass(tableDescriptor, clazz);
Pair<String, String> entry = getKeyValueForClass(builder, clazz);
if (entry == null) {
throw new IllegalArgumentException("Constraint: " + clazz.getName()
+ " is not associated with this table. You can't enable it!");
throw new IllegalArgumentException("Constraint: " + clazz.getName() +
" is not associated with this table. You can't enable it!");
}
// create a new configuration from that conf
@ -611,23 +428,19 @@ public final class Constraints {
conf.setBoolean(ENABLED_KEY, enabled);
// write it back out
writeConstraint(tableDescriptor, entry.getFirst(), conf);
return writeConstraint(builder, entry.getFirst(), conf);
}
/**
* Check to see if the given constraint is enabled.
*
* @param desc
* {@link HTableDescriptor} to check.
* @param clazz
* {@link Constraint} to check for
* @return <tt>true</tt> if the {@link Constraint} is present and enabled.
* <tt>false</tt> otherwise.
* @throws IOException
* If the constraint has improperly stored in the table
* @param desc {@link TableDescriptor} to check.
* @param clazz {@link Constraint} to check for
* @return <tt>true</tt> if the {@link Constraint} is present and enabled. <tt>false</tt>
* otherwise.
* @throws IOException If the constraint has improperly stored in the table
*/
public static boolean enabled(HTableDescriptor desc,
Class<? extends Constraint> clazz) throws IOException {
public static boolean enabled(TableDescriptor desc, Class<? extends Constraint> clazz)
throws IOException {
// get the kv
Pair<String, String> entry = getKeyValueForClass(desc, clazz);
// its not enabled so just return false. In fact, its not even present!
@ -643,24 +456,19 @@ public final class Constraints {
/**
* Get the constraints stored in the table descriptor
*
* @param desc
* To read from
* @param classloader
* To use when loading classes. If a special classloader is used on a
* region, for instance, then that should be the classloader used to
* load the constraints. This could also apply to unit-testing
* situation, where want to ensure that class is reloaded or not.
* @param desc To read from
* @param classloader To use when loading classes. If a special classloader is used on a region,
* for instance, then that should be the classloader used to load the constraints. This
* could also apply to unit-testing situation, where want to ensure that class is
* reloaded or not.
* @return List of configured {@link Constraint Constraints}
* @throws IOException
* if any part of reading/arguments fails
* @throws IOException if any part of reading/arguments fails
*/
static List<? extends Constraint> getConstraints(TableDescriptor desc,
ClassLoader classloader) throws IOException {
static List<? extends Constraint> getConstraints(TableDescriptor desc, ClassLoader classloader)
throws IOException {
List<Constraint> constraints = new ArrayList<>();
// loop through all the key, values looking for constraints
for (Map.Entry<Bytes, Bytes> e : desc
.getValues().entrySet()) {
for (Map.Entry<Bytes, Bytes> e : desc.getValues().entrySet()) {
// read out the constraint
String key = Bytes.toString(e.getKey().get()).trim();
String[] className = CONSTRAINT_HTD_ATTR_KEY_PATTERN.split(key);
@ -676,27 +484,25 @@ public final class Constraints {
conf = readConfiguration(e.getValue().get());
} catch (IOException e1) {
// long that we don't have a valid configuration stored, and move on.
LOG.warn("Corrupted configuration found for key:" + key
+ ", skipping it.");
LOG.warn("Corrupted configuration found for key:" + key + ", skipping it.");
continue;
}
// if it is not enabled, skip it
if (!conf.getBoolean(ENABLED_KEY, false)) {
if (LOG.isDebugEnabled())
LOG.debug("Constraint: " + key + " is DISABLED - skipping it");
LOG.debug("Constraint: {} is DISABLED - skipping it", key);
// go to the next constraint
continue;
}
try {
// add the constraint, now that we expect it to be valid.
Class<? extends Constraint> clazz = classloader.loadClass(key)
.asSubclass(Constraint.class);
Class<? extends Constraint> clazz =
classloader.loadClass(key).asSubclass(Constraint.class);
Constraint constraint = clazz.getDeclaredConstructor().newInstance();
constraint.setConf(conf);
constraints.add(constraint);
} catch (InvocationTargetException | NoSuchMethodException | ClassNotFoundException |
InstantiationException | IllegalAccessException e1) {
} catch (InvocationTargetException | NoSuchMethodException | ClassNotFoundException
| InstantiationException | IllegalAccessException e1) {
throw new IOException(e1);
}
}
@ -711,7 +517,7 @@ public final class Constraints {
public int compare(Constraint c1, Constraint c2) {
// compare the priorities of the constraints stored in their configuration
return Long.compare(c1.getConf().getLong(PRIORITY_KEY, DEFAULT_PRIORITY),
c2.getConf().getLong(PRIORITY_KEY, DEFAULT_PRIORITY));
c2.getConf().getLong(PRIORITY_KEY, DEFAULT_PRIORITY));
}
};

View File

@ -18,110 +18,121 @@
/**
* Restrict the domain of a data attribute, often times to fulfill business rules/requirements.
* <h2>Table of Contents</h2>
* <ul>
* <li><a href="#overview">Overview</a></li>
* <li><a href="#concurrency">Concurrency and Atomicity</a></li>
* <li><a href="#caveats">Caveats</a></li>
* <li><a href="#usage">Example Usage</a></li>
* </ul>
* <h2><a name="overview">Overview</a></h2> Constraints are used to enforce business rules in a
* database. By checking all {@link org.apache.hadoop.hbase.client.Put Puts} on a given table, you
* can enforce very specific data policies. For instance, you can ensure that a certain column
* family-column qualifier pair always has a value between 1 and 10. Otherwise, the
* {@link org.apache.hadoop.hbase.client.Put} is rejected and the data integrity is maintained.
* <p/>
* Constraints are designed to be configurable, so a constraint can be used across different tables,
* but implement different behavior depending on the specific configuration given to that
* constraint.
* <p/>
* By adding a constraint to a table (see <a href="#usage">Example Usage</a>), constraints will
* automatically be enabled. You also then have the option of to disable (just 'turn off') or remove
* (delete all associated information) all constraints on a table. If you remove all constraints
* (see
* {@link org.apache.hadoop.hbase.constraint.Constraints#remove(org.apache.hadoop.hbase.client.TableDescriptorBuilder)},
* you must re-add any {@link org.apache.hadoop.hbase.constraint.Constraint} you want on that table.
* However, if they are just disabled (see
* {@link org.apache.hadoop.hbase.constraint.Constraints#disable(org.apache.hadoop.hbase.client.TableDescriptorBuilder)},
* all you need to do is enable constraints again, and everything will be turned back on as it was
* configured. Individual constraints can also be individually enabled, disabled or removed without
* affecting other constraints.
* <p/>
* By default, constraints are disabled on a table. This means you will not see <i>any</i> slow down
* on a table if constraints are not enabled.
* <p/>
* <h2><a name="concurrency">Concurrency and Atomicity</a></h2> Currently, no attempts at enforcing
* correctness in a multi-threaded scenario when modifying a constraint, via
* {@link org.apache.hadoop.hbase.constraint.Constraints}, to the the
* {@link org.apache.hadoop.hbase.client.TableDescriptorBuilder}. This is particularly important
* when adding a constraint(s) to the {@link org.apache.hadoop.hbase.client.TableDescriptorBuilder}
* as it first retrieves the next priority from a custom value set in the descriptor, adds each
* constraint (with increasing priority) to the descriptor, and then the next available priority is
* re-stored back in the {@link org.apache.hadoop.hbase.client.TableDescriptorBuilder}.
* <p/>
* Locking is recommended around each of Constraints add methods:
* {@link org.apache.hadoop.hbase.constraint.Constraints#add(org.apache.hadoop.hbase.client.TableDescriptorBuilder, Class...)},
* {@link org.apache.hadoop.hbase.constraint.Constraints#add(org.apache.hadoop.hbase.client.TableDescriptorBuilder, org.apache.hadoop.hbase.util.Pair...)},
* and
* {@link org.apache.hadoop.hbase.constraint.Constraints#add(org.apache.hadoop.hbase.client.TableDescriptorBuilder, Class, org.apache.hadoop.conf.Configuration)}.
* Any changes on <i>a single TableDescriptor</i> should be serialized, either within a single
* thread or via external mechanisms.
* <p/>
* Note that having a higher priority means that a constraint will run later; e.g. a constraint with
* priority 1 will run before a constraint with priority 2.
* <p/>
* Since Constraints currently are designed to just implement simple checks (e.g. is the value in
* the right range), there will be no atomicity conflicts. Even if one of the puts finishes the
* constraint first, the single row will not be corrupted and the 'fastest' write will win; the
* underlying region takes care of breaking the tie and ensuring that writes get serialized to the
* table. So yes, this doesn't ensure that we are going to get specific ordering or even a fully
* consistent view of the underlying data.
* <p/>
* Each constraint should only use local/instance variables, unless doing more advanced usage.
* Static variables could cause difficulties when checking concurrent writes to the same region,
* leading to either highly locked situations (decreasing through-put) or higher probability of
* errors. However, as long as each constraint just uses local variables, each thread interacting
* with the constraint will execute correctly and efficiently.
* <h2><a name="caveats">Caveats</a></h2> In traditional (SQL) databases, Constraints are often used
* to enforce <a href="http://en.wikipedia.org/wiki/Relational_database#Constraints">referential
* integrity</a>. However, in HBase, this will likely cause significant overhead and dramatically
* decrease the number of {@link org.apache.hadoop.hbase.client.Put Puts}/second possible on a
* table. This is because to check the referential integrity when making a
* {@link org.apache.hadoop.hbase.client.Put}, one must block on a scan for the 'remote' table,
* checking for the valid reference. For millions of {@link org.apache.hadoop.hbase.client.Put Puts}
* a second, this will breakdown very quickly. There are several options around the blocking
* behavior including, but not limited to:
* <ul>
* <li>Create a 'pre-join' table where the keys are already denormalized</li>
* <li>Designing for 'incorrect' references</li>
* <li>Using an external enforcement mechanism</li>
* </ul>
* There are also several general considerations that must be taken into account, when using
* Constraints:
* <ol>
* <li>All changes made via {@link org.apache.hadoop.hbase.constraint.Constraints} will make
* modifications to the {@link org.apache.hadoop.hbase.client.TableDescriptor} for a given table. As
* such, the usual renabling of tables should be used for propagating changes to the table. When at
* all possible, Constraints should be added to the table before the table is created.</li>
* <li>Constraints are run in the order that they are added to a table. This has implications for
* what order constraints should be added to a table.</li>
* <li>Whenever new Constraint jars are added to a region server, those region servers need to go
* through a rolling restart to make sure that they pick up the new jars and can enable the new
* constraints.</li>
* <li>There are certain keys that are reserved for the Configuration namespace:
* <ul>
* <li>_ENABLED - used server-side to determine if a constraint should be run</li>
* <li>_PRIORITY - used server-side to determine what order a constraint should be run</li>
* </ul>
* If these items are set, they will be respected in the constraint configuration, but they are
* taken care of by default in when adding constraints to an
* {@link org.apache.hadoop.hbase.client.TableDescriptorBuilder} via the usual method.</li>
* </ol>
* <p/>
* Under the hood, constraints are implemented as a Coprocessor (see
* {@link org.apache.hadoop.hbase.constraint.ConstraintProcessor} if you are interested).
* <h2><a name="usage">Example usage</a></h2> First, you must define a
* {@link org.apache.hadoop.hbase.constraint.Constraint}. The best way to do this is to extend
* {@link org.apache.hadoop.hbase.constraint.BaseConstraint}, which takes care of some of the more
* mundane details of using a {@link org.apache.hadoop.hbase.constraint.Constraint}.
* <p/>
* Let's look at one possible implementation of a constraint - an IntegerConstraint(there are also
* several simple examples in the tests). The IntegerConstraint checks to make sure that the value
* is a String-encoded <code>int</code>. It is really simple to implement this kind of constraint,
* the only method needs to be implemented is
* {@link org.apache.hadoop.hbase.constraint.Constraint#check(org.apache.hadoop.hbase.client.Put)}:
* <div style="background-color: #cccccc; padding: 2px"> <blockquote>
*
<h2> Table of Contents</h2>
<ul>
<li><a href="#overview">Overview</a></li>
<li><a href="#concurrency">Concurrency and Atomicity</a></li>
<li><a href="#caveats">Caveats</a></li>
<li><a href="#usage">Example Usage</a></li>
</ul>
<h2><a name="overview">Overview</a></h2>
Constraints are used to enforce business rules in a database.
By checking all {@link org.apache.hadoop.hbase.client.Put Puts} on a given table, you can enforce very specific data policies.
For instance, you can ensure that a certain column family-column qualifier pair always has a value between 1 and 10.
Otherwise, the {@link org.apache.hadoop.hbase.client.Put} is rejected and the data integrity is maintained.
<p>
Constraints are designed to be configurable, so a constraint can be used across different tables, but implement different
behavior depending on the specific configuration given to that constraint.
<p>
By adding a constraint to a table (see <a href="#usage">Example Usage</a>), constraints will automatically enabled.
You also then have the option of to disable (just 'turn off') or remove (delete all associated information) all constraints on a table.
If you remove all constraints
(see {@link org.apache.hadoop.hbase.constraint.Constraints#remove(org.apache.hadoop.hbase.HTableDescriptor)},
you must re-add any {@link org.apache.hadoop.hbase.constraint.Constraint} you want on that table.
However, if they are just disabled (see {@link org.apache.hadoop.hbase.constraint.Constraints#disable(org.apache.hadoop.hbase.HTableDescriptor)},
all you need to do is enable constraints again, and everything will be turned back on as it was configured.
Individual constraints can also be individually enabled, disabled or removed without affecting other constraints.
<p>
By default, constraints are disabled on a table.
This means you will not see <i>any</i> slow down on a table if constraints are not enabled.
<p>
<h2><a name="concurrency">Concurrency and Atomicity</a></h2>
Currently, no attempts at enforcing correctness in a multi-threaded scenario when modifying a constraint, via
{@link org.apache.hadoop.hbase.constraint.Constraints}, to the the {@link org.apache.hadoop.hbase.HTableDescriptor}.
This is particularly important when adding a constraint(s) to the {@link org.apache.hadoop.hbase.HTableDescriptor}
as it first retrieves the next priority from a custom value set in the descriptor,
adds each constraint (with increasing priority) to the descriptor, and then the next available priority is re-stored
back in the {@link org.apache.hadoop.hbase.HTableDescriptor}.
<p>
Locking is recommended around each of Constraints add methods:
{@link org.apache.hadoop.hbase.constraint.Constraints#add(org.apache.hadoop.hbase.HTableDescriptor, Class...)},
{@link org.apache.hadoop.hbase.constraint.Constraints#add(org.apache.hadoop.hbase.HTableDescriptor, org.apache.hadoop.hbase.util.Pair...)},
and {@link org.apache.hadoop.hbase.constraint.Constraints#add(org.apache.hadoop.hbase.HTableDescriptor, Class, org.apache.hadoop.conf.Configuration)}.
Any changes on <i>a single HTableDescriptor</i> should be serialized, either within a single thread or via external mechanisms.
<p>
Note that having a higher priority means that a constraint will run later; e.g. a constraint with priority 1 will run before a
constraint with priority 2.
<p>
Since Constraints currently are designed to just implement simple checks (e.g. is the value in the right range), there will
be no atomicity conflicts.
Even if one of the puts finishes the constraint first, the single row will not be corrupted and the 'fastest' write will win;
the underlying region takes care of breaking the tie and ensuring that writes get serialized to the table.
So yes, this doesn't ensure that we are going to get specific ordering or even a fully consistent view of the underlying data.
<p>
Each constraint should only use local/instance variables, unless doing more advanced usage. Static variables could cause difficulties
when checking concurrent writes to the same region, leading to either highly locked situations (decreasing through-put) or higher probability of errors.
However, as long as each constraint just uses local variables, each thread interacting with the constraint will execute correctly and efficiently.
<h2><a name="caveats">Caveats</a></h2>
In traditional (SQL) databases, Constraints are often used to enforce <a href="http://en.wikipedia.org/wiki/Relational_database#Constraints">referential integrity</a>.
However, in HBase, this will likely cause significant overhead and dramatically decrease the number of
{@link org.apache.hadoop.hbase.client.Put Puts}/second possible on a table. This is because to check the referential integrity
when making a {@link org.apache.hadoop.hbase.client.Put}, one must block on a scan for the 'remote' table, checking for the valid reference.
For millions of {@link org.apache.hadoop.hbase.client.Put Puts} a second, this will breakdown very quickly.
There are several options around the blocking behavior including, but not limited to:
<ul>
<li>Create a 'pre-join' table where the keys are already denormalized</li>
<li>Designing for 'incorrect' references</li>
<li>Using an external enforcement mechanism</li>
</ul>
There are also several general considerations that must be taken into account, when using Constraints:
<ol>
<li>All changes made via {@link org.apache.hadoop.hbase.constraint.Constraints} will make modifications to the
{@link org.apache.hadoop.hbase.HTableDescriptor} for a given table. As such, the usual renabling of tables should be used for
propagating changes to the table. When at all possible, Constraints should be added to the table before the table is created.</li>
<li>Constraints are run in the order that they are added to a table. This has implications for what order constraints should
be added to a table.</li>
<li>Whenever new Constraint jars are added to a region server, those region servers need to go through a rolling restart to
make sure that they pick up the new jars and can enable the new constraints.</li>
<li>There are certain keys that are reserved for the Configuration namespace:
<ul>
<li>_ENABLED - used server-side to determine if a constraint should be run</li>
<li>_PRIORITY - used server-side to determine what order a constraint should be run</li>
</ul>
If these items are set, they will be respected in the constraint configuration, but they are taken care of by default in when
adding constraints to an {@link org.apache.hadoop.hbase.HTableDescriptor} via the usual method.</li>
</ol>
<p>
Under the hood, constraints are implemented as a Coprocessor (see {@link org.apache.hadoop.hbase.constraint.ConstraintProcessor}
if you are interested).
<h2><a name="usage">Example usage</a></h2>
First, you must define a {@link org.apache.hadoop.hbase.constraint.Constraint}.
The best way to do this is to extend {@link org.apache.hadoop.hbase.constraint.BaseConstraint}, which takes care of some of the more
mundane details of using a {@link org.apache.hadoop.hbase.constraint.Constraint}.
<p>
Let's look at one possible implementation of a constraint - an IntegerConstraint(there are also several simple examples in the tests).
The IntegerConstraint checks to make sure that the value is a String-encoded <code>int</code>.
It is really simple to implement this kind of constraint, the only method needs to be implemented is
{@link org.apache.hadoop.hbase.constraint.Constraint#check(org.apache.hadoop.hbase.client.Put)}:
<div style="background-color: #cccccc; padding: 2px">
<blockquote><pre>
* <pre>
public class IntegerConstraint extends BaseConstraint {
public void check(Put p) throws ConstraintException {
@ -140,123 +151,153 @@
throw new ConstraintException("Value in Put (" + p
+ ") was not a String-encoded integer", e);
} } }
</pre></blockquote>
</div>
<p>
Note that all exceptions that you expect to be thrown must be caught and then rethrown as a
{@link org.apache.hadoop.hbase.constraint.ConstraintException}. This way, you can be sure that a
{@link org.apache.hadoop.hbase.client.Put} fails for an expected reason, rather than for any reason.
For example, an {@link java.lang.OutOfMemoryError} is probably indicative of an inherent problem in
the {@link org.apache.hadoop.hbase.constraint.Constraint}, rather than a failed {@link org.apache.hadoop.hbase.client.Put}.
<p>
If an unexpected exception is thrown (for example, any kind of uncaught {@link java.lang.RuntimeException}),
constraint-checking will be 'unloaded' from the regionserver where that error occurred.
This means no further {@link org.apache.hadoop.hbase.constraint.Constraint Constraints} will be checked on that server
until it is reloaded. This is done to ensure the system remains as available as possible.
Therefore, be careful when writing your own Constraint.
<p>
So now that we have a Constraint, we want to add it to a table. It's as easy as:
<div style="background-color: #cccccc; padding: 2px">
<blockquote><pre>
HTableDescriptor desc = new HTableDescriptor(TABLE_NAME);
* </pre>
*
* </blockquote> </div>
* <p/>
* Note that all exceptions that you expect to be thrown must be caught and then rethrown as a
* {@link org.apache.hadoop.hbase.constraint.ConstraintException}. This way, you can be sure that a
* {@link org.apache.hadoop.hbase.client.Put} fails for an expected reason, rather than for any
* reason. For example, an {@link java.lang.OutOfMemoryError} is probably indicative of an inherent
* problem in the {@link org.apache.hadoop.hbase.constraint.Constraint}, rather than a failed
* {@link org.apache.hadoop.hbase.client.Put}.
* <p/>
* If an unexpected exception is thrown (for example, any kind of uncaught
* {@link java.lang.RuntimeException}), constraint-checking will be 'unloaded' from the regionserver
* where that error occurred. This means no further
* {@link org.apache.hadoop.hbase.constraint.Constraint Constraints} will be checked on that server
* until it is reloaded. This is done to ensure the system remains as available as possible.
* Therefore, be careful when writing your own Constraint.
* <p/>
* So now that we have a Constraint, we want to add it to a table. It's as easy as:
* <div style="background-color: #cccccc; padding: 2px"> <blockquote>
*
* <pre>
TableDescriptor builder = TableDescriptorBuilder.newBuilder(TABLE_NAME);
...
Constraints.add(desc, IntegerConstraint.class);
</pre></blockquote></div>
<p>
Once we added the IntegerConstraint, constraints will be enabled on the table (once it is created) and
we will always check to make sure that the value is an String-encoded integer.
<p>
However, suppose we also write our own constraint, <code>MyConstraint.java</code>.
First, you need to make sure this class-files are in the classpath (in a jar) on the regionserver where
that constraint will be run (this could require a rolling restart on the region server - see <a href="#caveats">Caveats</a> above)
<p>
Suppose that MyConstraint also uses a Configuration (see {@link org.apache.hadoop.hbase.constraint.Constraint#getConf()}).
Then adding MyConstraint looks like this:
<div style="background-color: #cccccc; padding: 2px">
<blockquote><pre>
HTableDescriptor desc = new HTableDescriptor(TABLE_NAME);
Constraints.add(builder, IntegerConstraint.class);
* </pre>
*
* </blockquote></div>
* <p/>
* Once we added the IntegerConstraint, constraints will be enabled on the table (once it is
* created) and we will always check to make sure that the value is an String-encoded integer.
* <p/>
* However, suppose we also write our own constraint, <code>MyConstraint.java</code>. First, you
* need to make sure this class-files are in the classpath (in a jar) on the regionserver where that
* constraint will be run (this could require a rolling restart on the region server - see
* <a href="#caveats">Caveats</a> above)
* <p/>
* Suppose that MyConstraint also uses a Configuration (see
* {@link org.apache.hadoop.hbase.constraint.Constraint#getConf()}). Then adding MyConstraint looks
* like this: <div style="background-color: #cccccc; padding: 2px"> <blockquote>
*
* <pre>
TableDescriptor builder = TableDescriptorBuilder.newBuilder(TABLE_NAME);
Configuration conf = new Configuration(false);
...
(add values to the conf)
(modify the table descriptor)
...
Constraints.add(desc, new Pair(MyConstraint.class, conf));
</pre></blockquote></div>
<p>
At this point we added both the IntegerConstraint and MyConstraint to the table, the IntegerConstraint
<i>will be run first</i>, followed by MyConstraint.
<p>
Suppose we realize that the {@link org.apache.hadoop.conf.Configuration} for MyConstraint is actually wrong
when it was added to the table. Note, when it is added to the table, it is <i>not</i> added by reference,
but is instead copied into the {@link org.apache.hadoop.hbase.HTableDescriptor}.
Thus, to change the {@link org.apache.hadoop.conf.Configuration} we are using for MyConstraint, we need to do this:
<div style="background-color: #cccccc; padding: 2px">
<blockquote><pre>
Constraints.add(builder, new Pair(MyConstraint.class, conf));
* </pre>
*
* </blockquote></div>
* <p/>
* At this point we added both the IntegerConstraint and MyConstraint to the table, the
* IntegerConstraint <i>will be run first</i>, followed by MyConstraint.
* <p/>
* Suppose we realize that the {@link org.apache.hadoop.conf.Configuration} for MyConstraint is
* actually wrong when it was added to the table. Note, when it is added to the table, it is
* <i>not</i> added by reference, but is instead copied into the
* {@link org.apache.hadoop.hbase.client.TableDescriptor}. Thus, to change the
* {@link org.apache.hadoop.conf.Configuration} we are using for MyConstraint, we need to do this:
* <div style="background-color: #cccccc; padding: 2px"> <blockquote>
*
* <pre>
(add/modify the conf)
...
Constraints.setConfiguration(desc, MyConstraint.class, conf);
</pre></blockquote></div>
<p>
This will overwrite the previous configuration for MyConstraint, but <i>not</i> change the order of the
constraint nor if it is enabled/disabled.
<p>
Note that the same constraint class can be added multiple times to a table without repercussion.
A use case for this is the same constraint working differently based on its configuration.
<p>
Suppose then we want to disable <i>just</i> MyConstraint. Its as easy as:
<div style="background-color: #cccccc">
<blockquote><pre>
Constraints.disable(desc, MyConstraint.class);
</pre></blockquote></div>
<p>
This just turns off MyConstraint, but retains the position and the configuration associated with MyConstraint.
Now, if we want to re-enable the constraint, its just another one-liner:
<div style="background-color: #cccccc">
<blockquote><pre>
Constraints.enable(desc, MyConstraint.class);
</pre></blockquote></div>
<p>
Similarly, constraints on the entire table are disabled via:
<div style="background-color: #cccccc">
<blockquote><pre>
Constraints.disable(desc);
</pre></blockquote></div>
<p>
Or enabled via:
<div style="background-color: #cccccc">
<blockquote><pre>
Constraints.enable(desc);
</pre></blockquote></div>
<p>
Lastly, suppose you want to remove MyConstraint from the table, including with position it should be run at and its configuration.
This is similarly simple:
<div style="background-color: #cccccc">
<blockquote><pre>
Constraints.remove(desc, MyConstraint.class);
</pre></blockquote></div>
<p>
Also, removing <i>all</i> constraints from a table is similarly simple:
<div style="background-color: #cccccc">
<blockquote><pre>
Constraints.remove(desc);
</pre></blockquote></div>
This will remove all constraints (and associated information) from the table and turn off the constraint processing.
<p><b>NOTE</b><p>
It is important to note the use above of
<div style="background-color: #cccccc">
<blockquote><pre>
Configuration conf = new Configuration(false);
</pre></blockquote></div>
If you just use <code> new Configuration()</code>, then the Configuration will be loaded with the default
properties. While in the simple case, this is not going to be an issue, it will cause pain down the road.
First, these extra properties are going to cause serious bloat in your {@link org.apache.hadoop.hbase.HTableDescriptor},
meaning you are keeping around a ton of redundant information. Second, it is going to make examining
your table in the shell, via <code>describe 'table'</code>, a huge pain as you will have to dig through
a ton of irrelevant config values to find the ones you set. In short, just do it the right way.
* </pre>
*
* </blockquote></div>
* <p/>
* This will overwrite the previous configuration for MyConstraint, but <i>not</i> change the order
* of the constraint nor if it is enabled/disabled.
* <p/>
* Note that the same constraint class can be added multiple times to a table without repercussion.
* A use case for this is the same constraint working differently based on its configuration.
* <p/>
* Suppose then we want to disable <i>just</i> MyConstraint. Its as easy as:
* <div style="background-color: #cccccc"> <blockquote>
*
* <pre>
* Constraints.disable(desc, MyConstraint.class);
* </pre>
*
* </blockquote></div>
* <p/>
* This just turns off MyConstraint, but retains the position and the configuration associated with
* MyConstraint. Now, if we want to re-enable the constraint, its just another one-liner:
* <div style="background-color: #cccccc"> <blockquote>
*
* <pre>
* Constraints.enable(desc, MyConstraint.class);
* </pre>
*
* </blockquote></div>
* <p/>
* Similarly, constraints on the entire table are disabled via:
* <div style="background-color: #cccccc"> <blockquote>
*
* <pre>
* Constraints.disable(desc);
* </pre>
*
* </blockquote></div>
* <p/>
* Or enabled via: <div style="background-color: #cccccc"> <blockquote>
*
* <pre>
* Constraints.enable(desc);
* </pre>
*
* </blockquote></div>
* <p/>
* Lastly, suppose you want to remove MyConstraint from the table, including with position it should
* be run at and its configuration. This is similarly simple:
* <div style="background-color: #cccccc"> <blockquote>
*
* <pre>
* Constraints.remove(desc, MyConstraint.class);
* </pre>
*
* </blockquote></div>
* <p/>
* Also, removing <i>all</i> constraints from a table is similarly simple:
* <div style="background-color: #cccccc"> <blockquote>
*
* <pre>
* Constraints.remove(desc);
* </pre>
*
* </blockquote></div> This will remove all constraints (and associated information) from the table
* and turn off the constraint processing.
* <p/>
* <b>NOTE</b>
* <p/>
* It is important to note the use above of <div style="background-color: #cccccc"> <blockquote>
*
* <pre>
* Configuration conf = new Configuration(false);
* </pre>
*
* </blockquote></div> If you just use <code> new Configuration()</code>, then the Configuration
* will be loaded with the default properties. While in the simple case, this is not going to be an
* issue, it will cause pain down the road. First, these extra properties are going to cause serious
* bloat in your {@link org.apache.hadoop.hbase.client.TableDescriptor}, meaning you are keeping
* around a ton of redundant information. Second, it is going to make examining your table in the
* shell, via <code>describe 'table'</code>, a huge pain as you will have to dig through a ton of
* irrelevant config values to find the ones you set. In short, just do it the right way.
*/
package org.apache.hadoop.hbase.constraint;

View File

@ -44,15 +44,14 @@ import org.slf4j.LoggerFactory;
/**
* Do the complex testing of constraints against a minicluster
*/
@Category({MiscTests.class, MediumTests.class})
@Category({ MiscTests.class, MediumTests.class })
public class TestConstraint {
@ClassRule
public static final HBaseClassTestRule CLASS_RULE =
HBaseClassTestRule.forClass(TestConstraint.class);
HBaseClassTestRule.forClass(TestConstraint.class);
private static final Logger LOG = LoggerFactory
.getLogger(TestConstraint.class);
private static final Logger LOG = LoggerFactory.getLogger(TestConstraint.class);
private static HBaseTestingUtility util;
private static final TableName tableName = TableName.valueOf("test");
@ -69,24 +68,20 @@ public class TestConstraint {
/**
* Test that we run a passing constraint
* @throws Exception
*/
@SuppressWarnings("unchecked")
@Test
public void testConstraintPasses() throws Exception {
// create the table
// it would be nice if this was also a method on the util
TableDescriptorBuilder.ModifyableTableDescriptor tableDescriptor =
new TableDescriptorBuilder.ModifyableTableDescriptor(tableName);
TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(tableName);
for (byte[] family : new byte[][]{dummy, test}) {
tableDescriptor.setColumnFamily(
new ColumnFamilyDescriptorBuilder.ModifyableColumnFamilyDescriptor(family));
for (byte[] family : new byte[][] { dummy, test }) {
builder.setColumnFamily(ColumnFamilyDescriptorBuilder.of(family));
}
// add a constraint
Constraints.add(tableDescriptor, CheckWasRunConstraint.class);
Constraints.add(builder, CheckWasRunConstraint.class);
util.getAdmin().createTable(tableDescriptor);
util.getAdmin().createTable(builder.build());
Table table = util.getConnection().getTable(tableName);
try {
// test that we don't fail on a valid put
@ -103,25 +98,20 @@ public class TestConstraint {
/**
* Test that constraints will fail properly
* @throws Exception
*/
@SuppressWarnings("unchecked")
@Test
public void testConstraintFails() throws Exception {
// create the table
// it would be nice if this was also a method on the util
TableDescriptorBuilder.ModifyableTableDescriptor tableDescriptor =
new TableDescriptorBuilder.ModifyableTableDescriptor(tableName);
for (byte[] family : new byte[][]{dummy, test}) {
tableDescriptor.setColumnFamily(
new ColumnFamilyDescriptorBuilder.ModifyableColumnFamilyDescriptor(family));
TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(tableName);
for (byte[] family : new byte[][] { dummy, test }) {
builder.setColumnFamily(ColumnFamilyDescriptorBuilder.of(family));
}
// add a constraint that is sure to fail
Constraints.add(tableDescriptor, AllFailConstraint.class);
Constraints.add(builder, AllFailConstraint.class);
util.getAdmin().createTable(tableDescriptor);
util.getAdmin().createTable(builder.build());
Table table = util.getConnection().getTable(tableName);
// test that we do fail on violation
@ -140,29 +130,25 @@ public class TestConstraint {
/**
* Check that if we just disable one constraint, then
* @throws Throwable
*/
@SuppressWarnings("unchecked")
@Test
public void testDisableConstraint() throws Throwable {
public void testDisableConstraint() throws Exception {
// create the table
TableDescriptorBuilder.ModifyableTableDescriptor tableDescriptor =
new TableDescriptorBuilder.ModifyableTableDescriptor(tableName);
TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(tableName);
// add a family to the table
for (byte[] family : new byte[][]{dummy, test}) {
tableDescriptor.setColumnFamily(
new ColumnFamilyDescriptorBuilder.ModifyableColumnFamilyDescriptor(family));
for (byte[] family : new byte[][] { dummy, test }) {
builder.setColumnFamily(ColumnFamilyDescriptorBuilder.of(family));
}
// add a constraint to make sure it others get run
Constraints.add(tableDescriptor, CheckWasRunConstraint.class);
Constraints.add(builder, CheckWasRunConstraint.class);
// Add Constraint to check
Constraints.add(tableDescriptor, AllFailConstraint.class);
Constraints.add(builder, AllFailConstraint.class);
// and then disable the failing constraint
Constraints.disableConstraint(tableDescriptor, AllFailConstraint.class);
Constraints.disableConstraint(builder, AllFailConstraint.class);
util.getAdmin().createTable(tableDescriptor);
util.getAdmin().createTable(builder.build());
Table table = util.getConnection().getTable(tableName);
try {
// test that we don't fail because its disabled
@ -178,27 +164,23 @@ public class TestConstraint {
/**
* Test that if we disable all constraints, then nothing gets run
* @throws Throwable
*/
@SuppressWarnings("unchecked")
@Test
public void testDisableConstraints() throws Throwable {
public void testDisableConstraints() throws Exception {
// create the table
TableDescriptorBuilder.ModifyableTableDescriptor tableDescriptor =
new TableDescriptorBuilder.ModifyableTableDescriptor(tableName);
TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(tableName);
// add a family to the table
for (byte[] family : new byte[][]{dummy, test}) {
tableDescriptor.setColumnFamily(
new ColumnFamilyDescriptorBuilder.ModifyableColumnFamilyDescriptor(family));
for (byte[] family : new byte[][] { dummy, test }) {
builder.setColumnFamily(ColumnFamilyDescriptorBuilder.of(family));
}
// add a constraint to check to see if is run
Constraints.add(tableDescriptor, CheckWasRunConstraint.class);
Constraints.add(builder, CheckWasRunConstraint.class);
// then disable all the constraints
Constraints.disable(tableDescriptor);
Constraints.disable(builder);
util.getAdmin().createTable(tableDescriptor);
util.getAdmin().createTable(builder.build());
Table table = util.getConnection().getTable(tableName);
try {
// test that we do fail on violation
@ -215,26 +197,23 @@ public class TestConstraint {
/**
* Check to make sure a constraint is unloaded when it fails
* @throws Exception
*/
@Test
public void testIsUnloaded() throws Exception {
// create the table
TableDescriptorBuilder.ModifyableTableDescriptor tableDescriptor =
new TableDescriptorBuilder.ModifyableTableDescriptor(tableName);
TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(tableName);
// add a family to the table
for (byte[] family : new byte[][]{dummy, test}) {
tableDescriptor.setColumnFamily(
new ColumnFamilyDescriptorBuilder.ModifyableColumnFamilyDescriptor(family));
for (byte[] family : new byte[][] { dummy, test }) {
builder.setColumnFamily(ColumnFamilyDescriptorBuilder.of(family));
}
// make sure that constraints are unloaded
Constraints.add(tableDescriptor, RuntimeFailConstraint.class);
Constraints.add(builder, RuntimeFailConstraint.class);
// add a constraint to check to see if is run
Constraints.add(tableDescriptor, CheckWasRunConstraint.class);
Constraints.add(builder, CheckWasRunConstraint.class);
CheckWasRunConstraint.wasRun = false;
util.getAdmin().createTable(tableDescriptor);
util.getAdmin().createTable(builder.build());
Table table = util.getConnection().getTable(tableName);
// test that we do fail on violation
@ -242,9 +221,9 @@ public class TestConstraint {
byte[] qualifier = new byte[0];
put.addColumn(dummy, qualifier, Bytes.toBytes("pass"));
try{
table.put(put);
fail("RuntimeFailConstraint wasn't triggered - this put shouldn't work!");
try {
table.put(put);
fail("RuntimeFailConstraint wasn't triggered - this put shouldn't work!");
} catch (Exception e) {// NOOP
}

View File

@ -25,9 +25,9 @@ import static org.junit.Assert.fail;
import java.util.List;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseClassTestRule;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.TableNameTestRule;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
import org.apache.hadoop.hbase.constraint.TestConstraint.CheckWasRunConstraint;
import org.apache.hadoop.hbase.constraint.WorksConstraint.NameConstraint;
import org.apache.hadoop.hbase.testclassification.MiscTests;
@ -37,38 +37,35 @@ import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.rules.TestName;
/**
* Test reading/writing the constraints into the {@link HTableDescriptor}
* Test reading/writing the constraints into the {@link TableDescriptorBuilder}.
*/
@Category({MiscTests.class, SmallTests.class})
@Category({ MiscTests.class, SmallTests.class })
public class TestConstraints {
@ClassRule
public static final HBaseClassTestRule CLASS_RULE =
HBaseClassTestRule.forClass(TestConstraints.class);
HBaseClassTestRule.forClass(TestConstraints.class);
@Rule
public TestName name = new TestName();
public TableNameTestRule name = new TableNameTestRule();
@SuppressWarnings("unchecked")
@Test
public void testSimpleReadWrite() throws Throwable {
HTableDescriptor desc = new HTableDescriptor(TableName.valueOf(name.getMethodName()));
Constraints.add(desc, WorksConstraint.class);
public void testSimpleReadWrite() throws Exception {
TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(name.getTableName());
Constraints.add(builder, WorksConstraint.class);
List<? extends Constraint> constraints = Constraints.getConstraints(desc,
this.getClass().getClassLoader());
List<? extends Constraint> constraints =
Constraints.getConstraints(builder.build(), this.getClass().getClassLoader());
assertEquals(1, constraints.size());
assertEquals(WorksConstraint.class, constraints.get(0).getClass());
// Check that we can add more than 1 constraint and that ordering is
// preserved
Constraints.add(desc, AlsoWorks.class, NameConstraint.class);
constraints = Constraints.getConstraints(desc, this.getClass()
.getClassLoader());
Constraints.add(builder, AlsoWorks.class, NameConstraint.class);
constraints = Constraints.getConstraints(builder.build(), this.getClass().getClassLoader());
assertEquals(3, constraints.size());
assertEquals(WorksConstraint.class, constraints.get(0).getClass());
@ -77,26 +74,24 @@ public class TestConstraints {
}
@SuppressWarnings("unchecked")
@Test
public void testReadWriteWithConf() throws Throwable {
HTableDescriptor desc = new HTableDescriptor(TableName.valueOf(name.getMethodName()));
Constraints.add(desc,
new Pair<>(CheckConfigurationConstraint.class,
CheckConfigurationConstraint.getConfiguration()));
public void testReadWriteWithConf() throws Exception {
TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(name.getTableName());
Constraints.add(builder, new Pair<>(CheckConfigurationConstraint.class,
CheckConfigurationConstraint.getConfiguration()));
List<? extends Constraint> c = Constraints.getConstraints(desc, this
.getClass().getClassLoader());
List<? extends Constraint> c =
Constraints.getConstraints(builder.build(), this.getClass().getClassLoader());
assertEquals(1, c.size());
assertEquals(CheckConfigurationConstraint.class, c.get(0).getClass());
// check to make sure that we overwrite configurations
Constraints.add(desc, new Pair<>(
CheckConfigurationConstraint.class, new Configuration(false)));
Constraints.add(builder,
new Pair<>(CheckConfigurationConstraint.class, new Configuration(false)));
try {
Constraints.getConstraints(desc, this.getClass().getClassLoader());
Constraints.getConstraints(builder.build(), this.getClass().getClassLoader());
fail("No exception thrown - configuration not overwritten");
} catch (IllegalArgumentException e) {
// expect to have the exception, so don't do anything
@ -105,100 +100,89 @@ public class TestConstraints {
/**
* Test that Constraints are properly enabled, disabled, and removed
*
* @throws Exception
*/
@SuppressWarnings("unchecked")
@Test
public void testEnableDisableRemove() throws Exception {
HTableDescriptor desc = new HTableDescriptor(TableName.valueOf(name.getMethodName()));
TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(name.getTableName());
// check general enabling/disabling of constraints
// first add a constraint
Constraints.add(desc, AllPassConstraint.class);
Constraints.add(builder, AllPassConstraint.class);
// make sure everything is enabled
assertTrue(Constraints.enabled(desc, AllPassConstraint.class));
assertTrue(desc.hasCoprocessor(ConstraintProcessor.class.getName()));
assertTrue(Constraints.enabled(builder.build(), AllPassConstraint.class));
assertTrue(builder.hasCoprocessor(ConstraintProcessor.class.getName()));
// check disabling
Constraints.disable(desc);
assertFalse(desc.hasCoprocessor(ConstraintProcessor.class.getName()));
Constraints.disable(builder);
assertFalse(builder.hasCoprocessor(ConstraintProcessor.class.getName()));
// make sure the added constraints are still present
assertTrue(Constraints.enabled(desc, AllPassConstraint.class));
assertTrue(Constraints.enabled(builder.build(), AllPassConstraint.class));
// check just removing the single constraint
Constraints.remove(desc, AllPassConstraint.class);
assertFalse(Constraints.has(desc, AllPassConstraint.class));
Constraints.remove(builder, AllPassConstraint.class);
assertFalse(Constraints.has(builder.build(), AllPassConstraint.class));
// Add back the single constraint
Constraints.add(desc, AllPassConstraint.class);
Constraints.add(builder, AllPassConstraint.class);
// and now check that when we remove constraints, all are gone
Constraints.remove(desc);
assertFalse(desc.hasCoprocessor(ConstraintProcessor.class.getName()));
assertFalse(Constraints.has(desc, AllPassConstraint.class));
Constraints.remove(builder);
assertFalse(builder.hasCoprocessor(ConstraintProcessor.class.getName()));
assertFalse(Constraints.has(builder.build(), AllPassConstraint.class));
}
/**
* Test that when we update a constraint the ordering is not modified.
*
* @throws Exception
*/
@SuppressWarnings("unchecked")
@Test
public void testUpdateConstraint() throws Exception {
HTableDescriptor desc = new HTableDescriptor(TableName.valueOf(name.getMethodName()));
Constraints.add(desc, CheckConfigurationConstraint.class,
CheckWasRunConstraint.class);
Constraints.setConfiguration(desc, CheckConfigurationConstraint.class,
CheckConfigurationConstraint.getConfiguration());
TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(name.getTableName());
Constraints.add(builder, CheckConfigurationConstraint.class, CheckWasRunConstraint.class);
Constraints.setConfiguration(builder, CheckConfigurationConstraint.class,
CheckConfigurationConstraint.getConfiguration());
List<? extends Constraint> constraints = Constraints.getConstraints(desc,
this.getClass().getClassLoader());
List<? extends Constraint> constraints =
Constraints.getConstraints(builder.build(), this.getClass().getClassLoader());
assertEquals(2, constraints.size());
// check to make sure the order didn't change
assertEquals(CheckConfigurationConstraint.class, constraints.get(0)
.getClass());
assertEquals(CheckConfigurationConstraint.class, constraints.get(0).getClass());
assertEquals(CheckWasRunConstraint.class, constraints.get(1).getClass());
}
/**
* Test that if a constraint hasn't been set that there are no problems with
* attempting to remove it.
*
* @throws Throwable
* on failure.
* Test that if a constraint hasn't been set that there are no problems with attempting to remove
* it.
*/
@Test
public void testRemoveUnsetConstraint() throws Throwable {
HTableDescriptor desc = new HTableDescriptor(TableName.valueOf(name.getMethodName()));
Constraints.remove(desc);
Constraints.remove(desc, AlsoWorks.class);
public void testRemoveUnsetConstraint() throws Exception {
TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(name.getTableName());
Constraints.remove(builder);
Constraints.remove(builder, AlsoWorks.class);
}
@Test
public void testConfigurationPreserved() throws Throwable {
public void testConfigurationPreserved() throws Exception {
Configuration conf = new Configuration();
conf.setBoolean("_ENABLED", false);
conf.setLong("_PRIORITY", 10);
HTableDescriptor desc = new HTableDescriptor(TableName.valueOf(name.getMethodName()));
Constraints.add(desc, AlsoWorks.class, conf);
Constraints.add(desc, WorksConstraint.class);
assertFalse(Constraints.enabled(desc, AlsoWorks.class));
List<? extends Constraint> constraints = Constraints.getConstraints(desc,
this.getClass().getClassLoader());
TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(name.getTableName());
Constraints.add(builder, AlsoWorks.class, conf);
Constraints.add(builder, WorksConstraint.class);
assertFalse(Constraints.enabled(builder.build(), AlsoWorks.class));
List<? extends Constraint> constraints =
Constraints.getConstraints(builder.build(), this.getClass().getClassLoader());
for (Constraint c : constraints) {
Configuration storedConf = c.getConf();
if (c instanceof AlsoWorks)
if (c instanceof AlsoWorks) {
assertEquals(10, storedConf.getLong("_PRIORITY", -1));
}
// its just a worksconstraint
else
else {
assertEquals(2, storedConf.getLong("_PRIORITY", -1));
}
}
}
// ---------- Constraints just used for testing