/** * Copyright 2007 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; import java.io.IOException; import java.util.ConcurrentModificationException; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.hbase.util.Writables; import org.apache.hadoop.io.Text; /** * Utility class to build a table of multiple regions. */ public class MultiRegionTable extends HBaseTestCase { static final Log LOG = LogFactory.getLog(MultiRegionTable.class.getName()); /** * Make a multi-region table. Presumption is that table already exists. * Makes it multi-region by filling with data and provoking splits. * Asserts parent region is cleaned up after its daughter splits release all * references. * @param conf * @param cluster * @param localFs * @param tableName * @param columnName * @throws IOException */ public static void makeMultiRegionTable(Configuration conf, MiniHBaseCluster cluster, FileSystem localFs, String tableName, String columnName) throws IOException { final int retries = 10; final long waitTime = conf.getLong("hbase.master.meta.thread.rescanfrequency", 10L * 1000L); // This size should make it so we always split using the addContent // below. After adding all data, the first region is 1.3M. Should // set max filesize to be <= 1M. assertTrue(conf.getLong("hbase.hregion.max.filesize", HConstants.DEFAULT_MAX_FILE_SIZE) <= 1024 * 1024); FileSystem fs = (cluster.getDFSCluster() == null) ? localFs : cluster.getDFSCluster().getFileSystem(); assertNotNull(fs); Path d = fs.makeQualified(new Path(conf.get(HConstants.HBASE_DIR))); // Get connection on the meta table and get count of rows. HTable meta = new HTable(conf, HConstants.META_TABLE_NAME); int count = count(meta, tableName); HTable t = new HTable(conf, new Text(tableName)); addContent(new HTableIncommon(t), columnName); // All is running in the one JVM so I should be able to get the single // region instance and bring on a split. HRegionInfo hri = t.getRegionLocation(HConstants.EMPTY_START_ROW).getRegionInfo(); HRegion r = cluster.regionThreads.get(0).getRegionServer(). onlineRegions.get(hri.getRegionName()); // Flush will provoke a split next time the split-checker thread runs. r.flushcache(false); // Now, wait until split makes it into the meta table. int oldCount = count; for (int i = 0; i < retries; i++) { count = count(meta, tableName); if (count > oldCount) { break; } try { Thread.sleep(waitTime); } catch (InterruptedException e) { // continue } } if (count <= oldCount) { throw new IOException("Failed waiting on splits to show up"); } // Get info on the parent from the meta table. Pass in 'hri'. Its the // region we have been dealing with up to this. Its the parent of the // region split. Map<Text, byte []> data = getSplitParentInfo(meta, hri); HRegionInfo parent = Writables.getHRegionInfoOrNull(data.get(HConstants.COL_REGIONINFO)); assertTrue(parent.isOffline()); assertTrue(parent.isSplit()); HRegionInfo splitA = Writables.getHRegionInfoOrNull(data.get(HConstants.COL_SPLITA)); HRegionInfo splitB = Writables.getHRegionInfoOrNull(data.get(HConstants.COL_SPLITB)); Path parentDir = HRegion.getRegionDir(d, parent.getRegionName()); assertTrue(fs.exists(parentDir)); LOG.info("Split happened. Parent is " + parent.getRegionName() + " and daughters are " + splitA.getRegionName() + ", " + splitB.getRegionName()); // Recalibrate will cause us to wait on new regions' deployment recalibrate(t, new Text(columnName), retries, waitTime); // Compact a region at a time so we can test case where one region has // no references but the other still has some compact(cluster, splitA); // Wait till the parent only has reference to remaining split, one that // still has references. while (getSplitParentInfo(meta, parent).size() == 3) { try { Thread.sleep(waitTime); } catch (InterruptedException e) { // continue } } LOG.info("Parent split returned " + getSplitParentInfo(meta, parent).keySet().toString()); // Call second split. compact(cluster, splitB); // Now wait until parent disappears. LOG.info("Waiting on parent " + parent.getRegionName() + " to disappear"); for (int i = 0; i < retries; i++) { if (getSplitParentInfo(meta, parent) == null) { break; } try { Thread.sleep(waitTime); } catch (InterruptedException e) { // continue } } assertNull(getSplitParentInfo(meta, parent)); // Assert cleaned up. for (int i = 0; i < retries; i++) { if (!fs.exists(parentDir)) { break; } try { Thread.sleep(waitTime); } catch (InterruptedException e) { // continue } } assertFalse(fs.exists(parentDir)); } /* * Count of regions in passed meta table. * @param t * @param column * @return * @throws IOException */ private static int count(final HTable t, final String tableName) throws IOException { int size = 0; Text [] cols = new Text[] {HConstants.COLUMN_FAMILY}; HScannerInterface s = t.obtainScanner(cols, HConstants.EMPTY_START_ROW, System.currentTimeMillis(), null); try { HStoreKey curKey = new HStoreKey(); TreeMap<Text, byte []> curVals = new TreeMap<Text, byte []>(); while(s.next(curKey, curVals)) { HRegionInfo hri = Writables. getHRegionInfoOrNull(curVals.get(HConstants.COL_REGIONINFO)); if (hri.getTableDesc().getName().toString().equals(tableName)) { size++; } } return size; } finally { s.close(); } } /* * @return Return row info for passed in region or null if not found in scan. */ private static Map<Text, byte []> getSplitParentInfo(final HTable t, final HRegionInfo parent) throws IOException { HScannerInterface s = t.obtainScanner(HConstants.COLUMN_FAMILY_ARRAY, HConstants.EMPTY_START_ROW, System.currentTimeMillis(), null); try { HStoreKey curKey = new HStoreKey(); TreeMap<Text, byte []> curVals = new TreeMap<Text, byte []>(); while(s.next(curKey, curVals)) { HRegionInfo hri = Writables. getHRegionInfoOrNull(curVals.get(HConstants.COL_REGIONINFO)); if (hri == null) { continue; } // Make sure I get the parent. if (hri.getRegionName().toString(). equals(parent.getRegionName().toString())) { return curVals; } } return null; } finally { s.close(); } } /* * Recalibrate passed in HTable. Run after change in region geography. * Open a scanner on the table. This will force HTable to recalibrate * and in doing so, will force us to wait until the new child regions * come on-line (since they are no longer automatically served by the * HRegionServer that was serving the parent. In this test they will * end up on the same server (since there is only one), but we have to * wait until the master assigns them. * @param t * @param retries */ private static void recalibrate(final HTable t, final Text column, final int retries, final long waitTime) throws IOException { for (int i = 0; i < retries; i++) { try { HScannerInterface s = t.obtainScanner(new Text[] {column}, HConstants.EMPTY_START_ROW); try { HStoreKey key = new HStoreKey(); TreeMap<Text, byte[]> results = new TreeMap<Text, byte[]>(); s.next(key, results); break; } finally { s.close(); } } catch (NotServingRegionException x) { System.out.println("it's alright"); try { Thread.sleep(waitTime); } catch (InterruptedException e) { // continue } } } } /* * Compact the passed in region <code>r</code>. * @param cluster * @param r * @throws IOException */ private static void compact(final MiniHBaseCluster cluster, final HRegionInfo r) throws IOException { LOG.info("Starting compaction"); for (MiniHBaseCluster.RegionServerThread thread: cluster.regionThreads) { SortedMap<Text, HRegion> regions = thread.getRegionServer().onlineRegions; // Retry if ConcurrentModification... alternative of sync'ing is not // worth it for sake of unit test. for (int i = 0; i < 10; i++) { try { for (HRegion online: regions.values()) { if (online.getRegionName().toString(). equals(r.getRegionName().toString())) { online.compactStores(); } } break; } catch (ConcurrentModificationException e) { LOG.warn("Retrying because ..." + e.toString() + " -- one or " + "two should be fine"); continue; } } } } }