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.assignment; 019 020import static org.junit.jupiter.api.Assertions.assertFalse; 021import static org.junit.jupiter.api.Assertions.assertTrue; 022import static org.junit.jupiter.api.Assertions.fail; 023 024import java.io.IOException; 025import java.util.List; 026import java.util.stream.Collectors; 027import java.util.stream.IntStream; 028import java.util.stream.Stream; 029import org.apache.hadoop.conf.Configuration; 030import org.apache.hadoop.fs.FileSystem; 031import org.apache.hadoop.fs.Path; 032import org.apache.hadoop.hbase.HBaseIOException; 033import org.apache.hadoop.hbase.HBaseTestingUtil; 034import org.apache.hadoop.hbase.TableName; 035import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 036import org.apache.hadoop.hbase.client.Put; 037import org.apache.hadoop.hbase.client.RegionInfo; 038import org.apache.hadoop.hbase.client.RegionReplicaUtil; 039import org.apache.hadoop.hbase.client.Table; 040import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 041import org.apache.hadoop.hbase.master.HMaster; 042import org.apache.hadoop.hbase.master.procedure.MasterProcedureEnv; 043import org.apache.hadoop.hbase.testclassification.MasterTests; 044import org.apache.hadoop.hbase.testclassification.MediumTests; 045import org.apache.hadoop.hbase.util.Bytes; 046import org.apache.hadoop.hbase.util.CommonFSUtils; 047import org.apache.hadoop.hbase.wal.WALSplitUtil; 048import org.junit.jupiter.api.AfterAll; 049import org.junit.jupiter.api.AfterEach; 050import org.junit.jupiter.api.BeforeAll; 051import org.junit.jupiter.api.Tag; 052import org.junit.jupiter.api.Test; 053 054@Tag(MasterTests.TAG) 055@Tag(MediumTests.TAG) 056public class TestAssignmentManagerUtil { 057 058 private static final HBaseTestingUtil UTIL = new HBaseTestingUtil(); 059 060 private static TableName TABLE_NAME = TableName.valueOf("AM"); 061 062 private static MasterProcedureEnv ENV; 063 064 private static AssignmentManager AM; 065 066 private static int REGION_REPLICATION = 3; 067 068 @BeforeAll 069 public static void setUp() throws Exception { 070 UTIL.startMiniCluster(1); 071 UTIL.getAdmin().balancerSwitch(false, true); 072 UTIL.createTable(TableDescriptorBuilder.newBuilder(TABLE_NAME) 073 .setColumnFamily(ColumnFamilyDescriptorBuilder.of("cf")) 074 .setRegionReplication(REGION_REPLICATION).build(), new byte[][] { Bytes.toBytes(0) }); 075 UTIL.waitTableAvailable(TABLE_NAME); 076 HMaster master = UTIL.getMiniHBaseCluster().getMaster(); 077 ENV = master.getMasterProcedureExecutor().getEnvironment(); 078 AM = master.getAssignmentManager(); 079 } 080 081 @AfterEach 082 public void tearDownAfterTest() throws IOException { 083 for (RegionInfo region : UTIL.getAdmin().getRegions(TABLE_NAME)) { 084 RegionStateNode regionNode = AM.getRegionStates().getRegionStateNode(region); 085 // confirm that we have released the lock 086 assertFalse(regionNode.isLocked()); 087 TransitRegionStateProcedure proc = regionNode.getProcedure(); 088 if (proc != null) { 089 regionNode.unsetProcedure(proc); 090 } 091 } 092 } 093 094 @AfterAll 095 public static void tearDown() throws Exception { 096 UTIL.shutdownMiniCluster(); 097 } 098 099 private List<RegionInfo> getPrimaryRegions() throws IOException { 100 return UTIL.getAdmin().getRegions(TABLE_NAME).stream() 101 .filter(r -> RegionReplicaUtil.isDefaultReplica(r)).collect(Collectors.toList()); 102 } 103 104 @Test 105 public void testCreateUnassignProcedureForSplitFail() throws IOException { 106 RegionInfo region = getPrimaryRegions().get(0); 107 AM.getRegionStates().getRegionStateNode(region) 108 .setProcedure(TransitRegionStateProcedure.unassign(ENV, region)); 109 try { 110 AssignmentManagerUtil.createUnassignProceduresForSplitOrMerge(ENV, Stream.of(region), 111 REGION_REPLICATION); 112 fail("Should fail as the region is in transition"); 113 } catch (HBaseIOException e) { 114 // expected 115 } 116 } 117 118 @Test 119 public void testCreateUnassignProceduresForMergeFail() throws IOException { 120 List<RegionInfo> regions = getPrimaryRegions(); 121 RegionInfo regionA = regions.get(0); 122 RegionInfo regionB = regions.get(1); 123 AM.getRegionStates().getRegionStateNode(regionB) 124 .setProcedure(TransitRegionStateProcedure.unassign(ENV, regionB)); 125 try { 126 AssignmentManagerUtil.createUnassignProceduresForSplitOrMerge(ENV, 127 Stream.of(regionA, regionB), REGION_REPLICATION); 128 fail("Should fail as the region is in transition"); 129 } catch (HBaseIOException e) { 130 // expected 131 } 132 IntStream.range(0, REGION_REPLICATION) 133 .mapToObj(i -> RegionReplicaUtil.getRegionInfoForReplica(regionA, i)) 134 .map(AM.getRegionStates()::getRegionStateNode).forEachOrdered( 135 rn -> assertFalse(rn.isTransitionScheduled(), "Should have unset the proc for " + rn)); 136 } 137 138 @Test 139 public void testCheckClosedRegionDropsStaleRecoveredEdits() throws Exception { 140 try (Table t = UTIL.getConnection().getTable(TABLE_NAME)) { 141 for (int i = 0; i < 10; i++) { 142 t.put(new Put(Bytes.toBytes(i)).addColumn(Bytes.toBytes("cf"), Bytes.toBytes("q"), 143 Bytes.toBytes(i))); 144 } 145 } 146 UTIL.getAdmin().flush(TABLE_NAME); 147 UTIL.waitFor(30_000, 148 () -> getPrimaryRegions().stream().anyMatch(r -> ENV.getMasterServices().getServerManager() 149 .getLastFlushedSequenceId(r.getEncodedNameAsBytes()).getLastFlushedSequenceId() > 0L)); 150 151 RegionInfo region = getPrimaryRegions().stream() 152 .filter(r -> ENV.getMasterServices().getServerManager() 153 .getLastFlushedSequenceId(r.getEncodedNameAsBytes()).getLastFlushedSequenceId() > 0L) 154 .findFirst().orElseThrow(() -> new AssertionError("no region has a durable seqid yet")); 155 long durable = ENV.getMasterServices().getServerManager() 156 .getLastFlushedSequenceId(region.getEncodedNameAsBytes()).getLastFlushedSequenceId(); 157 158 Configuration conf = ENV.getMasterConfiguration(); 159 Path walRegionDir = 160 CommonFSUtils.getWALRegionDir(conf, region.getTable(), region.getEncodedName()); 161 Path editsDir = WALSplitUtil.getRegionDirRecoveredEditsDir(walRegionDir); 162 FileSystem fs = CommonFSUtils.getWALFileSystem(conf); 163 fs.mkdirs(editsDir); 164 165 Path staleFile = new Path(editsDir, String.format("%019d", 1L)); 166 fs.create(staleFile).close(); 167 assertTrue(fs.exists(staleFile)); 168 169 AssignmentManagerUtil.checkClosedRegion(ENV, region); 170 assertFalse(fs.exists(staleFile), "stale recovered.edits should have been removed"); 171 172 Path freshFile = new Path(editsDir, String.format("%019d", durable + 1000L)); 173 fs.create(freshFile).close(); 174 try { 175 AssignmentManagerUtil.checkClosedRegion(ENV, region); 176 fail("checkClosedRegion should have thrown for a fresh recovered.edits file"); 177 } catch (IOException expected) { 178 // expected 179 } finally { 180 fs.delete(freshFile, false); 181 } 182 } 183}