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.HBaseTestingUtil; 030import org.apache.hadoop.hbase.SingleProcessHBaseCluster; 031import org.apache.hadoop.hbase.TableName; 032import org.apache.hadoop.hbase.client.Admin; 033import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; 034import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 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.client.TableDescriptorBuilder; 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 HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil(); 068 069 private SingleProcessHBaseCluster 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 * @param sizeInBytes The amount of data to write in bytes. 126 * @return The table the data was written to 127 */ 128 private TableName writeData(long sizeInBytes) throws IOException { 129 final Connection conn = TEST_UTIL.getConnection(); 130 final Admin admin = TEST_UTIL.getAdmin(); 131 final TableName tn = TableName.valueOf(testName.getMethodName()); 132 133 // Delete the old table 134 if (admin.tableExists(tn)) { 135 admin.disableTable(tn); 136 admin.deleteTable(tn); 137 } 138 139 // Create the table 140 TableDescriptorBuilder tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(tn); 141 ColumnFamilyDescriptor columnFamilyDescriptor = 142 ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(F1)).build(); 143 tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptor); 144 145 admin.createTable(tableDescriptorBuilder.build(), Bytes.toBytes("1"), Bytes.toBytes("9"), 146 NUM_SPLITS); 147 148 final Table table = conn.getTable(tn); 149 try { 150 List<Put> updates = new ArrayList<>(); 151 long bytesToWrite = sizeInBytes; 152 long rowKeyId = 0L; 153 final StringBuilder sb = new StringBuilder(); 154 while (bytesToWrite > 0L) { 155 sb.setLength(0); 156 sb.append(Long.toString(rowKeyId)); 157 // Use the reverse counter as the rowKey to get even spread across all regions 158 Put p = new Put(Bytes.toBytes(sb.reverse().toString())); 159 byte[] value = new byte[SIZE_PER_VALUE]; 160 Bytes.random(value); 161 p.addColumn(Bytes.toBytes(F1), Bytes.toBytes("q1"), value); 162 updates.add(p); 163 164 // Batch 50K worth of updates 165 if (updates.size() > 50) { 166 table.put(updates); 167 updates.clear(); 168 } 169 170 // Just count the value size, ignore the size of rowkey + column 171 bytesToWrite -= SIZE_PER_VALUE; 172 rowKeyId++; 173 } 174 175 // Write the final batch 176 if (!updates.isEmpty()) { 177 table.put(updates); 178 } 179 180 return tn; 181 } finally { 182 table.close(); 183 } 184 } 185 186 /** 187 * Computes the number of regions for the given table that have a positive size. 188 * @param tn The TableName in question 189 * @param regions A collection of region sizes 190 * @return The number of regions for the given table. 191 */ 192 private int numRegionsForTable(TableName tn, Map<RegionInfo, Long> regions) { 193 int sum = 0; 194 for (Entry<RegionInfo, Long> entry : regions.entrySet()) { 195 if (tn.equals(entry.getKey().getTable()) && 0 < entry.getValue()) { 196 sum++; 197 } 198 } 199 return sum; 200 } 201}