Skip to content

Commit

Permalink
Add Stork + k8s coverage
Browse files Browse the repository at this point in the history
- default service discovery
- default load balncer
  • Loading branch information
pablo gonzalez granados committed Mar 21, 2022
1 parent 40b0804 commit cd59788
Show file tree
Hide file tree
Showing 17 changed files with 131 additions and 89 deletions.
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@
<module>docker-build</module>
<module>javaee-like-getting-started</module>
<module>scaling</module>
<module>service-discovery/stork-consul</module>
<module>service-discovery/stork</module>
<module>lifecycle-application</module>
<module>external-applications</module>
<module>scheduling/quartz</module>
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
</parent>
<artifactId>stork</artifactId>
<packaging>jar</packaging>
<name>Quarkus QE TS: Service-discovery: Stork-consul</name>
<name>Quarkus QE TS: Service-discovery: Stork</name>
<dependencies>
<dependency>
<groupId>io.smallrye.stork</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -15,5 +16,5 @@ public interface MyBackendPongProxy {
@GET
@Produces(MediaType.TEXT_PLAIN)
@Path("/")
Uni<String> get();
Uni<RestResponse<String>> get();
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,25 +13,27 @@
@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<String> pung(RoutingContext context) {
return formatResponse(pungService.get());
public Uni<String> 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<String> pong(RoutingContext context) {
return formatResponse(pongService.get());
}

private Uni<String> formatResponse(Uni<String> 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()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -1,32 +1,37 @@
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.Path;
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.Order;
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 PREFIX = "ping-";
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;
Expand All @@ -36,8 +41,16 @@ public class OpenShiftStorkServiceDiscoveryIT {
.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");

Expand All @@ -49,21 +62,38 @@ public static void tearDown() {

@Test
public void invokeServiceByName() {
String response = makePingCall("pung");
assertThat("Service discovery by name fail.", PREFIX + "pung", is(response));
String response = makePingCall("pung").extract().body().asString();
assertThat("Service discovery by name fail.", PING_PREFIX + "pung", is(response));
}

@Test
public void storkLoadBalancerByRoundRobin() {

Map<String, Integer> 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<String, Integer> pod : uniqueResp.entrySet()) {
Assertions.assertTrue(uniqueResp.get(pod.getKey()) >= roundRobinError,
"Request load is not distributed following a round-robin distribution");
}
}

private String makePingCall(String subPath) {
private ValidatableResponse makePingCall(String subPath) {
return ping
.given()
.get("/ping/" + subPath).then()
.statusCode(HttpStatus.SC_OK)
.extract().body().asString();
.statusCode(HttpStatus.SC_OK);
}

/**
Expand All @@ -77,4 +107,4 @@ private static void setupClusterRoles() {
Path target = FileUtils.copyContentTo(content, new File("target/test-classes/" + RBAC_FILE_NAME).toPath());
openshift.apply(target);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -122,14 +122,12 @@ 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.");
}

return String.valueOf(port);
return String.valueOf(-1);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions service-discovery/stork/src/test/resources/test.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ts.pung.openshift.use-internal-service-as-url=true
ts.pong.openshift.use-internal-service-as-url=true

0 comments on commit cd59788

Please sign in to comment.