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