diff --git a/ci/smoke-tests.yml b/ci/smoke-tests.yml index b2fadaf5..928736f0 100644 --- a/ci/smoke-tests.yml +++ b/ci/smoke-tests.yml @@ -29,6 +29,7 @@ groups: - cloud-discovery-zookeeper - cloud-function-web - cloud-function-webflux + - cloud-loadbalancing-webflux - cloud-stream-kafka - cloud-stream-rabbit - cloud-task diff --git a/cloud-loadbalancing-webflux/build.gradle b/cloud-loadbalancing-webflux/build.gradle new file mode 100644 index 00000000..5ad84b28 --- /dev/null +++ b/cloud-loadbalancing-webflux/build.gradle @@ -0,0 +1,39 @@ +plugins { + id 'java' + id 'org.springframework.boot' + id 'org.springframework.aot.smoke-test' + id 'org.graalvm.buildtools.native' +} + +ext { + set('springCloudVersion', "2022.0.0-SNAPSHOT") +} + +dependencies { + implementation(platform( + org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES)) + implementation( + platform("org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}")) + implementation("org.springframework.boot:spring-boot-starter") + implementation("org.springframework.boot:spring-boot-starter-webflux") + implementation("org.springframework.cloud:spring-cloud-starter-loadbalancer") + + testImplementation("org.springframework.boot:spring-boot-starter-test") + + aotSmokeTestImplementation(project(":aot-smoke-test-support")) + aotSmokeTestImplementation("org.awaitility:awaitility:4.2.0") +} + +aotSmokeTest { + webApplication = true +} + +graalvmNative { + binaries { + main { + buildArgs.add("--exclude-config") + buildArgs.add("/netty-.*") + buildArgs.add("META-INF/native-image/.*") + } + } +} \ No newline at end of file diff --git a/cloud-loadbalancing-webflux/docker-compose.yml b/cloud-loadbalancing-webflux/docker-compose.yml new file mode 100644 index 00000000..337af601 --- /dev/null +++ b/cloud-loadbalancing-webflux/docker-compose.yml @@ -0,0 +1,10 @@ +version: '3' +services: + test-service: + image: springcloud/test-service:latest + ports: + - "8081" + demo-service: + image: springcloud/demo-service:latest + ports: + - "8082" \ No newline at end of file diff --git a/cloud-loadbalancing-webflux/src/aotSmokeTest/java/com/example/cloud/loadbalancing/CloudLoadBalancerClientWebFluxApplicationAotTests.java b/cloud-loadbalancing-webflux/src/aotSmokeTest/java/com/example/cloud/loadbalancing/CloudLoadBalancerClientWebFluxApplicationAotTests.java new file mode 100644 index 00000000..622183ec --- /dev/null +++ b/cloud-loadbalancing-webflux/src/aotSmokeTest/java/com/example/cloud/loadbalancing/CloudLoadBalancerClientWebFluxApplicationAotTests.java @@ -0,0 +1,29 @@ +package com.example.cloud.loadbalancing; + +import java.time.Duration; + +import org.awaitility.Awaitility; +import org.junit.jupiter.api.Test; + +import org.springframework.aot.smoketest.support.assertj.AssertableOutput; +import org.springframework.aot.smoketest.support.junit.AotSmokeTest; + +import static org.assertj.core.api.Assertions.assertThat; + +@AotSmokeTest +public class CloudLoadBalancerClientWebFluxApplicationAotTests { + + @Test + void shouldRetrieveInstance(AssertableOutput output) { + Awaitility.await().atMost(Duration.ofSeconds(30)).untilAsserted(() -> { + assertThat(output).hasLineContaining("On Start: "); + assertThat(output).hasLineContaining("On Start Request: "); + assertThat(output).hasLineContaining("On Complete: "); + assertThat(output).hasLineContaining("c.e.c.l.s.LoadBalancerClientTestService : demo"); + assertThat(output).hasLineContaining("c.e.c.l.s.LoadBalancerClientTestService : test"); + assertThat(output).hasNoLinesContaining("ERROR"); + }); + + } + +} diff --git a/cloud-loadbalancing-webflux/src/main/java/com/example/cloud/loadbalancing/CloudLoadBalancerClientWebFluxApplication.java b/cloud-loadbalancing-webflux/src/main/java/com/example/cloud/loadbalancing/CloudLoadBalancerClientWebFluxApplication.java new file mode 100644 index 00000000..9289575d --- /dev/null +++ b/cloud-loadbalancing-webflux/src/main/java/com/example/cloud/loadbalancing/CloudLoadBalancerClientWebFluxApplication.java @@ -0,0 +1,37 @@ +package com.example.cloud.loadbalancing; + +import com.example.cloud.loadbalancing.service.LoadBalancerClientTestService; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@SpringBootApplication +public class CloudLoadBalancerClientWebFluxApplication implements CommandLineRunner { + + @Autowired + LoadBalancerClientTestService testService; + + public static void main(String[] args) { + SpringApplication.run(CloudLoadBalancerClientWebFluxApplication.class, args); + } + + @Override + public void run(String... args) throws Exception { + testService.callServices(); + } + + @RestController + static class TestController { + + @GetMapping("/") + public String test() { + return "test"; + } + + } + +} diff --git a/cloud-loadbalancing-webflux/src/main/java/com/example/cloud/loadbalancing/client/DemoServiceClient.java b/cloud-loadbalancing-webflux/src/main/java/com/example/cloud/loadbalancing/client/DemoServiceClient.java new file mode 100644 index 00000000..6fd34c53 --- /dev/null +++ b/cloud-loadbalancing-webflux/src/main/java/com/example/cloud/loadbalancing/client/DemoServiceClient.java @@ -0,0 +1,24 @@ +package com.example.cloud.loadbalancing.client; + +import java.net.URI; + +import reactor.core.publisher.Mono; + +import org.springframework.cloud.client.loadbalancer.LoadBalanced; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.WebClient; + +@Component +public class DemoServiceClient { + + private final WebClient webClient; + + public DemoServiceClient(@LoadBalanced WebClient.Builder builder) { + this.webClient = builder.build(); + } + + public Mono demo() { + return webClient.get().uri(URI.create("http://demo-service")).retrieve().bodyToMono(String.class); + } + +} diff --git a/cloud-loadbalancing-webflux/src/main/java/com/example/cloud/loadbalancing/client/TestServiceClient.java b/cloud-loadbalancing-webflux/src/main/java/com/example/cloud/loadbalancing/client/TestServiceClient.java new file mode 100644 index 00000000..f23037ab --- /dev/null +++ b/cloud-loadbalancing-webflux/src/main/java/com/example/cloud/loadbalancing/client/TestServiceClient.java @@ -0,0 +1,24 @@ +package com.example.cloud.loadbalancing.client; + +import java.net.URI; + +import reactor.core.publisher.Mono; + +import org.springframework.cloud.client.loadbalancer.LoadBalanced; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.WebClient; + +@Component +public class TestServiceClient { + + private final WebClient webClient; + + public TestServiceClient(@LoadBalanced WebClient.Builder builder) { + this.webClient = builder.build(); + } + + public Mono test() { + return webClient.get().uri(URI.create("http://test-service")).retrieve().bodyToMono(String.class); + } + +} diff --git a/cloud-loadbalancing-webflux/src/main/java/com/example/cloud/loadbalancing/config/CustomLoadBalancerConfiguration.java b/cloud-loadbalancing-webflux/src/main/java/com/example/cloud/loadbalancing/config/CustomLoadBalancerConfiguration.java new file mode 100644 index 00000000..cc529519 --- /dev/null +++ b/cloud-loadbalancing-webflux/src/main/java/com/example/cloud/loadbalancing/config/CustomLoadBalancerConfiguration.java @@ -0,0 +1,47 @@ +package com.example.cloud.loadbalancing.config; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +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.context.annotation.Bean; + +public class CustomLoadBalancerConfiguration { + + @Bean + LoadBalancerLifecycle loadBalancerLifecycle() { + return new TestLoadBalancerLifecycle(); + } + + static class TestLoadBalancerLifecycle implements LoadBalancerLifecycle { + + private static final Log LOG = LogFactory.getLog(TestLoadBalancerLifecycle.class); + + @Override + public void onStart(Request request) { + if (LOG.isInfoEnabled()) { + LOG.info("On Start: " + request); + } + } + + @Override + public void onStartRequest(Request request, Response lbResponse) { + if (LOG.isInfoEnabled()) { + LOG.info("On Start Request: " + request + ", LB response: " + lbResponse); + } + } + + @Override + public void onComplete(CompletionContext completionContext) { + if (LOG.isInfoEnabled()) { + LOG.info("On Complete: " + completionContext); + } + } + + } + +} diff --git a/cloud-loadbalancing-webflux/src/main/java/com/example/cloud/loadbalancing/config/WebClientConfig.java b/cloud-loadbalancing-webflux/src/main/java/com/example/cloud/loadbalancing/config/WebClientConfig.java new file mode 100644 index 00000000..962c94dd --- /dev/null +++ b/cloud-loadbalancing-webflux/src/main/java/com/example/cloud/loadbalancing/config/WebClientConfig.java @@ -0,0 +1,19 @@ +package com.example.cloud.loadbalancing.config; + +import org.springframework.cloud.client.loadbalancer.LoadBalanced; +import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.reactive.function.client.WebClient; + +@Configuration +@LoadBalancerClient(name = "test-service", configuration = CustomLoadBalancerConfiguration.class) +public class WebClientConfig { + + @LoadBalanced + @Bean + WebClient.Builder webClientBuilder() { + return WebClient.builder(); + } + +} diff --git a/cloud-loadbalancing-webflux/src/main/java/com/example/cloud/loadbalancing/service/LoadBalancerClientTestService.java b/cloud-loadbalancing-webflux/src/main/java/com/example/cloud/loadbalancing/service/LoadBalancerClientTestService.java new file mode 100644 index 00000000..6bd2d3c1 --- /dev/null +++ b/cloud-loadbalancing-webflux/src/main/java/com/example/cloud/loadbalancing/service/LoadBalancerClientTestService.java @@ -0,0 +1,38 @@ +package com.example.cloud.loadbalancing.service; + +import com.example.cloud.loadbalancing.client.DemoServiceClient; +import com.example.cloud.loadbalancing.client.TestServiceClient; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.stereotype.Component; + +@Component +public class LoadBalancerClientTestService { + + private static final Log LOG = LogFactory.getLog(LoadBalancerClientTestService.class); + + private final TestServiceClient testServiceClient; + + private final DemoServiceClient demoServiceClient; + + public LoadBalancerClientTestService(TestServiceClient testServiceClient, DemoServiceClient demoServiceClient) { + this.testServiceClient = testServiceClient; + this.demoServiceClient = demoServiceClient; + + } + + public void callServices() { + testServiceClient.test().subscribe(responseBody -> { + if (LOG.isInfoEnabled()) { + LOG.info(responseBody); + } + }); + demoServiceClient.demo().subscribe(responseBody -> { + if (LOG.isInfoEnabled()) { + LOG.info(responseBody); + } + }); + } + +} diff --git a/cloud-loadbalancing-webflux/src/main/resources/application.yml b/cloud-loadbalancing-webflux/src/main/resources/application.yml new file mode 100644 index 00000000..11a40b92 --- /dev/null +++ b/cloud-loadbalancing-webflux/src/main/resources/application.yml @@ -0,0 +1,16 @@ +spring: + application: + name: loadbalancer-client + cloud: + loadbalancer: + eager-load: + clients: + - demo-service + discovery: + client: + simple: + instances: + test-service: + - uri: http://${TEST-SERVICE_HOST:localhost}:${TEST-SERVICE_PORT_8081:8081} + demo-service: + - uri: http://${DEMO-SERVICE_HOST:localhost}:${DEMO-SERVICE_PORT_8082:8082} diff --git a/settings.gradle b/settings.gradle index fb30ed19..4ad6c887 100644 --- a/settings.gradle +++ b/settings.gradle @@ -44,6 +44,7 @@ include "cloud-discovery-consul" include "cloud-discovery-zookeeper" include "cloud-function-web" include "cloud-function-webflux" +include 'cloud-loadbalancing-webflux' include "cloud-stream-kafka" include "cloud-stream-rabbit" include "cloud-task"