| /* |
| * 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(); |
| } |
| } |
| } |
| } |