Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -695,9 +695,6 @@ public boolean isMetaLoaded() {
* </p>
*/
public void checkIfShouldMoveSystemRegionAsync() {
// TODO: Fix this thread. If a server is killed and a new one started, this thread thinks that
// it should 'move' the system tables from the old server to the new server but
// ServerCrashProcedure is on it; and it will take care of the assign without dataloss.
if (this.master.getServerManager().countOfRegionServers() <= 1) {
return;
}
Expand All @@ -711,37 +708,37 @@ public void checkIfShouldMoveSystemRegionAsync() {
try {
synchronized (checkIfShouldMoveSystemRegionLock) {
List<RegionPlan> plans = new ArrayList<>();
// TODO: I don't think this code does a good job if all servers in cluster have same
// version. It looks like it will schedule unnecessary moves.
for (ServerName server : getExcludedServersForSystemTable()) {
if (master.getServerManager().isServerDead(server)) {
// TODO: See HBASE-18494 and HBASE-18495. Though getExcludedServersForSystemTable()
// considers only online servers, the server could be queued for dead server
// processing. As region assignments for crashed server is handled by
// ServerCrashProcedure, do NOT handle them here. The goal is to handle this through
// regular flow of LoadBalancer as a favored node and not to have this special
// handling.
if (!master.getServerManager().isServerOnline(server)) {
// Leave regions on crashed servers to ServerCrashProcedure.
continue;
}
List<RegionInfo> regionsShouldMove = getSystemTables(server);
if (!regionsShouldMove.isEmpty()) {
for (RegionInfo regionInfo : regionsShouldMove) {
// null value for dest forces destination server to be selected by balancer
RegionPlan plan = new RegionPlan(regionInfo, server, null);
if (regionInfo.isMetaRegion()) {
// Must move meta region first.
LOG.info("Async MOVE of {} to newer Server={}", regionInfo.getEncodedName(),
server);
moveAsync(plan);
} else {
plans.add(plan);
}
}
for (RegionInfo regionInfo : getSystemTables(server)) {
// null value for dest forces destination server to be selected by balancer
plans.add(new RegionPlan(regionInfo, server, null));
}
}
// Submit meta moves before other system regions, and submit each plan only once.
plans.sort(Comparator.comparing(plan -> !plan.getRegionInfo().isMetaRegion()));
for (RegionPlan plan : plans) {
RegionStateNode regionNode = regionStates.getRegionStateNode(plan.getRegionInfo());
if (regionNode == null || !master.getServerManager().isServerOnline(plan.getSource())) {
continue;
}
for (RegionPlan plan : plans) {
LOG.info("Async MOVE of {} to newer Server={}", plan.getRegionInfo().getEncodedName(),
server);
moveAsync(plan);
if (regionNode.isTransitionScheduled()) {
LOG.debug("Skip system region move for {}; a transition is already scheduled",
regionNode);
continue;
}
try {
// balance checks the source location; preTransitCheck still guards against a
// transition starting after the check above.
if (balance(plan) != null) {
LOG.info("Async MOVE of {} from {} to a newer RegionServer",
plan.getRegionInfo().getEncodedName(), plan.getSource());
}
} catch (HBaseIOException e) {
LOG.warn("Failed system region move {}, skipping this plan", plan, e);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hbase.master.assignment;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseIOException;
import org.apache.hadoop.hbase.ServerName;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.RegionInfo;
import org.apache.hadoop.hbase.client.RegionInfoBuilder;
import org.apache.hadoop.hbase.master.MasterServices;
import org.apache.hadoop.hbase.master.RegionPlan;
import org.apache.hadoop.hbase.master.RegionState.State;
import org.apache.hadoop.hbase.master.ServerManager;
import org.apache.hadoop.hbase.testclassification.MasterTests;
import org.apache.hadoop.hbase.testclassification.SmallTests;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

@Tag(MasterTests.TAG)
@Tag(SmallTests.TAG)
public class TestMoveSystemRegions {

private static final ServerName OLD_SERVER = ServerName.valueOf("old", 16020, 1);
private static final ServerName OTHER_OLD_SERVER = ServerName.valueOf("other-old", 16020, 1);
private static final ServerName NEW_SERVER = ServerName.valueOf("new", 16020, 1);
private static final RegionInfo META = RegionInfoBuilder.FIRST_META_REGIONINFO;
private static final RegionInfo SYSTEM_REGION =
RegionInfoBuilder.newBuilder(TableName.valueOf("hbase:test")).build();

private AssignmentManager am;
private ServerManager serverManager;
private final List<RegionInfo> movedRegions = new ArrayList<>();

@BeforeEach
public void setUp() throws Exception {
MasterServices master = mock(MasterServices.class);
when(master.getConfiguration()).thenReturn(new Configuration(false));
serverManager = mock(ServerManager.class);
when(master.getServerManager()).thenReturn(serverManager);
when(serverManager.countOfRegionServers()).thenReturn(3);
when(serverManager.getOnlineServersList())
.thenReturn(List.of(OLD_SERVER, OTHER_OLD_SERVER, NEW_SERVER));
when(serverManager.isServerOnline(any())).thenReturn(true);
when(master.getRegionServerVersion(OLD_SERVER)).thenReturn("2.6.4");
when(master.getRegionServerVersion(OTHER_OLD_SERVER)).thenReturn("2.6.4");
when(master.getRegionServerVersion(NEW_SERVER)).thenReturn("4.0.0");
am = spy(new AssignmentManager(master, null));
doAnswer(invocation -> {
RegionPlan plan = invocation.getArgument(0);
movedRegions.add(plan.getRegionInfo());
return CompletableFuture.completedFuture(null);
}).when(am).moveAsync(any());
}

private RegionStateNode addRegion(RegionInfo region, ServerName server) {
RegionStateNode node = am.getRegionStates().getOrCreateRegionStateNode(region);
node.setState(State.OPEN);
node.setRegionLocation(server);
am.getRegionStates().createServer(server);
am.getRegionStates().addRegionToServer(node);
return node;
}

private void checkSystemRegions() throws Exception {
CompletableFuture<Thread> checker = new CompletableFuture<>();
doAnswer(invocation -> {
checker.complete(Thread.currentThread());
return invocation.callRealMethod();
}).when(am).getExcludedServersForSystemTable();
am.checkIfShouldMoveSystemRegionAsync();
Thread thread = checker.get(10, TimeUnit.SECONDS);
thread.join(TimeUnit.SECONDS.toMillis(10));
assertFalse(thread.isAlive(), "System region check did not finish");
}

@Test
public void testSkipMetaInTransition() throws Exception {
RegionStateNode meta = addRegion(META, OLD_SERVER);
meta.setState(State.OPENING);
TransitRegionStateProcedure recovery = mock(TransitRegionStateProcedure.class);
meta.setProcedure(recovery);
addRegion(SYSTEM_REGION, OLD_SERVER);
// Exercise the real preTransitCheck if the compatibility check tries to move meta.
doCallRealMethod().when(am).moveAsync(argThat(p -> p.getRegionInfo().isMetaRegion()));

checkSystemRegions();

assertEquals(List.of(SYSTEM_REGION), movedRegions);
assertSame(recovery, meta.getProcedure());
assertEquals(State.OPENING, meta.getState());
verify(am, never()).moveAsync(argThat(p -> p.getRegionInfo().isMetaRegion()));
}

@Test
public void testSubmitEachPlanOnceAndMetaFirst() throws Exception {
addRegion(SYSTEM_REGION, OLD_SERVER);
addRegion(META, OTHER_OLD_SERVER);

checkSystemRegions();

assertEquals(List.of(META, SYSTEM_REGION), movedRegions);
verify(am, times(2)).moveAsync(any());
}

@Test
public void testSkipOfflineSource() throws Exception {
addRegion(META, OLD_SERVER);
addRegion(SYSTEM_REGION, OTHER_OLD_SERVER);
// The online-server snapshot can become stale before we examine its regions.
when(serverManager.isServerOnline(OLD_SERVER)).thenReturn(false);

checkSystemRegions();

assertEquals(List.of(SYSTEM_REGION), movedRegions);
}

@Test
public void testSkipChangedRegionLocation() throws Exception {
RegionStateNode meta = addRegion(META, OLD_SERVER);
meta.setRegionLocation(NEW_SERVER);
addRegion(SYSTEM_REGION, OTHER_OLD_SERVER);

checkSystemRegions();

assertEquals(List.of(SYSTEM_REGION), movedRegions);
}

@Test
public void testContinueAfterMoveFailure() throws Exception {
addRegion(META, OLD_SERVER);
addRegion(SYSTEM_REGION, OLD_SERVER);
doThrow(new HBaseIOException("Region entered transition after the check")).when(am)
.moveAsync(argThat(p -> p.getRegionInfo().isMetaRegion()));

checkSystemRegions();

assertEquals(List.of(SYSTEM_REGION), movedRegions);
}
}
Loading