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