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