package com.hubspot.baragon.agent; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.ReentrantLock; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.framework.recipes.leader.LeaderLatch; import org.apache.curator.retry.ExponentialBackoffRetry; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.jknack.handlebars.Handlebars; import com.google.common.base.Optional; import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.inject.Binder; import com.google.inject.Provides; import com.google.inject.Scopes; import com.google.inject.Singleton; import com.google.inject.name.Named; import com.hubspot.baragon.BaragonDataModule; import com.hubspot.baragon.agent.config.BaragonAgentConfiguration; import com.hubspot.baragon.agent.config.LoadBalancerConfiguration; import com.hubspot.baragon.agent.config.TemplateConfiguration; import com.hubspot.baragon.agent.config.TestingConfiguration; import com.hubspot.baragon.agent.handlebars.CurrentRackIsPresentHelper; import com.hubspot.baragon.agent.handlebars.FirstOfHelper; import com.hubspot.baragon.agent.handlebars.FormatTimestampHelper; import com.hubspot.baragon.agent.handlebars.IfEqualHelperSource; import com.hubspot.baragon.agent.handlebars.PreferSameRackWeightingHelper; import com.hubspot.baragon.agent.handlebars.ResolveHostnameHelper; import com.hubspot.baragon.agent.healthcheck.ConfigChecker; import com.hubspot.baragon.agent.healthcheck.LoadBalancerHealthcheck; import com.hubspot.baragon.agent.healthcheck.ZooKeeperHealthcheck; import com.hubspot.baragon.agent.lbs.FilesystemConfigHelper; import com.hubspot.baragon.agent.lbs.LbConfigGenerator; import com.hubspot.baragon.agent.lbs.LocalLbAdapter; import com.hubspot.baragon.agent.listeners.ResyncListener; import com.hubspot.baragon.agent.managed.BaragonAgentGraphiteReporterManaged; import com.hubspot.baragon.agent.managed.BootstrapManaged; import com.hubspot.baragon.agent.managed.LifecycleHelper; import com.hubspot.baragon.agent.managers.AgentRequestManager; import com.hubspot.baragon.agent.models.FilePathFormatType; import com.hubspot.baragon.agent.models.LbConfigTemplate; import com.hubspot.baragon.agent.resources.BargonAgentResourcesModule; import com.hubspot.baragon.agent.workers.AgentHeartbeatWorker; import com.hubspot.baragon.config.AuthConfiguration; import com.hubspot.baragon.config.HttpClientConfiguration; import com.hubspot.baragon.config.ZooKeeperConfiguration; import com.hubspot.baragon.data.BaragonConnectionStateListener; import com.hubspot.baragon.data.BaragonLoadBalancerDatastore; import com.hubspot.baragon.models.BaragonAgentEc2Metadata; import com.hubspot.baragon.models.BaragonAgentMetadata; import com.hubspot.baragon.models.BaragonAgentState; import com.hubspot.baragon.utils.JavaUtils; import com.hubspot.dropwizard.guicier.DropwizardAwareModule; import com.hubspot.horizon.HttpClient; import com.hubspot.horizon.HttpConfig; import com.hubspot.horizon.apache.ApacheHttpClient; import io.dropwizard.jetty.HttpConnectorFactory; import io.dropwizard.server.SimpleServerFactory; public class BaragonAgentServiceModule extends DropwizardAwareModule<BaragonAgentConfiguration> { public static final String AGENT_SCHEDULED_EXECUTOR = "baragon.service.scheduledExecutor"; public static final String AGENT_LEADER_LATCH = "baragon.agent.leaderLatch"; public static final String AGENT_LOCK = "baragon.agent.lock"; public static final String AGENT_TEMPLATES = "baragon.agent.templates"; public static final String AGENT_MOST_RECENT_REQUEST_ID = "baragon.agent.mostRecentRequestId"; public static final String AGENT_LOCK_TIMEOUT_MS = "baragon.agent.lock.timeoutMs"; public static final String DEFAULT_TEMPLATE_NAME = "default"; public static final String BARAGON_AGENT_HTTP_CLIENT = "baragon.agent.http.client"; public static final String CONFIG_ERROR_MESSAGE = "baragon.agent.config.error.message"; private static final Pattern FORMAT_PATTERN = Pattern.compile("[^%]%([+-]?\\d*.?\\d*)?[sdf]"); @Override public void configure(Binder binder) { binder.requireExplicitBindings(); binder.requireExactBindingAnnotations(); binder.requireAtInjectOnConstructors(); binder.install(new BaragonDataModule()); binder.install(new BargonAgentResourcesModule()); // Healthchecks binder.bind(LoadBalancerHealthcheck.class).in(Scopes.SINGLETON); binder.bind(ZooKeeperHealthcheck.class).in(Scopes.SINGLETON); binder.bind(ConfigChecker.class).in(Scopes.SINGLETON); // Managed binder.bind(BaragonAgentGraphiteReporterManaged.class).in(Scopes.SINGLETON); binder.bind(BootstrapManaged.class).in(Scopes.SINGLETON); binder.bind(LifecycleHelper.class).in(Scopes.SINGLETON); // Manager binder.bind(AgentRequestManager.class).in(Scopes.SINGLETON); binder.bind(ResyncListener.class).in(Scopes.SINGLETON); binder.bind(LocalLbAdapter.class).in(Scopes.SINGLETON); binder.bind(LbConfigGenerator.class).in(Scopes.SINGLETON); binder.bind(ServerProvider.class).in(Scopes.SINGLETON); binder.bind(FilesystemConfigHelper.class).in(Scopes.SINGLETON); binder.bind(AgentHeartbeatWorker.class).in(Scopes.SINGLETON); } @Provides @Singleton public Handlebars providesHandlebars(BaragonAgentConfiguration config, BaragonAgentMetadata agentMetadata) { final Handlebars handlebars = new Handlebars(); handlebars.registerHelper(FormatTimestampHelper.NAME, new FormatTimestampHelper(config.getDefaultDateFormat())); handlebars.registerHelper(FirstOfHelper.NAME, new FirstOfHelper("")); handlebars.registerHelper(CurrentRackIsPresentHelper.NAME, new CurrentRackIsPresentHelper(agentMetadata.getEc2().getAvailabilityZone())); handlebars.registerHelper(ResolveHostnameHelper.NAME, new ResolveHostnameHelper()); handlebars.registerHelpers(new PreferSameRackWeightingHelper(config, agentMetadata)); handlebars.registerHelpers(IfEqualHelperSource.class); return handlebars; } @Provides @Singleton @Named(AGENT_TEMPLATES) public Map<String, List<LbConfigTemplate>> providesAgentTemplates(Handlebars handlebars, BaragonAgentConfiguration configuration) throws Exception { Map<String, List<LbConfigTemplate>> templates = new HashMap<>(); for (TemplateConfiguration templateConfiguration : configuration.getTemplates()) { if (!Strings.isNullOrEmpty(templateConfiguration.getDefaultTemplate())) { if (templates.containsKey(DEFAULT_TEMPLATE_NAME)) { templates.get(DEFAULT_TEMPLATE_NAME).add(new LbConfigTemplate(templateConfiguration.getFilename(), handlebars.compileInline(templateConfiguration.getDefaultTemplate()), getFilePathFormatType(templateConfiguration.getFilename()))); } else { templates.put(DEFAULT_TEMPLATE_NAME, Lists.newArrayList(new LbConfigTemplate(templateConfiguration.getFilename(), handlebars.compileInline(templateConfiguration.getDefaultTemplate()), getFilePathFormatType(templateConfiguration.getFilename())))); } } if (templateConfiguration.getNamedTemplates() != null) { for (Map.Entry<String, String> entry : templateConfiguration.getNamedTemplates().entrySet()) { if (!Strings.isNullOrEmpty(entry.getValue())) { if (templates.containsKey(entry.getKey())) { templates.get(entry.getKey()).add(new LbConfigTemplate(templateConfiguration.getFilename(), handlebars.compileInline(entry.getValue()), getFilePathFormatType(templateConfiguration.getFilename()))); } else { templates.put(entry.getKey(), Lists.newArrayList(new LbConfigTemplate(templateConfiguration.getFilename(), handlebars.compileInline(entry.getValue()), getFilePathFormatType(templateConfiguration.getFilename())))); } } } } } return templates; } private FilePathFormatType getFilePathFormatType(String filenameFormat) { Matcher m = FORMAT_PATTERN.matcher(filenameFormat); int count = 0; while(m.find()) { count ++; } if (count == 0) { return FilePathFormatType.NONE; } else if (count == 1) { return FilePathFormatType.SERVICE; } else { return FilePathFormatType.DOMAIN_SERVICE; } } @Provides public LoadBalancerConfiguration provideLoadBalancerInfo(BaragonAgentConfiguration configuration) { return configuration.getLoadBalancerConfiguration(); } @Provides public ZooKeeperConfiguration provideZooKeeperConfiguration(BaragonAgentConfiguration configuration) { return configuration.getZooKeeperConfiguration(); } @Provides @Named(AGENT_LOCK_TIMEOUT_MS) public long provideAgentLockTimeoutMs(BaragonAgentConfiguration configuration) { return configuration.getAgentLockTimeoutMs(); } @Provides public AuthConfiguration providesAuthConfiguration(BaragonAgentConfiguration configuration) { return configuration.getAuthConfiguration(); } @Provides public HttpClientConfiguration provideHttpClientConfiguration(BaragonAgentConfiguration configuration) { return configuration.getHttpClientConfiguration(); } @Provides @Singleton public BaragonAgentMetadata providesAgentMetadata(BaragonAgentConfiguration config) throws Exception { final SimpleServerFactory simpleServerFactory = (SimpleServerFactory) config.getServerFactory(); final HttpConnectorFactory httpFactory = (HttpConnectorFactory) simpleServerFactory.getConnector(); final int httpPort = httpFactory.getPort(); final String hostname = config.getHostname().or(JavaUtils.getHostAddress()); final Optional<String> domain = config.getLoadBalancerConfiguration().getDomain(); final String appRoot = simpleServerFactory.getApplicationContextPath(); final String baseAgentUri = String.format(config.getBaseUrlTemplate(), hostname, httpPort, appRoot); final String agentId = String.format("%s:%s", hostname, httpPort); return new BaragonAgentMetadata(baseAgentUri, agentId, domain, BaragonAgentEc2Metadata.fromEnvironment(), config.getExtraAgentData(), true); } @Provides @Singleton @Named(AGENT_LEADER_LATCH) public LeaderLatch providesAgentLeaderLatch(BaragonLoadBalancerDatastore loadBalancerDatastore, BaragonAgentConfiguration config, BaragonAgentMetadata baragonAgentMetadata) { return loadBalancerDatastore.createLeaderLatch(config.getLoadBalancerConfiguration().getName(), baragonAgentMetadata); } @Provides @Singleton public Optional<TestingConfiguration> providesTestingConfiguration(BaragonAgentConfiguration configuration) { return Optional.fromNullable(configuration.getTestingConfiguration()); } @Provides @Singleton @Named(AGENT_LOCK) public ReentrantLock providesAgentLock() { return new ReentrantLock(); } @Provides @Singleton @Named(AGENT_MOST_RECENT_REQUEST_ID) public AtomicReference<String> providesMostRecentRequestId() { return new AtomicReference<>(); } @Provides @Singleton @Named(CONFIG_ERROR_MESSAGE) public AtomicReference<Optional<String>> providesConfigErrorMessage() { return new AtomicReference<>(); } @Provides @Singleton @Named(AGENT_SCHEDULED_EXECUTOR) public ScheduledExecutorService providesScheduledExecutor() { return Executors.newScheduledThreadPool(2); } @Provides @Singleton @Named(BARAGON_AGENT_HTTP_CLIENT) public HttpClient providesApacheHttpClient(HttpClientConfiguration config, ObjectMapper objectMapper) { HttpConfig.Builder configBuilder = HttpConfig.newBuilder() .setRequestTimeoutSeconds(config.getRequestTimeoutInMs() / 1000) .setUserAgent(config.getUserAgent()) .setConnectTimeoutSeconds(config.getConnectionTimeoutInMs() / 1000) .setFollowRedirects(true) .setMaxRetries(config.getMaxRequestRetry()) .setObjectMapper(objectMapper); return new ApacheHttpClient(configBuilder.build()); } @Singleton @Provides public CuratorFramework provideCurator(ZooKeeperConfiguration config, BaragonConnectionStateListener connectionStateListener) { CuratorFramework client = CuratorFrameworkFactory.newClient( config.getQuorum(), config.getSessionTimeoutMillis(), config.getConnectTimeoutMillis(), new ExponentialBackoffRetry(config.getRetryBaseSleepTimeMilliseconds(), config.getRetryMaxTries())); client.getConnectionStateListenable().addListener(connectionStateListener); client.start(); return client.usingNamespace(config.getZkNamespace()); } @Singleton @Provides public AtomicReference<BaragonAgentState> providesAgentState() { return new AtomicReference<>(BaragonAgentState.BOOTSTRAPING); } }