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 */
018
019package org.apache.hadoop.hbase.master.procedure;
020
021import java.io.IOException;
022import java.util.ArrayList;
023import java.util.List;
024
025import org.apache.hadoop.fs.FileSystem;
026import org.apache.hadoop.fs.Path;
027import org.apache.hadoop.hbase.DoNotRetryIOException;
028import org.apache.hadoop.hbase.MetaTableAccessor;
029import org.apache.hadoop.hbase.TableExistsException;
030import org.apache.hadoop.hbase.TableName;
031import org.apache.hadoop.hbase.client.RegionInfo;
032import org.apache.hadoop.hbase.client.RegionReplicaUtil;
033import org.apache.hadoop.hbase.client.TableDescriptor;
034import org.apache.hadoop.hbase.client.TableState;
035import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
036import org.apache.hadoop.hbase.master.MasterFileSystem;
037import org.apache.hadoop.hbase.procedure2.ProcedureStateSerializer;
038import org.apache.hadoop.hbase.util.FSTableDescriptors;
039import org.apache.hadoop.hbase.util.FSUtils;
040import org.apache.hadoop.hbase.util.ModifyRegionUtils;
041import org.apache.hadoop.hbase.util.ServerRegionReplicaUtil;
042import org.apache.yetus.audience.InterfaceAudience;
043import org.slf4j.Logger;
044import org.slf4j.LoggerFactory;
045import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting;
046import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
047import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
048import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos;
049import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos;
050import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.CreateTableState;
051
052@InterfaceAudience.Private
053public class CreateTableProcedure
054    extends AbstractStateMachineTableProcedure<CreateTableState> {
055  private static final Logger LOG = LoggerFactory.getLogger(CreateTableProcedure.class);
056
057  private TableDescriptor tableDescriptor;
058  private List<RegionInfo> newRegions;
059
060  public CreateTableProcedure() {
061    // Required by the Procedure framework to create the procedure on replay
062    super();
063  }
064
065  public CreateTableProcedure(final MasterProcedureEnv env,
066      final TableDescriptor tableDescriptor, final RegionInfo[] newRegions) {
067    this(env, tableDescriptor, newRegions, null);
068  }
069
070  public CreateTableProcedure(final MasterProcedureEnv env,
071      final TableDescriptor tableDescriptor, final RegionInfo[] newRegions,
072      final ProcedurePrepareLatch syncLatch) {
073    super(env, syncLatch);
074    this.tableDescriptor = tableDescriptor;
075    this.newRegions = newRegions != null ? Lists.newArrayList(newRegions) : null;
076  }
077
078  @Override
079  protected Flow executeFromState(final MasterProcedureEnv env, final CreateTableState state)
080      throws InterruptedException {
081    if (LOG.isTraceEnabled()) {
082      LOG.trace(this + " execute state=" + state);
083    }
084    try {
085      switch (state) {
086        case CREATE_TABLE_PRE_OPERATION:
087          // Verify if we can create the table
088          boolean exists = !prepareCreate(env);
089          releaseSyncLatch();
090
091          if (exists) {
092            assert isFailed() : "the delete should have an exception here";
093            return Flow.NO_MORE_STATE;
094          }
095
096          preCreate(env);
097          setNextState(CreateTableState.CREATE_TABLE_WRITE_FS_LAYOUT);
098          break;
099        case CREATE_TABLE_WRITE_FS_LAYOUT:
100          DeleteTableProcedure.deleteFromFs(env, getTableName(), newRegions, true);
101          newRegions = createFsLayout(env, tableDescriptor, newRegions);
102          setNextState(CreateTableState.CREATE_TABLE_ADD_TO_META);
103          break;
104        case CREATE_TABLE_ADD_TO_META:
105          newRegions = addTableToMeta(env, tableDescriptor, newRegions);
106          setNextState(CreateTableState.CREATE_TABLE_ASSIGN_REGIONS);
107          break;
108        case CREATE_TABLE_ASSIGN_REGIONS:
109          setEnablingState(env, getTableName());
110          addChildProcedure(env.getAssignmentManager()
111            .createRoundRobinAssignProcedures(newRegions));
112          setNextState(CreateTableState.CREATE_TABLE_UPDATE_DESC_CACHE);
113          break;
114        case CREATE_TABLE_UPDATE_DESC_CACHE:
115          setEnabledState(env, getTableName());
116          updateTableDescCache(env, getTableName());
117          setNextState(CreateTableState.CREATE_TABLE_POST_OPERATION);
118          break;
119        case CREATE_TABLE_POST_OPERATION:
120          postCreate(env);
121          return Flow.NO_MORE_STATE;
122        default:
123          throw new UnsupportedOperationException("unhandled state=" + state);
124      }
125    } catch (IOException e) {
126      if (isRollbackSupported(state)) {
127        setFailure("master-create-table", e);
128      } else {
129        LOG.warn("Retriable error trying to create table=" + getTableName() + " state=" + state, e);
130      }
131    }
132    return Flow.HAS_MORE_STATE;
133  }
134
135  @Override
136  protected void rollbackState(final MasterProcedureEnv env, final CreateTableState state)
137      throws IOException {
138    if (state == CreateTableState.CREATE_TABLE_PRE_OPERATION) {
139      // nothing to rollback, pre-create is just table-state checks.
140      // We can fail if the table does exist or the descriptor is malformed.
141      // TODO: coprocessor rollback semantic is still undefined.
142      if (hasException() /* avoid NPE */ &&
143          getException().getCause().getClass() != TableExistsException.class) {
144        DeleteTableProcedure.deleteTableStates(env, getTableName());
145
146        final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
147        if (cpHost != null) {
148          cpHost.postDeleteTable(getTableName());
149        }
150      }
151
152      releaseSyncLatch();
153      return;
154    }
155
156    // The procedure doesn't have a rollback. The execution will succeed, at some point.
157    throw new UnsupportedOperationException("unhandled state=" + state);
158  }
159
160  @Override
161  protected boolean isRollbackSupported(final CreateTableState state) {
162    switch (state) {
163      case CREATE_TABLE_PRE_OPERATION:
164        return true;
165      default:
166        return false;
167    }
168  }
169
170  @Override
171  protected CreateTableState getState(final int stateId) {
172    return CreateTableState.forNumber(stateId);
173  }
174
175  @Override
176  protected int getStateId(final CreateTableState state) {
177    return state.getNumber();
178  }
179
180  @Override
181  protected CreateTableState getInitialState() {
182    return CreateTableState.CREATE_TABLE_PRE_OPERATION;
183  }
184
185  @Override
186  public TableName getTableName() {
187    return tableDescriptor.getTableName();
188  }
189
190  @Override
191  public TableOperationType getTableOperationType() {
192    return TableOperationType.CREATE;
193  }
194
195  @Override
196  protected void serializeStateData(ProcedureStateSerializer serializer)
197      throws IOException {
198    super.serializeStateData(serializer);
199
200    MasterProcedureProtos.CreateTableStateData.Builder state =
201      MasterProcedureProtos.CreateTableStateData.newBuilder()
202        .setUserInfo(MasterProcedureUtil.toProtoUserInfo(getUser()))
203            .setTableSchema(ProtobufUtil.toTableSchema(tableDescriptor));
204    if (newRegions != null) {
205      for (RegionInfo hri: newRegions) {
206        state.addRegionInfo(ProtobufUtil.toRegionInfo(hri));
207      }
208    }
209    serializer.serialize(state.build());
210  }
211
212  @Override
213  protected void deserializeStateData(ProcedureStateSerializer serializer)
214      throws IOException {
215    super.deserializeStateData(serializer);
216
217    MasterProcedureProtos.CreateTableStateData state =
218        serializer.deserialize(MasterProcedureProtos.CreateTableStateData.class);
219    setUser(MasterProcedureUtil.toUserInfo(state.getUserInfo()));
220    tableDescriptor = ProtobufUtil.toTableDescriptor(state.getTableSchema());
221    if (state.getRegionInfoCount() == 0) {
222      newRegions = null;
223    } else {
224      newRegions = new ArrayList<>(state.getRegionInfoCount());
225      for (HBaseProtos.RegionInfo hri: state.getRegionInfoList()) {
226        newRegions.add(ProtobufUtil.toRegionInfo(hri));
227      }
228    }
229  }
230
231  @Override
232  protected boolean waitInitialized(MasterProcedureEnv env) {
233    if (getTableName().isSystemTable()) {
234      // Creating system table is part of the initialization, so do not wait here.
235      return false;
236    }
237    return super.waitInitialized(env);
238  }
239
240  @Override
241  protected LockState acquireLock(final MasterProcedureEnv env) {
242    if (env.getProcedureScheduler().waitTableExclusiveLock(this, getTableName())) {
243      return LockState.LOCK_EVENT_WAIT;
244    }
245    return LockState.LOCK_ACQUIRED;
246  }
247
248  private boolean prepareCreate(final MasterProcedureEnv env) throws IOException {
249    final TableName tableName = getTableName();
250    if (MetaTableAccessor.tableExists(env.getMasterServices().getConnection(), tableName)) {
251      setFailure("master-create-table", new TableExistsException(getTableName()));
252      return false;
253    }
254
255    // check that we have at least 1 CF
256    if (tableDescriptor.getColumnFamilyCount() == 0) {
257      setFailure("master-create-table", new DoNotRetryIOException("Table " +
258          getTableName().toString() + " should have at least one column family."));
259      return false;
260    }
261
262    return true;
263  }
264
265  private void preCreate(final MasterProcedureEnv env)
266      throws IOException, InterruptedException {
267    if (!getTableName().isSystemTable()) {
268      ProcedureSyncWait.getMasterQuotaManager(env)
269        .checkNamespaceTableAndRegionQuota(
270          getTableName(), (newRegions != null ? newRegions.size() : 0));
271    }
272
273    final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
274    if (cpHost != null) {
275      final RegionInfo[] regions = newRegions == null ? null :
276        newRegions.toArray(new RegionInfo[newRegions.size()]);
277      cpHost.preCreateTableAction(tableDescriptor, regions, getUser());
278    }
279  }
280
281  private void postCreate(final MasterProcedureEnv env)
282      throws IOException, InterruptedException {
283    final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
284    if (cpHost != null) {
285      final RegionInfo[] regions = (newRegions == null) ? null :
286        newRegions.toArray(new RegionInfo[newRegions.size()]);
287      cpHost.postCompletedCreateTableAction(tableDescriptor, regions, getUser());
288    }
289  }
290
291  protected interface CreateHdfsRegions {
292    List<RegionInfo> createHdfsRegions(final MasterProcedureEnv env,
293      final Path tableRootDir, final TableName tableName,
294      final List<RegionInfo> newRegions) throws IOException;
295  }
296
297  protected static List<RegionInfo> createFsLayout(final MasterProcedureEnv env,
298      final TableDescriptor tableDescriptor, final List<RegionInfo> newRegions)
299      throws IOException {
300    return createFsLayout(env, tableDescriptor, newRegions, new CreateHdfsRegions() {
301      @Override
302      public List<RegionInfo> createHdfsRegions(final MasterProcedureEnv env,
303          final Path tableRootDir, final TableName tableName,
304          final List<RegionInfo> newRegions) throws IOException {
305        RegionInfo[] regions = newRegions != null ?
306          newRegions.toArray(new RegionInfo[newRegions.size()]) : null;
307        return ModifyRegionUtils.createRegions(env.getMasterConfiguration(),
308            tableRootDir, tableDescriptor, regions, null);
309      }
310    });
311  }
312
313  protected static List<RegionInfo> createFsLayout(final MasterProcedureEnv env,
314      final TableDescriptor tableDescriptor, List<RegionInfo> newRegions,
315      final CreateHdfsRegions hdfsRegionHandler) throws IOException {
316    final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem();
317    final Path tempdir = mfs.getTempDir();
318
319    // 1. Create Table Descriptor
320    // using a copy of descriptor, table will be created enabling first
321    final Path tempTableDir = FSUtils.getTableDir(tempdir, tableDescriptor.getTableName());
322    ((FSTableDescriptors)(env.getMasterServices().getTableDescriptors()))
323        .createTableDescriptorForTableDirectory(
324          tempTableDir, tableDescriptor, false);
325
326    // 2. Create Regions
327    newRegions = hdfsRegionHandler.createHdfsRegions(env, tempdir,
328            tableDescriptor.getTableName(), newRegions);
329
330    // 3. Move Table temp directory to the hbase root location
331    moveTempDirectoryToHBaseRoot(env, tableDescriptor, tempTableDir);
332
333    return newRegions;
334  }
335
336  protected static void moveTempDirectoryToHBaseRoot(
337    final MasterProcedureEnv env,
338    final TableDescriptor tableDescriptor,
339    final Path tempTableDir) throws IOException {
340    final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem();
341    final Path tableDir = FSUtils.getTableDir(mfs.getRootDir(), tableDescriptor.getTableName());
342    FileSystem fs = mfs.getFileSystem();
343    if (!fs.delete(tableDir, true) && fs.exists(tableDir)) {
344      throw new IOException("Couldn't delete " + tableDir);
345    }
346    if (!fs.rename(tempTableDir, tableDir)) {
347      throw new IOException("Unable to move table from temp=" + tempTableDir +
348        " to hbase root=" + tableDir);
349    }
350  }
351
352  protected static List<RegionInfo> addTableToMeta(final MasterProcedureEnv env,
353      final TableDescriptor tableDescriptor,
354      final List<RegionInfo> regions) throws IOException {
355    assert (regions != null && regions.size() > 0) : "expected at least 1 region, got " + regions;
356
357    ProcedureSyncWait.waitMetaRegions(env);
358
359    // Add replicas if needed
360    // we need to create regions with replicaIds starting from 1
361    List<RegionInfo> newRegions = RegionReplicaUtil.addReplicas(tableDescriptor, regions, 1,
362      tableDescriptor.getRegionReplication());
363
364    // Add regions to META
365    addRegionsToMeta(env, tableDescriptor, newRegions);
366
367    // Setup replication for region replicas if needed
368    if (tableDescriptor.getRegionReplication() > 1) {
369      ServerRegionReplicaUtil.setupRegionReplicaReplication(env.getMasterConfiguration());
370    }
371    return newRegions;
372  }
373
374  protected static void setEnablingState(final MasterProcedureEnv env, final TableName tableName)
375      throws IOException {
376    // Mark the table as Enabling
377    env.getMasterServices().getTableStateManager()
378      .setTableState(tableName, TableState.State.ENABLING);
379  }
380
381  protected static void setEnabledState(final MasterProcedureEnv env, final TableName tableName)
382      throws IOException {
383    // Enable table
384    env.getMasterServices().getTableStateManager()
385      .setTableState(tableName, TableState.State.ENABLED);
386  }
387
388  /**
389   * Add the specified set of regions to the hbase:meta table.
390   */
391  private static void addRegionsToMeta(final MasterProcedureEnv env,
392      final TableDescriptor tableDescriptor,
393      final List<RegionInfo> regionInfos) throws IOException {
394    MetaTableAccessor.addRegionsToMeta(env.getMasterServices().getConnection(),
395      regionInfos, tableDescriptor.getRegionReplication());
396  }
397
398  protected static void updateTableDescCache(final MasterProcedureEnv env,
399      final TableName tableName) throws IOException {
400    env.getMasterServices().getTableDescriptors().get(tableName);
401  }
402
403  @Override
404  protected boolean shouldWaitClientAck(MasterProcedureEnv env) {
405    // system tables are created on bootstrap internally by the system
406    // the client does not know about this procedures.
407    return !getTableName().isSystemTable();
408  }
409
410  @VisibleForTesting
411  RegionInfo getFirstRegionInfo() {
412    if (newRegions == null || newRegions.isEmpty()) {
413      return null;
414    }
415    return newRegions.get(0);
416  }
417}