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