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.Arrays; 023import java.util.List; 024import org.apache.hadoop.hbase.HBaseIOException; 025import org.apache.hadoop.hbase.TableName; 026import org.apache.hadoop.hbase.TableNotDisabledException; 027import org.apache.hadoop.hbase.TableNotFoundException; 028import org.apache.hadoop.hbase.client.RegionInfo; 029import org.apache.hadoop.hbase.client.RegionInfoBuilder; 030import org.apache.hadoop.hbase.client.RegionReplicaUtil; 031import org.apache.hadoop.hbase.client.TableDescriptor; 032import org.apache.hadoop.hbase.master.MasterCoprocessorHost; 033import org.apache.hadoop.hbase.procedure2.ProcedureStateSerializer; 034import org.apache.hadoop.hbase.util.ModifyRegionUtils; 035import org.apache.yetus.audience.InterfaceAudience; 036import org.slf4j.Logger; 037import org.slf4j.LoggerFactory; 038 039import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; 040import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos; 041import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos; 042import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.TruncateTableState; 043 044@InterfaceAudience.Private 045public class TruncateTableProcedure extends AbstractStateMachineTableProcedure<TruncateTableState> { 046 private static final Logger LOG = LoggerFactory.getLogger(TruncateTableProcedure.class); 047 048 private boolean preserveSplits; 049 private List<RegionInfo> regions; 050 private TableDescriptor tableDescriptor; 051 private TableName tableName; 052 053 public TruncateTableProcedure() { 054 // Required by the Procedure framework to create the procedure on replay 055 super(); 056 } 057 058 public TruncateTableProcedure(final MasterProcedureEnv env, final TableName tableName, 059 boolean preserveSplits) throws HBaseIOException { 060 this(env, tableName, preserveSplits, null); 061 } 062 063 public TruncateTableProcedure(final MasterProcedureEnv env, final TableName tableName, 064 boolean preserveSplits, ProcedurePrepareLatch latch) throws HBaseIOException { 065 super(env, latch); 066 this.tableName = tableName; 067 preflightChecks(env, false); 068 this.preserveSplits = preserveSplits; 069 } 070 071 @Override 072 protected Flow executeFromState(final MasterProcedureEnv env, TruncateTableState state) 073 throws InterruptedException { 074 if (LOG.isTraceEnabled()) { 075 LOG.trace(this + " execute state=" + state); 076 } 077 try { 078 switch (state) { 079 case TRUNCATE_TABLE_PRE_OPERATION: 080 // Verify if we can truncate the table 081 if (!prepareTruncate(env)) { 082 assert isFailed() : "the truncate should have an exception here"; 083 return Flow.NO_MORE_STATE; 084 } 085 086 // TODO: Move out... in the acquireLock() 087 LOG.debug("waiting for '" + getTableName() + "' regions in transition"); 088 regions = env.getAssignmentManager().getRegionStates().getRegionsOfTable(getTableName()); 089 RegionReplicaUtil.removeNonDefaultRegions(regions); 090 assert regions != null && !regions.isEmpty() : "unexpected 0 regions"; 091 ProcedureSyncWait.waitRegionInTransition(env, regions); 092 093 // Call coprocessors 094 preTruncate(env); 095 096 // We need to cache table descriptor in the initial stage, so that it's saved within 097 // the procedure stage and can get recovered if the procedure crashes between 098 // TRUNCATE_TABLE_REMOVE_FROM_META and TRUNCATE_TABLE_CREATE_FS_LAYOUT 099 tableDescriptor = env.getMasterServices().getTableDescriptors().get(tableName); 100 setNextState(TruncateTableState.TRUNCATE_TABLE_CLEAR_FS_LAYOUT); 101 break; 102 case TRUNCATE_TABLE_CLEAR_FS_LAYOUT: 103 DeleteTableProcedure.deleteFromFs(env, getTableName(), regions, true); 104 // NOTE: It's very important that we create new HRegions before next state, so that 105 // they get persisted in procedure state before we start using them for anything. 106 // Otherwise, if we create them in next step and master crashes after creating fs 107 // layout but before saving state, region re-created after recovery will have different 108 // regionId(s) and encoded names. That will lead to unwanted regions in FS layout 109 // (which were created before the crash). 110 if (!preserveSplits) { 111 // if we are not preserving splits, generate a new single region 112 regions = Arrays.asList(ModifyRegionUtils.createRegionInfos(tableDescriptor, null)); 113 } else { 114 regions = recreateRegionInfo(regions); 115 } 116 setNextState(TruncateTableState.TRUNCATE_TABLE_REMOVE_FROM_META); 117 break; 118 case TRUNCATE_TABLE_REMOVE_FROM_META: 119 List<RegionInfo> originalRegions = 120 env.getAssignmentManager().getRegionStates().getRegionsOfTable(getTableName()); 121 DeleteTableProcedure.deleteFromMeta(env, getTableName(), originalRegions); 122 DeleteTableProcedure.deleteAssignmentState(env, getTableName()); 123 setNextState(TruncateTableState.TRUNCATE_TABLE_CREATE_FS_LAYOUT); 124 break; 125 case TRUNCATE_TABLE_CREATE_FS_LAYOUT: 126 DeleteTableProcedure.deleteFromFs(env, getTableName(), regions, true); 127 regions = CreateTableProcedure.createFsLayout(env, tableDescriptor, regions); 128 env.getMasterServices().getTableDescriptors().update(tableDescriptor, true); 129 setNextState(TruncateTableState.TRUNCATE_TABLE_ADD_TO_META); 130 break; 131 case TRUNCATE_TABLE_ADD_TO_META: 132 regions = CreateTableProcedure.addTableToMeta(env, tableDescriptor, regions); 133 setNextState(TruncateTableState.TRUNCATE_TABLE_ASSIGN_REGIONS); 134 break; 135 case TRUNCATE_TABLE_ASSIGN_REGIONS: 136 CreateTableProcedure.setEnablingState(env, getTableName()); 137 addChildProcedure(env.getAssignmentManager().createRoundRobinAssignProcedures(regions)); 138 setNextState(TruncateTableState.TRUNCATE_TABLE_POST_OPERATION); 139 tableDescriptor = null; 140 regions = null; 141 break; 142 case TRUNCATE_TABLE_POST_OPERATION: 143 CreateTableProcedure.setEnabledState(env, getTableName()); 144 postTruncate(env); 145 LOG.debug("truncate '" + getTableName() + "' completed"); 146 return Flow.NO_MORE_STATE; 147 default: 148 throw new UnsupportedOperationException("unhandled state=" + state); 149 } 150 } catch (IOException e) { 151 if (isRollbackSupported(state)) { 152 setFailure("master-truncate-table", e); 153 } else { 154 LOG.warn("Retriable error trying to truncate table=" + getTableName() + " state=" + state, 155 e); 156 } 157 } 158 return Flow.HAS_MORE_STATE; 159 } 160 161 @Override 162 protected void rollbackState(final MasterProcedureEnv env, final TruncateTableState state) { 163 if (state == TruncateTableState.TRUNCATE_TABLE_PRE_OPERATION) { 164 // nothing to rollback, pre-truncate is just table-state checks. 165 // We can fail if the table does not exist or is not disabled. 166 // TODO: coprocessor rollback semantic is still undefined. 167 return; 168 } 169 170 // The truncate doesn't have a rollback. The execution will succeed, at some point. 171 throw new UnsupportedOperationException("unhandled state=" + state); 172 } 173 174 @Override 175 protected void completionCleanup(final MasterProcedureEnv env) { 176 releaseSyncLatch(); 177 } 178 179 @Override 180 protected boolean isRollbackSupported(final TruncateTableState state) { 181 switch (state) { 182 case TRUNCATE_TABLE_PRE_OPERATION: 183 return true; 184 default: 185 return false; 186 } 187 } 188 189 @Override 190 protected TruncateTableState getState(final int stateId) { 191 return TruncateTableState.forNumber(stateId); 192 } 193 194 @Override 195 protected int getStateId(final TruncateTableState state) { 196 return state.getNumber(); 197 } 198 199 @Override 200 protected TruncateTableState getInitialState() { 201 return TruncateTableState.TRUNCATE_TABLE_PRE_OPERATION; 202 } 203 204 @Override 205 protected boolean holdLock(MasterProcedureEnv env) { 206 return true; 207 } 208 209 @Override 210 public TableName getTableName() { 211 return tableName; 212 } 213 214 @Override 215 public TableOperationType getTableOperationType() { 216 return TableOperationType.EDIT; 217 } 218 219 @Override 220 public void toStringClassDetails(StringBuilder sb) { 221 sb.append(getClass().getSimpleName()); 222 sb.append(" (table="); 223 sb.append(getTableName()); 224 sb.append(" preserveSplits="); 225 sb.append(preserveSplits); 226 sb.append(")"); 227 } 228 229 @Override 230 protected void serializeStateData(ProcedureStateSerializer serializer) throws IOException { 231 super.serializeStateData(serializer); 232 233 MasterProcedureProtos.TruncateTableStateData.Builder state = 234 MasterProcedureProtos.TruncateTableStateData.newBuilder() 235 .setUserInfo(MasterProcedureUtil.toProtoUserInfo(getUser())) 236 .setPreserveSplits(preserveSplits); 237 if (tableDescriptor != null) { 238 state.setTableSchema(ProtobufUtil.toTableSchema(tableDescriptor)); 239 } else { 240 state.setTableName(ProtobufUtil.toProtoTableName(tableName)); 241 } 242 if (regions != null) { 243 for (RegionInfo hri : regions) { 244 state.addRegionInfo(ProtobufUtil.toRegionInfo(hri)); 245 } 246 } 247 serializer.serialize(state.build()); 248 } 249 250 @Override 251 protected void deserializeStateData(ProcedureStateSerializer serializer) throws IOException { 252 super.deserializeStateData(serializer); 253 254 MasterProcedureProtos.TruncateTableStateData state = 255 serializer.deserialize(MasterProcedureProtos.TruncateTableStateData.class); 256 setUser(MasterProcedureUtil.toUserInfo(state.getUserInfo())); 257 if (state.hasTableSchema()) { 258 tableDescriptor = ProtobufUtil.toTableDescriptor(state.getTableSchema()); 259 tableName = tableDescriptor.getTableName(); 260 } else { 261 tableName = ProtobufUtil.toTableName(state.getTableName()); 262 } 263 preserveSplits = state.getPreserveSplits(); 264 if (state.getRegionInfoCount() == 0) { 265 regions = null; 266 } else { 267 regions = new ArrayList<>(state.getRegionInfoCount()); 268 for (HBaseProtos.RegionInfo hri : state.getRegionInfoList()) { 269 regions.add(ProtobufUtil.toRegionInfo(hri)); 270 } 271 } 272 } 273 274 private static List<RegionInfo> recreateRegionInfo(final List<RegionInfo> regions) { 275 ArrayList<RegionInfo> newRegions = new ArrayList<>(regions.size()); 276 for (RegionInfo hri : regions) { 277 newRegions.add(RegionInfoBuilder.newBuilder(hri.getTable()).setStartKey(hri.getStartKey()) 278 .setEndKey(hri.getEndKey()).build()); 279 } 280 return newRegions; 281 } 282 283 private boolean prepareTruncate(final MasterProcedureEnv env) throws IOException { 284 try { 285 env.getMasterServices().checkTableModifiable(getTableName()); 286 } catch (TableNotFoundException | TableNotDisabledException e) { 287 setFailure("master-truncate-table", e); 288 return false; 289 } 290 return true; 291 } 292 293 private boolean preTruncate(final MasterProcedureEnv env) 294 throws IOException, InterruptedException { 295 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); 296 if (cpHost != null) { 297 final TableName tableName = getTableName(); 298 cpHost.preTruncateTableAction(tableName, getUser()); 299 } 300 return true; 301 } 302 303 private void postTruncate(final MasterProcedureEnv env) throws IOException, InterruptedException { 304 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); 305 if (cpHost != null) { 306 final TableName tableName = getTableName(); 307 cpHost.postCompletedTruncateTableAction(tableName, getUser()); 308 } 309 } 310 311 RegionInfo getFirstRegionInfo() { 312 if (regions == null || regions.isEmpty()) { 313 return null; 314 } 315 return regions.get(0); 316 } 317}