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 java.util.function.Supplier;
025import org.apache.hadoop.fs.FileSystem;
026import org.apache.hadoop.fs.Path;
027import org.apache.hadoop.hbase.DoNotRetryIOException;
028import org.apache.hadoop.hbase.MetaTableAccessor;
029import org.apache.hadoop.hbase.NamespaceDescriptor;
030import org.apache.hadoop.hbase.TableExistsException;
031import org.apache.hadoop.hbase.TableName;
032import org.apache.hadoop.hbase.client.RegionInfo;
033import org.apache.hadoop.hbase.client.RegionReplicaUtil;
034import org.apache.hadoop.hbase.client.TableDescriptor;
035import org.apache.hadoop.hbase.client.TableState;
036import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
037import org.apache.hadoop.hbase.master.MasterFileSystem;
038import org.apache.hadoop.hbase.procedure2.ProcedureStateSerializer;
039import org.apache.hadoop.hbase.rsgroup.RSGroupInfo;
040import org.apache.hadoop.hbase.util.FSTableDescriptors;
041import org.apache.hadoop.hbase.util.FSUtils;
042import org.apache.hadoop.hbase.util.ModifyRegionUtils;
043import org.apache.hadoop.hbase.util.ServerRegionReplicaUtil;
044import org.apache.yetus.audience.InterfaceAudience;
045import org.slf4j.Logger;
046import org.slf4j.LoggerFactory;
047
048import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting;
049import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
050
051import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
052import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos;
053import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos;
054import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.CreateTableState;
055
056@InterfaceAudience.Private
057public class CreateTableProcedure
058    extends AbstractStateMachineTableProcedure<CreateTableState> {
059  private static final Logger LOG = LoggerFactory.getLogger(CreateTableProcedure.class);
060
061  private TableDescriptor tableDescriptor;
062  private List<RegionInfo> newRegions;
063
064  public CreateTableProcedure() {
065    // Required by the Procedure framework to create the procedure on replay
066    super();
067  }
068
069  public CreateTableProcedure(final MasterProcedureEnv env,
070      final TableDescriptor tableDescriptor, final RegionInfo[] newRegions) {
071    this(env, tableDescriptor, newRegions, null);
072  }
073
074  public CreateTableProcedure(final MasterProcedureEnv env,
075      final TableDescriptor tableDescriptor, final RegionInfo[] newRegions,
076      final ProcedurePrepareLatch syncLatch) {
077    super(env, syncLatch);
078    this.tableDescriptor = tableDescriptor;
079    this.newRegions = newRegions != null ? Lists.newArrayList(newRegions) : null;
080  }
081
082  @Override
083  protected Flow executeFromState(final MasterProcedureEnv env, final CreateTableState state)
084      throws InterruptedException {
085    LOG.info("{} execute state={}", this, state);
086    try {
087      switch (state) {
088        case CREATE_TABLE_PRE_OPERATION:
089          // Verify if we can create the table
090          boolean exists = !prepareCreate(env);
091          releaseSyncLatch();
092
093          if (exists) {
094            assert isFailed() : "the delete should have an exception here";
095            return Flow.NO_MORE_STATE;
096          }
097
098          preCreate(env);
099          setNextState(CreateTableState.CREATE_TABLE_WRITE_FS_LAYOUT);
100          break;
101        case CREATE_TABLE_WRITE_FS_LAYOUT:
102          DeleteTableProcedure.deleteFromFs(env, getTableName(), newRegions, true);
103          newRegions = createFsLayout(env, tableDescriptor, newRegions);
104          setNextState(CreateTableState.CREATE_TABLE_ADD_TO_META);
105          break;
106        case CREATE_TABLE_ADD_TO_META:
107          newRegions = addTableToMeta(env, tableDescriptor, newRegions);
108          setNextState(CreateTableState.CREATE_TABLE_ASSIGN_REGIONS);
109          break;
110        case CREATE_TABLE_ASSIGN_REGIONS:
111          setEnablingState(env, getTableName());
112          addChildProcedure(env.getAssignmentManager()
113            .createRoundRobinAssignProcedures(newRegions));
114          setNextState(CreateTableState.CREATE_TABLE_UPDATE_DESC_CACHE);
115          break;
116        case CREATE_TABLE_UPDATE_DESC_CACHE:
117          setEnabledState(env, getTableName());
118          updateTableDescCache(env, getTableName());
119          setNextState(CreateTableState.CREATE_TABLE_POST_OPERATION);
120          break;
121        case CREATE_TABLE_POST_OPERATION:
122          postCreate(env);
123          return Flow.NO_MORE_STATE;
124        default:
125          throw new UnsupportedOperationException("unhandled state=" + state);
126      }
127    } catch (IOException e) {
128      if (isRollbackSupported(state)) {
129        setFailure("master-create-table", e);
130      } else {
131        LOG.warn("Retriable error trying to create table=" + getTableName() + " state=" + state, e);
132      }
133    }
134    return Flow.HAS_MORE_STATE;
135  }
136
137  @Override
138  protected void rollbackState(final MasterProcedureEnv env, final CreateTableState state)
139      throws IOException {
140    if (state == CreateTableState.CREATE_TABLE_PRE_OPERATION) {
141      // nothing to rollback, pre-create is just table-state checks.
142      // We can fail if the table does exist or the descriptor is malformed.
143      // TODO: coprocessor rollback semantic is still undefined.
144      if (hasException() /* avoid NPE */ &&
145          getException().getCause().getClass() != TableExistsException.class) {
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)
199      throws IOException {
200    super.serializeStateData(serializer);
201
202    MasterProcedureProtos.CreateTableStateData.Builder state =
203      MasterProcedureProtos.CreateTableStateData.newBuilder()
204        .setUserInfo(MasterProcedureUtil.toProtoUserInfo(getUser()))
205            .setTableSchema(ProtobufUtil.toTableSchema(tableDescriptor));
206    if (newRegions != null) {
207      for (RegionInfo hri: newRegions) {
208        state.addRegionInfo(ProtobufUtil.toRegionInfo(hri));
209      }
210    }
211    serializer.serialize(state.build());
212  }
213
214  @Override
215  protected void deserializeStateData(ProcedureStateSerializer serializer)
216      throws IOException {
217    super.deserializeStateData(serializer);
218
219    MasterProcedureProtos.CreateTableStateData state =
220        serializer.deserialize(MasterProcedureProtos.CreateTableStateData.class);
221    setUser(MasterProcedureUtil.toUserInfo(state.getUserInfo()));
222    tableDescriptor = ProtobufUtil.toTableDescriptor(state.getTableSchema());
223    if (state.getRegionInfoCount() == 0) {
224      newRegions = null;
225    } else {
226      newRegions = new ArrayList<>(state.getRegionInfoCount());
227      for (HBaseProtos.RegionInfo hri: state.getRegionInfoList()) {
228        newRegions.add(ProtobufUtil.toRegionInfo(hri));
229      }
230    }
231  }
232
233  @Override
234  protected boolean waitInitialized(MasterProcedureEnv env) {
235    if (getTableName().isSystemTable()) {
236      // Creating system table is part of the initialization, so do not wait here.
237      return false;
238    }
239    return super.waitInitialized(env);
240  }
241
242  @Override
243  protected LockState acquireLock(final MasterProcedureEnv env) {
244    if (env.getProcedureScheduler().waitTableExclusiveLock(this, getTableName())) {
245      return LockState.LOCK_EVENT_WAIT;
246    }
247    return LockState.LOCK_ACQUIRED;
248  }
249
250  private boolean prepareCreate(final MasterProcedureEnv env) throws IOException {
251    final TableName tableName = getTableName();
252    if (MetaTableAccessor.tableExists(env.getMasterServices().getConnection(), tableName)) {
253      setFailure("master-create-table", new TableExistsException(getTableName()));
254      return false;
255    }
256
257    // check that we have at least 1 CF
258    if (tableDescriptor.getColumnFamilyCount() == 0) {
259      setFailure("master-create-table", new DoNotRetryIOException(
260        "Table " + getTableName().toString() + " should have at least one column family."));
261      return false;
262    }
263    if (!tableName.isSystemTable()) {
264      // do not check rs group for system tables as we may block the bootstrap.
265      Supplier<String> forWhom = () -> "table " + tableName;
266      RSGroupInfo rsGroupInfo = MasterProcedureUtil.checkGroupExists(
267        env.getMasterServices().getRSGroupInfoManager()::getRSGroup,
268        tableDescriptor.getRegionServerGroup(), forWhom);
269      if (rsGroupInfo == null) {
270        // we do not set rs group info on table, check if we have one on namespace
271        String namespace = tableName.getNamespaceAsString();
272        NamespaceDescriptor nd = env.getMasterServices().getClusterSchema().getNamespace(namespace);
273        forWhom = () -> "table " + tableName + "(inherit from namespace)";
274        rsGroupInfo = MasterProcedureUtil.checkGroupExists(
275          env.getMasterServices().getRSGroupInfoManager()::getRSGroup,
276          MasterProcedureUtil.getNamespaceGroup(nd), forWhom);
277      }
278      MasterProcedureUtil.checkGroupNotEmpty(rsGroupInfo, forWhom);
279    }
280
281    return true;
282  }
283
284  private void preCreate(final MasterProcedureEnv env)
285      throws IOException, InterruptedException {
286    if (!getTableName().isSystemTable()) {
287      ProcedureSyncWait.getMasterQuotaManager(env)
288        .checkNamespaceTableAndRegionQuota(
289          getTableName(), (newRegions != null ? newRegions.size() : 0));
290    }
291
292    final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
293    if (cpHost != null) {
294      final RegionInfo[] regions = newRegions == null ? null :
295        newRegions.toArray(new RegionInfo[newRegions.size()]);
296      cpHost.preCreateTableAction(tableDescriptor, regions, getUser());
297    }
298  }
299
300  private void postCreate(final MasterProcedureEnv env)
301      throws IOException, InterruptedException {
302    final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
303    if (cpHost != null) {
304      final RegionInfo[] regions = (newRegions == null) ? null :
305        newRegions.toArray(new RegionInfo[newRegions.size()]);
306      cpHost.postCompletedCreateTableAction(tableDescriptor, regions, getUser());
307    }
308  }
309
310  protected interface CreateHdfsRegions {
311    List<RegionInfo> createHdfsRegions(final MasterProcedureEnv env,
312      final Path tableRootDir, final TableName tableName,
313      final List<RegionInfo> newRegions) throws IOException;
314  }
315
316  protected static List<RegionInfo> createFsLayout(final MasterProcedureEnv env,
317      final TableDescriptor tableDescriptor, final List<RegionInfo> newRegions)
318      throws IOException {
319    return createFsLayout(env, tableDescriptor, newRegions, new CreateHdfsRegions() {
320      @Override
321      public List<RegionInfo> createHdfsRegions(final MasterProcedureEnv env,
322          final Path tableRootDir, final TableName tableName,
323          final List<RegionInfo> newRegions) throws IOException {
324        RegionInfo[] regions = newRegions != null ?
325          newRegions.toArray(new RegionInfo[newRegions.size()]) : null;
326        return ModifyRegionUtils.createRegions(env.getMasterConfiguration(),
327            tableRootDir, tableDescriptor, regions, null);
328      }
329    });
330  }
331
332  protected static List<RegionInfo> createFsLayout(final MasterProcedureEnv env,
333      final TableDescriptor tableDescriptor, List<RegionInfo> newRegions,
334      final CreateHdfsRegions hdfsRegionHandler) throws IOException {
335    final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem();
336    final Path tempdir = mfs.getTempDir();
337
338    // 1. Create Table Descriptor
339    // using a copy of descriptor, table will be created enabling first
340    final Path tempTableDir = FSUtils.getTableDir(tempdir, tableDescriptor.getTableName());
341    ((FSTableDescriptors)(env.getMasterServices().getTableDescriptors()))
342        .createTableDescriptorForTableDirectory(tempTableDir, tableDescriptor, false);
343
344    // 2. Create Regions
345    newRegions = hdfsRegionHandler.createHdfsRegions(env, tempdir,
346            tableDescriptor.getTableName(), newRegions);
347
348    // 3. Move Table temp directory to the hbase root location
349    moveTempDirectoryToHBaseRoot(env, tableDescriptor, tempTableDir);
350
351    return newRegions;
352  }
353
354  protected static void moveTempDirectoryToHBaseRoot(
355    final MasterProcedureEnv env,
356    final TableDescriptor tableDescriptor,
357    final Path tempTableDir) throws IOException {
358    final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem();
359    final Path tableDir = FSUtils.getTableDir(mfs.getRootDir(), tableDescriptor.getTableName());
360    FileSystem fs = mfs.getFileSystem();
361    if (!fs.delete(tableDir, true) && fs.exists(tableDir)) {
362      throw new IOException("Couldn't delete " + tableDir);
363    }
364    if (!fs.rename(tempTableDir, tableDir)) {
365      throw new IOException("Unable to move table from temp=" + tempTableDir +
366        " to hbase root=" + tableDir);
367    }
368  }
369
370  protected static List<RegionInfo> addTableToMeta(final MasterProcedureEnv env,
371      final TableDescriptor tableDescriptor,
372      final List<RegionInfo> regions) throws IOException {
373    assert (regions != null && regions.size() > 0) : "expected at least 1 region, got " + regions;
374
375    ProcedureSyncWait.waitMetaRegions(env);
376
377    // Add replicas if needed
378    // we need to create regions with replicaIds starting from 1
379    List<RegionInfo> newRegions = RegionReplicaUtil.addReplicas(tableDescriptor, regions, 1,
380      tableDescriptor.getRegionReplication());
381
382    // Add regions to META
383    addRegionsToMeta(env, tableDescriptor, newRegions);
384
385    // Setup replication for region replicas if needed
386    if (tableDescriptor.getRegionReplication() > 1) {
387      ServerRegionReplicaUtil.setupRegionReplicaReplication(env.getMasterConfiguration());
388    }
389    return newRegions;
390  }
391
392  protected static void setEnablingState(final MasterProcedureEnv env, final TableName tableName)
393      throws IOException {
394    // Mark the table as Enabling
395    env.getMasterServices().getTableStateManager()
396      .setTableState(tableName, TableState.State.ENABLING);
397  }
398
399  protected static void setEnabledState(final MasterProcedureEnv env, final TableName tableName)
400      throws IOException {
401    // Enable table
402    env.getMasterServices().getTableStateManager()
403      .setTableState(tableName, TableState.State.ENABLED);
404  }
405
406  /**
407   * Add the specified set of regions to the hbase:meta table.
408   */
409  private static void addRegionsToMeta(final MasterProcedureEnv env,
410      final TableDescriptor tableDescriptor,
411      final List<RegionInfo> regionInfos) throws IOException {
412    MetaTableAccessor.addRegionsToMeta(env.getMasterServices().getConnection(),
413      regionInfos, tableDescriptor.getRegionReplication());
414  }
415
416  protected static void updateTableDescCache(final MasterProcedureEnv env,
417      final TableName tableName) throws IOException {
418    env.getMasterServices().getTableDescriptors().get(tableName);
419  }
420
421  @Override
422  protected boolean shouldWaitClientAck(MasterProcedureEnv env) {
423    // system tables are created on bootstrap internally by the system
424    // the client does not know about this procedures.
425    return !getTableName().isSystemTable();
426  }
427
428  @VisibleForTesting
429  RegionInfo getFirstRegionInfo() {
430    if (newRegions == null || newRegions.isEmpty()) {
431      return null;
432    }
433    return newRegions.get(0);
434  }
435}