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.master.procedure;
019
020import static org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory.TRACKER_IMPL;
021import static org.junit.jupiter.api.Assertions.assertEquals;
022import static org.junit.jupiter.api.Assertions.assertFalse;
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 org.apache.hadoop.conf.Configuration;
029import org.apache.hadoop.fs.FileSystem;
030import org.apache.hadoop.fs.Path;
031import org.apache.hadoop.hbase.DoNotRetryIOException;
032import org.apache.hadoop.hbase.HBaseIOException;
033import org.apache.hadoop.hbase.TableExistsException;
034import org.apache.hadoop.hbase.TableName;
035import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
036import org.apache.hadoop.hbase.client.RegionInfo;
037import org.apache.hadoop.hbase.client.TableDescriptor;
038import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
039import org.apache.hadoop.hbase.master.MasterFileSystem;
040import org.apache.hadoop.hbase.procedure2.Procedure;
041import org.apache.hadoop.hbase.procedure2.ProcedureExecutor;
042import org.apache.hadoop.hbase.procedure2.ProcedureSuspendedException;
043import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility;
044import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory;
045import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerForTest;
046import org.apache.hadoop.hbase.testclassification.MasterTests;
047import org.apache.hadoop.hbase.testclassification.MediumTests;
048import org.apache.hadoop.hbase.util.Bytes;
049import org.apache.hadoop.hbase.util.CommonFSUtils;
050import org.apache.hadoop.hbase.util.FSUtils;
051import org.apache.hadoop.hbase.util.ModifyRegionUtils;
052import org.apache.hadoop.hbase.util.TableDescriptorChecker;
053import org.junit.jupiter.api.AfterAll;
054import org.junit.jupiter.api.BeforeAll;
055import org.junit.jupiter.api.BeforeEach;
056import org.junit.jupiter.api.Tag;
057import org.junit.jupiter.api.Test;
058import org.junit.jupiter.api.TestInfo;
059
060import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos;
061
062@Tag(MasterTests.TAG)
063@Tag(MediumTests.TAG)
064public class TestCreateTableProcedure extends TestTableDDLProcedureBase {
065
066  private static final String F1 = "f1";
067  private static final String F2 = "f2";
068  private String testMethodName;
069
070  @BeforeAll
071  public static void setupCluster() throws Exception {
072    TestTableDDLProcedureBase.setupCluster();
073  }
074
075  @AfterAll
076  public static void cleanupTest() throws Exception {
077    TestTableDDLProcedureBase.cleanupTest();
078  }
079
080  @BeforeEach
081  public void setTestMethod(TestInfo testInfo) {
082    testMethodName = testInfo.getTestMethod().get().getName();
083  }
084
085  @Test
086  public void testSimpleCreate() throws Exception {
087    final TableName tableName = TableName.valueOf(testMethodName);
088    final byte[][] splitKeys = null;
089    testSimpleCreate(tableName, splitKeys);
090  }
091
092  @Test
093  public void testSimpleCreateWithSplits() throws Exception {
094    final TableName tableName = TableName.valueOf(testMethodName);
095    final byte[][] splitKeys =
096      new byte[][] { Bytes.toBytes("a"), Bytes.toBytes("b"), Bytes.toBytes("c") };
097    testSimpleCreate(tableName, splitKeys);
098  }
099
100  private void testSimpleCreate(final TableName tableName, byte[][] splitKeys) throws Exception {
101    RegionInfo[] regions = MasterProcedureTestingUtility.createTable(getMasterProcedureExecutor(),
102      tableName, splitKeys, F1, F2);
103    MasterProcedureTestingUtility.validateTableCreation(getMaster(), tableName, regions, F1, F2);
104  }
105
106  @Test
107  public void testCreateWithTrackImpl() throws Exception {
108    final TableName tableName = TableName.valueOf(testMethodName);
109    ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
110    TableDescriptor htd = MasterProcedureTestingUtility.createHTD(tableName, F1);
111    String trackerName = StoreFileTrackerForTest.class.getName();
112    htd = TableDescriptorBuilder.newBuilder(htd).setValue(TRACKER_IMPL, trackerName).build();
113    RegionInfo[] regions = ModifyRegionUtils.createRegionInfos(htd, null);
114    long procId = ProcedureTestingUtility.submitAndWait(procExec,
115      new CreateTableProcedure(procExec.getEnvironment(), htd, regions));
116    ProcedureTestingUtility.assertProcNotFailed(procExec.getResult(procId));
117    htd = getMaster().getTableDescriptors().get(tableName);
118    assertEquals(trackerName, htd.getValue(TRACKER_IMPL));
119  }
120
121  @Test
122  public void testCreateWithFileBasedStoreTrackerImpl() throws Exception {
123    ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
124    procExec.getEnvironment().getMasterConfiguration().set(StoreFileTrackerFactory.TRACKER_IMPL,
125      StoreFileTrackerFactory.Trackers.FILE.name());
126    final TableName tableName = TableName.valueOf(testMethodName);
127    TableDescriptor htd = MasterProcedureTestingUtility.createHTD(tableName, F1);
128    RegionInfo[] regions = ModifyRegionUtils.createRegionInfos(htd, null);
129    long procId = ProcedureTestingUtility.submitAndWait(procExec,
130      new CreateTableProcedure(procExec.getEnvironment(), htd, regions));
131    ProcedureTestingUtility.assertProcNotFailed(procExec.getResult(procId));
132    htd = getMaster().getTableDescriptors().get(tableName);
133    assertEquals(StoreFileTrackerFactory.Trackers.FILE.name(), htd.getValue(TRACKER_IMPL));
134  }
135
136  @Test
137  public void testCreateWithoutColumnFamily() throws Exception {
138    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
139    final TableName tableName = TableName.valueOf(testMethodName);
140    // create table with 0 families will fail
141    final TableDescriptorBuilder builder =
142      TableDescriptorBuilder.newBuilder(MasterProcedureTestingUtility.createHTD(tableName));
143
144    // disable sanity check
145    builder.setValue(TableDescriptorChecker.TABLE_SANITY_CHECKS, Boolean.FALSE.toString());
146    TableDescriptor htd = builder.build();
147    final RegionInfo[] regions = ModifyRegionUtils.createRegionInfos(htd, null);
148
149    long procId = ProcedureTestingUtility.submitAndWait(procExec,
150      new CreateTableProcedure(procExec.getEnvironment(), htd, regions));
151    final Procedure<?> result = procExec.getResult(procId);
152    assertEquals(true, result.isFailed());
153    Throwable cause = ProcedureTestingUtility.getExceptionCause(result);
154    assertTrue(cause instanceof DoNotRetryIOException,
155      "expected DoNotRetryIOException, got " + cause);
156  }
157
158  @Test
159  public void testCreateExisting() throws Exception {
160    final TableName tableName = TableName.valueOf(testMethodName);
161    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
162    final TableDescriptor htd = MasterProcedureTestingUtility.createHTD(tableName, "f");
163    final RegionInfo[] regions = ModifyRegionUtils.createRegionInfos(htd, null);
164
165    // create the table
166    long procId1 =
167      procExec.submitProcedure(new CreateTableProcedure(procExec.getEnvironment(), htd, regions));
168
169    // create another with the same name
170    ProcedurePrepareLatch latch2 = new ProcedurePrepareLatch.CompatibilityLatch();
171    long procId2 = procExec
172      .submitProcedure(new CreateTableProcedure(procExec.getEnvironment(), htd, regions, latch2));
173
174    ProcedureTestingUtility.waitProcedure(procExec, procId1);
175    ProcedureTestingUtility.assertProcNotFailed(procExec.getResult(procId1));
176
177    ProcedureTestingUtility.waitProcedure(procExec, procId2);
178    assertThrows(TableExistsException.class, latch2::await);
179  }
180
181  @Test
182  public void testRecoveryAndDoubleExecution() throws Exception {
183    final TableName tableName = TableName.valueOf(testMethodName);
184
185    // create the table
186    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
187    ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true);
188
189    // Start the Create procedure && kill the executor
190    byte[][] splitKeys = null;
191    TableDescriptor htd = MasterProcedureTestingUtility.createHTD(tableName, "f1", "f2");
192    RegionInfo[] regions = ModifyRegionUtils.createRegionInfos(htd, splitKeys);
193    long procId =
194      procExec.submitProcedure(new CreateTableProcedure(procExec.getEnvironment(), htd, regions));
195
196    // Restart the executor and execute the step twice
197    MasterProcedureTestingUtility.testRecoveryAndDoubleExecution(procExec, procId);
198    MasterProcedureTestingUtility.validateTableCreation(getMaster(), tableName, regions, F1, F2);
199  }
200
201  @Test
202  public void testRollbackAndDoubleExecution() throws Exception {
203    final TableName tableName = TableName.valueOf(testMethodName);
204    testRollbackAndDoubleExecution(TableDescriptorBuilder
205      .newBuilder(MasterProcedureTestingUtility.createHTD(tableName, F1, F2)));
206  }
207
208  @Test
209  public void testRollbackAndDoubleExecutionOnMobTable() throws Exception {
210    final TableName tableName = TableName.valueOf(testMethodName);
211    TableDescriptor htd = MasterProcedureTestingUtility.createHTD(tableName, F1, F2);
212    TableDescriptorBuilder builder =
213      TableDescriptorBuilder.newBuilder(htd).modifyColumnFamily(ColumnFamilyDescriptorBuilder
214        .newBuilder(htd.getColumnFamily(Bytes.toBytes(F1))).setMobEnabled(true).build());
215    testRollbackAndDoubleExecution(builder);
216  }
217
218  private void testRollbackAndDoubleExecution(TableDescriptorBuilder builder) throws Exception {
219    // create the table
220    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
221    ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true);
222
223    // Start the Create procedure && kill the executor
224    final byte[][] splitKeys =
225      new byte[][] { Bytes.toBytes("a"), Bytes.toBytes("b"), Bytes.toBytes("c") };
226    builder.setRegionReplication(3);
227    TableDescriptor htd = builder.build();
228    RegionInfo[] regions = ModifyRegionUtils.createRegionInfos(htd, splitKeys);
229    long procId =
230      procExec.submitProcedure(new CreateTableProcedure(procExec.getEnvironment(), htd, regions));
231
232    int lastStep = 2; // failing before CREATE_TABLE_WRITE_FS_LAYOUT
233    MasterProcedureTestingUtility.testRollbackAndDoubleExecution(procExec, procId, lastStep);
234
235    TableName tableName = htd.getTableName();
236    MasterProcedureTestingUtility.validateTableDeletion(getMaster(), tableName);
237
238    // are we able to create the table after a rollback?
239    resetProcExecutorTestingKillFlag();
240    testSimpleCreate(tableName, splitKeys);
241  }
242
243  public static class CreateTableProcedureOnHDFSFailure extends CreateTableProcedure {
244    private boolean failOnce = false;
245
246    public CreateTableProcedureOnHDFSFailure() {
247      // Required by the Procedure framework to create the procedure on replay
248      super();
249    }
250
251    public CreateTableProcedureOnHDFSFailure(final MasterProcedureEnv env,
252      final TableDescriptor tableDescriptor, final RegionInfo[] newRegions)
253      throws HBaseIOException {
254      super(env, tableDescriptor, newRegions);
255    }
256
257    @Override
258    protected Flow executeFromState(MasterProcedureEnv env,
259      MasterProcedureProtos.CreateTableState state)
260      throws InterruptedException, ProcedureSuspendedException {
261
262      if (
263        !failOnce && state == MasterProcedureProtos.CreateTableState.CREATE_TABLE_WRITE_FS_LAYOUT
264      ) {
265        try {
266          // To emulate an HDFS failure, create only the first region directory
267          RegionInfo regionInfo = getFirstRegionInfo();
268          Configuration conf = env.getMasterConfiguration();
269          MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem();
270          Path tempdir = mfs.getTempDir();
271          Path tableDir = CommonFSUtils.getTableDir(tempdir, regionInfo.getTable());
272          Path regionDir = FSUtils.getRegionDirFromTableDir(tableDir, regionInfo);
273          FileSystem fs = FileSystem.get(conf);
274          fs.mkdirs(regionDir);
275
276          failOnce = true;
277          return Flow.HAS_MORE_STATE;
278        } catch (IOException e) {
279          fail("failed to create a region directory: " + e);
280        }
281      }
282
283      return super.executeFromState(env, state);
284    }
285  }
286
287  @Test
288  public void testOnHDFSFailure() throws Exception {
289    final TableName tableName = TableName.valueOf(testMethodName);
290
291    // create the table
292    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
293    final byte[][] splitKeys =
294      new byte[][] { Bytes.toBytes("a"), Bytes.toBytes("b"), Bytes.toBytes("c") };
295    TableDescriptor htd = MasterProcedureTestingUtility.createHTD(tableName, "f1", "f2");
296    RegionInfo[] regions = ModifyRegionUtils.createRegionInfos(htd, splitKeys);
297    long procId = ProcedureTestingUtility.submitAndWait(procExec,
298      new CreateTableProcedureOnHDFSFailure(procExec.getEnvironment(), htd, regions));
299    ProcedureTestingUtility.assertProcNotFailed(procExec, procId);
300  }
301
302  @Test
303  public void testCreateTableWithManyRegionReplication() throws IOException {
304    final int EXCEED_MAX_REGION_REPLICATION = 0x10001;
305    TableName tableName = TableName.valueOf(testMethodName);
306    ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
307
308    TableDescriptor tableWithManyRegionReplication = TableDescriptorBuilder.newBuilder(tableName)
309      .setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("f1")).build())
310      .setRegionReplication(EXCEED_MAX_REGION_REPLICATION).build();
311    RegionInfo[] regions01 =
312      ModifyRegionUtils.createRegionInfos(tableWithManyRegionReplication, null);
313    long procId01 = ProcedureTestingUtility.submitAndWait(procExec, new CreateTableProcedure(
314      procExec.getEnvironment(), tableWithManyRegionReplication, regions01));
315    Procedure<?> result01 = procExec.getResult(procId01);
316    assertTrue(result01.getException().getCause() instanceof IllegalArgumentException);
317    assertFalse(UTIL.getAdmin().tableExists(tableName));
318
319    TableDescriptor tdesc = TableDescriptorBuilder.newBuilder(tableName)
320      .setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("f1")).build())
321      .build();
322    RegionInfo[] regions02 = ModifyRegionUtils.createRegionInfos(tdesc, null);
323    long procId02 = ProcedureTestingUtility.submitAndWait(procExec,
324      new CreateTableProcedure(procExec.getEnvironment(), tdesc, regions02));
325    Procedure<?> result02 = procExec.getResult(procId02);
326    assertTrue(result02.isSuccess());
327    assertTrue(UTIL.getAdmin().tableExists(tableName));
328  }
329}