com.netflix.spinnaker.front50.model.EventingS3ObjectKeyLoader Maven / Gradle / Ivy
/*
* 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.spinnaker.front50.model;
import static net.logstash.logback.argument.StructuredArguments.value;
import com.amazonaws.services.sqs.model.Message;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListenableFutureTask;
import com.netflix.spectator.api.Registry;
import com.netflix.spinnaker.front50.config.S3MetadataStorageProperties;
import com.netflix.spinnaker.front50.model.events.S3Event;
import com.netflix.spinnaker.front50.model.events.S3EventWrapper;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import javax.annotation.PreDestroy;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An ObjectKeyLoader is responsible for returning a last modified timestamp for all objects of a
* particular type.
*
* This implementation listens to an S3 event stream and applies incremental updates whenever an
* event is received indicating that an object has been modified (add/update/delete).
*
*
It is significantly faster than delegating to `s3StorageService.listObjectKeys()` with some
* slight latency attributed to the time taken for an event to be received and processed.
*
*
Expected latency is less than 1s (Amazon
*/
public class EventingS3ObjectKeyLoader implements ObjectKeyLoader, Runnable {
private static final Logger log = LoggerFactory.getLogger(EventingS3ObjectKeyLoader.class);
private static final Executor executor = Executors.newFixedThreadPool(5);
private final ObjectMapper objectMapper;
private final TemporarySQSQueue temporarySQSQueue;
private final StorageService storageService;
private final Registry registry;
private final Cache objectKeysByLastModifiedCache;
private final LoadingCache> objectKeysByObjectTypeCache;
private final String rootFolder;
private boolean pollForMessages = true;
public EventingS3ObjectKeyLoader(
ExecutorService executionService,
ObjectMapper objectMapper,
S3MetadataStorageProperties s3Properties,
TemporarySQSQueue temporarySQSQueue,
StorageService storageService,
Registry registry,
boolean scheduleImmediately) {
this.objectMapper = objectMapper;
this.temporarySQSQueue = temporarySQSQueue;
this.storageService = storageService;
this.registry = registry;
this.objectKeysByLastModifiedCache =
CacheBuilder.newBuilder()
// ensure that these keys only expire _after_ their object type has been refreshed
.expireAfterWrite(
s3Properties.getEventing().getRefreshIntervalMs() + 60000, TimeUnit.MILLISECONDS)
.recordStats()
.build();
this.objectKeysByObjectTypeCache =
CacheBuilder.newBuilder()
.refreshAfterWrite(
s3Properties.getEventing().getRefreshIntervalMs(), TimeUnit.MILLISECONDS)
.recordStats()
.build(
new CacheLoader>() {
@Override
public Map load(ObjectType objectType) throws Exception {
log.debug("Loading object keys for {}", value("type", objectType));
return storageService.listObjectKeys(objectType);
}
@Override
public ListenableFuture