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