Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adds OrientDB 3.0.x module #1414

Merged
merged 23 commits into from Feb 5, 2020
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e3798c4
initial commit for OrientDB container support module
robfrank Feb 16, 2019
1aa3606
log configuration for test added
robfrank Feb 17, 2019
806de50
code cleanup
robfrank Feb 17, 2019
fab67ec
removes SELF as per code review
robfrank Feb 17, 2019
01208f7
adds orientdb module documentation
robfrank Mar 26, 2019
5194b0d
Merge remote-tracking branch 'upstream/master'
robfrank Mar 26, 2019
8dcb195
Merge remote-tracking branch 'upstream/master'
robfrank Apr 16, 2019
cceabb8
adds OrientDB 3.0.x module
robfrank Apr 19, 2019
6d061ca
Merge branch 'master' into orientdb
May 27, 2019
fbdf916
Merge remote-tracking branch 'upstream/master' into orientdb
robfrank May 28, 2019
394eb26
Merge remote-tracking branch 'upstream/master'
robfrank Jun 6, 2019
7ab3988
Merge remote-tracking branch 'upstream/master' into orientdb
robfrank Jun 6, 2019
667b666
remove unneeded test in OrientDBContainerTest
bsideup Jul 19, 2019
4c4fc2a
Merge remote-tracking branch 'upstream/master'
robfrank Jul 22, 2019
6496d09
removes gremlin tests that rely on custom image
robfrank Jul 22, 2019
2d68734
Merge branch 'orientdb' of github.com:robfrank/testcontainers-java in…
robfrank Jul 22, 2019
321804e
merged with master
robfrank Jul 22, 2019
4aacece
Merge branch 'master' into orientdb
rnorth Oct 8, 2019
82a3bd3
Merge remote-tracking branch 'upstream/master' into orientdb
robfrank Oct 30, 2019
7c8bcc3
update to orientdb 3.0.24
robfrank Oct 30, 2019
6460453
update to orientdb 3.0.24
robfrank Oct 30, 2019
e68cd4e
cleanup following comments on PR
robfrank Nov 2, 2019
47d911a
Merge branch 'master' into orientdb
rnorth Feb 5, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
74 changes: 74 additions & 0 deletions docs/modules/databases/orientdb.md
@@ -0,0 +1,74 @@
# OrientDB Module

