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.master.MasterCoprocessorHost;
035import org.apache.hadoop.hbase.master.MasterFileSystem;
036import org.apache.hadoop.hbase.procedure2.ProcedureStateSerializer;
037import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory;
038import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerValidationUtils;
039import org.apache.hadoop.hbase.rsgroup.RSGroupInfo;
040import org.apache.hadoop.hbase.util.CommonFSUtils;
041import org.apache.hadoop.hbase.util.FSTableDescriptors;
042import org.apache.hadoop.hbase.util.ModifyRegionUtils;
043import org.apache.yetus.audience.InterfaceAudience;
044import org.slf4j.Logger;
045import org.slf4j.LoggerFactory;
046
047import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
048
049import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
050import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos;
051import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos;
052import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.CreateTableState;
053
054@InterfaceAudience.Private
055public class CreateTableProcedure extends AbstractStateMachineTableProcedure<CreateTableState> {
056  private static final Logger LOG = LoggerFactory.getLogger(CreateTableProcedure.class);
057
058  private TableDescriptor tableDescriptor;
059  private List<RegionInfo> newRegions;
060
061  public CreateTableProcedure() {
062    // Required by the Procedure framework to create the procedure on replay
063    super();
064  }
065
066  public CreateTableProcedure(final MasterProcedureEnv env, final TableDescriptor tableDescriptor,
067    final RegionInfo[] newRegions) {
068    this(env, tableDescriptor, newRegions, null);
069  }
070
071  public CreateTableProcedure(final MasterProcedureEnv env, final TableDescriptor tableDescriptor,
072    final RegionInfo[] newRegions, 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    LOG.info("{} execute state={}", this, state);
082    try {
083      switch (state) {
084        case CREATE_TABLE_PRE_OPERATION:
085          // Verify if we can create the table
086          boolean exists = !prepareCreate(env);
087          releaseSyncLatch();
088
089          if (exists) {
090            assert isFailed() : "the delete should have an exception here";
091            return Flow.NO_MORE_STATE;
092          }
093
094          preCreate(env);
095          setNextState(CreateTableState.CREATE_TABLE_WRITE_FS_LAYOUT);
096          break;
097        case CREATE_TABLE_WRITE_FS_LAYOUT:
098          DeleteTableProcedure.deleteFromFs(env, getTableName(), newRegions, true);
099          newRegions = createFsLayout(env, tableDescriptor, newRegions);
100          env.getMasterServices().getTableDescriptors().update(tableDescriptor, true);
101          setNextState(CreateTableState.CREATE_TABLE_ADD_TO_META);
102          break;
103        case CREATE_TABLE_ADD_TO_META:
104          newRegions = addTableToMeta(env, tableDescriptor, newRegions);
105          setNextState(CreateTableState.CREATE_TABLE_ASSIGN_REGIONS);
106          break;
107        case CREATE_TABLE_ASSIGN_REGIONS:
108          setEnablingState(env, getTableName());
109          addChildProcedure(
110            env.getAssignmentManager().createRoundRobinAssignProcedures(newRegions));
111          setNextState(CreateTableState.CREATE_TABLE_UPDATE_DESC_CACHE);
112          break;
113        case CREATE_TABLE_UPDATE_DESC_CACHE:
114          // XXX: this stage should be named as set table enabled, as now we will cache the
115          // descriptor after writing fs layout.
116          setEnabledState(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 (
143        hasException()
144          /* avoid NPE */ && getException().getCause().getClass() != TableExistsException.class
145      ) {
146        DeleteTableProcedure.deleteTableStates(env, getTableName());
147
148        final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
149        if (cpHost != null) {
150          cpHost.postDeleteTable(getTableName());
151        }
152      }
153
154      releaseSyncLatch();
155      return;
156    }
157
158    // The procedure doesn't have a rollback. The execution will succeed, at some point.
159    throw new UnsupportedOperationException("unhandled state=" + state);
160  }
161
162  @Override
163  protected boolean isRollbackSupported(final CreateTableState state) {
164    switch (state) {
165      case CREATE_TABLE_PRE_OPERATION:
166        return true;
167      default:
168        return false;
169    }
170  }
171
172  @Override
173  protected CreateTableState getState(final int stateId) {
174    return CreateTableState.forNumber(stateId);
175  }
176
177  @Override
178  protected int getStateId(final CreateTableState state) {
179    return state.getNumber();
180  }
181
182  @Override
183  protected CreateTableState getInitialState() {
184    return CreateTableState.CREATE_TABLE_PRE_OPERATION;
185  }
186
187  @Override
188  public TableName getTableName() {
189    return tableDescriptor.getTableName();
190  }
191
192  @Override
193  public TableOperationType getTableOperationType() {
194    return TableOperationType.CREATE;
195  }
196
197  @Override
198  protected void serializeStateData(ProcedureStateSerializer serializer) throws IOException {
199    super.serializeStateData(serializer);
200
201    MasterProcedureProtos.CreateTableStateData.Builder state =
202      MasterProcedureProtos.CreateTableStateData.newBuilder()
203        .setUserInfo(MasterProcedureUtil.toProtoUserInfo(getUser()))
204        .setTableSchema(ProtobufUtil.toTableSchema(tableDescriptor));
205    if (newRegions != null) {
206      for (RegionInfo hri : newRegions) {
207        state.addRegionInfo(ProtobufUtil.toRegionInfo(hri));
208      }
209    }
210    serializer.serialize(state.build());
211  }
212
213  @Override
214  protected void deserializeStateData(ProcedureStateSerializer serializer) 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 only wait for meta loaded instead
235      // of waiting for master fully initialized.
236      return env.getAssignmentManager().waitMetaLoaded(this);
237    }
238    return super.waitInitialized(env);
239  }
240
241  private boolean prepareCreate(final MasterProcedureEnv env) throws IOException {
242    final TableName tableName = getTableName();
243    if (env.getMasterServices().getTableDescriptors().exists(tableName)) {
244      setFailure("master-create-table", new TableExistsException(getTableName()));
245      return false;
246    }
247
248    // check that we have at least 1 CF
249    if (tableDescriptor.getColumnFamilyCount() == 0) {
250      setFailure("master-create-table", new DoNotRetryIOException(
251        "Table " + getTableName().toString() + " should have at least one column family."));
252      return false;
253    }
254    if (!tableName.isSystemTable()) {
255      // do not check rs group for system tables as we may block the bootstrap.
256      Supplier<String> forWhom = () -> "table " + tableName;
257      RSGroupInfo rsGroupInfo = MasterProcedureUtil.checkGroupExists(
258        env.getMasterServices().getRSGroupInfoManager()::getRSGroup,
259        tableDescriptor.getRegionServerGroup(), forWhom);
260      if (rsGroupInfo == null) {
261        // we do not set rs group info on table, check if we have one on namespace
262        String namespace = tableName.getNamespaceAsString();
263        NamespaceDescriptor nd = env.getMasterServices().getClusterSchema().getNamespace(namespace);
264        forWhom = () -> "table " + tableName + "(inherit from namespace)";
265        rsGroupInfo = MasterProcedureUtil.checkGroupExists(
266          env.getMasterServices().getRSGroupInfoManager()::getRSGroup,
267          MasterProcedureUtil.getNamespaceGroup(nd), forWhom);
268      }
269      MasterProcedureUtil.checkGroupNotEmpty(rsGroupInfo, forWhom);
270    }
271
272    // check for store file tracker configurations
273    StoreFileTrackerValidationUtils.checkForCreateTable(env.getMasterConfiguration(),
274      tableDescriptor);
275
276    return true;
277  }
278
279  private void preCreate(final MasterProcedureEnv env) throws IOException, InterruptedException {
280    if (!getTableName().isSystemTable()) {
281      ProcedureSyncWait.getMasterQuotaManager(env).checkNamespaceTableAndRegionQuota(getTableName(),
282        (newRegions != null ? newRegions.size() : 0));
283    }
284
285    tableDescriptor = StoreFileTrackerFactory.updateWithTrackerConfigs(env.getMasterConfiguration(),
286      tableDescriptor);
287
288    final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
289    if (cpHost != null) {
290      final RegionInfo[] regions =
291        newRegions == null ? null : newRegions.toArray(new RegionInfo[newRegions.size()]);
292      cpHost.preCreateTableAction(tableDescriptor, regions, getUser());
293    }
294  }
295
296  private void postCreate(final MasterProcedureEnv env) throws IOException, InterruptedException {
297    final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
298    if (cpHost != null) {
299      final RegionInfo[] regions =
300        (newRegions == null) ? null : newRegions.toArray(new RegionInfo[newRegions.size()]);
301      cpHost.postCompletedCreateTableAction(tableDescriptor, regions, getUser());
302    }
303  }
304
305  protected interface CreateHdfsRegions {
306    List<RegionInfo> createHdfsRegions(final MasterProcedureEnv env, final Path tableRootDir,
307      final TableName tableName, final List<RegionInfo> newRegions) throws IOException;
308  }
309
310  protected static List<RegionInfo> createFsLayout(final MasterProcedureEnv env,
311    final TableDescriptor tableDescriptor, final List<RegionInfo> newRegions) throws IOException {
312    return createFsLayout(env, tableDescriptor, newRegions, new CreateHdfsRegions() {
313      @Override
314      public List<RegionInfo> createHdfsRegions(final MasterProcedureEnv env,
315        final Path tableRootDir, final TableName tableName, final List<RegionInfo> newRegions)
316        throws IOException {
317        RegionInfo[] regions =
318          newRegions != null ? newRegions.toArray(new RegionInfo[newRegions.size()]) : null;
319        return ModifyRegionUtils.createRegions(env.getMasterConfiguration(), tableRootDir,
320          tableDescriptor, regions, null);
321      }
322    });
323  }
324
325  protected static List<RegionInfo> createFsLayout(final MasterProcedureEnv env,
326    final TableDescriptor tableDescriptor, List<RegionInfo> newRegions,
327    final CreateHdfsRegions hdfsRegionHandler) throws IOException {
328    final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem();
329
330    // 1. Create Table Descriptor
331    // using a copy of descriptor, table will be created enabling first
332    final Path tableDir =
333      CommonFSUtils.getTableDir(mfs.getRootDir(), tableDescriptor.getTableName());
334    ((FSTableDescriptors) (env.getMasterServices().getTableDescriptors()))
335      .createTableDescriptorForTableDirectory(tableDir, tableDescriptor, false);
336
337    // 2. Create Regions
338    newRegions = hdfsRegionHandler.createHdfsRegions(env, mfs.getRootDir(),
339      tableDescriptor.getTableName(), newRegions);
340
341    return newRegions;
342  }
343
344  protected static List<RegionInfo> addTableToMeta(final MasterProcedureEnv env,
345    final TableDescriptor tableDescriptor, final List<RegionInfo> regions) throws IOException {
346    assert (regions != null && regions.size() > 0) : "expected at least 1 region, got " + regions;
347
348    ProcedureSyncWait.waitMetaRegions(env);
349
350    // Add replicas if needed
351    // we need to create regions with replicaIds starting from 1
352    List<RegionInfo> newRegions =
353      RegionReplicaUtil.addReplicas(regions, 1, tableDescriptor.getRegionReplication());
354
355    // Add regions to META
356    addRegionsToMeta(env, tableDescriptor, newRegions);
357
358    return newRegions;
359  }
360
361  protected static void setEnablingState(final MasterProcedureEnv env, final TableName tableName)
362    throws IOException {
363    // Mark the table as Enabling
364    env.getMasterServices().getTableStateManager().setTableState(tableName,
365      TableState.State.ENABLING);
366  }
367
368  protected static void setEnabledState(final MasterProcedureEnv env, final TableName tableName)
369    throws IOException {
370    // Enable table
371    env.getMasterServices().getTableStateManager().setTableState(tableName,
372      TableState.State.ENABLED);
373  }
374
375  /**
376   * Add the specified set of regions to the hbase:meta table.
377   */
378  private static void addRegionsToMeta(final MasterProcedureEnv env,
379    final TableDescriptor tableDescriptor, final List<RegionInfo> regionInfos) throws IOException {
380    MetaTableAccessor.addRegionsToMeta(env.getMasterServices().getConnection(), regionInfos,
381      tableDescriptor.getRegionReplication());
382  }
383
384  @Override
385  protected boolean shouldWaitClientAck(MasterProcedureEnv env) {
386    // system tables are created on bootstrap internally by the system
387    // the client does not know about this procedures.
388    return !getTableName().isSystemTable();
389  }
390
391  RegionInfo getFirstRegionInfo() {
392    if (newRegions == null || newRegions.isEmpty()) {
393      return null;
394    }
395    return newRegions.get(0);
396  }
397}