Skip to content

Commit c4ccd7d

Browse files
Umeshkumar9414virajjasani
authored andcommitted
HBASE-30142 Resolve NPE while running recoverUnknown command because null RegionLocation (#8192)
Signed-off-by: Andrew Purtell <apurtell@apache.org> Signed-off-by: Viraj Jasani <vjasani@apache.org>
1 parent f933240 commit c4ccd7d

2 files changed

Lines changed: 116 additions & 3 deletions

File tree

hbase-server/src/main/java/org/apache/hadoop/hbase/master/ServerManager.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -980,11 +980,14 @@ public synchronized boolean isServerDead(ServerName serverName) {
980980
/**
981981
* Check if a server is unknown. A server can be online, or known to be dead, or unknown to this
982982
* manager (i.e, not online, not known to be dead either; it is simply not tracked by the master
983-
* any more, for example, a very old previous instance).
983+
* anymore, for example, a very old previous instance). A serverName with value null should not be
984+
* considered unknown. We set value of regionLocation to null just before finding the assign
985+
* candidate (in-between region transition) or while marking it OFFLINE/FAILED_OPEN. Refer
986+
* HBASE-30142.
984987
*/
985988
public boolean isServerUnknown(ServerName serverName) {
986-
return serverName == null
987-
|| (!onlineServers.containsKey(serverName) && !deadservers.isDeadServer(serverName));
989+
return serverName != null
990+
&& (!onlineServers.containsKey(serverName) && !deadservers.isDeadServer(serverName));
988991
}
989992

990993
public void shutdownCluster() {
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.hadoop.hbase.master;
19+
20+
import static org.junit.jupiter.api.Assertions.assertEquals;
21+
import static org.junit.jupiter.api.Assertions.assertFalse;
22+
import static org.junit.jupiter.api.Assertions.assertNotNull;
23+
import static org.junit.jupiter.api.Assertions.assertNull;
24+
import static org.junit.jupiter.api.Assertions.assertTrue;
25+
26+
import java.util.List;
27+
import org.apache.hadoop.hbase.HBaseTestingUtil;
28+
import org.apache.hadoop.hbase.TableName;
29+
import org.apache.hadoop.hbase.client.RegionInfo;
30+
import org.apache.hadoop.hbase.client.Table;
31+
import org.apache.hadoop.hbase.master.assignment.AssignmentManager;
32+
import org.apache.hadoop.hbase.master.assignment.RegionStateNode;
33+
import org.apache.hadoop.hbase.testclassification.MasterTests;
34+
import org.apache.hadoop.hbase.testclassification.MediumTests;
35+
import org.apache.hadoop.hbase.util.Bytes;
36+
import org.junit.jupiter.api.AfterAll;
37+
import org.junit.jupiter.api.BeforeAll;
38+
import org.junit.jupiter.api.Tag;
39+
import org.junit.jupiter.api.Test;
40+
41+
import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos;
42+
43+
/**
44+
* Test for HBASE-30142. Verifies that {@code scheduleSCPsForUnknownServers} (i.e. the
45+
* recoverUnknown HBCK command) does not throw a NullPointerException when a region's
46+
* {@code regionLocation} is null while its state is something other than OFFLINE.
47+
* <p/>
48+
* Before HBASE-30142, {@link ServerManager#isServerUnknown(org.apache.hadoop.hbase.ServerName)}
49+
* returned {@code true} for a {@code null} server name, so a region whose location had been
50+
* temporarily nulled (e.g. between region transitions or while marking it FAILED_OPEN /
51+
* ABNORMALLY_CLOSED) was treated as living on an "unknown" server. The downstream call to
52+
* {@code shouldSubmitSCP(null)} then dereferenced the null and crashed.
53+
*/
54+
@Tag(MasterTests.TAG)
55+
@Tag(MediumTests.TAG)
56+
public class TestRecoverUnknownWithNullRegionLocation {
57+
58+
private static HBaseTestingUtil UTIL;
59+
private static final TableName TABLE_NAME =
60+
TableName.valueOf("TestRecoverUnknownWithNullRegionLocation");
61+
private static final byte[] FAMILY = Bytes.toBytes("cf");
62+
63+
@BeforeAll
64+
public static void setUpBeforeClass() throws Exception {
65+
UTIL = new HBaseTestingUtil();
66+
UTIL.startMiniCluster(2);
67+
}
68+
69+
@AfterAll
70+
public static void tearDownAfterClass() throws Exception {
71+
if (UTIL != null) {
72+
UTIL.shutdownMiniCluster();
73+
}
74+
}
75+
76+
/**
77+
* Drive the region into ABNORMALLY_CLOSED through the
78+
* {@link AssignmentManager#regionClosedAbnormally(RegionStateNode)} path (the same path SCP
79+
* uses). That method also nulls {@code regionLocation} while leaving state non-OFFLINE. Then call
80+
* {@code scheduleSCPsForUnknownServers} and assert no NPE.
81+
*/
82+
@Test
83+
public void testRecoverUnknownWithAbnormallyClosedRegion() throws Exception {
84+
try (Table ignored = UTIL.createTable(TABLE_NAME, FAMILY)) {
85+
UTIL.waitTableAvailable(TABLE_NAME);
86+
87+
HMaster master = UTIL.getMiniHBaseCluster().getMaster();
88+
AssignmentManager am = master.getAssignmentManager();
89+
List<RegionInfo> regions = am.getTableRegions(TABLE_NAME, true);
90+
assertFalse(regions.isEmpty(), "expected at least one region");
91+
RegionStateNode node = am.getRegionStates().getRegionStateNode(regions.get(0));
92+
assertNotNull(node, "expected a region state node for the test region");
93+
94+
node.lock();
95+
try {
96+
am.regionClosedAbnormally(node).get();
97+
assertTrue(node.isInState(RegionState.State.ABNORMALLY_CLOSED),
98+
"regionClosedAbnormally must move state to ABNORMALLY_CLOSED");
99+
assertNull(node.getRegionLocation(), "regionClosedAbnormally must null out the location");
100+
MasterProtos.ScheduleSCPsForUnknownServersResponse response =
101+
master.getMasterRpcServices().scheduleSCPsForUnknownServers(null,
102+
MasterProtos.ScheduleSCPsForUnknownServersRequest.newBuilder().build());
103+
assertEquals(0, response.getPidCount(),
104+
"no SCPs should be scheduled for an ABNORMALLY_CLOSED region with null location");
105+
} finally {
106+
node.unlock();
107+
}
108+
}
109+
}
110+
}

0 commit comments

Comments
 (0)