1919
2020import java .util .Collections ;
2121import java .util .Date ;
22+ import java .util .HashSet ;
2223import java .util .List ;
24+ import java .util .Map ;
25+ import java .util .Set ;
26+ import java .util .concurrent .ExecutorService ;
2327import java .util .concurrent .Executors ;
2428import java .util .concurrent .ScheduledExecutorService ;
2529import java .util .concurrent .TimeUnit ;
2630import java .util .function .ToDoubleFunction ;
2731import java .util .stream .Collectors ;
2832
2933import javax .inject .Inject ;
34+ import javax .naming .ConfigurationException ;
3035
3136import org .apache .cloudstack .framework .config .ConfigKey ;
3237import org .apache .cloudstack .framework .config .Configurable ;
38+ import org .apache .cloudstack .framework .config .dao .ConfigurationDao ;
3339import org .apache .cloudstack .resourcealert .dao .ResourceAlertDao ;
3440import org .apache .cloudstack .resourcealert .dao .ResourceAlertRuleDao ;
3541import org .apache .cloudstack .resourcealert .vo .ResourceAlertRuleVO ;
3642import org .apache .cloudstack .resourcealert .vo .ResourceAlertVO ;
3743import org .apache .cloudstack .storage .datastore .db .PrimaryDataStoreDao ;
44+ import org .apache .cloudstack .storage .datastore .db .StoragePoolVO ;
45+ import org .apache .cloudstack .utils .mailing .MailAddress ;
46+ import org .apache .cloudstack .utils .mailing .SMTPMailProperties ;
47+ import org .apache .cloudstack .utils .mailing .SMTPMailSender ;
48+ import org .apache .commons .lang3 .ArrayUtils ;
49+ import org .apache .commons .lang3 .StringUtils ;
3850
51+ import com .cloud .event .AlertGenerator ;
3952import com .cloud .host .Host ;
4053import com .cloud .host .HostStats ;
54+ import com .cloud .host .HostVO ;
4155import com .cloud .host .dao .HostDao ;
4256import com .cloud .server .StatsCollector ;
4357import com .cloud .storage .StorageStats ;
4458import com .cloud .storage .VolumeVO ;
4559import com .cloud .storage .dao .VolumeDao ;
4660import com .cloud .utils .component .ManagerBase ;
61+ import com .cloud .vm .UserVmVO ;
4762import com .cloud .vm .VirtualMachine ;
4863import com .cloud .vm .VmStats ;
4964import com .cloud .vm .dao .UserVmDao ;
@@ -61,8 +76,32 @@ public class ResourceAlertManagerImpl extends ManagerBase implements ResourceAle
6176 @ Inject PrimaryDataStoreDao storagePoolDao ;
6277 @ Inject VolumeDao volumeDao ;
6378 @ Inject StatsCollector statsCollector ;
79+ @ Inject ConfigurationDao configDao ;
6480
6581 private ScheduledExecutorService executor ;
82+ private final ExecutorService emailExecutor = Executors .newCachedThreadPool (r -> {
83+ Thread t = new Thread (r , "ResourceAlertEmailSender" );
84+ t .setDaemon (true );
85+ return t ;
86+ });
87+
88+ private SMTPMailSender mailSender ;
89+ private String [] emailRecipients ;
90+ private String senderAddress ;
91+
92+ @ Override
93+ public boolean configure (String name , Map <String , Object > params ) throws ConfigurationException {
94+ Map <String , String > configs = configDao .getConfiguration ("management-server" , params );
95+
96+ String emailList = configs .get ("alert.email.addresses" );
97+ if (StringUtils .isNotBlank (emailList )) {
98+ emailRecipients = emailList .split ("," );
99+ }
100+ senderAddress = configs .get ("alert.email.sender" );
101+ mailSender = new SMTPMailSender (configs , "alert.smtp" );
102+
103+ return super .configure (name , params );
104+ }
66105
67106 @ Override
68107 public boolean start () {
@@ -81,6 +120,7 @@ public boolean stop() {
81120 if (executor != null ) {
82121 executor .shutdown ();
83122 }
123+ emailExecutor .shutdown ();
84124 return true ;
85125 }
86126
@@ -230,6 +270,93 @@ private void fireAlert(ResourceAlertRuleVO rule, Long resourceId, double value)
230270 alertDao .persist (alert );
231271 logger .warn ("Alert fired: rule={} metric={} resource={} value={} threshold={}" ,
232272 rule .getUuid (), rule .getMetric (), resourceId , value , rule .getThreshold ());
273+
274+ String subject = buildSubject (rule , resourceId , value );
275+ String body = buildBody (rule , resourceId , value );
276+ long dcId = getDataCenterId (rule .getResourceType (), resourceId );
277+ try {
278+ AlertGenerator .publishAlertOnEventBus ("RESOURCE.ALERT" , dcId , null , subject , body );
279+ } catch (Exception e ) {
280+ logger .warn ("Failed to publish resource alert event: {}" , e .getMessage ());
281+ }
282+
283+ if (rule .isEmail ()) {
284+ sendEmail (subject , body );
285+ }
286+ }
287+
288+ private String buildSubject (ResourceAlertRuleVO rule , Long resourceId , double value ) {
289+ return String .format ("[%s] Resource Alert: %s %s %.2f on %s %s" ,
290+ rule .getSeverity ().name (),
291+ rule .getMetric (),
292+ rule .getCondition ().name (),
293+ rule .getThreshold (),
294+ rule .getResourceType ().name (),
295+ resourceId );
296+ }
297+
298+ private String buildBody (ResourceAlertRuleVO rule , Long resourceId , double value ) {
299+ StringBuilder sb = new StringBuilder ();
300+ sb .append ("Rule: " ).append (rule .getName ()).append ('\n' );
301+ sb .append ("Resource Type: " ).append (rule .getResourceType ().name ()).append ('\n' );
302+ sb .append ("Resource ID: " ).append (resourceId ).append ('\n' );
303+ sb .append ("Metric: " ).append (rule .getMetric ()).append ('\n' );
304+ sb .append (String .format ("Condition: %s %.2f%n" , rule .getCondition ().name (), rule .getThreshold ()));
305+ sb .append (String .format ("Current Value: %.2f%n" , value ));
306+ sb .append ("Severity: " ).append (rule .getSeverity ().name ()).append ('\n' );
307+ if (StringUtils .isNotBlank (rule .getMessage ())) {
308+ sb .append ("Message: " ).append (rule .getMessage ()).append ('\n' );
309+ }
310+ return sb .toString ();
311+ }
312+
313+ private long getDataCenterId (ResourceAlertRule .ResourceType type , long resourceId ) {
314+ try {
315+ switch (type ) {
316+ case VirtualMachine : {
317+ UserVmVO vm = userVmDao .findById (resourceId );
318+ return vm != null ? vm .getDataCenterId () : 0L ;
319+ }
320+ case Volume : {
321+ VolumeVO vol = volumeDao .findById (resourceId );
322+ return vol != null ? vol .getDataCenterId () : 0L ;
323+ }
324+ case Host : {
325+ HostVO host = hostDao .findById (resourceId );
326+ return host != null ? host .getDataCenterId () : 0L ;
327+ }
328+ case StoragePool : {
329+ StoragePoolVO pool = storagePoolDao .findById (resourceId );
330+ return pool != null ? pool .getDataCenterId () : 0L ;
331+ }
332+ default :
333+ return 0L ;
334+ }
335+ } catch (Exception e ) {
336+ return 0L ;
337+ }
338+ }
339+
340+ private void sendEmail (String subject , String body ) {
341+ if (mailSender == null || ArrayUtils .isEmpty (emailRecipients )) {
342+ return ;
343+ }
344+ SMTPMailProperties mailProps = new SMTPMailProperties ();
345+ if (StringUtils .isNotBlank (senderAddress )) {
346+ mailProps .setSender (new MailAddress (senderAddress ));
347+ }
348+ mailProps .setSubject (subject );
349+ mailProps .setContent (body );
350+ mailProps .setContentType ("text/plain" );
351+
352+ Set <MailAddress > addresses = new HashSet <>();
353+ for (String recipient : emailRecipients ) {
354+ if (StringUtils .isNotBlank (recipient )) {
355+ addresses .add (new MailAddress (recipient .trim ()));
356+ }
357+ }
358+ mailProps .setRecipients (addresses );
359+ emailExecutor .execute (() -> mailSender .sendMail (mailProps ));
233360 }
234361
235362 @ Override
0 commit comments