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