/*
*
* * RHQ Management Platform
* * Copyright (C) 2005-2012 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, version 2, as
* * published by the Free Software Foundation, and/or the GNU Lesser
* * General Public License, version 2.1, also 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 and the GNU Lesser General Public License
* * for more details.
* *
* * You should have received a copy of the GNU General Public License
* * and the GNU Lesser 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.cassandra.schema;
import java.util.Properties;
import java.util.UUID;
import com.datastax.driver.core.exceptions.AuthenticationException;
import com.datastax.driver.core.exceptions.NoHostAvailableException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.rhq.cassandra.schema.exception.InstalledSchemaTooAdvancedException;
import org.rhq.cassandra.schema.exception.InstalledSchemaTooOldException;
import org.rhq.cassandra.schema.exception.SchemaNotInstalledException;
/**
* @author Stefan Negrea
*/
class VersionManager extends AbstractManager {
private static final String SCHEMA_BASE_FOLDER = "schema";
private final Log log = LogFactory.getLog(VersionManager.class);
static enum Task {
Drop("drop"),
Create("create"),
Update("update");
private final String folder;
private Task(String folder){
this.folder = folder;
}
protected String getFolder() {
return SCHEMA_BASE_FOLDER + "/" + this.folder + "/";
}
}
public VersionManager(String username, String password, String[] nodes, int cqlPort, SessionManager sessionManager,
UpdateFolderFactory updateFolderFactory)
throws Exception {
super(username, password, nodes, cqlPort, sessionManager, updateFolderFactory);
}
/**
* Install and update the RHQ schema:
* 1) If the schema does not exist then attempt to create it and then run the updates in order.
* 2) If the schema exists then run the updates in order.
*
* @throws Exception
*/
public void install(Properties properties) throws Exception {
log.info("Preparing to install storage schema");
try {
// Drop the existing connection so we don't use stale session
shutdownClusterConnection();
initClusterSession();
} catch (AuthenticationException e) {
log.debug("Authentication exception. Will now attempt to create the storage schema.");
log.debug(e);
create();
}
update(properties);
}
/**
* Create RHQ schema and make related updates to the Cassandra installation.
*
* @throws Exception
*/
private void create() throws Exception {
UpdateFolder updateFolder = null;
Properties properties = null;
/**
* NOTE: Before applying any schema, we need to create the rhqadmin user. If we have more
* than a single node cluster then we also need to set the RF of the system_auth
* keyspace BEFORE we create the rhqadmin user. If we do not do in this order we will
* get inconsistent reads which will can result in failed authentication.
*/
//1. Execute the creation of RHQ schema, version table, admin user.
try {
//shutdown existing connection
shutdownClusterConnection();
//re-initialize the cluster connection with default cassandra password
initClusterSession(DEFAULT_CASSANDRA_USER, DEFAULT_CASSANDRA_PASSWORD);
updateFolder = updateFolderFactory.newUpdateFolder(Task.Create.getFolder());
properties = new Properties(System.getProperties());
properties.put("replication_factor", calculateNewReplicationFactor() + "");
properties.put("cassandra_user_password", UUID.randomUUID() + "");
properties.put("rhq_admin_username", getUsername());
properties.put("rhq_admin_password", getPassword());
if (!schemaExists()) {
execute(updateFolder.getUpdateFiles().get(0), properties);
} else {
log.info("Storage schema already exists.");
}
} catch (Exception ex) {
log.error(ex);
throw new RuntimeException(ex);
} finally {
shutdownClusterConnection();
}
//2. Change Cassandra default user privileges and password.
initClusterSession();
execute(updateFolder.getUpdateFiles().get(1), properties);
}
/**
* Update existing schema to the most current version in the update folder.
*
* @throws Exception
*/
private void update(Properties properties) throws Exception {
initClusterSession();
if (!schemaExists()) {
log.error("Storage schema not installed.");
throw new RuntimeException("Storage schema not installed propertly, cannot apply schema updates.");
}
UpdateFolder updateFolder = updateFolderFactory.newUpdateFolder(Task.Update.getFolder());
int installedSchemaVersion = getInstalledSchemaVersion();
log.info("Installed storage schema version is " + installedSchemaVersion);
int requiredSchemaVersion = updateFolder.getLatestVersion();
log.info("Required storage schema version is " + requiredSchemaVersion);
if (requiredSchemaVersion == installedSchemaVersion) {
log.info("Storage schema version is current ( " + installedSchemaVersion + " ). No updates applied.");
} else if (requiredSchemaVersion < installedSchemaVersion) {
log.error("Installed storage cluster schema version: " + installedSchemaVersion +
". Required schema version: " + requiredSchemaVersion
+
". Storage cluster schema has been updated beyond the capability of the existing server installation.");
throw new InstalledSchemaTooAdvancedException();
} else {
log.info("Storage schema requires udpates. Updating from version " + installedSchemaVersion
+ " to version " + requiredSchemaVersion + ".");
updateFolder.removeAppliedUpdates(installedSchemaVersion);
if (updateFolder.getUpdateFiles().size() == 0) {
log.info("Storage schema is current! No updates applied.");
} else {
for (UpdateFile updateFile : updateFolder.getUpdateFiles()) {
execute(updateFile, properties);
int version = updateFile.extractVersion();
long time = System.currentTimeMillis();
execute(
"INSERT INTO rhq.schema_version (version, time ) VALUES (" + version + ", " + time + ")");
log.info("Storage schema update " + updateFile + " applied.");
}
}
}
}
/**
* Drop RHQ schema and revert the database to pre-RHQ state:
* 1) Reinstate Cassandra superuser
* 2) Drop RHQ schema
* 3) Drop RHQ user
*
* @throws Exception
*/
public void drop() throws Exception {
log.info("Preparing to drop storage schema.");
UpdateFolder updateFolder = updateFolderFactory.newUpdateFolder(Task.Drop.getFolder());
Properties properties = new Properties(System.getProperties());
properties.put("rhq_admin_username", getUsername());
try{
initClusterSession();
//1. Reinstated Cassandra superuser
execute(updateFolder.getUpdateFiles().get(0), properties);
log.info("Cassandra user reverted to default configuration.");
} catch (AuthenticationException e) {
//if the initial auth failed then let later code to attempt to use
//the generic user cassandra user to do the cleanup
log.debug("Cannot establish connection with the RHQ specific user. "
+ "Will continue the drop procedure with the Cassandra admin user.");
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
shutdownClusterConnection();
}
try {
//Use Cassandra superuser to drop RHQ schema and user
initClusterSession(DEFAULT_CASSANDRA_USER, DEFAULT_CASSANDRA_PASSWORD);
if (schemaExists()) {
//2. Drop RHQ schema
execute(updateFolder.getUpdateFiles().get(1), properties);
log.info("Storage schema dropped.");
} else {
log.info("Storage schema does not exist. Drop operation not required.");
}
if (userExists()) {
//3. Drop RHQ user
execute(updateFolder.getUpdateFiles().get(2), properties);
log.info("RHQ admin user dropped from storage cluster.");
} else {
log.info("RHQ admin user does not exist on the storage cluster. Drop operation not required.");
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Check storage cluster schema version compatibility.
* If the version installed on the storage cluster is too advanced or too old compared
* to the version available in the current schema manager an error will thrown.
*
* @throws Exception schema compatibility exception
*/
public void checkCompatibility() throws Exception {
log.info("Preparing to check storage schema compatibility.");
try {
initClusterSession();
if (!this.schemaExists()) {
log.error("Storage cluster schema not installed. Please re-run the server installer to install the storage cluster schema properly.");
throw new SchemaNotInstalledException();
}
int installedSchemaVersion = this.getInstalledSchemaVersion();
UpdateFolder folder = updateFolderFactory.newUpdateFolder(Task.Update.getFolder());
int requiredSchemaVersion = folder.getLatestVersion();
if (installedSchemaVersion < requiredSchemaVersion) {
log.warn("Storage cluster schema version:" + installedSchemaVersion + ". Required schema version: "
+ requiredSchemaVersion + ". Please update storage cluster schema version.");
throw new InstalledSchemaTooOldException();
}
if (installedSchemaVersion > requiredSchemaVersion) {
log.error("Storage cluster schema version:" + installedSchemaVersion + ". Required schema version: "
+ requiredSchemaVersion
+ ". Storage cluster has been updated beyond the capability of the current server installation.");
throw new InstalledSchemaTooAdvancedException();
}
} catch (NoHostAvailableException e1) {
throw e1;
} catch (AuthenticationException e2) {
throw e2;
} catch (SchemaNotInstalledException e3) {
throw e3;
} catch (InstalledSchemaTooOldException e4) {
throw e4;
} catch (InstalledSchemaTooAdvancedException e5) {
throw e5;
} catch (Exception e6) {
throw new RuntimeException(e6);
} finally {
log.info("Completed storage schema compatibility check.");
}
}
}