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; 019 020import static org.junit.Assert.assertEquals; 021import static org.junit.Assert.assertNotEquals; 022import static org.junit.Assert.assertNotNull; 023import static org.junit.Assert.assertTrue; 024 025import java.io.IOException; 026import java.nio.charset.StandardCharsets; 027import java.util.ArrayList; 028import java.util.Collection; 029import java.util.EnumSet; 030import java.util.HashMap; 031import java.util.HashSet; 032import java.util.List; 033import java.util.Map; 034import java.util.Set; 035import java.util.concurrent.atomic.AtomicInteger; 036import org.apache.hadoop.conf.Configuration; 037import org.apache.hadoop.hbase.ClusterMetrics.Option; 038import org.apache.hadoop.hbase.HBaseClassTestRule; 039import org.apache.hadoop.hbase.HBaseTestingUtility; 040import org.apache.hadoop.hbase.HConstants; 041import org.apache.hadoop.hbase.HRegionLocation; 042import org.apache.hadoop.hbase.MetaTableAccessor; 043import org.apache.hadoop.hbase.MetaTableAccessor.Visitor; 044import org.apache.hadoop.hbase.RegionLocations; 045import org.apache.hadoop.hbase.ServerName; 046import org.apache.hadoop.hbase.StartMiniClusterOption; 047import org.apache.hadoop.hbase.TableName; 048import org.apache.hadoop.hbase.client.Admin; 049import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 050import org.apache.hadoop.hbase.client.Connection; 051import org.apache.hadoop.hbase.client.ConnectionFactory; 052import org.apache.hadoop.hbase.client.Delete; 053import org.apache.hadoop.hbase.client.RegionInfo; 054import org.apache.hadoop.hbase.client.RegionReplicaUtil; 055import org.apache.hadoop.hbase.client.Result; 056import org.apache.hadoop.hbase.client.Table; 057import org.apache.hadoop.hbase.client.TableDescriptor; 058import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 059import org.apache.hadoop.hbase.testclassification.MasterTests; 060import org.apache.hadoop.hbase.testclassification.MediumTests; 061import org.apache.hadoop.hbase.util.Bytes; 062import org.apache.hadoop.hbase.util.JVMClusterUtil; 063import org.junit.AfterClass; 064import org.junit.BeforeClass; 065import org.junit.ClassRule; 066import org.junit.Rule; 067import org.junit.Test; 068import org.junit.experimental.categories.Category; 069import org.junit.rules.TestName; 070import org.slf4j.Logger; 071import org.slf4j.LoggerFactory; 072 073import org.apache.hbase.thirdparty.com.google.common.io.Closeables; 074 075@Category({ MasterTests.class, MediumTests.class }) 076public class TestMasterOperationsForRegionReplicas { 077 078 @ClassRule 079 public static final HBaseClassTestRule CLASS_RULE = 080 HBaseClassTestRule.forClass(TestMasterOperationsForRegionReplicas.class); 081 082 private static final Logger LOG = LoggerFactory.getLogger(TestRegionPlacement.class); 083 private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 084 private static Connection CONNECTION = null; 085 private static Admin ADMIN; 086 private static int numSlaves = 2; 087 private final static StartMiniClusterOption option = StartMiniClusterOption.builder() 088 .numRegionServers(numSlaves).numMasters(1).numAlwaysStandByMasters(1).build(); 089 private static Configuration conf; 090 091 @Rule 092 public TestName name = new TestName(); 093 094 @BeforeClass 095 public static void setupBeforeClass() throws Exception { 096 conf = TEST_UTIL.getConfiguration(); 097 conf.setBoolean("hbase.tests.use.shortcircuit.reads", false); 098 TEST_UTIL.startMiniCluster(option); 099 TEST_UTIL.getAdmin().balancerSwitch(false, true); 100 resetConnections(); 101 while ( 102 ADMIN.getClusterMetrics(EnumSet.of(Option.LIVE_SERVERS)).getLiveServerMetrics().size() 103 < numSlaves 104 ) { 105 Thread.sleep(100); 106 } 107 } 108 109 private static void resetConnections() throws IOException { 110 Closeables.close(ADMIN, true); 111 Closeables.close(CONNECTION, true); 112 CONNECTION = ConnectionFactory.createConnection(TEST_UTIL.getConfiguration()); 113 ADMIN = CONNECTION.getAdmin(); 114 } 115 116 @AfterClass 117 public static void tearDownAfterClass() throws Exception { 118 Closeables.close(ADMIN, true); 119 Closeables.close(CONNECTION, true); 120 TEST_UTIL.shutdownMiniCluster(); 121 } 122 123 @Test 124 public void testCreateTableWithSingleReplica() throws Exception { 125 final int numRegions = 3; 126 final int numReplica = 1; 127 final TableName tableName = TableName.valueOf(name.getMethodName()); 128 try { 129 TableDescriptor desc = 130 TableDescriptorBuilder.newBuilder(tableName).setRegionReplication(numReplica) 131 .setColumnFamily(ColumnFamilyDescriptorBuilder.of("family")).build(); 132 ADMIN.createTable(desc, Bytes.toBytes("A"), Bytes.toBytes("Z"), numRegions); 133 TEST_UTIL.waitUntilAllRegionsAssigned(tableName); 134 TEST_UTIL.waitUntilNoRegionsInTransition(); 135 136 validateNumberOfRowsInMeta(tableName, numRegions, ADMIN.getConnection()); 137 List<RegionInfo> hris = MetaTableAccessor.getTableRegions(ADMIN.getConnection(), tableName); 138 assertEquals(numRegions * numReplica, hris.size()); 139 } finally { 140 ADMIN.disableTable(tableName); 141 ADMIN.deleteTable(tableName); 142 } 143 } 144 145 @Test 146 public void testCreateTableWithMultipleReplicas() throws Exception { 147 final TableName tableName = TableName.valueOf(name.getMethodName()); 148 final int numRegions = 3; 149 final int numReplica = 2; 150 try { 151 TableDescriptor desc = 152 TableDescriptorBuilder.newBuilder(tableName).setRegionReplication(numReplica) 153 .setColumnFamily(ColumnFamilyDescriptorBuilder.of("family")).build(); 154 ADMIN.createTable(desc, Bytes.toBytes("A"), Bytes.toBytes("Z"), numRegions); 155 TEST_UTIL.waitUntilAllRegionsAssigned(tableName); 156 TEST_UTIL.waitUntilNoRegionsInTransition(); 157 validateNumberOfRowsInMeta(tableName, numRegions, ADMIN.getConnection()); 158 159 List<RegionInfo> hris = MetaTableAccessor.getTableRegions(ADMIN.getConnection(), tableName); 160 assertEquals(numRegions * numReplica, hris.size()); 161 assertRegionStateNotNull(hris, numRegions, numReplica); 162 163 List<Result> metaRows = MetaTableAccessor.fullScanRegions(ADMIN.getConnection()); 164 int numRows = 0; 165 for (Result result : metaRows) { 166 RegionLocations locations = MetaTableAccessor.getRegionLocations(result); 167 RegionInfo hri = locations.getRegionLocation().getRegion(); 168 if (!hri.getTable().equals(tableName)) continue; 169 numRows += 1; 170 HRegionLocation[] servers = locations.getRegionLocations(); 171 // have two locations for the replicas of a region, and the locations should be different 172 assertEquals(2, servers.length); 173 assertNotEquals(servers[1], servers[0]); 174 } 175 assertEquals(numRegions, numRows); 176 177 // The same verification of the meta as above but with the SnapshotOfRegionAssignmentFromMeta 178 // class 179 validateFromSnapshotFromMeta(TEST_UTIL, tableName, numRegions, numReplica, 180 ADMIN.getConnection()); 181 182 // Now kill the master, restart it and see if the assignments are kept 183 ServerName master = TEST_UTIL.getHBaseClusterInterface().getClusterMetrics().getMasterName(); 184 TEST_UTIL.getHBaseClusterInterface().stopMaster(master); 185 TEST_UTIL.getHBaseClusterInterface().waitForMasterToStop(master, 30000); 186 TEST_UTIL.getHBaseClusterInterface().startMaster(master.getHostname(), master.getPort()); 187 TEST_UTIL.getHBaseClusterInterface().waitForActiveAndReadyMaster(); 188 TEST_UTIL.waitUntilAllRegionsAssigned(tableName); 189 TEST_UTIL.waitUntilNoRegionsInTransition(); 190 assertRegionStateNotNull(hris, numRegions, numReplica); 191 validateFromSnapshotFromMeta(TEST_UTIL, tableName, numRegions, numReplica, 192 ADMIN.getConnection()); 193 194 // Now shut the whole cluster down, and verify the assignments are kept so that the 195 // availability constraints are met. MiniHBaseCluster chooses arbitrary ports on each 196 // restart. This messes with our being able to test that we retain locality. Therefore, 197 // figure current cluster ports and pass them in on next cluster start so new cluster comes 198 // up at same coordinates -- and the assignment retention logic has a chance to cut in. 199 List<Integer> rsports = new ArrayList<>(); 200 for (JVMClusterUtil.RegionServerThread rst : TEST_UTIL.getHBaseCluster() 201 .getLiveRegionServerThreads()) { 202 rsports.add(rst.getRegionServer().getRpcServer().getListenerAddress().getPort()); 203 } 204 TEST_UTIL.shutdownMiniHBaseCluster(); 205 StartMiniClusterOption option = 206 StartMiniClusterOption.builder().numRegionServers(numSlaves).rsPorts(rsports).build(); 207 TEST_UTIL.startMiniHBaseCluster(option); 208 TEST_UTIL.waitUntilAllRegionsAssigned(tableName); 209 TEST_UTIL.waitUntilNoRegionsInTransition(); 210 resetConnections(); 211 validateFromSnapshotFromMeta(TEST_UTIL, tableName, numRegions, numReplica, 212 ADMIN.getConnection()); 213 214 // Now shut the whole cluster down, and verify regions are assigned even if there is only 215 // one server running 216 TEST_UTIL.shutdownMiniHBaseCluster(); 217 TEST_UTIL.startMiniHBaseCluster(); 218 TEST_UTIL.waitUntilAllRegionsAssigned(tableName); 219 TEST_UTIL.waitUntilNoRegionsInTransition(); 220 resetConnections(); 221 validateSingleRegionServerAssignment(ADMIN.getConnection(), numRegions, numReplica); 222 for (int i = 1; i < numSlaves; i++) { // restore the cluster 223 TEST_UTIL.getMiniHBaseCluster().startRegionServer(); 224 } 225 226 // Check on alter table 227 ADMIN.disableTable(tableName); 228 assertTrue(ADMIN.isTableDisabled(tableName)); 229 // increase the replica 230 ADMIN.modifyTable( 231 TableDescriptorBuilder.newBuilder(desc).setRegionReplication(numReplica + 1).build()); 232 ADMIN.enableTable(tableName); 233 LOG.info(ADMIN.getDescriptor(tableName).toString()); 234 assertTrue(ADMIN.isTableEnabled(tableName)); 235 TEST_UTIL.waitUntilAllRegionsAssigned(tableName); 236 TEST_UTIL.waitUntilNoRegionsInTransition(); 237 List<RegionInfo> regions = TEST_UTIL.getMiniHBaseCluster().getMaster().getAssignmentManager() 238 .getRegionStates().getRegionsOfTable(tableName); 239 assertTrue("regions.size=" + regions.size() + ", numRegions=" + numRegions + ", numReplica=" 240 + numReplica, regions.size() == numRegions * (numReplica + 1)); 241 242 // decrease the replica(earlier, table was modified to have a replica count of numReplica + 1) 243 ADMIN.disableTable(tableName); 244 ADMIN.modifyTable( 245 TableDescriptorBuilder.newBuilder(desc).setRegionReplication(numReplica).build()); 246 ADMIN.enableTable(tableName); 247 assertTrue(ADMIN.isTableEnabled(tableName)); 248 TEST_UTIL.waitUntilAllRegionsAssigned(tableName); 249 TEST_UTIL.waitUntilNoRegionsInTransition(); 250 regions = TEST_UTIL.getMiniHBaseCluster().getMaster().getAssignmentManager().getRegionStates() 251 .getRegionsOfTable(tableName); 252 assertEquals(numRegions * numReplica, regions.size()); 253 // also make sure the meta table has the replica locations removed 254 hris = MetaTableAccessor.getTableRegions(ADMIN.getConnection(), tableName); 255 assertEquals(numRegions * numReplica, hris.size()); 256 // just check that the number of default replica regions in the meta table are the same 257 // as the number of regions the table was created with, and the count of the 258 // replicas is numReplica for each region 259 Map<RegionInfo, Integer> defaultReplicas = new HashMap<>(); 260 for (RegionInfo hri : hris) { 261 RegionInfo regionReplica0 = RegionReplicaUtil.getRegionInfoForDefaultReplica(hri); 262 Integer i = defaultReplicas.get(regionReplica0); 263 defaultReplicas.put(regionReplica0, i == null ? 1 : i + 1); 264 } 265 assertEquals(numRegions, defaultReplicas.size()); 266 Collection<Integer> counts = new HashSet<>(defaultReplicas.values()); 267 assertEquals(1, counts.size()); 268 assertTrue(counts.contains(numReplica)); 269 } finally { 270 ADMIN.disableTable(tableName); 271 ADMIN.deleteTable(tableName); 272 } 273 } 274 275 private void assertRegionStateNotNull(List<RegionInfo> hris, int numRegions, int numReplica) { 276 // check that the master created expected number of RegionState objects 277 for (int i = 0; i < numRegions; i++) { 278 for (int j = 0; j < numReplica; j++) { 279 RegionInfo replica = RegionReplicaUtil.getRegionInfoForReplica(hris.get(i), j); 280 RegionState state = TEST_UTIL.getHBaseCluster().getMaster().getAssignmentManager() 281 .getRegionStates().getRegionState(replica); 282 assertNotNull(state); 283 } 284 } 285 } 286 287 @Test 288 public void testIncompleteMetaTableReplicaInformation() throws Exception { 289 final TableName tableName = TableName.valueOf(name.getMethodName()); 290 final int numRegions = 3; 291 final int numReplica = 2; 292 try { 293 // Create a table and let the meta table be updated with the location of the 294 // region locations. 295 TableDescriptor desc = 296 TableDescriptorBuilder.newBuilder(tableName).setRegionReplication(numReplica) 297 .setColumnFamily(ColumnFamilyDescriptorBuilder.of("family")).build(); 298 ADMIN.createTable(desc, Bytes.toBytes("A"), Bytes.toBytes("Z"), numRegions); 299 TEST_UTIL.waitUntilAllRegionsAssigned(tableName); 300 TEST_UTIL.waitUntilNoRegionsInTransition(); 301 Set<byte[]> tableRows = new HashSet<>(); 302 List<RegionInfo> hris = MetaTableAccessor.getTableRegions(ADMIN.getConnection(), tableName); 303 for (RegionInfo hri : hris) { 304 tableRows.add(hri.getRegionName()); 305 } 306 ADMIN.disableTable(tableName); 307 // now delete one replica info from all the rows 308 // this is to make the meta appear to be only partially updated 309 Table metaTable = ADMIN.getConnection().getTable(TableName.META_TABLE_NAME); 310 for (byte[] row : tableRows) { 311 Delete deleteOneReplicaLocation = new Delete(row); 312 deleteOneReplicaLocation.addColumns(HConstants.CATALOG_FAMILY, 313 MetaTableAccessor.getServerColumn(1)); 314 deleteOneReplicaLocation.addColumns(HConstants.CATALOG_FAMILY, 315 MetaTableAccessor.getSeqNumColumn(1)); 316 deleteOneReplicaLocation.addColumns(HConstants.CATALOG_FAMILY, 317 MetaTableAccessor.getStartCodeColumn(1)); 318 metaTable.delete(deleteOneReplicaLocation); 319 } 320 metaTable.close(); 321 // even if the meta table is partly updated, when we re-enable the table, we should 322 // get back the desired number of replicas for the regions 323 ADMIN.enableTable(tableName); 324 assertTrue(ADMIN.isTableEnabled(tableName)); 325 TEST_UTIL.waitUntilAllRegionsAssigned(tableName); 326 TEST_UTIL.waitUntilNoRegionsInTransition(); 327 List<RegionInfo> regions = TEST_UTIL.getMiniHBaseCluster().getMaster().getAssignmentManager() 328 .getRegionStates().getRegionsOfTable(tableName); 329 assertEquals(numRegions * numReplica, regions.size()); 330 } finally { 331 ADMIN.disableTable(tableName); 332 ADMIN.deleteTable(tableName); 333 } 334 } 335 336 private void validateNumberOfRowsInMeta(final TableName table, int numRegions, 337 Connection connection) throws IOException { 338 assert (ADMIN.tableExists(table)); 339 final AtomicInteger count = new AtomicInteger(); 340 Visitor visitor = new Visitor() { 341 @Override 342 public boolean visit(Result r) throws IOException { 343 if (MetaTableAccessor.getRegionInfo(r).getTable().equals(table)) count.incrementAndGet(); 344 return true; 345 } 346 }; 347 MetaTableAccessor.fullScanRegions(connection, visitor); 348 assertEquals(numRegions, count.get()); 349 } 350 351 private void validateFromSnapshotFromMeta(HBaseTestingUtility util, TableName table, 352 int numRegions, int numReplica, Connection connection) throws IOException { 353 SnapshotOfRegionAssignmentFromMeta snapshot = 354 new SnapshotOfRegionAssignmentFromMeta(connection); 355 snapshot.initialize(); 356 Map<RegionInfo, ServerName> regionToServerMap = snapshot.getRegionToRegionServerMap(); 357 assert (regionToServerMap.size() == numRegions * numReplica + 1); // '1' for the namespace 358 Map<ServerName, List<RegionInfo>> serverToRegionMap = snapshot.getRegionServerToRegionMap(); 359 for (Map.Entry<ServerName, List<RegionInfo>> entry : serverToRegionMap.entrySet()) { 360 if (entry.getKey().equals(util.getHBaseCluster().getMaster().getServerName())) { 361 continue; 362 } 363 List<RegionInfo> regions = entry.getValue(); 364 Set<byte[]> setOfStartKeys = new HashSet<>(); 365 for (RegionInfo region : regions) { 366 byte[] startKey = region.getStartKey(); 367 if (region.getTable().equals(table)) { 368 setOfStartKeys.add(startKey); // ignore other tables 369 LOG.info("--STARTKEY {}--", new String(startKey, StandardCharsets.UTF_8)); 370 } 371 } 372 // the number of startkeys will be equal to the number of regions hosted in each server 373 // (each server will be hosting one replica of a region) 374 assertEquals(numRegions, setOfStartKeys.size()); 375 } 376 } 377 378 private void validateSingleRegionServerAssignment(Connection connection, int numRegions, 379 int numReplica) throws IOException { 380 SnapshotOfRegionAssignmentFromMeta snapshot = 381 new SnapshotOfRegionAssignmentFromMeta(connection); 382 snapshot.initialize(); 383 Map<RegionInfo, ServerName> regionToServerMap = snapshot.getRegionToRegionServerMap(); 384 assertEquals(regionToServerMap.size(), numRegions * numReplica + 1); 385 Map<ServerName, List<RegionInfo>> serverToRegionMap = snapshot.getRegionServerToRegionMap(); 386 assertEquals("One Region Only", 1, serverToRegionMap.keySet().size()); 387 for (Map.Entry<ServerName, List<RegionInfo>> entry : serverToRegionMap.entrySet()) { 388 if (entry.getKey().equals(TEST_UTIL.getHBaseCluster().getMaster().getServerName())) { 389 continue; 390 } 391 assertEquals(entry.getValue().size(), numRegions * numReplica + 1); 392 } 393 } 394}