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 (getTableName().isSystemTable()) {
268      // Creating system table is part of the initialization, so only wait for meta loaded instead
269      // of waiting for master fully initialized.
270      return env.getAssignmentManager().waitMetaLoaded(this);
271    }
272    return super.waitInitialized(env);
273  }
274
275  private boolean prepareCreate(final MasterProcedureEnv env) throws IOException {
276    final TableName tableName = getTableName();
277    if (env.getMasterServices().getTableDescriptors().exists(tableName)) {
278      setFailure("master-create-table", new TableExistsException(getTableName()));
279      return false;
280    }
281
282    // check that we have at least 1 CF
283    if (tableDescriptor.getColumnFamilyCount() == 0) {
284      setFailure("master-create-table", new DoNotRetryIOException(
285        "Table " + getTableName().toString() + " should have at least one column family."));
286      return false;
287    }
288
289    int regionReplicationCount = tableDescriptor.getRegionReplication();
290    if (regionReplicationCount > MAX_REGION_REPLICATION) {
291      setFailure("master-create-table", new IllegalArgumentException(
292        "Region Replication cannot exceed " + MAX_REGION_REPLICATION + "."));
293      return false;
294    }
295
296    if (!tableName.isSystemTable()) {
297      // do not check rs group for system tables as we may block the bootstrap.
298      Supplier<String> forWhom = () -> "table " + tableName;
299      RSGroupInfo rsGroupInfo = MasterProcedureUtil.checkGroupExists(
300        env.getMasterServices().getRSGroupInfoManager()::getRSGroup,
301        tableDescriptor.getRegionServerGroup(), forWhom);
302      if (rsGroupInfo == null) {
303        // we do not set rs group info on table, check if we have one on namespace
304        String namespace = tableName.getNamespaceAsString();
305        NamespaceDescriptor nd = env.getMasterServices().getClusterSchema().getNamespace(namespace);
306        forWhom = () -> "table " + tableName + "(inherit from namespace)";
307        rsGroupInfo = MasterProcedureUtil.checkGroupExists(
308          env.getMasterServices().getRSGroupInfoManager()::getRSGroup,
309          MasterProcedureUtil.getNamespaceGroup(nd), forWhom);
310      }
311      MasterProcedureUtil.checkGroupNotEmpty(rsGroupInfo, forWhom);
312    }
313
314    // check for store file tracker configurations
315    StoreFileTrackerValidationUtils.checkForCreateTable(env.getMasterConfiguration(),
316      tableDescriptor);
317
318    CustomCellTieredUtils.checkForModifyTable(tableDescriptor);
319
320    return true;
321  }
322
323  private void preCreate(final MasterProcedureEnv env) throws IOException, InterruptedException {
324    if (!getTableName().isSystemTable()) {
325      ProcedureSyncWait.getMasterQuotaManager(env).checkNamespaceTableAndRegionQuota(getTableName(),
326        (newRegions != null ? newRegions.size() : 0));
327    }
328
329    tableDescriptor = StoreFileTrackerFactory.updateWithTrackerConfigs(env.getMasterConfiguration(),
330      tableDescriptor);
331
332    final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
333    if (cpHost != null) {
334      final RegionInfo[] regions =
335        newRegions == null ? null : newRegions.toArray(new RegionInfo[newRegions.size()]);
336      cpHost.preCreateTableAction(tableDescriptor, regions, getUser());
337    }
338  }
339
340  private void postCreate(final MasterProcedureEnv env) throws IOException, InterruptedException {
341    final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
342    if (cpHost != null) {
343      final RegionInfo[] regions =
344        (newRegions == null) ? null : newRegions.toArray(new RegionInfo[newRegions.size()]);
345      cpHost.postCompletedCreateTableAction(tableDescriptor, regions, getUser());
346    }
347  }
348
349  protected interface CreateHdfsRegions {
350    List<RegionInfo> createHdfsRegions(final MasterProcedureEnv env, final Path tableRootDir,
351      final TableName tableName, final List<RegionInfo> newRegions) throws IOException;
352  }
353
354  protected static List<RegionInfo> createFsLayout(final MasterProcedureEnv env,
355    final TableDescriptor tableDescriptor, final List<RegionInfo> newRegions) throws IOException {
356    return createFsLayout(env, tableDescriptor, newRegions, new CreateHdfsRegions() {
357      @Override
358      public List<RegionInfo> createHdfsRegions(final MasterProcedureEnv env,
359        final Path tableRootDir, final TableName tableName, final List<RegionInfo> newRegions)
360        throws IOException {
361        RegionInfo[] regions =
362          newRegions != null ? newRegions.toArray(new RegionInfo[newRegions.size()]) : null;
363        return ModifyRegionUtils.createRegions(env.getMasterConfiguration(), tableRootDir,
364          tableDescriptor, regions, null);
365      }
366    });
367  }
368
369  protected static List<RegionInfo> createFsLayout(final MasterProcedureEnv env,
370    final TableDescriptor tableDescriptor, List<RegionInfo> newRegions,
371    final CreateHdfsRegions hdfsRegionHandler) throws IOException {
372    final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem();
373
374    // 1. Create Table Descriptor
375    // using a copy of descriptor, table will be created enabling first
376    final Path tableDir =
377      CommonFSUtils.getTableDir(mfs.getRootDir(), tableDescriptor.getTableName());
378    ((FSTableDescriptors) (env.getMasterServices().getTableDescriptors()))
379      .createTableDescriptorForTableDirectory(tableDir, tableDescriptor, false);
380
381    // 2. Create Regions
382    newRegions = hdfsRegionHandler.createHdfsRegions(env, mfs.getRootDir(),
383      tableDescriptor.getTableName(), newRegions);
384
385    return newRegions;
386  }
387
388  protected static List<RegionInfo> addTableToMeta(final MasterProcedureEnv env,
389    final TableDescriptor tableDescriptor, final List<RegionInfo> regions) throws IOException {
390    assert (regions != null && regions.size() > 0) : "expected at least 1 region, got " + regions;
391
392    ProcedureSyncWait.waitMetaRegions(env);
393
394    // Add replicas if needed
395    // we need to create regions with replicaIds starting from 1
396    List<RegionInfo> newRegions =
397      RegionReplicaUtil.addReplicas(regions, 1, tableDescriptor.getRegionReplication());
398
399    // Add regions to META
400    addRegionsToMeta(env, tableDescriptor, newRegions);
401
402    return newRegions;
403  }
404
405  protected static void setEnablingState(final MasterProcedureEnv env, final TableName tableName)
406    throws IOException {
407    // Mark the table as Enabling
408    env.getMasterServices().getTableStateManager().setTableState(tableName,
409      TableState.State.ENABLING);
410  }
411
412  protected static void setEnabledState(final MasterProcedureEnv env, final TableName tableName)
413    throws IOException {
414    // Enable table
415    env.getMasterServices().getTableStateManager().setTableState(tableName,
416      TableState.State.ENABLED);
417  }
418
419  /**
420   * Add the specified set of regions to the hbase:meta table.
421   */
422  private static void addRegionsToMeta(final MasterProcedureEnv env,
423    final TableDescriptor tableDescriptor, final List<RegionInfo> regionInfos) throws IOException {
424    MetaTableAccessor.addRegionsToMeta(env.getMasterServices().getConnection(), regionInfos,
425      tableDescriptor.getRegionReplication());
426  }
427
428  @Override
429  protected boolean shouldWaitClientAck(MasterProcedureEnv env) {
430    // system tables are created on bootstrap internally by the system
431    // the client does not know about this procedures.
432    return !getTableName().isSystemTable();
433  }
434
435  RegionInfo getFirstRegionInfo() {
436    if (newRegions == null || newRegions.isEmpty()) {
437      return null;
438    }
439    return newRegions.get(0);
440  }
441}