@Test
  public void testPropertyChange() throws Exception {

    BlobStoreConfigurationSource source = new BlobStoreConfigurationSource(ctx);
    FixedDelayPollingScheduler scheduler = new FixedDelayPollingScheduler(0, 1000, false);
    DynamicConfiguration dynamicConfig = new DynamicConfiguration(source, scheduler);
    ConfigurationManager.loadPropertiesFromConfiguration(dynamicConfig);

    DynamicStringProperty test1 =
        DynamicPropertyFactory.getInstance().getStringProperty("test1", "");
    DynamicStringProperty test2 =
        DynamicPropertyFactory.getInstance().getStringProperty("test2", "");
    DynamicStringProperty test3 =
        DynamicPropertyFactory.getInstance().getStringProperty("test3", "");

    assertEquals("val1", test1.get());
    assertEquals("val2", test2.get());
    assertEquals("val3", test3.get());

    update();
    Thread.sleep(1000);

    assertEquals("vala", test1.get());
    assertEquals("valb", test2.get());
    assertEquals("valc", test3.get());
  }
  @Override
  public Server chooseServer(Object key) {
    if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
      logger.debug("Zone aware logic disabled or there is only one zone");
      return super.chooseServer(key);
    }
    Server server = null;
    try {
      LoadBalancerStats lbStats = getLoadBalancerStats();
      Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
      logger.debug("Zone snapshots: {}", zoneSnapshot);
      if (triggeringLoad == null) {
        triggeringLoad =
            DynamicPropertyFactory.getInstance()
                .getDoubleProperty(
                    "ZoneAwareNIWSDiscoveryLoadBalancer."
                        + this.getName()
                        + ".triggeringLoadPerServerThreshold",
                    0.2d);
      }

      if (triggeringBlackoutPercentage == null) {
        triggeringBlackoutPercentage =
            DynamicPropertyFactory.getInstance()
                .getDoubleProperty(
                    "ZoneAwareNIWSDiscoveryLoadBalancer."
                        + this.getName()
                        + ".avoidZoneWithBlackoutPercetage",
                    0.99999d);
      }
      Set<String> availableZones =
          ZoneAvoidanceRule.getAvailableZones(
              zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get());
      logger.debug("Available zones: {}", availableZones);
      if (availableZones != null && availableZones.size() < zoneSnapshot.keySet().size()) {
        String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones);
        logger.debug("Zone chosen: {}", zone);
        if (zone != null) {
          BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone);
          server = zoneLoadBalancer.chooseServer(key);
        }
      }
    } catch (Throwable e) {
      logger.error("Unexpected exception when choosing server using zone aware logic", e);
    }
    if (server != null) {
      return server;
    } else {
      logger.debug("Zone avoidance logic is not invoked.");
      return super.chooseServer(key);
    }
  }
Example #3
0
  public RibbonCommand(
      String commandKey,
      RestClient restClient,
      HttpClientRequest.Verb verb,
      String uri,
      MultivaluedMap<String, String> headers,
      MultivaluedMap<String, String> params,
      InputStream requestEntity)
      throws URISyntaxException {

    super(
        Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey(commandKey))
            .andCommandPropertiesDefaults(
                // we want to default to semaphore-isolation since this wraps
                // 2 others commands that are already thread isolated
                HystrixCommandProperties.Setter()
                    .withExecutionIsolationStrategy(
                        HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)
                    .withExecutionIsolationSemaphoreMaxConcurrentRequests(
                        DynamicPropertyFactory.getInstance()
                            .getIntProperty(
                                ZuulConstants.ZUUL_EUREKA + commandKey + ".semaphore.maxSemaphores",
                                100)
                            .get())));

    this.restClient = restClient;
    this.verb = verb;
    this.uri = new URI(uri);
    this.headers = headers;
    this.params = params;
    this.requestEntity = requestEntity;
  }
Example #4
0
 private static Object getPluginImplementationViaArchaius(Class<?> pluginClass) {
   String classSimpleName = pluginClass.getSimpleName();
   // Check Archaius for plugin class.
   String propertyName = "hystrix.plugin." + classSimpleName + ".implementation";
   String implementingClass =
       DynamicPropertyFactory.getInstance().getStringProperty(propertyName, null).get();
   if (implementingClass != null) {
     try {
       Class<?> cls = Class.forName(implementingClass);
       // narrow the scope (cast) to the type we're expecting
       cls = cls.asSubclass(pluginClass);
       return cls.newInstance();
     } catch (ClassCastException e) {
       throw new RuntimeException(
           classSimpleName
               + " implementation is not an instance of "
               + classSimpleName
               + ": "
               + implementingClass);
     } catch (ClassNotFoundException e) {
       throw new RuntimeException(
           classSimpleName + " implementation class not found: " + implementingClass, e);
     } catch (InstantiationException e) {
       throw new RuntimeException(
           classSimpleName + " implementation not able to be instantiated: " + implementingClass,
           e);
     } catch (IllegalAccessException e) {
       throw new RuntimeException(
           classSimpleName + " implementation not able to be accessed: " + implementingClass, e);
     }
   } else {
     return null;
   }
 }
Example #5
0
  /**
   * Constructor
   *
   * @param serviceName the service cluster name
   * @param myAvailabilityZone the availability zone of the server running the load balancer
   * @param metricRegistry the registry for collected metrics
   */
  public ZoneAwareLoadBalancer(
      String serviceName, String myAvailabilityZone, MetricRegistry metricRegistry) {
    Preconditions.checkArgument(
        !Strings.isNullOrEmpty(serviceName), "'serviceName' cannot be null or empty.");
    Preconditions.checkArgument(
        !Strings.isNullOrEmpty(myAvailabilityZone),
        "'myAvailabilityZone' cannot be null or empty.");

    this.serviceName = serviceName;
    this.myLocation = new Location(myAvailabilityZone);
    this.locations =
        CacheBuilder.newBuilder()
            .expireAfterAccess(5, TimeUnit.MINUTES)
            .build(
                new CacheLoader<ServerStats, Location>() {
                  @Override
                  public Location load(ServerStats key) throws Exception {
                    return getLocationFromServer(key);
                  }
                });

    this.propMaxRequestsPerSecond =
        DynamicPropertyFactory.getInstance()
            .getDoubleProperty("janus.serviceName." + serviceName + ".maxRequestsPerSecond", 100);
    this.propEscapeAreaThreshold =
        DynamicPropertyFactory.getInstance()
            .getDoubleProperty("janus.serviceName." + serviceName + ".escapeAreaThreshold", 0.9);
    this.propEscapeRegionThreshold =
        DynamicPropertyFactory.getInstance()
            .getDoubleProperty("janus.serviceName." + serviceName + ".escapeRegionThreshold", 0.9);
    this.propEscapeAvailabilityThreshold =
        DynamicPropertyFactory.getInstance()
            .getDoubleProperty(
                "janus.serviceName." + serviceName + ".escapeAvailabilityThreshold", 0.9);

    this.metricRegistry = metricRegistry;
  }
/** Servlet that writes SSE JSON every time a request is made */
public class HystrixRequestEventsSseServlet extends HystrixSampleSseServlet {

  private static final long serialVersionUID = 6389353893099737870L;

  /* used to track number of connections and throttle */
  private static AtomicInteger concurrentConnections = new AtomicInteger(0);
  private static DynamicIntProperty maxConcurrentConnections =
      DynamicPropertyFactory.getInstance()
          .getIntProperty("hystrix.config.stream.maxConcurrentConnections", 5);

