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 com.google.common.annotations.VisibleForTesting;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.io.OutputStream;
25  import java.security.PrivilegedExceptionAction;
26  import java.util.ArrayList;
27  import java.util.Arrays;
28  import java.util.List;
29  import org.apache.commons.logging.Log;
30  import org.apache.commons.logging.LogFactory;
31  import org.apache.hadoop.hbase.HRegionInfo;
32  import org.apache.hadoop.hbase.HTableDescriptor;
33  import org.apache.hadoop.hbase.TableName;
34  import org.apache.hadoop.hbase.TableNotDisabledException;
35  import org.apache.hadoop.hbase.TableNotFoundException;
36  import org.apache.hadoop.hbase.classification.InterfaceAudience;
37  import org.apache.hadoop.hbase.exceptions.HBaseException;
38  import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
39  import org.apache.hadoop.hbase.procedure2.StateMachineProcedure;
40  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
41  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos;
42  import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos;
43  import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos.TruncateTableState;
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        throws InterruptedException {
74      if (LOG.isTraceEnabled()) {
75        LOG.trace(this + " execute state=" + state);
76      }
77      try {
78        switch (state) {
79          case TRUNCATE_TABLE_PRE_OPERATION:
80            // Verify if we can truncate the table
81            if (!prepareTruncate(env)) {
82              assert isFailed() : "the truncate should have an exception here";
83              return Flow.NO_MORE_STATE;
84            }
85  
86            // TODO: Move out... in the acquireLock()
87            LOG.debug("waiting for '" + getTableName() + "' regions in transition");
88            regions = ProcedureSyncWait.getRegionsFromMeta(env, getTableName(), true, true);
89            assert regions != null && !regions.isEmpty() : "unexpected 0 regions";
90            ProcedureSyncWait.waitRegionInTransition(env, regions);
91  
92            // Call coprocessors
93            preTruncate(env);
94  
95            setNextState(TruncateTableState.TRUNCATE_TABLE_REMOVE_FROM_META);
96            break;
97          case TRUNCATE_TABLE_REMOVE_FROM_META:
98            hTableDescriptor = env.getMasterServices().getTableDescriptors().get(tableName);
99            DeleteTableProcedure.deleteFromMeta(env, getTableName(), regions);
100           DeleteTableProcedure.deleteAssignmentState(env, getTableName());
101           setNextState(TruncateTableState.TRUNCATE_TABLE_CLEAR_FS_LAYOUT);
102           break;
103         case TRUNCATE_TABLE_CLEAR_FS_LAYOUT:
104           DeleteTableProcedure.deleteFromFs(env, getTableName(), regions, true);
105           setNextState(TruncateTableState.TRUNCATE_TABLE_CREATE_FS_LAYOUT);
106           if (!preserveSplits) {
107             // if we are not preserving splits, generate a new single region
108             regions = Arrays.asList(ModifyRegionUtils.createHRegionInfos(hTableDescriptor, null));
109           } else {
110             regions = recreateRegionInfo(regions);
111           }
112           break;
113         case TRUNCATE_TABLE_CREATE_FS_LAYOUT:
114           DeleteTableProcedure.deleteFromFs(env, getTableName(), regions, true);
115           regions = CreateTableProcedure.createFsLayout(env, hTableDescriptor, regions);
116           CreateTableProcedure.updateTableDescCache(env, getTableName());
117           setNextState(TruncateTableState.TRUNCATE_TABLE_ADD_TO_META);
118           break;
119         case TRUNCATE_TABLE_ADD_TO_META:
120           regions = CreateTableProcedure.addTableToMeta(env, hTableDescriptor, regions);
121           setNextState(TruncateTableState.TRUNCATE_TABLE_ASSIGN_REGIONS);
122           break;
123         case TRUNCATE_TABLE_ASSIGN_REGIONS:
124           CreateTableProcedure.assignRegions(env, getTableName(), regions);
125           setNextState(TruncateTableState.TRUNCATE_TABLE_POST_OPERATION);
126           hTableDescriptor = null;
127           regions = null;
128           break;
129         case TRUNCATE_TABLE_POST_OPERATION:
130           postTruncate(env);
131           LOG.debug("truncate '" + getTableName() + "' completed");
132           return Flow.NO_MORE_STATE;
133         default:
134           throw new UnsupportedOperationException("unhandled state=" + state);
135       }
136     } catch (HBaseException|IOException e) {
137       LOG.warn("Retriable error trying to truncate table=" + getTableName() + " state=" + state, e);
138     }
139     return Flow.HAS_MORE_STATE;
140   }
141 
142   @Override
143   protected void rollbackState(final MasterProcedureEnv env, final TruncateTableState state) {
144     if (state == TruncateTableState.TRUNCATE_TABLE_PRE_OPERATION) {
145       // nothing to rollback, pre-truncate is just table-state checks.
146       // We can fail if the table does not exist or is not disabled.
147       return;
148     }
149 
150     // The truncate doesn't have a rollback. The execution will succeed, at some point.
151     throw new UnsupportedOperationException("unhandled state=" + state);
152   }
153 
154   @Override
155   protected TruncateTableState getState(final int stateId) {
156     return TruncateTableState.valueOf(stateId);
157   }
158 
159   @Override
160   protected int getStateId(final TruncateTableState state) {
161     return state.getNumber();
162   }
163 
164   @Override
165   protected TruncateTableState getInitialState() {
166     return TruncateTableState.TRUNCATE_TABLE_PRE_OPERATION;
167   }
168 
169   @Override
170   public TableName getTableName() {
171     return tableName;
172   }
173 
174   @Override
175   public TableOperationType getTableOperationType() {
176     return TableOperationType.EDIT;
177   }
178 
179   @Override
180   public boolean abort(final MasterProcedureEnv env) {
181     // TODO: We may be able to abort if the procedure is not started yet.
182     return false;
183   }
184 
185   @Override
186   protected boolean acquireLock(final MasterProcedureEnv env) {
187     if (env.waitInitialized(this)) {
188       return false;
189     }
190     return env.getProcedureQueue().tryAcquireTableExclusiveLock(this, getTableName());
191   }
192 
193   @Override
194   protected void releaseLock(final MasterProcedureEnv env) {
195     env.getProcedureQueue().releaseTableExclusiveLock(this, getTableName());
196   }
197 
198   @Override
199   public void toStringClassDetails(StringBuilder sb) {
200     sb.append(getClass().getSimpleName());
201     sb.append(" (table=");
202     sb.append(getTableName());
203     sb.append(" preserveSplits=");
204     sb.append(preserveSplits);
205     sb.append(")");
206   }
207 
208   @Override
209   public void serializeStateData(final OutputStream stream) throws IOException {
210     super.serializeStateData(stream);
211 
212     MasterProcedureProtos.TruncateTableStateData.Builder state =
213       MasterProcedureProtos.TruncateTableStateData.newBuilder()
214         .setUserInfo(MasterProcedureUtil.toProtoUserInfo(this.user))
215         .setPreserveSplits(preserveSplits);
216     if (hTableDescriptor != null) {
217       state.setTableSchema(hTableDescriptor.convert());
218     } else {
219       state.setTableName(ProtobufUtil.toProtoTableName(tableName));
220     }
221     if (regions != null) {
222       for (HRegionInfo hri: regions) {
223         state.addRegionInfo(HRegionInfo.convert(hri));
224       }
225     }
226     state.build().writeDelimitedTo(stream);
227   }
228 
229   @Override
230   public void deserializeStateData(final InputStream stream) throws IOException {
231     super.deserializeStateData(stream);
232 
233     MasterProcedureProtos.TruncateTableStateData state =
234       MasterProcedureProtos.TruncateTableStateData.parseDelimitedFrom(stream);
235     user = MasterProcedureUtil.toUserInfo(state.getUserInfo());
236     if (state.hasTableSchema()) {
237       hTableDescriptor = HTableDescriptor.convert(state.getTableSchema());
238       tableName = hTableDescriptor.getTableName();
239     } else {
240       tableName = ProtobufUtil.toTableName(state.getTableName());
241     }
242     preserveSplits = state.getPreserveSplits();
243     if (state.getRegionInfoCount() == 0) {
244       regions = null;
245     } else {
246       regions = new ArrayList<HRegionInfo>(state.getRegionInfoCount());
247       for (HBaseProtos.RegionInfo hri: state.getRegionInfoList()) {
248         regions.add(HRegionInfo.convert(hri));
249       }
250     }
251   }
252 
253   private static List<HRegionInfo> recreateRegionInfo(final List<HRegionInfo> regions) {
254     ArrayList<HRegionInfo> newRegions = new ArrayList<HRegionInfo>(regions.size());
255     for (HRegionInfo hri: regions) {
256       newRegions.add(new HRegionInfo(hri.getTable(), hri.getStartKey(), hri.getEndKey()));
257     }
258     return newRegions;
259   }
260 
261   private boolean prepareTruncate(final MasterProcedureEnv env) throws IOException {
262     try {
263       env.getMasterServices().checkTableModifiable(getTableName());
264     } catch (TableNotFoundException|TableNotDisabledException e) {
265       setFailure("master-truncate-table", e);
266       return false;
267     }
268     return true;
269   }
270 
271   private boolean preTruncate(final MasterProcedureEnv env)
272       throws IOException, InterruptedException {
273     final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
274     if (cpHost != null) {
275       final TableName tableName = getTableName();
276       user.doAs(new PrivilegedExceptionAction<Void>() {
277         @Override
278         public Void run() throws Exception {
279           cpHost.preTruncateTableHandler(tableName);
280           return null;
281         }
282       });
283     }
284     return true;
285   }
286 
287   private void postTruncate(final MasterProcedureEnv env)
288       throws IOException, InterruptedException {
289     final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
290     if (cpHost != null) {
291       final TableName tableName = getTableName();
292       user.doAs(new PrivilegedExceptionAction<Void>() {
293         @Override
294         public Void run() throws Exception {
295           cpHost.postTruncateTableHandler(tableName);
296           return null;
297         }
298       });
299     }
300   }
301 
302   @VisibleForTesting
303   HRegionInfo getFirstRegionInfo() {
304     if (regions == null || regions.isEmpty()) {
305       return null;
306     }
307     return regions.get(0);
308   }
309 }