001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.quotas; 019 020import static org.junit.Assert.assertTrue; 021 022import java.io.IOException; 023import java.util.ArrayList; 024import java.util.List; 025import java.util.Map; 026import java.util.Map.Entry; 027import java.util.Random; 028import org.apache.hadoop.conf.Configuration; 029import org.apache.hadoop.hbase.HBaseClassTestRule; 030import org.apache.hadoop.hbase.HBaseTestingUtility; 031import org.apache.hadoop.hbase.HColumnDescriptor; 032import org.apache.hadoop.hbase.HTableDescriptor; 033import org.apache.hadoop.hbase.MiniHBaseCluster; 034import org.apache.hadoop.hbase.TableName; 035import org.apache.hadoop.hbase.client.Admin; 036import org.apache.hadoop.hbase.client.Connection; 037import org.apache.hadoop.hbase.client.Put; 038import org.apache.hadoop.hbase.client.RegionInfo; 039import org.apache.hadoop.hbase.client.Table; 040import org.apache.hadoop.hbase.master.HMaster; 041import org.apache.hadoop.hbase.testclassification.MediumTests; 042import org.apache.hadoop.hbase.util.Bytes; 043import org.junit.After; 044import org.junit.Before; 045import org.junit.ClassRule; 046import org.junit.Rule; 047import org.junit.Test; 048import org.junit.experimental.categories.Category; 049import org.junit.rules.TestName; 050import org.slf4j.Logger; 051import org.slf4j.LoggerFactory; 052 053/** 054 * Test class which verifies that region sizes are reported to the master. 055 */ 056@Category(MediumTests.class) 057public class TestRegionSizeUse { 058 059 @ClassRule 060 public static final HBaseClassTestRule CLASS_RULE = 061 HBaseClassTestRule.forClass(TestRegionSizeUse.class); 062 063 private static final Logger LOG = LoggerFactory.getLogger(TestRegionSizeUse.class); 064 private static final int SIZE_PER_VALUE = 256; 065 private static final int NUM_SPLITS = 10; 066 private static final String F1 = "f1"; 067 private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 068 069 private MiniHBaseCluster cluster; 070 071 @Rule 072 public TestName testName = new TestName(); 073 074 @Before 075 public void setUp() throws Exception { 076 Configuration conf = TEST_UTIL.getConfiguration(); 077 // Increase the frequency of some of the chores for responsiveness of the test 078 SpaceQuotaHelperForTests.updateConfigForQuotas(conf); 079 cluster = TEST_UTIL.startMiniCluster(2); 080 } 081 082 @After 083 public void tearDown() throws Exception { 084 TEST_UTIL.shutdownMiniCluster(); 085 } 086 087 @Test 088 public void testBasicRegionSizeReports() throws Exception { 089 final long bytesWritten = 5L * 1024L * 1024L; // 5MB 090 final TableName tn = writeData(bytesWritten); 091 LOG.debug("Data was written to HBase"); 092 final Admin admin = TEST_UTIL.getAdmin(); 093 // Push the data to disk. 094 admin.flush(tn); 095 LOG.debug("Data flushed to disk"); 096 // Get the final region distribution 097 final List<RegionInfo> regions = TEST_UTIL.getAdmin().getRegions(tn); 098 099 HMaster master = cluster.getMaster(); 100 MasterQuotaManager quotaManager = master.getMasterQuotaManager(); 101 Map<RegionInfo,Long> regionSizes = quotaManager.snapshotRegionSizes(); 102 // Wait until we get all of the region reports for our table 103 // The table may split, so make sure we have at least as many as expected right after we 104 // finished writing the data. 105 int observedRegions = numRegionsForTable(tn, regionSizes); 106 while (observedRegions < regions.size()) { 107 LOG.debug("Expecting more regions. Saw " + observedRegions 108 + " region sizes reported, expected at least " + regions.size()); 109 Thread.sleep(1000); 110 regionSizes = quotaManager.snapshotRegionSizes(); 111 observedRegions = numRegionsForTable(tn, regionSizes); 112 } 113 114 LOG.debug("Observed region sizes by the HMaster: " + regionSizes); 115 long totalRegionSize = 0L; 116 for (Long regionSize : regionSizes.values()) { 117 totalRegionSize += regionSize; 118 } 119 assertTrue("Expected region size report to exceed " + bytesWritten + ", but was " 120 + totalRegionSize + ". RegionSizes=" + regionSizes, bytesWritten < totalRegionSize); 121 } 122 123 /** 124 * Writes at least {@code sizeInBytes} bytes of data to HBase and returns the TableName used. 125 * 126 * @param sizeInBytes The amount of data to write in bytes. 127 * @return The table the data was written to 128 */ 129 private TableName writeData(long sizeInBytes) throws IOException { 130 final Connection conn = TEST_UTIL.getConnection(); 131 final Admin admin = TEST_UTIL.getAdmin(); 132 final TableName tn = TableName.valueOf(testName.getMethodName()); 133 134 // Delete the old table 135 if (admin.tableExists(tn)) { 136 admin.disableTable(tn); 137 admin.deleteTable(tn); 138 } 139 140 // Create the table 141 HTableDescriptor tableDesc = new HTableDescriptor(tn); 142 tableDesc.addFamily(new HColumnDescriptor(F1)); 143 admin.createTable(tableDesc, Bytes.toBytes("1"), Bytes.toBytes("9"), NUM_SPLITS); 144 145 final Table table = conn.getTable(tn); 146 try { 147 List<Put> updates = new ArrayList<>(); 148 long bytesToWrite = sizeInBytes; 149 long rowKeyId = 0L; 150 final StringBuilder sb = new StringBuilder(); 151 final Random r = new Random(); 152 while (bytesToWrite > 0L) { 153 sb.setLength(0); 154 sb.append(Long.toString(rowKeyId)); 155 // Use the reverse counter as the rowKey to get even spread across all regions 156 Put p = new Put(Bytes.toBytes(sb.reverse().toString())); 157 byte[] value = new byte[SIZE_PER_VALUE]; 158 r.nextBytes(value); 159 p.addColumn(Bytes.toBytes(F1), Bytes.toBytes("q1"), value); 160 updates.add(p); 161 162 // Batch 50K worth of updates 163 if (updates.size() > 50) { 164 table.put(updates); 165 updates.clear(); 166 } 167 168 // Just count the value size, ignore the size of rowkey + column 169 bytesToWrite -= SIZE_PER_VALUE; 170 rowKeyId++; 171 } 172 173 // Write the final batch 174 if (!updates.isEmpty()) { 175 table.put(updates); 176 } 177 178 return tn; 179 } finally { 180 table.close(); 181 } 182 } 183 184 /** 185 * Computes the number of regions for the given table that have a positive size. 186 * 187 * @param tn The TableName in question 188 * @param regions A collection of region sizes 189 * @return The number of regions for the given table. 190 */ 191 private int numRegionsForTable(TableName tn, Map<RegionInfo,Long> regions) { 192 int sum = 0; 193 for (Entry<RegionInfo,Long> entry : regions.entrySet()) { 194 if (tn.equals(entry.getKey().getTable()) && 0 < entry.getValue()) { 195 sum++; 196 } 197 } 198 return sum; 199 } 200}