org.hyperic.hq.plugin.oracle.OracleServerDetector.java Source code

Java tutorial

Introduction

Here is the source code for org.hyperic.hq.plugin.oracle.OracleServerDetector.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-2008], 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.plugin.oracle;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.Reader;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;

import org.hyperic.hq.product.AutoServerDetector;
import org.hyperic.hq.product.PluginException;
import org.hyperic.hq.product.FileServerDetector;
import org.hyperic.hq.product.ProductPlugin;
import org.hyperic.hq.product.ServerDetector;
import org.hyperic.hq.product.ServerResource;
import org.hyperic.hq.product.ServiceResource;

import org.hyperic.sigar.SigarException;
import org.hyperic.util.config.ConfigResponse;
import org.hyperic.util.jdbc.DBUtil;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class OracleServerDetector extends ServerDetector implements FileServerDetector, AutoServerDetector {

    private transient Log log = LogFactory.getLog("OracleServerDetector");

    private static final String PTQL_QUERY = "State.Name.eq=oracle";

    private static final String TNS_PTQL_QUERY = "State.Name.eq=tnslsnr";

    private static final String PROP_PROC_PTQL = "process.ptql";

    private static final String PROP_TNSNAMES = "tnsnames";

    private static final String ORATAB = "/etc/oratab";

    private static final Pattern _serviceNameEx = Pattern.compile("\\(\\s*service_name\\s*=",
            Pattern.CASE_INSENSITIVE);

    // Versions
    static final String VERSION_8i = "8i";
    static final String VERSION_9i = "9i";
    static final String VERSION_10g = "10g";
    static final String VERSION_11g = "11g";

    // User instance
    static final String USER_INSTANCE = "User Instance";
    static final String USER_QUERY = "SELECT UNIQUE username FROM V$SESSION WHERE username IS NOT NULL";
    static final String DBA_USER_QUERY = "SELECT * FROM DBA_USERS WHERE USERNAME = ";

    // Tablespace
    static final String TABLESPACE = "Tablespace";
    static final String TABLESPACE_QUERY = "SELECT * FROM DBA_TABLESPACES";

    // Table
    static final String SEGMENT = "Segment";
    static final String SEGMENT_QUERY = "select SEGMENT_NAME, TABLESPACE_NAME" + " FROM USER_SEGMENTS"
            + " WHERE SEGMENT_NAME not like 'BIN$%'" + " and SEGMENT_NAME not like 'SYS_%'";

    // Server custom props
    static final String VERSION_QUERY = "SELECT * FROM V$VERSION";

    /**
     * Utility function to query the process table for Oracle
     */
    private List getServerProcessList() {
        ArrayList servers = new ArrayList();

        long[] pids = getPids(PTQL_QUERY);

        for (int i = 0; i < pids.length; i++) {
            String exe = getProcExe(pids[i]);

            if (exe == null) {
                continue;
            }

            File binary = new File(exe.toLowerCase());

            if (!binary.isAbsolute()) {
                continue;
            }

            if (!servers.contains(binary.getAbsolutePath()))
                servers.add(binary.getAbsolutePath());
        }

        return servers;
    }

    private boolean hasExe(File bin, String name) {
        if (isWin32()) {
            return new File(bin, name + ".exe").exists() || new File(bin, name + ".bat").exists();
        } else {
            return new File(bin, name).exists();
        }
    }

    //e.g. file = "orasql10.", matches: "orasql10.dll", "liborasql10.so", etc.
    private boolean hasFile(File dir, final String file) {
        String[] names = dir.list(new FilenameFilter() {
            public boolean accept(File dir, String name) {
                return name.toLowerCase().indexOf(file) != -1;
            }
        });
        if (names == null || names.length == 0) {
            return false;
        } else {
            return true;
        }
    }

    //for use w/ -jar hq-pdk.jar or agent.properties
    private boolean configureProperties(ConfigResponse config) {
        boolean hasCreds = false;
        String[] keys = getConfigSchema(getTypeInfo(), config).getOptionNames();

        for (int i = 0; i < keys.length; i++) {
            String key = keys[i];
            String val = getManager().getProperty(key);
            if (val != null) {
                config.setValue(key, val);
                if (key.startsWith("jdbc")) {
                    hasCreds = true;
                }
            }
        }
        return hasCreds;
    }

    /**
     * Utility method to determine oracle version by file layout
     * This method requires the path to the oracle exe. From there is verifies and creates a
     * server resource for the oracle server instance.
     * @param oracleExePath The path to the oracle exe, either from process scan or file scan.
     */
    private ServerResource getOracleServer(String oracleExePath) throws PluginException {
        log.debug("Found oracle exe path=" + oracleExePath);
        ServerResource oracleServer = null;
        String version = getTypeInfo().getVersion();
        boolean found = false;
        String oracleHome;
        File oracleExe = new File(oracleExePath);
        File binDirectory = oracleExe.getParentFile();
        if (binDirectory.getName().equals("bin")) {
            oracleHome = binDirectory.getParent();
        } else {
            throw new PluginException("Unable to locate correct oracle home: " + binDirectory.getParent());
        }

        // Make sure that oracle exists, and is a normal file
        if (oracleExe.exists() && binDirectory.isDirectory()) {
            if (hasExe(binDirectory, "adrci")) {
                found = version.equals(VERSION_11g);
            } else if (hasExe(binDirectory, "trcsess") || hasFile(binDirectory, "orasql10.")) {
                found = version.equals(VERSION_10g);
            } else if (hasExe(binDirectory, "dgmgrl")) {
                found = version.equals(VERSION_9i);
            } else {
                //assume Oracle 8i
                found = version.equals(VERSION_8i);
            }
        }

        if (found) {
            ConfigResponse productConfig = new ConfigResponse();
            oracleServer = createServerResource(oracleHome);
            oracleServer.setIdentifier(oracleHome);

            // Set custom properties
            ConfigResponse cprop = new ConfigResponse();
            cprop.setValue("version", version);
            oracleServer.setCustomProperties(cprop);
            setListeningPorts(productConfig);
            setProductConfig(oracleServer, productConfig);
            if (!version.equals(VERSION_9i) && !version.equals(VERSION_8i)) {
                oracleServer.setControlConfig();
            }
            if (configureProperties(productConfig)) {
                oracleServer.setMeasurementConfig();
            }
        }

        // HHQ-3577 allow listener names in tnsnames.ora to be used in the url
        String fs = File.separator;
        String tnsDir = getTnsNamesDir(oracleHome, "network" + fs + "admin" + fs + "tnsnames.ora");
        if (log.isDebugEnabled())
            log.debug("using tns dir as " + tnsDir);
        System.setProperty("oracle.net.tns_admin", tnsDir);

        return oracleServer;
    }

    private void setListeningPorts(ConfigResponse productConfig) {
        Set<Long> allPids = new HashSet<Long>();
        for (long pid : getPids(PTQL_QUERY)) {
            allPids.add(pid);
        }
        for (long pid : getPids(TNS_PTQL_QUERY)) {
            allPids.add(pid);
        }
        populateListeningPorts(allPids, productConfig);
    }

    // Auto-scan.. Does process scan first, falls back to oratab
    public List getServerResources(ConfigResponse platformConfig) throws PluginException {
        List servers = new ArrayList();

        // First do process table scan
        List paths = getServerProcessList();
        for (int i = 0; i < paths.size(); i++) {
            String pathToOracleExe = (String) paths.get(i);
            log.debug("Found oracle process path in process scan = " + pathToOracleExe);
            ServerResource oracleServer = getOracleServer(pathToOracleExe);
            if (oracleServer != null) {
                servers.add(oracleServer);
            }
        }

        // If nothing found, try parsing /etc/oratab
        if (servers.size() == 0 && !isWin32()) {
            try {
                String line;
                BufferedReader in = new BufferedReader(new FileReader(ORATAB));
                while ((line = in.readLine()) != null) {
                    // Check for empty or commented out lines
                    if (line.length() == 0 || line.startsWith("#")) {
                        continue;
                    }
                    // Ensure format
                    int x1, x2;
                    x1 = line.indexOf(':');
                    x2 = line.indexOf(':', x1 + 1);
                    if (x1 != -1 && x2 != -1) {
                        String oraSid = line.substring(0, x1);
                        log.debug("Found oracle sid = " + oraSid);
                        String oraHome = line.substring(x1 + 1, x2);
                        log.debug("Found ORACLE_HOME in /etc/oratab =" + oraHome);
                        //getServerList expects the oracle exe path
                        // the path below should exist off of the ORACLE_HOME
                        String oracleExePath = oraHome + File.separator + "bin" + File.separator + "oracle";
                        ServerResource oracleServer = getOracleServer(oracleExePath);
                        // This will automatically add the ORACLE_SID from the oratab file to the jdbcUrl.

                        if (oracleServer != null) {
                            ConfigResponse productConfig = oracleServer.getProductConfig();
                            productConfig.setValue("jdbcUrl", "jdbc:oracle:thin:@localhost:1521:" + oraSid);
                            setListeningPorts(productConfig);
                            oracleServer.setProductConfig(productConfig);
                            servers.add(oracleServer);
                        }
                    }
                }
            } catch (FileNotFoundException e) {
                //Ok, no oracle installed.
            } catch (IOException e) {
                log.error("Error parsing oratab: " + e);
            }
        }

        return servers;
    }

    // File scan
    public List getServerResources(ConfigResponse platformConfig, String path) throws PluginException {
        List serverResources = new ArrayList();
        ServerResource oracleServer = getOracleServer(path);
        if (oracleServer != null) {
            serverResources.add(getOracleServer(path));
        }
        return serverResources;
    }

    // Discover Oracle services
    protected List discoverServices(ConfigResponse config) throws PluginException {
        // HHQ-3577 allow listener names in tnsnames.ora to be used in the url
        String tnsDir = getTnsNamesDir(config.getValue(ProductPlugin.PROP_INSTALLPATH),
                config.getValue(PROP_TNSNAMES));
        if (log.isDebugEnabled())
            log.debug("using tns dir as " + tnsDir);
        System.setProperty("oracle.net.tns_admin", tnsDir);

        String url = config.getValue(OracleMeasurementPlugin.PROP_URL);
        if (url == null) {
            log.warn("No value for config property " + OracleMeasurementPlugin.PROP_URL
                    + ", no services will be discovered.");
            return null;
        }

        String user = config.getValue(OracleMeasurementPlugin.PROP_USER);
        if (user == null) {
            log.info("No value for config property " + OracleMeasurementPlugin.PROP_USER);
        }

        String pass = config.getValue(OracleMeasurementPlugin.PROP_PASSWORD);
        if (pass == null) {
            log.info("No value for config property " + OracleMeasurementPlugin.PROP_PASSWORD);
        }

        ArrayList services = new ArrayList();
        Connection conn = null;
        Statement stmt = null;
        try {
            String instance = url.substring(url.lastIndexOf(':') + 1);
            conn = DriverManager.getConnection(url, user, pass);
            stmt = conn.createStatement();
            services.addAll(getUserServices(stmt, instance));
            services.addAll(getTablespaceServices(stmt, instance));
            // turning this off by default.
            // There are too many table that this will discover
            // most of which the user probably won't care about.
            // Also work needs to be done by the user to enable
            // scheduled control actions to do an Anaylze per table
            // so that the system info gets updated before the
            // size is calc'd.
            //services.addAll(getSegmentServices(stmt, instance));
            services.addAll(getProcessServices(config));
            // this requires extra config on the user side to set ORACLE_HOME
            // env var, so to avoid confusion disabling
            //services.addAll(getTnsServices(config));
            setCustomProps(stmt);
        } catch (SQLException e) {
            // Try to do some investigation of what went wrong
            if (e.getMessage().indexOf("table or view does not exist") != -1) {
                log.error("System table does not exist, make sure that "
                        + " the Oracle user specified has the correct "
                        + " privileges.  See the HQ server configuration " + " page for more information");
                return services;
            }
            // Otherwise, dump the error.
            throw new PluginException("Error querying for Oracle " + "services: " + e.getMessage(), e);
        } finally {
            DBUtil.closeJDBCObjects(log, conn, stmt, null);
        }
        return services;
    }

    private void setCustomProps(Statement stmt) throws SQLException {
        ResultSet rs = null;
        try {
            // Query for server inventory properties
            ConfigResponse props = new ConfigResponse();
            rs = stmt.executeQuery(VERSION_QUERY);
            if (rs != null && rs.next()) {
                String version = rs.getString(1);
                props.setValue("version", version);
            }
            setCustomProperties(props);
        } finally {
            DBUtil.closeResultSet(log, rs);
        }
    }

    private List getUserServices(Statement stmt, String instance) throws SQLException {
        // Discover the user instances, for user instances to be
        // discovered, the user must be connected to the database.
        List rtn = new ArrayList();
        ResultSet rs = null;
        try {
            // Set server description
            setDescription("Oracle " + instance + " database instance");

            // Discover user instances
            ArrayList users = new ArrayList();
            rs = stmt.executeQuery(USER_QUERY);
            while (rs != null && rs.next()) {
                String username = rs.getString(1);
                users.add(username);
            }
            rs.close();
            for (int i = 0; i < users.size(); i++) {
                String username = (String) users.get(i);
                ServiceResource service = new ServiceResource();
                service.setType(this, USER_INSTANCE);
                service.setServiceName(username);
                service.setDescription("User of the " + instance + " database instance");
                ConfigResponse productConfig = new ConfigResponse();
                ConfigResponse metricConfig = new ConfigResponse();
                productConfig.setValue(OracleMeasurementPlugin.PROP_USERNAME, username);
                service.setProductConfig(productConfig);
                service.setMeasurementConfig(metricConfig);
                // Query for service inventory properties
                rs = stmt.executeQuery(DBA_USER_QUERY + "'" + username + "'");
                if (rs != null && rs.next()) {
                    ConfigResponse svcProps = new ConfigResponse();
                    svcProps.setValue("status", rs.getString("ACCOUNT_STATUS"));
                    svcProps.setValue("default_tablespace", rs.getString("DEFAULT_TABLESPACE"));
                    svcProps.setValue("temp_tablespace", rs.getString("TEMPORARY_TABLESPACE"));
                    service.setCustomProperties(svcProps);
                }
                rtn.add(service);
            }
        } finally {
            DBUtil.closeResultSet(log, rs);
        }
        return rtn;
    }

    private List getSegmentServices(Statement stmt, String instance) throws SQLException {
        List rtn = new ArrayList();
        ResultSet rs = null;
        try {
            // Discover tables
            rs = stmt.executeQuery(SEGMENT_QUERY);
            int segment_col = rs.findColumn("SEGMENT_NAME");
            int ts_col = rs.findColumn("TABLESPACE_NAME");
            while (rs != null && rs.next()) {
                String segment = rs.getString(segment_col);
                String tablespace = rs.getString(ts_col);
                ServiceResource service = new ServiceResource();
                service.setType(this, SEGMENT);
                service.setServiceName(segment);
                service.setDescription("Segment in the " + instance + " database instance");
                ConfigResponse productConfig = new ConfigResponse();
                ConfigResponse metricConfig = new ConfigResponse();
                productConfig.setValue(OracleMeasurementPlugin.PROP_SEGMENT, segment);
                productConfig.setValue(OracleMeasurementPlugin.PROP_TABLESPACE, tablespace);
                service.setProductConfig(productConfig);
                service.setMeasurementConfig(metricConfig);
                service.setControlConfig();
                rtn.add(service);
            }
        } finally {
            DBUtil.closeResultSet(log, rs);
        }
        return rtn;
    }

    private List getTablespaceServices(Statement stmt, String instance) throws SQLException {
        List rtn = new ArrayList();
        ResultSet rs = null;
        try {
            // Discover tablespaces
            rs = stmt.executeQuery(TABLESPACE_QUERY);
            int ts_col = rs.findColumn("TABLESPACE_NAME");
            while (rs != null && rs.next()) {
                String tablespace = rs.getString(ts_col);
                ServiceResource service = new ServiceResource();
                service.setType(this, TABLESPACE);
                service.setServiceName(tablespace);
                service.setDescription("Tablespace on the " + instance + " database instance");
                ConfigResponse productConfig = new ConfigResponse();
                ConfigResponse metricConfig = new ConfigResponse();
                productConfig.setValue(OracleMeasurementPlugin.PROP_TABLESPACE, tablespace);
                service.setProductConfig(productConfig);
                service.setMeasurementConfig(metricConfig);
                ConfigResponse svcProps = new ConfigResponse();
                // 9i and 10g only
                if (!getTypeInfo().getVersion().equals(VERSION_8i)) {
                    svcProps.setValue("block_size", rs.getString("BLOCK_SIZE"));
                    svcProps.setValue("allocation_type", rs.getString("ALLOCATION_TYPE"));
                    svcProps.setValue("space_management", rs.getString("SEGMENT_SPACE_MANAGEMENT"));
                }
                svcProps.setValue("contents", rs.getString("CONTENTS"));
                svcProps.setValue("logging", rs.getString("LOGGING"));
                service.setCustomProperties(svcProps);
                rtn.add(service);
            }
        } finally {
            DBUtil.closeResultSet(log, rs);
        }
        return rtn;
    }

    private List getProcessServices(ConfigResponse config) {
        List rtn = new ArrayList();
        String ptql = config.getValue(PROP_PROC_PTQL);
        if (log.isDebugEnabled())
            log.debug("using ptql, " + ptql + ", to retrieve processes");
        List processes = getProcesses(ptql);
        for (Iterator i = processes.iterator(); i.hasNext();) {
            String process = (String) i.next();
            if (log.isDebugEnabled())
                log.debug("adding Process Metrics " + process + " service");
            ServiceResource service = new ServiceResource();
            service.setType(this, "Process Metrics");
            service.setServiceName(process + " process");
            ConfigResponse productConfig = new ConfigResponse();
            ptql = "State.Name.eq=oracle,Args.0.sw=" + process;
            productConfig.setValue("process.query", ptql);
            service.setProductConfig(productConfig);
            service.setMeasurementConfig();
            rtn.add(service);
        }
        return rtn;
    }

    private List getProcesses(String ptql) {
        long[] pids = getPids(ptql);

        List rtn = new ArrayList();
        for (int i = 0; i < pids.length; i++) {
            String[] args = getProcArgs(pids[i]);
            if (args.length == 0 || args[0] == null) {
                continue;
            }
            rtn.add(args[0]);
        }
        return rtn;
    }

    private String getTnsNamesDir(String installpath, String tnsnames) {
        if (installpath == null || tnsnames == null) {
            return "";
        }
        String fs = File.separator;
        String[] toks = tnsnames.split(Pattern.quote(fs));
        StringBuilder rtn = new StringBuilder();
        for (int i = 0; i < toks.length - 1; i++) {
            rtn.append(toks[i]).append(fs);
        }
        return installpath + fs + rtn.toString();
    }

    private List getTnsServices(ConfigResponse config) {
        String line;
        BufferedReader reader = null;
        String tnsnames = config.getValue(PROP_TNSNAMES),
                installpath = config.getValue(ProductPlugin.PROP_INSTALLPATH);
        List rtn = new ArrayList();
        try {
            String fs = File.separator;
            if (log.isDebugEnabled()) {
                log.debug("READING tnsnames.ora FILE: " + installpath + fs + tnsnames);
            }
            reader = new BufferedReader(new FileReader(installpath + fs + tnsnames));
            while (null != (line = reader.readLine())) {
                if (_serviceNameEx.matcher(line).find()) {
                    String[] toks = line.split("=");
                    if (toks[1] == null)
                        continue;
                    String tnslistener = toks[1].replaceAll("\\s*\\)", "").trim();
                    if (log.isDebugEnabled())
                        log.debug("Configuring TNS Listener " + tnslistener);
                    ServiceResource service = new ServiceResource();
                    service.setType(this, "TNS Ping");
                    service.setServiceName(tnslistener + " TNS Ping");
                    ConfigResponse productConfig = new ConfigResponse();
                    productConfig.setValue("tnslistener", tnslistener);
                    service.setProductConfig(productConfig);
                    service.setMeasurementConfig();
                    rtn.add(service);
                }
            }
        } catch (IOException e) {
            log.error("Error reading " + tnsnames);
        } finally {
            close(reader);
        }
        return rtn;
    }

    private void close(Reader reader) {
        if (reader == null)
            return;
        try {
            reader.close();
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }
    }

    private void populateListeningPorts(Set<Long> pids, ConfigResponse productConfig) {
        try {
            Class du = Class.forName("org.hyperic.hq.product.DetectionUtil");
            Method plp = du.getMethod("populateListeningPorts", long.class, ConfigResponse.class);
            plp.invoke(null, pids, productConfig);
        } catch (ClassNotFoundException ex) {
            log.debug("[populateListeningPorts] Class 'DetectionUtil' not found", ex);
        } catch (NoSuchMethodException ex) {
            log.debug("[populateListeningPorts] Method 'populateListeningPorts' not found", ex);
        } catch (Exception ex) {
            log.debug("[populateListeningPorts] Problem with Method 'populateListeningPorts'", ex);
        }
    }
}