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 java.io.InputStream;
22 import java.io.IOException;
23 import java.io.OutputStream;
24 import java.security.PrivilegedExceptionAction;
25 import java.util.ArrayList;
26 import java.util.List;
27
28 import org.apache.commons.logging.Log;
29 import org.apache.commons.logging.LogFactory;
30 import org.apache.hadoop.hbase.*;
31 import org.apache.hadoop.hbase.classification.InterfaceAudience;
32 import org.apache.hadoop.fs.FileSystem;
33 import org.apache.hadoop.fs.FileStatus;
34 import org.apache.hadoop.fs.Path;
35 import org.apache.hadoop.hbase.backup.HFileArchiver;
36 import org.apache.hadoop.hbase.client.ClusterConnection;
37 import org.apache.hadoop.hbase.client.Delete;
38 import org.apache.hadoop.hbase.client.Result;
39 import org.apache.hadoop.hbase.client.ResultScanner;
40 import org.apache.hadoop.hbase.client.Scan;
41 import org.apache.hadoop.hbase.client.Table;
42 import org.apache.hadoop.hbase.exceptions.HBaseException;
43 import org.apache.hadoop.hbase.mob.MobConstants;
44 import org.apache.hadoop.hbase.mob.MobUtils;
45 import org.apache.hadoop.hbase.regionserver.HRegion;
46 import org.apache.hadoop.hbase.master.AssignmentManager;
47 import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
48 import org.apache.hadoop.hbase.master.MasterFileSystem;
49 import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos;
50 import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos;
51 import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos.DeleteTableState;
52 import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
53 import org.apache.hadoop.hbase.procedure2.StateMachineProcedure;
54 import org.apache.hadoop.hbase.util.FSUtils;
55 import org.apache.hadoop.security.UserGroupInformation;
56
57 @InterfaceAudience.Private
58 public class DeleteTableProcedure
59 extends StateMachineProcedure<MasterProcedureEnv, DeleteTableState>
60 implements TableProcedureInterface {
61 private static final Log LOG = LogFactory.getLog(DeleteTableProcedure.class);
62
63 private List<HRegionInfo> regions;
64 private UserGroupInformation user;
65 private TableName tableName;
66
67
68 private final ProcedurePrepareLatch syncLatch;
69
70 public DeleteTableProcedure() {
71
72 syncLatch = null;
73 }
74
75 public DeleteTableProcedure(final MasterProcedureEnv env, final TableName tableName)
76 throws IOException {
77 this(env, tableName, null);
78 }
79
80 public DeleteTableProcedure(final MasterProcedureEnv env, final TableName tableName,
81 final ProcedurePrepareLatch syncLatch) throws IOException {
82 this.tableName = tableName;
83 this.user = env.getRequestUser().getUGI();
84 this.setOwner(this.user.getShortUserName());
85
86
87
88 this.syncLatch = syncLatch;
89 }
90
91 @Override
92 protected Flow executeFromState(final MasterProcedureEnv env, DeleteTableState state) {
93 if (LOG.isTraceEnabled()) {
94 LOG.trace(this + " execute state=" + state);
95 }
96 try {
97 switch (state) {
98 case DELETE_TABLE_PRE_OPERATION:
99
100 boolean deletable = prepareDelete(env);
101 ProcedurePrepareLatch.releaseLatch(syncLatch, this);
102 if (!deletable) {
103 assert isFailed() : "the delete should have an exception here";
104 return Flow.NO_MORE_STATE;
105 }
106
107
108 LOG.debug("waiting for '" + getTableName() + "' regions in transition");
109 regions = ProcedureSyncWait.getRegionsFromMeta(env, getTableName());
110 assert regions != null && !regions.isEmpty() : "unexpected 0 regions";
111 ProcedureSyncWait.waitRegionInTransition(env, regions);
112
113
114 preDelete(env);
115
116 setNextState(DeleteTableState.DELETE_TABLE_REMOVE_FROM_META);
117 break;
118 case DELETE_TABLE_REMOVE_FROM_META:
119 LOG.debug("delete '" + getTableName() + "' regions from META");
120 DeleteTableProcedure.deleteFromMeta(env, getTableName(), regions);
121 setNextState(DeleteTableState.DELETE_TABLE_CLEAR_FS_LAYOUT);
122 break;
123 case DELETE_TABLE_CLEAR_FS_LAYOUT:
124 LOG.debug("delete '" + getTableName() + "' from filesystem");
125 DeleteTableProcedure.deleteFromFs(env, getTableName(), regions, true);
126 setNextState(DeleteTableState.DELETE_TABLE_UPDATE_DESC_CACHE);
127 regions = null;
128 break;
129 case DELETE_TABLE_UPDATE_DESC_CACHE:
130 LOG.debug("delete '" + getTableName() + "' descriptor");
131 DeleteTableProcedure.deleteTableDescriptorCache(env, getTableName());
132 setNextState(DeleteTableState.DELETE_TABLE_UNASSIGN_REGIONS);
133 break;
134 case DELETE_TABLE_UNASSIGN_REGIONS:
135 LOG.debug("delete '" + getTableName() + "' assignment state");
136 DeleteTableProcedure.deleteAssignmentState(env, getTableName());
137 setNextState(DeleteTableState.DELETE_TABLE_POST_OPERATION);
138 break;
139 case DELETE_TABLE_POST_OPERATION:
140 postDelete(env);
141 LOG.debug("delete '" + getTableName() + "' completed");
142 return Flow.NO_MORE_STATE;
143 default:
144 throw new UnsupportedOperationException("unhandled state=" + state);
145 }
146 } catch (HBaseException|IOException e) {
147 LOG.warn("Retriable error trying to delete table=" + getTableName() + " state=" + state, e);
148 } catch (InterruptedException e) {
149
150 LOG.warn("Interrupted trying to delete table=" + getTableName() + " state=" + state, e);
151 }
152 return Flow.HAS_MORE_STATE;
153 }
154
155 @Override
156 protected void rollbackState(final MasterProcedureEnv env, final DeleteTableState state) {
157 if (state == DeleteTableState.DELETE_TABLE_PRE_OPERATION) {
158
159
160 ProcedurePrepareLatch.releaseLatch(syncLatch, this);
161 return;
162 }
163
164
165 throw new UnsupportedOperationException("unhandled state=" + state);
166 }
167
168 @Override
169 protected DeleteTableState getState(final int stateId) {
170 return DeleteTableState.valueOf(stateId);
171 }
172
173 @Override
174 protected int getStateId(final DeleteTableState state) {
175 return state.getNumber();
176 }
177
178 @Override
179 protected DeleteTableState getInitialState() {
180 return DeleteTableState.DELETE_TABLE_PRE_OPERATION;
181 }
182
183 @Override
184 public TableName getTableName() {
185 return tableName;
186 }
187
188 @Override
189 public TableOperationType getTableOperationType() {
190 return TableOperationType.DELETE;
191 }
192
193 @Override
194 public boolean abort(final MasterProcedureEnv env) {
195
196 return false;
197 }
198
199 @Override
200 protected boolean acquireLock(final MasterProcedureEnv env) {
201 if (!env.isInitialized()) return false;
202 return env.getProcedureQueue().tryAcquireTableWrite(getTableName(), "delete table");
203 }
204
205 @Override
206 protected void releaseLock(final MasterProcedureEnv env) {
207 env.getProcedureQueue().releaseTableWrite(getTableName());
208 }
209
210 @Override
211 public void toStringClassDetails(StringBuilder sb) {
212 sb.append(getClass().getSimpleName());
213 sb.append(" (table=");
214 sb.append(getTableName());
215 sb.append(")");
216 }
217
218 @Override
219 public void serializeStateData(final OutputStream stream) throws IOException {
220 super.serializeStateData(stream);
221
222 MasterProcedureProtos.DeleteTableStateData.Builder state =
223 MasterProcedureProtos.DeleteTableStateData.newBuilder()
224 .setUserInfo(MasterProcedureUtil.toProtoUserInfo(this.user))
225 .setTableName(ProtobufUtil.toProtoTableName(tableName));
226 if (regions != null) {
227 for (HRegionInfo hri: regions) {
228 state.addRegionInfo(HRegionInfo.convert(hri));
229 }
230 }
231 state.build().writeDelimitedTo(stream);
232 }
233
234 @Override
235 public void deserializeStateData(final InputStream stream) throws IOException {
236 super.deserializeStateData(stream);
237
238 MasterProcedureProtos.DeleteTableStateData state =
239 MasterProcedureProtos.DeleteTableStateData.parseDelimitedFrom(stream);
240 user = MasterProcedureUtil.toUserInfo(state.getUserInfo());
241 tableName = ProtobufUtil.toTableName(state.getTableName());
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 boolean prepareDelete(final MasterProcedureEnv env) throws IOException {
253 try {
254 env.getMasterServices().checkTableModifiable(tableName);
255 } catch (TableNotFoundException|TableNotDisabledException e) {
256 setFailure("master-delete-table", e);
257 return false;
258 }
259 return true;
260 }
261
262 private boolean preDelete(final MasterProcedureEnv env)
263 throws IOException, InterruptedException {
264 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
265 if (cpHost != null) {
266 final TableName tableName = this.tableName;
267 user.doAs(new PrivilegedExceptionAction<Void>() {
268 @Override
269 public Void run() throws Exception {
270 cpHost.preDeleteTableHandler(tableName);
271 return null;
272 }
273 });
274 }
275 return true;
276 }
277
278 private void postDelete(final MasterProcedureEnv env)
279 throws IOException, InterruptedException {
280 deleteTableStates(env, tableName);
281
282 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
283 if (cpHost != null) {
284 final TableName tableName = this.tableName;
285 user.doAs(new PrivilegedExceptionAction<Void>() {
286 @Override
287 public Void run() throws Exception {
288 cpHost.postDeleteTableHandler(tableName);
289 return null;
290 }
291 });
292 }
293 }
294
295 protected static void deleteFromFs(final MasterProcedureEnv env,
296 final TableName tableName, final List<HRegionInfo> regions,
297 final boolean archive) throws IOException {
298 final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem();
299 final FileSystem fs = mfs.getFileSystem();
300 final Path tempdir = mfs.getTempDir();
301
302 final Path tableDir = FSUtils.getTableDir(mfs.getRootDir(), tableName);
303 final Path tempTableDir = FSUtils.getTableDir(tempdir, tableName);
304
305 if (fs.exists(tableDir)) {
306
307 if (!fs.exists(tempdir) && !fs.mkdirs(tempdir)) {
308 throw new IOException("HBase temp directory '" + tempdir + "' creation failure.");
309 }
310
311
312 if (!fs.exists(tempTableDir.getParent()) && !fs.mkdirs(tempTableDir.getParent())) {
313 throw new IOException("HBase temp directory '" + tempdir + "' creation failure.");
314 }
315
316
317 if (!fs.rename(tableDir, tempTableDir)) {
318 if (fs.exists(tempTableDir)) {
319
320
321
322 FileStatus[] files = fs.listStatus(tempdir);
323 if (files != null && files.length > 0) {
324 for (int i = 0; i < files.length; ++i) {
325 if (!files[i].isDir()) continue;
326 HFileArchiver.archiveRegion(fs, mfs.getRootDir(), tempTableDir, files[i].getPath());
327 }
328 }
329 fs.delete(tempdir, true);
330 }
331 throw new IOException("Unable to move '" + tableDir + "' to temp '" + tempTableDir + "'");
332 }
333 }
334
335
336 if (archive) {
337 for (HRegionInfo hri : regions) {
338 LOG.debug("Archiving region " + hri.getRegionNameAsString() + " from FS");
339 HFileArchiver.archiveRegion(fs, mfs.getRootDir(),
340 tempTableDir, HRegion.getRegionDir(tempTableDir, hri.getEncodedName()));
341 }
342 LOG.debug("Table '" + tableName + "' archived!");
343 }
344
345
346 Path mobTableDir = FSUtils.getTableDir(new Path(mfs.getRootDir(), MobConstants.MOB_DIR_NAME),
347 tableName);
348 Path regionDir =
349 new Path(mobTableDir, MobUtils.getMobRegionInfo(tableName).getEncodedName());
350 if (fs.exists(regionDir)) {
351 HFileArchiver.archiveRegion(fs, mfs.getRootDir(), mobTableDir, regionDir);
352 }
353
354
355 if (!fs.delete(tempTableDir, true) && fs.exists(tempTableDir)) {
356 throw new IOException("Couldn't delete " + tempTableDir);
357 }
358
359
360 if (mobTableDir != null && fs.exists(mobTableDir)) {
361 if (!fs.delete(mobTableDir, true)) {
362 throw new IOException("Couldn't delete mob dir " + mobTableDir);
363 }
364 }
365 }
366
367
368
369
370
371
372
373 private static void cleanAnyRemainingRows(final MasterProcedureEnv env,
374 final TableName tableName) throws IOException {
375 ClusterConnection connection = env.getMasterServices().getConnection();
376 Scan tableScan = MetaTableAccessor.getScanForTableName(tableName);
377 try (Table metaTable =
378 connection.getTable(TableName.META_TABLE_NAME)) {
379 List<Delete> deletes = new ArrayList<Delete>();
380 try (ResultScanner resScanner = metaTable.getScanner(tableScan)) {
381 for (Result result : resScanner) {
382 deletes.add(new Delete(result.getRow()));
383 }
384 }
385 if (!deletes.isEmpty()) {
386 LOG.warn("Deleting some vestigal " + deletes.size() + " rows of " + tableName +
387 " from " + TableName.META_TABLE_NAME);
388 metaTable.delete(deletes);
389 }
390 }
391 }
392
393 protected static void deleteFromMeta(final MasterProcedureEnv env,
394 final TableName tableName, List<HRegionInfo> regions) throws IOException {
395 MetaTableAccessor.deleteRegions(env.getMasterServices().getConnection(), regions);
396
397
398 cleanAnyRemainingRows(env, tableName);
399
400
401 env.getMasterServices().getServerManager().removeRegions(regions);
402 }
403
404 protected static void deleteAssignmentState(final MasterProcedureEnv env,
405 final TableName tableName) throws HBaseException, IOException {
406 AssignmentManager am = env.getMasterServices().getAssignmentManager();
407
408
409 LOG.debug("Removing '" + tableName + "' from region states.");
410 am.getRegionStates().tableDeleted(tableName);
411
412
413 LOG.debug("Marking '" + tableName + "' as deleted.");
414 am.getTableStateManager().setDeletedTable(tableName);
415 }
416
417 protected static void deleteTableDescriptorCache(final MasterProcedureEnv env,
418 final TableName tableName) throws IOException {
419 LOG.debug("Removing '" + tableName + "' descriptor.");
420 env.getMasterServices().getTableDescriptors().remove(tableName);
421 }
422
423 protected static void deleteTableStates(final MasterProcedureEnv env, final TableName tableName)
424 throws IOException {
425 if (!tableName.isSystemTable()) {
426 ProcedureSyncWait.getMasterQuotaManager(env).removeTableFromNamespaceQuota(tableName);
427 }
428 }
429 }