/** * 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 * <p/> * http://www.apache.org/licenses/LICENSE-2.0 * <p/> * 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.hdfs.server.datanode; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.hdfs.server.datanode.BPServiceActor.Scheduler; import org.junit.Rule; import org.junit.Test; import org.junit.rules.Timeout; import java.util.Arrays; import java.util.List; import java.util.Random; import static java.lang.Math.abs; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; /** * Verify the block report and heartbeat scheduling logic of BPServiceActor * using a few different values . */ public class TestBpServiceActorScheduler { protected static final Log LOG = LogFactory.getLog(TestBpServiceActorScheduler.class); @Rule public Timeout timeout = new Timeout(300000); private static final long HEARTBEAT_INTERVAL_MS = 5000; // 5 seconds private static final long LIFELINE_INTERVAL_MS = 3 * HEARTBEAT_INTERVAL_MS; private static final long BLOCK_REPORT_INTERVAL_MS = 10000; // 10 seconds private static final long SLOW_PEER_REPORT_INTERVAL_MS = 10000; // 10 seconds private final Random random = new Random(System.nanoTime()); @Test public void testInit() { for (final long now : getTimestamps()) { Scheduler scheduler = makeMockScheduler(now); assertTrue(scheduler.isHeartbeatDue(now)); assertTrue(scheduler.isBlockReportDue(scheduler.monotonicNow())); } } @Test public void testScheduleBlockReportImmediate() { for (final long now : getTimestamps()) { Scheduler scheduler = makeMockScheduler(now); scheduler.scheduleBlockReport(0); assertTrue(scheduler.resetBlockReportTime); assertThat(scheduler.nextBlockReportTime, is(now)); } } @Test public void testScheduleBlockReportDelayed() { for (final long now : getTimestamps()) { Scheduler scheduler = makeMockScheduler(now); final long delayMs = 10; scheduler.scheduleBlockReport(delayMs); assertTrue(scheduler.resetBlockReportTime); assertTrue(scheduler.nextBlockReportTime - now >= 0); assertTrue(scheduler.nextBlockReportTime - (now + delayMs) < 0); } } /** * If resetBlockReportTime is true then the next block report must be scheduled * in the range [now, now + BLOCK_REPORT_INTERVAL_SEC). */ @Test public void testScheduleNextBlockReport() { for (final long now : getTimestamps()) { Scheduler scheduler = makeMockScheduler(now); assertTrue(scheduler.resetBlockReportTime); scheduler.scheduleNextBlockReport(); assertTrue(scheduler.nextBlockReportTime - (now + BLOCK_REPORT_INTERVAL_MS) < 0); } } /** * If resetBlockReportTime is false then the next block report must be scheduled * exactly at (now + BLOCK_REPORT_INTERVAL_SEC). */ @Test public void testScheduleNextBlockReport2() { for (final long now : getTimestamps()) { Scheduler scheduler = makeMockScheduler(now); scheduler.resetBlockReportTime = false; scheduler.scheduleNextBlockReport(); assertThat(scheduler.nextBlockReportTime, is(now + BLOCK_REPORT_INTERVAL_MS)); } } /** * Tests the case when a block report was delayed past its scheduled time. * In that case the next block report should not be delayed for a full interval. */ @Test public void testScheduleNextBlockReport3() { for (final long now : getTimestamps()) { Scheduler scheduler = makeMockScheduler(now); scheduler.resetBlockReportTime = false; // Make it look like the block report was scheduled to be sent between 1-3 // intervals ago but sent just now. final long blockReportDelay = BLOCK_REPORT_INTERVAL_MS + random.nextInt(2 * (int) BLOCK_REPORT_INTERVAL_MS); final long origBlockReportTime = now - blockReportDelay; scheduler.nextBlockReportTime = origBlockReportTime; scheduler.scheduleNextBlockReport(); assertTrue(scheduler.nextBlockReportTime - now < BLOCK_REPORT_INTERVAL_MS); assertTrue(((scheduler.nextBlockReportTime - origBlockReportTime) % BLOCK_REPORT_INTERVAL_MS) == 0); } } @Test public void testScheduleHeartbeat() { for (final long now : getTimestamps()) { Scheduler scheduler = makeMockScheduler(now); scheduler.scheduleNextHeartbeat(); assertFalse(scheduler.isHeartbeatDue(now)); scheduler.scheduleHeartbeat(); assertTrue(scheduler.isHeartbeatDue(now)); } } /** * Regression test for HDFS-9305. * Delayed processing of a heartbeat can cause a subsequent heartbeat * storm. */ @Test public void testScheduleDelayedHeartbeat() { for (final long now : getTimestamps()) { Scheduler scheduler = makeMockScheduler(now); scheduler.scheduleNextHeartbeat(); assertFalse(scheduler.isHeartbeatDue(now)); // Simulate a delayed heartbeat e.g. due to slow processing by NN. scheduler.nextHeartbeatTime = now - (HEARTBEAT_INTERVAL_MS * 10); scheduler.scheduleNextHeartbeat(); // Ensure that the next heartbeat is not due immediately. assertFalse(scheduler.isHeartbeatDue(now)); } } @Test public void testScheduleLifeline() { for (final long now : getTimestamps()) { Scheduler scheduler = makeMockScheduler(now); scheduler.scheduleNextLifeline(now); assertFalse(scheduler.isLifelineDue(now)); assertThat(scheduler.getLifelineWaitTime(), is(LIFELINE_INTERVAL_MS)); scheduler.scheduleNextLifeline(now - LIFELINE_INTERVAL_MS); assertTrue(scheduler.isLifelineDue(now)); assertThat(scheduler.getLifelineWaitTime(), is(0L)); } } @Test public void testSlowPeerReportScheduling() { for (final long now : getTimestamps()) { Scheduler scheduler = makeMockScheduler(now); assertTrue(scheduler.isSlowPeersReportDue(now)); scheduler.scheduleNextSlowPeerReport(); assertFalse(scheduler.isSlowPeersReportDue(now)); assertFalse(scheduler.isSlowPeersReportDue(now + 1)); assertTrue(scheduler.isSlowPeersReportDue( now + SLOW_PEER_REPORT_INTERVAL_MS)); } } private Scheduler makeMockScheduler(long now) { LOG.info("Using now = " + now); Scheduler mockScheduler = spy(new Scheduler( HEARTBEAT_INTERVAL_MS, LIFELINE_INTERVAL_MS, BLOCK_REPORT_INTERVAL_MS, SLOW_PEER_REPORT_INTERVAL_MS)); doReturn(now).when(mockScheduler).monotonicNow(); mockScheduler.nextBlockReportTime = now; mockScheduler.nextHeartbeatTime = now; mockScheduler.nextSlowPeersReportTime = now; return mockScheduler; } List<Long> getTimestamps() { return Arrays.asList( 0L, Long.MIN_VALUE, Long.MAX_VALUE, // test boundaries Long.MAX_VALUE - 1, // test integer overflow abs(random.nextLong()), // positive random -abs(random.nextLong())); // negative random } }