/** * Copyright 2010 The Apache Software Foundation * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.hadoop.hbase.catalog; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.NavigableMap; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.HRegionInfo; import org.apache.hadoop.hbase.HServerAddress; import org.apache.hadoop.hbase.HServerInfo; import org.apache.hadoop.hbase.HTableDescriptor; import org.apache.hadoop.hbase.NotAllMetaRegionsOnlineException; import org.apache.hadoop.hbase.NotServingRegionException; import org.apache.hadoop.hbase.client.Get; import org.apache.hadoop.hbase.client.Result; import org.apache.hadoop.hbase.client.Scan; import org.apache.hadoop.hbase.ipc.HRegionInterface; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.Pair; import org.apache.hadoop.hbase.util.Writables; import org.apache.hadoop.ipc.RemoteException; /** * Reads region and assignment information from <code>.META.</code>. * <p> * Uses the {@link CatalogTracker} to obtain locations and connections to * catalogs. */ public class MetaReader { public static final byte [] META_REGION_PREFIX; static { // Copy the prefix from FIRST_META_REGIONINFO into META_REGION_PREFIX. // FIRST_META_REGIONINFO == '.META.,,1'. META_REGION_PREFIX == '.META.,' int len = HRegionInfo.FIRST_META_REGIONINFO.getRegionName().length - 2; META_REGION_PREFIX = new byte [len]; System.arraycopy(HRegionInfo.FIRST_META_REGIONINFO.getRegionName(), 0, META_REGION_PREFIX, 0, len); } /** * @param ct * @param tableName A user tablename or a .META. table name. * @return Interface on to server hosting the <code>-ROOT-</code> or * <code>.META.</code> regions. * @throws NotAllMetaRegionsOnlineException * @throws IOException */ private static HRegionInterface getCatalogRegionInterface(final CatalogTracker ct, final byte [] tableName) throws NotAllMetaRegionsOnlineException, IOException { return Bytes.equals(HConstants.META_TABLE_NAME, tableName)? ct.waitForRootServerConnectionDefault(): ct.waitForMetaServerConnectionDefault(); } /** * @param tableName * @return Returns region name to look in for regions for <code>tableName</code>; * e.g. if we are looking for <code>.META.</code> regions, we need to look * in the <code>-ROOT-</code> region, else if a user table, we need to look * in the <code>.META.</code> region. */ private static byte [] getCatalogRegionNameForTable(final byte [] tableName) { return Bytes.equals(HConstants.META_TABLE_NAME, tableName)? HRegionInfo.ROOT_REGIONINFO.getRegionName(): HRegionInfo.FIRST_META_REGIONINFO.getRegionName(); } /** * @param regionName * @return Returns region name to look in for <code>regionName</code>; * e.g. if we are looking for <code>.META.,,1</code> region, we need to look * in <code>-ROOT-</code> region, else if a user region, we need to look * in the <code>.META.,,1</code> region. */ private static byte [] getCatalogRegionNameForRegion(final byte [] regionName) { return isMetaRegion(regionName)? HRegionInfo.ROOT_REGIONINFO.getRegionName(): HRegionInfo.FIRST_META_REGIONINFO.getRegionName(); } /** * @param regionName * @return True if <code>regionName</code> is from <code>.META.</code> table. */ private static boolean isMetaRegion(final byte [] regionName) { if (regionName.length < META_REGION_PREFIX.length + 2 /* ',', + '1' */) { // Can't be meta table region. return false; } // Compare the prefix of regionName. If it matches META_REGION_PREFIX prefix, // then this is region from .META. table. return Bytes.compareTo(regionName, 0, META_REGION_PREFIX.length, META_REGION_PREFIX, 0, META_REGION_PREFIX.length) == 0; } /** * Performs a full scan of <code>.META.</code>. * <p> * Returns a map of every region to it's currently assigned server, according * to META. If the region does not have an assignment it will have a null * value in the map. * * @return map of regions to their currently assigned server * @throws IOException */ public static Map<HRegionInfo,HServerAddress> fullScan( CatalogTracker catalogTracker) throws IOException { return fullScan(catalogTracker, new TreeSet<String>()); } /** * Performs a full scan of <code>.META.</code>, skipping regions from any * tables in the specified set of disabled tables. * <p> * Returns a map of every region to it's currently assigned server, according * to META. If the region does not have an assignment it will have a null * value in the map. * * @param catalogTracker * @param disabledTables set of disabled tables that will not be returned * @return map of regions to their currently assigned server * @throws IOException */ public static Map<HRegionInfo,HServerAddress> fullScan( CatalogTracker catalogTracker, final Set<String> disabledTables) throws IOException { return fullScan(catalogTracker, disabledTables, false); } /** * Performs a full scan of <code>.META.</code>, skipping regions from any * tables in the specified set of disabled tables. * <p> * Returns a map of every region to it's currently assigned server, according * to META. If the region does not have an assignment it will have a null * value in the map. * * @param catalogTracker * @param disabledTables set of disabled tables that will not be returned * @param excludeOfflinedSplitParents If true, do not include offlined split * parents in the return. * @return map of regions to their currently assigned server * @throws IOException */ public static Map<HRegionInfo,HServerAddress> fullScan( CatalogTracker catalogTracker, final Set<String> disabledTables, final boolean excludeOfflinedSplitParents) throws IOException { final Map<HRegionInfo,HServerAddress> regions = new TreeMap<HRegionInfo,HServerAddress>(); Visitor v = new Visitor() { @Override public boolean visit(Result r) throws IOException { if (r == null || r.isEmpty()) return true; Pair<HRegionInfo,HServerAddress> region = metaRowToRegionPair(r); if (region == null) return true; HRegionInfo hri = region.getFirst(); if (disabledTables.contains( hri.getTableDesc().getNameAsString())) return true; // Are we to include split parents in the list? if (excludeOfflinedSplitParents && hri.isSplitParent()) return true; regions.put(hri, region.getSecond()); return true; } }; fullScan(catalogTracker, v); return regions; } /** * Performs a full scan of <code>.META.</code>. * <p> * Returns a map of every region to it's currently assigned server, according * to META. If the region does not have an assignment it will have a null * value in the map. * <p> * Returns HServerInfo which includes server startcode. * * @return map of regions to their currently assigned server * @throws IOException */ public static List<Result> fullScanOfResults( CatalogTracker catalogTracker) throws IOException { final List<Result> regions = new ArrayList<Result>(); Visitor v = new Visitor() { @Override public boolean visit(Result r) throws IOException { if (r == null || r.isEmpty()) return true; regions.add(r); return true; } }; fullScan(catalogTracker, v); return regions; } /** * Performs a full scan of <code>.META.</code>. * <p> * Returns a map of every region to it's currently assigned server, according * to META. If the region does not have an assignment it will have a null * value in the map. * @param catalogTracker * @param visitor * @throws IOException */ public static void fullScan(CatalogTracker catalogTracker, final Visitor visitor) throws IOException { HRegionInterface metaServer = catalogTracker.waitForMetaServerConnectionDefault(); Scan scan = new Scan(); scan.addFamily(HConstants.CATALOG_FAMILY); long scannerid = metaServer.openScanner( HRegionInfo.FIRST_META_REGIONINFO.getRegionName(), scan); try { Result data; while((data = metaServer.next(scannerid)) != null) { if (!data.isEmpty()) visitor.visit(data); } } finally { metaServer.close(scannerid); } return; } /** * Reads the location of META from ROOT. * @param metaServer connection to server hosting ROOT * @return location of META in ROOT, null if not available * @throws IOException */ public static HServerAddress readMetaLocation(HRegionInterface metaServer) throws IOException { return readLocation(metaServer, CatalogTracker.ROOT_REGION, CatalogTracker.META_REGION); } /** * Reads the location of the specified region from META. * @param catalogTracker * @param regionName region to read location of * @return location of region in META, null if not available * @throws IOException */ public static HServerAddress readRegionLocation(CatalogTracker catalogTracker, byte [] regionName) throws IOException { if (isMetaRegion(regionName)) throw new IllegalArgumentException("See readMetaLocation"); return readLocation(catalogTracker.waitForMetaServerConnectionDefault(), CatalogTracker.META_REGION, regionName); } private static HServerAddress readLocation(HRegionInterface metaServer, byte [] catalogRegionName, byte [] regionName) throws IOException { Result r = null; try { r = metaServer.get(catalogRegionName, new Get(regionName).addColumn(HConstants.CATALOG_FAMILY, HConstants.SERVER_QUALIFIER)); } catch (java.net.SocketTimeoutException e) { // Treat this exception + message as unavailable catalog table. Catch it // and fall through to return a null } catch (java.net.ConnectException e) { if (e.getMessage() != null && e.getMessage().contains("Connection refused")) { // Treat this exception + message as unavailable catalog table. Catch it // and fall through to return a null } else { throw e; } } catch (RemoteException re) { IOException ioe = re.unwrapRemoteException(); if (ioe instanceof NotServingRegionException) { // Treat this NSRE as unavailable table. Catch and fall through to // return null below } else if (ioe.getMessage().contains("Server not running")) { // Treat as unavailable table. } else { throw re; } } catch (IOException e) { if (e.getCause() != null && e.getCause() instanceof IOException && e.getCause().getMessage() != null && e.getCause().getMessage().contains("Connection reset by peer")) { // Treat this exception + message as unavailable catalog table. Catch it // and fall through to return a null } else { throw e; } } if (r == null || r.isEmpty()) { return null; } byte [] value = r.getValue(HConstants.CATALOG_FAMILY, HConstants.SERVER_QUALIFIER); return new HServerAddress(Bytes.toString(value)); } /** * Gets the region info and assignment for the specified region from META. * @param catalogTracker * @param regionName * @return region info and assignment from META, null if not available * @throws IOException */ public static Pair<HRegionInfo, HServerAddress> getRegion( CatalogTracker catalogTracker, byte [] regionName) throws IOException { Get get = new Get(regionName); get.addFamily(HConstants.CATALOG_FAMILY); byte [] meta = getCatalogRegionNameForRegion(regionName); Result r = catalogTracker.waitForMetaServerConnectionDefault().get(meta, get); if(r == null || r.isEmpty()) { return null; } return metaRowToRegionPair(r); } /** * @param data A .META. table row. * @return A pair of the regioninfo and the server address from <code>data</code> * or null for server address if no address set in .META. or null for a result * if no HRegionInfo found. * @throws IOException */ public static Pair<HRegionInfo, HServerAddress> metaRowToRegionPair( Result data) throws IOException { byte [] bytes = data.getValue(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER); if (bytes == null) return null; HRegionInfo info = Writables.getHRegionInfo(bytes); final byte[] value = data.getValue(HConstants.CATALOG_FAMILY, HConstants.SERVER_QUALIFIER); if (value != null && value.length > 0) { HServerAddress server = new HServerAddress(Bytes.toString(value)); return new Pair<HRegionInfo,HServerAddress>(info, server); } else { return new Pair<HRegionInfo, HServerAddress>(info, null); } } /** * @param data A .META. table row. * @return A pair of the regioninfo and the server info from <code>data</code> * (or null for server address if no address set in .META.). * @throws IOException */ public static Pair<HRegionInfo, HServerInfo> metaRowToRegionPairWithInfo( Result data) throws IOException { byte [] bytes = data.getValue(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER); if (bytes == null) return null; HRegionInfo info = Writables.getHRegionInfo(bytes); final byte[] value = data.getValue(HConstants.CATALOG_FAMILY, HConstants.SERVER_QUALIFIER); if (value != null && value.length > 0) { final long startCode = Bytes.toLong(data.getValue(HConstants.CATALOG_FAMILY, HConstants.STARTCODE_QUALIFIER)); HServerAddress server = new HServerAddress(Bytes.toString(value)); HServerInfo hsi = new HServerInfo(server, startCode, 0, server.getHostname()); return new Pair<HRegionInfo,HServerInfo>(info, hsi); } else { return new Pair<HRegionInfo, HServerInfo>(info, null); } } /** * Checks if the specified table exists. Looks at the META table hosted on * the specified server. * @param catalogTracker * @param tableName table to check * @return true if the table exists in meta, false if not * @throws IOException */ public static boolean tableExists(CatalogTracker catalogTracker, String tableName) throws IOException { if (tableName.equals(HTableDescriptor.ROOT_TABLEDESC.getNameAsString()) || tableName.equals(HTableDescriptor.META_TABLEDESC.getNameAsString())) { // Catalog tables always exist. return true; } HRegionInterface metaServer = catalogTracker.waitForMetaServerConnectionDefault(); byte[] firstRowInTable = Bytes.toBytes(tableName + ",,"); Scan scan = new Scan(firstRowInTable); scan.addColumn(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER); long scannerid = metaServer.openScanner( HRegionInfo.FIRST_META_REGIONINFO.getRegionName(), scan); try { Result data = metaServer.next(scannerid); if (data != null && data.size() > 0) { HRegionInfo info = Writables.getHRegionInfo( data.getValue(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER)); if (info.getTableDesc().getNameAsString().equals(tableName)) { // A region for this table already exists. Ergo table exists. return true; } } return false; } finally { metaServer.close(scannerid); } } /** * Gets all of the regions of the specified table. * @param catalogTracker * @param tableName * @return Ordered list of {@link HRegionInfo}. * @throws IOException */ public static List<HRegionInfo> getTableRegions(CatalogTracker catalogTracker, byte [] tableName) throws IOException { return getTableRegions(catalogTracker, tableName, false); } /** * Gets all of the regions of the specified table. * @param catalogTracker * @param tableName * @param excludeOfflinedSplitParents If true, do not include offlined split * parents in the return. * @return Ordered list of {@link HRegionInfo}. * @throws IOException */ public static List<HRegionInfo> getTableRegions(CatalogTracker catalogTracker, byte [] tableName, final boolean excludeOfflinedSplitParents) throws IOException { if (Bytes.equals(tableName, HConstants.ROOT_TABLE_NAME)) { // If root, do a bit of special handling. List<HRegionInfo> list = new ArrayList<HRegionInfo>(); list.add(HRegionInfo.ROOT_REGIONINFO); return list; } else if (Bytes.equals(tableName, HConstants.META_TABLE_NAME)) { // Same for .META. table List<HRegionInfo> list = new ArrayList<HRegionInfo>(); list.add(HRegionInfo.FIRST_META_REGIONINFO); return list; } // Its a user table. HRegionInterface metaServer = getCatalogRegionInterface(catalogTracker, tableName); List<HRegionInfo> regions = new ArrayList<HRegionInfo>(); String tableString = Bytes.toString(tableName); byte[] firstRowInTable = Bytes.toBytes(tableString + ",,"); Scan scan = new Scan(firstRowInTable); scan.addColumn(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER); long scannerid = metaServer.openScanner(getCatalogRegionNameForTable(tableName), scan); try { Result data; while((data = metaServer.next(scannerid)) != null) { if (data != null && data.size() > 0) { HRegionInfo info = Writables.getHRegionInfo( data.getValue(HConstants.CATALOG_FAMILY, HConstants.REGIONINFO_QUALIFIER)); if (info.getTableDesc().getNameAsString().equals(tableString)) { // Are we to include split parents in the list? if (excludeOfflinedSplitParents && info.isSplitParent()) continue; regions.add(info); } else { break; } } } return regions; } finally { metaServer.close(scannerid); } } /** * @param catalogTracker * @param tableName * @return Return list of regioninfos and server addresses. * @throws IOException * @throws InterruptedException */ public static List<Pair<HRegionInfo, HServerAddress>> getTableRegionsAndLocations(CatalogTracker catalogTracker, String tableName) throws IOException, InterruptedException { byte [] tableNameBytes = Bytes.toBytes(tableName); if (Bytes.equals(tableNameBytes, HConstants.ROOT_TABLE_NAME)) { // If root, do a bit of special handling. HServerAddress hsa = catalogTracker.getRootLocation(); List<Pair<HRegionInfo, HServerAddress>> list = new ArrayList<Pair<HRegionInfo, HServerAddress>>(); list.add(new Pair<HRegionInfo, HServerAddress>(HRegionInfo.ROOT_REGIONINFO, hsa)); return list; } HRegionInterface metaServer = getCatalogRegionInterface(catalogTracker, tableNameBytes); List<Pair<HRegionInfo, HServerAddress>> regions = new ArrayList<Pair<HRegionInfo, HServerAddress>>(); byte[] firstRowInTable = Bytes.toBytes(tableName + ",,"); Scan scan = new Scan(firstRowInTable); scan.addFamily(HConstants.CATALOG_FAMILY); long scannerid = metaServer.openScanner(getCatalogRegionNameForTable(tableNameBytes), scan); try { Result data; while((data = metaServer.next(scannerid)) != null) { if (data != null && data.size() > 0) { Pair<HRegionInfo, HServerAddress> region = metaRowToRegionPair(data); if (region == null) continue; if (region.getFirst().getTableDesc().getNameAsString().equals( tableName)) { regions.add(region); } else { break; } } } return regions; } finally { metaServer.close(scannerid); } } /** * @param catalogTracker * @param hsi Server specification * @return List of user regions installed on this server (does not include * catalog regions). * @throws IOException */ public static NavigableMap<HRegionInfo, Result> getServerUserRegions(CatalogTracker catalogTracker, final HServerInfo hsi) throws IOException { HRegionInterface metaServer = catalogTracker.waitForMetaServerConnectionDefault(); NavigableMap<HRegionInfo, Result> hris = new TreeMap<HRegionInfo, Result>(); Scan scan = new Scan(); scan.addFamily(HConstants.CATALOG_FAMILY); long scannerid = metaServer.openScanner( HRegionInfo.FIRST_META_REGIONINFO.getRegionName(), scan); try { Result result; while((result = metaServer.next(scannerid)) != null) { if (result != null && result.size() > 0) { Pair<HRegionInfo, HServerInfo> pair = metaRowToRegionPairWithInfo(result); if (pair == null) continue; if (pair.getSecond() == null || !pair.getSecond().equals(hsi)) { continue; } hris.put(pair.getFirst(), result); } } return hris; } finally { metaServer.close(scannerid); } } /** * Implementations 'visit' a catalog table row. */ public interface Visitor { /** * Visit the catalog table row. * @param r A row from catalog table * @return True if we are to proceed scanning the table, else false if * we are to stop now. */ public boolean visit(final Result r) throws IOException; } }