Skip to content

Commit

Permalink
First pass at model classes for canary configs. (#10)
Browse files Browse the repository at this point in the history
Add support for loading objects from buckets.
Add canary config controller capable of storing and retrieving canary configs to/from buckets.
  • Loading branch information
Matt Duftler committed May 16, 2017
1 parent d763ffe commit 6b5d465
Show file tree
Hide file tree
Showing 8 changed files with 321 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* 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.canary;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Singular;
import lombok.ToString;

import javax.validation.constraints.NotNull;
import java.util.List;
import java.util.Map;

@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class CanaryConfig {

@NotNull
@Getter
private String name;

@NotNull
@Getter
private String description;

@NotNull
@Getter
private String configVersion;

// TODO(duftler): Add support for timestamps. Would like to keep CanaryConfig immutable though.
// @NotNull
// @Getter
// @Setter
// private long createdTimestamp;
//
// @NotNull
// @Getter
// @Setter
// private long updatedTimestamp;

@NotNull
@Singular
@Getter
private List<CanaryMetricConfig> metrics;

@NotNull
@Singular
@Getter
private Map<String, CanaryServiceConfig> services;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* 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.canary;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Singular;
import lombok.ToString;

import javax.validation.constraints.NotNull;
import java.util.List;
import java.util.Map;

@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class CanaryMetricConfig {

@NotNull
@Getter
private String name;

@NotNull
@Getter
private String serviceName;

@NotNull
@Getter
private String query;

@NotNull
@Singular
@Getter
private List<String> groups;

@NotNull
@Singular
@Getter
private Map<String, Map> analysisConfigurations;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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.canary;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

import javax.validation.constraints.NotNull;

@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class CanaryServiceConfig {

@NotNull
@Getter
private String name;

@NotNull
@Getter
private String type;

@NotNull
@Getter
private String region;

@NotNull
@Getter
private String environment;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,22 @@

package com.netflix.kayenta.storage;

import com.netflix.kayenta.canary.CanaryConfig;
import com.netflix.kayenta.metrics.MetricSet;
import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
public enum ObjectType {
METRIC_SET("metrics", "metric_set.json");
CANARY_CONFIG(CanaryConfig.class, "canary_config", "canary_config.json"),
METRIC_SET(MetricSet.class, "metrics", "metric_set.json");

@Getter
final Class<?> clazz;

@Getter
final String group;

@Getter
final String defaultMetadataFilename;

ObjectType(String group, String defaultMetadataFilename) {
this.group = group;
this.defaultMetadataFilename = defaultMetadataFilename;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@

public interface StorageService {
boolean servicesAccount(String accountName);
<T> T loadObject(String accountName, ObjectType objectType, String objectKey);
<T> void storeObject(String accountName, ObjectType objectType, String objectKey, T obj);
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.springframework.util.StringUtils;

import javax.validation.constraints.NotNull;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;

Expand All @@ -57,14 +58,45 @@ public boolean servicesAccount(String accountName) {
}

@Override
public <T> void storeObject(String accountName, ObjectType objectType, String objectKey, T obj) {
public <T> T loadObject(String accountName, ObjectType objectType, String objectKey) {
GoogleNamedAccountCredentials credentials = (GoogleNamedAccountCredentials)accountCredentialsRepository
.getOne(accountName)
.orElseThrow(() -> new IllegalArgumentException("Unable to resolve account " + accountName + "."));
Storage storage = credentials.getStorage();
String bucketName = credentials.getBucket();
String path = keyToPath(credentials, objectType, objectKey);

ensureBucketExists(accountName);
try {
StorageObject storageObject = storage.objects().get(bucketName, path).execute();

return deserialize(storage, storageObject, (Class<T>)objectType.getClazz());
} catch (IOException e) {
if (e instanceof HttpResponseException) {
HttpResponseException hre = (HttpResponseException)e;
log.error("Failed to load {} {}: {} {}", objectType.getGroup(), objectKey, hre.getStatusCode(), hre.getStatusMessage());
if (hre.getStatusCode() == 404) {
throw new IllegalArgumentException("No file at path " + path + ".");
}
}
throw new IllegalStateException(e);
}
}

private <T> T deserialize(Storage storage, StorageObject object, Class<T> clazz) throws IOException {
ByteArrayOutputStream output = new java.io.ByteArrayOutputStream();
Storage.Objects.Get getter = storage.objects().get(object.getBucket(), object.getName());
getter.executeMediaAndDownloadTo(output);
String json = output.toString("UTF8");

return objectMapper.readValue(json, clazz);
}

@Override
public <T> void storeObject(String accountName, ObjectType objectType, String objectKey, T obj) {
GoogleNamedAccountCredentials credentials = (GoogleNamedAccountCredentials)accountCredentialsRepository
.getOne(accountName)
.orElseThrow(() -> new IllegalArgumentException("Unable to resolve account " + accountName + "."));
Storage storage = credentials.getStorage();
String bucketName = credentials.getBucket();
String path = keyToPath(credentials, objectType, objectKey);

Expand Down
3 changes: 2 additions & 1 deletion kayenta-web/config/kayenta.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ kayenta:

swagger:
enabled: true
title: Spinnaker Kayenta API
title: Kayenta API
description:
contact:
patterns:
- /canaryConfig.*
- /credentials.*
- /fetch.*
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* 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.controllers;

import com.netflix.kayenta.canary.CanaryConfig;
import com.netflix.kayenta.security.AccountCredentials;
import com.netflix.kayenta.security.AccountCredentialsRepository;
import com.netflix.kayenta.storage.ObjectType;
import com.netflix.kayenta.storage.StorageService;
import com.netflix.kayenta.storage.StorageServiceRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestBody;
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 java.io.IOException;
import java.util.Optional;
import java.util.UUID;

@RestController
@RequestMapping("/canaryConfig")
@Slf4j
public class CanaryConfigController {

@Autowired
AccountCredentialsRepository accountCredentialsRepository;

@Autowired
StorageServiceRepository storageServiceRepository;

@RequestMapping(method = RequestMethod.GET)
public CanaryConfig loadCanaryConfig(@RequestParam(required = false) final String accountName,
@RequestParam String canaryConfigUUID) {
AccountCredentials credentials;

if (StringUtils.hasLength(accountName)) {
credentials = accountCredentialsRepository
.getOne(accountName)
.orElseThrow(() -> new IllegalArgumentException("Unable to resolve account " + accountName + "."));
} else {
credentials = accountCredentialsRepository
.getOne(AccountCredentials.Type.OBJECT_STORE)
.orElseThrow(() -> new IllegalArgumentException("Unable to resolve account of type " + AccountCredentials.Type.OBJECT_STORE + "."));
}

String resolvedAccountName = credentials.getName();
Optional<StorageService> storageService = storageServiceRepository.getOne(resolvedAccountName);

if (storageService.isPresent()) {
return storageService.get().loadObject(resolvedAccountName, ObjectType.CANARY_CONFIG, canaryConfigUUID);
} else {
log.debug("No storage service was configured; skipping placeholder logic to read from bucket.");
return null;
}
}

@RequestMapping(consumes = "application/context+json", method = RequestMethod.POST)
public String storeCanaryConfig(@RequestParam(required = false) final String accountName,
@RequestBody CanaryConfig canaryConfig) throws IOException {
AccountCredentials credentials;

if (StringUtils.hasLength(accountName)) {
credentials = accountCredentialsRepository
.getOne(accountName)
.orElseThrow(() -> new IllegalArgumentException("Unable to resolve account " + accountName + "."));
} else {
credentials = accountCredentialsRepository
.getOne(AccountCredentials.Type.OBJECT_STORE)
.orElseThrow(() -> new IllegalArgumentException("Unable to resolve account of type " + AccountCredentials.Type.OBJECT_STORE + "."));
}

String resolvedAccountName = credentials.getName();
Optional<StorageService> storageService = storageServiceRepository.getOne(resolvedAccountName);
String canaryConfigId = UUID.randomUUID() + "";

if (storageService.isPresent()) {
storageService.get().storeObject(resolvedAccountName, ObjectType.CANARY_CONFIG, canaryConfigId, canaryConfig);
} else {
log.debug("No storage service was configured; skipping placeholder logic to write to bucket.");
}

return canaryConfigId;
}
}

0 comments on commit 6b5d465

Please sign in to comment.