Skip to content

Commit

Permalink
Implement backends.json for atlas config (#113)
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael Graff committed Nov 2, 2017
1 parent a150080 commit c5dbc97
Show file tree
Hide file tree
Showing 14 changed files with 431 additions and 55 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2017 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.kayenta.atlas.backends;

import com.netflix.kayenta.atlas.model.Backend;

import java.util.*;

public class BackendDatabase {

private List<Backend> backends = new ArrayList<>();

private boolean matches(Backend backend, String deployment, String dataset, String region, String environment) {
// return false if it doesn't match the deployment.
if (!backend.getDeployment().equals(deployment))
return false;

// return false if it doesn't match the dataset.
if (!backend.getDataset().equals(dataset))
return false;

// return false if it doesn't match the region.
if (backend.getRegions() != null && !backend.getRegions().contains(region))
return false;

// return false if it doesn't match the environment.
if (backend.getEnvironments() != null && !backend.getEnvironments().contains(environment))
return false;

return true;
}

public synchronized Optional<Backend> getOne(String deployment, String dataset, String region, String environment) {
return backends
.stream()
.filter(a -> matches(a, deployment, dataset, region, environment))
.findFirst();
}

public synchronized void update(List<Backend> newBackends) {
backends = newBackends;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.netflix.kayenta.atlas.backends;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.netflix.kayenta.atlas.model.Backend;
import com.netflix.kayenta.atlas.service.BackendsRemoteService;
import com.netflix.kayenta.retrofit.config.RemoteService;
import com.netflix.kayenta.retrofit.config.RetrofitClientFactory;
import com.squareup.okhttp.OkHttpClient;
import lombok.Builder;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import retrofit.RetrofitError;
import retrofit.converter.JacksonConverter;

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

@Slf4j
@Builder
public class BackendUpdater {
@Getter
private final BackendDatabase backendDatabase = new BackendDatabase();

@NotNull
private String uri;

// If we have retrieved backends.json at least once, we will keep using it forever
// even if we fail later. It doesn't really change much over time, so this
// is likely safe enough.
private boolean succeededAtLeastOnce = false;

public boolean run(RetrofitClientFactory retrofitClientFactory, ObjectMapper objectMapper) {
OkHttpClient okHttpClient = new OkHttpClient();
RemoteService remoteService = new RemoteService();
remoteService.setBaseUrl(uri);
BackendsRemoteService backendsRemoteService = retrofitClientFactory.createClient(BackendsRemoteService.class,
new JacksonConverter(objectMapper),
remoteService,
okHttpClient);
try {
List<Backend> backends = backendsRemoteService.fetch();
backendDatabase.update(backends);
} catch (RetrofitError e) {
log.warn("While fetching atlas backends from " + uri, e);
return succeededAtLeastOnce;
}
succeededAtLeastOnce = true;
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.netflix.kayenta.atlas.backends;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.netflix.kayenta.retrofit.config.RetrofitClientFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
@Slf4j
@EnableConfigurationProperties
@ConditionalOnProperty("kayenta.atlas.enabled")
public class BackendUpdaterService extends AbstractHealthIndicator {
@Autowired
private RetrofitClientFactory retrofitClientFactory;

@Autowired
private ObjectMapper objectMapper;

private final List<BackendUpdater> backendUpdaters = new ArrayList<>();
private int checksCompleted = 0;

@Scheduled(initialDelay = 10000, fixedDelay=120000)
public synchronized void run() {
// TODO: this will fetch the same uri even if they share the same URI.
// TODO: It also has locking issues, in that we could hold a lock for a long time.
// TODO: Locking may not matter as we should rarely, if ever, modify this list.
// TODO: Although, for healthcheck, it may...
int checks = 0;
for (BackendUpdater updater: backendUpdaters) {
Boolean result = updater.run(retrofitClientFactory, objectMapper);
if (result)
checks++;
}
checksCompleted = checks;
}

public synchronized void add(BackendUpdater updater) {
backendUpdaters.add(updater);
}

@Override
protected synchronized void doHealthCheck(Health.Builder builder) throws Exception {
if (checksCompleted == backendUpdaters.size()) {
builder.up();
} else {
builder.down();
}
builder.withDetail("checksCompleted", checksCompleted);
builder.withDetail("checksExpected", backendUpdaters.size());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ public class AtlasCanaryScope extends CanaryScope {
@NotNull
private String type;

@NotNull
private String deployment;

@NotNull
private String dataset;

@NotNull
private String region;

@NotNull
private String environment;

public String cq() {
if (type == null) {
throw new IllegalArgumentException("Atlas canary scope requires 'type' to be asg, cluster, or query.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import com.netflix.kayenta.canary.CanaryScope;
import com.netflix.kayenta.canary.CanaryScopeFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import java.util.Collections;
Expand All @@ -44,7 +45,12 @@ public CanaryScope buildCanaryScope(CanaryScope canaryScope){
if (extendedScopeParams == null) {
extendedScopeParams = Collections.emptyMap();
}

atlasCanaryScope.setType(extendedScopeParams.getOrDefault("type", "cluster"));
atlasCanaryScope.setDeployment(extendedScopeParams.getOrDefault("deployment", "main"));
atlasCanaryScope.setDataset(extendedScopeParams.getOrDefault("dataset", "regional"));
atlasCanaryScope.setRegion(extendedScopeParams.getOrDefault("region", "us-east-1"));
atlasCanaryScope.setEnvironment(extendedScopeParams.getOrDefault("environment", "test"));

return atlasCanaryScope;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,17 @@

package com.netflix.kayenta.atlas.config;

import com.netflix.kayenta.atlas.backends.BackendDatabase;
import com.netflix.kayenta.atlas.backends.BackendUpdater;
import com.netflix.kayenta.atlas.backends.BackendUpdaterService;
import com.netflix.kayenta.atlas.metrics.AtlasMetricsService;
import com.netflix.kayenta.atlas.security.AtlasCredentials;
import com.netflix.kayenta.atlas.security.AtlasNamedAccountCredentials;
import com.netflix.kayenta.atlas.service.AtlasRemoteService;
import com.netflix.kayenta.metrics.MetricsService;
import com.netflix.kayenta.retrofit.config.RetrofitClientFactory;
import com.netflix.kayenta.security.AccountCredentials;
import com.netflix.kayenta.security.AccountCredentialsRepository;
import com.squareup.okhttp.OkHttpClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
Expand All @@ -44,54 +45,49 @@
@Slf4j
public class AtlasConfiguration {

@Autowired
BackendUpdaterService backendUpdaterService;

@Bean
@ConfigurationProperties("kayenta.atlas")
AtlasConfigurationProperties atlasConfigurationProperties() {
return new AtlasConfigurationProperties();
}

@Bean
MetricsService atlasMetricsService(AtlasSSEConverter atlasSSEConverter,
AtlasConfigurationProperties atlasConfigurationProperties,
RetrofitClientFactory retrofitClientFactory,
OkHttpClient okHttpClient,
MetricsService atlasMetricsService(AtlasConfigurationProperties atlasConfigurationProperties,
AccountCredentialsRepository accountCredentialsRepository) throws IOException {
AtlasMetricsService.AtlasMetricsServiceBuilder atlasMetricsServiceBuilder = AtlasMetricsService.builder();

for (AtlasManagedAccount atlasManagedAccount : atlasConfigurationProperties.getAccounts()) {
String name = atlasManagedAccount.getName();
String namespace = atlasManagedAccount.getNamespace();
List<AccountCredentials.Type> supportedTypes = atlasManagedAccount.getSupportedTypes();
String backendsJsonUriPrefix = atlasManagedAccount.getBackendsJsonBaseUrl();

log.info("Registering Atlas account {} with supported types {}.", name, supportedTypes);

AtlasCredentials atlasCredentials =
AtlasCredentials
.builder()
.build();

BackendUpdater updater = BackendUpdater.builder().uri(backendsJsonUriPrefix).build();
AtlasNamedAccountCredentials.AtlasNamedAccountCredentialsBuilder atlasNamedAccountCredentialsBuilder =
AtlasNamedAccountCredentials
.builder()
.name(name)
.namespace(namespace)
.credentials(atlasCredentials);
.credentials(atlasCredentials)
.backendUpdater(updater);

if (!CollectionUtils.isEmpty(supportedTypes)) {
if (supportedTypes.contains(AccountCredentials.Type.METRICS_STORE)) {
AtlasRemoteService atlasRemoteService = retrofitClientFactory.createClient(AtlasRemoteService.class,
atlasSSEConverter,
atlasManagedAccount.getEndpoint(),
okHttpClient);

atlasNamedAccountCredentialsBuilder.atlasRemoteService(atlasRemoteService);
}

atlasNamedAccountCredentialsBuilder.supportedTypes(supportedTypes);
}

AtlasNamedAccountCredentials atlasNamedAccountCredentials = atlasNamedAccountCredentialsBuilder.build();
accountCredentialsRepository.save(name, atlasNamedAccountCredentials);
atlasMetricsServiceBuilder.accountName(name);

backendUpdaterService.add(atlasNamedAccountCredentials.getBackendUpdater());
}

AtlasMetricsService atlasMetricsService = atlasMetricsServiceBuilder.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,9 @@

package com.netflix.kayenta.atlas.config;

import com.netflix.kayenta.retrofit.config.RemoteService;
import com.netflix.kayenta.atlas.backends.BackendUpdater;
import com.netflix.kayenta.security.AccountCredentials;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;

import javax.validation.constraints.NotNull;
import java.util.List;
Expand All @@ -32,11 +30,7 @@ public class AtlasManagedAccount {
private String name;

@NotNull
@Getter
@Setter
private RemoteService endpoint;

private String namespace;
String backendsJsonBaseUrl;

private List<AccountCredentials.Type> supportedTypes;
}
Loading

0 comments on commit c5dbc97

Please sign in to comment.