From 15332f5048aa2dda733fbfe4dba817275c346725 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez?= Date: Fri, 23 Feb 2024 16:46:42 -0600 Subject: [PATCH 1/6] Add Ollama module --- .github/ISSUE_TEMPLATE/bug_report.yaml | 1 + .github/ISSUE_TEMPLATE/enhancement.yaml | 1 + .github/ISSUE_TEMPLATE/feature.yaml | 1 + .github/dependabot.yml | 5 ++++ .github/labeler.yml | 4 +++ .github/settings.yml | 3 ++ docs/modules/ollama.md | 30 +++++++++++++++++++ mkdocs.yml | 1 + modules/ollama/build.gradle | 8 +++++ .../ollama/OllamaContainer.java | 24 +++++++++++++++ .../ollama/OllamaContainerTest.java | 26 ++++++++++++++++ .../src/test/resources/logback-test.xml | 16 ++++++++++ 12 files changed, 120 insertions(+) create mode 100644 docs/modules/ollama.md create mode 100644 modules/ollama/build.gradle create mode 100644 modules/ollama/src/main/java/org/testcontainers/ollama/OllamaContainer.java create mode 100644 modules/ollama/src/test/java/org/testcontainers/ollama/OllamaContainerTest.java create mode 100644 modules/ollama/src/test/resources/logback-test.xml diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 8d363cb978f..3d2b08ebfd1 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -41,6 +41,7 @@ body: - MySQL - Neo4j - NGINX + - Ollama - Oracle Free - Oracle XE - OrientDB diff --git a/.github/ISSUE_TEMPLATE/enhancement.yaml b/.github/ISSUE_TEMPLATE/enhancement.yaml index 6ee160c982b..7079b7322a0 100644 --- a/.github/ISSUE_TEMPLATE/enhancement.yaml +++ b/.github/ISSUE_TEMPLATE/enhancement.yaml @@ -41,6 +41,7 @@ body: - MySQL - Neo4j - NGINX + - Ollama - Oracle Free - Oracle XE - OrientDB diff --git a/.github/ISSUE_TEMPLATE/feature.yaml b/.github/ISSUE_TEMPLATE/feature.yaml index 3f8920b4059..5f5d97f4191 100644 --- a/.github/ISSUE_TEMPLATE/feature.yaml +++ b/.github/ISSUE_TEMPLATE/feature.yaml @@ -41,6 +41,7 @@ body: - MySQL - Neo4j - NGINX + - Ollama - Oracle Free - Oracle XE - OrientDB diff --git a/.github/dependabot.yml b/.github/dependabot.yml index bf9b145f2b3..fef2822b0bc 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -211,6 +211,11 @@ updates: schedule: interval: "weekly" open-pull-requests-limit: 10 + - package-ecosystem: "gradle" + directory: "/modules/ollama" + schedule: + interval: "weekly" + open-pull-requests-limit: 10 - package-ecosystem: "gradle" directory: "/modules/oracle-free" schedule: diff --git a/.github/labeler.yml b/.github/labeler.yml index 0fd1ec90d29..4f1ae775d62 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -131,6 +131,10 @@ - changed-files: - any-glob-to-any-file: - modules/nginx/**/* +"modules/ollama": + - changed-files: + - any-glob-to-any-file: + - modules/ollama/**/* "modules/oracle": - changed-files: - any-glob-to-any-file: diff --git a/.github/settings.yml b/.github/settings.yml index a4589aab643..fb73b460931 100644 --- a/.github/settings.yml +++ b/.github/settings.yml @@ -190,6 +190,9 @@ labels: - name: modules/nginx color: '#006b75' + - name: modules/ollama + color: '#006b75' + - name: modules/oracle color: '#006b75' diff --git a/docs/modules/ollama.md b/docs/modules/ollama.md new file mode 100644 index 00000000000..97e8865bdc1 --- /dev/null +++ b/docs/modules/ollama.md @@ -0,0 +1,30 @@ +# Ollama + +Testcontainers module for [Ollama](https://hub.docker.com/r/ollama/ollama) . + +## Ollama's usage examples + +You can start an Ollama container instance from any Java application by using: + + +[Ollama container](../../modules/ollama/src/test/java/org/testcontainers/ollama/OllamaContainerTest.java) inside_block:container + + +## Adding this module to your project dependencies + +Add the following dependency to your `pom.xml`/`build.gradle` file: + +=== "Gradle" + ```groovy + testImplementation "org.testcontainers:ollama:{{latest_version}}" + ``` + +=== "Maven" + ```xml + + org.testcontainers + ollama + {{latest_version}} + test + + ``` diff --git a/mkdocs.yml b/mkdocs.yml index 784159e8e57..5d5fa0d68d8 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -88,6 +88,7 @@ nav: - modules/minio.md - modules/mockserver.md - modules/nginx.md + - modules/ollama.md - modules/pulsar.md - modules/qdrant.md - modules/rabbitmq.md diff --git a/modules/ollama/build.gradle b/modules/ollama/build.gradle new file mode 100644 index 00000000000..b64083062ec --- /dev/null +++ b/modules/ollama/build.gradle @@ -0,0 +1,8 @@ +description = "Testcontainers :: Ollama" + +dependencies { + api project(':testcontainers') + + testImplementation 'org.assertj:assertj-core:3.25.1' + testImplementation 'io.rest-assured:rest-assured:5.4.0' +} diff --git a/modules/ollama/src/main/java/org/testcontainers/ollama/OllamaContainer.java b/modules/ollama/src/main/java/org/testcontainers/ollama/OllamaContainer.java new file mode 100644 index 00000000000..4a5f69c2ada --- /dev/null +++ b/modules/ollama/src/main/java/org/testcontainers/ollama/OllamaContainer.java @@ -0,0 +1,24 @@ +package org.testcontainers.ollama; + +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.utility.DockerImageName; + +public class OllamaContainer extends GenericContainer { + + private static final DockerImageName DOCKER_IMAGE_NAME = DockerImageName.parse("ollama/ollama"); + + public OllamaContainer(String image) { + this(DockerImageName.parse(image)); + } + + public OllamaContainer(DockerImageName dockerImageName) { + super(dockerImageName); + dockerImageName.assertCompatibleWith(DOCKER_IMAGE_NAME); + + withExposedPorts(11434); + } + + public String getEndpoint() { + return "http://" + getHost() + ":" + getMappedPort(11434); + } +} diff --git a/modules/ollama/src/test/java/org/testcontainers/ollama/OllamaContainerTest.java b/modules/ollama/src/test/java/org/testcontainers/ollama/OllamaContainerTest.java new file mode 100644 index 00000000000..8c1e96870e4 --- /dev/null +++ b/modules/ollama/src/test/java/org/testcontainers/ollama/OllamaContainerTest.java @@ -0,0 +1,26 @@ +package org.testcontainers.ollama; + +import org.junit.Test; + +import static io.restassured.RestAssured.given; +import static org.assertj.core.api.Assertions.assertThat; + +public class OllamaContainerTest { + + @Test + public void containerStart() { + try ( // container { + OllamaContainer ollamaContainer = new OllamaContainer("ollama/ollama:0.1.26") + // } + ) { + ollamaContainer.start(); + + String version = given() + .baseUri(ollamaContainer.getEndpoint()) + .get("/api/version") + .jsonPath() + .get("version"); + assertThat(version).isEqualTo("0.1.26"); + } + } +} diff --git a/modules/ollama/src/test/resources/logback-test.xml b/modules/ollama/src/test/resources/logback-test.xml new file mode 100644 index 00000000000..83ef7a1a3ef --- /dev/null +++ b/modules/ollama/src/test/resources/logback-test.xml @@ -0,0 +1,16 @@ + + + + + + %d{HH:mm:ss.SSS} %-5level %logger - %msg%n + + + + + + + + + From 3a63c518626cfdf32004b973ee6c890d70c3879b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez?= Date: Mon, 26 Feb 2024 17:26:24 -0600 Subject: [PATCH 2/6] Enable gpu if available --- .../ollama/OllamaContainer.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/modules/ollama/src/main/java/org/testcontainers/ollama/OllamaContainer.java b/modules/ollama/src/main/java/org/testcontainers/ollama/OllamaContainer.java index 4a5f69c2ada..d141571094f 100644 --- a/modules/ollama/src/main/java/org/testcontainers/ollama/OllamaContainer.java +++ b/modules/ollama/src/main/java/org/testcontainers/ollama/OllamaContainer.java @@ -1,8 +1,14 @@ package org.testcontainers.ollama; +import com.github.dockerjava.api.model.DeviceRequest; +import com.github.dockerjava.api.model.Info; +import com.github.dockerjava.api.model.RuntimeInfo; import org.testcontainers.containers.GenericContainer; import org.testcontainers.utility.DockerImageName; +import java.util.Arrays; +import java.util.Map; + public class OllamaContainer extends GenericContainer { private static final DockerImageName DOCKER_IMAGE_NAME = DockerImageName.parse("ollama/ollama"); @@ -15,6 +21,21 @@ public OllamaContainer(DockerImageName dockerImageName) { super(dockerImageName); dockerImageName.assertCompatibleWith(DOCKER_IMAGE_NAME); + Info info = this.dockerClient.infoCmd().exec(); + Map runtimes = info.getRuntimes(); + if (runtimes != null) { + if (runtimes.containsKey("nvidia")) { + withCreateContainerCmdModifier(cmd -> { + cmd + .getHostConfig() + .withDeviceRequests( + Arrays.asList( + new DeviceRequest().withCapabilities(Arrays.asList(Arrays.asList("gpu"))).withCount(-1) + ) + ); + }); + } + } withExposedPorts(11434); } From 625dfec5bedb86537e9116df2250bbc9de617c19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez?= Date: Thu, 29 Feb 2024 19:52:06 -0600 Subject: [PATCH 3/6] Add createImage --- .../ollama/OllamaContainer.java | 40 +++++++++++++++++-- .../ollama/OllamaContainerTest.java | 34 ++++++++++++++++ 2 files changed, 71 insertions(+), 3 deletions(-) diff --git a/modules/ollama/src/main/java/org/testcontainers/ollama/OllamaContainer.java b/modules/ollama/src/main/java/org/testcontainers/ollama/OllamaContainer.java index d141571094f..7c4ba4a8367 100644 --- a/modules/ollama/src/main/java/org/testcontainers/ollama/OllamaContainer.java +++ b/modules/ollama/src/main/java/org/testcontainers/ollama/OllamaContainer.java @@ -1,14 +1,25 @@ package org.testcontainers.ollama; +import com.github.dockerjava.api.DockerClient; import com.github.dockerjava.api.model.DeviceRequest; +import com.github.dockerjava.api.model.Image; import com.github.dockerjava.api.model.Info; import com.github.dockerjava.api.model.RuntimeInfo; +import org.testcontainers.DockerClientFactory; import org.testcontainers.containers.GenericContainer; import org.testcontainers.utility.DockerImageName; -import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Map; +/** + * Testcontainers implementation for Ollama. + *

