Skip to content

Introduce an Istio test harness#6706

Open
jrhee17 wants to merge 7 commits intoline:mainfrom
jrhee17:poc/istio-step2
Open

Introduce an Istio test harness#6706
jrhee17 wants to merge 7 commits intoline:mainfrom
jrhee17:poc/istio-step2

Conversation

@jrhee17
Copy link
Copy Markdown
Contributor

@jrhee17 jrhee17 commented Apr 6, 2026

This PR is a subset of #6700

Motivation

Armeria's xDS support targets Istio as a primary deployment environment, but all existing tests use a fake/stub control plane. There is no way to catch regressions in actual Istio compatibility — Istiod's ADS bootstrap quirks, mTLS certificate distribution via SDS, Envoy sidecar injection, and so on — without running against a real cluster.

Modifications

  • it/xds-istio: a JUnit 5 test harness module that spins up a K3s cluster via Testcontainers and installs Istio into it.
  • IstioClusterExtension: a @RegisterExtension that manages K3s cluster lifecycle. Reuses an existing cluster if a kubeconfig is present and connectable; otherwise starts a fresh one.
  • IstioServerExtension: a @RegisterExtension that deploys an Armeria server workload (configured via a ServerConfigurator) into the cluster as a Kubernetes Deployment + Service with Istio sidecar injection enabled.
  • @IstioPodTest and IstioTestExtension: intercept annotated test methods and run them inside a temporary Kubernetes Pod instead of locally, propagating pass/fail and full pod logs back to JUnit.
  • IstioPodEntryPoint: the entry point for the test image. It can either execute a single JUnit test method (--class/--method) or start an Armeria server (--server-factory/--port).
  • IstioTestImage: builds a Docker image from the test runtime classpath and loads it into K3s's internal containerd store so pods can use imagePullPolicy: Never.
  • HostOnlyExtension: a base class that suppresses extension setUp/tearDown when running inside a pod, preventing recursive cluster creation.
  • EnabledIfDockerAvailable: an @ExecutionCondition that skips all tests when the Docker daemon is not reachable.
  • PodCustomizer / IstioPodCustomizer: an extension point for tests that need to customise the generated PodSpec.
  • Smoke tests: IstioStartupTest (Istiod readiness) and EnvoyDebugTest (in-cluster HTTP reachability, Envoy admin stats, config dump, bootstrap file inspection).
  • build.gradle that downloads istioctl for the host OS/arch at build time and passes cluster-related paths as environment variables to test tasks. Unsupported OS/arch combinations disable all tasks with a warning.
  • :it:xds-istio to settings.gradle and the required dependencies (testcontainers-k3s, kubernetes-httpclient-okhttp, istio version) to dependencies.toml.

Result

  • Provides a reusable test harness for writing Armeria integration tests against a real Istio-enabled cluster.

@jrhee17 jrhee17 modified the milestones: 1.38.0, 1.39.0 Apr 6, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 6, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new Istio-based integration test subproject (:it:xds-istio) that provisions or connects to K3s clusters, installs/uninstalls Istio, builds/loads a test image, and runs JUnit tests inside Istio-injected pods via new Gradle logic, JUnit extensions, Kubernetes helpers, and several integration tests.

Changes

Cohort / File(s) Summary
Dependency config
dependencies.toml
Add istio = "1.29.1" and new library aliases kubernetes-httpclient-okhttp and testcontainers-k3s.
Project inclusion & Gradle
settings.gradle, it/xds-istio/build.gradle
Include :it:xds-istio; add OS/arch detection, download/extract istioctl, prepare per-run runtime dirs, and wire test/JavaExec tasks with Istio env.
Cluster lifecycle helpers
it/xds-istio/src/main/java/.../K8sClusterHelper.java, IstioState.java
Start Testcontainers k3s, wait for readiness, create kubeconfig, connect-or-create cluster lifecycle and expose Kubernetes client.
Istio install/uninstall & readiness
.../IstioInstaller.java
Run istioctl install/uninstall, poll for istiod readiness/removal, run commands with timeouts and log collection.
Test image tooling
.../IstioTestImage.java
Build Docker image (ImageFromDockerfile) and load it into K3s containerd via docker save + ctr images import.
JUnit extensions & orchestration
IstioClusterExtension.java, IstioTestExtension.java, IstioServerExtension.java
Extensions to manage cluster lifecycle, intercept tests to run in pods, and provision in-cluster server workloads; share Kubernetes client via ExtensionContext store.
Pod runtime / entrypoint / annotations
IstioPodEntryPoint.java, IstioPodTest.java, PodCustomizer.java, IstioPodCustomizer.java
Entrypoint to run a selected JUnit method in a pod, annotation to mark pod-executed tests, pod customization and Istio-specific mounts/health checks.
Host-vs-pod helpers & env
HostOnlyExtension.java, IstioEnv.java
Helpers to detect RUNNING_IN_K8S_POD, gate setup/teardown, and centralized env var retrieval/validation (kubeconfig, istioctl path, dirs, JVM args).
Docker gating & BlockHound support
DockerAvailableCondition.java, EnabledIfDockerAvailable.java, IstioTestingBlockHoundIntegration.java, META-INF/services/...
JUnit condition/annotation to skip Docker-dependent tests; BlockHound integration registered via service loader.
Tests & utilities
it/xds-istio/src/test/java/.../IstioImageBuildTest.java, IstioStartupTest.java, EnvoyDebugTest.java, EchoConfigurator.java
New tests: image build, Istio readiness, Envoy/debugging and config inspection; test server configurator and log capture in teardown.
Service registration
it/xds-istio/src/test/resources/META-INF/services/...
Register BlockHound integration implementation for test runtime discovery.

