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.client;
21  
22  import java.io.Closeable;
23  import java.io.IOException;
24  import java.util.ArrayList;
25  import java.util.List;
26  import java.util.NavigableMap;
27  import java.util.TreeMap;
28  
29  import org.apache.commons.logging.Log;
30  import org.apache.commons.logging.LogFactory;
31  import org.apache.hadoop.classification.InterfaceAudience;
32  import org.apache.hadoop.conf.Configuration;
33  import org.apache.hadoop.hbase.HConstants;
34  import org.apache.hadoop.hbase.HRegionInfo;
35  import org.apache.hadoop.hbase.ServerName;
36  import org.apache.hadoop.hbase.exceptions.TableNotFoundException;
37  import org.apache.hadoop.hbase.util.Bytes;
38  
39  /**
40   * Scanner class that contains the <code>.META.</code> table scanning logic.
41   * Provided visitors will be called for each row.
42   *
43   * Although public visibility, this is not a public-facing API and may evolve in
44   * minor releases.
45   *
46   * <p> Note that during concurrent region splits, the scanner might not see
47   * META changes across rows (for parent and daughter entries) consistently.
48   * see HBASE-5986, and {@link DefaultMetaScannerVisitor} for details. </p>
49   */
50  @InterfaceAudience.Private
51  public class MetaScanner {
52    private static final Log LOG = LogFactory.getLog(MetaScanner.class);
53    /**
54     * Scans the meta table and calls a visitor on each RowResult and uses a empty
55     * start row value as table name.
56     *
57     * @param configuration conf
58     * @param visitor A custom visitor
59     * @throws IOException e
60     */
61    public static void metaScan(Configuration configuration,
62        MetaScannerVisitor visitor)
63    throws IOException {
64      metaScan(configuration, visitor, null);
65    }
66  
67    /**
68     * Scans the meta table and calls a visitor on each RowResult. Uses a table
69     * name to locate meta regions.
70     *
71     * @param configuration config
72     * @param visitor visitor object
73     * @param userTableName User table name in meta table to start scan at.  Pass
74     * null if not interested in a particular table.
75     * @throws IOException e
76     */
77    public static void metaScan(Configuration configuration,
78        MetaScannerVisitor visitor, byte [] userTableName)
79    throws IOException {
80      metaScan(configuration, visitor, userTableName, null, Integer.MAX_VALUE);
81    }
82  
83    /**
84     * Scans the meta table and calls a visitor on each RowResult. Uses a table
85     * name and a row name to locate meta regions. And it only scans at most
86     * <code>rowLimit</code> of rows.
87     *
88     * @param configuration HBase configuration.
89     * @param visitor Visitor object.
90     * @param userTableName User table name in meta table to start scan at.  Pass
91     * null if not interested in a particular table.
92     * @param row Name of the row at the user table. The scan will start from
93     * the region row where the row resides.
94     * @param rowLimit Max of processed rows. If it is less than 0, it
95     * will be set to default value <code>Integer.MAX_VALUE</code>.
96     * @throws IOException e
97     */
98    public static void metaScan(Configuration configuration,
99        MetaScannerVisitor visitor, byte [] userTableName, byte[] row,
100       int rowLimit)
101   throws IOException {
102     metaScan(configuration, visitor, userTableName, row, rowLimit,
103       HConstants.META_TABLE_NAME);
104   }
105 
106   /**
107    * Scans the meta table and calls a visitor on each RowResult. Uses a table
108    * name and a row name to locate meta regions. And it only scans at most
109    * <code>rowLimit</code> of rows.
110    *
111    * @param configuration HBase configuration.
112    * @param visitor Visitor object. Closes the visitor before returning.
113    * @param tableName User table name in meta table to start scan at.  Pass
114    * null if not interested in a particular table.
115    * @param row Name of the row at the user table. The scan will start from
116    * the region row where the row resides.
117    * @param rowLimit Max of processed rows. If it is less than 0, it
118    * will be set to default value <code>Integer.MAX_VALUE</code>.
119    * @param metaTableName Meta table to scan, root or meta.
120    * @throws IOException e
121    */
122   public static void metaScan(Configuration configuration,
123       final MetaScannerVisitor visitor, final byte[] tableName,
124       final byte[] row, final int rowLimit, final byte[] metaTableName)
125   throws IOException {
126     int rowUpperLimit = rowLimit > 0 ? rowLimit: Integer.MAX_VALUE;
127     HTable metaTable = new HTable(configuration, HConstants.META_TABLE_NAME);
128     // Calculate startrow for scan.
129     byte[] startRow;
130     try {
131       if (row != null) {
132         // Scan starting at a particular row in a particular table
133         byte[] searchRow = HRegionInfo.createRegionName(tableName, row, HConstants.NINES, false);
134         Result startRowResult = metaTable.getRowOrBefore(searchRow, HConstants.CATALOG_FAMILY);
135         if (startRowResult == null) {
136           throw new TableNotFoundException("Cannot find row in .META. for table: " +
137             Bytes.toString(tableName) + ", row=" + Bytes.toStringBinary(searchRow));
138         }
139         HRegionInfo regionInfo = getHRegionInfo(startRowResult);
140         if (regionInfo == null) {
141           throw new IOException("HRegionInfo was null or empty in Meta for " +
142             Bytes.toString(tableName) + ", row=" + Bytes.toStringBinary(searchRow));
143         }
144         byte[] rowBefore = regionInfo.getStartKey();
145         startRow = HRegionInfo.createRegionName(tableName, rowBefore, HConstants.ZEROES, false);
146       } else if (tableName == null || tableName.length == 0) {
147         // Full META scan
148         startRow = HConstants.EMPTY_START_ROW;
149       } else {
150         // Scan META for an entire table
151         startRow = HRegionInfo.createRegionName(tableName, HConstants.EMPTY_START_ROW,
152           HConstants.ZEROES, false);
153       }
154       final Scan scan = new Scan(startRow).addFamily(HConstants.CATALOG_FAMILY);
155       int rows = Math.min(rowLimit, configuration.getInt(HConstants.HBASE_META_SCANNER_CACHING,
156         HConstants.DEFAULT_HBASE_META_SCANNER_CACHING));
157       scan.setCaching(rows);
158       if (LOG.isDebugEnabled()) {
159         LOG.debug("Scanning " + Bytes.toString(metaTableName) + " starting at row=" +
160           Bytes.toStringBinary(startRow) + " for max=" + rowUpperLimit + " with caching=" + rows);
161       }
162       // Run the scan
163       ResultScanner scanner = metaTable.getScanner(scan);
164       Result result = null;
165       int processedRows = 0;
166       while ((result = scanner.next()) != null) {
167         if (visitor != null) {
168           if (!visitor.processRow(result)) break;
169         }
170         processedRows++;
171         if (processedRows >= rowUpperLimit) break;
172       }
173     } finally {
174       if (visitor != null) visitor.close();
175       if (metaTable != null) metaTable.close();
176     }
177   }
178 
179   /**
180    * Returns HRegionInfo object from the column
181    * HConstants.CATALOG_FAMILY:HConstants.REGIONINFO_QUALIFIER of the catalog
182    * table Result.
183    * @param data a Result object from the catalog table scan
184    * @return HRegionInfo or null
185    */
186   public static HRegionInfo getHRegionInfo(Result data) {
187     byte [] bytes =
188       data.getValue(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER);
189     if (bytes == null) return null;
190     HRegionInfo info = HRegionInfo.parseFromOrNull(bytes);
191     if (LOG.isTraceEnabled()) {
192       LOG.trace("Current INFO from scan results = " + info);
193     }
194     return info;
195   }
196 
197   /**
198    * Lists all of the regions currently in META.
199    * @param conf
200    * @return List of all user-space regions.
201    * @throws IOException
202    */
203   public static List<HRegionInfo> listAllRegions(Configuration conf)
204   throws IOException {
205     return listAllRegions(conf, true);
206   }
207 
208   /**
209    * Lists all of the regions currently in META.
210    * @param conf
211    * @param offlined True if we are to include offlined regions, false and we'll
212    * leave out offlined regions from returned list.
213    * @return List of all user-space regions.
214    * @throws IOException
215    */
216   public static List<HRegionInfo> listAllRegions(Configuration conf, final boolean offlined)
217   throws IOException {
218     final List<HRegionInfo> regions = new ArrayList<HRegionInfo>();
219     MetaScannerVisitor visitor = new DefaultMetaScannerVisitor() {
220         @Override
221         public boolean processRowInternal(Result result) throws IOException {
222           if (result == null || result.isEmpty()) {
223             return true;
224           }
225 
226           HRegionInfo regionInfo = getHRegionInfo(result);
227           if (regionInfo == null) {
228             LOG.warn("Null REGIONINFO_QUALIFIER: " + result);
229             return true;
230           }
231 
232           // If region offline AND we are not to include offlined regions, return.
233           if (regionInfo.isOffline() && !offlined) return true;
234           regions.add(regionInfo);
235           return true;
236         }
237     };
238     metaScan(conf, visitor);
239     return regions;
240   }
241 
242   /**
243    * Lists all of the table regions currently in META.
244    * @param conf
245    * @param offlined True if we are to include offlined regions, false and we'll
246    * leave out offlined regions from returned list.
247    * @return Map of all user-space regions to servers
248    * @throws IOException
249    */
250   public static NavigableMap<HRegionInfo, ServerName> allTableRegions(Configuration conf,
251       final byte [] tablename, final boolean offlined) throws IOException {
252     final NavigableMap<HRegionInfo, ServerName> regions =
253       new TreeMap<HRegionInfo, ServerName>();
254     MetaScannerVisitor visitor = new TableMetaScannerVisitor(tablename) {
255       @Override
256       public boolean processRowInternal(Result rowResult) throws IOException {
257         HRegionInfo info = getHRegionInfo(rowResult);
258         ServerName serverName = HRegionInfo.getServerName(rowResult);
259         regions.put(new UnmodifyableHRegionInfo(info), serverName);
260         return true;
261       }
262     };
263     metaScan(conf, visitor, tablename);
264     return regions;
265   }
266 
267   /**
268    * Visitor class called to process each row of the .META. table
269    */
270   public interface MetaScannerVisitor extends Closeable {
271     /**
272      * Visitor method that accepts a RowResult and the meta region location.
273      * Implementations can return false to stop the region's loop if it becomes
274      * unnecessary for some reason.
275      *
276      * @param rowResult result
277      * @return A boolean to know if it should continue to loop in the region
278      * @throws IOException e
279      */
280     public boolean processRow(Result rowResult) throws IOException;
281   }
282 
283   public static abstract class MetaScannerVisitorBase implements MetaScannerVisitor {
284     @Override
285     public void close() throws IOException {
286     }
287   }
288 
289   /**
290    * A MetaScannerVisitor that skips offline regions and split parents
291    */
292   public static abstract class DefaultMetaScannerVisitor
293     extends MetaScannerVisitorBase {
294 
295     public DefaultMetaScannerVisitor() {
296       super();
297     }
298 
299     public abstract boolean processRowInternal(Result rowResult) throws IOException;
300 
301     @Override
302     public boolean processRow(Result rowResult) throws IOException {
303       HRegionInfo info = getHRegionInfo(rowResult);
304       if (info == null) {
305         return true;
306       }
307 
308       //skip over offline and split regions
309       if (!(info.isOffline() || info.isSplit())) {
310         return processRowInternal(rowResult);
311       }
312       return true;
313     }
314   }
315 
316   /**
317    * A MetaScannerVisitor for a table. Provides a consistent view of the table's
318    * META entries during concurrent splits (see HBASE-5986 for details). This class
319    * does not guarantee ordered traversal of meta entries, and can block until the
320    * META entries for daughters are available during splits.
321    */
322   public static abstract class TableMetaScannerVisitor extends DefaultMetaScannerVisitor {
323     private byte[] tableName;
324 
325     public TableMetaScannerVisitor(byte[] tableName) {
326       super();
327       this.tableName = tableName;
328     }
329 
330     @Override
331     public final boolean processRow(Result rowResult) throws IOException {
332       HRegionInfo info = getHRegionInfo(rowResult);
333       if (info == null) {
334         return true;
335       }
336       if (!(Bytes.equals(info.getTableName(), tableName))) {
337         return false;
338       }
339       return super.processRow(rowResult);
340     }
341   }
342 }