package com.hubspot.baragon.service.worker;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableMap;
import com.google.inject.Inject;
import com.hubspot.baragon.data.BaragonAgentResponseDatastore;
import com.hubspot.baragon.data.BaragonRequestDatastore;
import com.hubspot.baragon.data.BaragonResponseHistoryDatastore;
import com.hubspot.baragon.data.BaragonStateDatastore;
import com.hubspot.baragon.models.BaragonRequest;
import com.hubspot.baragon.models.BaragonRequestKey;
import com.hubspot.baragon.models.BaragonResponse;
import com.hubspot.baragon.models.InternalRequestStates;
import com.hubspot.baragon.models.InternalStatesMap;
import com.hubspot.baragon.service.config.BaragonConfiguration;
import com.hubspot.baragon.service.exceptions.BaragonExceptionNotifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RequestPurgingWorker implements Runnable {
private static final Logger LOG = LoggerFactory.getLogger(RequestPurgingWorker.class);
private final BaragonRequestDatastore requestDatastore;
private final BaragonConfiguration configuration;
private final BaragonAgentResponseDatastore agentResponseDatastore;
private final BaragonResponseHistoryDatastore responseHistoryDatastore;
private final BaragonStateDatastore stateDatastore;
private final BaragonExceptionNotifier exceptionNotifier;
@Inject
public RequestPurgingWorker(BaragonRequestDatastore requestDatastore,
BaragonConfiguration configuration,
BaragonAgentResponseDatastore agentResponseDatastore,
BaragonResponseHistoryDatastore responseHistoryDatastore,
BaragonStateDatastore stateDatastore,
BaragonExceptionNotifier exceptionNotifier) {
this.requestDatastore = requestDatastore;
this.configuration = configuration;
this.agentResponseDatastore = agentResponseDatastore;
this.responseHistoryDatastore = responseHistoryDatastore;
this.stateDatastore = stateDatastore;
this.exceptionNotifier = exceptionNotifier;
}
private enum PurgeAction {
PURGE, SAVE, NONE
}
@Override
public void run() {
try {
long referenceTime = System.currentTimeMillis() - (TimeUnit.DAYS.toMillis(configuration.getHistoryConfiguration().getPurgeOldRequestsAfterDays()));
cleanUpActiveRequests(referenceTime);
if (configuration.getHistoryConfiguration().isPurgeOldRequests() && !Thread.interrupted()) {
purgeHistoricalRequests(referenceTime);
trimNumRequestsPerService();
}
} catch (Exception e) {
LOG.error("Caught exception during old request purging", e);
exceptionNotifier.notify(e, Collections.<String, String>emptyMap());
}
}
public void cleanUpActiveRequests(long referenceTime) {
List<String> allMaybeActiveRequestIds = requestDatastore.getAllRequestIds();
for (String requestId : allMaybeActiveRequestIds) {
try {
Optional<InternalRequestStates> maybeState = requestDatastore.getRequestState(requestId);
switch (getPurgeActionForMaybeActiveRequest(requestId, referenceTime, maybeState)) {
case PURGE:
requestDatastore.deleteRequest(requestId);
break;
case SAVE:
Optional<BaragonRequest> maybeRequest = requestDatastore.getRequest(requestId);
if (maybeRequest.isPresent()) {
BaragonResponse response = new BaragonResponse(maybeRequest.get().getLoadBalancerRequestId(), InternalStatesMap.getRequestState(maybeState.get()), requestDatastore.getRequestMessage(maybeRequest.get().getLoadBalancerRequestId()), Optional.of(agentResponseDatastore.getLastResponses(maybeRequest.get().getLoadBalancerRequestId())), maybeRequest);
responseHistoryDatastore.addResponse(maybeRequest.get().getLoadBalancerService().getServiceId(), maybeRequest.get().getLoadBalancerRequestId(), response);
requestDatastore.deleteRequest(requestId);
} else {
LOG.warn("Could not get request data to save history for request {}", requestId);
}
break;
case NONE:
default:
break;
}
} catch (Exception e) {
LOG.error("Caught exception trying to clean up request {}", requestId, e);
exceptionNotifier.notify(e, ImmutableMap.of("requestId", requestId));
}
if (Thread.interrupted()) {
LOG.warn("Purger was interrupted, stopping purge");
break;
}
}
}
private PurgeAction getPurgeActionForMaybeActiveRequest(String requestId, long referenceTime, Optional<InternalRequestStates> maybeState) {
Optional<Long> maybeUpdatedAt = requestDatastore.getRequestUpdatedAt(requestId);
if (!maybeState.isPresent() || InternalStatesMap.isRemovable(maybeState.get())) {
if (configuration.getHistoryConfiguration().isPurgeOldRequests()) {
if (shouldPurge(maybeUpdatedAt, referenceTime)) {
LOG.trace("Updated at time: {} is earlier than reference time: {}, purging request {}", maybeUpdatedAt.get(), referenceTime, requestId);
return PurgeAction.PURGE;
} else {
return PurgeAction.SAVE;
}
} else {
return PurgeAction.SAVE;
}
} else {
return PurgeAction.NONE;
}
}
private void purgeHistoricalRequests(long referenceTime) {
for (String serviceId : responseHistoryDatastore.getServiceIds()) {
if (!serviceId.equals("requestIdMapping")) {
List<String> requestIds = responseHistoryDatastore.getRequestIdsForService(serviceId);
if (stateDatastore.serviceExists(serviceId)) {
if (!requestIds.isEmpty()) {
for (String requestId : requestIds) {
Optional<Long> maybeUpdatedAt = responseHistoryDatastore.getRequestUpdatedAt(serviceId, requestId);
if (shouldPurge(maybeUpdatedAt, referenceTime)) {
LOG.trace("Updated at time: {} is earlier than reference time: {}, purging request {}", maybeUpdatedAt.get(), referenceTime, requestId);
responseHistoryDatastore.deleteResponse(serviceId, requestId);
}
if (Thread.interrupted()) {
LOG.warn("Purger was interrupted, stopping purge");
break;
}
}
}
} else {
responseHistoryDatastore.deleteResponses(serviceId);
}
}
if (Thread.interrupted()) {
LOG.warn("Purger was interrupted, stopping purge");
break;
}
}
}
private boolean shouldPurge(Optional<Long> maybeUpdatedAt, long referenceTime) {
return (maybeUpdatedAt.isPresent() && maybeUpdatedAt.get() < referenceTime) || (!maybeUpdatedAt.isPresent() && configuration.getHistoryConfiguration().isPurgeWhenDateNotFound());
}
private void trimNumRequestsPerService() {
LOG.trace("Checking for services with too many requests");
for (String serviceId : responseHistoryDatastore.getServiceIds()) {
if (!serviceId.equals("requestIdMapping")) {
try {
List<String> requestIds = responseHistoryDatastore.getRequestIdsForService(serviceId);
if (requestIds.size() > configuration.getHistoryConfiguration().getMaxRequestsPerService()) {
removeOldestRequestIds(serviceId, requestIds);
}
} catch (Exception e) {
LOG.error("Caught exception purging old requests for service {}", serviceId, e);
exceptionNotifier.notify(e, ImmutableMap.of("serviceId", serviceId));
}
}
}
}
private void removeOldestRequestIds(String serviceId, List<String> requestIds) {
LOG.debug("Service {} has {} requests, over limit of {}, will remove oldest requests", serviceId, requestIds.size(), configuration.getHistoryConfiguration().getMaxRequestsPerService());
List<BaragonRequestKey> requestKeyList = new ArrayList<>();
for (String requestId : requestIds) {
Optional<Long> maybeUpdatedAt = responseHistoryDatastore.getRequestUpdatedAt(serviceId, requestId);
if (maybeUpdatedAt.isPresent()) {
requestKeyList.add(new BaragonRequestKey(requestId, maybeUpdatedAt.get()));
} else {
if (configuration.getHistoryConfiguration().isPurgeWhenDateNotFound()) {
responseHistoryDatastore.deleteResponse(serviceId, requestId);
}
}
}
Collections.sort(requestKeyList);
for (BaragonRequestKey requestKey : requestKeyList.subList(configuration.getHistoryConfiguration().getMaxRequestsPerService(), requestKeyList.size())) {
responseHistoryDatastore.deleteResponse(serviceId, requestKey.getRequestId());
}
}
}