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.assertTrue;
023import static org.junit.jupiter.api.Assertions.fail;
024
025import java.util.Optional;
026import java.util.Set;
027import java.util.function.Supplier;
028import org.apache.hadoop.fs.Path;
029import org.apache.hadoop.hbase.HBaseParameterizedTestTemplate;
030import org.apache.hadoop.hbase.TableName;
031import org.apache.hadoop.hbase.master.MasterFileSystem;
032import org.apache.hadoop.hbase.testclassification.ClientTests;
033import org.apache.hadoop.hbase.testclassification.LargeTests;
034import org.apache.hadoop.hbase.util.Bytes;
035import org.apache.hadoop.hbase.util.CommonFSUtils;
036import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
037import org.apache.hadoop.hbase.util.FSTableDescriptors;
038import org.junit.Assert;
039import org.junit.jupiter.api.AfterAll;
040import org.junit.jupiter.api.BeforeAll;
041import org.junit.jupiter.api.Tag;
042import org.junit.jupiter.api.TestTemplate;
043
044/**
045 * Class to test asynchronous table admin operations
046 * @see TestAsyncTableAdminApi This test and it used to be joined it was taking longer than our ten
047 *      minute timeout so they were split.
048 */
049@Tag(LargeTests.TAG)
050@Tag(ClientTests.TAG)
051@HBaseParameterizedTestTemplate(name = "{index}: policy = {0}")
052public class TestAsyncTableAdminApi2 extends TestAsyncAdminBase {
053
054  public TestAsyncTableAdminApi2(Supplier<AsyncAdmin> admin) {
055    super(admin);
056  }
057
058  @BeforeAll
059  public static void setUpBeforeClass() throws Exception {
060    TestAsyncAdminBase.setUpBeforeClass();
061  }
062
063  @AfterAll
064  public static void tearDownAfterClass() throws Exception {
065    TestAsyncAdminBase.tearDownAfterClass();
066  }
067
068  @TestTemplate
069  public void testDisableCatalogTable() throws Exception {
070    try {
071      this.admin.disableTable(TableName.META_TABLE_NAME).join();
072      fail("Expected to throw ConstraintException");
073    } catch (Exception e) {
074    }
075    // Before the fix for HBASE-6146, the below table creation was failing as the hbase:meta table
076    // actually getting disabled by the disableTable() call.
077    createTableWithDefaultConf(tableName);
078  }
079
080  @TestTemplate
081  public void testAddColumnFamily() throws Exception {
082    // Create a table with two families
083    TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(tableName);
084    builder.setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY_0));
085    admin.createTable(builder.build()).join();
086    admin.disableTable(tableName).join();
087    // Verify the table descriptor
088    verifyTableDescriptor(tableName, FAMILY_0);
089
090    // Modify the table removing one family and verify the descriptor
091    admin.addColumnFamily(tableName, ColumnFamilyDescriptorBuilder.of(FAMILY_1)).join();
092    verifyTableDescriptor(tableName, FAMILY_0, FAMILY_1);
093  }
094
095  @TestTemplate
096  public void testAddSameColumnFamilyTwice() throws Exception {
097    // Create a table with one families
098    TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(tableName);
099    builder.setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY_0));
100    admin.createTable(builder.build()).join();
101    admin.disableTable(tableName).join();
102    // Verify the table descriptor
103    verifyTableDescriptor(tableName, FAMILY_0);
104
105    // Modify the table removing one family and verify the descriptor
106    admin.addColumnFamily(tableName, ColumnFamilyDescriptorBuilder.of(FAMILY_1)).join();
107    verifyTableDescriptor(tableName, FAMILY_0, FAMILY_1);
108
109    try {
110      // Add same column family again - expect failure
111      this.admin.addColumnFamily(tableName, ColumnFamilyDescriptorBuilder.of(FAMILY_1)).join();
112      Assert.fail("Delete a non-exist column family should fail");
113    } catch (Exception e) {
114      // Expected.
115    }
116  }
117
118  @TestTemplate
119  public void testModifyColumnFamily() throws Exception {
120    TableDescriptorBuilder tdBuilder = TableDescriptorBuilder.newBuilder(tableName);
121    ColumnFamilyDescriptor cfd = ColumnFamilyDescriptorBuilder.of(FAMILY_0);
122    int blockSize = cfd.getBlocksize();
123    admin.createTable(tdBuilder.setColumnFamily(cfd).build()).join();
124    admin.disableTable(tableName).join();
125    // Verify the table descriptor
126    verifyTableDescriptor(tableName, FAMILY_0);
127
128    int newBlockSize = 2 * blockSize;
129    cfd = ColumnFamilyDescriptorBuilder.newBuilder(FAMILY_0).setBlocksize(newBlockSize).build();
130    // Modify colymn family
131    admin.modifyColumnFamily(tableName, cfd).join();
132
133    TableDescriptor htd = admin.getDescriptor(tableName).get();
134    ColumnFamilyDescriptor hcfd = htd.getColumnFamily(FAMILY_0);
135    assertTrue(hcfd.getBlocksize() == newBlockSize);
136  }
137
138  @TestTemplate
139  public void testModifyNonExistingColumnFamily() throws Exception {
140    TableDescriptorBuilder tdBuilder = TableDescriptorBuilder.newBuilder(tableName);
141    ColumnFamilyDescriptor cfd = ColumnFamilyDescriptorBuilder.of(FAMILY_0);
142    int blockSize = cfd.getBlocksize();
143    admin.createTable(tdBuilder.setColumnFamily(cfd).build()).join();
144    admin.disableTable(tableName).join();
145    // Verify the table descriptor
146    verifyTableDescriptor(tableName, FAMILY_0);
147
148    int newBlockSize = 2 * blockSize;
149    cfd = ColumnFamilyDescriptorBuilder.newBuilder(FAMILY_1).setBlocksize(newBlockSize).build();
150
151    // Modify a column family that is not in the table.
152    try {
153      admin.modifyColumnFamily(tableName, cfd).join();
154      Assert.fail("Modify a non-exist column family should fail");
155    } catch (Exception e) {
156      // Expected.
157    }
158  }
159
160  @TestTemplate
161  public void testDeleteColumnFamily() throws Exception {
162    // Create a table with two families
163    TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(tableName);
164    builder.setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY_0))
165      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY_1));
166    admin.createTable(builder.build()).join();
167    admin.disableTable(tableName).join();
168    // Verify the table descriptor
169    verifyTableDescriptor(tableName, FAMILY_0, FAMILY_1);
170
171    // Modify the table removing one family and verify the descriptor
172    admin.deleteColumnFamily(tableName, FAMILY_1).join();
173    verifyTableDescriptor(tableName, FAMILY_0);
174  }
175
176  @TestTemplate
177  public void testDeleteSameColumnFamilyTwice() throws Exception {
178    // Create a table with two families
179    TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(tableName);
180    builder.setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY_0))
181      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY_1));
182    admin.createTable(builder.build()).join();
183    admin.disableTable(tableName).join();
184    // Verify the table descriptor
185    verifyTableDescriptor(tableName, FAMILY_0, FAMILY_1);
186
187    // Modify the table removing one family and verify the descriptor
188    admin.deleteColumnFamily(tableName, FAMILY_1).join();
189    verifyTableDescriptor(tableName, FAMILY_0);
190
191    try {
192      // Delete again - expect failure
193      admin.deleteColumnFamily(tableName, FAMILY_1).join();
194      Assert.fail("Delete a non-exist column family should fail");
195    } catch (Exception e) {
196      // Expected.
197    }
198  }
199
200  private void verifyTableDescriptor(final TableName tableName, final byte[]... families)
201    throws Exception {
202    // Verify descriptor from master
203    TableDescriptor htd = admin.getDescriptor(tableName).get();
204    verifyTableDescriptor(htd, tableName, families);
205
206    // Verify descriptor from HDFS
207    MasterFileSystem mfs = TEST_UTIL.getMiniHBaseCluster().getMaster().getMasterFileSystem();
208    Path tableDir = CommonFSUtils.getTableDir(mfs.getRootDir(), tableName);
209    TableDescriptor td = FSTableDescriptors.getTableDescriptorFromFs(mfs.getFileSystem(), tableDir);
210    verifyTableDescriptor(td, tableName, families);
211  }
212
213  private void verifyTableDescriptor(final TableDescriptor htd, final TableName tableName,
214    final byte[]... families) {
215    Set<byte[]> htdFamilies = htd.getColumnFamilyNames();
216    assertEquals(tableName, htd.getTableName());
217    assertEquals(families.length, htdFamilies.size());
218    for (byte[] familyName : families) {
219      assertTrue(htdFamilies.contains(familyName), "Expected family " + Bytes.toString(familyName));
220    }
221  }
222
223  @TestTemplate
224  public void testCompactionTimestamps() throws Exception {
225    createTableWithDefaultConf(tableName);
226    AsyncTable<?> table = ASYNC_CONN.getTable(tableName);
227    Optional<Long> ts = admin.getLastMajorCompactionTimestamp(tableName).get();
228    assertFalse(ts.isPresent());
229    Put p = new Put(Bytes.toBytes("row1"));
230    p.addColumn(FAMILY, Bytes.toBytes("q"), Bytes.toBytes("v"));
231    table.put(p).join();
232    ts = admin.getLastMajorCompactionTimestamp(tableName).get();
233    // no files written -> no data
234    assertFalse(ts.isPresent());
235
236    admin.flush(tableName).join();
237    ts = admin.getLastMajorCompactionTimestamp(tableName).get();
238    // still 0, we flushed a file, but no major compaction happened
239    assertFalse(ts.isPresent());
240
241    byte[] regionName = ASYNC_CONN.getRegionLocator(tableName)
242      .getRegionLocation(Bytes.toBytes("row1")).get().getRegion().getRegionName();
243    Optional<Long> ts1 = admin.getLastMajorCompactionTimestampForRegion(regionName).get();
244    assertFalse(ts1.isPresent());
245    p = new Put(Bytes.toBytes("row2"));
246    p.addColumn(FAMILY, Bytes.toBytes("q"), Bytes.toBytes("v"));
247    table.put(p).join();
248    admin.flush(tableName).join();
249    ts1 = admin.getLastMajorCompactionTimestamp(tableName).get();
250    // make sure the region API returns the same value, as the old file is still around
251    assertFalse(ts1.isPresent());
252
253    for (int i = 0; i < 3; i++) {
254      table.put(p).join();
255      admin.flush(tableName).join();
256    }
257    admin.majorCompact(tableName).join();
258    long curt = EnvironmentEdgeManager.currentTime();
259    long waitTime = 10000;
260    long endt = curt + waitTime;
261    CompactionState state = admin.getCompactionState(tableName).get();
262    LOG.info("Current compaction state 1 is " + state);
263    while (state == CompactionState.NONE && curt < endt) {
264      Thread.sleep(100);
265      state = admin.getCompactionState(tableName).get();
266      curt = EnvironmentEdgeManager.currentTime();
267      LOG.info("Current compaction state 2 is " + state);
268    }
269    // Now, should have the right compaction state, let's wait until the compaction is done
270    if (state == CompactionState.MAJOR) {
271      state = admin.getCompactionState(tableName).get();
272      LOG.info("Current compaction state 3 is " + state);
273      while (state != CompactionState.NONE && curt < endt) {
274        Thread.sleep(10);
275        state = admin.getCompactionState(tableName).get();
276        LOG.info("Current compaction state 4 is " + state);
277      }
278    }
279    // Sleep to wait region server report
280    Thread
281      .sleep(TEST_UTIL.getConfiguration().getInt("hbase.regionserver.msginterval", 3 * 1000) * 2);
282
283    ts = admin.getLastMajorCompactionTimestamp(tableName).get();
284    // after a compaction our earliest timestamp will have progressed forward
285    assertTrue(ts.isPresent());
286    assertTrue(ts.get() > 0);
287    // region api still the same
288    ts1 = admin.getLastMajorCompactionTimestampForRegion(regionName).get();
289    assertTrue(ts1.isPresent());
290    assertEquals(ts.get(), ts1.get());
291    table.put(p).join();
292    admin.flush(tableName).join();
293    ts = admin.getLastMajorCompactionTimestamp(tableName).join();
294    assertTrue(ts.isPresent());
295    assertEquals(ts.get(), ts1.get());
296    ts1 = admin.getLastMajorCompactionTimestampForRegion(regionName).get();
297    assertTrue(ts1.isPresent());
298    assertEquals(ts.get(), ts1.get());
299  }
300}