diff --git a/extensions/micrometer/deployment/pom.xml b/extensions/micrometer/deployment/pom.xml index 8273cf3a321fe..14beea0809dd5 100644 --- a/extensions/micrometer/deployment/pom.xml +++ b/extensions/micrometer/deployment/pom.xml @@ -81,22 +81,31 @@ io.quarkus - quarkus-resteasy-deployment + quarkus-resteasy-reactive-deployment test io.quarkus - quarkus-rest-client-deployment + quarkus-rest-client-reactive-deployment test io.quarkus - quarkus-resteasy-jackson-deployment + quarkus-resteasy-reactive-jackson-deployment test - + + io.smallrye.stork + stork-service-discovery-static-list + test + + + io.smallrye.stork + stork-configuration-generator + provided + io.quarkus quarkus-undertow-deployment @@ -132,6 +141,16 @@ resteasy-reactive-client test + + org.assertj + assertj-core + test + + + org.mockito + mockito-core + test + diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/StorkMetricsServiceDiscoveryFailTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/StorkMetricsServiceDiscoveryFailTest.java new file mode 100644 index 0000000000000..093503e04c13c --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/StorkMetricsServiceDiscoveryFailTest.java @@ -0,0 +1,84 @@ + +package io.quarkus.micrometer.deployment.binder; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import java.util.concurrent.TimeUnit; + +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mockito.Mockito; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.Timer; +import io.quarkus.micrometer.runtime.binder.stork.StorkObservationCollectorBean; +import io.quarkus.micrometer.test.FakeSelectorConfiguration; +import io.quarkus.micrometer.test.MockConfiguration; +import io.quarkus.micrometer.test.MockLoadBalancerProvider; +import io.quarkus.micrometer.test.MockServiceDiscoveryProvider; +import io.quarkus.micrometer.test.MockServiceDiscoveryProviderLoader; +import io.quarkus.micrometer.test.PingPongResource; +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; +import io.smallrye.mutiny.Uni; + +@DisabledOnOs(OS.WINDOWS) +public class StorkMetricsServiceDiscoveryFailTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("test-logging.properties") + .overrideConfigKey("quarkus.micrometer.binder.stork.enabled", "true") + .overrideConfigKey("quarkus.stork.pingpong-service.service-discovery.type", "mock") + .overrideConfigKey("pingpong/mp-rest/url", "stork://pingpong-service") + .withApplicationRoot((jar) -> jar + .addClasses(PingPongResource.class, PingPongResource.PingPongRestClient.class, + MockServiceDiscoveryProvider.class, MockLoadBalancerProvider.class, MockConfiguration.class, + FakeSelectorConfiguration.class, MockServiceDiscoveryProviderLoader.class)); + + @Inject + MeterRegistry registry; + + @Inject + MockServiceDiscoveryProvider provider; + + @Test + public void shouldGetStorkMetricsWhenEverythingSucceded() { + + Mockito.when(provider.getServiceDiscovery().getServiceInstances()) + .thenReturn(Uni.createFrom().failure(new RuntimeException("Service Discovery induced failure"))); + RestAssured.when().get("/ping/one").then().statusCode(500); + + Counter instanceCounter = registry.get("stork.instances.count").counter(); + Timer serviceDiscoveryDuration = registry.get("stork.service-discovery.duration").timer(); + Timer serviceSelectionDuration = registry.get("stork.service-selection.duration").timer(); + Timer overallDuration = registry.get("stork.overall.duration").timer(); + Gauge failures = registry.get("stork.failures.number").gauge(); + + assertTags(Tag.of("service-name", "pingpong-service"), instanceCounter, serviceDiscoveryDuration, + serviceSelectionDuration, overallDuration); + + assertThat(StorkObservationCollectorBean.STORK_METRICS.failure()).isNotNull(); + assertThat(instanceCounter.count()).isEqualTo(0); + assertThat(failures.value()).isGreaterThan(0); + assertThat(serviceDiscoveryDuration.totalTime(TimeUnit.NANOSECONDS)).isGreaterThan(0); + assertThat(serviceSelectionDuration.totalTime(TimeUnit.NANOSECONDS)).isEqualTo(0); + assertThat(overallDuration.totalTime(TimeUnit.NANOSECONDS)).isGreaterThan(0); + + } + + private static void assertTags(Tag tag, Meter... meters) { + for (Meter meter : meters) { + assertThat(meter.getId().getTags().contains(tag)); + } + } + +} diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/StorkMetricsTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/StorkMetricsTest.java index faacd16a00139..2353851106546 100644 --- a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/StorkMetricsTest.java +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/StorkMetricsTest.java @@ -1,22 +1,79 @@ package io.quarkus.micrometer.deployment.binder; +import static io.restassured.RestAssured.when; + +import java.util.concurrent.TimeUnit; + import jakarta.inject.Inject; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledOnOs; import org.junit.jupiter.api.condition.OS; import org.junit.jupiter.api.extension.RegisterExtension; +import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.Timer; +import io.quarkus.micrometer.test.PingPongResource; import io.quarkus.test.QuarkusUnitTest; @DisabledOnOs(OS.WINDOWS) public class StorkMetricsTest { @RegisterExtension - static final QuarkusUnitTest config = new QuarkusUnitTest(); + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("test-logging.properties") + .overrideConfigKey("quarkus.micrometer.binder.stork.enabled", "true") + .overrideConfigKey("quarkus.stork.pingpong-service.service-discovery.type", "static") + .overrideConfigKey("quarkus.stork.pingpong-service.service-discovery.address-list", "${test.url}") + .overrideConfigKey("pingpong/mp-rest/url", "stork://pingpong-service") + .withApplicationRoot((jar) -> jar + .addClasses(PingPongResource.class, PingPongResource.PingPongRestClient.class)); @Inject MeterRegistry registry; + @Test + public void shouldGetStorkMetricsWhenEverythingSucceded() { + when().get("/ping/one").then().statusCode(200); + + Counter instanceCounter = registry.get("stork.instances.count").counter(); + Timer serviceDiscoveryDuration = registry.get("stork.service-discovery.duration").timer(); + Timer serviceSelectionDuration = registry.get("stork.service-selection.duration").timer(); + + Assertions.assertTrue(instanceCounter.getId().getTags().contains(Tag.of("service-name", "pingpong-service"))); + Assertions.assertTrue(serviceDiscoveryDuration.getId().getTags().contains(Tag.of("service-name", "pingpong-service"))); + Assertions.assertTrue(serviceSelectionDuration.getId().getTags().contains(Tag.of("service-name", "pingpong-service"))); + + Assertions.assertEquals(instanceCounter.count(), 1); + Assertions.assertTrue(1 == serviceDiscoveryDuration.count()); + Assertions.assertTrue(serviceDiscoveryDuration.totalTime(TimeUnit.NANOSECONDS) > 0); + Assertions.assertTrue(1 == serviceSelectionDuration.count()); + Assertions.assertTrue(serviceSelectionDuration.totalTime(TimeUnit.NANOSECONDS) > 0); + + } + + @Test + public void shouldGetStorkMetricsWhenServiceDiscoveryFails() { + when().get("/ping/one").then().statusCode(200); + + Counter instanceCounter = registry.get("stork.instances.count").counter(); + Timer serviceDiscoveryDuration = registry.get("stork.service-discovery.duration").timer(); + Timer serviceSelectionDuration = registry.get("stork.service-selection.duration").timer(); + + Assertions.assertTrue(instanceCounter.getId().getTags().contains(Tag.of("service-name", "pingpong-service"))); + Assertions.assertTrue(serviceDiscoveryDuration.getId().getTags().contains(Tag.of("service-name", "pingpong-service"))); + Assertions.assertTrue(serviceSelectionDuration.getId().getTags().contains(Tag.of("service-name", "pingpong-service"))); + + Assertions.assertEquals(instanceCounter.count(), 1); + Assertions.assertTrue(1 == serviceDiscoveryDuration.count()); + Assertions.assertTrue(serviceDiscoveryDuration.totalTime(TimeUnit.NANOSECONDS) > 0); + Assertions.assertTrue(1 == serviceSelectionDuration.count()); + Assertions.assertTrue(serviceSelectionDuration.totalTime(TimeUnit.NANOSECONDS) > 0); + + } + } diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/FakeSelectorConfiguration.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/FakeSelectorConfiguration.java new file mode 100644 index 0000000000000..4b268b185d4b4 --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/FakeSelectorConfiguration.java @@ -0,0 +1,52 @@ +package io.quarkus.micrometer.test; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import io.smallrye.stork.api.config.ConfigWithType; + +/** + * Configuration for the {@code MockLoadBalancerProvider} LoadBalancer. + */ +public class FakeSelectorConfiguration implements io.smallrye.stork.api.config.ConfigWithType { + private final Map parameters; + + /** + * Creates a new FakeSelectorConfiguration + * + * @param params the parameters, must not be {@code null} + */ + public FakeSelectorConfiguration(Map params) { + parameters = Collections.unmodifiableMap(params); + } + + /** + * Creates a new FakeSelectorConfiguration + */ + public FakeSelectorConfiguration() { + parameters = Collections.emptyMap(); + } + + /** + * @return the type + */ + @Override + public String type() { + return "fake-selector"; + } + + /** + * @return the parameters + */ + @Override + public Map parameters() { + return parameters; + } + + private FakeSelectorConfiguration extend(String key, String value) { + Map copy = new HashMap<>(parameters); + copy.put(key, value); + return new FakeSelectorConfiguration(copy); + } +} diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/MockConfiguration.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/MockConfiguration.java new file mode 100644 index 0000000000000..f361901854f7d --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/MockConfiguration.java @@ -0,0 +1,52 @@ +package io.quarkus.micrometer.test; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import io.smallrye.stork.api.config.ConfigWithType; + +/** + * Configuration for the {@code MockServiceDiscoveryProvider} ServiceDiscovery. + */ +public class MockConfiguration implements io.smallrye.stork.api.config.ConfigWithType { + private final Map parameters; + + /** + * Creates a new MockConfiguration + * + * @param params the parameters, must not be {@code null} + */ + public MockConfiguration(Map params) { + parameters = Collections.unmodifiableMap(params); + } + + /** + * Creates a new MockConfiguration + */ + public MockConfiguration() { + parameters = Collections.emptyMap(); + } + + /** + * @return the type + */ + @Override + public String type() { + return "mock"; + } + + /** + * @return the parameters + */ + @Override + public Map parameters() { + return parameters; + } + + private MockConfiguration extend(String key, String value) { + Map copy = new HashMap<>(parameters); + copy.put(key, value); + return new MockConfiguration(copy); + } +} diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/MockLoadBalancerProvider.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/MockLoadBalancerProvider.java new file mode 100644 index 0000000000000..28fbf4738d0ea --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/MockLoadBalancerProvider.java @@ -0,0 +1,20 @@ +package io.quarkus.micrometer.test; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.mockito.Mockito; + +import io.smallrye.stork.api.LoadBalancer; +import io.smallrye.stork.api.ServiceDiscovery; +import io.smallrye.stork.api.config.LoadBalancerType; +import io.smallrye.stork.spi.LoadBalancerProvider; + +@LoadBalancerType("fake-selector") +@ApplicationScoped +public class MockLoadBalancerProvider implements LoadBalancerProvider { + + @Override + public LoadBalancer createLoadBalancer(FakeSelectorConfiguration config, ServiceDiscovery serviceDiscovery) { + return Mockito.mock(LoadBalancer.class); + } +} diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/MockServiceDiscoveryProvider.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/MockServiceDiscoveryProvider.java new file mode 100644 index 0000000000000..bebdadb500515 --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/MockServiceDiscoveryProvider.java @@ -0,0 +1,31 @@ +package io.quarkus.micrometer.test; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.mockito.Mockito; + +import io.smallrye.stork.api.ServiceDiscovery; +import io.smallrye.stork.api.config.ServiceConfig; +import io.smallrye.stork.api.config.ServiceDiscoveryType; +import io.smallrye.stork.spi.ServiceDiscoveryProvider; +import io.smallrye.stork.spi.StorkInfrastructure; + +@ServiceDiscoveryType("mock") +//@ServiceDiscoveryAttribute(name = "failure", description = "indicates if service discovery should fail") +@ApplicationScoped +public class MockServiceDiscoveryProvider implements ServiceDiscoveryProvider { + + public ServiceDiscovery getServiceDiscovery() { + return serviceDiscovery; + } + + private ServiceDiscovery serviceDiscovery; + + @Override + public ServiceDiscovery createServiceDiscovery(MockConfiguration config, String serviceName, ServiceConfig serviceConfig, + StorkInfrastructure storkInfrastructure) { + serviceDiscovery = Mockito.mock(ServiceDiscovery.class); + return serviceDiscovery; + } + +} diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/MockServiceDiscoveryProviderLoader.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/MockServiceDiscoveryProviderLoader.java new file mode 100644 index 0000000000000..246a1d7013ce3 --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/MockServiceDiscoveryProviderLoader.java @@ -0,0 +1,44 @@ +package io.quarkus.micrometer.test; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.spi.CDI; + +import io.smallrye.stork.api.ServiceDiscovery; +import io.smallrye.stork.api.config.ConfigWithType; +import io.smallrye.stork.api.config.ServiceConfig; +import io.smallrye.stork.spi.StorkInfrastructure; + +/** + * ServiceDiscoveryLoader for {@link io.quarkus.micrometer.test.MockServiceDiscoveryProvider} + */ +@ApplicationScoped +public class MockServiceDiscoveryProviderLoader implements io.smallrye.stork.spi.internal.ServiceDiscoveryLoader { + private final io.quarkus.micrometer.test.MockServiceDiscoveryProvider provider; + + public MockServiceDiscoveryProviderLoader() { + io.quarkus.micrometer.test.MockServiceDiscoveryProvider actual = null; + try { + actual = CDI.current().select(io.quarkus.micrometer.test.MockServiceDiscoveryProvider.class).get(); + } catch (Exception e) { + // Use direct instantiation + actual = new io.quarkus.micrometer.test.MockServiceDiscoveryProvider(); + } + this.provider = actual; + } + + @Override + public ServiceDiscovery createServiceDiscovery(ConfigWithType config, String serviceName, + ServiceConfig serviceConfig, StorkInfrastructure storkInfrastructure) { + io.quarkus.micrometer.test.MockConfiguration typedConfig = new io.quarkus.micrometer.test.MockConfiguration( + config.parameters()); + return provider.createServiceDiscovery(typedConfig, serviceName, serviceConfig, storkInfrastructure); + } + + /** + * @return the type + */ + @Override + public String type() { + return "mock"; + } +} diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/stork/StorkObservationCollectorBean.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/stork/StorkObservationCollectorBean.java index b26d88e8fcbae..f250df10811aa 100644 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/stork/StorkObservationCollectorBean.java +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/stork/StorkObservationCollectorBean.java @@ -1,5 +1,6 @@ package io.quarkus.micrometer.runtime.binder.stork; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; @@ -7,6 +8,7 @@ import jakarta.enterprise.inject.Typed; import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.Gauge; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Metrics; import io.micrometer.core.instrument.Tag; @@ -33,12 +35,11 @@ public ObservationPoints.StorkResolutionEvent create(String serviceName, String STORK_METRICS = new ObservationPoints.StorkResolutionEvent(serviceName, serviceDiscoveryType, serviceSelectionType, STORK_HANDLER) { - private final Tags tags = Tags.of(Tag.of("client-name", getServiceName()));; + private final Tags tags = Tags.of(Tag.of("service-name", getServiceName()));; private final Counter instanceCounter = Counter.builder("stork.instances.count") .description("The number of service instances discovered") .tags(tags) .register(registry);; - private String name = serviceName; private final Timer serviceDiscoveryTimer = Timer.builder("stork.service-discovery.duration") .description("The duration of the discovery operation") @@ -50,10 +51,21 @@ public ObservationPoints.StorkResolutionEvent create(String serviceName, String .tags(tags) .register(registry); + private final Timer overallTimer = Timer.builder("stork.overall.duration") + .description("The total duration of the Stork service discovery and selection operations") + .tags(tags) + .register(registry); + + private List exceptions = new ArrayList<>(); + + private final Gauge failures = Gauge.builder("stork.failures.number", exceptions, List::size) + .description("The number of failures during service discovery and selection.").tags(tags) + .register(registry); + @Override public void onServiceDiscoverySuccess(List instances) { this.endOfServiceDiscovery = System.nanoTime(); - this.serviceDiscoverySucceeded = true; + this.serviceDiscoveryDone = true; if (instances != null) { instanceCounter.increment(); } @@ -68,8 +80,13 @@ public int getDiscoveredInstancesCount() { @Override public void onServiceDiscoveryFailure(Throwable throwable) { - onServiceSelectionFailure(throwable); - // serviceDiscoveryTimer.record(getServiceDiscoveryDuration().getNano(), TimeUnit.NANOSECONDS); + this.endOfServiceDiscovery = System.nanoTime(); + this.serviceDiscoveryDone = true; + this.failure = throwable; + exceptions.add(throwable); + this.handler.complete(this); + serviceDiscoveryTimer.record(getServiceDiscoveryDuration().getNano(), TimeUnit.NANOSECONDS); + overallTimer.record(getServiceDiscoveryDuration().getNano(), TimeUnit.NANOSECONDS); } @@ -77,19 +94,13 @@ public void onServiceDiscoveryFailure(Throwable throwable) { public void onServiceSelectionSuccess(long id) { this.endOfServiceSelection = System.nanoTime(); selectedInstance = id; - succeeded = true; + done = true; this.handler.complete(this); serviceSelectionTimer.record(getServiceSelectionDuration().getNano(), TimeUnit.NANOSECONDS); + overallTimer.record(getOverallDuration().getNano(), TimeUnit.NANOSECONDS); } - // @Override - // public void onServiceSelectionFailure(Throwable throwable) { - // this.onServiceSelectionFailure(throwable); - // serviceSelectionTimer.record(getServiceDiscoveryDuration().getNano(), TimeUnit.NANOSECONDS); - // - // } - }; return STORK_METRICS; } diff --git a/integration-tests/rest-client-reactive-stork/pom.xml b/integration-tests/rest-client-reactive-stork/pom.xml index e1312b14ed167..4a2387d3b9641 100644 --- a/integration-tests/rest-client-reactive-stork/pom.xml +++ b/integration-tests/rest-client-reactive-stork/pom.xml @@ -65,6 +65,10 @@ rest-assured test + + org.mockito + mockito-core + org.awaitility awaitility diff --git a/integration-tests/rest-client-reactive-stork/src/main/java/io/quarkus/it/rest/client/reactive/stork/MockServiceDiscoveryProvider.java b/integration-tests/rest-client-reactive-stork/src/main/java/io/quarkus/it/rest/client/reactive/stork/MockServiceDiscoveryProvider.java new file mode 100644 index 0000000000000..d937f06b03863 --- /dev/null +++ b/integration-tests/rest-client-reactive-stork/src/main/java/io/quarkus/it/rest/client/reactive/stork/MockServiceDiscoveryProvider.java @@ -0,0 +1,23 @@ +package io.quarkus.it.rest.client.reactive.stork; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.mockito.Mockito; + +import io.smallrye.stork.api.ServiceDiscovery; +import io.smallrye.stork.api.config.ServiceConfig; +import io.smallrye.stork.api.config.ServiceDiscoveryType; +import io.smallrye.stork.spi.ServiceDiscoveryProvider; +import io.smallrye.stork.spi.StorkInfrastructure; + +@ServiceDiscoveryType("mock") +//@ServiceDiscoveryAttribute(name = "failure", description = "indicates if service discovery should fail") +@ApplicationScoped +public class MockServiceDiscoveryProvider implements ServiceDiscoveryProvider { + + @Override + public ServiceDiscovery createServiceDiscovery(MockConfiguration config, String serviceName, ServiceConfig serviceConfig, + StorkInfrastructure storkInfrastructure) { + return Mockito.mock(ServiceDiscovery.class); + } +}