/*
* 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.Collection;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.entity.EntityLocal;
import org.apache.brooklyn.api.entity.EntitySpec;
import org.apache.brooklyn.api.entity.Group;
import org.apache.brooklyn.api.location.Location;
import org.apache.brooklyn.api.location.LocationSpec;
import org.apache.brooklyn.api.mgmt.ManagementContext;
import org.apache.brooklyn.core.entity.Entities;
import org.apache.brooklyn.core.entity.factory.ApplicationBuilder;
import org.apache.brooklyn.core.location.SimulatedLocation;
import org.apache.brooklyn.core.test.entity.LocalManagementContextForTests;
import org.apache.brooklyn.core.test.entity.TestApplication;
import org.apache.brooklyn.entity.group.DynamicGroup;
import org.apache.brooklyn.test.Asserts;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.time.Time;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
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 com.google.common.base.Function;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
public class AbstractFollowTheSunPolicyTest {
private static final Logger LOG = LoggerFactory.getLogger(AbstractFollowTheSunPolicyTest.class);
protected static final long TIMEOUT_MS = 10*1000;
protected static final long SHORT_WAIT_MS = 250;
protected static final long CONTAINER_STARTUP_DELAY_MS = 100;
protected TestApplication app;
protected ManagementContext managementContext;
protected SimulatedLocation loc1;
protected SimulatedLocation loc2;
protected FollowTheSunPool pool;
protected DefaultFollowTheSunModel<Entity, Movable> model;
protected FollowTheSunPolicy policy;
protected Group containerGroup;
protected Group itemGroup;
protected Random random = new Random();
@BeforeMethod(alwaysRun=true)
public void setUp() throws Exception {
LOG.debug("In AbstractFollowTheSunPolicyTest.setUp()");
MockItemEntityImpl.totalMoveCount.set(0);
MockItemEntityImpl.lastMoveTime.set(0);
managementContext = LocalManagementContextForTests.newInstance();
app = ApplicationBuilder.newManagedApp(TestApplication.class, managementContext);
loc1 = managementContext.getLocationManager().createLocation(LocationSpec.create(SimulatedLocation.class).configure("name", "loc1"));
loc2 = managementContext.getLocationManager().createLocation(LocationSpec.create(SimulatedLocation.class).configure("name", "loc2"));
containerGroup = app.createAndManageChild(EntitySpec.create(DynamicGroup.class)
.displayName("containerGroup")
.configure(DynamicGroup.ENTITY_FILTER, Predicates.instanceOf(MockContainerEntity.class)));
itemGroup = app.createAndManageChild(EntitySpec.create(DynamicGroup.class)
.displayName("itemGroup")
.configure(DynamicGroup.ENTITY_FILTER, Predicates.instanceOf(MockItemEntity.class)));
model = new DefaultFollowTheSunModel<Entity, Movable>("pool-model");
pool = app.createAndManageChild(EntitySpec.create(FollowTheSunPool.class));
pool.setContents(containerGroup, itemGroup);
policy = new FollowTheSunPolicy(MockItemEntity.ITEM_USAGE_METRIC, model, FollowTheSunParameters.newDefault());
pool.policies().add(policy);
app.start(ImmutableList.of(loc1, loc2));
}
@AfterMethod(alwaysRun=true)
public void tearDown() {
if (pool != null && policy != null) pool.policies().remove(policy);
if (app != null) Entities.destroyAll(app.getManagementContext());
MockItemEntityImpl.totalMoveCount.set(0);
MockItemEntityImpl.lastMoveTime.set(0);
}
/**
* Asserts that the given container have the given expected workrates (by querying the containers directly).
* Accepts an accuracy of "precision" for each container's workrate.
*/
protected void assertItemDistributionEventually(final Map<MockContainerEntity, ? extends Collection<MockItemEntity>> expected) {
try {
Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
public void run() {
assertItemDistribution(expected);
}});
} catch (AssertionError e) {
String errMsg = e.getMessage()+"; "+verboseDumpToString();
throw new RuntimeException(errMsg, e);
}
}
protected void assertItemDistributionContinually(final Map<MockContainerEntity, Collection<MockItemEntity>> expected) {
try {
Asserts.succeedsContinually(MutableMap.of("timeout", SHORT_WAIT_MS), new Runnable() {
public void run() {
assertItemDistribution(expected);
}});
} catch (AssertionError e) {
String errMsg = e.getMessage()+"; "+verboseDumpToString();
throw new RuntimeException(errMsg, e);
}
}
protected void assertItemDistribution(Map<MockContainerEntity, ? extends Collection<MockItemEntity>> expected) {
String errMsg = verboseDumpToString();
for (Map.Entry<MockContainerEntity, ? extends Collection<MockItemEntity>> entry : expected.entrySet()) {
MockContainerEntity container = entry.getKey();
Collection<MockItemEntity> expectedItems = entry.getValue();
assertEquals(ImmutableSet.copyOf(container.getBalanceableItems()), ImmutableSet.copyOf(expectedItems), errMsg);
}
}
protected String verboseDumpToString() {
Iterable<MockContainerEntity> containers = Iterables.filter(app.getManagementContext().getEntityManager().getEntities(), MockContainerEntity.class);
Iterable<MockItemEntity> items = Iterables.filter(app.getManagementContext().getEntityManager().getEntities(), MockItemEntity.class);
Iterable<Double> containerRates = Iterables.transform(containers, new Function<MockContainerEntity, Double>() {
@Override public Double apply(MockContainerEntity input) {
return (double) input.getWorkrate();
}});
Iterable<Map<Entity, Double>> containerItemUsages = Iterables.transform(containers, new Function<MockContainerEntity, Map<Entity, Double>>() {
@Override public Map<Entity, Double> apply(MockContainerEntity input) {
return input.getItemUsage();
}});
Map<MockContainerEntity, Set<Movable>> itemDistributionByContainer = Maps.newLinkedHashMap();
for (MockContainerEntity container : containers) {
itemDistributionByContainer.put(container, container.getBalanceableItems());
}
Map<Movable, BalanceableContainer<?>> itemDistributionByItem = Maps.newLinkedHashMap();
for (Movable item : items) {
itemDistributionByItem.put(item, item.getAttribute(Movable.CONTAINER));
}
String modelItemDistribution = model.itemDistributionToString();
return "containers="+containers+"; containerRates="+containerRates
+"; containerItemUsages="+containerItemUsages
+"; itemDistributionByContainer="+itemDistributionByContainer
+"; itemDistributionByItem="+itemDistributionByItem
+"; model="+modelItemDistribution
+"; totalMoves="+MockItemEntityImpl.totalMoveCount
+"; lastMoveTime="+Time.makeDateString(MockItemEntityImpl.lastMoveTime.get());
}
protected MockContainerEntity newContainer(TestApplication app, Location loc, String name) {
return newAsyncContainer(app, loc, name, 0);
}
/**
* Creates a new container that will take "delay" millis to complete its start-up.
*/
protected MockContainerEntity newAsyncContainer(TestApplication app, Location loc, String name, long delay) {
// FIXME Is this comment true?
// Annoyingly, can't set parent until after the threshold config has been defined.
MockContainerEntity container = app.createAndManageChild(EntitySpec.create(MockContainerEntity.class)
.displayName(name)
.configure(MockContainerEntity.DELAY, delay));
LOG.debug("Managed new container {}", container);
container.start(ImmutableList.of(loc));
return container;
}
protected static MockItemEntity newLockedItem(TestApplication app, MockContainerEntity container, String name) {
MockItemEntity item = app.createAndManageChild(EntitySpec.create(MockItemEntity.class)
.displayName(name)
.configure(MockItemEntity.IMMOVABLE, true));
LOG.debug("Managed new locked item {}", container);
if (container != null) {
item.move(container);
}
return item;
}
protected static MockItemEntity newItem(TestApplication app, MockContainerEntity container, String name) {
MockItemEntity item = app.createAndManageChild(EntitySpec.create(MockItemEntity.class)
.displayName(name));
LOG.debug("Managed new item {} at {}", item, container);
if (container != null) {
item.move(container);
}
return item;
}
protected static MockItemEntity newItem(TestApplication app, MockContainerEntity container, String name, Map<? extends Entity, Double> workpattern) {
MockItemEntity item = newItem(app, container, name);
if (workpattern != null) {
((EntityLocal)item).sensors().set(MockItemEntity.ITEM_USAGE_METRIC, (Map) workpattern);
}
return item;
}
}