Sequence Diagram(s)

sequenceDiagram
    participant TestRunner as Test Runner
    participant IstioExt as IstioTestExtension
    participant ClusterExt as IstioClusterExtension
    participant K8s as Kubernetes API
    participant Pod as Test Pod
    participant Entry as IstioPodEntryPoint

    TestRunner->>IstioExt: interceptTestMethod()
    IstioExt->>IstioExt: check RUNNING_IN_K8S_POD
    alt host (not in pod)
        IstioExt->>ClusterExt: get KubernetesClient
        ClusterExt-->>IstioExt: KubernetesClient
        IstioExt->>K8s: create Pod (image + args --class/--method)
        K8s-->>IstioExt: Pod created
        IstioExt->>Pod: wait for readiness (podCustomizer.isPodHealthy)
        Pod-->>IstioExt: Pod ready
        Pod->>Entry: run IstioPodEntryPoint (container)
        Entry->>Entry: run specified test via JUnit Launcher
        Entry-->>Pod: exit code & logs
        Pod-->>IstioExt: container exit
        IstioExt->>TestRunner: pass/fail (collect logs on failure)
        IstioExt->>K8s: delete Pod
    else running in pod
        IstioExt->>TestRunner: invoke test locally
    end
Loading
sequenceDiagram
    participant ClusterSetup as IstioClusterExtension.setUp
    participant State as IstioState
    participant Helper as K8sClusterHelper
    participant K3s as K3sContainer
    participant Installer as IstioInstaller
    participant istioctl as istioctl CLI

    ClusterSetup->>State: connectOrCreate()
    alt kubeconfig exists
        State->>State: connect using existing kubeconfig
    else kubeconfig missing
        State->>Helper: startK3sAndWaitReady()
        Helper->>K3s: start rancher/k3s container
        Helper-->>State: return K3sContainer + kubeconfig
        State->>State: write kubeconfig to disk
    end
    State->>Installer: runIstioctlUninstall(kubeconfig)
    Installer->>istioctl: istioctl uninstall --purge -y
    istioctl-->>Installer: exit
    State->>Installer: waitForIstioRemoval(client)
    State->>Installer: installIfNeeded(kubeconfig, profile)
    Installer->>istioctl: istioctl install --kubeconfig ...
    istioctl->>K3s: deploy istiod components
    Installer->>Installer: waitForIstiodReady()
    Installer-->>State: istio ready
    State-->>ClusterSetup: return IstioState with client
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Suggested reviewers

  • trustin
  • minwoox
  • ikhoon

Poem

🐰 In K3s meadows where Istio hops,
I pack the image, hop through podded crops,
I run the test, fetch logs with zest,
A rabbit-cheered cluster — integration's best!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.74% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'Introduce an Istio test harness' accurately and concisely summarizes the main change: adding a comprehensive Istio testing infrastructure module (it/xds-istio) to Armeria.
Description check ✅ Passed The PR description is directly related to the changeset, providing clear motivation, detailed modifications across all major components, and the expected result of the changes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Nitpick comments (6)
it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/EnabledIfDockerAvailable.java (1)

25-28: Consider adding a brief Javadoc.

While the annotation name is self-documenting, adding a one-line Javadoc would align with the project's convention for public API documentation. Example:

/**
 * Enables the annotated test class or method only if Docker daemon is available.
 */

