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