// 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.net.URI;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
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.scheduler.dryrun.SubmitDryRunResponse;
import com.twitter.heron.scheduler.utils.LauncherUtils;
import com.twitter.heron.spi.common.Config;
import com.twitter.heron.spi.common.Key;
import com.twitter.heron.spi.packing.IPacking;
import com.twitter.heron.spi.packing.PackingException;
import com.twitter.heron.spi.scheduler.ILauncher;
import com.twitter.heron.spi.statemgr.IStateManager;
import com.twitter.heron.spi.statemgr.SchedulerStateManagerAdaptor;
import com.twitter.heron.spi.uploader.IUploader;
import com.twitter.heron.spi.uploader.UploaderException;
import com.twitter.heron.spi.utils.PackingTestUtils;
import com.twitter.heron.spi.utils.ReflectionUtils;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.atLeastOnce;
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.verify;
import static org.mockito.Mockito.when;
@RunWith(PowerMockRunner.class)
@PrepareForTest({LauncherUtils.class, ReflectionUtils.class})
public class SubmitterMainTest {
private static final String TOPOLOGY_NAME = "topologyName";
private static final String STATE_MANAGER_CLASS = "STATE_MANAGER_CLASS";
private static final String LAUNCHER_CLASS = "LAUNCHER_CLASS";
private static final String PACKING_CLASS = "PACKING_CLASS";
private static final String UPLOADER_CLASS = "UPLOADER_CLASS";
private IStateManager statemgr;
private ILauncher launcher;
private IUploader uploader;
private Config config;
private TopologyAPI.Topology topology;
@Before
public void setUp() throws Exception {
// Mock objects to be verified
IPacking packing = mock(IPacking.class);
statemgr = mock(IStateManager.class);
launcher = mock(ILauncher.class);
uploader = mock(IUploader.class);
// Mock ReflectionUtils stuff
PowerMockito.spy(ReflectionUtils.class);
PowerMockito.doReturn(statemgr)
.when(ReflectionUtils.class, "newInstance", STATE_MANAGER_CLASS);
PowerMockito.doReturn(launcher)
.when(ReflectionUtils.class, "newInstance", LAUNCHER_CLASS);
PowerMockito.doReturn(packing)
.when(ReflectionUtils.class, "newInstance", PACKING_CLASS);
PowerMockito.doReturn(uploader)
.when(ReflectionUtils.class, "newInstance", UPLOADER_CLASS);
config = mock(Config.class);
when(config.getStringValue(Key.STATE_MANAGER_CLASS))
.thenReturn(STATE_MANAGER_CLASS);
when(config.getStringValue(Key.LAUNCHER_CLASS))
.thenReturn(LAUNCHER_CLASS);
when(config.getStringValue(Key.PACKING_CLASS))
.thenReturn(PACKING_CLASS);
when(config.getStringValue(Key.UPLOADER_CLASS))
.thenReturn(UPLOADER_CLASS);
when(packing.pack())
.thenReturn(PackingTestUtils.testPackingPlan(TOPOLOGY_NAME, new RoundRobinPacking()));
topology = TopologyAPI.Topology.getDefaultInstance();
}
@Test
public void testValidateSubmit() throws Exception {
SubmitterMain submitterMain = new SubmitterMain(config, topology);
SchedulerStateManagerAdaptor adaptor = mock(SchedulerStateManagerAdaptor.class);
// Topology is not running
when(adaptor.isTopologyRunning(eq(TOPOLOGY_NAME))).thenReturn(null);
submitterMain.validateSubmit(adaptor, TOPOLOGY_NAME);
when(adaptor.isTopologyRunning(eq(TOPOLOGY_NAME))).thenReturn(false);
submitterMain.validateSubmit(adaptor, TOPOLOGY_NAME);
}
@Test(expected = TopologySubmissionException.class)
public void testValidateSubmitAlreadyRunning() throws Exception {
SubmitterMain submitterMain = new SubmitterMain(config, topology);
SchedulerStateManagerAdaptor adaptor = mock(SchedulerStateManagerAdaptor.class);
// Topology is running
when(adaptor.isTopologyRunning(eq(TOPOLOGY_NAME))).thenReturn(true);
submitterMain.validateSubmit(adaptor, TOPOLOGY_NAME);
}
@Test(expected = TopologySubmissionException.class)
public void testSubmitTopologyAlreadyRunning() throws Exception {
// Topology is running
SubmitterMain submitterMain = spy(new SubmitterMain(config, topology));
doThrow(new TopologySubmissionException("")).when(submitterMain)
.validateSubmit(any(SchedulerStateManagerAdaptor.class), anyString());
try {
submitterMain.submitTopology();
} finally {
verify(uploader, atLeastOnce()).close();
verify(launcher, atLeastOnce()).close();
verify(statemgr, atLeastOnce()).close();
}
}
@Test(expected = UploaderException.class)
public void testSubmitTopologyClassNotExist() throws Exception {
final String CLASS_NOT_EXIST = "class_not_exist";
when(config.getStringValue(Key.UPLOADER_CLASS)).thenReturn(CLASS_NOT_EXIST);
SubmitterMain submitterMain = spy(new SubmitterMain(config, topology));
doNothing().when(submitterMain)
.validateSubmit(any(SchedulerStateManagerAdaptor.class), anyString());
try {
submitterMain.submitTopology();
} finally {
verify(uploader, never()).close();
verify(launcher, never()).close();
verify(statemgr, never()).close();
when(config.getStringValue(Key.UPLOADER_CLASS)).thenReturn(UPLOADER_CLASS);
}
}
@Test(expected = UploaderException.class)
public void testSubmitTopologyUploaderException() throws Exception {
SubmitterMain submitterMain = spy(new SubmitterMain(config, topology));
doNothing().when(submitterMain)
.validateSubmit(any(SchedulerStateManagerAdaptor.class), anyString());
doThrow(new UploaderException("")).when(submitterMain).uploadPackage(eq(uploader));
try {
submitterMain.submitTopology();
} finally {
verify(uploader, never()).undo();
verify(uploader).close();
verify(launcher).close();
verify(statemgr).close();
}
}
@Test(expected = PackingException.class)
public void testSubmitTopologyLauncherException() throws Exception {
SubmitterMain submitterMain = spy(new SubmitterMain(config, topology));
doNothing().when(submitterMain)
.validateSubmit(any(SchedulerStateManagerAdaptor.class), anyString());
final URI packageURI = new URI("mock://uri:924/x#ke");
doReturn(packageURI).when(submitterMain).uploadPackage(eq(uploader));
doThrow(new PackingException("")).when(submitterMain)
.callLauncherRunner(Mockito.any(Config.class));
submitterMain.submitTopology();
}
@Test(expected = SubmitDryRunResponse.class)
public void testSubmitTopologyDryRun() throws Exception {
SubmitterMain submitterMain = spy(new SubmitterMain(config, topology));
when(config.getBooleanValue(Key.DRY_RUN)).thenReturn(true);
try {
submitterMain.submitTopology();
} finally {
/* under dry-run mode, the program should not
1. upload topology package
2. validate that topology is not running
*/
verify(uploader, never()).uploadPackage();
verify(statemgr, never()).initialize(any(Config.class));
}
}
@Test
public void testSubmitTopologySuccessful() throws Exception {
when(config.getBooleanValue(Key.DRY_RUN)).thenReturn(false);
SubmitterMain submitterMain = spy(new SubmitterMain(config, topology));
doNothing().when(submitterMain)
.validateSubmit(any(SchedulerStateManagerAdaptor.class), anyString());
doNothing().when(submitterMain).callLauncherRunner(Mockito.any(Config.class));
submitterMain.submitTopology();
}
}