  public HystrixRequestEventsSseServlet() {
    this(
        HystrixRequestEventsStream.getInstance().observe(),
        DEFAULT_PAUSE_POLLER_THREAD_DELAY_IN_MS);
  }

  /* package-private */ HystrixRequestEventsSseServlet(
      Observable<HystrixRequestEvents> sampleStream, int pausePollerThreadDelayInMs) {
    super(
        sampleStream.map(
            new Func1<HystrixRequestEvents, String>() {
              @Override
              public String call(HystrixRequestEvents requestEvents) {
                return SerialHystrixRequestEvents.toJsonString(requestEvents);
              }
            }),
        pausePollerThreadDelayInMs);
  }

  @Override
  protected int getMaxNumberConcurrentConnectionsAllowed() {
    return maxConcurrentConnections.get();
  }

  @Override
  protected int getNumberCurrentConnections() {
    return concurrentConnections.get();
  }

  @Override
  protected int incrementAndGetCurrentConcurrentConnections() {
    return concurrentConnections.incrementAndGet();
  }

  @Override
  protected void decrementCurrentConcurrentConnections() {
    concurrentConnections.decrementAndGet();
  }
}
  protected void setPropertyInternal(final String propName, Object value) {
    String stringValue = (value == null) ? "" : String.valueOf(value);
    properties.put(propName, stringValue);
    if (!enableDynamicProperties) {
      return;
    }
    String configKey = getConfigKey(propName);
    final DynamicStringProperty prop =
        DynamicPropertyFactory.getInstance().getStringProperty(configKey, null);
    Runnable callback =
        new Runnable() {
          @Override
          public void run() {
            String value = prop.get();
            if (value != null) {
              properties.put(propName, value);
            } else {
              properties.remove(propName);
            }
          }

          // equals and hashcode needed
          // since this is anonymous object is later used as a set key

          @Override
          public boolean equals(Object other) {
            if (other == null) {
              return false;
            }
            if (getClass() == other.getClass()) {
              return toString().equals(other.toString());
            }
            return false;
          }

          @Override
          public String toString() {
            return propName;
          }

          @Override
          public int hashCode() {
            return propName.hashCode();
          }
        };
    prop.addCallback(callback);
    dynamicProperties.put(propName, prop);
  }
Example #8
0
  /**
   * Convert <code>VIPAddress</code> by substituting environment variables if necessary.
   *
   * @param vipAddressMacro the macro for which the interpolation needs to be made.
   * @return a string representing the final <code>VIPAddress</code> after substitution.
   */
  private static String resolveDeploymentContextBasedVipAddresses(String vipAddressMacro) {
    String result = vipAddressMacro;

    if (vipAddressMacro == null) {
      return null;
    }

    Matcher matcher = VIP_ATTRIBUTES_PATTERN.matcher(result);
    while (matcher.find()) {
      String key = matcher.group(1);
      String value = DynamicPropertyFactory.getInstance().getStringProperty(key, "").get();

      logger.debug("att:" + matcher.group());
      logger.debug(", att key:" + key);
      logger.debug(", att value:" + value);
      logger.debug("");
      result = result.replaceAll("\\$\\{" + key + "\\}", value);
      matcher = VIP_ATTRIBUTES_PATTERN.matcher(result);
    }

    return result;
  }
/**
 * User: gorzell Date: 1/17/13 Time: 10:18 AM Some of the basic plumbing and properties for a
 * polling source that talks to Dynamo.
 */
public abstract class AbstractDynamoDbConfigurationSource<T> {
  private static final Logger log =
      LoggerFactory.getLogger(AbstractDynamoDbConfigurationSource.class);

  // Property names
  static final String tablePropertyName = "com.netflix.config.dynamo.tableName";
  static final String keyAttributePropertyName = "com.netflix.config.dynamo.keyAttributeName";
  static final String valueAttributePropertyName = "com.netflix.config.dynamo.valueAttributeName";
  static final String endpointPropertyName = "com.netflix.config.dynamo.endpoint";
  static final String pollingMaxBackOffMsPropertyName =
      "com.netflix.config.dynamo.maxPollingBackOffMs";
  static final String pollingMinBackOffMsPropertyName =
      "com.netflix.config.dynamo.maxPollingBackOffMs";
  static final String maxRetryCountPropertyName = "com.netflix.config.dynamo.maxRetryCount";

  // Property defaults
  static final String defaultTable = "archaiusProperties";
  static final String defaultKeyAttribute = "key";
  static final String defaultValueAttribute = "value";
  static final String defaultEndpoint = "dynamodb.us-east-1.amazonaws.com";
  static final Long defaultMaxBackOffMs = 5 * 1000L;
  static final Long defaultMinBackOffMs = 500L;
  static final Long defaultMaxRetryCount = 100L;

  // Dynamic Properties
  protected DynamicStringProperty tableName =
      DynamicPropertyFactory.getInstance().getStringProperty(tablePropertyName, defaultTable);
  protected DynamicStringProperty keyAttributeName =
      DynamicPropertyFactory.getInstance()
          .getStringProperty(keyAttributePropertyName, defaultKeyAttribute);
  protected DynamicStringProperty valueAttributeName =
      DynamicPropertyFactory.getInstance()
          .getStringProperty(valueAttributePropertyName, defaultValueAttribute);
  protected DynamicStringProperty endpointName =
      DynamicPropertyFactory.getInstance().getStringProperty(endpointPropertyName, defaultEndpoint);
  protected DynamicLongProperty maxBackOffMs =
      DynamicPropertyFactory.getInstance()
          .getLongProperty(pollingMaxBackOffMsPropertyName, defaultMaxBackOffMs);
  protected DynamicLongProperty minBackOffMs =
      DynamicPropertyFactory.getInstance()
          .getLongProperty(pollingMinBackOffMsPropertyName, defaultMinBackOffMs);
  protected DynamicLongProperty maxRetryCount =
      DynamicPropertyFactory.getInstance()
          .getLongProperty(maxRetryCountPropertyName, defaultMaxRetryCount);

  protected AmazonDynamoDB dbClient;

  public AbstractDynamoDbConfigurationSource() {
    this(new AmazonDynamoDBClient());
    setEndpoint();
  }

  public AbstractDynamoDbConfigurationSource(ClientConfiguration clientConfiguration) {
    this(new AmazonDynamoDBClient(clientConfiguration));
    setEndpoint();
  }

  public AbstractDynamoDbConfigurationSource(AWSCredentials credentials) {
    this(new AmazonDynamoDBClient(credentials));
    setEndpoint();
  }

  public AbstractDynamoDbConfigurationSource(
      AWSCredentials credentials, ClientConfiguration clientConfiguration) {
    this(new AmazonDynamoDBClient(credentials, clientConfiguration));
    setEndpoint();
  }

  public AbstractDynamoDbConfigurationSource(AWSCredentialsProvider credentialsProvider) {
    this(new AmazonDynamoDBClient(credentialsProvider));
    setEndpoint();
  }

  public AbstractDynamoDbConfigurationSource(
      AWSCredentialsProvider credentialsProvider, ClientConfiguration clientConfiguration) {
    this(new AmazonDynamoDBClient(credentialsProvider, clientConfiguration));
    setEndpoint();
  }

  public AbstractDynamoDbConfigurationSource(AmazonDynamoDB dbClient) {
    this.dbClient = dbClient;
  }

