// 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.mesos.framework;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.mesos.Protos;
import org.apache.mesos.SchedulerDriver;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import com.twitter.heron.spi.common.Config;
import com.twitter.heron.spi.common.Key;
public class MesosFrameworkTest {
private static final String TOPOLOGY_NAME = "testTopology";
private static final String ROLE = "role";
private static final long NUM_CONTAINER = 2;
private static final int CONTAINER_INDEX = 0;
private static final String TOPOLOGY_PACKAGE_URI = "topologyPackageURI";
private static final String CORE_PACKAGE_URI = "corePackageURI";
private Config config;
private Config runtime;
private MesosFramework mesosFramework;
protected Protos.Offer getOffer() {
final double CPU = 1;
Protos.Offer.Builder builder =
Protos.Offer.newBuilder()
.setId(Protos.OfferID.newBuilder().setValue("offer-id"))
.setFrameworkId(Protos.FrameworkID.newBuilder().setValue("framework-id"))
.setHostname("hostname")
.setSlaveId(Protos.SlaveID.newBuilder().setValue("slave-id"));
Protos.Resource cpu =
Protos.Resource.newBuilder()
.setType(Protos.Value.Type.SCALAR)
.setName(TaskResources.CPUS_RESOURCE_NAME)
.setScalar(
Protos.Value.Scalar.newBuilder().setValue(CPU))
.build();
Protos.Resource port =
Protos.Resource.newBuilder()
.setType(Protos.Value.Type.RANGES)
.setName(TaskResources.PORT_RESOURCE_NAME)
.setRanges(Protos.Value.Ranges.newBuilder()
.addRange(Protos.Value.Range.newBuilder().setBegin(0).setEnd(5)))
.build();
builder.addResources(cpu).addResources(port);
Protos.Offer offer = builder.build();
return offer;
}
public Map<Protos.Offer, TaskResources> getOfferResources(Protos.Offer offer) {
TaskResources resources = TaskResources.apply(offer, "*");
Map<Protos.Offer, TaskResources> offerResources = new HashMap<>();
offerResources.put(offer, resources);
return offerResources;
}
@Before
public void before() throws Exception {
config = Mockito.mock(Config.class);
Mockito.when(config.getStringValue(Key.TOPOLOGY_NAME)).thenReturn(TOPOLOGY_NAME);
Mockito.when(config.getStringValue(Key.ROLE)).thenReturn(ROLE);
Mockito.when(config.getStringValue(Key.CORE_PACKAGE_URI)).thenReturn(CORE_PACKAGE_URI);
runtime = Mockito.mock(Config.class);
Mockito.when(runtime.getLongValue(Key.NUM_CONTAINERS)).thenReturn(NUM_CONTAINER);
Properties properties = new Properties();
properties.put(Key.TOPOLOGY_PACKAGE_URI.value(), TOPOLOGY_PACKAGE_URI);
Mockito.when(runtime.get(Key.SCHEDULER_PROPERTIES)).thenReturn(properties);
mesosFramework = Mockito.spy(new MesosFramework(config, runtime));
SchedulerDriver driver = Mockito.mock(SchedulerDriver.class);
// Register the mesos framework
Protos.FrameworkID frameworkID =
Protos.FrameworkID.newBuilder().setValue("framework-id").build();
mesosFramework.registered(driver, frameworkID, Protos.MasterInfo.getDefaultInstance());
}
@After
public void after() throws Exception {
mesosFramework.getSchedulerDriver().stop();
}
@Test
public void testCreateJob() throws Exception {
BaseContainer container = new BaseContainer();
container.name = TaskUtils.getTaskNameForContainerIndex(CONTAINER_INDEX);
Map<Integer, BaseContainer> jobDef = new HashMap<>();
jobDef.put(CONTAINER_INDEX, container);
Assert.assertTrue(mesosFramework.createJob(jobDef));
Assert.assertEquals(1, mesosFramework.getContainersInfo().size());
Assert.assertEquals(1, mesosFramework.getTasksId().size());
Assert.assertEquals(1, mesosFramework.getToScheduleTasks().size());
Assert.assertTrue(
mesosFramework.getToScheduleTasks().peek().startsWith(
String.format("container_%d", CONTAINER_INDEX)));
}
@Test
public void testKillJob() throws Exception {
String taskId = "task_id";
mesosFramework.getToScheduleTasks().add(taskId);
mesosFramework.getTasksId().put(CONTAINER_INDEX, taskId);
Assert.assertTrue(mesosFramework.killJob());
Assert.assertTrue(mesosFramework.getToScheduleTasks().isEmpty());
Assert.assertTrue(mesosFramework.getTasksId().isEmpty());
Assert.assertTrue(mesosFramework.isTerminated());
Mockito.verify(mesosFramework.getSchedulerDriver())
.killTask(Protos.TaskID.newBuilder().setValue(taskId).build());
}
@Test
public void testRestartSingleContainer() throws Exception {
String taskId = "task_id";
mesosFramework.getTasksId().put(CONTAINER_INDEX, taskId);
Assert.assertTrue(mesosFramework.restartJob(CONTAINER_INDEX));
Assert.assertFalse(mesosFramework.isTerminated());
Mockito.verify(mesosFramework.getSchedulerDriver())
.killTask(Protos.TaskID.newBuilder().setValue(taskId).build());
}
@Test
public void testRestartAllContainer() throws Exception {
String taskId = "task_id";
mesosFramework.getTasksId().put(CONTAINER_INDEX, taskId);
// -1 means to restart all containers
Assert.assertTrue(mesosFramework.restartJob(-1));
Assert.assertFalse(mesosFramework.isTerminated());
Mockito.verify(mesosFramework.getSchedulerDriver())
.killTask(Protos.TaskID.newBuilder().setValue(taskId).build());
}
@Test
@SuppressWarnings("unchecked")
public void testDeclineUnusedOffer() throws Exception {
Protos.Offer offer = getOffer();
List<LaunchableTask> tasksToLaunch = new ArrayList<>();
Mockito.doReturn(tasksToLaunch).
when(mesosFramework).generateLaunchableTasks(Mockito.any(Map.class));
List<Protos.Offer> offers = new ArrayList<>();
offers.add(offer);
mesosFramework.resourceOffers(mesosFramework.getSchedulerDriver(), offers);
// The unused offer should be declined
Mockito.verify(mesosFramework.getSchedulerDriver()).declineOffer(offer.getId());
}
@Test
@SuppressWarnings("unchecked")
public void testConsumeOffer() throws Exception {
Protos.Offer offer = getOffer();
List<LaunchableTask> tasksToLaunch = new ArrayList<>();
LaunchableTask launchableTask =
new LaunchableTask("", new BaseContainer(), offer, new ArrayList<Integer>());
tasksToLaunch.add(launchableTask);
Mockito.doReturn(tasksToLaunch).
when(mesosFramework).generateLaunchableTasks(Mockito.any(Map.class));
Mockito.doNothing().when(mesosFramework).launchTasks(tasksToLaunch);
List<Protos.Offer> offers = new ArrayList<>();
offers.add(offer);
mesosFramework.resourceOffers(mesosFramework.getSchedulerDriver(), offers);
// The offer should be used and not be declined
Mockito.verify(mesosFramework.getSchedulerDriver(), Mockito.never())
.declineOffer(offer.getId());
}
@Test
public void testHandleFailure() throws Exception {
String taskName = TaskUtils.getTaskNameForContainerIndex(CONTAINER_INDEX);
String oldTaskId = TaskUtils.getTaskId(taskName, 0);
BaseContainer baseContainer = new BaseContainer();
baseContainer.retries = Integer.MAX_VALUE;
baseContainer.name = taskName;
mesosFramework.getContainersInfo().put(CONTAINER_INDEX, baseContainer);
mesosFramework.handleMesosFailure(oldTaskId);
String newTaskId = TaskUtils.getTaskId(taskName, 1);
Mockito.verify(mesosFramework).scheduleNewTask(newTaskId);
}
@Test
public void testScheduleNewTask() throws Exception {
String taskName = TaskUtils.getTaskNameForContainerIndex(CONTAINER_INDEX);
String newTaskId = TaskUtils.getTaskId(taskName, 0);
mesosFramework.scheduleNewTask(newTaskId);
Assert.assertEquals(1, mesosFramework.getTasksId().size());
Assert.assertEquals(newTaskId, mesosFramework.getTasksId().get(CONTAINER_INDEX));
Assert.assertEquals(1, mesosFramework.getToScheduleTasks().size());
Assert.assertEquals(newTaskId, mesosFramework.getToScheduleTasks().peek());
}
@Test
public void testGenerateLaunchableTasks() throws Exception {
Protos.Offer offer = getOffer();
Map<Protos.Offer, TaskResources> offerResources = getOfferResources(offer);
BaseContainer baseContainer = new BaseContainer();
baseContainer.cpu = 0.1;
baseContainer.memInMB = 0;
baseContainer.diskInMB = 0;
baseContainer.ports = 0;
String taskName = TaskUtils.getTaskNameForContainerIndex(CONTAINER_INDEX);
String taskId = TaskUtils.getTaskId(taskName, 0);
mesosFramework.getToScheduleTasks().add(taskId);
mesosFramework.getContainersInfo().put(CONTAINER_INDEX, baseContainer);
List<LaunchableTask> tasks = mesosFramework.generateLaunchableTasks(offerResources);
Assert.assertEquals(1, tasks.size());
LaunchableTask task = tasks.get(0);
Assert.assertEquals(offer, task.offer);
Assert.assertEquals(baseContainer, task.baseContainer);
Assert.assertEquals(taskId, task.taskId);
// We don't request any ports
Assert.assertTrue(task.freePorts.isEmpty());
}
@Test
public void testInsufficientResourcesGenerateLaunchableTasks() throws Exception {
Protos.Offer offer = getOffer();
Map<Protos.Offer, TaskResources> offerResources = getOfferResources(offer);
BaseContainer baseContainer = new BaseContainer();
// request more cpu than provided
baseContainer.cpu = 10000;
baseContainer.memInMB = 0;
baseContainer.diskInMB = 0;
baseContainer.ports = 0;
String taskName = TaskUtils.getTaskNameForContainerIndex(CONTAINER_INDEX);
String taskId = TaskUtils.getTaskId(taskName, 0);
mesosFramework.getToScheduleTasks().add(taskId);
mesosFramework.getContainersInfo().put(CONTAINER_INDEX, baseContainer);
List<LaunchableTask> tasks = mesosFramework.generateLaunchableTasks(offerResources);
// The provides resources could not meet the needed one
Assert.assertEquals(0, tasks.size());
}
@Test
public void testLaunchTasks() throws Exception {
Protos.Offer offer = getOffer();
String taskId = TaskUtils.getTaskId(
TaskUtils.getTaskNameForContainerIndex(CONTAINER_INDEX), 0);
List<LaunchableTask> tasksToLaunch = new ArrayList<>();
LaunchableTask launchableTask = Mockito.spy(
new LaunchableTask(taskId, new BaseContainer(), offer, new ArrayList<Integer>()));
Protos.TaskInfo pTask = Protos.TaskInfo.getDefaultInstance();
Mockito.doReturn(pTask).when(launchableTask).constructMesosTaskInfo(config, runtime);
tasksToLaunch.add(launchableTask);
List<Protos.TaskInfo> mesosTasks = new LinkedList<>();
mesosTasks.add(pTask);
// Launch topology successfully
Mockito.when(mesosFramework.getSchedulerDriver().launchTasks(
Mockito.anyCollectionOf(Protos.OfferID.class),
Mockito.anyCollectionOf(Protos.TaskInfo.class)))
.thenReturn(Protos.Status.DRIVER_RUNNING);
mesosFramework.launchTasks(tasksToLaunch);
// Should not invoke handleMesosFailure
Mockito.verify(mesosFramework, Mockito.never()).handleMesosFailure(taskId);
// Verify the launching
Mockito.verify(mesosFramework.getSchedulerDriver())
.launchTasks(Arrays.asList(new Protos.OfferID[]{offer.getId()}),
mesosTasks);
// Failed to launch the tasks
Mockito.when(mesosFramework.getSchedulerDriver().launchTasks(
Mockito.anyCollectionOf(Protos.OfferID.class),
Mockito.anyCollectionOf(Protos.TaskInfo.class)))
.thenReturn(Protos.Status.DRIVER_NOT_STARTED);
Mockito.doNothing().when(mesosFramework).handleMesosFailure(taskId);
mesosFramework.launchTasks(tasksToLaunch);
Mockito.verify(mesosFramework).handleMesosFailure(taskId);
}
}