http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bd44bb8f/usage/qa/src/main/java/org/apache/brooklyn/qa/load/SimulatedMySqlNodeImpl.java ---------------------------------------------------------------------- diff --git a/usage/qa/src/main/java/org/apache/brooklyn/qa/load/SimulatedMySqlNodeImpl.java b/usage/qa/src/main/java/org/apache/brooklyn/qa/load/SimulatedMySqlNodeImpl.java new file mode 100644 index 0000000..8a24f73 --- /dev/null +++ b/usage/qa/src/main/java/org/apache/brooklyn/qa/load/SimulatedMySqlNodeImpl.java @@ -0,0 +1,183 @@ +/* + * 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 static java.lang.String.format; + +import java.util.concurrent.Callable; + +import brooklyn.config.ConfigKey; +import brooklyn.entity.basic.AbstractSoftwareProcessSshDriver; +import brooklyn.entity.database.mysql.MySqlNode; +import brooklyn.entity.database.mysql.MySqlNodeImpl; +import brooklyn.entity.database.mysql.MySqlSshDriver; +import brooklyn.entity.software.SshEffectorTasks; +import brooklyn.event.feed.function.FunctionFeed; +import brooklyn.event.feed.function.FunctionPollConfig; +import brooklyn.location.basic.SshMachineLocation; +import brooklyn.util.collections.MutableMap; +import brooklyn.util.task.DynamicTasks; +import brooklyn.util.task.system.ProcessTaskWrapper; +import brooklyn.util.time.CountdownTimer; +import brooklyn.util.time.Duration; + +/** + * @see SimulatedJBoss7ServerImpl for description of purpose and configuration options. + */ +public class SimulatedMySqlNodeImpl extends MySqlNodeImpl { + + public static final ConfigKey<Boolean> SIMULATE_ENTITY = SimulatedTheeTierApp.SIMULATE_ENTITY; + public static final ConfigKey<Boolean> SIMULATE_EXTERNAL_MONITORING = SimulatedTheeTierApp.SIMULATE_EXTERNAL_MONITORING; + public static final ConfigKey<Boolean> SKIP_SSH_ON_START = SimulatedTheeTierApp.SKIP_SSH_ON_START; + + private FunctionFeed feed; + + @Override + public Class<?> getDriverInterface() { + return SimulatedMySqlSshDriver.class; + } + + @Override + protected void connectSensors() { + boolean simulateExternalMonitoring = getConfig(SIMULATE_EXTERNAL_MONITORING); + if (simulateExternalMonitoring) { + setAttribute(DATASTORE_URL, String.format("mysql://%s:%s/", getAttribute(HOSTNAME), getAttribute(MYSQL_PORT))); + + feed = FunctionFeed.builder() + .entity(this) + .period(Duration.FIVE_SECONDS) + .poll(new FunctionPollConfig<Boolean, Boolean>(SERVICE_UP) + .callable(new Callable<Boolean>() { + private int counter = 0; + public Boolean call() { + setAttribute(QUERIES_PER_SECOND_FROM_MYSQL, (double)(counter++ % 100)); + return true; + }}) + .setOnFailureOrException(false)) + .build(); + } else { + super.connectSensors(); + } + } + + public static class SimulatedMySqlSshDriver extends MySqlSshDriver { + + private int counter = 0; + + public SimulatedMySqlSshDriver(SimulatedMySqlNodeImpl entity, SshMachineLocation machine) { + super(entity, machine); + } + + // simulate metrics, for if using ssh polling + @Override + public String getStatusCmd() { + if (entity.getConfig(SIMULATE_ENTITY)) { + return "echo Uptime: 2427 Threads: 1 Questions: 581 Slow queries: 0 Opens: 53 Flush tables: 1 Open tables: 35 Queries per second avg: "+(counter++ % 100); + } else { + return super.getStatusCmd(); + } + } + + @Override + public void install() { + if (entity.getConfig(SKIP_SSH_ON_START)) { + // no-op + } else { + super.install(); + } + } + + // Not applying creation-script etc, as that requires launching msyqld (so would not scale for single-machine testing) + // This is a copy of super.customize, but with the mysqladmin-exec disabled + @Override + public void customize() { + if (!entity.getConfig(SIMULATE_ENTITY)) { + super.customize(); + return; + } else if (entity.getConfig(SKIP_SSH_ON_START)) { + // no-op + } else { + copyDatabaseConfigScript(); + + newScript(CUSTOMIZING) + .updateTaskAndFailOnNonZeroResultCode() + .body.append( + "chmod 600 "+getConfigFile(), + getBaseDir()+"/scripts/mysql_install_db "+ + "--basedir="+getBaseDir()+" --datadir="+getDataDir()+" "+ + "--defaults-file="+getConfigFile()) + .execute(); + + // launch, then we will configure it + launch(); + + CountdownTimer timer = Duration.seconds(20).countdownTimer(); + boolean hasCreationScript = copyDatabaseCreationScript(); + timer.waitForExpiryUnchecked(); + + // DELIBERATELY SKIPPED FOR SCALABILITY TESTING ON SINGLE MACHINE + DynamicTasks.queue( + SshEffectorTasks.ssh( + "cd "+getRunDir(), + "echo skipping exec of "+getBaseDir()+"/bin/mysqladmin --defaults-file="+getConfigFile()+" --password= password "+getPassword() + ).summary("setting password")); + + if (hasCreationScript) + executeScriptFromInstalledFileAsync("creation-script.sql"); + + // not sure necessary to stop then subsequently launch, but seems safest + // (if skipping, use a flag in launch to indicate we've just launched it) + stop(); + } + } + + @Override + public void launch() { + if (!entity.getConfig(SIMULATE_ENTITY)) { + super.launch(); + return; + } + + entity.setAttribute(MySqlNode.PID_FILE, getRunDir() + "/" + AbstractSoftwareProcessSshDriver.PID_FILENAME); + + if (entity.getConfig(SKIP_SSH_ON_START)) { + // minimal ssh, so that isRunning will subsequently work + newScript(MutableMap.of("usePidFile", true), LAUNCHING) + .body.append( + format("nohup sleep 100000 > %s 2>&1 < /dev/null &", getLogFile())) + .execute(); + } else { + newScript(MutableMap.of("usePidFile", true), LAUNCHING) + .updateTaskAndFailOnNonZeroResultCode() + .body.append(format("echo skipping normal exec of nohup %s/bin/mysqld --defaults-file=%s --user=`whoami` > %s 2>&1 < /dev/null &", getBaseDir(), getConfigFile(), getLogFile())) + .body.append(format("nohup sleep 100000 > %s 2>&1 < /dev/null &", getLogFile())) + .execute(); + } + } + + @Override + public ProcessTaskWrapper<Integer> executeScriptFromInstalledFileAsync(String filenameAlreadyInstalledAtServer) { + return DynamicTasks.queue( + SshEffectorTasks.ssh( + "cd "+getRunDir(), + "echo skipping exec of "+getBaseDir()+"/bin/mysql --defaults-file="+getConfigFile()+" < "+filenameAlreadyInstalledAtServer) + .summary("executing datastore script "+filenameAlreadyInstalledAtServer)); + } + } +}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bd44bb8f/usage/qa/src/main/java/org/apache/brooklyn/qa/load/SimulatedNginxControllerImpl.java ---------------------------------------------------------------------- diff --git a/usage/qa/src/main/java/org/apache/brooklyn/qa/load/SimulatedNginxControllerImpl.java b/usage/qa/src/main/java/org/apache/brooklyn/qa/load/SimulatedNginxControllerImpl.java new file mode 100644 index 0000000..8813a4f --- /dev/null +++ b/usage/qa/src/main/java/org/apache/brooklyn/qa/load/SimulatedNginxControllerImpl.java @@ -0,0 +1,196 @@ +/* + * 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 static java.lang.String.format; + +import java.net.URI; +import java.util.Collection; +import java.util.concurrent.Callable; + +import brooklyn.config.ConfigKey; +import brooklyn.entity.Group; +import brooklyn.entity.proxy.nginx.NginxControllerImpl; +import brooklyn.entity.proxy.nginx.NginxSshDriver; +import brooklyn.entity.proxy.nginx.UrlMapping; +import brooklyn.event.SensorEvent; +import brooklyn.event.SensorEventListener; +import brooklyn.event.feed.ConfigToAttributes; +import brooklyn.event.feed.function.FunctionFeed; +import brooklyn.event.feed.function.FunctionPollConfig; +import brooklyn.event.feed.http.HttpFeed; +import brooklyn.event.feed.http.HttpPollConfig; +import brooklyn.location.basic.SshMachineLocation; +import brooklyn.policy.PolicySpec; +import brooklyn.util.collections.MutableMap; +import brooklyn.util.net.Networking; + +import com.google.common.base.Functions; + +/** + * @see SimulatedJBoss7ServerImpl for description of purpose and configuration options. + */ +public class SimulatedNginxControllerImpl extends NginxControllerImpl { + + public static final ConfigKey<Boolean> SIMULATE_ENTITY = SimulatedTheeTierApp.SIMULATE_ENTITY; + public static final ConfigKey<Boolean> SIMULATE_EXTERNAL_MONITORING = SimulatedTheeTierApp.SIMULATE_EXTERNAL_MONITORING; + public static final ConfigKey<Boolean> SKIP_SSH_ON_START = SimulatedTheeTierApp.SKIP_SSH_ON_START; + + private HttpFeed httpFeed; + private FunctionFeed functionFeed; + + @Override + public Class<?> getDriverInterface() { + return SimulatedNginxSshDriver.class; + } + + @Override + public void connectSensors() { + boolean simulateEntity = getConfig(SIMULATE_ENTITY); + boolean simulateExternalMonitoring = getConfig(SIMULATE_EXTERNAL_MONITORING); + + if (!simulateEntity && !simulateExternalMonitoring) { + super.connectSensors(); + return; + } + + // From AbstractController.connectSensors + if (getUrl()==null) { + setAttribute(MAIN_URI, URI.create(inferUrl())); + setAttribute(ROOT_URL, inferUrl()); + } + addServerPoolMemberTrackingPolicy(); + + // From NginxController.connectSensors + ConfigToAttributes.apply(this); + + if (!simulateExternalMonitoring) { + // if simulating entity, then simulate work of periodic HTTP request; TODO but not parsing JSON response + String uriToPoll = (simulateEntity) ? "http://localhost:8081" : getAttribute(MAIN_URI).toString(); + + httpFeed = HttpFeed.builder() + .entity(this) + .period(getConfig(HTTP_POLL_PERIOD)) + .baseUri(uriToPoll) + .poll(new HttpPollConfig<Boolean>(SERVICE_UP) + .onSuccess(Functions.constant(true)) + .onFailureOrException(Functions.constant(true))) + .build(); + } + + functionFeed = FunctionFeed.builder() + .entity(this) + .period(getConfig(HTTP_POLL_PERIOD)) + .poll(new FunctionPollConfig<Boolean,Boolean>(SERVICE_UP) + .callable(new Callable<Boolean>() { + public Boolean call() { + return true; + }})) + .build(); + + // Can guarantee that parent/managementContext has been set + Group urlMappings = getConfig(URL_MAPPINGS); + if (urlMappings != null) { + // Listen to the targets of each url-mapping changing + subscribeToMembers(urlMappings, UrlMapping.TARGET_ADDRESSES, new SensorEventListener<Collection<String>>() { + @Override public void onEvent(SensorEvent<Collection<String>> event) { + updateNeeded(); + } + }); + + // Listen to url-mappings being added and removed + urlMappingsMemberTrackerPolicy = addPolicy(PolicySpec.create(UrlMappingsMemberTrackerPolicy.class) + .configure("group", urlMappings)); + } + } + + @Override + protected void disconnectSensors() { + super.disconnectSensors(); + if (httpFeed != null) httpFeed.stop(); + if (functionFeed != null) functionFeed.stop(); + } + + public static class SimulatedNginxSshDriver extends NginxSshDriver { + public SimulatedNginxSshDriver(SimulatedNginxControllerImpl entity, SshMachineLocation machine) { + super(entity, machine); + } + + @Override + public void install() { + if (entity.getConfig(SKIP_SSH_ON_START)) { + // no-op + } else { + super.install(); + } + } + + @Override + public void customize() { + if (entity.getConfig(SKIP_SSH_ON_START)) { + // no-op + } else { + super.customize(); + } + } + + @Override + public void launch() { + if (!entity.getConfig(SIMULATE_ENTITY)) { + super.launch(); + return; + } + + Networking.checkPortsValid(MutableMap.of("httpPort", getPort())); + + if (entity.getConfig(SKIP_SSH_ON_START)) { + // minimal ssh, so that isRunning will subsequently work + newScript(MutableMap.of("usePidFile", getPidFile()), LAUNCHING) + .body.append( + format("mkdir -p %s/logs", getRunDir()), + format("nohup sleep 100000 > %s 2>&1 < /dev/null &", getLogFileLocation())) + .execute(); + } else { + newScript(MutableMap.of("usePidFile", false), LAUNCHING) + .body.append( + format("cd %s", getRunDir()), + "echo skipping exec of requireExecutable ./sbin/nginx", + sudoBashCIfPrivilegedPort(getPort(), format( + "echo skipping exec of nohup ./sbin/nginx -p %s/ -c conf/server.conf > %s 2>&1 &", getRunDir(), getLogFileLocation())), + format("nohup sleep 100000 > %s 2>&1 < /dev/null &", getLogFileLocation()), + format("echo $! > "+getPidFile()), + format("for i in {1..10}\n" + + "do\n" + + " test -f %1$s && ps -p `cat %1$s` && exit\n" + + " sleep 1\n" + + "done\n" + + "echo \"No explicit error launching nginx but couldn't find process by pid; continuing but may subsequently fail\"\n" + + "cat %2$s | tee /dev/stderr", + getPidFile(), getLogFileLocation())) + .execute(); + } + } + + // Use pid file, because just simulating the run of nginx + @Override + public void stop() { + newScript(MutableMap.of("usePidFile", getPidFile()), STOPPING).execute(); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bd44bb8f/usage/qa/src/main/java/org/apache/brooklyn/qa/load/SimulatedTheeTierApp.java ---------------------------------------------------------------------- diff --git a/usage/qa/src/main/java/org/apache/brooklyn/qa/load/SimulatedTheeTierApp.java b/usage/qa/src/main/java/org/apache/brooklyn/qa/load/SimulatedTheeTierApp.java new file mode 100644 index 0000000..273d130 --- /dev/null +++ b/usage/qa/src/main/java/org/apache/brooklyn/qa/load/SimulatedTheeTierApp.java @@ -0,0 +1,140 @@ +/* + * 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 static brooklyn.event.basic.DependentConfiguration.attributeWhenReady; +import static brooklyn.event.basic.DependentConfiguration.formatString; + +import java.util.Collection; +import java.util.List; + +import brooklyn.config.ConfigKey; +import brooklyn.enricher.Enrichers; +import brooklyn.enricher.HttpLatencyDetector; +import brooklyn.entity.basic.AbstractApplication; +import brooklyn.entity.basic.Attributes; +import brooklyn.entity.basic.ConfigKeys; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.basic.StartableApplication; +import brooklyn.entity.database.mysql.MySqlNode; +import brooklyn.entity.group.DynamicCluster; +import brooklyn.entity.java.JavaEntityMethods; +import brooklyn.entity.proxy.nginx.NginxController; +import brooklyn.entity.proxying.EntitySpec; +import brooklyn.entity.trait.Startable; +import brooklyn.entity.webapp.ControlledDynamicWebAppCluster; +import brooklyn.entity.webapp.DynamicWebAppCluster; +import brooklyn.entity.webapp.JavaWebAppService; +import brooklyn.entity.webapp.WebAppService; +import brooklyn.entity.webapp.WebAppServiceConstants; +import brooklyn.entity.webapp.jboss.JBoss7Server; +import brooklyn.launcher.BrooklynLauncher; +import brooklyn.location.basic.PortRanges; +import brooklyn.policy.autoscaling.AutoScalerPolicy; +import brooklyn.util.CommandLineUtil; +import brooklyn.util.collections.MutableSet; + +import com.google.common.base.Function; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; + +/** + * A 3-tier app where all components are just "simulated" - they don't actually run + * real app-servers or databases, instead just executing a "sleep" command to simulate + * the running process. + * + * This is useful for load testing, where we want to test the performance of Brooklyn + * rather than the ability to host many running app-servers. + * + * The app is based on WebClusterDatabaseExampleApp + * + * @see SimulatedJBoss7ServerImpl for description of purpose and configuration options. + */ +public class SimulatedTheeTierApp extends AbstractApplication { + + public static final ConfigKey<Boolean> SIMULATE_ENTITY = ConfigKeys.newBooleanConfigKey("simulateEntity", "", true); + + public static final ConfigKey<Boolean> SIMULATE_EXTERNAL_MONITORING = ConfigKeys.newBooleanConfigKey("simulateExternalMonitoring", "", true); + + public static final ConfigKey<Boolean> SKIP_SSH_ON_START = ConfigKeys.newBooleanConfigKey("skipSshOnStart", "", false); + + public static final String WAR_PATH = "classpath://hello-world.war"; + public static final String DB_TABLE = "visitors"; + public static final String DB_USERNAME = "brooklyn"; + public static final String DB_PASSWORD = "br00k11n"; + public static final boolean USE_HTTPS = false; + + @Override + public void init() { + MySqlNode mysql = addChild( + EntitySpec.create(MySqlNode.class) + .impl(SimulatedMySqlNodeImpl.class)); + + ControlledDynamicWebAppCluster web = addChild( + EntitySpec.create(ControlledDynamicWebAppCluster.class) + .configure(ControlledDynamicWebAppCluster.MEMBER_SPEC, EntitySpec.create(JBoss7Server.class).impl(SimulatedJBoss7ServerImpl.class)) + .configure(ControlledDynamicWebAppCluster.CONTROLLER_SPEC, EntitySpec.create(NginxController.class).impl(SimulatedNginxControllerImpl.class)) + .configure(WebAppService.HTTP_PORT, PortRanges.fromString("8080+")) + .configure(JavaWebAppService.ROOT_WAR, WAR_PATH) + .configure(JavaEntityMethods.javaSysProp("brooklyn.example.db.url"), + formatString("jdbc:%s%s?user=%s\\&password=%s", + attributeWhenReady(mysql, MySqlNode.DATASTORE_URL), DB_TABLE, DB_USERNAME, DB_PASSWORD)) + .configure(DynamicCluster.INITIAL_SIZE, 2) + .configure(WebAppService.ENABLED_PROTOCOLS, ImmutableSet.of(USE_HTTPS ? "https" : "http")) ); + + web.getCluster().addPolicy(AutoScalerPolicy.builder(). + metric(DynamicWebAppCluster.REQUESTS_PER_SECOND_IN_WINDOW_PER_NODE). + metricRange(10, 100). + sizeRange(2, 5). + build()); + + addEnricher(Enrichers.builder() + .propagating(Attributes.MAIN_URI, WebAppServiceConstants.ROOT_URL, + DynamicWebAppCluster.REQUESTS_PER_SECOND_IN_WINDOW, + HttpLatencyDetector.REQUEST_LATENCY_IN_SECONDS_IN_WINDOW) + .from(web) + .build()); + + addEnricher(Enrichers.builder() + .aggregating(Startable.SERVICE_UP) + .publishing(Startable.SERVICE_UP) + .fromHardcodedProducers(ImmutableList.of(web, mysql)) + .computing(new Function<Collection<Boolean>, Boolean>() { + @Override public Boolean apply(Collection<Boolean> input) { + return input != null && input.size() == 2 && MutableSet.copyOf(input).equals(ImmutableSet.of(true)); + }}) + .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, SimulatedTheeTierApp.class) + .displayName("Brooklyn WebApp Cluster with Database example")) + .webconsolePort(port) + .location(location) + .start(); + + Entities.dumpInfo(launcher.getApplications()); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bd44bb8f/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/Monitor.java ---------------------------------------------------------------------- diff --git a/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/Monitor.java b/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/Monitor.java new file mode 100644 index 0000000..7a3ea42 --- /dev/null +++ b/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/Monitor.java @@ -0,0 +1,261 @@ +/* + * 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 static org.apache.brooklyn.qa.longevity.StatusRecorder.Factory.chain; +import static org.apache.brooklyn.qa.longevity.StatusRecorder.Factory.noop; +import static org.apache.brooklyn.qa.longevity.StatusRecorder.Factory.toFile; +import static org.apache.brooklyn.qa.longevity.StatusRecorder.Factory.toLog; +import static java.util.concurrent.TimeUnit.SECONDS; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import joptsimple.OptionParser; +import joptsimple.OptionSet; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.util.collections.TimeWindowedList; +import brooklyn.util.collections.TimestampedValue; +import brooklyn.util.time.Duration; + +import com.google.common.base.Charsets; +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.common.collect.Range; +import com.google.common.io.Files; + +public class Monitor { + + private static final Logger LOG = LoggerFactory.getLogger(Monitor.class); + + private static final int checkPeriodMs = 1000; + + private static final OptionParser parser = new OptionParser() { + { + acceptsAll(ImmutableList.of("help", "?", "h"), "show help"); + accepts("webUrl", "Web-app url") + .withRequiredArg().ofType(URL.class); + accepts("brooklynPid", "Brooklyn pid") + .withRequiredArg().ofType(Integer.class); + accepts("logFile", "Brooklyn log file") + .withRequiredArg().ofType(File.class); + accepts("logGrep", "Grep in log file (defaults to 'SEVERE|ERROR|WARN|Exception|Error'") + .withRequiredArg().ofType(String.class); + accepts("logGrepExclusionsFile", "File of expressions to be ignored in log file") + .withRequiredArg().ofType(File.class); + accepts("webProcesses", "Name (for `ps ax | grep` of web-processes") + .withRequiredArg().ofType(String.class); + accepts("numWebProcesses", "Number of web-processes expected (e.g. 1 or 1-3)") + .withRequiredArg().ofType(String.class); + accepts("webProcessesCyclingPeriod", "The period (in seconds) for cycling through the range of numWebProcesses") + .withRequiredArg().ofType(Integer.class); + accepts("outFile", "File to write monitor status info") + .withRequiredArg().ofType(File.class); + accepts("abortOnError", "Exit the JVM on error, with exit code 1") + .withRequiredArg().ofType(Boolean.class); + } + }; + + public static void main(String[] argv) throws InterruptedException, IOException { + OptionSet options = parse(argv); + + if (options == null || options.has("help")) { + parser.printHelpOn(System.out); + System.exit(0); + } + + MonitorPrefs prefs = new MonitorPrefs(); + prefs.webUrl = options.hasArgument("webUrl") ? (URL) options.valueOf("webUrl") : null; + prefs.brooklynPid = options.hasArgument("brooklynPid") ? (Integer) options.valueOf("brooklynPid") : -1; + prefs.logFile = options.hasArgument("logFile") ? (File) options.valueOf("logFile") : null; + prefs.logGrep = options.hasArgument("logGrep") ? (String) options.valueOf("logGrep") : "SEVERE|ERROR|WARN|Exception|Error"; + prefs.logGrepExclusionsFile = options.hasArgument("logGrepExclusionsFile") ? (File) options.valueOf("logGrepExclusionsFile") : null; + prefs.webProcessesRegex = options.hasArgument("webProcesses") ? (String) options.valueOf("webProcesses") : null; + prefs.numWebProcesses = options.hasArgument("numWebProcesses") ? parseRange((String) options.valueOf("numWebProcesses")) : null; + prefs.webProcessesCyclingPeriod = options.hasArgument("webProcessesCyclingPeriod") ? (Integer) options.valueOf("webProcessesCyclingPeriod") : -1; + prefs.outFile = options.hasArgument("outFile") ? (File) options.valueOf("outFile") : null; + prefs.abortOnError = options.hasArgument("abortOnError") ? (Boolean) options.valueOf("abortOnError") : false; + Monitor main = new Monitor(prefs, MonitorListener.NOOP); + main.start(); + } + + private static Range<Integer> parseRange(String range) { + if (range.contains("-")) { + String[] parts = range.split("-"); + return Range.closed(Integer.parseInt(parts[0]), Integer.parseInt(parts[1])); + } else { + return Range.singleton(Integer.parseInt(range)); + } + } + + private static OptionSet parse(String...argv) { + try { + return parser.parse(argv); + } catch (Exception e) { + System.out.println("Error in parsing options: " + e.getMessage()); + return null; + } + } + + private final MonitorPrefs prefs; + private final StatusRecorder recorder; + private final MonitorListener listener; + + public Monitor(MonitorPrefs prefs, MonitorListener listener) { + this.prefs = prefs; + this.listener = listener; + this.recorder = chain(toLog(LOG), (prefs.outFile != null ? toFile(prefs.outFile) : noop())); + } + + private void start() throws IOException { + LOG.info("Monitoring: "+prefs); + ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); + + final AtomicReference<List<String>> previousLogLines = new AtomicReference<List<String>>(Collections.<String>emptyList()); + final TimeWindowedList<Integer> numWebProcessesHistory = new TimeWindowedList<Integer>( + ImmutableMap.of("timePeriod", Duration.seconds(prefs.webProcessesCyclingPeriod), "minExpiredVals", 1)); + final Set<String> logGrepExclusions = ImmutableSet.copyOf(Files.readLines(prefs.logGrepExclusionsFile, Charsets.UTF_8)); + + executor.scheduleAtFixedRate(new Runnable() { + @Override public void run() { + StatusRecorder.Record record = new StatusRecorder.Record(); + StringBuilder failureMsg = new StringBuilder(); + try { + if (prefs.brooklynPid > 0) { + boolean pidRunning = MonitorUtils.isPidRunning(prefs.brooklynPid, "java"); + MonitorUtils.MemoryUsage memoryUsage = MonitorUtils.getMemoryUsage(prefs.brooklynPid, ".*brooklyn.*", 1000); + record.put("pidRunning", pidRunning); + record.put("totalMemoryBytes", memoryUsage.getTotalMemoryBytes()); + record.put("totalMemoryInstances", memoryUsage.getTotalInstances()); + record.put("instanceCounts", memoryUsage.getInstanceCounts()); + + if (!pidRunning) { + failureMsg.append("pid "+prefs.brooklynPid+" is not running"+"\n"); + } + } + if (prefs.webUrl != null) { + boolean webUrlUp = MonitorUtils.isUrlUp(prefs.webUrl); + record.put("webUrlUp", webUrlUp); + + if (!webUrlUp) { + failureMsg.append("web URL "+prefs.webUrl+" is not available"+"\n"); + } + } + if (prefs.logFile != null) { + List<String> logLines = MonitorUtils.searchLog(prefs.logFile, prefs.logGrep, logGrepExclusions); + List<String> newLogLines = getAdditions(previousLogLines.get(), logLines); + previousLogLines.set(logLines); + record.put("logLines", newLogLines); + + if (newLogLines.size() > 0) { + failureMsg.append("Log contains warnings/errors: "+newLogLines+"\n"); + } + } + if (prefs.webProcessesRegex != null) { + List<Integer> pids = MonitorUtils.getRunningPids(prefs.webProcessesRegex, "--webProcesses"); + pids.remove((Object)MonitorUtils.findOwnPid()); + + record.put("webPids", pids); + record.put("numWebPids", pids.size()); + numWebProcessesHistory.add(pids.size()); + + if (prefs.numWebProcesses != null) { + boolean numWebPidsInRange = prefs.numWebProcesses.apply(pids.size()); + record.put("numWebPidsInRange", numWebPidsInRange); + + if (!numWebPidsInRange) { + failureMsg.append("num web processes out-of-range: pids="+pids+"; size="+pids.size()+"; expected="+prefs.numWebProcesses); + } + + if (prefs.webProcessesCyclingPeriod > 0) { + List<TimestampedValue<Integer>> values = numWebProcessesHistory.getValues(); + long valuesTimeRange = (values.get(values.size()-1).getTimestamp() - values.get(0).getTimestamp()); + if (values.size() > 0 && valuesTimeRange > SECONDS.toMillis(prefs.webProcessesCyclingPeriod)) { + int min = -1; + int max = -1; + for (TimestampedValue<Integer> val : values) { + min = (min < 0) ? val.getValue() : Math.min(val.getValue(), min); + max = Math.max(val.getValue(), max); + } + record.put("minWebSizeInPeriod", min); + record.put("maxWebSizeInPeriod", max); + + if (min > prefs.numWebProcesses.lowerEndpoint() || max < prefs.numWebProcesses.upperEndpoint()) { + failureMsg.append("num web processes not increasing/decreasing correctly: " + + "pids="+pids+"; size="+pids.size()+"; cyclePeriod="+prefs.webProcessesCyclingPeriod+ + "; expectedRange="+prefs.numWebProcesses+"; min="+min+"; max="+max+"; history="+values); + } + } else { + int numVals = values.size(); + long startTime = (numVals > 0) ? values.get(0).getTimestamp() : 0; + long endTime = (numVals > 0) ? values.get(values.size()-1).getTimestamp() : 0; + LOG.info("Insufficient vals in time-window to determine cycling behaviour over period ("+prefs.webProcessesCyclingPeriod+"secs): "+ + "numVals="+numVals+"; startTime="+startTime+"; endTime="+endTime+"; periodCovered="+(endTime-startTime)/1000); + } + } + } + } + + } catch (Throwable t) { + LOG.error("Error during periodic checks", t); + throw Throwables.propagate(t); + } + + try { + recorder.record(record); + listener.onRecord(record); + + if (failureMsg.length() > 0) { + listener.onFailure(record, failureMsg.toString()); + + if (prefs.abortOnError) { + LOG.error("Aborting on error: "+failureMsg); + System.exit(1); + } + } + + } catch (Throwable t) { + LOG.warn("Error recording monitor info ("+record+")", t); + throw Throwables.propagate(t); + } + } + }, 0, checkPeriodMs, TimeUnit.MILLISECONDS); + } + + // TODO What is the guava equivalent? Don't want Set.difference, because duplicates/ordered. + private static List<String> getAdditions(List<String> prev, List<String> next) { + List<String> result = Lists.newArrayList(next); + result.removeAll(prev); + return result; + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bd44bb8f/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/MonitorListener.java ---------------------------------------------------------------------- diff --git a/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/MonitorListener.java b/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/MonitorListener.java new file mode 100644 index 0000000..11fdd3f --- /dev/null +++ b/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/MonitorListener.java @@ -0,0 +1,35 @@ +/* + * 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.StatusRecorder.Record; + +public interface MonitorListener { + + public static final MonitorListener NOOP = new MonitorListener() { + @Override public void onRecord(Record record) { + } + @Override public void onFailure(Record record, String msg) { + } + }; + + public void onRecord(Record record); + + public void onFailure(Record record, String msg); +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bd44bb8f/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/MonitorPrefs.java ---------------------------------------------------------------------- diff --git a/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/MonitorPrefs.java b/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/MonitorPrefs.java new file mode 100644 index 0000000..4d74045 --- /dev/null +++ b/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/MonitorPrefs.java @@ -0,0 +1,54 @@ +/* + * 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 java.io.File; +import java.net.URL; + +import com.google.common.base.Objects; +import com.google.common.collect.Range; + +public class MonitorPrefs { + + public URL webUrl; + public int brooklynPid; + public File logFile; + public String logGrep; + public File logGrepExclusionsFile; + public String webProcessesRegex; + public Range<Integer> numWebProcesses; + public int webProcessesCyclingPeriod; + public File outFile; + public boolean abortOnError; + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("webUrl", webUrl) + .add("brooklynPid", brooklynPid) + .add("logFile", logFile) + .add("logGrep", logGrep) + .add("logGrepExclusionsFile", logGrepExclusionsFile) + .add("outFile", outFile) + .add("webProcessesRegex", webProcessesRegex) + .add("numWebProcesses", numWebProcesses) + .add("webProcessesCyclingPeriod", webProcessesCyclingPeriod) + .toString(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bd44bb8f/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/MonitorUtils.java ---------------------------------------------------------------------- diff --git a/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/MonitorUtils.java b/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/MonitorUtils.java new file mode 100644 index 0000000..54acb3b --- /dev/null +++ b/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/MonitorUtils.java @@ -0,0 +1,329 @@ +/* + * 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 static com.google.common.base.Strings.isNullOrEmpty; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.util.http.HttpTool; +import brooklyn.util.http.HttpToolResponse; +import brooklyn.util.stream.StreamGobbler; + +import com.google.common.base.Objects; +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import com.google.common.io.ByteStreams; + +public class MonitorUtils { + + private static final Logger LOG = LoggerFactory.getLogger(MonitorUtils.class); + + private static volatile int ownPid = -1; + + /** + * Confirm can read from URL. + * + * @param url + */ + public static boolean isUrlUp(URL url) { + try { + HttpToolResponse result = HttpTool.httpGet( + HttpTool.httpClientBuilder().trustAll().build(), + URI.create(url.toString()), + ImmutableMap.<String,String>of()); + int statuscode = result.getResponseCode(); + + if (statuscode != 200) { + LOG.info("Error reading URL {}: {}, {}", new Object[]{url, statuscode, result.getReasonPhrase()}); + return false; + } else { + return true; + } + } catch (Exception e) { + LOG.info("Error reading URL {}: {}", url, e); + return false; + } + } + + public static boolean isPidRunning(int pid) { + return isPidRunning(pid, null); + } + + /** + * Confirm the given pid is running, and that the the process matches the given regex. + * + * @param pid + * @param regex + */ + public static boolean isPidRunning(int pid, String regex) { + Process process = exec("ps -p " + pid); + String out = waitFor(process); + if (process.exitValue() > 0) { + String err = toString(process.getErrorStream()); + LOG.info(String.format("pid %s not running: %s", pid, err)); + return false; + } + + if (regex != null) { + String regex2 = "^\\s*" + pid + ".*" + regex; + boolean found = false; + for (String line : out.split("\n")) { + if (hasAtLeastOneMatch(line, regex2)) { + found = true; + break; + } + } + + if (!found) { + String txt = toString(process.getInputStream()); + LOG.info("process did not match regular expression: "+txt); + return false; + } + } + + return true; + } + + private static boolean hasAtLeastOneMatch(String line, String regex) { + return Pattern.matches(".*"+regex+".*", line); + } + + private static String toString(InputStream in){ + try { + byte[] bytes = ByteStreams.toByteArray(in); + return new String(bytes); + } catch (IOException e) { + throw Throwables.propagate(e); + } + + } + + public static List<Integer> getRunningPids(String regex) { + return getRunningPids(regex, null); + } + + /** + * Confirm the given pid is running, and that the the process matches the given regex. + * + * @param regex + * @param excludingRegex + */ + public static List<Integer> getRunningPids(String regex, String excludingRegex) { + Process process = exec("ps ax"); + String out = waitFor(process); + + List<Integer> result = new LinkedList<Integer>(); + for (String line : out.split("\n")) { + if (excludingRegex != null && hasAtLeastOneMatch(line, excludingRegex)) { + continue; + } + if (hasAtLeastOneMatch(line, regex)) { + String[] linesplit = line.trim().split("\\s+"); + result.add(Integer.parseInt(linesplit[0])); + } + } + return result; + } + + public static MemoryUsage getMemoryUsage(int pid){ + return getMemoryUsage(pid, null,0); + } + + /** + * @param pid + */ + public static MemoryUsage getMemoryUsage(int pid, String clazzRegexOfInterest, int minInstancesOfInterest) { + Process process = exec(String.format("jmap -histo %s", pid)); + String out = waitFor(process); + + Map<String, Integer> instanceCounts = Maps.newLinkedHashMap(); + long totalInstances=0; + long totalMemoryBytes=0; + + for (String line : out.split("\n")) { + if (clazzRegexOfInterest!=null && hasAtLeastOneMatch(line, clazzRegexOfInterest)) { + // Format is: + // num #instances #bytes class name + // 1: 43506 8047096 example.MyClazz + + String[] parts = line.trim().split("\\s+"); + String clazz = parts[3]; + int instanceCount = Integer.parseInt(parts[1]); + if (instanceCount >= minInstancesOfInterest) { + instanceCounts.put(clazz, instanceCount); + } + } + if (hasAtLeastOneMatch(line, "^Total.*")) { + String[] parts = line.split("\\s+"); + totalInstances = Long.parseLong(parts[1]); + totalMemoryBytes = Long.parseLong(parts[2]); + } + } + + return new MemoryUsage(totalInstances, totalMemoryBytes, instanceCounts); + } + + public static class MemoryUsage { + final long totalInstances; + final long totalMemoryBytes; + final Map<String, Integer> instanceCounts; + + MemoryUsage(long totalInstances, long totalMemoryBytes, Map<String, Integer> instanceCounts) { + this.totalInstances = totalInstances; + this.totalMemoryBytes = totalMemoryBytes; + this.instanceCounts = instanceCounts; + } + + public String toString() { + return Objects.toStringHelper(this) + .add("totalInstances", totalInstances) + .add("totalMemoryBytes", totalMemoryBytes) + .add("instanceCounts", instanceCounts) + .toString(); + } + + public long getTotalInstances() { + return totalInstances; + } + + public long getTotalMemoryBytes() { + return totalMemoryBytes; + } + + public Map<String, Integer> getInstanceCounts() { + return instanceCounts; + } + } + + public static List<String> searchLog(File file, String grepOfInterest) { + return searchLog(file, grepOfInterest, new LinkedHashSet<String>()); + } + + /** + * Find lines in the given file that match given given regex. + * + * @param file + * @param grepOfInterest + */ + public static List<String> searchLog(File file, String grepOfInterest, Set<String> grepExclusions) { + Process process = exec(String.format("grep -E %s %s", grepOfInterest, file.getAbsoluteFile())); + String out = waitFor(process); + + // TODO Annoying that String.split() returns size 1 when empty string; lookup javadoc when back online... + if (out.length() == 0) return Collections.<String>emptyList(); + + List<String> result = new ArrayList<String>(); + for (String line : out.trim().split("\n")) { + boolean excluded = false; + for (String exclusion : grepExclusions) { + if (!isNullOrEmpty(exclusion) && hasAtLeastOneMatch(line, exclusion)) { + excluded = true; + } + } + if (!excluded) { + result.add(line); + } + } + return result; + } + + public static Process exec(String cmd) { + LOG.info("executing cmd: " + cmd); + + try { + return Runtime.getRuntime().exec(cmd); + } catch (IOException e) { + throw Throwables.propagate(e); + } + } + + public static class ProcessHasStderr extends IllegalStateException { + private static final long serialVersionUID = -937871002993888405L; + + byte[] stderrBytes; + public ProcessHasStderr(byte[] stderrBytes) { + this("Process printed to stderr: " + new String(stderrBytes), stderrBytes); + } + public ProcessHasStderr(String message, byte[] stderrBytes) { + super(message); + this.stderrBytes = stderrBytes; + } + } + + /** + * Waits for the given process to complete, consuming its stdout and returning it as a string. + * If there is any output on stderr an exception will be thrown. + */ + public static String waitFor(Process process) { + ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); + @SuppressWarnings("resource") //Closeable doesn't seem appropriate for StreamGobbler since it isn't expected to be called every time + StreamGobbler gobblerOut = new StreamGobbler(process.getInputStream(), bytesOut, null); + gobblerOut.start(); + + ByteArrayOutputStream bytesErr = new ByteArrayOutputStream(); + @SuppressWarnings("resource") + StreamGobbler gobblerErr = new StreamGobbler(process.getErrorStream(), bytesErr, null); + gobblerErr.start(); + + try { + process.waitFor(); + gobblerOut.blockUntilFinished(); + gobblerErr.blockUntilFinished(); + + if (bytesErr.size() > 0) { + throw new ProcessHasStderr(bytesErr.toByteArray()); + } + + return new String(bytesOut.toByteArray()); + } catch (Exception e) { + throw Throwables.propagate(e); + } finally { + if (gobblerOut.isAlive()) gobblerOut.interrupt(); + if (gobblerErr.isAlive()) gobblerErr.interrupt(); + } + } + + public static int findOwnPid() throws IOException { + if (ownPid >= 0) return ownPid; + + String[] cmd = new String[]{"bash", "-c", "echo $PPID"}; + Process process = Runtime.getRuntime().exec(cmd); + String out = MonitorUtils.waitFor(process); + ownPid = Integer.parseInt(out.trim()); + return ownPid; + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bd44bb8f/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/StatusRecorder.java ---------------------------------------------------------------------- diff --git a/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/StatusRecorder.java b/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/StatusRecorder.java new file mode 100644 index 0000000..210e0a2 --- /dev/null +++ b/usage/qa/src/main/java/org/apache/brooklyn/qa/longevity/StatusRecorder.java @@ -0,0 +1,130 @@ +/* + * 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 java.io.File; +import java.io.IOException; +import java.util.Map; + +import org.slf4j.Logger; + +import com.google.common.base.Charsets; +import com.google.common.collect.Maps; +import com.google.common.io.Files; + +public interface StatusRecorder { + + public void record(Record record) throws IOException; + + public static class Factory { + public static final StatusRecorder NOOP = new StatusRecorder() { + @Override public void record(Record record) {} + }; + + public static StatusRecorder noop() { + return NOOP; + } + public static StatusRecorder toFile(File outFile) { + return new FileBasedStatusRecorder(outFile); + } + public static StatusRecorder toSysout() { + return new SysoutBasedStatusRecorder(); + } + public static StatusRecorder toLog(Logger log) { + return new LogBasedStatusRecorder(log); + } + public static StatusRecorder chain(StatusRecorder...recorders) { + return new ChainingStatusRecorder(recorders); + } + } + + public static class Record { + private final Map<String,Object> fields = Maps.newLinkedHashMap(); + + public void putAll(Map<String,?> entries) { + fields.putAll(entries); + } + + public void putAll(String keyPrefix, Map<String,?> entries) { + for (Map.Entry<String,?> entry : entries.entrySet()) { + fields.put(keyPrefix+entry.getKey(), entry.getValue()); + } + } + + public void put(String key, Object val) { + fields.put(key, val); + } + + @Override + public String toString() { + return fields.toString(); + } + } + + public static class FileBasedStatusRecorder implements StatusRecorder { + private final File outFile; + + public FileBasedStatusRecorder(File outFile) { + this.outFile = outFile; + } + + @Override + public void record(Record record) throws IOException { + Files.append(record.fields.toString()+"\n", outFile, Charsets.UTF_8); + } + } + + public static class SysoutBasedStatusRecorder implements StatusRecorder { + public SysoutBasedStatusRecorder() { + } + + @Override + public void record(Record record) { + System.out.println(record.fields); + } + } + + public static class LogBasedStatusRecorder implements StatusRecorder { + private final Logger log; + + public LogBasedStatusRecorder(Logger log) { + this.log = log; + } + + @Override + public void record(Record record) { + log.info("{}", record.fields); + } + } + + public static class ChainingStatusRecorder implements StatusRecorder { + private final StatusRecorder[] recorders; + + public ChainingStatusRecorder(StatusRecorder... recorders) { + this.recorders = recorders; + } + + @Override + public void record(Record record) throws IOException { + for (StatusRecorder recorder : recorders) { + recorder.record(record); + } + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/bd44bb8f/usage/qa/src/test/java/brooklyn/qa/load/LoadTest.java ---------------------------------------------------------------------- diff --git a/usage/qa/src/test/java/brooklyn/qa/load/LoadTest.java b/usage/qa/src/test/java/brooklyn/qa/load/LoadTest.java deleted file mode 100644 index 16a0195..0000000 --- a/usage/qa/src/test/java/brooklyn/qa/load/LoadTest.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * 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 brooklyn.qa.load; - -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/brooklyn/qa/longevity/MonitorUtilsTest.java ---------------------------------------------------------------------- diff --git a/usage/qa/src/test/java/brooklyn/qa/longevity/MonitorUtilsTest.java b/usage/qa/src/test/java/brooklyn/qa/longevity/MonitorUtilsTest.java deleted file mode 100644 index 2f1e854..0000000 --- a/usage/qa/src/test/java/brooklyn/qa/longevity/MonitorUtilsTest.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * 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 brooklyn.qa.longevity; - -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 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/brooklyn/qa/longevity/webcluster/SinusoidalLoadGenerator.java ---------------------------------------------------------------------- diff --git a/usage/qa/src/test/java/brooklyn/qa/longevity/webcluster/SinusoidalLoadGenerator.java b/usage/qa/src/test/java/brooklyn/qa/longevity/webcluster/SinusoidalLoadGenerator.java deleted file mode 100644 index 392b976..0000000 --- a/usage/qa/src/test/java/brooklyn/qa/longevity/webcluster/SinusoidalLoadGenerator.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * 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 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/brooklyn/qa/longevity/webcluster/WebClusterApp.java ---------------------------------------------------------------------- diff --git a/usage/qa/src/test/java/brooklyn/qa/longevity/webcluster/WebClusterApp.java b/usage/qa/src/test/java/brooklyn/qa/longevity/webcluster/WebClusterApp.java deleted file mode 100644 index a223861..0000000 --- a/usage/qa/src/test/java/brooklyn/qa/longevity/webcluster/WebClusterApp.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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 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()); - } -}
