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.regionserver; 019 020import static org.junit.Assert.assertEquals; 021import static org.junit.Assert.assertTrue; 022 023import java.io.IOException; 024import java.util.ArrayList; 025import java.util.List; 026import java.util.Random; 027import java.util.concurrent.ThreadLocalRandom; 028import org.apache.hadoop.hbase.HBaseClassTestRule; 029import org.apache.hadoop.hbase.HBaseTestingUtil; 030import org.apache.hadoop.hbase.TableName; 031import org.apache.hadoop.hbase.client.Admin; 032import org.apache.hadoop.hbase.client.CompactionState; 033import org.apache.hadoop.hbase.client.Put; 034import org.apache.hadoop.hbase.client.Table; 035import org.apache.hadoop.hbase.master.HMaster; 036import org.apache.hadoop.hbase.testclassification.LargeTests; 037import org.apache.hadoop.hbase.testclassification.VerySlowRegionServerTests; 038import org.apache.hadoop.hbase.util.Bytes; 039import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 040import org.junit.AfterClass; 041import org.junit.BeforeClass; 042import org.junit.ClassRule; 043import org.junit.Rule; 044import org.junit.Test; 045import org.junit.experimental.categories.Category; 046import org.junit.rules.TestName; 047 048/** Unit tests to test retrieving table/region compaction state */ 049@Category({ VerySlowRegionServerTests.class, LargeTests.class }) 050public class TestCompactionState { 051 052 @ClassRule 053 public static final HBaseClassTestRule CLASS_RULE = 054 HBaseClassTestRule.forClass(TestCompactionState.class); 055 056 private final static HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil(); 057 058 @Rule 059 public TestName name = new TestName(); 060 061 @BeforeClass 062 public static void setUpBeforeClass() throws Exception { 063 TEST_UTIL.startMiniCluster(); 064 } 065 066 @AfterClass 067 public static void tearDownAfterClass() throws Exception { 068 TEST_UTIL.shutdownMiniCluster(); 069 } 070 071 enum StateSource { 072 ADMIN, 073 MASTER 074 } 075 076 @Test 077 public void testMajorCompactionStateFromAdmin() throws IOException, InterruptedException { 078 compaction(name.getMethodName(), 8, CompactionState.MAJOR, false, StateSource.ADMIN); 079 } 080 081 @Test 082 public void testMinorCompactionStateFromAdmin() throws IOException, InterruptedException { 083 compaction(name.getMethodName(), 15, CompactionState.MINOR, false, StateSource.ADMIN); 084 } 085 086 @Test 087 public void testMajorCompactionOnFamilyStateFromAdmin() throws IOException, InterruptedException { 088 compaction(name.getMethodName(), 8, CompactionState.MAJOR, true, StateSource.ADMIN); 089 } 090 091 @Test 092 public void testMinorCompactionOnFamilyStateFromAdmin() throws IOException, InterruptedException { 093 compaction(name.getMethodName(), 15, CompactionState.MINOR, true, StateSource.ADMIN); 094 } 095 096 @Test 097 public void testMajorCompactionStateFromMaster() throws IOException, InterruptedException { 098 compaction(name.getMethodName(), 8, CompactionState.MAJOR, false, StateSource.MASTER); 099 } 100 101 @Test 102 public void testMinorCompactionStateFromMaster() throws IOException, InterruptedException { 103 compaction(name.getMethodName(), 15, CompactionState.MINOR, false, StateSource.MASTER); 104 } 105 106 @Test 107 public void testMajorCompactionOnFamilyStateFromMaster() 108 throws IOException, InterruptedException { 109 compaction(name.getMethodName(), 8, CompactionState.MAJOR, true, StateSource.MASTER); 110 } 111 112 @Test 113 public void testMinorCompactionOnFamilyStateFromMaster() 114 throws IOException, InterruptedException { 115 compaction(name.getMethodName(), 15, CompactionState.MINOR, true, StateSource.MASTER); 116 } 117 118 @Test 119 public void testInvalidColumnFamily() throws IOException, InterruptedException { 120 final TableName tableName = TableName.valueOf(name.getMethodName()); 121 byte[] family = Bytes.toBytes("family"); 122 byte[] fakecf = Bytes.toBytes("fakecf"); 123 boolean caughtMinorCompact = false; 124 boolean caughtMajorCompact = false; 125 Table ht = null; 126 try { 127 ht = TEST_UTIL.createTable(tableName, family); 128 Admin admin = TEST_UTIL.getAdmin(); 129 try { 130 admin.compact(tableName, fakecf); 131 } catch (IOException ioe) { 132 caughtMinorCompact = true; 133 } 134 try { 135 admin.majorCompact(tableName, fakecf); 136 } catch (IOException ioe) { 137 caughtMajorCompact = true; 138 } 139 } finally { 140 if (ht != null) { 141 TEST_UTIL.deleteTable(tableName); 142 } 143 assertTrue(caughtMinorCompact); 144 assertTrue(caughtMajorCompact); 145 } 146 } 147 148 /** 149 * Load data to a table, flush it to disk, trigger compaction, confirm the compaction state is 150 * right and wait till it is done. 151 * @param singleFamily otherwise, run compaction on all cfs 152 * @param stateSource get the state by Admin or Master 153 */ 154 private void compaction(final String tableName, final int flushes, 155 final CompactionState expectedState, boolean singleFamily, StateSource stateSource) 156 throws IOException, InterruptedException { 157 // Create a table with regions 158 TableName table = TableName.valueOf(tableName); 159 byte[] family = Bytes.toBytes("family"); 160 byte[][] families = 161 { family, Bytes.add(family, Bytes.toBytes("2")), Bytes.add(family, Bytes.toBytes("3")) }; 162 Table ht = null; 163 try { 164 ht = TEST_UTIL.createTable(table, families); 165 loadData(ht, families, 3000, flushes); 166 HRegionServer rs = TEST_UTIL.getMiniHBaseCluster().getRegionServer(0); 167 HMaster master = TEST_UTIL.getMiniHBaseCluster().getMaster(); 168 List<HRegion> regions = rs.getRegions(table); 169 int countBefore = countStoreFilesInFamilies(regions, families); 170 int countBeforeSingleFamily = countStoreFilesInFamily(regions, family); 171 assertTrue(countBefore > 0); // there should be some data files 172 Admin admin = TEST_UTIL.getAdmin(); 173 if (expectedState == CompactionState.MINOR) { 174 if (singleFamily) { 175 admin.compact(table, family); 176 } else { 177 admin.compact(table); 178 } 179 } else { 180 if (singleFamily) { 181 admin.majorCompact(table, family); 182 } else { 183 admin.majorCompact(table); 184 } 185 } 186 long curt = EnvironmentEdgeManager.currentTime(); 187 long waitTime = 5000; 188 long endt = curt + waitTime; 189 CompactionState state = getCompactionState(stateSource, master, admin, table); 190 while (state == CompactionState.NONE && curt < endt) { 191 Thread.sleep(10); 192 state = getCompactionState(stateSource, master, admin, table); 193 curt = EnvironmentEdgeManager.currentTime(); 194 } 195 // Now, should have the right compaction state, 196 // otherwise, the compaction should have already been done 197 if (expectedState != state) { 198 for (Region region : regions) { 199 state = CompactionState.valueOf(region.getCompactionState().toString()); 200 assertEquals(CompactionState.NONE, state); 201 } 202 } else { 203 // Wait until the compaction is done 204 state = getCompactionState(stateSource, master, admin, table); 205 while (state != CompactionState.NONE && curt < endt) { 206 Thread.sleep(10); 207 state = getCompactionState(stateSource, master, admin, table); 208 } 209 // Now, compaction should be done. 210 assertEquals(CompactionState.NONE, state); 211 } 212 int countAfter = countStoreFilesInFamilies(regions, families); 213 int countAfterSingleFamily = countStoreFilesInFamily(regions, family); 214 assertTrue(countAfter < countBefore); 215 if (!singleFamily) { 216 if (expectedState == CompactionState.MAJOR) assertTrue(families.length == countAfter); 217 else assertTrue(families.length < countAfter); 218 } else { 219 int singleFamDiff = countBeforeSingleFamily - countAfterSingleFamily; 220 // assert only change was to single column family 221 assertTrue(singleFamDiff == (countBefore - countAfter)); 222 if (expectedState == CompactionState.MAJOR) { 223 assertTrue(1 == countAfterSingleFamily); 224 } else { 225 assertTrue(1 < countAfterSingleFamily); 226 } 227 } 228 } finally { 229 if (ht != null) { 230 TEST_UTIL.deleteTable(table); 231 } 232 } 233 } 234 235 private static CompactionState getCompactionState(StateSource stateSource, HMaster master, 236 Admin admin, TableName table) throws IOException { 237 CompactionState state = stateSource == StateSource.ADMIN 238 ? admin.getCompactionState(table) 239 : master.getCompactionState(table); 240 return state; 241 } 242 243 private static int countStoreFilesInFamily(List<HRegion> regions, final byte[] family) { 244 return countStoreFilesInFamilies(regions, new byte[][] { family }); 245 } 246 247 private static int countStoreFilesInFamilies(List<HRegion> regions, final byte[][] families) { 248 int count = 0; 249 for (HRegion region : regions) { 250 count += region.getStoreFileList(families).size(); 251 } 252 return count; 253 } 254 255 private static void loadData(final Table ht, final byte[][] families, final int rows, 256 final int flushes) throws IOException { 257 List<Put> puts = new ArrayList<>(rows); 258 byte[] qualifier = Bytes.toBytes("val"); 259 Random rand = ThreadLocalRandom.current(); 260 for (int i = 0; i < flushes; i++) { 261 for (int k = 0; k < rows; k++) { 262 byte[] row = Bytes.toBytes(rand.nextLong()); 263 Put p = new Put(row); 264 for (int j = 0; j < families.length; ++j) { 265 p.addColumn(families[j], qualifier, row); 266 } 267 puts.add(p); 268 } 269 ht.put(puts); 270 TEST_UTIL.flush(); 271 puts.clear(); 272 } 273 } 274}