package com.eucalyptus.upgrade;
import edu.emory.mathcs.backport.java.util.Collections;
import groovy.sql.Sql;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.Security;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentMap;
import org.apache.log4j.Logger;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.jboss.netty.util.internal.ConcurrentHashMap;
import com.eucalyptus.auth.DatabaseAuthProvider;
import com.eucalyptus.auth.Groups;
import com.eucalyptus.auth.SystemCredentialProvider;
import com.eucalyptus.auth.UserInfoStore;
import com.eucalyptus.auth.Users;
import com.eucalyptus.auth.crypto.Hmacs;
import com.eucalyptus.auth.util.EucaKeyStore;
import com.eucalyptus.bootstrap.Bootstrap;
import com.eucalyptus.bootstrap.BootstrapException;
import com.eucalyptus.bootstrap.ServiceJarDiscovery;
import com.eucalyptus.component.Components;
import com.eucalyptus.component.DispatcherFactory;
import com.eucalyptus.component.ServiceRegistrationException;
import com.eucalyptus.entities.PersistenceContextDiscovery;
import com.eucalyptus.entities.PersistenceContexts;
import com.eucalyptus.scripting.ScriptExecutionFailedException;
import com.eucalyptus.scripting.groovy.GroovyUtil;
import com.eucalyptus.system.BaseDirectory;
import com.eucalyptus.system.LogLevels;
import com.eucalyptus.system.SubDirectory;
import com.google.common.collect.Lists;
public class StandalonePersistence {
private static Logger LOG;
private static ConcurrentMap<String, Sql> sqlConnections = new ConcurrentHashMap<String, Sql>( );
private static List<UpgradeScript> upgradeScripts = Lists.newArrayList( );
static {
Security.addProvider( new BouncyCastleProvider( ) );
}
public static String eucaHome, eucaOld, eucaSource, eucaDest, eucaOldVersion, eucaNewVersion;
public static File oldLibDir, newLibDir;
private static DatabaseSource source;
private static DatabaseDestination dest;
public static void main( String[] args ) throws Throwable {
if ( ( eucaHome = System.getProperty( "euca.upgrade.new.dir" ) ) == null ) {
throw new RuntimeException( "Failed to find required 'euca.upgrade.new.dir' property: " + eucaHome );
} else if ( ( eucaOld = System.getProperty( "euca.upgrade.old.dir" ) ) == null ) {
throw new RuntimeException( "Failed to find required 'euca.upgrade.old.dir' property: " + eucaHome );
} else if ( ( eucaNewVersion = System.getProperty( "euca.upgrade.new.version" ) ) == null ) {
throw new RuntimeException( "Failed to find required 'euca.upgrade.new.version' property: " + eucaHome );
} else if ( ( eucaOldVersion = System.getProperty( "euca.upgrade.old.version" ) ) == null ) {
throw new RuntimeException( "Failed to find required 'euca.upgrade.old.version' property: " + eucaHome );
} else if ( ( eucaSource = System.getProperty( "euca.upgrade.source" ) ) == null ) {
throw new RuntimeException( "Failed to find required 'euca.upgrade.source' property: " + eucaHome );
} else if ( ( eucaDest = System.getProperty( "euca.upgrade.destination" ) ) == null ) {
throw new RuntimeException( "Failed to find required 'euca.upgrade.destination' property: " + eucaHome );
} else {
StandalonePersistence.setupSystemProperties( );
StandalonePersistence.setupConfigurations( );
StandalonePersistence.setupInitProviders( );
}
/** Prepare for database upgrade **/
try {
/** Setup the persistence contexts **/
StandalonePersistence.runDiscovery( );
/** Setup some system mechanisms after starting the true destination db **/
StandalonePersistence.setupProviders( );
/** Create connections for each of the source databases **/
StandalonePersistence.setupNewDatabase( );
StandalonePersistence.setupOldDatabase( );
StandalonePersistence.runUpgrade( );
System.exit(0);
} catch ( Throwable e ) {
e.printStackTrace( );
System.exit( -1 );
}
}
public static void runUpgrade( ) {
Collections.sort(upgradeScripts);
LOG.info( upgradeScripts );
for( UpgradeScript up : upgradeScripts ) {
try {
up.upgrade( oldLibDir, newLibDir );
} catch ( Throwable e ) {
LOG.error( e, e );
}
}
LOG.info( "=============================" );
LOG.info( "= DATABASE UPGRADE COMPLETE =" );
LOG.info( "=============================" );
}
public static Collection<Sql> listConnections( ) {
return sqlConnections.values( );
}
public static Sql getConnection( String persistenceContext ) throws SQLException {
Sql newSql = source.getSqlSession( persistenceContext );
Sql conn = sqlConnections.putIfAbsent( persistenceContext, newSql );
if ( conn != null ) {
newSql.close( );
} else {
conn = newSql;
LOG.info( "Created new connection for: " + persistenceContext + " to " + conn.getConnection( ).getMetaData( ).getURL( ) );
}
return conn;
}
private static void setupProviders( ) {
DatabaseAuthProvider dbAuth = new DatabaseAuthProvider( );
Users.setUserProvider( dbAuth );
Groups.setGroupProvider( dbAuth );
UserInfoStore.setUserInfoProvider( dbAuth );
}
private static void setupOldDatabase( ) throws Exception {
source = ( DatabaseSource ) ClassLoader.getSystemClassLoader( ).loadClass( eucaSource ).newInstance( );
/** Register a shutdown hook which closes all source-sql sessions **/
Runtime.getRuntime( ).addShutdownHook( new Thread( ) {
@Override
public void run( ) {
for ( Sql s : StandalonePersistence.sqlConnections.values( ) ) {
try {
s.close( );
} catch ( Throwable e ) {
LOG.debug( e, e );
}
}
}
} );
/** open connection for each context **/
List<String> oldContexts = ServiceJarDiscovery.contextsInDir( oldLibDir );
for ( String ctx : oldContexts ) {
StandalonePersistence.getConnection( ctx );
}
}
private static void setupNewDatabase( ) throws Exception {
dest = ( DatabaseDestination ) ClassLoader.getSystemClassLoader( ).loadClass( eucaDest ).newInstance( );
dest.initialize( );
Runtime.getRuntime( ).addShutdownHook( new Thread( ) {
@Override
public void run( ) {
PersistenceContexts.shutdown( );
}
} );
}
private static void setupInitProviders( ) throws Exception {
if ( !new File( EucaKeyStore.getInstance( ).getFileName( ) ).exists( ) ) {
throw new RuntimeException( "Database upgrade must be preceded by a key upgrade." );
}
new SystemCredentialProvider( ).load( Bootstrap.Stage.Anonymous );
DispatcherFactory.setFactory( ( DispatcherFactory ) ClassLoader.getSystemClassLoader( ).loadClass( "com.eucalyptus.ws.client.DefaultDispatcherFactory" ).newInstance( ) );
LOG.debug( "Initializing SSL just in case: " + ClassLoader.getSystemClassLoader( ).loadClass( "com.eucalyptus.auth.util.SslSetup" ) );
LOG.debug( "Initializing db password: " + ClassLoader.getSystemClassLoader( ).loadClass( "com.eucalyptus.auth.util.Hashes" ) );
}
private static void setupSystemProperties( ) {
/** Pre-flight configuration for system **/
System.setProperty( "euca.home", eucaHome );
System.setProperty( "euca.log.level", "TRACE" );
System.setProperty( "euca.log.appender", "console" );
System.setProperty( "euca.log.exhaustive.cc", "FATAL" );
System.setProperty( "euca.log.exhaustive.db", "FATAL" );
System.setProperty( "euca.log.exhaustive.external", "FATAL" );
System.setProperty( "euca.log.exhaustive.user", "FATAL" );
System.setProperty( "euca.var.dir", eucaHome + "/var/lib/eucalyptus/" );
System.setProperty( "euca.conf.dir", eucaHome + "/etc/eucalyptus/cloud.d/" );
System.setProperty( "euca.log.dir", eucaHome + "/var/log/eucalyptus/" );
System.setProperty( "euca.lib.dir", eucaHome + "/usr/share/eucalyptus/" );
boolean doTrace = "TRACE".equals( System.getProperty( "euca.log.level" ) );
boolean doDebug = "DEBUG".equals( System.getProperty( "euca.log.level" ) ) || doTrace;
LogLevels.DEBUG = doDebug;
LogLevels.TRACE = doDebug;
StandalonePersistence.LOG = Logger.getLogger( StandalonePersistence.class );
LOG.info( String.format( "%-20.20s %s", "New install directory:", eucaHome ) );
LOG.info( String.format( "%-20.20s %s", "Old install directory:", eucaOld ) );
LOG.info( String.format( "%-20.20s %s", "Upgrade data source:", eucaSource ) );
LOG.info( String.format( "%-20.20s %s", "Upgrade data destination:", eucaDest ) );
oldLibDir = getAndCheckLibDirectory( eucaOld );
newLibDir = getAndCheckLibDirectory( eucaHome );
}
private static void setupConfigurations( ) {
Enumeration<URL> p1;
URI u = null;
try {
p1 = Thread.currentThread( ).getContextClassLoader( ).getResources( "com.eucalyptus.CloudServiceProvider" );
if ( !p1.hasMoreElements( ) ) return;
while ( p1.hasMoreElements( ) ) {
u = p1.nextElement( ).toURI( );
Properties props = new Properties( );
props.load( u.toURL( ).openStream( ) );
String name = props.getProperty( "name" );
if ( Components.contains( name ) ) {
throw BootstrapException.throwFatal( "Duplicate component definition in: " + u.toASCIIString( ) );
} else {
try {
LOG.debug( "Loaded " + name + " from " + u );
Components.create( name, u );
} catch ( ServiceRegistrationException e ) {
LOG.debug( e, e );
throw BootstrapException.throwFatal( "Error in component bootstrap: " + e.getMessage( ), e );
}
}
}
} catch ( IOException e ) {
LOG.error( e, e );
throw BootstrapException.throwFatal( "Failed to load component resources from: " + u, e );
} catch ( URISyntaxException e ) {
LOG.error( e, e );
throw BootstrapException.throwFatal( "Failed to load component resources from: " + u, e );
}
}
private static File getAndCheckLibDirectory( String eucaHome ) {
String eucaLibDirPath;
File eucaLibDir;
if ( ( eucaLibDirPath = eucaHome + "/usr/share/eucalyptus" ) == null ) {
throw new RuntimeException( "The source directory has not been specified." );
} else if ( !( eucaLibDir = new File( eucaLibDirPath ) ).exists( ) ) {
throw new RuntimeException( "The source directory does not exist: " + eucaLibDirPath );
}
return eucaLibDir;
}
public static void registerUpgradeScript( UpgradeScript up ) {
if( up.accepts( eucaOldVersion, eucaNewVersion ) ) {
LOG.info( String.format( "Found upgrade script for [%s->%s] in: %s\n", eucaOldVersion, eucaNewVersion, up.getClass( ).getCanonicalName() ) );
upgradeScripts.add( up );
} else {
LOG.info( String.format( "Ignoring upgrade script for [%s->%s] in: %s\n", eucaOldVersion, eucaNewVersion, up.getClass( ).getCanonicalName() ) );
}
}
public static void runDiscovery( ) {
List<Class> classList = ServiceJarDiscovery.classesInDir( new File( BaseDirectory.LIB.toString( ) ) );
for( ServiceJarDiscovery d : Lists.newArrayList( new PersistenceContextDiscovery( ), new UpgradeScriptDiscovery( ) ) ) {
for ( Class c : classList ) {
try {
d.processClass( c );
} catch ( Throwable t ) {
if( t instanceof ClassNotFoundException ) {
} else {
t.printStackTrace( );
LOG.debug( t, t );
}
}
}
}
for( File script : SubDirectory.UPGRADE.getFile( ).listFiles( ) ) {
LOG.debug( "Trying to load what looks like an upgrade script: " + script.getAbsolutePath( ) );
try {
UpgradeScript u = GroovyUtil.newInstance( script.getAbsolutePath( ) );
registerUpgradeScript( u );
} catch ( ScriptExecutionFailedException e ) {
LOG.debug( e, e );
}
}
}
}