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