Adding Dapr Container default wait strategy based on healthz/outbound (#1105)

* Adding Dapr Container default wait strategy based on healthz/outbound

Signed-off-by: Artur Ciocanu <ciocanu@adobe.com>

* Ensure Testcontainers Dapr uses the alpha version

Signed-off-by: Artur Ciocanu <ciocanu@adobe.com>

---------

Signed-off-by: Artur Ciocanu <ciocanu@adobe.com>
Co-authored-by: Artur Ciocanu <ciocanu@adobe.com>
This commit is contained in:
artur-ciocanu 2024-08-26 18:29:20 +03:00 committed by GitHub
parent b808c92320
commit a6923ed75c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 92 additions and 73 deletions

View File

@ -25,6 +25,8 @@
<springboot.version>3.3.1</springboot.version>
<logback-classic.version>1.4.12</logback-classic.version>
<wiremock.version>3.9.1</wiremock.version>
<testcontainers-dapr.version>${dapr.sdk.alpha.version}</testcontainers-dapr.version>
<testcontainers-test.version>1.20.0</testcontainers-test.version>
</properties>
<dependencyManagement>
@ -214,6 +216,17 @@
<version>4.0.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.dapr</groupId>
<artifactId>testcontainers-dapr</artifactId>
<version>${testcontainers-dapr.version}</version>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${testcontainers-test.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@ -13,7 +13,7 @@ limitations under the License.
package io.dapr.it.testcontainers;
import com.github.tomakehurst.wiremock.junit.WireMockRule;
import com.github.tomakehurst.wiremock.junit5.WireMockTest;
import io.dapr.client.DaprClient;
import io.dapr.client.DaprClientBuilder;
import io.dapr.client.domain.Metadata;
@ -23,10 +23,10 @@ import io.dapr.testcontainers.DaprContainer;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.testcontainers.Testcontainers;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.io.IOException;
import java.util.Map;
@ -43,11 +43,12 @@ import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
import static com.github.tomakehurst.wiremock.client.WireMock.urlMatching;
import static com.github.tomakehurst.wiremock.client.WireMock.verify;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static java.util.Collections.singletonMap;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@Testcontainers
@WireMockTest(httpPort = 8081)
public class DaprContainerTest {
// Time-to-live for messages published.
@ -57,10 +58,7 @@ public class DaprContainerTest {
private static final String PUBSUB_NAME = "pubsub";
private static final String PUBSUB_TOPIC_NAME = "topic";
@ClassRule
public static WireMockRule wireMockRule = new WireMockRule(wireMockConfig().port(8081));
@ClassRule
@Container
public static DaprContainer daprContainer = new DaprContainer("daprio/daprd")
.withAppName("dapr-app")
.withAppPort(8081)
@ -69,16 +67,15 @@ public class DaprContainerTest {
/**
* Sets the Dapr properties for the test.
*/
@BeforeClass
@BeforeAll
public static void setDaprProperties() {
configStub();
Testcontainers.exposeHostPorts(8081);
org.testcontainers.Testcontainers.exposeHostPorts(8081);
System.setProperty("dapr.grpc.port", Integer.toString(daprContainer.getGrpcPort()));
System.setProperty("dapr.http.port", Integer.toString(daprContainer.getHttpPort()));
}
private static void configStub() {
stubFor(any(urlMatching("/dapr/subscribe"))
.willReturn(aResponse().withBody("[]").withStatus(200)));
@ -98,19 +95,20 @@ public class DaprContainerTest {
@Test
public void testDaprContainerDefaults() {
assertEquals(
"The pubsub and kvstore component should be configured by default",
2,
daprContainer.getComponents().size());
daprContainer.getComponents().size(),
"The pubsub and kvstore component should be configured by default"
);
assertEquals(
"A subscription should be configured by default if none is provided",
1,
daprContainer.getSubscriptions().size());
daprContainer.getSubscriptions().size(),
"A subscription should be configured by default if none is provided");
}
@Test
public void testStateStore() throws Exception {
try (DaprClient client = (new DaprClientBuilder()).build()) {
client.waitForSidecar(5000).block();
client.waitForSidecar(1000).block();
String value = "value";
// Save state
@ -127,7 +125,7 @@ public class DaprContainerTest {
public void testPlacement() throws Exception {
// Here we are just waiting for Dapr to be ready
try (DaprClient client = (new DaprClientBuilder()).build()) {
client.waitForSidecar(5000).block();
client.waitForSidecar(1000).block();
}
OkHttpClient client = new OkHttpClient.Builder().build();
@ -143,13 +141,12 @@ public class DaprContainerTest {
throw new IOException("Unexpected response: " + response.code());
}
}
}
@Test
public void testPubSub() throws Exception {
try (DaprClient client = (new DaprClientBuilder()).build()) {
client.waitForSidecar(5000).block();
client.waitForSidecar(1000).block();
String message = "message content";
Map<String, String> metadata = singletonMap(Metadata.TTL_IN_SECONDS, MESSAGE_TTL_IN_SECONDS);

View File

@ -11,19 +11,23 @@
limitations under the License.
*/
package io.dapr.testcontainers;
package io.dapr.it.testcontainers;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Test;
import io.dapr.testcontainers.DaprPlacementContainer;
import org.junit.jupiter.api.Test;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import static org.junit.jupiter.api.Assertions.assertEquals;
@Testcontainers
public class DaprPlacementContainerTest {
@ClassRule
public static DaprPlacementContainer placement = new DaprPlacementContainer("daprio/placement");
@Container
private static final DaprPlacementContainer PLACEMENT_CONTAINER = new DaprPlacementContainer("daprio/placement");
@Test
public void testDaprPlacementContainerDefaults() {
Assert.assertEquals("The default port is set", 50005, placement.getPort());
assertEquals(50005, PLACEMENT_CONTAINER.getPort(), "The default port is set");
}
}

View File

@ -15,6 +15,11 @@
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>

View File

@ -15,6 +15,8 @@ package io.dapr.testcontainers;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.containers.wait.strategy.WaitStrategy;
import org.testcontainers.images.builder.Transferable;
import org.testcontainers.utility.DockerImageName;
import org.yaml.snakeyaml.DumperOptions;
@ -38,6 +40,10 @@ public class DaprContainer extends GenericContainer<DaprContainer> {
private static final int DAPRD_DEFAULT_HTTP_PORT = 3500;
private static final int DAPRD_DEFAULT_GRPC_PORT = 50001;
private static final WaitStrategy WAIT_STRATEGY = Wait.forHttp("/v1.0/healthz/outbound")
.forPort(DAPRD_DEFAULT_HTTP_PORT)
.forStatusCodeMatching(statusCode -> statusCode >= 200 && statusCode <= 399);
private final Set<Component> components = new HashSet<>();
private final Set<Subscription> subscriptions = new HashSet<>();
private DaprProtocol protocol = DaprProtocol.HTTP;
@ -59,23 +65,9 @@ public class DaprContainer extends GenericContainer<DaprContainer> {
public DaprContainer(DockerImageName dockerImageName) {
super(dockerImageName);
dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);
// For susbcriptions the container needs to access the app channel
withAccessToHost(true);
// Here we don't want to wait for the Dapr sidecar to be ready, as the sidecar
// needs to
// connect with the application for susbcriptions
withExposedPorts(DAPRD_DEFAULT_HTTP_PORT, DAPRD_DEFAULT_GRPC_PORT);
}
private static Yaml getYamlMapper() {
DumperOptions options = new DumperOptions();
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
options.setPrettyFlow(true);
Representer representer = new Representer(options);
representer.addClassTag(MetadataEntry.class, Tag.MAP);
return new Yaml(representer);
setWaitStrategy(WAIT_STRATEGY);
}
/**
@ -124,8 +116,6 @@ public class DaprContainer extends GenericContainer<DaprContainer> {
return this;
}
/**
* Adds a Dapr component from a YAML file.
* @param path Path to the YAML file.
@ -197,6 +187,7 @@ public class DaprContainer extends GenericContainer<DaprContainer> {
if (!component.getMetadata().isEmpty()) {
componentSpec.put("metadata", component.getMetadata());
}
componentProps.put("spec", componentSpec);
return Collections.unmodifiableMap(componentProps);
}
@ -231,6 +222,7 @@ public class DaprContainer extends GenericContainer<DaprContainer> {
if (getNetwork() == null) {
withNetwork(Network.newNetwork());
}
if (this.placementContainer == null) {
this.placementContainer = new DaprPlacementContainer(this.placementDockerImageName)
.withNetwork(getNetwork())
@ -253,10 +245,12 @@ public class DaprContainer extends GenericContainer<DaprContainer> {
cmds.add("--app-channel-address");
cmds.add(appChannelAddress);
}
if (appPort != null) {
cmds.add("--app-port");
cmds.add(Integer.toString(appPort));
}
cmds.add("--log-level");
cmds.add(daprLogLevel.toString());
cmds.add("-components-path");
@ -338,4 +332,13 @@ public class DaprContainer extends GenericContainer<DaprContainer> {
public int hashCode() {
return super.hashCode();
}
private static Yaml getYamlMapper() {
DumperOptions options = new DumperOptions();
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
options.setPrettyFlow(true);
Representer representer = new Representer(options);
representer.addClassTag(MetadataEntry.class, Tag.MAP);
return new Yaml(representer);
}
}

View File

@ -17,7 +17,7 @@ public enum DaprProtocol {
HTTP("http"),
GRPC("grpc");
private String name;
private final String name;
DaprProtocol(String name) {
this.name = name;

View File

@ -14,10 +14,10 @@ limitations under the License.
package io.dapr.testcontainers;
public class Subscription {
private String name;
private String pubsubName;
private String topic;
private String route;
private final String name;
private final String pubsubName;
private final String topic;
private final String route;
/**
* Creates a new subscription.

View File

@ -13,8 +13,7 @@ limitations under the License.
package io.dapr.testcontainers;
import org.junit.Assert;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import java.net.URL;
import java.nio.file.Path;
@ -22,7 +21,9 @@ import java.nio.file.Paths;
import java.util.Collections;
import java.util.Set;
import static org.junit.Assert.assertThrows;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class DaprComponentTest {
@ -39,10 +40,10 @@ public class DaprComponentTest {
.withAppChannelAddress("host.testcontainers.internal");
Set<Component> components = dapr.getComponents();
Assert.assertEquals(1, components.size());
assertEquals(1, components.size());
Component kvstore = components.iterator().next();
Assert.assertEquals(false, kvstore.getMetadata().isEmpty());
assertFalse(kvstore.getMetadata().isEmpty());
String componentYaml = dapr.componentToYaml(kvstore);
String expectedComponentYaml = "metadata:\n" + " name: statestore\n"
@ -55,25 +56,21 @@ public class DaprComponentTest {
+ " type: state.in-memory\n"
+ " version: v1\n";
Assert.assertEquals(expectedComponentYaml, componentYaml);
assertEquals(expectedComponentYaml, componentYaml);
}
@Test
public void containerConfigurationTest() {
DaprContainer dapr = new DaprContainer("daprio/daprd")
.withAppName("dapr-app")
.withAppPort(8081)
.withDaprLogLevel(DaprLogLevel.DEBUG)
.withAppChannelAddress("host.testcontainers.internal");
.withAppName("dapr-app")
.withAppPort(8081)
.withDaprLogLevel(DaprLogLevel.DEBUG)
.withAppChannelAddress("host.testcontainers.internal");
dapr.configure();
assertThrows(IllegalStateException.class, () -> { dapr.getHttpEndpoint(); });
assertThrows(IllegalStateException.class, () -> { dapr.getGrpcPort(); });
assertThrows(IllegalStateException.class, dapr::getHttpEndpoint);
assertThrows(IllegalStateException.class, dapr::getGrpcPort);
}
@Test
@ -85,7 +82,7 @@ public class DaprComponentTest {
.withAppChannelAddress("host.testcontainers.internal");
Set<Subscription> subscriptions = dapr.getSubscriptions();
Assert.assertEquals(1, subscriptions.size());
assertEquals(1, subscriptions.size());
String subscriptionYaml = dapr.subscriptionToYaml(subscriptions.iterator().next());
String expectedSubscriptionYaml = "metadata:\n" + " name: my-subscription\n"
@ -95,7 +92,7 @@ public class DaprComponentTest {
+ " route: /events\n"
+ " pubsubname: pubsub\n"
+ " topic: topic\n";
Assert.assertEquals(expectedSubscriptionYaml, subscriptionYaml);
assertEquals(expectedSubscriptionYaml, subscriptionYaml);
}
@Test
@ -110,9 +107,9 @@ public class DaprComponentTest {
.withAppChannelAddress("host.testcontainers.internal");
Set<Component> components = dapr.getComponents();
Assert.assertEquals(1, components.size());
assertEquals(1, components.size());
Component kvstore = components.iterator().next();
Assert.assertEquals(false, kvstore.getMetadata().isEmpty());
assertFalse(kvstore.getMetadata().isEmpty());
String componentYaml = dapr.componentToYaml(kvstore);
String expectedComponentYaml = "metadata:\n"
@ -136,6 +133,6 @@ public class DaprComponentTest {
+ " type: null\n"
+ " version: v1\n";
Assert.assertEquals(expectedComponentYaml, componentYaml);
assertEquals(expectedComponentYaml, componentYaml);
}
}