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