blob: 3c313b31e66c6b51ee9ac3cd1bd99c497df148cb [file] [log] [blame]
/*
* 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 org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.core.entity.Attributes;
import org.apache.brooklyn.core.location.access.BrooklynAccessUtils;
import org.apache.brooklyn.enricher.stock.Enrichers;
import org.apache.brooklyn.entity.webapp.jboss.JBoss7ServerImpl;
import org.apache.brooklyn.entity.webapp.jboss.JBoss7SshDriver;
import org.apache.brooklyn.feed.function.FunctionFeed;
import org.apache.brooklyn.feed.function.FunctionPollConfig;
import org.apache.brooklyn.feed.http.HttpFeed;
import org.apache.brooklyn.feed.http.HttpPollConfig;
import org.apache.brooklyn.feed.http.HttpValueFunctions;
import org.apache.brooklyn.location.ssh.SshMachineLocation;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.guava.Functionals;
import org.apache.brooklyn.util.os.Os;
import com.google.common.net.HostAndPort;
/**
* For simulating various aspects of the JBoss 7 app-server entity.
*
* The use-case for this is that the desired configuration is not always available for testing.
* For example, there may be insufficient resources to run 100s of JBoss app-servers, or one
* may be experimenting with possible configurations such as use of an external monitoring tool
* that is not yet available.
*
* It is then possible to simulate aspects of the behaviour, for performance and load testing purposes.
*
* There is configuration for:
* <ul>
* <li>{@code simulateEntity}
* <ul>
* <li>if true, no underlying entity will be started. Instead a sleep 100000 job will be run and monitored.
* <li>if false, the underlying entity (i.e. a JBoss app-server) will be started as normal.
* </ul>
* <li>{@code simulateExternalMonitoring}
* <ul>
* <li>if true, disables the default monitoring mechanism. Instead, a function will periodically execute
* to set the entity's sensors (as though the values had been obtained from the external monitoring tool).
* <li>if false, then:
* <ul>
* <li>If {@code simulateEntity==true} it will execute comparable commands (e.g. execute a command of the same
* size over ssh or do a comparable number of http GET requests).
* <li>If {@code simulateEntity==false} then normal monitoring will be done.
* </ul>
* </ul>
* <li>{@code skipSshOnStart}
* <ul>
* <li>If true (and if {@code simulateEntity==true}), then no ssh commands will be executed at deploy-time.
* This is useful for speeding up load testing, to get to the desired number of entities.
* Should not be set to {@code true} if {@code simulateEntity==false}.
* <li>If false, the ssh commands will be executed (based on the value of {@code simulateEntity}.
* </ul>
* </ul>
*/
public class SimulatedJBoss7ServerImpl extends JBoss7ServerImpl {
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 SimulatedJBoss7SshDriver.class;
}
@Override
protected void connectSensors() {
boolean simulateEntity = getConfig(SIMULATE_ENTITY);
boolean simulateExternalMonitoring = getConfig(SIMULATE_EXTERNAL_MONITORING);
if (!simulateEntity && !simulateExternalMonitoring) {
super.connectSensors();
return;
}
HostAndPort hp = BrooklynAccessUtils.getBrooklynAccessibleAddress(this,
getAttribute(MANAGEMENT_HTTP_PORT) + getConfig(PORT_INCREMENT));
String managementUri = String.format("http://%s:%s/management/subsystem/web/connector/http/read-resource",
hp.getHostText(), hp.getPort());
sensors().set(MANAGEMENT_URL, managementUri);
if (simulateExternalMonitoring) {
// TODO What would set this normally, if not doing connectServiceUpIsRunning?
sensors().set(SERVICE_PROCESS_IS_RUNNING, true);
} else {
// if simulating entity, then simulate work of periodic HTTP request; TODO but not parsing JSON response
String uriToPoll = (simulateEntity) ? "http://localhost:8081" : managementUri;
httpFeed = HttpFeed.builder()
.entity(this)
.period(200)
.baseUri(uriToPoll)
.credentials(getConfig(MANAGEMENT_USER), getConfig(MANAGEMENT_PASSWORD))
.poll(new HttpPollConfig<Integer>(MANAGEMENT_STATUS)
.onSuccess(HttpValueFunctions.responseCode()))
.build();
// Polls over ssh for whether process is running
connectServiceUpIsRunning();
}
functionFeed = FunctionFeed.builder()
.entity(this)
.period(5000)
.poll(new FunctionPollConfig<Boolean,Boolean>(MANAGEMENT_URL_UP)
.callable(new Callable<Boolean>() {
private int counter = 0;
@Override
public Boolean call() {
sensors().set(REQUEST_COUNT, (counter++ % 100));
sensors().set(ERROR_COUNT, (counter++ % 100));
sensors().set(TOTAL_PROCESSING_TIME, (counter++ % 100));
sensors().set(MAX_PROCESSING_TIME, (counter++ % 100));
sensors().set(BYTES_RECEIVED, (long) (counter++ % 100));
sensors().set(BYTES_SENT, (long) (counter++ % 100));
return true;
}}))
.build();
enrichers().add(Enrichers.builder().updatingMap(Attributes.SERVICE_NOT_UP_INDICATORS)
.from(MANAGEMENT_URL_UP)
.computing(Functionals.ifNotEquals(true).value("Management URL not reachable") )
.build());
}
@Override
protected void disconnectSensors() {
super.disconnectSensors();
if (httpFeed != null) httpFeed.stop();
if (functionFeed != null) functionFeed.stop();
}
public static class SimulatedJBoss7SshDriver extends JBoss7SshDriver {
public SimulatedJBoss7SshDriver(SimulatedJBoss7ServerImpl entity, SshMachineLocation machine) {
super(entity, machine);
}
@Override
public boolean isRunning() {
if (entity.getConfig(SKIP_SSH_ON_START)) {
return true;
} else {
return super.isRunning();
}
}
@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;
}
// We wait for evidence of JBoss running because, using SshCliTool,
// we saw the ssh session return before the JBoss process was fully running
// so the process failed to start.
String pidFile = Os.mergePathsUnix(getRunDir(), PID_FILENAME);
if (entity.getConfig(SKIP_SSH_ON_START)) {
// minimal ssh, so that isRunning will subsequently work
newScript(MutableMap.of("usePidFile", pidFile), LAUNCHING)
.body.append(
format("nohup sleep 100000 > %s/console 2>&1 < /dev/null &", getRunDir()))
.execute();
} else {
newScript(MutableMap.of(USE_PID_FILE, false), LAUNCHING)
.body.append(
"export LAUNCH_JBOSS_IN_BACKGROUND=true",
format("export JBOSS_HOME=%s", getExpandedInstallDir()),
format("export JBOSS_PIDFILE=%s/%s", getRunDir(), PID_FILENAME),
format("echo skipping exec of %s/bin/%s.sh ", getExpandedInstallDir(), SERVER_TYPE) +
format("--server-config %s ", CONFIG_FILE) +
format("-Djboss.server.base.dir=%s/%s ", getRunDir(), SERVER_TYPE) +
format("\"-Djboss.server.base.url=file://%s/%s\" ", getRunDir(), SERVER_TYPE) +
"-Djava.net.preferIPv4Stack=true " +
"-Djava.net.preferIPv6Addresses=false " +
format(" >> %s/console 2>&1 </dev/null &", getRunDir()),
format("nohup sleep 100000 > %s/console 2>&1 < /dev/null &", getRunDir()),
format("echo $! > "+pidFile),
format("echo starting > %s/console", getRunDir()),
"for i in {1..10}\n" +
"do\n" +
" grep -i 'starting' "+getRunDir()+"/console && exit\n" +
" sleep 1\n" +
"done\n" +
"echo \"Couldn't determine if process is running (console output does not contain 'starting'); continuing but may subsequently fail\""
)
.execute();
}
}
}
}