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