/*
* 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.postgresql;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
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.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hyperic.hq.product.AutoServerDetector;
import org.hyperic.hq.product.PluginException;
import org.hyperic.hq.product.ServerDetector;
import org.hyperic.hq.product.ServerResource;
import org.hyperic.hq.product.ServiceResource;
import org.hyperic.util.config.ConfigResponse;
import org.hyperic.util.jdbc.DBUtil;
public class PostgreSQLServerDetector extends ServerDetector implements AutoServerDetector {
private Log log = LogFactory.getLog(PostgreSQLServerDetector.class);
private static final String PTQL_QUERY = "State.Name.re=post(master|gres),State.Name.Pne=$1,Args.0.re=.*post(master|gres)(.exe)?$";
protected static final String DB_QUERY = "SELECT datname FROM pg_database WHERE datistemplate IS FALSE AND datallowconn IS TRUE";
private static final String TABLE_QUERY = "SELECT relname, schemaname FROM pg_stat_user_tables";
private static final String INDEX_QUERY = "SELECT indexrelname, schemaname FROM pg_stat_user_indexes";
public List getServerResources(ConfigResponse platformConfig) throws PluginException {
List servers = new ArrayList();
long[] pids = getPids(PTQL_QUERY);
log.debug("[getServerProcessList] pids.length=" + pids.length);
for (int i = 0; i < pids.length; i++) {
String exe = getProcExe(pids[i]);
List args = Arrays.asList(getProcArgs(pids[i]));
log.debug("[getServerProcessList] pid=" + pids[i] + " exec=" + exe + " args=" + args);
if (exe != null) {
String version = getVersion(exe);
String expectedVersion = getTypeProperty("version");
String pgData = getArgument("-D", args);
try {
pgData = new File(pgData).getCanonicalPath();
} catch (IOException ex) {
log.debug(ex, ex);
}
boolean correctVersion = Pattern.compile(expectedVersion).matcher(version).find();
log.debug("[getServerProcessList] version=" + version + " correctVersion=" + correctVersion);
//check for vPostgres and HQ
final boolean isHQ = exe.indexOf("hqdb") != -1;
if (correctVersion) {
File vflicense = new File(new File(exe).getParent(), "vflicense");
if (getTypeInfo().getName().startsWith("vPostgres")) {
correctVersion = vflicense.exists();
} else if (getTypeInfo().getName().startsWith("HQ")) {
correctVersion = isHQ;
} else {
correctVersion = !(vflicense.exists() || isHQ);
}
}
if (correctVersion) {
ServerResource server = createServerResource(exe);
server.setMeasurementConfig();
if (!isHQ) {
server.setControlConfig();
}
server.setIdentifier(server.getIdentifier() + "$" + pgData);
ConfigResponse cprop = new ConfigResponse();
cprop.setValue("version", version);
setCustomProperties(server, cprop);
ConfigResponse productConfig = prepareConfig(pgData, args);
populateListeningPorts(pids[i], productConfig, true);
setProductConfig(server, productConfig);
String basename = getPlatformName() + " " + getTypeInfo().getName();
server.setName(prepareName(basename + " " + (isHQ ? PostgreSQL.HQ_SERVER_NAME : PostgreSQL.SERVER_NAME), server.getProductConfig(), null));
servers.add(server);
} else {
log.debug("[getServerProcessList] pid='" + pids[i] + "' Is not a '" + getTypeInfo().getName() + "'");
}
}
}
return servers;
}
@Override
protected List discoverServices(ConfigResponse serverConfig) throws PluginException {
log.debug("[discoverServices] config=" + serverConfig);
boolean isHQ = getTypeInfo().getName().startsWith("HQ");
ArrayList services = new ArrayList();
String user = serverConfig.getValue(PostgreSQL.PROP_USER);
String pass = serverConfig.getValue(PostgreSQL.PROP_PASS);
String url = PostgreSQL.prepareUrl(serverConfig.toProperties(), null);
try {
Class.forName(ResourceMeasurement.JDBC_DRIVER);
} catch (ClassNotFoundException e) {
throw new PluginException("Unable to load JDBC Driver: " + e.getMessage());
}
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
List<String> dataBases = new ArrayList<String>();
try {
conn = DriverManager.getConnection(url, user, pass);
stmt = conn.createStatement();
rs = stmt.executeQuery(DB_QUERY);
while (rs != null && rs.next()) {
dataBases.add(rs.getString(1));
}
} catch (SQLException e) {
throw new PluginException("Error querying for DataBases: " + e.getMessage(), e);
} finally {
DBUtil.closeJDBCObjects(this.log, conn, stmt, rs);
}
Pattern table_reg = null;
boolean table_all = serverConfig.getValue(PostgreSQL.PROP_TABLE_REG).equalsIgnoreCase("ALL");
boolean table_off = serverConfig.getValue(PostgreSQL.PROP_TABLE_REG).equalsIgnoreCase("OFF");
if (!table_all && !table_off) {
table_reg = Pattern.compile(serverConfig.getValue(PostgreSQL.PROP_TABLE_REG));
}
Pattern index_reg = null;
boolean index_all = serverConfig.getValue(PostgreSQL.PROP_INDEX_REG).equalsIgnoreCase("ALL");
boolean index_off = serverConfig.getValue(PostgreSQL.PROP_INDEX_REG).equalsIgnoreCase("OFF");
if (!index_all && !index_off) {
index_reg = Pattern.compile(serverConfig.getValue(PostgreSQL.PROP_INDEX_REG));
}
log.debug("[discoverServices] databases: " + dataBases);
for (int i = 0; i < dataBases.size(); i++) {
String dataBase = dataBases.get(i);
ServiceResource db = new ServiceResource();
db.setType(this, "DataBase");
ConfigResponse dbPC = new ConfigResponse();
dbPC.setValue(PostgreSQL.PROP_DB, dataBase);
db.setProductConfig(dbPC);
db.setMeasurementConfig();
db.setControlConfig();
db.setServiceName(prepareName(isHQ ? PostgreSQL.HQ_DB_NAME : PostgreSQL.DB_NAME, serverConfig, dbPC));
services.add(db);
try {
conn = DriverManager.getConnection(PostgreSQL.prepareUrl(serverConfig.toProperties(), dataBase), user, pass);
stmt = conn.createStatement();
rs = stmt.executeQuery(TABLE_QUERY);
while (rs != null && rs.next()) {
String tablename = rs.getString(1);
String schemaname = rs.getString(2);
if (isValidName(tablename, table_all, table_off, table_reg)) {
ServiceResource service = new ServiceResource();
service.setType(this, "Table");
ConfigResponse tablePC = new ConfigResponse();
tablePC.setValue(PostgreSQL.PROP_DB, dataBase);
tablePC.setValue(PostgreSQL.PROP_TABLE, tablename);
tablePC.setValue(PostgreSQL.PROP_SCHEMA, schemaname);
service.setProductConfig(tablePC);
service.setMeasurementConfig();
service.setControlConfig();
service.setServiceName(prepareName(isHQ ? PostgreSQL.HQ_TABLE_NAME : PostgreSQL.TABLE_NAME, serverConfig, tablePC));
services.add(service);
}
}
DBUtil.closeJDBCObjects(this.log, null, stmt, rs);
stmt = conn.createStatement();
rs = stmt.executeQuery(INDEX_QUERY);
while (rs != null && rs.next()) {
String indexname = rs.getString(1);
String schemaname = rs.getString(2);
if (isValidName(indexname, index_all, index_off, index_reg)) {
ServiceResource service = new ServiceResource();
service.setType(this, "Index");
ConfigResponse indexPC = new ConfigResponse();
indexPC.setValue(PostgreSQL.PROP_DB, dataBase);
indexPC.setValue(PostgreSQL.PROP_INDEX, indexname);
indexPC.setValue(PostgreSQL.PROP_SCHEMA, schemaname);
service.setProductConfig(indexPC);
service.setMeasurementConfig();
service.setControlConfig();
service.setServiceName(prepareName(isHQ ? PostgreSQL.INDEX_NAME : PostgreSQL.INDEX_NAME, serverConfig, indexPC));
services.add(service);
}
}
} catch (SQLException e) {
throw new PluginException("Error querying for services: " + e.getMessage(), e);
} finally {
DBUtil.closeJDBCObjects(this.log, conn, stmt, rs);
}
}
return services;
}
private String prepareName(String pattern, ConfigResponse serverConf, ConfigResponse serviceConf) {
List<ConfigResponse> props = new ArrayList<ConfigResponse>();
String res = pattern;
props.add(serverConf);
if (serviceConf != null) {
props.add(serviceConf);
}
for (int i = 0; i < props.size(); i++) {
ConfigResponse cfg = props.get(i);
for (Iterator<String> it = cfg.getKeys().iterator(); it.hasNext();) {
String key = it.next();
String val = cfg.getValue(key);
if (val == null) {
val = "";
}
res = res.replace("${" + key + "}", val);
}
}
return res.trim();
}
private String getVersion(String exec) {
String command[] = {exec, "--version"};
log.debug("[getVersionString] command= '" + Arrays.asList(command) + "'");
String version = "";
try {
Process cmd = Runtime.getRuntime().exec(command);
cmd.getOutputStream().close();
cmd.waitFor();
String out = inputStreamAsString(cmd.getInputStream());
String err = inputStreamAsString(cmd.getErrorStream());
if (log.isDebugEnabled()) {
if (cmd.exitValue() != 0) {
log.error("[getVersionString] exit=" + cmd.exitValue());
log.error("[getVersionString] out=" + out);
log.error("[getVersionString] err=" + err);
} else {
log.debug("[getVersionString] out=" + out);
}
}
version = out;
} catch (InterruptedException ex) {
log.debug("[getVersionString] Error:" + ex.getMessage(), ex);
} catch (IOException ex) {
log.debug("[getVersionString] Error:" + ex.getMessage(), ex);
}
return version;
}
private String inputStreamAsString(InputStream stream) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(stream));
StringBuilder sb = new StringBuilder();
try {
String line;
while ((line = br.readLine()) != null) {
sb.append(line).append("\n");
}
} finally {
br.close();
}
return sb.toString().trim();
}
private String getArgument(String opt, List<String> args) {
String res = "";
int i = args.indexOf(opt);
if (i > -1) {
res = args.get(i + 1);
}
return res.trim();
}
private String getConfiguration(String regex, String config) {
String res = "";
Matcher m = Pattern.compile(regex, Pattern.MULTILINE + Pattern.CASE_INSENSITIVE).matcher(config);
if (m.find()) {
res = m.group(1).trim();
res = res.replaceAll("=", "");
res = res.replaceAll("'", "");
res = res.replaceAll("\"", "");
}
return res.trim();
}
private String loadConfiguration(String pgData) {
String configuration = "";
File configFile = new File(pgData, "postgresql.conf");
if (configFile.exists() && configFile.canRead()) {
FileInputStream in = null;
try {
in = new FileInputStream(configFile);
configuration = inputStreamAsString(in);
} catch (IOException ex) {
log.error("Error reading file '" + configFile + "': " + ex.getMessage(), ex);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException ex) {
log.error("Error closing file '" + configFile + "'");
}
}
}
}
return configuration;
}
private ConfigResponse prepareConfig(String pgData, List<String> args) {
ConfigResponse cf = new ConfigResponse();
cf.setValue(PostgreSQL.PROP_DATA, pgData);
String configuration = loadConfiguration(pgData);
String addr = getConfiguration("^ *listen_addresses([^#]*)", configuration);
if (addr.length() > 0) {
if (addr.equals("*")) {
addr = "localhost";
}
cf.setValue(PostgreSQL.PROP_HOST, addr);
} else {
addr = getArgument("-h", args);
if (addr.length() > 0) {
cf.setValue(PostgreSQL.PROP_HOST, addr);
}
}
String port = getConfiguration("^ *port([^#]*)", configuration);
if (port.length() > 0) {
cf.setValue(PostgreSQL.PROP_PORT, port);
} else {
port = getArgument("-p", args);
if (port.length() > 0) {
cf.setValue(PostgreSQL.PROP_PORT, port);
}
}
return cf;
}
private boolean isValidName(String name, boolean all, boolean off, Pattern reg) {
boolean res;
if (all) {
res = true;
} else if (off) {
res = false;
} else {
res = reg.matcher(name).matches();
}
return res;
}
private void populateListeningPorts(long pid, ConfigResponse productConfig, boolean b) {
try {
Class du = Class.forName("org.hyperic.hq.product.DetectionUtil");
Method plp = du.getMethod("populateListeningPorts", long.class, ConfigResponse.class, boolean.class);
plp.invoke(null, pid, productConfig, b);
} 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);
}
}
}