/*
* RHQ Management Platform
* Copyright (C) 2005-2014 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package org.rhq.plugins.postgres;
import static java.lang.Boolean.TRUE;
import static org.rhq.core.domain.configuration.ConfigurationUpdateStatus.FAILURE;
import static org.rhq.core.domain.configuration.ConfigurationUpdateStatus.SUCCESS;
import static org.rhq.core.domain.measurement.AvailabilityType.DOWN;
import static org.rhq.core.domain.measurement.AvailabilityType.UP;
import static org.rhq.plugins.database.DatabasePluginUtil.getNumericQueryValues;
import static org.rhq.plugins.database.DatabasePluginUtil.getSingleNumericQueryValue;
import static org.rhq.plugins.database.DatabasePluginUtil.safeClose;
import static org.rhq.plugins.postgres.PostgresUserComponent.ResourceConfig.CAN_CREATE_DATABASE_OBJECTS;
import static org.rhq.plugins.postgres.PostgresUserComponent.ResourceConfig.CAN_CREATE_ROLES;
import static org.rhq.plugins.postgres.PostgresUserComponent.ResourceConfig.CAN_LOGIN;
import static org.rhq.plugins.postgres.PostgresUserComponent.ResourceConfig.CAN_UPDATE_SYSTEM_CATALOGS_DIRECTLY;
import static org.rhq.plugins.postgres.PostgresUserComponent.ResourceConfig.CONNECTION_LIMIT;
import static org.rhq.plugins.postgres.PostgresUserComponent.ResourceConfig.INHERIT_RIGHTS;
import static org.rhq.plugins.postgres.PostgresUserComponent.ResourceConfig.PASSWORD;
import static org.rhq.plugins.postgres.PostgresUserComponent.ResourceConfig.SUPERUSER;
import static org.rhq.plugins.postgres.PostgresUserComponent.ResourceConfig.USER;
import static org.rhq.plugins.postgres.PostgresUserDiscoveryComponent.OID_PREFIX;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Map;
import java.util.Set;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.configuration.PropertySimple;
import org.rhq.core.domain.measurement.AvailabilityType;
import org.rhq.core.domain.measurement.MeasurementDataNumeric;
import org.rhq.core.domain.measurement.MeasurementReport;
import org.rhq.core.domain.measurement.MeasurementScheduleRequest;
import org.rhq.core.pluginapi.configuration.ConfigurationFacet;
import org.rhq.core.pluginapi.configuration.ConfigurationUpdateReport;
import org.rhq.core.pluginapi.inventory.DeleteResourceFacet;
import org.rhq.core.pluginapi.inventory.InvalidPluginConfigurationException;
import org.rhq.core.pluginapi.inventory.ResourceContext;
import org.rhq.core.pluginapi.measurement.MeasurementFacet;
import org.rhq.plugins.database.ConnectionPoolingSupport;
import org.rhq.plugins.database.DatabaseComponent;
import org.rhq.plugins.database.PooledConnectionProvider;
/**
* @author Greg Hinkle
*/
public class PostgresUserComponent implements DatabaseComponent<PostgresServerComponent<?>>, ConnectionPoolingSupport,
MeasurementFacet, ConfigurationFacet, DeleteResourceFacet {
/**
* @deprecated as of RHQ 4.12. Shouldn't have been exposed.
*/
@Deprecated
public enum UpdateType {
CREATE, ALTER, DROP
}
static class ResourceConfig {
static final String USER = "user";
static final String CAN_LOGIN = "canLogin";
static final String PASSWORD = "password";
static final String INHERIT_RIGHTS = "inheritRights";
static final String SUPERUSER = "superuser";
static final String CAN_CREATE_DATABASE_OBJECTS = "canCreateDatabaseObjects";
static final String CAN_CREATE_ROLES = "canCreateRoles";
static final String CAN_UPDATE_SYSTEM_CATALOGS_DIRECTLY = "canModifyCatalogDirectly";
static final String CONNECTION_LIMIT = "connectionLimit";
private ResourceConfig() {
// Constants class
}
}
static final String FIND_ROLE_BY_OID = "select * from pg_roles where oid = ?";
static final String COUNT_ROLES_WITH_OID = "select count(*) from pg_roles where oid = ?";
// See http://wiki.postgresql.org/wiki/What%27s_new_in_PostgreSQL_9.2#pg_stat_activity_and_pg_stat_replication.27s_definitions_have_changed
static final String FIND_STAT_ACTIVITY_BY_OID_PRE_PG_9_2 = "select " //
+ "(select count(*) from pg_stat_activity where usesysid = ? and current_query != '<IDLE>') as active, " //
+ "(select count(*) from pg_stat_activity where usesysid = ?) as total";
static final String FIND_STAT_ACTIVITY_BY_OID = "select " //
+ "(select count(*) from pg_stat_activity where usesysid = ? and state = 'active') as active, " //
+ "(select count(*) from pg_stat_activity where usesysid = ?) as total";
static final String UPDATE_PG_AUTHID_SET_ROLCATUPDATE_WHERE_OID = "update pg_authid " //
+ "set rolcatupdate=? where oid=?";
private ResourceContext<PostgresServerComponent<?>> resourceContext;
private long pgRoleOid;
@Override
public void start(ResourceContext<PostgresServerComponent<?>> resourceContext) {
this.resourceContext = resourceContext;
try {
pgRoleOid = Long.parseLong(resourceContext.getResourceKey().substring(OID_PREFIX.length()));
} catch (Exception e) {
throw new InvalidPluginConfigurationException("Invalid resource key [" + resourceContext.getResourceKey()
+ "]");
}
}
@Override
public void stop() {
resourceContext = null;
}
@Override
public boolean supportsConnectionPooling() {
return true;
}
@Override
public PooledConnectionProvider getPooledConnectionProvider() {
return resourceContext.getParentResourceComponent().getPooledConnectionProvider();
}
/**
* @deprecated as of RHQ 4.12. Shouldn't have been exposed.
*/
@Deprecated
public String getUserName() {
try {
String result = getUserNameInternal();
if (result == null) {
throw new RuntimeException("Found no user with oid [" + pgRoleOid + "]");
}
return result;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
private String getUserNameInternal() throws SQLException {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
connection = getPooledConnectionProvider().getPooledConnection();
statement = connection.prepareStatement(FIND_ROLE_BY_OID);
statement.setLong(1, pgRoleOid);
resultSet = statement.executeQuery();
if (resultSet.next()) {
return resultSet.getString("rolname");
}
return null;
} finally {
safeClose(connection, statement, resultSet);
}
}
@Override
public AvailabilityType getAvailability() {
if (getSingleNumericQueryValue(this, COUNT_ROLES_WITH_OID, pgRoleOid) == 1) {
return UP;
} else {
return DOWN;
}
}
@Override
public Connection getConnection() {
return this.resourceContext.getParentResourceComponent().getConnection();
}
@Override
public void removeConnection() {
this.resourceContext.getParentResourceComponent().removeConnection();
}
@Override
public void getValues(MeasurementReport report, Set<MeasurementScheduleRequest> metrics) throws Exception {
Map<String, Double> values;
if (resourceContext.getParentResourceComponent().isVersionGreaterThan92()) {
values = getNumericQueryValues(this, FIND_STAT_ACTIVITY_BY_OID, pgRoleOid, pgRoleOid);
} else {
values = getNumericQueryValues(this, FIND_STAT_ACTIVITY_BY_OID_PRE_PG_9_2, pgRoleOid, pgRoleOid);
}
for (MeasurementScheduleRequest request : metrics) {
report.addData(new MeasurementDataNumeric(request, values.get(request.getName())));
}
}
@Override
public Configuration loadResourceConfiguration() throws Exception {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
connection = getPooledConnectionProvider().getPooledConnection();
statement = connection.prepareStatement(FIND_ROLE_BY_OID);
statement.setLong(1, pgRoleOid);
resultSet = statement.executeQuery();
resultSet.next();
Configuration config = new Configuration();
config.put(new PropertySimple(USER, resultSet.getString("rolname")));
config.put(new PropertySimple(CAN_LOGIN, resultSet.getBoolean("rolcanlogin")));
config.put(new PropertySimple(INHERIT_RIGHTS, resultSet.getBoolean("rolinherit")));
boolean rolsuper = resultSet.getBoolean("rolsuper");
config.put(new PropertySimple(SUPERUSER, rolsuper));
if (rolsuper) {
config.put(new PropertySimple(CAN_CREATE_DATABASE_OBJECTS, String.valueOf(true)));
config.put(new PropertySimple(CAN_CREATE_ROLES, String.valueOf(true)));
} else {
config.put(new PropertySimple(CAN_CREATE_DATABASE_OBJECTS, resultSet.getBoolean("rolcreatedb")));
config.put(new PropertySimple(CAN_CREATE_ROLES, resultSet.getBoolean("rolcreaterole")));
}
config.put(new PropertySimple(CAN_UPDATE_SYSTEM_CATALOGS_DIRECTLY, resultSet.getBoolean("rolcatupdate")));
config.put(new PropertySimple(CONNECTION_LIMIT, resultSet.getInt("rolconnlimit")));
return config;
} finally {
safeClose(connection, statement, resultSet);
}
}
@Override
public void updateResourceConfiguration(ConfigurationUpdateReport report) {
Configuration config = report.getConfiguration();
String userName = config.getSimpleValue(USER);
if (userName == null || userName.trim().isEmpty()) {
report.setStatus(FAILURE);
report.setErrorMessage("User name is missing");
return;
}
String currentUserName;
try {
currentUserName = getUserNameInternal();
} catch (SQLException e) {
report.setStatus(FAILURE);
report.setErrorMessageFromThrowable(e);
return;
}
if (!userName.equals(currentUserName)) {
Connection connection = null;
Statement statement = null;
try {
connection = getPooledConnectionProvider().getPooledConnection();
statement = connection.createStatement();
statement.executeUpdate("alter role " + currentUserName + " rename to " + userName);
} catch (SQLException e) {
report.setStatus(FAILURE);
report.setErrorMessageFromThrowable(e);
return;
} finally {
safeClose(connection, statement);
}
}
Connection connection = null;
PreparedStatement statement = null;
try {
connection = getPooledConnectionProvider().getPooledConnection();
statement = connection.prepareStatement(buildUserSql(config, /*existing user*/false));
statement.executeUpdate();
} catch (SQLException e) {
report.setStatus(FAILURE);
report.setErrorMessageFromThrowable(e);
return;
} finally {
safeClose(connection, statement);
}
try {
connection = getPooledConnectionProvider().getPooledConnection();
statement = connection.prepareStatement(UPDATE_PG_AUTHID_SET_ROLCATUPDATE_WHERE_OID);
statement.setBoolean(
1,
Boolean.valueOf(config.getSimpleValue(SUPERUSER))
&& Boolean.valueOf(config.getSimpleValue(CAN_UPDATE_SYSTEM_CATALOGS_DIRECTLY)));
statement.setLong(2, pgRoleOid);
statement.executeUpdate();
} catch (SQLException e) {
report.setStatus(FAILURE);
report.setErrorMessageFromThrowable(e);
return;
} finally {
safeClose(connection, statement);
}
report.setStatus(SUCCESS);
}
static String buildUserSql(Configuration config, boolean newUser) {
StringBuilder sql = new StringBuilder();
if (newUser) {
sql.append("create");
} else {
sql.append("alter");
}
sql.append(" user ").append(config.getSimpleValue(USER));
sql.append(Boolean.valueOf(config.getSimpleValue(CAN_LOGIN)) ? " LOGIN" : " NOLOGIN");
String password = config.getSimpleValue(PASSWORD);
if (password != null && !password.trim().isEmpty()) {
sql.append(" password '").append(password).append("'");
}
sql.append(Boolean.valueOf(config.getSimpleValue(INHERIT_RIGHTS)) ? " INHERIT" : " NOINHERIT");
if (Boolean.valueOf(config.getSimpleValue(SUPERUSER))) {
// Do not add implied CREATEROLE and CREATEDB permissions otherwise Postgres rejects the query
sql.append(" SUPERUSER");
} else {
sql.append(" NOSUPERUSER");
sql.append(Boolean.valueOf(config.getSimpleValue(CAN_CREATE_DATABASE_OBJECTS)) ? " CREATEDB"
: " NOCREATEDB");
sql.append(Boolean.valueOf(config.getSimpleValue(CAN_CREATE_ROLES)) ? " CREATEROLE" : " NOCREATEROLE");
}
sql.append(" connection limit ");
PropertySimple connectionLimit = config.getSimple(CONNECTION_LIMIT);
if (connectionLimit != null) {
sql.append(connectionLimit.getIntegerValue());
} else {
sql.append("-1");
}
return sql.toString();
}
/**
* @deprecated as of RHQ 4.12. Shouldn't have been exposed.
*/
public static String getUserSQL(Configuration config, UpdateType type) {
String result;
switch (type) {
case CREATE:
result = buildUserSql(config, /*new user*/true);
break;
case ALTER:
result = buildUserSql(config, /*existing user*/false);
break;
case DROP:
result = "drop user " + config.getSimpleValue(USER);
break;
default:
result = null;
}
return result;
}
@Override
public void deleteResource() throws Exception {
String userName = getUserNameInternal();
Connection connection = null;
Statement statement = null;
try {
connection = getPooledConnectionProvider().getPooledConnection();
statement = connection.createStatement();
statement.executeUpdate("drop role " + userName);
} finally {
safeClose(connection, statement);
}
}
}