Skip to content
This repository has been archived by the owner on Feb 22, 2021. It is now read-only.

Commit

Permalink
Add markers notification sender (#470)
Browse files Browse the repository at this point in the history
Add marker notification type which sends the STARTED deployment as a logzio marker to our public markers api
(there is no smarted mechanism for disabling some of the notifiers, so let's discuss it or add one in the future)
  • Loading branch information
Kfir Shainholtz authored Jan 13, 2021
2 parents c6fd4fc + 04e8300 commit fe02397
Show file tree
Hide file tree
Showing 7 changed files with 281 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ Deployment startDeployment(Deployment deployment) {

logger.info("Started deployment id {}", deployment.getId());
deployment.setStatus(Deployment.DeploymentStatus.STARTED);
apolloNotifications.notify(Deployment.DeploymentStatus.STARTED, deployment);
return deployment;

} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import io.logz.apollo.models.Environment;
import io.logz.apollo.models.Notification;
import io.logz.apollo.models.Service;
import io.logz.apollo.notifications.senders.MarkersSender;
import io.logz.apollo.notifications.senders.NotificationSender;
import io.logz.apollo.notifications.senders.SlackSender;
import org.slf4j.Logger;
Expand Down Expand Up @@ -60,6 +61,7 @@ public void notify(Deployment.DeploymentStatus status, Deployment deployment) {
notificationDao.getAllNotifications()
.stream()
.filter(notification -> isNotificationInScope(notification, deployment))
.filter(notification -> filterNotificationByType(notification, status))
.forEach(notification -> {
NotificationTemplateMetadata notificationTemplateMetadata = new NotificationTemplateMetadata(deployment.getLastUpdate(),
status.toString(), service.getName(), environment.getName(),
Expand Down Expand Up @@ -93,6 +95,9 @@ private void processQueue() {
case SLACK:
notificationSender = new SlackSender(notificationTemplateMetadata.getNotificationJsonConfiguration());
break;
case MARKER:
notificationSender = new MarkersSender(notificationTemplateMetadata.getNotificationJsonConfiguration());
break;
default:
continue;
}
Expand Down Expand Up @@ -136,4 +141,16 @@ private boolean isNotificationInScope(Notification notification, Deployment depl
}
return false;
}

private boolean filterNotificationByType(Notification notification, Deployment.DeploymentStatus status) {
if (notification.getType() == Notification.NotificationType.SLACK && status == Deployment.DeploymentStatus.STARTED) {
return false;
}

if (notification.getType() == Notification.NotificationType.MARKER && status != Deployment.DeploymentStatus.STARTED) {
return false;
}

return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package io.logz.apollo.notifications.senders;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableList;
import io.logz.apollo.common.HttpStatus;
import io.logz.apollo.models.DeploymentMarker;
import io.logz.apollo.models.DeploymentMarkers;
import io.logz.apollo.notifications.NotificationTemplateMetadata;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import org.mariadb.jdbc.internal.logging.Logger;
import org.mariadb.jdbc.internal.logging.LoggerFactory;

import java.io.IOException;
import java.util.HashMap;

public class MarkersSender implements NotificationSender {
private static final Logger logger = LoggerFactory.getLogger(MarkersSender.class);
private final String X_API_TOKEN_HEADER = "X-API-TOKEN";
private final String apiToken;
private final String markersEndpointUrl;
private final ObjectMapper objectMapper;
private final OkHttpClient httpClient;

public MarkersSender(String notificationJsonConfiguration) throws IOException {
objectMapper = new ObjectMapper();
httpClient = new OkHttpClient();
DeploymentMarkersConfiguration markersConfiguration = objectMapper.readValue(notificationJsonConfiguration, DeploymentMarkersConfiguration.class);
this.apiToken = markersConfiguration.getApiToken();
this.markersEndpointUrl = markersConfiguration.getMarkersEndpointUrl();
}

@Override
public boolean send(NotificationTemplateMetadata notificationTemplateMetadata) {
Request.Builder builder = new Request.Builder().url(this.markersEndpointUrl)
.header(X_API_TOKEN_HEADER, this.apiToken);

DeploymentMarkers deploymentMarkers = getDeploymentMarkers(notificationTemplateMetadata);
return executeMarkersRequest(httpClient, builder, deploymentMarkers);
}

private boolean executeMarkersRequest(OkHttpClient httpClient, Request.Builder builder, DeploymentMarkers deploymentMarkers) {
try {
String markersJson = objectMapper.writeValueAsString(deploymentMarkers);

Request request = builder.post(RequestBody.create(MediaType.parse("application/json; charset=utf-8"), markersJson)).build();
try (okhttp3.Response response = httpClient.newCall(request).execute()) {
if (HttpStatus.isPositive(response.code())) {
logger.info("successfully sent marker");
return true;
} else {
logger.warn("exception while sending marker");
}
} catch (IOException e) {
logger.warn("IO Exception when trying to send deployment markers request", e);
}
} catch (JsonProcessingException jsonProcessingException) {
logger.warn("Could not process json", jsonProcessingException);
}
return false;
}

private DeploymentMarkers getDeploymentMarkers(NotificationTemplateMetadata deployment) {
DeploymentMarker deploymentMarker = new DeploymentMarker();
deploymentMarker.setTitle(deployment.getServiceName());
deploymentMarker.setDescription(String.format("Deployment to service: %s, with commit message: %s", deployment.getServiceName(), deployment.getDeploymentMessage()));

HashMap<String, String> metadata = new HashMap<>();
metadata.put("deployment_message", String.valueOf(deployment.getDeploymentMessage()));
metadata.put("deployment_id", String.valueOf(deployment.getDeploymentId()));
metadata.put("deployment_status", String.valueOf(deployment.getStatus()));
metadata.put("group_name", String.valueOf(deployment.getGroupName()));
metadata.put("service_name", String.valueOf(deployment.getServiceName()));
metadata.put("environment_name", String.valueOf(deployment.getEnvironmentName()));
deploymentMarker.setMetadata(metadata);

DeploymentMarkers deploymentMarkers = new DeploymentMarkers();
deploymentMarkers.setMarkers(ImmutableList.of(deploymentMarker));
return deploymentMarkers;
}

public static class DeploymentMarkersConfiguration {
private String apiToken;
private String markersEndpointUrl;

public DeploymentMarkersConfiguration() {
}

public String getApiToken() {
return apiToken;
}

public String getMarkersEndpointUrl() {
return markersEndpointUrl;
}
}
}


109 changes: 96 additions & 13 deletions apollo-backend/src/test/java/io/logz/apollo/NotificationsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,29 @@

public class NotificationsTest {

private static int slackMockPort = Common.getAvailablePort();
private static final String pseudoUrl = "/slack/submit";
private static String slackMockDomain = "http://localhost:" + slackMockPort + pseudoUrl;
private static int mockHttpServerPort = Common.getAvailablePort();
private static final String slackPath = "/slack/submit";
private static final String markersPath = "/markers/create-marker";
private static String slackMockDomain = "http://localhost:" + mockHttpServerPort + slackPath;
private static String markersServiceMockDomain = "http://localhost:" + mockHttpServerPort + markersPath;
private static String slackNotificationConfiguration;
private static String markerNotificationConfiguration;

private MockServerClient mockServerClient;

@Rule
public MockServerRule mockServerRule = new MockServerRule(this, slackMockPort);
public MockServerRule mockServerRule = new MockServerRule(this, mockHttpServerPort);

@BeforeClass
public static void beforeClass() {
slackNotificationConfiguration = "{\n" +
" \"channel\": \"#slack\",\n" +
" \"webhookUrl\": \""+ slackMockDomain +"\"\n" +
" \"webhookUrl\": \"" + slackMockDomain + "\"\n" +
"}";

markerNotificationConfiguration = "{\n" +
" \"apiToken\": \"bla\",\n" +
" \"markersEndpointUrl\": \"" + markersServiceMockDomain + "\"\n" +
"}";
}

Expand All @@ -57,33 +65,108 @@ public void testSlackNotification() throws Exception {

StandaloneApollo.getOrCreateServer().getInstance(ApolloNotifications.class).notify(Deployment.DeploymentStatus.DONE, deployment);

waitForRequest();
waitForRequest(slackPath);

mockServerClient.verify(
HttpRequest.request()
.withMethod("POST")
.withPath(pseudoUrl),
.withMethod("POST")
.withPath(slackPath),
VerificationTimes.exactly(1)
);
}

@Test
public void testMarkerNotification() throws Exception {
ApolloTestClient apolloTestClient = Common.signupAndLogin();
Notification notification = ModelsGenerator.createAndSubmitNotification(apolloTestClient,
NotificationType.MARKER, markerNotificationConfiguration);

Environment environment = apolloTestClient.getEnvironment(notification.getEnvironmentId());
Service service = apolloTestClient.getService(notification.getServiceId());

DeployableVersion deployableVersion = ModelsGenerator.createAndSubmitDeployableVersion(apolloTestClient, service);
Deployment deployment = ModelsGenerator.createAndSubmitDeployment(apolloTestClient, environment, service, deployableVersion);

mockMarkersService();

StandaloneApollo.getOrCreateServer().getInstance(ApolloNotifications.class).notify(Deployment.DeploymentStatus.STARTED, deployment);

waitForRequest(markersPath);

mockServerClient.verify(
HttpRequest.request()
.withMethod("POST")
.withPath(markersPath),
VerificationTimes.exactly(1)
);
}

@Test
public void testNotificationsNotSentWhenShouldBeFiltered() throws Exception {
ApolloTestClient apolloTestClient = Common.signupAndLogin();

Notification markerNotification = ModelsGenerator.createAndSubmitNotification(apolloTestClient,
NotificationType.MARKER, markerNotificationConfiguration);

ModelsGenerator.createAndSubmitNotification(apolloTestClient,
NotificationType.SLACK, slackNotificationConfiguration);

Environment environment = apolloTestClient.getEnvironment(markerNotification.getEnvironmentId());
Service service = apolloTestClient.getService(markerNotification.getServiceId());

DeployableVersion deployableVersion = ModelsGenerator.createAndSubmitDeployableVersion(apolloTestClient, service);
Deployment deployment = ModelsGenerator.createAndSubmitDeployment(apolloTestClient, environment, service, deployableVersion);

mockMarkersService();
mockSlackServer();

StandaloneApollo.getOrCreateServer().getInstance(ApolloNotifications.class).notify(Deployment.DeploymentStatus.PENDING, deployment);

waitForRequest(markersPath);

mockServerClient.verifyZeroInteractions();

StandaloneApollo.getOrCreateServer().getInstance(ApolloNotifications.class).notify(Deployment.DeploymentStatus.STARTED, deployment);

waitForRequest(slackPath);

mockServerClient.verify(
HttpRequest.request()
.withMethod("POST")
.withPath(slackPath),
VerificationTimes.exactly(0)
);
}

private void mockSlackServer() {

mockServerClient.when(
HttpRequest.request()
.withMethod("POST")
.withPath(pseudoUrl)
.withMethod("POST")
.withPath(slackPath)
).respond(
HttpResponse.response()
.withStatusCode(200)
);
}

private void mockMarkersService() {

mockServerClient.when(
HttpRequest.request()
.withMethod("POST")
.withPath(markersPath)
).respond(
HttpResponse.response()
.withStatusCode(200)
.withStatusCode(200)
);
}

private void waitForRequest() throws InterruptedException {
private void waitForRequest(String requestPath) throws InterruptedException {
long waitDuration = 100;
while (mockServerClient.retrieveRecordedRequests(HttpRequest.request()
.withMethod("POST")
.withPath(pseudoUrl)
.withPath(requestPath)
).length == 0 && waitDuration-- > 0) {
Thread.sleep(10);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package io.logz.apollo.models;

import java.util.Map;

public class DeploymentMarker {
private String description;
private String title;
private Map<String, String> metadata;
private String tag = "DEPLOYMENT";

public DeploymentMarker() {

}

public String getDescription() {
return description;
}

public void setDescription(String description) {
this.description = description;
}

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}

public Map<String, String> getMetadata() {
return metadata;
}

public void setMetadata(Map<String, String> metadata) {
this.metadata = metadata;
}

public String getTag() {
return tag;
}

public void setTag(String tag) {
this.tag = tag;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.logz.apollo.models;

import java.util.List;

public class DeploymentMarkers {
private List<DeploymentMarker> markers;

public DeploymentMarkers() {
}

public List<DeploymentMarker> getMarkers() {
return markers;
}

public void setMarkers(List<DeploymentMarker> markers) {
this.markers = markers;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public class Notification {
private NotificationType type;
private String notificationJsonConfiguration;
public enum NotificationType {
SLACK
SLACK, MARKER
}

public Notification() {
Expand Down

0 comments on commit fe02397

Please sign in to comment.