// 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.roundrobin; import java.util.HashMap; import java.util.HashSet; 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.common.basics.ByteAmount; import com.twitter.heron.packing.AssertPacking; import com.twitter.heron.packing.CommonPackingTests; import com.twitter.heron.packing.utils.PackingUtils; import com.twitter.heron.spi.packing.IPacking; import com.twitter.heron.spi.packing.IRepacking; import com.twitter.heron.spi.packing.PackingException; import com.twitter.heron.spi.packing.PackingPlan; import com.twitter.heron.spi.packing.Resource; import com.twitter.heron.spi.utils.TopologyUtils; public class ResourceCompliantRRPackingTest extends CommonPackingTests { @Override protected IPacking getPackingImpl() { return new ResourceCompliantRRPacking(); } @Override protected IRepacking getRepackingImpl() { return new ResourceCompliantRRPacking(); } private int countComponent(String component, Set<PackingPlan.InstancePlan> instances) { int count = 0; for (PackingPlan.InstancePlan instancePlan : instances) { if (component.equals(instancePlan.getComponentName())) { count++; } } return count; } @Test(expected = PackingException.class) public void testFailureInsufficientContainerRamRequested() throws Exception { topologyConfig.setContainerRamRequested(ByteAmount.ZERO); pack(getTopology(spoutParallelism, boltParallelism, topologyConfig)); } /** * Test the scenario where the max container size is the default */ @Test public void testDefaultResources() throws Exception { int numContainers = 2; PackingPlan packingPlanNoExplicitResourcesConfig = pack(topology); Assert.assertEquals(numContainers, packingPlanNoExplicitResourcesConfig.getContainers().size()); Assert.assertEquals(totalInstances, packingPlanNoExplicitResourcesConfig.getInstanceCount()); } /** * Test the scenario where the max container size is the default and padding is configured */ @Test public void testDefaultContainerSizeWithPadding() throws Exception { int numContainers = 2; int padding = 50; topologyConfig.setContainerPaddingPercentage(padding); TopologyAPI.Topology newTopology = getTopology(spoutParallelism, boltParallelism, topologyConfig); PackingPlan packingPlan = pack(newTopology); Assert.assertEquals(numContainers, packingPlan.getContainers().size()); Assert.assertEquals(totalInstances, packingPlan.getInstanceCount()); } /** * Test the scenario where container level resource config are set */ @Test public void testContainerRequestedResourcesSingleContainer() throws Exception { int numContainers = 1; // Set up the topology and its config topologyConfig.put(com.twitter.heron.api.Config.TOPOLOGY_STMGRS, numContainers); // Explicit set resources for container ByteAmount containerRam = ByteAmount.fromGigabytes(10); ByteAmount containerDisk = ByteAmount.fromGigabytes(20); float containerCpu = 30; topologyConfig.setContainerRamRequested(containerRam); topologyConfig.setContainerDiskRequested(containerDisk); topologyConfig.setContainerCpuRequested(containerCpu); TopologyAPI.Topology topologyExplicitResourcesConfig = getTopology(spoutParallelism, boltParallelism, topologyConfig); PackingPlan packingPlanExplicitResourcesConfig = pack(topologyExplicitResourcesConfig); Assert.assertEquals(numContainers, packingPlanExplicitResourcesConfig.getContainers().size()); Assert.assertEquals(totalInstances, packingPlanExplicitResourcesConfig.getInstanceCount()); for (PackingPlan.ContainerPlan containerPlan : packingPlanExplicitResourcesConfig.getContainers()) { Assert.assertEquals(Math.round(PackingUtils.increaseBy(totalInstances * instanceDefaultResources.getCpu(), DEFAULT_CONTAINER_PADDING)), (long) containerPlan.getRequiredResource().getCpu()); Assert.assertEquals(instanceDefaultResources.getRam() .multiply(totalInstances) .increaseBy(DEFAULT_CONTAINER_PADDING), containerPlan.getRequiredResource().getRam()); Assert.assertEquals(instanceDefaultResources.getDisk() .multiply(totalInstances) .increaseBy(DEFAULT_CONTAINER_PADDING), containerPlan.getRequiredResource().getDisk()); // All instances' resource requirement should be equal // So the size of set should be 1 Set<Resource> resources = new HashSet<>(); for (PackingPlan.InstancePlan instancePlan : containerPlan.getInstances()) { resources.add(instancePlan.getResource()); } Assert.assertEquals(1, resources.size()); Assert.assertEquals(instanceDefaultResources.getRam(), resources.iterator().next().getRam()); } } @Test public void testContainersRequestedExceedsInstanceCount() throws Exception { doTestContainerCountRequested(8, 7); // each of the 7 instances will get their own container } /** * Test the scenario ram map config is partially set */ @Test public void testCompleteRamMapRequested() throws Exception { int numContainers = 2; // Explicit set resources for container // the value should be ignored, since we set the complete component ram map ByteAmount containerRam = ByteAmount.fromGigabytes(Long.MAX_VALUE); // Explicit set component ram map ByteAmount boltRam = ByteAmount.fromGigabytes(1); topologyConfig.setContainerRamRequested(containerRam); topologyConfig.setComponentRam(BOLT_NAME, boltRam); TopologyAPI.Topology topologyExplicitRamMap = getTopology(spoutParallelism, boltParallelism, topologyConfig); PackingPlan packingPlanExplicitRamMap = pack(topologyExplicitRamMap); Assert.assertEquals(totalInstances, packingPlanExplicitRamMap.getInstanceCount()); Assert.assertEquals(numContainers, packingPlanExplicitRamMap.getContainers().size()); AssertPacking.assertContainers(packingPlanExplicitRamMap.getContainers(), BOLT_NAME, SPOUT_NAME, boltRam, instanceDefaultResources.getRam(), containerRam); } /** * Test the scenario ram map config is fully set */ @Test public void testPartialRamMap() throws Exception { int numContainers = 2; // Explicit set resources for container ByteAmount containerRam = ByteAmount.fromGigabytes(10); // Explicit set component ram map ByteAmount boltRam = ByteAmount.fromGigabytes(1); ByteAmount spoutRam = ByteAmount.fromGigabytes(2); topologyConfig.setContainerRamRequested(containerRam); topologyConfig.setComponentRam(BOLT_NAME, boltRam); topologyConfig.setComponentRam(SPOUT_NAME, spoutRam); TopologyAPI.Topology topologyExplicitRamMap = getTopology(spoutParallelism, boltParallelism, topologyConfig); PackingPlan packingPlanExplicitRamMap = pack(topologyExplicitRamMap); Assert.assertEquals(totalInstances, packingPlanExplicitRamMap.getInstanceCount()); Assert.assertEquals(numContainers, packingPlanExplicitRamMap.getContainers().size()); AssertPacking.assertContainers(packingPlanExplicitRamMap.getContainers(), BOLT_NAME, SPOUT_NAME, boltRam, spoutRam, containerRam); } /** * Test the scenario where the user defined number of containers is not sufficient. */ @Test public void testInsufficientContainersWithOneAdjustment() throws Exception { int numContainers = 1; // Set up the topology and its config topologyConfig.put(com.twitter.heron.api.Config.TOPOLOGY_STMGRS, numContainers); // Explicit set resources for container ByteAmount containerRam = ByteAmount.fromGigabytes(2); topologyConfig.setContainerRamRequested(containerRam); TopologyAPI.Topology newTopology = getTopology(spoutParallelism, boltParallelism, topologyConfig); PackingPlan packingPlan = pack(newTopology); Assert.assertEquals(7, packingPlan.getContainers().size()); Assert.assertEquals(totalInstances, packingPlan.getInstanceCount()); } /** * Test the scenario where the user defined number of containers is not sufficient. */ @Test public void testInsufficientContainersWithMultipleAdjustments() throws Exception { int numContainers = 1; // Set up the topology and its config topologyConfig.put(com.twitter.heron.api.Config.TOPOLOGY_STMGRS, numContainers); // Explicit set resources for container ByteAmount containerRam = ByteAmount.fromGigabytes(3); // Explicit set component ram map ByteAmount boltRam = ByteAmount.fromGigabytes(1); ByteAmount spoutRam = ByteAmount.fromGigabytes(2); topologyConfig.setContainerRamRequested(containerRam); topologyConfig.setComponentRam(BOLT_NAME, boltRam); topologyConfig.setComponentRam(SPOUT_NAME, spoutRam); TopologyAPI.Topology topologyExplicitRamMap = getTopology(spoutParallelism, boltParallelism, topologyConfig); PackingPlan packingPlan = pack(topologyExplicitRamMap); Assert.assertEquals(7, packingPlan.getContainers().size()); Assert.assertEquals(totalInstances, packingPlan.getInstanceCount()); } /** * test even packing of instances */ @Test public void testEvenPacking() throws Exception { int numContainers = 2; int componentParallelism = 4; // Set up the topology and its config topologyConfig.put(com.twitter.heron.api.Config.TOPOLOGY_STMGRS, numContainers); TopologyAPI.Topology newTopology = getTopology(componentParallelism, componentParallelism, topologyConfig); int numInstance = TopologyUtils.getTotalInstance(newTopology); // Two components Assert.assertEquals(2 * componentParallelism, numInstance); PackingPlan output = pack(newTopology); Assert.assertEquals(numContainers, output.getContainers().size()); Assert.assertEquals((Integer) numInstance, output.getInstanceCount()); for (PackingPlan.ContainerPlan container : output.getContainers()) { Assert.assertEquals(numInstance / numContainers, container.getInstances().size()); Assert.assertEquals(2, countComponent("spout", container.getInstances())); Assert.assertEquals(2, countComponent("bolt", container.getInstances())); } } /** * Test the scenario where the max container size is the default * and scaling is requested. */ @Test public void testDefaultContainerSizeRepack() throws Exception { int numScalingInstances = 5; Map<String, Integer> componentChanges = new HashMap<>(); componentChanges.put(BOLT_NAME, numScalingInstances); int numContainersBeforeRepack = 2; PackingPlan newPackingPlan = doDefaultScalingTest(componentChanges, numContainersBeforeRepack); Assert.assertEquals(4, newPackingPlan.getContainers().size()); Assert.assertEquals((Integer) (totalInstances + numScalingInstances), newPackingPlan.getInstanceCount()); AssertPacking.assertContainers(newPackingPlan.getContainers(), BOLT_NAME, SPOUT_NAME, instanceDefaultResources.getRam(), instanceDefaultResources.getRam(), null); for (PackingPlan.ContainerPlan containerPlan : newPackingPlan.getContainers()) { Assert.assertEquals(Math.round(PackingUtils.increaseBy( containerPlan.getInstances().size() * instanceDefaultResources.getCpu(), DEFAULT_CONTAINER_PADDING)), (long) containerPlan.getRequiredResource().getCpu()); Assert.assertEquals(instanceDefaultResources.getRam() .multiply(containerPlan.getInstances().size()) .increaseBy(DEFAULT_CONTAINER_PADDING), containerPlan.getRequiredResource().getRam()); Assert.assertEquals(instanceDefaultResources.getDisk() .multiply(containerPlan.getInstances().size()) .increaseBy(DEFAULT_CONTAINER_PADDING), containerPlan.getRequiredResource().getDisk()); } } /** * Test the scenario ram map config is partially set and scaling is requested */ @Test public void testRepackPadding() throws Exception { int paddingPercentage = 50; topologyConfig.setContainerPaddingPercentage(paddingPercentage); // Explicit set component ram map ByteAmount boltRam = ByteAmount.fromGigabytes(4); ByteAmount maxContainerRam = ByteAmount.fromGigabytes(10); topologyConfig.setComponentRam(BOLT_NAME, boltRam); topologyConfig.setContainerRamRequested(maxContainerRam); TopologyAPI.Topology topologyExplicitRamMap = getTopology(spoutParallelism, boltParallelism, topologyConfig); int numScalingInstances = 3; Map<String, Integer> componentChanges = new HashMap<>(); componentChanges.put(BOLT_NAME, numScalingInstances); int numContainersBeforeRepack = 3; PackingPlan newPackingPlan = doScalingTest(topologyExplicitRamMap, componentChanges, boltRam, boltParallelism, instanceDefaultResources.getRam(), spoutParallelism, numContainersBeforeRepack, totalInstances); Assert.assertEquals(6, newPackingPlan.getContainers().size()); Assert.assertEquals((Integer) (totalInstances + numScalingInstances), newPackingPlan.getInstanceCount()); AssertPacking.assertContainers(newPackingPlan.getContainers(), BOLT_NAME, SPOUT_NAME, boltRam, instanceDefaultResources.getRam(), null); for (PackingPlan.ContainerPlan containerPlan : newPackingPlan.getContainers()) { //Each container either contains a single bolt or 1 bolt and 2 spouts or 1 bolt and 1 spout if (containerPlan.getInstances().size() == 1) { Assert.assertEquals(boltRam.increaseBy(paddingPercentage), containerPlan.getRequiredResource().getRam()); } if (containerPlan.getInstances().size() == 2) { Assert.assertEquals(boltRam.plus(instanceDefaultResources.getRam()) .increaseBy(paddingPercentage), containerPlan.getRequiredResource().getRam()); } if (containerPlan.getInstances().size() == 3) { Assert.assertEquals(boltRam.plus(instanceDefaultResources.getRam().multiply(2)) .increaseBy(paddingPercentage), containerPlan.getRequiredResource().getRam()); } } } /** * Test the scenario ram map config is partially set and scaling is requested */ @Test public void testPartialRamMapScaling() throws Exception { // Explicit set resources for container ByteAmount maxContainerRam = ByteAmount.fromGigabytes(10); // Explicit set component ram map ByteAmount boltRam = ByteAmount.fromGigabytes(4); topologyConfig.setContainerRamRequested(maxContainerRam); topologyConfig.setComponentRam(BOLT_NAME, boltRam); TopologyAPI.Topology topologyExplicitRamMap = getTopology(spoutParallelism, boltParallelism, topologyConfig); int numScalingInstances = 3; Map<String, Integer> componentChanges = new HashMap<>(); componentChanges.put(BOLT_NAME, numScalingInstances); int numContainersBeforeRepack = 3; PackingPlan newPackingPlan = doScalingTest(topologyExplicitRamMap, componentChanges, boltRam, boltParallelism, instanceDefaultResources.getRam(), spoutParallelism, numContainersBeforeRepack, totalInstances); Assert.assertEquals(6, newPackingPlan.getContainers().size()); Assert.assertEquals((Integer) (totalInstances + numScalingInstances), newPackingPlan.getInstanceCount()); AssertPacking.assertContainers(newPackingPlan.getContainers(), BOLT_NAME, SPOUT_NAME, boltRam, instanceDefaultResources.getRam(), null); } /** * Test the scenario where the scaling down is requested */ @Test public void testScaleDown() throws Exception { int spoutScalingDown = -2; int boltScalingDown = -1; Map<String, Integer> componentChanges = new HashMap<>(); componentChanges.put(SPOUT_NAME, spoutScalingDown); //leave 2 spouts componentChanges.put(BOLT_NAME, boltScalingDown); //leave 2 bolts int numContainersBeforeRepack = 2; PackingPlan newPackingPlan = doDefaultScalingTest(componentChanges, numContainersBeforeRepack); Assert.assertEquals(1, newPackingPlan.getContainers().size()); Assert.assertEquals((Integer) (totalInstances + spoutScalingDown + boltScalingDown), newPackingPlan.getInstanceCount()); AssertPacking.assertNumInstances(newPackingPlan.getContainers(), BOLT_NAME, 2); AssertPacking.assertNumInstances(newPackingPlan.getContainers(), SPOUT_NAME, 2); } /** * Test the scenario where the scaling down is requested and the first container is removed */ @Test public void removeFirstContainer() throws Exception { int spoutScalingDown = -3; int boltScalingDown = -3; /* The packing plan consists of two containers. The first one contains 2 spouts and 2 bolts the second one contains 2 spouts and 1 bolt. During scaling we remove 3 spouts and 3 bolts and thus the first container is removed. */ Map<String, Integer> componentChanges = new HashMap<>(); componentChanges.put(SPOUT_NAME, spoutScalingDown); //leave 1 spout componentChanges.put(BOLT_NAME, boltScalingDown); //leave 1 bolt int numContainersBeforeRepack = 2; PackingPlan newPackingPlan = doDefaultScalingTest(componentChanges, numContainersBeforeRepack); Assert.assertEquals(1, newPackingPlan.getContainers().size()); Assert.assertEquals((Integer) (totalInstances + spoutScalingDown + boltScalingDown), newPackingPlan.getInstanceCount()); AssertPacking.assertNumInstances(newPackingPlan.getContainers(), BOLT_NAME, 0); AssertPacking.assertNumInstances(newPackingPlan.getContainers(), SPOUT_NAME, 1); } /** * Test the scenario where scaling down and up is simultaneously requested and padding is * configured */ @Test public void scaleDownAndUpWithExtraPadding() throws Exception { int paddingPercentage = 50; int numContainers = 1; topologyConfig.setContainerPaddingPercentage(paddingPercentage); // Explicit set resources for container ByteAmount maxContainerRam = ByteAmount.fromGigabytes(12); // Explicit set component ram map ByteAmount spoutRam = ByteAmount.fromGigabytes(2); topologyConfig.setContainerRamRequested(maxContainerRam); topologyConfig.setComponentRam(SPOUT_NAME, spoutRam); topologyConfig.setNumStmgrs(numContainers); int noBolts = 2; int noSpouts = 1; TopologyAPI.Topology topologyExplicitRamMap = getTopology(noSpouts, noBolts, topologyConfig); int spoutScalingUp = 1; int boltScalingDown = -2; Map<String, Integer> componentChanges = new HashMap<>(); componentChanges.put(SPOUT_NAME, spoutScalingUp); // 2 spouts componentChanges.put(BOLT_NAME, boltScalingDown); // 0 bolts int numContainersBeforeRepack = 1; PackingPlan newPackingPlan = doScalingTest(topologyExplicitRamMap, componentChanges, instanceDefaultResources.getRam(), noBolts, spoutRam, noSpouts, numContainersBeforeRepack, noSpouts + noBolts); Assert.assertEquals(1, newPackingPlan.getContainers().size()); Assert.assertEquals((Integer) (noSpouts + noBolts + spoutScalingUp + boltScalingDown), newPackingPlan.getInstanceCount()); AssertPacking.assertNumInstances(newPackingPlan.getContainers(), BOLT_NAME, noBolts + boltScalingDown); AssertPacking.assertNumInstances(newPackingPlan.getContainers(), SPOUT_NAME, noSpouts + spoutScalingUp); } /** * Test the scenario where scaling down and up is simultaneously requested and padding is * configured */ @Test public void scaleDownAndUpNoPadding() throws Exception { int paddingPercentage = 0; int numContainers = 1; topologyConfig.setContainerPaddingPercentage(paddingPercentage); // Explicit set resources for container ByteAmount maxContainerRam = ByteAmount.fromGigabytes(12); // Explicit set component ram map ByteAmount spoutRam = ByteAmount.fromGigabytes(4); topologyConfig.setContainerRamRequested(maxContainerRam); topologyConfig.setComponentRam(SPOUT_NAME, spoutRam); topologyConfig.setNumStmgrs(numContainers); int noBolts = 3; int noSpouts = 1; TopologyAPI.Topology topologyExplicitRamMap = getTopology(noSpouts, noBolts, topologyConfig); int spoutScalingUp = 1; int boltScalingDown = -1; Map<String, Integer> componentChanges = new HashMap<>(); componentChanges.put(SPOUT_NAME, spoutScalingUp); // 2 spouts componentChanges.put(BOLT_NAME, boltScalingDown); // 2 bolts int numContainersBeforeRepack = 1; PackingPlan newPackingPlan = doScalingTest(topologyExplicitRamMap, componentChanges, instanceDefaultResources.getRam(), noBolts, spoutRam, noSpouts, numContainersBeforeRepack, noSpouts + noBolts); Assert.assertEquals(2, newPackingPlan.getContainers().size()); Assert.assertEquals((Integer) (noSpouts + noBolts + spoutScalingUp + boltScalingDown), newPackingPlan.getInstanceCount()); AssertPacking.assertNumInstances(newPackingPlan.getContainers(), BOLT_NAME, noBolts + boltScalingDown); AssertPacking.assertNumInstances(newPackingPlan.getContainers(), SPOUT_NAME, noSpouts + spoutScalingUp); } @Test public void scaleUpMultiple() throws Exception { int spoutScalingUp = 4; int boltScalingUp = 4; Map<String, Integer> componentChanges = new HashMap<>(); componentChanges.put(SPOUT_NAME, spoutScalingUp); // 8 spouts componentChanges.put(BOLT_NAME, boltScalingUp); // 8 bolts int numContainersBeforeRepack = 2; PackingPlan newPackingPlan = doDefaultScalingTest(componentChanges, numContainersBeforeRepack); Assert.assertEquals(4, newPackingPlan.getContainers().size()); Assert.assertEquals((Integer) (totalInstances + spoutScalingUp + boltScalingUp), newPackingPlan.getInstanceCount()); AssertPacking.assertNumInstances(newPackingPlan.getContainers(), BOLT_NAME, boltParallelism + boltScalingUp); AssertPacking.assertNumInstances(newPackingPlan.getContainers(), SPOUT_NAME, spoutParallelism + spoutScalingUp); } }