/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.apache.brooklyn.policy.followthesun; import static org.testng.Assert.assertEquals; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.entity.EntityLocal; import org.apache.brooklyn.api.location.Location; import org.apache.brooklyn.core.entity.Entities; import org.apache.brooklyn.core.location.SimulatedLocation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.annotations.Test; import org.apache.brooklyn.policy.loadbalancing.BalanceableContainer; import org.apache.brooklyn.policy.loadbalancing.MockContainerEntity; import org.apache.brooklyn.policy.loadbalancing.MockItemEntity; import org.apache.brooklyn.policy.loadbalancing.MockItemEntityImpl; import org.apache.brooklyn.policy.loadbalancing.Movable; import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.util.collections.MutableMap; import com.google.common.base.Function; import com.google.common.base.Predicates; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; public class FollowTheSunPolicySoakTest extends AbstractFollowTheSunPolicyTest { private static final Logger LOG = LoggerFactory.getLogger(FollowTheSunPolicySoakTest.class); private static final long TIMEOUT_MS = 10*1000; @Test public void testFollowTheSunQuickTest() { RunConfig config = new RunConfig(); config.numCycles = 1; config.numLocations=3; config.numContainersPerLocation = 5; config.numLockedItemsPerLocation = 2; config.numMovableItems = 10; runFollowTheSunSoakTest(config); } @Test public void testLoadBalancingManyItemsQuickTest() { RunConfig config = new RunConfig(); config.numCycles = 1; config.numLocations=2; config.numContainersPerLocation = 3; config.numLockedItemsPerLocation = 2; config.numMovableItems = 10; config.numContainerStopsPerCycle = 1; config.numItemStopsPerCycle = 1; runFollowTheSunSoakTest(config); } @Test(groups={"Integration"}) // takes ~2s public void testLoadBalancingManyItemsNotTooLongTest() { RunConfig config = new RunConfig(); config.numCycles = 1; config.numLocations=3; config.numContainersPerLocation = 5; config.numLockedItemsPerLocation = 2; config.numMovableItems = 500; config.numContainerStopsPerCycle = 1; config.numItemStopsPerCycle = 1; runFollowTheSunSoakTest(config); } @Test(groups={"Integration","Acceptance"}) // integration group, because it's slow to run many cycles public void testLoadBalancingSoakTest() { RunConfig config = new RunConfig(); config.numCycles = 100; config.numLocations=3; config.numContainersPerLocation = 5; config.numLockedItemsPerLocation = 2; config.numMovableItems = 10; runFollowTheSunSoakTest(config); } @Test(groups={"Integration","Acceptance"}) // integration group, because it's slow to run many cycles public void testLoadBalancingManyItemsSoakTest() { RunConfig config = new RunConfig(); config.numCycles = 100; config.numLocations=3; config.numContainersPerLocation = 5; config.numLockedItemsPerLocation = 2; config.numMovableItems = 100; config.numContainerStopsPerCycle = 3; config.numItemStopsPerCycle = 10; runFollowTheSunSoakTest(config); } @Test(groups={"Integration","Acceptance"}) // integration group, because it's slow to run many cycles public void testLoadBalancingManyManyItemsTest() { RunConfig config = new RunConfig(); config.numCycles = 1; config.numLocations=10; config.numContainersPerLocation = 5; config.numLockedItemsPerLocation = 100; config.numMovableItems = 1000; config.numContainerStopsPerCycle = 0; config.numItemStopsPerCycle = 0; config.timeout_ms = 30*1000; config.verbose = false; runFollowTheSunSoakTest(config); } private void runFollowTheSunSoakTest(RunConfig config) { int numCycles = config.numCycles; int numLocations = config.numLocations; int numContainersPerLocation = config.numContainersPerLocation; int numLockedItemsPerLocation = config.numLockedItemsPerLocation; int numMovableItems = config.numMovableItems; int numContainerStopsPerCycle = config.numContainerStopsPerCycle; int numItemStopsPerCycle = config.numItemStopsPerCycle; long timeout_ms = config.timeout_ms; final boolean verbose = config.verbose; MockItemEntityImpl.totalMoveCount.set(0); List<Location> locations = new ArrayList<Location>(); Multimap<Location,MockContainerEntity> containers = HashMultimap.<Location,MockContainerEntity>create(); Multimap<Location,MockItemEntity> lockedItems = HashMultimap.<Location,MockItemEntity>create(); final List<MockItemEntity> movableItems = new ArrayList<MockItemEntity>(); for (int i = 1; i <= numLocations; i++) { String locName = "loc"+i; Location loc = new SimulatedLocation(MutableMap.of("name",locName)); locations.add(loc); for (int j = 1; j <= numContainersPerLocation; j++) { MockContainerEntity container = newContainer(app, loc, "container-"+locName+"-"+j); containers.put(loc, container); } for (int j = 1; j <= numLockedItemsPerLocation; j++) { MockContainerEntity container = Iterables.get(containers.get(loc), j%numContainersPerLocation); MockItemEntity item = newLockedItem(app, container, "item-locked-"+locName+"-"+j); lockedItems.put(loc, item); } } for (int i = 1; i <= numMovableItems; i++) { MockContainerEntity container = Iterables.get(containers.values(), i%containers.size()); MockItemEntity item = newItem(app, container, "item-movable"+i); movableItems.add(item); } for (int i = 1; i <= numCycles; i++) { LOG.info("{}: cycle {}", FollowTheSunPolicySoakTest.class.getSimpleName(), i); // Stop movable items, and start others for (int j = 1; j <= numItemStopsPerCycle; j++) { int itemIndex = random.nextInt(numMovableItems); MockItemEntity itemToStop = movableItems.get(itemIndex); itemToStop.stop(); LOG.debug("Unmanaging item {}", itemToStop); Entities.unmanage(itemToStop); movableItems.set(itemIndex, newItem(app, Iterables.get(containers.values(), 0), "item-movable"+itemIndex)); } // Choose a location to be busiest int locIndex = random.nextInt(numLocations); final Location busiestLocation = locations.get(locIndex); // Repartition the load across the items for (int j = 0; j < numMovableItems; j++) { MockItemEntity item = movableItems.get(j); Map<Entity, Double> workrates = Maps.newLinkedHashMap(); for (Map.Entry<Location,MockItemEntity> entry : lockedItems.entries()) { Location location = entry.getKey(); MockItemEntity source = entry.getValue(); double baseWorkrate = (location == busiestLocation ? 1000 : 100); double jitter = 10; double jitteredWorkrate = Math.max(0, baseWorkrate + (random.nextDouble()*jitter*2 - jitter)); workrates.put(source, jitteredWorkrate); } ((EntityLocal)item).sensors().set(MockItemEntity.ITEM_USAGE_METRIC, workrates); } // Stop containers, and start others // This offloads the "immovable" items to other containers in the same location! for (int j = 1; j <= numContainerStopsPerCycle; j++) { int containerIndex = random.nextInt(containers.size()); MockContainerEntity containerToStop = Iterables.get(containers.values(), containerIndex); Location location = Iterables.get(containerToStop.getLocations(), 0); MockContainerEntity otherContainerInLocation = Iterables.find(containers.get(location), Predicates.not(Predicates.equalTo(containerToStop)), null); containerToStop.offloadAndStop(otherContainerInLocation); LOG.debug("Unmanaging container {}", containerToStop); Entities.unmanage(containerToStop); containers.remove(location, containerToStop); MockContainerEntity containerToAdd = newContainer(app, location, "container-"+location.getDisplayName()+"-new."+i+"."+j); containers.put(location, containerToAdd); } // Assert that the items all end up in the location with maximum load-generation Asserts.succeedsEventually(MutableMap.of("timeout", timeout_ms), new Runnable() { public void run() { Iterable<Location> itemLocs = Iterables.transform(movableItems, new Function<MockItemEntity, Location>() { public Location apply(MockItemEntity input) { BalanceableContainer<?> container = input.getAttribute(Movable.CONTAINER); Collection<Location> locs = (container != null) ? container.getLocations(): null; return (locs != null && locs.size() > 0) ? Iterables.get(locs, 0) : null; }}); Iterable<String> itemLocNames = Iterables.transform(itemLocs, new Function<Location, String>() { public String apply(Location input) { return (input != null) ? input.getDisplayName() : null; }}); String errMsg; if (verbose) { errMsg = verboseDumpToString()+"; itemLocs="+itemLocNames; } else { Set<String> locNamesInUse = Sets.newLinkedHashSet(itemLocNames); errMsg = "locsInUse="+locNamesInUse+"; totalMoves="+MockItemEntityImpl.totalMoveCount; } assertEquals(ImmutableList.copyOf(itemLocs), Collections.nCopies(movableItems.size(), busiestLocation), errMsg); }}); } } static class RunConfig { int numCycles = 1; int numLocations = 3; int numContainersPerLocation = 5; int numLockedItemsPerLocation = 5; int numMovableItems = 5; int numContainerStopsPerCycle = 0; int numItemStopsPerCycle = 0; long timeout_ms = TIMEOUT_MS; boolean verbose = true; } }