From 4c9fb448271e1e52b1d4edb806a05e4d671335d7 Mon Sep 17 00:00:00 2001 From: pablo gonzalez granados Date: Mon, 14 Mar 2022 17:25:01 +0100 Subject: [PATCH 1/3] Add baremetal stork/consul scenario --- README.md | 12 ++ pom.xml | 1 + service-discovery/stork-consul/pom.xml | 63 ++++++++ .../quarkus/ts/stork/MyBackendPongProxy.java | 19 +++ .../quarkus/ts/stork/MyBackendPungProxy.java | 19 +++ .../io/quarkus/ts/stork/PingResource.java | 37 +++++ .../quarkus/ts/stork/PongReplicaResource.java | 46 ++++++ .../io/quarkus/ts/stork/PongResource.java | 45 ++++++ .../io/quarkus/ts/stork/PungResource.java | 41 ++++++ .../src/main/resources/application.properties | 1 + .../ts/stork/StorkServiceDiscoveryIT.java | 135 ++++++++++++++++++ 11 files changed, 419 insertions(+) create mode 100644 service-discovery/stork-consul/pom.xml create mode 100644 service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/MyBackendPongProxy.java create mode 100644 service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/MyBackendPungProxy.java create mode 100644 service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PingResource.java create mode 100644 service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PongReplicaResource.java create mode 100644 service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PongResource.java create mode 100644 service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PungResource.java create mode 100644 service-discovery/stork-consul/src/main/resources/application.properties create mode 100644 service-discovery/stork-consul/src/test/java/io/quarkus/ts/stork/StorkServiceDiscoveryIT.java diff --git a/README.md b/README.md index 0e9390a78..b4053c251 100644 --- a/README.md +++ b/README.md @@ -651,6 +651,18 @@ this.router.get("/secured") * authZ::authorize: custom AuthZ(authorization) provider. * secure.helloWorld(rc): actual http endpoint (Rest layer). +### Service-discovery/stork-consul + +Verifies Stork-consul integration in order to provide service discovering and round-robin load balancing between services + +`StorkServiceDiscoveryIT` scenario deploys four services: +* Pung: is a simple endpoint that returns "pung" as a string +* Pong: is a simple endpoint that returns "pong" as a string +* PongReplica: is a "Pong service" replica, that is deployed in another physical service +* Ping: is the main client microservice that will use `pung` and `pong` (Pong and PongReplica) services. The service +discovery will be done by Stork, and the request dispatching between "pong" services is going to be done by Stork load balancer. + + ### `monitoring/microprofile` diff --git a/pom.xml b/pom.xml index a8628341f..0e8572830 100644 --- a/pom.xml +++ b/pom.xml @@ -349,6 +349,7 @@ docker-build javaee-like-getting-started scaling + service-discovery/stork-consul lifecycle-application external-applications scheduling/quartz diff --git a/service-discovery/stork-consul/pom.xml b/service-discovery/stork-consul/pom.xml new file mode 100644 index 000000000..f6795e3fd --- /dev/null +++ b/service-discovery/stork-consul/pom.xml @@ -0,0 +1,63 @@ + + + 4.0.0 + + io.quarkus.ts.qe + parent + 1.0.0-SNAPSHOT + ../.. + + stork + jar + Quarkus QE TS: stork-consul + + + io.smallrye.stork + stork-service-discovery-consul + + + io.smallrye.reactive + smallrye-mutiny-vertx-consul-client + + + io.quarkus + quarkus-reactive-routes + + + io.quarkus + quarkus-rest-client-reactive + + + io.quarkus.qe + quarkus-test-service-consul + test + + + + + + skip-tests-on-windows + + + windows + + + + + + maven-surefire-plugin + + true + + + + maven-failsafe-plugin + + true + + + + + + + diff --git a/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/MyBackendPongProxy.java b/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/MyBackendPongProxy.java new file mode 100644 index 000000000..b54d474b7 --- /dev/null +++ b/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/MyBackendPongProxy.java @@ -0,0 +1,19 @@ +package io.quarkus.ts.stork; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +import io.smallrye.mutiny.Uni; + +@Path("/pong") +@RegisterRestClient(baseUri = "stork://pong") +public interface MyBackendPongProxy { + @GET + @Produces(MediaType.TEXT_PLAIN) + @Path("/") + Uni get(); +} diff --git a/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/MyBackendPungProxy.java b/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/MyBackendPungProxy.java new file mode 100644 index 000000000..83af3f220 --- /dev/null +++ b/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/MyBackendPungProxy.java @@ -0,0 +1,19 @@ +package io.quarkus.ts.stork; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +import io.smallrye.mutiny.Uni; + +@Path("/pung") +@RegisterRestClient(baseUri = "stork://pung") +public interface MyBackendPungProxy { + @GET + @Produces(MediaType.TEXT_PLAIN) + @Path("/") + Uni get(); +} diff --git a/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PingResource.java b/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PingResource.java new file mode 100644 index 000000000..6ce0dd36d --- /dev/null +++ b/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PingResource.java @@ -0,0 +1,37 @@ +package io.quarkus.ts.stork; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; + +import org.eclipse.microprofile.rest.client.inject.RestClient; + +import io.quarkus.vertx.web.Route; +import io.quarkus.vertx.web.RouteBase; +import io.smallrye.mutiny.Uni; +import io.vertx.ext.web.RoutingContext; + +@RouteBase(path = "/ping", produces = MediaType.TEXT_PLAIN) +public class PingResource { + + @RestClient + MyBackendPongProxy pongService; + + @RestClient + MyBackendPungProxy pungService; + + @Route(methods = Route.HttpMethod.GET, path = "/pung") + public Uni pung(RoutingContext context) { + return formatResponse(pungService.get()); + } + + @Route(methods = Route.HttpMethod.GET, path = "/pong") + public Uni pong(RoutingContext context) { + return formatResponse(pongService.get()); + } + + private Uni formatResponse(Uni response) { + return response + .onFailure().transform(error -> new WebApplicationException(error.getMessage())) + .map(resp -> "ping-" + resp); + } +} diff --git a/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PongReplicaResource.java b/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PongReplicaResource.java new file mode 100644 index 000000000..9c9c062f5 --- /dev/null +++ b/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PongReplicaResource.java @@ -0,0 +1,46 @@ +package io.quarkus.ts.stork; + +import static io.quarkus.ts.stork.PongResource.PONG_SERVICE_NAME; + +import javax.enterprise.event.Observes; +import javax.ws.rs.core.MediaType; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import io.quarkus.runtime.StartupEvent; +import io.quarkus.vertx.web.Route; +import io.quarkus.vertx.web.RouteBase; +import io.smallrye.mutiny.Uni; +import io.vertx.ext.consul.ConsulClientOptions; +import io.vertx.ext.consul.ServiceOptions; +import io.vertx.mutiny.core.Vertx; +import io.vertx.mutiny.ext.consul.ConsulClient; + +@RouteBase(path = "/pong", produces = MediaType.TEXT_PLAIN) +public class PongReplicaResource { + + public static final String DEFAULT_PONG_REPLICA_RESPONSE = "pongReplica"; + + @ConfigProperty(name = "stork.pong-replica.service-discovery.consul-host") + String host; + @ConfigProperty(name = "stork.pong-replica.service-discovery.consul-port") + String port; + @ConfigProperty(name = "pong-replica-service-port") + String pongPort; + @ConfigProperty(name = "pong-replica-service-host") + String pongHost; + + public void init(@Observes StartupEvent ev, Vertx vertx) { + ConsulClient client = ConsulClient.create(vertx, + new ConsulClientOptions().setHost(host).setPort(Integer.parseInt(port))); + + client.registerServiceAndAwait( + new ServiceOptions().setPort(Integer.parseInt(pongPort)).setAddress(pongHost).setName(PONG_SERVICE_NAME) + .setId("pongReplica")); + } + + @Route(path = "/", methods = Route.HttpMethod.GET) + public Uni pong() { + return Uni.createFrom().item(DEFAULT_PONG_REPLICA_RESPONSE); + } +} diff --git a/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PongResource.java b/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PongResource.java new file mode 100644 index 000000000..cfecc8d41 --- /dev/null +++ b/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PongResource.java @@ -0,0 +1,45 @@ +package io.quarkus.ts.stork; + +import javax.enterprise.event.Observes; +import javax.ws.rs.core.MediaType; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import io.quarkus.runtime.StartupEvent; +import io.quarkus.vertx.web.Route; +import io.quarkus.vertx.web.RouteBase; +import io.smallrye.mutiny.Uni; +import io.vertx.ext.consul.ConsulClientOptions; +import io.vertx.ext.consul.ServiceOptions; +import io.vertx.mutiny.core.Vertx; +import io.vertx.mutiny.ext.consul.ConsulClient; + +@RouteBase(path = "/pong", produces = MediaType.TEXT_PLAIN) +public class PongResource { + + public static final String PONG_SERVICE_NAME = "pong"; + public static final String DEFAULT_PONG_RESPONSE = "pong"; + + @ConfigProperty(name = "stork.pong.service-discovery.consul-host") + String host; + @ConfigProperty(name = "stork.pong.service-discovery.consul-port") + String port; + @ConfigProperty(name = "pong-service-port") + String pongPort; + @ConfigProperty(name = "pong-service-host") + String pongHost; + + public void init(@Observes StartupEvent ev, Vertx vertx) { + ConsulClient client = ConsulClient.create(vertx, + new ConsulClientOptions().setHost(host).setPort(Integer.parseInt(port))); + + client.registerServiceAndAwait( + new ServiceOptions().setPort(Integer.parseInt(pongPort)).setAddress(pongHost).setName(PONG_SERVICE_NAME) + .setId("pong")); + } + + @Route(path = "/", methods = Route.HttpMethod.GET) + public Uni pong() { + return Uni.createFrom().item(DEFAULT_PONG_RESPONSE); + } +} diff --git a/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PungResource.java b/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PungResource.java new file mode 100644 index 000000000..c79927a08 --- /dev/null +++ b/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PungResource.java @@ -0,0 +1,41 @@ +package io.quarkus.ts.stork; + +import javax.enterprise.event.Observes; +import javax.ws.rs.core.MediaType; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import io.quarkus.runtime.StartupEvent; +import io.quarkus.vertx.web.Route; +import io.quarkus.vertx.web.RouteBase; +import io.smallrye.mutiny.Uni; +import io.vertx.ext.consul.ConsulClientOptions; +import io.vertx.ext.consul.ServiceOptions; +import io.vertx.mutiny.core.Vertx; +import io.vertx.mutiny.ext.consul.ConsulClient; + +@RouteBase(path = "/pung", produces = MediaType.TEXT_PLAIN) +public class PungResource { + + @ConfigProperty(name = "stork.pung.service-discovery.consul-host") + String host; + @ConfigProperty(name = "stork.pung.service-discovery.consul-port") + String port; + @ConfigProperty(name = "pung-service-port") + String pungPort; + @ConfigProperty(name = "pung-service-host") + String pungHost; + + public void init(@Observes StartupEvent ev, Vertx vertx) { + ConsulClient client = ConsulClient.create(vertx, + new ConsulClientOptions().setHost(host).setPort(Integer.parseInt(port))); + + client.registerServiceAndAwait( + new ServiceOptions().setPort(Integer.parseInt(pungPort)).setAddress(pungHost).setName("pung").setId("pung")); + } + + @Route(path = "/", methods = Route.HttpMethod.GET) + public Uni pung() { + return Uni.createFrom().item("pung"); + } +} diff --git a/service-discovery/stork-consul/src/main/resources/application.properties b/service-discovery/stork-consul/src/main/resources/application.properties new file mode 100644 index 000000000..9e4640c38 --- /dev/null +++ b/service-discovery/stork-consul/src/main/resources/application.properties @@ -0,0 +1 @@ +# application properties should be here diff --git a/service-discovery/stork-consul/src/test/java/io/quarkus/ts/stork/StorkServiceDiscoveryIT.java b/service-discovery/stork-consul/src/test/java/io/quarkus/ts/stork/StorkServiceDiscoveryIT.java new file mode 100644 index 000000000..9cdf3128d --- /dev/null +++ b/service-discovery/stork-consul/src/test/java/io/quarkus/ts/stork/StorkServiceDiscoveryIT.java @@ -0,0 +1,135 @@ +package io.quarkus.ts.stork; + +import static io.quarkus.ts.stork.PongReplicaResource.DEFAULT_PONG_REPLICA_RESPONSE; +import static io.quarkus.ts.stork.PongResource.DEFAULT_PONG_RESPONSE; +import static org.hamcrest.CoreMatchers.hasItems; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.IOException; +import java.net.ServerSocket; +import java.util.HashMap; +import java.util.Map; + +import org.apache.http.HttpStatus; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.bootstrap.ConsulService; +import io.quarkus.test.bootstrap.RestService; +import io.quarkus.test.scenarios.QuarkusScenario; +import io.quarkus.test.services.Container; +import io.quarkus.test.services.QuarkusApplication; + +@QuarkusScenario +public class StorkServiceDiscoveryIT { + + @Container(image = "${consul.image}", expectedLog = "Synced node info", port = 8500) + static ConsulService consul = new ConsulService(); + + private static final String PREFIX = "ping-"; + private static final String PUNG_PORT = getAvailablePort(); + private static final String PONG_PORT = getAvailablePort(); + private static final String PONG_REPLICA_PORT = getAvailablePort(); + + @QuarkusApplication(classes = PungResource.class) + static RestService pungService = new RestService() + .withProperty("quarkus.http.port", PUNG_PORT) + .withProperty("pung-service-port", PUNG_PORT) + .withProperty("pung-service-host", "localhost") + .withProperty("stork.pung.service-discovery", "consul") + .withProperty("stork.pung.service-discovery.consul-port", () -> String.valueOf(consul.getPort())) + .withProperty("stork.pung.service-discovery.consul-host", () -> getConsultEndpoint(consul.getConsulEndpoint())); + + @QuarkusApplication(classes = PongResource.class) + static RestService pongService = new RestService() + .withProperty("quarkus.http.port", PONG_PORT) + .withProperty("pong-service-port", PONG_PORT) + .withProperty("pong-service-host", "localhost") + .withProperty("stork.pong.service-discovery.refresh-period", "1") + .withProperty("stork.pong.service-discovery", "consul") + .withProperty("stork.pong.service-discovery.consul-port", () -> String.valueOf(consul.getPort())) + .withProperty("stork.pong.service-discovery.consul-host", () -> getConsultEndpoint(consul.getConsulEndpoint())); + + @QuarkusApplication(classes = PongReplicaResource.class) + static RestService pongReplicaService = new RestService() + .withProperty("quarkus.http.port", PONG_REPLICA_PORT) + .withProperty("pong-replica-service-port", PONG_REPLICA_PORT) + .withProperty("pong-replica-service-host", "localhost") + .withProperty("stork.pong-replica.service-discovery.refresh-period", "1") + .withProperty("stork.pong-replica.service-discovery", "consul") + .withProperty("stork.pong-replica.service-discovery.consul-port", () -> String.valueOf(consul.getPort())) + .withProperty("stork.pong-replica.service-discovery.consul-host", + () -> getConsultEndpoint(consul.getConsulEndpoint())); + + @QuarkusApplication(classes = { PingResource.class, MyBackendPungProxy.class, MyBackendPongProxy.class }) + static RestService mainPingService = new RestService() + .withProperty("stork.pung.service-discovery", "consul") + .withProperty("stork.pung.service-discovery.consul-port", () -> String.valueOf(consul.getPort())) + .withProperty("stork.pung.service-discovery.consul-host", () -> getConsultEndpoint(consul.getConsulEndpoint())) + .withProperty("stork.pong-replica.service-discovery", "consul") + .withProperty("stork.pong-replica.service-discovery.refresh-period", "1") + .withProperty("stork.pong-replica.load-balancer", "round-robin") + .withProperty("stork.pong-replica.service-discovery.consul-port", () -> String.valueOf(consul.getPort())) + .withProperty("stork.pong-replica.service-discovery.consul-host", + () -> getConsultEndpoint(consul.getConsulEndpoint())) + .withProperty("stork.pong.service-discovery", "consul") + .withProperty("stork.pong.service-discovery.refresh-period", "1") + .withProperty("stork.pong.load-balancer", "round-robin") + .withProperty("stork.pong.service-discovery.consul-port", () -> String.valueOf(consul.getPort())) + .withProperty("stork.pong.service-discovery.consul-host", () -> getConsultEndpoint(consul.getConsulEndpoint())); + + @Test + public void invokeServiceByName() { + String response = makePingCall("pung"); + assertThat("Service discovery by name fail.", PREFIX + "pung", is(response)); + } + + @Test + public void storkLoadBalancerByRoundRobin() { + Map uniqueResp = new HashMap<>(); + final int requestAmount = 100; + final int roundRobinError = (requestAmount / 2) - 1; + for (int i = 0; i < requestAmount; i++) { + String response = makePingCall("pong"); + if (uniqueResp.containsKey(response)) { + uniqueResp.put(response, uniqueResp.get(response) + 1); + } else { + uniqueResp.put(response, 1); + } + } + + Assertions.assertEquals(uniqueResp.size(), 2, "Only 2 services should response"); + assertThat("Unexpected service names", uniqueResp.keySet(), + hasItems(PREFIX + DEFAULT_PONG_RESPONSE, PREFIX + DEFAULT_PONG_REPLICA_RESPONSE)); + assertThat("Load balancer doesn't behaves as round-robin", uniqueResp.get(PREFIX + DEFAULT_PONG_RESPONSE), + is(greaterThanOrEqualTo(roundRobinError))); + assertThat("Load balancer doesn't behaves as round-robin", uniqueResp.get(PREFIX + DEFAULT_PONG_REPLICA_RESPONSE), + is(greaterThanOrEqualTo(roundRobinError))); + } + + private String makePingCall(String subPath) { + return mainPingService + .given() + .get("/ping/" + subPath).then() + .statusCode(HttpStatus.SC_OK) + .extract().body().asString(); + } + + private static String getConsultEndpoint(String endpoint) { + return endpoint.replaceFirst(":\\d+", ""); + } + + private static String getAvailablePort() { + int port = 0; + try (ServerSocket serverSocket = new ServerSocket(port)) { + port = serverSocket.getLocalPort(); + } catch (IOException e) { + fail("no free port available."); + } + + return String.valueOf(port); + } +} From 40b080496c024c433bb377c6581c588b9372c7f1 Mon Sep 17 00:00:00 2001 From: pablo gonzalez granados Date: Fri, 18 Mar 2022 09:58:49 +0100 Subject: [PATCH 2/3] Stork k8s extension coverage --- service-discovery/stork-consul/pom.xml | 6 +- .../quarkus/ts/stork/PongReplicaResource.java | 2 +- .../io/quarkus/ts/stork/PongResource.java | 2 +- .../io/quarkus/ts/stork/PungResource.java | 23 +++--- .../OpenShiftStorkServiceDiscoveryIT.java | 80 +++++++++++++++++++ .../ts/stork/StorkServiceDiscoveryIT.java | 4 +- .../src/test/resources/cluster-role.yaml | 9 +++ .../src/test/resources/fabric8-rbac.yaml | 14 ++++ .../src/test/resources/test.properties | 1 + 9 files changed, 127 insertions(+), 14 deletions(-) create mode 100644 service-discovery/stork-consul/src/test/java/io/quarkus/ts/stork/OpenShiftStorkServiceDiscoveryIT.java create mode 100644 service-discovery/stork-consul/src/test/resources/cluster-role.yaml create mode 100644 service-discovery/stork-consul/src/test/resources/fabric8-rbac.yaml create mode 100644 service-discovery/stork-consul/src/test/resources/test.properties diff --git a/service-discovery/stork-consul/pom.xml b/service-discovery/stork-consul/pom.xml index f6795e3fd..75db3664d 100644 --- a/service-discovery/stork-consul/pom.xml +++ b/service-discovery/stork-consul/pom.xml @@ -9,7 +9,7 @@ stork jar - Quarkus QE TS: stork-consul + Quarkus QE TS: Service-discovery: Stork-consul io.smallrye.stork @@ -19,6 +19,10 @@ io.smallrye.reactive smallrye-mutiny-vertx-consul-client + + io.smallrye.stork + stork-service-discovery-kubernetes + io.quarkus quarkus-reactive-routes diff --git a/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PongReplicaResource.java b/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PongReplicaResource.java index 9c9c062f5..12860dc2f 100644 --- a/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PongReplicaResource.java +++ b/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PongReplicaResource.java @@ -19,7 +19,7 @@ @RouteBase(path = "/pong", produces = MediaType.TEXT_PLAIN) public class PongReplicaResource { - public static final String DEFAULT_PONG_REPLICA_RESPONSE = "pongReplica"; + private static final String DEFAULT_PONG_REPLICA_RESPONSE = "pongReplica"; @ConfigProperty(name = "stork.pong-replica.service-discovery.consul-host") String host; diff --git a/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PongResource.java b/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PongResource.java index cfecc8d41..30d052d73 100644 --- a/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PongResource.java +++ b/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PongResource.java @@ -18,7 +18,7 @@ public class PongResource { public static final String PONG_SERVICE_NAME = "pong"; - public static final String DEFAULT_PONG_RESPONSE = "pong"; + private static final String DEFAULT_PONG_RESPONSE = "pong"; @ConfigProperty(name = "stork.pong.service-discovery.consul-host") String host; diff --git a/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PungResource.java b/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PungResource.java index c79927a08..60f588be2 100644 --- a/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PungResource.java +++ b/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PungResource.java @@ -17,21 +17,26 @@ @RouteBase(path = "/pung", produces = MediaType.TEXT_PLAIN) public class PungResource { - @ConfigProperty(name = "stork.pung.service-discovery.consul-host") + @ConfigProperty(name = "stork.pung.service-discovery.consul-host", defaultValue = "localhost") String host; - @ConfigProperty(name = "stork.pung.service-discovery.consul-port") + @ConfigProperty(name = "stork.pung.service-discovery.consul-port", defaultValue = "8500") String port; - @ConfigProperty(name = "pung-service-port") + @ConfigProperty(name = "pung-service-port", defaultValue = "8080") String pungPort; - @ConfigProperty(name = "pung-service-host") + @ConfigProperty(name = "pung-service-host", defaultValue = "localhost") String pungHost; + @ConfigProperty(name = "stork.pung.service-discovery", defaultValue = "consul") + String serviceDiscoveryType; public void init(@Observes StartupEvent ev, Vertx vertx) { - ConsulClient client = ConsulClient.create(vertx, - new ConsulClientOptions().setHost(host).setPort(Integer.parseInt(port))); - - client.registerServiceAndAwait( - new ServiceOptions().setPort(Integer.parseInt(pungPort)).setAddress(pungHost).setName("pung").setId("pung")); + if (serviceDiscoveryType.equalsIgnoreCase("consul")) { + ConsulClient client = ConsulClient.create(vertx, + new ConsulClientOptions().setHost(host).setPort(Integer.parseInt(port))); + + client.registerServiceAndAwait( + new ServiceOptions().setPort(Integer.parseInt(pungPort)).setAddress(pungHost).setName("pung") + .setId("pung")); + } } @Route(path = "/", methods = Route.HttpMethod.GET) diff --git a/service-discovery/stork-consul/src/test/java/io/quarkus/ts/stork/OpenShiftStorkServiceDiscoveryIT.java b/service-discovery/stork-consul/src/test/java/io/quarkus/ts/stork/OpenShiftStorkServiceDiscoveryIT.java new file mode 100644 index 000000000..f14c11b50 --- /dev/null +++ b/service-discovery/stork-consul/src/test/java/io/quarkus/ts/stork/OpenShiftStorkServiceDiscoveryIT.java @@ -0,0 +1,80 @@ +package io.quarkus.ts.stork; + +import static java.util.regex.Pattern.quote; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; + +import javax.inject.Inject; + +import org.apache.http.HttpStatus; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.bootstrap.RestService; +import io.quarkus.test.bootstrap.inject.OpenShiftClient; +import io.quarkus.test.scenarios.OpenShiftScenario; +import io.quarkus.test.services.QuarkusApplication; +import io.quarkus.test.utils.FileUtils; + +@OpenShiftScenario +public class OpenShiftStorkServiceDiscoveryIT { + + private static final String PREFIX = "ping-"; + private static final String CLUSTER_ROLE_FILE_NAME = "cluster-role.yaml"; + private static final String RBAC_FILE_NAME = "fabric8-rbac.yaml"; + + @Inject + static OpenShiftClient openshift; + + @QuarkusApplication(classes = PungResource.class) + static RestService pung = new RestService() + .withProperty("stork.pung.service-discovery", "kubernetes") + .withProperty("stork.pung.service-discovery.k8s-namespace", "all"); + + @QuarkusApplication(classes = { PingResource.class, MyBackendPungProxy.class, MyBackendPongProxy.class }) + static RestService ping = new RestService().onPreStart(app -> setupClusterRoles()) + .withProperty("stork.pung.service-discovery", "kubernetes") + .withProperty("stork.pung.service-discovery.k8s-namespace", "all"); + + @AfterAll + public static void tearDown() { + openshift.delete(Paths.get(new File("target/test-classes/" + CLUSTER_ROLE_FILE_NAME).toURI())); + openshift.delete(Paths.get(new File("target/test-classes/" + RBAC_FILE_NAME).toURI())); + } + + @Test + public void invokeServiceByName() { + String response = makePingCall("pung"); + assertThat("Service discovery by name fail.", PREFIX + "pung", is(response)); + } + + @Test + public void storkLoadBalancerByRoundRobin() { + + } + + private String makePingCall(String subPath) { + return ping + .given() + .get("/ping/" + subPath).then() + .statusCode(HttpStatus.SC_OK) + .extract().body().asString(); + } + + /** + * setup `stork-service-discovery-kubernetes` - roles and roles bindings (required) + */ + private static void setupClusterRoles() { + String namespace = openshift.project(); + openshift.apply(Paths.get(new File("target/test-classes/" + CLUSTER_ROLE_FILE_NAME).toURI())); + String content = FileUtils.loadFile(new File("target/test-classes/" + RBAC_FILE_NAME)) + .replaceAll(quote("${NAMESPACE}"), namespace); + Path target = FileUtils.copyContentTo(content, new File("target/test-classes/" + RBAC_FILE_NAME).toPath()); + openshift.apply(target); + } +} \ No newline at end of file diff --git a/service-discovery/stork-consul/src/test/java/io/quarkus/ts/stork/StorkServiceDiscoveryIT.java b/service-discovery/stork-consul/src/test/java/io/quarkus/ts/stork/StorkServiceDiscoveryIT.java index 9cdf3128d..597f0cfe5 100644 --- a/service-discovery/stork-consul/src/test/java/io/quarkus/ts/stork/StorkServiceDiscoveryIT.java +++ b/service-discovery/stork-consul/src/test/java/io/quarkus/ts/stork/StorkServiceDiscoveryIT.java @@ -1,7 +1,5 @@ package io.quarkus.ts.stork; -import static io.quarkus.ts.stork.PongReplicaResource.DEFAULT_PONG_REPLICA_RESPONSE; -import static io.quarkus.ts.stork.PongResource.DEFAULT_PONG_RESPONSE; import static org.hamcrest.CoreMatchers.hasItems; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.greaterThanOrEqualTo; @@ -30,6 +28,8 @@ public class StorkServiceDiscoveryIT { static ConsulService consul = new ConsulService(); private static final String PREFIX = "ping-"; + private static final String DEFAULT_PONG_REPLICA_RESPONSE = "pongReplica"; + private static final String DEFAULT_PONG_RESPONSE = "pong"; private static final String PUNG_PORT = getAvailablePort(); private static final String PONG_PORT = getAvailablePort(); private static final String PONG_REPLICA_PORT = getAvailablePort(); diff --git a/service-discovery/stork-consul/src/test/resources/cluster-role.yaml b/service-discovery/stork-consul/src/test/resources/cluster-role.yaml new file mode 100644 index 000000000..77598d6d4 --- /dev/null +++ b/service-discovery/stork-consul/src/test/resources/cluster-role.yaml @@ -0,0 +1,9 @@ +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + namespace: default + name: endpoints-reader +rules: + - apiGroups: [""] # "" indicates the core API group + resources: ["endpoints"] + verbs: ["get", "watch", "list"] \ No newline at end of file diff --git a/service-discovery/stork-consul/src/test/resources/fabric8-rbac.yaml b/service-discovery/stork-consul/src/test/resources/fabric8-rbac.yaml new file mode 100644 index 000000000..273fa334a --- /dev/null +++ b/service-discovery/stork-consul/src/test/resources/fabric8-rbac.yaml @@ -0,0 +1,14 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: fabric8-rbac +subjects: + - kind: ServiceAccount + # Reference to upper's `metadata.name` + name: default + # Reference to upper's `metadata.namespace` + namespace: "${NAMESPACE}" +roleRef: + kind: ClusterRole + name: cluster-admin + apiGroup: rbac.authorization.k8s.io \ No newline at end of file diff --git a/service-discovery/stork-consul/src/test/resources/test.properties b/service-discovery/stork-consul/src/test/resources/test.properties new file mode 100644 index 000000000..87eca67da --- /dev/null +++ b/service-discovery/stork-consul/src/test/resources/test.properties @@ -0,0 +1 @@ +ts.pung.openshift.use-internal-service-as-url=true \ No newline at end of file From fb8ec0b6fd6816aab56b82db5d5662445985dc7e Mon Sep 17 00:00:00 2001 From: pablo gonzalez granados Date: Fri, 18 Mar 2022 16:33:45 +0100 Subject: [PATCH 3/3] Add Stork + k8s coverage - default service discovery - default load balncer --- README.md | 6 +- pom.xml | 2 +- .../io/quarkus/ts/stork/PongResource.java | 45 ------- .../src/main/resources/application.properties | 1 - .../OpenShiftStorkServiceDiscoveryIT.java | 80 ------------- .../src/test/resources/test.properties | 1 - .../{stork-consul => stork}/pom.xml | 2 +- .../quarkus/ts/stork/MyBackendPongProxy.java | 3 +- .../quarkus/ts/stork/MyBackendPungProxy.java | 0 .../io/quarkus/ts/stork/PingResource.java | 22 ++-- .../quarkus/ts/stork/PongReplicaResource.java | 24 ++-- .../io/quarkus/ts/stork/PongResource.java | 54 +++++++++ .../io/quarkus/ts/stork/PungResource.java | 0 .../src/main/resources/application.properties | 3 + .../OpenShiftStorkServiceDiscoveryIT.java | 111 ++++++++++++++++++ .../ts/stork/StorkServiceDiscoveryIT.java | 14 +-- .../src/test/resources/cluster-role.yaml | 2 +- .../src/test/resources/fabric8-rbac.yaml | 1 - .../stork/src/test/resources/test.properties | 2 + 19 files changed, 209 insertions(+), 164 deletions(-) delete mode 100644 service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PongResource.java delete mode 100644 service-discovery/stork-consul/src/main/resources/application.properties delete mode 100644 service-discovery/stork-consul/src/test/java/io/quarkus/ts/stork/OpenShiftStorkServiceDiscoveryIT.java delete mode 100644 service-discovery/stork-consul/src/test/resources/test.properties rename service-discovery/{stork-consul => stork}/pom.xml (97%) rename service-discovery/{stork-consul => stork}/src/main/java/io/quarkus/ts/stork/MyBackendPongProxy.java (82%) rename service-discovery/{stork-consul => stork}/src/main/java/io/quarkus/ts/stork/MyBackendPungProxy.java (100%) rename service-discovery/{stork-consul => stork}/src/main/java/io/quarkus/ts/stork/PingResource.java (56%) rename service-discovery/{stork-consul => stork}/src/main/java/io/quarkus/ts/stork/PongReplicaResource.java (58%) create mode 100644 service-discovery/stork/src/main/java/io/quarkus/ts/stork/PongResource.java rename service-discovery/{stork-consul => stork}/src/main/java/io/quarkus/ts/stork/PungResource.java (100%) create mode 100644 service-discovery/stork/src/main/resources/application.properties create mode 100644 service-discovery/stork/src/test/java/io/quarkus/ts/stork/OpenShiftStorkServiceDiscoveryIT.java rename service-discovery/{stork-consul => stork}/src/test/java/io/quarkus/ts/stork/StorkServiceDiscoveryIT.java (95%) rename service-discovery/{stork-consul => stork}/src/test/resources/cluster-role.yaml (88%) rename service-discovery/{stork-consul => stork}/src/test/resources/fabric8-rbac.yaml (86%) create mode 100644 service-discovery/stork/src/test/resources/test.properties diff --git a/README.md b/README.md index b4053c251..e47b11559 100644 --- a/README.md +++ b/README.md @@ -651,9 +651,9 @@ this.router.get("/secured") * authZ::authorize: custom AuthZ(authorization) provider. * secure.helloWorld(rc): actual http endpoint (Rest layer). -### Service-discovery/stork-consul +### Service-discovery/stork -Verifies Stork-consul integration in order to provide service discovering and round-robin load balancing between services +Verifies Stork integration in order to provide service discovering and round-robin load balancing between services `StorkServiceDiscoveryIT` scenario deploys four services: * Pung: is a simple endpoint that returns "pung" as a string @@ -662,8 +662,6 @@ Verifies Stork-consul integration in order to provide service discovering and ro * Ping: is the main client microservice that will use `pung` and `pong` (Pong and PongReplica) services. The service discovery will be done by Stork, and the request dispatching between "pong" services is going to be done by Stork load balancer. - - ### `monitoring/microprofile` Verifies combined usage of MicroProfile RestClient, Fault Tolerance and OpenTracing. diff --git a/pom.xml b/pom.xml index 0e8572830..36b8ee977 100644 --- a/pom.xml +++ b/pom.xml @@ -349,7 +349,7 @@ docker-build javaee-like-getting-started scaling - service-discovery/stork-consul + service-discovery/stork lifecycle-application external-applications scheduling/quartz diff --git a/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PongResource.java b/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PongResource.java deleted file mode 100644 index 30d052d73..000000000 --- a/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PongResource.java +++ /dev/null @@ -1,45 +0,0 @@ -package io.quarkus.ts.stork; - -import javax.enterprise.event.Observes; -import javax.ws.rs.core.MediaType; - -import org.eclipse.microprofile.config.inject.ConfigProperty; - -import io.quarkus.runtime.StartupEvent; -import io.quarkus.vertx.web.Route; -import io.quarkus.vertx.web.RouteBase; -import io.smallrye.mutiny.Uni; -import io.vertx.ext.consul.ConsulClientOptions; -import io.vertx.ext.consul.ServiceOptions; -import io.vertx.mutiny.core.Vertx; -import io.vertx.mutiny.ext.consul.ConsulClient; - -@RouteBase(path = "/pong", produces = MediaType.TEXT_PLAIN) -public class PongResource { - - public static final String PONG_SERVICE_NAME = "pong"; - private static final String DEFAULT_PONG_RESPONSE = "pong"; - - @ConfigProperty(name = "stork.pong.service-discovery.consul-host") - String host; - @ConfigProperty(name = "stork.pong.service-discovery.consul-port") - String port; - @ConfigProperty(name = "pong-service-port") - String pongPort; - @ConfigProperty(name = "pong-service-host") - String pongHost; - - public void init(@Observes StartupEvent ev, Vertx vertx) { - ConsulClient client = ConsulClient.create(vertx, - new ConsulClientOptions().setHost(host).setPort(Integer.parseInt(port))); - - client.registerServiceAndAwait( - new ServiceOptions().setPort(Integer.parseInt(pongPort)).setAddress(pongHost).setName(PONG_SERVICE_NAME) - .setId("pong")); - } - - @Route(path = "/", methods = Route.HttpMethod.GET) - public Uni pong() { - return Uni.createFrom().item(DEFAULT_PONG_RESPONSE); - } -} diff --git a/service-discovery/stork-consul/src/main/resources/application.properties b/service-discovery/stork-consul/src/main/resources/application.properties deleted file mode 100644 index 9e4640c38..000000000 --- a/service-discovery/stork-consul/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ -# application properties should be here diff --git a/service-discovery/stork-consul/src/test/java/io/quarkus/ts/stork/OpenShiftStorkServiceDiscoveryIT.java b/service-discovery/stork-consul/src/test/java/io/quarkus/ts/stork/OpenShiftStorkServiceDiscoveryIT.java deleted file mode 100644 index f14c11b50..000000000 --- a/service-discovery/stork-consul/src/test/java/io/quarkus/ts/stork/OpenShiftStorkServiceDiscoveryIT.java +++ /dev/null @@ -1,80 +0,0 @@ -package io.quarkus.ts.stork; - -import static java.util.regex.Pattern.quote; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; - -import java.io.File; -import java.nio.file.Path; -import java.nio.file.Paths; - -import javax.inject.Inject; - -import org.apache.http.HttpStatus; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; - -import io.quarkus.test.bootstrap.RestService; -import io.quarkus.test.bootstrap.inject.OpenShiftClient; -import io.quarkus.test.scenarios.OpenShiftScenario; -import io.quarkus.test.services.QuarkusApplication; -import io.quarkus.test.utils.FileUtils; - -@OpenShiftScenario -public class OpenShiftStorkServiceDiscoveryIT { - - private static final String PREFIX = "ping-"; - private static final String CLUSTER_ROLE_FILE_NAME = "cluster-role.yaml"; - private static final String RBAC_FILE_NAME = "fabric8-rbac.yaml"; - - @Inject - static OpenShiftClient openshift; - - @QuarkusApplication(classes = PungResource.class) - static RestService pung = new RestService() - .withProperty("stork.pung.service-discovery", "kubernetes") - .withProperty("stork.pung.service-discovery.k8s-namespace", "all"); - - @QuarkusApplication(classes = { PingResource.class, MyBackendPungProxy.class, MyBackendPongProxy.class }) - static RestService ping = new RestService().onPreStart(app -> setupClusterRoles()) - .withProperty("stork.pung.service-discovery", "kubernetes") - .withProperty("stork.pung.service-discovery.k8s-namespace", "all"); - - @AfterAll - public static void tearDown() { - openshift.delete(Paths.get(new File("target/test-classes/" + CLUSTER_ROLE_FILE_NAME).toURI())); - openshift.delete(Paths.get(new File("target/test-classes/" + RBAC_FILE_NAME).toURI())); - } - - @Test - public void invokeServiceByName() { - String response = makePingCall("pung"); - assertThat("Service discovery by name fail.", PREFIX + "pung", is(response)); - } - - @Test - public void storkLoadBalancerByRoundRobin() { - - } - - private String makePingCall(String subPath) { - return ping - .given() - .get("/ping/" + subPath).then() - .statusCode(HttpStatus.SC_OK) - .extract().body().asString(); - } - - /** - * setup `stork-service-discovery-kubernetes` - roles and roles bindings (required) - */ - private static void setupClusterRoles() { - String namespace = openshift.project(); - openshift.apply(Paths.get(new File("target/test-classes/" + CLUSTER_ROLE_FILE_NAME).toURI())); - String content = FileUtils.loadFile(new File("target/test-classes/" + RBAC_FILE_NAME)) - .replaceAll(quote("${NAMESPACE}"), namespace); - Path target = FileUtils.copyContentTo(content, new File("target/test-classes/" + RBAC_FILE_NAME).toPath()); - openshift.apply(target); - } -} \ No newline at end of file diff --git a/service-discovery/stork-consul/src/test/resources/test.properties b/service-discovery/stork-consul/src/test/resources/test.properties deleted file mode 100644 index 87eca67da..000000000 --- a/service-discovery/stork-consul/src/test/resources/test.properties +++ /dev/null @@ -1 +0,0 @@ -ts.pung.openshift.use-internal-service-as-url=true \ No newline at end of file diff --git a/service-discovery/stork-consul/pom.xml b/service-discovery/stork/pom.xml similarity index 97% rename from service-discovery/stork-consul/pom.xml rename to service-discovery/stork/pom.xml index 75db3664d..729075597 100644 --- a/service-discovery/stork-consul/pom.xml +++ b/service-discovery/stork/pom.xml @@ -9,7 +9,7 @@ stork jar - Quarkus QE TS: Service-discovery: Stork-consul + Quarkus QE TS: Service-discovery: Stork io.smallrye.stork diff --git a/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/MyBackendPongProxy.java b/service-discovery/stork/src/main/java/io/quarkus/ts/stork/MyBackendPongProxy.java similarity index 82% rename from service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/MyBackendPongProxy.java rename to service-discovery/stork/src/main/java/io/quarkus/ts/stork/MyBackendPongProxy.java index b54d474b7..e222e6518 100644 --- a/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/MyBackendPongProxy.java +++ b/service-discovery/stork/src/main/java/io/quarkus/ts/stork/MyBackendPongProxy.java @@ -6,6 +6,7 @@ import javax.ws.rs.core.MediaType; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; +import org.jboss.resteasy.reactive.RestResponse; import io.smallrye.mutiny.Uni; @@ -15,5 +16,5 @@ public interface MyBackendPongProxy { @GET @Produces(MediaType.TEXT_PLAIN) @Path("/") - Uni get(); + Uni> get(); } diff --git a/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/MyBackendPungProxy.java b/service-discovery/stork/src/main/java/io/quarkus/ts/stork/MyBackendPungProxy.java similarity index 100% rename from service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/MyBackendPungProxy.java rename to service-discovery/stork/src/main/java/io/quarkus/ts/stork/MyBackendPungProxy.java diff --git a/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PingResource.java b/service-discovery/stork/src/main/java/io/quarkus/ts/stork/PingResource.java similarity index 56% rename from service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PingResource.java rename to service-discovery/stork/src/main/java/io/quarkus/ts/stork/PingResource.java index 6ce0dd36d..4c6c0e35b 100644 --- a/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PingResource.java +++ b/service-discovery/stork/src/main/java/io/quarkus/ts/stork/PingResource.java @@ -13,6 +13,9 @@ @RouteBase(path = "/ping", produces = MediaType.TEXT_PLAIN) public class PingResource { + public static final String PING_PREFIX = "ping-"; + public static final String HEADER_ID = "x-id"; + @RestClient MyBackendPongProxy pongService; @@ -20,18 +23,17 @@ public class PingResource { MyBackendPungProxy pungService; @Route(methods = Route.HttpMethod.GET, path = "/pung") - public Uni pung(RoutingContext context) { - return formatResponse(pungService.get()); + public Uni pung() { + return pungService.get() + .onFailure().transform(error -> new WebApplicationException(error.getMessage())) + .map(resp -> PING_PREFIX + resp); } @Route(methods = Route.HttpMethod.GET, path = "/pong") - public Uni pong(RoutingContext context) { - return formatResponse(pongService.get()); - } - - private Uni formatResponse(Uni response) { - return response - .onFailure().transform(error -> new WebApplicationException(error.getMessage())) - .map(resp -> "ping-" + resp); + public void pong(RoutingContext context) { + pongService.get().onFailure().transform(error -> new WebApplicationException(error.getMessage())).subscribe() + .with(resp -> context.response() + .putHeader(HEADER_ID, resp.getHeaderString(HEADER_ID)) + .end(PING_PREFIX + resp.getEntity())); } } diff --git a/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PongReplicaResource.java b/service-discovery/stork/src/main/java/io/quarkus/ts/stork/PongReplicaResource.java similarity index 58% rename from service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PongReplicaResource.java rename to service-discovery/stork/src/main/java/io/quarkus/ts/stork/PongReplicaResource.java index 12860dc2f..9b7fa91d0 100644 --- a/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PongReplicaResource.java +++ b/service-discovery/stork/src/main/java/io/quarkus/ts/stork/PongReplicaResource.java @@ -21,22 +21,26 @@ public class PongReplicaResource { private static final String DEFAULT_PONG_REPLICA_RESPONSE = "pongReplica"; - @ConfigProperty(name = "stork.pong-replica.service-discovery.consul-host") + @ConfigProperty(name = "stork.pong-replica.service-discovery.consul-host", defaultValue = "localhost") String host; - @ConfigProperty(name = "stork.pong-replica.service-discovery.consul-port") + @ConfigProperty(name = "stork.pong-replica.service-discovery.consul-port", defaultValue = "8500") String port; - @ConfigProperty(name = "pong-replica-service-port") + @ConfigProperty(name = "pong-replica-service-port", defaultValue = "8080") String pongPort; - @ConfigProperty(name = "pong-replica-service-host") + @ConfigProperty(name = "pong-replica-service-host", defaultValue = "localhost") String pongHost; + @ConfigProperty(name = "stork.pong-replica.service-discovery", defaultValue = "consul") + String serviceDiscoveryType; public void init(@Observes StartupEvent ev, Vertx vertx) { - ConsulClient client = ConsulClient.create(vertx, - new ConsulClientOptions().setHost(host).setPort(Integer.parseInt(port))); - - client.registerServiceAndAwait( - new ServiceOptions().setPort(Integer.parseInt(pongPort)).setAddress(pongHost).setName(PONG_SERVICE_NAME) - .setId("pongReplica")); + if (serviceDiscoveryType.equalsIgnoreCase("consul")) { + ConsulClient client = ConsulClient.create(vertx, + new ConsulClientOptions().setHost(host).setPort(Integer.parseInt(port))); + + client.registerServiceAndAwait( + new ServiceOptions().setPort(Integer.parseInt(pongPort)).setAddress(pongHost).setName(PONG_SERVICE_NAME) + .setId("pongReplica")); + } } @Route(path = "/", methods = Route.HttpMethod.GET) diff --git a/service-discovery/stork/src/main/java/io/quarkus/ts/stork/PongResource.java b/service-discovery/stork/src/main/java/io/quarkus/ts/stork/PongResource.java new file mode 100644 index 000000000..67b6d6076 --- /dev/null +++ b/service-discovery/stork/src/main/java/io/quarkus/ts/stork/PongResource.java @@ -0,0 +1,54 @@ +package io.quarkus.ts.stork; + +import java.util.UUID; + +import javax.enterprise.event.Observes; +import javax.ws.rs.core.MediaType; + +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import io.quarkus.runtime.StartupEvent; +import io.quarkus.vertx.web.Route; +import io.quarkus.vertx.web.RouteBase; +import io.vertx.ext.consul.ConsulClientOptions; +import io.vertx.ext.consul.ServiceOptions; +import io.vertx.ext.web.RoutingContext; +import io.vertx.mutiny.core.Vertx; +import io.vertx.mutiny.ext.consul.ConsulClient; + +@RouteBase(path = "/pong", produces = MediaType.TEXT_PLAIN) +public class PongResource { + + public static final String PONG_SERVICE_NAME = "pong"; + private static final String DEFAULT_PONG_RESPONSE = "pong"; + private static final String HEADER_ID = "x-id"; + private String instanceUniqueId; + + @ConfigProperty(name = "stork.pong.service-discovery.consul-host", defaultValue = "localhost") + String host; + @ConfigProperty(name = "stork.pong.service-discovery.consul-port", defaultValue = "8500") + String port; + @ConfigProperty(name = "pong-service-port", defaultValue = "8080") + String pongPort; + @ConfigProperty(name = "pong-service-host", defaultValue = "localhost") + String pongHost; + @ConfigProperty(name = "stork.pong.service-discovery", defaultValue = "consul") + String serviceDiscoveryType; + + public void init(@Observes StartupEvent ev, Vertx vertx) { + instanceUniqueId = UUID.randomUUID().toString(); + if (serviceDiscoveryType.equalsIgnoreCase("consul")) { + ConsulClient client = ConsulClient.create(vertx, + new ConsulClientOptions().setHost(host).setPort(Integer.parseInt(port))); + + client.registerServiceAndAwait( + new ServiceOptions().setPort(Integer.parseInt(pongPort)).setAddress(pongHost).setName(PONG_SERVICE_NAME) + .setId("pong")); + } + } + + @Route(path = "/", methods = Route.HttpMethod.GET) + public void pong(final RoutingContext context) { + context.response().putHeader(HEADER_ID, instanceUniqueId).end(DEFAULT_PONG_RESPONSE); + } +} diff --git a/service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PungResource.java b/service-discovery/stork/src/main/java/io/quarkus/ts/stork/PungResource.java similarity index 100% rename from service-discovery/stork-consul/src/main/java/io/quarkus/ts/stork/PungResource.java rename to service-discovery/stork/src/main/java/io/quarkus/ts/stork/PungResource.java diff --git a/service-discovery/stork/src/main/resources/application.properties b/service-discovery/stork/src/main/resources/application.properties new file mode 100644 index 000000000..2a708270c --- /dev/null +++ b/service-discovery/stork/src/main/resources/application.properties @@ -0,0 +1,3 @@ +# application properties should be here +# TODO https://github.com/quarkusio/quarkus/issues/24444 +quarkus.native.additional-build-args=--allow-incomplete-classpath, --initialize-at-run-time=io.fabric8.kubernetes.client.internal.CertUtils, --enable-url-protocols=https diff --git a/service-discovery/stork/src/test/java/io/quarkus/ts/stork/OpenShiftStorkServiceDiscoveryIT.java b/service-discovery/stork/src/test/java/io/quarkus/ts/stork/OpenShiftStorkServiceDiscoveryIT.java new file mode 100644 index 000000000..a670edd72 --- /dev/null +++ b/service-discovery/stork/src/test/java/io/quarkus/ts/stork/OpenShiftStorkServiceDiscoveryIT.java @@ -0,0 +1,111 @@ +package io.quarkus.ts.stork; + +import static io.quarkus.ts.stork.PingResource.HEADER_ID; +import static io.quarkus.ts.stork.PingResource.PING_PREFIX; +import static java.util.regex.Pattern.quote; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +import java.io.File; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; + +import javax.inject.Inject; + +import org.apache.http.HttpStatus; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.bootstrap.RestService; +import io.quarkus.test.bootstrap.inject.OpenShiftClient; +import io.quarkus.test.scenarios.OpenShiftScenario; +import io.quarkus.test.services.QuarkusApplication; +import io.quarkus.test.utils.FileUtils; +import io.restassured.response.ValidatableResponse; + +@OpenShiftScenario +public class OpenShiftStorkServiceDiscoveryIT { + + private static final String CLUSTER_ROLE_FILE_NAME = "cluster-role.yaml"; + private static final String RBAC_FILE_NAME = "fabric8-rbac.yaml"; + private static final int PONG_INSTANCES_AMOUNT = 2; // we need at least two instances in order to verify stork LB + + @Inject + static OpenShiftClient openshift; + + @QuarkusApplication(classes = PungResource.class) + static RestService pung = new RestService() + .withProperty("stork.pung.service-discovery", "kubernetes") + .withProperty("stork.pung.service-discovery.k8s-namespace", "all"); + + @QuarkusApplication(classes = PongResource.class) + static RestService pong = new RestService() + .onPostStart(app -> openshift.scaleTo(app, PONG_INSTANCES_AMOUNT)) + .withProperty("stork.pong.service-discovery", "kubernetes") + .withProperty("stork.pong.service-discovery.k8s-namespace", "all"); + + @QuarkusApplication(classes = { PingResource.class, MyBackendPungProxy.class, MyBackendPongProxy.class }) + static RestService ping = new RestService().onPreStart(app -> setupClusterRoles()) + .withProperty("stork.pong.service-discovery", "kubernetes") + .withProperty("stork.pong.service-discovery.k8s-namespace", "all") + .withProperty("stork.pung.service-discovery", "kubernetes") + .withProperty("stork.pung.service-discovery.k8s-namespace", "all"); + + @AfterAll + public static void tearDown() { + openshift.delete(Paths.get(new File("target/test-classes/" + CLUSTER_ROLE_FILE_NAME).toURI())); + openshift.delete(Paths.get(new File("target/test-classes/" + RBAC_FILE_NAME).toURI())); + } + + @Test + public void invokeServiceByName() { + String response = makePingCall("pung").extract().body().asString(); + assertThat("Service discovery by name fail.", PING_PREFIX + "pung", is(response)); + } + + @Test + public void storkLoadBalancerByRoundRobin() { + Map uniqueResp = new HashMap<>(); + final int requestAmount = 10; + final int roundRobinError = (requestAmount / PONG_INSTANCES_AMOUNT) - 1; + for (int i = 0; i < requestAmount; i++) { + String pongInstanceId = makePingCall("pong").extract().header(HEADER_ID); + if (uniqueResp.containsKey(pongInstanceId)) { + uniqueResp.put(pongInstanceId, uniqueResp.get(pongInstanceId) + 1); + } else { + uniqueResp.put(pongInstanceId, 1); + } + } + + Assertions.assertEquals(uniqueResp.size(), PONG_INSTANCES_AMOUNT, + "Only " + PONG_INSTANCES_AMOUNT + " services should response"); + + for (Map.Entry pod : uniqueResp.entrySet()) { + Assertions.assertTrue(uniqueResp.get(pod.getKey()) >= roundRobinError, + "Request load is not distributed following a round-robin distribution"); + } + } + + private ValidatableResponse makePingCall(String subPath) { + return ping + .given() + .get("/ping/" + subPath).then() + .statusCode(HttpStatus.SC_OK); + } + + /** + * setup `stork-service-discovery-kubernetes` - roles and roles bindings (required) + */ + private static void setupClusterRoles() { + String namespace = openshift.project(); + String clusterRoleContent = FileUtils.loadFile(new File("target/test-classes/" + CLUSTER_ROLE_FILE_NAME)) + .replaceAll(quote("${NAMESPACE}"), namespace); + openshift.apply(FileUtils.copyContentTo(clusterRoleContent, + new File("target/test-classes/" + CLUSTER_ROLE_FILE_NAME).toPath())); + String contentRBAC = FileUtils.loadFile(new File("target/test-classes/" + RBAC_FILE_NAME)) + .replaceAll(quote("${NAMESPACE}"), namespace); + openshift.apply(FileUtils.copyContentTo(contentRBAC, new File("target/test-classes/" + RBAC_FILE_NAME).toPath())); + } +} diff --git a/service-discovery/stork-consul/src/test/java/io/quarkus/ts/stork/StorkServiceDiscoveryIT.java b/service-discovery/stork/src/test/java/io/quarkus/ts/stork/StorkServiceDiscoveryIT.java similarity index 95% rename from service-discovery/stork-consul/src/test/java/io/quarkus/ts/stork/StorkServiceDiscoveryIT.java rename to service-discovery/stork/src/test/java/io/quarkus/ts/stork/StorkServiceDiscoveryIT.java index 597f0cfe5..9e8717a80 100644 --- a/service-discovery/stork-consul/src/test/java/io/quarkus/ts/stork/StorkServiceDiscoveryIT.java +++ b/service-discovery/stork/src/test/java/io/quarkus/ts/stork/StorkServiceDiscoveryIT.java @@ -4,7 +4,6 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; import java.net.ServerSocket; @@ -21,6 +20,8 @@ import io.quarkus.test.services.Container; import io.quarkus.test.services.QuarkusApplication; +import junit.framework.AssertionFailedError; + @QuarkusScenario public class StorkServiceDiscoveryIT { @@ -122,14 +123,11 @@ private static String getConsultEndpoint(String endpoint) { return endpoint.replaceFirst(":\\d+", ""); } - private static String getAvailablePort() { - int port = 0; - try (ServerSocket serverSocket = new ServerSocket(port)) { - port = serverSocket.getLocalPort(); + public static String getAvailablePort() { + try (ServerSocket socket = new ServerSocket(0)) { + return String.valueOf(socket.getLocalPort()); } catch (IOException e) { - fail("no free port available."); + throw new AssertionFailedError(); } - - return String.valueOf(port); } } diff --git a/service-discovery/stork-consul/src/test/resources/cluster-role.yaml b/service-discovery/stork/src/test/resources/cluster-role.yaml similarity index 88% rename from service-discovery/stork-consul/src/test/resources/cluster-role.yaml rename to service-discovery/stork/src/test/resources/cluster-role.yaml index 77598d6d4..ad74137aa 100644 --- a/service-discovery/stork-consul/src/test/resources/cluster-role.yaml +++ b/service-discovery/stork/src/test/resources/cluster-role.yaml @@ -1,7 +1,7 @@ kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: - namespace: default + namespace: "${NAMESPACE}" name: endpoints-reader rules: - apiGroups: [""] # "" indicates the core API group diff --git a/service-discovery/stork-consul/src/test/resources/fabric8-rbac.yaml b/service-discovery/stork/src/test/resources/fabric8-rbac.yaml similarity index 86% rename from service-discovery/stork-consul/src/test/resources/fabric8-rbac.yaml rename to service-discovery/stork/src/test/resources/fabric8-rbac.yaml index 273fa334a..6270952ab 100644 --- a/service-discovery/stork-consul/src/test/resources/fabric8-rbac.yaml +++ b/service-discovery/stork/src/test/resources/fabric8-rbac.yaml @@ -6,7 +6,6 @@ subjects: - kind: ServiceAccount # Reference to upper's `metadata.name` name: default - # Reference to upper's `metadata.namespace` namespace: "${NAMESPACE}" roleRef: kind: ClusterRole diff --git a/service-discovery/stork/src/test/resources/test.properties b/service-discovery/stork/src/test/resources/test.properties new file mode 100644 index 000000000..71b4dd3ad --- /dev/null +++ b/service-discovery/stork/src/test/resources/test.properties @@ -0,0 +1,2 @@ +ts.pung.openshift.use-internal-service-as-url=true +ts.pong.openshift.use-internal-service-as-url=true