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