| 
 | 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.util;  | 
 | 19 | + | 
 | 20 | +import java.io.IOException;  | 
 | 21 | + | 
 | 22 | +import org.apache.hadoop.conf.Configuration;  | 
 | 23 | +import org.apache.hadoop.hbase.CompoundConfiguration;  | 
 | 24 | +import org.apache.hadoop.hbase.DoNotRetryIOException;  | 
 | 25 | +import org.apache.hadoop.hbase.HConstants;  | 
 | 26 | +import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;  | 
 | 27 | +import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;  | 
 | 28 | +import org.apache.hadoop.hbase.client.TableDescriptor;  | 
 | 29 | +import org.apache.hadoop.hbase.master.HMaster;  | 
 | 30 | +import org.apache.hadoop.hbase.regionserver.DefaultStoreEngine;  | 
 | 31 | +import org.apache.hadoop.hbase.regionserver.HStore;  | 
 | 32 | +import org.apache.hadoop.hbase.regionserver.RegionCoprocessorHost;  | 
 | 33 | +import org.apache.hadoop.hbase.regionserver.RegionSplitPolicy;  | 
 | 34 | +import org.apache.hadoop.hbase.regionserver.compactions.ExploringCompactionPolicy;  | 
 | 35 | +import org.apache.hadoop.hbase.regionserver.compactions.FIFOCompactionPolicy;  | 
 | 36 | +import org.apache.yetus.audience.InterfaceAudience;  | 
 | 37 | +import org.slf4j.Logger;  | 
 | 38 | +import org.slf4j.LoggerFactory;  | 
 | 39 | + | 
 | 40 | +import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos;  | 
 | 41 | + | 
 | 42 | +/**  | 
 | 43 | + * Used for master to sanity check {@link org.apache.hadoop.hbase.client.TableDescriptor}.  | 
 | 44 | + */  | 
 | 45 | +@InterfaceAudience.Private  | 
 | 46 | +public final class TableDescriptorChecker {  | 
 | 47 | +  private static Logger LOG = LoggerFactory.getLogger(TableDescriptorChecker.class);  | 
 | 48 | + | 
 | 49 | +  public static final String TABLE_SANITY_CHECKS = "hbase.table.sanity.checks";  | 
 | 50 | +  public static final boolean DEFAULT_TABLE_SANITY_CHECKS = true;  | 
 | 51 | + | 
 | 52 | +  private TableDescriptorChecker() {  | 
 | 53 | +  }  | 
 | 54 | + | 
 | 55 | +  /**  | 
 | 56 | +   * Checks whether the table conforms to some sane limits, and configured  | 
 | 57 | +   * values (compression, etc) work. Throws an exception if something is wrong.  | 
 | 58 | +   */  | 
 | 59 | +  public static void sanityCheck(final Configuration conf, final TableDescriptor td)  | 
 | 60 | +      throws IOException {  | 
 | 61 | +    sanityCheck(conf, td,  | 
 | 62 | +        conf.getBoolean(HMaster.MASTER_CHECK_COMPRESSION, HMaster.DEFAULT_MASTER_CHECK_COMPRESSION),  | 
 | 63 | +        conf.getBoolean(HMaster.MASTER_CHECK_ENCRYPTION, HMaster.DEFAULT_MASTER_CHECK_ENCRYPTION));  | 
 | 64 | +  }  | 
 | 65 | + | 
 | 66 | +  /**  | 
 | 67 | +   * Checks whether the table conforms to some sane limits, and configured  | 
 | 68 | +   * values (compression, etc) work. Throws an exception if something is wrong.  | 
 | 69 | +   */  | 
 | 70 | +  public static void sanityCheck(final Configuration conf, final TableDescriptor td,  | 
 | 71 | +      boolean checkCompression, boolean checkEncryption) throws IOException {  | 
 | 72 | +    // Setting this to true logs the warning instead of throwing exception  | 
 | 73 | +    boolean logWarn = false;  | 
 | 74 | +    if (!conf.getBoolean(TABLE_SANITY_CHECKS, DEFAULT_TABLE_SANITY_CHECKS)) {  | 
 | 75 | +      logWarn = true;  | 
 | 76 | +    }  | 
 | 77 | +    String tableVal = td.getValue(TABLE_SANITY_CHECKS);  | 
 | 78 | +    if (tableVal != null && !Boolean.valueOf(tableVal)) {  | 
 | 79 | +      logWarn = true;  | 
 | 80 | +    }  | 
 | 81 | + | 
 | 82 | +    // check max file size  | 
 | 83 | +    long maxFileSizeLowerLimit = 2 * 1024 * 1024L; // 2M is the default lower limit  | 
 | 84 | +    long maxFileSize = td.getMaxFileSize();  | 
 | 85 | +    if (maxFileSize < 0) {  | 
 | 86 | +      maxFileSize = conf.getLong(HConstants.HREGION_MAX_FILESIZE, maxFileSizeLowerLimit);  | 
 | 87 | +    }  | 
 | 88 | +    if (maxFileSize < conf.getLong("hbase.hregion.max.filesize.limit", maxFileSizeLowerLimit)) {  | 
 | 89 | +      String message =  | 
 | 90 | +          "MAX_FILESIZE for table descriptor or " + "\"hbase.hregion.max.filesize\" (" +  | 
 | 91 | +              maxFileSize + ") is too small, which might cause over splitting into unmanageable " +  | 
 | 92 | +              "number of regions.";  | 
 | 93 | +      warnOrThrowExceptionForFailure(logWarn, message, null);  | 
 | 94 | +    }  | 
 | 95 | + | 
 | 96 | +    // check flush size  | 
 | 97 | +    long flushSizeLowerLimit = 1024 * 1024L; // 1M is the default lower limit  | 
 | 98 | +    long flushSize = td.getMemStoreFlushSize();  | 
 | 99 | +    if (flushSize < 0) {  | 
 | 100 | +      flushSize = conf.getLong(HConstants.HREGION_MEMSTORE_FLUSH_SIZE, flushSizeLowerLimit);  | 
 | 101 | +    }  | 
 | 102 | +    if (flushSize < conf.getLong("hbase.hregion.memstore.flush.size.limit", flushSizeLowerLimit)) {  | 
 | 103 | +      String message = "MEMSTORE_FLUSHSIZE for table descriptor or " +  | 
 | 104 | +          "\"hbase.hregion.memstore.flush.size\" (" + flushSize +  | 
 | 105 | +          ") is too small, which might cause" + " very frequent flushing.";  | 
 | 106 | +      warnOrThrowExceptionForFailure(logWarn, message, null);  | 
 | 107 | +    }  | 
 | 108 | + | 
 | 109 | +    // check that coprocessors and other specified plugin classes can be loaded  | 
 | 110 | +    try {  | 
 | 111 | +      checkClassLoading(conf, td);  | 
 | 112 | +    } catch (Exception ex) {  | 
 | 113 | +      warnOrThrowExceptionForFailure(logWarn, ex.getMessage(), null);  | 
 | 114 | +    }  | 
 | 115 | + | 
 | 116 | +    if (checkCompression) {  | 
 | 117 | +      // check compression can be loaded  | 
 | 118 | +      try {  | 
 | 119 | +        checkCompression(td);  | 
 | 120 | +      } catch (IOException e) {  | 
 | 121 | +        warnOrThrowExceptionForFailure(logWarn, e.getMessage(), e);  | 
 | 122 | +      }  | 
 | 123 | +    }  | 
 | 124 | + | 
 | 125 | +    if (checkEncryption) {  | 
 | 126 | +      // check encryption can be loaded  | 
 | 127 | +      try {  | 
 | 128 | +        checkEncryption(conf, td);  | 
 | 129 | +      } catch (IOException e) {  | 
 | 130 | +        warnOrThrowExceptionForFailure(logWarn, e.getMessage(), e);  | 
 | 131 | +      }  | 
 | 132 | +    }  | 
 | 133 | + | 
 | 134 | +    // Verify compaction policy  | 
 | 135 | +    try {  | 
 | 136 | +      checkCompactionPolicy(conf, td);  | 
 | 137 | +    } catch (IOException e) {  | 
 | 138 | +      warnOrThrowExceptionForFailure(false, e.getMessage(), e);  | 
 | 139 | +    }  | 
 | 140 | +    // check that we have at least 1 CF  | 
 | 141 | +    if (td.getColumnFamilyCount() == 0) {  | 
 | 142 | +      String message = "Table should have at least one column family.";  | 
 | 143 | +      warnOrThrowExceptionForFailure(logWarn, message, null);  | 
 | 144 | +    }  | 
 | 145 | + | 
 | 146 | +    // check that we have minimum 1 region replicas  | 
 | 147 | +    int regionReplicas = td.getRegionReplication();  | 
 | 148 | +    if (regionReplicas < 1) {  | 
 | 149 | +      String message = "Table region replication should be at least one.";  | 
 | 150 | +      warnOrThrowExceptionForFailure(logWarn, message, null);  | 
 | 151 | +    }  | 
 | 152 | + | 
 | 153 | +    for (ColumnFamilyDescriptor hcd : td.getColumnFamilies()) {  | 
 | 154 | +      if (hcd.getTimeToLive() <= 0) {  | 
 | 155 | +        String message = "TTL for column family " + hcd.getNameAsString() + " must be positive.";  | 
 | 156 | +        warnOrThrowExceptionForFailure(logWarn, message, null);  | 
 | 157 | +      }  | 
 | 158 | + | 
 | 159 | +      // check blockSize  | 
 | 160 | +      if (hcd.getBlocksize() < 1024 || hcd.getBlocksize() > 16 * 1024 * 1024) {  | 
 | 161 | +        String message = "Block size for column family " + hcd.getNameAsString() +  | 
 | 162 | +            "  must be between 1K and 16MB.";  | 
 | 163 | +        warnOrThrowExceptionForFailure(logWarn, message, null);  | 
 | 164 | +      }  | 
 | 165 | + | 
 | 166 | +      // check versions  | 
 | 167 | +      if (hcd.getMinVersions() < 0) {  | 
 | 168 | +        String message =  | 
 | 169 | +            "Min versions for column family " + hcd.getNameAsString() + "  must be positive.";  | 
 | 170 | +        warnOrThrowExceptionForFailure(logWarn, message, null);  | 
 | 171 | +      }  | 
 | 172 | +      // max versions already being checked  | 
 | 173 | + | 
 | 174 | +      // HBASE-13776 Setting illegal versions for ColumnFamilyDescriptor  | 
 | 175 | +      //  does not throw IllegalArgumentException  | 
 | 176 | +      // check minVersions <= maxVerions  | 
 | 177 | +      if (hcd.getMinVersions() > hcd.getMaxVersions()) {  | 
 | 178 | +        String message = "Min versions for column family " + hcd.getNameAsString() +  | 
 | 179 | +            " must be less than the Max versions.";  | 
 | 180 | +        warnOrThrowExceptionForFailure(logWarn, message, null);  | 
 | 181 | +      }  | 
 | 182 | + | 
 | 183 | +      // check replication scope  | 
 | 184 | +      checkReplicationScope(hcd);  | 
 | 185 | +      // check bloom filter type  | 
 | 186 | +      checkBloomFilterType(hcd);  | 
 | 187 | + | 
 | 188 | +      // check data replication factor, it can be 0(default value) when user has not explicitly  | 
 | 189 | +      // set the value, in this case we use default replication factor set in the file system.  | 
 | 190 | +      if (hcd.getDFSReplication() < 0) {  | 
 | 191 | +        String message = "HFile Replication for column family " + hcd.getNameAsString() +  | 
 | 192 | +            "  must be greater than zero.";  | 
 | 193 | +        warnOrThrowExceptionForFailure(logWarn, message, null);  | 
 | 194 | +      }  | 
 | 195 | +    }  | 
 | 196 | +  }  | 
 | 197 | + | 
 | 198 | +  private static void checkReplicationScope(final ColumnFamilyDescriptor cfd) throws IOException {  | 
 | 199 | +    // check replication scope  | 
 | 200 | +    WALProtos.ScopeType scop = WALProtos.ScopeType.valueOf(cfd.getScope());  | 
 | 201 | +    if (scop == null) {  | 
 | 202 | +      String message =  | 
 | 203 | +          "Replication scope for column family " + cfd.getNameAsString() + " is " + cfd.getScope() +  | 
 | 204 | +              " which is invalid.";  | 
 | 205 | + | 
 | 206 | +      LOG.error(message);  | 
 | 207 | +      throw new DoNotRetryIOException(message);  | 
 | 208 | +    }  | 
 | 209 | +  }  | 
 | 210 | + | 
 | 211 | +  private static void checkCompactionPolicy(Configuration conf, TableDescriptor td)  | 
 | 212 | +      throws IOException {  | 
 | 213 | +    // FIFO compaction has some requirements  | 
 | 214 | +    // Actually FCP ignores periodic major compactions  | 
 | 215 | +    String className = td.getValue(DefaultStoreEngine.DEFAULT_COMPACTION_POLICY_CLASS_KEY);  | 
 | 216 | +    if (className == null) {  | 
 | 217 | +      className = conf.get(DefaultStoreEngine.DEFAULT_COMPACTION_POLICY_CLASS_KEY,  | 
 | 218 | +          ExploringCompactionPolicy.class.getName());  | 
 | 219 | +    }  | 
 | 220 | + | 
 | 221 | +    int blockingFileCount = HStore.DEFAULT_BLOCKING_STOREFILE_COUNT;  | 
 | 222 | +    String sv = td.getValue(HStore.BLOCKING_STOREFILES_KEY);  | 
 | 223 | +    if (sv != null) {  | 
 | 224 | +      blockingFileCount = Integer.parseInt(sv);  | 
 | 225 | +    } else {  | 
 | 226 | +      blockingFileCount = conf.getInt(HStore.BLOCKING_STOREFILES_KEY, blockingFileCount);  | 
 | 227 | +    }  | 
 | 228 | + | 
 | 229 | +    for (ColumnFamilyDescriptor hcd : td.getColumnFamilies()) {  | 
 | 230 | +      String compactionPolicy =  | 
 | 231 | +          hcd.getConfigurationValue(DefaultStoreEngine.DEFAULT_COMPACTION_POLICY_CLASS_KEY);  | 
 | 232 | +      if (compactionPolicy == null) {  | 
 | 233 | +        compactionPolicy = className;  | 
 | 234 | +      }  | 
 | 235 | +      if (!compactionPolicy.equals(FIFOCompactionPolicy.class.getName())) {  | 
 | 236 | +        continue;  | 
 | 237 | +      }  | 
 | 238 | +      // FIFOCompaction  | 
 | 239 | +      String message = null;  | 
 | 240 | + | 
 | 241 | +      // 1. Check TTL  | 
 | 242 | +      if (hcd.getTimeToLive() == ColumnFamilyDescriptorBuilder.DEFAULT_TTL) {  | 
 | 243 | +        message = "Default TTL is not supported for FIFO compaction";  | 
 | 244 | +        throw new IOException(message);  | 
 | 245 | +      }  | 
 | 246 | + | 
 | 247 | +      // 2. Check min versions  | 
 | 248 | +      if (hcd.getMinVersions() > 0) {  | 
 | 249 | +        message = "MIN_VERSION > 0 is not supported for FIFO compaction";  | 
 | 250 | +        throw new IOException(message);  | 
 | 251 | +      }  | 
 | 252 | + | 
 | 253 | +      // 3. blocking file count  | 
 | 254 | +      sv = hcd.getConfigurationValue(HStore.BLOCKING_STOREFILES_KEY);  | 
 | 255 | +      if (sv != null) {  | 
 | 256 | +        blockingFileCount = Integer.parseInt(sv);  | 
 | 257 | +      }  | 
 | 258 | +      if (blockingFileCount < 1000) {  | 
 | 259 | +        message =  | 
 | 260 | +            "Blocking file count '" + HStore.BLOCKING_STOREFILES_KEY + "' " + blockingFileCount +  | 
 | 261 | +                " is below recommended minimum of 1000 for column family " + hcd.getNameAsString();  | 
 | 262 | +        throw new IOException(message);  | 
 | 263 | +      }  | 
 | 264 | +    }  | 
 | 265 | +  }  | 
 | 266 | + | 
 | 267 | +  private static void checkBloomFilterType(ColumnFamilyDescriptor cfd) throws IOException {  | 
 | 268 | +    Configuration conf = new CompoundConfiguration().addStringMap(cfd.getConfiguration());  | 
 | 269 | +    try {  | 
 | 270 | +      BloomFilterUtil.getBloomFilterParam(cfd.getBloomFilterType(), conf);  | 
 | 271 | +    } catch (IllegalArgumentException e) {  | 
 | 272 | +      throw new DoNotRetryIOException("Failed to get bloom filter param", e);  | 
 | 273 | +    }  | 
 | 274 | +  }  | 
 | 275 | + | 
 | 276 | +  private static void checkCompression(final TableDescriptor td) throws IOException {  | 
 | 277 | +    for (ColumnFamilyDescriptor cfd : td.getColumnFamilies()) {  | 
 | 278 | +      CompressionTest.testCompression(cfd.getCompressionType());  | 
 | 279 | +      CompressionTest.testCompression(cfd.getCompactionCompressionType());  | 
 | 280 | +    }  | 
 | 281 | +  }  | 
 | 282 | + | 
 | 283 | +  private static void checkEncryption(final Configuration conf, final TableDescriptor td)  | 
 | 284 | +      throws IOException {  | 
 | 285 | +    for (ColumnFamilyDescriptor cfd : td.getColumnFamilies()) {  | 
 | 286 | +      EncryptionTest.testEncryption(conf, cfd.getEncryptionType(), cfd.getEncryptionKey());  | 
 | 287 | +    }  | 
 | 288 | +  }  | 
 | 289 | + | 
 | 290 | +  private static void checkClassLoading(final Configuration conf, final TableDescriptor td)  | 
 | 291 | +      throws IOException {  | 
 | 292 | +    RegionSplitPolicy.getSplitPolicyClass(td, conf);  | 
 | 293 | +    RegionCoprocessorHost.testTableCoprocessorAttrs(conf, td);  | 
 | 294 | +  }  | 
 | 295 | + | 
 | 296 | +  // HBASE-13350 - Helper method to log warning on sanity check failures if checks disabled.  | 
 | 297 | +  private static void warnOrThrowExceptionForFailure(boolean logWarn, String message,  | 
 | 298 | +      Exception cause) throws IOException {  | 
 | 299 | +    if (!logWarn) {  | 
 | 300 | +      throw new DoNotRetryIOException(message + " Set " + TABLE_SANITY_CHECKS +  | 
 | 301 | +          " to false at conf or table descriptor if you want to bypass sanity checks", cause);  | 
 | 302 | +    }  | 
 | 303 | +    LOG.warn(message);  | 
 | 304 | +  }  | 
 | 305 | +}  | 
0 commit comments