Skip to content

Commit

Permalink
Migrate alert receivers (#3120)
Browse files Browse the repository at this point in the history
* Add list configuration field

We need a field to move email receivers into the email callback
model.

* Add missing prop to DropdownField

* Add support to get list in `Configuration`

* Move email receivers to `EmailAlarmCallback`

- Make user/email receivers part of the `EmailAlarmCallback` model
- Add configuration fields to the callback, ensuring we do not offer
user auto-completion if the user does not have permissions to list users
- Move logic to resolve email addresses to its own class

* Clean up alert receivers

Clean up frontend code related to alert receivers.

* Add Builder to AlarmCallbackConfigurationAVImpl

This allows us to modify the object instead of creating new ones.

* More clean up of alert receivers

This time remove some unused backend code, and adapt previous API to
add/remove receivers to use an `EmailAlarmCallback` if available.

* Convert EmailAlarmCallback migration into a Migration

We need to do this to ensure it runs before the upcoming alert receivers
migration. I also changed the event that gets stored in the cluster
config, as that was still not release and there's no harm running the
migration again in our development machines.

* Move existing alert receivers to EmailAlarmCallback

The migration will move all alert receivers from the streams having at
least a condition and an EmailAlarmCallback.

* Fix condition to run EmailAlarmCallback migrations

Also modified some tests to ensure this works as expected.
  • Loading branch information
edmundoa authored and bernd committed Nov 30, 2016
1 parent 05c4e83 commit 31e5304
Show file tree
Hide file tree
Showing 45 changed files with 1,160 additions and 479 deletions.
Expand Up @@ -56,6 +56,8 @@ public abstract class AlarmCallbackConfigurationAVImpl implements AlarmCallbackC
@Override @Override
public abstract String getCreatorUserId(); public abstract String getCreatorUserId();


public abstract Builder toBuilder();

@JsonCreator @JsonCreator
public static AlarmCallbackConfigurationAVImpl create(@JsonProperty("_id") String id, public static AlarmCallbackConfigurationAVImpl create(@JsonProperty("_id") String id,
@JsonProperty("stream_id") String streamId, @JsonProperty("stream_id") String streamId,
Expand All @@ -73,6 +75,30 @@ public static AlarmCallbackConfigurationAVImpl create(@JsonProperty("_id") Strin
@JsonProperty("configuration") Map<String, Object> configuration, @JsonProperty("configuration") Map<String, Object> configuration,
@JsonProperty("created_at") Date createdAt, @JsonProperty("created_at") Date createdAt,
@JsonProperty("creator_user_id") String creatorUserId) { @JsonProperty("creator_user_id") String creatorUserId) {
return new AutoValue_AlarmCallbackConfigurationAVImpl(id, streamId, type, configuration, createdAt, creatorUserId); return new AutoValue_AlarmCallbackConfigurationAVImpl.Builder()
.setId(id)
.setStreamId(streamId)
.setType(type)
.setConfiguration(configuration)
.setCreatedAt(createdAt)
.setCreatorUserId(creatorUserId)
.build();
}

@AutoValue.Builder
public abstract static class Builder {
public abstract Builder setId(String id);

public abstract Builder setStreamId(String streamId);

public abstract Builder setType(String type);

public abstract Builder setConfiguration(Map<String, Object> configuration);

public abstract Builder setCreatedAt(Date createdAt);

public abstract Builder setCreatorUserId(String creatorUserId);

public abstract AlarmCallbackConfigurationAVImpl build();
} }
} }
Expand Up @@ -24,9 +24,6 @@
import javax.inject.Inject; import javax.inject.Inject;
import java.util.Set; import java.util.Set;


