package com.hubspot.baragon.data;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.api.transaction.CuratorTransactionResult;
import org.apache.curator.utils.ZKPaths;
import org.apache.zookeeper.CreateMode;
import com.codahale.metrics.annotation.Timed;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Optional;
import com.google.common.base.Throwables;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.hubspot.baragon.config.ZooKeeperConfiguration;
import com.hubspot.baragon.models.BaragonRequest;
import com.hubspot.baragon.models.InternalRequestStates;
import com.hubspot.baragon.models.QueuedRequestId;
@Singleton
public class BaragonRequestDatastore extends AbstractDataStore {
public static final String REQUESTS_FORMAT = "/request";
public static final String REQUEST_FORMAT = REQUESTS_FORMAT + "/%s";
public static final String REQUEST_STATE_FORMAT = REQUEST_FORMAT + "/status";
public static final String REQUEST_MESSAGE_FORMAT = REQUEST_FORMAT + "/message";
public static final String REQUEST_QUEUE_FORMAT = "/queue";
public static final String REQUEST_ENQUEUE_FORMAT = REQUEST_QUEUE_FORMAT + "/%s|%s|";
public static final String REQUEST_QUEUE_ITEM_FORMAT = REQUEST_QUEUE_FORMAT + "/%s";
@Inject
public BaragonRequestDatastore(CuratorFramework curatorFramework, ObjectMapper objectMapper, ZooKeeperConfiguration zooKeeperConfiguration) {
super(curatorFramework, objectMapper, zooKeeperConfiguration);
}
//
// REQUEST DATA
//
@Timed
public Optional<BaragonRequest> getRequest(String requestId) {
return readFromZk(String.format(REQUEST_FORMAT, requestId), BaragonRequest.class);
}
@Timed
public Optional<BaragonRequest> deleteRequest(String requestId) {
final Optional<BaragonRequest> maybeRequest = getRequest(requestId);
if (maybeRequest.isPresent()) {
deleteNode(String.format(REQUEST_FORMAT, requestId), true);
}
return maybeRequest;
}
@Timed
public List<String> getAllRequestIds() {
return getChildren(REQUESTS_FORMAT);
}
@Timed
public Optional<Long> getRequestUpdatedAt(String requestId) {
return getUpdatedAt(String.format(String.format(REQUEST_STATE_FORMAT, requestId)));
}
//
// REQUEST STATE
//
public boolean activeRequestExists(String requestId) {
return nodeExists(String.format(REQUEST_FORMAT, requestId));
}
@Timed
public Optional<InternalRequestStates> getRequestState(String requestId) {
return readFromZk(String.format(REQUEST_STATE_FORMAT, requestId), InternalRequestStates.class);
}
@Timed
public void setRequestState(String requestId, InternalRequestStates state) {
writeToZk(String.format(REQUEST_STATE_FORMAT, requestId), state);
}
// REQUEST MESSAGE
@Timed
public Optional<String> getRequestMessage(String requestId) {
return readFromZk(String.format(REQUEST_MESSAGE_FORMAT, requestId), String.class);
}
@Timed
public void setRequestMessage(String requestId, String message) {
writeToZk(String.format(REQUEST_MESSAGE_FORMAT, requestId), message);
}
//
// REQUEST QUEUING
//
@Timed
public QueuedRequestId enqueueRequest(BaragonRequest request, InternalRequestStates state) {
final long start = System.currentTimeMillis();
try {
final String queuedRequestPath = String.format(REQUEST_ENQUEUE_FORMAT, request.getLoadBalancerService().getServiceId(), request.getLoadBalancerRequestId());
final String requestPath = String.format(REQUEST_FORMAT, request.getLoadBalancerRequestId());
final String requestStatePath = String.format(REQUEST_STATE_FORMAT, request.getLoadBalancerRequestId());
if (!nodeExists(REQUESTS_FORMAT)) {
createNode(REQUESTS_FORMAT);
}
if (!nodeExists(REQUEST_QUEUE_FORMAT)) {
createNode(REQUEST_QUEUE_FORMAT);
}
byte[] requestBytes = objectMapper.writeValueAsBytes(request);
byte[] stateBytes = objectMapper.writeValueAsBytes(state);
Collection<CuratorTransactionResult> results = curatorFramework.inTransaction()
.create().forPath(requestPath, requestBytes).and()
.create().forPath(requestStatePath, stateBytes).and()
.create().withMode(CreateMode.PERSISTENT_SEQUENTIAL).forPath(queuedRequestPath)
.and().commit();
log(OperationType.WRITE, Optional.of(3), Optional.of(requestBytes.length + stateBytes.length), start, String.format("Transaction Paths [%s + %s + %s]", requestPath, requestStatePath, queuedRequestPath));
return QueuedRequestId.fromString(ZKPaths.getNodeFromPath(Iterables.find(results, CuratorTransactionResult.ofTypeAndPath(org.apache.curator.framework.api.transaction.OperationType.CREATE, queuedRequestPath)).getResultPath()));
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
@Timed
public List<QueuedRequestId> getQueuedRequestIds() {
final List<String> nodes = getChildren(REQUEST_QUEUE_FORMAT);
Collections.sort(nodes, SEQUENCE_NODE_COMPARATOR_LOW_TO_HIGH);
final List<QueuedRequestId> queuedRequestIds = Lists.newArrayListWithCapacity(nodes.size());
for (String node : nodes) {
queuedRequestIds.add(QueuedRequestId.fromString(node));
}
return queuedRequestIds;
}
@Timed
public int getQueuedRequestCount() {
return getChildren(REQUEST_QUEUE_FORMAT).size();
}
@Timed
public void removeQueuedRequest(QueuedRequestId queuedRequestId) {
deleteNode(String.format(REQUEST_QUEUE_ITEM_FORMAT, queuedRequestId.buildZkPath()));
}
}