Skip to content

Add an example for xDS integration - #6886

Open
jrhee17 wants to merge 3 commits into
line:mainfrom
jrhee17:feat/xds-examples
Open

Add an example for xDS integration#6886
jrhee17 wants to merge 3 commits into
line:mainfrom
jrhee17:feat/xds-examples

Conversation

@jrhee17

@jrhee17 jrhee17 commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Motivation:

Armeria's xDS client only checked the per-route retry_policy and ignored the virtual host level retry_policy. In Envoy, a virtual host level retry policy acts as a default that applies to all routes unless a route explicitly overrides it. Users who configured retry at the virtual host level (a common pattern) got no retry behavior.

Additionally, Armeria's xDS support has grown significantly (file-based config, zone-aware routing, subset routing, fault injection, mTLS, server-side xDS) but there was no end-to-end example demonstrating these features together.

Modifications:

  • Modified RouteStream to fall back to the virtual host level retry_policy when no route-level policy is set. Route-level takes precedence over virtual host level, matching Envoy's behavior.
  • Added two integration tests in RetryTest:
    • virtualHostLevelRetryPolicy: verifies that a vhost-level retry_on: "5xx" with num_retries: 2 triggers retries on 503 responses.
    • routeLevelRetryPolicyOverridesVirtualHost: verifies that a route-level retry_on: "gateway-error" overrides a vhost-level retry_on: "5xx", so a 500 response does not trigger retries.
  • Added a new examples/xds-example module — a three-layer service mesh demo with:
    • 9 servers per layer-pair spread across 3 zones (us-east, us-west, eu-west)
    • File-based xDS configuration with 4 switchable profiles (basic, tls, fault, retry)
    • A dashboard UI for traffic generation, profile switching, and live xDS resource editing
    • Docker Compose setup with Prometheus + Grafana and a pre-provisioned dashboard covering request rates, retries, routing distribution, control plane metrics, load balancer state, zone-aware routing, and LB selection

Result:

  • Virtual host level retry_policy is now correctly used as a fallback when no route-level policy is configured, matching Envoy's behavior.
  • Users can run ./gradlew :examples:xds-example:run and docker compose up to get a fully working xDS service mesh with a visual dashboard and Grafana metrics, making it easy to explore Armeria's xDS capabilities interactively.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

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 runnable three-layer Armeria xDS example with profile-based YAML resources, TLS and fault scenarios, a browser dashboard, Prometheus/Grafana monitoring, integration tests, and route-over-virtual-host retry policy resolution.

Changes

xDS Example Application

Layer / File(s) Summary
Build and monitoring configuration
examples/xds-example/build.gradle, settings.gradle, examples/xds-example/docker-compose.yml, examples/xds-example/prometheus.yml, examples/xds-example/grafana/...
Adds the Gradle application module, local Prometheus and Grafana services, scrape configuration, datasource provisioning, dashboard provisioning, and xDS metrics panels.
xDS bootstraps and profile templates
examples/xds-example/src/main/resources/xds/...
Adds basic, TLS, retry, fault, and unhealthy xDS bootstrap, listener, cluster, and endpoint templates for Layers 1–3.
Template materialization and profile state
examples/xds-example/src/main/java/example/armeria/xds/ProfileState.java, examples/xds-example/src/main/java/example/armeria/xds/XdsTemplateReader.java
Adds profile discovery and resolution, variable substitution, protobuf parsing, discovery-response serialization, atomic file updates, and port-variable mapping.
Mesh servers and runtime orchestration
examples/xds-example/src/main/java/example/armeria/xds/ServiceMesh.java, Layer2Server.java, Layer3Server.java, RouteMetricsClient.java
Adds certificate generation, layered xDS server startup, metrics-enabled clients, zone-aware routing, profile application, and asynchronous shutdown.
Dashboard API and application entrypoint
examples/xds-example/src/main/java/example/armeria/xds/DashboardService.java, Main.java
Adds APIs for traffic generation, resource inspection, previews, file updates, profile operations, and application startup.
Dashboard web interface and validation
examples/xds-example/src/main/resources/index.html, examples/xds-example/src/test/java/example/armeria/xds/ServiceMeshTest.java
Adds the Bootstrap dashboard for traffic, profiles, resource overrides, and activity logs. Tests verify basic and TLS mesh requests.

xDS Retry Policy Precedence

Layer / File(s) Summary
Retry policy resolution and integration coverage
xds/src/main/java/com/linecorp/armeria/xds/RouteStream.java, it/xds-client/src/test/java/com/linecorp/armeria/xds/it/RetryTest.java
Uses route-level retry configuration before virtual-host configuration. Tests cover virtual-host retries and route-level overrides.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant DashboardService
  participant ServiceMesh
  participant Layer2Server
  participant Layer3Server
  Browser->>DashboardService: request traffic or profile update
  DashboardService->>ServiceMesh: send request or apply profile
  ServiceMesh->>Layer2Server: forward Layer 1 traffic
  Layer2Server->>Layer3Server: forward Layer 2 traffic
  Layer3Server-->>ServiceMesh: return HTTP response
  ServiceMesh-->>DashboardService: return operation result
  DashboardService-->>Browser: return API response
Loading

Possibly related PRs

  • line/armeria#6709: The xDS example uses the xDS client resource parsing and configuration support changed by this PR.
  • line/armeria#6798: The PR overlaps with retry-policy behavior and tests.
  • line/armeria#6811: The xDS example uses cluster-type and endpoint-resolution APIs related to this PR.

Suggested reviewers: ikhoon, minwoox

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.63% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly identifies the primary change: adding an xDS integration example.
Description check ✅ Passed The description directly explains the retry-policy fix, integration tests, and new xDS service-mesh example.
✨ 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.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 12

🧹 Nitpick comments (3)
examples/xds-example/src/main/java/example/armeria/xds/Layer2Server.java (1)

