Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions docs/modules/nacos.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Hashicorp Nacos Module

Testcontainers module for [Nacos](https://github.com/alibaba/nacos). Nacos an easy-to-use dynamic service discovery, configuration and service management platform for building AI cloud native applications. More information on Nacos [here](https://nacos.io/docs/latest/overview/).

## Usage example

<!--codeinclude-->
[Running Nacos in your Junit tests](../../modules/nacos/src/test/java/org/testcontainers/nacos/NacosContainerTest.java)
<!--/codeinclude-->

## Why Nacos in Junit tests?

With the increasing popularity of Nacos and config externalization, applications are now needing to source properties from Nacos.
This can prove challenging in the development phase without a running Nacos instance readily on hand. This library
aims to solve your apps integration testing with Nacos. You can also use it to
test how your application behaves with Nacos by writing different test scenarios in Junit.

## Adding this module to your project dependencies

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

=== "Gradle"
```groovy
testImplementation "org.testcontainers:nacos:{{latest_version}}"
```

=== "Maven"
```xml
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>nacos</artifactId>
<version>{{latest_version}}</version>
<scope>test</scope>
</dependency>
```
16 changes: 16 additions & 0 deletions modules/nacos/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
description = "Testcontainers :: Nacos"

dependencies {
api project(':testcontainers')

testRuntimeOnly 'org.junit.platform:junit-platform-launcher:1.11.0'

testImplementation 'org.junit.jupiter:junit-jupiter:5.13.4'
testImplementation 'com.alibaba.nacos:nacos-client:3.0.3'
testImplementation 'io.rest-assured:rest-assured:5.5.6'
testImplementation 'org.assertj:assertj-core:3.27.4'
}

test {
useJUnitPlatform()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package org.testcontainers.nacos;

import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.utility.DockerImageName;

/**
* Testcontainers implementation for Nacos.
* <p>
* Supported images: {@code nacos/nacos-server}, {@code nacos}
* <p>
* Exposed ports:
* <ul>
* <li>HTTP: 8848</li>
* <li>HTTP: 8080</li>
* <li>gRPC: 9848</li>
* </ul>
*
*/
public class NacosContainer extends GenericContainer<NacosContainer> {

private static final DockerImageName DEFAULT_OLD_IMAGE_NAME = DockerImageName.parse("nacos");

private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("nacos/nacos-server");

private static final int NACOS_HTTP_ADMIN_PORT = 8848;

private static final int NACOS_HTTP_CONSOLE_PORT = 8080;

private static final int NACOS_GRPC_PORT = 9848;

public NacosContainer(String dockerImageName) {
this(DockerImageName.parse(dockerImageName), 38848, 38080, 39848);
}

public NacosContainer(final DockerImageName dockerImageName, int adminPort, int consolePort, int grpcPort) {
super(dockerImageName);
dockerImageName.assertCompatibleWith(DEFAULT_OLD_IMAGE_NAME, DEFAULT_IMAGE_NAME);

// Wait until the Nacos server is ready to accept requests.
// Visit the login page to verify if nacos is running.
setWaitStrategy(Wait.forHttp("/#/login").forPort(NACOS_HTTP_CONSOLE_PORT).forStatusCode(200));

// According to Nacos' design, the gRPC client port adds 1000 to the main port, which means that if the main port is 8849, the gRPC port defaults to 9849
addFixedExposedPort(adminPort, NACOS_HTTP_ADMIN_PORT);
addFixedExposedPort(consolePort, NACOS_HTTP_CONSOLE_PORT);
addFixedExposedPort(grpcPort, NACOS_GRPC_PORT);

// Configure Nacos for single machine startup.
withEnv("MODE", "standalone");
// Nacos is used to generate keys for JWT tokens, using strings longer than 32 characters and then encoded with Base64.
withEnv("NACOS_AUTH_TOKEN", "SecretKey012345678901234567890123456789012345678901234567890123456789");
// The key for the identity identifier of the Inner API between Nacos servers is required.
withEnv("NACOS_AUTH_IDENTITY_KEY", "serverIdentity");
// The value of the identity identifier for the Inner API between Nacos servers is required.
withEnv("NACOS_AUTH_IDENTITY_VALUE", "security");
}

public String getServerAddr() {
return String.format("%s:%s", this.getHost(), this.getMappedPort(NACOS_HTTP_ADMIN_PORT));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package org.testcontainers.nacos;

import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.exception.NacosException;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.Properties;

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

class NacosContainerTest {

private ConfigService configService;

private static NacosContainer nacos = new NacosContainer("nacos/nacos-server:v3.0.3");

@BeforeAll
static void setup() {
nacos.start();
}

@AfterAll
static void teardown() {
nacos.stop();
}

@BeforeEach
void init() throws NacosException {
Properties properties = new Properties();
properties.put(PropertyKeyConst.SERVER_ADDR, nacos.getServerAddr());
properties.put(PropertyKeyConst.USERNAME, "nacos");
properties.put(PropertyKeyConst.PASSWORD, "nacos");
configService = NacosFactory.createConfigService(properties);
}


@Test
void writeAndRemoveValue() throws NacosException, InterruptedException {
assertThat(configService.publishConfig("test.yaml", "DEFAULT", "name: 123")).isTrue();
Thread.sleep(1500);
assertThat(configService.getConfig("test.yaml", "DEFAULT", 5000)).isEqualTo("name: 123");
assertThat(configService.removeConfig("test.yaml", "DEFAULT")).isTrue();
Thread.sleep(1500);
assertThat(configService.getConfig("test.yaml", "DEFAULT", 5000)).isEqualTo(null);
}

}
16 changes: 16 additions & 0 deletions modules/nacos/src/test/resources/logback-test.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<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="INFO"/>
</configuration>