View Javadoc

1   /**
2    * Copyright 2010 The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  
21  package org.apache.hadoop.hbase.util;
22  
23  import org.apache.commons.logging.Log;
24  import org.apache.commons.logging.LogFactory;
25  import org.apache.hadoop.conf.Configuration;
26  import org.apache.hadoop.fs.FileSystem;
27  import org.apache.hadoop.fs.Path;
28  import org.apache.hadoop.hbase.HBaseConfiguration;
29  import org.apache.hadoop.hbase.HColumnDescriptor;
30  import org.apache.hadoop.hbase.HConstants;
31  import org.apache.hadoop.hbase.client.Delete;
32  import org.apache.hadoop.hbase.client.Get;
33  import org.apache.hadoop.hbase.client.HTable;
34  import org.apache.hadoop.hbase.client.Put;
35  import org.apache.hadoop.hbase.client.Result;
36  import org.apache.hadoop.hbase.client.Scan;
37  import org.apache.hadoop.hbase.regionserver.HRegion;
38  import org.apache.hadoop.hbase.regionserver.InternalScanner;
39  import org.apache.hadoop.hbase.regionserver.Store;
40  import org.apache.hadoop.hbase.regionserver.wal.HLog;
41  import org.apache.hadoop.hbase.HRegionInfo;
42  import org.apache.hadoop.hbase.KeyValue;
43  
44  import java.io.IOException;
45  import java.util.ArrayList;
46  import java.util.Collections;
47  import java.util.List;
48  import java.util.Map;
49  import java.util.TreeMap;
50  
51  /**
52   * Contains utility methods for manipulating HBase meta tables.
53   * Be sure to call {@link #shutdown()} when done with this class so it closes
54   * resources opened during meta processing (ROOT, META, etc.).  Be careful
55   * how you use this class.  If used during migrations, be careful when using
56   * this class to check whether migration is needed.
57   */
58  public class MetaUtils {
59    private static final Log LOG = LogFactory.getLog(MetaUtils.class);
60    private final Configuration conf;
61    private FileSystem fs;
62    private Path rootdir;
63    private HLog log;
64    private HRegion rootRegion;
65    private Map<byte [], HRegion> metaRegions = Collections.synchronizedSortedMap(
66      new TreeMap<byte [], HRegion>(Bytes.BYTES_COMPARATOR));
67  
68    /** Default constructor
69     * @throws IOException e
70     */
71    public MetaUtils() throws IOException {
72      this(HBaseConfiguration.create());
73    }
74  
75    /**
76     * @param conf Configuration
77     * @throws IOException e
78     */
79    public MetaUtils(Configuration conf) throws IOException {
80      this.conf = conf;
81      conf.setInt("hbase.client.retries.number", 1);
82      this.rootRegion = null;
83      initialize();
84    }
85  
86    /**
87     * Verifies that DFS is available and that HBase is off-line.
88     * @throws IOException e
89     */
90    private void initialize() throws IOException {
91      this.fs = FileSystem.get(this.conf);
92      // Get root directory of HBase installation
93      this.rootdir = FSUtils.getRootDir(this.conf);
94    }
95  
96    /**
97     * @return the HLog
98     * @throws IOException e
99     */
100   public synchronized HLog getLog() throws IOException {
101     if (this.log == null) {
102       Path logdir = new Path(this.fs.getHomeDirectory(),
103           HConstants.HREGION_LOGDIR_NAME + "_" + System.currentTimeMillis());
104       Path oldLogDir = new Path(this.fs.getHomeDirectory(),
105           HConstants.HREGION_OLDLOGDIR_NAME);
106       this.log = new HLog(this.fs, logdir, oldLogDir, this.conf);
107     }
108     return this.log;
109   }
110 
111   /**
112    * @return HRegion for root region
113    * @throws IOException e
114    */
115   public HRegion getRootRegion() throws IOException {
116     if (this.rootRegion == null) {
117       openRootRegion();
118     }
119     return this.rootRegion;
120   }
121 
122   /**
123    * Open or return cached opened meta region
124    *
125    * @param metaInfo HRegionInfo for meta region
126    * @return meta HRegion
127    * @throws IOException e
128    */
129   public HRegion getMetaRegion(HRegionInfo metaInfo) throws IOException {
130     HRegion meta = metaRegions.get(metaInfo.getRegionName());
131     if (meta == null) {
132       meta = openMetaRegion(metaInfo);
133       LOG.info("OPENING META " + meta.toString());
134       this.metaRegions.put(metaInfo.getRegionName(), meta);
135     }
136     return meta;
137   }
138 
139   /**
140    * Closes catalog regions if open. Also closes and deletes the HLog. You
141    * must call this method if you want to persist changes made during a
142    * MetaUtils edit session.
143    */
144   public void shutdown() {
145     if (this.rootRegion != null) {
146       try {
147         this.rootRegion.close();
148       } catch (IOException e) {
149         LOG.error("closing root region", e);
150       } finally {
151         this.rootRegion = null;
152       }
153     }
154     try {
155       for (HRegion r: metaRegions.values()) {
156         LOG.info("CLOSING META " + r.toString());
157         r.close();
158       }
159     } catch (IOException e) {
160       LOG.error("closing meta region", e);
161     } finally {
162       metaRegions.clear();
163     }
164     try {
165       if (this.log != null) {
166         this.log.rollWriter();
167         this.log.closeAndDelete();
168       }
169     } catch (IOException e) {
170       LOG.error("closing HLog", e);
171     } finally {
172       this.log = null;
173     }
174   }
175 
176   /**
177    * Used by scanRootRegion and scanMetaRegion to call back the caller so it
178    * can process the data for a row.
179    */
180   public interface ScannerListener {
181     /**
182      * Callback so client of scanner can process row contents
183      *
184      * @param info HRegionInfo for row
185      * @return false to terminate the scan
186      * @throws IOException e
187      */
188     public boolean processRow(HRegionInfo info) throws IOException;
189   }
190 
191   /**
192    * Scans the root region. For every meta region found, calls the listener with
193    * the HRegionInfo of the meta region.
194    *
195    * @param listener method to be called for each meta region found
196    * @throws IOException e
197    */
198   public void scanRootRegion(ScannerListener listener) throws IOException {
199     // Open root region so we can scan it
200     if (this.rootRegion == null) {
201       openRootRegion();
202     }
203     scanMetaRegion(this.rootRegion, listener);
204   }
205 
206   /**
207    * Scan the passed in metaregion <code>m</code> invoking the passed
208    * <code>listener</code> per row found.
209    * @param r region
210    * @param listener scanner listener
211    * @throws IOException e
212    */
213   public void scanMetaRegion(final HRegion r, final ScannerListener listener)
214   throws IOException {
215     Scan scan = new Scan();
216     scan.addColumn(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER);
217     InternalScanner s = r.getScanner(scan);
218     try {
219       List<KeyValue> results = new ArrayList<KeyValue>();
220       boolean hasNext = true;
221       do {
222         hasNext = s.next(results);
223         HRegionInfo info = null;
224         for (KeyValue kv: results) {
225           info = Writables.getHRegionInfoOrNull(kv.getValue());
226           if (info == null) {
227             LOG.warn("Region info is null for row " +
228               Bytes.toStringBinary(kv.getRow()) + " in table " +
229               r.getTableDesc().getNameAsString());
230           }
231           continue;
232         }
233         if (!listener.processRow(info)) {
234           break;
235         }
236         results.clear();
237       } while (hasNext);
238     } finally {
239       s.close();
240     }
241   }
242 
243   /**
244    * Scans a meta region. For every region found, calls the listener with
245    * the HRegionInfo of the region.
246    * TODO: Use Visitor rather than Listener pattern.  Allow multiple Visitors.
247    * Use this everywhere we scan meta regions: e.g. in metascanners, in close
248    * handling, etc.  Have it pass in the whole row, not just HRegionInfo.
249    * <p>Use for reading meta only.  Does not close region when done.
250    * Use {@link #getMetaRegion(HRegionInfo)} instead if writing.  Adds
251    * meta region to list that will get a close on {@link #shutdown()}.
252    *
253    * @param metaRegionInfo HRegionInfo for meta region
254    * @param listener method to be called for each meta region found
255    * @throws IOException e
256    */
257   public void scanMetaRegion(HRegionInfo metaRegionInfo,
258     ScannerListener listener)
259   throws IOException {
260     // Open meta region so we can scan it
261     HRegion metaRegion = openMetaRegion(metaRegionInfo);
262     scanMetaRegion(metaRegion, listener);
263   }
264 
265   private synchronized HRegion openRootRegion() throws IOException {
266     if (this.rootRegion != null) {
267       return this.rootRegion;
268     }
269     this.rootRegion = HRegion.openHRegion(HRegionInfo.ROOT_REGIONINFO, getLog(),
270       this.conf);
271     this.rootRegion.compactStores();
272     return this.rootRegion;
273   }
274 
275   private HRegion openMetaRegion(HRegionInfo metaInfo) throws IOException {
276     HRegion meta = HRegion.openHRegion(metaInfo, getLog(), this.conf);
277     meta.compactStores();
278     return meta;
279   }
280 
281   /**
282    * Set a single region on/offline.
283    * This is a tool to repair tables that have offlined tables in their midst.
284    * Can happen on occasion.  Use at your own risk.  Call from a bit of java
285    * or jython script.  This method is 'expensive' in that it creates a
286    * {@link HTable} instance per invocation to go against <code>.META.</code>
287    * @param c A configuration that has its <code>hbase.master</code>
288    * properly set.
289    * @param row Row in the catalog .META. table whose HRegionInfo's offline
290    * status we want to change.
291    * @param onlineOffline Pass <code>true</code> to OFFLINE the region.
292    * @throws IOException e
293    */
294   public static void changeOnlineStatus (final Configuration c,
295       final byte [] row, final boolean onlineOffline)
296   throws IOException {
297     HTable t = new HTable(c, HConstants.META_TABLE_NAME);
298     Get get = new Get(row);
299     get.addColumn(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER);
300     Result res = t.get(get);
301     KeyValue [] kvs = res.raw();
302     if(kvs.length <= 0) {
303       throw new IOException("no information for row " + Bytes.toString(row));
304     }
305     byte [] value = kvs[0].getValue();
306     if (value == null) {
307       throw new IOException("no information for row " + Bytes.toString(row));
308     }
309     HRegionInfo info = Writables.getHRegionInfo(value);
310     Put put = new Put(row);
311     info.setOffline(onlineOffline);
312     put.add(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER,
313         Writables.getBytes(info));
314     t.put(put);
315 
316     Delete delete = new Delete(row);
317     delete.deleteColumns(HConstants.CATALOG_FAMILY, HConstants.SERVER_QUALIFIER);
318     delete.deleteColumns(HConstants.CATALOG_FAMILY,
319         HConstants.STARTCODE_QUALIFIER);
320 
321     t.delete(delete);
322   }
323 
324   /**
325    * Offline version of the online TableOperation,
326    * org.apache.hadoop.hbase.master.AddColumn.
327    * @param tableName table name
328    * @param hcd Add this column to <code>tableName</code>
329    * @throws IOException e
330    */
331   public void addColumn(final byte [] tableName,
332       final HColumnDescriptor hcd)
333   throws IOException {
334     List<HRegionInfo> metas = getMETARows(tableName);
335     for (HRegionInfo hri: metas) {
336       final HRegion m = getMetaRegion(hri);
337       scanMetaRegion(m, new ScannerListener() {
338         private boolean inTable = true;
339 
340         @SuppressWarnings("synthetic-access")
341         public boolean processRow(HRegionInfo info) throws IOException {
342           LOG.debug("Testing " + Bytes.toString(tableName) + " against " +
343             Bytes.toString(info.getTableDesc().getName()));
344           if (Bytes.equals(info.getTableDesc().getName(), tableName)) {
345             this.inTable = false;
346             info.getTableDesc().addFamily(hcd);
347             updateMETARegionInfo(m, info);
348             return true;
349           }
350           // If we got here and we have not yet encountered the table yet,
351           // inTable will be false.  Otherwise, we've passed out the table.
352           // Stop the scanner.
353           return this.inTable;
354         }});
355     }
356   }
357 
358   /**
359    * Offline version of the online TableOperation,
360    * org.apache.hadoop.hbase.master.DeleteColumn.
361    * @param tableName table name
362    * @param columnFamily Name of column name to remove.
363    * @throws IOException e
364    */
365   public void deleteColumn(final byte [] tableName,
366       final byte [] columnFamily) throws IOException {
367     List<HRegionInfo> metas = getMETARows(tableName);
368     for (HRegionInfo hri: metas) {
369       final HRegion m = getMetaRegion(hri);
370       scanMetaRegion(m, new ScannerListener() {
371         private boolean inTable = true;
372 
373         @SuppressWarnings("synthetic-access")
374         public boolean processRow(HRegionInfo info) throws IOException {
375           if (Bytes.equals(info.getTableDesc().getName(), tableName)) {
376             this.inTable = false;
377             info.getTableDesc().removeFamily(columnFamily);
378             updateMETARegionInfo(m, info);
379             Path tabledir = new Path(rootdir,
380               info.getTableDesc().getNameAsString());
381             Path p = Store.getStoreHomedir(tabledir, info.getEncodedName(),
382               columnFamily);
383             if (!fs.delete(p, true)) {
384               LOG.warn("Failed delete of " + p);
385             }
386             return false;
387           }
388           // If we got here and we have not yet encountered the table yet,
389           // inTable will be false.  Otherwise, we've passed out the table.
390           // Stop the scanner.
391           return this.inTable;
392         }});
393     }
394   }
395 
396   /**
397    * Update COL_REGIONINFO in meta region r with HRegionInfo hri
398    *
399    * @param r region
400    * @param hri region info
401    * @throws IOException e
402    */
403   public void updateMETARegionInfo(HRegion r, final HRegionInfo hri)
404   throws IOException {
405     if (LOG.isDebugEnabled()) {
406       Get get = new Get(hri.getRegionName());
407       get.addColumn(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER);
408       Result res = r.get(get, null);
409       KeyValue [] kvs = res.raw();
410       if(kvs.length <= 0) {
411         return;
412       }
413       byte [] value = kvs[0].getValue();
414       if (value == null) {
415         return;
416       }
417       HRegionInfo h = Writables.getHRegionInfoOrNull(value);
418 
419       LOG.debug("Old " + Bytes.toString(HConstants.CATALOG_FAMILY) + ":" +
420           Bytes.toString(HConstants.REGIONINFO_QUALIFIER) + " for " +
421           hri.toString() + " in " + r.toString() + " is: " + h.toString());
422     }
423 
424     Put put = new Put(hri.getRegionName());
425     put.add(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER,
426         Writables.getBytes(hri));
427     r.put(put);
428 
429     if (LOG.isDebugEnabled()) {
430       Get get = new Get(hri.getRegionName());
431       get.addColumn(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER);
432       Result res = r.get(get, null);
433       KeyValue [] kvs = res.raw();
434       if(kvs.length <= 0) {
435         return;
436       }
437       byte [] value = kvs[0].getValue();
438       if (value == null) {
439         return;
440       }
441       HRegionInfo h = Writables.getHRegionInfoOrNull(value);
442         LOG.debug("New " + Bytes.toString(HConstants.CATALOG_FAMILY) + ":" +
443             Bytes.toString(HConstants.REGIONINFO_QUALIFIER) + " for " +
444             hri.toString() + " in " + r.toString() + " is: " +  h.toString());
445     }
446   }
447 
448   /**
449    * @return List of {@link HRegionInfo} rows found in the ROOT or META
450    * catalog table.
451    * @param tableName Name of table to go looking for.
452    * @throws IOException e
453    * @see #getMetaRegion(HRegionInfo)
454    */
455   public List<HRegionInfo> getMETARows(final byte [] tableName)
456   throws IOException {
457     final List<HRegionInfo> result = new ArrayList<HRegionInfo>();
458     // If passed table name is META, then  return the root region.
459     if (Bytes.equals(HConstants.META_TABLE_NAME, tableName)) {
460       result.add(openRootRegion().getRegionInfo());
461       return result;
462     }
463     // Return all meta regions that contain the passed tablename.
464     scanRootRegion(new ScannerListener() {
465       private final Log SL_LOG = LogFactory.getLog(this.getClass());
466 
467       public boolean processRow(HRegionInfo info) throws IOException {
468         SL_LOG.debug("Testing " + info);
469         if (Bytes.equals(info.getTableDesc().getName(),
470             HConstants.META_TABLE_NAME)) {
471           result.add(info);
472           return false;
473         }
474         return true;
475       }});
476     return result;
477   }
478 }