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  
20  package org.apache.hadoop.hbase.util;
21  
22  import java.io.IOException;
23  
24  import org.apache.commons.logging.Log;
25  import org.apache.commons.logging.LogFactory;
26  import org.apache.hadoop.hbase.classification.InterfaceAudience;
27  import org.apache.hadoop.conf.Configuration;
28  import org.apache.hadoop.conf.Configured;
29  import org.apache.hadoop.fs.FileSystem;
30  import org.apache.hadoop.fs.Path;
31  import org.apache.hadoop.hbase.HBaseInterfaceAudience;
32  import org.apache.hadoop.hbase.TableDescriptor;
33  import org.apache.hadoop.hbase.TableName;
34  import org.apache.hadoop.hbase.HBaseConfiguration;
35  import org.apache.hadoop.hbase.HConstants;
36  import org.apache.hadoop.hbase.HRegionInfo;
37  import org.apache.hadoop.hbase.HTableDescriptor;
38  import org.apache.hadoop.hbase.MasterNotRunningException;
39  import org.apache.hadoop.hbase.ZooKeeperConnectionException;
40  import org.apache.hadoop.hbase.client.Delete;
41  import org.apache.hadoop.hbase.client.Get;
42  import org.apache.hadoop.hbase.client.HBaseAdmin;
43  import org.apache.hadoop.hbase.client.Result;
44  import org.apache.hadoop.hbase.regionserver.HRegion;
45  import org.apache.hadoop.io.WritableComparator;
46  import org.apache.hadoop.util.Tool;
47  import org.apache.hadoop.util.ToolRunner;
48  
49  import com.google.common.base.Preconditions;
50  
51  /**
52   * Utility that can merge any two regions in the same table: adjacent,
53   * overlapping or disjoint.
54   */
55  @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.TOOLS)
56  public class Merge extends Configured implements Tool {
57    private static final Log LOG = LogFactory.getLog(Merge.class);
58    private Path rootdir;
59    private volatile MetaUtils utils;
60    private TableName tableName;               // Name of table
61    private volatile byte [] region1;        // Name of region 1
62    private volatile byte [] region2;        // Name of region 2
63    private volatile HRegionInfo mergeInfo = null;
64  
65    @Override
66    public int run(String[] args) throws Exception {
67      if (parseArgs(args) != 0) {
68        return -1;
69      }
70  
71      // Verify file system is up.
72      FileSystem fs = FileSystem.get(getConf());              // get DFS handle
73      LOG.info("Verifying that file system is available...");
74      try {
75        FSUtils.checkFileSystemAvailable(fs);
76      } catch (IOException e) {
77        LOG.fatal("File system is not available", e);
78        return -1;
79      }
80  
81      // Verify HBase is down
82      LOG.info("Verifying that HBase is not running...");
83      try {
84        HBaseAdmin.checkHBaseAvailable(getConf());
85        LOG.fatal("HBase cluster must be off-line, and is not. Aborting.");
86        return -1;
87      } catch (ZooKeeperConnectionException zkce) {
88        // If no zk, presume no master.
89      } catch (MasterNotRunningException e) {
90        // Expected. Ignore.
91      }
92  
93      // Initialize MetaUtils and and get the root of the HBase installation
94  
95      this.utils = new MetaUtils(getConf());
96      this.rootdir = FSUtils.getRootDir(getConf());
97      try {
98        mergeTwoRegions();
99        return 0;
100     } catch (IOException e) {
101       LOG.fatal("Merge failed", e);
102       return -1;
103 
104     } finally {
105       if (this.utils != null) {
106         this.utils.shutdown();
107       }
108     }
109   }
110 
111   /** @return HRegionInfo for merge result */
112   HRegionInfo getMergedHRegionInfo() {
113     return this.mergeInfo;
114   }
115 
116   /*
117    * Merges two regions from a user table.
118    */
119   private void mergeTwoRegions() throws IOException {
120     LOG.info("Merging regions " + Bytes.toStringBinary(this.region1) + " and " +
121         Bytes.toStringBinary(this.region2) + " in table " + this.tableName);
122     HRegion meta = this.utils.getMetaRegion();
123     Get get = new Get(region1);
124     get.addColumn(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER);
125     Result result1 =  meta.get(get);
126     Preconditions.checkState(!result1.isEmpty(),
127         "First region cells can not be null");
128     HRegionInfo info1 = HRegionInfo.getHRegionInfo(result1);
129     if (info1 == null) {
130       throw new NullPointerException("info1 is null using key " +
131           Bytes.toStringBinary(region1) + " in " + meta);
132     }
133     get = new Get(region2);
134     get.addColumn(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER);
135     Result result2 =  meta.get(get);
136     Preconditions.checkState(!result2.isEmpty(),
137         "Second region cells can not be null");
138     HRegionInfo info2 = HRegionInfo.getHRegionInfo(result2);
139     if (info2 == null) {
140       throw new NullPointerException("info2 is null using key " + meta);
141     }
142     TableDescriptor htd = FSTableDescriptors.getTableDescriptorFromFs(FileSystem.get(getConf()),
143       this.rootdir, this.tableName);
144     HRegion merged = merge(htd.getHTableDescriptor(), meta, info1, info2);
145 
146     LOG.info("Adding " + merged.getRegionInfo() + " to " +
147         meta.getRegionInfo());
148 
149     HRegion.addRegionToMETA(meta, merged);
150     merged.close();
151   }
152 
153   /*
154    * Actually merge two regions and update their info in the meta region(s)
155    * Returns HRegion object for newly merged region
156    */
157   private HRegion merge(final HTableDescriptor htd, HRegion meta,
158                         HRegionInfo info1, HRegionInfo info2)
159   throws IOException {
160     if (info1 == null) {
161       throw new IOException("Could not find " + Bytes.toStringBinary(region1) + " in " +
162           Bytes.toStringBinary(meta.getRegionInfo().getRegionName()));
163     }
164     if (info2 == null) {
165       throw new IOException("Could not find " + Bytes.toStringBinary(region2) + " in " +
166           Bytes.toStringBinary(meta.getRegionInfo().getRegionName()));
167     }
168     HRegion merged = null;
169     HRegion r1 = HRegion.openHRegion(info1, htd, utils.getLog(info1), getConf());
170     try {
171       HRegion r2 = HRegion.openHRegion(info2, htd, utils.getLog(info2), getConf());
172       try {
173         merged = HRegion.merge(r1, r2);
174       } finally {
175         if (!r2.isClosed()) {
176           r2.close();
177         }
178       }
179     } finally {
180       if (!r1.isClosed()) {
181         r1.close();
182       }
183     }
184 
185     // Remove the old regions from meta.
186     // HRegion.merge has already deleted their files
187 
188     removeRegionFromMeta(meta, info1);
189     removeRegionFromMeta(meta, info2);
190 
191     this.mergeInfo = merged.getRegionInfo();
192     return merged;
193   }
194 
195   /*
196    * Removes a region's meta information from the passed <code>meta</code>
197    * region.
198    *
199    * @param meta hbase:meta HRegion to be updated
200    * @param regioninfo HRegionInfo of region to remove from <code>meta</code>
201    *
202    * @throws IOException
203    */
204   private void removeRegionFromMeta(HRegion meta, HRegionInfo regioninfo)
205   throws IOException {
206     if (LOG.isDebugEnabled()) {
207       LOG.debug("Removing region: " + regioninfo + " from " + meta);
208     }
209 
210     Delete delete  = new Delete(regioninfo.getRegionName(),
211         System.currentTimeMillis());
212     meta.delete(delete);
213   }
214 
215   /**
216    * Parse given arguments and assign table name and regions names.
217    * (generic args are handled by ToolRunner.)
218    *
219    * @param args the arguments to parse
220    *
221    * @throws IOException
222    */
223   private int parseArgs(String[] args) throws IOException {
224     if (args.length != 3) {
225       usage();
226       return -1;
227     }
228     tableName = TableName.valueOf(args[0]);
229 
230     region1 = Bytes.toBytesBinary(args[1]);
231     region2 = Bytes.toBytesBinary(args[2]);
232     int status = 0;
233     if (notInTable(tableName, region1) || notInTable(tableName, region2)) {
234       status = -1;
235     } else if (Bytes.equals(region1, region2)) {
236       LOG.error("Can't merge a region with itself");
237       status = -1;
238     }
239     return status;
240   }
241 
242   private boolean notInTable(final TableName tn, final byte [] rn) {
243     if (WritableComparator.compareBytes(tn.getName(), 0, tn.getName().length,
244         rn, 0, tn.getName().length) != 0) {
245       LOG.error("Region " + Bytes.toStringBinary(rn) + " does not belong to table " +
246         tn);
247       return true;
248     }
249     return false;
250   }
251 
252   private void usage() {
253     System.err
254         .println("For hadoop 0.21+, Usage: bin/hbase org.apache.hadoop.hbase.util.Merge "
255             + "[-Dfs.defaultFS=hdfs://nn:port] <table-name> <region-1> <region-2>\n");
256   }
257 
258   public static void main(String[] args) {
259     int status;
260     try {
261       status = ToolRunner.run(HBaseConfiguration.create(), new Merge(), args);
262     } catch (Exception e) {
263       LOG.error("exiting due to error", e);
264       status = -1;
265     }
266     System.exit(status);
267   }
268 }