/**
* @author Dennis Oelkers <dennis@torch.sh>
*/
public class AlarmCallbackFactory { public class AlarmCallbackFactory {
private Injector injector; private Injector injector;
private final Set<Class<? extends AlarmCallback>> availableAlarmCallbacks; private final Set<Class<? extends AlarmCallback>> availableAlarmCallbacks;
Expand Down
Expand Up @@ -18,6 +18,7 @@


import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import org.graylog2.alerts.AlertSender; import org.graylog2.alerts.AlertSender;
import org.graylog2.alerts.EmailRecipients;
import org.graylog2.alerts.FormattedEmailAlertSender; import org.graylog2.alerts.FormattedEmailAlertSender;
import org.graylog2.notifications.Notification; import org.graylog2.notifications.Notification;
import org.graylog2.notifications.NotificationService; import org.graylog2.notifications.NotificationService;
Expand All @@ -31,46 +32,60 @@
import org.graylog2.plugin.configuration.ConfigurationException; import org.graylog2.plugin.configuration.ConfigurationException;
import org.graylog2.plugin.configuration.ConfigurationRequest; import org.graylog2.plugin.configuration.ConfigurationRequest;
import org.graylog2.plugin.configuration.fields.ConfigurationField; import org.graylog2.plugin.configuration.fields.ConfigurationField;
import org.graylog2.plugin.configuration.fields.ListField;
import org.graylog2.plugin.configuration.fields.TextField; import org.graylog2.plugin.configuration.fields.TextField;
import org.graylog2.plugin.database.users.User;
import org.graylog2.plugin.streams.Stream; import org.graylog2.plugin.streams.Stream;
import org.graylog2.plugin.system.NodeId; import org.graylog2.plugin.system.NodeId;
import org.graylog2.shared.users.UserService;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;


import javax.inject.Inject; import javax.inject.Inject;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors;


public class EmailAlarmCallback implements AlarmCallback { public class EmailAlarmCallback implements AlarmCallback {
private static final Logger LOG = LoggerFactory.getLogger(EmailAlarmCallback.class); private static final Logger LOG = LoggerFactory.getLogger(EmailAlarmCallback.class);

public static final String CK_USER_RECEIVERS = "user_receivers";
public static final String CK_EMAIL_RECEIVERS = "email_receivers";

private final AlertSender alertSender; private final AlertSender alertSender;
private final NotificationService notificationService; private final NotificationService notificationService;
private final NodeId nodeId; private final NodeId nodeId;
private final EmailRecipients.Factory emailRecipientsFactory;
private final UserService userService;
private Configuration configuration; private Configuration configuration;


@Inject @Inject
public EmailAlarmCallback(AlertSender alertSender, public EmailAlarmCallback(AlertSender alertSender,
NotificationService notificationService, NotificationService notificationService,
NodeId nodeId) { NodeId nodeId,
EmailRecipients.Factory emailRecipientsFactory, UserService userService) {
this.alertSender = alertSender; this.alertSender = alertSender;
this.notificationService = notificationService; this.notificationService = notificationService;
this.nodeId = nodeId; this.nodeId = nodeId;
this.emailRecipientsFactory = emailRecipientsFactory;
this.userService = userService;
} }


@Override @Override
public void call(Stream stream, AlertCondition.CheckResult result) { public void call(Stream stream, AlertCondition.CheckResult result) {
// Send alerts. // Send alerts.
AlertCondition alertCondition = result.getTriggeredCondition(); AlertCondition alertCondition = result.getTriggeredCondition();
if (stream.getAlertReceivers().size() > 0) { final EmailRecipients emailRecipients = this.getEmailRecipients();
if (!emailRecipients.isEmpty()) {
try { try {
if (alertCondition.getBacklog() > 0 && result.getMatchingMessages() != null) { if (alertCondition.getBacklog() > 0 && result.getMatchingMessages() != null) {
alertSender.sendEmails(stream, result, getAlarmBacklog(result)); alertSender.sendEmails(stream, emailRecipients, result, getAlarmBacklog(result));
} else { } else {
alertSender.sendEmails(stream, result); alertSender.sendEmails(stream, emailRecipients, result);
} }
} catch (TransportConfigurationException e) { } catch (TransportConfigurationException e) {
LOG.warn("Stream [{}] has alert receivers and is triggered, but email transport is not configured.", stream); LOG.warn("Stream [{}] has email recipients and is triggered, but email transport is not configured.", stream);
Notification notification = notificationService.buildNow() Notification notification = notificationService.buildNow()
.addNode(nodeId.toString()) .addNode(nodeId.toString())
.addType(Notification.Type.EMAIL_TRANSPORT_CONFIGURATION_INVALID) .addType(Notification.Type.EMAIL_TRANSPORT_CONFIGURATION_INVALID)
Expand All @@ -79,7 +94,7 @@ public void call(Stream stream, AlertCondition.CheckResult result) {
.addDetail("exception", e.getMessage()); .addDetail("exception", e.getMessage());
notificationService.publishIfFirst(notification); notificationService.publishIfFirst(notification);
} catch (Exception e) { } catch (Exception e) {
LOG.error("Stream [" + stream + "] has alert receivers and is triggered, but sending emails failed", e); LOG.error("Stream [" + stream + "] has email recipients and is triggered, but sending emails failed", e);


String exceptionDetail = e.toString(); String exceptionDetail = e.toString();
if (e.getCause() != null) { if (e.getCause() != null) {
Expand All @@ -97,6 +112,13 @@ public void call(Stream stream, AlertCondition.CheckResult result) {
} }
} }


private EmailRecipients getEmailRecipients() {
return emailRecipientsFactory.create(
configuration.getList(CK_USER_RECEIVERS, Collections.emptyList()),
configuration.getList(CK_EMAIL_RECEIVERS, Collections.emptyList())
);
}

protected List<Message> getAlarmBacklog(AlertCondition.CheckResult result) { protected List<Message> getAlarmBacklog(AlertCondition.CheckResult result) {
final AlertCondition alertCondition = result.getTriggeredCondition(); final AlertCondition alertCondition = result.getTriggeredCondition();
final List<MessageSummary> matchingMessages = result.getMatchingMessages(); final List<MessageSummary> matchingMessages = result.getMatchingMessages();
Expand Down Expand Up @@ -124,8 +146,8 @@ public void initialize(Configuration config) throws AlarmCallbackConfigurationEx
this.alertSender.initialize(configuration); this.alertSender.initialize(configuration);
} }


@Override // I am truly sorry about this, but leaking the user list is not okay...
public ConfigurationRequest getRequestedConfiguration() { private ConfigurationRequest getConfigurationRequest(Map<String, String> userNames) {
ConfigurationRequest configurationRequest = new ConfigurationRequest(); ConfigurationRequest configurationRequest = new ConfigurationRequest();
configurationRequest.addField(new TextField("sender", configurationRequest.addField(new TextField("sender",
"Sender", "Sender",
Expand All @@ -146,9 +168,37 @@ public ConfigurationRequest getRequestedConfiguration() {
ConfigurationField.Optional.OPTIONAL, ConfigurationField.Optional.OPTIONAL,
TextField.Attribute.TEXTAREA)); TextField.Attribute.TEXTAREA));


configurationRequest.addField(new ListField(CK_USER_RECEIVERS,
"User Receivers",
Collections.emptyList(),
userNames,
"Graylog usernames that should receive this alert",
ConfigurationField.Optional.OPTIONAL));

configurationRequest.addField(new ListField(CK_EMAIL_RECEIVERS,
"E-Mail Receivers",
Collections.emptyList(),
Collections.emptyMap(),
"E-Mail addresses that should receive this alert",
ConfigurationField.Optional.OPTIONAL,
ListField.Attribute.ALLOW_CREATE));

return configurationRequest; return configurationRequest;
} }


@Override
public ConfigurationRequest getRequestedConfiguration() {
return getConfigurationRequest(Collections.emptyMap());
}

/* This method should be used when we want to provide user auto-completion to users that have permissions for it */
public ConfigurationRequest getEnrichedRequestedConfiguration() {
final Map<String, String> userNames = userService.loadAll().stream()
.collect(Collectors.toMap(User::getName, User::getName));

return getConfigurationRequest(userNames);
}

@Override @Override
public String getName() { public String getName() {
return "Email Alert Callback"; return "Email Alert Callback";
Expand Down

This file was deleted.

Expand Up @@ -25,13 +25,10 @@


import java.util.List; import java.util.List;


/**
* @author Dennis Oelkers <dennis@torch.sh>
*/
public interface AlertSender { public interface AlertSender {
void initialize(Configuration configuration); void initialize(Configuration configuration);


void sendEmails(Stream stream, AlertCondition.CheckResult checkResult) throws TransportConfigurationException, EmailException; void sendEmails(Stream stream, EmailRecipients recipients, AlertCondition.CheckResult checkResult) throws TransportConfigurationException, EmailException;


void sendEmails(Stream stream, AlertCondition.CheckResult checkResult, List<Message> backlog) throws TransportConfigurationException, EmailException; void sendEmails(Stream stream, EmailRecipients recipients, AlertCondition.CheckResult checkResult, List<Message> backlog) throws TransportConfigurationException, EmailException;
} }
@@ -0,0 +1,81 @@
/**
* This file is part of Graylog.
*
* Graylog 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, either version 3 of the License, or
* (at your option) any later version.
*
* Graylog 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 Graylog. If not, see <http://www.gnu.org/licenses/>.
*/
package org.graylog2.alerts;

import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import org.graylog2.plugin.database.users.User;
import org.graylog2.shared.users.UserService;

import java.util.List;
import java.util.Set;

import static com.google.common.base.Strings.isNullOrEmpty;

public class EmailRecipients {
private final UserService userService;

private final List<String> usernames;
private final List<String> emails;

private Set<String> resolvedEmails;

public interface Factory {
EmailRecipients create(
@Assisted("usernames") List<String> usernames,
@Assisted("emails") List<String> emails);
}

@Inject
public EmailRecipients(UserService userService,
@Assisted("usernames") List<String> usernames,
@Assisted("emails") List<String> emails) {
this.userService = userService;
this.usernames = usernames;
this.emails = emails;
}

public Set<String> getEmailRecipients() {
if (resolvedEmails != null) {
return resolvedEmails;
}

final ImmutableSet.Builder<String> emails = ImmutableSet.builder();
emails.addAll(this.emails);

for (String username : usernames) {
final User user = userService.load(username);

if (user != null && !isNullOrEmpty(user.getEmail())) {
// LDAP users might have multiple email addresses defined.
// See: https://github.com/Graylog2/graylog2-server/issues/1439
final Iterable<String> addresses = Splitter.on(",").omitEmptyStrings().trimResults().split(user.getEmail());
emails.addAll(addresses);
}
}

resolvedEmails = emails.build();

return resolvedEmails;
}

public boolean isEmpty() {
return usernames.isEmpty() && emails.isEmpty();
}
}
Expand Up @@ -24,7 +24,6 @@
import org.graylog2.plugin.configuration.Configuration; import org.graylog2.plugin.configuration.Configuration;
import org.graylog2.plugin.streams.Stream; import org.graylog2.plugin.streams.Stream;
import org.graylog2.plugin.system.NodeId; import org.graylog2.plugin.system.NodeId;
import org.graylog2.shared.users.UserService;
import org.graylog2.streams.StreamRuleService; import org.graylog2.streams.StreamRuleService;


import javax.inject.Inject; import javax.inject.Inject;
Expand Down Expand Up @@ -63,10 +62,9 @@ public class FormattedEmailAlertSender extends StaticEmailAlertSender implements
@Inject @Inject
public FormattedEmailAlertSender(EmailConfiguration configuration, public FormattedEmailAlertSender(EmailConfiguration configuration,
StreamRuleService streamRuleService, StreamRuleService streamRuleService,
UserService userService,
NotificationService notificationService, NotificationService notificationService,
NodeId nodeId) { NodeId nodeId) {
super(configuration, streamRuleService, userService, notificationService, nodeId); super(configuration, streamRuleService, notificationService, nodeId);
} }


@Override @Override
Expand Down

0 comments on commit 31e5304

Please sign in to comment.