Repository: incubator-brooklyn Updated Branches: refs/heads/master 726bebca3 -> 12625d82c
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bd44bb8f/usage/qa/src/test/java/org/apache/brooklyn/qa/load/LoadTest.java ---------------------------------------------------------------------- diff --git a/usage/qa/src/test/java/org/apache/brooklyn/qa/load/LoadTest.java b/usage/qa/src/test/java/org/apache/brooklyn/qa/load/LoadTest.java new file mode 100644 index 0000000..d6c479c --- /dev/null +++ b/usage/qa/src/test/java/org/apache/brooklyn/qa/load/LoadTest.java @@ -0,0 +1,244 @@ +/* + * 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.qa.load; + +import org.apache.brooklyn.qa.load.SimulatedTheeTierApp; +import static org.testng.Assert.assertEquals; + +import java.io.File; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import brooklyn.entity.basic.Entities; +import brooklyn.entity.basic.StartableApplication; +import brooklyn.entity.proxying.EntitySpec; +import brooklyn.entity.rebind.persister.PersistMode; +import brooklyn.entity.trait.Startable; +import brooklyn.launcher.BrooklynLauncher; +import brooklyn.location.Location; +import brooklyn.management.ManagementContext; +import brooklyn.management.ha.HighAvailabilityMode; +import brooklyn.management.internal.LocalManagementContext; +import brooklyn.test.PerformanceTestUtils; +import brooklyn.util.os.Os; +import brooklyn.util.time.Duration; + +import com.google.common.base.Stopwatch; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.common.io.Files; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; + +/** + * Customers ask about the scalability of Brooklyn. These load tests investigate how many + * concurrent apps can be deployed and managed by a single Brooklyn management node. + * + * The apps are "simulated" in that they don't create the underlying resources + * (we are not checking if the test machine can run 100s of app-servers simultaneously!) + * The install/customize/launch will instead execute ssh commands of comparable length, + * but that just echo rather than execute the actual commands. + * + * "SIMULATE_EXTERNAL_MONITORING" means that we do not poll the entities directly (over ssh, http or + * whatever). Instead we simulate the metrics being injected directly to be set on the entity (e.g. + * having been collected from a Graphite server). + * + * "SKIP_SSH_ON_START" means don't do the normal install+customize+launch ssh commands. Instead, just + * startup the entities so we can monitor their resource usage. + */ +public class LoadTest { + + // TODO Could/should issue provisioning request through REST api, rather than programmatically; + // and poll to detect completion. + + /* + * Useful commands when investigating: + * LOG_FILE=usage/qa/brooklyn-camp-tests.log + * grep -E "OutOfMemoryError|[P|p]rovisioning time|sleeping before|CPU fraction|LoadTest using" $LOG_FILE | less + * grep -E "OutOfMemoryError|[P|p]rovisioning time" $LOG_FILE; grep "CPU fraction" $LOG_FILE | tail -1; grep "LoadTest using" $LOG_FILE | tail -1 + * grep -E "OutOfMemoryError|LoadTest using" $LOG_FILE + */ + private static final Logger LOG = LoggerFactory.getLogger(LoadTest.class); + + private File persistenceDir; + private BrooklynLauncher launcher; + private String webServerUrl; + private ManagementContext managementContext; + private ListeningExecutorService executor; + private Future<?> cpuFuture; + + private Location localhost; + + List<Duration> provisioningTimes; + + + @BeforeMethod(alwaysRun=true) + public void setUp() throws Exception { + // Create management node + persistenceDir = Files.createTempDir(); + launcher = BrooklynLauncher.newInstance() + .persistMode(PersistMode.CLEAN) + .highAvailabilityMode(HighAvailabilityMode.MASTER) + .persistenceDir(persistenceDir) + .start(); + webServerUrl = launcher.getServerDetails().getWebServerUrl(); + managementContext = launcher.getServerDetails().getManagementContext(); + + localhost = managementContext.getLocationRegistry().resolve("localhost"); + + provisioningTimes = Collections.synchronizedList(Lists.<Duration>newArrayList()); + + // Create executors + executor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool()); + + // Monitor utilisation (memory/CPU) while tests run + executor.submit(new Callable<Void>() { + public Void call() { + try { + while (true) { + managementContext.getExecutionManager(); // force GC to be instantiated + String usage = ((LocalManagementContext)managementContext).getGarbageCollector().getUsageString(); + LOG.info("LoadTest using "+usage); + Thread.sleep(1000); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); // exit gracefully + } catch (Exception e) { + LOG.error("Error getting usage info", e); + } + return null; + }}); + + cpuFuture = PerformanceTestUtils.sampleProcessCpuTime(Duration.ONE_SECOND, "during testProvisionAppsConcurrently"); + + } + + @AfterMethod(alwaysRun=true) + public void tearDown() throws Exception { + if (cpuFuture != null) cpuFuture.cancel(true); + if (executor != null) executor.shutdownNow(); + if (launcher != null) launcher.terminate(); + if (persistenceDir != null) Os.deleteRecursively(persistenceDir); + } + + /** + * Creates multiple apps simultaneously. + * + * Long-term target is 50 concurrent provisioning requests (which may be issued while there are + * many existing applications under management). Until we reach that point, we can partition the + * load across multiple (separate) brooklyn management nodes. + * TODO TBD: is that 50 VMs worth, or 50 apps with 4 VMs in each? + * + * TODO Does not measure the cost of jclouds for creating all the VMs/containers. + */ + @Test(groups="Acceptance") + public void testLocalhostProvisioningAppsConcurrently() throws Exception { + final int NUM_CONCURRENT_APPS_PROVISIONING = 20; + + List<ListenableFuture<StartableApplication>> futures = Lists.newArrayList(); + for (int i = 0; i < NUM_CONCURRENT_APPS_PROVISIONING; i++) { + ListenableFuture<StartableApplication> future = executor.submit(newProvisionAppTask(managementContext, + EntitySpec.create(StartableApplication.class, SimulatedTheeTierApp.class) + .configure(SimulatedTheeTierApp.SIMULATE_EXTERNAL_MONITORING, true) + .displayName("Simulated app "+i))); + futures.add(future); + } + + List<StartableApplication> apps = Futures.allAsList(futures).get(); + + for (StartableApplication app : apps) { + assertEquals(app.getAttribute(Startable.SERVICE_UP), (Boolean)true); + } + } + + /** + * Creates many apps, to monitor resource usage etc. + * + * "SIMULATE_EXTERNAL_MONITORING" means that we do not poll the entities directly (over ssh, http or + * whatever). Instead we simulate the metrics being injected directly to be set on the entity (e.g. + * having been collected from a Graphite server). + * + * Long-term target is 2500 VMs under management. + * Until we reach that point, we can partition the load across multiple (separate) brooklyn management nodes. + */ + @Test(groups="Acceptance") + public void testLocalhostManyApps() throws Exception { + final int NUM_APPS = 630; // target is 2500 VMs; each blueprint has 4 (rounding up) + final int NUM_APPS_PER_BATCH = 10; + final int SLEEP_BETWEEN_BATCHES = 10*1000; + final boolean SKIP_SSH_ON_START = true; // getting ssh errors otherwise! + + int counter = 0; + + for (int i = 0; i < NUM_APPS / NUM_APPS_PER_BATCH; i++) { + List<ListenableFuture<StartableApplication>> futures = Lists.newArrayList(); + for (int j = 0; j < NUM_APPS_PER_BATCH; j++) { + ListenableFuture<StartableApplication> future = executor.submit(newProvisionAppTask( + managementContext, + EntitySpec.create(StartableApplication.class, SimulatedTheeTierApp.class) + .configure(SimulatedTheeTierApp.SIMULATE_EXTERNAL_MONITORING, true) + .configure(SimulatedTheeTierApp.SKIP_SSH_ON_START, SKIP_SSH_ON_START) + .displayName("Simulated app "+(++counter)))); + futures.add(future); + } + + List<StartableApplication> apps = Futures.allAsList(futures).get(); + + for (StartableApplication app : apps) { + assertEquals(app.getAttribute(Startable.SERVICE_UP), (Boolean)true); + } + + synchronized (provisioningTimes) { + LOG.info("cycle="+i+"; numApps="+counter+": provisioning times: "+provisioningTimes); + provisioningTimes.clear(); + } + + LOG.info("cycle="+i+"; numApps="+counter+": sleeping before next batch of apps"); + Thread.sleep(SLEEP_BETWEEN_BATCHES); + } + } + + protected <T extends StartableApplication> Callable<T> newProvisionAppTask(final ManagementContext managementContext, final EntitySpec<T> entitySpec) { + return new Callable<T>() { + public T call() { + Stopwatch stopwatch = Stopwatch.createStarted(); + T app = managementContext.getEntityManager().createEntity(entitySpec); + Entities.startManagement(app, managementContext); + app.start(ImmutableList.of(localhost)); + Duration duration = Duration.of(stopwatch.elapsed(TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS); + LOG.info("Provisioning time: "+duration); + provisioningTimes.add(duration); + + return app; + } + }; + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bd44bb8f/usage/qa/src/test/java/org/apache/brooklyn/qa/longevity/MonitorUtilsTest.java ---------------------------------------------------------------------- diff --git a/usage/qa/src/test/java/org/apache/brooklyn/qa/longevity/MonitorUtilsTest.java b/usage/qa/src/test/java/org/apache/brooklyn/qa/longevity/MonitorUtilsTest.java new file mode 100644 index 0000000..cf377e9 --- /dev/null +++ b/usage/qa/src/test/java/org/apache/brooklyn/qa/longevity/MonitorUtilsTest.java @@ -0,0 +1,166 @@ +/* + * 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.qa.longevity; + +import org.apache.brooklyn.qa.longevity.MonitorUtils; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.testng.annotations.Test; + +import org.apache.brooklyn.qa.longevity.MonitorUtils.ProcessHasStderr; +import brooklyn.util.os.Os; +import brooklyn.util.text.Strings; + +import com.google.common.base.Charsets; +import com.google.common.collect.ImmutableMap; +import com.google.common.io.Files; + +public class MonitorUtilsTest { + + @Test(enabled=false, timeOut=1000) // Demonstrates that process.waitFor() hangs for big output streams + public void testExecuteAndWaitFor() throws Exception { + Process process = createDumpingProcess(false); + process.waitFor(); + fail("Should block while waiting to consume process output"); + } + + @Test(enabled=false, timeOut=1000) // Demonstrates that process.waitFor() hangs for big err streams + public void testExecuteAndWaitForErr() throws Exception { + Process process = createDumpingProcess(true); + process.waitFor(); + fail("Should block while waiting to consume process output"); + } + + @Test(timeOut=1000) + public void testExecuteAndWaitForConsumingOutputStream() throws Exception { + Process process = createDumpingProcess(false); + String out = MonitorUtils.waitFor(process); + assertTrue(out.length() > 100000, "out.size="+out.length()); + } + + @Test(timeOut=1000, expectedExceptions=IllegalStateException.class) + public void testExecuteAndWaitForConsumingErrorStream() throws Exception { + Process process = createDumpingProcess(true); + MonitorUtils.waitFor(process); + } + + private Process createDumpingProcess(boolean writeToErr) throws IOException { + String errSuffix = writeToErr ? " >&2" : ""; + //Windows limits the length of the arguments so echo multiple times instead + String bigstr = Strings.repeat("a", 8000); + String bigcmd = Strings.repeat(getSilentPrefix() + "echo " + bigstr + errSuffix + Os.LINE_SEPARATOR, 15); + File file = Os.newTempFile("test-consume", ".bat"); + file.setExecutable(true); + Files.write(bigcmd, file, Charsets.UTF_8); + Process process = MonitorUtils.exec(file.getAbsolutePath()); + return process; + } + + @Test(groups="UNIX") + public void testFindOwnPid() throws Exception { + int ownpid = MonitorUtils.findOwnPid(); + assertTrue(ownpid > 0, "ownpid=$ownpid"); + assertTrue(MonitorUtils.isPidRunning(ownpid, "java"),"java is not running"); + } + + @Test(groups="UNIX") + public void testIsPidRunning() throws Exception { + int usedPid = MonitorUtils.findOwnPid(); + + //the child process will terminate freeing it PID + String[] cmd = new String[]{"bash", "-c", "echo $$"}; + Process process = Runtime.getRuntime().exec(cmd); + String out = MonitorUtils.waitFor(process); + int unusedPid = Integer.parseInt(out.trim()); + + assertTrue(MonitorUtils.isPidRunning(usedPid)); + assertFalse(MonitorUtils.isPidRunning(unusedPid)); + + try { + assertFalse(MonitorUtils.isPidRunning(1234567)); // too large + } catch (ProcessHasStderr e) { + // expected on osx + } + } + + @Test(groups="UNIX") + public void testGetRunningPids() throws Exception { + int ownpid = MonitorUtils.findOwnPid(); + + List<Integer> javapids = MonitorUtils.getRunningPids("java"); + + assertTrue(javapids.contains(ownpid), "javapids="+javapids+"; ownpid="+ownpid); + } + + @Test + public void testIsUrlUp() throws Exception { + assertFalse(MonitorUtils.isUrlUp(new URL("http://localhost/thispathdoesnotexist"))); + } + + @Test(groups="UNIX") + public void testSearchLog() throws Exception { + String fileContents = "line1\nline2\nline3\n"; + File file = File.createTempFile("monitorUtilsTest.testSearchLog", ".txt"); + Files.write(fileContents, file, Charsets.UTF_8); + + try { + assertEquals(MonitorUtils.searchLog(file, "line1"), Arrays.asList("line1")); + assertEquals(MonitorUtils.searchLog(file, "line1|line2"), Arrays.asList("line1", "line2")); + assertEquals(MonitorUtils.searchLog(file, "textnotthere"), Collections.emptyList()); + assertEquals(MonitorUtils.searchLog(file, "line"), Arrays.asList("line1", "line2", "line3")); + } finally { + file.delete(); + } + } + + @Test(groups="Integration") + public void testMemoryUsage() throws Exception { + int ownpid = MonitorUtils.findOwnPid(); + + MonitorUtils.MemoryUsage memUsage = MonitorUtils.getMemoryUsage(ownpid); + assertTrue(memUsage.totalInstances > 0, memUsage.toString()); + assertTrue(memUsage.totalMemoryBytes > 0, memUsage.toString()); + assertEquals(memUsage.getInstanceCounts(), Collections.emptyMap()); + + MonitorUtils.MemoryUsage memUsage2 = MonitorUtils.getMemoryUsage(ownpid, MonitorUtilsTest.class.getCanonicalName(),0); + assertEquals(memUsage2.getInstanceCounts(), ImmutableMap.of(MonitorUtilsTest.class.getCanonicalName(), 1)); + + MonitorUtils.MemoryUsage memUsage3 = MonitorUtils.getMemoryUsage(ownpid, MonitorUtilsTest.class.getCanonicalName(), 2); + assertEquals(memUsage3.getInstanceCounts(), Collections.emptyMap()); + } + + private String getSilentPrefix() { + if (Os.isMicrosoftWindows()) { + return "@"; + } else { + return ""; + } + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bd44bb8f/usage/qa/src/test/java/org/apache/brooklyn/qa/longevity/webcluster/SinusoidalLoadGenerator.java ---------------------------------------------------------------------- diff --git a/usage/qa/src/test/java/org/apache/brooklyn/qa/longevity/webcluster/SinusoidalLoadGenerator.java b/usage/qa/src/test/java/org/apache/brooklyn/qa/longevity/webcluster/SinusoidalLoadGenerator.java new file mode 100644 index 0000000..17e7a32 --- /dev/null +++ b/usage/qa/src/test/java/org/apache/brooklyn/qa/longevity/webcluster/SinusoidalLoadGenerator.java @@ -0,0 +1,90 @@ +/* + * 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.qa.longevity.webcluster; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.config.ConfigKey; +import brooklyn.enricher.basic.AbstractEnricher; +import brooklyn.entity.basic.ConfigKeys; +import brooklyn.entity.basic.EntityLocal; +import brooklyn.event.AttributeSensor; + +import com.google.common.base.Throwables; +import com.google.common.reflect.TypeToken; + +/** + * Periodically publishes values in the range of 0 to #amplitude. + * The value varies sinusoidally over time. + */ +public class SinusoidalLoadGenerator extends AbstractEnricher { + + private static final Logger LOG = LoggerFactory.getLogger(SinusoidalLoadGenerator.class); + + public static final ConfigKey<AttributeSensor<Double>> TARGET = ConfigKeys.newConfigKey(new TypeToken<AttributeSensor<Double>>() {}, "target"); + + public static final ConfigKey<Long> PUBLISH_PERIOD_MS = ConfigKeys.newLongConfigKey("publishPeriodMs"); + + public static final ConfigKey<Long> SIN_PERIOD_MS = ConfigKeys.newLongConfigKey("sinPeriodMs"); + + public static final ConfigKey<Double> SIN_AMPLITUDE = ConfigKeys.newDoubleConfigKey("sinAmplitude"); + + private final ScheduledExecutorService executor; + + public SinusoidalLoadGenerator() { + this.executor = Executors.newSingleThreadScheduledExecutor(); + } + + public SinusoidalLoadGenerator(AttributeSensor<Double> target, long publishPeriodMs, long sinPeriodMs, double sinAmplitude) { + config().set(TARGET, target); + config().set(PUBLISH_PERIOD_MS, publishPeriodMs); + config().set(SIN_PERIOD_MS, sinPeriodMs); + config().set(SIN_AMPLITUDE, sinAmplitude); + this.executor = Executors.newSingleThreadScheduledExecutor(); + } + + @Override + public void setEntity(final EntityLocal entity) { + super.setEntity(entity); + + executor.scheduleAtFixedRate(new Runnable() { + @Override public void run() { + try { + long time = System.currentTimeMillis(); + double val = getRequiredConfig(SIN_AMPLITUDE) * (1 + Math.sin( (1.0*time) / getRequiredConfig(SIN_PERIOD_MS) * Math.PI * 2 - Math.PI/2 )) / 2; + entity.setAttribute(getRequiredConfig(TARGET), val); + } catch (Throwable t) { + LOG.warn("Error generating sinusoidal-load metric", t); + throw Throwables.propagate(t); + } + } + + }, 0, getRequiredConfig(PUBLISH_PERIOD_MS), TimeUnit.MILLISECONDS); + } + + @Override + public void destroy() { + executor.shutdownNow(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bd44bb8f/usage/qa/src/test/java/org/apache/brooklyn/qa/longevity/webcluster/WebClusterApp.java ---------------------------------------------------------------------- diff --git a/usage/qa/src/test/java/org/apache/brooklyn/qa/longevity/webcluster/WebClusterApp.java b/usage/qa/src/test/java/org/apache/brooklyn/qa/longevity/webcluster/WebClusterApp.java new file mode 100644 index 0000000..0d3d694 --- /dev/null +++ b/usage/qa/src/test/java/org/apache/brooklyn/qa/longevity/webcluster/WebClusterApp.java @@ -0,0 +1,101 @@ +/* + * 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.qa.longevity.webcluster; + +import java.util.List; + +import brooklyn.config.BrooklynProperties; +import brooklyn.enricher.Enrichers; +import brooklyn.entity.basic.AbstractApplication; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.basic.StartableApplication; +import brooklyn.entity.proxy.nginx.NginxController; +import brooklyn.entity.proxying.EntitySpec; +import brooklyn.entity.webapp.ControlledDynamicWebAppCluster; +import brooklyn.entity.webapp.jboss.JBoss7Server; +import brooklyn.event.AttributeSensor; +import brooklyn.event.basic.Sensors; +import brooklyn.launcher.BrooklynLauncher; +import brooklyn.policy.EnricherSpec; +import brooklyn.policy.autoscaling.AutoScalerPolicy; +import brooklyn.util.CommandLineUtil; + +import com.google.common.collect.Lists; + +public class WebClusterApp extends AbstractApplication { + + static BrooklynProperties config = BrooklynProperties.Factory.newDefault(); + + public static final String WAR_PATH = "classpath://hello-world.war"; + + private static final long loadCyclePeriodMs = 2 * 60 * 1000L; + + @Override + public void initApp() { + final AttributeSensor<Double> sinusoidalLoad = + Sensors.newDoubleSensor("brooklyn.qa.sinusoidalLoad", "Sinusoidal server load"); + AttributeSensor<Double> averageLoad = + Sensors.newDoubleSensor("brooklyn.qa.averageLoad", "Average load in cluster"); + + NginxController nginxController = addChild(EntitySpec.create(NginxController.class) + // .configure("domain", "webclusterexample.brooklyn.local") + .configure("port", "8000+")); + + EntitySpec<JBoss7Server> jbossSpec = EntitySpec.create(JBoss7Server.class) + .configure("httpPort", "8080+") + .configure("war", WAR_PATH) + .enricher(EnricherSpec.create(SinusoidalLoadGenerator.class) + .configure(SinusoidalLoadGenerator.TARGET, sinusoidalLoad) + .configure(SinusoidalLoadGenerator.PUBLISH_PERIOD_MS, 500L) + .configure(SinusoidalLoadGenerator.SIN_PERIOD_MS, loadCyclePeriodMs) + .configure(SinusoidalLoadGenerator.SIN_AMPLITUDE, 1d)); + + ControlledDynamicWebAppCluster web = addChild(EntitySpec.create(ControlledDynamicWebAppCluster.class) + .displayName("WebApp cluster") + .configure("controller", nginxController) + .configure("initialSize", 1) + .configure("memberSpec", jbossSpec)); + + web.getCluster().addEnricher(Enrichers.builder() + .aggregating(sinusoidalLoad) + .publishing(averageLoad) + .fromMembers() + .computingAverage() + .build()); + web.getCluster().addPolicy(AutoScalerPolicy.builder() + .metric(averageLoad) + .sizeRange(1, 3) + .metricRange(0.3, 0.7) + .build()); + } + + public static void main(String[] argv) { + List<String> args = Lists.newArrayList(argv); + String port = CommandLineUtil.getCommandLineOption(args, "--port", "8081+"); + String location = CommandLineUtil.getCommandLineOption(args, "--location", "localhost"); + + BrooklynLauncher launcher = BrooklynLauncher.newInstance() + .application(EntitySpec.create(StartableApplication.class, WebClusterApp.class).displayName("Brooklyn WebApp Cluster example")) + .webconsolePort(port) + .location(location) + .start(); + + Entities.dumpInfo(launcher.getApplications()); + } +}
