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);