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