  protected ScanResult dbScanWithThroughputBackOff(ScanRequest scanRequest) {
    Long currentBackOffMs = minBackOffMs.get();
    Long retryCount = 0L;
    while (true) {
      try {
        return dbClient.scan(scanRequest);
      } catch (ProvisionedThroughputExceededException e) {
        currentBackOffMs = Math.min(currentBackOffMs * 2, maxBackOffMs.get());
        log.error(
            String.format(
                "Failed to poll Dynamo due to ProvisionedThroughputExceededException. Backing off for %d ms.",
                currentBackOffMs));
        if (retryCount > maxRetryCount.get()) {
          throw e;
        }
        retryCount++;

        try {
          Thread.sleep(currentBackOffMs);
        } catch (InterruptedException ex) {
        }
      }
    }
  }

  protected abstract Map<String, T> loadPropertiesFromTable(String table);

  // TODO Javadoc
  public void validateDb() {
    String table = tableName.get();

    loadPropertiesFromTable(table);
    log.info("Successfully polled Dynamo for a new configuration based on table:" + table);
  }

  private void setEndpoint() {
    String endpoint = endpointName.get();
    dbClient.setEndpoint(endpoint);
    log.info("Set Dynamo endpoint:" + endpoint);
  }
}
Example #10
0
public class Rewriter {
  private DatastoreObjectCache<RewriteRule> rules =
      new DatastoreObjectCache<>(
          RewriteRule.class,
          DynamicPropertyFactory.getInstance().getLongProperty("rewrite.cache", 60 * 1000));
}
 protected DynamicStringProperty getProperty(String key) {
   return DynamicPropertyFactory.getInstance().getStringProperty(key, VALUE_NOT_SET);
 }
@CommonsLog
public class SimpleHostRoutingFilter extends ZuulFilter {

  private static final DynamicIntProperty SOCKET_TIMEOUT =
      DynamicPropertyFactory.getInstance()
          .getIntProperty(ZuulConstants.ZUUL_HOST_SOCKET_TIMEOUT_MILLIS, 10000);

  private static final DynamicIntProperty CONNECTION_TIMEOUT =
      DynamicPropertyFactory.getInstance()
          .getIntProperty(ZuulConstants.ZUUL_HOST_CONNECT_TIMEOUT_MILLIS, 2000);

  private final Timer connectionManagerTimer =
      new Timer("SimpleHostRoutingFilter.connectionManagerTimer", true);

  private ProxyRequestHelper helper;
  private PoolingHttpClientConnectionManager connectionManager;
  private CloseableHttpClient httpClient;

  private final Runnable clientloader =
      new Runnable() {
        @Override
        public void run() {
          try {
            httpClient.close();
          } catch (IOException ex) {
            log.error("error closing client", ex);
          }
          httpClient = newClient();
        }
      };

  public SimpleHostRoutingFilter() {
    this(new ProxyRequestHelper());
  }

  public SimpleHostRoutingFilter(ProxyRequestHelper helper) {
    this.helper = helper;
  }

  @PostConstruct
  private void initialize() {
    this.httpClient = newClient();
    SOCKET_TIMEOUT.addCallback(clientloader);
    CONNECTION_TIMEOUT.addCallback(clientloader);
    connectionManagerTimer.schedule(
        new TimerTask() {
          @Override
          public void run() {
            if (connectionManager == null) {
              return;
            }
            connectionManager.closeExpiredConnections();
          }
        },
        30000,
        5000);
  }

  @PreDestroy
  public void stop() {
    connectionManagerTimer.cancel();
  }

  @Override
  public String filterType() {
    return "route";
  }

  @Override
  public int filterOrder() {
    return 100;
  }

  @Override
  public boolean shouldFilter() {
    return RequestContext.getCurrentContext().getRouteHost() != null
        && RequestContext.getCurrentContext().sendZuulResponse();
  }

