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}