org.rhq.plugins.byteman.BytemanAgentDiscoveryComponent Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rhq-byteman-plugin Show documentation
Show all versions of rhq-byteman-plugin Show documentation
a plugin for managing VMs with a byteman agent installed
package org.rhq.plugins.byteman;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.configuration.PropertySimple;
import org.rhq.core.pluginapi.inventory.ClassLoaderFacet;
import org.rhq.core.pluginapi.inventory.DiscoveredResourceDetails;
import org.rhq.core.pluginapi.inventory.InvalidPluginConfigurationException;
import org.rhq.core.pluginapi.inventory.ManualAddFacet;
import org.rhq.core.pluginapi.inventory.ProcessScanResult;
import org.rhq.core.pluginapi.inventory.ResourceDiscoveryComponent;
import org.rhq.core.pluginapi.inventory.ResourceDiscoveryContext;
/**
* Discovers a Byteman agent.
*
* @author John Mazzitelli
*/
public class BytemanAgentDiscoveryComponent implements ResourceDiscoveryComponent,
ManualAddFacet, ClassLoaderFacet {
private final Log log = LogFactory.getLog(BytemanAgentDiscoveryComponent.class);
public static final String DEFAULT_BYTEMAN_ADDRESS = "127.0.0.1";
public static final String DEFAULT_BYTEMAN_PORT = "9091";
public static final String PLUGIN_CONFIG_PROP_ADDRESS = "listenerAddress";
public static final String PLUGIN_CONFIG_PROP_PORT = "listenerPort";
public static final String PLUGIN_CONFIG_PROP_CLIENT_JAR = "bytemanClientJar";
private static final String DEFAULT_DESCRIPTION = "Byteman agent that is able to perform byte-code manipulation within its JVM";
private static final String DEFAULT_NAME = "Byteman";
public Set discoverResources(ResourceDiscoveryContext context) {
log.info("Discovering byteman agents");
HashSet set = new HashSet();
// auto-discovery is performed by the container for us, via process scans
List autoDiscoveryResults = context.getAutoDiscoveredProcesses();
for (ProcessScanResult autoDiscoveryResult : autoDiscoveryResults) {
String[] cmdline = autoDiscoveryResult.getProcessInfo().getCommandLine();
if (cmdline != null) {
for (String arg : cmdline) {
if (arg.startsWith("-javaagent:") && arg.contains("byteman") && arg.contains(".jar")) {
// we know the main byteman jar has the submit client in it, too - so use it for the client jar
String libPath = arg.substring("-javaagent:".length(), arg.indexOf(".jar") + ".jar".length());
// try to normalize libPath to the byteman jar by getting the absoluate path to it
File libFile = new File(libPath);
if (libFile.exists()) {
libPath = libFile.getAbsolutePath();
} else {
// hmm... the byteman jar doesn't exist at the path we found; its probably a relative path to the VM's current working dir
String cwd = autoDiscoveryResult.getProcessInfo().getCurrentWorkingDirectory();
libFile = new File(cwd, libPath);
if (libFile.exists()) {
libPath = libFile.getAbsolutePath();
}
}
// get the listener address
String address = DEFAULT_BYTEMAN_ADDRESS;
Pattern pattern = Pattern.compile(".*address:([^,]+).*");
Matcher matcher = pattern.matcher(arg);
if (matcher.matches()) {
address = matcher.group(1);
// sanity check
if (address == null) {
log.warn("Address could not be parsed from byteman cmdline: " + arg);
address = DEFAULT_BYTEMAN_ADDRESS;
}
}
// get the listener port
String port = DEFAULT_BYTEMAN_PORT;
pattern = Pattern.compile(".*port:(\\d+).*");
matcher = pattern.matcher(arg);
if (matcher.matches()) {
port = matcher.group(1);
// sanity check
try {
Integer.parseInt(port);
} catch (NumberFormatException e) {
log.warn("Port # could not be parsed from byteman cmdline: " + arg);
port = DEFAULT_BYTEMAN_PORT;
}
}
// create a default plugin config, with the data we just discovered
Configuration pluginConfig = context.getDefaultPluginConfiguration();
pluginConfig.put(new PropertySimple(PLUGIN_CONFIG_PROP_ADDRESS, address));
pluginConfig.put(new PropertySimple(PLUGIN_CONFIG_PROP_PORT, port));
pluginConfig.put(new PropertySimple(PLUGIN_CONFIG_PROP_CLIENT_JAR, libPath));
// build the general information for the new resource
String key = address + ':' + port;
String name = DEFAULT_NAME;
String description = DEFAULT_DESCRIPTION;
String version = getVersion(address, port, libPath);
// add the new details for the new resource in our set to be returned
DiscoveredResourceDetails details = new DiscoveredResourceDetails(context.getResourceType(),
key, name, version, description, pluginConfig, null);
set.add(details);
// done, no need to keep looking at the command line args, we found what we need
break;
}
} // END for each command line argument
} // END if command line arguments exist
} // END for each process found in the process scan
return set;
}
public DiscoveredResourceDetails discoverResource(Configuration pluginConfiguration,
ResourceDiscoveryContext context) throws InvalidPluginConfigurationException {
// verify the plugin config for correctness
String address = pluginConfiguration.getSimpleValue(PLUGIN_CONFIG_PROP_ADDRESS, null);
String port = pluginConfiguration.getSimpleValue(PLUGIN_CONFIG_PROP_PORT, null);
String clientJar = pluginConfiguration.getSimpleValue(PLUGIN_CONFIG_PROP_CLIENT_JAR, null);
if (address == null) {
throw new InvalidPluginConfigurationException("Byteman address was not specified");
}
if (port == null) {
throw new InvalidPluginConfigurationException("Byteman port was not specified");
}
if (clientJar == null) {
throw new InvalidPluginConfigurationException("Byteman client jar was not specified");
}
try {
Integer.parseInt(port);
} catch (NumberFormatException e) {
throw new InvalidPluginConfigurationException("Port number was invalid: " + port, e);
}
File clientJarFile = new File(clientJar);
if (!clientJarFile.isFile() || !clientJarFile.canRead()) {
throw new InvalidPluginConfigurationException("Byteman client jar [" + clientJar + "] cannot be read");
}
// build the general information for the new resource
String key = address + ':' + port;
String name = DEFAULT_NAME;
String description = DEFAULT_DESCRIPTION;
String version = getVersion(address, port, clientJar);
DiscoveredResourceDetails details = new DiscoveredResourceDetails(context.getResourceType(), key, name,
version, description, pluginConfiguration, null);
return details;
}
public List getAdditionalClasspathUrls(ResourceDiscoveryContext context,
DiscoveredResourceDetails details) throws Exception {
PropertySimple clientJarProperty = details.getPluginConfiguration().getSimple(PLUGIN_CONFIG_PROP_CLIENT_JAR);
if (clientJarProperty == null || clientJarProperty.getStringValue() == null) {
throw new InvalidPluginConfigurationException("Byteman client jar not specified in plugin configuration");
}
String clientJarString = clientJarProperty.getStringValue();
File clientJarFile = new File(clientJarString);
if (!clientJarFile.exists()) {
throw new InvalidPluginConfigurationException("Byteman client jar [" + clientJarString + "] does not exist");
}
if (!clientJarFile.canRead()) {
throw new InvalidPluginConfigurationException("Byteman client jar [" + clientJarString + "] is unreadable");
}
List list = new ArrayList(1);
list.add(clientJarFile.toURI().toURL());
return list;
}
/**
* Tries to determine the version of the remote byteman agent. If, for some reason, the remote agent
* cannot be contacted, the manifest within the given jar will be used as the version as a "best guess".
* If all else fails, "0" will be returned as the version.
*
* @param address the address of the remote byteman agent listener
* @param port the port that the remote byteman agent is listening to
* @param jarPath the path to the byteman jar (which may be the client jar, or the core byteman jar)
*
* @return the version of the managed byteman agent; "0" if unknown
*/
protected String getVersion(String address, String port, String jarPath) {
String version = null;
Socket commSocket = null;
try {
// make a simple request to the remote agent for the version string
// we can't use the byteman Submit client here because its not in our classloader
int portInt = Integer.parseInt(port);
commSocket = new Socket(address, portInt);
BufferedReader commInput = new BufferedReader(new InputStreamReader(commSocket.getInputStream()));
PrintWriter commOutput = new PrintWriter(new OutputStreamWriter(commSocket.getOutputStream()));
commOutput.println("VERSION");
commOutput.flush();
String line = commInput.readLine();
while (line != null && !line.trim().equals("OK")) {
version = line.trim();
line = commInput.readLine();
}
} catch (Throwable t) {
// for some reason, failed to communicate with the remote agent, read version from jar manifest
version = getJarAttribute(jarPath, "Implementation-Version", null);
} finally {
if (commSocket != null) {
try {
commSocket.close();
} catch (Exception ignore) {
}
}
}
if (version == null) {
version = "0";
}
return version;
}
/**
* Static utility method that can extract a main attribute value from a given jar file's manifest.
*
* @param jarPath location of the jar file
* @param attributeName name of the main attribute to retrieve
* @param defaultValue if the manifest doesn't have the attribute or the manifest itself doesn't exist, this is returned
* @return the value of the attribute
*/
public static String getJarAttribute(String jarPath, String attributeName, String defaultValue) {
String attributeValue = null;
try {
JarFile jarFile = new JarFile(jarPath);
try {
Manifest manifest = jarFile.getManifest();
attributeValue = manifest.getMainAttributes().getValue(attributeName);
} finally {
jarFile.close();
}
} catch (Throwable t1) {
// jar file doesn't have a manifest?
}
if (attributeValue == null) {
attributeValue = defaultValue;
}
return attributeValue;
}
}