diff --git a/.github/workflows/simple-build-test.yml b/.github/workflows/simple-build-test.yml
index 2244ee6f4c..3e60dd9d2d 100644
--- a/.github/workflows/simple-build-test.yml
+++ b/.github/workflows/simple-build-test.yml
@@ -1,5 +1,8 @@
name: Basic build and test
+env:
+ MANDREL_VERSION: "22.0.0.2-Final"
+
on:
push:
paths-ignore:
@@ -47,7 +50,7 @@ jobs:
- rest-fights
- rest-heroes
- rest-villains
- name: "build-test-${{ matrix.project }}-java-${{ matrix.java }}"
+ name: "build-test-${{ matrix.project }}-${{ matrix.java }}"
steps:
- uses: actions/checkout@v3
@@ -61,3 +64,50 @@ jobs:
- name: "build-test-jvm-${{ matrix.project }}-java-${{ matrix.java }}"
working-directory: ${{ matrix.project }}
run: ./mvnw -B clean verify -Dquarkus.http.host=0.0.0.0 -Dmaven.compiler.release=${{ matrix.java }}
+
+ native-build-test:
+ runs-on: ubuntu-latest
+ if: github.repository == 'quarkusio/quarkus-super-heroes'
+ strategy:
+ fail-fast: false
+ matrix:
+ java:
+ - '11'
+ - '17'
+ project:
+ - event-statistics
+ - rest-fights
+ - rest-heroes
+ - rest-villains
+ name: "native-build-test-${{ matrix.project }}-${{ matrix.java }}"
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Cache and restore Mandrel distro
+ id: check-mandrel-cache
+ uses: actions/cache@v3
+ with:
+ path: mandrel-${{ env.MANDREL_VERSION }}-${{ matrix.java }}.tar.gz
+ key: mandrel-distro-${{ env.MANDREL_VERSION }}-${{ matrix.java }}
+
+ - name: Download Mandrel
+ if: steps.check-mandrel-cache.outputs.cache-hit != 'true'
+ run: |
+ download_url="https://github.com/graalvm/mandrel/releases/download/mandrel-${MANDREL_VERSION}/mandrel-java${{ matrix.java }}-linux-amd64-${MANDREL_VERSION}.tar.gz"
+ wget -q -O mandrel-${{ env.MANDREL_VERSION }}-${{ matrix.java }}.tar.gz $download_url
+
+ - name: Setup Maven+OpenJDK Distro
+ uses: actions/setup-java@v2
+ with:
+ distribution: 'jdkfile'
+ jdkFile: mandrel-${{ env.MANDREL_VERSION }}-${{ matrix.java }}.tar.gz
+ java-version: ${{ matrix.java }}
+ architecture: x64
+ cache: maven
+
+ - name: "build-test-native-${{ matrix.project }}-java-${{ matrix.java }}"
+ working-directory: ${{ matrix.project }}
+ run: |
+ ./mvnw -B clean verify -Pnative \
+ -Dquarkus.http.host=0.0.0.0 \
+ -Dmaven.compiler.release=${{ matrix.java }}
diff --git a/rest-fights/README.md b/rest-fights/README.md
index 7926033ad0..574d3e54af 100644
--- a/rest-fights/README.md
+++ b/rest-fights/README.md
@@ -10,6 +10,9 @@
- [Retries](#retries)
- [Hero Client](#hero-client)
- [Villain Client](#villain-client)
+- [Service Discovery and Load Balancing](#service-discovery-and-client-load-balancing)
+ - [Service Discovery](#service-discovery)
+ - [Client-side Load Balancing](#client-side-load-balancing)
- [Testing](#testing)
- [Running the Application](#running-the-application)
- [Running Locally via Docker Compose](#running-locally-via-docker-compose)
@@ -66,6 +69,27 @@ The [`VillainClient`](src/main/java/io/quarkus/sample/superheroes/fight/client/V
- The downstream [Villain service](../rest-villains) returns a `404` if no random [`Villain`](src/main/java/io/quarkus/sample/superheroes/fight/client/Villain.java) is found. `VillainClient` handles this case and simulates the service returning nothing.
- In the event the downstream [Villain service](../rest-heroes) returns an error, `VillainClient` adds 3 retries with a 200ms delay between each retry.
+## Service Discovery and Client Load Balancing
+The fight service implements service discovery and client-side load balancing when making downstream calls to the [`rest-heroes`](../rest-heroes) and [`rest-villains`](../rest-villains) services. The service discovery is implemented in Quarkus using [SmallRye Stork](https://quarkus.io/blog/smallrye-stork-intro).
+
+Stork [integrates directly with the Quarkus REST Client Reactive](http://smallrye.io/smallrye-stork/1.1.0/quarkus). This means that there is no additional code needed in the [`HeroRestClient`](src/main/java/io/quarkus/sample/superheroes/fight/client/HeroRestClient.java) in order to take advantage of Stork's service discovery and client-side load balancing.
+
+> You could disable Stork completely for the `HeroRestClient` by setting `quarkus.rest-client.hero-client.url` to any non-Stork URL (i.e. something that doesn't start with `stork://`).
+
+The [`VillainClient`](src/main/java/io/quarkus/sample/superheroes/fight/client/VillainClient.java), on the other hand, is implemented by directly using the [JAX-RS client API](https://docs.oracle.com/javaee/7/tutorial/jaxrs-client001.htm) with the [RESTEasy Reactive client](https://quarkus.io/guides/resteasy-reactive#resteasy-reactive-client). Therefore, the [Stork API](http://smallrye.io/smallrye-stork/1.1.0/concepts) is used directly in order to get the same functionality available in the [`HeroRestClient`](src/main/java/io/quarkus/sample/superheroes/fight/client/HeroRestClient.java).
+
+> Similarly. you could disable Stork completely for the `VillainClient` by setting `fight.villain.client-base-url` to any non-Stork URL (i.e. something that doesn't start with `stork://`).
+
+### Service Discovery
+In local development mode, as well as when running via Docker Compose, SmallRye Stork is configured using [static list discovery](https://github.com/smallrye/smallrye-stork/blob/main/docs/service-discovery/static-list.md). In this mode, the downstream URLs are statically defined in an address list. In [`application.properties`](src/main/resources/application.properties), see the `quarkus.stork.hero-service.service-discovery.address-list` and `quarkus.stork.villain-service.service-discovery.address-list` properties.
+
+When [running in Kubernetes](https://quarkus.io/blog/stork-kubernetes-discovery), Stork is configured to use the [Kubernetes Service Discovery](http://smallrye.io/smallrye-stork/1.1.0/kubernetes). In this mode, Stork will read the Kubernetes `Service`s for the [`rest-heroes`](../rest-heroes) and [`rest-villains`](../rest-villains) services to obtain the instance information. Additionally, the instance information has been configured to refresh every minute. See the `rest-fights-config` ConfigMap in [the Kubernetes deployment descriptors](deploy/k8s). Look for the `quarkus.stork.*` properties within the various `ConfigMap`s.
+
+All of the other Stork service discovery mechanisms ([Consul](http://smallrye.io/smallrye-stork/1.1.0/consul) and [Eureka](http://smallrye.io/smallrye-stork/1.1.0/eureka)) can be used simply by updating the configuration appropriately according to the Stork documentation.
+
+### Client-Side Load Balancing
+In all cases, the default load balancing algorithm used is [round robin](http://smallrye.io/smallrye-stork/1.1.0/round-robin). All of the other load balancing algorithms ([random](http://smallrye.io/smallrye-stork/1.1.0/random), [least requests](http://smallrye.io/smallrye-stork/1.1.0/least-requests), [least response time](http://smallrye.io/smallrye-stork/1.1.0/response-time), and [power of two choices](http://smallrye.io/smallrye-stork/1.1.0/power-of-two-choices)) are available on the application's classpath, so feel free to play around with them by updating the configuration appropriately according to the Stork documentation.
+
## Testing
This application has a full suite of tests, including an [integration test suite](src/test/java/io/quarkus/sample/superheroes/fight/rest/FightResourceIT.java).
- The test suite uses [Wiremock](http://wiremock.org/) for [mocking http calls](https://quarkus.io/guides/rest-client-reactive#using-a-mock-http-server-for-tests) (see [`HeroesVillainsWiremockServerResource`](src/test/java/io/quarkus/sample/superheroes/fight/HeroesVillainsWiremockServerResource.java)) to the downstream [Hero](../rest-heroes) and [Villain](../rest-villains) services.
@@ -92,8 +116,8 @@ By default, the application is configured with the following:
| Database password | `QUARKUS_MONGODB_CREDENTIALS_PASSWORD` | `quarkus.mongodb.credentials.password` | `superfight` |
| Kafka Bootstrap servers | `KAFKA_BOOTSTRAP_SERVERS` | `kafka.bootstrap.servers` | `PLAINTEXT://localhost:9092` |
| Apicurio Schema Registry | `MP_MESSAGING_CONNECTOR_SMALLRYE_KAFKA_APICURIO_REGISTRY_URL` | `mp.messaging.connector.smallrye-kafka.apicurio.registry.url` | `http://localhost:8086/apis/registry/v2` |
-| Heroes Service URL | `quarkus.rest-client.hero-client.url` | `quarkus.rest-client.hero-client.url` | `http://localhost:8083` |
-| Villains Service URL | `fight.villain.client-base-url` | `fight.villain.client-base-url` | `http://localhost:8084` |
+| Heroes Service URL | `QUARKUS_REST_CLIENT_HERO_CLIENT_URL` | `quarkus.rest-client.hero-client.url` | `stork://hero-service` |
+| Villains Service URL | `FIGHT_VILLAIN_CLIENT_BASE_URL` | `fight.villain.client-base-url` | `stork://villain-service` |
## Running Locally via Docker Compose
Pre-built images for this application can be found at [`quay.io/quarkus-super-heroes/rest-fights`](https://quay.io/repository/quarkus-super-heroes/rest-fights?tab=tags).
diff --git a/rest-fights/pom.xml b/rest-fights/pom.xml
index 37b90ed309..b8868f324e 100644
--- a/rest-fights/pom.xml
+++ b/rest-fights/pom.xml
@@ -132,6 +132,46 @@
io.quarkus
quarkus-liquibase-mongodb
+
+ io.smallrye.stork
+ stork-service-discovery-static-list
+
+
+ io.smallrye.stork
+ stork-service-discovery-kubernetes
+
+
+ io.smallrye.stork
+ stork-service-discovery-eureka
+
+
+ io.smallrye.stork
+ stork-service-discovery-consul
+
+
+ io.smallrye.stork
+ stork-load-balancer-random
+
+
+ io.smallrye.stork
+ stork-load-balancer-least-requests
+
+
+ io.smallrye.stork
+ stork-load-balancer-least-response-time
+
+
+ io.smallrye.stork
+ stork-load-balancer-power-of-two-choices
+
+
+ org.bouncycastle
+ bcprov-jdk15on
+
+
+ org.bouncycastle
+ bcpkix-jdk15on
+
io.quarkus
quarkus-junit5
diff --git a/rest-fights/src/main/docker-compose/java11.yml b/rest-fights/src/main/docker-compose/java11.yml
index f58332d4be..2d89cf5fbb 100644
--- a/rest-fights/src/main/docker-compose/java11.yml
+++ b/rest-fights/src/main/docker-compose/java11.yml
@@ -16,8 +16,8 @@
QUARKUS_LIQUIBASE_MONGODB_MIGRATE_AT_START: false
QUARKUS_MONGODB_CREDENTIALS_USERNAME: superfight
QUARKUS_MONGODB_CREDENTIALS_PASSWORD: superfight
- QUARKUS_REST_CLIENT_HERO_CLIENT_URL: http://rest-heroes:8083
- FIGHT_VILLAIN_CLIENT_BASE_URL: http://rest-villains:8084
+ QUARKUS_STORK_HERO_SERVICE_SERVICE_DISCOVERY_ADDRESS_LIST: rest-heroes:8083
+ QUARKUS_STORK_VILLAIN_SERVICE_SERVICE_DISCOVERY_ADDRESS_LIST: rest-villains:8084
MP_MESSAGING_CONNECTOR_SMALLRYE_KAFKA_APICURIO_REGISTRY_URL: http://apicurio:8086/apis/registry/v2
restart: on-failure
networks:
diff --git a/rest-fights/src/main/docker-compose/java17.yml b/rest-fights/src/main/docker-compose/java17.yml
index 423415df6a..0daa87937d 100644
--- a/rest-fights/src/main/docker-compose/java17.yml
+++ b/rest-fights/src/main/docker-compose/java17.yml
@@ -16,8 +16,8 @@
QUARKUS_LIQUIBASE_MONGODB_MIGRATE_AT_START: false
QUARKUS_MONGODB_CREDENTIALS_USERNAME: superfight
QUARKUS_MONGODB_CREDENTIALS_PASSWORD: superfight
- QUARKUS_REST_CLIENT_HERO_CLIENT_URL: http://rest-heroes:8083
- FIGHT_VILLAIN_CLIENT_BASE_URL: http://rest-villains:8084
+ QUARKUS_STORK_HERO_SERVICE_SERVICE_DISCOVERY_ADDRESS_LIST: rest-heroes:8083
+ QUARKUS_STORK_VILLAIN_SERVICE_SERVICE_DISCOVERY_ADDRESS_LIST: rest-villains:8084
MP_MESSAGING_CONNECTOR_SMALLRYE_KAFKA_APICURIO_REGISTRY_URL: http://apicurio:8086/apis/registry/v2
restart: on-failure
networks:
diff --git a/rest-fights/src/main/docker-compose/native-java11.yml b/rest-fights/src/main/docker-compose/native-java11.yml
index 179666a8ab..6ca1c7d8a4 100644
--- a/rest-fights/src/main/docker-compose/native-java11.yml
+++ b/rest-fights/src/main/docker-compose/native-java11.yml
@@ -16,8 +16,8 @@
QUARKUS_LIQUIBASE_MONGODB_MIGRATE_AT_START: false
QUARKUS_MONGODB_CREDENTIALS_USERNAME: superfight
QUARKUS_MONGODB_CREDENTIALS_PASSWORD: superfight
- QUARKUS_REST_CLIENT_HERO_CLIENT_URL: http://rest-heroes:8083
- FIGHT_VILLAIN_CLIENT_BASE_URL: http://rest-villains:8084
+ QUARKUS_STORK_HERO_SERVICE_SERVICE_DISCOVERY_ADDRESS_LIST: rest-heroes:8083
+ QUARKUS_STORK_VILLAIN_SERVICE_SERVICE_DISCOVERY_ADDRESS_LIST: rest-villains:8084
MP_MESSAGING_CONNECTOR_SMALLRYE_KAFKA_APICURIO_REGISTRY_URL: http://apicurio:8086/apis/registry/v2
restart: on-failure
networks:
diff --git a/rest-fights/src/main/docker-compose/native-java17.yml b/rest-fights/src/main/docker-compose/native-java17.yml
index f12cc5491e..c4d8e80ba4 100644
--- a/rest-fights/src/main/docker-compose/native-java17.yml
+++ b/rest-fights/src/main/docker-compose/native-java17.yml
@@ -16,8 +16,8 @@
QUARKUS_LIQUIBASE_MONGODB_MIGRATE_AT_START: false
QUARKUS_MONGODB_CREDENTIALS_USERNAME: superfight
QUARKUS_MONGODB_CREDENTIALS_PASSWORD: superfight
- QUARKUS_REST_CLIENT_HERO_CLIENT_URL: http://rest-heroes:8083
- FIGHT_VILLAIN_CLIENT_BASE_URL: http://rest-villains:8084
+ QUARKUS_STORK_HERO_SERVICE_SERVICE_DISCOVERY_ADDRESS_LIST: rest-heroes:8083
+ QUARKUS_STORK_VILLAIN_SERVICE_SERVICE_DISCOVERY_ADDRESS_LIST: rest-villains:8084
MP_MESSAGING_CONNECTOR_SMALLRYE_KAFKA_APICURIO_REGISTRY_URL: http://apicurio:8086/apis/registry/v2
restart: on-failure
networks:
diff --git a/rest-fights/src/main/java/io/quarkus/sample/superheroes/fight/client/VillainClient.java b/rest-fights/src/main/java/io/quarkus/sample/superheroes/fight/client/VillainClient.java
index 61140f8b29..3f47b00841 100644
--- a/rest-fights/src/main/java/io/quarkus/sample/superheroes/fight/client/VillainClient.java
+++ b/rest-fights/src/main/java/io/quarkus/sample/superheroes/fight/client/VillainClient.java
@@ -5,6 +5,7 @@
import java.util.concurrent.CompletionStage;
import javax.enterprise.context.ApplicationScoped;
+import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
@@ -12,10 +13,13 @@
import org.eclipse.microprofile.faulttolerance.CircuitBreaker;
import org.jboss.resteasy.reactive.client.impl.UniInvoker;
+import io.quarkus.logging.Log;
import io.quarkus.sample.superheroes.fight.config.FightConfig;
import io.smallrye.faulttolerance.api.CircuitBreakerName;
import io.smallrye.mutiny.Uni;
+import io.smallrye.stork.Stork;
+import io.smallrye.stork.api.ServiceInstance;
/**
* Bean to be used for interacting with the Villain service.
@@ -25,12 +29,20 @@
*/
@ApplicationScoped
public class VillainClient {
- private final WebTarget villainClient;
+ private static final String STORK_PREFIX = "stork://";
+ private static final String VILLAINS_API_PATH = "/api/villains";
+
+ private final WebTargetProvider webTargetProvider;
public VillainClient(FightConfig fightConfig) {
- this.villainClient = ClientBuilder.newClient()
- .target(fightConfig.villain().clientBaseUrl())
- .path("api/villains/");
+ var villainClientBaseUrl = fightConfig.villain().clientBaseUrl();
+
+ if (villainClientBaseUrl.startsWith(STORK_PREFIX)) {
+ this.webTargetProvider = new StorkWebTargetProvider(villainClientBaseUrl.replace(STORK_PREFIX, ""));
+ }
+ else {
+ this.webTargetProvider = new DefaultWebTargetProvider(villainClientBaseUrl);
+ }
}
/**
@@ -39,16 +51,18 @@ public VillainClient(FightConfig fightConfig) {
*/
@CircuitBreaker(requestVolumeThreshold = 8, failureRatio = 0.5, delay = 2, delayUnit = ChronoUnit.SECONDS)
@CircuitBreakerName("findRandomVillain")
- CompletionStage getRandomVillain() {
- // Want the 404 handling to be part of the circuit breaker
- // This means that the 404 responses aren't considered errors by the circuit breaker
- return this.villainClient.path("random")
- .request(MediaType.APPLICATION_JSON_TYPE)
- .rx(UniInvoker.class)
- .get(Villain.class)
- .onFailure(Is404Exception.IS_404).recoverWithNull()
- .subscribeAsCompletionStage();
- }
+ CompletionStage getRandomVillain() {
+ // Want the 404 handling to be part of the circuit breaker
+ // This means that the 404 responses aren't considered errors by the circuit breaker
+ return this.webTargetProvider.getWebTarget("/random")
+ .flatMap(webTarget ->
+ webTarget.request(MediaType.APPLICATION_JSON_TYPE)
+ .rx(UniInvoker.class)
+ .get(Villain.class)
+ .onFailure(Is404Exception.IS_404).recoverWithNull()
+ )
+ .subscribeAsCompletionStage();
+ }
/**
* Finds a random {@link Villain}. The retry logic is applied to the result of the {@link CircuitBreaker}, meaning that retries that return failures could trigger the breaker to open.
@@ -66,9 +80,69 @@ public Uni findRandomVillain() {
* @return A "hello" from Villains
*/
public Uni helloVillains() {
- return this.villainClient.path("hello")
- .request(MediaType.TEXT_PLAIN_TYPE)
- .rx(UniInvoker.class)
- .get(String.class);
+ return this.webTargetProvider.getWebTarget("/hello")
+ .flatMap(webTarget ->
+ webTarget.request(MediaType.TEXT_PLAIN_TYPE)
+ .rx(UniInvoker.class)
+ .get(String.class)
+ );
+ }
+
+ private static abstract class WebTargetProvider {
+ protected abstract Uni getWebTarget(String path);
+ }
+
+ private static class DefaultWebTargetProvider extends WebTargetProvider {
+ private final WebTarget webTarget;
+
+ private DefaultWebTargetProvider(String baseUrl) {
+ Log.debugf("Creating Default provider for baseUrl = %s", baseUrl);
+ this.webTarget = ClientBuilder.newClient()
+ .target(baseUrl)
+ .path(VILLAINS_API_PATH);
+ }
+
+ @Override
+ protected Uni getWebTarget(String path) {
+ return Uni.createFrom().item(this.webTarget.path(path));
+ }
+ }
+
+ private static class StorkWebTargetProvider extends WebTargetProvider {
+ private final Client villainClient = ClientBuilder.newClient();
+ private final String storkServiceName;
+
+ private StorkWebTargetProvider(String storkServiceName) {
+ Log.debugf("Creating Stork provider for service name = %s", storkServiceName);
+ this.storkServiceName = storkServiceName;
+ }
+
+ private Uni getServiceInstance() {
+ return Stork.getInstance()
+ .getService(this.storkServiceName)
+ .selectInstanceAndRecordStart(true);
+ }
+
+ private WebTarget createWebTarget(ServiceInstance serviceInstance, String path) {
+ var url = String.format(
+ "%s://%s:%d",
+ serviceInstance.isSecure() ? "https" : "http",
+ serviceInstance.getHost(),
+ serviceInstance.getPort()
+ );
+
+ Log.debugf("Targeting Stork client for service with URL = %s", url);
+
+ return this.villainClient.target(url)
+ .path(VILLAINS_API_PATH)
+ .path(path);
+ }
+
+ @Override
+ protected Uni getWebTarget(String path) {
+ return getServiceInstance()
+ .onItem().ifNotNull().transform(serviceInstance -> createWebTarget(serviceInstance, path))
+ .onItem().ifNull().failWith(() -> new IllegalArgumentException(String.format("Can't determine a downstream service for service name '%s'. Is one configured?")));
+ }
}
}
diff --git a/rest-fights/src/main/java/io/quarkus/sample/superheroes/fight/rest/FightResource.java b/rest-fights/src/main/java/io/quarkus/sample/superheroes/fight/rest/FightResource.java
index 0227a2be47..7c5d90b542 100644
--- a/rest-fights/src/main/java/io/quarkus/sample/superheroes/fight/rest/FightResource.java
+++ b/rest-fights/src/main/java/io/quarkus/sample/superheroes/fight/rest/FightResource.java
@@ -88,7 +88,7 @@ public Uni getFight(@Parameter(name = "id", required = true) @PathPara
return Response.ok(f).build();
})
.onItem().ifNull().continueWith(() -> {
- Log.debugf("No fight found with id %d", id);
+ Log.debugf("No fight found with id %s", id);
return Response.status(Status.NOT_FOUND).build();
});
}
diff --git a/rest-fights/src/main/kubernetes/knative.yml b/rest-fights/src/main/kubernetes/knative.yml
index e9cba8cd40..f8589a70a7 100644
--- a/rest-fights/src/main/kubernetes/knative.yml
+++ b/rest-fights/src/main/kubernetes/knative.yml
@@ -1,4 +1,16 @@
---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: default_view
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: view
+subjects:
+ - kind: ServiceAccount
+ name: default
+---
apiVersion: v1
kind: ConfigMap
metadata:
@@ -10,8 +22,12 @@ metadata:
data:
quarkus.liquibase-mongodb.migrate-at-start: false
quarkus.mongodb.hosts: fights-db:27017
- quarkus.rest-client.hero-client.url: http://rest-heroes
- fight.villain.client-base-url: http://rest-villains
+ quarkus.stork.hero-service.service-discovery.type: kubernetes
+ quarkus.stork.hero-service.service-discovery.application: rest-heroes
+ quarkus.stork.hero-service.service-discovery.refresh-period: 1M
+ quarkus.stork.villain-service.service-discovery.type: kubernetes
+ quarkus.stork.villain-service.service-discovery.application: rest-villains
+ quarkus.stork.villain-service.service-discovery.refresh-period: 1M
kafka.bootstrap.servers: PLAINTEXT://fights-kafka:9092
mp.messaging.connector.smallrye-kafka.apicurio.registry.url: http://apicurio:8080/apis/registry/v2
---
diff --git a/rest-fights/src/main/kubernetes/kubernetes.yml b/rest-fights/src/main/kubernetes/kubernetes.yml
index e9cba8cd40..f8589a70a7 100644
--- a/rest-fights/src/main/kubernetes/kubernetes.yml
+++ b/rest-fights/src/main/kubernetes/kubernetes.yml
@@ -1,4 +1,16 @@
---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: default_view
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: view
+subjects:
+ - kind: ServiceAccount
+ name: default
+---
apiVersion: v1
kind: ConfigMap
metadata:
@@ -10,8 +22,12 @@ metadata:
data:
quarkus.liquibase-mongodb.migrate-at-start: false
quarkus.mongodb.hosts: fights-db:27017
- quarkus.rest-client.hero-client.url: http://rest-heroes
- fight.villain.client-base-url: http://rest-villains
+ quarkus.stork.hero-service.service-discovery.type: kubernetes
+ quarkus.stork.hero-service.service-discovery.application: rest-heroes
+ quarkus.stork.hero-service.service-discovery.refresh-period: 1M
+ quarkus.stork.villain-service.service-discovery.type: kubernetes
+ quarkus.stork.villain-service.service-discovery.application: rest-villains
+ quarkus.stork.villain-service.service-discovery.refresh-period: 1M
kafka.bootstrap.servers: PLAINTEXT://fights-kafka:9092
mp.messaging.connector.smallrye-kafka.apicurio.registry.url: http://apicurio:8080/apis/registry/v2
---
diff --git a/rest-fights/src/main/kubernetes/minikube.yml b/rest-fights/src/main/kubernetes/minikube.yml
index e9cba8cd40..f8589a70a7 100644
--- a/rest-fights/src/main/kubernetes/minikube.yml
+++ b/rest-fights/src/main/kubernetes/minikube.yml
@@ -1,4 +1,16 @@
---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: default_view
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: view
+subjects:
+ - kind: ServiceAccount
+ name: default
+---
apiVersion: v1
kind: ConfigMap
metadata:
@@ -10,8 +22,12 @@ metadata:
data:
quarkus.liquibase-mongodb.migrate-at-start: false
quarkus.mongodb.hosts: fights-db:27017
- quarkus.rest-client.hero-client.url: http://rest-heroes
- fight.villain.client-base-url: http://rest-villains
+ quarkus.stork.hero-service.service-discovery.type: kubernetes
+ quarkus.stork.hero-service.service-discovery.application: rest-heroes
+ quarkus.stork.hero-service.service-discovery.refresh-period: 1M
+ quarkus.stork.villain-service.service-discovery.type: kubernetes
+ quarkus.stork.villain-service.service-discovery.application: rest-villains
+ quarkus.stork.villain-service.service-discovery.refresh-period: 1M
kafka.bootstrap.servers: PLAINTEXT://fights-kafka:9092
mp.messaging.connector.smallrye-kafka.apicurio.registry.url: http://apicurio:8080/apis/registry/v2
---
diff --git a/rest-fights/src/main/kubernetes/openshift.yml b/rest-fights/src/main/kubernetes/openshift.yml
index 5bea81a5b7..dc738344bd 100644
--- a/rest-fights/src/main/kubernetes/openshift.yml
+++ b/rest-fights/src/main/kubernetes/openshift.yml
@@ -1,4 +1,16 @@
---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+ name: default_view
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: view
+subjects:
+ - kind: ServiceAccount
+ name: default
+---
apiVersion: v1
kind: ConfigMap
metadata:
@@ -10,8 +22,12 @@ metadata:
data:
quarkus.liquibase-mongodb.migrate-at-start: false
quarkus.mongodb.hosts: fights-db:27017
- quarkus.rest-client.hero-client.url: http://rest-heroes
- fight.villain.client-base-url: http://rest-villains
+ quarkus.stork.hero-service.service-discovery.type: kubernetes
+ quarkus.stork.hero-service.service-discovery.application: rest-heroes
+ quarkus.stork.hero-service.service-discovery.refresh-period: 1M
+ quarkus.stork.villain-service.service-discovery.type: kubernetes
+ quarkus.stork.villain-service.service-discovery.application: rest-villains
+ quarkus.stork.villain-service.service-discovery.refresh-period: 1M
kafka.bootstrap.servers: PLAINTEXT://fights-kafka:9092
mp.messaging.connector.smallrye-kafka.apicurio.registry.url: http://apicurio:8080/apis/registry/v2
---
diff --git a/rest-fights/src/main/resources/application.properties b/rest-fights/src/main/resources/application.properties
index 1e7fb47d3a..f86d40b4f1 100644
--- a/rest-fights/src/main/resources/application.properties
+++ b/rest-fights/src/main/resources/application.properties
@@ -16,14 +16,20 @@ quarkus.liquibase-mongodb.migrate-at-start=true
quarkus.http.cors=true
## Rest client
-quarkus.rest-client.hero-client.url=http://localhost:8083
+quarkus.rest-client.hero-client.url=stork://hero-service
+
+## Stork
+quarkus.stork.hero-service.service-discovery.type=static
+quarkus.stork.hero-service.service-discovery.address-list=localhost:8083
+quarkus.stork.villain-service.service-discovery.type=static
+quarkus.stork.villain-service.service-discovery.address-list=localhost:8084
## Fight configuration
fight.hero.fallback.name=Fallback hero
fight.hero.fallback.picture=https://dummyimage.com/280x380/1e8fff/ffffff&text=Fallback+Hero
fight.hero.fallback.powers=Fallback hero powers
fight.hero.fallback.level=1
-fight.villain.client-base-url=http://localhost:8084
+fight.villain.client-base-url=stork://villain-service
fight.villain.fallback.name=Fallback villain
fight.villain.fallback.picture=https://dummyimage.com/280x380/b22222/ffffff&text=Fallback+Villain
fight.villain.fallback.powers=Fallback villain powers
@@ -37,6 +43,10 @@ mp.messaging.outgoing.fights.apicurio.registry.auto-register=true
%test.mp.messaging.outgoing.fights.merge=true
## Logging configuration
+%dev.quarkus.log.category."io.quarkus.sample.superheroes.fight".level=DEBUG
+%test.quarkus.log.category."io.quarkus.sample.superheroes.fight".level=DEBUG
+%dev.quarkus.log.console.level=DEBUG
+%test.quarkus.log.console.level=DEBUG
quarkus.log.level=INFO
quarkus.log.console.enable=true
quarkus.log.console.format=%d{HH:mm:ss} %-5p [%c{2.}] (%t) %s%e%n
diff --git a/rest-fights/src/test/java/io/quarkus/sample/superheroes/fight/HeroesVillainsWiremockServerResource.java b/rest-fights/src/test/java/io/quarkus/sample/superheroes/fight/HeroesVillainsWiremockServerResource.java
index 5655bd286f..229fac4a0a 100644
--- a/rest-fights/src/test/java/io/quarkus/sample/superheroes/fight/HeroesVillainsWiremockServerResource.java
+++ b/rest-fights/src/test/java/io/quarkus/sample/superheroes/fight/HeroesVillainsWiremockServerResource.java
@@ -20,10 +20,15 @@ public class HeroesVillainsWiremockServerResource implements QuarkusTestResource
public Map start() {
this.wireMockServer.start();
- return Map.of(
- "quarkus.rest-client.hero-client.url", this.wireMockServer.baseUrl(),
- "fight.villain.client-base-url", this.wireMockServer.baseUrl()
- );
+ var url = String.format(
+ "localhost:%d",
+ this.wireMockServer.isHttpsEnabled() ? this.wireMockServer.httpsPort() : this.wireMockServer.port()
+ );
+
+ return Map.of(
+ "quarkus.stork.hero-service.service-discovery.address-list", url,
+ "quarkus.stork.villain-service.service-discovery.address-list", url
+ );
}
@Override
diff --git a/rest-fights/src/test/java/io/quarkus/sample/superheroes/fight/client/HeroClientTests.java b/rest-fights/src/test/java/io/quarkus/sample/superheroes/fight/client/HeroClientTests.java
index c89d8fc9b7..a9e9c4da35 100644
--- a/rest-fights/src/test/java/io/quarkus/sample/superheroes/fight/client/HeroClientTests.java
+++ b/rest-fights/src/test/java/io/quarkus/sample/superheroes/fight/client/HeroClientTests.java
@@ -2,7 +2,7 @@
import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static javax.ws.rs.core.HttpHeaders.ACCEPT;
-import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+import static javax.ws.rs.core.MediaType.*;
import static org.assertj.core.api.Assertions.assertThat;
import java.time.Duration;
@@ -35,7 +35,9 @@
@QuarkusTest
@QuarkusTestResource(HeroesVillainsWiremockServerResource.class)
class HeroClientTests {
- private static final String HERO_URI = "/api/heroes/random";
+ private static final String HERO_API_BASE_URI = "/api/heroes";
+ private static final String HERO_URI = HERO_API_BASE_URI + "/random";
+ private static final String HERO_HELLO_URI = HERO_API_BASE_URI + "/hello";
private static final String DEFAULT_HERO_NAME = "Super Baguette";
private static final String DEFAULT_HERO_PICTURE = "super_baguette.png";
private static final String DEFAULT_HERO_POWERS = "eats baguette really quickly";
@@ -182,6 +184,25 @@ public void doesntRecoverFrom500() {
);
}
+ @Test
+ public void helloHeroes() {
+ this.wireMockServer.stubFor(
+ get(urlEqualTo(HERO_HELLO_URI))
+ .willReturn(okForContentType(TEXT_PLAIN, "Hello heroes!"))
+ );
+
+ this.heroClient.helloHeroes()
+ .subscribe().withSubscriber(UniAssertSubscriber.create())
+ .assertSubscribed()
+ .awaitItem(Duration.ofSeconds(5))
+ .assertItem("Hello heroes!");
+
+ this.wireMockServer.verify(1,
+ getRequestedFor(urlEqualTo(HERO_HELLO_URI))
+ .withHeader(ACCEPT, containing(TEXT_PLAIN))
+ );
+ }
+
private String getDefaultHeroJson() {
try {
return this.objectMapper.writeValueAsString(DEFAULT_HERO);
diff --git a/rest-fights/src/test/java/io/quarkus/sample/superheroes/fight/client/VillainClientTests.java b/rest-fights/src/test/java/io/quarkus/sample/superheroes/fight/client/VillainClientTests.java
index f63c4eb6ec..4b9007516a 100644
--- a/rest-fights/src/test/java/io/quarkus/sample/superheroes/fight/client/VillainClientTests.java
+++ b/rest-fights/src/test/java/io/quarkus/sample/superheroes/fight/client/VillainClientTests.java
@@ -2,7 +2,7 @@
import static com.github.tomakehurst.wiremock.client.WireMock.*;
import static javax.ws.rs.core.HttpHeaders.ACCEPT;
-import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
+import static javax.ws.rs.core.MediaType.*;
import static org.assertj.core.api.Assertions.assertThat;
import java.time.Duration;
@@ -35,7 +35,9 @@
@QuarkusTest
@QuarkusTestResource(HeroesVillainsWiremockServerResource.class)
class VillainClientTests {
- private static final String VILLAIN_API = "/api/villains/random";
+ private static final String VILLAIN_API_BASE_URI = "/api/villains";
+ private static final String VILLAIN_API = VILLAIN_API_BASE_URI + "/random";
+ private static final String VILLAIN_HELLO_URI = VILLAIN_API_BASE_URI + "/hello";
private static final String DEFAULT_VILLAIN_NAME = "Super Chocolatine";
private static final String DEFAULT_VILLAIN_PICTURE = "super_chocolatine.png";
private static final String DEFAULT_VILLAIN_POWERS = "does not eat pain au chocolat";
@@ -182,6 +184,25 @@ public void doesntRecoverFrom500() {
);
}
+ @Test
+ public void helloVillains() {
+ this.wireMockServer.stubFor(
+ get(urlEqualTo(VILLAIN_HELLO_URI))
+ .willReturn(okForContentType(TEXT_PLAIN, "Hello villains!"))
+ );
+
+ this.villainClient.helloVillains()
+ .subscribe().withSubscriber(UniAssertSubscriber.create())
+ .assertSubscribed()
+ .awaitItem(Duration.ofSeconds(5))
+ .assertItem("Hello villains!");
+
+ this.wireMockServer.verify(1,
+ getRequestedFor(urlEqualTo(VILLAIN_HELLO_URI))
+ .withHeader(ACCEPT, containing(TEXT_PLAIN))
+ );
+ }
+
private String getDefaultVillainJson() {
try {
return this.objectMapper.writeValueAsString(DEFAULT_VILLAIN);