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.HashSet;
26 import java.util.List;
27 import java.util.Set;
28 import java.util.concurrent.atomic.AtomicBoolean;
29
30 import org.apache.commons.logging.Log;
31 import org.apache.commons.logging.LogFactory;
32 import org.apache.hadoop.hbase.DoNotRetryIOException;
33 import org.apache.hadoop.hbase.HConstants;
34 import org.apache.hadoop.hbase.HRegionInfo;
35 import org.apache.hadoop.hbase.HTableDescriptor;
36 import org.apache.hadoop.hbase.MetaTableAccessor;
37 import org.apache.hadoop.hbase.TableName;
38 import org.apache.hadoop.hbase.TableNotDisabledException;
39 import org.apache.hadoop.hbase.TableNotFoundException;
40 import org.apache.hadoop.hbase.classification.InterfaceAudience;
41 import org.apache.hadoop.hbase.client.Connection;
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.executor.EventType;
47 import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
48 import org.apache.hadoop.hbase.procedure2.StateMachineProcedure;
49 import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos;
50 import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos.ModifyTableState;
51 import org.apache.hadoop.hbase.protobuf.generated.ZooKeeperProtos;
52 import org.apache.hadoop.hbase.util.ServerRegionReplicaUtil;
53 import org.apache.hadoop.security.UserGroupInformation;
54
55 @InterfaceAudience.Private
56 public class ModifyTableProcedure
57 extends StateMachineProcedure<MasterProcedureEnv, ModifyTableState>
58 implements TableProcedureInterface {
59 private static final Log LOG = LogFactory.getLog(ModifyTableProcedure.class);
60
61 private final AtomicBoolean aborted = new AtomicBoolean(false);
62
63 private HTableDescriptor unmodifiedHTableDescriptor = null;
64 private HTableDescriptor modifiedHTableDescriptor;
65 private UserGroupInformation user;
66 private boolean deleteColumnFamilyInModify;
67
68 private List<HRegionInfo> regionInfoList;
69 private Boolean traceEnabled = null;
70
71 public ModifyTableProcedure() {
72 initilize();
73 }
74
75 public ModifyTableProcedure(
76 final MasterProcedureEnv env,
77 final HTableDescriptor htd) throws IOException {
78 initilize();
79 this.modifiedHTableDescriptor = htd;
80 this.user = env.getRequestUser().getUGI();
81 this.setOwner(this.user.getShortUserName());
82 }
83
84 private void initilize() {
85 this.unmodifiedHTableDescriptor = null;
86 this.regionInfoList = null;
87 this.traceEnabled = null;
88 this.deleteColumnFamilyInModify = false;
89 }
90
91 @Override
92 protected Flow executeFromState(final MasterProcedureEnv env, final ModifyTableState state)
93 throws InterruptedException {
94 if (isTraceEnabled()) {
95 LOG.trace(this + " execute state=" + state);
96 }
97
98 try {
99 switch (state) {
100 case MODIFY_TABLE_PREPARE:
101 prepareModify(env);
102 setNextState(ModifyTableState.MODIFY_TABLE_PRE_OPERATION);
103 break;
104 case MODIFY_TABLE_PRE_OPERATION:
105 preModify(env, state);
106 setNextState(ModifyTableState.MODIFY_TABLE_UPDATE_TABLE_DESCRIPTOR);
107 break;
108 case MODIFY_TABLE_UPDATE_TABLE_DESCRIPTOR:
109 updateTableDescriptor(env);
110 setNextState(ModifyTableState.MODIFY_TABLE_REMOVE_REPLICA_COLUMN);
111 break;
112 case MODIFY_TABLE_REMOVE_REPLICA_COLUMN:
113 updateReplicaColumnsIfNeeded(env, unmodifiedHTableDescriptor, modifiedHTableDescriptor);
114 if (deleteColumnFamilyInModify) {
115 setNextState(ModifyTableState.MODIFY_TABLE_DELETE_FS_LAYOUT);
116 } else {
117 setNextState(ModifyTableState.MODIFY_TABLE_POST_OPERATION);
118 }
119 break;
120 case MODIFY_TABLE_DELETE_FS_LAYOUT:
121 deleteFromFs(env, unmodifiedHTableDescriptor, modifiedHTableDescriptor);
122 setNextState(ModifyTableState.MODIFY_TABLE_POST_OPERATION);
123 break;
124 case MODIFY_TABLE_POST_OPERATION:
125 postModify(env, state);
126 setNextState(ModifyTableState.MODIFY_TABLE_REOPEN_ALL_REGIONS);
127 break;
128 case MODIFY_TABLE_REOPEN_ALL_REGIONS:
129 reOpenAllRegionsIfTableIsOnline(env);
130 return Flow.NO_MORE_STATE;
131 default:
132 throw new UnsupportedOperationException("unhandled state=" + state);
133 }
134 } catch (IOException e) {
135 if (!isRollbackSupported(state)) {
136
137 LOG.warn("Error trying to modify table=" + getTableName() + " state=" + state, e);
138 } else {
139 LOG.error("Error trying to modify table=" + getTableName() + " state=" + state, e);
140 setFailure("master-modify-table", e);
141 }
142 }
143 return Flow.HAS_MORE_STATE;
144 }
145
146 @Override
147 protected void rollbackState(final MasterProcedureEnv env, final ModifyTableState state)
148 throws IOException {
149 if (isTraceEnabled()) {
150 LOG.trace(this + " rollback state=" + state);
151 }
152 try {
153 switch (state) {
154 case MODIFY_TABLE_REOPEN_ALL_REGIONS:
155 break;
156 case MODIFY_TABLE_POST_OPERATION:
157
158 break;
159 case MODIFY_TABLE_DELETE_FS_LAYOUT:
160
161
162
163 assert deleteColumnFamilyInModify;
164 throw new UnsupportedOperationException(this + " rollback of state=" + state
165 + " is unsupported.");
166 case MODIFY_TABLE_REMOVE_REPLICA_COLUMN:
167
168 updateReplicaColumnsIfNeeded(env, modifiedHTableDescriptor, unmodifiedHTableDescriptor);
169 break;
170 case MODIFY_TABLE_UPDATE_TABLE_DESCRIPTOR:
171 restoreTableDescriptor(env);
172 break;
173 case MODIFY_TABLE_PRE_OPERATION:
174
175 break;
176 case MODIFY_TABLE_PREPARE:
177 break;
178 default:
179 throw new UnsupportedOperationException("unhandled state=" + state);
180 }
181 } catch (IOException e) {
182 LOG.warn("Fail trying to rollback modify table=" + getTableName() + " state=" + state, e);
183 throw e;
184 }
185 }
186
187 @Override
188 protected ModifyTableState getState(final int stateId) {
189 return ModifyTableState.valueOf(stateId);
190 }
191
192 @Override
193 protected int getStateId(final ModifyTableState state) {
194 return state.getNumber();
195 }
196
197 @Override
198 protected ModifyTableState getInitialState() {
199 return ModifyTableState.MODIFY_TABLE_PREPARE;
200 }
201
202 @Override
203 protected void setNextState(final ModifyTableState state) {
204 if (aborted.get() && isRollbackSupported(state)) {
205 setAbortFailure("modify-table", "abort requested");
206 } else {
207 super.setNextState(state);
208 }
209 }
210
211 @Override
212 public boolean abort(final MasterProcedureEnv env) {
213 aborted.set(true);
214 return true;
215 }
216
217 @Override
218 protected boolean acquireLock(final MasterProcedureEnv env) {
219 if (env.waitInitialized(this)) return false;
220 return env.getProcedureQueue().tryAcquireTableExclusiveLock(this, getTableName());
221 }
222
223 @Override
224 protected void releaseLock(final MasterProcedureEnv env) {
225 env.getProcedureQueue().releaseTableExclusiveLock(this, getTableName());
226 }
227
228 @Override
229 public void serializeStateData(final OutputStream stream) throws IOException {
230 super.serializeStateData(stream);
231
232 MasterProcedureProtos.ModifyTableStateData.Builder modifyTableMsg =
233 MasterProcedureProtos.ModifyTableStateData.newBuilder()
234 .setUserInfo(MasterProcedureUtil.toProtoUserInfo(user))
235 .setModifiedTableSchema(modifiedHTableDescriptor.convert())
236 .setDeleteColumnFamilyInModify(deleteColumnFamilyInModify);
237
238 if (unmodifiedHTableDescriptor != null) {
239 modifyTableMsg.setUnmodifiedTableSchema(unmodifiedHTableDescriptor.convert());
240 }
241
242 modifyTableMsg.build().writeDelimitedTo(stream);
243 }
244
245 @Override
246 public void deserializeStateData(final InputStream stream) throws IOException {
247 super.deserializeStateData(stream);
248
249 MasterProcedureProtos.ModifyTableStateData modifyTableMsg =
250 MasterProcedureProtos.ModifyTableStateData.parseDelimitedFrom(stream);
251 user = MasterProcedureUtil.toUserInfo(modifyTableMsg.getUserInfo());
252 modifiedHTableDescriptor = HTableDescriptor.convert(modifyTableMsg.getModifiedTableSchema());
253 deleteColumnFamilyInModify = modifyTableMsg.getDeleteColumnFamilyInModify();
254
255 if (modifyTableMsg.hasUnmodifiedTableSchema()) {
256 unmodifiedHTableDescriptor =
257 HTableDescriptor.convert(modifyTableMsg.getUnmodifiedTableSchema());
258 }
259 }
260
261 @Override
262 public void toStringClassDetails(StringBuilder sb) {
263 sb.append(getClass().getSimpleName());
264 sb.append(" (table=");
265 sb.append(getTableName());
266 sb.append(")");
267 }
268
269 @Override
270 public TableName getTableName() {
271 return modifiedHTableDescriptor.getTableName();
272 }
273
274 @Override
275 public TableOperationType getTableOperationType() {
276 return TableOperationType.EDIT;
277 }
278
279
280
281
282
283
284 private void prepareModify(final MasterProcedureEnv env) throws IOException {
285
286 if (!MetaTableAccessor.tableExists(env.getMasterServices().getConnection(), getTableName())) {
287 throw new TableNotFoundException(getTableName());
288 }
289
290
291 if (modifiedHTableDescriptor.getColumnFamilies().length == 0) {
292 throw new DoNotRetryIOException("Table " + getTableName().toString() +
293 " should have at least one column family.");
294 }
295
296
297 this.unmodifiedHTableDescriptor =
298 env.getMasterServices().getTableDescriptors().get(getTableName());
299
300 if (env.getMasterServices().getAssignmentManager().getTableStateManager()
301 .isTableState(getTableName(), ZooKeeperProtos.Table.State.ENABLED)) {
302
303 if (!MasterDDLOperationHelper.isOnlineSchemaChangeAllowed(env)) {
304 throw new TableNotDisabledException(getTableName());
305 }
306
307 if (modifiedHTableDescriptor.getRegionReplication() != unmodifiedHTableDescriptor
308 .getRegionReplication()) {
309 throw new IOException("REGION_REPLICATION change is not supported for enabled tables");
310 }
311 }
312
313
314
315 final Set<byte[]> oldFamilies = unmodifiedHTableDescriptor.getFamiliesKeys();
316 final Set<byte[]> newFamilies = modifiedHTableDescriptor.getFamiliesKeys();
317 for (byte[] familyName : oldFamilies) {
318 if (!newFamilies.contains(familyName)) {
319 this.deleteColumnFamilyInModify = true;
320 break;
321 }
322 }
323 }
324
325
326
327
328
329
330
331
332 private void preModify(final MasterProcedureEnv env, final ModifyTableState state)
333 throws IOException, InterruptedException {
334 runCoprocessorAction(env, state);
335 }
336
337
338
339
340
341
342 private void updateTableDescriptor(final MasterProcedureEnv env) throws IOException {
343 env.getMasterServices().getTableDescriptors().add(modifiedHTableDescriptor);
344 }
345
346
347
348
349
350
351 private void restoreTableDescriptor(final MasterProcedureEnv env) throws IOException {
352 env.getMasterServices().getTableDescriptors().add(unmodifiedHTableDescriptor);
353
354
355 deleteFromFs(env, modifiedHTableDescriptor, unmodifiedHTableDescriptor);
356
357
358 reOpenAllRegionsIfTableIsOnline(env);
359 }
360
361
362
363
364
365
366 private void deleteFromFs(final MasterProcedureEnv env,
367 final HTableDescriptor oldHTableDescriptor, final HTableDescriptor newHTableDescriptor)
368 throws IOException {
369 final Set<byte[]> oldFamilies = oldHTableDescriptor.getFamiliesKeys();
370 final Set<byte[]> newFamilies = newHTableDescriptor.getFamiliesKeys();
371 for (byte[] familyName : oldFamilies) {
372 if (!newFamilies.contains(familyName)) {
373 MasterDDLOperationHelper.deleteColumnFamilyFromFileSystem(
374 env,
375 getTableName(),
376 getRegionInfoList(env),
377 familyName,
378 oldHTableDescriptor.getFamily(familyName).isMobEnabled());
379 }
380 }
381 }
382
383
384
385
386
387
388 private void updateReplicaColumnsIfNeeded(
389 final MasterProcedureEnv env,
390 final HTableDescriptor oldHTableDescriptor,
391 final HTableDescriptor newHTableDescriptor) throws IOException {
392 final int oldReplicaCount = oldHTableDescriptor.getRegionReplication();
393 final int newReplicaCount = newHTableDescriptor.getRegionReplication();
394
395 if (newReplicaCount < oldReplicaCount) {
396 Set<byte[]> tableRows = new HashSet<byte[]>();
397 Connection connection = env.getMasterServices().getConnection();
398 Scan scan = MetaTableAccessor.getScanForTableName(getTableName());
399 scan.addColumn(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER);
400
401 try (Table metaTable = connection.getTable(TableName.META_TABLE_NAME)) {
402 ResultScanner resScanner = metaTable.getScanner(scan);
403 for (Result result : resScanner) {
404 tableRows.add(result.getRow());
405 }
406 MetaTableAccessor.removeRegionReplicasFromMeta(
407 tableRows,
408 newReplicaCount,
409 oldReplicaCount - newReplicaCount,
410 connection);
411 }
412 }
413
414
415 if (newReplicaCount > 1 && oldReplicaCount <= 1) {
416 ServerRegionReplicaUtil.setupRegionReplicaReplication(env.getMasterConfiguration());
417 }
418 }
419
420
421
422
423
424
425
426
427 private void postModify(final MasterProcedureEnv env, final ModifyTableState state)
428 throws IOException, InterruptedException {
429 runCoprocessorAction(env, state);
430 }
431
432
433
434
435
436
437 private void reOpenAllRegionsIfTableIsOnline(final MasterProcedureEnv env) throws IOException {
438
439 if (!env.getMasterServices().getAssignmentManager().getTableStateManager()
440 .isTableState(getTableName(), ZooKeeperProtos.Table.State.ENABLED)) {
441 return;
442 }
443
444 if (MasterDDLOperationHelper.reOpenAllRegions(env, getTableName(), getRegionInfoList(env))) {
445 LOG.info("Completed modify table operation on table " + getTableName());
446 } else {
447 LOG.warn("Error on reopening the regions on table " + getTableName());
448 }
449 }
450
451
452
453
454
455
456 private Boolean isTraceEnabled() {
457 if (traceEnabled == null) {
458 traceEnabled = LOG.isTraceEnabled();
459 }
460 return traceEnabled;
461 }
462
463
464
465
466
467
468
469
470 private void runCoprocessorAction(final MasterProcedureEnv env, final ModifyTableState state)
471 throws IOException, InterruptedException {
472 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
473 if (cpHost != null) {
474 user.doAs(new PrivilegedExceptionAction<Void>() {
475 @Override
476 public Void run() throws Exception {
477 switch (state) {
478 case MODIFY_TABLE_PRE_OPERATION:
479 cpHost.preModifyTableHandler(getTableName(), modifiedHTableDescriptor);
480 break;
481 case MODIFY_TABLE_POST_OPERATION:
482 cpHost.postModifyTableHandler(getTableName(), modifiedHTableDescriptor);
483 break;
484 default:
485 throw new UnsupportedOperationException(this + " unhandled state=" + state);
486 }
487 return null;
488 }
489 });
490 }
491 }
492
493
494
495
496 private boolean isRollbackSupported(final ModifyTableState state) {
497 if (deleteColumnFamilyInModify) {
498 switch (state) {
499 case MODIFY_TABLE_DELETE_FS_LAYOUT:
500 case MODIFY_TABLE_POST_OPERATION:
501 case MODIFY_TABLE_REOPEN_ALL_REGIONS:
502
503 return false;
504 default:
505 break;
506 }
507 }
508 return true;
509 }
510
511 private List<HRegionInfo> getRegionInfoList(final MasterProcedureEnv env) throws IOException {
512 if (regionInfoList == null) {
513 regionInfoList = ProcedureSyncWait.getRegionsFromMeta(env, getTableName());
514 }
515 return regionInfoList;
516 }
517 }