/* * NOTE: This copyright does *not* cover user programs that use HQ * program services by normal system calls through the application * program interfaces provided as part of the Hyperic Plug-in Development * Kit or the Hyperic Client Development Kit - this is merely considered * normal use of the program, and does *not* fall under the heading of * "derived work". * * Copyright (C) [2004, 2005, 2006], Hyperic, Inc. * This file is part of HQ. * * HQ is free software; you can redistribute it and/or modify * it under the terms version 2 of the GNU General Public License as * published by the Free Software Foundation. This program is distributed * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA. */ package org.hyperic.hq.product.jmx; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.regex.Pattern; import javax.management.MBeanServerConnection; import javax.management.MalformedObjectNameException; import javax.management.ObjectName; import javax.management.remote.JMXConnector; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hyperic.hq.product.AutoServerDetector; import org.hyperic.hq.product.DaemonDetector; import org.hyperic.hq.product.Metric; import org.hyperic.hq.product.PluginException; import org.hyperic.hq.product.ProductPlugin; import org.hyperic.hq.product.ServerResource; import org.hyperic.hq.product.ServerTypeInfo; import org.hyperic.hq.product.ServiceResource; import org.hyperic.sigar.SigarException; import org.hyperic.util.config.ConfigOption; import org.hyperic.util.config.ConfigResponse; import org.hyperic.util.config.ConfigSchema; public class MxServerDetector extends DaemonDetector implements AutoServerDetector { private static final String SUN_REMOTE_AUTHENTICATION_FALSE = "com.sun.management.jmxremote.authenticate=false"; private static final String TEMPLATE_PROPERTY = "template"; private static final String CONTROL_CLASS_PROPERTY = "control-class"; private static final String MEASUREMENT_CLASS_PROPERTY = "measurement-class"; private static final Log log = LogFactory.getLog(MxServerDetector.class); static final String PROP_SERVICE_NAME = "name"; public static final String PROC_MAIN_CLASS = "PROC_MAIN_CLASS"; public static final String PROC_HOME_PROPERTY = "PROC_HOME_PROPERTY"; public static final String PROC_HOME_ENV = "PROC_HOME_ENV"; public static final String PROP_PROCESS_QUERY = "process.query"; protected static final String PROC_JAVA = "State.Name.sw=java"; protected static final String SUN_JMX_REMOTE = "-Dcom.sun.management.jmxremote"; protected static final String SUN_JMX_PORT = SUN_JMX_REMOTE + ".port="; private ServiceTypeFactory serviceTypeFactory = new ServiceTypeFactory(); protected static String getMxURL(String port) { return "service:jmx:rmi:///jndi/rmi://localhost:" + port + "/jmxrmi"; } protected String parseMxPort(String arg) { if (!arg.startsWith(SUN_JMX_PORT)) { return null; } return arg.substring(SUN_JMX_PORT.length()); } /** * First checks if the ptql query is specified in the process properties, * if not, then it checks to see if the port value is specified, to generate the service URL. * @return True if configured, otherwise false. */ protected boolean configureMxURL(ConfigResponse config, String arg){ final String prop = SUN_JMX_REMOTE + "="; if (arg.startsWith(prop)){ String subString = arg.substring(prop.length()); if (subString.startsWith(MxUtil.PTQL_PREFIX)) { log.debug("Found jmx ptql query for local pid connection: " + subString); // local access enabled via: // -Dcom.sun.management.jmxremote=ptql:State.Name.eq=java,... config.setValue(MxUtil.PROP_JMX_URL, subString); return true; } } String port; if ((port = parseMxPort(arg)) != null) { String serviceUrl = getMxURL(port); log.debug("Found jmx port, creating service url:" + serviceUrl); // remote access enabled via: // -Dcom.sun.management.jmxremote.port=xxxx config.setValue(MxUtil.PROP_JMX_URL, serviceUrl); // for use in name %jmx.port% template config.setValue(MxUtil.PROP_JMX_PORT, port); return true; } return false; } /** * Goes through several checks in order to find the jmx url in the configuration. * For each process argument it will check the following: * 1) Does it use the ptql query option to detect the process (i.e -Dcom.sun.management.jmxremote=ptql:State.Name...). * 2) Does it match -Dcom.sun.management.jmxremote.port=<port>. * 3) Did the user configure the jmx.url in the ui(optionally the jmx.username and jmx.password). * 4) Does it match the ptql query generated using the install path. * 5) Otherwise it returns false. * * @param config * @param args * @param processQuery * @return True if the url was set, otherwise false. */ protected boolean findAndSetURL(ConfigResponse config, List<String> args, String processQuery) { boolean authenticationNotRequired = args.contains(SUN_REMOTE_AUTHENTICATION_FALSE); for (String arg : args) { if (!configureMxURL(config, arg)) { if (!configureUserSpecifiedMxUrl(config, arg, authenticationNotRequired)) { if (!configureLocalMxURL(config, arg, processQuery)) { return false; } } } return true; } return false; } private boolean configureUserSpecifiedMxUrl(ConfigResponse config, String arg, boolean authenticationNotRequired) { String mxUrl = config.getValue(MxUtil.PROP_JMX_URL); boolean urlSet = false; if (mxUrl != null) { log.debug("Using jmx.url specified in the configuration: " + mxUrl); urlSet = true; config.setValue(MxUtil.PROP_JMX_URL, mxUrl); if (!authenticationNotRequired) { String username = config.getValue(MxUtil.PROP_JMX_USERNAME); String password = config.getValue(MxUtil.PROP_JMX_PASSWORD); if (username != null && password != null) { log.debug("Found username and password for JMX auth."); // only set these values if both exist. config.setValue(MxUtil.PROP_JMX_USERNAME, username); config.setValue(MxUtil.PROP_JMX_PASSWORD, password); } else { log.debug("No username and/or password was specified for JMX connection."); } } } return urlSet; } protected boolean configureLocalMxURL(ConfigResponse config, String arg, String query) { String mxUrl = null; boolean urlSet = false; try { //verify local url access is supported by this JVM //and we have the appropriate permissions // MxUtil will throw an exception if it doesn't get the pid url MxUtil.getUrlFromPid(query); mxUrl = MxUtil.PTQL_PREFIX + query; config.setValue(MxUtil.PROP_JMX_URL, mxUrl); urlSet = true; log.debug("Using the local pid to create jmx url: " + mxUrl); } catch (Exception e) { log.debug("Cannot configure jmx.url using local pid: " + e.getMessage(),e); } return urlSet; } protected String getProcMainClass() { return getTypeProperty(PROC_MAIN_CLASS); } protected String getProcHomeProperty() { return getTypeProperty(PROC_HOME_PROPERTY); } protected String getProcHomeEnv() { return getTypeProperty(PROC_HOME_ENV); } private String getProcHomeEnv(long pid) { String key = getProcHomeEnv(); if (key == null) { return null; } try { String val = getSigar().getProcEnv(pid, key); return val; } catch (SigarException e) { return null; } } protected String getProcQuery() { return getProcQuery(null); } private boolean isMatch(String val) { return val.indexOf('=') != -1; } protected String getProcQuery(String path) { StringBuffer query = new StringBuffer(); String mainClass = getProcMainClass(); query.append(PROC_JAVA); if (mainClass != null) { query.append(",Args.*.eq=" + mainClass); } String homeProp = getProcHomeProperty(); if (homeProp != null) { boolean isMatch = isMatch(homeProp); if (path == null) { query.append(",Args.*."); if (isMatch) { query.append("re=-D" + homeProp); } else { query.append("sw=-D" + homeProp + "="); } } else { if (isMatch) { int ix = homeProp.indexOf('='); if (ix != -1) { homeProp = homeProp.substring(0, ix); } } //expand to exact match if given path query.append(",Args.*.eq=-D" + homeProp + "=" + path); } } if ((homeProp == null) && (mainClass == null)) { String msg = "No " + PROC_MAIN_CLASS + " or " + PROC_HOME_PROPERTY + " defined"; throw new IllegalStateException(msg); } if (log.isDebugEnabled()) log.debug("using ptql query=" + query); return query.toString(); } public static class MxProcess { long _pid; String _installpath; String[] _args; String _url; public MxProcess(long pid, String[] args, String installpath) { _pid = pid; _args = args; _installpath = installpath; } public long getPid() { return _pid; } public String getInstallPath() { return _installpath; } public String[] getArgs() { return _args; } public String getURL() { return _url; } public void setURL(String url) { _url = url; } } private boolean matches(String source, String regex) { return Pattern.compile(regex).matcher(source).find(); } protected List getServerProcessList() { List procs = new ArrayList(); String query = getProcQuery(); long[] pids = getPids(query); if (log.isDebugEnabled()) log.debug("ptql=" + query + " matched pids=" + Arrays.asList(pids)); String homeProp = getProcHomeProperty(); final boolean isMatch = isMatch(homeProp); if (isMatch) { homeProp = "-D" + homeProp; } else { homeProp = "-D" + homeProp + "="; } for (int i=0; i<pids.length; i++) { long pid = pids[i]; //need to find installpath for each match //-Dfoo.home arg, FOO_HOME env var or cwd String[] args = getProcArgs(pid); String path = null; for (int j=0; j<args.length; j++) { String arg = args[j]; if (isMatch) { if (matches(arg, homeProp)) { int ix = arg.indexOf('='); if (ix != -1) { path = arg.substring(ix+1); break; } } } else if (arg.startsWith(homeProp)) { path = arg.substring(homeProp.length()); break; } } if (path == null) { path = getProcHomeEnv(pid); } if (path == null) { path = getProcCwd(pid); } if (path != null) { MxProcess process = new MxProcess(pid, args, path); procs.add(process); } } return procs; } protected boolean isInstallTypeVersion(MxProcess process) { String dir = process.getInstallPath(); return isInstallTypeVersion(dir); } protected void setProductConfig(ServerResource server, ConfigResponse config, long pid) { super.setProductConfig(server, config); } protected ServerResource getServerResource(MxProcess process) { String dir = process.getInstallPath(); //set process.query using the same query used to find the process, //with PROC_HOME_DIR (if defined) expanded to match dir String query = getProcQuery(dir); // Create the server resource ServerResource server = newServerResource(dir); adjustClassPath(dir); ConfigResponse config = new ConfigResponse(); ConfigSchema schema = getConfigSchema(getTypeInfo().getName(), ProductPlugin.CFGTYPE_IDX_PRODUCT); if (schema != null) { ConfigOption option = schema.getOption(PROP_PROCESS_QUERY); if (option != null) { // Configure process.query config.setValue(option.getName(), query); } } try { setJmxUrl(process,config); } catch (MxRuntimeException e){ if (log.isDebugEnabled()){ log.debug(e.getMessage(), e); } else { log.info(e.getMessage()); } } // default anything not auto-configured setProductConfig(server, config,process.getPid()); discoverServerConfig(server, process.getPid()); server.setMeasurementConfig(); return server; } /** * Sets the JMX url. * First checks whether the process supplies the jmx.url. Then does some searching for the url. * @param process * @param config * @throws MxRuntimeException If there is no jmx.url found. */ protected void setJmxUrl(MxProcess process, ConfigResponse config) { boolean urlSet = false; String processQuery = getProcQuery(process.getInstallPath()); if (process.getURL() != null) { log.debug("Found jmx.url in the process args: " + process.getURL()); // check if jmx.url was found in the process props. config.setValue(MxUtil.PROP_JMX_URL, process.getURL()); urlSet = true; } else { // check for url in other places List<String> args = Arrays.asList(process.getArgs()); urlSet = findAndSetURL(config, args, processQuery); } if (!urlSet) { throw new MxRuntimeException( "Unable to find the jmx.url in configuration properties: " + config); } } public List getServerResources(ConfigResponse platformConfig) throws PluginException { setPlatformConfig(platformConfig); List servers = new ArrayList(); List procs = getServerProcessList(); for (int i=0; i<procs.size(); i++) { MxProcess process = (MxProcess)procs.get(i); if (!isInstallTypeVersion(process)) { continue; } servers.add(getServerResource(process)); } return servers; } protected List discoverMxServices(MBeanServerConnection mServer, ConfigResponse serverConfig) throws PluginException { String url = serverConfig.getValue(MxUtil.PROP_JMX_URL); log.debug("[discoverMxServices] url="+url); configure(serverConfig); //for MxServerQuery to use detector.getConfig() MxServerQuery serverQuery = new MxServerQuery(this); String objName = getTypeProperty(MxQuery.PROP_OBJECT_NAME); log.debug("[discoverMxServices] objName="+objName); if (objName != null) { try { objName = Metric.translate(objName, serverConfig); log.debug("[discoverMxServices] objName="+objName); serverQuery.setObjectName(new ObjectName(objName)); } catch (MalformedObjectNameException e) { throw new PluginException(objName, e); } } serverQuery.setURL(url); serverQuery.getAttributes(mServer); serverQuery.findServices(mServer); List queries = serverQuery.getServiceQueries(); getLog().debug("discovered " + queries.size() + " services"); List services = new ArrayList(); for (int i=0; i<queries.size(); i++) { MxServiceQuery query = (MxServiceQuery)queries.get(i); ServiceResource service = new ServiceResource(); ConfigResponse config = new ConfigResponse(query.getResourceConfig()); ConfigResponse cprops = new ConfigResponse(query.getCustomProperties()); service.setType(query.getResourceType()); String name = formatAutoInventoryName(service.getType(), serverConfig, config, cprops); if (name == null) { //prefix w/ server name name = ServiceResource.SERVER_NAME_PREFIX; String queryName = query.getName(); if ((queryName != null) && (queryName.length() != 0)) { name += query.getName() + " "; } name += query.getServiceResourceType(); } service.setName(name); if (query.hasControl()) { ConfigResponse controlConfig = new ConfigResponse(query.getControlConfig()); service.setControlConfig(controlConfig); } service.setProductConfig(config); service.setMeasurementConfig(); service.setCustomProperties(cprops); services.add(service); } setCustomProperties(new ConfigResponse(serverQuery.getCustomProperties())); return services; } protected List discoverServices(ConfigResponse serverConfig) throws PluginException { log.debug("[discoverServices] serverConfig="+serverConfig); JMXConnector connector = null; MBeanServerConnection mServer; try { connector = MxUtil.getCachedMBeanConnector(serverConfig.toProperties()); mServer = connector.getMBeanServerConnection(); } catch (Exception e) { MxUtil.close(connector); throw new PluginException(e.getMessage(), e); } try { return discoverMxServices(mServer, serverConfig); } finally { MxUtil.close(connector); } } public Set discoverServiceTypes(ConfigResponse serverConfig) throws PluginException { JMXConnector connector = null; MBeanServerConnection mServer; Set serviceTypes = new HashSet(); //plugins need to define these properties at the plugin level to discover dynamic service types if(getProductPlugin().getPluginData() .getProperty(MEASUREMENT_CLASS_PROPERTY) == null || getProductPlugin().getPluginData() .getProperty(CONTROL_CLASS_PROPERTY) == null || getProductPlugin().getPluginData() .getProperty(TEMPLATE_PROPERTY) == null) { return serviceTypes; } try { connector = MxUtil.getCachedMBeanConnector(serverConfig.toProperties()); mServer = connector.getMBeanServerConnection(); } catch (Exception e) { MxUtil.close(connector); throw new PluginException(e.getMessage(), e); } try { final Set objectNames = mServer.queryNames(new ObjectName(MBeanUtil.DYNAMIC_SERVICE_DOMAIN + ":*"), null); serviceTypes = serviceTypeFactory.create(getProductPlugin(), (ServerTypeInfo)getTypeInfo(), mServer, objectNames); } catch (Exception e) { throw new PluginException(e.getMessage(), e); } finally { MxUtil.close(connector); } return serviceTypes; } }