68-73: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Shutdown order: dependencies closed before the server that uses them. Both Layer2Server.close() and Layer3Server.close() close their xdsBootstrap (and, for Layer2, preprocessor) before calling server.stop().join(), even though the still-running server's route handler and XdsServerPlugin depend on those resources. In-flight requests during the stop/drain window can fail as a result.

  • examples/xds-example/src/main/java/example/armeria/xds/Layer2Server.java#L68-L73: call server.stop().join() first, then preprocessor.close() and xdsBootstrap.close().
  • examples/xds-example/src/main/java/example/armeria/xds/Layer3Server.java#L52-L56: call server.stop().join() first, then xdsBootstrap.close().
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/xds-example/src/main/java/example/armeria/xds/Layer2Server.java`
around lines 68 - 73, Update Layer2Server.close() in
examples/xds-example/src/main/java/example/armeria/xds/Layer2Server.java:68-73
to call server.stop().join() before closing preprocessor and xdsBootstrap. Apply
the same shutdown-order change in Layer3Server.close() at
examples/xds-example/src/main/java/example/armeria/xds/Layer3Server.java:52-56,
stopping the server before closing xdsBootstrap.
xds/src/main/java/com/linecorp/armeria/xds/filter/FaultInjectionFilterFactory.java (1)

165-191: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate control flow in httpDecorator/serviceDecorator.

Both decorators repeat the same header-match → maybeFault → maybeDelay sequence, differing only in the delegate invocation.

♻️ Proposed refactor to share the fault-application logic
+        private HttpResponse applyFault(HttpHeaders headers, EventExecutor executor,
+                                         java.util.function.Supplier<HttpResponse> delegateCall) {
+            if (!headersMatch(headers)) {
+                return delegateCall.get();
+            }
+            final HttpResponse faultResponse = maybeFault();
+            if (faultResponse != null) {
+                return maybeDelay(faultResponse, executor);
+            }
+            return maybeDelay(delegateCall.get(), executor);
+        }
+
         `@Override`
         public DecoratingHttpClientFunction httpDecorator() {
-            return (delegate, ctx, req) -> {
-                if (!headersMatch(req.headers())) {
-                    return delegate.execute(ctx, req);
-                }
-                final HttpResponse faultResponse = maybeFault();
-                if (faultResponse != null) {
-                    return maybeDelay(faultResponse, ctx.eventLoop());
-                }
-                return maybeDelay(delegate.execute(ctx, req), ctx.eventLoop());
-            };
+            return (delegate, ctx, req) ->
+                    applyFault(req.headers(), ctx.eventLoop(), () -> delegate.execute(ctx, req));
         }

         `@Override`
         public DecoratingHttpServiceFunction serviceDecorator() {
-            return (delegate, ctx, req) -> {
-                if (!headersMatch(req.headers())) {
-                    return delegate.serve(ctx, req);
-                }
-                final HttpResponse faultResponse = maybeFault();
-                if (faultResponse != null) {
-                    return maybeDelay(faultResponse, ctx.eventLoop());
-                }
-                return maybeDelay(delegate.serve(ctx, req), ctx.eventLoop());
-            };
+            return (delegate, ctx, req) ->
+                    applyFault(req.headers(), ctx.eventLoop(), () -> delegate.serve(ctx, req));
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@xds/src/main/java/com/linecorp/armeria/xds/filter/FaultInjectionFilterFactory.java`
around lines 165 - 191, Extract the shared header-match, maybeFault, and
maybeDelay sequence from httpDecorator and serviceDecorator into a reusable
helper in FaultInjectionFilterFactory, parameterizing only the delegate
execution. Update both decorators to call this helper while preserving their
respective delegate.execute and delegate.serve behavior.
examples/xds-example/src/main/java/example/armeria/xds/DashboardService.java (1)

99-107: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Header building happens outside the try/catch.

headers.forEach(reqBuilder::add) (lines 99-103) runs before the try block starts at line 105. If any header name/value is rejected by RequestHeadersBuilder (invalid characters, reserved names, etc.), the exception escapes the per-iteration error handling that every other failure path in this method relies on (lines 120-123), aborting the whole request instead of reporting a per-iteration TrafficResult error.

♻️ Proposed fix
             final RequestHeadersBuilder reqBuilder =
                     RequestHeaders.builder(HttpMethod.GET, "/");
