/* * Copyright (C) 2014-2017 LinkedIn Corp. All rights reserved. * * 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. */ package gobblin.restli.throttling; import com.codahale.metrics.Timer; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.collect.Maps; import com.google.inject.AbstractModule; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.TypeLiteral; import com.google.inject.name.Names; import com.google.inject.servlet.GuiceServletContextListener; import com.google.inject.servlet.ServletModule; import com.linkedin.r2.filter.FilterChain; import com.linkedin.r2.filter.FilterChains; import com.linkedin.r2.filter.compression.EncodingType; import com.linkedin.r2.filter.compression.ServerCompressionFilter; import com.linkedin.r2.filter.logging.SimpleLoggingFilter; import com.linkedin.restli.server.RestLiConfig; import com.linkedin.restli.server.guice.GuiceRestliServlet; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; import gobblin.broker.SharedResourcesBrokerFactory; import gobblin.broker.iface.NotConfiguredException; import gobblin.broker.iface.SharedResourcesBroker; import gobblin.metrics.MetricContext; import gobblin.metrics.broker.MetricContextFactory; import gobblin.metrics.broker.MetricContextKey; import java.io.Closeable; import java.io.IOException; import java.net.InetAddress; import java.net.URI; import java.net.URISyntaxException; import java.util.Enumeration; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import lombok.Getter; import lombok.extern.slf4j.Slf4j; /** * {@link GuiceServletContextListener} for creating an injector in a gobblin-throttling-server servlet. */ @Slf4j @Getter public class ThrottlingGuiceServletConfig extends GuiceServletContextListener implements Closeable { public static final String THROTTLING_SERVER_PREFIX = "throttlingServer."; public static final String LISTENING_PORT = THROTTLING_SERVER_PREFIX + "listeningPort"; public static final String HOSTNAME = THROTTLING_SERVER_PREFIX + "hostname"; public static final String ZK_STRING_KEY = "throttling.helix.zkString"; private Optional<LeaderFinder<URIMetadata>> _leaderFinder; private Config _config; private Injector _injector; @Override public void contextInitialized(ServletContextEvent servletContextEvent) { ServletContext context = servletContextEvent.getServletContext(); Enumeration<String> parameters = context.getInitParameterNames(); Map<String, String> configMap = Maps.newHashMap(); while (parameters.hasMoreElements()) { String key = parameters.nextElement(); configMap.put(key, context.getInitParameter(key)); } initialize(ConfigFactory.parseMap(configMap)); super.contextInitialized(servletContextEvent); } public void initialize(Config config) { try { this._config = config; this._leaderFinder = getLeaderFinder(this._config); if (this._leaderFinder.isPresent()) { this._leaderFinder.get().startAsync(); this._leaderFinder.get().awaitRunning(100, TimeUnit.SECONDS); } this._injector = createInjector(this._config, this._leaderFinder); } catch (URISyntaxException | IOException | TimeoutException exc) { log.error(String.format("Error in %s initialization.", ThrottlingGuiceServletConfig.class.getSimpleName()), exc); throw new RuntimeException(exc); } } @Override public Injector getInjector() { return this._injector; } private Injector createInjector(final Config config, final Optional<LeaderFinder<URIMetadata>> leaderFinder) { final SharedResourcesBroker<ThrottlingServerScopes> topLevelBroker = SharedResourcesBrokerFactory.createDefaultTopLevelBroker(config, ThrottlingServerScopes.GLOBAL.defaultScopeInstance()); return Guice.createInjector(new AbstractModule() { @Override protected void configure() { try { RestLiConfig restLiConfig = new RestLiConfig(); restLiConfig.setResourcePackageNames("gobblin.restli.throttling"); bind(RestLiConfig.class).toInstance(restLiConfig); bind(SharedResourcesBroker.class).annotatedWith(Names.named(LimiterServerResource.BROKER_INJECT_NAME)).toInstance(topLevelBroker); MetricContext metricContext = topLevelBroker.getSharedResource(new MetricContextFactory<ThrottlingServerScopes>(), new MetricContextKey()); Timer timer = metricContext.timer(LimiterServerResource.REQUEST_TIMER_NAME); bind(MetricContext.class).annotatedWith(Names.named(LimiterServerResource.METRIC_CONTEXT_INJECT_NAME)).toInstance(metricContext); bind(Timer.class).annotatedWith(Names.named(LimiterServerResource.REQUEST_TIMER_INJECT_NAME)).toInstance(timer); bind(new TypeLiteral<Optional<LeaderFinder<URIMetadata>>>() { }).annotatedWith(Names.named(LimiterServerResource.HELIX_MANAGER_INJECT_NAME)).toInstance(leaderFinder); FilterChain filterChain = FilterChains.create(new ServerCompressionFilter(new EncodingType[]{EncodingType.SNAPPY}), new SimpleLoggingFilter()); bind(FilterChain.class).toInstance(filterChain); } catch (NotConfiguredException nce) { throw new RuntimeException(nce); } } }, new ServletModule() { @Override protected void configureServlets() { serve("/*").with(GuiceRestliServlet.class); } }); } private static Optional<LeaderFinder<URIMetadata>> getLeaderFinder(Config config) throws URISyntaxException, IOException { if (config.hasPath(ZK_STRING_KEY)) { Preconditions.checkArgument(config.hasPath(LISTENING_PORT), "Missing required config " + LISTENING_PORT); int port = config.getInt(LISTENING_PORT); String hostname = config.hasPath(HOSTNAME) ? config.getString(HOSTNAME) : InetAddress.getLocalHost().getCanonicalHostName(); String zkString = config.getString(ZK_STRING_KEY); return Optional.<LeaderFinder<URIMetadata>>of(new ZookeeperLeaderElection<>(zkString, "ThrottlingServer", new URIMetadata(new URI("http", null, hostname, port, null, null, null)))); } return Optional.absent(); } @Override public void contextDestroyed(ServletContextEvent servletContextEvent) { close(); super.contextDestroyed(servletContextEvent); } @Override public void close() { try { if (this._leaderFinder.isPresent()) { this._leaderFinder.get().stopAsync(); this._leaderFinder.get().awaitTerminated(2, TimeUnit.SECONDS); } } catch (TimeoutException te) { // Do nothing } } /** * Get an instance of {@link LimiterServerResource}. */ public LimiterServerResource getLimiterResource() { return this._injector.getInstance(LimiterServerResource.class); } }