/* * 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 java.util.Collections; import java.util.List; import java.util.Queue; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import org.apache.brooklyn.api.entity.EntityLocal; import org.apache.brooklyn.core.entity.Entities; import org.apache.brooklyn.core.test.entity.TestApplication; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import com.google.common.collect.Lists; public class LoadBalancingPolicyConcurrencyTest extends AbstractLoadBalancingPolicyTest { private static final Logger LOG = LoggerFactory.getLogger(LoadBalancingPolicyConcurrencyTest.class); private static final double WORKRATE_JITTER = 2d; private static final int NUM_CONTAINERS = 20; private static final int WORKRATE_UPDATE_PERIOD_MS = 1000; private ScheduledExecutorService scheduledExecutor; @BeforeMethod(alwaysRun=true) @Override public void before() { scheduledExecutor = Executors.newScheduledThreadPool(10); super.before(); } @AfterMethod(alwaysRun=true) @Override public void after() { if (scheduledExecutor != null) scheduledExecutor.shutdownNow(); super.after(); } @Test public void testSimplePeriodicWorkrateUpdates() { List<MockItemEntity> items = Lists.newArrayList(); List<MockContainerEntity> containers = Lists.newArrayList(); for (int i = 0; i < NUM_CONTAINERS; i++) { containers.add(newContainer(app, "container"+i, 10, 30)); } for (int i = 0; i < NUM_CONTAINERS; i++) { newItemWithPeriodicWorkrates(app, containers.get(0), "item"+i, 20); } assertWorkratesEventually(containers, items, Collections.nCopies(NUM_CONTAINERS, 20d), WORKRATE_JITTER); } @Test public void testConcurrentlyAddContainers() { final Queue<MockContainerEntity> containers = new ConcurrentLinkedQueue<MockContainerEntity>(); final List<MockItemEntity> items = Lists.newArrayList(); containers.add(newContainer(app, "container-orig", 10, 30)); for (int i = 0; i < NUM_CONTAINERS; i++) { items.add(newItemWithPeriodicWorkrates(app, containers.iterator().next(), "item"+i, 20)); } for (int i = 0; i < NUM_CONTAINERS-1; i++) { final int index = i; scheduledExecutor.submit(new Callable<Void>() { @Override public Void call() { containers.add(newContainer(app, "container"+index, 10, 30)); return null; }}); } assertWorkratesEventually(containers, items, Collections.nCopies(NUM_CONTAINERS, 20d), WORKRATE_JITTER); } @Test public void testConcurrentlyAddItems() { final Queue<MockItemEntity> items = new ConcurrentLinkedQueue<MockItemEntity>(); final List<MockContainerEntity> containers = Lists.newArrayList(); for (int i = 0; i < NUM_CONTAINERS; i++) { containers.add(newContainer(app, "container"+i, 10, 30)); } for (int i = 0; i < NUM_CONTAINERS; i++) { final int index = i; scheduledExecutor.submit(new Callable<Void>() { @Override public Void call() { items.add(newItemWithPeriodicWorkrates(app, containers.get(0), "item"+index, 20)); return null; }}); } assertWorkratesEventually(containers, items, Collections.nCopies(NUM_CONTAINERS, 20d), WORKRATE_JITTER); } // TODO Got IndexOutOfBoundsException from containers.last() @Test(groups="WIP", invocationCount=100) public void testConcurrentlyRemoveContainers() { List<MockItemEntity> items = Lists.newArrayList(); final List<MockContainerEntity> containers = Lists.newArrayList(); for (int i = 0; i < NUM_CONTAINERS; i++) { containers.add(newContainer(app, "container"+i, 15, 45)); } for (int i = 0; i < NUM_CONTAINERS; i++) { items.add(newItemWithPeriodicWorkrates(app, containers.get(i), "item"+i, 20)); } final List<MockContainerEntity> containersToStop = Lists.newArrayList(); for (int i = 0; i < NUM_CONTAINERS/2; i++) { containersToStop.add(containers.remove(0)); } for (final MockContainerEntity containerToStop : containersToStop) { scheduledExecutor.submit(new Callable<Void>() { @Override public Void call() { try { containerToStop.offloadAndStop(containers.get(containers.size()-1)); Entities.unmanage(containerToStop); } catch (Throwable t) { LOG.error("Error stopping container "+containerToStop, t); } return null; }}); } assertWorkratesEventually(containers, items, Collections.nCopies((int)(NUM_CONTAINERS/2), 40d), WORKRATE_JITTER*2); } @Test(groups="WIP") public void testConcurrentlyRemoveItems() { List<MockItemEntity> items = Lists.newArrayList(); List<MockContainerEntity> containers = Lists.newArrayList(); for (int i = 0; i < NUM_CONTAINERS; i++) { containers.add(newContainer(app, "container"+i, 15, 45)); } for (int i = 0; i < NUM_CONTAINERS*2; i++) { items.add(newItemWithPeriodicWorkrates(app, containers.get(i%NUM_CONTAINERS), "item"+i, 20)); } // should now have item0 and item{0+NUM_CONTAINERS} on container0, etc for (int i = 0; i < NUM_CONTAINERS; i++) { // not removing consecutive items as that would leave it balanced! int indexToStop = (i < NUM_CONTAINERS/2) ? NUM_CONTAINERS : 0; final MockItemEntity itemToStop = items.remove(indexToStop); scheduledExecutor.submit(new Callable<Void>() { @Override public Void call() { try { itemToStop.stop(); Entities.unmanage(itemToStop); } catch (Throwable t) { LOG.error("Error stopping item "+itemToStop, t); } return null; }}); } assertWorkratesEventually(containers, items, Collections.nCopies(NUM_CONTAINERS, 20d), WORKRATE_JITTER); } protected MockItemEntity newItemWithPeriodicWorkrates(TestApplication app, MockContainerEntity container, String name, double workrate) { MockItemEntity item = newItem(app, container, name, workrate); scheduleItemWorkrateUpdates(item, workrate, WORKRATE_JITTER); return item; } private void scheduleItemWorkrateUpdates(final MockItemEntity item, final double workrate, final double jitter) { final AtomicReference<Future<?>> futureRef = new AtomicReference<Future<?>>(); Future<?> future = scheduledExecutor.scheduleAtFixedRate( new Runnable() { @Override public void run() { if (item.isStopped() && futureRef.get() != null) { futureRef.get().cancel(true); return; } double jitteredWorkrate = workrate + (random.nextDouble()*jitter*2 - jitter); ((EntityLocal)item).sensors().set(TEST_METRIC, (int) Math.max(0, jitteredWorkrate)); } }, 0, WORKRATE_UPDATE_PERIOD_MS, TimeUnit.MILLISECONDS); futureRef.set(future); } }