As per coding guidelines, "Javadoc: all public classes and public/protected methods must have Javadoc".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/EnabledIfDockerAvailable.java`
around lines 25 - 28, Add a one-line Javadoc to the public annotation
EnabledIfDockerAvailable explaining its purpose; e.g., document that the
annotation enables the annotated test class or method only when the Docker
daemon is available and mention it uses DockerAvailableCondition via `@ExtendWith`
to perform the check. Place the Javadoc immediately above the annotation
declaration for EnabledIfDockerAvailable so it satisfies the project's public
API documentation guideline.
it/xds-istio/src/test/java/com/linecorp/armeria/it/istio/testing/IstioTestingBlockHoundIntegration.java (1)

15-17: Minor: Remove extra blank line between license header and package statement.

There's an extra blank line after the license header (line 16) which is inconsistent with the typical Java file format where the package declaration immediately follows the closing comment.

Suggested fix
  * under the License.
  */
-
 package com.linecorp.armeria.it.istio.testing;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@it/xds-istio/src/test/java/com/linecorp/armeria/it/istio/testing/IstioTestingBlockHoundIntegration.java`
around lines 15 - 17, Remove the extra blank line between the file header
comment and the package declaration so the package statement immediately follows
the closing comment; locate the top of the file around the package declaration
for the IstioTestingBlockHoundIntegration class (package
com.linecorp.armeria.it.istio.testing) and delete the stray blank line after the
license block.
it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/DockerAvailableCondition.java (1)

26-29: Consider extracting the double-negative for readability.

The condition !HostOnlyExtension.notRunningInPod() uses a double negative which can be confusing. Consider extracting to a local variable with a positive name:

final boolean runningInPod = !HostOnlyExtension.notRunningInPod();
if (runningInPod) {
    return ConditionEvaluationResult.enabled("Running inside K8s pod");
}

Alternatively, if HostOnlyExtension is under your control, consider adding a positive isRunningInPod() method.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/DockerAvailableCondition.java`
around lines 26 - 29, The double-negative check
(!HostOnlyExtension.notRunningInPod()) in DockerAvailableCondition should be
made clearer: introduce a positive-named local boolean (e.g., runningInPod)
assigned from the negative call and use it in the if, or (if you can change
HostOnlyExtension) replace the call with a new positive helper method
isRunningInPod() and use that; update the conditional in the method inside
DockerAvailableCondition to use the new positive variable/method (referencing
HostOnlyExtension.notRunningInPod() or HostOnlyExtension.isRunningInPod() and
the DockerAvailableCondition class).
it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioPodEntryPoint.java (1)

83-85: The launcher.discover() call is redundant.

The Launcher.execute() method performs discovery internally when given a LauncherDiscoveryRequest. The separate discover() call on line 84 is unnecessary.

♻️ Suggested simplification
         final SummaryGeneratingListener listener = new SummaryGeneratingListener();
         final Launcher launcher = LauncherFactory.create();
-        launcher.discover(request);
         launcher.execute(request, listener);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioPodEntryPoint.java`
around lines 83 - 85, The call to Launcher.discover(request) is redundant
because Launcher.execute(request, listener) already performs discovery for a
LauncherDiscoveryRequest; remove the explicit launcher.discover(request)
invocation in IstioPodEntryPoint (the block that creates Launcher via
LauncherFactory.create(), calls launcher.discover(request), then
launcher.execute(request, listener)) and keep only launcher.execute(request,
listener) to simplify and avoid duplicate discovery.
it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioTestImage.java (1)

61-66: Capture stderr for docker save failure diagnostics.

When docker save fails, the error message doesn't include the actual error output, making debugging difficult. Consider capturing stderr similar to how it's done for ctr images import.

