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.master.procedure; 019 020import static org.junit.Assert.assertEquals; 021import static org.junit.Assert.assertNotEquals; 022 023import java.util.ArrayList; 024import java.util.HashMap; 025import java.util.HashSet; 026import java.util.List; 027import java.util.Map; 028import java.util.Set; 029import org.apache.hadoop.conf.Configuration; 030import org.apache.hadoop.hbase.HBaseClassTestRule; 031import org.apache.hadoop.hbase.HBaseTestingUtil; 032import org.apache.hadoop.hbase.TableName; 033import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; 034import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 035import org.apache.hadoop.hbase.client.RegionInfo; 036import org.apache.hadoop.hbase.client.TableDescriptor; 037import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 038import org.apache.hadoop.hbase.regionserver.HRegion; 039import org.apache.hadoop.hbase.regionserver.MetricsRegionWrapperImpl; 040import org.apache.hadoop.hbase.testclassification.MasterTests; 041import org.apache.hadoop.hbase.testclassification.MediumTests; 042import org.apache.hadoop.hbase.util.Bytes; 043import org.junit.AfterClass; 044import org.junit.BeforeClass; 045import org.junit.ClassRule; 046import org.junit.Test; 047import org.junit.experimental.categories.Category; 048 049@Category({ MasterTests.class, MediumTests.class }) 050public class TestReopenTableRegionsIntegration { 051 052 @ClassRule 053 public static final HBaseClassTestRule CLASS_RULE = 054 HBaseClassTestRule.forClass(TestReopenTableRegionsIntegration.class); 055 056 private static final HBaseTestingUtil UTIL = new HBaseTestingUtil(); 057 private static final TableName TABLE_NAME = TableName.valueOf("testLazyUpdateReopen"); 058 private static final byte[] CF = Bytes.toBytes("cf"); 059 060 @BeforeClass 061 public static void setupCluster() throws Exception { 062 Configuration conf = UTIL.getConfiguration(); 063 conf.setInt(MasterProcedureConstants.MASTER_PROCEDURE_THREADS, 1); 064 UTIL.startMiniCluster(1); 065 } 066 067 @AfterClass 068 public static void tearDown() throws Exception { 069 UTIL.shutdownMiniCluster(); 070 } 071 072 @Test 073 public void testLazyUpdateThenReopenUpdatesTableDescriptorHash() throws Exception { 074 // Step 1: Create table with column family and 3 regions 075 ColumnFamilyDescriptor cfd = 076 ColumnFamilyDescriptorBuilder.newBuilder(CF).setMaxVersions(1).build(); 077 078 TableDescriptor td = TableDescriptorBuilder.newBuilder(TABLE_NAME).setColumnFamily(cfd) 079 .setMaxFileSize(100 * 1024 * 1024L).build(); 080 081 UTIL.getAdmin().createTable(td, Bytes.toBytes("a"), Bytes.toBytes("z"), 3); 082 UTIL.waitTableAvailable(TABLE_NAME); 083 084 try { 085 // Step 2: Capture initial tableDescriptorHash from all regions 086 List<HRegion> regions = UTIL.getHBaseCluster().getRegions(TABLE_NAME); 087 assertEquals("Expected 3 regions", 3, regions.size()); 088 089 Map<byte[], String> initialHashes = new HashMap<>(); 090 091 for (HRegion region : regions) { 092 String hash; 093 try (MetricsRegionWrapperImpl wrapper = new MetricsRegionWrapperImpl(region)) { 094 hash = wrapper.getTableDescriptorHash(); 095 } 096 initialHashes.put(region.getRegionInfo().getRegionName(), hash); 097 } 098 099 // Verify all regions have same hash 100 Set<String> uniqueHashes = new HashSet<>(initialHashes.values()); 101 assertEquals("All regions should have same hash", 1, uniqueHashes.size()); 102 String initialHash = uniqueHashes.iterator().next(); 103 104 // Step 3: Perform lazy table descriptor update 105 ColumnFamilyDescriptor newCfd = 106 ColumnFamilyDescriptorBuilder.newBuilder(cfd).setMaxVersions(5).build(); 107 108 TableDescriptor newTd = TableDescriptorBuilder.newBuilder(td).modifyColumnFamily(newCfd) 109 .setMaxFileSize(200 * 1024 * 1024L).build(); 110 111 // Perform lazy update (reopenRegions = false) 112 UTIL.getAdmin().modifyTableAsync(newTd, false).get(); 113 114 // Wait for modification to complete 115 UTIL.waitFor(30000, () -> { 116 try { 117 TableDescriptor currentTd = UTIL.getAdmin().getDescriptor(TABLE_NAME); 118 return currentTd.getMaxFileSize() == 200 * 1024 * 1024L; 119 } catch (Exception e) { 120 return false; 121 } 122 }); 123 124 // Step 4: Verify tableDescriptorHash has NOT changed in region metrics 125 List<HRegion> regionsAfterLazyUpdate = UTIL.getHBaseCluster().getRegions(TABLE_NAME); 126 for (HRegion region : regionsAfterLazyUpdate) { 127 String currentHash; 128 try (MetricsRegionWrapperImpl wrapper = new MetricsRegionWrapperImpl(region)) { 129 currentHash = wrapper.getTableDescriptorHash(); 130 } 131 assertEquals("Hash should NOT change without region reopen", 132 initialHashes.get(region.getRegionInfo().getRegionName()), currentHash); 133 } 134 135 // Verify the table descriptor itself has changed 136 TableDescriptor currentTd = UTIL.getAdmin().getDescriptor(TABLE_NAME); 137 String newDescriptorHash = currentTd.getDescriptorHash(); 138 assertNotEquals("Table descriptor should have new hash", initialHash, newDescriptorHash); 139 140 // Step 5: Use new Admin API to reopen all regions 141 UTIL.getAdmin().reopenTableRegions(TABLE_NAME); 142 143 // Wait for all regions to be reopened 144 UTIL.waitFor(60000, () -> { 145 try { 146 List<HRegion> currentRegions = UTIL.getHBaseCluster().getRegions(TABLE_NAME); 147 if (currentRegions.size() != 3) { 148 return false; 149 } 150 151 // Check if all regions now have the new hash 152 for (HRegion region : currentRegions) { 153 String hash; 154 try (MetricsRegionWrapperImpl wrapper = new MetricsRegionWrapperImpl(region)) { 155 hash = wrapper.getTableDescriptorHash(); 156 } 157 if (hash.equals(initialHash)) { 158 return false; 159 } 160 } 161 return true; 162 } catch (Exception e) { 163 return false; 164 } 165 }); 166 167 // Step 6: Verify tableDescriptorHash HAS changed in all region metrics 168 List<HRegion> reopenedRegions = UTIL.getHBaseCluster().getRegions(TABLE_NAME); 169 assertEquals("Should still have 3 regions", 3, reopenedRegions.size()); 170 171 for (HRegion region : reopenedRegions) { 172 String currentHash; 173 try (MetricsRegionWrapperImpl wrapper = new MetricsRegionWrapperImpl(region)) { 174 currentHash = wrapper.getTableDescriptorHash(); 175 } 176 assertNotEquals("Hash SHOULD change after region reopen", initialHash, currentHash); 177 assertEquals("Hash should match current table descriptor", newDescriptorHash, currentHash); 178 } 179 180 // Verify all regions show the same new hash 181 Set<String> newHashes = new HashSet<>(); 182 for (HRegion region : reopenedRegions) { 183 try (MetricsRegionWrapperImpl wrapper = new MetricsRegionWrapperImpl(region)) { 184 newHashes.add(wrapper.getTableDescriptorHash()); 185 } 186 } 187 assertEquals("All regions should have same new hash", 1, newHashes.size()); 188 189 } finally { 190 UTIL.deleteTable(TABLE_NAME); 191 } 192 } 193 194 @Test 195 public void testLazyUpdateThenReopenSpecificRegions() throws Exception { 196 TableName tableName = TableName.valueOf("testSpecificRegionsReopen"); 197 198 // Step 1: Create table with 5 regions 199 ColumnFamilyDescriptor cfd = 200 ColumnFamilyDescriptorBuilder.newBuilder(CF).setMaxVersions(1).build(); 201 202 TableDescriptor td = TableDescriptorBuilder.newBuilder(tableName).setColumnFamily(cfd) 203 .setMaxFileSize(100 * 1024 * 1024L).build(); 204 205 UTIL.getAdmin().createTable(td, Bytes.toBytes("a"), Bytes.toBytes("z"), 5); 206 UTIL.waitTableAvailable(tableName); 207 208 try { 209 // Step 2: Capture initial hashes 210 List<HRegion> regions = UTIL.getHBaseCluster().getRegions(tableName); 211 assertEquals("Expected 5 regions", 5, regions.size()); 212 213 Map<byte[], String> initialHashes = new HashMap<>(); 214 215 for (HRegion region : regions) { 216 String hash; 217 try (MetricsRegionWrapperImpl wrapper = new MetricsRegionWrapperImpl(region)) { 218 hash = wrapper.getTableDescriptorHash(); 219 } 220 initialHashes.put(region.getRegionInfo().getRegionName(), hash); 221 } 222 223 String initialHash = initialHashes.values().iterator().next(); 224 225 // Step 3: Perform lazy update 226 ColumnFamilyDescriptor newCfd = 227 ColumnFamilyDescriptorBuilder.newBuilder(cfd).setMaxVersions(10).build(); 228 229 TableDescriptor newTd = TableDescriptorBuilder.newBuilder(td).modifyColumnFamily(newCfd) 230 .setMaxFileSize(300 * 1024 * 1024L).build(); 231 232 UTIL.getAdmin().modifyTableAsync(newTd, false).get(); 233 234 UTIL.waitFor(30000, () -> { 235 try { 236 TableDescriptor currentTd = UTIL.getAdmin().getDescriptor(tableName); 237 return currentTd.getMaxFileSize() == 300 * 1024 * 1024L; 238 } catch (Exception e) { 239 return false; 240 } 241 }); 242 243 String newDescriptorHash = UTIL.getAdmin().getDescriptor(tableName).getDescriptorHash(); 244 245 // Step 4: Reopen only first 2 regions 246 List<RegionInfo> regionsToReopen = new ArrayList<>(); 247 regionsToReopen.add(regions.get(0).getRegionInfo()); 248 regionsToReopen.add(regions.get(1).getRegionInfo()); 249 250 UTIL.getAdmin().reopenTableRegions(tableName, regionsToReopen); 251 252 // Wait for those regions to reopen 253 UTIL.waitFor(60000, () -> { 254 try { 255 List<HRegion> currentRegions = UTIL.getHBaseCluster().getRegions(tableName); 256 int newHashCount = 0; 257 for (HRegion region : currentRegions) { 258 String hash; 259 try (MetricsRegionWrapperImpl wrapper = new MetricsRegionWrapperImpl(region)) { 260 hash = wrapper.getTableDescriptorHash(); 261 } 262 if (!hash.equals(initialHash)) { 263 newHashCount++; 264 } 265 } 266 return newHashCount >= 2; 267 } catch (Exception e) { 268 return false; 269 } 270 }); 271 272 // Step 5: Verify only reopened regions have new hash 273 List<HRegion> regionsAfterFirstReopen = UTIL.getHBaseCluster().getRegions(tableName); 274 int newHashCount = 0; 275 int oldHashCount = 0; 276 277 for (HRegion region : regionsAfterFirstReopen) { 278 String currentHash; 279 try (MetricsRegionWrapperImpl wrapper = new MetricsRegionWrapperImpl(region)) { 280 currentHash = wrapper.getTableDescriptorHash(); 281 } 282 283 if (currentHash.equals(newDescriptorHash)) { 284 newHashCount++; 285 } else if (currentHash.equals(initialHash)) { 286 oldHashCount++; 287 } 288 } 289 290 assertEquals("Should have 2 regions with new hash", 2, newHashCount); 291 assertEquals("Should have 3 regions with old hash", 3, oldHashCount); 292 293 // Step 6: Reopen remaining regions 294 List<RegionInfo> remainingRegions = new ArrayList<>(); 295 for (int i = 2; i < regions.size(); i++) { 296 remainingRegions.add(regions.get(i).getRegionInfo()); 297 } 298 299 UTIL.getAdmin().reopenTableRegions(tableName, remainingRegions); 300 301 // Wait for all regions to have new hash 302 UTIL.waitFor(60000, () -> { 303 try { 304 List<HRegion> currentRegions = UTIL.getHBaseCluster().getRegions(tableName); 305 for (HRegion region : currentRegions) { 306 String hash; 307 try (MetricsRegionWrapperImpl wrapper = new MetricsRegionWrapperImpl(region)) { 308 hash = wrapper.getTableDescriptorHash(); 309 } 310 if (!hash.equals(newDescriptorHash)) { 311 return false; 312 } 313 } 314 return true; 315 } catch (Exception e) { 316 return false; 317 } 318 }); 319 320 // Step 7: Verify all regions now have new hash 321 List<HRegion> finalRegions = UTIL.getHBaseCluster().getRegions(tableName); 322 for (HRegion region : finalRegions) { 323 String currentHash; 324 try (MetricsRegionWrapperImpl wrapper = new MetricsRegionWrapperImpl(region)) { 325 currentHash = wrapper.getTableDescriptorHash(); 326 } 327 328 assertEquals("All regions should now have new hash", newDescriptorHash, currentHash); 329 } 330 331 } finally { 332 UTIL.deleteTable(tableName); 333 } 334 } 335}