// Copyright 2016 Twitter. All rights reserved.
//
// Licensed 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 com.twitter.heron.scheduler.local;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import com.twitter.heron.proto.scheduler.Scheduler;
import com.twitter.heron.spi.common.Config;
import com.twitter.heron.spi.common.Key;
import com.twitter.heron.spi.packing.PackingPlan;
import com.twitter.heron.spi.utils.PackingTestUtils;
public class LocalSchedulerTest {
private static final String TOPOLOGY_NAME = "testTopology";
private static final int MAX_WAITING_SECOND = 10;
private static LocalScheduler scheduler;
private Config config;
private Config runtime;
@Before
public void before() throws Exception {
scheduler = Mockito.spy(LocalScheduler.class);
config = Mockito.mock(Config.class);
Mockito.when(config.getStringValue(Key.TOPOLOGY_NAME)).thenReturn(TOPOLOGY_NAME);
runtime = Mockito.mock(Config.class);
scheduler.initialize(config, runtime);
}
@After
public void after() throws Exception {
scheduler.close();
}
@Test
public void testClose() throws Exception {
LocalScheduler localScheduler = new LocalScheduler();
localScheduler.initialize(Mockito.mock(Config.class), Mockito.mock(Config.class));
// The MonitorService should started
ExecutorService monitorService = localScheduler.getMonitorService();
Assert.assertFalse(monitorService.isShutdown());
Assert.assertEquals(0, localScheduler.getProcessToContainer().size());
// The MonitorService should shutdown
localScheduler.close();
Assert.assertTrue(monitorService.isShutdown());
}
@Test
public void testOnSchedule() throws Exception {
Mockito.doNothing().
when(scheduler).startExecutorMonitor(Mockito.anyInt(), Mockito.any(Process.class));
Process[] mockProcesses = new Process[4];
for (int i = 0; i < 4; i++) {
mockProcesses[i] = Mockito.mock(Process.class);
Mockito.doReturn(mockProcesses[i]).when(scheduler).startExecutorProcess(i);
}
PackingPlan packingPlan = Mockito.mock(PackingPlan.class);
Set<PackingPlan.ContainerPlan> containers = new HashSet<>();
containers.add(PackingTestUtils.testContainerPlan(1));
containers.add(PackingTestUtils.testContainerPlan(3));
Mockito.when(packingPlan.getContainers()).thenReturn(containers);
Assert.assertTrue(scheduler.onSchedule(packingPlan));
verifyIdsOfLaunchedContainers(0, 1, 3);
for (int i = 0; i < 4; i++) {
if (i == 2) {
// id 2 was not in the container plan
continue;
}
Mockito.verify(scheduler).startExecutor(i);
Mockito.verify(scheduler).startExecutorProcess(i);
Mockito.verify(scheduler).startExecutorMonitor(i, mockProcesses[i]);
}
}
@Test
public void testAddContainer() throws Exception {
Mockito.when(runtime.getLongValue(Key.NUM_CONTAINERS)).thenReturn(2L);
scheduler.initialize(config, runtime);
//verify plan is deployed and containers are created
Mockito.doNothing().
when(scheduler).startExecutorMonitor(Mockito.anyInt(), Mockito.any(Process.class));
Process mockProcessTM = Mockito.mock(Process.class);
Mockito.doReturn(mockProcessTM).when(scheduler).startExecutorProcess(0);
Process mockProcessWorker1 = Mockito.mock(Process.class);
Mockito.doReturn(mockProcessWorker1).when(scheduler).startExecutorProcess(1);
PackingPlan packingPlan = Mockito.mock(PackingPlan.class);
Set<PackingPlan.ContainerPlan> containers = new HashSet<>();
containers.add(PackingTestUtils.testContainerPlan(1));
Mockito.when(packingPlan.getContainers()).thenReturn(containers);
Assert.assertTrue(scheduler.onSchedule(packingPlan));
Mockito.verify(scheduler, Mockito.times(2)).startExecutor(Mockito.anyInt());
//now verify add container adds new container
Process mockProcessWorker2 = Mockito.mock(Process.class);
Mockito.doReturn(mockProcessWorker2).when(scheduler).startExecutorProcess(3);
containers.clear();
containers.add(PackingTestUtils.testContainerPlan(3));
scheduler.addContainers(containers);
Mockito.verify(scheduler).startExecutor(3);
Process mockProcess = Mockito.mock(Process.class);
Mockito.doReturn(mockProcess).when(scheduler).startExecutorProcess(Mockito.anyInt());
containers.clear();
containers.add(PackingTestUtils.testContainerPlan(4));
containers.add(PackingTestUtils.testContainerPlan(5));
scheduler.addContainers(containers);
Mockito.verify(scheduler).startExecutor(4);
Mockito.verify(scheduler).startExecutor(5);
}
/**
* Verify containers can be removed by Local Scheduler
*/
@Test
public void testRemoveContainer() throws Exception {
final int LOCAL_NUM_CONTAINER = 6;
//verify plan is deployed and containers are created
Mockito.doNothing().
when(scheduler).startExecutorMonitor(Mockito.anyInt(), Mockito.any(Process.class));
Process[] processes = new Process[LOCAL_NUM_CONTAINER];
Set<PackingPlan.ContainerPlan> existingContainers = new HashSet<>();
for (int i = 0; i < LOCAL_NUM_CONTAINER; i++) {
processes[i] = Mockito.mock(Process.class);
Mockito.doReturn(processes[i]).when(scheduler).startExecutorProcess(i);
if (i > 0) {
// ignore the container for TMaster. existing containers simulate the containers created
// by packing plan
existingContainers.add(PackingTestUtils.testContainerPlan(i));
}
}
PackingPlan packingPlan = Mockito.mock(PackingPlan.class);
Mockito.when(packingPlan.getContainers()).thenReturn(existingContainers);
Assert.assertTrue(scheduler.onSchedule(packingPlan));
verifyIdsOfLaunchedContainers(0, 1, 2, 3, 4, 5);
Mockito.verify(scheduler, Mockito.times(LOCAL_NUM_CONTAINER)).startExecutor(Mockito.anyInt());
Set<PackingPlan.ContainerPlan> containersToRemove = new HashSet<>();
PackingPlan.ContainerPlan containerToRemove =
PackingTestUtils.testContainerPlan(LOCAL_NUM_CONTAINER - 1);
containersToRemove.add(containerToRemove);
scheduler.removeContainers(containersToRemove);
verifyIdsOfLaunchedContainers(0, 1, 2, 3, 4);
Mockito.verify(processes[LOCAL_NUM_CONTAINER - 1]).destroy();
// verify no new process restarts
Mockito.verify(scheduler, Mockito.times(LOCAL_NUM_CONTAINER)).startExecutor(Mockito.anyInt());
containersToRemove.clear();
containersToRemove.add(PackingTestUtils.testContainerPlan(1));
containersToRemove.add(PackingTestUtils.testContainerPlan(2));
scheduler.removeContainers(containersToRemove);
verifyIdsOfLaunchedContainers(0, 3, 4);
Mockito.verify(processes[1]).destroy();
Mockito.verify(processes[2]).destroy();
// verify no new process restarts
Mockito.verify(scheduler, Mockito.times(LOCAL_NUM_CONTAINER)).startExecutor(Mockito.anyInt());
}
private void verifyIdsOfLaunchedContainers(int... ids) {
HashSet<Integer> containerIdsLaunched =
new HashSet<>(scheduler.getProcessToContainer().values());
Assert.assertEquals(ids.length, containerIdsLaunched.size());
for (int i = 0; i < ids.length; i++) {
Assert.assertTrue(containerIdsLaunched.contains(ids[i]));
}
}
@Test
public void testOnKill() throws Exception {
Process mockProcess = Mockito.mock(Process.class);
scheduler.getProcessToContainer().put(mockProcess, 0);
Assert.assertTrue(scheduler.onKill(Scheduler.KillTopologyRequest.getDefaultInstance()));
Assert.assertEquals(0, scheduler.getProcessToContainer().size());
Assert.assertTrue(scheduler.isTopologyKilled());
Mockito.verify(mockProcess).destroy();
}
@Test
public void testOnRestart() throws Exception {
Process mockProcess = Mockito.mock(Process.class);
// Restart only one container
Scheduler.RestartTopologyRequest restartOne =
Scheduler.RestartTopologyRequest.newBuilder().
setTopologyName(TOPOLOGY_NAME).
setContainerIndex(1).
build();
scheduler.getProcessToContainer().put(mockProcess, 0);
// Failed to restart due to container not exist
Assert.assertFalse(scheduler.onRestart(restartOne));
// Happy to restart one container
Scheduler.RestartTopologyRequest restartZero =
Scheduler.RestartTopologyRequest.newBuilder().
setTopologyName(TOPOLOGY_NAME).
setContainerIndex(0).
build();
scheduler.getProcessToContainer().put(mockProcess, 0);
Assert.assertTrue(scheduler.onRestart(restartZero));
// Happy to restart all containers
Scheduler.RestartTopologyRequest restartAll =
Scheduler.RestartTopologyRequest.newBuilder().
setTopologyName(TOPOLOGY_NAME).
setContainerIndex(-1).
build();
scheduler.getProcessToContainer().put(mockProcess, 0);
Assert.assertTrue(scheduler.onRestart(restartAll));
}
@Test
public void testStartExecutorMonitorNotKilled() throws Exception {
int containerId = 0;
int exitValue = 1;
Process containerExecutor = Mockito.mock(Process.class);
Mockito.doReturn(exitValue).when(containerExecutor).exitValue();
Mockito.doNothing().when(scheduler).startExecutor(Mockito.anyInt());
// Start the process
scheduler.getProcessToContainer().put(containerExecutor, containerId);
scheduler.startExecutorMonitor(containerId, containerExecutor);
// Shut down the MonitorService
scheduler.getMonitorService().shutdown();
scheduler.getMonitorService().awaitTermination(MAX_WAITING_SECOND, TimeUnit.SECONDS);
// The dead process should be restarted
Mockito.verify(scheduler).startExecutor(containerId);
Assert.assertFalse(scheduler.isTopologyKilled());
}
@Test
public void testStartExecutorMonitorKilled() throws Exception {
int containerId = 0;
int exitValue = 1;
Process containerExecutor = Mockito.mock(Process.class);
Mockito.doReturn(exitValue).when(containerExecutor).exitValue();
Mockito.doNothing().when(scheduler).startExecutor(Mockito.anyInt());
// Set the killed flag and the dead process should not be restarted
scheduler.onKill(Scheduler.KillTopologyRequest.getDefaultInstance());
// Start the process
scheduler.getProcessToContainer().put(containerExecutor, containerId);
scheduler.startExecutorMonitor(containerId, containerExecutor);
// Shut down the MonitorService
scheduler.getMonitorService().shutdown();
scheduler.getMonitorService().awaitTermination(MAX_WAITING_SECOND, TimeUnit.SECONDS);
// The dead process should not be restarted
Mockito.verify(scheduler, Mockito.never()).startExecutor(Mockito.anyInt());
Assert.assertTrue(scheduler.isTopologyKilled());
}
}