Skip to content

Commit

Permalink
feat(newrelic): added ability for operators to set default scope and …
Browse files Browse the repository at this point in the history
…location keys. Also Added inline template support. (spinnaker#603)
  • Loading branch information
fieldju authored Aug 1, 2019
1 parent 22c5707 commit 8269040
Show file tree
Hide file tree
Showing 13 changed files with 610 additions and 109 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,11 @@ private static void populateTemplateBindings(
}
}

String propertyValue = (String) propertyDescriptor.get().getReadMethod().invoke(bean);
// Some stuff like start, end and step can't be cast to a string.
String propertyValue =
Optional.ofNullable(propertyDescriptor.get().getReadMethod().invoke(bean))
.map(String::valueOf)
.orElse(null);

if (!StringUtils.isEmpty(propertyValue)) {
templateBindings.put(baseScopeAttribute, propertyValue);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,44 @@

import com.fasterxml.jackson.annotation.JsonTypeName;
import com.netflix.kayenta.canary.CanaryMetricSetQueryConfig;
import javax.validation.constraints.NotNull;
import javax.annotation.Nullable;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.util.StringUtils;

@Builder
@Builder(toBuilder = true)
@ToString
@NoArgsConstructor
@AllArgsConstructor
@JsonTypeName("newrelic")
public class NewRelicCanaryMetricSetQueryConfig implements CanaryMetricSetQueryConfig {

@Getter private String q;
public static final String SERVICE_TYPE = "newrelic";

@NotNull @Getter private String select;
@Nullable @Getter private String q;

@Getter private String resolution;
@Nullable @Getter private String select;

@Nullable @Getter private String customInlineTemplate;

@Getter private String customFilterTemplate;

@Override
public CanaryMetricSetQueryConfig cloneWithEscapedInlineTemplate() {
if (StringUtils.isEmpty(customInlineTemplate)) {
return this;
} else {
return this.toBuilder()
.customInlineTemplate(customInlineTemplate.replace("${", "$\\{"))
.build();
}
}

@Override
public String getServiceType() {
return "newrelic";
return SERVICE_TYPE;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
package com.netflix.kayenta.newrelic.canary;

import com.netflix.kayenta.canary.CanaryScope;
import javax.validation.constraints.NotNull;
import javax.annotation.Nullable;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
Expand All @@ -27,5 +27,6 @@
@ToString(callSuper = true)
public class NewRelicCanaryScope extends CanaryScope {

@NotNull private String scopeKey;
@Nullable private String scopeKey;
@Nullable private String locationKey;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,44 +16,42 @@

package com.netflix.kayenta.newrelic.canary;

import static com.netflix.kayenta.canary.providers.metrics.NewRelicCanaryMetricSetQueryConfig.SERVICE_TYPE;

import com.netflix.kayenta.canary.CanaryScope;
import com.netflix.kayenta.canary.CanaryScopeFactory;
import java.util.Map;
import java.util.Optional;
import org.springframework.stereotype.Component;

@Component
public class NewRelicCanaryScopeFactory implements CanaryScopeFactory {
private static final String SCOPE_KEY_KEY = "_scope_key";
public static final String SCOPE_KEY_KEY = "_scope_key";
public static String LOCATION_KEY_KEY = "_location_key";

@Override
public boolean handles(String serviceType) {
return "newrelic".equals(serviceType);
return SERVICE_TYPE.equals(serviceType);
}

@Override
public CanaryScope buildCanaryScope(CanaryScope canaryScope) {
Map<String, String> extendedParameters =
Optional.ofNullable(canaryScope.getExtendedScopeParams())
.orElseThrow(
() -> new IllegalArgumentException("New Relic requires extended parameters"));

NewRelicCanaryScope newRelicCanaryScope = new NewRelicCanaryScope();
newRelicCanaryScope.setScope(canaryScope.getScope());
newRelicCanaryScope.setLocation(canaryScope.getLocation());
newRelicCanaryScope.setStart(canaryScope.getStart());
newRelicCanaryScope.setEnd(canaryScope.getEnd());
newRelicCanaryScope.setStep(canaryScope.getStep());
newRelicCanaryScope.setExtendedScopeParams(extendedParameters);
newRelicCanaryScope.setScopeKey(getRequiredExtendedParam(SCOPE_KEY_KEY, extendedParameters));

return newRelicCanaryScope;
}
Optional.ofNullable(canaryScope.getExtendedScopeParams())
.ifPresent(
extendedParameters -> {
newRelicCanaryScope.setScopeKey(extendedParameters.getOrDefault(SCOPE_KEY_KEY, null));
newRelicCanaryScope.setLocationKey(
extendedParameters.getOrDefault(LOCATION_KEY_KEY, null));
newRelicCanaryScope.setExtendedScopeParams(extendedParameters);
});

private String getRequiredExtendedParam(String key, Map<String, String> extendedParameters) {
if (!extendedParameters.containsKey(key)) {
throw new IllegalArgumentException(
String.format("New Relic requires that %s is set in the extended scope params", key));
}
return extendedParameters.get(key);
return newRelicCanaryScope;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@
import com.netflix.kayenta.security.AccountCredentials;
import com.netflix.kayenta.security.AccountCredentialsRepository;
import com.squareup.okhttp.OkHttpClient;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
Expand Down Expand Up @@ -59,14 +60,38 @@ NewRelicConfigurationProperties newrelicConfigurationProperties() {
return new NewRelicConfigurationTestControllerDefaultProperties();
}

/**
* Create a map of account name to scope configurations. This allows operators to define default
* scope and location keys which can sometimes be the same for all users within an org.
*
* <p>For example, some companies always add the attribute region to be the current deployed
* region, so the defaultLocation key can be `region`
*
* @param newrelicConfigurationProperties Wrapper object around the list of configured accounts.
* @return map of account name to default scope configurations.
*/
@Bean
Map<String, NewRelicScopeConfiguration> newrelicScopeConfigurationMap(
NewRelicConfigurationProperties newrelicConfigurationProperties) {
return newrelicConfigurationProperties.getAccounts().stream()
.collect(
Collectors.toMap(
NewRelicManagedAccount::getName,
accountConfig ->
NewRelicScopeConfiguration.builder()
.defaultScopeKey(accountConfig.getDefaultScopeKey())
.defaultLocationKey(accountConfig.getDefaultLocationKey())
.build()));
}

@Bean
MetricsService newrelicMetricsService(
NewRelicConfigurationProperties newrelicConfigurationProperties,
RetrofitClientFactory retrofitClientFactory,
ObjectMapper objectMapper,
OkHttpClient okHttpClient,
AccountCredentialsRepository accountCredentialsRepository)
throws IOException {
AccountCredentialsRepository accountCredentialsRepository) {

NewRelicMetricsService.NewRelicMetricsServiceBuilder metricsServiceBuilder =
NewRelicMetricsService.builder();

Expand Down Expand Up @@ -106,8 +131,11 @@ MetricsService newrelicMetricsService(
}

log.info(
"Populated NewRelicMetricsService with {} NewRelic accounts.",
newrelicConfigurationProperties.getAccounts().size());
"Configured the New Relic Metrics Service with the following accounts: {}",
newrelicConfigurationProperties.getAccounts().stream()
.map(NewRelicManagedAccount::getName)
.collect(Collectors.joining(",")));

return metricsServiceBuilder.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.netflix.kayenta.retrofit.config.RemoteService;
import com.netflix.kayenta.security.AccountCredentials;
import java.util.List;
import javax.annotation.Nullable;
import javax.validation.constraints.NotNull;
import lombok.Data;

Expand All @@ -31,5 +32,9 @@ public class NewRelicManagedAccount {

@NotNull private RemoteService endpoint;

@Nullable private String defaultScopeKey;

@Nullable private String defaultLocationKey;

private List<AccountCredentials.Type> supportedTypes;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.netflix.kayenta.newrelic.config;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class NewRelicScopeConfiguration {

private String defaultScopeKey;
private String defaultLocationKey;
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,25 @@
package com.netflix.kayenta.newrelic.controller;

import static com.netflix.kayenta.canary.util.FetchControllerUtils.determineDefaultProperty;
import static com.netflix.kayenta.newrelic.canary.NewRelicCanaryScopeFactory.LOCATION_KEY_KEY;
import static com.netflix.kayenta.newrelic.canary.NewRelicCanaryScopeFactory.SCOPE_KEY_KEY;

import com.netflix.kayenta.canary.CanaryMetricConfig;
import com.netflix.kayenta.canary.CanaryScope;
import com.netflix.kayenta.canary.providers.metrics.NewRelicCanaryMetricSetQueryConfig;
import com.netflix.kayenta.metrics.SynchronousQueryProcessor;
import com.netflix.kayenta.newrelic.canary.NewRelicCanaryScope;
import com.netflix.kayenta.newrelic.config.NewRelicConfigurationTestControllerDefaultProperties;
import com.netflix.kayenta.security.AccountCredentials;
import com.netflix.kayenta.security.AccountCredentialsRepository;
import com.netflix.kayenta.security.CredentialsHelper;
import io.swagger.annotations.ApiParam;
import java.io.IOException;
import java.time.Instant;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import javax.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/fetch/newrelic")
Expand Down Expand Up @@ -65,16 +63,21 @@ public NewRelicFetchController(
public Map queryMetrics(
@RequestParam(required = false) final String metricsAccountName,
@RequestParam(required = false) final String storageAccountName,
@ApiParam(defaultValue = "cpu") @RequestParam String metricSetName,
@ApiParam(defaultValue = "avg:system.cpu.user") @RequestParam String metricName,
@ApiParam(required = true) @Valid @RequestBody NewRelicFetchRequest newRelicFetchRequest,
@ApiParam(value = "The scope of the NewRelic query. e.g. autoscaling_group:myapp-prod-v002")
@RequestParam(required = false)
String scope,
@ApiParam(value = "The location of the NewRelic query. e.g. us-west-2")
@RequestParam(required = false)
String location,
@ApiParam(value = "An ISO format timestamp, e.g.: 2018-03-15T01:23:45Z") @RequestParam
String start,
@ApiParam(value = "An ISO format timestamp, e.g.: 2018-03-15T01:23:45Z") @RequestParam
String end,
@ApiParam(defaultValue = "60", value = "seconds") @RequestParam Long step,
@ApiParam(defaultValue = "0", value = "canary config metrics index")
@RequestParam(required = false)
Integer metricIndex,
@ApiParam(defaultValue = "false") @RequestParam(required = false) final boolean dryRun)
throws IOException {

Expand Down Expand Up @@ -105,25 +108,28 @@ public Map queryMetrics(
CredentialsHelper.resolveAccountByNameOrType(
storageAccountName, AccountCredentials.Type.OBJECT_STORE, accountCredentialsRepository);

NewRelicCanaryMetricSetQueryConfig newrelicCanaryMetricSetQueryConfig =
NewRelicCanaryMetricSetQueryConfig.builder().q(metricName).build();
NewRelicCanaryScope canaryScope = new NewRelicCanaryScope();
canaryScope.setScope(scope);

CanaryMetricConfig canaryMetricConfig =
CanaryMetricConfig.builder()
.name(metricSetName)
.query(newrelicCanaryMetricSetQueryConfig)
.build();
Optional.ofNullable(newRelicFetchRequest.extendedScopeParams)
.ifPresent(
esp -> {
canaryScope.setLocationKey(esp.getOrDefault(LOCATION_KEY_KEY, null));
canaryScope.setScopeKey(esp.getOrDefault(SCOPE_KEY_KEY, null));
});

CanaryScope canaryScope =
new CanaryScope(
scope, null, Instant.parse(start), Instant.parse(end), step, Collections.emptyMap());
canaryScope.setLocation(location);
canaryScope.setStart(Instant.parse(start));
canaryScope.setEnd(Instant.parse(end));
canaryScope.setStep(step);
canaryScope.setExtendedScopeParams(newRelicFetchRequest.extendedScopeParams);

return synchronousQueryProcessor.processQueryAndReturnMap(
resolvedMetricsAccountName,
resolvedStorageAccountName,
null,
canaryMetricConfig,
0,
newRelicFetchRequest.getCanaryConfig(),
newRelicFetchRequest.getCanaryMetricConfig(),
Optional.ofNullable(metricIndex).orElse(0),
canaryScope,
dryRun);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2019 Nike, 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.kayenta.newrelic.controller;

import com.netflix.kayenta.canary.CanaryConfig;
import com.netflix.kayenta.canary.CanaryMetricConfig;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nullable;
import javax.validation.constraints.NotNull;
import lombok.*;

@Data
@Builder
@ToString
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(description = "Request body for using the New Relic Fetch controller")
class NewRelicFetchRequest {
@NotNull
@ApiModelProperty(value = "The metric config to query New Relic insights for")
CanaryMetricConfig canaryMetricConfig;

@NotNull @Builder.Default Map<String, String> extendedScopeParams = new HashMap<>();

@Nullable CanaryConfig canaryConfig;
}
Loading

0 comments on commit 8269040

Please sign in to comment.