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