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.util.List;
25 import java.util.concurrent.atomic.AtomicBoolean;
26
27 import org.apache.commons.logging.Log;
28 import org.apache.commons.logging.LogFactory;
29 import org.apache.hadoop.hbase.HColumnDescriptor;
30 import org.apache.hadoop.hbase.HRegionInfo;
31 import org.apache.hadoop.hbase.HTableDescriptor;
32 import org.apache.hadoop.hbase.InvalidFamilyOperationException;
33 import org.apache.hadoop.hbase.TableName;
34 import org.apache.hadoop.hbase.classification.InterfaceAudience;
35 import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
36 import org.apache.hadoop.hbase.procedure2.StateMachineProcedure;
37 import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
38 import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos;
39 import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos.ModifyColumnFamilyState;
40 import org.apache.hadoop.hbase.protobuf.generated.ZooKeeperProtos;
41 import org.apache.hadoop.hbase.security.User;
42
43
44
45
46 @InterfaceAudience.Private
47 public class ModifyColumnFamilyProcedure
48 extends StateMachineProcedure<MasterProcedureEnv, ModifyColumnFamilyState>
49 implements TableProcedureInterface {
50 private static final Log LOG = LogFactory.getLog(ModifyColumnFamilyProcedure.class);
51
52 private final AtomicBoolean aborted = new AtomicBoolean(false);
53
54 private TableName tableName;
55 private HTableDescriptor unmodifiedHTableDescriptor;
56 private HColumnDescriptor cfDescriptor;
57 private User user;
58
59 private Boolean traceEnabled;
60
61 public ModifyColumnFamilyProcedure() {
62 this.unmodifiedHTableDescriptor = null;
63 this.traceEnabled = null;
64 }
65
66 public ModifyColumnFamilyProcedure(final MasterProcedureEnv env, final TableName tableName,
67 final HColumnDescriptor cfDescriptor) {
68 this.tableName = tableName;
69 this.cfDescriptor = cfDescriptor;
70 this.user = env.getRequestUser();
71 this.setOwner(this.user.getShortName());
72 this.unmodifiedHTableDescriptor = null;
73 this.traceEnabled = null;
74 }
75
76 @Override
77 protected Flow executeFromState(final MasterProcedureEnv env,
78 final ModifyColumnFamilyState state) throws InterruptedException {
79 if (isTraceEnabled()) {
80 LOG.trace(this + " execute state=" + state);
81 }
82
83 try {
84 switch (state) {
85 case MODIFY_COLUMN_FAMILY_PREPARE:
86 prepareModify(env);
87 setNextState(ModifyColumnFamilyState.MODIFY_COLUMN_FAMILY_PRE_OPERATION);
88 break;
89 case MODIFY_COLUMN_FAMILY_PRE_OPERATION:
90 preModify(env, state);
91 setNextState(ModifyColumnFamilyState.MODIFY_COLUMN_FAMILY_UPDATE_TABLE_DESCRIPTOR);
92 break;
93 case MODIFY_COLUMN_FAMILY_UPDATE_TABLE_DESCRIPTOR:
94 updateTableDescriptor(env);
95 setNextState(ModifyColumnFamilyState.MODIFY_COLUMN_FAMILY_POST_OPERATION);
96 break;
97 case MODIFY_COLUMN_FAMILY_POST_OPERATION:
98 postModify(env, state);
99 setNextState(ModifyColumnFamilyState.MODIFY_COLUMN_FAMILY_REOPEN_ALL_REGIONS);
100 break;
101 case MODIFY_COLUMN_FAMILY_REOPEN_ALL_REGIONS:
102 reOpenAllRegionsIfTableIsOnline(env);
103 return Flow.NO_MORE_STATE;
104 default:
105 throw new UnsupportedOperationException(this + " unhandled state=" + state);
106 }
107 } catch (IOException e) {
108 LOG.warn("Error trying to modify the column family " + getColumnFamilyName()
109 + " of the table " + tableName + "(in state=" + state + ")", e);
110
111 setFailure("master-modify-columnfamily", e);
112 }
113 return Flow.HAS_MORE_STATE;
114 }
115
116 @Override
117 protected void rollbackState(final MasterProcedureEnv env, final ModifyColumnFamilyState state)
118 throws IOException {
119 if (isTraceEnabled()) {
120 LOG.trace(this + " rollback state=" + state);
121 }
122 try {
123 switch (state) {
124 case MODIFY_COLUMN_FAMILY_REOPEN_ALL_REGIONS:
125 break;
126 case MODIFY_COLUMN_FAMILY_POST_OPERATION:
127
128 break;
129 case MODIFY_COLUMN_FAMILY_UPDATE_TABLE_DESCRIPTOR:
130 restoreTableDescriptor(env);
131 break;
132 case MODIFY_COLUMN_FAMILY_PRE_OPERATION:
133
134 break;
135 case MODIFY_COLUMN_FAMILY_PREPARE:
136 break;
137 default:
138 throw new UnsupportedOperationException(this + " unhandled state=" + state);
139 }
140 } catch (IOException e) {
141
142
143 LOG.warn("Failed rollback attempt step " + state + " for adding the column family"
144 + getColumnFamilyName() + " to the table " + tableName, e);
145 throw e;
146 }
147 }
148
149 @Override
150 protected ModifyColumnFamilyState getState(final int stateId) {
151 return ModifyColumnFamilyState.valueOf(stateId);
152 }
153
154 @Override
155 protected int getStateId(final ModifyColumnFamilyState state) {
156 return state.getNumber();
157 }
158
159 @Override
160 protected ModifyColumnFamilyState getInitialState() {
161 return ModifyColumnFamilyState.MODIFY_COLUMN_FAMILY_PREPARE;
162 }
163
164 @Override
165 protected void setNextState(ModifyColumnFamilyState state) {
166 if (aborted.get()) {
167 setAbortFailure("modify-columnfamily", "abort requested");
168 } else {
169 super.setNextState(state);
170 }
171 }
172
173 @Override
174 public boolean abort(final MasterProcedureEnv env) {
175 aborted.set(true);
176 return true;
177 }
178
179 @Override
180 protected boolean acquireLock(final MasterProcedureEnv env) {
181 if (env.waitInitialized(this)) return false;
182 return env.getProcedureQueue().tryAcquireTableExclusiveLock(this, tableName);
183 }
184
185 @Override
186 protected void releaseLock(final MasterProcedureEnv env) {
187 env.getProcedureQueue().releaseTableExclusiveLock(this, tableName);
188 }
189
190 @Override
191 public void serializeStateData(final OutputStream stream) throws IOException {
192 super.serializeStateData(stream);
193
194 MasterProcedureProtos.ModifyColumnFamilyStateData.Builder modifyCFMsg =
195 MasterProcedureProtos.ModifyColumnFamilyStateData.newBuilder()
196 .setUserInfo(MasterProcedureUtil.toProtoUserInfo(user))
197 .setTableName(ProtobufUtil.toProtoTableName(tableName))
198 .setColumnfamilySchema(cfDescriptor.convert());
199 if (unmodifiedHTableDescriptor != null) {
200 modifyCFMsg.setUnmodifiedTableSchema(unmodifiedHTableDescriptor.convert());
201 }
202
203 modifyCFMsg.build().writeDelimitedTo(stream);
204 }
205
206 @Override
207 public void deserializeStateData(final InputStream stream) throws IOException {
208 super.deserializeStateData(stream);
209
210 MasterProcedureProtos.ModifyColumnFamilyStateData modifyCFMsg =
211 MasterProcedureProtos.ModifyColumnFamilyStateData.parseDelimitedFrom(stream);
212 user = MasterProcedureUtil.toUserInfo(modifyCFMsg.getUserInfo());
213 tableName = ProtobufUtil.toTableName(modifyCFMsg.getTableName());
214 cfDescriptor = HColumnDescriptor.convert(modifyCFMsg.getColumnfamilySchema());
215 if (modifyCFMsg.hasUnmodifiedTableSchema()) {
216 unmodifiedHTableDescriptor = HTableDescriptor.convert(modifyCFMsg.getUnmodifiedTableSchema());
217 }
218 }
219
220 @Override
221 public void toStringClassDetails(StringBuilder sb) {
222 sb.append(getClass().getSimpleName());
223 sb.append(" (table=");
224 sb.append(tableName);
225 sb.append(", columnfamily=");
226 if (cfDescriptor != null) {
227 sb.append(getColumnFamilyName());
228 } else {
229 sb.append("Unknown");
230 }
231 sb.append(")");
232 }
233
234 @Override
235 public TableName getTableName() {
236 return tableName;
237 }
238
239 @Override
240 public TableOperationType getTableOperationType() {
241 return TableOperationType.EDIT;
242 }
243
244
245
246
247
248
249 private void prepareModify(final MasterProcedureEnv env) throws IOException {
250
251 MasterDDLOperationHelper.checkTableModifiable(env, tableName);
252
253 unmodifiedHTableDescriptor = env.getMasterServices().getTableDescriptors().get(tableName);
254 if (unmodifiedHTableDescriptor == null) {
255 throw new IOException("HTableDescriptor missing for " + tableName);
256 }
257 if (!unmodifiedHTableDescriptor.hasFamily(cfDescriptor.getName())) {
258 throw new InvalidFamilyOperationException("Family '" + getColumnFamilyName()
259 + "' does not exist, so it cannot be modified");
260 }
261 }
262
263
264
265
266
267
268
269
270 private void preModify(final MasterProcedureEnv env, final ModifyColumnFamilyState state)
271 throws IOException, InterruptedException {
272 runCoprocessorAction(env, state);
273 }
274
275
276
277
278 private void updateTableDescriptor(final MasterProcedureEnv env) throws IOException {
279
280 LOG.info("ModifyColumnFamily. Table = " + tableName + " HCD = " + cfDescriptor.toString());
281
282 HTableDescriptor htd = env.getMasterServices().getTableDescriptors().get(tableName);
283 htd.modifyFamily(cfDescriptor);
284 env.getMasterServices().getTableDescriptors().add(htd);
285 }
286
287
288
289
290
291
292 private void restoreTableDescriptor(final MasterProcedureEnv env) throws IOException {
293 env.getMasterServices().getTableDescriptors().add(unmodifiedHTableDescriptor);
294
295
296 reOpenAllRegionsIfTableIsOnline(env);
297 }
298
299
300
301
302
303
304
305
306 private void postModify(final MasterProcedureEnv env, final ModifyColumnFamilyState state)
307 throws IOException, InterruptedException {
308 runCoprocessorAction(env, state);
309 }
310
311
312
313
314
315
316 private void reOpenAllRegionsIfTableIsOnline(final MasterProcedureEnv env) throws IOException {
317
318 if (!env.getMasterServices().getAssignmentManager().getTableStateManager()
319 .isTableState(getTableName(), ZooKeeperProtos.Table.State.ENABLED)) {
320 return;
321 }
322
323 List<HRegionInfo> regionInfoList = ProcedureSyncWait.getRegionsFromMeta(env, getTableName());
324 if (MasterDDLOperationHelper.reOpenAllRegions(env, getTableName(), regionInfoList)) {
325 LOG.info("Completed add column family operation on table " + getTableName());
326 } else {
327 LOG.warn("Error on reopening the regions on table " + getTableName());
328 }
329 }
330
331
332
333
334
335
336 private Boolean isTraceEnabled() {
337 if (traceEnabled == null) {
338 traceEnabled = LOG.isTraceEnabled();
339 }
340 return traceEnabled;
341 }
342
343 private String getColumnFamilyName() {
344 return cfDescriptor.getNameAsString();
345 }
346
347
348
349
350
351
352
353
354 private void runCoprocessorAction(final MasterProcedureEnv env,
355 final ModifyColumnFamilyState state) throws IOException, InterruptedException {
356 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
357 if (cpHost != null) {
358 switch (state) {
359 case MODIFY_COLUMN_FAMILY_PRE_OPERATION:
360 cpHost.preModifyColumnHandler(tableName, cfDescriptor, user);
361 break;
362 case MODIFY_COLUMN_FAMILY_POST_OPERATION:
363 cpHost.postModifyColumnHandler(tableName, cfDescriptor, user);
364 break;
365 default:
366 throw new UnsupportedOperationException(this + " unhandled state=" + state);
367 }
368 }
369 }
370 }