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.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.executor.EventType;
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.DeleteColumnFamilyState;
41 import org.apache.hadoop.hbase.protobuf.generated.ZooKeeperProtos;
42 import org.apache.hadoop.hbase.util.ByteStringer;
43 import org.apache.hadoop.hbase.util.Bytes;
44 import org.apache.hadoop.security.UserGroupInformation;
45
46
47
48
49 @InterfaceAudience.Private
50 public class DeleteColumnFamilyProcedure
51 extends StateMachineProcedure<MasterProcedureEnv, DeleteColumnFamilyState>
52 implements TableProcedureInterface {
53 private static final Log LOG = LogFactory.getLog(DeleteColumnFamilyProcedure.class);
54
55 private final AtomicBoolean aborted = new AtomicBoolean(false);
56
57 private HTableDescriptor unmodifiedHTableDescriptor;
58 private TableName tableName;
59 private byte [] familyName;
60 private UserGroupInformation user;
61
62 private List<HRegionInfo> regionInfoList;
63 private Boolean traceEnabled;
64
65 public DeleteColumnFamilyProcedure() {
66 this.unmodifiedHTableDescriptor = null;
67 this.regionInfoList = null;
68 this.traceEnabled = null;
69 }
70
71 public DeleteColumnFamilyProcedure(
72 final MasterProcedureEnv env,
73 final TableName tableName,
74 final byte[] familyName) throws IOException {
75 this.tableName = tableName;
76 this.familyName = familyName;
77 this.user = env.getRequestUser().getUGI();
78 this.setOwner(this.user.getShortUserName());
79 this.unmodifiedHTableDescriptor = null;
80 this.regionInfoList = null;
81 this.traceEnabled = null;
82 }
83
84 @Override
85 protected Flow executeFromState(final MasterProcedureEnv env, DeleteColumnFamilyState state) {
86 if (isTraceEnabled()) {
87 LOG.trace(this + " execute state=" + state);
88 }
89
90 try {
91 switch (state) {
92 case DELETE_COLUMN_FAMILY_PREPARE:
93 prepareDelete(env);
94 setNextState(DeleteColumnFamilyState.DELETE_COLUMN_FAMILY_PRE_OPERATION);
95 break;
96 case DELETE_COLUMN_FAMILY_PRE_OPERATION:
97 preDelete(env, state);
98 setNextState(DeleteColumnFamilyState.DELETE_COLUMN_FAMILY_UPDATE_TABLE_DESCRIPTOR);
99 break;
100 case DELETE_COLUMN_FAMILY_UPDATE_TABLE_DESCRIPTOR:
101 updateTableDescriptor(env);
102 setNextState(DeleteColumnFamilyState.DELETE_COLUMN_FAMILY_DELETE_FS_LAYOUT);
103 break;
104 case DELETE_COLUMN_FAMILY_DELETE_FS_LAYOUT:
105 deleteFromFs(env);
106 setNextState(DeleteColumnFamilyState.DELETE_COLUMN_FAMILY_POST_OPERATION);
107 break;
108 case DELETE_COLUMN_FAMILY_POST_OPERATION:
109 postDelete(env, state);
110 setNextState(DeleteColumnFamilyState.DELETE_COLUMN_FAMILY_REOPEN_ALL_REGIONS);
111 break;
112 case DELETE_COLUMN_FAMILY_REOPEN_ALL_REGIONS:
113 reOpenAllRegionsIfTableIsOnline(env);
114 return Flow.NO_MORE_STATE;
115 default:
116 throw new UnsupportedOperationException(this + " unhandled state=" + state);
117 }
118 } catch (InterruptedException|IOException e) {
119 if (!isRollbackSupported(state)) {
120
121 LOG.warn("Error trying to delete the column family " + getColumnFamilyName()
122 + " from table " + tableName + "(in state=" + state + ")", e);
123 } else {
124 LOG.error("Error trying to delete the column family " + getColumnFamilyName()
125 + " from table " + tableName + "(in state=" + state + ")", e);
126 setFailure("master-delete-column-family", e);
127 }
128 }
129 return Flow.HAS_MORE_STATE;
130 }
131
132 @Override
133 protected void rollbackState(final MasterProcedureEnv env, final DeleteColumnFamilyState state)
134 throws IOException {
135 if (isTraceEnabled()) {
136 LOG.trace(this + " rollback state=" + state);
137 }
138 try {
139 switch (state) {
140 case DELETE_COLUMN_FAMILY_REOPEN_ALL_REGIONS:
141 break;
142 case DELETE_COLUMN_FAMILY_POST_OPERATION:
143
144 break;
145 case DELETE_COLUMN_FAMILY_DELETE_FS_LAYOUT:
146
147
148
149 throw new UnsupportedOperationException(this + " rollback of state=" + state
150 + " is unsupported.");
151 case DELETE_COLUMN_FAMILY_UPDATE_TABLE_DESCRIPTOR:
152 restoreTableDescriptor(env);
153 break;
154 case DELETE_COLUMN_FAMILY_PRE_OPERATION:
155
156 break;
157 case DELETE_COLUMN_FAMILY_PREPARE:
158 break;
159 default:
160 throw new UnsupportedOperationException(this + " unhandled state=" + state);
161 }
162 } catch (IOException e) {
163
164
165 LOG.warn("Failed rollback attempt step " + state + " for deleting the column family"
166 + getColumnFamilyName() + " to the table " + tableName, e);
167 throw e;
168 }
169 }
170
171 @Override
172 protected DeleteColumnFamilyState getState(final int stateId) {
173 return DeleteColumnFamilyState.valueOf(stateId);
174 }
175
176 @Override
177 protected int getStateId(final DeleteColumnFamilyState state) {
178 return state.getNumber();
179 }
180
181 @Override
182 protected DeleteColumnFamilyState getInitialState() {
183 return DeleteColumnFamilyState.DELETE_COLUMN_FAMILY_PREPARE;
184 }
185
186 @Override
187 protected void setNextState(DeleteColumnFamilyState state) {
188 if (aborted.get() && isRollbackSupported(state)) {
189 setAbortFailure("delete-columnfamily", "abort requested");
190 } else {
191 super.setNextState(state);
192 }
193 }
194
195 @Override
196 public boolean abort(final MasterProcedureEnv env) {
197 aborted.set(true);
198 return true;
199 }
200
201 @Override
202 protected boolean acquireLock(final MasterProcedureEnv env) {
203 if (!env.isInitialized()) return false;
204 return env.getProcedureQueue().tryAcquireTableWrite(
205 tableName,
206 EventType.C_M_DELETE_FAMILY.toString());
207 }
208
209 @Override
210 protected void releaseLock(final MasterProcedureEnv env) {
211 env.getProcedureQueue().releaseTableWrite(tableName);
212 }
213
214 @Override
215 public void serializeStateData(final OutputStream stream) throws IOException {
216 super.serializeStateData(stream);
217
218 MasterProcedureProtos.DeleteColumnFamilyStateData.Builder deleteCFMsg =
219 MasterProcedureProtos.DeleteColumnFamilyStateData.newBuilder()
220 .setUserInfo(MasterProcedureUtil.toProtoUserInfo(user))
221 .setTableName(ProtobufUtil.toProtoTableName(tableName))
222 .setColumnfamilyName(ByteStringer.wrap(familyName));
223 if (unmodifiedHTableDescriptor != null) {
224 deleteCFMsg.setUnmodifiedTableSchema(unmodifiedHTableDescriptor.convert());
225 }
226
227 deleteCFMsg.build().writeDelimitedTo(stream);
228 }
229
230 @Override
231 public void deserializeStateData(final InputStream stream) throws IOException {
232 super.deserializeStateData(stream);
233 MasterProcedureProtos.DeleteColumnFamilyStateData deleteCFMsg =
234 MasterProcedureProtos.DeleteColumnFamilyStateData.parseDelimitedFrom(stream);
235 user = MasterProcedureUtil.toUserInfo(deleteCFMsg.getUserInfo());
236 tableName = ProtobufUtil.toTableName(deleteCFMsg.getTableName());
237 familyName = deleteCFMsg.getColumnfamilyName().toByteArray();
238
239 if (deleteCFMsg.hasUnmodifiedTableSchema()) {
240 unmodifiedHTableDescriptor = HTableDescriptor.convert(deleteCFMsg.getUnmodifiedTableSchema());
241 }
242 }
243
244 @Override
245 public void toStringClassDetails(StringBuilder sb) {
246 sb.append(getClass().getSimpleName());
247 sb.append(" (table=");
248 sb.append(tableName);
249 sb.append(", columnfamily=");
250 if (familyName != null) {
251 sb.append(getColumnFamilyName());
252 } else {
253 sb.append("Unknown");
254 }
255 sb.append(")");
256 }
257
258 @Override
259 public TableName getTableName() {
260 return tableName;
261 }
262
263 @Override
264 public TableOperationType getTableOperationType() {
265 return TableOperationType.EDIT;
266 }
267
268
269
270
271
272
273 private void prepareDelete(final MasterProcedureEnv env) throws IOException {
274
275 MasterDDLOperationHelper.checkTableModifiable(env, tableName);
276
277
278 unmodifiedHTableDescriptor = env.getMasterServices().getTableDescriptors().get(tableName);
279 if (unmodifiedHTableDescriptor == null) {
280 throw new IOException("HTableDescriptor missing for " + tableName);
281 }
282 if (!unmodifiedHTableDescriptor.hasFamily(familyName)) {
283 throw new InvalidFamilyOperationException("Family '" + getColumnFamilyName()
284 + "' does not exist, so it cannot be deleted");
285 }
286
287 if (unmodifiedHTableDescriptor.getColumnFamilies().length == 1) {
288 throw new InvalidFamilyOperationException("Family '" + getColumnFamilyName()
289 + "' is the only column family in the table, so it cannot be deleted");
290 }
291 }
292
293
294
295
296
297
298
299
300 private void preDelete(final MasterProcedureEnv env, final DeleteColumnFamilyState state)
301 throws IOException, InterruptedException {
302 runCoprocessorAction(env, state);
303 }
304
305
306
307
308 private void updateTableDescriptor(final MasterProcedureEnv env) throws IOException {
309
310 LOG.info("DeleteColumn. Table = " + tableName + " family = " + getColumnFamilyName());
311
312 HTableDescriptor htd = env.getMasterServices().getTableDescriptors().get(tableName);
313
314 if (!htd.hasFamily(familyName)) {
315
316
317
318 return;
319 }
320
321 htd.removeFamily(familyName);
322 env.getMasterServices().getTableDescriptors().add(htd);
323 }
324
325
326
327
328
329
330 private void restoreTableDescriptor(final MasterProcedureEnv env) throws IOException {
331 env.getMasterServices().getTableDescriptors().add(unmodifiedHTableDescriptor);
332
333
334 reOpenAllRegionsIfTableIsOnline(env);
335 }
336
337
338
339
340 private void deleteFromFs(final MasterProcedureEnv env) throws IOException {
341 MasterDDLOperationHelper.deleteColumnFamilyFromFileSystem(env, tableName,
342 getRegionInfoList(env), familyName);
343 }
344
345
346
347
348
349
350
351
352 private void postDelete(final MasterProcedureEnv env, final DeleteColumnFamilyState state)
353 throws IOException, InterruptedException {
354 runCoprocessorAction(env, state);
355 }
356
357
358
359
360
361
362 private void reOpenAllRegionsIfTableIsOnline(final MasterProcedureEnv env) throws IOException {
363
364 if (!env.getMasterServices().getAssignmentManager().getTableStateManager()
365 .isTableState(getTableName(), ZooKeeperProtos.Table.State.ENABLED)) {
366 return;
367 }
368
369 if (MasterDDLOperationHelper.reOpenAllRegions(env, getTableName(), getRegionInfoList(env))) {
370 LOG.info("Completed delete column family operation on table " + getTableName());
371 } else {
372 LOG.warn("Error on reopening the regions on table " + getTableName());
373 }
374 }
375
376
377
378
379
380
381 private Boolean isTraceEnabled() {
382 if (traceEnabled == null) {
383 traceEnabled = LOG.isTraceEnabled();
384 }
385 return traceEnabled;
386 }
387
388 private String getColumnFamilyName() {
389 return Bytes.toString(familyName);
390 }
391
392
393
394
395
396
397
398
399 private void runCoprocessorAction(final MasterProcedureEnv env,
400 final DeleteColumnFamilyState state) throws IOException, InterruptedException {
401 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
402 if (cpHost != null) {
403 user.doAs(new PrivilegedExceptionAction<Void>() {
404 @Override
405 public Void run() throws Exception {
406 switch (state) {
407 case DELETE_COLUMN_FAMILY_PRE_OPERATION:
408 cpHost.preDeleteColumnHandler(tableName, familyName);
409 break;
410 case DELETE_COLUMN_FAMILY_POST_OPERATION:
411 cpHost.postDeleteColumnHandler(tableName, familyName);
412 break;
413 default:
414 throw new UnsupportedOperationException(this + " unhandled state=" + state);
415 }
416 return null;
417 }
418 });
419 }
420 }
421
422
423
424
425 private boolean isRollbackSupported(final DeleteColumnFamilyState state) {
426 switch (state) {
427 case DELETE_COLUMN_FAMILY_REOPEN_ALL_REGIONS:
428 case DELETE_COLUMN_FAMILY_POST_OPERATION:
429 case DELETE_COLUMN_FAMILY_DELETE_FS_LAYOUT:
430
431 return false;
432 default:
433 break;
434 }
435 return true;
436 }
437
438 private List<HRegionInfo> getRegionInfoList(final MasterProcedureEnv env) throws IOException {
439 if (regionInfoList == null) {
440 regionInfoList = ProcedureSyncWait.getRegionsFromMeta(env, getTableName());
441 }
442 return regionInfoList;
443 }
444 }