Skip to content

Commit

Permalink
Merge pull request #20269 from michael-simons/neo4j-dev-services
Browse files Browse the repository at this point in the history
  • Loading branch information
gastaldi authored Sep 21, 2021
2 parents b4913c3 + 6edf61c commit 8cc9513
Show file tree
Hide file tree
Showing 9 changed files with 519 additions and 1 deletion.
19 changes: 19 additions & 0 deletions docs/src/main/asciidoc/neo4j.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ The Neo4j driver can be configured with standard Quarkus properties:
[source,properties]
.src/main/resources/application.properties
----
# Those are the default values and are implicitly assumed
quarkus.neo4j.uri = bolt://localhost:7687
quarkus.neo4j.authentication.username = neo4j
quarkus.neo4j.authentication.password = secret
Expand All @@ -176,6 +177,24 @@ You'll recognize the authentication here that you passed on to the docker comman

Having done that, the driver is ready to use, there are however other configuration options, detailed below.

[[dev-services]]
=== Dev Services (Configuration Free Databases)

Quarkus supports a feature called Dev Services that allows you to create various datasources without any config.
In the case of Neo4j this support applies to the single Neo4j driver instance.
Dev Services will bring up a Neo4j container if you didn't explicit add the default values or configured custom values for
any of `quarkus.neo4j.uri`, `quarkus.neo4j.authentication.username` or `quarkus.neo4j.authentication.password`.
If Neo4j seems to be reachable via the default properties, Dev Services will also step back.

Otherwise, Quarkus will automatically start a Neo4j container when running tests or dev-mode,
and automatically configure the connection.

When running the production version of the application, the Neo4j connection need to be configured as normal,
so if you want to include a production database config in your `application.properties` and continue to use Dev Services
we recommend that you use the `%prod.` profile to define your Neo4j settings.

include::{generated-dir}/config/quarkus-neo4j-config-group-dev-services-build-time-config.adoc[opts=optional, leveloffset=+1]

== Using the driver

=== General remarks
Expand Down
39 changes: 39 additions & 0 deletions extensions/neo4j/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,36 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-neo4j</artifactId>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>neo4j</artifactId>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-devservices-common</artifactId>
</dependency>

<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-internal</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand All @@ -45,6 +75,15 @@
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<!-- See https://github.com/junit-team/junit5/issues/1377#issuecomment-381964988 -->
<artifactId>maven-surefire-plugin</artifactId>
<configuration combine.self="append">
<excludes>
<exclude/>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package io.quarkus.neo4j.deployment;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.time.Duration;

/**
* This implements the protocol version negotiation of bolt. Testing to see if in address will respond to this is a
* quick way to find out if it's a running bolt server.
* <p>
* This class first appeared in https://github.com/michael-simons/junit-jupiter-causal-cluster-testcontainer-extension
* by Andrew Jefferson and Michael Simons
*/
final class BoltHandshaker {

private static final int magicToken = 1616949271;

// Versions message that cannot be matched because it is all zeros.
private static final byte[] versionsMessage = {
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00
};

private final String address;
private final int port;

BoltHandshaker(String address, int port) {
this.address = address;
this.port = port;
}

private boolean doBoltHandshake(String address, int port, int timeoutMillis) {

try (Socket socket = new Socket()) {

// Set the socket timeout for blocking operations
socket.setSoTimeout(timeoutMillis);

// Connects this socket to the server (also with the specified timeout value).
socket.connect(new InetSocketAddress(address, port), timeoutMillis);

DataOutputStream dOut = new DataOutputStream(socket.getOutputStream());
DataInputStream dIn = new DataInputStream(socket.getInputStream());

// Send magic token (0x6060B017)
dOut.writeInt(magicToken);
dOut.flush();

// Send 4 supported versions
// Except we don't support any versions and communicate that by sending all zeros
dOut.write(versionsMessage);
dOut.flush();

// Receive agreed version
// It should be 0 because there are no possible versions we can agree on
int response = dIn.readInt();
assert response == 0;

// Because we cannot agree on a version the server should close its side of the connection
// resulting in EOF (-1) on all subsequent reads.
return dIn.read() == -1;
} catch (IOException exception) {
// Return false if handshake fails
return false;
}
}

boolean isBoltPortReachable(Duration timeout) {
int timeoutMillis = Math.toIntExact(timeout.toMillis());
return doBoltHandshake(address, port, timeoutMillis);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package io.quarkus.neo4j.deployment;

import java.util.Map;
import java.util.Optional;

import io.quarkus.runtime.annotations.ConfigGroup;
import io.quarkus.runtime.annotations.ConfigItem;

@ConfigGroup
public class DevServicesBuildTimeConfig {

/**
* If DevServices has been explicitly enabled or disabled. DevServices is generally enabled
* by default, unless there is an existing configuration present.
* When DevServices is enabled Quarkus will attempt to automatically configure and start
* a database when running in Dev or Test mode.
*/
@ConfigItem
public Optional<Boolean> enabled = Optional.empty();

/**
* The container image name to use, for container based DevServices providers.
*/
@ConfigItem(defaultValue = "neo4j:4.3")
public String imageName;

/**
* Additional environment entries that can be added to the container before its start.
*/
@ConfigItem
public Map<String, String> additionalEnv;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,16 @@

@ConfigRoot(name = "neo4j", phase = ConfigPhase.BUILD_TIME)
public class Neo4jBuildTimeConfig {

/**
* Whether or not an health check is published in case the smallrye-health extension is present.
* Whether a health check is published in case the smallrye-health extension is present.
*/
@ConfigItem(name = "health.enabled", defaultValue = "true")
public boolean healthEnabled;

/**
* Configuration for DevServices. DevServices allows Quarkus to automatically start a Neo4j instance in dev and test mode.
*/
@ConfigItem
public DevServicesBuildTimeConfig devservices;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.quarkus.neo4j.deployment;

import io.quarkus.builder.item.SimpleBuildItem;

public final class Neo4jDevServiceBuildItem extends SimpleBuildItem {
}
Loading

0 comments on commit 8cc9513

Please sign in to comment.