// 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.packing;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.junit.Assert;
import org.junit.Test;
import com.twitter.heron.api.generated.TopologyAPI;
import com.twitter.heron.spi.common.ClusterDefaults;
import com.twitter.heron.spi.common.Config;
import com.twitter.heron.spi.common.Constants;
import com.twitter.heron.spi.common.Context;
import com.twitter.heron.spi.common.Keys;
import com.twitter.heron.spi.packing.InstanceId;
import com.twitter.heron.spi.packing.PackingPlan;
import com.twitter.heron.spi.packing.Resource;
import com.twitter.heron.spi.utils.PackingTestUtils;
import com.twitter.heron.spi.utils.TopologyTests;
public class PackingUtilsTest {
private static TopologyAPI.Topology getTopology(
int spoutParallelism, int boltParallelism,
com.twitter.heron.api.Config topologyConfig) {
return TopologyTests.createTopology("testTopology", topologyConfig, "spout", "bolt",
spoutParallelism, boltParallelism);
}
private static Set<PackingPlan.ContainerPlan> generateContainers(Integer[] containerIds,
Integer[] instanceIds) {
Set<PackingPlan.ContainerPlan> containerPlan = new HashSet<>();
for (int containerId : containerIds) {
containerPlan.add(PackingTestUtils.testContainerPlan(containerId, instanceIds));
}
return containerPlan;
}
private static PackingPlan generatePacking(Map<Integer, List<InstanceId>> basePacking)
throws RuntimeException {
Resource resource = new Resource(2.0, 6 * Constants.GB, 25 * Constants.GB);
Set<PackingPlan.ContainerPlan> containerPlans = new HashSet<>();
for (int containerId : basePacking.keySet()) {
List<InstanceId> instanceList = basePacking.get(containerId);
Set<PackingPlan.InstancePlan> instancePlans = new HashSet<>();
for (InstanceId instanceId : instanceList) {
String componentName = instanceId.getComponentName();
Resource instanceResource;
switch (componentName) {
case "bolt":
instanceResource = new Resource(1.0, 2 * Constants.GB, 10 * Constants.GB);
break;
case "spout":
instanceResource = new Resource(1.0, 3 * Constants.GB, 10 * Constants.GB);
break;
default:
throw new RuntimeException(String.format("%s is not a valid component name",
componentName));
}
instancePlans.add(new PackingPlan.InstancePlan(instanceId, instanceResource));
}
PackingPlan.ContainerPlan containerPlan =
new PackingPlan.ContainerPlan(containerId, instancePlans, resource);
containerPlans.add(containerPlan);
}
return new PackingPlan("", containerPlans);
}
/**
* Tests the sorting of containers based on the container Id.
*/
@Test
public void testContainerSortOnId() {
Integer[] containerIds = {5, 4, 1, 2, 3};
Integer[] instanceIds = {1, 2, 3};
Set<PackingPlan.ContainerPlan> containers = generateContainers(containerIds, instanceIds);
PackingPlan.ContainerPlan[] currentContainers = PackingUtils.sortOnContainerId(containers);
Assert.assertEquals(containerIds.length, currentContainers.length);
for (int i = 0; i < currentContainers.length; i++) {
Assert.assertEquals((currentContainers[i]).getId(), i + 1);
}
}
/**
* Tests the increaseBy method for long values
*/
@Test
public void testIncreaseByLong() {
long value = 1024;
int padding = 1;
long expectedResult = 1034;
Assert.assertEquals(expectedResult, PackingUtils.increaseBy(value, padding));
}
/**
* Tests the increaseBy method for double values
*/
@Test
public void testIncreaseByDouble() {
double value = 10.0;
int padding = 1;
double expectedResult = 10.1;
Assert.assertEquals(0, Double.compare(PackingUtils.increaseBy(value, padding), expectedResult));
}
/**
* Tests containerAllocation method
*/
@Test
public void testAllocateContainer() {
ArrayList<Container> containers = new ArrayList<>();
int expectedNumContainers = 1;
Assert.assertEquals(expectedNumContainers, PackingUtils.allocateNewContainer(containers,
new Resource(1, 1, 1), 10));
}
/**
* Tests the component scale up and down methods.
*/
@Test
public void testComponentScaling() {
Map<String, Integer> componentChanges = new HashMap<>();
componentChanges.put("spout", -2);
componentChanges.put("bolt1", 2);
componentChanges.put("bolt2", -1);
Map<String, Integer> componentToScaleUp = PackingUtils.getComponentsToScale(componentChanges,
PackingUtils.ScalingDirection.UP);
Assert.assertEquals(1, componentToScaleUp.size());
Assert.assertEquals(2, (int) componentToScaleUp.get("bolt1"));
Map<String, Integer> componentToScaleDown =
PackingUtils.getComponentsToScale(componentChanges, PackingUtils.ScalingDirection.DOWN);
Assert.assertEquals(2, componentToScaleDown.size());
Assert.assertEquals(-2, (int) componentToScaleDown.get("spout"));
Assert.assertEquals(-1, (int) componentToScaleDown.get("bolt2"));
}
/**
* Tests the component scale up and down methods.
*/
@Test
public void testRemoveContainers() {
Map<Integer, List<InstanceId>> allocation = new HashMap<>();
allocation.put(1, new ArrayList<InstanceId>());
ArrayList<InstanceId> instances = new ArrayList<InstanceId>();
for (int i = 1; i <= 3; i++) {
instances.add(new InstanceId("instance", 1, i));
}
allocation.put(2, instances);
ArrayList<InstanceId> instances2 = new ArrayList<InstanceId>();
instances2.add(new InstanceId("instance", 2, 1));
allocation.put(3, instances2);
allocation.put(4, new ArrayList<InstanceId>());
PackingUtils.removeEmptyContainers(allocation);
Assert.assertEquals(2, allocation.size());
Assert.assertEquals(instances, allocation.get(2));
Assert.assertEquals(instances2, allocation.get(3));
}
/**
* Tests the getContainers method.
*/
@Test
public void testGetContainers() {
int paddingPercentage = 10;
Map<Integer, List<InstanceId>> packing = new HashMap<>();
packing.put(1, Arrays.asList(
new InstanceId("spout", 1, 0),
new InstanceId("bolt", 2, 0)));
packing.put(2, Arrays.asList(
new InstanceId("spout", 3, 0),
new InstanceId("bolt", 4, 0)));
PackingPlan packingPlan = generatePacking(packing);
ArrayList<Container> containers = PackingUtils.getContainers(packingPlan, paddingPercentage);
Assert.assertEquals(2, containers.size());
for (int i = 0; i < 2; i++) {
Assert.assertEquals(paddingPercentage, containers.get(i).getPaddingPercentage());
Assert.assertEquals(packingPlan.getMaxContainerResources(), containers.get(i).getCapacity());
Assert.assertEquals(2, containers.get(i).getInstances().size());
}
}
/**
* Tests the getAllocation method.
*/
@Test
public void testGetAllocation() {
Map<Integer, List<InstanceId>> packing = new HashMap<>();
packing.put(1, Arrays.asList(
new InstanceId("spout", 2, 0),
new InstanceId("bolt", 2, 0)));
packing.put(2, Arrays.asList(
new InstanceId("spout", 3, 0),
new InstanceId("bolt", 3, 0)));
PackingPlan packingPlan = generatePacking(packing);
Map<Integer, List<InstanceId>> allocation = PackingUtils.getAllocation(packingPlan);
Assert.assertEquals(2, allocation.size());
for (int i = 1; i <= 2; i++) {
Assert.assertEquals(2, allocation.get(i).size());
}
for (int container = 1; container <= 2; container++) {
Assert.assertEquals("spout", allocation.get(container).get(0).getComponentName());
Assert.assertEquals("bolt", allocation.get(container).get(1).getComponentName());
for (int i = 0; i < 2; i++) {
Assert.assertEquals(0, allocation.get(container).get(i).getComponentIndex());
Assert.assertEquals(container + 1, allocation.get(container).get(i).getTaskId());
}
}
}
@Test
public void testResourceScaleDown() {
int noSpouts = 6;
int noBolts = 3;
int boltScalingDown = 2;
com.twitter.heron.api.Config topologyConfig = new com.twitter.heron.api.Config();
TopologyAPI.Topology topology = getTopology(noSpouts, noBolts, topologyConfig);
Config config = Config.newBuilder()
.put(Keys.topologyId(), topology.getId())
.put(Keys.topologyName(), topology.getName())
.putAll(ClusterDefaults.getDefaults())
.build();
Resource defaultInstanceResources = new Resource(
Context.instanceCpu(config),
Context.instanceRam(config),
Context.instanceDisk(config));
Map<String, Integer> componentChanges = new HashMap<>();
componentChanges.put("bolt", -boltScalingDown); // 1 bolt
Resource scaledownResource = PackingUtils.computeTotalResourceChange(topology,
componentChanges, defaultInstanceResources, PackingUtils.ScalingDirection.DOWN);
Assert.assertEquals((long) (boltScalingDown * defaultInstanceResources.getCpu()),
(long) scaledownResource.getCpu());
Assert.assertEquals(boltScalingDown * defaultInstanceResources.getRam(),
scaledownResource.getRam());
Assert.assertEquals(boltScalingDown * defaultInstanceResources.getDisk(),
scaledownResource.getDisk());
}
@Test
public void testResourceScaleUp() {
int noSpouts = 6;
int noBolts = 3;
int boltScalingUp = 2;
com.twitter.heron.api.Config topologyConfig = new com.twitter.heron.api.Config();
TopologyAPI.Topology topology = getTopology(noSpouts, noBolts, topologyConfig);
Config config = Config.newBuilder()
.put(Keys.topologyId(), topology.getId())
.put(Keys.topologyName(), topology.getName())
.putAll(ClusterDefaults.getDefaults())
.build();
Resource defaultInstanceResources = new Resource(
Context.instanceCpu(config),
Context.instanceRam(config),
Context.instanceDisk(config));
Map<String, Integer> componentChanges = new HashMap<>();
componentChanges.put("bolt", boltScalingUp); // 5 bolts
Resource scaleupResource = PackingUtils.computeTotalResourceChange(topology,
componentChanges, defaultInstanceResources, PackingUtils.ScalingDirection.UP);
Assert.assertEquals((long) (boltScalingUp * defaultInstanceResources.getCpu()),
(long) scaleupResource.getCpu());
Assert.assertEquals(boltScalingUp * defaultInstanceResources.getRam(),
scaleupResource.getRam());
Assert.assertEquals(boltScalingUp * defaultInstanceResources.getDisk(),
scaleupResource.getDisk());
}
}