diff --git a/README.md b/README.md
index b254582f..8291eff8 100644
--- a/README.md
+++ b/README.md
@@ -13,8 +13,8 @@ Seyren ([/ˈsaɪ.rʌn/](http://en.wikipedia.org/wiki/Wikipedia:IPA_for_English#K
[SLF4J](http://www.slf4j.org),
[Slack](https://www.slack.com),
[SNMP](http://en.wikipedia.org/wiki/Simple_Network_Management_Protocol),
-[Twilio](https://www.twilio.com/)
-
+[Twilio](https://www.twilio.com/),
+[SNS](https://aws.amazon.com/sns/)
#[
](http://i.imgur.com/ahu3aM6.png)
@@ -142,6 +142,11 @@ The target for a Slack subscription will be the channel name (including the `#`,
##### [Victorops](https://www.victorops.com/)
* `VICTOROPS_REST_ENDPOINT` - The REST Endpoint given by Victorops. See [REST Integration]( http://victorops.force.com/knowledgebase/articles/Integration/Alert-Ingestion-API-Documentation/) Default ``
+##### [SNS](https://aws.amazon.com/sns/)
+* `SNS_REGION` - The region of the SNS ARN. Default `eu-west-1`. Must match the [region from the list](http://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/regions/Regions.html#valueOf-java.lang.String-).
+* AWS SNS Client uses [default AWS credentials provider chain.](http://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/auth/DefaultAWSCredentialsProviderChain.html)
+* Target is an ARN of the SNS Topic.
+
##### [Proxy](http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/impl/client/HttpClientBuilder.html#useSystemProperties())
The Proxy settings are changed by using JAVA_OPTS. It important that you exclude your graphite server or you will proxy these requests also.
* `JAVA_OPTS Example` JAVA_OPTS="-server -Dhttps.proxyHost=`PROXYSERVER` -Dhttps.proxyPort=`PROXYPORT` -Dhttp.proxyHost=`PROXYSERVER` -Dhttp.proxyPort=`PROXYPORT` -Dhttp.nonProxyHosts=`GRAPHITESERVER|*YOURDOMAIN.COM|INTERNALHIPCHAT`"
diff --git a/pom.xml b/pom.xml
index a41a4b13..532d55ca 100755
--- a/pom.xml
+++ b/pom.xml
@@ -50,7 +50,7 @@
3.2.6
-
+
UTF-8
UTF-8
1.1.2
@@ -59,7 +59,7 @@
2.5.0
1.1.42
18.0
- 2.0.7
+ 2.4.2
1.0.1
2.4
2.6
@@ -68,7 +68,7 @@
1.4.7
2.6
4.12
- 4.3.6
+ 4.5.2
1.7
9.2.2.v20140723
1.3
@@ -77,10 +77,10 @@
3.2.2
2.5.3
1.7.9
- 2.3.3
+ 2.5.2
4.1.4.RELEASE
-
+
3.0
@@ -189,7 +189,7 @@
-
+
org.apache.maven.plugins
maven-release-plugin
diff --git a/seyren-core/pom.xml b/seyren-core/pom.xml
index c986ad24..6376e288 100755
--- a/seyren-core/pom.xml
+++ b/seyren-core/pom.xml
@@ -11,6 +11,11 @@
+
+ com.amazonaws
+ aws-java-sdk-sns
+ 1.11.46
+
com.squareup.pagerduty
pagerduty-incidents
diff --git a/seyren-core/src/main/java/com/seyren/core/domain/SubscriptionType.java b/seyren-core/src/main/java/com/seyren/core/domain/SubscriptionType.java
index b7eed588..12fdff84 100644
--- a/seyren-core/src/main/java/com/seyren/core/domain/SubscriptionType.java
+++ b/seyren-core/src/main/java/com/seyren/core/domain/SubscriptionType.java
@@ -14,7 +14,20 @@
package com.seyren.core.domain;
public enum SubscriptionType {
-
- EMAIL, PAGERDUTY, HIPCHAT, HUBOT, FLOWDOCK, HTTP, IRCCAT, PUSHOVER, LOGGER, SNMP, SLACK, TWILIO, VICTOROPS,
- OPSGENIE, BIGPANDA
+ EMAIL,
+ PAGERDUTY,
+ HIPCHAT,
+ HUBOT,
+ FLOWDOCK,
+ HTTP,
+ IRCCAT,
+ PUSHOVER,
+ LOGGER,
+ SNMP,
+ SLACK,
+ TWILIO,
+ VICTOROPS,
+ OPSGENIE,
+ BIGPANDA,
+ SNS
}
diff --git a/seyren-core/src/main/java/com/seyren/core/service/notification/SnsNotificationService.java b/seyren-core/src/main/java/com/seyren/core/service/notification/SnsNotificationService.java
new file mode 100644
index 00000000..94bcefad
--- /dev/null
+++ b/seyren-core/src/main/java/com/seyren/core/service/notification/SnsNotificationService.java
@@ -0,0 +1,79 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.seyren.core.service.notification;
+
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import org.springframework.mail.javamail.JavaMailSender;
+
+import com.seyren.core.domain.Alert;
+import com.seyren.core.domain.Check;
+import com.seyren.core.domain.Subscription;
+import com.seyren.core.domain.SubscriptionType;
+import com.seyren.core.exception.NotificationFailedException;
+import com.seyren.core.util.config.SeyrenConfig;
+
+import com.amazonaws.services.sns.AmazonSNSClient;
+import com.amazonaws.auth.ClasspathPropertiesFileCredentialsProvider;
+import com.amazonaws.regions.Region;
+import com.amazonaws.regions.Regions;
+import com.amazonaws.services.sns.model.PublishRequest;
+import com.amazonaws.services.sns.model.PublishResult;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Named
+public class SnsNotificationService implements NotificationService {
+ private static final Logger LOGGER = LoggerFactory.getLogger(SnsNotificationService.class);
+
+ private AmazonSNSClient snsClient;
+ private SeyrenConfig seyrenConfig;
+
+ @Inject
+ public SnsNotificationService(SeyrenConfig seyrenConfig) {
+ this.seyrenConfig = seyrenConfig;
+
+ String snsRegion = this.seyrenConfig.getSnsRegion();
+
+ this.snsClient = new AmazonSNSClient();
+ this.snsClient.setRegion(Region.getRegion(Regions.fromName(snsRegion)));
+ }
+
+ @Override
+ public void sendNotification(Check check, Subscription subscription, List alerts) {
+ String checkUrl = this.seyrenConfig.getBaseUrl() + "/#/checks/" + check.getId();
+ String topicArn = subscription.getTarget();
+
+ String msg = String.format("Seyren notification '%s' changed state to '%s' %s",
+ check.getName(),
+ check.getState().name(),
+ checkUrl);
+
+ LOGGER.info("Sending Notification to SNS Topic: " + topicArn + " with message " + msg);
+
+ PublishRequest publishRequest = new PublishRequest(topicArn, msg);
+ PublishResult publishResult = snsClient.publish(publishRequest);
+
+ LOGGER.info("Send Notification - " + publishResult.getMessageId());
+ }
+
+ @Override
+ public boolean canHandle(SubscriptionType subscriptionType) {
+ return subscriptionType == SubscriptionType.SNS;
+ }
+}
diff --git a/seyren-core/src/main/java/com/seyren/core/util/config/SeyrenConfig.java b/seyren-core/src/main/java/com/seyren/core/util/config/SeyrenConfig.java
index d4ef4e14..753cc2c0 100644
--- a/seyren-core/src/main/java/com/seyren/core/util/config/SeyrenConfig.java
+++ b/seyren-core/src/main/java/com/seyren/core/util/config/SeyrenConfig.java
@@ -31,7 +31,7 @@
@Named
public class SeyrenConfig {
-
+
private static final String DEFAULT_BASE_URL = "http://localhost:8080/seyren";
private final String baseUrl;
@@ -79,6 +79,7 @@ public class SeyrenConfig {
private final Integer snmpPort;
private final String snmpCommunity;
private final String snmpOID;
+ private final String snsRegion;
private final String victorOpsRestAPIEndpoint;
private final String emailTemplateFileName;
private final String emailSubjectTemplateFileName;
@@ -87,7 +88,7 @@ public class SeyrenConfig {
private final int noOfThreads;
private final String httpNotificationUrl;
public SeyrenConfig() {
-
+
// Base
this.baseUrl = stripEnd(configOrDefault("SEYREN_URL", DEFAULT_BASE_URL), "/");
this.mongoUrl = configOrDefault("MONGO_URL", "mongodb://localhost:27017/seyren");
@@ -117,12 +118,12 @@ public SeyrenConfig() {
this.smtpHost = configOrDefault("SMTP_HOST", "localhost");
this.smtpProtocol = configOrDefault("SMTP_PROTOCOL", "smtp");
this.smtpPort = Integer.parseInt(configOrDefault("SMTP_PORT", "25"));
-
+
// HipChat
this.hipChatBaseUrl = configOrDefault(list("HIPCHAT_BASEURL", "HIPCHAT_BASE_URL"), "https://api.hipchat.com");
this.hipChatAuthToken = configOrDefault(list("HIPCHAT_AUTHTOKEN", "HIPCHAT_AUTH_TOKEN"), "");
this.hipChatUsername = configOrDefault(list("HIPCHAT_USERNAME", "HIPCHAT_USER_NAME"), "Seyren Alert");
-
+
// PagerDuty
// Twilio
@@ -130,13 +131,13 @@ public SeyrenConfig() {
this.twilioAccountSid = configOrDefault("TWILIO_ACCOUNT_SID", "");
this.twilioAuthToken = configOrDefault("TWILIO_AUTH_TOKEN", "");
this.twilioPhoneNumber = configOrDefault("TWILIO_PHONE_NUMBER", "");
-
+
// OpsGenie
this.opsGenieTeams = Arrays.asList(configOrDefault("OPSGENIE_TEAMS", "ops").split(","));
-
+
// Hubot
this.hubotUrl = configOrDefault(list("HUBOT_URL", "SEYREN_HUBOT_URL"), "");
-
+
// Flowdock
this.flowdockExternalUsername = configOrDefault("FLOWDOCK_EXTERNAL_USERNAME", "Seyren");
this.flowdockTags = configOrDefault("FLOWDOCK_TAGS", "");
@@ -161,6 +162,9 @@ public SeyrenConfig() {
this.snmpCommunity = configOrDefault("SNMP_COMMUNITY", "public");
this.snmpOID = configOrDefault("SNMP_OID", "1.3.6.1.4.1.32473.1");
+ // SNS
+ this.snsRegion = configOrDefault("SNS_REGION", "eu-west-1");
+
//VictorOps
this.victorOpsRestAPIEndpoint = configOrDefault("VICTOROPS_REST_ENDPOINT", "");
@@ -172,13 +176,13 @@ public SeyrenConfig() {
this.emailTemplateFileName = configOrDefault("TEMPLATE_EMAIL_FILE_PATH","com/seyren/core/service/notification/email-template.vm");
this.emailSubjectTemplateFileName = configOrDefault("TEMPLATE_EMAIL_SUBJECT_FILE_PATH","com/seyren/core/service/notification/email-subject-template.vm");
}
-
+
@PostConstruct
public void init() {
Velocity.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM, new Slf4jLogChute());
Velocity.init();
}
-
+
public String getBaseUrl() {
return baseUrl;
}
@@ -187,7 +191,7 @@ public String getBaseUrl() {
public boolean isBaseUrlSetToDefault() {
return getBaseUrl().equals(DEFAULT_BASE_URL);
}
-
+
@JsonIgnore
public String getMongoUrl() {
return mongoUrl;
@@ -196,7 +200,7 @@ public String getMongoUrl() {
public boolean isGraphsEnabled() {
return Boolean.valueOf(graphsEnable);
}
-
+
@JsonIgnore
public String getTwilioUrl() {
return twilioUrl;
@@ -211,12 +215,12 @@ public String getTwilioAccountSid() {
public String getTwilioAuthToken() {
return twilioAuthToken;
}
-
+
@JsonIgnore
public List getOpsGenieTeams() {
return opsGenieTeams;
}
-
+
@JsonIgnore
public String getTwilioPhoneNumber() {
return twilioPhoneNumber;
@@ -231,27 +235,27 @@ public String getHipChatBaseUrl() {
public String getHipChatAuthToken() {
return hipChatAuthToken;
}
-
+
@JsonIgnore
public String getHipChatUsername() {
return hipChatUsername;
}
-
+
@JsonIgnore
public String getHubotUrl() {
return hubotUrl;
}
-
+
@JsonIgnore
public String getFlowdockExternalUsername() {
return flowdockExternalUsername;
}
-
+
@JsonIgnore
public String getFlowdockTags() {
return flowdockTags;
}
-
+
@JsonIgnore
public String getFlowdockEmojis() {
return flowdockEmojis;
@@ -276,27 +280,27 @@ public String getPushoverAppApiToken() {
public String getSmtpFrom() {
return smtpFrom;
}
-
+
@JsonIgnore
public String getSmtpUsername() {
return smtpUsername;
}
-
+
@JsonIgnore
public String getSmtpPassword() {
return smtpPassword;
}
-
+
@JsonIgnore
public String getSmtpHost() {
return smtpHost;
}
-
+
@JsonIgnore
public String getSmtpProtocol() {
return smtpProtocol;
}
-
+
@JsonIgnore
public Integer getSmtpPort() {
return smtpPort;
@@ -306,62 +310,67 @@ public Integer getSmtpPort() {
public String getSnmpHost() {
return snmpHost;
}
-
+
@JsonIgnore
public Integer getSnmpPort() {
return snmpPort;
}
-
+
@JsonIgnore
public String getSnmpCommunity() {
return snmpCommunity;
}
-
+
@JsonIgnore
public String getSnmpOID() {
return snmpOID;
}
-
+
+ @JsonIgnore
+ public String getSnsRegion() {
+ return snsRegion;
+ }
+
@JsonIgnore
public String getGraphiteUrl() {
return graphiteUrl;
}
-
+
@JsonIgnore
public String getGraphiteUsername() {
return graphiteUsername;
}
-
+
@JsonIgnore
public String getGraphitePassword() {
return graphitePassword;
}
-
+
@JsonIgnore
public String getGraphiteScheme() {
return splitBaseUrl(graphiteUrl)[0];
}
-
+
@JsonIgnore
public int getGraphiteSSLPort() {
return Integer.valueOf(splitBaseUrl(graphiteUrl)[1]);
}
-
+
@JsonIgnore
public String getGraphiteHost() {
return splitBaseUrl(graphiteUrl)[2];
}
-
+
@JsonIgnore
public String getGraphitePath() {
return splitBaseUrl(graphiteUrl)[3];
}
-
+
@JsonIgnore
public String getGraphiteKeyStore() {
return graphiteKeyStore;
}
-
+
@JsonIgnore
public String getGraphiteKeyStorePassword() {
return graphiteKeyStorePassword;
@@ -381,17 +390,17 @@ public int getGraphiteCarbonPicklePort() {
public boolean getGraphiteCarbonPickleEnable() {
return Boolean.valueOf(graphiteCarbonPickleEnable);
}
-
+
@JsonIgnore
public int getGraphiteConnectionRequestTimeout() {
return graphiteConnectionRequestTimeout;
}
-
+
@JsonIgnore
public int getGraphiteConnectTimeout() {
return graphiteConnectTimeout;
}
-
+
@JsonIgnore
public int getGraphiteSocketTimeout() {
return graphiteSocketTimeout;
@@ -452,45 +461,45 @@ public String getVictorOpsRestEndpoint() {
private static String configOrDefault(String propertyName, String defaultValue) {
return configOrDefault(list(propertyName), defaultValue);
}
-
+
private static String configOrDefault(List propertyNames, String defaultValue) {
-
+
for (String propertyName : propertyNames) {
-
+
String value = System.getProperty(propertyName);
if (isNotEmpty(value)) {
return value;
}
-
+
value = System.getenv(propertyName);
if (isNotEmpty(value)) {
return value;
}
}
-
+
return defaultValue;
}
-
+
private static List list(String... propertyNames) {
return Arrays.asList(propertyNames);
}
-
+
private static String[] splitBaseUrl(String baseUrl) {
String[] baseParts = new String[4];
-
+
if (baseUrl.contains("://")) {
baseParts[0] = baseUrl.split("://")[0];
baseUrl = baseUrl.split("://")[1];
} else {
baseParts[0] = "http";
}
-
+
if (baseUrl.contains(":")) {
baseParts[1] = baseUrl.split(":")[1];
} else {
baseParts[1] = "443";
}
-
+
if (baseUrl.contains("/")) {
baseParts[2] = baseUrl.split("/")[0];
baseParts[3] = "/" + baseUrl.split("/", 2)[1];
@@ -498,7 +507,7 @@ private static String[] splitBaseUrl(String baseUrl) {
baseParts[2] = baseUrl;
baseParts[3] = "";
}
-
+
return baseParts;
}
}
diff --git a/seyren-mongo/src/main/java/com/seyren/mongo/MongoStore.java b/seyren-mongo/src/main/java/com/seyren/mongo/MongoStore.java
index 34adbdae..fe13caa9 100644
--- a/seyren-mongo/src/main/java/com/seyren/mongo/MongoStore.java
+++ b/seyren-mongo/src/main/java/com/seyren/mongo/MongoStore.java
@@ -55,12 +55,12 @@
@Named
public class MongoStore implements ChecksStore, AlertsStore, SubscriptionsStore {
-
+
private static final Logger LOGGER = LoggerFactory.getLogger(MongoStore.class);
-
+
private MongoMapper mapper = new MongoMapper();
private DB mongo;
-
+
@Inject
public MongoStore(SeyrenConfig seyrenConfig) {
try {
@@ -75,7 +75,7 @@ public MongoStore(SeyrenConfig seyrenConfig) {
throw new RuntimeException(e);
}
}
-
+
private void bootstrapMongo() {
LOGGER.info("Bootstrapping Mongo indexes. Depending on the number of checks and alerts you've got it may take a little while.");
try {
@@ -125,11 +125,11 @@ private void addTargetHashToAlerts() {
getAlertsCollection().save(mapper.alertToDBObject(alert));
}
}
-
+
private DBCollection getChecksCollection() {
return mongo.getCollection("checks");
}
-
+
private DBCollection getAlertsCollection() {
return mongo.getCollection("alerts");
}
@@ -146,7 +146,7 @@ protected SeyrenResponse executeQueryAndCollectResponse(DBObject query) {
.withValues(checks)
.withTotal(dbc.count());
}
-
+
@Override
public SeyrenResponse getChecks(Boolean enabled, Boolean live) {
List checks = new ArrayList();
@@ -165,23 +165,23 @@ public SeyrenResponse getChecks(Boolean enabled, Boolean live) {
.withValues(checks)
.withTotal(dbc.count());
}
-
+
@Override
public SeyrenResponse getChecksByState(Set states, Boolean enabled) {
List checks = new ArrayList();
-
+
DBObject query = new BasicDBObject();
query.put("state", object("$in", states.toArray()));
if (enabled != null) {
query.put("enabled", enabled);
}
DBCursor dbc = getChecksCollection().find(query);
-
+
while (dbc.hasNext()) {
checks.add(mapper.checkFrom(dbc.next()));
}
dbc.close();
-
+
return new SeyrenResponse()
.withValues(checks)
.withTotal(dbc.count());
@@ -215,26 +215,26 @@ public Check getCheck(String checkId) {
}
return mapper.checkFrom(dbo);
}
-
+
@Override
public void deleteCheck(String checkId) {
getChecksCollection().remove(forId(checkId));
deleteAlerts(checkId, null);
}
-
+
@Override
public Check createCheck(Check check) {
check.setId(ObjectId.get().toString());
getChecksCollection().insert(mapper.checkToDBObject(check));
return check;
}
-
+
@Override
public Check saveCheck(Check check) {
DBObject findObject = forId(check.getId());
-
+
DateTime lastCheck = check.getLastCheck();
-
+
DBObject partialObject = object("name", check.getName())
.with("description", check.getDescription())
.with("target", check.getTarget())
@@ -247,28 +247,28 @@ public Check saveCheck(Check check) {
.with("allowNoData", check.isAllowNoData())
.with("lastCheck", lastCheck == null ? null : new Date(lastCheck.getMillis()))
.with("state", check.getState().toString());
-
+
DBObject setObject = object("$set", partialObject);
-
+
getChecksCollection().update(findObject, setObject);
-
+
return check;
}
-
+
@Override
public Check updateStateAndLastCheck(String checkId, AlertType state, DateTime lastCheck) {
DBObject findObject = forId(checkId);
-
+
DBObject partialObject = object("lastCheck", new Date(lastCheck.getMillis()))
.with("state", state.toString());
-
+
DBObject setObject = object("$set", partialObject);
-
+
getChecksCollection().update(findObject, setObject);
return getCheck(checkId);
}
-
+
@Override
public Alert createAlert(String checkId, Alert alert) {
alert.setId(ObjectId.get().toString());
@@ -276,7 +276,7 @@ public Alert createAlert(String checkId, Alert alert) {
getAlertsCollection().insert(mapper.alertToDBObject(alert));
return alert;
}
-
+
@Override
public SeyrenResponse getAlerts(String checkId, int start, int items) {
DBCursor dbc = getAlertsCollection().find(object("checkId", checkId)).sort(object("timestamp", -1)).skip(start).limit(items);
@@ -291,7 +291,7 @@ public SeyrenResponse getAlerts(String checkId, int start, int items) {
.withStart(start)
.withTotal(dbc.count());
}
-
+
@Override
public SeyrenResponse getAlerts(int start, int items) {
DBCursor dbc = getAlertsCollection().find().sort(object("timestamp", -1)).skip(start).limit(items);
@@ -306,18 +306,18 @@ public SeyrenResponse getAlerts(int start, int items) {
.withStart(start)
.withTotal(dbc.count());
}
-
+
@Override
public void deleteAlerts(String checkId, DateTime before) {
DBObject query = object("checkId", checkId);
-
+
if (before != null) {
query.put("timestamp", object("$lt", new Date(before.getMillis())));
}
-
+
getAlertsCollection().remove(query);
}
-
+
@Override
public Alert getLastAlertForTargetOfCheck(String target, String checkId) {
DBObject query = object("checkId", checkId).with("targetHash", TargetHash.create(target));
@@ -331,7 +331,7 @@ public Alert getLastAlertForTargetOfCheck(String target, String checkId) {
}
return null;
}
-
+
@Override
public Subscription createSubscription(String checkId, Subscription subscription) {
subscription.setId(ObjectId.get().toString());
@@ -340,14 +340,14 @@ public Subscription createSubscription(String checkId, Subscription subscription
getChecksCollection().update(check, query);
return subscription;
}
-
+
@Override
public void deleteSubscription(String checkId, String subscriptionId) {
DBObject check = forId(checkId);
BasicDBObject subscription = object("$pull", object("subscriptions", forId(subscriptionId)));
getChecksCollection().update(check, subscription);
}
-
+
@Override
public void updateSubscription(String checkId, Subscription subscription) {
DBObject subscriptionObject = mapper.subscriptionToDBObject(subscription);
@@ -356,5 +356,4 @@ public void updateSubscription(String checkId, Subscription subscription) {
DBObject updateObject = object("$set", object("subscriptions.$", subscriptionObject));
getChecksCollection().update(checkFindObject, updateObject);
}
-
}
diff --git a/seyren-web/src/main/webapp/html/modal-partial-subscription.html b/seyren-web/src/main/webapp/html/modal-partial-subscription.html
index ac5e119c..bf2281d2 100644
--- a/seyren-web/src/main/webapp/html/modal-partial-subscription.html
+++ b/seyren-web/src/main/webapp/html/modal-partial-subscription.html
@@ -31,6 +31,7 @@ Edit subscriptionOpsGenie
+