diff --git a/clc/modules/msgs/conf/scripts/notifications.groovy b/clc/modules/msgs/conf/scripts/notifications.groovy new file mode 100644 index 00000000000..d0a1fc9d55d --- /dev/null +++ b/clc/modules/msgs/conf/scripts/notifications.groovy @@ -0,0 +1,30 @@ +import com.eucalyptus.component.ServiceConfiguration +import com.eucalyptus.component.Faults.FaultRecord +import com.eucalyptus.scripting.Groovyness +import com.eucalyptus.util.Exceptions +import com.google.common.base.Throwables + +summary = "" +details = "" +faults.each{ FaultRecord f -> + ServiceConfiguration s = Groovyness.expandoMetaClass(f.getServiceConfiguration( )); + summary += """ +- ${s.getFullName( )} ${f.getTransitionRecord( ).getRule( ).getFromState( )}->${f.getFinalState( )} ${f.getError( ).getTimestamp( )} + ${Throwables.getRootCause(f.getError( )).getMessage( )} +""" + details += """ +- ${s.getFullName( )} ------------ + ${f.getTransitionRecord( )} + ${Exceptions.causeString(f.getError())} +""" +} +content = """ +Impacted Services Summary +========================= +${summary} + +Details +======= +${details} + +""".toString() diff --git a/clc/modules/msgs/conf/scripts/notifications_digest.groovy b/clc/modules/msgs/conf/scripts/notifications_digest.groovy new file mode 100644 index 00000000000..8eaed86e480 --- /dev/null +++ b/clc/modules/msgs/conf/scripts/notifications_digest.groovy @@ -0,0 +1,3 @@ +import com.eucalyptus.component.Components + +Components.list().collect{ c -> c.services().collect{ s -> "\n" + s.toString() } } diff --git a/clc/modules/msgs/src/main/java/com/eucalyptus/bootstrap/Databases.java b/clc/modules/msgs/src/main/java/com/eucalyptus/bootstrap/Databases.java index c509bcb72fd..a7e78130a48 100644 --- a/clc/modules/msgs/src/main/java/com/eucalyptus/bootstrap/Databases.java +++ b/clc/modules/msgs/src/main/java/com/eucalyptus/bootstrap/Databases.java @@ -168,6 +168,7 @@ public DatabaseStateException( String string ) { } private static final int MAX_TX_START_SYNC_RETRIES = 120; + private static final AtomicInteger counter = new AtomicInteger( 120 ); private static final Predicate FILTER_SYNCING_DBS = Predicates.and( DbFilter.INSTANCE, Predicates.not( SyncedDbFilter.INSTANCE ) ); private static final ScriptedDbBootstrapper singleton = new ScriptedDbBootstrapper( ); private static Logger LOG = Logger.getLogger( Databases.class ); @@ -260,8 +261,13 @@ public void run( ) { if ( !Hosts.isCoordinator( ) && Hosts.localHost( ).hasDatabase( ) ) { while ( !Databases.enable( Hosts.localHost( ) ) ) { LOG.warn( LogUtil.subheader( "Synchronization of the database failed: " + Hosts.localHost( ) ) ); - LOG.warn( "Sleeping for " + INITIAL_DB_SYNC_RETRY_WAIT + " seconds before trying again." ); - TimeUnit.SECONDS.sleep( INITIAL_DB_SYNC_RETRY_WAIT ); + if ( counter.decrementAndGet( ) == 0 ) { + LOG.fatal( "Restarting process to force re-synchronization." ); + System.exit( 123 ); + } else { + LOG.warn( "Sleeping for " + INITIAL_DB_SYNC_RETRY_WAIT + " seconds before trying again." ); + TimeUnit.SECONDS.sleep( INITIAL_DB_SYNC_RETRY_WAIT ); + } } Hosts.UpdateEntry.INSTANCE.apply( Hosts.localHost( ) ); LOG.info( LogUtil.subheader( "Database synchronization complete: " + Hosts.localHost( ) ) ); @@ -484,7 +490,8 @@ public void run( ) { Logs.extreme( ).debug( "Skipping addition of db connections for host which already exists: " + hostName ); } catch ( IllegalStateException ex ) { if ( Exceptions.isCausedBy( ex, InstanceAlreadyExistsException.class ) ) { - ManagementFactory.getPlatformMBeanServer( ).unregisterMBean( new ObjectName( "net.sf.hajdbc:cluster=" + ctx + ",database=" + hostName ) ); + ManagementFactory.getPlatformMBeanServer( ).unregisterMBean( + new ObjectName( "net.sf.hajdbc:cluster=" + ctx + ",database=" + hostName ) ); cluster.add( hostName, realJdbcDriver, dbUrl ); } else { throw ex; @@ -626,7 +633,7 @@ static boolean disable( final String hostName ) { } } } - private static final AtomicInteger counter = new AtomicInteger( 5 ); + static boolean enable( final Host host ) { if ( !host.hasDatabase( ) ) { return false; @@ -644,9 +651,6 @@ static boolean enable( final Host host ) { SyncState.NOTSYNCED.set( ); LOG.error( ex ); Logs.extreme( ).error( ex, ex ); - if ( Exceptions.isCausedBy( ex, InstanceNotFoundException.class ) && counter.decrementAndGet( ) == 0 ) { - System.exit( 123 ); - } return false; } } else if ( !SyncState.SYNCING.isCurrent( ) ) { @@ -788,7 +792,7 @@ public Set get( ) { } }; private static final AtomicBoolean last = new AtomicBoolean( false ); - + @Override public abstract Set get( ); @@ -1005,15 +1009,15 @@ public static void dropForeignKeys( SynchronizationContext context ) thro } statement.executeBatch( ); } catch ( SQLException sqle ) { - LOG.error( sqle ); - Logs.extreme( ).error( sqle, sqle ); - throw sqle; + LOG.error( sqle ); + Logs.extreme( ).error( sqle, sqle ); + throw sqle; } finally { - try { - statement.close( ); - } catch ( Exception e ) { - LOG.error( e ); - } + try { + statement.close( ); + } catch ( Exception e ) { + LOG.error( e ); + } } } @@ -1040,15 +1044,15 @@ public static void restoreForeignKeys( SynchronizationContext context ) t } statement.executeBatch( ); } catch ( SQLException sqle ) { - LOG.error( sqle ); - Logs.extreme( ).error( sqle, sqle ); - throw sqle; + LOG.error( sqle ); + Logs.extreme( ).error( sqle, sqle ); + throw sqle; } finally { - try { - statement.close( ); - } catch ( Exception e ) { - LOG.error( e ); - } + try { + statement.close( ); + } catch ( Exception e ) { + LOG.error( e ); + } } } @@ -1086,15 +1090,15 @@ public Long call( ) throws SQLException long value = resultSet.getLong( 1 ); return value; } catch ( SQLException sqle ) { - LOG.error(sqle); - Logs.extreme( ).error( sqle, sqle ); - throw sqle; + LOG.error( sqle ); + Logs.extreme( ).error( sqle, sqle ); + throw sqle; } finally { - try { - statement.close( ); - } catch ( Exception e ) { - LOG.error( e ); - } + try { + statement.close( ); + } catch ( Exception e ) { + LOG.error( e ); + } } } }; @@ -1133,11 +1137,11 @@ public Long call( ) throws SQLException Logs.extreme( ).error( sqle, sqle ); throw sqle; } finally { - try { - targetStatement.close( ); - } catch ( Exception e ) { - LOG.error( e ); - } + try { + targetStatement.close( ); + } catch ( Exception e ) { + LOG.error( e ); + } } } } @@ -1148,10 +1152,10 @@ public Long call( ) throws SQLException * @throws SQLException */ public static void synchronizeIdentityColumns( SynchronizationContext context ) throws SQLException { - + Statement sourceStatement = null; Statement targetStatement = null; - try { + try { sourceStatement = context.getConnection( context.getSourceDatabase( ) ).createStatement( ); targetStatement = context.getConnection( context.getTargetDatabase( ) ).createStatement( ); Dialect dialect = context.getDialect( ); @@ -1181,20 +1185,20 @@ public static void synchronizeIdentityColumns( SynchronizationContext con } } } - } catch (SQLException sqle ) { - LOG.error( sqle ); - Logs.extreme( ).error( sqle, sqle ); - throw sqle; + } catch ( SQLException sqle ) { + LOG.error( sqle ); + Logs.extreme( ).error( sqle, sqle ); + throw sqle; } finally { try { - sourceStatement.close( ); + sourceStatement.close( ); } catch ( Exception e1 ) { - LOG.error( e1 ); + LOG.error( e1 ); } try { targetStatement.close( ); } catch ( Exception e2 ) { - LOG.error( e2 ); + LOG.error( e2 ); } } } @@ -1220,15 +1224,15 @@ public static void dropUniqueConstraints( SynchronizationContext context } statement.executeBatch( ); } catch ( SQLException sqle ) { - LOG.error( sqle ); - Logs.extreme( ).error( sqle, sqle ); - throw sqle; + LOG.error( sqle ); + Logs.extreme( ).error( sqle, sqle ); + throw sqle; } finally { - try { - statement.close( ); - } catch (Exception e) { - LOG.error( e ); - } + try { + statement.close( ); + } catch ( Exception e ) { + LOG.error( e ); + } } } @@ -1254,14 +1258,14 @@ public static void restoreUniqueConstraints( SynchronizationContext conte } statement.executeBatch( ); } catch ( SQLException sqle ) { - LOG.error( sqle ); - Logs.extreme( ).error( sqle, sqle ); - throw sqle; + LOG.error( sqle ); + Logs.extreme( ).error( sqle, sqle ); + throw sqle; } finally { try { statement.close( ); } catch ( Exception e ) { - LOG.error( e ); + LOG.error( e ); } } } diff --git a/clc/modules/msgs/src/main/java/com/eucalyptus/component/Faults.java b/clc/modules/msgs/src/main/java/com/eucalyptus/component/Faults.java index 2890da77db3..be5078aa1e8 100644 --- a/clc/modules/msgs/src/main/java/com/eucalyptus/component/Faults.java +++ b/clc/modules/msgs/src/main/java/com/eucalyptus/component/Faults.java @@ -66,11 +66,16 @@ import java.util.Arrays; import java.util.Collection; import java.util.Date; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.UUID; import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; import javax.persistence.Column; import javax.persistence.EnumType; import javax.persistence.Enumerated; @@ -93,25 +98,56 @@ import org.hibernate.annotations.NaturalId; import org.jboss.netty.util.internal.LinkedTransferQueue; import com.eucalyptus.bootstrap.Bootstrap; +import com.eucalyptus.bootstrap.BootstrapArgs; import com.eucalyptus.bootstrap.Bootstrapper; -import com.eucalyptus.bootstrap.Databases; import com.eucalyptus.bootstrap.Hosts; import com.eucalyptus.component.Component.State; import com.eucalyptus.component.Component.Transition; +import com.eucalyptus.component.id.Eucalyptus; +import com.eucalyptus.configurable.ConfigurableClass; +import com.eucalyptus.configurable.ConfigurableField; import com.eucalyptus.empyrean.ServiceStatusDetail; import com.eucalyptus.empyrean.ServiceStatusType; +import com.eucalyptus.event.ClockTick; +import com.eucalyptus.event.EventListener; +import com.eucalyptus.event.Listeners; import com.eucalyptus.records.Logs; +import com.eucalyptus.scripting.Groovyness; +import com.eucalyptus.system.SubDirectory; +import com.eucalyptus.system.Threads; +import com.eucalyptus.util.Emails; import com.eucalyptus.util.Exceptions; import com.eucalyptus.util.TypeMapper; import com.eucalyptus.util.TypeMappers; import com.eucalyptus.util.fsm.TransitionRecord; import com.google.common.base.Function; import com.google.common.base.Predicate; +import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +@ConfigurableClass( root = "bootstrap.notifications", + description = "Parameters controlling the handling of service state notifications." ) public class Faults { - private static Logger LOG = Logger.getLogger( Faults.class ); + private static Logger LOG = Logger.getLogger( Faults.class ); + @ConfigurableField( description = "Email address where notifications are to be delivered." ) + public static String EMAIL_TO; + @ConfigurableField( description = "From email address used for notification delivery." ) + public static String EMAIL_FROM = "notification@eucalyptus"; + @ConfigurableField( description = "From email name used for notification delivery." ) + public static String EMAIL_FROM_NAME = "Eucalyptus Notifications"; + @ConfigurableField( description = "Email subject used for notification delivery." ) + public static final String EMAIL_SUBJECT_PREFIX = "[eucalyptus-notifications] "; + @ConfigurableField( description = "Interval (in seconds) during which a notification will be delayed to allow for batching events for delivery." ) + public static Integer BATCH_DELAY_SECONDS = 60; + @ConfigurableField( description = "Send a system state digest periodically." ) + public static Boolean DIGEST = Boolean.FALSE; + @ConfigurableField( description = "If sending system state digests is set to true, then only send the digest when the system has failures to report." ) + public static Boolean DIGEST_ONLY_ON_ERRORS = Boolean.TRUE; + @ConfigurableField( description = "Period (in hours) with which a system state digest will be delivered." ) + public static Integer DIGEST_FREQUENCY_HOURS = 24; + @ConfigurableField( description = "Period (in hours) with which a system state digest will be delivered." ) + public static Boolean INCLUDE_FAULT_STACK = Boolean.FALSE; enum NoopErrorFilter implements Predicate { INSTANCE; @@ -334,7 +370,7 @@ private void setOther( final CheckException other ) { public String getStackString( ) { return this.stackString; } - + private String getServiceFullName( ) { return this.serviceFullName; } @@ -422,7 +458,7 @@ public enum Severity implements Predicate { ERROR, //default: store, describe, ui, notification URGENT, //default: store, describe, ui, notification, alert FATAL; - + @Override public boolean apply( CheckException input ) { if ( input == null ) { @@ -544,27 +580,32 @@ private static class FaultRecord { private final ServiceConfiguration serviceConfiguration; private final TransitionRecord transitionRecord; private final CheckException error; + private final Component.State finalState; private FaultRecord( ServiceConfiguration serviceConfiguration, TransitionRecord transitionRecord, CheckException error ) { super( ); this.serviceConfiguration = serviceConfiguration; + this.finalState = serviceConfiguration.lookupState( ); this.transitionRecord = transitionRecord; this.error = error; } - + public ServiceConfiguration getServiceConfiguration( ) { return this.serviceConfiguration; } - + public TransitionRecord getTransitionRecord( ) { return this.transitionRecord; } - + public CheckException getError( ) { return this.error; } + private Component.State getFinalState( ) { + return this.finalState; + } } @@ -582,11 +623,119 @@ public static Collection lookup( final ServiceConfiguration conf } public static void submit( final ServiceConfiguration parent, TransitionRecord transitionRecord, final CheckException errors ) { - if ( errors != null && Hosts.isCoordinator( ) && Bootstrap.isFinished( ) && !Databases.isVolatile( ) ) { - Logs.extreme( ).error( errors, errors ); - FaultRecord record = new FaultRecord( parent, transitionRecord, errors ); - serviceExceptions.put( parent, record ); - // errorQueue.offer( record ); + FaultRecord record = new FaultRecord( parent, transitionRecord, errors ); + serviceExceptions.put( parent, record ); + if ( errors != null && BootstrapArgs.isCloudController( ) && Bootstrap.isFinished( ) ) { + errorQueue.offer( record ); + } + } + + public static class FaultNotificationHandler implements EventListener, Callable { + private static final AtomicBoolean ready = new AtomicBoolean( true ); + private static final AtomicLong lastDigest = new AtomicLong( System.currentTimeMillis( ) ); + + public static void register( ) { + Listeners.register( ClockTick.class, new FaultNotificationHandler( ) ); + } + + @Override + public void fireEvent( final ClockTick event ) { + if ( BootstrapArgs.isCloudController( ) && ready.compareAndSet( true, false ) ) { + try { + Threads.enqueue( Eucalyptus.class, Faults.class, this ); + } catch ( final Exception ex ) { + ready.set( true ); + } + } + } + + @Override + public Boolean call( ) throws Exception { + try { + TimeUnit.SECONDS.sleep( Faults.BATCH_DELAY_SECONDS ); + sendFaults( ); + sendDigest( ); + } finally { + ready.set( true ); + } + return true; + } + + private static void sendDigest( ) { + if ( Hosts.isCoordinator( ) && Faults.DIGEST ) { + long lastTime = lastDigest.getAndSet( System.currentTimeMillis( ) ); + if ( ( lastDigest.get( ) - lastTime ) > Faults.DIGEST_FREQUENCY_HOURS * 60 * 60 * 1000 ) { + Date digestDate = new Date( lastDigest.get( ) ); + if ( !serviceExceptions.isEmpty( ) || !Faults.DIGEST_ONLY_ON_ERRORS ) { + LOG.debug( "Fault notifications: preparing digest for " + digestDate + "." ); + try { + String subject = Faults.EMAIL_SUBJECT_PREFIX + " system state for " + digestDate; + String result = Groovyness.run( SubDirectory.SCRIPTS, "notifications_digest" ); + if ( !Strings.isNullOrEmpty( result ) ) { + dispatchEmail( subject, result ); + } + } catch ( Exception ex ) { + LOG.error( "Fault notifications: rendering digest failed: " + ex.getMessage( ) ); + Logs.extreme( ).error( ex, ex ); + } + } else { + LOG.debug( "Fault notifications: skipping digest for " + digestDate + "." ); + } + } else { + lastDigest.set( lastTime ); + } + } + } + + private static void sendFaults( ) { + LOG.debug( "Fault notifications: waking up to service error queue." ); + final List pendingFaults = Lists.newArrayList( ); + errorQueue.drainTo( pendingFaults ); + if ( pendingFaults.isEmpty( ) ) { + LOG.debug( "Fault notifications: service error queue is empty... going back to sleep." ); + } else { + if ( Hosts.isCoordinator( ) ) { + String subject = Faults.EMAIL_SUBJECT_PREFIX; + List noStateChange = Lists.newArrayList( ); + List stateChange = Lists.newArrayList( ); + for ( FaultRecord f : pendingFaults ) { + TransitionRecord tr = f.getTransitionRecord( ); + if ( tr.getRule( ).getFromState( ).equals( f.getFinalState( ) ) ) { + noStateChange.add( f ); + } else { + stateChange.add( f ); + subject += " " + f.getServiceConfiguration( ).getName( ) + "->" + f.getFinalState( ); + } + } + if ( stateChange.isEmpty( ) ) { + LOG.debug( "Fault notifications: no state changes pending, discarding pending faults" ); + } else { + try { + String result = Groovyness.run( SubDirectory.SCRIPTS, "notifications", new HashMap( ) { + { + this.put( "faults", pendingFaults ); + } + } ); + if ( !Strings.isNullOrEmpty( result ) ) { + dispatchEmail( subject, result ); + } + } catch ( Exception ex ) { + LOG.error( "Fault notifications: rendering notification failed: " + ex.getMessage( ) ); + Logs.extreme( ).error( ex, ex ); + } + } + } + } + } + + public static void dispatchEmail( String subject, String result ) { + LOG.debug( "From: " + Faults.EMAIL_FROM_NAME + " <" + Faults.EMAIL_FROM + ">" ); + LOG.debug( "To: " + Faults.EMAIL_TO ); + LOG.debug( "Subject: " + subject ); + LOG.debug( result ); + if ( !Strings.isNullOrEmpty( Faults.EMAIL_TO ) ) { + Emails.send( Faults.EMAIL_FROM, Faults.EMAIL_FROM_NAME, Faults.EMAIL_TO, subject, result ); + } } } diff --git a/clc/modules/msgs/src/main/java/com/eucalyptus/component/ServiceTransitions.java b/clc/modules/msgs/src/main/java/com/eucalyptus/component/ServiceTransitions.java index 9cd765dbb89..21d4839182e 100644 --- a/clc/modules/msgs/src/main/java/com/eucalyptus/component/ServiceTransitions.java +++ b/clc/modules/msgs/src/main/java/com/eucalyptus/component/ServiceTransitions.java @@ -178,11 +178,11 @@ public static CheckedListenableFuture pathTo( final Servic CheckedListenableFuture result = executeTransition( configuration, Automata.sequenceTransitions( configuration, path ) ); return result; } catch ( RuntimeException ex ) { - Logs.extreme( ).error( ex, ex ); - LOG.error( configuration.getFullName( ) + " failed to transition to " + Logs.extreme( ).error( configuration.getFullName( ) + " failed to transition to " + goalState + " because of: " + Exceptions.causeString( ex ) ); + Logs.extreme( ).error( ex, ex ); throw ex; } } @@ -348,7 +348,7 @@ private static void processTransition( final ServiceConfiguration parent, final trans = ServiceRemoteTransitionNotification.valueOf( transitionAction.name( ) ); } if ( trans != null ) { - Logs.exhaust( ).debug( "Executing transition: " + trans.getClass( ) + Logs.extreme( ).debug( "Executing transition: " + trans.getClass( ) + "." + transitionAction.name( ) + " for " @@ -356,6 +356,7 @@ private static void processTransition( final ServiceConfiguration parent, final trans.fire( parent ); } transitionCallback.fire( ); + Faults.flush( parent ); } catch ( Exception ex ) { LOG.error( parent.getFullName( ) + " failed transition " + transitionAction.name( ) + " because of " + ex.getMessage( ) ); if ( Faults.filter( parent, ex ) ) { diff --git a/clc/modules/msgs/src/main/java/com/eucalyptus/component/Topology.java b/clc/modules/msgs/src/main/java/com/eucalyptus/component/Topology.java index b15d8df2a50..4a53f22a246 100644 --- a/clc/modules/msgs/src/main/java/com/eucalyptus/component/Topology.java +++ b/clc/modules/msgs/src/main/java/com/eucalyptus/component/Topology.java @@ -976,7 +976,7 @@ public ServiceConfiguration call( ) throws Exception { return result; } catch ( final Exception ex ) { final Throwable t = Exceptions.unwrapCause( ex ); - LOG.error( config.getFullName( ) + " failed to transition because of:\n" + t.getMessage( ) ); + Logs.extreme( ).error( config.getFullName( ) + " failed to transition because of:\n" + t.getMessage( ) ); Logs.extreme( ).error( t, t ); throw ex; } @@ -1139,9 +1139,9 @@ private ServiceConfiguration doTopologyChange( final ServiceConfiguration input, } private String toString( final ServiceConfiguration endResult, final State initialState, final State nextState, final Throwable... throwables ) { - return String.format( "%s %s %s->%s=%s \n[%s]\n", this.toString( ), endResult.getFullName( ), initialState, nextState, endResult.lookupState( ), + return String.format( "%s %s %s->%s=%s [%s]\n", this.toString( ), endResult.getFullName( ), initialState, nextState, endResult.lookupState( ), ( ( throwables != null ) && ( throwables.length > 0 ) - ? Exceptions.causeString( throwables[0] ) + ? Throwables.getRootCause( throwables[0] ).getMessage( ) : "WINNING" ) ); } diff --git a/clc/modules/msgs/src/main/java/com/eucalyptus/scripting/Groovyness.java b/clc/modules/msgs/src/main/java/com/eucalyptus/scripting/Groovyness.java index dacda5b2682..e65a3092f5b 100644 --- a/clc/modules/msgs/src/main/java/com/eucalyptus/scripting/Groovyness.java +++ b/clc/modules/msgs/src/main/java/com/eucalyptus/scripting/Groovyness.java @@ -22,6 +22,7 @@ import org.codehaus.groovy.control.CompilerConfiguration; import com.eucalyptus.system.SubDirectory; import com.eucalyptus.util.Exceptions; +import com.google.common.collect.Maps; public class Groovyness { private static Logger LOG = Logger.getLogger( Groovyness.class ); @@ -29,7 +30,7 @@ public class Groovyness { public static T expandoMetaClass( T obj ) { ExpandoMetaClass emc = new ExpandoMetaClass( obj.getClass( ), false ); - emc.initialize(); + emc.initialize( ); obj.setMetaClass( emc ); return obj; } @@ -45,7 +46,6 @@ private static GroovyClassLoader getGroovyClassLoader( ) { return loader; } - private static ScriptEngine getGroovyEngine( ) { synchronized ( Groovyness.class ) { if ( groovyEngine == null ) { @@ -63,8 +63,8 @@ public static T newInstance( String fileName ) throws ScriptExecutionFailedE File f = new File( fileName ); if ( !f.exists( ) ) { f = new File( SubDirectory.SCRIPTS + File.separator + fileName + ( fileName.endsWith( ".groovy" ) - ? "" - : ".groovy" ) ); + ? "" + : ".groovy" ) ); } GroovyClassLoader loader = getGroovyClassLoader( ); Class groovyClass = loader.parseClass( f ); @@ -80,8 +80,8 @@ public static T newInstance( String fileName ) throws ScriptExecutionFailedE throw new ScriptExecutionFailedException( e.getMessage( ), e ); } } - - public static T run( SubDirectory dir, String fileName ) { + + public static T run( SubDirectory dir, String fileName, Map context ) { fileName = dir + File.separator + fileName; String fileNameWExt = fileName + ".groovy"; if ( !new File( fileName ).exists( ) && new File( fileNameWExt ).exists( ) ) { @@ -90,21 +90,29 @@ public static T run( SubDirectory dir, String fileName ) { FileReader fileReader = null; try { fileReader = new FileReader( fileName ); - T ret = ( T ) getGroovyEngine( ).eval( fileReader ); + Bindings bindings = new SimpleBindings( context ); + SimpleScriptContext scriptContext = new SimpleScriptContext( ); + scriptContext.setBindings( bindings, SimpleScriptContext.ENGINE_SCOPE ); + T ret = ( T ) getGroovyEngine( ).eval( fileReader, scriptContext ); return ret; } catch ( Exception e ) { LOG.debug( e, e ); throw new RuntimeException( "Executing the requested script failed: " + fileName, e ); } finally { - if ( fileReader != null ) + if ( fileReader != null ) { try { - fileReader.close( ); + fileReader.close( ); } catch ( IOException e ) { - LOG.error( e ); + LOG.error( e ); } + } } } + public static T run( SubDirectory dir, String fileName ) { + return run( dir, fileName, Maps.newHashMap( ) ); + } + public static T run( String fileName ) { return ( T ) run( SubDirectory.SCRIPTS, fileName ); } @@ -131,10 +139,10 @@ public static T eval( String code, Map context ) throws ScriptExecutionFaile return ( T ) getGroovyEngine( ).eval( code, scriptContext ); } catch ( Exception e ) { LOG.debug( e, e ); - throw new ScriptExecutionFailedException( "Executing the requested script failed:\n" - + "============================\n" - + code - + "============================\n" + throw new ScriptExecutionFailedException( "Executing the requested script failed:\n" + + "============================\n" + + code + + "============================\n" + "\nbecause of:\n" + Exceptions.causeString( e ), e ); } } @@ -144,10 +152,10 @@ public static T eval( String code ) throws ScriptExecutionFailedException { return ( T ) getGroovyEngine( ).eval( code ); } catch ( Exception e ) { LOG.debug( e, e ); - throw new ScriptExecutionFailedException( "Executing the requested script failed:\n" - + "============================\n" - + code - + "============================\n" + throw new ScriptExecutionFailedException( "Executing the requested script failed:\n" + + "============================\n" + + code + + "============================\n" + "\nbecause of:\n" + Exceptions.causeString( e ), e ); } } @@ -164,8 +172,8 @@ public static void loadConfig( String confFile ) { try { fileReader = new BufferedReader( new FileReader( confFile ) ); for ( ; ( line = fileReader.readLine( ) ) != null; conf += !line.matches( "\\s*\\w+\\s*=[\\s\\.\\w*\"']*;{0,1}" ) - ? "" - : "\n" + className + "." + line ); + ? "" + : "\n" + className + "." + line ); LOG.debug( conf ); try { getGroovyEngine( ).eval( conf ); diff --git a/clc/modules/msgs/src/main/java/com/eucalyptus/util/Emails.java b/clc/modules/msgs/src/main/java/com/eucalyptus/util/Emails.java new file mode 100644 index 00000000000..c0e031e1b36 --- /dev/null +++ b/clc/modules/msgs/src/main/java/com/eucalyptus/util/Emails.java @@ -0,0 +1,187 @@ +/******************************************************************************* + * Copyright (c) 2009 Eucalyptus Systems, Inc. + * + * 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, only version 3 of the License. + * + * + * This file 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, see . + * + * Please contact Eucalyptus Systems, Inc., 130 Castilian + * Dr., Goleta, CA 93101 USA or visit + * if you need additional information or have any questions. + * + * This file may incorporate work covered under the following copyright and + * permission notice: + * + * Software License Agreement (BSD License) + * + * Copyright (c) 2008, Regents of the University of California + * All rights reserved. + * + * Redistribution and use of this software in source and binary forms, with + * or without modification, are permitted provided that the following + * conditions are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. USERS OF + * THIS SOFTWARE ACKNOWLEDGE THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE + * LICENSED MATERIAL, COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS + * SOFTWARE, AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING + * IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA, SANTA + * BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY, WHICH IN + * THE REGENTS’ DISCRETION MAY INCLUDE, WITHOUT LIMITATION, REPLACEMENT + * OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO IDENTIFIED, OR + * WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT NEEDED TO COMPLY WITH + * ANY SUCH LICENSES OR RIGHTS. + ******************************************************************************* + * @author chris grzegorczyk + */ + +package com.eucalyptus.util; + +import java.io.UnsupportedEncodingException; +import java.util.Properties; +import javax.mail.Address; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.Session; +import javax.mail.Transport; +import javax.mail.internet.AddressException; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; +import org.apache.log4j.Logger; +import com.eucalyptus.configurable.ConfigurableClass; +import com.eucalyptus.configurable.ConfigurableField; +import com.eucalyptus.configurable.ConfigurableFieldType; + +@ConfigurableClass( root = "bootstrap.notifications.email", + description = "Parameters controlling the delivery of notification emails." ) +public class Emails { + private static Logger LOG = Logger.getLogger( Emails.class ); + @ConfigurableField( description = "SMTP host to use when sending email. If unset, the following values are tried: 1) the value of the 'mail.smtp.host' system property, 2) localhost, 3) mailhost." ) + public static String EMAIL_SMTP_HOST = null; + @ConfigurableField( description = "SMTP port to use when sending email. Defaults to 25" ) + public static Integer EMAIL_SMTP_PORT = 25; + + enum SessionProperties { + MAIL_SMTP_HOST { + + @Override + public String getPropertyValue( ) { + if ( Emails.EMAIL_SMTP_HOST != null ) { + return Emails.EMAIL_SMTP_HOST; + } else if ( System.getProperty( this.getPropertyName( ) ) != null ) { + return System.getProperty( this.getPropertyName( ) ); + } else { + return "localhost"; + } + } + + }, + MAIL_SMTP_PORT( String.valueOf( 25 ) ) { + + @Override + public String getPropertyValue( ) { + if ( Emails.EMAIL_SMTP_PORT != null ) { + return String.valueOf( Emails.EMAIL_SMTP_PORT ); + } else { + return super.getPropertyValue( ); + } + } + + }, + MAIL_TRANSPORT_PROTOCOL( "smtp" ), + MAIL_DEBUG( String.valueOf( true ) ); + private final String value; + + private SessionProperties( ) { + this.value = null; + } + + private SessionProperties( String value ) { + this.value = value; + } + + public String getPropertyValue( ) { + return this.value; + } + + public final String getPropertyName( ) { + return this.name( ).replace( "_", "." ).toLowerCase( ); + } + } + + private static Address validate( String emailAddress, String emailName ) { + if ( emailAddress != null ) { + try { + InternetAddress addr = new InternetAddress( emailAddress, emailName ); + addr.validate( ); + return addr; + } catch ( AddressException ex ) { + throw new IllegalArgumentException( "Provided address could not be validated: " + ( emailName != null ? emailName : "" ) + " <" + emailAddress + ">", ex ); + } catch ( UnsupportedEncodingException ex ) { + throw new IllegalArgumentException( "Provided address could not be validated: " + ( emailName != null ? emailName : "" ) + " <" + emailAddress + ">", ex ); + } + } else { + throw new IllegalArgumentException( "Address must be not-null." ); + } + } + + public static void send( String from, String to, String subject, String content ) { + send( from, null, to, subject, content ); + } + + public static void send( String from, String fromName, String to, String subject, String content ) { + Address fromAddress = validate( from, fromName ); + Address toAddress = validate( to, null ); + final Properties properties = new Properties( System.getProperties( ) ); + for ( SessionProperties p : SessionProperties.values( ) ) { + properties.setProperty( p.getPropertyName( ), p.getPropertyValue( ) ); + } + try { + doSend( subject, content, fromAddress, toAddress, properties ); + } catch ( MessagingException ex ) { + try { + properties.setProperty( SessionProperties.MAIL_SMTP_HOST.getPropertyName( ), "mailhost" ); + doSend( subject, content, fromAddress, toAddress, properties ); + } catch ( MessagingException ex1 ) { + LOG.error( ex1, ex1 ); + } + } + } + + private static void doSend( String subject, String content, Address fromAddress, Address toAddress, final Properties properties ) throws MessagingException { + Session session = Session.getDefaultInstance( properties ); + MimeMessage message = new MimeMessage( session ); + message.setSubject( subject ); + message.setText( content ); + message.setFrom( fromAddress ); + message.setRecipient( Message.RecipientType.TO, toAddress ); + Transport.send( message ); + } + +}