+ * Supported image: {@code ollama/ollama} + *

+ * Exposed ports: 11434 + */ public class OllamaContainer extends GenericContainer { private static final DockerImageName DOCKER_IMAGE_NAME = DockerImageName.parse("ollama/ollama"); @@ -29,8 +40,10 @@ public OllamaContainer(DockerImageName dockerImageName) { cmd .getHostConfig() .withDeviceRequests( - Arrays.asList( - new DeviceRequest().withCapabilities(Arrays.asList(Arrays.asList("gpu"))).withCount(-1) + Collections.singletonList( + new DeviceRequest() + .withCapabilities(Collections.singletonList(Collections.singletonList("gpu"))) + .withCount(-1) ) ); }); @@ -39,6 +52,27 @@ public OllamaContainer(DockerImageName dockerImageName) { withExposedPorts(11434); } + /** + * Create a new image from the container + * @param imageName the name of the new image + */ + public void createImage(String imageName) { + DockerImageName dockerImageName = DockerImageName.parse(getDockerImageName()); + if (!dockerImageName.equals(DockerImageName.parse(imageName))) { + DockerClient dockerClient = DockerClientFactory.instance().client(); + List images = dockerClient.listImagesCmd().withReferenceFilter(imageName).exec(); + if (images.isEmpty()) { + DockerImageName imageModel = DockerImageName.parse(imageName); + dockerClient + .commitCmd(getContainerId()) + .withRepository(imageModel.getUnversionedPart()) + .withLabels(Collections.singletonMap("org.testcontainers.sessionId", "")) + .withTag(imageModel.getVersionPart()) + .exec(); + } + } + } + public String getEndpoint() { return "http://" + getHost() + ":" + getMappedPort(11434); } diff --git a/modules/ollama/src/test/java/org/testcontainers/ollama/OllamaContainerTest.java b/modules/ollama/src/test/java/org/testcontainers/ollama/OllamaContainerTest.java index 8c1e96870e4..9152a009fdb 100644 --- a/modules/ollama/src/test/java/org/testcontainers/ollama/OllamaContainerTest.java +++ b/modules/ollama/src/test/java/org/testcontainers/ollama/OllamaContainerTest.java @@ -1,6 +1,10 @@ package org.testcontainers.ollama; import org.junit.Test; +import org.testcontainers.utility.Base58; +import org.testcontainers.utility.DockerImageName; + +import java.io.IOException; import static io.restassured.RestAssured.given; import static org.assertj.core.api.Assertions.assertThat; @@ -23,4 +27,34 @@ public void containerStart() { assertThat(version).isEqualTo("0.1.26"); } } + + @Test + public void downloadModelAndCreateImage() throws IOException, InterruptedException { + String newImageName = "tc-ollama-allminilm-" + Base58.randomString(4).toLowerCase(); + try (OllamaContainer ollama = new OllamaContainer("ollama/ollama:0.1.26")) { + ollama.start(); + ollama.execInContainer("ollama", "pull", "all-minilm"); + + String modelName = given() + .baseUri(ollama.getEndpoint()) + .get("/api/tags") + .jsonPath() + .getString("models[0].name"); + assertThat(modelName).contains("all-minilm"); + ollama.createImage(newImageName); + } + try ( + OllamaContainer ollama = new OllamaContainer( + DockerImageName.parse(newImageName).asCompatibleSubstituteFor("ollama/ollama") + ) + ) { + ollama.start(); + String modelName = given() + .baseUri(ollama.getEndpoint()) + .get("/api/tags") + .jsonPath() + .getString("models[0].name"); + assertThat(modelName).contains("all-minilm"); + } + } } From d1fb50c9344b3623dd0233795d39d3d5223ee75b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez=20Gonzales?= Date: Fri, 1 Mar 2024 17:16:47 -0600 Subject: [PATCH 4/6] Update modules/ollama/src/main/java/org/testcontainers/ollama/OllamaContainer.java Co-authored-by: Kevin Wittek --- .../main/java/org/testcontainers/ollama/OllamaContainer.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/ollama/src/main/java/org/testcontainers/ollama/OllamaContainer.java b/modules/ollama/src/main/java/org/testcontainers/ollama/OllamaContainer.java index 7c4ba4a8367..cd54ab32ae2 100644 --- a/modules/ollama/src/main/java/org/testcontainers/ollama/OllamaContainer.java +++ b/modules/ollama/src/main/java/org/testcontainers/ollama/OllamaContainer.java @@ -53,7 +53,8 @@ public OllamaContainer(DockerImageName dockerImageName) { } /** - * Create a new image from the container + * Commits the current file system changes in the container into a new image. + * Should be used for creating an image that contains a loaded model. * @param imageName the name of the new image */ public void createImage(String imageName) { From d7879170ae0da917484cba67022341a54901cb93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez?= Date: Fri, 1 Mar 2024 17:19:26 -0600 Subject: [PATCH 5/6] Rename to commitToImage --- .../main/java/org/testcontainers/ollama/OllamaContainer.java | 4 ++-- .../java/org/testcontainers/ollama/OllamaContainerTest.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/ollama/src/main/java/org/testcontainers/ollama/OllamaContainer.java b/modules/ollama/src/main/java/org/testcontainers/ollama/OllamaContainer.java index cd54ab32ae2..1c739b95a6b 100644 --- a/modules/ollama/src/main/java/org/testcontainers/ollama/OllamaContainer.java +++ b/modules/ollama/src/main/java/org/testcontainers/ollama/OllamaContainer.java @@ -53,11 +53,11 @@ public OllamaContainer(DockerImageName dockerImageName) { } /** - * Commits the current file system changes in the container into a new image. + * Commits the current file system changes in the container into a new image. * Should be used for creating an image that contains a loaded model. * @param imageName the name of the new image */ - public void createImage(String imageName) { + public void commitToImage(String imageName) { DockerImageName dockerImageName = DockerImageName.parse(getDockerImageName()); if (!dockerImageName.equals(DockerImageName.parse(imageName))) { DockerClient dockerClient = DockerClientFactory.instance().client(); diff --git a/modules/ollama/src/test/java/org/testcontainers/ollama/OllamaContainerTest.java b/modules/ollama/src/test/java/org/testcontainers/ollama/OllamaContainerTest.java index 9152a009fdb..25712c55e15 100644 --- a/modules/ollama/src/test/java/org/testcontainers/ollama/OllamaContainerTest.java +++ b/modules/ollama/src/test/java/org/testcontainers/ollama/OllamaContainerTest.java @@ -29,7 +29,7 @@ public void containerStart() { } @Test - public void downloadModelAndCreateImage() throws IOException, InterruptedException { + public void downloadModelAndCommitToImage() throws IOException, InterruptedException { String newImageName = "tc-ollama-allminilm-" + Base58.randomString(4).toLowerCase(); try (OllamaContainer ollama = new OllamaContainer("ollama/ollama:0.1.26")) { ollama.start(); @@ -41,7 +41,7 @@ public void downloadModelAndCreateImage() throws IOException, InterruptedExcepti .jsonPath() .getString("models[0].name"); assertThat(modelName).contains("all-minilm"); - ollama.createImage(newImageName); + ollama.commitToImage(newImageName); } try ( OllamaContainer ollama = new OllamaContainer( From 8b8c2e4dd9db81f32bf4554cbc012193fa47e72a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez?= Date: Fri, 1 Mar 2024 18:12:31 -0600 Subject: [PATCH 6/6] Fix docs --- .../testcontainers/ollama/OllamaContainerTest.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/modules/ollama/src/test/java/org/testcontainers/ollama/OllamaContainerTest.java b/modules/ollama/src/test/java/org/testcontainers/ollama/OllamaContainerTest.java index 25712c55e15..d33660b40b0 100644 --- a/modules/ollama/src/test/java/org/testcontainers/ollama/OllamaContainerTest.java +++ b/modules/ollama/src/test/java/org/testcontainers/ollama/OllamaContainerTest.java @@ -12,18 +12,14 @@ public class OllamaContainerTest { @Test - public void containerStart() { + public void withDefaultConfig() { try ( // container { - OllamaContainer ollamaContainer = new OllamaContainer("ollama/ollama:0.1.26") + OllamaContainer ollama = new OllamaContainer("ollama/ollama:0.1.26") // } ) { - ollamaContainer.start(); + ollama.start(); - String version = given() - .baseUri(ollamaContainer.getEndpoint()) - .get("/api/version") - .jsonPath() - .get("version"); + String version = given().baseUri(ollama.getEndpoint()).get("/api/version").jsonPath().get("version"); assertThat(version).isEqualTo("0.1.26"); } }