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.Objects;
022import java.util.Optional;
023import org.apache.hadoop.hbase.TableName;
024import org.apache.hadoop.hbase.client.TableDescriptor;
025import org.apache.hadoop.hbase.procedure2.ProcedureStateSerializer;
026import org.apache.hadoop.hbase.procedure2.ProcedureSuspendedException;
027import org.apache.hadoop.hbase.procedure2.ProcedureYieldException;
028import org.apache.yetus.audience.InterfaceAudience;
029import org.slf4j.Logger;
030import org.slf4j.LoggerFactory;
031
032import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
033import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.ModifyTableDescriptorState;
034import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.ModifyTableDescriptorStateData;
035
036/**
037 * The procedure will only update the table descriptor without reopening all the regions.
038 * <p/>
039 * It is usually used for migrating when upgrading, where we need to add something into the table
040 * descriptor, such as the rs group information.
041 */
042@InterfaceAudience.Private
043public abstract class ModifyTableDescriptorProcedure
044  extends AbstractStateMachineTableProcedure<ModifyTableDescriptorState> {
045
046  private static final Logger LOG = LoggerFactory.getLogger(ModifyTableDescriptorProcedure.class);
047
048  private TableName tableName;
049
050  private TableDescriptor modifiedTableDescriptor;
051
052  protected ModifyTableDescriptorProcedure() {
053  }
054
055  protected ModifyTableDescriptorProcedure(MasterProcedureEnv env, TableName tableName) {
056    super(env);
057    this.tableName = Objects.requireNonNull(tableName);
058  }
059
060  @Override
061  public TableName getTableName() {
062    return tableName;
063  }
064
065  @Override
066  public TableOperationType getTableOperationType() {
067    return TableOperationType.EDIT;
068  }
069
070  /**
071   * Sub class should implement this method to modify the table descriptor, such as storing the rs
072   * group information.
073   * <p/>
074   * Since the migrating is asynchronouns, it is possible that users have already changed the rs
075   * group for a table, in this case we do not need to modify the table descriptor any more, then
076   * you could just return {@link Optional#empty()}.
077   */
078  protected abstract Optional<TableDescriptor> modify(MasterProcedureEnv env,
079    TableDescriptor current) throws IOException;
080
081  @Override
082  protected Flow executeFromState(MasterProcedureEnv env, ModifyTableDescriptorState state)
083    throws ProcedureSuspendedException, ProcedureYieldException, InterruptedException {
084    try {
085      switch (state) {
086        case MODIFY_TABLE_DESCRIPTOR_PREPARE:
087          TableDescriptor current = env.getMasterServices().getTableDescriptors().get(tableName);
088          if (current == null) {
089            LOG.info("Table {} does not exist, skip modifying", tableName);
090            return Flow.NO_MORE_STATE;
091          }
092          Optional<TableDescriptor> modified = modify(env, current);
093          if (modified.isPresent()) {
094            modifiedTableDescriptor = modified.get();
095            setNextState(ModifyTableDescriptorState.MODIFY_TABLE_DESCRIPTOR_UPDATE);
096            return Flow.HAS_MORE_STATE;
097          } else {
098            // do not need to modify
099            return Flow.NO_MORE_STATE;
100          }
101        case MODIFY_TABLE_DESCRIPTOR_UPDATE:
102          env.getMasterServices().getTableDescriptors().update(modifiedTableDescriptor);
103          return Flow.NO_MORE_STATE;
104        default:
105          throw new UnsupportedOperationException("unhandled state=" + state);
106      }
107    } catch (IOException e) {
108      if (isRollbackSupported(state)) {
109        setFailure("master-modify-table-descriptor", e);
110      } else {
111        LOG.warn("Retriable error trying to modify table descriptor={} (in state={})",
112          getTableName(), state, e);
113      }
114    }
115    return Flow.HAS_MORE_STATE;
116  }
117
118  @Override
119  protected boolean holdLock(MasterProcedureEnv env) {
120    // here we want to make sure that our modification result will not be overwrite by other MTPs,
121    // so we set holdLock to true. Since we do not need to schedule any sub procedures, especially
122    // no remote procedures, so it is OK for us a hold the lock all the time, it will not hurt the
123    // availability too much.
124    return true;
125  }
126
127  @Override
128  protected void rollbackState(MasterProcedureEnv env, ModifyTableDescriptorState state)
129    throws IOException, InterruptedException {
130    if (state == ModifyTableDescriptorState.MODIFY_TABLE_DESCRIPTOR_PREPARE) {
131      return;
132    }
133    throw new UnsupportedOperationException("unhandled state=" + state);
134  }
135
136  @Override
137  protected boolean isRollbackSupported(ModifyTableDescriptorState state) {
138    return state == ModifyTableDescriptorState.MODIFY_TABLE_DESCRIPTOR_PREPARE;
139  }
140
141  @Override
142  protected ModifyTableDescriptorState getState(int stateId) {
143    return ModifyTableDescriptorState.forNumber(stateId);
144  }
145
146  @Override
147  protected int getStateId(ModifyTableDescriptorState state) {
148    return state.getNumber();
149  }
150
151  @Override
152  protected ModifyTableDescriptorState getInitialState() {
153    return ModifyTableDescriptorState.MODIFY_TABLE_DESCRIPTOR_PREPARE;
154  }
155
156  @Override
157  protected void serializeStateData(ProcedureStateSerializer serializer) throws IOException {
158    super.serializeStateData(serializer);
159    ModifyTableDescriptorStateData.Builder builder = ModifyTableDescriptorStateData.newBuilder()
160      .setTableName(ProtobufUtil.toProtoTableName(tableName));
161    if (modifiedTableDescriptor != null) {
162      builder.setModifiedTableSchema(ProtobufUtil.toTableSchema(modifiedTableDescriptor));
163    }
164    serializer.serialize(builder.build());
165  }
166
167  @Override
168  protected void deserializeStateData(ProcedureStateSerializer serializer) throws IOException {
169    super.deserializeStateData(serializer);
170    ModifyTableDescriptorStateData data =
171      serializer.deserialize(ModifyTableDescriptorStateData.class);
172    tableName = ProtobufUtil.toTableName(data.getTableName());
173    if (data.hasModifiedTableSchema()) {
174      modifiedTableDescriptor = ProtobufUtil.toTableDescriptor(data.getModifiedTableSchema());
175    }
176  }
177}