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