/*
Copyright 2014 Philipp Leitner
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 at.ac.tuwien.infosys.jcloudscale.test.integration.local;
import static com.google.common.collect.Lists.newArrayList;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import java.io.Closeable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.After;
import org.junit.Test;
import at.ac.tuwien.infosys.jcloudscale.annotations.JCloudScaleShutdown;
import at.ac.tuwien.infosys.jcloudscale.api.CloudObjects;
import at.ac.tuwien.infosys.jcloudscale.configuration.JCloudScaleConfiguration;
import at.ac.tuwien.infosys.jcloudscale.management.CloudManager;
import at.ac.tuwien.infosys.jcloudscale.policy.AbstractScalingPolicy;
import at.ac.tuwien.infosys.jcloudscale.test.testobject.TestCloudObject1;
import at.ac.tuwien.infosys.jcloudscale.test.testobject.policy.CalculatingRunnable;
import at.ac.tuwien.infosys.jcloudscale.test.testobject.policy.WaitingRunnable;
import at.ac.tuwien.infosys.jcloudscale.test.util.ConfigurationHelper;
import at.ac.tuwien.infosys.jcloudscale.vm.ClientCloudObject;
import at.ac.tuwien.infosys.jcloudscale.vm.JCloudScaleClient;
import at.ac.tuwien.infosys.jcloudscale.vm.IHost;
import at.ac.tuwien.infosys.jcloudscale.vm.IHostPool;
import at.ac.tuwien.infosys.jcloudscale.vm.localVm.LocalCloudPlatformConfiguration;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
/**
* @author Gregor Schauer
*/
public class TestScalingPolicy {
static final int DEFAULT_TIMEOUT = 10000;
public JCloudScaleClient getClient(AbstractScalingPolicy scalingPolicy) {
JCloudScaleConfiguration config = ConfigurationHelper.createDefaultTestConfiguration()
.with(scalingPolicy)
.withMonitoring(false)
.build();
((LocalCloudPlatformConfiguration)config.server().cloudPlatform()).setJavaHeapSizeMB(8);
config.common().setScaleDownIntervalInSec(1);
JCloudScaleClient.setConfiguration(config);
return JCloudScaleClient.getClient();
}
@After
@JCloudScaleShutdown
public void after() throws Exception {
}
@Test
public void testSingleHost() throws Exception {
getClient(new AbstractScalingPolicy() {
@Override
public IHost selectHost(ClientCloudObject newCloudObject, IHostPool hostPool)
{
if(hostPool.getHostsCount() > 0)
return hostPool.getHosts().iterator().next();
else
return hostPool.startNewHost();
}
@Override
public boolean scaleDown(IHost scaledHost, IHostPool hostPool) {
return scaledHost.getCloudObjectsCount() == 0;
}
});
assertInstances(0, 0, DEFAULT_TIMEOUT);
WaitingRunnable first = new WaitingRunnable();
first.start();
assertInstances(1, 1, DEFAULT_TIMEOUT);
WaitingRunnable second = new WaitingRunnable();
second.start();
assertInstances(1, 2, DEFAULT_TIMEOUT);
first.close();
second.close();
assertInstances(0, 0, DEFAULT_TIMEOUT);
}
@Test
public void testCategorizedHosts() throws Exception {
getClient(new AbstractScalingPolicy() {
final Map<UUID, Class<?>> categories = Maps.newHashMap();
@Override
public IHost selectHost(ClientCloudObject newCloudObject, IHostPool hostPool) {
synchronized (categories) {
for (IHost host : hostPool.getHosts()) {
if (categories.get(host.getId()) == newCloudObject.getCloudObjectClass()) {
return host;
}
}
IHost host = hostPool.startNewHost();
categories.put(host.getId(), newCloudObject.getCloudObjectClass());
return host;
}
}
@Override
public boolean scaleDown(IHost scaledHost, IHostPool hostPool) {
return scaledHost.getCloudObjectsCount() == 0;
}
});
assertInstances(0, 0, DEFAULT_TIMEOUT);
List<Closeable> closeables = newArrayList();
int i = 1;
for (; i < 10; i++) {
closeables.add(new WaitingRunnable());
assertInstances(1, i, DEFAULT_TIMEOUT);
}
for (;i < 13; i++) {
closeables.add(CloudObjects.create(CalculatingRunnable.class));
assertInstances(2, i, DEFAULT_TIMEOUT);
}
for (Closeable c : Iterables.filter(closeables, Predicates.instanceOf(WaitingRunnable.class))) {
c.close();
}
assertInstances(1, 3, DEFAULT_TIMEOUT);
for (Closeable c : Iterables.filter(closeables, Predicates.instanceOf(CalculatingRunnable.class))) {
CloudObjects.destroy(c);
}
assertInstances(0, 0, DEFAULT_TIMEOUT);
}
@Test
public void testSync() throws Exception {
AtomicBoolean async = new AtomicBoolean(false);
AtomicInteger reserve = new AtomicInteger();
getClient(new AsyncScalingPolicy(async, reserve));
CalculatingRunnable worker = CloudObjects.create(CalculatingRunnable.class);
assertInstances(1, 1, -1);
CloudObjects.destroy(worker);
assertInstances(0, 0, DEFAULT_TIMEOUT);
}
@Test
public void testAsync() throws Exception {
AtomicBoolean async = new AtomicBoolean(true);
AtomicInteger reserve = new AtomicInteger();
getClient(new AsyncScalingPolicy(async, reserve));
CalculatingRunnable worker = CloudObjects.create(CalculatingRunnable.class);
// Note that deploying an object on a host that is started asynchronously blocks until the host is started
assertInstances(1, 1, -1);
CloudObjects.destroy(worker);
assertInstances(0, 0, DEFAULT_TIMEOUT);
}
@Test
public void testSingleHostScalingPolicy() throws Exception {
getClient(new SingleHostScalingPolicy(false));
List<TestCloudObject1> objs = new ArrayList<>();
objs.add(new TestCloudObject1());
objs.add(new TestCloudObject1());
objs.add(new TestCloudObject1());
objs.add(new TestCloudObject1());
for(TestCloudObject1 obj : objs)
obj.killMeSoftly();
}
@Test
public void testSingleHostScalingPolicyAsync() throws Exception {
getClient(new SingleHostScalingPolicy(true));
List<TestCloudObject1> objs = new ArrayList<>();
objs.add(new TestCloudObject1());
objs.add(new TestCloudObject1());
objs.add(new TestCloudObject1());
objs.add(new TestCloudObject1());
for(TestCloudObject1 obj : objs)
obj.killMeSoftly();
}
@Test
public void testSingleHostScalingPolicyParallel() throws Exception {
getClient(new SingleHostScalingPolicy(false));
final List<TestCloudObject1> objs = new CopyOnWriteArrayList<>();
ExecutorService threadPool = Executors.newCachedThreadPool();
try
{
for(int i=0;i<5;++i)
threadPool.execute(new Runnable() {
@Override
public void run()
{
objs.add(new TestCloudObject1());
}
});
}
finally
{
threadPool.shutdown();
if(!threadPool.awaitTermination(10, TimeUnit.MINUTES))
throw new Exception("Failed to create objects in time!");
}
for(TestCloudObject1 obj : objs)
obj.killMeSoftly();
}
@Test
public void testSingleHostScalingPolicyParallelAsync() throws Exception {
getClient(new SingleHostScalingPolicy(true));
final List<TestCloudObject1> objs = new CopyOnWriteArrayList<>();
ExecutorService threadPool = Executors.newCachedThreadPool();
try
{
for(int i=0;i<5;++i)
threadPool.execute(new Runnable() {
@Override
public void run()
{
objs.add(new TestCloudObject1());
}
});
}
finally
{
threadPool.shutdown();
if(!threadPool.awaitTermination(10, TimeUnit.MINUTES))
throw new Exception("Failed to create objects in time!");
}
for(TestCloudObject1 obj : objs)
obj.killMeSoftly();
}
private void assertInstances(int hosts, int cloudObjects, int timeout) throws InterruptedException {
CloudManager cloudManager = CloudManager.getInstance();
assertNotNull(cloudManager);
for (long now = System.currentTimeMillis(); timeout > 0 && System.currentTimeMillis() - now < timeout
&& cloudObjects != cloudManager.getCloudObjects().size(); )
;
assertEquals(cloudObjects, cloudManager.getCloudObjects().size());
assertEquals(cloudObjects, cloudManager.getHostPool().countCloudObjects());
assertEquals(cloudObjects, cloudManager.getHostPool().getCloudObjects().size());
for (long now = System.currentTimeMillis(); System.currentTimeMillis() - now < timeout
&& hosts != cloudManager.getHosts().size(); )
;
assertEquals(hosts, cloudManager.getHosts().size());
assertEquals(hosts, cloudManager.getHostPool().getHostsCount());
assertEquals(hosts, Iterables.size(cloudManager.getHostPool().getHosts()));
}
private static class SingleHostScalingPolicy extends AbstractScalingPolicy
{
private volatile IHost host = null;
private boolean startAsync = false;
private Object lock = new Object();
public SingleHostScalingPolicy(boolean startAsync)
{
this.startAsync = startAsync;
}
@Override
public IHost selectHost(ClientCloudObject newCloudObject, IHostPool hostPool)
{
if(host == null)
{
synchronized (lock)
{
if(host == null)
host = startAsync ? hostPool.startNewHostAsync() : hostPool.startNewHost();
}
}
if(hostPool.getHostsCount() != 1 || !hostPool.getHosts().iterator().next().equals(host))
throw new RuntimeException("THERE'S SOMETHING WRONG WITH SCALING POLICY!");
return host;
}
@Override
public boolean scaleDown(IHost scaledHost, IHostPool hostPool) {
return false;
}
}
private static class AsyncScalingPolicy extends AbstractScalingPolicy {
private final AtomicBoolean async;
private AtomicInteger reserve;
public AsyncScalingPolicy(AtomicBoolean async, AtomicInteger reserve) {
this.async = async;
this.reserve = reserve;
}
@Override
public IHost selectHost(ClientCloudObject newCloudObject, IHostPool hostPool) {
IHost host = null;
for (int i = 0; i <= reserve.get(); i++) {
host = async.get() ? hostPool.startNewHostAsync() : hostPool.startNewHost();
}
return host;
}
@Override
public boolean scaleDown(IHost scaledHost, IHostPool hostPool) {
return reserve.get() == 0 && scaledHost.getCloudObjectsCount() == 0;
}
}
}