-            if (headers != null) {
-                headers.forEach(reqBuilder::add);
-            }
-
             try {
+                if (headers != null) {
+                    headers.forEach(reqBuilder::add);
+                }
                 final AggregatedHttpResponse response =
                         layer1Client.execute(reqBuilder.build());
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/xds-example/src/main/java/example/armeria/xds/DashboardService.java`
around lines 99 - 107, Move the RequestHeadersBuilder initialization and
headers.forEach(reqBuilder::add) logic inside the existing try block in the
per-iteration flow of DashboardService. Preserve the current catch handling so
header-construction exceptions are converted into that iteration’s TrafficResult
error instead of escaping the request loop.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/xds-example/docker-compose.yml`:
- Line 4: Update the startup command in the usage comment to reference the
registered Gradle module path :examples:xds-example:run instead of
:examples:xds:run.

In
`@examples/xds-example/src/main/java/example/armeria/xds/DashboardService.java`:
- Around line 92-126: Update sendTraffic to enforce a finite upper bound on the
requested count before entering the synchronous request loop. Clamp or reject
values above the chosen maximum while preserving the existing default for
non-positive counts, and ensure the response count matches the bounded number of
requests performed.
- Around line 174-210: Harden updateFile by validating the resolved path remains
within configDir before writing, rejecting absolute or traversal-based file
values with BAD_REQUEST; use a normalized configDir and resolved path
containment check. Also validate the first resource’s type is non-null before
the switch, returning BAD_REQUEST instead of allowing a null-type NPE. Keep the
existing resource conversion and response behavior unchanged for valid requests.

In `@examples/xds-example/src/main/java/example/armeria/xds/Main.java`:
- Around line 19-53: Update Main.main() to ensure the ServiceMesh instance is
closed both during normal JVM shutdown and if dashboard.start().join() fails
after mesh creation. Register mesh with the shutdown lifecycle alongside the
dashboard Server, or use a try/finally structure that closes mesh while
preserving dashboard startup behavior.

In `@examples/xds-example/src/main/java/example/armeria/xds/ServiceMesh.java`:
- Around line 57-180: Update the ServiceMesh constructor to guard the entire
initialization sequence, including server startup and clientFactory/XDS setup,
with failure cleanup. If any later step throws, best-effort close every
already-created Layer3Server and Layer2Server plus clientFactory and other
initialized resources before rethrowing the original failure; preserve normal
construction behavior and avoid masking the original exception with cleanup
failures.
- Around line 210-218: Update ServiceMesh.close() to close every resource even
when an individual close operation throws. Replace the fail-fast
layer2Servers/layer3Servers iteration with per-resource exception isolation, and
ensure later servers and meterRegistry.close() are still attempted; preserve
closure of the existing resources and the current close ordering.
- Around line 23-24: Replace the direct SelfSignedCertificate and
SignedCertificate usage in ServiceMesh with the public TlsKeyPair.ofSelfSigned()
API when populating certVars. Remove the internal TLS imports and preserve the
example’s existing certificate configuration behavior.

In
`@examples/xds-example/src/main/java/example/armeria/xds/XdsTemplateReader.java`:
- Around line 68-84: The writeDiscoveryResponse method must publish YAML
atomically instead of writing directly to path. Create a sibling temporary file
in the same directory, write the complete UTF-8 content to it, then move it over
path using an atomic replacement rename; clean up the temporary file if any step
fails while preserving the existing IOException wrapping.
- Around line 117-133: Replace the Path-based classpath enumeration in
scanValidProfiles() with an explicit/generated template index or a
resource-scanning approach that supports both directory and JAR classpath
entries. Preserve the existing profile-to-template mapping, sorting, and
immutable return behavior while ensuring packaged /xds/ resources can be
discovered without requiring a mounted filesystem.

In `@examples/xds-example/src/main/resources/index.html`:
- Around line 322-351: Apply the existing escHtml encoder to every dynamic value
interpolated into innerHTML: the counter key and value display in renderStats,
r.name and label in populateDropdown, and the corresponding selected-resource
fields in showSelectedResource. Keep numeric styling and behavior unchanged
while ensuring all server-derived text is escaped before assignment.

In
`@it/xds-client/src/test/java/com/linecorp/armeria/xds/it/FaultInjectionFilterTest.java`:
- Around line 19-41: Add the missing explicit import for XdsResourceReader in
FaultInjectionFilterTest so both XdsResourceReader.fromYaml(...) usages resolve
from the com.linecorp.armeria.xds package.

In `@xds/src/main/java/com/linecorp/armeria/xds/server/XdsServerPlugin.java`:
- Around line 176-177: Update the TLS consistency check in XdsServerPlugin to
use SessionProtocol.isTls() instead of comparing only against
SessionProtocol.HTTPS, so HTTPS, H1, and H2 over TLS are consistently recognized
as TLS by FilterChainMatcher.

---

Nitpick comments:
In
`@examples/xds-example/src/main/java/example/armeria/xds/DashboardService.java`:
- Around line 99-107: Move the RequestHeadersBuilder initialization and
headers.forEach(reqBuilder::add) logic inside the existing try block in the
per-iteration flow of DashboardService. Preserve the current catch handling so
header-construction exceptions are converted into that iteration’s TrafficResult
error instead of escaping the request loop.

In `@examples/xds-example/src/main/java/example/armeria/xds/Layer2Server.java`:
- Around line 68-73: Update Layer2Server.close() in
examples/xds-example/src/main/java/example/armeria/xds/Layer2Server.java:68-73
to call server.stop().join() before closing preprocessor and xdsBootstrap. Apply
the same shutdown-order change in Layer3Server.close() at
examples/xds-example/src/main/java/example/armeria/xds/Layer3Server.java:52-56,
stopping the server before closing xdsBootstrap.

In
`@xds/src/main/java/com/linecorp/armeria/xds/filter/FaultInjectionFilterFactory.java`:
- Around line 165-191: Extract the shared header-match, maybeFault, and
maybeDelay sequence from httpDecorator and serviceDecorator into a reusable
helper in FaultInjectionFilterFactory, parameterizing only the delegate
execution. Update both decorators to call this helper while preserving their
respective delegate.execute and delegate.serve behavior.
🪄 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 Plus

Run ID: 7bafc09c-86cd-424d-9eb4-b06f4ae81cf9

📥 Commits

Reviewing files that changed from the base of the PR and between c2e8841 and 36c0a4c.

📒 Files selected for processing (55)
  • examples/xds-example/build.gradle
  • examples/xds-example/docker-compose.yml
  • examples/xds-example/grafana/dashboards/xds-traffic.json
  • examples/xds-example/grafana/provisioning/dashboards/provider.yml
  • examples/xds-example/grafana/provisioning/datasources/datasource.yml
  • examples/xds-example/prometheus.yml
  • examples/xds-example/src/main/java/example/armeria/xds/DashboardService.java
  • examples/xds-example/src/main/java/example/armeria/xds/Layer2Server.java
  • examples/xds-example/src/main/java/example/armeria/xds/Layer3Server.java
  • examples/xds-example/src/main/java/example/armeria/xds/Main.java
  • examples/xds-example/src/main/java/example/armeria/xds/ProfileState.java
  • examples/xds-example/src/main/java/example/armeria/xds/RouteMetricsClient.java
  • examples/xds-example/src/main/java/example/armeria/xds/ServiceMesh.java
  • examples/xds-example/src/main/java/example/armeria/xds/TrafficCounter.java
  • examples/xds-example/src/main/java/example/armeria/xds/XdsTemplateReader.java
  • examples/xds-example/src/main/resources/index.html
  • examples/xds-example/src/main/resources/xds/basic/layer1-client-cluster.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer1-client-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer2-client-cluster.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer2-client-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer2-eds.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer2-server-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer3-eds.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer3-server-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/fault-retry/layer1-client-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/fault-retry/layer2-client-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/fault/layer1-client-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/fault/layer2-client-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/layer1-bootstrap.template.yaml
  • examples/xds-example/src/main/resources/xds/layer2-bootstrap.template.yaml
  • examples/xds-example/src/main/resources/xds/layer3-bootstrap.template.yaml
  • examples/xds-example/src/main/resources/xds/tls/layer1-client-cluster.template.yaml
  • examples/xds-example/src/main/resources/xds/tls/layer2-client-cluster.template.yaml
  • examples/xds-example/src/main/resources/xds/tls/layer2-server-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/tls/layer3-server-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/unhealthy/layer2-eds.template.yaml
  • examples/xds-example/src/main/resources/xds/unhealthy/layer3-eds.template.yaml
  • examples/xds-example/src/test/java/example/armeria/xds/ServiceMeshTest.java
  • it/xds-client/src/test/java/com/linecorp/armeria/xds/RouteEntryMatcherTest.java
  • it/xds-client/src/test/java/com/linecorp/armeria/xds/it/FaultInjectionFilterTest.java
  • it/xds-client/src/test/java/com/linecorp/armeria/xds/it/server/ServerFilterChainMatchTest.java
  • settings.gradle
  • xds-api/src/main/proto/envoy/config/core/v3/base.proto
  • xds-api/src/main/proto/envoy/extensions/filters/common/fault/v3/fault.proto
  • xds-api/src/main/proto/envoy/extensions/filters/http/fault/v3/fault.proto
  • xds-api/src/main/proto/envoy/type/v3/percent.proto
  • xds/src/main/java/com/linecorp/armeria/xds/RetryStateFactory.java
  • xds/src/main/java/com/linecorp/armeria/xds/RouteEntryMatcher.java
  • xds/src/main/java/com/linecorp/armeria/xds/StateCoordinator.java
  • xds/src/main/java/com/linecorp/armeria/xds/XdsExtensionRegistry.java
  • xds/src/main/java/com/linecorp/armeria/xds/XdsResourceReader.java
  • xds/src/main/java/com/linecorp/armeria/xds/client/endpoint/LocalityRoutingStateFactory.java
  • xds/src/main/java/com/linecorp/armeria/xds/filter/FaultInjectionFilterFactory.java
  • xds/src/main/java/com/linecorp/armeria/xds/internal/XdsHeaderMatcher.java
  • xds/src/main/java/com/linecorp/armeria/xds/server/XdsServerPlugin.java

Comment thread examples/xds-example/docker-compose.yml Outdated
Comment thread examples/xds-example/src/main/java/example/armeria/xds/DashboardService.java Outdated
Comment thread examples/xds-playground/src/main/java/example/armeria/xds/Main.java
Comment thread examples/xds-example/src/main/java/example/armeria/xds/XdsTemplateReader.java Outdated
Comment thread examples/xds-example/src/main/resources/index.html Outdated
Comment thread xds/src/main/java/com/linecorp/armeria/xds/server/XdsServerPlugin.java Outdated
@jrhee17
jrhee17 force-pushed the feat/xds-examples branch from 36c0a4c to d9b75e0 Compare August 4, 2026 07:50

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/xds-example/src/main/java/example/armeria/xds/ProfileState.java`:
- Line 1: Add the standard LY Corporation Apache 2.0 copyright header before the
package declaration in ProfileState.java, TrafficCounter.java, and
ServiceMesh.java; make no other changes.
- Around line 28-34: Validate each profile override in ProfileState.mergeFrom()
or its request acceptor against XdsTemplateReader.VALID_PROFILES for the
corresponding resource before storing it, while preserving valid overrides.
Update the /apply-profile validation to allow the OVERRIDE profile value, and
reject invalid values before applyProfile() invokes profileFor(resource).

In `@examples/xds-example/src/main/java/example/armeria/xds/ServiceMesh.java`:
- Around line 65-66: Update ServiceMesh.close() to delete the temporary
directory stored in configDir after all server and resource cleanup completes.
Remove the directory tree recursively, including generated files, while
preserving existing close behavior and handling the cleanup through the method’s
established exception strategy.

In `@examples/xds-example/src/main/resources/index.html`:
- Around line 505-528: Update the apply-profile request in the update flow after
`/api/update-file` succeeds to validate its response status and report a partial
failure when it is non-2xx, rather than calling `loadProfiles()` as if both
mutations succeeded. If feasible, consolidate the `/api/update-file` and
`/api/apply-profile` operations behind one server endpoint that performs them
atomically.
- Around line 363-369: Update loadConfig and the corresponding reload logic
around the referenced resource list so the current resource’s stable identity
(file, type, and name) is captured before replacing allResources, then restore
the matching resource after repopulating instead of reusing its old index; if no
match exists, clear the selection.
🪄 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 Plus

Run ID: 6813af8a-4614-4a58-8594-d0bc908d0dae

📥 Commits

Reviewing files that changed from the base of the PR and between 36c0a4c and d9b75e0.

📒 Files selected for processing (39)
  • examples/xds-example/build.gradle
  • examples/xds-example/docker-compose.yml
  • examples/xds-example/grafana/dashboards/xds-traffic.json
  • examples/xds-example/grafana/provisioning/dashboards/provider.yml
  • examples/xds-example/grafana/provisioning/datasources/datasource.yml
  • examples/xds-example/prometheus.yml
  • examples/xds-example/src/main/java/example/armeria/xds/DashboardService.java
  • examples/xds-example/src/main/java/example/armeria/xds/Layer2Server.java
  • examples/xds-example/src/main/java/example/armeria/xds/Layer3Server.java
  • examples/xds-example/src/main/java/example/armeria/xds/Main.java
  • examples/xds-example/src/main/java/example/armeria/xds/ProfileState.java
  • examples/xds-example/src/main/java/example/armeria/xds/RouteMetricsClient.java
  • examples/xds-example/src/main/java/example/armeria/xds/ServiceMesh.java
  • examples/xds-example/src/main/java/example/armeria/xds/TrafficCounter.java
  • examples/xds-example/src/main/java/example/armeria/xds/XdsTemplateReader.java
  • examples/xds-example/src/main/resources/index.html
  • examples/xds-example/src/main/resources/xds/basic/layer1-client-cluster.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer1-client-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer2-client-cluster.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer2-client-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer2-eds.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer2-server-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer3-eds.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer3-server-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/fault-retry/layer1-client-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/fault-retry/layer2-client-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/fault/layer1-client-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/fault/layer2-client-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/layer1-bootstrap.template.yaml
  • examples/xds-example/src/main/resources/xds/layer2-bootstrap.template.yaml
  • examples/xds-example/src/main/resources/xds/layer3-bootstrap.template.yaml
  • examples/xds-example/src/main/resources/xds/tls/layer1-client-cluster.template.yaml
  • examples/xds-example/src/main/resources/xds/tls/layer2-client-cluster.template.yaml
  • examples/xds-example/src/main/resources/xds/tls/layer2-server-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/tls/layer3-server-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/unhealthy/layer2-eds.template.yaml
  • examples/xds-example/src/main/resources/xds/unhealthy/layer3-eds.template.yaml
  • examples/xds-example/src/test/java/example/armeria/xds/ServiceMeshTest.java
  • settings.gradle
🚧 Files skipped from review as they are similar to previous changes (34)
  • settings.gradle
  • examples/xds-example/prometheus.yml
  • examples/xds-example/src/main/resources/xds/layer3-bootstrap.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer1-client-cluster.template.yaml
  • examples/xds-example/src/main/resources/xds/tls/layer1-client-cluster.template.yaml
  • examples/xds-example/src/main/resources/xds/tls/layer2-client-cluster.template.yaml
  • examples/xds-example/src/main/resources/xds/tls/layer2-server-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/unhealthy/layer2-eds.template.yaml
  • examples/xds-example/src/main/resources/xds/tls/layer3-server-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/layer2-bootstrap.template.yaml
  • examples/xds-example/src/main/java/example/armeria/xds/Layer3Server.java
  • examples/xds-example/build.gradle
  • examples/xds-example/docker-compose.yml
  • examples/xds-example/grafana/provisioning/dashboards/provider.yml
  • examples/xds-example/grafana/provisioning/datasources/datasource.yml
  • examples/xds-example/src/main/java/example/armeria/xds/Layer2Server.java
  • examples/xds-example/src/test/java/example/armeria/xds/ServiceMeshTest.java
  • examples/xds-example/src/main/java/example/armeria/xds/Main.java
  • examples/xds-example/src/main/java/example/armeria/xds/RouteMetricsClient.java
  • examples/xds-example/src/main/resources/xds/basic/layer2-client-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/layer1-bootstrap.template.yaml
  • examples/xds-example/src/main/resources/xds/unhealthy/layer3-eds.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer3-server-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer3-eds.template.yaml
  • examples/xds-example/src/main/resources/xds/fault/layer1-client-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer2-eds.template.yaml
  • examples/xds-example/src/main/resources/xds/fault-retry/layer2-client-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer2-client-cluster.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer1-client-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer2-server-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/fault/layer2-client-listener.template.yaml
  • examples/xds-example/src/main/java/example/armeria/xds/XdsTemplateReader.java
  • examples/xds-example/src/main/resources/xds/fault-retry/layer1-client-listener.template.yaml
  • examples/xds-example/src/main/java/example/armeria/xds/DashboardService.java

Comment thread examples/xds-example/src/main/java/example/armeria/xds/ServiceMesh.java Outdated
Comment thread examples/xds-playground/src/main/resources/index.html
Comment thread examples/xds-playground/src/main/resources/index.html

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
examples/xds-example/src/main/java/example/armeria/xds/XdsTemplateReader.java (2)

127-129: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Close both DirectoryStream loops.

Files.newDirectoryStream opens a DirectoryStream, and enhanced for loops do not close it. Wrap the outer and inner streams in try-with-resources in scanValidProfiles().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@examples/xds-example/src/main/java/example/armeria/xds/XdsTemplateReader.java`
around lines 127 - 129, The nested for loops in scanValidProfiles() use
Files.newDirectoryStream() which opens DirectoryStream resources that are not
closed by enhanced for loops, causing resource leaks. Wrap both the outer
Files.newDirectoryStream(base, Files::isDirectory) and inner
Files.newDirectoryStream(dir, "*.template.yaml") calls in separate
try-with-resources statements to ensure each DirectoryStream is properly closed,
while preserving the existing nested loop structure and logic.

71-76: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use the canonical xDS type URL when packing resources.

DiscoveryResponse.type_url uses type.googleapis.com/..., but Any.pack(resource, "") sets the packed resource type URL to the resource full qualified name without the canonical prefix. Envoy requires the packed resource URL to match the response type URL. Use Any.pack(resource) or pass "type.googleapis.com" explicitly.

Proposed fix
-            builder.addResources(Any.pack(resource, ""));
+            builder.addResources(Any.pack(resource));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@examples/xds-example/src/main/java/example/armeria/xds/XdsTemplateReader.java`
around lines 71 - 76, The Any.pack call in the resources loop is using an empty
string as the type URL prefix instead of the canonical xDS prefix that the
DiscoveryResponse typeUrl field expects. Update the
builder.addResources(Any.pack(resource, "")) line to either omit the second
parameter and call Any.pack(resource) to use the default canonical prefix, or
explicitly pass "type.googleapis.com" as the prefix parameter. This ensures the
packed resource URL matches the canonical format in the response type URL.
♻️ Duplicate comments (2)
examples/xds-example/src/main/java/example/armeria/xds/XdsTemplateReader.java (2)

82-87: 🩺 Stability & Availability | 🟠 Major

Delete the temporary file after a failed publication.

If Files.writeString or Files.move fails, tmp remains in the configuration directory. Repeated profile update failures can accumulate .xds-*.tmp files and exhaust disk space. Delete the temporary file in the failure path while preserving the original exception.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@examples/xds-example/src/main/java/example/armeria/xds/XdsTemplateReader.java`
around lines 82 - 87, Update the temporary-file publication flow in
XdsTemplateReader to delete tmp when Files.writeString or Files.move fails,
while preserving and rethrowing the original exception. Use a failure-safe
cleanup mechanism around the existing write-and-move operations so cleanup
errors do not replace the publication failure.

Source: Linters/SAST tools


123-139: 🩺 Stability & Availability | 🟠 Major

Support profile discovery from packaged JARs.

If /xds/ resolves to a jar: URI, Path.of(...) requires an existing filesystem for that URI. The static initializer can then fail before the dashboard starts. Use an explicit template index or a scanner that supports both directory and JAR classpath entries. The JDK documents FileSystemNotFoundException when the filesystem identified by a URI is unavailable. (docs.oracle.com)

This remains the issue reported in the previous review.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@examples/xds-example/src/main/java/example/armeria/xds/XdsTemplateReader.java`
around lines 123 - 139, Update scanValidProfiles() to discover templates when
the /xds/ classpath resource is packaged in a JAR, not only when it resolves to
a directory Path. Use an explicit template index or classpath scanner that
handles both file and jar URLs, while preserving the existing profile/name
mapping and sorting behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/xds-example/src/main/java/example/armeria/xds/Layer2Server.java`:
- Around line 69-74: Update Layer2Server.closeAsync() to replace the synchronous
preprocessor.close() and xdsBootstrap.close() calls with their asynchronous
close APIs. Combine both returned futures with server.stop() and return the
combined shutdown future without blocking.

---

Outside diff comments:
In
`@examples/xds-example/src/main/java/example/armeria/xds/XdsTemplateReader.java`:
- Around line 127-129: The nested for loops in scanValidProfiles() use
Files.newDirectoryStream() which opens DirectoryStream resources that are not
closed by enhanced for loops, causing resource leaks. Wrap both the outer
Files.newDirectoryStream(base, Files::isDirectory) and inner
Files.newDirectoryStream(dir, "*.template.yaml") calls in separate
try-with-resources statements to ensure each DirectoryStream is properly closed,
while preserving the existing nested loop structure and logic.
- Around line 71-76: The Any.pack call in the resources loop is using an empty
string as the type URL prefix instead of the canonical xDS prefix that the
DiscoveryResponse typeUrl field expects. Update the
builder.addResources(Any.pack(resource, "")) line to either omit the second
parameter and call Any.pack(resource) to use the default canonical prefix, or
explicitly pass "type.googleapis.com" as the prefix parameter. This ensures the
packed resource URL matches the canonical format in the response type URL.

---

Duplicate comments:
In
`@examples/xds-example/src/main/java/example/armeria/xds/XdsTemplateReader.java`:
- Around line 82-87: Update the temporary-file publication flow in
XdsTemplateReader to delete tmp when Files.writeString or Files.move fails,
while preserving and rethrowing the original exception. Use a failure-safe
cleanup mechanism around the existing write-and-move operations so cleanup
errors do not replace the publication failure.
- Around line 123-139: Update scanValidProfiles() to discover templates when the
/xds/ classpath resource is packaged in a JAR, not only when it resolves to a
directory Path. Use an explicit template index or classpath scanner that handles
both file and jar URLs, while preserving the existing profile/name mapping and
sorting behavior.
🪄 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 Plus

Run ID: 8d1a67a7-4d01-486f-9848-a5dc7eec3e77

📥 Commits

Reviewing files that changed from the base of the PR and between d9b75e0 and 409403f.

📒 Files selected for processing (4)
  • examples/xds-example/src/main/java/example/armeria/xds/Layer2Server.java
  • examples/xds-example/src/main/java/example/armeria/xds/Layer3Server.java
  • examples/xds-example/src/main/java/example/armeria/xds/ServiceMesh.java
  • examples/xds-example/src/main/java/example/armeria/xds/XdsTemplateReader.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • examples/xds-example/src/main/java/example/armeria/xds/ServiceMesh.java
  • examples/xds-example/src/main/java/example/armeria/xds/Layer3Server.java

@jrhee17
jrhee17 force-pushed the feat/xds-examples branch from 409403f to bd4c7ff Compare August 4, 2026 09:30

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 7

🧹 Nitpick comments (3)
examples/xds-example/src/main/resources/index.html (1)

91-96: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Populate the profile options from the server.

The profileSelect options are hardcoded to basic, tls, fault, and fault-retry. The server derives profiles from the xds template directories, which also include unhealthy. The list drifts whenever a template directory is added or removed. Build the options from the /api/profiles response in loadProfiles, and keep profileDescriptions as a lookup for known profiles.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/xds-example/src/main/resources/index.html` around lines 91 - 96,
Update loadProfiles to fetch profiles from /api/profiles and dynamically
populate profileSelect instead of relying on hardcoded option elements. Preserve
profileDescriptions as the lookup for known profile descriptions, while allowing
server-provided profiles such as unhealthy and reflecting template additions or
removals.
examples/xds-example/src/main/java/example/armeria/xds/XdsTemplateReader.java (2)

53-68: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Extract the template substitution loop.

read and readBootstrap repeat the same ${key} replacement loop. Extract one private helper and call it from both methods.

♻️ Proposed refactor
     static <T extends GeneratedMessageV3> T read(
             ProfileState state, String name, Map<String, String> vars, Class<T> type) {
-        String yaml = readTemplate(state.profileFor(name) + '/' + name);
-        for (var entry : vars.entrySet()) {
-            yaml = yaml.replace("${" + entry.getKey() + '}', entry.getValue());
-        }
-        return XdsResourceReader.from(yaml, type);
+        final String yaml = substitute(readTemplate(state.profileFor(name) + '/' + name), vars);
+        return XdsResourceReader.from(yaml, type);
     }
 
     static Bootstrap readBootstrap(String name, Map<String, String> vars) {
-        String yaml = readTemplate(name);
-        for (var entry : vars.entrySet()) {
-            yaml = yaml.replace("${" + entry.getKey() + '}', entry.getValue());
-        }
-        return XdsResourceReader.from(yaml, Bootstrap.class);
+        return XdsResourceReader.from(substitute(readTemplate(name), vars), Bootstrap.class);
     }
+
+    private static String substitute(String template, Map<String, String> vars) {
+        String yaml = template;
+        for (Map.Entry<String, String> entry : vars.entrySet()) {
+            yaml = yaml.replace("${" + entry.getKey() + '}', entry.getValue());
+        }
+        return yaml;
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@examples/xds-example/src/main/java/example/armeria/xds/XdsTemplateReader.java`
around lines 53 - 68, Extract the duplicated variable-substitution loop from
read and readBootstrap into a private helper that accepts the YAML template and
vars map, performs each ${key} replacement, and returns the updated YAML.
Replace both inline loops with calls to this helper while preserving the
existing parsing behavior and method signatures.

103-111: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Narrow the catch to IOException.

JsonFormat.Printer.print throws InvalidProtocolBufferException and the Jackson calls throw JsonProcessingException. Both are IOException subtypes. Catch IOException and throw UncheckedIOException, which matches the other methods in this class and avoids swallowing unrelated runtime exceptions.

♻️ Proposed refactor
     static String printProtoYaml(GeneratedMessageV3 msg) {
         try {
             final String json = PROTO_PRINTER.print(msg);
             final JsonNode tree = JSON_MAPPER.readTree(json);
             return YAML_MAPPER.writeValueAsString(tree);
-        } catch (Exception e) {
-            throw new RuntimeException(e);
+        } catch (IOException e) {
+            throw new UncheckedIOException("Failed to print: " + msg.getDescriptorForType().getFullName(), e);
         }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@examples/xds-example/src/main/java/example/armeria/xds/XdsTemplateReader.java`
around lines 103 - 111, Update printProtoYaml to catch only IOException,
covering the checked failures from PROTO_PRINTER.print and the Jackson
read/write calls, and rethrow it as UncheckedIOException. Leave unrelated
runtime exceptions uncaught and match the exception-handling pattern used by the
other methods in XdsTemplateReader.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@examples/xds-example/src/main/java/example/armeria/xds/DashboardService.java`:
- Around line 35-36: Annotate DashboardService with `@UnstableApi` so its public
API methods inherit the required stability marker, and add Javadoc to every
public endpoint method in the class, including those at the referenced
locations. Preserve the existing endpoint behavior and ensure the annotation
import is added if needed.
- Around line 205-209: Update applyProfile in DashboardService to synchronize
the profileState merge and XdsTemplateReader.applyProfile call under a single
lock. Ensure concurrent requests cannot read-modify-write profileState or apply
profile files simultaneously, while preserving the existing response behavior.

In `@examples/xds-example/src/main/java/example/armeria/xds/Main.java`:
- Around line 19-25: Annotate the public Main class with `@UnstableApi` and add
Javadoc documenting the public main(String[] args) entry point. Ensure the
required annotation import is present and retain the existing startup behavior.

In
`@examples/xds-example/src/main/java/example/armeria/xds/RouteMetricsClient.java`:
- Line 14: Annotate the newly added public API in RouteMetricsClient with
`@UnstableApi`, applying it to the class or execute() method, and add Javadoc
documenting execute(). Ensure every public method in the affected class has the
required documentation.

In
`@examples/xds-example/src/main/java/example/armeria/xds/XdsTemplateReader.java`:
- Around line 83-91: Update the temp-file handling in XdsTemplateReader so the
Path created by Files.createTempFile is available for cleanup, and ensure it is
deleted when writing or atomic publishing fails. Keep successful moves intact,
and perform cleanup without masking the original IOException.
- Around line 120-127: The outer and inner Files.newDirectoryStream calls in the
nested loops are not being closed, causing OS handle leaks. Wrap the outer for
loop that iterates over Files.newDirectoryStream(base, Files::isDirectory) with
a try-with-resources statement that declares the returned DirectoryStream, and
similarly wrap the inner for loop that iterates over
Files.newDirectoryStream(dir, "*.template.yaml") with its own try-with-resources
statement. Ensure both try-with-resources blocks are properly nested to close
both streams in the correct order.

In `@examples/xds-example/src/main/resources/index.html`:
- Around line 533-535: Chain the initial load calls so loadConfig executes only
after loadProfiles has completed and populated validProfiles, then run
refreshStats after the dependent loads finish. Update the startup sequence
around loadProfiles, loadConfig, and refreshStats while preserving their
existing behavior.

---

Nitpick comments:
In
`@examples/xds-example/src/main/java/example/armeria/xds/XdsTemplateReader.java`:
- Around line 53-68: Extract the duplicated variable-substitution loop from read
and readBootstrap into a private helper that accepts the YAML template and vars
map, performs each ${key} replacement, and returns the updated YAML. Replace
both inline loops with calls to this helper while preserving the existing
parsing behavior and method signatures.
- Around line 103-111: Update printProtoYaml to catch only IOException, covering
the checked failures from PROTO_PRINTER.print and the Jackson read/write calls,
and rethrow it as UncheckedIOException. Leave unrelated runtime exceptions
uncaught and match the exception-handling pattern used by the other methods in
XdsTemplateReader.

In `@examples/xds-example/src/main/resources/index.html`:
- Around line 91-96: Update loadProfiles to fetch profiles from /api/profiles
and dynamically populate profileSelect instead of relying on hardcoded option
elements. Preserve profileDescriptions as the lookup for known profile
descriptions, while allowing server-provided profiles such as unhealthy and
reflecting template additions or removals.
🪄 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 Plus

Run ID: 72dd5669-8344-4af8-8d17-72ae4f2b430e

📥 Commits

Reviewing files that changed from the base of the PR and between 409403f and bd4c7ff.

📒 Files selected for processing (39)
  • examples/xds-example/build.gradle
  • examples/xds-example/docker-compose.yml
  • examples/xds-example/grafana/dashboards/xds-traffic.json
  • examples/xds-example/grafana/provisioning/dashboards/provider.yml
  • examples/xds-example/grafana/provisioning/datasources/datasource.yml
  • examples/xds-example/prometheus.yml
  • examples/xds-example/src/main/java/example/armeria/xds/DashboardService.java
  • examples/xds-example/src/main/java/example/armeria/xds/Layer2Server.java
  • examples/xds-example/src/main/java/example/armeria/xds/Layer3Server.java
  • examples/xds-example/src/main/java/example/armeria/xds/Main.java
  • examples/xds-example/src/main/java/example/armeria/xds/ProfileState.java
  • examples/xds-example/src/main/java/example/armeria/xds/RouteMetricsClient.java
  • examples/xds-example/src/main/java/example/armeria/xds/ServiceMesh.java
  • examples/xds-example/src/main/java/example/armeria/xds/TrafficCounter.java
  • examples/xds-example/src/main/java/example/armeria/xds/XdsTemplateReader.java
  • examples/xds-example/src/main/resources/index.html
  • examples/xds-example/src/main/resources/xds/basic/layer1-client-cluster.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer1-client-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer2-client-cluster.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer2-client-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer2-eds.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer2-server-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer3-eds.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer3-server-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/fault-retry/layer1-client-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/fault-retry/layer2-client-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/fault/layer1-client-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/fault/layer2-client-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/layer1-bootstrap.template.yaml
  • examples/xds-example/src/main/resources/xds/layer2-bootstrap.template.yaml
  • examples/xds-example/src/main/resources/xds/layer3-bootstrap.template.yaml
  • examples/xds-example/src/main/resources/xds/tls/layer1-client-cluster.template.yaml
  • examples/xds-example/src/main/resources/xds/tls/layer2-client-cluster.template.yaml
  • examples/xds-example/src/main/resources/xds/tls/layer2-server-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/tls/layer3-server-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/unhealthy/layer2-eds.template.yaml
  • examples/xds-example/src/main/resources/xds/unhealthy/layer3-eds.template.yaml
  • examples/xds-example/src/test/java/example/armeria/xds/ServiceMeshTest.java
  • settings.gradle
🚧 Files skipped from review as they are similar to previous changes (33)
  • settings.gradle
  • examples/xds-example/src/main/resources/xds/layer1-bootstrap.template.yaml
  • examples/xds-example/build.gradle
  • examples/xds-example/grafana/provisioning/dashboards/provider.yml
  • examples/xds-example/src/main/resources/xds/basic/layer2-server-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/fault-retry/layer2-client-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/fault-retry/layer1-client-listener.template.yaml
  • examples/xds-example/grafana/provisioning/datasources/datasource.yml
  • examples/xds-example/src/main/resources/xds/fault/layer1-client-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer2-eds.template.yaml
  • examples/xds-example/docker-compose.yml
  • examples/xds-example/src/main/resources/xds/basic/layer2-client-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer3-server-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/tls/layer2-client-cluster.template.yaml
  • examples/xds-example/src/main/resources/xds/layer3-bootstrap.template.yaml
  • examples/xds-example/src/main/resources/xds/tls/layer1-client-cluster.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer1-client-cluster.template.yaml
  • examples/xds-example/src/main/java/example/armeria/xds/TrafficCounter.java
  • examples/xds-example/prometheus.yml
  • examples/xds-example/src/main/resources/xds/tls/layer3-server-listener.template.yaml
  • examples/xds-example/src/test/java/example/armeria/xds/ServiceMeshTest.java
  • examples/xds-example/src/main/resources/xds/unhealthy/layer2-eds.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer2-client-cluster.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer1-client-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer3-eds.template.yaml
  • examples/xds-example/grafana/dashboards/xds-traffic.json
  • examples/xds-example/src/main/resources/xds/layer2-bootstrap.template.yaml
  • examples/xds-example/src/main/resources/xds/tls/layer2-server-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/unhealthy/layer3-eds.template.yaml
  • examples/xds-example/src/main/resources/xds/fault/layer2-client-listener.template.yaml
  • examples/xds-example/src/main/java/example/armeria/xds/Layer3Server.java
  • examples/xds-example/src/main/java/example/armeria/xds/ServiceMesh.java
  • examples/xds-example/src/main/java/example/armeria/xds/Layer2Server.java

Comment thread examples/xds-example/src/main/java/example/armeria/xds/DashboardService.java Outdated
Comment thread examples/xds-playground/src/main/java/example/armeria/xds/Main.java
Comment thread examples/xds-example/src/main/java/example/armeria/xds/XdsTemplateReader.java Outdated
Comment thread examples/xds-example/src/main/resources/index.html Outdated
@jrhee17
jrhee17 force-pushed the feat/xds-examples branch from bd4c7ff to d97fc61 Compare August 5, 2026 09:29
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 6

🧹 Nitpick comments (1)
xds/src/main/java/com/linecorp/armeria/xds/RouteStream.java (1)

178-188: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use protobuf field-presence checks for retry-policy precedence.

getRetryPolicy() returns the default message when the field is absent, so comparing with RetryPolicy.getDefaultInstance() treats non-absent configured policies as absent and can skip route-level precedence. Use hasRetryPolicy() for the route action and virtual host before building the retry decoration.

Proposed change
-            if (routeRetryPolicy != RetryPolicy.getDefaultInstance()) {
+            if (route.getRoute().hasRetryPolicy()) {
                 effectiveRetryPolicy = routeRetryPolicy;
-            } else if (vhostRetryPolicy != RetryPolicy.getDefaultInstance()) {
+            } else if (vhostResource.resource().hasRetryPolicy()) {
                 effectiveRetryPolicy = vhostRetryPolicy;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@xds/src/main/java/com/linecorp/armeria/xds/RouteStream.java` around lines 178
- 188, Update the retry-policy selection in RouteStream to use protobuf
field-presence checks: prefer the route action’s policy when
route.getRoute().hasRetryPolicy() is true, otherwise use the virtual host policy
when vhostResource.resource().hasRetryPolicy() is true, and retain null when
neither field is present before building the retry decoration.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/xds-example/grafana/dashboards/xds-traffic.json`:
- Around line 258-260: Remove the trailing comma from the single-element
transformations array in the xds-traffic dashboard JSON so the organize
transformation entry is the last item without a dangling comma; keep the
existing transformations structure and only update the JSON syntax around the
transformations field.

In
`@examples/xds-example/src/main/java/example/armeria/xds/DashboardService.java`:
- Line 1: Add the repository-standard copyright header before the package
declaration in DashboardService.java and ServiceMeshTest.java at the specified
sites; leave the existing package declarations and file contents unchanged.
- Around line 167-171: Update the type dispatch in DashboardService’s preview
handling to treat unsupported type values as a BAD_REQUEST response directly,
matching the existing missing-field behavior. Replace the
IllegalArgumentException path in the switch default with the established
response flow while preserving the listener, cluster, and endpoint handling.

In
`@examples/xds-example/src/main/java/example/armeria/xds/RouteMetricsClient.java`:
- Line 1: Insert the standard project copyright header at the top of
RouteMetricsClient before the package declaration, using the same header format
used by other files in the example.armeria.xds package.

In
`@examples/xds-example/src/main/java/example/armeria/xds/XdsTemplateReader.java`:
- Line 1: Add the project-standard copyright header at the beginning of
XdsTemplateReader.java, before the package declaration, matching the header
format used by other Java source files in the project.

In
`@examples/xds-example/src/main/resources/xds/tls/layer2-client-cluster.template.yaml`:
- Around line 18-20: Add match_typed_subject_alt_names under validation_context
to require the Layer 2 service identity in the upstream peer certificate, while
retaining trusted_ca validation. Update the corresponding Layer 2 certificate
issuance configuration to include the same identity so Envoy’s SAN check
succeeds.

---

Nitpick comments:
In `@xds/src/main/java/com/linecorp/armeria/xds/RouteStream.java`:
- Around line 178-188: Update the retry-policy selection in RouteStream to use
protobuf field-presence checks: prefer the route action’s policy when
route.getRoute().hasRetryPolicy() is true, otherwise use the virtual host policy
when vhostResource.resource().hasRetryPolicy() is true, and retain null when
neither field is present before building the retry decoration.
🪄 Autofix

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 Plus

Run ID: 1ff9ddd4-d6eb-4a14-89c1-7eda4eec4b47

📥 Commits

Reviewing files that changed from the base of the PR and between cde362f and d97fc61.

📒 Files selected for processing (41)
  • examples/xds-example/build.gradle
  • examples/xds-example/docker-compose.yml
  • examples/xds-example/grafana/dashboards/xds-traffic.json
  • examples/xds-example/grafana/provisioning/dashboards/provider.yml
  • examples/xds-example/grafana/provisioning/datasources/datasource.yml
  • examples/xds-example/prometheus.yml
  • examples/xds-example/src/main/java/example/armeria/xds/DashboardService.java
  • examples/xds-example/src/main/java/example/armeria/xds/Layer2Server.java
  • examples/xds-example/src/main/java/example/armeria/xds/Layer3Server.java
  • examples/xds-example/src/main/java/example/armeria/xds/Main.java
  • examples/xds-example/src/main/java/example/armeria/xds/ProfileState.java
  • examples/xds-example/src/main/java/example/armeria/xds/RouteMetricsClient.java
  • examples/xds-example/src/main/java/example/armeria/xds/ServiceMesh.java
  • examples/xds-example/src/main/java/example/armeria/xds/XdsTemplateReader.java
  • examples/xds-example/src/main/resources/index.html
  • examples/xds-example/src/main/resources/xds/basic/layer1-client-cluster.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer1-client-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer1-endpoints.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer2-client-cluster.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer2-client-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer2-endpoints.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer2-server-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer3-server-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/fault/layer2-server-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/fault/layer3-server-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/layer1-bootstrap.template.yaml
  • examples/xds-example/src/main/resources/xds/layer2-client-bootstrap.template.yaml
  • examples/xds-example/src/main/resources/xds/layer2-server-bootstrap.template.yaml
  • examples/xds-example/src/main/resources/xds/layer3-bootstrap.template.yaml
  • examples/xds-example/src/main/resources/xds/retry/layer1-client-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/retry/layer2-client-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/tls/layer1-client-cluster.template.yaml
  • examples/xds-example/src/main/resources/xds/tls/layer2-client-cluster.template.yaml
  • examples/xds-example/src/main/resources/xds/tls/layer2-server-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/tls/layer3-server-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/unhealthy/layer1-endpoints.template.yaml
  • examples/xds-example/src/main/resources/xds/unhealthy/layer2-endpoints.template.yaml
  • examples/xds-example/src/test/java/example/armeria/xds/ServiceMeshTest.java
  • it/xds-client/src/test/java/com/linecorp/armeria/xds/it/RetryTest.java
  • settings.gradle
  • xds/src/main/java/com/linecorp/armeria/xds/RouteStream.java
🚧 Files skipped from review as they are similar to previous changes (20)
  • settings.gradle
  • examples/xds-example/src/main/resources/xds/tls/layer1-client-cluster.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer1-client-cluster.template.yaml
  • examples/xds-example/docker-compose.yml
  • examples/xds-example/build.gradle
  • examples/xds-example/grafana/provisioning/dashboards/provider.yml
  • examples/xds-example/src/main/resources/xds/basic/layer1-client-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/layer3-bootstrap.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer2-client-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/tls/layer3-server-listener.template.yaml
  • examples/xds-example/grafana/provisioning/datasources/datasource.yml
  • examples/xds-example/src/main/resources/xds/tls/layer2-server-listener.template.yaml
  • examples/xds-example/src/main/resources/xds/basic/layer2-server-listener.template.yaml
  • examples/xds-example/src/main/java/example/armeria/xds/ProfileState.java
  • examples/xds-example/src/main/java/example/armeria/xds/Main.java
  • examples/xds-example/prometheus.yml
  • examples/xds-example/src/main/java/example/armeria/xds/ServiceMesh.java
  • examples/xds-example/src/main/resources/xds/basic/layer3-server-listener.template.yaml
  • examples/xds-example/src/main/java/example/armeria/xds/Layer2Server.java
  • examples/xds-example/src/main/java/example/armeria/xds/Layer3Server.java

Comment thread examples/xds-playground/grafana/dashboards/xds-traffic.json
@jrhee17
jrhee17 force-pushed the feat/xds-examples branch 4 times, most recently from d7f0f7c to 4d08628 Compare August 5, 2026 10:16
@jrhee17 jrhee17 changed the title [POC] Add an example for xDS integration Add an example for xDS integration Aug 5, 2026
@jrhee17 jrhee17 added this to the 1.42.0 milestone Aug 5, 2026
@jrhee17
jrhee17 force-pushed the feat/xds-examples branch from 4d08628 to 4526719 Compare August 5, 2026 12:52
@codecov

codecov Bot commented Aug 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 61.39535% with 166 lines in your changes missing coverage. Please review.
✅ Project coverage is 75.09%. Comparing base (8150425) to head (4526719).
⚠️ Report is 562 commits behind head on main.

Files with missing lines Patch % Lines
...ain/java/example/armeria/xds/DashboardService.java 0.00% 98 Missing ⚠️
...in/java/example/armeria/xds/XdsTemplateReader.java 76.23% 21 Missing and 3 partials ⚠️
...xample/src/main/java/example/armeria/xds/Main.java 0.00% 22 Missing ⚠️
...rc/main/java/example/armeria/xds/ProfileState.java 35.29% 7 Missing and 4 partials ⚠️
...src/main/java/example/armeria/xds/ServiceMesh.java 92.70% 7 Missing ⚠️
...rc/main/java/example/armeria/xds/Layer2Server.java 95.45% 2 Missing ⚠️
...rc/main/java/example/armeria/xds/Layer3Server.java 92.85% 2 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #6886      +/-   ##
============================================
+ Coverage     74.46%   75.09%   +0.63%     
- Complexity    22234    25567    +3333     
============================================
  Files          1963     2275     +312     
  Lines         82437    94978   +12541     
  Branches      10764    12417    +1653     
============================================
+ Hits          61385    71323    +9938     
- Misses        15918    17771    +1853     
- Partials       5134     5884     +750     

☔ View full report in Codecov by Harness.
📢 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 August 6, 2026 01:18
@jrhee17
jrhee17 requested review from ikhoon and minwoox as code owners August 6, 2026 01:18
@mergify

mergify Bot commented Aug 6, 2026

Copy link
Copy Markdown

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

@minwoox minwoox left a comment

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.

Nice example. 👍

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