View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.constraint;
19  
20  import java.io.ByteArrayInputStream;
21  import java.io.ByteArrayOutputStream;
22  import java.io.DataOutputStream;
23  import java.io.IOException;
24  import java.util.ArrayList;
25  import java.util.Collections;
26  import java.util.Comparator;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Map.Entry;
30  import java.util.regex.Pattern;
31  
32  import org.apache.commons.logging.Log;
33  import org.apache.commons.logging.LogFactory;
34  import org.apache.hadoop.classification.InterfaceAudience;
35  import org.apache.hadoop.conf.Configuration;
36  import org.apache.hadoop.hbase.HTableDescriptor;
37  import org.apache.hadoop.hbase.util.Bytes;
38  import org.apache.hadoop.hbase.util.Pair;
39  
40  /**
41   * Utilities for adding/removing constraints from a table.
42   * <p>
43   * Constraints can be added on table load time, via the {@link HTableDescriptor}.
44   * <p>
45   * NOTE: this class is NOT thread safe. Concurrent setting/enabling/disabling of
46   * constraints can cause constraints to be run at incorrect times or not at all.
47   */
48  @InterfaceAudience.Private
49  public final class Constraints {
50    private static final int DEFAULT_PRIORITY = -1;
51  
52    private Constraints() {
53    }
54  
55    private static final Log LOG = LogFactory.getLog(Constraints.class);
56    private static final String CONSTRAINT_HTD_KEY_PREFIX = "constraint $";
57    private static final Pattern CONSTRAINT_HTD_ATTR_KEY_PATTERN = Pattern
58        .compile(CONSTRAINT_HTD_KEY_PREFIX, Pattern.LITERAL);
59  
60    // configuration key for if the constraint is enabled
61    private static final String ENABLED_KEY = "_ENABLED";
62    // configuration key for the priority
63    private static final String PRIORITY_KEY = "_PRIORITY";
64  
65    // smallest priority a constraiNt can have
66    private static final long MIN_PRIORITY = 0L;
67    // ensure a priority less than the smallest we could intentionally set
68    private static final long UNSET_PRIORITY = MIN_PRIORITY - 1;
69  
70    private static String COUNTER_KEY = "hbase.constraint.counter";
71  
72    /**
73     * Enable constraints on a table.
74     * <p>
75     * Currently, if you attempt to add a constraint to the table, then
76     * Constraints will automatically be turned on.
77     * 
78     * @param desc
79     *          table description to add the processor
80     * @throws IOException
81     *           If the {@link ConstraintProcessor} CP couldn't be added to the
82     *           table.
83     */
84    public static void enable(HTableDescriptor desc) throws IOException {
85      // if the CP has already been loaded, do nothing
86      String clazz = ConstraintProcessor.class.getName();
87      if (desc.hasCoprocessor(clazz)) {
88        return;
89      }
90  
91      // add the constrain processor CP to the table
92      desc.addCoprocessor(clazz);
93    }
94  
95    /**
96     * Turn off processing constraints for a given table, even if constraints have
97     * been turned on or added.
98     * 
99     * @param desc
100    *          {@link HTableDescriptor} where to disable {@link Constraint
101    *          Constraints}.
102    */
103   public static void disable(HTableDescriptor desc) {
104     desc.removeCoprocessor(ConstraintProcessor.class.getName());
105   }
106 
107   /**
108    * Remove all {@link Constraint Constraints} that have been added to the table
109    * and turn off the constraint processing.
110    * <p>
111    * All {@link Configuration Configurations} and their associated
112    * {@link Constraint} are removed.
113    * 
114    * @param desc
115    *          {@link HTableDescriptor} to remove {@link Constraint Constraints}
116    *          from.
117    */
118   public static void remove(HTableDescriptor desc) {
119     // disable constraints
120     disable(desc);
121 
122     // remove all the constraint settings
123     List<Bytes> keys = new ArrayList<Bytes>();
124     // loop through all the key, values looking for constraints
125     for (Map.Entry<Bytes, Bytes> e : desc
126         .getValues().entrySet()) {
127       String key = Bytes.toString((e.getKey().get()));
128       String[] className = CONSTRAINT_HTD_ATTR_KEY_PATTERN.split(key);
129       if (className.length == 2) {
130         keys.add(e.getKey());
131       }
132     }
133     // now remove all the keys we found
134     for (Bytes key : keys) {
135       desc.remove(key);
136     }
137   }
138 
139   /**
140    * Check to see if the Constraint is currently set.
141    * 
142    * @param desc
143    *          {@link HTableDescriptor} to check
144    * @param clazz
145    *          {@link Constraint} class to check for.
146    * @return <tt>true</tt> if the {@link Constraint} is present, even if it is
147    *         disabled. <tt>false</tt> otherwise.
148    */
149   public static boolean has(HTableDescriptor desc,
150       Class<? extends Constraint> clazz) {
151     return getKeyValueForClass(desc, clazz) != null;
152   }
153 
154   /**
155    * Get the kv {@link Entry} in the descriptor for the specified class
156    * 
157    * @param desc
158    *          {@link HTableDescriptor} to read
159    * @param clazz
160    *          to search for
161    * @return the {@link Pair} of <key, value> in the table, if that class is
162    *         present. <tt>null</tt> otherwise.
163    */
164   private static Pair<String, String> getKeyValueForClass(
165       HTableDescriptor desc, Class<? extends Constraint> clazz) {
166     // get the serialized version of the constraint
167     String key = serializeConstraintClass(clazz);
168     String value = desc.getValue(key);
169 
170     return value == null ? null : new Pair<String, String>(key, value);
171   }
172 
173   /**
174    * Add configuration-less constraints to the table.
175    * <p>
176    * This will overwrite any configuration associated with the previous
177    * constraint of the same class.
178    * <p>
179    * Each constraint, when added to the table, will have a specific priority,
180    * dictating the order in which the {@link Constraint} will be run. A
181    * {@link Constraint} earlier in the list will be run before those later in
182    * the list. The same logic applies between two Constraints over time (earlier
183    * added is run first on the regionserver).
184    * 
185    * @param desc
186    *          {@link HTableDescriptor} to add {@link Constraint Constraints}
187    * @param constraints
188    *          {@link Constraint Constraints} to add. All constraints are
189    *          considered automatically enabled on add
190    * @throws IOException
191    *           If constraint could not be serialized/added to table
192    */
193   public static void add(HTableDescriptor desc,
194       Class<? extends Constraint>... constraints) throws IOException {
195     // make sure constraints are enabled
196     enable(desc);
197     long priority = getNextPriority(desc);
198 
199     // store each constraint
200     for (Class<? extends Constraint> clazz : constraints) {
201       addConstraint(desc, clazz, null, priority++);
202     }
203     updateLatestPriority(desc, priority);
204   }
205 
206   /**
207    * Add constraints and their associated configurations to the table.
208    * <p>
209    * Adding the same constraint class twice will overwrite the first
210    * constraint's configuration
211    * <p>
212    * Each constraint, when added to the table, will have a specific priority,
213    * dictating the order in which the {@link Constraint} will be run. A
214    * {@link Constraint} earlier in the list will be run before those later in
215    * the list. The same logic applies between two Constraints over time (earlier
216    * added is run first on the regionserver).
217    * 
218    * @param desc
219    *          {@link HTableDescriptor} to add a {@link Constraint}
220    * @param constraints
221    *          {@link Pair} of a {@link Constraint} and its associated
222    *          {@link Configuration}. The Constraint will be configured on load
223    *          with the specified configuration.All constraints are considered
224    *          automatically enabled on add
225    * @throws IOException
226    *           if any constraint could not be deserialized. Assumes if 1
227    *           constraint is not loaded properly, something has gone terribly
228    *           wrong and that all constraints need to be enforced.
229    */
230   public static void add(HTableDescriptor desc,
231       Pair<Class<? extends Constraint>, Configuration>... constraints)
232       throws IOException {
233     enable(desc);
234     long priority = getNextPriority(desc);
235     for (Pair<Class<? extends Constraint>, Configuration> pair : constraints) {
236       addConstraint(desc, pair.getFirst(), pair.getSecond(), priority++);
237     }
238     updateLatestPriority(desc, priority);
239   }
240 
241   /**
242    * Add a {@link Constraint} to the table with the given configuration
243    * <p>
244    * Each constraint, when added to the table, will have a specific priority,
245    * dictating the order in which the {@link Constraint} will be run. A
246    * {@link Constraint} added will run on the regionserver before those added to
247    * the {@link HTableDescriptor} later.
248    * 
249    * @param desc
250    *          table descriptor to the constraint to
251    * @param constraint
252    *          to be added
253    * @param conf
254    *          configuration associated with the constraint
255    * @throws IOException
256    *           if any constraint could not be deserialized. Assumes if 1
257    *           constraint is not loaded properly, something has gone terribly
258    *           wrong and that all constraints need to be enforced.
259    */
260   public static void add(HTableDescriptor desc,
261       Class<? extends Constraint> constraint, Configuration conf)
262       throws IOException {
263     enable(desc);
264     long priority = getNextPriority(desc);
265     addConstraint(desc, constraint, conf, priority++);
266 
267     updateLatestPriority(desc, priority);
268   }
269 
270   /**
271    * Write the raw constraint and configuration to the descriptor.
272    * <p>
273    * This method takes care of creating a new configuration based on the passed
274    * in configuration and then updating that with enabled and priority of the
275    * constraint.
276    * <p>
277    * When a constraint is added, it is automatically enabled.
278    */
279   private static void addConstraint(HTableDescriptor desc,
280       Class<? extends Constraint> clazz, Configuration conf, long priority)
281       throws IOException {
282     writeConstraint(desc, serializeConstraintClass(clazz),
283         configure(conf, true, priority));
284   }
285 
286   /**
287    * Setup the configuration for a constraint as to whether it is enabled and
288    * its priority
289    * 
290    * @param conf
291    *          on which to base the new configuration
292    * @param enabled
293    *          <tt>true</tt> if it should be run
294    * @param priority
295    *          relative to other constraints
296    * @return a new configuration, storable in the {@link HTableDescriptor}
297    */
298   private static Configuration configure(Configuration conf, boolean enabled,
299       long priority) {
300     // create the configuration to actually be stored
301     // clone if possible, but otherwise just create an empty configuration
302     Configuration toWrite = conf == null ? new Configuration()
303         : new Configuration(conf);
304 
305     // update internal properties
306     toWrite.setBooleanIfUnset(ENABLED_KEY, enabled);
307 
308     // set if unset long
309     if (toWrite.getLong(PRIORITY_KEY, UNSET_PRIORITY) == UNSET_PRIORITY) {
310       toWrite.setLong(PRIORITY_KEY, priority);
311     }
312 
313     return toWrite;
314   }
315 
316   /**
317    * Just write the class to a String representation of the class as a key for
318    * the {@link HTableDescriptor}
319    * 
320    * @param clazz
321    *          Constraint class to convert to a {@link HTableDescriptor} key
322    * @return key to store in the {@link HTableDescriptor}
323    */
324   private static String serializeConstraintClass(
325       Class<? extends Constraint> clazz) {
326     String constraintClazz = clazz.getName();
327     return CONSTRAINT_HTD_KEY_PREFIX + constraintClazz;
328   }
329 
330   /**
331    * Write the given key and associated configuration to the
332    * {@link HTableDescriptor}
333    */
334   private static void writeConstraint(HTableDescriptor desc, String key,
335       Configuration conf) throws IOException {
336     // store the key and conf in the descriptor
337     desc.setValue(key, serializeConfiguration(conf));
338   }
339 
340   /**
341    * Write the configuration to a String
342    * 
343    * @param conf
344    *          to write
345    * @return String representation of that configuration
346    * @throws IOException
347    */
348   private static String serializeConfiguration(Configuration conf)
349       throws IOException {
350     // write the configuration out to the data stream
351     ByteArrayOutputStream bos = new ByteArrayOutputStream();
352     DataOutputStream dos = new DataOutputStream(bos);
353     conf.writeXml(dos);
354     dos.flush();
355     byte[] data = bos.toByteArray();
356     return Bytes.toString(data);
357   }
358 
359   /**
360    * Read the {@link Configuration} stored in the byte stream.
361    * 
362    * @param bytes
363    *          to read from
364    * @return A valid configuration
365    */
366   private static Configuration readConfiguration(byte[] bytes)
367       throws IOException {
368     ByteArrayInputStream is = new ByteArrayInputStream(bytes);
369     Configuration conf = new Configuration(false);
370     conf.addResource(is);
371     return conf;
372   }
373 
374   /**
375    * Read in the configuration from the String encoded configuration
376    * 
377    * @param bytes
378    *          to read from
379    * @return A valid configuration
380    * @throws IOException
381    *           if the configuration could not be read
382    */
383   private static Configuration readConfiguration(String bytes)
384       throws IOException {
385     return readConfiguration(Bytes.toBytes(bytes));
386   }
387 
388   private static long getNextPriority(HTableDescriptor desc) {
389     String value = desc.getValue(COUNTER_KEY);
390 
391     long priority;
392     // get the current priority
393     if (value == null) {
394       priority = MIN_PRIORITY;
395     } else {
396       priority = Long.parseLong(value) + 1;
397     }
398 
399     return priority;
400   }
401 
402   private static void updateLatestPriority(HTableDescriptor desc, long priority) {
403     // update the max priority
404     desc.setValue(COUNTER_KEY, Long.toString(priority));
405   }
406 
407   /**
408    * Update the configuration for the {@link Constraint}; does not change the
409    * order in which the constraint is run.
410    * 
411    * @param desc
412    *          {@link HTableDescriptor} to update
413    * @param clazz
414    *          {@link Constraint} to update
415    * @param configuration
416    *          to update the {@link Constraint} with.
417    * @throws IOException
418    *           if the Constraint was not stored correctly
419    * @throws IllegalArgumentException
420    *           if the Constraint was not present on this table.
421    */
422   public static void setConfiguration(HTableDescriptor desc,
423       Class<? extends Constraint> clazz, Configuration configuration)
424       throws IOException, IllegalArgumentException {
425     // get the entry for this class
426     Pair<String, String> e = getKeyValueForClass(desc, clazz);
427 
428     if (e == null) {
429       throw new IllegalArgumentException("Constraint: " + clazz.getName()
430           + " is not associated with this table.");
431     }
432 
433     // clone over the configuration elements
434     Configuration conf = new Configuration(configuration);
435 
436     // read in the previous info about the constraint
437     Configuration internal = readConfiguration(e.getSecond());
438 
439     // update the fields based on the previous settings
440     conf.setIfUnset(ENABLED_KEY, internal.get(ENABLED_KEY));
441     conf.setIfUnset(PRIORITY_KEY, internal.get(PRIORITY_KEY));
442 
443     // update the current value
444     writeConstraint(desc, e.getFirst(), conf);
445   }
446 
447   /**
448    * Remove the constraint (and associated information) for the table
449    * descriptor.
450    * 
451    * @param desc
452    *          {@link HTableDescriptor} to modify
453    * @param clazz
454    *          {@link Constraint} class to remove
455    */
456   public static void remove(HTableDescriptor desc,
457       Class<? extends Constraint> clazz) {
458     String key = serializeConstraintClass(clazz);
459     desc.remove(key);
460   }
461 
462   /**
463    * Enable the given {@link Constraint}. Retains all the information (e.g.
464    * Configuration) for the {@link Constraint}, but makes sure that it gets
465    * loaded on the table.
466    * 
467    * @param desc
468    *          {@link HTableDescriptor} to modify
469    * @param clazz
470    *          {@link Constraint} to enable
471    * @throws IOException
472    *           If the constraint cannot be properly deserialized
473    */
474   public static void enableConstraint(HTableDescriptor desc,
475       Class<? extends Constraint> clazz) throws IOException {
476     changeConstraintEnabled(desc, clazz, true);
477   }
478 
479   /**
480    * Disable the given {@link Constraint}. Retains all the information (e.g.
481    * Configuration) for the {@link Constraint}, but it just doesn't load the
482    * {@link Constraint} on the table.
483    * 
484    * @param desc
485    *          {@link HTableDescriptor} to modify
486    * @param clazz
487    *          {@link Constraint} to disable.
488    * @throws IOException
489    *           if the constraint cannot be found
490    */
491   public static void disableConstraint(HTableDescriptor desc,
492       Class<? extends Constraint> clazz) throws IOException {
493     changeConstraintEnabled(desc, clazz, false);
494   }
495 
496   /**
497    * Change the whether the constraint (if it is already present) is enabled or
498    * disabled.
499    */
500   private static void changeConstraintEnabled(HTableDescriptor desc,
501       Class<? extends Constraint> clazz, boolean enabled) throws IOException {
502     // get the original constraint
503     Pair<String, String> entry = getKeyValueForClass(desc, clazz);
504     if (entry == null) {
505       throw new IllegalArgumentException("Constraint: " + clazz.getName()
506           + " is not associated with this table. You can't enable it!");
507     }
508 
509     // create a new configuration from that conf
510     Configuration conf = readConfiguration(entry.getSecond());
511 
512     // set that it is enabled
513     conf.setBoolean(ENABLED_KEY, enabled);
514 
515     // write it back out
516     writeConstraint(desc, entry.getFirst(), conf);
517   }
518 
519   /**
520    * Check to see if the given constraint is enabled.
521    * 
522    * @param desc
523    *          {@link HTableDescriptor} to check.
524    * @param clazz
525    *          {@link Constraint} to check for
526    * @return <tt>true</tt> if the {@link Constraint} is present and enabled.
527    *         <tt>false</tt> otherwise.
528    * @throws IOException
529    *           If the constraint has improperly stored in the table
530    */
531   public static boolean enabled(HTableDescriptor desc,
532       Class<? extends Constraint> clazz) throws IOException {
533     // get the kv
534     Pair<String, String> entry = getKeyValueForClass(desc, clazz);
535     // its not enabled so just return false. In fact, its not even present!
536     if (entry == null) {
537       return false;
538     }
539 
540     // get the info about the constraint
541     Configuration conf = readConfiguration(entry.getSecond());
542 
543     return conf.getBoolean(ENABLED_KEY, false);
544   }
545 
546   /**
547    * Get the constraints stored in the table descriptor
548    * 
549    * @param desc
550    *          To read from
551    * @param classloader
552    *          To use when loading classes. If a special classloader is used on a
553    *          region, for instance, then that should be the classloader used to
554    *          load the constraints. This could also apply to unit-testing
555    *          situation, where want to ensure that class is reloaded or not.
556    * @return List of configured {@link Constraint Constraints}
557    * @throws IOException
558    *           if any part of reading/arguments fails
559    */
560   static List<? extends Constraint> getConstraints(HTableDescriptor desc,
561       ClassLoader classloader) throws IOException {
562     List<Constraint> constraints = new ArrayList<Constraint>();
563     // loop through all the key, values looking for constraints
564     for (Map.Entry<Bytes, Bytes> e : desc
565         .getValues().entrySet()) {
566       // read out the constraint
567       String key = Bytes.toString(e.getKey().get()).trim();
568       String[] className = CONSTRAINT_HTD_ATTR_KEY_PATTERN.split(key);
569       if (className.length == 2) {
570         key = className[1];
571         if (LOG.isDebugEnabled()) {
572           LOG.debug("Loading constraint:" + key);
573         }
574 
575         // read in the rest of the constraint
576         Configuration conf;
577         try {
578           conf = readConfiguration(e.getValue().get());
579         } catch (IOException e1) {
580           // long that we don't have a valid configuration stored, and move on.
581           LOG.warn("Corrupted configuration found for key:" + key
582               + ",  skipping it.");
583           continue;
584         }
585         // if it is not enabled, skip it
586         if (!conf.getBoolean(ENABLED_KEY, false)) {
587           if (LOG.isDebugEnabled())
588             LOG.debug("Constraint: " + key + " is DISABLED - skipping it");
589           // go to the next constraint
590           continue;
591         }
592 
593         try {
594           // add the constraint, now that we expect it to be valid.
595           Class<? extends Constraint> clazz = classloader.loadClass(key)
596               .asSubclass(Constraint.class);
597           Constraint constraint = clazz.newInstance();
598           constraint.setConf(conf);
599           constraints.add(constraint);
600         } catch (ClassNotFoundException e1) {
601           throw new IOException(e1);
602         } catch (InstantiationException e1) {
603           throw new IOException(e1);
604         } catch (IllegalAccessException e1) {
605           throw new IOException(e1);
606         }
607       }
608     }
609     // sort them, based on the priorities
610     Collections.sort(constraints, constraintComparator);
611     return constraints;
612   }
613 
614   private static final Comparator<Constraint> constraintComparator = new Comparator<Constraint>() {
615     @Override
616     public int compare(Constraint c1, Constraint c2) {
617       // compare the priorities of the constraints stored in their configuration
618       return Long.valueOf(c1.getConf().getLong(PRIORITY_KEY, DEFAULT_PRIORITY))
619           .compareTo(c2.getConf().getLong(PRIORITY_KEY, DEFAULT_PRIORITY));
620     }
621   };
622 
623 }