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