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(final MasterProcedureEnv env, final HTableDescriptor htd) {
76 initilize();
77 this.modifiedHTableDescriptor = htd;
78 this.user = env.getRequestUser().getUGI();
79 this.setOwner(this.user.getShortUserName());
80 }
81
82 private void initilize() {
83 this.unmodifiedHTableDescriptor = null;
84 this.regionInfoList = null;
85 this.traceEnabled = null;
86 this.deleteColumnFamilyInModify = false;
87 }
88
89 @Override
90 protected Flow executeFromState(final MasterProcedureEnv env, final ModifyTableState state)
91 throws InterruptedException {
92 if (isTraceEnabled()) {
93 LOG.trace(this + " execute state=" + state);
94 }
95
96 try {
97 switch (state) {
98 case MODIFY_TABLE_PREPARE:
99 prepareModify(env);
100 setNextState(ModifyTableState.MODIFY_TABLE_PRE_OPERATION);
101 break;
102 case MODIFY_TABLE_PRE_OPERATION:
103 preModify(env, state);
104 setNextState(ModifyTableState.MODIFY_TABLE_UPDATE_TABLE_DESCRIPTOR);
105 break;
106 case MODIFY_TABLE_UPDATE_TABLE_DESCRIPTOR:
107 updateTableDescriptor(env);
108 setNextState(ModifyTableState.MODIFY_TABLE_REMOVE_REPLICA_COLUMN);
109 break;
110 case MODIFY_TABLE_REMOVE_REPLICA_COLUMN:
111 updateReplicaColumnsIfNeeded(env, unmodifiedHTableDescriptor, modifiedHTableDescriptor);
112 if (deleteColumnFamilyInModify) {
113 setNextState(ModifyTableState.MODIFY_TABLE_DELETE_FS_LAYOUT);
114 } else {
115 setNextState(ModifyTableState.MODIFY_TABLE_POST_OPERATION);
116 }
117 break;
118 case MODIFY_TABLE_DELETE_FS_LAYOUT:
119 deleteFromFs(env, unmodifiedHTableDescriptor, modifiedHTableDescriptor);
120 setNextState(ModifyTableState.MODIFY_TABLE_POST_OPERATION);
121 break;
122 case MODIFY_TABLE_POST_OPERATION:
123 postModify(env, state);
124 setNextState(ModifyTableState.MODIFY_TABLE_REOPEN_ALL_REGIONS);
125 break;
126 case MODIFY_TABLE_REOPEN_ALL_REGIONS:
127 reOpenAllRegionsIfTableIsOnline(env);
128 return Flow.NO_MORE_STATE;
129 default:
130 throw new UnsupportedOperationException("unhandled state=" + state);
131 }
132 } catch (IOException e) {
133 if (!isRollbackSupported(state)) {
134
135 LOG.warn("Error trying to modify table=" + getTableName() + " state=" + state, e);
136 } else {
137 LOG.error("Error trying to modify table=" + getTableName() + " state=" + state, e);
138 setFailure("master-modify-table", e);
139 }
140 }
141 return Flow.HAS_MORE_STATE;
142 }
143
144 @Override
145 protected void rollbackState(final MasterProcedureEnv env, final ModifyTableState state)
146 throws IOException {
147 if (isTraceEnabled()) {
148 LOG.trace(this + " rollback state=" + state);
149 }
150 try {
151 switch (state) {
152 case MODIFY_TABLE_REOPEN_ALL_REGIONS:
153 break;
154 case MODIFY_TABLE_POST_OPERATION:
155
156 break;
157 case MODIFY_TABLE_DELETE_FS_LAYOUT:
158
159
160
161 assert deleteColumnFamilyInModify;
162 throw new UnsupportedOperationException(this + " rollback of state=" + state
163 + " is unsupported.");
164 case MODIFY_TABLE_REMOVE_REPLICA_COLUMN:
165
166 updateReplicaColumnsIfNeeded(env, modifiedHTableDescriptor, unmodifiedHTableDescriptor);
167 break;
168 case MODIFY_TABLE_UPDATE_TABLE_DESCRIPTOR:
169 restoreTableDescriptor(env);
170 break;
171 case MODIFY_TABLE_PRE_OPERATION:
172
173 break;
174 case MODIFY_TABLE_PREPARE:
175 break;
176 default:
177 throw new UnsupportedOperationException("unhandled state=" + state);
178 }
179 } catch (IOException e) {
180 LOG.warn("Fail trying to rollback modify table=" + getTableName() + " state=" + state, e);
181 throw e;
182 }
183 }
184
185 @Override
186 protected ModifyTableState getState(final int stateId) {
187 return ModifyTableState.valueOf(stateId);
188 }
189
190 @Override
191 protected int getStateId(final ModifyTableState state) {
192 return state.getNumber();
193 }
194
195 @Override
196 protected ModifyTableState getInitialState() {
197 return ModifyTableState.MODIFY_TABLE_PREPARE;
198 }
199
200 @Override
201 protected void setNextState(final ModifyTableState state) {
202 if (aborted.get() && isRollbackSupported(state)) {
203 setAbortFailure("modify-table", "abort requested");
204 } else {
205 super.setNextState(state);
206 }
207 }
208
209 @Override
210 public boolean abort(final MasterProcedureEnv env) {
211 aborted.set(true);
212 return true;
213 }
214
215 @Override
216 protected boolean acquireLock(final MasterProcedureEnv env) {
217 if (env.waitInitialized(this)) return false;
218 return env.getProcedureQueue().tryAcquireTableExclusiveLock(this, getTableName());
219 }
220
221 @Override
222 protected void releaseLock(final MasterProcedureEnv env) {
223 env.getProcedureQueue().releaseTableExclusiveLock(this, 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 if (modifiedHTableDescriptor.getColumnFamilies().length == 0) {
290 throw new DoNotRetryIOException("Table " + getTableName().toString() +
291 " should have at least one column family.");
292 }
293
294
295 this.unmodifiedHTableDescriptor =
296 env.getMasterServices().getTableDescriptors().get(getTableName());
297
298 if (env.getMasterServices().getAssignmentManager().getTableStateManager()
299 .isTableState(getTableName(), ZooKeeperProtos.Table.State.ENABLED)) {
300
301 if (!MasterDDLOperationHelper.isOnlineSchemaChangeAllowed(env)) {
302 throw new TableNotDisabledException(getTableName());
303 }
304
305 if (modifiedHTableDescriptor.getRegionReplication() != unmodifiedHTableDescriptor
306 .getRegionReplication()) {
307 throw new IOException("REGION_REPLICATION change is not supported for enabled tables");
308 }
309 }
310
311
312
313 final Set<byte[]> oldFamilies = unmodifiedHTableDescriptor.getFamiliesKeys();
314 final Set<byte[]> newFamilies = modifiedHTableDescriptor.getFamiliesKeys();
315 for (byte[] familyName : oldFamilies) {
316 if (!newFamilies.contains(familyName)) {
317 this.deleteColumnFamilyInModify = true;
318 break;
319 }
320 }
321 }
322
323
324
325
326
327
328
329
330 private void preModify(final MasterProcedureEnv env, final ModifyTableState state)
331 throws IOException, InterruptedException {
332 runCoprocessorAction(env, state);
333 }
334
335
336
337
338
339
340 private void updateTableDescriptor(final MasterProcedureEnv env) throws IOException {
341 env.getMasterServices().getTableDescriptors().add(modifiedHTableDescriptor);
342 }
343
344
345
346
347
348
349 private void restoreTableDescriptor(final MasterProcedureEnv env) throws IOException {
350 env.getMasterServices().getTableDescriptors().add(unmodifiedHTableDescriptor);
351
352
353 deleteFromFs(env, modifiedHTableDescriptor, unmodifiedHTableDescriptor);
354
355
356 reOpenAllRegionsIfTableIsOnline(env);
357 }
358
359
360
361
362
363
364 private void deleteFromFs(final MasterProcedureEnv env,
365 final HTableDescriptor oldHTableDescriptor, final HTableDescriptor newHTableDescriptor)
366 throws IOException {
367 final Set<byte[]> oldFamilies = oldHTableDescriptor.getFamiliesKeys();
368 final Set<byte[]> newFamilies = newHTableDescriptor.getFamiliesKeys();
369 for (byte[] familyName : oldFamilies) {
370 if (!newFamilies.contains(familyName)) {
371 MasterDDLOperationHelper.deleteColumnFamilyFromFileSystem(
372 env,
373 getTableName(),
374 getRegionInfoList(env),
375 familyName);
376 }
377 }
378 }
379
380
381
382
383
384
385 private void updateReplicaColumnsIfNeeded(
386 final MasterProcedureEnv env,
387 final HTableDescriptor oldHTableDescriptor,
388 final HTableDescriptor newHTableDescriptor) throws IOException {
389 final int oldReplicaCount = oldHTableDescriptor.getRegionReplication();
390 final int newReplicaCount = newHTableDescriptor.getRegionReplication();
391
392 if (newReplicaCount < oldReplicaCount) {
393 Set<byte[]> tableRows = new HashSet<byte[]>();
394 Connection connection = env.getMasterServices().getConnection();
395 Scan scan = MetaTableAccessor.getScanForTableName(getTableName());
396 scan.addColumn(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER);
397
398 try (Table metaTable = connection.getTable(TableName.META_TABLE_NAME)) {
399 ResultScanner resScanner = metaTable.getScanner(scan);
400 for (Result result : resScanner) {
401 tableRows.add(result.getRow());
402 }
403 MetaTableAccessor.removeRegionReplicasFromMeta(
404 tableRows,
405 newReplicaCount,
406 oldReplicaCount - newReplicaCount,
407 connection);
408 }
409 }
410
411
412 if (newReplicaCount > 1 && oldReplicaCount <= 1) {
413 ServerRegionReplicaUtil.setupRegionReplicaReplication(env.getMasterConfiguration());
414 }
415 }
416
417
418
419
420
421
422
423
424 private void postModify(final MasterProcedureEnv env, final ModifyTableState state)
425 throws IOException, InterruptedException {
426 runCoprocessorAction(env, state);
427 }
428
429
430
431
432
433
434 private void reOpenAllRegionsIfTableIsOnline(final MasterProcedureEnv env) throws IOException {
435
436 if (!env.getMasterServices().getAssignmentManager().getTableStateManager()
437 .isTableState(getTableName(), ZooKeeperProtos.Table.State.ENABLED)) {
438 return;
439 }
440
441 if (MasterDDLOperationHelper.reOpenAllRegions(env, getTableName(), getRegionInfoList(env))) {
442 LOG.info("Completed modify table operation on table " + getTableName());
443 } else {
444 LOG.warn("Error on reopening the regions on table " + getTableName());
445 }
446 }
447
448
449
450
451
452
453 private Boolean isTraceEnabled() {
454 if (traceEnabled == null) {
455 traceEnabled = LOG.isTraceEnabled();
456 }
457 return traceEnabled;
458 }
459
460
461
462
463
464
465
466
467 private void runCoprocessorAction(final MasterProcedureEnv env, final ModifyTableState state)
468 throws IOException, InterruptedException {
469 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
470 if (cpHost != null) {
471 user.doAs(new PrivilegedExceptionAction<Void>() {
472 @Override
473 public Void run() throws Exception {
474 switch (state) {
475 case MODIFY_TABLE_PRE_OPERATION:
476 cpHost.preModifyTableHandler(getTableName(), modifiedHTableDescriptor);
477 break;
478 case MODIFY_TABLE_POST_OPERATION:
479 cpHost.postModifyTableHandler(getTableName(), modifiedHTableDescriptor);
480 break;
481 default:
482 throw new UnsupportedOperationException(this + " unhandled state=" + state);
483 }
484 return null;
485 }
486 });
487 }
488 }
489
490
491
492
493 private boolean isRollbackSupported(final ModifyTableState state) {
494 if (deleteColumnFamilyInModify) {
495 switch (state) {
496 case MODIFY_TABLE_DELETE_FS_LAYOUT:
497 case MODIFY_TABLE_POST_OPERATION:
498 case MODIFY_TABLE_REOPEN_ALL_REGIONS:
499
500 return false;
501 default:
502 break;
503 }
504 }
505 return true;
506 }
507
508 private List<HRegionInfo> getRegionInfoList(final MasterProcedureEnv env) throws IOException {
509 if (regionInfoList == null) {
510 regionInfoList = ProcedureSyncWait.getRegionsFromMeta(env, getTableName());
511 }
512 return regionInfoList;
513 }
514 }