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.client.TableDescriptor; 034import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 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 * Since {@link TableDescriptor} is immutable now, you should use {@link TableDescriptorBuilder}. 045 * And when disabling or removing constraints, you could use 046 * {@link TableDescriptorBuilder#newBuilder(TableDescriptor)} to clone the old 047 * {@link TableDescriptor} and then pass it the below methods. 048 */ 049@InterfaceAudience.Public 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. 078 */ 079 public static TableDescriptorBuilder enable(TableDescriptorBuilder builder) throws IOException { 080 if (!builder.hasCoprocessor(ConstraintProcessor.class.getName())) { 081 builder.setCoprocessor(ConstraintProcessor.class.getName()); 082 } 083 return builder; 084 } 085 086 /** 087 * Turn off processing constraints for a given table, even if constraints have been turned on or 088 * added. 089 */ 090 public static TableDescriptorBuilder disable(TableDescriptorBuilder builder) throws IOException { 091 try { 092 return builder.removeCoprocessor(ConstraintProcessor.class.getName()); 093 } catch (IllegalArgumentException e) { 094 LOG.warn("ConstraintProcessor was unset.", e); 095 return builder; 096 } 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. 104 */ 105 public static TableDescriptorBuilder remove(TableDescriptorBuilder builder) throws IOException { 106 disable(builder); 107 return builder 108 .removeValue((k, v) -> CONSTRAINT_HTD_ATTR_KEY_PATTERN.split(k.toString()).length == 2); 109 } 110 111 /** 112 * Check to see if the Constraint is currently set. 113 * @param desc {@link TableDescriptor} to check 114 * @param clazz {@link Constraint} class to check for. 115 * @return <tt>true</tt> if the {@link Constraint} is present, even if it is disabled. 116 * <tt>false</tt> otherwise. 117 */ 118 public static boolean has(TableDescriptor desc, Class<? extends Constraint> clazz) { 119 return getKeyValueForClass(desc, clazz) != null; 120 } 121 122 /** 123 * Get the kv {@link Entry} in the descriptor for the specified class 124 * @param desc {@link TableDescriptor} to read 125 * @param clazz To search for 126 * @return The {@link Pair} of {@literal <key, value>} in the table, if that class is present. 127 * {@code null} otherwise. 128 */ 129 private static Pair<String, String> getKeyValueForClass(TableDescriptor desc, 130 Class<? extends Constraint> clazz) { 131 // get the serialized version of the constraint 132 String key = serializeConstraintClass(clazz); 133 String value = desc.getValue(key); 134 135 return value == null ? null : new Pair<>(key, value); 136 } 137 138 /** 139 * Get the kv {@link Entry} in the descriptor builder for the specified class 140 * @param builder {@link TableDescriptorBuilder} to read 141 * @param clazz To search for 142 * @return The {@link Pair} of {@literal <key, value>} in the table, if that class is present. 143 * {@code null} otherwise. 144 */ 145 private static Pair<String, String> getKeyValueForClass(TableDescriptorBuilder builder, 146 Class<? extends Constraint> clazz) { 147 // get the serialized version of the constraint 148 String key = serializeConstraintClass(clazz); 149 String value = builder.getValue(key); 150 151 return value == null ? null : new Pair<>(key, value); 152 } 153 154 /** 155 * Add configuration-less constraints to the table. 156 * <p/> 157 * This will overwrite any configuration associated with the previous constraint of the same 158 * class. 159 * <p/> 160 * Each constraint, when added to the table, will have a specific priority, dictating the order in 161 * which the {@link Constraint} will be run. A {@link Constraint} earlier in the list will be run 162 * before those later in the list. The same logic applies between two Constraints over time 163 * (earlier added is run first on the regionserver). 164 * @param builder {@link TableDescriptorBuilder} to add a {@link Constraint} 165 * @param constraints {@link Constraint Constraints} to add. All constraints are considered 166 * automatically enabled on add 167 * @throws IOException If constraint could not be serialized/added to table 168 */ 169 @SafeVarargs 170 public static TableDescriptorBuilder add(TableDescriptorBuilder builder, 171 Class<? extends Constraint>... constraints) throws IOException { 172 // make sure constraints are enabled 173 enable(builder); 174 long priority = getNextPriority(builder); 175 176 // store each constraint 177 for (Class<? extends Constraint> clazz : constraints) { 178 addConstraint(builder, clazz, null, priority++); 179 } 180 return updateLatestPriority(builder, priority); 181 } 182 183 /** 184 * Add constraints and their associated configurations to the table. 185 * <p> 186 * Adding the same constraint class twice will overwrite the first constraint's configuration 187 * <p> 188 * Each constraint, when added to the table, will have a specific priority, dictating the order in 189 * which the {@link Constraint} will be run. A {@link Constraint} earlier in the list will be run 190 * before those later in the list. The same logic applies between two Constraints over time 191 * (earlier added is run first on the regionserver). 192 * @param builder {@link TableDescriptorBuilder} to add a {@link Constraint} 193 * @param constraints {@link Pair} of a {@link Constraint} and its associated 194 * {@link Configuration}. The Constraint will be configured on load with the 195 * specified configuration.All constraints are considered automatically enabled 196 * on add 197 * @throws IOException if any constraint could not be deserialized. Assumes if 1 constraint is not 198 * loaded properly, something has gone terribly wrong and that all constraints 199 * need to be enforced. 200 */ 201 @SafeVarargs 202 public static TableDescriptorBuilder add(TableDescriptorBuilder builder, 203 Pair<Class<? extends Constraint>, Configuration>... constraints) throws IOException { 204 enable(builder); 205 long priority = getNextPriority(builder); 206 for (Pair<Class<? extends Constraint>, Configuration> pair : constraints) { 207 addConstraint(builder, pair.getFirst(), pair.getSecond(), priority++); 208 } 209 return updateLatestPriority(builder, priority); 210 } 211 212 /** 213 * Add a {@link Constraint} to the table with the given configuration 214 * <p/> 215 * Each constraint, when added to the table, will have a specific priority, dictating the order in 216 * which the {@link Constraint} will be run. A {@link Constraint} added will run on the 217 * regionserver before those added to the {@link TableDescriptorBuilder} later. 218 * @param builder {@link TableDescriptorBuilder} to add a {@link Constraint} 219 * @param constraint to be added 220 * @param conf configuration associated with the constraint 221 * @throws IOException if any constraint could not be deserialized. Assumes if 1 constraint is not 222 * loaded properly, something has gone terribly wrong and that all constraints 223 * need to be enforced. 224 */ 225 public static TableDescriptorBuilder add(TableDescriptorBuilder builder, 226 Class<? extends Constraint> constraint, Configuration conf) throws IOException { 227 enable(builder); 228 long priority = getNextPriority(builder); 229 addConstraint(builder, constraint, conf, priority++); 230 231 return updateLatestPriority(builder, priority); 232 } 233 234 /** 235 * Write the raw constraint and configuration to the descriptor. 236 * <p/> 237 * This method takes care of creating a new configuration based on the passed in configuration and 238 * then updating that with enabled and priority of the constraint. 239 * <p/> 240 * When a constraint is added, it is automatically enabled. 241 */ 242 private static TableDescriptorBuilder addConstraint(TableDescriptorBuilder builder, 243 Class<? extends Constraint> clazz, Configuration conf, long priority) throws IOException { 244 return writeConstraint(builder, serializeConstraintClass(clazz), 245 configure(conf, true, priority)); 246 } 247 248 /** 249 * Setup the configuration for a constraint as to whether it is enabled and its priority 250 * @param conf on which to base the new configuration 251 * @param enabled <tt>true</tt> if it should be run 252 * @param priority relative to other constraints 253 * @return a new configuration, storable in the {@link TableDescriptor} 254 */ 255 private static Configuration configure(Configuration conf, boolean enabled, long priority) { 256 // create the configuration to actually be stored 257 // clone if possible, but otherwise just create an empty configuration 258 Configuration toWrite = conf == null ? new Configuration() : new Configuration(conf); 259 260 // update internal properties 261 toWrite.setBooleanIfUnset(ENABLED_KEY, enabled); 262 263 // set if unset long 264 if (toWrite.getLong(PRIORITY_KEY, UNSET_PRIORITY) == UNSET_PRIORITY) { 265 toWrite.setLong(PRIORITY_KEY, priority); 266 } 267 268 return toWrite; 269 } 270 271 /** 272 * Just write the class to a String representation of the class as a key for the 273 * {@link TableDescriptor} 274 * @param clazz Constraint class to convert to a {@link TableDescriptor} key 275 * @return key to store in the {@link TableDescriptor} 276 */ 277 private static String serializeConstraintClass(Class<? extends Constraint> clazz) { 278 String constraintClazz = clazz.getName(); 279 return CONSTRAINT_HTD_KEY_PREFIX + constraintClazz; 280 } 281 282 /** 283 * Write the given key and associated configuration to the {@link TableDescriptorBuilder}. 284 */ 285 private static TableDescriptorBuilder writeConstraint(TableDescriptorBuilder builder, String key, 286 Configuration conf) throws IOException { 287 // store the key and conf in the descriptor 288 return builder.setValue(key, serializeConfiguration(conf)); 289 } 290 291 /** 292 * Write the configuration to a String 293 * @param conf to write 294 * @return String representation of that configuration 295 */ 296 private static String serializeConfiguration(Configuration conf) throws IOException { 297 // write the configuration out to the data stream 298 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 299 DataOutputStream dos = new DataOutputStream(bos); 300 conf.writeXml(dos); 301 dos.flush(); 302 byte[] data = bos.toByteArray(); 303 return Bytes.toString(data); 304 } 305 306 /** 307 * Read the {@link Configuration} stored in the byte stream. 308 * @param bytes to read from 309 * @return A valid configuration 310 */ 311 private static Configuration readConfiguration(byte[] bytes) throws IOException { 312 ByteArrayInputStream is = new ByteArrayInputStream(bytes); 313 Configuration conf = new Configuration(false); 314 conf.addResource(is); 315 return conf; 316 } 317 318 /** 319 * Read in the configuration from the String encoded configuration 320 * @param bytes to read from 321 * @return A valid configuration 322 * @throws IOException if the configuration could not be read 323 */ 324 private static Configuration readConfiguration(String bytes) throws IOException { 325 return readConfiguration(Bytes.toBytes(bytes)); 326 } 327 328 private static long getNextPriority(TableDescriptorBuilder builder) { 329 String value = builder.getValue(COUNTER_KEY); 330 331 long priority; 332 // get the current priority 333 if (value == null) { 334 priority = MIN_PRIORITY; 335 } else { 336 priority = Long.parseLong(value) + 1; 337 } 338 339 return priority; 340 } 341 342 private static TableDescriptorBuilder updateLatestPriority(TableDescriptorBuilder builder, 343 long priority) { 344 // update the max priority 345 return builder.setValue(COUNTER_KEY, Long.toString(priority)); 346 } 347 348 /** 349 * Update the configuration for the {@link Constraint}; does not change the order in which the 350 * constraint is run. 351 * @param builder {@link TableDescriptorBuilder} to update 352 * @param clazz {@link Constraint} to update 353 * @param configuration to update the {@link Constraint} with. 354 * @throws IOException if the Constraint was not stored correctly 355 * @throws IllegalArgumentException if the Constraint was not present on this table. 356 */ 357 public static TableDescriptorBuilder setConfiguration(TableDescriptorBuilder builder, 358 Class<? extends Constraint> clazz, Configuration configuration) 359 throws IOException, IllegalArgumentException { 360 // get the entry for this class 361 Pair<String, String> e = getKeyValueForClass(builder, clazz); 362 363 if (e == null) { 364 throw new IllegalArgumentException( 365 "Constraint: " + clazz.getName() + " is not associated with this table."); 366 } 367 368 // clone over the configuration elements 369 Configuration conf = new Configuration(configuration); 370 371 // read in the previous info about the constraint 372 Configuration internal = readConfiguration(e.getSecond()); 373 374 // update the fields based on the previous settings 375 conf.setIfUnset(ENABLED_KEY, internal.get(ENABLED_KEY)); 376 conf.setIfUnset(PRIORITY_KEY, internal.get(PRIORITY_KEY)); 377 378 // update the current value 379 return writeConstraint(builder, e.getFirst(), conf); 380 } 381 382 /** 383 * Remove the constraint (and associated information) for the table descriptor. 384 * @param builder {@link TableDescriptorBuilder} to modify 385 * @param clazz {@link Constraint} class to remove 386 */ 387 public static TableDescriptorBuilder remove(TableDescriptorBuilder builder, 388 Class<? extends Constraint> clazz) { 389 String key = serializeConstraintClass(clazz); 390 return builder.removeValue(key); 391 } 392 393 /** 394 * Enable the given {@link Constraint}. Retains all the information (e.g. Configuration) for the 395 * {@link Constraint}, but makes sure that it gets loaded on the table. 396 * @param builder {@link TableDescriptorBuilder} to modify 397 * @param clazz {@link Constraint} to enable 398 * @throws IOException If the constraint cannot be properly deserialized 399 */ 400 public static void enableConstraint(TableDescriptorBuilder builder, 401 Class<? extends Constraint> clazz) throws IOException { 402 changeConstraintEnabled(builder, clazz, true); 403 } 404 405 /** 406 * Disable the given {@link Constraint}. Retains all the information (e.g. Configuration) for the 407 * {@link Constraint}, but it just doesn't load the {@link Constraint} on the table. 408 * @param builder {@link TableDescriptorBuilder} to modify 409 * @param clazz {@link Constraint} to disable. 410 * @throws IOException if the constraint cannot be found 411 */ 412 public static void disableConstraint(TableDescriptorBuilder builder, 413 Class<? extends Constraint> clazz) throws IOException { 414 changeConstraintEnabled(builder, clazz, false); 415 } 416 417 /** 418 * Change the whether the constraint (if it is already present) is enabled or disabled. 419 */ 420 private static TableDescriptorBuilder changeConstraintEnabled(TableDescriptorBuilder builder, 421 Class<? extends Constraint> clazz, boolean enabled) throws IOException { 422 // get the original constraint 423 Pair<String, String> entry = getKeyValueForClass(builder, clazz); 424 if (entry == null) { 425 throw new IllegalArgumentException("Constraint: " + clazz.getName() 426 + " is not associated with this table. You can't enable it!"); 427 } 428 429 // create a new configuration from that conf 430 Configuration conf = readConfiguration(entry.getSecond()); 431 432 // set that it is enabled 433 conf.setBoolean(ENABLED_KEY, enabled); 434 435 // write it back out 436 return writeConstraint(builder, entry.getFirst(), conf); 437 } 438 439 /** 440 * Check to see if the given constraint is enabled. 441 * @param desc {@link TableDescriptor} to check. 442 * @param clazz {@link Constraint} to check for 443 * @return <tt>true</tt> if the {@link Constraint} is present and enabled. <tt>false</tt> 444 * otherwise. 445 * @throws IOException If the constraint has improperly stored in the table 446 */ 447 public static boolean enabled(TableDescriptor desc, Class<? extends Constraint> clazz) 448 throws IOException { 449 // get the kv 450 Pair<String, String> entry = getKeyValueForClass(desc, clazz); 451 // its not enabled so just return false. In fact, its not even present! 452 if (entry == null) { 453 return false; 454 } 455 456 // get the info about the constraint 457 Configuration conf = readConfiguration(entry.getSecond()); 458 459 return conf.getBoolean(ENABLED_KEY, false); 460 } 461 462 /** 463 * Get the constraints stored in the table descriptor 464 * @param desc To read from 465 * @param classloader To use when loading classes. If a special classloader is used on a region, 466 * for instance, then that should be the classloader used to load the 467 * constraints. This could also apply to unit-testing situation, where want to 468 * ensure that class is reloaded or not. 469 * @return List of configured {@link Constraint Constraints} 470 * @throws IOException if any part of reading/arguments fails 471 */ 472 static List<? extends Constraint> getConstraints(TableDescriptor desc, ClassLoader classloader) 473 throws IOException { 474 List<Constraint> constraints = new ArrayList<>(); 475 // loop through all the key, values looking for constraints 476 for (Map.Entry<Bytes, Bytes> e : desc.getValues().entrySet()) { 477 // read out the constraint 478 String key = Bytes.toString(e.getKey().get()).trim(); 479 String[] className = CONSTRAINT_HTD_ATTR_KEY_PATTERN.split(key); 480 if (className.length == 2) { 481 key = className[1]; 482 if (LOG.isDebugEnabled()) { 483 LOG.debug("Loading constraint:" + key); 484 } 485 486 // read in the rest of the constraint 487 Configuration conf; 488 try { 489 conf = readConfiguration(e.getValue().get()); 490 } catch (IOException e1) { 491 // long that we don't have a valid configuration stored, and move on. 492 LOG.warn("Corrupted configuration found for key:" + key + ", skipping it."); 493 continue; 494 } 495 // if it is not enabled, skip it 496 if (!conf.getBoolean(ENABLED_KEY, false)) { 497 LOG.debug("Constraint: {} is DISABLED - skipping it", key); 498 // go to the next constraint 499 continue; 500 } 501 502 try { 503 // add the constraint, now that we expect it to be valid. 504 Class<? extends Constraint> clazz = 505 classloader.loadClass(key).asSubclass(Constraint.class); 506 Constraint constraint = clazz.getDeclaredConstructor().newInstance(); 507 constraint.setConf(conf); 508 constraints.add(constraint); 509 } catch (InvocationTargetException | NoSuchMethodException | ClassNotFoundException 510 | InstantiationException | IllegalAccessException e1) { 511 throw new IOException(e1); 512 } 513 } 514 } 515 // sort them, based on the priorities 516 Collections.sort(constraints, constraintComparator); 517 return constraints; 518 } 519 520 private static final Comparator<Constraint> constraintComparator = new Comparator<Constraint>() { 521 @Override 522 public int compare(Constraint c1, Constraint c2) { 523 // compare the priorities of the constraints stored in their configuration 524 return Long.compare(c1.getConf().getLong(PRIORITY_KEY, DEFAULT_PRIORITY), 525 c2.getConf().getLong(PRIORITY_KEY, DEFAULT_PRIORITY)); 526 } 527 }; 528 529}