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