Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughCentralizes xDS extension/validation via a new Changes
Sequence DiagramsequenceDiagram
participant Bootstrap as XdsBootstrapImpl
participant Validator as XdsResourceValidator
participant Registry as XdsExtensionRegistry
participant Parser as ResourceParser
participant Factory as XdsExtensionFactory
participant Stream as TransportSocketStream
participant Resource as XdsResource
Bootstrap->>Validator: new XdsResourceValidator()
Validator->>Validator: load SPI validators
Bootstrap->>Registry: XdsExtensionRegistry.of(validator)
Registry->>Registry: load/register SPI factories + built-ins
Parser->>Registry: parseResources(anys, registry, version)
Registry->>Validator: assertValid(unpackedMessage)
Parser->>Factory: query(Any/typeUrl or name)
Factory->>Validator: validator.unpack(Any, MessageType)
Factory-->>Parser: create(...) -> XdsResource
Stream->>Registry: context.extensionRegistry()
Stream->>Factory: query by typeUrl/name (transport socket)
Factory-->>Stream: SnapshotStream<TransportSocketSnapshot>
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
xds/src/main/java/com/linecorp/armeria/xds/XdsStreamSubscriber.java (1)
56-65:⚠️ Potential issue | 🟡 Minor
restartTimer()can accumulate multiple pending absent timers.If this method is called more than once before timeout, prior scheduled tasks are not canceled, which can trigger duplicate
onAbsent()notifications.Suggested fix
void restartTimer() { if (!enableAbsentOnTimeout) { return; } + maybeCancelAbsentTimer(); initialAbsentFuture = eventLoop.schedule(() -> { initialAbsentFuture = null; onAbsent(); }, timeoutMillis, TimeUnit.MILLISECONDS); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@xds/src/main/java/com/linecorp/armeria/xds/XdsStreamSubscriber.java` around lines 56 - 65, restartTimer() can schedule multiple pending timers because it doesn't cancel any previously scheduled initialAbsentFuture; modify restartTimer() to check initialAbsentFuture and cancel it (e.g., initialAbsentFuture.cancel(false)) if it's non-null and not done before assigning a new scheduled future on eventLoop, preserving existing checks for enableAbsentOnTimeout and retaining the same behavior that clears initialAbsentFuture and calls onAbsent() when the timeout fires.
🧹 Nitpick comments (6)
xds/src/main/java/com/linecorp/armeria/xds/CompositeXdsStream.java (1)
28-38: Document stream supplier contract or add deduplication.The API accepts a supplier with no explicit guarantee that distinct stream instances are created per
XdsType. All current call sites correctly create distinct instances, but the contract should be clarified. Either document that suppliers must return distinct instances, or add identity deduplication inclose()to guard against accidental reuse.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@xds/src/main/java/com/linecorp/armeria/xds/CompositeXdsStream.java` around lines 28 - 38, The constructor of CompositeXdsStream accepts a streamSupplier without guaranteeing distinct instances per XdsType; to fix, update the contract and add identity-based deduplication in close(): in CompositeXdsStream.close() use an identity-based Set (e.g., Collections.newSetFromMap(new IdentityHashMap<>())) to track seen XdsStream instances and call close() only once per unique instance; mention in the constructor/Javadoc that suppliers should preferably return distinct instances but the close() protects against accidental reuse of the same instance across types.xds/src/main/java/com/linecorp/armeria/xds/SecretXdsResource.java (1)
41-43: Add a defensive null-check forsecretin constructor.This keeps failure mode explicit at construction time rather than surfacing as later NPEs.
As per coding guidelines, follow LY OSS style for null checks using `Objects.requireNonNull`.Suggested fix
private SecretXdsResource(Secret secret, String version, long revision) { super(version, revision); - this.secret = secret; + this.secret = java.util.Objects.requireNonNull(secret, "secret"); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@xds/src/main/java/com/linecorp/armeria/xds/SecretXdsResource.java` around lines 41 - 43, Add a defensive null-check in the SecretXdsResource constructor: use Objects.requireNonNull on the secret parameter (in SecretXdsResource(Secret secret, String version, long revision)) and provide a clear message (e.g., "secret") so construction fails fast; also ensure java.util.Objects is imported if not already and then assign the checked value to the instance field 'secret'.xds/src/main/java/com/linecorp/armeria/xds/XdsUnpackUtil.java (1)
33-45: Consider replacingassertwith explicit null check.The
assert factory != nullon line 41 will be a no-op when assertions are disabled (which is typical in production). If reaching this point without a factory is a programming error that should never occur, consider usingObjects.requireNonNullorPreconditions.checkNotNullto ensure consistent behavior regardless of assertion settings.♻️ Suggested fix
`@Nullable` static HttpConnectionManager unpackConnectionManager(Listener listener, XdsExtensionRegistry registry) { if (listener.getApiListener().hasApiListener()) { final Any apiListener = listener.getApiListener().getApiListener(); final HttpConnectionManagerFactory factory = registry.queryByTypeUrl(apiListener.getTypeUrl(), HttpConnectionManagerFactory.class); - assert factory != null; + if (factory == null) { + throw new IllegalStateException( + "No HttpConnectionManagerFactory registered for type URL: " + + apiListener.getTypeUrl()); + } return factory.create(apiListener, registry.validator()); } return null; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@xds/src/main/java/com/linecorp/armeria/xds/XdsUnpackUtil.java` around lines 33 - 45, In unpackConnectionManager(Listener listener, XdsExtensionRegistry registry) replace the reliance on a Java assert for the HttpConnectionManagerFactory lookup with an explicit null-check and fail-fast behavior: after calling registry.queryByTypeUrl(..., HttpConnectionManagerFactory.class) verify the returned factory is non-null (e.g. Objects.requireNonNull or throw new IllegalStateException with a clear message referencing the typeUrl and listener) before invoking factory.create(apiListener, registry.validator()); this ensures consistent runtime behavior when HttpConnectionManagerFactory is missing.it/xds-client/src/test/java/com/linecorp/armeria/xds/it/XdsControlPlaneErrorHandlingTest.java (1)
90-92: Static shared state across tests - verify isolation.The
version,cache, andnackTrackerare static and shared across all parameterized test executions. WhilesetUpresetsnackTracker, thecacheretains snapshots from prior tests andversionkeeps incrementing. This should be fine since each test sets its own snapshot before assertions, but consider if test ordering or parallel execution could cause flakiness.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@it/xds-client/src/test/java/com/linecorp/armeria/xds/it/XdsControlPlaneErrorHandlingTest.java` around lines 90 - 92, The static shared state (version, cache, nackTracker) can leak between parameterized test runs; fix by ensuring isolation: either make version and cache instance fields instead of static, or reinitialize them in the test setUp method (e.g., version = new AtomicLong(); cache = new SimpleCache<>(node -> GROUP); and call nackTracker.reset() as already done) so each test gets fresh instances; update references to version/cache accordingly and keep nackTracker.reset() in setUp to guarantee a clean state before each test.it/xds-client/src/test/java/com/linecorp/armeria/xds/it/DeltaXdsPreprocessorTest.java (1)
93-95: Minor: Addfinalmodifier for consistency.Other
@RegisterExtensionfields in this class are declaredstatic final. Consider addingfinaltoeventLoopfor consistency.Suggested fix
`@RegisterExtension` `@Order`(3) - static EventLoopExtension eventLoop = new EventLoopExtension(); + static final EventLoopExtension eventLoop = new EventLoopExtension();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@it/xds-client/src/test/java/com/linecorp/armeria/xds/it/DeltaXdsPreprocessorTest.java` around lines 93 - 95, Make the field declaration for the EventLoopExtension consistent by adding the final modifier: change the declaration of the static field eventLoop (type EventLoopExtension) in DeltaXdsPreprocessorTest to be static final so it matches the other `@RegisterExtension` fields in the class.xds/src/main/java/com/linecorp/armeria/xds/ListenerXdsResource.java (1)
116-121: Consider adding Javadoc for future public exposure.The
downstreamFilters()accessor is currently package-private, which is fine. If this method is intended to become public in the future, consider adding Javadoc now.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@xds/src/main/java/com/linecorp/armeria/xds/ListenerXdsResource.java` around lines 116 - 121, Add Javadoc to the downstreamFilters() accessor in ListenerXdsResource describing what it returns and any expectations for callers: state that it returns the pre-resolved downstream XdsHttpFilter instances, whether the returned List is mutable/immutable and thread-safety assumptions, include an `@return` tag, and mention nullability (never null) or alternative behavior; keep the method package-private but document that it may be made public in the future so callers understand its contract.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@xds/src/main/java/com/linecorp/armeria/xds/DeltaDiscoveryStub.java`:
- Around line 63-64: The switch default in DeltaDiscoveryStub.basic() (and the
analogous default in SotwDiscoveryStub) throws java.lang.Error for unsupported
XdsType values; change these to throw new IllegalArgumentException("Unexpected
value: " + type) (or similar IllegalArgumentException) so the code uses a proper
checked runtime exception for bad input/enum values; update both
DeltaDiscoveryStub.basic() and the matching method in SotwDiscoveryStub to
replace throw new Error(...) with throw new IllegalArgumentException(...) and
keep the original message text for clarity.
In `@xds/src/main/java/com/linecorp/armeria/xds/TransportSocketFactory.java`:
- Line 2: Revert the copyright header year change in TransportSocketFactory.java
back to the original year; locate the file/class TransportSocketFactory and
restore the previous copyright line (do not update to 2026) so the file keeps
its original header year.
In `@xds/src/main/java/com/linecorp/armeria/xds/XdsBootstrapImpl.java`:
- Around line 51-53: The bootstrap validation is performed after the
DirectoryWatchService is already created, which can leak resources if validation
throws; before creating DirectoryWatchService, run validation by instantiating
XdsResourceValidator and XdsExtensionRegistry and calling
extensionRegistry.assertValid(bootstrap), or if you must keep
DirectoryWatchService creation earlier, ensure any partially initialized state
is closed on validation failure by calling the
DirectoryWatchService.close()/shutdown method in the catch/finally surrounding
extensionRegistry.assertValid(bootstrap); update the code paths that reference
XdsResourceValidator, XdsExtensionRegistry,
extensionRegistry.assertValid(bootstrap), and DirectoryWatchService accordingly.
In `@xds/src/main/java/com/linecorp/armeria/xds/XdsExtensionRegistry.java`:
- Around line 73-79: The register method currently overwrites existing entries
in byName and byTypeUrl which hides duplicate XdsExtensionFactory registrations;
update register(XdsExtensionFactory factory, Map<String,XdsExtensionFactory>
byName, Map<String,XdsExtensionFactory> byTypeUrl) to detect collisions before
inserting: if byName already contains factory.name() or byTypeUrl already
contains any typeUrl from factory.typeUrls(), throw an IllegalStateException (or
a clear RuntimeException) that includes the conflicting name/typeUrl and the
existing and new factory implementations to fail fast rather than silently
overwriting.
In `@xds/src/main/java/com/linecorp/armeria/xds/XdsResourceValidator.java`:
- Around line 76-82: In XdsResourceValidator.unpack(Any message, Class<T> clazz)
add explicit null checks using Objects.requireNonNull for both parameters
(message and clazz) at the top of the method, and when throwing the
IllegalArgumentException on InvalidProtocolBufferException include richer
context (e.g., clazz.getName() and message.getTypeUrl() or other identifying
info) in the exception message so the error clearly identifies what failed to
unpack; update the message passed to Objects.requireNonNull to be a meaningful
description like "message must not be null" / "clazz must not be null".
In `@xds/src/test/java/com/linecorp/armeria/xds/SubscriberStorageTest.java`:
- Around line 68-84: The CapturingWatcher class has plain fields missingType and
missingName that are written in onResourceDoesNotExist (event-loop thread) and
read by Awaitility/test thread, which is racy; make these cross-thread safe by
changing the fields in CapturingWatcher to have proper concurrency semantics
(e.g., declare missingType as volatile XdsType missingType and missingName as
volatile String missingName, or use
AtomicReference<XdsType>/AtomicReference<String>) so that writes in
onResourceDoesNotExist establish a happens-before edge and the test reads see
the updated values.
In `@xds/src/test/java/com/linecorp/armeria/xds/XdsClientIntegrationTest.java`:
- Around line 94-100: The test currently calls
watcher.pollChanged(ClusterSnapshot.class) once, which only consumes a single
queued event and can leave older intermediate ClusterSnapshot events in
watcher.events(), causing flakiness; replace the single poll with a loop that
repeatedly polls/consumes ClusterSnapshot events from the watcher until none
remain, keeping the last non-null ClusterSnapshot as the snapshot to assert
against (still using ClusterSnapshot and watcher.pollChanged/ watcher.events()
APIs), and then assert that the last snapshot.xdsResource().resource() equals
the expected cluster; apply the same draining change to the other similar blocks
that use watcher.pollChanged(ClusterSnapshot.class) (the blocks corresponding to
the other occurrences mentioned).
---
Outside diff comments:
In `@xds/src/main/java/com/linecorp/armeria/xds/XdsStreamSubscriber.java`:
- Around line 56-65: restartTimer() can schedule multiple pending timers because
it doesn't cancel any previously scheduled initialAbsentFuture; modify
restartTimer() to check initialAbsentFuture and cancel it (e.g.,
initialAbsentFuture.cancel(false)) if it's non-null and not done before
assigning a new scheduled future on eventLoop, preserving existing checks for
enableAbsentOnTimeout and retaining the same behavior that clears
initialAbsentFuture and calls onAbsent() when the timeout fires.
---
Nitpick comments:
In
`@it/xds-client/src/test/java/com/linecorp/armeria/xds/it/DeltaXdsPreprocessorTest.java`:
- Around line 93-95: Make the field declaration for the EventLoopExtension
consistent by adding the final modifier: change the declaration of the static
field eventLoop (type EventLoopExtension) in DeltaXdsPreprocessorTest to be
static final so it matches the other `@RegisterExtension` fields in the class.
In
`@it/xds-client/src/test/java/com/linecorp/armeria/xds/it/XdsControlPlaneErrorHandlingTest.java`:
- Around line 90-92: The static shared state (version, cache, nackTracker) can
leak between parameterized test runs; fix by ensuring isolation: either make
version and cache instance fields instead of static, or reinitialize them in the
test setUp method (e.g., version = new AtomicLong(); cache = new
SimpleCache<>(node -> GROUP); and call nackTracker.reset() as already done) so
each test gets fresh instances; update references to version/cache accordingly
and keep nackTracker.reset() in setUp to guarantee a clean state before each
test.
In `@xds/src/main/java/com/linecorp/armeria/xds/CompositeXdsStream.java`:
- Around line 28-38: The constructor of CompositeXdsStream accepts a
streamSupplier without guaranteeing distinct instances per XdsType; to fix,
update the contract and add identity-based deduplication in close(): in
CompositeXdsStream.close() use an identity-based Set (e.g.,
Collections.newSetFromMap(new IdentityHashMap<>())) to track seen XdsStream
instances and call close() only once per unique instance; mention in the
constructor/Javadoc that suppliers should preferably return distinct instances
but the close() protects against accidental reuse of the same instance across
types.
In `@xds/src/main/java/com/linecorp/armeria/xds/ListenerXdsResource.java`:
- Around line 116-121: Add Javadoc to the downstreamFilters() accessor in
ListenerXdsResource describing what it returns and any expectations for callers:
state that it returns the pre-resolved downstream XdsHttpFilter instances,
whether the returned List is mutable/immutable and thread-safety assumptions,
include an `@return` tag, and mention nullability (never null) or alternative
behavior; keep the method package-private but document that it may be made
public in the future so callers understand its contract.
In `@xds/src/main/java/com/linecorp/armeria/xds/SecretXdsResource.java`:
- Around line 41-43: Add a defensive null-check in the SecretXdsResource
constructor: use Objects.requireNonNull on the secret parameter (in
SecretXdsResource(Secret secret, String version, long revision)) and provide a
clear message (e.g., "secret") so construction fails fast; also ensure
java.util.Objects is imported if not already and then assign the checked value
to the instance field 'secret'.
In `@xds/src/main/java/com/linecorp/armeria/xds/XdsUnpackUtil.java`:
- Around line 33-45: In unpackConnectionManager(Listener listener,
XdsExtensionRegistry registry) replace the reliance on a Java assert for the
HttpConnectionManagerFactory lookup with an explicit null-check and fail-fast
behavior: after calling registry.queryByTypeUrl(...,
HttpConnectionManagerFactory.class) verify the returned factory is non-null
(e.g. Objects.requireNonNull or throw new IllegalStateException with a clear
message referencing the typeUrl and listener) before invoking
factory.create(apiListener, registry.validator()); this ensures consistent
runtime behavior when HttpConnectionManagerFactory is missing.
🪄 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: 72f95b20-1467-4d98-b7f9-7b3a38c1b8bf
📒 Files selected for processing (67)
it/xds-client/src/test/java/com/linecorp/armeria/xds/it/ConfigSourceLifecycleObserverTest.javait/xds-client/src/test/java/com/linecorp/armeria/xds/it/DeltaXdsPreprocessorTest.javait/xds-client/src/test/java/com/linecorp/armeria/xds/it/DeltaXdsResourceWatcherTest.javait/xds-client/src/test/java/com/linecorp/armeria/xds/it/XdsControlPlaneErrorHandlingTest.javait/xds-client/src/test/java/com/linecorp/armeria/xds/it/XdsControlPlaneMatrixTest.javatesting-internal/src/main/java/com/linecorp/armeria/internal/testing/InternalTestingBlockHoundIntegration.javaxds/src/main/java/com/linecorp/armeria/xds/AbstractXdsResource.javaxds/src/main/java/com/linecorp/armeria/xds/AdsXdsStream.javaxds/src/main/java/com/linecorp/armeria/xds/ClusterResourceParser.javaxds/src/main/java/com/linecorp/armeria/xds/ClusterXdsResource.javaxds/src/main/java/com/linecorp/armeria/xds/CompositeXdsStream.javaxds/src/main/java/com/linecorp/armeria/xds/ConfigSourceClient.javaxds/src/main/java/com/linecorp/armeria/xds/ConfigSourceLifecycleObserver.javaxds/src/main/java/com/linecorp/armeria/xds/ControlPlaneClientManager.javaxds/src/main/java/com/linecorp/armeria/xds/DefaultConfigSourceLifecycleObserver.javaxds/src/main/java/com/linecorp/armeria/xds/DefaultResponseHandler.javaxds/src/main/java/com/linecorp/armeria/xds/DefaultSubscriptionContext.javaxds/src/main/java/com/linecorp/armeria/xds/DeltaActualStream.javaxds/src/main/java/com/linecorp/armeria/xds/DeltaDiscoveryStub.javaxds/src/main/java/com/linecorp/armeria/xds/EndpointResourceParser.javaxds/src/main/java/com/linecorp/armeria/xds/EndpointXdsResource.javaxds/src/main/java/com/linecorp/armeria/xds/FilterUtil.javaxds/src/main/java/com/linecorp/armeria/xds/HttpConnectionManagerFactory.javaxds/src/main/java/com/linecorp/armeria/xds/ListenerManager.javaxds/src/main/java/com/linecorp/armeria/xds/ListenerResourceParser.javaxds/src/main/java/com/linecorp/armeria/xds/ListenerSnapshot.javaxds/src/main/java/com/linecorp/armeria/xds/ListenerXdsResource.javaxds/src/main/java/com/linecorp/armeria/xds/RawBufferTransportSocketFactory.javaxds/src/main/java/com/linecorp/armeria/xds/ResourceParser.javaxds/src/main/java/com/linecorp/armeria/xds/ResourceStateStore.javaxds/src/main/java/com/linecorp/armeria/xds/RouteEntry.javaxds/src/main/java/com/linecorp/armeria/xds/RouteResourceParser.javaxds/src/main/java/com/linecorp/armeria/xds/RouteStream.javaxds/src/main/java/com/linecorp/armeria/xds/RouteXdsResource.javaxds/src/main/java/com/linecorp/armeria/xds/SecretResourceParser.javaxds/src/main/java/com/linecorp/armeria/xds/SecretXdsResource.javaxds/src/main/java/com/linecorp/armeria/xds/SotwActualStream.javaxds/src/main/java/com/linecorp/armeria/xds/SotwXdsStream.javaxds/src/main/java/com/linecorp/armeria/xds/StateCoordinator.javaxds/src/main/java/com/linecorp/armeria/xds/SubscriberStorage.javaxds/src/main/java/com/linecorp/armeria/xds/SubscriptionContext.javaxds/src/main/java/com/linecorp/armeria/xds/TransportSocketFactory.javaxds/src/main/java/com/linecorp/armeria/xds/TransportSocketStream.javaxds/src/main/java/com/linecorp/armeria/xds/UpstreamTlsTransportSocketFactory.javaxds/src/main/java/com/linecorp/armeria/xds/VirtualHostXdsResource.javaxds/src/main/java/com/linecorp/armeria/xds/XdsBootstrapImpl.javaxds/src/main/java/com/linecorp/armeria/xds/XdsConverterUtil.javaxds/src/main/java/com/linecorp/armeria/xds/XdsExtensionFactory.javaxds/src/main/java/com/linecorp/armeria/xds/XdsExtensionRegistry.javaxds/src/main/java/com/linecorp/armeria/xds/XdsResource.javaxds/src/main/java/com/linecorp/armeria/xds/XdsResourceException.javaxds/src/main/java/com/linecorp/armeria/xds/XdsResourceValidator.javaxds/src/main/java/com/linecorp/armeria/xds/XdsResponseHandler.javaxds/src/main/java/com/linecorp/armeria/xds/XdsStreamSubscriber.javaxds/src/main/java/com/linecorp/armeria/xds/XdsUnpackUtil.javaxds/src/main/java/com/linecorp/armeria/xds/XdsValidatorIndexRegistry.javaxds/src/main/java/com/linecorp/armeria/xds/client/endpoint/RouterFilterFactory.javaxds/src/main/java/com/linecorp/armeria/xds/filter/HttpFilterFactory.javaxds/src/main/java/com/linecorp/armeria/xds/filter/HttpFilterFactoryRegistry.javaxds/src/test/java/com/linecorp/armeria/xds/SotwXdsStreamTest.javaxds/src/test/java/com/linecorp/armeria/xds/StateCoordinatorTest.javaxds/src/test/java/com/linecorp/armeria/xds/SubscriberStorageTest.javaxds/src/test/java/com/linecorp/armeria/xds/TestResourceWatcher.javaxds/src/test/java/com/linecorp/armeria/xds/XdsClientIntegrationTest.javaxds/src/test/java/com/linecorp/armeria/xds/XdsExtensionRegistryTest.javaxds/src/test/java/com/linecorp/armeria/xds/XdsResourceValidatorTest.javaxds/src/test/java/com/linecorp/armeria/xds/XdsValidatorIndexRegistryTest.java
💤 Files with no reviewable changes (8)
- xds/src/test/java/com/linecorp/armeria/xds/XdsValidatorIndexRegistryTest.java
- xds/src/main/java/com/linecorp/armeria/xds/filter/HttpFilterFactoryRegistry.java
- xds/src/main/java/com/linecorp/armeria/xds/DefaultResponseHandler.java
- xds/src/main/java/com/linecorp/armeria/xds/XdsConverterUtil.java
- xds/src/main/java/com/linecorp/armeria/xds/XdsValidatorIndexRegistry.java
- xds/src/main/java/com/linecorp/armeria/xds/XdsResponseHandler.java
- xds/src/main/java/com/linecorp/armeria/xds/SotwXdsStream.java
- xds/src/test/java/com/linecorp/armeria/xds/SotwXdsStreamTest.java
There was a problem hiding this comment.
🧹 Nitpick comments (1)
xds/src/main/java/com/linecorp/armeria/xds/XdsExtensionRegistry.java (1)
51-52: Remove misleading@VisibleForTestingannotation fromof(...)method.Line 52: This method is called from
XdsBootstrapImpl(production code) during normal bootstrap initialization, not just from tests. The@VisibleForTestingannotation contradicts this usage and should be removed.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@xds/src/main/java/com/linecorp/armeria/xds/XdsExtensionRegistry.java` around lines 51 - 52, The `@VisibleForTesting` annotation on the static method XdsExtensionRegistry.of(XdsResourceValidator) is misleading because the method is used by production code (XdsBootstrapImpl); remove the `@VisibleForTesting` annotation from the of(...) method in class XdsExtensionRegistry so its visibility correctly reflects production usage and no test-only marker remains.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@xds/src/main/java/com/linecorp/armeria/xds/XdsExtensionRegistry.java`:
- Around line 51-52: The `@VisibleForTesting` annotation on the static method
XdsExtensionRegistry.of(XdsResourceValidator) is misleading because the method
is used by production code (XdsBootstrapImpl); remove the `@VisibleForTesting`
annotation from the of(...) method in class XdsExtensionRegistry so its
visibility correctly reflects production usage and no test-only marker remains.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 326d66f6-6d7d-42a2-a753-99c8ebafeae3
📒 Files selected for processing (1)
xds/src/main/java/com/linecorp/armeria/xds/XdsExtensionRegistry.java
d74d615 to
6204fae
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
xds/src/main/java/com/linecorp/armeria/xds/XdsResourceValidator.java (1)
34-50: Javadoc says "exactly two levels" but describes three.The class doc states validation occurs at "exactly two levels" (static, dynamic), then adds
unpack(Any, Class)as an "In addition" point — which is itself a third validation site per the PR summary ("Validation occurs at three points: bootstrap construction, dynamic resource parsing, and Any unpacking"). Consider rewording for consistency so readers don't miss theAny-unpacking validation entry point.♻️ Suggested wording
- * <p>Validation is performed at exactly two levels: + * <p>Validation is performed at the following three points: * <ul> - * <li><b>Static resources</b> — {`@link` XdsBootstrapImpl} calls {`@link` `#assertValid`(Object)} once + * <li><b>Static resources</b> — {`@link` XdsBootstrapImpl} calls {`@link` `#assertValid`(Object)} once @@ - * resources (NACK'd back to the control plane).</li> + * resources (NACK'd back to the control plane).</li> + * <li><b>{`@code` Any}-typed fields</b> — {`@link` `#unpack`(Any, Class)} validates each unpacked + * message because protobuf treats {`@code` Any} as an opaque blob that parent-level validation + * cannot recurse into.</li> * </ul> - * - * <p>In addition, {`@link` `#unpack`(Any, Class)} is used for {`@code` google.protobuf.Any}-typed - * fields that cannot be validated by parent recursion (since {`@code` Any} is opaque to protobuf - * field traversal).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@xds/src/main/java/com/linecorp/armeria/xds/XdsResourceValidator.java` around lines 34 - 50, The doc comment claims validation happens "exactly two levels" but then adds a third point for Any unpacking; update the class Javadoc for XdsResourceValidator to consistently describe three validation sites: bootstrap construction (XdsBootstrapImpl calling assertValid(Object)), dynamic resource parsing (assertValid(Object) on DiscoveryResponse unpacked resources), and Any-type field handling (unpack(Any, Class) for google.protobuf.Any fields). Mention all three explicitly and remove the "exactly two levels" phrase so readers see the Any-unpacking step as a first-class validation site.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@xds/src/main/java/com/linecorp/armeria/xds/UpstreamTlsTransportSocketFactory.java`:
- Around line 94-109: The code currently picks only the first entry via get(0)
in the branches that build tlsCertStream and silently drops additional entries;
update the branches in UpstreamTlsTransportSocketFactory that reference
commonTlsContext.getTlsCertificatesList() and
commonTlsContext.getTlsCertificateSdsSecretConfigsList() to detect when size() >
1 and surface that fact (either by logging a warning with the list size and
which entry is being used, e.g., via the existing logger, or by failing fast
with an exception), and keep the existing behavior of using the first entry
(TlsCertificateStream/SecretStream + map(Optional::of)) unless you explicitly
choose to iterate/merge—ensure both branches that use get(0) are changed
consistently to emit the warning or reject on extra entries.
In `@xds/src/main/java/com/linecorp/armeria/xds/XdsExtensionRegistry.java`:
- Around line 105-107: The `@VisibleForTesting` annotation on queryByTypeUrl is
misleading because it's called from production code (e.g.,
XdsUnpackUtil.unpackConnectionManager uses registry.queryByTypeUrl(...)); remove
the `@VisibleForTesting` annotation from the method declaration in
XdsExtensionRegistry and, if needed for visibility, ensure the method's access
modifier (queryByTypeUrl) remains appropriate for its callers (adjust to
package-public or public to match usage) and update any method javadoc to
reflect that it is used by production code.
- Around line 51-69: Remove the `@VisibleForTesting` annotation from the static
factory method XdsExtensionRegistry.of(XdsResourceValidator) because it is used
as the primary production construction entry point (see XdsBootstrapImpl usage);
edit the XdsExtensionRegistry class to delete the `@VisibleForTesting` import and
the annotation placed on the of(...) method so the method is no longer marked
test-only.
---
Nitpick comments:
In `@xds/src/main/java/com/linecorp/armeria/xds/XdsResourceValidator.java`:
- Around line 34-50: The doc comment claims validation happens "exactly two
levels" but then adds a third point for Any unpacking; update the class Javadoc
for XdsResourceValidator to consistently describe three validation sites:
bootstrap construction (XdsBootstrapImpl calling assertValid(Object)), dynamic
resource parsing (assertValid(Object) on DiscoveryResponse unpacked resources),
and Any-type field handling (unpack(Any, Class) for google.protobuf.Any fields).
Mention all three explicitly and remove the "exactly two levels" phrase so
readers see the Any-unpacking step as a first-class validation site.
🪄 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: 04ec1849-c8e8-4346-bb16-9a0748e330e1
📒 Files selected for processing (45)
xds/src/main/java/com/linecorp/armeria/xds/ClusterResourceParser.javaxds/src/main/java/com/linecorp/armeria/xds/ClusterXdsResource.javaxds/src/main/java/com/linecorp/armeria/xds/ConfigSourceClient.javaxds/src/main/java/com/linecorp/armeria/xds/ControlPlaneClientManager.javaxds/src/main/java/com/linecorp/armeria/xds/DefaultSubscriptionContext.javaxds/src/main/java/com/linecorp/armeria/xds/DeltaActualStream.javaxds/src/main/java/com/linecorp/armeria/xds/EndpointResourceParser.javaxds/src/main/java/com/linecorp/armeria/xds/EndpointXdsResource.javaxds/src/main/java/com/linecorp/armeria/xds/FilterUtil.javaxds/src/main/java/com/linecorp/armeria/xds/HttpConnectionManagerFactory.javaxds/src/main/java/com/linecorp/armeria/xds/ListenerManager.javaxds/src/main/java/com/linecorp/armeria/xds/ListenerResourceParser.javaxds/src/main/java/com/linecorp/armeria/xds/ListenerSnapshot.javaxds/src/main/java/com/linecorp/armeria/xds/ListenerXdsResource.javaxds/src/main/java/com/linecorp/armeria/xds/RawBufferTransportSocketFactory.javaxds/src/main/java/com/linecorp/armeria/xds/ResourceParser.javaxds/src/main/java/com/linecorp/armeria/xds/RouteEntry.javaxds/src/main/java/com/linecorp/armeria/xds/RouteResourceParser.javaxds/src/main/java/com/linecorp/armeria/xds/RouteStream.javaxds/src/main/java/com/linecorp/armeria/xds/RouteXdsResource.javaxds/src/main/java/com/linecorp/armeria/xds/SecretResourceParser.javaxds/src/main/java/com/linecorp/armeria/xds/SecretXdsResource.javaxds/src/main/java/com/linecorp/armeria/xds/SotwActualStream.javaxds/src/main/java/com/linecorp/armeria/xds/StateCoordinator.javaxds/src/main/java/com/linecorp/armeria/xds/SubscriptionContext.javaxds/src/main/java/com/linecorp/armeria/xds/TransportSocketFactory.javaxds/src/main/java/com/linecorp/armeria/xds/TransportSocketStream.javaxds/src/main/java/com/linecorp/armeria/xds/UpstreamTlsTransportSocketFactory.javaxds/src/main/java/com/linecorp/armeria/xds/VirtualHostXdsResource.javaxds/src/main/java/com/linecorp/armeria/xds/XdsBootstrapImpl.javaxds/src/main/java/com/linecorp/armeria/xds/XdsConverterUtil.javaxds/src/main/java/com/linecorp/armeria/xds/XdsExtensionFactory.javaxds/src/main/java/com/linecorp/armeria/xds/XdsExtensionRegistry.javaxds/src/main/java/com/linecorp/armeria/xds/XdsResourceValidator.javaxds/src/main/java/com/linecorp/armeria/xds/XdsStreamSubscriber.javaxds/src/main/java/com/linecorp/armeria/xds/XdsUnpackUtil.javaxds/src/main/java/com/linecorp/armeria/xds/XdsValidatorIndexRegistry.javaxds/src/main/java/com/linecorp/armeria/xds/client/endpoint/RouterFilterFactory.javaxds/src/main/java/com/linecorp/armeria/xds/filter/HttpFilterFactory.javaxds/src/main/java/com/linecorp/armeria/xds/filter/HttpFilterFactoryRegistry.javaxds/src/test/java/com/linecorp/armeria/xds/StateCoordinatorTest.javaxds/src/test/java/com/linecorp/armeria/xds/SubscriberStorageTest.javaxds/src/test/java/com/linecorp/armeria/xds/XdsExtensionRegistryTest.javaxds/src/test/java/com/linecorp/armeria/xds/XdsResourceValidatorTest.javaxds/src/test/java/com/linecorp/armeria/xds/XdsValidatorIndexRegistryTest.java
💤 Files with no reviewable changes (9)
- xds/src/main/java/com/linecorp/armeria/xds/RouteXdsResource.java
- xds/src/test/java/com/linecorp/armeria/xds/XdsValidatorIndexRegistryTest.java
- xds/src/main/java/com/linecorp/armeria/xds/XdsConverterUtil.java
- xds/src/main/java/com/linecorp/armeria/xds/SecretXdsResource.java
- xds/src/main/java/com/linecorp/armeria/xds/XdsValidatorIndexRegistry.java
- xds/src/main/java/com/linecorp/armeria/xds/filter/HttpFilterFactoryRegistry.java
- xds/src/main/java/com/linecorp/armeria/xds/EndpointXdsResource.java
- xds/src/main/java/com/linecorp/armeria/xds/ClusterXdsResource.java
- xds/src/main/java/com/linecorp/armeria/xds/VirtualHostXdsResource.java
✅ Files skipped from review due to trivial changes (2)
- xds/src/main/java/com/linecorp/armeria/xds/XdsStreamSubscriber.java
- xds/src/main/java/com/linecorp/armeria/xds/XdsExtensionFactory.java
🚧 Files skipped from review as they are similar to previous changes (18)
- xds/src/main/java/com/linecorp/armeria/xds/SubscriptionContext.java
- xds/src/main/java/com/linecorp/armeria/xds/ClusterResourceParser.java
- xds/src/main/java/com/linecorp/armeria/xds/ConfigSourceClient.java
- xds/src/main/java/com/linecorp/armeria/xds/RouteStream.java
- xds/src/main/java/com/linecorp/armeria/xds/ListenerSnapshot.java
- xds/src/main/java/com/linecorp/armeria/xds/EndpointResourceParser.java
- xds/src/main/java/com/linecorp/armeria/xds/ControlPlaneClientManager.java
- xds/src/main/java/com/linecorp/armeria/xds/RawBufferTransportSocketFactory.java
- xds/src/main/java/com/linecorp/armeria/xds/DefaultSubscriptionContext.java
- xds/src/main/java/com/linecorp/armeria/xds/RouteResourceParser.java
- xds/src/test/java/com/linecorp/armeria/xds/StateCoordinatorTest.java
- xds/src/main/java/com/linecorp/armeria/xds/filter/HttpFilterFactory.java
- xds/src/test/java/com/linecorp/armeria/xds/XdsExtensionRegistryTest.java
- xds/src/main/java/com/linecorp/armeria/xds/ResourceParser.java
- xds/src/main/java/com/linecorp/armeria/xds/XdsBootstrapImpl.java
- xds/src/test/java/com/linecorp/armeria/xds/XdsResourceValidatorTest.java
- xds/src/main/java/com/linecorp/armeria/xds/XdsUnpackUtil.java
- xds/src/main/java/com/linecorp/armeria/xds/TransportSocketStream.java
e7e4967 to
923062d
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (1)
xds/src/main/java/com/linecorp/armeria/xds/FilterUtil.java (1)
85-107: Consider moving theConfigTypeCasecheck before the registry lookup.Two minor consequences of performing the
checkArgumentafterresolveInstancehas looked up and (potentially) returnednullfor an optional filter:
- For an optional filter that uses the deprecated
configvariant (neitherTYPED_CONFIGnorCONFIGTYPE_NOT_SET), the invalid config type is silently ignored rather than rejected.- For a non-optional filter whose factory is unknown and whose
ConfigTypeCaseis invalid, the user sees "Unknown HTTP filter..." instead of the more precise "Only 'typed_config' is supported" message.Neither is a correctness bug, but fail-fast validation of the config type case tends to produce clearer error messages. Not a blocker.
♻️ Proposed reordering
static XdsHttpFilter resolveInstance( XdsExtensionRegistry extensionRegistry, HttpFilter httpFilter, `@Nullable` Any perRouteConfig) { + checkArgument(httpFilter.getConfigTypeCase() == ConfigTypeCase.TYPED_CONFIG || + httpFilter.getConfigTypeCase() == ConfigTypeCase.CONFIGTYPE_NOT_SET, + "Only 'typed_config' is supported, but '%s' was supplied", + httpFilter.getConfigTypeCase()); final Any defaultConfig = httpFilter.getTypedConfig(); final Any filterConfig = perRouteConfig != null ? perRouteConfig : defaultConfig; final HttpFilterFactory factory = extensionRegistry.query( filterConfig, httpFilter.getName(), HttpFilterFactory.class); if (factory == null) { if (!httpFilter.getIsOptional()) { throw new IllegalArgumentException( "Unknown HTTP filter '" + httpFilter.getName() + "': no HttpFilterFactory registered. Register an " + "HttpFilterFactory implementation to handle this filter."); } return null; } - checkArgument(httpFilter.getConfigTypeCase() == ConfigTypeCase.TYPED_CONFIG || - httpFilter.getConfigTypeCase() == ConfigTypeCase.CONFIGTYPE_NOT_SET, - "Only 'typed_config' is supported, but '%s' was supplied", - httpFilter.getConfigTypeCase()); return factory.create(httpFilter, filterConfig, extensionRegistry.validator()); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@xds/src/main/java/com/linecorp/armeria/xds/FilterUtil.java` around lines 85 - 107, Move the ConfigTypeCase validation to the top of resolveInstance so the filter's config type is validated before querying the registry: call checkArgument(httpFilter.getConfigTypeCase() == ConfigTypeCase.TYPED_CONFIG || httpFilter.getConfigTypeCase() == ConfigTypeCase.CONFIGTYPE_NOT_SET, ...) immediately inside resolveInstance, then perform the extensionRegistry.query(filterConfig, httpFilter.getName(), HttpFilterFactory.class) lookup and the existing optional/unknown-factory handling, leaving factory.create(...) unchanged; this ensures invalid config types are rejected before extensionRegistry.query() and preserves behavior for optional filters and non-optional unknown factories.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@xds/src/main/java/com/linecorp/armeria/xds/FilterUtil.java`:
- Around line 85-107: Move the ConfigTypeCase validation to the top of
resolveInstance so the filter's config type is validated before querying the
registry: call checkArgument(httpFilter.getConfigTypeCase() ==
ConfigTypeCase.TYPED_CONFIG || httpFilter.getConfigTypeCase() ==
ConfigTypeCase.CONFIGTYPE_NOT_SET, ...) immediately inside resolveInstance, then
perform the extensionRegistry.query(filterConfig, httpFilter.getName(),
HttpFilterFactory.class) lookup and the existing optional/unknown-factory
handling, leaving factory.create(...) unchanged; this ensures invalid config
types are rejected before extensionRegistry.query() and preserves behavior for
optional filters and non-optional unknown factories.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: a9db1a73-0e88-479f-bc66-2220d274da2c
📒 Files selected for processing (12)
xds/src/main/java/com/linecorp/armeria/xds/FilterUtil.javaxds/src/main/java/com/linecorp/armeria/xds/ListenerManager.javaxds/src/main/java/com/linecorp/armeria/xds/ListenerResourceParser.javaxds/src/main/java/com/linecorp/armeria/xds/ListenerXdsResource.javaxds/src/main/java/com/linecorp/armeria/xds/RouteStream.javaxds/src/main/java/com/linecorp/armeria/xds/SotwActualStream.javaxds/src/main/java/com/linecorp/armeria/xds/XdsBootstrapImpl.javaxds/src/main/java/com/linecorp/armeria/xds/XdsExtensionFactory.javaxds/src/main/java/com/linecorp/armeria/xds/XdsExtensionRegistry.javaxds/src/main/java/com/linecorp/armeria/xds/XdsResourceValidator.javaxds/src/main/java/com/linecorp/armeria/xds/XdsStreamSubscriber.javaxds/src/test/java/com/linecorp/armeria/xds/SubscriberStorageTest.java
✅ Files skipped from review due to trivial changes (1)
- xds/src/main/java/com/linecorp/armeria/xds/XdsExtensionFactory.java
🚧 Files skipped from review as they are similar to previous changes (5)
- xds/src/main/java/com/linecorp/armeria/xds/ListenerManager.java
- xds/src/main/java/com/linecorp/armeria/xds/RouteStream.java
- xds/src/main/java/com/linecorp/armeria/xds/XdsExtensionRegistry.java
- xds/src/main/java/com/linecorp/armeria/xds/ListenerResourceParser.java
- xds/src/main/java/com/linecorp/armeria/xds/XdsBootstrapImpl.java
This PR is a subset of #6721
Motivation
The xDS module previously scattered extension handling (HTTP filters, transport sockets, network filters) across multiple static registries and utility classes (
HttpFilterFactoryRegistry,XdsValidatorIndexRegistry,XdsConverterUtil), each with inconsistent lookup and validation behavior. Validation was also performed redundantly — both during resource parsing and in individualXdsResourceconstructors — which could be expensive due to full message tree traversal.This PR introduces
XdsExtensionRegistryas the single, bootstrap-scoped registry that unifies extension factory lookup, protoAnyunpacking, and validation.Modifications
Introduce
XdsExtensionRegistryto unify unpack/validation behaviorXdsExtensionFactoryas the base interface for all extension factories, providingname()andtypeUrls()for dual-key resolution (type URL primary, extension name fallback).XdsExtensionRegistryas the central registry that discovers factories via SPI and built-in registration. Providesquery()for factory lookup,unpack()forAnyfields, andassertValid()for proto validation.XdsBootstrapImpland threaded through the pipeline viaSubscriptionContext, rather than being a static singleton. This allows users to inject custom factories via the bootstrap builder in the future.HttpFilterFactoryRegistryandXdsValidatorIndexRegistrywhich are now subsumed byXdsExtensionRegistry.Consolidate validation into three well-defined points
XdsResourceValidatorwhich delegates to the highest-priorityXdsValidatorIndexloaded via SPI. Validation now happens at exactly three points:Bootstrapproto is validated once at construction time.ResourceParser.parseResources()/parseDeltaResources().Anyfields are validated when unpacked viaregistry.unpack().ClusterXdsResource,ListenerXdsResource,EndpointXdsResource,RouteXdsResource,SecretXdsResource, andVirtualHostXdsResourceconstructors. This avoids redundant full-message-tree traversals.XdsConverterUtilwhose config source validation logic is subsumed by the centralized validation.HTTP filters now use the extension registry
HttpFilterFactorynow extendsXdsExtensionFactory;create()receivesXdsResourceValidatorfor config unpacking.RouterFilterFactoryimplementsname()/typeUrls()and now properly unpacks theRouterproto. ExtractedRouterXdsHttpFilteras a public inner class with arouter()getter.FilterUtillookups changed fromHttpFilterFactoryRegistrytoXdsExtensionRegistry.query().ListenerSnapshot(one per listener) toRouteEntry(one per route), so downstream filters receive per-routetyped_per_filter_configoverrides — matching Envoy's behavior of resolving the route before running the HCM filter chain.Transport sockets now use the extension registry
TransportSocketFactoryas the extension factory interface for transport sockets.UpstreamTlsTransportSocketFactorywhich encapsulates the TLS logic previously hardcoded inTransportSocketStream.RawBufferTransportSocketFactoryas a simple pass-through for plaintext transport sockets.TransportSocketStreamfrom monolithic TLS handling to a strategy pattern — queries the registry for the appropriateTransportSocketFactoryand delegates creation.Listener unpacking centralized via
XdsUnpackUtilXdsUnpackUtilto centralize unpacking ofHttpConnectionManagerfrom listener configs and downstream filter resolution.HttpConnectionManagerFactoryfor parsingHttpConnectionManagernetwork filter configs.ListenerResourceParser/ListenerXdsResource: Downstream filters are resolved at parse time viaXdsUnpackUtiland stored as a field, rather than computed on demand.Result
XdsExtensionRegistrywith dual-key lookup (type URL + name).