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.client;
019
020import static org.junit.jupiter.api.Assertions.assertFalse;
021import static org.junit.jupiter.api.Assertions.assertTrue;
022import static org.junit.jupiter.api.Assertions.fail;
023import static org.mockito.ArgumentMatchers.contains;
024import static org.mockito.Mockito.mock;
025import static org.mockito.Mockito.verify;
026
027import java.io.IOException;
028import java.lang.reflect.Field;
029import org.apache.hadoop.conf.Configuration;
030import org.apache.hadoop.hbase.HBaseTestingUtil;
031import org.apache.hadoop.hbase.HConstants;
032import org.apache.hadoop.hbase.TableName;
033import org.apache.hadoop.hbase.regionserver.DataTieringManager;
034import org.apache.hadoop.hbase.regionserver.DataTieringType;
035import org.apache.hadoop.hbase.regionserver.StoreEngine;
036import org.apache.hadoop.hbase.testclassification.ClientTests;
037import org.apache.hadoop.hbase.testclassification.LargeTests;
038import org.apache.hadoop.hbase.util.Bytes;
039import org.apache.hadoop.hbase.util.TableDescriptorChecker;
040import org.junit.jupiter.api.AfterAll;
041import org.junit.jupiter.api.BeforeAll;
042import org.junit.jupiter.api.BeforeEach;
043import org.junit.jupiter.api.Tag;
044import org.junit.jupiter.api.Test;
045import org.junit.jupiter.api.TestInfo;
046import org.slf4j.Logger;
047
048@Tag(LargeTests.TAG)
049@Tag(ClientTests.TAG)
050public class TestIllegalTableDescriptor {
051
052  // NOTE: Increment tests were moved to their own class, TestIncrementsFromClientSide.
053  private static final Logger LOGGER = mock(Logger.class);
054
055  protected final static HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil();
056
057  private static byte[] FAMILY = Bytes.toBytes("testFamily");
058
059  private String methodName;
060
061  @BeforeEach
062  public void setUp(TestInfo testInfo) {
063    methodName = testInfo.getTestMethod().get().getName();
064  }
065
066  @BeforeAll
067  public static void setUpBeforeClass() throws Exception {
068    // replacing HMaster.LOG with our mock logger for verifying logging
069    Field field = TableDescriptorChecker.class.getDeclaredField("LOG");
070    field.setAccessible(true);
071    field.set(null, LOGGER);
072    Configuration conf = TEST_UTIL.getConfiguration();
073    conf.setBoolean(TableDescriptorChecker.TABLE_SANITY_CHECKS, true); // enable for below tests
074    TEST_UTIL.startMiniCluster(1);
075  }
076
077  @AfterAll
078  public static void tearDownAfterClass() throws Exception {
079    TEST_UTIL.shutdownMiniCluster();
080  }
081
082  @Test
083  public void testIllegalTableDescriptor() throws Exception {
084    TableDescriptorBuilder builder =
085      TableDescriptorBuilder.newBuilder(TableName.valueOf(methodName));
086    ColumnFamilyDescriptorBuilder cfBuilder = ColumnFamilyDescriptorBuilder.newBuilder(FAMILY);
087
088    // create table with 0 families
089    checkTableIsIllegal(builder.build());
090    checkTableIsLegal(builder.setColumnFamily(cfBuilder.build()).build());
091
092    builder.setMaxFileSize(1024); // 1K
093    checkTableIsIllegal(builder.build());
094    builder.setMaxFileSize(0);
095    checkTableIsIllegal(builder.build());
096    builder.setMaxFileSize(1024 * 1024 * 1024); // 1G
097    checkTableIsLegal(builder.build());
098
099    builder.setMemStoreFlushSize(1024);
100    checkTableIsIllegal(builder.build());
101    builder.setMemStoreFlushSize(0);
102    checkTableIsIllegal(builder.build());
103    builder.setMemStoreFlushSize(128 * 1024 * 1024); // 128M
104    checkTableIsLegal(builder.build());
105
106    builder.setRegionSplitPolicyClassName("nonexisting.foo.class");
107    checkTableIsIllegal(builder.build());
108    builder.setRegionSplitPolicyClassName(null);
109    checkTableIsLegal(builder.build());
110
111    builder.setValue(HConstants.HBASE_REGION_SPLIT_POLICY_KEY, "nonexisting.foo.class");
112    checkTableIsIllegal(builder.build());
113    builder.removeValue(Bytes.toBytes(HConstants.HBASE_REGION_SPLIT_POLICY_KEY));
114    checkTableIsLegal(builder.build());
115
116    cfBuilder.setBlocksize(0);
117    checkTableIsIllegal(builder.modifyColumnFamily(cfBuilder.build()).build());
118    cfBuilder.setBlocksize(1024 * 1024 * 128); // 128M
119    checkTableIsIllegal(builder.modifyColumnFamily(cfBuilder.build()).build());
120    cfBuilder.setBlocksize(1024);
121    checkTableIsLegal(builder.modifyColumnFamily(cfBuilder.build()).build());
122
123    cfBuilder.setTimeToLive(0);
124    checkTableIsIllegal(builder.modifyColumnFamily(cfBuilder.build()).build());
125    cfBuilder.setTimeToLive(-1);
126    checkTableIsIllegal(builder.modifyColumnFamily(cfBuilder.build()).build());
127    cfBuilder.setTimeToLive(1);
128    checkTableIsLegal(builder.modifyColumnFamily(cfBuilder.build()).build());
129
130    cfBuilder.setMinVersions(-1);
131    checkTableIsIllegal(builder.modifyColumnFamily(cfBuilder.build()).build());
132    cfBuilder.setMinVersions(3);
133    try {
134      cfBuilder.setMaxVersions(2);
135      fail();
136    } catch (IllegalArgumentException ex) {
137      // expected
138      cfBuilder.setMaxVersions(10);
139    }
140    checkTableIsLegal(builder.modifyColumnFamily(cfBuilder.build()).build());
141
142    // HBASE-13776 Setting illegal versions for HColumnDescriptor
143    // does not throw IllegalArgumentException
144    // finally, minVersions must be less than or equal to maxVersions
145    cfBuilder.setMaxVersions(4);
146    cfBuilder.setMinVersions(5);
147    checkTableIsIllegal(builder.modifyColumnFamily(cfBuilder.build()).build());
148    cfBuilder.setMinVersions(3);
149
150    cfBuilder.setScope(-1);
151    checkTableIsIllegal(builder.modifyColumnFamily(cfBuilder.build()).build());
152    cfBuilder.setScope(0);
153    checkTableIsLegal(builder.modifyColumnFamily(cfBuilder.build()).build());
154
155    cfBuilder.setValue(ColumnFamilyDescriptorBuilder.IN_MEMORY_COMPACTION, "INVALID");
156    checkTableIsIllegal(builder.modifyColumnFamily(cfBuilder.build()).build());
157    cfBuilder.setValue(ColumnFamilyDescriptorBuilder.IN_MEMORY_COMPACTION, "NONE");
158    checkTableIsLegal(builder.modifyColumnFamily(cfBuilder.build()).build());
159
160    try {
161      cfBuilder.setDFSReplication((short) -1);
162      fail("Illegal value for setDFSReplication did not throw");
163    } catch (IllegalArgumentException e) {
164      // pass
165    }
166    // set an illegal DFS replication value by hand
167    cfBuilder.setValue(ColumnFamilyDescriptorBuilder.DFS_REPLICATION, "-1");
168    checkTableIsIllegal(builder.modifyColumnFamily(cfBuilder.build()).build());
169    try {
170      cfBuilder.setDFSReplication((short) -1);
171      fail("Should throw exception if an illegal value is explicitly being set");
172    } catch (IllegalArgumentException e) {
173      // pass
174    }
175
176    // check the conf settings to disable sanity checks
177    builder.setMemStoreFlushSize(0);
178
179    // Check that logs warn on invalid table but allow it.
180    builder.setValue(TableDescriptorChecker.TABLE_SANITY_CHECKS, Boolean.FALSE.toString());
181    checkTableIsLegal(builder.build());
182
183    verify(LOGGER).warn(contains("MEMSTORE_FLUSHSIZE for table "
184      + "descriptor or \"hbase.hregion.memstore.flush.size\" (0) is too small, which might "
185      + "cause very frequent flushing."));
186  }
187
188  @Test
189  public void testIllegalTableDescriptorWithDataTiering() throws IOException {
190    // table level configuration changes
191    TableDescriptorBuilder builder =
192      TableDescriptorBuilder.newBuilder(TableName.valueOf(methodName));
193    ColumnFamilyDescriptorBuilder cfBuilder = ColumnFamilyDescriptorBuilder.newBuilder(FAMILY);
194    builder.setColumnFamily(cfBuilder.build());
195
196    // First scenario: DataTieringType set to TIME_RANGE without DateTieredStoreEngine
197    builder.setValue(DataTieringManager.DATATIERING_KEY, DataTieringType.TIME_RANGE.name());
198    checkTableIsIllegal(builder.build());
199
200    // Second scenario: DataTieringType set to TIME_RANGE with DateTieredStoreEngine
201    builder.setValue(StoreEngine.STORE_ENGINE_CLASS_KEY,
202      "org.apache.hadoop.hbase.regionserver.DateTieredStoreEngine");
203    checkTableIsLegal(builder.build());
204
205    // Third scenario: Disabling DateTieredStoreEngine while Time Range DataTiering is active
206    builder.setValue(StoreEngine.STORE_ENGINE_CLASS_KEY,
207      "org.apache.hadoop.hbase.regionserver.DefaultStoreEngine");
208    checkTableIsIllegal(builder.build());
209
210    // column family level configuration changes
211    for (boolean viaSetValue : new boolean[] { false, true }) {
212      builder = TableDescriptorBuilder.newBuilder(TableName.valueOf(methodName));
213      cfBuilder = ColumnFamilyDescriptorBuilder.newBuilder(FAMILY);
214
215      // First scenario: DataTieringType set to TIME_RANGE without DateTieredStoreEngine
216      setCfKey(cfBuilder, viaSetValue, DataTieringManager.DATATIERING_KEY,
217        DataTieringType.TIME_RANGE.name());
218      checkTableIsIllegal(builder.setColumnFamily(cfBuilder.build()).build());
219
220      // Second scenario: DataTieringType set to TIME_RANGE with DateTieredStoreEngine
221      setCfKey(cfBuilder, viaSetValue, StoreEngine.STORE_ENGINE_CLASS_KEY,
222        "org.apache.hadoop.hbase.regionserver.DateTieredStoreEngine");
223      checkTableIsLegal(builder.modifyColumnFamily(cfBuilder.build()).build());
224
225      // Third scenario: Disabling DateTieredStoreEngine while Time Range DataTiering is active
226      setCfKey(cfBuilder, viaSetValue, StoreEngine.STORE_ENGINE_CLASS_KEY,
227        "org.apache.hadoop.hbase.regionserver.DefaultStoreEngine");
228      checkTableIsIllegal(builder.modifyColumnFamily(cfBuilder.build()).build());
229    }
230  }
231
232  private static void setCfKey(ColumnFamilyDescriptorBuilder cfb, boolean viaSetValue, String key,
233    String value) {
234    if (viaSetValue) {
235      cfb.setValue(key, value);
236    } else {
237      cfb.setConfiguration(key, value);
238    }
239  }
240
241  private void checkTableIsLegal(TableDescriptor tableDescriptor) throws IOException {
242    Admin admin = TEST_UTIL.getAdmin();
243    admin.createTable(tableDescriptor);
244    assertTrue(admin.tableExists(tableDescriptor.getTableName()));
245    TEST_UTIL.deleteTable(tableDescriptor.getTableName());
246  }
247
248  private void checkTableIsIllegal(TableDescriptor tableDescriptor) throws IOException {
249    Admin admin = TEST_UTIL.getAdmin();
250    try {
251      admin.createTable(tableDescriptor);
252      fail();
253    } catch (Exception ex) {
254      // should throw ex
255    }
256    assertFalse(admin.tableExists(tableDescriptor.getTableName()));
257  }
258}