  @Override
  public Object run() {
    RequestContext context = RequestContext.getCurrentContext();
    HttpServletRequest request = context.getRequest();
    MultiValueMap<String, String> headers = this.helper.buildZuulRequestHeaders(request);
    MultiValueMap<String, String> params = this.helper.buildZuulRequestQueryParams(request);
    String verb = getVerb(request);
    InputStream requestEntity = getRequestBody(request);

    String uri = this.helper.buildZuulRequestURI(request);

    try {
      HttpResponse response =
          forward(httpClient, verb, uri, request, headers, params, requestEntity);
      setResponse(response);
    } catch (Exception ex) {
      context.set("error.status_code", HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
      context.set("error.exception", ex);
    }
    return null;
  }

  protected PoolingHttpClientConnectionManager newConnectionManager() {
    try {
      final SSLContext sslContext = SSLContext.getInstance("SSL");
      sslContext.init(
          null,
          new TrustManager[] {
            new X509TrustManager() {
              @Override
              public void checkClientTrusted(X509Certificate[] x509Certificates, String s)
                  throws CertificateException {}

              @Override
              public void checkServerTrusted(X509Certificate[] x509Certificates, String s)
                  throws CertificateException {}

              @Override
              public X509Certificate[] getAcceptedIssuers() {
                return null;
              }
            }
          },
          new SecureRandom());

      final Registry<ConnectionSocketFactory> registry =
          RegistryBuilder.<ConnectionSocketFactory>create()
              .register("http", PlainConnectionSocketFactory.INSTANCE)
              .register("https", new SSLConnectionSocketFactory(sslContext))
              .build();

      connectionManager = new PoolingHttpClientConnectionManager(registry);
      connectionManager.setMaxTotal(
          Integer.parseInt(System.getProperty("zuul.max.host.connections", "200")));
      connectionManager.setDefaultMaxPerRoute(
          Integer.parseInt(System.getProperty("zuul.max.host.connections", "20")));
      return connectionManager;
    } catch (Exception ex) {
      throw new RuntimeException(ex);
    }
  }

  protected CloseableHttpClient newClient() {
    final RequestConfig requestConfig =
        RequestConfig.custom()
            .setSocketTimeout(SOCKET_TIMEOUT.get())
            .setConnectTimeout(CONNECTION_TIMEOUT.get())
            .setCookieSpec(CookieSpecs.IGNORE_COOKIES)
            .build();

    return HttpClients.custom()
        .setConnectionManager(newConnectionManager())
        .setDefaultRequestConfig(requestConfig)
        .setRetryHandler(new DefaultHttpRequestRetryHandler(0, false))
        .setRedirectStrategy(
            new RedirectStrategy() {
              @Override
              public boolean isRedirected(
                  HttpRequest request, HttpResponse response, HttpContext context)
                  throws ProtocolException {
                return false;
              }

              @Override
              public HttpUriRequest getRedirect(
                  HttpRequest request, HttpResponse response, HttpContext context)
                  throws ProtocolException {
                return null;
              }
            })
        .build();
  }

  private HttpResponse forward(
      HttpClient httpclient,
      String verb,
      String uri,
      HttpServletRequest request,
      MultiValueMap<String, String> headers,
      MultiValueMap<String, String> params,
      InputStream requestEntity)
      throws Exception {
    Map<String, Object> info = this.helper.debug(verb, uri, headers, params, requestEntity);
    URL host = RequestContext.getCurrentContext().getRouteHost();
    HttpHost httpHost = getHttpHost(host);
    uri = StringUtils.cleanPath(host.getPath() + uri);
    HttpRequest httpRequest;
    switch (verb.toUpperCase()) {
      case "POST":
        HttpPost httpPost = new HttpPost(uri + getQueryString());
        httpRequest = httpPost;
        httpPost.setEntity(new InputStreamEntity(requestEntity, request.getContentLength()));
        break;
      case "PUT":
        HttpPut httpPut = new HttpPut(uri + getQueryString());
        httpRequest = httpPut;
        httpPut.setEntity(new InputStreamEntity(requestEntity, request.getContentLength()));
        break;
      case "PATCH":
        HttpPatch httpPatch = new HttpPatch(uri + getQueryString());
        httpRequest = httpPatch;
        httpPatch.setEntity(new InputStreamEntity(requestEntity, request.getContentLength()));
        break;
      default:
        httpRequest = new BasicHttpRequest(verb, uri + getQueryString());
        log.debug(uri + getQueryString());
    }
    try {
      httpRequest.setHeaders(convertHeaders(headers));
      log.debug(httpHost.getHostName() + " " + httpHost.getPort() + " " + httpHost.getSchemeName());
      HttpResponse zuulResponse = forwardRequest(httpclient, httpHost, httpRequest);
      this.helper.appendDebug(
          info,
          zuulResponse.getStatusLine().getStatusCode(),
          revertHeaders(zuulResponse.getAllHeaders()));
      return zuulResponse;
    } finally {
      // When HttpClient instance is no longer needed,
      // shut down the connection manager to ensure
      // immediate deallocation of all system resources
      // httpclient.getConnectionManager().shutdown();
    }
  }

  private MultiValueMap<String, String> revertHeaders(Header[] headers) {
    MultiValueMap<String, String> map = new LinkedMultiValueMap<String, String>();
    for (Header header : headers) {
      String name = header.getName();
      if (!map.containsKey(name)) {
        map.put(name, new ArrayList<String>());
      }
      map.get(name).add(header.getValue());
    }
    return map;
  }

  private Header[] convertHeaders(MultiValueMap<String, String> headers) {
    List<Header> list = new ArrayList<>();
    for (String name : headers.keySet()) {
      for (String value : headers.get(name)) {
        list.add(new BasicHeader(name, value));
      }
    }
    return list.toArray(new BasicHeader[0]);
  }

  private HttpResponse forwardRequest(
      HttpClient httpclient, HttpHost httpHost, HttpRequest httpRequest) throws IOException {
    return httpclient.execute(httpHost, httpRequest);
  }

  private String getQueryString() throws UnsupportedEncodingException {
    HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
    MultiValueMap<String, String> params = helper.buildZuulRequestQueryParams(request);
    StringBuilder query = new StringBuilder();
    for (Map.Entry<String, List<String>> entry : params.entrySet()) {
      String key = URLEncoder.encode(entry.getKey(), "UTF-8");
      for (String value : entry.getValue()) {
        query.append("&");
        query.append(key);
        query.append("=");
        query.append(URLEncoder.encode(value, "UTF-8"));
      }
    }
    return (query.length() > 0) ? "?" + query.substring(1) : "";
  }

  private HttpHost getHttpHost(URL host) {
    HttpHost httpHost = new HttpHost(host.getHost(), host.getPort(), host.getProtocol());
    return httpHost;
  }

  private InputStream getRequestBody(HttpServletRequest request) {
    InputStream requestEntity = null;
    try {
      requestEntity = request.getInputStream();
    } catch (IOException ex) {
      // no requestBody is ok.
    }
    return requestEntity;
  }

  private String getVerb(HttpServletRequest request) {
    String sMethod = request.getMethod();
    return sMethod.toUpperCase();
  }

  private void setResponse(HttpResponse response) throws IOException {
    this.helper.setResponse(
        response.getStatusLine().getStatusCode(),
        response.getEntity() == null ? null : response.getEntity().getContent(),
        revertHeaders(response.getAllHeaders()));
  }

  /**
   * Add header names to exclude from proxied response in the current request.
   *
   * @param names
   */
  protected void addIgnoredHeaders(String... names) {
    helper.addIgnoredHeaders(names);
  }
}
/**
 * A default implementation of eureka server configuration as required by {@link
 * EurekaServerConfig}.
 *
 * <p>The information required for configuring eureka server is provided in a configuration file.The
 * configuration file is searched for in the classpath with the name specified by the property
 * <em>eureka.server.props</em> and with the suffix <em>.properties</em>. If the property is not
 * specified, <em>eureka-server.properties</em> is assumed as the default.The properties that are
 * looked up uses the <em>namespace</em> passed on to this class.
 *
 * <p>If the <em>eureka.environment</em> property is specified, additionally
 * <em>eureka-server-<eureka.environment>.properties</em> is loaded in addition to
 * <em>eureka-server.properties</em>.
 *
 * @author Karthik Ranganathan
 */
public class DefaultEurekaServerConfig implements EurekaServerConfig {
  private static final String ARCHAIUS_DEPLOYMENT_ENVIRONMENT = "archaius.deployment.environment";
  private static final String TEST = "test";
  private static final String EUREKA_ENVIRONMENT = "eureka.environment";
  private static final Logger logger = LoggerFactory.getLogger(DefaultEurekaServerConfig.class);
  private static final DynamicPropertyFactory configInstance =
      com.netflix.config.DynamicPropertyFactory.getInstance();
  private static final DynamicStringProperty EUREKA_PROPS_FILE =
      DynamicPropertyFactory.getInstance()
          .getStringProperty("eureka.server.props", "eureka-server");
  private String namespace = "eureka.";

  public DefaultEurekaServerConfig() {
    init();
  }

  public DefaultEurekaServerConfig(String namespace) {
    this.namespace = namespace;
    init();
  }

  private void init() {
    String env = ConfigurationManager.getConfigInstance().getString(EUREKA_ENVIRONMENT, TEST);
    ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_ENVIRONMENT, env);

    String eurekaPropsFile = EUREKA_PROPS_FILE.get();
    try {
      // ConfigurationManager
      // .loadPropertiesFromResources(eurekaPropsFile);
      ConfigurationManager.loadCascadedPropertiesFromResources(eurekaPropsFile);
    } catch (IOException e) {
      logger.warn(
          "Cannot find the properties specified : {}. This may be okay if there are other environment specific properties or the configuration is installed with a different mechanism.",
          eurekaPropsFile);
    }
  }

  /*
   * (non-Javadoc)
   * @see com.netflix.eureka.EurekaServerConfig#getAWSAccessId()
   */
  @Override
  public String getAWSAccessId() {
    return configInstance.getStringProperty(namespace + "awsAccessId", null).get().trim();
  }

  /*
   * (non-Javadoc)
   * @see com.netflix.eureka.EurekaServerConfig#getAWSSecretKey()
   */
  @Override
  public String getAWSSecretKey() {
    return configInstance.getStringProperty(namespace + "awsSecretKey", null).get().trim();
  }

  /*
   * (non-Javadoc)
   * @see com.netflix.eureka.EurekaServerConfig#getEIPBindRebindRetries()
   */
  @Override
  public int getEIPBindRebindRetries() {
    return configInstance.getIntProperty(namespace + "eipBindRebindRetries", 3).get();
  }

  /*
   * (non-Javadoc)
   * @see com.netflix.eureka.EurekaServerConfig#getEIPBindingRetryInterval()
   */
  @Override
  public int getEIPBindingRetryIntervalMs() {
    return configInstance
        .getIntProperty(namespace + "eipBindRebindRetryIntervalMs", (5 * 60 * 1000))
        .get();
  }

  /*
   * (non-Javadoc)
   * @see com.netflix.eureka.EurekaServerConfig#shouldEnableSelfPreservation()
   */
  @Override
  public boolean shouldEnableSelfPreservation() {
    return configInstance.getBooleanProperty(namespace + "enableSelfPreservation", true).get();
  }

