package com.hubspot.baragon.data; import java.util.Collection; import java.util.Collections; import java.util.Set; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.recipes.leader.LeaderLatch; import org.apache.zookeeper.KeeperException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.codahale.metrics.annotation.Timed; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Charsets; import com.google.common.base.Optional; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.inject.Inject; import com.google.inject.Singleton; import com.hubspot.baragon.config.ZooKeeperConfiguration; import com.hubspot.baragon.models.BaragonAgentMetadata; import com.hubspot.baragon.models.BaragonGroup; import com.hubspot.baragon.models.TrafficSource; @Singleton public class BaragonLoadBalancerDatastore extends AbstractDataStore { private static final Logger LOG = LoggerFactory.getLogger(BaragonLoadBalancerDatastore.class); public static final String LOAD_BALANCER_GROUPS_FORMAT = "/load-balancer"; public static final String LOAD_BALANCER_GROUP_FORMAT = LOAD_BALANCER_GROUPS_FORMAT + "/%s"; public static final String LOAD_BALANCER_TARGET_COUNT_FORMAT = LOAD_BALANCER_GROUP_FORMAT + "/targetCount"; public static final String LOAD_BALANCER_GROUP_LAST_REQUEST_FORMAT = LOAD_BALANCER_GROUP_FORMAT + "/lastRequest"; public static final String LOAD_BALANCER_GROUP_HOSTS_FORMAT = LOAD_BALANCER_GROUP_FORMAT + "/hosts"; public static final String LOAD_BALANCER_GROUP_HOST_FORMAT = LOAD_BALANCER_GROUP_HOSTS_FORMAT + "/%s"; public static final String LOAD_BALANCER_BASE_PATHS_FORMAT = LOAD_BALANCER_GROUPS_FORMAT + "/%s/base-uris"; public static final String LOAD_BALANCER_BASE_PATH_FORMAT = LOAD_BALANCER_BASE_PATHS_FORMAT + "/%s"; @Inject public BaragonLoadBalancerDatastore(CuratorFramework curatorFramework, ObjectMapper objectMapper, ZooKeeperConfiguration zooKeeperConfiguration) { super(curatorFramework, objectMapper, zooKeeperConfiguration); } public LeaderLatch createLeaderLatch(String clusterName, BaragonAgentMetadata agentMetadata) { try { return new LeaderLatch(curatorFramework, String.format(LOAD_BALANCER_GROUP_HOSTS_FORMAT, clusterName), objectMapper.writeValueAsString(agentMetadata)); } catch (JsonProcessingException e) { throw Throwables.propagate(e); } } @Timed public Collection<BaragonGroup> getLoadBalancerGroups() { final Collection<String> nodes = getChildren(LOAD_BALANCER_GROUPS_FORMAT); if (nodes.isEmpty()) { return Collections.emptyList(); } final Collection<BaragonGroup> groups = Lists.newArrayListWithCapacity(nodes.size()); for (String node : nodes) { try { groups.addAll(readFromZk(String.format(LOAD_BALANCER_GROUP_FORMAT, node), BaragonGroup.class).asSet()); } catch (Exception e) { LOG.error(String.format("Could not fetch info for group %s due to error %s", node, e)); } } return groups; } @Timed public Optional<BaragonGroup> getLoadBalancerGroup(String name) { try { return readFromZk(String.format(LOAD_BALANCER_GROUP_FORMAT, name), BaragonGroup.class); } catch (RuntimeException e) { if (e.getMessage().contains("No content")) { return Optional.absent(); } throw Throwables.propagate(e); } } @Timed public BaragonGroup addSourceToGroup(String name, TrafficSource source) { Optional<BaragonGroup> maybeGroup = getLoadBalancerGroup(name); BaragonGroup group; if (maybeGroup.isPresent()) { group = maybeGroup.get(); group.addTrafficSource(source); } else { group = new BaragonGroup(name, Optional.<String>absent(), Sets.newHashSet(source), null, Optional.<String>absent(), Collections.<String>emptySet()); } writeToZk(String.format(LOAD_BALANCER_GROUP_FORMAT, name), group); return group; } @Timed public Optional<BaragonGroup> removeSourceFromGroup(String name, TrafficSource source) { Optional<BaragonGroup> maybeGroup = getLoadBalancerGroup(name); if (maybeGroup.isPresent()) { maybeGroup.get().removeTrafficSource(source); writeToZk(String.format(LOAD_BALANCER_GROUP_FORMAT, name), maybeGroup.get()); return maybeGroup; } else { return Optional.absent(); } } @Timed public void updateGroupInfo(String name, Optional<String> defaultDomain, Set<String> domains) { Optional<BaragonGroup> maybeGroup = getLoadBalancerGroup(name); BaragonGroup group; if (maybeGroup.isPresent()) { group = maybeGroup.get(); group.setDefaultDomain(defaultDomain); group.setDomains(domains); } else { group = new BaragonGroup(name, defaultDomain, Collections.<TrafficSource>emptySet(), null, defaultDomain, domains); } writeToZk(String.format(LOAD_BALANCER_GROUP_FORMAT, name), group); } @Timed public Set<String> getLoadBalancerGroupNames() { return ImmutableSet.copyOf(getChildren(LOAD_BALANCER_GROUPS_FORMAT)); } @Timed public Optional<BaragonAgentMetadata> getAgent(String path) { return readFromZk(path, BaragonAgentMetadata.class); } @Timed public Optional<BaragonAgentMetadata> getAgent(String clusterName, String agentId) { Collection<BaragonAgentMetadata> agents = getAgentMetadata(clusterName); Optional<BaragonAgentMetadata> maybeAgent = Optional.absent(); for (BaragonAgentMetadata agent : agents) { if (agent.getAgentId().equals(agentId)) { maybeAgent = Optional.of(agent); break; } } return maybeAgent; } @Timed public Collection<BaragonAgentMetadata> getAgentMetadata(String clusterName) { final Collection<String> nodes = getChildren(String.format(LOAD_BALANCER_GROUP_HOSTS_FORMAT, clusterName)); if (nodes.isEmpty()) { return Collections.emptyList(); } final Collection<BaragonAgentMetadata> metadata = Lists.newArrayListWithCapacity(nodes.size()); for (String node : nodes) { try { final String value = new String(curatorFramework.getData().forPath(String.format(LOAD_BALANCER_GROUP_HOST_FORMAT, clusterName, node)), Charsets.UTF_8); if (value.startsWith("http://")) { metadata.add(BaragonAgentMetadata.fromString(value)); } else { metadata.add(objectMapper.readValue(value, BaragonAgentMetadata.class)); } } catch (KeeperException.NoNodeException nne) { // uhh, didnt see that... } catch (JsonParseException | JsonMappingException je) { LOG.warn(String.format("Exception deserializing %s", String.format(LOAD_BALANCER_GROUP_HOST_FORMAT, clusterName, node)), je); } catch (Exception e) { throw Throwables.propagate(e); } } return metadata; } @Timed public Collection<BaragonAgentMetadata> getAgentMetadata(Collection<String> clusterNames) { final Set<BaragonAgentMetadata> metadata = Sets.newHashSet(); for (String clusterName : clusterNames) { metadata.addAll(getAgentMetadata(clusterName)); } return metadata; } @Timed public Optional<String> getBasePathServiceId(String loadBalancerGroup, String basePath) { return readFromZk(String.format(LOAD_BALANCER_BASE_PATH_FORMAT, loadBalancerGroup, encodeUrl(basePath)), String.class); } @Timed public void clearBasePath(String loadBalancerGroup, String basePath) { deleteNode(String.format(LOAD_BALANCER_BASE_PATH_FORMAT, loadBalancerGroup, encodeUrl(basePath))); } @Timed public void setBasePathServiceId(String loadBalancerGroup, String basePath, String serviceId) { writeToZk(String.format(LOAD_BALANCER_BASE_PATH_FORMAT, loadBalancerGroup, encodeUrl(basePath)), serviceId); } @Timed public Collection<String> getBasePaths(String loadBalancerGroup) { final Collection<String> encodedPaths = getChildren(String.format(LOAD_BALANCER_BASE_PATHS_FORMAT, loadBalancerGroup)); final Collection<String> decodedPaths = Lists.newArrayListWithCapacity(encodedPaths.size()); for (String encodedPath : encodedPaths) { decodedPaths.add(decodeUrl(encodedPath)); } return decodedPaths; } @Timed public Optional<String> getLastRequestForGroup(String loadBalancerGroup) { return readFromZk(String.format(LOAD_BALANCER_GROUP_LAST_REQUEST_FORMAT, loadBalancerGroup), String.class); } @Timed public void setLastRequestId(String loadBalancerGroup, String requestId) { writeToZk(String.format(LOAD_BALANCER_GROUP_LAST_REQUEST_FORMAT, loadBalancerGroup), requestId); } public int setTargetCount(String group, Integer count) { writeToZk(String.format(LOAD_BALANCER_TARGET_COUNT_FORMAT, group), count.toString()); return count; } public Optional<Integer> getTargetCount(String group) { return readFromZk(String.format(LOAD_BALANCER_TARGET_COUNT_FORMAT, group), Integer.class); } }