Skip to content

Commit c34fff8

Browse files
committed
Implement email notification feature in Resource Alert Manager
1 parent 8af5494 commit c34fff8

2 files changed

Lines changed: 130 additions & 0 deletions

File tree

‎plugins/resource-alerts/src/main/java/org/apache/cloudstack/resourcealert/ResourceAlertManagerImpl.java‎

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,31 +19,46 @@
1919

2020
import java.util.Collections;
2121
import java.util.Date;
22+
import java.util.HashSet;
2223
import java.util.List;
24+
import java.util.Map;
25+
import java.util.Set;
26+
import java.util.concurrent.ExecutorService;
2327
import java.util.concurrent.Executors;
2428
import java.util.concurrent.ScheduledExecutorService;
2529
import java.util.concurrent.TimeUnit;
2630
import java.util.function.ToDoubleFunction;
2731
import java.util.stream.Collectors;
2832

2933
import javax.inject.Inject;
34+
import javax.naming.ConfigurationException;
3035

3136
import org.apache.cloudstack.framework.config.ConfigKey;
3237
import org.apache.cloudstack.framework.config.Configurable;
38+
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
3339
import org.apache.cloudstack.resourcealert.dao.ResourceAlertDao;
3440
import org.apache.cloudstack.resourcealert.dao.ResourceAlertRuleDao;
3541
import org.apache.cloudstack.resourcealert.vo.ResourceAlertRuleVO;
3642
import org.apache.cloudstack.resourcealert.vo.ResourceAlertVO;
3743
import 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;
3952
import com.cloud.host.Host;
4053
import com.cloud.host.HostStats;
54+
import com.cloud.host.HostVO;
4155
import com.cloud.host.dao.HostDao;
4256
import com.cloud.server.StatsCollector;
4357
import com.cloud.storage.StorageStats;
4458
import com.cloud.storage.VolumeVO;
4559
import com.cloud.storage.dao.VolumeDao;
4660
import com.cloud.utils.component.ManagerBase;
61+
import com.cloud.vm.UserVmVO;
4762
import com.cloud.vm.VirtualMachine;
4863
import com.cloud.vm.VmStats;
4964
import 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

‎plugins/resource-alerts/src/test/java/org/apache/cloudstack/resourcealert/ResourceAlertManagerImplTest.java‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@
5757
import com.cloud.vm.dao.UserVmDao;
5858
import com.cloud.vm.UserVmVO;
5959

60+
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
61+
6062
@RunWith(MockitoJUnitRunner.class)
6163
public class ResourceAlertManagerImplTest {
6264

@@ -70,6 +72,7 @@ public class ResourceAlertManagerImplTest {
7072
@Mock PrimaryDataStoreDao storagePoolDao;
7173
@Mock VolumeDao volumeDao;
7274
@Mock StatsCollector statsCollector;
75+
@Mock ConfigurationDao configDao;
7376

7477
@Captor ArgumentCaptor<ResourceAlertVO> alertCaptor;
7578

0 commit comments

Comments
 (0)