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.IOException;
22 import java.io.InputStream;
23 import java.io.OutputStream;
24 import java.util.List;
25 import java.util.concurrent.ExecutorService;
26 import java.util.concurrent.atomic.AtomicBoolean;
27
28 import org.apache.commons.logging.Log;
29 import org.apache.commons.logging.LogFactory;
30 import org.apache.hadoop.hbase.HRegionInfo;
31 import org.apache.hadoop.hbase.MetaTableAccessor;
32 import org.apache.hadoop.hbase.TableName;
33 import org.apache.hadoop.hbase.TableNotEnabledException;
34 import org.apache.hadoop.hbase.TableNotFoundException;
35 import org.apache.hadoop.hbase.TableStateManager;
36 import org.apache.hadoop.hbase.classification.InterfaceAudience;
37 import org.apache.hadoop.hbase.constraint.ConstraintException;
38 import org.apache.hadoop.hbase.exceptions.HBaseException;
39 import org.apache.hadoop.hbase.master.AssignmentManager;
40 import org.apache.hadoop.hbase.master.BulkAssigner;
41 import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
42 import org.apache.hadoop.hbase.master.RegionState;
43 import org.apache.hadoop.hbase.master.RegionStates;
44 import org.apache.hadoop.hbase.procedure2.StateMachineProcedure;
45 import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
46 import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos;
47 import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos.DisableTableState;
48 import org.apache.hadoop.hbase.protobuf.generated.ZooKeeperProtos;
49 import org.apache.hadoop.hbase.security.User;
50 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
51 import org.apache.htrace.Trace;
52
53 @InterfaceAudience.Private
54 public class DisableTableProcedure
55 extends StateMachineProcedure<MasterProcedureEnv, DisableTableState>
56 implements TableProcedureInterface {
57 private static final Log LOG = LogFactory.getLog(DisableTableProcedure.class);
58
59 private final AtomicBoolean aborted = new AtomicBoolean(false);
60
61
62 private final ProcedurePrepareLatch syncLatch;
63
64 private TableName tableName;
65 private boolean skipTableStateCheck;
66 private User user;
67
68 private Boolean traceEnabled = null;
69
70 enum MarkRegionOfflineOpResult {
71 MARK_ALL_REGIONS_OFFLINE_SUCCESSFUL,
72 BULK_ASSIGN_REGIONS_FAILED,
73 MARK_ALL_REGIONS_OFFLINE_INTERRUPTED,
74 }
75
76 public DisableTableProcedure() {
77 syncLatch = null;
78 }
79
80
81
82
83
84
85
86 public DisableTableProcedure(final MasterProcedureEnv env, final TableName tableName,
87 final boolean skipTableStateCheck) {
88 this(env, tableName, skipTableStateCheck, null);
89 }
90
91
92
93
94
95
96
97 public DisableTableProcedure(final MasterProcedureEnv env, final TableName tableName,
98 final boolean skipTableStateCheck, final ProcedurePrepareLatch syncLatch) {
99 this.tableName = tableName;
100 this.skipTableStateCheck = skipTableStateCheck;
101 this.user = env.getRequestUser();
102 this.setOwner(this.user.getShortName());
103
104
105
106
107
108
109
110
111
112 this.syncLatch = syncLatch;
113 }
114
115 @Override
116 protected Flow executeFromState(final MasterProcedureEnv env, final DisableTableState state)
117 throws InterruptedException {
118 if (isTraceEnabled()) {
119 LOG.trace(this + " execute state=" + state);
120 }
121
122 try {
123 switch (state) {
124 case DISABLE_TABLE_PREPARE:
125 if (prepareDisable(env)) {
126 setNextState(DisableTableState.DISABLE_TABLE_PRE_OPERATION);
127 } else {
128 assert isFailed() : "disable should have an exception here";
129 return Flow.NO_MORE_STATE;
130 }
131 break;
132 case DISABLE_TABLE_PRE_OPERATION:
133 preDisable(env, state);
134 setNextState(DisableTableState.DISABLE_TABLE_SET_DISABLING_TABLE_STATE);
135 break;
136 case DISABLE_TABLE_SET_DISABLING_TABLE_STATE:
137 setTableStateToDisabling(env, tableName);
138 setNextState(DisableTableState.DISABLE_TABLE_MARK_REGIONS_OFFLINE);
139 break;
140 case DISABLE_TABLE_MARK_REGIONS_OFFLINE:
141 if (markRegionsOffline(env, tableName, true) ==
142 MarkRegionOfflineOpResult.MARK_ALL_REGIONS_OFFLINE_SUCCESSFUL) {
143 setNextState(DisableTableState.DISABLE_TABLE_SET_DISABLED_TABLE_STATE);
144 } else {
145 LOG.trace("Retrying later to disable the missing regions");
146 }
147 break;
148 case DISABLE_TABLE_SET_DISABLED_TABLE_STATE:
149 setTableStateToDisabled(env, tableName);
150 setNextState(DisableTableState.DISABLE_TABLE_POST_OPERATION);
151 break;
152 case DISABLE_TABLE_POST_OPERATION:
153 postDisable(env, state);
154 return Flow.NO_MORE_STATE;
155 default:
156 throw new UnsupportedOperationException("unhandled state=" + state);
157 }
158 } catch (HBaseException|IOException e) {
159 LOG.warn("Retriable error trying to disable table=" + tableName + " state=" + state, e);
160 }
161 return Flow.HAS_MORE_STATE;
162 }
163
164 @Override
165 protected void rollbackState(final MasterProcedureEnv env, final DisableTableState state)
166 throws IOException {
167 if (state == DisableTableState.DISABLE_TABLE_PREPARE) {
168 undoTableStateChange(env);
169 ProcedurePrepareLatch.releaseLatch(syncLatch, this);
170 return;
171 }
172
173
174 throw new UnsupportedOperationException("unhandled state=" + state);
175 }
176
177 @Override
178 protected DisableTableState getState(final int stateId) {
179 return DisableTableState.valueOf(stateId);
180 }
181
182 @Override
183 protected int getStateId(final DisableTableState state) {
184 return state.getNumber();
185 }
186
187 @Override
188 protected DisableTableState getInitialState() {
189 return DisableTableState.DISABLE_TABLE_PREPARE;
190 }
191
192 @Override
193 protected void setNextState(final DisableTableState state) {
194 if (aborted.get()) {
195 setAbortFailure("disable-table", "abort requested");
196 } else {
197 super.setNextState(state);
198 }
199 }
200
201 @Override
202 public boolean abort(final MasterProcedureEnv env) {
203 aborted.set(true);
204 return true;
205 }
206
207 @Override
208 protected boolean acquireLock(final MasterProcedureEnv env) {
209 if (env.waitInitialized(this)) return false;
210 return env.getProcedureQueue().tryAcquireTableExclusiveLock(this, tableName);
211 }
212
213 @Override
214 protected void releaseLock(final MasterProcedureEnv env) {
215 env.getProcedureQueue().releaseTableExclusiveLock(this, tableName);
216 }
217
218 @Override
219 public void serializeStateData(final OutputStream stream) throws IOException {
220 super.serializeStateData(stream);
221
222 MasterProcedureProtos.DisableTableStateData.Builder disableTableMsg =
223 MasterProcedureProtos.DisableTableStateData.newBuilder()
224 .setUserInfo(MasterProcedureUtil.toProtoUserInfo(user))
225 .setTableName(ProtobufUtil.toProtoTableName(tableName))
226 .setSkipTableStateCheck(skipTableStateCheck);
227
228 disableTableMsg.build().writeDelimitedTo(stream);
229 }
230
231 @Override
232 public void deserializeStateData(final InputStream stream) throws IOException {
233 super.deserializeStateData(stream);
234
235 MasterProcedureProtos.DisableTableStateData disableTableMsg =
236 MasterProcedureProtos.DisableTableStateData.parseDelimitedFrom(stream);
237 user = MasterProcedureUtil.toUserInfo(disableTableMsg.getUserInfo());
238 tableName = ProtobufUtil.toTableName(disableTableMsg.getTableName());
239 skipTableStateCheck = disableTableMsg.getSkipTableStateCheck();
240 }
241
242 @Override
243 public void toStringClassDetails(StringBuilder sb) {
244 sb.append(getClass().getSimpleName());
245 sb.append(" (table=");
246 sb.append(tableName);
247 sb.append(")");
248 }
249
250 @Override
251 public TableName getTableName() {
252 return tableName;
253 }
254
255 @Override
256 public TableOperationType getTableOperationType() {
257 return TableOperationType.DISABLE;
258 }
259
260
261
262
263
264
265
266
267 private boolean prepareDisable(final MasterProcedureEnv env) throws HBaseException, IOException {
268 boolean canTableBeDisabled = true;
269 if (tableName.equals(TableName.META_TABLE_NAME)) {
270 setFailure("master-disable-table", new ConstraintException("Cannot disable catalog table"));
271 canTableBeDisabled = false;
272 } else if (!MetaTableAccessor.tableExists(env.getMasterServices().getConnection(), tableName)) {
273 setFailure("master-disable-table", new TableNotFoundException(tableName));
274 canTableBeDisabled = false;
275 } else if (!skipTableStateCheck) {
276
277
278
279
280
281
282
283
284
285
286
287 TableStateManager tsm =
288 env.getMasterServices().getAssignmentManager().getTableStateManager();
289 if (!tsm.setTableStateIfInStates(tableName, ZooKeeperProtos.Table.State.DISABLING,
290 ZooKeeperProtos.Table.State.DISABLING, ZooKeeperProtos.Table.State.ENABLED)) {
291 LOG.info("Table " + tableName + " isn't enabled; skipping disable");
292 setFailure("master-disable-table", new TableNotEnabledException(tableName));
293 canTableBeDisabled = false;
294 }
295 }
296
297
298 ProcedurePrepareLatch.releaseLatch(syncLatch, this);
299
300 return canTableBeDisabled;
301 }
302
303
304
305
306
307 @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="REC_CATCH_EXCEPTION",
308 justification="Intended")
309 private void undoTableStateChange(final MasterProcedureEnv env) {
310 if (!skipTableStateCheck) {
311 try {
312
313 if (env.getMasterServices().getAssignmentManager().getTableStateManager().isTableState(
314 tableName, ZooKeeperProtos.Table.State.DISABLING)) {
315 EnableTableProcedure.setTableStateToEnabled(env, tableName);
316 }
317 } catch (Exception e) {
318
319 LOG.trace(e.getMessage());
320 }
321 }
322 }
323
324
325
326
327
328
329
330
331 protected void preDisable(final MasterProcedureEnv env, final DisableTableState state)
332 throws IOException, InterruptedException {
333 runCoprocessorAction(env, state);
334 }
335
336
337
338
339
340
341 protected static void setTableStateToDisabling(
342 final MasterProcedureEnv env,
343 final TableName tableName) throws HBaseException, IOException {
344
345 env.getMasterServices().getAssignmentManager().getTableStateManager().setTableState(
346 tableName,
347 ZooKeeperProtos.Table.State.DISABLING);
348 }
349
350
351
352
353
354
355
356
357
358 protected static MarkRegionOfflineOpResult markRegionsOffline(
359 final MasterProcedureEnv env,
360 final TableName tableName,
361 final Boolean retryRequired) throws IOException {
362
363 int maxTry = (retryRequired ? 10 : 1);
364 MarkRegionOfflineOpResult operationResult =
365 MarkRegionOfflineOpResult.BULK_ASSIGN_REGIONS_FAILED;
366 do {
367 try {
368 operationResult = markRegionsOffline(env, tableName);
369 if (operationResult == MarkRegionOfflineOpResult.MARK_ALL_REGIONS_OFFLINE_SUCCESSFUL) {
370 break;
371 }
372 maxTry--;
373 } catch (Exception e) {
374 LOG.warn("Received exception while marking regions online. tries left: " + maxTry, e);
375 maxTry--;
376 if (maxTry > 0) {
377 continue;
378 }
379 throw e;
380 }
381 } while (maxTry > 0);
382
383 if (operationResult != MarkRegionOfflineOpResult.MARK_ALL_REGIONS_OFFLINE_SUCCESSFUL) {
384 LOG.warn("Some or all regions of the Table '" + tableName + "' were still online");
385 }
386
387 return operationResult;
388 }
389
390
391
392
393
394
395
396
397 private static MarkRegionOfflineOpResult markRegionsOffline(
398 final MasterProcedureEnv env,
399 final TableName tableName) throws IOException {
400
401
402
403
404 MarkRegionOfflineOpResult operationResult =
405 MarkRegionOfflineOpResult.MARK_ALL_REGIONS_OFFLINE_SUCCESSFUL;
406 final List<HRegionInfo> regions =
407 env.getMasterServices().getAssignmentManager().getRegionStates()
408 .getRegionsOfTable(tableName);
409 if (regions.size() > 0) {
410 LOG.info("Offlining " + regions.size() + " regions.");
411
412 BulkDisabler bd = new BulkDisabler(env, tableName, regions);
413 try {
414 if (!bd.bulkAssign()) {
415 operationResult = MarkRegionOfflineOpResult.BULK_ASSIGN_REGIONS_FAILED;
416 }
417 } catch (InterruptedException e) {
418 LOG.warn("Disable was interrupted");
419
420 Thread.currentThread().interrupt();
421 operationResult = MarkRegionOfflineOpResult.MARK_ALL_REGIONS_OFFLINE_INTERRUPTED;
422 }
423 }
424 return operationResult;
425 }
426
427
428
429
430
431
432 protected static void setTableStateToDisabled(
433 final MasterProcedureEnv env,
434 final TableName tableName) throws HBaseException, IOException {
435
436 env.getMasterServices().getAssignmentManager().getTableStateManager().setTableState(
437 tableName,
438 ZooKeeperProtos.Table.State.DISABLED);
439 LOG.info("Disabled table, " + tableName + ", is completed.");
440 }
441
442
443
444
445
446
447
448
449 protected void postDisable(final MasterProcedureEnv env, final DisableTableState state)
450 throws IOException, InterruptedException {
451 runCoprocessorAction(env, state);
452 }
453
454
455
456
457
458
459 private Boolean isTraceEnabled() {
460 if (traceEnabled == null) {
461 traceEnabled = LOG.isTraceEnabled();
462 }
463 return traceEnabled;
464 }
465
466
467
468
469
470
471
472
473 private void runCoprocessorAction(final MasterProcedureEnv env, final DisableTableState state)
474 throws IOException, InterruptedException {
475 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
476 if (cpHost != null) {
477 switch (state) {
478 case DISABLE_TABLE_PRE_OPERATION:
479 cpHost.preDisableTableHandler(tableName, user);
480 break;
481 case DISABLE_TABLE_POST_OPERATION:
482 cpHost.postDisableTableHandler(tableName, user);
483 break;
484 default:
485 throw new UnsupportedOperationException(this + " unhandled state=" + state);
486 }
487 }
488 }
489
490
491
492
493 private static class BulkDisabler extends BulkAssigner {
494 private final AssignmentManager assignmentManager;
495 private final List<HRegionInfo> regions;
496 private final TableName tableName;
497 private final int waitingTimeForEvents;
498
499 public BulkDisabler(final MasterProcedureEnv env, final TableName tableName,
500 final List<HRegionInfo> regions) {
501 super(env.getMasterServices());
502 this.assignmentManager = env.getMasterServices().getAssignmentManager();
503 this.tableName = tableName;
504 this.regions = regions;
505 this.waitingTimeForEvents =
506 env.getMasterServices().getConfiguration()
507 .getInt("hbase.master.event.waiting.time", 1000);
508 }
509
510 @Override
511 protected void populatePool(ExecutorService pool) {
512 RegionStates regionStates = assignmentManager.getRegionStates();
513 for (final HRegionInfo region : regions) {
514 if (regionStates.isRegionInTransition(region)
515 && !regionStates.isRegionInState(region, RegionState.State.FAILED_CLOSE)) {
516 continue;
517 }
518 pool.execute(Trace.wrap("DisableTableHandler.BulkDisabler", new Runnable() {
519 @Override
520 public void run() {
521 assignmentManager.unassign(region);
522 }
523 }));
524 }
525 }
526
527 @Override
528 protected boolean waitUntilDone(long timeout) throws InterruptedException {
529 long startTime = EnvironmentEdgeManager.currentTime();
530 long remaining = timeout;
531 List<HRegionInfo> regions = null;
532 long lastLogTime = startTime;
533 while (!server.isStopped() && remaining > 0) {
534 Thread.sleep(waitingTimeForEvents);
535 regions = assignmentManager.getRegionStates().getRegionsOfTable(tableName);
536 long now = EnvironmentEdgeManager.currentTime();
537
538
539 if (LOG.isDebugEnabled() && ((now - lastLogTime) > 10000)) {
540 lastLogTime = now;
541 LOG.debug("Disable waiting until done; " + remaining + " ms remaining; " + regions);
542 }
543 if (regions.isEmpty()) break;
544 remaining = timeout - (now - startTime);
545 }
546 return regions != null && regions.isEmpty();
547 }
548 }
549 }