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 regions = Arrays.asList(ModifyRegionUtils.createHRegionInfos(hTableDescriptor, null));
108 } else {
109 regions = recreateRegionInfo(regions);
110 }
111 break;
112 case TRUNCATE_TABLE_CREATE_FS_LAYOUT:
113 DeleteTableProcedure.deleteFromFs(env, getTableName(), regions, true);
114 regions = CreateTableProcedure.createFsLayout(env, hTableDescriptor, regions);
115 CreateTableProcedure.updateTableDescCache(env, getTableName());
116 setNextState(TruncateTableState.TRUNCATE_TABLE_ADD_TO_META);
117 break;
118 case TRUNCATE_TABLE_ADD_TO_META:
119 regions = CreateTableProcedure.addTableToMeta(env, hTableDescriptor, regions);
120 setNextState(TruncateTableState.TRUNCATE_TABLE_ASSIGN_REGIONS);
121 break;
122 case TRUNCATE_TABLE_ASSIGN_REGIONS:
123 CreateTableProcedure.assignRegions(env, getTableName(), regions);
124 setNextState(TruncateTableState.TRUNCATE_TABLE_POST_OPERATION);
125 hTableDescriptor = null;
126 regions = null;
127 break;
128 case TRUNCATE_TABLE_POST_OPERATION:
129 postTruncate(env);
130 LOG.debug("truncate '" + getTableName() + "' completed");
131 return Flow.NO_MORE_STATE;
132 default:
133 throw new UnsupportedOperationException("unhandled state=" + state);
134 }
135 } catch (HBaseException|IOException e) {
136 LOG.warn("Retriable error trying to truncate table=" + getTableName() + " state=" + state, e);
137 }
138 return Flow.HAS_MORE_STATE;
139 }
140
141 @Override
142 protected void rollbackState(final MasterProcedureEnv env, final TruncateTableState state) {
143 if (state == TruncateTableState.TRUNCATE_TABLE_PRE_OPERATION) {
144
145
146 return;
147 }
148
149
150 throw new UnsupportedOperationException("unhandled state=" + state);
151 }
152
153 @Override
154 protected TruncateTableState getState(final int stateId) {
155 return TruncateTableState.valueOf(stateId);
156 }
157
158 @Override
159 protected int getStateId(final TruncateTableState state) {
160 return state.getNumber();
161 }
162
163 @Override
164 protected TruncateTableState getInitialState() {
165 return TruncateTableState.TRUNCATE_TABLE_PRE_OPERATION;
166 }
167
168 @Override
169 public TableName getTableName() {
170 return tableName;
171 }
172
173 @Override
174 public TableOperationType getTableOperationType() {
175 return TableOperationType.EDIT;
176 }
177
178 @Override
179 public boolean abort(final MasterProcedureEnv env) {
180
181 return false;
182 }
183
184 @Override
185 protected boolean acquireLock(final MasterProcedureEnv env) {
186 if (env.waitInitialized(this)) {
187 return false;
188 }
189 return env.getProcedureQueue().tryAcquireTableExclusiveLock(this, getTableName());
190 }
191
192 @Override
193 protected void releaseLock(final MasterProcedureEnv env) {
194 env.getProcedureQueue().releaseTableExclusiveLock(this, getTableName());
195 }
196
197 @Override
198 public void toStringClassDetails(StringBuilder sb) {
199 sb.append(getClass().getSimpleName());
200 sb.append(" (table=");
201 sb.append(getTableName());
202 sb.append(" preserveSplits=");
203 sb.append(preserveSplits);
204 sb.append(")");
205 }
206
207 @Override
208 public void serializeStateData(final OutputStream stream) throws IOException {
209 super.serializeStateData(stream);
210
211 MasterProcedureProtos.TruncateTableStateData.Builder state =
212 MasterProcedureProtos.TruncateTableStateData.newBuilder()
213 .setUserInfo(MasterProcedureUtil.toProtoUserInfo(this.user))
214 .setPreserveSplits(preserveSplits);
215 if (hTableDescriptor != null) {
216 state.setTableSchema(hTableDescriptor.convert());
217 } else {
218 state.setTableName(ProtobufUtil.toProtoTableName(tableName));
219 }
220 if (regions != null) {
221 for (HRegionInfo hri: regions) {
222 state.addRegionInfo(HRegionInfo.convert(hri));
223 }
224 }
225 state.build().writeDelimitedTo(stream);
226 }
227
228 @Override
229 public void deserializeStateData(final InputStream stream) throws IOException {
230 super.deserializeStateData(stream);
231
232 MasterProcedureProtos.TruncateTableStateData state =
233 MasterProcedureProtos.TruncateTableStateData.parseDelimitedFrom(stream);
234 user = MasterProcedureUtil.toUserInfo(state.getUserInfo());
235 if (state.hasTableSchema()) {
236 hTableDescriptor = HTableDescriptor.convert(state.getTableSchema());
237 tableName = hTableDescriptor.getTableName();
238 } else {
239 tableName = ProtobufUtil.toTableName(state.getTableName());
240 }
241 preserveSplits = state.getPreserveSplits();
242 if (state.getRegionInfoCount() == 0) {
243 regions = null;
244 } else {
245 regions = new ArrayList<HRegionInfo>(state.getRegionInfoCount());
246 for (HBaseProtos.RegionInfo hri: state.getRegionInfoList()) {
247 regions.add(HRegionInfo.convert(hri));
248 }
249 }
250 }
251
252 private static List<HRegionInfo> recreateRegionInfo(final List<HRegionInfo> regions) {
253 ArrayList<HRegionInfo> newRegions = new ArrayList<HRegionInfo>(regions.size());
254 for (HRegionInfo hri: regions) {
255 newRegions.add(new HRegionInfo(hri.getTable(), hri.getStartKey(), hri.getEndKey()));
256 }
257 return newRegions;
258 }
259
260 private boolean prepareTruncate(final MasterProcedureEnv env) throws IOException {
261 try {
262 env.getMasterServices().checkTableModifiable(getTableName());
263 } catch (TableNotFoundException|TableNotDisabledException e) {
264 setFailure("master-truncate-table", e);
265 return false;
266 }
267 return true;
268 }
269
270 private boolean preTruncate(final MasterProcedureEnv env)
271 throws IOException, InterruptedException {
272 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
273 if (cpHost != null) {
274 final TableName tableName = getTableName();
275 user.doAs(new PrivilegedExceptionAction<Void>() {
276 @Override
277 public Void run() throws Exception {
278 cpHost.preTruncateTableHandler(tableName);
279 return null;
280 }
281 });
282 }
283 return true;
284 }
285
286 private void postTruncate(final MasterProcedureEnv env)
287 throws IOException, InterruptedException {
288 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
289 if (cpHost != null) {
290 final TableName tableName = getTableName();
291 user.doAs(new PrivilegedExceptionAction<Void>() {
292 @Override
293 public Void run() throws Exception {
294 cpHost.postTruncateTableHandler(tableName);
295 return null;
296 }
297 });
298 }
299 }
300
301 @VisibleForTesting
302 HRegionInfo getFirstRegionInfo() {
303 if (regions == null || regions.isEmpty()) {
304 return null;
305 }
306 return regions.get(0);
307 }
308 }