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