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.junit.Assert.assertEquals;
021import static org.junit.Assert.assertTrue;
022import static org.junit.Assert.fail;
023
024import java.io.IOException;
025import org.apache.hadoop.conf.Configuration;
026import org.apache.hadoop.fs.FileSystem;
027import org.apache.hadoop.fs.Path;
028import org.apache.hadoop.hbase.DoNotRetryIOException;
029import org.apache.hadoop.hbase.HBaseClassTestRule;
030import org.apache.hadoop.hbase.HBaseIOException;
031import org.apache.hadoop.hbase.TableExistsException;
032import org.apache.hadoop.hbase.TableName;
033import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
034import org.apache.hadoop.hbase.client.RegionInfo;
035import org.apache.hadoop.hbase.client.TableDescriptor;
036import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
037import org.apache.hadoop.hbase.master.MasterFileSystem;
038import org.apache.hadoop.hbase.procedure2.Procedure;
039import org.apache.hadoop.hbase.procedure2.ProcedureExecutor;
040import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility;
041import org.apache.hadoop.hbase.testclassification.MasterTests;
042import org.apache.hadoop.hbase.testclassification.MediumTests;
043import org.apache.hadoop.hbase.util.Bytes;
044import org.apache.hadoop.hbase.util.FSUtils;
045import org.apache.hadoop.hbase.util.ModifyRegionUtils;
046import org.junit.ClassRule;
047import org.junit.Rule;
048import org.junit.Test;
049import org.junit.experimental.categories.Category;
050import org.junit.rules.TestName;
051
052import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos;
053
054@Category({MasterTests.class, MediumTests.class})
055public class TestCreateTableProcedure extends TestTableDDLProcedureBase {
056
057  @ClassRule
058  public static final HBaseClassTestRule CLASS_RULE =
059    HBaseClassTestRule.forClass(TestCreateTableProcedure.class);
060
061  private static final String F1 = "f1";
062  private static final String F2 = "f2";
063
064  @Rule
065  public TestName name = new TestName();
066
067  @Test
068  public void testSimpleCreate() throws Exception {
069    final TableName tableName = TableName.valueOf(name.getMethodName());
070    final byte[][] splitKeys = null;
071    testSimpleCreate(tableName, splitKeys);
072  }
073
074  @Test
075  public void testSimpleCreateWithSplits() throws Exception {
076    final TableName tableName = TableName.valueOf(name.getMethodName());
077    final byte[][] splitKeys = new byte[][] {
078      Bytes.toBytes("a"), Bytes.toBytes("b"), Bytes.toBytes("c")
079    };
080    testSimpleCreate(tableName, splitKeys);
081  }
082
083  private void testSimpleCreate(final TableName tableName, byte[][] splitKeys) throws Exception {
084    RegionInfo[] regions = MasterProcedureTestingUtility.createTable(
085      getMasterProcedureExecutor(), tableName, splitKeys, F1, F2);
086    MasterProcedureTestingUtility.validateTableCreation(getMaster(), tableName, regions, F1, F2);
087  }
088
089  @Test
090  public void testCreateWithoutColumnFamily() throws Exception {
091    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
092    final TableName tableName = TableName.valueOf(name.getMethodName());
093    // create table with 0 families will fail
094    final TableDescriptorBuilder builder =
095      TableDescriptorBuilder.newBuilder(MasterProcedureTestingUtility.createHTD(tableName));
096
097    // disable sanity check
098    builder.setValue("hbase.table.sanity.checks", Boolean.FALSE.toString());
099    TableDescriptor htd = builder.build();
100    final RegionInfo[] regions = ModifyRegionUtils.createRegionInfos(htd, null);
101
102    long procId =
103        ProcedureTestingUtility.submitAndWait(procExec,
104            new CreateTableProcedure(procExec.getEnvironment(), htd, regions));
105    final Procedure<?> result = procExec.getResult(procId);
106    assertEquals(true, result.isFailed());
107    Throwable cause = ProcedureTestingUtility.getExceptionCause(result);
108    assertTrue("expected DoNotRetryIOException, got " + cause,
109        cause instanceof DoNotRetryIOException);
110  }
111
112  @Test(expected=TableExistsException.class)
113  public void testCreateExisting() throws Exception {
114    final TableName tableName = TableName.valueOf(name.getMethodName());
115    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
116    final TableDescriptor htd = MasterProcedureTestingUtility.createHTD(tableName, "f");
117    final RegionInfo[] regions = ModifyRegionUtils.createRegionInfos(htd, null);
118
119    // create the table
120    long procId1 = procExec.submitProcedure(
121      new CreateTableProcedure(procExec.getEnvironment(), htd, regions));
122
123    // create another with the same name
124    ProcedurePrepareLatch latch2 = new ProcedurePrepareLatch.CompatibilityLatch();
125    long procId2 = procExec.submitProcedure(
126      new CreateTableProcedure(procExec.getEnvironment(), htd, regions, latch2));
127
128    ProcedureTestingUtility.waitProcedure(procExec, procId1);
129    ProcedureTestingUtility.assertProcNotFailed(procExec.getResult(procId1));
130
131    ProcedureTestingUtility.waitProcedure(procExec, procId2);
132    latch2.await();
133  }
134
135  @Test
136  public void testRecoveryAndDoubleExecution() throws Exception {
137    final TableName tableName = TableName.valueOf(name.getMethodName());
138
139    // create the table
140    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
141    ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true);
142
143    // Start the Create procedure && kill the executor
144    byte[][] splitKeys = null;
145    TableDescriptor htd = MasterProcedureTestingUtility.createHTD(tableName, "f1", "f2");
146    RegionInfo[] regions = ModifyRegionUtils.createRegionInfos(htd, splitKeys);
147    long procId = procExec.submitProcedure(
148      new CreateTableProcedure(procExec.getEnvironment(), htd, regions));
149
150    // Restart the executor and execute the step twice
151    MasterProcedureTestingUtility.testRecoveryAndDoubleExecution(procExec, procId);
152    MasterProcedureTestingUtility.validateTableCreation(getMaster(), tableName, regions, F1, F2);
153  }
154
155  @Test
156  public void testRollbackAndDoubleExecution() throws Exception {
157    final TableName tableName = TableName.valueOf(name.getMethodName());
158    testRollbackAndDoubleExecution(TableDescriptorBuilder
159      .newBuilder(MasterProcedureTestingUtility.createHTD(tableName, F1, F2)));
160  }
161
162  @Test
163  public void testRollbackAndDoubleExecutionOnMobTable() throws Exception {
164    final TableName tableName = TableName.valueOf(name.getMethodName());
165    TableDescriptor htd = MasterProcedureTestingUtility.createHTD(tableName, F1, F2);
166    TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(htd)
167            .modifyColumnFamily(ColumnFamilyDescriptorBuilder
168              .newBuilder(htd.getColumnFamily(Bytes.toBytes(F1)))
169              .setMobEnabled(true)
170              .build());
171    testRollbackAndDoubleExecution(builder);
172  }
173
174  private void testRollbackAndDoubleExecution(TableDescriptorBuilder builder) throws Exception {
175    // create the table
176    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
177    ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true);
178
179    // Start the Create procedure && kill the executor
180    final byte[][] splitKeys = new byte[][] {
181      Bytes.toBytes("a"), Bytes.toBytes("b"), Bytes.toBytes("c")
182    };
183    builder.setRegionReplication(3);
184    TableDescriptor htd = builder.build();
185    RegionInfo[] regions = ModifyRegionUtils.createRegionInfos(htd, splitKeys);
186    long procId = procExec.submitProcedure(
187      new CreateTableProcedure(procExec.getEnvironment(), htd, regions));
188
189    int lastStep = 2; // failing before CREATE_TABLE_WRITE_FS_LAYOUT
190    MasterProcedureTestingUtility.testRollbackAndDoubleExecution(procExec, procId, lastStep);
191
192    TableName tableName = htd.getTableName();
193    MasterProcedureTestingUtility.validateTableDeletion(getMaster(), tableName);
194
195    // are we able to create the table after a rollback?
196    resetProcExecutorTestingKillFlag();
197    testSimpleCreate(tableName, splitKeys);
198  }
199
200  public static class CreateTableProcedureOnHDFSFailure extends CreateTableProcedure {
201    private boolean failOnce = false;
202
203    public CreateTableProcedureOnHDFSFailure() {
204      // Required by the Procedure framework to create the procedure on replay
205      super();
206    }
207
208    public CreateTableProcedureOnHDFSFailure(final MasterProcedureEnv env,
209      final TableDescriptor tableDescriptor, final RegionInfo[] newRegions)
210      throws HBaseIOException {
211      super(env, tableDescriptor, newRegions);
212    }
213
214    @Override
215    protected Flow executeFromState(MasterProcedureEnv env,
216      MasterProcedureProtos.CreateTableState state) throws InterruptedException {
217
218      if (!failOnce &&
219        state == MasterProcedureProtos.CreateTableState.CREATE_TABLE_WRITE_FS_LAYOUT) {
220        try {
221          // To emulate an HDFS failure, create only the first region directory
222          RegionInfo regionInfo = getFirstRegionInfo();
223          Configuration conf = env.getMasterConfiguration();
224          MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem();
225          Path tempdir = mfs.getTempDir();
226          Path tableDir = FSUtils.getTableDir(tempdir, regionInfo.getTable());
227          Path regionDir = FSUtils.getRegionDir(tableDir, regionInfo);
228          FileSystem fs = FileSystem.get(conf);
229          fs.mkdirs(regionDir);
230
231          failOnce = true;
232          return Flow.HAS_MORE_STATE;
233        } catch (IOException e) {
234          fail("failed to create a region directory: " + e);
235        }
236      }
237
238      return super.executeFromState(env, state);
239    }
240  }
241
242  @Test
243  public void testOnHDFSFailure() throws Exception {
244    final TableName tableName = TableName.valueOf(name.getMethodName());
245
246    // create the table
247    final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
248    final byte[][] splitKeys = new byte[][] {
249      Bytes.toBytes("a"), Bytes.toBytes("b"), Bytes.toBytes("c")
250    };
251    TableDescriptor htd = MasterProcedureTestingUtility.createHTD(tableName, "f1", "f2");
252    RegionInfo[] regions = ModifyRegionUtils.createRegionInfos(htd, splitKeys);
253    long procId = ProcedureTestingUtility.submitAndWait(procExec,
254      new CreateTableProcedureOnHDFSFailure(procExec.getEnvironment(), htd, regions));
255    ProcedureTestingUtility.assertProcNotFailed(procExec, procId);
256  }
257}