/*
* 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.loadbalancing;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import org.apache.brooklyn.api.entity.EntityLocal;
import org.apache.brooklyn.core.entity.Entities;
import org.apache.brooklyn.test.Asserts;
import org.apache.brooklyn.util.collections.MutableMap;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
public class LoadBalancingPolicyTest extends AbstractLoadBalancingPolicyTest {
// Expect no balancing to occur as container A isn't above the high threshold.
@Test
public void testNoopWhenWithinThresholds() {
MockContainerEntity containerA = newContainer(app, "A", 10, 100);
MockContainerEntity containerB = newContainer(app, "B", 20, 60);
MockItemEntity item1 = newItem(app, containerA, "1", 10);
MockItemEntity item2 = newItem(app, containerA, "2", 10);
MockItemEntity item3 = newItem(app, containerA, "3", 10);
MockItemEntity item4 = newItem(app, containerA, "4", 10);
assertWorkratesEventually(
ImmutableList.of(containerA, containerB),
ImmutableList.of(item1, item2, item3, item4),
ImmutableList.of(40d, 0d));
}
@Test
public void testNoopWhenAlreadyBalanced() {
MockContainerEntity containerA = newContainer(app, "A", 20, 80);
MockContainerEntity containerB = newContainer(app, "B", 20, 80);
MockItemEntity item1 = newItem(app, containerA, "1", 10);
MockItemEntity item2 = newItem(app, containerA, "2", 30);
MockItemEntity item3 = newItem(app, containerB, "3", 20);
MockItemEntity item4 = newItem(app, containerB, "4", 20);
assertWorkratesEventually(
ImmutableList.of(containerA, containerB),
ImmutableList.of(item1, item2, item3, item4),
ImmutableList.of(40d, 40d));
assertEquals(containerA.getBalanceableItems(), ImmutableSet.of(item1, item2));
assertEquals(containerB.getBalanceableItems(), ImmutableSet.of(item3, item4));
}
// Expect 20 units of workload to be migrated from hot container (A) to cold (B).
@Test
public void testSimpleBalancing() {
// Set-up containers and items.
MockContainerEntity containerA = newContainer(app, "A", 10, 25);
MockContainerEntity containerB = newContainer(app, "B", 20, 60);
MockItemEntity item1 = newItem(app, containerA, "1", 10);
MockItemEntity item2 = newItem(app, containerA, "2", 10);
MockItemEntity item3 = newItem(app, containerA, "3", 10);
MockItemEntity item4 = newItem(app, containerA, "4", 10);
assertWorkratesEventually(
ImmutableList.of(containerA, containerB),
ImmutableList.of(item1, item2, item3, item4),
ImmutableList.of(20d, 20d));
}
@Test
public void testSimpleBalancing2() {
MockContainerEntity containerA = newContainer(app, "A", 20, 40);
MockContainerEntity containerB = newContainer(app, "B", 20, 40);
MockItemEntity item1 = newItem(app, containerA, "1", 0);
MockItemEntity item2 = newItem(app, containerB, "2", 40);
MockItemEntity item3 = newItem(app, containerB, "3", 20);
MockItemEntity item4 = newItem(app, containerB, "4", 20);
assertWorkratesEventually(
ImmutableList.of(containerA, containerB),
ImmutableList.of(item1, item2, item3, item4),
ImmutableList.of(40d, 40d));
}
// @Test
// public void testAdjustedItemNotMoved() {
// MockBalancingModel pool = new MockBalancingModel(
// containers(
// containerA, 20, 50,
// containerB, 20, 50),
// items(
// "item1", containerA, 0,
// "item2", containerB, -40,
// "item3", containerB, 20,
// "item4", containerB, 20)
// );
//
// BalancingStrategy<String, String> policy = new BalancingStrategy<String, String>("Test", pool);
// policy.rebalance();
//
// assertEquals((Object)pool.getItemsForContainer(containerA), ImmutableSet.of("item1", "item3", "item4"), pool.itemDistributionToString());
// assertEquals((Object)pool.getItemsForContainer(containerB), ImmutableSet.of("item2"), pool.itemDistributionToString());
// }
@Test
public void testMultiMoveBalancing() {
MockContainerEntity containerA = newContainer(app, "A", 20, 50);
MockContainerEntity containerB = newContainer(app, "B", 20, 50);
MockItemEntity item1 = newItem(app, containerA, "1", 10);
MockItemEntity item2 = newItem(app, containerA, "2", 10);
MockItemEntity item3 = newItem(app, containerA, "3", 10);
MockItemEntity item4 = newItem(app, containerA, "4", 10);
MockItemEntity item5 = newItem(app, containerA, "5", 10);
MockItemEntity item6 = newItem(app, containerA, "6", 10);
MockItemEntity item7 = newItem(app, containerA, "7", 10);
MockItemEntity item8 = newItem(app, containerA, "8", 10);
MockItemEntity item9 = newItem(app, containerA, "9", 10);
MockItemEntity item10 = newItem(app, containerA, "10", 10);
// non-deterministic which items will be moved; but can assert how many (given they all have same workrate)
assertWorkratesEventually(
ImmutableList.of(containerA, containerB),
ImmutableList.of(item1, item2, item3, item4, item5, item6, item7, item8, item9, item10),
ImmutableList.of(50d, 50d));
assertEquals(containerA.getBalanceableItems().size(), 5);
assertEquals(containerB.getBalanceableItems().size(), 5);
}
@Test
public void testRebalanceWhenWorkratesChange() {
// Set-up containers and items.
MockContainerEntity containerA = newContainer(app, "A", 10, 50);
MockContainerEntity containerB = newContainer(app, "B", 10, 50);
MockItemEntity item1 = newItem(app, containerA, "1", 0);
MockItemEntity item2 = newItem(app, containerA, "2", 0);
item1.sensors().set(MockItemEntity.TEST_METRIC, 40);
item2.sensors().set(MockItemEntity.TEST_METRIC, 40);
assertWorkratesEventually(
ImmutableList.of(containerA, containerB),
ImmutableList.of(item1, item2),
ImmutableList.of(40d, 40d));
}
// Expect no balancing to occur in hot pool (2 containers over-threshold at 40).
// On addition of new container, expect hot containers to offload 10 each.
@Test
public void testAddContainerWhenHot() {
// Set-up containers and items.
MockContainerEntity containerA = newContainer(app, "A", 10, 30);
MockContainerEntity containerB = newContainer(app, "B", 10, 30);
MockItemEntity item1 = newItem(app, containerA, "1", 10);
MockItemEntity item2 = newItem(app, containerA, "2", 10);
MockItemEntity item3 = newItem(app, containerA, "3", 10);
MockItemEntity item4 = newItem(app, containerA, "4", 10);
MockItemEntity item5 = newItem(app, containerB, "5", 10);
MockItemEntity item6 = newItem(app, containerB, "6", 10);
MockItemEntity item7 = newItem(app, containerB, "7", 10);
MockItemEntity item8 = newItem(app, containerB, "8", 10);
// Both containers are over-threshold at this point; should not rebalance.
MockContainerEntity containerC = newAsyncContainer(app, "C", 10, 30, CONTAINER_STARTUP_DELAY_MS);
// New container allows hot ones to offload work.
assertWorkratesEventually(
ImmutableList.of(containerA, containerB, containerC),
ImmutableList.of(item1, item2, item3, item4, item5, item6, item7, item8),
ImmutableList.of(30d, 30d, 20d));
}
// On addition of new container, expect no rebalancing to occur as no existing container is hot.
@Test
public void testAddContainerWhenCold() {
// Set-up containers and items.
MockContainerEntity containerA = newContainer(app, "A", 10, 50);
MockContainerEntity containerB = newContainer(app, "B", 10, 50);
MockItemEntity item1 = newItem(app, containerA, "1", 10);
MockItemEntity item2 = newItem(app, containerA, "2", 10);
MockItemEntity item3 = newItem(app, containerA, "3", 10);
MockItemEntity item4 = newItem(app, containerA, "4", 10);
MockItemEntity item5 = newItem(app, containerB, "5", 10);
MockItemEntity item6 = newItem(app, containerB, "6", 10);
MockItemEntity item7 = newItem(app, containerB, "7", 10);
MockItemEntity item8 = newItem(app, containerB, "8", 10);
assertWorkratesEventually(
ImmutableList.of(containerA, containerB),
ImmutableList.of(item1, item2, item3, item4, item5, item6, item7, item8),
ImmutableList.of(40d, 40d));
MockContainerEntity containerC = newAsyncContainer(app, "C", 10, 50, CONTAINER_STARTUP_DELAY_MS);
assertWorkratesEventually(
ImmutableList.of(containerA, containerB, containerC),
ImmutableList.of(item1, item2, item3, item4, item5, item6, item7, item8),
ImmutableList.of(40d, 40d, 0d));
}
// Expect no balancing to occur in cool pool (2 containers under-threshold at 30).
// On addition of new item, expect over-threshold container (A) to offload 20 to B.
@Test
public void testAddItem() {
// Set-up containers and items.
MockContainerEntity containerA = newContainer(app, "A", 10, 50);
MockContainerEntity containerB = newContainer(app, "B", 10, 50);
MockItemEntity item1 = newItem(app, containerA, "1", 10);
MockItemEntity item2 = newItem(app, containerA, "2", 10);
MockItemEntity item3 = newItem(app, containerA, "3", 10);
MockItemEntity item4 = newItem(app, containerB, "4", 10);
MockItemEntity item5 = newItem(app, containerB, "5", 10);
MockItemEntity item6 = newItem(app, containerB, "6", 10);
assertWorkratesEventually(
ImmutableList.of(containerA, containerB),
ImmutableList.of(item1, item2, item3, item4, item5, item6),
ImmutableList.of(30d, 30d));
newItem(app, containerA, "7", 40);
assertWorkratesEventually(
ImmutableList.of(containerA, containerB),
ImmutableList.of(item1, item2, item3, item4, item5, item6),
ImmutableList.of(50d, 50d));
}
// FIXME Failed in build repeatedly (e.g. #1035), but couldn't reproduce locally yet with invocationCount=100
@Test(groups="WIP")
public void testRemoveContainerCausesRebalancing() {
// Set-up containers and items.
MockContainerEntity containerA = newContainer(app, "A", 10, 30);
MockContainerEntity containerB = newContainer(app, "B", 10, 30);
MockContainerEntity containerC = newContainer(app, "C", 10, 30);
MockItemEntity item1 = newItem(app, containerA, "1", 10);
MockItemEntity item2 = newItem(app, containerA, "2", 10);
MockItemEntity item3 = newItem(app, containerB, "3", 10);
MockItemEntity item4 = newItem(app, containerB, "4", 10);
MockItemEntity item5 = newItem(app, containerC, "5", 10);
MockItemEntity item6 = newItem(app, containerC, "6", 10);
Entities.unmanage(containerC);
item5.move(containerA);
item6.move(containerA);
assertWorkratesEventually(
ImmutableList.of(containerA, containerB),
ImmutableList.of(item1, item2, item3, item4, item5, item6),
ImmutableList.of(30d, 30d));
}
@Test
public void testRemoveItemCausesRebalancing() {
// Set-up containers and items.
MockContainerEntity containerA = newContainer(app, "A", 10, 30);
MockContainerEntity containerB = newContainer(app, "B", 10, 30);
MockItemEntity item1 = newItem(app, containerA, "1", 30);
MockItemEntity item2 = newItem(app, containerB, "2", 20);
MockItemEntity item3 = newItem(app, containerB, "3", 20);
item1.stop();
Entities.unmanage(item1);
assertWorkratesEventually(
ImmutableList.of(containerA, containerB),
ImmutableList.of(item1, item2, item3),
ImmutableList.of(20d, 20d));
}
@Test
public void testRebalancesAfterManualMove() {
// Set-up containers and items.
MockContainerEntity containerA = newContainer(app, "A", 10, 50);
MockContainerEntity containerB = newContainer(app, "B", 10, 50);
MockItemEntity item1 = newItem(app, containerA, "1", 20);
MockItemEntity item2 = newItem(app, containerA, "2", 20);
MockItemEntity item3 = newItem(app, containerB, "3", 20);
MockItemEntity item4 = newItem(app, containerB, "4", 20);
// Move everything onto containerA, and expect it to be automatically re-balanced
item3.move(containerA);
item4.move(containerA);
assertWorkratesEventually(
ImmutableList.of(containerA, containerB),
ImmutableList.of(item1, item2, item3, item4),
ImmutableList.of(40d, 40d));
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Test
public void testModelIncludesItemsAndContainersStartedBeforePolicyCreated() {
pool.policies().remove(policy);
policy.destroy();
// Set-up containers and items.
final MockContainerEntity containerA = newContainer(app, "A", 10, 100);
newItem(app, containerA, "1", 10);
policy = new LoadBalancingPolicy(MutableMap.of(), TEST_METRIC, model);
pool.policies().add(policy);
Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
public void run() {
assertEquals(model.getContainerWorkrates(), ImmutableMap.of(containerA, 10d));
}
});
}
@Test
public void testLockedItemsNotMoved() {
// Set-up containers and items.
MockContainerEntity containerA = newContainer(app, "A", 10, 50);
MockContainerEntity containerB = newContainer(app, "B", 10, 50);
MockItemEntity item1 = newLockedItem(app, containerA, "1", 40);
MockItemEntity item2 = newLockedItem(app, containerA, "2", 40);
assertWorkratesContinually(
ImmutableList.of(containerA, containerB),
ImmutableList.of(item1, item2),
ImmutableList.of(80d, 0d));
}
@Test
public void testLockedItemsContributeToOverloadedMeasurements() {
// Set-up containers and items.
MockContainerEntity containerA = newContainer(app, "A", 10, 50);
MockContainerEntity containerB = newContainer(app, "B", 10, 50);
MockItemEntity item1 = newLockedItem(app, containerA, "1", 40);
MockItemEntity item2 = newItem(app, containerA, "2", 25);
MockItemEntity item3 = newItem(app, containerA, "3", 25);
assertWorkratesEventually(
ImmutableList.of(containerA, containerB),
ImmutableList.of(item1, item2, item3),
ImmutableList.of(40d, 50d));
}
@Test
public void testOverloadedLockedItemsPreventMoreWorkEnteringContainer() throws Exception {
// Set-up containers and items.
MockContainerEntity containerA = newContainer(app, "A", 10, 50);
MockContainerEntity containerB = newContainer(app, "B", 10, 50);
MockItemEntity item1 = newLockedItem(app, containerA, "1", 50);
Thread.sleep(1); // increase chances of item1's workrate having been received first
MockItemEntity item2 = newItem(app, containerB, "2", 30);
MockItemEntity item3 = newItem(app, containerB, "3", 30);
assertWorkratesContinually(
ImmutableList.of(containerA, containerB),
ImmutableList.of(item1, item2, item3),
ImmutableList.of(50d, 60d));
}
@Test
public void testPolicyUpdatesModel() {
final MockContainerEntity containerA = newContainer(app, "A", 10, 20);
final MockContainerEntity containerB = newContainer(app, "B", 11, 21);
final MockItemEntity item1 = newItem(app, containerA, "1", 12);
final MockItemEntity item2 = newItem(app, containerB, "2", 13);
Asserts.succeedsEventually(MutableMap.of("timeout", TIMEOUT_MS), new Runnable() {
public void run() {
assertEquals(model.getPoolSize(), 2);
assertEquals(model.getPoolContents(), ImmutableSet.of(containerA, containerB));
assertEquals(model.getItemWorkrate(item1), 12d);
assertEquals(model.getItemWorkrate(item2), 13d);
assertEquals(model.getParentContainer(item1), containerA);
assertEquals(model.getParentContainer(item2), containerB);
assertEquals(model.getContainerWorkrates(), ImmutableMap.of(containerA, 12d, containerB, 13d));
assertEquals(model.getPoolLowThreshold(), 10+11d);
assertEquals(model.getPoolHighThreshold(), 20+21d);
assertEquals(model.getCurrentPoolWorkrate(), 12+13d);
assertFalse(model.isHot());
assertFalse(model.isCold());
}
});
}
}