View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.hadoop.hbase.master.procedure;
20  
21  import java.io.InputStream;
22  import java.io.IOException;
23  import java.io.OutputStream;
24  import java.security.PrivilegedExceptionAction;
25  import java.util.ArrayList;
26  import java.util.Arrays;
27  import java.util.List;
28  
29  import org.apache.commons.logging.Log;
30  import org.apache.commons.logging.LogFactory;
31  import org.apache.hadoop.hbase.classification.InterfaceAudience;
32  import org.apache.hadoop.hbase.TableName;
33  import org.apache.hadoop.hbase.TableNotDisabledException;
34  import org.apache.hadoop.hbase.TableNotFoundException;
35  import org.apache.hadoop.hbase.HRegionInfo;
36  import org.apache.hadoop.hbase.HTableDescriptor;
37  import org.apache.hadoop.hbase.exceptions.HBaseException;
38  import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
39  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos;
40  import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos;
41  import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos.TruncateTableState;
42  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
43  import org.apache.hadoop.hbase.procedure2.StateMachineProcedure;
44  import org.apache.hadoop.hbase.util.ModifyRegionUtils;
45  import org.apache.hadoop.security.UserGroupInformation;
46  
47  @InterfaceAudience.Private
48  public class TruncateTableProcedure
49      extends StateMachineProcedure<MasterProcedureEnv, TruncateTableState>
50      implements TableProcedureInterface {
51    private static final Log LOG = LogFactory.getLog(TruncateTableProcedure.class);
52  
53    private boolean preserveSplits;
54    private List<HRegionInfo> regions;
55    private UserGroupInformation user;
56    private HTableDescriptor hTableDescriptor;
57    private TableName tableName;
58  
59    public TruncateTableProcedure() {
60      // Required by the Procedure framework to create the procedure on replay
61    }
62  
63    public TruncateTableProcedure(final MasterProcedureEnv env, final TableName tableName,
64        boolean preserveSplits) {
65      this.tableName = tableName;
66      this.preserveSplits = preserveSplits;
67      this.user = env.getRequestUser().getUGI();
68      this.setOwner(this.user.getShortUserName());
69    }
70  
71    @Override
72    protected Flow executeFromState(final MasterProcedureEnv env, TruncateTableState state) {
73      if (LOG.isTraceEnabled()) {
74        LOG.trace(this + " execute state=" + state);
75      }
76      try {
77        switch (state) {
78          case TRUNCATE_TABLE_PRE_OPERATION:
79            // Verify if we can truncate the table
80            if (!prepareTruncate(env)) {
81              assert isFailed() : "the truncate should have an exception here";
82              return Flow.NO_MORE_STATE;
83            }
84  
85            // TODO: Move out... in the acquireLock()
86            LOG.debug("waiting for '" + getTableName() + "' regions in transition");
87            regions = ProcedureSyncWait.getRegionsFromMeta(env, getTableName());
88            assert regions != null && !regions.isEmpty() : "unexpected 0 regions";
89            ProcedureSyncWait.waitRegionInTransition(env, regions);
90  
91            // Call coprocessors
92            preTruncate(env);
93  
94            setNextState(TruncateTableState.TRUNCATE_TABLE_REMOVE_FROM_META);
95            break;
96          case TRUNCATE_TABLE_REMOVE_FROM_META:
97            hTableDescriptor = env.getMasterServices().getTableDescriptors().get(tableName);
98            DeleteTableProcedure.deleteFromMeta(env, getTableName(), regions);
99            DeleteTableProcedure.deleteAssignmentState(env, getTableName());
100           setNextState(TruncateTableState.TRUNCATE_TABLE_CLEAR_FS_LAYOUT);
101           break;
102         case TRUNCATE_TABLE_CLEAR_FS_LAYOUT:
103           DeleteTableProcedure.deleteFromFs(env, getTableName(), regions, true);
104           if (!preserveSplits) {
105             // if we are not preserving splits, generate a new single region
106             regions = Arrays.asList(ModifyRegionUtils.createHRegionInfos(hTableDescriptor, null));
107           } else {
108             regions = recreateRegionInfo(regions);
109           }
110           setNextState(TruncateTableState.TRUNCATE_TABLE_CREATE_FS_LAYOUT);
111           break;
112         case TRUNCATE_TABLE_CREATE_FS_LAYOUT:
113           regions = CreateTableProcedure.createFsLayout(env, hTableDescriptor, regions);
114           CreateTableProcedure.updateTableDescCache(env, getTableName());
115           setNextState(TruncateTableState.TRUNCATE_TABLE_ADD_TO_META);
116           break;
117         case TRUNCATE_TABLE_ADD_TO_META:
118           regions = CreateTableProcedure.addTableToMeta(env, hTableDescriptor, regions);
119           setNextState(TruncateTableState.TRUNCATE_TABLE_ASSIGN_REGIONS);
120           break;
121         case TRUNCATE_TABLE_ASSIGN_REGIONS:
122           CreateTableProcedure.assignRegions(env, getTableName(), regions);
123           setNextState(TruncateTableState.TRUNCATE_TABLE_POST_OPERATION);
124           hTableDescriptor = null;
125           regions = null;
126           break;
127         case TRUNCATE_TABLE_POST_OPERATION:
128           postTruncate(env);
129           LOG.debug("truncate '" + getTableName() + "' completed");
130           return Flow.NO_MORE_STATE;
131         default:
132           throw new UnsupportedOperationException("unhandled state=" + state);
133       }
134     } catch (HBaseException|IOException e) {
135       LOG.warn("Retriable error trying to truncate table=" + getTableName() + " state=" + state, e);
136     } catch (InterruptedException e) {
137       // if the interrupt is real, the executor will be stopped.
138       LOG.warn("Interrupted trying to truncate table=" + getTableName() + " state=" + state, e);
139     }
140     return Flow.HAS_MORE_STATE;
141   }
142 
143   @Override
144   protected void rollbackState(final MasterProcedureEnv env, final TruncateTableState state) {
145     if (state == TruncateTableState.TRUNCATE_TABLE_PRE_OPERATION) {
146       // nothing to rollback, pre-truncate is just table-state checks.
147       // We can fail if the table does not exist or is not disabled.
148       return;
149     }
150 
151     // The truncate doesn't have a rollback. The execution will succeed, at some point.
152     throw new UnsupportedOperationException("unhandled state=" + state);
153   }
154 
155   @Override
156   protected TruncateTableState getState(final int stateId) {
157     return TruncateTableState.valueOf(stateId);
158   }
159 
160   @Override
161   protected int getStateId(final TruncateTableState state) {
162     return state.getNumber();
163   }
164 
165   @Override
166   protected TruncateTableState getInitialState() {
167     return TruncateTableState.TRUNCATE_TABLE_PRE_OPERATION;
168   }
169 
170   @Override
171   public TableName getTableName() {
172     return tableName;
173   }
174 
175   @Override
176   public TableOperationType getTableOperationType() {
177     return TableOperationType.EDIT;
178   }
179 
180   @Override
181   public boolean abort(final MasterProcedureEnv env) {
182     // TODO: We may be able to abort if the procedure is not started yet.
183     return false;
184   }
185 
186   @Override
187   protected boolean acquireLock(final MasterProcedureEnv env) {
188     if (!env.isInitialized()) return false;
189     return env.getProcedureQueue().tryAcquireTableWrite(getTableName(), "truncate table");
190   }
191 
192   @Override
193   protected void releaseLock(final MasterProcedureEnv env) {
194     env.getProcedureQueue().releaseTableWrite(getTableName());
195   }
196 
197   @Override
198   public void toStringClassDetails(StringBuilder sb) {
199     sb.append(getClass().getSimpleName());
200     sb.append(" (table=");
201     sb.append(getTableName());
202     sb.append(" preserveSplits=");
203     sb.append(preserveSplits);
204     sb.append(")");
205   }
206 
207   @Override
208   public void serializeStateData(final OutputStream stream) throws IOException {
209     super.serializeStateData(stream);
210 
211     MasterProcedureProtos.TruncateTableStateData.Builder state =
212       MasterProcedureProtos.TruncateTableStateData.newBuilder()
213         .setUserInfo(MasterProcedureUtil.toProtoUserInfo(this.user))
214         .setPreserveSplits(preserveSplits);
215     if (hTableDescriptor != null) {
216       state.setTableSchema(hTableDescriptor.convert());
217     } else {
218       state.setTableName(ProtobufUtil.toProtoTableName(tableName));
219     }
220     if (regions != null) {
221       for (HRegionInfo hri: regions) {
222         state.addRegionInfo(HRegionInfo.convert(hri));
223       }
224     }
225     state.build().writeDelimitedTo(stream);
226   }
227 
228   @Override
229   public void deserializeStateData(final InputStream stream) throws IOException {
230     super.deserializeStateData(stream);
231 
232     MasterProcedureProtos.TruncateTableStateData state =
233       MasterProcedureProtos.TruncateTableStateData.parseDelimitedFrom(stream);
234     user = MasterProcedureUtil.toUserInfo(state.getUserInfo());
235     if (state.hasTableSchema()) {
236       hTableDescriptor = HTableDescriptor.convert(state.getTableSchema());
237       tableName = hTableDescriptor.getTableName();
238     } else {
239       tableName = ProtobufUtil.toTableName(state.getTableName());
240     }
241     preserveSplits = state.getPreserveSplits();
242     if (state.getRegionInfoCount() == 0) {
243       regions = null;
244     } else {
245       regions = new ArrayList<HRegionInfo>(state.getRegionInfoCount());
246       for (HBaseProtos.RegionInfo hri: state.getRegionInfoList()) {
247         regions.add(HRegionInfo.convert(hri));
248       }
249     }
250   }
251 
252   private static List<HRegionInfo> recreateRegionInfo(final List<HRegionInfo> regions) {
253     ArrayList<HRegionInfo> newRegions = new ArrayList<HRegionInfo>(regions.size());
254     for (HRegionInfo hri: regions) {
255       newRegions.add(new HRegionInfo(hri.getTable(), hri.getStartKey(), hri.getEndKey()));
256     }
257     return newRegions;
258   }
259 
260   private boolean prepareTruncate(final MasterProcedureEnv env) throws IOException {
261     try {
262       env.getMasterServices().checkTableModifiable(getTableName());
263     } catch (TableNotFoundException|TableNotDisabledException e) {
264       setFailure("master-truncate-table", e);
265       return false;
266     }
267     return true;
268   }
269 
270   private boolean preTruncate(final MasterProcedureEnv env)
271       throws IOException, InterruptedException {
272     final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
273     if (cpHost != null) {
274       final TableName tableName = getTableName();
275       user.doAs(new PrivilegedExceptionAction<Void>() {
276         @Override
277         public Void run() throws Exception {
278           cpHost.preTruncateTableHandler(tableName);
279           return null;
280         }
281       });
282     }
283     return true;
284   }
285 
286   private void postTruncate(final MasterProcedureEnv env)
287       throws IOException, InterruptedException {
288     final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
289     if (cpHost != null) {
290       final TableName tableName = getTableName();
291       user.doAs(new PrivilegedExceptionAction<Void>() {
292         @Override
293         public Void run() throws Exception {
294           cpHost.postTruncateTableHandler(tableName);
295           return null;
296         }
297       });
298     }
299   }
300 }