Skip to content

Commit

Permalink
Adding Dapr configuration support to Testcontainers (#1148)
Browse files Browse the repository at this point in the history
* Adding Dapr configuration support to Testcontainers

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

* Fixing a small styling issue

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

* Fixing Dapr Testcontainer Component test

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

* Fix Dapr Testcontainer Subscription test

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

* Fix Darp Testcontainer Configuration test

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

* Fix expected YAML structure

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

* Fix Configuration test

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

* Fix Dapr Component test

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

---------

Signed-off-by: Artur Ciocanu <ciocanu@adobe.com>
Co-authored-by: Artur Ciocanu <ciocanu@adobe.com>
  • Loading branch information
artur-ciocanu and Artur Ciocanu authored Oct 17, 2024
1 parent 7dcab0b commit b1196d3
Show file tree
Hide file tree
Showing 15 changed files with 549 additions and 167 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright 2024 The Dapr Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
limitations under the License.
*/

package io.dapr.testcontainers;

/**
* Represents a Dapr component.
*/
public class Configuration {
private final String name;
private final TracingConfigurationSettings tracing;

//@TODO: add httpPipeline
//@TODO: add secrets
//@TODO: add components
//@TODO: add accessControl

/**
* Creates a new configuration.
* @param name Configuration name.
* @param tracing TracingConfigParameters tracing configuration parameters.
*/
public Configuration(String name, TracingConfigurationSettings tracing) {
this.name = name;
this.tracing = tracing;
}

public String getName() {
return name;
}

public TracingConfigurationSettings getTracing() {
return tracing;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.dapr.testcontainers;

// This is a marker interface, so we could get
// a list of all the configuration settings implementations
public interface ConfigurationSettings {
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,49 +13,54 @@

package io.dapr.testcontainers;

import io.dapr.testcontainers.converter.ComponentYamlConverter;
import io.dapr.testcontainers.converter.ConfigurationYamlConverter;
import io.dapr.testcontainers.converter.SubscriptionYamlConverter;
import io.dapr.testcontainers.converter.YamlConverter;
import io.dapr.testcontainers.converter.YamlMapperFactory;
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;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.nodes.Tag;
import org.yaml.snakeyaml.representer.Representer;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

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 DaprProtocol DAPR_PROTOCOL = DaprProtocol.HTTP;
private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("daprio/daprd");
private static final Yaml YAML_MAPPER = YamlMapperFactory.create();
private static final YamlConverter<Component> COMPONENT_CONVERTER = new ComponentYamlConverter(YAML_MAPPER);
private static final YamlConverter<Subscription> SUBSCRIPTION_CONVERTER = new SubscriptionYamlConverter(YAML_MAPPER);
private static final YamlConverter<Configuration> CONFIGURATION_CONVERTER = new ConfigurationYamlConverter(
YAML_MAPPER);
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;
private String appName;
private Integer appPort = null;
private DaprLogLevel daprLogLevel = DaprLogLevel.INFO;
private String appChannelAddress = "localhost";
private String placementService = "placement";
private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("daprio/daprd");
private static final Yaml yaml = getYamlMapper();
private DaprPlacementContainer placementContainer;
private String placementDockerImageName = "daprio/placement";

private Configuration configuration;
private DaprPlacementContainer placementContainer;
private String appName;
private Integer appPort;
private boolean shouldReusePlacement;

/**
Expand All @@ -78,6 +83,10 @@ public DaprContainer(String image) {
this(DockerImageName.parse(image));
}

public Configuration getConfiguration() {
return configuration;
}

public Set<Component> getComponents() {
return components;
}
Expand All @@ -91,6 +100,16 @@ public DaprContainer withAppPort(Integer port) {
return this;
}

public DaprContainer withAppChannelAddress(String appChannelAddress) {
this.appChannelAddress = appChannelAddress;
return this;
}

public DaprContainer withConfiguration(Configuration configuration) {
this.configuration = configuration;
return this;
}

public DaprContainer withPlacementService(String placementService) {
this.placementService = placementService;
return this;
Expand All @@ -111,6 +130,21 @@ public DaprContainer withSubscription(Subscription subscription) {
return this;
}

public DaprContainer withPlacementImage(String placementDockerImageName) {
this.placementDockerImageName = placementDockerImageName;
return this;
}

public DaprContainer withReusablePlacement(boolean reuse) {
this.shouldReusePlacement = reuse;
return this;
}

public DaprContainer withPlacementContainer(DaprPlacementContainer placementContainer) {
this.placementContainer = placementContainer;
return this;
}

public DaprContainer withComponent(Component component) {
components.add(component);
return this;
Expand All @@ -123,7 +157,7 @@ public DaprContainer withComponent(Component component) {
*/
public DaprContainer withComponent(Path path) {
try {
Map<String, Object> component = this.yaml.loadAs(Files.newInputStream(path), Map.class);
Map<String, Object> component = this.YAML_MAPPER.loadAs(Files.newInputStream(path), Map.class);

String type = (String) component.get("type");
Map<String, Object> metadata = (Map<String, Object>) component.get("metadata");
Expand Down Expand Up @@ -165,60 +199,6 @@ public String getGrpcEndpoint() {
return ":" + getMappedPort(DAPRD_DEFAULT_GRPC_PORT);
}

public DaprContainer withAppChannelAddress(String appChannelAddress) {
this.appChannelAddress = appChannelAddress;
return this;
}

/**
* Get a map of Dapr component details.
* @param component A Dapr Component.
* @return Map of component details.
*/
public Map<String, Object> componentToMap(Component component) {
Map<String, Object> componentProps = new HashMap<>();
componentProps.put("apiVersion", "dapr.io/v1alpha1");
componentProps.put("kind", "Component");

Map<String, String> componentMetadata = new LinkedHashMap<>();
componentMetadata.put("name", component.getName());
componentProps.put("metadata", componentMetadata);

Map<String, Object> componentSpec = new HashMap<>();
componentSpec.put("type", component.getType());
componentSpec.put("version", component.getVersion());

if (!component.getMetadata().isEmpty()) {
componentSpec.put("metadata", component.getMetadata());
}

componentProps.put("spec", componentSpec);
return Collections.unmodifiableMap(componentProps);
}

/**
* Get a map of Dapr subscription details.
* @param subscription A Dapr Subscription.
* @return Map of subscription details.
*/
public Map<String, Object> subscriptionToMap(Subscription subscription) {
Map<String, Object> subscriptionProps = new HashMap<>();
subscriptionProps.put("apiVersion", "dapr.io/v1alpha1");
subscriptionProps.put("kind", "Subscription");

Map<String, String> subscriptionMetadata = new LinkedHashMap<>();
subscriptionMetadata.put("name", subscription.getName());
subscriptionProps.put("metadata", subscriptionMetadata);

Map<String, Object> subscriptionSpec = new HashMap<>();
subscriptionSpec.put("pubsubname", subscription.getPubsubName());
subscriptionSpec.put("topic", subscription.getTopic());
subscriptionSpec.put("route", subscription.getRoute());

subscriptionProps.put("spec", subscriptionSpec);
return Collections.unmodifiableMap(subscriptionProps);
}

@Override
protected void configure() {
super.configure();
Expand All @@ -241,7 +221,7 @@ protected void configure() {
cmds.add(appName);
cmds.add("--dapr-listen-addresses=0.0.0.0");
cmds.add("--app-protocol");
cmds.add(protocol.getName());
cmds.add(DAPR_PROTOCOL.getName());
cmds.add("-placement-host-address");
cmds.add(placementService + ":50005");

Expand All @@ -257,10 +237,15 @@ protected void configure() {

cmds.add("--log-level");
cmds.add(daprLogLevel.toString());
cmds.add("-components-path");
cmds.add("--resources-path");
cmds.add("/dapr-resources");
withCommand(cmds.toArray(new String[]{}));

if (configuration != null) {
String configurationYaml = CONFIGURATION_CONVERTER.convert(configuration);
withCopyToContainer(Transferable.of(configurationYaml), "/dapr-resources/" + configuration.getName() + ".yaml");
}

if (components.isEmpty()) {
components.add(new Component("kvstore", "state.in-memory", "v1", Collections.emptyMap()));
components.add(new Component("pubsub", "pubsub.in-memory", "v1", Collections.emptyMap()));
Expand All @@ -271,28 +256,18 @@ protected void configure() {
}

for (Component component : components) {
String componentYaml = componentToYaml(component);
String componentYaml = COMPONENT_CONVERTER.convert(component);
withCopyToContainer(Transferable.of(componentYaml), "/dapr-resources/" + component.getName() + ".yaml");
}

for (Subscription subscription : subscriptions) {
String subscriptionYaml = subscriptionToYaml(subscription);
String subscriptionYaml = SUBSCRIPTION_CONVERTER.convert(subscription);
withCopyToContainer(Transferable.of(subscriptionYaml), "/dapr-resources/" + subscription.getName() + ".yaml");
}

dependsOn(placementContainer);
}

public String subscriptionToYaml(Subscription subscription) {
Map<String, Object> subscriptionMap = subscriptionToMap(subscription);
return yaml.dumpAsMap(subscriptionMap);
}

public String componentToYaml(Component component) {
Map<String, Object> componentMap = componentToMap(component);
return yaml.dumpAsMap(componentMap);
}

public String getAppName() {
return appName;
}
Expand All @@ -313,21 +288,6 @@ public static DockerImageName getDefaultImageName() {
return DEFAULT_IMAGE_NAME;
}

public DaprContainer withPlacementImage(String placementDockerImageName) {
this.placementDockerImageName = placementDockerImageName;
return this;
}

public DaprContainer withReusablePlacement(boolean reuse) {
this.shouldReusePlacement = reuse;
return this;
}

public DaprContainer withPlacementContainer(DaprPlacementContainer placementContainer) {
this.placementContainer = placementContainer;
return this;
}

// Required by spotbugs plugin
@Override
public boolean equals(Object o) {
Expand All @@ -338,13 +298,4 @@ public boolean equals(Object o) {
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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package io.dapr.testcontainers;

/**
* Configuration settings for Otel tracing.
*/
public class OtelTracingConfigurationSettings implements ConfigurationSettings {
private final String endpointAddress;
private final Boolean isSecure;
private final String protocol;

/**
* Creates a new configuration.
* @param endpointAddress tracing endpoint address
* @param isSecure if the endpoint is secure
* @param protocol tracing protocol
*/
public OtelTracingConfigurationSettings(String endpointAddress, Boolean isSecure, String protocol) {
this.endpointAddress = endpointAddress;
this.isSecure = isSecure;
this.protocol = protocol;
}

public String getEndpointAddress() {
return endpointAddress;
}

public Boolean getSecure() {
return isSecure;
}

public String getProtocol() {
return protocol;
}
}
Loading

0 comments on commit b1196d3

Please sign in to comment.