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.assertEquals;
021import static org.junit.jupiter.api.Assertions.assertFalse;
022import static org.junit.jupiter.api.Assertions.assertNull;
023import static org.junit.jupiter.api.Assertions.assertThrows;
024import static org.junit.jupiter.api.Assertions.assertTrue;
025import static org.junit.jupiter.api.Assertions.fail;
026
027import java.io.IOException;
028import java.util.regex.Pattern;
029import org.apache.hadoop.hbase.TableName;
030import org.apache.hadoop.hbase.exceptions.DeserializationException;
031import org.apache.hadoop.hbase.exceptions.HBaseException;
032import org.apache.hadoop.hbase.rsgroup.RSGroupInfo;
033import org.apache.hadoop.hbase.testclassification.MiscTests;
034import org.apache.hadoop.hbase.testclassification.SmallTests;
035import org.apache.hadoop.hbase.util.BuilderStyleTest;
036import org.apache.hadoop.hbase.util.Bytes;
037import org.junit.jupiter.api.Tag;
038import org.junit.jupiter.api.Test;
039import org.junit.jupiter.api.TestInfo;
040import org.slf4j.Logger;
041import org.slf4j.LoggerFactory;
042
043/**
044 * Test setting values in the descriptor.
045 */
046@Tag(MiscTests.TAG)
047@Tag(SmallTests.TAG)
048public class TestTableDescriptorBuilder {
049
050  private static final Logger LOG = LoggerFactory.getLogger(TestTableDescriptorBuilder.class);
051
052  public String name;
053
054  @org.junit.jupiter.api.BeforeEach
055  public void setupTestName(TestInfo testInfo) {
056    name = testInfo.getTestMethod().get().getName();
057  }
058
059  @Test
060  public void testAddCoprocessorTwice() throws IOException {
061    String cpName = "a.b.c.d";
062    assertThrows(IOException.class, () -> TableDescriptorBuilder
063      .newBuilder(TableName.META_TABLE_NAME).setCoprocessor(cpName).setCoprocessor(cpName).build());
064  }
065
066  @Test
067  public void testPb() throws DeserializationException, IOException {
068    final int v = 123;
069    TableDescriptor htd =
070      TableDescriptorBuilder.newBuilder(TableName.META_TABLE_NAME).setMaxFileSize(v)
071        .setDurability(Durability.ASYNC_WAL).setReadOnly(true).setRegionReplication(2).build();
072
073    byte[] bytes = TableDescriptorBuilder.toByteArray(htd);
074    TableDescriptor deserializedHtd = TableDescriptorBuilder.parseFrom(bytes);
075    assertEquals(htd, deserializedHtd);
076    assertEquals(v, deserializedHtd.getMaxFileSize());
077    assertTrue(deserializedHtd.isReadOnly());
078    assertEquals(Durability.ASYNC_WAL, deserializedHtd.getDurability());
079    assertEquals(2, deserializedHtd.getRegionReplication());
080  }
081
082  /**
083   * Test cps in the table description.
084   * @throws Exception if setting a coprocessor fails
085   */
086  @Test
087  public void testGetSetRemoveCP() throws Exception {
088    // simple CP
089    String className = "org.apache.hadoop.hbase.coprocessor.SimpleRegionObserver";
090    TableDescriptor desc =
091      TableDescriptorBuilder.newBuilder(TableName.valueOf(name)).setCoprocessor(className) // add
092                                                                                           // and
093                                                                                           // check
094                                                                                           // that
095                                                                                           // it is
096                                                                                           // present
097        .build();
098    assertTrue(desc.hasCoprocessor(className));
099    desc = TableDescriptorBuilder.newBuilder(desc).removeCoprocessor(className) // remove it and
100                                                                                // check that it is
101                                                                                // gone
102      .build();
103    assertFalse(desc.hasCoprocessor(className));
104  }
105
106  /**
107   * Test cps in the table description.
108   * @throws Exception if setting a coprocessor fails
109   */
110  @Test
111  public void testSetListRemoveCP() throws Exception {
112    TableDescriptor desc = TableDescriptorBuilder.newBuilder(TableName.valueOf(name)).build();
113    // Check that any coprocessor is present.
114    assertTrue(desc.getCoprocessorDescriptors().isEmpty());
115
116    // simple CP
117    String className1 = "org.apache.hadoop.hbase.coprocessor.SimpleRegionObserver";
118    String className2 = "org.apache.hadoop.hbase.coprocessor.SampleRegionWALObserver";
119    // Add the 1 coprocessor and check if present.
120    desc = TableDescriptorBuilder.newBuilder(desc).setCoprocessor(className1).build();
121    assertTrue(desc.getCoprocessorDescriptors().size() == 1);
122    assertTrue(desc.getCoprocessorDescriptors().stream().map(CoprocessorDescriptor::getClassName)
123      .anyMatch(name -> name.equals(className1)));
124    // Add the 2nd coprocessor and check if present.
125    // remove it and check that it is gone
126    desc = TableDescriptorBuilder.newBuilder(desc)
127
128      .setCoprocessor(className2).build();
129    assertTrue(desc.getCoprocessorDescriptors().size() == 2);
130    assertTrue(desc.getCoprocessorDescriptors().stream().map(CoprocessorDescriptor::getClassName)
131      .anyMatch(name -> name.equals(className2)));
132    // Remove one and check
133    desc = TableDescriptorBuilder.newBuilder(desc)
134
135      .removeCoprocessor(className1).build();
136    assertTrue(desc.getCoprocessorDescriptors().size() == 1);
137    assertFalse(desc.getCoprocessorDescriptors().stream().map(CoprocessorDescriptor::getClassName)
138      .anyMatch(name -> name.equals(className1)));
139    assertTrue(desc.getCoprocessorDescriptors().stream().map(CoprocessorDescriptor::getClassName)
140      .anyMatch(name -> name.equals(className2)));
141    // Remove the last and check
142    desc = TableDescriptorBuilder.newBuilder(desc)
143
144      .removeCoprocessor(className2).build();
145    assertTrue(desc.getCoprocessorDescriptors().isEmpty());
146    assertFalse(desc.getCoprocessorDescriptors().stream().map(CoprocessorDescriptor::getClassName)
147      .anyMatch(name -> name.equals(className1)));
148    assertFalse(desc.getCoprocessorDescriptors().stream().map(CoprocessorDescriptor::getClassName)
149      .anyMatch(name -> name.equals(className2)));
150  }
151
152  /**
153   * Test removing cps in the table description that does not exist
154   * @throws Exception if removing a coprocessor fails other than IllegalArgumentException
155   */
156  @Test
157  public void testRemoveNonExistingCoprocessor() throws Exception {
158    String className = "org.apache.hadoop.hbase.coprocessor.SimpleRegionObserver";
159    TableDescriptor desc = TableDescriptorBuilder.newBuilder(TableName.valueOf(name)).build();
160    assertFalse(desc.hasCoprocessor(className));
161    assertThrows(IllegalArgumentException.class,
162      () -> TableDescriptorBuilder.newBuilder(desc).removeCoprocessor(className).build());
163  }
164
165  /**
166   * Test that we add and remove strings from settings properly.
167   */
168  @Test
169  public void testRemoveString() {
170    byte[] key = Bytes.toBytes("Some");
171    byte[] value = Bytes.toBytes("value");
172    TableDescriptor desc =
173      TableDescriptorBuilder.newBuilder(TableName.valueOf(name)).setValue(key, value).build();
174    assertTrue(Bytes.equals(value, desc.getValue(key)));
175    desc = TableDescriptorBuilder.newBuilder(desc).removeValue(key).build();
176    assertTrue(desc.getValue(key) == null);
177  }
178
179  String[] legalTableNames = { "foo", "with-dash_under.dot", "_under_start_ok",
180    "with-dash.with_underscore", "02-01-2012.my_table_01-02", "xyz._mytable_", "9_9_0.table_02",
181    "dot1.dot2.table", "new.-mytable", "with-dash.with.dot", "legal..t2", "legal..legal.t2",
182    "trailingdots..", "trailing.dots...", "ns:mytable", "ns:_mytable_", "ns:my_table_01-02" };
183  String[] illegalTableNames = { ".dot_start_illegal", "-dash_start_illegal", "spaces not ok",
184    "-dash-.start_illegal", "new.table with space", "01 .table", "ns:-illegaldash",
185    "new:.illegaldot", "new:illegalcolon1:", "new:illegalcolon1:2" };
186
187  @Test
188  public void testLegalTableNames() {
189    for (String tn : legalTableNames) {
190      TableName.isLegalFullyQualifiedTableName(Bytes.toBytes(tn));
191    }
192  }
193
194  @Test
195  public void testIllegalTableNames() {
196    for (String tn : illegalTableNames) {
197      try {
198        TableName.isLegalFullyQualifiedTableName(Bytes.toBytes(tn));
199        fail("invalid tablename " + tn + " should have failed");
200      } catch (Exception e) {
201        // expected
202      }
203    }
204  }
205
206  @Test
207  public void testLegalTableNamesRegex() {
208    for (String tn : legalTableNames) {
209      TableName tName = TableName.valueOf(tn);
210      assertTrue(Pattern.matches(TableName.VALID_USER_TABLE_REGEX, tName.getNameAsString()),
211        "Testing: '" + tn + "'");
212    }
213  }
214
215  @Test
216  public void testIllegalTableNamesRegex() {
217    for (String tn : illegalTableNames) {
218      LOG.info("Testing: '" + tn + "'");
219      assertFalse(Pattern.matches(TableName.VALID_USER_TABLE_REGEX, tn));
220    }
221  }
222
223  /**
224   * Test default value handling for maxFileSize
225   */
226  @Test
227  public void testGetMaxFileSize() {
228    TableDescriptor desc = TableDescriptorBuilder.newBuilder(TableName.valueOf(name)).build();
229    assertEquals(-1, desc.getMaxFileSize());
230    desc = TableDescriptorBuilder.newBuilder(TableName.valueOf(name)).setMaxFileSize(1111L).build();
231    assertEquals(1111L, desc.getMaxFileSize());
232  }
233
234  @Test
235  public void testSetMaxFileSize() throws HBaseException {
236    TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(TableName.valueOf(name));
237
238    String maxFileSize = "1073741824";
239    builder.setMaxFileSize(maxFileSize);
240    assertEquals(1073741824, builder.build().getMaxFileSize());
241
242    maxFileSize = "1GB";
243    builder.setMaxFileSize(maxFileSize);
244    assertEquals(1073741824, builder.build().getMaxFileSize());
245
246    maxFileSize = "10GB 25MB";
247    builder.setMaxFileSize(maxFileSize);
248    assertEquals(10763632640L, builder.build().getMaxFileSize());
249
250    // ignore case
251    maxFileSize = "10GB 512mb 512KB 512b";
252    builder.setMaxFileSize(maxFileSize);
253    assertEquals(11274813952L, builder.build().getMaxFileSize());
254
255    maxFileSize = "10737942528 B (10GB 512KB)";
256    builder.setMaxFileSize(maxFileSize);
257    assertEquals(10737942528L, builder.build().getMaxFileSize());
258  }
259
260  /**
261   * Test default value handling for memStoreFlushSize
262   */
263  @Test
264  public void testGetMemStoreFlushSize() {
265    TableDescriptor desc = TableDescriptorBuilder.newBuilder(TableName.valueOf(name)).build();
266    assertEquals(-1, desc.getMemStoreFlushSize());
267    desc = TableDescriptorBuilder.newBuilder(TableName.valueOf(name)).setMemStoreFlushSize(1111L)
268      .build();
269    assertEquals(1111L, desc.getMemStoreFlushSize());
270  }
271
272  @Test
273  public void testSetMemStoreFlushSize() throws HBaseException {
274    TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(TableName.valueOf(name));
275
276    String memstoreFlushSize = "1073741824";
277    builder.setMemStoreFlushSize(memstoreFlushSize);
278    assertEquals(1073741824, builder.build().getMemStoreFlushSize());
279
280    memstoreFlushSize = "1GB";
281    builder.setMemStoreFlushSize(memstoreFlushSize);
282    assertEquals(1073741824, builder.build().getMemStoreFlushSize());
283
284    memstoreFlushSize = "10GB 25MB";
285    builder.setMemStoreFlushSize(memstoreFlushSize);
286    assertEquals(10763632640L, builder.build().getMemStoreFlushSize());
287
288    // ignore case
289    memstoreFlushSize = "10GB 512mb 512KB 512b";
290    builder.setMemStoreFlushSize(memstoreFlushSize);
291    assertEquals(11274813952L, builder.build().getMemStoreFlushSize());
292
293    memstoreFlushSize = "10737942528 B (10GB 512KB)";
294    builder.setMemStoreFlushSize(memstoreFlushSize);
295    assertEquals(10737942528L, builder.build().getMemStoreFlushSize());
296  }
297
298  @Test
299  public void testClassMethodsAreBuilderStyle() {
300    BuilderStyleTest.assertClassesAreBuilderStyle(TableDescriptorBuilder.class);
301  }
302
303  @Test
304  public void testModifyFamily() {
305    byte[] familyName = Bytes.toBytes("cf");
306    ColumnFamilyDescriptor hcd = ColumnFamilyDescriptorBuilder.newBuilder(familyName)
307      .setBlocksize(1000).setDFSReplication((short) 3).build();
308    TableDescriptor htd =
309      TableDescriptorBuilder.newBuilder(TableName.valueOf(name)).setColumnFamily(hcd).build();
310
311    assertEquals(1000, htd.getColumnFamily(familyName).getBlocksize());
312    assertEquals(3, htd.getColumnFamily(familyName).getDFSReplication());
313    hcd = ColumnFamilyDescriptorBuilder.newBuilder(familyName).setBlocksize(2000)
314      .setDFSReplication((short) 1).build();
315    htd = TableDescriptorBuilder.newBuilder(htd).modifyColumnFamily(hcd).build();
316    assertEquals(2000, htd.getColumnFamily(familyName).getBlocksize());
317    assertEquals(1, htd.getColumnFamily(familyName).getDFSReplication());
318  }
319
320  @Test
321  public void testModifyInexistentFamily() {
322    byte[] familyName = Bytes.toBytes("cf");
323    assertThrows(IllegalArgumentException.class,
324      () -> TableDescriptorBuilder.newBuilder(TableName.valueOf(name))
325        .modifyColumnFamily(ColumnFamilyDescriptorBuilder.of(familyName)).build());
326  }
327
328  @Test
329  public void testAddDuplicateFamilies() {
330    byte[] familyName = Bytes.toBytes("cf");
331    ColumnFamilyDescriptor hcd =
332      ColumnFamilyDescriptorBuilder.newBuilder(familyName).setBlocksize(1000).build();
333    TableDescriptor htd =
334      TableDescriptorBuilder.newBuilder(TableName.valueOf(name)).setColumnFamily(hcd).build();
335    assertEquals(1000, htd.getColumnFamily(familyName).getBlocksize());
336    hcd = ColumnFamilyDescriptorBuilder.newBuilder(familyName).setBlocksize(2000).build();
337    // add duplicate column
338    ColumnFamilyDescriptor finalHcd = hcd;
339    assertThrows(IllegalArgumentException.class,
340      () -> TableDescriptorBuilder.newBuilder(htd).setColumnFamily(finalHcd).build());
341  }
342
343  @Test
344  public void testPriority() {
345    TableDescriptor htd =
346      TableDescriptorBuilder.newBuilder(TableName.valueOf(name)).setPriority(42).build();
347    assertEquals(42, htd.getPriority());
348  }
349
350  @Test
351  public void testStringCustomizedValues() throws HBaseException {
352    byte[] familyName = Bytes.toBytes("cf");
353    ColumnFamilyDescriptor hcd =
354      ColumnFamilyDescriptorBuilder.newBuilder(familyName).setBlocksize(131072).build();
355    TableDescriptor htd = TableDescriptorBuilder.newBuilder(TableName.valueOf(name))
356      .setColumnFamily(hcd).setDurability(Durability.ASYNC_WAL).build();
357
358    assertEquals(
359      "'testStringCustomizedValues', " + "{TABLE_ATTRIBUTES => {DURABILITY => 'ASYNC_WAL'}}, "
360        + "{NAME => 'cf', BLOCKSIZE => '131072 B (128KB)'}",
361      htd.toStringCustomizedValues());
362
363    htd = TableDescriptorBuilder.newBuilder(htd).setMaxFileSize("10737942528")
364      .setMemStoreFlushSize("256MB").setErasureCodingPolicy("RS-6-3-1024k").build();
365    assertEquals(
366      "'testStringCustomizedValues', " + "{TABLE_ATTRIBUTES => {DURABILITY => 'ASYNC_WAL', "
367        + "ERASURE_CODING_POLICY => 'RS-6-3-1024k', MAX_FILESIZE => '10737942528 B (10GB 512KB)', "
368        + "MEMSTORE_FLUSHSIZE => '268435456 B (256MB)'}}, "
369        + "{NAME => 'cf', BLOCKSIZE => '131072 B (128KB)'}",
370      htd.toStringCustomizedValues());
371  }
372
373  @Test
374  public void testGetSetRegionServerGroup() {
375    String groupName = name;
376    TableDescriptor htd = TableDescriptorBuilder.newBuilder(TableName.valueOf(name))
377      .setRegionServerGroup(groupName).build();
378    assertEquals(htd.getValue(RSGroupInfo.TABLE_DESC_PROP_GROUP), groupName);
379    htd = TableDescriptorBuilder.newBuilder(htd).setRegionServerGroup(null).build();
380    assertNull(htd.getValue(RSGroupInfo.TABLE_DESC_PROP_GROUP));
381  }
382
383  @Test
384  public void testSetEmptyValue() {
385    TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(TableName.valueOf(name));
386    String testValue = "TestValue";
387    // test setValue
388    builder.setValue(testValue, "2");
389    assertEquals("2", builder.build().getValue(testValue));
390    builder.setValue(testValue, "");
391    assertNull(builder.build().getValue(Bytes.toBytes(testValue)));
392
393    // test setFlushPolicyClassName
394    builder.setFlushPolicyClassName("class");
395    assertEquals("class", builder.build().getFlushPolicyClassName());
396    builder.setFlushPolicyClassName("");
397    assertNull(builder.build().getFlushPolicyClassName());
398  }
399}