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 */
018
019package org.apache.hadoop.hbase.master.procedure;
020
021import java.io.IOException;
022import java.util.ArrayList;
023import java.util.List;
024import org.apache.hadoop.fs.FileSystem;
025import org.apache.hadoop.fs.Path;
026import org.apache.hadoop.hbase.DoNotRetryIOException;
027import org.apache.hadoop.hbase.MetaTableAccessor;
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.util.CommonFSUtils;
038import org.apache.hadoop.hbase.util.FSTableDescriptors;
039import org.apache.hadoop.hbase.util.ModifyRegionUtils;
040import org.apache.hadoop.hbase.util.ServerRegionReplicaUtil;
041import org.apache.yetus.audience.InterfaceAudience;
042import org.slf4j.Logger;
043import org.slf4j.LoggerFactory;
044
045import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting;
046import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
047
048import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
049import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos;
050import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos;
051import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.CreateTableState;
052
053@InterfaceAudience.Private
054public class CreateTableProcedure
055    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,
067      final TableDescriptor tableDescriptor, final RegionInfo[] newRegions) {
068    this(env, tableDescriptor, newRegions, null);
069  }
070
071  public CreateTableProcedure(final MasterProcedureEnv env,
072      final TableDescriptor tableDescriptor, final RegionInfo[] newRegions,
073      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          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(env.getAssignmentManager()
110            .createRoundRobinAssignProcedures(newRegions));
111          setNextState(CreateTableState.CREATE_TABLE_UPDATE_DESC_CACHE);
112          break;
113        case CREATE_TABLE_UPDATE_DESC_CACHE:
114          setEnabledState(env, getTableName());
115          updateTableDescCache(env, getTableName());
116          setNextState(CreateTableState.CREATE_TABLE_POST_OPERATION);
117          break;
118        case CREATE_TABLE_POST_OPERATION:
119          postCreate(env);
120          return Flow.NO_MORE_STATE;
121        default:
122          throw new UnsupportedOperationException("unhandled state=" + state);
123      }
124    } catch (IOException e) {
125      if (isRollbackSupported(state)) {
126        setFailure("master-create-table", e);
127      } else {
128        LOG.warn("Retriable error trying to create table=" + getTableName() + " state=" + state, e);
129      }
130    }
131    return Flow.HAS_MORE_STATE;
132  }
133
134  @Override
135  protected void rollbackState(final MasterProcedureEnv env, final CreateTableState state)
136      throws IOException {
137    if (state == CreateTableState.CREATE_TABLE_PRE_OPERATION) {
138      // nothing to rollback, pre-create is just table-state checks.
139      // We can fail if the table does exist or the descriptor is malformed.
140      // TODO: coprocessor rollback semantic is still undefined.
141      if (hasException() /* avoid NPE */ &&
142          getException().getCause().getClass() != TableExistsException.class) {
143        DeleteTableProcedure.deleteTableStates(env, getTableName());
144
145        final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
146        if (cpHost != null) {
147          cpHost.postDeleteTable(getTableName());
148        }
149      }
150
151      releaseSyncLatch();
152      return;
153    }
154
155    // The procedure doesn't have a rollback. The execution will succeed, at some point.
156    throw new UnsupportedOperationException("unhandled state=" + state);
157  }
158
159  @Override
160  protected boolean isRollbackSupported(final CreateTableState state) {
161    switch (state) {
162      case CREATE_TABLE_PRE_OPERATION:
163        return true;
164      default:
165        return false;
166    }
167  }
168
169  @Override
170  protected CreateTableState getState(final int stateId) {
171    return CreateTableState.forNumber(stateId);
172  }
173
174  @Override
175  protected int getStateId(final CreateTableState state) {
176    return state.getNumber();
177  }
178
179  @Override
180  protected CreateTableState getInitialState() {
181    return CreateTableState.CREATE_TABLE_PRE_OPERATION;
182  }
183
184  @Override
185  public TableName getTableName() {
186    return tableDescriptor.getTableName();
187  }
188
189  @Override
190  public TableOperationType getTableOperationType() {
191    return TableOperationType.CREATE;
192  }
193
194  @Override
195  protected void serializeStateData(ProcedureStateSerializer serializer)
196      throws IOException {
197    super.serializeStateData(serializer);
198
199    MasterProcedureProtos.CreateTableStateData.Builder state =
200      MasterProcedureProtos.CreateTableStateData.newBuilder()
201        .setUserInfo(MasterProcedureUtil.toProtoUserInfo(getUser()))
202            .setTableSchema(ProtobufUtil.toTableSchema(tableDescriptor));
203    if (newRegions != null) {
204      for (RegionInfo hri: newRegions) {
205        state.addRegionInfo(ProtobufUtil.toRegionInfo(hri));
206      }
207    }
208    serializer.serialize(state.build());
209  }
210
211  @Override
212  protected void deserializeStateData(ProcedureStateSerializer serializer)
213      throws IOException {
214    super.deserializeStateData(serializer);
215
216    MasterProcedureProtos.CreateTableStateData state =
217        serializer.deserialize(MasterProcedureProtos.CreateTableStateData.class);
218    setUser(MasterProcedureUtil.toUserInfo(state.getUserInfo()));
219    tableDescriptor = ProtobufUtil.toTableDescriptor(state.getTableSchema());
220    if (state.getRegionInfoCount() == 0) {
221      newRegions = null;
222    } else {
223      newRegions = new ArrayList<>(state.getRegionInfoCount());
224      for (HBaseProtos.RegionInfo hri: state.getRegionInfoList()) {
225        newRegions.add(ProtobufUtil.toRegionInfo(hri));
226      }
227    }
228  }
229
230  @Override
231  protected boolean waitInitialized(MasterProcedureEnv env) {
232    if (getTableName().isSystemTable()) {
233      // Creating system table is part of the initialization, so do not wait here.
234      return false;
235    }
236    return super.waitInitialized(env);
237  }
238
239  private boolean prepareCreate(final MasterProcedureEnv env) throws IOException {
240    final TableName tableName = getTableName();
241    if (MetaTableAccessor.tableExists(env.getMasterServices().getConnection(), tableName)) {
242      setFailure("master-create-table", new TableExistsException(getTableName()));
243      return false;
244    }
245
246    // check that we have at least 1 CF
247    if (tableDescriptor.getColumnFamilyCount() == 0) {
248      setFailure("master-create-table", new DoNotRetryIOException("Table " +
249          getTableName().toString() + " should have at least one column family."));
250      return false;
251    }
252
253    return true;
254  }
255
256  private void preCreate(final MasterProcedureEnv env)
257      throws IOException, InterruptedException {
258    if (!getTableName().isSystemTable()) {
259      ProcedureSyncWait.getMasterQuotaManager(env)
260        .checkNamespaceTableAndRegionQuota(
261          getTableName(), (newRegions != null ? newRegions.size() : 0));
262    }
263
264    final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
265    if (cpHost != null) {
266      final RegionInfo[] regions = newRegions == null ? null :
267        newRegions.toArray(new RegionInfo[newRegions.size()]);
268      cpHost.preCreateTableAction(tableDescriptor, regions, getUser());
269    }
270  }
271
272  private void postCreate(final MasterProcedureEnv env)
273      throws IOException, InterruptedException {
274    final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
275    if (cpHost != null) {
276      final RegionInfo[] regions = (newRegions == null) ? null :
277        newRegions.toArray(new RegionInfo[newRegions.size()]);
278      cpHost.postCompletedCreateTableAction(tableDescriptor, regions, getUser());
279    }
280  }
281
282  protected interface CreateHdfsRegions {
283    List<RegionInfo> createHdfsRegions(final MasterProcedureEnv env,
284      final Path tableRootDir, final TableName tableName,
285      final List<RegionInfo> newRegions) throws IOException;
286  }
287
288  protected static List<RegionInfo> createFsLayout(final MasterProcedureEnv env,
289      final TableDescriptor tableDescriptor, final List<RegionInfo> newRegions)
290      throws IOException {
291    return createFsLayout(env, tableDescriptor, newRegions, new CreateHdfsRegions() {
292      @Override
293      public List<RegionInfo> createHdfsRegions(final MasterProcedureEnv env,
294          final Path tableRootDir, final TableName tableName,
295          final List<RegionInfo> newRegions) throws IOException {
296        RegionInfo[] regions = newRegions != null ?
297          newRegions.toArray(new RegionInfo[newRegions.size()]) : null;
298        return ModifyRegionUtils.createRegions(env.getMasterConfiguration(),
299            tableRootDir, tableDescriptor, regions, null);
300      }
301    });
302  }
303
304  protected static List<RegionInfo> createFsLayout(final MasterProcedureEnv env,
305      final TableDescriptor tableDescriptor, List<RegionInfo> newRegions,
306      final CreateHdfsRegions hdfsRegionHandler) throws IOException {
307    final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem();
308    final Path tempdir = mfs.getTempDir();
309
310    // 1. Create Table Descriptor
311    // using a copy of descriptor, table will be created enabling first
312    final Path tempTableDir = CommonFSUtils.getTableDir(tempdir, tableDescriptor.getTableName());
313    ((FSTableDescriptors)(env.getMasterServices().getTableDescriptors()))
314        .createTableDescriptorForTableDirectory(tempTableDir, tableDescriptor, false);
315
316    // 2. Create Regions
317    newRegions = hdfsRegionHandler.createHdfsRegions(env, tempdir,
318            tableDescriptor.getTableName(), newRegions);
319
320    // 3. Move Table temp directory to the hbase root location
321    moveTempDirectoryToHBaseRoot(env, tableDescriptor, tempTableDir);
322
323    return newRegions;
324  }
325
326  protected static void moveTempDirectoryToHBaseRoot(
327    final MasterProcedureEnv env,
328    final TableDescriptor tableDescriptor,
329    final Path tempTableDir) throws IOException {
330    final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem();
331    final Path tableDir =
332      CommonFSUtils.getTableDir(mfs.getRootDir(), tableDescriptor.getTableName());
333    FileSystem fs = mfs.getFileSystem();
334    if (!fs.delete(tableDir, true) && fs.exists(tableDir)) {
335      throw new IOException("Couldn't delete " + tableDir);
336    }
337    if (!fs.rename(tempTableDir, tableDir)) {
338      throw new IOException("Unable to move table from temp=" + tempTableDir +
339        " to hbase root=" + tableDir);
340    }
341  }
342
343  protected static List<RegionInfo> addTableToMeta(final MasterProcedureEnv env,
344      final TableDescriptor tableDescriptor,
345      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 = RegionReplicaUtil.addReplicas(tableDescriptor, regions, 1,
353      tableDescriptor.getRegionReplication());
354
355    // Add regions to META
356    addRegionsToMeta(env, tableDescriptor, newRegions);
357
358    // Setup replication for region replicas if needed
359    if (tableDescriptor.getRegionReplication() > 1) {
360      ServerRegionReplicaUtil.setupRegionReplicaReplication(env.getMasterConfiguration());
361    }
362    return newRegions;
363  }
364
365  protected static void setEnablingState(final MasterProcedureEnv env, final TableName tableName)
366      throws IOException {
367    // Mark the table as Enabling
368    env.getMasterServices().getTableStateManager()
369      .setTableState(tableName, TableState.State.ENABLING);
370  }
371
372  protected static void setEnabledState(final MasterProcedureEnv env, final TableName tableName)
373      throws IOException {
374    // Enable table
375    env.getMasterServices().getTableStateManager()
376      .setTableState(tableName, TableState.State.ENABLED);
377  }
378
379  /**
380   * Add the specified set of regions to the hbase:meta table.
381   */
382  private static void addRegionsToMeta(final MasterProcedureEnv env,
383      final TableDescriptor tableDescriptor,
384      final List<RegionInfo> regionInfos) throws IOException {
385    MetaTableAccessor.addRegionsToMeta(env.getMasterServices().getConnection(),
386      regionInfos, tableDescriptor.getRegionReplication());
387  }
388
389  protected static void updateTableDescCache(final MasterProcedureEnv env,
390      final TableName tableName) throws IOException {
391    env.getMasterServices().getTableDescriptors().get(tableName);
392  }
393
394  @Override
395  protected boolean shouldWaitClientAck(MasterProcedureEnv env) {
396    // system tables are created on bootstrap internally by the system
397    // the client does not know about this procedures.
398    return !getTableName().isSystemTable();
399  }
400
401  @VisibleForTesting
402  RegionInfo getFirstRegionInfo() {
403    if (newRegions == null || newRegions.isEmpty()) {
404      return null;
405    }
406    return newRegions.get(0);
407  }
408}