♻️ Proposed improvement
-            final Process saveProcess = new ProcessBuilder("docker", "save", IMAGE_NAME)
-                    .redirectOutput(tempFile.toFile())
-                    .start();
-            if (saveProcess.waitFor() != 0) {
-                throw new IllegalStateException("docker save exited with non-zero status");
-            }
+            final ProcessBuilder pb = new ProcessBuilder("docker", "save", IMAGE_NAME)
+                    .redirectOutput(tempFile.toFile());
+            final Process saveProcess = pb.start();
+            final int exitCode = saveProcess.waitFor();
+            if (exitCode != 0) {
+                final String stderr = new String(saveProcess.getErrorStream().readAllBytes());
+                throw new IllegalStateException(
+                        "docker save exited with status " + exitCode + ": " + stderr);
+            }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioTestImage.java`
around lines 61 - 66, The docker save invocation using new
ProcessBuilder("docker", "save", IMAGE_NAME") does not capture stderr, so when
saveProcess.waitFor() != 0 the thrown IllegalStateException lacks diagnostic
output; modify the ProcessBuilder for docker save (the saveProcess) to capture
stderr like the ctr images import path does (e.g., redirectError to a temporary
file or capture the error stream), and include the captured stderr contents in
the IllegalStateException message so failures around IMAGE_NAME and tempFile
produce actionable diagnostics.
dependencies.toml (1)

94-96: Istio version 1.29.0 is valid and released (February 16, 2026).

Consider updating to Istio 1.29.1 (released March 10, 2026), the latest patch in the 1.29.x series, to include any available bug fixes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dependencies.toml` around lines 94 - 96, Update the istio dependency version
from "1.29.0" to the current 1.29.x patch release "1.29.1" in dependencies.toml
by changing the istio = "1.29.0" entry to istio = "1.29.1" so the project
benefits from the latest 1.29.x bug fixes; verify no other references override
this value and run dependency resolution to confirm compatibility.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioEnv.java`:
- Around line 62-64: The podJvmArgs() method currently calls
require(ISTIO_POD_JVM_ARGS_ENV) which throws if the ISTIO_POD_JVM_ARGS
environment variable is absent; change podJvmArgs() to read the env var
defensively (e.g., System.getenv or the existing helper that returns
null/optional) and return an empty string when the variable is not set instead
of throwing; update references to podJvmArgs() if they expect non-null but can
handle empty string.

In
`@it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioInstaller.java`:
- Around line 112-128: The runCommand method can hang if the subprocess (e.g.,
istioctl) never exits; modify runCommand(List<String> command, Path workDir,
String logPrefix) to enforce a configurable timeout: start the process as
before, consume stdout/stderr in a background thread or continue using the
current reader, then call process.waitFor(timeout, TimeUnit.SECONDS) (or
similar) and if it returns false call process.destroyForcibly(), wait a short
grace period, capture/flush remaining output, and throw an IllegalStateException
indicating the timeout and the command; ensure the thrown exception and log
include the command, exit/timeout status, and logPrefix so callers of runCommand
can fail fast when istioctl hangs.

In
`@it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioPodCustomizer.java`:
- Around line 52-55: The isPodHealthy(Pod pod) method in IstioPodCustomizer
assumes pod.getStatus() is non-null and can throw an NPE; update
isPodHealthy(Pod pod) to defensively check pod.getStatus() for null before
calling getPhase() (mirror the base interface default behavior), returning false
if status is null or if the phase is not "Running", so use pod.getStatus() !=
null and then check pod.getStatus().getPhase().

In
`@it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioPodEntryPoint.java`:
- Around line 53-55: When parsing the "--port" CLI arg in IstioPodEntryPoint
(inside main where serverPort is set), wrap the Integer.parseInt(args[i + 1])
call in a try/catch for NumberFormatException; on catch log a clear error via
the same logger or System.err including the invalid value (args[i + 1]) and exit
with a non-zero status so the process fails fast instead of throwing a stack
trace.

In
`@it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioServerExtension.java`:
- Around line 205-216: The current waitForReady method returns as soon as any
Pod is in phase "Running", which misses whether the application container(s) and
the istio-proxy sidecar are actually started and ready; update the predicate
inside K8sClusterHelper.poll (in waitForReady) to inspect each
Pod.getStatus().getContainerStatuses() and only return true when (a) the
server/application container(s) have ready==true and state running/terminated as
appropriate, and (b) there exists a container named "istio-proxy" with
ready==true (or otherwise validate sidecar injection), so the method only
completes when both app containers and the istio sidecar are present and ready
for serviceName.

In
`@it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioState.java`:
- Around line 67-68: connectOrCreate() currently always calls reinstallIstio(),
which runs istioctl uninstall --purge and can destroy Istio on shared clusters;
change the flow so reinstallIstio() is only invoked when a new cluster is
created (or when an explicit force/reinstall flag is set), not when
attachToExisting/attachToKubeconfig succeeds; update connectOrCreate() to detect
whether the kubeconfig was created vs attached (the return value or a
created/attached boolean from the cluster creation logic) and only call
reinstallIstio(kubeconfigPath, client) in the created-new case; ensure the same
fix is applied to the other branch referenced around the code block covering
lines 96-105 that also calls reinstallIstio().

In
`@it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioTestExtension.java`:
- Around line 142-156: The waitForPodHealthy method currently throws on timeout
without surfacing pod logs; update waitForPodHealthy(KubernetesClient client,
String podName, String namespace, PodCustomizer podCustomizer) to fetch the Pod
object and the logs from the test container (via
client.pods().inNamespace(namespace).withName(podName).inContainer(...).getLog(...)
or equivalent) when healthy==false and include that output in the thrown
IllegalStateException or log it before deletion; ensure you handle null
pod/status and IOException from log retrieval and include
pod.getMetadata().getName()/pod.getStatus() and the retrieved logs in the error
message to aid debugging.

In
`@it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/K8sClusterHelper.java`:
- Around line 59-68: startK3sAndWaitReady currently leaves the K3sContainer
(k3s) running if readiness waiting or IstioTestImage.loadIntoK3s throws; wrap
the post-start logic so any exception stops the container before propagating:
after creating k3s via startK3s(), use a try/catch (or try/finally with rethrow)
around the client wait (waitForReadyNode) and the
IstioTestImage.loadIntoK3s(k3s) call; on any failure call k3s.stop() (or
close/shutdown method on K3sContainer) and rethrow the exception so a successful
path still returns a running k3s but all error paths clean up the container.

In
`@it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/PodCustomizer.java`:
- Around line 42-44: The isPodHealthy default implementation can NPE if
pod.getStatus() is null; update isPodHealthy(Pod pod) to defensively check
pod.getStatus() (and optionally pod.getStatus().getPhase()) before calling
getPhase(), and return false (or an appropriate non-healthy value) when status
or phase is null so it safely handles pods in early lifecycle stages.

In `@it/xds-istio/src/test/java/com/linecorp/armeria/it/xds/EnvoyDebugTest.java`:
- Around line 85-100: The test envoyBootstrapFile currently only logs files and
can pass when no bootstrap JSON exists; change it to assert that at least one
filename in filenames endsWith(".json") (or that
filenames.stream().anyMatch(name -> name.endsWith(".json")) is true) and fail
the test if none are found, then continue to log/read the matching file(s) as
before; update references in the method (dir, filenames, the for-loop) so the
assertion occurs before iterating the files and reading their contents.

---

Nitpick comments:
In `@dependencies.toml`:
- Around line 94-96: Update the istio dependency version from "1.29.0" to the
current 1.29.x patch release "1.29.1" in dependencies.toml by changing the istio
= "1.29.0" entry to istio = "1.29.1" so the project benefits from the latest
1.29.x bug fixes; verify no other references override this value and run
dependency resolution to confirm compatibility.

In
`@it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/DockerAvailableCondition.java`:
- Around line 26-29: The double-negative check
(!HostOnlyExtension.notRunningInPod()) in DockerAvailableCondition should be
made clearer: introduce a positive-named local boolean (e.g., runningInPod)
assigned from the negative call and use it in the if, or (if you can change
HostOnlyExtension) replace the call with a new positive helper method
isRunningInPod() and use that; update the conditional in the method inside
DockerAvailableCondition to use the new positive variable/method (referencing
HostOnlyExtension.notRunningInPod() or HostOnlyExtension.isRunningInPod() and
the DockerAvailableCondition class).

In
`@it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/EnabledIfDockerAvailable.java`:
- Around line 25-28: Add a one-line Javadoc to the public annotation
EnabledIfDockerAvailable explaining its purpose; e.g., document that the
annotation enables the annotated test class or method only when the Docker
daemon is available and mention it uses DockerAvailableCondition via `@ExtendWith`
to perform the check. Place the Javadoc immediately above the annotation
declaration for EnabledIfDockerAvailable so it satisfies the project's public
API documentation guideline.

In
`@it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioPodEntryPoint.java`:
- Around line 83-85: The call to Launcher.discover(request) is redundant because
Launcher.execute(request, listener) already performs discovery for a
LauncherDiscoveryRequest; remove the explicit launcher.discover(request)
invocation in IstioPodEntryPoint (the block that creates Launcher via
LauncherFactory.create(), calls launcher.discover(request), then
launcher.execute(request, listener)) and keep only launcher.execute(request,
listener) to simplify and avoid duplicate discovery.

In
`@it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioTestImage.java`:
- Around line 61-66: The docker save invocation using new
ProcessBuilder("docker", "save", IMAGE_NAME") does not capture stderr, so when
saveProcess.waitFor() != 0 the thrown IllegalStateException lacks diagnostic
output; modify the ProcessBuilder for docker save (the saveProcess) to capture
stderr like the ctr images import path does (e.g., redirectError to a temporary
file or capture the error stream), and include the captured stderr contents in
the IllegalStateException message so failures around IMAGE_NAME and tempFile
produce actionable diagnostics.

In
`@it/xds-istio/src/test/java/com/linecorp/armeria/it/istio/testing/IstioTestingBlockHoundIntegration.java`:
- Around line 15-17: Remove the extra blank line between the file header comment
and the package declaration so the package statement immediately follows the
closing comment; locate the top of the file around the package declaration for
the IstioTestingBlockHoundIntegration class (package
com.linecorp.armeria.it.istio.testing) and delete the stray blank line after the
license block.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 723aaaf8-995a-4498-aa5e-9413e03dfc1f

📥 Commits

Reviewing files that changed from the base of the PR and between 946470a and 3aa4d3f.

📒 Files selected for processing (24)
  • dependencies.toml
  • it/xds-istio/build.gradle
  • it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/DockerAvailableCondition.java
  • it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/EnabledIfDockerAvailable.java
  • it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/HostOnlyExtension.java
  • it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioClusterExtension.java
  • it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioEnv.java
  • it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioInstaller.java
  • it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioPodCustomizer.java
  • it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioPodEntryPoint.java
  • it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioPodTest.java
  • it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioServerExtension.java
  • it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioState.java
  • it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioTestExtension.java
  • it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioTestImage.java
  • it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/K8sClusterHelper.java
  • it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/PodCustomizer.java
  • it/xds-istio/src/test/java/com/linecorp/armeria/it/istio/testing/IstioImageBuildTest.java
  • it/xds-istio/src/test/java/com/linecorp/armeria/it/istio/testing/IstioStartupTest.java
  • it/xds-istio/src/test/java/com/linecorp/armeria/it/istio/testing/IstioTestingBlockHoundIntegration.java
  • it/xds-istio/src/test/java/com/linecorp/armeria/it/xds/EchoConfigurator.java
  • it/xds-istio/src/test/java/com/linecorp/armeria/it/xds/EnvoyDebugTest.java
  • it/xds-istio/src/test/resources/META-INF/services/reactor.blockhound.integration.BlockHoundIntegration
  • settings.gradle

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioServerExtension.java (1)

166-176: Consider adding containerPort to the container spec for clarity.

While Kubernetes Services can target any port on a pod, explicitly declaring containerPort in the container spec improves documentation and enables port-related tooling. This is optional but aids maintainability.

💡 Suggested addition
                                 .withArgs("--server-factory", configuratorClass.getName(),
                                           "--port", String.valueOf(port))
+                                .addNewPort()
+                                    .withContainerPort(port)
+                                .endPort()
                                 .addNewEnv()
                                     .withName("JAVA_TOOL_OPTIONS")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioServerExtension.java`