  /*
   * (non-Javadoc)
   * @see com.netflix.eureka.EurekaServerConfig#getPeerEurekaNodesUpdateInterval()
   */
  @Override
  public int getPeerEurekaNodesUpdateIntervalMs() {
    return configInstance
        .getIntProperty(namespace + "peerEurekaNodesUpdateIntervalMs", (10 * 60 * 1000))
        .get();
  }

  @Override
  public int getRenewalThresholdUpdateIntervalMs() {
    return configInstance
        .getIntProperty(namespace + "renewalThresholdUpdateIntervalMs", (15 * 60 * 1000))
        .get();
  }

  @Override
  public double getRenewalPercentThreshold() {
    return configInstance.getDoubleProperty(namespace + "renewalPercentThreshold", 0.85).get();
  }

  @Override
  public int getNumberOfReplicationRetries() {
    return configInstance.getIntProperty(namespace + "numberOfReplicationRetries", 5).get();
  }

  @Override
  public boolean shouldReplicateOnlyIfUP() {
    return configInstance.getBooleanProperty(namespace + "replicateOnlyIfUP", true).get();
  }

  @Override
  public int getPeerEurekaStatusRefreshTimeIntervalMs() {
    return configInstance
        .getIntProperty(namespace + "peerEurekaStatusRefreshTimeIntervalMs", (30 * 1000))
        .get();
  }

  @Override
  public int getWaitTimeInMsWhenSyncEmpty() {
    return configInstance
        .getIntProperty(namespace + "waitTimeInMsWhenSyncEmpty", (1000 * 60 * 5))
        .get();
  }

  @Override
  public int getPeerNodeConnectTimeoutMs() {
    return configInstance.getIntProperty(namespace + "peerNodeConnectTimeoutMs", 200).get();
  }

  @Override
  public int getPeerNodeReadTimeoutMs() {
    return configInstance.getIntProperty(namespace + "peerNodeReadTimeoutMs", 200).get();
  }

  @Override
  public int getPeerNodeTotalConnections() {
    return configInstance.getIntProperty(namespace + "peerNodeTotalConnections", 1000).get();
  }

  @Override
  public int getPeerNodeTotalConnectionsPerHost() {
    return configInstance.getIntProperty(namespace + "peerNodeTotalConnections", 500).get();
  }

  @Override
  public int getPeerNodeConnectionIdleTimeoutSeconds() {
    return configInstance
        .getIntProperty(namespace + "peerNodeConnectionIdleTimeoutSeconds", 30)
        .get();
  }

  @Override
  public boolean shouldRetryIndefinitelyToReplicateStatus() {
    return configInstance
        .getBooleanProperty(namespace + "retryIndefinitelyToReplicateStatus", true)
        .get();
  }

  @Override
  public long getRetentionTimeInMSInDeltaQueue() {
    return configInstance
        .getLongProperty(namespace + "retentionTimeInMSInDeltaQueue", (3 * 60 * 1000))
        .get();
  }

  @Override
  public long getDeltaRetentionTimerIntervalInMs() {
    return configInstance
        .getLongProperty(namespace + "deltaRetentionTimerIntervalInMs", (30 * 1000))
        .get();
  }

  @Override
  public long getEvictionIntervalTimerInMs() {
    return configInstance
        .getLongProperty(namespace + "evictionIntervalTimerInMs", (60 * 1000))
        .get();
  }

  @Override
  public int getASGQueryTimeoutMs() {
    return configInstance.getIntProperty(namespace + "asgQueryTimeoutMs", 1000).get();
  }

  @Override
  public long getASGUpdateIntervalMs() {
    return configInstance.getIntProperty(namespace + "asgUpdateIntervalMs", (60 * 1000)).get();
  }

  @Override
  public long getResponseCacheAutoExpirationInSeconds() {
    return configInstance
        .getIntProperty(namespace + "responseCacheAutoExpirationInSeconds", 180)
        .get();
  }

  @Override
  public long getResponseCacheUpdateIntervalMs() {
    return configInstance
        .getIntProperty(namespace + "responseCacheUpdateIntervalMs", (30 * 1000))
        .get();
  }

  @Override
  public boolean shouldDisableDelta() {
    return configInstance.getBooleanProperty(namespace + "disableDelta", false).get();
  }

  @Override
  public long getMaxIdleThreadInMinutesAgeForStatusReplication() {
    return configInstance
        .getLongProperty(namespace + "maxIdleThreadAgeInMinutesForStatusReplication", 10)
        .get();
  }

  @Override
  public int getMinThreadsForStatusReplication() {
    return configInstance.getIntProperty(namespace + "minThreadsForStatusReplication", 1).get();
  }

  @Override
  public int getMaxThreadsForStatusReplication() {
    return configInstance.getIntProperty(namespace + "maxThreadsForStatusReplication", 1).get();
  }

  @Override
  public int getMaxElementsInStatusReplicationPool() {
    return configInstance
        .getIntProperty(namespace + "maxElementsInStatusReplicationPool", 10000)
        .get();
  }

  @Override
  public int getMaxElementsInReplicationPool() {
    return configInstance.getIntProperty(namespace + "maxElementsInReplicationPool", 120).get();
  }

  @Override
  public long getMaxIdleThreadAgeInMinutesForReplication() {
    return configInstance
        .getIntProperty(namespace + "maxIdleThreadAgeInMinutesForReplication", 15)
        .get();
  }

  @Override
  public int getMinThreadsForReplication() {
    return configInstance.getIntProperty(namespace + "minThreadsForReplication", 20).get();
  }

  @Override
  public int getMaxThreadsForReplication() {
    return configInstance.getIntProperty(namespace + "maxThreadsForReplication", 60).get();
  }

  @Override
  public boolean shouldSyncWhenTimestampDiffers() {
    return configInstance.getBooleanProperty(namespace + "syncWhenTimestampDiffers", true).get();
  }
}
Example #14
0
/**
 * An {@link InstanceInfo} configuration for AWS cloud deployments.
 *
 * <p>The information required for registration with eureka by a combination of user-supplied values
 * as well as querying AWS instance metadata.An utility class {@link AmazonInfo} helps in retrieving
 * AWS specific values. Some of that information including <em>availability zone</em> is used for
 * determining which eureka server to communicate to.
 *
 * @author Karthik Ranganathan
 */
@Singleton
@ProvidedBy(CloudInstanceConfigProvider.class)
public class CloudInstanceConfig extends PropertiesInstanceConfig {
  private static final Logger logger = LoggerFactory.getLogger(CloudInstanceConfig.class);
  private static final DynamicPropertyFactory INSTANCE = DynamicPropertyFactory.getInstance();

  private static final String[] DEFAULT_AWS_ADDRESS_RESOLUTION_ORDER =
      new String[] {MetaDataKey.publicHostname.name(), MetaDataKey.localIpv4.name()};

  private DynamicBooleanProperty propValidateInstanceId;
  /* Visible for testing */ volatile AmazonInfo info;

  /* For testing */ CloudInstanceConfig(AmazonInfo info) {
    this.info = info;
  }

  public CloudInstanceConfig() {
    initCloudInstanceConfig(namespace);
  }

  public CloudInstanceConfig(String namespace) {
    super(namespace);
    initCloudInstanceConfig(namespace);
  }

  private void initCloudInstanceConfig(String namespace) {
    propValidateInstanceId = INSTANCE.getBooleanProperty(namespace + "validateInstanceId", true);
    info = initDataCenterInfo();
  }

