Skip to content

Commit 8b76637

Browse files
committed
Add LRA Dev Services
1 parent 5c3a518 commit 8b76637

File tree

22 files changed

+497
-93
lines changed

22 files changed

+497
-93
lines changed

docs/src/main/asciidoc/compose-dev-services.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,10 @@ The following is the list of platform extensions with dev services support:
213213
| kube-apiserver, k3s, kindest/node
214214
| 6443
215215

216+
| LRA
217+
| lra-coordinator
218+
| 8080
219+
216220
| MongoDB
217221
| mongo
218222
| 27017

docs/src/main/asciidoc/dev-services.adoc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,13 @@ xref:kubernetes-dev-services.adoc[Kubernetes Dev Services Guide].
115115

116116
include::{generated-dir}/config/quarkus-kubernetes-client_quarkus.kubernetes-client.devservices.adoc[opts=optional, leveloffset=+1]
117117

118+
=== LRA
119+
120+
The Narayana LRA Dev Service will be enabled when the `quarkus-narayana-lra` extension is present in your application, and the coordinator URL has not been explicitly configured. More information can be found in the
121+
xref:lra-dev-services.adoc[Narayana LRA Dev Services Guide].
122+
123+
include::{generated-dir}/config/quarkus-narayana-lra_quarkus.lra.devservices.adoc[opts=optional, leveloffset=+1]
124+
118125
=== MongoDB
119126

120127
The MongoDB Dev Service will be enabled when the `quarkus-mongodb-client` extension is present in your application, and
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
////
2+
This guide is maintained in the main Quarkus repository
3+
and pull requests should be submitted there:
4+
https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc
5+
////
6+
= Dev Services for LRA
7+
include::_attributes.adoc[]
8+
:categories: data
9+
:summary: Start the Narayana LRA coordinator automatically in dev and test modes.
10+
:topics: data,lra,narayana
11+
:extensions: io.quarkus:quarkus-narayana-lra
12+
13+
If the Narayana LRA extension is present (`quarkus-narayana-lra`), the Dev
14+
Services for Narayana LRA coordinator automatically starts the Narayana LRA
15+
coordinator in dev mode and when running tests. So, you don't have to start it
16+
manually. The application is also configured automatically to connect to the
17+
coordinator and to make possible for the coordinator to call back the
18+
application.
19+
20+
WARNING: Because the LRA coordinator needs to call back your application from
21+
the container, we need to expose the application on any host address, not just
22+
`localhost`. So the Dev Services for LRA coordinator injects
23+
`quarkus.http.host=0.0.0.0` into your application configuration which means that
24+
your application is accessible from any machine running in the same network as
25+
your development machine. *This is a potential security risk*, so use the LRA
26+
Dev Services only if you are sure that it is safe.
27+
28+
== Enabling / Disabling Dev Services for LRA
29+
30+
Dev Services for LRA is automatically enabled unless:
31+
32+
- `quarkus.lra.devservices.enabled` is set to `false`
33+
- the `quarkus.lra.coordinator-url` is configured
34+
35+
Dev Services for LRA relies on Docker or Podman to start the coordinator. If
36+
your environment does not support Docker, you will need to start the coordinator
37+
manually, or connect to an already running coordinator. You can configure the
38+
coordinator URL using `quarkus.lra.coordinator-url`.
39+
40+
== Sharing LRA coordinator
41+
42+
Most likely you need to share the LRA coordinator between applications. The Dev
43+
Services for LRA coordinator implements a _service discovery_ mechanism for your
44+
multiple Quarkus applications running in _dev_ mode to share a single
45+
coordinator.
46+
47+
NOTE: Dev Services for LRA starts the container with the
48+
`quarkus-dev-service-lra-coordinator` label which is used to identify the
49+
container.
50+
51+
If you need multiple (shared) coordinators, you can configure the
52+
`quarkus.lra.devservices.service-name` attribute and indicate the coordinator
53+
name. It looks for a container with the same value, or starts a new one if none
54+
can be found. The default service name is `lra-coordinator`.
55+
56+
Sharing is enabled by default in dev mode, but disabled in test mode.
57+
You can disable the sharing with `quarkus.lra.devservices.shared=false`.
58+
59+
== Setting the port
60+
61+
By default, Dev Services for LRA picks a random port on which to run the LRA
62+
coordinator. You can set the port by configuring the
63+
`quarkus.lra.devservices.port` property. The connection property
64+
`quarkus.lra.coordinator-url` is automatically configured with the chosen port.
65+
66+
[[configuring-the-image]]
67+
== Configuring the image
68+
69+
The default image for the LRA coordinator is `quay.io/jbosstm/lra-coordinator`.
70+
You can configure the image used by Dev Services for LRA coordinator using the
71+
`quarkus.lra.devservices.image-name` property. You can browse the available
72+
images at https://quay.io/repository/jbosstm/lra-coordinator?tab=tags.
73+
74+
[[Compose]]
75+
== Compose
76+
77+
The LRA Dev Services supports xref:compose-dev-services.adoc[Compose Dev Services].
78+
It relies on a `compose-devservices.yml`, such as:
79+
80+
[source,yaml]
81+
----
82+
name: <application name>
83+
services:
84+
mongo:
85+
image: quay.io/jbosstm/lra-coordinator:7.2.2.Final-3.24.4
86+
ports:
87+
- "8080"
88+
----
89+
90+
[[configuration-reference-devservices]]
91+
== Configuration reference
92+
93+
include::{generated-dir}/config/quarkus-narayana-lra_quarkus.lra.devservices.adoc[opts=optional, leveloffset=+1]

