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.util;
019
020import static org.junit.jupiter.api.Assertions.assertThrows;
021
022import java.io.IOException;
023import org.apache.hadoop.conf.Configuration;
024import org.apache.hadoop.hbase.DoNotRetryIOException;
025import org.apache.hadoop.hbase.TableName;
026import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
027import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
028import org.apache.hadoop.hbase.conf.ConfigKey;
029import org.apache.hadoop.hbase.regionserver.BloomType;
030import org.apache.hadoop.hbase.regionserver.DefaultStoreEngine;
031import org.apache.hadoop.hbase.regionserver.HStore;
032import org.apache.hadoop.hbase.regionserver.compactions.FIFOCompactionPolicy;
033import org.apache.hadoop.hbase.testclassification.MiscTests;
034import org.apache.hadoop.hbase.testclassification.SmallTests;
035import org.junit.jupiter.api.Tag;
036import org.junit.jupiter.api.Test;
037
038@Tag(MiscTests.TAG)
039@Tag(SmallTests.TAG)
040public class TestTableDescriptorChecker {
041
042  @Test
043  public void testSanityCheck() throws IOException {
044    Configuration conf = new Configuration();
045    TableDescriptorBuilder t = TableDescriptorBuilder.newBuilder(TableName.valueOf("test"));
046    ColumnFamilyDescriptorBuilder cf = ColumnFamilyDescriptorBuilder.newBuilder("cf".getBytes());
047    t.setColumnFamily(cf.build());
048
049    // Empty configuration. Should be fine.
050    TableDescriptorChecker.sanityCheck(conf, t.build());
051
052    // Declare configuration type as int.
053    String key = "hbase.hstore.compaction.ratio";
054    ConfigKey.INT(key, v -> v > 0);
055
056    // Error in table configuration.
057    t.setValue(key, "xx");
058    assertThrows(DoNotRetryIOException.class,
059      () -> TableDescriptorChecker.sanityCheck(conf, t.build()),
060      "Should have thrown IllegalArgumentException");
061
062    // Fix the error.
063    t.setValue(key, "1");
064    TableDescriptorChecker.sanityCheck(conf, t.build());
065
066    // Verify column family configuration.
067    for (boolean viaSetValue : new boolean[] { true, false }) {
068      // Error in column family configuration.
069      if (viaSetValue) {
070        cf.setValue(key, "xx");
071      } else {
072        cf.setConfiguration(key, "xx");
073      }
074      t.removeColumnFamily("cf".getBytes());
075      t.setColumnFamily(cf.build());
076      assertThrows(DoNotRetryIOException.class,
077        () -> TableDescriptorChecker.sanityCheck(conf, t.build()),
078        "Should have thrown IllegalArgumentException");
079
080      // Fix the error.
081      if (viaSetValue) {
082        cf.setValue(key, "");
083      } else {
084        cf.setConfiguration(key, "");
085      }
086      t.removeColumnFamily("cf".getBytes());
087      t.setColumnFamily(cf.build());
088      TableDescriptorChecker.sanityCheck(conf, t.build());
089    }
090  }
091
092  @Test
093  public void testBloomFilterPrefixLengthValidation() throws IOException {
094    Configuration conf = new Configuration();
095    String key = BloomFilterUtil.PREFIX_LENGTH_KEY;
096
097    for (boolean viaSetValue : new boolean[] { true, false }) {
098      ColumnFamilyDescriptorBuilder cf = ColumnFamilyDescriptorBuilder.newBuilder("cf".getBytes())
099        .setBloomFilterType(BloomType.ROWPREFIX_FIXED_LENGTH);
100      TableDescriptorBuilder t = TableDescriptorBuilder.newBuilder(TableName.valueOf("test"));
101
102      // Invalid: prefix length must be > 0 for ROWPREFIX_FIXED_LENGTH
103      if (viaSetValue) {
104        cf.setValue(key, "0");
105      } else {
106        cf.setConfiguration(key, "0");
107      }
108      t.setColumnFamily(cf.build());
109      assertThrows(DoNotRetryIOException.class,
110        () -> TableDescriptorChecker.sanityCheck(conf, t.build()),
111        "Should reject ROWPREFIX_FIXED_LENGTH with prefix length 0 set via "
112          + (viaSetValue ? "setValue" : "setConfiguration"));
113
114      // Fix the error.
115      if (viaSetValue) {
116        cf.setValue(key, "5");
117      } else {
118        cf.setConfiguration(key, "5");
119      }
120      t.removeColumnFamily("cf".getBytes());
121      t.setColumnFamily(cf.build());
122      TableDescriptorChecker.sanityCheck(conf, t.build());
123    }
124  }
125
126  @Test
127  public void testFifoCompactionPolicyValidation() throws IOException {
128    Configuration conf = new Configuration();
129    String key = DefaultStoreEngine.DEFAULT_COMPACTION_POLICY_CLASS_KEY;
130
131    // FIFO compaction requires a non-default TTL. The policy must be honored whether it is set on
132    // the column family via setValue (the shell path since HBASE-20819) or setConfiguration.
133    for (boolean viaSetValue : new boolean[] { true, false }) {
134      ColumnFamilyDescriptorBuilder cf = ColumnFamilyDescriptorBuilder.newBuilder("cf".getBytes());
135      TableDescriptorBuilder t = TableDescriptorBuilder.newBuilder(TableName.valueOf("test"));
136
137      if (viaSetValue) {
138        cf.setValue(key, FIFOCompactionPolicy.class.getName());
139        cf.setValue(HStore.BLOCKING_STOREFILES_KEY, "1000");
140      } else {
141        cf.setConfiguration(key, FIFOCompactionPolicy.class.getName());
142        cf.setConfiguration(HStore.BLOCKING_STOREFILES_KEY, "1000");
143      }
144      t.setColumnFamily(cf.build());
145      assertThrows(DoNotRetryIOException.class,
146        () -> TableDescriptorChecker.sanityCheck(conf, t.build()),
147        "Should reject FIFO compaction with default TTL set via "
148          + (viaSetValue ? "setValue" : "setConfiguration"));
149
150      // Fix the error: FIFO needs a finite TTL and no min versions.
151      cf.setTimeToLive(3600).setMinVersions(0);
152      t.removeColumnFamily("cf".getBytes());
153      t.setColumnFamily(cf.build());
154      TableDescriptorChecker.sanityCheck(conf, t.build());
155    }
156  }
157}