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