Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -33,11 +33,14 @@ import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycle;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycleValidator;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequest;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerRequestAdapter;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerUriTools;
|
||||
import org.springframework.cloud.client.loadbalancer.Request;
|
||||
import org.springframework.cloud.client.loadbalancer.Response;
|
||||
import org.springframework.cloud.client.loadbalancer.ResponseData;
|
||||
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer.REQUEST;
|
||||
@@ -65,7 +68,7 @@ public class BlockingLoadBalancerClient implements LoadBalancerClient {
|
||||
@Override
|
||||
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
|
||||
String hint = getHint(serviceId);
|
||||
DefaultRequest<DefaultRequestContext> lbRequest = new DefaultRequest<>(
|
||||
LoadBalancerRequestAdapter<T, DefaultRequestContext> lbRequest = new LoadBalancerRequestAdapter<>(request,
|
||||
new DefaultRequestContext(request, hint));
|
||||
Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator
|
||||
.getSupportedLifecycleProcessors(
|
||||
@@ -74,11 +77,11 @@ public class BlockingLoadBalancerClient implements LoadBalancerClient {
|
||||
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
|
||||
ServiceInstance serviceInstance = choose(serviceId, lbRequest);
|
||||
if (serviceInstance == null) {
|
||||
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle
|
||||
.onComplete(new CompletionContext<>(CompletionContext.Status.DISCARD, new EmptyResponse())));
|
||||
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete(
|
||||
new CompletionContext<>(CompletionContext.Status.DISCARD, lbRequest, new EmptyResponse())));
|
||||
throw new IllegalStateException("No instances available for " + serviceId);
|
||||
}
|
||||
return execute(serviceId, serviceInstance, request);
|
||||
return execute(serviceId, serviceInstance, lbRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -89,25 +92,45 @@ public class BlockingLoadBalancerClient implements LoadBalancerClient {
|
||||
.getSupportedLifecycleProcessors(
|
||||
loadBalancerClientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),
|
||||
DefaultRequestContext.class, Object.class, ServiceInstance.class);
|
||||
Request lbRequest = request instanceof Request ? (Request) request : new DefaultRequest<>();
|
||||
supportedLifecycleProcessors
|
||||
.forEach(lifecycle -> lifecycle.onStartRequest(lbRequest, new DefaultResponse(serviceInstance)));
|
||||
try {
|
||||
T response = request.apply(serviceInstance);
|
||||
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle
|
||||
.onComplete(new CompletionContext<>(CompletionContext.Status.SUCCESS, defaultResponse, response)));
|
||||
Object clientResponse = getClientResponse(response);
|
||||
supportedLifecycleProcessors
|
||||
.forEach(lifecycle -> lifecycle.onComplete(new CompletionContext<>(CompletionContext.Status.SUCCESS,
|
||||
lbRequest, defaultResponse, clientResponse)));
|
||||
return response;
|
||||
}
|
||||
catch (IOException iOException) {
|
||||
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete(
|
||||
new CompletionContext<>(CompletionContext.Status.FAILED, iOException, defaultResponse)));
|
||||
new CompletionContext<>(CompletionContext.Status.FAILED, iOException, lbRequest, defaultResponse)));
|
||||
throw iOException;
|
||||
}
|
||||
catch (Exception exception) {
|
||||
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle
|
||||
.onComplete(new CompletionContext<>(CompletionContext.Status.FAILED, exception, defaultResponse)));
|
||||
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete(
|
||||
new CompletionContext<>(CompletionContext.Status.FAILED, exception, lbRequest, defaultResponse)));
|
||||
ReflectionUtils.rethrowRuntimeException(exception);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private <T> Object getClientResponse(T response) {
|
||||
ClientHttpResponse clientHttpResponse = null;
|
||||
if (response instanceof ClientHttpResponse) {
|
||||
clientHttpResponse = (ClientHttpResponse) response;
|
||||
}
|
||||
if (clientHttpResponse != null) {
|
||||
try {
|
||||
return new ResponseData(clientHttpResponse, null);
|
||||
}
|
||||
catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI reconstructURI(ServiceInstance serviceInstance, URI original) {
|
||||
return LoadBalancerUriTools.reconstructURI(serviceInstance, original);
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.loadbalancer.config;
|
||||
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cloud.loadbalancer.stats.MicrometerStatsLoadBalancerLifecycle;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Autoconfiguration that provides a {@link MicrometerStatsLoadBalancerLifecycle} bean.
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @since 3.0.0
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(MeterRegistry.class)
|
||||
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.stats.micrometer.enabled", havingValue = "true")
|
||||
public class LoadBalancerStatsAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnBean(MeterRegistry.class)
|
||||
public MicrometerStatsLoadBalancerLifecycle micrometerStatsLifecycle(MeterRegistry meterRegistry) {
|
||||
return new MicrometerStatsLoadBalancerLifecycle(meterRegistry);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.loadbalancer.stats;
|
||||
|
||||
import io.micrometer.core.instrument.Tag;
|
||||
import io.micrometer.core.instrument.Tags;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.CompletionContext;
|
||||
import org.springframework.cloud.client.loadbalancer.RequestData;
|
||||
import org.springframework.cloud.client.loadbalancer.RequestDataContext;
|
||||
import org.springframework.cloud.client.loadbalancer.ResponseData;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Utility class for building metrics tags for load-balanced calls.
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @since 3.0.0
|
||||
*/
|
||||
final class LoadBalancerTags {
|
||||
|
||||
static final String UNKNOWN = "UNKNOWN";
|
||||
|
||||
private LoadBalancerTags() {
|
||||
throw new UnsupportedOperationException("Cannot instantiate utility class");
|
||||
}
|
||||
|
||||
static Iterable<Tag> buildSuccessRequestTags(CompletionContext<Object, ServiceInstance, Object> completionContext) {
|
||||
ServiceInstance serviceInstance = completionContext.getLoadBalancerResponse().getServer();
|
||||
Tags tags = Tags.of(buildServiceInstanceTags(serviceInstance));
|
||||
Object clientResponse = completionContext.getClientResponse();
|
||||
if (clientResponse instanceof ResponseData) {
|
||||
ResponseData responseData = (ResponseData) clientResponse;
|
||||
RequestData requestData = responseData.getRequestData();
|
||||
if (requestData != null) {
|
||||
tags = tags.and(valueOrUnknown("method", requestData.getHttpMethod()),
|
||||
valueOrUnknown("uri", getPath(requestData)));
|
||||
}
|
||||
else {
|
||||
tags = tags.and(Tag.of("method", UNKNOWN), Tag.of("uri", UNKNOWN));
|
||||
}
|
||||
|
||||
tags = tags.and(Tag.of("outcome", forStatus(statusValue(responseData))),
|
||||
valueOrUnknown("status", statusValue(responseData)));
|
||||
}
|
||||
else {
|
||||
tags = tags.and(Tag.of("method", UNKNOWN), Tag.of("uri", UNKNOWN), Tag.of("outcome", UNKNOWN),
|
||||
Tag.of("status", UNKNOWN));
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
// In keeping with the way null HttpStatus is handled in Actuator
|
||||
private static int statusValue(ResponseData responseData) {
|
||||
return responseData.getHttpStatus() != null ? responseData.getHttpStatus().value() : 200;
|
||||
}
|
||||
|
||||
private static String getPath(RequestData requestData) {
|
||||
return requestData.getUrl() != null ? requestData.getUrl().getPath() : UNKNOWN;
|
||||
}
|
||||
|
||||
static Iterable<Tag> buildDiscardedRequestTags(
|
||||
CompletionContext<Object, ServiceInstance, Object> completionContext) {
|
||||
if (completionContext.getLoadBalancerRequest().getContext() instanceof RequestDataContext) {
|
||||
RequestData requestData = ((RequestDataContext) completionContext.getLoadBalancerRequest().getContext())
|
||||
.getClientRequest();
|
||||
if (requestData != null) {
|
||||
return Tags.of(valueOrUnknown("method", requestData.getHttpMethod()),
|
||||
valueOrUnknown("uri", getPath(requestData)), valueOrUnknown("serviceId", getHost(requestData)));
|
||||
}
|
||||
}
|
||||
return Tags.of(valueOrUnknown("method", UNKNOWN), valueOrUnknown("uri", UNKNOWN),
|
||||
valueOrUnknown("serviceId", UNKNOWN));
|
||||
|
||||
}
|
||||
|
||||
private static String getHost(RequestData requestData) {
|
||||
return requestData.getUrl() != null ? requestData.getUrl().getHost() : UNKNOWN;
|
||||
}
|
||||
|
||||
static Iterable<Tag> buildFailedRequestTags(CompletionContext<Object, ServiceInstance, Object> completionContext) {
|
||||
ServiceInstance serviceInstance = completionContext.getLoadBalancerResponse().getServer();
|
||||
Tags tags = Tags.of(buildServiceInstanceTags(serviceInstance)).and(exception(completionContext.getThrowable()));
|
||||
if (completionContext.getLoadBalancerRequest().getContext() instanceof RequestDataContext) {
|
||||
RequestData requestData = ((RequestDataContext) completionContext.getLoadBalancerRequest().getContext())
|
||||
.getClientRequest();
|
||||
if (requestData != null) {
|
||||
return tags.and(Tags.of(valueOrUnknown("method", requestData.getHttpMethod()),
|
||||
valueOrUnknown("uri", getPath(requestData))));
|
||||
}
|
||||
}
|
||||
return tags.and(Tags.of(valueOrUnknown("method", UNKNOWN), valueOrUnknown("uri", UNKNOWN)));
|
||||
}
|
||||
|
||||
static Iterable<Tag> buildServiceInstanceTags(ServiceInstance serviceInstance) {
|
||||
return Tags.of(valueOrUnknown("serviceId", serviceInstance.getServiceId()),
|
||||
valueOrUnknown("serviceInstance.instanceId", serviceInstance.getInstanceId()),
|
||||
valueOrUnknown("serviceInstance.host", serviceInstance.getHost()),
|
||||
valueOrUnknown("serviceInstance.port", String.valueOf(serviceInstance.getPort())));
|
||||
}
|
||||
|
||||
private static Tag valueOrUnknown(String key, String value) {
|
||||
if (value != null) {
|
||||
return Tag.of(key, value);
|
||||
}
|
||||
return Tag.of(key, UNKNOWN);
|
||||
}
|
||||
|
||||
private static Tag valueOrUnknown(String key, Object value) {
|
||||
if (value != null) {
|
||||
return Tag.of(key, String.valueOf(value));
|
||||
}
|
||||
return Tag.of(key, UNKNOWN);
|
||||
}
|
||||
|
||||
private static Tag exception(Throwable exception) {
|
||||
if (exception != null) {
|
||||
String simpleName = exception.getClass().getSimpleName();
|
||||
return Tag.of("exception", StringUtils.hasText(simpleName) ? simpleName : exception.getClass().getName());
|
||||
}
|
||||
return Tag.of("exception", "None");
|
||||
}
|
||||
|
||||
// Logic from Actuator's `Outcome` class. Copied in here to avoid adding Actuator
|
||||
// dependency.
|
||||
public static String forStatus(int status) {
|
||||
if (status >= 100 && status < 200) {
|
||||
return "INFORMATIONAL";
|
||||
}
|
||||
else if (status >= 200 && status < 300) {
|
||||
return "SUCCESS";
|
||||
}
|
||||
else if (status >= 300 && status < 400) {
|
||||
return "REDIRECTION";
|
||||
}
|
||||
else if (status >= 400 && status < 500) {
|
||||
return "CLIENT_ERROR";
|
||||
}
|
||||
else if (status >= 500 && status < 600) {
|
||||
return "SERVER_ERROR";
|
||||
}
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.loadbalancer.stats;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import io.micrometer.core.instrument.Counter;
|
||||
import io.micrometer.core.instrument.Gauge;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.Timer;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.CompletionContext;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycle;
|
||||
import org.springframework.cloud.client.loadbalancer.Request;
|
||||
import org.springframework.cloud.client.loadbalancer.Response;
|
||||
import org.springframework.cloud.client.loadbalancer.TimedRequestContext;
|
||||
|
||||
import static org.springframework.cloud.loadbalancer.stats.LoadBalancerTags.buildDiscardedRequestTags;
|
||||
import static org.springframework.cloud.loadbalancer.stats.LoadBalancerTags.buildFailedRequestTags;
|
||||
import static org.springframework.cloud.loadbalancer.stats.LoadBalancerTags.buildServiceInstanceTags;
|
||||
import static org.springframework.cloud.loadbalancer.stats.LoadBalancerTags.buildSuccessRequestTags;
|
||||
|
||||
/**
|
||||
* An implementation of {@link LoadBalancerLifecycle} that records metrics for
|
||||
* load-balanced calls.
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @since 3.0.0
|
||||
*/
|
||||
public class MicrometerStatsLoadBalancerLifecycle implements LoadBalancerLifecycle<Object, Object, ServiceInstance> {
|
||||
|
||||
private final MeterRegistry meterRegistry;
|
||||
|
||||
private final ConcurrentHashMap<ServiceInstance, AtomicLong> activeRequestsPerInstance = new ConcurrentHashMap<>();
|
||||
|
||||
public MicrometerStatsLoadBalancerLifecycle(MeterRegistry meterRegistry) {
|
||||
this.meterRegistry = meterRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class requestContextClass, Class responseClass, Class serverTypeClass) {
|
||||
return ServiceInstance.class.isAssignableFrom(serverTypeClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart(Request<Object> request) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartRequest(Request<Object> request, Response<ServiceInstance> lbResponse) {
|
||||
if (request.getContext() instanceof TimedRequestContext) {
|
||||
((TimedRequestContext) request.getContext()).setRequestStartTime(System.nanoTime());
|
||||
}
|
||||
if (!lbResponse.hasServer()) {
|
||||
return;
|
||||
}
|
||||
ServiceInstance serviceInstance = lbResponse.getServer();
|
||||
AtomicLong activeRequestsCounter = activeRequestsPerInstance.computeIfAbsent(serviceInstance, instance -> {
|
||||
AtomicLong createdCounter = new AtomicLong();
|
||||
Gauge.builder("loadbalancer.requests.active", () -> createdCounter)
|
||||
.tags(buildServiceInstanceTags(serviceInstance)).register(meterRegistry);
|
||||
return createdCounter;
|
||||
});
|
||||
activeRequestsCounter.incrementAndGet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(CompletionContext<Object, ServiceInstance, Object> completionContext) {
|
||||
long requestFinishedTimestamp = System.nanoTime();
|
||||
if (CompletionContext.Status.DISCARD.equals(completionContext.status())) {
|
||||
Counter.builder("loadbalancer.requests.discard").tags(buildDiscardedRequestTags(completionContext))
|
||||
.register(meterRegistry).increment();
|
||||
return;
|
||||
}
|
||||
ServiceInstance serviceInstance = completionContext.getLoadBalancerResponse().getServer();
|
||||
AtomicLong activeRequestsCounter = activeRequestsPerInstance.get(serviceInstance);
|
||||
if (activeRequestsCounter != null) {
|
||||
activeRequestsCounter.decrementAndGet();
|
||||
}
|
||||
Object loadBalancerRequestContext = completionContext.getLoadBalancerRequest().getContext();
|
||||
if (requestHasBeenTimed(loadBalancerRequestContext)) {
|
||||
if (CompletionContext.Status.FAILED.equals(completionContext.status())) {
|
||||
Timer.builder("loadbalancer.requests.failed").tags(buildFailedRequestTags(completionContext))
|
||||
.register(meterRegistry)
|
||||
.record(requestFinishedTimestamp
|
||||
- ((TimedRequestContext) loadBalancerRequestContext).getRequestStartTime(),
|
||||
TimeUnit.NANOSECONDS);
|
||||
return;
|
||||
}
|
||||
Timer.builder("loadbalancer.requests.success").tags(buildSuccessRequestTags(completionContext))
|
||||
.register(meterRegistry)
|
||||
.record(requestFinishedTimestamp
|
||||
- ((TimedRequestContext) loadBalancerRequestContext).getRequestStartTime(),
|
||||
TimeUnit.NANOSECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean requestHasBeenTimed(Object loadBalancerRequestContext) {
|
||||
return loadBalancerRequestContext instanceof TimedRequestContext
|
||||
&& (((TimedRequestContext) loadBalancerRequestContext).getRequestStartTime() != 0L);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,4 +3,5 @@ org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration,\
|
||||
org.springframework.cloud.loadbalancer.config.BlockingLoadBalancerClientAutoConfiguration,\
|
||||
org.springframework.cloud.loadbalancer.config.LoadBalancerCacheAutoConfiguration,\
|
||||
org.springframework.cloud.loadbalancer.security.OAuth2LoadBalancerClientAutoConfiguration
|
||||
org.springframework.cloud.loadbalancer.security.OAuth2LoadBalancerClientAutoConfiguration,\
|
||||
org.springframework.cloud.loadbalancer.config.LoadBalancerStatsAutoConfiguration
|
||||
@@ -20,6 +20,7 @@ import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -173,12 +174,18 @@ class BlockingLoadBalancerClientTests {
|
||||
Collection<Request<Object>> lifecycleLogRequests = ((TestLoadBalancerLifecycle) factory
|
||||
.getInstances("myservice", LoadBalancerLifecycle.class).get("loadBalancerLifecycle")).getStartLog()
|
||||
.values();
|
||||
Collection<CompletionContext<Object, ServiceInstance>> anotherLifecycleLogRequests = ((AnotherLoadBalancerLifecycle) factory
|
||||
Collection<Request<Object>> lifecycleLogStartedRequests = ((TestLoadBalancerLifecycle) factory
|
||||
.getInstances("myservice", LoadBalancerLifecycle.class).get("loadBalancerLifecycle"))
|
||||
.getStartRequestLog().values();
|
||||
Collection<CompletionContext<Object, ServiceInstance, Object>> anotherLifecycleLogRequests = ((AnotherLoadBalancerLifecycle) factory
|
||||
.getInstances("myservice", LoadBalancerLifecycle.class).get("anotherLoadBalancerLifecycle"))
|
||||
.getCompleteLog().values();
|
||||
assertThat(actualResult).isEqualTo(result);
|
||||
assertThat(lifecycleLogRequests).extracting(request -> ((DefaultRequestContext) request.getContext()).getHint())
|
||||
.contains(callbackTestHint);
|
||||
assertThat(lifecycleLogStartedRequests)
|
||||
.extracting(request -> ((DefaultRequestContext) request.getContext()).getHint())
|
||||
.contains(callbackTestHint);
|
||||
assertThat(anotherLifecycleLogRequests).extracting(CompletionContext::getClientResponse).contains(result);
|
||||
}
|
||||
|
||||
@@ -231,9 +238,11 @@ class BlockingLoadBalancerClientTests {
|
||||
|
||||
protected static class TestLoadBalancerLifecycle implements LoadBalancerLifecycle<Object, Object, ServiceInstance> {
|
||||
|
||||
final ConcurrentHashMap<String, Request<Object>> startLog = new ConcurrentHashMap<>();
|
||||
final Map<String, Request<Object>> startLog = new ConcurrentHashMap<>();
|
||||
|
||||
final ConcurrentHashMap<String, CompletionContext<Object, ServiceInstance>> completeLog = new ConcurrentHashMap<>();
|
||||
final Map<String, Request<Object>> startRequestLog = new ConcurrentHashMap<>();
|
||||
|
||||
final Map<String, CompletionContext<Object, ServiceInstance, Object>> completeLog = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void onStart(Request<Object> request) {
|
||||
@@ -241,18 +250,27 @@ class BlockingLoadBalancerClientTests {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(CompletionContext<Object, ServiceInstance> completionContext) {
|
||||
public void onStartRequest(Request<Object> request, Response<ServiceInstance> lbResponse) {
|
||||
startRequestLog.put(getName() + UUID.randomUUID(), request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(CompletionContext<Object, ServiceInstance, Object> completionContext) {
|
||||
completeLog.put(getName() + UUID.randomUUID(), completionContext);
|
||||
}
|
||||
|
||||
ConcurrentHashMap<String, Request<Object>> getStartLog() {
|
||||
Map<String, Request<Object>> getStartLog() {
|
||||
return startLog;
|
||||
}
|
||||
|
||||
ConcurrentHashMap<String, CompletionContext<Object, ServiceInstance>> getCompleteLog() {
|
||||
Map<String, CompletionContext<Object, ServiceInstance, Object>> getCompleteLog() {
|
||||
return completeLog;
|
||||
}
|
||||
|
||||
Map<String, Request<Object>> getStartRequestLog() {
|
||||
return startRequestLog;
|
||||
}
|
||||
|
||||
protected String getName() {
|
||||
return this.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* https://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.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.loadbalancer.stats;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.Tag;
|
||||
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.cloud.client.DefaultServiceInstance;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.loadbalancer.CompletionContext;
|
||||
import org.springframework.cloud.client.loadbalancer.DefaultRequest;
|
||||
import org.springframework.cloud.client.loadbalancer.DefaultRequestContext;
|
||||
import org.springframework.cloud.client.loadbalancer.DefaultResponse;
|
||||
import org.springframework.cloud.client.loadbalancer.EmptyResponse;
|
||||
import org.springframework.cloud.client.loadbalancer.Request;
|
||||
import org.springframework.cloud.client.loadbalancer.RequestData;
|
||||
import org.springframework.cloud.client.loadbalancer.RequestDataContext;
|
||||
import org.springframework.cloud.client.loadbalancer.Response;
|
||||
import org.springframework.cloud.client.loadbalancer.ResponseData;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.util.MultiValueMapAdapter;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.cloud.loadbalancer.stats.LoadBalancerTags.UNKNOWN;
|
||||
|
||||
/**
|
||||
* Tests for {@link MicrometerStatsLoadBalancerLifecycle}.
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*/
|
||||
class MicrometerStatsLoadBalancerLifecycleTests {
|
||||
|
||||
MeterRegistry meterRegistry = new SimpleMeterRegistry();
|
||||
|
||||
MicrometerStatsLoadBalancerLifecycle statsLifecycle = new MicrometerStatsLoadBalancerLifecycle(meterRegistry);
|
||||
|
||||
@Test
|
||||
void shouldRecordSuccessfulTimedRequest() {
|
||||
RequestData requestData = new RequestData(HttpMethod.GET, URI.create("http://test.org/test"), new HttpHeaders(),
|
||||
new HttpHeaders(), new HashMap<>());
|
||||
Request<Object> lbRequest = new DefaultRequest<>(new RequestDataContext(requestData));
|
||||
Response<ServiceInstance> lbResponse = new DefaultResponse(
|
||||
new DefaultServiceInstance("test-1", "test", "test.org", 8080, false, new HashMap<>()));
|
||||
ResponseData responseData = new ResponseData(HttpStatus.OK, new HttpHeaders(),
|
||||
new MultiValueMapAdapter<>(new HashMap<>()), requestData);
|
||||
statsLifecycle.onStartRequest(lbRequest, lbResponse);
|
||||
assertThat(meterRegistry.get("loadbalancer.requests.active").gauge().value()).isEqualTo(1);
|
||||
|
||||
statsLifecycle.onComplete(
|
||||
new CompletionContext<>(CompletionContext.Status.SUCCESS, lbRequest, lbResponse, responseData));
|
||||
|
||||
assertThat(meterRegistry.getMeters()).hasSize(2);
|
||||
assertThat(meterRegistry.get("loadbalancer.requests.active").gauge().value()).isEqualTo(0);
|
||||
assertThat(meterRegistry.get("loadbalancer.requests.success").timers()).hasSize(1);
|
||||
assertThat(meterRegistry.get("loadbalancer.requests.success").timer().count()).isEqualTo(1);
|
||||
assertThat(meterRegistry.get("loadbalancer.requests.success").timer().getId().getTags()).contains(
|
||||
Tag.of("method", "GET"), Tag.of("outcome", "SUCCESS"), Tag.of("serviceId", "test"),
|
||||
Tag.of("serviceInstance.host", "test.org"), Tag.of("serviceInstance.instanceId", "test-1"),
|
||||
Tag.of("serviceInstance.port", "8080"), Tag.of("status", "200"), Tag.of("uri", "/test"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRecordFailedTimedRequest() {
|
||||
RequestData requestData = new RequestData(HttpMethod.GET, URI.create("http://test.org/test"), new HttpHeaders(),
|
||||
new HttpHeaders(), new HashMap<>());
|
||||
Request<Object> lbRequest = new DefaultRequest<>(new RequestDataContext(requestData));
|
||||
Response<ServiceInstance> lbResponse = new DefaultResponse(
|
||||
new DefaultServiceInstance("test-1", "test", "test.org", 8080, false, new HashMap<>()));
|
||||
statsLifecycle.onStartRequest(lbRequest, lbResponse);
|
||||
assertThat(meterRegistry.get("loadbalancer.requests.active").gauge().value()).isEqualTo(1);
|
||||
|
||||
statsLifecycle.onComplete(new CompletionContext<>(CompletionContext.Status.FAILED, new IllegalStateException(),
|
||||
lbRequest, lbResponse));
|
||||
|
||||
assertThat(meterRegistry.getMeters()).hasSize(2);
|
||||
assertThat(meterRegistry.get("loadbalancer.requests.active").gauge().value()).isEqualTo(0);
|
||||
assertThat(meterRegistry.get("loadbalancer.requests.failed").timers()).hasSize(1);
|
||||
assertThat(meterRegistry.get("loadbalancer.requests.failed").timer().count()).isEqualTo(1);
|
||||
assertThat(meterRegistry.get("loadbalancer.requests.failed").timer().getId().getTags()).contains(
|
||||
Tag.of("exception", "IllegalStateException"), Tag.of("method", "GET"), Tag.of("serviceId", "test"),
|
||||
Tag.of("serviceInstance.host", "test.org"), Tag.of("serviceInstance.instanceId", "test-1"),
|
||||
Tag.of("serviceInstance.port", "8080"), Tag.of("uri", "/test"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotRecordDiscardedRequest() {
|
||||
RequestData requestData = new RequestData(HttpMethod.GET, URI.create("http://test.org/test"), new HttpHeaders(),
|
||||
new HttpHeaders(), new HashMap<>());
|
||||
Request<Object> lbRequest = new DefaultRequest<>(new RequestDataContext(requestData));
|
||||
Response<ServiceInstance> lbResponse = new EmptyResponse();
|
||||
statsLifecycle.onStartRequest(lbRequest, lbResponse);
|
||||
|
||||
statsLifecycle.onComplete(new CompletionContext<>(CompletionContext.Status.DISCARD, lbRequest, lbResponse));
|
||||
assertThat(meterRegistry.getMeters()).hasSize(1);
|
||||
assertThat(meterRegistry.get("loadbalancer.requests.discard").counter().count()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotRecordUnTimedRequest() {
|
||||
Request<Object> lbRequest = new DefaultRequest<>(new StatsTestContext());
|
||||
Response<ServiceInstance> lbResponse = new DefaultResponse(
|
||||
new DefaultServiceInstance("test-1", "test", "test.org", 8080, false, new HashMap<>()));
|
||||
ResponseData responseData = new ResponseData(HttpStatus.OK, new HttpHeaders(),
|
||||
new MultiValueMapAdapter<>(new HashMap<>()), null);
|
||||
statsLifecycle.onStartRequest(lbRequest, lbResponse);
|
||||
assertThat(meterRegistry.get("loadbalancer.requests.active").gauge().value()).isEqualTo(1);
|
||||
|
||||
statsLifecycle.onComplete(
|
||||
new CompletionContext<>(CompletionContext.Status.SUCCESS, lbRequest, lbResponse, responseData));
|
||||
|
||||
assertThat(meterRegistry.getMeters()).hasSize(1);
|
||||
assertThat(meterRegistry.get("loadbalancer.requests.active").gauge().value()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotCreateNullTagsWhenNullDataObjects() {
|
||||
Request<Object> lbRequest = new DefaultRequest<>(new DefaultRequestContext());
|
||||
Response<ServiceInstance> lbResponse = new DefaultResponse(new DefaultServiceInstance());
|
||||
statsLifecycle.onStartRequest(lbRequest, lbResponse);
|
||||
assertThat(meterRegistry.get("loadbalancer.requests.active").gauge().value()).isEqualTo(1);
|
||||
|
||||
statsLifecycle
|
||||
.onComplete(new CompletionContext<>(CompletionContext.Status.SUCCESS, lbRequest, lbResponse, null));
|
||||
|
||||
assertThat(meterRegistry.getMeters()).hasSize(2);
|
||||
assertThat(meterRegistry.get("loadbalancer.requests.active").gauge().value()).isEqualTo(0);
|
||||
assertThat(meterRegistry.get("loadbalancer.requests.success").timers()).hasSize(1);
|
||||
assertThat(meterRegistry.get("loadbalancer.requests.success").timer().count()).isEqualTo(1);
|
||||
assertThat(meterRegistry.get("loadbalancer.requests.success").timer().getId().getTags()).contains(
|
||||
Tag.of("method", UNKNOWN), Tag.of("outcome", UNKNOWN), Tag.of("serviceId", UNKNOWN),
|
||||
Tag.of("serviceInstance.host", UNKNOWN), Tag.of("serviceInstance.instanceId", UNKNOWN),
|
||||
Tag.of("serviceInstance.port", "0"), Tag.of("status", UNKNOWN), Tag.of("uri", UNKNOWN));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotCreateNullTagsWhenEmptyDataObjects() {
|
||||
RequestData requestData = new RequestData(null, null, null, null, null);
|
||||
Request<Object> lbRequest = new DefaultRequest<>(new RequestDataContext());
|
||||
Response<ServiceInstance> lbResponse = new DefaultResponse(new DefaultServiceInstance());
|
||||
ResponseData responseData = new ResponseData(null, null, null, requestData);
|
||||
statsLifecycle.onStartRequest(lbRequest, lbResponse);
|
||||
assertThat(meterRegistry.get("loadbalancer.requests.active").gauge().value()).isEqualTo(1);
|
||||
|
||||
statsLifecycle.onComplete(
|
||||
new CompletionContext<>(CompletionContext.Status.SUCCESS, lbRequest, lbResponse, responseData));
|
||||
|
||||
assertThat(meterRegistry.getMeters()).hasSize(2);
|
||||
assertThat(meterRegistry.get("loadbalancer.requests.active").gauge().value()).isEqualTo(0);
|
||||
assertThat(meterRegistry.get("loadbalancer.requests.success").timers()).hasSize(1);
|
||||
assertThat(meterRegistry.get("loadbalancer.requests.success").timer().count()).isEqualTo(1);
|
||||
assertThat(meterRegistry.get("loadbalancer.requests.success").timer().getId().getTags()).contains(
|
||||
Tag.of("method", UNKNOWN), Tag.of("outcome", "SUCCESS"), Tag.of("serviceId", UNKNOWN),
|
||||
Tag.of("serviceInstance.host", UNKNOWN), Tag.of("serviceInstance.instanceId", UNKNOWN),
|
||||
Tag.of("serviceInstance.port", "0"), Tag.of("status", "200"), Tag.of("uri", UNKNOWN));
|
||||
}
|
||||
|
||||
private static class StatsTestContext {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user