rnorth marked this conversation as resolved.
Show resolved Hide resolved
This module helps running [OrientDB](https://orientdb.org/download) using Testcontainers.

Note that it's based on the [official Docker image](https://hub.docker.com/_/orientdb/) provided by OrientDB.

## Usage example

Declare your Testcontainer as a `@ClassRule` or `@Rule` in a JUnit 4 test or as static or member attribute of a JUnit 5 test annotated with `@Container` as you would with other Testcontainers.
You can call `getDbUrl()` OrientDB container and build the `ODatabaseSession` by your own, but a more useful `getSession()` method is provided.
On the JVM you would most likely use the [Java driver](https://github.com/).

The following example uses the JUnit 5 extension `@Testcontainers` and demonstrates both the usage of the Java Client:

```java tab="JUnit 5 example"
@Testcontainers
public class ExampleTest {

@Container
private static OrientDBContainer container = new OrientDBContainer();

@Test
void testDbCreation() {

final ODatabaseSession session = container.getSession();

session.command("CREATE CLASS Person EXTENDS V");
session.command("INSERT INTO Person set name='john'");
session.command("INSERT INTO Person set name='jane'");

assertThat(session.query("SELECT FROM Person").stream()).hasSize(2);
}

}
```

You are not limited to Unit tests and can of course use an instance of the OrientDB Testcontainer in vanilla Java code as well.


## Adding this module to your project dependencies

Add the following dependency to your `pom.xml`/`build.gradle` file:

```groovy tab='Gradle'
testCompile "org.testcontainers:orientdb:{{latest_version}}"
```

```xml tab='Maven'
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>orientdb</artifactId>
<version>{{latest_version}}</version>
<scope>test</scope>
</dependency>
```

!!! hint
Add the OrientDB Java client if you plan to access the Testcontainer:

```groovy tab='Gradle'
compile "com.orientechnologies:orientdb-client:3.0.17"
```

```xml tab='Maven'
<dependency>
<groupId>com.orientechnologies</groupId>
<artifactId>orientdb-client</artifactId>
<version>3.0.17</version>
</dependency>
```




11 changes: 11 additions & 0 deletions modules/orientdb/build.gradle
@@ -0,0 +1,11 @@
description = "TestContainers :: Orientdb"

dependencies {
compile project(":testcontainers")

compile "com.orientechnologies:orientdb-client:3.0.24"

testCompile 'org.assertj:assertj-core:3.12.0'
testCompile 'org.apache.tinkerpop:gremlin-driver:3.3.4'
testCompile "com.orientechnologies:orientdb-gremlin:3.0.18"
}
@@ -0,0 +1,181 @@
package org.testcontainers.containers;

import com.github.dockerjava.api.command.InspectContainerResponse;
import com.orientechnologies.orient.core.db.ODatabaseSession;
import com.orientechnologies.orient.core.db.ODatabaseType;
import com.orientechnologies.orient.core.db.OrientDB;
import com.orientechnologies.orient.core.db.OrientDBConfig;
import lombok.NonNull;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.wait.strategy.HttpWaitStrategy;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.containers.wait.strategy.WaitAllStrategy;
import org.testcontainers.containers.wait.strategy.WaitStrategy;

import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Optional;

import static java.net.HttpURLConnection.HTTP_OK;

/**
* @author robfrank
*/
public class OrientDBContainer extends GenericContainer<OrientDBContainer> {

private static final String DEFAULT_IMAGE_NAME = "orientdb";

private static final String DEFAULT_TAG = "3.0.24-tp3";

private static final String DEFAULT_USERNAME = "admin";

private static final String DEFAULT_PASSWORD = "admin";

private static final String DEFAULT_DATABASE_NAME = "testcontainers";

private static final int DEFAULT_BINARY_PORT = 2424;

private static final int DEFAULT_HTTP_PORT = 2480;

private static final Logger LOGGER = LoggerFactory.getLogger(OrientDBContainer.class);

private static final String DOCKER_IMAGE_NAME = DEFAULT_IMAGE_NAME + ":" + DEFAULT_TAG;

private String databaseName;

private String username;

private String password;

private OrientDB orientDB;

private ODatabaseSession session;

private Optional<String> scriptPath = Optional.empty();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a tiny style point, but I'd be inclined to reduce the spacing and reorder some of these fields (to group similar fields together, and to reduce visible space taken).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cleaned



public OrientDBContainer() {
this(DOCKER_IMAGE_NAME);
}

public OrientDBContainer(@NonNull String dockerImageName) {
super(dockerImageName);

username = DEFAULT_USERNAME;
password = DEFAULT_PASSWORD;
databaseName = DEFAULT_DATABASE_NAME;

WaitStrategy waitForHttp = new HttpWaitStrategy()
.forPort(DEFAULT_HTTP_PORT)
.forStatusCodeMatching(response -> response == HTTP_OK);

waitStrategy = new WaitAllStrategy()
.withStrategy(Wait.forListeningPort())
.withStrategy(waitForHttp)
.withStartupTimeout(Duration.ofMinutes(2));
}

@Override
protected void configure() {

addExposedPorts(DEFAULT_BINARY_PORT, DEFAULT_HTTP_PORT);
addEnv("ORIENTDB_ROOT_PASSWORD", "root");
}


public String getDatabaseName() {
return databaseName;
}

public String getUsername() {
return username;
}

public String getPassword() {
return password;
}

public String getTestQueryString() {
return "SELECT FROM V";
}

public OrientDBContainer withDatabaseName(final String databaseName) {
this.databaseName = databaseName;
return self();
}

public OrientDBContainer withUsername(final String username) {
this.username = username;
return self();
}

public OrientDBContainer withPassword(final String password) {
this.password = password;
return self();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This username and password don't seem to be used to configure the container. In the JDBC database classes, username/password are passed through to the container via environment variables, to tell the container's bootstrap script to create that account.

It doesn't look like OrientDB supports this natively. Line 147 might be an opportunity to create the database and give access to the provided username, but as far as I can tell it's just expecting those credentials to work on line 150.

Is that accurate?

If these methods don't ensure that the account exists, I'd prefer to remove them to avoid confusion, TBH.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. I misunderstood the purpose of the member variables of the Container class, mixing server and client variables. I moved to the getSession method.


public OrientDBContainer withScriptPath(String scriptPath) {

this.scriptPath = Optional.of(scriptPath);
return self();
}


@Override
protected void containerIsStarted(InspectContainerResponse containerInfo) {
orientDB = new OrientDB(getServerUrl(), "root", "root", OrientDBConfig.defaultConfig());
}

public OrientDB getOrientDB() {
return orientDB;
}

public String getServerUrl() {
return "remote:" + getContainerIpAddress() + ":" + getMappedPort(2424);
}

public String getDbUrl() {
return getServerUrl() + "/" + databaseName;
}

public ODatabaseSession getSession() {

orientDB.createIfNotExists(databaseName, ODatabaseType.PLOCAL);

if (session == null) {
rnorth marked this conversation as resolved.
Show resolved Hide resolved
session = orientDB.open(databaseName, username, password);

scriptPath.ifPresent(path -> loadScript(path, session));
}
return session;
}


private void loadScript(String path, ODatabaseSession session) {
try {
URL resource = getClass().getClassLoader().getResource(path);

if (resource == null) {
LOGGER.warn("Could not load classpath init script: {}", scriptPath);
throw new RuntimeException("Could not load classpath init script: " + scriptPath + ". Resource not found.");
}

String script = IOUtils.toString(resource, StandardCharsets.UTF_8);

session.execute("sql", script);
} catch (IOException e) {
LOGGER.warn("Could not load classpath init script: {}", scriptPath);
throw new RuntimeException("Could not load classpath init script: " + scriptPath, e);
} catch (UnsupportedOperationException e) {
LOGGER.error("Error while executing init script: {}", scriptPath, e);
throw new RuntimeException("Error while executing init script: " + scriptPath, e);
}

}


}
@@ -0,0 +1,86 @@
package org.testcontainers.containers;

import com.orientechnologies.orient.core.db.ODatabaseSession;
import org.junit.Test;

import static org.assertj.core.api.Assertions.*;

/**
* @author robfrank
*/
public class OrientDBContainerTest {

@Test
public void testContainerLifecycle() {
OrientDBContainer container = new OrientDBContainer();

container.start();

assertThat(container.isRunning()).isTrue();

container.stop();

assertThat(container.isRunning()).isFalse();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this test is unnecessary, since it's a feature of Testcontainers core. Can we remove?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed


@Test
public void shouldReturnTheSameSession() {
try (OrientDBContainer container = new OrientDBContainer()) {
container.start();

final ODatabaseSession session = container.getSession();
final ODatabaseSession session2 = container.getSession();

assertThat(session).isSameAs(session2);
}
}

@Test
public void shouldInitializeWithCommands() {
try (OrientDBContainer container = new OrientDBContainer()) {
container.start();

final ODatabaseSession session = container.getSession();

session.command("CREATE CLASS Person EXTENDS V");
session.command("INSERT INTO Person set name='john'");
session.command("INSERT INTO Person set name='jane'");

assertThat(session.query("SELECT FROM Person").stream()).hasSize(2);
}
}

@Test
public void shouldQueryWithGremlin() {

try (OrientDBContainer container = new OrientDBContainer()) {
container.start();

final ODatabaseSession session = container.getSession();

session.command("CREATE CLASS Person EXTENDS V");
session.command("INSERT INTO Person set name='john'");
session.command("INSERT INTO Person set name='jane'");

assertThat(session.execute("gremlin",
"g.V().hasLabel('Person')").stream()).hasSize(2);
}
}

@Test
public void shouldInitializeDatabaseFromScript() {
try (OrientDBContainer container = new OrientDBContainer()
.withScriptPath("initscript.osql")
.withDatabaseName("persons")) {

container.start();

assertThat(container.getDbUrl())
.isEqualTo("remote:" + container.getContainerIpAddress() + ":" + container.getMappedPort(2424) + "/persons");

final ODatabaseSession session = container.getSession();

assertThat(session.query("SELECT FROM Person").stream()).hasSize(4);
}
}
}
6 changes: 6 additions & 0 deletions modules/orientdb/src/test/resources/initscript.osql
@@ -0,0 +1,6 @@
CREATE CLASS Person EXTENDS V;

INSERT INTO Person set name="john";
INSERT INTO Person set name="paul";
INSERT INTO Person set name="luke";
INSERT INTO Person set name="albert";
18 changes: 18 additions & 0 deletions modules/orientdb/src/test/resources/logback-test.xml
@@ -0,0 +1,18 @@
<configuration>

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} %-5level %logger - %msg%n</pattern>
</encoder>
</appender>

<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>

<logger name="org.testcontainers" level="DEBUG"/>
<logger name="org.testcontainers.shaded" level="WARN"/>

</configuration>