diff --git a/README.md b/README.md index 0e9390a78..e47b11559 100644 --- a/README.md +++ b/README.md @@ -651,6 +651,16 @@ this.router.get("/secured") * authZ::authorize: custom AuthZ(authorization) provider. * secure.helloWorld(rc): actual http endpoint (Rest layer). +### Service-discovery/stork + +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 +* 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 ade1a3187..866c37f6e 100644 --- a/pom.xml +++ b/pom.xml @@ -349,6 +349,7 @@ docker-build javaee-like-getting-started scaling + service-discovery/stork lifecycle-application external-applications scheduling/quartz diff --git a/service-discovery/stork/pom.xml b/service-discovery/stork/pom.xml new file mode 100644 index 000000000..729075597 --- /dev/null +++ b/service-discovery/stork/pom.xml @@ -0,0 +1,67 @@ + + + 4.0.0 + + io.quarkus.ts.qe + parent + 1.0.0-SNAPSHOT + ../.. + + stork + jar + Quarkus QE TS: Service-discovery: Stork + + + io.smallrye.stork + stork-service-discovery-consul + + + io.smallrye.reactive + smallrye-mutiny-vertx-consul-client + + + io.smallrye.stork + stork-service-discovery-kubernetes + + + 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/src/main/java/io/quarkus/ts/stork/MyBackendPongProxy.java b/service-discovery/stork/src/main/java/io/quarkus/ts/stork/MyBackendPongProxy.java new file mode 100644 index 000000000..e222e6518 --- /dev/null +++ b/service-discovery/stork/src/main/java/io/quarkus/ts/stork/MyBackendPongProxy.java @@ -0,0 +1,20 @@ +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 org.jboss.resteasy.reactive.RestResponse; + +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/src/main/java/io/quarkus/ts/stork/MyBackendPungProxy.java b/service-discovery/stork/src/main/java/io/quarkus/ts/stork/MyBackendPungProxy.java new file mode 100644 index 000000000..83af3f220 --- /dev/null +++ b/service-discovery/stork/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/src/main/java/io/quarkus/ts/stork/PingResource.java b/service-discovery/stork/src/main/java/io/quarkus/ts/stork/PingResource.java new file mode 100644 index 000000000..4c6c0e35b --- /dev/null +++ b/service-discovery/stork/src/main/java/io/quarkus/ts/stork/PingResource.java @@ -0,0 +1,39 @@ +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 { + + public static final String PING_PREFIX = "ping-"; + public static final String HEADER_ID = "x-id"; + + @RestClient + MyBackendPongProxy pongService; + + @RestClient + MyBackendPungProxy pungService; + + @Route(methods = Route.HttpMethod.GET, path = "/pung") + 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 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/src/main/java/io/quarkus/ts/stork/PongReplicaResource.java b/service-discovery/stork/src/main/java/io/quarkus/ts/stork/PongReplicaResource.java new file mode 100644 index 000000000..9b7fa91d0 --- /dev/null +++ b/service-discovery/stork/src/main/java/io/quarkus/ts/stork/PongReplicaResource.java @@ -0,0 +1,50 @@ +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 { + + private static final String DEFAULT_PONG_REPLICA_RESPONSE = "pongReplica"; + + @ConfigProperty(name = "stork.pong-replica.service-discovery.consul-host", defaultValue = "localhost") + String host; + @ConfigProperty(name = "stork.pong-replica.service-discovery.consul-port", defaultValue = "8500") + String port; + @ConfigProperty(name = "pong-replica-service-port", defaultValue = "8080") + String pongPort; + @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) { + 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) + public Uni pong() { + return Uni.createFrom().item(DEFAULT_PONG_REPLICA_RESPONSE); + } +} 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/src/main/java/io/quarkus/ts/stork/PungResource.java b/service-discovery/stork/src/main/java/io/quarkus/ts/stork/PungResource.java new file mode 100644 index 000000000..60f588be2 --- /dev/null +++ b/service-discovery/stork/src/main/java/io/quarkus/ts/stork/PungResource.java @@ -0,0 +1,46 @@ +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", defaultValue = "localhost") + String host; + @ConfigProperty(name = "stork.pung.service-discovery.consul-port", defaultValue = "8500") + String port; + @ConfigProperty(name = "pung-service-port", defaultValue = "8080") + String pungPort; + @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) { + 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) + public Uni pung() { + return Uni.createFrom().item("pung"); + } +} 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/src/test/java/io/quarkus/ts/stork/StorkServiceDiscoveryIT.java b/service-discovery/stork/src/test/java/io/quarkus/ts/stork/StorkServiceDiscoveryIT.java new file mode 100644 index 000000000..9e8717a80 --- /dev/null +++ b/service-discovery/stork/src/test/java/io/quarkus/ts/stork/StorkServiceDiscoveryIT.java @@ -0,0 +1,133 @@ +package io.quarkus.ts.stork; + +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 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; + +import junit.framework.AssertionFailedError; + +@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 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(); + + @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+", ""); + } + + public static String getAvailablePort() { + try (ServerSocket socket = new ServerSocket(0)) { + return String.valueOf(socket.getLocalPort()); + } catch (IOException e) { + throw new AssertionFailedError(); + } + } +} diff --git a/service-discovery/stork/src/test/resources/cluster-role.yaml b/service-discovery/stork/src/test/resources/cluster-role.yaml new file mode 100644 index 000000000..ad74137aa --- /dev/null +++ b/service-discovery/stork/src/test/resources/cluster-role.yaml @@ -0,0 +1,9 @@ +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + namespace: "${NAMESPACE}" + 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/src/test/resources/fabric8-rbac.yaml b/service-discovery/stork/src/test/resources/fabric8-rbac.yaml new file mode 100644 index 000000000..6270952ab --- /dev/null +++ b/service-discovery/stork/src/test/resources/fabric8-rbac.yaml @@ -0,0 +1,13 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: fabric8-rbac +subjects: + - kind: ServiceAccount + # Reference to upper's `metadata.name` + name: default + 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/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