Skip to content

Commit

Permalink
Break out provider-specific canary scopes. (#25)
Browse files Browse the repository at this point in the history
Update Atlas remote service interface (still not quite there, but sneaking up on it).
Define MetricSetQuery and provider-specific implementations.
Add provider-specific /fetch controllers for development.
Make MetricsService provider-agnostic.
Use canary config and canary scope attributes in provider-specific metric services.
Break out accountName parameter of development controllers into metricsAccountName and storageAccountName.
  • Loading branch information
Matt Duftler committed May 23, 2017
1 parent 02dfe9c commit 4fe3b65
Show file tree
Hide file tree
Showing 24 changed files with 562 additions and 128 deletions.
50 changes: 46 additions & 4 deletions json-formats.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@ Atlas is used.

```JSON
{
"name": "MySampleCanaryConfig",
"description": "Example Automated Canary Analysis (ACA) Configuration",
"name": "MySampleAtlasCanaryConfig",
"description": "Example Automated Canary Analysis (ACA) Configuration using Atlas",
"configVersion": 1.0,
"metrics": [
{
"serviceName": "atlas",
"name": "cpu",
"query": "name,CpuRawUser,:eq,:sum,name,numProcs,:eq,:sum,:div",
"query": {
"type": "atlas",
"q": "name,CpuRawUser,:eq,:sum,name,numProcs,:eq,:sum,:div"
},
"analysisConfigurations": {
"canary": { }
},
Expand All @@ -28,7 +31,10 @@ Atlas is used.
{
"serviceName": "atlas",
"name": "requests",
"query": "name,apache.http.requests,:eq,:sum",
"query": {
"type": "atlas",
"q": "name,apache.http.requests,:eq,:sum"
},
"analysisConfigurations": {
"canary": { }
},
Expand Down Expand Up @@ -59,6 +65,42 @@ Atlas is used.
}
}
```
```JSON
{
"name": "MySampleStackdriverCanaryConfig",
"description": "Example Automated Canary Analysis (ACA) Configuration using Stackdriver",
"configVersion": 1.0,
"metrics": [
{
"serviceName": "stackdriver",
"name": "cpu",
"query": {
"type": "stackdriver",
"metricType": "compute.googleapis.com/instance/cpu/utilization"
},
"analysisConfigurations": {
"canary": { }
},
"groups": ["system"]
}
],
"services": {
"stackdriver": {
"type": "stackdriver",
"name": "stackdriver"
}
},
"classifier": {
"groupWeights": {
"system": 50.0
},
"scoreThresholds": {
"pass": 95.0,
"marginal": 75.0
}
}
}
```

## Canary Data Archival Format

Expand Down
1 change: 1 addition & 0 deletions kayenta-atlas/kayenta-atlas.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
dependencies {
compile project(":kayenta-core")
compile spinnaker.dependency('bootWeb')
compile spinnaker.dependency("korkSwagger")
compile spinnaker.dependency('lombok')
spinnaker.group('retrofitDefault')

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright 2017 Google, 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.atlas.canary;

import com.netflix.kayenta.canary.CanaryScope;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;

import javax.validation.constraints.NotNull;

@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class AtlasCanaryScope extends CanaryScope {

@NotNull
private String type;

public String cq() {
switch (type) {
case "application":
return ":list,(,nf.app," + scope + ",:eq,:cq,),:each";
case "cluster":
return ":list,(,nf.cluster," + scope + ",:eq,:cq,),:each";
default:
throw new IllegalArgumentException("Scope type is unknown: " + scope);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@
* limitations under the License.
*/

package com.netflix.kayenta.controllers;
package com.netflix.kayenta.atlas.controllers;

import com.netflix.kayenta.atlas.canary.AtlasCanaryScope;
import com.netflix.kayenta.canary.CanaryMetricConfig;
import com.netflix.kayenta.metrics.AtlasMetricSetQuery;
import com.netflix.kayenta.metrics.MetricSet;
import com.netflix.kayenta.metrics.MetricsService;
import com.netflix.kayenta.metrics.MetricsServiceRepository;
import com.netflix.kayenta.security.AccountCredentials;
import com.netflix.kayenta.security.AccountCredentialsRepository;
import com.netflix.kayenta.security.CredentialsHelper;
import com.netflix.kayenta.storage.ObjectType;
import com.netflix.kayenta.storage.StorageService;
import com.netflix.kayenta.storage.StorageServiceRepository;
Expand All @@ -39,9 +43,9 @@
import java.util.UUID;

@RestController
@RequestMapping("/fetch")
@RequestMapping("/fetch/atlas")
@Slf4j
public class FetchController {
public class AtlasFetchController {

@Autowired
AccountCredentialsRepository accountCredentialsRepository;
Expand All @@ -52,35 +56,61 @@ public class FetchController {
@Autowired
StorageServiceRepository storageServiceRepository;

@RequestMapping(value = "/query", method = RequestMethod.GET)
public String queryMetrics(@RequestParam(required = false) final String accountName,
@RequestMapping(value = "/query", method = RequestMethod.POST)
public String queryMetrics(@RequestParam(required = false) final String metricsAccountName,
@RequestParam(required = false) final String storageAccountName,
@RequestParam(required = false) String q,
@ApiParam(defaultValue = "cpu") @RequestParam String metricSetName,
@ApiParam(defaultValue = "myapp-v010-") @RequestParam String instanceNamePrefix,
@ApiParam(defaultValue = "2017-05-01T15:13:00Z") @RequestParam String intervalStartTime,
@ApiParam(defaultValue = "2017-05-02T15:27:00Z") @RequestParam String intervalEndTime) throws IOException {
String resolvedAccountName = CredentialsHelper.resolveAccountByNameOrType(accountName,
AccountCredentials.Type.METRICS_STORE,
accountCredentialsRepository);
Optional<MetricsService> metricsService = metricsServiceRepository.getOne(resolvedAccountName);
@ApiParam(defaultValue = "cluster") @RequestParam String type,
@RequestParam String scope,
@ApiParam(defaultValue = "0") @RequestParam Long start,
@ApiParam(defaultValue = "6000000") @RequestParam Long end,
@ApiParam(defaultValue = "PT1M") @RequestParam String step) throws IOException {
String resolvedMetricsAccountName = CredentialsHelper.resolveAccountByNameOrType(metricsAccountName,
AccountCredentials.Type.METRICS_STORE,
accountCredentialsRepository);
Optional<MetricsService> metricsService = metricsServiceRepository.getOne(resolvedMetricsAccountName);
List<MetricSet> metricSetList;

if (metricsService.isPresent()) {
AtlasCanaryScope atlasCanaryScope = new AtlasCanaryScope();
atlasCanaryScope.setType(type);
atlasCanaryScope.setScope(scope);
atlasCanaryScope.setStart(start);
atlasCanaryScope.setEnd(end);
atlasCanaryScope.setStep(step);

AtlasMetricSetQuery atlasMetricSetQuery =
AtlasMetricSetQuery
.builder()
.q(q)
.build();
CanaryMetricConfig canaryMetricConfig =
CanaryMetricConfig
.builder()
.name(metricSetName)
.query(atlasMetricSetQuery)
.build();

metricSetList = metricsService
.get()
.queryMetrics(resolvedAccountName, metricSetName, instanceNamePrefix, intervalStartTime, intervalEndTime);
.queryMetrics(resolvedMetricsAccountName, canaryMetricConfig, atlasCanaryScope);
} else {
log.debug("No metrics service was configured; skipping placeholder logic to read from metrics store.");

metricSetList = Collections.singletonList(MetricSet.builder().name("no-metrics").build());
}

// TODO(duftler): This is placeholder logic. Just demonstrating that we can write to the bucket.
// It is not expected that this would (necessarily) use the same account name as that used for the metrics store.
Optional<StorageService> storageService = storageServiceRepository.getOne(resolvedAccountName);
String resolvedStorageAccountName = CredentialsHelper.resolveAccountByNameOrType(storageAccountName,
AccountCredentials.Type.OBJECT_STORE,
accountCredentialsRepository);
Optional<StorageService> storageService = storageServiceRepository.getOne(resolvedStorageAccountName);
String metricSetListId = UUID.randomUUID() + "";

if (storageService.isPresent()) {
storageService.get().storeObject(resolvedAccountName, ObjectType.METRIC_SET_LIST, metricSetListId, metricSetList);
storageService
.get()
.storeObject(resolvedStorageAccountName, ObjectType.METRIC_SET_LIST, metricSetListId, metricSetList);
} else {
log.debug("No storage service was configured; skipping placeholder logic to write to bucket.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@

package com.netflix.kayenta.atlas.metrics;

import com.netflix.kayenta.atlas.canary.AtlasCanaryScope;
import com.netflix.kayenta.atlas.model.AtlasResults;
import com.netflix.kayenta.atlas.model.AtlasResultsHelper;
import com.netflix.kayenta.atlas.security.AtlasNamedAccountCredentials;
import com.netflix.kayenta.atlas.service.AtlasRemoteService;
import com.netflix.kayenta.canary.CanaryMetricConfig;
import com.netflix.kayenta.canary.CanaryScope;
import com.netflix.kayenta.metrics.MetricSet;
import com.netflix.kayenta.metrics.MetricSetQuery;
import com.netflix.kayenta.metrics.MetricsService;
import com.netflix.kayenta.security.AccountCredentialsRepository;
import lombok.Builder;
Expand Down Expand Up @@ -54,19 +58,25 @@ public boolean servicesAccount(String accountName) {
}

@Override
// These are still placeholder arguments. Each metrics service will have its own set of required/optional arguments. The return type is a placeholder as well.
// Using metricSetName to pass the sse filename for now.
// TODO(duftler): Make this api generic.
public List<MetricSet> queryMetrics(String accountName,
String metricSetName,
String instanceNamePrefix,
String intervalStartTime,
String intervalEndTime) throws IOException {
CanaryMetricConfig canaryMetricConfig,
CanaryScope canaryScope) throws IOException {
if (!(canaryScope instanceof AtlasCanaryScope)) {
throw new IllegalArgumentException("Canary scope not instance of AtlasCanaryScope: " + canaryScope);
}

AtlasCanaryScope atlasCanaryScope = (AtlasCanaryScope)canaryScope;
AtlasNamedAccountCredentials credentials = (AtlasNamedAccountCredentials)accountCredentialsRepository
.getOne(accountName)
.orElseThrow(() -> new IllegalArgumentException("Unable to resolve account " + accountName + "."));
AtlasRemoteService atlasRemoteService = credentials.getAtlasRemoteService();
List<AtlasResults> atlasResultsList = atlasRemoteService.fetch(metricSetName);
// TODO(mgraff): Is this how we decorate the base query?
MetricSetQuery metricSetQuery = canaryMetricConfig.getQuery();
String decoratedQuery = metricSetQuery + "," + atlasCanaryScope.cq();
List<AtlasResults> atlasResultsList = atlasRemoteService.fetch(decoratedQuery,
atlasCanaryScope.getStart() + "",
atlasCanaryScope.getEnd() + "",
atlasCanaryScope.getStep());
Map<String, AtlasResults> idToAtlasResultsMap = AtlasResultsHelper.merge(atlasResultsList);
List<MetricSet> metricSetList = new ArrayList<>();

Expand All @@ -78,10 +88,9 @@ public List<MetricSet> queryMetrics(String accountName,
timeSeriesList = new ArrayList<>();
}

// TODO: Get the metric set name from the request/canary-config.
MetricSet.MetricSetBuilder metricSetBuilder =
MetricSet.builder()
.name("cpu")
.name(canaryMetricConfig.getName())
.startTimeMillis(atlasResults.getStart())
.startTimeIso(responseStartTimeInstant.toString())
.stepMillis(atlasResults.getStep())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@

public interface AtlasRemoteService {

// TODO(mgraff): I know this isn't quite right. Just adding all of these in as a starting point.
@GET("/api/v2/fetch")
List<AtlasResults> fetch(@Query("q") String q);
List<AtlasResults> fetch(@Query("q") String q,
@Query("s") String start,
@Query("e") String end,
@Query("step") String step);
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

import java.io.IOException;
import java.io.InputStream;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

Expand Down Expand Up @@ -66,7 +67,7 @@ private static Map<String, String> createQueryMap() {
}

private AtlasResults generateDummyContent(String q, String s, String e, String step) {
long stepLong = java.time.Duration.parse(step).toMillis();
long stepLong = Duration.parse(step).toMillis();
long sLong = Long.parseLong(s);
long eLong = Long.parseLong(e);
sLong = (sLong / stepLong) * stepLong;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
{
"service": "atlas",
"name": "cpu",
"query": "name,CpuRawUser,:eq,:sum",
"query": {
"type": "atlas",
"q": "name,CpuRawUser,:eq,:sum"
},
"extensions": {
"canary": { }
},
Expand All @@ -14,7 +17,10 @@
{
"service": "atlas",
"name": "requests",
"query": "name,apache.http.requests,:eq,(,requestCode,),:by,:sum",
"query": {
"type": "atlas",
"q": "name,apache.http.requests,:eq,(,requestCode,),:by,:sum"
},
"extensions": {
"canary": { }
},
Expand All @@ -24,8 +30,9 @@
"services": {
"atlas": {
"type": "atlas",
"name": "atlas",
"region": "us-east-1",
"env": "prod",
"environment": "prod",
"backend": {
"deployment": "main",
"dataset": "regional"
Expand Down
Loading

0 comments on commit 4fe3b65

Please sign in to comment.