org.hyperic.hq.product.jmx.MxServerDetector.java Source code

Java tutorial

Introduction

Here is the source code for org.hyperic.hq.product.jmx.MxServerDetector.java

Source

/*
 * 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;
    }

}