blob: f633f3c6a001550829e557073256a45314c4b09c [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.entity.java;
import java.util.EnumSet;
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.entity.EntityLocal;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.config.ConfigKey.HasConfigKey;
import org.apache.brooklyn.core.entity.EntityInternal;
import org.apache.brooklyn.core.location.Locations;
import org.apache.brooklyn.core.location.access.BrooklynAccessUtils;
import org.apache.brooklyn.feed.jmx.JmxHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.brooklyn.location.ssh.SshMachineLocation;
import org.apache.brooklyn.util.collections.MutableList;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.core.BrooklynMavenArtifacts;
import org.apache.brooklyn.util.core.ResourceUtils;
import org.apache.brooklyn.util.core.task.Tasks;
import org.apache.brooklyn.util.guava.Maybe;
import org.apache.brooklyn.util.jmx.jmxmp.JmxmpAgent;
import org.apache.brooklyn.util.jmx.jmxrmi.JmxRmiAgent;
import org.apache.brooklyn.util.maven.MavenArtifact;
import org.apache.brooklyn.util.maven.MavenRetriever;
import org.apache.brooklyn.util.net.Urls;
import org.apache.brooklyn.util.text.Strings;
import com.google.common.base.Preconditions;
import com.google.common.net.HostAndPort;
public class JmxSupport implements UsesJmx {
private static final Logger log = LoggerFactory.getLogger(JmxSupport.class);
private final Entity entity;
private final String runDir;
private Boolean isJmx;
private Boolean isSecure;
private JmxAgentModes jmxAgentMode;
private static boolean warnedAboutNotOnClasspath = false;
/** run dir may be null if it is not accessed */
public JmxSupport(Entity entity, @Nullable String runDir) {
this.entity = Preconditions.checkNotNull(entity, "entity must be supplied");
this.runDir = runDir;
}
@Nonnull
public String getRunDir() {
return Preconditions.checkNotNull(runDir, "runDir must have been supplied to perform this operation");
}
public Entity getEntity() {
return entity;
}
<T> T getConfig(ConfigKey<T> key) {
return getEntity().getConfig(key);
}
<T> T getConfig(HasConfigKey<T> key) {
return getEntity().getConfig(key);
}
<T> void setConfig(ConfigKey<T> key, T value) {
((EntityLocal)getEntity()).setConfig(key, value);
}
public Maybe<SshMachineLocation> getMachine() {
return Locations.findUniqueSshMachineLocation(entity.getLocations());
}
public boolean isJmx() {
init();
return isJmx;
}
public JmxAgentModes getJmxAgentMode() {
init();
if (jmxAgentMode==null) return JmxAgentModes.NONE;
return jmxAgentMode;
}
public boolean isSecure() {
init();
if (isSecure==null) return false;
return isSecure;
}
protected synchronized void init() {
if (isJmx!=null)
return;
if (Boolean.FALSE.equals(entity.getConfig(USE_JMX))) {
isJmx = false;
return;
}
isJmx = true;
jmxAgentMode = entity.getConfig(JMX_AGENT_MODE);
if (jmxAgentMode==null) jmxAgentMode = JmxAgentModes.AUTODETECT;
isSecure = entity.getConfig(JMX_SSL_ENABLED);
if (isSecure==null) isSecure = false;
if (jmxAgentMode==JmxAgentModes.AUTODETECT) {
if (isSecure()) {
jmxAgentMode = JmxAgentModes.JMXMP;
} else {
jmxAgentMode = JmxAgentModes.JMXMP_AND_RMI;
if (!ResourceUtils.create(this).doesUrlExist(getJmxAgentJarUrl())) {
// can happen e.g. if eclipse build
log.warn("JMX agent JAR not found ("+getJmxAgentJarUrl()+") when auto-detecting JMX settings for "+entity+"; " +
"likely cause is an incomplete build (e.g. from Eclipse; run a maven build then retry in the IDE); "+
"reverting to NONE (use built-in Java JMX support, which will not go through firewalls)");
jmxAgentMode = JmxAgentModes.NONE;
}
}
((EntityLocal)entity).setConfig(JMX_AGENT_MODE, jmxAgentMode);
}
if (isSecure && jmxAgentMode!=JmxAgentModes.JMXMP) {
String msg = "JMX SSL is specified, but it requires JMXMP which is disabled, when configuring "+entity;
log.warn(msg);
throw new IllegalStateException(msg);
}
}
public void setJmxUrl() {
((EntityInternal)entity).setAttribute(JMX_URL, getJmxUrl());
}
public String getJmxUrl() {
init();
HostAndPort jmx = BrooklynAccessUtils.getBrooklynAccessibleAddress(entity, entity.getAttribute(JMX_PORT));
if (EnumSet.of(JmxAgentModes.JMXMP, JmxAgentModes.JMXMP_AND_RMI).contains(getJmxAgentMode())) {
return JmxHelper.toJmxmpUrl(jmx.getHostText(), jmx.getPort());
} else {
if (getJmxAgentMode() == JmxAgentModes.NONE) {
fixPortsForModeNone();
}
// this will work for agent or agentless
HostAndPort rmi = BrooklynAccessUtils.getBrooklynAccessibleAddress(entity, entity.getAttribute(RMI_REGISTRY_PORT));
return JmxHelper.toRmiJmxUrl(jmx.getHostText(), jmx.getPort(), rmi.getPort(),
entity.getAttribute(JMX_CONTEXT));
}
}
/** mode NONE cannot set a JMX (RMI server) port; it needs an RMI registry port,
* then gets redirected to an anonymous RMI server port;
* both the hostname and the anonymous port must be accessible to use this mode
* (hence the use of the other agents in most cases) */
protected int fixPortsForModeNone() {
assert getJmxAgentMode()==JmxAgentModes.NONE;
Integer jmxRemotePort = getEntity().getAttribute(JMX_PORT);
Integer rmiRegistryPort = getEntity().getAttribute(RMI_REGISTRY_PORT);
if (rmiRegistryPort!=null && rmiRegistryPort>0) {
if (jmxRemotePort==null || jmxRemotePort!=rmiRegistryPort) {
if (jmxRemotePort!=null && jmxRemotePort>0) {
// ignore RMI registry port when mode 'none' is set -- set same as JMX port here
// (bit irritating, but JMX_PORT will be ignored in this mode)
log.warn("Ignoring JMX_PORT "+jmxRemotePort+" when configuring agentless JMX on "+getEntity()+"; will use RMI_REGISTRY_PORT "+rmiRegistryPort);
}
jmxRemotePort = rmiRegistryPort;
((EntityLocal)getEntity()).setAttribute(JMX_PORT, jmxRemotePort);
}
} else {
if (jmxRemotePort==null || jmxRemotePort<=0) {
throw new IllegalStateException("Invalid JMX_PORT "+jmxRemotePort+" and RMI_REGISTRY_PORT "+rmiRegistryPort+" when configuring JMX "+getJmxAgentMode()+" on "+getEntity());
}
((EntityLocal)getEntity()).setAttribute(RMI_REGISTRY_PORT, jmxRemotePort);
}
return jmxRemotePort;
}
public List<String> getJmxJavaConfigOptions() {
if (EnumSet.<JmxAgentModes>of(JmxAgentModes.NONE, JmxAgentModes.JMX_RMI).contains(getJmxAgentMode())) {
return MutableList.of();
} else {
return MutableList.of(String.format("-javaagent:%s", getJmxAgentJarDestinationFilePath()));
}
}
public String getJmxAgentJarDestinationFilePath() {
// cache the local path so we continue to work post-rebind to a different version
String result = getEntity().getAttribute(JMX_AGENT_LOCAL_PATH);
if (Strings.isNonBlank(result)) return result;
result = getJmxAgentJarDestinationFilePathDefault();
((EntityInternal)getEntity()).setAttribute(JMX_AGENT_LOCAL_PATH, result);
return result;
}
public String getJmxAgentJarDestinationFilePathDefault() {
return Urls.mergePaths(getRunDir(), getJmxAgentJarBasename());
}
@Nullable public MavenArtifact getJmxAgentJarMavenArtifact() {
switch (getJmxAgentMode()) {
case JMXMP:
case JMXMP_AND_RMI:
MavenArtifact result = BrooklynMavenArtifacts.artifact(null, "brooklyn-jmxmp-agent", "jar", "with-dependencies");
// the "with-dependencies" variant is needed; however the filename then has the classifier segment _replaced_ by "shaded" when this filename is created
result.setCustomFileNameAfterArtifactMarker("shaded");
result.setClassifierFileNameMarker("");
return result;
case JMX_RMI_CUSTOM_AGENT:
return BrooklynMavenArtifacts.jar("brooklyn-jmxrmi-agent");
default:
return null;
}
}
/** @deprecated since 0.6.0; use {@link #getJmxAgentJarMavenArtifact()} */
@Deprecated
public String getJmxAgentJarBasename() {
MavenArtifact artifact = getJmxAgentJarMavenArtifact();
if (artifact==null)
throw new IllegalStateException("Either JMX is not enabled or there is an error in the configuration (JMX mode "+getJmxAgentMode()+" does not support agent JAR)");
return artifact.getFilename();
}
/** returns URL for accessing the java agent, throwing if not applicable;
* prefers on classpath where it should be, but will fall back to taking from maven hosted
* (known problem in Eclipse where JARs are not always copied)
*/
public String getJmxAgentJarUrl() {
MavenArtifact artifact = getJmxAgentJarMavenArtifact();
if (artifact==null)
throw new IllegalStateException("Either JMX is not enabled or there is an error in the configuration (JMX mode "+getJmxAgentMode()+" does not support agent JAR)");
String jar = "classpath://" + artifact.getFilename();
if (ResourceUtils.create(this).doesUrlExist(jar))
return jar;
String result = MavenRetriever.localUrl(artifact);
if (warnedAboutNotOnClasspath) {
log.debug("JMX JAR for "+artifact+" is not on the classpath; taking from "+result);
} else {
log.warn("JMX JAR for "+artifact+" is not on the classpath; taking from "+result+" (subsequent similar messages will be logged at debug)");
warnedAboutNotOnClasspath = true;
}
return result;
}
/** applies _some_ of the common settings needed to connect via JMX */
public void applyJmxJavaSystemProperties(MutableMap.Builder<String,Object> result) {
if (!isJmx()) return ;
Integer jmxPort = Preconditions.checkNotNull(entity.getAttribute(JMX_PORT), "jmx port must not be null for %s", entity);
HostAndPort jmx = BrooklynAccessUtils.getBrooklynAccessibleAddress(entity, jmxPort);
Integer jmxRemotePort = getEntity().getAttribute(JMX_PORT);
String hostName = jmx.getHostText();
result.put("com.sun.management.jmxremote", null);
result.put("java.rmi.server.hostname", hostName);
switch (getJmxAgentMode()) {
case JMXMP_AND_RMI:
Integer rmiRegistryPort = Preconditions.checkNotNull(entity.getAttribute(UsesJmx.RMI_REGISTRY_PORT), "registry port (config val %s)", entity.getConfig(UsesJmx.RMI_REGISTRY_PORT));
result.put(JmxmpAgent.RMI_REGISTRY_PORT_PROPERTY, rmiRegistryPort);
case JMXMP:
if (jmxRemotePort==null || jmxRemotePort<=0)
throw new IllegalStateException("Unsupported JMX port "+jmxRemotePort+" - when applying system properties ("+getJmxAgentMode()+" / "+getEntity()+")");
result.put(JmxmpAgent.JMXMP_PORT_PROPERTY, jmxRemotePort);
// with JMXMP don't try to tell it the hostname -- it isn't needed for JMXMP, and if specified
// it will break if the hostname we see is not known at the server, e.g. a forwarding public IP
result.remove("java.rmi.server.hostname");
break;
case JMX_RMI_CUSTOM_AGENT:
if (jmxRemotePort==null || jmxRemotePort<=0)
throw new IllegalStateException("Unsupported JMX port "+jmxRemotePort+" - when applying system properties ("+getJmxAgentMode()+" / "+getEntity()+")");
result.put(JmxRmiAgent.RMI_REGISTRY_PORT_PROPERTY, Preconditions.checkNotNull(entity.getAttribute(UsesJmx.RMI_REGISTRY_PORT), "registry port"));
result.put(JmxRmiAgent.JMX_SERVER_PORT_PROPERTY, jmxRemotePort);
break;
case NONE:
jmxRemotePort = fixPortsForModeNone();
case JMX_RMI:
result.put("com.sun.management.jmxremote.port", jmxRemotePort);
result.put("java.rmi.server.useLocalHostname", "true");
break;
default:
throw new IllegalStateException("Unsupported JMX mode - when applying system properties ("+getJmxAgentMode()+" / "+getEntity()+")");
}
if (isSecure()) {
// set values true, and apply keys pointing to keystore / truststore
getJmxSslSupport().applyAgentJmxJavaSystemProperties(result);
} else {
result.
put("com.sun.management.jmxremote.ssl", false).
put("com.sun.management.jmxremote.authenticate", false);
}
}
/** installs files needed for JMX, to the runDir given in constructor, assuming the runDir has been created */
public void install() {
if (EnumSet.of(JmxAgentModes.JMXMP_AND_RMI, JmxAgentModes.JMXMP, JmxAgentModes.JMX_RMI_CUSTOM_AGENT).contains(getJmxAgentMode())) {
Tasks.setBlockingDetails("Copying JMX agent jar to server.");
try {
getMachine().get().copyTo(ResourceUtils.create(this).getResourceFromUrl(
getJmxAgentJarUrl()), getJmxAgentJarDestinationFilePath());
} finally {
Tasks.resetBlockingDetails();
}
}
if (isSecure()) {
getJmxSslSupport().install();
}
}
protected JmxmpSslSupport getJmxSslSupport() {
return new JmxmpSslSupport(this);
}
/** sets JMR_RMI_CUSTOM_AGENT as the connection mode for the indicated apps.
* <p>
* TODO callers of this method have RMI dependencies in the actual app;
* we should look at removing them, so that those pieces of software can run behind
* forwarding public IP's and over SSL (both reasons JMXMP is preferred by us!)
*/
public void recommendJmxRmiCustomAgent() {
// set JMX_RMI because the registry is needed (i think)
Maybe<Object> jmx = entity.getConfigRaw(UsesJmx.JMX_AGENT_MODE, true);
if (!jmx.isPresentAndNonNull()) {
setConfig(UsesJmx.JMX_AGENT_MODE, JmxAgentModes.JMX_RMI_CUSTOM_AGENT);
} else if (jmx.get()!=JmxAgentModes.JMX_RMI_CUSTOM_AGENT) {
log.warn("Entity "+entity+" may not function unless running JMX_RMI_CUSTOM_AGENT mode (asked to use "+jmx.get()+")");
}
}
}