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]

Reply via email to