// 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; import java.util.Map; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import com.twitter.heron.api.generated.TopologyAPI; import com.twitter.heron.packing.roundrobin.RoundRobinPacking; import com.twitter.heron.proto.scheduler.Scheduler; import com.twitter.heron.proto.system.PackingPlans; import com.twitter.heron.scheduler.client.ISchedulerClient; import com.twitter.heron.scheduler.utils.Runtime; import com.twitter.heron.spi.common.Config; import com.twitter.heron.spi.common.Key; import com.twitter.heron.spi.statemgr.SchedulerStateManagerAdaptor; import com.twitter.heron.spi.utils.PackingTestUtils; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.any; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.eq; 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; @RunWith(PowerMockRunner.class) public class RuntimeManagerRunnerTest { private static final String TOPOLOGY_NAME = "testTopology"; private final Config config = mock(Config.class); private final Config runtime = mock(Config.class); @Before public void setUp() throws Exception { when(config.getStringValue(Key.TOPOLOGY_NAME)).thenReturn(TOPOLOGY_NAME); } private RuntimeManagerRunner newRuntimeManagerRunner(Command command) { return newRuntimeManagerRunner(command, mock(ISchedulerClient.class)); } private RuntimeManagerRunner newRuntimeManagerRunner(Command command, ISchedulerClient client) { return spy(new RuntimeManagerRunner(config, runtime, command, client)); } @Test public void testCallRestart() throws Exception { // Restart Runner RuntimeManagerRunner restartRunner = newRuntimeManagerRunner(Command.RESTART); doNothing().when(restartRunner).restartTopologyHandler(TOPOLOGY_NAME); restartRunner.call(); verify(restartRunner).restartTopologyHandler(TOPOLOGY_NAME); } @Test public void testCallKill() throws Exception { // Kill Runner RuntimeManagerRunner killRunner = newRuntimeManagerRunner(Command.KILL); doNothing().when(killRunner).killTopologyHandler(TOPOLOGY_NAME); killRunner.call(); verify(killRunner).killTopologyHandler(TOPOLOGY_NAME); } @Test(expected = TopologyRuntimeManagementException.class) public void testRestartTopologyHandlerFailRestartTopology() { ISchedulerClient client = mock(ISchedulerClient.class); SchedulerStateManagerAdaptor adaptor = mock(SchedulerStateManagerAdaptor.class); RuntimeManagerRunner runner = newRuntimeManagerRunner(Command.RESTART, client); // Restart container 1, not containing TMaster Scheduler.RestartTopologyRequest restartTopologyRequest = Scheduler.RestartTopologyRequest.newBuilder() .setTopologyName(TOPOLOGY_NAME).setContainerIndex(1).build(); when(config.getIntegerValue(Key.TOPOLOGY_CONTAINER_ID)).thenReturn(1); when(client.restartTopology(restartTopologyRequest)).thenReturn(false); try { runner.restartTopologyHandler(TOPOLOGY_NAME); } finally { verify(adaptor, never()).deleteTMasterLocation(TOPOLOGY_NAME); } } @Test public void testRestartTopologyHandlerSuccRestartTopology() { ISchedulerClient client = mock(ISchedulerClient.class); SchedulerStateManagerAdaptor adaptor = mock(SchedulerStateManagerAdaptor.class); RuntimeManagerRunner runner = newRuntimeManagerRunner(Command.RESTART, client); // Restart container 1, not containing TMaster Scheduler.RestartTopologyRequest restartTopologyRequest = Scheduler.RestartTopologyRequest.newBuilder() .setTopologyName(TOPOLOGY_NAME).setContainerIndex(1).build(); when(config.getIntegerValue(Key.TOPOLOGY_CONTAINER_ID)).thenReturn(1); // Success case when(client.restartTopology(restartTopologyRequest)).thenReturn(true); runner.restartTopologyHandler(TOPOLOGY_NAME); // Should not invoke DeleteTMasterLocation verify(adaptor, never()).deleteTMasterLocation(TOPOLOGY_NAME); } @Test(expected = TopologyRuntimeManagementException.class) public void testRestartTopologyHandlerFailDeleteTMasterLoc() { ISchedulerClient client = mock(ISchedulerClient.class); SchedulerStateManagerAdaptor adaptor = mock(SchedulerStateManagerAdaptor.class); RuntimeManagerRunner runner = newRuntimeManagerRunner(Command.RESTART, client); // Restart container 1, not containing TMaster Scheduler.RestartTopologyRequest restartTopologyRequest = Scheduler.RestartTopologyRequest.newBuilder() .setTopologyName(TOPOLOGY_NAME).setContainerIndex(1).build(); when(config.getIntegerValue(Key.TOPOLOGY_CONTAINER_ID)).thenReturn(1); // Restart container 0, containing TMaster when(config.getIntegerValue(Key.TOPOLOGY_CONTAINER_ID)).thenReturn(0); when(runtime.get(Key.SCHEDULER_STATE_MANAGER_ADAPTOR)).thenReturn(adaptor); when(adaptor.deleteTMasterLocation(TOPOLOGY_NAME)).thenReturn(false); try { runner.restartTopologyHandler(TOPOLOGY_NAME); } finally { // DeleteTMasterLocation should be invoked verify(adaptor).deleteTMasterLocation(TOPOLOGY_NAME); } } @Test(expected = TopologyRuntimeManagementException.class) public void testKillTopologyHandlerClientCantKill() { Scheduler.KillTopologyRequest killTopologyRequest = Scheduler.KillTopologyRequest.newBuilder() .setTopologyName(TOPOLOGY_NAME).build(); ISchedulerClient client = mock(ISchedulerClient.class); RuntimeManagerRunner runner = newRuntimeManagerRunner(Command.KILL, client); // Failed to invoke client's killTopology when(client.killTopology(killTopologyRequest)).thenReturn(false); try { runner.killTopologyHandler(TOPOLOGY_NAME); } finally { verify(client).killTopology(killTopologyRequest); } } @Test(expected = TopologyRuntimeManagementException.class) public void testKillTopologyHandlerFailCleanState() { Scheduler.KillTopologyRequest killTopologyRequest = Scheduler.KillTopologyRequest.newBuilder() .setTopologyName(TOPOLOGY_NAME).build(); ISchedulerClient client = mock(ISchedulerClient.class); RuntimeManagerRunner runner = newRuntimeManagerRunner(Command.KILL, client); // Failed to invoke client's killTopology when(client.killTopology(killTopologyRequest)).thenReturn(true); doThrow(new TopologyRuntimeManagementException("")).when(runner).cleanState( eq(TOPOLOGY_NAME), any(SchedulerStateManagerAdaptor.class)); try { runner.killTopologyHandler(TOPOLOGY_NAME); } finally { verify(client).killTopology(killTopologyRequest); } } @Test public void testKillTopologyHandlerOk() { Scheduler.KillTopologyRequest killTopologyRequest = Scheduler.KillTopologyRequest.newBuilder() .setTopologyName(TOPOLOGY_NAME).build(); ISchedulerClient client = mock(ISchedulerClient.class); RuntimeManagerRunner runner = newRuntimeManagerRunner(Command.KILL, client); when(client.killTopology(killTopologyRequest)).thenReturn(true); // Success case doNothing().when(runner).cleanState( eq(TOPOLOGY_NAME), any(SchedulerStateManagerAdaptor.class)); runner.killTopologyHandler(TOPOLOGY_NAME); verify(client).killTopology(killTopologyRequest); } @PrepareForTest(Runtime.class) @Test public void testUpdateTopologyHandler() throws Exception { String newParallelism = "testSpout:1,testBolt:4"; doUpdateTopologyHandlerTest(newParallelism, true); } @PrepareForTest(Runtime.class) @Test(expected = TopologyRuntimeManagementException.class) public void testUpdateTopologyHandlerWithSameParallelism() throws Exception { String newParallelism = "testSpout:2,testBolt:3"; // same as current test packing plan doUpdateTopologyHandlerTest(newParallelism, false); } private void doUpdateTopologyHandlerTest(String newParallelism, boolean expectedResult) { ISchedulerClient client = mock(ISchedulerClient.class); SchedulerStateManagerAdaptor manager = mock(SchedulerStateManagerAdaptor.class); RuntimeManagerRunner runner = newRuntimeManagerRunner(Command.UPDATE, client); PowerMockito.mockStatic(Runtime.class); PowerMockito.when(Runtime.schedulerStateManagerAdaptor(runtime)).thenReturn(manager); RoundRobinPacking packing = new RoundRobinPacking(); PackingPlans.PackingPlan currentPlan = PackingTestUtils.testProtoPackingPlan(TOPOLOGY_NAME, packing); PackingPlans.PackingPlan proposedPlan = PackingTestUtils.testProtoPackingPlan(TOPOLOGY_NAME, packing); Map<String, Integer> changeRequests = runner.parseNewParallelismParam(newParallelism); when(manager.getPackingPlan(eq(TOPOLOGY_NAME))).thenReturn(currentPlan); doReturn(proposedPlan).when(runner).buildNewPackingPlan( eq(currentPlan), eq(changeRequests), any(TopologyAPI.Topology.class)); Scheduler.UpdateTopologyRequest updateTopologyRequest = Scheduler.UpdateTopologyRequest.newBuilder() .setCurrentPackingPlan(currentPlan) .setProposedPackingPlan(proposedPlan) .build(); when(client.updateTopology(updateTopologyRequest)).thenReturn(true); try { runner.updateTopologyHandler(TOPOLOGY_NAME, newParallelism); } finally { int expectedClientUpdateCalls = expectedResult ? 1 : 0; verify(client, times(expectedClientUpdateCalls)).updateTopology(updateTopologyRequest); } } @Test public void testParseNewParallelismParam() { RuntimeManagerRunner runner = newRuntimeManagerRunner(Command.SUBMIT); Map<String, Integer> changes = runner.parseNewParallelismParam("foo:1,bar:2"); assertEquals(2, changes.size()); assertEquals(new Integer(1), changes.get("foo")); assertEquals(new Integer(2), changes.get("bar")); } @Test public void testParseNewParallelismParamEmpty() { RuntimeManagerRunner runner = newRuntimeManagerRunner(Command.SUBMIT); Map<String, Integer> changes = runner.parseNewParallelismParam(""); assertEquals(0, changes.size()); } @Test(expected = IllegalArgumentException.class) public void testParseNewParallelismParamInvalid1() { RuntimeManagerRunner runner = newRuntimeManagerRunner(Command.SUBMIT); try { runner.parseNewParallelismParam("foo:1,bar2"); } catch (IllegalArgumentException e) { assertEquals(e.getMessage(), "Invalid parallelism parameter found. Expected: " + "<component>:<parallelism>[,<component>:<parallelism>], Found: foo:1,bar2"); throw e; } } @Test(expected = IllegalArgumentException.class) public void testParseNewParallelismParamInvalid12() { RuntimeManagerRunner runner = newRuntimeManagerRunner(Command.SUBMIT); try { runner.parseNewParallelismParam("foo:1bar:2"); } catch (IllegalArgumentException e) { assertEquals(e.getMessage(), "Invalid parallelism parameter found. Expected: " + "<component>:<parallelism>[,<component>:<parallelism>], Found: foo:1bar:2"); throw e; } } @Test public void testParallelismDelta() { doTestParallelismDelta("foo:1,bar:2", "foo:3", "foo:2"); doTestParallelismDelta("foo:1,bar:2", "foo:1", ""); doTestParallelismDelta("foo:1,bar:2", "bar:1", "bar:-1"); } private void doTestParallelismDelta(String initial, String changes, String delta) { RuntimeManagerRunner runner = newRuntimeManagerRunner(Command.SUBMIT); Map<String, Integer> initialCounts = runner.parseNewParallelismParam(initial); Map<String, Integer> changeRequest = runner.parseNewParallelismParam(changes); assertEquals(runner.parseNewParallelismParam(delta), runner.parallelismDelta(initialCounts, changeRequest)); } }