This is an automated email from the ASF dual-hosted git repository.
kfaraz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git
The following commit(s) were added to refs/heads/master by this push:
new 9ffa0218e6d Add embedded tests for running Druid in a K3s cluster
(#18425)
9ffa0218e6d is described below
commit 9ffa0218e6d4fe0b48e8cbcb99e61aa728252906
Author: Kashif Faraz <[email protected]>
AuthorDate: Thu Aug 21 13:05:10 2025 +0530
Add embedded tests for running Druid in a K3s cluster (#18425)
Changes:
- Add `K3sClusterResource`. It is similar in essence to
`DruidContainerResource`.
- Add `K3sDruidService` for each service pod using the given template
- Add `KubernetesClusterTest` which runs with the Druid image built by the
`docker-tests` GHA job
---
embedded-tests/pom.xml | 39 +++
.../embedded/docker/DruidContainerResource.java | 13 +-
.../testing/embedded/k8s/K3sClusterResource.java | 360 +++++++++++++++++++++
.../testing/embedded/k8s/K3sDruidService.java | 125 +++++++
.../embedded/k8s/KubernetesClusterDockerTest.java | 76 +++++
.../test/resources/manifests/druid-namespace.yaml | 4 +
.../test/resources/manifests/druid-service.yaml | 56 ++++
7 files changed, 672 insertions(+), 1 deletion(-)
diff --git a/embedded-tests/pom.xml b/embedded-tests/pom.xml
index b9f663429d3..a76849f02ef 100644
--- a/embedded-tests/pom.xml
+++ b/embedded-tests/pom.xml
@@ -327,6 +327,12 @@
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.testcontainers</groupId>
+ <artifactId>k3s</artifactId>
+ <version>${testcontainers.version}</version>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
@@ -339,6 +345,39 @@
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>io.fabric8</groupId>
+ <artifactId>kubernetes-model-core</artifactId>
+ <version>7.2.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>io.fabric8</groupId>
+ <artifactId>kubernetes-client-api</artifactId>
+ <version>7.2.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>io.fabric8</groupId>
+ <artifactId>kubernetes-client</artifactId>
+ <version>7.2.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.github.docker-java</groupId>
+ <artifactId>docker-java-api</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.github.docker-java</groupId>
+ <artifactId>docker-java-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.github.docker-java</groupId>
+ <artifactId>docker-java-transport-httpclient5</artifactId>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
diff --git
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/docker/DruidContainerResource.java
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/docker/DruidContainerResource.java
index fc7a18d41fd..38383c9b088 100644
---
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/docker/DruidContainerResource.java
+++
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/docker/DruidContainerResource.java
@@ -107,6 +107,17 @@ public class DruidContainerResource extends
TestcontainerResource<DruidContainer
* {@link #PROPERTY_TEST_IMAGE} for this container.
*/
public DruidContainerResource usingTestImage()
+ {
+ return usingImage(DockerImageName.parse(getTestDruidImageName()));
+ }
+
+ /**
+ * Gets the Druid image name specified by the system property
+ * {@link #PROPERTY_TEST_IMAGE}.
+ *
+ * @throws org.apache.druid.error.DruidException if the system property is
not set.
+ */
+ public static String getTestDruidImageName()
{
final String imageName = System.getProperty(PROPERTY_TEST_IMAGE);
InvalidInput.conditionalException(
@@ -118,7 +129,7 @@ public class DruidContainerResource extends
TestcontainerResource<DruidContainer
PROPERTY_TEST_IMAGE, PROPERTY_TEST_IMAGE
)
);
- return usingImage(DockerImageName.parse(imageName));
+ return imageName;
}
public DruidContainerResource addProperty(String key, String value)
diff --git
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/k8s/K3sClusterResource.java
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/k8s/K3sClusterResource.java
new file mode 100644
index 00000000000..8a28398b751
--- /dev/null
+++
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/k8s/K3sClusterResource.java
@@ -0,0 +1,360 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.druid.testing.embedded.k8s;
+
+import com.github.dockerjava.api.DockerClient;
+import com.github.dockerjava.api.exception.NotFoundException;
+import com.github.dockerjava.core.DefaultDockerClientConfig;
+import com.github.dockerjava.core.DockerClientImpl;
+import com.github.dockerjava.httpclient5.ApacheDockerHttpClient;
+import io.fabric8.kubernetes.api.model.ConfigMap;
+import io.fabric8.kubernetes.api.model.ObjectMeta;
+import io.fabric8.kubernetes.client.Config;
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.KubernetesClientBuilder;
+import io.fabric8.kubernetes.client.KubernetesClientTimeoutException;
+import io.fabric8.kubernetes.client.dsl.PodResource;
+import io.fabric8.kubernetes.client.utils.Serialization;
+import org.apache.druid.java.util.common.FileUtils;
+import org.apache.druid.java.util.common.ISE;
+import org.apache.druid.java.util.common.StringUtils;
+import org.apache.druid.java.util.common.io.Closer;
+import org.apache.druid.java.util.common.logger.Logger;
+import org.apache.druid.testing.embedded.EmbeddedDruidCluster;
+import org.apache.druid.testing.embedded.TestFolder;
+import org.apache.druid.testing.embedded.TestcontainerResource;
+import org.apache.druid.testing.embedded.docker.DruidContainerResource;
+import org.apache.druid.testing.embedded.indexing.Resources;
+import org.apache.druid.testing.tools.ITRetryUtil;
+import org.testcontainers.k3s.K3sContainer;
+import org.testcontainers.utility.DockerImageName;
+import org.testcontainers.utility.MountableFile;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * {@link TestcontainerResource} to run a K3s cluster which can launch Druid
pods.
+ */
+public class K3sClusterResource extends TestcontainerResource<K3sContainer>
+{
+ private static final Logger log = new Logger(K3sClusterResource.class);
+
+ private static final String K3S_IMAGE_NAME = "rancher/k3s:v1.28.8-k3s1";
+
+ public static final String DRUID_NAMESPACE = "druid";
+ private static final String NAMESPACE_MANIFEST =
"manifests/druid-namespace.yaml";
+
+ private static final String COMMON_CONFIG_MAP = "druid-common-props";
+ private static final String SERVICE_CONFIG_MAP = "druid-%s-props";
+
+ public static final long POD_READY_TIMEOUT_SECONDS = 300;
+
+ private final List<File> manifestFiles = new ArrayList<>();
+ private final List<K3sDruidService> services = new ArrayList<>();
+
+ private KubernetesClient client;
+ private String druidImageName;
+
+ private final Closer closer = Closer.create();
+
+ public K3sClusterResource()
+ {
+ // Add the namespace manifest
+ manifestFiles.add(Resources.getFileForResource(NAMESPACE_MANIFEST));
+ }
+
+ public K3sClusterResource addService(K3sDruidService service)
+ {
+ services.add(service);
+ return this;
+ }
+
+ public K3sClusterResource usingDruidImage(String druidImageName)
+ {
+ this.druidImageName = druidImageName;
+ return this;
+ }
+
+ /**
+ * Uses the Docker test image specified by the system property
+ * {@link DruidContainerResource#PROPERTY_TEST_IMAGE} for the Druid pods.
+ */
+ public K3sClusterResource usingTestImage()
+ {
+ return usingDruidImage(DruidContainerResource.getTestDruidImageName());
+ }
+
+ @Override
+ protected K3sContainer createContainer()
+ {
+ Objects.requireNonNull(druidImageName, "No Druid image specified");
+ final K3sContainer container = new
K3sContainer(DockerImageName.parse(K3S_IMAGE_NAME));
+
+ final List<String> portBindings = new ArrayList<>();
+ for (K3sDruidService service : services) {
+ for (int port : service.getCommand().getExposedPorts()) {
+ container.addExposedPorts(port);
+
+ // Bind the ports statically (rather than using a mapped port) so that
this
+ // Druid service is discoverable with the Druid service discovery
+ portBindings.add(port + ":" + port);
+ }
+ }
+
+ container.setPortBindings(portBindings);
+ return container;
+ }
+
+ @Override
+ public void onStarted(EmbeddedDruidCluster cluster)
+ {
+ client = new KubernetesClientBuilder()
+ .withConfig(Config.fromKubeconfig(getContainer().getKubeConfigYaml()))
+ .build();
+ closer.register(client);
+
+ loadLocalDockerImageIntoContainer(druidImageName, cluster.getTestFolder());
+
+ manifestFiles.forEach(this::applyManifest);
+
+ // Create common config map
+ final Properties commonProperties = new Properties();
+ commonProperties.putAll(cluster.getCommonProperties());
+ commonProperties.remove("druid.extensions.modulesForEmbeddedTests");
+ applyConfigMap(
+ newConfigMap(COMMON_CONFIG_MAP, commonProperties,
"common.runtime.properties")
+ );
+
+ // Create config maps and manifests for each service
+ for (K3sDruidService druidService : services) {
+ final String serviceConfigMap = StringUtils.format(SERVICE_CONFIG_MAP,
druidService.getName());
+ applyConfigMap(
+ newConfigMap(serviceConfigMap, druidService.getProperties(),
"runtime.properties")
+ );
+ applyManifest(druidService);
+ }
+
+ // Wait for all pods to be ready and services to be healthy
+
client.pods().inNamespace(DRUID_NAMESPACE).resources().forEach(this::waitUntilPodIsReady);
+ services.forEach(this::waitUntilServiceIsHealthy);
+ }
+
+ @Override
+ public void stop()
+ {
+ try {
+ closer.close();
+ }
+ catch (Exception e) {
+ log.error(e, "Could not close resources");
+ }
+ super.stop();
+ }
+
+ /**
+ * Loads the given Docker image from the host Docker to the container. If the
+ * image does not exist in the host Docker, the image will be pulled by the
+ * K3s container itself.
+ */
+ private void loadLocalDockerImageIntoContainer(String localImageName,
TestFolder testFolder)
+ {
+ ensureRunning();
+
+ final File tempDir = testFolder.getOrCreateFolder("druid-k3s-image");
+ final File tarFile = new File(tempDir, "druid-image.tar");
+
+ final DefaultDockerClientConfig config =
DefaultDockerClientConfig.createDefaultConfigBuilder().build();
+
+ try (
+ final ApacheDockerHttpClient httpClient = new ApacheDockerHttpClient
+ .Builder()
+ .dockerHost(config.getDockerHost())
+ .build();
+ final DockerClient dockerClient = DockerClientImpl.getInstance(config,
httpClient);
+ final FileOutputStream tarOutputStream = new FileOutputStream(tarFile);
+ final InputStream imageInputStream =
dockerClient.saveImageCmd(localImageName).exec();
+ ) {
+ if (doesImageExistInHostDocker(localImageName, dockerClient)) {
+ log.info("Transfering image[%s] from host Docker to K3s container.",
localImageName);
+ } else {
+ log.info("Image[%s] will be pulled by K3s container as it does not
exist host Docker.", localImageName);
+ return;
+ }
+
+ imageInputStream.transferTo(tarOutputStream);
+ log.info("Saved Docker image[%s] to tar[%s].", localImageName, tarFile);
+
+ final String imagePathInContainer = "/tmp/druid-image.tar";
+ getContainer().copyFileToContainer(
+ MountableFile.forHostPath(tarFile.getAbsolutePath()),
+ imagePathInContainer
+ );
+
+ getContainer().execInContainer("ctr", "-n", "k8s.io", "images",
"import", imagePathInContainer);
+ log.info("Image[%s] loaded into K3s containerd", localImageName);
+
+ getContainer().execInContainer("rm", imagePathInContainer);
+ FileUtils.deleteDirectory(tempDir);
+ }
+ catch (Exception e) {
+ throw new ISE(e, "Failed to load local Docker image[%s]",
localImageName);
+ }
+ }
+
+ private boolean doesImageExistInHostDocker(String localImageName,
DockerClient dockerClient)
+ {
+ try {
+ dockerClient.inspectImageCmd(localImageName).exec();
+ return true;
+ }
+ catch (NotFoundException e) {
+ return false;
+ }
+ }
+
+ private void applyConfigMap(ConfigMap configMap)
+ {
+ client.configMaps()
+ .inNamespace(DRUID_NAMESPACE)
+ .resource(configMap)
+ .serverSideApply();
+ }
+
+ /**
+ * Creates and applies the manifest for the given Druid service.
+ */
+ private void applyManifest(K3sDruidService service)
+ {
+ final String manifestYaml = service.createManifestYaml(druidImageName);
+ log.info("Applying manifest for service[%s]: %s", service.getName(),
manifestYaml);
+
+ try (ByteArrayInputStream bis = new
ByteArrayInputStream(manifestYaml.getBytes(StandardCharsets.UTF_8))) {
+ client.load(bis).inNamespace(DRUID_NAMESPACE).serverSideApply();
+ log.info("Applied manifest for service[%s]", service.getName());
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Applies a YAML manifest file to the K3s container.
+ */
+ private void applyManifest(File manifest)
+ {
+ try (FileInputStream fis = new FileInputStream(manifest)) {
+ client.load(fis).inNamespace(DRUID_NAMESPACE).serverSideApply();
+ log.info("Applied manifest file[%s]", manifest);
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Waits for the given pod to be ready.
+ */
+ private void waitUntilPodIsReady(PodResource pod)
+ {
+ try {
+ pod.waitUntilCondition(
+ p -> p.getStatus() != null &&
+ p.getStatus().getConditions() != null &&
+ p.getStatus().getConditions().stream().anyMatch(
+ c -> "Ready".equals(c.getType()) &&
"True".equals(c.getStatus())
+ ),
+ POD_READY_TIMEOUT_SECONDS,
+ TimeUnit.SECONDS
+ );
+ }
+ catch (KubernetesClientTimeoutException e) {
+ throw new ISE("Timed out waiting for pod[%s] to be ready",
pod.get().getMetadata().getName());
+ }
+ }
+
+ /**
+ * Polls the health check endpoint of the given service until it is healthy.
+ */
+ private void waitUntilServiceIsHealthy(K3sDruidService service)
+ {
+ final URL url;
+ try {
+ url = new URL(service.getHealthCheckUrl());
+ }
+ catch (Exception e) {
+ throw new ISE(e, "Could not construct URL for service[%s]",
service.getName());
+ }
+
+ ITRetryUtil.retryUntilEquals(
+ () -> {
+ byte[] resp = url
+ .openConnection()
+ .getInputStream()
+ .readAllBytes();
+ String body = new String(resp, StandardCharsets.UTF_8);
+ return body.contains("true");
+ },
+ true,
+ 1_000L,
+ 100,
+ StringUtils.format("Service[%s] is healthy", service.getName())
+ );
+ }
+
+ /**
+ * Creates a new {@link ConfigMap} that can be applied to the K3s cluster.
+ */
+ private static ConfigMap newConfigMap(String name, Properties properties,
String fileName)
+ {
+ try {
+ // Serialize the properties
+ StringWriter writer = new StringWriter();
+ properties.store(writer, null);
+
+ final ConfigMap configMap = new ConfigMap();
+ ObjectMeta meta = new ObjectMeta();
+ meta.setName(name);
+ meta.setNamespace(DRUID_NAMESPACE);
+ configMap.setMetadata(meta);
+ configMap.setData(Map.of(fileName, writer.toString()));
+
+ final String configMapYaml = Serialization.asYaml(configMap);
+ log.info("Created config map[%s]: %s", name, configMapYaml);
+
+ return configMap;
+ }
+ catch (Exception e) {
+ throw new ISE(e, "Could not write config map[%s]", name);
+ }
+ }
+}
diff --git
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/k8s/K3sDruidService.java
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/k8s/K3sDruidService.java
new file mode 100644
index 00000000000..774beb69a27
--- /dev/null
+++
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/k8s/K3sDruidService.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.druid.testing.embedded.k8s;
+
+import org.apache.druid.java.util.common.IAE;
+import org.apache.druid.java.util.common.ISE;
+import org.apache.druid.java.util.common.StringUtils;
+import org.apache.druid.testing.DruidCommand;
+import org.apache.druid.testing.embedded.EmbeddedHostname;
+import org.apache.druid.testing.embedded.indexing.Resources;
+
+import java.nio.file.Files;
+import java.util.Locale;
+import java.util.Properties;
+
+/**
+ * Represents a single Druid service to be run inside a Kubernetes cluster
using
+ * {@link K3sClusterResource}.
+ */
+public class K3sDruidService
+{
+ private static final String MANIFEST_TEMPLATE =
"manifests/druid-service.yaml";
+
+ private final DruidCommand command;
+ private final Properties properties;
+
+ public K3sDruidService(DruidCommand command)
+ {
+ this.command = command;
+ this.properties = new Properties();
+
+ addProperty("druid.host", EmbeddedHostname.containerFriendly().toString());
+ command.getDefaultProperties().forEach(properties::setProperty);
+ }
+
+ public String getName()
+ {
+ return command.getName().toLowerCase(Locale.ROOT);
+ }
+
+ public DruidCommand getCommand()
+ {
+ return command;
+ }
+
+ /**
+ * Creates a manifest YAML String for this service.
+ */
+ public String createManifestYaml(String druidImage)
+ {
+ try {
+ final String template = Files.readString(
+ Resources.getFileForResource(MANIFEST_TEMPLATE).toPath()
+ );
+
+ String manifest = StringUtils.replace(template, "${service}", getName());
+ manifest = StringUtils.replace(manifest, "${command}",
command.getName());
+ manifest = StringUtils.replace(manifest, "${port}",
String.valueOf(command.getExposedPorts()[0]));
+ manifest = StringUtils.replace(manifest, "${image}", druidImage);
+ manifest = StringUtils.replace(manifest, "${serviceFolder}",
getServicePropsFolder());
+
+ return manifest;
+ }
+ catch (Exception e) {
+ throw new ISE(e, "Could not create manifest for service[%s]", command);
+ }
+ }
+
+ public Properties getProperties()
+ {
+ return properties;
+ }
+
+ public K3sDruidService addProperty(String key, String value)
+ {
+ properties.setProperty(key, value);
+ return this;
+ }
+
+ public String getHealthCheckUrl()
+ {
+ return StringUtils.format(
+ "http://%s:%s/status/health",
+ EmbeddedHostname.containerFriendly().toString(),
+ command.getExposedPorts()[0]
+ );
+ }
+
+ private String getServicePropsFolder()
+ {
+ final DruidCommand.Server server = (DruidCommand.Server) command;
+ switch (server) {
+ case COORDINATOR:
+ case OVERLORD:
+ return "master/coordinator-overlord";
+ case ROUTER:
+ return "query/router";
+ case BROKER:
+ return "query/broker";
+ case HISTORICAL:
+ return "data/historical";
+ case MIDDLE_MANAGER:
+ return "data/middleManager";
+ default:
+ throw new IAE("Unsupported command[%s]", server);
+ }
+ }
+}
diff --git
a/embedded-tests/src/test/java/org/apache/druid/testing/embedded/k8s/KubernetesClusterDockerTest.java
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/k8s/KubernetesClusterDockerTest.java
new file mode 100644
index 00000000000..e7f618a8c13
--- /dev/null
+++
b/embedded-tests/src/test/java/org/apache/druid/testing/embedded/k8s/KubernetesClusterDockerTest.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.druid.testing.embedded.k8s;
+
+import org.apache.druid.testing.DruidCommand;
+import org.apache.druid.testing.embedded.EmbeddedDruidCluster;
+import org.apache.druid.testing.embedded.docker.LatestImageDockerTest;
+import org.apache.druid.testing.embedded.indexing.IngestionSmokeTest;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+
+/**
+ * Runs some basic ingestion tests against latest image Druid containers
running
+ * on a K3s cluster.
+ */
+public class KubernetesClusterDockerTest extends IngestionSmokeTest implements
LatestImageDockerTest
+{
+ @Override
+ protected EmbeddedDruidCluster addServers(EmbeddedDruidCluster cluster)
+ {
+ final K3sDruidService brokerService = new
K3sDruidService(DruidCommand.Server.BROKER)
+ .addProperty("druid.sql.planner.metadataRefreshPeriod", "PT1s");
+
+ // Create a K3s cluster with all the required services
+ final K3sClusterResource k3sCluster = new K3sClusterResource()
+ .usingTestImage()
+ .addService(new K3sDruidService(DruidCommand.Server.COORDINATOR))
+ .addService(new K3sDruidService(DruidCommand.Server.OVERLORD))
+ .addService(new K3sDruidService(DruidCommand.Server.HISTORICAL))
+ .addService(new K3sDruidService(DruidCommand.Server.MIDDLE_MANAGER))
+ .addService(new K3sDruidService(DruidCommand.Server.ROUTER))
+ .addService(brokerService);
+
+ // Add an EmbeddedOverlord and EmbeddedBroker to use their client and
mapper bindings.
+ overlord.addProperty("druid.plaintextPort", "7090");
+ broker.addProperty("druid.plaintextPort", "7082");
+
+ return cluster
+ .useContainerFriendlyHostname()
+ .addResource(k3sCluster)
+ .addServer(overlord)
+ .addServer(broker)
+ .addServer(eventCollector)
+ .addCommonProperty(
+ "druid.extensions.loadList",
+ "[\"druid-s3-extensions\", \"druid-kafka-indexing-service\","
+ + "\"druid-multi-stage-query\", \"postgresql-metadata-storage\"]"
+ );
+ }
+
+ @BeforeEach
+ public void verifyOverlordLeader()
+ {
+ // Verify that the EmbeddedOverlord is not leader i.e. the pod Overlord is
leader
+ Assertions.assertFalse(
+ overlord.bindings().overlordLeaderSelector().isLeader()
+ );
+ }
+}
diff --git a/embedded-tests/src/test/resources/manifests/druid-namespace.yaml
b/embedded-tests/src/test/resources/manifests/druid-namespace.yaml
new file mode 100644
index 00000000000..d0f18c12d5b
--- /dev/null
+++ b/embedded-tests/src/test/resources/manifests/druid-namespace.yaml
@@ -0,0 +1,4 @@
+apiVersion: v1
+kind: Namespace
+metadata:
+ name: druid
diff --git a/embedded-tests/src/test/resources/manifests/druid-service.yaml
b/embedded-tests/src/test/resources/manifests/druid-service.yaml
new file mode 100644
index 00000000000..329298feaa7
--- /dev/null
+++ b/embedded-tests/src/test/resources/manifests/druid-service.yaml
@@ -0,0 +1,56 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: druid-${service}
+ namespace: druid
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: druid-${service}
+ template:
+ metadata:
+ labels:
+ app: druid-${service}
+ spec:
+ hostNetwork: true
+ containers:
+ - name: druid-${service}
+ image: ${image}
+ args: ["${command}"]
+ ports:
+ - containerPort: ${port}
+ env:
+ - name: DRUID_SET_HOST_IP
+ value: "0"
+ - name: DRUID_SET_HOST
+ value: "0"
+ - name: DRUID_XMS
+ value: "128m"
+ - name: DRUID_XMX
+ value: "128m"
+ volumeMounts:
+ - name: druid-common-props
+ mountPath:
/opt/druid/conf/druid/cluster/_common/common.runtime.properties
+ subPath: common.runtime.properties
+ - name: druid-${service}-props
+ mountPath:
/opt/druid/conf/druid/cluster/${serviceFolder}/runtime.properties
+ subPath: runtime.properties
+ volumes:
+ - name: druid-common-props
+ configMap:
+ name: druid-common-props
+ - name: druid-${service}-props
+ configMap:
+ name: druid-${service}-props
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: druid-${service}
+ namespace: druid
+spec:
+ selector:
+ app: druid-${service}
+ ports:
+ - port: ${port}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]