001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.constraint; 019 020import java.io.ByteArrayInputStream; 021import java.io.ByteArrayOutputStream; 022import java.io.DataOutputStream; 023import java.io.IOException; 024import java.lang.reflect.InvocationTargetException; 025import java.util.ArrayList; 026import java.util.Collections; 027import java.util.Comparator; 028import java.util.List; 029import java.util.Map; 030import java.util.Map.Entry; 031import java.util.regex.Pattern; 032import org.apache.hadoop.conf.Configuration; 033import org.apache.hadoop.hbase.HTableDescriptor; 034import org.apache.hadoop.hbase.client.TableDescriptor; 035import org.apache.hadoop.hbase.util.Bytes; 036import org.apache.hadoop.hbase.util.Pair; 037import org.apache.yetus.audience.InterfaceAudience; 038import org.slf4j.Logger; 039import org.slf4j.LoggerFactory; 040 041/** 042 * Utilities for adding/removing constraints from a table. 043 * <p> 044 * Constraints can be added on table load time, via the {@link HTableDescriptor}. 045 * <p> 046 * NOTE: this class is NOT thread safe. Concurrent setting/enabling/disabling of constraints can 047 * cause constraints to be run at incorrect times or not at all. 048 */ 049@InterfaceAudience.Private 050public final class Constraints { 051 private static final int DEFAULT_PRIORITY = -1; 052 053 private Constraints() { 054 } 055 056 private static final Logger LOG = LoggerFactory.getLogger(Constraints.class); 057 private static final String CONSTRAINT_HTD_KEY_PREFIX = "constraint $"; 058 private static final Pattern CONSTRAINT_HTD_ATTR_KEY_PATTERN = 059 Pattern.compile(CONSTRAINT_HTD_KEY_PREFIX, Pattern.LITERAL); 060 061 // configuration key for if the constraint is enabled 062 private static final String ENABLED_KEY = "_ENABLED"; 063 // configuration key for the priority 064 private static final String PRIORITY_KEY = "_PRIORITY"; 065 066 // smallest priority a constraiNt can have 067 private static final long MIN_PRIORITY = 0L; 068 // ensure a priority less than the smallest we could intentionally set 069 private static final long UNSET_PRIORITY = MIN_PRIORITY - 1; 070 071 private static String COUNTER_KEY = "hbase.constraint.counter"; 072 073 /** 074 * Enable constraints on a table. 075 * <p> 076 * Currently, if you attempt to add a constraint to the table, then Constraints will automatically 077 * be turned on. n * table description to add the processor n * If the {@link ConstraintProcessor} 078 * CP couldn't be added to the table. 079 */ 080 public static void enable(HTableDescriptor desc) throws IOException { 081 // if the CP has already been loaded, do nothing 082 String clazz = ConstraintProcessor.class.getName(); 083 if (desc.hasCoprocessor(clazz)) { 084 return; 085 } 086 087 // add the constrain processor CP to the table 088 desc.addCoprocessor(clazz); 089 } 090 091 /** 092 * Turn off processing constraints for a given table, even if constraints have been turned on or 093 * added. n * {@link HTableDescriptor} where to disable {@link Constraint Constraints}. 094 */ 095 public static void disable(HTableDescriptor desc) { 096 desc.removeCoprocessor(ConstraintProcessor.class.getName()); 097 } 098 099 /** 100 * Remove all {@link Constraint Constraints} that have been added to the table and turn off the 101 * constraint processing. 102 * <p> 103 * All {@link Configuration Configurations} and their associated {@link Constraint} are removed. n 104 * * {@link HTableDescriptor} to remove {@link Constraint Constraints} from. 105 */ 106 public static void remove(HTableDescriptor desc) { 107 // disable constraints 108 disable(desc); 109 110 // remove all the constraint settings 111 List<Bytes> keys = new ArrayList<>(); 112 // loop through all the key, values looking for constraints 113 for (Map.Entry<Bytes, Bytes> e : desc.getValues().entrySet()) { 114 String key = Bytes.toString((e.getKey().get())); 115 String[] className = CONSTRAINT_HTD_ATTR_KEY_PATTERN.split(key); 116 if (className.length == 2) { 117 keys.add(e.getKey()); 118 } 119 } 120 // now remove all the keys we found 121 for (Bytes key : keys) { 122 desc.remove(key); 123 } 124 } 125 126 /** 127 * Check to see if the Constraint is currently set. n * {@link HTableDescriptor} to check n * 128 * {@link Constraint} class to check for. 129 * @return <tt>true</tt> if the {@link Constraint} is present, even if it is disabled. 130 * <tt>false</tt> otherwise. 131 */ 132 public static boolean has(HTableDescriptor desc, Class<? extends Constraint> clazz) { 133 return getKeyValueForClass(desc, clazz) != null; 134 } 135 136 /** 137 * Get the kv {@link Entry} in the descriptor for the specified class 138 * @param desc {@link HTableDescriptor} to read 139 * @param clazz To search for 140 * @return The {@link Pair} of {@literal <key, value>} in the table, if that class is present. 141 * {@code NULL} otherwise. 142 */ 143 private static Pair<String, String> getKeyValueForClass(HTableDescriptor desc, 144 Class<? extends Constraint> clazz) { 145 // get the serialized version of the constraint 146 String key = serializeConstraintClass(clazz); 147 String value = desc.getValue(key); 148 149 return value == null ? null : new Pair<>(key, value); 150 } 151 152 /** 153 * Add configuration-less constraints to the table. 154 * <p> 155 * This will overwrite any configuration associated with the previous constraint of the same 156 * class. 157 * <p> 158 * Each constraint, when added to the table, will have a specific priority, dictating the order in 159 * which the {@link Constraint} will be run. A {@link Constraint} earlier in the list will be run 160 * before those later in the list. The same logic applies between two Constraints over time 161 * (earlier added is run first on the regionserver). n * {@link HTableDescriptor} to add 162 * {@link Constraint Constraints} n * {@link Constraint Constraints} to add. All constraints are 163 * considered automatically enabled on add n * If constraint could not be serialized/added to 164 * table 165 */ 166 public static void add(HTableDescriptor desc, Class<? extends Constraint>... constraints) 167 throws IOException { 168 // make sure constraints are enabled 169 enable(desc); 170 long priority = getNextPriority(desc); 171 172 // store each constraint 173 for (Class<? extends Constraint> clazz : constraints) { 174 addConstraint(desc, clazz, null, priority++); 175 } 176 updateLatestPriority(desc, priority); 177 } 178 179 /** 180 * Add constraints and their associated configurations to the table. 181 * <p> 182 * Adding the same constraint class twice will overwrite the first constraint's configuration 183 * <p> 184 * Each constraint, when added to the table, will have a specific priority, dictating the order in 185 * which the {@link Constraint} will be run. A {@link Constraint} earlier in the list will be run 186 * before those later in the list. The same logic applies between two Constraints over time 187 * (earlier added is run first on the regionserver). n * {@link HTableDescriptor} to add a 188 * {@link Constraint} n * {@link Pair} of a {@link Constraint} and its associated 189 * {@link Configuration}. The Constraint will be configured on load with the specified 190 * configuration.All constraints are considered automatically enabled on add n * if any constraint 191 * could not be deserialized. Assumes if 1 constraint is not loaded properly, something has gone 192 * terribly wrong and that all constraints need to be enforced. 193 */ 194 public static void add(HTableDescriptor desc, 195 Pair<Class<? extends Constraint>, Configuration>... constraints) throws IOException { 196 enable(desc); 197 long priority = getNextPriority(desc); 198 for (Pair<Class<? extends Constraint>, Configuration> pair : constraints) { 199 addConstraint(desc, pair.getFirst(), pair.getSecond(), priority++); 200 } 201 updateLatestPriority(desc, priority); 202 } 203 204 /** 205 * Add a {@link Constraint} to the table with the given configuration 206 * <p> 207 * Each constraint, when added to the table, will have a specific priority, dictating the order in 208 * which the {@link Constraint} will be run. A {@link Constraint} added will run on the 209 * regionserver before those added to the {@link HTableDescriptor} later. n * table descriptor to 210 * the constraint to n * to be added n * configuration associated with the constraint n * if any 211 * constraint could not be deserialized. Assumes if 1 constraint is not loaded properly, something 212 * has gone terribly wrong and that all constraints need to be enforced. 213 */ 214 public static void add(HTableDescriptor desc, Class<? extends Constraint> constraint, 215 Configuration conf) throws IOException { 216 enable(desc); 217 long priority = getNextPriority(desc); 218 addConstraint(desc, constraint, conf, priority++); 219 220 updateLatestPriority(desc, priority); 221 } 222 223 /** 224 * Write the raw constraint and configuration to the descriptor. 225 * <p> 226 * This method takes care of creating a new configuration based on the passed in configuration and 227 * then updating that with enabled and priority of the constraint. 228 * <p> 229 * When a constraint is added, it is automatically enabled. 230 */ 231 private static void addConstraint(HTableDescriptor desc, Class<? extends Constraint> clazz, 232 Configuration conf, long priority) throws IOException { 233 writeConstraint(desc, serializeConstraintClass(clazz), configure(conf, true, priority)); 234 } 235 236 /** 237 * Setup the configuration for a constraint as to whether it is enabled and its priority n * on 238 * which to base the new configuration n * <tt>true</tt> if it should be run n * relative to other 239 * constraints 240 * @return a new configuration, storable in the {@link HTableDescriptor} 241 */ 242 private static Configuration configure(Configuration conf, boolean enabled, long priority) { 243 // create the configuration to actually be stored 244 // clone if possible, but otherwise just create an empty configuration 245 Configuration toWrite = conf == null ? new Configuration() : new Configuration(conf); 246 247 // update internal properties 248 toWrite.setBooleanIfUnset(ENABLED_KEY, enabled); 249 250 // set if unset long 251 if (toWrite.getLong(PRIORITY_KEY, UNSET_PRIORITY) == UNSET_PRIORITY) { 252 toWrite.setLong(PRIORITY_KEY, priority); 253 } 254 255 return toWrite; 256 } 257 258 /** 259 * Just write the class to a String representation of the class as a key for the 260 * {@link HTableDescriptor} n * Constraint class to convert to a {@link HTableDescriptor} key 261 * @return key to store in the {@link HTableDescriptor} 262 */ 263 private static String serializeConstraintClass(Class<? extends Constraint> clazz) { 264 String constraintClazz = clazz.getName(); 265 return CONSTRAINT_HTD_KEY_PREFIX + constraintClazz; 266 } 267 268 /** 269 * Write the given key and associated configuration to the {@link HTableDescriptor} 270 */ 271 private static void writeConstraint(HTableDescriptor desc, String key, Configuration conf) 272 throws IOException { 273 // store the key and conf in the descriptor 274 desc.setValue(key, serializeConfiguration(conf)); 275 } 276 277 /** 278 * Write the configuration to a String n * to write 279 * @return String representation of that configuration n 280 */ 281 private static String serializeConfiguration(Configuration conf) throws IOException { 282 // write the configuration out to the data stream 283 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 284 DataOutputStream dos = new DataOutputStream(bos); 285 conf.writeXml(dos); 286 dos.flush(); 287 byte[] data = bos.toByteArray(); 288 return Bytes.toString(data); 289 } 290 291 /** 292 * Read the {@link Configuration} stored in the byte stream. n * to read from 293 * @return A valid configuration 294 */ 295 private static Configuration readConfiguration(byte[] bytes) throws IOException { 296 ByteArrayInputStream is = new ByteArrayInputStream(bytes); 297 Configuration conf = new Configuration(false); 298 conf.addResource(is); 299 return conf; 300 } 301 302 /** 303 * Read in the configuration from the String encoded configuration n * to read from 304 * @return A valid configuration n * if the configuration could not be read 305 */ 306 private static Configuration readConfiguration(String bytes) throws IOException { 307 return readConfiguration(Bytes.toBytes(bytes)); 308 } 309 310 private static long getNextPriority(HTableDescriptor desc) { 311 String value = desc.getValue(COUNTER_KEY); 312 313 long priority; 314 // get the current priority 315 if (value == null) { 316 priority = MIN_PRIORITY; 317 } else { 318 priority = Long.parseLong(value) + 1; 319 } 320 321 return priority; 322 } 323 324 private static void updateLatestPriority(HTableDescriptor desc, long priority) { 325 // update the max priority 326 desc.setValue(COUNTER_KEY, Long.toString(priority)); 327 } 328 329 /** 330 * Update the configuration for the {@link Constraint}; does not change the order in which the 331 * constraint is run. n * {@link HTableDescriptor} to update n * {@link Constraint} to update n * 332 * to update the {@link Constraint} with. n * if the Constraint was not stored correctly n * if 333 * the Constraint was not present on this table. 334 */ 335 public static void setConfiguration(HTableDescriptor desc, Class<? extends Constraint> clazz, 336 Configuration configuration) throws IOException, IllegalArgumentException { 337 // get the entry for this class 338 Pair<String, String> e = getKeyValueForClass(desc, clazz); 339 340 if (e == null) { 341 throw new IllegalArgumentException( 342 "Constraint: " + clazz.getName() + " is not associated with this table."); 343 } 344 345 // clone over the configuration elements 346 Configuration conf = new Configuration(configuration); 347 348 // read in the previous info about the constraint 349 Configuration internal = readConfiguration(e.getSecond()); 350 351 // update the fields based on the previous settings 352 conf.setIfUnset(ENABLED_KEY, internal.get(ENABLED_KEY)); 353 conf.setIfUnset(PRIORITY_KEY, internal.get(PRIORITY_KEY)); 354 355 // update the current value 356 writeConstraint(desc, e.getFirst(), conf); 357 } 358 359 /** 360 * Remove the constraint (and associated information) for the table descriptor. n * 361 * {@link HTableDescriptor} to modify n * {@link Constraint} class to remove 362 */ 363 public static void remove(HTableDescriptor desc, Class<? extends Constraint> clazz) { 364 String key = serializeConstraintClass(clazz); 365 desc.remove(key); 366 } 367 368 /** 369 * Enable the given {@link Constraint}. Retains all the information (e.g. Configuration) for the 370 * {@link Constraint}, but makes sure that it gets loaded on the table. n * 371 * {@link HTableDescriptor} to modify n * {@link Constraint} to enable n * If the constraint 372 * cannot be properly deserialized 373 */ 374 public static void enableConstraint(HTableDescriptor desc, Class<? extends Constraint> clazz) 375 throws IOException { 376 changeConstraintEnabled(desc, clazz, true); 377 } 378 379 /** 380 * Disable the given {@link Constraint}. Retains all the information (e.g. Configuration) for the 381 * {@link Constraint}, but it just doesn't load the {@link Constraint} on the table. n * 382 * {@link HTableDescriptor} to modify n * {@link Constraint} to disable. n * if the constraint 383 * cannot be found 384 */ 385 public static void disableConstraint(HTableDescriptor desc, Class<? extends Constraint> clazz) 386 throws IOException { 387 changeConstraintEnabled(desc, clazz, false); 388 } 389 390 /** 391 * Change the whether the constraint (if it is already present) is enabled or disabled. 392 */ 393 private static void changeConstraintEnabled(HTableDescriptor desc, 394 Class<? extends Constraint> clazz, boolean enabled) throws IOException { 395 // get the original constraint 396 Pair<String, String> entry = getKeyValueForClass(desc, clazz); 397 if (entry == null) { 398 throw new IllegalArgumentException("Constraint: " + clazz.getName() 399 + " is not associated with this table. You can't enable it!"); 400 } 401 402 // create a new configuration from that conf 403 Configuration conf = readConfiguration(entry.getSecond()); 404 405 // set that it is enabled 406 conf.setBoolean(ENABLED_KEY, enabled); 407 408 // write it back out 409 writeConstraint(desc, entry.getFirst(), conf); 410 } 411 412 /** 413 * Check to see if the given constraint is enabled. n * {@link HTableDescriptor} to check. n * 414 * {@link Constraint} to check for 415 * @return <tt>true</tt> if the {@link Constraint} is present and enabled. <tt>false</tt> 416 * otherwise. n * If the constraint has improperly stored in the table 417 */ 418 public static boolean enabled(HTableDescriptor desc, Class<? extends Constraint> clazz) 419 throws IOException { 420 // get the kv 421 Pair<String, String> entry = getKeyValueForClass(desc, clazz); 422 // its not enabled so just return false. In fact, its not even present! 423 if (entry == null) { 424 return false; 425 } 426 427 // get the info about the constraint 428 Configuration conf = readConfiguration(entry.getSecond()); 429 430 return conf.getBoolean(ENABLED_KEY, false); 431 } 432 433 /** 434 * Get the constraints stored in the table descriptor n * To read from n * To use when loading 435 * classes. If a special classloader is used on a region, for instance, then that should be the 436 * classloader used to load the constraints. This could also apply to unit-testing situation, 437 * where want to ensure that class is reloaded or not. 438 * @return List of configured {@link Constraint Constraints} n * if any part of reading/arguments 439 * fails 440 */ 441 static List<? extends Constraint> getConstraints(TableDescriptor desc, ClassLoader classloader) 442 throws IOException { 443 List<Constraint> constraints = new ArrayList<>(); 444 // loop through all the key, values looking for constraints 445 for (Map.Entry<Bytes, Bytes> e : desc.getValues().entrySet()) { 446 // read out the constraint 447 String key = Bytes.toString(e.getKey().get()).trim(); 448 String[] className = CONSTRAINT_HTD_ATTR_KEY_PATTERN.split(key); 449 if (className.length == 2) { 450 key = className[1]; 451 if (LOG.isDebugEnabled()) { 452 LOG.debug("Loading constraint:" + key); 453 } 454 455 // read in the rest of the constraint 456 Configuration conf; 457 try { 458 conf = readConfiguration(e.getValue().get()); 459 } catch (IOException e1) { 460 // long that we don't have a valid configuration stored, and move on. 461 LOG.warn("Corrupted configuration found for key:" + key + ", skipping it."); 462 continue; 463 } 464 // if it is not enabled, skip it 465 if (!conf.getBoolean(ENABLED_KEY, false)) { 466 if (LOG.isDebugEnabled()) LOG.debug("Constraint: " + key + " is DISABLED - skipping it"); 467 // go to the next constraint 468 continue; 469 } 470 471 try { 472 // add the constraint, now that we expect it to be valid. 473 Class<? extends Constraint> clazz = 474 classloader.loadClass(key).asSubclass(Constraint.class); 475 Constraint constraint = clazz.getDeclaredConstructor().newInstance(); 476 constraint.setConf(conf); 477 constraints.add(constraint); 478 } catch (InvocationTargetException | NoSuchMethodException | ClassNotFoundException 479 | InstantiationException | IllegalAccessException e1) { 480 throw new IOException(e1); 481 } 482 } 483 } 484 // sort them, based on the priorities 485 Collections.sort(constraints, constraintComparator); 486 return constraints; 487 } 488 489 private static final Comparator<Constraint> constraintComparator = new Comparator<Constraint>() { 490 @Override 491 public int compare(Constraint c1, Constraint c2) { 492 // compare the priorities of the constraints stored in their configuration 493 return Long.compare(c1.getConf().getLong(PRIORITY_KEY, DEFAULT_PRIORITY), 494 c2.getConf().getLong(PRIORITY_KEY, DEFAULT_PRIORITY)); 495 } 496 }; 497 498}