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 @@