  private AmazonInfo initDataCenterInfo() {
    AmazonInfo info;
    try {
      info = AmazonInfo.Builder.newBuilder().autoBuild(namespace);
      logger.info("Datacenter is: " + Name.Amazon);
    } catch (Throwable e) {
      logger.error("Cannot initialize amazon info :", e);
      throw new RuntimeException(e);
    }
    // Instance id being null means we could not get the amazon metadata
    if (info.get(MetaDataKey.instanceId) == null) {
      if (propValidateInstanceId.get()) {
        throw new RuntimeException(
            "Your datacenter is defined as cloud but we are not able to get the amazon metadata to "
                + "register. \nSet the property "
                + namespace
                + "validateInstanceId to false to "
                + "ignore the metadata call");
      } else {
        // The property to not validate instance ids may be set for
        // development and in that scenario, populate instance id
        // and public hostname with the hostname of the machine
        Map<String, String> metadataMap = new HashMap<String, String>();
        metadataMap.put(MetaDataKey.instanceId.getName(), super.getIpAddress());
        metadataMap.put(MetaDataKey.publicHostname.getName(), super.getHostName(false));
        info.setMetadata(metadataMap);
      }
    } else if ((info.get(MetaDataKey.publicHostname) == null)
        && (info.get(MetaDataKey.localIpv4) != null)) {
      // :( legacy code and logic
      // This might be a case of VPC where the instance id is not null, but
      // public hostname might be null
      info.getMetadata()
          .put(MetaDataKey.publicHostname.getName(), (info.get(MetaDataKey.localIpv4)));
    }
    return info;
  }

  public String resolveDefaultAddress() {
    // In this method invocation data center info will be refreshed.
    String result = getHostName(true);

    for (String name : getDefaultAddressResolutionOrder()) {
      try {
        AmazonInfo.MetaDataKey key = AmazonInfo.MetaDataKey.valueOf(name);
        String address = info.get(key);
        if (address != null && !address.isEmpty()) {
          result = address;
          break;
        }
      } catch (Exception e) {
        logger.error("failed to resolve default address for key {}, skipping", name, e);
      }
    }

    return result;
  }

  @Override
  public String getHostName(boolean refresh) {
    if (refresh) {
      refreshAmazonInfo();
    }
    return info.get(MetaDataKey.publicHostname);
  }

  @Override
  public String getIpAddress() {
    String ipAddr = info.get(MetaDataKey.localIpv4);
    return ipAddr == null ? super.getIpAddress() : ipAddr;
  }

  @Override
  public DataCenterInfo getDataCenterInfo() {
    return info;
  }

  @Override
  public String[] getDefaultAddressResolutionOrder() {
    String[] order = super.getDefaultAddressResolutionOrder();
    return (order.length == 0) ? DEFAULT_AWS_ADDRESS_RESOLUTION_ORDER : order;
  }

  /**
   * Refresh instance info - currently only used when in AWS cloud as a public ip can change
   * whenever an EIP is associated or dissociated.
   */
  public synchronized void refreshAmazonInfo() {
    try {
      AmazonInfo newInfo = AmazonInfo.Builder.newBuilder().autoBuild(namespace);
      if (shouldUpdate(newInfo, info)) {
        // the datacenter info has changed, re-sync it
        logger.info("The AmazonInfo changed from : {} => {}", info, newInfo);
        this.info = newInfo;
      }
    } catch (Throwable t) {
      logger.error("Cannot refresh the Amazon Info ", t);
    }
  }

  /**
   * Rules of updating AmazonInfo: - instanceId must exist - localIp/privateIp most exist -
   * publicHostname does not necessarily need to exist (e.g. in vpc)
   */
  /* visible for testing */ static boolean shouldUpdate(AmazonInfo newInfo, AmazonInfo oldInfo) {
    if (newInfo.getMetadata().isEmpty()) {
      logger.warn("Newly resolved AmazonInfo is empty, skipping an update cycle");
    } else if (!newInfo.equals(oldInfo)) {
      if (isBlank(newInfo.get(MetaDataKey.instanceId))) {
        logger.warn("instanceId is blank, skipping an update cycle");
        return false;
      } else if (isBlank(newInfo.get(MetaDataKey.localIpv4))) {
        logger.warn("localIpv4 is blank, skipping an update cycle");
        return false;
      } else {
        Set<String> newKeys = new HashSet<>(newInfo.getMetadata().keySet());
        Set<String> oldKeys = new HashSet<>(oldInfo.getMetadata().keySet());

        Set<String> union = new HashSet<>(newKeys);
        union.retainAll(oldKeys);
        newKeys.removeAll(union);
        oldKeys.removeAll(union);

        for (String key : newKeys) {
          logger.info("Adding new metadata {}={}", key, newInfo.getMetadata().get(key));
        }

        for (String key : oldKeys) {
          logger.info("Removing old metadata {}={}", key, oldInfo.getMetadata().get(key));
        }
      }

      return true;
    }
    return false;
  }

  private static boolean isBlank(String str) {
    return str == null || str.isEmpty();
  }
}
/**
 * Load balancer that can avoid a zone as a whole when choosing server.
 *
 * <p>The key metric used to measure the zone condition is Average Active Requests, which is
 * aggregated per rest client per zone. It is the total outstanding requests in a zone divided by
 * number of available targeted instances (excluding circuit breaker tripped instances). This metric
 * is very effective when timeout occurs slowly on a bad zone.
 *
 * <p>The LoadBalancer will calculate and examine zone stats of all available zones. If the Average
 * Active Requests for any zone has reached a configured threshold, this zone will be dropped from
 * the active server list. In case more than one zone has reached the threshold, the zone with the
 * most active requests per server will be dropped. Once the the worst zone is dropped, a zone will
 * be chosen among the rest with the probability proportional to its number of instances. A server
 * will be returned from the chosen zone with a given Rule (A Rule is a load balancing strategy, for
 * example {@link AvailabilityFilteringRule}) For each request, the steps above will be repeated.
 * That is to say, each zone related load balancing decisions are made at real time with the
 * up-to-date statistics aiding the choice.
 *
 * @author awang
 * @param <T>
 */
public class ZoneAwareLoadBalancer<T extends Server> extends DynamicServerListLoadBalancer<T> {

  private ConcurrentHashMap<String, BaseLoadBalancer> balancers =
      new ConcurrentHashMap<String, BaseLoadBalancer>();

  private static final Logger logger = LoggerFactory.getLogger(ZoneAwareLoadBalancer.class);

  private volatile DynamicDoubleProperty triggeringLoad;

  private volatile DynamicDoubleProperty triggeringBlackoutPercentage;

  private static final DynamicBooleanProperty ENABLED =
      DynamicPropertyFactory.getInstance()
          .getBooleanProperty("ZoneAwareNIWSDiscoveryLoadBalancer.enabled", true);

  void setUpServerList(List<Server> upServerList) {
    this.upServerList = upServerList;
  }

  public ZoneAwareLoadBalancer() {
    super();
  }

  public ZoneAwareLoadBalancer(
      IClientConfig clientConfig,
      IRule rule,
      IPing ping,
      ServerList<T> serverList,
      ServerListFilter<T> filter) {
    super(clientConfig, rule, ping, serverList, filter);
  }

  public ZoneAwareLoadBalancer(IClientConfig niwsClientConfig) {
    super(niwsClientConfig);
  }

