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