Skip to content

Commit

Permalink
fix(credentials): Swap LoadingCache for @Scheduled (#516)
Browse files Browse the repository at this point in the history
Given that accounts rarely change, it is reasonable to cache the
entire set of accounts from clouddriver for ~30s and refresh on a
background thread.

This handles a situation where `clouddriver` is momentarily unavailable.
  • Loading branch information
ajordens committed Mar 17, 2018
1 parent 4ba994d commit eb2120b
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,60 +18,59 @@ package com.netflix.spinnaker.gate.services

import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.ObjectMapper
import com.google.common.cache.CacheBuilder
import com.google.common.cache.CacheLoader
import com.google.common.cache.LoadingCache
import com.google.common.util.concurrent.UncheckedExecutionException
import com.netflix.spinnaker.gate.services.internal.ClouddriverService
import com.netflix.spinnaker.gate.services.internal.ClouddriverService.AccountDetails
import groovy.util.logging.Slf4j
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Component

import java.util.concurrent.ExecutionException
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicReference

/**
* DefaultProviderLookupService.
*/
@Slf4j
@Component("providerLookupService")
class DefaultProviderLookupService implements ProviderLookupService, AccountLookupService {

private static final String FALLBACK = "unknown"
private static final String ACCOUNTS_KEY = "all"
private static final TypeReference<List<Map>> JSON_LIST = new TypeReference<List<Map>>() {}
private static final TypeReference<List<AccountDetails>> ACCOUNT_DETAILS_LIST = new TypeReference<List<AccountDetails>>() {}

private final ClouddriverService clouddriverService
private final ObjectMapper mapper = new ObjectMapper()

private final LoadingCache<String, List<AccountDetails>> accountsCache = CacheBuilder.newBuilder()
.initialCapacity(1)
.maximumSize(1)
.refreshAfterWrite(2, TimeUnit.SECONDS)
.build(new CacheLoader<String, List<AccountDetails>>() {
@Override
List<AccountDetails> load(String key) throws Exception {
return clouddriverService.getAccountDetails()
}
})
private final AtomicReference<List<AccountDetails>> accountsCache = new AtomicReference<>([])

@Autowired
public DefaultProviderLookupService(ClouddriverService clouddriverService) {
DefaultProviderLookupService(ClouddriverService clouddriverService) {
this.clouddriverService = clouddriverService
}

@Scheduled(fixedDelay = 30000L)
void refreshCache() {
try {
accountsCache.set(clouddriverService.getAccountDetails())
} catch (Exception e) {
log.error("Unable to refresh account details cache, reason: ${e.message}")
}
}

@Override
public String providerForAccount(String account) {
String providerForAccount(String account) {
try {
return accountsCache.get(ACCOUNTS_KEY)?.find { it.name == account }?.type ?: FALLBACK
return accountsCache.get()?.find { it.name == account }?.type ?: FALLBACK
} catch (ExecutionException | UncheckedExecutionException ex) {
return FALLBACK
}
}

@Override
public List<AccountDetails> getAccounts() {
final List<AccountDetails> original = accountsCache.get(ACCOUNTS_KEY)
List<AccountDetails> getAccounts() {
final List<AccountDetails> original = accountsCache.get()
final List<Map> accountsCopy = mapper.convertValue(original, JSON_LIST)
return mapper.convertValue(accountsCopy, ACCOUNT_DETAILS_LIST)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright 2018 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.gate.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;

@Configuration
public class SchedulingConfigurerConfiguration implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(10);
taskScheduler.initialize();

taskRegistrar.setTaskScheduler(taskScheduler);
}
}

0 comments on commit eb2120b

Please sign in to comment.