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