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; 032 033import org.apache.yetus.audience.InterfaceAudience; 034import org.slf4j.Logger; 035import org.slf4j.LoggerFactory; 036import org.apache.hadoop.conf.Configuration; 037import org.apache.hadoop.hbase.HTableDescriptor; 038import org.apache.hadoop.hbase.client.TableDescriptor; 039import org.apache.hadoop.hbase.util.Bytes; 040import org.apache.hadoop.hbase.util.Pair; 041 042/** 043 * Utilities for adding/removing constraints from a table. 044 * <p> 045 * Constraints can be added on table load time, via the {@link HTableDescriptor}. 046 * <p> 047 * NOTE: this class is NOT thread safe. Concurrent setting/enabling/disabling of 048 * constraints can cause constraints to be run at incorrect times or not at all. 049 */ 050@InterfaceAudience.Private 051public final class Constraints { 052 private static final int DEFAULT_PRIORITY = -1; 053 054 private Constraints() { 055 } 056 057 private static final Logger LOG = LoggerFactory.getLogger(Constraints.class); 058 private static final String CONSTRAINT_HTD_KEY_PREFIX = "constraint $"; 059 private static final Pattern CONSTRAINT_HTD_ATTR_KEY_PATTERN = Pattern 060 .compile(CONSTRAINT_HTD_KEY_PREFIX, Pattern.LITERAL); 061 062 // configuration key for if the constraint is enabled 063 private static final String ENABLED_KEY = "_ENABLED"; 064 // configuration key for the priority 065 private static final String PRIORITY_KEY = "_PRIORITY"; 066 067 // smallest priority a constraiNt can have 068 private static final long MIN_PRIORITY = 0L; 069 // ensure a priority less than the smallest we could intentionally set 070 private static final long UNSET_PRIORITY = MIN_PRIORITY - 1; 071 072 private static String COUNTER_KEY = "hbase.constraint.counter"; 073 074 /** 075 * Enable constraints on a table. 076 * <p> 077 * Currently, if you attempt to add a constraint to the table, then 078 * Constraints will automatically be turned on. 079 * 080 * @param desc 081 * table description to add the processor 082 * @throws IOException 083 * If the {@link ConstraintProcessor} CP couldn't be added to the 084 * table. 085 */ 086 public static void enable(HTableDescriptor desc) throws IOException { 087 // if the CP has already been loaded, do nothing 088 String clazz = ConstraintProcessor.class.getName(); 089 if (desc.hasCoprocessor(clazz)) { 090 return; 091 } 092 093 // add the constrain processor CP to the table 094 desc.addCoprocessor(clazz); 095 } 096 097 /** 098 * Turn off processing constraints for a given table, even if constraints have 099 * been turned on or added. 100 * 101 * @param desc 102 * {@link HTableDescriptor} where to disable {@link Constraint 103 * Constraints}. 104 */ 105 public static void disable(HTableDescriptor desc) { 106 desc.removeCoprocessor(ConstraintProcessor.class.getName()); 107 } 108 109 /** 110 * Remove all {@link Constraint Constraints} that have been added to the table 111 * and turn off the constraint processing. 112 * <p> 113 * All {@link Configuration Configurations} and their associated 114 * {@link Constraint} are removed. 115 * 116 * @param desc 117 * {@link HTableDescriptor} to remove {@link Constraint Constraints} 118 * from. 119 */ 120 public static void remove(HTableDescriptor desc) { 121 // disable constraints 122 disable(desc); 123 124 // remove all the constraint settings 125 List<Bytes> keys = new ArrayList<>(); 126 // loop through all the key, values looking for constraints 127 for (Map.Entry<Bytes, Bytes> e : desc 128 .getValues().entrySet()) { 129 String key = Bytes.toString((e.getKey().get())); 130 String[] className = CONSTRAINT_HTD_ATTR_KEY_PATTERN.split(key); 131 if (className.length == 2) { 132 keys.add(e.getKey()); 133 } 134 } 135 // now remove all the keys we found 136 for (Bytes key : keys) { 137 desc.remove(key); 138 } 139 } 140 141 /** 142 * Check to see if the Constraint is currently set. 143 * 144 * @param desc 145 * {@link HTableDescriptor} to check 146 * @param clazz 147 * {@link Constraint} class to check for. 148 * @return <tt>true</tt> if the {@link Constraint} is present, even if it is 149 * disabled. <tt>false</tt> otherwise. 150 */ 151 public static boolean has(HTableDescriptor desc, 152 Class<? extends Constraint> clazz) { 153 return getKeyValueForClass(desc, clazz) != null; 154 } 155 156 /** 157 * Get the kv {@link Entry} in the descriptor for the specified class 158 * 159 * @param desc {@link HTableDescriptor} to read 160 * @param clazz To search for 161 * @return The {@link Pair} of {@literal <key, value>} in the table, if that class is 162 * present. {@code NULL} 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<>(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(TableDescriptor desc, 561 ClassLoader classloader) throws IOException { 562 List<Constraint> constraints = new ArrayList<>(); 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.getDeclaredConstructor().newInstance(); 598 constraint.setConf(conf); 599 constraints.add(constraint); 600 } catch (InvocationTargetException | NoSuchMethodException | ClassNotFoundException | 601 InstantiationException | IllegalAccessException e1) { 602 throw new IOException(e1); 603 } 604 } 605 } 606 // sort them, based on the priorities 607 Collections.sort(constraints, constraintComparator); 608 return constraints; 609 } 610 611 private static final Comparator<Constraint> constraintComparator = new Comparator<Constraint>() { 612 @Override 613 public int compare(Constraint c1, Constraint c2) { 614 // compare the priorities of the constraints stored in their configuration 615 return Long.compare(c1.getConf().getLong(PRIORITY_KEY, DEFAULT_PRIORITY), 616 c2.getConf().getLong(PRIORITY_KEY, DEFAULT_PRIORITY)); 617 } 618 }; 619 620}