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