View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.master.handler;
20  
21  import java.io.IOException;
22  import java.io.InterruptedIOException;
23  import java.util.List;
24  
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  import org.apache.hadoop.hbase.classification.InterfaceAudience;
28  import org.apache.hadoop.fs.FileSystem;
29  import org.apache.hadoop.fs.Path;
30  import org.apache.hadoop.hbase.HRegionInfo;
31  import org.apache.hadoop.hbase.Server;
32  import org.apache.hadoop.hbase.TableExistsException;
33  import org.apache.hadoop.hbase.TableName;
34  import org.apache.hadoop.hbase.backup.HFileArchiver;
35  import org.apache.hadoop.hbase.catalog.MetaEditor;
36  import org.apache.hadoop.hbase.master.AssignmentManager;
37  import org.apache.hadoop.hbase.master.HMaster;
38  import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
39  import org.apache.hadoop.hbase.master.MasterFileSystem;
40  import org.apache.hadoop.hbase.master.MasterServices;
41  import org.apache.hadoop.hbase.master.RegionState.State;
42  import org.apache.hadoop.hbase.master.RegionStates;
43  import org.apache.hadoop.hbase.regionserver.HRegion;
44  import org.apache.hadoop.hbase.util.FSTableDescriptors;
45  import org.apache.hadoop.hbase.util.FSUtils;
46  import org.apache.hadoop.hbase.util.ModifyRegionUtils;
47  import org.apache.zookeeper.KeeperException;
48  
49  /**
50   * Truncate the table by removing META and the HDFS files and recreating it.
51   * If the 'preserveSplits' option is set to true, the region splits are preserved on recreate.
52   *
53   * If the operation fails in the middle it may require hbck to fix the system state.
54   */
55  @InterfaceAudience.Private
56  public class TruncateTableHandler extends DeleteTableHandler {
57    private static final Log LOG = LogFactory.getLog(TruncateTableHandler.class);
58  
59    private final boolean preserveSplits;
60  
61    public TruncateTableHandler(final TableName tableName, final Server server,
62        final MasterServices masterServices, boolean preserveSplits) {
63      super(tableName, server, masterServices);
64      this.preserveSplits = preserveSplits;
65    }
66  
67    @Override
68    protected void handleTableOperation(List<HRegionInfo> regions)
69        throws IOException, KeeperException {
70      MasterCoprocessorHost cpHost = ((HMaster) this.server).getCoprocessorHost();
71      if (cpHost != null) {
72        cpHost.preTruncateTableHandler(this.tableName);
73      }
74  
75      // 1. Wait because of region in transition
76      waitRegionInTransition(regions);
77  
78      // 2. Remove table from .META. and HDFS
79      removeTableData(regions);
80  
81      // -----------------------------------------------------------------------
82      // PONR: At this point the table is deleted.
83      //       If the recreate fails, the user can only re-create the table.
84      // -----------------------------------------------------------------------
85  
86      // 3. Recreate the regions
87      recreateTable(regions);
88  
89      if (cpHost != null) {
90        cpHost.postTruncateTableHandler(this.tableName);
91      }
92    }
93  
94    private void recreateTable(final List<HRegionInfo> regions) throws IOException {
95      MasterFileSystem mfs = this.masterServices.getMasterFileSystem();
96      Path tempdir = mfs.getTempDir();
97      FileSystem fs = mfs.getFileSystem();
98  
99      AssignmentManager assignmentManager = this.masterServices.getAssignmentManager();
100 
101     // 1. Set table znode
102     checkAndSetEnablingTable(assignmentManager, tableName);
103     try {
104       // 1. Create Table Descriptor
105       Path tempTableDir = FSUtils.getTableDir(tempdir, this.tableName);
106       new FSTableDescriptors(server.getConfiguration())
107         .createTableDescriptorForTableDirectory(tempTableDir, getTableDescriptor(), false);
108       Path tableDir = FSUtils.getTableDir(mfs.getRootDir(), this.tableName);
109 
110       HRegionInfo[] newRegions;
111       if (this.preserveSplits) {
112         newRegions = regions.toArray(new HRegionInfo[regions.size()]);
113         LOG.info("Truncate will preserve " + newRegions.length + " regions");
114       } else {
115         newRegions = new HRegionInfo[1];
116         newRegions[0] = new HRegionInfo(this.tableName, null, null);
117         LOG.info("Truncate will not preserve the regions");
118       }
119 
120       // 2. Create Regions
121       List<HRegionInfo> regionInfos = ModifyRegionUtils.createRegions(
122         masterServices.getConfiguration(), tempdir,
123         getTableDescriptor(), newRegions, null);
124 
125       // 3. Move Table temp directory to the hbase root location
126       if (!fs.rename(tempTableDir, tableDir)) {
127         throw new IOException("Unable to move table from temp=" + tempTableDir +
128           " to hbase root=" + tableDir);
129       }
130 
131       // 4. Add regions to META
132       MetaEditor.addRegionsToMeta(masterServices.getCatalogTracker(), regionInfos);
133 
134       // 5. Trigger immediate assignment of the regions in round-robin fashion
135       ModifyRegionUtils.assignRegions(assignmentManager, regionInfos);
136 
137       // 6. Set table enabled flag up in zk.
138       try {
139         assignmentManager.getZKTable().setEnabledTable(tableName);
140       } catch (KeeperException e) {
141         throw new IOException("Unable to ensure that " + tableName + " will be" +
142           " enabled because of a ZooKeeper issue", e);
143       }
144     } catch (IOException e) {
145       removeEnablingTable(assignmentManager, tableName);
146       throw e;
147     }
148   }
149 
150   void checkAndSetEnablingTable(final AssignmentManager assignmentManager, final TableName tableName)
151       throws IOException {
152     // If we have multiple client threads trying to create the table at the
153     // same time, given the async nature of the operation, the table
154     // could be in a state where hbase:meta table hasn't been updated yet in
155     // the process() function.
156     // Use enabling state to tell if there is already a request for the same
157     // table in progress. This will introduce a new zookeeper call. Given
158     // createTable isn't a frequent operation, that should be ok.
159     // TODO: now that we have table locks, re-evaluate above -- table locks are not enough.
160     // We could have cleared the hbase.rootdir and not zk. How can we detect this case?
161     // Having to clean zk AND hdfs is awkward.
162     try {
163       if (!assignmentManager.getZKTable().checkAndSetEnablingTable(tableName)) {
164         throw new TableExistsException(tableName);
165       }
166     } catch (KeeperException e) {
167       throw new IOException("Unable to ensure that the table will be"
168           + " enabling because of a ZooKeeper issue", e);
169     }
170   }
171 
172   void removeEnablingTable(final AssignmentManager assignmentManager, final TableName tableName) {
173     // Try deleting the enabling node in case of error
174     // If this does not happen then if the client tries to create the table
175     // again with the same Active master
176     // It will block the creation saying TableAlreadyExists.
177     try {
178       assignmentManager.getZKTable().removeEnablingTable(tableName, false);
179     } catch (KeeperException e) {
180       // Keeper exception should not happen here
181       LOG.error("Got a keeper exception while removing the ENABLING table znode " + tableName, e);
182     }
183   }
184 
185   /**
186    * Removes the table from .META. and archives the HDFS files.
187    */
188   void removeTableData(final List<HRegionInfo> regions) throws IOException, KeeperException {
189     // 1. Remove regions from META
190     LOG.debug("Deleting regions from META");
191     MetaEditor.deleteRegions(this.server.getCatalogTracker(), regions);
192 
193     // -----------------------------------------------------------------------
194     // NOTE: At this point we still have data on disk, but nothing in .META.
195     // if the rename below fails, hbck will report an inconsistency.
196     // -----------------------------------------------------------------------
197 
198     // 2. Move the table in /hbase/.tmp
199     MasterFileSystem mfs = this.masterServices.getMasterFileSystem();
200     Path tempTableDir = mfs.moveTableToTemp(tableName);
201 
202     // 3. Archive regions from FS (temp directory)
203     FileSystem fs = mfs.getFileSystem();
204     for (HRegionInfo hri : regions) {
205       LOG.debug("Archiving region " + hri.getRegionNameAsString() + " from FS");
206       HFileArchiver.archiveRegion(fs, mfs.getRootDir(), tempTableDir,
207         HRegion.getRegionDir(tempTableDir, hri.getEncodedName()));
208     }
209 
210     // 4. Delete table directory from FS (temp directory)
211     if (!fs.delete(tempTableDir, true)) {
212       LOG.error("Couldn't delete " + tempTableDir);
213     }
214 
215     LOG.debug("Table '" + tableName + "' archived!");
216   }
217 
218   void waitRegionInTransition(final List<HRegionInfo> regions) throws IOException {
219     AssignmentManager am = this.masterServices.getAssignmentManager();
220     RegionStates states = am.getRegionStates();
221     long waitTime = server.getConfiguration().getLong("hbase.master.wait.on.region", 5 * 60 * 1000);
222     for (HRegionInfo region : regions) {
223       long done = System.currentTimeMillis() + waitTime;
224       while (System.currentTimeMillis() < done) {
225         if (states.isRegionInState(region, State.FAILED_OPEN)) {
226           am.regionOffline(region);
227         }
228         if (!states.isRegionInTransition(region)) {
229           break;
230         }
231         try {
232           Thread.sleep(waitingTimeForEvents);
233         } catch (InterruptedException e) {
234           LOG.warn("Interrupted while sleeping");
235           throw (InterruptedIOException) new InterruptedIOException().initCause(e);
236         }
237         LOG.debug("Waiting on region to clear regions in transition; "
238             + am.getRegionStates().getRegionTransitionState(region));
239       }
240       if (states.isRegionInTransition(region)) {
241         throw new IOException("Waited hbase.master.wait.on.region (" + waitTime
242             + "ms) for region to leave region " + region.getRegionNameAsString()
243             + " in transitions");
244       }
245     }
246   }
247 }