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