  @Override
  protected void setServerListForZones(Map<String, List<Server>> zoneServersMap) {
    super.setServerListForZones(zoneServersMap);
    if (balancers == null) {
      balancers = new ConcurrentHashMap<String, BaseLoadBalancer>();
    }
    for (Map.Entry<String, List<Server>> entry : zoneServersMap.entrySet()) {
      String zone = entry.getKey().toLowerCase();
      getLoadBalancer(zone).setServersList(entry.getValue());
    }
    // check if there is any zone that no longer has a server
    // and set the list to empty so that the zone related metrics does not
    // contain stale data
    for (Map.Entry<String, BaseLoadBalancer> existingLBEntry : balancers.entrySet()) {
      if (!zoneServersMap.keySet().contains(existingLBEntry.getKey())) {
        existingLBEntry.getValue().setServersList(Collections.emptyList());
      }
    }
  }

  @Override
  public Server chooseServer(Object key) {
    if (!ENABLED.get() || getLoadBalancerStats().getAvailableZones().size() <= 1) {
      logger.debug("Zone aware logic disabled or there is only one zone");
      return super.chooseServer(key);
    }
    Server server = null;
    try {
      LoadBalancerStats lbStats = getLoadBalancerStats();
      Map<String, ZoneSnapshot> zoneSnapshot = ZoneAvoidanceRule.createSnapshot(lbStats);
      logger.debug("Zone snapshots: {}", zoneSnapshot);
      if (triggeringLoad == null) {
        triggeringLoad =
            DynamicPropertyFactory.getInstance()
                .getDoubleProperty(
                    "ZoneAwareNIWSDiscoveryLoadBalancer."
                        + this.getName()
                        + ".triggeringLoadPerServerThreshold",
                    0.2d);
      }

      if (triggeringBlackoutPercentage == null) {
        triggeringBlackoutPercentage =
            DynamicPropertyFactory.getInstance()
                .getDoubleProperty(
                    "ZoneAwareNIWSDiscoveryLoadBalancer."
                        + this.getName()
                        + ".avoidZoneWithBlackoutPercetage",
                    0.99999d);
      }
      Set<String> availableZones =
          ZoneAvoidanceRule.getAvailableZones(
              zoneSnapshot, triggeringLoad.get(), triggeringBlackoutPercentage.get());
      logger.debug("Available zones: {}", availableZones);
      if (availableZones != null && availableZones.size() < zoneSnapshot.keySet().size()) {
        String zone = ZoneAvoidanceRule.randomChooseZone(zoneSnapshot, availableZones);
        logger.debug("Zone chosen: {}", zone);
        if (zone != null) {
          BaseLoadBalancer zoneLoadBalancer = getLoadBalancer(zone);
          server = zoneLoadBalancer.chooseServer(key);
        }
      }
    } catch (Throwable e) {
      logger.error("Unexpected exception when choosing server using zone aware logic", e);
    }
    if (server != null) {
      return server;
    } else {
      logger.debug("Zone avoidance logic is not invoked.");
      return super.chooseServer(key);
    }
  }

  @VisibleForTesting
  BaseLoadBalancer getLoadBalancer(String zone) {
    zone = zone.toLowerCase();
    BaseLoadBalancer loadBalancer = balancers.get(zone);
    if (loadBalancer == null) {
      // We need to create rule object for load balancer for each zone
      IRule rule = cloneRule(this.getRule());
      loadBalancer =
          new BaseLoadBalancer(this.getName() + "_" + zone, rule, this.getLoadBalancerStats());
      BaseLoadBalancer prev = balancers.putIfAbsent(zone, loadBalancer);
      if (prev != null) {
        loadBalancer = prev;
      }
    }
    return loadBalancer;
  }

  private IRule cloneRule(IRule toClone) {
    IRule rule;
    if (toClone == null) {
      rule = new AvailabilityFilteringRule();
    } else {
      String ruleClass = toClone.getClass().getName();
      try {
        rule =
            (IRule)
                ClientFactory.instantiateInstanceWithClientConfig(
                    ruleClass, this.getClientConfig());
      } catch (Exception e) {
        throw new RuntimeException(
            "Unexpected exception creating rule for ZoneAwareLoadBalancer", e);
      }
    }
    return rule;
  }

  @Override
  public void setRule(IRule rule) {
    super.setRule(rule);
    if (balancers != null) {
      for (String zone : balancers.keySet()) {
        balancers.get(zone).setRule(cloneRule(rule));
      }
    }
  }
}
/**
 * A copy of the InstrumentedHandler from Codahale Metric's library. The current version of the
 * library does not support the most current Jetty version, in which HttpChannelState.isDispatched()
 * has been removed. This class will be used until a Metric's library version is released which
 * supports the Jetty change.
 */
public class InstrumentedHandler extends HandlerWrapper {
  private final MetricRegistry metricRegistry;

  private String name;

  // the requests handled by this handler, excluding active
  private Timer requests;

  // the number of dispatches seen by this handler, excluding active
  private Timer dispatches;

  // the number of active requests
  private Counter activeRequests;

  // the number of active dispatches
  private Counter activeDispatches;

  // the number of requests currently suspended.
  private Counter activeSuspended;

  // the number of requests that have been asynchronously dispatched
  private Meter asyncDispatches;

  // the number of requests that expired while suspended
  private Meter asyncTimeouts;

  private Meter[] responses;

  private Timer getRequests;
  private Timer postRequests;
  private Timer headRequests;
  private Timer putRequests;
  private Timer deleteRequests;
  private Timer optionsRequests;
  private Timer traceRequests;
  private Timer connectRequests;
  private Timer moveRequests;
  private Timer otherRequests;

  private AsyncListener listener;

  private DynamicLongProperty timerReservoirSeconds =
      DynamicPropertyFactory.getInstance()
          .getLongProperty("http.metrics.handler.timerReservoirSeconds", 60);