around lines 166 - 176, Add an explicit container port to the server container
spec to improve clarity and tooling support: in the container builder chain that
starts with .addNewContainer().withName("server")... (the block using
.withArgs(..., String.valueOf(port)) and .withImagePullPolicy("Never")), insert
a port declaration such as .addNewPort().withContainerPort(port).endPort()
before .endContainer() so the container explicitly exposes the same port number
passed via String.valueOf(port).
it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioTestExtension.java (1)

161-185: Consider throwing on timeout instead of silently returning failure exit code.

When the test container times out without terminating, the method logs a warning and returns exit code 1. This causes the test to fail with a generic "pod failed" message, obscuring the actual timeout. Throwing an explicit timeout exception would make the failure reason clearer.

💡 Suggested change
         if (!terminated) {
-            logger.warn("Timed out waiting for test container in pod '{}' to terminate", podName);
+            throw new IllegalStateException(
+                    "Timed out waiting for test container in pod '" + podName + "' to terminate");
         }
         return exitCode[0];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioTestExtension.java`
around lines 161 - 185, The method waitForTestContainerTerminated currently logs
a warning and returns exit code 1 on timeout; change it to throw an explicit
exception instead so callers see a clear timeout failure. In
waitForTestContainerTerminated, after K8sClusterHelper.poll returns false (i.e.,
when terminated is false) replace the logger.warn + return exitCode[0] path with
throwing a descriptive runtime exception (e.g., IllegalStateException or a
custom TimeoutException) that includes podName and namespace; keep the
successful return of the discovered exit code unchanged. Ensure any callers of
waitForTestContainerTerminated can handle or propagate this exception as needed.
it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioTestImage.java (1)

61-69: Process error stream should be consumed during execution to avoid potential deadlock.

If docker save writes significant output to stderr before exiting, the process can block waiting for the buffer to be drained. Reading stderr only after waitFor() may miss content or cause hangs. Consider redirecting stderr to stdout or consuming it concurrently.

💡 Suggested fix using redirectErrorStream
             final Process saveProcess = new ProcessBuilder("docker", "save", IMAGE_NAME)
+                    .redirectErrorStream(true)
                     .redirectOutput(tempFile.toFile())
                     .start();
             final int exitCode = saveProcess.waitFor();
             if (exitCode != 0) {
-                final String stderr = new String(saveProcess.getErrorStream().readAllBytes());
                 throw new IllegalStateException(
-                        "docker save exited with status " + exitCode + ": " + stderr);
+                        "docker save exited with status " + exitCode +
+                        ". Check Docker daemon availability and image existence.");
             }

Alternatively, redirect stderr separately to a temp file or use ProcessBuilder.Redirect.DISCARD if the error details aren't critical.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioTestImage.java`
around lines 61 - 69, The docker save invocation may deadlock because stderr is
only read after waitFor(); modify the ProcessBuilder setup that constructs
saveProcess (the ProcessBuilder call with "docker", "save", IMAGE_NAME and the
saveProcess variable) to consume stderr during execution—either call
redirectErrorStream(true) on the ProcessBuilder so stderr is merged into the
redirected output, or
redirectError(ProcessBuilder.Redirect.to(...))/redirectError(ProcessBuilder.Redirect.DISCARD)
to a temp file before start(), or spawn a background thread to read
saveProcess.getErrorStream() while the process runs; ensure the stream is
consumed before calling saveProcess.waitFor().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioInstaller.java`:
- Around line 81-87: enableNamespaceInjection may NPE when
ns.getMetadata().getLabels() is null; update the edit lambda to defensively
initialize the labels map if null before putting "istio-injection":"enabled". In
the Kubernetes edit block (enableNamespaceInjection /
client.namespaces().withName(...).edit), check ns.getMetadata().getLabels() and
if it is null create a new HashMap and set it via
ns.getMetadata().setLabels(...), then put the label and return ns; keep the
logger call unchanged.
- Around line 126-131: The code calls process.waitFor(DEFAULT_READY_TIMEOUT) but
Process has no waitFor(Duration) overload; change the call to use the
waitFor(long timeout, TimeUnit unit) overload (e.g.,
process.waitFor(DEFAULT_READY_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS)) and
add the necessary import for java.util.concurrent.TimeUnit; keep the existing
timeout handling and exception message (you may use
DEFAULT_READY_TIMEOUT.toMinutes() or DEFAULT_READY_TIMEOUT.toMillis() in the
message to preserve precision) and update references around the wait to use the
new boolean result variable as before.

---

Nitpick comments:
In
`@it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioServerExtension.java`:
- Around line 166-176: Add an explicit container port to the server container
spec to improve clarity and tooling support: in the container builder chain that
starts with .addNewContainer().withName("server")... (the block using
.withArgs(..., String.valueOf(port)) and .withImagePullPolicy("Never")), insert
a port declaration such as .addNewPort().withContainerPort(port).endPort()
before .endContainer() so the container explicitly exposes the same port number
passed via String.valueOf(port).

In
`@it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioTestExtension.java`:
- Around line 161-185: The method waitForTestContainerTerminated currently logs
a warning and returns exit code 1 on timeout; change it to throw an explicit
exception instead so callers see a clear timeout failure. In
waitForTestContainerTerminated, after K8sClusterHelper.poll returns false (i.e.,
when terminated is false) replace the logger.warn + return exitCode[0] path with
throwing a descriptive runtime exception (e.g., IllegalStateException or a
custom TimeoutException) that includes podName and namespace; keep the
successful return of the discovered exit code unchanged. Ensure any callers of
waitForTestContainerTerminated can handle or propagate this exception as needed.

In
`@it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioTestImage.java`:
- Around line 61-69: The docker save invocation may deadlock because stderr is
only read after waitFor(); modify the ProcessBuilder setup that constructs
saveProcess (the ProcessBuilder call with "docker", "save", IMAGE_NAME and the
saveProcess variable) to consume stderr during execution—either call
redirectErrorStream(true) on the ProcessBuilder so stderr is merged into the
redirected output, or
redirectError(ProcessBuilder.Redirect.to(...))/redirectError(ProcessBuilder.Redirect.DISCARD)
to a temp file before start(), or spawn a background thread to read
saveProcess.getErrorStream() while the process runs; ensure the stream is
consumed before calling saveProcess.waitFor().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 68d725ad-74d1-416f-9102-114359cdc151

📥 Commits

Reviewing files that changed from the base of the PR and between 3aa4d3f and fa9bfc8.

📒 Files selected for processing (14)
  • dependencies.toml
  • it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/DockerAvailableCondition.java
  • it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/EnabledIfDockerAvailable.java
  • it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/HostOnlyExtension.java
  • it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioInstaller.java
  • it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioPodCustomizer.java
  • it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioPodEntryPoint.java
  • it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioServerExtension.java
  • it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioTestExtension.java
  • it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioTestImage.java
  • it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/K8sClusterHelper.java
  • it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/PodCustomizer.java
  • it/xds-istio/src/test/java/com/linecorp/armeria/it/istio/testing/IstioTestingBlockHoundIntegration.java
  • it/xds-istio/src/test/java/com/linecorp/armeria/it/xds/EnvoyDebugTest.java
✅ Files skipped from review due to trivial changes (2)
  • it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/EnabledIfDockerAvailable.java
  • it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/PodCustomizer.java
🚧 Files skipped from review as they are similar to previous changes (8)
  • it/xds-istio/src/test/java/com/linecorp/armeria/it/istio/testing/IstioTestingBlockHoundIntegration.java
  • it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/DockerAvailableCondition.java
  • it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioPodCustomizer.java
  • it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/HostOnlyExtension.java
  • it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioPodEntryPoint.java
  • dependencies.toml
  • it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/K8sClusterHelper.java
  • it/xds-istio/src/test/java/com/linecorp/armeria/it/xds/EnvoyDebugTest.java

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 6, 2026

Codecov Report

❌ Patch coverage is 64.82759% with 204 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.96%. Comparing base (8150425) to head (7c2dc42).
⚠️ Report is 413 commits behind head on main.

Files with missing lines Patch % Lines
...p/armeria/it/istio/testing/IstioTestExtension.java 57.62% 40 Missing and 10 partials ⚠️
...p/armeria/it/istio/testing/IstioPodEntryPoint.java 0.00% 43 Missing ⚠️
...ecorp/armeria/it/istio/testing/IstioInstaller.java 64.63% 17 Missing and 12 partials ⚠️
...armeria/it/istio/testing/IstioServerExtension.java 84.92% 12 Missing and 7 partials ⚠️
...orp/armeria/it/istio/testing/K8sClusterHelper.java 61.22% 12 Missing and 7 partials ⚠️
.../linecorp/armeria/it/istio/testing/IstioState.java 75.55% 7 Missing and 4 partials ⚠️
...rmeria/it/istio/testing/IstioClusterExtension.java 71.42% 6 Missing and 2 partials ⚠️
...p/armeria/it/istio/testing/IstioPodCustomizer.java 78.57% 3 Missing and 3 partials ⚠️
...ecorp/armeria/it/istio/testing/IstioTestImage.java 79.31% 4 Missing and 2 partials ⚠️
...ria/it/istio/testing/DockerAvailableCondition.java 42.85% 2 Missing and 2 partials ⚠️
... and 3 more
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #6706      +/-   ##
============================================
- Coverage     74.46%   73.96%   -0.50%     
- Complexity    22234    24234    +2000     
============================================
  Files          1963     2188     +225     
  Lines         82437    91088    +8651     
  Branches      10764    11917    +1153     
============================================
+ Hits          61385    67376    +5991     
- Misses        15918    18046    +2128     
- Partials       5134     5666     +532     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jrhee17 jrhee17 marked this pull request as ready for review April 9, 2026 01:28
Copy link
Copy Markdown
Contributor

@ikhoon ikhoon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍👍

implementation libs.junit5.platform.launcher
implementation libs.kubernetes.client.api
implementation libs.kubernetes.client.impl
implementation libs.kubernetes.httpclient.okhttp
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question) Could we use Armeria Kubernetes client implementation? If not, is there any reason we can't use it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No reason in particular, done

}

def kubeconfigEnvValue =
layout.buildDirectory.file('kubeconfig/kubeconfig.yaml').get().asFile.absolutePath
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question) Where does this file come from?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is written here: https://github.com/jrhee17/armeria/blob/7c2dc4282cf46a1207757a707334a2ee6d83892c/it/xds-istio/src/main/java/com/linecorp/armeria/it/istio/testing/IstioState.java#L91

The config is written so that each test has the option of reusing the existing k8s cluster since starting a new container takes > 1min.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants