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