/* * 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.mysql; import java.io.File; 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.List; import org.hyperic.hq.product.AutoServerDetector; import org.hyperic.hq.product.FileServerDetector; import org.hyperic.hq.product.PluginException; import org.hyperic.hq.product.RegistryServerDetector; import org.hyperic.hq.product.ServerDetector; import org.hyperic.hq.product.ServerResource; import org.hyperic.hq.product.ServiceResource; import org.hyperic.hq.product.ProductPlugin; import org.hyperic.util.config.ConfigResponse; import org.hyperic.sigar.win32.RegistryKey; import org.hyperic.util.jdbc.DBUtil; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class MySQLServerDetector extends ServerDetector implements FileServerDetector, RegistryServerDetector, AutoServerDetector { // Server Types static final String SERVER_NAME = "MySQL"; // Service Types static final String TABLE = "Table"; // Versions static final String VERSION_3 = "3.x"; static final String VERSION_4 = "4.x"; static final String VERSION_5 = "5.x"; // Discovery queries private static final String DATABASE_QUERY = "SHOW DATABASES"; private static final String TABLE_QUERY = "SHOW TABLE STATUS"; private Log log = LogFactory.getLog("MySQLServerDetector"); // likely will only work w/ linux due to permissions // and setting of argv[0] to the full binary path. private static final String PTQL_QUERY = "State.Name.eq=mysqld"; private static final String PTQL_QUERY_WIN32 = "State.Name.eq=mysqld-nt"; private String getProcessQuery() { if (isWin32()) { return PTQL_QUERY_WIN32; } else { return PTQL_QUERY; } } private List getServerValues(String path, String version) throws PluginException { List servers = new ArrayList(); ServerResource server = createServerResource(path); // Avoid clash with HQ 2.0 AI identifier for MySQL databases server.setIdentifier(getTypeInfo().getName() + path); // Set PTQL query ConfigResponse config = new ConfigResponse(); config.setValue("process.query", getProcessQuery()); server.setProductConfig(config); servers.add(server); return servers; } /** * Helper method to discover MySQL servers using the process table */ 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; } // mysqld is usually kept in ${install}/bin or // ${install}/libexec (gentoo). move up one directory // and check for safe_mysqld (3.x) or mysqld_safe (4.x) String installPath = getParentDir(exe, 2); File binDir = new File(installPath, "bin"); // Handle 5.x and 4.x servers File mysqld_safe = new File(binDir, "mysqld_safe"); if (mysqld_safe.exists() && mysqld_safe.isAbsolute()) { servers.add(mysqld_safe.getAbsolutePath()); continue; } // Handle 3.x servers File safe_mysqld = new File(binDir, "safe_mysqld"); if (safe_mysqld.exists() && safe_mysqld.isAbsolute()) { servers.add(safe_mysqld.getAbsolutePath()); continue; } } return servers; } /** * Auto scan */ public List getServerResources(ConfigResponse platformConfig) throws PluginException { List servers = new ArrayList(); List paths = getServerProcessList(); for (int i = 0; i < paths.size(); i++) { String dir = (String) paths.get(i); List found = getServerResources(platformConfig, dir); if (found != null && !found.isEmpty()) { servers.addAll(found); } } return servers; } /** * File scan */ public List getServerResources(ConfigResponse platformConfig, String path) throws PluginException { if (getTypeInfo().getVersion().equals(VERSION_3)) { if (path.endsWith("safe_mysqld")) { path = getParentDir(path, 2); return getServerValues(path, VERSION_3); } } else if (getTypeInfo().getVersion().equals(VERSION_4)) { if (path.endsWith("mysqld_safe")) { path = getParentDir(path, 2); // 4.x versions include isamchk File bindir = new File(path, "bin"); File isamchk = new File(bindir, "isamchk"); File myisamchk = new File(bindir, "myisamchk"); if (isamchk.exists() && !myisamchk.exists()) { return getServerValues(path, VERSION_4); } } } else if (getTypeInfo().getVersion().equals(VERSION_5)) { if (path.endsWith("mysqld_safe")) { path = getParentDir(path, 2); // 5.x no longer includes isamchk File bindir = new File(path, "bin"); File isamchk = new File(bindir, "isamchk"); File myisamchk = new File(bindir, "myisamchk"); if (!isamchk.exists() && myisamchk.exists()) { return getServerValues(path, VERSION_5); } } } else { // Unable to determine MySQL version throw new IllegalArgumentException("Unknown mysql version at: " + path); } return null; } /** * Registry scan */ public List getServerResources(ConfigResponse platformConfig, String path, RegistryKey current) throws PluginException { String version; String name = current.getSubKeyName(); if (name == null) { return null; } if (name.indexOf(SERVER_NAME) == -1) { return null; } if (name.indexOf("3.") != -1) { version = VERSION_3; } else if (name.indexOf("4.") != -1) { version = VERSION_4; } else if (name.indexOf("5.") != -1) { version = VERSION_5; } else { this.log.info("Found unsupported MySQL version: " + name); return null; } // 4.1 if (new File(path).exists()) { return getServerValues(path, version); } // 4.0 // e.g. path=="C:\WINDOWS\IsUninst.exe -fC:\mysql\Unint.isu" int ix = path.indexOf("-f"); if (ix == -1) { return null; } path = getParentDir(path.substring(ix + 2)); return getServerValues(path, version); } /** * Rather than implement discoverServices() we override discoverServers() so * that we can discover multiple databases within a single MySQL instance */ protected List discoverServers(ConfigResponse config) throws PluginException { String url = config.getValue(MySQLMeasurementPlugin.PROP_URL); String user = config.getValue(MySQLMeasurementPlugin.PROP_USER); String pass = config.getValue(MySQLMeasurementPlugin.PROP_PASSWORD); String installpath = config.getValue(ProductPlugin.PROP_INSTALLPATH); this.log.debug("discover using: " + config); if (url == null) { // url might not be configured (java -jar hq-plugin.jar -m discover) return null; } String baseUrl = url.substring(0, url.lastIndexOf("/") + 1); String type = SERVER_NAME + " " + getTypeInfo().getVersion(); List aiservers = new ArrayList(); try { Class.forName(MySQLMeasurementPlugin.JDBC_DRIVER); } catch (ClassNotFoundException e) { // No driver. Should not happen. throw new PluginException("Unable to load JDBC " + "Driver: " + e.getMessage()); } Connection conn = null; Statement stmt = null; ResultSet rs = null; try { conn = DriverManager.getConnection(url, user, pass); stmt = conn.createStatement(); rs = stmt.executeQuery(DATABASE_QUERY); while (rs != null && rs.next()) { String database = rs.getString(1); ServerResource server = new ServerResource(); server.setType(type); server.setInstallPath(installpath); server.setIdentifier(installpath + database); server.setName(getPlatformName() + " " + type + " " + database); // Auto-configure ConfigResponse productConfig = new ConfigResponse(); String dbUrl = baseUrl + database; productConfig.setValue(MySQLMeasurementPlugin.PROP_URL, dbUrl); productConfig.setValue(MySQLMeasurementPlugin.PROP_USER, user); productConfig.setValue(MySQLMeasurementPlugin.PROP_PASSWORD, pass); server.setProductConfig(productConfig); server.setMeasurementConfig(); this.log.debug("found database: " + productConfig); List services = discoverTables(dbUrl, user, pass); for (int i = 0; i < services.size(); i++) { server.addService((ServiceResource) services.get(i)); } aiservers.add(server); } } catch (SQLException e) { throw new PluginException("Error querying for databases " + e.getMessage()); } finally { DBUtil.closeJDBCObjects(this.log, conn, stmt, rs); } return aiservers; } /** * Discover all MySQL Table services */ protected List discoverTables(String url, String user, String pass) throws PluginException { try { Class.forName(MySQLMeasurementPlugin.JDBC_DRIVER); } catch (ClassNotFoundException e) { // No driver. Should not happen. throw new PluginException("Unable to load JDBC " + "Driver: " + e.getMessage()); } ArrayList services = new ArrayList(); Connection conn = null; Statement stmt = null; ResultSet rs = null; // Discover all MySQL tables. try { conn = DriverManager.getConnection(url, user, pass); stmt = conn.createStatement(); rs = stmt.executeQuery(TABLE_QUERY); // Loop detecting MySQL tables while (rs != null && rs.next()) { String tablename = rs.getString(1); String engine = rs.getString(2); ServiceResource service = new ServiceResource(); service.setType(SERVER_NAME + " " + getTypeInfo().getVersion() + " " + TABLE); service.setServiceName(tablename); // Set custom properties (5.x only) if (getTypeInfo().getVersion().equals( MySQLServerDetector.VERSION_5)) { ConfigResponse cprop = new ConfigResponse(); cprop.setValue("Engine", engine); service.setCustomProperties(cprop); } ConfigResponse productConfig = new ConfigResponse(); productConfig.setValue(MySQLMeasurementPlugin.PROP_TABLE, tablename); service.setProductConfig(productConfig); service.setMeasurementConfig(); service.setControlConfig(); services.add(service); } } catch (SQLException e) { throw new PluginException("Error querying for table " + "services: " + e.getMessage()); } finally { DBUtil.closeJDBCObjects(this.log, conn, stmt, rs); } return services; } }