Skip to content

Commit

Permalink
Merge branch 'master' into container_builds
Browse files Browse the repository at this point in the history
  • Loading branch information
plumpy committed Feb 25, 2020
2 parents 6c67165 + afd73ca commit a21d8c7
Show file tree
Hide file tree
Showing 17 changed files with 355 additions and 115 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class SlackLegacyProperties {
String token
boolean forceUseIncomingWebhook = false
boolean sendCompactMessages = false
boolean expandUserNames = false

boolean getUseIncomingWebhook() {
return forceUseIncomingWebhook || isIncomingWebhookToken(token)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class NotificationController {
EchoResponse create(@RequestBody Notification notification) {
notificationServices?.find {
it.supportsType(notification.notificationType) &&
!notification.isInteractive() || it instanceof InteractiveNotificationService
(!notification.isInteractive() || it instanceof InteractiveNotificationService)
}?.handle(notification)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,20 @@ import retrofit.http.*

interface SlackClient {

/**
* Post a message via Slack API
*
* see https://api.slack.com/methods/chat.postMessage
* One note: linkUserNames according to slack API is a boolean. But guess what? It doesn't work, it must be an int 0 or 1
*/
@FormUrlEncoded
@POST('/api/chat.postMessage')
Response sendMessage(
@Field('token') String token,
@Field('attachments') String attachments,
@Field('channel') String channel,
@Field('as_user') boolean asUser)
@Field('as_user') boolean asUser,
@Field('link_names') Integer linkUserNames)

/**
* Documentation: https://api.slack.com/incoming-webhooks
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,16 @@ class SlackService {
SlackLegacyProperties config

Response sendCompactMessage(CompactSlackMessage message, String channel, boolean asUser) {
slackClient.sendMessage(config.token, message.buildMessage(), channel, asUser)
slackClient.sendMessage(config.token, message.buildMessage(), channel, asUser, config.expandUserNames ? 1 : 0)
}

Response sendMessage(SlackAttachment message, String channel, boolean asUser) {
config.useIncomingWebhook ?
slackClient.sendUsingIncomingWebHook(config.token, new SlackRequest([message], channel)) :
slackClient.sendMessage(config.token, toJson(message), channel, asUser)
slackClient.sendMessage(config.token, toJson(message), channel, asUser, config.expandUserNames ? 1 : 0)
}


SlackUserInfo getUserInfo(String userId) {
slackClient.getUserInfo(config.token, userId)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class NotificationControllerSpec extends Specification {
Mock(Environment)
)
notificationController = new NotificationController(
notificationServices: [ notificationService, interactiveNotificationService ],
notificationServices: [ interactiveNotificationService, notificationService ],
interactiveNotificationCallbackHandler: interactiveNotificationCallbackHandler
)
}
Expand Down Expand Up @@ -98,6 +98,22 @@ class NotificationControllerSpec extends Specification {
1 * interactiveNotificationService.handle(notification)
}

void 'only interactive notifications are delegated to interactive notification services'() {
given:
Notification nonInteractiveNotification = new Notification()
nonInteractiveNotification.notificationType = Notification.Type.PAGER_DUTY

notificationService.supportsType(Notification.Type.PAGER_DUTY) >> true
interactiveNotificationService.supportsType(Notification.Type.SLACK) >> true

when:
notificationController.create(nonInteractiveNotification)

then:
1 * notificationService.handle(nonInteractiveNotification)
0 * interactiveNotificationService.handle(nonInteractiveNotification)
}

void 'an incoming callback from the notification service delegates to the appropriate service class'() {
given:
RequestEntity<String> request = new RequestEntity<>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
Expand All @@ -31,7 +30,10 @@
@Slf4j
@Configuration
@ComponentScan(value = "com.netflix.spinnaker.echo.pipelinetriggers")
@EnableConfigurationProperties(FiatClientConfigurationProperties.class)
@EnableConfigurationProperties({
FiatClientConfigurationProperties.class,
QuietPeriodIndicatorConfigurationProperties.class
})
public class PipelineTriggerConfiguration {
private Client retrofitClient;
private RequestInterceptor requestInterceptor;
Expand Down Expand Up @@ -73,12 +75,6 @@ PubsubEventHandler pubsubEventHandler(
return new PubsubEventHandler(registry, objectMapper, fiatPermissionEvaluator);
}

@Bean
@ConfigurationProperties(prefix = "quiet-period")
public QuietPeriodIndicatorConfigurationProperties quietPeriodIndicatorConfigurationProperties() {
return new QuietPeriodIndicatorConfigurationProperties();
}

@Bean
public ExecutorService executorService(
@Value("${orca.pipeline-initiator-threadpool-size:16}") int threadPoolSize) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,64 @@

package com.netflix.spinnaker.echo.config;

import static java.time.format.DateTimeFormatter.ISO_INSTANT;

import com.google.common.base.Strings;
import com.netflix.spinnaker.kork.dynamicconfig.DynamicConfigService;
import java.time.Instant;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.List;
import lombok.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;

@Data
@NoArgsConstructor
@AllArgsConstructor
@ConfigurationProperties(prefix = "quiet-period")
public class QuietPeriodIndicatorConfigurationProperties {

boolean enabled;
private List<String> suppressedTriggerTypes = new ArrayList<>();
private DynamicConfigService dynamicConfigService;
private Logger log = LoggerFactory.getLogger(this.getClass().getName());

public QuietPeriodIndicatorConfigurationProperties(DynamicConfigService dynamicConfigService) {
this.dynamicConfigService = dynamicConfigService;
}

public boolean isEnabled() {
return (dynamicConfigService.isEnabled("quiet-period", true)
&& getStartTime() > 0
&& getEndTime() > 0);
}

public long getStartTime() {
return parseIso(dynamicConfigService.getConfig(String.class, "quiet-period.start-iso", ""));
}

public long getEndTime() {
return parseIso(dynamicConfigService.getConfig(String.class, "quiet-period.end-iso", ""));
}

String startIso;
public List<String> getSuppressedTriggerTypes() {
return suppressedTriggerTypes;
}

String endIso;
public void setSuppressedTriggerTypes(List<String> suppressedTriggerTypes) {
this.suppressedTriggerTypes = suppressedTriggerTypes;
}

List<String> suppressedTriggerTypes = new ArrayList<>();
private long parseIso(String iso) {
if (Strings.isNullOrEmpty(iso)) {
return -1;
}
try {
Instant instant = Instant.from(ISO_INSTANT.parse(iso));
return instant.toEpochMilli();
} catch (DateTimeParseException e) {
log.warn(
"Unable to parse {} as an ISO date/time, disabling quiet periods: {}",
iso,
e.getMessage());
return -1;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ private Map<String, Object> planPipelineIfNeeded(
Map<String, Object> pipeline, Predicate<Map<String, Object>> isV2Pipeline) {
if (isV2Pipeline.test(pipeline)) {
try {
return orca.v2Plan(pipeline);
return AuthenticatedRequest.allowAnonymous(() -> orca.v2Plan(pipeline));
} catch (Exception e) {
// Don't fail the entire cache cycle if we fail a plan.
log.error("Caught exception while planning templated pipeline: {}", pipeline, e);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright 2020 Netflix, Inc.
*
* 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.netflix.spinnaker.echo.pipelinetriggers;

import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RequestMapping("/quietPeriod")
@RestController
public class QuietPeriodController {
private QuietPeriodIndicator quietPeriodIndicator;

@Autowired
public QuietPeriodController(QuietPeriodIndicator quietPeriodIndicator) {
this.quietPeriodIndicator = quietPeriodIndicator;
}

@GetMapping
QuietPeriodStatus getQuietPeriodStatus() {
QuietPeriodStatus qps = new QuietPeriodStatus();

qps.isEnabled = quietPeriodIndicator.isEnabled();
qps.isInQuietPeriod = quietPeriodIndicator.inQuietPeriod(System.currentTimeMillis());
qps.startTime = quietPeriodIndicator.getStartTime();
qps.endTime = quietPeriodIndicator.getEndTime();

return qps;
}

@Data
private static class QuietPeriodStatus {
private boolean isEnabled;
private boolean isInQuietPeriod;
private long startTime;
private long endTime;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,9 @@

package com.netflix.spinnaker.echo.pipelinetriggers;

import static java.time.format.DateTimeFormatter.ISO_INSTANT;

import com.netflix.spectator.api.Id;
import com.netflix.spectator.api.Registry;
import com.netflix.spectator.api.patterns.PolledMeter;
import com.netflix.spinnaker.echo.config.QuietPeriodIndicatorConfigurationProperties;
import java.time.Instant;
import java.time.format.DateTimeParseException;
import java.util.List;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -39,84 +33,50 @@
@Component
public class QuietPeriodIndicator {
private final Registry registry;

private final boolean enabled;
private final long startEpochMillis;
private final long endEpochMillis;
private final List<String> suppressedTriggerTypes;

private final Id quietPeriodTestId;

private final QuietPeriodIndicatorConfigurationProperties config;

@Autowired
public QuietPeriodIndicator(
@NonNull Registry registry, @NonNull QuietPeriodIndicatorConfigurationProperties config) {
this.registry = registry;
this.config = config;

long startEpochMillis = parseIso(config.getStartIso());
long endEpochMillis = parseIso(config.getEndIso());
this.startEpochMillis = startEpochMillis;
this.endEpochMillis = endEpochMillis;
this.suppressedTriggerTypes = config.getSuppressedTriggerTypes();

this.enabled = config.isEnabled() && (startEpochMillis > 0 && endEpochMillis > 0);
if (this.enabled) {
log.warn(
"Enabling quiet periods. Span in millis: {} to {}, ISO {} to {} (inclusive)",
startEpochMillis,
endEpochMillis,
config.getStartIso(),
config.getEndIso());
log.warn(
"Will suppress triggers of types {} during quiet periods.",
config.getSuppressedTriggerTypes());
}

PolledMeter.using(registry)
.withName("quietPeriod.enabled")
.monitorValue(this, QuietPeriodIndicator::gaugeMonitor);
quietPeriodTestId = registry.createId("quietPeriod.tests");
}

public boolean isEnabled() {
return enabled;
return config.isEnabled();
}

private double gaugeMonitor() {
return enabled ? 1.0 : 0.0;
public long getStartTime() {
return config.getStartTime();
}

public boolean inQuietPeriod(long now) {
boolean result = enabled && (now >= startEpochMillis && now <= endEpochMillis);
public long getEndTime() {
return config.getEndTime();
}

public boolean inQuietPeriod(long testTime) {
boolean result =
isEnabled() && (testTime >= config.getStartTime() && testTime <= config.getEndTime());
registry.counter(quietPeriodTestId.withTag("result", result)).increment();

return result;
}

private boolean shouldSuppressType(String triggerType) {
for (String trigger : suppressedTriggerTypes) {
for (String trigger : config.getSuppressedTriggerTypes()) {
if (trigger.equalsIgnoreCase(triggerType)) {
return true;
}
}
return false;
}

public boolean inQuietPeriod(long now, String triggerType) {
return inQuietPeriod(now) && shouldSuppressType(triggerType);
return false;
}

private long parseIso(String iso) {
if (iso == null) {
return -1;
}
try {
Instant instant = Instant.from(ISO_INSTANT.parse(iso));
return instant.toEpochMilli();
} catch (DateTimeParseException e) {
log.warn(
"Unable to parse {} as an ISO date/time, disabling quiet periods: {}",
iso,
e.getMessage());
return -1;
}
public boolean inQuietPeriod(long testTime, String triggerType) {
return inQuietPeriod(testTime) && shouldSuppressType(triggerType);
}
}
Loading

0 comments on commit a21d8c7

Please sign in to comment.