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.util.ArrayList;
25 import java.util.List;
26
27 import org.apache.commons.logging.Log;
28 import org.apache.commons.logging.LogFactory;
29 import org.apache.hadoop.hbase.classification.InterfaceAudience;
30 import org.apache.hadoop.fs.FileSystem;
31 import org.apache.hadoop.fs.FileStatus;
32 import org.apache.hadoop.fs.Path;
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.HRegionInfo;
37 import org.apache.hadoop.hbase.backup.HFileArchiver;
38 import org.apache.hadoop.hbase.MetaTableAccessor;
39 import org.apache.hadoop.hbase.client.ClusterConnection;
40 import org.apache.hadoop.hbase.client.Delete;
41 import org.apache.hadoop.hbase.client.Result;
42 import org.apache.hadoop.hbase.client.ResultScanner;
43 import org.apache.hadoop.hbase.client.Scan;
44 import org.apache.hadoop.hbase.client.Table;
45 import org.apache.hadoop.hbase.exceptions.HBaseException;
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.security.User;
55 import org.apache.hadoop.hbase.util.FSUtils;
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 User 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 this(env, tableName, null);
77 }
78
79 public DeleteTableProcedure(final MasterProcedureEnv env, final TableName tableName,
80 final ProcedurePrepareLatch syncLatch) {
81 this.tableName = tableName;
82 this.user = env.getRequestUser();
83 this.setOwner(this.user.getShortName());
84
85
86
87 this.syncLatch = syncLatch;
88 }
89
90 @Override
91 protected Flow executeFromState(final MasterProcedureEnv env, DeleteTableState state)
92 throws InterruptedException {
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 }
149 return Flow.HAS_MORE_STATE;
150 }
151
152 @Override
153 protected void rollbackState(final MasterProcedureEnv env, final DeleteTableState state) {
154 if (state == DeleteTableState.DELETE_TABLE_PRE_OPERATION) {
155
156
157 ProcedurePrepareLatch.releaseLatch(syncLatch, this);
158 return;
159 }
160
161
162 throw new UnsupportedOperationException("unhandled state=" + state);
163 }
164
165 @Override
166 protected DeleteTableState getState(final int stateId) {
167 return DeleteTableState.valueOf(stateId);
168 }
169
170 @Override
171 protected int getStateId(final DeleteTableState state) {
172 return state.getNumber();
173 }
174
175 @Override
176 protected DeleteTableState getInitialState() {
177 return DeleteTableState.DELETE_TABLE_PRE_OPERATION;
178 }
179
180 @Override
181 public TableName getTableName() {
182 return tableName;
183 }
184
185 @Override
186 public TableOperationType getTableOperationType() {
187 return TableOperationType.DELETE;
188 }
189
190 @Override
191 public boolean abort(final MasterProcedureEnv env) {
192
193 return false;
194 }
195
196 @Override
197 protected boolean acquireLock(final MasterProcedureEnv env) {
198 if (env.waitInitialized(this)) return false;
199 return env.getProcedureQueue().tryAcquireTableExclusiveLock(this, getTableName());
200 }
201
202 @Override
203 protected void releaseLock(final MasterProcedureEnv env) {
204 env.getProcedureQueue().releaseTableExclusiveLock(this, getTableName());
205 }
206
207 @Override
208 public void toStringClassDetails(StringBuilder sb) {
209 sb.append(getClass().getSimpleName());
210 sb.append(" (table=");
211 sb.append(getTableName());
212 sb.append(")");
213 }
214
215 @Override
216 public void serializeStateData(final OutputStream stream) throws IOException {
217 super.serializeStateData(stream);
218
219 MasterProcedureProtos.DeleteTableStateData.Builder state =
220 MasterProcedureProtos.DeleteTableStateData.newBuilder()
221 .setUserInfo(MasterProcedureUtil.toProtoUserInfo(this.user))
222 .setTableName(ProtobufUtil.toProtoTableName(tableName));
223 if (regions != null) {
224 for (HRegionInfo hri: regions) {
225 state.addRegionInfo(HRegionInfo.convert(hri));
226 }
227 }
228 state.build().writeDelimitedTo(stream);
229 }
230
231 @Override
232 public void deserializeStateData(final InputStream stream) throws IOException {
233 super.deserializeStateData(stream);
234
235 MasterProcedureProtos.DeleteTableStateData state =
236 MasterProcedureProtos.DeleteTableStateData.parseDelimitedFrom(stream);
237 user = MasterProcedureUtil.toUserInfo(state.getUserInfo());
238 tableName = ProtobufUtil.toTableName(state.getTableName());
239 if (state.getRegionInfoCount() == 0) {
240 regions = null;
241 } else {
242 regions = new ArrayList<HRegionInfo>(state.getRegionInfoCount());
243 for (HBaseProtos.RegionInfo hri: state.getRegionInfoList()) {
244 regions.add(HRegionInfo.convert(hri));
245 }
246 }
247 }
248
249 private boolean prepareDelete(final MasterProcedureEnv env) throws IOException {
250 try {
251 env.getMasterServices().checkTableModifiable(tableName);
252 } catch (TableNotFoundException|TableNotDisabledException e) {
253 setFailure("master-delete-table", e);
254 return false;
255 }
256 return true;
257 }
258
259 private boolean preDelete(final MasterProcedureEnv env)
260 throws IOException, InterruptedException {
261 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
262 if (cpHost != null) {
263 final TableName tableName = this.tableName;
264 cpHost.preDeleteTableHandler(tableName, user);
265 }
266 return true;
267 }
268
269 private void postDelete(final MasterProcedureEnv env)
270 throws IOException, InterruptedException {
271 deleteTableStates(env, tableName);
272
273 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
274 if (cpHost != null) {
275 final TableName tableName = this.tableName;
276 cpHost.postDeleteTableHandler(tableName, user);
277 }
278 }
279
280 protected static void deleteFromFs(final MasterProcedureEnv env,
281 final TableName tableName, final List<HRegionInfo> regions,
282 final boolean archive) throws IOException {
283 final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem();
284 final FileSystem fs = mfs.getFileSystem();
285 final Path tempdir = mfs.getTempDir();
286
287 final Path tableDir = FSUtils.getTableDir(mfs.getRootDir(), tableName);
288 final Path tempTableDir = FSUtils.getTableDir(tempdir, tableName);
289
290 if (fs.exists(tableDir)) {
291
292 if (!fs.exists(tempdir) && !fs.mkdirs(tempdir)) {
293 throw new IOException("HBase temp directory '" + tempdir + "' creation failure.");
294 }
295
296
297 if (!fs.exists(tempTableDir.getParent()) && !fs.mkdirs(tempTableDir.getParent())) {
298 throw new IOException("HBase temp directory '" + tempdir + "' creation failure.");
299 }
300
301
302 if (!fs.rename(tableDir, tempTableDir)) {
303 if (fs.exists(tempTableDir)) {
304
305
306
307 FileStatus[] files = fs.listStatus(tempdir);
308 if (files != null && files.length > 0) {
309 for (int i = 0; i < files.length; ++i) {
310 if (!files[i].isDir()) continue;
311 HFileArchiver.archiveRegion(fs, mfs.getRootDir(), tempTableDir, files[i].getPath());
312 }
313 }
314 fs.delete(tempdir, true);
315 }
316 throw new IOException("Unable to move '" + tableDir + "' to temp '" + tempTableDir + "'");
317 }
318 }
319
320
321 if (archive) {
322 for (HRegionInfo hri : regions) {
323 LOG.debug("Archiving region " + hri.getRegionNameAsString() + " from FS");
324 HFileArchiver.archiveRegion(fs, mfs.getRootDir(),
325 tempTableDir, new Path(tempTableDir, hri.getEncodedName()));
326 }
327 LOG.debug("Table '" + tableName + "' archived!");
328 }
329
330
331 if (!fs.delete(tempTableDir, true) && fs.exists(tempTableDir)) {
332 throw new IOException("Couldn't delete " + tempTableDir);
333 }
334 }
335
336
337
338
339
340
341
342 private static void cleanAnyRemainingRows(final MasterProcedureEnv env,
343 final TableName tableName) throws IOException {
344 ClusterConnection connection = env.getMasterServices().getConnection();
345 Scan tableScan = MetaTableAccessor.getScanForTableName(tableName);
346 try (Table metaTable =
347 connection.getTable(TableName.META_TABLE_NAME)) {
348 List<Delete> deletes = new ArrayList<Delete>();
349 try (ResultScanner resScanner = metaTable.getScanner(tableScan)) {
350 for (Result result : resScanner) {
351 deletes.add(new Delete(result.getRow()));
352 }
353 }
354 if (!deletes.isEmpty()) {
355 LOG.warn("Deleting some vestigial " + deletes.size() + " rows of " + tableName +
356 " from " + TableName.META_TABLE_NAME);
357 metaTable.delete(deletes);
358 }
359 }
360 }
361
362 protected static void deleteFromMeta(final MasterProcedureEnv env,
363 final TableName tableName, List<HRegionInfo> regions) throws IOException {
364 MetaTableAccessor.deleteRegions(env.getMasterServices().getConnection(), regions);
365
366
367 cleanAnyRemainingRows(env, tableName);
368
369
370 env.getMasterServices().getServerManager().removeRegions(regions);
371 }
372
373 protected static void deleteAssignmentState(final MasterProcedureEnv env,
374 final TableName tableName) throws HBaseException, IOException {
375 AssignmentManager am = env.getMasterServices().getAssignmentManager();
376
377
378 LOG.debug("Removing '" + tableName + "' from region states.");
379 am.getRegionStates().tableDeleted(tableName);
380
381
382 LOG.debug("Marking '" + tableName + "' as deleted.");
383 am.getTableStateManager().setDeletedTable(tableName);
384 }
385
386 protected static void deleteTableDescriptorCache(final MasterProcedureEnv env,
387 final TableName tableName) throws IOException {
388 LOG.debug("Removing '" + tableName + "' descriptor.");
389 env.getMasterServices().getTableDescriptors().remove(tableName);
390 }
391
392 protected static void deleteTableStates(final MasterProcedureEnv env, final TableName tableName)
393 throws IOException {
394 if (!tableName.isSystemTable()) {
395 ProcedureSyncWait.getMasterQuotaManager(env).removeTableFromNamespaceQuota(tableName);
396 }
397 }
398 }