extensions/narayana-lra/deployment/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@
3535
<artifactId>quarkus-rest-jackson-deployment</artifactId>
3636
<scope>test</scope>
3737
</dependency>
38+
<dependency>
39+
<groupId>io.quarkus</groupId>
40+
<artifactId>quarkus-devservices-deployment</artifactId>
41+
</dependency>
3842
<dependency>
3943
<groupId>io.quarkus</groupId>
4044
<artifactId>quarkus-rest-client-deployment</artifactId>

extensions/narayana-lra/deployment/src/main/java/io/quarkus/narayana/lra/deployment/LRABuildTimeConfiguration.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package io.quarkus.narayana.lra.deployment;
22

3+
import io.quarkus.narayana.lra.deployment.devservice.LRACoordinatorDevServicesBuildTimeConfig;
4+
import io.quarkus.runtime.annotations.ConfigDocSection;
35
import io.quarkus.runtime.annotations.ConfigPhase;
46
import io.quarkus.runtime.annotations.ConfigRoot;
57
import io.smallrye.config.ConfigMapping;
@@ -19,4 +21,12 @@ public interface LRABuildTimeConfiguration {
1921
@WithName("openapi.included")
2022
@WithDefault("false")
2123
boolean openapiIncluded();
24+
25+
/**
26+
* Dev Services.
27+
* <p>
28+
* Dev Services that automatically start Narayana LRA coordinator in dev and test modes.
29+
*/
30+
@ConfigDocSection(generated = true)
31+
LRACoordinatorDevServicesBuildTimeConfig devservices();
2232
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package io.quarkus.narayana.lra.deployment.devservice;
2+
3+
import static io.quarkus.devservices.common.ContainerLocator.locateContainerWithLabels;
4+
5+
import java.util.List;
6+
import java.util.Map;
7+
8+
import org.jboss.logging.Logger;
9+
import org.testcontainers.utility.DockerImageName;
10+
11+
import io.quarkus.deployment.Feature;
12+
import io.quarkus.deployment.IsDevServicesSupportedByLaunchMode;
13+
import io.quarkus.deployment.annotations.BuildStep;
14+
import io.quarkus.deployment.annotations.BuildSteps;
15+
import io.quarkus.deployment.builditem.DevServicesComposeProjectBuildItem;
16+
import io.quarkus.deployment.builditem.DevServicesResultBuildItem;
17+
import io.quarkus.deployment.builditem.DevServicesSharedNetworkBuildItem;
18+
import io.quarkus.deployment.builditem.DockerStatusBuildItem;
19+
import io.quarkus.deployment.builditem.LaunchModeBuildItem;
20+
import io.quarkus.deployment.builditem.Startable;
21+
import io.quarkus.deployment.dev.devservices.DevServicesConfig;
22+
import io.quarkus.devservices.common.ComposeLocator;
23+
import io.quarkus.devservices.common.ContainerLocator;
24+
import io.quarkus.narayana.lra.deployment.LRABuildTimeConfiguration;
25+
import io.quarkus.runtime.configuration.ConfigUtils;
26+
27+
/**
28+
* Start Narayana LRA coordinator as a dev service.
29+
*/
30+
@BuildSteps(onlyIf = { IsDevServicesSupportedByLaunchMode.class, DevServicesConfig.Enabled.class })
31+
public class DevServicesLRAProcessor {
32+
33+
private static final Logger log = Logger.getLogger(DevServicesLRAProcessor.class);
34+
private static final String LRA_COORDINATOR_URL_PROPERTY = "quarkus.lra.coordinator-url";
35+
36+
static final String DEV_SERVICE_LABEL = "quarkus-dev-service-lra-coordinator";
37+
static final int LRA_COORDINATOR_CONTAINER_PORT = 8080;
38+
39+
private static final ContainerLocator lraCoordinatorContainerLocator = locateContainerWithLabels(
40+
LRA_COORDINATOR_CONTAINER_PORT, DEV_SERVICE_LABEL);
41+
42+
@BuildStep
43+
public DevServicesResultBuildItem lraCoordinatorDevService(
44+
LRABuildTimeConfiguration lraBuildTimeConfiguration,
45+
DevServicesComposeProjectBuildItem compose,
46+
DockerStatusBuildItem dockerStatusBuildItem,
47+
LaunchModeBuildItem launchMode,
48+
List<DevServicesSharedNetworkBuildItem> sharedNetwork,
49+
DevServicesConfig devServicesConfig) {
50+
LRACoordinatorDevServicesBuildTimeConfig config = lraBuildTimeConfiguration.devservices();
51+
if (isDevServiceDisabled(dockerStatusBuildItem, config)) {
52+
return null;
53+
}
54+
55+
boolean useSharedNetwork = DevServicesSharedNetworkBuildItem.isSharedNetworkRequired(devServicesConfig, sharedNetwork);
56+
57+
if (config.logWarning()) {
58+
log.warn("Dev Services for LRA requires exposing your application on the 0.0.0.0 host address. " +
59+
"Your application will be accessible from your network. You can disable this warning by setting " +
60+
"quarkus.lra.devservices.log-warning=false.");
61+
}
62+
63+
return lraCoordinatorContainerLocator
64+
.locateContainer(config.serviceName(), config.shared(), launchMode.getLaunchMode())
65+
.or(() -> ComposeLocator.locateContainer(compose,
66+
List.of(config.imageName(), "lra-coordinator"),
67+
LRA_COORDINATOR_CONTAINER_PORT, launchMode.getLaunchMode(), useSharedNetwork))
68+
.map(containerAddress -> DevServicesResultBuildItem.discovered()
69+
.feature(Feature.NARAYANA_LRA)
70+
.containerId(containerAddress.getId())
71+
.config(Map.of(LRA_COORDINATOR_URL_PROPERTY, "http://" + containerAddress.getUrl() + "/lra-coordinator",
72+
"quarkus.http.host", "0.0.0.0", // Required since the container needs to call the host application
73+
"quarkus.lra.base-uri",
74+
"http://host.containers.internal:"
75+
+ (launchMode.isTest() ? "${quarkus.http.test-port}" : "${quarkus.http.port}")))
76+
.build())
77+
.orElseGet(() -> DevServicesResultBuildItem.owned()
78+
.feature(Feature.NARAYANA_LRA)
79+
.serviceName(config.serviceName())
80+
.serviceConfig(config)
81+
.startable(() -> createContainer(compose, config, useSharedNetwork, launchMode))
82+
.postStartHook(s -> logDevServiceStarted(s.getConnectionInfo()))
83+
.configProvider(Map.of(LRA_COORDINATOR_URL_PROPERTY, Startable::getConnectionInfo,
84+
"quarkus.http.host", s -> "0.0.0.0", // Required since the container needs to call the host application
85+
"quarkus.lra.base-uri",
86+
s -> "http://host.containers.internal:"
87+
+ (launchMode.isTest() ? "${quarkus.http.test-port}" : "${quarkus.http.port}")))
88+
.build());
89+
}
90+
91+
private void logDevServiceStarted(String connectionInfo) {
92+
log.infof("Dev Services for the LRA coordinator started. Other applications in dev mode will find the " +
93+
"LRA coordinator automatically. For Quarkus application in production mode, you can connect to " +
94+
"this coordinator by starting you application with -D%s=%s\n", LRA_COORDINATOR_URL_PROPERTY, connectionInfo);
95+
96+
}
97+
98+
private Startable createContainer(DevServicesComposeProjectBuildItem compose,
99+
LRACoordinatorDevServicesBuildTimeConfig config, boolean useSharedNetwork, LaunchModeBuildItem launchMode) {
100+
return new LRACoordinatorContainer(DockerImageName.parse(config.imageName()),
101+
config.port().orElse(0),
102+
compose.getDefaultNetworkId(),
103+
useSharedNetwork)
104+
.withEnv(config.containerEnv())
105+
.withSharedServiceLabel(launchMode.getLaunchMode(), config.serviceName());
106+
}
107+
108+
private boolean isDevServiceDisabled(DockerStatusBuildItem dockerStatusBuildItem,
109+
LRACoordinatorDevServicesBuildTimeConfig config) {
110+
// explicitly disable
111+
if (!config.enabled()) {
112+
log.debug("Not starting dev services for the LRA coordinator, as it has been disabled in the config.");
113+
return true;
114+
}
115+
116+
// defined LRA coordinator URL
117+
if (ConfigUtils.isPropertyNonEmpty(LRA_COORDINATOR_URL_PROPERTY)) {
118+
log.debugf("Not starting dev services for the LRA coordinator, the \"%s\" is configured",
119+
LRA_COORDINATOR_URL_PROPERTY);
120+
return true;
121+
}
122+
123+
// missing docker environment
124+
if (!dockerStatusBuildItem.isContainerRuntimeAvailable()) {
125+
log.warnf("Couldn't find valid Docker environment, please configure the \"%s\" configuration property.",
126+
LRA_COORDINATOR_URL_PROPERTY);
127+
return true;
128+
}
129+
130+
return false;
131+
}
132+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package io.quarkus.narayana.lra.deployment.devservice;
2+
3+
import static io.quarkus.devservices.common.ConfigureUtil.configureSharedServiceLabel;
4+
import static io.quarkus.narayana.lra.deployment.devservice.DevServicesLRAProcessor.DEV_SERVICE_LABEL;
5+
6+
import org.testcontainers.containers.GenericContainer;
7+
import org.testcontainers.containers.wait.strategy.Wait;
8+
import org.testcontainers.utility.DockerImageName;
9+
10+
import com.github.dockerjava.api.command.InspectContainerResponse;
11+
12+
import io.quarkus.deployment.builditem.Startable;
13+
import io.quarkus.devservices.common.ConfigureUtil;
14+
import io.quarkus.runtime.LaunchMode;
15+
16+
public class LRACoordinatorContainer extends GenericContainer<LRACoordinatorContainer> implements Startable {
17+
18+
private final Integer fixedExposedPort;
19+
20+
private int exposedPort = -1;
21+
22+
public LRACoordinatorContainer(DockerImageName imageName, Integer fixedExposedPort, String defaultNetworkId,
23+
boolean useSharedNetwork) {
24+
super(imageName);
25+
this.fixedExposedPort = fixedExposedPort;
26+
ConfigureUtil.configureNetwork(this, defaultNetworkId, useSharedNetwork, "lra-coordinator");
27+
waitingFor(Wait.forLogMessage(".*lra-coordinator-quarkus.*started in.*", 1));
28+
}
29+
30+
@Override
31+
protected void containerIsStarting(InspectContainerResponse inspectContainerResponse, boolean reused) {
32+
super.containerIsStarting(inspectContainerResponse, reused);
33+
this.exposedPort = getMappedPort(DevServicesLRAProcessor.LRA_COORDINATOR_CONTAINER_PORT);
34+
}
35+
36+
@Override
37+
public void configure() {
38+
super.configure();
39+
40+
addExposedPort(DevServicesLRAProcessor.LRA_COORDINATOR_CONTAINER_PORT);
41+
42+
if (fixedExposedPort != null) {
43+
addFixedExposedPort(fixedExposedPort, DevServicesLRAProcessor.LRA_COORDINATOR_CONTAINER_PORT);
44+
}
45+
}
46+
47+
@Override
48+
public String getConnectionInfo() {
49+
return String.format("http://%s:%d/lra-coordinator", getHost(), getExposedPort());
50+
}
51+
52+
@Override
53+
public void close() {
54+
super.close();
55+
}
56+
57+
public int getExposedPort() {
58+
return exposedPort;
59+
}
60+
61+
public LRACoordinatorContainer withSharedServiceLabel(LaunchMode launchMode, String serviceName) {
62+
return configureSharedServiceLabel(this, launchMode, DEV_SERVICE_LABEL, serviceName);
63+
}
64+
}

0 commit comments

Comments
 (0)