  /**
   * Create a new instrumented handler using a given metrics registry.
   *
   * @param registry the registry for the metrics
   */
  public InstrumentedHandler(MetricRegistry registry) {
    this.metricRegistry = registry;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  @Override
  protected void doStart() throws Exception {
    super.doStart();

    final String prefix = name(getHandler().getClass(), name);

    this.requests = timer(name(prefix, "requests"));
    this.dispatches = timer(name(prefix, "dispatches"));

    this.activeRequests = metricRegistry.counter(name(prefix, "active-requests"));
    this.activeDispatches = metricRegistry.counter(name(prefix, "active-dispatches"));
    this.activeSuspended = metricRegistry.counter(name(prefix, "active-suspended"));

    this.asyncDispatches = metricRegistry.meter(name(prefix, "async-dispatches"));
    this.asyncTimeouts = metricRegistry.meter(name(prefix, "async-timeouts"));

    this.responses =
        new Meter[] {
          metricRegistry.meter(name(prefix, "1xx-responses")), // 1xx
          metricRegistry.meter(name(prefix, "2xx-responses")), // 2xx
          metricRegistry.meter(name(prefix, "3xx-responses")), // 3xx
          metricRegistry.meter(name(prefix, "4xx-responses")), // 4xx
          metricRegistry.meter(name(prefix, "5xx-responses")) // 5xx
        };

    this.getRequests = timer(name(prefix, "get-requests"));
    this.postRequests = timer(name(prefix, "post-requests"));
    this.headRequests = timer(name(prefix, "head-requests"));
    this.putRequests = timer(name(prefix, "put-requests"));
    this.deleteRequests = timer(name(prefix, "delete-requests"));
    this.optionsRequests = timer(name(prefix, "options-requests"));
    this.traceRequests = timer(name(prefix, "trace-requests"));
    this.connectRequests = timer(name(prefix, "connect-requests"));
    this.moveRequests = timer(name(prefix, "move-requests"));
    this.otherRequests = timer(name(prefix, "other-requests"));

    this.listener =
        new AsyncListener() {
          @Override
          public void onTimeout(AsyncEvent event) throws IOException {
            asyncTimeouts.mark();
          }

          @Override
          public void onStartAsync(AsyncEvent event) throws IOException {
            event.getAsyncContext().addListener(this);
          }

          @Override
          public void onError(AsyncEvent event) throws IOException {}

          @Override
          public void onComplete(AsyncEvent event) throws IOException {
            final AsyncContextState state = (AsyncContextState) event.getAsyncContext();
            final Request request = (Request) state.getRequest();
            updateResponses(request);
            if (!(state.getHttpChannelState().getState() == State.DISPATCHED)) {
              activeSuspended.dec();
            }
          }
        };
  }

  private Timer timer(String name) {
    Timer timer = metricRegistry.getTimers().get(name);
    if (timer != null) {
      return timer;
    }
    try {
      return metricRegistry.register(
          name,
          new Timer(new SlidingTimeWindowReservoir(timerReservoirSeconds.get(), TimeUnit.SECONDS)));
    } catch (IllegalArgumentException e) {
      // timer already exists. this happens due to race condition. its fine.
      return metricRegistry.getTimers().get(name);
    }
  }

  @Override
  public void handle(
      String path,
      Request request,
      HttpServletRequest httpRequest,
      HttpServletResponse httpResponse)
      throws IOException, ServletException {

    activeDispatches.inc();

    final long start;
    final HttpChannelState state = request.getHttpChannelState();
    if (state.isInitial()) {
      // new request
      activeRequests.inc();
      start = request.getTimeStamp();
    } else {
      // resumed request
      start = System.currentTimeMillis();
      activeSuspended.dec();
      if (state.getState() == State.DISPATCHED) {
        asyncDispatches.mark();
      }
    }

    try {
      super.handle(path, request, httpRequest, httpResponse);
    } finally {
      final long now = System.currentTimeMillis();
      final long dispatched = now - start;

      activeDispatches.dec();
      dispatches.update(dispatched, TimeUnit.MILLISECONDS);

      if (state.isSuspended()) {
        if (state.isInitial()) {
          state.addListener(listener);
        }
        activeSuspended.inc();
      } else if (state.isInitial()) {
        requests.update(dispatched, TimeUnit.MILLISECONDS);
        updateResponses(request);
      }
      // else onCompletion will handle it.
    }
  }

  private Timer requestTimer(String method) {
    final HttpMethod m = HttpMethod.fromString(method);
    if (m == null) {
      return otherRequests;
    } else {
      switch (m) {
        case GET:
          return getRequests;
        case POST:
          return postRequests;
        case PUT:
          return putRequests;
        case HEAD:
          return headRequests;
        case DELETE:
          return deleteRequests;
        case OPTIONS:
          return optionsRequests;
        case TRACE:
          return traceRequests;
        case CONNECT:
          return connectRequests;
        case MOVE:
          return moveRequests;
        default:
          return otherRequests;
      }
    }
  }

  private void updateResponses(Request request) {
    final int response = request.getResponse().getStatus() / 100;
    if (response >= 1 && response <= 5) {
      responses[response - 1].mark();
    }
    activeRequests.dec();
    final long elapsedTime = System.currentTimeMillis() - request.getTimeStamp();
    requests.update(elapsedTime, TimeUnit.MILLISECONDS);
    requestTimer(request.getMethod()).update(elapsedTime, TimeUnit.MILLISECONDS);
  }
}
/** @author Spencer Gibb */
@CommonsLog
public class SpringAggregatorFactory implements ClusterMonitorFactory<AggDataFromCluster> {

  private static final DynamicStringProperty aggClusters =
      DynamicPropertyFactory.getInstance()
          .getStringProperty("turbine.aggregator.clusterConfig", null);

  /**
   * @return {@link com.netflix.turbine.monitor.cluster.ClusterMonitor}< {@link
   *     com.netflix.turbine.data.AggDataFromCluster}>
   */
  @Override
  public ClusterMonitor<AggDataFromCluster> getClusterMonitor(String name) {
    TurbineDataMonitor<AggDataFromCluster> clusterMonitor =
        AggregateClusterMonitor.AggregatorClusterMonitorConsole.findMonitor(name + "_agg");
    return (ClusterMonitor<AggDataFromCluster>) clusterMonitor;
  }

  public static TurbineDataMonitor<AggDataFromCluster> findOrRegisterAggregateMonitor(
      String clusterName) {
    TurbineDataMonitor<AggDataFromCluster> clusterMonitor =
        AggregatorClusterMonitorConsole.findMonitor(clusterName + "_agg");
    if (clusterMonitor == null) {
      log.info("Could not find monitors: " + AggregatorClusterMonitorConsole.toString());
      clusterMonitor = new SpringClusterMonitor(clusterName + "_agg", clusterName);
      clusterMonitor = AggregatorClusterMonitorConsole.findOrRegisterMonitor(clusterMonitor);
    }
    return clusterMonitor;
  }

  @Override
  public void initClusterMonitors() {
    for (String clusterName : getClusterNames()) {
      ClusterMonitor<AggDataFromCluster> clusterMonitor =
          (ClusterMonitor<AggDataFromCluster>) findOrRegisterAggregateMonitor(clusterName);
      clusterMonitor.registerListenertoClusterMonitor(this.StaticListener);
      try {
        clusterMonitor.startMonitor();
      } catch (Exception ex) {
        log.warn("Could not init cluster monitor for: " + clusterName);
        clusterMonitor.stopMonitor();
        clusterMonitor.getDispatcher().stopDispatcher();
      }
    }
  }

  private List<String> getClusterNames() {
    List<String> clusters = new ArrayList<String>();
    String clusterNames = aggClusters.get();
    if (clusterNames == null || clusterNames.trim().length() == 0) {
      clusters.add("default");
    } else {
      String[] parts = aggClusters.get().split(",");
      for (String s : parts) {
        clusters.add(s);
      }
    }
    return clusters;
  }

  /** shutdown all configured cluster monitors */
  @Override
  public void shutdownClusterMonitors() {
    for (String clusterName : getClusterNames()) {
      ClusterMonitor<AggDataFromCluster> clusterMonitor =
          (ClusterMonitor<AggDataFromCluster>)
              AggregateClusterMonitor.findOrRegisterAggregateMonitor(clusterName);
      clusterMonitor.stopMonitor();
      clusterMonitor.getDispatcher().stopDispatcher();
    }
  }

  private TurbineDataHandler<AggDataFromCluster> StaticListener =
      new TurbineDataHandler<AggDataFromCluster>() {

        @Override
        public String getName() {
          return "StaticListener_For_Aggregator";
        }

        @Override
        public void handleData(Collection<AggDataFromCluster> stats) {}

        @Override
        public void handleHostLost(Instance host) {}

        @Override
        public PerformanceCriteria getCriteria() {
          return SpringAggregatorFactory.this.NonCriticalCriteria;
        }
      };

  private PerformanceCriteria NonCriticalCriteria =
      new PerformanceCriteria() {

        @Override
        public boolean isCritical() {
          return false;
        }

        @Override
        public int getMaxQueueSize() {
          return 0;
        }

        @Override
        public int numThreads() {
          return 0;
        }
      };
}