http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/WebAppServiceMetrics.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/WebAppServiceMetrics.java b/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/WebAppServiceMetrics.java new file mode 100644 index 0000000..7152c78 --- /dev/null +++ b/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/WebAppServiceMetrics.java @@ -0,0 +1,77 @@ +/* + * 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.entity.webapp; + +import brooklyn.config.render.RendererHints; +import brooklyn.event.AttributeSensor; +import brooklyn.event.basic.BasicAttributeSensor; +import brooklyn.event.basic.Sensors; +import brooklyn.util.math.MathFunctions; +import brooklyn.util.text.ByteSizeStrings; +import brooklyn.util.time.Duration; + +public interface WebAppServiceMetrics { + + public static final brooklyn.event.basic.BasicAttributeSensor<Integer> ERROR_COUNT = + new brooklyn.event.basic.BasicAttributeSensor<Integer>(Integer.class, "webapp.reqs.errors", "Request errors"); + public static final AttributeSensor<Integer> TOTAL_PROCESSING_TIME = Sensors.newIntegerSensor( + "webapp.reqs.processingTime.total", "Total processing time, reported by webserver (millis)"); + public static final AttributeSensor<Integer> MAX_PROCESSING_TIME = + Sensors.newIntegerSensor("webapp.reqs.processingTime.max", "Max processing time for any single request, reported by webserver (millis)"); + + /** the fraction of time represented by the most recent delta to TOTAL_PROCESSING_TIME, ie 0.4 if 800 millis were accumulated in last 2s; + * easily configured with {@link WebAppServiceMethods#connectWebAppServerPolicies(brooklyn.entity.basic.EntityLocal, brooklyn.util.time.Duration)} */ + public static final AttributeSensor<Double> PROCESSING_TIME_FRACTION_LAST = + Sensors.newDoubleSensor("webapp.reqs.processingTime.fraction.last", "Fraction of time spent processing, reported by webserver (percentage, last datapoint)"); + public static final AttributeSensor<Double> PROCESSING_TIME_FRACTION_IN_WINDOW = + Sensors.newDoubleSensor("webapp.reqs.processingTime.fraction.windowed", "Fraction of time spent processing, reported by webserver (percentage, over time window)"); + + public static final AttributeSensor<Long> BYTES_RECEIVED = + new BasicAttributeSensor<Long>(Long.class, "webapp.reqs.bytes.received", "Total bytes received by the webserver"); + public static final AttributeSensor<Long> BYTES_SENT = + new BasicAttributeSensor<Long>(Long.class, "webapp.reqs.bytes.sent", "Total bytes sent by the webserver"); + + /** req/second computed from the delta of the last request count and an associated timestamp */ + public static final AttributeSensor<Double> REQUESTS_PER_SECOND_LAST = + Sensors.newDoubleSensor("webapp.reqs.perSec.last", "Reqs/sec (last datapoint)"); + + /** rolled-up req/second for a window, + * easily configured with {@link WebAppServiceMethods#connectWebAppServerPolicies(brooklyn.entity.basic.EntityLocal, brooklyn.util.time.Duration)} */ + public static final AttributeSensor<Double> REQUESTS_PER_SECOND_IN_WINDOW = + Sensors.newDoubleSensor("webapp.reqs.perSec.windowed", "Reqs/sec (over time window)"); + + public static final AttributeSensor<Integer> REQUEST_COUNT = Initializer.REQUEST_COUNT; + + // this class is added because the above need static initialization which unfortunately can't be added to an interface. + // (but should only be referenced after the other fields have been set) + static class Initializer { + public static final AttributeSensor<Integer> REQUEST_COUNT = + Sensors.newIntegerSensor("webapp.reqs.total", "Request count"); + + static { + RendererHints.register(WebAppServiceConstants.TOTAL_PROCESSING_TIME, RendererHints.displayValue(Duration.millisToStringRounded())); + RendererHints.register(WebAppServiceConstants.MAX_PROCESSING_TIME, RendererHints.displayValue(Duration.millisToStringRounded())); + RendererHints.register(WebAppServiceConstants.BYTES_RECEIVED, RendererHints.displayValue(ByteSizeStrings.metric())); + RendererHints.register(WebAppServiceConstants.BYTES_SENT, RendererHints.displayValue(ByteSizeStrings.metric())); + RendererHints.register(WebAppServiceConstants.PROCESSING_TIME_FRACTION_LAST, RendererHints.displayValue(MathFunctions.percent(2))); + RendererHints.register(WebAppServiceConstants.PROCESSING_TIME_FRACTION_IN_WINDOW, RendererHints.displayValue(MathFunctions.percent(2))); + } + } + +}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss6Driver.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss6Driver.java b/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss6Driver.java new file mode 100644 index 0000000..d8ba9d7 --- /dev/null +++ b/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss6Driver.java @@ -0,0 +1,24 @@ +/* + * 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.entity.webapp.jboss; + +import org.apache.brooklyn.entity.webapp.JavaWebAppDriver; + +public interface JBoss6Driver extends JavaWebAppDriver { +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss6Server.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss6Server.java b/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss6Server.java new file mode 100644 index 0000000..5ce6ded --- /dev/null +++ b/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss6Server.java @@ -0,0 +1,63 @@ +/* + * 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.entity.webapp.jboss; + +import org.apache.brooklyn.catalog.Catalog; +import org.apache.brooklyn.entity.webapp.JavaWebAppSoftwareProcess; + +import brooklyn.config.ConfigKey; +import brooklyn.entity.basic.ConfigKeys; +import brooklyn.entity.basic.SoftwareProcess; +import brooklyn.entity.java.UsesJmx; +import brooklyn.entity.proxying.ImplementedBy; +import brooklyn.event.basic.BasicAttributeSensorAndConfigKey; +import brooklyn.util.flags.SetFromFlag; + +@Catalog(name="JBoss Application Server 6", description="AS6: an open source Java application server from JBoss", iconUrl="classpath:///jboss-logo.png") +@ImplementedBy(JBoss6ServerImpl.class) +public interface JBoss6Server extends JavaWebAppSoftwareProcess, UsesJmx { + + // TODO Instead of using portIncrement, would prefer to use http_port as "8080+" etc. + // On localhost, if an existing jboss6 is running and consuming the required port(s), + // then we don't spot that and don't claim a different port. + // Things then fail silently! + + @SetFromFlag("version") + ConfigKey<String> SUGGESTED_VERSION = + ConfigKeys.newConfigKeyWithDefault(SoftwareProcess.SUGGESTED_VERSION, "6.0.0.Final"); + + @SetFromFlag("downloadUrl") + BasicAttributeSensorAndConfigKey<String> DOWNLOAD_URL = new BasicAttributeSensorAndConfigKey<String>( + SoftwareProcess.DOWNLOAD_URL, "http://downloads.sourceforge.net/project/jboss/JBoss/JBoss-${version}/jboss-as-distribution-${version}.zip?" + + "r=http%3A%2F%2Fsourceforge.net%2Fprojects%2Fjboss%2Ffiles%2FJBoss%2F${version}%2F&ts=1307104229&use_mirror=kent"); + + @SetFromFlag("bindAddress") + BasicAttributeSensorAndConfigKey<String> BIND_ADDRESS = + new BasicAttributeSensorAndConfigKey<String>(String.class, "jboss6.bind.address", + "Address of interface JBoss should listen on, defaulting 0.0.0.0 (but could set e.g. to attributeWhenReady(HOSTNAME)", + "0.0.0.0"); + + @SetFromFlag("portIncrement") + BasicAttributeSensorAndConfigKey<Integer> PORT_INCREMENT = + new BasicAttributeSensorAndConfigKey<Integer>(Integer.class, "jboss6.portincrement", "Increment to be used for all jboss ports", 0); + + @SetFromFlag("clusterName") + BasicAttributeSensorAndConfigKey<String> CLUSTER_NAME = + new BasicAttributeSensorAndConfigKey<String>(String.class, "jboss6.clusterName", "Identifier used to group JBoss instances", ""); +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss6ServerImpl.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss6ServerImpl.java b/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss6ServerImpl.java new file mode 100644 index 0000000..59d35dc --- /dev/null +++ b/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss6ServerImpl.java @@ -0,0 +1,103 @@ +/* + * 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.entity.webapp.jboss; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.apache.brooklyn.entity.webapp.JavaWebAppSoftwareProcessImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.enricher.Enrichers; +import brooklyn.entity.Entity; +import brooklyn.entity.basic.Attributes; +import brooklyn.event.feed.jmx.JmxAttributePollConfig; +import brooklyn.event.feed.jmx.JmxFeed; + +import com.google.common.base.Functions; + +public class JBoss6ServerImpl extends JavaWebAppSoftwareProcessImpl implements JBoss6Server { + + public static final Logger log = LoggerFactory.getLogger(JBoss6ServerImpl.class); + + private volatile JmxFeed jmxFeed; + + public JBoss6ServerImpl() { + this(new LinkedHashMap(), null); + } + + public JBoss6ServerImpl(Entity parent) { + this(new LinkedHashMap(), parent); + } + + public JBoss6ServerImpl(Map flags){ + this(flags, null); + } + + public JBoss6ServerImpl(Map flags, Entity parent) { + super(flags, parent); + } + + @Override + public void connectSensors() { + super.connectSensors(); + + String requestProcessorMbeanName = "jboss.web:type=GlobalRequestProcessor,name=http-*"; + String serverMbeanName = "jboss.system:type=Server"; + boolean retrieveUsageMetrics = getConfig(RETRIEVE_USAGE_METRICS); + + jmxFeed = JmxFeed.builder() + .entity(this) + .period(500, TimeUnit.MILLISECONDS) + .pollAttribute(new JmxAttributePollConfig<Boolean>(SERVICE_UP) + // TODO instead of setting SERVICE_UP directly, want to use equivalent of + // addEnricher(Enrichers.builder().updatingMap(Attributes.SERVICE_NOT_UP_INDICATORS).key("serverMBean")... + // but not supported in feed? + .objectName(serverMbeanName) + .attributeName("Started") + .onException(Functions.constant(false)) + .suppressDuplicates(true)) + .pollAttribute(new JmxAttributePollConfig<Integer>(ERROR_COUNT) + .objectName(requestProcessorMbeanName) + .attributeName("errorCount") + .enabled(retrieveUsageMetrics)) + .pollAttribute(new JmxAttributePollConfig<Integer>(REQUEST_COUNT) + .objectName(requestProcessorMbeanName) + .attributeName("requestCount") + .enabled(retrieveUsageMetrics)) + .pollAttribute(new JmxAttributePollConfig<Integer>(TOTAL_PROCESSING_TIME) + .objectName(requestProcessorMbeanName) + .attributeName("processingTime") + .enabled(retrieveUsageMetrics)) + .build(); + } + + @Override + public void disconnectSensors() { + super.disconnectSensors(); + if (jmxFeed != null) jmxFeed.stop(); + } + + @Override + public Class<JBoss6Driver> getDriverInterface() { + return JBoss6Driver.class; + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss6SshDriver.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss6SshDriver.java b/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss6SshDriver.java new file mode 100644 index 0000000..10caeab --- /dev/null +++ b/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss6SshDriver.java @@ -0,0 +1,244 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.entity.webapp.jboss; + +import static brooklyn.util.text.Strings.isEmpty; +import static java.lang.String.format; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.brooklyn.entity.webapp.JavaWebAppSshDriver; + +import brooklyn.entity.basic.Attributes; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.java.UsesJmx; +import brooklyn.entity.java.UsesJmx.JmxAgentModes; +import brooklyn.location.basic.SshMachineLocation; +import brooklyn.util.collections.MutableList; +import brooklyn.util.collections.MutableMap; +import brooklyn.util.net.Networking; +import brooklyn.util.os.Os; +import brooklyn.util.ssh.BashCommands; + +public class JBoss6SshDriver extends JavaWebAppSshDriver implements JBoss6Driver { + + public static final String SERVER_TYPE = "standard"; + public static final int DEFAULT_HTTP_PORT = 8080; + private static final String PORT_GROUP_NAME = "ports-brooklyn"; + + public JBoss6SshDriver(JBoss6ServerImpl entity, SshMachineLocation machine) { + super(entity, machine); + } + + @Override + public JBoss6ServerImpl getEntity() { + return (JBoss6ServerImpl) super.getEntity(); + } + + protected String getLogFileLocation() { + return Os.mergePathsUnix(getRunDir(), "server", SERVER_TYPE, "log/server.log"); + } + + protected String getDeploySubdir() { + return Os.mergePathsUnix("server", SERVER_TYPE, "deploy"); + } // FIXME what is this in as6? + + protected String getBindAddress() { + return entity.getAttribute(JBoss6Server.BIND_ADDRESS); + } + + protected Integer getPortIncrement() { + return entity.getAttribute(JBoss6Server.PORT_INCREMENT); + } + + protected String getClusterName() { + return entity.getAttribute(JBoss6Server.CLUSTER_NAME); + } + + // FIXME Should this pattern be used elsewhere? How? + @Override + public String getExpandedInstallDir() { + // Ensure never returns null, so if stop called even if install/start was not then don't throw exception. + String result = super.getExpandedInstallDir(); + return (result != null) ? result : getInstallDir()+"/" + "jboss-"+getVersion(); + } + + @Override + public void postLaunch() { + entity.setAttribute(JBoss6Server.HTTP_PORT, DEFAULT_HTTP_PORT + getPortIncrement()); + super.postLaunch(); + } + + @Override + public void preInstall() { + resolver = Entities.newDownloader(this); + setExpandedInstallDir(Os.mergePaths(getInstallDir(), resolver.getUnpackedDirectoryName(format("jboss-%s", getVersion())))); + } + + @Override + public void install() { + List<String> urls = resolver.getTargets(); + String saveAs = resolver.getFilename(); + + // Note the -o option to unzip, to overwrite existing files without warning. + // The JBoss zip file contains lgpl.txt (at least) twice and the prompt to + // overwrite interrupts the installer. + + List<String> commands = new LinkedList<String>(); + commands.addAll(BashCommands.commandsToDownloadUrlsAs(urls, saveAs)); + commands.add(BashCommands.INSTALL_UNZIP); + commands.add(format("unzip -o %s",saveAs)); + + newScript(INSTALLING) + .body.append(commands) + .execute(); + } + + @Override + public void customize() { + newScript(CUSTOMIZING) + .body.append( + format("mkdir -p %s/server", getRunDir()), + format("cd %s/server", getRunDir()), + format("cp -r %s/server/%s %s", getExpandedInstallDir(), SERVER_TYPE, SERVER_TYPE), + format("cd %s/conf/bindingservice.beans/META-INF/",SERVER_TYPE), + "BJB=\"bindings-jboss-beans.xml\"", + format("sed -i.bk 's/ports-03/%s/' $BJB",PORT_GROUP_NAME), + format("sed -i.bk 's/<parameter>300<\\/parameter>/<parameter>%s<\\/parameter>/' $BJB",getPortIncrement()) + ) + .execute(); + + getEntity().deployInitialWars(); + } + + @Override + public void launch() { + Map<String,Integer> ports = new HashMap<String, Integer>(); + ports.put("httpPort",getHttpPort()); + ports.put("jmxPort",getJmxPort()); + + Networking.checkPortsValid(ports); + + String clusterArg = isEmpty(getClusterName()) ? "":"-g "+getClusterName(); + // run.sh must be backgrounded otherwise the script will never return. + + // Don't automatically create pid; instead set JBOSS_PIDFILE to create the pid file we need + // We wait for evidence of tomcat running because, using + // brooklyn.ssh.config.tool.class=brooklyn.util.internal.ssh.cli.SshCliTool, + // we saw the ssh session return before the tomcat process was fully running + // so the process failed to start. + newScript(MutableMap.of(USE_PID_FILE, false), LAUNCHING) + .body.append( + format("export JBOSS_CLASSPATH=%s/lib/jboss-logmanager.jar",getExpandedInstallDir()), + format("export JBOSS_PIDFILE=%s/%s", getRunDir(), PID_FILENAME), + format("%s/bin/run.sh -Djboss.service.binding.set=%s -Djboss.server.base.dir=$RUN_DIR/server ",getExpandedInstallDir(),PORT_GROUP_NAME) + + format("-Djboss.server.base.url=file://$RUN_DIR/server -Djboss.messaging.ServerPeerID=%s ",entity.getId())+ + format("-Djboss.boot.server.log.dir=%s/server/%s/log ",getRunDir(),SERVER_TYPE) + + format("-b %s %s -c %s ", getBindAddress(), clusterArg,SERVER_TYPE) + + ">>$RUN_DIR/console 2>&1 </dev/null &", + "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(); + } + + @Override + public boolean isRunning() { + JmxAgentModes jmxMode = entity.getConfig(UsesJmx.JMX_AGENT_MODE); + if (jmxMode == JmxAgentModes.JMX_RMI_CUSTOM_AGENT) { + String host = entity.getAttribute(Attributes.HOSTNAME); + Integer port = entity.getAttribute(UsesJmx.RMI_REGISTRY_PORT); + + List<String> checkRunningScript = new LinkedList<String>(); + checkRunningScript.add( + format("%s/bin/twiddle.sh --host %s --port %s get \"jboss.system:type=Server\" Started | grep true || exit 1", + getExpandedInstallDir(), host, port)); + + //have to override the CLI/JMX options + + Map<String, Object> flags = new LinkedHashMap<String, Object>(); + flags.put("env", new LinkedHashMap<String, String>()); + + int result = execute(flags, checkRunningScript, "checkRunning " + entity + " on " + getMachine()); + if (result == 0) return true; + if (result == 1) return false; + throw new IllegalStateException(format("%s running check gave result code %s",getEntity(),result)); + } else { + return newScript(MutableMap.of(USE_PID_FILE, true), CHECK_RUNNING).execute() == 0; + } + } + + @Override + public void stop() { + JmxAgentModes jmxMode = entity.getConfig(UsesJmx.JMX_AGENT_MODE); + if (jmxMode == JmxAgentModes.JMX_RMI_CUSTOM_AGENT) { + String host = entity.getAttribute(Attributes.HOSTNAME); + Integer port = entity.getAttribute(UsesJmx.RMI_REGISTRY_PORT); + List<String> shutdownScript = new LinkedList<String>(); + shutdownScript.add(format("%s/bin/shutdown.sh --host %s --port %s -S", getExpandedInstallDir(), host, port)); + + //again, messy copy of parent; but new driver scheme could allow script-helper to customise parameters + log.debug("invoking shutdown script for {}: {}", entity, shutdownScript); + Map<String, Object> flags = new LinkedHashMap<String, Object>(); + flags.put("env", new LinkedHashMap<String, String>()); + int result = execute(flags, shutdownScript, "shutdown " + entity + " on " + getMachine()); + if (result != 0) log.warn("non-zero result code terminating {}: {}", entity, result); + log.debug("done invoking shutdown script for {}", entity); + } else { + newScript(MutableMap.of(USE_PID_FILE, true), STOPPING).execute(); + } + } + + @Override + protected List<String> getCustomJavaConfigOptions() { + return MutableList.<String>builder() + .addAll(super.getCustomJavaConfigOptions()) + .add("-Xms200m") + .add("-Xmx800m") + .add("-XX:MaxPermSize=400m") + .build(); + } + + @Override + public Map<String, String> getShellEnvironment() { + return MutableMap.<String, String>builder() + .putAll(super.getShellEnvironment()) + .put("LAUNCH_JBOSS_IN_BACKGROUND", "1") + .put("RUN", getRunDir()) + .build(); + } + + @Override + protected Map getCustomJavaSystemProperties() { + return MutableMap.<String, String>builder() + .put("jboss.platform.mbeanserver", null) + .put("javax.management.builder.initial", "org.jboss.system.server.jmx.MBeanServerBuilderImpl") + .put("java.util.logging.manager", "org.jboss.logmanager.LogManager") + .put("org.jboss.logging.Logger.pluginClass", "org.jboss.logging.logmanager.LoggerPluginImpl") + .build(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss7Driver.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss7Driver.java b/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss7Driver.java new file mode 100644 index 0000000..ad5e101 --- /dev/null +++ b/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss7Driver.java @@ -0,0 +1,30 @@ +/* + * 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.entity.webapp.jboss; + +import org.apache.brooklyn.entity.webapp.JavaWebAppDriver; + +public interface JBoss7Driver extends JavaWebAppDriver{ + + /** + * The path to the keystore file on the AS7 server machine. + * Result is undefined if SSL is not enabled/configured. + */ + public String getSslKeystoreFile(); +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss7Server.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss7Server.java b/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss7Server.java new file mode 100644 index 0000000..090cd6c --- /dev/null +++ b/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss7Server.java @@ -0,0 +1,105 @@ +/* + * 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.entity.webapp.jboss; + +import org.apache.brooklyn.catalog.Catalog; +import org.apache.brooklyn.entity.webapp.JavaWebAppSoftwareProcess; + +import brooklyn.config.ConfigKey; +import brooklyn.entity.basic.ConfigKeys; +import brooklyn.entity.basic.SoftwareProcess; +import brooklyn.entity.proxying.ImplementedBy; +import brooklyn.entity.trait.HasShortName; +import brooklyn.event.AttributeSensor; +import brooklyn.event.basic.BasicAttributeSensorAndConfigKey; +import brooklyn.event.basic.BasicAttributeSensorAndConfigKey.StringAttributeSensorAndConfigKey; +import brooklyn.event.basic.PortAttributeSensorAndConfigKey; +import brooklyn.event.basic.Sensors; +import brooklyn.util.flags.SetFromFlag; +import brooklyn.util.javalang.JavaClassNames; + +@Catalog(name="JBoss Application Server 7", description="AS7: an open source Java application server from JBoss", iconUrl="classpath:///jboss-logo.png") +@ImplementedBy(JBoss7ServerImpl.class) +public interface JBoss7Server extends JavaWebAppSoftwareProcess, HasShortName { + + @SetFromFlag("version") + ConfigKey<String> SUGGESTED_VERSION = + ConfigKeys.newConfigKeyWithDefault(SoftwareProcess.SUGGESTED_VERSION, "7.1.1.Final"); + // note: 7.1.2.Final fixes many bugs but is not available for download, + // see https://community.jboss.org/thread/197780 + // 7.2.0.Final should be out during Q3 2012 + + @SetFromFlag("downloadUrl") + BasicAttributeSensorAndConfigKey<String> DOWNLOAD_URL = new StringAttributeSensorAndConfigKey( + SoftwareProcess.DOWNLOAD_URL, "http://download.jboss.org/jbossas/7.1/jboss-as-${version}/jboss-as-${version}.tar.gz"); + + @SetFromFlag("bindAddress") + BasicAttributeSensorAndConfigKey<String> BIND_ADDRESS = + new StringAttributeSensorAndConfigKey("jboss.bind.address", + "Address of interface JBoss should listen on, defaulting 0.0.0.0 (but could set e.g. to attributeWhenReady(HOSTNAME)", + "0.0.0.0"); + + @SetFromFlag("managementHttpPort") + PortAttributeSensorAndConfigKey MANAGEMENT_HTTP_PORT = + new PortAttributeSensorAndConfigKey("webapp.jboss.managementHttpPort", "Management port", "9990+"); + + @SetFromFlag("managementHttpsPort") + PortAttributeSensorAndConfigKey MANAGEMENT_HTTPS_PORT = + new PortAttributeSensorAndConfigKey("webapp.jboss.managementHttpsPort", "Management port", "9443+"); + + @SetFromFlag("managementNativePort") + PortAttributeSensorAndConfigKey MANAGEMENT_NATIVE_PORT = + new PortAttributeSensorAndConfigKey("webapp.jboss.managementNativePort", "Management native port", "10999+"); + + /** + * Port increments are the standard way to run multiple instances of AS7 on the same machine. + */ + @SetFromFlag("portIncrement") + ConfigKey<Integer> PORT_INCREMENT = + ConfigKeys.newConfigKey("webapp.jboss.portIncrement", "Port increment for all ports in config file", 0); + + @SetFromFlag("deploymentTimeout") + ConfigKey<Integer> DEPLOYMENT_TIMEOUT = + ConfigKeys.newConfigKey("webapp.jboss.deploymentTimeout", "Deployment timeout, in seconds", 600); + + ConfigKey<String> TEMPLATE_CONFIGURATION_URL = ConfigKeys.newConfigKey( + "webapp.jboss.templateConfigurationUrl", "Template file (in freemarker format) for the standalone.xml file", + JavaClassNames.resolveClasspathUrl(JBoss7Server.class, "jboss7-standalone.xml")); + + @SetFromFlag("managementUser") + ConfigKey<String> MANAGEMENT_USER = ConfigKeys.newConfigKey("webapp.jboss.managementUser", + "A user to be placed in the management realm. Brooklyn will use this user to poll sensors", + "brooklyn"); + + @SetFromFlag("managementPassword") + ConfigKey<String> MANAGEMENT_PASSWORD = + ConfigKeys.newStringConfigKey("webapp.jboss.managementPassword", "Password for MANAGEMENT_USER."); + + AttributeSensor<String> MANAGEMENT_URL = + Sensors.newStringSensor("webapp.jboss.managementUrl", "URL where management endpoint is available"); + + AttributeSensor<Integer> MANAGEMENT_STATUS = + Sensors.newIntegerSensor("webapp.jboss.managementStatus", "HTTP response code for the management server"); + + AttributeSensor<Boolean> MANAGEMENT_URL_UP = + Sensors.newBooleanSensor("webapp.jboss.managementUp", "Management server is responding with OK"); + + public static final AttributeSensor<String> PID_FILE = Sensors.newStringSensor( "jboss.pid.file", "PID file"); + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss7ServerImpl.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss7ServerImpl.java b/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss7ServerImpl.java new file mode 100644 index 0000000..5105f0f --- /dev/null +++ b/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss7ServerImpl.java @@ -0,0 +1,199 @@ +/* + * 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.entity.webapp.jboss; + +import java.util.Map; + +import org.apache.brooklyn.entity.webapp.JavaWebAppSoftwareProcessImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.config.render.RendererHints; +import brooklyn.enricher.Enrichers; +import brooklyn.entity.Entity; +import brooklyn.entity.basic.Attributes; +import brooklyn.event.feed.http.HttpFeed; +import brooklyn.event.feed.http.HttpPollConfig; +import brooklyn.event.feed.http.HttpValueFunctions; +import brooklyn.location.access.BrooklynAccessUtils; +import brooklyn.util.guava.Functionals; + +import com.google.common.base.Functions; +import com.google.common.collect.ImmutableMap; +import com.google.common.net.HostAndPort; + +public class JBoss7ServerImpl extends JavaWebAppSoftwareProcessImpl implements JBoss7Server { + + public static final Logger log = LoggerFactory.getLogger(JBoss7ServerImpl.class); + + private volatile HttpFeed httpFeed; + + public JBoss7ServerImpl(){ + super(); + } + + public JBoss7ServerImpl(@SuppressWarnings("rawtypes") Map flags){ + this(flags, null); + } + + public JBoss7ServerImpl(@SuppressWarnings("rawtypes") Map flags, Entity parent) { + super(flags, parent); + } + + @Override + public Class<?> getDriverInterface() { + return JBoss7Driver.class; + } + + @Override + public JBoss7Driver getDriver() { + return (JBoss7Driver) super.getDriver(); + } + + static { + RendererHints.register(MANAGEMENT_URL, RendererHints.namedActionWithUrl()); + } + + @Override + protected void connectSensors() { + super.connectSensors(); + + 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()); + setAttribute(MANAGEMENT_URL, managementUri); + log.debug("JBoss sensors for "+this+" reading from "+managementUri); + Map<String, String> includeRuntimeUriVars = ImmutableMap.of("include-runtime","true"); + boolean retrieveUsageMetrics = getConfig(RETRIEVE_USAGE_METRICS); + + httpFeed = HttpFeed.builder() + .entity(this) + .period(200) + .baseUri(managementUri) + .credentials(getConfig(MANAGEMENT_USER), getConfig(MANAGEMENT_PASSWORD)) + .poll(new HttpPollConfig<Integer>(MANAGEMENT_STATUS) + .onSuccess(HttpValueFunctions.responseCode()) + .suppressDuplicates(true)) + .poll(new HttpPollConfig<Boolean>(MANAGEMENT_URL_UP) + .onSuccess(HttpValueFunctions.responseCodeEquals(200)) + .onFailureOrException(Functions.constant(false)) + .suppressDuplicates(true)) + .poll(new HttpPollConfig<Integer>(REQUEST_COUNT) + .vars(includeRuntimeUriVars) + .onSuccess(HttpValueFunctions.jsonContents("requestCount", Integer.class)) + .enabled(retrieveUsageMetrics)) + .poll(new HttpPollConfig<Integer>(ERROR_COUNT) + .vars(includeRuntimeUriVars) + .onSuccess(HttpValueFunctions.jsonContents("errorCount", Integer.class)) + .enabled(retrieveUsageMetrics)) + .poll(new HttpPollConfig<Integer>(TOTAL_PROCESSING_TIME) + .vars(includeRuntimeUriVars) + .onSuccess(HttpValueFunctions.jsonContents("processingTime", Integer.class)) + .enabled(retrieveUsageMetrics)) + .poll(new HttpPollConfig<Integer>(MAX_PROCESSING_TIME) + .vars(includeRuntimeUriVars) + .onSuccess(HttpValueFunctions.jsonContents("maxTime", Integer.class)) + .enabled(retrieveUsageMetrics)) + .poll(new HttpPollConfig<Long>(BYTES_RECEIVED) + .vars(includeRuntimeUriVars) + // jboss seems to report 0 even if it has received lots of requests; dunno why. + .onSuccess(HttpValueFunctions.jsonContents("bytesReceived", Long.class)) + .enabled(retrieveUsageMetrics)) + .poll(new HttpPollConfig<Long>(BYTES_SENT) + .vars(includeRuntimeUriVars) + .onSuccess(HttpValueFunctions.jsonContents("bytesSent", Long.class)) + .enabled(retrieveUsageMetrics)) + .build(); + + connectServiceUp(); + } + + protected void connectServiceUp() { + addEnricher(Enrichers.builder().updatingMap(Attributes.SERVICE_NOT_UP_INDICATORS) + .from(MANAGEMENT_URL_UP) + .computing(Functionals.ifNotEquals(true).value("Management URL not reachable") ) + .suppressDuplicates(true) + .build()); + } + + protected void disconnectServiceUp() { + disconnectServiceUpIsRunning(); + } + + @Override + protected void disconnectSensors() { + super.disconnectSensors(); + + if (httpFeed != null) httpFeed.stop(); + disconnectServiceUp(); + } + + public int getManagementHttpsPort() { + return getAttribute(MANAGEMENT_HTTPS_PORT); + } + + public int getManagementHttpPort() { + return getAttribute(MANAGEMENT_HTTP_PORT); + } + + public int getManagementNativePort() { + return getAttribute(MANAGEMENT_NATIVE_PORT); + } + + public int getPortOffset() { + return getConfig(PORT_INCREMENT); + } + + public boolean isWelcomeRootEnabled() { + return false; + } + + public String getBindAddress() { + return getConfig(BIND_ADDRESS); + } + + public String getManagementBindAddress() { + return getConfig(BIND_ADDRESS); + } + + public String getUnsecureBindAddress() { + return getConfig(BIND_ADDRESS); + } + + // If empty-string, disables Management security (!) by excluding the security-realm attribute + public String getHttpManagementInterfaceSecurityRealm() { + return ""; + } + + public int getDeploymentTimeoutSecs() { + return getConfig(DEPLOYMENT_TIMEOUT); + } + + /** Path of the keystore file on the AS7 server */ + public String getHttpsSslKeystoreFile() { + return getDriver().getSslKeystoreFile(); + } + + @Override + public String getShortName() { + return "JBossAS7"; + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss7SshDriver.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss7SshDriver.java b/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss7SshDriver.java new file mode 100644 index 0000000..e9db6ba --- /dev/null +++ b/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jboss/JBoss7SshDriver.java @@ -0,0 +1,276 @@ +/* + * 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.entity.webapp.jboss; + +import static java.lang.String.format; + +import java.io.InputStream; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.brooklyn.entity.webapp.JavaWebAppSshDriver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.entity.basic.Entities; +import brooklyn.entity.basic.SoftwareProcess; +import brooklyn.location.basic.SshMachineLocation; +import brooklyn.util.collections.MutableList; +import brooklyn.util.collections.MutableMap; +import brooklyn.util.net.Networking; +import brooklyn.util.os.Os; +import brooklyn.util.ssh.BashCommands; +import brooklyn.util.text.Strings; + +import com.google.common.base.Charsets; +import com.google.common.base.Preconditions; +import com.google.common.hash.Hashing; +import com.google.common.io.BaseEncoding; + +public class JBoss7SshDriver extends JavaWebAppSshDriver implements JBoss7Driver { + + private static final Logger LOG = LoggerFactory.getLogger(JBoss7SshDriver.class); + + // TODO more configurability of config files, java memory, etc + + public static final String SERVER_TYPE = "standalone"; + public static final String CONFIG_FILE = "standalone-brooklyn.xml"; + public static final String KEYSTORE_FILE = ".keystore"; + public static final String MANAGEMENT_REALM = "ManagementRealm"; + + public JBoss7SshDriver(JBoss7ServerImpl entity, SshMachineLocation machine) { + super(entity, machine); + } + + @Override + public JBoss7ServerImpl getEntity() { + return (JBoss7ServerImpl) super.getEntity(); + } + + @Override + public String getSslKeystoreFile() { + return Os.mergePathsUnix(getRunDir(), SERVER_TYPE, "configuration", KEYSTORE_FILE); + } + + protected String getTemplateConfigurationUrl() { + return entity.getConfig(JBoss7Server.TEMPLATE_CONFIGURATION_URL); + } + + @Override + protected String getLogFileLocation() { + return Os.mergePathsUnix(getRunDir(), SERVER_TYPE, "log/server.log"); + } + + @Override + protected String getDeploySubdir() { + return Os.mergePathsUnix(SERVER_TYPE, "deployments"); + } + + private Integer getManagementHttpPort() { + return entity.getAttribute(JBoss7Server.MANAGEMENT_HTTP_PORT); + } + + private Integer getManagementHttpsPort() { + return entity.getAttribute(JBoss7Server.MANAGEMENT_HTTPS_PORT); + } + + private Integer getManagementNativePort() { + return entity.getAttribute(JBoss7Server.MANAGEMENT_NATIVE_PORT); + } + + private String getManagementUsername() { + return entity.getConfig(JBoss7Server.MANAGEMENT_USER); + } + + private String getManagementPassword() { + return entity.getConfig(JBoss7Server.MANAGEMENT_PASSWORD); + } + + @Override + public void preInstall() { + resolver = Entities.newDownloader(this); + setExpandedInstallDir(Os.mergePaths(getInstallDir(), resolver.getUnpackedDirectoryName(format("jboss-as-%s", getVersion())))); + } + + @Override + public void install() { + List<String> urls = resolver.getTargets(); + String saveAs = resolver.getFilename(); + + List<String> commands = new LinkedList<String>(); + commands.addAll(BashCommands.commandsToDownloadUrlsAs(urls, saveAs)); + commands.add(BashCommands.INSTALL_TAR); + commands.add("tar xzfv " + saveAs); + + newScript(INSTALLING) + // don't set vars yet -- it resolves dependencies (e.g. DB) which we don't want until we start + .environmentVariablesReset() + .body.append(commands) + .execute(); + } + + /** + * AS7 config notes and TODOs: + * We're using the http management interface on port managementPort + * We're not using any JMX. + * - AS 7 simply doesn't boot with Sun JMX enabled (https://issues.jboss.org/browse/JBAS-7427) + * - 7.1 onwards uses Remoting 3, which we haven't configured + * - We have generic support for jmxmp, which one could configure + * We're completely disabling security on the management interface. + * - In the future we probably want to use the as7/bin/add-user.sh script using config keys for user and password + * - Or we could create our own security realm and use that. + * We disable the root welcome page, since we can't deploy our own root otherwise + * We bind all interfaces to entity.hostname, rather than 127.0.0.1. + */ + @Override + public void customize() { + // Check that a password was set for the management user + Preconditions.checkState(Strings.isNonBlank(getManagementUsername()), "User for management realm required"); + String managementPassword = getManagementPassword(); + if (Strings.isBlank(managementPassword)) { + LOG.debug(this+" has no password specified for "+JBoss7Server.MANAGEMENT_PASSWORD.getName()+"; using a random string"); + entity.setConfig(JBoss7Server.MANAGEMENT_PASSWORD, Strings.makeRandomId(8)); + } + String hashedPassword = hashPassword(getManagementUsername(), getManagementPassword(), MANAGEMENT_REALM); + + // Check that ports are all configured + Map<String,Integer> ports = MutableMap.<String,Integer>builder() + .put("managementHttpPort", getManagementHttpPort()) + .put("managementHttpsPort", getManagementHttpsPort()) + .put("managementNativePort", getManagementNativePort()) + .build(); + if (isProtocolEnabled("HTTP")) { + ports.put("httpPort", getHttpPort()); + } + if (isProtocolEnabled("HTTPS")) { + ports.put("httpsPort", getHttpsPort()); + } + Networking.checkPortsValid(ports); + + // Check hostname is defined + String hostname = entity.getAttribute(SoftwareProcess.HOSTNAME); + Preconditions.checkNotNull(hostname, "AS 7 entity must set hostname otherwise server will only be visible on localhost"); + + // Copy the install files to the run-dir and add the management user + newScript(CUSTOMIZING) + // don't set vars yet -- it resolves dependencies (e.g. DB) which we don't want until we start + .environmentVariablesReset() + .body.append( + format("cp -r %s/%s . || exit $!", getExpandedInstallDir(), SERVER_TYPE), + format("echo -e '\n%s=%s' >> %s/%s/configuration/mgmt-users.properties", + getManagementUsername(), hashedPassword, getRunDir(), SERVER_TYPE) + ) + .execute(); + + // Copy the keystore across, if there is one + if (isProtocolEnabled("HTTPS")) { + String keystoreUrl = Preconditions.checkNotNull(getSslKeystoreUrl(), "keystore URL must be specified if using HTTPS for "+entity); + String destinationSslKeystoreFile = getSslKeystoreFile(); + InputStream keystoreStream = resource.getResourceFromUrl(keystoreUrl); + getMachine().copyTo(keystoreStream, destinationSslKeystoreFile); + } + + // Copy the configuration file across + String destinationConfigFile = Os.mergePathsUnix(getRunDir(), SERVER_TYPE, "configuration", CONFIG_FILE); + copyTemplate(getTemplateConfigurationUrl(), destinationConfigFile); + + // Copy the initial wars to the deploys directory + getEntity().deployInitialWars(); + } + + @Override + public void launch() { + entity.setAttribute(JBoss7Server.PID_FILE, Os.mergePathsUnix(getRunDir(), PID_FILENAME)); + + // We wait for evidence of JBoss running because, using + // brooklyn.ssh.config.tool.class=brooklyn.util.internal.ssh.cli.SshCliTool, + // we saw the ssh session return before the JBoss process was fully running + // so the process failed to start. + 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("%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()), + "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(); + } + + @Override + public boolean isRunning() { + return newScript(MutableMap.of(USE_PID_FILE, true), CHECK_RUNNING).execute() == 0; + } + + @Override + public void stop() { + newScript(MutableMap.of(USE_PID_FILE, true), STOPPING).environmentVariablesReset().execute(); + } + + @Override + public void kill() { + newScript(MutableMap.of(USE_PID_FILE, true), KILLING).execute(); + } + + @Override + protected List<String> getCustomJavaConfigOptions() { + return MutableList.<String>builder() + .addAll(super.getCustomJavaConfigOptions()) + .add("-Xms200m") + .add("-Xmx800m") + .add("-XX:MaxPermSize=400m") + .build(); + } + + /** + * Creates a hash of a username, password and security realm that is suitable for use + * with AS7 and Wildfire. + * <p/> + * Although AS7 has an <code>add-user.sh</code> script it is unsuitable for use in + * non-interactive modes. (See AS7-5061 for details.) Versions 7.1.2+ (EAP) accept + * a <code>--silent</code> flag. When this entity is updated past 7.1.1 we should + * probably use that instead. + * <p/> + * This method mirrors AS7 and Wildfire's method of hashing user's passwords. Refer + * to its class <code>UsernamePasswordHashUtil.generateHashedURP</code> for their + * implementation. + * + * @see <a href="https://issues.jboss.org/browse/AS7-5061">AS7-5061</a> + * @see <a href="https://github.com/jboss-remoting/jboss-sasl/blob/master/src/main/java/org/jboss/sasl/util/UsernamePasswordHashUtil.java"> + * UsernamePasswordHashUtil.generateHashedURP</a> + * @return <code>HEX(MD5(username ':' realm ':' password))</code> + */ + public static String hashPassword(String username, String password, String realm) { + String concat = username + ":" + realm + ":" + password; + byte[] hashed = Hashing.md5().hashString(concat, Charsets.UTF_8).asBytes(); + return BaseEncoding.base16().lowerCase().encode(hashed); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jetty/Jetty6Driver.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jetty/Jetty6Driver.java b/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jetty/Jetty6Driver.java new file mode 100644 index 0000000..4732cf5 --- /dev/null +++ b/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jetty/Jetty6Driver.java @@ -0,0 +1,24 @@ +/* + * 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.entity.webapp.jetty; + +import org.apache.brooklyn.entity.webapp.JavaWebAppDriver; + +public interface Jetty6Driver extends JavaWebAppDriver { +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jetty/Jetty6Server.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jetty/Jetty6Server.java b/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jetty/Jetty6Server.java new file mode 100644 index 0000000..5532661 --- /dev/null +++ b/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jetty/Jetty6Server.java @@ -0,0 +1,61 @@ +/* + * 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.entity.webapp.jetty; + +import org.apache.brooklyn.catalog.Catalog; +import org.apache.brooklyn.entity.webapp.JavaWebAppSoftwareProcess; + +import brooklyn.config.ConfigKey; +import brooklyn.entity.basic.ConfigKeys; +import brooklyn.entity.basic.SoftwareProcess; +import brooklyn.entity.java.UsesJmx; +import brooklyn.entity.proxying.ImplementedBy; +import brooklyn.entity.trait.HasShortName; +import brooklyn.event.AttributeSensor; +import brooklyn.event.basic.BasicAttributeSensorAndConfigKey; +import brooklyn.event.basic.Sensors; +import brooklyn.util.flags.SetFromFlag; +import brooklyn.util.time.Duration; + +/** + * An {@link brooklyn.entity.Entity} that represents a single Jetty instance. + */ +@Catalog(name="Jetty6 Server", description="Old version (v6 @ Mortbay) of the popular Jetty webapp container", iconUrl="classpath:///jetty-logo.png") +@ImplementedBy(Jetty6ServerImpl.class) +public interface Jetty6Server extends JavaWebAppSoftwareProcess, UsesJmx, HasShortName { + + @SetFromFlag("version") + ConfigKey<String> SUGGESTED_VERSION = ConfigKeys.newConfigKeyWithDefault(SoftwareProcess.SUGGESTED_VERSION, "6.1.26"); + + ConfigKey<Duration> START_TIMEOUT = ConfigKeys.newConfigKeyWithDefault(SoftwareProcess.START_TIMEOUT, Duration.FIVE_MINUTES); + + @SetFromFlag("configXmlTemplateUrl") + ConfigKey<String> CONFIG_XML_TEMPLATE_URL = ConfigKeys.newStringConfigKey("jetty.configXml.templateUrl", "Extra XML configuration file template URL if required"); + + @SetFromFlag("downloadUrl") + BasicAttributeSensorAndConfigKey<String> DOWNLOAD_URL = new BasicAttributeSensorAndConfigKey<String>( + SoftwareProcess.DOWNLOAD_URL, "http://get.jenv.mvnsearch.org/download/jetty/jetty-${version}.zip"); + + AttributeSensor<Integer> RESPONSES_4XX_COUNT = + Sensors.newIntegerSensor("webapp.responses.4xx", "Responses in the 400's"); + + AttributeSensor<Integer> RESPONSES_5XX_COUNT = + Sensors.newIntegerSensor("webapp.responses.5xx", "Responses in the 500's"); + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jetty/Jetty6ServerImpl.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jetty/Jetty6ServerImpl.java b/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jetty/Jetty6ServerImpl.java new file mode 100644 index 0000000..1e9e533 --- /dev/null +++ b/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jetty/Jetty6ServerImpl.java @@ -0,0 +1,141 @@ +/* + * 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.entity.webapp.jetty; + +import java.util.concurrent.TimeUnit; + +import org.apache.brooklyn.entity.webapp.JavaWebAppSoftwareProcessImpl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.enricher.Enrichers; +import brooklyn.entity.basic.Lifecycle; +import brooklyn.entity.java.JavaAppUtils; +import brooklyn.entity.java.UsesJmx; +import brooklyn.event.feed.jmx.JmxAttributePollConfig; +import brooklyn.event.feed.jmx.JmxFeed; + +import com.google.common.base.Functions; +import com.google.common.base.Predicates; + +/** + * An {@link brooklyn.entity.Entity} that represents a single Jetty instance. + */ +public class Jetty6ServerImpl extends JavaWebAppSoftwareProcessImpl implements Jetty6Server { + + private static final Logger log = LoggerFactory.getLogger(Jetty6ServerImpl.class); + + private volatile JmxFeed jmxFeedJetty, jmxFeedMx; + + @Override + public void connectSensors() { + super.connectSensors(); + + if (getDriver().isJmxEnabled()) { + String serverMbeanName = "org.mortbay.jetty:type=server,id=0"; + String statsMbeanName = "org.mortbay.jetty.handler:type=atomicstatisticshandler,id=0"; + + jmxFeedJetty = JmxFeed.builder() + .entity(this) + .period(500, TimeUnit.MILLISECONDS) + .pollAttribute(new JmxAttributePollConfig<Boolean>(SERVICE_UP) + .objectName(serverMbeanName) + .attributeName("running") + .onSuccess(Functions.forPredicate(Predicates.<Object>equalTo(true))) + .setOnFailureOrException(false)) + .pollAttribute(new JmxAttributePollConfig<Integer>(REQUEST_COUNT) + .objectName(statsMbeanName) + .attributeName("requests")) + .pollAttribute(new JmxAttributePollConfig<Integer>(RESPONSES_4XX_COUNT) + .objectName(statsMbeanName) + .attributeName("responses4xx")) + .pollAttribute(new JmxAttributePollConfig<Integer>(RESPONSES_5XX_COUNT) + .objectName(statsMbeanName) + .attributeName("responses5xx")) + .pollAttribute(new JmxAttributePollConfig<Integer>(TOTAL_PROCESSING_TIME) + .objectName(statsMbeanName) + .attributeName("requestTimeTotal")) + .pollAttribute(new JmxAttributePollConfig<Integer>(MAX_PROCESSING_TIME) + .objectName(statsMbeanName) + .attributeName("requestTimeMax")) + // NB: requestsActive may be useful + .build(); + + addEnricher(Enrichers.builder() + .combining(RESPONSES_4XX_COUNT, RESPONSES_5XX_COUNT) + .publishing(ERROR_COUNT) + .computingSum() + .build()); + + jmxFeedMx = JavaAppUtils.connectMXBeanSensors(this); + } else { + // if not using JMX + log.warn("Jetty running without JMX monitoring; limited visibility of service available"); + // TODO we could do simple things, like check that web server is accepting connections + } + } + + @Override + protected void disconnectSensors() { + if (jmxFeedJetty != null) jmxFeedJetty.stop(); + if (jmxFeedMx != null) jmxFeedMx.stop(); + super.disconnectSensors(); + } + + public Integer getJmxPort() { + if (((Jetty6Driver) getDriver()).isJmxEnabled()) { + return getAttribute(UsesJmx.JMX_PORT); + } else { + return Integer.valueOf(-1); + } + } + + @Override + public Class getDriverInterface() { + return Jetty6Driver.class; + } + + @Override + public String getShortName() { + return "Jetty"; + } + + @Override + public void deploy(String url, String targetName) { + super.deploy(url, targetName); + restartIfRunning(); + } + + @Override + public void undeploy(String targetName) { + super.undeploy(targetName); + restartIfRunning(); + } + + protected void restartIfRunning() { + // TODO for now we simply restart jetty to achieve "hot deployment"; should use the config mechanisms + Lifecycle serviceState = getAttribute(SERVICE_STATE_ACTUAL); + if (serviceState == Lifecycle.RUNNING) + restart(); + // may need a restart also if deploy effector is done in parallel to starting + // but note this routine is used by initialDeployWars so just being in starting state is not enough! + } + +} + http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jetty/Jetty6SshDriver.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jetty/Jetty6SshDriver.java b/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jetty/Jetty6SshDriver.java new file mode 100644 index 0000000..49f1f15 --- /dev/null +++ b/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/jetty/Jetty6SshDriver.java @@ -0,0 +1,174 @@ +/* + * 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.entity.webapp.jetty; + +import static java.lang.String.format; + +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.brooklyn.entity.webapp.JavaWebAppSshDriver; + +import brooklyn.entity.basic.Entities; +import brooklyn.location.basic.SshMachineLocation; +import brooklyn.util.collections.MutableList; +import brooklyn.util.collections.MutableMap; +import brooklyn.util.net.Networking; +import brooklyn.util.os.Os; +import brooklyn.util.ssh.BashCommands; +import brooklyn.util.text.Strings; + +public class Jetty6SshDriver extends JavaWebAppSshDriver implements Jetty6Driver { + + public Jetty6SshDriver(Jetty6ServerImpl entity, SshMachineLocation machine) { + super(entity, machine); + } + + @Override + protected String getLogFileLocation() { + // TODO no wildcard, also there is .requests.log + return Os.mergePathsUnix(getRunDir(), "logs", "*.stderrout.log"); + } + + @Override + protected String getDeploySubdir() { + return "webapps"; + } + + @Override + public void preInstall() { + resolver = Entities.newDownloader(this); + setExpandedInstallDir(Os.mergePaths(getInstallDir(), resolver.getUnpackedDirectoryName(format("jetty-%s", getVersion())))); + } + + @Override + public void install() { + List<String> urls = resolver.getTargets(); + String saveAs = resolver.getFilename(); + + List<String> commands = new LinkedList<String>(); + commands.addAll(BashCommands.commandsToDownloadUrlsAs(urls, saveAs)); + commands.add(BashCommands.INSTALL_ZIP); + commands.add("unzip "+saveAs); + + newScript(INSTALLING) + .body.append(commands) + .execute(); + } + + @Override + public void customize() { + newScript(CUSTOMIZING) + .body.append( + // create app-specific dirs + "mkdir logs contexts webapps", + // link to the binary directories; silly that we have to do this but jetty has only one notion of "jetty.home" + // (jetty.run is used only for writing the pid file, not for looking up webapps or even for logging) + format("for x in start.jar bin contrib modules lib extras; do ln -s %s/$x $x ; done", getExpandedInstallDir()), + // copy config files across + format("for x in etc resources; do cp -r %s/$x $x ; done", getExpandedInstallDir()) + ) + .execute(); + + + // Copy configuration XML files across + String destinationBrooklynConfig = Os.mergePathsUnix(getRunDir(), "etc/jetty-brooklyn.xml"); + copyTemplate("classpath://org/apache/brooklyn/entity/webapp/jetty/jetty-brooklyn.xml", destinationBrooklynConfig); + String customConfigTemplateUrl = getConfigXmlTemplateUrl(); + if (Strings.isNonEmpty(customConfigTemplateUrl)) { + String destinationConfigFile = Os.mergePathsUnix(getRunDir(), "etc/jetty-custom.xml"); + copyTemplate(customConfigTemplateUrl, destinationConfigFile); + } + + getEntity().deployInitialWars(); + } + + private String getConfigXmlTemplateUrl() { + return getEntity().getConfig(Jetty6Server.CONFIG_XML_TEMPLATE_URL); + } + + @Override + public void launch() { + Map ports = MutableMap.of("httpPort", getHttpPort(), "jmxPort", getJmxPort(), "rmiRegistryPort", getRmiRegistryPort()); + Networking.checkPortsValid(ports); + + newScript(MutableMap.of(USE_PID_FILE, false), LAUNCHING) + .body.append( + "./bin/jetty.sh start jetty-brooklyn.xml jetty.xml jetty-logging.xml jetty-stats.xml " + + (Strings.isEmpty(getConfigXmlTemplateUrl()) ? "" : "jetty-custom.xml ") + + ">> $RUN_DIR/console 2>&1 < /dev/null", + "for i in {1..10} ; do\n" + + " if [ -s "+getLogFileLocation()+" ]; then exit; fi\n" + + " sleep 1\n" + + "done", + "echo \"Couldn't determine if jetty-server is running (log file is still empty); continuing but may subsequently fail\"" + ) + .execute(); + log.debug("launched jetty"); + } + + @Override + public boolean isRunning() { + return newScript(MutableMap.of(USE_PID_FILE, "jetty.pid"), CHECK_RUNNING).execute() == 0; + } + + @Override + public void stop() { + newScript(MutableMap.of(USE_PID_FILE, false), STOPPING) + .body.append("./bin/jetty.sh stop") + .execute(); + } + + // not used, but an alternative to stop which might be useful + public void kill1() { + newScript(MutableMap.of(USE_PID_FILE, "jetty.pid"), STOPPING).execute(); + } + + public void kill9() { + newScript(MutableMap.of(USE_PID_FILE, "jetty.pid"), KILLING).execute(); + } + + @Override + public void kill() { + kill9(); + } + + @Override + protected List<String> getCustomJavaConfigOptions() { + return MutableList.<String>builder() + .addAll(super.getCustomJavaConfigOptions()) + .add("-Xms200m") + .add("-Xmx800m") + .add("-XX:MaxPermSize=400m") + .build(); + } + + @Override + public Map<String, String> getShellEnvironment() { + return MutableMap.<String, String>builder() + .putAll(super.getShellEnvironment()) + .put("JETTY_RUN", getRunDir()) + .put("JETTY_HOME", getRunDir()) + .put("JETTY_LOGS", Os.mergePathsUnix(getRunDir(), "logs")) + .put("JETTY_PORT", getHttpPort().toString()) + .renameKey("JAVA_OPTS", "JAVA_OPTIONS") + .build(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/nodejs/NodeJsWebAppDriver.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/nodejs/NodeJsWebAppDriver.java b/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/nodejs/NodeJsWebAppDriver.java new file mode 100644 index 0000000..167f843 --- /dev/null +++ b/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/nodejs/NodeJsWebAppDriver.java @@ -0,0 +1,29 @@ +/* + * 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.entity.webapp.nodejs; + +import brooklyn.entity.basic.SoftwareProcessDriver; + +public interface NodeJsWebAppDriver extends SoftwareProcessDriver { + + Integer getHttpPort(); + + String getAppDir(); + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/nodejs/NodeJsWebAppService.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/nodejs/NodeJsWebAppService.java b/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/nodejs/NodeJsWebAppService.java new file mode 100644 index 0000000..44e5885 --- /dev/null +++ b/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/nodejs/NodeJsWebAppService.java @@ -0,0 +1,75 @@ +/* + * 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.entity.webapp.nodejs; + +import java.util.List; + +import org.apache.brooklyn.catalog.Catalog; +import org.apache.brooklyn.entity.webapp.WebAppService; + +import brooklyn.config.ConfigKey; +import brooklyn.entity.basic.Attributes; +import brooklyn.entity.basic.ConfigKeys; +import brooklyn.entity.basic.SoftwareProcess; +import brooklyn.entity.proxying.ImplementedBy; +import brooklyn.location.PortRange; +import brooklyn.location.basic.PortRanges; +import brooklyn.util.flags.SetFromFlag; + +import com.google.common.collect.ImmutableList; +import com.google.common.reflect.TypeToken; + +@Catalog(name="Node.JS Application", + description="Node.js is a cross-platform runtime environment for server-side and networking applications. Node.js applications are written in JavaScriptq", + iconUrl="classpath:///nodejs-logo.png") +@ImplementedBy(NodeJsWebAppServiceImpl.class) +public interface NodeJsWebAppService extends SoftwareProcess, WebAppService { + + ConfigKey<String> SUGGESTED_VERSION = ConfigKeys.newConfigKeyWithDefault(SoftwareProcess.SUGGESTED_VERSION, "stable"); + + @SetFromFlag("httpPort") + ConfigKey<PortRange> HTTP_PORT = ConfigKeys.newConfigKeyWithDefault(Attributes.HTTP_PORT.getConfigKey(), PortRanges.fromInteger(3000)); + + @SetFromFlag("gitRepoUrl") + ConfigKey<String> APP_GIT_REPOSITORY_URL = ConfigKeys.newStringConfigKey("nodejs.gitRepo.url", "The Git repository where the application is hosted"); + + @SetFromFlag("archiveUrl") + ConfigKey<String> APP_ARCHIVE_URL = ConfigKeys.newStringConfigKey("nodejs.archive.url", "The URL where the application archive is hosted"); + + @SetFromFlag("appFileName") + ConfigKey<String> APP_FILE = ConfigKeys.newStringConfigKey("nodejs.app.fileName", "The NodeJS application file to start", "app.js"); + + @SetFromFlag("appName") + ConfigKey<String> APP_NAME = ConfigKeys.newStringConfigKey("nodejs.app.name", "The name of the NodeJS application"); + + @SetFromFlag("appCommand") + ConfigKey<String> APP_COMMAND = ConfigKeys.newStringConfigKey("nodejs.app.command", "Command to start the NodeJS application (defaults to node)", "node"); + + @SetFromFlag("appCommandLine") + ConfigKey<String> APP_COMMAND_LINE = ConfigKeys.newStringConfigKey("nodejs.app.commandLine", "Replacement command line to start the NodeJS application (ignores command and file if set)"); + + @SetFromFlag("nodePackages") + ConfigKey<List<String>> NODE_PACKAGE_LIST = ConfigKeys.newConfigKey(new TypeToken<List<String>>() { }, + "nodejs.packages", "The NPM packages to install", ImmutableList.<String>of()); + + ConfigKey<String> SERVICE_UP_PATH = ConfigKeys.newStringConfigKey("nodejs.serviceUp.path", "Path to use when checking the NodeJS application is running", "/"); + + Integer getHttpPort(); + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/77dff880/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/nodejs/NodeJsWebAppServiceImpl.java ---------------------------------------------------------------------- diff --git a/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/nodejs/NodeJsWebAppServiceImpl.java b/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/nodejs/NodeJsWebAppServiceImpl.java new file mode 100644 index 0000000..74eb500 --- /dev/null +++ b/software/webapp/src/main/java/org/apache/brooklyn/entity/webapp/nodejs/NodeJsWebAppServiceImpl.java @@ -0,0 +1,92 @@ +/* + * 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.entity.webapp.nodejs; + +import org.apache.brooklyn.entity.webapp.WebAppServiceMethods; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.entity.basic.Attributes; +import brooklyn.entity.basic.SoftwareProcessImpl; +import brooklyn.event.feed.ConfigToAttributes; +import brooklyn.event.feed.http.HttpFeed; +import brooklyn.event.feed.http.HttpPollConfig; +import brooklyn.event.feed.http.HttpValueFunctions; +import brooklyn.location.access.BrooklynAccessUtils; + +import com.google.common.base.Predicates; +import com.google.common.net.HostAndPort; + +public class NodeJsWebAppServiceImpl extends SoftwareProcessImpl implements NodeJsWebAppService { + + private static final Logger LOG = LoggerFactory.getLogger(NodeJsWebAppService.class); + + private transient HttpFeed httpFeed; + + @Override + public Class<?> getDriverInterface() { + return NodeJsWebAppDriver.class; + } + + @Override + public NodeJsWebAppDriver getDriver() { + return (NodeJsWebAppDriver) super.getDriver(); + } + + @Override + protected void connectSensors() { + super.connectSensors(); + + ConfigToAttributes.apply(this); + + HostAndPort accessible = BrooklynAccessUtils.getBrooklynAccessibleAddress(this, getHttpPort()); + String nodeJsUrl = String.format("http://%s:%d", accessible.getHostText(), accessible.getPort()); + LOG.info("Connecting to {}", nodeJsUrl); + + httpFeed = HttpFeed.builder() + .entity(this) + .baseUri(nodeJsUrl) + .poll(new HttpPollConfig<Boolean>(SERVICE_UP) + .suburl(getConfig(NodeJsWebAppService.SERVICE_UP_PATH)) + .checkSuccess(Predicates.alwaysTrue()) + .onSuccess(HttpValueFunctions.responseCodeEquals(200)) + .setOnException(false)) + .build(); + + WebAppServiceMethods.connectWebAppServerPolicies(this); + } + + @Override + public void disconnectSensors() { + if (httpFeed != null) httpFeed.stop(); + super.disconnectSensors(); + } + + @Override + protected void postStop() { + super.postStop(); + + setAttribute(REQUESTS_PER_SECOND_LAST, 0D); + setAttribute(REQUESTS_PER_SECOND_IN_WINDOW, 0D); + } + + @Override + public Integer getHttpPort() { return getAttribute(Attributes.HTTP_PORT); } + +}
