1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.hadoop.hbase.master.procedure;
20
21 import com.google.common.annotations.VisibleForTesting;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.OutputStream;
25 import java.security.PrivilegedExceptionAction;
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.List;
29 import org.apache.commons.logging.Log;
30 import org.apache.commons.logging.LogFactory;
31 import org.apache.hadoop.hbase.HRegionInfo;
32 import org.apache.hadoop.hbase.HTableDescriptor;
33 import org.apache.hadoop.hbase.TableName;
34 import org.apache.hadoop.hbase.TableNotDisabledException;
35 import org.apache.hadoop.hbase.TableNotFoundException;
36 import org.apache.hadoop.hbase.classification.InterfaceAudience;
37 import org.apache.hadoop.hbase.exceptions.HBaseException;
38 import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
39 import org.apache.hadoop.hbase.procedure2.StateMachineProcedure;
40 import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
41 import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos;
42 import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos;
43 import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos.TruncateTableState;
44 import org.apache.hadoop.hbase.util.ModifyRegionUtils;
45 import org.apache.hadoop.security.UserGroupInformation;
46
47 @InterfaceAudience.Private
48 public class TruncateTableProcedure
49 extends StateMachineProcedure<MasterProcedureEnv, TruncateTableState>
50 implements TableProcedureInterface {
51 private static final Log LOG = LogFactory.getLog(TruncateTableProcedure.class);
52
53 private boolean preserveSplits;
54 private List<HRegionInfo> regions;
55 private UserGroupInformation user;
56 private HTableDescriptor hTableDescriptor;
57 private TableName tableName;
58
59 public TruncateTableProcedure() {
60
61 }
62
63 public TruncateTableProcedure(final MasterProcedureEnv env, final TableName tableName,
64 boolean preserveSplits) {
65 this.tableName = tableName;
66 this.preserveSplits = preserveSplits;
67 this.user = env.getRequestUser().getUGI();
68 this.setOwner(this.user.getShortUserName());
69 }
70
71 @Override
72 protected Flow executeFromState(final MasterProcedureEnv env, TruncateTableState state)
73 throws InterruptedException {
74 if (LOG.isTraceEnabled()) {
75 LOG.trace(this + " execute state=" + state);
76 }
77 try {
78 switch (state) {
79 case TRUNCATE_TABLE_PRE_OPERATION:
80
81 if (!prepareTruncate(env)) {
82 assert isFailed() : "the truncate should have an exception here";
83 return Flow.NO_MORE_STATE;
84 }
85
86
87 LOG.debug("waiting for '" + getTableName() + "' regions in transition");
88 regions = ProcedureSyncWait.getRegionsFromMeta(env, getTableName(), true, true);
89 assert regions != null && !regions.isEmpty() : "unexpected 0 regions";
90 ProcedureSyncWait.waitRegionInTransition(env, regions);
91
92
93 preTruncate(env);
94
95 setNextState(TruncateTableState.TRUNCATE_TABLE_REMOVE_FROM_META);
96 break;
97 case TRUNCATE_TABLE_REMOVE_FROM_META:
98 hTableDescriptor = env.getMasterServices().getTableDescriptors().get(tableName);
99 DeleteTableProcedure.deleteFromMeta(env, getTableName(), regions);
100 DeleteTableProcedure.deleteAssignmentState(env, getTableName());
101 setNextState(TruncateTableState.TRUNCATE_TABLE_CLEAR_FS_LAYOUT);
102 break;
103 case TRUNCATE_TABLE_CLEAR_FS_LAYOUT:
104 DeleteTableProcedure.deleteFromFs(env, getTableName(), regions, true);
105 setNextState(TruncateTableState.TRUNCATE_TABLE_CREATE_FS_LAYOUT);
106 if (!preserveSplits) {
107
108 regions = Arrays.asList(ModifyRegionUtils.createHRegionInfos(hTableDescriptor, null));
109 } else {
110 regions = recreateRegionInfo(regions);
111 }
112 break;
113 case TRUNCATE_TABLE_CREATE_FS_LAYOUT:
114 DeleteTableProcedure.deleteFromFs(env, getTableName(), regions, true);
115 regions = CreateTableProcedure.createFsLayout(env, hTableDescriptor, regions);
116 CreateTableProcedure.updateTableDescCache(env, getTableName());
117 setNextState(TruncateTableState.TRUNCATE_TABLE_ADD_TO_META);
118 break;
119 case TRUNCATE_TABLE_ADD_TO_META:
120 regions = CreateTableProcedure.addTableToMeta(env, hTableDescriptor, regions);
121 setNextState(TruncateTableState.TRUNCATE_TABLE_ASSIGN_REGIONS);
122 break;
123 case TRUNCATE_TABLE_ASSIGN_REGIONS:
124 CreateTableProcedure.assignRegions(env, getTableName(), regions);
125 setNextState(TruncateTableState.TRUNCATE_TABLE_POST_OPERATION);
126 hTableDescriptor = null;
127 regions = null;
128 break;
129 case TRUNCATE_TABLE_POST_OPERATION:
130 postTruncate(env);
131 LOG.debug("truncate '" + getTableName() + "' completed");
132 return Flow.NO_MORE_STATE;
133 default:
134 throw new UnsupportedOperationException("unhandled state=" + state);
135 }
136 } catch (HBaseException|IOException e) {
137 LOG.warn("Retriable error trying to truncate table=" + getTableName() + " state=" + state, e);
138 }
139 return Flow.HAS_MORE_STATE;
140 }
141
142 @Override
143 protected void rollbackState(final MasterProcedureEnv env, final TruncateTableState state) {
144 if (state == TruncateTableState.TRUNCATE_TABLE_PRE_OPERATION) {
145
146
147 return;
148 }
149
150
151 throw new UnsupportedOperationException("unhandled state=" + state);
152 }
153
154 @Override
155 protected TruncateTableState getState(final int stateId) {
156 return TruncateTableState.valueOf(stateId);
157 }
158
159 @Override
160 protected int getStateId(final TruncateTableState state) {
161 return state.getNumber();
162 }
163
164 @Override
165 protected TruncateTableState getInitialState() {
166 return TruncateTableState.TRUNCATE_TABLE_PRE_OPERATION;
167 }
168
169 @Override
170 public TableName getTableName() {
171 return tableName;
172 }
173
174 @Override
175 public TableOperationType getTableOperationType() {
176 return TableOperationType.EDIT;
177 }
178
179 @Override
180 public boolean abort(final MasterProcedureEnv env) {
181
182 return false;
183 }
184
185 @Override
186 protected boolean acquireLock(final MasterProcedureEnv env) {
187 if (env.waitInitialized(this)) {
188 return false;
189 }
190 return env.getProcedureQueue().tryAcquireTableExclusiveLock(this, getTableName());
191 }
192
193 @Override
194 protected void releaseLock(final MasterProcedureEnv env) {
195 env.getProcedureQueue().releaseTableExclusiveLock(this, getTableName());
196 }
197
198 @Override
199 public void toStringClassDetails(StringBuilder sb) {
200 sb.append(getClass().getSimpleName());
201 sb.append(" (table=");
202 sb.append(getTableName());
203 sb.append(" preserveSplits=");
204 sb.append(preserveSplits);
205 sb.append(")");
206 }
207
208 @Override
209 public void serializeStateData(final OutputStream stream) throws IOException {
210 super.serializeStateData(stream);
211
212 MasterProcedureProtos.TruncateTableStateData.Builder state =
213 MasterProcedureProtos.TruncateTableStateData.newBuilder()
214 .setUserInfo(MasterProcedureUtil.toProtoUserInfo(this.user))
215 .setPreserveSplits(preserveSplits);
216 if (hTableDescriptor != null) {
217 state.setTableSchema(hTableDescriptor.convert());
218 } else {
219 state.setTableName(ProtobufUtil.toProtoTableName(tableName));
220 }
221 if (regions != null) {
222 for (HRegionInfo hri: regions) {
223 state.addRegionInfo(HRegionInfo.convert(hri));
224 }
225 }
226 state.build().writeDelimitedTo(stream);
227 }
228
229 @Override
230 public void deserializeStateData(final InputStream stream) throws IOException {
231 super.deserializeStateData(stream);
232
233 MasterProcedureProtos.TruncateTableStateData state =
234 MasterProcedureProtos.TruncateTableStateData.parseDelimitedFrom(stream);
235 user = MasterProcedureUtil.toUserInfo(state.getUserInfo());
236 if (state.hasTableSchema()) {
237 hTableDescriptor = HTableDescriptor.convert(state.getTableSchema());
238 tableName = hTableDescriptor.getTableName();
239 } else {
240 tableName = ProtobufUtil.toTableName(state.getTableName());
241 }
242 preserveSplits = state.getPreserveSplits();
243 if (state.getRegionInfoCount() == 0) {
244 regions = null;
245 } else {
246 regions = new ArrayList<HRegionInfo>(state.getRegionInfoCount());
247 for (HBaseProtos.RegionInfo hri: state.getRegionInfoList()) {
248 regions.add(HRegionInfo.convert(hri));
249 }
250 }
251 }
252
253 private static List<HRegionInfo> recreateRegionInfo(final List<HRegionInfo> regions) {
254 ArrayList<HRegionInfo> newRegions = new ArrayList<HRegionInfo>(regions.size());
255 for (HRegionInfo hri: regions) {
256 newRegions.add(new HRegionInfo(hri.getTable(), hri.getStartKey(), hri.getEndKey()));
257 }
258 return newRegions;
259 }
260
261 private boolean prepareTruncate(final MasterProcedureEnv env) throws IOException {
262 try {
263 env.getMasterServices().checkTableModifiable(getTableName());
264 } catch (TableNotFoundException|TableNotDisabledException e) {
265 setFailure("master-truncate-table", e);
266 return false;
267 }
268 return true;
269 }
270
271 private boolean preTruncate(final MasterProcedureEnv env)
272 throws IOException, InterruptedException {
273 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
274 if (cpHost != null) {
275 final TableName tableName = getTableName();
276 user.doAs(new PrivilegedExceptionAction<Void>() {
277 @Override
278 public Void run() throws Exception {
279 cpHost.preTruncateTableHandler(tableName);
280 return null;
281 }
282 });
283 }
284 return true;
285 }
286
287 private void postTruncate(final MasterProcedureEnv env)
288 throws IOException, InterruptedException {
289 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
290 if (cpHost != null) {
291 final TableName tableName = getTableName();
292 user.doAs(new PrivilegedExceptionAction<Void>() {
293 @Override
294 public Void run() throws Exception {
295 cpHost.postTruncateTableHandler(tableName);
296 return null;
297 }
298 });
299 }
300 }
301
302 @VisibleForTesting
303 HRegionInfo getFirstRegionInfo() {
304 if (regions == null || regions.isEmpty()) {
305 return null;
306 }
307 return regions.get(0);
308 }
309 }